Start on rust API

This commit is contained in:
Ottatop 2023-10-18 23:05:07 -05:00
parent 36261d146b
commit 3e56450e29
6 changed files with 491 additions and 0 deletions

13
api/rust/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "pinnacle_api"
version = "0.0.1"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1.0.188", features = ["derive"] }
rmp = { version = "0.8.12" }
rmp-serde = { version = "1.1.2" }
anyhow = { version = "1.0.75", features = ["backtrace"] }
lazy_static = "1.4.0"

View file

@ -0,0 +1,6 @@
fn main() {
pinnacle_api::setup(|pinnacle| {
pinnacle.process.spawn(vec!["alacritty"]).unwrap();
})
.unwrap();
}

52
api/rust/metaconfig.toml Normal file
View file

@ -0,0 +1,52 @@
# This metaconfig.toml file dictates what config Pinnacle will run.
#
# When running Pinnacle, the compositor will look in the following directories for a metaconfig.toml file,
# in order from top to bottom:
# $PINNACLE_CONFIG_DIR
# $XDG_CONFIG_HOME/pinnacle/
# ~/.config/pinnacle/
#
# When Pinnacle finds a metaconfig.toml file, it will execute the command provided to `command`.
# For now, the only thing that should be here is `lua` with a path to the main config file.
# In the future, there will be a Rust API that can be run using `cargo run`.
#
# Because configuration is done using an external process, if it ever crashes, you lose all of your keybinds.
# In order prevent you from getting stuck in the compositor, you must define keybinds to reload your config
# and kill Pinnacle.
#
# More details on each setting can be found below.
# The command Pinnacle will run on startup and when you reload your config.
# Paths are relative to the directory the metaconfig.toml file is in.
# This must be an array.
command = ["cargo", "run", "--example", "example_config"]
### Keybinds ###
# Each keybind takes in a table with two fields: `modifiers` and `key`.
# - `modifiers` can be one of "Ctrl", "Alt", "Shift", or "Super".
# - `key` can be a string of any lowercase letter, number,
# "numN" where N is a number for numpad keys, or "esc"/"escape".
# Support for any xkbcommon key is planned for a future update.
# The keybind that will reload your config.
reload_keybind = { modifiers = ["Ctrl", "Alt"], key = "r" }
# The keybind that will kill Pinnacle.
kill_keybind = { modifiers = ["Ctrl", "Alt", "Shift"], key = "escape" }
### Socket directory ###
# Pinnacle will open a Unix socket at `$XDG_RUNTIME_DIR` by default, falling back to `/tmp` if it doesn't exist.
# If you want/need to change this, use the `socket_dir` setting set to the directory of your choosing.
#
# socket_dir = "/your/dir/here/"
### Environment Variables ###
# You may need to specify to Lua where Pinnacle's Lua API library is.
# This is currently done using the `envs` table, with keys as the name of the environment variable and
# the value as the variable value. This supports $var expansion, and paths are relative to this metaconfig.toml file.
#
# Pinnacle will run your config with the additional PINNACLE_DIR environment variable.
#
# Here, LUA_PATH and LUA_CPATH are used to tell Lua the path to the library.
[envs]
# LUA_PATH = "$PINNACLE_LIB_DIR/lua/?.lua;$PINNACLE_LIB_DIR/lua/?/init.lua;$PINNACLE_LIB_DIR/lua/lib/?.lua;$PINNACLE_LIB_DIR/lua/lib/?/init.lua;$LUA_PATH"
# LUA_CPATH = "$PINNACLE_LIB_DIR/lua/lib/?.so;$LUA_CPATH"

56
api/rust/src/lib.rs Normal file
View file

@ -0,0 +1,56 @@
mod msg;
mod process;
use std::{
io::Write,
os::unix::net::UnixStream,
path::PathBuf,
sync::{Mutex, OnceLock},
};
use msg::{Args, Msg, Request};
use process::Process;
static STREAM: OnceLock<Mutex<UnixStream>> = OnceLock::new();
#[allow(clippy::type_complexity)]
static CALLBACK_VEC: Mutex<Vec<Box<dyn FnMut(Args) + Send>>> = Mutex::new(Vec::new());
pub fn setup(config_func: impl FnOnce(Pinnacle)) -> anyhow::Result<()> {
STREAM
.set(Mutex::new(UnixStream::connect(PathBuf::from(
std::env::var("PINNACLE_SOCKET").unwrap_or("/tmp/pinnacle_socket".to_string()),
))?))
.unwrap();
let pinnacle = Pinnacle { process: Process };
config_func(pinnacle);
Ok(())
}
fn send_msg(msg: Msg) -> anyhow::Result<()> {
let mut msg = rmp_serde::encode::to_vec_named(&msg)?;
let mut msg_len = (msg.len() as u32).to_ne_bytes();
let mut stream = STREAM.get().unwrap().lock().unwrap();
stream.write_all(msg_len.as_mut_slice())?;
stream.write_all(msg.as_mut_slice())?;
Ok(())
}
fn read_msg() {
todo!()
}
fn request(request: Request) {
//
}
pub struct Pinnacle {
pub process: Process,
}
impl Pinnacle {}

