VCMute: refactor toolbar and settings

- settings are not reset upon closing
- can't select virtual cam as physical
- win+N works reliably
- add missed telemetry call for mic muting
This commit is contained in:
yuyoyuppe
2020-10-12 18:36:32 +03:00
parent a2b30209f4
commit 813fe15f52
8 changed files with 162 additions and 146 deletions

View File

@@ -141,9 +141,14 @@ public
auto names = gcnew List<String ^>();
VideoCaptureDeviceList vcdl;
vcdl.EnumerateDevices();
for (UINT32 i = 0; i < vcdl.Count(); ++i)
{
names->Add(gcnew String(vcdl.GetDeviceName(i).data()));
auto name = gcnew String(vcdl.GetDeviceName(i).data());
if (name != L"PowerToys VideoConference")
{
names->Add(name);
}
}
return names;
}

View File

@@ -17,7 +17,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
public SndVideoConferenceSettings(VideoConferenceSettings settings)
{
this.VideoConference = settings;
VideoConference = settings;
}
public string ToJsonString()

View File

@@ -20,19 +20,14 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
[JsonPropertyName("properties")]
public VideoConferenceConfigProperties Properties { get; set; }
string ISettingsConfig.GetModuleName()
public string GetModuleName()
{
throw new System.NotImplementedException();
}
string ISettingsConfig.ToJsonString()
{
throw new System.NotImplementedException();
return Name;
}
bool ISettingsConfig.UpgradeSettingsConfiguration()
{
throw new System.NotImplementedException();
return false;
}
}
}

View File

@@ -26,7 +26,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private GeneralSettings GeneralSettingsConfig { get; set; }
private const string ModuleName = "Video Conference";
private const string ProxyCameraName = "PowerToys VideoConference";
private Func<string, int> SendConfigMSG { get; }
@@ -80,11 +79,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_selectedMicrophoneIndex = MicrophoneNames.FindIndex(name => name == Settings.Properties.SelectedMicrophone.Value);
}
if (shouldSaveSettings)
{
_settingsUtils.SaveSettings(Settings.ToJsonString(), ModuleName);
}
_isEnabled = GeneralSettingsConfig.Enabled.VideoConference;
_cameraAndMicrophoneMuteHotkey = Settings.Properties.MuteCameraAndMicrophoneHotkey.Value;
_mirophoneMuteHotkey = Settings.Properties.MuteMicrophoneHotkey.Value;
@@ -127,6 +121,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_toolbarMonitorIndex = 1;
break;
}
if (shouldSaveSettings)
{
_settingsUtils.SaveSettings(Settings.ToJsonString(), ModuleName);
}
}
private bool _isEnabled = false;
@@ -259,7 +258,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
_cameraAndMicrophoneMuteHotkey = value;
Settings.Properties.MuteCameraAndMicrophoneHotkey.Value = value;
RaisePropertyChanged();
RaisePropertyChanged("CameraAndMicrophoneMuteHotkey");
}
}
}
@@ -277,7 +276,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
_mirophoneMuteHotkey = value;
Settings.Properties.MuteMicrophoneHotkey.Value = value;
RaisePropertyChanged();
RaisePropertyChanged("MicrophoneMuteHotkey");
}
}
}
@@ -295,7 +294,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
_cameraMuteHotkey = value;
Settings.Properties.MuteCameraHotkey.Value = value;
RaisePropertyChanged();
RaisePropertyChanged("CameraMuteHotkey");
}
}
}
@@ -316,34 +315,30 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
case 0:
Settings.Properties.ToolbarPosition.Value = "Top left corner";
RaisePropertyChanged();
break;
case 1:
Settings.Properties.ToolbarPosition.Value = "Top center";
RaisePropertyChanged();
break;
case 2:
Settings.Properties.ToolbarPosition.Value = "Top right corner";
RaisePropertyChanged();
break;
case 3:
Settings.Properties.ToolbarPosition.Value = "Bottom left corner";
RaisePropertyChanged();
break;
case 4:
Settings.Properties.ToolbarPosition.Value = "Bottom center";
RaisePropertyChanged();
break;
case 5:
Settings.Properties.ToolbarPosition.Value = "Bottom right corner";
RaisePropertyChanged();
break;
}
RaisePropertyChanged("ToolbarPostionIndex");
}
}
}
@@ -364,14 +359,14 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
case 0:
Settings.Properties.ToolbarMonitor.Value = "Main monitor";
RaisePropertyChanged();
break;
case 1:
Settings.Properties.ToolbarMonitor.Value = "All monitors";
RaisePropertyChanged();
break;
}
RaisePropertyChanged("ToolbarMonitorIndex");
}
}
}
@@ -389,7 +384,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
_hideToolbarWhenUnmuted = value;
Settings.Properties.HideToolbarWhenUnmuted.Value = value;
RaisePropertyChanged();
RaisePropertyChanged("HideToolbarWhenUnmuted");
}
}
}

