MacアプリでUPnPを使ってポートを開く

やりたいこと:

MacCocoaアプリで簡単にルータのポートを自動的に開きたい。

使うもの:

調べてみたら「TCMPortMapper」というフレームワークがあるそうです。UPnPかNAT-PMPに対応しているルータなら、とても簡単です。サンプルとしてGUIでポートを開くツール「PortMap」も上記からダウンロードできます。

コード:

アプリの起動時に

    TCMPortMapper *pm = [TCMPortMapper sharedInstance];
    [pm addPortMapping:
        [TCMPortMapping portMappingWithLocalPort:1234
                        desiredExternalPort:1234
                        transportProtocol:TCMPortMappingTransportProtocolTCP
                        userInfo:nil]];
    [pm start];

でポートを開き、アプリ終了時に NSApplicationのapplicationWillTerminate: で

    [[TCMPortMapper sharedInstance] stopBlocking];

と後片付けをすればOK。


インターフェースを切り替えたりしてネットワーク接続が変更されると自動的に再設定してくれたり、1234番ポートが衝突していると1235を使ったり、といったことも勝手にやってくれます。


簡単、便利。

shebangの怪

LinuxMac OS Xではshebangに書いたオプションの扱いが異なるらしい、という話。

ちょっと実験してみました。

#include <stdio.h>
main(int argc, char *argv[])
{
	int i;
	for (i = 0; i < argc; i++)
		printf("argv[%d] = %s\n", i, argv[i]);
}
#!./args a b c -d -e #abc
hoge

これで./test.shを実行してみる。まずLinuxでは、

argv[0] = ./args
argv[1] = a b c -d -e #abc
argv[2] = ./test.sh

という結果。引数が全て一つに纏められてしまっている。

一方Mac OS Xでは、

argv[0] = ./args
argv[1] = a
argv[2] = b
argv[3] = c
argv[4] = -d
argv[5] = -e
argv[6] = ./test.sh

という結果。引数は複数に区切られ、#以降は切り捨てられている。


以下にも解説がありますが、各OSでばらばらのようです。(なおMac OS Xの説明が上の実験結果と食い違っているようです。)
UNIXの部屋 コマンド:シェバング (*BSD/Linux)
いずれにせよ複数のオプションを書いてしまうと互換性がなくなるということですね。。。


perlrubyなどのインタプリタ型言語の場合、よくこういうオプションをshebangに書きますが、それがうまくいくのはshebangperl/rubyインタプリタが自分で読み取って動作を変えてくれているおかげだったのですね。


そういう賢い動作をしないコマンドの場合、そのコマンドをwrapするシェルスクリプトを書けば良いのですが、オプションだけのためにファイルをいちいち作るのは嫌なので、ファイルを一つで済ませられないか、と考えてみます。


標準入力から入力を食わせられる言語であれば、

#!/bin/sh
/usr/local/bin/some-interpreter -a -b -c <<EOF

%% 実際のコード

EOF

とhereドキュメントで流し込む方法が考えられます。
ただし、これだと 「some-interpreter -a -b -c スクリプト」として起動することはできません。


そこで、「 # 〜 」 も「 // 〜 」も1行コメント開始として扱ってくれる言語なら、

#!/bin/sh
//usr/local/bin/some-interpreter -a -b -c $0 ; exit

%% 実際のコード

という手もありかも。
//usr のようにディレクトリの区切りが複数になっても同じ意味であることを利用しています。
$0はシェルから見ると起動したファイル名になるので、それがスクリプトとしてオプション付きでインタプリタに引き渡されます。
後はインタプリタ側はshebangや//の行をコメントとして無視して実行してくれる、と。

これなら「some-interpreter -a -b -c スクリプト」としても動きます(当然オプションはコマンドラインで指定した方が有効なので、コマンドラインを追加したい時に便利)。


ちなみに、『「 # 〜 」 も「 // 〜 」も1行コメント開始として扱ってくれるがshebangのオプションは無視する言語』というのはSystemTapなんですが、オプションをいろいろ渡さなきゃならない (-D_HOGE_=1といったマクロ定義とか) ようなスクリプトを書いていると、テストするたびに毎回オプションを指定せねばならず、かつ時々オプションを変えたくなるので、デフォルト値をshebangに書いとけたらなあ、と考えていて思いついただけです。これはひどい

Growl用WebAPI提供サーバ

前のエントリで書いたGrowl用Web APIを単独で動くサーバにし、アイコンにも対応しました。
バックグラウンドで動かしておいて、

http://localhost:23080/title=たいとる&msg=ほげほげ&icon=http://url/〜.jpg

