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

スポンサーサイト

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

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

[Rainmeter-dev] Aeroが有効な環境で"On Desktop"にすると、スキンウィンドウが消える問題

2009.11.05 | Rainmeter-dev // Issue/互換性問題やバグ修正

0 Comments

Aeroが有効な状態でスキンのZオーダーを"On Desktop"に変更したら、画面から消えてなくなってしまう問題についてのまとめです。
私的なまとめですので、公開されているビルドに反映されているものではありません。ご注意ください。

■更新履歴(※更新のたびに、たぶん一番上にあがってきます)
- 2009.10.30 : Desktop compositionが有効なら、"On Desktop"を使わせないようにする("Bottom"に切り替える)
- 2009.11.05 : "On Desktop"なスキンウィンドウが存在するときにDesktop compositonを無効→有効にされたら、その切り替えを検知して"Bottom"に切り替えるようにする

以下、続きに格納。

VMwareの新しいバージョンでAeroが有効にできるらしい(WDDM対応)と聞いて、ここ数日、今まで記事にしたようなものがどう動くのかのチェックも兼ねて、いろいろとWindows 7(の試用版)を試してました。Aeroが有効にできるまでにいろいろと嵌ったところもありましたが……。

Aeroが有効にできて最初に確認したかったのが、タイトルのとおりで、Aeroが有効な状態でスキンのZオーダーを"On Desktop"に変更したら、消えてなくなってしまうらしいという問題です。
Vista以降はDWMという新しいウィンドウマネージャーが用意されていて、Aeroが有効にできる環境では、Aeroテーマを使うことでこの新しい機能群を使うことができます。Aero Glassとか。7なら追加してAero Peek/Snap/Shakeなど。Aeroテーマ以外のBasicテーマなどを使うことで、以前の動作互換にすることができますが、条件を満たしてるならAeroを使いたいですよね。

Aeroが有効というのはつまり、DWMによるDesktop compositionが有効になるということでして、このDesktop compositionこそが"On Desktop"での動作を阻害してしまっている原因のようです。

Rainmeterの"On Desktop"についておさらい。
"Bottom"と"On Desktop"の違いは、デスクトップ画面に貼り付いた状態を維持できるかどうかの差です。例えばタスクバーから「デスクトップを表示」メニューをクリックしたときに、"Bottom"では非表示になってしまいますが、"On Desktop"ではデスクトップに貼り付いたままを維持することができます。

その仕組みを実現するのに、Rainmeterでは以下のような"charm"を使っています。

        case ZPOSITION_ONDESKTOP:
            // Set the window's parent to progman, so it stays always on desktop
            HWND ProgmanHwnd = FindWindow(L"Progman", L"Program Manager");
            if (ProgmanHwnd && (parent != ProgmanHwnd))
            {
                SetParent(m_Window, ProgmanHwnd);
            }
            else
            {
                return;        // The window is already on desktop
            }
            break;
        }

この方法は検索するとちらほらと出てきますが、本来は推奨されない使い方です(スレッドの違うウィンドウを親に設定しているので、意図しない挙動をすることがある)。ただ、「簡単にデスクトップに貼り付いたままにできる」という理由からよく使われてしまっていますが。

デスクトップ画面は、実際には大きなリストビューになっているのですが、それを子に持つのが"Progman"です。これは「デスクトップを表示」にしても消えないので(ある意味デスクトップ自身だから)、こいつを親に設定してやれば消えなくなるという理屈でしょうか。

このウィンドウは、Vistaでも7でも存在はしています。Vista/7でも、Basicテーマなどを使えば上のコードのままでデスクトップに貼り付いたまま表示できるようです。

問題は、Desktop compositionが有効になっている時です。こいつが有効になっていると、見えている(と思われる)ウィンドウ全てを一度DWM側で合成してから描画します。この流れで、SetParentした非正規なスキンウィンドウが反映されず、消えてしまいます(本当に親子関係がいびつなのが原因で反映されないのかとか、そういう詳しい仕組みはよくわからない)。その状態からBasicテーマに変更するなどして無効にしてやると、ちゃんと"On Desktop"として表示されます。

