Save input mappings to disk
This commit is contained in:
		
							parent
							
								
									ae04f9f73b
								
							
						
					
					
						commit
						6e2d70abd7
					
				| 
						 | 
					@ -439,6 +439,9 @@ name = "bitflags"
 | 
				
			||||||
version = "2.6.0"
 | 
					version = "2.6.0"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
 | 
					checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "serde",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "block"
 | 
					name = "block"
 | 
				
			||||||
| 
						 | 
					@ -820,6 +823,9 @@ name = "cursor-icon"
 | 
				
			||||||
version = "1.1.0"
 | 
					version = "1.1.0"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991"
 | 
					checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "serde",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "d3d12"
 | 
					name = "d3d12"
 | 
				
			||||||
| 
						 | 
					@ -838,6 +844,27 @@ version = "0.11.0"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
 | 
					checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "directories"
 | 
				
			||||||
 | 
					version = "5.0.1"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "dirs-sys",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "dirs-sys"
 | 
				
			||||||
 | 
					version = "0.4.1"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "libc",
 | 
				
			||||||
 | 
					 "option-ext",
 | 
				
			||||||
 | 
					 "redox_users",
 | 
				
			||||||
 | 
					 "windows-sys 0.48.0",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "dispatch"
 | 
					name = "dispatch"
 | 
				
			||||||
version = "0.2.0"
 | 
					version = "0.2.0"
 | 
				
			||||||
| 
						 | 
					@ -884,6 +911,9 @@ name = "dpi"
 | 
				
			||||||
version = "0.1.1"
 | 
					version = "0.1.1"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53"
 | 
					checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "serde",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "ecolor"
 | 
					name = "ecolor"
 | 
				
			||||||
