2011年2月23日水曜日

Cocoa ObjC Viewだけでリサイズ

透明ウィンドウで、Viewだけの状態でウィンドウごとリサイズできるか調査中。

昨日作った実験用プロジェクトで、あれこれ実験。まだ成果はなし。どーもViewのsetFrameがうまくいかない。

要は、Viewのサイズをかえたらウィンドウのサイズも連動してかえる、ということ。普通はウィンドウのサイズ変更がViewの大きさと連動するから、その逆。

簡単な方法としては、メニューかなにかでウィンドウを可視化してしまう、というのがあるわけだが、ここはやはりViewだけが見える状態でウィンドウごとリサイズできないかやってみようとしている。


ウィンドウは透明化して、リサイズのアンカーとタイトルバーだけが表示されいる状態。そこに半透明なViewを表示して、Viewの右隅にNSTrackingAreaを作っている。白い四角がtrackingarea。

trackingareaでマウスを左クリックするとサイズ変更、とやりたいわけだが、どーも納得いく動きにならない。なぜだろうかなあ。さらに明日以降研究する。

2011年2月22日火曜日

Cocoa ObjC NSGradient

NSBezierPathの中身をグラディーションで描画してみる。

まずはxcatsan師匠教えの通りにやってみる。

実験用プロジェクトをつくり、Viewに単純な四角を黒でぬりつぶす。

NSRect rect=NSMakeRect(100, 100, 100, 200);
NSBezierPath* path = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:2 yRadius:2];
[[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:1.0] set];
[path fill];
これで次のようになる。

この四角をグラデーションで塗りつぶす。

NSArray* colorArray = [NSArray arrayWithObjects:
         [NSColor colorWithDeviceWhite:1.0 alpha:1.0],
         [NSColor colorWithDeviceWhite:0.8 alpha:1.0],
         [NSColor colorWithDeviceWhite:0.4 alpha:1.0],
         [NSColor colorWithDeviceWhite:0.1 alpha:1.0],
         [NSColor colorWithDeviceWhite:0.0 alpha:1.0],
         nil];
NSGradient* gradient = [[NSGradient alloc] initWithColors:colorArray];
[gradient drawInBezierPath:path angle:270];



これは5階調もつけてしまった例。こんなにたくさん階調をつけなくてもそれらしくなる、ということが実験でわかったので、あとあと3階調に減らしている。しかし、簡単だなあ。NSColorの配列を渡してinitするだけでいいのか。drawInBezierPathしているけど、drawInRectの方が単純でいいのかな。

5階調のまま半透明なウィンドウ&Viewの上に描画してみる。


これを3階調に減らしてみる。


なるほど、これならすぐにSpinに組み込んでも大丈夫だろうということで、SpinのSpacesViewの描画部分に入れてみる。


現在activeなWorkspaceだけalphaが1.0でくどいので、alphaを落としてみる。


さらにグレイから黒へのグラデーションでに変更。


Spinへの組み込みはこんな感じのソースにしてある。
-(NSArray *)setColorArray:(BOOL)state{
 float alha;
 
 if (state==YES) alha=0.8;
 else alha=0.5;
  
  NSArray* colorArray = [NSArray arrayWithObjects:
          [NSColor colorWithDeviceWhite:0.4 alpha:alha],
          [NSColor colorWithDeviceWhite:0.1 alpha:alha],
          [NSColor colorWithDeviceWhite:0.0 alpha:alha],
          nil];
 return colorArray;
 
}
- (void)drawRect:(NSRect)dirtyRect {
NSBezierPath* path = [NSBezierPath bezierPathWithRoundedRect:[self bounds] xRadius:5 yRadius:5];
 NSArray* colorArray;
 if ([self active]==YES) {
  colorArray = [self setColorArray:YES];  
 }
 else{
  colorArray = [self setColorArray:NO]; 
 }
 
 NSGradient* gradient = [[NSGradient alloc] initWithColors:colorArray];
 [gradient drawInBezierPath:path angle:270];
}
見て目についてはもう少し研究したい。

2011年2月21日月曜日

Cocoa ObjC 試行錯誤のスタイル

xcatsan師匠のblogを読んでいて、とにかく参考になったのが「メインのプロジェクト」に取り込む機能をつくるため、試験プロジェクトをたくさん作る、というスタイル。

バージョン管理システムを使うのはいいとして、メインのプロジェクトにあれこれ書き散らかすと結局なにがなんだかわからなくなるので、試験的なプロジェクトを作ったほうがいい、とはわかっていたが、今まではなんとなくあれこれ書き散らかしていた。

cocoaの場合、試験的なプロジェクトを作ってちょっとした機能を試してみる、という作業が非常に楽にできるみたい。

グラディーションの実験も、新しく試験プロジェクトをひとつ作ったほうがコードの見通しもいいだろうと思っている。

OSXに乗り換えてよかったなー、とこのごろ強く思う。

Cocoa ObjC Spin(その3) removeFromSuperview

Spacesの環境設定が変わったとき、どのようにSpinの表示も変えるか、という話。Notificationがらみの部分は後日。(自分用の備忘録という性格が強いblogなので、誰も知りたくないようなメモが続く(^^;))

とりあえずSpacesの設定が変わったというNotificationを受け取ったら、Spacesの「行と桁」を取得しなおして、それに合わせてsubviewの数も変える。増えただけなら単純に増やしてaddSubviewする。

減った時は、「行と桁」をかけて最大Workspace数を得、「現在のsubviewの数」と比べて減らす。

NSArray *views=[self subviews];

if([views count]>0){
  if(spacesMax > [views count]){
   for(NSInteger i=[views count];i<spacesMax;i++){
    SpacesView *spview=[[SpacesView alloc] initWithFrame:rect spaceNo:i+1];
    //NSLog(@"add View:no %d",[spview spaceNo]);
    [self addSubview:spview];
    [spview release];
   }
  }else if(spacesMax < [views count]) {
   for(NSInteger i=[views count]-1;i>=spacesMax;i--){
    SpacesView *spview=[views objectAtIndex:i];
    //NSLog(@"remove View:no %d count:%d MAX:%d loop:i:%d",[spview spaceNo],[views count],spacesMax,i);
    [spview removeSelf];
   }
  }
  CGSWorkspace currentSpace=[self spaceNumber];
  for(NSInteger i=0;i<spacesMax;i++){
   if (i==currentSpace-1) {
    SpacesView *spview=[views objectAtIndex:i];
    [spview setActive:YES];
   }
  }
    
 }

相変わらず半角<がbloggerに怒られるので全角にしてある。うーん、下手なコードだなー。もう少しきれいに書けるなあ。似たような処理をまとめて他のメソッドにしてしまおう、明日あたり。setActiveは「現在のアクティブなWorkspace」を表すBOOL値。YESなら自分がアクティブ、ということで。

多少見た目もいじって現在はこんな感じ。

removeFromSuperviewは実際は「SpacesView」というクラス(subviewになってるクラス)のメソッドで記述している。removeするのをindexの大きい方から(つまり配列の後ろから)やっているので、MutableArrayの作り直しが発生していないようだ。ま、順調に動いている。Spacesの設定をいじってもほぼリアルタイムに反映できるようになった。


次は見た目。グラディーションを使えば少しは見映えがするはず。そう思って「cocoa グラディーション」とググったら、xcatsan師匠新cocoの最近の記事に行き着いた。なるほどー、こうやるのか。

あと、透明ウィンドウでの、半透明Viewを使ったリサイズ。まだまだ調べることがたくさんある。楽しいなあ。

2011年2月20日日曜日

OSX 使っているアプリをさらす

最近、XcodeとInterfaceBuilderとiTermとSimpleCapとFirefoxしか使っていない、と感じる。

OSXを使い始めて日が浅いので、どんなアプリをどんなふうに使うのかよくわかっていないと自分では思う。「ばりばりのOSX使い」の人たちはどんなアプリをつかってるんだろう。

現在、私のDockはこんな感じ。


もともと、WinでもUbuntuでもデスクトップ上にあれこれアイコンを置くのはきらい。Dockのような「ランチャにしてタスクスイッチャ」にしても、できるだけシンプルな表示になるよう心がけている。ブラウザが3つ並んでいるのはrails触っていた名残ですな。最近はEmacsも全然起動していないや。

さらにApplicationはこうなる。


アイコンから起動することの少ないアプリはappやdevというフォルダに収めている。これでもたくさんありすぎるかな、と思うんだけどどうなのかなあ。

合理的で使いやすいDockやApplicationの整理の仕方を解説したサイトはないものか・・。今度OSXの入門書を図書館で借りてみよう(^^;)。

Cocoa ObjC Spin(その2)

超小物アプリなので、大枠は本日できあがった。さんざん書き散らかした実験用コードのカットアンドペーストが多かったのですぐに基本的な部分はできあがったが・・・。

とりあえずの実行画面はこんな感じ。


現在アクティブなWorkspaceだけ色を変えて表示してみた。これをスクリーンのどこかにおいてマウスをころころするとなかなかいい具合。


Workspace1つにつき、SpacesViewと名付けたViewを1つ割り振っている。それをaddSubviewしている、と。Viewなのでイベント処理とかが楽にできるだろう、という目算。

現在のところの問題点は

Spacesの設定が変わったあとの処理

これはWarpのソースを読みながら該当する箇所をカットアンドペーストして、エラーがでないように修正して、という感じでけっこうさくさく実装できた。しかし、

a,設定が変わったら、いったん全部のsubviewを「remveFormSuperview」で削除する
b,それから新しく「Workspaceの総数」分のsubviewをaddする

という手順をとったところ、aの段階でものすごい時間がかかることが判明した。コンソール出力はこんな感じになる。

