Compare commits
	
		
			16 Commits
		
	
	
		
			v1.13.0
			...
			pureos/1.1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8293c5f10d | |||
| 601c835416 | |||
| 07d7486e06 | |||
| 5cb70a096c | |||
| cb211bb764 | |||
| 8c8728aa0f | |||
| f71e769315 | |||
| 273179e1ec | |||
| eb4b630b39 | |||
| b60ebdbd99 | |||
| 99f062fe31 | |||
| 0bc654b832 | |||
| 00e9641a5f | |||
| ea3da22f9b | |||
| 99c04fd8f5 | |||
| 2b7e8f829e | 
							
								
								
									
										24
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										24
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -265,9 +265,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "libc"
 | 
			
		||||
version = "0.2.93"
 | 
			
		||||
version = "0.2.94"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
 | 
			
		||||
checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "linked-hash-map"
 | 
			
		||||
@ -353,9 +353,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "regex-syntax"
 | 
			
		||||
version = "0.6.23"
 | 
			
		||||
version = "0.6.25"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548"
 | 
			
		||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rs"
 | 
			
		||||
@ -380,18 +380,18 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "serde"
 | 
			
		||||
version = "1.0.125"
 | 
			
		||||
version = "1.0.126"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
 | 
			
		||||
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "serde_derive",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "serde_derive"
 | 
			
		||||
version = "1.0.125"
 | 
			
		||||
version = "1.0.126"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
 | 
			
		||||
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
@ -412,9 +412,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "syn"
 | 
			
		||||
version = "1.0.69"
 | 
			
		||||
version = "1.0.72"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb"
 | 
			
		||||
checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
@ -438,9 +438,9 @@ checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "unicode-xid"
 | 
			
		||||
version = "0.2.1"
 | 
			
		||||
version = "0.2.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
 | 
			
		||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "winapi"
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,7 @@ if out_path:
 | 
			
		||||
    i = args.index(out_path)
 | 
			
		||||
    args.pop(i)    
 | 
			
		||||
 | 
			
		||||