| 
						 | 
					@ -1266,6 +1296,7 @@ dependencies = [
 | 
				
			||||||
 "fnv",
 | 
					 "fnv",
 | 
				
			||||||
 "gilrs-core",
 | 
					 "gilrs-core",
 | 
				
			||||||
 "log",
 | 
					 "log",
 | 
				
			||||||
 | 
					 "serde",
 | 
				
			||||||
 "uuid",
 | 
					 "uuid",
 | 
				
			||||||
 "vec_map",
 | 
					 "vec_map",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
| 
						 | 
					@ -1284,6 +1315,7 @@ dependencies = [
 | 
				
			||||||
 "libudev-sys",
 | 
					 "libudev-sys",
 | 
				
			||||||
 "log",
 | 
					 "log",
 | 
				
			||||||
 "nix",
 | 
					 "nix",
 | 
				
			||||||
 | 
					 "serde",
 | 
				
			||||||
 "uuid",
 | 
					 "uuid",
 | 
				
			||||||
 "vec_map",
 | 
					 "vec_map",
 | 
				
			||||||
 "wasm-bindgen",
 | 
					 "wasm-bindgen",
 | 
				
			||||||
| 
						 | 
					@ -1651,6 +1683,12 @@ dependencies = [
 | 
				
			||||||
 "either",
 | 
					 "either",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "itoa"
 | 
				
			||||||
 | 
					version = "1.0.14"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "jni"
 | 
					name = "jni"
 | 
				
			||||||
version = "0.21.1"
 | 
					version = "0.21.1"
 | 
				
			||||||
| 
						 | 
					@ -1719,6 +1757,7 @@ dependencies = [
 | 
				
			||||||
 "cc",
 | 
					 "cc",
 | 
				
			||||||
 "clap",
 | 
					 "clap",
 | 
				
			||||||
 "cpal",
 | 
					 "cpal",
 | 
				
			||||||
 | 
					 "directories",
 | 
				
			||||||
 "egui",
 | 
					 "egui",
 | 
				
			||||||
 "egui-toast",
 | 
					 "egui-toast",
 | 
				
			||||||
 "egui-wgpu",
 | 
					 "egui-wgpu",
 | 
				
			||||||
| 
						 | 
					@ -1734,6 +1773,8 @@ dependencies = [
 | 
				
			||||||
 "rfd",
 | 
					 "rfd",
 | 
				
			||||||
 "rtrb",
 | 
					 "rtrb",
 | 
				
			||||||
 "rubato",
 | 
					 "rubato",
 | 
				
			||||||
 | 
					 "serde",
 | 
				
			||||||
 | 
					 "serde_json",
 | 
				
			||||||
 "thread-priority",
 | 
					 "thread-priority",
 | 
				
			||||||
 "wgpu",
 | 
					 "wgpu",
 | 
				
			||||||
 "windows 0.58.0",
 | 
					 "windows 0.58.0",
 | 
				
			||||||
| 
						 | 
					@ -2309,6 +2350,12 @@ version = "0.1.8"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "e296cf87e61c9cfc1a61c3c63a0f7f286ed4554e0e22be84e8a38e1d264a2a29"
 | 
					checksum = "e296cf87e61c9cfc1a61c3c63a0f7f286ed4554e0e22be84e8a38e1d264a2a29"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "option-ext"
 | 
				
			||||||
 | 
					version = "0.2.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "orbclient"
 | 
					name = "orbclient"
 | 
				
			||||||
version = "0.3.48"
 | 
					version = "0.3.48"
 | 
				
			||||||
| 
						 | 
					@ -2602,6 +2649,17 @@ dependencies = [
 | 
				
			||||||
 "bitflags 2.6.0",
 | 
					 "bitflags 2.6.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "redox_users"
 | 
				
			||||||
 | 
					version = "0.4.6"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "getrandom",
 | 
				
			||||||
 | 
					 "libredox",
 | 
				
			||||||
 | 
					 "thiserror",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "regex"
 | 
					name = "regex"
 | 
				
			||||||
version = "1.11.1"
 | 
					version = "1.11.1"
 | 
				
			||||||
| 
						 | 
					@ -2717,6 +2775,12 @@ version = "1.0.18"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
 | 
					checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "ryu"
 | 
				
			||||||
 | 
					version = "1.0.18"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "same-file"
 | 
					name = "same-file"
 | 
				
			||||||
version = "1.0.6"
 | 
					version = "1.0.6"
 | 
				
			||||||
| 
						 | 
					@ -2753,24 +2817,36 @@ dependencies = [
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "serde"
 | 
					name = "serde"
 | 
				
			||||||
version = "1.0.215"
 | 
					version = "1.0.216"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
 | 
					checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "serde_derive",
 | 
					 "serde_derive",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "serde_derive"
 | 
					name = "serde_derive"
 | 
				
			||||||
version = "1.0.215"
 | 
					version = "1.0.216"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
 | 
					checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "proc-macro2",
 | 
					 "proc-macro2",
 | 
				
			||||||
 "quote",
 | 
					 "quote",
 | 
				
			||||||
 "syn 2.0.90",
 | 
					 "syn 2.0.90",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "serde_json"
 | 
				
			||||||
 | 
					version = "1.0.133"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "itoa",
 | 
				
			||||||
 | 
					 "memchr",
 | 
				
			||||||
 | 
					 "ryu",
 | 
				
			||||||
 | 
					 "serde",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "serde_repr"
 | 
					name = "serde_repr"
 | 
				
			||||||
version = "0.1.19"
 | 
					version = "0.1.19"
 | 
				
			||||||
| 
						 | 
					@ -3960,6 +4036,7 @@ dependencies = [
 | 
				
			||||||
 "redox_syscall 0.4.1",
 | 
					 "redox_syscall 0.4.1",
 | 
				
			||||||
 "rustix",
 | 
					 "rustix",
 | 
				
			||||||
 "sctk-adwaita",
 | 
					 "sctk-adwaita",
 | 
				
			||||||
 | 
					 "serde",
 | 
				
			||||||
 "smithay-client-toolkit",
 | 
					 "smithay-client-toolkit",
 | 
				
			||||||
 "smol_str",
 | 
					 "smol_str",
 | 
				
			||||||
 "tracing",
 | 
					 "tracing",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,16 +5,17 @@ edition = "2021"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies]
 | 
					[dependencies]
 | 
				
			||||||
anyhow = "1"
 | 
					anyhow = "1"
 | 
				
			||||||
bitflags = "2"
 | 
					bitflags = { version = "2", features = ["serde"] }
 | 
				
			||||||
bytemuck = { version = "1", features = ["derive"] }
 | 
					bytemuck = { version = "1", features = ["derive"] }
 | 
				
			||||||
clap = { version = "4", features = ["derive"] }
 | 
					clap = { version = "4", features = ["derive"] }
 | 
				
			||||||
cpal = { git = "https://github.com/sidit77/cpal.git", rev = "66ed6be" }
 | 
					cpal = { git = "https://github.com/sidit77/cpal.git", rev = "66ed6be" }
 | 
				
			||||||
 | 
					directories = "5"
 | 
				
			||||||
egui = "0.29"
 | 
					egui = "0.29"
 | 
				
			||||||
egui_extras = "0.29"
 | 
					egui_extras = "0.29"
 | 
				
			||||||
egui-toast = "0.15"
 | 
					egui-toast = "0.15"
 | 
				
			||||||
egui-winit = "0.29"
 | 
					egui-winit = "0.29"
 | 
				
			||||||
egui-wgpu = { version = "0.29", features = ["winit"] }
 | 
					egui-wgpu = { version = "0.29", features = ["winit"] }
 | 
				
			||||||
gilrs = "0.11"
 | 
					gilrs = { version = "0.11", features = ["serde-serialize"] }
 | 
				
			||||||
image = { version = "0.25", default-features = false, features = ["png"] }
 | 
					image = { version = "0.25", default-features = false, features = ["png"] }
 | 
				
			||||||
itertools = "0.13"
 | 
					itertools = "0.13"
 | 
				
			||||||
num-derive = "0.4"
 | 
					num-derive = "0.4"
 | 
				
			||||||
| 
						 | 
					@ -24,9 +25,11 @@ pollster = "0.4"
 | 
				
			||||||
rfd = "0.15"
 | 
					rfd = "0.15"
 | 
				
			||||||
rtrb = "0.3"
 | 
					rtrb = "0.3"
 | 
				
			||||||
rubato = "0.16"
 | 
					rubato = "0.16"
 | 
				
			||||||
 | 
					serde = { version = "1", features = ["derive"] }
 | 
				
			||||||
 | 
					serde_json = "1"
 | 
				
			||||||
thread-priority = "1"
 | 
					thread-priority = "1"
 | 
				
			||||||
wgpu = "22.1"
 | 
					wgpu = "22.1"
 | 
				
			||||||
winit = "0.30"
 | 
					winit = { version = "0.30", features = ["serde"] }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[target.'cfg(windows)'.dependencies]
 | 
					[target.'cfg(windows)'.dependencies]
 | 
				
			||||||
windows = { version = "0.58", features = ["Win32_System_Threading"] }
 | 
					windows = { version = "0.58", features = ["Win32_System_Threading"] }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,7 @@ use crate::{
 | 
				
			||||||
    controller::ControllerManager,
 | 
					    controller::ControllerManager,
 | 
				
			||||||
    emulator::{EmulatorClient, EmulatorCommand, SimId},
 | 
					    emulator::{EmulatorClient, EmulatorCommand, SimId},
 | 
				
			||||||
    input::MappingProvider,
 | 
					    input::MappingProvider,
 | 
				
			||||||
 | 
					    persistence::Persistence,
 | 
				
			||||||
    window::{AppWindow, GameWindow, InputWindow},
 | 
					    window::{AppWindow, GameWindow, InputWindow},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,7 +45,8 @@ pub struct Application {
 | 
				
			||||||
impl Application {
 | 
					impl Application {
 | 
				
			||||||
    pub fn new(client: EmulatorClient, proxy: EventLoopProxy<UserEvent>) -> Self {
 | 
					    pub fn new(client: EmulatorClient, proxy: EventLoopProxy<UserEvent>) -> Self {
 | 
				
			||||||
        let icon = load_icon().ok().map(Arc::new);
 | 
					        let icon = load_icon().ok().map(Arc::new);
 | 
				
			||||||
        let mappings = MappingProvider::new();
 | 
					        let persistence = Persistence::new();
 | 
				
			||||||
 | 
					        let mappings = MappingProvider::new(persistence);
 | 
				
			||||||
        let controllers = ControllerManager::new(client.clone(), &mappings);
 | 
					        let controllers = ControllerManager::new(client.clone(), &mappings);
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            let mappings = mappings.clone();
 | 
					            let mappings = mappings.clone();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ use std::{ffi::c_void, ptr, slice};
 | 
				
			||||||
use anyhow::{anyhow, Result};
 | 
					use anyhow::{anyhow, Result};
 | 
				
			||||||
use bitflags::bitflags;
 | 
					use bitflags::bitflags;
 | 
				
			||||||
use num_derive::{FromPrimitive, ToPrimitive};
 | 
					use num_derive::{FromPrimitive, ToPrimitive};
 | 
				
			||||||
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[repr(C)]
 | 
					#[repr(C)]
 | 
				
			||||||
struct VB {
 | 
					struct VB {
 | 
				
			||||||
| 
						 | 
					@ -33,7 +34,7 @@ enum VBOption {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bitflags! {
 | 
					bitflags! {
 | 
				
			||||||
    #[repr(transparent)]
 | 
					    #[repr(transparent)]
 | 
				
			||||||
    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 | 
					    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
 | 
				
			||||||
    pub struct VBKey: u16 {
 | 
					    pub struct VBKey: u16 {
 | 
				
			||||||
        const PWR = 0x0001;
 | 
					        const PWR = 0x0001;
 | 
				
			||||||
        const SGN = 0x0002;
 | 
					        const SGN = 0x0002;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										138
									
								
								src/input.rs
								
								
								
								
							
							
						
						
									
										138
									
								
								src/input.rs
								
								
								
								
							| 
						 | 
					@ -1,16 +1,40 @@
 | 
				
			||||||
use std::{
 | 
					use std::{
 | 
				
			||||||
    collections::{hash_map::Entry, HashMap},
 | 
					    collections::{hash_map::Entry, HashMap},
 | 
				
			||||||
 | 
					    fmt::Display,
 | 
				
			||||||
 | 
					    str::FromStr,
 | 
				
			||||||
    sync::{Arc, RwLock},
 | 
					    sync::{Arc, RwLock},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use anyhow::anyhow;
 | 
				
			||||||
use gilrs::{ev::Code, Axis, Button, Gamepad, GamepadId};
 | 
					use gilrs::{ev::Code, Axis, Button, Gamepad, GamepadId};
 | 
				
			||||||
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
use winit::keyboard::{KeyCode, PhysicalKey};
 | 
					use winit::keyboard::{KeyCode, PhysicalKey};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::emulator::{SimId, VBKey};
 | 
					use crate::{
 | 
				
			||||||
 | 
					    emulator::{SimId, VBKey},
 | 
				
			||||||
 | 
					    persistence::Persistence,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
 | 
					#[derive(Clone, PartialEq, Eq, Hash)]
 | 
				
			||||||
struct DeviceId(u16, u16);
 | 
					struct DeviceId(u16, u16);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl FromStr for DeviceId {
 | 
				
			||||||
 | 
					    type Err = anyhow::Error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn from_str(s: &str) -> Result<Self, Self::Err> {
 | 
				
			||||||
 | 
					        let mut ids = s.split("-");
 | 
				
			||||||
 | 
					        let vendor_id: u16 = ids.next().ok_or(anyhow!("missing vendor id"))?.parse()?;
 | 
				
			||||||
 | 
					        let product_id: u16 = ids.next().ok_or(anyhow!("missing product id"))?.parse()?;
 | 
				
			||||||
 | 
					        Ok(Self(vendor_id, product_id))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Display for DeviceId {
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
				
			||||||
 | 
					        f.write_fmt(format_args!("{}-{}", self.0, self.1))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Clone)]
 | 
					#[derive(Clone)]
 | 
				
			||||||
pub struct GamepadInfo {
 | 
					pub struct GamepadInfo {
 | 
				
			||||||
    pub id: GamepadId,
 | 
					    pub id: GamepadId,
 | 
				
			||||||
| 
						 | 
					@ -26,6 +50,7 @@ pub trait Mappings {
 | 
				
			||||||
    fn use_default_mappings(&mut self);
 | 
					    fn use_default_mappings(&mut self);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Serialize, Deserialize)]
 | 
				
			||||||
pub struct GamepadMapping {
 | 
					pub struct GamepadMapping {
 | 
				
			||||||
    buttons: HashMap<Code, VBKey>,
 | 
					    buttons: HashMap<Code, VBKey>,
 | 
				
			||||||
    axes: HashMap<Code, (VBKey, VBKey)>,
 | 
					    axes: HashMap<Code, (VBKey, VBKey)>,
 | 
				
			||||||
| 
						 | 
					@ -89,6 +114,30 @@ impl GamepadMapping {
 | 
				
			||||||
            .or_insert((VBKey::empty(), VBKey::empty()));
 | 
					            .or_insert((VBKey::empty(), VBKey::empty()));
 | 
				
			||||||
        entry.1 = entry.1.union(key);
 | 
					        entry.1 = entry.1.union(key);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn save_mappings(&self) -> PersistedGamepadMapping {
 | 
				
			||||||
 | 
					        fn flatten<V: Copy>(values: &HashMap<Code, V>) -> Vec<(Code, V)> {
 | 
				
			||||||
 | 
					            values.iter().map(|(k, v)| (*k, *v)).collect()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        PersistedGamepadMapping {
 | 
				
			||||||
 | 
					            buttons: flatten(&self.buttons),
 | 
				
			||||||
 | 
					            axes: flatten(&self.axes),
 | 
				
			||||||
 | 
					            default_buttons: flatten(&self.default_buttons),
 | 
				
			||||||
 | 
					            default_axes: flatten(&self.default_axes),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn from_mappings(mappings: &PersistedGamepadMapping) -> Self {
 | 
				
			||||||
 | 
					        fn unflatten<V: Copy>(values: &[(Code, V)]) -> HashMap<Code, V> {
 | 
				
			||||||
 | 
					            values.iter().map(|(k, v)| (*k, *v)).collect()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            buttons: unflatten(&mappings.buttons),
 | 
				
			||||||
 | 
					            axes: unflatten(&mappings.axes),
 | 
				
			||||||
 | 
					            default_buttons: unflatten(&mappings.default_buttons),
 | 
				
			||||||
 | 
					            default_axes: unflatten(&mappings.default_axes),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Mappings for GamepadMapping {
 | 
					impl Mappings for GamepadMapping {
 | 
				
			||||||
| 
						 | 
					@ -158,6 +207,16 @@ impl InputMapping {
 | 
				
			||||||
        let entry = self.keys.entry(keyboard_key).or_insert(VBKey::empty());
 | 
					        let entry = self.keys.entry(keyboard_key).or_insert(VBKey::empty());
 | 
				
			||||||
        *entry = entry.union(key);
 | 
					        *entry = entry.union(key);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn save_mappings(&self) -> PersistedKeyboardMapping {
 | 
				
			||||||
 | 
					        PersistedKeyboardMapping {
 | 
				
			||||||
 | 
					            keys: self.keys.iter().map(|(k, v)| (*k, *v)).collect(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn restore_mappings(&mut self, persisted: &PersistedKeyboardMapping) {
 | 
				
			||||||
 | 
					        self.keys = persisted.keys.iter().map(|(k, v)| (*k, *v)).collect();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Mappings for InputMapping {
 | 
					impl Mappings for InputMapping {
 | 
				
			||||||
| 
						 | 
					@ -210,25 +269,42 @@ impl Mappings for InputMapping {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Clone)]
 | 
					#[derive(Clone)]
 | 
				
			||||||
pub struct MappingProvider {
 | 
					pub struct MappingProvider {
 | 
				
			||||||
 | 
					    persistence: Persistence,
 | 
				
			||||||
    device_mappings: Arc<RwLock<HashMap<DeviceId, Arc<RwLock<GamepadMapping>>>>>,
 | 
					    device_mappings: Arc<RwLock<HashMap<DeviceId, Arc<RwLock<GamepadMapping>>>>>,
 | 
				
			||||||
    sim_mappings: HashMap<SimId, Arc<RwLock<InputMapping>>>,
 | 
					    sim_mappings: HashMap<SimId, Arc<RwLock<InputMapping>>>,
 | 
				
			||||||
    gamepad_info: Arc<RwLock<HashMap<GamepadId, GamepadInfo>>>,
 | 
					    gamepad_info: Arc<RwLock<HashMap<GamepadId, GamepadInfo>>>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl MappingProvider {
 | 
					impl MappingProvider {
 | 
				
			||||||
    pub fn new() -> Self {
 | 
					    pub fn new(persistence: Persistence) -> Self {
 | 
				
			||||||
        let mut mappings = HashMap::new();
 | 
					        let mut sim_mappings = HashMap::new();
 | 
				
			||||||
 | 
					        let mut device_mappings = HashMap::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let mut p1_mappings = InputMapping::default();
 | 
					        let mut p1_mappings = InputMapping::default();
 | 
				
			||||||
        p1_mappings.use_default_mappings();
 | 
					        let mut p2_mappings = InputMapping::default();
 | 
				
			||||||
        let p2_mappings = InputMapping::default();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        mappings.insert(SimId::Player1, Arc::new(RwLock::new(p1_mappings)));
 | 
					        if let Ok(persisted) = persistence.load_config::<PersistedInputMappings>("mappings") {
 | 
				
			||||||
        mappings.insert(SimId::Player2, Arc::new(RwLock::new(p2_mappings)));
 | 
					            p1_mappings.restore_mappings(&persisted.p1_keyboard);
 | 
				
			||||||
 | 
					            p2_mappings.restore_mappings(&persisted.p2_keyboard);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (device_id, mappings) in persisted.gamepads {
 | 
				
			||||||
 | 
					                let Ok(device_id) = device_id.parse::<DeviceId>() else {
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                let gamepad = GamepadMapping::from_mappings(&mappings);
 | 
				
			||||||
 | 
					                device_mappings.insert(device_id, Arc::new(RwLock::new(gamepad)));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            p1_mappings.use_default_mappings();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sim_mappings.insert(SimId::Player1, Arc::new(RwLock::new(p1_mappings)));
 | 
				
			||||||
 | 
					        sim_mappings.insert(SimId::Player2, Arc::new(RwLock::new(p2_mappings)));
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            device_mappings: Arc::new(RwLock::new(HashMap::new())),
 | 
					            persistence,
 | 
				
			||||||
 | 
					            device_mappings: Arc::new(RwLock::new(device_mappings)),
 | 
				
			||||||
            gamepad_info: Arc::new(RwLock::new(HashMap::new())),
 | 
					            gamepad_info: Arc::new(RwLock::new(HashMap::new())),
 | 
				
			||||||
            sim_mappings: mappings,
 | 
					            sim_mappings,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -238,7 +314,7 @@ impl MappingProvider {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn for_gamepad(&self, gamepad_id: GamepadId) -> Option<Arc<RwLock<GamepadMapping>>> {
 | 
					    pub fn for_gamepad(&self, gamepad_id: GamepadId) -> Option<Arc<RwLock<GamepadMapping>>> {
 | 
				
			||||||
        let lock = self.gamepad_info.read().unwrap();
 | 
					        let lock = self.gamepad_info.read().unwrap();
 | 
				
			||||||
        let device_id = lock.get(&gamepad_id)?.device_id;
 | 
					        let device_id = lock.get(&gamepad_id)?.device_id.clone();
 | 
				
			||||||
        drop(lock);
 | 
					        drop(lock);
 | 
				
			||||||
        let lock = self.device_mappings.read().unwrap();
 | 
					        let lock = self.device_mappings.read().unwrap();
 | 
				
			||||||
        lock.get(&device_id).cloned()
 | 
					        lock.get(&device_id).cloned()
 | 
				
			||||||
| 
						 | 
					@ -250,7 +326,7 @@ impl MappingProvider {
 | 
				
			||||||
            gamepad.product_id().unwrap_or_default(),
 | 
					            gamepad.product_id().unwrap_or_default(),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        let mut lock = self.device_mappings.write().unwrap();
 | 
					        let mut lock = self.device_mappings.write().unwrap();
 | 
				
			||||||
        let mappings = match lock.entry(device_id) {
 | 
					        let mappings = match lock.entry(device_id.clone()) {
 | 
				
			||||||
            Entry::Vacant(entry) => {
 | 
					            Entry::Vacant(entry) => {
 | 
				
			||||||
                let mappings = GamepadMapping::for_gamepad(gamepad);
 | 
					                let mappings = GamepadMapping::for_gamepad(gamepad);
 | 
				
			||||||
                entry.insert(Arc::new(RwLock::new(mappings)))
 | 
					                entry.insert(Arc::new(RwLock::new(mappings)))
 | 
				
			||||||
| 
						 | 
					@ -303,7 +379,7 @@ impl MappingProvider {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        info.bound_to = Some(sim_id);
 | 
					        info.bound_to = Some(sim_id);
 | 
				
			||||||
        let device_id = info.device_id;
 | 
					        let device_id = info.device_id.clone();
 | 
				
			||||||
        drop(lock);
 | 
					        drop(lock);
 | 
				
			||||||
        let Some(device_mappings) = self
 | 
					        let Some(device_mappings) = self
 | 
				
			||||||
            .device_mappings
 | 
					            .device_mappings
 | 
				
			||||||
| 
						 | 
					@ -341,4 +417,40 @@ impl MappingProvider {
 | 
				
			||||||
            .cloned()
 | 
					            .cloned()
 | 
				
			||||||
            .collect()
 | 
					            .collect()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn save(&self) {
 | 
				
			||||||
 | 
					        let p1_keyboard = self.for_sim(SimId::Player1).read().unwrap().save_mappings();
 | 
				
			||||||
 | 
					        let p2_keyboard = self.for_sim(SimId::Player2).read().unwrap().save_mappings();
 | 
				
			||||||
 | 
					        let mut gamepads = HashMap::new();
 | 
				
			||||||
 | 
					        for (device_id, gamepad) in self.device_mappings.read().unwrap().iter() {
 | 
				
			||||||
 | 
					            let mapping = gamepad.read().unwrap().save_mappings();
 | 
				
			||||||
 | 
					            gamepads.insert(device_id.to_string(), mapping);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        let persisted = PersistedInputMappings {
 | 
				
			||||||
 | 
					            p1_keyboard,
 | 
				
			||||||
 | 
					            p2_keyboard,
 | 
				
			||||||
 | 
					            gamepads,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let _ = self.persistence.save_config("mappings", &persisted);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Serialize, Deserialize)]
 | 
				
			||||||
 | 
					struct PersistedInputMappings {
 | 
				
			||||||
 | 
					    p1_keyboard: PersistedKeyboardMapping,
 | 
				
			||||||
 | 
					    p2_keyboard: PersistedKeyboardMapping,
 | 
				
			||||||
 | 
					    gamepads: HashMap<String, PersistedGamepadMapping>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Serialize, Deserialize)]
 | 
				
			||||||
 | 
					struct PersistedKeyboardMapping {
 | 
				
			||||||
 | 
					    keys: Vec<(PhysicalKey, VBKey)>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Serialize, Deserialize)]
 | 
				
			||||||
 | 
					struct PersistedGamepadMapping {
 | 
				
			||||||
 | 
					    buttons: Vec<(Code, VBKey)>,
 | 
				
			||||||
 | 
					    axes: Vec<(Code, (VBKey, VBKey))>,
 | 
				
			||||||
 | 
					    default_buttons: Vec<(Code, VBKey)>,
 | 
				
			||||||
 | 
					    default_axes: Vec<(Code, (VBKey, VBKey))>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,6 +13,7 @@ mod controller;
 | 
				
			||||||
mod emulator;
 | 
					mod emulator;
 | 
				
			||||||
mod graphics;
 | 
					mod graphics;
 | 
				
			||||||
mod input;
 | 
					mod input;
 | 
				
			||||||
 | 
					mod persistence;
 | 
				
			||||||
mod window;
 | 
					mod window;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Parser)]
 | 
					#[derive(Parser)]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,46 @@
 | 
				
			||||||
 | 
					use std::{fs, path::PathBuf};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use anyhow::{bail, Result};
 | 
				
			||||||
 | 
					use directories::ProjectDirs;
 | 
				
			||||||
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone)]
 | 
				
			||||||
 | 
					pub struct Persistence {
 | 
				
			||||||
 | 
					    dirs: Option<Dirs>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Persistence {
 | 
				
			||||||
 | 
					    pub fn new() -> Self {
 | 
				
			||||||
 | 
					        Self { dirs: init_dirs() }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn save_config<T: Serialize>(&self, file: &str, data: &T) -> Result<()> {
 | 
				
			||||||
 | 
					        if let Some(dirs) = self.dirs.as_ref() {
 | 
				
			||||||
 | 
					            let bytes = serde_json::to_vec_pretty(data)?;
 | 
				
			||||||
 | 
					            let filename = dirs.config_dir.join(file).with_extension("json");
 | 
				
			||||||
 | 
					            fs::write(&filename, bytes)?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn load_config<T: for<'a> Deserialize<'a>>(&self, file: &str) -> Result<T> {
 | 
				
			||||||
 | 
					        let Some(dirs) = self.dirs.as_ref() else {
 | 
				
			||||||
 | 
					            bail!("config directory not found");
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let filename = dirs.config_dir.join(file).with_extension("json");
 | 
				
			||||||
 | 
					        let bytes = fs::read(filename)?;
 | 
				
			||||||
 | 
					        Ok(serde_json::from_slice(&bytes)?)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone)]
 | 
				
			||||||
 | 
					struct Dirs {
 | 
				
			||||||
 | 
					    config_dir: PathBuf,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn init_dirs() -> Option<Dirs> {
 | 
				
			||||||
 | 
					    let dirs = ProjectDirs::from("com", "virtual-boy", "Lemur")?;
 | 
				
			||||||
 | 
					    let config_dir = dirs.config_dir().to_path_buf();
 | 
				
			||||||
 | 
					    fs::create_dir_all(&config_dir).ok()?;
 | 
				
			||||||
 | 
					    Some(Dirs { config_dir })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -53,10 +53,12 @@ impl InputWindow {
 | 
				
			||||||
        ui.horizontal(|ui| {
 | 
					        ui.horizontal(|ui| {
 | 
				
			||||||
            if ui.button("Use defaults").clicked() {
 | 
					            if ui.button("Use defaults").clicked() {
 | 
				
			||||||
                mappings.write().unwrap().use_default_mappings();
 | 
					                mappings.write().unwrap().use_default_mappings();
 | 
				
			||||||
 | 
					                self.mappings.save();
 | 
				
			||||||
                self.now_binding = None;
 | 
					                self.now_binding = None;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if ui.button("Clear all").clicked() {
 | 
					            if ui.button("Clear all").clicked() {
 | 
				
			||||||
                mappings.write().unwrap().clear_all_mappings();
 | 
					                mappings.write().unwrap().clear_all_mappings();
 | 
				
			||||||
 | 
					                self.mappings.save();
 | 
				
			||||||
                self.now_binding = None;
 | 
					                self.now_binding = None;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
| 
						 | 
					@ -99,6 +101,8 @@ impl InputWindow {
 | 
				
			||||||
                                {
 | 
					                                {
 | 
				
			||||||
                                    let mut mapping = mappings.write().unwrap();
 | 
					                                    let mut mapping = mappings.write().unwrap();
 | 
				
			||||||
                                    mapping.clear_mappings(*key);
 | 
					                                    mapping.clear_mappings(*key);
 | 
				
			||||||
 | 
					                                    drop(mapping);
 | 
				
			||||||
 | 
					                                    self.mappings.save();
 | 
				
			||||||
                                    self.now_binding = None;
 | 
					                                    self.now_binding = None;
 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
                            });
 | 
					                            });
 | 
				
			||||||
| 
						 | 
					@ -210,6 +214,8 @@ impl AppWindow for InputWindow {
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        let mut mappings = self.mappings.for_sim(sim_id).write().unwrap();
 | 
					        let mut mappings = self.mappings.for_sim(sim_id).write().unwrap();
 | 
				
			||||||
        mappings.add_keyboard_mapping(vb, event.physical_key);
 | 
					        mappings.add_keyboard_mapping(vb, event.physical_key);
 | 
				
			||||||
 | 
					        drop(mappings);
 | 
				
			||||||
 | 
					        self.mappings.save();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn handle_gamepad_event(&mut self, event: &gilrs::Event) {
 | 
					    fn handle_gamepad_event(&mut self, event: &gilrs::Event) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue