1
0
mirror of synced 2024-11-22 07:43:50 +00:00

Started work on Linux application

This commit is contained in:
Mark van Renswoude 2024-06-14 18:29:55 +02:00
parent 4d364c6be2
commit 7c87a7974c
25 changed files with 3669 additions and 0 deletions

1
Linux/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target

5
Linux/.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"recommendations": [
"arrterian.nix-env-selector"
]
}

3
Linux/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"nixEnvSelector.nixFile": "${workspaceFolder}/shell.nix"
}

2775
Linux/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

19
Linux/Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "MassiveKnob"
version = "0.1.0"
edition = "2021"
build = "build.rs"
[dependencies]
env_logger = "0.11.3"
log = "0.4.21"
relm4 = "0.8.1"
relm4-icons = "0.8.3"
rust-i18n = "3.0.1"
[dependencies.min-rs]
git = "https://github.com/MvRens/min-rs.git"
[build-dependencies]
slint-build = "1.6"
walkdir = "2.5.0"

24
Linux/README.md Normal file
View File

@ -0,0 +1,24 @@
# MassiveKnob - Linux GUI
This is a WIP port of the MassiveKnob Windows application to Rust. It may also run on Windows and is intended to replace the Windows version eventually so only one needs to be maintained. However, since this is my first proper project in Rust I am focusing on getting it to work on Linux first. I use NixOS btw. ;-)
## USB device access
If you're getting an access denied when accessing the USB port you should configure user access. On my system this is handled by udev, for which configuration is usually in ```/etc/udev/rules.d/```. The corresponding NixOS configuration is as follows:
```
# Specific device
services.udev.extraRules = ''
KERNEL=="ttyACM1", MODE:="666"
'';
# Any Arduino Leonardo (check dmesg or lsusb for specs)
services.udev.extraRules = ''
ACTION=="add", SUBSYSTEMS=="usb", ATTRS{idVendor}=="2341", ATTRS{idProduct}=="8036", MODE="0666"
'';
```
## Developing
### NixOS / Nix
Use the 'Nix Environment Selector' VSCode extension to get the dev dependencies for Rust Analyzer to work. In the terminal, use ```nix-shell``` to get the same environment for ```cargo run```.

4
Linux/build.rs Normal file
View File

@ -0,0 +1,4 @@
fn main()
{
println!("cargo:rerun-if-changed=locales");
}

2
Linux/icons.toml Normal file
View File

@ -0,0 +1,2 @@
base_resource_path = "/com/github/mvrens/massiveknob/"
icons = []

View File

@ -0,0 +1,5 @@
_version: 2
emulatorwindow:
title:
en: MassiveKnob device emulator

View File

@ -0,0 +1,5 @@
_version: 2
mainwindow:
title:
en: MassiveKnob

306
Linux/prototypes/readinputs/Cargo.lock generated Normal file
View File

