also looking at this
fix(shortcuts): make global hotkeys actually fire
#54
11 files
+2435
-417
| @@ -165,10 +165,6 @@ pub fn build(b: *std.Build) void { | ||
| 165 | 165 | .root_source_file = b.path("src/menubar.zig"), |
| 166 | 166 | }); |
| 167 | 167 | |
| 168 | const bridge_menu_module = b.createModule(.{ | |
| 169 | .root_source_file = b.path("src/bridge_menu.zig"), | |
| 170 | }); | |
| 171 | ||
| 172 | 168 | const components_module = b.createModule(.{ |
| 173 | 169 | .root_source_file = b.path("src/components.zig"), |
| 174 | 170 | }); |
| @@ -561,6 +557,54 @@ pub fn build(b: *std.Build) void { | ||
| 561 | 557 | }), |
| 562 | 558 | }); |
| 563 | 559 | |
| 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 | ||
| 564 | 608 | const config_tests = b.addTest(.{ |
| 565 | 609 | .root_module = b.createModule(.{ |
| 566 | 610 | .root_source_file = b.path("test/config_test.zig"), |
| @@ -897,6 +941,10 @@ pub fn build(b: *std.Build) void { | ||
| 897 | 941 | const run_space_list_tests = b.addRunArtifact(space_list_tests); |
| 898 | 942 | const run_local_tls_tests = b.addRunArtifact(local_tls_tests); |
| 899 | 943 | 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); | |
| 900 | 948 | const run_config_tests = b.addRunArtifact(config_tests); |
| 901 | 949 | const run_ipc_tests = b.addRunArtifact(ipc_tests); |
| 902 | 950 | const run_performance_tests = b.addRunArtifact(performance_tests); |
| @@ -945,12 +993,17 @@ pub fn build(b: *std.Build) void { | ||
| 945 | 993 | .optimize = optimize, |
| 946 | 994 | .imports = &.{ |
| 947 | 995 | .{ .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 | }) }, | |
| 954 | 1007 | }, |
| 955 | 1008 | }), |
| 956 | 1009 | }); |
| @@ -998,6 +1051,10 @@ pub fn build(b: *std.Build) void { | ||
| 998 | 1051 | test_step.dependOn(&run_space_list_tests.step); |
| 999 | 1052 | test_step.dependOn(&run_local_tls_tests.step); |
| 1000 | 1053 | 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); | |
| 1001 | 1058 | test_step.dependOn(&run_config_tests.step); |
| 1002 | 1059 | test_step.dependOn(&run_ipc_tests.step); |
| 1003 | 1060 | test_step.dependOn(&run_performance_tests.step); |
| @@ -1097,6 +1154,16 @@ pub fn build(b: *std.Build) void { | ||
| 1097 | 1154 | const test_menu_roles_step = b.step("test:menu-roles", "Run menu role table tests"); |
| 1098 | 1155 | test_menu_roles_step.dependOn(&run_menu_roles_tests.step); |
| 1099 | 1156 | |
| 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 | ||
| 1100 | 1167 | const test_config_step = b.step("test:config", "Run Config tests"); |
| 1101 | 1168 | test_config_step.dependOn(&run_config_tests.step); |
| 1102 | 1169 | |
| @@ -1599,6 +1666,11 @@ fn linkPlatformLibraries(b: *std.Build, module: *std.Build.Module, target_os: st | ||
| 1599 | 1666 | // when the app dynamically loads them. |
| 1600 | 1667 | module.linkFramework("CoreMIDI", .{}); |
| 1601 | 1668 | 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", .{}); | |
| 1602 | 1674 | applySdkPaths(b, module, sdk_path); |
| 1603 | 1675 | }, |
| 1604 | 1676 | .linux => { |