スキンウィンドウにWS_CHILDを付けてやったり、NativeTransparencyを無効にしてやってみても結果は同じだったので、たぶん上のコードの方法では無理なのでしょう。検索しても、解決できたらしい書き込みは見つけられませんでした。
(Windowsの用意する"ガジェット"ではそういうことが起こらないらしいですが、たぶんMS的な対応なんでしょう。「デスクトップを表示」[Win+d]の対象からそもそも外されてるとか。……そもそもWin+dってウィンドウ自体にメッセージが全く送られてこないから("Progman"にWM_USER+83のメッセージが送られるだけ)、"Progman"のメッセージをフックするくらいしか思いつかないし。何か方法あるのかな?)

消えてしまっては操作のしようもないので、回避策として、Aeroが有効な環境では"On Desktop"を指定されたら"Bottom"として動くようにしておくほうが良いいかなぁと思うので、次のようにしてみます。
(デスクトップへの貼り付き機能はなくなりますが、現時点では他に回避策が見つからないので)

        m_DwmapiLibrary = LoadLibrary(L"dwmapi.dll");
 
        /* snip */
 
        case ZPOSITION_ONDESKTOP:
            // If the DWM desktop composition is enabled, use "Bottom" instead of "On Desktop"
            if (m_DwmapiLibrary)
            {
                typedef HRESULT (WINAPI * FPDWMISCOMPOSITIONENABLED)(BOOL* pfEnabled);
 
                FPDWMISCOMPOSITIONENABLED DwmIsCompositionEnabled = (FPDWMISCOMPOSITIONENABLED)GetProcAddress(m_DwmapiLibrary, "DwmIsCompositionEnabled");
                if (DwmIsCompositionEnabled)
                {
                    BOOL fEnabled = FALSE;
                    if (DwmIsCompositionEnabled(&fEnabled) == S_OK && fEnabled)
                    {
                        DebugLog(L"DWM desktop composition is enabled, so \"Bottom\" is used instead of \"On Desktop\".");
                        m_WindowZPosition = ZPOSITION_ONBOTTOM;
                        winPos = HWND_BOTTOM;
                        break;
                    }
                }
            }
            // Set the window's parent to progman, so it stays always on desktop
            HWND ProgmanHwnd = FindWindow(L"Progman", L"Program Manager");
            if (ProgmanHwnd && (parent != ProgmanHwnd))
            {
                SetParent(m_Window, ProgmanHwnd);
            }
            else
            {
                return;        // The window is already on desktop
            }
            break;
        }

MeterWindow内でAero Peekの一部機能を無効にしている処理と同様に、dwmapi.dllが用意しているDWM制御用APIを呼び出してチェックします。このDLLは2000/XPには存在せず、Vista以降に追加されたDLLなので、LoadLibrary→GetProcAddressしてやってから使います(一部、XPでもdwmapi.dllが存在してしまう変な環境があるようで、その環境ではたぶんエントリーポイントが見つからないとかそういうエラーメッセージが表示されます。が、これはそもそもイレギュラーな環境なので、そのDLLをリネームしちゃうなり、それを導入してしまったアプリを削除するなりで解決してください)。

内容としては、DwmIsCompositonEnabled APIを使って、Desktop compositionが有効かどうかをチェックし、有効であれば"Bottom"を使うように変更します。ログ出力だと気付きにくいのでメッセージボックスで表示してもいいかもしれません。Bangされるたびに表示されてしまいますが……。

ちなみに、DwmEnableComposition APIという、Desktop compositionを一時的に有効/無効にするAPIもあります。これにアプリ側から無効フラグを引数として渡してやると、そのアプリから再度有効にしてやるか、そのアプリが終了するまで、一時的に無効の状態とすることができます。DWM側からすると、DWM非互換なアプリがいる間は無効にして振る舞うようです。パフォーマンスが必要なアプリなど(ベンチマークやフルスクリーンゲーム?)は、これを事前に呼び出してから実行してやるといいのかもしれません。でも、せっかくのAero Glass(やその他機能)が無効にされるのは勿体ないので、問題なければ無効にはしたくないですね。

* 2009.11.05 追記 *

