mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-14 15:19:50 +01:00
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request 1. Fix quick access not working bug 2. Add custom value mapping 3. Fix some vcp slider visibility bug demo for custom vcp value name mapping: <img width="1399" height="744" alt="image" src="https://github.com/user-attachments/assets/517e4dbb-409a-4e43-b15a-d0d31e59ce49" /> <img width="1379" height="337" alt="image" src="https://github.com/user-attachments/assets/18f6f389-089c-4441-ad9f-5c45cac53814" /> <img width="521" height="1152" alt="image" src="https://github.com/user-attachments/assets/27b5f796-66fa-4781-b16f-4770bebf3504" /> <img width="295" height="808" alt="image" src="https://github.com/user-attachments/assets/54eaf5b9-5d54-4531-a40b-de3113122715" /> <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [ ] Closes: #xxx <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) --> - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Co-authored-by: Yu Leng <yuleng@microsoft.com>
286 lines
8.5 KiB
C++
286 lines
8.5 KiB
C++
// Copyright (c) Microsoft Corporation
|
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
#include "pch.h"
|
|
#include "PowerDisplayProcessManager.h"
|
|
|
|
#include <common/logger/logger.h>
|
|
#include <common/utils/winapi_error.h>
|
|
#include <common/interop/shared_constants.h>
|
|
#include <atlstr.h>
|
|
|
|
namespace
|
|
{
|
|
std::optional<std::wstring> get_pipe_name(const std::wstring& prefix)
|
|
{
|
|
UUID temp_uuid;
|
|
wchar_t* uuid_chars = nullptr;
|
|
if (UuidCreate(&temp_uuid) == RPC_S_UUID_NO_ADDRESS)
|
|
{
|
|
const auto val = get_last_error_message(GetLastError());
|
|
Logger::error(L"UuidCreate cannot create guid. {}", val.has_value() ? val.value() : L"");
|
|
return std::nullopt;
|
|
}
|
|
else if (UuidToString(&temp_uuid, reinterpret_cast<RPC_WSTR*>(&uuid_chars)) != RPC_S_OK)
|
|
{
|
|
const auto val = get_last_error_message(GetLastError());
|
|
Logger::error(L"UuidToString cannot convert to string. {}", val.has_value() ? val.value() : L"");
|
|
return std::nullopt;
|
|
}
|
|
|
|
const auto pipe_name = std::format(L"{}{}", prefix, std::wstring(uuid_chars));
|
|
RpcStringFree(reinterpret_cast<RPC_WSTR*>(&uuid_chars));
|
|
|
|
return pipe_name;
|
|
}
|
|
}
|
|
|
|
void PowerDisplayProcessManager::start()
|
|
{
|
|
m_enabled = true;
|
|
submit_task([this]() { refresh(); });
|
|
}
|
|
|
|
void PowerDisplayProcessManager::stop()
|
|
{
|
|
m_enabled = false;
|
|
submit_task([this]() { refresh(); });
|
|
}
|
|
|
|
void PowerDisplayProcessManager::send_message(const std::wstring& message_type, const std::wstring& message_arg)
|
|
{
|
|
submit_task([this, message_type, message_arg] {
|
|
// Ensure process is running before sending message
|
|
// If process is not running, enable and start it - this allows Quick Access launch
|
|
// to work even when the module was not previously enabled
|
|
if (!is_process_running())
|
|
{
|
|
m_enabled = true;
|
|
refresh();
|
|
}
|
|
send_named_pipe_message(message_type, message_arg);
|
|
});
|
|
}
|
|
|
|
void PowerDisplayProcessManager::bring_to_front()
|
|
{
|
|
submit_task([this] {
|
|
if (!is_process_running())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const auto enum_windows = [](HWND hwnd, LPARAM param) -> BOOL {
|
|
const auto process_handle = reinterpret_cast<HANDLE>(param);
|
|
DWORD window_process_id = 0;
|
|
|
|
GetWindowThreadProcessId(hwnd, &window_process_id);
|
|
if (GetProcessId(process_handle) == window_process_id)
|
|
{
|
|
SetForegroundWindow(hwnd);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
};
|
|
|
|
EnumWindows(enum_windows, reinterpret_cast<LPARAM>(m_hProcess));
|
|
});
|
|
}
|
|
|
|
bool PowerDisplayProcessManager::is_running() const
|
|
{
|
|
return is_process_running();
|
|
}
|
|
|
|
void PowerDisplayProcessManager::submit_task(std::function<void()> task)
|
|
{
|
|
m_thread_executor.submit(OnThreadExecutor::task_t{ task });
|
|
}
|
|
|
|
bool PowerDisplayProcessManager::is_process_running() const
|
|
{
|
|
return m_hProcess != 0 && WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT;
|
|
}
|
|
|
|
void PowerDisplayProcessManager::terminate_process()
|
|
{
|
|
if (m_hProcess != 0)
|
|
{
|
|
TerminateProcess(m_hProcess, 1);
|
|
CloseHandle(m_hProcess);
|
|
m_hProcess = 0;
|
|
}
|
|
}
|
|
|
|
HRESULT PowerDisplayProcessManager::start_process(const std::wstring& pipe_name)
|
|
{
|
|
const unsigned long powertoys_pid = GetCurrentProcessId();
|
|
|
|
// Pass both PID and pipe name as arguments
|
|
const auto executable_args = std::format(L"{} {}", std::to_wstring(powertoys_pid), pipe_name);
|
|
|
|
SHELLEXECUTEINFOW sei{ sizeof(sei) };
|
|
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
|
|
sei.lpFile = L"WinUI3Apps\\PowerToys.PowerDisplay.exe";
|
|
sei.nShow = SW_SHOWNORMAL;
|
|
sei.lpParameters = executable_args.data();
|
|
if (ShellExecuteExW(&sei))
|
|
{
|
|
Logger::trace("Successfully started PowerDisplay process");
|
|
terminate_process();
|
|
m_hProcess = sei.hProcess;
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
Logger::error(L"PowerDisplay process failed to start. {}", get_last_error_or_default(GetLastError()));
|
|
return E_FAIL;
|
|
}
|
|
}
|
|
|
|
HRESULT PowerDisplayProcessManager::start_named_pipe_server(const std::wstring& pipe_name)
|
|
{
|
|
m_write_pipe = nullptr;
|
|
|
|
const constexpr DWORD BUFSIZE = 4096 * 4;
|
|
|
|
const auto full_pipe_name = std::format(L"\\\\.\\pipe\\{}", pipe_name);
|
|
|
|
const auto hPipe = CreateNamedPipe(
|
|
full_pipe_name.c_str(), // pipe name
|
|
PIPE_ACCESS_OUTBOUND | // write access
|
|
FILE_FLAG_OVERLAPPED, // overlapped mode
|
|
PIPE_TYPE_MESSAGE | // message type pipe
|
|
PIPE_READMODE_MESSAGE | // message-read mode
|
|
PIPE_WAIT, // blocking mode
|
|
1, // max. instances
|
|
BUFSIZE, // output buffer size
|
|
0, // input buffer size
|
|
0, // client time-out
|
|
NULL); // default security attribute
|
|
|
|
if (hPipe == NULL || hPipe == INVALID_HANDLE_VALUE)
|
|
{
|
|
Logger::error(L"Error creating handle for named pipe");
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Create overlapped event to wait for client to connect to pipe.
|
|
OVERLAPPED overlapped = { 0 };
|
|
overlapped.hEvent = CreateEvent(nullptr, true, false, nullptr);
|
|
if (!overlapped.hEvent)
|
|
{
|
|
Logger::error(L"Error creating overlapped event for named pipe");
|
|
CloseHandle(hPipe);
|
|
return E_FAIL;
|
|
}
|
|
|
|
const auto clean_up_and_fail = [&]() {
|
|
CloseHandle(overlapped.hEvent);
|
|
CloseHandle(hPipe);
|
|
return E_FAIL;
|
|
};
|
|
|
|
if (!ConnectNamedPipe(hPipe, &overlapped))
|
|
{
|
|
const auto lastError = GetLastError();
|
|
|
|
if (lastError != ERROR_IO_PENDING && lastError != ERROR_PIPE_CONNECTED)
|
|
{
|
|
Logger::error(L"Error connecting to named pipe");
|
|
return clean_up_and_fail();
|
|
}
|
|
}
|
|
|
|
// Wait for client.
|
|
const constexpr DWORD client_timeout_millis = 5000;
|
|
switch (WaitForSingleObject(overlapped.hEvent, client_timeout_millis))
|
|
{
|
|
case WAIT_OBJECT_0:
|
|
{
|
|
DWORD bytes_transferred = 0;
|
|
if (GetOverlappedResult(hPipe, &overlapped, &bytes_transferred, FALSE))
|
|
{
|
|
CloseHandle(overlapped.hEvent);
|
|
m_write_pipe = std::make_unique<CAtlFile>(hPipe);
|
|
|
|
Logger::trace(L"PowerDisplay successfully connected to named pipe");
|
|
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
Logger::error(L"Error waiting for PowerDisplay to connect to named pipe");
|
|
return clean_up_and_fail();
|
|
}
|
|
}
|
|
|
|
case WAIT_TIMEOUT:
|
|
case WAIT_FAILED:
|
|
default:
|
|
Logger::error(L"Error waiting for PowerDisplay to connect to named pipe");
|
|
return clean_up_and_fail();
|
|
}
|
|
}
|
|
|
|
void PowerDisplayProcessManager::refresh()
|
|
{
|
|
if (m_enabled == is_process_running())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (m_enabled)
|
|
{
|
|
Logger::trace(L"Starting PowerDisplay process");
|
|
|
|
const auto pipe_name = get_pipe_name(L"powertoys_power_display_");
|
|
|
|
if (!pipe_name)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (start_process(pipe_name.value()) != S_OK)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (start_named_pipe_server(pipe_name.value()) != S_OK)
|
|
{
|
|
Logger::error(L"Named pipe initialization failed; terminating PowerDisplay process");
|
|
terminate_process();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Logger::trace(L"Exiting PowerDisplay process");
|
|
|
|
send_named_pipe_message(CommonSharedConstants::POWER_DISPLAY_TERMINATE_APP_MESSAGE);
|
|
WaitForSingleObject(m_hProcess, 5000);
|
|
|
|
if (is_process_running())
|
|
{
|
|
Logger::error(L"PowerDisplay process failed to gracefully exit; terminating");
|
|
}
|
|
else
|
|
{
|
|
Logger::trace(L"PowerDisplay process successfully exited");
|
|
}
|
|
|
|
terminate_process();
|
|
}
|
|
}
|
|
|
|
void PowerDisplayProcessManager::send_named_pipe_message(const std::wstring& message_type, const std::wstring& message_arg)
|
|
{
|
|
if (m_write_pipe)
|
|
{
|
|
const auto message = message_arg.empty() ? std::format(L"{}\r\n", message_type) : std::format(L"{} {}\r\n", message_type, message_arg);
|
|
|
|
const CString file_name(message.c_str());
|
|
m_write_pipe->Write(file_name, file_name.GetLength() * sizeof(TCHAR));
|
|
}
|
|
}
|