janela 0.16.0 → 0.16.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/package.json +1 -1
- package/shim/wvshim.cc +142 -0
package/package.json
CHANGED
package/shim/wvshim.cc
CHANGED
|
@@ -358,6 +358,137 @@ bool run_file_dialog(const DialogRequest &req, std::vector<std::string> &out,
|
|
|
358
358
|
|
|
359
359
|
#if defined(__APPLE__)
|
|
360
360
|
|
|
361
|
+
// A standard macOS main menu.
|
|
362
|
+
//
|
|
363
|
+
// On macOS a Command-key shortcut is a MENU key equivalent, not a
|
|
364
|
+
// window-manager gesture the way Alt+F4 is on Windows. With no main menu
|
|
365
|
+
// nothing claims Cmd+Q, Cmd+C, Cmd+V, Cmd+Z or Cmd+A, and a webview app simply
|
|
366
|
+
// appears to ignore them — the window had zero menu bars, which is exactly
|
|
367
|
+
// what that looks like from the outside.
|
|
368
|
+
//
|
|
369
|
+
// webview.h leaves this to the embedder on purpose: webview/webview#127 is
|
|
370
|
+
// still open, and #237, which added precisely this Edit menu, was closed with
|
|
371
|
+
// "anyone who is impatient can always take this code on their own". Tauri does
|
|
372
|
+
// the same thing one layer up, through muda.
|
|
373
|
+
//
|
|
374
|
+
// Every item here is a STANDARD AppKit selector travelling up the responder
|
|
375
|
+
// chain, so there is nothing to call back into TypeScript for and no new FFI
|
|
376
|
+
// surface. WKWebView answers the editing ones itself. Custom menus — a
|
|
377
|
+
// declarative table passed down from the host — are a separate feature.
|
|
378
|
+
static void install_main_menu() {
|
|
379
|
+
using namespace webview::detail;
|
|
380
|
+
objc::autoreleasepool arp;
|
|
381
|
+
|
|
382
|
+
id app = objc::msg_send<id>(objc::get_class("NSApplication"),
|
|
383
|
+
objc::selector("sharedApplication"));
|
|
384
|
+
if (!app) return;
|
|
385
|
+
// Idempotent: an embedder that already installed a menu keeps it.
|
|
386
|
+
if (objc::msg_send<id>(app, objc::selector("mainMenu"))) return;
|
|
387
|
+
|
|
388
|
+
// NSEventModifierFlags. The default for a key equivalent is Command alone,
|
|
389
|
+
// so only the combinations need naming.
|
|
390
|
+
const NSUInteger kShift = 1UL << 17;
|
|
391
|
+
const NSUInteger kControl = 1UL << 18;
|
|
392
|
+
const NSUInteger kOption = 1UL << 19;
|
|
393
|
+
const NSUInteger kCommand = 1UL << 20;
|
|
394
|
+
|
|
395
|
+
auto str = [](const std::string &v) {
|
|
396
|
+
return cocoa::NSString_stringWithUTF8String(v);
|
|
397
|
+
};
|
|
398
|
+
auto alloc_init = [](const char *cls) {
|
|
399
|
+
return objc::msg_send<id>(
|
|
400
|
+
objc::msg_send<id>(objc::get_class(cls), objc::selector("alloc")),
|
|
401
|
+
objc::selector("init"));
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
// AppKit labels the first submenu from the bundle, not from this title, but
|
|
405
|
+
// "About <name>" and "Quit <name>" are ours to spell.
|
|
406
|
+
id process = objc::msg_send<id>(objc::get_class("NSProcessInfo"),
|
|
407
|
+
objc::selector("processInfo"));
|
|
408
|
+
const char *raw =
|
|
409
|
+
process ? objc::msg_send<const char *>(
|
|
410
|
+
objc::msg_send<id>(process, objc::selector("processName")),
|
|
411
|
+
objc::selector("UTF8String"))
|
|
412
|
+
: nullptr;
|
|
413
|
+
std::string name = raw ? raw : "App";
|
|
414
|
+
|
|
415
|
+
id menubar = alloc_init("NSMenu");
|
|
416
|
+
|
|
417
|
+
auto submenu = [&](const std::string &title) {
|
|
418
|
+
id holder = alloc_init("NSMenuItem");
|
|
419
|
+
id menu = objc::msg_send<id>(
|
|
420
|
+
objc::msg_send<id>(objc::get_class("NSMenu"), objc::selector("alloc")),
|
|
421
|
+
objc::selector("initWithTitle:"), str(title));
|
|
422
|
+
objc::msg_send<void>(holder, objc::selector("setSubmenu:"), menu);
|
|
423
|
+
objc::msg_send<void>(menubar, objc::selector("addItem:"), holder);
|
|
424
|
+
return menu;
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
auto item = [&](id menu, const std::string &title, const char *sel,
|
|
428
|
+
const std::string &key, NSUInteger mods) {
|
|
429
|
+
id it = objc::msg_send<id>(
|
|
430
|
+
objc::msg_send<id>(objc::get_class("NSMenuItem"),
|
|
431
|
+
objc::selector("alloc")),
|
|
432
|
+
objc::selector("initWithTitle:action:keyEquivalent:"), str(title),
|
|
433
|
+
sel ? objc::selector(sel) : nullptr, str(key));
|
|
434
|
+
if (mods) {
|
|
435
|
+
objc::msg_send<void>(
|
|
436
|
+
it, objc::selector("setKeyEquivalentModifierMask:"), mods);
|
|
437
|
+
}
|
|
438
|
+
objc::msg_send<void>(menu, objc::selector("addItem:"), it);
|
|
439
|
+
};
|
|
440
|
+
auto separator = [&](id menu) {
|
|
441
|
+
objc::msg_send<void>(menu, objc::selector("addItem:"),
|
|
442
|
+
objc::msg_send<id>(objc::get_class("NSMenuItem"),
|
|
443
|
+
objc::selector("separatorItem")));
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
id app_menu = submenu(name);
|
|
447
|
+
item(app_menu, "About " + name, "orderFrontStandardAboutPanel:", "", 0);
|
|
448
|
+
separator(app_menu);
|
|
449
|
+
item(app_menu, "Hide " + name, "hide:", "h", 0);
|
|
450
|
+
item(app_menu, "Hide Others", "hideOtherApplications:", "h",
|
|
451
|
+
kOption | kCommand);
|
|
452
|
+
item(app_menu, "Show All", "unhideAllApplications:", "", 0);
|
|
453
|
+
separator(app_menu);
|
|
454
|
+
// performClose:, not terminate: — measured, not assumed. Both quit and
|
|
455
|
+
// both exit 0, but terminate: exits the process itself, so the host never
|
|
456
|
+
// returns from wv_run and anything after app.run() is skipped;
|
|
457
|
+
// performClose: goes through the same window-close path the red button
|
|
458
|
+
// uses, the run loop unwinds, and the host prints its own "run returned 0".
|
|
459
|
+
// One shutdown path instead of two. muda picks terminate: because Tauri's
|
|
460
|
+
// app logic is native and multi-window; janela is single-window, so closing
|
|
461
|
+
// the window IS quitting. If janela ever grows multiple windows this has to
|
|
462
|
+
// become a real Quit that closes all of them.
|
|
463
|
+
//
|
|
464
|
+
// (An earlier note here claimed performClose: did not fire at all. That was
|
|
465
|
+
// a bad test: posted key events go to the FRONTMOST app, and the app was not
|
|
466
|
+
// active. With it activated first, both actions work.)
|
|
467
|
+
item(app_menu, "Quit " + name, "performClose:", "q", 0);
|
|
468
|
+
|
|
469
|
+
id edit = submenu("Edit");
|
|
470
|
+
item(edit, "Undo", "undo:", "z", 0);
|
|
471
|
+
item(edit, "Redo", "redo:", "z", kShift | kCommand);
|
|
472
|
+
separator(edit);
|
|
473
|
+
item(edit, "Cut", "cut:", "x", 0);
|
|
474
|
+
item(edit, "Copy", "copy:", "c", 0);
|
|
475
|
+
item(edit, "Paste", "paste:", "v", 0);
|
|
476
|
+
item(edit, "Select All", "selectAll:", "a", 0);
|
|
477
|
+
|
|
478
|
+
id view = submenu("View");
|
|
479
|
+
item(view, "Enter Full Screen", "toggleFullScreen:", "f",
|
|
480
|
+
kControl | kCommand);
|
|
481
|
+
|
|
482
|
+
id window = submenu("Window");
|
|
483
|
+
item(window, "Minimize", "performMiniaturize:", "m", 0);
|
|
484
|
+
item(window, "Close", "performClose:", "w", 0);
|
|
485
|
+
|
|
486
|
+
objc::msg_send<void>(app, objc::selector("setMainMenu:"), menubar);
|
|
487
|
+
// Lets AppKit add the standard Window-menu bookkeeping (window list,
|
|
488
|
+
// Bring All to Front).
|
|
489
|
+
objc::msg_send<void>(app, objc::selector("setWindowsMenu:"), window);
|
|
490
|
+
}
|
|
491
|
+
|
|
361
492
|
bool run_file_dialog(const DialogRequest &req, std::vector<std::string> &out,
|
|
362
493
|
std::string &error) {
|
|
363
494
|
(void)error;
|
|
@@ -608,6 +739,14 @@ bool run_file_dialog(const DialogRequest &req, std::vector<std::string> &out,
|
|
|
608
739
|
|
|
609
740
|
#endif
|
|
610
741
|
|
|
742
|
+
#if !defined(__APPLE__)
|
|
743
|
+
// Windows and Linux need no menu for these shortcuts: Alt+F4 is a
|
|
744
|
+
// window-manager message the win32 backend already answers as WM_CLOSE, and
|
|
745
|
+
// the editing keys are handled inside WebView2 and WebKitGTK. A menu here
|
|
746
|
+
// would be a feature, not a fix.
|
|
747
|
+
static void install_main_menu() {}
|
|
748
|
+
#endif
|
|
749
|
+
|
|
611
750
|
// Runs on the UI thread with no TS frame beneath it — see the note on the job
|
|
612
751
|
// pool above for why that matters.
|
|
613
752
|
void dialog_on_ui_thread(webview_t, void *arg) {
|
|
@@ -728,6 +867,9 @@ int32_t wv_create(int32_t debug) {
|
|
|
728
867
|
if (g_apps[i].used) continue;
|
|
729
868
|
webview_t w = webview_create(debug, nullptr);
|
|
730
869
|
if (!w) return -1;
|
|
870
|
+
// After webview_create, which is what brings NSApplication into being.
|
|
871
|
+
// A no-op off macOS.
|
|
872
|
+
install_main_menu();
|
|
731
873
|
// Field-wise reset: App holds a thread and atomics, so it is not
|
|
732
874
|
// copy-assignable from a temporary.
|
|
733
875
|
g_apps[i].binds.clear();
|