diff options
| author | Teddy Wing | 2018-10-27 16:58:49 +0200 | 
|---|---|---|
| committer | Teddy Wing | 2018-10-27 16:58:49 +0200 | 
| commit | e8bf5effb7b7c37684f397fd5865b29431ecbcc0 (patch) | |
| tree | 72af2635cf7cc3f06792ab35fd40ef9b0f392a4f /src/ffi.rs | |
| parent | 44d53f2ecef1645cee490dc75577b92e5c3cc86f (diff) | |
| download | dome-key-map-e8bf5effb7b7c37684f397fd5865b29431ecbcc0.tar.bz2 | |
Rename `cocoa_bridge.rs` to `ffi.rs`
When I first created the file, it was going to be used to call Cocoa
methods using the 'cocoa' crate. It's since turned into a module that
contains C functions called by Objective-C code. This new name makes
more sense.
We'll need to move one or two non-FFI functions outside of this module
for better organisation.
Diffstat (limited to 'src/ffi.rs')
| -rw-r--r-- | src/ffi.rs | 245 | 
1 files changed, 245 insertions, 0 deletions
| diff --git a/src/ffi.rs b/src/ffi.rs new file mode 100644 index 0000000..f3175b7 --- /dev/null +++ b/src/ffi.rs @@ -0,0 +1,245 @@ +use std::env; +use std::ffi::{CStr, CString, OsString}; +use std::fs; +use std::process::Command; +use std::ptr; +use std::slice; + +use libc::{c_char, size_t}; +use stderrlog; +use xdg; + +use {Action, HeadphoneButton, MapAction, MapGroup, MapKind}; +use config::{self, Config}; +use trial; + +#[repr(C)] +#[derive(Debug)] +pub struct Trigger { +    pub buttons: *const HeadphoneButton, +    pub length: size_t, +} + +#[repr(C)] +pub enum ActionKind { +    Map, +    Command, +    Mode, +} + +#[derive(Default)] +pub struct State { +    in_mode: Option<Vec<HeadphoneButton>>, +    map_group: Option<MapGroup>, +} + +#[no_mangle] +pub extern "C" fn dome_key_logger_init() { +    stderrlog::new() +        .module(module_path!()) +        .color(stderrlog::ColorChoice::Never) +        .timestamp(stderrlog::Timestamp::Millisecond) +        .init() +        .unwrap(); +} + +#[no_mangle] +pub extern "C" fn dome_key_state_new() -> *mut State { +    Box::into_raw(Box::new(State::default())) +} + +#[no_mangle] +pub extern "C" fn dome_key_state_free(ptr: *mut State) { +    if ptr.is_null() { return } +    unsafe { Box::from_raw(ptr); } +} + +#[no_mangle] +pub extern "C" fn dome_key_state_load_map_group(ptr: *mut State) { +    match xdg::BaseDirectories::with_prefix("dome-key") { +        Ok(xdg_dirs) => { +            match xdg_dirs.find_config_file("mappings.dkmap") { +                Some(mapping_file) => { +                    let state = unsafe { +                        assert!(!ptr.is_null()); +                        &mut *ptr +                    }; + +                    let dkmap = fs::read_to_string(mapping_file) +                        .expect("Failed to read 'mappings.dkmap'"); + +                    let mut map_group = MapGroup::parse(&dkmap) +                        .expect("Failed to parse 'mappings.dkmap'"); +                    map_group.parse_actions(); + +                    state.map_group = Some(map_group); +                }, +                None => { +                    match xdg_dirs.get_config_home().to_str() { +                        Some(config_home) => { +                            error!( +                                "No mapping file found at '{}{}'", +                                config_home, +                                "mappings.dkmap" +                            ) +                        }, +                        None => { +                            error!("Config home path contains invalid unicode") +                        } +                    } +                }, +            } +        }, +        Err(e) => error!("{}", e), +    } +} + +#[no_mangle] +pub extern "C" fn dome_key_run_key_action( +    state: *mut State, +    trigger: Trigger, +) { +    let trigger = unsafe { +        assert!(!trigger.buttons.is_null()); + +        slice::from_raw_parts(trigger.buttons, trigger.length as usize) +    }; + +    let mut state = unsafe { +        assert!(!state.is_null()); +        &mut *state +    }; + +    run_key_action_for_mode(&mut state, trigger); +} + +// TODO: un-extern +#[no_mangle] +pub extern "C" fn run_key_action_for_mode<'a>( +    state: &mut State, +    trigger: &'a [HeadphoneButton], +) { +    match state.map_group { +        Some(ref map_group) => { +            let map = map_group.maps.get(trigger); +            let mode = map_group.modes.get(trigger); + +            if let Some(in_mode) = state.in_mode.clone() { +                if let Some(mode) = map_group.modes.get(&in_mode) { +                    // Deactivate mode by pressing current mode trigger +                    if &in_mode[..] == trigger { +                        state.in_mode = None; + +                        return; +                    } + +                    if let Some(map) = mode.get(trigger) { +                        run_action(&map); +                    } +                } +            } + +            // TODO: make sure this doesn't run when in_mode +            if state.in_mode.is_none() { +                if let Some(map) = map { +                    run_action(&map); +                } +            } + +            if mode.is_some() { +                state.in_mode = Some(trigger.to_vec()); +            } +        }, +        None => (), +    } +} + +fn run_action(map_action: &MapAction) { +    match map_action.kind { +        MapKind::Map => { +            if let Action::Map(action) = &map_action.action { +                for key in action { +                    key.tap() +                } +            } +        }, +        MapKind::Command => { +            if let Action::String(action) = &map_action.action { +                let shell = match env::var_os("SHELL") { +                    Some(s) => s, +                    None => OsString::from("/bin/sh"), +                }; + +                match Command::new(shell) +                    .arg("-c") +                    .arg(action) +                    .spawn() { +                    Ok(_) => (), +                    Err(e) => error!( +                        "Command failed to start: `{}'", +                        e +                    ), +                } +            } +        }, +    } +} + +#[no_mangle] +pub extern "C" fn dome_key_parse_args( +    args: *const *const c_char, +    length: size_t, +    config_ptr: *mut Config +) -> *mut Config { +    let args = unsafe { +        assert!(!args.is_null()); + +        let args = slice::from_raw_parts(args, length as usize); + +        args +            .iter() +            .map(|s| { +                assert!(!s.is_null()); + +                CStr::from_ptr(*s) +                    .to_string_lossy() +                    .into_owned() +            }) +            .collect::<Vec<String>>() +    }; + +    let config = unsafe { +        assert!(!config_ptr.is_null()); + +        &mut *config_ptr +    }; +    config::parse_args(&args, config); + +    config_ptr +} + +#[no_mangle] +pub extern "C" fn dome_key_config_get() -> *mut Config { +    match config::get_config() { +        Ok(config) => Box::into_raw(Box::new(config)), +        Err(e) => { +            error!("{}", e); + +            ptr::null_mut() +        }, +    } +} + +#[no_mangle] +pub extern "C" fn dome_key_config_free(ptr: *mut Config) { +    if ptr.is_null() { return } +    let config = unsafe { Box::from_raw(ptr) }; + +    if config.args.license.is_null() { return } +    unsafe { CString::from_raw(config.args.license); } +} + +#[no_mangle] +pub extern "C" fn dome_key_do_trial() { +    trial::do_trial(); +} | 
