mirror of
https://github.com/reactos/reactos.git
synced 2026-09-26 12:13:10 +08:00
[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:
@@ -8,6 +8,7 @@ list(APPEND SOURCE
|
||||
appbar.cpp
|
||||
desktop.cpp
|
||||
explorer.cpp
|
||||
mediabtns.cpp
|
||||
notifyiconscust.cpp
|
||||
rshell.cpp
|
||||
settings.cpp
|
||||
|
||||
352
base/shell/explorer/mediabtns.cpp
Normal file
352
base/shell/explorer/mediabtns.cpp
Normal 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);
|
||||
}
|
||||
65
base/shell/explorer/mediabtns.h
Normal file
65
base/shell/explorer/mediabtns.h
Normal 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;
|
||||
};
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user