Add input and window stuff

This commit is contained in:
Ottatop 2023-10-19 17:43:37 -05:00
parent 3e56450e29
commit 2c3fb2dbd7
7 changed files with 456 additions and 20 deletions

View file

@ -11,3 +11,4 @@ rmp = { version = "0.8.12" }
rmp-serde = { version = "1.1.2" }
anyhow = { version = "1.0.75", features = ["backtrace"] }
lazy_static = "1.4.0"
xkbcommon = "0.7.0"

View file

@ -1,6 +1,19 @@
use pinnacle_api::{Modifier, MouseButton, MouseEdge};
fn main() {
pinnacle_api::setup(|pinnacle| {
pinnacle.process.spawn(vec!["alacritty"]).unwrap();
pinnacle.input.keybind(&[Modifier::Ctrl], 'a', move || {
pinnacle.process.spawn(vec!["alacritty"]).unwrap();
});
pinnacle.input.mousebind(
&[Modifier::Ctrl],
MouseButton::Left,
MouseEdge::Press,
|| {},
);
})
.unwrap();
}

99
api/rust/src/input.rs Normal file
View file

@ -0,0 +1,99 @@
use xkbcommon::xkb::Keysym;
use crate::{
msg::{Args, CallbackId, KeyIntOrString, Modifier, MouseEdge, Msg},
send_msg, CALLBACK_VEC,
};
pub struct Input;
impl Input {
/// Set a keybind.
///
/// This function takes in three parameters:
/// - `modifiers`: A slice of the modifiers you want held for the keybind to trigger.
/// - `key`: The key that needs to be pressed. This takes `impl Into<KeyIntOrString>` and can
/// take the following three types:
/// - [`char`]: A character of the key you want. This can be `a`, `~`, `@`, and so on.
/// - [`u32`]: The key in numeric form. You can use the keys defined in
/// [`xkbcommon::xkb::keysyms`] for this.
/// - [`Keysym`]: The key in `Keysym` form, from [xkbcommon::xkb::Keysym].
pub fn keybind<F>(&self, modifiers: &[Modifier], key: impl Into<KeyIntOrString>, mut action: F)
where
F: FnMut() + Send + 'static,
{
let args_callback = move |_: Option<Args>| {
action();
};
let mut callback_vec = CALLBACK_VEC.lock().unwrap();
let len = callback_vec.len();
callback_vec.push(Box::new(args_callback));
let key = key.into();
let msg = Msg::SetKeybind {
key,
modifiers: modifiers.to_vec(),
callback_id: CallbackId(len as u32),
};
send_msg(msg).unwrap();
}
pub fn mousebind<F>(
&self,
modifiers: &[Modifier],
button: MouseButton,
edge: MouseEdge,
mut action: F,
) where
F: FnMut() + Send + 'static,
{
let args_callback = move |_: Option<Args>| {
action();
};
let mut callback_vec = CALLBACK_VEC.lock().unwrap();
let len = callback_vec.len();
callback_vec.push(Box::new(args_callback));
let msg = Msg::SetMousebind {
modifiers: modifiers.to_vec(),
button: button as u32,
edge,
callback_id: CallbackId(len as u32),
};
send_msg(msg).unwrap();
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MouseButton {
Left = 0x110,
Right,
Middle,
Side,
Extra,
Forward,
Back,
}
impl From<char> for KeyIntOrString {
fn from(value: char) -> Self {
Self::String(value.to_string())
}
}
impl From<u32> for KeyIntOrString {
fn from(value: u32) -> Self {
Self::Int(value)
}
}
impl From<Keysym> for KeyIntOrString {
fn from(value: Keysym) -> Self {
Self::Int(value.raw())
}
}

View file

@ -1,20 +1,40 @@
mod input;
mod msg;
mod process;
mod window;
use input::Input;
pub use input::MouseButton;
pub use msg::Modifier;
pub use msg::MouseEdge;
use window::Window;
pub use xkbcommon::xkb::keysyms;
pub use xkbcommon::xkb::Keysym;
use std::{
io::Write,
collections::HashMap,
io::{Read, Write},
os::unix::net::UnixStream,
path::PathBuf,
sync::{Mutex, OnceLock},
sync::{atomic::AtomicU32, Mutex, OnceLock},
};
use msg::{Args, Msg, Request};
use msg::{Args, CallbackId, IncomingMsg, Msg, Request, RequestResponse};
use process::Process;
use crate::msg::RequestId;
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());
static CALLBACK_VEC: Mutex<Vec<Box<dyn FnMut(Option<Args>) + Send>>> = Mutex::new(Vec::new());
lazy_static::lazy_static! {
static ref UNREAD_CALLBACK_MSGS: Mutex<HashMap<CallbackId, IncomingMsg>> = Mutex::new(HashMap::new());
static ref UNREAD_REQUEST_MSGS: Mutex<HashMap<RequestId, IncomingMsg>> = Mutex::new(HashMap::new());
}
static REQUEST_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
/// Setup Pinnacle.
pub fn setup(config_func: impl FnOnce(Pinnacle)) -> anyhow::Result<()> {
STREAM
.set(Mutex::new(UnixStream::connect(PathBuf::from(
@ -22,11 +42,46 @@ pub fn setup(config_func: impl FnOnce(Pinnacle)) -> anyhow::Result<()> {
))?))
.unwrap();
let pinnacle = Pinnacle { process: Process };
let pinnacle = Pinnacle {
process: Process,
input: Input,
window: Window,
};
config_func(pinnacle);
Ok(())
loop {
let mut unread_callback_msgs = UNREAD_CALLBACK_MSGS.lock().unwrap();
let mut callback_vec = CALLBACK_VEC.lock().unwrap();
let mut to_remove = vec![];
for (cb_id, incoming_msg) in unread_callback_msgs.iter() {
let IncomingMsg::CallCallback { callback_id, args } = incoming_msg else {
continue;
};
let Some(f) = callback_vec.get_mut(callback_id.0 as usize) else {
continue;
};
f(args.clone());
to_remove.push(*cb_id);
}
for id in to_remove {
unread_callback_msgs.remove(&id);
}
let incoming_msg = read_msg(None);
assert!(matches!(incoming_msg, IncomingMsg::CallCallback { .. }));
let IncomingMsg::CallCallback { callback_id, args } = incoming_msg else {
unreachable!()
};
let Some(f) = callback_vec.get_mut(callback_id.0 as usize) else {
continue;
};
f(args);
}
}
fn send_msg(msg: Msg) -> anyhow::Result<()> {
@ -41,16 +96,78 @@ fn send_msg(msg: Msg) -> anyhow::Result<()> {
Ok(())
}
fn read_msg() {
todo!()
fn read_msg(request_id: Option<RequestId>) -> IncomingMsg {
loop {
if let Some(request_id) = request_id {
if let Some(msg) = UNREAD_REQUEST_MSGS.lock().unwrap().remove(&request_id) {
return msg;
}
}
let mut stream = STREAM.get().unwrap().lock().unwrap();
let mut msg_len_bytes = [0u8; 4];
stream.read_exact(msg_len_bytes.as_mut_slice()).unwrap();
let msg_len = u32::from_ne_bytes(msg_len_bytes);
let mut msg_bytes = vec![0u8; msg_len as usize];
stream.read_exact(msg_bytes.as_mut_slice()).unwrap();
let incoming_msg: IncomingMsg = rmp_serde::from_slice(msg_bytes.as_slice()).unwrap();
if let Some(request_id) = request_id {
match &incoming_msg {
IncomingMsg::CallCallback {
callback_id,
args: _,
} => {
UNREAD_CALLBACK_MSGS
.lock()
.unwrap()
.insert(*callback_id, incoming_msg);
}
IncomingMsg::RequestResponse {
request_id: req_id,
response: _,
} => {
if req_id != &request_id {
UNREAD_REQUEST_MSGS
.lock()
.unwrap()
.insert(*req_id, incoming_msg);
} else {
return incoming_msg;
}
}
}
} else {
return incoming_msg;
}
}
}
fn request(request: Request) {
//
fn request(request: Request) -> RequestResponse {
use std::sync::atomic::Ordering;
let request_id = REQUEST_ID_COUNTER.fetch_add(1, Ordering::Relaxed);
let msg = Msg::Request {
request_id: RequestId(request_id),
request,
};
send_msg(msg).unwrap(); // TODO: propogate
let IncomingMsg::RequestResponse {
request_id: _,
response,
} = read_msg(Some(RequestId(request_id)))
else {
unreachable!()
};
response
}
pub struct Pinnacle {
pub process: Process,
pub window: Window,
pub input: Input,
}
impl Pinnacle {}

View file

@ -1,6 +1,6 @@
use std::num::NonZeroU32;
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy)]
#[derive(Debug, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy)]
pub struct CallbackId(pub u32);
#[derive(Debug, Hash, serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq)]
@ -88,8 +88,8 @@ pub enum LibinputSetting {
TapEnabled(bool),
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct RequestId(u32);
#[derive(Debug, Hash, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct RequestId(pub u32);
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct WindowRuleCondition {
@ -116,7 +116,7 @@ pub enum FloatingOrTiled {
Tiled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum FullscreenOrMaximized {
Neither,
Fullscreen,
@ -300,7 +300,7 @@ pub enum Modifier {
Super,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
pub enum Args {
/// Send a message with lines from the spawned process.
Spawn {
@ -317,3 +317,66 @@ pub enum Args {
output_name: String,
},
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum IncomingMsg {
CallCallback {
callback_id: CallbackId,
#[serde(default)]
args: Option<Args>,
},
RequestResponse {
request_id: RequestId,
response: RequestResponse,
},
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum RequestResponse {
Window {
window_id: Option<WindowId>,
},
Windows {
window_ids: Vec<WindowId>,
},
WindowProps {
size: Option<(i32, i32)>,
loc: Option<(i32, i32)>,
class: Option<String>,
title: Option<String>,
focused: Option<bool>,
floating: Option<bool>,
fullscreen_or_maximized: Option<FullscreenOrMaximized>,
},
Output {
output_name: Option<String>,
},
Outputs {
output_names: Vec<String>,
},
OutputProps {
/// The make of the output.
make: Option<String>,
/// The model of the output.
model: Option<String>,
/// The location of the output in the space.
loc: Option<(i32, i32)>,
/// The resolution of the output.
res: Option<(i32, i32)>,
/// The refresh rate of the output.
refresh_rate: Option<i32>,
/// The size of the output, in millimeters.
physical_size: Option<(i32, i32)>,
/// Whether the output is focused or not.
focused: Option<bool>,
tag_ids: Option<Vec<TagId>>,
},
Tags {
tag_ids: Vec<TagId>,
},
TagProps {
active: Option<bool>,
name: Option<String>,
output_name: Option<String>,
},
}

View file

@ -19,13 +19,13 @@ impl Process {
where
F: FnMut(Option<String>, Option<String>, Option<i32>, Option<String>) + Send + 'static,
{
let args_callback = move |args: Args| {
if let Args::Spawn {
let args_callback = move |args: Option<Args>| {
if let Some(Args::Spawn {
stdout,
stderr,
exit_code,
exit_msg,
} = args
}) = args
{
callback(stdout, stderr, exit_code, exit_msg);
}

143
api/rust/src/window.rs Normal file
View file

@ -0,0 +1,143 @@
use crate::{
msg::{FullscreenOrMaximized, Msg, Request, RequestResponse, WindowId},
request, send_msg,
};
pub struct Window;
impl Window {
pub fn get_by_class<'a>(&self, class: &'a str) -> impl Iterator<Item = WindowHandle> + 'a {
self.get_all()
.filter(|win| win.class().as_deref() == Some(class))
}
pub fn get_focused(&self) -> Option<WindowHandle> {
self.get_all()
.find(|win| win.focused().is_some_and(|focused| focused))
}
pub fn get_all(&self) -> impl Iterator<Item = WindowHandle> {
let RequestResponse::Windows { window_ids } = request(Request::GetWindows) else {
unreachable!()
};
window_ids.into_iter().map(WindowHandle)
}
}
pub struct WindowHandle(WindowId);
impl WindowHandle {
pub fn toggle_floating(&self) {
send_msg(Msg::ToggleFloating { window_id: self.0 }).unwrap();
}
pub fn toggle_fullscreen(&self) {
send_msg(Msg::ToggleFullscreen { window_id: self.0 }).unwrap();
}
pub fn toggle_maximized(&self) {
send_msg(Msg::ToggleMaximized { window_id: self.0 }).unwrap();
}
pub fn set_size(&self, width: Option<i32>, height: Option<i32>) {
send_msg(Msg::SetWindowSize {
window_id: self.0,
width,
height,
})
.unwrap();
}
pub fn close(&self) {
send_msg(Msg::CloseWindow { window_id: self.0 }).unwrap();
}
pub fn size(&self) -> Option<(i32, i32)> {
let RequestResponse::WindowProps { size, .. } =
request(Request::GetWindowProps { window_id: self.0 })
else {
unreachable!()
};
size
}
pub fn loc(&self) -> Option<(i32, i32)> {
let RequestResponse::WindowProps { loc, .. } =
request(Request::GetWindowProps { window_id: self.0 })
else {
unreachable!()
};
loc
}
pub fn class(&self) -> Option<String> {
let RequestResponse::WindowProps { class, .. } =
request(Request::GetWindowProps { window_id: self.0 })
else {
unreachable!()
};
class
}
pub fn title(&self) -> Option<String> {
let RequestResponse::WindowProps { title, .. } =
request(Request::GetWindowProps { window_id: self.0 })
else {
unreachable!()
};
title
}
pub fn floating(&self) -> Option<bool> {
let RequestResponse::WindowProps { floating, .. } =
request(Request::GetWindowProps { window_id: self.0 })
else {
unreachable!()
};
floating
}
pub fn maximized(&self) -> Option<bool> {
let RequestResponse::WindowProps {
fullscreen_or_maximized,
..
} = request(Request::GetWindowProps { window_id: self.0 })
else {
unreachable!()
};
fullscreen_or_maximized.map(|fullscreen_or_maximized| {
matches!(fullscreen_or_maximized, FullscreenOrMaximized::Maximized)
})
}
pub fn fullscreen(&self) -> Option<bool> {
let RequestResponse::WindowProps {
fullscreen_or_maximized,
..
} = request(Request::GetWindowProps { window_id: self.0 })
else {
unreachable!()
};
fullscreen_or_maximized.map(|fullscreen_or_maximized| {
matches!(fullscreen_or_maximized, FullscreenOrMaximized::Fullscreen)
})
}
pub fn focused(&self) -> Option<bool> {
let RequestResponse::WindowProps { focused, .. } =
request(Request::GetWindowProps { window_id: self.0 })
else {
unreachable!()
};
focused
}
}