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 }