WIP
WIP WIP: keymap generation test passes meta: Update features and version WiP: cargo.lock WIP: don't crash WIP: no outlines parsing: New tests WIP: base level works WIP: remove old keyboard symbols correctly input WIP: lodaing files WIP: fallback works Valid fallback
This commit is contained in:
		
							
								
								
									
										483
									
								
								src/data.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										483
									
								
								src/data.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,483 @@
 | 
			
		||||
/**! The parsing of the data files for layouts */
 | 
			
		||||
 | 
			
		||||
use std::cell::RefCell;
 | 
			
		||||
use std::collections::{ HashMap, HashSet };
 | 
			
		||||
use std::env;
 | 
			
		||||
use std::ffi::CString;
 | 
			
		||||
use std::fmt;
 | 
			
		||||
use std::fs;
 | 
			
		||||
use std::io;
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
use std::rc::Rc;
 | 
			
		||||
use std::vec::Vec;
 | 
			
		||||
 | 
			
		||||
use ::keyboard::{
 | 
			
		||||
    KeyState,
 | 
			
		||||
    generate_keymap, generate_keycodes, FormattingError
 | 
			
		||||
};
 | 
			
		||||
use ::resources;
 | 
			
		||||
use ::util::c::as_str;
 | 
			
		||||
use ::xdg;
 | 
			
		||||
 | 
			
		||||
// traits, derives
 | 
			
		||||
use std::io::BufReader;
 | 
			
		||||
use std::iter::FromIterator;
 | 
			
		||||
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Gathers stuff defined in C or called by C
 | 
			
		||||
pub mod c {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use std::os::raw::c_char;
 | 
			
