dllmain.cpp
  1  #include "pch.h"
  2  
  3  #include <interface/powertoy_module_interface.h>
  4  
  5  #include <common/logger/logger.h>
  6  #include <common/utils/resources.h>
  7  #include <common/utils/winapi_error.h>
  8  
  9  #include <AlwaysOnTop/trace.h>
 10  #include <AlwaysOnTop/ModuleConstants.h>
 11  
 12  #include <shellapi.h>
 13  #include <common/SettingsAPI/settings_objects.h>
 14  #include <common/interop/shared_constants.h>
 15  
 16  namespace NonLocalizable
 17  {
 18      const wchar_t ModulePath[] = L"PowerToys.AlwaysOnTop.exe";
 19  }
 20  
 21  namespace
 22  {
 23      const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
 24      const wchar_t JSON_KEY_WIN[] = L"win";
 25      const wchar_t JSON_KEY_ALT[] = L"alt";
 26      const wchar_t JSON_KEY_CTRL[] = L"ctrl";
 27      const wchar_t JSON_KEY_SHIFT[] = L"shift";
 28      const wchar_t JSON_KEY_CODE[] = L"code";
 29      const wchar_t JSON_KEY_HOTKEY[] = L"hotkey";
 30      const wchar_t JSON_KEY_VALUE[] = L"value";
 31  }
 32  
 33  BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
 34  {
 35      switch (ul_reason_for_call)
 36      {
 37      case DLL_PROCESS_ATTACH:
 38          Trace::AlwaysOnTop::RegisterProvider();
 39          break;
 40  
 41      case DLL_THREAD_ATTACH:
 42      case DLL_THREAD_DETACH:
 43          break;
 44  
 45      case DLL_PROCESS_DETACH:
 46          Trace::AlwaysOnTop::UnregisterProvider();
 47          break;
 48      }
 49      return TRUE;
 50  }
 51  
 52  class AlwaysOnTopModuleInterface : public PowertoyModuleIface
 53  {
 54  public:
 55      // Return the localized display name of the powertoy
 56      virtual PCWSTR get_name() override
 57      {
 58          return app_name.c_str();
 59      }
 60  
 61      // Return the non localized key of the powertoy, this will be cached by the runner
 62      virtual const wchar_t* get_key() override
 63      {
 64          return app_key.c_str();
 65      }
 66  
 67      // Return the configured status for the gpo policy for the module
 68      virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
 69      {
 70          return powertoys_gpo::getConfiguredAlwaysOnTopEnabledValue();
 71      }
 72  
 73      // Return JSON with the configuration options.
 74      // These are the settings shown on the settings page along with their current values.
 75      virtual bool get_config(wchar_t* buffer, int* buffer_size) override
 76      {
 77          HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
 78  
 79          // Create a Settings object.
 80          PowerToysSettings::Settings settings(hinstance, get_name());
 81  
 82          return settings.serialize_to_buffer(buffer, buffer_size);
 83      }
 84  
 85      // Passes JSON with the configuration settings for the powertoy.
 86      // This is called when the user hits Save on the settings page.
 87      virtual void set_config(const wchar_t* config) override
 88      {
 89          try
 90          {
 91              // Parse the input JSON string.
 92              PowerToysSettings::PowerToyValues values =
 93                  PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
 94  
 95              parse_hotkey(values);
 96              // If you don't need to do any custom processing of the settings, proceed
 97              // to persists the values calling:
 98              values.save_to_settings_file();
 99              // Otherwise call a custom function to process the settings before saving them to disk:
100              // save_settings();
101          }
102          catch (std::exception&)
103          {
104              // Improper JSON.
105          }
106      }
107  
108      virtual bool on_hotkey(size_t hotkeyId) override
109      {
110          if (m_enabled)
111          {
112              Logger::trace(L"AlwaysOnTop hotkey pressed, id={}", hotkeyId);
113              if (!is_process_running())
114              {
115                  Enable();
116              }
117  
118              if (hotkeyId == 0)
119              {
120                  SetEvent(m_hPinEvent);
121              }
122              else if (hotkeyId == 1)
123              {
124                  SetEvent(m_hIncreaseOpacityEvent);
125              }
126              else if (hotkeyId == 2)
127              {
128                  SetEvent(m_hDecreaseOpacityEvent);
129              }
130  
131              return true;
132          }
133  
134          return false;
135      }
136  
137      virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
138      {
139          size_t count = 0;
140          
141          // Hotkey 0: Pin/Unpin (e.g., Win+Ctrl+T)
142          if (m_hotkey.key)
143          {
144              if (hotkeys && buffer_size > count)
145              {
146                  hotkeys[count] = m_hotkey;
147                  Logger::trace(L"AlwaysOnTop hotkey[0]: win={}, ctrl={}, shift={}, alt={}, key={}", 
148                      m_hotkey.win, m_hotkey.ctrl, m_hotkey.shift, m_hotkey.alt, m_hotkey.key);
149              }
150              count++;
151          }
152  
153          // Hotkey 1: Increase opacity (same modifiers + VK_OEM_PLUS '=')
154          if (m_hotkey.key)
155          {
156              if (hotkeys && buffer_size > count)
157              {
158                  hotkeys[count] = m_hotkey;
159                  hotkeys[count].key = VK_OEM_PLUS; // '=' key
160                  Logger::trace(L"AlwaysOnTop hotkey[1] (increase opacity): win={}, ctrl={}, shift={}, alt={}, key={}", 
161                      hotkeys[count].win, hotkeys[count].ctrl, hotkeys[count].shift, hotkeys[count].alt, hotkeys[count].key);
162              }
163              count++;
164          }
165  
166          // Hotkey 2: Decrease opacity (same modifiers + VK_OEM_MINUS '-')
167          if (m_hotkey.key)
168          {
169              if (hotkeys && buffer_size > count)
170              {
171                  hotkeys[count] = m_hotkey;
172                  hotkeys[count].key = VK_OEM_MINUS; // '-' key
173                  Logger::trace(L"AlwaysOnTop hotkey[2] (decrease opacity): win={}, ctrl={}, shift={}, alt={}, key={}", 
174                      hotkeys[count].win, hotkeys[count].ctrl, hotkeys[count].shift, hotkeys[count].alt, hotkeys[count].key);
175              }
176              count++;
177          }
178  
179          Logger::trace(L"AlwaysOnTop get_hotkeys returning count={}", count);
180          return count;
181      }
182  
183      // Enable the powertoy
184      virtual void enable()
185      {
186          Logger::info("AlwaysOnTop enabling");
187  
188          Enable();
189      }
190  
191      // Disable the powertoy
192      virtual void disable()
193      {
194          Logger::info("AlwaysOnTop disabling");
195  
196          Disable(true);
197      }
198  
199      // Returns if the powertoy is enabled
200      virtual bool is_enabled() override
201      {
202          return m_enabled;
203      }
204  
205      // Destroy the powertoy and free memory
206      virtual void destroy() override
207      {
208          Disable(false);
209          delete this;
210      }
211  
212      AlwaysOnTopModuleInterface()
213      {
214          app_name = L"AlwaysOnTop"; //TODO: localize
215          app_key = NonLocalizable::ModuleKey;
216          m_hPinEvent = CreateDefaultEvent(CommonSharedConstants::ALWAYS_ON_TOP_PIN_EVENT);
217          m_hTerminateEvent = CreateDefaultEvent(CommonSharedConstants::ALWAYS_ON_TOP_TERMINATE_EVENT);
218          m_hIncreaseOpacityEvent = CreateDefaultEvent(CommonSharedConstants::ALWAYS_ON_TOP_INCREASE_OPACITY_EVENT);
219          m_hDecreaseOpacityEvent = CreateDefaultEvent(CommonSharedConstants::ALWAYS_ON_TOP_DECREASE_OPACITY_EVENT);
220          init_settings();
221      }
222  
223  private:
224      void Enable()
225      {
226          m_enabled = true;
227  
228          // Log telemetry
229          Trace::AlwaysOnTop::Enable(true);
230  
231          unsigned long powertoys_pid = GetCurrentProcessId();
232          std::wstring executable_args = L"";
233          executable_args.append(std::to_wstring(powertoys_pid));
234          ResetEvent(m_hPinEvent);
235  
236          SHELLEXECUTEINFOW sei{ sizeof(sei) };
237          sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
238          sei.lpFile = NonLocalizable::ModulePath;
239          sei.nShow = SW_SHOWNORMAL;
240          sei.lpParameters = executable_args.data();
241          if (ShellExecuteExW(&sei) == false)
242          {
243              Logger::error(L"Failed to start AlwaysOnTop");
244              auto message = get_last_error_message(GetLastError());
245              if (message.has_value())
246              {
247                  Logger::error(message.value());
248              }
249          }
250          else
251          {
252              m_hProcess = sei.hProcess;
253          }
254      }
255  
256      void Disable(bool const traceEvent)
257      {
258          m_enabled = false;
259          ResetEvent(m_hPinEvent);
260  
261          // Log telemetry
262          if (traceEvent)
263          {
264              Trace::AlwaysOnTop::Enable(false);
265          }
266  
267          SetEvent(m_hTerminateEvent);
268  
269          // Wait for 1.5 seconds for the process to end correctly and stop etw tracer
270          WaitForSingleObject(m_hProcess, 1500);
271  
272          // If process is still running, terminate it
273          if (m_hProcess)
274          {
275              TerminateProcess(m_hProcess, 0);
276              m_hProcess = nullptr;
277          }
278      }
279  
280      void parse_hotkey(PowerToysSettings::PowerToyValues& settings)
281      {
282          auto settingsObject = settings.get_raw_json();
283          if (settingsObject.GetView().Size())
284          {
285              try
286              {
287                  auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_HOTKEY).GetNamedObject(JSON_KEY_VALUE);
288                  m_hotkey.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
289                  m_hotkey.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
290                  m_hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
291                  m_hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
292                  m_hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
293              }
294              catch (...)
295              {
296                  Logger::error("Failed to initialize AlwaysOnTop start shortcut");
297              }
298          }
299          else
300          {
301              Logger::info("AlwaysOnTop settings are empty");
302          }
303      }
304  
305      bool is_process_running()
306      {
307          return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT;
308      }
309  
310      void init_settings()
311      {
312          try
313          {
314              // Load and parse the settings file for this PowerToy.
315              PowerToysSettings::PowerToyValues settings =
316                  PowerToysSettings::PowerToyValues::load_from_settings_file(get_key());
317  
318              parse_hotkey(settings);
319          }
320          catch (std::exception&)
321          {
322              Logger::warn(L"An exception occurred while loading the settings file");
323              // Error while loading from the settings file. Let default values stay as they are.
324          }
325      }
326  
327      std::wstring app_name;
328      std::wstring app_key; //contains the non localized key of the powertoy
329  
330      bool m_enabled = false;
331      HANDLE m_hProcess = nullptr;
332      Hotkey m_hotkey;
333  
334      // Handle to event used to pin/unpin windows
335      HANDLE m_hPinEvent;
336      HANDLE m_hTerminateEvent;
337      HANDLE m_hIncreaseOpacityEvent;
338      HANDLE m_hDecreaseOpacityEvent;
339  };
340  
341  extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
342  {
343      return new AlwaysOnTopModuleInterface();
344  }