Skip to content

Commit

Permalink
Add initial codebase (#4)
Browse files Browse the repository at this point in the history
* Add Cargo.toml

* Add actions module

* Add events module

* Add main.rs
  • Loading branch information
diego-plan9 committed Jul 19, 2021
1 parent b56fc72 commit 30f1216
Show file tree
Hide file tree
Showing 8 changed files with 502 additions and 0 deletions.
19 changes: 19 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "lillinput"
version = "0.1.0"
authors = ["Diego M. Rodríguez <diego@moreda.io>"]
edition = "2018"
description = "Connect libinput gestures to i3 and others"
repository = "https://github.com/diego-plan9/lillinput/"
license = "BSD-3-Clause"
keywords = ["i3", "swipe", "touchpad", "x11", "libinput", "gestures"]
categories = ["command-line-utilities", "gui"]

[dependencies]
input = "0.5.0"
libc = "0.2.84"
i3ipc = "0.10.1"
filedescriptor = "0.7.3"
clap = "3.0.0-beta.2"
strum = { version = "0.21", features = ["derive"] }
shlex = "1.0.0"
27 changes: 27 additions & 0 deletions src/actions/commandaction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//! Action for executing commands.

use super::{Action, ActionExt};
use shlex::split;
use std::process::Command;

pub struct CommandAction {
command: String,
}

impl Action for CommandAction {
fn execute_command(&mut self) {
// Perform the command, if specified.
let split_commands = split(&self.command).unwrap();
// TODO: capture result gracefully.
Command::new(&split_commands[0])
.args(&split_commands[1..])
.output()
.expect("Failed to execute command");
}
}

impl ActionExt for CommandAction {
fn new(command: String) -> CommandAction {
CommandAction { command }
}
}
147 changes: 147 additions & 0 deletions src/actions/controller.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
//! Controller for actions.

use super::commandaction::CommandAction;
use super::i3action::{I3Action, I3ActionExt};
use super::{Action, ActionChoices, ActionController, ActionEvents, ActionExt, ActionMap, Opts};
use i3ipc::I3Connection;

use std::cell::RefCell;
use std::rc::Rc;
use std::str::FromStr;

impl ActionController for ActionMap {
fn new(opts: &Opts) -> Self {
// Create the I3 connection if needed.
let connection = match opts
.enabled_actions
.contains(&ActionChoices::I3.to_string())
{
true => match I3Connection::connect() {
Ok(mut conn) => {
println!(
"i3: opened connection ... ({:?})",
conn.get_version().unwrap().human_readable
);
Some(Rc::new(RefCell::new(conn)))
}
Err(error) => {
println!("i3: could not establish a connection: {:?}", error);
None
}
},
false => None,
};

ActionMap {
threshold: opts.threshold,
connection,
swipe_left: vec![],
swipe_right: vec![],
swipe_up: vec![],
swipe_down: vec![],
}
}

fn populate_actions(&mut self, opts: &Opts) {
/// Add actions to a destination vector.
///
/// # Arguments
///
/// * `arguments` - list of command line arguments
/// * `destination` - vector to append actions to
/// * `connection` - optional i3 connection
fn parse_action_list(
arguments: &[String],
destination: &mut Vec<Box<dyn Action>>,
connection: &Option<Rc<RefCell<I3Connection>>>,
) {
for value in arguments.iter() {
// Split the arguments, in the form "{type}:{value}".
let mut splitter = value.splitn(2, ':');
let action_type = splitter.next().unwrap();
let action_value = splitter.next().unwrap();

// Create new actions and add them to the controller.
match ActionChoices::from_str(action_type) {
Ok(ActionChoices::Command) => {
destination.push(Box::new(CommandAction::new(action_value.to_string())));
}
Ok(ActionChoices::I3) => match connection {
Some(conn) => {
destination.push(Box::new(I3Action::new(
action_value.to_string(),
Rc::clone(&conn),
)));
}
None => {
println!("ignoring i3 action, as the i3 connection could not be set.")
}
},
Err(_) => {}
}
}
}

parse_action_list(&opts.swipe_left_3, &mut self.swipe_left, &self.connection);
parse_action_list(&opts.swipe_right_3, &mut self.swipe_right, &self.connection);
parse_action_list(&opts.swipe_up_3, &mut self.swipe_up, &self.connection);
parse_action_list(&opts.swipe_down_3, &mut self.swipe_down, &self.connection);

// Print information.
println!(
"Controller started: {:?}/{:?}/{:?}/{:?} actions enabled",
self.swipe_left.len(),
self.swipe_right.len(),
self.swipe_up.len(),
self.swipe_down.len(),
);
}

#[allow(clippy::collapsible_else_if)]
fn receive_end_event(&mut self, dx: &f64, dy: &f64) {
// Avoid acting if the displacement is below the threshold.
if dx.abs() < self.threshold && dy.abs() < self.threshold {
return;
}

// Determine the command for the event.
let command: ActionEvents;
if dx.abs() > dy.abs() {
if dx > &0.0 {
command = ActionEvents::ThreeFingerSwipeRight
} else {
command = ActionEvents::ThreeFingerSwipeLeft
}
} else {
if dy > &0.0 {
command = ActionEvents::ThreeFingerSwipeUp
} else {
command = ActionEvents::ThreeFingerSwipeDown
}
}

// Invoke actions.
match command {
ActionEvents::ThreeFingerSwipeLeft => {
for action in self.swipe_left.iter_mut() {
action.execute_command();
}
}
ActionEvents::ThreeFingerSwipeRight => {
for action in self.swipe_right.iter_mut() {
action.execute_command();
}
}
ActionEvents::ThreeFingerSwipeUp => {
for action in self.swipe_up.iter_mut() {
action.execute_command();
}
}
ActionEvents::ThreeFingerSwipeDown => {
for action in self.swipe_down.iter_mut() {
action.execute_command();
}
}
}
}
}
36 changes: 36 additions & 0 deletions src/actions/i3action.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! Action for interacting with `i3`.

use super::Action;
use i3ipc::I3Connection;

use std::cell::RefCell;
use std::rc::Rc;

pub struct I3Action {
connection: Rc<RefCell<I3Connection>>,
command: String,
}

impl Action for I3Action {
fn execute_command(&mut self) {
// Perform the command, if specified.
// TODO: capture result.
Rc::clone(&self.connection)
.borrow_mut()
.run_command(&self.command)
.unwrap();
}
}

pub trait I3ActionExt {
fn new(command: String, connection: Rc<RefCell<I3Connection>>) -> Self;
}

impl I3ActionExt for I3Action {
fn new(command: String, connection: Rc<RefCell<I3Connection>>) -> Self {
I3Action {
connection,
command,
}
}
}
61 changes: 61 additions & 0 deletions src/actions/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//! Traits for actions.
//!
//! Provides the interface for defining `Action`s that handle the different
//! events.

pub mod commandaction;
pub mod controller;
pub mod i3action;

use super::{ActionChoices, ActionEvents, Opts};
use i3ipc::I3Connection;

use std::cell::RefCell;
use std::rc::Rc;

/// Maps between events and actions.
pub struct ActionMap {
threshold: f64,
connection: Option<Rc<RefCell<I3Connection>>>,
swipe_left: Vec<Box<dyn Action>>,
swipe_right: Vec<Box<dyn Action>>,
swipe_up: Vec<Box<dyn Action>>,
swipe_down: Vec<Box<dyn Action>>,
}

/// Controller that connects events and actions.
pub trait ActionController {
fn new(opts: &Opts) -> Self;

/// Create the individual actions used by this controller.
///
/// Parse the command line arguments and add the individual actions to
/// the internal structures in this controller.
///
/// # Arguments
///
/// * `self` - action controller.
/// * `opts` - command line arguments.
fn populate_actions(&mut self, opts: &Opts);

/// Receive the end of swipe gesture event.
///
/// # Arguments
///
/// * `self` - action controller.
/// * `dx` - the current position in the `x` axis
/// * `dy` - the current position in the `y` axis
fn receive_end_event(&mut self, dx: &f64, dy: &f64);
}

/// Action handler for events.
pub trait Action {
/// Execute the command for this action.
fn execute_command(&mut self);
}

/// Extended trait for action handler for events.
pub trait ActionExt {
/// Return a new action.
fn new(command: String) -> Self;
}
32 changes: 32 additions & 0 deletions src/events/libinput.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//! Components for interacting with `libinput`.

use std::fs::{File, OpenOptions};
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd};
use std::path::Path;

use input::LibinputInterface;

use libc::{O_RDONLY, O_RDWR, O_WRONLY};

/// Struct for libinput interface.
pub struct Interface;

impl LibinputInterface for Interface {
#[allow(clippy::bad_bit_mask)]
fn open_restricted(&mut self, path: &Path, flags: i32) -> Result<RawFd, i32> {
OpenOptions::new()
.custom_flags(flags)
.read((flags & O_RDONLY != 0) | (flags & O_RDWR != 0))
.write((flags & O_WRONLY != 0) | (flags & O_RDWR != 0))
.open(path)
.map(|file| file.into_raw_fd())
.map_err(|err| err.raw_os_error().unwrap())
}

fn close_restricted(&mut self, fd: RawFd) {
unsafe {
File::from_raw_fd(fd);
}
}
}
Loading

0 comments on commit 30f1216

Please sign in to comment.