		||||
    
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_load_layout(name: *const c_char) -> *mut ::layout::Layout {
 | 
			
		||||
        let name = as_str(&name)
 | 
			
		||||
            .expect("Bad layout name")
 | 
			
		||||
            .expect("Empty layout name");
 | 
			
		||||
 | 
			
		||||
        let layout = load_layout_with_fallback(name);
 | 
			
		||||
        Box::into_raw(Box::new(layout))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const FALLBACK_LAYOUT_NAME: &str = "us";
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
enum LoadError {
 | 
			
		||||
    BadData(Error),
 | 
			
		||||
    MissingResource,
 | 
			
		||||
    BadResource(serde_yaml::Error),
 | 
			
		||||
    BadKeyMap(FormattingError),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl fmt::Display for LoadError {
 | 
			
		||||
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
			
		||||
        use self::LoadError::*;
 | 
			
		||||
        match self {
 | 
			
		||||
            BadData(e) => write!(f, "Bad data: {}", e),
 | 
			
		||||
            MissingResource => write!(f, "Missing resource"),
 | 
			
		||||
            BadResource(e) => write!(f, "Bad resource: {}", e),
 | 
			
		||||
            BadKeyMap(e) => write!(f, "Bad key map: {}", e),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn load_layout_from_resource(
 | 
			
		||||
    name: &str
 | 
			
		||||
) -> Result<Layout, LoadError> {
 | 
			
		||||
    let data = resources::get_keyboard(name)
 | 
			
		||||
                .ok_or(LoadError::MissingResource)?;
 | 
			
		||||
    serde_yaml::from_str(data)
 | 
			
		||||
                .map_err(|e| LoadError::BadResource(e))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum DataSource {
 | 
			
		||||
    File(PathBuf),
 | 
			
		||||
    Resource(String),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl fmt::Display for DataSource {
 | 
			
		||||
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            DataSource::File(path) => write!(f, "Path: {:?}", path.display()),
 | 
			
		||||
            DataSource::Resource(name) => write!(f, "Resource: {}", name),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Tries to load the layout from the first place where it's present.
 | 
			
		||||
/// If the layout exists, but is broken, fallback is activated.
 | 
			
		||||
fn load_layout(
 | 
			
		||||
    name: &str
 | 
			
		||||
) -> (Result<::layout::Layout, LoadError>, DataSource) {
 | 
			
		||||
    let path = env::var_os("SQUEEKBOARD_KEYBOARDSDIR")
 | 
			
		||||
        .map(PathBuf::from)
 | 
			
		||||
        .or_else(|| xdg::data_path("squeekboard/keyboards"))
 | 
			
		||||
        .map(|path| path.join(name).with_extension("yaml"));
 | 
			
		||||
 | 
			
		||||
    let (layout, source) = match path {
 | 
			
		||||
        Some(path) => {(
 | 
			
		||||
            Layout::from_yaml_stream(path.clone())
 | 
			
		||||
                .map_err(|e| LoadError::BadData(e)),
 | 
			
		||||
            DataSource::File(path)
 | 
			
		||||
        )},
 | 
			
		||||
        None => {(
 | 
			
		||||
            load_layout_from_resource(name),
 | 
			
		||||
            DataSource::Resource(name.into())
 | 
			
		||||
        )},
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    let layout = layout.and_then(
 | 
			
		||||
        |layout| layout.build().map_err(LoadError::BadKeyMap)
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    (layout, source)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn load_layout_with_fallback(
 | 
			
		||||
    name: &str
 | 
			
		||||
) -> ::layout::Layout {
 | 
			
		||||
    let (layout, source) = load_layout(name);
 | 
			
		||||
    let (layout, source) = match (layout, source) {
 | 
			
		||||
        (Err(e), source) => {
 | 
			
		||||
            eprintln!(
 | 
			
		||||
                "Failed to load layout from {}: {}, using fallback",
 | 
			
		||||
                source, e
 | 
			
		||||
            );
 | 
			
		||||
            load_layout(FALLBACK_LAYOUT_NAME)
 | 
			
		||||
        },
 | 
			
		||||
        res => res,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let (layout, source) = match (layout, source) {
 | 
			
		||||
        (Err(e), source) => {
 | 
			
		||||
            eprintln!(
 | 
			
		||||
                "Failed to load fallback layout from {}: {}, using hardcoded",
 | 
			
		||||
                source, e
 | 
			
		||||
            );
 | 
			
		||||
            (
 | 
			
		||||
                load_layout_from_resource(FALLBACK_LAYOUT_NAME)
 | 
			
		||||
                    .and_then(
 | 
			
		||||
                        |layout| layout.build().map_err(LoadError::BadKeyMap)
 | 
			
		||||
                    ),
 | 
			
		||||
                DataSource::Resource(FALLBACK_LAYOUT_NAME.into()),
 | 
			
		||||
            )
 | 
			
		||||
        },
 | 
			
		||||
        res => res,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    match (layout, source) {
 | 
			
		||||
        (Err(e), source) => {
 | 
			
		||||
            panic!(
 | 
			
		||||
                format!("Failed to load hardcoded layout from {}: {:?}",
 | 
			
		||||
                    source, e
 | 
			
		||||
                )
 | 
			
		||||
            );
 | 
			
		||||
        },
 | 
			
		||||
        (Ok(layout), source) => {
 | 
			
		||||
            eprintln!("Loaded layout from {}", source);
 | 
			
		||||
            layout
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// The root element describing an entire keyboard
 | 
			
		||||
#[derive(Debug, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(deny_unknown_fields)]
 | 
			
		||||
struct Layout {
 | 
			
		||||
    bounds: Bounds,
 | 
			
		||||
    views: HashMap<String, Vec<ButtonIds>>,
 | 
			
		||||
    #[serde(default)] 
 | 
			
		||||
    buttons: HashMap<String, ButtonMeta>,
 | 
			
		||||
    outlines: HashMap<String, Outline>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(deny_unknown_fields)]
 | 
			
		||||
struct Bounds {
 | 
			
		||||
    x: f64,
 | 
			
		||||
    y: f64,
 | 
			
		||||
    width: f64,
 | 
			
		||||
    height: f64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Buttons are embedded in a single string
 | 
			
		||||
type ButtonIds = String;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Default, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(deny_unknown_fields)]
 | 
			
		||||
struct ButtonMeta {
 | 
			
		||||
    /// Action other than keysym (conflicts with keysym)
 | 
			
		||||
    action: Option<Action>,
 | 
			
		||||
    /// The name of the outline. If not present, will be "default"
 | 
			
		||||
    outline: Option<String>,
 | 
			
		||||
    /// FIXME: start using it
 | 
			
		||||
    keysym: Option<String>,
 | 
			
		||||
    /// If not present, will be derived from the button ID
 | 
			
		||||
    label: Option<String>,
 | 
			
		||||
    /// Conflicts with label
 | 
			
		||||
    icon: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(deny_unknown_fields)]
 | 
			
		||||
enum Action {
 | 
			
		||||
    /// FIXME: start using it
 | 
			
		||||
    #[serde(rename="set_view")]
 | 
			
		||||
    SetView(String),
 | 
			
		||||
    #[serde(rename="show_prefs")]
 | 
			
		||||
    ShowPrefs,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(deny_unknown_fields)]
 | 
			
		||||
struct Outline {
 | 
			
		||||
    corner_radius: f64,
 | 
			
		||||
    bounds: Bounds,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
enum Error {
 | 
			
		||||
    Yaml(serde_yaml::Error),
 | 
			
		||||
    Io(io::Error),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl fmt::Display for Error {
 | 
			
		||||
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            Error::Yaml(e) => write!(f, "YAML: {}", e),
 | 
			
		||||
            Error::Io(e) => write!(f, "IO: {}", e),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Layout {
 | 
			
		||||
    fn from_yaml_stream(path: PathBuf) -> Result<Layout, Error> {
 | 
			
		||||
        let infile = BufReader::new(
 | 
			
		||||
            fs::OpenOptions::new()
 | 
			
		||||
                .read(true)
 | 
			
		||||
                .open(&path)
 | 
			
		||||
                .map_err(Error::Io)?
 | 
			
		||||
        );
 | 
			
		||||
        serde_yaml::from_reader(infile)
 | 
			
		||||
            .map_err(Error::Yaml)
 | 
			
		||||
    }
 | 
			
		||||
    fn build(self) -> Result<::layout::Layout, FormattingError> {
 | 
			
		||||
        let button_names = self.views.values()
 | 
			
		||||
            .flat_map(|rows| {
 | 
			
		||||
                rows.iter()
 | 
			
		||||
                    .flat_map(|row| row.split_ascii_whitespace())
 | 
			
		||||
            });
 | 
			
		||||
        
 | 
			
		||||
        let button_names: HashSet<&str>
 | 
			
		||||
            = HashSet::from_iter(button_names);
 | 
			
		||||
 | 
			
		||||
        let keycodes = generate_keycodes(
 | 
			
		||||
            button_names.iter()
 | 
			
		||||
                .map(|name| *name)
 | 
			
		||||
                .filter(|name| {
 | 
			
		||||
                    match self.buttons.get(*name) {
 | 
			
		||||
                        // buttons with defined action can't emit keysyms
 | 
			
		||||
                        // and so don't need keycodes
 | 
			
		||||
                        Some(ButtonMeta { action: Some(_), .. }) => false,
 | 
			
		||||
                        _ => true,
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let button_states = button_names.iter().map(|name| {(
 | 
			
		||||
            String::from(*name),
 | 
			
		||||
            Rc::new(RefCell::new(KeyState {
 | 
			
		||||
                pressed: false,
 | 
			
		||||
                locked: false,
 | 
			
		||||
                keycode: keycodes.get(*name).map(|k| *k),
 | 
			
		||||
                symbol: ::symbol::Symbol {
 | 
			
		||||
                    // FIXME: allow user to switch views
 | 
			
		||||
                    action: ::symbol::Action::Submit {
 | 
			
		||||
                        text: None,
 | 
			
		||||
                        // TODO: derive keysym name & value from button name
 | 
			
		||||
                        keys: vec!(),
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
            }))
 | 
			
		||||
        )});
 | 
			
		||||
 | 
			
		||||
        let button_states =
 | 
			
		||||
            HashMap::<String, Rc<RefCell<KeyState>>>::from_iter(
 | 
			
		||||
                button_states
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
        // TODO: generate from symbols
 | 
			
		||||
        let keymap_str = generate_keymap(&button_states)?;
 | 
			
		||||
 | 
			
		||||
        let views = HashMap::from_iter(
 | 
			
		||||
            self.views.iter().map(|(name, view)| {(
 | 
			
		||||
                String::from(name),
 | 
			
		||||
                Box::new(::layout::View {
 | 
			
		||||
                    bounds: ::layout::c::Bounds {
 | 
			
		||||
                        x: self.bounds.x,
 | 
			
		||||
                        y: self.bounds.y,
 | 
			
		||||
                        width: self.bounds.width,
 | 
			
		||||
                        height: self.bounds.height,
 | 
			
		||||
                    },
 | 
			
		||||
                    rows: view.iter().map(|row| {
 | 
			
		||||
                        Box::new(::layout::Row {
 | 
			
		||||
                            angle: 0,
 | 
			
		||||
                            bounds: None,
 | 
			
		||||
                            buttons: row.split_ascii_whitespace().map(|name| {
 | 
			
		||||
                                Box::new(create_button(
 | 
			
		||||
                                    &self.buttons,
 | 
			
		||||
                                    &self.outlines,
 | 
			
		||||
                                    name,
 | 
			
		||||
                                    button_states.get(name.into())
 | 
			
		||||
                                        .expect("Button state not created")
 | 
			
		||||
                                        .clone()
 | 
			
		||||
                                ))
 | 
			
		||||
                            }).collect(),
 | 
			
		||||
                        })
 | 
			
		||||
                    }).collect(),
 | 
			
		||||
                })
 | 
			
		||||
            )})
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Ok(::layout::Layout {
 | 
			
		||||
            current_view: "base".into(),
 | 
			
		||||
            views: views,
 | 
			
		||||
            keymap_str: {
 | 
			
		||||
                CString::new(keymap_str)
 | 
			
		||||
                    .expect("Invalid keymap string generated")
 | 
			
		||||
            },
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// TODO: Since this will receive user-provided data,
 | 
			
		||||
/// all .expect() on them should be turned into soft fails
 | 
			
		||||
fn create_button(
 | 
			
		||||
    button_info: &HashMap<String, ButtonMeta>,
 | 
			
		||||
    outlines: &HashMap<String, Outline>,
 | 
			
		||||
    name: &str,
 | 
			
		||||
    state: Rc<RefCell<KeyState>>,
 | 
			
		||||
) -> ::layout::Button {
 | 
			
		||||
    let cname = CString::new(name.clone())
 | 
			
		||||
        .expect("Bad name");
 | 
			
		||||
    // don't remove, because multiple buttons with the same name are allowed
 | 
			
		||||
    let default_meta = ButtonMeta::default();
 | 
			
		||||
    let button_meta = button_info.get(name)
 | 
			
		||||
        .unwrap_or(&default_meta);
 | 
			
		||||
 | 
			
		||||
    // TODO: move conversion to the C/Rust boundary
 | 
			
		||||
    let label = if let Some(label) = &button_meta.label {
 | 
			
		||||
        ::layout::Label::Text(CString::new(label.as_str())
 | 
			
		||||
            .expect("Bad label"))
 | 
			
		||||
    } else if let Some(icon) = &button_meta.icon {
 | 
			
		||||
        ::layout::Label::IconName(CString::new(icon.as_str())
 | 
			
		||||
            .expect("Bad icon"))
 | 
			
		||||
    } else {
 | 
			
		||||
        ::layout::Label::Text(cname.clone())
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let outline_name = match &button_meta.outline {
 | 
			
		||||
        Some(outline) => {
 | 
			
		||||
            if outlines.contains_key(outline) {
 | 
			
		||||
                outline.clone()
 | 
			
		||||
            } else {
 | 
			
		||||
                eprintln!("Outline named {} does not exist! Using default for button {}", outline, name);
 | 
			
		||||
                "default".into()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        None => "default".into(),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let outline = outlines.get(&outline_name)
 | 
			
		||||
        .map(|outline| (*outline).clone())
 | 
			
		||||
        .unwrap_or_else(|| {
 | 
			
		||||
            eprintln!("No default outline defied Using 1x1!");
 | 
			
		||||
            Outline {
 | 
			
		||||
                corner_radius: 0f64,
 | 
			
		||||
                bounds: Bounds { x: 0f64, y: 0f64, width: 1f64, height: 1f64 },
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    ::layout::Button {
 | 
			
		||||
        name: cname,
 | 
			
		||||
        // TODO: do layout before creating buttons
 | 
			
		||||
        bounds: ::layout::c::Bounds {
 | 
			
		||||
            x: outline.bounds.x,
 | 
			
		||||
            y: outline.bounds.y,
 | 
			
		||||
            width: outline.bounds.width,
 | 
			
		||||
            height: outline.bounds.height,
 | 
			
		||||
        },
 | 
			
		||||
        corner_radius: outline.corner_radius,
 | 
			
		||||
        label: label,
 | 
			
		||||
        state: state,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    
 | 
			
		||||
    use std::error::Error as ErrorTrait;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_parse_path() {
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            Layout::from_yaml_stream(
 | 
			
		||||
                PathBuf::from("tests/layout.yaml")
 | 
			
		||||
            ).unwrap(),
 | 
			
		||||
            Layout {
 | 
			
		||||
                bounds: Bounds { x: 0f64, y: 0f64, width: 0f64, height: 0f64 },
 | 
			
		||||
                views: hashmap!(
 | 
			
		||||
                    "base".into() => vec!("test".into()),
 | 
			
		||||
                ),
 | 
			
		||||
                buttons: hashmap!{
 | 
			
		||||
                    "test".into() => ButtonMeta {
 | 
			
		||||
                        icon: None,
 | 
			
		||||
                        keysym: None,
 | 
			
		||||
                        action: None,
 | 
			
		||||
                        label: Some("test".into()),
 | 
			
		||||
                        outline: None,
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                outlines: hashmap!{
 | 
			
		||||
                    "default".into() => Outline {
 | 
			
		||||
                        corner_radius: 1f64,
 | 
			
		||||
                        bounds: Bounds {
 | 
			
		||||
                            x: 0f64, y: 0f64, width: 0f64, height: 0f64
 | 
			
		||||
                        }, 
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Check if the default protection works
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_empty_views() {
 | 
			
		||||
        let out = Layout::from_yaml_stream(PathBuf::from("tests/layout2.yaml"));
 | 
			
		||||
        match out {
 | 
			
		||||
            Ok(_) => assert!(false, "Data mistakenly accepted"),
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                let mut handled = false;
 | 
			
		||||
                if let Error::Yaml(ye) = &e {
 | 
			
		||||
                    handled = ye.description() == "missing field `views`";
 | 
			
		||||
                };
 | 
			
		||||
                if !handled {
 | 
			
		||||
                    println!("Unexpected error {:?}", e);
 | 
			
		||||
                    assert!(false)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_extra_field() {
 | 
			
		||||
        let out = Layout::from_yaml_stream(PathBuf::from("tests/layout3.yaml"));
 | 
			
		||||
        match out {
 | 
			
		||||
            Ok(_) => assert!(false, "Data mistakenly accepted"),
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                let mut handled = false;
 | 
			
		||||
                if let Error::Yaml(ye) = &e {
 | 
			
		||||
                    handled = ye.description()
 | 
			
		||||
                        .starts_with("unknown field `bad_field`");
 | 
			
		||||
                };
 | 
			
		||||
                if !handled {
 | 
			
		||||
                    println!("Unexpected error {:?}", e);
 | 
			
		||||
                    assert!(false)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn parsing_fallback() {
 | 
			
		||||
        assert!(load_layout_from_resource(FALLBACK_LAYOUT_NAME)
 | 
			
		||||
            .and_then(|layout| layout.build().map_err(LoadError::BadKeyMap))
 | 
			
		||||
            .is_ok()
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -4,6 +4,7 @@ use std::num::Wrapping;
 | 
			
		||||
use std::string::String;
 | 
			
		||||
 | 
			
		||||
use super::bitflags;
 | 
			
		||||
use ::util::c::into_cstring;
 | 
			
		||||
 | 
			
		||||
// Traits
 | 
			
		||||
use std::convert::TryFrom;
 | 
			
		||||
@ -13,15 +14,8 @@ use std::convert::TryFrom;
 | 
			
		||||
pub mod c {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    
 | 
			
		||||
    use std::ffi::CStr;
 | 
			
		||||
    use std::os::raw::{c_char, c_void};
 | 
			
		||||
    
 | 
			
		||||
    fn into_cstring(s: *const c_char) -> Result<CString, std::ffi::NulError> {
 | 
			
		||||
        CString::new(
 | 
			
		||||
            unsafe {CStr::from_ptr(s)}.to_bytes()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // The following defined in C
 | 
			
		||||
        
 | 
			
		||||
    /// struct zwp_input_method_v2*
 | 
			
		||||
@ -91,7 +85,9 @@ pub mod c {
 | 
			
		||||
    {
 | 
			
		||||
        let imservice = check_imservice(imservice, im).unwrap();
 | 
			
		||||
        imservice.pending = IMProtocolState {
 | 
			
		||||
            surrounding_text: into_cstring(text).expect("Received invalid string"),
 | 
			
		||||
            surrounding_text: into_cstring(text)
 | 
			
		||||
                .expect("Received invalid string")
 | 
			
		||||
                .expect("Received null string"),
 | 
			
		||||
            surrounding_cursor: cursor,
 | 
			
		||||
            ..imservice.pending.clone()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										292
									
								
								src/keyboard.rs
									
									
									
									
									
								
							
							
						
						
									
										292
									
								
								src/keyboard.rs
									
									
									
									
									
								
							@ -1,29 +1,29 @@
 | 
			
		||||
use std::vec::Vec;
 | 
			
		||||
/*! State of the emulated keyboard and keys */
 | 
			
		||||
 | 
			
		||||
use super::symbol;
 | 
			
		||||
use std::cell::RefCell;
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use std::fmt;
 | 
			
		||||
use std::io;
 | 
			
		||||
use std::rc::Rc;
 | 
			
		||||
use std::string::FromUtf8Error;
 | 
			
		||||
    
 | 
			
		||||
use ::symbol::{ Symbol, Action };
 | 
			
		||||
 | 
			
		||||
use std::io::Write;
 | 
			
		||||
use std::iter::{ FromIterator, IntoIterator };
 | 
			
		||||
 | 
			
		||||
/// Gathers stuff defined in C or called by C
 | 
			
		||||
pub mod c {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use ::util::c::{ as_cstr, into_cstring };
 | 
			
		||||
    use ::util::c::as_cstr;
 | 
			
		||||
    
 | 
			
		||||
    use std::cell::RefCell;
 | 
			
		||||
    use std::ffi::CString;
 | 
			
		||||
    use std::os::raw::c_char;
 | 
			
		||||
    use std::ptr;
 | 
			
		||||
    use std::rc::Rc;
 | 
			
		||||
 | 
			
		||||
    // traits
 | 
			
		||||
    
 | 
			
		||||
    use std::borrow::ToOwned;
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    // The following defined in C
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    extern "C" {
 | 
			
		||||
        fn eek_keysym_from_name(name: *const c_char) -> u32;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// The wrapped structure for KeyState suitable for handling in C
 | 
			
		||||
    /// Since C doesn't respect borrowing rules,
 | 
			
		||||
    /// RefCell will enforce them dynamically (only 1 writer/many readers)
 | 
			
		||||
@ -64,23 +64,6 @@ pub mod c {
 | 
			
		||||
    // TODO: unwrapping
 | 
			
		||||
 | 
			
		||||
    // The following defined in Rust. TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers
 | 
			
		||||
    
 | 
			
		||||
    // TODO: this will receive data from the filesystem,
 | 
			
		||||
    // so it should handle garbled strings in the future
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_key_new(keycode: u32) -> CKeyState {
 | 
			
		||||
        let state: Rc<RefCell<KeyState>> = Rc::new(RefCell::new(
 | 
			
		||||
            KeyState {
 | 
			
		||||
                pressed: false,
 | 
			
		||||
                locked: false,
 | 
			
		||||
                keycode: keycode,
 | 
			
		||||
                symbol: None,
 | 
			
		||||
            }
 | 
			
		||||
        ));
 | 
			
		||||
        CKeyState::wrap(state)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_key_free(key: CKeyState) {
 | 
			
		||||
@ -115,103 +98,7 @@ pub mod c {
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_key_get_keycode(key: CKeyState) -> u32 {
 | 
			
		||||
        return key.to_owned().keycode as u32;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_key_set_keycode(key: CKeyState, code: u32) {
 | 
			
		||||
        key.borrow_mut(|key| key.keycode = code);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // TODO: this will receive data from the filesystem,
 | 
			
		||||
    // so it should handle garbled strings in the future
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_key_add_symbol(
 | 
			
		||||
        key: CKeyState,
 | 
			
		||||
        element: *const c_char,
 | 
			
		||||
        text_raw: *const c_char, keyval: u32,
 | 
			
		||||
        label: *const c_char, icon: *const c_char,
 | 
			
		||||
        tooltip: *const c_char,
 | 
			
		||||
    ) {
 | 
			
		||||
        let element = as_cstr(&element)
 | 
			
		||||
            .expect("Missing element name");
 | 
			
		||||
 | 
			
		||||
        let text = into_cstring(text_raw)
 | 
			
		||||
            .unwrap_or_else(|e| {
 | 
			
		||||
                eprintln!("Text unreadable: {}", e);
 | 
			
		||||
                None
 | 
			
		||||
            })
 | 
			
		||||
            .and_then(|text| {
 | 
			
		||||
                if text.as_bytes() == b"" {
 | 
			
		||||
                    None
 | 
			
		||||
                } else {
 | 
			
		||||
                    Some(text)
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        let icon = into_cstring(icon)
 | 
			
		||||
            .unwrap_or_else(|e| {
 | 
			
		||||
                eprintln!("Icon name unreadable: {}", e);
 | 
			
		||||
                None
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        use symbol::*;
 | 
			
		||||
        // Only read label if there's no icon
 | 
			
		||||
        let label = match icon {
 | 
			
		||||
            Some(icon) => Label::IconName(icon),
 | 
			
		||||
            None => Label::Text(
 | 
			
		||||
                into_cstring(label)
 | 
			
		||||
                    .unwrap_or_else(|e| {
 | 
			
		||||
                        eprintln!("Label unreadable: {}", e);
 | 
			
		||||
                        Some(CString::new(" ").unwrap())
 | 
			
		||||
                    })
 | 
			
		||||
                    .unwrap_or_else(|| {
 | 
			
		||||
                        eprintln!("Label missing");
 | 
			
		||||
                        CString::new(" ").unwrap()
 | 
			
		||||
                    })
 | 
			
		||||
            ),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let tooltip = into_cstring(tooltip)
 | 
			
		||||
            .unwrap_or_else(|e| {
 | 
			
		||||
                eprintln!("Tooltip unreadable: {}", e);
 | 
			
		||||
                None
 | 
			
		||||
            });
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        key.borrow_mut(|key| {
 | 
			
		||||
            if let Some(_) = key.symbol {
 | 
			
		||||
                eprintln!("Key {:?} already has a symbol defined", text);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            key.symbol = Some(match element.to_bytes() {
 | 
			
		||||
                b"symbol" => Symbol {
 | 
			
		||||
                    action: Action::Submit {
 | 
			
		||||
                        text: text,
 | 
			
		||||
                        keys: Vec::new(),
 | 
			
		||||
                    },
 | 
			
		||||
                    label: label,
 | 
			
		||||
                    tooltip: tooltip,
 | 
			
		||||
                },
 | 
			
		||||
                _ => panic!("unsupported element type {:?}", element),
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_key_get_symbol(key: CKeyState) -> *const symbol::Symbol {
 | 
			
		||||
        key.borrow_mut(|key| {
 | 
			
		||||
            match key.symbol {
 | 
			
		||||
                // This pointer stays after the function exits,
 | 
			
		||||
                // so it must reference borrowed data and not any copy
 | 
			
		||||
                Some(ref symbol) => symbol as *const symbol::Symbol,
 | 
			
		||||
                None => ptr::null(),
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        return key.to_owned().keycode.unwrap_or(0u32);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
@ -225,20 +112,14 @@ pub mod c {
 | 
			
		||||
            .to_str()
 | 
			
		||||
            .expect("Bad key name");
 | 
			
		||||
 | 
			
		||||
        let symbol_name = match key.to_owned().symbol {
 | 
			
		||||
            Some(ref symbol) => match &symbol.action {
 | 
			
		||||
                symbol::Action::Submit { text: Some(text), .. } => {
 | 
			
		||||
                    Some(
 | 
			
		||||
                        text.clone()
 | 
			
		||||
                            .into_string().expect("Bad symbol")
 | 
			
		||||
                    )
 | 
			
		||||
                },
 | 
			
		||||
                _ => None
 | 
			
		||||
            },
 | 
			
		||||
            None => {
 | 
			
		||||
                eprintln!("Key {} has no symbol", key_name);
 | 
			
		||||
                None
 | 
			
		||||
        let symbol_name = match key.to_owned().symbol.action {
 | 
			
		||||
            Action::Submit { text: Some(text), .. } => {
 | 
			
		||||
                Some(
 | 
			
		||||
                    text.clone()
 | 
			
		||||
                        .into_string().expect("Bad symbol text")
 | 
			
		||||
                )
 | 
			
		||||
            },
 | 
			
		||||
            _ => None
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let inner = match symbol_name {
 | 
			
		||||
@ -251,7 +132,7 @@ pub mod c {
 | 
			
		||||
            .into_raw()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
        #[no_mangle]
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_key_get_action_name(
 | 
			
		||||
        key_name: *const c_char,
 | 
			
		||||
@ -262,20 +143,14 @@ pub mod c {
 | 
			
		||||
            .to_str()
 | 
			
		||||
            .expect("Bad key name");
 | 
			
		||||
 | 
			
		||||
        let symbol_name = match key.to_owned().symbol {
 | 
			
		||||
            Some(ref symbol) => match &symbol.action {
 | 
			
		||||
                symbol::Action::Submit { text: Some(text), .. } => {
 | 
			
		||||
                    Some(
 | 
			
		||||
                        text.clone()
 | 
			
		||||
                            .into_string().expect("Bad symbol")
 | 
			
		||||
                    )
 | 
			
		||||
                },
 | 
			
		||||
                _ => None
 | 
			
		||||
            },
 | 
			
		||||
            None => {
 | 
			
		||||
                eprintln!("Key {} has no symbol", key_name);
 | 
			
		||||
                None
 | 
			
		||||
        let symbol_name = match key.to_owned().symbol.action {
 | 
			
		||||
            Action::Submit { text: Some(text), .. } => {
 | 
			
		||||
                Some(
 | 
			
		||||
                    text.clone()
 | 
			
		||||
                        .into_string().expect("Bad symbol text")
 | 
			
		||||
                )
 | 
			
		||||
            },
 | 
			
		||||
            _ => None
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let inner = match symbol_name {
 | 
			
		||||
@ -287,14 +162,115 @@ pub mod c {
 | 
			
		||||
            .expect("Couldn't convert string")
 | 
			
		||||
            .into_raw()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct KeyState {
 | 
			
		||||
    pub pressed: bool,
 | 
			
		||||
    pub locked: bool,
 | 
			
		||||
    pub keycode: u32,
 | 
			
		||||
    // TODO: remove the optionality of a symbol
 | 
			
		||||
    pub symbol: Option<symbol::Symbol>,
 | 
			
		||||
    pub keycode: Option<u32>,
 | 
			
		||||
    pub symbol: Symbol,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Generates a mapping where each key gets a keycode, starting from 8
 | 
			
		||||
pub fn generate_keycodes<'a, C: IntoIterator<Item=&'a str>>(
 | 
			
		||||
    key_names: C
 | 
			
		||||
) -> HashMap<String, u32> {
 | 
			
		||||
    HashMap::from_iter(
 | 
			
		||||
        key_names.into_iter()
 | 
			
		||||
            .map(|name| String::from(name))
 | 
			
		||||
            .zip(8..)
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub enum FormattingError {
 | 
			
		||||
    Utf(FromUtf8Error),
 | 
			
		||||
    Format(io::Error),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl fmt::Display for FormattingError {
 | 
			
		||||
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            FormattingError::Utf(e) => write!(f, "UTF: {}", e),
 | 
			
		||||
            FormattingError::Format(e) => write!(f, "Format: {}", e),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<io::Error> for FormattingError {
 | 
			
		||||
    fn from(e: io::Error) -> Self {
 | 
			
		||||
        FormattingError::Format(e)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Generates a de-facto single level keymap. TODO: actually drop second level
 | 
			
		||||
/// TODO: take in keysym->keycode mapping
 | 
			
		||||
pub fn generate_keymap(
 | 
			
		||||
    keystates: &HashMap::<String, Rc<RefCell<KeyState>>>
 | 
			
		||||
) -> Result<String, FormattingError> {
 | 
			
		||||
    let mut buf: Vec<u8> = Vec::new();
 | 
			
		||||
    writeln!(
 | 
			
		||||
        buf,
 | 
			
		||||
        "xkb_keymap {{
 | 
			
		||||
 | 
			
		||||
    xkb_keycodes \"squeekboard\" {{
 | 
			
		||||
        minimum = 8;
 | 
			
		||||
        maximum = 255;"
 | 
			
		||||
    )?;
 | 
			
		||||
    
 | 
			
		||||
    for (name, state) in keystates.iter() {
 | 
			
		||||
        if let Some(keycode) = state.borrow().keycode {
 | 
			
		||||
            write!(
 | 
			
		||||
                buf,
 | 
			
		||||
                "
 | 
			
		||||
        <{}> = {};",
 | 
			
		||||
                name,
 | 
			
		||||
                keycode
 | 
			
		||||
            )?;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    writeln!(
 | 
			
		||||
        buf,
 | 
			
		||||
        "
 | 
			
		||||
    }};
 | 
			
		||||
    
 | 
			
		||||
    xkb_symbols \"squeekboard\" {{
 | 
			
		||||
 | 
			
		||||
        name[Group1] = \"Letters\";
 | 
			
		||||
        name[Group2] = \"Numbers/Symbols\";"
 | 
			
		||||
    )?;
 | 
			
		||||
    
 | 
			
		||||
    for (name, state) in keystates.iter() {
 | 
			
		||||
        if let Some(_) = state.borrow().keycode {
 | 
			
		||||
            write!(
 | 
			
		||||
                buf,
 | 
			
		||||
                "
 | 
			
		||||
        key <{}> {{ [ {0} ] }};",
 | 
			
		||||
                name,
 | 
			
		||||
            )?;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    writeln!(
 | 
			
		||||
        buf,
 | 
			
		||||
        "
 | 
			
		||||
    }};
 | 
			
		||||
 | 
			
		||||
    xkb_types \"squeekboard\" {{
 | 
			
		||||
 | 
			
		||||
	type \"TWO_LEVEL\" {{
 | 
			
		||||
            modifiers = Shift;
 | 
			
		||||
            map[Shift] = Level2;
 | 
			
		||||
            level_name[Level1] = \"Base\";
 | 
			
		||||
            level_name[Level2] = \"Shift\";
 | 
			
		||||
	}};
 | 
			
		||||
    }};
 | 
			
		||||
 | 
			
		||||
    xkb_compatibility \"squeekboard\" {{
 | 
			
		||||
    }};
 | 
			
		||||
}};"
 | 
			
		||||
    )?;
 | 
			
		||||
    
 | 
			
		||||
    String::from_utf8(buf).map_err(FormattingError::Utf)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										40
									
								
								src/layout.h
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								src/layout.h
									
									
									
									
									
								
							@ -9,17 +9,10 @@
 | 
			
		||||
struct squeek_button;
 | 
			
		||||
struct squeek_row;
 | 
			
		||||
struct squeek_view;
 | 
			
		||||
struct squeek_layout;
 | 
			
		||||
 | 
			
		||||
struct squeek_row *squeek_row_new(int32_t angle);
 | 
			
		||||
struct squeek_button *squeek_row_create_button (struct squeek_row *row,
 | 
			
		||||
                                          guint keycode, guint oref);
 | 
			
		||||
struct squeek_button *squeek_row_create_button_with_state(struct squeek_row *row,
 | 
			
		||||
                                    struct squeek_button *source);
 | 
			
		||||
void squeek_row_set_angle(struct squeek_row *row, int32_t angle);
 | 
			
		||||
int32_t squeek_row_get_angle(const struct squeek_row*);
 | 
			
		||||
 | 
			
		||||
EekBounds squeek_row_get_bounds(const struct squeek_row*);
 | 
			
		||||
void squeek_row_set_bounds(struct squeek_row* row, EekBounds bounds);
 | 
			
		||||
 | 
			
		||||
uint32_t squeek_row_contains(struct squeek_row*, struct squeek_button *button);
 | 
			
		||||
 | 
			
		||||
@ -33,34 +26,20 @@ void squeek_row_foreach(struct squeek_row*,
 | 
			
		||||
 | 
			
		||||
void squeek_row_free(struct squeek_row*);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
struct squeek_button *squeek_buttons_find_by_position(
 | 
			
		||||
    const struct squeek_buttons *buttons,
 | 
			
		||||
    double x, double y,
 | 
			
		||||
    double origin_x, double origin_y,
 | 
			
		||||
    double angle);
 | 
			
		||||
void squeek_buttons_add(struct squeek_buttons*, const struct squeek_button* button);
 | 
			
		||||
void squeek_buttons_remove_key(struct squeek_buttons*, const struct squeek_key* key);
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
struct squeek_button *squeek_button_new(uint32_t keycode, uint32_t oref);
 | 
			
		||||
struct squeek_button *squeek_button_new_with_state(const struct squeek_button* source);
 | 
			
		||||
uint32_t squeek_button_get_oref(const struct squeek_button*);
 | 
			
		||||
EekBounds squeek_button_get_bounds(const struct squeek_button*);
 | 
			
		||||
void squeek_button_set_bounds(struct squeek_button* button, EekBounds bounds);
 | 
			
		||||
const char *squeek_button_get_label(const struct squeek_button*);
 | 
			
		||||
const char *squeek_button_get_icon_name(const struct squeek_button*);
 | 
			
		||||
const char *squeek_button_get_name(struct squeek_button*);
 | 
			
		||||
 | 
			
		||||
struct squeek_symbol *squeek_button_get_symbol (
 | 
			
		||||
    const struct squeek_button *button);
 | 
			
		||||
struct squeek_key *squeek_button_get_key(const struct squeek_button*);
 | 
			
		||||
uint32_t *squeek_button_has_key(const struct squeek_button* button,
 | 
			
		||||
                                const struct squeek_key *key);
 | 
			
		||||
void squeek_button_print(const struct squeek_button* button);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
struct squeek_view *squeek_view_new(EekBounds bounds);
 | 
			
		||||
struct squeek_row *squeek_view_create_row(struct squeek_view *, int32_t angle);
 | 
			
		||||
EekBounds squeek_view_get_bounds(const struct squeek_view*);
 | 
			
		||||
void squeek_view_set_bounds(const struct squeek_view*, EekBounds bounds);
 | 
			
		||||
 | 
			
		||||
typedef void (*RowCallback) (struct squeek_row *row, gpointer user_data);
 | 
			
		||||
void squeek_view_foreach(struct squeek_view*,
 | 
			
		||||
@ -70,9 +49,16 @@ void squeek_view_foreach(struct squeek_view*,
 | 
			
		||||
struct squeek_row *squeek_view_get_row(struct squeek_view *view,
 | 
			
		||||
                                       struct squeek_button *button);
 | 
			
		||||
 | 
			
		||||
struct squeek_button *squeek_view_find_button_by_position(struct squeek_view *view, EekPoint point);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
squeek_view_place_contents(struct squeek_view *view, LevelKeyboard *keyboard);
 | 
			
		||||
squeek_layout_place_contents(struct squeek_layout*);
 | 
			
		||||
struct squeek_view *squeek_layout_get_current_view(struct squeek_layout*);
 | 
			
		||||
uint32_t squeek_layout_get_level(struct squeek_layout*);
 | 
			
		||||
void squeek_layout_set_level(struct squeek_layout* layout, uint32_t level);
 | 
			
		||||
 | 
			
		||||
struct squeek_button *squeek_view_find_button_by_position(struct squeek_view *view, EekPoint point);
 | 
			
		||||
struct squeek_layout *squeek_load_layout(const char *type);
 | 
			
		||||
const char *squeek_layout_get_keymap(const struct squeek_layout*);
 | 
			
		||||
void squeek_layout_free(struct squeek_layout*);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										441
									
								
								src/layout.rs
									
									
									
									
									
								
							
							
						
						
									
										441
									
								
								src/layout.rs
									
									
									
									
									
								
							@ -1,4 +1,4 @@
 | 
			
		||||
/**
 | 
			
		||||
/*!
 | 
			
		||||
 * Layout-related data.
 | 
			
		||||
 * 
 | 
			
		||||
 * The `View` contains `Row`s and each `Row` contains `Button`s.
 | 
			
		||||
@ -18,6 +18,8 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use std::cell::RefCell;
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use std::ffi::CString;
 | 
			
		||||
use std::rc::Rc;
 | 
			
		||||
use std::vec::Vec;
 | 
			
		||||
 | 
			
		||||
@ -29,14 +31,15 @@ use ::symbol::*;
 | 
			
		||||
pub mod c {
 | 
			
		||||
    use super::*;
 | 
			
		||||
 | 
			
		||||
    use std::os::raw::c_void;
 | 
			
		||||
    use std::ffi::CStr;
 | 
			
		||||
    use std::os::raw::{ c_char, c_void };
 | 
			
		||||
    use std::ptr;
 | 
			
		||||
 | 
			
		||||
    // The following defined in C
 | 
			
		||||
 | 
			
		||||
    #[repr(transparent)]
 | 
			
		||||
    pub struct UserData(*const c_void);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /// The index in the relevant outline table
 | 
			
		||||
    #[repr(C)]
 | 
			
		||||
    #[derive(Clone, Debug)]
 | 
			
		||||
@ -60,57 +63,18 @@ pub mod c {
 | 
			
		||||
        pub height: f64
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    impl Bounds {
 | 
			
		||||
        pub fn zero() -> Bounds {
 | 
			
		||||
            Bounds { x: 0f64, y: 0f64, width: 0f64, height: 0f64 }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    type ButtonCallback = unsafe extern "C" fn(button: *mut ::layout::Button, data: *mut UserData);
 | 
			
		||||
    type RowCallback = unsafe extern "C" fn(row: *mut ::layout::Row, data: *mut UserData);
 | 
			
		||||
 | 
			
		||||
    // The following defined in Rust. TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers
 | 
			
		||||
    
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_view_new(bounds: Bounds) -> *mut ::layout::View {
 | 
			
		||||
        Box::into_raw(Box::new(::layout::View {
 | 
			
		||||
            rows: Vec::new(),
 | 
			
		||||
            bounds: bounds,
 | 
			
		||||
        }))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_view_get_bounds(view: *const ::layout::View) -> Bounds {
 | 
			
		||||
        unsafe { &*view }.bounds.clone()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_view_set_bounds(view: *mut ::layout::View, bounds: Bounds) {
 | 
			
		||||
        unsafe { &mut *view }.bounds = bounds;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Places a row into the view and returns a reference to it
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_view_create_row(
 | 
			
		||||
        view: *mut ::layout::View,
 | 
			
		||||
        angle: i32,
 | 
			
		||||
    ) -> *mut ::layout::Row {
 | 
			
		||||
        let view = unsafe { &mut *view };
 | 
			
		||||
 | 
			
		||||
        view.rows.push(Box::new(::layout::Row::new(angle)));
 | 
			
		||||
        // Return the reference directly instead of a Box, it's not on the stack
 | 
			
		||||
        // It will live as long as the Vec
 | 
			
		||||
        let last_idx = view.rows.len() - 1;
 | 
			
		||||
        // Caution: Box can't be returned directly,
 | 
			
		||||
        // so returning a reference to its innards
 | 
			
		||||
        view.rows[last_idx].as_mut() as *mut ::layout::Row
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
        #[no_mangle]
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_view_foreach(
 | 
			
		||||
        view: *mut ::layout::View,
 | 
			
		||||
@ -123,68 +87,6 @@ pub mod c {
 | 
			
		||||
            unsafe { callback(row, data) };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_row_new(angle: i32) -> *mut ::layout::Row {
 | 
			
		||||
        Box::into_raw(Box::new(::layout::Row::new(angle)))
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Places a button into the row and returns a reference to it
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_row_create_button(
 | 
			
		||||
        row: *mut ::layout::Row,
 | 
			
		||||
        keycode: u32, oref: u32
 | 
			
		||||
    ) -> *mut ::layout::Button {
 | 
			
		||||
        let row = unsafe { &mut *row };
 | 
			
		||||
        let state: Rc<RefCell<::keyboard::KeyState>> = Rc::new(RefCell::new(
 | 
			
		||||
            ::keyboard::KeyState {
 | 
			
		||||
                pressed: false,
 | 
			
		||||
                locked: false,
 | 
			
		||||
                keycode: keycode,
 | 
			
		||||
                symbol: None,
 | 
			
		||||
            }
 | 
			
		||||
        ));
 | 
			
		||||
        row.buttons.push(Box::new(::layout::Button {
 | 
			
		||||
            oref: OutlineRef(oref),
 | 
			
		||||
            bounds: None,
 | 
			
		||||
            state: state,
 | 
			
		||||
        }));
 | 
			
		||||
        // Return the reference directly instead of a Box, it's not on the stack
 | 
			
		||||
        // It will live as long as the Vec
 | 
			
		||||
        let last_idx = row.buttons.len() - 1;
 | 
			
		||||
        // Caution: Box can't be returned directly,
 | 
			
		||||
        // so returning a reference to its innards
 | 
			
		||||
        row.buttons[last_idx].as_mut() as *mut ::layout::Button
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Places a button into the row, copying its state,
 | 
			
		||||
    /// and returns a reference to it
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_row_create_button_with_state(
 | 
			
		||||
        row: *mut ::layout::Row,
 | 
			
		||||
        button: *const ::layout::Button,
 | 
			
		||||
    ) -> *mut ::layout::Button {
 | 
			
		||||
        let row = unsafe { &mut *row };
 | 
			
		||||
        let source = unsafe { &*button };
 | 
			
		||||
        row.buttons.push(Box::new(source.clone()));
 | 
			
		||||
        // Return the reference directly instead of a Box, it's not on the stack
 | 
			
		||||
        // It will live as long as the Vec
 | 
			
		||||
        let last_idx = row.buttons.len() - 1;
 | 
			
		||||
        // Caution: Box can't be returned directly,
 | 
			
		||||
        // so returning a reference to its innards directly
 | 
			
		||||
        row.buttons[last_idx].as_mut() as *mut ::layout::Button
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_row_set_angle(row: *mut ::layout::Row, angle: i32) {
 | 
			
		||||
        let row = unsafe { &mut *row };
 | 
			
		||||
        row.angle = angle;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
@ -203,14 +105,6 @@ pub mod c {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set bounds by consuming the value
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_row_set_bounds(row: *mut ::layout::Row, bounds: Bounds) {
 | 
			
		||||
        let row = unsafe { &mut *row };
 | 
			
		||||
        row.bounds = Some(bounds);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_row_foreach(
 | 
			
		||||
@ -231,59 +125,13 @@ pub mod c {
 | 
			
		||||
        unsafe { Box::from_raw(row) }; // gets dropped
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_button_new(keycode: u32, oref: u32) -> *mut ::layout::Button {
 | 
			
		||||
        let state: Rc<RefCell<::keyboard::KeyState>> = Rc::new(RefCell::new(
 | 
			
		||||
            ::keyboard::KeyState {
 | 
			
		||||
                pressed: false,
 | 
			
		||||
                locked: false,
 | 
			
		||||
                keycode: keycode,
 | 
			
		||||
                symbol: None,
 | 
			
		||||
            }
 | 
			
		||||
        ));
 | 
			
		||||
        Box::into_raw(Box::new(::layout::Button {
 | 
			
		||||
            oref: OutlineRef(oref),
 | 
			
		||||
            bounds: None,
 | 
			
		||||
            state: state,
 | 
			
		||||
        }))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_button_new_with_state(source: *mut ::layout::Button) -> *mut ::layout::Button {
 | 
			
		||||
        let source = unsafe { &*source };
 | 
			
		||||
        let button = Box::new(source.clone());
 | 
			
		||||
        Box::into_raw(button)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_button_get_oref(button: *const ::layout::Button) -> u32 {
 | 
			
		||||
        let button = unsafe { &*button };
 | 
			
		||||
        button.oref.0
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Bounds transparently mapped to C, therefore no pointer needed
 | 
			
		||||
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_button_get_bounds(button: *const ::layout::Button) -> Bounds {
 | 
			
		||||
        let button = unsafe { &*button };
 | 
			
		||||
        match &button.bounds {
 | 
			
		||||
            Some(bounds) => bounds.clone(),
 | 
			
		||||
            None => panic!("Button doesn't have any bounds yet"),
 | 
			
		||||
        }
 | 
			
		||||
        button.bounds.clone()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Set bounds by consuming the value
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_button_set_bounds(button: *mut ::layout::Button, bounds: Bounds) {
 | 
			
		||||
        let button = unsafe { &mut *button };
 | 
			
		||||
        button.bounds = Some(bounds);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /// Borrow a new reference to key state. Doesn't need freeing
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
@ -302,12 +150,43 @@ pub mod c {
 | 
			
		||||
    ) -> *const Symbol {
 | 
			
		||||
        let button = unsafe { &*button };
 | 
			
		||||
        let state = button.state.borrow();
 | 
			
		||||
        match state.symbol {
 | 
			
		||||
            Some(ref symbol) => symbol as *const Symbol,
 | 
			
		||||
            None => ptr::null(),
 | 
			
		||||
        &state.symbol as *const Symbol
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_button_get_label(
 | 
			
		||||
        button: *const ::layout::Button
 | 
			
		||||
    ) -> *const c_char {
 | 
			
		||||
        let button = unsafe { &*button };
 | 
			
		||||
        match &button.label {
 | 
			
		||||
            Label::Text(text) => text.as_ptr(),
 | 
			
		||||
            // returning static strings to C is a bit cumbersome
 | 
			
		||||
            Label::IconName(_) => unsafe {
 | 
			
		||||
                // CStr doesn't allocate anything, so it only points to
 | 
			
		||||
                // the 'static str, avoiding a memory leak
 | 
			
		||||
                CStr::from_bytes_with_nul_unchecked(b"icon\0")
 | 
			
		||||
            }.as_ptr(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_button_get_icon_name(button: *const Button) -> *const c_char {
 | 
			
		||||
        let button = unsafe { &*button };
 | 
			
		||||
        match &button.label {
 | 
			
		||||
            Label::Text(_) => ptr::null(),
 | 
			
		||||
            Label::IconName(name) => name.as_ptr(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_button_get_name(button: *const Button) -> *const c_char {
 | 
			
		||||
        let button = unsafe { &*button };
 | 
			
		||||
        button.name.as_ptr()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_button_has_key(
 | 
			
		||||
@ -328,13 +207,61 @@ pub mod c {
 | 
			
		||||
        println!("{:?}", button);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_layout_get_current_view(layout: *const Layout) -> *const View {
 | 
			
		||||
        let layout = unsafe { &*layout };
 | 
			
		||||
        let view_name = layout.current_view.clone();
 | 
			
		||||
        layout.views.get(&view_name)
 | 
			
		||||
            .expect("Current view doesn't exist")
 | 
			
		||||
            .as_ref() as *const View
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// FIXME: very temporary way to minimize level impact
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_layout_get_level(layout: *const Layout) -> u32 {
 | 
			
		||||
        let layout = unsafe { &*layout };
 | 
			
		||||
        match layout.current_view.as_str() {
 | 
			
		||||
            "base" => 0,
 | 
			
		||||
            "upper" => 1,
 | 
			
		||||
            "numbers" => 2,
 | 
			
		||||
            "symbols" => 3,
 | 
			
		||||
            _ => 0
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_layout_set_level(layout: *mut Layout, level: u32) {
 | 
			
		||||
        let mut layout = unsafe { &mut*layout };
 | 
			
		||||
        layout.current_view = String::from(match level {
 | 
			
		||||
            0 => "base",
 | 
			
		||||
            1 => "upper",
 | 
			
		||||
            2 => "numbers",
 | 
			
		||||
            3 => "symbols",
 | 
			
		||||
            _ => "base",
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_layout_get_keymap(layout: *const Layout) -> *const c_char {
 | 
			
		||||
        let layout = unsafe { &*layout };
 | 
			
		||||
        layout.keymap_str.as_ptr()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_layout_free(layout: *mut Layout) {
 | 
			
		||||
        unsafe { Box::from_raw(layout) };
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    /// Entry points for more complex procedures and algoithms which span multiple modules
 | 
			
		||||
    pub mod procedures {
 | 
			
		||||
        use super::*;
 | 
			
		||||
        
 | 
			
		||||
        #[repr(transparent)]
 | 
			
		||||
        pub struct LevelKeyboard(*const c_void);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        #[repr(C)]
 | 
			
		||||
        #[derive(PartialEq, Debug)]
 | 
			
		||||
        pub struct ButtonPlace {
 | 
			
		||||
@ -344,11 +271,6 @@ pub mod c {
 | 
			
		||||
 | 
			
		||||
        #[no_mangle]
 | 
			
		||||
        extern "C" {
 | 
			
		||||
            fn eek_get_outline_size(
 | 
			
		||||
                keyboard: *const LevelKeyboard,
 | 
			
		||||
                outline: u32
 | 
			
		||||
            ) -> Bounds;
 | 
			
		||||
 | 
			
		||||
            /// Checks if point falls within bounds,
 | 
			
		||||
            /// which are relative to origin and rotated by angle (I think)
 | 
			
		||||
            pub fn eek_are_bounds_inside (bounds: Bounds,
 | 
			
		||||
@ -357,15 +279,6 @@ pub mod c {
 | 
			
		||||
                angle: i32
 | 
			
		||||
            ) -> u32;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        fn squeek_buttons_get_outlines(
 | 
			
		||||
            buttons: &Vec<Box<Button>>,
 | 
			
		||||
            keyboard: *const LevelKeyboard,
 | 
			
		||||
        ) -> Vec<Bounds> {
 | 
			
		||||
            buttons.iter().map(|button| {
 | 
			
		||||
                unsafe { eek_get_outline_size(keyboard, button.oref.0) }
 | 
			
		||||
            }).collect()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// Places each button in order, starting from 0 on the left,
 | 
			
		||||
        /// keeping the spacing.
 | 
			
		||||
@ -375,17 +288,19 @@ pub mod c {
 | 
			
		||||
        /// Sets button and row sizes according to their contents.
 | 
			
		||||
        #[no_mangle]
 | 
			
		||||
        pub extern "C"
 | 
			
		||||
        fn squeek_view_place_contents(
 | 
			
		||||
            view: *mut ::layout::View,
 | 
			
		||||
            keyboard: *const LevelKeyboard, // source of outlines
 | 
			
		||||
        fn squeek_layout_place_contents(
 | 
			
		||||
            layout: *mut Layout,
 | 
			
		||||
        ) {
 | 
			
		||||
            let view = unsafe { &mut *view };
 | 
			
		||||
            
 | 
			
		||||
            let sizes: Vec<Vec<Bounds>> = view.rows.iter().map(|row|
 | 
			
		||||
                squeek_buttons_get_outlines(&row.buttons, keyboard)
 | 
			
		||||
            ).collect();
 | 
			
		||||
            let layout = unsafe { &mut *layout };
 | 
			
		||||
            for view in layout.views.values_mut() {
 | 
			
		||||
                let sizes: Vec<Vec<Bounds>> = view.rows.iter().map(|row| {
 | 
			
		||||
                    row.buttons.iter()
 | 
			
		||||
                        .map(|button| button.bounds.clone())
 | 
			
		||||
                        .collect()
 | 
			
		||||
                }).collect();
 | 
			
		||||
 | 
			
		||||
            view.place_buttons_with_sizes(sizes);
 | 
			
		||||
                view.place_buttons_with_sizes(sizes);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fn squeek_row_contains(row: &Row, needle: *const Button) -> bool {
 | 
			
		||||
@ -454,35 +369,36 @@ pub mod c {
 | 
			
		||||
        mod test {
 | 
			
		||||
            use super::*;
 | 
			
		||||
 | 
			
		||||
            use super::super::test::*;
 | 
			
		||||
 | 
			
		||||
            #[test]
 | 
			
		||||
            fn row_has_button() {
 | 
			
		||||
                let mut row = Row::new(0);
 | 
			
		||||
                let button = squeek_row_create_button(&mut row as *mut Row, 0, 0);
 | 
			
		||||
                assert_eq!(squeek_row_contains(&row, button), true);
 | 
			
		||||
                let shared_button = squeek_row_create_button_with_state(
 | 
			
		||||
                    &mut row as *mut Row,
 | 
			
		||||
                    button
 | 
			
		||||
                let state = make_state();
 | 
			
		||||
                let button = make_button_with_state(
 | 
			
		||||
                    "test".into(),
 | 
			
		||||
                    state.clone()
 | 
			
		||||
                );
 | 
			
		||||
                assert_eq!(squeek_row_contains(&row, shared_button), true);
 | 
			
		||||
                let button_ptr = button_as_raw(&button);
 | 
			
		||||
                let mut row = Row::new(0);
 | 
			
		||||
                row.buttons.push(button);
 | 
			
		||||
                assert_eq!(squeek_row_contains(&row, button_ptr), true);
 | 
			
		||||
                let shared_button = make_button_with_state(
 | 
			
		||||
                    "test2".into(),
 | 
			
		||||
                    state
 | 
			
		||||
                );
 | 
			
		||||
                let shared_button_ptr = button_as_raw(&shared_button);
 | 
			
		||||
                row.buttons.push(shared_button);
 | 
			
		||||
                assert_eq!(squeek_row_contains(&row, shared_button_ptr), true);
 | 
			
		||||
                let row = Row::new(0);
 | 
			
		||||
                assert_eq!(squeek_row_contains(&row, button), false);
 | 
			
		||||
                assert_eq!(squeek_row_contains(&row, button_ptr), false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            #[test]
 | 
			
		||||
            fn view_has_button() {
 | 
			
		||||
                let state = Rc::new(RefCell::new(::keyboard::KeyState {
 | 
			
		||||
                    pressed: false,
 | 
			
		||||
                    locked: false,
 | 
			
		||||
                    keycode: 0,
 | 
			
		||||
                    symbol: None,
 | 
			
		||||
                }));
 | 
			
		||||
                let state = make_state();
 | 
			
		||||
                let state_clone = ::keyboard::c::CKeyState::wrap(state.clone());
 | 
			
		||||
 | 
			
		||||
                let button = Box::new(Button {
 | 
			
		||||
                    oref: OutlineRef(0),
 | 
			
		||||
                    bounds: None,
 | 
			
		||||
                    state: state,
 | 
			
		||||
                });
 | 
			
		||||
                let button = make_button_with_state("1".into(), state);
 | 
			
		||||
                let button_ptr = button.as_ref() as *const Button;
 | 
			
		||||
                
 | 
			
		||||
                let row = Box::new(Row {
 | 
			
		||||
@ -535,18 +451,64 @@ pub mod c {
 | 
			
		||||
    #[cfg(test)]
 | 
			
		||||
    mod test {
 | 
			
		||||
        use super::*;
 | 
			
		||||
        
 | 
			
		||||
        use ::keyboard::c::CKeyState;
 | 
			
		||||
 | 
			
		||||
        pub fn make_state() -> Rc<RefCell<::keyboard::KeyState>> {
 | 
			
		||||
            Rc::new(RefCell::new(::keyboard::KeyState {
 | 
			
		||||
                pressed: false,
 | 
			
		||||
                locked: false,
 | 
			
		||||
                keycode: None,
 | 
			
		||||
                symbol: Symbol {
 | 
			
		||||
                    action: Action::SetLevel(0),
 | 
			
		||||
                }
 | 
			
		||||
            }))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub fn make_button_with_state(
 | 
			
		||||
            name: String,
 | 
			
		||||
            state: Rc<RefCell<::keyboard::KeyState>>,
 | 
			
		||||
        ) -> Box<Button> {
 | 
			
		||||
            Box::new(Button {
 | 
			
		||||
                name: CString::new(name.clone()).unwrap(),
 | 
			
		||||
                corner_radius: 0f64,
 | 
			
		||||
                bounds: c::Bounds {
 | 
			
		||||
                    x: 0f64, y: 0f64, width: 0f64, height: 0f64
 | 
			
		||||
                },
 | 
			
		||||
                label: Label::Text(CString::new(name).unwrap()),
 | 
			
		||||
                state: state,
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub fn button_as_raw(button: &Box<Button>) -> *const Button {
 | 
			
		||||
            button.as_ref() as *const Button
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        fn button_has_key() {
 | 
			
		||||
            let button = squeek_button_new(0, 0);
 | 
			
		||||
            let state = squeek_button_get_key(button);
 | 
			
		||||
            assert_eq!(squeek_button_has_key(button, state.clone()), 1);
 | 
			
		||||
            let other_button = squeek_button_new(0, 0);
 | 
			
		||||
            assert_eq!(squeek_button_has_key(other_button, state.clone()), 0);
 | 
			
		||||
            let other_state = ::keyboard::c::squeek_key_new(0);
 | 
			
		||||
            assert_eq!(squeek_button_has_key(button, other_state), 0);
 | 
			
		||||
            let shared_button = squeek_button_new_with_state(button);
 | 
			
		||||
            assert_eq!(squeek_button_has_key(shared_button, state), 1);
 | 
			
		||||
            let state = make_state();
 | 
			
		||||
            let button = make_button_with_state("1".into(), state.clone());
 | 
			
		||||
            assert_eq!(
 | 
			
		||||
                squeek_button_has_key(
 | 
			
		||||
                    button_as_raw(&button),
 | 
			
		||||
                    CKeyState::wrap(state.clone())
 | 
			
		||||
                ),
 | 
			
		||||
                1
 | 
			
		||||
            );
 | 
			
		||||
            let other_state = make_state();
 | 
			
		||||
            let other_button = make_button_with_state("1".into(), other_state);
 | 
			
		||||
            assert_eq!(
 | 
			
		||||
                squeek_button_has_key(
 | 
			
		||||
                    button_as_raw(&other_button),
 | 
			
		||||
                    CKeyState::wrap(state.clone())
 | 
			
		||||
                ),
 | 
			
		||||
                0
 | 
			
		||||
            );
 | 
			
		||||
            let orphan_state = CKeyState::wrap(make_state());
 | 
			
		||||
            assert_eq!(
 | 
			
		||||
                squeek_button_has_key(button_as_raw(&button), orphan_state),
 | 
			
		||||
                0
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -557,13 +519,25 @@ pub struct Size {
 | 
			
		||||
    pub height: f64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub enum Label {
 | 
			
		||||
    /// Text used to display the symbol
 | 
			
		||||
    Text(CString),
 | 
			
		||||
    /// Icon name used to render the symbol
 | 
			
		||||
    IconName(CString),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// The graphical representation of a button
 | 
			
		||||
#[derive(Clone, Debug)]
 | 
			
		||||
pub struct Button {
 | 
			
		||||
    oref: c::OutlineRef,
 | 
			
		||||
    /// TODO: abolish Option, buttons should be created with bounds fully formed
 | 
			
		||||
    /// ID string, e.g. for CSS 
 | 
			
		||||
    pub name: CString,
 | 
			
		||||
    /// Label to display to the user
 | 
			
		||||
    pub label: Label,
 | 
			
		||||
    pub corner_radius: f64,
 | 
			
		||||
    /// TODO: position the buttons before they get initial bounds
 | 
			
		||||
    /// Position relative to some origin (i.e. parent/row)
 | 
			
		||||
    bounds: Option<c::Bounds>,
 | 
			
		||||
    pub bounds: c::Bounds,
 | 
			
		||||
    /// current state, shared with other buttons
 | 
			
		||||
    pub state: Rc<RefCell<KeyState>>,
 | 
			
		||||
}
 | 
			
		||||
@ -574,11 +548,11 @@ const ROW_SPACING: f64 = 7.0;
 | 
			
		||||
 | 
			
		||||
/// The graphical representation of a row of buttons
 | 
			
		||||
pub struct Row {
 | 
			
		||||
    buttons: Vec<Box<Button>>,
 | 
			
		||||
    pub buttons: Vec<Box<Button>>,
 | 
			
		||||
    /// Angle is not really used anywhere...
 | 
			
		||||
    angle: i32,
 | 
			
		||||
    pub angle: i32,
 | 
			
		||||
    /// Position relative to some origin (i.e. parent/view origin)
 | 
			
		||||
    bounds: Option<c::Bounds>,
 | 
			
		||||
    pub bounds: Option<c::Bounds>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Row {
 | 
			
		||||
@ -639,9 +613,7 @@ impl Row {
 | 
			
		||||
        };
 | 
			
		||||
        let angle = self.angle;
 | 
			
		||||
        self.buttons.iter_mut().find(|button| {
 | 
			
		||||
            let bounds = button.bounds
 | 
			
		||||
                .as_ref().expect("Missing bounds on button")
 | 
			
		||||
                .clone();
 | 
			
		||||
            let bounds = button.bounds.clone();
 | 
			
		||||
            let point = point.clone();
 | 
			
		||||
            let origin = origin.clone();
 | 
			
		||||
            procedures::is_point_inside(bounds, point, origin, angle)
 | 
			
		||||
@ -651,8 +623,8 @@ impl Row {
 | 
			
		||||
 | 
			
		||||
pub struct View {
 | 
			
		||||
    /// Position relative to keyboard origin
 | 
			
		||||
    bounds: c::Bounds,
 | 
			
		||||
    rows: Vec<Box<Row>>,
 | 
			
		||||
    pub bounds: c::Bounds,
 | 
			
		||||
    pub rows: Vec<Box<Row>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl View {
 | 
			
		||||
@ -704,7 +676,7 @@ impl View {
 | 
			
		||||
            for (mut button, button_position)
 | 
			
		||||
                in row.buttons.iter_mut()
 | 
			
		||||
                    .zip(button_positions) {
 | 
			
		||||
                button.bounds = Some(button_position);
 | 
			
		||||
                button.bounds = button_position;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -727,6 +699,13 @@ impl View {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct Layout {
 | 
			
		||||
    pub current_view: String,
 | 
			
		||||
    pub views: HashMap<String, Box<View>>,
 | 
			
		||||
    // TODO: move to ::keyboard::Keyboard
 | 
			
		||||
    pub keymap_str: CString,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
mod procedures {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,16 @@
 | 
			
		||||
#[macro_use]
 | 
			
		||||
extern crate maplit;
 | 
			
		||||
extern crate serde;
 | 
			
		||||
 | 
			
		||||
#[macro_use]
 | 
			
		||||
mod bitflags;
 | 
			
		||||
 | 
			
		||||
mod data;
 | 
			
		||||
mod float_ord;
 | 
			
		||||
mod imservice;
 | 
			
		||||
mod keyboard;
 | 
			
		||||
mod layout;
 | 
			
		||||
mod resources;
 | 
			
		||||
mod symbol;
 | 
			
		||||
mod util;
 | 
			
		||||
mod xdg;
 | 
			
		||||
 | 
			
		||||
@ -23,7 +23,6 @@ sources = [
 | 
			
		||||
  '../eek/eek-keysym.c',
 | 
			
		||||
  '../eek/eek-layout.c',
 | 
			
		||||
  '../eek/eek-renderer.c',
 | 
			
		||||
  '../eek/eek-section.c',
 | 
			
		||||
  '../eek/eek-types.c',
 | 
			
		||||
  '../eek/eek-xml-layout.c',
 | 
			
		||||
  '../eek/layersurface.c',
 | 
			
		||||
@ -65,6 +64,7 @@ rslibs = custom_target(
 | 
			
		||||
    build_always_stale: true,
 | 
			
		||||
    output: ['librs.a'],
 | 
			
		||||
    install: false,
 | 
			
		||||
    console: true,
 | 
			
		||||
    command: [cargo_script, '@CURRENT_SOURCE_DIR@', '@OUTPUT@', 'build']
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										22
									
								
								src/resources.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/resources.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
			
		||||
/*! Statically linked resources.
 | 
			
		||||
 * This could be done using GResource, but that would need additional work.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const KEYBOARDS: &[(*const str, *const str)] = &[
 | 
			
		||||
    ("us", include_str!("../data/keyboards/us.yaml"))
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
pub fn get_keyboard(needle: &str) -> Option<&'static str> {
 | 
			
		||||
    // Need to dereference in unsafe code
 | 
			
		||||
    // comparing *const str to &str will compare pointers
 | 
			
		||||
    KEYBOARDS.iter()
 | 
			
		||||
        .find(|(name, _)| {
 | 
			
		||||
            let name: *const str = *name;
 | 
			
		||||
            (unsafe { &*name }) == needle
 | 
			
		||||
        })
 | 
			
		||||
        .map(|(_, value)| {
 | 
			
		||||
            let value: *const str = *value;
 | 
			
		||||
            unsafe { &*value }
 | 
			
		||||
        })
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								src/symbol.h
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								src/symbol.h
									
									
									
									
									
								
							@ -1,24 +0,0 @@
 | 
			
		||||
#ifndef __SYMBOL_H
 | 
			
		||||
#define __SYMBOL_H
 | 
			
		||||
 | 
			
		||||
#include "stdbool.h"
 | 
			
		||||
#include "inttypes.h"
 | 
			
		||||
// Defined in Rust
 | 
			
		||||
 | 
			
		||||
struct squeek_symbol;
 | 
			
		||||
struct squeek_symbols;
 | 
			
		||||
 | 
			
		||||
void squeek_symbols_add(struct squeek_symbols*,
 | 
			
		||||
                        const char *element_name,
 | 
			
		||||
                        const char *text, uint32_t keyval,
 | 
			
		||||
                        const char *label, const char *icon,
 | 
			
		||||
                        const char *tooltip);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const char *squeek_symbol_get_name(struct squeek_symbol* symbol);
 | 
			
		||||
const char *squeek_symbol_get_label(struct squeek_symbol* symbol);
 | 
			
		||||
const char *squeek_symbol_get_icon_name(struct squeek_symbol* symbol);
 | 
			
		||||
uint32_t squeek_symbol_get_modifier_mask(struct squeek_symbol* symbol);
 | 
			
		||||
 | 
			
		||||
void squeek_symbol_print(struct squeek_symbol* symbol);
 | 
			
		||||
#endif
 | 
			
		||||
@ -1,78 +1,7 @@
 | 
			
		||||
/*! The symbol object, defining actions that the key can do when activated */
 | 
			
		||||
 | 
			
		||||
use std::ffi::CString;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Gathers stuff defined in C or called by C
 | 
			
		||||
pub mod c {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    
 | 
			
		||||
    use std::ffi::CStr;
 | 
			
		||||
    use std::os::raw::c_char;
 | 
			
		||||
    use std::ptr;
 | 
			
		||||
    
 | 
			
		||||
    // The following defined in C
 | 
			
		||||
    
 | 
			
		||||
    // Legacy; Will never be used in Rust as a bit field
 | 
			
		||||
    enum ModifierMask {
 | 
			
		||||
        Nothing = 0,
 | 
			
		||||
        Shift = 1,
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // The following defined in Rust.
 | 
			
		||||
    
 | 
			
		||||
    // TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers
 | 
			
		||||
    // Symbols are owned by Rust and will move towards no C manipulation, so it may make sense not to wrap them
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_symbol_get_name(symbol: *const Symbol) -> *const c_char {
 | 
			
		||||
        let symbol = unsafe { &*symbol };
 | 
			
		||||
        match &symbol.action {
 | 
			
		||||
            Action::Submit { text: Some(text), .. } => text.as_ptr(),
 | 
			
		||||
            _ => ptr::null(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_symbol_get_label(symbol: *const Symbol) -> *const c_char {
 | 
			
		||||
        let symbol = unsafe { &*symbol };
 | 
			
		||||
        match &symbol.label {
 | 
			
		||||
            Label::Text(text) => text.as_ptr(),
 | 
			
		||||
            // returning static strings to C is a bit cumbersome
 | 
			
		||||
            Label::IconName(_) => unsafe {
 | 
			
		||||
                CStr::from_bytes_with_nul_unchecked(b"icon\0")
 | 
			
		||||
            }.as_ptr(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_symbol_get_icon_name(symbol: *const Symbol) -> *const c_char {
 | 
			
		||||
        let symbol = unsafe { &*symbol };
 | 
			
		||||
        match &symbol.label {
 | 
			
		||||
            Label::Text(_) => ptr::null(),
 | 
			
		||||
            Label::IconName(name) => name.as_ptr(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Legacy; throw away
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_symbol_get_modifier_mask(symbol: *const Symbol) -> u32 {
 | 
			
		||||
        let symbol = unsafe { &*symbol };
 | 
			
		||||
        (match &symbol.action {
 | 
			
		||||
            Action::SetLevel(1) => ModifierMask::Shift,
 | 
			
		||||
            _ => ModifierMask::Nothing,
 | 
			
		||||
        }) as u32
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    #[no_mangle]
 | 
			
		||||
    pub extern "C"
 | 
			
		||||
    fn squeek_symbol_print(symbol: *const Symbol) {
 | 
			
		||||
        let symbol = unsafe { &*symbol };
 | 
			
		||||
        println!("{:?}", symbol);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Just defines some int->identifier mappings for convenience
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub enum KeySym {
 | 
			
		||||
@ -92,14 +21,6 @@ impl KeySym {
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct XKeySym(pub u32);
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub enum Label {
 | 
			
		||||
    /// Text used to display the symbol
 | 
			
		||||
    Text(CString),
 | 
			
		||||
    /// Icon name used to render the symbol
 | 
			
		||||
    IconName(CString),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Use to switch layouts
 | 
			
		||||
type Level = u8;
 | 
			
		||||
 | 
			
		||||
@ -119,7 +40,7 @@ pub enum Action {
 | 
			
		||||
    SetModifier(Modifier),
 | 
			
		||||
    /// Submit some text
 | 
			
		||||
    Submit {
 | 
			
		||||
        /// orig: Canonical name of the symbol
 | 
			
		||||
        /// Text to submit with input-method
 | 
			
		||||
        text: Option<CString>,
 | 
			
		||||
        /// The key events this symbol submits when submitting text is not possible
 | 
			
		||||
        keys: Vec<XKeySym>,
 | 
			
		||||
@ -131,8 +52,4 @@ pub enum Action {
 | 
			
		||||
pub struct Symbol {
 | 
			
		||||
    /// The action that this key performs
 | 
			
		||||
    pub action: Action,
 | 
			
		||||
    /// Label to display to the user
 | 
			
		||||
    pub label: Label,
 | 
			
		||||
    // FIXME: is it used?
 | 
			
		||||
    pub tooltip: Option<CString>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,6 @@ pub mod c {
 | 
			
		||||
    use std::os::raw::c_char;
 | 
			
		||||
    use std::str::Utf8Error;
 | 
			
		||||
    
 | 
			
		||||
    #[allow(dead_code)]
 | 
			
		||||
    pub fn as_str(s: &*const c_char) -> Result<Option<&str>, Utf8Error> {
 | 
			
		||||
        if s.is_null() {
 | 
			
		||||
            Ok(None)
 | 
			
		||||
@ -13,7 +12,7 @@ pub mod c {
 | 
			
		||||
                .map(Some)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    pub fn as_cstr(s: &*const c_char) -> Option<&CStr> {
 | 
			
		||||
        if s.is_null() {
 | 
			
		||||
            None
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										63
									
								
								src/xdg.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/xdg.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
			
		||||
/*! XDG directory handling. */
 | 
			
		||||
 | 
			
		||||
/* Based on dirs-sys https://github.com/soc/dirs-sys-rs
 | 
			
		||||
 * by "Simon Ochsenreither <simon@ochsenreither.de>",
 | 
			
		||||
 * Licensed under either of
 | 
			
		||||
 | 
			
		||||
    Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
 | 
			
		||||
    MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
 | 
			
		||||
 | 
			
		||||
at your option.
 | 
			
		||||
 * 
 | 
			
		||||
 * Based on dirs https://github.com/soc/dirs-rs
 | 
			
		||||
 * by "Simon Ochsenreither <simon@ochsenreither.de>",
 | 
			
		||||
 * Licensed under either of
 | 
			
		||||
 | 
			
		||||
    Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
 | 
			
		||||
    MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
 | 
			
		||||
 | 
			
		||||
at your option.
 | 
			
		||||
 * 
 | 
			
		||||
 * Based on xdg https://github.com/whitequark/rust-xdg
 | 
			
		||||
 * by "Ben Longbons <b.r.longbons@gmail.com>",
 | 
			
		||||
 *    "whitequark <whitequark@whitequark.org>",
 | 
			
		||||
rust-xdg is distributed under the terms of both the MIT license and the Apache License (Version 2.0).
 | 
			
		||||
 * 
 | 
			
		||||
 * The above crates were used to get
 | 
			
		||||
 * a version without all the excessive dependencies.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use std::env;
 | 
			
		||||
use std::ffi::OsString;
 | 
			
		||||
use std::path::{ Path, PathBuf };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
fn is_absolute_path(path: OsString) -> Option<PathBuf> {
 | 
			
		||||
    let path = PathBuf::from(path);
 | 
			
		||||
    if path.is_absolute() {
 | 
			
		||||
        Some(path)
 | 
			
		||||
    } else {
 | 
			
		||||
        None
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn home_dir() -> Option<PathBuf> {
 | 
			
		||||
    return env::var_os("HOME")
 | 
			
		||||
        .and_then(|h| if h.is_empty() { None } else { Some(h) })
 | 
			
		||||
        .map(PathBuf::from);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn data_dir() -> Option<PathBuf> {
 | 
			
		||||
    env::var_os("XDG_DATA_HOME")
 | 
			
		||||
        .and_then(is_absolute_path)
 | 
			
		||||
        .or_else(|| home_dir().map(|h| h.join(".local/share")))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Returns the path to the directory within the data dir
 | 
			
		||||
pub fn data_path<P>(path: P) -> Option<PathBuf>
 | 
			
		||||
    where P: AsRef<Path>
 | 
			
		||||
{
 | 
			
		||||
    data_dir().map(|dir| {
 | 
			
		||||
        dir.join(path.as_ref())
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user