mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-25 09:59:21 +01:00
Start on rust API
This commit is contained in:
parent
36261d146b
commit
3e56450e29
6 changed files with 491 additions and 0 deletions
13
api/rust/Cargo.toml
Normal file
13
api/rust/Cargo.toml
Normal 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"
|
6
api/rust/examples/example_config.rs
Normal file
6
api/rust/examples/example_config.rs
Normal 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
52
api/rust/metaconfig.toml
Normal 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
56
api/rust/src/lib.rs
Normal 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
319
api/rust/src/msg.rs
Normal 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
45
api/rust/src/process.rs
Normal 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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue