From aba47033dd2756cd7d26cbf1b77bc52ca4882a0d Mon Sep 17 00:00:00 2001 From: Vitaly Orekhov Date: Wed, 2 Sep 2026 23:15:44 +0300 Subject: [PATCH] [EXPLORER] Implement volume button controls (#8577) This provides a more convenient way to adjust system volume: through media buttons! Handles `APPCOMMAND_VOLUME_MUTE`, `APPCOMMAND_VOLUME_UP`, and `APPCOMMAND_VOLUME_DOWN` notifications. CORE-12323 --- base/shell/explorer/CMakeLists.txt | 1 + base/shell/explorer/mediabtns.cpp | 352 +++++++++++++++++++++++++++++ base/shell/explorer/mediabtns.h | 65 ++++++ base/shell/explorer/taskswnd.cpp | 45 ++-- base/shell/explorer/traywnd.cpp | 13 ++ 5 files changed, 456 insertions(+), 20 deletions(-) create mode 100644 base/shell/explorer/mediabtns.cpp create mode 100644 base/shell/explorer/mediabtns.h diff --git a/base/shell/explorer/CMakeLists.txt b/base/shell/explorer/CMakeLists.txt index 66d10755bfc..2fa8357d2b1 100644 --- a/base/shell/explorer/CMakeLists.txt +++ b/base/shell/explorer/CMakeLists.txt @@ -8,6 +8,7 @@ list(APPEND SOURCE appbar.cpp desktop.cpp explorer.cpp + mediabtns.cpp notifyiconscust.cpp rshell.cpp settings.cpp diff --git a/base/shell/explorer/mediabtns.cpp b/base/shell/explorer/mediabtns.cpp new file mode 100644 index 00000000000..02d5630a282 --- /dev/null +++ b/base/shell/explorer/mediabtns.cpp @@ -0,0 +1,352 @@ +/* + * PROJECT: ReactOS Explorer + * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) + * PURPOSE: Backend class for media buttons + * COPYRIGHT: Copyright 2026 Vitaly Orekhov + */ + +#include "precomp.h" +#include +#include "mediabtns.h" + +CMultimediaBackend::CMultimediaBackend() +{ + HMODULE hWinMM = LoadLibraryW(L"winmm.dll"); + bool bFailedGetProcAddress = false; + + if (!hWinMM) + return; + + bFailedGetProcAddress |= !WINMM_PROC(mixerOpen, pmxOpen); + bFailedGetProcAddress |= !WINMM_PROC(mixerClose, pmxClose); + bFailedGetProcAddress |= !WINMM_PROC(mixerSetControlDetails, pmxControlDetails); + bFailedGetProcAddress |= !WINMM_PROC(mixerGetControlDetailsW, pmxControlDetails); + bFailedGetProcAddress |= !WINMM_PROC(mixerGetID, pmxGetID); + bFailedGetProcAddress |= !WINMM_PROC(mixerGetLineInfoW, pmxLine); + bFailedGetProcAddress |= !WINMM_PROC(mixerGetLineControlsW, pmxLineControls); + bFailedGetProcAddress |= !WINMM_PROC(waveOutGetErrorTextW, pwoGetErrText); + bFailedGetProcAddress |= !WINMM_PROC(waveOutGetNumDevs, pwoGetNumDevs); + bFailedGetProcAddress |= !WINMM_PROC(waveOutMessage, pwoMessage); + + if (bFailedGetProcAddress) + { + ERR("CMultimediaBackend::ctor: One or more entry points to WinMM functions not found\n"); + FreeLibrary(hWinMM); + return; + } + + m_hWinMM = hWinMM; +}; + +CMultimediaBackend::~CMultimediaBackend() +{ + if (m_hMixer) + mixerClose(m_hMixer); + + HeapFree(hProcessHeap, 0, m_pdwChannelSteps); + + FreeLibrary(m_hWinMM); +} + +void +CMultimediaBackend::Initialize() +{ + if (m_hMixer) + mixerClose(m_hMixer); + m_hMixer = NULL; + + if (m_pdwChannelSteps != NULL) + HeapFree(hProcessHeap, 0, m_pdwChannelSteps); + m_pdwChannelSteps = NULL; + + if (waveOutGetNumDevs() == 0) + { + ERR("CMultimediaBackend::Initialize: No waveform-audio output devices detected\n"); + return; + } + + DWORD dwWaveOutDeviceID; + DWORD dw2 = 0; + + MMRESULT mmres = waveOutMessage((HWAVEOUT)UlongToHandle(WAVE_MAPPER), DRVM_MAPPER_PREFERRED_GET, + (DWORD_PTR)&dwWaveOutDeviceID, (DWORD_PTR)&dw2); + if (mmres != MMSYSERR_NOERROR) + LogError(__func__, "waveOutMessage failed", mmres); + + if (dwWaveOutDeviceID == (DWORD)-1) + { + TRACE("CMultimediaBackend::Initialize: No default output device was assigned, falling back to the first device\n"); + dwWaveOutDeviceID = 0; + } + + UINT mxID; + mmres = mixerGetID((HMIXEROBJ)UlongToHandle(dwWaveOutDeviceID), &mxID, MIXER_OBJECTF_WAVEOUT); + if (mmres != MMSYSERR_NOERROR) + { + LogError(__func__, "mixerGetID failed", mmres); + + if (mmres == MMSYSERR_NODRIVER) + { + ERR(" Not providing fallback to the first mixer device.\n"); + return; + } + + mxID = 0; + } + + mmres = mixerOpen(&m_hMixer, mxID, NULL, 0, MIXER_OBJECTF_MIXER); + if (mmres != MMSYSERR_NOERROR) + { + m_hMixer = NULL; + LogError(__func__, "mixerOpen failed", mmres); + return; + } + + MIXERLINEW mxln; + mxln.cbStruct = sizeof(mxln); + mxln.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS; + + mmres = mixerGetLineInfoW((HMIXEROBJ)m_hMixer, &mxln, MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE); + if (mmres != MMSYSERR_NOERROR) + { + LogError(__func__, "mixerGetLineInfoW failed", mmres); + return; + } + + m_dwMasterChannels = mxln.cChannels; + + MIXERLINECONTROLS mxlctrl; + MIXERCONTROLW mxc; + mxlctrl.cbStruct = sizeof(mxlctrl); + mxlctrl.dwLineID = mxln.dwLineID; + mxlctrl.dwControlID = MIXERCONTROL_CONTROLTYPE_MUTE; + mxlctrl.cControls = 1; + mxlctrl.cbmxctrl = sizeof(mxc); + mxlctrl.pamxctrl = &mxc; + + mmres = mixerGetLineControlsW((HMIXEROBJ)m_hMixer, &mxlctrl, MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE); + if (mmres != MMSYSERR_NOERROR) + { + LogError(__func__, "mixerGetLineControlsW failed retrieving Master Mute control", mmres); + return; + } + + m_dwMasterMuteControlID = mxc.dwControlID; + + mxlctrl.dwControlID = MIXERCONTROL_CONTROLTYPE_VOLUME; + + mmres = mixerGetLineControlsW((HMIXEROBJ)m_hMixer, &mxlctrl, MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE); + if (mmres != MMSYSERR_NOERROR) + { + LogError(__func__, "mixerGetLineControlsW failed retrieving Master Volume control", mmres); + return; + } + + m_dwMasterVolumeID = mxc.dwControlID; + m_dwMasterRanges.dwMinimum = mxc.Bounds.dwMinimum; + m_dwMasterRanges.dwMaximum = mxc.Bounds.dwMaximum; + + m_pdwChannelSteps = (PDWORD)HeapAlloc(hProcessHeap, 0, m_dwMasterChannels * sizeof(*m_pdwChannelSteps)); + + TRACE("CMultimediaBackend::Initialize: Master controls configured successfully:\n" + " Master Mute dwControlID: %lu; Master Volume dwControlID %lu\n" + " Master Volume channels: %lu; Master Volume value range: %lu-%lu\n", + m_dwMasterMuteControlID, m_dwMasterVolumeID, + m_dwMasterChannels, m_dwMasterRanges.dwMinimum, m_dwMasterRanges.dwMaximum); +} + +bool +CMultimediaBackend::Mute() +{ + if (!m_hWinMM) + { + WARN("CMultimediaBackend::Mute: WinMM functions unavailable; cannot (un)mute sound. Check the prior class constructor calls\n"); + return false; + } + + MIXERCONTROLDETAILS mxcd; + MIXERCONTROLDETAILS_BOOLEAN mxcdMute; + mxcd.cbStruct = sizeof(mxcd); + mxcd.dwControlID = m_dwMasterMuteControlID; + mxcd.cChannels = 1; + mxcd.cMultipleItems = 0; + mxcd.cbDetails = sizeof(mxcdMute); + mxcd.paDetails = &mxcdMute; + + mixerGetControlDetailsW((HMIXEROBJ)m_hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_GETCONTROLDETAILSF_VALUE); + mxcdMute.fValue = !mxcdMute.fValue; + mixerSetControlDetails((HMIXEROBJ)m_hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE); + + return true; +} + +bool +CMultimediaBackend::AdjustVolume(_In_ UINT direction, _In_ HANDLE hProcessHeap) +{ + if (!m_hWinMM) + { + WARN("CMultimediaBackend: WinMM functions unavailable; cannot %s volume. Check the prior class constructor calls\n", + direction == APPCOMMAND_VOLUME_DOWN ? "decrease" : "increase"); + return FALSE; + } + + MMRESULT mmres; + + MIXERCONTROLDETAILS mxcd; + mxcd.cbStruct = sizeof(mxcd); + mxcd.cMultipleItems = 0; + + /* If sound is muted and we want to increase volume, we are unmuting it. */ + if (direction == APPCOMMAND_VOLUME_UP) + { + MIXERCONTROLDETAILS_BOOLEAN mxcdMute; + mxcd.cChannels = 1; + mxcd.dwControlID = m_dwMasterMuteControlID; + mxcd.cbDetails = sizeof(mxcdMute); + mxcd.paDetails = &mxcdMute; + mxcdMute.fValue = false; + + mmres = mixerSetControlDetails((HMIXEROBJ)m_hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE); + if (mmres != MMSYSERR_NOERROR) + LogError(__func__, "mixerSetControlDetails failed unmuting Master Volume", mmres); + } + + if (GetCurrentVolume(&mxcd) != MMSYSERR_NOERROR) + return FALSE; + + PMIXERCONTROLDETAILS_UNSIGNED mxcdVolume = (PMIXERCONTROLDETAILS_UNSIGNED)mxcd.paDetails; + + bool bEitherCeiling = [&]() + { + DWORD dwValue = m_dwMasterRanges.dwMinimum; + + for (DWORD i = 0; i < m_dwMasterChannels; ++i) + { + if (mxcdVolume[i].dwValue > dwValue) + dwValue = mxcdVolume[i].dwValue; + } + + return direction == APPCOMMAND_VOLUME_DOWN + ? dwValue == m_dwMasterRanges.dwMinimum + : dwValue == m_dwMasterRanges.dwMaximum; + }(); + + if (bEitherCeiling) + { + TRACE("CMultimediaBackend::AdjustVolume: Volume boundary hit, no action\n"); + goto cleanup; + } + + CalculateSteps(mxcdVolume); + + for (DWORD i = 0; i < m_dwMasterChannels; ++i) + { + if (direction == APPCOMMAND_VOLUME_DOWN) + { + /* Protecting from underflow when decreasing volume */ + if (mxcdVolume[i].dwValue < m_pdwChannelSteps[i]) + mxcdVolume[i].dwValue = m_dwMasterRanges.dwMinimum; + else + mxcdVolume[i].dwValue -= m_pdwChannelSteps[i]; + } + else if (direction == APPCOMMAND_VOLUME_UP) + { + /* Protecting from overflow when increasing volume */ + if (m_dwMasterRanges.dwMaximum - mxcdVolume[i].dwValue < m_pdwChannelSteps[i]) + mxcdVolume[i].dwValue = m_dwMasterRanges.dwMaximum; + else + mxcdVolume[i].dwValue += m_pdwChannelSteps[i]; + } + } + + mmres = mixerSetControlDetails((HMIXEROBJ)m_hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE); + if (mmres == MMSYSERR_NOERROR) + { + TRACE("CMultimediaBackend::AdjustVolume: Volume values were updated:\n"); + for (DWORD i = 0; i < m_dwMasterChannels; ++i) + TRACE(" Channel %lu volume: %lu\n", i, mxcdVolume[i].dwValue); + } + else + { + LogError(__func__, "mixerSetControlDetails failed updating Master Volume control details", mmres); + } + +cleanup: + HeapFree(hProcessHeap, 0, mxcdVolume); + return TRUE; +} + +void +CMultimediaBackend::CalculateSteps(_Inout_ PMIXERCONTROLDETAILS_UNSIGNED pmxcdVolume) +{ + DWORD dwHighestVolume = m_dwMasterRanges.dwMinimum; + DWORD dwLoudestChIdx = 0; + + /* Find the loudest channel of all available */ + for (DWORD i = 0; i < m_dwMasterChannels; ++i) + { + if (pmxcdVolume[i].dwValue < dwHighestVolume) + continue; + + dwLoudestChIdx = i; + dwHighestVolume = pmxcdVolume[dwLoudestChIdx].dwValue; + } + + /* Calculate steps for uneven channels */ + for (DWORD i = 0; i < m_dwMasterChannels; ++i) + { + /* As we rely on MIXERLINEW.cChannels, there may be a case of multiple channels having equal high volume. */ + if (dwHighestVolume == m_dwMasterRanges.dwMinimum) + { + TRACE("CMultimediaBackend::CalculateSteps: All channels were at minimum volume, steps will be unchanged\n"); + break; + } + else if (i == dwLoudestChIdx || (pmxcdVolume[i].dwValue == dwHighestVolume)) + { + m_pdwChannelSteps[i] = (m_dwMasterRanges.dwMaximum - m_dwMasterRanges.dwMinimum) / VOLUME_STEPS_COUNT; + TRACE("CMultimediaBackend::CalculateSteps: Channel %lu is the loudest, volume step will be %lu\n", i, m_pdwChannelSteps[i]); + } + else + { + if (dwHighestVolume > 0) + { + float dwStepRatio = (float)pmxcdVolume[i].dwValue / dwHighestVolume; + m_pdwChannelSteps[i] = ceil(dwStepRatio * m_pdwChannelSteps[dwLoudestChIdx]); + } + else + { + m_pdwChannelSteps[i] = 0; + } + TRACE("CMultimediaBackend::CalculateSteps: Channel %lu volume step will be %lu\n", i, m_pdwChannelSteps[i]); + } + } +} + +void +CMultimediaBackend::LogError(_In_ const char* pszFunctionName, _In_ const char* pszMessage, _In_ MMRESULT mmres) +{ + static WCHAR pszWinMMErrText[MAXERRORLENGTH] = {0}; + waveOutGetErrorTextW(mmres, pszWinMMErrText, _countof(pszWinMMErrText)); + ERR("CMultimediaBackend::%s: %: %lu (%S)\n", pszFunctionName, pszMessage, mmres, pszWinMMErrText); +} + +MMRESULT +CMultimediaBackend::GetCurrentVolume(_Inout_ PMIXERCONTROLDETAILS pmxcd) +{ + PMIXERCONTROLDETAILS_UNSIGNED mxcdVolume = + (PMIXERCONTROLDETAILS_UNSIGNED)HeapAlloc(hProcessHeap, 0, + m_dwMasterChannels * sizeof(*mxcdVolume)); + + if (mxcdVolume == NULL) + { + ERR("CMultimediaBackend::GetCurrentVolume: HeapAlloc failed, requested %lu bytes\n", m_dwMasterChannels * sizeof(*mxcdVolume)); + return MMSYSERR_NOMEM; + } + + pmxcd->cChannels = m_dwMasterChannels; + pmxcd->dwControlID = m_dwMasterVolumeID; + pmxcd->cbDetails = sizeof(*mxcdVolume); + pmxcd->paDetails = mxcdVolume; + + return mixerGetControlDetailsW((HMIXEROBJ)m_hMixer, pmxcd, MIXER_OBJECTF_HMIXER | MIXER_GETCONTROLDETAILSF_VALUE); +} diff --git a/base/shell/explorer/mediabtns.h b/base/shell/explorer/mediabtns.h new file mode 100644 index 00000000000..05df8b5ebbe --- /dev/null +++ b/base/shell/explorer/mediabtns.h @@ -0,0 +1,65 @@ +/* + * PROJECT: ReactOS Explorer + * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) + * PURPOSE: Backend class for media buttons (header) + * COPYRIGHT: Copyright 2026 Vitaly Orekhov + */ + +#include + +typedef MMRESULT (WINAPI* pmxControlDetails)(HMIXEROBJ hmxobj, LPMIXERCONTROLDETAILS pmxcd, DWORD fdwDetails); +typedef MMRESULT (WINAPI* pmxGetID)(HMIXEROBJ hmxobj, UINT* puMxId, DWORD fdwId); +typedef MMRESULT (WINAPI* pmxLine)(HMIXEROBJ hmxobj, LPMIXERLINEW pmxcd, DWORD fdwDetails); +typedef MMRESULT (WINAPI* pmxLineControls)(HMIXEROBJ hmxobj, LPMIXERLINECONTROLSW pmxcd, DWORD fdwDetails); +typedef MMRESULT (WINAPI* pmxOpen)(LPHMIXER phmx, UINT uMxId, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD fdwOpen); +typedef MMRESULT (WINAPI* pmxClose)(HMIXER hmx); +typedef MMRESULT (WINAPI* pwoGetErrText)(MMRESULT mmrError, LPWSTR pszText, UINT cchText); +typedef MMRESULT (WINAPI* pwoGetNumDevs)(VOID); +typedef MMRESULT (WINAPI* pwoMessage)(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dw1, DWORD_PTR dw2); +#define WINMM_PROC(func, type) ((func) = (type)GetProcAddress(hWinMM, #func)) + +/* Media volume buttons are synchronized against sndvol32 meters */ +#define VOLUME_STEPS_COUNT 25 + +class CMultimediaBackend +{ +public: + CMultimediaBackend(); + ~CMultimediaBackend(); + CMultimediaBackend(CMultimediaBackend&) = delete; + CMultimediaBackend(CMultimediaBackend&&) = delete; + CMultimediaBackend& operator=(CMultimediaBackend&) = delete; + CMultimediaBackend& operator=(CMultimediaBackend&&) = delete; + + void Initialize(); + bool Mute(); + bool AdjustVolume(_In_ UINT direction, _In_ HANDLE hProcessHeap); + +private: + void CalculateSteps(_Inout_ PMIXERCONTROLDETAILS_UNSIGNED pmxcdVolume); + void LogError(_In_ const char* pszFunctionName, _In_ const char* pszMessage, _In_ MMRESULT mmres); + MMRESULT GetCurrentVolume(_Inout_ PMIXERCONTROLDETAILS pmxcd); + + pmxControlDetails mixerSetControlDetails; + pmxControlDetails mixerGetControlDetailsW; + pmxGetID mixerGetID; + pmxLine mixerGetLineInfoW; + pmxLineControls mixerGetLineControlsW; + pmxOpen mixerOpen; + pmxClose mixerClose; + pwoGetErrText waveOutGetErrorTextW; + pwoGetNumDevs waveOutGetNumDevs; + pwoMessage waveOutMessage; + + HMODULE m_hWinMM; + HMIXER m_hMixer = NULL; + DWORD m_dwMasterMuteControlID; + DWORD m_dwMasterVolumeID; + DWORD m_dwMasterChannels; + struct + { + DWORD dwMinimum; + DWORD dwMaximum; + } m_dwMasterRanges; + PDWORD m_pdwChannelSteps = NULL; +}; diff --git a/base/shell/explorer/taskswnd.cpp b/base/shell/explorer/taskswnd.cpp index 74efb5d85fc..381ba1c3f11 100644 --- a/base/shell/explorer/taskswnd.cpp +++ b/base/shell/explorer/taskswnd.cpp @@ -1,24 +1,13 @@ /* - * ReactOS Explorer - * - * Copyright 2006 - 2007 Thomas Weidenmueller - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * PROJECT: ReactOS Explorer + * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) + * PURPOSE: Task window implementation + * COPYRIGHT: Copyright 2006-2007 Thomas Weidenmueller + * Copyright 2026 Vitaly Orekhov */ #include "precomp.h" +#include "mediabtns.h" #include #include #include @@ -387,6 +376,7 @@ class CTaskSwitchWnd : CComPtr m_Tray; UINT m_ShellHookMsg; + UINT m_WinMMDevChgMsg; WORD m_TaskItemCount; WORD m_AllocatedTaskItems; @@ -410,10 +400,12 @@ class CTaskSwitchWnd : UINT m_uHardErrorMsg; CHardErrorThread m_HardErrorThread; + CMultimediaBackend m_Mixer; public: CTaskSwitchWnd() : m_ShellHookMsg(NULL), + m_WinMMDevChgMsg(NULL), m_TaskItemCount(0), m_AllocatedTaskItems(0), m_TaskGroups(NULL), @@ -1542,6 +1534,11 @@ public: #if DUMP_TASKS != 0 SetTimer(hwnd, 1, 5000, NULL); #endif + + /* WinMM is needed to change system volume via media buttons */ + m_WinMMDevChgMsg = RegisterWindowMessageW(L"winmm_devicechange"); + m_Mixer.Initialize(); + return TRUE; } @@ -1610,12 +1607,12 @@ public: return TRUE; switch (uAppCmd) { + // TODO: When MMDevAPI arrives, try IMMDeviceEnumerator::GetDefaultAudioEndpoint first and then fall back to WinMM. case APPCOMMAND_VOLUME_MUTE: + return m_Mixer.Mute(); case APPCOMMAND_VOLUME_DOWN: case APPCOMMAND_VOLUME_UP: - // TODO: Try IMMDeviceEnumerator::GetDefaultAudioEndpoint first and then fall back to mixer. - FIXME("Call the mixer API to change the global volume\n"); - return TRUE; + return m_Mixer.AdjustVolume(uAppCmd, hProcessHeap); case APPCOMMAND_BROWSER_SEARCH: return SHFindFiles(NULL, NULL); } @@ -1706,6 +1703,13 @@ public: return Ret; } + LRESULT OnWinMMDeviceChange(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) + { + TRACE("Audio device was added or removed, invalidating mixer\n"); + m_Mixer.Initialize(); + return TRUE; + } + VOID HandleTaskItemClick(IN OUT PTASK_ITEM TaskItem) { BOOL bIsMinimized; @@ -2279,6 +2283,7 @@ public: MESSAGE_HANDLER(WM_SETFONT, OnSetFont) MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChanged) MESSAGE_HANDLER(m_ShellHookMsg, OnShellHook) + MESSAGE_HANDLER(m_WinMMDevChgMsg, OnWinMMDeviceChange) MESSAGE_HANDLER(WM_MOUSEACTIVATE, OnMouseActivate) MESSAGE_HANDLER(WM_KLUDGEMINRECT, OnKludgeItemRect) MESSAGE_HANDLER(WM_COPYDATA, OnCopyData) diff --git a/base/shell/explorer/traywnd.cpp b/base/shell/explorer/traywnd.cpp index 409abaede7f..b5c6fe98980 100644 --- a/base/shell/explorer/traywnd.cpp +++ b/base/shell/explorer/traywnd.cpp @@ -351,6 +351,8 @@ class CTrayWindow : HDPA m_ShellServices; + UINT m_WinMMDevChgMsg; + public: CComPtr m_TrayBandSite; @@ -385,6 +387,7 @@ public: m_RunFileDlgOwner(NULL), m_AutoHideState(NULL), m_ShellServices(NULL), + m_WinMMDevChgMsg(NULL), Flags(0) { ZeroMemory(&m_TrayRects, sizeof(m_TrayRects)); @@ -2444,6 +2447,8 @@ ChangePos: } } + m_WinMMDevChgMsg = RegisterWindowMessageW(L"winmm_devicechange"); + return TRUE; } @@ -3465,6 +3470,7 @@ HandleTrayContextMenu: MESSAGE_HANDLER(WM_ACTIVATE, OnActivate) MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus) MESSAGE_HANDLER(WM_GETMINMAXINFO, OnGetMinMaxInfo) + MESSAGE_HANDLER(m_WinMMDevChgMsg, OnWinMMDeviceChange) MESSAGE_HANDLER(TWM_SETTINGSCHANGED, OnTaskbarSettingsChanged) MESSAGE_HANDLER(TWM_OPENSTARTMENU, OnOpenStartMenu) MESSAGE_HANDLER(TWM_DOEXITWINDOWS, OnDoExitWindows) @@ -3475,6 +3481,13 @@ HandleTrayContextMenu: /*****************************************************************************/ + LRESULT OnWinMMDeviceChange(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) + { + /* CTaskSwitchWnd does not receive this message directly */ + ::SendMessageW(m_TaskSwitch, uMsg, wParam, lParam); + return TRUE; + } + VOID TrayProcessMessages() { MSG Msg;