Hatena::ブログ(Diary)

ザリガニが見ていた...。 このページをアンテナに追加 RSSフィード

2012-09-13

通知センターでHello, world!

Mountain Lionの新機能の一つ、通知センターをうまく使えないかと試してみた記録。今まではgrowlnotifyコマンドを利用して、AppleScriptなどの処理の状態を知る手がかりにしていた。できることならOSX標準の仕組みだけで通知したいのだ。

Objective-C

  • CocoaはObjective-Cで書かれている。Objective-CはCocoaのために存在する、と言えるかもしれない。
  • だから、最初はObjective-Cで考えることが、理解への近道だと考えた。
#import "AppDelegate.h"

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    // Insert code here to initialize your application
    NSUserNotification *myNotification = [[NSUserNotification alloc] init];
    myNotification.title = @"Hello, world!";
    myNotification.informativeText = @"Nice to meet you.";
    [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:myNotification];
    
    [NSApp terminate:self];
}

@end

できた!

f:id:zariganitosh:20120913082449p:image

  • 通知をクリックすると、その通知を登録した元のアプリケーションを起動する仕様のようだ。
  • 通知をクリックするたびに、新たな通知が表示された。

RubyCocoa

  • XcodeでRubyCocoaのアプリケーションを作ろうと思ったが、雛形プロジェクトがが見当たらない...。
  • もしやMountain LionではrubyCocoaが標準インストールされなくなってしまったのか、と確認してみると...
$ ruby -e "require 'osx/cocoa';puts OSX::RUBYCOCOA_VERSION"
1.0.1
  • しっかりと/System/FrameworksにRubyCocoa 1.0.1がインストールされている!
  • ならば、Xcodeが利用するRubyCocoa用の雛形プロジェクトをインストールすればいいのだ。
$ svn export --force http://rubycocoa.svn.sourceforge.net/svnroot/rubycocoa/trunk/src/template/Xcode4.4/Templates ~/Library/Developer/Xcode/Templates
  • Ruby-Cocoa Applicationを新規作成して、AppDelegate.rbに以下のコードを書いて完成した。
class AppDelegate < OSX::NSObject
  def applicationDidFinishLaunching_(notification)
    # Insert code here to initialize your application
    myNotification = OSX::NSUserNotification.alloc.init
    myNotification.title = "Hello, world!"
    myNotification.informativeText = "Nice to meet you."
    OSX::NSUserNotificationCenter.defaultUserNotificationCenter.deliverNotification_(myNotification)
    
    OSX::NSApp.terminate_(self)
  end

end

できた!

f:id:zariganitosh:20120913082449p:image

MacRuby

  • RubyからCocoa環境を利用するもう一つの手段がMacRubyである。
  • MacRubyはOSX標準ではインストールされていないので、自分でダウンロードする必要がある。
  • RubyCocoaがOSXモジュール配下にCocoaクラスを追加して拡張しているのに対し、
  • MacRubyではRuby本体のソースコードを拡張して、Cocoa環境にアクセスしている。
  • そのため、MacRubyではすべてのオブジェクトがNSObject(Cocoaのルートオブジェクト)を継承して、よりRubyとCocoaの親和性が高まっているのだ。
$ ruby -e "require 'osx/cocoa'; p OSX::NSObject.ancestors"
[OSX::NSObject, OSX::OCObjWrapper, OSX::NSKeyValueCodingAttachment, OSX::NSKVCAccessorUtil, OSX::ObjcID, Object, Kernel]
$ ruby -e "require 'osx/cocoa'; p Object.ancestors"
[Object, Kernel]
$ macruby -e 'p NSObject.ancestors'
[NSObject, Kernel]
$ macruby -e 'p Object.ancestors'
[NSObject, Kernel]
  • MacRubyをダウンロード&インストールすると、すでにXcodeの雛形プロジェクトにもMacRuby Applicationが追加されていた。
  • さっそく以下のように入力して完成と思いきや、実行するとエラーが出る...。
class AppDelegate
    attr_accessor :window
    def applicationDidFinishLaunching(a_notification)
        # Insert code here to initialize your application
        myNotification = NSUserNotification.alloc().init()
        myNotification.title = "Hello, world!"
        myNotification.informativeText = "Nice to meet you."
        NSUserNotificationCenter.defaultUserNotificationCenter().deliverNotification(myNotification)
        
        NSApp.terminate(self)
    end

end

f:id:zariganitosh:20120913085857p:image

f:id:zariganitosh:20120913090640p:image

できた!

f:id:zariganitosh:20120913082449p:image

Cocoa-AppleScript Applet.app

  • Lion以降、いつのまにかAppleScript エディタにファイル >> テンプレートから新規作成というメニューが追加されていた。
  • そこでCocoa-AppleScript Applet.appを選択すると、AppleScriptからCocoa環境を利用したアプリが作れるようになっていた。

 set myNotification to current application's NSUserNotification's alloc()'s init()
 set myNotification's title to "Hello, world!"
 set myNotification's informativeText to "Nice to meet you."
 current application's NSUserNotificationCenter's defaultUserNotificationCenter's deliverNotification_(myNotification)
 quit --quit重要--これがないと通知されない--謎

  • 実行する時は「アプリケーションを実行」ボタンを押すのだ。(「スクリプトを実行」ではCocoaにアクセスできず、エラーになる)