というURLにアクセスすると、Growlでメッセージが通知されます。デフォルトではlocalhostのみからアクセス可能です。しかもアイコン付き。一応MD5ハッシュファイル名でキャッシュします*1。前のエントリに書いたtwicli GrowlプラグインもURLを書き換えればそのまま使えます。
ソースは以下から。

*1:ハッシュ衝突なんてしないよねJK(ぉ

続きを読む

Growl用のWeb API

GrowlをWebアプリから叩くためのAPIを提供します。ローカルホスト上にCGIが動くWebサーバが必要です。

0. Growlをインストールし、システム環境設定>Growl>ネットワークで「通知される受信を聞く」をONにする。
1. CPANMac::Growlなど必要なライブラリをインストール。
2. 下記スクリプトを ~/Sites/growl.cgi 内におく。
3. http://localhost/~USERNAME/growl.cgi?title=タイトル&msg=メッセージ内容 にアクセスすると、通知が表示される。

localhost以外からでも何でも通知できてしまうので、Webサーバの公開先にはご注意を。

利用例としては、前のエントリのTwitterクライアントなんかがあります。

以下、スクリプト

#!/usr/bin/perl
use strict;
use Mac::Growl ':all';
use utf8;
use CGI;
my $q = new CGI;

my $name = "Message via Web API";
my $title = $q->param("title");
my $msg = $q->param("msg");

RegisterNotifications("Growl Web API", [$name], [$name]);
PostNotification("Growl Web API", $name, $title, $msg);

print "Content-type: text/javascript\n\n\"OK\";";

HTTP::Daemonに載せた方が良かったかな。
あと未だアイコンに対応していないので誰の発言か瞬時に認識できない…。

↑追記

デーモン化&アイコン表示に対応しました。次のエントリ参照。

VLCで動画をiPhone/touch用に変換するAppleScript

使い方

  1. VLCをインストール(適当なフォルダにコピー)。VLC-0.8.6iで動作確認しています。最新版だと動かないかも。
  2. 下記スクリプトを「スクリプトエディタ」に貼付けてアプリケーション形式で保存
  3. 変換したい動画ファイルをドラッグ&ドロップ


変換後、VLCが終了します。
変換結果はデスクトップに「元の名前.mp4」として保存されるので、あとはiTunesiPhoneやtouchに転送すればOK。
なお変換ログはデスクトップにconvert.logという名前で出力されます。エラー時はここをチェック。


なお下記はアスペクト比が16:9固定です。4:3などの動画の場合は、スクリプト中の width=640, height=320 部分を 480×360などに調整すると良いでしょう。あまり大きくすると再生できません(縦×横に上限があるらしい)。

on open source
	tell application "Finder" to set destname to name of file (source as string)
	if character -3 of destname is "." then
		set destname to characters 1 thru -4 of destname as string
	else if character -4 of destname is "." then
		set destname to characters 1 thru -5 of destname as string
	end if
	
	set cmd to "cd '" & (POSIX path of (path to application "VLC")) & "'/Contents/MacOS;" & " ./VLC -I dummy 'file://" & (POSIX path of source) & "' :sout='#transcode {vcodec=mp4v, vb=1024, width=640, height=320, acodec=mp4a, ab=128}:standard {mux=mp4, url='$HOME'/Desktop/" & destname & ".mp4, access=file}' vlc:quit >~/Desktop/convert.log 2>&1"
	--display dialog cmd
	do shell script cmd
end open

MacBook Pro + Linuxで3Dデスクトップ

こんな感じにしてみました。透明ターミナルの裏側はブラーまでかかってて妙に良い質感。しかもサクサク動く。さすがはGeForce 8600M。

iTunesiPod touchのCoverFlowのようなアニメーションでウィンドウを切り替えられたりも。

必要なもの:

  1. ネイティブで動作するFedora 9。id:NeoCat:20080420 辺りを参考に。
  2. nVidiaドライバ
  3. Compiz Fusion

下二つのインストール方法は以下のとおり。

続きを読む

AppleScriptでSpacesをコントロール

MacのSpaces(デスクトップを複数持つ機能)をAppleScriptで変更する方法。

tell application "System Events"
	tell spaces preferences of expose preferences
		(* 現在の設定を取得 *)
		set new_bindings to application bindings

		(* 特定のアプリケーションの設定を「デスクトップ1」に変更 *)
		set |com.apple.safari| of new_bindings to 1

		(* 設定を反映 *)
		set application bindings to new_bindings

	end tell
end tell

「全てのデスクトップ」にするには、上記の1の代わりに65544 ( 0x10008 ) を指定します。
com.apple.safariの部分は、アプリケーションのバンドルIDを全て小文字にしたものを使う模様。
一旦アプリを手動で登録してみて、application bindingsを取得してみるのが一番確実?