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,414 @@
1
+ // ============================================================================
2
+ // FileSystem/filesystem_search_service.cpp
3
+ // Multi-threaded streamed filesystem search with cancellation.
4
+ // ============================================================================
5
+
6
+ #include "filesystem_search_service.h"
7
+
8
+ #include "file_index_service.h"
9
+
10
+ #include <filesystem>
11
+ #include <thread>
12
+ #include <algorithm>
13
+
14
+ #ifdef _WIN32
15
+ #include <windows.h>
16
+ #include <shlwapi.h>
17
+ #pragma comment(lib, "shlwapi.lib")
18
+ #endif
19
+
20
+ namespace wpp::fs {
21
+
22
+ // ─── ThreadPool Implementation ───────────────────────────────────────────────────
23
+
24
+ ThreadPool::ThreadPool(size_t num_threads) {
25
+ // Default to hardware concurrency if not specified, min 2, max 8
26
+ size_t actual_threads = num_threads;
27
+ if (actual_threads == 0) {
28
+ actual_threads = std::thread::hardware_concurrency();
29
+ actual_threads = std::max(size_t(2), std::min(actual_threads, size_t(8)));
30
+ }
31
+
32
+ workers_.reserve(actual_threads);
33
+ for (size_t i = 0; i < actual_threads; ++i) {
34
+ workers_.emplace_back(&ThreadPool::worker_thread, this);
35
+ }
36
+ }
37
+
38
+ ThreadPool::~ThreadPool() {
39
+ shutdown();
40
+ }
41
+
42
+ void ThreadPool::worker_thread() {
43
+ while (true) {
44
+ std::function<void()> task;
45
+ {
46
+ std::unique_lock<std::mutex> lock(mutex_);
47
+ cv_.wait(lock, [this] { return stop_.load() || !tasks_.empty(); });
48
+
49
+ if (stop_.load() && tasks_.empty()) {
50
+ return;
51
+ }
52
+
53
+ task = std::move(tasks_.front());
54
+ tasks_.pop();
55
+ }
56
+ task();
57
+ }
58
+ }
59
+
60
+ void ThreadPool::shutdown() {
61
+ stop_.store(true);
62
+ cv_.notify_all();
63
+ for (auto& worker : workers_) {
64
+ if (worker.joinable()) {
65
+ worker.join();
66
+ }
67
+ }
68
+ workers_.clear();
69
+ }
70
+
71
+ // ─── Platform-specific glob matching ────────────────────────────────────────────
72
+
73
+ #ifdef _WIN32
74
+
75
+ // Windows: Use PathMatchSpecW for fast native glob matching
76
+ static std::wstring toWide(const std::string& utf8) {
77
+ if (utf8.empty()) return {};
78
+ int len = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, nullptr, 0);
79
+ std::wstring ws(len, L'\0');
80
+ MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, ws.data(), len);
81
+ if (!ws.empty() && ws.back() == L'\0') ws.pop_back();
82
+ return ws;
83
+ }
84
+
85
+ static bool matches_glob(const std::string& filename, const std::string& pattern) {
86
+ std::wstring wfilename = toWide(filename);
87
+ std::wstring wpattern = toWide(pattern);
88
+ return PathMatchSpecW(wfilename.c_str(), wpattern.c_str()) != 0;
89
+ }
90
+
91
+ #else
92
+
93
+ // POSIX: Use fnmatch for fast native glob matching
94
+ #include <fnmatch.h>
95
+
96
+ static bool matches_glob(const std::string& filename, const std::string& pattern) {
97
+ #if defined(__APPLE__)
98
+ return fnmatch(pattern.c_str(), filename.c_str(), FNM_CASEFOLD) == 0;
99
+ #else
100
+ return fnmatch(pattern.c_str(), filename.c_str(), 0) == 0;
101
+ #endif
102
+ }
103
+
104
+ #endif
105
+
106
+ static bool is_hidden_entry(const std::filesystem::directory_entry& entry) {
107
+ const auto filename = entry.path().filename().u8string();
108
+ if (!filename.empty() && filename[0] == '.') {
109
+ return true;
110
+ }
111
+
112
+ #ifdef _WIN32
113
+ const DWORD attributes = GetFileAttributesW(entry.path().c_str());
114
+ return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_HIDDEN) != 0;
115
+ #else
116
+ return false;
117
+ #endif
118
+ }
119
+
120
+ // ─── FileSearchService Implementation ───────────────────────────────────────────
121
+
122
+ FileSearchService::FileSearchService() : threadPool_(4) {}
123
+
124
+ FileSearchService::~FileSearchService() {
125
+ threadPool_.shutdown();
126
+ }
127
+
128
+ FileSearchService& FileSearchService::instance() {
129
+ static FileSearchService service;
130
+ return service;
131
+ }
132
+
133
+ void FileSearchService::configure_cache_root(const std::string& cacheRoot) {
134
+ FileIndexService::instance().configure_cache_root(cacheRoot);
135
+ }
136
+
137
+ std::string FileSearchService::start(const std::vector<std::string>& roots,
138
+ const SearchOptions& options,
139
+ StreamBatchCallback onBatch,
140
+ StreamDoneCallback onDone,
141
+ const std::string& preferredId) {
142
+ auto state = std::make_shared<SearchState>();
143
+ state->id = preferredId.empty()
144
+ ? std::to_string(next_id_.fetch_add(1))
145
+ : preferredId;
146
+ state->roots = roots;
147
+ state->options = options;
148
+ state->onBatch = std::move(onBatch);
149
+ state->onDone = std::move(onDone);
150
+
151
+ {
152
+ std::lock_guard<std::mutex> lock(mutex_);
153
+ searches_[state->id] = state;
154
+ }
155
+
156
+ std::thread([this, state]() {
157
+ run_search(state);
158
+ }).detach();
159
+
160
+ return state->id;
161
+ }
162
+
163
+ void FileSearchService::cancel(const std::string& searchId) {
164
+ std::shared_ptr<SearchState> state;
165
+ {
166
+ std::lock_guard<std::mutex> lock(mutex_);
167
+ auto it = searches_.find(searchId);
168
+ if (it == searches_.end()) {
169
+ return;
170
+ }
171
+ state = it->second;
172
+ }
173
+ state->cancelled.store(true);
174
+ }
175
+
176
+ void FileSearchService::search_root(const std::shared_ptr<SearchState>& state,
177
+ const std::string& root,
178
+ std::vector<std::string>& results,
179
+ std::mutex& resultsMutex) {
180
+ const auto pattern = state->options.pattern.empty() ? std::string("*") : state->options.pattern;
181
+ const auto max_results = state->options.maxResults;
182
+ const auto recursive = state->options.recursive;
183
+ const auto includeHidden = state->options.includeHidden;
184
+ const auto includeDirectories = state->options.includeDirectories;
185
+
186
+ if (recursive) {
187
+ uint64_t indexed_entries = 0;
188
+ const auto indexed = FileIndexService::instance().get_or_build_root_index(
189
+ root,
190
+ &state->cancelled,
191
+ indexed_entries);
192
+ state->scannedCount.fetch_add(indexed_entries);
193
+
194
+ for (const auto& entry : indexed) {
195
+ if (state->cancelled.load()) {
196
+ return;
197
+ }
198
+
199
+ if (!includeHidden && entry.isHidden) {
200
+ continue;
201
+ }
202
+
203
+ if (max_results > 0 && state->matchedCount.load() >= max_results) {
204
+ return;
205
+ }
206
+
207
+ if (entry.isDir && !includeDirectories) {
208
+ continue;
209
+ }
210
+
211
+ if (matches_glob(entry.name, pattern)) {
212
+ std::lock_guard<std::mutex> lock(resultsMutex);
213
+ results.push_back(entry.path);
214
+ state->matchedCount.fetch_add(1);
215
+ }
216
+ }
217
+ return;
218
+ }
219
+
220
+ auto is_file = [&](const std::filesystem::path& path) -> bool {
221
+ std::error_code ec;
222
+ return std::filesystem::is_regular_file(path, ec);
223
+ };
224
+
225
+ auto is_dir = [&](const std::filesystem::path& path) -> bool {
226
+ std::error_code ec;
227
+ return std::filesystem::is_directory(path, ec);
228
+ };
229
+
230
+ // Start processing from root
231
+ if (is_file(root)) {
232
+ state->scannedCount.fetch_add(1);
233
+ std::filesystem::path path(root);
234
+ const auto filename = path.filename().u8string();
235
+ if (matches_glob(filename, pattern)) {
236
+ std::lock_guard<std::mutex> lock(resultsMutex);
237
+ results.push_back(root);
238
+ state->matchedCount.fetch_add(1);
239
+ }
240
+ } else if (is_dir(root)) {
241
+ std::vector<std::filesystem::path> pending_dirs;
242
+ pending_dirs.push_back(root);
243
+
244
+ while (!pending_dirs.empty()) {
245
+ if (state->cancelled.load()) {
246
+ return;
247
+ }
248
+
249
+ const std::filesystem::path dir = std::move(pending_dirs.back());
250
+ pending_dirs.pop_back();
251
+
252
+ std::error_code ec;
253
+ auto it = std::filesystem::directory_iterator(
254
+ dir,
255
+ std::filesystem::directory_options::skip_permission_denied,
256
+ ec);
257
+ if (ec) {
258
+ continue;
259
+ }
260
+
261
+ for (const auto& entry : it) {
262
+ if (state->cancelled.load()) {
263
+ return;
264
+ }
265
+
266
+ if (max_results > 0 && state->matchedCount.load() >= max_results) {
267
+ return;
268
+ }
269
+
270
+ if (!includeHidden && is_hidden_entry(entry)) {
271
+ continue;
272
+ }
273
+
274
+ state->scannedCount.fetch_add(1);
275
+ const bool is_directory = is_dir(entry.path());
276
+ if (is_directory) {
277
+ const auto dirname = entry.path().filename().u8string();
278
+ if (includeDirectories && matches_glob(dirname, pattern)) {
279
+ std::lock_guard<std::mutex> lock(resultsMutex);
280
+ results.push_back(entry.path().u8string());
281
+ state->matchedCount.fetch_add(1);
282
+ if (max_results > 0 && state->matchedCount.load() >= max_results) {
283
+ return;
284
+ }
285
+ }
286
+ if (recursive) {
287
+ pending_dirs.push_back(entry.path());
288
+ }
289
+ continue;
290
+ }
291
+
292
+ const auto filename = entry.path().filename().u8string();
293
+ if (matches_glob(filename, pattern)) {
294
+ std::lock_guard<std::mutex> lock(resultsMutex);
295
+ results.push_back(entry.path().u8string());
296
+ state->matchedCount.fetch_add(1);
297
+ }
298
+ }
299
+ }
300
+ }
301
+ }
302
+
303
+ void FileSearchService::run_search(const std::shared_ptr<SearchState>& state) {
304
+ StreamSearchSummary summary;
305
+ const auto max_results = state->options.maxResults;
306
+
307
+ std::vector<std::string> batch;
308
+ batch.reserve(24);
309
+ std::mutex batchMutex;
310
+
311
+ std::vector<std::string> allResults;
312
+ std::mutex resultsMutex;
313
+
314
+ // Auto-flush timer for instant UI updates without manual intervals
315
+ auto flush_batch = [&]() {
316
+ std::lock_guard<std::mutex> lock(batchMutex);
317
+ if (batch.empty()) return;
318
+
319
+ // Flush immediately, no waiting
320
+ state->onBatch(state->id, std::move(batch));
321
+ batch.clear();
322
+ batch.reserve(24);
323
+ };
324
+
325
+ try {
326
+ // Enqueue all roots for parallel processing
327
+ for (const auto& root : state->roots) {
328
+ if (root.empty()) {
329
+ continue;
330
+ }
331
+
332
+ std::error_code ec;
333
+ if (!std::filesystem::exists(root, ec)) {
334
+ if (summary.error.empty()) {
335
+ summary.error = "Path does not exist: " + root;
336
+ }
337
+ continue;
338
+ }
339
+
340
+ state->activeWorkers.fetch_add(1);
341
+ threadPool_.enqueue([this, state, root, &allResults, &resultsMutex]() {
342
+ search_root(state, root, allResults, resultsMutex);
343
+ state->activeWorkers.fetch_sub(1);
344
+ });
345
+ }
346
+
347
+ // Wait for all workers to complete or cancellation
348
+ while (!state->cancelled.load() && state->activeWorkers.load() > 0) {
349
+ // Flush frequently for immediate feedback - every 8 items or every 5ms
350
+ if (batch.size() >= 3) {
351
+ flush_batch();
352
+ }
353
+
354
+ // Collect any new results into batch immediately
355
+ {
356
+ std::lock_guard<std::mutex> lock(resultsMutex);
357
+ if (!allResults.empty()) {
358
+ size_t to_take = std::min(allResults.size(), size_t(8) - batch.size());
359
+ if (to_take > 0) {
360
+ batch.insert(batch.end(),
361
+ std::make_move_iterator(allResults.begin()),
362
+ std::make_move_iterator(allResults.begin() + to_take));
363
+ allResults.erase(allResults.begin(), allResults.begin() + to_take);
364
+ if (batch.size() >= 2) {
365
+ flush_batch();
366
+ }
367
+ }
368
+ }
369
+ }
370
+
371
+ if (max_results > 0 && state->matchedCount.load() >= max_results) {
372
+ break;
373
+ }
374
+
375
+ // Very short sleep - just yield to reduce CPU while staying responsive
376
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
377
+ }
378
+
379
+ // Collect any remaining results
380
+ {
381
+ std::lock_guard<std::mutex> lock(resultsMutex);
382
+ if (!allResults.empty()) {
383
+ batch.insert(batch.end(),
384
+ std::make_move_iterator(allResults.begin()),
385
+ std::make_move_iterator(allResults.end()));
386
+ allResults.clear();
387
+ }
388
+ }
389
+
390
+ } catch (const std::exception& e) {
391
+ summary.error = e.what();
392
+ } catch (...) {
393
+ summary.error = "Unknown filesystem search error";
394
+ }
395
+
396
+ summary.scannedEntries = state->scannedCount.load();
397
+ summary.matchedEntries = state->matchedCount.load();
398
+ summary.cancelled = state->cancelled.load();
399
+ summary.truncated = (max_results > 0 && state->matchedCount.load() >= max_results);
400
+
401
+ flush_batch();
402
+ finish_search(state, summary);
403
+ }
404
+
405
+ void FileSearchService::finish_search(const std::shared_ptr<SearchState>& state,
406
+ const StreamSearchSummary& summary) {
407
+ {
408
+ std::lock_guard<std::mutex> lock(mutex_);
409
+ searches_.erase(state->id);
410
+ }
411
+ state->onDone(state->id, summary);
412
+ }
413
+
414
+ } // namespace wpp::fs
@@ -0,0 +1,94 @@
1
+ // ============================================================================
2
+ // FileSystem/filesystem_search_service.h
3
+ // Cross-platform streamed filesystem search with cancellation and multi-threading.
4
+ // ============================================================================
5
+
6
+ #pragma once
7
+
8
+ #include <atomic>
9
+ #include <condition_variable>
10
+ #include <functional>
11
+ #include <memory>
12
+ #include <mutex>
13
+ #include <queue>
14
+ #include <string>
15
+ #include <thread>
16
+ #include <unordered_map>
17
+ #include <vector>
18
+
19
+ #include "filesystem_handler.h"
20
+
21
+ namespace wpp::fs {
22
+
23
+ // Thread pool for parallel file scanning
24
+ class ThreadPool {
25
+ public:
26
+ explicit ThreadPool(size_t num_threads = 0);
27
+ ~ThreadPool();
28
+
29
+ template<typename F>
30
+ void enqueue(F&& task) {
31
+ {
32
+ std::unique_lock<std::mutex> lock(mutex_);
33
+ tasks_.emplace(std::forward<F>(task));
34
+ }
35
+ cv_.notify_one();
36
+ }
37
+
38
+ void shutdown();
39
+
40
+ private:
41
+ void worker_thread();
42
+
43
+ std::vector<std::thread> workers_;
44
+ std::queue<std::function<void()>> tasks_;
45
+ std::mutex mutex_;
46
+ std::condition_variable cv_;
47
+ std::atomic<bool> stop_{false};
48
+ };
49
+
50
+ class FileSearchService {
51
+ public:
52
+ static FileSearchService& instance();
53
+
54
+ void configure_cache_root(const std::string& cacheRoot);
55
+
56
+ std::string start(const std::vector<std::string>& roots,
57
+ const SearchOptions& options,
58
+ StreamBatchCallback onBatch,
59
+ StreamDoneCallback onDone,
60
+ const std::string& preferredId = "");
61
+
62
+ void cancel(const std::string& searchId);
63
+
64
+ private:
65
+ struct SearchState {
66
+ std::string id;
67
+ std::vector<std::string> roots;
68
+ SearchOptions options;
69
+ StreamBatchCallback onBatch;
70
+ StreamDoneCallback onDone;
71
+ std::atomic<bool> cancelled{false};
72
+ std::atomic<uint64_t> matchedCount{0};
73
+ std::atomic<uint64_t> scannedCount{0};
74
+ std::atomic<size_t> activeWorkers{0};
75
+ };
76
+
77
+ std::mutex mutex_;
78
+ std::unordered_map<std::string, std::shared_ptr<SearchState>> searches_;
79
+ std::atomic<uint64_t> next_id_{1};
80
+
81
+ // Thread pool for parallel directory scanning
82
+ ThreadPool threadPool_;
83
+
84
+ FileSearchService();
85
+ ~FileSearchService();
86
+
87
+ void run_search(const std::shared_ptr<SearchState>& state);
88
+ void search_root(const std::shared_ptr<SearchState>& state, const std::string& root,
89
+ std::vector<std::string>& results, std::mutex& resultsMutex);
90
+ void finish_search(const std::shared_ptr<SearchState>& state,
91
+ const StreamSearchSummary& summary);
92
+ };
93
+
94
+ } // namespace wpp::fs
@@ -0,0 +1,161 @@
1
+ export interface InputKeyEvent {
2
+ type: 'keydown' | 'keyup';
3
+ key: string;
4
+ keyName: string;
5
+ keycode: number;
6
+ scancode: number;
7
+ ctrl: boolean;
8
+ shift: boolean;
9
+ alt: boolean;
10
+ meta: boolean;
11
+ repeat: boolean;
12
+ global: boolean;
13
+ windowId: string;
14
+ pressedKeys: string[];
15
+ }
16
+
17
+ export interface ShortcutEvent {
18
+ id: string;
19
+ event: InputKeyEvent;
20
+ }
21
+
22
+ interface PendingCall {
23
+ resolve: (value: any) => void;
24
+ reject: (reason: Error) => void;
25
+ }
26
+
27
+ let callId = 0;
28
+ const pending = new Map<string, PendingCall>();
29
+ const keyListeners = new Set<(event: InputKeyEvent) => void>();
30
+ const shortcutListeners = new Set<(event: ShortcutEvent) => void>();
31
+ let streamEnabled = false;
32
+
33
+ function getGlobal(): any {
34
+ if (typeof window !== 'undefined') return window;
35
+ if (typeof globalThis !== 'undefined') return globalThis;
36
+ return {};
37
+ }
38
+
39
+ const globalObject = getGlobal();
40
+ const previousResponse = globalObject.__response__;
41
+ const previousInputEvent = globalObject.__wpp_input_event__;
42
+
43
+ globalObject.__response__ = function(id: string, result: any, error?: string) {
44
+ const request = pending.get(id);
45
+ if (request) {
46
+ pending.delete(id);
47
+ if (error) {
48
+ request.reject(new Error(error));
49
+ } else {
50
+ request.resolve(result);
51
+ }
52
+ return;
53
+ }
54
+
55
+ if (previousResponse) {
56
+ previousResponse(id, result, error);
57
+ }
58
+ };
59
+
60
+ globalObject.__wpp_input_event__ = function(eventName: string, payload: any) {
61
+ if (eventName === 'key') {
62
+ keyListeners.forEach((listener) => listener(payload as InputKeyEvent));
63
+ } else if (eventName === 'shortcut') {
64
+ shortcutListeners.forEach((listener) => listener(payload as ShortcutEvent));
65
+ }
66
+
67
+ if (previousInputEvent) {
68
+ previousInputEvent(eventName, payload);
69
+ }
70
+ };
71
+
72
+ function invoke<T>(method: string, params: unknown[] = []): Promise<T> {
73
+ return new Promise<T>((resolve, reject) => {
74
+ const id = `input:${++callId}`;
75
+ pending.set(id, { resolve, reject });
76
+ const payload = JSON.stringify({ id, method, params });
77
+
78
+ if (globalObject.__native_invoke__) {
79
+ globalObject.__native_invoke__(payload);
80
+ return;
81
+ }
82
+
83
+ if (globalObject.chrome?.webview?.postMessage) {
84
+ globalObject.chrome.webview.postMessage(payload);
85
+ return;
86
+ }
87
+
88
+ pending.delete(id);
89
+ reject(new Error('Native bridge not available'));
90
+ });
91
+ }
92
+
93
+ async function ensureStreamEnabled() {
94
+ if (streamEnabled) {
95
+ return;
96
+ }
97
+ await invoke<void>('input:setEventStreamEnabled', [true]);
98
+ streamEnabled = true;
99
+ }
100
+
101
+ export async function onKeyEvent(listener: (event: InputKeyEvent) => void): Promise<() => void> {
102
+ await ensureStreamEnabled();
103
+ keyListeners.add(listener);
104
+ return () => {
105
+ keyListeners.delete(listener);
106
+ };
107
+ }
108
+
109
+ export function onShortcut(listener: (event: ShortcutEvent) => void): () => void {
110
+ shortcutListeners.add(listener);
111
+ return () => {
112
+ shortcutListeners.delete(listener);
113
+ };
114
+ }
115
+
116
+ export function getPressedKeys(): Promise<string[]> {
117
+ return invoke<string[]>('input:getPressedKeys');
118
+ }
119
+
120
+ export function isPressed(key: string): Promise<boolean> {
121
+ return invoke<boolean>('input:isPressed', [key]);
122
+ }
123
+
124
+ export function registerLocalShortcut(id: string, shortcut: string): Promise<void> {
125
+ return invoke<void>('input:registerLocalShortcut', [id, shortcut]);
126
+ }
127
+
128
+ export function registerAppShortcut(id: string, shortcut: string): Promise<void> {
129
+ return registerLocalShortcut(id, shortcut);
130
+ }
131
+
132
+ export function registerGlobalHotkey(id: string, shortcut: string): Promise<void> {
133
+ return invoke<void>('input:registerGlobalHotkey', [id, shortcut]);
134
+ }
135
+
136
+ export function registerGlobalShortcut(id: string, shortcut: string): Promise<void> {
137
+ return registerGlobalHotkey(id, shortcut);
138
+ }
139
+
140
+ export function unregisterShortcut(id: string): Promise<void> {
141
+ return invoke<void>('input:unregisterShortcut', [id]);
142
+ }
143
+
144
+ export function clearShortcuts(): Promise<void> {
145
+ return invoke<void>('input:clearShortcuts');
146
+ }
147
+
148
+ export const input = {
149
+ onKeyEvent,
150
+ onShortcut,
151
+ getPressedKeys,
152
+ isPressed,
153
+ registerAppShortcut,
154
+ registerLocalShortcut,
155
+ registerGlobalShortcut,
156
+ registerGlobalHotkey,
157
+ unregisterShortcut,
158
+ clearShortcuts,
159
+ };
160
+
161
+ export default input;