Compare commits

..

No commits in common. "me" and "main" have entirely different histories.
me ... main

7 changed files with 76 additions and 2075 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
Cargo.lock
target target

1867
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -17,7 +17,6 @@ libc = "0.2"
linux-raw-sys = { version = "0.9", features = ["ioctl"] } linux-raw-sys = { version = "0.9", features = ["ioctl"] }
memmap2 = "0.9" memmap2 = "0.9"
polling = "3" polling = "3"
xdg = "2.5.2"
reis = "0.4" reis = "0.4"
rgb = "0.8" rgb = "0.8"
signal-hook = "0.3" signal-hook = "0.3"
@ -34,21 +33,21 @@ zbus = { version = "5", default-features = false, features = ["tokio"] }
[build-dependencies] [build-dependencies]
bindgen = "0.71" bindgen = "0.71"
#[[bin]] [[bin]]
#name = "ufkbd-evfb" name = "ufkbd-evfb"
#path = "src/ufkbd_evfb.rs" path = "src/ufkbd_evfb.rs"
#[[bin]] [[bin]]
#name = "ufkbd-evfbo" name = "ufkbd-evfbo"
#path = "src/ufkbd_evfbo.rs" path = "src/ufkbd_evfbo.rs"
[[bin]] [[bin]]
name = "ufkbd-gnome" name = "ufkbd-gnome"
path = "src/ufkbd_gnome.rs" path = "src/ufkbd_gnome.rs"
#[[bin]] [[bin]]
#name = "ufkbd-kde" name = "ufkbd-kde"
#path = "src/ufkbd_kde.rs" path = "src/ufkbd_kde.rs"
[[bin]] [[bin]]
name = "ufkbd-sxmo" name = "ufkbd-sxmo"

View file

@ -23,5 +23,16 @@ fn main()
bindings.write_to_file(out_file).expect("Writing failure"); bindings.write_to_file(out_file).expect("Writing failure");
let builder = bindgen::builder();
let bindings = builder.header("/usr/include/linux/fb.h")
.generate()
.expect("The Linux headers must be installed");
let out_dir: PathBuf = env::var("OUT_DIR")
.expect("Environment variable $OUT_DIR must be defined")
.into();
let out_file = out_dir.join("linuxfb.rs");
bindings.write_to_file(out_file).expect("Writing failure");
} }

View file