@ -0,0 +1,306 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
[[package]]
name = "anstyle-parse"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "colorchoice"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
[[package]]
name = "env_filter"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"humantime",
"log",
]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "ioctl-rs"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7970510895cee30b3e9128319f2cefd4bde883a39f38baa279567ba3a7eb97d"
dependencies = [
"libc",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
[[package]]
name = "libc"
version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "memchr"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "min-rs"
version = "0.1.0"
source = "git+https://github.com/MvRens/min-rs.git#ca485964494aba2a330c3bc3eb1370f21e511ff6"
dependencies = [
"log",
]
[[package]]
name = "readinputs"
version = "0.1.0"
dependencies = [
"env_logger",
"log",
"min-rs",
"serial",
]
[[package]]
name = "regex"
version = "1.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "serial"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1237a96570fc377c13baa1b88c7589ab66edced652e43ffb17088f003db3e86"
dependencies = [
"serial-core",
"serial-unix",
"serial-windows",
]
[[package]]
name = "serial-core"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f46209b345401737ae2125fe5b19a77acce90cd53e1658cda928e4fe9a64581"
dependencies = [
"libc",
]
[[package]]
name = "serial-unix"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f03fbca4c9d866e24a459cbca71283f545a37f8e3e002ad8c70593871453cab7"
dependencies = [
"ioctl-rs",
"libc",
"serial-core",
"termios",
]
[[package]]
name = "serial-windows"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15c6d3b776267a75d31bbdfd5d36c0ca051251caafc285827052bc53bcdc8162"
dependencies = [
"libc",
"serial-core",
]
[[package]]
name = "termios"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a"
dependencies = [
"libc",
]
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"

View File

@ -0,0 +1,12 @@
[package]
name = "readinputs"
version = "0.1.0"
edition = "2021"
[dependencies]
env_logger = "0.11.3"
log = "0.4.21"
serial = "0.4.0"
[dependencies.min-rs]
git = "https://github.com/MvRens/min-rs.git"

View File

@ -0,0 +1,260 @@
extern crate serial;
extern crate min_rs as min;
use std::str;
use std::time::Duration;
use std::thread;
use serial::prelude::*;
use serial::SystemPort;
use std::io::prelude::*;
use std::cell::RefCell;
use std::sync::{Arc, Mutex};
use log::{LevelFilter, debug, trace};
use env_logger;
const SERIAL_PORT: &str = "/dev/ttyACM1";
const BAUD_RATE: serial::BaudRate = serial::Baud115200;
struct Uart
{
port: RefCell<SystemPort>,
name: String,
tx_space_avaliable: u16,
output: Arc<Mutex<String>>,
}
impl Uart
{
fn new(port: SystemPort, name: String, tx_space_avaliable: u16) -> Self
{
Uart
{
port: RefCell::new(port),
name: name,
tx_space_avaliable: tx_space_avaliable,
output: Arc::new(Mutex::new(String::from(""))),
}
}
fn open(&self)
{
const SETTINGS: serial::PortSettings = serial::PortSettings
{
baud_rate: BAUD_RATE,
char_size: serial::Bits8,
parity: serial::ParityNone,
stop_bits: serial::Stop1,
flow_control: serial::FlowNone,
};
let mut port = self.port.borrow_mut();
port.configure(&SETTINGS).unwrap();
port.set_timeout(Duration::from_millis(1000)).unwrap();
debug!(target: self.name.as_str(), "{}: Open uart.", self.name);
}
fn available_for_write(&self) -> u16
{
self.tx_space_avaliable
}
fn tx(&self, byte: u8)
{
let mut output = self.output.lock().unwrap();
output.push_str(format!("0x{:02x} ", byte).as_str());
let mut port = self.port.borrow_mut();
match port.write(&[byte])
{
Ok(_) => {},
Err(e) =>
{
debug!(target: self.name.as_str(), "{}", e);
},
}
}
fn read(&self, buf: &mut [u8]) -> Result<usize, ()>
{
let mut port = self.port.borrow_mut();
match port.read(&mut buf[..])
{
Ok(n) => Ok(n),
_ => Err(()),
}
}
}
impl min::Interface for Uart
{
fn tx_start(&self)
{
let mut output = self.output.lock().unwrap();
output.clear();
output.push_str(format!("send frame: [ ").as_str());
}
fn tx_finished(&self)
{
let mut output = self.output.lock().unwrap();
output.push_str(format!("]").as_str());
trace!(target: self.name.as_str(), "{}", output);
}
fn tx_space(&self) -> u16
{
self.available_for_write()
}
fn tx_byte(&self, _min_port: u8, byte: u8)
{
self.tx(byte);
}
}
#[derive(Copy, Clone)]
enum MassiveKnobHostToDeviceFrameID
{
Handshake = 42
//AnalogOutput = 3,
//DigitalOutput = 4,
//Quit = 62,
}
enum MassiveKnobDeviceToHostFrameID
{
HandshakeResponse = 43,
AnalogInput = 1,
//DigitalInput = 2,
Error = 63
}
struct MassiveKnobDeviceSpecs
{
analog_inputs: u8,
digital_inputs: u8,
analog_outputs: u8,
digital_outputs: u8
}
impl TryFrom<u8> for MassiveKnobDeviceToHostFrameID
{
type Error = ();
fn try_from(v: u8) -> Result<Self, <MassiveKnobDeviceToHostFrameID as TryFrom<u8>>::Error>
{
match v {
x if x == MassiveKnobDeviceToHostFrameID::HandshakeResponse as u8 => Ok(MassiveKnobDeviceToHostFrameID::HandshakeResponse),
x if x == MassiveKnobDeviceToHostFrameID::AnalogInput as u8 => Ok(MassiveKnobDeviceToHostFrameID::AnalogInput),
x if x == MassiveKnobDeviceToHostFrameID::Error as u8 => Ok(MassiveKnobDeviceToHostFrameID::Error),
_ => Err(()),
}
}
}
fn main() {
log::set_max_level(LevelFilter::Debug);
env_logger::init();
//let tx_data: [u8; 3] = [1, 2, 3];
let port = serial::open(SERIAL_PORT).unwrap();
let uart = Uart::new(port, String::from("uart"), 128);
let mut min = min::Context::new(
String::from("min"),
&uart,
0,
true,
);
min.hw_if.open();
min.reset_transport(true).unwrap_or(());
// Send handshake
let handshake: [u8; 2] = ['M' as u8, 'K' as u8];
min.queue_frame(MassiveKnobHostToDeviceFrameID::Handshake as u8, &handshake[..], handshake.len() as u8).unwrap_or(());
let mut buf: Vec<u8> = (0..255).collect();
loop
{
min.poll(&[0][0..0], 0);
if let Ok(n) = min.hw_if.read(&mut buf[..])
{
min.poll(&buf[0..n], n as u32);
};
if let Ok(msg) = min.get_msg()
{
match MassiveKnobDeviceToHostFrameID::try_from(msg.min_id)
{
Ok(MassiveKnobDeviceToHostFrameID::HandshakeResponse) =>
{
if msg.len < 4
{
println!("Invalid handshake response length, expected 4, got {}", msg.len);
}
let specs = MassiveKnobDeviceSpecs
{
analog_inputs: msg.buf[0],
digital_inputs: msg.buf[1],
analog_outputs: msg.buf[2],
digital_outputs: msg.buf[3]
};
println!("Received handshake response:");
println!(" Analog inputs: {}", specs.analog_inputs);
println!(" Digital inputs: {}", specs.digital_inputs);
println!(" Analog outputs: {}", specs.analog_outputs);
println!(" Digital outputs: {}", specs.digital_outputs);
},
Ok(MassiveKnobDeviceToHostFrameID::AnalogInput) =>
{
if msg.len < 2
{
println!("Invalid analog input payload length, expected 2, got {}", msg.len);
}
println!("[Analog input #{}] {}", msg.buf[0], msg.buf[1]);
},
Ok(MassiveKnobDeviceToHostFrameID::Error) =>
{
println!("[Device error] {}", match str::from_utf8(msg.buf.as_slice())
{
Ok(v) => v,
Err(_) => "(unable to parse device error message, invalid UTF-8 sequence)"
})
},
Err(_) =>
{
println!("Unknown message ID: {}", msg.min_id);
}
}
}
thread::sleep(Duration::from_millis(10));
}
}

14
Linux/shell.nix Normal file
View File

@ -0,0 +1,14 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
nativeBuildInputs = with pkgs; [
pkg-config
gtk4
graphene
gdk-pixbuf
];
# For a reason I'm yet to find out, my VSCode terminal sets GDK_BACKEND to x11.
# This is empty in the regular terminal and I'm running wayland.
# Comment this out if it's causing issues for you.
GDK_BACKEND = "wayland";
}

