rewrite in rust

This commit is contained in:
Richard Acayan 2024-07-18 20:32:55 -04:00
parent d43f61ec76
commit 4a6b261be0
51 changed files with 3104 additions and 9319 deletions

528
src/core/button.rs Normal file
View file

@ -0,0 +1,528 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Copyright (c) 2024, Richard Acayan. All rights reserved.
*/
use core::Display;
use core::Graphics;
use core::Layout;
use core::layout::Key;
use core::layout::MODIFIERS_MAX;
use core::xkeysym::Keysym;
use std::collections::VecDeque;
use std::convert::TryInto;
use std::iter;
use std::ops::Add;
use std::sync::Arc;
use std::sync::Mutex;
use std::time::Duration;
use std::time::Instant;
/*
* The available states for the modifier keys.
*
* The state goes Released -> Pressed when the modifier key is pressed.
*
* When pressed, the modifier can be released, going Pressed -> Latched so that
* the modifier will apply to the next key press.
* It could be held down for long enough that it goes Pressed -> Locked, and the
* modifier would apply to any other key press until it is released, pressed and
* released again.
* Finally, it could be held down while another key is being pressed, going
* Pressed -> Held (or Locked -> HeldLocked) to apply until it is released.
*
* When latched, it could go Latched -> Released when any key
* (including itself) is released.
*
* When held, it goes Held -> Released (or HeldLocked -> Released) when the
* modifier key is released.
*
* When locked, the modifier key goes Locked -> HeldLocked when it is pressed
* again.
*/
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ModState {
Released,
Pressed,
Latched,
Held,
Locked,
HeldLocked,
}
#[derive(PartialEq)]
enum DrawOperation {
Key,
Modifiers([bool; MODIFIERS_MAX]),
Labels,
}
impl DrawOperation {
fn from_modifier(modifier: usize) -> DrawOperation
{
let mut modifiers = [false; MODIFIERS_MAX];
modifiers[modifier - 1] = true;
DrawOperation::Modifiers(modifiers)
}
fn from_modifier_edge(modifier: usize) -> DrawOperation
{
if Layout::is_label_modifier(modifier) {
DrawOperation::Labels
} else {
DrawOperation::from_modifier(modifier)
}
}
}
impl Add for DrawOperation {
type Output = Self;
fn add(self, other: Self) -> Self
{
match (&self, &other) {
(&DrawOperation::Labels, _) => self,
(_, &DrawOperation::Labels) => other,
(&DrawOperation::Key, &DrawOperation::Modifiers(_)) => other,
(&DrawOperation::Modifiers(_), &DrawOperation::Key) => self,
(&DrawOperation::Modifiers(a), &DrawOperation::Modifiers(b)) => {
let mods = iter::zip(&a, &b).map(|(a, b)| *a || *b);
DrawOperation::Modifiers(mods.collect::<Vec<_>>().try_into().unwrap())
},
(&DrawOperation::Key, &DrawOperation::Key) => self,
}
}
}
struct Press {
x1: f64,
y1: f64,
part: usize,
timer: Instant,
}
const PRESSES_MAX: usize = 64;
pub trait Keyboard {
fn press(&mut self, sym: Keysym);
fn release(&mut self, sym: Keysym);
fn change_layout(&mut self, layout: &Layout);
}
pub struct Button<D: Display, K: Keyboard> {
layout: Layout,
kbd: K,
gfx: Arc<Mutex<Graphics<D>>>,
presses: [Option<Press>; PRESSES_MAX],
timers: VecDeque<usize>,
modifiers: [ModState; MODIFIERS_MAX],
longpress: Duration,
repeat: Duration,
r_square: f64,
}
const PART_TL: usize = 1;
const PART_TR: usize = 2;
const PART_BL: usize = 3;
const PART_BR: usize = 4;
const PART_LEFT: usize = 5;
const PART_RIGHT: usize = 6;
const PART_TOP: usize = 7;
const PART_BOTTOM: usize = 8;
const ANGLE_PARTS: [[usize; 4]; 16] = [
[PART_LEFT, PART_TL, PART_BL, PART_TOP],
[PART_TL, PART_LEFT, PART_TOP, PART_BL],
[PART_TL, PART_TOP, PART_LEFT, PART_TR],
[PART_TOP, PART_TL, PART_TR, PART_LEFT],
[PART_TOP, PART_TR, PART_TL, PART_RIGHT],
[PART_TR, PART_TOP, PART_RIGHT, PART_TL],
[PART_TR, PART_RIGHT, PART_TOP, PART_BR],
[PART_RIGHT, PART_TR, PART_BR, PART_TOP],
[PART_RIGHT, PART_BR, PART_TR, PART_BOTTOM],
[PART_BR, PART_RIGHT, PART_BOTTOM, PART_TR],
[PART_BR, PART_BOTTOM, PART_RIGHT, PART_BL],
[PART_BOTTOM, PART_BR, PART_BL, PART_RIGHT],
[PART_BOTTOM, PART_BL, PART_BR, PART_LEFT],
[PART_BL, PART_BOTTOM, PART_LEFT, PART_BR],
[PART_BL, PART_LEFT, PART_BOTTOM, PART_TL],
[PART_LEFT, PART_BL, PART_TL, PART_BOTTOM],
];
// The None option cannot be copied, but it works if it is a constant. Rust is insane.
const NO_PRESS: Option<Press> = None;
impl<D: Display, K: Keyboard> Button<D, K> {
pub fn new(layout: Layout, mut kbd: K,
gfx: Arc<Mutex<Graphics<D>>>) -> Button<D, K>
{
kbd.change_layout(&layout);
Button {
layout,
kbd,
gfx,
presses: [NO_PRESS; PRESSES_MAX],
modifiers: [ModState::Released; MODIFIERS_MAX],
timers: VecDeque::with_capacity(PRESSES_MAX),
longpress: Duration::from_millis(600),
repeat: Duration::from_millis(25),
r_square: 0.25,
}
}
#[inline(always)]
pub fn layout(&self) -> &Layout
{
&self.layout
}
pub fn mod_state(&self) -> &[ModState; MODIFIERS_MAX]
{
&self.modifiers
}
pub fn next_time(&self) -> Option<Instant>
{
let id = *self.timers.front()?;
if id == PRESSES_MAX {
return None;
}
// The next timer must always be held by an active key press.
Some(self.presses[id].as_ref().unwrap().timer)
}
fn update_modifier(&mut self, modifier: usize, old: ModState, new: ModState) -> bool
{
if self.modifiers[modifier - 1] == old {
self.modifiers[modifier - 1] = new;
if new == ModState::Released {
self.kbd.release(Layout::modifier_keysym(modifier));
}
if old == ModState::Released || new == ModState::Released {
self.layout.update_modifiers(&self.modifiers);
if Layout::is_keysym_modifier(modifier) {
self.kbd.change_layout(&self.layout);
}
}
if old == ModState::Released {
self.kbd.press(Layout::modifier_keysym(modifier));
}
true
} else {
false
}
}
fn try_press_mod(&mut self, modifier: usize) -> DrawOperation
{
let mut draw = DrawOperation::Key;
if modifier != 0 {
let changed = self.update_modifier(modifier,
ModState::Released,
ModState::Pressed);
if changed {
draw = draw + DrawOperation::from_modifier_edge(modifier);
}
self.update_modifier(modifier,
ModState::Latched,
ModState::Held);
self.update_modifier(modifier,
ModState::Locked,
ModState::HeldLocked);
} else {
for modifier in 1..=MODIFIERS_MAX {
self.update_modifier(modifier,
ModState::Pressed,
ModState::Held);
self.update_modifier(modifier,
ModState::Locked,
ModState::HeldLocked);
}
}
draw
}
fn draw(op: DrawOperation,
gfx: &Arc<Mutex<Graphics<D>>>,
layout: &Layout,
key: Option<&Key>,
mod_state: &[ModState])
{
let mut gfx = gfx.lock().unwrap();
match op {
DrawOperation::Key => {
if let Some(key) = key {
gfx.draw_single(key, mod_state);
}
},
DrawOperation::Modifiers(m) => {
gfx.draw_modifiers(layout, mod_state, &m);
if let Some(key) = key {
gfx.draw_single(key, mod_state);
}
},
DrawOperation::Labels => {
gfx.change_layout(layout, mod_state);
}
}
}
pub fn press(&mut self, id: usize, x: f64, y: f64)
{
if self.presses[id].is_some() {
eprintln!("warn: pressed same ID twice before releasing");
return;
}
let key = match self.layout.locate_key_mut(x, y) {
Some(k) => k,
// Ignore the press if it is not for a key.
None => return,
};
key.parts[0].press();
let modifier = key.parts[0].modifier_id();
let draw = self.try_press_mod(modifier);
let timer = Instant::now() + self.longpress;
self.presses[id] = Some(Press {
x1: x,
y1: y,
part: 0,
timer,
});
self.timers.push_back(id);
let key = self.layout.locate_key(x, y).unwrap();
Self::draw(draw, &self.gfx, &self.layout, Some(key), &self.modifiers);
}
fn update_pressed_part(press: &mut Press, key: &Key, angle: usize)
{
if key.parts[ANGLE_PARTS[angle][0]].sym() != Keysym::NoSymbol {
press.part = ANGLE_PARTS[angle][0];
} else if key.parts[ANGLE_PARTS[angle][1]].sym() != Keysym::NoSymbol {
press.part = ANGLE_PARTS[angle][1];
} else if key.parts[ANGLE_PARTS[angle][2]].sym() != Keysym::NoSymbol {
press.part = ANGLE_PARTS[angle][2];
} else if key.parts[ANGLE_PARTS[angle][3]].sym() != Keysym::NoSymbol {
press.part = ANGLE_PARTS[angle][3];
}
}
fn remove_timer(&mut self, id: usize)
{
for evt in self.timers.iter_mut() {
if *evt == id {
*evt = PRESSES_MAX;
break;
}
}
while self.timers.front() == Some(&PRESSES_MAX) {
self.timers.pop_front();
}
}
fn try_cancel_mod(&mut self, modifier: usize) -> DrawOperation
{
let mut draw = DrawOperation::Key;
if modifier != 0 {
let changed = self.update_modifier(modifier,
ModState::Pressed,
ModState::Released);
if changed && Layout::is_label_modifier(modifier) {
draw = draw + DrawOperation::Labels;
}
let changed = self.update_modifier(modifier,
ModState::Locked,
ModState::Released);
if changed && Layout::is_label_modifier(modifier) {
draw = draw + DrawOperation::Labels;
}
self.update_modifier(modifier,
ModState::Held,
ModState::Latched);
self.update_modifier(modifier,
ModState::HeldLocked,
ModState::Locked);
}
draw
}
pub fn pos(&mut self, id: usize, x: f64, y: f64)
{
let press = match &mut self.presses[id] {
Some(p) => p,
None => return,
};
let (dx, dy) = (x - press.x1, y - press.y1);
if dx * dx + dy * dy < self.r_square {
// We only need to make changes when the key is being dragged.
return;
}
let angle = dy.atan2(dx) * 8.0 / std::f64::consts::PI + 8.0;
let angle = angle.clamp(0.0, 15.0) as usize;
let key = self.layout.locate_key_mut(press.x1, press.y1).unwrap();
let old_part = press.part;
Self::update_pressed_part(press, key, angle);
let new_part = press.part;
if new_part == old_part {
return;
}
key.parts[old_part].release();
key.parts[new_part].press();
let modifier1 = key.parts[old_part].modifier_id();
let modifier2 = key.parts[new_part].modifier_id();
press.timer = Instant::now() + self.longpress;
self.remove_timer(id);
self.timers.push_back(id);
let mut draw = self.try_cancel_mod(modifier1);
draw = draw + self.try_press_mod(modifier2);
let press = self.presses[id].as_ref().unwrap();
let key = self.layout.locate_key(press.x1, press.y1).unwrap();
Self::draw(draw, &self.gfx, &self.layout, Some(key), &self.modifiers);
}
/*
* Release the given modifier key. If the key being pressed is not a
* modifier key (i.e. modifier = 0), release all latched modifier keys.
*/
fn release_mod(&mut self, modifier: usize) -> DrawOperation
{
let mut draw = DrawOperation::Key;
if modifier != 0 {
self.update_modifier(modifier,
ModState::Pressed,
ModState::Latched);
let changed = self.update_modifier(modifier,
ModState::Held,
ModState::Released);
if changed && Layout::is_label_modifier(modifier) {
draw = draw + DrawOperation::Labels;
}
let changed = self.update_modifier(modifier,
ModState::HeldLocked,
ModState::Released);
if changed && Layout::is_label_modifier(modifier) {
draw = draw + DrawOperation::Labels;
}
} else {
for modifier in 1..=MODIFIERS_MAX {
let changed = self.update_modifier(modifier,
ModState::Latched,
ModState::Released);
if changed {
draw = draw + DrawOperation::from_modifier_edge(modifier);
}
}
}
draw
}
pub fn release(&mut self, id: usize)
{
let press = match &mut self.presses[id] {
Some(p) => p,
None => return,
};
let key = self.layout.locate_key_mut(press.x1, press.y1).unwrap();
key.parts[press.part].release();
let modifier = key.parts[press.part].modifier_id();
if modifier == 0 {
self.kbd.press(key.parts[press.part].sym());
self.kbd.release(key.parts[press.part].sym());
}
let draw = self.release_mod(modifier);
let press = self.presses[id].as_ref().unwrap();
let key = self.layout.locate_key(press.x1, press.y1).unwrap();
Self::draw(draw, &self.gfx, &self.layout, Some(key), &self.modifiers);
self.remove_timer(id);
self.presses[id] = None;
}
fn lock_mod(&mut self, modifier: usize) -> DrawOperation
{
let changed = self.update_modifier(modifier, ModState::Pressed, ModState::Locked);
if changed {
DrawOperation::from_modifier(modifier)
} else {
DrawOperation::Key
}
}
pub fn dispatch_timers(&mut self)
{
let mut draw = DrawOperation::Key;
while !self.timers.is_empty() {
let id = *self.timers.front().unwrap();
if id == PRESSES_MAX {
self.timers.pop_front();
continue;
}
let press = self.presses[id].as_mut().unwrap();
if Instant::now() < press.timer {
break;
}
self.timers.pop_front();
let key = self.layout.locate_key(press.x1, press.y1).unwrap();
let modifier = key.parts[press.part].modifier_id();
if modifier != 0 {
draw = draw + self.lock_mod(modifier);
} else {
self.kbd.press(key.parts[press.part].sym());
self.kbd.release(key.parts[press.part].sym());
press.timer += self.repeat;
self.timers.push_back(id);
}
}
Self::draw(draw, &self.gfx, &self.layout, None, &self.modifiers);
}
}

