kkAyatakaのメモ帳。

誰かの役に立つかもしれない備忘録。

デスクトップを指定してプロセスを起動する

プロセスを起動する際は、通常、元のWindow Stationとデスクトップを引き継ぎますが、CreateProcessではこれらを明示的に指定できるようになっています。

STARTUPINFO si = {};
si.cb = sizeof(si);
si.lpDesktop = "WinSta0\\AltDesktop";

PROCESS_INFORMATION pa = {};

CreateProcess(
  NULL,
  "C:\\Windows\\explorer.exe",
  NULL,
  NULL,
  FALSE,
  0x00000020,
  NULL,
  NULL,
  &si,
  &pa
  );

あんまSTARTUPINFOの中身までみませんよね。ただ、これでDesktopを指定して動かすことが出来ます。

新しいデスクトップでExplorerを動かす

CreateDesktopで作成したデスクトップはまっさらで、何も表示されず、普通に使うことが出来ません。たまーにWindowsはこんな感じになりますが、これは、そのデスクトップ用のExplorerが動いていないことが起因しています。

そこで、上記を用いて、Explorerを動かしてやることで、まともに使えるデスクトップにしてやります。

また、3秒で戻ってきましょう(ただ、今回は、がんばれば戻ってこれます)。

#include <Windows.h>

int main() {
  HDESK current = OpenInputDesktop(0, FALSE, GENERIC_ALL);

  HDESK desk = CreateDesktop("AltDesktop", NULL, NULL, 0, GENERIC_ALL, NULL);

  SwitchDesktop(desk);

  STARTUPINFO si = {};
  si.cb = sizeof(si);
  si.lpDesktop = "WinSta0\\AltDesktop";

  PROCESS_INFORMATION pa = {};

  CreateProcess(
    NULL,
    "C:\\Windows\\explorer.exe", // 64-bit OSは64-bit版を
    NULL,
    NULL,
    FALSE,
    0x00000020, // NORMAL_PRIORITY_CLASS
    NULL,
    NULL,
    &si,
    &pa
    );

  Sleep(3 * 1000);

  SwitchDesktop(current);

  CloseHandle(pa.hThread);
  CloseHandle(pa.hProcess);
}

少し注意するのは64-bit OSの場合は64-bit版のExplorerを起動することです。上記のようにフルパス指定なら問題ないですが、explorer.exeと指定した場合、プロセスのアーキテクチャに依存して起動します。タスクバーが表示されないので、間違えるとやや詰みます。

Windows 7の場合、追加したデスクトップではAeroが切れます。Windows 8ではAeroも動きますが、まあ、逆に見分けづらかったりしますね。

魔法のWindow Message

Windows 8の場合、単にExplorerを起動しただけではタスクバーがうまく表示されません。ただ、動く条件はありそうだったので、Spy++で調べたりしながら試したところ、次のようにWindow Messageを送ることでうまくいきました。

HWND wnd = FindWindow("Shell_TrayWnd", NULL);
PostMessage(wnd, 0x574, 0x02, 0x00);

まあ、無理やり探し出しているので、良い方法とは言えんです。製品作ったりするならサポートにきいたほうが良いでしょうね。

参考

Window Stationとデスクトップ

Windowsのデスクトップはそもそも一つではありません。デスクトップはWindow Stationによって管理されており、既定でDefault、WinLogon、ScreenSaverがあることになっています。

プロセスはWindow Stationに、スレッドはデスクトップにひも付く感じになっており、それぞれ制御用の関数で切り替えが可能です。

f:id:kkAyataka:20141010213758j:plain

こんな感じなんで、1プロセスで複数のデスクトップに関連付いたアプリを書くことが出来ます。まあ、デスクトップを切り替えるアプリ以外不要でしょうけど...

