summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bin/br/main.rs34
-rw-r--r--src/devices/file_device.rs143
-rw-r--r--src/devices/input_device.rs1
-rw-r--r--src/emulators/graphical_emulator.rs51
-rw-r--r--src/emulators/headless_emulator.rs2
-rw-r--r--src/emulators/mod.rs3
-rw-r--r--src/lib.rs22
-rw-r--r--src/types/controller.rs6
8 files changed, 176 insertions, 86 deletions
diff --git a/src/bin/br/main.rs b/src/bin/br/main.rs
index 81c5ec9..da11a18 100644
--- a/src/bin/br/main.rs
+++ b/src/bin/br/main.rs
@@ -55,6 +55,7 @@ fn main() {
args.named("size").short('s');
args.named("decode-stdin").short('i');
args.named("encode-stdout").short('o');
+ args.named("trust-files");
args.raise_errors();
let source = args.get("source").as_path_opt();
@@ -71,6 +72,7 @@ fn main() {
};
let decode_stdin = args.get("decode-stdin").as_bool();
let encode_stdout = args.get("encode-stdout").as_bool();
+ let trust_files = args.get("trust-files").as_bool();
// -----------------------------------------------------------------------
@@ -78,7 +80,9 @@ fn main() {
let mut title = String::from("Bedrock program");
let mut icon = None;
- if let Some(metadata) = Metadata::from(&bytecode) {
+ let metadata = Metadata::from(&bytecode);
+
+ if let Some(ref metadata) = metadata {
let name = metadata.name().unwrap_or("unnamed".to_string());
let authors = metadata.authors().unwrap_or_else(Vec::new);
let mut metadata_string = format!("Program is '{name}'");
@@ -110,16 +114,26 @@ fn main() {
path.add_extension("sym"); path
});
+ let name = metadata.and_then(|m| m.name()).and_then(|n| match n.split_once('/') {
+ Some((name, _)) => Some(name.to_string()),
+ None => Some(n),
+ });
+ let identifier = name.as_ref().and_then(
+ |n| Some(n.to_lowercase().chars().filter_map(
+ |c| c.is_alphanumeric().then_some(c)
+ ).collect())
+ );
+
let config = EmulatorConfig {
dimensions, fullscreen, zoom, palette, show_cursor,
- decode_stdin, encode_stdout,
- symbols_path, title, icon,
+ decode_stdin, encode_stdout, trust_files,
+ symbols_path, name, identifier, title, icon,
};
- if let Ok(phosphor) = Phosphor::new() {
- match mode {
+ match Phosphor::new() {
+ Ok(phosphor) => match mode {
Mode::Dynamic => {
- info!("Starting graphical emulator");
+ info!("Starting graphical emulator (hidden)");
let mut emulator = GraphicalEmulator::new(config, debug);
emulator.load_program(&bytecode);
emulator.run(phosphor, false);
@@ -128,7 +142,7 @@ fn main() {
info!("Starting graphical emulator");
let mut emulator = GraphicalEmulator::new(config, debug);
emulator.load_program(&bytecode);
- emulator.run(phosphor, false);
+ emulator.run(phosphor, true);
}
Mode::Headless => {
info!("Starting headless emulator");
@@ -137,9 +151,9 @@ fn main() {
emulator.run();
}
}
- } else {
- match mode {
+ Err(err) => match mode {
Mode::Dynamic => {
+ eprintln!("EventLoopError: {err:?}");
info!("Could not start graphical event loop");
info!("Starting headless emulator");
let mut emulator = HeadlessEmulator::new(&config, debug);
@@ -147,6 +161,7 @@ fn main() {
emulator.run();
}
Mode::Graphical => {
+ eprintln!("EventLoopError: {err:?}");
fatal!("Could not start graphical event loop");
}
Mode::Headless => {
@@ -210,6 +225,7 @@ Switches:
--debug, (-d) Show debug information while the program is running
--decode-stdin (-i) Decode transmissions on standard input from text lines.
--encode-stdout (-o) Encode transmissions on standard output as text lines.
+ --trust-files Give the program unrestricted access to the file system.
--help (-h) Print this help information
--verbose, (-v) Print additional information
--version Print the program version and exit
diff --git a/src/devices/file_device.rs b/src/devices/file_device.rs
index ff5629b..83f0a56 100644
--- a/src/devices/file_device.rs
+++ b/src/devices/file_device.rs
@@ -9,13 +9,14 @@ pub struct FileDevice {
pub action_buffer: BedrockPathBuffer,
pub path_buffer: BedrockPathBuffer,
- pub entry: Option<(Entry, BedrockFilePath)>,
- pub cached_dir: Option<(Entry, BedrockFilePath)>,
+ pub entry: Option<(Entry, BedrockFilePath, Instant)>,
+ pub cached_dir: Option<(Entry, BedrockFilePath, Instant)>,
- pub success: bool,
+ pub error: bool,
pub pointer_write: u32,
pub length_write: u32,
+ pub enable: bool,
pub enable_read: bool,
pub enable_write: bool,
pub enable_create: bool,
@@ -26,9 +27,10 @@ pub struct FileDevice {
impl Device for FileDevice {
fn read(&mut self, port: u8) -> u8 {
+ if !self.enable { return 0x00; }
match port {
0x0 => read_b!(self.entry.is_some()),
- 0x1 => read_b!(self.success),
+ 0x1 => read_b!(std::mem::take(&mut self.error)),
0x2 => self.read_byte(),
0x3 => self.read_byte(),
0x4 => self.path_buffer.read(),
@@ -48,6 +50,7 @@ impl Device for FileDevice {
}
fn write(&mut self, port: u8, value: u8) -> Option<Signal> {
+ if !self.enable { return None; }
match port {
0x0 => self.write_to_entry_port(value),
0x1 => self.write_to_action_port(value),
@@ -81,20 +84,35 @@ impl Device for FileDevice {
impl FileDevice {
- pub fn new() -> Self {
+ pub fn new(config: &EmulatorConfig) -> Self {
#[cfg(target_family = "unix")]
let default_base: PathBuf = PathBuf::from("/");
#[cfg(target_family = "windows")]
let default_base: PathBuf = PathBuf::from("");
+ let current_dir = match std::env::current_dir() {
+ Ok(dir) => PathBuf::from(dir),
+ Err(_) => PathBuf::from(""),
+ };
+
+ let (enable, base_path, default_path) = if config.trust_files {
+ (true, default_base, current_dir)
+ } else if let Some(config_dir) = dirs_next::config_dir() {
+ let bedrock_dir = config_dir.join("bedrock");
+ let identifier = config.identifier.clone().unwrap_or("default".to_string());
+ let sandbox_dir = bedrock_dir.join(identifier);
+ vagabond::make_directory(&sandbox_dir).unwrap();
+ (true, sandbox_dir.clone(), sandbox_dir)
+ } else {
+ error!("Could not determine sandbox path for file device");
+ (false, default_base, current_dir)
+ };
+
// TODO: I'm not at all confident that the default path is correct
// when not being set as the current directory.
Self {
- base_path: default_base,
- default_path: match std::env::current_dir() {
- Ok(dir) => PathBuf::from(dir),
- Err(_) => PathBuf::from(""),
- },
+ base_path,
+ default_path,
entry_buffer: BedrockPathBuffer::new(),
action_buffer: BedrockPathBuffer::new(),
@@ -103,10 +121,11 @@ impl FileDevice {
entry: None,
cached_dir: None,
- success: false,
+ error: false,
pointer_write: 0,
length_write: 0,
+ enable,
enable_read: true,
enable_write: true,
enable_create: true,
@@ -115,6 +134,12 @@ impl FileDevice {
}
}
+ pub fn check_success(&mut self, success: bool) {
+ if !success {
+ self.error = true;
+ }
+ }
+
/// Safely close the current entry, cleaning up entry variables.
pub fn close(&mut self) {
self.entry_buffer.clear();
@@ -122,10 +147,10 @@ impl FileDevice {
self.path_buffer.clear();
self.flush();
- if let Some((Entry::Directory(mut dir), path)) = std::mem::take(&mut self.entry) {
+ if let Some((Entry::Directory(mut dir), path, time)) = std::mem::take(&mut self.entry) {
// Prevent the selected child from persisting when loading from cache.
dir.deselect_child();
- self.cached_dir = Some((Entry::Directory(dir), path));
+ self.cached_dir = Some((Entry::Directory(dir), path, time));
}
}
@@ -141,17 +166,17 @@ impl FileDevice {
if let Ok(file) = open_result {
self.close();
self.path_buffer.populate(path.as_buffer());
- self.entry = Some((Entry::File(BufferedFile::new(file)), path));
+ self.entry = Some((Entry::File(BufferedFile::new(file)), path, Instant::now()));
return Ok(());
};
}
Some(EntryType::Directory) => {
- // Attempt to use the cached directory.
- if let Some((dir, cached_path)) = std::mem::take(&mut self.cached_dir) {
- if cached_path == path {
+ // Attempt to use the cached directory if not too old.
+ if let Some((dir, cached_path, time)) = std::mem::take(&mut self.cached_dir) {
+ if cached_path == path && time.elapsed() < Duration::from_secs(1) {
self.close();
self.path_buffer.populate(cached_path.as_buffer());
- self.entry = Some((dir, cached_path));
+ self.entry = Some((dir, cached_path, time));
return Ok(());
}
}
@@ -159,7 +184,7 @@ impl FileDevice {
if let Some(listing) = DirectoryListing::from_path(&path) {
self.close();
self.path_buffer.populate(path.as_buffer());
- self.entry = Some((Entry::Directory(listing), path));
+ self.entry = Some((Entry::Directory(listing), path, Instant::now()));
return Ok(());
};
}
@@ -173,10 +198,16 @@ impl FileDevice {
pub fn write_to_entry_port(&mut self, byte: u8) {
if let Some(buffer) = self.entry_buffer.write(byte) {
self.close();
- match BedrockFilePath::from_buffer(buffer, &self.base_path) {
- Some(path) => self.success = self.open(path).is_ok(),
- None => self.success = false,
- };
+ // Attempt to open file if buffer was not empty.
+ if buffer[0] != 0 {
+ let success = match BedrockFilePath::from_buffer(buffer, &self.base_path) {
+ Some(path) => self.open(path).is_ok(),
+ None => false,
+ };
+ self.check_success(success);
+ } else {
+ self.check_success(true);
+ }
}
}
@@ -185,22 +216,23 @@ impl FileDevice {
if let Some(buffer) = self.action_buffer.write(byte) {
let destination_blank = buffer[0] == 0x00;
let destination = BedrockFilePath::from_buffer(buffer, &self.base_path);
- self.success = false;
- if let Some((_, source)) = &self.entry {
+ if let Some((_, source, _)) = &self.entry {
if destination_blank {
if self.enable_delete {
- self.success = delete_entry(&source.as_path());
+ self.check_success(delete_entry(&source.as_path()));
}
} else if let Some(dest) = destination {
if self.enable_move {
- self.success = move_entry(&source.as_path(), &dest.as_path());
+ self.check_success(move_entry(&source.as_path(), &dest.as_path()));
}
}
} else if let Some(dest) = destination {
if self.enable_create {
- self.success = create_file(&dest.as_path());
+ self.check_success(create_file(&dest.as_path()));
}
+ } else {
+ self.check_success(false);
}
self.close();
}
@@ -208,35 +240,38 @@ impl FileDevice {
/// Attempt to open the parent directory of the current entry.
pub fn ascend_to_parent(&mut self) {
- if let Some((_, path)) = &self.entry {
- match path.parent() {
- Some(parent) => self.success = self.open(parent).is_ok(),
- None => self.success = false,
+ if let Some((_, path, _)) = &self.entry {
+ let success = match path.parent() {
+ Some(parent) => self.open(parent).is_ok(),
+ None => false,
};
+ self.check_success(success);
} else {
- match BedrockFilePath::from_path(&self.default_path, &self.base_path) {
- Some(default) => self.success = self.open(default).is_ok(),
- None => self.success = false,
+ let success = match BedrockFilePath::from_path(&self.default_path, &self.base_path) {
+ Some(default) => self.open(default).is_ok(),
+ None => false,
};
+ self.check_success(success);
}
}
/// Attempt to open the selected child of the current directory.
pub fn descend_to_child(&mut self) {
- if let Some((Entry::Directory(dir), _)) = &self.entry {
- match dir.child_path() {
- Some(child) => self.success = self.open(child).is_ok(),
- None => self.success = false,
+ if let Some((Entry::Directory(dir), _, _)) = &self.entry {
+ let success = match dir.child_path() {
+ Some(child) => self.open(child).is_ok(),
+ None => false,
};
+ self.check_success(success);
} else {
- self.success = false;
+ self.check_success(false);
}
}
/// Return true if the current entry is a directory.
pub fn entry_type(&self) -> bool {
match self.entry {
- Some((Entry::Directory(_), _)) => true,
+ Some((Entry::Directory(_), _, _)) => true,
_ => false,
}
}
@@ -244,13 +279,13 @@ impl FileDevice {
/// Read a byte from the path buffer of the selected child.
pub fn read_child_path(&mut self) -> u8 {
match &mut self.entry {
- Some((Entry::Directory(dir), _)) => dir.child_path_buffer().read(),
+ Some((Entry::Directory(dir), _, _)) => dir.child_path_buffer().read(),
_ => 0,
}
}
pub fn set_child_path(&mut self, byte: u8) {
- if let Some((Entry::Directory(dir), _)) = &mut self.entry {
+ if let Some((Entry::Directory(dir), _, _)) = &mut self.entry {
dir.child_path_buffer().set_pointer(byte);
}
}
@@ -258,7 +293,7 @@ impl FileDevice {
/// Return true if the selected child is a directory.
pub fn child_type(&self) -> bool {
match &self.entry {
- Some((Entry::Directory(dir), _)) => match dir.child_type() {
+ Some((Entry::Directory(dir), _, _)) => match dir.child_type() {
Some(EntryType::Directory) => true,
_ => false,
}
@@ -269,7 +304,7 @@ impl FileDevice {
/// Read a byte from the current file.
pub fn read_byte(&mut self) -> u8 {
match &mut self.entry {
- Some((Entry::File(file), _)) => file.read(),
+ Some((Entry::File(file), _, _)) => file.read(),
_ => 0,
}
}
@@ -277,44 +312,44 @@ impl FileDevice {
/// Writes a byte to the currently-open file.
pub fn write_byte(&mut self, byte: u8) {
match &mut self.entry {
- Some((Entry::File(file), _)) => file.write(byte),
+ Some((Entry::File(file), _, _)) => file.write(byte),
_ => (),
}
}
pub fn pointer(&mut self) -> u32 {
match &mut self.entry {
- Some((Entry::File(file), _)) => file.pointer(),
- Some((Entry::Directory(dir), _)) => dir.selected(),
+ Some((Entry::File(file), _, _)) => file.pointer(),
+ Some((Entry::Directory(dir), _, _)) => dir.selected(),
_ => 0,
}
}
pub fn commit_pointer(&mut self) {
match &mut self.entry {
- Some((Entry::File(file), _)) => file.set_pointer(self.pointer_write),
- Some((Entry::Directory(dir), _)) => dir.set_selected(self.pointer_write),
+ Some((Entry::File(file), _, _)) => file.set_pointer(self.pointer_write),
+ Some((Entry::Directory(dir), _, _)) => dir.set_selected(self.pointer_write),
_ => (),
}
}
pub fn length(&mut self) -> u32 {
match &mut self.entry {
- Some((Entry::File(file), _)) => file.length(),
- Some((Entry::Directory(dir), _)) => dir.length(),
+ Some((Entry::File(file), _, _)) => file.length(),
+ Some((Entry::Directory(dir), _, _)) => dir.length(),
_ => 0,
}
}
pub fn commit_length(&mut self) {
match &mut self.entry {
- Some((Entry::File(file), _)) => file.set_length(self.length_write),
+ Some((Entry::File(file), _, _)) => file.set_length(self.length_write),
_ => (),
}
}
pub fn flush(&mut self) {
- if let Some((Entry::File(buffered_file), _)) = &mut self.entry {
+ if let Some((Entry::File(buffered_file), _, _)) = &mut self.entry {
let _ = buffered_file;
}
}
diff --git a/src/devices/input_device.rs b/src/devices/input_device.rs
index d4e0cb0..3ebeb4c 100644
--- a/src/devices/input_device.rs
+++ b/src/devices/input_device.rs
@@ -132,6 +132,7 @@ impl InputDevice {
}
}
+ #[cfg(feature = "gamepad")]
pub fn on_gamepad_event(&mut self, event: gilrs::Event) {
if let Some(g) = self.gamepad_1.register(event.id) {
self.wake |= g.process_event(&event); return; }
diff --git a/src/emulators/graphical_emulator.rs b/src/emulators/graphical_emulator.rs
index 2680d3f..503a8f4 100644
--- a/src/emulators/graphical_emulator.rs
+++ b/src/emulators/graphical_emulator.rs
@@ -1,14 +1,13 @@
use crate::*;
-use gilrs::Gilrs;
-
-
pub struct GraphicalEmulator {
pub br: BedrockEmulator<GraphicalDeviceBus>,
pub debug: DebugState,
+ #[cfg(feature = "gamepad")]
pub gilrs: Option<Gilrs>,
pub fullscreen: bool,
+ pub visible: bool,
pub scale: u32,
pub render_mark: Instant, // last time screen was rendered
pub frame_mark: Instant, // refreshes when clean
@@ -18,18 +17,18 @@ pub struct GraphicalEmulator {
impl GraphicalEmulator {
pub fn new(config: EmulatorConfig, debug: bool) -> Self {
- let gilrs = match Gilrs::new() {
- Ok(gilrs) => Some(gilrs),
- Err(err) => {
- info!("Could not start gamepad listener: {}", err);
- None
- }
- };
-
Self {
br: BedrockEmulator::new(GraphicalDeviceBus::new(&config)),
debug: DebugState::new(debug, config.symbols_path.as_ref()),
- gilrs,
+
+ #[cfg(feature = "gamepad")]
+ gilrs: match Gilrs::new() {
+ Ok(gilrs) => Some(gilrs),
+ Err(err) => {
+ info!("Could not start gamepad listener: {}", err);
+ None
+ }
+ },
fullscreen: config.fullscreen,
scale: config.zoom.into(),
@@ -37,6 +36,7 @@ impl GraphicalEmulator {
render_mark: Instant::now(),
frame_mark: Instant::now(),
config,
+ visible: false,
}
}
@@ -44,7 +44,8 @@ impl GraphicalEmulator {
self.br.core.mem.load_program(bytecode);
}
- pub fn run(self, mut phosphor: Phosphor, visible: bool) {
+ pub fn run(mut self, mut phosphor: Phosphor, visible: bool) {
+ self.visible = visible;
let window = WindowBuilder {
dimensions: Some(self.dimensions()),
size_bounds: Some(self.size_bounds()),
@@ -59,7 +60,6 @@ impl GraphicalEmulator {
program: Box::new(self),
visible,
};
-
phosphor.add_window(window);
phosphor.run().unwrap();
}
@@ -112,7 +112,7 @@ impl GraphicalDeviceBus {
input: InputDevice::new(),
screen: ScreenDevice::new(&config),
stream: StreamDevice::new(&config),
- file: FileDevice::new(),
+ file: FileDevice::new(&config),
wake_queue: WakeQueue::new(),
}
}
@@ -164,13 +164,13 @@ impl WindowProgram for GraphicalEmulator {
Event::FocusChange(_) => (),
Event::Initialise => (),
Event::ScrollLines { axis, distance } => match axis {
- Axis::Horizontal => self.br.dev.input.on_horizontal_scroll(distance * 20.0),
- Axis::Vertical => self.br.dev.input.on_vertical_scroll(distance * 20.0),
- }
- Event::ScrollPixels { axis, distance } => match axis {
Axis::Horizontal => self.br.dev.input.on_horizontal_scroll(distance),
Axis::Vertical => self.br.dev.input.on_vertical_scroll(distance),
}
+ Event::ScrollPixels { axis, distance } => match axis {
+ Axis::Horizontal => self.br.dev.input.on_horizontal_scroll(distance / 20.0),
+ Axis::Vertical => self.br.dev.input.on_vertical_scroll(distance / 20.0),
+ }
Event::Close => (),
Event::KeyboardInput { key, action } => {
@@ -203,6 +203,8 @@ impl WindowProgram for GraphicalEmulator {
fn process(&mut self, requests: &mut EventWriter<Request>) {
self.br.dev.stream.flush();
+
+ #[cfg(feature = "gamepad")]
if let Some(gilrs) = &mut self.gilrs {
while let Some(event) = gilrs.next_event() {
self.br.dev.input.on_gamepad_event(event);
@@ -233,9 +235,6 @@ impl WindowProgram for GraphicalEmulator {
if let Some(signal) = self.br.evaluate(BATCH_SIZE, self.debug.enabled) {
match signal {
Signal::Break => {
- if self.br.dev.input.accessed || self.br.dev.screen.accessed {
- requests.write(Request::SetVisible(true));
- }
}
Signal::Fork | Signal::Reset => {
self.br.reset();
@@ -260,6 +259,14 @@ impl WindowProgram for GraphicalEmulator {
}
}
+ if !self.visible {
+ if self.br.dev.input.accessed || self.br.dev.screen.accessed {
+ info!("Making window visible");
+ requests.write(Request::SetVisible(true));
+ self.visible = true;
+ }
+ }
+
if std::mem::take(&mut self.br.dev.screen.dirty_dimensions) {
requests.write(Request::SetSizeBounds(self.size_bounds()));
}
diff --git a/src/emulators/headless_emulator.rs b/src/emulators/headless_emulator.rs
index cac58cf..770bae3 100644
--- a/src/emulators/headless_emulator.rs
+++ b/src/emulators/headless_emulator.rs
@@ -70,7 +70,7 @@ impl HeadlessDeviceBus {
math: MathDevice::new(),
clock: ClockDevice::new(),
stream: StreamDevice::new(&config),
- file: FileDevice::new(),
+ file: FileDevice::new(&config),
wake_queue: WakeQueue::new(),
}
}
diff --git a/src/emulators/mod.rs b/src/emulators/mod.rs
index 8f04e4d..d4a58f9 100644
--- a/src/emulators/mod.rs
+++ b/src/emulators/mod.rs
@@ -15,7 +15,10 @@ pub struct EmulatorConfig {
pub show_cursor: bool,
pub decode_stdin: bool,
pub encode_stdout: bool,
+ pub trust_files: bool,
pub symbols_path: Option<PathBuf>,
+ pub name: Option<String>,
+ pub identifier: Option<String>,
pub title: String,
pub icon: Option<Icon>,
}
diff --git a/src/lib.rs b/src/lib.rs
index f260042..20e4e4e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -29,3 +29,25 @@ pub const DEFAULT_SCREEN_SCALE: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(
pub type ScreenPosition = geometry::Point<u16>;
pub type ScreenDimensions = geometry::Dimensions<u16>;
+
+
+pub fn run_program(bytecode: &[u8], config: EmulatorConfig) {
+ let mut args = switchboard::Switchboard::from_env();
+ args.named("verbose").short('v');
+ if args.get("verbose").as_bool() {
+ log::set_log_level(log::LogLevel::Info);
+ }
+
+ match Phosphor::new() {
+ Ok(phosphor) => {
+ info!("Starting graphical emulator");
+ let mut emulator = GraphicalEmulator::new(config, false);
+ emulator.load_program(&bytecode);
+ emulator.run(phosphor, true);
+ }
+ Err(err) => {
+ eprintln!("EventLoopError: {err:?}");
+ fatal!("Could not start graphical event loop");
+ }
+ }
+}
diff --git a/src/types/controller.rs b/src/types/controller.rs
index 76b77e3..42d3f8c 100644
--- a/src/types/controller.rs
+++ b/src/types/controller.rs
@@ -1,11 +1,15 @@
use crate::*;
+#[cfg(feature = "gamepad")]
pub use gilrs::{Gilrs, GamepadId};
pub struct OwnedGamepad {
tag: usize,
+ #[cfg(feature = "gamepad")]
id: Option<GamepadId>,
+ #[cfg(not(feature = "gamepad"))]
+ id: Option<()>,
gamepad: Gamepad,
}
@@ -15,6 +19,7 @@ impl OwnedGamepad {
}
/// Returns Some if the ID owns this gamepad.
+ #[cfg(feature = "gamepad")]
pub fn register(&mut self, new_id: GamepadId) -> Option<&mut Gamepad> {
if let Some(id) = self.id {
match id == new_id {
@@ -102,6 +107,7 @@ impl Gamepad {
}
// Returns true if the state changed.
+ #[cfg(feature = "gamepad")]
pub fn process_event(&mut self, event: &gilrs::Event) -> bool {
macro_rules! schmitt {
($name_neg:ident, $name_pos:ident, $v:expr) => {{