319
api/rust/src/msg.rs Normal file
View file

@ -0,0 +1,319 @@
use std::num::NonZeroU32;
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy)]
pub struct CallbackId(pub u32);
#[derive(Debug, Hash, serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq)]
pub enum MouseEdge {
Press,
Release,
}
/// A unique identifier for each window.
#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum WindowId {
/// A config API returned an invalid window. It should be using this variant.
None,
/// A valid window id.
#[serde(untagged)]
Some(u32),
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub enum TagId {
None,
#[serde(untagged)]
Some(u32),
}
/// A unique identifier for an output.
///
/// An empty string represents an invalid output.
#[derive(Debug, Hash, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct OutputName(pub String);
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub enum Layout {
MasterStack,
Dwindle,
Spiral,
CornerTopLeft,
CornerTopRight,
CornerBottomLeft,
CornerBottomRight,
}
#[derive(Debug, PartialEq, Copy, Clone, serde::Serialize)]
pub enum AccelProfile {
Flat,
Adaptive,
}
#[derive(Debug, PartialEq, Copy, Clone, serde::Serialize)]
pub enum ClickMethod {
ButtonAreas,
Clickfinger,
}
#[derive(Debug, PartialEq, Copy, Clone, serde::Serialize)]
pub enum ScrollMethod {
NoScroll,
TwoFinger,
Edge,
OnButtonDown,
}
#[derive(Debug, PartialEq, Copy, Clone, serde::Serialize)]
pub enum TapButtonMap {
LeftRightMiddle,
LeftMiddleRight,
}
#[derive(Debug, PartialEq, Copy, Clone, serde::Serialize)]
pub enum LibinputSetting {
AccelProfile(AccelProfile),
AccelSpeed(f64),
CalibrationMatrix([f32; 6]),
ClickMethod(ClickMethod),
DisableWhileTypingEnabled(bool),
LeftHanded(bool),
MiddleEmulationEnabled(bool),
RotationAngle(u32),
ScrollMethod(ScrollMethod),
NaturalScrollEnabled(bool),
ScrollButton(u32),
TapButtonMap(TapButtonMap),
TapDragEnabled(bool),
TapDragLockEnabled(bool),
TapEnabled(bool),
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct RequestId(u32);
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct WindowRuleCondition {
/// This condition is met when any of the conditions provided is met.
#[serde(default)]
cond_any: Option<Vec<WindowRuleCondition>>,
/// This condition is met when all of the conditions provided are met.
#[serde(default)]
cond_all: Option<Vec<WindowRuleCondition>>,
/// This condition is met when the class matches.
#[serde(default)]
class: Option<Vec<String>>,
/// This condition is met when the title matches.
#[serde(default)]
title: Option<Vec<String>>,
/// This condition is met when the tag matches.
#[serde(default)]
tag: Option<Vec<TagId>>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize)]
pub enum FloatingOrTiled {
Floating,
Tiled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
pub enum FullscreenOrMaximized {
Neither,
Fullscreen,
Maximized,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct WindowRule {
/// Set the output the window will open on.
#[serde(default)]
pub output: Option<OutputName>,
/// Set the tags the output will have on open.
#[serde(default)]
pub tags: Option<Vec<TagId>>,
/// Set the window to floating or tiled on open.
#[serde(default)]
pub floating_or_tiled: Option<FloatingOrTiled>,
/// Set the window to fullscreen, maximized, or force it to neither.
#[serde(default)]
pub fullscreen_or_maximized: Option<FullscreenOrMaximized>,
/// Set the window's initial size.
#[serde(default)]
pub size: Option<(NonZeroU32, NonZeroU32)>,
/// Set the window's initial location. If the window is tiled, it will snap to this position
/// when set to floating.
#[serde(default)]
pub location: Option<(i32, i32)>,
}
#[derive(Debug, serde::Serialize)]
pub(crate) enum Msg {
// Input
SetKeybind {
key: KeyIntOrString,
modifiers: Vec<Modifier>,
callback_id: CallbackId,
},
SetMousebind {
modifiers: Vec<Modifier>,
button: u32,
edge: MouseEdge,
callback_id: CallbackId,
},
// Window management
CloseWindow {
window_id: WindowId,
},
SetWindowSize {
window_id: WindowId,
#[serde(default)]
width: Option<i32>,
#[serde(default)]
height: Option<i32>,
},
MoveWindowToTag {
window_id: WindowId,
tag_id: TagId,
},
ToggleTagOnWindow {
window_id: WindowId,
tag_id: TagId,
},
ToggleFloating {
window_id: WindowId,
},
ToggleFullscreen {
window_id: WindowId,
},
ToggleMaximized {
window_id: WindowId,
},
AddWindowRule {
cond: WindowRuleCondition,
rule: WindowRule,
},
WindowMoveGrab {
button: u32,
},
WindowResizeGrab {
button: u32,
},
// Tag management
ToggleTag {
tag_id: TagId,
},
SwitchToTag {
tag_id: TagId,
},
AddTags {
/// The name of the output you want these tags on.
output_name: OutputName,
tag_names: Vec<String>,
},
RemoveTags {
/// The name of the output you want these tags removed from.
tag_ids: Vec<TagId>,
},
SetLayout {
tag_id: TagId,
layout: Layout,
},
// Output management
ConnectForAllOutputs {
callback_id: CallbackId,
},
SetOutputLocation {
output_name: OutputName,
#[serde(default)]
x: Option<i32>,
#[serde(default)]
y: Option<i32>,
},
// Process management
/// Spawn a program with an optional callback.
Spawn {
command: Vec<String>,
#[serde(default)]
callback_id: Option<CallbackId>,
},
SetEnv {
key: String,
value: String,
},
// Pinnacle management
/// Quit the compositor.
Quit,
// Input management
SetXkbConfig {
#[serde(default)]
rules: Option<String>,
#[serde(default)]
variant: Option<String>,
#[serde(default)]
layout: Option<String>,
#[serde(default)]
model: Option<String>,
#[serde(default)]
options: Option<String>,
},
SetLibinputSetting(LibinputSetting),
Request {
request_id: RequestId,
request: Request,
},
}
#[allow(clippy::enum_variant_names)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
/// Messages that require a server response, usually to provide some data.
pub enum Request {
// Windows
GetWindows,
GetWindowProps { window_id: WindowId },
// Outputs
GetOutputs,
GetOutputProps { output_name: String },
// Tags
GetTags,
GetTagProps { tag_id: TagId },
}
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
pub enum KeyIntOrString {
Int(u32),
String(String),
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum Modifier {
Shift,
Ctrl,
Alt,
Super,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum Args {
/// Send a message with lines from the spawned process.
Spawn {
#[serde(default)]
stdout: Option<String>,
#[serde(default)]
stderr: Option<String>,
#[serde(default)]
exit_code: Option<i32>,
#[serde(default)]
exit_msg: Option<String>,
},
ConnectForAllOutputs {
output_name: String,
},
}

45
api/rust/src/process.rs Normal file
View file

@ -0,0 +1,45 @@
use crate::{
msg::{Args, CallbackId, Msg},
send_msg, CALLBACK_VEC,
};
pub struct Process;
impl Process {
pub fn spawn(&self, command: Vec<&str>) -> anyhow::Result<()> {
let msg = Msg::Spawn {
command: command.into_iter().map(|s| s.to_string()).collect(),
callback_id: None,
};
send_msg(msg)
}
pub fn spawn_with_callback<F>(&self, command: Vec<&str>, mut callback: F) -> anyhow::Result<()>
where
F: FnMut(Option<String>, Option<String>, Option<i32>, Option<String>) + Send + 'static,
{
let args_callback = move |args: Args| {
if let Args::Spawn {
stdout,
stderr,
exit_code,
exit_msg,
} = args
{
callback(stdout, stderr, exit_code, exit_msg);
}
};
let mut callback_vec = CALLBACK_VEC.lock().unwrap();
let len = callback_vec.len();
callback_vec.push(Box::new(args_callback));
let msg = Msg::Spawn {
command: command.into_iter().map(|s| s.to_string()).collect(),
callback_id: Some(CallbackId(len as u32)),
};
send_msg(msg)
}
}