@bobfrankston/msger 0.1.421 → 0.1.423

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
@@ -80,9 +80,20 @@ msger -url "https://example.com" -detach
80
80
  # Fullscreen mode
81
81
  msger -fullscreen -url "https://example.com"
82
82
 
83
- # Multi-monitor (Windows only - show on second screen)
83
+ # Multi-monitor - show on second screen
84
84
  msger -message "Second screen" -pos 0,100 -screen 1
85
85
 
86
+ # What displays are there? (JSON, no window; indices match -screen)
87
+ msger -list
88
+
89
+ # Transparent click-through overlay on screen 1, gone after 3 seconds
90
+ msger -overlay -fullscreen -screen 1 -timeout 3 "1"
91
+
92
+ # Number every screen, the way the display settings "Identify" button does
93
+ msger -list | ConvertFrom-Json | ForEach-Object { $_.monitors } | ForEach-Object {
94
+ Start-Process msger -ArgumentList "-overlay -fullscreen -screen $($_.index) -timeout 3 `"$($_.index + 1)`""
95
+ }
96
+
86
97
  # Auto-close timeout
87
98
  msger -message "Closing in 5 seconds..." -timeout 5
88
99
 
@@ -239,6 +250,9 @@ msger -title "Test" -message "Saving config..." -save test.json
239
250
  | `autoSize` | boolean | true when no size | Auto-resize window to fit content |
240
251
  | `alwaysOnTop` | boolean | false | Keep window on top |
241
252
  | `fullscreen` | boolean | false | Start in fullscreen mode |
253
+ | `screen` | number | null | Monitor index (0-based) for `fullscreen`, and the fallback for `pos.screen`. The portable way to target a display — Wayland refuses client-set window coordinates but honors fullscreen on a chosen output |
254
+ | `overlay` | boolean | false | Frameless, transparent, click-through, always-on-top window drawn over the desktop, kept out of the taskbar and never taking focus. Pair with `timeout` — it has no close button and passes clicks through, so nothing in it can be operated. With `url`/`rawHtml` the page must set its own transparent background; the built-in template already does. Each overlay gets its own WebView2 profile (keyed by screen) so a set of them can start in parallel |
255
+ | `listMonitors` | boolean | false | Print the monitor layout as JSON and exit without opening a window: `{"monitors":[{index,name,x,y,width,height,scale,primary}]}` |
242
256
  | `zoom` | number | 100 | Initial zoom level (100=100%, 150=150%). Applies in every mode — message, HTML and URL |
243
257
  | `newWindow` | string | `"browser"` | Where "open in new window" / `target=_blank` links go: `"browser"` (system browser) or `"msger"` (spawns another msger window) |
244
258
  | `contextMenu` | string | `"page"` | `"always"` forces the native right-click menu everywhere, even where the page suppresses it with its own `contextmenu` handler |
@@ -320,7 +334,9 @@ Options:
320
334
  -default, --default <text> Default value for input field
321
335
  -size, --size <width,height> Window size (e.g., -size 800,600)
322
336
  -pos, --pos <x,y> Window position (e.g., -pos 100,200)
323
- -screen, --screen <number> [Windows only] Screen index (0=primary, 1=second, etc.)
337
+ -screen, --screen <number> Screen index (0=primary, 1=second, etc.). Offsets -pos into
338
+ that screen, and picks which screen -fullscreen covers.
339
+ -list, --list Print the monitor layout as JSON and exit (no window)
324
340
  -zoom, --zoom <percent> Zoom level as percentage (e.g., -zoom 150 for 150%).
325
341
  Works in message, HTML and URL modes alike.
326
342
  -newwindow, --newwindow <where> Where "open in new window" links go:
@@ -329,6 +345,9 @@ Options:
329
345
  -ontop, --ontop Keep window always on top
330
346
  -detach, --detach Launch window independently (parent returns immediately)
331
347
  -fullscreen, --fullscreen Start window in fullscreen mode (F11 to toggle, Escape to exit)
348
+ -overlay, --overlay Frameless, transparent, click-through window over the desktop.
349
+ Always on top, no taskbar slot, never takes focus. Pair with
350
+ -timeout; combine with -fullscreen -screen <n> per display.
332
351
  -raw, --raw With -html: load HTML as-is, no msger template/buttons (alias: -full)
333
352
  -debug, --debug Return debug info (HTML, size, autoSize) in result
334
353
  -render, --render [file] Render the page to a bitmap instead of displaying it.
package/cli.js CHANGED
@@ -46,11 +46,21 @@ Options:
46
46
  -size <width,height> Set window size (e.g., -size 800,600)
47
47
  -zoom <percent> Set zoom level as percentage (e.g., -zoom 150 for 150%)
48
48
  -pos <x,y> Set window position (e.g., -pos 100,200)
49
- -screen <number> [Windows only] Screen index for multi-monitor (0=primary, 1=second, etc.)
49
+ -screen <number> Screen index for multi-monitor (0=primary, 1=second, etc.). Offsets
50
+ -pos into that screen, and picks which screen -fullscreen covers.
51
+ -list Print the monitor layout as JSON and exit (no window). Indices
52
+ match -screen: {"monitors":[{index,name,x,y,width,height,scale,primary}]}
50
53
  -icon <path> Set window icon (PNG file). Default: icon.png in current directory
51
54
  -timeout <seconds> Auto-close after specified seconds
52
55
  -detach Leave window open after app exits (parent process returns immediately)
53
56
  -fullscreen Start window in fullscreen mode (F11 to toggle, Escape to exit)
57
+ -overlay Frameless, transparent, click-through window drawn over the
58
+ desktop. Always on top, no taskbar slot, never takes focus.
59
+ Pair with -timeout: it has no close button and passes clicks
60
+ through, so the timeout is how it goes away. Combine with
61
+ -fullscreen -screen <n> to cover one display, e.g.
62
+ msger -overlay -fullscreen -screen 1 -timeout 3 "2"
63
+ With -url/-raw the page must set its own transparent background.
54
64
  -no-escape-closes Prevent Escape key from closing window (still exits fullscreen)
55
65
  -reset Clear localStorage on startup (useful for resetting web content state)
56
66
  -ontop Keep window always on top of other windows
Binary file
@@ -54,18 +54,48 @@ function getUserBinDir() {
54
54
 
55
55
  const userBinDir = getUserBinDir();
56
56
 
57
- // Step 1: Copy from target/release/ → bin/ if build output is newer
57
+ // Step 1: Copy from target/release/ → bin/ if build output is newer.
58
+ //
59
+ // A plain copy here fails whenever a msger window is running from bin/ —
60
+ // Windows refuses to overwrite a running exe — and the old behavior was to
61
+ // shrug and keep shipping the STALE binary, which is how a fresh build can
62
+ // silently not reach the package. Use the same rename-aside trick step 2b
63
+ // uses: renaming a running exe is permitted, so retire the locked file and
64
+ // drop the new one into the freed path. The running process keeps executing
65
+ // from the retired name.
66
+ //
67
+ // Retired copies go to msger-native/.old/ rather than beside the binary,
68
+ // because package.json ships `msger-native/bin/` — an `.old-*` file left in
69
+ // bin/ would be published as a stray multi-MB binary. Same volume, so the
70
+ // rename stays a rename (a cross-device move degrades to copy+delete, which
71
+ // is exactly what a running exe forbids).
58
72
  if (fs.existsSync(buildBinary)) {
59
73
  const buildTime = fs.statSync(buildBinary).mtimeMs;
60
74
  const srcTime = fs.existsSync(srcBinary) ? fs.statSync(srcBinary).mtimeMs : 0;
61
75
  if (buildTime > srcTime) {
76
+ const retireDir = path.join(nativeDir, ".old");
62
77
  try {
63
78
  fs.copyFileSync(buildBinary, srcBinary);
64
79
  console.log(` msger: updated ${baseName}${ext} from build`);
65
80
  } catch (e) {
66
- // Source might be locked by running process — that's OK, timestamped copy will use what we have
67
- console.log(` msger: could not update ${baseName}${ext} (locked?) — using existing`);
81
+ try {
82
+ fs.mkdirSync(retireDir, { recursive: true });
83
+ const retired = path.join(retireDir, `${baseName}${ext}.old-${Date.now()}`);
84
+ fs.renameSync(srcBinary, retired);
85
+ fs.copyFileSync(buildBinary, srcBinary);
86
+ console.log(` msger: updated ${baseName}${ext} from build (retired the running copy aside)`);
87
+ } catch (e2) {
88
+ console.error(` msger: could not update ${baseName}${ext}: ${e2.message}`);
89
+ console.error(` msger: the package would ship a STALE binary — close any running msger and rebuild.`);
90
+ }
68
91
  }
92
+ // Sweep retired copies whose process has since exited. Still-locked
93
+ // ones stay for the next run; that's expected, not an error.
94
+ try {
95
+ for (const f of fs.readdirSync(retireDir)) {
96
+ try { fs.unlinkSync(path.join(retireDir, f)); } catch { /* still running */ }
97
+ }
98
+ } catch { /* no retire dir — nothing was ever locked */ }
69
99
  }
70
100
  }
71
101
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/msger",
3
- "version": "0.1.421",
3
+ "version": "0.1.423",
4
4
  "description": "Fast, lightweight, cross-platform message box - Rust-powered alternative to msgview",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -9,6 +9,13 @@
9
9
  *
10
10
  * Excludes `target/` (build output, gigabytes), `pibuild/` (Pi-specific),
11
11
  * and the existing `bin/` (Linux/Windows binaries — Mac doesn't need them).
12
+ *
13
+ * EXCEPT the image assets in `bin/`: `main.rs` pulls `../bin/msger.png` in with
14
+ * `include_bytes!`, so it is a compile-time input, not build output. Skipping
15
+ * the whole of `bin/` silently broke the Mac build the moment that include
16
+ * landed ("couldn't read `src/../bin/msger.png`"), which is why Mac binaries
17
+ * went stale. The binaries themselves are still excluded — only the small
18
+ * icon files come across.
12
19
  */
13
20
  import fs from "fs";
14
21
  import path from "path";
@@ -55,6 +62,28 @@ function copyDir(src, dst) {
55
62
 
56
63
  copyDir(srcDir, dstDir);
57
64
 
65
+ // bin/ is skipped wholesale above (it holds tens of MB of other platforms'
66
+ // binaries), but the icon assets in it are `include_bytes!` inputs the Rust
67
+ // build needs. Copy just those.
68
+ const binSrc = path.join(srcDir, "bin");
69
+ const binDst = path.join(dstDir, "bin");
70
+ if (fs.existsSync(binSrc)) {
71
+ fs.mkdirSync(binDst, { recursive: true });
72
+ for (const name of fs.readdirSync(binSrc)) {
73
+ if (!/\.(png|ico)$/i.test(name)) continue;
74
+ const s = path.join(binSrc, name);
75
+ const d = path.join(binDst, name);
76
+ if (!fs.statSync(s).isFile()) continue;
77
+ const dMtime = fs.existsSync(d) ? fs.statSync(d).mtimeMs : 0;
78
+ if (dMtime >= fs.statSync(s).mtimeMs) {
79
+ skipped++;
80
+ continue;
81
+ }
82
+ fs.copyFileSync(s, d);
83
+ copied++;
84
+ }
85
+ }
86
+
58
87
  console.log(` msger: prep:mac — ${copied} file(s) copied, ${skipped} unchanged`);
59
88
  console.log(` msger: source staged at mac/msger-native/. OneDrive will sync to the Mac;`);
60
89
  console.log(` msger: then run \`./build-mac.sh\` from the OneDrive folder on the Mac.`);