f:id:zariganitosh:20120913103838p:image

できた!

f:id:zariganitosh:20120913103804p:image

AppleScript

  • Cocoa-AppleScript Applet.appでは、Cocoaの機能を使えて大変便利なのだが、
  • 通常の.scpt形式のAppleScriptでは、Cocoaにはまったくアクセスできない...。(非常に残念)
  • ならば、力技で.scpt形式のAppleScriptから、Cocoa-AppleScript Applet.appを作成して、実行して、破棄してみた。
  • システム環境設定 >> アクセシビリティ >> 「補助装置にアクセスできるようにする」チェック入 にしておく必要あり。

 tell application "AppleScript Editor" to activate
 
 delay 1
 
 tell application "System Events" to tell process "AppleScript Editor"
   pick menu item "Cocoa-AppleScript Applet.app" of menu "テンプレートから新規作成" of menu item "テンプレートから新規作成" of menu "ファイル" of menu bar item "ファイル" of menu bar 1
 end tell
 
 tell application "AppleScript Editor"
   set document 1's text to "set notification to my NSUserNotification's alloc()'s init()
 set notification's title to \"Hello, world!\"
 set notification's informativeText to \"Nice to meet you.\"
 my NSUserNotificationCenter's defaultUserNotificationCenter's deliverNotification_(notification)
 quit"
   activate
 end tell
 
 tell application "System Events" to tell process "AppleScript Editor"
   pick menu item "アプリケーションを実行" of menu "スクリプト" of menu bar item "スクリプト" of menu bar 1
 end tell
 
 delay 1
 
 tell application "AppleScript Editor" to close document 1 saving no

できた!(まったく実用的ではないが...)

f:id:zariganitosh:20120913103804p:image

AppleScript&RubyCocoa

  • 上記のような面倒なことをしないで、AppleScriptからRubyを実行して、RubyCocoaを使えばいいじゃないかと、ふつうは考える。
  • 当然、自分もやった。その結果どうなったかというと...

 set ruby_cocoa to "require 'osx/cocoa'
 myNotification = OSX::NSUserNotification.alloc.init
 myNotification.title = 'Hello, world!'
 myNotification.informativeText = 'Nice to meet you.'
 OSX::NSUserNotificationCenter.defaultUserNotificationCenter.deliverNotification(myNotification)"
 do shell script "/usr/bin/ruby -e " & quoted form of ruby_cocoa

error "-e:5: undefined method `deliverNotification' for nil:NilClass (NoMethodError)" number 1
  • Ruby-Cocoaアプリケーションでは動いていたコードが、エラーになってしまう...。
  • nil:NilClassに対して、deliverNotificationメソッドは定義されていない、と警告される。
$ irb
>> require 'osx/cocoa'
=> true
>> OSX::NSUserNotificationCenter.defaultUserNotificationCenter
=> nil
  • irbで試してみると、nilが返っている。
  • どうやら、アプリケーションの実行環境以外から通知センターを取得しようとしてもnilになってしまう仕様のようだ。
  • そう思って通知センターを見ると、確かにアプリケーション単位で通知が区分されている。
  • 通知センターを簡単に利用できそうな気がしてやってみたが、それほど簡単ではなかった...。

terminal-notifer

  • そうなると、頼みの綱はこれ。やはり、通知センターを利用することを目的に開発されたterminal-notiferコマンドは、使いやすい!
$ terminal-notifier -message 'Hello, world!'

できた!(とっても手軽)

f:id:zariganitosh:20120913130249p:image


  • タイトル・サブタイトルも指定する。
$ terminal-notifier -message 'Hello, world!' -title 'タイトル' -subtitle 'サブタイトル'

f:id:zariganitosh:20120913130316p:image


  • 同じIDの通知は上書きする。
$ terminal-notifier -message 'Hello, world! 1回目' -group 0
∗ Notification delivered.
$ terminal-notifier -message 'Hello, world! 2回目' -group 0
∗ Removing previously sent notification, which was sent on: 2012-09-13 01:59:41 +0000
∗ Notification delivered.

  • 通知をクリックしたら、アプリをアクティブにする。(この例:iTunesをアクティブにする)
$ terminal-notifier -message 'Hello, world!' -activate com.apple.iTunes

  • 通知をクリックしたら、URLを開く。(この例:ザリガニが見ていた...。を開く)
$ terminal-notifier -message 'Hello, world!' -open 'http://d.hatena.ne.jp/zariganitosh/’

  • 通知をクリックしたら、コマンドを実行する。(この例:警告音を鳴らす)
