@matjash/pixi-native-linux-x64 0.1.2 → 0.2.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.
- package/THIRD_PARTY_NOTICES.md +7 -6
- package/index.cjs +1 -0
- package/native/audio/binding-path.js +16 -0
- package/native/audio/dist/linux-x64/native_audio.node +0 -0
- package/native/audio/package.json +7 -0
- package/native/audio/src/audio_impl.rs +1348 -0
- package/native/audio/src/index.d.ts +63 -0
- package/native/audio/src/index.js +5 -0
- package/native/audio/src/lib.rs +5 -0
- package/native/gpu/dist/linux-x64/pixi_native_gpu.node +0 -0
- package/native/gpu/package.json +20 -20
- package/native/gpu/src/binding-path.js +13 -9
- package/native/gpu/src/index.d.ts +430 -683
- package/native/gpu/src/index.js +34 -33
- package/native/video/package.json +5 -5
- package/native/video/src/binding-path.js +16 -7
- package/native/video/src/index.d.ts +28 -28
- package/native/video/src/index.js +39 -39
- package/native/video/src/lib.rs +34 -7
- package/native/window/binding-path.js +9 -3
- package/native/window/dist/linux-x64/libSDL3.so.0 +0 -0
- package/native/window/dist/linux-x64/native_window.node +0 -0
- package/native/window/package.json +5 -5
- package/native/window/src/index.d.ts +54 -8
- package/native/window/src/index.js +80 -11
- package/native/window/src/key-mapping.js +32 -0
- package/native/window/src/lib.rs +658 -46
- package/package.json +1 -1
package/native/window/src/lib.rs
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
use std::ffi::{CStr, CString};
|
|
2
|
+
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
3
|
+
|
|
1
4
|
use napi::bindgen_prelude::FunctionRef;
|
|
2
5
|
use napi::bindgen_prelude::*;
|
|
3
6
|
use napi_derive::napi;
|
|
7
|
+
use sdl3_sys::everything::*;
|
|
4
8
|
|
|
5
9
|
#[cfg(windows)]
|
|
6
10
|
use windows_sys::Win32::Foundation::FreeLibrary;
|
|
@@ -21,19 +25,651 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{
|
|
|
21
25
|
WM_EXITSIZEMOVE, WM_TIMER, WNDPROC,
|
|
22
26
|
};
|
|
23
27
|
|
|
28
|
+
static WINDOW_COUNT: AtomicUsize = AtomicUsize::new(0);
|
|
29
|
+
const DEFAULT_COMPOSITOR_WAIT_MS: u32 = 1_000;
|
|
30
|
+
|
|
31
|
+
#[napi(object)]
|
|
32
|
+
pub struct NativeWindowOptions {
|
|
33
|
+
pub title: String,
|
|
34
|
+
pub width: i32,
|
|
35
|
+
pub height: i32,
|
|
36
|
+
pub resizable: bool,
|
|
37
|
+
pub borderless: bool,
|
|
38
|
+
pub transparent: bool,
|
|
39
|
+
pub x: Option<i32>,
|
|
40
|
+
pub y: Option<i32>,
|
|
41
|
+
pub graphics: String,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#[napi(object)]
|
|
45
|
+
pub struct NativeSurfaceDescriptor {
|
|
46
|
+
pub version: u32,
|
|
47
|
+
pub api: String,
|
|
48
|
+
pub window: Buffer,
|
|
49
|
+
pub display: Option<Buffer>,
|
|
50
|
+
pub instance: Option<Buffer>,
|
|
51
|
+
pub egl_window: Option<Buffer>,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#[napi(object)]
|
|
55
|
+
pub struct NativeWindowEvent {
|
|
56
|
+
pub kind: String,
|
|
57
|
+
pub window_id: u32,
|
|
58
|
+
pub x: Option<f64>,
|
|
59
|
+
pub y: Option<f64>,
|
|
60
|
+
pub dx: Option<f64>,
|
|
61
|
+
pub dy: Option<f64>,
|
|
62
|
+
pub button: Option<u32>,
|
|
63
|
+
pub key: Option<String>,
|
|
64
|
+
pub scancode: Option<u32>,
|
|
65
|
+
pub repeat: Option<bool>,
|
|
66
|
+
pub shift: Option<bool>,
|
|
67
|
+
pub ctrl: Option<bool>,
|
|
68
|
+
pub alt: Option<bool>,
|
|
69
|
+
pub super_key: Option<bool>,
|
|
70
|
+
pub flipped: Option<bool>,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fn sdl_error(context: &str) -> Error {
|
|
74
|
+
let message = unsafe {
|
|
75
|
+
let error = SDL_GetError();
|
|
76
|
+
if error.is_null() {
|
|
77
|
+
"unknown SDL error".to_owned()
|
|
78
|
+
} else {
|
|
79
|
+
CStr::from_ptr(error).to_string_lossy().into_owned()
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
Error::from_reason(format!("{context}: {message}"))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
fn pointer_buffer(pointer: usize) -> Buffer {
|
|
86
|
+
Buffer::from(pointer.to_ne_bytes().to_vec())
|
|
87
|
+
}
|
|
88
|
+
|
|
24
89
|
#[cfg(windows)]
|
|
25
90
|
fn hwnd_from_native_data(native_data: &[u8]) -> Result<HWND> {
|
|
26
91
|
if native_data.len() < std::mem::size_of::<usize>() {
|
|
27
92
|
return Err(Error::from_reason("native window data is invalid"));
|
|
28
93
|
}
|
|
29
|
-
let
|
|
94
|
+
let mut bytes = [0_u8; std::mem::size_of::<usize>()];
|
|
95
|
+
bytes.copy_from_slice(&native_data[..std::mem::size_of::<usize>()]);
|
|
96
|
+
let hwnd = usize::from_ne_bytes(bytes) as HWND;
|
|
30
97
|
if hwnd.is_null() {
|
|
31
98
|
return Err(Error::from_reason("native window handle is null"));
|
|
32
99
|
}
|
|
33
100
|
Ok(hwnd)
|
|
34
101
|
}
|
|
35
102
|
|
|
36
|
-
|
|
103
|
+
fn current_video_driver() -> String {
|
|
104
|
+
unsafe {
|
|
105
|
+
let driver = SDL_GetCurrentVideoDriver();
|
|
106
|
+
if driver.is_null() {
|
|
107
|
+
"unknown".to_owned()
|
|
108
|
+
} else {
|
|
109
|
+
CStr::from_ptr(driver).to_string_lossy().into_owned()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fn set_bool_property(properties: SDL_PropertiesID, name: *const i8, value: bool) -> Result<()> {
|
|
115
|
+
if unsafe { SDL_SetBooleanProperty(properties, name, value) } {
|
|
116
|
+
Ok(())
|
|
117
|
+
} else {
|
|
118
|
+
Err(sdl_error("could not set SDL window property"))
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fn set_number_property(properties: SDL_PropertiesID, name: *const i8, value: i64) -> Result<()> {
|
|
123
|
+
if unsafe { SDL_SetNumberProperty(properties, name, value) } {
|
|
124
|
+
Ok(())
|
|
125
|
+
} else {
|
|
126
|
+
Err(sdl_error("could not set SDL window property"))
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
#[napi]
|
|
131
|
+
pub struct NativeSdlWindow {
|
|
132
|
+
window: usize,
|
|
133
|
+
gl_context: usize,
|
|
134
|
+
id: u32,
|
|
135
|
+
transparent: bool,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#[napi]
|
|
139
|
+
impl NativeSdlWindow {
|
|
140
|
+
#[napi(constructor)]
|
|
141
|
+
pub fn new(options: NativeWindowOptions) -> Result<Self> {
|
|
142
|
+
if options.width <= 0 || options.height <= 0 {
|
|
143
|
+
return Err(Error::from_reason(
|
|
144
|
+
"window width and height must be positive",
|
|
145
|
+
));
|
|
146
|
+
}
|
|
147
|
+
if options.graphics != "webgpu" && options.graphics != "webgl" {
|
|
148
|
+
return Err(Error::from_reason("graphics must be webgpu or webgl"));
|
|
149
|
+
}
|
|
150
|
+
let use_webgl = options.graphics == "webgl";
|
|
151
|
+
if use_webgl {
|
|
152
|
+
// Keep SDL's context on the same GLES/EGL implementation as the
|
|
153
|
+
// generated native-gles call table (ANGLE on Windows).
|
|
154
|
+
unsafe {
|
|
155
|
+
SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, c"1".as_ptr());
|
|
156
|
+
SDL_SetHint(SDL_HINT_VIDEO_FORCE_EGL, c"1".as_ptr());
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if WINDOW_COUNT.load(Ordering::SeqCst) == 0 && !unsafe { SDL_Init(SDL_INIT_VIDEO) } {
|
|
160
|
+
return Err(sdl_error("could not initialize SDL3 video"));
|
|
161
|
+
}
|
|
162
|
+
// SDL's X11 backend only asks EGL for a transparent ARGB visual when
|
|
163
|
+
// the window has the OpenGL flag. WebGPU still owns the external
|
|
164
|
+
// Vulkan context; this flag is used only for SDL's visual selection.
|
|
165
|
+
let use_x11_transparent_visual = cfg!(target_os = "linux")
|
|
166
|
+
&& !use_webgl
|
|
167
|
+
&& options.transparent
|
|
168
|
+
&& current_video_driver() == "x11";
|
|
169
|
+
if use_x11_transparent_visual {
|
|
170
|
+
unsafe {
|
|
171
|
+
SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, c"1".as_ptr());
|
|
172
|
+
SDL_SetHint(SDL_HINT_VIDEO_FORCE_EGL, c"1".as_ptr());
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if use_webgl || use_x11_transparent_visual {
|
|
176
|
+
for (attribute, value) in [
|
|
177
|
+
(SDL_GL_CONTEXT_MAJOR_VERSION, 3),
|
|
178
|
+
(SDL_GL_CONTEXT_MINOR_VERSION, 0),
|
|
179
|
+
(
|
|
180
|
+
SDL_GL_CONTEXT_PROFILE_MASK,
|
|
181
|
+
SDL_GL_CONTEXT_PROFILE_ES.0 as i32,
|
|
182
|
+
),
|
|
183
|
+
(SDL_GL_ALPHA_SIZE, 8),
|
|
184
|
+
(SDL_GL_DEPTH_SIZE, 24),
|
|
185
|
+
(SDL_GL_STENCIL_SIZE, 8),
|
|
186
|
+
(SDL_GL_DOUBLEBUFFER, 1),
|
|
187
|
+
(SDL_GL_EGL_PLATFORM, 1),
|
|
188
|
+
] {
|
|
189
|
+
if !unsafe { SDL_GL_SetAttribute(attribute, value) } {
|
|
190
|
+
return Err(sdl_error("could not configure the SDL3 OpenGL ES context"));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let properties = unsafe { SDL_CreateProperties() };
|
|
196
|
+
if properties == 0 {
|
|
197
|
+
return Err(sdl_error("could not allocate SDL3 window properties"));
|
|
198
|
+
}
|
|
199
|
+
let title = CString::new(options.title)
|
|
200
|
+
.map_err(|_| Error::from_reason("window title contains a null byte"))?;
|
|
201
|
+
|
|
202
|
+
let result = (|| {
|
|
203
|
+
if !unsafe {
|
|
204
|
+
SDL_SetStringProperty(
|
|
205
|
+
properties,
|
|
206
|
+
SDL_PROP_WINDOW_CREATE_TITLE_STRING,
|
|
207
|
+
title.as_ptr(),
|
|
208
|
+
)
|
|
209
|
+
} {
|
|
210
|
+
return Err(sdl_error("could not set SDL window title"));
|
|
211
|
+
}
|
|
212
|
+
set_number_property(
|
|
213
|
+
properties,
|
|
214
|
+
SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER,
|
|
215
|
+
options.width.into(),
|
|
216
|
+
)?;
|
|
217
|
+
set_number_property(
|
|
218
|
+
properties,
|
|
219
|
+
SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER,
|
|
220
|
+
options.height.into(),
|
|
221
|
+
)?;
|
|
222
|
+
set_bool_property(
|
|
223
|
+
properties,
|
|
224
|
+
SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN,
|
|
225
|
+
options.resizable,
|
|
226
|
+
)?;
|
|
227
|
+
set_bool_property(
|
|
228
|
+
properties,
|
|
229
|
+
SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN,
|
|
230
|
+
options.borderless,
|
|
231
|
+
)?;
|
|
232
|
+
set_bool_property(
|
|
233
|
+
properties,
|
|
234
|
+
SDL_PROP_WINDOW_CREATE_TRANSPARENT_BOOLEAN,
|
|
235
|
+
options.transparent,
|
|
236
|
+
)?;
|
|
237
|
+
set_bool_property(
|
|
238
|
+
properties,
|
|
239
|
+
SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN,
|
|
240
|
+
true,
|
|
241
|
+
)?;
|
|
242
|
+
if use_webgl || use_x11_transparent_visual {
|
|
243
|
+
set_bool_property(properties, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true)?;
|
|
244
|
+
}
|
|
245
|
+
if !use_webgl {
|
|
246
|
+
set_bool_property(
|
|
247
|
+
properties,
|
|
248
|
+
SDL_PROP_WINDOW_CREATE_EXTERNAL_GRAPHICS_CONTEXT_BOOLEAN,
|
|
249
|
+
true,
|
|
250
|
+
)?;
|
|
251
|
+
}
|
|
252
|
+
if let (Some(x), Some(y)) = (options.x, options.y) {
|
|
253
|
+
set_number_property(properties, SDL_PROP_WINDOW_CREATE_X_NUMBER, x.into())?;
|
|
254
|
+
set_number_property(properties, SDL_PROP_WINDOW_CREATE_Y_NUMBER, y.into())?;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let window = unsafe { SDL_CreateWindowWithProperties(properties) };
|
|
258
|
+
if window.is_null() {
|
|
259
|
+
return Err(sdl_error("could not create SDL3 window"));
|
|
260
|
+
}
|
|
261
|
+
let id = unsafe { SDL_GetWindowID(window) };
|
|
262
|
+
if id == 0 {
|
|
263
|
+
unsafe { SDL_DestroyWindow(window) };
|
|
264
|
+
return Err(sdl_error("SDL3 window has no id"));
|
|
265
|
+
}
|
|
266
|
+
let gl_context = if use_webgl {
|
|
267
|
+
let context = unsafe { SDL_GL_CreateContext(window) };
|
|
268
|
+
if context.is_null() {
|
|
269
|
+
unsafe { SDL_DestroyWindow(window) };
|
|
270
|
+
return Err(sdl_error("could not create the SDL3 OpenGL ES context"));
|
|
271
|
+
}
|
|
272
|
+
if !unsafe { SDL_GL_MakeCurrent(window, context) } {
|
|
273
|
+
unsafe {
|
|
274
|
+
SDL_GL_DestroyContext(context);
|
|
275
|
+
SDL_DestroyWindow(window);
|
|
276
|
+
}
|
|
277
|
+
return Err(sdl_error(
|
|
278
|
+
"could not make the SDL3 OpenGL ES context current",
|
|
279
|
+
));
|
|
280
|
+
}
|
|
281
|
+
context as usize
|
|
282
|
+
} else {
|
|
283
|
+
0
|
|
284
|
+
};
|
|
285
|
+
let actual_transparent =
|
|
286
|
+
unsafe { (SDL_GetWindowFlags(window).0 & SDL_WINDOW_TRANSPARENT.0) != 0 };
|
|
287
|
+
Ok(Self {
|
|
288
|
+
window: window as usize,
|
|
289
|
+
gl_context,
|
|
290
|
+
id: id.into(),
|
|
291
|
+
transparent: actual_transparent,
|
|
292
|
+
})
|
|
293
|
+
})();
|
|
294
|
+
unsafe { SDL_DestroyProperties(properties) };
|
|
295
|
+
|
|
296
|
+
match result {
|
|
297
|
+
Ok(window) => {
|
|
298
|
+
WINDOW_COUNT.fetch_add(1, Ordering::SeqCst);
|
|
299
|
+
Ok(window)
|
|
300
|
+
}
|
|
301
|
+
Err(error) => {
|
|
302
|
+
if WINDOW_COUNT.load(Ordering::SeqCst) == 0 {
|
|
303
|
+
unsafe { SDL_QuitSubSystem(SDL_INIT_VIDEO) };
|
|
304
|
+
}
|
|
305
|
+
Err(error)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
#[napi(getter)]
|
|
311
|
+
pub fn id(&self) -> u32 {
|
|
312
|
+
self.id
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
#[napi(getter)]
|
|
316
|
+
pub fn destroyed(&self) -> bool {
|
|
317
|
+
self.window == 0
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
#[napi(getter)]
|
|
321
|
+
pub fn video_driver(&self) -> String {
|
|
322
|
+
current_video_driver()
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
#[napi(getter)]
|
|
326
|
+
pub fn transparent(&self) -> bool {
|
|
327
|
+
self.transparent
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
#[napi(getter)]
|
|
331
|
+
pub fn x(&self) -> Result<i32> {
|
|
332
|
+
Ok(self.position()?.0)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
#[napi(getter)]
|
|
336
|
+
pub fn y(&self) -> Result<i32> {
|
|
337
|
+
Ok(self.position()?.1)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
#[napi(getter)]
|
|
341
|
+
pub fn pixel_width(&self) -> Result<i32> {
|
|
342
|
+
Ok(self.pixel_size()?.0)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
#[napi(getter)]
|
|
346
|
+
pub fn pixel_height(&self) -> Result<i32> {
|
|
347
|
+
Ok(self.pixel_size()?.1)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
#[napi(getter)]
|
|
351
|
+
pub fn refresh_rate(&self) -> f64 {
|
|
352
|
+
let window = self.window_pointer();
|
|
353
|
+
if window.is_null() {
|
|
354
|
+
return 60.0;
|
|
355
|
+
}
|
|
356
|
+
unsafe {
|
|
357
|
+
let mode = SDL_GetCurrentDisplayMode(SDL_GetDisplayForWindow(window));
|
|
358
|
+
if mode.is_null() || (*mode).refresh_rate <= 0.0 {
|
|
359
|
+
60.0
|
|
360
|
+
} else {
|
|
361
|
+
(*mode).refresh_rate.into()
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
#[napi(getter)]
|
|
367
|
+
pub fn surface(&self) -> Result<NativeSurfaceDescriptor> {
|
|
368
|
+
let properties = unsafe { SDL_GetWindowProperties(self.require_window()?) };
|
|
369
|
+
if properties == 0 {
|
|
370
|
+
return Err(sdl_error("could not read SDL3 window properties"));
|
|
371
|
+
}
|
|
372
|
+
let driver = current_video_driver();
|
|
373
|
+
let pointer = |name| unsafe {
|
|
374
|
+
SDL_GetPointerProperty(properties, name, std::ptr::null_mut()) as usize
|
|
375
|
+
};
|
|
376
|
+
let number = |name| unsafe { SDL_GetNumberProperty(properties, name, 0) as usize };
|
|
377
|
+
match driver.as_str() {
|
|
378
|
+
"windows" => {
|
|
379
|
+
let hwnd = pointer(SDL_PROP_WINDOW_WIN32_HWND_POINTER);
|
|
380
|
+
let instance = pointer(SDL_PROP_WINDOW_WIN32_INSTANCE_POINTER);
|
|
381
|
+
if hwnd == 0 || instance == 0 {
|
|
382
|
+
return Err(sdl_error("SDL3 did not expose Win32 window handles"));
|
|
383
|
+
}
|
|
384
|
+
Ok(NativeSurfaceDescriptor {
|
|
385
|
+
version: 1,
|
|
386
|
+
api: "win32".to_owned(),
|
|
387
|
+
window: pointer_buffer(hwnd),
|
|
388
|
+
display: None,
|
|
389
|
+
instance: Some(pointer_buffer(instance)),
|
|
390
|
+
egl_window: None,
|
|
391
|
+
})
|
|
392
|
+
}
|
|
393
|
+
"x11" => {
|
|
394
|
+
let display = pointer(SDL_PROP_WINDOW_X11_DISPLAY_POINTER);
|
|
395
|
+
let xwindow = number(SDL_PROP_WINDOW_X11_WINDOW_NUMBER);
|
|
396
|
+
if display == 0 || xwindow == 0 {
|
|
397
|
+
return Err(sdl_error("SDL3 did not expose X11 window handles"));
|
|
398
|
+
}
|
|
399
|
+
Ok(NativeSurfaceDescriptor {
|
|
400
|
+
version: 1,
|
|
401
|
+
api: "x11".to_owned(),
|
|
402
|
+
window: pointer_buffer(xwindow),
|
|
403
|
+
display: Some(pointer_buffer(display)),
|
|
404
|
+
instance: None,
|
|
405
|
+
egl_window: None,
|
|
406
|
+
})
|
|
407
|
+
}
|
|
408
|
+
"wayland" => {
|
|
409
|
+
let display = pointer(SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER);
|
|
410
|
+
let surface = pointer(SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER);
|
|
411
|
+
let egl_window = pointer(SDL_PROP_WINDOW_WAYLAND_EGL_WINDOW_POINTER);
|
|
412
|
+
if display == 0 || surface == 0 {
|
|
413
|
+
return Err(sdl_error("SDL3 did not expose Wayland window handles"));
|
|
414
|
+
}
|
|
415
|
+
Ok(NativeSurfaceDescriptor {
|
|
416
|
+
version: 1,
|
|
417
|
+
api: "wayland".to_owned(),
|
|
418
|
+
window: pointer_buffer(surface),
|
|
419
|
+
display: Some(pointer_buffer(display)),
|
|
420
|
+
instance: None,
|
|
421
|
+
egl_window: (egl_window != 0).then(|| pointer_buffer(egl_window)),
|
|
422
|
+
})
|
|
423
|
+
}
|
|
424
|
+
_ => Err(Error::from_reason(format!(
|
|
425
|
+
"unsupported SDL3 video driver: {driver}"
|
|
426
|
+
))),
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
#[napi]
|
|
431
|
+
pub fn set_position(&self, x: i32, y: i32) -> Result<bool> {
|
|
432
|
+
if current_video_driver() == "wayland" {
|
|
433
|
+
return Ok(false);
|
|
434
|
+
}
|
|
435
|
+
if unsafe { SDL_SetWindowPosition(self.require_window()?, x, y) } {
|
|
436
|
+
Ok(true)
|
|
437
|
+
} else {
|
|
438
|
+
Err(sdl_error("could not move SDL3 window"))
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
#[napi]
|
|
443
|
+
pub fn minimize(&self) -> Result<()> {
|
|
444
|
+
self.window_action(SDL_MinimizeWindow, "could not minimize SDL3 window")
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
#[napi]
|
|
448
|
+
pub fn maximize(&self) -> Result<()> {
|
|
449
|
+
self.window_action(SDL_MaximizeWindow, "could not maximize SDL3 window")
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
#[napi]
|
|
453
|
+
pub fn restore(&self) -> Result<()> {
|
|
454
|
+
self.window_action(SDL_RestoreWindow, "could not restore SDL3 window")
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
#[napi]
|
|
458
|
+
pub fn make_gl_current(&self) -> Result<bool> {
|
|
459
|
+
if self.gl_context == 0 {
|
|
460
|
+
return Err(Error::from_reason("window has no SDL3 OpenGL context"));
|
|
461
|
+
}
|
|
462
|
+
Ok(unsafe { SDL_GL_MakeCurrent(self.require_window()?, self.gl_context as SDL_GLContext) })
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
#[napi]
|
|
466
|
+
pub fn set_gl_swap_interval(&self, interval: i32) -> Result<bool> {
|
|
467
|
+
self.make_gl_current()?;
|
|
468
|
+
Ok(unsafe { SDL_GL_SetSwapInterval(interval) })
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
#[napi]
|
|
472
|
+
pub fn swap_gl(&self) -> Result<bool> {
|
|
473
|
+
self.make_gl_current()?;
|
|
474
|
+
Ok(unsafe { SDL_GL_SwapWindow(self.require_window()?) })
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
#[napi]
|
|
478
|
+
pub fn gl_version(&self) -> Result<String> {
|
|
479
|
+
self.make_gl_current()?;
|
|
480
|
+
type GetString = unsafe extern "C" fn(u32) -> *const u8;
|
|
481
|
+
let name = c"glGetString";
|
|
482
|
+
let procedure = unsafe { SDL_GL_GetProcAddress(name.as_ptr()) }
|
|
483
|
+
.ok_or_else(|| Error::from_reason("SDL3 did not load glGetString"))?;
|
|
484
|
+
let get_string =
|
|
485
|
+
unsafe { std::mem::transmute::<unsafe extern "C" fn(), GetString>(procedure) };
|
|
486
|
+
let version = unsafe { get_string(0x1F02) };
|
|
487
|
+
if version.is_null() {
|
|
488
|
+
return Err(Error::from_reason("SDL3 glGetString returned null"));
|
|
489
|
+
}
|
|
490
|
+
Ok(unsafe { CStr::from_ptr(version.cast()) }
|
|
491
|
+
.to_string_lossy()
|
|
492
|
+
.into_owned())
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
#[napi]
|
|
496
|
+
pub fn destroy(&mut self) {
|
|
497
|
+
self.destroy_inner();
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
fn window_pointer(&self) -> *mut SDL_Window {
|
|
501
|
+
self.window as *mut SDL_Window
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
fn require_window(&self) -> Result<*mut SDL_Window> {
|
|
505
|
+
let window = self.window_pointer();
|
|
506
|
+
if window.is_null() {
|
|
507
|
+
Err(Error::from_reason("native window is destroyed"))
|
|
508
|
+
} else {
|
|
509
|
+
Ok(window)
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
fn position(&self) -> Result<(i32, i32)> {
|
|
514
|
+
let (mut x, mut y) = (0, 0);
|
|
515
|
+
if unsafe { SDL_GetWindowPosition(self.require_window()?, &mut x, &mut y) } {
|
|
516
|
+
Ok((x, y))
|
|
517
|
+
} else {
|
|
518
|
+
Err(sdl_error("could not query SDL3 window position"))
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
fn pixel_size(&self) -> Result<(i32, i32)> {
|
|
523
|
+
let (mut width, mut height) = (0, 0);
|
|
524
|
+
if unsafe { SDL_GetWindowSizeInPixels(self.require_window()?, &mut width, &mut height) } {
|
|
525
|
+
Ok((width, height))
|
|
526
|
+
} else {
|
|
527
|
+
Err(sdl_error("could not query SDL3 window pixel size"))
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
fn window_action(
|
|
532
|
+
&self,
|
|
533
|
+
action: unsafe extern "C" fn(*mut SDL_Window) -> bool,
|
|
534
|
+
context: &str,
|
|
535
|
+
) -> Result<()> {
|
|
536
|
+
if unsafe { action(self.require_window()?) } {
|
|
537
|
+
Ok(())
|
|
538
|
+
} else {
|
|
539
|
+
Err(sdl_error(context))
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
fn destroy_inner(&mut self) {
|
|
544
|
+
if self.window == 0 {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if self.gl_context != 0 {
|
|
548
|
+
unsafe { SDL_GL_DestroyContext(self.gl_context as SDL_GLContext) };
|
|
549
|
+
self.gl_context = 0;
|
|
550
|
+
}
|
|
551
|
+
unsafe { SDL_DestroyWindow(self.window_pointer()) };
|
|
552
|
+
self.window = 0;
|
|
553
|
+
if WINDOW_COUNT.fetch_sub(1, Ordering::SeqCst) == 1 {
|
|
554
|
+
unsafe { SDL_QuitSubSystem(SDL_INIT_VIDEO) };
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
impl Drop for NativeSdlWindow {
|
|
560
|
+
fn drop(&mut self) {
|
|
561
|
+
self.destroy_inner();
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
fn empty_event(kind: &str, window_id: u32) -> NativeWindowEvent {
|
|
566
|
+
NativeWindowEvent {
|
|
567
|
+
kind: kind.to_owned(),
|
|
568
|
+
window_id,
|
|
569
|
+
x: None,
|
|
570
|
+
y: None,
|
|
571
|
+
dx: None,
|
|
572
|
+
dy: None,
|
|
573
|
+
button: None,
|
|
574
|
+
key: None,
|
|
575
|
+
scancode: None,
|
|
576
|
+
repeat: None,
|
|
577
|
+
shift: None,
|
|
578
|
+
ctrl: None,
|
|
579
|
+
alt: None,
|
|
580
|
+
super_key: None,
|
|
581
|
+
flipped: None,
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/// Drains SDL's process-wide queue. JavaScript routes each event by window id.
|
|
586
|
+
#[napi]
|
|
587
|
+
pub fn poll_events() -> Vec<NativeWindowEvent> {
|
|
588
|
+
let mut events = Vec::new();
|
|
589
|
+
let mut raw = SDL_Event::default();
|
|
590
|
+
while unsafe { SDL_PollEvent(&mut raw) } {
|
|
591
|
+
let event_type = unsafe { raw.r#type };
|
|
592
|
+
if event_type == SDL_EVENT_QUIT.0 {
|
|
593
|
+
events.push(empty_event("quit", 0));
|
|
594
|
+
} else if event_type == SDL_EVENT_WINDOW_CLOSE_REQUESTED.0 {
|
|
595
|
+
events.push(empty_event("close", unsafe { raw.window.windowID.into() }));
|
|
596
|
+
} else if event_type == SDL_EVENT_WINDOW_RESIZED.0
|
|
597
|
+
|| event_type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED.0
|
|
598
|
+
{
|
|
599
|
+
events.push(empty_event("resize", unsafe { raw.window.windowID.into() }));
|
|
600
|
+
} else if event_type == SDL_EVENT_WINDOW_MOVED.0 {
|
|
601
|
+
let raw = unsafe { raw.window };
|
|
602
|
+
let mut event = empty_event("move", raw.windowID.into());
|
|
603
|
+
event.x = Some(raw.data1.into());
|
|
604
|
+
event.y = Some(raw.data2.into());
|
|
605
|
+
events.push(event);
|
|
606
|
+
} else if event_type == SDL_EVENT_WINDOW_MINIMIZED.0 {
|
|
607
|
+
events.push(empty_event("minimize", unsafe {
|
|
608
|
+
raw.window.windowID.into()
|
|
609
|
+
}));
|
|
610
|
+
} else if event_type == SDL_EVENT_WINDOW_MAXIMIZED.0 {
|
|
611
|
+
events.push(empty_event("maximize", unsafe {
|
|
612
|
+
raw.window.windowID.into()
|
|
613
|
+
}));
|
|
614
|
+
} else if event_type == SDL_EVENT_WINDOW_RESTORED.0 {
|
|
615
|
+
events.push(empty_event("restore", unsafe {
|
|
616
|
+
raw.window.windowID.into()
|
|
617
|
+
}));
|
|
618
|
+
} else if event_type == SDL_EVENT_MOUSE_MOTION.0 {
|
|
619
|
+
let raw = unsafe { raw.motion };
|
|
620
|
+
let mut event = empty_event("mouseMove", raw.windowID.into());
|
|
621
|
+
event.x = Some(raw.x.into());
|
|
622
|
+
event.y = Some(raw.y.into());
|
|
623
|
+
events.push(event);
|
|
624
|
+
} else if event_type == SDL_EVENT_MOUSE_BUTTON_DOWN.0
|
|
625
|
+
|| event_type == SDL_EVENT_MOUSE_BUTTON_UP.0
|
|
626
|
+
{
|
|
627
|
+
let raw = unsafe { raw.button };
|
|
628
|
+
let mut event = empty_event(
|
|
629
|
+
if event_type == SDL_EVENT_MOUSE_BUTTON_DOWN.0 {
|
|
630
|
+
"mouseButtonDown"
|
|
631
|
+
} else {
|
|
632
|
+
"mouseButtonUp"
|
|
633
|
+
},
|
|
634
|
+
raw.windowID.into(),
|
|
635
|
+
);
|
|
636
|
+
event.x = Some(raw.x.into());
|
|
637
|
+
event.y = Some(raw.y.into());
|
|
638
|
+
event.button = Some(raw.button.into());
|
|
639
|
+
events.push(event);
|
|
640
|
+
} else if event_type == SDL_EVENT_MOUSE_WHEEL.0 {
|
|
641
|
+
let raw = unsafe { raw.wheel };
|
|
642
|
+
let mut event = empty_event("mouseWheel", raw.windowID.into());
|
|
643
|
+
event.x = Some(raw.mouse_x.into());
|
|
644
|
+
event.y = Some(raw.mouse_y.into());
|
|
645
|
+
event.dx = Some(raw.x.into());
|
|
646
|
+
event.dy = Some(raw.y.into());
|
|
647
|
+
event.flipped = Some(raw.direction == SDL_MOUSEWHEEL_FLIPPED);
|
|
648
|
+
events.push(event);
|
|
649
|
+
} else if event_type == SDL_EVENT_KEY_DOWN.0 || event_type == SDL_EVENT_KEY_UP.0 {
|
|
650
|
+
let raw = unsafe { raw.key };
|
|
651
|
+
let mut event = empty_event(
|
|
652
|
+
if event_type == SDL_EVENT_KEY_DOWN.0 {
|
|
653
|
+
"keyDown"
|
|
654
|
+
} else {
|
|
655
|
+
"keyUp"
|
|
656
|
+
},
|
|
657
|
+
raw.windowID.into(),
|
|
658
|
+
);
|
|
659
|
+
let key_name = unsafe { SDL_GetKeyName(raw.key) };
|
|
660
|
+
event.key = (!key_name.is_null())
|
|
661
|
+
.then(|| unsafe { CStr::from_ptr(key_name).to_string_lossy().into_owned() });
|
|
662
|
+
event.scancode = Some(raw.scancode.0 as u32);
|
|
663
|
+
event.repeat = Some(raw.repeat);
|
|
664
|
+
event.shift = Some((raw.r#mod.0 & SDL_KMOD_SHIFT.0) != 0);
|
|
665
|
+
event.ctrl = Some((raw.r#mod.0 & SDL_KMOD_CTRL.0) != 0);
|
|
666
|
+
event.alt = Some((raw.r#mod.0 & SDL_KMOD_ALT.0) != 0);
|
|
667
|
+
event.super_key = Some((raw.r#mod.0 & SDL_KMOD_GUI.0) != 0);
|
|
668
|
+
events.push(event);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
events
|
|
672
|
+
}
|
|
37
673
|
|
|
38
674
|
pub struct CompositorFrameTask {
|
|
39
675
|
timeout_ms: u32,
|
|
@@ -48,11 +684,8 @@ impl Task for CompositorFrameTask {
|
|
|
48
684
|
{
|
|
49
685
|
return Ok(wait_for_windows_compositor(self.timeout_ms));
|
|
50
686
|
}
|
|
51
|
-
|
|
52
687
|
#[cfg(not(windows))]
|
|
53
|
-
|
|
54
|
-
Ok(false)
|
|
55
|
-
}
|
|
688
|
+
Ok(false)
|
|
56
689
|
}
|
|
57
690
|
|
|
58
691
|
fn resolve(&mut self, _env: Env, signaled: Self::Output) -> Result<Self::JsValue> {
|
|
@@ -60,7 +693,6 @@ impl Task for CompositorFrameTask {
|
|
|
60
693
|
}
|
|
61
694
|
}
|
|
62
695
|
|
|
63
|
-
/// Waits off the JavaScript thread for the next Windows compositor clock tick.
|
|
64
696
|
#[napi]
|
|
65
697
|
pub fn wait_for_compositor_frame(timeout_ms: Option<u32>) -> AsyncTask<CompositorFrameTask> {
|
|
66
698
|
AsyncTask::new(CompositorFrameTask {
|
|
@@ -71,13 +703,11 @@ pub fn wait_for_compositor_frame(timeout_ms: Option<u32>) -> AsyncTask<Composito
|
|
|
71
703
|
#[cfg(windows)]
|
|
72
704
|
fn wait_for_windows_compositor(timeout_ms: u32) -> bool {
|
|
73
705
|
type WaitForCompositorClock = unsafe extern "system" fn(u32, *const HANDLE, u32) -> u32;
|
|
74
|
-
|
|
75
706
|
let library_name: Vec<u16> = "dcomp.dll\0".encode_utf16().collect();
|
|
76
707
|
let module = unsafe { LoadLibraryW(library_name.as_ptr()) };
|
|
77
708
|
if module.is_null() {
|
|
78
709
|
return false;
|
|
79
710
|
}
|
|
80
|
-
|
|
81
711
|
let procedure =
|
|
82
712
|
unsafe { GetProcAddress(module, b"DCompositionWaitForCompositorClock\0".as_ptr()) };
|
|
83
713
|
let signaled = match procedure {
|
|
@@ -91,28 +721,16 @@ fn wait_for_windows_compositor(timeout_ms: u32) -> bool {
|
|
|
91
721
|
}
|
|
92
722
|
None => false,
|
|
93
723
|
};
|
|
94
|
-
unsafe {
|
|
95
|
-
FreeLibrary(module);
|
|
96
|
-
}
|
|
724
|
+
unsafe { FreeLibrary(module) };
|
|
97
725
|
signaled
|
|
98
726
|
}
|
|
99
727
|
|
|
100
728
|
#[napi]
|
|
101
729
|
pub fn set_transparent(native_data: Buffer, transparent: bool) -> Result<()> {
|
|
102
730
|
#[cfg(windows)]
|
|
103
|
-
|
|
104
|
-
let hwnd = hwnd_from_native_data(&native_data)?;
|
|
105
|
-
configure_dwm_transparency(hwnd, transparent)?;
|
|
106
|
-
}
|
|
107
|
-
|
|
731
|
+
configure_dwm_transparency(hwnd_from_native_data(&native_data)?, transparent)?;
|
|
108
732
|
#[cfg(not(windows))]
|
|
109
|
-
|
|
110
|
-
let _ = (native_data, transparent);
|
|
111
|
-
return Err(Error::from_reason(
|
|
112
|
-
"transparent native windows are supported only on Windows 11",
|
|
113
|
-
));
|
|
114
|
-
}
|
|
115
|
-
|
|
733
|
+
let _ = (native_data, transparent);
|
|
116
734
|
Ok(())
|
|
117
735
|
}
|
|
118
736
|
|
|
@@ -128,7 +746,6 @@ fn configure_dwm_transparency(hwnd: HWND, transparent: bool) -> Result<()> {
|
|
|
128
746
|
"could not create DWM transparency region",
|
|
129
747
|
));
|
|
130
748
|
}
|
|
131
|
-
|
|
132
749
|
let blur = DWM_BLURBEHIND {
|
|
133
750
|
dwFlags: if transparent {
|
|
134
751
|
DWM_BB_ENABLE | DWM_BB_BLURREGION
|
|
@@ -141,9 +758,7 @@ fn configure_dwm_transparency(hwnd: HWND, transparent: bool) -> Result<()> {
|
|
|
141
758
|
};
|
|
142
759
|
let result = unsafe { DwmEnableBlurBehindWindow(hwnd, &blur) };
|
|
143
760
|
if !region.is_null() {
|
|
144
|
-
unsafe {
|
|
145
|
-
DeleteObject(region as _);
|
|
146
|
-
}
|
|
761
|
+
unsafe { DeleteObject(region as _) };
|
|
147
762
|
}
|
|
148
763
|
if result < 0 {
|
|
149
764
|
return Err(Error::from_reason(format!(
|
|
@@ -182,10 +797,8 @@ impl ModalFrameController {
|
|
|
182
797
|
) -> Result<Self> {
|
|
183
798
|
#[cfg(windows)]
|
|
184
799
|
let hwnd = hwnd_from_native_data(&native_data)?;
|
|
185
|
-
|
|
186
800
|
#[cfg(not(windows))]
|
|
187
801
|
let _ = native_data;
|
|
188
|
-
|
|
189
802
|
Ok(Self {
|
|
190
803
|
env,
|
|
191
804
|
frame_callback,
|
|
@@ -211,21 +824,20 @@ impl ModalFrameController {
|
|
|
211
824
|
return Ok(());
|
|
212
825
|
}
|
|
213
826
|
self.previous_userdata = unsafe { GetWindowLongPtrW(self.hwnd, GWLP_USERDATA) };
|
|
214
|
-
|
|
215
|
-
let previous_proc =
|
|
216
|
-
unsafe { SetWindowLongPtrW(self.hwnd, GWLP_USERDATA, self_pointer) };
|
|
827
|
+
unsafe { SetWindowLongPtrW(self.hwnd, GWLP_USERDATA, self as *mut Self as isize) };
|
|
217
828
|
self.previous_proc = unsafe {
|
|
218
|
-
SetWindowLongPtrW(
|
|
829
|
+
SetWindowLongPtrW(
|
|
830
|
+
self.hwnd,
|
|
831
|
+
GWLP_WNDPROC,
|
|
832
|
+
modal_window_proc as *const () as usize as isize,
|
|
833
|
+
)
|
|
219
834
|
};
|
|
220
835
|
if self.previous_proc == 0 {
|
|
221
|
-
unsafe {
|
|
222
|
-
SetWindowLongPtrW(self.hwnd, GWLP_USERDATA, self.previous_userdata);
|
|
223
|
-
}
|
|
836
|
+
unsafe { SetWindowLongPtrW(self.hwnd, GWLP_USERDATA, self.previous_userdata) };
|
|
224
837
|
return Err(Error::from_reason(
|
|
225
838
|
"could not install Windows modal window hook",
|
|
226
839
|
));
|
|
227
840
|
}
|
|
228
|
-
let _ = previous_proc;
|
|
229
841
|
self.attached = true;
|
|
230
842
|
}
|
|
231
843
|
Ok(())
|
|
@@ -280,29 +892,29 @@ unsafe extern "system" fn modal_window_proc(
|
|
|
280
892
|
wparam: WPARAM,
|
|
281
893
|
lparam: LPARAM,
|
|
282
894
|
) -> LRESULT {
|
|
283
|
-
let controller = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *mut ModalFrameController;
|
|
895
|
+
let controller = unsafe { GetWindowLongPtrW(hwnd, GWLP_USERDATA) } as *mut ModalFrameController;
|
|
284
896
|
if !controller.is_null() {
|
|
285
|
-
let controller = &mut *controller;
|
|
897
|
+
let controller = unsafe { &mut *controller };
|
|
286
898
|
match message {
|
|
287
899
|
WM_ENTERSIZEMOVE | WM_ENTERMENULOOP => {
|
|
288
900
|
controller.invoke_state(true);
|
|
289
|
-
SetTimer(hwnd, MODAL_TIMER_ID, 16, None);
|
|
901
|
+
unsafe { SetTimer(hwnd, MODAL_TIMER_ID, 16, None) };
|
|
290
902
|
}
|
|
291
903
|
WM_TIMER if wparam == MODAL_TIMER_ID => {
|
|
292
904
|
controller.invoke_frame();
|
|
293
905
|
return 0;
|
|
294
906
|
}
|
|
295
907
|
WM_EXITSIZEMOVE | WM_EXITMENULOOP => {
|
|
296
|
-
KillTimer(hwnd, MODAL_TIMER_ID);
|
|
908
|
+
unsafe { KillTimer(hwnd, MODAL_TIMER_ID) };
|
|
297
909
|
controller.invoke_state(false);
|
|
298
910
|
}
|
|
299
911
|
_ => {}
|
|
300
912
|
}
|
|
301
|
-
let previous = std::mem::transmute::<isize, WNDPROC>(controller.previous_proc);
|
|
913
|
+
let previous = unsafe { std::mem::transmute::<isize, WNDPROC>(controller.previous_proc) };
|
|
302
914
|
return match previous {
|
|
303
|
-
Some(proc) => CallWindowProcW(Some(proc), hwnd, message, wparam, lparam),
|
|
304
|
-
None => DefWindowProcW(hwnd, message, wparam, lparam),
|
|
915
|
+
Some(proc) => unsafe { CallWindowProcW(Some(proc), hwnd, message, wparam, lparam) },
|
|
916
|
+
None => unsafe { DefWindowProcW(hwnd, message, wparam, lparam) },
|
|
305
917
|
};
|
|
306
918
|
}
|
|
307
|
-
DefWindowProcW(hwnd, message, wparam, lparam)
|
|
919
|
+
unsafe { DefWindowProcW(hwnd, message, wparam, lparam) }
|
|
308
920
|
}
|