windowpp 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/bin/windowpp.js +86 -0
  2. package/cmake/embed_assets.py +144 -0
  3. package/framework/CMakeLists.txt +176 -0
  4. package/framework/include/windowpp/windowpp.h +704 -0
  5. package/framework/src/AppData/API/AppData.ts +137 -0
  6. package/framework/src/AppData/appdata_bridge.h +138 -0
  7. package/framework/src/AppData/appdata_manager.cpp +126 -0
  8. package/framework/src/AppData/appdata_manager.h +3 -0
  9. package/framework/src/FileSystem/API/FileSystem.ts +389 -0
  10. package/framework/src/FileSystem/Linux/filesearch.cpp +148 -0
  11. package/framework/src/FileSystem/Linux/readfile.cpp +79 -0
  12. package/framework/src/FileSystem/Linux/savefile.cpp +333 -0
  13. package/framework/src/FileSystem/MacOS/filesearch.cpp +149 -0
  14. package/framework/src/FileSystem/MacOS/readfile.cpp +80 -0
  15. package/framework/src/FileSystem/MacOS/savefile.cpp +264 -0
  16. package/framework/src/FileSystem/Windows/filesearch.cpp +195 -0
  17. package/framework/src/FileSystem/Windows/readfile.cpp +122 -0
  18. package/framework/src/FileSystem/Windows/savefile.cpp +290 -0
  19. package/framework/src/FileSystem/file_index_service.cpp +262 -0
  20. package/framework/src/FileSystem/file_index_service.h +55 -0
  21. package/framework/src/FileSystem/filesystem_bridge.h +243 -0
  22. package/framework/src/FileSystem/filesystem_handler.h +93 -0
  23. package/framework/src/FileSystem/filesystem_json.h +241 -0
  24. package/framework/src/FileSystem/filesystem_search_service.cpp +414 -0
  25. package/framework/src/FileSystem/filesystem_search_service.h +94 -0
  26. package/framework/src/Input/API/Input.ts +161 -0
  27. package/framework/src/Input/Linux/linux_key_utils.h +135 -0
  28. package/framework/src/Input/MacOS/macos_key_utils.h +137 -0
  29. package/framework/src/Input/Windows/win32_key_utils.h +199 -0
  30. package/framework/src/Input/input_bridge.h +192 -0
  31. package/framework/src/Input/input_service.cpp +584 -0
  32. package/framework/src/Input/input_service.h +21 -0
  33. package/framework/src/application.cpp +29 -0
  34. package/framework/src/common/hit_test.cpp +40 -0
  35. package/framework/src/common/image_loader.cpp +24 -0
  36. package/framework/src/common/paths.cpp +75 -0
  37. package/framework/src/filedrop/filedrop.cpp +316 -0
  38. package/framework/src/filedrop/filedrop.css +421 -0
  39. package/framework/src/filedrop/filedrop.hpp +92 -0
  40. package/framework/src/filedrop/filedrop.ts +183 -0
  41. package/framework/src/platform/API/App.ts +156 -0
  42. package/framework/src/platform/API/Window.ts +249 -0
  43. package/framework/src/platform/linux/app_linux.cpp +256 -0
  44. package/framework/src/platform/linux/app_linux.h +64 -0
  45. package/framework/src/platform/linux/linux_helpers.cpp +26 -0
  46. package/framework/src/platform/linux/linux_helpers.h +19 -0
  47. package/framework/src/platform/linux/tray_linux.cpp +21 -0
  48. package/framework/src/platform/linux/tray_linux.h +26 -0
  49. package/framework/src/platform/linux/window_linux.cpp +256 -0
  50. package/framework/src/platform/linux/window_linux.h +70 -0
  51. package/framework/src/platform/macos/app_macos.h +59 -0
  52. package/framework/src/platform/macos/app_macos.mm +223 -0
  53. package/framework/src/platform/macos/macos_helpers.h +21 -0
  54. package/framework/src/platform/macos/tray_macos.h +22 -0
  55. package/framework/src/platform/macos/tray_macos.mm +53 -0
  56. package/framework/src/platform/macos/window_macos.h +74 -0
  57. package/framework/src/platform/macos/window_macos.mm +318 -0
  58. package/framework/src/platform/platform_bridge.h +514 -0
  59. package/framework/src/platform/platform_factory.cpp +33 -0
  60. package/framework/src/platform/platform_factory.h +19 -0
  61. package/framework/src/platform/win32/app_win32.cpp +572 -0
  62. package/framework/src/platform/win32/app_win32.h +83 -0
  63. package/framework/src/platform/win32/tray_win32.cpp +57 -0
  64. package/framework/src/platform/win32/tray_win32.h +30 -0
  65. package/framework/src/platform/win32/win32_helpers.h +61 -0
  66. package/framework/src/platform/win32/window_win32.cpp +267 -0
  67. package/framework/src/platform/win32/window_win32.h +79 -0
  68. package/framework/src/renderer/webgpu.h +128 -0
  69. package/framework/src/renderer/webview/include/WebView2.h +48014 -0
  70. package/framework/src/renderer/webview/include/WebView2EnvironmentOptions.h +342 -0
  71. package/framework/src/renderer/webview/webview.h +13 -0
  72. package/framework/src/renderer/webview/webview_linux.cpp +392 -0
  73. package/framework/src/renderer/webview/webview_macos.mm +388 -0
  74. package/framework/src/renderer/webview/webview_win32.cpp +688 -0
  75. package/framework/src/renderer/webview/x64/WebView2Loader.dll +0 -0
  76. package/framework/src/renderer/webview/x64/WebView2Loader.lib +0 -0
  77. package/framework/src/renderer/webview/x64/WebView2LoaderStatic.lib +0 -0
  78. package/lib/build.js +112 -0
  79. package/lib/create.js +283 -0
  80. package/lib/dev.js +155 -0
  81. package/package.json +24 -0
  82. package/scripts/publish.js +67 -0
  83. package/scripts/sync-framework.js +73 -0
  84. package/templates/solid/CMakeLists.txt +56 -0
  85. package/templates/solid/frontend/package.json +22 -0
  86. package/templates/solid/frontend/vite.config.ts +25 -0
  87. package/templates/solid/main.cpp +72 -0
  88. package/templates/solid/package.json +12 -0