6
src/core/expat.rs Normal file
View file

@ -0,0 +1,6 @@
#![allow(dead_code,
improper_ctypes,
non_camel_case_types,
non_snake_case,
non_upper_case_globals)]
include!(concat!(env!("OUT_DIR"), "/expat.rs"));

694
src/core/graphics.rs Normal file
View file

@ -0,0 +1,694 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Copyright (c) 2024, Richard Acayan. All rights reserved.
*/
use core::button::ModState;
use core::fontconfig;
use core::freetype::freetype;
use core::imgref::ImgRefMut;
use core::imgref::ImgRef;
use core::imgref::ImgVec;
use core::Layout;
use core::layout::Key;
use core::layout::Part;
use core::rgb::alt::BGR;
use core::rgb::alt::BGRA;
use core::xkeysym::Keysym;
use std::collections::HashMap;
use std::convert::TryInto;
use std::fs::File;
use std::io::Read;
use std::iter;
use std::ptr;
fn convert_gray_to_bgrx(mut dest: ImgRefMut<BGRA<u8>>, src: ImgRef<u8>, fg_color: BGR<f32>)
{
for (dest, src) in iter::zip(dest.rows_mut(), src.rows()) {
for (dest, src) in iter::zip(dest, src) {
dest.r = ((dest.r as f32 + *src as f32 * fg_color.r) as u16).try_into().unwrap_or(255);
dest.g = ((dest.g as f32 + *src as f32 * fg_color.g) as u16).try_into().unwrap_or(255);
dest.b = ((dest.b as f32 + *src as f32 * fg_color.b) as u16).try_into().unwrap_or(255);
}
}
}
fn copy_image(mut dest: ImgRefMut<u8>, src: ImgRef<u8>)
{
for (dest, src) in iter::zip(dest.pixels_mut(), src.pixels()) {
*dest = (src as u16 + *dest as u16).try_into().unwrap_or(255);
}
}
fn fill_image(dest: &mut ImgRefMut<BGRA<u8>>, src: BGRA<u8>)
{
for dest in dest.pixels_mut() {
*dest = src;
}
}
fn bitmap_to_imgref(bitmap: &freetype::FT_Bitmap) -> ImgRef<u8>
{
unsafe {
let buf = &*ptr::slice_from_raw_parts(bitmap.buffer,
bitmap.pitch as usize
* bitmap.rows as usize);
ImgRef::new_stride(buf,
bitmap.width as usize,
bitmap.rows as usize,
bitmap.pitch as usize)
}
}
#[derive(Clone, Copy)]
enum Anchor {
Min,
Center,
Max,
}
pub trait Display {
fn size(&self) -> (u32, u32);
fn begin(&mut self) -> ImgRefMut<BGRA<u8>>;
fn resize(&mut self, width: u32, height: u32);
fn end(&mut self, x: u32, y: u32, width: u32, height: u32);
}
pub struct Graphics<D: Display> {
disp: D,
fonts: Vec<(String, freetype::FT_Long)>,
fontbufs: Vec<Vec<u8>>,
labels: HashMap<Keysym, ImgVec<u8>>,
sublabels: HashMap<Keysym, ImgVec<u8>>,
ft: freetype::FT_Library,
x_scale: f64,
y_scale: f64,
}
struct Label<'a> {
sym: Keysym,
label: &'a str,
secondary: bool,
small: bool,
}
const LABELS: [Label; 43] = [
Label { sym: Keysym::Greek_MU, label: "\u{039C}",
secondary: false, small: false, },
Label { sym: Keysym::emdash, label: "\u{2014}",
secondary: false, small: false, },
Label { sym: Keysym::endash, label: "\u{2013}",
secondary: false, small: false, },
Label { sym: Keysym::ellipsis, label: "\u{2026}",
secondary: false, small: false, },
Label { sym: Keysym::leftsinglequotemark, label: "\u{2018}",
secondary: false, small: false, },
Label { sym: Keysym::rightsinglequotemark, label: "\u{2019}",
secondary: false, small: false, },
Label { sym: Keysym::leftdoublequotemark, label: "\u{201C}",
secondary: false, small: false, },
Label { sym: Keysym::rightdoublequotemark, label: "\u{201D}",
secondary: false, small: false, },
Label { sym: Keysym::permille, label: "\u{2030}",
secondary: false, small: false, },
Label { sym: Keysym::singlelowquotemark, label: "\u{201A}",
secondary: false, small: false, },
Label { sym: Keysym::doublelowquotemark, label: "\u{201E}",
secondary: false, small: false, },
Label { sym: Keysym::OE, label: "\u{0152}",
secondary: false, small: false, },
Label { sym: Keysym::oe, label: "\u{0153}",
secondary: false, small: false, },
Label { sym: Keysym::BackSpace, label: "\u{232B}",
secondary: true, small: false, },
Label { sym: Keysym::Tab, label: "\u{2B7E}",
secondary: true, small: true, },
Label { sym: Keysym::Return, label: "\u{23CE}",
secondary: true, small: false, },
Label { sym: Keysym::Escape, label: "Esc",
secondary: true, small: true, },
Label { sym: Keysym::Home, label: "\u{21E4}",
secondary: true, small: false, },
Label { sym: Keysym::Left, label: "\u{2190}",
secondary: true, small: false, },
Label { sym: Keysym::Up, label: "\u{2191}",
secondary: true, small: false, },
Label { sym: Keysym::Right, label: "\u{2192}",
secondary: true, small: false, },
Label { sym: Keysym::Down, label: "\u{2193}",
secondary: true, small: false, },
Label { sym: Keysym::Page_Up, label: "\u{21D1}",
secondary: true, small: false, },
Label { sym: Keysym::Page_Down, label: "\u{21D3}",
secondary: true, small: false, },
Label { sym: Keysym::End, label: "\u{21E5}",
secondary: true, small: false, },
Label { sym: Keysym::Insert, label: "Ins",
secondary: true, small: true, },
Label { sym: Keysym::F1, label: "F1",
secondary: false, small: false, },
Label { sym: Keysym::F2, label: "F2",
secondary: false, small: false, },
Label { sym: Keysym::F3, label: "F3",
secondary: false, small: false, },
Label { sym: Keysym::F4, label: "F4",
secondary: false, small: false, },
Label { sym: Keysym::F5, label: "F5",
secondary: false, small: false, },
Label { sym: Keysym::F6, label: "F6",
secondary: false, small: false, },
Label { sym: Keysym::F7, label: "F7",
secondary: false, small: false, },
Label { sym: Keysym::F8, label: "F8",
secondary: false, small: false, },
Label { sym: Keysym::F9, label: "F9",
secondary: false, small: false, },
Label { sym: Keysym::F10, label: "F10",
secondary: false, small: false, },
Label { sym: Keysym::F11, label: "F11",
secondary: false, small: true, },
Label { sym: Keysym::F12, label: "F12",
secondary: false, small: true, },
Label { sym: Keysym::Shift_L, label: "\u{21E7}",
secondary: true, small: false, },
Label { sym: Keysym::Control_L, label: "Ctrl",
secondary: true, small: true, },
Label { sym: Keysym::Alt_L, label: "Alt",
secondary: true, small: true, },
Label { sym: Keysym::Delete, label: "\u{2326}",
secondary: true, small: false, },
Label { sym: Keysym::XF86_Fn, label: "Fn",
secondary: true, small: true, },
/*
Label { sym: Keysym::approxeq, label: "\u{2248}",
secondary: false, small: false, },
*/
];
const ANCHORS: [(Anchor, Anchor); 8] = [
(Anchor::Min, Anchor::Min),
(Anchor::Max, Anchor::Min),
(Anchor::Min, Anchor::Max),
(Anchor::Max, Anchor::Max),
(Anchor::Min, Anchor::Center),
(Anchor::Max, Anchor::Center),
(Anchor::Center, Anchor::Min),
(Anchor::Center, Anchor::Max),
];
impl<D: Display> Graphics<D> {
pub fn new(disp: D) -> Graphics<D>
{
let ft = unsafe {
let mut ft = std::ptr::null_mut();
let err = freetype::FT_Init_FreeType(&mut ft);
if err != 0 {
panic!("FreeType error {}", err);
}
ft
};
/*
* We want a pattern with default and config substitution, so use the
* side effect in font_match(). The first font might not provide all
* glyphs needed, so the returned font is unused.
*/
let fc = fontconfig::Fontconfig::new().unwrap();
let mut pattern = fontconfig::Pattern::new(&fc);
pattern.font_match();
let fonts = fontconfig::sort_fonts(&pattern, true);
let mut fontfiles = Vec::new();
for font in fonts.iter() {
let fname = match font.filename() {
Some(fname) => String::from(fname),
None => continue,
};
let idx = match font.face_index() {
Some(idx) => idx.into(),
None => 0,
};
fontfiles.push((fname, idx));
}
Graphics {
disp,
ft,
fonts: fontfiles,
fontbufs: Vec::new(),
labels: HashMap::new(),
sublabels: HashMap::new(),
x_scale: 0.0,
y_scale: 0.0,
}
}
fn keysym_to_label(sym: Keysym) -> String
{
let res = LABELS.binary_search_by_key(&sym, |&Label { sym, .. }| sym);
if let Ok(idx) = res {
return String::from(LABELS[idx].label);
}
// The ! symbol is bitwise NOT.
match char::from_u32(sym.raw() & !0x01000000) {
Some(c) => {
let mut label = String::new();
label.push(c);
label
},
None => {
format!("U+{}", sym.raw())
},
}
}
fn is_keysym_small(sym: Keysym) -> bool
{
match LABELS.binary_search_by_key(&sym, |&Label { sym, .. }| sym) {
Ok(idx) => LABELS[idx].small,
Err(_) => false,
}
}
fn open_font(&mut self, idx: usize) -> Option<freetype::FT_Face>
{
// Read the font if it is not already cached.
if self.fontbufs.len() <= idx {
let mut f = File::open(&self.fonts[idx].0).ok()?;
let mut fontbuf = Vec::new();
f.read_to_end(&mut fontbuf).ok()?;
self.fontbufs.push(fontbuf);
}
let fontbuf = &self.fontbufs[idx];
unsafe {
let mut ftface = std::ptr::null_mut();
freetype::FT_New_Memory_Face(self.ft,
fontbuf.as_ptr(),
fontbuf.len() as freetype::FT_Long,
self.fonts[idx].1 as freetype::FT_Long,
&mut ftface);
if ptr::eq(ftface, ptr::null_mut()) {
None
} else {
Some(ftface)
}
}
}
fn can_font_render_text(face: Option<freetype::FT_Face>, text: &str) -> bool
{
let face = match face {
Some(face) => face,
None => return false,
};
for c in text.chars() {
unsafe {
if freetype::FT_Get_Char_Index(face, c.into()) == 0 {
return false;
}
}
}
true
}
fn open_font_for_text(&mut self, text: &str) -> Option<freetype::FT_Face>
{
let mut ftface = self.open_font(0);
let mut i = 1;
while !Self::can_font_render_text(ftface, text) {
if let Some(ftface) = ftface {
unsafe {
freetype::FT_Done_Face(ftface);
}
}
ftface = self.open_font(i);
i += 1;
if i >= self.fonts.len() {
// Fall back to the first font with placeholder glyphs
return self.open_font(0);
}
}
ftface
}
fn rasterize_text(&mut self, size: f64, text: &str) -> ImgVec<u8>
{
let ftface = self.open_font_for_text(text).unwrap();
unsafe {
freetype::FT_Set_Char_Size(ftface, (size * 64.0) as i64, 0, 0, 0);
};
let (metrics, units_per_em, bbox) = unsafe {
((*(*ftface).size).metrics,
(*ftface).units_per_EM,
(*ftface).bbox)
};
let mut width = 0;
let glyph_width = (bbox.xMax - bbox.xMin) as usize
* metrics.x_ppem as usize
/ units_per_em as usize;
let stride = metrics.max_advance as usize / 64 * (text.len() - 1) + glyph_width;
let height = (metrics.ascender - metrics.descender) as usize / 64;
let vec: Vec<u8> = vec![0; stride * height];
let mut img = ImgVec::new(vec, stride, height);
let mut pen: i64 = 0;
for c in text.chars() {
let glyph = unsafe {
freetype::FT_Load_Char(ftface, c.into(), 0);
freetype::FT_Render_Glyph((*ftface).glyph, freetype::FT_Render_Mode::FT_RENDER_MODE_NORMAL);
*(*ftface).glyph
};
if glyph.bitmap.rows != 0 && glyph.bitmap.width != 0 {
if pen < -glyph.bitmap_left as i64 {
pen -= glyph.bitmap_left as i64;
}
let dest_x = pen + glyph.bitmap_left as i64;
let dest_y = metrics.ascender / 64 - glyph.bitmap_top as i64;
let src = bitmap_to_imgref(&glyph.bitmap);
let dest = img.sub_image_mut(dest_x as usize,
dest_y as usize,
glyph.bitmap.width as usize,
glyph.bitmap.rows as usize);
copy_image(dest, src);
}
let new_width = (pen + glyph.bitmap_left as i64
+ glyph.bitmap.width as i64) as usize;
if width < new_width {
width = new_width;
}
pen += glyph.advance.x / 64;
}
unsafe {
freetype::FT_Done_Face(ftface);
};
let vec = img.into_buf();
ImgVec::new_stride(vec, width, height, stride)
}
/*
* Rasterize a key symbol and store it in the labels hash table.
*
* We need two functions, one for labels and one for sublabels, in order to
* borrow the hash tables mutably. The text rasterizing operation borrows
* the self as mutable to store fonts for later use. Since the self and hash
* table cannot be mutable at the same time, this cannot be abstracted to a
* mutable self and mutable hash table.
*/
fn rasterize_label(&mut self, size: f64, sym: Keysym)
{
if !self.labels.contains_key(&sym) {
let label = Self::keysym_to_label(sym);
let size = if Self::is_keysym_small(sym) {
size * 0.75
} else {
size
};
let img = self.rasterize_text(size, &label);
self.labels.insert(sym, img);
}
}
/*
* Rasterize a key symbol and store it in the sublabels hash table.
*
* We need two functions, one for labels and one for sublabels, in order to
* borrow the hash tables mutably. The text rasterizing operation borrows
* the self as mutable to store fonts for later use. Since the self and hash
* table cannot be mutable at the same time, this cannot be abstracted to a
* mutable self and mutable hash table.
*/
fn rasterize_sublabel(&mut self, size: f64, sym: Keysym)
{
if !self.sublabels.contains_key(&sym) {
let label = Self::keysym_to_label(sym);
let size = if Self::is_keysym_small(sym) {
size * 0.75
} else {
size
};
let img = self.rasterize_text(size, &label);
self.sublabels.insert(sym, img);
}
}
fn rasterize_labels(&mut self, layout: &Layout, mod_state: &[ModState])
{
self.labels = HashMap::new();
self.sublabels = HashMap::new();
let label_size = self.y_scale * 0.33;
let sublabel_size = self.y_scale * 0.22;
for row in layout.rows() {
self.labels.reserve(row.len());
self.sublabels.reserve(row.len() * 8);
for key in row {
let sym = key.parts[0].display_symbol(mod_state);
self.rasterize_label(label_size, sym);
for part in &key.parts[1..] {
let sym = part.display_symbol(mod_state);
self.rasterize_sublabel(sublabel_size, sym);
}
}
}
}
fn draw_label_part(labels: &HashMap<Keysym, ImgVec<u8>>,
x_anchor: Anchor, y_anchor: Anchor,
img: &mut ImgRefMut<BGRA<u8>>,
sym: Keysym, fg_color: BGR<f32>)
{
let src = labels.get(&sym)
.expect("Layout and size should be set before drawing");
let x = match x_anchor {
Anchor::Min => 0,
Anchor::Center => (img.width() - src.width()) / 2,
Anchor::Max => img.width() - src.width(),
};
let y = match y_anchor {
Anchor::Min => 0,
Anchor::Center => (img.height() - src.height()) / 2,
Anchor::Max => img.height() - src.height(),
};
let width = src.width();
let height = src.height();
let src = src.sub_image(0, 0, width, height);
let dest = img.sub_image_mut(x, y, width, height);
convert_gray_to_bgrx(dest, src, fg_color);
}
fn keypart_to_color(part: &Part, sublabel: bool,
mod_state: &[ModState]) -> BGR<f32>
{
let color_locked = BGR { r: 0.0, g: 1.0, b: 0.1, };
let color_pressed = BGR { r: 0.0, g: 0.6, b: 1.0, };
let color_label = BGR { r: 1.0, g: 1.0, b: 1.0, };
let color_sublabel = BGR { r: 0.7, g: 0.7, b: 0.7, };
let modifier = part.modifier_id();
if modifier != 0 {
let modifier = modifier - 1;
if mod_state[modifier] == ModState::Locked
|| mod_state[modifier] == ModState::HeldLocked {
return color_locked;
} else if mod_state[modifier] != ModState::Released {
return color_pressed;
}
}
if part.presses() != 0 {
return color_pressed;
}
if sublabel {
return color_sublabel;
}
let res = LABELS.binary_search_by_key(&part.sym(), |&Label { sym, .. }| sym);
if let Ok(idx) = res {
if LABELS[idx].secondary {
return color_sublabel;
}
}
color_label
}
fn draw_key(labels: &HashMap<Keysym, ImgVec<u8>>,
sublabels: &HashMap<Keysym, ImgVec<u8>>,
x_scale: f64, y_scale: f64,
img: &mut ImgRefMut<BGRA<u8>>,
key: &Key, mod_state: &[ModState])
{
let x1 = (key.x1 * x_scale) as usize;
let y1 = (key.y1 * y_scale) as usize;
let x2 = (key.x2 * x_scale) as usize;
let y2 = (key.y2 * y_scale) as usize;
let mut keyimg = img.sub_image_mut(x1, y1, x2 - x1, y2 - y1);
let mut pressed = false;
for part in &key.parts {
let modifier = part.modifier_id();
if modifier != 0 && mod_state[modifier - 1] != ModState::Released {
pressed = true;
break;
}
if part.presses() != 0 {
pressed = true;
break;
}
}
if pressed {
fill_image(&mut keyimg, BGRA { r: 51, g: 51, b: 51, a: 0, });
} else {
fill_image(&mut keyimg, BGRA { r: 0, g: 0, b: 0, a: 0, });
}
let color = Self::keypart_to_color(&key.parts[0], false, mod_state);
let sym = key.parts[0].display_symbol(mod_state);
Self::draw_label_part(labels, Anchor::Center, Anchor::Center,
&mut keyimg, sym, color);
for i in 0..8 {
let color = Self::keypart_to_color(&key.parts[i + 1], true, mod_state);
let sym = key.parts[i + 1].display_symbol(mod_state);
Self::draw_label_part(sublabels, ANCHORS[i].0, ANCHORS[i].1,
&mut keyimg, sym, color);
}
}
pub fn draw_single(&mut self, key: &Key, mod_state: &[ModState])
{
let mut img = self.disp.begin();
let x1 = (key.x1 * self.x_scale) as u32;
let y1 = (key.y1 * self.y_scale) as u32;
let x2 = (key.x2 * self.x_scale) as u32;
let y2 = (key.y2 * self.y_scale) as u32;
Self::draw_key(&self.labels, &self.sublabels,
self.x_scale, self.y_scale,
&mut img, key, mod_state);
self.disp.end(x1, y1, x2 - x1, y2 - y1);
}
pub fn draw_modifiers(&mut self,
layout: &Layout,
mod_state: &[ModState],
modifiers: &[bool])
{
for row in layout.rows() {
for key in row {
let draw = key.parts.iter().any(|p| p.modifier_id() != 0
&& modifiers[p.modifier_id() - 1]);
if draw {
self.draw_single(key, mod_state);
}
}
}
}
pub fn draw(&mut self, layout: &Layout, mod_state: &[ModState])
{
let mut img = self.disp.begin();
fill_image(&mut img, BGRA { r: 0, g: 0, b: 0, a: 0, });
for row in layout.rows() {
for key in row {
Self::draw_key(&self.labels, &self.sublabels,
self.x_scale, self.y_scale,
&mut img, key, mod_state);
}
}
let (width, height) = (img.width() as u32, img.height() as u32);
self.disp.end(0, 0, width, height);
}
pub fn change_layout(&mut self, layout: &Layout, mod_state: &[ModState])
{
self.rasterize_labels(layout, mod_state);
self.draw(layout, mod_state);
}
pub fn resize(&mut self,
layout: &Layout, mod_state: &[ModState],
width: u32, height: u32)
{
self.x_scale = width as f64 / layout.width();
self.y_scale = height as f64 / layout.height();
self.rasterize_labels(layout, mod_state);
self.disp.resize(width, height);
self.draw(layout, mod_state);
}
#[inline(always)]
pub fn display(&self) -> &D
{
&self.disp
}
#[inline(always)]
pub fn display_mut(&mut self) -> &mut D
{
&mut self.disp
}
}
impl<D: Display> Drop for Graphics<D> {
fn drop(&mut self)
{
unsafe {
freetype::FT_Done_FreeType(self.ft);
}
}
}

