[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
This commit is contained in:
Vitaly Orekhov
2026-09-02 23:15:44 +03:00
committed by GitHub
parent dbdf684561
commit aba47033dd
5 changed files with 456 additions and 20 deletions

View File

@@ -8,6 +8,7 @@ list(APPEND SOURCE
appbar.cpp
desktop.cpp
explorer.cpp
mediabtns.cpp
notifyiconscust.cpp
rshell.cpp
settings.cpp

View File

@@ -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 <vkvo2000@vivaldi.net>
*/
#include "precomp.h"
#include <math.h>
#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);
}

View File

@@ -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 <vkvo2000@vivaldi.net>
*/
#include <mmddk.h>
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;
};

View File

@@ -1,24 +1,13 @@
/*
* ReactOS Explorer
*
* Copyright 2006 - 2007 Thomas Weidenmueller <w3seek@reactos.org>
*
* 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 <w3seek@reactos.org>
* Copyright 2026 Vitaly Orekhov <vkvo2000@vivaldi.net>
*/
#include "precomp.h"
#include "mediabtns.h"
#include <commoncontrols.h>
#include <regstr.h>
#include <shlwapi_undoc.h>
@@ -387,6 +376,7 @@ class CTaskSwitchWnd :
CComPtr<ITrayWindow> 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)

View File

@@ -351,6 +351,8 @@ class CTrayWindow :
HDPA m_ShellServices;
UINT m_WinMMDevChgMsg;
public:
CComPtr<ITrayBandSite> 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;