マウスを自在にコントロールするAppleScript

前回からの続き。
AppleScriptからマウスイベントを徹底的にコントロールする方法をある程度理解できたので、ハンドラにまとめてみた。数か月後に別人となった自分のために、自分用のAppleScriptライブラリに_mouse.scptとして追加しておいた。

AppleScriptコード


-------- CGEventType --------
property kCGEventNull : 0
property kCGEventLeftMouseDown : 1
property kCGEventLeftMouseUp : 2
property kCGEventRightMouseDown : 3
property kCGEventRightMouseUp : 4
property kCGEventMouseMoved : 5
property kCGEventLeftMouseDragged : 6
property kCGEventRightMouseDragged : 7
property kCGEventKeyDown : 10
property kCGEventKeyUp : 11
property kCGEventFlagsChanged : 12
property kCGEventScrollWheel : 22
property kCGEventTabletPointer : 23
property kCGEventTabletProximity : 24
property kCGEventOtherMouseDown : 25
property kCGEventOtherMouseUp : 26
property kCGEventOtherMouseDragged : 27
property kCGEventTapDisabledByTimeout : "0xFFFFFFFE"
property kCGEventTapDisabledByUserInput : "0xFFFFFFFF"
-------- CGEventType --------

-------- CGMouseButton --------
property kCGMouseButtonLeft : 0
property kCGMouseButtonRight : 1
property kCGMouseButtonCenter : 2
-------- CGMouseButton --------

-------- CGEventFlags --------
property kCGEventFlagMaskAlphaShift : 65536 --caps lock
property kCGEventFlagMaskShift : 131072
property kCGEventFlagMaskControl : 262144
property kCGEventFlagMaskAlternate : 524288 --option
property kCGEventFlagMaskCommand : 1048576
property kCGEventFlagMaskHelp : 4194304
property kCGEventFlagMaskSecondaryFn : 8388608 --fn
property kCGEventFlagMaskNumericPad : 2097152
property kCGEventFlagMaskNonCoalesced : 256
-------- CGEventFlags --------
-------- CGEventFlagsの短縮形 --------
property command : kCGEventFlagMaskCommand
property option : kCGEventFlagMaskAlternate
property shift : kCGEventFlagMaskShift
property control : kCGEventFlagMaskControl
property fn : kCGEventFlagMaskSecondaryFn
-------- CGEventFlagsの短縮形 --------

-------- CGEventField --------
property kCGMouseEventClickState : 1
-------- CGEventField --------

(* サンプルコード
CGEventPost(kCGEventLeftMouseDown, 27, 12, kCGMouseButtonLeft, option + shift, 1) --optionshiftを押しながら、アップルメニューでマウスボタンを押す
CGEventPost(kCGEventLeftMouseUp, 27, 12, kCGMouseButtonLeft, option + shift, 1) --optionshiftを押しながら、アップルメニューでマウスボタンを放す
*)
--RubyCocoaを利用して、AppleScriptから最もローレベルなマウスイベントを発行する
--(最もローレベルなマウスイベント=マウスボタンを押す、放す、移動するなど)
--引数:
--●CGEventType : kCGEventLeftMouseDown, kCGEventMouseMovedなど
--●x,y : マウスカーソルの座標
-- 例:27,12 数値による絶対座標指定
-- 例:"m+100","m-5" mを含めた相対座標指定(mは現在のカーソル位置のx座標・y座標に変換される)
--●CGMouseButton : 押されたマウスボタンの番号
-- 下記のCGEventTypeの時だけ有効になる。下記以外の場合は無視される。
-- kCGEventOtherMouseDown, kCGEventOtherMouseDragged, kCGEventOtherMouseUp
--●click_count : クリック回数(MouseDown時に指定した回数が有効になる)
--●CGEventFlags : 修飾キーの状態
-- 例:option + shift optionキーとshiftキーを押しながらマウスを操作する扱いになる
on CGEventPost(CGEventType, x, y, CGMouseButton, click_count, CGEventFlags)
set ruby_cocoa to "require 'osx/cocoa' loc=OSX::CGEventGetLocation(OSX::CGEventCreate(nil)) x=eval('" & x & "'.sub(/m/,loc.x.to_s)) y=eval('" & y & "'.sub(/m/,loc.y.to_s)) point=OSX::CGPointMake(x,y) event=OSX::CGEventCreateMouseEvent(nil, " & CGEventType & ", point, " & CGMouseButton & ") OSX::CGEventSetIntegerValueField(event, " & kCGMouseEventClickState & ", " & click_count & ") OSX::CGEventSetFlags(event, " & CGEventFlags & ") OSX::CGEventPost(0,event)"
do shell script "/usr/bin/ruby -e " & quoted form of ruby_cocoa
end CGEventPost

