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