View File

@@ -7,23 +7,7 @@
#include "VideoConferenceModule.h"
ToolbarImages Toolbar::darkImages;
ToolbarImages Toolbar::lightImages;
bool Toolbar::valueUpdated = false;
bool Toolbar::cameraMuted = false;
bool Toolbar::cameraInUse = false;
bool Toolbar::microphoneMuted = false;
std::wstring Toolbar::theme = L"system";
bool Toolbar::HideToolbarWhenUnmuted = true;
std::vector<HWND> Toolbar::hwnds;
UINT_PTR Toolbar::nTimerId;
unsigned __int64 Toolbar::lastTimeCamOrMicMuteStateChanged;
Toolbar* toolbar = nullptr;
const int REFRESH_RATE = 100;
const int OVERLAY_SHOW_TIME = 500;
@@ -31,6 +15,7 @@ const int BORDER_OFFSET = 12;
Toolbar::Toolbar()
{
toolbar = this;
darkImages.camOnMicOn = Gdiplus::Image::FromFile(L"modules/VideoConference/Icons/On-On Dark.png");
darkImages.camOffMicOn = Gdiplus::Image::FromFile(L"modules/VideoConference/Icons/On-Off Dark.png");
darkImages.camOnMicOff = Gdiplus::Image::FromFile(L"modules/VideoConference/Icons/Off-On Dark.png");
@@ -64,10 +49,9 @@ LRESULT Toolbar::WindowProcessMessages(HWND hwnd, UINT msg, WPARAM wparam, LPARA
else
{
VideoConferenceModule::reverseVirtualCameraMuteState();
setCameraMute(VideoConferenceModule::getVirtualCameraMuteState());
}
return DefWindowProc(hwnd, msg, wparam, lparam);
return DefWindowProcW(hwnd, msg, wparam, lparam);
}
case WM_CREATE:
case WM_PAINT:
@@ -79,96 +63,103 @@ LRESULT Toolbar::WindowProcessMessages(HWND hwnd, UINT msg, WPARAM wparam, LPARA
Gdiplus::Graphics graphic(hdc);
ToolbarImages* themeImages = &darkImages;
ToolbarImages* themeImages = &toolbar->darkImages;
if (theme == L"light" || (theme == L"system" && !WindowsColors::is_dark_mode()))
if (toolbar->theme == L"light" || (toolbar->theme == L"system" && !WindowsColors::is_dark_mode()))
{
themeImages = &lightImages;
themeImages = &toolbar->lightImages;
}
else
{
themeImages = &darkImages;
themeImages = &toolbar->darkImages;
}
if (!cameraInUse)
Gdiplus::Image* toolbarImage = nullptr;
if (!toolbar->cameraInUse)
{
if (microphoneMuted)
if (toolbar->microphoneMuted)
{
graphic.DrawImage(themeImages->camUnusedMicOff, 0, 0, themeImages->camUnusedMicOff->GetWidth(), themeImages->camUnusedMicOff->GetHeight());
toolbarImage = themeImages->camUnusedMicOff;
}
else
{
graphic.DrawImage(themeImages->camUnusedMicOn, 0, 0, themeImages->camUnusedMicOn->GetWidth(), themeImages->camUnusedMicOn->GetHeight());
toolbarImage = themeImages->camUnusedMicOn;
}
}
else if (microphoneMuted )
else if (toolbar->microphoneMuted)
{
if (cameraMuted)
if (toolbar->cameraMuted)
{
graphic.DrawImage(themeImages->camOffMicOff, 0, 0, themeImages->camOffMicOff->GetWidth(), themeImages->camOffMicOff->GetHeight());
toolbarImage = themeImages->camOffMicOff;
}
else
{
graphic.DrawImage(themeImages->camOnMicOff, 0, 0, themeImages->camOnMicOff->GetWidth(), themeImages->camOnMicOff->GetHeight());
toolbarImage = themeImages->camOnMicOff;
}
}
else
{
if (cameraMuted)
if (toolbar->cameraMuted)
{
graphic.DrawImage(themeImages->camOffMicOn, 0, 0, themeImages->camOffMicOn->GetWidth(), themeImages->camOffMicOn->GetHeight());
toolbarImage = themeImages->camOffMicOn;
}
else
{
graphic.DrawImage(themeImages->camOnMicOn, 0, 0, themeImages->camOnMicOn->GetWidth(), themeImages->camOnMicOn->GetHeight());
toolbarImage = themeImages->camOnMicOn;
}
}
graphic.DrawImage(toolbarImage, 0, 0, toolbarImage->GetWidth(), toolbarImage->GetHeight());
EndPaint(hwnd, &ps);
break;
}
case WM_TIMER:
{
cameraInUse = VideoConferenceModule::getVirtualCameraInUse();
toolbar->cameraInUse = VideoConferenceModule::getVirtualCameraInUse();
InvalidateRect(hwnd, NULL, NULL);
using namespace std::chrono;
if (cameraInUse || microphoneMuted || !HideToolbarWhenUnmuted)
using namespace std::chrono;
const auto nowMillis = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
const bool showOverlayTimeout = nowMillis - toolbar->lastTimeCamOrMicMuteStateChanged > OVERLAY_SHOW_TIME;
bool show = false;
if (toolbar->cameraInUse)
{
show = toolbar->HideToolbarWhenUnmuted ? toolbar->microphoneMuted || toolbar->cameraMuted : true;
}
else
{
show = toolbar->microphoneMuted;
}
show = show || !showOverlayTimeout;
if (show)
{
ShowWindow(hwnd, SW_SHOW);
}
else
{
if (duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count() - lastTimeCamOrMicMuteStateChanged > OVERLAY_SHOW_TIME || !valueUpdated)
{
ShowWindow(hwnd, SW_HIDE);
}
else
{
ShowWindow(hwnd, SW_SHOW);
}
ShowWindow(hwnd, SW_HIDE);
}
KillTimer(hwnd, nTimerId);
KillTimer(hwnd, toolbar->nTimerId);
break;
}
default:
return DefWindowProc(hwnd, msg, wparam, lparam);
return DefWindowProcW(hwnd, msg, wparam, lparam);
}
nTimerId = SetTimer(hwnd, 101, REFRESH_RATE, NULL);
toolbar->nTimerId = SetTimer(hwnd, 101, REFRESH_RATE, nullptr);
return DefWindowProc(hwnd, msg, wparam, lparam);
return DefWindowProcW(hwnd, msg, wparam, lparam);
}
void Toolbar::show(std::wstring position, std::wstring monitorString)
{
valueUpdated = false;
for (auto& hwnd : hwnds)
{
PostMessage(hwnd, WM_CLOSE, 0, 0);
PostMessageW(hwnd, WM_CLOSE, 0, 0);
}
hwnds.clear();
@@ -178,12 +169,12 @@ void Toolbar::show(std::wstring position, std::wstring monitorString)
// Register the window class
LPCWSTR CLASS_NAME = L"MuteNotificationWindowClass";
WNDCLASS wc{};
wc.hInstance = GetModuleHandle(NULL);
wc.hInstance = GetModuleHandleW(nullptr);
wc.lpszClassName = CLASS_NAME;
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpfnWndProc = WindowProcessMessages;
RegisterClass(&wc);
RegisterClassW(&wc);
// Create the window
DWORD dwExtStyle = 0;
@@ -237,7 +228,7 @@ void Toolbar::show(std::wstring position, std::wstring monitorString)
}
HWND hwnd;
hwnd = CreateWindowEx(
hwnd = CreateWindowExW(
WS_EX_TOOLWINDOW | WS_EX_LAYERED,
CLASS_NAME,
CLASS_NAME,
@@ -246,10 +237,10 @@ void Toolbar::show(std::wstring position, std::wstring monitorString)
positionY,
overlayWidth,
overlayHeight,
NULL,
NULL,
GetModuleHandle(NULL),
NULL);
nullptr,
nullptr,
GetModuleHandleW(nullptr),
nullptr);
auto transparrentColorKey = RGB(0, 0, 255);
HBRUSH brush = CreateSolidBrush(transparrentColorKey);
@@ -279,8 +270,10 @@ bool Toolbar::getCameraMute()
void Toolbar::setCameraMute(bool mute)
{
valueUpdated = true;
lastTimeCamOrMicMuteStateChanged = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
if (mute != cameraMuted)
{
lastTimeCamOrMicMuteStateChanged = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}
cameraMuted = mute;
}
@@ -293,7 +286,6 @@ void Toolbar::setMicrophoneMute(bool mute)
{
if (mute != microphoneMuted)
{
valueUpdated = true;
lastTimeCamOrMicMuteStateChanged = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}

View File

@@ -20,36 +20,35 @@ class Toolbar
public:
Toolbar();
static void show(std::wstring position, std::wstring monitorString);
static void hide();
void show(std::wstring position, std::wstring monitorString);
void hide();
bool static getCameraMute();
void static setCameraMute(bool mute);
bool static getMicrophoneMute();
void static setMicrophoneMute(bool mute);
bool getCameraMute();
void setCameraMute(bool mute);
bool getMicrophoneMute();
void setMicrophoneMute(bool mute);
void static setTheme(std::wstring theme);
void static setHideToolbarWhenUnmuted(bool hide);
void setTheme(std::wstring theme);
void setHideToolbarWhenUnmuted(bool hide);
private:
static LRESULT CALLBACK WindowProcessMessages(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
// Window callback can't be non-static so this members can't as well
static std::vector<HWND> hwnds;
std::vector<HWND> hwnds;
static ToolbarImages darkImages;
static ToolbarImages lightImages;
ToolbarImages darkImages;
ToolbarImages lightImages;
static bool valueUpdated;
static bool cameraMuted;
static bool cameraInUse;
static bool microphoneMuted;
bool cameraMuted = false;
bool cameraInUse = false;
bool microphoneMuted = false;
static std::wstring theme;
std::wstring theme = L"system";
static bool HideToolbarWhenUnmuted;
bool HideToolbarWhenUnmuted = true;
static unsigned __int64 lastTimeCamOrMicMuteStateChanged;
uint64_t lastTimeCamOrMicMuteStateChanged;
static UINT_PTR nTimerId;
UINT_PTR nTimerId;
};

View File

@@ -42,9 +42,21 @@ bool VideoConferenceModule::isHotkeyPressed(DWORD code, PowerToysSettings::Hotke
void VideoConferenceModule::reverseMicrophoneMute()
{
bool muted = false;
for (auto& controlledMic : instance->_controlledMicrophones)
{
const bool was_muted = controlledMic.muted();
controlledMic.toggle_muted();
muted = muted || !was_muted;
}
if (muted)
{
Trace::MicrophoneMuted();
toolbar.setMicrophoneMute(true);
}
else
{
toolbar.setMicrophoneMute(false);
}
}
@@ -70,6 +82,11 @@ void VideoConferenceModule::reverseVirtualCameraMuteState()
if (camera_muted)
{
Trace::CameraMuted();
toolbar.setCameraMute(true);
}
else
{
toolbar.setCameraMute(false);
}
}
@@ -112,11 +129,31 @@ LRESULT CALLBACK VideoConferenceModule::LowLevelKeyboardProc(int nCode, WPARAM w
if (isHotkeyPressed(kbd->vkCode, settings.cameraAndMicrophoneMuteHotkey))
{
reverseMicrophoneMute();
if (toolbar.getCameraMute() != toolbar.getMicrophoneMute())
const bool cameraInUse = getVirtualCameraInUse();
const bool microphoneIsMuted = getMicrophoneMuteState();
const bool cameraIsMuted = cameraInUse && getVirtualCameraMuteState();
if (cameraInUse)
{
reverseVirtualCameraMuteState();
toolbar.setCameraMute(getVirtualCameraMuteState());
// we're likely on a video call, so we must mute the unmuted cam/mic or reverse the mute state
// of everything, if cam and mic mute states are the same
if (microphoneIsMuted == cameraIsMuted)
{
reverseMicrophoneMute();
reverseVirtualCameraMuteState();
}
else if (cameraIsMuted)
{
reverseMicrophoneMute();
}
else if (microphoneIsMuted)
{
reverseVirtualCameraMuteState();
}
}
else
{
// if the camera is not in use, we just mute/unmute the mic
reverseMicrophoneMute();
}
}
else if (isHotkeyPressed(kbd->vkCode, settings.microphoneMuteHotkey))
@@ -126,7 +163,6 @@ LRESULT CALLBACK VideoConferenceModule::LowLevelKeyboardProc(int nCode, WPARAM w
else if (isHotkeyPressed(kbd->vkCode, settings.cameraMuteHotkey))
{
reverseVirtualCameraMuteState();
toolbar.setCameraMute(getVirtualCameraMuteState());
}
}
}
@@ -151,17 +187,7 @@ VideoConferenceModule::VideoConferenceModule()
inline VideoConferenceModule::~VideoConferenceModule()
{
if (toolbar.getCameraMute())
{
reverseVirtualCameraMuteState();
toolbar.setCameraMute(getVirtualCameraMuteState());
}
if (toolbar.getMicrophoneMute())
{
reverseMicrophoneMute();
}
unmuteAll();
toolbar.hide();
}
@@ -217,7 +243,7 @@ void VideoConferenceModule::set_config(const wchar_t* config)
}
if (const auto val = values.get_bool_value(L"hide_toolbar_when_unmuted"))
{
Toolbar::setHideToolbarWhenUnmuted(val.value());
toolbar.setHideToolbarWhenUnmuted(val.value());
}
if (const auto val = values.get_string_value(L"selected_mic"))
{
@@ -270,7 +296,7 @@ void VideoConferenceModule::init_settings()
}
if (const auto val = powerToysSettings.get_bool_value(L"hide_toolbar_when_unmuted"))
{
Toolbar::setHideToolbarWhenUnmuted(val.value());
toolbar.setHideToolbarWhenUnmuted(val.value());
}
if (const auto val = powerToysSettings.get_string_value(L"selected_mic"); *val != settings.selectedMicrophone)
{
@@ -291,7 +317,7 @@ void VideoConferenceModule::init_settings()
{
settings_theme = L"system";
}
Toolbar::setTheme(settings_theme);
toolbar.setTheme(settings_theme);
}
catch (...)
{
@@ -336,10 +362,10 @@ void VideoConferenceModule::updateControlledMicrophones(const std::wstring_view
}
if (_microphoneTrackedInUI)
{
_microphoneTrackedInUI->set_mute_changed_callback([](const bool muted) {
Toolbar::setMicrophoneMute(muted);
_microphoneTrackedInUI->set_mute_changed_callback([&](const bool muted) {
toolbar.setMicrophoneMute(muted);
});
Toolbar::setMicrophoneMute(_microphoneTrackedInUI->muted());
toolbar.setMicrophoneMute(_microphoneTrackedInUI->muted());
}
}
@@ -364,6 +390,19 @@ void VideoConferenceModule::enable()
}
}
void VideoConferenceModule::unmuteAll()
{
if (getVirtualCameraMuteState())
{
reverseVirtualCameraMuteState();
}
if (getMicrophoneMuteState())
{
reverseMicrophoneMute();
}
}
void VideoConferenceModule::disable()
{
if (_enabled)
@@ -377,17 +416,7 @@ void VideoConferenceModule::disable()
}
}
if (toolbar.getCameraMute()) // TODO: unmute
{
reverseVirtualCameraMuteState();
toolbar.setCameraMute(getVirtualCameraMuteState());
}
if (toolbar.getMicrophoneMute())
{
reverseMicrophoneMute();
}
unmuteAll();
toolbar.hide();
_enabled = false;
@@ -401,8 +430,8 @@ bool VideoConferenceModule::is_enabled()
void VideoConferenceModule::destroy()
{
delete this;
instance = nullptr;
delete this;
}
void VideoConferenceModule::sendSourceCameraNameUpdate()
@@ -426,10 +455,10 @@ void VideoConferenceModule::sendOverlayImageUpdate()
}
_imageOverlayChannel.reset();
TCHAR* powertoysDirectory = new TCHAR[255];
wchar_t powertoysDirectory[MAX_PATH + 1];
DWORD length = GetModuleFileName(NULL, powertoysDirectory, 255);
PathRemoveFileSpec(powertoysDirectory);
DWORD length = GetModuleFileNameW(nullptr, powertoysDirectory, MAX_PATH);
PathRemoveFileSpecW(powertoysDirectory);
std::wstring blankImagePath(powertoysDirectory);
blankImagePath += L"\\modules\\VideoConference\\black.bmp";

View File

@@ -54,6 +54,7 @@ public:
static bool getVirtualCameraInUse();
private:
void unmuteAll();
void init_settings();
void updateControlledMicrophones(const std::wstring_view new_mic);
// all callback methods and used by callback have to be static