スレッドはSetThreadDesktopで動作するデスクトップを切り替え可能ですが、UIスレッド(メッセージループが動いているもの)は切り替えることが出来ません(関数が失敗する)。ただし、切り替えが出来ないだけであり、新しく作ることはできるため、複数のデスクトップで動作するウィンドウを制御することは可能です。

Windows Formsで(いろいろはしょってますが)書くと、次のような感じになります。スレッドを生成後、デスクトップを指定して、メッセージループを回すことで、複数のデスクトップにウィンドウを表示できます。

var th = new Thread(
  () =>
  {
  var desk = OpenDesktop("AltDesktop", 0, false, 0x10000000);
  SetThreadDesktop(desk);

  var form = new Form();

  Application.Run(form);
  });

th.SetApartmentState(ApartmentState.STA);
th.Start();

これを利用して、複数のデスクトップで常駐する(通知領域にアイコンを登録する)、設定画面を表示するといったことが可能になります。

通常のアプリに比べて設計が複雑になりますが、これはこれで面白いです。

なお、WPFではうまくいきませんWPFレンダリングの仕組みと、デスクトップ間でWindow Messageがやり取り出来ないことが起因していると思われます。あまり詳細に調べられなかったのですが、デスクトップが異なる場合、レンダリングスレッドの指示が届いて無いのかなーという感じでした。ウィンドウの枠とかは表示されるんですが、中身は全然ダメです。

参考

CreateDesktopによる仮想デスクトップ

Windows 10になって仮想(マルチ)デスクトップが入るみたいですが、しばらく現行Windowsにて仮想デスクトップが出来ないかいじっていました。

CreateDesktopに関しては、前々から気にしつつも見ていなかったのですが、調べてみると結構簡単でした。

/*
  1. 新しいデスクトップを作成して
  2. 切り替えて、
  3. 3秒間まって
  4. 元に戻す。
*/
#include <Windows.h>
#include <tchar.h>

int main() {
  HDESK current = OpenInputDesktop(0, FALSE, GENERIC_ALL);

  HDESK desk = CreateDesktop(_T("NewDesktop"), NULL, NULL, 0, GENERIC_ALL, NULL); // 1.

  SwitchDesktop(desk); // 2.

  Sleep(3 * 1000); // 3.

  SwitchDesktop(current); // 4.

  CloseDesktop(desk);
  CloseDesktop(current);
}

不用意に動かすと元のデスクトップに戻ってこれないので注意です。Explorerが無いので詰みます。...まあ、何度もやりました。

MSDNにまとまったドキュメントがあります。分量もそれほど多くありません。調べてみるまで全然知らなかったんですが、面白い構造になっています。

Logonとかスクリーンセーバーとか別のデスクトップだったんすねぇ~と。

ただ、まあ、コレで出来る仮想デスクトップはあまりうまくない(万人向けではない)です。一通りくみ上げて、しばらく使っていたのですが、

  1. アプリがすでに動いている場合、別のデスクトップで新しく起動しないものがある(Chrome等)
  2. デフォルトのデスクトップ以外だとNAS上のOfficeファイルが開けない場合がある

1.はアプリのつくりに依存します。UIスレッドは所属するデスクトップを切り替えられないといった制限もあるので(つまり、ウィンドウはデスクトップ間を移動できない)、使い方に制限が出来ます。

2.に関して、通常設定のOfficeはNAS上のファイルを開く際、警告が出て編集できない状態で開きます。この状態のファイルは、デフォルト以外のデスクトップではうまく開けません。一旦警告を確認して、編集可能状態にすれば問題なく開けるようになります。

こんな感じで、分かって使う分には問題ないんですが、広く一般に使ってもらうのは厳しい感じになります。ただ、分かって使う分には作業コンテキストをズバッと切り替えられるので、便利なんですけどね。

CreateDesktopを使わずに、仮想デスクトップを実現するアプリもあって、Windowsではそっちのが主流っぽいです。実装のアイデアが浮かんだので、そちらも軽く実装してみましたが、確かに筋はよさそうに感じました。まあ、個人的にはCreateDesktopを用いたほうが好みですね。

