quick_access_host.cpp
1 #include "pch.h" 2 #include "quick_access_host.h" 3 4 #include <mutex> 5 #include <string> 6 #include <vector> 7 #include <rpc.h> 8 #include <new> 9 #include <memory> 10 11 #include <common/logger/logger.h> 12 #include <common/utils/process_path.h> 13 #include <common/interop/two_way_pipe_message_ipc.h> 14 #include <wil/resource.h> 15 16 extern void receive_json_send_to_main_thread(const std::wstring& msg); 17 18 namespace 19 { 20 wil::unique_handle quick_access_process; 21 wil::unique_handle quick_access_job; 22 wil::unique_handle show_event; 23 wil::unique_handle exit_event; 24 std::wstring show_event_name; 25 std::wstring exit_event_name; 26 std::wstring runner_pipe_name; 27 std::wstring app_pipe_name; 28 std::unique_ptr<TwoWayPipeMessageIPC> quick_access_ipc; 29 std::mutex quick_access_mutex; 30 31 bool is_process_active_locked() 32 { 33 if (!quick_access_process) 34 { 35 return false; 36 } 37 38 DWORD exit_code = 0; 39 if (!GetExitCodeProcess(quick_access_process.get(), &exit_code)) 40 { 41 Logger::warn(L"QuickAccessHost: failed to read Quick Access process exit code. error={}.", GetLastError()); 42 return false; 43 } 44 45 return exit_code == STILL_ACTIVE; 46 } 47 48 void reset_state_locked() 49 { 50 if (quick_access_ipc) 51 { 52 quick_access_ipc->end(); 53 quick_access_ipc.reset(); 54 } 55 56 quick_access_process.reset(); 57 quick_access_job.reset(); 58 show_event.reset(); 59 exit_event.reset(); 60 show_event_name.clear(); 61 exit_event_name.clear(); 62 runner_pipe_name.clear(); 63 app_pipe_name.clear(); 64 } 65 66 std::wstring build_event_name(const wchar_t* suffix) 67 { 68 std::wstring name = L"Local\\PowerToysQuickAccess_"; 69 name += std::to_wstring(GetCurrentProcessId()); 70 if (suffix) 71 { 72 name += suffix; 73 } 74 return name; 75 } 76 77 std::wstring build_command_line(const std::wstring& exe_path) 78 { 79 std::wstring command_line = L"\""; 80 command_line += exe_path; 81 command_line += L"\" --show-event=\""; 82 command_line += show_event_name; 83 command_line += L"\" --exit-event=\""; 84 command_line += exit_event_name; 85 command_line += L"\""; 86 if (!runner_pipe_name.empty()) 87 { 88 command_line.append(L" --runner-pipe=\""); 89 command_line += runner_pipe_name; 90 command_line += L"\""; 91 } 92 if (!app_pipe_name.empty()) 93 { 94 command_line.append(L" --app-pipe=\""); 95 command_line += app_pipe_name; 96 command_line += L"\""; 97 } 98 return command_line; 99 } 100 } 101 102 namespace QuickAccessHost 103 { 104 bool is_running() 105 { 106 std::scoped_lock lock(quick_access_mutex); 107 return is_process_active_locked(); 108 } 109 110 void start() 111 { 112 Logger::info(L"QuickAccessHost::start() called"); 113 std::scoped_lock lock(quick_access_mutex); 114 if (is_process_active_locked()) 115 { 116 Logger::info(L"QuickAccessHost::start: process already active"); 117 return; 118 } 119 120 reset_state_locked(); 121 122 show_event_name = build_event_name(L"_Show"); 123 exit_event_name = build_event_name(L"_Exit"); 124 125 show_event.reset(CreateEventW(nullptr, FALSE, FALSE, show_event_name.c_str())); 126 if (!show_event) 127 { 128 Logger::error(L"QuickAccessHost: failed to create show event. error={}.", GetLastError()); 129 reset_state_locked(); 130 return; 131 } 132 133 exit_event.reset(CreateEventW(nullptr, FALSE, FALSE, exit_event_name.c_str())); 134 if (!exit_event) 135 { 136 Logger::error(L"QuickAccessHost: failed to create exit event. error={}.", GetLastError()); 137 reset_state_locked(); 138 return; 139 } 140 141 runner_pipe_name = L"\\\\.\\pipe\\powertoys_quick_access_runner_"; 142 app_pipe_name = L"\\\\.\\pipe\\powertoys_quick_access_ui_"; 143 UUID temp_uuid; 144 wchar_t* uuid_chars = nullptr; 145 if (UuidCreate(&temp_uuid) == RPC_S_UUID_NO_ADDRESS) 146 { 147 Logger::warn(L"QuickAccessHost: failed to create UUID for pipe names. error={}.", GetLastError()); 148 } 149 else if (UuidToString(&temp_uuid, reinterpret_cast<RPC_WSTR*>(&uuid_chars)) != RPC_S_OK) 150 { 151 Logger::warn(L"QuickAccessHost: failed to convert UUID to string. error={}.", GetLastError()); 152 } 153 154 if (uuid_chars != nullptr) 155 { 156 runner_pipe_name += std::wstring(uuid_chars); 157 app_pipe_name += std::wstring(uuid_chars); 158 RpcStringFree(reinterpret_cast<RPC_WSTR*>(&uuid_chars)); 159 uuid_chars = nullptr; 160 } 161 else 162 { 163 const std::wstring fallback_suffix = std::to_wstring(GetTickCount64()); 164 runner_pipe_name += fallback_suffix; 165 app_pipe_name += fallback_suffix; 166 } 167 168 HANDLE token_handle = nullptr; 169 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token_handle)) 170 { 171 Logger::error(L"QuickAccessHost: failed to open process token. error={}.", GetLastError()); 172 reset_state_locked(); 173 return; 174 } 175 176 wil::unique_handle token(token_handle); 177 quick_access_ipc.reset(new (std::nothrow) TwoWayPipeMessageIPC(runner_pipe_name, app_pipe_name, receive_json_send_to_main_thread)); 178 if (!quick_access_ipc) 179 { 180 Logger::error(L"QuickAccessHost: failed to allocate IPC instance."); 181 reset_state_locked(); 182 return; 183 } 184 185 try 186 { 187 quick_access_ipc->start(token.get()); 188 } 189 catch (...) 190 { 191 Logger::error(L"QuickAccessHost: failed to start IPC server for Quick Access."); 192 reset_state_locked(); 193 return; 194 } 195 196 const std::wstring exe_path = get_module_folderpath() + L"\\WinUI3Apps\\PowerToys.QuickAccess.exe"; 197 if (GetFileAttributesW(exe_path.c_str()) == INVALID_FILE_ATTRIBUTES) 198 { 199 Logger::warn(L"QuickAccessHost: missing Quick Access executable at {}", exe_path); 200 reset_state_locked(); 201 return; 202 } 203 204 const std::wstring command_line = build_command_line(exe_path); 205 std::vector<wchar_t> command_line_buffer(command_line.begin(), command_line.end()); 206 command_line_buffer.push_back(L'\0'); 207 STARTUPINFOW startup_info{}; 208 startup_info.cb = sizeof(startup_info); 209 PROCESS_INFORMATION process_info{}; 210 211 BOOL created = CreateProcessW(exe_path.c_str(), command_line_buffer.data(), nullptr, nullptr, FALSE, CREATE_SUSPENDED, nullptr, nullptr, &startup_info, &process_info); 212 if (!created) 213 { 214 Logger::error(L"QuickAccessHost: failed to launch Quick Access host. error={}.", GetLastError()); 215 reset_state_locked(); 216 return; 217 } 218 219 quick_access_process.reset(process_info.hProcess); 220 221 // Assign to job object to ensure the process is killed if the runner exits unexpectedly (e.g. debugging stop) 222 quick_access_job.reset(CreateJobObjectW(nullptr, nullptr)); 223 if (quick_access_job) 224 { 225 JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 }; 226 jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; 227 if (!SetInformationJobObject(quick_access_job.get(), JobObjectExtendedLimitInformation, &jeli, sizeof(jeli))) 228 { 229 Logger::warn(L"QuickAccessHost: failed to set job object information. error={}", GetLastError()); 230 } 231 else 232 { 233 if (!AssignProcessToJobObject(quick_access_job.get(), quick_access_process.get())) 234 { 235 Logger::warn(L"QuickAccessHost: failed to assign process to job object. error={}", GetLastError()); 236 } 237 } 238 } 239 else 240 { 241 Logger::warn(L"QuickAccessHost: failed to create job object. error={}", GetLastError()); 242 } 243 244 ResumeThread(process_info.hThread); 245 CloseHandle(process_info.hThread); 246 } 247 248 void show() 249 { 250 start(); 251 std::scoped_lock lock(quick_access_mutex); 252 253 if (show_event) 254 { 255 if (!SetEvent(show_event.get())) 256 { 257 Logger::warn(L"QuickAccessHost: failed to signal show event. error={}.", GetLastError()); 258 } 259 } 260 } 261 262 void stop() 263 { 264 Logger::info(L"QuickAccessHost::stop() called"); 265 std::unique_lock lock(quick_access_mutex); 266 if (exit_event) 267 { 268 SetEvent(exit_event.get()); 269 } 270 271 if (quick_access_process) 272 { 273 const DWORD wait_result = WaitForSingleObject(quick_access_process.get(), 2000); 274 Logger::info(L"QuickAccessHost::stop: WaitForSingleObject result={}", wait_result); 275 if (wait_result == WAIT_TIMEOUT) 276 { 277 Logger::warn(L"QuickAccessHost: Quick Access process did not exit in time, terminating."); 278 if (!TerminateProcess(quick_access_process.get(), 0)) 279 { 280 Logger::error(L"QuickAccessHost: failed to terminate Quick Access process. error={}.", GetLastError()); 281 } 282 else 283 { 284 Logger::info(L"QuickAccessHost: TerminateProcess succeeded."); 285 WaitForSingleObject(quick_access_process.get(), 5000); 286 } 287 } 288 else if (wait_result == WAIT_FAILED) 289 { 290 Logger::error(L"QuickAccessHost: failed while waiting for Quick Access process. error={}.", GetLastError()); 291 } 292 } 293 294 reset_state_locked(); 295 } 296 }