(* サンプルコード
mouse_move(26, 12) --マウスカーソルをアップルメニューへ移動する
*)
--マウスカーソルを移動する
on mouse_move(x, y)
CGEventPost(kCGEventMouseMoved, x, y, kCGMouseButtonLeft, 0, 0)
end mouse_move

(* サンプルコード
left_click(26, 12) --アップルメニューをクリック
left_click("m", "m") --現在のカーソル位置をクリック
left_click("m+" & 100, "m") --現在のカーソル位置から右に100px移動した位置をクリック
*)
--左クリックする
on left_click(x, y)
modifire_left_click(x, y, 1, 0)
end left_click

--右クリックする
on right_click(x, y)
modifire_right_click(x, y, 1, 0)
end right_click

(* サンプルコード
center_click("m", "m",) --現在のカーソル位置で中央クリック
*)
--中央クリックする
on center_click(x, y)
modifire_other_click(x, y, 1, 0, kCGMouseButtonCenter)
end center_click

(* サンプルコード
left_drag(28, 13, 44, 150) --アップルメニューをクリックして、最近使った項目までドラッグして離す
*)
--左ボタンを押しながらドラッグして離す
on left_drag(x1, y1, x2, y2)
modifire_left_drag(x1, y1, x2, y2, 0)
end left_drag

(* サンプルコード
modifire_left_click(26, 12, 1, option + shift) --optionshiftを押しながら、アップルメニューをクリック
modifire_left_click("m", "m", 2, 0) --現在のカーソル位置で左ダブルクリック
modifire_left_click("m", "m", 3, 0) --現在のカーソル位置で左トリプルクリック
*)
--修飾キーを押しながら、左ボタンをクリック・ダブルクリック等する
on modifire_left_click(x, y, click_count, modifire_key)
CGEventPost(kCGEventLeftMouseDown, x, y, kCGMouseButtonLeft, click_count, modifire_key)
delay 0.5
CGEventPost(kCGEventLeftMouseUp, "m", "m", kCGMouseButtonLeft, click_count, modifire_key)
end modifire_left_click

--修飾キーを押しながら、右ボタンをクリック・ダブルクリック等する
on modifire_right_click(x, y, click_count, modifire_key)
CGEventPost(kCGEventRightMouseDown, x, y, kCGMouseButtonRight, click_count, modifire_key)
delay 0.5
CGEventPost(kCGEventRightMouseUp, "m", "m", kCGMouseButtonRight, click_count, modifire_key)
end modifire_right_click

(* サンプルコード
modifire_other_click("m", "m", 1, 0, 0) --mightyマウスの左クリック
modifire_other_click("m", "m", 1, 0, 1) --mightyマウスの右クリック
modifire_other_click("m", "m", 1, 0, 2) --mightyマウスの中央クリック
modifire_other_click("m", "m", 1, 0, 3) --mightyマウスのサイドクリック
*)
--修飾キーを押しながら、マウスのボタン番号を指定してクリック・ダブルクリック等する
on modifire_other_click(x, y, click_count, modifire_key, CGMouseButton)
CGEventPost(kCGEventOtherMouseDown, x, y, CGMouseButton, click_count, modifire_key)
delay 0.5
CGEventPost(kCGEventOtherMouseUp, "m", "m", CGMouseButton, click_count, modifire_key)
end modifire_other_click

(* サンプルコード
modifire_left_drag(28, 13, 35, 38, option) --アップルメニューをクリックして、システム情報...までドラッグして離す
*)
--修飾キーを押しながら、左ボタンを押しながらドラッグして離す
on modifire_left_drag(x1, y1, x2, y2, modifire_key)
CGEventPost(kCGEventLeftMouseDown, x1, y1, kCGMouseButtonLeft, 0, modifire_key)
CGEventPost(kCGEventLeftMouseDragged, x2, y2, kCGMouseButtonLeft, 0, modifire_key)
CGEventPost(kCGEventLeftMouseUp, x2, y2, kCGMouseButtonLeft, 0, modifire_key)
end modifire_left_drag

