# HG changeset patch # User Emmanuel Gil Peyrot # Date 1602036633 -7200 # Node ID 816237b684eaadfa79361d5f2780c0f80e3b3cf9 Hello world! diff --git a/.hgignore b/.hgignore new file mode 100644 --- /dev/null +++ b/.hgignore @@ -0,0 +1,2 @@ +^target/ +glob:Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "tablet-emu" +version = "0.1.0" +authors = ["Emmanuel Gil Peyrot "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cairo-rs = "0.9.1" +gdk = "0.13.2" +gio = "0.9.1" +glib = "0.10.2" +gtk = "0.9.2" +input-linux = "0.3.0" + +[profile.release] +lto = true diff --git a/src/main.rs b/src/main.rs new file mode 100644 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,403 @@ +use gio::prelude::*; +use gtk::prelude::*; + +use std::env::args; +use std::fs::{File, OpenOptions}; +use std::io::ErrorKind; +use std::sync::{Arc, Mutex}; + +use input_linux::{ + sys::input_event, sys::timeval, AbsoluteAxis, AbsoluteInfo, AbsoluteInfoSetup, EventKind, + InputId, InputProperty, Key, MiscKind, UInputHandle, +}; + +const WIDTH: i32 = 320; +const HEIGHT: i32 = 180; + +const MAX_X: i32 = 69920; +const MAX_Y: i32 = 39980; + +fn create_uinput_device() -> std::io::Result> { + let file = OpenOptions::new().write(true).open("/dev/uinput")?; + let dev = UInputHandle::new(file); + + dev.set_evbit(EventKind::Synchronize)?; + dev.set_evbit(EventKind::Key)?; + dev.set_evbit(EventKind::Absolute)?; + dev.set_evbit(EventKind::Misc)?; + dev.set_keybit(Key::ButtonToolPen)?; + dev.set_keybit(Key::ButtonToolRubber)?; + dev.set_keybit(Key::ButtonToolBrush)?; + dev.set_keybit(Key::ButtonToolPencil)?; + dev.set_keybit(Key::ButtonToolAirbrush)?; + dev.set_keybit(Key::ButtonTouch)?; + dev.set_keybit(Key::ButtonStylus)?; + dev.set_keybit(Key::ButtonStylus2)?; + dev.set_keybit(Key::ButtonStylus3)?; + dev.set_mscbit(MiscKind::Serial)?; + dev.set_propbit(InputProperty::Direct)?; + + dev.set_absbit(AbsoluteAxis::X)?; + dev.set_absbit(AbsoluteAxis::Y)?; + dev.set_absbit(AbsoluteAxis::Z)?; + dev.set_absbit(AbsoluteAxis::Wheel)?; + dev.set_absbit(AbsoluteAxis::Pressure)?; + dev.set_absbit(AbsoluteAxis::Distance)?; + dev.set_absbit(AbsoluteAxis::TiltX)?; + dev.set_absbit(AbsoluteAxis::TiltY)?; + dev.set_absbit(AbsoluteAxis::Misc)?; + + let id = InputId { + bustype: 3, + vendor: 0x56a, + product: 0x350, + version: 0xb, + }; + + let x = AbsoluteInfoSetup { + axis: AbsoluteAxis::X, + info: AbsoluteInfo { + value: 0, + minimum: 0, + maximum: MAX_X, + fuzz: 0, + flat: 0, + resolution: 200, + }, + }; + let y = AbsoluteInfoSetup { + axis: AbsoluteAxis::Y, + info: AbsoluteInfo { + value: 0, + minimum: 0, + maximum: MAX_Y, + fuzz: 0, + flat: 0, + resolution: 200, + }, + }; + let z = AbsoluteInfoSetup { + axis: AbsoluteAxis::Z, + info: AbsoluteInfo { + value: 0, + minimum: -900, + maximum: 899, + fuzz: 0, + flat: 0, + resolution: 287, + }, + }; + let wheel = AbsoluteInfoSetup { + axis: AbsoluteAxis::Wheel, + info: AbsoluteInfo { + value: 0, + minimum: 0, + maximum: 2047, + fuzz: 0, + flat: 0, + resolution: 0, + }, + }; + let pressure = AbsoluteInfoSetup { + axis: AbsoluteAxis::Pressure, + info: AbsoluteInfo { + value: 0, + minimum: 0, + maximum: 8196, + fuzz: 0, + flat: 0, + resolution: 0, + }, + }; + let distance = AbsoluteInfoSetup { + axis: AbsoluteAxis::Distance, + info: AbsoluteInfo { + value: 0, + minimum: 0, + maximum: 63, + fuzz: 0, + flat: 0, + resolution: 0, + }, + }; + let tilt_x = AbsoluteInfoSetup { + axis: AbsoluteAxis::TiltX, + info: AbsoluteInfo { + value: 0, + minimum: -64, + maximum: 63, + fuzz: 0, + flat: 0, + resolution: 57, + }, + }; + let tilt_y = AbsoluteInfoSetup { + axis: AbsoluteAxis::TiltY, + info: AbsoluteInfo { + value: 0, + minimum: -64, + maximum: 63, + fuzz: 0, + flat: 0, + resolution: 57, + }, + }; + let misc = AbsoluteInfoSetup { + axis: AbsoluteAxis::Misc, + info: AbsoluteInfo { + value: 0, + minimum: 0, + maximum: 0, + fuzz: 0, + flat: 0, + resolution: 0, + }, + }; + + dev.create( + &id, + b"TabletEmu", + 0, + &[x, y, z, wheel, pressure, distance, tilt_x, tilt_y, misc], + )?; + Ok(dev) +} + +fn input_event_new(type_: EventKind, code: AbsoluteAxis, value: i32) -> input_event { + input_event { + time: timeval { + tv_sec: 0, + tv_usec: 0, + }, + type_: type_ as u16, + code: code as u16, + value, + } +} + +fn build_ui(application: >k::Application) { + let dev = match create_uinput_device() { + Ok(dev) => Arc::new(dev), + Err(err) => { + match err.kind() { + ErrorKind::NotFound => { + eprintln!("Couldn’t find /dev/uinput: {}", err); + eprintln!("Maybe you forgot to `modprobe uinput`?"); + } + ErrorKind::PermissionDenied => { + eprintln!("Couldn’t open /dev/uinput for writing: {}", err); + eprintln!("Maybe you aren’t allowed to create input devices?"); + } + _ => eprintln!("Couldn’t open /dev/uinput for writing: {}", err), + } + return; + } + }; + println!( + "New device at {:?} ({:?})", + dev.evdev_path().unwrap(), + dev.sys_path().unwrap() + ); + + let window = gtk::ApplicationWindow::new(application); + + window.set_title("tablet-emu"); + window.set_position(gtk::WindowPosition::Center); + + let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 0); + let tools_box = gtk::Box::new(gtk::Orientation::Vertical, 0); + + let tool_name = Arc::new(Mutex::new(String::from("Pen"))); + let tool1 = gtk::Button::with_label("Pen"); + let tool2 = gtk::Button::with_label("Rubber"); + let tool3 = gtk::Button::with_label("Brush"); + let tool4 = gtk::Button::with_label("Pencil"); + let tool5 = gtk::Button::with_label("Airbrush"); + + macro_rules! impl_tool_signal { + ($tool:ident) => { + let tool_name_weak = Arc::downgrade(&tool_name); + $tool.connect_clicked(move |b| { + let tool_name = tool_name_weak.upgrade().unwrap(); + let mut tool_name = tool_name.lock().unwrap(); + *tool_name = b.get_label().unwrap().to_string(); + }); + }; + }; + impl_tool_signal!(tool1); + impl_tool_signal!(tool2); + impl_tool_signal!(tool3); + impl_tool_signal!(tool4); + impl_tool_signal!(tool5); + + tools_box.add(&tool1); + tools_box.add(&tool2); + tools_box.add(&tool3); + tools_box.add(&tool4); + tools_box.add(&tool5); + + let current_size = Arc::new(Mutex::new((WIDTH as f64, HEIGHT as f64))); + let pressed = Arc::new(Mutex::new(false)); + + let drawing_area = gtk::DrawingArea::new(); + drawing_area.set_size_request(WIDTH, HEIGHT); + drawing_area.set_hexpand(true); + drawing_area.set_events( + gdk::EventMask::BUTTON_PRESS_MASK + | gdk::EventMask::BUTTON_RELEASE_MASK + | gdk::EventMask::POINTER_MOTION_MASK, + ); + let current_size_weak = Arc::downgrade(¤t_size); + drawing_area.connect_configure_event(move |_, event| { + let current_size = current_size_weak.upgrade().unwrap(); + let mut current_size = current_size.lock().unwrap(); + *current_size = match event.get_size() { + (width, height) => (width as f64, height as f64), + }; + true + }); + let dev_weak = Arc::downgrade(&dev); + let current_size_weak = Arc::downgrade(¤t_size); + let pressed_weak = Arc::downgrade(&pressed); + drawing_area.connect_button_press_event(move |_, event| { + if event.get_button() != 1 { + return Inhibit(false); + } + + let dev = dev_weak.upgrade().unwrap(); + let pressed = pressed_weak.upgrade().unwrap(); + let mut pressed = pressed.lock().unwrap(); + *pressed = true; + let (x, y) = event.get_position(); + println!("press tool {} at {}, {}", tool_name.lock().unwrap(), x, y); + let current_size = current_size_weak.upgrade().unwrap(); + let current_size = current_size.lock().unwrap(); + let (width, height) = *current_size; + dev.write(&[ + input_event_new( + EventKind::Absolute, + AbsoluteAxis::X, + (x * MAX_X as f64 / width) as i32, + ), + input_event_new( + EventKind::Absolute, + AbsoluteAxis::Y, + (y * MAX_Y as f64 / height) as i32, + ), + input_event_new(EventKind::Absolute, AbsoluteAxis::Z, 0), + input_event_new(EventKind::Absolute, AbsoluteAxis::Wheel, 0), + input_event_new(EventKind::Absolute, AbsoluteAxis::Pressure, 1024), + input_event_new(EventKind::Absolute, AbsoluteAxis::Distance, 0), + input_event_new(EventKind::Absolute, AbsoluteAxis::TiltX, 16), + input_event_new(EventKind::Absolute, AbsoluteAxis::TiltY, 0), + // TODO: Change the type of the second parameter here. + input_event_new(EventKind::Misc, AbsoluteAxis::X, 0), + input_event_new(EventKind::Synchronize, AbsoluteAxis::X, 0), + ]) + .unwrap(); + Inhibit(false) + }); + let dev_weak = Arc::downgrade(&dev); + let current_size_weak = Arc::downgrade(¤t_size); + let pressed_weak = Arc::downgrade(&pressed); + drawing_area.connect_button_release_event(move |_, event| { + if event.get_button() != 1 { + return Inhibit(false); + } + + let dev = dev_weak.upgrade().unwrap(); + let (x, y) = event.get_position(); + let pressed = pressed_weak.upgrade().unwrap(); + let mut pressed = pressed.lock().unwrap(); + *pressed = false; + //println!("release {}, {}", x, y); + let current_size = current_size_weak.upgrade().unwrap(); + let current_size = current_size.lock().unwrap(); + let (width, height) = *current_size; + dev.write(&[ + input_event_new( + EventKind::Absolute, + AbsoluteAxis::X, + (x * MAX_X as f64 / width) as i32, + ), + input_event_new( + EventKind::Absolute, + AbsoluteAxis::Y, + (y * MAX_Y as f64 / height) as i32, + ), + input_event_new(EventKind::Absolute, AbsoluteAxis::Z, 0), + input_event_new(EventKind::Absolute, AbsoluteAxis::Wheel, 0), + input_event_new(EventKind::Absolute, AbsoluteAxis::Pressure, 0), + input_event_new(EventKind::Absolute, AbsoluteAxis::Distance, 16), + input_event_new(EventKind::Absolute, AbsoluteAxis::TiltX, 16), + input_event_new(EventKind::Absolute, AbsoluteAxis::TiltY, 0), + // TODO: Change the type of the second parameter here. + input_event_new(EventKind::Misc, AbsoluteAxis::X, 0), + input_event_new(EventKind::Synchronize, AbsoluteAxis::X, 0), + ]) + .unwrap(); + Inhibit(false) + }); + drawing_area.connect_motion_notify_event(move |_, event| { + let (x, y) = event.get_position(); + let pressed = pressed.lock().unwrap(); + //println!("motion {}, {}", x, y); + let current_size = current_size.lock().unwrap(); + let (width, height) = *current_size; + dev.write(&[ + input_event_new( + EventKind::Absolute, + AbsoluteAxis::X, + (x * MAX_X as f64 / width) as i32, + ), + input_event_new( + EventKind::Absolute, + AbsoluteAxis::Y, + (y * MAX_Y as f64 / height) as i32, + ), + input_event_new(EventKind::Absolute, AbsoluteAxis::Z, 0), + input_event_new(EventKind::Absolute, AbsoluteAxis::Wheel, 0), + input_event_new( + EventKind::Absolute, + AbsoluteAxis::Pressure, + if *pressed { 2048 } else { 0 }, + ), + input_event_new( + EventKind::Absolute, + AbsoluteAxis::Distance, + if *pressed { 0 } else { 32 }, + ), + input_event_new(EventKind::Absolute, AbsoluteAxis::TiltX, 16), + input_event_new(EventKind::Absolute, AbsoluteAxis::TiltY, 0), + // TODO: Change the type of the second parameter here. + input_event_new(EventKind::Misc, AbsoluteAxis::X, 0), + input_event_new(EventKind::Synchronize, AbsoluteAxis::X, 0), + ]) + .unwrap(); + Inhibit(false) + }); + drawing_area.connect_draw(move |_, ctx| { + //println!("drawing {}", drawing_area); + ctx.set_source_rgb(1., 0., 0.); + ctx.set_operator(cairo::Operator::Screen); + ctx.paint(); + Inhibit(false) + }); + + hbox.add(&tools_box); + hbox.add(&drawing_area); + + window.add(&hbox); + + window.show_all(); +} + +fn main() { + let application = gtk::Application::new( + Some("fr.linkmauve.TabletEmu"), + gio::ApplicationFlags::empty(), + ) + .expect("Initialisation failed…"); + application.connect_activate(build_ui); + application.run(&args().collect::>()); +}