2010/09/24

省電力状態からの自動復帰も可能なタイマー機能

NILScriptに用意されている「Timer」クラスには、コンピュータがサスペンドやハイバネート状態になっても指定時間経過後に自動復帰させる機能が用意されています。
この機能を利用すれば、サーバが空いている時間帯を利用してのダウンロードなどの待ち時間の長い自動処理を行う際に、予定時刻を待っている間コンピュータを起動したままにしておく必要が無くなり、電力を節約できます。
この機能を利用するには、Timerオブジェクトのコンストラクタで第4引数にtrueを指定します。Timerの使用方法については、同梱のdoc\base_task.txtを参照してください。

復帰とは逆に、スクリプト上からサスペンドやハイバネートを実行するためのメソッドも用意されています。これらは「System.suspend()」や「System.hibernate()」というSystemクラスのメソッドとなっています。
また、サスペンドやハイバネート状態に移行し、指定時間後に自動復帰するという処理を行う「System.suspendFor()」や「System.hibernateFor()」というメソッドも用意されています。これらの機能の説明は、doc\base_main.txtにあります。

なお、タイマーによる復帰機能では、タイマーで指定した時間に復帰処理が始まるため、復帰が完了して予約された処理が実行されるまでには数秒~数十秒のタイムラグがあることに注意しなければなりません。
テレビの自動録画のように遅れることが許されない処理では、予約実行のタイマーとは別に、予定時刻の1~2分前に自動復帰させるためのタイマーをセットしておくとよいでしょう。
この場合、「System.awake()」というメソッドを使用すれば、単にサスペンド・ハイバネートから復帰するだけのタイマーを生成できます。

2010/09/21

プロセスのCPU使用率を監視して自動処理

先日の更新で、CPUの使用状況などを取得するためのクラスを提供する「SystemMonitor」ユニットが追加されました。
このユニットには、「CPU」、「ProcessCPU」、「Network」、「PerformanceCounter」の4つのクラスが用意されています。
「CPU」クラスでは、システム全体のCPU使用率が取得できます。マルチプロセッサの個別の稼働状況も取得可能です。
「ProcessCPU」クラスでは、指定のプロセスのCPU使用率を取得できます。
「Network」は、ネットワークインターフェイスの転送量を取得するクラスです。複数のネットワークインターフェイスの個別の転送量も取得できます。
他にも、「PerformanceCounter」を使うことで、HDDの読み書き量など様々な情報を取得できます。
また、標準クラスである「System」には「memory」メンバとしてシステムのメモリ使用状況を取得するメンバが追加された他、以前からProcessインスタンスの「pagefileUsage」プロパティや「workingSetSize」プロパティでプロセスごとのメモリ使用状況を取得可能になっています。

これらの機能は、タスクマネージャのような情報表示だけでなく、自動処理を進める条件などとしても役立つでしょう。
例えば、動画のエンコードなどの時間のかかる処理が終ってから別の処理を自動実行させたいけれどエンコードソフトがプロセスの存続やウィンドウのテキストなどによる終了判定がしにくい仕様になっている場合に、プロセスのCPU使用率が低くなった時点で処理完了とみなすといった具合です。

このような場合、以下の例のようなスクリプトで、処理の完了まで待機させることが出来ます。
var p=Process.find("VirtualDub.exe");
try{
    var mon=new (require('SystemMonitor').ProcessCPU)(p);
    sleep(1000);
    while(mon.get()>0.05){
        sleep(1000);
    }
}finally{
    free(mon);
}
sleep(10000);
//以降にエンコード終了後の処理を記述

最初の行で、監視対象となるProcessオブジェクトを取得しています。
この例では、既に実行中のプロセスを取得する「Process.find()」を使っていますが、「Process.create()」でプロセスの起動から自動化してもよいでしょう。
「var mon=new (require('SystemMonitor').ProcessCPU)(p);」で、そのプロセスのCPU使用率を取得するためのProcessCPUオブジェクトを生成しています。
プロセスのCPU使用率を取得するには、このオブジェクトの「get()」メソッドを呼び出します。
このメソッドは、前回呼び出し時かインスタンス生成時から今回呼び出し時までの期間のCPU使用率を返すので、インスタンス後に即座に呼び出してしまうと正常な値が取得できないため、最初のget()呼び出しの前にもsleep()が呼び出されるようにしておく必要があります。
get()の返り値は0~1の間の小数となるので、上記の例ではCPU使用率が5%以下になるまでループし続けることになります。
CPU使用率が低くなった直後は、まだファイルへの書き込みなどの処理が行われている可能性があるので、数秒程度sleep()してから次の処理に進むようにするとよいでしょう。
なお、「free(mon)」は、監視オブジェクトで使用したリソースを解放するための処理ですが、一連の処理を実行してすぐに終了するタイプのスクリプトでは、省略しても問題は無いでしょう。

