mickey 0.1.1

Accessibility utility to move mouse cursor using keyboard with binary search algorithm
mod xdo;
use serde::{Deserialize, Serialize};
use xdo::XDO;

use clap::Parser;

use anyhow::anyhow;
use anyhow::Result;

#[derive(Parser, Debug)]
pub struct App {
    #[clap(subcommand)]
    command: Command,
}

#[derive(Parser, Debug)]
pub enum Command {
    Click(Click),
    Centralize,
    Move(Move),
    Hold,
}

#[derive(Parser, Debug)]
pub struct Click {
    #[clap(short, long, default_value_t = 1)]
    repeat: u32,

    #[clap(short, long, default_value_t = 5)]
    sleep: u64,

    #[clap(subcommand)]
    buton: MouseButton,
}

impl Click {
    fn fire(&self, mut xdo: XDO) -> Result<()> {
        xdo.click(self.buton as i32, self.repeat, self.sleep);

        State::reset()?;
        Ok(())
    }
}

#[derive(Parser, Debug, Copy, Clone)]
pub enum MouseButton {
    Left = 1,
    Right = 2,
    Center = 3,
    ScrollUp = 4,
    ScrollDown = 5,
}

#[derive(Parser, Debug)]
pub struct Move {
    #[clap(subcommand)]
    motion: Motion,
}

impl Move {
    fn move_x(&self, s: &mut State, xdo: &mut XDO, vp: (u32, u32)) {
        s.raise.0 += 1;

        let mut mv = f64::max(vp.0 as f64 / (1 << s.raise.0) as f64, 10.);

        if self.motion == Motion::Left {
            mv *= -1.;
        }

        xdo.relative_move(mv as i32, 0);
    }

    fn move_y(&self, s: &mut State, xdo: &mut XDO, vp: (u32, u32)) {
        s.raise.1 += 1;

        let mut mv = f64::max(vp.1 as f64 / (1 << s.raise.1) as f64, 10.);

        if self.motion == Motion::Top {
            mv *= -1.;
        }

        xdo.relative_move(0, mv as i32);
    }

    fn fire(&self, xdo: &mut XDO) -> Result<()> {
        let mut s = State::load().unwrap_or_default();

        let vp = xdo.viewport();

        match self.motion.axis() {
            Axis::X => self.move_x(&mut s, xdo, vp),
            Axis::Y => self.move_y(&mut s, xdo, vp),
        }

        s.save()?;

        Ok(())
    }
}

#[derive(Parser, Debug, Serialize, Deserialize, Clone, PartialEq)]
pub enum Motion {
    Top,
    Left,
    Right,
    Bottom,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub enum Axis {
    X,
    Y,
}

impl Motion {
    fn axis(&self) -> Axis {
        match self {
            Motion::Top | Motion::Bottom => Axis::Y,
            _ => Axis::X,
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
struct State {
    raise: (usize, usize),
    hold: bool,
}

impl Default for State {
    fn default() -> Self {
        State {
            hold: false,
            raise: (1, 1),
        }
    }
}

impl State {
    fn load() -> Result<State> {
        Ok(bincode::deserialize(&std::fs::read(
            "/dev/shm/mykeymouse",
        )?)?)
    }

    fn reset() -> Result<()> {
        let mut s = State::load()?;
        s.raise = (1, 1);
        s.save()?;

        Ok(())
    }

    fn save(&self) -> Result<()> {
        std::fs::write("/dev/shm/mykeymouse", bincode::serialize(self)?)?;
        Ok(())
    }
}

fn main() -> Result<()> {
    let app = App::parse();
    let mut xdo = XDO::new().map_err(|_| anyhow!("Unable to use libxdo"))?;

    match app.command {
        Command::Centralize => {
            xdo.centralize();
            State::reset()?
        }
        Command::Click(ref c) => c.fire(xdo)?,
        Command::Move(ref m) => m.fire(&mut xdo)?,
        Command::Hold => {
            let mut s = State::load().unwrap_or_default();

            if s.hold {
                xdo.mouse_up();
                s.hold = false;
            } else {
                xdo.mouse_down();
                s.hold = true;
            }

            s.save()?;
        }
    }

    Ok(())
}