@bobfrankston/msger 0.1.78 → 0.1.79
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/msger-native/Cargo.toml
CHANGED
|
Binary file
|
package/msger-native/src/main.rs
CHANGED
|
@@ -5,96 +5,56 @@ use tao::{
|
|
|
5
5
|
event_loop::{ControlFlow, EventLoop},
|
|
6
6
|
keyboard::KeyCode,
|
|
7
7
|
window::WindowBuilder,
|
|
8
|
+
platform::windows::WindowExtWindows,
|
|
8
9
|
};
|
|
9
10
|
use wry::{WebViewBuilder, WebContext};
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
12
|
+
#[cfg(windows)]
|
|
13
|
+
use windows::Win32::UI::WindowsAndMessaging::{
|
|
14
|
+
GetSystemMenu, AppendMenuW, MF_STRING, MF_SEPARATOR, WM_SYSCOMMAND,
|
|
15
|
+
};
|
|
16
|
+
#[cfg(windows)]
|
|
17
|
+
use windows::Win32::UI::Shell::{
|
|
18
|
+
SetWindowSubclass, DefSubclassProc,
|
|
19
|
+
};
|
|
20
|
+
#[cfg(windows)]
|
|
21
|
+
use windows::Win32::Foundation::{HWND, LPARAM, WPARAM, LRESULT};
|
|
22
|
+
#[cfg(windows)]
|
|
23
|
+
use windows::core::PCWSTR;
|
|
24
|
+
#[cfg(windows)]
|
|
25
|
+
use std::sync::atomic::{AtomicBool, Ordering};
|
|
26
|
+
|
|
27
|
+
// Custom menu ID for About item (must be < 0xF000 to avoid conflicts with system commands)
|
|
28
|
+
#[cfg(windows)]
|
|
29
|
+
const IDM_ABOUT: u32 = 0x1000;
|
|
30
|
+
|
|
31
|
+
// Global flag to signal About menu was clicked
|
|
32
|
+
#[cfg(windows)]
|
|
33
|
+
static ABOUT_CLICKED: AtomicBool = AtomicBool::new(false);
|
|
34
|
+
|
|
35
|
+
// Window subclass procedure to intercept WM_SYSCOMMAND
|
|
36
|
+
#[cfg(windows)]
|
|
37
|
+
unsafe extern "system" fn subclass_proc(
|
|
38
|
+
hwnd: HWND,
|
|
39
|
+
msg: u32,
|
|
40
|
+
wparam: WPARAM,
|
|
41
|
+
lparam: LPARAM,
|
|
42
|
+
_uidsubclass: usize,
|
|
43
|
+
_dwrefdata: usize,
|
|
44
|
+
) -> LRESULT {
|
|
45
|
+
if msg == WM_SYSCOMMAND {
|
|
46
|
+
let cmd = wparam.0 & 0xFFF0;
|
|
47
|
+
if cmd == IDM_ABOUT as usize {
|
|
48
|
+
// Set flag that About was clicked
|
|
49
|
+
ABOUT_CLICKED.store(true, Ordering::SeqCst);
|
|
50
|
+
return LRESULT(0);
|
|
32
51
|
}
|
|
33
52
|
}
|
|
53
|
+
DefSubclassProc(hwnd, msg, wparam, lparam)
|
|
54
|
+
}
|
|
34
55
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
close: function(result) {
|
|
38
|
-
// IPC is broken in wry 0.47.2, so we use window.close() instead
|
|
39
|
-
// Result cannot be returned to parent process
|
|
40
|
-
try {
|
|
41
|
-
window.close();
|
|
42
|
-
return true;
|
|
43
|
-
} catch(e) {
|
|
44
|
-
console.error('window.close() failed:', e);
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
saveData: function(key, value) {
|
|
49
|
-
try {
|
|
50
|
-
localStorage.setItem('msger_' + key, JSON.stringify(value));
|
|
51
|
-
return true;
|
|
52
|
-
} catch(e) {
|
|
53
|
-
console.error('Failed to save data:', e);
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
loadData: function(key, defaultValue) {
|
|
58
|
-
try {
|
|
59
|
-
const item = localStorage.getItem('msger_' + key);
|
|
60
|
-
return item ? JSON.parse(item) : defaultValue;
|
|
61
|
-
} catch(e) {
|
|
62
|
-
console.error('Failed to load data:', e);
|
|
63
|
-
return defaultValue;
|
|
64
|
-
}
|
|
65
|
-
},
|
|
66
|
-
removeData: function(key) {
|
|
67
|
-
try {
|
|
68
|
-
localStorage.removeItem('msger_' + key);
|
|
69
|
-
return true;
|
|
70
|
-
} catch(e) {
|
|
71
|
-
console.error('Failed to remove data:', e);
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
clearData: function() {
|
|
76
|
-
try {
|
|
77
|
-
const keys = Object.keys(localStorage);
|
|
78
|
-
for (const key of keys) {
|
|
79
|
-
if (key.startsWith('msger_')) {
|
|
80
|
-
localStorage.removeItem(key);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return true;
|
|
84
|
-
} catch(e) {
|
|
85
|
-
console.error('Failed to clear data:', e);
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
// Check if running in msger environment
|
|
90
|
-
isAvailable: function() {
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
console.log('[MSGER] API initialized successfully! window.msger =', window.msger);
|
|
96
|
-
})();
|
|
97
|
-
"#;
|
|
56
|
+
// JavaScript API to inject into webviews
|
|
57
|
+
const MSGER_JS_API: &str = include_str!("msger-api.js");
|
|
98
58
|
|
|
99
59
|
#[derive(Deserialize, Debug)]
|
|
100
60
|
#[serde(rename_all = "camelCase")]
|
|
@@ -359,6 +319,22 @@ fn main() {
|
|
|
359
319
|
.build(&event_loop)
|
|
360
320
|
.expect("Failed to create window"));
|
|
361
321
|
|
|
322
|
+
// Add About item to Windows system menu and install subclass
|
|
323
|
+
#[cfg(windows)]
|
|
324
|
+
unsafe {
|
|
325
|
+
let hwnd = HWND(window.hwnd() as *mut _);
|
|
326
|
+
let hmenu = GetSystemMenu(hwnd, false);
|
|
327
|
+
if !hmenu.is_invalid() {
|
|
328
|
+
// Add separator
|
|
329
|
+
AppendMenuW(hmenu, MF_SEPARATOR, 0, PCWSTR::null()).ok();
|
|
330
|
+
// Add About item
|
|
331
|
+
let about_text: Vec<u16> = "About (Window Info)\0".encode_utf16().collect();
|
|
332
|
+
AppendMenuW(hmenu, MF_STRING, IDM_ABOUT as usize, PCWSTR(about_text.as_ptr())).ok();
|
|
333
|
+
}
|
|
334
|
+
// Install window subclass to intercept WM_SYSCOMMAND
|
|
335
|
+
SetWindowSubclass(hwnd, Some(subclass_proc), 1, 0).ok();
|
|
336
|
+
}
|
|
337
|
+
|
|
362
338
|
// Generate HTML
|
|
363
339
|
let html = generate_html(&options);
|
|
364
340
|
|
|
@@ -488,6 +464,55 @@ fn main() {
|
|
|
488
464
|
|
|
489
465
|
// Run event loop
|
|
490
466
|
event_loop.run(move |event, _,control_flow| {
|
|
467
|
+
// Check for About menu click
|
|
468
|
+
#[cfg(windows)]
|
|
469
|
+
if ABOUT_CLICKED.swap(false, Ordering::SeqCst) {
|
|
470
|
+
// Get window information
|
|
471
|
+
let position = window_clone.outer_position().unwrap_or_default();
|
|
472
|
+
let inner_size = window_clone.inner_size();
|
|
473
|
+
let outer_size = window_clone.outer_size();
|
|
474
|
+
let scale_factor = window_clone.scale_factor();
|
|
475
|
+
let is_maximized = window_clone.is_maximized();
|
|
476
|
+
let is_fullscreen = window_clone.fullscreen().is_some();
|
|
477
|
+
let is_always_on_top = window_clone.is_always_on_top();
|
|
478
|
+
|
|
479
|
+
// Calculate logical sizes (what you specify in the API)
|
|
480
|
+
let logical_inner_width = (inner_size.width as f64 / scale_factor) as i32;
|
|
481
|
+
let logical_inner_height = (inner_size.height as f64 / scale_factor) as i32;
|
|
482
|
+
let logical_outer_width = (outer_size.width as f64 / scale_factor) as i32;
|
|
483
|
+
let logical_outer_height = (outer_size.height as f64 / scale_factor) as i32;
|
|
484
|
+
|
|
485
|
+
let info = format!(
|
|
486
|
+
"Window Information\\n\\n\
|
|
487
|
+
Position: x={}, y={}\\n\\n\
|
|
488
|
+
Logical Size (API): {}x{} inner\\n\
|
|
489
|
+
Physical Size (Screen): {}x{} inner\\n\
|
|
490
|
+
Outer Size (Physical): {}x{}\\n\
|
|
491
|
+
Outer Size (Logical): {}x{}\\n\\n\
|
|
492
|
+
Scale Factor: {:.2} ({}% DPI)\\n\
|
|
493
|
+
Maximized: {}\\n\
|
|
494
|
+
Fullscreen: {}\\n\
|
|
495
|
+
Always On Top: {}\\n\\n\
|
|
496
|
+
Version: {}",
|
|
497
|
+
position.x, position.y,
|
|
498
|
+
logical_inner_width, logical_inner_height,
|
|
499
|
+
inner_size.width, inner_size.height,
|
|
500
|
+
outer_size.width, outer_size.height,
|
|
501
|
+
logical_outer_width, logical_outer_height,
|
|
502
|
+
scale_factor, (scale_factor * 100.0) as i32,
|
|
503
|
+
is_maximized,
|
|
504
|
+
is_fullscreen,
|
|
505
|
+
is_always_on_top,
|
|
506
|
+
env!("NPM_VERSION")
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
// Show the info in an alert
|
|
510
|
+
let script = format!("alert('{}')", info);
|
|
511
|
+
if let Err(e) = webview.evaluate_script(&script) {
|
|
512
|
+
eprintln!("Failed to show about dialog: {}", e);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
491
516
|
// Check timeout
|
|
492
517
|
if let Some(deadline) = timeout_instant {
|
|
493
518
|
if std::time::Instant::now() >= deadline {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
console.log('[MSGER] Initializing msger API...');
|
|
3
|
+
|
|
4
|
+
// Check if IPC is available
|
|
5
|
+
const hasIPC = typeof window.ipc !== 'undefined' && window.ipc.postMessage;
|
|
6
|
+
console.log('[MSGER] IPC available:', hasIPC);
|
|
7
|
+
|
|
8
|
+
// Helper to safely send IPC messages
|
|
9
|
+
function safePostMessage(data) {
|
|
10
|
+
try {
|
|
11
|
+
if (!hasIPC) {
|
|
12
|
+
console.error('msger API: window.ipc not available');
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
window.ipc.postMessage(JSON.stringify(data));
|
|
16
|
+
return true;
|
|
17
|
+
} catch(e) {
|
|
18
|
+
console.error('msger API error:', e);
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
window.msger = {
|
|
24
|
+
// Window control - only close() with window.close() workaround (IPC broken)
|
|
25
|
+
close: function(result) {
|
|
26
|
+
// IPC is broken in wry 0.47.2, so we use window.close() instead
|
|
27
|
+
// Result cannot be returned to parent process
|
|
28
|
+
try {
|
|
29
|
+
window.close();
|
|
30
|
+
return true;
|
|
31
|
+
} catch(e) {
|
|
32
|
+
console.error('window.close() failed:', e);
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
saveData: function(key, value) {
|
|
37
|
+
try {
|
|
38
|
+
localStorage.setItem('msger_' + key, JSON.stringify(value));
|
|
39
|
+
return true;
|
|
40
|
+
} catch(e) {
|
|
41
|
+
console.error('Failed to save data:', e);
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
loadData: function(key, defaultValue) {
|
|
46
|
+
try {
|
|
47
|
+
const item = localStorage.getItem('msger_' + key);
|
|
48
|
+
return item ? JSON.parse(item) : defaultValue;
|
|
49
|
+
} catch(e) {
|
|
50
|
+
console.error('Failed to load data:', e);
|
|
51
|
+
return defaultValue;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
removeData: function(key) {
|
|
55
|
+
try {
|
|
56
|
+
localStorage.removeItem('msger_' + key);
|
|
57
|
+
return true;
|
|
58
|
+
} catch(e) {
|
|
59
|
+
console.error('Failed to remove data:', e);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
clearData: function() {
|
|
64
|
+
try {
|
|
65
|
+
const keys = Object.keys(localStorage);
|
|
66
|
+
for (const key of keys) {
|
|
67
|
+
if (key.startsWith('msger_')) {
|
|
68
|
+
localStorage.removeItem(key);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
} catch(e) {
|
|
73
|
+
console.error('Failed to clear data:', e);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
// Check if running in msger environment
|
|
78
|
+
isAvailable: function() {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
console.log('[MSGER] API initialized successfully! window.msger =', window.msger);
|
|
84
|
+
})();
|