8
Linux/src/actions/mod.rs Normal file
View File

@ -0,0 +1,8 @@
pub mod registry;
pub mod pipewire;
pub fn register()
{
pipewire::register();
}

View File

@ -0,0 +1,10 @@
use super::registry::{MkAction, register_action};
pub mod set_volume;
pub fn register()
{
register_action(MkAction::new("Set volume"));
}

View File

View File

@ -0,0 +1,26 @@
use log::info;
pub struct MkAction
{
// TODO prepare for i18n by making this a translation key?
name: &'static str
}
impl MkAction
{
pub fn new(name: &'static str) -> Self
{
Self
{
name
}
}
}
pub fn register_action(action: MkAction)
{
info!("Registered action: {}", action.name);
}

View File

@ -0,0 +1,47 @@
use gtk::prelude::*;
use relm4::prelude::*;
pub struct EmulatorWindow
{
}
#[derive(Debug)]
pub enum Msg
{
}
#[relm4::component(pub)]
impl SimpleComponent for EmulatorWindow
{
type Init = ();
type Input = Msg;
type Output = ();
view!
{
gtk::Window
{
set_title: Some(&t!("emulatorwindow.title")),
set_default_size: (300, 500)
}
}
fn init(_data: Self::Init, root: Self::Root, _sender: ComponentSender<Self>, ) -> ComponentParts<Self>
{
let model = EmulatorWindow {};
let widgets = view_output!();
ComponentParts { model, widgets }
}
fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>)
{
match msg
{
}
}
}

