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,156 @@
1
+ export interface AppInfo {
2
+ name: string;
3
+ version: string;
4
+ identifier: string;
5
+ }
6
+
7
+ export interface Point {
8
+ x: number;
9
+ y: number;
10
+ }
11
+
12
+ export interface Size {
13
+ width: number;
14
+ height: number;
15
+ }
16
+
17
+ export interface Rect extends Point, Size {}
18
+
19
+ export interface MonitorInfo {
20
+ name: string;
21
+ bounds: Rect;
22
+ workArea: Rect;
23
+ scaleFactor: number;
24
+ isPrimary: boolean;
25
+ }
26
+
27
+ export interface ClipboardChangedEvent {
28
+ name: 'clipboardChanged';
29
+ clipboardText: string;
30
+ }
31
+
32
+ export type AppEvent = ClipboardChangedEvent;
33
+
34
+ interface PendingCall {
35
+ resolve: (value: any) => void;
36
+ reject: (reason: Error) => void;
37
+ }
38
+
39
+ let callId = 0;
40
+ const pending = new Map<string, PendingCall>();
41
+ const appListeners = new Set<(event: AppEvent) => void>();
42
+ let streamEnabled = false;
43
+
44
+ function getGlobal(): any {
45
+ if (typeof window !== 'undefined') return window;
46
+ if (typeof globalThis !== 'undefined') return globalThis;
47
+ return {};
48
+ }
49
+
50
+ const globalObject = getGlobal();
51
+ const previousResponse = globalObject.__response__;
52
+ const previousPlatformEvent = globalObject.__wpp_platform_event__;
53
+
54
+ globalObject.__response__ = function(id: string, result: any, error?: string) {
55
+ const request = pending.get(id);
56
+ if (request) {
57
+ pending.delete(id);
58
+ if (error) {
59
+ request.reject(new Error(error));
60
+ } else {
61
+ request.resolve(result);
62
+ }
63
+ return;
64
+ }
65
+
66
+ if (previousResponse) {
67
+ previousResponse(id, result, error);
68
+ }
69
+ };
70
+
71
+ globalObject.__wpp_platform_event__ = function(eventName: string, payload: any) {
72
+ if (eventName === 'app') {
73
+ appListeners.forEach((listener) => listener(payload as AppEvent));
74
+ }
75
+
76
+ if (previousPlatformEvent) {
77
+ previousPlatformEvent(eventName, payload);
78
+ }
79
+ };
80
+
81
+ function invoke<T>(method: string, params: unknown[] = []): Promise<T> {
82
+ return new Promise<T>((resolve, reject) => {
83
+ const id = `app:${++callId}`;
84
+ pending.set(id, { resolve, reject });
85
+ const payload = JSON.stringify({ id, method, params });
86
+
87
+ if (globalObject.__native_invoke__) {
88
+ globalObject.__native_invoke__(payload);
89
+ return;
90
+ }
91
+
92
+ if (globalObject.chrome?.webview?.postMessage) {
93
+ globalObject.chrome.webview.postMessage(payload);
94
+ return;
95
+ }
96
+
97
+ pending.delete(id);
98
+ reject(new Error('Native bridge not available'));
99
+ });
100
+ }
101
+
102
+ async function ensureStreamEnabled() {
103
+ if (streamEnabled) {
104
+ return;
105
+ }
106
+ await invoke<void>('app:setEventStreamEnabled', [true]);
107
+ streamEnabled = true;
108
+ }
109
+
110
+ export function getInfo(): Promise<AppInfo> {
111
+ return invoke<AppInfo>('app:getInfo');
112
+ }
113
+
114
+ export function getMonitors(): Promise<MonitorInfo[]> {
115
+ return invoke<MonitorInfo[]>('app:getMonitors');
116
+ }
117
+
118
+ export function getPrimaryMonitor(): Promise<MonitorInfo> {
119
+ return invoke<MonitorInfo>('app:getPrimaryMonitor');
120
+ }
121
+
122
+ export function getClipboardText(): Promise<string> {
123
+ return invoke<string>('app:getClipboardText');
124
+ }
125
+
126
+ export function setClipboardText(text: string): Promise<void> {
127
+ return invoke<void>('app:setClipboardText', [text]);
128
+ }
129
+
130
+ export async function onEvent(listener: (event: AppEvent) => void): Promise<() => void> {
131
+ await ensureStreamEnabled();
132
+ appListeners.add(listener);
133
+ return () => {
134
+ appListeners.delete(listener);
135
+ };
136
+ }
137
+
138
+ export async function onClipboardText(listener: (text: string) => void): Promise<() => void> {
139
+ return onEvent((event) => {
140
+ if (event.name === 'clipboardChanged') {
141
+ listener(event.clipboardText);
142
+ }
143
+ });
144
+ }
145
+
146
+ export const app = {
147
+ getInfo,
148
+ getMonitors,
149
+ getPrimaryMonitor,
150
+ getClipboardText,
151
+ setClipboardText,
152
+ onEvent,
153
+ onClipboardText,
154
+ };
155
+
156
+ export default app;
@@ -0,0 +1,249 @@
1
+ export type WindowState = 'normal' | 'minimized' | 'maximized' | 'fullscreen' | 'hidden';
2
+
3
+ export interface Point {
4
+ x: number;
5
+ y: number;
6
+ }
7
+
8
+ export interface Size {
9
+ width: number;
10
+ height: number;
11
+ }
12
+
13
+ export interface WindowInfo {
14
+ id: string;
15
+ title: string;
16
+ size: Size;
17
+ position: Point;
18
+ state: WindowState;
19
+ visible: boolean;
20
+ focused: boolean;
21
+ scaleFactor: number;
22
+ }
23
+
24
+ export interface ResizeDetail {
25
+ oldSize: Size;
26
+ newSize: Size;
27
+ }
28
+
29
+ export interface MoveDetail {
30
+ oldPosition: Point;
31
+ newPosition: Point;
32
+ }
33
+
34
+ export interface WindowEvent {
35
+ name: string;
36
+ windowId: string;
37
+ window?: WindowInfo | null;
38
+ detail?: ResizeDetail | MoveDetail | null;
39
+ }
40
+
41
+ interface PendingCall {
42
+ resolve: (value: any) => void;
43
+ reject: (reason: Error) => void;
44
+ }
45
+
46
+ let callId = 0;
47
+ const pending = new Map<string, PendingCall>();
48
+ const windowListeners = new Set<(event: WindowEvent) => void>();
49
+ let streamEnabled = false;
50
+
51
+ function getGlobal(): any {
52
+ if (typeof window !== 'undefined') return window;
53
+ if (typeof globalThis !== 'undefined') return globalThis;
54
+ return {};
55
+ }
56
+
57
+ const globalObject = getGlobal();
58
+ const previousResponse = globalObject.__response__;
59
+ const previousPlatformEvent = globalObject.__wpp_platform_event__;
60
+
61
+ globalObject.__response__ = function(id: string, result: any, error?: string) {
62
+ const request = pending.get(id);
63
+ if (request) {
64
+ pending.delete(id);
65
+ if (error) {
66
+ request.reject(new Error(error));
67
+ } else {
68
+ request.resolve(result);
69
+ }
70
+ return;
71
+ }
72
+
73
+ if (previousResponse) {
74
+ previousResponse(id, result, error);
75
+ }
76
+ };
77
+
78
+ globalObject.__wpp_platform_event__ = function(eventName: string, payload: any) {
79
+ if (eventName === 'window') {
80
+ windowListeners.forEach((listener) => listener(payload as WindowEvent));
81
+ }
82
+
83
+ if (previousPlatformEvent) {
84
+ previousPlatformEvent(eventName, payload);
85
+ }
86
+ };
87
+
88
+ function invoke<T>(method: string, params: unknown[] = []): Promise<T> {
89
+ return new Promise<T>((resolve, reject) => {
90
+ const id = `window:${++callId}`;
91
+ pending.set(id, { resolve, reject });
92
+ const payload = JSON.stringify({ id, method, params });
93
+
94
+ if (globalObject.__native_invoke__) {
95
+ globalObject.__native_invoke__(payload);
96
+ return;
97
+ }
98
+
99
+ if (globalObject.chrome?.webview?.postMessage) {
100
+ globalObject.chrome.webview.postMessage(payload);
101
+ return;
102
+ }
103
+
104
+ pending.delete(id);
105
+ reject(new Error('Native bridge not available'));
106
+ });
107
+ }
108
+
109
+ async function ensureStreamEnabled() {
110
+ if (streamEnabled) {
111
+ return;
112
+ }
113
+ await invoke<void>('window:setEventStreamEnabled', [true]);
114
+ streamEnabled = true;
115
+ }
116
+
117
+ export async function onEvent(listener: (event: WindowEvent) => void): Promise<() => void> {
118
+ await ensureStreamEnabled();
119
+ windowListeners.add(listener);
120
+ return () => {
121
+ windowListeners.delete(listener);
122
+ };
123
+ }
124
+
125
+ export function getCurrent(): Promise<WindowInfo> {
126
+ return invoke<WindowInfo>('window:getCurrent');
127
+ }
128
+
129
+ export function get(windowId?: string): Promise<WindowInfo> {
130
+ return windowId ? invoke<WindowInfo>('window:get', [windowId]) : getCurrent();
131
+ }
132
+
133
+ export function list(): Promise<WindowInfo[]> {
134
+ return invoke<WindowInfo[]>('window:list');
135
+ }
136
+
137
+ export function setTitle(title: string, windowId?: string): Promise<WindowInfo> {
138
+ return windowId ? invoke<WindowInfo>('window:setTitle', [title, windowId]) : invoke<WindowInfo>('window:setTitle', [title]);
139
+ }
140
+
141
+ export function setSize(width: number, height: number, windowId?: string): Promise<WindowInfo> {
142
+ return windowId
143
+ ? invoke<WindowInfo>('window:setSize', [width, height, windowId])
144
+ : invoke<WindowInfo>('window:setSize', [width, height]);
145
+ }
146
+
147
+ export function setPosition(x: number, y: number, windowId?: string): Promise<WindowInfo> {
148
+ return windowId
149
+ ? invoke<WindowInfo>('window:setPosition', [x, y, windowId])
150
+ : invoke<WindowInfo>('window:setPosition', [x, y]);
151
+ }
152
+
153
+ export function setMinSize(width: number, height: number, windowId?: string): Promise<WindowInfo> {
154
+ return windowId
155
+ ? invoke<WindowInfo>('window:setMinSize', [width, height, windowId])
156
+ : invoke<WindowInfo>('window:setMinSize', [width, height]);
157
+ }
158
+
159
+ export function setMaxSize(width: number, height: number, windowId?: string): Promise<WindowInfo> {
160
+ return windowId
161
+ ? invoke<WindowInfo>('window:setMaxSize', [width, height, windowId])
162
+ : invoke<WindowInfo>('window:setMaxSize', [width, height]);
163
+ }
164
+
165
+ export function setResizable(value: boolean, windowId?: string): Promise<WindowInfo> {
166
+ return windowId ? invoke<WindowInfo>('window:setResizable', [value, windowId]) : invoke<WindowInfo>('window:setResizable', [value]);
167
+ }
168
+
169
+ export function setAlwaysOnTop(value: boolean, windowId?: string): Promise<WindowInfo> {
170
+ return windowId ? invoke<WindowInfo>('window:setAlwaysOnTop', [value, windowId]) : invoke<WindowInfo>('window:setAlwaysOnTop', [value]);
171
+ }
172
+
173
+ export function setOpacity(value: number, windowId?: string): Promise<WindowInfo> {
174
+ return windowId ? invoke<WindowInfo>('window:setOpacity', [value, windowId]) : invoke<WindowInfo>('window:setOpacity', [value]);
175
+ }
176
+
177
+ export function show(windowId?: string): Promise<WindowInfo> {
178
+ return windowId ? invoke<WindowInfo>('window:show', [windowId]) : invoke<WindowInfo>('window:show');
179
+ }
180
+
181
+ export function hide(windowId?: string): Promise<WindowInfo> {
182
+ return windowId ? invoke<WindowInfo>('window:hide', [windowId]) : invoke<WindowInfo>('window:hide');
183
+ }
184
+
185
+ export function close(windowId?: string): Promise<void> {
186
+ return windowId ? invoke<void>('window:close', [windowId]) : invoke<void>('window:close');
187
+ }
188
+
189
+ export function focus(windowId?: string): Promise<WindowInfo> {
190
+ return windowId ? invoke<WindowInfo>('window:focus', [windowId]) : invoke<WindowInfo>('window:focus');
191
+ }
192
+
193
+ export function minimize(windowId?: string): Promise<WindowInfo> {
194
+ return windowId ? invoke<WindowInfo>('window:minimize', [windowId]) : invoke<WindowInfo>('window:minimize');
195
+ }
196
+
197
+ export function maximize(windowId?: string): Promise<WindowInfo> {
198
+ return windowId ? invoke<WindowInfo>('window:maximize', [windowId]) : invoke<WindowInfo>('window:maximize');
199
+ }
200
+
201
+ export function restore(windowId?: string): Promise<WindowInfo> {
202
+ return windowId ? invoke<WindowInfo>('window:restore', [windowId]) : invoke<WindowInfo>('window:restore');
203
+ }
204
+
205
+ export function fullscreen(windowId?: string): Promise<WindowInfo> {
206
+ return windowId ? invoke<WindowInfo>('window:fullscreen', [windowId]) : invoke<WindowInfo>('window:fullscreen');
207
+ }
208
+
209
+ export function center(windowId?: string): Promise<WindowInfo> {
210
+ return windowId ? invoke<WindowInfo>('window:center', [windowId]) : invoke<WindowInfo>('window:center');
211
+ }
212
+
213
+ export interface CreateWindowOptions {
214
+ title?: string;
215
+ width?: number;
216
+ height?: number;
217
+ }
218
+
219
+ export function create(url: string, options: CreateWindowOptions = {}): Promise<void> {
220
+ const { title = 'New Window', width = 800, height = 600 } = options;
221
+ return invoke<void>('window:create', [url, title, width, height]);
222
+ }
223
+
224
+ export const windowApi = {
225
+ onEvent,
226
+ getCurrent,
227
+ get,
228
+ list,
229
+ setTitle,
230
+ setSize,
231
+ setPosition,
232
+ setMinSize,
233
+ setMaxSize,
234
+ setResizable,
235
+ setAlwaysOnTop,
236
+ setOpacity,
237
+ show,
238
+ hide,
239
+ close,
240
+ focus,
241
+ minimize,
242
+ maximize,
243
+ restore,
244
+ fullscreen,
245
+ center,
246
+ create,
247
+ };
248
+
249
+ export default windowApi;
@@ -0,0 +1,256 @@
1
+ // ============================================================================
2
+ // app_linux.cpp — Linux/X11 Application implementation
3
+ // ============================================================================
4
+
5
+ #include "app_linux.h"
6
+ #include "../../Input/input_service.h"
7
+ #include "../../Input/Linux/linux_key_utils.h"
8
+ #include "../../renderer/webview/webview.h"
9
+ #include <cstring>
10
+ #include <glib.h>
11
+
12
+ namespace wpp {
13
+
14
+ LinuxApplication::LinuxApplication(const AppConfig& config) : config_(config) {
15
+ connection_ = xcb_connect(nullptr, nullptr);
16
+ int screen = 0;
17
+ root_ = xcb_setup_roots_iterator(xcb_get_setup(connection_)).data->root;
18
+
19
+ if (config_.tray_enabled) {
20
+ set_tray(config_.tray);
21
+ }
22
+ }
23
+
24
+ LinuxApplication::~LinuxApplication() {
25
+ xcb_disconnect(connection_);
26
+ }
27
+
28
+ LinuxWindow* LinuxApplication::get_window_by_native(xcb_window_t window) {
29
+ for (auto& entry : windows_) {
30
+ if (entry.second && entry.second->window() == window) {
31
+ return entry.second.get();
32
+ }
33
+ }
34
+ return nullptr;
35
+ }
36
+
37
+ Window* LinuxApplication::create_window(const WindowConfig& config) {
38
+ auto id = config.id.empty() ? "main" : config.id;
39
+
40
+ uint32_t value_mask = XCB_CW_EVENT_MASK;
41
+ uint32_t value_list[] = {
42
+ XCB_EVENT_MASK_KEY_PRESS |
43
+ XCB_EVENT_MASK_KEY_RELEASE |
44
+ XCB_EVENT_MASK_BUTTON_PRESS |
45
+ XCB_EVENT_MASK_BUTTON_RELEASE |
46
+ XCB_EVENT_MASK_POINTER_MOTION |
47
+ XCB_EVENT_MASK_EXPOSURE |
48
+ XCB_EVENT_MASK_STRUCTURE_NOTIFY
49
+ };
50
+
51
+ xcb_window_t window = xcb_generate_id(connection_);
52
+ xcb_create_window(connection_,
53
+ XCB_COPY_FROM_PARENT,
54
+ window,
55
+ root_,
56
+ config.x >= 0 ? config.x : 100,
57
+ config.y >= 0 ? config.y : 100,
58
+ config.width,
59
+ config.height,
60
+ 0,
61
+ XCB_WINDOW_CLASS_INPUT_OUTPUT,
62
+ XCB_COPY_FROM_PARENT,
63
+ value_mask,
64
+ value_list
65
+ );
66
+
67
+ if (config.frameless) {
68
+ auto atom = linux::get_atom(connection_, "_MOTIF_WM_HINTS");
69
+ if (atom != XCB_NONE) {
70
+ uint32_t hints[5] = { 0, 0, 0, 0, 0 };
71
+ xcb_change_property(connection_, XCB_PROP_MODE_REPLACE, window,
72
+ atom, XCB_ATOM_CARDINAL, 32, 5, hints);
73
+ }
74
+ }
75
+
76
+ if (!config.title.empty()) {
77
+ xcb_change_property(connection_, XCB_PROP_MODE_REPLACE, window,
78
+ XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
79
+ config.title.size(), config.title.c_str());
80
+ }
81
+
82
+ if (config.initial_state != WindowState::Hidden) {
83
+ xcb_map_window(connection_, window);
84
+ }
85
+
86
+ auto window_ptr = std::make_unique<LinuxWindow>(config, window);
87
+
88
+ if (config.webview.enabled) {
89
+ auto webview_config = config.webview;
90
+ webview_config.scrollbars = config.scrollbars;
91
+ window_ptr->set_webview(create_webview(webview_config, reinterpret_cast<void*>(static_cast<uintptr_t>(window))));
92
+ if (auto* webview = window_ptr->webview()) {
93
+ webview->set_drag_drop_enabled(config.file_drop);
94
+ }
95
+ }
96
+
97
+ auto* ptr = window_ptr.get();
98
+ windows_[id] = std::move(window_ptr);
99
+
100
+ if (config.on_created) {
101
+ config.on_created();
102
+ }
103
+
104
+ return ptr;
105
+ }
106
+
107
+ Window* LinuxApplication::get_window(const std::string& id) {
108
+ auto it = windows_.find(id);
109
+ return it != windows_.end() ? it->second.get() : nullptr;
110
+ }
111
+
112
+ std::vector<Window*> LinuxApplication::all_windows() {
113
+ std::vector<Window*> result;
114
+ for (auto& [id, win] : windows_) {
115
+ result.push_back(win.get());
116
+ }
117
+ return result;
118
+ }
119
+
120
+ void LinuxApplication::run() {
121
+ running_ = true;
122
+ while (running_) {
123
+ handle_events();
124
+ }
125
+ }
126
+
127
+ void LinuxApplication::quit() {
128
+ running_ = false;
129
+ if (config_.on_quit) {
130
+ config_.on_quit();
131
+ }
132
+ }
133
+
134
+ void LinuxApplication::set_tray(const TrayConfig& config) {
135
+ tray_ = std::make_unique<LinuxTray>(config, connection_, root_);
136
+ }
137
+
138
+ void LinuxApplication::remove_tray() {
139
+ tray_.reset();
140
+ }
141
+
142
+ void LinuxApplication::handle_events() {
143
+ xcb_generic_event_t* event = xcb_poll_for_event(connection_);
144
+ while (event) {
145
+ uint8_t response_type = event->response_type & ~0x80;
146
+ switch (response_type) {
147
+ case XCB_KEY_PRESS: {
148
+ auto* key_event = reinterpret_cast<xcb_key_press_event_t*>(event);
149
+ auto* win = get_window_by_native(key_event->event);
150
+ if (win) {
151
+ auto translated = input::linux::make_key_event(connection_, key_event, input::KeyEventType::KeyDown, win->id());
152
+ input::detail::emit_key_event(translated);
153
+ if (win->config().on_key_down) {
154
+ win->config().on_key_down(translated);
155
+ }
156
+ }
157
+ break;
158
+ }
159
+ case XCB_KEY_RELEASE: {
160
+ auto* key_event = reinterpret_cast<xcb_key_press_event_t*>(event);
161
+ auto* win = get_window_by_native(key_event->event);
162
+ if (win) {
163
+ auto translated = input::linux::make_key_event(connection_, key_event, input::KeyEventType::KeyUp, win->id());
164
+ input::detail::emit_key_event(translated);
165
+ if (win->config().on_key_up) {
166
+ win->config().on_key_up(translated);
167
+ }
168
+ }
169
+ break;
170
+ }
171
+ case XCB_EXPOSE:
172
+ for (auto& [id, win] : windows_) {
173
+ if (win->config().on_paint) {
174
+ win->config().on_paint(win->native_handle());
175
+ }
176
+ }
177
+ break;
178
+ case XCB_CLIENT_MESSAGE:
179
+ if (((xcb_client_message_event_t*)event)->type ==
180
+ linux::get_atom(connection_, "WM_PROTOCOLS")) {
181
+ quit();
182
+ }
183
+ break;
184
+ }
185
+ free(event);
186
+ event = xcb_poll_for_event(connection_);
187
+ }
188
+ }
189
+
190
+ std::vector<MonitorInfo> LinuxApplication::monitors() {
191
+ std::vector<MonitorInfo> result;
192
+ MonitorInfo mi;
193
+ mi.bounds = {0, 0, 1920, 1080};
194
+ mi.work_area = mi.bounds;
195
+ mi.scale_factor = 1.0f;
196
+ mi.is_primary = true;
197
+ mi.name = "Screen 0";
198
+ result.push_back(mi);
199
+ return result;
200
+ }
201
+
202
+ MonitorInfo LinuxApplication::primary_monitor() {
203
+ MonitorInfo mi;
204
+ mi.bounds = {0, 0, 1920, 1080};
205
+ mi.work_area = mi.bounds;
206
+ mi.scale_factor = 1.0f;
207
+ mi.is_primary = true;
208
+ mi.name = "Screen 0";
209
+ return mi;
210
+ }
211
+
212
+ std::string LinuxApplication::clipboard_text() {
213
+ return {};
214
+ }
215
+
216
+ void LinuxApplication::set_clipboard_text(const std::string& text) {
217
+ }
218
+
219
+ void LinuxApplication::send_message(const std::string& window_id,
220
+ const std::string& event,
221
+ const std::string& payload) {
222
+ auto it = message_handlers_.find(window_id);
223
+ if (it != message_handlers_.end()) {
224
+ for (auto& handler : it->second) {
225
+ handler(event, payload);
226
+ }
227
+ }
228
+ }
229
+
230
+ void LinuxApplication::on_message(const std::string& window_id, MessageHandler handler) {
231
+ message_handlers_[window_id].push_back(handler);
232
+ }
233
+
234
+ Application::TimerId LinuxApplication::set_timeout(uint32_t ms, std::function<void()> callback) {
235
+ return 0;
236
+ }
237
+
238
+ Application::TimerId LinuxApplication::set_interval(uint32_t ms, std::function<void()> callback) {
239
+ return 0;
240
+ }
241
+
242
+ void LinuxApplication::clear_timer(TimerId id) {
243
+ }
244
+
245
+ void LinuxApplication::dispatch(std::function<void()> fn) {
246
+ auto* callback = new std::function<void()>(std::move(fn));
247
+ g_main_context_invoke(nullptr, [](gpointer data) -> gboolean {
248
+ std::unique_ptr<std::function<void()>> callback_ptr(static_cast<std::function<void()>*>(data));
249
+ if (*callback_ptr) {
250
+ (*callback_ptr)();
251
+ }
252
+ return G_SOURCE_REMOVE;
253
+ }, callback);
254
+ }
255
+
256
+ } // namespace wpp