540
src/core/layout.rs Normal file
View file

@ -0,0 +1,540 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Copyright (c) 2024, Richard Acayan. All rights reserved.
*/
use core::button::ModState;
use core::expat;
use core::xkeysym::Keysym;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::str::FromStr;
use std::ffi::CStr;
use std::fs::File;
use std::io::Error;
use std::io::ErrorKind;
use std::io::Read;
use std::os::raw::c_char;
use std::os::raw::c_void;
use std::path::Path;
use std::ptr;
unsafe extern "C" fn start_elem(data: *mut c_void,
c_name: *const expat::XML_Char,
c_attrs: *mut *const expat::XML_Char)
{
let layout = &mut *(data as *mut Layout);
let name = CStr::from_ptr(c_name)
.to_str().expect("layout: XML element name must be UTF-8");
let mut attrs = HashMap::with_capacity(10);
while !ptr::eq(*c_attrs.add(attrs.len() * 2), ptr::null()) {
let k = CStr::from_ptr(*c_attrs.add(attrs.len() * 2))
.to_str().expect("layout: XML attribute name must be UTF-8");
let v = CStr::from_ptr(*c_attrs.add(attrs.len() * 2 + 1))
.to_str().expect("layout: XML attribute value must be UTF-8");
attrs.insert(k, v);
}
layout.start_elem(name, &attrs);
}
unsafe extern "C" fn end_elem(data: *mut c_void, c_name: *const expat::XML_Char)
{
let layout = &mut *(data as *mut Layout);
let name = CStr::from_ptr(c_name)
.to_str().expect("layout: XML element name must be UTF-8");
layout.end_elem(name);
}
pub const MOD_SHIFT: usize = 1;
pub const MOD_CTRL: usize = 2;
pub const MOD_ALT: usize = 3;
pub const MOD_FN: usize = 4;
pub const MODIFIERS_MAX: usize = 4;
pub struct Part {
orig_sym: Keysym,
sym: Keysym,
// TODO: use accessors here
presses: u32,
}
impl Part {
pub fn modify_shift(orig_sym: Keysym) -> Keysym
{
match orig_sym {
Keysym::a => Keysym::A,
Keysym::b => Keysym::B,
Keysym::c => Keysym::C,
Keysym::d => Keysym::D,
Keysym::e => Keysym::E,
Keysym::f => Keysym::F,
Keysym::g => Keysym::G,
Keysym::h => Keysym::H,
Keysym::i => Keysym::I,
Keysym::j => Keysym::J,
Keysym::k => Keysym::K,
Keysym::l => Keysym::L,
Keysym::m => Keysym::M,
Keysym::n => Keysym::N,
Keysym::o => Keysym::O,
Keysym::p => Keysym::P,
Keysym::q => Keysym::Q,
Keysym::r => Keysym::R,
Keysym::s => Keysym::S,
Keysym::t => Keysym::T,
Keysym::u => Keysym::U,
Keysym::v => Keysym::V,
Keysym::w => Keysym::W,
Keysym::x => Keysym::X,
Keysym::y => Keysym::Y,
Keysym::z => Keysym::Z,
Keysym::ae => Keysym::AE,
Keysym::oe => Keysym::OE,
Keysym::mu => Keysym::Greek_MU,
_ => orig_sym,
}
}
fn modify_fn(orig_sym: Keysym) -> Keysym
{
match orig_sym {
Keysym::Up => Keysym::Prior, // Page Up
Keysym::Down => Keysym::Next, // Page Down
Keysym::Left => Keysym::Home,
Keysym::Right => Keysym::End,
Keysym::Escape => Keysym::Insert,
Keysym::_1 => Keysym::F1,
Keysym::_2 => Keysym::F2,
Keysym::_3 => Keysym::F3,
Keysym::_4 => Keysym::F4,
Keysym::_5 => Keysym::F5,
Keysym::_6 => Keysym::F6,
Keysym::_7 => Keysym::F7,
Keysym::_8 => Keysym::F8,
Keysym::_9 => Keysym::F9,
Keysym::_0 => Keysym::F10,
Keysym::less => Keysym::guillemetleft,
Keysym::greater => Keysym::guillemetright,
// Keysym::braceleft => Keysym::from_char('\u{2039}'),
// Keysym::braceright => Keysym::from_char('\u{203A}'),
Keysym::bracketleft => Keysym::leftsinglequotemark,
Keysym::bracketright => Keysym::rightsinglequotemark,
Keysym::parenleft => Keysym::leftdoublequotemark,
Keysym::parenright => Keysym::rightdoublequotemark,
Keysym::apostrophe => Keysym::singlelowquotemark,
Keysym::quotedbl => Keysym::doublelowquotemark,
Keysym::minus => Keysym::endash,
Keysym::underscore => Keysym::emdash,
Keysym::asciicircum => Keysym::notsign,
Keysym::percent => Keysym::permille,
Keysym::equal => Keysym::approxeq,
Keysym::u => Keysym::mu,
Keysym::a => Keysym::ae,
Keysym::o => Keysym::oe,
Keysym::asterisk => Keysym::degree,
Keysym::period => Keysym::ellipsis,
Keysym::comma => Keysym::periodcentered,
Keysym::exclam => Keysym::exclamdown,
Keysym::question => Keysym::questiondown,
Keysym::bar => Keysym::brokenbar,
Keysym::e => Keysym::EuroSign,
Keysym::l => Keysym::sterling,
// Keysym::r => Keysym::from_char('\u{20B9}'),
Keysym::y => Keysym::yen,
Keysym::c => Keysym::cent,
// Keysym::p => Keysym::from_char('\u{20BD}'),
// Keysym::b => Keysym::from_char('\u{20B1}'),
// Keysym::h => Keysym::from_char('\u{20B4}'),
// Keysym::z => Keysym::from_char('\u{20BF}'),
_ => orig_sym,
}
}
fn modify_no_fn(orig_sym: Keysym) -> Keysym
{
match orig_sym {
/*
Keysym::F11 => Keysym::NoSymbol,
Keysym::F12 => Keysym::NoSymbol,
*/
_ => orig_sym,
}
}
fn new(orig_sym: Keysym) -> Part
{
Part {
orig_sym,
sym: Self::modify_no_fn(orig_sym),
presses: 0,
}
}
#[inline(always)]
pub fn sym(&self) -> Keysym
{
self.sym
}
#[inline(always)]
pub fn press(&mut self)
{
self.presses += 1;
}
#[inline(always)]
pub fn release(&mut self)
{
self.presses -= 1;
}
#[inline(always)]
pub fn presses(&self) -> u32
{
self.presses
}
pub fn modifier_id(&self) -> usize
{
match self.sym {
Keysym::Shift_L => MOD_SHIFT,
Keysym::Control_L => MOD_CTRL,
Keysym::Alt_L => MOD_ALT,
Keysym::XF86_Fn => MOD_FN,
_ => 0,
}
}
pub fn update_modifiers(&mut self, mod_state: &[ModState])
{
let mut sym = self.orig_sym;
if mod_state[MOD_FN - 1] != ModState::Released {
sym = Self::modify_fn(sym);
} else {
sym = Self::modify_no_fn(sym);
}
self.sym = sym;
}
pub fn display_symbol(&self, mod_state: &[ModState]) -> Keysym
{
let mut sym = self.sym;
if mod_state[MOD_SHIFT - 1] != ModState::Released {
sym = Self::modify_shift(sym);
}
sym
}
}
pub struct Key {
// NOPUSH
pub parts: [Part; 9],
pub x1: f64,
pub y1: f64,
pub x2: f64,
pub y2: f64,
}
impl Key {
fn new(x1: f64, y1: f64, x2: f64, y2: f64,
center: Keysym,
top_left: Keysym,
top_right: Keysym,
bottom_left: Keysym,
bottom_right: Keysym,
left: Keysym,
right: Keysym,
top: Keysym,
bottom: Keysym) -> Key
{
Key {
x1, y1, x2, y2,
parts: [
Part::new(center),
Part::new(top_left),
Part::new(top_right),
Part::new(bottom_left),
Part::new(bottom_right),
Part::new(left),
Part::new(right),
Part::new(top),
Part::new(bottom),
],
}
}
}
pub struct Layout {
rows: Vec<Vec<Key>>,
width: f64,
height: f64,
row_width: f64,
in_row: u32,
}
const KEYSYMS: [(&str, Keysym); 20] = [
("\\#", Keysym::numbersign),
("\\?", Keysym::question),
("\\@", Keysym::at),
("\\\\", Keysym::backslash),
("backspace", Keysym::BackSpace),
("ctrl", Keysym::Control_L),
("delete", Keysym::Delete),
("down", Keysym::Down),
("enter", Keysym::Return),
("esc", Keysym::Escape),
("f11_placeholder", Keysym::F11),
("f12_placeholder", Keysym::F12),
("fn", Keysym::XF86_Fn),
("left", Keysym::Left),
("loc alt", Keysym::Alt_L),
("right", Keysym::Right),
("shift", Keysym::Shift_L),
("space", Keysym::space),
("tab", Keysym::Tab),
("up", Keysym::Up),
];
impl Layout {
pub fn is_keysym_modifier(modifier: usize) -> bool
{
match modifier {
MOD_FN => true,
_ => false,
}
}
pub fn is_label_modifier(modifier: usize) -> bool
{
// Shift does not change the keysym used for input, but changes the
// rendered keysym.
Self::is_keysym_modifier(modifier) || modifier == MOD_SHIFT
}
pub fn modifier_keysym(modifier: usize) -> Keysym
{
match modifier {
MOD_SHIFT => Keysym::Shift_L,
MOD_CTRL => Keysym::Control_L,
MOD_ALT => Keysym::Alt_L,
MOD_FN => Keysym::XF86_Fn,
_ => Keysym::NoSymbol,
}
}
fn get_keysym_by_name(name: &str) -> Keysym
{
if let Ok(idx) = KEYSYMS.binary_search_by_key(&name, |&(k, _)| k) {
return KEYSYMS[idx].1;
}
let mut chars = name.chars();
let c1 = chars.next();
let c2 = chars.next();
match (c1, c2) {
(Some(c), None) => Keysym::new(c as u32),
_ => Keysym::NoSymbol,
}
}
fn start_key(&mut self, attrs: &HashMap<&str, &str>)
{
if self.in_row != 1 {
println!("layout: key element not in row element ignored");
return;
}
let shift = f64::from_str(attrs.get("shift").unwrap_or(&"")).unwrap_or(0.0);
let x1 = self.row_width + shift;
let width = f64::from_str(attrs.get("width").unwrap_or(&"")).unwrap_or(1.0);
let x2 = x1 + width;
self.row_width = x2;
let key = Key::new(
x1, self.height, x2, self.height + 1.0,
Self::get_keysym_by_name(attrs.get("key0").unwrap_or(&"")),
Self::get_keysym_by_name(attrs.get("key1").unwrap_or(&"")),
Self::get_keysym_by_name(attrs.get("key2").unwrap_or(&"")),
Self::get_keysym_by_name(attrs.get("key3").unwrap_or(&"")),
Self::get_keysym_by_name(attrs.get("key4").unwrap_or(&"")),
Self::get_keysym_by_name(attrs.get("key5").unwrap_or(&"")),
Self::get_keysym_by_name(attrs.get("key6").unwrap_or(&"")),
Self::get_keysym_by_name(attrs.get("key7").unwrap_or(&"")),
Self::get_keysym_by_name(attrs.get("key8").unwrap_or(&""))
);
let idx = self.rows.len() - 1;
self.rows[idx].push(key);
}
fn start_row(&mut self)
{
self.rows.push(Vec::new());
self.in_row += 1;
self.row_width = 0.0;
}
fn start_elem(&mut self, name: &str, attrs: &HashMap<&str, &str>)
{
if name == "key" {
self.start_key(attrs);
} else if name == "row" {
self.start_row();
}
}
fn end_row(&mut self)
{
self.in_row -= 1;
if self.row_width > self.width {
self.width = self.row_width;
}
self.height += 1.0;
}
fn end_elem(&mut self, name: &str)
{
if name == "row" {
self.end_row();
}
}
pub fn load(dir: &Path, filename: &str) -> Result<Self, Error>
{
let mut layout = Layout {
rows: Vec::new(),
width: 0.0,
height: 0.0,
row_width: 0.0,
in_row: 0,
};
let mut vec = Vec::new();
let path = dir.join(filename);
let mut file = File::open(path)?;
file.read_to_end(&mut vec)?;
let mut vec2 = Vec::new();
let path = dir.join("bottom_row.xml");
let mut file = File::open(path)?;
file.read_to_end(&mut vec2)?;
unsafe {
let parser = expat::XML_ParserCreate(ptr::null());
if ptr::eq(parser, ptr::null_mut()) {
return Err(Error::new(ErrorKind::OutOfMemory, "Could not parse XML"));
}
expat::XML_SetElementHandler(parser, Some(start_elem), Some(end_elem));
expat::XML_SetUserData(parser, &mut layout as *mut Layout as *mut c_void);
let status = expat::XML_Parse(parser, vec.as_ptr() as *const c_char,
vec.len() as i32, 1);
if status != expat::XML_Status_XML_STATUS_OK {
return Err(Error::new(ErrorKind::InvalidData, "Could not parse XML"));
}
expat::XML_ParserReset(parser, ptr::null());
expat::XML_SetElementHandler(parser, Some(start_elem), Some(end_elem));
expat::XML_SetUserData(parser, &mut layout as *mut Layout as *mut c_void);
let status = expat::XML_Parse(parser, vec2.as_ptr() as *const c_char,
vec2.len() as i32, 1);
if status != expat::XML_Status_XML_STATUS_OK {
return Err(Error::new(ErrorKind::InvalidData, "Could not parse XML"));
}
expat::XML_ParserFree(parser);
};
Ok(layout)
}
#[inline(always)]
pub fn width(&self) -> f64
{
self.width
}
#[inline(always)]
pub fn height(&self) -> f64
{
self.height
}
#[inline(always)]
pub fn rows(&self) -> &Vec<Vec<Key>>
{
&self.rows
}
pub fn locate_key(&self, x: f64, y: f64) -> Option<&Key>
{
let row = &self.rows[y as usize];
let res = row.binary_search_by(|k| {
if k.x2 < x {
Ordering::Less
} else if k.x1 > x {
Ordering::Greater
} else {
Ordering::Equal
}
});
match res {
Ok(i) => Some(&row[i]),
Err(_) => None,
}
}
pub fn locate_key_mut(&mut self, x: f64, y: f64) -> Option<&mut Key>
{
let row = &mut self.rows[y as usize];
let res = row.binary_search_by(|k| {
if k.x2 < x {
Ordering::Less
} else if k.x1 > x {
Ordering::Greater
} else {
Ordering::Equal
}
});
match res {
Ok(i) => Some(&mut row[i]),
Err(_) => None,
}
}
pub fn update_modifiers(&mut self, mod_state: &[ModState])
{
for row in self.rows.iter_mut() {
for key in row.iter_mut() {
for part in key.parts.iter_mut() {
part.update_modifiers(mod_state);
}
}
}
}
}

