@bobfrankston/msger 0.1.148 → 0.1.150

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/README.md CHANGED
@@ -141,7 +141,7 @@ msger -title "Test" -message "Saving config..." -save test.json
141
141
  "autoSize": false,
142
142
  "alwaysOnTop": false,
143
143
  "fullscreen": false,
144
- "zoomPercent": 100,
144
+ "zoom": 100,
145
145
  "debug": false,
146
146
  "icon": "path/to/icon.png",
147
147
  "dev": false
@@ -172,7 +172,7 @@ msger -title "Test" -message "Saving config..." -save test.json
172
172
  | `autoSize` | boolean | true when no size | Auto-resize window to fit content |
173
173
  | `alwaysOnTop` | boolean | false | Keep window on top |
174
174
  | `fullscreen` | boolean | false | Start in fullscreen mode |
175
- | `zoomPercent` | number | 100 | Initial zoom level (100=100%, 150=150%) |
175
+ | `zoom` | number | 100 | Initial zoom level (100=100%, 150=150%) |
176
176
  | `debug` | boolean | false | Return debug info in result |
177
177
  | `icon` | string | null | Path to window icon (.png, .ico) |
178
178
  | `dev` | boolean | false | Open DevTools automatically (for debugging HTML/URL content) |
@@ -383,7 +383,7 @@ interface MessageBoxOptions {
383
383
  allowInput?: boolean; // Show input field (default: false)
384
384
  autoSize?: boolean; // Auto-resize window to fit content (default: true when no size specified)
385
385
  alwaysOnTop?: boolean; // Keep window on top of other windows (default: false)
386
- zoomPercent?: number; // Initial zoom level (100=100%, 150=150%, 50=50%)
386
+ zoom?: number; // Initial zoom level (100=100%, 150=150%, 50=50%)
387
387
  timeout?: number; // Auto-close after N seconds
388
388
  detach?: boolean; // Launch detached from parent process
389
389
  fullscreen?: boolean; // Start window in fullscreen mode (F11 to toggle)
@@ -575,7 +575,7 @@ await showMessageBox({
575
575
  title: 'Important Alert',
576
576
  message: 'This window stays on top!',
577
577
  alwaysOnTop: true,
578
- zoomPercent: 150
578
+ zoom: 150
579
579
  });
580
580
  ```
581
581
 
package/cli.js CHANGED
@@ -61,8 +61,8 @@ export default async function main() {
61
61
  finalOptions.alwaysOnTop = options.alwaysOnTop;
62
62
  if (options.fullscreen !== undefined)
63
63
  finalOptions.fullscreen = options.fullscreen;
64
- if (options.zoomPercent !== undefined)
65
- finalOptions.zoomPercent = options.zoomPercent;
64
+ if (options.zoom !== undefined)
65
+ finalOptions.zoom = options.zoom;
66
66
  if (options.icon)
67
67
  finalOptions.icon = options.icon;
68
68
  if (options.dev !== undefined)
@@ -356,7 +356,7 @@ function parseCliArgs(args) {
356
356
  if (i + 1 >= args.length) {
357
357
  throw new Error('-zoom requires a percentage argument');
358
358
  }
359
- cli.zoomPercent = parseFloat(args[++i]);
359
+ cli.zoom = parseFloat(args[++i]);
360
360
  i++;
361
361
  }
362
362
  else if (arg === '-pos' || arg === '--pos') {
@@ -490,7 +490,7 @@ function parseCliArgs(args) {
490
490
  detach: cli.detach,
491
491
  fullscreen: cli.fullscreen,
492
492
  alwaysOnTop: cli.alwaysOnTop,
493
- zoomPercent: cli.zoomPercent,
493
+ zoom: cli.zoom,
494
494
  icon: cli.icon,
495
495
  dev: cli.dev,
496
496
  debug: cli.debug
@@ -583,8 +583,8 @@ function saveConfigFile(filePath, options) {
583
583
  config.alwaysOnTop = options.alwaysOnTop;
584
584
  if (options.fullscreen)
585
585
  config.fullscreen = options.fullscreen;
586
- if (options.zoomPercent && options.zoomPercent !== 100)
587
- config.zoomPercent = options.zoomPercent;
586
+ if (options.zoom && options.zoom !== 100)
587
+ config.zoom = options.zoom;
588
588
  if (options.icon)
589
589
  config.icon = options.icon;
590
590
  if (options.dev)
package/cli.ts CHANGED
@@ -52,7 +52,7 @@ export default async function main() {
52
52
  if (options.timeout !== undefined) finalOptions.timeout = options.timeout;
53
53
  if (options.alwaysOnTop !== undefined) finalOptions.alwaysOnTop = options.alwaysOnTop;
54
54
  if (options.fullscreen !== undefined) finalOptions.fullscreen = options.fullscreen;
55
- if (options.zoomPercent !== undefined) finalOptions.zoomPercent = options.zoomPercent;
55
+ if (options.zoom !== undefined) finalOptions.zoom = options.zoom;
56
56
  if (options.icon) finalOptions.icon = options.icon;
57
57
  if (options.dev !== undefined) finalOptions.dev = options.dev;
58
58
  if (options.debug !== undefined) finalOptions.debug = options.debug;
@@ -169,7 +169,7 @@ interface CliOptions {
169
169
  escapeCloses?: boolean;
170
170
  reset?: boolean;
171
171
  alwaysOnTop?: boolean;
172
- zoomPercent?: number;
172
+ zoom?: number;
173
173
  posX?: number;
174
174
  posY?: number;
175
175
  screen?: number;
@@ -378,7 +378,7 @@ function parseCliArgs(args: string[]): {
378
378
  if (i + 1 >= args.length) {
379
379
  throw new Error('-zoom requires a percentage argument');
380
380
  }
381
- cli.zoomPercent = parseFloat(args[++i]);
381
+ cli.zoom = parseFloat(args[++i]);
382
382
  i++;
383
383
  } else if (arg === '-pos' || arg === '--pos') {
384
384
  if (i + 1 >= args.length) {
@@ -494,7 +494,7 @@ function parseCliArgs(args: string[]): {
494
494
  detach: cli.detach,
495
495
  fullscreen: cli.fullscreen,
496
496
  alwaysOnTop: cli.alwaysOnTop,
497
- zoomPercent: cli.zoomPercent,
497
+ zoom: cli.zoom,
498
498
  icon: cli.icon,
499
499
  dev: cli.dev,
500
500
  debug: cli.debug
@@ -580,7 +580,7 @@ function saveConfigFile(filePath: string, options: MessageBoxOptions): void {
580
580
  if (options.timeout) config.timeout = options.timeout;
581
581
  if (options.alwaysOnTop) config.alwaysOnTop = options.alwaysOnTop;
582
582
  if (options.fullscreen) config.fullscreen = options.fullscreen;
583
- if (options.zoomPercent && options.zoomPercent !== 100) config.zoomPercent = options.zoomPercent;
583
+ if (options.zoom && options.zoom !== 100) config.zoom = options.zoom;
584
584
  if (options.icon) config.icon = options.icon;
585
585
  if (options.dev) config.dev = options.dev;
586
586
  if (options.debug) config.debug = options.debug;
Binary file
@@ -32,13 +32,33 @@ use windows::core::PCWSTR;
32
32
  #[cfg(windows)]
33
33
  use std::sync::atomic::{AtomicBool, Ordering};
34
34
 
35
- // Custom menu ID for About item (must be < 0xF000 to avoid conflicts with system commands)
35
+ // Custom menu IDs (must be < 0xF000 to avoid conflicts with system commands)
36
36
  #[cfg(windows)]
37
37
  const IDM_ABOUT: u32 = 0x1000;
38
+ #[cfg(windows)]
39
+ const IDM_ZOOM_IN: u32 = 0x1001;
40
+ #[cfg(windows)]
41
+ const IDM_ZOOM_OUT: u32 = 0x1002;
42
+ #[cfg(windows)]
43
+ const IDM_ZOOM_RESET: u32 = 0x1003;
44
+ #[cfg(windows)]
45
+ const IDM_ZOOM_50: u32 = 0x1010;
46
+ #[cfg(windows)]
47
+ const IDM_ZOOM_75: u32 = 0x1011;
48
+ #[cfg(windows)]
49
+ const IDM_ZOOM_100: u32 = 0x1012;
50
+ #[cfg(windows)]
51
+ const IDM_ZOOM_125: u32 = 0x1013;
52
+ #[cfg(windows)]
53
+ const IDM_ZOOM_150: u32 = 0x1014;
54
+ #[cfg(windows)]
55
+ const IDM_ZOOM_200: u32 = 0x1015;
38
56
 
39
- // Global flag to signal About menu was clicked
57
+ // Global flags
40
58
  #[cfg(windows)]
41
59
  static ABOUT_CLICKED: AtomicBool = AtomicBool::new(false);
60
+ #[cfg(windows)]
61
+ static ZOOM_ACTION: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
42
62
 
43
63
  // Window subclass procedure to intercept WM_SYSCOMMAND
44
64
  #[cfg(windows)]
@@ -52,10 +72,48 @@ unsafe extern "system" fn subclass_proc(
52
72
  ) -> LRESULT {
53
73
  if msg == WM_SYSCOMMAND {
54
74
  let cmd = wparam.0 & 0xFFF0;
55
- if cmd == IDM_ABOUT as usize {
56
- // Set flag that About was clicked
57
- ABOUT_CLICKED.store(true, Ordering::SeqCst);
58
- return LRESULT(0);
75
+ match cmd as u32 {
76
+ IDM_ABOUT => {
77
+ ABOUT_CLICKED.store(true, Ordering::SeqCst);
78
+ return LRESULT(0);
79
+ }
80
+ IDM_ZOOM_IN => {
81
+ ZOOM_ACTION.store(1, Ordering::SeqCst); // Zoom in
82
+ return LRESULT(0);
83
+ }
84
+ IDM_ZOOM_OUT => {
85
+ ZOOM_ACTION.store(2, Ordering::SeqCst); // Zoom out
86
+ return LRESULT(0);
87
+ }
88
+ IDM_ZOOM_RESET => {
89
+ ZOOM_ACTION.store(100, Ordering::SeqCst); // Reset to 100%
90
+ return LRESULT(0);
91
+ }
92
+ IDM_ZOOM_50 => {
93
+ ZOOM_ACTION.store(50, Ordering::SeqCst);
94
+ return LRESULT(0);
95
+ }
96
+ IDM_ZOOM_75 => {
97
+ ZOOM_ACTION.store(75, Ordering::SeqCst);
98
+ return LRESULT(0);
99
+ }
100
+ IDM_ZOOM_100 => {
101
+ ZOOM_ACTION.store(100, Ordering::SeqCst);
102
+ return LRESULT(0);
103
+ }
104
+ IDM_ZOOM_125 => {
105
+ ZOOM_ACTION.store(125, Ordering::SeqCst);
106
+ return LRESULT(0);
107
+ }
108
+ IDM_ZOOM_150 => {
109
+ ZOOM_ACTION.store(150, Ordering::SeqCst);
110
+ return LRESULT(0);
111
+ }
112
+ IDM_ZOOM_200 => {
113
+ ZOOM_ACTION.store(200, Ordering::SeqCst);
114
+ return LRESULT(0);
115
+ }
116
+ _ => {}
59
117
  }
60
118
  }
61
119
  DefSubclassProc(hwnd, msg, wparam, lparam)
@@ -120,7 +178,7 @@ struct MessageBoxOptions {
120
178
  #[serde(default)]
121
179
  fullscreen: bool,
122
180
  #[serde(default)]
123
- zoom_percent: Option<f64>,
181
+ zoom: Option<f64>,
124
182
  #[serde(default)]
125
183
  debug: bool,
126
184
  #[serde(default)]
@@ -289,7 +347,7 @@ fn generate_html(options: &MessageBoxOptions) -> String {
289
347
  .replace("{FULLSCREEN}", if options.fullscreen { "true" } else { "false" })
290
348
  .replace("{ESCAPE_CLOSES}", if options.escape_closes { "true" } else { "false" })
291
349
  .replace("{RESET}", if options.reset { "true" } else { "false" })
292
- .replace("{ZOOM_PERCENT}", &options.zoom_percent.unwrap_or(100.0).to_string())
350
+ .replace("{ZOOM_PERCENT}", &options.zoom.unwrap_or(100.0).to_string())
293
351
  .replace("{DEFAULT_BUTTON}", options.buttons.first().unwrap_or(&"Cancel".to_string()))
294
352
  .replace("{TIMEOUT_SECONDS}", &options.timeout.map(|t| t.to_string()).unwrap_or_else(|| "0".to_string()));
295
353
 
@@ -389,8 +447,22 @@ fn main() {
389
447
  let icon = if let Some(ref icon_path) = options.icon {
390
448
  load_icon(icon_path)
391
449
  } else {
392
- // Try default icon.png in current directory
393
- load_icon("icon.png")
450
+ // Try default msger.png next to the binary
451
+ if let Ok(exe_path) = std::env::current_exe() {
452
+ if let Some(exe_dir) = exe_path.parent() {
453
+ let default_icon = exe_dir.join("msger.png");
454
+ if default_icon.exists() {
455
+ load_icon(default_icon.to_str().unwrap_or("msger.png"))
456
+ } else {
457
+ // Fallback to msger.png in current directory
458
+ load_icon("msger.png")
459
+ }
460
+ } else {
461
+ load_icon("msger.png")
462
+ }
463
+ } else {
464
+ load_icon("msger.png")
465
+ }
394
466
  };
395
467
 
396
468
  let mut window_builder = WindowBuilder::new()
@@ -434,9 +506,45 @@ fn main() {
434
506
  if !hmenu.is_invalid() {
435
507
  // Add separator
436
508
  AppendMenuW(hmenu, MF_SEPARATOR, 0, PCWSTR::null()).ok();
509
+
437
510
  // Add About item
438
- let about_text: Vec<u16> = "About (Window Info)\0".encode_utf16().collect();
511
+ let about_text: Vec<u16> = "About msger (Window Info)\0".encode_utf16().collect();
439
512
  AppendMenuW(hmenu, MF_STRING, IDM_ABOUT as usize, PCWSTR(about_text.as_ptr())).ok();
513
+
514
+ // Add another separator
515
+ AppendMenuW(hmenu, MF_SEPARATOR, 0, PCWSTR::null()).ok();
516
+
517
+ // Add Zoom controls
518
+ let zoom_in: Vec<u16> = "Zoom In\tCtrl++\0".encode_utf16().collect();
519
+ AppendMenuW(hmenu, MF_STRING, IDM_ZOOM_IN as usize, PCWSTR(zoom_in.as_ptr())).ok();
520
+
521
+ let zoom_out: Vec<u16> = "Zoom Out\tCtrl+-\0".encode_utf16().collect();
522
+ AppendMenuW(hmenu, MF_STRING, IDM_ZOOM_OUT as usize, PCWSTR(zoom_out.as_ptr())).ok();
523
+
524
+ let zoom_reset: Vec<u16> = "Reset Zoom\tCtrl+0\0".encode_utf16().collect();
525
+ AppendMenuW(hmenu, MF_STRING, IDM_ZOOM_RESET as usize, PCWSTR(zoom_reset.as_ptr())).ok();
526
+
527
+ // Add separator before preset zooms
528
+ AppendMenuW(hmenu, MF_SEPARATOR, 0, PCWSTR::null()).ok();
529
+
530
+ // Add preset zoom levels
531
+ let zoom_50: Vec<u16> = "Zoom 50%\0".encode_utf16().collect();
532
+ AppendMenuW(hmenu, MF_STRING, IDM_ZOOM_50 as usize, PCWSTR(zoom_50.as_ptr())).ok();
533
+
534
+ let zoom_75: Vec<u16> = "Zoom 75%\0".encode_utf16().collect();
535
+ AppendMenuW(hmenu, MF_STRING, IDM_ZOOM_75 as usize, PCWSTR(zoom_75.as_ptr())).ok();
536
+
537
+ let zoom_100: Vec<u16> = "Zoom 100%\0".encode_utf16().collect();
538
+ AppendMenuW(hmenu, MF_STRING, IDM_ZOOM_100 as usize, PCWSTR(zoom_100.as_ptr())).ok();
539
+
540
+ let zoom_125: Vec<u16> = "Zoom 125%\0".encode_utf16().collect();
541
+ AppendMenuW(hmenu, MF_STRING, IDM_ZOOM_125 as usize, PCWSTR(zoom_125.as_ptr())).ok();
542
+
543
+ let zoom_150: Vec<u16> = "Zoom 150%\0".encode_utf16().collect();
544
+ AppendMenuW(hmenu, MF_STRING, IDM_ZOOM_150 as usize, PCWSTR(zoom_150.as_ptr())).ok();
545
+
546
+ let zoom_200: Vec<u16> = "Zoom 200%\0".encode_utf16().collect();
547
+ AppendMenuW(hmenu, MF_STRING, IDM_ZOOM_200 as usize, PCWSTR(zoom_200.as_ptr())).ok();
440
548
  }
441
549
  // Install window subclass to intercept WM_SYSCOMMAND
442
550
  let _ = SetWindowSubclass(hwnd, Some(subclass_proc), 1, 0).ok();
@@ -696,6 +804,13 @@ fn main() {
696
804
  // Check for About menu click
697
805
  #[cfg(windows)]
698
806
  if ABOUT_CLICKED.swap(false, Ordering::SeqCst) {
807
+ // Get current zoom from the page
808
+ let zoom_script = "document.body.style.zoom || '100%'";
809
+ let current_zoom = match webview.evaluate_script(zoom_script) {
810
+ Ok(_) => "100%".to_string(), // Default if can't get it
811
+ Err(_) => "100%".to_string(),
812
+ };
813
+
699
814
  // Get window information
700
815
  let position = window_clone.outer_position().unwrap_or_default();
701
816
  let inner_size = window_clone.inner_size();
@@ -712,27 +827,29 @@ fn main() {
712
827
  let logical_outer_height = (outer_size.height as f64 / scale_factor) as i32;
713
828
 
714
829
  let info = format!(
715
- "Window Information\\n\\n\
716
- Position: x={}, y={}\\n\\n\
830
+ "msger v{}\\n\\n\
831
+ Rust/wry-based message viewer\\n\\n\
832
+ Position: x={}, y={}\\n\
717
833
  Logical Size (API): {}x{} inner\\n\
718
834
  Physical Size (Screen): {}x{} inner\\n\
719
835
  Outer Size (Physical): {}x{}\\n\
720
836
  Outer Size (Logical): {}x{}\\n\\n\
837
+ Current Zoom: {}\\n\
721
838
  Scale Factor: {:.2} ({}% DPI)\\n\
722
839
  Maximized: {}\\n\
723
840
  Fullscreen: {}\\n\
724
- Always On Top: {}\\n\\n\
725
- Version: {}",
841
+ Always On Top: {}",
842
+ get_package_version().unwrap_or_else(|| "unknown".to_string()),
726
843
  position.x, position.y,
727
844
  logical_inner_width, logical_inner_height,
728
845
  inner_size.width, inner_size.height,
729
846
  outer_size.width, outer_size.height,
730
847
  logical_outer_width, logical_outer_height,
848
+ current_zoom,
731
849
  scale_factor, (scale_factor * 100.0) as i32,
732
850
  is_maximized,
733
851
  is_fullscreen,
734
- is_always_on_top,
735
- get_package_version().unwrap_or_else(|| "unknown".to_string())
852
+ is_always_on_top
736
853
  );
737
854
 
738
855
  // Show the info in an alert
@@ -742,6 +859,32 @@ fn main() {
742
859
  }
743
860
  }
744
861
 
862
+ // Check for zoom menu actions
863
+ #[cfg(windows)]
864
+ {
865
+ let zoom_action = ZOOM_ACTION.swap(0, Ordering::SeqCst);
866
+ if zoom_action != 0 {
867
+ let zoom_script = match zoom_action {
868
+ 1 => {
869
+ // Zoom in - get current and increase by 25%
870
+ "var currentZoom = parseFloat(document.body.style.zoom || '100'); document.body.style.zoom = (currentZoom + 25) + '%';"
871
+ }
872
+ 2 => {
873
+ // Zoom out - get current and decrease by 25%
874
+ "var currentZoom = parseFloat(document.body.style.zoom || '100'); document.body.style.zoom = Math.max(25, currentZoom - 25) + '%';"
875
+ }
876
+ percent => {
877
+ // Set specific zoom level
878
+ &format!("document.body.style.zoom = '{}%';", percent)
879
+ }
880
+ };
881
+
882
+ if let Err(e) = webview.evaluate_script(zoom_script) {
883
+ eprintln!("Failed to set zoom: {}", e);
884
+ }
885
+ }
886
+ }
887
+
745
888
  // Check timeout
746
889
  if let Some(deadline) = timeout_instant {
747
890
  if std::time::Instant::now() >= deadline {
package/msger.png ADDED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/msger",
3
- "version": "0.1.148",
3
+ "version": "0.1.150",
4
4
  "description": "Fast, lightweight, cross-platform message box - Rust-powered alternative to msgview",
5
5
  "type": "module",
6
6
  "main": "./index.js",
package/shower.d.ts CHANGED
@@ -24,7 +24,7 @@ export interface MessageBoxOptions {
24
24
  y: number; /** Window Y position in pixels */
25
25
  screen?: number; /** Optional screen index (0-based, Windows only) - NOT used by Rust binary */
26
26
  };
27
- zoomPercent?: number; /** Initial zoom level as percentage (100=100%, 150=150%, 50=50%, etc.) */
27
+ zoom?: number; /** Initial zoom level as percentage (100=100%, 150=150%, 50=50%, etc.) */
28
28
  autoSize?: boolean; /** Automatically resize window to fit content (default: false) */
29
29
  alwaysOnTop?: boolean; /** Keep window on top of other windows (default: false) */
30
30
  buttons?: string[]; /** Array of button labels to display (default: ['OK']) */
package/shower.ts CHANGED
@@ -62,7 +62,7 @@ export interface MessageBoxOptions {
62
62
  y: number; /** Window Y position in pixels */
63
63
  screen?: number; /** Optional screen index (0-based, Windows only) - NOT used by Rust binary */
64
64
  };
65
- zoomPercent?: number; /** Initial zoom level as percentage (100=100%, 150=150%, 50=50%, etc.) */
65
+ zoom?: number; /** Initial zoom level as percentage (100=100%, 150=150%, 50=50%, etc.) */
66
66
  autoSize?: boolean; /** Automatically resize window to fit content (default: false) */
67
67
  alwaysOnTop?: boolean; /** Keep window on top of other windows (default: false) */
68
68
  buttons?: string[]; /** Array of button labels to display (default: ['OK']) */