View File

@ -0,0 +1,21 @@
use emulatorwindow::EmulatorWindow;
use relm4::prelude::*;
use relm4::gtk::prelude::GtkApplicationExt;
use super::registry::{MkDevice, register_device};
pub mod emulatorwindow;
pub fn register()
{
register_device(MkDevice::new("Emulator"));
let app = relm4::main_application();
let builder = EmulatorWindow::builder();
app.add_window(&builder.root);
builder.launch(()).detach_runtime();
}

10
Linux/src/devices/mod.rs Normal file
View File

@ -0,0 +1,10 @@
pub mod registry;
pub mod emulator;
pub mod serial_min;
pub fn register()
{
emulator::register();
serial_min::register();
}

View File

@ -0,0 +1,26 @@
use log::info;
pub struct MkDevice
{
// TODO prepare for i18n by making this a translation key?
name: &'static str
}
impl MkDevice
{
pub fn new(name: &'static str) -> Self
{
Self
{
name
}
}
}
pub fn register_device(action: MkDevice)
{
info!("Registered device: {}", action.name);
}

View File

@ -0,0 +1,7 @@
use super::registry::{MkDevice, register_device};
pub fn register()
{
register_device(MkDevice::new("Serial device using MIN protocol"));
}

29
Linux/src/main.rs Normal file
View File

@ -0,0 +1,29 @@
use env_logger::Env;
use relm4::prelude::*;
#[macro_use]
extern crate rust_i18n;
i18n!("locales");
pub mod devices;
pub mod actions;
pub mod mainwindow;
fn main()
{
env_logger::Builder::from_env(Env::default().default_filter_or("info"))
// .format_timestamp(None)
.init();
//devices::register();
actions::register();
relm4_icons::initialize_icons();
let app = RelmApp::new("com.github.mvrens.massiveknob");
app.run::<mainwindow::MainWindow>(());
}

50
Linux/src/mainwindow.rs Normal file
View File

@ -0,0 +1,50 @@
use gtk::prelude::*;
use relm4::prelude::*;
pub struct MainWindow
{
}
#[derive(Debug)]
pub enum Msg
{
}
#[relm4::component(pub)]
impl SimpleComponent for MainWindow
{
type Init = ();
type Input = Msg;
type Output = ();
view!
{
gtk::Window
{
set_title: Some(&t!("mainwindow.title")),
set_default_size: (300, 100)
}
}
fn init(_data: Self::Init, root: Self::Root, _sender: ComponentSender<Self>, ) -> ComponentParts<Self>
{
// TEMP - device should not use GTK at register time
crate::devices::register();
let model = MainWindow {};
let widgets = view_output!();
ComponentParts { model, widgets }
}
fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>)
{
match msg
{
}
}
}