23
src/core/mod.rs Normal file
View file

@ -0,0 +1,23 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Copyright (c) 2024, Richard Acayan. All rights reserved.
*/
pub extern crate fontconfig;
pub extern crate freetype;
pub extern crate imgref;
pub extern crate rgb;
pub extern crate xkeysym;
mod button;
mod expat;
mod graphics;
mod layout;
pub use self::button::Button;
pub use self::button::Keyboard;
pub use self::button::ModState;
pub use self::graphics::Display;
pub use self::graphics::Graphics;
pub use self::layout::Layout;
pub use self::layout::Part;

59
src/main.rs Normal file
View file

@ -0,0 +1,59 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Copyright (c) 2024, Richard Acayan. All rights reserved.
*/
extern crate polling;
mod wayland;
mod core;
use core::Layout;
use polling::Event;
use polling::Events;
use polling::Poller;
use std::path::Path;
use std::time::Instant;
use wayland::Dispatcher;
use wayland::wayland_client::Connection;
use wayland::wayland_client::globals;
fn main()
{
let conn = Connection::connect_to_env().unwrap();
let (globals, mut queue) = globals::registry_queue_init::<Dispatcher>(&conn)
.expect("Registry required");
let layouts = Path::new("/usr/share/unfettered-keyboard/layouts");
let layout = Layout::load(&layouts, "latn_qwerty_us.xml")
.expect("Layout should be loadable");
let mut dispatcher = Dispatcher::new(layout, queue.handle(), &globals).unwrap();
let wl_evt = Event::readable(0);
let poller = Poller::new().unwrap();
let mut events = Events::new();
loop {
queue.flush().unwrap();
let guard = queue.prepare_read().unwrap();
let fd = guard.connection_fd();
let timer = dispatcher.button().next_time().map(|t| t - Instant::now());
unsafe {
poller.add(&fd, wl_evt).unwrap();
}
events.clear();
poller.wait(&mut events, timer).unwrap();
poller.delete(fd).unwrap();
if !events.is_empty() {
guard.read().unwrap();
queue.dispatch_pending(&mut dispatcher).unwrap();
}
dispatcher.dispatch_timers();
}
}

