ReviewOS

also looking at this

craft-native/craft

fix(shortcuts): make global hotkeys actually fire

#54
Merged glennmichael123 wants to merge fix/global-shortcuts-never-fire into main
11 files +2435 -417
packages/zig/build.zigmodified+82-10
Changes to packages/zig/build.zig
@@ -165,10 +165,6 @@ pub fn build(b: *std.Build) void {
165165 .root_source_file = b.path("src/menubar.zig"),
166166 });
167167
168 const bridge_menu_module = b.createModule(.{
169 .root_source_file = b.path("src/bridge_menu.zig"),
170 });
171
172168 const components_module = b.createModule(.{
173169 .root_source_file = b.path("src/components.zig"),
174170 });
@@ -561,6 +557,54 @@ pub fn build(b: *std.Build) void {
561557 }),
562558 });
563559
560 // Accelerator strings ("Cmd+Shift+H") and the Carbon hotkey binding they
561 // are registered through. Both are pure enough to test without a window.
562 const accelerator_tests = b.addTest(.{
563 .root_module = b.createModule(.{
564 .root_source_file = b.path("src/accelerator.zig"),
565 .target = target,
566 .optimize = optimize,
567 }),
568 });
569
570 const macos_hotkey_tests = b.addTest(.{
571 .root_module = b.createModule(.{
572 .root_source_file = b.path("src/macos_hotkey.zig"),
573 .target = target,
574 .optimize = optimize,
575 }),
576 });
577 if (target_os == .macos) {
578 // These tests put a real `EventRef` through Carbon's own dispatcher —
579 // the only way to find out whether the handler signature and the
580 // `EventHotKeyID` layout are what Carbon expects, short of pressing
581 // the key on a Mac.
582 macos_hotkey_tests.root_module.linkFramework("Carbon", .{});
583 macos_hotkey_tests.root_module.link_libc = true;
584 }
585
586 // The global-shortcut registry: register / replace / disable / unregister,
587 // driven against a fake platform so the whole lifecycle is covered without
588 // an app, a window or a key press.
589 const shortcut_registry_tests = b.addTest(.{
590 .root_module = b.createModule(.{
591 .root_source_file = b.path("src/shortcut_registry.zig"),
592 .target = target,
593 .optimize = optimize,
594 }),
595 });
596
597 // The name <-> macOS virtual key code table that global shortcuts are
598 // registered from. Pure data, so it is tested here rather than by pressing
599 // keys at a running app.
600 const key_codes_tests = b.addTest(.{
601 .root_module = b.createModule(.{
602 .root_source_file = b.path("src/key_codes.zig"),
603 .target = target,
604 .optimize = optimize,
605 }),
606 });
607
564608 const config_tests = b.addTest(.{
565609 .root_module = b.createModule(.{
566610 .root_source_file = b.path("test/config_test.zig"),
@@ -897,6 +941,10 @@ pub fn build(b: *std.Build) void {
897941 const run_space_list_tests = b.addRunArtifact(space_list_tests);
898942 const run_local_tls_tests = b.addRunArtifact(local_tls_tests);
899943 const run_menu_roles_tests = b.addRunArtifact(menu_roles_tests);
944 const run_key_codes_tests = b.addRunArtifact(key_codes_tests);
945 const run_accelerator_tests = b.addRunArtifact(accelerator_tests);
946 const run_macos_hotkey_tests = b.addRunArtifact(macos_hotkey_tests);
947 const run_shortcut_registry_tests = b.addRunArtifact(shortcut_registry_tests);
900948 const run_config_tests = b.addRunArtifact(config_tests);
901949 const run_ipc_tests = b.addRunArtifact(ipc_tests);
902950 const run_performance_tests = b.addRunArtifact(performance_tests);
@@ -945,12 +993,17 @@ pub fn build(b: *std.Build) void {
945993 .optimize = optimize,
946994 .imports = &.{
947995 .{ .name = "js", .module = js_dep.module("js") },
948 // The native half of the menu contract. The test drives
949 // the real craft-bridge.js and then decodes what it
950 // posted with the very parser `setAppMenu` uses, so a
951 // rename on either side fails the build rather than
952 // silently producing a menu nobody can click (#27, #31).
953 .{ .name = "../src/bridge_menu.zig", .module = bridge_menu_module },
996 // The native halves of the contracts under test. The
997 // test drives the real craft-bridge.js and then decodes
998 // what it posted with the very code the bridge
999 // dispatches to, so a rename on either side fails the
1000 // build rather than silently producing a menu nobody
1001 // can click (#27, #31) or a hotkey that never fires
1002 // (#47). One module, because both reach
1003 // `bridge_error.zig` — see the file's own comment.
1004 .{ .name = "bridge_contracts", .module = b.createModule(.{
1005 .root_source_file = b.path("src/bridge_contracts.zig"),
1006 }) },
9541007 },
9551008 }),
9561009 });
@@ -998,6 +1051,10 @@ pub fn build(b: *std.Build) void {
9981051 test_step.dependOn(&run_space_list_tests.step);
9991052 test_step.dependOn(&run_local_tls_tests.step);
10001053 test_step.dependOn(&run_menu_roles_tests.step);
1054 test_step.dependOn(&run_key_codes_tests.step);
1055 test_step.dependOn(&run_accelerator_tests.step);
1056 test_step.dependOn(&run_macos_hotkey_tests.step);
1057 test_step.dependOn(&run_shortcut_registry_tests.step);
10011058 test_step.dependOn(&run_config_tests.step);
10021059 test_step.dependOn(&run_ipc_tests.step);
10031060 test_step.dependOn(&run_performance_tests.step);
@@ -1097,6 +1154,16 @@ pub fn build(b: *std.Build) void {
10971154 const test_menu_roles_step = b.step("test:menu-roles", "Run menu role table tests");
10981155 test_menu_roles_step.dependOn(&run_menu_roles_tests.step);
10991156
1157 const test_key_codes_step = b.step("test:key-codes", "Run key code table tests");
1158 test_key_codes_step.dependOn(&run_key_codes_tests.step);
1159
1160 const test_accelerator_step = b.step("test:accelerator", "Run accelerator parsing tests");
1161 test_accelerator_step.dependOn(&run_accelerator_tests.step);
1162 test_accelerator_step.dependOn(&run_macos_hotkey_tests.step);
1163
1164 const test_shortcut_registry_step = b.step("test:shortcut-registry", "Run global shortcut registry tests");
1165 test_shortcut_registry_step.dependOn(&run_shortcut_registry_tests.step);
1166
11001167 const test_config_step = b.step("test:config", "Run Config tests");
11011168 test_config_step.dependOn(&run_config_tests.step);
11021169
@@ -1599,6 +1666,11 @@ fn linkPlatformLibraries(b: *std.Build, module: *std.Build.Module, target_os: st
15991666 // when the app dynamically loads them.
16001667 module.linkFramework("CoreMIDI", .{});
16011668 module.linkFramework("CoreSpotlight", .{});
1669 // Carbon for `macos_hotkey.zig`: `RegisterEventHotKey` is the only
1670 // route to a global hotkey that neither needs an Objective-C block
1671 // (which Zig cannot write) nor Accessibility permission (which a
1672 // CGEventTap would).
1673 module.linkFramework("Carbon", .{});
16021674 applySdkPaths(b, module, sdk_path);
16031675 },
16041676 .linux => {