(* サンプルコード
mouse_location() --結果:{x:28, y:14} *)
--マウスカーソルの座標(左上原点)を返す
on mouse_location()
set ruby_cocoa to "require 'osx/cocoa' pt=OSX::CGEventGetLocation(OSX::CGEventCreate(nil)) puts pt.x, pt.y"
set pt to do shell script "/usr/bin/ruby -e " & quoted form of ruby_cocoa
{x:pt's paragraph 1 as number, y:pt's paragraph 2 as number}
end mouse_location

  • CGEventPostハンドラによって、RubyCocoaを利用して、AppleScriptからローレベルなマウスイベントを発行する。
    • CGEventPost(CGEventType, x, y, CGMouseButton, click_count, CGEventFlags)
  • CGEventPostハンドラを使えば、かなり詳細なローレベルイベントを発行できるが、引数が多くて扱いにくい...。
  • 引数を少なくして簡単に扱える簡易ハンドラも用意した。
    • mouse_move(x, y)
    • left_click(x, y)
    • right_click(x, y)
    • center_click(x, y)
  • さらに修飾キーやダブルクリック・トリプルクリックなども扱えるようにした簡易ハンドラ。
    • modifire_left_click(x, y, click_count, modifire_key)
    • modifire_right_click(x, y, click_count, modifire_key)
    • modifire_other_click(x, y, click_count, modifire_key, CGMouseButton)
  • マウスの座標だけ取得したこともあるかと思いマウス座標を返すハンドラ。
    • mouse_location()

悩みどころ

  • ObjectiveCのCocoa環境からは、kCGEventLeftMouseDownのような定数に簡単にアクセスできるのだが、RubyCocoaからはどうやってアクセスすれば良いのだろう?
  • 結局分からなくて、以下のようなNSLog出力を実行して値を取得し、AppleScriptのプロパティとしてしまった...。
  • だから_mouse.scptにはプロパティが異常に多いのだ。RubyCocoaから定数にアクセスできればなくせるのに...。
//
//  main.m
//  kCGEventLeftMouseDown
//
//  Created by zarigani on 12/01/25.
//  Copyright 2012 __MyCompanyName__. All rights reserved.
//

#import <Cocoa/Cocoa.h>

int main(int argc, char *argv[])
{
    NSLog(@"kCGEventNull: %u", kCGEventNull);
    NSLog(@"kCGEventLeftMouseDown: %u", kCGEventLeftMouseDown);
    NSLog(@"kCGEventLeftMouseUp: %u", kCGEventLeftMouseUp);
    NSLog(@"kCGEventRightMouseDown: %u", kCGEventRightMouseDown);
    NSLog(@"kCGEventRightMouseUp: %u", kCGEventRightMouseUp);
    NSLog(@"kCGEventMouseMoved: %u", kCGEventMouseMoved);
    NSLog(@"%kCGEventLeftMouseDragged: u", kCGEventLeftMouseDragged);
    NSLog(@"kCGEventRightMouseDragged: %u", kCGEventRightMouseDragged);
    NSLog(@"kCGEventKeyDown: %u", kCGEventKeyDown);
    NSLog(@"kCGEventKeyUp: %u", kCGEventKeyUp);
    NSLog(@"kCGEventFlagsChanged: %u", kCGEventFlagsChanged);
    NSLog(@"kCGEventScrollWheel: %u", kCGEventScrollWheel);
    NSLog(@"kCGEventTabletPointer: %u", kCGEventTabletPointer);
    NSLog(@"kCGEventTabletProximity: %u", kCGEventTabletProximity);
    NSLog(@"kCGEventOtherMouseDown: %u", kCGEventOtherMouseDown);
    NSLog(@"kCGEventOtherMouseUp: %u", kCGEventOtherMouseUp);
    NSLog(@"kCGEventOtherMouseDragged: %u", kCGEventOtherMouseDragged);
    NSLog(@"kCGEventTapDisabledByTimeout: %u", kCGEventTapDisabledByTimeout);
    NSLog(@"kCGEventTapDisabledByUserInput: %u", kCGEventTapDisabledByUserInput);

    NSLog(@"kCGEventFlagMaskAlphaShift: %u", kCGEventFlagMaskAlphaShift);
    NSLog(@"kCGEventFlagMaskShift: %u", kCGEventFlagMaskShift);
    NSLog(@"kCGEventFlagMaskControl: %u", kCGEventFlagMaskControl);
    NSLog(@"kCGEventFlagMaskAlternate: %u", kCGEventFlagMaskAlternate);
    NSLog(@"kCGEventFlagMaskCommand: %u", kCGEventFlagMaskCommand);
    NSLog(@"kCGEventFlagMaskHelp: %u", kCGEventFlagMaskHelp);
    NSLog(@"kCGEventFlagMaskSecondaryFn: %u", kCGEventFlagMaskSecondaryFn);
    NSLog(@"kCGEventFlagMaskNumericPad: %u", kCGEventFlagMaskNumericPad);
    NSLog(@"kCGEventFlagMaskNonCoalesced: %u", kCGEventFlagMaskNonCoalesced);
    return NSApplicationMain(argc,  (const char **) argv);
}
  • 工夫も何もなく、ただひたすら定数の値をNSLog出力する、やっつけコードなのであった。