191
src/wayland/buffer.rs Normal file
View file

@ -0,0 +1,191 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Copyright (c) 2024, Richard Acayan. All rights reserved.
*/
extern crate libc;
extern crate memmap2;
use core::imgref::ImgRef;
use core::imgref::ImgRefMut;
use core::rgb::alt::BGRA;
use core::rgb::FromSlice;
use self::memmap2::MmapMut;
use std::ffi::CString;
use std::fs::File;
use std::io::Error;
use std::io::ErrorKind;
use std::iter;
use std::os::fd::FromRawFd;
use std::os::fd::AsFd;
use std::process;
use wayland::wayland_client::protocol::wl_buffer::WlBuffer;
use wayland::wayland_client::protocol::wl_shm_pool::WlShmPool;
use wayland::wayland_client::protocol::wl_shm;
use wayland::wayland_client::protocol::wl_surface::WlSurface;
use wayland::wayland_client::Dispatch;
use wayland::wayland_client::QueueHandle;
pub struct Buffer<T> {
file: File,
map: MmapMut,
id: u32,
capacity: i32,
width: i32,
height: i32,
queue: QueueHandle<T>,
pool: WlShmPool,
buf: WlBuffer,
used: bool,
resizing: bool,
}
impl<T: Dispatch<WlShmPool, u32>
+ Dispatch<WlBuffer, u32>
+ 'static> Buffer<T> {
pub fn new(queue: QueueHandle<T>, shm: &wl_shm::WlShm, id: u32) -> Result<Self, Error>
{
// Integers are normally not represented as text using NUL.
let name = match CString::new(format!("/ufkbd_pid{}_{}", process::id(), id)) {
Ok(name) => name,
Err(e) => return Err(Error::new(ErrorKind::InvalidInput, e)),
};
let file = unsafe {
let fd = libc::shm_open(name.as_ptr(),
libc::O_RDWR | libc::O_CREAT | libc::O_EXCL,
libc::S_IRUSR | libc::S_IWUSR);
if fd == -1 {
return Err(Error::last_os_error());
}
File::from_raw_fd(fd)
};
/*
* A buffer must be at least 1x1, and 32 bits per pixel
* (ARGB8888 and XRGB8888) is supported on all Wayland compositors.
*/
file.set_len(4)?;
let map = unsafe { MmapMut::map_mut(&file) }?;
let pool = shm.create_pool(file.as_fd(), 4, &queue, id);
let buf = pool.create_buffer(0, 1, 1, 4, wl_shm::Format::Xrgb8888,
&queue, id);
/*
* This can happen before expanding the file. The shm_unlink(3p) manual
* says:
*
* If one or more references to the shared memory object exist when
* the object is unlinked, the name shall be removed before
* shm_unlink() returns, but the removal of the memory object
* contents shall be postponed until all open and map references to
* the shared memory object have been removed.
*/
unsafe {
libc::shm_unlink(name.as_ptr());
};
Ok(Buffer {
file, map,
id, capacity: 4, width: 1, height: 1,
queue, pool, buf,
used: false, resizing: false,
})
}
fn commit_resize(&mut self)
{
if self.resizing {
self.buf = self.pool.create_buffer(0, self.width, self.height,
self.width * 4,
wl_shm::Format::Xrgb8888,
&self.queue, self.id);
self.resizing = false;
}
}
pub fn resize(&mut self, width: i32, height: i32) -> Result<(), Error>
{
if width <= 0 || height <= 0 {
return Err(Error::new(ErrorKind::InvalidInput, "Buffer must be at least 1x1"));
}
// Expand as necessary. Shrinking is not supported by wl_shm_pool.resize().
if self.capacity < width * height * 4 {
self.capacity = width * height * 4;
// Failure is a bug in the above check or the capacity calculation.
let filelen = self.capacity as u64;
self.file.set_len(filelen)?;
self.pool.resize(self.capacity);
self.map = unsafe { MmapMut::map_mut(&self.file) }?;
}
// Try to resize the buffer now, otherwise defer it.
self.width = width;
self.height = height;
self.resizing = true;
if !self.used {
self.commit_resize();
}
Ok(())
}
pub fn image(&self) -> ImgRef<BGRA<u8>>
{
let ptr = self.map.as_ref().as_bgra();
ImgRef::new(ptr, self.width as usize, self.height as usize)
}
pub fn consume(&mut self, surface: &WlSurface) -> Option<ImgRefMut<BGRA<u8>>>
{
if self.used {
return None;
}
self.used = true;
surface.attach(Some(&self.buf), 0, 0);
// The ARGB format is in little endian.
let ptr = self.map.as_mut().as_bgra_mut();
let img = ImgRefMut::new(ptr, self.width as usize, self.height as usize);
Some(img)
}
pub fn release(&mut self)
{
self.used = false;
self.commit_resize();
}
pub fn writeback(&mut self, src: &ImgRef<BGRA<u8>>, x: usize, y: usize)
{
if self.used {
panic!("Writeback must always be on an inactive buffer");
}
// The ARGB format is in little endian.
let ptr = self.map.as_mut().as_bgra_mut();
let mut img = ImgRefMut::new(ptr, self.width as usize, self.height as usize);
let mut subimg = img.sub_image_mut(x, y, src.width(), src.height());
for (dest, src) in iter::zip(subimg.pixels_mut(), src.pixels()) {
*dest = src;
}
}
#[inline(always)]
pub fn size(&self) -> (i32, i32)
{
(self.width, self.height)
}
}