@ -3,15 +3,15 @@
* Copyright (c) 2024, Richard Acayan. All rights reserved. * Copyright (c) 2024, Richard Acayan. All rights reserved.
*/ */
use std::convert::TryInto;
use std::fs::File;
use rgb::alt::BGR; use rgb::alt::BGR;
use rgb::alt::BGRA; use rgb::alt::BGRA;
use rgb::RGB; use rgb::RGB;
use std::convert::TryInto;
use std::fs::File;
use yaml_rust2::yaml::Hash; use yaml_rust2::yaml::Hash;
use yaml_rust2::yaml::LoadError; use yaml_rust2::yaml::LoadError;
use yaml_rust2::yaml::Yaml;
use yaml_rust2::yaml::YamlDecoder; use yaml_rust2::yaml::YamlDecoder;
use yaml_rust2::yaml::Yaml;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct KeyboardColors { pub struct KeyboardColors {
@ -25,7 +25,8 @@ pub struct KeyboardColors {
} }
impl Default for KeyboardColors { impl Default for KeyboardColors {
fn default() -> Self { fn default() -> Self
{
Self { Self {
background: RGB::new(0, 0, 0).into(), background: RGB::new(0, 0, 0).into(),
keycap: RGB::new(0, 0, 0).into(), keycap: RGB::new(0, 0, 0).into(),
@ -38,7 +39,8 @@ impl Default for KeyboardColors {
} }
} }
fn parse_color(yaml: &Yaml) -> Option<RGB<u8>> { fn parse_color(yaml: &Yaml) -> Option<RGB<u8>>
{
let mut color_bytes = yaml let mut color_bytes = yaml
.as_vec()? .as_vec()?
.iter() .iter()
@ -48,7 +50,7 @@ fn parse_color(yaml: &Yaml) -> Option<RGB<u8>> {
Some(RGB::new( Some(RGB::new(
color_bytes.next()?, color_bytes.next()?,
color_bytes.next()?, color_bytes.next()?,
color_bytes.next()?, color_bytes.next()?
)) ))
} }
@ -62,26 +64,12 @@ pub struct Configuration {
wayland_height: i32, wayland_height: i32,
wayland_im_enable: bool, wayland_im_enable: bool,
key_padding: u64,
key_corner_radius: u64,
colors: KeyboardColors, colors: KeyboardColors,
} }
impl Configuration { impl Configuration {
fn get_configuration_file() -> Result<File, Box<dyn std::error::Error>> { fn load_evfb(&mut self, yaml: &Hash)
let xdg_dirs = xdg::BaseDirectories::with_prefix("unfettered-keyboard")?; {
let config_file = xdg_dirs
.find_config_file("unfettered-keyboard.yaml")
.ok_or(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Config file not found",
))?;
println!("test{:?}", config_file);
Ok(File::open(config_file)?)
}
fn load_evfb(&mut self, yaml: &Hash) {
let height = yaml.get(&Yaml::String(String::from("height"))); let height = yaml.get(&Yaml::String(String::from("height")));
if let Some(height) = height { if let Some(height) = height {
let height = match height.as_f64() { let height = match height.as_f64() {
@ -93,7 +81,8 @@ impl Configuration {
} }
} }
fn load_wayland(&mut self, yaml: &Hash) { fn load_wayland(&mut self, yaml: &Hash)
{
let height = yaml.get(&Yaml::String(String::from("height"))); let height = yaml.get(&Yaml::String(String::from("height")));
if let Some(height) = height { if let Some(height) = height {
let height = match height.as_i64() { let height = match height.as_i64() {
@ -112,7 +101,8 @@ impl Configuration {
} }
} }
fn load_colors(&mut self, yaml: &Hash) { fn load_colors(&mut self, yaml: &Hash)
{
if let Some(keycap) = yaml.get(&Yaml::String(String::from("keycap"))) { if let Some(keycap) = yaml.get(&Yaml::String(String::from("keycap"))) {
self.colors.keycap = parse_color(keycap) self.colors.keycap = parse_color(keycap)
.expect("Keycap color needs to be a list of 3 8-bit unsigned integers") .expect("Keycap color needs to be a list of 3 8-bit unsigned integers")
@ -150,27 +140,23 @@ impl Configuration {
} }
} }
pub fn load() -> Result<Self, LoadError> { pub fn load() -> Result<Self, LoadError>
{
let mut cfg = Configuration { let mut cfg = Configuration {
longpress: 600, longpress: 600,
repeat: 25, repeat: 25,
layout: String::from("latn_qwerty_us.xml"), layout: String::from("latn_qwerty_us.xml"),
extra_keys: vec![String::from("alt"), String::from("meta")], extra_keys: vec![ String::from("alt"), String::from("meta") ],
wayland_height: 185, wayland_height: 185,
wayland_im_enable: true, wayland_im_enable: true,
evfb_height: 0.25, evfb_height: 0.25,
key_padding: 2,
key_corner_radius: 5,
colors: KeyboardColors::default(), colors: KeyboardColors::default(),
}; };
if let Ok(file) = Self::get_configuration_file() { if let Ok(file) = File::open("/etc/unfettered-keyboard.yaml") {
let yaml = YamlDecoder::read(file).decode()?; let yaml = YamlDecoder::read(file).decode()?;
let yaml = yaml[0] let yaml = yaml[0].as_hash().expect("Top-level configuration should be a YAML mapping");
.as_hash()
.expect("Top-level configuration should be a YAML mapping");
let longpress = yaml.get(&Yaml::String(String::from("longpress_ms"))); let longpress = yaml.get(&Yaml::String(String::from("longpress_ms")));
if let Some(longpress) = longpress { if let Some(longpress) = longpress {
@ -178,8 +164,7 @@ impl Configuration {
Some(l) => l.try_into().ok(), Some(l) => l.try_into().ok(),
None => None, None => None,
}; };
let longpress = let longpress = longpress.expect("Longpress time should be a 64-bit unsigned integer");
longpress.expect("Longpress time should be a 64-bit unsigned integer");
cfg.longpress = longpress; cfg.longpress = longpress;
} }
@ -202,56 +187,25 @@ impl Configuration {
let keys = yaml.get(&Yaml::String(String::from("extra_keys"))); let keys = yaml.get(&Yaml::String(String::from("extra_keys")));
if let Some(keys) = keys { if let Some(keys) = keys {
let keys = keys.as_vec().expect("Extra keys should be a list"); let keys = keys.as_vec().expect("Extra keys should be a list");
cfg.extra_keys = keys cfg.extra_keys = keys.iter().map(|y| String::from(y.as_str().unwrap())).collect();
.iter()
.map(|y| String::from(y.as_str().unwrap()))
.collect();
cfg.extra_keys.sort_unstable(); cfg.extra_keys.sort_unstable();
} }
let wl = yaml.get(&Yaml::String(String::from("wayland"))); let wl = yaml.get(&Yaml::String(String::from("wayland")));
if let Some(wl) = wl { if let Some(wl) = wl {
let wl = wl let wl = wl.as_hash().expect("Wayland configuration should be a YAML mapping");
.as_hash()
.expect("Wayland configuration should be a YAML mapping");
cfg.load_wayland(wl); cfg.load_wayland(wl);
} }
let evfb = yaml.get(&Yaml::String(String::from("evfb"))); let evfb = yaml.get(&Yaml::String(String::from("evfb")));
if let Some(evfb) = evfb { if let Some(evfb) = evfb {
let evfb = evfb let evfb = evfb.as_hash().expect("Linux evdev-fbdev configuration should be a YAML mapping");
.as_hash()
.expect("Linux evdev-fbdev configuration should be a YAML mapping");
cfg.load_evfb(evfb); cfg.load_evfb(evfb);
} }
let key_padding = yaml.get(&Yaml::String(String::from("key_padding")));
if let Some(key_padding) = key_padding {
let key_padding = match key_padding.as_i64() {
Some(l) => l.try_into().ok(),
None => None,
};
let key_padding =
key_padding.expect("Key padding should be a 64-bit unsigned integer");
cfg.key_padding = key_padding;
}
let key_corner_radius = yaml.get(&Yaml::String(String::from("key_corner_radius")));
if let Some(key_corner_radius) = key_corner_radius {
let key_corner_radius = match key_corner_radius.as_i64() {
Some(l) => l.try_into().ok(),
None => None,
};
let key_corner_radius = key_corner_radius
.expect("Key corner radius should be a 64-bit unsigned integer");
cfg.key_corner_radius = key_corner_radius;
}
let colors = yaml.get(&Yaml::String(String::from("colors"))); let colors = yaml.get(&Yaml::String(String::from("colors")));
if let Some(colors) = colors { if let Some(colors) = colors {
let colors = colors let colors = colors.as_hash().expect("Color configuration should be a YAML mapping");
.as_hash()
.expect("Color configuration should be a YAML mapping");
cfg.load_colors(colors); cfg.load_colors(colors);
} }
} }
@ -260,52 +214,50 @@ impl Configuration {
} }
#[inline(always)] #[inline(always)]
pub fn longpress_ms(&self) -> u64 { pub fn longpress_ms(&self) -> u64
{
self.longpress self.longpress
} }
#[inline(always)] #[inline(always)]
pub fn repeat_ms(&self) -> u64 { pub fn repeat_ms(&self) -> u64
{
self.repeat self.repeat
} }
#[inline(always)] #[inline(always)]
pub fn layout(&self) -> &str { pub fn layout(&self) -> &str
{
&self.layout &self.layout
} }
#[inline(always)] #[inline(always)]
pub fn extra_keys(&self) -> &Vec<String> { pub fn extra_keys(&self) -> &Vec<String>
{
&self.extra_keys &self.extra_keys
} }
#[inline(always)] #[inline(always)]
pub fn wayland_height(&self) -> i32 { pub fn wayland_height(&self) -> i32
{
self.wayland_height self.wayland_height
} }
#[inline(always)] #[inline(always)]
pub fn wayland_im_enable(&self) -> bool { pub fn wayland_im_enable(&self) -> bool
{
self.wayland_im_enable self.wayland_im_enable
} }
#[inline(always)] #[inline(always)]
pub fn evfb_height(&self) -> f64 { pub fn evfb_height(&self) -> f64
{
self.evfb_height self.evfb_height
} }
#[inline(always)] #[inline(always)]
pub fn colors(&self) -> &KeyboardColors { pub fn colors(&self) -> &KeyboardColors
{
&self.colors &self.colors
} }
#[inline(always)]
pub fn key_padding(&self) -> u64 {
self.key_padding
}
#[inline(always)]
pub fn key_corner_radius(&self) -> u64 {
self.key_corner_radius
}
} }

View file

@ -49,70 +49,6 @@ fn fill_image(dest: &mut ImgRefMut<BGRA<u8>>, src: BGRA<u8>)
} }
} }
fn distance(x1: usize, y1: usize, x2: usize, y2: usize) -> f32
{
(((x1 as isize - x2 as isize).pow(2) + (y1 as isize - y2 as isize).pow(2)) as f32).sqrt()
}
fn is_point_in_rounded_rectangle(
x: usize,
y: usize,
width: usize,
height: usize,
radius: usize,
) -> bool
{
// Check if the point is within the rectangle's main area
if x >= radius && x <= width - radius && y <= height {
return true;
}
if x <= width && y >= radius && y <= height - radius {
return true;
}
// Check the corners
// Top-left corner
if distance(x, y, radius, radius) < radius as f32 {
return true;
}
// Top-right corner
if distance(x, y, width - radius, radius) < radius as f32 {
return true;
}
// Bottom-left corner
if distance(x, y, radius, height - radius) < radius as f32 {
return true;
}
// Bottom-right corner
if distance(x, y, width - radius, height - radius) < radius as f32 {
return true;
}
false
}
fn rounded_rectangle(dest: &mut ImgRefMut<BGRA<u8>>, src: BGRA<u8>, radius: usize)
{
let (width, height) = (dest.width(), dest.height());
for (y, row) in dest.rows_mut().enumerate() {
for (x, dest) in row.iter_mut().enumerate() {
if is_point_in_rounded_rectangle(x, y, width, height, radius) {
*dest = src;
}
}
}
}
fn bitmap_to_imgref(bitmap: &freetype::FT_Bitmap) -> ImgRef<u8> fn bitmap_to_imgref(bitmap: &freetype::FT_Bitmap) -> ImgRef<u8>
{ {
unsafe { unsafe {
@ -157,9 +93,6 @@ pub struct Graphics<D: Display> {
x_scale: f64, x_scale: f64,
y_scale: f64, y_scale: f64,
key_padding: u64,
key_corner_radius: u64,
colors: KeyboardColors, colors: KeyboardColors,
} }
@ -284,9 +217,6 @@ impl<D: Display> Graphics<D> {
x_scale: 0.0, x_scale: 0.0,
y_scale: 0.0, y_scale: 0.0,
key_padding: cfg.key_padding(),
key_corner_radius: cfg.key_corner_radius(),
colors: cfg.colors().clone(), colors: cfg.colors().clone(),
} }
} }
@ -543,9 +473,6 @@ impl<D: Display> Graphics<D> {
let src = labels.get(label) let src = labels.get(label)
.expect("Layout and size should be set before drawing"); .expect("Layout and size should be set before drawing");
assert!(src.img.height() > src.y, "Not enough space to draw label, reduce padding and/or corner radius");
let width = src.img.width(); let width = src.img.width();
let height = src.img.height() - src.y; let height = src.img.height() - src.y;
@ -606,13 +533,12 @@ impl<D: Display> Graphics<D> {
x_scale: f64, y_scale: f64, x_scale: f64, y_scale: f64,
img: &mut ImgRefMut<BGRA<u8>>, img: &mut ImgRefMut<BGRA<u8>>,
key: &Key, mod_state: &[ModState], key: &Key, mod_state: &[ModState],
key_padding: usize, key_corner_radius: usize,
colors: &KeyboardColors) colors: &KeyboardColors)
{ {
let x1 = (key.x1 * x_scale) as usize + key_padding; let x1 = (key.x1 * x_scale) as usize;
let y1 = (key.y1 * y_scale) as usize + key_padding; let y1 = (key.y1 * y_scale) as usize;
let x2 = (key.x2 * x_scale) as usize - key_padding; let x2 = (key.x2 * x_scale) as usize;
let y2 = (key.y2 * y_scale) as usize - key_padding; let y2 = (key.y2 * y_scale) as usize;
let mut keyimg = img.sub_image_mut(x1, y1, x2 - x1, y2 - y1); let mut keyimg = img.sub_image_mut(x1, y1, x2 - x1, y2 - y1);
@ -631,30 +557,21 @@ impl<D: Display> Graphics<D> {
} }
if pressed { if pressed {
rounded_rectangle(&mut keyimg, colors.keycap_pressed, key_corner_radius); fill_image(&mut keyimg, colors.keycap_pressed);
} else { } else {
rounded_rectangle(&mut keyimg, colors.keycap, key_corner_radius); fill_image(&mut keyimg, colors.keycap);
} }
let half_radius = key_corner_radius / 2;
let mut labelimg = keyimg.sub_image_mut(
half_radius,
half_radius,
keyimg.width() - half_radius * 2,
keyimg.height() - half_radius * 2
);
let color = Self::keypart_to_color(&key.parts[0], false, mod_state, colors); let color = Self::keypart_to_color(&key.parts[0], false, mod_state, colors);
let label = key.parts[0].display_label(); let label = key.parts[0].display_label();
Self::draw_label_part(labels, Anchor::Center, Anchor::Center, Self::draw_label_part(labels, Anchor::Center, Anchor::Center,
&mut labelimg, label, color.map(|c| c as f32 / 255.)); &mut keyimg, label, color.map(|c| c as f32 / 255.));
for i in 0..8 { for i in 0..8 {
let color = Self::keypart_to_color(&key.parts[i + 1], true, mod_state, colors); let color = Self::keypart_to_color(&key.parts[i + 1], true, mod_state, colors);
let label = key.parts[i + 1].display_label(); let label = key.parts[i + 1].display_label();
Self::draw_label_part(sublabels, ANCHORS[i].0, ANCHORS[i].1, Self::draw_label_part(sublabels, ANCHORS[i].0, ANCHORS[i].1,
&mut labelimg, label, color.map(|c| c as f32 / 255.)); &mut keyimg, label, color.map(|c| c as f32 / 255.));
} }
} }
@ -669,9 +586,7 @@ impl<D: Display> Graphics<D> {
Self::draw_key(&self.labels, &self.sublabels, Self::draw_key(&self.labels, &self.sublabels,
self.x_scale, self.y_scale, self.x_scale, self.y_scale,
&mut img, key, mod_state, &mut img, key, mod_state, &self.colors);
self.key_padding as usize, self.key_corner_radius as usize,
&self.colors);
self.disp.end(x1, y1, x2 - x1, y2 - y1); self.disp.end(x1, y1, x2 - x1, y2 - y1);
} }
@ -702,9 +617,7 @@ impl<D: Display> Graphics<D> {
for key in row { for key in row {
Self::draw_key(&self.labels, &self.sublabels, Self::draw_key(&self.labels, &self.sublabels,
self.x_scale, self.y_scale, self.x_scale, self.y_scale,
&mut img, key, mod_state, &mut img, key, mod_state, &self.colors);
self.key_padding as usize, self.key_corner_radius as usize,
&self.colors);
} }
} }

View file

@ -19,7 +19,6 @@ use std::os::raw::c_char;
use std::os::raw::c_void; use std::os::raw::c_void;
use std::path::Path; use std::path::Path;
use std::ptr; use std::ptr;
use xdg::BaseDirectories;
use xkeysym::Keysym; use xkeysym::Keysym;
unsafe extern "C" fn start_elem(data: *mut c_void, unsafe extern "C" fn start_elem(data: *mut c_void,
@ -726,25 +725,18 @@ impl Layout {
pub fn load(cfg: &Configuration) -> Result<Self, Error> pub fn load(cfg: &Configuration) -> Result<Self, Error>
{ {
let extra_keys = cfg.extra_keys(); let extra_keys = cfg.extra_keys();
let dir = Path::new("/usr/share/unfettered-keyboard/layouts");
let base_dirs = BaseDirectories::with_prefix("unfettered-keyboard/layouts")?; let path = dir.join(cfg.layout());
println!("{:?}",base_dirs);
let path = base_dirs.find_config_file(cfg.layout())
.ok_or(Error::new(ErrorKind::NotFound, "Main layout file not found"))?;
let main_layout = LayoutLoader::load(path, extra_keys)?; let main_layout = LayoutLoader::load(path, extra_keys)?;
let path = base_dirs.find_config_file("numeric.xml") let path = dir.join("numeric.xml");
.ok_or(Error::new(ErrorKind::NotFound, "Numeric layout file not found"))?;
let numeric = LayoutLoader::load(path, extra_keys)?; let numeric = LayoutLoader::load(path, extra_keys)?;
let path = base_dirs.find_config_file("greekmath.xml") let path = dir.join("greekmath.xml");
.ok_or(Error::new(ErrorKind::NotFound, "Greekmath layout file not found"))?;
let greekmath = LayoutLoader::load(path, extra_keys)?; let greekmath = LayoutLoader::load(path, extra_keys)?;
let path = base_dirs.find_config_file("bottom_row.xml") let path = dir.join("bottom_row.xml");
.ok_or(Error::new(ErrorKind::NotFound, "Bottom row layout file not found"))?;
let bottom_row = LayoutLoader::load(path, extra_keys)?; let bottom_row = LayoutLoader::load(path, extra_keys)?;
let mut layout = Layout { let mut layout = Layout {