run
[Switching to process 19005]
実行中...
2012-01-26 0:32:52.467 kCGEventLeftMouseDown[19005:a0f] kCGEventNull: 0
2012-01-26 0:32:52.470 kCGEventLeftMouseDown[19005:a0f] kCGEventLeftMouseDown: 1
2012-01-26 0:32:52.471 kCGEventLeftMouseDown[19005:a0f] kCGEventLeftMouseUp: 2
2012-01-26 0:32:52.471 kCGEventLeftMouseDown[19005:a0f] kCGEventRightMouseDown: 3
2012-01-26 0:32:52.472 kCGEventLeftMouseDown[19005:a0f] kCGEventRightMouseUp: 4
2012-01-26 0:32:52.473 kCGEventLeftMouseDown[19005:a0f] kCGEventMouseMoved: 5
2012-01-26 0:32:52.474 kCGEventLeftMouseDown[19005:a0f] kCGEventLeftMouseDragged: u
2012-01-26 0:32:52.474 kCGEventLeftMouseDown[19005:a0f] kCGEventRightMouseDragged: 7
2012-01-26 0:32:52.485 kCGEventLeftMouseDown[19005:a0f] kCGEventKeyDown: 10
2012-01-26 0:32:52.486 kCGEventLeftMouseDown[19005:a0f] kCGEventKeyUp: 11
2012-01-26 0:32:52.486 kCGEventLeftMouseDown[19005:a0f] kCGEventFlagsChanged: 12
2012-01-26 0:32:52.487 kCGEventLeftMouseDown[19005:a0f] kCGEventScrollWheel: 22
2012-01-26 0:32:52.488 kCGEventLeftMouseDown[19005:a0f] kCGEventTabletPointer: 23
2012-01-26 0:32:52.488 kCGEventLeftMouseDown[19005:a0f] kCGEventTabletProximity: 24
2012-01-26 0:32:52.489 kCGEventLeftMouseDown[19005:a0f] kCGEventOtherMouseDown: 25
2012-01-26 0:32:52.489 kCGEventLeftMouseDown[19005:a0f] kCGEventOtherMouseUp: 26
2012-01-26 0:32:52.490 kCGEventLeftMouseDown[19005:a0f] kCGEventOtherMouseDragged: 27
2012-01-26 0:32:52.490 kCGEventLeftMouseDown[19005:a0f] kCGEventTapDisabledByTimeout: 4294967294
2012-01-26 0:32:52.491 kCGEventLeftMouseDown[19005:a0f] kCGEventTapDisabledByUserInput: 4294967295
2012-01-26 0:32:52.492 kCGEventLeftMouseDown[19005:a0f] kCGEventFlagMaskAlphaShift: 65536
2012-01-26 0:32:52.492 kCGEventLeftMouseDown[19005:a0f] kCGEventFlagMaskShift: 131072
2012-01-26 0:32:52.493 kCGEventLeftMouseDown[19005:a0f] kCGEventFlagMaskControl: 262144
2012-01-26 0:32:52.494 kCGEventLeftMouseDown[19005:a0f] kCGEventFlagMaskAlternate: 524288
2012-01-26 0:32:52.495 kCGEventLeftMouseDown[19005:a0f] kCGEventFlagMaskCommand: 1048576
2012-01-26 0:32:52.495 kCGEventLeftMouseDown[19005:a0f] kCGEventFlagMaskHelp: 4194304
2012-01-26 0:32:52.496 kCGEventLeftMouseDown[19005:a0f] kCGEventFlagMaskSecondaryFn: 8388608
2012-01-26 0:32:52.497 kCGEventLeftMouseDown[19005:a0f] kCGEventFlagMaskNumericPad: 2097152
2012-01-26 0:32:52.497 kCGEventLeftMouseDown[19005:a0f] kCGEventFlagMaskNonCoalesced: 256

Debugger stopped.
Program exited with status value:0.