mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-11-15 21:32:26 +01:00
refactor(komorebic): the macOS komorebic uses the pan module of komorebic-core
komorebi-for-mac/komorebic/src/pan.rs (810 lines) is deleted: it differed from the Windows copy by three doc comments only (whkd wording versus a generic one, 21 changed lines), so the shared module keeps the Windows wording of those comments; no code differs. pan --stream runs komorebic_core::pan with a PanLink over the client connection. Verified: macOS fake-CC cargo check --locked --workspace --all-targets: 0 errors. The 16 pan tests of the macOS tree were the same tests as Windows'; they run in komorebic-core (27 passed). Not verified on macOS itself.
This commit is contained in:
1 parent
7c655cbc25
commit
0345fccba2
2 files changed
+11
-813
No files matched your search
@@ -14,6 +14,7 @@ use komorebic_core::cli::Meta;
|
|||||||
use komorebic_core::cli::command;
|
use komorebic_core::cli::command;
|
||||||
use komorebic_core::docgen::DocFormat;
|
use komorebic_core::docgen::DocFormat;
|
||||||
use komorebic_core::messages::Messages;
|
use komorebic_core::messages::Messages;
|
||||||
|
use komorebic_core::pan;
|
||||||
use komorebic_core::platform::macos::*;
|
use komorebic_core::platform::macos::*;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
@@ -30,8 +31,6 @@ use std::sync::atomic::Ordering;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use sysinfo::ProcessesToUpdate;
|
use sysinfo::ProcessesToUpdate;
|
||||||
|
|
||||||
mod pan;
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref HAS_CUSTOM_CONFIG_HOME: AtomicBool = AtomicBool::new(false);
|
static ref HAS_CUSTOM_CONFIG_HOME: AtomicBool = AtomicBool::new(false);
|
||||||
static ref HOME_DIR: PathBuf = {
|
static ref HOME_DIR: PathBuf = {
|
||||||
@@ -82,6 +81,15 @@ fn send_or_query(messages: Messages) -> eyre::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The connection to the daemon that the gesture stream tells what the fingers do over.
|
||||||
|
struct PanLink(komorebi_client::Connection);
|
||||||
|
|
||||||
|
impl pan::Link for PanLink {
|
||||||
|
fn send(&mut self, message: &SocketMessage) -> io::Result<()> {
|
||||||
|
self.0.send(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() -> eyre::Result<()> {
|
fn main() -> eyre::Result<()> {
|
||||||
let opts = komorebic_core::cli::parse::<MacosCommands>(&META);
|
let opts = komorebic_core::cli::parse::<MacosCommands>(&META);
|
||||||
|
|
||||||
@@ -775,7 +783,7 @@ exit 1
|
|||||||
reversed: args.reversed,
|
reversed: args.reversed,
|
||||||
paging: args.paging,
|
paging: args.paging,
|
||||||
},
|
},
|
||||||
&mut pan::Socket::new(komorebi_client::Connection::open),
|
&mut pan::Socket::new(|| komorebi_client::Connection::open().map(PanLink)),
|
||||||
)?;
|
)?;
|
||||||
} else if let Some(direction) = args.direction {
|
} else if let Some(direction) = args.direction {
|
||||||
send_message(&SocketMessage::Pan(direction))?;
|
send_message(&SocketMessage::Pan(direction))?;
|
||||||
|
|||||||
@@ -1,810 +0,0 @@
|
|||||||
//! Reading the stream of gestures that `komorebic pan --stream` is given, which is what a program
|
|
||||||
//! that reads a touchpad writes for a swipe of fingers, and telling komorebi what the fingers do.
|
|
||||||
//!
|
|
||||||
//! The format is the gesture stream that whkd writes on Windows, and anything that writes it can
|
|
||||||
//! be piped in: one message to a line, a name and then `key=value` fields in any order, the
|
|
||||||
//! distances in centimetres. A field that is not known is ignored, and so is a message that is not
|
|
||||||
//! known.
|
|
||||||
|
|
||||||
use komorebi_client::PanDirection;
|
|
||||||
use komorebi_client::PanGesture;
|
|
||||||
use komorebi_client::SocketMessage;
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
use std::io;
|
|
||||||
use std::io::BufRead;
|
|
||||||
use std::sync::mpsc;
|
|
||||||
|
|
||||||
/// How far the fingers go with `--paging` for each step, in centimetres
|
|
||||||
pub const PAGE_CM: f64 = 2.0;
|
|
||||||
|
|
||||||
/// One thing that the fingers do
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
||||||
pub enum Message {
|
|
||||||
Begin,
|
|
||||||
Move { dx: f64, dy: f64, t: f64 },
|
|
||||||
End,
|
|
||||||
Cancel,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// What a line of the stream comes to
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum Line {
|
|
||||||
Message(Message),
|
|
||||||
/// Nothing to do: a line with nothing in it, or a comment, or a message that is not known
|
|
||||||
Ignored,
|
|
||||||
/// A message that is known, and is not what it should be
|
|
||||||
Bad(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// What a line of the stream says
|
|
||||||
#[must_use]
|
|
||||||
pub fn parse_line(line: &str) -> Line {
|
|
||||||
let line = line.trim();
|
|
||||||
if line.is_empty() || line.starts_with('#') {
|
|
||||||
return Line::Ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut words = line.split_whitespace();
|
|
||||||
let Some(name) = words.next() else {
|
|
||||||
return Line::Ignored;
|
|
||||||
};
|
|
||||||
|
|
||||||
let fields: Vec<(&str, &str)> = words.filter_map(|word| word.split_once('=')).collect();
|
|
||||||
let number = |key: &str| -> Option<Result<f64, String>> {
|
|
||||||
fields
|
|
||||||
.iter()
|
|
||||||
.find(|(name, _)| *name == key)
|
|
||||||
.map(|(_, value)| {
|
|
||||||
value
|
|
||||||
.parse::<f64>()
|
|
||||||
.ok()
|
|
||||||
.filter(|number| number.is_finite())
|
|
||||||
.ok_or_else(|| format!("{key}={value} is not a number"))
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
match name {
|
|
||||||
"begin" => Line::Message(Message::Begin),
|
|
||||||
"end" => Line::Message(Message::End),
|
|
||||||
"cancel" => Line::Message(Message::Cancel),
|
|
||||||
"move" => {
|
|
||||||
let dx = match number("dx") {
|
|
||||||
Some(Ok(dx)) => dx,
|
|
||||||
Some(Err(error)) => return Line::Bad(error),
|
|
||||||
None => return Line::Bad(String::from("a move has no dx")),
|
|
||||||
};
|
|
||||||
let t = match number("t") {
|
|
||||||
Some(Ok(t)) => t,
|
|
||||||
Some(Err(error)) => return Line::Bad(error),
|
|
||||||
None => return Line::Bad(String::from("a move has no t")),
|
|
||||||
};
|
|
||||||
let dy = match number("dy") {
|
|
||||||
Some(Ok(dy)) => dy,
|
|
||||||
Some(Err(error)) => return Line::Bad(error),
|
|
||||||
None => 0.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
Line::Message(Message::Move { dx, dy, t })
|
|
||||||
}
|
|
||||||
_ => Line::Ignored,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// What `--stream` is asked to do with the distances
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub struct Options {
|
|
||||||
/// What the distances are multiplied by
|
|
||||||
pub sensitivity: f64,
|
|
||||||
/// The strip goes the other way to the fingers
|
|
||||||
pub reversed: bool,
|
|
||||||
/// One step for each [`PAGE_CM`] that the fingers go, and not the strip going with them
|
|
||||||
pub paging: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Options {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
sensitivity: 1.0,
|
|
||||||
reversed: false,
|
|
||||||
paging: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Works out what komorebi is told for what the fingers do
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Forwarder {
|
|
||||||
options: Options,
|
|
||||||
/// Whether a gesture is going on
|
|
||||||
active: bool,
|
|
||||||
/// How far the fingers have gone since the last step, with paging
|
|
||||||
page: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Forwarder {
|
|
||||||
#[must_use]
|
|
||||||
pub const fn new(options: Options) -> Self {
|
|
||||||
Self {
|
|
||||||
options,
|
|
||||||
active: false,
|
|
||||||
page: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// What komorebi is told for this. Fingers that move when there was no beginning, and an end
|
|
||||||
/// or a cancel for a gesture that is not going on, are not anything.
|
|
||||||
pub fn handle(&mut self, message: Message) -> Vec<SocketMessage> {
|
|
||||||
let gesture = |gesture| vec![SocketMessage::PanGesture(gesture)];
|
|
||||||
|
|
||||||
match message {
|
|
||||||
Message::Begin => {
|
|
||||||
self.active = true;
|
|
||||||
self.page = 0.0;
|
|
||||||
|
|
||||||
if self.options.paging {
|
|
||||||
Vec::new()
|
|
||||||
} else {
|
|
||||||
gesture(PanGesture::Begin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Message::Move { dx, dy, t } if self.active => {
|
|
||||||
let sign = if self.options.reversed { -1.0 } else { 1.0 };
|
|
||||||
let dx = dx * self.options.sensitivity * sign;
|
|
||||||
let dy = dy * self.options.sensitivity;
|
|
||||||
|
|
||||||
if self.options.paging {
|
|
||||||
self.page += dx;
|
|
||||||
self.pages()
|
|
||||||
} else {
|
|
||||||
gesture(PanGesture::Move { dx, dy, t })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Message::End if self.active => {
|
|
||||||
self.active = false;
|
|
||||||
|
|
||||||
if self.options.paging {
|
|
||||||
Vec::new()
|
|
||||||
} else {
|
|
||||||
gesture(PanGesture::End)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Message::Cancel if self.active => {
|
|
||||||
self.active = false;
|
|
||||||
|
|
||||||
if self.options.paging {
|
|
||||||
Vec::new()
|
|
||||||
} else {
|
|
||||||
gesture(PanGesture::Cancel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The stream is over: a gesture that is still going on was not finished, so it is given up
|
|
||||||
pub fn finish(&mut self) -> Vec<SocketMessage> {
|
|
||||||
let unfinished = self.active && !self.options.paging;
|
|
||||||
self.active = false;
|
|
||||||
|
|
||||||
if unfinished {
|
|
||||||
vec![SocketMessage::PanGesture(PanGesture::Cancel)]
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The steps that what the fingers have gone since the last one comes to. Fingers that go to
|
|
||||||
/// the right take the strip with them, which is to look to the left.
|
|
||||||
fn pages(&mut self) -> Vec<SocketMessage> {
|
|
||||||
let mut steps = Vec::new();
|
|
||||||
|
|
||||||
while self.page.abs() >= PAGE_CM {
|
|
||||||
let direction = if self.page > 0.0 {
|
|
||||||
PanDirection::Left
|
|
||||||
} else {
|
|
||||||
PanDirection::Right
|
|
||||||
};
|
|
||||||
|
|
||||||
self.page -= PAGE_CM.copysign(self.page);
|
|
||||||
steps.push(SocketMessage::Pan(direction));
|
|
||||||
}
|
|
||||||
|
|
||||||
steps
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The next message to handle, with the moves that follow it added to it if it is one: when
|
|
||||||
/// the stream comes faster than it can be handled, the distances add up and the time is the
|
|
||||||
/// last one, as whkd says that they may.
|
|
||||||
pub fn next_merged(queue: &mut VecDeque<Message>) -> Option<Message> {
|
|
||||||
let first = queue.pop_front()?;
|
|
||||||
let Message::Move {
|
|
||||||
mut dx,
|
|
||||||
mut dy,
|
|
||||||
mut t,
|
|
||||||
} = first
|
|
||||||
else {
|
|
||||||
return Some(first);
|
|
||||||
};
|
|
||||||
|
|
||||||
while let Some(Message::Move {
|
|
||||||
dx: more_dx,
|
|
||||||
dy: more_dy,
|
|
||||||
t: later,
|
|
||||||
}) = queue.front().copied()
|
|
||||||
{
|
|
||||||
dx += more_dx;
|
|
||||||
dy += more_dy;
|
|
||||||
t = later;
|
|
||||||
queue.pop_front();
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Message::Move { dx, dy, t })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Where komorebi is told what the fingers do
|
|
||||||
pub trait Sink {
|
|
||||||
fn send(&mut self, message: &SocketMessage) -> io::Result<()>;
|
|
||||||
|
|
||||||
/// The gesture that was being told is over
|
|
||||||
fn gesture_over(&mut self);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A way of telling komorebi things, in order
|
|
||||||
pub trait Link {
|
|
||||||
fn send(&mut self, message: &SocketMessage) -> io::Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Link for komorebi_client::Connection {
|
|
||||||
fn send(&mut self, message: &SocketMessage) -> io::Result<()> {
|
|
||||||
komorebi_client::Connection::send(self, message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tells komorebi what the fingers do over one connection for each gesture. Messages that are
|
|
||||||
/// sent on a connection each are not handled in the order that they were sent, so a gesture is
|
|
||||||
/// told on one, which is made when it is first needed and let go of when the gesture is over.
|
|
||||||
/// If it cannot be written to the connection is made again once, as komorebi may have let go of
|
|
||||||
/// one that nothing has been written to for a while.
|
|
||||||
pub struct Socket<L, F> {
|
|
||||||
open: F,
|
|
||||||
link: Option<L>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<L: Link, F: FnMut() -> io::Result<L>> Socket<L, F> {
|
|
||||||
pub const fn new(open: F) -> Self {
|
|
||||||
Self { open, link: None }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_send(&mut self, message: &SocketMessage) -> io::Result<()> {
|
|
||||||
if self.link.is_none() {
|
|
||||||
self.link = Some((self.open)()?);
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.link.as_mut() {
|
|
||||||
Some(link) => link.send(message),
|
|
||||||
None => Ok(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<L: Link, F: FnMut() -> io::Result<L>> Sink for Socket<L, F> {
|
|
||||||
fn send(&mut self, message: &SocketMessage) -> io::Result<()> {
|
|
||||||
if self.try_send(message).is_err() {
|
|
||||||
self.link = None;
|
|
||||||
return self.try_send(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gesture_over(&mut self) {
|
|
||||||
self.link = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read gestures from `input` until it closes and tell komorebi what the fingers do with `sink`
|
|
||||||
pub fn run(
|
|
||||||
input: impl BufRead + Send + 'static,
|
|
||||||
options: Options,
|
|
||||||
sink: &mut impl Sink,
|
|
||||||
) -> io::Result<()> {
|
|
||||||
let (sender, receiver) = mpsc::channel();
|
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
for line in input.lines() {
|
|
||||||
let Ok(line) = line else {
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
|
|
||||||
match parse_line(&line) {
|
|
||||||
Line::Message(message) => {
|
|
||||||
if sender.send(message).is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Line::Ignored => {}
|
|
||||||
Line::Bad(why) => eprintln!("pan: {why}: {line}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut forwarder = Forwarder::new(options);
|
|
||||||
let mut queue = VecDeque::new();
|
|
||||||
|
|
||||||
while let Ok(first) = receiver.recv() {
|
|
||||||
queue.push_back(first);
|
|
||||||
queue.extend(receiver.try_iter());
|
|
||||||
|
|
||||||
while let Some(message) = next_merged(&mut queue) {
|
|
||||||
let over = matches!(message, Message::End | Message::Cancel);
|
|
||||||
|
|
||||||
for told in forwarder.handle(message) {
|
|
||||||
sink.send(&told)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if over {
|
|
||||||
sink.gesture_over();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for told in forwarder.finish() {
|
|
||||||
sink.send(&told)?;
|
|
||||||
}
|
|
||||||
sink.gesture_over();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
const TRANSCRIPT: &str = include_str!("../fixtures/gesture-stream.transcript.txt");
|
|
||||||
|
|
||||||
fn gestures(told: &[SocketMessage]) -> Vec<PanGesture> {
|
|
||||||
told.iter()
|
|
||||||
.filter_map(|message| match message {
|
|
||||||
SocketMessage::PanGesture(gesture) => Some(*gesture),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn messages(text: &str) -> Vec<Message> {
|
|
||||||
text.lines()
|
|
||||||
.filter_map(|line| match parse_line(line) {
|
|
||||||
Line::Message(message) => Some(message),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// What is told, and how many gestures have been over
|
|
||||||
#[derive(Default)]
|
|
||||||
struct Recorder {
|
|
||||||
told: Vec<SocketMessage>,
|
|
||||||
over: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sink for Recorder {
|
|
||||||
fn send(&mut self, message: &SocketMessage) -> io::Result<()> {
|
|
||||||
self.told.push(message.clone());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gesture_over(&mut self) {
|
|
||||||
self.over += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A link that remembers what it was written, and whose connections are numbered
|
|
||||||
struct Wire {
|
|
||||||
connection: usize,
|
|
||||||
written: Written,
|
|
||||||
fail_next: Failing,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Link for Wire {
|
|
||||||
fn send(&mut self, message: &SocketMessage) -> io::Result<()> {
|
|
||||||
if self.fail_next.replace(false) {
|
|
||||||
return Err(io::Error::new(io::ErrorKind::BrokenPipe, "let go of"));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.written
|
|
||||||
.borrow_mut()
|
|
||||||
.push((self.connection, message.clone()));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Written = std::rc::Rc<std::cell::RefCell<Vec<(usize, SocketMessage)>>>;
|
|
||||||
|
|
||||||
type Failing = std::rc::Rc<std::cell::Cell<bool>>;
|
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
fn wire() -> (
|
|
||||||
Socket<Wire, impl FnMut() -> io::Result<Wire>>,
|
|
||||||
Written,
|
|
||||||
Failing,
|
|
||||||
) {
|
|
||||||
let written = std::rc::Rc::new(std::cell::RefCell::new(Vec::new()));
|
|
||||||
let fail_next = std::rc::Rc::new(std::cell::Cell::new(false));
|
|
||||||
let mut made = 0;
|
|
||||||
|
|
||||||
let socket = Socket::new({
|
|
||||||
let written = written.clone();
|
|
||||||
let fail_next = fail_next.clone();
|
|
||||||
move || {
|
|
||||||
made += 1;
|
|
||||||
Ok(Wire {
|
|
||||||
connection: made,
|
|
||||||
written: written.clone(),
|
|
||||||
fail_next: fail_next.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
(socket, written, fail_next)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_gesture_is_told_on_one_connection_and_the_next_has_its_own() {
|
|
||||||
let (mut socket, written, _) = wire();
|
|
||||||
let message = |gesture| SocketMessage::PanGesture(gesture);
|
|
||||||
let moved = PanGesture::Move {
|
|
||||||
dx: 1.0,
|
|
||||||
dy: 0.0,
|
|
||||||
t: 1.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
socket.send(&message(PanGesture::Begin)).unwrap();
|
|
||||||
socket.send(&message(moved)).unwrap();
|
|
||||||
socket.send(&message(moved)).unwrap();
|
|
||||||
socket.send(&message(PanGesture::End)).unwrap();
|
|
||||||
socket.gesture_over();
|
|
||||||
socket.send(&message(PanGesture::Begin)).unwrap();
|
|
||||||
|
|
||||||
let connections: Vec<_> = written.borrow().iter().map(|(number, _)| *number).collect();
|
|
||||||
assert_eq!(connections, vec![1, 1, 1, 1, 2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_connection_that_komorebi_let_go_of_is_made_again_once() {
|
|
||||||
let (mut socket, written, fail_next) = wire();
|
|
||||||
let message = SocketMessage::PanGesture(PanGesture::Begin);
|
|
||||||
|
|
||||||
socket.send(&message).unwrap();
|
|
||||||
fail_next.set(true);
|
|
||||||
socket.send(&message).unwrap();
|
|
||||||
|
|
||||||
let connections: Vec<_> = written.borrow().iter().map(|(number, _)| *number).collect();
|
|
||||||
assert_eq!(
|
|
||||||
connections,
|
|
||||||
vec![1, 2],
|
|
||||||
"the second was written on a new one"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_message_that_cannot_be_written_twice_is_an_error() {
|
|
||||||
let mut socket = Socket::new(|| -> io::Result<Wire> {
|
|
||||||
Err(io::Error::new(io::ErrorKind::NotFound, "no komorebi"))
|
|
||||||
});
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
socket
|
|
||||||
.send(&SocketMessage::PanGesture(PanGesture::Begin))
|
|
||||||
.is_err()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn what_is_told_ends_each_gesture_that_ends_and_the_stream_that_stops() {
|
|
||||||
let input = b"begin\nmove dx=1 t=1\nend\nbegin\nmove dx=1 t=2\n".to_vec();
|
|
||||||
let mut sink = Recorder::default();
|
|
||||||
|
|
||||||
run(io::Cursor::new(input), Options::default(), &mut sink).unwrap();
|
|
||||||
|
|
||||||
// the first ended, and the second was given up when the stream stopped
|
|
||||||
assert_eq!(sink.over, 2);
|
|
||||||
assert_eq!(gestures(&sink.told).last(), Some(&PanGesture::Cancel));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn step(dx: f64) -> Message {
|
|
||||||
Message::Move {
|
|
||||||
dx,
|
|
||||||
dy: 0.0,
|
|
||||||
t: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn what_whkd_writes_is_read_as_it_was_written() {
|
|
||||||
assert_eq!(
|
|
||||||
messages(TRANSCRIPT),
|
|
||||||
vec![
|
|
||||||
Message::Begin,
|
|
||||||
Message::Move {
|
|
||||||
dx: 0.42,
|
|
||||||
dy: -0.03,
|
|
||||||
t: 118_345.0
|
|
||||||
},
|
|
||||||
Message::Move {
|
|
||||||
dx: 0.51,
|
|
||||||
dy: -0.02,
|
|
||||||
t: 118_353.0
|
|
||||||
},
|
|
||||||
Message::Move {
|
|
||||||
dx: 0.47,
|
|
||||||
dy: 0.0,
|
|
||||||
t: 118_361.0
|
|
||||||
},
|
|
||||||
Message::End,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// What whkd writes for a swipe of three fingers that goes on for about two seconds, a
|
|
||||||
/// flick, and one that eases to a stop: `whkd touchpad --demo <name> --instant`
|
|
||||||
const LONG: &str = include_str!("../fixtures/pan3-long.transcript.txt");
|
|
||||||
const FLICK: &str = include_str!("../fixtures/pan3-flick.transcript.txt");
|
|
||||||
const SLOW: &str = include_str!("../fixtures/pan3-slow.transcript.txt");
|
|
||||||
|
|
||||||
/// How many lines of a transcript are not part of the stream: whkd's demo says which swipe
|
|
||||||
/// it saw, as `swipe swipe3right`, among them, and nothing says that the program that it
|
|
||||||
/// runs is told it, so it is ignored like any other message that is not known
|
|
||||||
fn others(transcript: &str) -> usize {
|
|
||||||
transcript
|
|
||||||
.lines()
|
|
||||||
.filter(|line| line.starts_with("swipe "))
|
|
||||||
.count()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn every_line_that_whkd_writes_for_a_swipe_is_read_and_nothing_is_said_to_be_wrong() {
|
|
||||||
for (transcript, lines) in [(LONG, 218), (FLICK, 18), (SLOW, 67)] {
|
|
||||||
assert_eq!(transcript.lines().count(), lines);
|
|
||||||
|
|
||||||
for line in transcript.lines() {
|
|
||||||
match parse_line(line) {
|
|
||||||
Line::Message(_) => {}
|
|
||||||
Line::Ignored => assert!(line.starts_with("swipe "), "ignored: {line}"),
|
|
||||||
Line::Bad(why) => panic!("{why}: {line}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let messages = messages(transcript);
|
|
||||||
assert_eq!(messages.first(), Some(&Message::Begin));
|
|
||||||
assert_eq!(messages.last(), Some(&Message::End));
|
|
||||||
assert_eq!(messages.len(), lines - others(transcript));
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(others(LONG) + others(FLICK) + others(SLOW), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_swipe_is_told_to_komorebi_move_for_move() {
|
|
||||||
for transcript in [LONG, FLICK, SLOW] {
|
|
||||||
let mut forwarder = Forwarder::new(Options::default());
|
|
||||||
let told: Vec<_> = messages(transcript)
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(|message| forwarder.handle(message))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let gestures = gestures(&told);
|
|
||||||
assert_eq!(
|
|
||||||
gestures.len(),
|
|
||||||
transcript.lines().count() - others(transcript)
|
|
||||||
);
|
|
||||||
assert_eq!(gestures.first(), Some(&PanGesture::Begin));
|
|
||||||
assert_eq!(gestures.last(), Some(&PanGesture::End));
|
|
||||||
|
|
||||||
// the distances add up to how far the fingers went across the touchpad
|
|
||||||
let across: f64 = gestures
|
|
||||||
.iter()
|
|
||||||
.filter_map(|gesture| match gesture {
|
|
||||||
PanGesture::Move { dx, .. } => Some(*dx),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.sum();
|
|
||||||
assert!(across > 1.0, "{across}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_field_that_is_not_known_is_not_anything_and_nor_is_a_message() {
|
|
||||||
// x, y, w, h, and a field of the future, which whkd says to ignore without a word
|
|
||||||
assert_eq!(
|
|
||||||
parse_line("move dx=1.5 dy=0.25 x=4.420 y=1.970 t=10 pressure=0.7"),
|
|
||||||
Line::Message(Message::Move {
|
|
||||||
dx: 1.5,
|
|
||||||
dy: 0.25,
|
|
||||||
t: 10.0
|
|
||||||
})
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse_line("begin fingers=3 x=4.000 y=2.000 w=10.500 h=7.200 t=118337"),
|
|
||||||
Line::Message(Message::Begin)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse_line("end x=5.4 y=1.95 t=118369"),
|
|
||||||
Line::Message(Message::End)
|
|
||||||
);
|
|
||||||
assert_eq!(parse_line("pinch scale=1.1"), Line::Ignored);
|
|
||||||
assert_eq!(parse_line(""), Line::Ignored);
|
|
||||||
assert_eq!(parse_line(" "), Line::Ignored);
|
|
||||||
assert_eq!(parse_line("# a comment"), Line::Ignored);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_move_that_is_not_what_it_should_be_is_said_to_be() {
|
|
||||||
assert!(matches!(parse_line("move dy=1 t=3"), Line::Bad(_)), "no dx");
|
|
||||||
assert!(matches!(parse_line("move dx=1 dy=1"), Line::Bad(_)), "no t");
|
|
||||||
assert!(matches!(parse_line("move dx=wide t=3"), Line::Bad(_)));
|
|
||||||
assert!(matches!(parse_line("move dx=NaN t=3"), Line::Bad(_)));
|
|
||||||
assert_eq!(
|
|
||||||
parse_line("move dx=2 t=3"),
|
|
||||||
Line::Message(Message::Move {
|
|
||||||
dx: 2.0,
|
|
||||||
dy: 0.0,
|
|
||||||
t: 3.0
|
|
||||||
}),
|
|
||||||
"dy is not needed"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn the_strip_goes_with_the_fingers_and_the_times_are_told_as_they_are() {
|
|
||||||
let mut forwarder = Forwarder::new(Options::default());
|
|
||||||
let told: Vec<_> = messages(TRANSCRIPT)
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(|message| forwarder.handle(message))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let gestures = gestures(&told);
|
|
||||||
assert_eq!(gestures.len(), 5);
|
|
||||||
assert_eq!(gestures[0], PanGesture::Begin);
|
|
||||||
assert_eq!(
|
|
||||||
gestures[1],
|
|
||||||
PanGesture::Move {
|
|
||||||
dx: 0.42,
|
|
||||||
dy: -0.03,
|
|
||||||
t: 118_345.0
|
|
||||||
}
|
|
||||||
);
|
|
||||||
assert_eq!(gestures[4], PanGesture::End);
|
|
||||||
assert!(
|
|
||||||
forwarder.finish().is_empty(),
|
|
||||||
"it ended, so nothing is left"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sensitivity_scales_the_distances_and_reversing_flips_the_way_it_goes() {
|
|
||||||
let mut forwarder = Forwarder::new(Options {
|
|
||||||
sensitivity: 2.0,
|
|
||||||
reversed: true,
|
|
||||||
paging: false,
|
|
||||||
});
|
|
||||||
forwarder.handle(Message::Begin);
|
|
||||||
|
|
||||||
let told = forwarder.handle(Message::Move {
|
|
||||||
dx: 1.0,
|
|
||||||
dy: 0.5,
|
|
||||||
t: 1.0,
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
gestures(&told),
|
|
||||||
vec![PanGesture::Move {
|
|
||||||
dx: -2.0,
|
|
||||||
dy: 1.0,
|
|
||||||
t: 1.0
|
|
||||||
}]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn what_has_no_beginning_is_not_anything() {
|
|
||||||
let mut forwarder = Forwarder::new(Options::default());
|
|
||||||
|
|
||||||
assert!(forwarder.handle(step(1.0)).is_empty());
|
|
||||||
assert!(forwarder.handle(Message::End).is_empty());
|
|
||||||
assert!(forwarder.handle(Message::Cancel).is_empty());
|
|
||||||
assert!(forwarder.finish().is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_stream_that_stops_in_the_middle_of_a_gesture_gives_it_up() {
|
|
||||||
let mut forwarder = Forwarder::new(Options::default());
|
|
||||||
forwarder.handle(Message::Begin);
|
|
||||||
forwarder.handle(step(1.0));
|
|
||||||
|
|
||||||
assert_eq!(gestures(&forwarder.finish()), vec![PanGesture::Cancel]);
|
|
||||||
assert!(forwarder.finish().is_empty(), "once");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn fingers_that_go_far_enough_take_a_step_each_with_paging() {
|
|
||||||
let mut forwarder = Forwarder::new(Options {
|
|
||||||
paging: true,
|
|
||||||
..Options::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
assert!(forwarder.handle(Message::Begin).is_empty());
|
|
||||||
assert!(forwarder.handle(step(1.5)).is_empty(), "not far enough");
|
|
||||||
|
|
||||||
// 1.5 and 0.7 is 2.2: a step to the left, as the strip goes right with the fingers
|
|
||||||
let told = forwarder.handle(step(0.7));
|
|
||||||
assert!(matches!(
|
|
||||||
told.as_slice(),
|
|
||||||
[SocketMessage::Pan(PanDirection::Left)]
|
|
||||||
));
|
|
||||||
|
|
||||||
// 0.2 is left over, and with 4.0 more it is two steps
|
|
||||||
let told = forwarder.handle(step(4.0));
|
|
||||||
assert_eq!(told.len(), 2);
|
|
||||||
|
|
||||||
// and the other way: 0.2 and 8.5 back is 8.3, which is four
|
|
||||||
let told = forwarder.handle(step(-8.5));
|
|
||||||
assert_eq!(told.len(), 4);
|
|
||||||
assert!(
|
|
||||||
told.iter()
|
|
||||||
.all(|message| matches!(message, SocketMessage::Pan(PanDirection::Right)))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(forwarder.handle(Message::End).is_empty());
|
|
||||||
assert!(forwarder.finish().is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn moves_that_wait_are_added_up_and_nothing_else_is_put_out_of_its_place() {
|
|
||||||
let mut queue: VecDeque<Message> = [
|
|
||||||
Message::Begin,
|
|
||||||
Message::Move {
|
|
||||||
dx: 1.0,
|
|
||||||
dy: 0.5,
|
|
||||||
t: 1.0,
|
|
||||||
},
|
|
||||||
Message::Move {
|
|
||||||
dx: 2.0,
|
|
||||||
dy: 0.25,
|
|
||||||
t: 2.0,
|
|
||||||
},
|
|
||||||
Message::Move {
|
|
||||||
dx: 4.0,
|
|
||||||
dy: 0.0,
|
|
||||||
t: 3.0,
|
|
||||||
},
|
|
||||||
Message::End,
|
|
||||||
]
|
|
||||||
.into();
|
|
||||||
|
|
||||||
assert_eq!(next_merged(&mut queue), Some(Message::Begin));
|
|
||||||
assert_eq!(
|
|
||||||
next_merged(&mut queue),
|
|
||||||
Some(Message::Move {
|
|
||||||
dx: 7.0,
|
|
||||||
dy: 0.75,
|
|
||||||
t: 3.0
|
|
||||||
})
|
|
||||||
);
|
|
||||||
assert_eq!(next_merged(&mut queue), Some(Message::End));
|
|
||||||
assert_eq!(next_merged(&mut queue), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_stream_is_told_to_komorebi_in_order_and_given_up_if_it_stops() {
|
|
||||||
let input = b"begin fingers=3\nmove dx=1.0 dy=0 t=1\nmove dx=2.0 dy=0 t=2\n".to_vec();
|
|
||||||
let mut sink = Recorder::default();
|
|
||||||
|
|
||||||
run(io::Cursor::new(input), Options::default(), &mut sink).unwrap();
|
|
||||||
|
|
||||||
let gestures = gestures(&sink.told);
|
|
||||||
assert_eq!(gestures[0], PanGesture::Begin);
|
|
||||||
assert!(matches!(gestures[1], PanGesture::Move { .. }));
|
|
||||||
assert_eq!(gestures.last(), Some(&PanGesture::Cancel));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user