Search
Categories
Articles
Rainmeter関連
ファイル置き場
お知り合いなど

スポンサーサイト

--.--.-- | スポンサー広告

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

[Rainmeter-dev] マルチモニタ対応

2009.12.17 | Rainmeter-dev // Issue/マルチモニタ関連

4 Comments

Rainmeter0.14.1で追加されたマルチモニタ対応についてのまとめです。
私的なまとめですので、公開されているビルドに反映されているものではありません。ご注意ください。

■更新履歴 (※更新のたびに、たぶん一番上にあがってきます)
- 2009.09.28 : WindowToScreen関数からexit関数を追い出す(MyInfoEnumProc関数の修正)
- 2009.10.07 : マルチモニタ環境でのモニタ順序と、座標や作業領域の取り扱い
- 2009.10.20 : Negative coordinatesの扱いと、上の件で間違ってたと思われるところを補足
- 2009.10.23 - 27 : (EnumDisplayMonitors APIでの)モニタの順序ってアテにならないのね…… → 別の方法を模索
- 2009.11.02 : これまでの変更に対応するために、WindowToScreen関数を修正して、一旦終わり
- 2009.11.11 : !RainmeterMoveをマルチモニタ対応にする
- 2009.11.15 : ユーザー切り替え時にEnumDisplayDevices APIが失敗してクラッシュを引き起こすことがある問題を修正

- 2009.12.17 : これまでの成果のほか、ディスプレイセレクタ機能などを追加したマルチモニタ対応コードを本家にコミットしました (r317)
 ※ マルチモニタ環境でウィンドウの挙動がおかしくなる場合には、こちらのテスト用DLL(Rainmeter-1.1用)を使ってみてください。いくつかの環境で症状の改善を確認しています。(ディスプレイセレクタ機能はついていません)

以下、続きに格納。

Rainmeter0.14.1以降に追加された大きな変更に、マルチモニタ対応があります。
といっても、0.14の頃には全然対応してなかったのかとか、そのへんは環境がなかったのと、あまり興味がなかったのとで知らなかったりします。

大きく変わってる部分は、スキンウィンドウをどのモニタを基準に表示するかが指定できるようになったことでしょうか。
WindowX/Yの指定に、座標とともに"@"に続けてモニタ番号を指定すると、そのモニタのスクリーン座標を基準に表示してくれるようになります。指定できるのは、現状@1~@32です(32台のモニタまで対応)。@0を指定すると、そういったモニタ基準ではなく、Windowsが管理する仮想領域(モニタを全部合わせた領域)の座標を指定できるようになります。何も指定しなければ、@1を指定したことになります。

そういった座標変換などを担当するのが、WindowToScreen関数と、ScreenToWindow関数です。前者は、座標や表示方法の指定、モニタ番号が指定された文字列を解釈して、モニタごとに割り振られた領域へ座標をマッピングしてやる関数です。後者は逆で、座標値やモニタ番号などから、文字列を再生成します。Rainmeter.iniへの書き出し前に実行されたりします。

これらの作業には当然、各モニタに割り当てられている領域の情報が必要になってきます。その取得もWindowToScreen関数内で行われています。

本題。そのモニタ情報の取得ですが、現状はあまり好きになれない実装がなされています。それが、以下の部分。

    if(m_Monitors.count == 0) //Do this here so that if resolution changes, just set count to 0.
    {
        if(GetSystemMetrics(SM_CMONITORS)>32) 
        {
            MessageBox(m_Window, L"That's alot of monitors! 32 is the max.", L"Rainmeter", MB_OK);
            exit(-1);
        }
        m_Monitors.count = 0;
        EnumDisplayMonitors(NULL, NULL, MyInfoEnumProc, (LPARAM)(&m_Monitors)); 
        m_Monitors.vsT = GetSystemMetrics(SM_YVIRTUALSCREEN);
        m_Monitors.vsL = GetSystemMetrics(SM_XVIRTUALSCREEN);
        m_Monitors.vsH = GetSystemMetrics(SM_CYVIRTUALSCREEN);
        m_Monitors.vsW = GetSystemMetrics(SM_CXVIRTUALSCREEN);
    }

モニタ数を調べて、もし32個を越えるようであれば、「32個が限界だよ!」というメッセージを出して強制終了するというものです。
そもそも32個を越える数のモニタって時点で、現実的に容易に起こりうることではないんですが、exit関数を埋め込んだままにしておくのも、なんだか気持ちが悪いです。

モニタの列挙は、EnumDisplayMonitors APIの第3引数で指定されているMyInfoEnumProc関数を使って行われ、そこで各モニタの諸元を取得しています。モニタ諸元を格納しておく配列は32個しか用意されていないため、EnumDisplayMonitors APIを呼び出す前に、あらかじめモニタ数を調べて強制終了させようとしているんだろうと思います。

でも、MyInfoEnumProc関数で戻り値としてFALSEを返すようにすると、それ以上はモニタを列挙しないようにすることができます。33個目を取得しようとしたらFALSEを返すようにすれば、33個目以上のモニタの存在は無視されるようになりますが、exitしてしまうような挙動よりはマシじゃなかろうかと。
(根本的に修正するなら、パフォーマンスの面で固定配列よりは当然落ちると思いますが、32個しかない配列をvectorなどに置き換えて、動的に増やしていくほうがいいでしょう……)

/* MyInfoEnumProc
**
** Gets the multi monitor settings
**
*/
BOOL CALLBACK MyInfoEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
    MULTIMONITOR_INFO *m = (MULTIMONITOR_INFO *)dwData;
 
    if (m->count < 32)
    {
        m->m_Monitors[m->count] = hMonitor;
        memcpy(&(m->m_MonitorRect[m->count]), lprcMonitor, sizeof(RECT));
 
        m->m_MonitorInfo[m->count].cbSize = sizeof(MONITORINFO);
        GetMonitorInfo(hMonitor, &(m->m_MonitorInfo[m->count]));
 
        m->count++;
        return TRUE;
    }
    else
    {
        DebugLog(L"That's alot of monitors! 32 is the max.");
        return FALSE;
    }
}

32個以内なら取得して個数をカウントアップし、32個を越えていたらFALSEを返してそれ以上の列挙を中止します。
(エラーメッセージをメッセージボックスで表示すると、再計算のたびに何度も表示されて鬱陶しいので、DebugLogに変えてあります。LSLogを使ってエラーログを出力するほうがいいかも)

問題があるとすれば、前述の通りスクリーン単位の(@1~32のような)指定では、内部でも33個目以上は持っていないので無視されるものの(存在しないものを指定したときは、@1として扱われる)、@0を指定したときの仮想領域指定では、33個目以上の領域もちゃんと考慮されているという部分でしょうか。厳密にやるならば、これも前述の通りでvector化して全モニタ分保持するやり方に変更するほうがよさそうです。

モニタ32個って、実際にやるとどんなことになるんだろう……。トレーダーのような使い方ならそういう数も現実的……?

* 2009.10.07 追記 *

うちの環境でも無理矢理デュアルモニタにできたので、いろいろと試してみた。
その過程でソースコードを眺めて知ったけれど、WindowX/Yを指定するときのR/Bや%、@の指定は、固定値じゃないと使えないんですね。式を使うとたぶん無視される気がする(式が使えるようになったのはマルチモニタ機能がついてからずっと後なので、式機能を追加したときにそこまで考えてなかったのかもしれない)。

その話は置いといて……とりあえずうちの環境で、以下の設定で動かしたときのスクリーンショットを2つほど撮ったものをぺたりと。
Enigmaで試しましたが、Homeは重いのとデカいのとで省きました。式指定なので、モニタ指定の確認にはなっていませんが……。

