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

スポンサーサイト

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

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

[Rainmeter-dev] モニタセレクタ

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

0 Comments

モニタセレクタ

公式のフォーラムで行われていたマルチモニタについての議論の中で、手動でRainmeter.iniを編集することなく、スキンを表示するモニタを指定したいというような要望というか希望というか、そういう話が出ていたので、これまで書いてきたマルチモニタ対応コードを整理して他の部分へも適用しやすくしつつ、新たにモニタセレクタメニューなるものを追加してみました(上の画像の"Display Monitor"サブメニューがそれ)。r317にて他のマルチモニタ対応コードとともにコミット済みです。

Rainmeterには0.14.1の後期から、WindowX/Yに"@2"などのモニタ番号を付加することで、スキンを表示するモニタを指定する機能がついていますが、これは自動的に付加されるものではなく、ユーザ側が手動でRainmeter.iniを編集して付加する必要がありました(一部の環境ではマルチモニタ機能がバグっていたために、モニタ番号を付加しないと意図したように動かなかったこともあり、その回避策として編集してもらうこともあった)。今回追加したメニューはその編集の手間を減らします。

Rainmeter.iniにおけるモニタ番号の解釈には3タイプあり、

 ・プライマリモニタを表す(なにもつけない) 例: WindowX=100
 ・仮想スクリーンを表す("@0"をつける) 例: WindowX=100@0
 ・存在する各モニタの番号を表す("@n"をつける) 例: WindowX=100@2

のどれかが使えます。"@n"のnには1~モニタ数分まで指定できます。1台のみなら"@1"だけで、3台なら"@1"から"@3"まで指定できます。

メニュー項目は上の3項目それぞれに対応しています。"Use default: Primary monitor"がプライマリモニタ、"@0: Virtual screen"が仮想スクリーン、"@1: (モニタ名)" ~ "@3: (モニタ名)"までが各モニタになります。項目を選択した時点で、Rainmeter.iniでの表記もその指定された項目を基準にした座標に置き換わります。

一番下の"Auto-select based on window position"は、モニタ番号の指定をさらに簡略化させるオプションです。これをチェックした状態でスキンウィンドウをドラッグさせると、モニタ番号の指定を自動的にそのウィンドウが位置するモニタ番号に置き換えるようになります。例えば、"@1"にあるスキンウィンドウを"@2"へドラッグすると、Rainmeter.iniのモニタ番号も自動的に"@2"へと置き換わります。
自動判定の仕組み上、メニューからモニタが直接選択されたら、このオプションは解除されます。

* * *

と、いうわけで、シングルモニタ環境ではまったく利益のない機能ではありますが、マルチモニタ環境ではちょっぴり便利になる機能です。

続きからは具体的な処理内容について。

Rainmeterのコンテキストメニューは表示のたびにCRainmeterで作られています。そこへ"Display Monitor"サブメニューを埋め込みます。
直接埋め込むと長くなるので、CreateMonitorMenu関数を用意して、そちらでサブメニューを作ることにします。

HMENU CRainmeter::CreateMonitorMenu(CMeterWindow* meterWindow)
{
    WCHAR buffer[256];
    std::wstring item;
    UINT flags;
 
    HMENU monitorMenu = CreatePopupMenu();
 
    bool screenDefined = meterWindow->GetXScreenDefined();
    int screenIndex = meterWindow->GetXScreen();
 
    // for the "Primary monitor"
    flags = 0;
    if (!screenDefined)
    {
        flags |= MF_CHECKED;
    }
    AppendMenu(monitorMenu, flags, ID_CONTEXT_SKINMENU_MONITOR_PRIMARY, L"Use default: Primary monitor");
 
    // for the "Virtual screen" (@0)
    flags = 0;
    if (screenDefined && screenIndex == 0)
    {
        flags |= MF_CHECKED;
    }
    AppendMenu(monitorMenu, flags, ID_MONITOR_FIRST, L"@0: Virtual screen");
 
    // for the "Specified monitor" (@n)
    if (CMeterWindow::GetMonitorCount() > 0)
    {
        AppendMenu(monitorMenu, MF_SEPARATOR, 0, NULL);
 
        const MULTIMONITOR_INFO& multimonInfo = CMeterWindow::GetMultiMonitorInfo();
        const std::vector<MONITOR_INFO>& monitors = multimonInfo.monitors;
 
        for (size_t i = 0; i < monitors.size(); i++)
        {
            wsprintf(buffer, L"@%i: ", i + 1);
            item = buffer;
 
            flags = 0;
            if (screenDefined && screenIndex == (int)i + 1)
            {
                flags |= MF_CHECKED;
            }
 
            size_t len = wcslen(monitors[i].monitorName);
            if (len > 32)
            {
                item += std::wstring(monitors[i].monitorName, 32);
                item += L"...";
            }
            else
            {
                item += monitors[i].monitorName;
            }
 
            if (!monitors[i].active)
            {
                flags |= MF_GRAYED;
            }
 
            AppendMenu(monitorMenu, flags, ID_MONITOR_FIRST + i + 1, item.c_str());
        }
    }
 
    AppendMenu(monitorMenu, MF_SEPARATOR, 0, NULL);
 
    flags = 0;
    if (meterWindow->GetAutoSelectScreen())
    {
        flags |= MF_CHECKED;
    }
    AppendMenu(monitorMenu, flags, ID_CONTEXT_SKINMENU_MONITOR_AUTOSELECT, L"Auto-select based on window position");
 
    return monitorMenu;
}