なお、CPUやNetwork、PerformanceCounterクラスも、インスタンスを生成してget()で値を取得するという流れはProcessCPUと同じです。
コンストラクタに渡す引数やget()が返す内容などの詳しい仕様は、同梱のdoc\SystemMonitor.txtを参照してください。

2010/09/01

クリックしたウィンドウのクラス名をコピーするスクリプト

Hotstrokesの条件割り当てなどでは、ウィンドウの「クラス名」を指定する事がよくあります。
ウィンドウのクラス名は、Windowsのウィンドウの種類を表す文字列です。NILScriptに用意されているクラスの仕組みとは、特に関係はありません。
タイトルバーの文字列と違って、クラス名は画面に表示されたりしないので、調べるには何らかのツールを使う必要があります。
そこで、クリックしたウィンドウのクラス名をクリップボードにコピーするスクリプトの例を紹介します。
use('Mouse','Window','Clipboard');
Mouse.wait('lbuttonUp');
Clipboard.text=Window.fromPoint(Mouse.x,Mouse.y).className;
実行すると「Mouse.wait('lbuttonUp');」によってマウスの左ボタンが押されるまで待機状態になるので、クラス名を調べたいウィンドウをクリックしてください。
次の行では、クリップボードにテキストとしてウィンドウのクラス名がセットされます。
「Window.fromPoint()」は、デスクトップ全体における指定座標に存在する最も前面のウィンドウを表すWindowオブジェクトを返すクラスメソッドで、Windowオブジェクトの「className」プロパティでクラス名を取得できます。

最小化されたウィンドウをタスクトレイに格納する

NILScriptでは「Main.createNotifyIcon()」でスクリプトの終了などの操作を行うためのタスクトレイアイコンを生成できますが、他にも任意の数のタスクトレイアイコンを生成できます。
これを利用し、特定の種類のウィンドウが最小化された時にそのウィンドウをタスクバーに表示されないようにして代わりにタスクトレイアイコンとして表示するというスクリプトの例を紹介します。

Main.createNotifyIcon();
var targetClasses=['Notepad','MSPaintApp'];
var {Window}=require('Window');
var prev=Window.active;
var myWindow=Window.create();
Window.observe('activate',function(o){
    if(prev.minimized && (o.window.handle!=prev.handle) && targetClasses.contains(prev.className)){
        var w=prev;
        w.hide();
        var restore=function(){
            w.show();
            w.restore();
        };
        Main.observe('exit',restore);
        myWindow.addNotifyIcon({
            icon:w.icon,
            text:w.title,
            events:{
                lbuttonUp:function(){
                    restore();
                    Main.unobserve('exit',restore);
                    this.free();
                },
            },
        });
    }
    prev=o.window;
});

「Window.observe()」でウィンドウの状態変化を監視するイベントハンドラを登録しています。
Window.observe()が利用しているシェル用のフック機能では、最小化を直接検出することは出来ないようなので、アクティブウィンドウが変った時に前にアクティブだったウィンドウの状態を調べるという方法で代用しています。
最小化ボタンによる通常の最小化ならば、この方法で問題なく検出できるようです。

Window.observe()では、イベントハンドラの第1引数に与えられるオブジェクトの「window」メンバでイベントが発生したウィンドウを参照できますが、ここでは「prev」変数に代入されている直前にアクティブだったウィンドウの方が対象となります。prevのウィンドウが最小化されていなかったり、対象クラス名に一致しない場合は、単にprev変数に今回アクティブ化されたウィンドウを代入して終ります。
なお、prev変数は最初の定義時にアクティブウィンドウを表す「Window.active」で初期化してあります。

対象ウィンドウが最小化された場合、ウィンドウの不可視化、スクリプト終了時にウィンドウを復元する処理の登録、トレイアイコンの登録の3つの処理を行います。
その前に、最初の「var w=prev」で、その時最小化されたウィンドウを表すオブジェクトを変数に代入しておく必要があります。こうしておかないと、prev変数が書き換わってしまい、どのウィンドウを対象にしていたのか分からなくなってしまうからです。
このローカル変数「w」はJavaScriptの「クロージャ」という仕組みでrestoreやlbuttonUpにセットした関数から参照されており、次に最小化時処理が行われる時には、新たなw変数が生成され、新たに生成されたrestoreやlbuttonUpの関数から参照されます。

