view src/server.rs @ 17:0bce7fe96937

Add a client, copy of the GTK interface.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Thu, 26 Aug 2021 22:00:55 +0200
parents 478cf2a7d577
children
line wrap: on
line source

// Tablet emulator, for people who don’t own one
// Copyright © 2020 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

use crate::state::State;
use bitflags::bitflags;
use input_linux::Key;
use std::io::{self, ErrorKind};
use std::net::UdpSocket;

bitflags! {
    /// This is the memory layout of the buttons on the 3DS.
    #[derive(Default)]
    pub struct Buttons: u32 {
        const A = 0x00000001;
        const B = 0x00000002;
        const SELECT = 0x00000004;
        const START = 0x00000008;
        const RIGHT = 0x00000010;
        const LEFT = 0x00000020;
        const UP = 0x00000040;
        const DOWN = 0x00000080;
        const R = 0x00000100;
        const L = 0x00000200;
        const X = 0x00000400;
        const Y = 0x00000800;
        // Nothing
        // Nothing
        const ZL = 0x00004000;
        const ZR = 0x00008000;
        const RESIZE = 0x00010000; // Not an actual 3DS button!
        // Nothing
        // Nothing
        // Nothing
        const TOUCH = 0x00100000;
        // Nothing
        // Nothing
        // Nothing
        const C_RIGHT = 0x01000000;
        const C_LEFT = 0x02000000;
        const C_UP = 0x04000000;
        const C_DOWN = 0x08000000;
        const CIRCLE_RIGHT = 0x10000000;
        const CIRCLE_LEFT = 0x20000000;
        const CIRCLE_UP = 0x40000000;
        const CIRCLE_DOWN = 0x80000000;
    }
}

#[derive(Debug, Default)]
pub struct Event {
    pub buttons: Buttons,
    pad: (i16, i16),
    c_pad: (i16, i16),
    pub touch: (u16, u16),
}

pub fn run_server(address: &str) -> io::Result<()> {
    let mut state = match State::new() {
        Ok(state) => state,
        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),
            }
            std::process::exit(1);
        }
    };

    let socket = UdpSocket::bind(address)?;
    println!("Listening on {:?}", socket);
    println!("Here is an example client: https://hg.linkmauve.fr/remote-gamepad");

    let mut event: Event = Default::default();
    let mut last = Some((0., 0.));
    state.set_size(320, 240);
    loop {
        // TODO: Yolo-alignment.
        let buf: &mut [u8; 16] = unsafe { std::mem::transmute(&mut event) };
        let (amount, source) = socket.recv_from(buf)?;
        if amount != std::mem::size_of::<Event>() {
            eprintln!("Invalid data length: {}", amount);
            continue;
        }
        println!("{:?} from {:?}", event, source);
        if event.buttons.contains(Buttons::A) {
            state.select_tool(Key::ButtonToolPen);
        } else if event.buttons.contains(Buttons::B) {
            state.select_tool(Key::ButtonToolRubber);
        } else if event.buttons.contains(Buttons::X) {
            state.select_tool(Key::ButtonToolBrush);
        } else if event.buttons.contains(Buttons::Y) {
            state.select_tool(Key::ButtonToolPencil);
        } else if event.buttons.contains(Buttons::SELECT) {
            state.select_tool(Key::ButtonToolAirbrush);
        } else if event.buttons.contains(Buttons::RESIZE) {
            println!("set_size({}, {})", event.touch.0 as i32, event.touch.1 as i32);
            state.set_size(event.touch.0 as i32, event.touch.1 as i32);
        }
        let (x, y) = event.touch;
        if event.buttons.contains(Buttons::TOUCH) {
            if let None = last {
                state.press(x as f64, y as f64)?;
                last = Some((x as f64, y as f64));
                continue;
            }
        } else {
            if let Some((x, y)) = last {
                state.release(x, y)?;
                last = None;
                continue;
            }
        }
        state.motion(x as f64, y as f64)?;
        last = Some((x as f64, y as f64));
    }
}

pub fn main(args: &[String]) {
    let address = if args.len() > 1 {
        args[1].clone()
    } else {
        String::from("0.0.0.0:16150")
    };
    run_server(&address).unwrap();
}