391
src/wayland/dispatcher.rs Normal file
View file

@ -0,0 +1,391 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Copyright (c) 2024, Richard Acayan. All rights reserved.
*/
use core::Button;
use core::Display;
use core::Graphics;
use core::Layout;
use std::io::Error;
use std::sync::Arc;
use std::sync::Mutex;
use wayland::Seat;
use wayland::Surface;
use wayland::wayland_client::globals::GlobalList;
use wayland::wayland_client::globals::GlobalListContents;
use wayland::wayland_client::protocol::wl_buffer;
use wayland::wayland_client::protocol::wl_compositor;
use wayland::wayland_client::protocol::wl_pointer;
use wayland::wayland_client::protocol::wl_registry;
use wayland::wayland_client::protocol::wl_seat;
use wayland::wayland_client::protocol::wl_shm;
use wayland::wayland_client::protocol::wl_shm_pool;
use wayland::wayland_client::protocol::wl_surface;
use wayland::wayland_client::protocol::wl_touch;
use wayland::wayland_client::Connection;
use wayland::wayland_client::Dispatch;
use wayland::wayland_client::Proxy;
use wayland::wayland_client::QueueHandle;
use wayland::wayland_client::WEnum;
use wayland::wlr_layer_shell_unstable_v1::zwlr_layer_shell_v1;
use wayland::wlr_layer_shell_unstable_v1::zwlr_layer_surface_v1;
use wayland::fractional_scale_v1::wp_fractional_scale_manager_v1;
use wayland::fractional_scale_v1::wp_fractional_scale_v1;
use wayland::viewporter::wp_viewporter;
use wayland::viewporter::wp_viewport;
use wayland::virtual_keyboard_unstable_v1::zwp_virtual_keyboard_manager_v1;
use wayland::virtual_keyboard_unstable_v1::zwp_virtual_keyboard_v1;
use wayland::VirtualKeyboard;
pub struct Dispatcher {
seat: Seat<Surface<Self>, VirtualKeyboard, Self>,
gfx: Arc<Mutex<Graphics<Surface<Self>>>>,
}
impl Dispatcher {
pub fn new(layout: Layout, queue: QueueHandle<Self>, globals: &GlobalList) -> Result<Self, Error>
{
let shm = globals.bind(&queue, 1..=1, ())
.expect("Compositor must implement wl_shm");
let compositor = globals.bind(&queue, 4..=6, ())
.expect("Compositor must implement wl_compositor");
let seat = globals.bind(&queue, 5..=9, ())
.expect("Compositor must implement wl_seat");
let layer_shell = globals.bind(&queue, 3..=4, ())
.expect("Compositor must implement zwlr_layer_shell_v1");
let vk_man = globals.bind(&queue, 1..=1, ())
.expect("Compositor must implement zwp_virtual_keyboard_manager_v1");
let frac_scale_man = match globals.bind(&queue, 1..=1, ()) {
Ok(g) => Some(g),
Err(_) => None,
};
let vper = match globals.bind(&queue, 1..=1, ()) {
Ok(g) => Some(g),
Err(_) => None,
};
let disp = Surface::new(queue.clone(),
&shm, &compositor, &layer_shell,
frac_scale_man, vper, 185)?;
let gfx = Graphics::new(disp);
let gfx = Mutex::new(gfx);
let gfx = Arc::new(gfx);
let vk = VirtualKeyboard::new(&queue, &vk_man, &seat);
let seat = Seat::new(layout, vk, gfx.clone(), queue.clone(), seat);
Ok(Dispatcher {
seat,
gfx,
})
}
#[inline(always)]
pub fn button(&self) -> &Button<Surface<Self>, VirtualKeyboard>
{
self.seat.button()
}
pub fn dispatch_timers(&mut self)
{
self.seat.button_mut().dispatch_timers();
}
}
impl Dispatch<wl_buffer::WlBuffer, u32> for Dispatcher {
fn event(_ctx: &mut Dispatcher,
_buf: &wl_buffer::WlBuffer,
_evt: <wl_buffer::WlBuffer as Proxy>::Event,
_data: &u32,
_conn: &Connection,
_qh: &QueueHandle<Dispatcher>)
{
}
}
impl Dispatch<wl_compositor::WlCompositor, ()> for Dispatcher {
fn event(_ctx: &mut Dispatcher,
_comp: &wl_compositor::WlCompositor,
_evt: <wl_compositor::WlCompositor as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Dispatcher>)
{
}
}
impl Dispatch<wl_pointer::WlPointer, ()> for Dispatcher {
fn event(ctx: &mut Dispatcher,
_ptr: &wl_pointer::WlPointer,
evt: <wl_pointer::WlPointer as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Dispatcher>)
{
match evt {
wl_pointer::Event::Motion { surface_x, surface_y, .. } => {
ctx.seat.ptr_motion(surface_x, surface_y);
},
wl_pointer::Event::Button { state: WEnum::Value(state), .. } => {
ctx.seat.ptr_button(state);
},
wl_pointer::Event::Frame => {
ctx.seat.commit();
},
wl_pointer::Event::Enter { .. } => (),
wl_pointer::Event::Leave { .. } => (),
_ => eprintln!("warn: unknown wl_pointer event emitted"),
};
}
}
impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for Dispatcher {
fn event(_ctx: &mut Dispatcher,
_reg: &wl_registry::WlRegistry,
_evt: <wl_registry::WlRegistry as Proxy>::Event,
_data: &GlobalListContents,
_conn: &Connection,
_qh: &QueueHandle<Dispatcher>)
{
}
}
impl Dispatch<wl_seat::WlSeat, ()> for Dispatcher {
fn event(ctx: &mut Dispatcher,
_seat: &wl_seat::WlSeat,
evt: <wl_seat::WlSeat as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Dispatcher>)
{
match evt {
wl_seat::Event::Name { .. } => (),
wl_seat::Event::Capabilities { capabilities: WEnum::Value(caps) } => {
ctx.seat.set_capabilities(caps);
},
_ => eprintln!("warn: unknown wl_seat event emitted"),
};
}
}
impl Dispatch<wl_shm::WlShm, ()> for Dispatcher {
fn event(_ctx: &mut Dispatcher,
_shm: &wl_shm::WlShm,
evt: <wl_shm::WlShm as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Dispatcher>)
{
match evt {
wl_shm::Event::Format { .. } => (),
_ => eprintln!("warn: unknown wl_shm event emitted"),
};
}
}
impl Dispatch<wl_shm_pool::WlShmPool, u32> for Dispatcher {
fn event(_ctx: &mut Dispatcher,
_pool: &wl_shm_pool::WlShmPool,
_evt: <wl_shm_pool::WlShmPool as Proxy>::Event,
_data: &u32,
_conn: &Connection,
_qh: &QueueHandle<Dispatcher>)
{
eprintln!("warn: unknown wl_shm_pool event emitted");
}
}
impl Dispatch<wl_surface::WlSurface, ()> for Dispatcher {
fn event(_ctx: &mut Dispatcher,
_pool: &wl_surface::WlSurface,
evt: <wl_surface::WlSurface as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Dispatcher>)
{
match evt {
wl_surface::Event::PreferredBufferTransform { .. } => (),
wl_surface::Event::PreferredBufferScale { .. } => (), // TODO
_ => eprintln!("warn: unknown wl_surface event emitted"),
}
}
}
impl Dispatch<wl_touch::WlTouch, ()> for Dispatcher {
fn event(ctx: &mut Dispatcher,
_ptr: &wl_touch::WlTouch,
evt: <wl_touch::WlTouch as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Dispatcher>)
{
match evt {
wl_touch::Event::Down { id, x, y, .. } => {
ctx.seat.touch_press(id as u32, x, y);
},
wl_touch::Event::Motion { id, x, y, .. } => {
ctx.seat.touch_pos(id as u32, x, y);
},
wl_touch::Event::Up { id, .. } => {
ctx.seat.touch_release(id as u32);
},
wl_touch::Event::Frame => {
ctx.seat.commit();
},
_ => eprintln!("warn: unknown wl_touch event emitted"),
};
}
}
impl Dispatch<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1, ()> for Dispatcher {
fn event(_ctx: &mut Dispatcher,
_pool: &wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1,
_evt: wp_fractional_scale_manager_v1::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Dispatcher>)
{
eprintln!("warn: unknown wp_fractional_scale_manager_v1 event emitted");
}
}
impl Dispatch<wp_fractional_scale_v1::WpFractionalScaleV1, ()> for Dispatcher {
fn event(ctx: &mut Dispatcher,
_pool: &wp_fractional_scale_v1::WpFractionalScaleV1,
evt: wp_fractional_scale_v1::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Dispatcher>)
{
match evt {
wp_fractional_scale_v1::Event::PreferredScale { scale } => {
let mut gfx = ctx.gfx.lock().unwrap();
if scale == gfx.display().scale() {
return;
}
let old_scale = gfx.display().scale();
let (width, height) = gfx.display().size();
let (width, height) = (width * scale / old_scale,
height * scale / old_scale);
gfx.display_mut().update_scale(scale);
if gfx.display().configured() {
let layout = ctx.seat.layout();
let mod_state = ctx.seat.mod_state();
gfx.resize(layout, mod_state, width, height);
let (width, height) = gfx.display().size();
let width = width as f64 * 120.0 / scale as f64;
let height = height as f64 * 120.0 / scale as f64;
ctx.seat.set_scale(layout.width() / width,
layout.height() / height);
}
},
_ => eprintln!("warn: unknown wp_fractional_scale_v1 event emitted"),
};
}
}
impl Dispatch<wp_viewporter::WpViewporter, ()> for Dispatcher {
fn event(_ctx: &mut Dispatcher,
_pool: &wp_viewporter::WpViewporter,
_evt: wp_viewporter::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Dispatcher>)
{
eprintln!("warn: unknown wp_viewporter event emitted");
}
}
impl Dispatch<wp_viewport::WpViewport, ()> for Dispatcher {
fn event(_ctx: &mut Dispatcher,
_pool: &wp_viewport::WpViewport,
_evt: wp_viewport::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Dispatcher>)
{
eprintln!("warn: unknown wp_viewport event emitted");
}
}
impl Dispatch<zwlr_layer_shell_v1::ZwlrLayerShellV1, ()> for Dispatcher {
fn event(_ctx: &mut Dispatcher,
_pool: &zwlr_layer_shell_v1::ZwlrLayerShellV1,
_evt: <zwlr_layer_shell_v1::ZwlrLayerShellV1 as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Dispatcher>)
{
eprintln!("warn: unknown zwlr_layer_shell_v1 event emitted");
}
}
impl Dispatch<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, ()> for Dispatcher {
fn event(ctx: &mut Dispatcher,
_surface: &zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
evt: zwlr_layer_surface_v1::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Dispatcher>)
{
match evt {
zwlr_layer_surface_v1::Event::Configure { width, height, serial } => {
let mut gfx = ctx.gfx.lock().unwrap();
gfx.display_mut().ack_configure(serial);
let layout = ctx.seat.layout();
let mod_state = ctx.seat.mod_state();
let scale = gfx.display().scale() / 120;
let (width, height) = (width * scale, height * scale);
gfx.resize(layout, mod_state, width, height);
let disp = gfx.display();
let scale = disp.scale() as f64;
let (width, height) = disp.size();
let width = width as f64 * 120.0 / scale;
let height = height as f64 * 120.0 / scale;
ctx.seat.set_scale(layout.width() / width,
layout.height() / height);
},
_ => eprintln!("warn: unknown zwlr_layer_surface_v1 event emitted"),
}
}
}
impl Dispatch<zwp_virtual_keyboard_manager_v1::ZwpVirtualKeyboardManagerV1, ()> for Dispatcher {
fn event(_ctx: &mut Dispatcher,
_vk_man: &zwp_virtual_keyboard_manager_v1::ZwpVirtualKeyboardManagerV1,
_evt: zwp_virtual_keyboard_manager_v1::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Dispatcher>)
{
eprintln!("warn: unknown zwp_virtual_keyboard_manager_v1 event emitted");
}
}
impl Dispatch<zwp_virtual_keyboard_v1::ZwpVirtualKeyboardV1, ()> for Dispatcher {
fn event(_ctx: &mut Dispatcher,
_vk: &zwp_virtual_keyboard_v1::ZwpVirtualKeyboardV1,
_evt: zwp_virtual_keyboard_v1::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Dispatcher>)
{
eprintln!("warn: unknown zwp_virtual_keyboard_v1 event emitted");
}
}

