mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2025-01-30 20:34:49 +01:00
Impl services for input, tag, and process
This commit is contained in:
parent
46ba7d9ad0
commit
31d3ffc10d
8 changed files with 628 additions and 32 deletions
|
@ -43,8 +43,10 @@ message SetXkbConfigRequest {
|
|||
optional string options = 5;
|
||||
}
|
||||
|
||||
message SetXkbRepeatRequest {
|
||||
message SetRepeatRateRequest {
|
||||
// How often the key should repeat, in milliseconds
|
||||
optional int32 rate = 1;
|
||||
// How long the key has to be held down before repeating, in milliseconds
|
||||
optional int32 delay = 2;
|
||||
}
|
||||
|
||||
|
@ -53,7 +55,7 @@ service InputService {
|
|||
rpc SetMousebind(SetMousebindRequest) returns (stream SetMousebindResponse);
|
||||
|
||||
rpc SetXkbConfig(SetXkbConfigRequest) returns (google.protobuf.Empty);
|
||||
rpc SetXkbRepeat(SetXkbRepeatRequest) returns (google.protobuf.Empty);
|
||||
rpc SetRepeatRate(SetRepeatRateRequest) returns (google.protobuf.Empty);
|
||||
|
||||
rpc SetLibinputSetting(.pinnacle.input.libinput.v0alpha1.SetLibinputSettingRequest) returns (google.protobuf.Empty);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
syntax = "proto2";
|
||||
|
||||
package pinnacle.input.v0alpha1;
|
||||
package pinnacle.process.v0alpha1;
|
||||
|
||||
message SpawnRequest {
|
||||
repeated string args = 1;
|
||||
|
|
|
@ -5,9 +5,10 @@ package pinnacle.tag.v0alpha1;
|
|||
import "google/protobuf/empty.proto";
|
||||
|
||||
message SetActiveRequest {
|
||||
optional uint32 tag_id = 1;
|
||||
oneof set_or_toggle {
|
||||
bool set = 1;
|
||||
google.protobuf.Empty toggle = 2;
|
||||
bool set = 2;
|
||||
google.protobuf.Empty toggle = 3;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,12 @@ pub mod pinnacle {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod process {
|
||||
pub mod v0alpha1 {
|
||||
tonic::include_proto!("pinnacle.process.v0alpha1");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const FILE_DESCRIPTOR_SET: &[u8] = tonic::include_file_descriptor_set!("pinnacle");
|
||||
|
|
|
@ -1,19 +1,38 @@
|
|||
use std::{collections::HashSet, pin::Pin};
|
||||
use std::{ffi::OsString, pin::Pin, process::Stdio};
|
||||
|
||||
use smithay::reexports::calloop;
|
||||
use pinnacle_api_defs::pinnacle::{
|
||||
input::libinput::v0alpha1::set_libinput_setting_request::{
|
||||
AccelProfile, ClickMethod, ScrollMethod, TapButtonMap,
|
||||
},
|
||||
tag::v0alpha1::{
|
||||
AddRequest, AddResponse, RemoveRequest, SetActiveRequest, SetLayoutRequest, SwitchToRequest,
|
||||
},
|
||||
};
|
||||
use smithay::{
|
||||
input::keyboard::XkbConfig,
|
||||
reexports::{calloop, input as libinput},
|
||||
};
|
||||
use sysinfo::ProcessRefreshKind;
|
||||
use tokio::io::AsyncBufReadExt;
|
||||
use tokio_stream::Stream;
|
||||
use tonic::{Response, Status};
|
||||
use tonic::{Request, Response, Status};
|
||||
|
||||
use crate::{input::ModifierMask, state::State};
|
||||
use crate::{
|
||||
input::ModifierMask,
|
||||
output::OutputName,
|
||||
state::{State, WithState},
|
||||
tag::{Tag, TagId},
|
||||
};
|
||||
|
||||
use self::pinnacle::{
|
||||
input::{
|
||||
libinput::v0alpha1::SetLibinputSettingRequest,
|
||||
v0alpha1::{
|
||||
SetKeybindRequest, SetKeybindResponse, SetMousebindRequest, SetMousebindResponse,
|
||||
SetXkbConfigRequest, SetXkbRepeatRequest,
|
||||
SetRepeatRateRequest, SetXkbConfigRequest,
|
||||
},
|
||||
},
|
||||
process::v0alpha1::{SpawnRequest, SpawnResponse},
|
||||
v0alpha1::QuitRequest,
|
||||
};
|
||||
|
||||
|
@ -29,10 +48,7 @@ pub struct PinnacleService {
|
|||
|
||||
#[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> {
|
||||
async fn quit(&self, _request: Request<QuitRequest>) -> Result<Response<()>, Status> {
|
||||
tracing::trace!("PinnacleService.quit");
|
||||
let f = Box::new(|state: &mut State| {
|
||||
state.loop_signal.stop();
|
||||
|
@ -40,7 +56,7 @@ impl pinnacle::v0alpha1::pinnacle_service_server::PinnacleService for PinnacleSe
|
|||
// 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(()))
|
||||
Ok(Response::new(()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,12 +71,13 @@ impl pinnacle::input::v0alpha1::input_service_server::InputService for InputServ
|
|||
|
||||
async fn set_keybind(
|
||||
&self,
|
||||
request: tonic::Request<SetKeybindRequest>,
|
||||
request: Request<SetKeybindRequest>,
|
||||
) -> Result<Response<Self::SetKeybindStream>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
tracing::debug!(request = ?request);
|
||||
|
||||
// TODO: impl From<&[Modifier]> for ModifierMask
|
||||
let modifiers = request
|
||||
.modifiers()
|
||||
.fold(ModifierMask::empty(), |acc, modifier| match modifier {
|
||||
|
@ -108,36 +125,568 @@ impl pinnacle::input::v0alpha1::input_service_server::InputService for InputServ
|
|||
|
||||
let receiver_stream = tokio_stream::wrappers::UnboundedReceiverStream::new(receiver);
|
||||
|
||||
Ok(Response::new(
|
||||
Box::pin(receiver_stream) as Self::SetKeybindStream
|
||||
))
|
||||
Ok(Response::new(Box::pin(receiver_stream)))
|
||||
}
|
||||
|
||||
async fn set_mousebind(
|
||||
&self,
|
||||
request: tonic::Request<SetMousebindRequest>,
|
||||
request: Request<SetMousebindRequest>,
|
||||
) -> Result<Response<Self::SetMousebindStream>, Status> {
|
||||
todo!()
|
||||
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 button = request
|
||||
.button
|
||||
.ok_or_else(|| Status::invalid_argument("no key specified"))?;
|
||||
|
||||
let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
self.sender
|
||||
.send(Box::new(move |state| {
|
||||
state
|
||||
.input_state
|
||||
.grpc_mousebinds
|
||||
.insert((modifiers, button), 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)))
|
||||
}
|
||||
|
||||
async fn set_xkb_config(
|
||||
&self,
|
||||
request: tonic::Request<SetXkbConfigRequest>,
|
||||
request: Request<SetXkbConfigRequest>,
|
||||
) -> Result<Response<()>, Status> {
|
||||
todo!()
|
||||
let request = request.into_inner();
|
||||
|
||||
let f = Box::new(move |state: &mut State| {
|
||||
let new_config = XkbConfig {
|
||||
rules: request.rules(),
|
||||
variant: request.variant(),
|
||||
model: request.model(),
|
||||
layout: request.layout(),
|
||||
options: request.options.clone(),
|
||||
};
|
||||
if let Some(kb) = state.seat.get_keyboard() {
|
||||
if let Err(err) = kb.set_xkb_config(state, new_config) {
|
||||
tracing::error!("Failed to set xkbconfig: {err}");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.sender
|
||||
.send(f)
|
||||
.map_err(|_| Status::internal("internal state was not running"))?;
|
||||
|
||||
Ok(Response::new(()))
|
||||
}
|
||||
|
||||
async fn set_xkb_repeat(
|
||||
async fn set_repeat_rate(
|
||||
&self,
|
||||
request: tonic::Request<SetXkbRepeatRequest>,
|
||||
request: Request<SetRepeatRateRequest>,
|
||||
) -> Result<Response<()>, Status> {
|
||||
todo!()
|
||||
let request = request.into_inner();
|
||||
|
||||
let rate = request
|
||||
.rate
|
||||
.ok_or_else(|| Status::invalid_argument("no rate specified"))?;
|
||||
let delay = request
|
||||
.delay
|
||||
.ok_or_else(|| Status::invalid_argument("no rate specified"))?;
|
||||
|
||||
let f = Box::new(move |state: &mut State| {
|
||||
if let Some(kb) = state.seat.get_keyboard() {
|
||||
kb.change_repeat_info(rate, delay);
|
||||
}
|
||||
});
|
||||
|
||||
self.sender
|
||||
.send(f)
|
||||
.map_err(|_| Status::internal("internal state was not running"))?;
|
||||
|
||||
Ok(Response::new(()))
|
||||
}
|
||||
|
||||
async fn set_libinput_setting(
|
||||
&self,
|
||||
request: tonic::Request<SetLibinputSettingRequest>,
|
||||
request: Request<SetLibinputSettingRequest>,
|
||||
) -> Result<Response<()>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
let setting = request
|
||||
.setting
|
||||
.ok_or_else(|| Status::invalid_argument("no setting specified"))?;
|
||||
|
||||
let discriminant = std::mem::discriminant(&setting);
|
||||
|
||||
use pinnacle::input::libinput::v0alpha1::set_libinput_setting_request::Setting;
|
||||
let apply_setting: Box<dyn Fn(&mut libinput::Device) + Send> = match setting {
|
||||
Setting::AccelProfile(profile) => {
|
||||
let profile = AccelProfile::try_from(profile).unwrap_or(AccelProfile::Unspecified);
|
||||
|
||||
match profile {
|
||||
AccelProfile::Unspecified => {
|
||||
return Err(Status::invalid_argument("unspecified accel profile"));
|
||||
}
|
||||
AccelProfile::Flat => Box::new(|device| {
|
||||
let _ = device.config_accel_set_profile(libinput::AccelProfile::Flat);
|
||||
}),
|
||||
AccelProfile::Adaptive => Box::new(|device| {
|
||||
let _ = device.config_accel_set_profile(libinput::AccelProfile::Adaptive);
|
||||
}),
|
||||
}
|
||||
}
|
||||
Setting::AccelSpeed(speed) => Box::new(move |device| {
|
||||
let _ = device.config_accel_set_speed(speed);
|
||||
}),
|
||||
Setting::CalibrationMatrix(matrix) => {
|
||||
let matrix = <[f32; 6]>::try_from(matrix.matrix).map_err(|vec| {
|
||||
Status::invalid_argument(format!(
|
||||
"matrix requires exactly 6 floats but {} were specified",
|
||||
vec.len()
|
||||
))
|
||||
})?;
|
||||
|
||||
Box::new(move |device| {
|
||||
let _ = device.config_calibration_set_matrix(matrix);
|
||||
})
|
||||
}
|
||||
Setting::ClickMethod(method) => {
|
||||
let method = ClickMethod::try_from(method).unwrap_or(ClickMethod::Unspecified);
|
||||
|
||||
match method {
|
||||
ClickMethod::Unspecified => {
|
||||
return Err(Status::invalid_argument("unspecified click method"))
|
||||
}
|
||||
ClickMethod::ButtonAreas => Box::new(|device| {
|
||||
let _ = device.config_click_set_method(libinput::ClickMethod::ButtonAreas);
|
||||
}),
|
||||
ClickMethod::ClickFinger => Box::new(|device| {
|
||||
let _ = device.config_click_set_method(libinput::ClickMethod::Clickfinger);
|
||||
}),
|
||||
}
|
||||
}
|
||||
Setting::DisableWhileTyping(disable) => Box::new(move |device| {
|
||||
let _ = device.config_dwt_set_enabled(disable);
|
||||
}),
|
||||
Setting::LeftHanded(enable) => Box::new(move |device| {
|
||||
let _ = device.config_left_handed_set(enable);
|
||||
}),
|
||||
Setting::MiddleEmulation(enable) => Box::new(move |device| {
|
||||
let _ = device.config_middle_emulation_set_enabled(enable);
|
||||
}),
|
||||
Setting::RotationAngle(angle) => Box::new(move |device| {
|
||||
let _ = device.config_rotation_set_angle(angle % 360);
|
||||
}),
|
||||
Setting::ScrollButton(button) => Box::new(move |device| {
|
||||
let _ = device.config_scroll_set_button(button);
|
||||
}),
|
||||
Setting::ScrollButtonLock(enable) => Box::new(move |device| {
|
||||
let _ = device.config_scroll_set_button_lock(match enable {
|
||||
true => libinput::ScrollButtonLockState::Enabled,
|
||||
false => libinput::ScrollButtonLockState::Disabled,
|
||||
});
|
||||
}),
|
||||
Setting::ScrollMethod(method) => {
|
||||
let method = ScrollMethod::try_from(method).unwrap_or(ScrollMethod::Unspecified);
|
||||
|
||||
match method {
|
||||
ScrollMethod::Unspecified => {
|
||||
return Err(Status::invalid_argument("unspecified scroll method"));
|
||||
}
|
||||
ScrollMethod::NoScroll => Box::new(|device| {
|
||||
let _ = device.config_scroll_set_method(libinput::ScrollMethod::NoScroll);
|
||||
}),
|
||||
ScrollMethod::TwoFinger => Box::new(|device| {
|
||||
let _ = device.config_scroll_set_method(libinput::ScrollMethod::TwoFinger);
|
||||
}),
|
||||
ScrollMethod::Edge => Box::new(|device| {
|
||||
let _ = device.config_scroll_set_method(libinput::ScrollMethod::Edge);
|
||||
}),
|
||||
ScrollMethod::OnButtonDown => Box::new(|device| {
|
||||
let _ =
|
||||
device.config_scroll_set_method(libinput::ScrollMethod::OnButtonDown);
|
||||
}),
|
||||
}
|
||||
}
|
||||
Setting::NaturalScroll(enable) => Box::new(move |device| {
|
||||
let _ = device.config_scroll_set_natural_scroll_enabled(enable);
|
||||
}),
|
||||
Setting::TapButtonMap(map) => {
|
||||
let map = TapButtonMap::try_from(map).unwrap_or(TapButtonMap::Unspecified);
|
||||
|
||||
match map {
|
||||
TapButtonMap::Unspecified => {
|
||||
return Err(Status::invalid_argument("unspecified tap button map"));
|
||||
}
|
||||
TapButtonMap::LeftRightMiddle => Box::new(|device| {
|
||||
let _ = device
|
||||
.config_tap_set_button_map(libinput::TapButtonMap::LeftRightMiddle);
|
||||
}),
|
||||
TapButtonMap::LeftMiddleRight => Box::new(|device| {
|
||||
let _ = device
|
||||
.config_tap_set_button_map(libinput::TapButtonMap::LeftMiddleRight);
|
||||
}),
|
||||
}
|
||||
}
|
||||
Setting::TapDrag(enable) => Box::new(move |device| {
|
||||
let _ = device.config_tap_set_drag_enabled(enable);
|
||||
}),
|
||||
Setting::TapDragLock(enable) => Box::new(move |device| {
|
||||
let _ = device.config_tap_set_drag_lock_enabled(enable);
|
||||
}),
|
||||
Setting::Tap(enable) => Box::new(move |device| {
|
||||
let _ = device.config_tap_set_enabled(enable);
|
||||
}),
|
||||
};
|
||||
|
||||
let f = Box::new(move |state: &mut State| {
|
||||
for device in state.input_state.libinput_devices.iter_mut() {
|
||||
apply_setting(device);
|
||||
}
|
||||
|
||||
state
|
||||
.input_state
|
||||
.grpc_libinput_settings
|
||||
.insert(discriminant, apply_setting);
|
||||
});
|
||||
|
||||
self.sender
|
||||
.send(f)
|
||||
.map_err(|_| Status::internal("internal state was not running"))?;
|
||||
|
||||
Ok(Response::new(()))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProcessService {
|
||||
pub sender: StateFnSender,
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl pinnacle::process::v0alpha1::process_service_server::ProcessService for ProcessService {
|
||||
type SpawnStream = ResponseStream<SpawnResponse>;
|
||||
|
||||
async fn spawn(
|
||||
&self,
|
||||
request: Request<SpawnRequest>,
|
||||
) -> Result<Response<Self::SpawnStream>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
let once = request.once();
|
||||
let has_callback = request.has_callback();
|
||||
let mut command = request.args.into_iter();
|
||||
let arg0 = command
|
||||
.next()
|
||||
.ok_or_else(|| Status::invalid_argument("no args specified"))?;
|
||||
|
||||
let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
let f = Box::new(move |state: &mut State| {
|
||||
if once {
|
||||
state
|
||||
.system_processes
|
||||
.refresh_processes_specifics(ProcessRefreshKind::new());
|
||||
|
||||
let compositor_pid = std::process::id();
|
||||
let already_running =
|
||||
state
|
||||
.system_processes
|
||||
.processes_by_exact_name(&arg0)
|
||||
.any(|proc| {
|
||||
proc.parent()
|
||||
.is_some_and(|parent_pid| parent_pid.as_u32() == compositor_pid)
|
||||
});
|
||||
|
||||
if already_running {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let Ok(mut child) = tokio::process::Command::new(OsString::from(arg0.clone()))
|
||||
.envs(
|
||||
[("WAYLAND_DISPLAY", state.socket_name.clone())]
|
||||
.into_iter()
|
||||
.chain(state.xdisplay.map(|xdisp| ("DISPLAY", format!(":{xdisp}")))),
|
||||
)
|
||||
.stdin(match has_callback {
|
||||
true => Stdio::piped(),
|
||||
false => Stdio::null(),
|
||||
})
|
||||
.stdout(match has_callback {
|
||||
true => Stdio::piped(),
|
||||
false => Stdio::null(),
|
||||
})
|
||||
.stderr(match has_callback {
|
||||
true => Stdio::piped(),
|
||||
false => Stdio::null(),
|
||||
})
|
||||
.args(command)
|
||||
.spawn()
|
||||
else {
|
||||
tracing::warn!("Tried to run {arg0}, but it doesn't exist",);
|
||||
return;
|
||||
};
|
||||
|
||||
if !has_callback {
|
||||
return;
|
||||
}
|
||||
|
||||
let stdout = child.stdout.take();
|
||||
let stderr = child.stderr.take();
|
||||
|
||||
if let Some(stdout) = stdout {
|
||||
let sender = sender.clone();
|
||||
|
||||
let mut reader = tokio::io::BufReader::new(stdout).lines();
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Ok(Some(line)) = reader.next_line().await {
|
||||
let response: Result<_, Status> = Ok(SpawnResponse {
|
||||
stdout: Some(line),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// TODO: handle error
|
||||
let _ = sender.send(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(stderr) = stderr {
|
||||
let sender = sender.clone();
|
||||
|
||||
let mut reader = tokio::io::BufReader::new(stderr).lines();
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Ok(Some(line)) = reader.next_line().await {
|
||||
let response: Result<_, Status> = Ok(SpawnResponse {
|
||||
stderr: Some(line),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// TODO: handle error
|
||||
let _ = sender.send(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
match child.wait().await {
|
||||
Ok(exit_status) => {
|
||||
let response = Ok(SpawnResponse {
|
||||
exit_code: exit_status.code(),
|
||||
exit_message: Some(exit_status.to_string()),
|
||||
..Default::default()
|
||||
});
|
||||
// TODO: handle error
|
||||
let _ = sender.send(response);
|
||||
}
|
||||
Err(err) => tracing::warn!("child wait() err: {err}"),
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
self.sender
|
||||
.send(f)
|
||||
.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)))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TagService {
|
||||
pub sender: StateFnSender,
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl pinnacle::tag::v0alpha1::tag_service_server::TagService for TagService {
|
||||
async fn set_active(&self, request: Request<SetActiveRequest>) -> Result<Response<()>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
let tag_id = TagId::Some(
|
||||
request
|
||||
.tag_id
|
||||
.ok_or_else(|| Status::invalid_argument("no tag specified"))?,
|
||||
);
|
||||
|
||||
let set_or_toggle = match request.set_or_toggle {
|
||||
Some(pinnacle::tag::v0alpha1::set_active_request::SetOrToggle::Set(set)) => Some(set),
|
||||
Some(pinnacle::tag::v0alpha1::set_active_request::SetOrToggle::Toggle(_)) => None,
|
||||
None => return Err(Status::invalid_argument("unspecified set or toggle")),
|
||||
};
|
||||
|
||||
let f = Box::new(move |state: &mut State| {
|
||||
let Some(tag) = tag_id.tag(state) else {
|
||||
return;
|
||||
};
|
||||
match set_or_toggle {
|
||||
Some(set) => tag.set_active(set),
|
||||
None => tag.set_active(!tag.active()),
|
||||
}
|
||||
|
||||
let Some(output) = tag.output(state) else {
|
||||
return;
|
||||
};
|
||||
|
||||
state.update_windows(&output);
|
||||
state.update_focus(&output);
|
||||
state.schedule_render(&output);
|
||||
});
|
||||
|
||||
self.sender
|
||||
.send(f)
|
||||
.map_err(|_| Status::internal("internal state was not running"))?;
|
||||
|
||||
Ok(Response::new(()))
|
||||
}
|
||||
|
||||
async fn switch_to(&self, request: Request<SwitchToRequest>) -> Result<Response<()>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
let tag_id = TagId::Some(
|
||||
request
|
||||
.tag_id
|
||||
.ok_or_else(|| Status::invalid_argument("no tag specified"))?,
|
||||
);
|
||||
|
||||
let f = Box::new(move |state: &mut State| {
|
||||
let Some(tag) = tag_id.tag(state) else { return };
|
||||
let Some(output) = tag.output(state) else { return };
|
||||
|
||||
output.with_state(|state| {
|
||||
for op_tag in state.tags.iter_mut() {
|
||||
op_tag.set_active(false);
|
||||
}
|
||||
tag.set_active(true);
|
||||
});
|
||||
|
||||
state.update_windows(&output);
|
||||
state.update_focus(&output);
|
||||
state.schedule_render(&output);
|
||||
});
|
||||
|
||||
self.sender
|
||||
.send(f)
|
||||
.map_err(|_| Status::internal("internal state was not running"))?;
|
||||
|
||||
Ok(Response::new(()))
|
||||
}
|
||||
|
||||
async fn add(&self, request: Request<AddRequest>) -> Result<Response<AddResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
let output_name = OutputName(
|
||||
request
|
||||
.output_name
|
||||
.ok_or_else(|| Status::invalid_argument("no output specified"))?,
|
||||
);
|
||||
|
||||
let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel::<Vec<u32>>();
|
||||
|
||||
let f = Box::new(move |state: &mut State| {
|
||||
let new_tags = request
|
||||
.tag_names
|
||||
.into_iter()
|
||||
.map(Tag::new)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let tag_ids = new_tags
|
||||
.iter()
|
||||
.map(|tag| tag.id())
|
||||
.map(|id| match id {
|
||||
TagId::None => unreachable!(),
|
||||
TagId::Some(id) => id,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let _ = sender.send(tag_ids);
|
||||
|
||||
if let Some(saved_state) = state.config.connector_saved_states.get_mut(&output_name) {
|
||||
let mut tags = saved_state.tags.clone();
|
||||
tags.extend(new_tags.clone());
|
||||
saved_state.tags = tags;
|
||||
} else {
|
||||
state.config.connector_saved_states.insert(
|
||||
output_name.clone(),
|
||||
crate::config::ConnectorSavedState {
|
||||
tags: new_tags.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let Some(output) = state
|
||||
.space
|
||||
.outputs()
|
||||
.find(|output| output.name() == output_name.0)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
output.with_state(|state| {
|
||||
state.tags.extend(new_tags.clone());
|
||||
tracing::debug!("tags added, are now {:?}", state.tags);
|
||||
});
|
||||
|
||||
for tag in new_tags {
|
||||
for window in state.windows.iter() {
|
||||
window.with_state(|state| {
|
||||
for win_tag in state.tags.iter_mut() {
|
||||
if win_tag.id() == tag.id() {
|
||||
*win_tag = tag.clone();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.sender
|
||||
.send(f)
|
||||
.map_err(|_| Status::internal("internal state was not running"))?;
|
||||
|
||||
let ids = receiver
|
||||
.recv()
|
||||
.await
|
||||
.ok_or_else(|| Status::internal("internal state was not running"))?;
|
||||
|
||||
Ok(Response::new(AddResponse { tag_ids: ids }))
|
||||
}
|
||||
|
||||
async fn remove(&self, request: Request<RemoveRequest>) -> Result<Response<()>, Status> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn set_layout(&self, request: Request<SetLayoutRequest>) -> Result<Response<()>, Status> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn get(
|
||||
&self,
|
||||
request: Request<pinnacle::tag::v0alpha1::GetRequest>,
|
||||
) -> Result<Response<pinnacle::tag::v0alpha1::GetResponse>, Status> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn get_properties(
|
||||
&self,
|
||||
request: Request<pinnacle::tag::v0alpha1::GetPropertiesRequest>,
|
||||
) -> Result<Response<pinnacle::tag::v0alpha1::GetPropertiesResponse>, Status> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
29
src/input.rs
29
src/input.rs
|
@ -2,7 +2,7 @@
|
|||
|
||||
pub mod libinput;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::{collections::HashMap, mem::Discriminant};
|
||||
|
||||
use crate::{
|
||||
api::msg::{CallbackId, Modifier, MouseEdge, OutgoingMsg},
|
||||
|
@ -10,7 +10,10 @@ use crate::{
|
|||
state::WithState,
|
||||
window::WindowElement,
|
||||
};
|
||||
use pinnacle_api_defs::pinnacle::input::v0alpha1::SetKeybindResponse;
|
||||
use pinnacle_api_defs::pinnacle::input::{
|
||||
libinput::v0alpha1::set_libinput_setting_request::Setting,
|
||||
v0alpha1::{SetKeybindResponse, SetMousebindResponse},
|
||||
};
|
||||
use smithay::{
|
||||
backend::input::{
|
||||
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputBackend, InputEvent,
|
||||
|
@ -42,7 +45,7 @@ bitflags::bitflags! {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Default)]
|
||||
pub struct InputState {
|
||||
/// A hashmap of modifier keys and keycodes to callback IDs
|
||||
pub keybinds: HashMap<(crate::api::msg::ModifierMask, Keysym), CallbackId>,
|
||||
|
@ -57,6 +60,25 @@ pub struct InputState {
|
|||
|
||||
pub grpc_keybinds:
|
||||
HashMap<(ModifierMask, Keysym), UnboundedSender<Result<SetKeybindResponse, tonic::Status>>>,
|
||||
pub grpc_mousebinds:
|
||||
HashMap<(ModifierMask, u32), UnboundedSender<Result<SetMousebindResponse, tonic::Status>>>,
|
||||
pub grpc_libinput_settings:
|
||||
HashMap<Discriminant<Setting>, Box<dyn Fn(&mut input::Device) + Send>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for InputState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("InputState")
|
||||
.field("keybinds", &self.keybinds)
|
||||
.field("mousebinds", &self.mousebinds)
|
||||
.field("reload_keybind", &self.reload_keybind)
|
||||
.field("kill_keybind", &self.kill_keybind)
|
||||
.field("libinput_settings", &self.libinput_settings)
|
||||
.field("libinput_devices", &self.libinput_devices)
|
||||
.field("grpc_keybinds", &self.grpc_keybinds)
|
||||
.field("grpc_libinput_settings", &"...")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl InputState {
|
||||
|
@ -227,6 +249,7 @@ impl State {
|
|||
let raw_sym = keysym.raw_syms().iter().next();
|
||||
let mod_sym = keysym.modified_sym();
|
||||
|
||||
// TODO: check both modsym and rawsym
|
||||
if state.input_state.grpc_keybinds.get(&(grpc_modifiers, keysym.modified_sym())).is_some() {
|
||||
return FilterResult::Intercept(KeyAction::CallGrpcCallback(grpc_modifiers, keysym.modified_sym()));
|
||||
}
|
||||
|
|
|
@ -120,6 +120,9 @@ impl State {
|
|||
for setting in self.input_state.libinput_settings.iter() {
|
||||
setting.apply_to_device(&mut device);
|
||||
}
|
||||
for setting in self.input_state.grpc_libinput_settings.values() {
|
||||
setting(&mut device);
|
||||
}
|
||||
|
||||
self.input_state.libinput_devices.push(device);
|
||||
}
|
||||
|
|
16
src/state.rs
16
src/state.rs
|
@ -7,7 +7,7 @@ use crate::{
|
|||
msg::Msg,
|
||||
protocol::{
|
||||
pinnacle::v0alpha1::pinnacle_service_server::PinnacleServiceServer, InputService,
|
||||
PinnacleService,
|
||||
PinnacleService, ProcessService, TagService,
|
||||
},
|
||||
ApiState,
|
||||
},
|
||||
|
@ -18,7 +18,11 @@ use crate::{
|
|||
grab::resize_grab::ResizeSurfaceState,
|
||||
window::WindowElement,
|
||||
};
|
||||
use pinnacle_api_defs::pinnacle::input::v0alpha1::input_service_server::InputServiceServer;
|
||||
use pinnacle_api_defs::pinnacle::{
|
||||
input::v0alpha1::input_service_server::InputServiceServer,
|
||||
process::v0alpha1::process_service_server::ProcessServiceServer,
|
||||
tag::v0alpha1::tag_service_server::TagServiceServer,
|
||||
};
|
||||
use smithay::{
|
||||
desktop::{PopupManager, Space},
|
||||
input::{keyboard::XkbConfig, pointer::CursorImageStatus, Seat, SeatState},
|
||||
|
@ -254,6 +258,12 @@ impl State {
|
|||
let input_service = InputService {
|
||||
sender: grpc_sender.clone(),
|
||||
};
|
||||
let process_service = ProcessService {
|
||||
sender: grpc_sender.clone(),
|
||||
};
|
||||
let tag_service = TagService {
|
||||
sender: grpc_sender.clone(),
|
||||
};
|
||||
|
||||
let refl_service = tonic_reflection::server::Builder::configure()
|
||||
.register_encoded_file_descriptor_set(crate::api::protocol::FILE_DESCRIPTOR_SET)
|
||||
|
@ -266,6 +276,8 @@ impl State {
|
|||
.add_service(refl_service)
|
||||
.add_service(PinnacleServiceServer::new(pinnacle_service))
|
||||
.add_service(InputServiceServer::new(input_service))
|
||||
.add_service(ProcessServiceServer::new(process_service))
|
||||
.add_service(TagServiceServer::new(tag_service))
|
||||
.serve(addr)
|
||||
.await
|
||||
.expect("failed to serve grpc");
|
||||
|
|
Loading…
Add table
Reference in a new issue