/ src / runner / quick_access_host.cpp
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  }