$ terminal-notifier -message 'Hello, world!' -execute 'afplay "/System/Library/Sounds/Blow.aiff"'
$ terminal-notifier -message 'Hello, world!' -execute 'osascript -e "beep"'
$ terminal-notifier -message 'Hello, world!' -execute "ruby -e \"require 'osx/cocoa'; OSX::NSSound.soundNamed('Blow').play; sleep 1\""

AppleScriptライブラリのmessageハンドラ

  • 今までgrowlnotifyを利用してきたAppleScriptのmessageハンドラは、以下のように修正した。
  • terminal-notifierで-messageの先頭が「<」で始まっていると、なぜかエラーが出て通知されなかった。
  • 上記対策のため、-messageの先頭に必ず「> 」を付加する仕様にした。

 message("タイトル", "日本国民は、恒久の平和を念願し、人間相互の関係を支配する崇高な理想を深く自覚するのであって、平和を愛する諸国民の公正と信義に信頼して、われらの安全と生存を保持しようと決意した。われらは、平和を維持し、専制と隷従、圧迫と偏狭を地上から永遠に除去しようと努めてゐる国際社会において、名誉ある地位を占めたいと思ふ。われらは、全世界の国民が、ひとしく恐怖と欠乏から免れ、平和のうちに生存する権利を有することを確認する。")
 on message(title, msg)
   try
     --msgの文字数を350文字に制限する(\"等のエスケープ記号はカウントしない)
     set msg to (msg's items 1 thru 350 as text) & "……"
   end try
   
   try
     set option to ""
     if title"" then set option to option & " -title " & quoted form of title
     do shell script "terminal-notifier -message " & quoted form of ("> " & msg) & option
   on error
     activate
     display dialog msg buttons {"OK"} default button "OK" with title title with icon 1 giving up after 4 --with icon note:=1 caution=2 stop=0
   end try
 end message

できた!

f:id:zariganitosh:20120913135356p:image

f:id:zariganitosh:20120913135354p:image


OSX標準ではなくなってしまったが、この辺が落としどころ。

ツトムツトム 2013/02/04 09:52 はじめまして。通知センターのプログラミングでとても参考になりました。

Cocoa-AppleScript Applet.app ですが、quitが必要な理由がわかりましたので自分のメモを兼ねてコメントを残したいと思います。

quitがないと通知されないとありますが、実際には通知されており通知センターを開くとメッセージが存在していました。つまり通知のポップアップが表示されていないだけのようです。

ポップアップ通知が表示されない理由はAppletがフォアグラウンドで動いている為。ポップアップ通知表示の条件はAppletがフォアグラウンドでない時です。

これを考慮して以下のようにするとフォアグランドでもポップアップ通知が表示されるようになりました。

set userNotification to current application's NSUserNotification's alloc()'s init()
set userNotification's title to "THIS IS TEST"
set userNotification's subtitle to "notification test"
set userNotification's informativeText to "This is notification test from cocoa applescript applet."
current application's NSUserNotificationCenter's defaultUserNotificationCenter's deliverNotification_(userNotification)

current application's NSUserNotificationCenter's defaultUserNotificationCenter's setDelegate_(me)

on userNotificationCenter_shouldPresentNotification_(notificationcenter, notification)
return 1
end userNotificationCenter_shouldPresentNotification_

zariganitoshzariganitosh 2013/02/04 10:16 なるほど!userNotificationCenter_shouldPresentNotification_で1=YESを返すと、常にポップアップが表示されるようになるんですね。
早速やってみると、素晴らしい!quitがなくても通知がポップアップされました。
ところが...
もう一度実行してみると、自分の環境(OSX10.8.2)では2回目以降の通知がまったく届かない状況になってしまいます。
(quitなら何度やってもちゃんとポップアップされます)
またしても疑問が残ってしまいました。
後日、もう少し検証してみようと思います。
情報ありがとうございました!

zariganitoshzariganitosh 2013/02/04 10:26 すいません、わかりました!
終了していないので、2度目以降はrunハンドラが実行されていなかったのですね。
実行中に何度でも表示されるには、以下のコードでできました。

on run
main()
end run

on reopen {}
main()
end reopen

on main()
set myNotification to current application's NSUserNotification's alloc()'s init()
set myNotification's title to "Hello, world!"
set myNotification's informativeText to "Nice to meet you."
current application's NSUserNotificationCenter's defaultUserNotificationCenter's deliverNotification_(myNotification)
current application's NSUserNotificationCenter's defaultUserNotificationCenter's setDelegate_(me)
end main

on userNotificationCenter_shouldPresentNotification_(notificationcenter, notification)
return yes
end userNotificationCenter_shouldPresentNotification_

無事、解決です!
素晴らしい情報をありがとうございました。
勉強になります。

玉ねぎ玉ねぎ 2013/12/20 16:14 10.8か10.9でAppleスクリプトにdisplay notification が追加されちょる。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/zariganitosh/20120913/user_notification_hello_world
リンク元