199
src/wayland/keyboard.rs Normal file
View file

@ -0,0 +1,199 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Copyright (c) 2024, Richard Acayan. All rights reserved.
*/
use core::Keyboard;
use core::Layout;
use core::Part;
use core::xkeysym::Keysym;
use std::collections::HashMap;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::Seek;
use std::io::Write;
use std::os::fd::AsFd;
use std::process;
use wayland::wayland_client::Dispatch;
use wayland::wayland_client::QueueHandle;
use wayland::wayland_client::protocol::wl_seat::WlSeat;
use wayland::wayland_client::protocol::wl_keyboard::KeymapFormat;
use wayland::virtual_keyboard_unstable_v1::zwp_virtual_keyboard_manager_v1::ZwpVirtualKeyboardManagerV1;
use wayland::virtual_keyboard_unstable_v1::zwp_virtual_keyboard_v1::ZwpVirtualKeyboardV1;
pub struct VirtualKeyboard {
vk: ZwpVirtualKeyboardV1,
keymap: File,
keymap_id: u8,
keycodes: HashMap<Keysym, u8>,
mod_state: u32,
}
impl VirtualKeyboard {
pub fn new<T: Dispatch<ZwpVirtualKeyboardV1, ()>
+ 'static>(queue: &QueueHandle<T>,
vk_man: &ZwpVirtualKeyboardManagerV1,
seat: &WlSeat)
-> VirtualKeyboard
{
let vk = vk_man.create_virtual_keyboard(seat, queue, ());
let path = format!("/tmp/ufkbd-keymap-pid{}-1", process::id());
let keymap = File::create(path).unwrap();
VirtualKeyboard {
vk,
keymap,
keymap_id: 1,
keycodes: HashMap::new(),
mod_state: 0,
}
}
fn write_key(&mut self, part: &Part, keycode: u8)
{
let sym = part.sym();
let lower = &sym.name().unwrap()[3..];
let upper = &Part::modify_shift(sym).name().unwrap()[3..];
write!(self.keymap, " key <I{:03}> {{ [ {}, {} ] }};\n",
keycode, lower, upper).unwrap();
if sym == Keysym::Shift_L {
write!(self.keymap,
" modifier_map Shift {{ <I{:03}> }};\n",
keycode).unwrap();
} else if sym == Keysym::Control_L {
write!(self.keymap,
" modifier_map Control {{ <I{:03}> }};\n",
keycode).unwrap();
} else if sym == Keysym::Alt_L {
write!(self.keymap,
" modifier_map Mod1 {{ <I{:03}> }};\n",
keycode).unwrap();
}
}
}
impl Keyboard for VirtualKeyboard {
fn press(&mut self, sym: Keysym)
{
if sym == Keysym::NoSymbol || sym == Keysym::XF86_Fn {
return;
}
match sym {
Keysym::Shift_L => {
self.mod_state |= 0x1;
self.vk.modifiers(self.mod_state, 0, 0, 0);
},
Keysym::Control_L => {
self.mod_state |= 0x4;
self.vk.modifiers(self.mod_state, 0, 0, 0);
},
Keysym::Alt_L => {
self.mod_state |= 0x8;
self.vk.modifiers(self.mod_state, 0, 0, 0);
},
_ => (),
}
let keycode = *self.keycodes.get(&sym).unwrap();
self.vk.key(0, keycode as u32, 1);
}
fn release(&mut self, sym: Keysym)
{
if sym == Keysym::NoSymbol || sym == Keysym::XF86_Fn {
return;
}
match sym {
Keysym::Shift_L => {
self.mod_state &= !0x1;
self.vk.modifiers(self.mod_state, 0, 0, 0);
},
Keysym::Control_L => {
self.mod_state &= !0x4;
self.vk.modifiers(self.mod_state, 0, 0, 0);
},
Keysym::Alt_L => {
self.mod_state &= !0x8;
self.vk.modifiers(self.mod_state, 0, 0, 0);
},
_ => (),
}
let keycode = *self.keycodes.get(&sym).unwrap();
self.vk.key(0, keycode as u32, 0);
}
fn change_layout(&mut self, layout: &Layout)
{
let mut keycode = 8;
self.keymap_id = match self.keymap_id {
0 => 1,
1 => 0,
_ => panic!("There should only be up to two keymaps"),
};
let path = format!("/tmp/ufkbd-keymap-pid{}-{}",
process::id(), self.keymap_id);
self.keymap = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)
.unwrap();
self.keymap.write(b"xkb_keymap {\n").unwrap();
self.keymap.write(b" xkb_symbols \"ufkbd\" {\n").unwrap();
for row in layout.rows() {
for key in row {
for part in &key.parts {
if part.sym() == Keysym::NoSymbol || part.sym() == Keysym::XF86_Fn {
continue;
}
self.keycodes.insert(part.sym(), keycode - 8);
self.write_key(part, keycode);
keycode += 1;
}
}
}
self.keymap.write(b" };\n").unwrap();
self.keymap.write(b"\n").unwrap();
self.keymap.write(b" xkb_keycodes \"ufkbd\" {\n").unwrap();
self.keymap.write(b" minimum = 8;\n").unwrap();
self.keymap.write(b" maximum = 255;\n").unwrap();
for i in 8..keycode {
write!(self.keymap, " <I{:03}> = {};\n", i, i).unwrap();
}
self.keymap.write(b" indicator 1 = \"Caps Lock\";\n").unwrap();
self.keymap.write(b" };\n").unwrap();
self.keymap.write(b"\n").unwrap();
self.keymap.write(b" xkb_types \"ufkbd\" {\n").unwrap();
self.keymap.write(b" type \"TWO_LEVEL\" {\n").unwrap();
self.keymap.write(b" modifiers = Shift;\n").unwrap();
self.keymap.write(b" map[Shift] = Level2;\n").unwrap();
self.keymap.write(b" level_name[Level1] = \"Base\";\n").unwrap();
self.keymap.write(b" level_name[Level2] = \"Shift\";\n").unwrap();
self.keymap.write(b" };\n").unwrap();
self.keymap.write(b" };\n").unwrap();
self.keymap.write(b"\n").unwrap();
self.keymap.write(b" xkb_compatibility \"ufkbd\" {\n").unwrap();
self.keymap.write(b" };\n").unwrap();
self.keymap.write(b"};\n").unwrap();
let len = self.keymap.stream_position().unwrap() as u32;
self.vk.keymap(KeymapFormat::XkbV1 as u32, self.keymap.as_fd(), len);
}
}

30
src/wayland/mod.rs Normal file
View file

@ -0,0 +1,30 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Copyright (c) 2024, Richard Acayan. All rights reserved.
*/
pub extern crate wayland_client;
pub extern crate wayland_protocols;
pub extern crate wayland_protocols_wlr;
pub extern crate wayland_scanner;
mod buffer;
mod dispatcher;
mod keyboard;
mod protocols;
mod seat;
mod surface;
pub use self::buffer::Buffer;
pub use self::dispatcher::Dispatcher;
pub use self::keyboard::VirtualKeyboard;
pub use self::seat::Seat;
pub use self::surface::Surface;
pub use self::wayland_protocols_wlr::layer_shell::v1::client as wlr_layer_shell_unstable_v1;
pub use self::wayland_protocols::wp::fractional_scale::v1::client as fractional_scale_v1;
pub use self::wayland_protocols::wp::text_input::zv3::client as text_input_unstable_v3;
pub use self::wayland_protocols::wp::viewporter::client as viewporter;
pub use self::protocols::input_method_unstable_v2;
pub use self::protocols::virtual_keyboard_unstable_v1;

35
src/wayland/protocols.rs Normal file
View file