2011-02-20 21:15:36.032 Spin[7077:a0f] *** Collection was mutated while being enumerated.CFArray 0x100121540 [0x7fff70429ee0]{type = mutable-small, count = 7, values = (
0 : SpacesView: 0x1001276e0
1 : SpacesView: 0x102a13a10
2 : SpacesView: 0x102a13680
3 : SpacesView: 0x102a13ac0
4 : SpacesView: 0x10050d320
5 : SpacesView: 0x102700090
6 : SpacesView: 0x102a13e50
)}
2011-02-20 21:15:36.034 Spin[7077:a0f] Call ConfigChange!
2011-02-20 21:15:36.034 Spin[7077:a0f] remove No2
2011-02-20 21:15:36.035 Spin[7077:a0f] *** Collection was mutated while being enumerated.CFArray 0x100121540 [0x7fff70429ee0]{type = mutable-small, count = 6, values = (
0 : SpacesView: 0x102a13a10
1 : SpacesView: 0x102a13680
2 : SpacesView: 0x102a13ac0
3 : SpacesView: 0x10050d320
4 : SpacesView: 0x102700090
5 : SpacesView: 0x102a13e50
)}
2011-02-20 21:15:37.911 Spin[7077:a0f] Call ConfigChange!
2011-02-20 21:15:37.911 Spin[7077:a0f] remove No3
2011-02-20 21:15:37.911 Spin[7077:a0f] *** Collection was mutated while being enumerated.CFArray 0x100121540 [0x7fff70429ee0]{type = mutable-small, count = 5, values = (
0 : SpacesView: 0x102a13680
1 : SpacesView: 0x102a13ac0
2 : SpacesView: 0x10050d320
3 : SpacesView: 0x102700090
4 : SpacesView: 0x102a13e50
)}
(tagとみなされるので、引用中の半角<>は全部削除してあります)
1つのsubviewをremoveFromSuperviewするたびに、viewを収めていたMutableArrayが切り詰められている様子。ひとつ切り詰めるたびに1秒近くかかっている。

変だなあ、なにかおかしなことしているのかなあ。
時間はかかるけど、処理そのものはエラーがでるわけでもなく、時間がかかったあとは正常に動作する。こういうのがいちばん厄介だ。
一応明日以降の方針としては、「Workspaceが増えた場合は単純にsubviewを増やすだけ」、「Workspaceが減った場合は、減った分のsubviewだけ削除してあとは残す」という感じで実装してみる。Spacesの設定変更は滅多に行われないけれど、こういう部分がやはりプログラミングの肝、と自分では考える。ここらをていねいに作ってこそ、プログラミングの勉強になるんだな。

見た目(^^;)
すてきな見た目を作るにはどーしたらいいんだろう。現在ののっぺりした感じをもっとかっこよくするにはどうしたらいいのか研究する。

ウィンドウのサイズ変更
現在、ウィンドウそのものは非表示の透明にしている。当然サイズ変更はできない。サイズ変更したい時だけウィンドウ枠を表示するようにするか?(これは簡単。コンテキストメニューとかでそういう項目を作ればいいだけ)

それともViewを直接サイズ変更して、それに連動してウィンドウも大きさを変えるようにするか?(こっちは難しそう)

移動は[window setMovableByWindowBackground:YES];としているのでマウスでドラッグすれば大丈夫。

という感じで今日は目がしくしく痛くなるまでiMacの前でXcodeを触っていた。楽しいなあ。

2011年2月19日土曜日

Cocoa ObjC Spin開発開始

超小物アプリSpinの開発を開始した。

機能は2つ。

1,Spacesの設定を読み込んで、Workspaceの数だけ小さなViewを表示する。そのViewをクリックすると該当のWorkspaceに切り替わる
2,マウスホイールをころころするとWorkspaceが切り替わる

予想実行画面。


やたらとだだっ広いiMacのデスクトップを使っている、キーボードよりマウス派、というなんだかすごくニッチな人向けのアプリになると考える。ま、自分用ですな。

しかし作る上ですごくCocoaフレームワークの勉強になるな。

Cocoa ObjC 起動中のアプリをSpacesごとに表示する

起動しているアプリを、表示しているWorkspaceごとに区分けして表示してみる。

ウィンドウを一つしか開かないアプリなら話は簡単だけれど、たくさんウィンドウを開くアプリの扱いで悩む。

とりあえず、開いているウィンドウを「1つのアプリ」とみなして表示する。こんな感じ。


現在のXcode3.2.5だとやたらたくさんウィンドウを開くことになるので、画像のようなことになる。試しにWorkspace4にデベロッパドキュメントのウィンドウを開いている。これをクリックすると、残念ながら「Xcodeの現在のアクティブなウィンドウ」がアクティブになる。


dockの右クリックで開くメニューの、一番最初に表示されているウィンドウが開く、と。

それなら、同じアプリのウィンドウは1つのWorkspaceにつき1つだけ表示するようにしてみる。


これならまあまあ、というところか。Workspace4のXcodeのアイコンをクリックするとWorkspace2のXcodeがアクティブになるのは相変わらず。(これはきっとどこかにどうにかするプライベート関数があるのではないか、と思ってずっと探しているが、まだみつからない。特定のウィンドウを選択してアプリをアクティブにする・・・できないかなあ)

ここまでの問題点は

1,ウィンドウの選択ができない
2,「全てのWorkspaceに表示する」を設定しているアプリが拾えない
3,Finderのアクティブ化が想定したようにならない
4,最小化しているアプリを拾っていない

という感じ。1は今のところどーにもならない。2は本日気づいた。


iTermを「全ての操作スペース」で表示する、にしたところ、「起動中のアプリ」として拾えなくなった。

