mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-24 04:00:02 +01:00
Refactor PowerDisplay IPC and add hotkey support
Refactored IPC initialization to handle window visibility based on launch mode (standalone or IPC). Added `IsWindowVisible` P/Invoke method and implemented IPC commands for window control, monitor refresh, and settings updates. Fixed bidirectional pipe creation and adjusted process startup order in `PowerDisplayProcessManager`. Made `ShowWindow` and `HideWindow` methods public and added `IsWindowVisible` to `MainWindow.xaml.cs`. Introduced activation hotkey parsing and configuration with a default of `Win+Alt+M`. Exposed hotkey to PowerToys runner and integrated it into the dashboard with localization and a launch button. Renamed module DLL for consistency.
This commit is contained in:
@@ -54,6 +54,10 @@ namespace PowerDisplay.Native
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static partial bool SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static partial bool IsWindowVisible(IntPtr hWnd);
|
||||
|
||||
// ==================== User32.dll - Window Creation and Messaging ====================
|
||||
[LibraryImport("user32.dll", EntryPoint = "CreateWindowExW", StringMarshalling = StringMarshalling.Utf16)]
|
||||
internal static partial IntPtr CreateWindowEx(
|
||||
|
||||
@@ -171,7 +171,9 @@ namespace PowerDisplay
|
||||
|
||||
// Initialize IPC in background (non-blocking)
|
||||
// Only connect pipes when launched from PowerToys (not standalone)
|
||||
if (!string.IsNullOrEmpty(_pipeUuid) && _powerToysRunnerPid != -1)
|
||||
bool isIPCMode = !string.IsNullOrEmpty(_pipeUuid) && _powerToysRunnerPid != -1;
|
||||
|
||||
if (isIPCMode)
|
||||
{
|
||||
// Async pipe connection in background - don't block UI thread
|
||||
_ = Task.Run(() => InitializeBidirectionalPipes(_pipeUuid));
|
||||
@@ -182,8 +184,23 @@ namespace PowerDisplay
|
||||
Logger.LogInfo("Running in standalone mode, IPC disabled");
|
||||
}
|
||||
|
||||
// Create main window but don't activate, window will auto-hide after initialization
|
||||
// Create main window
|
||||
_mainWindow = new MainWindow();
|
||||
|
||||
// FIX BUG #5: Window visibility depends on launch mode
|
||||
// - IPC mode (launched by PowerToys Runner): Start hidden, wait for show_window IPC command
|
||||
// - Standalone mode (no command-line args): Show window immediately
|
||||
if (!isIPCMode)
|
||||
{
|
||||
// Standalone mode - activate and show window
|
||||
_mainWindow.Activate();
|
||||
Logger.LogInfo("Window activated (standalone mode)");
|
||||
}
|
||||
else
|
||||
{
|
||||
// IPC mode - window remains inactive (hidden) until show_window command received
|
||||
Logger.LogInfo("Window created but not activated (IPC mode - waiting for show_window command)");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -251,7 +268,7 @@ namespace PowerDisplay
|
||||
/// <summary>
|
||||
/// Message receiver thread procedure
|
||||
/// </summary>
|
||||
private static void MessageReceiverThreadProc()
|
||||
private void MessageReceiverThreadProc()
|
||||
{
|
||||
Logger.LogInfo("Message receiver thread started");
|
||||
|
||||
@@ -299,7 +316,7 @@ namespace PowerDisplay
|
||||
/// <summary>
|
||||
/// Handle IPC messages received from ModuleInterface/Settings UI
|
||||
/// </summary>
|
||||
private static void OnIPCMessageReceived(string message)
|
||||
private void OnIPCMessageReceived(string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -317,31 +334,71 @@ namespace PowerDisplay
|
||||
case "show_window":
|
||||
Logger.LogInfo("Received show_window command");
|
||||
|
||||
// TODO: Implement window show logic
|
||||
// FIX BUG #3: Implement window show logic
|
||||
_mainWindow?.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (_mainWindow is MainWindow mainWindow)
|
||||
{
|
||||
mainWindow.ShowWindow();
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case "toggle_window":
|
||||
Logger.LogInfo("Received toggle_window command");
|
||||
|
||||
// TODO: Implement window toggle logic
|
||||
// FIX BUG #3: Implement window toggle logic
|
||||
_mainWindow?.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (_mainWindow is MainWindow mainWindow)
|
||||
{
|
||||
if (mainWindow.IsWindowVisible())
|
||||
{
|
||||
mainWindow.HideWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
mainWindow.ShowWindow();
|
||||
}
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case "refresh_monitors":
|
||||
Logger.LogInfo("Received refresh_monitors command");
|
||||
|
||||
// TODO: Implement monitor refresh logic
|
||||
// FIX BUG #3: Implement monitor refresh logic
|
||||
_mainWindow?.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (_mainWindow is MainWindow mainWindow && mainWindow.ViewModel != null)
|
||||
{
|
||||
mainWindow.ViewModel.RefreshCommand?.Execute(null);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case "settings_updated":
|
||||
Logger.LogInfo("Received settings_updated command");
|
||||
|
||||
// TODO: Implement settings update logic
|
||||
// FIX BUG #3: Implement settings update logic
|
||||
_mainWindow?.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (_mainWindow is MainWindow mainWindow && mainWindow.ViewModel != null)
|
||||
{
|
||||
// Reload settings from file
|
||||
_ = mainWindow.ViewModel.ReloadMonitorSettingsAsync();
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case "terminate":
|
||||
Logger.LogInfo("Received terminate command");
|
||||
|
||||
// TODO: Implement graceful shutdown
|
||||
// FIX BUG #3: Implement graceful shutdown
|
||||
_mainWindow?.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
Shutdown();
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -109,8 +109,11 @@ namespace PowerDisplay
|
||||
// Start async initialization (monitor scanning happens here)
|
||||
await InitializeAsync();
|
||||
|
||||
// Hide window after initialization completes
|
||||
HideWindow();
|
||||
// FIX BUG #4: Don't auto-hide window after initialization
|
||||
// Window visibility should be controlled by IPC commands (show_window/toggle_window)
|
||||
// If launched via PowerToys Runner, window should start hidden and wait for IPC show command
|
||||
// If launched standalone, window should stay visible
|
||||
// HideWindow(); // REMOVED - controlled by IPC or standalone mode
|
||||
}
|
||||
|
||||
private async Task InitializeAsync()
|
||||
@@ -223,7 +226,7 @@ namespace PowerDisplay
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowWindow()
|
||||
public void ShowWindow()
|
||||
{
|
||||
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
|
||||
|
||||
@@ -253,7 +256,7 @@ namespace PowerDisplay
|
||||
}
|
||||
}
|
||||
|
||||
private void HideWindow()
|
||||
public void HideWindow()
|
||||
{
|
||||
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
|
||||
|
||||
@@ -278,6 +281,16 @@ namespace PowerDisplay
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if window is currently visible
|
||||
/// </summary>
|
||||
/// <returns>True if window is visible, false otherwise</returns>
|
||||
public bool IsWindowVisible()
|
||||
{
|
||||
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
|
||||
return PInvoke.IsWindowVisible(hWnd);
|
||||
}
|
||||
|
||||
private async void OnUIRefreshRequested(object? sender, EventArgs e)
|
||||
{
|
||||
Logger.LogInfo("UI refresh requested due to settings change");
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\modules\PowerDisplay\</OutDir>
|
||||
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(Platform)\$(Configuration)\PowerDisplayModuleInterface\</IntDir>
|
||||
<TargetName>PowerToys.PowerDisplayModuleInterface</TargetName>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -103,9 +103,15 @@ bool PowerDisplayProcessManager::is_process_running() const
|
||||
|
||||
void PowerDisplayProcessManager::terminate_process()
|
||||
{
|
||||
// Close pipe
|
||||
// Close pipes
|
||||
m_write_pipe.reset();
|
||||
|
||||
if (m_read_pipe != nullptr)
|
||||
{
|
||||
CloseHandle(m_read_pipe);
|
||||
m_read_pipe = nullptr;
|
||||
}
|
||||
|
||||
// Terminate process
|
||||
if (m_hProcess != nullptr)
|
||||
{
|
||||
@@ -147,8 +153,12 @@ HRESULT PowerDisplayProcessManager::start_process(const std::wstring& pipe_uuid)
|
||||
HRESULT PowerDisplayProcessManager::start_command_pipe(const std::wstring& pipe_uuid)
|
||||
{
|
||||
const constexpr DWORD BUFSIZE = 4096 * 4;
|
||||
const constexpr DWORD client_timeout_millis = 5000;
|
||||
|
||||
// Create pipe for writing to PowerDisplay (OUT)
|
||||
// FIX BUG #2: Create BOTH pipes (bidirectional)
|
||||
// PowerDisplay.exe expects both IN and OUT pipes to exist
|
||||
|
||||
// Create OUT pipe: ModuleInterface writes, PowerDisplay reads
|
||||
m_pipe_name_out = std::format(L"\\\\.\\pipe\\powertoys_powerdisplay_{}_out", pipe_uuid);
|
||||
|
||||
HANDLE hWritePipe = CreateNamedPipe(
|
||||
@@ -164,52 +174,113 @@ HRESULT PowerDisplayProcessManager::start_command_pipe(const std::wstring& pipe_
|
||||
|
||||
if (hWritePipe == NULL || hWritePipe == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
Logger::error(L"Error creating write pipe for PowerDisplay");
|
||||
Logger::error(L"Error creating OUT pipe for PowerDisplay");
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// Create overlapped event for waiting for client to connect
|
||||
OVERLAPPED write_overlapped = { 0 };
|
||||
write_overlapped.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
|
||||
// Create IN pipe: PowerDisplay writes, ModuleInterface reads
|
||||
m_pipe_name_in = std::format(L"\\\\.\\pipe\\powertoys_powerdisplay_{}_in", pipe_uuid);
|
||||
|
||||
if (!write_overlapped.hEvent)
|
||||
m_read_pipe = CreateNamedPipe(
|
||||
m_pipe_name_in.c_str(),
|
||||
PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
|
||||
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
|
||||
1, // max instances
|
||||
0, // out buffer size (not used for inbound)
|
||||
BUFSIZE, // in buffer size
|
||||
0, // client timeout
|
||||
NULL // default security
|
||||
);
|
||||
|
||||
if (m_read_pipe == NULL || m_read_pipe == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
Logger::error(L"Error creating overlapped event for PowerDisplay pipe");
|
||||
Logger::error(L"Error creating IN pipe for PowerDisplay");
|
||||
CloseHandle(hWritePipe);
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// Connect write pipe
|
||||
// Create overlapped events for waiting for client to connect
|
||||
OVERLAPPED write_overlapped = { 0 };
|
||||
write_overlapped.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
|
||||
|
||||
OVERLAPPED read_overlapped = { 0 };
|
||||
read_overlapped.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
|
||||
|
||||
if (!write_overlapped.hEvent || !read_overlapped.hEvent)
|
||||
{
|
||||
Logger::error(L"Error creating overlapped events for PowerDisplay pipes");
|
||||
if (write_overlapped.hEvent) CloseHandle(write_overlapped.hEvent);
|
||||
if (read_overlapped.hEvent) CloseHandle(read_overlapped.hEvent);
|
||||
CloseHandle(hWritePipe);
|
||||
CloseHandle(m_read_pipe);
|
||||
m_read_pipe = nullptr;
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// Connect both pipes
|
||||
bool write_pipe_pending = false;
|
||||
bool read_pipe_pending = false;
|
||||
|
||||
if (!ConnectNamedPipe(hWritePipe, &write_overlapped))
|
||||
{
|
||||
const auto lastError = GetLastError();
|
||||
if (lastError != ERROR_IO_PENDING && lastError != ERROR_PIPE_CONNECTED)
|
||||
if (lastError == ERROR_IO_PENDING)
|
||||
{
|
||||
Logger::error(L"Error connecting to write pipe");
|
||||
write_pipe_pending = true;
|
||||
}
|
||||
else if (lastError != ERROR_PIPE_CONNECTED)
|
||||
{
|
||||
Logger::error(L"Error connecting OUT pipe");
|
||||
CloseHandle(write_overlapped.hEvent);
|
||||
CloseHandle(read_overlapped.hEvent);
|
||||
CloseHandle(hWritePipe);
|
||||
CloseHandle(m_read_pipe);
|
||||
m_read_pipe = nullptr;
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for pipe to connect (with timeout)
|
||||
const constexpr DWORD client_timeout_millis = 5000;
|
||||
DWORD wait_result = WaitForSingleObject(write_overlapped.hEvent, client_timeout_millis);
|
||||
if (!ConnectNamedPipe(m_read_pipe, &read_overlapped))
|
||||
{
|
||||
const auto lastError = GetLastError();
|
||||
if (lastError == ERROR_IO_PENDING)
|
||||
{
|
||||
read_pipe_pending = true;
|
||||
}
|
||||
else if (lastError != ERROR_PIPE_CONNECTED)
|
||||
{
|
||||
Logger::error(L"Error connecting IN pipe");
|
||||
CloseHandle(write_overlapped.hEvent);
|
||||
CloseHandle(read_overlapped.hEvent);
|
||||
CloseHandle(hWritePipe);
|
||||
CloseHandle(m_read_pipe);
|
||||
m_read_pipe = nullptr;
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for both pipes to connect (with timeout)
|
||||
HANDLE wait_handles[2] = { write_overlapped.hEvent, read_overlapped.hEvent };
|
||||
DWORD wait_result = WaitForMultipleObjects(2, wait_handles, TRUE, client_timeout_millis);
|
||||
|
||||
CloseHandle(write_overlapped.hEvent);
|
||||
CloseHandle(read_overlapped.hEvent);
|
||||
|
||||
if (wait_result == WAIT_OBJECT_0)
|
||||
{
|
||||
// Pipe connected successfully
|
||||
// Both pipes connected successfully
|
||||
m_write_pipe = std::make_unique<CAtlFile>(hWritePipe);
|
||||
CloseHandle(write_overlapped.hEvent);
|
||||
|
||||
Logger::trace(L"PowerDisplay command pipe connected successfully");
|
||||
Logger::trace(L"PowerDisplay bidirectional pipes connected successfully (OUT: {}, IN: {})",
|
||||
m_pipe_name_out, m_pipe_name_in);
|
||||
return S_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"Timeout waiting for PowerDisplay to connect to command pipe");
|
||||
CloseHandle(write_overlapped.hEvent);
|
||||
Logger::error(L"Timeout waiting for PowerDisplay to connect to pipes");
|
||||
CloseHandle(hWritePipe);
|
||||
CloseHandle(m_read_pipe);
|
||||
m_read_pipe = nullptr;
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
@@ -234,15 +305,18 @@ void PowerDisplayProcessManager::refresh()
|
||||
return;
|
||||
}
|
||||
|
||||
if (start_command_pipe(pipe_uuid.value()) != S_OK)
|
||||
// FIX BUG #1: Start process FIRST, then create pipes
|
||||
// This ensures PowerDisplay.exe is running when pipes try to connect
|
||||
if (start_process(pipe_uuid.value()) != S_OK)
|
||||
{
|
||||
Logger::error(L"Failed to initialize command pipe");
|
||||
Logger::error(L"Failed to start PowerDisplay process");
|
||||
return;
|
||||
}
|
||||
|
||||
if (start_process(pipe_uuid.value()) != S_OK)
|
||||
// Now create pipes and wait for PowerDisplay to connect
|
||||
if (start_command_pipe(pipe_uuid.value()) != S_OK)
|
||||
{
|
||||
Logger::error(L"Failed to start PowerDisplay process, cleaning up pipes");
|
||||
Logger::error(L"Failed to initialize command pipes, terminating process");
|
||||
terminate_process();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,14 @@ class PowerDisplayProcessManager
|
||||
{
|
||||
private:
|
||||
HANDLE m_hProcess = nullptr;
|
||||
std::unique_ptr<CAtlFile> m_write_pipe; // Write to PowerDisplay (OUT)
|
||||
std::unique_ptr<CAtlFile> m_write_pipe; // Write to PowerDisplay (OUT pipe)
|
||||
HANDLE m_read_pipe = nullptr; // Read from PowerDisplay (IN pipe) - for bidirectional support
|
||||
OnThreadExecutor m_thread_executor;
|
||||
bool m_enabled = false;
|
||||
|
||||
// Pipe name for this session
|
||||
// Pipe names for this session
|
||||
std::wstring m_pipe_name_out;
|
||||
std::wstring m_pipe_name_in;
|
||||
|
||||
public:
|
||||
PowerDisplayProcessManager() = default;
|
||||
|
||||
@@ -40,6 +40,12 @@ namespace
|
||||
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
|
||||
const wchar_t JSON_KEY_ENABLED[] = L"enabled";
|
||||
const wchar_t JSON_KEY_HOTKEY_ENABLED[] = L"hotkey_enabled";
|
||||
const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"ActivationShortcut";
|
||||
const wchar_t JSON_KEY_WIN[] = L"win";
|
||||
const wchar_t JSON_KEY_ALT[] = L"alt";
|
||||
const wchar_t JSON_KEY_CTRL[] = L"ctrl";
|
||||
const wchar_t JSON_KEY_SHIFT[] = L"shift";
|
||||
const wchar_t JSON_KEY_CODE[] = L"code";
|
||||
}
|
||||
|
||||
class PowerDisplayModule : public PowertoyModuleIface
|
||||
@@ -47,6 +53,7 @@ class PowerDisplayModule : public PowertoyModuleIface
|
||||
private:
|
||||
bool m_enabled = false;
|
||||
bool m_hotkey_enabled = false;
|
||||
Hotkey m_activation_hotkey = { .win = true, .ctrl = false, .shift = false, .alt = true, .key = 'M' };
|
||||
|
||||
// Process manager for handling PowerDisplay.exe lifecycle and IPC
|
||||
PowerDisplayProcessManager m_process_manager;
|
||||
@@ -82,6 +89,41 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void parse_activation_hotkey(PowerToysSettings::PowerToyValues& settings)
|
||||
{
|
||||
auto settingsObject = settings.get_raw_json();
|
||||
if (settingsObject.GetView().Size())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (settingsObject.HasKey(JSON_KEY_PROPERTIES))
|
||||
{
|
||||
auto properties = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
|
||||
if (properties.HasKey(JSON_KEY_ACTIVATION_SHORTCUT))
|
||||
{
|
||||
auto jsonHotkeyObject = properties.GetNamedObject(JSON_KEY_ACTIVATION_SHORTCUT);
|
||||
m_activation_hotkey.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
|
||||
m_activation_hotkey.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
|
||||
m_activation_hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
|
||||
m_activation_hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
|
||||
m_activation_hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
|
||||
Logger::trace(L"Parsed activation hotkey: Win={} Ctrl={} Alt={} Shift={} Key={}",
|
||||
m_activation_hotkey.win, m_activation_hotkey.ctrl, m_activation_hotkey.alt,
|
||||
m_activation_hotkey.shift, m_activation_hotkey.key);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::info("ActivationShortcut not found in settings, using default Win+Alt+M");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::error("Failed to parse PowerDisplay activation shortcut, using default Win+Alt+M");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init_settings()
|
||||
{
|
||||
try
|
||||
@@ -90,6 +132,7 @@ private:
|
||||
PowerToysSettings::PowerToyValues::load_from_settings_file(get_key());
|
||||
|
||||
parse_hotkey_settings(settings);
|
||||
parse_activation_hotkey(settings);
|
||||
}
|
||||
catch (std::exception&)
|
||||
{
|
||||
@@ -187,6 +230,7 @@ public:
|
||||
PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
|
||||
|
||||
parse_hotkey_settings(values);
|
||||
parse_activation_hotkey(values);
|
||||
values.save_to_settings_file();
|
||||
|
||||
// Notify PowerDisplay.exe that settings have been updated
|
||||
@@ -237,6 +281,19 @@ public:
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
|
||||
{
|
||||
if (m_activation_hotkey.key != 0)
|
||||
{
|
||||
if (hotkeys && buffer_size >= 1)
|
||||
{
|
||||
hotkeys[0] = m_activation_hotkey;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
|
||||
|
||||
@@ -178,7 +178,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
|
||||
L"PowerToys.CmdPalModuleInterface.dll",
|
||||
L"PowerToys.ZoomItModuleInterface.dll",
|
||||
L"PowerToys.LightSwitchModuleInterface.dll",
|
||||
L"PowerToys.PowerDisplayExt.dll",
|
||||
L"PowerToys.PowerDisplayModuleInterface.dll",
|
||||
};
|
||||
|
||||
for (auto moduleSubdir : knownModules)
|
||||
|
||||
@@ -4,13 +4,18 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Settings.UI.Library.Attributes;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
public class PowerDisplayProperties
|
||||
{
|
||||
[CmdConfigureIgnore]
|
||||
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x4D); // Win+Alt+M
|
||||
|
||||
public PowerDisplayProperties()
|
||||
{
|
||||
ActivationShortcut = DefaultActivationShortcut;
|
||||
LaunchAtStartup = false;
|
||||
BrightnessUpdateRate = "1s";
|
||||
Monitors = new List<MonitorInfo>();
|
||||
@@ -20,6 +25,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
// which is managed separately by PowerDisplay app
|
||||
}
|
||||
|
||||
public HotkeySettings ActivationShortcut { get; set; }
|
||||
|
||||
[JsonPropertyName("launch_at_startup")]
|
||||
public bool LaunchAtStartup { get; set; }
|
||||
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
public class PowerDisplaySettings : BasePTModuleSettings, ISettingsConfig
|
||||
public class PowerDisplaySettings : BasePTModuleSettings, ISettingsConfig, IHotkeyConfig
|
||||
{
|
||||
public const string ModuleName = "PowerDisplay";
|
||||
|
||||
@@ -28,5 +31,20 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
// This can be utilized in the future if the settings.json file is to be modified/deleted.
|
||||
public bool UpgradeSettingsConfiguration()
|
||||
=> false;
|
||||
|
||||
public ModuleType GetModuleType() => ModuleType.PowerDisplay;
|
||||
|
||||
public HotkeyAccessor[] GetAllHotkeyAccessors()
|
||||
{
|
||||
var hotkeyAccessors = new List<HotkeyAccessor>
|
||||
{
|
||||
new HotkeyAccessor(
|
||||
() => Properties.ActivationShortcut,
|
||||
value => Properties.ActivationShortcut = value ?? Properties.DefaultActivationShortcut,
|
||||
"Activation_Shortcut"),
|
||||
};
|
||||
|
||||
return hotkeyAccessors.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5503,6 +5503,10 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
|
||||
<data name="PowerDisplay_Configuration_GroupSettings.Header" xml:space="preserve">
|
||||
<value>Configuration</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ToggleWindow" xml:space="preserve">
|
||||
<value>Toggle Power Display</value>
|
||||
<comment>Dashboard: Label for the PowerDisplay activation hotkey</comment>
|
||||
</data>
|
||||
<data name="PowerDisplay_LaunchButtonControl.Header" xml:space="preserve">
|
||||
<value>Open Power Display</value>
|
||||
</data>
|
||||
|
||||
@@ -232,6 +232,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
ModuleType.MouseJump => GetModuleItemsMouseJump(),
|
||||
ModuleType.MousePointerCrosshairs => GetModuleItemsMousePointerCrosshairs(),
|
||||
ModuleType.Peek => GetModuleItemsPeek(),
|
||||
ModuleType.PowerDisplay => GetModuleItemsPowerDisplay(),
|
||||
ModuleType.PowerLauncher => GetModuleItemsPowerLauncher(),
|
||||
ModuleType.PowerAccent => GetModuleItemsPowerAccent(),
|
||||
ModuleType.Workspaces => GetModuleItemsWorkspaces(),
|
||||
@@ -510,6 +511,18 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
return new ObservableCollection<DashboardModuleItem>(list);
|
||||
}
|
||||
|
||||
private ObservableCollection<DashboardModuleItem> GetModuleItemsPowerDisplay()
|
||||
{
|
||||
ISettingsRepository<PowerDisplaySettings> moduleSettingsRepository = SettingsRepository<PowerDisplaySettings>.GetInstance(new SettingsUtils());
|
||||
var settings = moduleSettingsRepository.SettingsConfig;
|
||||
var list = new List<DashboardModuleItem>
|
||||
{
|
||||
new DashboardModuleShortcutItem() { Label = resourceLoader.GetString("PowerDisplay_ToggleWindow"), Shortcut = settings.Properties.ActivationShortcut.GetKeysList() },
|
||||
new DashboardModuleButtonItem() { ButtonTitle = resourceLoader.GetString("PowerDisplay_LaunchButtonControl/Header"), IsButtonDescriptionVisible = true, ButtonDescription = resourceLoader.GetString("PowerDisplay_LaunchButtonControl/Description"), ButtonGlyph = "ms-appx:///Assets/Settings/Icons/PowerDisplay.png", ButtonClickHandler = PowerDisplayLaunchClicked },
|
||||
};
|
||||
return new ObservableCollection<DashboardModuleItem>(list);
|
||||
}
|
||||
|
||||
internal void SWVersionButtonClicked()
|
||||
{
|
||||
NavigationService.Navigate(typeof(GeneralPage));
|
||||
@@ -547,6 +560,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
SendConfigMSG("{\"action\":{\"RegistryPreview\":{\"action_name\":\"" + actionName + "\", \"value\":\"\"}}}");
|
||||
}
|
||||
|
||||
private void PowerDisplayLaunchClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var actionName = "Launch";
|
||||
SendConfigMSG("{\"action\":{\"PowerDisplay\":{\"action_name\":\"" + actionName + "\", \"value\":\"\"}}}");
|
||||
}
|
||||
|
||||
internal void DashboardListItemClick(object sender)
|
||||
{
|
||||
if (sender is SettingsCard card && card.Tag is ModuleType moduleType)
|
||||
|
||||
Reference in New Issue
Block a user