@@ -0,0 +1,584 @@
1
+ #include "input_service.h"
2
+
3
+ #include <algorithm>
4
+ #include <cctype>
5
+ #include <map>
6
+ #include <mutex>
7
+ #include <set>
8
+ #include <stdexcept>
9
+ #include <unordered_map>
10
+ #include <utility>
11
+ #include <vector>
12
+
13
+ namespace wpp::input {
14
+ namespace {
15
+
16
+ using KeyListener = Service::KeyListener;
17
+ using ShortcutHandler = Service::ShortcutHandler;
18
+ using ListenerId = Service::ListenerId;
19
+
20
+ struct RegisteredShortcut {
21
+ Shortcut shortcut;
22
+ ShortcutOptions options;
23
+ ShortcutHandler handler;
24
+ };
25
+
26
+ struct State {
27
+ std::mutex mutex;
28
+ ListenerId next_listener_id = 1;
29
+ std::unordered_map<ListenerId, KeyListener> listeners;
30
+ std::unordered_map<std::string, RegisteredShortcut> shortcuts;
31
+ std::set<Key> pressed;
32
+ std::vector<Key> pressed_order;
33
+ detail::DispatchFn dispatch;
34
+ detail::GlobalRegisterFn register_global;
35
+ detail::GlobalUnregisterFn unregister_global;
36
+ };
37
+
38
+ State& state() {
39
+ static State instance;
40
+ return instance;
41
+ }
42
+
43
+ std::string trim(std::string value) {
44
+ auto is_space = [](unsigned char ch) { return std::isspace(ch) != 0; };
45
+ value.erase(value.begin(), std::find_if(value.begin(), value.end(), [&](unsigned char ch) { return !is_space(ch); }));
46
+ value.erase(std::find_if(value.rbegin(), value.rend(), [&](unsigned char ch) { return !is_space(ch); }).base(), value.end());
47
+ return value;
48
+ }
49
+
50
+ std::string uppercase(std::string value) {
51
+ std::transform(value.begin(), value.end(), value.begin(), [](unsigned char ch) {
52
+ return static_cast<char>(std::toupper(ch));
53
+ });
54
+ return value;
55
+ }
56
+
57
+ std::string canonical_token(std::string value) {
58
+ value = trim(std::move(value));
59
+ value.erase(std::remove_if(value.begin(), value.end(), [](unsigned char ch) {
60
+ return std::isspace(ch) != 0 || ch == '_';
61
+ }), value.end());
62
+ return uppercase(std::move(value));
63
+ }
64
+
65
+ bool is_modifier_token(const std::string& token) {
66
+ return token == "CTRL" || token == "CONTROL" || token == "SHIFT" ||
67
+ token == "ALT" || token == "OPTION" || token == "CMD" ||
68
+ token == "COMMAND" || token == "WIN" || token == "META" || token == "SUPER";
69
+ }
70
+
71
+ Key key_from_token(const std::string& token) {
72
+ static const std::unordered_map<std::string, Key> key_map = {
73
+ {"A", Key::A}, {"B", Key::B}, {"C", Key::C}, {"D", Key::D},
74
+ {"E", Key::E}, {"F", Key::F}, {"G", Key::G}, {"H", Key::H},
75
+ {"I", Key::I}, {"J", Key::J}, {"K", Key::K}, {"L", Key::L},
76
+ {"M", Key::M}, {"N", Key::N}, {"O", Key::O}, {"P", Key::P},
77
+ {"Q", Key::Q}, {"R", Key::R}, {"S", Key::S}, {"T", Key::T},
78
+ {"U", Key::U}, {"V", Key::V}, {"W", Key::W}, {"X", Key::X},
79
+ {"Y", Key::Y}, {"Z", Key::Z},
80
+ {"0", Key::Digit0}, {"1", Key::Digit1}, {"2", Key::Digit2}, {"3", Key::Digit3},
81
+ {"4", Key::Digit4}, {"5", Key::Digit5}, {"6", Key::Digit6}, {"7", Key::Digit7},
82
+ {"8", Key::Digit8}, {"9", Key::Digit9},
83
+ {"SPACE", Key::Space},
84
+ {"ENTER", Key::Enter}, {"RETURN", Key::Enter},
85
+ {"ESC", Key::Escape}, {"ESCAPE", Key::Escape},
86
+ {"TAB", Key::Tab},
87
+ {"BACKSPACE", Key::Backspace},
88
+ {"DELETE", Key::Delete}, {"DEL", Key::Delete},
89
+ {"INSERT", Key::Insert},
90
+ {"HOME", Key::Home},
91
+ {"END", Key::End},
92
+ {"PAGEUP", Key::PageUp},
93
+ {"PAGEDOWN", Key::PageDown},
94
+ {"LEFT", Key::Left},
95
+ {"RIGHT", Key::Right},
96
+ {"UP", Key::Up},
97
+ {"DOWN", Key::Down},
98
+ {"CAPS", Key::CapsLock}, {"CAPSLOCK", Key::CapsLock},
99
+ {"SHIFT", Key::Shift},
100
+ {"CTRL", Key::Control}, {"CONTROL", Key::Control},
101
+ {"ALT", Key::Alt}, {"OPTION", Key::Alt},
102
+ {"META", Key::Meta}, {"SUPER", Key::Meta}, {"WIN", Key::Meta}, {"COMMAND", Key::Meta}, {"CMD", Key::Meta},
103
+ {"F1", Key::F1}, {"F2", Key::F2}, {"F3", Key::F3}, {"F4", Key::F4},
104
+ {"F5", Key::F5}, {"F6", Key::F6}, {"F7", Key::F7}, {"F8", Key::F8},
105
+ {"F9", Key::F9}, {"F10", Key::F10}, {"F11", Key::F11}, {"F12", Key::F12},
106
+ };
107
+
108
+ auto it = key_map.find(token);
109
+ return it == key_map.end() ? Key::Unknown : it->second;
110
+ }
111
+
112
+ bool shortcut_matches_event(const Shortcut& shortcut, const KeyEvent& event) {
113
+ return shortcut.key == event.key &&
114
+ shortcut.ctrl == event.ctrl &&
115
+ shortcut.shift == event.shift &&
116
+ shortcut.alt == event.alt &&
117
+ shortcut.meta == event.meta;
118
+ }
119
+
120
+ bool pressed_for_modifier(const KeyEvent& event, Key modifier, bool current_flag) {
121
+ if (event.key == modifier) {
122
+ return event.type == KeyEventType::KeyDown;
123
+ }
124
+ return current_flag;
125
+ }
126
+
127
+ void reconcile_modifier_state(std::set<Key>& pressed, const KeyEvent& event) {
128
+ if (pressed_for_modifier(event, Key::Control, event.ctrl)) {
129
+ pressed.insert(Key::Control);
130
+ } else {
131
+ pressed.erase(Key::Control);
132
+ }
133
+
134
+ if (pressed_for_modifier(event, Key::Shift, event.shift)) {
135
+ pressed.insert(Key::Shift);
136
+ } else {
137
+ pressed.erase(Key::Shift);
138
+ }
139
+
140
+ if (pressed_for_modifier(event, Key::Alt, event.alt)) {
141
+ pressed.insert(Key::Alt);
142
+ } else {
143
+ pressed.erase(Key::Alt);
144
+ }
145
+
146
+ if (pressed_for_modifier(event, Key::Meta, event.meta)) {
147
+ pressed.insert(Key::Meta);
148
+ } else {
149
+ pressed.erase(Key::Meta);
150
+ }
151
+ }
152
+
153
+ void set_pressed_key(State& current, Key key, bool is_pressed) {
154
+ if (key == Key::Unknown) {
155
+ return;
156
+ }
157
+
158
+ if (is_pressed) {
159
+ const auto inserted = current.pressed.insert(key).second;
160
+ if (inserted) {
161
+ current.pressed_order.push_back(key);
162
+ }
163
+ return;
164
+ }
165
+
166
+ current.pressed.erase(key);
167
+ current.pressed_order.erase(
168
+ std::remove(current.pressed_order.begin(), current.pressed_order.end(), key),
169
+ current.pressed_order.end());
170
+ }
171
+
172
+ void reconcile_modifier_state(State& current, const KeyEvent& event) {
173
+ set_pressed_key(current, Key::Control, pressed_for_modifier(event, Key::Control, event.ctrl));
174
+ set_pressed_key(current, Key::Shift, pressed_for_modifier(event, Key::Shift, event.shift));
175
+ set_pressed_key(current, Key::Alt, pressed_for_modifier(event, Key::Alt, event.alt));
176
+ set_pressed_key(current, Key::Meta, pressed_for_modifier(event, Key::Meta, event.meta));
177
+ }
178
+
179
+ KeyEvent make_shortcut_event(const RegisteredShortcut& registration) {
180
+ KeyEvent event{};
181
+ event.key = registration.shortcut.key;
182
+ event.type = registration.options.trigger_on_key_up ? KeyEventType::KeyUp : KeyEventType::KeyDown;
183
+ event.key_name = key_to_string(registration.shortcut.key);
184
+ event.ctrl = registration.shortcut.ctrl;
185
+ event.shift = registration.shortcut.shift;
186
+ event.alt = registration.shortcut.alt;
187
+ event.meta = registration.shortcut.meta;
188
+ event.is_repeat = false;
189
+ event.is_global = registration.options.global;
190
+ event.window_id = registration.options.window_id;
191
+ return event;
192
+ }
193
+
194
+ std::vector<KeyListener> collect_listeners_locked(const State& current) {
195
+ std::vector<KeyListener> listeners;
196
+ listeners.reserve(current.listeners.size());
197
+ for (const auto& entry : current.listeners) {
198
+ listeners.push_back(entry.second);
199
+ }
200
+ return listeners;
201
+ }
202
+
203
+ std::vector<ShortcutHandler> collect_matching_handlers_locked(const State& current, const KeyEvent& event) {
204
+ std::vector<ShortcutHandler> handlers;
205
+ for (const auto& entry : current.shortcuts) {
206
+ const auto& registration = entry.second;
207
+ if (registration.options.global) {
208
+ continue;
209
+ }
210
+ if (!registration.options.window_id.empty() && registration.options.window_id != event.window_id) {
211
+ continue;
212
+ }
213
+ if (registration.options.trigger_on_key_up) {
214
+ if (event.type != KeyEventType::KeyUp) {
215
+ continue;
216
+ }
217
+ } else {
218
+ if (event.type != KeyEventType::KeyDown) {
219
+ continue;
220
+ }
221
+ if (event.is_repeat && !registration.options.allow_repeat) {
222
+ continue;
223
+ }
224
+ }
225
+ if (!shortcut_matches_event(registration.shortcut, event)) {
226
+ continue;
227
+ }
228
+ handlers.push_back(registration.handler);
229
+ }
230
+ return handlers;
231
+ }
232
+
233
+ void invoke_listeners(const std::vector<KeyListener>& listeners, const KeyEvent& event) {
234
+ for (const auto& listener : listeners) {
235
+ if (listener) {
236
+ listener(event);
237
+ }
238
+ }
239
+ }
240
+
241
+ void invoke_handlers(const std::vector<ShortcutHandler>& handlers, const KeyEvent& event) {
242
+ for (const auto& handler : handlers) {
243
+ if (handler) {
244
+ handler(event);
245
+ }
246
+ }
247
+ }
248
+
249
+ } // namespace
250
+
251
+ Service& Service::instance() {
252
+ static Service instance;
253
+ return instance;
254
+ }
255
+
256
+ Service& service() {
257
+ return Service::instance();
258
+ }
259
+
260
+ Service::ListenerId Service::add_key_listener(KeyListener listener) {
261
+ auto& current = state();
262
+ std::lock_guard<std::mutex> lock(current.mutex);
263
+ const auto id = current.next_listener_id++;
264
+ current.listeners.emplace(id, std::move(listener));
265
+ return id;
266
+ }
267
+
268
+ void Service::remove_key_listener(ListenerId id) {
269
+ auto& current = state();
270
+ std::lock_guard<std::mutex> lock(current.mutex);
271
+ current.listeners.erase(id);
272
+ }
273
+
274
+ Shortcut Service::parse_shortcut(const std::string& text) const {
275
+ const auto trimmed = trim(text);
276
+ if (trimmed.empty()) {
277
+ throw std::invalid_argument("Shortcut cannot be empty");
278
+ }
279
+
280
+ Shortcut shortcut;
281
+ shortcut.text = trimmed;
282
+
283
+ size_t start = 0;
284
+ std::vector<std::string> parts;
285
+ while (start <= trimmed.size()) {
286
+ const auto end = trimmed.find('+', start);
287
+ parts.push_back(trim(trimmed.substr(start, end == std::string::npos ? std::string::npos : end - start)));
288
+ if (end == std::string::npos) {
289
+ break;
290
+ }
291
+ start = end + 1;
292
+ }
293
+
294
+ if (parts.empty()) {
295
+ throw std::invalid_argument("Shortcut cannot be empty");
296
+ }
297
+
298
+ for (size_t index = 0; index < parts.size(); ++index) {
299
+ const auto token = canonical_token(parts[index]);
300
+ if (token.empty()) {
301
+ continue;
302
+ }
303
+
304
+ const bool last = index == parts.size() - 1;
305
+ if (!last && is_modifier_token(token)) {
306
+ if (token == "CTRL" || token == "CONTROL") shortcut.ctrl = true;
307
+ else if (token == "SHIFT") shortcut.shift = true;
308
+ else if (token == "ALT" || token == "OPTION") shortcut.alt = true;
309
+ else shortcut.meta = true;
310
+ continue;
311
+ }
312
+
313
+ const auto key = key_from_token(token);
314
+ if (key == Key::Unknown) {
315
+ throw std::invalid_argument("Unknown key in shortcut: " + parts[index]);
316
+ }
317
+
318
+ if (!last && is_modifier_token(token)) {
319
+ if (key == Key::Control) shortcut.ctrl = true;
320
+ else if (key == Key::Shift) shortcut.shift = true;
321
+ else if (key == Key::Alt) shortcut.alt = true;
322
+ else if (key == Key::Meta) shortcut.meta = true;
323
+ continue;
324
+ }
325
+
326
+ shortcut.key = key;
327
+ }
328
+
329
+ if (shortcut.key == Key::Unknown) {
330
+ throw std::invalid_argument("Shortcut is missing a key");
331
+ }
332
+
333
+ shortcut.text = format_shortcut(shortcut);
334
+ return shortcut;
335
+ }
336
+
337
+ std::string Service::format_shortcut(const Shortcut& shortcut) const {
338
+ std::vector<std::string> parts;
339
+ if (shortcut.ctrl) parts.push_back("Ctrl");
340
+ if (shortcut.shift) parts.push_back("Shift");
341
+ if (shortcut.alt) parts.push_back("Alt");
342
+ if (shortcut.meta) parts.push_back("Meta");
343
+ parts.push_back(key_to_string(shortcut.key));
344
+
345
+ std::string result;
346
+ for (size_t index = 0; index < parts.size(); ++index) {
347
+ if (index > 0) {
348
+ result += '+';
349
+ }
350
+ result += parts[index];
351
+ }
352
+ return result;
353
+ }
354
+
355
+ void Service::register_shortcut(const std::string& id,
356
+ const std::string& shortcut_text,
357
+ ShortcutHandler handler,
358
+ ShortcutOptions options) {
359
+ if (id.empty()) {
360
+ throw std::invalid_argument("Shortcut id cannot be empty");
361
+ }
362
+
363
+ auto parsed = parse_shortcut(shortcut_text);
364
+ auto& current = state();
365
+
366
+ std::lock_guard<std::mutex> lock(current.mutex);
367
+ auto existing = current.shortcuts.find(id);
368
+ if (existing != current.shortcuts.end() && existing->second.options.global && current.unregister_global) {
369
+ current.unregister_global(id);
370
+ }
371
+
372
+ RegisteredShortcut registration{parsed, std::move(options), std::move(handler)};
373
+ if (registration.options.global) {
374
+ if (!current.register_global) {
375
+ throw std::runtime_error("Global hotkeys are not available on this platform yet");
376
+ }
377
+ std::string error;
378
+ if (!current.register_global(id, registration.shortcut, &error)) {
379
+ throw std::runtime_error(error.empty() ? "Failed to register global hotkey" : error);
380
+ }
381
+ }
382
+
383
+ current.shortcuts[id] = std::move(registration);
384
+ }
385
+
386
+ void Service::unregister_shortcut(const std::string& id) {
387
+ auto& current = state();
388
+ std::lock_guard<std::mutex> lock(current.mutex);
389
+ auto it = current.shortcuts.find(id);
390
+ if (it == current.shortcuts.end()) {
391
+ return;
392
+ }
393
+ if (it->second.options.global && current.unregister_global) {
394
+ current.unregister_global(id);
395
+ }
396
+ current.shortcuts.erase(it);
397
+ }
398
+
399
+ void Service::clear_shortcuts() {
400
+ auto& current = state();
401
+ std::lock_guard<std::mutex> lock(current.mutex);
402
+ if (current.unregister_global) {
403
+ for (const auto& entry : current.shortcuts) {
404
+ if (entry.second.options.global) {
405
+ current.unregister_global(entry.first);
406
+ }
407
+ }
408
+ }
409
+ current.shortcuts.clear();
410
+ current.pressed.clear();
411
+ current.pressed_order.clear();
412
+ }
413
+
414
+ bool Service::is_pressed(Key key) const {
415
+ auto& current = state();
416
+ std::lock_guard<std::mutex> lock(current.mutex);
417
+ return current.pressed.find(key) != current.pressed.end();
418
+ }
419
+
420
+ std::vector<Key> Service::pressed_keys() const {
421
+ auto& current = state();
422
+ std::lock_guard<std::mutex> lock(current.mutex);
423
+ return current.pressed_order;
424
+ }
425
+
426
+ std::string key_to_string(Key key) {
427
+ switch (key) {
428
+ case Key::A: return "A";
429
+ case Key::B: return "B";
430
+ case Key::C: return "C";
431
+ case Key::D: return "D";
432
+ case Key::E: return "E";
433
+ case Key::F: return "F";
434
+ case Key::G: return "G";
435
+ case Key::H: return "H";
436
+ case Key::I: return "I";
437
+ case Key::J: return "J";
438
+ case Key::K: return "K";
439
+ case Key::L: return "L";
440
+ case Key::M: return "M";
441
+ case Key::N: return "N";
442
+ case Key::O: return "O";
443
+ case Key::P: return "P";
444
+ case Key::Q: return "Q";
445
+ case Key::R: return "R";
446
+ case Key::S: return "S";
447
+ case Key::T: return "T";
448
+ case Key::U: return "U";
449
+ case Key::V: return "V";
450
+ case Key::W: return "W";
451
+ case Key::X: return "X";
452
+ case Key::Y: return "Y";
453
+ case Key::Z: return "Z";
454
+ case Key::Digit0: return "0";
455
+ case Key::Digit1: return "1";
456
+ case Key::Digit2: return "2";
457
+ case Key::Digit3: return "3";
458
+ case Key::Digit4: return "4";
459
+ case Key::Digit5: return "5";
460
+ case Key::Digit6: return "6";
461
+ case Key::Digit7: return "7";
462
+ case Key::Digit8: return "8";
463
+ case Key::Digit9: return "9";
464
+ case Key::Space: return "Space";
465
+ case Key::Enter: return "Enter";
466
+ case Key::Escape: return "Escape";
467
+ case Key::Tab: return "Tab";
468
+ case Key::Backspace: return "Backspace";
469
+ case Key::Delete: return "Delete";
470
+ case Key::Insert: return "Insert";
471
+ case Key::Home: return "Home";
472
+ case Key::End: return "End";
473
+ case Key::PageUp: return "PageUp";
474
+ case Key::PageDown: return "PageDown";
475
+ case Key::Left: return "Left";
476
+ case Key::Right: return "Right";
477
+ case Key::Up: return "Up";
478
+ case Key::Down: return "Down";
479
+ case Key::CapsLock: return "CapsLock";
480
+ case Key::Shift: return "Shift";
481
+ case Key::Control: return "Ctrl";
482
+ case Key::Alt: return "Alt";
483
+ case Key::Meta: return "Meta";
484
+ case Key::F1: return "F1";
485
+ case Key::F2: return "F2";
486
+ case Key::F3: return "F3";
487
+ case Key::F4: return "F4";
488
+ case Key::F5: return "F5";
489
+ case Key::F6: return "F6";
490
+ case Key::F7: return "F7";
491
+ case Key::F8: return "F8";
492
+ case Key::F9: return "F9";
493
+ case Key::F10: return "F10";
494
+ case Key::F11: return "F11";
495
+ case Key::F12: return "F12";
496
+ default: return "Unknown";
497
+ }
498
+ }
499
+
500
+ namespace detail {
501
+
502
+ void set_dispatch(DispatchFn dispatch) {
503
+ auto& current = state();
504
+ std::lock_guard<std::mutex> lock(current.mutex);
505
+ current.dispatch = std::move(dispatch);
506
+ }
507
+
508
+ void set_global_backend(GlobalRegisterFn reg, GlobalUnregisterFn unreg) {
509
+ auto& current = state();
510
+ std::lock_guard<std::mutex> lock(current.mutex);
511
+ current.register_global = std::move(reg);
512
+ current.unregister_global = std::move(unreg);
513
+ }
514
+
515
+ void clear_global_backend() {
516
+ auto& current = state();
517
+ std::lock_guard<std::mutex> lock(current.mutex);
518
+ current.register_global = {};
519
+ current.unregister_global = {};
520
+ }
521
+
522
+ void emit_key_event(const KeyEvent& event) {
523
+ std::vector<KeyListener> listeners;
524
+ std::vector<ShortcutHandler> handlers;
525
+ KeyEvent enriched = event;
526
+
527
+ {
528
+ auto& current = state();
529
+ std::lock_guard<std::mutex> lock(current.mutex);
530
+ if (event.type == KeyEventType::KeyDown) {
531
+ reconcile_modifier_state(current, event);
532
+ if (event.key != Key::Control && event.key != Key::Shift && event.key != Key::Alt && event.key != Key::Meta) {
533
+ set_pressed_key(current, event.key, true);
534
+ }
535
+ } else {
536
+ if (event.key != Key::Control && event.key != Key::Shift && event.key != Key::Alt && event.key != Key::Meta) {
537
+ set_pressed_key(current, event.key, false);
538
+ }
539
+ reconcile_modifier_state(current, event);
540
+ }
541
+ enriched.pressed_keys = current.pressed_order;
542
+ listeners = collect_listeners_locked(current);
543
+ handlers = collect_matching_handlers_locked(current, enriched);
544
+ }
545
+
546
+ invoke_listeners(listeners, enriched);
547
+ invoke_handlers(handlers, enriched);
548
+ }
549
+
550
+ void trigger_global_shortcut(const std::string& id) {
551
+ std::vector<KeyListener> listeners;
552
+ ShortcutHandler handler;
553
+ KeyEvent event{};
554
+ DispatchFn dispatch_fn;
555
+
556
+ {
557
+ auto& current = state();
558
+ std::lock_guard<std::mutex> lock(current.mutex);
559
+ auto it = current.shortcuts.find(id);
560
+ if (it == current.shortcuts.end() || !it->second.options.global) {
561
+ return;
562
+ }
563
+ listeners = collect_listeners_locked(current);
564
+ handler = it->second.handler;
565
+ event = make_shortcut_event(it->second);
566
+ dispatch_fn = current.dispatch;
567
+ }
568
+
569
+ auto dispatch = [listeners = std::move(listeners), event, handler = std::move(handler)]() {
570
+ invoke_listeners(listeners, event);
571
+ if (handler) {
572
+ handler(event);
573
+ }
574
+ };
575
+
576
+ if (dispatch_fn) {
577
+ dispatch_fn(std::move(dispatch));
578
+ return;
579
+ }
580
+ dispatch();
581
+ }
582
+
583
+ } // namespace detail
584
+ } // namespace wpp::input
@@ -0,0 +1,21 @@
1
+ #pragma once
2
+
3
+ #include <functional>
4
+ #include <string>
5
+
6
+ #include <windowpp/windowpp.h>
7
+
8
+ namespace wpp::input::detail {
9
+
10
+ using DispatchFn = std::function<void(std::function<void()>)>;
11
+ using GlobalRegisterFn = std::function<bool(const std::string& id, const Shortcut& shortcut, std::string* error)>;
12
+ using GlobalUnregisterFn = std::function<void(const std::string& id)>;
13
+
14
+ void set_dispatch(DispatchFn dispatch);
15
+ void set_global_backend(GlobalRegisterFn reg, GlobalUnregisterFn unreg);
16
+ void clear_global_backend();
17
+
18
+ void emit_key_event(const KeyEvent& event);
19
+ void trigger_global_shortcut(const std::string& id);
20
+
21
+ } // namespace wpp::input::detail
@@ -0,0 +1,29 @@
1
+ // ============================================================================
2
+ // application.cpp — Platform dispatch and factory
3
+ // ============================================================================
4
+
5
+ #include <windowpp/windowpp.h>
6
+
7
+ #if defined(WIN32)
8
+ #include "platform/win32/app_win32.h"
9
+ #elif defined(__APPLE__)
10
+ #include "platform/macos/app_macos.h"
11
+ #elif defined(__linux__)
12
+ #include "platform/linux/app_linux.h"
13
+ #else
14
+ #error "Unsupported platform"
15
+ #endif
16
+
17
+ namespace wpp {
18
+
19
+ std::unique_ptr<Application> create_app(const AppConfig& config) {
20
+ #if defined(WIN32)
21
+ return std::make_unique<Win32Application>(config);
22
+ #elif defined(__APPLE__)
23
+ return std::make_unique<MacOSApplication>(config);
24
+ #elif defined(__linux__)
25
+ return std::make_unique<LinuxApplication>(config);
26
+ #endif
27
+ }
28
+
29
+ } // namespace wpp
@@ -0,0 +1,40 @@
1
+ // ============================================================================
2
+ // hit_test.cpp — Cross-platform hit-test logic for frameless windows
3
+ // ============================================================================
4
+
5
+ #include <windowpp/windowpp.h>
6
+
7
+ namespace wpp {
8
+
9
+ HitTestRegion default_hit_test(Point cursor, Size window_size,
10
+ int border_width, int caption_height,
11
+ bool resizable) {
12
+ const int w = window_size.width;
13
+ const int h = window_size.height;
14
+ const int b = border_width;
15
+
16
+ const bool at_left = cursor.x < b;
17
+ const bool at_right = cursor.x >= w - b;
18
+ const bool at_top = cursor.y < b;
19
+ const bool at_bottom = cursor.y >= h - b;
20
+
21
+ if (resizable) {
22
+ if (at_top && at_left) return HitTestRegion::ResizeTopLeft;
23
+ if (at_top && at_right) return HitTestRegion::ResizeTopRight;
24
+ if (at_bottom && at_left) return HitTestRegion::ResizeBottomLeft;
25
+ if (at_bottom && at_right) return HitTestRegion::ResizeBottomRight;
26
+
27
+ if (at_top) return HitTestRegion::ResizeTop;
28
+ if (at_bottom) return HitTestRegion::ResizeBottom;
29
+ if (at_left) return HitTestRegion::ResizeLeft;
30
+ if (at_right) return HitTestRegion::ResizeRight;
31
+ }
32
+
33
+ if (cursor.y < caption_height) {
34
+ return HitTestRegion::Caption;
35
+ }
36
+
37
+ return HitTestRegion::Client;
38
+ }
39
+
40
+ } // namespace wpp
@@ -0,0 +1,24 @@
1
+ // ============================================================================
2
+ // image_loader.cpp — PNG/ICO loading using stb_image
3
+ // ============================================================================
4
+
5
+ #define STB_IMAGE_IMPLEMENTATION
6
+ #include <stb_image.h>
7
+
8
+ namespace wpp {
9
+
10
+ ImageData load_image_from_file(const std::string& path, int* width, int* height, int* channels) {
11
+ ImageData data;
12
+ data.pixels = stbi_load(path.c_str(), width, height, channels, STBI_rgb_alpha);
13
+ data.size = *width * *height * 4;
14
+ return data;
15
+ }
16
+
17
+ void free_image_data(ImageData& data) {
18
+ if (data.pixels) {
19
+ stbi_image_free(data.pixels);
20
+ data.pixels = nullptr;
21
+ }
22
+ }
23
+
24
+ } // namespace wpp