これは原因はわかっていた、CGWindowListCopyWindowInfo( kCGWindowListOptionAll , kCGNullWindowID)で全ウィンドウの情報を取得して、
if (CFDictionaryContainsKey(w, kCGWindowWorkspace)) {
とWorkspace番号をもっているアプリだけ拾っているから。「全ての操作スペースで・・・」を選択しているアプリは、Workspace番号を持っていない。


ちなみに画像はxcatsan師匠がCGWindowListCopyWindowInfoのサンプルとして作ったツール。私のために作ってくれた(^^;)ようなツールでめちゃ使えますな。

3のFinder関連は、「現在のWorkspaceにFinderのウィンドウがないときはメニューバーでFinderがアクティブになるだけ」という現象。Fiderで一つでもウィンドウが開いているなら、それがアクティブになってほしいのだけれど、残念ながらそうはならない。Workspace1にFinderのウィンドウがなくて、Workspace2にFinderのウィンドウが一つ開いている、という状態だとすると、Workspace2でFinderのアイコンをクリックすると開いているウィンドウがアクティブになるけれど、Workspace1だとメニューバーでFinderがアクティブになってしまう、という感じ。

OSX、けっこうクセがありますなあ。

というわけで、Workspaceの操作とタスクスイッチャをいっしょにするにはちょっと無理がある、という結論に傾きつつある。

ワークスペース切替器とタスクスイッチャは別物として作ったほうがいいようだ。

2011年2月18日金曜日

Cocoa ObjC MatrixなView

NSMatrixとは全然関係ありません。自分で作る、Matrixな表示のViewの話。

本日の成果はこんな感じ。


基本的なViewの上に、四角いViewを並べる、と。おそらく
1,MainController
2,MainView
3,SpacesView
4,RunAppView
というクラス構成と名前になると思われ、画像はそのうちMainViewとSpacesViewにあたる予定。
画像で16個ある四角がSpacesView、1つがSpacesのWorkspace1つを表している(予定)。
MainViewの大きさとWorkspaceの数に合わせてSpacesViewの大きさは自動で設定される。当然MainViewがリサイズされたらSpacesViewもリサイズ。MainViewの大きさが同じでWorkspaceが4つだとこうなる。


この感じだとこんなふうにリサイズするのが一般的だろう。


で、実際にはこの位置、このくらいのサイズで使うつもり。


スクリーンの右隅に置くのが自分としてはいいかなあ。
ウィンドウのどこでマウスホイールをころころしてもWorkspaceが切り替わり、SpacesViewのクリックで該当Workspaceへの切替、さらにSpacesViewに表示されているアプリのアイコンで、タスクスイッチ、という感じで機能をつけていく予定。
実際のSpacesViewのサイズ・表示位置決定部分のソースはたったこれだけ。

-(void)setSpaceFrameSize{
 NSRect rect;
 NSRect bound=[self bounds];
 rect.origin.x=VIEW_FRAME_SPACE;
 rect.origin.y=(bound.size.height / ROW)*(ROW-1)+VIEW_FRAME_SPACE;
 rect.size.width=(bound.size.width / COL)-(VIEW_FRAME_SPACE * 2);
 rect.size.height=(bound.size.height / ROW)-(VIEW_FRAME_SPACE * 2);
 NSArray *views=[self subviews];
 if([views count]>0){ 
  for(NSInteger j=0; j< ROW ;j++){
   for(NSInteger i =0;i<COL;i++){
    //NSLog(@"x:%f y:%f w:%f h:%f",rect.origin.x,rect.origin.y,rect.size.width,rect.size.height);
    FrameView *aFrame=[views objectAtIndex:i+(COL*j)];
    aFrame.frame=rect;
    rect.origin.x+=(VIEW_FRAME_SPACE *2)+rect.size.width;
    
   }
   rect.origin.y-=(rect.size.height+VIEW_FRAME_SPACE*2);
   rect.origin.x=VIEW_FRAME_SPACE;

  }
 }
}


(bloggerに文句を言われるのでひとつだけ全角の<を使っております)
ROW、COLは現在のところdefineした定数。本番ではちゃんとSpacesのRowとColになるでしょう。

*NIXのWindowManagerに付属する(というかワークスペース切替器はWindowManagerの基本的な機能の一部ですな)ワークスペース切替器だと、現在表示されているウィンドウをリアルタイムに表示できるものですが、そこまでやる気力は今のところ、ない。というか必要ないような気もするし。

全体的な設計を考えてつくり直していくので、まだまだ完成まで時間がかかりそう。

2011年2月17日木曜日

Cocoa ObjC Warpのソースを読む

Warp

Spaces.appをちょいとパワーアップするプリファレンスアプリ。具体的にはスクリーンの上下左右の縁にマウスのポインタをもっていくとWorkspaceをスイッチする。Workspaceのリアルタイムプレビューを表示する機能もある。

このアプリでマウスホイールころころが実現できるなら、わざわざ自分でつくろうとはしなかっただろう、と思う。その意味で、Cocoaに触り始める契機となった恩人のようのアプリかも。

ありがたいことにオープンソースなのでソースを一式いただいてよく参照している。xcatsan師匠と同じくらい私淑していると言っていい。Kent Sutherlandちゃんありがとう。

タスクスイッチャもどきの機能がけっこうめどがついたので、いよいよObjC版のSpinをつくろうかな、と思っているが、もう少しViewについて研究する必要を感じる。具体的には、

1,起動中のアプリを表示する「アイコンボタン風」View
2,それをWorkspaceごとに区分けして表示する「Workspace視覚化」View
3,さらに2をSpacesのWorkspaceの数だけ表示する「Spaces視覚化」View
4,以上3つを操作するためのcontroller

というふうに4つのクラスで構成しようと思っている。どのクラスにどんな機能をもたせて、どこまで関連させてどこをカプセル化するのか、なかなか決められない。

これはおそらくObjC、Xcodeによるプログラミング経験が少ないからと思われ、それならすでに完成しているプロジェクトのソースを読んで勉強しよう、と。

さらに
詳解 Objective-C 2.0 改訂版
荻原 剛志
ソフトバンククリエイティブ
売り上げランキング: 20298

この本の第17章をじっくり読もうと思う。やっぱり基礎って大切だよなー。デリゲートについてよくわかっていないのが最悪、と自分でも思うんだなー。

2011年2月16日水曜日

xcatsan師匠 CGWindowListCreateImage

xcatsan師匠にCGWindowListCreateImageのふるまいについて質問してみた。新Cocoaの日々で。

師匠、見ず知らずの素人の質問に親切にあれこれと答えてくれて感涙。いい人だ。

しかし師匠の教えてくれたとおりにCGWindowListCreateImageでいろいろやってみるものの、どーもうまくいかない。だいたいこういう時は自分のせいなので、もう少しがんばってやってみよう。

問題は CGWindowListCopyWindowInfoの引数なのか、CGWindowListCreateImageなのか、CGWindowListCreateImageFromArrayなのか、それともCFArrayRefの扱い方なのか・・・。

それと同時に、やっぱり他のアプリのウィンドウを選択してアクティブにする方法もしつこく調べている。dockをあれこれするプライベート関数がないかな、とか。

実際に

meeu labs

というサイトで、_CoreDockというプレフィックスがつくプライベート関数があることを知る。引数も戻り値もわからないんだけど、

_CoreDockGetProcessWindow

とかいう関数もあるみたい。

こういう隠されたものを探っていくおもしろさってたしかにあるけれど、でもオープンソースならなにもかも白日のもとにさらされて、みんなで楽しもうぜ、ということになってるわけで、やっぱりそっちのほうがいいなあ。

Cocoa ObjC タスクスイッチャもどき(3)他のアプリの起動、終了を感知する

タスクスイッチャなので、自分が起動している間に他のアプリが起動されたり終了されたりしたら、ちゃんとそれを感知しないと話にならない。

デベロッパドキュメントを読むと、NSWorkspaceクラスのNotificationCenterで、NSWorkspaceDidLaunchApplicationNotification、NSWorkspaceDidTerminateApplicationNotificationをObserveすればいいことがわかった。さっそく実装。

NSWorkspace *ws=[NSWorkspace sharedWorkspace];
 [[ws notificationCenter] addObserver:self selector:@selector(appLaunched:) name:NSWorkspaceDidLaunchApplicationNotification object:ws];
 [[ws notificationCenter] addObserver:self selector:@selector(appTermed:) name:NSWorkspaceDidTerminateApplicationNotification object:ws];

Observerの設定はこれでOK。あとは@selectorを書くだけ。

-(void)appLaunched:(NSNotification *)notification{
 
 NSRunningApplication* app=[[notification userInfo] objectForKey:@"NSWorkspaceApplicationKey"];
 
 [self addAppAndButton:app]; 
}
addAppAndButtonは実際にaddViewをするメソッドで、たいしたことはしていない。

-(void)addAppAndButton:(NSRunningApplication *)app{
 
 if([app bundleIdentifier]!=[[NSRunningApplication currentApplication] bundleIdentifier]){
  NSRect viewFrame=NSMakeRect(0, 0, 10,10);
  SPApp *sp=[[SPApp alloc] initWithRunningApp:app];
  MyView *buttonView=[[MyView alloc] initWithFrame:viewFrame runApp:sp];
  [view addSubview:buttonView];
  [buttonView release];
  [sp release];
 }
}

自分自身を拾わないために

if([app bundleIdentifier]!=[[NSRunningApplication currentApplication] bundleIdentifier]){

としているが、こんなんでいいのかな。

他のアプリが起動すればaddSubViewすればいいわけだが、終了したらどうするか。こっちのほうが難しいような感じがしたが、

-(void)appTermed:(NSNotification *)notification{
 NSRunningApplication* app=[[notification userInfo] objectForKey:@"NSWorkspaceApplicationKey"];
 NSArray *views = [view subviews];
 for( MyView * a_view in views){
  if([[a_view _app] _pid]==[app processIdentifier]){
   [a_view removeFromSuperview];
  }
 }
 
}

意外とこれだけで大丈夫のようだ。[a_view removeFromSuperview];のあとに「break;」を入れたほうがよりいいかな。deallocがうまくいっているかどうか、調べる方法をみつけなければ(^^;)。

本日わかったことはNotificationで通知されるuserInfoからObjectw取り出すやりかた。rubyで言うところのHashと同じ、とは知っていたが、実際に自分でKeyからObjectを指定して、うまくいくかどうかちょっとどきどき。

subViewを削除するのも[a_view removeFromSuperview];だけでいいとは。拍子抜けするほど簡単だ。
もしかしてObjCって、rubyくらいユーザフレンドリなのか?

Cocoaフレームワークに触りだしてから、毎日が異常に充実している。家に帰ってきてiMac起動させるのが待ち遠しくて仕方がない。職場のPCがWindowsマシンでよかった。Macだったら仕事もしないでプログラミングしているところだった。(^^;)

2011年2月15日火曜日

Cocoa ObjC CGSSetUniversalOwner

CGSSetUniversalOwnerに行き着くまでの経過をメモ。

CGSPrivate.hの中にはウィンドウを操作するらしい関数がいくつかある。それが実際に動くか試してみた。

例えば

extern CGError CGSOrderWindow(const CGSConnection cid, const CGSWindow wid, CGSWindowOrderingMode place, CGSWindow relativeToWindowID /* can be NULL */);

これはきっとウィンドウを画面の一番上に持ってくる関数だろう。(いや、もしかしたら「常に一番上に表示」状態にするのかも・・・。ちゃんと試していないので実際は不明)

extern CGError CGSGetWindowAlpha(const CGSConnection cid, const CGSWindow wid, float* alpha);

これは明らかにウィンドウの透明度を設定する関数。どちらの関数も第1引数はCGSConnection cidで、これは次の関数で得ることのできる「現在のプロセスのコネクションID」。

/* Get the default connection for the current process. */
extern CGSConnection _CGSDefaultConnection(void);

自分のアプリケーションとCPUのプロセスを一意に結びつけている番号、みたいなものでしょうか。第2引数ではconst CGSWindow widを使うものが多い。これにはNSWindowクラスのプロパティwindowNumberを入れればいい。ただし、他のアプリのwinddowNumberを入れるとエラーになる。結局、const CGSConnection cidで操作できるのはほとんど自分のアプリのウィンドウだけ、らしい。

wid関連の操作でない時は自由にいろいろなウィンドウが操作できる。例えばこの関数。

extern CGError CGSMoveWorkspaceWindows(const CGSConnection connection, CGSWorkspace toWorkspace, CGSWorkspace fromWorkspace);

Workspace1にあるウィンドウを全部Workspace2へ、なんてことが簡単にできてしまう。

ここでこういう関数を発見。

extern CGError CGSGetConnectionIDForPSN(const CGSConnection cid, ProcessSerialNumber *psn, CGSConnection *targetConnection);

任意のアプリのConnectionIDを得ることができる?
それで得たIDを第1引数に指定すれば、任意のアプリの任意のウィンドウを操作できるのか?

というわけでProcessSerialNumberをデベロッパドキュメントで調べたら、

GetProcessForPID(pid ,&psn)
という関数があった。PIDがわかれば、そのProcessSerialNumberを得ることができる、と。そこでこんなふうに書いてみた。

 CGSConnection targetConnection;
 ProcessSerialNumber psn;
 
 GetProcessForPID([_app _pid] ,&psn);
 NSLog(@"%@:%d:%o",[_app _name],[_app _pid], psn.lowLongOfPSN);
 CGSGetConnectionIDForPSN(cid, &psn, &targetConnection);
 CGSOrderWindow(targetConnection, [sender tag], kCGSOrderAbove , 0);

第1引数を他のアプリのConnectionIDに替えてみたけれど・・やはりエラーになりました。orzだめか。

この他、ニセのNSEventをでっちあげてみる、とか、Notificationを投げてみる、とかやってみましたがことごとくだめ。

先日の3連休はこの試行錯誤でほとんどなくなりましたね。

cocoadev CGSPrivate.h

CGSPrivate.hはもともとこのフォーラムからコピペしてたわけだが、あとになって「Warp」のプロジェクトフォルダがコピーしたものを使っている。

本日、いろいろ調べているうちにまたまたこのフォーラムに行き着いて、ずーっとページをスクロールさせたらこんなことが書いてあるではないか。

So, I've tried writing a little utility that would change the alpha value of any window, but it only works for windows in my application... What can I do to make it work with any cocoa window?

おお、まさに私の知りたいことではないか!これに対するレスポンス。

You can't. A "special" connection to the WindowServer? is required to mess with other application's windows, and this connection is maintained exclusively by the Dock. If you quit the Dock you can take control of other windows... but good luck getting your users to ditch the dock :)

orz。そうか、そういうことだったのか。自分のアプリのウィンドウしか操作できないのか・・。
ま、納得しました。HyperDockが「ユニバーサルアクセス」を必要とする理由はこれか。Dockは不可触領域のようですな。
CGSSetUniversalOwnerのUniversalOwneが「maintained exclusively by the Dock」なんでしょう。

けっこうディープに調べた感を得ることができたここ数日だけど、結局やりたことができないとわかってフラストレーションが溜まってしまった。

次はGUI関連で実験していこう。まずは「Viewをリサイズする」かな。

Cocoa ObjC タスクスイッチャもどき(2)アプリのウィンドウ一覧を得る

タスクスイッチャまで
起動中のアプリをアイコンボタン風に表示して、そこからタスクを切り替える、というところまでは簡単にできる。

起動中のアプリを保存しておくデータ用のクラスは現在こんな感じ。

@interface SPApp : NSObject {
 NSString* _name;
 NSImage* _icon;
 pid_t _pid;
}
-(id)initWithRunningApp:(NSRunningApplication*)aApp;
@property(readonly) NSString* _name;
@property(readonly) NSImage* _icon;
@property(readonly) pid_t _pid;

@end

荻原本によるとインスタンス変数にアンダースコアつけるのはやめよ、ということなんだけど・・・。

で、このクラスにNSRunnnigApplicationのインスタンスからデータをコピーして、表示用のViewに渡す。

-(void)setRunningApp{
 NSWorkspace *ws=[NSWorkspace sharedWorkspace];
 NSArray* ra=[ws runningApplications];
 
 for(NSRunningApplication* app in ra){
  if([app activationPolicy]==NSApplicationActivationPolicyRegular){
   NSRect viewFrame=NSMakeRect(0, 0, 10,10);
   SPApp *sp=[[SPApp alloc] initWithRunningApp:app];
   MyView *buttonView=[[MyView alloc] initWithFrame:viewFrame runApp:sp];
   [view addSubview:buttonView];
   [buttonView release];
   [sp release];
  
  }
 }
 
}

クラス名がMyViewなのはご愛嬌。(^^;)
このままだとViewのrectが(0,0,10,10)なんだけど、これはViewの中でサイズを定義しなおしている。その辺はごちゃごちゃしたコードになっているので省略。

で、このViewのmouseUpあたりでこんなふうに書いてタスクスイッチャ機能は完成。
-(void)mouseUp:(NSEvent *)theEvent{
 [self set_state:BUTTON_STATE_OVER];
 [self setNeedsDisplay:YES];
 NSRunningApplication *selectApp=[NSRunningApplication runningApplicationWithProcessIdentifier:[_app _pid]];
 [selectApp activateWithOptions:NSApplicationActivateIgnoringOtherApps]; 
 

}


で、さらに、どーせなら起動中のアプリが開いているウィンドウの一覧を表示して、ウィンドウを選択してタスクを切り替えるようにしたい、と考えた。

ここまでが前段。
開いているウィンドウの一覧
ユニバーサルアクセスを無効にしたら、「HyperDock」が使えなくなって、ちょっと難儀している。HyperDockはDock上にWinVISTA、WIN7のタスクバー風にウィンドウの一覧を表示するユーティリティ。


これがあるとXcodeとInterfaceBuilderでたくさんウィンドウが開いても、すぐに目的のウィンドウをアクティブにできる。HyperDockが使えなくなったのでExposeを使うようになったけれど、全画面を使って「ウィンドウの一覧」を表示するため、アプリの選択をまちがったら、やり直す作業がちょっと煩雑。
そこでHyperDockもどきの機能をつけようとしてみた。

まずはウィンドウの一覧を得る。

あとになってxcatsan師匠が同じことを書いているのに気づいたんだけど、一応、半分くらいは自分でこの方法を考えた。

CGWindowListCopyWindowInfoを使えば全アプリのウィンドウの一覧を表示できる。これはxcatsan師匠が便利なサンプル付きで解説してくれているので知っていた。

で、ウィンドウの親プロセスもPIDで得ることができるので、起動中のアプリのPIDと同じPIDをもっているウィンドウがそのアプリのもの、ということになる。

-(void)rightMouseDown:(NSEvent *)theEvent{
 CFStringRef ownerPid;
 CFIndex i;
 CFDictionaryRef w;
 CFStringRef winName;
 CGRect rect; 
 CGWindowID spot_window_id;

 
 CFArrayRef list =CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
 
 NSMenu *menu = [[[NSMenu alloc] initWithTitle:@"HELLO"] autorelease];
 for (i=0; i < CFArrayGetCount(list); i++) {
  w = CFArrayGetValueAtIndex(list, i);
  if (CFDictionaryContainsKey(w, kCGWindowWorkspace)) {
   ownerPid=CFDictionaryGetValue(w, kCGWindowOwnerPID);
   if ([(NSString*)ownerPid intValue] ==[_app _pid] ) {
    winName=CFDictionaryGetValue(w, kCGWindowName);
    if((NSString *)winName !=@""){
     CGRectMakeWithDictionaryRepresentation(CFDictionaryGetValue(w, kCGWindowBounds), &rect);
     
     CFNumberGetValue(CFDictionaryGetValue(w, kCGWindowNumber),kCGWindowIDCFNumberType, &spot_window_id);

本当に全てのウィンドウの一覧なので、
if (CFDictionaryContainsKey(w, kCGWindowWorkspace)) {
この条件を挟んで「Workspace上にウィンドウとして表示されている」ものに限定する。で、同じPIDならウィンドウのタイトルと(winName)、表示領域(rect)と、windowID(spot_window_id)を取得する。 で、とりあえずウィンドウを画像として表示してみる実験をした。
     CGImageRef cgimage = CGWindowListCreateImage(rect,kCGWindowListOptionIncludingWindow , spot_window_id, kCGWindowImageDefault);
     NSSize imageSize=NSMakeSize((int)rect.size.width*0.25, (int)rect.size.height*0.25);
     NSImage* newImage=[[[NSImage alloc ]initWithCGImage:cgimage size:imageSize] autorelease];
     [newImage drawAtPoint:NSMakePoint(0, 0) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
CGWindowListCreateImageでえたCGImageRefを使ってinitWithCGImagでNSImageを作っている。その時原寸の4分の1に縮小している。それをウィンドウの(0,0)で描画する。
表示されたときは飛び上がって喜びましたよ。こういう、グラフィックス系の操作をやったのはPalmOSでjpegとbitmapをちょっこと使おうとしたことがあるだけなので。 しかし・・・・残念ながらこれだと、ちがうSpaceにあるアプリのイメージは取得できない。CGWindowListCreateImageのオプションをいろいろいじってみたけれど、せいぜい「アクテイブなWorkspaceの、該当するrectを画像として生成する」ことしかできない。 あれじゃあ、SimpleCapはどういう動作をするんだったけ、と思って試してみたら、やはり非アクティブなWorkspaceに表示されているアプリのキャプチャはできなかった。xcatsan師匠に図々しく問い合せてみたら、これは初めからの仕様ということだった。
メニューにしてみる
画像をどうしても取得できないなら、とりあえずメニューにしておいてウィンドウをアクティブにできるか試す。
     NSMenuItem *item=[[NSMenuItem alloc] init];
     [item setTitle:(NSString *)winName];
     [item setTarget:self];
     [item setAction:@selector(select:)];
     [item setTag:(NSUInteger) spot_window_id];
     [menu addItem:item];

もちろんこれはうまくいく。
しかし・・ここからわからない。どーしても「あるアプリのあるウィンドウ」にアクセスする方法がわからない。 要はDockの右クリックから出てくるメニュー、
これと同じことがしたいわけだ。メニューとして表示することまではできた。ではどうやってウィンドウを選択するのか。
結論:できない
普通の方法ではできないことがわかった(^^;)。CGSPrivateを使うのではないか、と当たりをつけて調べてみたら、
CGSSetUniversalOwner
がそれを実現する方法。ただし、これはDockをkillして、それから自分のConectionIDをDockの代わりに登録する、という関数。つまりDockの再発明をする、ということになる。

アホらしくて実際に試していない。それならDockを使えばいいわけだから。

モバギ復活!

121ware.com > 製品情報 > LifeTouch NOTE

webOSの新製品にも、NokiaとMSの提携にも反応しなかったけど、この記事にはぶっ飛んだ。

モバギが復活した!


内蔵4Gメモリ+4GのSDで39,900円とな。
完全におもちゃだよなあ、もしも買ったとしたら。うーん。

2011年2月14日月曜日

Skim 1.3.11(日本語化バージョン)

MLに告知されてました。Skimの新しいverがダウンロード可能。

今回のVersionUpには日本語化が含まれております。担当は不肖ワタクシ。訳に変なところがあったら教えてくだされ。

ダウンロードはこちらから。

Cocoa ObjC タスクスイッチャもどき

起動中のアプリの一覧を、こんなふうに表示してみた。


一応、アイコン+アプリ名の横長版、アイコン+アイコンの下にアプリ名、アイコンのみ、という3つの表示方法を取れるようにしてみた。

書き散らかしの実験用プロジェクトなもので、クラス名がいちいちMyViewだったりCustumViewだったりするので(^^;)、その辺をすこし整理しながら書きなおそうと考えている。現在は

1,コントローラで起動中のアプリを調査して専用のクラスに入れて保持
2,それをもとに「アプリ1つを保持して表示するViewクラス」をインスタンス化
3,さらにそいつを「並べて表示する専用View」にaddSubview

という感じ。いろいろコードを書いては直し、直しては書き、としていると、コントローラはいらないな表示用のViewでデータを保持したほうがいいじゃん、とか、NSRunnnigApplicationクラスが作ってくれる「起動中アプリの一覧」Arrayをそのまま使ったほうがいいんじゃね?とか、アイディアだけは湧いてくるものだ。こうやってのめりこんでいくのが面白いんだなあ。

アプリを保持しているView(ちゃんとクラス名考えないとだめだな(^^;))はマウスのトラッキングとかもできるようにしてみた。xcatsan師匠のコードのカットアンドペーストだけど。


これでアイコンを左クリックすると、そのアプリがアクティブになる。

NSRunningApplication *selectApp=[NSRunningApplication runningApplicationWithProcessIdentifier:[_app _pid]];
 [selectApp activateWithOptions:NSApplicationActivateIgnoringOtherApps]; 
アプリケーションのpidをコピーしてあるので、これだけでアクティブにできる。おもしろー。

2011年2月13日日曜日

Cocoa ObjC 最初のマウスクリックでマウスイベントを受け取る

すぐ忘れるので備忘録。

-(BOOL)acceptsFirstMouse:(NSEvent *)theEvent{
return YES;
}

プロパティとかでセットするのではなくこのハンドラをどこかに書く。

Cocoa Objc 非アクティブ時でもマウストラッキングを有効にする

これも備忘録。

自分が非アクティブの時でもマウストラッキングは有効にする。トラッキングエリア登録時のオプション。
_tracking_area = [[NSTrackingArea alloc] initWithRect:tracking_rect
              options:(NSTrackingMouseEnteredAndExited |
                 NSTrackingMouseMoved |
                  NSTrackingActiveAlways)
             owner:self
             userInfo:nil];
オプション3つめの NSTrackingActiveAlwaysが重要、と。

2011年2月12日土曜日

OSX fluxboxを野良ビルドする

自分用のアプリを作っていて、どーやら作りたいのは、Linuxデストリビューションで言うところのいわゆる「デスクトップ切替器」、なのではないか、と気づいた。

で、いろいろググったりしているうちにOSXでfluxboxをX11のウィンドウマネージャとして使う、という記事に出会う。

ほほう、ということで、さっそく自分でも野良ビルドしてみた。ソースをtarballで落としてきて、configure make make installというなんのことない作業だった。簡単すぎて気が抜けますな。

ただしconfigureには以下のオプションが必要。

./configure --x-include=/usr/X11R6/include --x-libraries=/usr/X11R6/lib

ふむ、えらそうに書くほどのこともないか。(^^;)

で、.xinitrcを以下のように書く。

#This line sets your path environment
PATH=$PATH:/usr/X11R6/bin:/sw/bin:/usr/local/bin
export PATH

# Opens up an xterm
xterm -geometry -250+200 &

# opens up the oclock clock
oclock -geometry -100+50 &

# Runs the fluxbox window manager
exec /usr/local/bin/fluxbox

これはどこやらで見かけたインストール記事からカット&ペーストしたもの。

これでX11.appを起動すると、ウィンドウマネージャがfluxboxに。


ちゃんと、えーとこのバーの名前はなんだったっけ(^^;)、とにかくステータスバーまがいのこいつが起動する。このバーの上でマウスホイールをころころすると、x11のデスクトップが切り替わる。Spacesのworkspaceは変わらない。おもしろいもんですな。Spacesの設定で、x11を全部のworkspaceで表示するようにしたら、Spacesのworkspace×x11のデスクトップ、という数の仮想デスクトップになるのかしらん。


ウィンドウの表示がしょぼいのでテーマを替えたいけれど、デスクトップでのクリックが効かない(Finderが全部もっていきますな、もちろん)ので面倒になってやめた。

fluxboxなので、起動できたからといってそれほどうれしいものでもない。(^^;)じゃあ他のウィンドウマネージャはビルドできるの?という当然の好奇心が湧き上がる。

そこでWindowMaker、afterstepという、NextStepインスパイア組をビルドしてみたところ、どーも64bit環境でのビルドは具合がわるいらしくてエラー終了。

ちなみにどちらもportsでは入れられるみたい・・・なのかな。こっちも64bit問題があったような気もする。

ということで野良ビルドはこれにて終了。

Cocoa ObjC Spaces 起動中のアプリケーション一覧

現在起動中のアプリケーションの一覧を、NSViewに表示する。

なんとかここまでできた。

起動中のアプリケーションを得るのは簡単。
-(void)setRunningApp{
 NSWorkspace *ws=[NSWorkspace sharedWorkspace];
 NSArray* ra=[ws runningApplications];
 
 for(NSRunningApplication* app in ra){
  if([app activationPolicy]==NSApplicationActivationPolicyRegular){
   SPApp *sp=[[SPApp alloc] initWithRunningApp:app];
   
   [_spAppArray addObject:sp];
  
  }
 }
 
}


基本的なことは下の2行だけで可能。
NSWorkspace *ws=[NSWorkspace sharedWorkspace];
NSArray* ra=[ws runningApplications];

この辺のことは以前MacRubyでやった経過を書いている。

MacRuby・Snatcher・タスクセレクタ(2)

今回はメニューに表示するわけではなく、Viewの勉強を兼ねて自分でなんとかしてみよう、と努力している。

現在のところ、

1,MyControllerがアプリの一覧を保持
2,アプリ1個分のデータ(名前、アイコン、PID)を受け取ってそれをボタン風に表示するためのView
3,上のViewをsubViewとして受け取って、表示する基本のView

という3つクラスに分けている。xcatsan師匠の「ThinButton」シリーズ(現在師匠のページが重くて表示できず、リンクははれません)を参考にしながら作業してみた。

アプリの一覧のデータをコントローラが持つのがいいのか、基本のViewが持っていてボタン風のViewを生成するのがいいのか、あれこれ悩みながら試行錯誤。せっかくクラスをわけて作るのだから、できるだけ相互依存のない状態にして役割分担をはっきりさせたほうがいいと思うのだけど・・・初心者はその辺の判断に苦しむ。

最初の画像の状態まで表示することはできたけど、コードはあまりにも稚拙で汚い(座標の数値とかもろに数字で書いているし)ので割愛。

でもCocoaプログラミングはおもしろいなー。もっと詳しく知りたいなー、と思う。

2011年2月11日金曜日

atMonitor

Macの強力な無料のシステムモニタリングツール『atMonitor』 : ライフハッカー[日本版]

上記ライフハッカーの記事にて初めて知る。さっそく本家サイトからダウンロードして使ってみた。


なかなかよさそう。起動中のアプリ・プリファレンスの情報を詳細に得ることができる。

プログラミングしていると、人の作ったアプリでの「見せ方」がすごく気になる。お、ここでHUDのパネルを使ってるのか、みたいな。

Cocoa ObjC 初めてクラスを書いてみる

1からまだクラスを書いたことがなかった。ちょうど「実行中のアプリ情報」を保持する仕組みが必要になったので自分で書いてみる。

人のコードをカットアンドペーストして、じっくり読んでそれを応用する、というスタイルで今のところきている。初心者にはちょうどいい勉強法。しかし実際に自分で書くとなると、知識がいかにいいかげんか痛感する。

とりあえず、実行中のアプリを保持するためのクラスのヘッダ。
#import 


@interface SPApp : NSObject {
 NSString* _name;
 NSImage* _icon;
 pid_t _pid;

}
-(id)initWithRunningApp:(NSRunningApplication*)aApp;
@property(readonly) NSString* _name;
@property(readonly) NSImage* _icon;
@property(readonly) pid_t _pid;

@end


そしてimplementation。
#import "SPApp.h"
#define ICON_SIZE 32

@implementation SPApp
@synthesize _name;
@synthesize _icon;
@synthesize _pid;
-(id)initWithRunningApp:(NSRunningApplication *)aApp{
 self=[super init];
 if (self) {
  _name=[[aApp localizedName] retain];
  _icon=[[aApp icon] retain];
  [_icon setSize:NSMakeSize(ICON_SIZE,ICON_SIZE)];
  _pid=[aApp processIdentifier];
 }
 return self;
}
-(void)dealloc{
 [_name release];
 [_icon release];
 [super dealloc];
}
@end

dealocが本当に必要なのか今イチ自信がない。しかし今のところ不都合はないようだ。クラッシュとか。プロパティとかアクセサとか、書いてみないと書き方がわからないものだなあ。

retainってどこでどうやって使うんだかまだまださっぱり理解していないが、ここでつけないとコンパイラに怒られる。

自分でコードを書くときは

詳解 Objective-C 2.0 改訂版
荻原 剛志
ソフトバンククリエイティブ
売り上げランキング: 2710

この本と首っ引き。買っておいてよかった。これがないと到底クラスなんか書けなかったぞ。

MacRubyで書きかけていたアプリは、タスクスイッチャみたいな機能をつけたところで止まっている。起動中のアプリを取得して、それをサブメニューの中に仕込んで、というところまでいって、じゃあメニュー以外の表現方法にしてみよう、となってなぜかObjCの勉強をしている。本当に気まぐれだな。
MacRubyでつくっていた「Spin」と同じような機能のアプリを作成中で、今度は透明ウィンドウの中に起動中のアプリを何らかの方法で表示しようとしている。

はたしてうまくいくか・・・。Viewの勉強をもっとしなければ。

2011年2月10日木曜日

Cocoa NSImageView

基本的なことをちゃんと勉強しようということで、NSViewやNSImageViewなどを触ってみる。InterfaceBuilderでインスタンスを生成せず、コントローラーなどから動的に生成して、addSubViewとかをしてみる、と。

xcatsan師匠がSimpleCapを作る過程で山ほどこの手の処理を書いていてくれているので、あれこれ参考にしながら作業をしてみる。

まずはNSImageViewにデスクトップのキャプチャ画像を表示させてみた。コントローラのawakeFormNibでviewを生成する。
@implementation MyController
-(void)awakeFromNib{
 NSRect viewFrame = [[window contentView] bounds];
 MyImageView *view=[[MyImageView alloc] initWithFrame:viewFrame];
 [[window contentView] addSubview:view];
 [view release];
}
@end
これだけならInterfaceBuilderで生成してもいいわけだけど、ま、あとあといろいろ変更してみようと考えている。

Viewはカスタムクラスにして、こんな感じ。
@implementation MyImageView
-(id)initWithFrame:(NSRect)frameRect{
 self = [super initWithFrame:frameRect];
 if (self) {
  [self setCaptureImage]; 
 }
 return self;
}
-(void)setCaptureImage{
 CGWindowID window_id = kCGNullWindowID;
 
 CGImageRef cgimage = CGWindowListCreateImage(CGRectInfinite, kCGWindowListOptionOnScreenBelowWindow,window_id, kCGWindowImageDefault);
 NSBitmapImageRep *bitmap_rep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
 NSImage *image = [[[NSImage alloc] init] autorelease];
 [image addRepresentation:bitmap_rep];
 [bitmap_rep release];
 [self setImage:image];
 CGImageRelease(cgimage);
 [self setImageScaling:NSImageScaleAxesIndependently];
 NSLog(@"%d",[self imageScaling]);
}

setCaptureImage内の処理はほぼxcatsan師匠のコードの引き写し。これを自分で書けるようになるのが当面の目標だな。

これでデスクトップ全体のキャプチャ画像がViewいっぱいに縮小されて表示できる。


NSImageViewの場合、drawRect:をオーバーライトしたら自動でimageを表示してくれなくなることが本日わかった。(^^;)もう、素人なんだから仕方ないわな。

2011年2月9日水曜日

Cocoa ObjC Spaces Workspace切り替えの視覚効果

というわけでMacRubyで書いたcodeをさらにObjCに直すという本末転倒。ま、いいけど。

書きなおしてみたけど、今イチ動作が納得できない。CGSPrivate.hについてもう少し研究したほうがよさそうだ。

さらに書けば、視覚効果をつけるのはあまりお勧めできないな。(^^;)余計なごてごてした挙動、という感じがする。とりあえず自分では必要としない。それなら視覚効果はやめておこうか。Notificationで十分かも。

Cocoa Cocoa Browser Air

不分明にして今まで知らなかった。

Cocoa Browser Air

デベロッパドキュメントはClassリファレンスとしてはいいとして、へー、こんなClassもあるんだ、みたいなドキュメント探索がしづらくて、API一覧のような文書がないのか探したらこのアプリが出てきた。ありがたやー。

ダウンロードはGithubでした。

欲を言えばFontのサイズを大きく出来るようにしてほしい。(^^;)ロートルにはちょっとつらい大きさなので。


これでCocoaのクラス探索がかなり楽になった。

2011年2月8日火曜日

ユニバーサルアクセスの「補助装置にアクセスできるようにする」

xcatsan師匠が最近、Event MonitorというAPIについて触れている。システム全てのイベントを取得できる超便利なAPIらしい。

難点はユニバーサルアクセスの「補助装置にアクセスできるようにする」を有効にしないといけないこと、なんだそうで、師匠は次のように書いている。

とても強力な API だが、ユニバーサルアクセスの設定が必要なのと、何よりも悪用される可能性が高いことから使い道はかなり限られる。別の言い方をすると非常に簡単にキーロガーやイベントロガーの作成が可能なので、もし「補助装置にアクセスができるようにする」にチェックが入っていると簡単にそういったソフトの標的になりうる。この為、Global Event Monitor を使いこの設定を前提としたアプリケーションの配布は行うべきではないだろう(ユーザが一旦設定してしまうと悪意を持った他のアプリに容易にイベントを盗聴されてしまう可能性が高まる)。便利なんだが....残念。

なるほど、そういえば自分のiMacでも「補助装置云々」は有効にしていた覚えがあるので、さっそく無効にしてみた。なにが使えなくなるのか、と思ったらHyper Dockだった。がっかり。このアプリ、すごく便利なんだよなあ、ウィンドウの選択するのに。

致し方なく、Exposeの全ウィンドウ表示をマウスホイールのクリックに割り当ててなんとかしのいでいる。なんとかならないかなあ。

とにかくxcatsan師匠の教えの通りに、こんなふうに描いてみた。

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
 // Insert code here to initialize your application 
 [NSEvent addGlobalMonitorForEventsMatchingMask:NSScrollWheelMask
             handler:^(NSEvent* event) {
           NSLog(@"GlobalMonitor: %@", event);
             }] 
}

で、ユニバーサルアクセスを可能にした状態で起動すると、マウスホイールの回転がどこで起きてもLOGがとれた。

2011-02-08 21:34:23.638 event_c[1193:a0f] GlobalMonitor: NSEvent: type=ScrollWheel loc=(2135,1122) time=8890.1 flags=0x100 win=0x0 winNum=11 ctxt=0x0 deltaX=0.000000 deltaY=-5.000000 deltaZ=0.000000 deltaX=0.000000 deltaY=-5.000000 deltaZ=0.000000 scrollPhase=None
2011-02-08 21:34:44.726 event_c[1193:a0f] GlobalMonitor: NSEvent: type=ScrollWheel loc=(931,1369) time=8911.2 flags=0x100 win=0x0 winNum=835 ctxt=0x0 deltaX=0.000000 deltaY=-5.000000 deltaZ=0.000000 deltaX=0.000000 deltaY=-5.000000 deltaZ=0.000000 scrollPhase=None

ちなみに最初のwinNum=11がデスクトップ上のころころの記録。
で、ユニバーサルアクセスを無効にして実行してみると・・・あれれ、師匠、スクロールホイールのイベント取れてますよ?(^^;)

ホイールのイベントだけはたいていのアプリが非アクティブ状態でも受け取れるので、きっと特殊なんでしょう。・・・お?ということは、どんな環境でもこのAPIがあればデスクトップ上のホイールイベントが取れる、ということか。おお、これは面白くなってきた。

[追記]デベロッパドキュメントを読んだら、

Key-related events may only be monitored if accessibility is enabled or if your application is trusted for accessibility access

ということでマウス関係ならユニバーサルアクセスを無効にしても受け取れるようだ。ふーむ。

Cocoa ObjC Spaces Workspaceを切り替える

透明ウィンドウ上でマウスホイールをころころさせるとSpacesのWorkspaceが切り替わるようにする。

すでにMacRubyで一度やっているのでそれを修正しながら実装した。
必要な前提はCGSPrivate.hをimportすること。このヘッダはネットのあちこちに転がっている。私は「Warp」のプロジェクトに含まれていたのものを転用させてもらっている。

必要なメソッドは
1,現在のSpacesの行と桁を取得して最大Workspace数を得る

-(void) getSpaceMax{
 CFPreferencesAppSynchronize(CFSTR("com.apple.dock"));
 _space_row=CFPreferencesGetAppIntegerValue(CFSTR("workspaces-rows"),CFSTR( "com.apple.dock"), nil);
 _space_col=CFPreferencesGetAppIntegerValue(CFSTR("workspaces-cols"), CFSTR("com.apple.dock"), nil);
 _space_max=_space_row * _space_col;
}
row、col、maxともにインスタンス変数にしてみた。とりあえず、ということで。

2,現在のSpacesのWorkspaceのindexを得る
- (NSInteger)spaceNumber {
 CGSWorkspace currentSpace;
 
 if (CGSGetWorkspace(_CGSDefaultConnection(), ¤tSpace) == kCGErrorSuccess) {
  if (currentSpace == 65538) {
   return -1;
  }
  
  return currentSpace;
 } else {
  return -1;
 }
} 

いわゆるCGプライベート関数を使う。実はこのメソッド、「Warp」からいただいてしまっている。だから「65538」なんていう数字もWarpのソースのまま。

現在のWorkspaceをNSLogで出力しながら確認してみると、「1」とか「2」とか順当な値の中にいきなり「65538」が出てくる。理由は今のところ不明。Warpのように65538が出てきたら「-1」を返すようにしても、動作そのものには全く支障がない。そのうち追求してみよう。

3,Workspaceを実際に切り替える
NSString *SwitchSpacesNotification = @"com.apple.switchSpaces";
- (void)postNotificationSpacesSwitch:(NSInteger)object{
 [[NSDistributedNotificationCenter defaultCenter] postNotificationName:SwitchSpacesNotification object:[NSString stringWithFormat:@"%d", object]];
}
今のところは単にNotificationをSpacesに向かって通知しているだけ。CGプライベートの「CGError CGSSetWorkspace(const CGSConnection cid, CGSWorkspace workspace);」を使ってみるのは明日以降。

あとはマウスホイールのイベントハンドラで処理するだけ。

 (void)scrollWheel:(NSEvent *)theEvent{
 NSLog(@"%f",theEvent.deltaY);
 NSInteger number_space=[self spaceNumber];
 NSLog(@"%d",number_space);
 if ([theEvent deltaY] < 0.0) {
  if (number_space==_space_max) {
   number_space=1;
  }
  else{
   number_space++;
  }
    
 }
 else{
  if (number_space==1) {
   number_space=_space_max;
  }
  else{
   number_space--;
  }
 }
 [self postNotificationSpacesSwitch:number_space-1];
 
}

相変わらずVBAのExcelマクロみたいな書き方しかできていないのががっくり。ま、だんだん上手にかけるようになることを自分に期待しよう。

「システムよりのものすごく難しそうなこと」に思えた「Workspaceの切り替え」、ど素人でもけっこう簡単にできてしまうところがCocoaの面白さか。

2011年2月7日月曜日

ヒレガス本も滋味深い

ちょっと自分でコードを書こうとするととたんに行き詰まる。ObjCほとんど書いたことないので、まだ全然書けない。

Mac OS X Cocoaプログラミング
アーロン・ヒレガス 村上 雅章
ピアソンエデュケーション
売り上げランキング: 196349


そこで基本的な書き方はヒレガス本をあちこちめくって探すことになる。

ざっと斜めに読んだだけではこの本の真価はわからないようだ。やはりじっくり手を動かしながら読むべきだなあ。

ひとつひとつのトピックの記述がさらっとしていて、もう少し詳しく、と思わないでもない。でも和書ではそんな本は手に入らないようだな。残念。

一方、こっちの本はObjCを触り始めるととたんにそのありがたさがわかってきた。

詳解 Objective-C 2.0 改訂版
荻原 剛志
ソフトバンククリエイティブ
売り上げランキング: 2061

Cocoa ObjC NSWindow setCollectionBehaviorのふるまい

透明ウィンドウが表示できたので、今度はその上でマウスホイールをころころさせたらworkspaceを切り替えられるようにする。

MacRubyで書いたrubyのコードを参照しながら作業。とりあえず全部のworkspaceで透明ウィンドウを表示できるようにした。

MacRubyの時は
my_window.setCanBeVisibleOnAllSpaces(true)

としていたが、ObjCだとDeprecatedだといわれる。そういえばそんなことをxcatsan師匠も書いていたぞ。そのかわりsetCollectionBehaviorを使え、ということなのでデベロッパドキュメントで調べてみる。ここでやりたいことを実現するには

[window setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
と書けばよかった。

setCollectionBehaviorはOSX10.5以降に追加されたメソッドで、「Window collection behaviors related to Exposé and Spaces.」だそうで、以下のようなenumが定義されている。

enum {
   NSWindowCollectionBehaviorDefault = 0,
   NSWindowCollectionBehaviorCanJoinAllSpaces = 1 << 0,
   NSWindowCollectionBehaviorMoveToActiveSpace = 1 << 1
};
enum {
   NSWindowCollectionBehaviorManaged = 1 << 2,
   NSWindowCollectionBehaviorTransient = 1 << 3,
   NSWindowCollectionBehaviorStationary = 1 << 4,
};
enum {
   NSWindowCollectionBehaviorParticipatesInCycle = 1 << 5,
   NSWindowCollectionBehaviorIgnoresCycle = 1 << 6
};
typedef NSUInteger NSWindowCollectionBehavior;
最初の3つがSpaces.appの関係のふるまい。
NSWindowCollectionBehaviorDefault
デフォルト。アプリケーションは一時にひとつのSpaceにしか表示できない。
NSWindowCollectionBehaviorCanJoinAllSpaces
アプリケーションは全てのSpaceに表示される。メニューも同じ。
NSWindowCollectionBehaviorMoveToActiveSpace
アプリケーションをアクティブにした時、Spaceを切り替えずに、現在アクティブなSpaceに表示する。これはSpaces.appの設定で、Spacesを切り替える、というオプションがあるのでそれ関係か。 次の3つがExposé関係のふるまい。(Spacesにも関係するけど)
NSWindowCollectionBehaviorManaged
Spacesの一覧表示、Exposéの表示にウィンドウが表示される。NSNormalWindowLevelで表示されているウィンドウのふつうの状態。
NSWindowCollectionBehaviorTransient
こっちはNSNormalWindowLeveではない時の普通の状態。試してないのでいったいどーなるのだか。解説によればSpacesの一覧では表示されるけどExposéでは表示されないようだ。
NSWindowCollectionBehaviorStationary
Spaces、Exposé両方でデスクトップのようにふるまう。 以下の2つは「Cycle Through Windows Window menu item」に関連するんですが、Cycle Through Windows Window menu itemって何?なのでよくわからず。
NSWindowCollectionBehaviorParticipatesInCycle
参加する。
NSWindowCollectionBehaviorIgnoresCycle
参加しない(^^;)。 というわけで今のところ、実験用プロジェクトではこう書いている。
[window setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces | NSWindowCollectionBehaviorStationary | NSWindowCollectionBehaviorIgnoresCycle];
全部のSpacesに図々しく現れるわりに、Exposéとかからには隠れているいるという感じ。 本日はこんなところで終了してしまった。ちょっとずついろいろ確かめながら進む、という姿勢もxcatsan師匠から教わったひとつだな。

2011年2月6日日曜日

Cocoa ObjC 入門1日目終了

というわけでObjCによるCocoa入門の1日目が終わった。

このところずっとMacRubyによるObjCの書き換えをやっていたため、ソースコードを読むことに抵抗感は少ない。

ただ、いざ自分でメソッドを書こう、となるとじたばたする。API並べていくだけならいいんだけど。本日、一番多かったtypoは

「;」のつけ忘れ

でありました。rubyは偉大、ということで。(^^;)本当にrubyはタイプ量が少なくて済むようにできている。

一応、ヒレガス本はひと通り目を通してみた。
Mac OS X Cocoa プログラミング 第3版
Aaron Hillegass アーロン ヒレガス
ピアソンエデュケーション
売り上げランキング: 87127

この本は折にふれて参照する類の本みたいだから、やはり手元においておくべきだな。Amazonで買いますか、新版を。

あと、InterfaceBuilderとbindingに関する詳細な解説とサンプルを載せた本があればな、と感じるこの頃。Windowsに比べると需要がはるかに少ないんでしょうが、iOS興隆の昨今、もっとCocoa本があってもいいのでは。電子書籍ならなお歓迎。

Cocoa ObjC 透明ウィンドウで遊ぶ・その2

AppDelegateのawakeFormNibに、

[window setFrameAutosaveName:@"MainFrame"];

と書いておけばUserDefaultにウィンドウの位置情報を勝手に記憶して、再現してくれることを知る。やってみて首をひねってしまった。

ウィンドウの大きさを次の状態で終了する。

ちょっと縦長にしていおく、と。しかし次回起動時になるとこんなありさまになる。


InterfaceBuilderで設定してある大きさのようだ。というわけでInterfaceBuilderの中をあちこちいじったり探したりしたけれど、それらしい設定はない。変だなあ、ということで、とりあえず、awakeFormNibで一番最初にsetFrameAutosaveNameを書いたところ、期待する動作ができるようになった。順番、関係あるのか?
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
 // Insert code here to initialize your application 
 [window setFrameAutosaveName:@"MainFrame"];
 [window setStyleMask:NSBorderlessWindowMask];
 [window setOpaque:NO];
 [window setBackgroundColor:[NSColor clearColor]];
 [window setMovableByWindowBackground:YES];
 [window makeKeyAndOrderFront:self];
}


さらに起動中のsetOpaque:の変更について。これもあれこれやってみたところ、メインのウィンドウをNSPanalにしておいて、

[window setStyleMask:NSResizableWindowMask | NSUtilityWindowMask ];
に変更した場合、もう一度
    [window setStyleMask:NSBorderlessWindowMask];
    [window setOpaque:NO];
としてもおかしな動作がなかった。うーん、よくわかんない、結果オーライだけど。

半透明なウィンドウをデスクトップのどこかにおいておいて、そこでマウスホイールをころころさせたらworkspaceが切り替わる、という自分用のアプリを書いている。iMacの大画面だと、Warpを使うにもマウスの移動量が大変。だからデスクトップの右下4分の1くらいの領域をworkspace切り替え用に使えればけっこう幸せになるだろう、と。
実はマイクロソフトマウスの第4、第5ボタンに「ctrl+→」とかを割り当てていて、それでworkspaceかえられるのだけれど、今イチ押しづらいんだよなあ。

Cocoa ObjC 透明ウィンドウで遊ぶ

本日よりMacRubyではなくObjCでcocoaの勉強を始めた。

とりあえず、MacRubyでやったことの復習。透明ウィンドウを作って、そこに半透明なViewをはりつけて表示する。



やっぱXcodeはObjC用のIDEなんで、ObjCで使うと便利であると知る。(^^;)当たり前か。
View上でmouseDownがあると、カーソル位置に「MouseDown」と描画させてみる。

ほとんどがxcatsan師匠からの受け売りですな。本当に師匠の
(旧)Cocoaの日々

は日本語によるCocoaプログラミングの最高のサイトではないかしらん。いつもお世話になりっぱなしで、「これはどうやって実現するんだろう」とふつーにググったら、だいたい師匠の旧cocoに行き当たってそこで大正解にたどり着くことがおおい。

本日の一番の収穫は

setMovableByWindowBackground:

を知ったこと。ウィンドウのどこでドラッグしてもウィンドの移動をできる、というやつ。
また、WindowのstyleMaskを後付でいろいろ替えられるみたいなので、右クリックでNSResizableWindowMaskとNSBorderlessWindowMaskを切り替えられるように実験。

- (void)rightMouseDown:(NSEvent *)theEvent{
 NSWindow* window=[self window];
 if([window styleMask]==NSBorderlessWindowMask){
  [window setStyleMask:NSResizableWindowMask];
  [window setOpaque:YES];
 }
 else {
  [window setStyleMask:NSBorderlessWindowMask];
  [window setOpaque:NO];
  [window setBackgroundColor:[NSColor clearColor]];
  
 }
 [self setNeedsDisplay:YES];

}

で、右クリックするとちゃんとサイズ変更のアンカーが現れますな。


どーもよくわかんないのが[window setOpaque:YES];の挙動。これを一度でもYESにしちゃうとViewが真っ黒になってしまう。うーん、YESにしなかればいいわけだけど、サイズ変更アンカーがすごく見えづらいんだよなあ。

一度真っ黒で塗りつぶされてしまったものの上からいくら半透明なViewを描き直してもだめ、ということか。

2011年2月5日土曜日

Macruby・NSMatrix

現在起動中のタスクを取得して、コンテキストメニュー風に表示することはできた。だからメニュー以外の表示方法にも挑戦しようと考えて、NSCollectionViewを試してみたけれどうまくいかなかった。

アイコンを縦一列にViewに並べることくらいできるはずだよな、と調べているうちにNSMatrixというViewがあることを発見。はりきってググったら、やっぱりxcatsan師匠が親切にも(^^;)サンプル付きで解説してくれていた。

で、サンプルのソースをいただいてMacRubyに書き換えていく。書いていく過程で、今までなんとなくわかっていたことが改めて理解できた。同じプロジェクトの中からなら、requireとかしないでも他のclassをnewできるのね。

今回は3つのクラスを書き換えた。NSCellのサブクラス、NSMatrixのサブクラス、そして両方をコントロールする役目のクラス。

しかし・・・NSMatrixのsizeToCellsを呼び出すところでエラーがでる。unrecognized selector sent to classって言われてしまう。うーむ、よくわかんない。NSCellのサブクラスに、理解出来ないセレクターが送りつけられた、と行っているようだけど、なにがどうしてそうなるのかわからない素人の悲しさ。

やっぱり少しObjCの勉強をしようか、と考えている。MacRubyでGUIアプリの開発は、いささか私にはまだ荷が重いのか。cocoaの知識が少ないだけにかなりつらいことがある。(楽しいことも多いけど)せっかくヒレガス本もあるので、明日はObjCの勉強を始めよう。

2011年2月4日金曜日

Macruby・NSCollectionViewその2・撃沈

むー、どうもNSCollectionViewでうまく画像を表示できない。なぜだ。

bindingの知識とかあまりにも少なすぎるか。もう少し勉強しよう。
2日続きで収穫なしでいささか消沈してしまう。

MacRubyでうまく行かないときはObjCでチュートリアルどおりにやってみると理解が深まったりするので、そっち方面でやってみよう。

明日は休みだからがんばろう。

2011年2月3日木曜日

図書館で借りた

帰りがけ、図書館によってヒレガス本の旧版を借りる。

Mac OS X Cocoaプログラミング
アーロン・ヒレガス 村上 雅章
ピアソンエデュケーション
売り上げランキング: 364471

新版を買おうとは思うけれど、とりあえず旧版で内容を確かめてから、という方針で。
しばらくはこの本とMacRubyの勉強、並行して進めたい。

Macruby・NSCollectionView

本日はいろいろ忙しくて時間がなく、xcatsan師匠が私のために書いてくれた(^^;)このページを読み、ADCのサンプルコードIconCollectionを覗いてみたりしているうちに時間切れ。

自分でも昨日作った実験用プロジェクトで少し試してみたけど、bindをよく理解していないためうまくいかない。

明日以降、週末でがんばろう。今日はもう疲れた。

2011年2月2日水曜日

Macruby・NSImageView

タスクスイッチャをメニュー以外の方法で表示してみる。
そのために本日はNSImageViewをもったWindowひとつの実験プロジェクトを作って、起動中のアプリのiconを表示させてみた。


NSImageCellをひとつだけもったView、ということで本当にひとつだけだった。なんか寂しいなあ。あまり使う用途がないViewのような気がするが。

明日はNSCollectionViewを試してみよう。本日は道路状況が悪くて帰宅が遅くなったことにいろいろ重なって自由時間がとても少なく残念。

夏なら20分しかかからないのに、冬の凍りついた道路だと45分くらいかかる。この時期は難儀だ。

2011年2月1日火曜日

MacRubyをさわり始めて1か月

だいたい1か月くらいたちました、XcodeとMacRubyの組み合わせでさわり始めて。

iMacを買って4か月半くらい。rubyは昨年の8月から本格的に書き始めた。(趣味のプログラミング歴だけは無用に長いけど)

「こういうふうにしたいなあ」と思った機能がだいたい実現できるのがおもしろさの最大の理由。
rubyはマジカルな書き方ができる言語だと思っていたし実際にマジカルな人もたくさんいるようだけど、「読みやすさ、書きやすさ」はすごいですね。Cだと実際にやりたい機能を作るまでの「予備動作」がおおいわけだけど、rubyはすっと書ける感じ。

さらにcocoaフレームワークのわかりやすさが大きいな。いやもちろん、xcatsan師匠という存在がなければここまで「こうやって書く」ことがわからなかったわけですが。師匠だけでなく、cocoaにはすてきな伝道師が多いような気がする。ものすごくディープな知識をさらりと公開してくれている多くの先達がいますな。

はっと気づくと自由時間のほとんどはMacRuby+Xcodeの中にいる。生活の中心になってるなー。

これでiOSのアプリが書けるようになったらさらに楽しいだろうなあ。

MacRuby・Snatcher・タスクセレクタ(2)

NSWorkspaceのlaunchApplicationだと、例えばFinderを「起動」するとアクティブなウィンドウがない場合はHOMEを開いて表示する。期待する動作としてはメニューバーでFinderがアクティブになる、こと。

じゃあ一体どうすればいいか悩む。それ用のNotificationでもあるんだろうか。とりあえずデベロッパドキュメントで「Activate」とかを検索する。(力業ですな)

検索して出てきた項目の11番目(^^;)が「NSRunnnigApplicationのactivateWithOption」で、これがなんだかよさそうなのでNSRunnnigApplicationのドキュメントをよく読む。ほおほお、現在起動しているアプリそのものはNSWorkspace.runningApplicationsで取得せよ、と。そんなんでこう書いてみる。

ws=NSWorkspace.sharedWorkspace
    ra=ws.runningApplications
    puts ra[0].localizedName
表示されたのは「loginwindow」とかで首をひねる。

昨日書いたmenuに表示するメソッドを流用してこう書いた。

ws=NSWorkspace.sharedWorkspace
  ra=ws.runningApplications
  my_menu.removeAllItems
 
  ra.each do |app|
   
    app_name=app.localizedName
    image=app.icon
    image.setSize(NSMakeSize(32,32))
    item=NSMenuItem.alloc.init
    item.setTitle(app_name)
    item.setImage(image)    
    item.setTarget(self)
    item.setAction("select:")
    my_menu.addItem(item)
   
    
  end
実行してみたところ、こんなふうに表示される。

ふむ、とりあえず起動しているアプリが全部拾えるらしい。WarpDeamonとかEvernoteHelperとかのシステム環境設定パネルに表示されるのも全部。なるほど、プリファレンス関係のプログラムってHelperと名付けられることが多いのね。iconがNSImageの状態で取得できる分、こっちのほうが楽かもしれない。

それはともかくこれでは非実用的。
ドキュメントをあさったら、それぞれのアプリにはactivationPolicyというプロパティがあって、これには
NSApplicationActivationPolicyRegular,
NSApplicationActivationPolicyAccessory,
NSApplicationActivationPolicyProhibited

という3つの選択肢があるらしい。で、普通のGUIアプリがNSApplicationActivationPolicyRegular、他のはBackGraundOnlyだったりするアプリ用と。

そこでこう書きなおしたらうまくいった。
  ws=NSWorkspace.sharedWorkspace
  ra=ws.runningApplications
  my_menu.removeAllItems
 
  ra.each do |app|
   if app.activationPolicy==NSApplicationActivationPolicyRegular then
    app_name=app.localizedName
    image=app.icon
    image.setSize(NSMakeSize(32,32))
    item=NSMenuItem.alloc.init
    item.setTitle(app_name)
    item.setImage(image)
    item.setTarget(self)
    item.setAction("select:")
    my_menu.addItem(item)
   end
    
  end



よしよし。あとはメニューから選択されたアプリをactivateするだけなんだけど、一度menuにしてしまうと名前とiconしかアプリの情報が残らない。NSRunnnigApplicationのArrayをmenuのアクションにどうやって渡すか悩む。

結局、NSRunnnigApplicationのインスタンスはPIDかBundleNameからでも取得できるので、menuItemのtagにPIDを仕込むことにする。
item.setTag(app.processIdentifier)

こうやっておいた上でアクションをこうしてみた。ちなみにコメントは将来の自分用(^^;)。
 def select(sender)
  #sender.tag==RunApp.processIdentifier
  app=NSRunningApplication.runningApplicationWithProcessIdentifier(sender.tag)
  app.activateWithOptions(NSApplicationActivateIgnoringOtherApps)
 end


これで最初考えたとおり、タスクを選択すると「launch」ではなく「Activate」するので、Firefoxなら「多重起動できません」と文句をいわないし、Finderなら黙ってメニューバーに現れる。

xcatsan師匠なら「よし」と書くところですな。(^^;)

考えた通りのことを実現できるのは気持ちがいい。