subprocess.run(['sh', "{}/cargo.sh".format(shlex.quote(source_dir.as_posix())), 'build']
 | 
			
		||||
subprocess.run(['sh', "{}/cargo.sh".format(source_dir.as_posix()), 'build']
 | 
			
		||||
    + args,
 | 
			
		||||
    check=True)
 | 
			
		||||
 | 
			
		||||
@ -43,7 +43,7 @@ if out_path:
 | 
			
		||||
    out_basename = out_path.name
 | 
			
		||||
    filename = filename or out_basename
 | 
			
		||||
    subprocess.run(['cp', '-a',
 | 
			
		||||
        './{}/{}'.format(shlex.quote(binary_dir), shlex.quote(filename)),
 | 
			
		||||
        './{}/{}'.format(binary_dir, filename),
 | 
			
		||||
        out_path],
 | 
			
		||||
        check=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -438,7 +438,7 @@ buttons:
 | 
			
		||||
                unlock_view: "カタカナ"
 | 
			
		||||
        outline: "altline"
 | 
			
		||||
        label: "。"
 | 
			
		||||
    # Buttons for Latin charachters
 | 
			
		||||
    # Buttons for Latin characters
 | 
			
		||||
    RSYM1:
 | 
			
		||||
        action:
 | 
			
		||||
            locking:
 | 
			
		||||
 | 
			
		||||
@ -438,7 +438,7 @@ buttons:
 | 
			
		||||
                unlock_view: "カタカナ"
 | 
			
		||||
        outline: "altline"
 | 
			
		||||
        label: "。"
 | 
			
		||||
    # Buttons for Latin charachters
 | 
			
		||||
    # Buttons for Latin characters
 | 
			
		||||
    RSYM1:
 | 
			
		||||
        action:
 | 
			
		||||
            locking:
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										18
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							@ -1,3 +1,21 @@
 | 
			
		||||
squeekboard (1.14.0pureos0~amber0) amber-phone; urgency=medium
 | 
			
		||||
 | 
			
		||||
  [ Dorota Czaplejewicz ]
 | 
			
		||||
  * data: Split into loading and parsing
 | 
			
		||||
  * layout: Remove unused code
 | 
			
		||||
  * build: Fix unnecessary shell quotes
 | 
			
		||||
  * popover: Allow spanning outside panel area
 | 
			
		||||
  * cargo: Update dependencies before release
 | 
			
		||||
 | 
			
		||||
  [ undef ]
 | 
			
		||||
  * Fix typos jp keyboard comments
 | 
			
		||||
 | 
			
		||||
  [ anteater ]
 | 
			
		||||
  * use the correct GtkStyleProviderPriority to indicate that the styles are provided by the application
 | 
			
		||||
  * remove some unnecessary unsafe code
 | 
			
		||||
 | 
			
		||||
 -- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm>  Sat, 15 May 2021 12:45:20 +0000
 | 
			
		||||
 | 
			
		||||
squeekboard (1.13.0pureos0~amber0) amber-phone; urgency=medium
 | 
			
		||||
 | 
			
		||||
  [ Dorota Czaplejewicz ]
 | 
			
		||||
 | 
			
		||||
@ -287,7 +287,7 @@ eek_renderer_new (LevelKeyboard  *keyboard,
 | 
			
		||||
    }
 | 
			
		||||
    gtk_style_context_add_provider (renderer->view_context,
 | 
			
		||||
        GTK_STYLE_PROVIDER(renderer->css_provider),
 | 
			
		||||
        GTK_STYLE_PROVIDER_PRIORITY_USER);
 | 
			
		||||
        GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
 | 
			
		||||
 | 
			
		||||
    /* Create a style context for the buttons */
 | 
			
		||||
    path = gtk_widget_path_new();
 | 
			
		||||
@ -303,7 +303,7 @@ eek_renderer_new (LevelKeyboard  *keyboard,
 | 
			
		||||
    gtk_style_context_set_state (renderer->button_context, GTK_STATE_FLAG_NORMAL);
 | 
			
		||||
    gtk_style_context_add_provider (renderer->button_context,
 | 
			
		||||
        GTK_STYLE_PROVIDER(renderer->css_provider),
 | 
			
		||||
        GTK_STYLE_PROVIDER_PRIORITY_USER);
 | 
			
		||||
        GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
 | 
			
		||||
    return renderer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
project(
 | 
			
		||||
    'squeekboard',
 | 
			
		||||
    'c', 'rust',
 | 
			
		||||
    version: '1.13.0',
 | 
			
		||||
    version: '1.14.0',
 | 
			
		||||
    license: 'GPLv3',
 | 
			
		||||
    meson_version: '>=0.51.0',
 | 
			
		||||
    default_options: [
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										424
									
								
								src/data/loading.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										424
									
								
								src/data/loading.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,424 @@
 | 
			
		||||
/* Copyright (C) 2020-2021 Purism SPC
 | 
			
		||||
 * SPDX-License-Identifier: GPL-3.0+
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*! Loading layout files */
 | 
			
		||||
 | 
			
		||||
use std::env;
 | 
			
		||||
use std::fmt;
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
use std::convert::TryFrom;
 | 
			
		||||
 | 
			
		||||
use super::{ Error, LoadError };
 | 
			
		||||
use super::parsing;
 | 
			
		||||
 | 
			
		||||
use ::layout::ArrangementKind;
 | 
			
		||||
use ::logging;
 | 
			
		||||
use ::util::c::as_str;
 | 
			
		||||
use ::xdg;
 | 
			
		||||
use ::imservice::ContentPurpose;
 | 
			
		||||
 | 
			
		||||
// traits, derives
 | 
			
		||||
use ::logging::Warn;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// 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,    // name of the keyboard
 | 
			
		||||
        type_: u32,             // type like Wide
 | 
			
		||||
        variant: u32,          // purpose variant like numeric, terminal...
 | 
			
		||||
        overlay: *const c_char, // the overlay (looking for "terminal")
 | 
			
		||||
    ) -> *mut ::layout::Layout {
 | 
			
		||||
        let type_ = match type_ {
 | 
			
		||||
            0 => ArrangementKind::Base,
 | 
			
		||||
            1 => ArrangementKind::Wide,
 | 
			
		||||
            _ => panic!("Bad enum value"),
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        let name = as_str(&name)
 | 
			
		||||
            .expect("Bad layout name")
 | 
			
		||||
            .expect("Empty layout name");
 | 
			
		||||
 | 
			
		||||
        let variant = ContentPurpose::try_from(variant)
 | 
			
		||||
                    .or_print(
 | 
			
		||||
                        logging::Problem::Warning,
 | 
			
		||||
                        "Received invalid purpose value",
 | 
			
		||||
                    )
 | 
			
		||||
                    .unwrap_or(ContentPurpose::Normal);
 | 
			
		||||
 | 
			
		||||
        let overlay_str = as_str(&overlay)
 | 
			
		||||
                .expect("Bad overlay name")
 | 
			
		||||
                .expect("Empty overlay name");
 | 
			
		||||
        let overlay_str = match overlay_str {
 | 
			
		||||
            "" => None,
 | 
			
		||||
            other => Some(other),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let (kind, layout) = load_layout_data_with_fallback(&name, type_, variant, overlay_str);
 | 
			
		||||
        let layout = ::layout::Layout::new(layout, kind);
 | 
			
		||||
        Box::into_raw(Box::new(layout))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const FALLBACK_LAYOUT_NAME: &str = "us";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, PartialEq)]
 | 
			
		||||
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),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* All functions in this family carry around ArrangementKind,
 | 
			
		||||
 * because it's not guaranteed to be preserved,
 | 
			
		||||
 * and the resulting layout needs to know which version was loaded.
 | 
			
		||||
 * See `squeek_layout_get_kind`.
 | 
			
		||||
 * Possible TODO: since this is used only in styling,
 | 
			
		||||
 * and makes the below code nastier than needed, maybe it should go.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/// Returns ordered names treating `name` as the base name,
 | 
			
		||||
/// ignoring any `+` inside.
 | 
			
		||||
fn _get_arrangement_names(name: &str, arrangement: ArrangementKind)
 | 
			
		||||
    -> Vec<(ArrangementKind, String)>
 | 
			
		||||
{
 | 
			
		||||
    let name_with_arrangement = match arrangement {    
 | 
			
		||||
        ArrangementKind::Base => name.into(),
 | 
			
		||||
        ArrangementKind::Wide => format!("{}_wide", name),
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    let mut ret = Vec::new();
 | 
			
		||||
    if name_with_arrangement != name {
 | 
			
		||||
        ret.push((arrangement, name_with_arrangement));
 | 
			
		||||
    }
 | 
			
		||||
    ret.push((ArrangementKind::Base, name.into()));
 | 
			
		||||
    ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Returns names accounting for any `+` in the `name`,
 | 
			
		||||
/// including the fallback to the default layout.
 | 
			
		||||
fn get_preferred_names(name: &str, kind: ArrangementKind)
 | 
			
		||||
    -> Vec<(ArrangementKind, String)>
 | 
			
		||||
{
 | 
			
		||||
    let mut ret = _get_arrangement_names(name, kind);
 | 
			
		||||
    
 | 
			
		||||
    let base_name_preferences = {
 | 
			
		||||
        let mut parts = name.splitn(2, '+');
 | 
			
		||||
        match parts.next() {
 | 
			
		||||
            Some(base) => {
 | 
			
		||||
                // The name is already equal to base, so nothing to add
 | 
			
		||||
                if base == name {
 | 
			
		||||
                    vec![]
 | 
			
		||||
                } else {
 | 
			
		||||
                    _get_arrangement_names(base, kind)
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            // The layout's base name starts with a "+". Weird but OK.
 | 
			
		||||
            None => {
 | 
			
		||||
                log_print!(logging::Level::Surprise, "Base layout name is empty: {}", name);
 | 
			
		||||
                vec![]
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    ret.extend(base_name_preferences.into_iter());
 | 
			
		||||
    let fallback_names = _get_arrangement_names(FALLBACK_LAYOUT_NAME, kind);
 | 
			
		||||
    ret.extend(fallback_names.into_iter());
 | 
			
		||||
    ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Includes the subdirectory before the forward slash.
 | 
			
		||||
type LayoutPath = String;
 | 
			
		||||
 | 
			
		||||
// This is only used inside iter_fallbacks_with_meta.
 | 
			
		||||
// Placed at the top scope
 | 
			
		||||
// because `use LayoutPurpose::*;`
 | 
			
		||||
// complains about "not in scope" otherwise.
 | 
			
		||||
// This seems to be a Rust 2015 edition problem.
 | 
			
		||||
/// Helper for determining where to look up the layout.
 | 
			
		||||
enum LayoutPurpose<'a> {
 | 
			
		||||
    Default,
 | 
			
		||||
    Special(&'a str),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Returns the directory string
 | 
			
		||||
/// where the layout should be looked up, including the slash.
 | 
			
		||||
fn get_directory_string(
 | 
			
		||||
    content_purpose: ContentPurpose,
 | 
			
		||||
    overlay: Option<&str>) -> String
 | 
			
		||||
{
 | 
			
		||||
    use self::LayoutPurpose::*;
 | 
			
		||||
 | 
			
		||||
    let layout_purpose = match overlay {
 | 
			
		||||
        None => match content_purpose {
 | 
			
		||||
            ContentPurpose::Number => Special("number"),
 | 
			
		||||
            ContentPurpose::Digits => Special("number"),
 | 
			
		||||
            ContentPurpose::Phone => Special("number"),
 | 
			
		||||
            ContentPurpose::Terminal => Special("terminal"),
 | 
			
		||||
            _ => Default,
 | 
			
		||||
        },
 | 
			
		||||
        Some(overlay) => Special(overlay),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // For intuitiveness,
 | 
			
		||||
    // default purpose layouts are stored in the root directory,
 | 
			
		||||
    // as they correspond to typical text
 | 
			
		||||
    // and are seen the most often.
 | 
			
		||||
    match layout_purpose {
 | 
			
		||||
        Default => "".into(),
 | 
			
		||||
        Special(purpose) => format!("{}/", purpose),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Returns an iterator over all fallback paths.
 | 
			
		||||
fn to_layout_paths(
 | 
			
		||||
    name_fallbacks: Vec<(ArrangementKind, String)>,
 | 
			
		||||
    content_purpose: ContentPurpose,
 | 
			
		||||
    overlay: Option<&str>,
 | 
			
		||||
) -> impl Iterator<Item=(ArrangementKind, LayoutPath)> {
 | 
			
		||||
    let prepend_directory = get_directory_string(content_purpose, overlay);
 | 
			
		||||
 | 
			
		||||
    name_fallbacks.into_iter()
 | 
			
		||||
        .map(move |(arrangement, name)|
 | 
			
		||||
            (arrangement, format!("{}{}", prepend_directory, name))
 | 
			
		||||
        )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LayoutSource = (ArrangementKind, DataSource);
 | 
			
		||||
 | 
			
		||||
fn to_layout_sources(
 | 
			
		||||
    layout_paths: impl Iterator<Item=(ArrangementKind, LayoutPath)>,
 | 
			
		||||
    filesystem_path: Option<PathBuf>,
 | 
			
		||||
) -> impl Iterator<Item=LayoutSource> {
 | 
			
		||||
    layout_paths.flat_map(move |(arrangement, layout_path)| {
 | 
			
		||||
        let mut sources = Vec::new();
 | 
			
		||||
        if let Some(path) = &filesystem_path {
 | 
			
		||||
            sources.push((
 | 
			
		||||
                arrangement,
 | 
			
		||||
                DataSource::File(
 | 
			
		||||
                    path.join(&layout_path)
 | 
			
		||||
                        .with_extension("yaml")
 | 
			
		||||
                )
 | 
			
		||||
            ));
 | 
			
		||||
        };
 | 
			
		||||
        sources.push((arrangement, DataSource::Resource(layout_path.clone())));
 | 
			
		||||
        sources.into_iter()
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Returns possible sources, with first as the most preferred one.
 | 
			
		||||
/// Trying order: native lang of the right kind, native base,
 | 
			
		||||
/// fallback lang of the right kind, fallback base
 | 
			
		||||
fn iter_layout_sources(
 | 
			
		||||
    name: &str,
 | 
			
		||||
    arrangement: ArrangementKind,
 | 
			
		||||
    purpose: ContentPurpose,
 | 
			
		||||
    ui_overlay: Option<&str>,
 | 
			
		||||
    layout_storage: Option<PathBuf>,
 | 
			
		||||
) -> impl Iterator<Item=LayoutSource> {
 | 
			
		||||
    let names = get_preferred_names(name, arrangement);
 | 
			
		||||
    let paths = to_layout_paths(names, purpose, ui_overlay);
 | 
			
		||||
    to_layout_sources(paths, layout_storage)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn load_layout_data(source: DataSource)
 | 
			
		||||
    -> Result<::layout::LayoutData, LoadError>
 | 
			
		||||
{
 | 
			
		||||
    let handler = logging::Print {};
 | 
			
		||||
    match source {
 | 
			
		||||
        DataSource::File(path) => {
 | 
			
		||||
            parsing::Layout::from_file(path.clone())
 | 
			
		||||
                .map_err(LoadError::BadData)
 | 
			
		||||
                .and_then(|layout|
 | 
			
		||||
                    layout.build(handler).0.map_err(LoadError::BadKeyMap)
 | 
			
		||||
                )
 | 
			
		||||
        },
 | 
			
		||||
        DataSource::Resource(name) => {
 | 
			
		||||
            parsing::Layout::from_resource(&name)
 | 
			
		||||
                .and_then(|layout|
 | 
			
		||||
                    layout.build(handler).0.map_err(LoadError::BadKeyMap)
 | 
			
		||||
                )
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn load_layout_data_with_fallback(
 | 
			
		||||
    name: &str,
 | 
			
		||||
    kind: ArrangementKind,
 | 
			
		||||
    purpose: ContentPurpose,
 | 
			
		||||
    overlay: Option<&str>,
 | 
			
		||||
) -> (ArrangementKind, ::layout::LayoutData) {
 | 
			
		||||
 | 
			
		||||
    // Build the path to the right keyboard layout subdirectory
 | 
			
		||||
    let path = env::var_os("SQUEEKBOARD_KEYBOARDSDIR")
 | 
			
		||||
        .map(PathBuf::from)
 | 
			
		||||
        .or_else(|| xdg::data_path("squeekboard/keyboards"));
 | 
			
		||||
 | 
			
		||||
    for (kind, source) in iter_layout_sources(&name, kind, purpose, overlay, path) {
 | 
			
		||||
        let layout = load_layout_data(source.clone());
 | 
			
		||||
        match layout {
 | 
			
		||||
            Err(e) => match (e, source) {
 | 
			
		||||
                (
 | 
			
		||||
                    LoadError::BadData(Error::Missing(e)),
 | 
			
		||||
                    DataSource::File(file)
 | 
			
		||||
                ) => log_print!(
 | 
			
		||||
                    logging::Level::Debug,
 | 
			
		||||
                    "Tried file {:?}, but it's missing: {}",
 | 
			
		||||
                    file, e
 | 
			
		||||
                ),
 | 
			
		||||
                (e, source) => log_print!(
 | 
			
		||||
                    logging::Level::Warning,
 | 
			
		||||
                    "Failed to load layout from {}: {}, skipping",
 | 
			
		||||
                    source, e
 | 
			
		||||
                ),
 | 
			
		||||
            },
 | 
			
		||||
            Ok(layout) => {
 | 
			
		||||
                log_print!(logging::Level::Info, "Loaded layout {}", source);
 | 
			
		||||
                return (kind, layout);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    panic!("No useful layout found!");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
 | 
			
		||||
    use ::logging::ProblemPanic;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn parsing_fallback() {
 | 
			
		||||
        assert!(parsing::Layout::from_resource(FALLBACK_LAYOUT_NAME)
 | 
			
		||||
            .map(|layout| layout.build(ProblemPanic).0.unwrap())
 | 
			
		||||
            .is_ok()
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// First fallback should be to builtin, not to FALLBACK_LAYOUT_NAME
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_fallback_basic_builtin() {
 | 
			
		||||
        let sources = iter_layout_sources("nb", ArrangementKind::Base, ContentPurpose::Normal, None, None);
 | 
			
		||||
        
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            sources.collect::<Vec<_>>(),
 | 
			
		||||
            vec!(
 | 
			
		||||
                (ArrangementKind::Base, DataSource::Resource("nb".into())),
 | 
			
		||||
                (
 | 
			
		||||
                    ArrangementKind::Base,
 | 
			
		||||
                    DataSource::Resource(FALLBACK_LAYOUT_NAME.into())
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Prefer loading from file system before builtin.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_preferences_order_path() {
 | 
			
		||||
        let sources = iter_layout_sources("nb", ArrangementKind::Base, ContentPurpose::Normal, None, Some(".".into()));
 | 
			
		||||
        
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            sources.collect::<Vec<_>>(),
 | 
			
		||||
            vec!(
 | 
			
		||||
                (ArrangementKind::Base, DataSource::File("./nb.yaml".into())),
 | 
			
		||||
                (ArrangementKind::Base, DataSource::Resource("nb".into())),
 | 
			
		||||
                (
 | 
			
		||||
                    ArrangementKind::Base,
 | 
			
		||||
                    DataSource::File("./us.yaml".into())
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    ArrangementKind::Base,
 | 
			
		||||
                    DataSource::Resource("us".into())
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// If layout contains a "+", it should reach for what's in front of it too.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_preferences_order_base() {
 | 
			
		||||
        let sources = iter_layout_sources("nb+aliens", ArrangementKind::Base, ContentPurpose::Normal, None, None);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            sources.collect::<Vec<_>>(),
 | 
			
		||||
            vec!(
 | 
			
		||||
                (ArrangementKind::Base, DataSource::Resource("nb+aliens".into())),
 | 
			
		||||
                (ArrangementKind::Base, DataSource::Resource("nb".into())),
 | 
			
		||||
                (
 | 
			
		||||
                    ArrangementKind::Base,
 | 
			
		||||
                    DataSource::Resource(FALLBACK_LAYOUT_NAME.into())
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_preferences_order_arrangement() {
 | 
			
		||||
        let sources = iter_layout_sources("nb", ArrangementKind::Wide, ContentPurpose::Normal, None, None);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            sources.collect::<Vec<_>>(),
 | 
			
		||||
            vec!(
 | 
			
		||||
                (ArrangementKind::Wide, DataSource::Resource("nb_wide".into())),
 | 
			
		||||
                (ArrangementKind::Base, DataSource::Resource("nb".into())),
 | 
			
		||||
                (
 | 
			
		||||
                    ArrangementKind::Wide,
 | 
			
		||||
                    DataSource::Resource("us_wide".into())
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    ArrangementKind::Base,
 | 
			
		||||
                    DataSource::Resource("us".into())
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_preferences_order_overlay() {
 | 
			
		||||
        let sources = iter_layout_sources("nb", ArrangementKind::Base, ContentPurpose::Normal, Some("terminal"), None);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            sources.collect::<Vec<_>>(),
 | 
			
		||||
            vec!(
 | 
			
		||||
                (ArrangementKind::Base, DataSource::Resource("terminal/nb".into())),
 | 
			
		||||
                (
 | 
			
		||||
                    ArrangementKind::Base,
 | 
			
		||||
                    DataSource::Resource("terminal/us".into())
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_preferences_order_hint() {
 | 
			
		||||
        let sources = iter_layout_sources("nb", ArrangementKind::Base, ContentPurpose::Terminal, None, None);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            sources.collect::<Vec<_>>(),
 | 
			
		||||
            vec!(
 | 
			
		||||
                (ArrangementKind::Base, DataSource::Resource("terminal/nb".into())),
 | 
			
		||||
                (
 | 
			
		||||
                    ArrangementKind::Base,
 | 
			
		||||
                    DataSource::Resource("terminal/us".into())
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								src/data/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/data/mod.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,65 @@
 | 
			
		||||
/* Copyright (C) 2020-2021 Purism SPC
 | 
			
		||||
 * SPDX-License-Identifier: GPL-3.0+
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*! Combined module for dealing with layout files */
 | 
			
		||||
 | 
			
		||||
mod loading;
 | 
			
		||||
pub mod parsing;
 | 
			
		||||
 | 
			
		||||
use std::io;
 | 
			
		||||
use std::fmt;
 | 
			
		||||
 | 
			
		||||
use ::keyboard::FormattingError;
 | 
			
		||||
 | 
			
		||||
/// Errors encountered loading the layout into yaml
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub enum Error {
 | 
			
		||||
    Yaml(serde_yaml::Error),
 | 
			
		||||
    Io(io::Error),
 | 
			
		||||
    /// The file was missing.
 | 
			
		||||
    /// It's distinct from Io in order to make it matchable
 | 
			
		||||
    /// without calling io::Error::kind()
 | 
			
		||||
    Missing(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),
 | 
			
		||||
            Error::Missing(e) => write!(f, "Missing: {}", e),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<io::Error> for Error {
 | 
			
		||||
    fn from(e: io::Error) -> Self {
 | 
			
		||||
        let kind = e.kind();
 | 
			
		||||
        match kind {
 | 
			
		||||
            io::ErrorKind::NotFound => Error::Missing(e),
 | 
			
		||||
            _ => Error::Io(e),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub 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),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,34 +1,30 @@
 | 
			
		||||
/**! The parsing of the data files for layouts */
 | 
			
		||||
/* Copyright (C) 2020-2021 Purism SPC
 | 
			
		||||
 * SPDX-License-Identifier: GPL-3.0+
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// TODO: find a nice way to make sure non-positive sizes don't break layouts
 | 
			
		||||
/*! Parsing of the data files containing 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 std::convert::TryFrom;
 | 
			
		||||
 | 
			
		||||
use xkbcommon::xkb;
 | 
			
		||||
 | 
			
		||||
use super::{ Error, LoadError };
 | 
			
		||||
 | 
			
		||||
use ::action;
 | 
			
		||||
use ::keyboard::{
 | 
			
		||||
    KeyState, PressType,
 | 
			
		||||
    generate_keymaps, generate_keycodes, KeyCode, FormattingError
 | 
			
		||||
};
 | 
			
		||||
use ::layout;
 | 
			
		||||
use ::layout::ArrangementKind;
 | 
			
		||||
use ::logging;
 | 
			
		||||
use ::resources;
 | 
			
		||||
use ::util::c::as_str;
 | 
			
		||||
use ::util::hash_map_map;
 | 
			
		||||
use ::xdg;
 | 
			
		||||
use ::imservice::ContentPurpose;
 | 
			
		||||
use ::resources;
 | 
			
		||||
 | 
			
		||||
// traits, derives
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
@ -36,299 +32,7 @@ use std::io::BufReader;
 | 
			
		||||
use std::iter::FromIterator;
 | 
			
		||||
use ::logging::Warn;
 | 
			
		||||
 | 
			
		||||
/// 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,    // name of the keyboard
 | 
			
		||||
        type_: u32,             // type like Wide
 | 
			
		||||
        variant: u32,          // purpose variant like numeric, terminal...
 | 
			
		||||
        overlay: *const c_char, // the overlay (looking for "terminal")
 | 
			
		||||
    ) -> *mut ::layout::Layout {
 | 
			
		||||
        let type_ = match type_ {
 | 
			
		||||
            0 => ArrangementKind::Base,
 | 
			
		||||
            1 => ArrangementKind::Wide,
 | 
			
		||||
            _ => panic!("Bad enum value"),
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        let name = as_str(&name)
 | 
			
		||||
            .expect("Bad layout name")
 | 
			
		||||
            .expect("Empty layout name");
 | 
			
		||||
 | 
			
		||||
        let variant = ContentPurpose::try_from(variant)
 | 
			
		||||
                    .or_print(
 | 
			
		||||
                        logging::Problem::Warning,
 | 
			
		||||
                        "Received invalid purpose value",
 | 
			
		||||
                    )
 | 
			
		||||
                    .unwrap_or(ContentPurpose::Normal);
 | 
			
		||||
 | 
			
		||||
        let overlay_str = as_str(&overlay)
 | 
			
		||||
                .expect("Bad overlay name")
 | 
			
		||||
                .expect("Empty overlay name");
 | 
			
		||||
        let overlay_str = match overlay_str {
 | 
			
		||||
            "" => None,
 | 
			
		||||
            other => Some(other),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let (kind, layout) = load_layout_data_with_fallback(&name, type_, variant, overlay_str);
 | 
			
		||||
        let layout = ::layout::Layout::new(layout, kind);
 | 
			
		||||
        Box::into_raw(Box::new(layout))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const FALLBACK_LAYOUT_NAME: &str = "us";
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub 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),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, PartialEq)]
 | 
			
		||||
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),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* All functions in this family carry around ArrangementKind,
 | 
			
		||||
 * because it's not guaranteed to be preserved,
 | 
			
		||||
 * and the resulting layout needs to know which version was loaded.
 | 
			
		||||
 * See `squeek_layout_get_kind`.
 | 
			
		||||
 * Possible TODO: since this is used only in styling,
 | 
			
		||||
 * and makes the below code nastier than needed, maybe it should go.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/// Returns ordered names treating `name` as the base name,
 | 
			
		||||
/// ignoring any `+` inside.
 | 
			
		||||
fn _get_arrangement_names(name: &str, arrangement: ArrangementKind)
 | 
			
		||||
    -> Vec<(ArrangementKind, String)>
 | 
			
		||||
{
 | 
			
		||||
    let name_with_arrangement = match arrangement {    
 | 
			
		||||
        ArrangementKind::Base => name.into(),
 | 
			
		||||
        ArrangementKind::Wide => format!("{}_wide", name),
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    let mut ret = Vec::new();
 | 
			
		||||
    if name_with_arrangement != name {
 | 
			
		||||
        ret.push((arrangement, name_with_arrangement));
 | 
			
		||||
    }
 | 
			
		||||
    ret.push((ArrangementKind::Base, name.into()));
 | 
			
		||||
    ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Returns names accounting for any `+` in the `name`,
 | 
			
		||||
/// including the fallback to the default layout.
 | 
			
		||||
fn get_preferred_names(name: &str, kind: ArrangementKind)
 | 
			
		||||
    -> Vec<(ArrangementKind, String)>
 | 
			
		||||
{
 | 
			
		||||
    let mut ret = _get_arrangement_names(name, kind);
 | 
			
		||||
    
 | 
			
		||||
    let base_name_preferences = {
 | 
			
		||||
        let mut parts = name.splitn(2, '+');
 | 
			
		||||
        match parts.next() {
 | 
			
		||||
            Some(base) => {
 | 
			
		||||
                // The name is already equal to base, so nothing to add
 | 
			
		||||
                if base == name {
 | 
			
		||||
                    vec![]
 | 
			
		||||
                } else {
 | 
			
		||||
                    _get_arrangement_names(base, kind)
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            // The layout's base name starts with a "+". Weird but OK.
 | 
			
		||||
            None => {
 | 
			
		||||
                log_print!(logging::Level::Surprise, "Base layout name is empty: {}", name);
 | 
			
		||||
                vec![]
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    ret.extend(base_name_preferences.into_iter());
 | 
			
		||||
    let fallback_names = _get_arrangement_names(FALLBACK_LAYOUT_NAME, kind);
 | 
			
		||||
    ret.extend(fallback_names.into_iter());
 | 
			
		||||
    ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Includes the subdirectory before the forward slash.
 | 
			
		||||
type LayoutPath = String;
 | 
			
		||||
 | 
			
		||||
// This is only used inside iter_fallbacks_with_meta.
 | 
			
		||||
// Placed at the top scope
 | 
			
		||||
// because `use LayoutPurpose::*;`
 | 
			
		||||
// complains about "not in scope" otherwise.
 | 
			
		||||
// This seems to be a Rust 2015 edition problem.
 | 
			
		||||
/// Helper for determining where to look up the layout.
 | 
			
		||||
enum LayoutPurpose<'a> {
 | 
			
		||||
    Default,
 | 
			
		||||
    Special(&'a str),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Returns the directory string
 | 
			
		||||
/// where the layout should be looked up, including the slash.
 | 
			
		||||
fn get_directory_string(
 | 
			
		||||
    content_purpose: ContentPurpose,
 | 
			
		||||
    overlay: Option<&str>) -> String
 | 
			
		||||
{
 | 
			
		||||
    use self::LayoutPurpose::*;
 | 
			
		||||
 | 
			
		||||
    let layout_purpose = match overlay {
 | 
			
		||||
        None => match content_purpose {
 | 
			
		||||
            ContentPurpose::Number => Special("number"),
 | 
			
		||||
            ContentPurpose::Digits => Special("number"),
 | 
			
		||||
            ContentPurpose::Phone => Special("number"),
 | 
			
		||||
            ContentPurpose::Terminal => Special("terminal"),
 | 
			
		||||
            _ => Default,
 | 
			
		||||
        },
 | 
			
		||||
        Some(overlay) => Special(overlay),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // For intuitiveness,
 | 
			
		||||
    // default purpose layouts are stored in the root directory,
 | 
			
		||||
    // as they correspond to typical text
 | 
			
		||||
    // and are seen the most often.
 | 
			
		||||
    match layout_purpose {
 | 
			
		||||
        Default => "".into(),
 | 
			
		||||
        Special(purpose) => format!("{}/", purpose),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Returns an iterator over all fallback paths.
 | 
			
		||||
fn to_layout_paths(
 | 
			
		||||
    name_fallbacks: Vec<(ArrangementKind, String)>,
 | 
			
		||||
    content_purpose: ContentPurpose,
 | 
			
		||||
    overlay: Option<&str>,
 | 
			
		||||
) -> impl Iterator<Item=(ArrangementKind, LayoutPath)> {
 | 
			
		||||
    let prepend_directory = get_directory_string(content_purpose, overlay);
 | 
			
		||||
 | 
			
		||||
    name_fallbacks.into_iter()
 | 
			
		||||
        .map(move |(arrangement, name)|
 | 
			
		||||
            (arrangement, format!("{}{}", prepend_directory, name))
 | 
			
		||||
        )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LayoutSource = (ArrangementKind, DataSource);
 | 
			
		||||
 | 
			
		||||
fn to_layout_sources(
 | 
			
		||||
    layout_paths: impl Iterator<Item=(ArrangementKind, LayoutPath)>,
 | 
			
		||||
    filesystem_path: Option<PathBuf>,
 | 
			
		||||
) -> impl Iterator<Item=LayoutSource> {
 | 
			
		||||
    layout_paths.flat_map(move |(arrangement, layout_path)| {
 | 
			
		||||
        let mut sources = Vec::new();
 | 
			
		||||
        if let Some(path) = &filesystem_path {
 | 
			
		||||
            sources.push((
 | 
			
		||||
                arrangement,
 | 
			
		||||
                DataSource::File(
 | 
			
		||||
                    path.join(&layout_path)
 | 
			
		||||
                        .with_extension("yaml")
 | 
			
		||||
                )
 | 
			
		||||
            ));
 | 
			
		||||
        };
 | 
			
		||||
        sources.push((arrangement, DataSource::Resource(layout_path.clone())));
 | 
			
		||||
        sources.into_iter()
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Returns possible sources, with first as the most preferred one.
 | 
			
		||||
/// Trying order: native lang of the right kind, native base,
 | 
			
		||||
/// fallback lang of the right kind, fallback base
 | 
			
		||||
fn iter_layout_sources(
 | 
			
		||||
    name: &str,
 | 
			
		||||
    arrangement: ArrangementKind,
 | 
			
		||||
    purpose: ContentPurpose,
 | 
			
		||||
    ui_overlay: Option<&str>,
 | 
			
		||||
    layout_storage: Option<PathBuf>,
 | 
			
		||||
) -> impl Iterator<Item=LayoutSource> {
 | 
			
		||||
    let names = get_preferred_names(name, arrangement);
 | 
			
		||||
    let paths = to_layout_paths(names, purpose, ui_overlay);
 | 
			
		||||
    to_layout_sources(paths, layout_storage)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn load_layout_data(source: DataSource)
 | 
			
		||||
    -> Result<::layout::LayoutData, LoadError>
 | 
			
		||||
{
 | 
			
		||||
    let handler = logging::Print {};
 | 
			
		||||
    match source {
 | 
			
		||||
        DataSource::File(path) => {
 | 
			
		||||
            Layout::from_file(path.clone())
 | 
			
		||||
                .map_err(LoadError::BadData)
 | 
			
		||||
                .and_then(|layout|
 | 
			
		||||
                    layout.build(handler).0.map_err(LoadError::BadKeyMap)
 | 
			
		||||
                )
 | 
			
		||||
        },
 | 
			
		||||
        DataSource::Resource(name) => {
 | 
			
		||||
            Layout::from_resource(&name)
 | 
			
		||||
                .and_then(|layout|
 | 
			
		||||
                    layout.build(handler).0.map_err(LoadError::BadKeyMap)
 | 
			
		||||
                )
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn load_layout_data_with_fallback(
 | 
			
		||||
    name: &str,
 | 
			
		||||
    kind: ArrangementKind,
 | 
			
		||||
    purpose: ContentPurpose,
 | 
			
		||||
    overlay: Option<&str>,
 | 
			
		||||
) -> (ArrangementKind, ::layout::LayoutData) {
 | 
			
		||||
 | 
			
		||||
    // Build the path to the right keyboard layout subdirectory
 | 
			
		||||
    let path = env::var_os("SQUEEKBOARD_KEYBOARDSDIR")
 | 
			
		||||
        .map(PathBuf::from)
 | 
			
		||||
        .or_else(|| xdg::data_path("squeekboard/keyboards"));
 | 
			
		||||
 | 
			
		||||
    for (kind, source) in iter_layout_sources(&name, kind, purpose, overlay, path) {
 | 
			
		||||
        let layout = load_layout_data(source.clone());
 | 
			
		||||
        match layout {
 | 
			
		||||
            Err(e) => match (e, source) {
 | 
			
		||||
                (
 | 
			
		||||
                    LoadError::BadData(Error::Missing(e)),
 | 
			
		||||
                    DataSource::File(file)
 | 
			
		||||
                ) => log_print!(
 | 
			
		||||
                    logging::Level::Debug,
 | 
			
		||||
                    "Tried file {:?}, but it's missing: {}",
 | 
			
		||||
                    file, e
 | 
			
		||||
                ),
 | 
			
		||||
                (e, source) => log_print!(
 | 
			
		||||
                    logging::Level::Warning,
 | 
			
		||||
                    "Failed to load layout from {}: {}, skipping",
 | 
			
		||||
                    source, e
 | 
			
		||||
                ),
 | 
			
		||||
            },
 | 
			
		||||
            Ok(layout) => {
 | 
			
		||||
                log_print!(logging::Level::Info, "Loaded layout {}", source);
 | 
			
		||||
                return (kind, layout);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    panic!("No useful layout found!");
 | 
			
		||||
}
 | 
			
		||||
// TODO: find a nice way to make sure non-positive sizes don't break layouts
 | 
			
		||||
 | 
			
		||||
/// The root element describing an entire keyboard
 | 
			
		||||
#[derive(Debug, Deserialize, PartialEq)]
 | 
			
		||||
@ -421,37 +125,6 @@ struct Outline {
 | 
			
		||||
    height: f64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Errors encountered loading the layout into yaml
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub enum Error {
 | 
			
		||||
    Yaml(serde_yaml::Error),
 | 
			
		||||
    Io(io::Error),
 | 
			
		||||
    /// The file was missing.
 | 
			
		||||
    /// It's distinct from Io in order to make it matchable
 | 
			
		||||
    /// without calling io::Error::kind()
 | 
			
		||||
    Missing(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),
 | 
			
		||||
            Error::Missing(e) => write!(f, "Missing: {}", e),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<io::Error> for Error {
 | 
			
		||||
    fn from(e: io::Error) -> Self {
 | 
			
		||||
        let kind = e.kind();
 | 
			
		||||
        match kind {
 | 
			
		||||
            io::ErrorKind::NotFound => Error::Missing(e),
 | 
			
		||||
            _ => Error::Io(e),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn add_offsets<'a, I: 'a, T, F: 'a>(iterator: I, get_size: F)
 | 
			
		||||
    -> impl Iterator<Item=(f64, T)> + 'a
 | 
			
		||||
    where I: Iterator<Item=T>,
 | 
			
		||||
@ -871,10 +544,13 @@ fn extract_symbol_names<'a>(actions: &'a [(&str, action::Action)])
 | 
			
		||||
        .map(|named_keysym| named_keysym.0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    
 | 
			
		||||
    use std::env;
 | 
			
		||||
    
 | 
			
		||||
    use ::logging::ProblemPanic;
 | 
			
		||||
 | 
			
		||||
    fn path_from_root(file: &'static str) -> PathBuf {
 | 
			
		||||
@ -1024,124 +700,6 @@ mod tests {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn parsing_fallback() {
 | 
			
		||||
        assert!(Layout::from_resource(FALLBACK_LAYOUT_NAME)
 | 
			
		||||
            .map(|layout| layout.build(ProblemPanic).0.unwrap())
 | 
			
		||||
            .is_ok()
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// First fallback should be to builtin, not to FALLBACK_LAYOUT_NAME
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_fallback_basic_builtin() {
 | 
			
		||||
        let sources = iter_layout_sources("nb", ArrangementKind::Base, ContentPurpose::Normal, None, None);
 | 
			
		||||
        
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            sources.collect::<Vec<_>>(),
 | 
			
		||||
            vec!(
 | 
			
		||||
                (ArrangementKind::Base, DataSource::Resource("nb".into())),
 | 
			
		||||
                (
 | 
			
		||||
                    ArrangementKind::Base,
 | 
			
		||||
                    DataSource::Resource(FALLBACK_LAYOUT_NAME.into())
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Prefer loading from file system before builtin.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_preferences_order_path() {
 | 
			
		||||
        let sources = iter_layout_sources("nb", ArrangementKind::Base, ContentPurpose::Normal, None, Some(".".into()));
 | 
			
		||||
        
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            sources.collect::<Vec<_>>(),
 | 
			
		||||
            vec!(
 | 
			
		||||
                (ArrangementKind::Base, DataSource::File("./nb.yaml".into())),
 | 
			
		||||
                (ArrangementKind::Base, DataSource::Resource("nb".into())),
 | 
			
		||||
                (
 | 
			
		||||
                    ArrangementKind::Base,
 | 
			
		||||
                    DataSource::File("./us.yaml".into())
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    ArrangementKind::Base,
 | 
			
		||||
                    DataSource::Resource("us".into())
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// If layout contains a "+", it should reach for what's in front of it too.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_preferences_order_base() {
 | 
			
		||||
        let sources = iter_layout_sources("nb+aliens", ArrangementKind::Base, ContentPurpose::Normal, None, None);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            sources.collect::<Vec<_>>(),
 | 
			
		||||
            vec!(
 | 
			
		||||
                (ArrangementKind::Base, DataSource::Resource("nb+aliens".into())),
 | 
			
		||||
                (ArrangementKind::Base, DataSource::Resource("nb".into())),
 | 
			
		||||
                (
 | 
			
		||||
                    ArrangementKind::Base,
 | 
			
		||||
                    DataSource::Resource(FALLBACK_LAYOUT_NAME.into())
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_preferences_order_arrangement() {
 | 
			
		||||
        let sources = iter_layout_sources("nb", ArrangementKind::Wide, ContentPurpose::Normal, None, None);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            sources.collect::<Vec<_>>(),
 | 
			
		||||
            vec!(
 | 
			
		||||
                (ArrangementKind::Wide, DataSource::Resource("nb_wide".into())),
 | 
			
		||||
                (ArrangementKind::Base, DataSource::Resource("nb".into())),
 | 
			
		||||
                (
 | 
			
		||||
                    ArrangementKind::Wide,
 | 
			
		||||
                    DataSource::Resource("us_wide".into())
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    ArrangementKind::Base,
 | 
			
		||||
                    DataSource::Resource("us".into())
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_preferences_order_overlay() {
 | 
			
		||||
        let sources = iter_layout_sources("nb", ArrangementKind::Base, ContentPurpose::Normal, Some("terminal"), None);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            sources.collect::<Vec<_>>(),
 | 
			
		||||
            vec!(
 | 
			
		||||
                (ArrangementKind::Base, DataSource::Resource("terminal/nb".into())),
 | 
			
		||||
                (
 | 
			
		||||
                    ArrangementKind::Base,
 | 
			
		||||
                    DataSource::Resource("terminal/us".into())
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_preferences_order_hint() {
 | 
			
		||||
        let sources = iter_layout_sources("nb", ArrangementKind::Base, ContentPurpose::Terminal, None, None);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            sources.collect::<Vec<_>>(),
 | 
			
		||||
            vec!(
 | 
			
		||||
                (ArrangementKind::Base, DataSource::Resource("terminal/nb".into())),
 | 
			
		||||
                (
 | 
			
		||||
                    ArrangementKind::Base,
 | 
			
		||||
                    DataSource::Resource("terminal/us".into())
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn unicode_keysym() {
 | 
			
		||||
        let keysym = xkb::keysym_from_name(
 | 
			
		||||
@ -621,12 +621,6 @@ pub enum LatchedState {
 | 
			
		||||
    Not,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl LatchedState {
 | 
			
		||||
    pub fn is_latched(&self) -> bool {
 | 
			
		||||
        self != &LatchedState::Not
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: split into sth like
 | 
			
		||||
// Arrangement (views) + details (keymap) + State (keys)
 | 
			
		||||
/// State of the UI, contains the backend as well
 | 
			
		||||
@ -777,23 +771,6 @@ impl Layout {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    pub fn get_locked_keys(&self) -> Vec<Rc<RefCell<KeyState>>> {
 | 
			
		||||
        let mut out = Vec::new();
 | 
			
		||||
        let view = self.get_current_view();
 | 
			
		||||
        for (_, row) in view.get_rows() {
 | 
			
		||||
            for (_, button) in &row.buttons {
 | 
			
		||||
                let locked = {
 | 
			
		||||
                    let state = RefCell::borrow(&button.state).clone();
 | 
			
		||||
                    state.action.is_locked(&self.current_view)
 | 
			
		||||
                };
 | 
			
		||||
                if locked {
 | 
			
		||||
                    out.push(button.state.clone());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        out
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn apply_view_transition(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        action: &Action,
 | 
			
		||||
 | 
			
		||||
@ -401,6 +401,7 @@ pub fn show(
 | 
			
		||||
        width: position.width.floor() as i32,
 | 
			
		||||
        height: position.width.floor() as i32,
 | 
			
		||||
    });
 | 
			
		||||
    menu.set_constrain_to(gtk::PopoverConstraint::None);
 | 
			
		||||
 | 
			
		||||
    if let Some(current_layout) = get_current_layout(manager, &system_layouts) {
 | 
			
		||||
        let current_name_variant = choices.iter()
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ use std::iter::FromIterator;
 | 
			
		||||
// and what a convenience layout. "_wide" is not a layout,
 | 
			
		||||
// neither is "number"
 | 
			
		||||
/// List of builtin layouts
 | 
			
		||||
const KEYBOARDS: &[(*const str, *const str)] = &[
 | 
			
		||||
static KEYBOARDS: &[(&'static str, &'static str)] = &[
 | 
			
		||||
    // layouts: us must be left as first, as it is the,
 | 
			
		||||
    // fallback layout.
 | 
			
		||||
    ("us", include_str!("../data/keyboards/us.yaml")),
 | 
			
		||||
@ -93,34 +93,20 @@ const KEYBOARDS: &[(*const str, *const str)] = &[
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
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 }
 | 
			
		||||
        })
 | 
			
		||||
    KEYBOARDS.iter().find(|(name, _)| *name == needle).map(|(_, layout)| *layout)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const OVERLAY_NAMES: &[*const str] = &[
 | 
			
		||||
static OVERLAY_NAMES: &[&'static str] = &[
 | 
			
		||||
    "emoji",
 | 
			
		||||
    "terminal",
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
pub fn get_overlays() -> Vec<&'static str> {
 | 
			
		||||
    OVERLAY_NAMES.iter()
 | 
			
		||||
        .map(|name| {
 | 
			
		||||
            let name: *const str = *name;
 | 
			
		||||
            unsafe { &*name }
 | 
			
		||||
        }).collect()
 | 
			
		||||
    OVERLAY_NAMES.to_vec()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Translations of the layout identifier strings
 | 
			
		||||
const LAYOUT_NAMES: &[(*const str, *const str)] = &[
 | 
			
		||||
static LAYOUT_NAMES: &[(&'static str, &'static str)] = &[
 | 
			
		||||
    ("de-DE", include_str!("../data/langs/de-DE.txt")),
 | 
			
		||||
    ("en-US", include_str!("../data/langs/en-US.txt")),
 | 
			
		||||
    ("es-ES", include_str!("../data/langs/es-ES.txt")),
 | 
			
		||||
@ -135,14 +121,8 @@ pub fn get_layout_names(lang: &str)
 | 
			
		||||
    -> Option<HashMap<&'static str, Translation<'static>>>
 | 
			
		||||
{
 | 
			
		||||
    let translations = LAYOUT_NAMES.iter()
 | 
			
		||||
        .find(|(name, _data)| {
 | 
			
		||||
            let name: *const str = *name;
 | 
			
		||||
            (unsafe { &*name }) == lang
 | 
			
		||||
        })
 | 
			
		||||
        .map(|(_name, data)| {
 | 
			
		||||
            let data: *const str = *data;
 | 
			
		||||
            unsafe { &*data }
 | 
			
		||||
        });
 | 
			
		||||
        .find(|(name, _data)| *name == lang)
 | 
			
		||||
        .map(|(_name, data)| *data);
 | 
			
		||||
    translations.map(make_mapping)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
/*! Testing functionality */
 | 
			
		||||
 | 
			
		||||
use ::data::Layout;
 | 
			
		||||
use ::data::parsing::Layout;
 | 
			
		||||
use ::logging;
 | 
			
		||||
use xkbcommon::xkb;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user