スクリーンセーバーの起動やスリープを阻止する [AppleScript辞書はつくれるか?]
自作のAppが起動している間だけ、スクリーンセーバーの起動をさせたくなかったり、スリープをさせないようにしたい場合があったとしたら、どうしたら良いのでしょう?
システム環境設定で動かないように設定したら、それで動かなくなりますが、自作のAppが起動している、していないにかかわらず、ずっとその状態になってします。
Appから環境設定を変更して、終了時には戻すというプログラムを組んだとしても、意図していない時にAppが強制終了してしまうと、元に戻らない場合が発生したりします。
つまり、Appに常に管理させておく必要があると思われます。
ではどのように管理をするかいくつか試してみよう。
1)定期的にスクリーンセーバーの停止命令を送ってみたら?
定期的に
|
on idle tell application "System Events" to stop current screen saver return 30 end idle |
という、スクリーンセーバーを停止するシステムイベントを送り続けてみる方法があります。
しかし、これでは起動しているスクリーンセーバーを止めるだけなので、一度スクリーンセーバーが起動してしまいます。しかも、スクリーンセーバーの起動までの時間を延長させるものではないので、環境設定で入力されている時間がくれば毎回画面がちらつく事になります。
さらに、スリープに関してはスルーなので、時間がくればコンピューターがスリープ状態に入ってしまうでしょう。
2)常にAppが演算状態にしたらどうか?
これは意味がありません。Appが何かの演算を延々としていたとしても、スクリーンセーバーはお構いなしで動いてしまいます。もし演算状態でスクリーンセーバーが起動しないのであれば、バックグラウンドで動いている、システムのアプリケーションにも反応するはずなのですが、そんなことがないのは言うまでの無いことだと思います。
3)スクリーンセーバーが起動しない条件を探してみる。
スクリーンセーバーが起動しない状態とは、ユーザーが何かしらのキーボード作業やマウス作業をしている場合か、インターネットブラウザーや動画のプレーヤーで動画を見ている状態などが考えられます。
まぁ、延々と動画を流しておくというのは、今回適切では無いので省きます。
4)疑似的にキーボード入力をしている状態を作る。
・システムイベントで疑似的に入力をし続けてみた場合
|
tell application "System Events" to keystroke "a" |
Appで他にどんな作業をしているのかが不明な状態で、文字の入力を繰り返すのは、Appでさせている別の処理を阻害してしまう可能性があるので、これは無理があるでしょう。
・システムイベントで疑似的にシフトキーを押した状態にしてみた場合。
|
tell application "System Events" to keystroke using shift down |
シフトキーではスクリーンセーバーの起動を阻止はできませんでした。
5)マウスポインターを移動してみる。
・システムイベントで疑似的にクリックをさせる
|
tell application "System Events" to click at {100, 100} |
マウスポインターを移動させるものでは無く、そのポイントをクリックした場所の処理をさせるだけで、実際はマウスをクリックした動作をさせているわけでは無いようで、スクリーンセーバーの起動を阻止できなかった。
アップルメニューなどのメニューバーをクリックしておけばいけるかもしれないが、メニューが開いて画面がチラついてしまうので使えるとは思えない。
・Objective-Cの力を借りてポインターを移動してみる
|
tell current application CGPostMouseEvent({x:mouseX, y:mouseY}, 1, 1, 0) end tell |
これで初めてスクリーンセーバーの起動を阻止することができました。
CGPostMouseEventを使うと、実際にマウスポインターが動きます。そしてシステムもマウスを動かした作業をしたと認識してくれているようです。
このイベントはCGPostMouseEvent({x:100, y:100}, 1, 1, 1)とすると、デスクトップの座標{x:100,y:100}でマウスの左ボタンを押した状態を再現します。
CGPostMouseEvent({x:100, y:100}, 1, 1, 0)とすると、マウスの左ボタンを離した状態を再現します。
つまり、押した状態の後に離した状態を作ってあげればクリックしたことを再現したことになります。
今回はクリックする必要はなく、離したままの状態で移動だけをすれば、なんの処理も邪魔せずスクリーンセーバーの起動を阻止できるようになるのではないでしょうか。
とは言うものの、ただマウスポインターが無差別に動いてしまう動作をしてしまうと、ユーザーが別の作業している時に邪魔になる可能性があります。
ユーザーが作業している状態、マウスが盛んに移動している場合はプログラムでは動かさないという回避行動が必要でしょう。
常にマウスポインターの位置を監視し、位置が変わっていない時のみ実行するようにします。
|
set {x:mouseX, y:mouseY} to current application's class "NSEvent"'s mouseLocation() |
これで、現在のマウスポインターがいる座標を取得し監視します。
では、実際にユーザーが作業していない(マウスが動いていない)状態になったら、マウスポインターを動かすわけですが、
マウスポインターをどのように動かすか考えてみましょう。
例えば、座標 {x:0, y:0} (画面左上)のような決まった位置に移動してしまうと、ユーザーが作業に戻ってきた時に、迷ったり困ってしまうかもしれません。
今のポインターの位置から少し右か左に動かすとした方が良いでしょう。ですが画面の端にポイントがいた場合はポインターがそれ以上動かないと言う場合がある可能性もあります。
なので、右と左に2箇所に動かしてみる方が良いと思います。
できれば、最後に元に戻っていた方が良いと思いますので、これを含めた3ヶ所ほどマウスポインターを動かしてみましょう。
|
CGPostMouseEvent({x:mouseX + 2, y:mouseY}, 1, 1, 0) CGPostMouseEvent({x:mouseX - 2, y:mouseY}, 1, 1, 0) CGPostMouseEvent({x:mouseX, y:mouseY}, 1, 1, 0) |
1行目で元の位置からX方向の +2 移動(右)
2行目で元の位置からX方向へ -2 移動(左)
3行目で元の位置に戻る動作をさせています。
以上で、マウスポインターを動かしてスクリーンセーバーが動くのを阻止することができるようになるのですが、OSのバージョンによっては不具合が発生することがあります。
不具合(1)
プログラミングをしていく上で避けられない状況での問題となるのですが、繰り返しコードを作り直して保存をし直す、入力作業&デバグ作業をすると、最初は動いていたCGPostMouseEventが、なぜか動作しなくなってしまう不具合。
これは、最初にプログラムを実行するとセキュリティーのアクセシビリティが反応して実行しても良いプログラムですか?とお伺いのダイアログが出てくると思います。
ですが、2回目以降は聞いてこないのですが、コードを変更して保存し直すとApp名は残ってるものの、実行して良いプロセスではないと静かに除外され実行されなくなります。
改善するには、一度アクセシビリティからApp名を外し再登録し直さなければいけません。
でも、気づかずになっている場合があるので実行されているかのチェックが必要になります。
|
if (my security()) then quit me
on security() set {x:mouseX, y:mouseY} to current application's class "NSEvent"'s mouseLocation() tell current application CGPostMouseEvent({x:mouseX + 2, y:mainScreenHeight - mouseY}, 1, 1, 0) end tell set {x:mouseX2, y:mouseY2} to current application's class "NSEvent"'s mouseLocation() if {mouseX, mouseY} = {mouseX2, mouseY2} then display alert "アクセシビリティをの再設定が必要です" message "システム環境設定のセキュリティー>アクセシビリティに、Appを再登録する必要があります" as warning buttons "cancel" default button 1 return true end if return false end security |
試しにマウスを動かしてみたけど座標が変わっていなかった(CGPostMouseEventが動作することができなかった)場合にアラートを表示して終了するとコードを作ります。
この後ユーザーが手動で登録し直すわけですが、将来的には安全なAppとして登録するか、又は、安全な証明書を作ってみるのも良いでしょう。
不具合(2)
古いOS(MacOSX10.13~10.14)で発生するCGPostMouseEventが認識されない現象への対応。
最近のOSでは解消されていますが、Apple developerのサイトではCGPostMouseEventが非推奨扱いになっています。そのため一度外れたのか何かは不明ですが使えなくなっていた頃がありました。
ですが、今後、再発する可能性が無いとは言い切れませんので、回避させるコードを追加しておきます。
|
set codeText to "import sys; from Quartz.CoreGraphics import CGEventCreateMouseEvent,CGEventPost,CGPointMake; " set codeText to codeText & "x = float(" & (mouseX - 2) & "); y = float(" & (mainScreenHeight - mouseY) & "); po = CGPointMake(x, y); event = CGEventCreateMouseEvent(None, 2, po, 0); CGEventPost(0, event); " do shell script "python -c " & quoted form of codeText |
python言語でコードを作り、シェルスクリプト経由で実行をします。CGPostMouseEventで動作しないと分かった時点で切り替えて実行します。
これなら、最初からCGEventCreateMouseEventを使えば良いと思われるかもしれませんが、このコードは動作が遅いので避難措置用です。
不具合(3)
不具合というのとは違うかもしれませんが、mouseLocation()で取得するY座標と、CGPostMouseEventで疑似クリックをするためのY座標が逆という仕様が存在します。
mouseLocation()ではメインスクリーンの左下が{x:0, y:0}の原点で上にのぼるほど座標が加算されていきます。
CGPostMouseEventではメインスクリーンの左上が原点で下へ降りるほど加算されます。
この逆向きの座標の数値を調整してやる必要があります。
|
set mainScreenHeight to item 2 of item 2 of ((item 1 of (current application's class "NSScreen"'s screens()))'s frame()) |
class "NSScreen"'s screens()でメインやサブを含むデスクトップのすべてのフレーム情報を取得します。
(複数のモニターがあった場合にも対応します)
(item 1 of ...)'s frame()でメインのスクリーン(主となるデスクトップ)のフレームサイズを取得します。
(複数のモニターがあってもメインのスクリーンの原点がすべてのモニター原点となります)
フレームのサイズの情報は{{原点のx座標, 原点のy座標}, {横幅, 高さ}}(例:{{0.0, 0.0}, {1920.0, 1080.0}})と二重の階層で収められています。
item 2 of item 2 of ... で2番目の情報を取得して、さらに2番目の数値を取得すると、メインスクリーンの高さが求められます。
そこから引き算でY座標を求めればクリックするy座標が得られます。
|
mainScreenHeight - mouseY |
とりあえず、ようやくこれですべて解決です。
最後に全てをまとめたソースを載せておきます。
|
(* Objective-Cのコードを実装するために追加を担う部分 *) use scripting additions use framework "Foundation" (* 初期設定とグローバル変数の有効化 *) global beforeMousePosition, mainScreenHeight, moveCode set beforeMousePosition to {0, 0} --マウスの位置が変わっているか確認するための前回座標の記録 set moveCode to 0 --CGPostMouseEventを使用するかの判断値 (* メインスクリーンのデスクトップの高さの取得 *) set mainScreenHeight to item 2 of item 2 of ((item 1 of (current application's class "NSScreen"'s screens()))'s frame()) (* クリック動作が可能になっているかチェック *) if (my security()) then quit me -- 動作していない場合は終了 return end if (* 一定時間(今回は20秒)の間隔で繰り返し実行 *) on idle my sleepStop() return 20 end idle (* CGPostMouseEventまたはCGEventCreateMouseEventが使用可能になっているかをチェック *) on security() set {x:mouseX, y:mouseY} to current application's class "NSEvent"'s mouseLocation() try tell current application CGPostMouseEvent({x:mouseX + 2, y:mainScreenHeight - mouseY}, 1, 1, 0) end tell on error set codeText to "import sys; from Quartz.CoreGraphics import CGEventCreateMouseEvent,CGEventPost,CGPointMake; " set codeText to codeText & "x = float(" & (mouseX - 2) & "); y = float(" & (mainScreenHeight - mouseY) & "); po = CGPointMake(x, y); event = CGEventCreateMouseEvent(None, 2, po, 0); CGEventPost(0, event); " do shell script "python -c " & quoted form of codeText end try set {x:mouseX2, y:mouseY2} to current application's class "NSEvent"'s mouseLocation() if {mouseX, mouseY} = {mouseX2, mouseY2} then display alert "アクセシビリティをの再設定が必要です" message "システム環境設定のセキュリティー>アクセシビリティに、Appを再登録する必要があります" as warning buttons "cancel" default button 1 return true end if return false end security (* スクリーンセーバーの実行を阻止する部分 *) on sleepStop() set {x:mouseX, y:mouseY} to current application's class "NSEvent"'s mouseLocation() set mouseY to mainScreenHeight - mouseY if beforeMousePosition = {mouseX, mouseY} then -- |マウスポインターが動いていないと判断| if moveCode = 0 then --CGPostMouseEventを使ってクリックする部分へ try tell current application CGPostMouseEvent({x:mouseX + 2, y:mouseY}, 1, 1, 0) CGPostMouseEvent({x:mouseX - 2, y:mouseY}, 1, 1, 0) CGPostMouseEvent({x:mouseX, y:mouseY}, 1, 1, 0) end tell return on error set moveCode to 1 --CGPostMouseEventが使えなかった時の行き先変更値 end try end if if moveCode = 1 then --CGPostMouseEventが使えなかった場合のクリック処理(コードが長いので分割して記述) set codeText to "import sys; from Quartz.CoreGraphics import CGEventCreateMouseEvent,CGEventPost,CGPointMake; " set codeText to codeText & "x = float(" & (mouseX + 2) & "); y = float(" & mouseY & "); po = CGPointMake(x, y); event = CGEventCreateMouseEvent(None, 2, po, 0); CGEventPost(0, event); " set codeText to codeText & "x = float(" & (mouseX - 2) & "); y = float(" & mouseY & "); po = CGPointMake(x, y); event = CGEventCreateMouseEvent(None, 2, po, 0); CGEventPost(0, event); " set codeText to codeText & "x = float(" & mouseX & "); y = float(" & mouseY & "); po = CGPointMake(x, y); event = CGEventCreateMouseEvent(None, 2, po, 0); CGEventPost(0, event); " do shell script "python -c " & quoted form of codeText end if else -- |マウスポインターが動いていると判断| set beforeMousePosition to {mouseX, mouseY} -- 旧座標を更新 end if return end sleepStop |
コメント 0