@ -0,0 +1,35 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Copyright (c) 2024, Richard Acayan. All rights reserved.
*/
pub mod input_method_unstable_v2 {
use wayland::wayland_client;
use wayland::wayland_client::protocol::*;
use wayland::text_input_unstable_v3::*;
pub mod __interfaces {
use wayland::wayland_client::protocol::__interfaces::*;
wayland_scanner::generate_interfaces!("wayland-protocols/input-method-unstable-v2.xml");
}
use self::__interfaces::*;
wayland_scanner::generate_client_code!("wayland-protocols/input-method-unstable-v2.xml");
}
pub mod virtual_keyboard_unstable_v1 {
use wayland::wayland_client;
use wayland::wayland_client::protocol::*;
pub mod __interfaces {
use wayland::wayland_client::protocol::__interfaces::*;
wayland_scanner::generate_interfaces!("wayland-protocols/virtual-keyboard-unstable-v1.xml");
}
use self::__interfaces::*;
wayland_scanner::generate_client_code!("wayland-protocols/virtual-keyboard-unstable-v1.xml");
}

186
src/wayland/seat.rs Normal file
View file

@ -0,0 +1,186 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Copyright (c) 2024, Richard Acayan. All rights reserved.
*/
use core::Button;
use core::ModState;
use core::Display;
use core::Graphics;
use core::Keyboard;
use core::Layout;
use std::sync::Arc;
use std::sync::Mutex;
use wayland::wayland_client::protocol::wl_pointer;
use wayland::wayland_client::protocol::wl_seat;
use wayland::wayland_client::protocol::wl_touch::WlTouch;
use wayland::wayland_client::Dispatch;
use wayland::wayland_client::QueueHandle;
enum PressAction {
Pos(f64, f64),
Press(f64, f64),
Release,
}
struct TouchAction {
id: usize,
act: PressAction,
}
pub struct Seat<D: Display, K: Keyboard, T> {
seat: wl_seat::WlSeat,
queue: QueueHandle<T>,
ptr: Option<wl_pointer::WlPointer>,
touch: Option<WlTouch>,
button: Button<D, K>,
x_scale: f64,
y_scale: f64,
mouse_x: f64,
mouse_y: f64,
actions: Vec<TouchAction>,
}
impl<D: Display, K: Keyboard,
T: Dispatch<wl_pointer::WlPointer, ()>
+ Dispatch<WlTouch, ()>
+ 'static> Seat<D, K, T> {
pub fn new(layout: Layout, kbd: K, gfx: Arc<Mutex<Graphics<D>>>,
queue: QueueHandle<T>, seat: wl_seat::WlSeat) -> Seat<D, K, T>
{
let actions = Vec::new();
let button = Button::new(layout, kbd, gfx);
Seat {
seat,
queue,
ptr: None,
touch: None,
button,
x_scale: 0.0,
y_scale: 0.0,
mouse_x: 0.0,
mouse_y: 0.0,
actions,
}
}
#[inline(always)]
pub fn button(&self) -> &Button<D, K>
{
&self.button
}
#[inline(always)]
pub fn button_mut(&mut self) -> &mut Button<D, K>
{
&mut self.button
}
#[inline(always)]
pub fn layout(&self) -> &Layout
{
self.button.layout()
}
#[inline(always)]
pub fn mod_state(&self) -> &[ModState]
{
self.button.mod_state()
}
pub fn set_capabilities(&mut self, caps: wl_seat::Capability)
{
if caps.contains(wl_seat::Capability::Pointer) {
self.ptr = Some(self.seat.get_pointer(&self.queue, ()));
}
if caps.contains(wl_seat::Capability::Touch) {
self.touch = Some(self.seat.get_touch(&self.queue, ()));
}
}
pub fn set_scale(&mut self, x_scale: f64, y_scale: f64)
{
self.x_scale = x_scale;
self.y_scale = y_scale;
}
pub fn ptr_motion(&mut self, x: f64, y: f64)
{
let (x, y) = (x * self.x_scale, y * self.y_scale);
self.mouse_x = x;
self.mouse_y = y;
let act = TouchAction { id: 0, act: PressAction::Pos(x, y), };
self.actions.push(act);
}
pub fn ptr_button(&mut self, state: wl_pointer::ButtonState)
{
match state {
wl_pointer::ButtonState::Pressed => {
let (x, y) = (self.mouse_x, self.mouse_y);
let act = TouchAction { id: 0, act: PressAction::Press(x, y), };
self.actions.push(act);
},
wl_pointer::ButtonState::Released => {
let act = TouchAction { id: 0, act: PressAction::Release, };
self.actions.push(act);
},
_ => eprintln!("warn: ignoring unknown pointer event"),
}
}
pub fn touch_press(&mut self, id: u32, x: f64, y: f64)
{
let id = id as usize + 1;
let (x, y) = (x * self.x_scale, y * self.y_scale);
let act = TouchAction { id, act: PressAction::Press(x, y), };
self.actions.push(act);
}
pub fn touch_pos(&mut self, id: u32, x: f64, y: f64)
{
let id = id as usize + 1;
let (x, y) = (x * self.x_scale, y * self.y_scale);
let act = TouchAction { id, act: PressAction::Pos(x, y), };
self.actions.push(act);
}
pub fn touch_release(&mut self, id: u32)
{
let id = id as usize + 1;
let act = TouchAction { id, act: PressAction::Release, };
self.actions.push(act);
}
pub fn commit(&mut self)
{
for action in &self.actions {
match action.act {
PressAction::Press(x, y) => {
self.button.press(action.id, x, y);
},
PressAction::Pos(x, y) => {
self.button.pos(action.id, x, y);
},
PressAction::Release => {
self.button.release(action.id);
},
}
}
// Reset the action for the next frame.
self.actions.clear();
}
}

179
src/wayland/surface.rs Normal file
View file

@ -0,0 +1,179 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Copyright (c) 2024, Richard Acayan. All rights reserved.
*/
use core::Display;
use core::imgref::ImgRefMut;
use core::rgb::alt::BGRA;
use std::io::Error;
use wayland::Buffer;
use wayland::wayland_client::protocol::wl_buffer::WlBuffer;
use wayland::wayland_client::protocol::wl_compositor::WlCompositor;
use wayland::wayland_client::protocol::wl_shm_pool::WlShmPool;
use wayland::wayland_client::protocol::wl_shm::WlShm;
use wayland::wayland_client::protocol::wl_surface::WlSurface;
use wayland::wayland_client::Dispatch;
use wayland::wayland_client::QueueHandle;
use wayland::wlr_layer_shell_unstable_v1::zwlr_layer_shell_v1;
use wayland::wlr_layer_shell_unstable_v1::zwlr_layer_surface_v1;
use wayland::fractional_scale_v1::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1;
use wayland::fractional_scale_v1::wp_fractional_scale_v1::WpFractionalScaleV1;
use wayland::viewporter::wp_viewporter::WpViewporter;
use wayland::viewporter::wp_viewport::WpViewport;
pub struct Surface<T> {
bufs: [Buffer<T>; 2],
surface: WlSurface,
layer_surf: zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
vp: Option<WpViewport>,
used_buf: usize,
configured: bool,
scale: u32,
}
const LAYER_ANCHOR: zwlr_layer_surface_v1::Anchor
= zwlr_layer_surface_v1::Anchor::from_bits_truncate(0)
.union(zwlr_layer_surface_v1::Anchor::Bottom)
.union(zwlr_layer_surface_v1::Anchor::Left)
.union(zwlr_layer_surface_v1::Anchor::Right);
impl<T: Dispatch<WlBuffer, u32>
+ Dispatch<WlShmPool, u32>
+ Dispatch<WlSurface, ()>
+ Dispatch<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, ()>
+ Dispatch<WpFractionalScaleV1, ()>
+ Dispatch<WpViewport, ()>
+ 'static> Surface<T> {
pub fn new(queue: QueueHandle<T>, shm: &WlShm, comp: &WlCompositor,
layer_shell: &zwlr_layer_shell_v1::ZwlrLayerShellV1,
frac_scale_man: Option<WpFractionalScaleManagerV1>,
vper: Option<WpViewporter>,
size: i32)
-> Result<Surface<T>, Error>
{
let buf1 = Buffer::new(queue.clone(), shm, 0)?;
let buf2 = Buffer::new(queue.clone(), shm, 1)?;
let surface = comp.create_surface(&queue, ());
let layer_surf = layer_shell.get_layer_surface(&surface, None,
zwlr_layer_shell_v1::Layer::Top,
String::from("osk-ufkbd"),
&queue, ());
frac_scale_man.map(|fsm| fsm.get_fractional_scale(&surface, &queue, ()));
let vp = vper.map(|vper| vper.get_viewport(&surface, &queue, ()));
layer_surf.set_size(0, size as u32);
layer_surf.set_exclusive_zone(size);
layer_surf.set_anchor(LAYER_ANCHOR);
surface.commit();
Ok(Surface {
bufs: [buf1, buf2],
surface,
layer_surf,
vp,
used_buf: 1,
configured: false,
scale: 120,
})
}
pub fn configured(&self) -> bool
{
self.configured
}
pub fn ack_configure(&mut self, serial: u32)
{
self.configured = true;
self.layer_surf.ack_configure(serial);
}
pub fn scale(&self) -> u32
{
self.scale
}
pub fn update_scale(&mut self, scale: u32)
{
self.scale = scale;
}
}
impl<T: Dispatch<WlBuffer, u32>
+ Dispatch<WlShmPool, u32>
+ Dispatch<WlSurface, ()>
+ Dispatch<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, ()>
+ Dispatch<WpFractionalScaleV1, ()>
+ Dispatch<WpViewport, ()>
+ 'static> Display for Surface<T> {
#[inline(always)]
fn size(&self) -> (u32, u32)
{
let (width, height) = self.bufs[self.used_buf].size();
(width as u32, height as u32)
}
fn begin(&mut self) -> ImgRefMut<BGRA<u8>>
{
for (id, buf) in self.bufs.iter_mut().enumerate() {
if let Some(img) = buf.consume(&self.surface) {
self.used_buf = id;
return img;
}
}
panic!("No buffers available")
}
fn resize(&mut self, width: u32, height: u32)
{
let width_unscaled = (width * 120 / self.scale) as i32;
let height_unscaled = (height * 120 / self.scale) as i32;
for buf in &mut self.bufs {
buf.resize(width as i32, height as i32).unwrap();
}
if let Some(vp) = &self.vp {
vp.set_source(0.0, 0.0, width as f64, height as f64);
vp.set_destination(width_unscaled, height_unscaled);
}
}
fn end(&mut self, x: u32, y: u32, width: u32, height: u32)
{
self.surface.damage_buffer(x as i32, y as i32, width as i32, height as i32);
self.surface.commit();
for (id, buf) in self.bufs.iter_mut().enumerate() {
if id != self.used_buf {
buf.release();
}
}
let (src, dest) = match self.used_buf {
0 => {
let [src, ref mut dest] = &mut self.bufs;
(src, dest)
},
1 => {
let [ref mut dest, src] = &mut self.bufs;
(src, dest)
},
2.. => panic!("Surface must only use two buffers"),
};
let img = src.image().sub_image(x as usize,
y as usize,
width as usize,
height as usize);
dest.writeback(&img, x as usize, y as usize);
}
}