上のコードだけでは、意図してZオーダーを"On Desktop"に変更したときにしか働いてくれません。

例えば(というかこれが全てなのですが)、Basicテーマの状態でRainmeterを起動し、スキンウィンドウを"On Desktop"に設定します。その後、テーマをAeroテーマに切り替えたとします。
Aeroテーマが使われることで、Desktop compositionが有効になります。この際、Zオーダーの変更は発生しません。つまり、そのままでは"On Desktop"の状態を維持しようとするので、スキンウィンドウが見えなくなってしまいます。

このような設定の切り替わりを検知して適切な処理をするために、ちゃんとウィンドウメッセージが定義されています。Desktop compositionの切り替わりはWM_DWMCOMPOSITIONCHANGEDメッセージを処理してやります。
……が、CMeterWindowのウィンドウプロシージャで処理すると、なぜか届きません。"On Desktop"以外のスキンウィンドウだと届くのですが、"Progman"にSetParentしてあるスキンウィンドウには届かないようです。トップレベルウィンドウではなくなっているので当然かもしれませんが。

とはいえ、"On Desktop"なスキンウィンドウこそ処理したい対象なので、届かないと先に進みません。そこで、WM_DISPLAYCHANGE / WM_SETTINGCHANGEの検知と同様に、CTrayWindowのウィンドウプロシージャで検知して処理してやるようにします。

    case WM_DWMCOMPOSITIONCHANGED:
        {
            // Deliver WM_DWMCOMPOSITIONCHANGED message to all meter windows
            std::map<std::wstring, CMeterWindow*>& windows = Rainmeter->GetAllMeterWindows();
            std::map<std::wstring, CMeterWindow*>::iterator iter = windows.begin();
            for( ; iter != windows.end(); iter++)
            {
                (*iter).second->OnDwmCompositionChanged();
            }
        }
        return 0;

CMeterWindow側には処理用の関数を用意しておきます。PostMessageしてやるほうがよさそうですが、処理内容はそんなに多くないので直接呼んでいます。

/*
** DwmIsCompositionEnabled
**
** Returns TRUE if the DWM desktop composition is enabled
**
*/
BOOL CMeterWindow::DwmIsCompositionEnabled()
{
    typedef HRESULT (WINAPI * FPDWMISCOMPOSITIONENABLED)(BOOL* pfEnabled);
 
    if (m_DwmapiLibrary)
    {
        FPDWMISCOMPOSITIONENABLED DwmIsCompositionEnabled = (FPDWMISCOMPOSITIONENABLED)GetProcAddress(m_DwmapiLibrary, "DwmIsCompositionEnabled");
        if (DwmIsCompositionEnabled)
        {
            BOOL fEnabled = FALSE;
            if (DwmIsCompositionEnabled(&fEnabled) == S_OK)
            {
                return fEnabled;
            }
        }
    }
 
    return FALSE;
}
 
/*
** OnDwmCompositionChanged
**
** Changes ZPosition of the window if the DWM desktop composition is enabled.
** This function is called by CTrayWindow.
**
*/
void CMeterWindow::OnDwmCompositionChanged() 
{
    if (!m_ChildWindow && m_WindowZPosition == ZPOSITION_ONDESKTOP)
    {
        // If the DWM desktop composition is enabled, use "Bottom" instead of "On Desktop"
        if (DwmIsCompositionEnabled())
        {
            DebugLog(L"DWM desktop composition is enabled, so \"Bottom\" is used instead of \"On Desktop\".");
            ChangeZPos(ZPOSITION_ONBOTTOM);
        }
    }
}

"On Desktop"になっているものだけ処理します。
Desktop compositionが有効/無効のどちらになったのかはわからないので、まず確認してから、有効になっていれば"Bottom"に切り替えています。

もう少し手を加えてやれば、"On Desktop"というフラグは維持したまま、Desktop composition有効中は"Bottom"と同様に動き、無効中は本来の"On Desktop"として動くなんてこともできそうです(若干フラグ管理が必要になりそうですが)。

« useLocalTime [WDM] Aero Glassを有効にしてみる »

- Comments
0 Comments

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


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

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