Extract proto into crate, start impl'ing services

This commit is contained in:
Ottatop 2024-01-09 19:25:51 -06:00
parent 3d05338c82
commit 46ba7d9ad0
10 changed files with 328 additions and 77 deletions

14
Cargo.lock generated
View file

@ -1548,11 +1548,12 @@ name = "pinnacle"
version = "0.0.1"
dependencies = [
"anyhow",
"bitflags 2.4.1",
"clap",
"const_format",
"image",
"lazy_static",
"nix 0.27.1",
"pinnacle-api-defs",
"prost",
"prost-types",
"rmp",
@ -1567,7 +1568,6 @@ dependencies = [
"tokio-stream",
"toml",
"tonic",
"tonic-build",
"tonic-reflection",
"tracing",
"tracing-appender",
@ -1578,6 +1578,16 @@ dependencies = [
"xkbcommon",
]
[[package]]
name = "pinnacle-api-defs"
version = "0.0.1"
dependencies = [
"const_format",
"prost",
"tonic",
"tonic-build",
]
[[package]]
name = "pkg-config"
version = "0.3.28"

View file

@ -21,11 +21,6 @@ image = { version = "0.24", default-features = false, optional = true }
serde = { version = "1.0", features = ["derive"] }
rmp = { version = "0.8.12" }
rmp-serde = { version = "1.1.2" }
# calloop = { version = "0.12.2", features = ["executor", "futures-io"] }
# futures-lite = { version = "2" }
# async-process = { version = "2" }
# async-channel = "2"
# async-net = "2.0.0"
x11rb = { version = "0.13", default-features = false, features = ["composite"], optional = true }
shellexpand = "3.1.0"
toml = "0.8"
@ -42,10 +37,9 @@ tonic = "0.10.2"
tonic-reflection = "0.10.2"
tokio-stream = { version = "0.1.14", features = ["net"] }
tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread", "process", "io-util"]}
bitflags = "2.4.1"
pinnacle-api-defs = { path = "./pinnacle-api-defs" }
[build-dependencies]
tonic-build = "0.10.2"
const_format = "0.2.32"
[features]
default = ["egl", "winit", "udev", "xwayland"]
@ -66,3 +60,6 @@ udev = [
]
winit = ["smithay/backend_winit", "smithay/backend_drm"]
xwayland = ["smithay/xwayland", "x11rb", "smithay/x11rb_event_source", "xcursor"]
[workspace]
members = ["pinnacle-api-defs"]

View file

@ -9,7 +9,8 @@ enum Modifier {
MODIFIER_UNSPECIFIED = 0;
MODIFIER_SHIFT = 1;
MODIFIER_CTRL = 2;
MODIFIER_SUPER = 3;
MODIFIER_ALT = 3;
MODIFIER_SUPER = 4;
}
message SetKeybindRequest {

View file

@ -1,32 +1,8 @@
use std::path::PathBuf;
use const_format::formatcp;
fn main() {
println!("cargo:rerun-if-changed=api/lua");
println!("cargo:rerun-if-changed=api/protocol");
std::process::Command::new("/bin/sh")
.arg("install_libs.sh")
.spawn()
.unwrap();
const VERSION: &str = "v0alpha1";
const PROTOS: &[&str] = &[
formatcp!("api/protocol/pinnacle/{VERSION}/pinnacle.proto"),
formatcp!("api/protocol/pinnacle/input/{VERSION}/input.proto"),
formatcp!("api/protocol/pinnacle/input/libinput/{VERSION}/libinput.proto"),
formatcp!("api/protocol/pinnacle/output/{VERSION}/output.proto"),
formatcp!("api/protocol/pinnacle/process/{VERSION}/process.proto"),
formatcp!("api/protocol/pinnacle/tag/{VERSION}/tag.proto"),
formatcp!("api/protocol/pinnacle/window/{VERSION}/window.proto"),
formatcp!("api/protocol/pinnacle/window/rules/{VERSION}/rules.proto"),
];
let descriptor_path = PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("pinnacle.bin");
tonic_build::configure()
.file_descriptor_set_path(descriptor_path)
.compile(PROTOS, &["api/protocol"])
.unwrap();
}

View file

@ -0,0 +1,13 @@
[package]
name = "pinnacle-api-defs"
version = "0.0.1"
edition = "2021"
[dependencies]
tonic = "0.10.2"
prost = "0.12.3"
# prost-types = "0.12.3"
[build-dependencies]
tonic-build = "0.10.2"
const_format = "0.2.32"

View file

@ -0,0 +1,26 @@
use std::path::PathBuf;
use const_format::formatcp;
fn main() {
println!("cargo:rerun-if-changed=api/protocol");
const VERSION: &str = "v0alpha1";
const PROTOS: &[&str] = &[
formatcp!("../api/protocol/pinnacle/{VERSION}/pinnacle.proto"),
formatcp!("../api/protocol/pinnacle/input/{VERSION}/input.proto"),
formatcp!("../api/protocol/pinnacle/input/libinput/{VERSION}/libinput.proto"),
formatcp!("../api/protocol/pinnacle/output/{VERSION}/output.proto"),
formatcp!("../api/protocol/pinnacle/process/{VERSION}/process.proto"),
formatcp!("../api/protocol/pinnacle/tag/{VERSION}/tag.proto"),
formatcp!("../api/protocol/pinnacle/window/{VERSION}/window.proto"),
formatcp!("../api/protocol/pinnacle/window/rules/{VERSION}/rules.proto"),
];
let descriptor_path = PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("pinnacle.bin");
tonic_build::configure()
.file_descriptor_set_path(descriptor_path)
.compile(PROTOS, &["../api/protocol"])
.unwrap();
}

View file

@ -0,0 +1,44 @@
pub mod pinnacle {
pub mod v0alpha1 {
tonic::include_proto!("pinnacle.v0alpha1");
}
pub mod input {
pub mod v0alpha1 {
tonic::include_proto!("pinnacle.input.v0alpha1");
}
pub mod libinput {
pub mod v0alpha1 {
tonic::include_proto!("pinnacle.input.libinput.v0alpha1");
}
}
}
pub mod output {
pub mod v0alpha1 {
tonic::include_proto!("pinnacle.output.v0alpha1");
}
}
pub mod tag {
pub mod v0alpha1 {
tonic::include_proto!("pinnacle.tag.v0alpha1");
}
}
pub mod window {
pub mod v0alpha1 {
tonic::include_proto!("pinnacle.window.v0alpha1");
}
pub mod rules {
pub mod v0alpha1 {
tonic::include_proto!("pinnacle.window.rules.v0alpha1");
}
}
}
}
pub const FILE_DESCRIPTOR_SET: &[u8] = tonic::include_file_descriptor_set!("pinnacle");

View file

@ -1,44 +1,143 @@
pub mod pinnacle {
use std::{collections::HashSet, pin::Pin};
pub mod v0alpha1 {
tonic::include_proto!("pinnacle.v0alpha1");
}
use smithay::reexports::calloop;
use tokio_stream::Stream;
use tonic::{Response, Status};
pub mod input {
pub mod v0alpha1 {
tonic::include_proto!("pinnacle.input.v0alpha1");
}
use crate::{input::ModifierMask, state::State};
pub mod libinput {
pub mod v0alpha1 {
tonic::include_proto!("pinnacle.input.libinput.v0alpha1");
}
}
}
use self::pinnacle::{
input::{
libinput::v0alpha1::SetLibinputSettingRequest,
v0alpha1::{
SetKeybindRequest, SetKeybindResponse, SetMousebindRequest, SetMousebindResponse,
SetXkbConfigRequest, SetXkbRepeatRequest,
},
},
v0alpha1::QuitRequest,
};
pub mod output {
pub mod v0alpha1 {
tonic::include_proto!("pinnacle.output.v0alpha1");
}
}
pub use pinnacle_api_defs::pinnacle;
pub use pinnacle_api_defs::FILE_DESCRIPTOR_SET;
pub mod tag {
pub mod v0alpha1 {
tonic::include_proto!("pinnacle.tag.v0alpha1");
}
}
type ResponseStream<T> = Pin<Box<dyn Stream<Item = Result<T, Status>> + Send>>;
pub type StateFnSender = calloop::channel::Sender<Box<dyn FnOnce(&mut State) + Send>>;
pub mod window {
pub mod v0alpha1 {
tonic::include_proto!("pinnacle.window.v0alpha1");
}
pub struct PinnacleService {
pub sender: StateFnSender,
}
pub mod rules {
pub mod v0alpha1 {
tonic::include_proto!("pinnacle.window.rules.v0alpha1");
}
}
#[tonic::async_trait]
impl pinnacle::v0alpha1::pinnacle_service_server::PinnacleService for PinnacleService {
async fn quit(
&self,
_request: tonic::Request<QuitRequest>,
) -> Result<tonic::Response<()>, tonic::Status> {
tracing::trace!("PinnacleService.quit");
let f = Box::new(|state: &mut State| {
state.loop_signal.stop();
});
// Expect is ok here, if it panics then the state was dropped beforehand
self.sender.send(f).expect("failed to send f");
Ok(tonic::Response::new(()))
}
}
pub const FILE_DESCRIPTOR_SET: &[u8] = tonic::include_file_descriptor_set!("pinnacle");
pub struct InputService {
pub sender: StateFnSender,
}
#[tonic::async_trait]
impl pinnacle::input::v0alpha1::input_service_server::InputService for InputService {
type SetKeybindStream = ResponseStream<SetKeybindResponse>;
type SetMousebindStream = ResponseStream<SetMousebindResponse>;
async fn set_keybind(
&self,
request: tonic::Request<SetKeybindRequest>,
) -> Result<Response<Self::SetKeybindStream>, Status> {
let request = request.into_inner();
tracing::debug!(request = ?request);
let modifiers = request
.modifiers()
.fold(ModifierMask::empty(), |acc, modifier| match modifier {
pinnacle::input::v0alpha1::Modifier::Unspecified => acc,
pinnacle::input::v0alpha1::Modifier::Shift => acc | ModifierMask::SHIFT,
pinnacle::input::v0alpha1::Modifier::Ctrl => acc | ModifierMask::CTRL,
pinnacle::input::v0alpha1::Modifier::Alt => acc | ModifierMask::ALT,
pinnacle::input::v0alpha1::Modifier::Super => acc | ModifierMask::SUPER,
});
let key = request
.key
.ok_or_else(|| Status::invalid_argument("no key specified"))?;
use pinnacle::input::v0alpha1::set_keybind_request::Key;
let keysym = match key {
Key::RawCode(num) => {
tracing::info!("set keybind: {:?}, raw {}", modifiers, num);
xkbcommon::xkb::Keysym::new(num)
}
Key::XkbName(s) => {
if s.chars().count() == 1 {
let Some(ch) = s.chars().next() else { unreachable!() };
let keysym = xkbcommon::xkb::Keysym::from_char(ch);
tracing::info!("set keybind: {:?}, {:?}", modifiers, keysym);
keysym
} else {
let keysym =
xkbcommon::xkb::keysym_from_name(&s, xkbcommon::xkb::KEYSYM_NO_FLAGS);
tracing::info!("set keybind: {:?}, {:?}", modifiers, keysym);
keysym
}
}
};
let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
self.sender
.send(Box::new(move |state| {
state
.input_state
.grpc_keybinds
.insert((modifiers, keysym), sender);
}))
.map_err(|_| Status::internal("internal state was not running"))?;
let receiver_stream = tokio_stream::wrappers::UnboundedReceiverStream::new(receiver);
Ok(Response::new(
Box::pin(receiver_stream) as Self::SetKeybindStream
))
}
async fn set_mousebind(
&self,
request: tonic::Request<SetMousebindRequest>,
) -> Result<Response<Self::SetMousebindStream>, Status> {
todo!()
}
async fn set_xkb_config(
&self,
request: tonic::Request<SetXkbConfigRequest>,
) -> Result<Response<()>, Status> {
todo!()
}
async fn set_xkb_repeat(
&self,
request: tonic::Request<SetXkbRepeatRequest>,
) -> Result<Response<()>, Status> {
todo!()
}
async fn set_libinput_setting(
&self,
request: tonic::Request<SetLibinputSettingRequest>,
) -> Result<Response<()>, Status> {
todo!()
}
}

View file

@ -5,11 +5,12 @@ pub mod libinput;
use std::collections::HashMap;
use crate::{
api::msg::{CallbackId, Modifier, ModifierMask, MouseEdge, OutgoingMsg},
api::msg::{CallbackId, Modifier, MouseEdge, OutgoingMsg},
focus::FocusTarget,
state::WithState,
window::WindowElement,
};
use pinnacle_api_defs::pinnacle::input::v0alpha1::SetKeybindResponse;
use smithay::{
backend::input::{
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputBackend, InputEvent,
@ -24,24 +25,38 @@ use smithay::{
utils::{Logical, Point, SERIAL_COUNTER},
wayland::{seat::WaylandFocus, shell::wlr_layer},
};
use tokio::sync::mpsc::UnboundedSender;
use xkbcommon::xkb::Keysym;
use crate::state::State;
use self::libinput::LibinputSetting;
bitflags::bitflags! {
#[derive(Debug, Hash, Copy, Clone, PartialEq, Eq)]
pub struct ModifierMask: u8 {
const SHIFT = 1;
const CTRL = 1 << 1;
const ALT = 1 << 2;
const SUPER = 1 << 3;
}
}
#[derive(Default, Debug)]
pub struct InputState {
/// A hashmap of modifier keys and keycodes to callback IDs
pub keybinds: HashMap<(ModifierMask, Keysym), CallbackId>,
pub keybinds: HashMap<(crate::api::msg::ModifierMask, Keysym), CallbackId>,
/// A hashmap of modifier keys and mouse button codes to callback IDs
pub mousebinds: HashMap<(ModifierMask, u32, MouseEdge), CallbackId>,
pub reload_keybind: Option<(ModifierMask, Keysym)>,
pub kill_keybind: Option<(ModifierMask, Keysym)>,
pub mousebinds: HashMap<(crate::api::msg::ModifierMask, u32, MouseEdge), CallbackId>,
pub reload_keybind: Option<(crate::api::msg::ModifierMask, Keysym)>,
pub kill_keybind: Option<(crate::api::msg::ModifierMask, Keysym)>,
/// User defined libinput settings that will be applied
pub libinput_settings: Vec<LibinputSetting>,
/// All libinput devices that have been connected
pub libinput_devices: Vec<input::Device>,
pub grpc_keybinds:
HashMap<(ModifierMask, Keysym), UnboundedSender<Result<SetKeybindResponse, tonic::Status>>>,
}
impl InputState {
@ -54,6 +69,7 @@ impl InputState {
enum KeyAction {
/// Call a callback from a config process
CallCallback(CallbackId),
CallGrpcCallback(ModifierMask, Keysym),
Quit,
SwitchVt(i32),
ReloadConfig,
@ -176,6 +192,7 @@ impl State {
serial,
time,
|state, modifiers, keysym| {
tracing::debug!(keysym = ?keysym, raw_keysyms = ?keysym.raw_syms(), modified_syms = ?keysym.modified_syms());
if press_state == KeyState::Pressed {
let mut modifier_mask = Vec::<Modifier>::new();
if modifiers.alt {
@ -190,11 +207,30 @@ impl State {
if modifiers.logo {
modifier_mask.push(Modifier::Super);
}
let modifier_mask = ModifierMask::from(modifier_mask);
let modifier_mask = crate::api::msg::ModifierMask::from(modifier_mask);
let mut grpc_modifiers = ModifierMask::empty();
if modifiers.alt {
grpc_modifiers |= ModifierMask::ALT;
}
if modifiers.shift {
grpc_modifiers |= ModifierMask::SHIFT;
}
if modifiers.ctrl {
grpc_modifiers |= ModifierMask::CTRL;
}
if modifiers.logo {
grpc_modifiers |= ModifierMask::SUPER;
}
let raw_sym = keysym.raw_syms().iter().next();
let mod_sym = keysym.modified_sym();
if state.input_state.grpc_keybinds.get(&(grpc_modifiers, keysym.modified_sym())).is_some() {
return FilterResult::Intercept(KeyAction::CallGrpcCallback(grpc_modifiers, keysym.modified_sym()));
}
let cb_id_mod = state.input_state.keybinds.get(&(modifier_mask, mod_sym));
let cb_id_raw = raw_sym.and_then(|raw_sym| {
@ -239,6 +275,11 @@ impl State {
}
}
}
Some(KeyAction::CallGrpcCallback(mods, keysym)) => {
if let Some(sender) = self.input_state.grpc_keybinds.get(&(mods, keysym)) {
let _ = sender.send(Ok(SetKeybindResponse {}));
}
}
Some(KeyAction::SwitchVt(vt)) => {
self.switch_vt(vt);
}
@ -270,7 +311,7 @@ impl State {
ButtonState::Released => MouseEdge::Release,
ButtonState::Pressed => MouseEdge::Press,
};
let modifier_mask = ModifierMask::from(keyboard.modifier_state());
let modifier_mask = crate::api::msg::ModifierMask::from(keyboard.modifier_state());
// If any mousebinds are detected, call the config's callback and return.
if let Some(&callback_id) =

View file

@ -3,7 +3,14 @@
use std::{cell::RefCell, sync::Arc, time::Duration};
use crate::{
api::{msg::Msg, ApiState},
api::{
msg::Msg,
protocol::{
pinnacle::v0alpha1::pinnacle_service_server::PinnacleServiceServer, InputService,
PinnacleService,
},
ApiState,
},
backend::Backend,
config::Config,
cursor::Cursor,
@ -11,6 +18,7 @@ use crate::{
grab::resize_grab::ResizeSurfaceState,
window::WindowElement,
};
use pinnacle_api_defs::pinnacle::input::v0alpha1::input_service_server::InputServiceServer;
use smithay::{
desktop::{PopupManager, Space},
input::{keyboard::XkbConfig, pointer::CursorImageStatus, Seat, SeatState},
@ -227,6 +235,42 @@ impl State {
};
tracing::debug!("xwayland set up");
let (grpc_sender, grpc_receiver) =
calloop::channel::channel::<Box<dyn FnOnce(&mut Self) + Send>>();
loop_handle.insert_idle(|data| {
data.state
.loop_handle
.insert_source(grpc_receiver, |msg, _, data| match msg {
Event::Msg(f) => f(&mut data.state),
Event::Closed => panic!("grpc receiver was closed"),
})
.expect("failed to insert grpc_receiver into loop");
});
let pinnacle_service = PinnacleService {
sender: grpc_sender.clone(),
};
let input_service = InputService {
sender: grpc_sender.clone(),
};
let refl_service = tonic_reflection::server::Builder::configure()
.register_encoded_file_descriptor_set(crate::api::protocol::FILE_DESCRIPTOR_SET)
.build()?;
let addr = "127.0.0.1:8080".parse()?;
tokio::spawn(async move {
tonic::transport::Server::builder()
.add_service(refl_service)
.add_service(PinnacleServiceServer::new(pinnacle_service))
.add_service(InputServiceServer::new(input_service))
.serve(addr)
.await
.expect("failed to serve grpc");
});
Ok(Self {
backend,
loop_signal,