Merge branch 'errors' into 'master'
Better layout checking Closes #131 See merge request Librem5/squeekboard!255
This commit is contained in:
15
cargo.sh
15
cargo.sh
@ -11,14 +11,21 @@ SOURCE_DIR="$(dirname "$SCRIPT_PATH")"
|
|||||||
|
|
||||||
CARGO_TARGET_DIR="$(pwd)"
|
CARGO_TARGET_DIR="$(pwd)"
|
||||||
export CARGO_TARGET_DIR
|
export CARGO_TARGET_DIR
|
||||||
if [ -n "${1}" ]; then
|
if [ "${1}" = "--rename" ]; then
|
||||||
OUT_PATH="$(realpath "$1")"
|
shift
|
||||||
|
FILENAME="${1}"
|
||||||
|
shift
|
||||||
|
OUT_PATH="$(realpath "${1}")"
|
||||||
|
elif [ "${1}" = "--output" ]; then
|
||||||
|
shift
|
||||||
|
OUT_PATH="$(realpath "${1}")"
|
||||||
|
FILENAME="$(basename "${OUT_PATH}")"
|
||||||
fi
|
fi
|
||||||
|
shift
|
||||||
|
|
||||||
cd "$SOURCE_DIR"
|
cd "$SOURCE_DIR"
|
||||||
shift
|
|
||||||
cargo "$@"
|
cargo "$@"
|
||||||
|
|
||||||
if [ -n "${OUT_PATH}" ]; then
|
if [ -n "${OUT_PATH}" ]; then
|
||||||
cp "${CARGO_TARGET_DIR}"/debug/librs.a "${OUT_PATH}"
|
cp -a "${CARGO_TARGET_DIR}"/debug/"${FILENAME}" "${OUT_PATH}"
|
||||||
fi
|
fi
|
||||||
|
|||||||
10
debian/control
vendored
10
debian/control
vendored
@ -40,3 +40,13 @@ Depends:
|
|||||||
${misc:Depends}
|
${misc:Depends}
|
||||||
Description: On-screen keyboard for Wayland
|
Description: On-screen keyboard for Wayland
|
||||||
Virtual keyboard supporting Wayland, built primarily for the Librem 5 phone.
|
Virtual keyboard supporting Wayland, built primarily for the Librem 5 phone.
|
||||||
|
|
||||||
|
Package: squeekboard-devel
|
||||||
|
Architecture: linux-any
|
||||||
|
Depends:
|
||||||
|
${shlibs:Depends}
|
||||||
|
${misc:Depends}
|
||||||
|
Description: Resources for making Squeekboard layouts
|
||||||
|
Tools for creating Squeekboard layouts:
|
||||||
|
.
|
||||||
|
* squeekboard-test-layout
|
||||||
|
|||||||
1
debian/squeekboard-devel.install
vendored
Normal file
1
debian/squeekboard-devel.install
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
usr/bin/squeekboard-test-layout /usr/bin
|
||||||
2
debian/squeekboard.install
vendored
Normal file
2
debian/squeekboard.install
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
usr/bin/squeekboard-real /usr/bin
|
||||||
|
usr/bin/squeekboard /usr/bin
|
||||||
@ -1,52 +1,10 @@
|
|||||||
extern crate rs;
|
extern crate rs;
|
||||||
extern crate xkbcommon;
|
|
||||||
|
|
||||||
|
use rs::tests::check_builtin_layout;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use rs::data::{ Layout, LoadError };
|
|
||||||
|
|
||||||
use xkbcommon::xkb;
|
|
||||||
|
|
||||||
|
|
||||||
fn check_layout(name: &str) {
|
|
||||||
let layout = Layout::from_resource(name)
|
|
||||||
.and_then(|layout| layout.build().map_err(LoadError::BadKeyMap))
|
|
||||||
.expect("layout broken");
|
|
||||||
|
|
||||||
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
|
||||||
|
|
||||||
let keymap_str = layout.keymap_str
|
|
||||||
.clone()
|
|
||||||
.into_string().expect("Failed to decode keymap string");
|
|
||||||
|
|
||||||
let keymap = xkb::Keymap::new_from_string(
|
|
||||||
&context,
|
|
||||||
keymap_str.clone(),
|
|
||||||
xkb::KEYMAP_FORMAT_TEXT_V1,
|
|
||||||
xkb::KEYMAP_COMPILE_NO_FLAGS,
|
|
||||||
).expect("Failed to create keymap");
|
|
||||||
|
|
||||||
let state = xkb::State::new(&keymap);
|
|
||||||
|
|
||||||
// "Press" each button with keysyms
|
|
||||||
for view in layout.views.values() {
|
|
||||||
for row in &view.rows {
|
|
||||||
for button in &row.buttons {
|
|
||||||
let keystate = button.state.borrow();
|
|
||||||
for keycode in &keystate.keycodes {
|
|
||||||
match state.key_get_one_sym(*keycode) {
|
|
||||||
xkb::KEY_NoSymbol => {
|
|
||||||
eprintln!("{}", keymap_str);
|
|
||||||
panic!("Keysym {} on key {:?} can't be resolved", keycode, button.name);
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> () {
|
fn main() -> () {
|
||||||
check_layout(env::args().nth(1).expect("No argument given").as_str());
|
check_builtin_layout(
|
||||||
|
env::args().nth(1).expect("No argument given").as_str()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/bin/test_layout.rs
Normal file
8
src/bin/test_layout.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
extern crate rs;
|
||||||
|
|
||||||
|
use rs::tests::check_layout_file;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
fn main() -> () {
|
||||||
|
check_layout_file(env::args().nth(1).expect("No argument given").as_str());
|
||||||
|
}
|
||||||
148
src/data.rs
148
src/data.rs
@ -26,8 +26,8 @@ use ::xdg;
|
|||||||
// traits, derives
|
// traits, derives
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use util::WarningHandler;
|
||||||
|
|
||||||
|
|
||||||
/// Gathers stuff defined in C or called by C
|
/// Gathers stuff defined in C or called by C
|
||||||
@ -151,21 +151,30 @@ fn list_layout_sources(
|
|||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct PrintWarnings;
|
||||||
|
|
||||||
|
impl WarningHandler for PrintWarnings {
|
||||||
|
fn handle(&mut self, warning: &str) {
|
||||||
|
println!("{}", warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn load_layout_data(source: DataSource)
|
fn load_layout_data(source: DataSource)
|
||||||
-> Result<::layout::LayoutData, LoadError>
|
-> Result<::layout::LayoutData, LoadError>
|
||||||
{
|
{
|
||||||
|
let handler = PrintWarnings{};
|
||||||
match source {
|
match source {
|
||||||
DataSource::File(path) => {
|
DataSource::File(path) => {
|
||||||
Layout::from_file(path.clone())
|
Layout::from_file(path.clone())
|
||||||
.map_err(LoadError::BadData)
|
.map_err(LoadError::BadData)
|
||||||
.and_then(|layout|
|
.and_then(|layout|
|
||||||
layout.build().map_err(LoadError::BadKeyMap)
|
layout.build(handler).0.map_err(LoadError::BadKeyMap)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
DataSource::Resource(name) => {
|
DataSource::Resource(name) => {
|
||||||
Layout::from_resource(&name)
|
Layout::from_resource(&name)
|
||||||
.and_then(|layout|
|
.and_then(|layout|
|
||||||
layout.build().map_err(LoadError::BadKeyMap)
|
layout.build(handler).0.map_err(LoadError::BadKeyMap)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -296,7 +305,7 @@ impl Layout {
|
|||||||
.map_err(LoadError::BadResource)
|
.map_err(LoadError::BadResource)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_file(path: PathBuf) -> Result<Layout, Error> {
|
pub fn from_file(path: PathBuf) -> Result<Layout, Error> {
|
||||||
let infile = BufReader::new(
|
let infile = BufReader::new(
|
||||||
fs::OpenOptions::new()
|
fs::OpenOptions::new()
|
||||||
.read(true)
|
.read(true)
|
||||||
@ -305,8 +314,8 @@ impl Layout {
|
|||||||
serde_yaml::from_reader(infile).map_err(Error::Yaml)
|
serde_yaml::from_reader(infile).map_err(Error::Yaml)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self)
|
pub fn build<H: WarningHandler>(self, mut warning_handler: H)
|
||||||
-> Result<::layout::LayoutData, FormattingError>
|
-> (Result<::layout::LayoutData, FormattingError>, H)
|
||||||
{
|
{
|
||||||
let button_names = self.views.values()
|
let button_names = self.views.values()
|
||||||
.flat_map(|rows| {
|
.flat_map(|rows| {
|
||||||
@ -323,7 +332,8 @@ impl Layout {
|
|||||||
create_action(
|
create_action(
|
||||||
&self.buttons,
|
&self.buttons,
|
||||||
name,
|
name,
|
||||||
self.views.keys().collect()
|
self.views.keys().collect(),
|
||||||
|
&mut warning_handler,
|
||||||
)
|
)
|
||||||
)}).collect();
|
)}).collect();
|
||||||
|
|
||||||
@ -368,13 +378,15 @@ impl Layout {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let button_states
|
let button_states = HashMap::<String, KeyState>::from_iter(
|
||||||
= HashMap::<String, KeyState>::from_iter(
|
button_states
|
||||||
button_states
|
);
|
||||||
);
|
|
||||||
|
|
||||||
// TODO: generate from symbols
|
// TODO: generate from symbols
|
||||||
let keymap_str = generate_keymap(&button_states)?;
|
let keymap_str = match generate_keymap(&button_states) {
|
||||||
|
Err(e) => { return (Err(e), warning_handler) },
|
||||||
|
Ok(v) => v,
|
||||||
|
};
|
||||||
|
|
||||||
let button_states_cache = hash_map_map(
|
let button_states_cache = hash_map_map(
|
||||||
button_states,
|
button_states,
|
||||||
@ -405,7 +417,8 @@ impl Layout {
|
|||||||
name,
|
name,
|
||||||
button_states_cache.get(name.into())
|
button_states_cache.get(name.into())
|
||||||
.expect("Button state not created")
|
.expect("Button state not created")
|
||||||
.clone()
|
.clone(),
|
||||||
|
&mut warning_handler,
|
||||||
))
|
))
|
||||||
}).collect(),
|
}).collect(),
|
||||||
})
|
})
|
||||||
@ -414,42 +427,29 @@ impl Layout {
|
|||||||
)})
|
)})
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(::layout::LayoutData {
|
(
|
||||||
views: views,
|
Ok(::layout::LayoutData {
|
||||||
keymap_str: {
|
views: views,
|
||||||
CString::new(keymap_str)
|
keymap_str: {
|
||||||
.expect("Invalid keymap string generated")
|
CString::new(keymap_str)
|
||||||
},
|
.expect("Invalid keymap string generated")
|
||||||
})
|
},
|
||||||
|
}),
|
||||||
|
warning_handler,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_action(
|
fn create_action<H: WarningHandler>(
|
||||||
button_info: &HashMap<String, ButtonMeta>,
|
button_info: &HashMap<String, ButtonMeta>,
|
||||||
name: &str,
|
name: &str,
|
||||||
view_names: Vec<&String>,
|
view_names: Vec<&String>,
|
||||||
|
warning_handler: &mut H,
|
||||||
) -> ::action::Action {
|
) -> ::action::Action {
|
||||||
let default_meta = ButtonMeta::default();
|
let default_meta = ButtonMeta::default();
|
||||||
let symbol_meta = button_info.get(name)
|
let symbol_meta = button_info.get(name)
|
||||||
.unwrap_or(&default_meta);
|
.unwrap_or(&default_meta);
|
||||||
|
|
||||||
fn filter_view_name(
|
|
||||||
button_name: &str,
|
|
||||||
view_name: String,
|
|
||||||
view_names: &Vec<&String>
|
|
||||||
) -> String {
|
|
||||||
if view_names.contains(&&view_name) {
|
|
||||||
view_name
|
|
||||||
} else {
|
|
||||||
eprintln!(
|
|
||||||
"Button {} switches to missing view {}",
|
|
||||||
button_name,
|
|
||||||
view_name
|
|
||||||
);
|
|
||||||
"base".into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn keysym_valid(name: &str) -> bool {
|
fn keysym_valid(name: &str) -> bool {
|
||||||
xkb::keysym_from_name(name, xkb::KEYSYM_NO_FLAGS) != xkb::KEY_NoSymbol
|
xkb::keysym_from_name(name, xkb::KEYSYM_NO_FLAGS) != xkb::KEY_NoSymbol
|
||||||
}
|
}
|
||||||
@ -463,7 +463,10 @@ fn create_action(
|
|||||||
Some(keysym) => vec!(match keysym_valid(keysym.as_str()) {
|
Some(keysym) => vec!(match keysym_valid(keysym.as_str()) {
|
||||||
true => keysym.clone(),
|
true => keysym.clone(),
|
||||||
false => {
|
false => {
|
||||||
eprintln!("Keysym name invalid: {}", keysym);
|
warning_handler.handle(&format!(
|
||||||
|
"Keysym name invalid: {}",
|
||||||
|
keysym,
|
||||||
|
));
|
||||||
"space".into() // placeholder
|
"space".into() // placeholder
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@ -479,7 +482,10 @@ fn create_action(
|
|||||||
if name.chars().count() == 0 {
|
if name.chars().count() == 0 {
|
||||||
// A name read from yaml with no valid Unicode.
|
// A name read from yaml with no valid Unicode.
|
||||||
// Highly improbable, but let's be safe.
|
// Highly improbable, but let's be safe.
|
||||||
eprintln!("Key {} doesn't have any characters", name);
|
warning_handler.handle(&format!(
|
||||||
|
"Key {} doesn't have any characters",
|
||||||
|
name,
|
||||||
|
));
|
||||||
vec!("space".into()) // placeholder
|
vec!("space".into()) // placeholder
|
||||||
} else {
|
} else {
|
||||||
name.chars().map(|codepoint| {
|
name.chars().map(|codepoint| {
|
||||||
@ -495,18 +501,44 @@ fn create_action(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn filter_view_name<H: WarningHandler>(
|
||||||
|
button_name: &str,
|
||||||
|
view_name: String,
|
||||||
|
view_names: &Vec<&String>,
|
||||||
|
warning_handler: &mut H,
|
||||||
|
) -> String {
|
||||||
|
if view_names.contains(&&view_name) {
|
||||||
|
view_name
|
||||||
|
} else {
|
||||||
|
warning_handler.handle(&format!("Button {} switches to missing view {}",
|
||||||
|
button_name,
|
||||||
|
view_name,
|
||||||
|
));
|
||||||
|
"base".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match &symbol_meta.action {
|
match &symbol_meta.action {
|
||||||
Some(Action::SetView(view_name)) => ::action::Action::SetLevel(
|
Some(Action::SetView(view_name)) => ::action::Action::SetLevel(
|
||||||
filter_view_name(name, view_name.clone(), &view_names)
|
filter_view_name(
|
||||||
|
name, view_name.clone(), &view_names,
|
||||||
|
warning_handler,
|
||||||
|
)
|
||||||
),
|
),
|
||||||
Some(Action::Locking {
|
Some(Action::Locking {
|
||||||
lock_view, unlock_view
|
lock_view, unlock_view
|
||||||
}) => ::action::Action::LockLevel {
|
}) => ::action::Action::LockLevel {
|
||||||
lock: filter_view_name(name, lock_view.clone(), &view_names),
|
lock: filter_view_name(
|
||||||
|
name,
|
||||||
|
lock_view.clone(),
|
||||||
|
&view_names,
|
||||||
|
warning_handler,
|
||||||
|
),
|
||||||
unlock: filter_view_name(
|
unlock: filter_view_name(
|
||||||
name,
|
name,
|
||||||
unlock_view.clone(),
|
unlock_view.clone(),
|
||||||
&view_names
|
&view_names,
|
||||||
|
warning_handler,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
Some(Action::ShowPrefs) => ::action::Action::ShowPreferences,
|
Some(Action::ShowPrefs) => ::action::Action::ShowPreferences,
|
||||||
@ -519,11 +551,12 @@ fn create_action(
|
|||||||
|
|
||||||
/// TODO: Since this will receive user-provided data,
|
/// TODO: Since this will receive user-provided data,
|
||||||
/// all .expect() on them should be turned into soft fails
|
/// all .expect() on them should be turned into soft fails
|
||||||
fn create_button(
|
fn create_button<H: WarningHandler>(
|
||||||
button_info: &HashMap<String, ButtonMeta>,
|
button_info: &HashMap<String, ButtonMeta>,
|
||||||
outlines: &HashMap<String, Outline>,
|
outlines: &HashMap<String, Outline>,
|
||||||
name: &str,
|
name: &str,
|
||||||
state: Rc<RefCell<KeyState>>,
|
state: Rc<RefCell<KeyState>>,
|
||||||
|
warning_handler: &mut H,
|
||||||
) -> ::layout::Button {
|
) -> ::layout::Button {
|
||||||
let cname = CString::new(name.clone())
|
let cname = CString::new(name.clone())
|
||||||
.expect("Bad name");
|
.expect("Bad name");
|
||||||
@ -548,7 +581,7 @@ fn create_button(
|
|||||||
if outlines.contains_key(outline) {
|
if outlines.contains_key(outline) {
|
||||||
outline.clone()
|
outline.clone()
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Outline named {} does not exist! Using default for button {}", outline, name);
|
warning_handler.handle(&format!("Outline named {} does not exist! Using default for button {}", outline, name));
|
||||||
"default".into()
|
"default".into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -558,7 +591,9 @@ fn create_button(
|
|||||||
let outline = outlines.get(&outline_name)
|
let outline = outlines.get(&outline_name)
|
||||||
.map(|outline| (*outline).clone())
|
.map(|outline| (*outline).clone())
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
eprintln!("No default outline defied Using 1x1!");
|
warning_handler.handle(
|
||||||
|
&format!("No default outline defined! Using 1x1!")
|
||||||
|
);
|
||||||
Outline {
|
Outline {
|
||||||
bounds: Bounds { x: 0f64, y: 0f64, width: 1f64, height: 1f64 },
|
bounds: Bounds { x: 0f64, y: 0f64, width: 1f64, height: 1f64 },
|
||||||
}
|
}
|
||||||
@ -585,6 +620,14 @@ mod tests {
|
|||||||
|
|
||||||
use std::error::Error as ErrorTrait;
|
use std::error::Error as ErrorTrait;
|
||||||
|
|
||||||
|
struct PanicWarn;
|
||||||
|
|
||||||
|
impl WarningHandler for PanicWarn {
|
||||||
|
fn handle(&mut self, warning: &str) {
|
||||||
|
panic!("{}", warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_path() {
|
fn test_parse_path() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -656,7 +699,7 @@ mod tests {
|
|||||||
fn test_layout_punctuation() {
|
fn test_layout_punctuation() {
|
||||||
let out = Layout::from_file(PathBuf::from("tests/layout_key1.yaml"))
|
let out = Layout::from_file(PathBuf::from("tests/layout_key1.yaml"))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.build()
|
.build(PanicWarn).0
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out.views["base"]
|
out.views["base"]
|
||||||
@ -671,7 +714,7 @@ mod tests {
|
|||||||
fn test_layout_unicode() {
|
fn test_layout_unicode() {
|
||||||
let out = Layout::from_file(PathBuf::from("tests/layout_key2.yaml"))
|
let out = Layout::from_file(PathBuf::from("tests/layout_key2.yaml"))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.build()
|
.build(PanicWarn).0
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out.views["base"]
|
out.views["base"]
|
||||||
@ -687,7 +730,7 @@ mod tests {
|
|||||||
fn test_layout_unicode_multi() {
|
fn test_layout_unicode_multi() {
|
||||||
let out = Layout::from_file(PathBuf::from("tests/layout_key3.yaml"))
|
let out = Layout::from_file(PathBuf::from("tests/layout_key3.yaml"))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.build()
|
.build(PanicWarn).0
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out.views["base"]
|
out.views["base"]
|
||||||
@ -702,7 +745,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn parsing_fallback() {
|
fn parsing_fallback() {
|
||||||
assert!(Layout::from_resource(FALLBACK_LAYOUT_NAME)
|
assert!(Layout::from_resource(FALLBACK_LAYOUT_NAME)
|
||||||
.and_then(|layout| layout.build().map_err(LoadError::BadKeyMap))
|
.map(|layout| layout.build(PanicWarn).0.unwrap())
|
||||||
.is_ok()
|
.is_ok()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -748,12 +791,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
".",
|
".",
|
||||||
Vec::new()
|
Vec::new(),
|
||||||
|
&mut PanicWarn,
|
||||||
),
|
),
|
||||||
::action::Action::Submit {
|
::action::Action::Submit {
|
||||||
text: None,
|
text: None,
|
||||||
keys: vec!(::action::KeySym("U002E".into())),
|
keys: vec!(::action::KeySym("U002E".into())),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,5 +24,6 @@ mod outputs;
|
|||||||
mod popover;
|
mod popover;
|
||||||
mod resources;
|
mod resources;
|
||||||
mod submission;
|
mod submission;
|
||||||
mod util;
|
pub mod tests;
|
||||||
|
pub mod util;
|
||||||
mod xdg;
|
mod xdg;
|
||||||
|
|||||||
@ -58,7 +58,7 @@ rslibs = custom_target(
|
|||||||
output: ['librs.a'],
|
output: ['librs.a'],
|
||||||
install: false,
|
install: false,
|
||||||
console: true,
|
console: true,
|
||||||
command: [cargo_script, '@OUTPUT@', 'build']
|
command: [cargo_script, '--output', '@OUTPUT@', 'build', '--lib']
|
||||||
)
|
)
|
||||||
|
|
||||||
build_rstests = custom_target(
|
build_rstests = custom_target(
|
||||||
@ -124,3 +124,14 @@ squeekboard = executable('squeekboard-real',
|
|||||||
'-DEEKBOARD_COMPILATION=1',
|
'-DEEKBOARD_COMPILATION=1',
|
||||||
'-DEEK_COMPILATION=1'],
|
'-DEEK_COMPILATION=1'],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test_layout = custom_target('squeekboard-test-layout',
|
||||||
|
build_by_default: true,
|
||||||
|
# meson doesn't track all inputs, cargo does
|
||||||
|
build_always_stale: true,
|
||||||
|
output: ['squeekboard-test-layout'],
|
||||||
|
console: true,
|
||||||
|
command: [cargo_script, '--rename', 'test_layout', '@OUTPUT@', 'build', '--bin', 'test_layout'],
|
||||||
|
install: true,
|
||||||
|
install_dir: bindir,
|
||||||
|
)
|
||||||
|
|||||||
78
src/tests.rs
Normal file
78
src/tests.rs
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/*! Testing functionality */
|
||||||
|
|
||||||
|
use ::data::Layout;
|
||||||
|
use xkbcommon::xkb;
|
||||||
|
|
||||||
|
use ::util::WarningHandler;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct CountAndPrint(u32);
|
||||||
|
|
||||||
|
impl WarningHandler for CountAndPrint {
|
||||||
|
fn handle(&mut self, warning: &str) {
|
||||||
|
self.0 = self.0 + 1;
|
||||||
|
println!("{}", warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CountAndPrint {
|
||||||
|
fn new() -> CountAndPrint {
|
||||||
|
CountAndPrint(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_builtin_layout(name: &str) {
|
||||||
|
check_layout(Layout::from_resource(name).expect("Invalid layout data"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_layout_file(path: &str) {
|
||||||
|
check_layout(Layout::from_file(path.into()).expect("Invalid layout file"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_layout(layout: Layout) {
|
||||||
|
let handler = CountAndPrint::new();
|
||||||
|
let (layout, handler) = layout.build(handler);
|
||||||
|
|
||||||
|
if handler.0 > 0 {
|
||||||
|
println!("{} mistakes in layout", handler.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
let layout = layout.expect("layout broken");
|
||||||
|
|
||||||
|
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
||||||
|
|
||||||
|
let keymap_str = layout.keymap_str
|
||||||
|
.clone()
|
||||||
|
.into_string().expect("Failed to decode keymap string");
|
||||||
|
|
||||||
|
let keymap = xkb::Keymap::new_from_string(
|
||||||
|
&context,
|
||||||
|
keymap_str.clone(),
|
||||||
|
xkb::KEYMAP_FORMAT_TEXT_V1,
|
||||||
|
xkb::KEYMAP_COMPILE_NO_FLAGS,
|
||||||
|
).expect("Failed to create keymap");
|
||||||
|
|
||||||
|
let state = xkb::State::new(&keymap);
|
||||||
|
|
||||||
|
// "Press" each button with keysyms
|
||||||
|
for view in layout.views.values() {
|
||||||
|
for row in &view.rows {
|
||||||
|
for button in &row.buttons {
|
||||||
|
let keystate = button.state.borrow();
|
||||||
|
for keycode in &keystate.keycodes {
|
||||||
|
match state.key_get_one_sym(*keycode) {
|
||||||
|
xkb::KEY_NoSymbol => {
|
||||||
|
eprintln!("{}", keymap_str);
|
||||||
|
panic!("Keysym {} on key {:?} can't be resolved", keycode, button.name);
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if handler.0 > 0 {
|
||||||
|
panic!("Layout contains mistakes");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -177,6 +177,11 @@ impl<T> Borrow<Rc<T>> for Pointer<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait WarningHandler {
|
||||||
|
/// Handle a warning
|
||||||
|
fn handle(&mut self, warning: &str);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
Reference in New Issue
Block a user