ウィンドウの可視化と最小化からの復元を行う処理は、関数として「restore」変数に代入して、「Main.observe('exit',restore)」でスクリプト終了時にも実行されるようにイベント割り当てを行っておきます。

トレイアイコンの登録は、自前ウィンドウのオブジェクトの「addNotifyIcon()」メソッドで行います。ここでは、あらかじめWindow.create()で生成して「myWindow」変数に代入しておいた空の自前ウィンドウを使用しています。
引数となるオプションオブジェクトのiconメンバには、今回最小化されたウィンドウのアイコンである「w.icon」を指定しています。ここでは、他にもアイコンファイルや実行ファイルのパスを指定することも可能です。
トレイアイコンの操作イベントに対する動作割り当てを定義するeventsでは、左クリック時に発生するlbuttonUpイベントに対して、復元関数の呼び出しとスクリプト終了時のイベント登録の解除、トレイアイコン自身の削除の3つの処理を行う関数を割り当てています。

このトレイアイコン生成機能を利用すれば、様々なツールを手軽に実現できるでしょう。

選択テキストをWeb検索するHotstrokes定義

選択中の文字列をパラメータにして検索サイトのURLを開くなど、エディタやブラウザでの選択文字列に対して処理を行いたいことはよくあります。

NILScriptでは、「Window」ユニットの「Edit」クラスに用意されている「selection」プロパティで選択文字列の読み書きを行なえますが、エディットコントロール側がエディタ関連のウィンドウメッセージに応答するように作られている必要があり、全ての選択テキストに対して有効なわけではありません。
エディタ関連メッセージに未対応のテキストエディタやブラウザなどからは、別の方法で選択テキストを取得しなければなりません。
一番汎用的だと思われるのは、Ctrl+Cなどを送信してコピーを実行し、クリップボードにコピーされたテキストを取得する方法でしょう。

下記に挙げるのが、エディタ関連メッセージに対応したコントロールではselection、それ以外のコントロールではクリップボードを使用して選択テキストを取得しGoogle検索するという機能をHotstrokesに割り当てるスクリプトです。
Main.createNotifyIcon();
var {Clipboard}=require('Clipboard');
var {Hotstrokes}=require('Hotstrokes');
var {Edit}=require('Window');
Hotstrokes.defineConditions({
 'Editor':function()(['Edit','TEditorX'].contains(this.focusedWindow.className)),
}).map({
 'Win+G':{
  'Editor':function(){
   this.cancel();
   exec('http://www.google.com/search?q='+encodeURIComponent(Edit.focused.selection));
  },
  '':function(){
   this.cancel();
   Thread.create(function(){
    var bk=Clipboard.text;
    Hotstrokes.send("Ctrl+C");
    sleep(100);
    exec('http://www.google.com/search?q='+encodeURIComponent(Clipboard.text));
    if(bk){
     Clipboard.text=bk;
    }
   });
  },
 },
}).register();
最初のdefineConditions()では、「Editor」という条件名にエディタ関連メッセージに対応したコントロールのクラス名を条件とする関数を定義しています。
「contains()」は、引数で指定した値を要素として持つかどうかを返す配列の拡張メソッド、「this.focusedWindow.className」は、ストロークが受理された時にフォーカスを持っているコントロールのクラス名を得る式です。
上記の例では、メモ帳などで使われる基本的なテキスト編集欄であるEditや、VxEditorで使われているTEditorXを対象として定義しています。

map()では、Windowsキー+Gに対して、条件に応じた動作を割り当てています。
「this.cancel()」というのは、Gキーの押し下げストロークの本来の動作を無効化するという動作です。これがないと、「g」という文字が入力されて選択テキストが上書きされてしまうでしょう。

Editorの場合の動作では「Edit.focused.selection」でフォーカスのあるエディタの選択テキストを取得しています。「Edit.forused」は、フォーカスのあるコントロールをEditクラスのインスタンスとして取得するというEditクラスのプロパティです。
exec()は、システムの関連付けに従ってファイルやURLを開く関数、encodeURIComponent()はURLのパラメータとして使えるように文字列をエンコードするJavaScript標準の関数です。


他の条件に一致しなかった時に参照されるデフォルト動作である「''」に対する割り当てでは、this.cancel()以外を「Thread.create()」によって新規スレッドで実行しています。
sleep()などを含み時間がかかる処理は、そのまま実行すると動作が不安定になってしまう場合があるので、このようにして新規スレッドで実行させるように習慣づけておくとよいでしょう。
スレッド内では、クリップボードのテキストを変数にバックアップし、Ctrl+Cを送信し、コピー処理が完了するのをsleep()で待って、検索結果のURLを開き、バックアップしたテキストがあれば書き戻すという処理を行っています。