[Enigma\Taskbar\_Taskbar]
WindowX=(#WORKAREAX#)
WindowY=(#WORKAREAY# + (#WORKAREAHEIGHT# - 33))
Draggable=1
SavePosition=0
SnapEdges=0
KeepOnScreen=1

[Enigma\Sidebar\_Sidebar]
WindowX=(#WORKAREAX# + (#WORKAREAWIDTH# - 214))
WindowY=(#WORKAREAY#)
Draggable=1
SavePosition=0
SnapEdges=0
KeepOnScreen=1

[Enigma\Sidebar\Clock]
WindowX=(#WORKAREAX# + (#WORKAREAWIDTH# - 283))
WindowY=(#WORKAREAY#)
Draggable=1
SavePosition=0
SnapEdges=0
KeepOnScreen=1

[Enigma\Sidebar\Music]
WindowX=(#WORKAREAX# + (#WORKAREAWIDTH# - 195))
WindowY=(#WORKAREAY# + (#WORKAREAHEIGHT# - 115))
Draggable=1
SavePosition=0
SnapEdges=1
KeepOnScreen=1

[Enigma\Sidebar\Notes]
WindowX=(#WORKAREAX# + (#WORKAREAWIDTH# - 188))
WindowY=(#WORKAREAY# + 110)
Draggable=1
SavePosition=0
SnapEdges=0
KeepOnScreen=0

[Enigma\Taskbar\Combos\Tray Systems]
WindowX=(#WORKAREAX# + (#WORKAREAWIDTH# - 253))
WindowY=(#WORKAREAY# + (#WORKAREAHEIGHT# - 28))
Draggable=1
SavePosition=0
SnapEdges=0
KeepOnScreen=1

まずその1。
プライマリモニタは液晶(1280x1024)、セカンダリモニタはTV(800x600)にしてあります。得られた作業領域の各変数は以下の通り。

#WORKAREAX#=0
#WORKAREAY#=0
#WORKAREAWIDTH#=1280
#WORKAREAHEIGHT#=998    //タスクバーを除いたサイズ

左が液晶(1280x1024)、右がTV(800x600) 
見た感じ、問題なさそうです。元々プライマリモニタしか意識してないような式だから当然と言えば当然でしょうか。

そして、問題のその2。
今度は液晶をセカンダリにし、TVをプライマリに指定してみます。配置は同じで、液晶が左側、TVが右側。得られた作業領域の各変数は以下の通り。

#WORKAREAX#=0
#WORKAREAY#=0
#WORKAREAWIDTH#=800
#WORKAREAHEIGHT#=600    //タスクバーは表示されていない

左が液晶(1280x1024)、右がTV(800x600) 
ああ……残念なことに……。
おかしなところは指摘すればいっぱいあるんですが、とりあえず、プライマリモニタに表示されてないのがおかしい。サイズは式に使われてる変数を見てもわかるように、プライマリモニタ準拠になっています。プログラム自身も、モニタの指定がないのでデフォルトで@1として認識しています(※)。それなのにセカンダリモニタに表示されています。

なぜかといえば簡単で、(※)の部分の解釈がおかしいからです。
プライマリモニタはプライマリモニタであって、「EnumDisplayMonitors APIで一番最初に列挙されたモニタ」とは限らないからです。Rainmeterは後者を「1番目」のモニタと解釈して動いています。Windowsが管理するプライマリモニタかどうかの確認はしていません。そのため、最初に列挙されたと思われるセカンダリモニタのほうの領域を@1として参照し、座標がそちらにマッピングされてしまったようです。

Windowsでは、モニタに番号を付け、それぞれがどんな領域を持っているか管理しています。
(画面のプロパティで弄れます。今回いろいろ試して初めて知りましたが、モニタの位置をぐりぐりドラッグして決められるんですね。上下左右から、座標単位で中途半端な位置に持っていったりできるとは知りませんでした)

画面のプロパティ

画面のプロパティでは表示されている各モニタアイコンに番号が振られていて、プライマリモニタに指定すると、そのアイコンに「1」が振られるようになります。Rainmeterからでも、てっきりその順序にモニタが列挙されていくんだろうと思っていたら、環境によってバラバラだそうで(順番通りに取れたとしても、たまたまそうなったというだけ)。
現状でも、「プライマリモニタかどうか」は、MONITORINFO構造体のdwFlagsにMONITORINFOF_PRIMARYが含まれているかどうかを調べればわかります。でもセカンダリモニタ以降は列挙されたどれがどう対応しているのか、現状ではわかりません。

というわけで、現状はRainmeterでモニタの指定をしても、それはWindowsが表示してくれるモニタ番号順とは対応していません、ということで……。
この順序を取得して補正したいんですが、取得方法がよくわからないので調査中です。表示上、「(ディスプレイアダプタ)上の(モニタ名)」のような形になっているので、EnumDisplayDevices APIを使えばできるのかな……。でも、同じ名前のものが複数セットある環境だと、名前の比較だけじゃわからないような。やっぱり何か一意に識別する方法があるのか。

Windowsですらこういう状態で、さらにNVIDIAやAMD(ATI)などのドライバ上での設定、さらにさらにマルチモニタ環境を独自に拡張したりするミドルウェアが組み合わさって、正直こんなの対応してられるのか……他のマルチモニタ対応ソフトはどんなふうに対応してるんだろう?
とりあえずはWindows標準でまともに動いてないので、そこをなんとかしてみたいところです。

* 2009.10.20 追記 *

まずは盛大に勘違いしてた部分の補足から……。
画面のプロパティで、「2番目」のモニタを「プライマリ」に設定すると、番号は「2」のままで、プライマリなモニタになりました。もしかしたら、EnumDisplayMonitors APIで返される順番は、この画面のプロパティの順番通りになっていて、プライマリに設定されているかどうかだけが違うのかもしれません。

そうであれば、@1~@32の指定は画面のプロパティの並び順通りに指定してやればいいと。(※やっぱりダメでした

問題はプライマリモニタの扱いです。プライマリモニタは@1とは限らないので、もし@の指定がなければ、プライマリモニタを(暗黙に)指定したと仮定すればいいかなぁ……。考えとしては、現行の@0(仮想領域での指定)に近いというか、そのまんまというか。プライマリモニタは特殊な例がなければ、座標(0, 0)を持ったモニタだと思われるので、指定された座標値を変換なくそのまま扱えるはずです。負の座標も同様。

そういった部分も含め、ローカルで実装テスト中。!RainmeterMoveでもモニタ番号の指定が(WindowX/Yと同じ書式で)できるような実装もテスト中。

さて本題に……。
マルチモニタのテストをしていて気になっていた部分がちょうどIssue 115でもレポートされていますが、Rainmeterは0.14.1のマルチモニタ対応版以降は、負の座標を扱えません。WindowX/Yで指定された負の座標値は、0として読み込まれます。
理由は、WindowToScreen関数での座標変換処理が、負の座標が指定されるのを考慮していないからです(下は1.0-r163での該当箇所)。

    index = m_WindowX.find_first_not_of(L"0123456789.");
    m_WindowXNumber = _wtof(m_WindowX.substr(0,index).c_str());
    index = m_WindowX.find(L'%');
    if(index != std::wstring::npos) m_WindowXPercentage = true;
    index = m_WindowX.find(L'R');
    if(index != std::wstring::npos) m_WindowXFromRight = true;
    index = m_WindowX.find(L'@');
    if(index != std::wstring::npos) 
    {
        /* snip */
    }

上のコードの1~2行目で、座標値(もしくは%値)を読んでいます。「数値か"."」以外を探して、そのindexまでを座標値として取り込みます。
読み飛ばされるのは数値か"."だけなので、"500"や"50.0%"、"500R@2"などは読み取ることができますが、"-500"は読めません。いきなり"-"があるので、1行目のindexには0が返ってきて、2行目の数値変換では0になります。結果、"-500@2"と書いても、"0@2"と書いたと勘違いされ、2番目のモニタの0座標位置に表示されちゃいます。

へんなのはこの部分だけなので、これを使わない内部処理はマイナス値がきても処理できます。たとえばWindowX="500@2"で読み込まれたスキンをドラッグして@1のモニタへ移動させた場合、Rainmeter.iniには"-100@2"のような@2からの相対座標値で書き出されます(@1での座標値に直すべきじゃないかという議論は置いといて)。この状態から終了→起動すると、もちろん上の「罠」処理が入って、"0@2"の位置に戻されます。

負の座標を読み込めるようにするには、1行目の処理に"-"を含めてやります。%値として扱う際には絶対値を取るようにします。(%値でもマイナスを許容しないと位置がおかしくなるので、止め)。

    index = m_WindowX.find_first_not_of(L"-0123456789.");
    m_WindowXNumber = _wtof(m_WindowX.substr(0,index).c_str());
    index = m_WindowX.find(L'%');
    if(index != std::wstring::npos) m_WindowXPercentage = true;
    index = m_WindowX.find(L'R');
    if(index != std::wstring::npos) m_WindowXFromRight = true;
    index = m_WindowX.find(L'@');
    if(index != std::wstring::npos) 
    {
        /* snip */
    }
    /* snip */
    if(m_WindowXPercentage) //is a percentage
    {
        m_WindowXNumber = fabs(m_WindowXNumber);
        float p = m_WindowXNumber;
        pixel = (int)(screenw * p / 100.0f) + screenx;
    }

AllowNegativeCoordinatesを設定からなくすなら、こういうところまでサポートして欲しかったです(もちろん、AllowNegativeCoordinatesをそのまま残していても変な処理になるだけですが)。
各モニタの例えば(0,0)-(1280,1024)という座標値を、内部で使う仮想座標値に変換して使い、そのあとも各モニタの座標に変換し直して保存するのだから、負にはならないという仮定からこうなったのかもしれませんが、実際には少しだけ負側へ寄せて表示したいって用途もあるし、何より@0なら左端/上端が負の座標になることもあるわけで、単純に想定不足でバグとして残ってたんでしょうね。

(この修正は1.1ではスルーして、リリース後にコミットしようかなと考え中。RCが出たしあまり入れたくない)

***

これで負の座標を取り扱えるようにはなりましたが、まだ気になるところはあります。
上端方向へウィンドウをドラッグしていってみるとわかりますが、ドラッグを終了して位置を確定させると、ウィンドウが半端に押し戻されてきます(ほんの少しだけ上端を越えたまま残っています)。タイトルバーを持つウィンドウで試すと(タイトルバーのメニューから移動を選んで、カーソルキーで上に移動)、同じくらい(タイトルバー分だけ)残るので、Windowsがドラッグ移動のときだけ押し戻してるんでしょう。たぶん上端以外では起こりません。

これは別に負の座標が扱えないとかいう理由ではなくて、ドラッグ移動確定後にさらにWindowsが補正位置を含んだメッセージを寄こしてくるから押し戻されたように見えるだけです。これはWM_WINDOWPOSCHANGINGメッセージの処理で反映させないようにできそうなので、ウィンドウのドラッグ関連(フラグ制御)も含めてテスト中です。ドラッグ処理中のフラグ管理も含めて、こっちの記事の続きを新規記事として書く予定。

* 2009.10.23 - 27 追記 *

いろいろと画面のプロパティでモニタの接続を付けたり外したりしてたら、EnumDisplayMonitors APIで得られるモニタの順番が入れ替わることがありました(モニタ3台で、画面のプロパティでは2番が付けられているのに、Rainmeterでは3番として認識)。
これだと、Windowsを再起動したら(ドライバの認識順で)モニタの順序が変わってしまってて、Rainmeterの設定は変えてないのに表示されるモニタが違うなんてことも起こったりするかもしれませんね。こんな状況だとモニタ指定なんて使えないわけで……。何か順序を合わせるための方法はないんだろうか。

***

とりあえず思いついた方法は、EnumDisplayMonitors APIで列挙されたモニタを、デバイス名順に並び変えるやり方。
モニタにはそれぞれデバイス名があって、例えば"\\.\DISPLAY1"のような名前がついています。ここでは一旦"\\.\DISPLAY"とついていると仮定して、その後ろの番号を使って順番通りに並べ替えてみます。

struct MONITOR_INFO
{
    HMONITOR handle;                        //Monitor handle
    RECT rect;                                //Monitor rect on virtual screen
    MONITORINFOEX info;                        //Monitor information
    int index;                                //Monitor index
};
 
struct MULTIMONITOR_INFO
{
    int primary;                            //Index of primary monitor
    int vsT, vsL, vsH, vsW;                    //Coordinates of top-left corner and size of virtual screen
    std::vector<MONITOR_INFO> monitors;        //Monitor information
};
 
/* MyInfoEnumProc
**
** Gets the multi monitor settings
**
*/
BOOL CALLBACK MyInfoEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
    MULTIMONITOR_INFO* m = (MULTIMONITOR_INFO*)dwData;
    MONITOR_INFO monitor;
 
    monitor.handle = hMonitor;
    monitor.rect = *lprcMonitor;
 
    monitor.info.cbSize = sizeof(MONITORINFOEX);
    GetMonitorInfo(hMonitor, &(monitor.info));
 
    std::wstring device = monitor.info.szDevice;  // e.g. "\\.\DISPLAY1"
    device.erase(0, 11);  // erases "\\.\DISPLAY"
    monitor.index = _wtoi(device.c_str());  // e.g. 1
 
    bool insert = false;
    std::vector<MONITOR_INFO>::iterator iter = m->monitors.begin();
    for ( ; iter != m->monitors.end(); iter++)
    {
        if (monitor.index < (*iter).index)
        {
            m->monitors.insert(iter, monitor);
            insert = true;
            break;
        }
    }
    if (!insert)
    {
        m->monitors.push_back(monitor);
    }
 
    return TRUE;
}
 
/* ResetMultiMonitorInfo
**
** Resets the multi monitor settings
**
*/
void CMeterWindow::ResetMultiMonitorInfo()
{
    std::vector<MONITOR_INFO>& monitors = m_Monitors.monitors;
 
    monitors.clear();
    if (monitors.capacity() < 16) { monitors.reserve(16); }
 
    m_Monitors.vsT = GetSystemMetrics(SM_YVIRTUALSCREEN);
    m_Monitors.vsL = GetSystemMetrics(SM_XVIRTUALSCREEN);
    m_Monitors.vsH = GetSystemMetrics(SM_CYVIRTUALSCREEN);
    m_Monitors.vsW = GetSystemMetrics(SM_CXVIRTUALSCREEN);
 
    EnumDisplayMonitors(NULL, NULL, MyInfoEnumProc, (LPARAM)(&m_Monitors));
 
    m_Monitors.primary = 1;  // If primary screen is not found, 1st screen is assumed as primary screen.
    for (size_t i = 0; i < monitors.size(); i++)
    {
        if (monitors[i].info.dwFlags & MONITORINFOF_PRIMARY)
        {
            // It's primary monitor!
            m_Monitors.primary = (int)i + 1;
            break;
        }
    }
 
    //DEBUG---------------
    DebugLog(L"******************************");
    DebugLog(L"MONITOR: Count=%i, Primary=%i", monitors.size(), m_Monitors.primary);
    DebugLog(L"V: L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", m_Monitors.vsL, m_Monitors.vsT, m_Monitors.vsL + m_Monitors.vsW, m_Monitors.vsT + m_Monitors.vsH, m_Monitors.vsW, m_Monitors.vsH);
    for (size_t i = 0; i < monitors.size(); i++)
    {
        DebugLog(L"%i: %s : L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", i + 1, monitors[i].info.szDevice, monitors[i].rect.left, monitors[i].rect.top, monitors[i].rect.right, monitors[i].rect.bottom, monitors[i].rect.right - monitors[i].rect.left, monitors[i].rect.bottom - monitors[i].rect.top);
    }
    DebugLog(L"******************************");
    //DEBUG---------------
}

まず、これは順序どうこうとは関係ない変更ですが、WindowToScreen関数内にあった処理をResetMultiMonitorInfo関数として切り出して、そこからマルチモニタの情報を取得するようにしています。マルチモニタの情報を格納する場所も、固定配列からstd::vectorへ変更して、上限を撤廃してあります(メモリ確保が頻繁に起こらないよう、最初に16個分の領域を確保)。

順序の話に戻すと、コールバック関数内でモニタの情報とデバイス名を取得し、そのデバイス名に振られた番号同士を比較して、後から取得したモニタのほうが若い番号を持つのであれば、その要素の前に挿入してやります。そうでなければ後ろに追加していきます。

結果はこんな感じ。(※Vは仮想スクリーン = @0で使える領域)

DEBUG: (00:00:00.313) ******************************
DEBUG: (00:00:00.313) MONITOR: Count=3, Primary=1
DEBUG: (00:00:00.313) V: L=-1024, T=0, R=2080, B=1024 (W=3104, H=1024)
DEBUG: (00:00:00.313) 1: \\.\DISPLAY1 : L=0, T=0, R=1280, B=1024 (W=1280, H=1024)
DEBUG: (00:00:00.313) 2: \\.\DISPLAY2 : L=1280, T=0, R=2080, B=600 (W=800, H=600)
DEBUG: (00:00:00.313) 3: \\.\DISPLAY4 : L=-1024, T=0, R=0, B=768 (W=1024, H=768)
DEBUG: (00:00:00.313) ******************************

実行結果にはEnumDisplayMonitors APIそのままの順序が出てませんが、「1→2→4」の並びでも「1→4→2」の並びでも上の結果が出力されました。

が、この対策だけだと、画面のプロパティでモニタの接続を外したときに順序がおかしくなります。
例えば上の順序の状態で2番目のモニタの「接続」のチェックを外すと、出力は下のようになります。

DEBUG: (00:37:13.735) ******************************
DEBUG: (00:37:13.735) MONITOR: Count=2, Primary=1
DEBUG: (00:37:13.735) V: L=-1024, T=0, R=1280, B=1024 (W=2304, H=1024)
DEBUG: (00:37:13.735) 1: \\.\DISPLAY1 : L=0, T=0, R=1280, B=1024 (W=1280, H=1024)
DEBUG: (00:37:13.735) 2: \\.\DISPLAY4 : L=-1024, T=0, R=0, B=768 (W=1024, H=768)
DEBUG: (00:37:13.735) ******************************

接続を外したのでモニタは2個となり、3番目のモニタが2番目になりました。ですが、画面のプロパティではモニタは3つあって、順序も最初から変わっていません。結局、モニタの番号指定はズレるという結果に。

***

EnumDisplayMonitors APIだけでは、この「接続されていない2番目のモニタ」を取得することができないので、別の方法を考えます。
Rainmeter内部で2番目のモニタの情報を使うことはありませんが(接続されてないのだから使いようがない)、番号のズレをなくすために、2番目は使わないという情報を持っておきたい。

順番が必ず画面のプロパティと一緒になるのかどうかまではわかりませんが、デバイスの列挙にEnumDisplayDevices APIが使えそうなので、それで得られた結果と、EnumDisplayMonitors APIで得られた結果をつき合わせることで、順番を維持できるようにしてみます。

struct MONITOR_INFO
{
    HMONITOR handle;                        //Monitor handle
    RECT rect;                                //Monitor rect on virtual screen
    MONITORINFOEX info;                        //Monitor information
    bool active;
};
 
struct MULTIMONITOR_INFO
{
    int primary;                            //Index of primary monitor
    int vsT, vsL, vsH, vsW;                    //Coordinates of top-left corner and size of virtual screen
    std::vector<MONITOR_INFO> monitors;        //Monitor information
};
 
/* MyInfoEnumProc
**
** Gets the multi monitor settings
**
*/
BOOL CALLBACK MyInfoEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
    MULTIMONITOR_INFO* m = (MULTIMONITOR_INFO*)dwData;
 
    MONITORINFOEX info;
    info.cbSize = sizeof(MONITORINFOEX);
    GetMonitorInfo(hMonitor, &info);
 
    //DEBUG---------------
    DebugLog(L"%s : L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)%s", info.szDevice, lprcMonitor->left, lprcMonitor->top, lprcMonitor->right, lprcMonitor->bottom, lprcMonitor->right - lprcMonitor->left, lprcMonitor->bottom - lprcMonitor->top, (info.dwFlags & MONITORINFOF_PRIMARY) ? L", PRIMARY" : L"");
    //DEBUG---------------
 
    for (size_t i = 0; i < m->monitors.size(); i++)
    {
        if (_wcsnicmp(info.szDevice, m->monitors[i].info.szDevice, 32) == 0)
        {
            m->monitors[i].handle = hMonitor;
            m->monitors[i].rect = *lprcMonitor;
            m->monitors[i].info = info;
            break;
        }
    }
 
    return TRUE;
}
 
/* ResetMultiMonitorInfo
**
** Resets the multi monitor settings
**
*/
void CMeterWindow::ResetMultiMonitorInfo()
{
    std::vector<MONITOR_INFO>& monitors = m_Monitors.monitors;
 
    monitors.clear();
    if (monitors.capacity() < 16) { monitors.reserve(16); }
 
    m_Monitors.vsT = GetSystemMetrics(SM_YVIRTUALSCREEN);
    m_Monitors.vsL = GetSystemMetrics(SM_XVIRTUALSCREEN);
    m_Monitors.vsH = GetSystemMetrics(SM_CYVIRTUALSCREEN);
    m_Monitors.vsW = GetSystemMetrics(SM_CXVIRTUALSCREEN);
 
    //DEBUG---------------
    DebugLog(L"******************************");
    DebugLog(L"EnumDisplayDevices API");
    //DEBUG---------------
    DISPLAY_DEVICE dd = {0};
    dd.cb = sizeof(DISPLAY_DEVICE);
 
    m_Monitors.primary = 1;  // If primary screen is not found, 1st screen is assumed as primary screen.
 
    DWORD dwDevice = 0;
    while (EnumDisplayDevices(NULL, dwDevice, &dd, 0))
    {
        //DEBUG---------------
        std::wstring msg = dd.DeviceName;
        msg += L" - ";
        msg += dd.DeviceString;
        msg += L" : ";
        if (dd.StateFlags & DISPLAY_DEVICE_ACTIVE)
        {
            msg += L"ACTIVE ";
        }
        if (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
        {
            msg += L"PRIMARY ";
        }
        if (dd.StateFlags & DISPLAY_DEVICE_MULTI_DRIVER)
        {
            msg += L"MULTI ";
        }
        if (dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER)
        {
            msg += L"MIRROR ";
        }
        if (dd.StateFlags & DISPLAY_DEVICE_MODESPRUNED)
        {
            msg += L"PRUNED ";
        }
        if (dd.StateFlags & DISPLAY_DEVICE_REMOVABLE)
        {
            msg += L"REMOVABLE";
        }
        DebugLog(msg.c_str());
        //DEBUG---------------
        if ((dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) == 0)
        {
            MONITOR_INFO monitor = {0};
            wcsncpy_s(monitor.info.szDevice, 32, dd.DeviceName, 32);  // e.g. "\\.\DISPLAY1"
            monitor.active = (dd.StateFlags & DISPLAY_DEVICE_ACTIVE);
            monitors.push_back(monitor);  // reserves the space
 
            if (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
            {
                // It's primary monitor!
                m_Monitors.primary = (int)monitors.size();
            }
        }
        dwDevice++;
    }
 
    //DEBUG---------------
    DebugLog(L"******************************");
    DebugLog(L"EnumDisplayMonitors API");
    //DEBUG---------------
    EnumDisplayMonitors(NULL, NULL, MyInfoEnumProc, (LPARAM)(&m_Monitors));
 
    //DEBUG---------------
    DebugLog(L"******************************");
    DebugLog(L"MONITOR: Count=%i, Primary=%i", monitors.size(), m_Monitors.primary);
    DebugLog(L"V: L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", m_Monitors.vsL, m_Monitors.vsT, m_Monitors.vsL + m_Monitors.vsW, m_Monitors.vsT + m_Monitors.vsH, m_Monitors.vsW, m_Monitors.vsH);
    for (size_t i = 0; i < monitors.size(); i++)
    {
        if (monitors[i].active)
        {
            DebugLog(L"%i: %s (active) : L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", i + 1, monitors[i].info.szDevice, monitors[i].rect.left, monitors[i].rect.top, monitors[i].rect.right, monitors[i].rect.bottom, monitors[i].rect.right - monitors[i].rect.left, monitors[i].rect.bottom - monitors[i].rect.top);
        }
        else
        {
            DebugLog(L"%i: %s (inactive)", i + 1, monitors[i].info.szDevice);
        }
    }
    DebugLog(L"******************************");
    //DEBUG---------------
}

ちょっとデバッグ用の出力が多くて読みづらいですが……。
まずEnumDisplayDevices APIを呼び出してモニタデバイスを全て列挙し、その中からミラーでないものを選び、デバイス名を保持しておきます(デバイス名をコピーしたMONITOR_INFOを用意して、あらかじめvectorに追加しておきます)。そのモニタがアクティブかどうかも、ここで取得しています。次に、その得られたデバイス名をEnumDisplayMonitors APIの呼び出しで得られたデバイス名と比較して、同じであればモニタ諸元を取得します。

実行結果は下のような感じです。多いですが、EnumDisplayMonitors APIでの取得順がひっくり返っているところから始めます。画面のプロパティでは、DISPLAY1=1番、DISPLAY2=2番、DISPLAY4=3番が付けられています。

初期状態

DEBUG: (00:00:00.234) ******************************
DEBUG: (00:00:00.234) EnumDisplayDevices API
DEBUG: (00:00:00.234) \\.\DISPLAY1 - NVIDIA GeForce 6800 LE : ACTIVE PRIMARY PRUNED
DEBUG: (00:00:00.234) \\.\DISPLAY2 - NVIDIA GeForce 6800 LE : ACTIVE
DEBUG: (00:00:00.234) \\.\DISPLAYV1 - NetMeeting driver : MIRROR
DEBUG: (00:00:00.234) \\.\DISPLAYV2 - RDPDD Chained DD : MIRROR
DEBUG: (00:00:00.250) \\.\DISPLAY4 - ZoneScreen virtual display driver : ACTIVE
DEBUG: (00:00:00.250) ******************************
DEBUG: (00:00:00.250) EnumDisplayMonitors API
DEBUG: (00:00:00.250) \\.\DISPLAY1 : L=0, T=0, R=1280, B=1024 (W=1280, H=1024), PRIMARY
DEBUG: (00:00:00.250) \\.\DISPLAY4 : L=-1024, T=0, R=0, B=768 (W=1024, H=768)
DEBUG: (00:00:00.250) \\.\DISPLAY2 : L=1280, T=0, R=2080, B=600 (W=800, H=600)
DEBUG: (00:00:00.250) ******************************
DEBUG: (00:00:00.250) MONITOR: Count=3, Primary=1
DEBUG: (00:00:00.250) V: L=-1024, T=0, R=2080, B=1024 (W=3104, H=1024)
DEBUG: (00:00:00.250) 1: \\.\DISPLAY1 (active) : L=0, T=0, R=1280, B=1024 (W=1280, H=1024)
DEBUG: (00:00:00.250) 2: \\.\DISPLAY2 (active) : L=1280, T=0, R=2080, B=600 (W=800, H=600)
DEBUG: (00:00:00.250) 3: \\.\DISPLAY4 (active) : L=-1024, T=0, R=0, B=768 (W=1024, H=768)
DEBUG: (00:00:00.265) ******************************

DISPLAY4の「接続」を外す → 3番目のモニタは使えなくなる

DEBUG: (00:01:19.734) ******************************
DEBUG: (00:01:19.734) EnumDisplayDevices API
DEBUG: (00:01:19.750) \\.\DISPLAY1 - NVIDIA GeForce 6800 LE : ACTIVE PRIMARY PRUNED
DEBUG: (00:01:19.750) \\.\DISPLAY2 - NVIDIA GeForce 6800 LE : ACTIVE
DEBUG: (00:01:19.765) \\.\DISPLAYV1 - NetMeeting driver : MIRROR
DEBUG: (00:01:19.797) \\.\DISPLAYV2 - RDPDD Chained DD : MIRROR
DEBUG: (00:01:19.797) \\.\DISPLAY4 - ZoneScreen virtual display driver :
DEBUG: (00:01:19.797) ******************************
DEBUG: (00:01:19.797) EnumDisplayMonitors API
DEBUG: (00:01:19.797) \\.\DISPLAY1 : L=0, T=0, R=1280, B=1024 (W=1280, H=1024), PRIMARY
DEBUG: (00:01:19.797) \\.\DISPLAY2 : L=1280, T=0, R=2080, B=600 (W=800, H=600)
DEBUG: (00:01:19.797) ******************************
DEBUG: (00:01:19.812) MONITOR: Count=3, Primary=1
DEBUG: (00:01:19.828) V: L=0, T=0, R=2080, B=1024 (W=2080, H=1024)
DEBUG: (00:01:19.828) 1: \\.\DISPLAY1 (active) : L=0, T=0, R=1280, B=1024 (W=1280, H=1024)
DEBUG: (00:01:19.828) 2: \\.\DISPLAY2 (active) : L=1280, T=0, R=2080, B=600 (W=800, H=600)
DEBUG: (00:01:19.843) 3: \\.\DISPLAY4 (inactive)
DEBUG: (00:01:19.875) ******************************

DISPLAY4を「接続」する → EnumDisplayMonitors APIの順番が普通になる

DEBUG: (00:02:03.515) ******************************
DEBUG: (00:02:03.547) EnumDisplayDevices API
DEBUG: (00:02:03.547) \\.\DISPLAY1 - NVIDIA GeForce 6800 LE : ACTIVE PRIMARY PRUNED
DEBUG: (00:02:03.578) \\.\DISPLAY2 - NVIDIA GeForce 6800 LE : ACTIVE
DEBUG: (00:02:03.578) \\.\DISPLAYV1 - NetMeeting driver : MIRROR
DEBUG: (00:02:03.578) \\.\DISPLAYV2 - RDPDD Chained DD : MIRROR
DEBUG: (00:02:03.593) \\.\DISPLAY4 - ZoneScreen virtual display driver : ACTIVE
DEBUG: (00:02:03.593) ******************************
DEBUG: (00:02:03.593) EnumDisplayMonitors API
DEBUG: (00:02:03.609) \\.\DISPLAY1 : L=0, T=0, R=1280, B=1024 (W=1280, H=1024), PRIMARY
DEBUG: (00:02:03.609) \\.\DISPLAY2 : L=1280, T=0, R=2080, B=600 (W=800, H=600)
DEBUG: (00:02:03.609) \\.\DISPLAY4 : L=-1024, T=0, R=0, B=768 (W=1024, H=768)
DEBUG: (00:02:03.609) ******************************
DEBUG: (00:02:03.609) MONITOR: Count=3, Primary=1
DEBUG: (00:02:03.625) V: L=-1024, T=0, R=2080, B=1024 (W=3104, H=1024)
DEBUG: (00:02:03.625) 1: \\.\DISPLAY1 (active) : L=0, T=0, R=1280, B=1024 (W=1280, H=1024)
DEBUG: (00:02:03.625) 2: \\.\DISPLAY2 (active) : L=1280, T=0, R=2080, B=600 (W=800, H=600)
DEBUG: (00:02:03.640) 3: \\.\DISPLAY4 (active) : L=-1024, T=0, R=0, B=768 (W=1024, H=768)
DEBUG: (00:02:03.672) ******************************

DISPLAY2の「接続」を外す → 2番目のモニタは使えなくなるけど、順番は維持したまま

DEBUG: (00:04:38.234) ******************************
DEBUG: (00:04:38.250) EnumDisplayDevices API
DEBUG: (00:04:38.250) \\.\DISPLAY1 - NVIDIA GeForce 6800 LE : ACTIVE PRIMARY PRUNED
DEBUG: (00:04:38.265) \\.\DISPLAY2 - NVIDIA GeForce 6800 LE :
DEBUG: (00:04:38.265) \\.\DISPLAYV1 - NetMeeting driver : MIRROR
DEBUG: (00:04:38.265) \\.\DISPLAYV2 - RDPDD Chained DD : MIRROR
DEBUG: (00:04:38.281) \\.\DISPLAY4 - ZoneScreen virtual display driver : ACTIVE
DEBUG: (00:04:38.281) ******************************
DEBUG: (00:04:38.281) EnumDisplayMonitors API
DEBUG: (00:04:38.297) \\.\DISPLAY1 : L=0, T=0, R=1280, B=1024 (W=1280, H=1024), PRIMARY
DEBUG: (00:04:38.297) \\.\DISPLAY4 : L=-1024, T=0, R=0, B=768 (W=1024, H=768)
DEBUG: (00:04:38.312) ******************************
DEBUG: (00:04:38.312) MONITOR: Count=3, Primary=1
DEBUG: (00:04:38.312) V: L=-1024, T=0, R=1280, B=1024 (W=2304, H=1024)
DEBUG: (00:04:38.312) 1: \\.\DISPLAY1 (active) : L=0, T=0, R=1280, B=1024 (W=1280, H=1024)
DEBUG: (00:04:38.312) 2: \\.\DISPLAY2 (inactive)
DEBUG: (00:04:38.312) 3: \\.\DISPLAY4 (active) : L=-1024, T=0, R=0, B=768 (W=1024, H=768)
DEBUG: (00:04:38.328) ******************************

DISPLAY2を「接続」する → EnumDisplayMonitors APIの順番がまたひっくり返る

DEBUG: (00:07:13.609) ******************************
DEBUG: (00:07:13.609) EnumDisplayDevices API
DEBUG: (00:07:13.609) \\.\DISPLAY1 - NVIDIA GeForce 6800 LE : ACTIVE PRIMARY PRUNED
DEBUG: (00:07:13.609) \\.\DISPLAY2 - NVIDIA GeForce 6800 LE : ACTIVE
DEBUG: (00:07:13.609) \\.\DISPLAYV1 - NetMeeting driver : MIRROR
DEBUG: (00:07:13.609) \\.\DISPLAYV2 - RDPDD Chained DD : MIRROR
DEBUG: (00:07:13.609) \\.\DISPLAY4 - ZoneScreen virtual display driver : ACTIVE
DEBUG: (00:07:13.609) ******************************
DEBUG: (00:07:13.609) EnumDisplayMonitors API
DEBUG: (00:07:13.609) \\.\DISPLAY1 : L=0, T=0, R=1280, B=1024 (W=1280, H=1024), PRIMARY
DEBUG: (00:07:13.609) \\.\DISPLAY4 : L=-1024, T=0, R=0, B=768 (W=1024, H=768)
DEBUG: (00:07:13.609) \\.\DISPLAY2 : L=1280, T=0, R=2080, B=600 (W=800, H=600)
DEBUG: (00:07:13.609) ******************************
DEBUG: (00:07:13.609) MONITOR: Count=3, Primary=1
DEBUG: (00:07:13.609) V: L=-1024, T=0, R=2080, B=1024 (W=3104, H=1024)
DEBUG: (00:07:13.609) 1: \\.\DISPLAY1 (active) : L=0, T=0, R=1280, B=1024 (W=1280, H=1024)
DEBUG: (00:07:13.609) 2: \\.\DISPLAY2 (active) : L=1280, T=0, R=2080, B=600 (W=800, H=600)
DEBUG: (00:07:13.609) 3: \\.\DISPLAY4 (active) : L=-1024, T=0, R=0, B=768 (W=1024, H=768)
DEBUG: (00:07:13.609) ******************************

プライマリモニタをDISPLAY2に変更

DEBUG: (00:07:34.718) ******************************
DEBUG: (00:07:34.718) EnumDisplayDevices API
DEBUG: (00:07:34.718) \\.\DISPLAY1 - NVIDIA GeForce 6800 LE : ACTIVE PRUNED
DEBUG: (00:07:34.718) \\.\DISPLAY2 - NVIDIA GeForce 6800 LE : ACTIVE PRIMARY
DEBUG: (00:07:34.734) \\.\DISPLAYV1 - NetMeeting driver : MIRROR
DEBUG: (00:07:34.734) \\.\DISPLAYV2 - RDPDD Chained DD : MIRROR
DEBUG: (00:07:34.734) \\.\DISPLAY4 - ZoneScreen virtual display driver : ACTIVE
DEBUG: (00:07:34.734) ******************************
DEBUG: (00:07:34.734) EnumDisplayMonitors API
DEBUG: (00:07:34.734) \\.\DISPLAY1 : L=-1280, T=0, R=0, B=1024 (W=1280, H=1024)
DEBUG: (00:07:34.734) \\.\DISPLAY4 : L=-2304, T=0, R=-1280, B=768 (W=1024, H=768)
DEBUG: (00:07:34.734) \\.\DISPLAY2 : L=0, T=0, R=800, B=600 (W=800, H=600), PRIMARY
DEBUG: (00:07:34.734) ******************************
DEBUG: (00:07:34.734) MONITOR: Count=3, Primary=2
DEBUG: (00:07:34.734) V: L=-2304, T=0, R=800, B=1024 (W=3104, H=1024)
DEBUG: (00:07:34.734) 1: \\.\DISPLAY1 (active) : L=-1280, T=0, R=0, B=1024 (W=1280, H=1024)
DEBUG: (00:07:34.734) 2: \\.\DISPLAY2 (active) : L=0, T=0, R=800, B=600 (W=800, H=600)
DEBUG: (00:07:34.734) 3: \\.\DISPLAY4 (active) : L=-2304, T=0, R=-1280, B=768 (W=1024, H=768)
DEBUG: (00:07:34.734) ******************************

プライマリモニタをDISPLAY4に変更

DEBUG: (00:07:59.453) ******************************
DEBUG: (00:07:59.453) EnumDisplayDevices API
DEBUG: (00:07:59.453) \\.\DISPLAY1 - NVIDIA GeForce 6800 LE : ACTIVE PRUNED
DEBUG: (00:07:59.453) \\.\DISPLAY2 - NVIDIA GeForce 6800 LE : ACTIVE
DEBUG: (00:07:59.453) \\.\DISPLAYV1 - NetMeeting driver : MIRROR
DEBUG: (00:07:59.453) \\.\DISPLAYV2 - RDPDD Chained DD : MIRROR
DEBUG: (00:07:59.453) \\.\DISPLAY4 - ZoneScreen virtual display driver : ACTIVE PRIMARY
DEBUG: (00:07:59.453) ******************************
DEBUG: (00:07:59.453) EnumDisplayMonitors API
DEBUG: (00:07:59.453) \\.\DISPLAY1 : L=1024, T=0, R=2304, B=1024 (W=1280, H=1024)
DEBUG: (00:07:59.453) \\.\DISPLAY4 : L=0, T=0, R=1024, B=768 (W=1024, H=768), PRIMARY
DEBUG: (00:07:59.453) \\.\DISPLAY2 : L=2304, T=0, R=3104, B=600 (W=800, H=600)
DEBUG: (00:07:59.453) ******************************
DEBUG: (00:07:59.453) MONITOR: Count=3, Primary=3
DEBUG: (00:07:59.453) V: L=0, T=0, R=3104, B=1024 (W=3104, H=1024)
DEBUG: (00:07:59.453) 1: \\.\DISPLAY1 (active) : L=1024, T=0, R=2304, B=1024 (W=1280, H=1024)
DEBUG: (00:07:59.453) 2: \\.\DISPLAY2 (active) : L=2304, T=0, R=3104, B=600 (W=800, H=600)
DEBUG: (00:07:59.453) 3: \\.\DISPLAY4 (active) : L=0, T=0, R=1024, B=768 (W=1024, H=768)
DEBUG: (00:07:59.453) ******************************

プライマリモニタをDISPLAY1に変更して、DISPLAY2とDISPLAY4の「接続」を外す → 3台認識してるけど、使えるのは1台だけ

DEBUG: (00:09:17.093) ******************************
DEBUG: (00:09:17.093) EnumDisplayDevices API
DEBUG: (00:09:17.093) \\.\DISPLAY1 - NVIDIA GeForce 6800 LE : ACTIVE PRIMARY PRUNED
DEBUG: (00:09:17.093) \\.\DISPLAY2 - NVIDIA GeForce 6800 LE :
DEBUG: (00:09:17.093) \\.\DISPLAYV1 - NetMeeting driver : MIRROR
DEBUG: (00:09:17.093) \\.\DISPLAYV2 - RDPDD Chained DD : MIRROR
DEBUG: (00:09:17.093) \\.\DISPLAY4 - ZoneScreen virtual display driver :
DEBUG: (00:09:17.093) ******************************
DEBUG: (00:09:17.093) EnumDisplayMonitors API
DEBUG: (00:09:17.093) \\.\DISPLAY1 : L=0, T=0, R=1280, B=1024 (W=1280, H=1024), PRIMARY
DEBUG: (00:09:17.093) ******************************
DEBUG: (00:09:17.093) MONITOR: Count=3, Primary=1
DEBUG: (00:09:17.093) V: L=0, T=0, R=1280, B=1024 (W=1280, H=1024)
DEBUG: (00:09:17.093) 1: \\.\DISPLAY1 (active) : L=0, T=0, R=1280, B=1024 (W=1280, H=1024)
DEBUG: (00:09:17.093) 2: \\.\DISPLAY2 (inactive)
DEBUG: (00:09:17.093) 3: \\.\DISPLAY4 (inactive)
DEBUG: (00:09:17.109) ******************************

環境次第でどういう結果が返ってくるかは全然わからないので、あとは実機テストの機会を増やすくらいしかなさそうな予感。
ちなみにWindows 7 (on VirtualBox)でも試してみたので、画像をぺたり。

Windows 7

DEBUG: (00:00:01.462) ******************************
DEBUG: (00:00:01.462) EnumDisplayDevices API
DEBUG: (00:00:01.462) \\.\DISPLAY1 - VirtualBox Graphics Adapter : ACTIVE PRIMARY
DEBUG: (00:00:01.462) \\.\DISPLAYV1 - RDPDD Chained DD : MIRROR
DEBUG: (00:00:01.462) \\.\DISPLAYV2 - RDP Encoder Mirror Driver : MIRROR
DEBUG: (00:00:01.462) \\.\DISPLAYV3 - RDP Reflector Display Driver : MIRROR
DEBUG: (00:00:01.462) \\.\DISPLAY2 - VirtualBox Graphics Adapter : REMOVABLE
DEBUG: (00:00:01.472) \\.\DISPLAY3 - VirtualBox Graphics Adapter : ACTIVE REMOVABLE
DEBUG: (00:00:01.472) ******************************
DEBUG: (00:00:01.472) EnumDisplayMonitors API
DEBUG: (00:00:01.472) \\.\DISPLAY1 : L=0, T=0, R=1024, B=768 (W=1024, H=768), PRIMARY
DEBUG: (00:00:01.472) \\.\DISPLAY3 : L=1024, T=0, R=1664, B=480 (W=640, H=480)
DEBUG: (00:00:01.472) ******************************
DEBUG: (00:00:01.472) MONITOR: Count=3, Primary=1
DEBUG: (00:00:01.472) V: L=0, T=0, R=1664, B=768 (W=1664, H=768)
DEBUG: (00:00:01.482) 1: \\.\DISPLAY1 (active) : L=0, T=0, R=1024, B=768 (W=1024, H=768)
DEBUG: (00:00:01.482) 2: \\.\DISPLAY2 (inactive)
DEBUG: (00:00:01.482) 3: \\.\DISPLAY3 (active) : L=1024, T=0, R=1664, B=480 (W=640, H=480)
DEBUG: (00:00:01.482) ******************************

画面を3つに増やしてやってみましたが、増やしたモニタにはREMOVABLEというフラグが付いてます。正直なところ、このフラグ類の判定がわからなくて、ある種いい加減なコードになってる感も……。

順番は(十分に)正しいと仮定すると、次に問題となるのは、inactiveなモニタを指定された場合にどうするか。プライマリモニタに出すか、それとも出さないか。SavePositionがオンになってると、モニタ指定してたのにプライマリに書きかえられちゃっててヤダ!ってこともあるだろうし。

といいつつ、プライマリモニタに出してやるほうが一貫性あっていいかなぁ……。

* 2009.11.02 追記 *

上記までの変更に対応するため、座標変換を行っているWindowToScreen/ScreenToWindow関数を修正します。修正内容は、指定されたモニタ番号が無効などの理由により使えない場合、プライマリモニタが指定されたと仮定して処理を継続します。ここでいうプライマリモニタとは上記で何度も書きましたが、「1番」が振られたモニタという意味ではなく、Windowsがプライマリモニタとして管理しているモニタのことです。

    index = m_WindowX.find(L'@');
    if(index != std::wstring::npos) 
    {
        index = index + 1;
        index2 = m_WindowX.find_first_not_of(L"0123456789", index);
        std::wstring screenStr;
        if (index2 != std::wstring::npos)
        {
            screenStr = m_WindowX.substr(index, index2 - index);
        }
        else
        {
            screenStr = m_WindowX.substr(index);
        }
        if (!screenStr.empty())
        {
            int screenIndex = _wtoi(screenStr.c_str());
            if (screenIndex == 0 || screenIndex <= (int)monitors.size() && monitors[screenIndex-1].active)
            {
                m_WindowXScreen = screenIndex;
                m_WindowXScreenDefined = true;
                m_WindowYScreen = m_WindowXScreen; //Default to X and Y on same screen if not overridden on WindowY
                m_WindowYScreenDefined = true;
            }
        }
    }

WindowToScreen関数内で、WindowXから指定されたモニタ番号を解釈している部分です。
モニタ番号が指定されていれば、その番号が存在し、有効な状態であるかをチェックしてからメンバ変数へセットするようにします。指定がない・番号が存在しない・指定された番号が無効である場合、メンバ変数にはデフォルトでプライマリモニタの情報がセットされているので、それが使われます。

***

ここまでいろいろと試行錯誤してきましたが、まだまだ不完全でどうしようもない部分もあります。
たとえば、Windowsを再起動したらモニタ番号そのものが変わってしまうとか。いつからかわかりませんが、うちでは以前2番を振られていたはずのモニタが、今では3番になってます。1番と同じビデオカード上のモニタなのに3番。2番はZoneScreenの仮想ディスプレイドライバが提供している仮想モニタになってます。これは前は3番でした。再起動のたびに、Windowsやドライバが認識を変えてこういう順番がころころと変わるような環境があれば、モニタ番号指定そのものが意味のないものになってしまいます。それこそ、デバイスの一意なIDを保持しておいて、それを指定してやるとか、そういう面倒なやり方になってくるんでしょうか。

そういうことはひとまず考えないように脇へ置いといて、現時点ではベターに纏められたんじゃないかと……。
いずれ本家でbranchきってテストしてもらって、trunkにマージできればいいなと思っています。そのためには仕様などを含め、膨大な量の説明文(英文)を書かねばならんわけで、さてそれだけで何ヶ月かかるでしょうか……w

* 2009.11.11 追記 *

ウィンドウの移動処理の記事に書こうと思っていたけれど、処理内容がマルチモニタに関連しているのでこちらに書くことにしました。
!RainmeterMoveを使うと、スキンウィンドウを新たな座標位置に移動させることができますが、現状ではプライマリモニタの基準点(0,0)からの相対座標値でしか指定できません。この指定部分をマルチモニタ対応にしてみます。例えば、

!RainmeterMove 100@2 50@2

と指定されたら、2番目のモニタの(100,50)へ移動させます。
モニタの指定がなければ、プライマリモニタと仮定して……といきたいところですが、そのスキンウィンドウはもしかしたらWindowX/Yでモニタを指定して動いているかもしれません。その状態を考えるなら、指定されているモニタと仮定してやるほうがよさそうです。その指定もなければプライマリモニタと仮定と。

以上を元に、CMeterWindow::MoveWindow関数を書き換えます。元は引数が整数値でしたが、呼び出し側で処理しやすいように文字列にしてあります。

/*
** MoveWindow
**
** Moves the window to a new place (on the specific screen)
**
*/
void CMeterWindow::MoveWindow(std::wstring& x, std::wstring& y)
{
    WCHAR buffer[256];
 
    m_WindowX = x;
    if (m_WindowX.find(L'@') == std::wstring::npos && m_WindowXScreenDefined)
    {
        // Use present screen
        wsprintf(buffer, L"@%i", m_WindowXScreen);
        m_WindowX.append(buffer);
    }
    m_WindowY = y;
    if (m_WindowY.find(L'@') == std::wstring::npos && m_WindowYScreenDefined)
    {
        // Use present screen
        wsprintf(buffer, L"@%i", m_WindowYScreen);
        m_WindowY.append(buffer);
    }
 
    WindowToScreen();  // Calculate coordinates on the virtual screen (ScreenX/Y)
 
    DebugLog(L"MoveWindow: Convert coords: \"%s, %s\" to \"%i, %i\"", m_WindowX.c_str(), m_WindowY.c_str(), m_ScreenX, m_ScreenY);
 
    SetWindowPos(m_Window, NULL, m_ScreenX, m_ScreenY, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);
 
    DebugLog(L"MoveWindow: Move coords: to \"%i, %i\"", m_ScreenX, m_ScreenY);
 
    ScreenToWindow();
 
    if (m_SavePosition)
    {
        DebugLog(L"MoveWindow: WriteConfig()");
        WriteConfig();
    }
}

モニタ番号と移動先座標を計算するためにWindowToScreen関数を使うので、その前にWindowX/Yを組み立てています。
WindowToScreen関数での座標計算が終わったら、SetWindowPos APIで移動させます。このAPIを呼ぶと、内部でWM_WINDOWPOSCHANGINGを処理するので、Snap/KeepOnScreenの補正が入り、WM_MOVE(OnMove関数)で補正後の座標値を取得してから処理が戻ってきます。後は座標の保存が必要なら保存して終了です。

呼び出し側(CMeterWindow::RunBang関数)も、整数渡しから文字列渡しへと修正します。

    case BANG_MOVE:
        {
            // Separate "x y"
            std::wstring x = arg;
            std::wstring::size_type index = x.find(L' ');
            if (index != std::wstring::npos)
            {
                std::wstring y = x.substr(index + 1);
                x.erase(index);
                MoveWindow(x, y);
            }
            else
            {
                DebugLog(L"Cannot parse parameters for !RainmeterMove");
            }
        }
        break;

スペースで2つの文字列を分割して、MoveWindow関数に渡すだけです。

* 2009.11.15 追記 *

ユーザー切り替え機能を使って別のユーザーとしてログインし、作業を終えて再び元のユーザーへと戻ってきたら、Rainmeterがクラッシュしてしまっていたことがありました。
厳密には、Win+Lで新たにログインするユーザーを選び、そのログインの最中にクラッシュしてしまっていたようです。そのときのログは下のようなものでした。

DEBUG: (17:42:56.047) ******************************
DEBUG: (17:42:56.047) EnumDisplayDevices API
DEBUG: (17:42:56.047) ******************************
DEBUG: (17:42:56.047) EnumDisplayMonitors API
DEBUG: (17:42:56.047) WinDisc : L=0, T=0, R=1280, B=1024 (W=1280, H=1024), PRIMARY
DEBUG: (17:42:56.047) ******************************
DEBUG: (17:42:56.047) MONITOR: Count=0, Primary=1
DEBUG: (17:42:56.047) V: L=0, T=0, R=1280, B=1024 (W=1280, H=1024)
DEBUG: (17:42:56.047) ******************************

EnumDisplayDevices APIの部分で取得に失敗してしまって、EnumDisplayMonitors APIの結果とマージする処理が実行されずに、モニタがないことになってしまってたようです(その後の処理ではモニタがひとつもないことを考慮しないので、クラッシュしてしまいます)。

こんなふうにクラッシュすることばかりではなく、ちゃんと取れることもあるので、たぶんタイミングの問題なんでしょうが(失敗したらしばらく待ってから取得し直すとかすればいい?)、失敗してしまっては仕方ないので、失敗した場合は以前のようにEnumDisplayMonitors APIだけを使って取得し、この問題を回避しておきます。

ついでに、マルチモニタ情報のリセットの仕組みも、ResetMultiMonitorInfo関数を呼んでクリア→セットの両方をするのではなく、ClearMultiMonitorInfo関数とSetMultiMonitorInfo関数の2つに分けて、リセットするときにはClearのほうを呼び出すだけにしておきます(0.14.1でのやり方に戻す)。

struct MONITOR_INFO
{
    HMONITOR handle;                        //Monitor handle
    RECT rect;                                //Monitor rect on virtual screen
    MONITORINFOEX info;                        //Monitor information
    bool active;
};
 
struct MULTIMONITOR_INFO
{
    bool useEnumDisplayDevices;                //If false, Multi-monitor information is retrieved using only EnumDisplayMonitors
 
    int primary;                            //Index of primary monitor
    int vsT, vsL, vsH, vsW;                    //Coordinates of top-left corner and size of virtual screen
    std::vector<MONITOR_INFO> monitors;        //Monitor information
};
 
/* MyInfoEnumProc
**
** Retrieves the multi-monitor information
**
*/
BOOL CALLBACK MyInfoEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
    MULTIMONITOR_INFO* m = (MULTIMONITOR_INFO*)dwData;
 
    MONITORINFOEX info;
    info.cbSize = sizeof(MONITORINFOEX);
    GetMonitorInfo(hMonitor, &info);
 
    //DEBUG---------------
    DebugLog(L"%s : L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)%s", info.szDevice, lprcMonitor->left, lprcMonitor->top, lprcMonitor->right, lprcMonitor->bottom, lprcMonitor->right - lprcMonitor->left, lprcMonitor->bottom - lprcMonitor->top, (info.dwFlags & MONITORINFOF_PRIMARY) ? L", PRIMARY" : L"");
    //DEBUG---------------
 
    if (m->useEnumDisplayDevices)
    {
        for (size_t i = 0; i < m->monitors.size(); i++)
        {
            if (_wcsnicmp(info.szDevice, m->monitors[i].info.szDevice, 32) == 0)
            {
                m->monitors[i].handle = hMonitor;
                m->monitors[i].rect = *lprcMonitor;
                m->monitors[i].info = info;
                break;
            }
        }
    }
    else  // use only EnumDisplayMonitors
    {
        MONITOR_INFO monitor;
        monitor.handle = hMonitor;
        monitor.rect = *lprcMonitor;
        monitor.info = info;
        monitor.active = true;
 
        m->monitors.push_back(monitor);
 
        if (info.dwFlags & MONITORINFOF_PRIMARY)
        {
            // It's primary monitor!
            m->primary = (int)m->monitors.size();
        }
    }
 
    return TRUE;
}
 
/* ClearMultiMonitorInfo
**
** Clears the multi-monitor information
**
*/
void CMeterWindow::ClearMultiMonitorInfo()
{
    std::vector<MONITOR_INFO>& monitors = m_Monitors.monitors;
 
    //DEBUG---------------
    DebugLog(L"Multi-monitor information is cleared.");
    //DEBUG---------------
 
    monitors.clear();
    if (monitors.capacity() < 16) { monitors.reserve(16); }
}
 
/* SetMultiMonitorInfo
**
** Sets the multi-monitor information
**
*/
void CMeterWindow::SetMultiMonitorInfo()
{
    std::vector<MONITOR_INFO>& monitors = m_Monitors.monitors;
 
    m_Monitors.vsT = GetSystemMetrics(SM_YVIRTUALSCREEN);
    m_Monitors.vsL = GetSystemMetrics(SM_XVIRTUALSCREEN);
    m_Monitors.vsH = GetSystemMetrics(SM_CYVIRTUALSCREEN);
    m_Monitors.vsW = GetSystemMetrics(SM_CXVIRTUALSCREEN);
 
    m_Monitors.primary = 1;  // If primary screen is not found, 1st screen is assumed as primary screen.
 
    //DEBUG---------------
    DebugLog(L"******************************");
    DebugLog(L"EnumDisplayDevices API");
    //DEBUG---------------
    DISPLAY_DEVICE dd = {0};
    dd.cb = sizeof(DISPLAY_DEVICE);
 
    if (EnumDisplayDevices(NULL, 0, &dd, 0))
    {
        m_Monitors.useEnumDisplayDevices = true;
        DWORD dwDevice = 0;
 
        do
        {
            //DEBUG---------------
            std::wstring msg = dd.DeviceName;
            msg += L" - ";
            msg += dd.DeviceString;
            msg += L" : ";
            if (dd.StateFlags & DISPLAY_DEVICE_ACTIVE)
            {
                msg += L"ACTIVE ";
            }
            if (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
            {
                msg += L"PRIMARY ";
            }
            if (dd.StateFlags & DISPLAY_DEVICE_MULTI_DRIVER)
            {
                msg += L"MULTI ";
            }
            if (dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER)
            {
                msg += L"MIRROR ";
            }
            if (dd.StateFlags & DISPLAY_DEVICE_MODESPRUNED)
            {
                msg += L"PRUNED ";
            }
            if (dd.StateFlags & DISPLAY_DEVICE_REMOVABLE)
            {
                msg += L"REMOVABLE";
            }
            DebugLog(msg.c_str());
            //DEBUG---------------
            if ((dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) == 0)
            {
                MONITOR_INFO monitor = {0};
                wcsncpy_s(monitor.info.szDevice, 32, dd.DeviceName, 32);  // e.g. "\\.\DISPLAY1"
                monitor.active = (dd.StateFlags & DISPLAY_DEVICE_ACTIVE);
                monitors.push_back(monitor);  // reserves the space
 
                if (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
                {
                    // It's primary monitor!
                    m_Monitors.primary = (int)monitors.size();
                }
            }
            dwDevice++;
        } while (EnumDisplayDevices(NULL, dwDevice, &dd, 0));
    }
    else
    {
        m_Monitors.useEnumDisplayDevices = false;
        //DEBUG---------------
        DebugLog(L"EnumDisplayDevices failed. Only EnumDisplayMonitors is used instead.");
        //DEBUG---------------
    }
 
    //DEBUG---------------
    DebugLog(L"******************************");
    DebugLog(L"EnumDisplayMonitors API");
    //DEBUG---------------
    EnumDisplayMonitors(NULL, NULL, MyInfoEnumProc, (LPARAM)(&m_Monitors));
 
    //DEBUG---------------
    DebugLog(L"******************************");
    DebugLog(L"MONITOR: Count=%i, Primary=%i", monitors.size(), m_Monitors.primary);
    DebugLog(L"V: L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", m_Monitors.vsL, m_Monitors.vsT, m_Monitors.vsL + m_Monitors.vsW, m_Monitors.vsT + m_Monitors.vsH, m_Monitors.vsW, m_Monitors.vsH);
    for (size_t i = 0; i < monitors.size(); i++)
    {
        if (monitors[i].active)
        {
            DebugLog(L"%i: %s (active) : L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", i + 1, monitors[i].info.szDevice, monitors[i].rect.left, monitors[i].rect.top, monitors[i].rect.right, monitors[i].rect.bottom, monitors[i].rect.right - monitors[i].rect.left, monitors[i].rect.bottom - monitors[i].rect.top);
        }
        else
        {
            DebugLog(L"%i: %s (inactive)", i + 1, monitors[i].info.szDevice);
        }
    }
    DebugLog(L"******************************");
    //DEBUG---------------
}

EnumDisplayDevices APIが失敗したら(dwDeviceが0のままだったら)、その結果を使うことを示すフラグを立てないようにして、EnumDisplayMonitors APIの結果をそのまま使うようにしています。デバイス名での並べ替えもしません(ログを見ても変な名前が入ってきてるので、目安にできない)。

これでテストしてみた結果が以下の通り。

初期状態

DEBUG: (00:00:00.141) ******************************
DEBUG: (00:00:00.141) EnumDisplayDevices API
DEBUG: (00:00:00.141) \\.\DISPLAY1 - NVIDIA GeForce 6800 LE : ACTIVE PRIMARY PRUNED
DEBUG: (00:00:00.141) \\.\DISPLAY2 - ZoneScreen virtual display driver :
DEBUG: (00:00:00.141) \\.\DISPLAY3 - NVIDIA GeForce 6800 LE :
DEBUG: (00:00:00.141) \\.\DISPLAYV1 - NetMeeting driver : MIRROR
DEBUG: (00:00:00.141) \\.\DISPLAYV2 - RDPDD Chained DD : MIRROR
DEBUG: (00:00:00.141) ******************************
DEBUG: (00:00:00.141) EnumDisplayMonitors API
DEBUG: (00:00:00.141) \\.\DISPLAY1 : L=0, T=0, R=1280, B=1024 (W=1280, H=1024), PRIMARY
DEBUG: (00:00:00.141) ******************************
DEBUG: (00:00:00.141) MONITOR: Count=3, Primary=1
DEBUG: (00:00:00.141) V: L=0, T=0, R=1280, B=1024 (W=1280, H=1024)
DEBUG: (00:00:00.141) 1: \\.\DISPLAY1 (active) : L=0, T=0, R=1280, B=1024 (W=1280, H=1024)
DEBUG: (00:00:00.141) 2: \\.\DISPLAY2 (inactive)
DEBUG: (00:00:00.141) 3: \\.\DISPLAY3 (inactive)
DEBUG: (00:00:00.141) ******************************

Win+Lを押して、ユーザー切り替え選択画面を出した(※WM_SETTINGCHANGEが飛んできた)

DEBUG: (00:00:33.219) Multi-monitor information is cleared.
DEBUG: (00:00:33.281) ******************************
DEBUG: (00:00:33.281) EnumDisplayDevices API
DEBUG: (00:00:33.281) \\.\DISPLAY1 - NVIDIA GeForce 6800 LE : ACTIVE PRIMARY PRUNED
DEBUG: (00:00:33.281) \\.\DISPLAY2 - ZoneScreen virtual display driver :
DEBUG: (00:00:33.281) \\.\DISPLAY3 - NVIDIA GeForce 6800 LE :
DEBUG: (00:00:33.281) \\.\DISPLAYV1 - NetMeeting driver : MIRROR
DEBUG: (00:00:33.281) \\.\DISPLAYV2 - RDPDD Chained DD : MIRROR
DEBUG: (00:00:33.281) ******************************
DEBUG: (00:00:33.281) EnumDisplayMonitors API
DEBUG: (00:00:33.281) \\.\DISPLAY1 : L=0, T=0, R=1280, B=1024 (W=1280, H=1024), PRIMARY
DEBUG: (00:00:33.281) ******************************
DEBUG: (00:00:33.281) MONITOR: Count=3, Primary=1
DEBUG: (00:00:33.281) V: L=0, T=0, R=1280, B=1024 (W=1280, H=1024)
DEBUG: (00:00:33.281) 1: \\.\DISPLAY1 (active) : L=0, T=0, R=1280, B=1024 (W=1280, H=1024)
DEBUG: (00:00:33.281) 2: \\.\DISPLAY2 (inactive)
DEBUG: (00:00:33.281) 3: \\.\DISPLAY3 (inactive)
DEBUG: (00:00:33.281) ******************************

別のユーザーを選んでログインした(※WM_SETTINGCHANGEが飛んできたけど、EnumDisplayDevices APIが失敗)

DEBUG: (00:00:38.719) Multi-monitor information is cleared.
DEBUG: (00:00:39.031) ******************************
DEBUG: (00:00:39.031) EnumDisplayDevices API
DEBUG: (00:00:39.031) EnumDisplayDevices failed. Only EnumDisplayMonitors is used instead.
DEBUG: (00:00:39.047) ******************************
DEBUG: (00:00:39.047) EnumDisplayMonitors API
DEBUG: (00:00:39.047) WinDisc : L=0, T=0, R=1280, B=1024 (W=1280, H=1024), PRIMARY
DEBUG: (00:00:39.047) ******************************
DEBUG: (00:00:39.047) MONITOR: Count=1, Primary=1
DEBUG: (00:00:39.047) V: L=0, T=0, R=1280, B=1024 (W=1280, H=1024)
DEBUG: (00:00:39.047) 1: WinDisc (active) : L=0, T=0, R=1280, B=1024 (W=1280, H=1024)
DEBUG: (00:00:39.047) ******************************

これでCount=0となることはなくなりました。
ただ、デバイス名もなんだかこれでいいのかどうかわからないし、マルチモニタが有効な状態で起こったらどうなるのかがわかりません(自分の環境ではマルチモニタの状態で再現できなかった)。この対応にさらに加えるとすれば、EnumDisplayDevices APIが失敗したら何度かリトライして、それでもダメなら今回の措置を使うようにしたほうがいいかもしれません。

* * *

リトライを実装してみたものの……

DEBUG: (00:42:24.015) Multi-monitor information is cleared.
DEBUG: (00:42:24.172) EnumDisplayDevices failed. Wait for 300ms. (1/10)
DEBUG: (00:42:24.484) EnumDisplayDevices failed. Wait for 300ms. (2/10)
DEBUG: (00:42:24.781) EnumDisplayDevices failed. Wait for 300ms. (3/10)
DEBUG: (00:42:25.093) EnumDisplayDevices failed. Wait for 300ms. (4/10)
DEBUG: (00:42:25.609) EnumDisplayDevices failed. Wait for 300ms. (5/10)
DEBUG: (00:42:25.937) EnumDisplayDevices failed. Wait for 300ms. (6/10)
DEBUG: (00:42:26.281) EnumDisplayDevices failed. Wait for 300ms. (7/10)
DEBUG: (00:42:27.047) EnumDisplayDevices failed. Wait for 300ms. (8/10)
DEBUG: (00:42:27.406) EnumDisplayDevices failed. Wait for 300ms. (9/10)
DEBUG: (00:42:27.703) EnumDisplayDevices failed. Wait for 300ms. (10/10)
DEBUG: (00:42:28.015) EnumDisplayDevices failed. Only EnumDisplayMonitors is used instead.
DEBUG: (00:42:28.015) ******************************
DEBUG: (00:42:28.031) EnumDisplayMonitors API
DEBUG: (00:42:28.031) WinDisc : L=0, T=0, R=1280, B=1024 (W=1280, H=1024), PRIMARY
DEBUG: (00:42:28.031) ******************************
DEBUG: (00:42:28.031) MONITOR: Count=1, Primary=1
DEBUG: (00:42:28.031) V: L=0, T=0, R=1280, B=1024 (W=1280, H=1024)
DEBUG: (00:42:28.031) 1: WinDisc (active) : L=0, T=0, R=1280, B=1024 (W=1280, H=1024)
DEBUG: (00:42:28.047) ******************************

300ms×10回で計3秒分程度のSleepでは、結局取得できないという結果に。

ログイン処理には結構な時間がかかってしまうので、このSleepを合計10~20秒とかに増やしてしまうといいのかもしれないけれど、そうしたらそうしたで、そのSleepの間はRainmeter内の全ての処理がこのマルチモニタ情報の取得部分で止まってしまうという状態になってしまうので、むやみに延ばすのもなぁ……というところ。
(ユーザー切り替え中はどうせ見えないので、長時間止まってても大差ないんだけれど、使い方によっては値が意図しないほど大きな値になったりするかもしれない(Sleepしていたぶん、更新間隔ごとに複数回に分けて行われるはずだった処理が、一回で行われてしまう))

通常使用中にEnumDisplayDevices APIが失敗するような状況を考えないなら(そんな状況なら他のアプリもやばい気がする)、若干Sleep時間を延ばして500ms×20回の計10秒程度なら待つのもありかなぁ……。その間は更新間隔ごとに更新もできればいいけれど、これは原理上無理なので諦め。

/* SetMultiMonitorInfo
**
** Sets the multi-monitor information
**
*/
void CMeterWindow::SetMultiMonitorInfo()
{
    std::vector<MONITOR_INFO>& monitors = m_Monitors.monitors;
 
    m_Monitors.vsT = GetSystemMetrics(SM_YVIRTUALSCREEN);
    m_Monitors.vsL = GetSystemMetrics(SM_XVIRTUALSCREEN);
    m_Monitors.vsH = GetSystemMetrics(SM_CYVIRTUALSCREEN);
    m_Monitors.vsW = GetSystemMetrics(SM_CXVIRTUALSCREEN);
 
    m_Monitors.primary = 1;  // If primary screen is not found, 1st screen is assumed as primary screen.
 
    DISPLAY_DEVICE dd = {0};
    dd.cb = sizeof(DISPLAY_DEVICE);
 
    BOOL result = FALSE;
    for (int i = 1, loop = 20; i <= loop; i++)
    {
        if ((result = EnumDisplayDevices(NULL, 0, &dd, 0)) != 0) break;
 
        //DEBUG---------------
        DebugLog(L"EnumDisplayDevices failed. Wait for 500ms. (%i/%i)", i, loop);
        //DEBUG---------------
        Sleep(500);
    }
 
    if (result)
    {
        m_Monitors.useEnumDisplayDevices = true;
        DWORD dwDevice = 0;
 
        //DEBUG---------------
        DebugLog(L"******************************");
        DebugLog(L"EnumDisplayDevices API");
        //DEBUG---------------
 
        do
        {
            //DEBUG---------------
            std::wstring msg = dd.DeviceName;
            msg += L" - ";
            msg += dd.DeviceString;
            msg += L" : ";
            if (dd.StateFlags & DISPLAY_DEVICE_ACTIVE)
            {
                msg += L"ACTIVE ";
            }
            if (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
            {
                msg += L"PRIMARY ";
            }
            if (dd.StateFlags & DISPLAY_DEVICE_MULTI_DRIVER)
            {
                msg += L"MULTI ";
            }
            if (dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER)
            {
                msg += L"MIRROR ";
            }
            if (dd.StateFlags & DISPLAY_DEVICE_MODESPRUNED)
            {
                msg += L"PRUNED ";
            }
            if (dd.StateFlags & DISPLAY_DEVICE_REMOVABLE)
            {
                msg += L"REMOVABLE";
            }
            DebugLog(msg.c_str());
            //DEBUG---------------
            if ((dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) == 0)
            {
                MONITOR_INFO monitor = {0};
                wcsncpy_s(monitor.info.szDevice, 32, dd.DeviceName, 32);  // e.g. "\\.\DISPLAY1"
                monitor.active = (dd.StateFlags & DISPLAY_DEVICE_ACTIVE);
                monitors.push_back(monitor);  // reserves the space
 
                if (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
                {
                    // It's primary monitor!
                    m_Monitors.primary = (int)monitors.size();
                }
            }
            dwDevice++;
        } while (EnumDisplayDevices(NULL, dwDevice, &dd, 0));
    }
    else
    {
        m_Monitors.useEnumDisplayDevices = false;
        //DEBUG---------------
        DebugLog(L"EnumDisplayDevices failed. Only EnumDisplayMonitors is used instead.");
        //DEBUG---------------
    }
 
    //DEBUG---------------
    DebugLog(L"******************************");
    DebugLog(L"EnumDisplayMonitors API");
    //DEBUG---------------
    EnumDisplayMonitors(NULL, NULL, MyInfoEnumProc, (LPARAM)(&m_Monitors));
 
    //DEBUG---------------
    DebugLog(L"******************************");
    DebugLog(L"MONITOR: Count=%i, Primary=%i", monitors.size(), m_Monitors.primary);
    DebugLog(L"V: L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", m_Monitors.vsL, m_Monitors.vsT, m_Monitors.vsL + m_Monitors.vsW, m_Monitors.vsT + m_Monitors.vsH, m_Monitors.vsW, m_Monitors.vsH);
    for (size_t i = 0; i < monitors.size(); i++)
    {
        if (monitors[i].active)
        {
            DebugLog(L"%i: %s (active) : L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", i + 1, monitors[i].info.szDevice, monitors[i].rect.left, monitors[i].rect.top, monitors[i].rect.right, monitors[i].rect.bottom, monitors[i].rect.right - monitors[i].rect.left, monitors[i].rect.bottom - monitors[i].rect.top);
        }
        else
        {
            DebugLog(L"%i: %s (inactive)", i + 1, monitors[i].info.szDevice);
        }
    }
    DebugLog(L"******************************");
    //DEBUG---------------
}

結果、

DEBUG: (00:11:33.828) Multi-monitor information is cleared.
DEBUG: (00:11:34.406) EnumDisplayDevices failed. Wait for 500ms. (1/20)
DEBUG: (00:11:34.906) EnumDisplayDevices failed. Wait for 500ms. (2/20)
DEBUG: (00:11:35.500) EnumDisplayDevices failed. Wait for 500ms. (3/20)
DEBUG: (00:11:36.062) EnumDisplayDevices failed. Wait for 500ms. (4/20)
DEBUG: (00:11:36.562) EnumDisplayDevices failed. Wait for 500ms. (5/20)
DEBUG: (00:11:37.078) EnumDisplayDevices failed. Wait for 500ms. (6/20)
DEBUG: (00:11:37.578) EnumDisplayDevices failed. Wait for 500ms. (7/20)
DEBUG: (00:11:38.078) EnumDisplayDevices failed. Wait for 500ms. (8/20)
DEBUG: (00:11:38.594) EnumDisplayDevices failed. Wait for 500ms. (9/20)
DEBUG: (00:11:39.109) EnumDisplayDevices failed. Wait for 500ms. (10/20)
DEBUG: (00:11:39.609) EnumDisplayDevices failed. Wait for 500ms. (11/20)
DEBUG: (00:11:40.125) EnumDisplayDevices failed. Wait for 500ms. (12/20)
DEBUG: (00:11:40.640) EnumDisplayDevices failed. Wait for 500ms. (13/20)
DEBUG: (00:11:41.140) EnumDisplayDevices failed. Wait for 500ms. (14/20)
DEBUG: (00:11:41.734) EnumDisplayDevices failed. Wait for 500ms. (15/20)
DEBUG: (00:11:42.328) EnumDisplayDevices failed. Wait for 500ms. (16/20)
DEBUG: (00:11:42.828) EnumDisplayDevices failed. Wait for 500ms. (17/20)
DEBUG: (00:11:43.328) EnumDisplayDevices failed. Wait for 500ms. (18/20)
DEBUG: (00:11:43.828) EnumDisplayDevices failed. Wait for 500ms. (19/20)
DEBUG: (00:11:44.328) EnumDisplayDevices failed. Wait for 500ms. (20/20)
DEBUG: (00:11:44.828) EnumDisplayDevices failed. Only EnumDisplayMonitors is used instead.
DEBUG: (00:11:44.828) ******************************
DEBUG: (00:11:44.828) EnumDisplayMonitors API
DEBUG: (00:11:44.828) WinDisc : L=0, T=0, R=1280, B=1024 (W=1280, H=1024), PRIMARY
DEBUG: (00:11:44.828) ******************************
DEBUG: (00:11:44.828) MONITOR: Count=1, Primary=1
DEBUG: (00:11:44.828) V: L=0, T=0, R=1280, B=1024 (W=1280, H=1024)
DEBUG: (00:11:44.828) 1: WinDisc (active) : L=0, T=0, R=1280, B=1024 (W=1280, H=1024)
DEBUG: (00:11:44.828) ******************************

どれだけ延ばしても無理っぽい(明らかにログイン処理が終わってるのにまだ続いてたし)。
セッションが切り替わると取得できなくなるのかもしれないので、やっぱり元のコードに戻そう。

« 明けました [Rainmeter-dev] いろいろ »

- Comments
4 Comments

初めましてこんにちは

先日モニタを新たに購入し、マルチモニタ環境になったのですが、Rainmeterスキンをセカンダリモニタに配置してからPCを再起動するとセカンダリモニタに配置されていたスキンがプライマリモニタに戻されるという現象に悩まされています。
そこでこちらの記事にありました、Rainmeter-dll.zip(http://www.rainmeter.net/forum/viewtopic.php?f=14&t=1491#p14780)を導入してみようと思ったのですが、該当リンク先でのダウンロードリンクを見つけることができませんでした。
単純な見落としでしたら大変失礼なのですが、もしリンク切れが起きているようでしたら該当ファイルの再アップロードをお願いできないでしょうか。

お返事お待ちしております

by じゃむ | 05 23, 2010 - URL [ edit ]

じゃむさんこんにちは。

先日フォーラムでの添付ファイルを整理した際にうっかり削除してしまったようです。お手間をかけさせてしまってごめんなさい。
オリジナルはもう削除しちゃって手元に残っていないので、該当の修正が入っているr320版を下に置いておきました。
http://cid-8ce9e4300babcfde.skydrive.live.com/self.aspx/.Public/Rainmeter%20temporal/Rainmeter-dll%5E_r320.zip

正式な修正版(Rainmeter1.2)はもうじき出ると思いますので、出たらそちらも試してみてください。

by furu | 05 23, 2010 - URL [ edit ]

お早い御返事ありがとうございます。
早速ダウンロードして導入したところ、無事に問題点を解決することができました。
また「TwitterのTLが文字化けする」のエントリより、WebParser.dllも同時に導入しTwitterスキンの不具合も解消することができました。
個人的に不具合修正をしてくださるfuruさんのような方には本当に助かります。
これからもRainmeterの更なる発展に向けて頑張ってください。
影ながら応援しております!



色々とはっちゃけてるえりりん、私もファンですノ゚ ヮ゚ノ

by じゃむ | 05 23, 2010 - URL [ edit ]

問題解決の手助けができてよかったです。
わっほわっほ!

by furu | 05 23, 2010 - URL [ edit ]

管理者にだけ表示を許可する
- Trackbacks
0 Trackbacks


この記事にトラックバックする(FC2ブログユーザー)

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。