changeset 0:816237b684ea

Hello world!
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Wed, 07 Oct 2020 04:10:33 +0200
parents
children 6dbe2bbeef70
files .hgignore Cargo.toml src/main.rs
diffstat 3 files changed, 423 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,2 @@
+^target/
+glob:Cargo.lock
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 <linkmauve@linkmauve.fr>"]
+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
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<UInputHandle<File>> {
+    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: &gtk::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(&current_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(&current_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(&current_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::<Vec<_>>());
+}