package/shower.d.ts CHANGED
@@ -23,8 +23,11 @@ export interface MessageBoxOptions {
23
23
  pos?: {
24
24
  x: number; /** Window X position in pixels */
25
25
  y: number; /** Window Y position in pixels */
26
- screen?: number; /** Optional screen index (0-based, Windows only) - NOT used by Rust binary */
26
+ screen?: number; /** Optional screen index (0-based). Offsets x/y into that monitor. Was Windows-only; now applied on Mac and X11 too. Wayland ignores client-set window positions entirely use `fullscreen` + `screen` there. */
27
27
  };
28
+ screen?: number; /** Monitor index (0-based) for `fullscreen`, and the fallback for `pos.screen`. The portable way to target a display: Wayland refuses client-set coordinates but honors fullscreen-on-a-given-output. */
29
+ overlay?: boolean; /** Frameless, transparent, click-through, always-on-top window drawn over the desktop — transient notices, e.g. per-monitor identification. Combine with `fullscreen` + `screen` to cover one display and `timeout` to dismiss it; an overlay has no close button and passes clicks through, so nothing in it can be operated. With `url` / `rawHtml` the page must set its own transparent background (the built-in template already does). */
30
+ listMonitors?: boolean; /** Print the monitor layout as JSON (`{monitors:[{index,name,x,y,width,height,scale,primary}]}`) and exit without opening a window. Indices match `screen`. */
28
31
  zoom?: number; /** Initial zoom level as percentage (100=100%, 150=150%, 50=50%, etc.) */
29
32
  autoSize?: boolean; /** Automatically resize window to fit content (default: false) */
30
33
  alwaysOnTop?: boolean; /** Keep window on top of other windows (default: false) */
package/shower.js CHANGED
@@ -637,9 +637,12 @@ function createMessageBoxHandle(options) {
637
637
  });
638
638
  // Send options to stdin
639
639
  try {
640
- // If autoSize is not explicitly set, enable it when no size is specified
640
+ // If autoSize is not explicitly set, enable it when no size is specified.
641
+ // Not for overlays: auto-sizing shrinks the window to its content,
642
+ // which for a full-screen overlay collapses the very area it is
643
+ // meant to cover. The caller's geometry (or fullscreen) wins there.
641
644
  const optionsToSend = { ...options };
642
- if (optionsToSend.autoSize === undefined && !optionsToSend.size) {
645
+ if (optionsToSend.autoSize === undefined && !optionsToSend.size && !optionsToSend.overlay) {
643
646
  optionsToSend.autoSize = true;
644
647
  }
645
648
  // Render mode: Rust wants a bare boolean plus a format string; the