mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-26 21:58:10 +01:00
Add input and window stuff
This commit is contained in:
parent
3e56450e29
commit
2c3fb2dbd7
7 changed files with 456 additions and 20 deletions
|
@ -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"
|
||||
|
|
|
@ -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
99
api/rust/src/input.rs
Normal 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())
|
||||
}
|
||||
}
|
|
@ -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 {}
|
||||
|
|
|
@ -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>,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
143
api/rust/src/window.rs
Normal 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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue