Add touchscreen support with onscreen keyboard (#1)

Possible that framebuffer doesn't update without actual keypress (eg, volume down). But, works.
This commit is contained in:
Jon Dough
2025-02-10 05:03:49 +00:00
committed by GitHub
parent 8d0a317819
commit cc34a54cd9
13 changed files with 536 additions and 72 deletions

View File

@@ -3,19 +3,24 @@ use std::vec;
use std::collections::{ HashMap, VecDeque };
use std::fmt;
use std::boxed::Box;
use std::io::{ stdin, stdout, Write };
use std::io::{ stdin, stdout, BufReader, BufRead, Write };
use std::process::exit;
use std::cell::RefCell;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use std::env;
use std::process::{ Command, Stdio };
use linux_framebuffer::Framebuffer;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use termion::cursor;
use termion::{ clear, cursor };
use serde::{ Deserialize, Serialize };
use crate::framebuffer::{ FramebufferWriter, FramebufferInfo, Point, Dimensions, RGBColor };
use crate::themes::{ ThemeInfo, Themes, get_theme_info };
use crate::keyboard::{ KeyChar, key_to_char };
use crate::utils::{ min, key_to_char, point_inside };
use crate::messages::*;
use crate::proxy_window_like::ProxyWindowLike;
use crate::essential::desktop_background::DesktopBackground;
@@ -25,13 +30,40 @@ use crate::essential::workspace_indicator::WorkspaceIndicator;
use crate::essential::start_menu::StartMenu;
use crate::essential::about::About;
use crate::essential::help::Help;
use crate::essential::onscreen_keyboard::OnscreenKeyboard;
//use crate::logging::log;
//todo: a lot of the usize should be changed to u16
pub const TASKBAR_HEIGHT: usize = 38;
pub const INDICATOR_HEIGHT: usize = 20;
const WINDOW_TOP_HEIGHT: usize = 26;
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum KeyChar {
Press(char),
Alt(char),
Ctrl(char),
}
pub fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
let args: Vec<_> = env::args().collect();
let rotate = args.contains(&"rotate".to_string());
let framebuffer_info = if rotate {
FramebufferInfo {
byte_len: framebuffer_info.byte_len,
width: framebuffer_info.height,
height: framebuffer_info.width,
bytes_per_pixel: framebuffer_info.bytes_per_pixel,
stride: framebuffer_info.height,
old_stride: Some(framebuffer_info.stride),
}
} else {
framebuffer_info
};
let dimensions = [framebuffer_info.width, framebuffer_info.height];
println!("bg: {}x{}", dimensions[0], dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT);
@@ -40,36 +72,101 @@ pub fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
writer.init(framebuffer_info.clone());
let mut wm: WindowManager = WindowManager::new(writer, framebuffer, dimensions);
let mut wm: WindowManager = WindowManager::new(writer, framebuffer, dimensions, rotate);
let mut stdout = stdout().into_raw_mode().unwrap();
write!(stdout, "{}", clear::All).unwrap();
write!(stdout, "{}", cursor::Hide).unwrap();
stdout.flush().unwrap();
wm.draw(None, false);
let stdin = stdin().lock();
let mut stdout = stdout().into_raw_mode().unwrap();
let (tx, rx) = mpsc::channel();
write!(stdout, "{}", cursor::Hide).unwrap();
stdout.flush().unwrap();
let tx1 = tx.clone();
//read key presses
thread::spawn(move || {
let stdin = stdin().lock();
for c in stdin.keys() {
if let Some(kc) = key_to_char(c.unwrap()) {
//do not allow exit when locked unless debugging
//if kc == KeyChar::Alt('E') {
if kc == KeyChar::Alt('E') {
tx.send(ThreadMessage::Exit).unwrap();
} else {
tx.send(ThreadMessage::KeyChar(kc)).unwrap();
}
}
thread::sleep(Duration::from_millis(1));
}
});
for c in stdin.keys() {
if let Some(kc) = key_to_char(c.unwrap()) {
//do not allow exit when locked unless debugging
//if kc == KeyChar::Alt('E') {
if kc == KeyChar::Alt('E') && !wm.locked {
write!(stdout, "{}", cursor::Show).unwrap();
stdout.suspend_raw_mode().unwrap();
exit(0);
} else {
wm.handle_message(WindowManagerMessage::KeyChar(kc.clone()));
let touch = args.contains(&"touch".to_string());
//read touchscreen presses (hopefully)
thread::spawn(move || {
//spawn evtest, parse it for touch coords
if touch {
let mut evtest = Command::new("evtest").arg("/dev/input/by-path/first-touchscreen").stdout(Stdio::piped()).spawn().unwrap();
let reader = BufReader::new(evtest.stdout.as_mut().unwrap());
let mut x: Option<usize> = None;
let mut y: Option<usize> = None;
for line in reader.lines() {
let line = line.unwrap();
if line.contains(&"ABS_X), value ") || line.contains(&"ABS_Y), value ") {
let value: Vec<_> = line.split("), value ").collect();
let value = value[value.len() - 1].parse::<usize>().unwrap();
if line.contains(&"ABS_X") {
x = Some(value);
} else {
y = Some(value);
}
if x.is_some() && y.is_some() {
let (x2, y2) = if rotate {
(y.unwrap(), dimensions[1] - x.unwrap())
} else {
(x.unwrap(), y.unwrap())
};
//top right, clear
//useful sometimes, I think.
if x2 > dimensions[0] - 100 && y2 < 100 {
tx1.send(ThreadMessage::Clear).unwrap();
}
tx1.send(ThreadMessage::Touch(x2, y2)).unwrap();
x = None;
y = None;
}
}
thread::sleep(Duration::from_millis(1));
}
}
});
if touch {
//opens osk
wm.handle_message(WindowManagerMessage::Touch(1, 1));
}
for message in rx {
match message {
ThreadMessage::KeyChar(kc) => wm.handle_message(WindowManagerMessage::KeyChar(kc.clone())),
ThreadMessage::Touch(x, y) => wm.handle_message(WindowManagerMessage::Touch(x, y)),
ThreadMessage::Clear => {
write!(stdout, "{}", clear::All).unwrap();
stdout.flush().unwrap();
},
ThreadMessage::Exit => {
if !wm.locked {
write!(stdout, "{}", cursor::Show).unwrap();
stdout.suspend_raw_mode().unwrap();
exit(0);
}
},
};
}
//
}
pub fn min(one: usize, two: usize) -> usize {
if one > two { two } else { one }
}
#[derive(Debug, Serialize, Deserialize)]
@@ -89,6 +186,7 @@ pub enum WindowLikeType {
Taskbar,
StartMenu,
WorkspaceIndicator,
OnscreenKeyboard,
}
pub trait WindowLike {
@@ -133,8 +231,10 @@ impl fmt::Debug for WindowLikeInfo {
pub struct WindowManager {
writer: RefCell<FramebufferWriter>,
rotate: bool,
id_count: usize,
window_infos: Vec<WindowLikeInfo>,
osk: Option<WindowLikeInfo>,
dimensions: Dimensions,
theme: Themes,
focused_id: usize,
@@ -147,11 +247,13 @@ pub struct WindowManager {
//1 is up, 2 is down
impl WindowManager {
pub fn new(writer: FramebufferWriter, framebuffer: Framebuffer, dimensions: Dimensions) -> Self {
pub fn new(writer: FramebufferWriter, framebuffer: Framebuffer, dimensions: Dimensions, rotate: bool) -> Self {
let mut wm = WindowManager {
writer: RefCell::new(writer),
rotate,
id_count: 0,
window_infos: Vec::new(),
osk: None,
dimensions,
theme: Themes::Standard,
focused_id: 0,
@@ -169,10 +271,9 @@ impl WindowManager {
let dimensions = dimensions.unwrap_or(window_like.ideal_dimensions(self.dimensions));
self.id_count = self.id_count + 1;
let id = self.id_count;
self.focused_id = id;
window_like.handle_message(WindowMessage::Init(dimensions));
let dimensions = if window_like.subtype() == WindowLikeType::Window { [dimensions[0], dimensions[1] + WINDOW_TOP_HEIGHT] } else { dimensions };
self.window_infos.push(WindowLikeInfo {
let window_info = WindowLikeInfo {
id,
window_like,
top_left,
@@ -183,7 +284,13 @@ impl WindowManager {
Workspace::All
},
fullscreen: false,
});
};
if subtype == WindowLikeType::OnscreenKeyboard {
self.osk = Some(window_info);
} else {
self.focused_id = id;
self.window_infos.push(window_info);
}
}
fn get_focused_index(&self) -> Option<usize> {
@@ -215,6 +322,7 @@ impl WindowManager {
}
//if off_only is true, also handle request
//written confusingly but it works I promise
fn toggle_start_menu(&mut self, off_only: bool) -> WindowMessageResponse {
let start_menu_exists = self.window_infos.iter().find(|w| w.window_like.subtype() == WindowLikeType::StartMenu).is_some();
if (start_menu_exists && off_only) || !off_only {
@@ -518,22 +626,53 @@ impl WindowManager {
},
}
},
//
WindowManagerMessage::Touch(x, y) => {
if x < 100 && y < 100 {
//toggle onscreen keyboard if top left keyboard clicked
if self.osk.is_some() {
self.osk = None;
} else {
let osk = Box::new(OnscreenKeyboard::new());
let ideal_dimensions = osk.ideal_dimensions(self.dimensions);
self.add_window_like(osk, [175, self.dimensions[1] - TASKBAR_HEIGHT - 250], Some(ideal_dimensions));
}
WindowMessageResponse::JustRedraw
} else {
//see if in onscreen keyboard, if so send to it after offsetting coords
if self.osk.is_some() {
let osk = self.osk.as_mut().unwrap();
if point_inside([x, y], osk.top_left, osk.dimensions) {
osk.window_like.handle_message(WindowMessage::Touch(x - osk.top_left[0], y - osk.top_left[1]))
} else {
WindowMessageResponse::DoNothing
}
} else {
WindowMessageResponse::DoNothing
}
}
}
};
if response != WindowMessageResponse::DoNothing {
let is_key_char_request = response.is_key_char_request();
match response {
WindowMessageResponse::Request(request) => self.handle_request(request),
_ => {},
};
self.draw(redraw_ids, use_saved_buffer);
if !is_key_char_request {
self.draw(redraw_ids, use_saved_buffer);
}
}
}
pub fn handle_request(&mut self, request: WindowManagerRequest) {
let focused_index = self.get_focused_index().unwrap();
let subtype = self.window_infos[focused_index].window_like.subtype();
let subtype = if let Some(focused_index) = self.get_focused_index() {
Some(self.window_infos[focused_index].window_like.subtype())
} else {
None
};
match request {
WindowManagerRequest::OpenWindow(w) => {
let subtype = subtype.unwrap();
if subtype != WindowLikeType::Taskbar && subtype != WindowLikeType::StartMenu {
return;
}
@@ -565,6 +704,7 @@ impl WindowManager {
self.taskbar_update_windows();
},
WindowManagerRequest::CloseStartMenu => {
let subtype = subtype.unwrap();
if subtype != WindowLikeType::Taskbar && subtype != WindowLikeType::StartMenu {
return;
}
@@ -574,13 +714,13 @@ impl WindowManager {
}
},
WindowManagerRequest::Unlock => {
if subtype != WindowLikeType::LockScreen {
if subtype.unwrap() != WindowLikeType::LockScreen {
return;
}
self.unlock();
},
WindowManagerRequest::Lock => {
if subtype != WindowLikeType::StartMenu {
if subtype.unwrap() != WindowLikeType::StartMenu {
return;
}
self.lock();
@@ -588,6 +728,9 @@ impl WindowManager {
WindowManagerRequest::ClipboardCopy(content) => {
self.clipboard = Some(content);
},
WindowManagerRequest::DoKeyChar(kc) => {
self.handle_message(WindowManagerMessage::KeyChar(kc));
},
};
}
@@ -605,12 +748,15 @@ impl WindowManager {
}
//get windows to redraw
let redraw_ids = maybe_redraw_ids.unwrap_or(Vec::new());
let all_in_workspace = self.get_windows_in_workspace(true);
let mut all_in_workspace = self.get_windows_in_workspace(true);
if let Some(osk) = &self.osk {
all_in_workspace.push(osk);
}
let maybe_length = all_in_workspace.len();
let redraw_windows = all_in_workspace.iter().filter(|w| {
//basically, maybe_redraw_ids was None
if redraw_ids.len() > 0 {
redraw_ids.contains(&w.id)
redraw_ids.contains(&w.id) || w.window_like.subtype() == WindowLikeType::OnscreenKeyboard
} else {
true
}
@@ -697,6 +843,9 @@ impl WindowManager {
self.writer.borrow_mut().draw_buffer(window_info.top_left, window_dimensions[1], window_dimensions[0] * bytes_per_pixel, &window_writer.get_buffer());
w_index += 1;
}
self.framebuffer.write_frame(self.writer.borrow().get_buffer());
//could probably figure out a way to do borrow() when self.rotate is false but does it matter?
let mut writer_borrow = self.writer.borrow_mut();
let frame = if self.rotate { writer_borrow.get_transposed_buffer() } else { writer_borrow.get_buffer() };
self.framebuffer.write_frame(frame);
}
}