特筆すべき部分は特にありません。各モニタのメニューを作る際には、メニューが選択された際に送られるメニューIDをID_MONITOR_FIRSTを基準にした一意な値にしてあります。また、スキンウィンドウの現時点のモニタ番号を参考にメニュー項目にチェックを入れたり、無効化されたモニタならグレーアウトさせたりしています。
モニタ名は長すぎるとメニューが酷いことになるため、表示は32文字までに制限してあります。それでも長い気がしますが……。

これらのメニューが選択されたときの処理はCMeterWindow側にあります。本当はメッセージの配送上、CTrayWindowも関係するのですが(491行目と505行目)、説明は省きます。

LRESULT CMeterWindow::OnCommand(WPARAM wParam, LPARAM lParam) 
{
    try 
    {
        /* ... */
 
        else if (wParam == ID_CONTEXT_SKINMENU_MONITOR_AUTOSELECT)
        {
            m_AutoSelectScreen = !m_AutoSelectScreen;
 
            ScreenToWindow();
 
            WriteConfig();
        }
        else if (wParam == ID_CONTEXT_SKINMENU_MONITOR_PRIMARY || wParam >= ID_MONITOR_FIRST && wParam <= ID_MONITOR_LAST)
        {
            std::vector<MONITOR_INFO>& monitors = c_Monitors.monitors;
 
            int screenIndex;
            bool screenDefined;
            if (wParam == ID_CONTEXT_SKINMENU_MONITOR_PRIMARY)
            {
                screenIndex = c_Monitors.primary;
                screenDefined = false;
            }
            else
            {
                screenIndex = (wParam & 0x0ffff) - ID_MONITOR_FIRST;
                screenDefined = true;
            }
 
            if (screenIndex >= 0 && (screenIndex == 0 || screenIndex <= (int)monitors.size() && monitors[screenIndex-1].active))
            {
                if (m_AutoSelectScreen)
                {
                    m_AutoSelectScreen = false;
                }
 
                m_WindowXScreen = m_WindowYScreen = screenIndex;
                m_WindowXScreenDefined = m_WindowYScreenDefined = screenDefined;
 
                m_Parser.ResetVariables(m_Rainmeter, this);  // Set present monitor variables
                ScreenToWindow();
 
                WriteConfig();
            }
        }
 
        /* ... */
    } 
 
    /* ... */
 
    return 0;
}

モニタが選択されたときは、そのメニューIDからID_MONITOR_FIRSTを引くことでモニタ番号に戻して使います。モニタが存在し、使用可能な状態であれば、その指定されたモニタを基準に座標を再計算し(ScreenToWindow関数)、Rainmeter.iniへ書き出します(WriteConfig関数)。#SCREENAREAX#などの変数も、この指定されたモニタの値へと置き換えられます。

"Auto-select ..."メニューが選択されたときは、フラグを反転させてからScreenToWindow関数を呼び出します。
ScreenToWindow関数はウィンドウがドラッグされたときにも呼ばれるため、この関数の冒頭に自動判定処理が組み込まれています。

    std::vector<MONITOR_INFO>& monitors = c_Monitors.monitors;
 
    if (monitors.empty())
    {
        DebugLog(L"There are no monitors. ScreenToWindow function fails.");
        return;
    }
 
    // Correct to auto-selected screen
    if (m_AutoSelectScreen)
    {
        RECT rect = {m_ScreenX, m_ScreenY, m_ScreenX + m_WindowW, m_ScreenY + m_WindowH};
        HMONITOR hMonitor = MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST);
 
        if (hMonitor != NULL)
        {
            for (size_t i = 0; i < monitors.size(); i++)
            {
                if (monitors[i].active && monitors[i].handle == hMonitor)
                {
                    int screenIndex = (int)i + 1;
                    bool reset = (!m_WindowXScreenDefined || !m_WindowYScreenDefined ||
                        m_WindowXScreen != screenIndex || m_WindowYScreen != screenIndex);
 
                    m_WindowXScreen = m_WindowYScreen = screenIndex;
                    m_WindowXScreenDefined = m_WindowYScreenDefined = true;
 
                    if (reset)
                    {
                        m_Parser.ResetVariables(m_Rainmeter, this);  // Set present monitor variables
                    }
                    break;
                }
            }
        }
    }

自動判定が有効なら、MonitorFromRect APIを使ってスキンウィンドウが乗っかっているモニタのハンドルを取得し、そのハンドルと一致するモニタ番号を探します。一致するものがあれば、それが現在のモニタ番号になります。必要であれば変数も更新します。
該当するものがなかったときの戻り値候補としてMONITOR_DEFAULTTONEARESTを渡しているので、必ずどれか有効なモニタのハンドルが返ってくると思います。

« [Rainmeter-dev] ネットワークトラフィックの値がおかしい #2 [Rainmeter-dev] Rainmeter on Windows 2000 »

- Comments
0 Comments

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


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

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