From beac63849ce65b81a5a6002e2339d4d4ddbe17d2 Mon Sep 17 00:00:00 2001 From: Ludovic Marie <136255825+ludovic111@users.noreply.github.com> Date: Tue, 27 Oct 2026 08:42:36 +0100 Subject: [PATCH] refactor: move the client's input modules into horizon-client-input Key bindings and names, toggle keys, mouse options, controller settings, the clipboard and window options move to their own crate, re-exported under their old paths. Their unit tests now rebuild in about a second after an edit instead of rebuilding the client lib's test binary. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 5 ++-- Cargo.lock | 14 ++++++++++- Cargo.toml | 1 + crates/horizon-client-input/Cargo.toml | 23 +++++++++++++++++++ .../src/clipboard.rs | 0 .../src/gamepad.rs | 0 .../src/input.rs | 0 .../src/interaction_input.rs | 0 .../src/key_names.rs | 0 .../src/keybinds.rs | 0 .../src/keymapping.rs | 0 crates/horizon-client-input/src/lib.rs | 14 +++++++++++ .../src/mouse.rs | 0 .../src/window.rs | 0 crates/horizon-client/Cargo.toml | 4 ++-- crates/horizon-client/src/lib.rs | 18 +++++++-------- docs/controllers.md | 2 +- tools/test_frame_limiter_parity.py | 2 +- tools/test_interaction_input_parity.py | 2 +- tools/test_local_input_parity.py | 2 +- tools/test_options_parity.py | 4 ++-- tools/test_sprint_input_parity.py | 2 +- tools/test_toggle_keys_parity.py | 2 +- 23 files changed, 73 insertions(+), 22 deletions(-) create mode 100644 crates/horizon-client-input/Cargo.toml rename crates/{horizon-client => horizon-client-input}/src/clipboard.rs (100%) rename crates/{horizon-client => horizon-client-input}/src/gamepad.rs (100%) rename crates/{horizon-client => horizon-client-input}/src/input.rs (100%) rename crates/{horizon-client => horizon-client-input}/src/interaction_input.rs (100%) rename crates/{horizon-client => horizon-client-input}/src/key_names.rs (100%) rename crates/{horizon-client => horizon-client-input}/src/keybinds.rs (100%) rename crates/{horizon-client => horizon-client-input}/src/keymapping.rs (100%) create mode 100644 crates/horizon-client-input/src/lib.rs rename crates/{horizon-client => horizon-client-input}/src/mouse.rs (100%) rename crates/{horizon-client => horizon-client-input}/src/window.rs (100%) diff --git a/AGENTS.md b/AGENTS.md index 0eb1fe8b56..2f754ed53a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,6 +38,7 @@ Game workspace (root `Cargo.toml`); `launcher/` and `crates/horizon-telemetry-se | `horizon-client` | The game: a lib (`src/lib.rs`, ~180 flat modules) and the bin (`src/main.rs` → `game.rs` + `game/`), plus `horizon-ctl` (`src/bin/horizon-ctl/`). Also the dedicated server (`--dedicated`) | | `horizon-command` | Command parsing (Brigadier): the vanilla tree, argument types, selectors, suggestions, the tree's network form. Re-exported as `horizon_client::command` | | `horizon-client-core` | The client lib's bottom layer: `text.rs`, `lang.rs`, `snbt.rs`, `nbt_path.rs`, `component.rs`, `scoreboard.rs`, `bossbar.rs`, `gamerules.rs`, `block_entity_nbt.rs`, registry/particle/sound tables. Re-exported under the old paths (`horizon_client::text`...) | +| `horizon-client-input` | Key bindings and key names, toggle keys, mouse options, controller settings and glyphs, the clipboard, window options (`keybinds.rs`, `gamepad.rs`, `mouse.rs`, `window.rs`...). Re-exported under the old paths | | `horizon-world` | Vanilla world data with no gameplay loop: NBT, regions, chunks, `datafix/`, `worldgen/`, `level/` (server-side block behaviour, ticks, redstone), entity physics, items, effects, food, damage | | `horizon-core` | Simulation primitives: Java `Random`s, `jmath` (HotSpot-exact `Math`), positions, noise, density, paletted storage, `version.rs` (the pinned version) | | `horizon-protocol` | Network protocol 777: codecs, framing, encryption, `packets/{handshake,status,login,configuration,play}.rs`, `entity_data.rs` | @@ -112,7 +113,7 @@ python3 tools/agent_control_e2e.py # horizon-ctl / MCP end to end | Subsystem | Files | |---|---| -| Game loop, tick, frame | `game.rs`: `fn run`, `impl ApplicationHandler for Game` (`window_event`, `about_to_wait`), `fn tick`, `fn tick_world`, `fn render`; L: `clock.rs`, `window.rs`, `spans.rs` | +| Game loop, tick, frame | `game.rs`: `fn run`, `impl ApplicationHandler for Game` (`window_event`, `about_to_wait`), `fn tick`, `fn tick_world`, `fn render`; L: `clock.rs`, `spans.rs`; `horizon-client-input/src/window.rs` | | Rendering | L: `gfx.rs` (`Renderer::new`, `Renderer::render`), `gfx/` (cull, occlusion, translucency, upload, timing, lod, weather, stars, end_sky), `*.wgsl`, `particles.rs`, `block_entity_render.rs`, `item_render.rs`, `fog.rs`, `clouds.rs`, `camera.rs`, `first_person.rs`; G: `render_tick.rs`, `item_draw.rs` | | Chunks, streaming, light | L: `streaming.rs` (worker pool: generate, light, mesh), `world.rs`, `chunk.rs`, `chunk_map.rs`, `light.rs`, `mesher.rs` + `mesher/`, `vanillagen.rs` (binds horizon-world's generator), `lod.rs` + `lod/`, `ticket_levels.rs`; G: `world_loading.rs`, `postprocess.rs`; game.rs `fn update_world` | | World generation | `crates/horizon-world/src/worldgen/` (`generator*.rs`, `feature_graph.rs`, `structure/`, `carver/`) | @@ -128,7 +129,7 @@ python3 tools/agent_control_e2e.py # horizon-ctl / MCP end to end | Integrated server | G: `integrated.rs` (the server thread), `levels.rs` (one level per dimension, `fn tick_server`), `server_travel.rs` | | Dedicated server | G: `dedicated.rs`, `dedicated/status.rs`; crates `horizon-server-files`, `horizon-server` | | Save and load | L: `save.rs` (level.dat, regions, playerdata), `entity_nbt.rs`, `options_file.rs`; `horizon-client-core/src/block_entity_nbt.rs`; G: `saving.rs`, `persist.rs` (entities); `crates/horizon-world/src/{region.rs,nbt.rs,datafix/}` | -| Input | game.rs `fn window_event`, `fn key_pressed`, `fn play_mouse`; L: `keybinds.rs`, `keymapping.rs`, `key_names.rs`, `mouse.rs`, `input.rs`, `interaction_input.rs`, `gamepad.rs`, `osk.rs`, `clipboard.rs`; G: `pad.rs`, `pad_menu.rs` | +| Input | game.rs `fn window_event`, `fn key_pressed`, `fn play_mouse`; crate `horizon-client-input` (`keybinds.rs`, `keymapping.rs`, `key_names.rs`, `mouse.rs`, `input.rs`, `interaction_input.rs`, `gamepad.rs`, `clipboard.rs`); L: `osk.rs`; G: `pad.rs`, `pad_menu.rs` | | Audio | L: `audio.rs` (mixer), `sound_engine.rs`, `sound_events.rs`, `music.rs`, `ambience.rs`; G: `music.rs`, `subtitle_hud.rs` | | Resource packs | L: `packs.rs` + `packs/`, `pack_screen.rs`, `atlas_sources.rs`, `sprites.rs`, `mipmap.rs`, `model_library.rs`, `font.rs`; `horizon-client-core/src/lang.rs`; crate `horizon-assets`; G: `packs.rs` | | Agent control | L: `control.rs` (socket), `bin/horizon-ctl/`; G: `control.rs`, `agent.rs`, `events.rs`, `ui_control.rs` ([docs/agent-control.md](docs/agent-control.md)) | diff --git a/Cargo.lock b/Cargo.lock index 0e0a390380..2af972983a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1363,7 +1363,6 @@ name = "horizon-client" version = "0.1.0" dependencies = [ "ab_glyph", - "arboard", "bytemuck", "chrono", "cpal", @@ -1373,6 +1372,7 @@ dependencies = [ "horizon-assets", "horizon-bench", "horizon-client-core", + "horizon-client-input", "horizon-command", "horizon-core", "horizon-dualsense", @@ -1413,6 +1413,18 @@ dependencies = [ "serde_json", ] +[[package]] +name = "horizon-client-input" +version = "0.1.0" +dependencies = [ + "arboard", + "horizon-client-core", + "horizon-dualsense", + "horizon-theme", + "png", + "winit", +] + [[package]] name = "horizon-command" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 933901dce8..967ce08e82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "crates/horizon-world", "crates/horizon-client", "crates/horizon-client-core", + "crates/horizon-client-input", "crates/horizon-command", "crates/horizon-server-files", "crates/horizon-server", diff --git a/crates/horizon-client-input/Cargo.toml b/crates/horizon-client-input/Cargo.toml new file mode 100644 index 0000000000..e1d7cf72bf --- /dev/null +++ b/crates/horizon-client-input/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "horizon-client-input" +version = "0.1.0" +edition.workspace = true +license.workspace = true +rust-version = "1.90" +description = "The client's input layer: key bindings and their names, mouse, controllers, clipboard and window options" + +[dependencies] +horizon-client-core = { path = "../horizon-client-core" } +winit = "0.30.13" +# The window icon. +horizon-theme = { path = "../horizon-theme" } +png = "0.18" +arboard = { version = "3.6.1", default-features = false, features = ["wayland-data-control"] } +# The DualSense's trigger effects (the `controllers` feature). +horizon-dualsense = { path = "../horizon-dualsense", optional = true } + +[features] +controllers = ["dep:horizon-dualsense"] + +[lints] +workspace = true diff --git a/crates/horizon-client/src/clipboard.rs b/crates/horizon-client-input/src/clipboard.rs similarity index 100% rename from crates/horizon-client/src/clipboard.rs rename to crates/horizon-client-input/src/clipboard.rs diff --git a/crates/horizon-client/src/gamepad.rs b/crates/horizon-client-input/src/gamepad.rs similarity index 100% rename from crates/horizon-client/src/gamepad.rs rename to crates/horizon-client-input/src/gamepad.rs diff --git a/crates/horizon-client/src/input.rs b/crates/horizon-client-input/src/input.rs similarity index 100% rename from crates/horizon-client/src/input.rs rename to crates/horizon-client-input/src/input.rs diff --git a/crates/horizon-client/src/interaction_input.rs b/crates/horizon-client-input/src/interaction_input.rs similarity index 100% rename from crates/horizon-client/src/interaction_input.rs rename to crates/horizon-client-input/src/interaction_input.rs diff --git a/crates/horizon-client/src/key_names.rs b/crates/horizon-client-input/src/key_names.rs similarity index 100% rename from crates/horizon-client/src/key_names.rs rename to crates/horizon-client-input/src/key_names.rs diff --git a/crates/horizon-client/src/keybinds.rs b/crates/horizon-client-input/src/keybinds.rs similarity index 100% rename from crates/horizon-client/src/keybinds.rs rename to crates/horizon-client-input/src/keybinds.rs diff --git a/crates/horizon-client/src/keymapping.rs b/crates/horizon-client-input/src/keymapping.rs similarity index 100% rename from crates/horizon-client/src/keymapping.rs rename to crates/horizon-client-input/src/keymapping.rs diff --git a/crates/horizon-client-input/src/lib.rs b/crates/horizon-client-input/src/lib.rs new file mode 100644 index 0000000000..53d8f889ad --- /dev/null +++ b/crates/horizon-client-input/src/lib.rs @@ -0,0 +1,14 @@ +//! The client's input layer, below gameplay and screens: key bindings and their +//! names, toggle keys, mouse settings, controllers, the clipboard and window +//! options. horizon-client re-exports each module under its old path +//! (`horizon_client::keybinds`, ...). +#![forbid(unsafe_code)] +pub mod clipboard; +pub mod gamepad; +pub mod input; +pub mod interaction_input; +pub mod key_names; +pub mod keybinds; +pub mod keymapping; +pub mod mouse; +pub mod window; diff --git a/crates/horizon-client/src/mouse.rs b/crates/horizon-client-input/src/mouse.rs similarity index 100% rename from crates/horizon-client/src/mouse.rs rename to crates/horizon-client-input/src/mouse.rs diff --git a/crates/horizon-client/src/window.rs b/crates/horizon-client-input/src/window.rs similarity index 100% rename from crates/horizon-client/src/window.rs rename to crates/horizon-client-input/src/window.rs diff --git a/crates/horizon-client/Cargo.toml b/crates/horizon-client/Cargo.toml index 5611edbc9b..a3a047176b 100644 --- a/crates/horizon-client/Cargo.toml +++ b/crates/horizon-client/Cargo.toml @@ -10,6 +10,7 @@ description = "Horizon native development client" [dependencies] horizon-assets = { path = "../horizon-assets" } horizon-client-core = { path = "../horizon-client-core" } +horizon-client-input = { path = "../horizon-client-input" } horizon-command = { path = "../horizon-command" } horizon-core = { path = "../horizon-core" } # The server thread ahead of the workers (MinecraftServer.spin's Thread.setPriority). @@ -54,7 +55,6 @@ horizon-theme = { path = "../horizon-theme" } # the anonymous "I'm playing" ping the game sends itself (`heartbeat.rs`). horizon-telemetry = { path = "../horizon-telemetry", default-features = false, features = ["live"] } horizon-bench = { path = "../horizon-bench" } -arboard = { version = "3.6.1", default-features = false, features = ["wayland-data-control"] } zip = { version = "2", default-features = false, features = ["deflate"] } # Controllers (evdev + libudev on Linux, IOKit on macOS, Windows.Gaming.Input); the `controllers` feature. gilrs = { version = "0.11", optional = true } @@ -88,7 +88,7 @@ default = ["audio", "controllers"] # Linux: the server build (`--no-default-features`, released as horizon-engine; docs/server.md). audio = ["dep:cpal"] # Controllers: gilrs, and the DualSense's own output. Without it only `horizon-ctl pad`'s virtual pad. -controllers = ["dep:gilrs", "dep:horizon-dualsense"] +controllers = ["dep:gilrs", "dep:horizon-dualsense", "horizon-client-input/controllers"] # Runs code mods (wasmtime and cranelift: a quarter of a clean build). On in every release build # (release.yml); the tests and probes don't need it. Content mods load without it. mods = ["horizon-mods/runtime"] diff --git a/crates/horizon-client/src/lib.rs b/crates/horizon-client/src/lib.rs index fa5ef1c4d4..f2ce492f5e 100644 --- a/crates/horizon-client/src/lib.rs +++ b/crates/horizon-client/src/lib.rs @@ -32,7 +32,6 @@ pub mod cauldron; pub mod chat; pub mod chunk; pub mod chunk_map; -pub mod clipboard; pub mod clock; pub mod clouds; pub mod collisions; @@ -74,7 +73,6 @@ pub mod fog; pub mod font; pub mod friends_screen; pub mod game_mode_switcher; -pub mod gamepad; pub mod gfx; pub mod gizmos; pub mod gossip; @@ -88,8 +86,6 @@ pub mod hud; pub mod hud_layout; pub mod hud_overlays; pub mod hunger; -pub mod input; -pub mod interaction_input; pub mod inv_sort; pub mod inventory_effects; pub mod item_data; @@ -98,9 +94,6 @@ pub mod item_sync; pub mod item_tooltip; pub mod items; pub mod java_text; -pub mod key_names; -pub mod keybinds; -pub mod keymapping; pub mod lan; pub mod landing; pub mod level_loading; @@ -120,7 +113,6 @@ pub mod mod_list; mod model_library; pub mod models; pub mod motion; -pub mod mouse; pub mod moved_extras; pub mod multiplayer; pub mod music; @@ -190,7 +182,6 @@ pub mod vault; pub mod weather; pub mod widgets; pub mod win_screen; -pub mod window; pub mod world; pub mod world_spawn; pub mod worldborder; @@ -206,4 +197,13 @@ pub use horizon_client_core::nbt_path; pub use horizon_client_core::scoreboard; pub use horizon_client_core::snbt; pub use horizon_client_core::text; +pub use horizon_client_input::clipboard; +pub use horizon_client_input::gamepad; +pub use horizon_client_input::input; +pub use horizon_client_input::interaction_input; +pub use horizon_client_input::key_names; +pub use horizon_client_input::keybinds; +pub use horizon_client_input::keymapping; +pub use horizon_client_input::mouse; +pub use horizon_client_input::window; pub use horizon_command as command; diff --git a/docs/controllers.md b/docs/controllers.md index c2ac6b5d35..b94e07b9c5 100644 --- a/docs/controllers.md +++ b/docs/controllers.md @@ -25,7 +25,7 @@ Select/Start). No second windowing or event stack: gilrs is polled once per loop iteration next to winit's events. - The pure logic (mapping table, deadzones and curves, button edges, the - settings file, glyph choice) lives in `crates/horizon-client/src/gamepad.rs` + settings file, glyph choice) lives in `crates/horizon-client-input/src/gamepad.rs` and is unit-tested. The game owns a thin gilrs backend and applies actions. - Controller actions go through the same path a keyboard key takes after rebinding: an action names a vanilla key mapping (`key.jump`, `key.attack`, diff --git a/tools/test_frame_limiter_parity.py b/tools/test_frame_limiter_parity.py index 1ff2099bec..1e1fdfd75f 100644 --- a/tools/test_frame_limiter_parity.py +++ b/tools/test_frame_limiter_parity.py @@ -62,7 +62,7 @@ def main(): 'real OS sleep accuracy is outside the comparison.') harness.differential( 'rust-frame-limiter-parity', 'VanillaFrameLimiterOracle', 'frame_limiter_probe', make_cases, scope, - sources=[lab.ROOT / 'crates/horizon-client/src/window.rs']) + sources=[lab.ROOT / 'crates/horizon-client-input/src/window.rs']) if __name__ == '__main__': diff --git a/tools/test_interaction_input_parity.py b/tools/test_interaction_input_parity.py index 1aa34a73f7..1944a2ac78 100644 --- a/tools/test_interaction_input_parity.py +++ b/tools/test_interaction_input_parity.py @@ -43,7 +43,7 @@ def main(): "Includes passing entity interactions over solid blocks: only the held " "item may be tried afterward. " "Game-mode side effects and world simulation are controlled fixtures.", - sources=[lab.ROOT / "crates/horizon-client/src/interaction_input.rs", + sources=[lab.ROOT / "crates/horizon-client-input/src/interaction_input.rs", lab.ROOT / "crates/horizon-client/src/game/remote.rs", lab.ROOT / "crates/horizon-client/src/game/hand.rs", lab.ROOT / "mods/horizon/src/test/java/dev/horizonlab/parity/ParityData.java"]) diff --git a/tools/test_local_input_parity.py b/tools/test_local_input_parity.py index c50087138e..6d46dcf29f 100644 --- a/tools/test_local_input_parity.py +++ b/tools/test_local_input_parity.py @@ -35,7 +35,7 @@ def main(): build=lab.LOCAL/'rust-local-input';build.mkdir(exist_ok=True) report=build/'report.json';report.unlink(missing_ok=True) oracle=lab.ROOT/'mods/horizon/src/test/java/dev/horizonlab/parity/VanillaLocalInputOracle.java' - sources=[Path(__file__),lab.ROOT/'tools/test_rust_parity.py',lab.ROOT/'tools/test_model_baking_parity.py',lab.ROOT/'tools/lab.py',oracle,lab.ROOT/'Cargo.toml',lab.ROOT/'Cargo.lock',lab.ROOT/'crates/horizon-client/Cargo.toml',lab.ROOT/'crates/horizon-client/src/input.rs',lab.ROOT/'crates/horizon-client/src/lib.rs',lab.ROOT/'crates/horizon-client/examples/probe/input_probe.rs'] + sources=[Path(__file__),lab.ROOT/'tools/test_rust_parity.py',lab.ROOT/'tools/test_model_baking_parity.py',lab.ROOT/'tools/lab.py',oracle,lab.ROOT/'Cargo.toml',lab.ROOT/'Cargo.lock',lab.ROOT/'crates/horizon-client/Cargo.toml',lab.ROOT/'crates/horizon-client-input/src/input.rs',lab.ROOT/'crates/horizon-client/src/lib.rs',lab.ROOT/'crates/horizon-client/examples/probe/input_probe.rs'] tested=base.source_hashes(sources) def run(command,name): with (build/(name+'.log')).open('w') as log:subprocess.run(list(map(str,command)),cwd=lab.ROOT,check=True,timeout=600,stdout=log,stderr=subprocess.STDOUT) diff --git a/tools/test_options_parity.py b/tools/test_options_parity.py index e99f67a44c..82bc5b9ec7 100644 --- a/tools/test_options_parity.py +++ b/tools/test_options_parity.py @@ -189,8 +189,8 @@ def main(): harness.differential( 'rust-options-parity', 'VanillaOptionsOracle', 'options_probe', make_cases, scope, sources=[lab.ROOT / 'crates/horizon-client/src/options_file.rs', lab.ROOT / 'crates/horizon-client/src/options_fixes.rs', - lab.ROOT / 'crates/horizon-client/src/java_text.rs', lab.ROOT / 'crates/horizon-client/src/key_names.rs', - lab.ROOT / 'crates/horizon-client/src/keybinds.rs']) + lab.ROOT / 'crates/horizon-client/src/java_text.rs', lab.ROOT / 'crates/horizon-client-input/src/key_names.rs', + lab.ROOT / 'crates/horizon-client-input/src/keybinds.rs']) if __name__ == '__main__': diff --git a/tools/test_sprint_input_parity.py b/tools/test_sprint_input_parity.py index b2b86b7c29..d895308260 100644 --- a/tools/test_sprint_input_parity.py +++ b/tools/test_sprint_input_parity.py @@ -40,7 +40,7 @@ def main(): build=lab.LOCAL/'rust-sprint-input';build.mkdir(exist_ok=True) report=build/'report.json';report.unlink(missing_ok=True) oracle=lab.ROOT/'mods/horizon/src/test/java/dev/horizonlab/parity/VanillaSprintInputOracle.java' - sources=[Path(__file__),lab.ROOT/'tools/test_rust_parity.py',lab.ROOT/'tools/test_model_baking_parity.py',lab.ROOT/'tools/lab.py',oracle,lab.ROOT/'Cargo.toml',lab.ROOT/'Cargo.lock',lab.ROOT/'crates/horizon-client/Cargo.toml',lab.ROOT/'crates/horizon-client/src/input.rs',lab.ROOT/'crates/horizon-client/src/sprint.rs',lab.ROOT/'crates/horizon-client/src/lib.rs',lab.ROOT/'crates/horizon-client/examples/probe/sprint_probe.rs'] + sources=[Path(__file__),lab.ROOT/'tools/test_rust_parity.py',lab.ROOT/'tools/test_model_baking_parity.py',lab.ROOT/'tools/lab.py',oracle,lab.ROOT/'Cargo.toml',lab.ROOT/'Cargo.lock',lab.ROOT/'crates/horizon-client/Cargo.toml',lab.ROOT/'crates/horizon-client-input/src/input.rs',lab.ROOT/'crates/horizon-client/src/sprint.rs',lab.ROOT/'crates/horizon-client/src/lib.rs',lab.ROOT/'crates/horizon-client/examples/probe/sprint_probe.rs'] tested=base.source_hashes(sources) def run(command,name): with (build/(name+'.log')).open('w') as log:subprocess.run(list(map(str,command)),cwd=lab.ROOT,check=True,timeout=600,stdout=log,stderr=subprocess.STDOUT) diff --git a/tools/test_toggle_keys_parity.py b/tools/test_toggle_keys_parity.py index a5d2b05009..07e9523fff 100644 --- a/tools/test_toggle_keys_parity.py +++ b/tools/test_toggle_keys_parity.py @@ -69,7 +69,7 @@ def main(): 'not exercised; KeyboardInput then reads isDown as the sprint and local input harnesses compare.') harness.differential( 'rust-toggle-keys-parity', 'VanillaToggleKeysOracle', 'toggle_keys_probe', make_cases, scope, - sources=[lab.ROOT / 'crates/horizon-client/src/keymapping.rs']) + sources=[lab.ROOT / 'crates/horizon-client-input/src/keymapping.rs']) if __name__ == '__main__':