Windows 10はまだ見ていませんが、どんな感じに実装されてるのかなーと見てみるのも面白そうです。

Epic -> Reach

背景を白にしたEpicのまっさらさが結構好きだったんですが、Reachに変更しました。

多少でもカスタムできたらよかったのですが、時間がかかりそうだったので断念。

前々から、はてなブログに書いて、Evernoteにクリップという方法で、整形されたノートを作っていたのですが、Epicだと日付が入んないんですよね。

f:id:kkAyataka:20140424222252p:plain

クリップした日付は入るし、まあ、いいかぁと思っていたんですが、一時期うまく抜けなかったりして、再クリップが発生したので、やっぱまずいなぁと。どの程度の鮮度かってやっぱり重要ですし。

f:id:kkAyataka:20140424222905p:plain

Reachもシンプルですし、これもEvernoteでうまくくりぬけるので、良い感じです。

argv

Node.jsで組んで、必要なツールもNode.jsで書くのが楽だなぁと。ロジックもそのまま使えるので。

そうすると、引数の処理どうしようかなぁとなりますが、argvがヘルプを自動的に作ってくれていい感じでした。

var argv = require('argv');

argv.option({
  name: 'option',
  short: 'o',
  type: 'boolean',
  description: 'Description',
  example: 'Example'
});
    
var args = argv.run(); // 引数を処理
var opts = args.options;
var tars = args.targets;

if (opts.option) {
  console.log(opts, tars);
}
else {
  argv.help(); // ヘルプの表示
}

ヘルプ表示は以下のような感じ。引数なんかはすぐに忘れるので、非常に助かる。

$ node app.js

Usage: app.js [options]

    --help, -h
        Displays help information about this script
        'app.js -h' or 'app.js --help'

    --option, -o
        Description
        Example

更新が停滞してるっぽいし、引数処理は他にもいろいろあるし、ヘルプ表示がイマイチ好みで無かったりしますが、簡単に使えるので、ちょっとしたツールには十分です。

Nodemailer

Node.jsからちょっとメール使って通知したいなーとか思ったときに便利に使えます。

  • CentOS 6.5 x86_64
  • Node.js v0.10.28
  • Nodemailer v0.6.3

公式の説明が丁寧なので、簡単に使えますね。

Gmailなどを利用しての送信もできますが、サーバーからの通知であれば、sendmailを使って送るのが楽かなぁと思います。

var nodemailer = require("nodemailer");

var transport = nodemailer.createTransport("sendmail");

transport.sendMail({
  from: "nodemailer@server",
  to: "sample@sample.com",
  bcc: "sample@sample.com",
  subject: "Mail Subject",
  text: "Message body"
 });

EPELリポジトリを追加する

CentOSでtmux使いたいよーっと。さくらのVPSだと最初から入っているので、別のクリーン環境で混乱しました...

EPELに関しては以下を参照。

手順は以下の通り。

  1. rpmパッケージのDL
  2. rpmパッケージのインストール

/etc/yum.repos.dに手書きで追加すると思っていたんだけど、rpm落としてインストールすんのね。

$ wget http://ftp.riken.jp/Linux/fedora/epel/6/i386/epel-release-6-8.noarch.rpm
$ sudo rpm -Uvh epel-release-6-8.noarch.rpm

CentOSrpmのバージョンによってパッケージが異なるので、都度最新版を確認した方が良さそうです。

rpmをインストール後はyumでインストールできるようになります。

$ yum search tmux
...

tmux.x86_64 : A terminal multiplexer

補足

EPELが常に検索されるのはどうなんだろうなぁと思っていたら、コントロールできるのね。

epelをdisableにしておいて、

$ sudo vim /etc/yum.repos.d/epel.repo
[epel]
...

enabled=0

yum実行時に引数を渡すことで、コントロールできます。

$ yum --enablerepo=epel search tmux