plusui-native-core 0.1.102 → 0.1.104
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.
- package/Core/Features/API/Connect_API.ts +62 -146
- package/Core/Features/API/filedrop-api.ts +12 -1
- package/Core/Features/API/index.ts +3 -4
- package/Core/Features/App/app.cpp +32 -32
- package/Core/Features/Connection/README.md +176 -52
- package/Core/Features/Connection/connect.ts +136 -21
- package/Core/Features/FileDrop/filedrop.ts +139 -36
- package/Core/Features/Keyboard/keyboard.cpp +218 -189
- package/Core/Features/Keyboard/keyboard.ts +100 -31
- package/Core/Features/Keyboard/keyboard_linux.cpp +226 -0
- package/Core/Features/Keyboard/keyboard_macos.cpp +220 -0
- package/Core/Features/Keyboard/keyboard_windows.cpp +56 -0
- package/Core/Features/Window/webview.cpp +248 -57
- package/Core/Features/Window/webview.ts +1 -1
- package/Core/Features/Window/window.cpp +433 -63
- package/Core/include/plusui/api.hpp +2 -2
- package/Core/include/plusui/app.hpp +41 -41
- package/Core/include/plusui/connect.hpp +125 -81
- package/Core/include/plusui/window.hpp +38 -40
- package/package.json +1 -1
|
@@ -1,189 +1,218 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
ShortcutManager
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
1
|
+
/**
|
|
2
|
+
* keyboard.cpp — Platform-unified keyboard implementation
|
|
3
|
+
*
|
|
4
|
+
* Platform-specific code lives in:
|
|
5
|
+
* keyboard_windows.cpp — Windows (Win32 / WebView2)
|
|
6
|
+
* keyboard_macos.cpp — macOS (Carbon / CGEvent)
|
|
7
|
+
* keyboard_linux.cpp — Linux (X11 / XGrabKey)
|
|
8
|
+
*
|
|
9
|
+
* Each platform file defines (inside namespace plusui):
|
|
10
|
+
* static bool areModsActiveImpl(int flags)
|
|
11
|
+
* static bool isKeyPressedImpl(int keyCode)
|
|
12
|
+
* static bool registerGlobalShortcutImpl(id, keyCode, mods, callback) [macOS/Linux]
|
|
13
|
+
* static bool unregisterGlobalShortcutImpl(id) [macOS/Linux]
|
|
14
|
+
* static void clearGlobalShortcutsImpl() [macOS/Linux]
|
|
15
|
+
*
|
|
16
|
+
* Windows global shortcuts are handled in window.cpp/webview.cpp via
|
|
17
|
+
* RegisterHotKey/WM_HOTKEY (requires the HWND which lives on Window::Impl).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
#include <iostream>
|
|
21
|
+
#include <plusui/keyboard.hpp>
|
|
22
|
+
#include <unordered_map>
|
|
23
|
+
|
|
24
|
+
// Pull in platform-specific helpers
|
|
25
|
+
#ifdef _WIN32
|
|
26
|
+
# include "keyboard_windows.cpp"
|
|
27
|
+
#elif defined(__APPLE__)
|
|
28
|
+
# include "keyboard_macos.cpp"
|
|
29
|
+
#else
|
|
30
|
+
# include "keyboard_linux.cpp"
|
|
31
|
+
#endif
|
|
32
|
+
|
|
33
|
+
namespace plusui {
|
|
34
|
+
|
|
35
|
+
// ── Keyboard::Impl ────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
struct Keyboard::Impl {
|
|
38
|
+
std::unordered_map<int, bool> pressedKeys;
|
|
39
|
+
std::vector<std::function<void(const KeyEvent&)>> keyDownCallbacks;
|
|
40
|
+
std::vector<std::function<void(const KeyEvent&)>> keyUpCallbacks;
|
|
41
|
+
bool autoRepeat = true;
|
|
42
|
+
|
|
43
|
+
static const std::unordered_map<KeyCode, std::string>& keyNames() {
|
|
44
|
+
static std::unordered_map<KeyCode, std::string> names = {
|
|
45
|
+
{KeyCode::Space, "Space"},
|
|
46
|
+
{KeyCode::Escape, "Escape"},
|
|
47
|
+
{KeyCode::Enter, "Enter"},
|
|
48
|
+
{KeyCode::Tab, "Tab"},
|
|
49
|
+
{KeyCode::Backspace, "Backspace"},
|
|
50
|
+
{KeyCode::Delete, "Delete"},
|
|
51
|
+
{KeyCode::Right, "Right"},
|
|
52
|
+
{KeyCode::Left, "Left"},
|
|
53
|
+
{KeyCode::Down, "Down"},
|
|
54
|
+
{KeyCode::Up, "Up"},
|
|
55
|
+
{KeyCode::A, "A"}, {KeyCode::B, "B"}, {KeyCode::C, "C"},
|
|
56
|
+
{KeyCode::D, "D"}, {KeyCode::E, "E"}, {KeyCode::F, "F"},
|
|
57
|
+
{KeyCode::G, "G"}, {KeyCode::H, "H"}, {KeyCode::I, "I"},
|
|
58
|
+
{KeyCode::J, "J"}, {KeyCode::K, "K"}, {KeyCode::L, "L"},
|
|
59
|
+
{KeyCode::M, "M"}, {KeyCode::N, "N"}, {KeyCode::O, "O"},
|
|
60
|
+
{KeyCode::P, "P"}, {KeyCode::Q, "Q"}, {KeyCode::R, "R"},
|
|
61
|
+
{KeyCode::S, "S"}, {KeyCode::T, "T"}, {KeyCode::U, "U"},
|
|
62
|
+
{KeyCode::V, "V"}, {KeyCode::W, "W"}, {KeyCode::X, "X"},
|
|
63
|
+
{KeyCode::Y, "Y"}, {KeyCode::Z, "Z"},
|
|
64
|
+
{KeyCode::F1, "F1"}, {KeyCode::F2, "F2"}, {KeyCode::F3, "F3"},
|
|
65
|
+
{KeyCode::F4, "F4"}, {KeyCode::F5, "F5"}, {KeyCode::F6, "F6"},
|
|
66
|
+
{KeyCode::F7, "F7"}, {KeyCode::F8, "F8"}, {KeyCode::F9, "F9"},
|
|
67
|
+
{KeyCode::F10, "F10"}, {KeyCode::F11, "F11"}, {KeyCode::F12, "F12"},
|
|
68
|
+
};
|
|
69
|
+
return names;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// ── Keyboard ──────────────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
Keyboard::Keyboard() : pImpl(std::make_unique<Impl>()) {}
|
|
76
|
+
Keyboard::~Keyboard() = default;
|
|
77
|
+
|
|
78
|
+
Keyboard& Keyboard::instance() {
|
|
79
|
+
static Keyboard instance;
|
|
80
|
+
return instance;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
void Keyboard::onKeyDown(std::function<void(const KeyEvent&)> callback) {
|
|
84
|
+
pImpl->keyDownCallbacks.push_back(callback);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
void Keyboard::onKeyUp(std::function<void(const KeyEvent&)> callback) {
|
|
88
|
+
pImpl->keyUpCallbacks.push_back(callback);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
bool Keyboard::isKeyPressed(KeyCode key) const {
|
|
92
|
+
// Prefer live query via platform helper over the cached map
|
|
93
|
+
return isKeyPressedImpl(static_cast<int>(key));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
bool Keyboard::areModsActive(KeyMod mods) const {
|
|
97
|
+
return areModsActiveImpl(static_cast<int>(mods));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
std::string Keyboard::getKeyName(KeyCode key) const {
|
|
101
|
+
auto it = Impl::keyNames().find(key);
|
|
102
|
+
return it != Impl::keyNames().end() ? it->second : "Unknown";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
KeyCode Keyboard::getKeyFromName(const std::string& name) const {
|
|
106
|
+
for (const auto& [key, keyName] : Impl::keyNames()) {
|
|
107
|
+
if (keyName == name) return key;
|
|
108
|
+
}
|
|
109
|
+
return KeyCode::Unknown;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
void Keyboard::setAutoRepeat(bool enabled) { pImpl->autoRepeat = enabled; }
|
|
113
|
+
bool Keyboard::getAutoRepeat() const { return pImpl->autoRepeat; }
|
|
114
|
+
|
|
115
|
+
// ── ShortcutManager ───────────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
struct ShortcutManager::Impl {
|
|
118
|
+
std::map<std::string, std::pair<Shortcut, std::function<void()>>> shortcuts;
|
|
119
|
+
std::function<void(const std::string&)> triggerCallback;
|
|
120
|
+
// callback to execute JS in the webview when a global shortcut fires
|
|
121
|
+
std::function<void(const std::string&)> shortcutFiredFn;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
ShortcutManager::ShortcutManager() : pImpl(std::make_unique<Impl>()) {}
|
|
125
|
+
ShortcutManager::~ShortcutManager() = default;
|
|
126
|
+
|
|
127
|
+
ShortcutManager& ShortcutManager::instance() {
|
|
128
|
+
static ShortcutManager instance;
|
|
129
|
+
return instance;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
bool ShortcutManager::registerShortcut(const std::string& id,
|
|
133
|
+
const Shortcut& shortcut,
|
|
134
|
+
std::function<void()> callback) {
|
|
135
|
+
pImpl->shortcuts[id] = {shortcut, callback};
|
|
136
|
+
|
|
137
|
+
#if defined(__APPLE__) || (!defined(_WIN32) && !defined(__APPLE__))
|
|
138
|
+
// On non-Windows platforms, register the hotkey natively here.
|
|
139
|
+
// The callback fires the JS shortcut event via shortcutFiredFn.
|
|
140
|
+
auto fired = pImpl->shortcutFiredFn;
|
|
141
|
+
return registerGlobalShortcutImpl(
|
|
142
|
+
id,
|
|
143
|
+
static_cast<int>(shortcut.key),
|
|
144
|
+
static_cast<int>(shortcut.mods),
|
|
145
|
+
[id, callback, fired]() {
|
|
146
|
+
if (callback) callback();
|
|
147
|
+
if (fired) fired(id);
|
|
148
|
+
});
|
|
149
|
+
#else
|
|
150
|
+
// Windows: hotkey registration is done in webview.cpp via RegisterHotKey
|
|
151
|
+
// (requires the HWND which is only available there). ShortcutManager
|
|
152
|
+
// just stores the callback so it can be invoked from the JS bridge.
|
|
153
|
+
return true;
|
|
154
|
+
#endif
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
bool ShortcutManager::unregisterShortcut(const std::string& id) {
|
|
158
|
+
bool removed = pImpl->shortcuts.erase(id) > 0;
|
|
159
|
+
#if defined(__APPLE__) || (!defined(_WIN32) && !defined(__APPLE__))
|
|
160
|
+
unregisterGlobalShortcutImpl(id);
|
|
161
|
+
#endif
|
|
162
|
+
return removed;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
void ShortcutManager::clearShortcuts() {
|
|
166
|
+
pImpl->shortcuts.clear();
|
|
167
|
+
#if defined(__APPLE__) || (!defined(_WIN32) && !defined(__APPLE__))
|
|
168
|
+
clearGlobalShortcutsImpl();
|
|
169
|
+
#endif
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
bool ShortcutManager::isRegistered(const std::string& id) const {
|
|
173
|
+
return pImpl->shortcuts.count(id) > 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
Shortcut ShortcutManager::getShortcut(const std::string& id) const {
|
|
177
|
+
auto it = pImpl->shortcuts.find(id);
|
|
178
|
+
return it != pImpl->shortcuts.end() ? it->second.first : Shortcut();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
void ShortcutManager::onShortcutTriggered(
|
|
182
|
+
std::function<void(const std::string&)> callback) {
|
|
183
|
+
pImpl->triggerCallback = callback;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ── Shortcut helpers ──────────────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
std::string Shortcut::toString() const {
|
|
189
|
+
std::string s;
|
|
190
|
+
if (mods & KeyMod::Control) s += "Ctrl+";
|
|
191
|
+
if (mods & KeyMod::Alt) s += "Alt+";
|
|
192
|
+
if (mods & KeyMod::Shift) s += "Shift+";
|
|
193
|
+
if (mods & KeyMod::Super) s += "Super+";
|
|
194
|
+
return s + std::to_string(static_cast<int>(key));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
Shortcut Shortcut::parse(const std::string& str) {
|
|
198
|
+
Shortcut shortcut;
|
|
199
|
+
KeyMod mods = KeyMod::None;
|
|
200
|
+
std::string key;
|
|
201
|
+
|
|
202
|
+
for (size_t i = 0; i < str.size(); ++i) {
|
|
203
|
+
if (str[i] == '+') {
|
|
204
|
+
if (key == "Ctrl" || key == "Control") mods = mods | KeyMod::Control;
|
|
205
|
+
else if (key == "Alt") mods = mods | KeyMod::Alt;
|
|
206
|
+
else if (key == "Shift") mods = mods | KeyMod::Shift;
|
|
207
|
+
else if (key == "Super" || key == "Win") mods = mods | KeyMod::Super;
|
|
208
|
+
key.clear();
|
|
209
|
+
} else {
|
|
210
|
+
key += str[i];
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
shortcut.mods = mods;
|
|
215
|
+
return shortcut;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
} // namespace plusui
|
|
@@ -14,6 +14,15 @@
|
|
|
14
14
|
export enum KeyCode {
|
|
15
15
|
Unknown = 0,
|
|
16
16
|
Space = 32,
|
|
17
|
+
// Digits 0–9
|
|
18
|
+
D0 = 48, D1 = 49, D2 = 50, D3 = 51, D4 = 52,
|
|
19
|
+
D5 = 53, D6 = 54, D7 = 55, D8 = 56, D9 = 57,
|
|
20
|
+
// Letters A–Z (GLFW convention: same as ASCII upper-case)
|
|
21
|
+
A = 65, B = 66, C = 67, D = 68, E = 69, F = 70,
|
|
22
|
+
G = 71, H = 72, I = 73, J = 74, K = 75, L = 76,
|
|
23
|
+
M = 77, N = 78, O = 79, P = 80, Q = 81, R = 82,
|
|
24
|
+
S = 83, T = 84, U = 85, V = 86, W = 87, X = 88,
|
|
25
|
+
Y = 89, Z = 90,
|
|
17
26
|
Escape = 256,
|
|
18
27
|
Enter = 257,
|
|
19
28
|
Tab = 258,
|
|
@@ -65,40 +74,79 @@ export interface Shortcut {
|
|
|
65
74
|
export type KeyEventCallback = (event: KeyEvent) => void;
|
|
66
75
|
export type ShortcutCallback = () => void;
|
|
67
76
|
|
|
68
|
-
|
|
69
|
-
let _event: ((event: string, callback: (...args: unknown[]) => void) => () => void) | null = null;
|
|
70
|
-
const shortcutHandlers: Map<string, ShortcutCallback> = new Map();
|
|
77
|
+
// ── Bridge (matches window.ts exactly) ───────────────────────────────────────
|
|
71
78
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
79
|
+
let callId = 0;
|
|
80
|
+
const pending = new Map<string, { resolve: (v: any) => void; reject: (e: any) => void }>();
|
|
75
81
|
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
function getGlobal(): any {
|
|
83
|
+
if (typeof window !== 'undefined') return window;
|
|
84
|
+
if (typeof globalThis !== 'undefined') return globalThis as any;
|
|
85
|
+
return {} as any;
|
|
78
86
|
}
|
|
79
87
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
const g = getGlobal();
|
|
89
|
+
|
|
90
|
+
// Merge __response__ handler — multiple modules may define it; guard so we
|
|
91
|
+
// don't overwrite a version already installed by window.ts.
|
|
92
|
+
if (!g.__response__) {
|
|
93
|
+
g.__response__ = function(id: string, result: any, error?: any) {
|
|
94
|
+
const p = pending.get(id);
|
|
95
|
+
if (p) {
|
|
96
|
+
pending.delete(id);
|
|
97
|
+
error ? p.reject(new Error(error)) : p.resolve(result);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
} else {
|
|
101
|
+
// Wrap the existing handler so both pending maps are served.
|
|
102
|
+
const existing = g.__response__;
|
|
103
|
+
g.__response__ = function(id: string, result: any, error?: any) {
|
|
104
|
+
existing(id, result, error);
|
|
105
|
+
const p = pending.get(id);
|
|
106
|
+
if (p) {
|
|
107
|
+
pending.delete(id);
|
|
108
|
+
error ? p.reject(new Error(error)) : p.resolve(result);
|
|
86
109
|
}
|
|
87
|
-
}
|
|
88
|
-
return _invoke!(name, args) as Promise<T>;
|
|
110
|
+
};
|
|
89
111
|
}
|
|
90
112
|
|
|
91
|
-
function
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
113
|
+
async function invoke<T>(method: string, params: any[] = []): Promise<T> {
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
const id = String(++callId) + '-kb';
|
|
116
|
+
pending.set(id, { resolve, reject });
|
|
117
|
+
|
|
118
|
+
const payload = JSON.stringify({ id, method, params });
|
|
119
|
+
|
|
120
|
+
if (g.__native_invoke__) {
|
|
121
|
+
g.__native_invoke__(payload);
|
|
122
|
+
} else if (g.chrome && g.chrome.webview && g.chrome.webview.postMessage) {
|
|
123
|
+
g.chrome.webview.postMessage(payload);
|
|
95
124
|
} else {
|
|
96
|
-
|
|
125
|
+
pending.delete(id);
|
|
126
|
+
console.warn('[PlusUI] ' + method + ' - native bridge not ready');
|
|
127
|
+
resolve(null as T);
|
|
97
128
|
}
|
|
98
|
-
}
|
|
99
|
-
return _event!(event, callback);
|
|
129
|
+
});
|
|
100
130
|
}
|
|
101
131
|
|
|
132
|
+
// ── onKeyDown / onKeyUp / onShortcut ─────────────────────────────────────────
|
|
133
|
+
// C++ fires these as CustomEvents on `window` via ExecuteScript:
|
|
134
|
+
// plusui:keyboard:keydown { detail: KeyEvent }
|
|
135
|
+
// plusui:keyboard:keyup { detail: KeyEvent }
|
|
136
|
+
// plusui:keyboard:shortcut { detail: { id: string } }
|
|
137
|
+
|
|
138
|
+
function onDomEvent<T>(
|
|
139
|
+
eventName: string,
|
|
140
|
+
callback: (data: T) => void
|
|
141
|
+
): () => void {
|
|
142
|
+
if (typeof window === 'undefined') return () => {};
|
|
143
|
+
const handler = (e: Event) => callback((e as CustomEvent<T>).detail);
|
|
144
|
+
window.addEventListener(eventName, handler as EventListener);
|
|
145
|
+
return () => window.removeEventListener(eventName, handler as EventListener);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ── Public API ────────────────────────────────────────────────────────────────
|
|
149
|
+
|
|
102
150
|
export const keyboard = {
|
|
103
151
|
async isKeyPressed(key: KeyCode): Promise<boolean> {
|
|
104
152
|
return invoke<boolean>('keyboard.isKeyPressed', [key]);
|
|
@@ -113,30 +161,40 @@ export const keyboard = {
|
|
|
113
161
|
},
|
|
114
162
|
|
|
115
163
|
async registerShortcut(id: string, shortcut: Shortcut, callback: ShortcutCallback): Promise<boolean> {
|
|
116
|
-
|
|
164
|
+
// Store the callback under the global handler table that WM_HOTKEY fires.
|
|
165
|
+
if (typeof window !== 'undefined') {
|
|
166
|
+
const w = window as any;
|
|
167
|
+
if (!w.__plusui_shortcut_handlers__) w.__plusui_shortcut_handlers__ = {};
|
|
168
|
+
w.__plusui_shortcut_handlers__[id] = callback;
|
|
169
|
+
}
|
|
117
170
|
return invoke<boolean>('keyboard.registerShortcut', [id, shortcut]);
|
|
118
171
|
},
|
|
119
172
|
|
|
120
173
|
async unregisterShortcut(id: string): Promise<boolean> {
|
|
121
|
-
|
|
174
|
+
if (typeof window !== 'undefined') {
|
|
175
|
+
const w = window as any;
|
|
176
|
+
if (w.__plusui_shortcut_handlers__) delete w.__plusui_shortcut_handlers__[id];
|
|
177
|
+
}
|
|
122
178
|
return invoke<boolean>('keyboard.unregisterShortcut', [id]);
|
|
123
179
|
},
|
|
124
180
|
|
|
125
181
|
async clearShortcuts(): Promise<void> {
|
|
126
|
-
|
|
182
|
+
if (typeof window !== 'undefined') {
|
|
183
|
+
(window as any).__plusui_shortcut_handlers__ = {};
|
|
184
|
+
}
|
|
127
185
|
await invoke('keyboard.clearShortcuts');
|
|
128
186
|
},
|
|
129
187
|
|
|
130
188
|
onKeyDown(callback: KeyEventCallback): () => void {
|
|
131
|
-
return
|
|
189
|
+
return onDomEvent<KeyEvent>('plusui:keyboard:keydown', callback);
|
|
132
190
|
},
|
|
133
191
|
|
|
134
192
|
onKeyUp(callback: KeyEventCallback): () => void {
|
|
135
|
-
return
|
|
193
|
+
return onDomEvent<KeyEvent>('plusui:keyboard:keyup', callback);
|
|
136
194
|
},
|
|
137
195
|
|
|
138
196
|
onShortcut(callback: (id: string) => void): () => void {
|
|
139
|
-
return
|
|
197
|
+
return onDomEvent<{ id: string }>('plusui:keyboard:shortcut', (d) => callback(d.id));
|
|
140
198
|
},
|
|
141
199
|
|
|
142
200
|
parseShortcut(str: string): Shortcut {
|
|
@@ -155,7 +213,7 @@ export const keyboard = {
|
|
|
155
213
|
} else if (trimmed === 'super' || trimmed === 'win' || trimmed === 'cmd') {
|
|
156
214
|
mods |= KeyMod.Super;
|
|
157
215
|
} else {
|
|
158
|
-
key =
|
|
216
|
+
key = keyboard.keyNameToCode(trimmed);
|
|
159
217
|
}
|
|
160
218
|
}
|
|
161
219
|
|
|
@@ -166,10 +224,13 @@ export const keyboard = {
|
|
|
166
224
|
const map: Record<string, KeyCode> = {
|
|
167
225
|
'space': KeyCode.Space,
|
|
168
226
|
'escape': KeyCode.Escape,
|
|
227
|
+
'esc': KeyCode.Escape,
|
|
169
228
|
'enter': KeyCode.Enter,
|
|
229
|
+
'return': KeyCode.Enter,
|
|
170
230
|
'tab': KeyCode.Tab,
|
|
171
231
|
'backspace': KeyCode.Backspace,
|
|
172
232
|
'delete': KeyCode.Delete,
|
|
233
|
+
'del': KeyCode.Delete,
|
|
173
234
|
'right': KeyCode.Right,
|
|
174
235
|
'left': KeyCode.Left,
|
|
175
236
|
'down': KeyCode.Down,
|
|
@@ -186,8 +247,16 @@ export const keyboard = {
|
|
|
186
247
|
'f10': KeyCode.F10,
|
|
187
248
|
'f11': KeyCode.F11,
|
|
188
249
|
'f12': KeyCode.F12,
|
|
250
|
+
// A–Z single letters (GLFW convention: A=65 … Z=90)
|
|
251
|
+
'a': KeyCode.A, 'b': KeyCode.B, 'c': KeyCode.C, 'd': KeyCode.D,
|
|
252
|
+
'e': KeyCode.E, 'f': KeyCode.F, 'g': KeyCode.G, 'h': KeyCode.H,
|
|
253
|
+
'i': KeyCode.I, 'j': KeyCode.J, 'k': KeyCode.K, 'l': KeyCode.L,
|
|
254
|
+
'm': KeyCode.M, 'n': KeyCode.N, 'o': KeyCode.O, 'p': KeyCode.P,
|
|
255
|
+
'q': KeyCode.Q, 'r': KeyCode.R, 's': KeyCode.S, 't': KeyCode.T,
|
|
256
|
+
'u': KeyCode.U, 'v': KeyCode.V, 'w': KeyCode.W, 'x': KeyCode.X,
|
|
257
|
+
'y': KeyCode.Y, 'z': KeyCode.Z,
|
|
189
258
|
};
|
|
190
|
-
return map[name]
|
|
259
|
+
return map[name] ?? KeyCode.Unknown;
|
|
191
260
|
},
|
|
192
261
|
};
|
|
193
262
|
|