2024-02-22 01:08:23 +01:00
|
|
|
pub mod signal;
|
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
use std::{ffi::OsString, num::NonZeroU32, pin::Pin, process::Stdio};
|
2023-06-26 00:18:50 +02:00
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
use pinnacle_api_defs::pinnacle::{
|
|
|
|
input::v0alpha1::{
|
2024-02-19 06:05:37 +01:00
|
|
|
input_service_server,
|
2024-01-22 06:42:48 +01:00
|
|
|
set_libinput_setting_request::{AccelProfile, ClickMethod, ScrollMethod, TapButtonMap},
|
|
|
|
set_mousebind_request::MouseEdge,
|
|
|
|
SetKeybindRequest, SetKeybindResponse, SetLibinputSettingRequest, SetMousebindRequest,
|
|
|
|
SetMousebindResponse, SetRepeatRateRequest, SetXkbConfigRequest,
|
|
|
|
},
|
2024-02-19 06:05:37 +01:00
|
|
|
output::{
|
|
|
|
self,
|
2024-02-22 06:30:28 +01:00
|
|
|
v0alpha1::{output_service_server, SetLocationRequest},
|
2024-01-22 06:42:48 +01:00
|
|
|
},
|
2024-02-19 06:05:37 +01:00
|
|
|
process::v0alpha1::{process_service_server, SetEnvRequest, SpawnRequest, SpawnResponse},
|
|
|
|
tag::{
|
|
|
|
self,
|
|
|
|
v0alpha1::{
|
|
|
|
tag_service_server, AddRequest, AddResponse, RemoveRequest, SetActiveRequest,
|
|
|
|
SetLayoutRequest, SwitchToRequest,
|
|
|
|
},
|
|
|
|
},
|
2024-02-24 21:41:52 +01:00
|
|
|
v0alpha1::{pinnacle_service_server, Geometry, QuitRequest, SetOrToggle},
|
2024-02-19 06:05:37 +01:00
|
|
|
window::{
|
|
|
|
self,
|
|
|
|
v0alpha1::{
|
|
|
|
window_service_server, AddWindowRuleRequest, CloseRequest, FullscreenOrMaximized,
|
|
|
|
MoveGrabRequest, MoveToTagRequest, ResizeGrabRequest, SetFloatingRequest,
|
2024-02-24 21:41:52 +01:00
|
|
|
SetFocusedRequest, SetFullscreenRequest, SetGeometryRequest, SetMaximizedRequest,
|
|
|
|
SetTagRequest, WindowRule, WindowRuleCondition,
|
2024-02-19 06:05:37 +01:00
|
|
|
},
|
2024-01-22 06:42:48 +01:00
|
|
|
},
|
|
|
|
};
|
|
|
|
use smithay::{
|
2024-03-01 03:10:23 +01:00
|
|
|
desktop::{space::SpaceElement, WindowSurface},
|
2024-01-22 06:42:48 +01:00
|
|
|
input::keyboard::XkbConfig,
|
|
|
|
reexports::{calloop, input as libinput, wayland_protocols::xdg::shell::server},
|
|
|
|
utils::{Point, Rectangle, SERIAL_COUNTER},
|
2024-03-01 03:10:23 +01:00
|
|
|
wayland::seat::WaylandFocus,
|
2023-06-18 01:55:04 +02:00
|
|
|
};
|
2024-01-22 06:42:48 +01:00
|
|
|
use sysinfo::ProcessRefreshKind;
|
2024-02-19 06:05:37 +01:00
|
|
|
use tokio::{
|
|
|
|
io::AsyncBufReadExt,
|
|
|
|
sync::mpsc::{unbounded_channel, UnboundedSender},
|
2024-02-22 02:40:11 +01:00
|
|
|
task::JoinHandle,
|
2024-02-19 06:05:37 +01:00
|
|
|
};
|
2024-02-22 01:08:23 +01:00
|
|
|
use tokio_stream::{Stream, StreamExt};
|
|
|
|
use tonic::{Request, Response, Status, Streaming};
|
2024-03-01 03:10:23 +01:00
|
|
|
use tracing::{error, warn};
|
2023-06-18 01:55:04 +02:00
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
use crate::{
|
|
|
|
config::ConnectorSavedState,
|
2024-03-01 07:07:01 +01:00
|
|
|
focus::keyboard::KeyboardFocusTarget,
|
2024-01-22 06:42:48 +01:00
|
|
|
input::ModifierMask,
|
|
|
|
output::OutputName,
|
|
|
|
state::{State, WithState},
|
|
|
|
tag::{Tag, TagId},
|
2024-03-01 03:10:23 +01:00
|
|
|
window::window_state::WindowId,
|
2023-06-18 01:55:04 +02:00
|
|
|
};
|
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
type ResponseStream<T> = Pin<Box<dyn Stream<Item = Result<T, Status>> + Send>>;
|
|
|
|
pub type StateFnSender = calloop::channel::Sender<Box<dyn FnOnce(&mut State) + Send>>;
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
async fn run_unary_no_response<F>(
|
|
|
|
fn_sender: &StateFnSender,
|
|
|
|
with_state: F,
|
|
|
|
) -> Result<Response<()>, Status>
|
|
|
|
where
|
|
|
|
F: FnOnce(&mut State) + Send + 'static,
|
|
|
|
{
|
|
|
|
fn_sender
|
|
|
|
.send(Box::new(with_state))
|
|
|
|
.map_err(|_| Status::internal("failed to execute request"))?;
|
|
|
|
|
|
|
|
Ok(Response::new(()))
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn run_unary<F, T>(fn_sender: &StateFnSender, with_state: F) -> Result<Response<T>, Status>
|
|
|
|
where
|
|
|
|
F: FnOnce(&mut State) -> T + Send + 'static,
|
|
|
|
T: Send + 'static,
|
|
|
|
{
|
|
|
|
let (sender, receiver) = tokio::sync::oneshot::channel::<T>();
|
|
|
|
|
|
|
|
let f = Box::new(|state: &mut State| {
|
|
|
|
// TODO: find a way to handle this error
|
|
|
|
if sender.send(with_state(state)).is_err() {
|
|
|
|
panic!("failed to send result to config");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
fn_sender
|
|
|
|
.send(f)
|
|
|
|
.map_err(|_| Status::internal("failed to execute request"))?;
|
|
|
|
|
|
|
|
receiver.await.map(Response::new).map_err(|err| {
|
|
|
|
Status::internal(format!(
|
|
|
|
"failed to transfer response for transport to client: {err}"
|
|
|
|
))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run_server_streaming<F, T>(
|
|
|
|
fn_sender: &StateFnSender,
|
|
|
|
with_state: F,
|
|
|
|
) -> Result<Response<ResponseStream<T>>, Status>
|
|
|
|
where
|
|
|
|
F: FnOnce(&mut State, UnboundedSender<Result<T, Status>>) + Send + 'static,
|
|
|
|
T: Send + 'static,
|
|
|
|
{
|
|
|
|
let (sender, receiver) = unbounded_channel::<Result<T, Status>>();
|
|
|
|
|
|
|
|
let f = Box::new(|state: &mut State| {
|
|
|
|
with_state(state, sender);
|
|
|
|
});
|
|
|
|
|
|
|
|
fn_sender
|
|
|
|
.send(f)
|
|
|
|
.map_err(|_| Status::internal("failed to execute request"))?;
|
|
|
|
|
|
|
|
let receiver_stream = tokio_stream::wrappers::UnboundedReceiverStream::new(receiver);
|
|
|
|
Ok(Response::new(Box::pin(receiver_stream)))
|
|
|
|
}
|
|
|
|
|
2024-02-22 01:08:23 +01:00
|
|
|
fn run_bidirectional_streaming<F1, F2, I, O>(
|
|
|
|
fn_sender: StateFnSender,
|
|
|
|
mut in_stream: Streaming<I>,
|
2024-02-22 05:48:09 +01:00
|
|
|
with_client_request: F1,
|
2024-02-22 01:08:23 +01:00
|
|
|
with_out_stream: F2,
|
|
|
|
) -> Result<Response<ResponseStream<O>>, Status>
|
|
|
|
where
|
|
|
|
F1: Fn(&mut State, Result<I, Status>) + Clone + Send + 'static,
|
2024-02-22 02:40:11 +01:00
|
|
|
F2: FnOnce(&mut State, UnboundedSender<Result<O, Status>>, JoinHandle<()>) + Send + 'static,
|
2024-02-22 01:08:23 +01:00
|
|
|
I: Send + 'static,
|
|
|
|
O: Send + 'static,
|
|
|
|
{
|
|
|
|
let (sender, receiver) = unbounded_channel::<Result<O, Status>>();
|
|
|
|
|
2024-02-22 02:40:11 +01:00
|
|
|
let fn_sender_clone = fn_sender.clone();
|
2024-02-22 01:08:23 +01:00
|
|
|
|
|
|
|
let with_in_stream = async move {
|
2024-02-22 05:48:09 +01:00
|
|
|
while let Some(request) = in_stream.next().await {
|
|
|
|
let with_client_request = with_client_request.clone();
|
2024-02-22 01:08:23 +01:00
|
|
|
// TODO: handle error
|
2024-02-22 02:40:11 +01:00
|
|
|
let _ = fn_sender_clone.send(Box::new(move |state: &mut State| {
|
2024-02-22 05:48:09 +01:00
|
|
|
with_client_request(state, request);
|
2024-02-22 01:08:23 +01:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-02-22 02:40:11 +01:00
|
|
|
let join_handle = tokio::spawn(with_in_stream);
|
|
|
|
|
|
|
|
let with_out_stream = Box::new(|state: &mut State| {
|
|
|
|
with_out_stream(state, sender, join_handle);
|
|
|
|
});
|
|
|
|
|
|
|
|
fn_sender
|
|
|
|
.send(with_out_stream)
|
|
|
|
.map_err(|_| Status::internal("failed to execute request"))?;
|
2024-02-22 01:08:23 +01:00
|
|
|
|
|
|
|
let receiver_stream = tokio_stream::wrappers::UnboundedReceiverStream::new(receiver);
|
|
|
|
Ok(Response::new(Box::pin(receiver_stream)))
|
|
|
|
}
|
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
pub struct PinnacleService {
|
2024-02-19 06:05:37 +01:00
|
|
|
sender: StateFnSender,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PinnacleService {
|
|
|
|
pub fn new(sender: StateFnSender) -> Self {
|
|
|
|
Self { sender }
|
|
|
|
}
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[tonic::async_trait]
|
2024-02-19 06:05:37 +01:00
|
|
|
impl pinnacle_service_server::PinnacleService for PinnacleService {
|
2024-01-22 06:42:48 +01:00
|
|
|
async fn quit(&self, _request: Request<QuitRequest>) -> Result<Response<()>, Status> {
|
|
|
|
tracing::trace!("PinnacleService.quit");
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary_no_response(&self.sender, |state| {
|
|
|
|
state.shutdown();
|
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct InputService {
|
2024-02-19 06:05:37 +01:00
|
|
|
sender: StateFnSender,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl InputService {
|
|
|
|
pub fn new(sender: StateFnSender) -> Self {
|
|
|
|
Self { sender }
|
|
|
|
}
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[tonic::async_trait]
|
2024-02-19 06:05:37 +01:00
|
|
|
impl input_service_server::InputService for InputService {
|
2024-01-22 06:42:48 +01:00
|
|
|
type SetKeybindStream = ResponseStream<SetKeybindResponse>;
|
|
|
|
type SetMousebindStream = ResponseStream<SetMousebindResponse>;
|
|
|
|
|
|
|
|
async fn set_keybind(
|
|
|
|
&self,
|
|
|
|
request: Request<SetKeybindRequest>,
|
|
|
|
) -> Result<Response<Self::SetKeybindStream>, Status> {
|
|
|
|
let request = request.into_inner();
|
|
|
|
|
|
|
|
// TODO: impl From<&[Modifier]> for ModifierMask
|
|
|
|
let modifiers = request
|
|
|
|
.modifiers()
|
|
|
|
.fold(ModifierMask::empty(), |acc, modifier| match modifier {
|
|
|
|
pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Unspecified => acc,
|
|
|
|
pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Shift => {
|
|
|
|
acc | ModifierMask::SHIFT
|
|
|
|
}
|
|
|
|
pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Ctrl => {
|
|
|
|
acc | ModifierMask::CTRL
|
|
|
|
}
|
|
|
|
pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Alt => {
|
|
|
|
acc | ModifierMask::ALT
|
|
|
|
}
|
|
|
|
pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Super => {
|
|
|
|
acc | ModifierMask::SUPER
|
|
|
|
}
|
|
|
|
});
|
|
|
|
let key = request
|
|
|
|
.key
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no key specified"))?;
|
|
|
|
|
|
|
|
use pinnacle_api_defs::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
|
|
|
|
}
|
2023-06-18 01:55:04 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_server_streaming(&self.sender, move |state, sender| {
|
|
|
|
state
|
|
|
|
.input_state
|
|
|
|
.keybinds
|
|
|
|
.insert((modifiers, keysym), sender);
|
|
|
|
})
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn set_mousebind(
|
|
|
|
&self,
|
|
|
|
request: Request<SetMousebindRequest>,
|
|
|
|
) -> Result<Response<Self::SetMousebindStream>, Status> {
|
|
|
|
let request = request.into_inner();
|
|
|
|
|
|
|
|
tracing::debug!(request = ?request);
|
|
|
|
|
|
|
|
let modifiers = request
|
|
|
|
.modifiers()
|
|
|
|
.fold(ModifierMask::empty(), |acc, modifier| match modifier {
|
|
|
|
pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Unspecified => acc,
|
|
|
|
pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Shift => {
|
|
|
|
acc | ModifierMask::SHIFT
|
|
|
|
}
|
|
|
|
pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Ctrl => {
|
|
|
|
acc | ModifierMask::CTRL
|
|
|
|
}
|
|
|
|
pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Alt => {
|
|
|
|
acc | ModifierMask::ALT
|
|
|
|
}
|
|
|
|
pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Super => {
|
|
|
|
acc | ModifierMask::SUPER
|
|
|
|
}
|
|
|
|
});
|
|
|
|
let button = request
|
|
|
|
.button
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no key specified"))?;
|
|
|
|
|
|
|
|
let edge = request.edge();
|
|
|
|
|
|
|
|
if let MouseEdge::Unspecified = edge {
|
|
|
|
return Err(Status::invalid_argument("press or release not specified"));
|
|
|
|
}
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_server_streaming(&self.sender, move |state, sender| {
|
|
|
|
state
|
|
|
|
.input_state
|
|
|
|
.mousebinds
|
|
|
|
.insert((modifiers, button, edge), sender);
|
|
|
|
})
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn set_xkb_config(
|
|
|
|
&self,
|
|
|
|
request: Request<SetXkbConfigRequest>,
|
|
|
|
) -> Result<Response<()>, Status> {
|
|
|
|
let request = request.into_inner();
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary_no_response(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
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) {
|
2024-03-01 03:10:23 +01:00
|
|
|
error!("Failed to set xkbconfig: {err}");
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
}
|
2024-02-19 06:05:37 +01:00
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn set_repeat_rate(
|
|
|
|
&self,
|
|
|
|
request: Request<SetRepeatRateRequest>,
|
|
|
|
) -> Result<Response<()>, Status> {
|
|
|
|
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"))?;
|
2023-06-18 01:55:04 +02:00
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary_no_response(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
if let Some(kb) = state.seat.get_keyboard() {
|
|
|
|
kb.change_repeat_info(rate, delay);
|
2023-06-18 01:55:04 +02:00
|
|
|
}
|
2024-02-19 06:05:37 +01:00
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn set_libinput_setting(
|
|
|
|
&self,
|
|
|
|
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_api_defs::pinnacle::input::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);
|
|
|
|
}),
|
2023-06-18 01:55:04 +02:00
|
|
|
};
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary_no_response(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
for device in state.input_state.libinput_devices.iter_mut() {
|
|
|
|
apply_setting(device);
|
|
|
|
}
|
|
|
|
|
|
|
|
state
|
|
|
|
.input_state
|
|
|
|
.libinput_settings
|
|
|
|
.insert(discriminant, apply_setting);
|
2024-02-19 06:05:37 +01:00
|
|
|
})
|
|
|
|
.await
|
2023-06-18 01:55:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
pub struct ProcessService {
|
2024-02-19 06:05:37 +01:00
|
|
|
sender: StateFnSender,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ProcessService {
|
|
|
|
pub fn new(sender: StateFnSender) -> Self {
|
|
|
|
Self { sender }
|
|
|
|
}
|
2023-06-18 01:55:04 +02:00
|
|
|
}
|
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
#[tonic::async_trait]
|
2024-02-19 06:05:37 +01:00
|
|
|
impl process_service_server::ProcessService for ProcessService {
|
2024-01-22 06:42:48 +01:00
|
|
|
type SpawnStream = ResponseStream<SpawnResponse>;
|
|
|
|
|
|
|
|
async fn spawn(
|
|
|
|
&self,
|
|
|
|
request: Request<SpawnRequest>,
|
|
|
|
) -> Result<Response<Self::SpawnStream>, Status> {
|
|
|
|
tracing::debug!("ProcessService.spawn");
|
|
|
|
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"))?;
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_server_streaming(&self.sender, move |state, sender| {
|
2024-01-22 06:42:48 +01:00
|
|
|
if once {
|
|
|
|
state
|
|
|
|
.system_processes
|
|
|
|
.refresh_processes_specifics(ProcessRefreshKind::new());
|
2023-08-07 02:41:48 +02:00
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
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)
|
|
|
|
});
|
2023-09-11 10:58:43 +02:00
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
if already_running {
|
|
|
|
return;
|
2023-09-11 10:58:43 +02:00
|
|
|
}
|
|
|
|
}
|
2024-01-22 06:42:48 +01:00
|
|
|
|
|
|
|
let Ok(mut child) = tokio::process::Command::new(OsString::from(arg0.clone()))
|
2024-02-18 06:57:02 +01:00
|
|
|
.envs(state.xdisplay.map(|xdisp| ("DISPLAY", format!(":{xdisp}"))))
|
2024-01-22 06:42:48 +01:00
|
|
|
.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 {
|
2024-03-01 03:10:23 +01:00
|
|
|
warn!("Tried to run {arg0}, but it doesn't exist",);
|
2024-01-22 06:42:48 +01:00
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
|
|
|
if !has_callback {
|
|
|
|
return;
|
2023-06-18 01:55:04 +02:00
|
|
|
}
|
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
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()
|
|
|
|
});
|
2023-08-16 18:28:35 +02:00
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
// TODO: handle error
|
|
|
|
match sender.send(response) {
|
|
|
|
Ok(_) => (),
|
|
|
|
Err(err) => {
|
2024-03-01 03:10:23 +01:00
|
|
|
error!(err = ?err);
|
2024-01-22 06:42:48 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
match sender.send(response) {
|
|
|
|
Ok(_) => (),
|
|
|
|
Err(err) => {
|
2024-03-01 03:10:23 +01:00
|
|
|
error!(err = ?err);
|
2024-01-22 06:42:48 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2024-03-01 03:10:23 +01:00
|
|
|
Err(err) => warn!("child wait() err: {err}"),
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
});
|
2024-02-19 06:05:37 +01:00
|
|
|
})
|
2023-06-18 01:55:04 +02:00
|
|
|
}
|
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
async fn set_env(&self, request: Request<SetEnvRequest>) -> Result<Response<()>, Status> {
|
|
|
|
let request = request.into_inner();
|
|
|
|
|
|
|
|
let key = request
|
|
|
|
.key
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no key specified"))?;
|
|
|
|
let value = request
|
|
|
|
.value
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no value specified"))?;
|
|
|
|
|
|
|
|
if key.is_empty() {
|
|
|
|
return Err(Status::invalid_argument("key was empty"));
|
|
|
|
}
|
|
|
|
|
|
|
|
if key.contains(['\0', '=']) {
|
|
|
|
return Err(Status::invalid_argument("key contained NUL or ="));
|
2023-06-18 01:55:04 +02:00
|
|
|
}
|
2023-12-17 04:20:29 +01:00
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
if value.contains('\0') {
|
|
|
|
return Err(Status::invalid_argument("value contained NUL"));
|
2023-06-18 01:55:04 +02:00
|
|
|
}
|
2024-01-22 06:42:48 +01:00
|
|
|
|
|
|
|
std::env::set_var(key, value);
|
|
|
|
|
|
|
|
Ok(Response::new(()))
|
2023-12-17 04:20:29 +01:00
|
|
|
}
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
2023-12-17 04:20:29 +01:00
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
pub struct TagService {
|
2024-02-19 06:05:37 +01:00
|
|
|
sender: StateFnSender,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TagService {
|
|
|
|
pub fn new(sender: StateFnSender) -> Self {
|
|
|
|
Self { sender }
|
|
|
|
}
|
2023-06-18 01:55:04 +02:00
|
|
|
}
|
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
#[tonic::async_trait]
|
2024-02-19 06:05:37 +01:00
|
|
|
impl tag_service_server::TagService for TagService {
|
2024-01-22 06:42:48 +01:00
|
|
|
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"))?,
|
|
|
|
);
|
|
|
|
|
2024-02-24 21:41:52 +01:00
|
|
|
let set_or_toggle = request.set_or_toggle();
|
|
|
|
|
|
|
|
if set_or_toggle == SetOrToggle::Unspecified {
|
|
|
|
return Err(Status::invalid_argument("unspecified set or toggle"));
|
|
|
|
}
|
2023-06-18 01:55:04 +02:00
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary_no_response(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
let Some(tag) = tag_id.tag(state) else {
|
|
|
|
return;
|
|
|
|
};
|
2024-02-24 21:41:52 +01:00
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
match set_or_toggle {
|
2024-02-24 21:41:52 +01:00
|
|
|
SetOrToggle::Set => tag.set_active(true),
|
|
|
|
SetOrToggle::Unset => tag.set_active(false),
|
|
|
|
SetOrToggle::Toggle => tag.set_active(!tag.active()),
|
|
|
|
SetOrToggle::Unspecified => unreachable!(),
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
2023-06-18 01:55:04 +02:00
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
let Some(output) = tag.output(state) else {
|
|
|
|
return;
|
|
|
|
};
|
2023-06-18 01:55:04 +02:00
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
state.update_windows(&output);
|
|
|
|
state.update_focus(&output);
|
|
|
|
state.schedule_render(&output);
|
2024-02-19 06:05:37 +01:00
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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"))?,
|
|
|
|
);
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary_no_response(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
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);
|
2024-02-22 02:40:11 +01:00
|
|
|
|
2024-02-22 05:48:09 +01:00
|
|
|
state.signal_state.layout.signal(|buffer| {
|
|
|
|
buffer.push_back(
|
|
|
|
pinnacle_api_defs::pinnacle::signal::v0alpha1::LayoutResponse {
|
|
|
|
window_ids: vec![1, 2, 3],
|
|
|
|
tag_id: Some(1),
|
|
|
|
},
|
|
|
|
);
|
2024-02-22 02:40:11 +01:00
|
|
|
});
|
2024-02-19 06:05:37 +01:00
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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"))?,
|
|
|
|
);
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
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<_>>();
|
|
|
|
|
|
|
|
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()
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
if let Some(output) = state
|
2024-01-22 06:42:48 +01:00
|
|
|
.space
|
|
|
|
.outputs()
|
|
|
|
.find(|output| output.name() == output_name.0)
|
2024-02-19 06:05:37 +01:00
|
|
|
{
|
|
|
|
output.with_state(|state| {
|
|
|
|
state.tags.extend(new_tags.clone());
|
|
|
|
tracing::debug!("tags added, are now {:?}", state.tags);
|
|
|
|
});
|
|
|
|
}
|
2024-01-22 06:42:48 +01:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
2023-06-30 00:41:08 +02:00
|
|
|
}
|
2023-06-18 01:55:04 +02:00
|
|
|
});
|
|
|
|
}
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
2023-06-18 01:55:04 +02:00
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
AddResponse { tag_ids }
|
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: test
|
|
|
|
async fn remove(&self, request: Request<RemoveRequest>) -> Result<Response<()>, Status> {
|
|
|
|
let request = request.into_inner();
|
|
|
|
|
|
|
|
let tag_ids = request.tag_ids.into_iter().map(TagId::Some);
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary_no_response(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
let tags_to_remove = tag_ids.flat_map(|id| id.tag(state)).collect::<Vec<_>>();
|
|
|
|
|
|
|
|
for output in state.space.outputs().cloned().collect::<Vec<_>>() {
|
|
|
|
// TODO: seriously, convert state.tags into a hashset
|
|
|
|
output.with_state(|state| {
|
|
|
|
for tag_to_remove in tags_to_remove.iter() {
|
|
|
|
state.tags.retain(|tag| tag != tag_to_remove);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
state.update_windows(&output);
|
|
|
|
state.schedule_render(&output);
|
|
|
|
}
|
|
|
|
|
|
|
|
for conn_saved_state in state.config.connector_saved_states.values_mut() {
|
|
|
|
for tag_to_remove in tags_to_remove.iter() {
|
|
|
|
conn_saved_state.tags.retain(|tag| tag != tag_to_remove);
|
|
|
|
}
|
|
|
|
}
|
2024-02-19 06:05:37 +01:00
|
|
|
})
|
|
|
|
.await
|
2023-06-18 01:55:04 +02:00
|
|
|
}
|
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
async fn set_layout(&self, request: Request<SetLayoutRequest>) -> 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"))?,
|
|
|
|
);
|
|
|
|
|
|
|
|
use pinnacle_api_defs::pinnacle::tag::v0alpha1::set_layout_request::Layout;
|
|
|
|
|
|
|
|
// TODO: from impl
|
|
|
|
let layout = match request.layout() {
|
|
|
|
Layout::Unspecified => return Err(Status::invalid_argument("unspecified layout")),
|
|
|
|
Layout::MasterStack => crate::layout::Layout::MasterStack,
|
|
|
|
Layout::Dwindle => crate::layout::Layout::Dwindle,
|
|
|
|
Layout::Spiral => crate::layout::Layout::Spiral,
|
|
|
|
Layout::CornerTopLeft => crate::layout::Layout::CornerTopLeft,
|
|
|
|
Layout::CornerTopRight => crate::layout::Layout::CornerTopRight,
|
|
|
|
Layout::CornerBottomLeft => crate::layout::Layout::CornerBottomLeft,
|
|
|
|
Layout::CornerBottomRight => crate::layout::Layout::CornerBottomRight,
|
|
|
|
};
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary_no_response(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
let Some(tag) = tag_id.tag(state) else { return };
|
|
|
|
|
|
|
|
tag.set_layout(layout);
|
|
|
|
|
|
|
|
let Some(output) = tag.output(state) else { return };
|
|
|
|
|
|
|
|
state.update_windows(&output);
|
|
|
|
state.schedule_render(&output);
|
2024-02-19 06:05:37 +01:00
|
|
|
})
|
|
|
|
.await
|
2023-06-18 01:55:04 +02:00
|
|
|
}
|
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
async fn get(
|
|
|
|
&self,
|
2024-02-19 06:05:37 +01:00
|
|
|
_request: Request<tag::v0alpha1::GetRequest>,
|
|
|
|
) -> Result<Response<tag::v0alpha1::GetResponse>, Status> {
|
|
|
|
run_unary(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
let tag_ids = state
|
|
|
|
.space
|
|
|
|
.outputs()
|
|
|
|
.flat_map(|op| op.with_state(|state| state.tags.clone()))
|
|
|
|
.map(|tag| tag.id())
|
|
|
|
.map(|id| match id {
|
|
|
|
TagId::None => unreachable!(),
|
|
|
|
TagId::Some(id) => id,
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
tag::v0alpha1::GetResponse { tag_ids }
|
|
|
|
})
|
|
|
|
.await
|
2023-06-18 01:55:04 +02:00
|
|
|
}
|
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
async fn get_properties(
|
|
|
|
&self,
|
2024-02-19 06:05:37 +01:00
|
|
|
request: Request<tag::v0alpha1::GetPropertiesRequest>,
|
|
|
|
) -> Result<Response<tag::v0alpha1::GetPropertiesResponse>, Status> {
|
2024-01-22 06:42:48 +01:00
|
|
|
let request = request.into_inner();
|
|
|
|
|
|
|
|
let tag_id = TagId::Some(
|
|
|
|
request
|
|
|
|
.tag_id
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no tag specified"))?,
|
|
|
|
);
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
let tag = tag_id.tag(state);
|
|
|
|
|
|
|
|
let output_name = tag
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|tag| tag.output(state))
|
|
|
|
.map(|output| output.name());
|
|
|
|
let active = tag.as_ref().map(|tag| tag.active());
|
|
|
|
let name = tag.as_ref().map(|tag| tag.name());
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
tag::v0alpha1::GetPropertiesResponse {
|
|
|
|
active,
|
|
|
|
name,
|
|
|
|
output_name,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.await
|
2023-06-18 01:55:04 +02:00
|
|
|
}
|
|
|
|
}
|
2023-09-22 00:12:16 +02:00
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
pub struct OutputService {
|
2024-02-19 06:05:37 +01:00
|
|
|
sender: StateFnSender,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl OutputService {
|
|
|
|
pub fn new(sender: StateFnSender) -> Self {
|
|
|
|
Self { sender }
|
|
|
|
}
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[tonic::async_trait]
|
2024-02-19 06:05:37 +01:00
|
|
|
impl output_service_server::OutputService for OutputService {
|
2024-01-22 06:42:48 +01:00
|
|
|
async fn set_location(
|
|
|
|
&self,
|
|
|
|
request: Request<SetLocationRequest>,
|
|
|
|
) -> Result<Response<()>, Status> {
|
|
|
|
let request = request.into_inner();
|
|
|
|
|
|
|
|
let output_name = OutputName(
|
|
|
|
request
|
|
|
|
.output_name
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no output specified"))?,
|
|
|
|
);
|
|
|
|
|
|
|
|
let x = request.x;
|
|
|
|
let y = request.y;
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary_no_response(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
if let Some(saved_state) = state.config.connector_saved_states.get_mut(&output_name) {
|
|
|
|
if let Some(x) = x {
|
|
|
|
saved_state.loc.x = x;
|
|
|
|
}
|
|
|
|
if let Some(y) = y {
|
|
|
|
saved_state.loc.y = y;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
state.config.connector_saved_states.insert(
|
|
|
|
output_name.clone(),
|
|
|
|
ConnectorSavedState {
|
|
|
|
loc: (x.unwrap_or_default(), y.unwrap_or_default()).into(),
|
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let Some(output) = output_name.output(state) else {
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
let mut loc = output.current_location();
|
|
|
|
if let Some(x) = x {
|
|
|
|
loc.x = x;
|
|
|
|
}
|
|
|
|
if let Some(y) = y {
|
|
|
|
loc.y = y;
|
|
|
|
}
|
|
|
|
output.change_current_state(None, None, None, Some(loc));
|
|
|
|
state.space.map_output(&output, loc);
|
|
|
|
tracing::debug!("Mapping output {} to {loc:?}", output.name());
|
|
|
|
state.update_windows(&output);
|
2024-02-19 06:05:37 +01:00
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn get(
|
|
|
|
&self,
|
2024-02-19 06:05:37 +01:00
|
|
|
_request: Request<output::v0alpha1::GetRequest>,
|
|
|
|
) -> Result<Response<output::v0alpha1::GetResponse>, Status> {
|
|
|
|
run_unary(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
let output_names = state
|
|
|
|
.space
|
|
|
|
.outputs()
|
|
|
|
.map(|output| output.name())
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
output::v0alpha1::GetResponse { output_names }
|
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn get_properties(
|
|
|
|
&self,
|
2024-02-19 06:05:37 +01:00
|
|
|
request: Request<output::v0alpha1::GetPropertiesRequest>,
|
|
|
|
) -> Result<Response<output::v0alpha1::GetPropertiesResponse>, Status> {
|
2024-01-22 06:42:48 +01:00
|
|
|
let request = request.into_inner();
|
|
|
|
|
|
|
|
let output_name = OutputName(
|
|
|
|
request
|
|
|
|
.output_name
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no output specified"))?,
|
|
|
|
);
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
let output = output_name.output(state);
|
|
|
|
|
|
|
|
let pixel_width = output
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|output| output.current_mode().map(|mode| mode.size.w as u32));
|
|
|
|
|
|
|
|
let pixel_height = output
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|output| output.current_mode().map(|mode| mode.size.h as u32));
|
|
|
|
|
|
|
|
let refresh_rate = output
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|output| output.current_mode().map(|mode| mode.refresh as u32));
|
|
|
|
|
|
|
|
let model = output
|
|
|
|
.as_ref()
|
|
|
|
.map(|output| output.physical_properties().model);
|
|
|
|
|
|
|
|
let physical_width = output
|
|
|
|
.as_ref()
|
|
|
|
.map(|output| output.physical_properties().size.w as u32);
|
|
|
|
|
|
|
|
let physical_height = output
|
|
|
|
.as_ref()
|
|
|
|
.map(|output| output.physical_properties().size.h as u32);
|
|
|
|
|
|
|
|
let make = output
|
|
|
|
.as_ref()
|
|
|
|
.map(|output| output.physical_properties().make);
|
|
|
|
|
|
|
|
let x = output.as_ref().map(|output| output.current_location().x);
|
|
|
|
|
|
|
|
let y = output.as_ref().map(|output| output.current_location().y);
|
|
|
|
|
|
|
|
let focused = state
|
2024-02-24 21:41:52 +01:00
|
|
|
.output_focus_stack
|
|
|
|
.current_focus()
|
2024-01-22 06:42:48 +01:00
|
|
|
.and_then(|foc_op| output.as_ref().map(|op| op == foc_op));
|
|
|
|
|
|
|
|
let tag_ids = output
|
|
|
|
.as_ref()
|
|
|
|
.map(|output| {
|
|
|
|
output.with_state(|state| {
|
|
|
|
state
|
|
|
|
.tags
|
|
|
|
.iter()
|
|
|
|
.map(|tag| match tag.id() {
|
|
|
|
TagId::None => unreachable!(),
|
|
|
|
TagId::Some(id) => id,
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
output::v0alpha1::GetPropertiesResponse {
|
|
|
|
make,
|
|
|
|
model,
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
pixel_width,
|
|
|
|
pixel_height,
|
|
|
|
refresh_rate,
|
|
|
|
physical_width,
|
|
|
|
physical_height,
|
|
|
|
focused,
|
|
|
|
tag_ids,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct WindowService {
|
2024-02-19 06:05:37 +01:00
|
|
|
sender: StateFnSender,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl WindowService {
|
|
|
|
pub fn new(sender: StateFnSender) -> Self {
|
|
|
|
Self { sender }
|
|
|
|
}
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[tonic::async_trait]
|
2024-02-19 06:05:37 +01:00
|
|
|
impl window_service_server::WindowService for WindowService {
|
2024-01-22 06:42:48 +01:00
|
|
|
async fn close(&self, request: Request<CloseRequest>) -> Result<Response<()>, Status> {
|
|
|
|
let request = request.into_inner();
|
|
|
|
|
2024-02-22 05:48:09 +01:00
|
|
|
let window_id = WindowId(
|
2024-01-22 06:42:48 +01:00
|
|
|
request
|
|
|
|
.window_id
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no window specified"))?,
|
|
|
|
);
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary_no_response(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
let Some(window) = window_id.window(state) else { return };
|
|
|
|
|
2024-03-01 03:10:23 +01:00
|
|
|
match window.underlying_surface() {
|
|
|
|
WindowSurface::Wayland(toplevel) => toplevel.send_close(),
|
|
|
|
WindowSurface::X11(surface) => {
|
|
|
|
if !surface.is_override_redirect() {
|
|
|
|
if let Err(err) = surface.close() {
|
|
|
|
error!("failed to close x11 window: {err}");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
warn!("tried to close OR window");
|
|
|
|
}
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
}
|
2024-02-19 06:05:37 +01:00
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn set_geometry(
|
|
|
|
&self,
|
|
|
|
request: Request<SetGeometryRequest>,
|
|
|
|
) -> Result<Response<()>, Status> {
|
|
|
|
let request = request.into_inner();
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
tracing::info!(request = ?request);
|
|
|
|
|
2024-02-22 05:48:09 +01:00
|
|
|
let window_id = WindowId(
|
2024-01-22 06:42:48 +01:00
|
|
|
request
|
|
|
|
.window_id
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no window specified"))?,
|
|
|
|
);
|
|
|
|
|
|
|
|
let geometry = request.geometry.unwrap_or_default();
|
|
|
|
let x = geometry.x;
|
|
|
|
let y = geometry.y;
|
|
|
|
let width = geometry.width;
|
|
|
|
let height = geometry.height;
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary_no_response(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
let Some(window) = window_id.window(state) else { return };
|
|
|
|
|
|
|
|
// TODO: with no x or y, defaults unmapped windows to 0, 0
|
|
|
|
let mut window_loc = state
|
|
|
|
.space
|
|
|
|
.element_location(&window)
|
|
|
|
.unwrap_or((x.unwrap_or_default(), y.unwrap_or_default()).into());
|
|
|
|
window_loc.x = x.unwrap_or(window_loc.x);
|
|
|
|
window_loc.y = y.unwrap_or(window_loc.y);
|
|
|
|
|
|
|
|
let mut window_size = window.geometry().size;
|
|
|
|
window_size.w = width.unwrap_or(window_size.w);
|
|
|
|
window_size.h = height.unwrap_or(window_size.h);
|
|
|
|
|
|
|
|
let rect = Rectangle::from_loc_and_size(window_loc, window_size);
|
|
|
|
// window.change_geometry(rect);
|
|
|
|
window.with_state(|state| {
|
|
|
|
use crate::window::window_state::FloatingOrTiled;
|
|
|
|
state.floating_or_tiled = match state.floating_or_tiled {
|
|
|
|
FloatingOrTiled::Floating(_) => FloatingOrTiled::Floating(rect),
|
|
|
|
FloatingOrTiled::Tiled(_) => FloatingOrTiled::Tiled(Some(rect)),
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
for output in state.space.outputs_for_element(&window) {
|
|
|
|
state.update_windows(&output);
|
|
|
|
state.schedule_render(&output);
|
|
|
|
}
|
2024-02-19 06:05:37 +01:00
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn set_fullscreen(
|
|
|
|
&self,
|
|
|
|
request: Request<SetFullscreenRequest>,
|
|
|
|
) -> Result<Response<()>, Status> {
|
|
|
|
let request = request.into_inner();
|
|
|
|
|
2024-02-22 05:48:09 +01:00
|
|
|
let window_id = WindowId(
|
2024-01-22 06:42:48 +01:00
|
|
|
request
|
|
|
|
.window_id
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no window specified"))?,
|
|
|
|
);
|
|
|
|
|
2024-02-24 21:41:52 +01:00
|
|
|
let set_or_toggle = request.set_or_toggle();
|
|
|
|
|
|
|
|
if set_or_toggle == SetOrToggle::Unspecified {
|
|
|
|
return Err(Status::invalid_argument("unspecified set or toggle"));
|
|
|
|
}
|
2024-01-22 06:42:48 +01:00
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary_no_response(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
let Some(window) = window_id.window(state) else {
|
|
|
|
return;
|
|
|
|
};
|
2024-02-24 21:41:52 +01:00
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
match set_or_toggle {
|
2024-02-24 21:41:52 +01:00
|
|
|
SetOrToggle::Set => {
|
|
|
|
if !window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()) {
|
|
|
|
window.toggle_fullscreen();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SetOrToggle::Unset => {
|
|
|
|
if window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()) {
|
2024-01-22 06:42:48 +01:00
|
|
|
window.toggle_fullscreen();
|
|
|
|
}
|
|
|
|
}
|
2024-02-24 21:41:52 +01:00
|
|
|
SetOrToggle::Toggle => window.toggle_fullscreen(),
|
|
|
|
SetOrToggle::Unspecified => unreachable!(),
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
let Some(output) = window.output(state) else {
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
|
|
|
state.update_windows(&output);
|
|
|
|
state.schedule_render(&output);
|
2024-02-19 06:05:37 +01:00
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn set_maximized(
|
|
|
|
&self,
|
|
|
|
request: Request<SetMaximizedRequest>,
|
|
|
|
) -> Result<Response<()>, Status> {
|
|
|
|
let request = request.into_inner();
|
|
|
|
|
2024-02-22 05:48:09 +01:00
|
|
|
let window_id = WindowId(
|
2024-01-22 06:42:48 +01:00
|
|
|
request
|
|
|
|
.window_id
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no window specified"))?,
|
|
|
|
);
|
|
|
|
|
2024-02-24 21:41:52 +01:00
|
|
|
let set_or_toggle = request.set_or_toggle();
|
|
|
|
|
|
|
|
if set_or_toggle == SetOrToggle::Unspecified {
|
|
|
|
return Err(Status::invalid_argument("unspecified set or toggle"));
|
|
|
|
}
|
2024-01-22 06:42:48 +01:00
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary_no_response(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
let Some(window) = window_id.window(state) else {
|
|
|
|
return;
|
|
|
|
};
|
2024-02-24 21:41:52 +01:00
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
match set_or_toggle {
|
2024-02-24 21:41:52 +01:00
|
|
|
SetOrToggle::Set => {
|
|
|
|
if !window.with_state(|state| state.fullscreen_or_maximized.is_maximized()) {
|
2024-01-22 06:42:48 +01:00
|
|
|
window.toggle_maximized();
|
|
|
|
}
|
|
|
|
}
|
2024-02-24 21:41:52 +01:00
|
|
|
SetOrToggle::Unset => {
|
|
|
|
if window.with_state(|state| state.fullscreen_or_maximized.is_maximized()) {
|
|
|
|
window.toggle_maximized();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SetOrToggle::Toggle => window.toggle_maximized(),
|
|
|
|
SetOrToggle::Unspecified => unreachable!(),
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
let Some(output) = window.output(state) else {
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
|
|
|
state.update_windows(&output);
|
|
|
|
state.schedule_render(&output);
|
2024-02-19 06:05:37 +01:00
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn set_floating(
|
|
|
|
&self,
|
|
|
|
request: Request<SetFloatingRequest>,
|
|
|
|
) -> Result<Response<()>, Status> {
|
|
|
|
let request = request.into_inner();
|
|
|
|
|
2024-02-22 05:48:09 +01:00
|
|
|
let window_id = WindowId(
|
2024-01-22 06:42:48 +01:00
|
|
|
request
|
|
|
|
.window_id
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no window specified"))?,
|
|
|
|
);
|
|
|
|
|
2024-02-24 21:41:52 +01:00
|
|
|
let set_or_toggle = request.set_or_toggle();
|
|
|
|
|
|
|
|
if set_or_toggle == SetOrToggle::Unspecified {
|
|
|
|
return Err(Status::invalid_argument("unspecified set or toggle"));
|
|
|
|
}
|
2024-01-22 06:42:48 +01:00
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary_no_response(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
let Some(window) = window_id.window(state) else {
|
|
|
|
return;
|
|
|
|
};
|
2024-02-24 21:41:52 +01:00
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
match set_or_toggle {
|
2024-02-24 21:41:52 +01:00
|
|
|
SetOrToggle::Set => {
|
|
|
|
if !window.with_state(|state| state.floating_or_tiled.is_floating()) {
|
|
|
|
window.toggle_floating();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SetOrToggle::Unset => {
|
|
|
|
if window.with_state(|state| state.floating_or_tiled.is_floating()) {
|
2024-01-22 06:42:48 +01:00
|
|
|
window.toggle_floating();
|
|
|
|
}
|
|
|
|
}
|
2024-02-24 21:41:52 +01:00
|
|
|
SetOrToggle::Toggle => window.toggle_floating(),
|
|
|
|
SetOrToggle::Unspecified => unreachable!(),
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
let Some(output) = window.output(state) else {
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
|
|
|
state.update_windows(&output);
|
|
|
|
state.schedule_render(&output);
|
2024-02-19 06:05:37 +01:00
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
2024-02-24 21:41:52 +01:00
|
|
|
async fn set_focused(
|
|
|
|
&self,
|
|
|
|
request: Request<SetFocusedRequest>,
|
|
|
|
) -> Result<Response<()>, Status> {
|
|
|
|
let request = request.into_inner();
|
|
|
|
|
|
|
|
let window_id = WindowId(
|
|
|
|
request
|
|
|
|
.window_id
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no window specified"))?,
|
|
|
|
);
|
|
|
|
|
|
|
|
let set_or_toggle = request.set_or_toggle();
|
|
|
|
|
|
|
|
if set_or_toggle == SetOrToggle::Unspecified {
|
|
|
|
return Err(Status::invalid_argument("unspecified set or toggle"));
|
|
|
|
}
|
|
|
|
|
|
|
|
run_unary_no_response(&self.sender, move |state| {
|
|
|
|
let Some(window) = window_id.window(state) else {
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
2024-03-01 03:10:23 +01:00
|
|
|
if window.is_x11_override_redirect() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-02-24 21:41:52 +01:00
|
|
|
let Some(output) = window.output(state) else {
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
|
|
|
for win in state.space.elements() {
|
|
|
|
win.set_activate(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
match set_or_toggle {
|
|
|
|
SetOrToggle::Set => {
|
|
|
|
window.set_activate(true);
|
|
|
|
output.with_state(|state| state.focus_stack.set_focus(window.clone()));
|
|
|
|
state.output_focus_stack.set_focus(output.clone());
|
|
|
|
if let Some(keyboard) = state.seat.get_keyboard() {
|
|
|
|
keyboard.set_focus(
|
|
|
|
state,
|
2024-03-01 07:07:01 +01:00
|
|
|
Some(KeyboardFocusTarget::Window(window)),
|
2024-02-24 21:41:52 +01:00
|
|
|
SERIAL_COUNTER.next_serial(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SetOrToggle::Unset => {
|
|
|
|
if output.with_state(|state| state.focus_stack.current_focus() == Some(&window))
|
|
|
|
{
|
|
|
|
output.with_state(|state| state.focus_stack.unset_focus());
|
|
|
|
if let Some(keyboard) = state.seat.get_keyboard() {
|
|
|
|
keyboard.set_focus(state, None, SERIAL_COUNTER.next_serial());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SetOrToggle::Toggle => {
|
|
|
|
if output.with_state(|state| state.focus_stack.current_focus() == Some(&window))
|
|
|
|
{
|
|
|
|
output.with_state(|state| state.focus_stack.unset_focus());
|
|
|
|
if let Some(keyboard) = state.seat.get_keyboard() {
|
|
|
|
keyboard.set_focus(state, None, SERIAL_COUNTER.next_serial());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
window.set_activate(true);
|
|
|
|
output.with_state(|state| state.focus_stack.set_focus(window.clone()));
|
|
|
|
state.output_focus_stack.set_focus(output.clone());
|
|
|
|
if let Some(keyboard) = state.seat.get_keyboard() {
|
|
|
|
keyboard.set_focus(
|
|
|
|
state,
|
2024-03-01 07:07:01 +01:00
|
|
|
Some(KeyboardFocusTarget::Window(window)),
|
2024-02-24 21:41:52 +01:00
|
|
|
SERIAL_COUNTER.next_serial(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SetOrToggle::Unspecified => unreachable!(),
|
|
|
|
}
|
|
|
|
|
|
|
|
for window in state.space.elements() {
|
2024-03-01 03:10:23 +01:00
|
|
|
if let Some(toplevel) = window.toplevel() {
|
|
|
|
toplevel.send_configure();
|
2024-02-24 21:41:52 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
state.update_windows(&output);
|
|
|
|
state.schedule_render(&output);
|
|
|
|
})
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
|
2024-01-22 06:42:48 +01:00
|
|
|
async fn move_to_tag(
|
|
|
|
&self,
|
|
|
|
request: Request<MoveToTagRequest>,
|
|
|
|
) -> Result<Response<()>, Status> {
|
|
|
|
let request = request.into_inner();
|
|
|
|
|
2024-02-22 05:48:09 +01:00
|
|
|
let window_id = WindowId(
|
2024-01-22 06:42:48 +01:00
|
|
|
request
|
|
|
|
.window_id
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no window specified"))?,
|
|
|
|
);
|
|
|
|
|
|
|
|
let tag_id = TagId::Some(
|
|
|
|
request
|
|
|
|
.tag_id
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no tag specified"))?,
|
|
|
|
);
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary_no_response(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
let Some(window) = window_id.window(state) else { return };
|
|
|
|
let Some(tag) = tag_id.tag(state) else { return };
|
|
|
|
window.with_state(|state| {
|
|
|
|
state.tags = vec![tag.clone()];
|
|
|
|
});
|
|
|
|
let Some(output) = tag.output(state) else { return };
|
|
|
|
state.update_windows(&output);
|
|
|
|
state.schedule_render(&output);
|
2024-02-19 06:05:37 +01:00
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn set_tag(&self, request: Request<SetTagRequest>) -> Result<Response<()>, Status> {
|
|
|
|
let request = request.into_inner();
|
|
|
|
|
2024-02-22 05:48:09 +01:00
|
|
|
let window_id = WindowId(
|
2024-01-22 06:42:48 +01:00
|
|
|
request
|
|
|
|
.window_id
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no window specified"))?,
|
|
|
|
);
|
|
|
|
|
|
|
|
let tag_id = TagId::Some(
|
|
|
|
request
|
|
|
|
.tag_id
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no tag specified"))?,
|
|
|
|
);
|
|
|
|
|
2024-02-24 21:41:52 +01:00
|
|
|
let set_or_toggle = request.set_or_toggle();
|
|
|
|
|
|
|
|
if set_or_toggle == SetOrToggle::Unspecified {
|
|
|
|
return Err(Status::invalid_argument("unspecified set or toggle"));
|
|
|
|
}
|
2024-01-22 06:42:48 +01:00
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary_no_response(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
let Some(window) = window_id.window(state) else { return };
|
|
|
|
let Some(tag) = tag_id.tag(state) else { return };
|
|
|
|
|
|
|
|
// TODO: turn state.tags into a hashset
|
|
|
|
match set_or_toggle {
|
2024-02-24 21:41:52 +01:00
|
|
|
SetOrToggle::Set => window.with_state(|state| {
|
|
|
|
state.tags.retain(|tg| tg != &tag);
|
|
|
|
state.tags.push(tag.clone());
|
|
|
|
}),
|
|
|
|
SetOrToggle::Unset => window.with_state(|state| {
|
|
|
|
state.tags.retain(|tg| tg != &tag);
|
|
|
|
}),
|
|
|
|
SetOrToggle::Toggle => window.with_state(|state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
if !state.tags.contains(&tag) {
|
|
|
|
state.tags.push(tag.clone());
|
|
|
|
} else {
|
|
|
|
state.tags.retain(|tg| tg != &tag);
|
|
|
|
}
|
|
|
|
}),
|
2024-02-24 21:41:52 +01:00
|
|
|
SetOrToggle::Unspecified => unreachable!(),
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
let Some(output) = tag.output(state) else { return };
|
|
|
|
state.update_windows(&output);
|
|
|
|
state.schedule_render(&output);
|
2024-02-19 06:05:37 +01:00
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn move_grab(&self, request: Request<MoveGrabRequest>) -> Result<Response<()>, Status> {
|
|
|
|
let request = request.into_inner();
|
|
|
|
|
|
|
|
let button = request
|
|
|
|
.button
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no button specified"))?;
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary_no_response(&self.sender, move |state| {
|
2024-03-04 23:43:54 +01:00
|
|
|
let Some(pointer_location) = state.seat.get_pointer().map(|ptr| ptr.current_location())
|
|
|
|
else {
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
let Some((pointer_focus, _)) = state.pointer_focus_target_under(pointer_location)
|
2024-01-22 06:42:48 +01:00
|
|
|
else {
|
|
|
|
return;
|
|
|
|
};
|
2024-03-01 07:07:01 +01:00
|
|
|
let Some(window) = pointer_focus.window_for(state) else {
|
|
|
|
tracing::info!("Move grabs are currently not implemented for non-windows");
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
let Some(wl_surf) = window.wl_surface() else {
|
|
|
|
return;
|
|
|
|
};
|
2024-01-22 06:42:48 +01:00
|
|
|
let seat = state.seat.clone();
|
|
|
|
|
|
|
|
crate::grab::move_grab::move_request_server(
|
|
|
|
state,
|
|
|
|
&wl_surf,
|
|
|
|
&seat,
|
|
|
|
SERIAL_COUNTER.next_serial(),
|
|
|
|
button,
|
|
|
|
);
|
2024-02-19 06:05:37 +01:00
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn resize_grab(
|
|
|
|
&self,
|
|
|
|
request: Request<ResizeGrabRequest>,
|
|
|
|
) -> Result<Response<()>, Status> {
|
|
|
|
let request = request.into_inner();
|
|
|
|
|
|
|
|
let button = request
|
|
|
|
.button
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no button specified"))?;
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary_no_response(&self.sender, move |state| {
|
2024-03-04 23:43:54 +01:00
|
|
|
let Some(pointer_loc) = state.seat.get_pointer().map(|ptr| ptr.current_location())
|
|
|
|
else {
|
|
|
|
return;
|
|
|
|
};
|
2024-03-01 07:07:01 +01:00
|
|
|
let Some((pointer_focus, window_loc)) = state.pointer_focus_target_under(pointer_loc)
|
2024-01-22 06:42:48 +01:00
|
|
|
else {
|
|
|
|
return;
|
|
|
|
};
|
2024-03-01 07:07:01 +01:00
|
|
|
let Some(window) = pointer_focus.window_for(state) else {
|
|
|
|
tracing::info!("Move grabs are currently not implemented for non-windows");
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
let Some(wl_surf) = window.wl_surface() else {
|
|
|
|
return;
|
|
|
|
};
|
2024-01-22 06:42:48 +01:00
|
|
|
|
|
|
|
let window_geometry = window.geometry();
|
|
|
|
let window_x = window_loc.x as f64;
|
|
|
|
let window_y = window_loc.y as f64;
|
|
|
|
let window_width = window_geometry.size.w as f64;
|
|
|
|
let window_height = window_geometry.size.h as f64;
|
|
|
|
let half_width = window_x + window_width / 2.0;
|
|
|
|
let half_height = window_y + window_height / 2.0;
|
|
|
|
let full_width = window_x + window_width;
|
|
|
|
let full_height = window_y + window_height;
|
|
|
|
|
|
|
|
let edges = match pointer_loc {
|
|
|
|
Point { x, y, .. }
|
|
|
|
if (window_x..=half_width).contains(&x)
|
|
|
|
&& (window_y..=half_height).contains(&y) =>
|
|
|
|
{
|
|
|
|
server::xdg_toplevel::ResizeEdge::TopLeft
|
|
|
|
}
|
|
|
|
Point { x, y, .. }
|
|
|
|
if (half_width..=full_width).contains(&x)
|
|
|
|
&& (window_y..=half_height).contains(&y) =>
|
|
|
|
{
|
|
|
|
server::xdg_toplevel::ResizeEdge::TopRight
|
|
|
|
}
|
|
|
|
Point { x, y, .. }
|
|
|
|
if (window_x..=half_width).contains(&x)
|
|
|
|
&& (half_height..=full_height).contains(&y) =>
|
|
|
|
{
|
|
|
|
server::xdg_toplevel::ResizeEdge::BottomLeft
|
|
|
|
}
|
|
|
|
Point { x, y, .. }
|
|
|
|
if (half_width..=full_width).contains(&x)
|
|
|
|
&& (half_height..=full_height).contains(&y) =>
|
|
|
|
{
|
|
|
|
server::xdg_toplevel::ResizeEdge::BottomRight
|
|
|
|
}
|
|
|
|
_ => server::xdg_toplevel::ResizeEdge::None,
|
|
|
|
};
|
|
|
|
|
|
|
|
crate::grab::resize_grab::resize_request_server(
|
|
|
|
state,
|
|
|
|
&wl_surf,
|
|
|
|
&state.seat.clone(),
|
|
|
|
SERIAL_COUNTER.next_serial(),
|
|
|
|
edges.into(),
|
|
|
|
button,
|
|
|
|
);
|
2024-02-19 06:05:37 +01:00
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn get(
|
|
|
|
&self,
|
2024-02-19 06:05:37 +01:00
|
|
|
_request: Request<window::v0alpha1::GetRequest>,
|
|
|
|
) -> Result<Response<window::v0alpha1::GetResponse>, Status> {
|
|
|
|
run_unary(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
let window_ids = state
|
|
|
|
.windows
|
|
|
|
.iter()
|
2024-02-22 05:48:09 +01:00
|
|
|
.map(|win| win.with_state(|state| state.id.0))
|
2024-01-22 06:42:48 +01:00
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
window::v0alpha1::GetResponse { window_ids }
|
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn get_properties(
|
|
|
|
&self,
|
2024-02-19 06:05:37 +01:00
|
|
|
request: Request<window::v0alpha1::GetPropertiesRequest>,
|
|
|
|
) -> Result<Response<window::v0alpha1::GetPropertiesResponse>, Status> {
|
2024-01-22 06:42:48 +01:00
|
|
|
let request = request.into_inner();
|
|
|
|
|
2024-02-22 05:48:09 +01:00
|
|
|
let window_id = WindowId(
|
2024-01-22 06:42:48 +01:00
|
|
|
request
|
|
|
|
.window_id
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no window specified"))?,
|
|
|
|
);
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
let window = window_id.window(state);
|
|
|
|
|
|
|
|
let width = window.as_ref().map(|win| win.geometry().size.w);
|
|
|
|
|
|
|
|
let height = window.as_ref().map(|win| win.geometry().size.h);
|
|
|
|
|
|
|
|
let x = window
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|win| state.space.element_location(win))
|
|
|
|
.map(|loc| loc.x);
|
|
|
|
|
|
|
|
let y = window
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|win| state.space.element_location(win))
|
|
|
|
.map(|loc| loc.y);
|
|
|
|
|
|
|
|
let geometry = if width.is_none() && height.is_none() && x.is_none() && y.is_none() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(Geometry {
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
width,
|
|
|
|
height,
|
|
|
|
})
|
|
|
|
};
|
|
|
|
|
2024-03-01 03:10:23 +01:00
|
|
|
let class = window.as_ref().and_then(|win| win.class());
|
|
|
|
let title = window.as_ref().and_then(|win| win.title());
|
2024-01-22 06:42:48 +01:00
|
|
|
|
|
|
|
let focused = window.as_ref().and_then(|win| {
|
2024-02-24 21:58:58 +01:00
|
|
|
state
|
|
|
|
.output_focus_stack
|
|
|
|
.current_focus()
|
|
|
|
.and_then(|output| state.focused_window(output))
|
|
|
|
.map(|foc_win| win == &foc_win)
|
2024-01-22 06:42:48 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
let floating = window
|
|
|
|
.as_ref()
|
|
|
|
.map(|win| win.with_state(|state| state.floating_or_tiled.is_floating()));
|
|
|
|
|
|
|
|
let fullscreen_or_maximized = window
|
|
|
|
.as_ref()
|
|
|
|
.map(|win| win.with_state(|state| state.fullscreen_or_maximized))
|
|
|
|
.map(|fs_or_max| match fs_or_max {
|
|
|
|
// TODO: from impl
|
|
|
|
crate::window::window_state::FullscreenOrMaximized::Neither => {
|
|
|
|
FullscreenOrMaximized::Neither
|
|
|
|
}
|
|
|
|
crate::window::window_state::FullscreenOrMaximized::Fullscreen => {
|
|
|
|
FullscreenOrMaximized::Fullscreen
|
|
|
|
}
|
|
|
|
crate::window::window_state::FullscreenOrMaximized::Maximized => {
|
|
|
|
FullscreenOrMaximized::Maximized
|
|
|
|
}
|
|
|
|
} as i32);
|
|
|
|
|
|
|
|
let tag_ids = window
|
|
|
|
.as_ref()
|
|
|
|
.map(|win| {
|
|
|
|
win.with_state(|state| {
|
|
|
|
state
|
|
|
|
.tags
|
|
|
|
.iter()
|
|
|
|
.map(|tag| match tag.id() {
|
|
|
|
TagId::Some(id) => id,
|
|
|
|
TagId::None => unreachable!(),
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
window::v0alpha1::GetPropertiesResponse {
|
|
|
|
geometry,
|
|
|
|
class,
|
|
|
|
title,
|
|
|
|
focused,
|
|
|
|
floating,
|
|
|
|
fullscreen_or_maximized,
|
|
|
|
tag_ids,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn add_window_rule(
|
|
|
|
&self,
|
|
|
|
request: Request<AddWindowRuleRequest>,
|
|
|
|
) -> Result<Response<()>, Status> {
|
|
|
|
let request = request.into_inner();
|
|
|
|
|
|
|
|
let cond = request
|
|
|
|
.cond
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no condition specified"))?
|
|
|
|
.into();
|
|
|
|
|
|
|
|
let rule = request
|
|
|
|
.rule
|
|
|
|
.ok_or_else(|| Status::invalid_argument("no rule specified"))?
|
|
|
|
.into();
|
|
|
|
|
2024-02-19 06:05:37 +01:00
|
|
|
run_unary_no_response(&self.sender, move |state| {
|
2024-01-22 06:42:48 +01:00
|
|
|
state.config.window_rules.push((cond, rule));
|
2024-02-19 06:05:37 +01:00
|
|
|
})
|
|
|
|
.await
|
2024-01-22 06:42:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<WindowRuleCondition> for crate::window::rules::WindowRuleCondition {
|
|
|
|
fn from(cond: WindowRuleCondition) -> Self {
|
|
|
|
let cond_any = match cond.any.is_empty() {
|
|
|
|
true => None,
|
|
|
|
false => Some(
|
|
|
|
cond.any
|
|
|
|
.into_iter()
|
|
|
|
.map(crate::window::rules::WindowRuleCondition::from)
|
|
|
|
.collect::<Vec<_>>(),
|
|
|
|
),
|
|
|
|
};
|
|
|
|
|
|
|
|
let cond_all = match cond.all.is_empty() {
|
|
|
|
true => None,
|
|
|
|
false => Some(
|
|
|
|
cond.all
|
|
|
|
.into_iter()
|
|
|
|
.map(crate::window::rules::WindowRuleCondition::from)
|
|
|
|
.collect::<Vec<_>>(),
|
|
|
|
),
|
|
|
|
};
|
|
|
|
|
|
|
|
let class = match cond.classes.is_empty() {
|
|
|
|
true => None,
|
|
|
|
false => Some(cond.classes),
|
|
|
|
};
|
|
|
|
|
|
|
|
let title = match cond.titles.is_empty() {
|
|
|
|
true => None,
|
|
|
|
false => Some(cond.titles),
|
|
|
|
};
|
|
|
|
|
|
|
|
let tag = match cond.tags.is_empty() {
|
|
|
|
true => None,
|
|
|
|
false => Some(cond.tags.into_iter().map(TagId::Some).collect::<Vec<_>>()),
|
|
|
|
};
|
|
|
|
|
|
|
|
crate::window::rules::WindowRuleCondition {
|
|
|
|
cond_any,
|
|
|
|
cond_all,
|
|
|
|
class,
|
|
|
|
title,
|
|
|
|
tag,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<WindowRule> for crate::window::rules::WindowRule {
|
|
|
|
fn from(rule: WindowRule) -> Self {
|
|
|
|
let fullscreen_or_maximized = match rule.fullscreen_or_maximized() {
|
|
|
|
FullscreenOrMaximized::Unspecified => None,
|
|
|
|
FullscreenOrMaximized::Neither => {
|
|
|
|
Some(crate::window::window_state::FullscreenOrMaximized::Neither)
|
|
|
|
}
|
|
|
|
FullscreenOrMaximized::Fullscreen => {
|
|
|
|
Some(crate::window::window_state::FullscreenOrMaximized::Fullscreen)
|
|
|
|
}
|
|
|
|
FullscreenOrMaximized::Maximized => {
|
|
|
|
Some(crate::window::window_state::FullscreenOrMaximized::Maximized)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let output = rule.output.map(OutputName);
|
|
|
|
let tags = match rule.tags.is_empty() {
|
|
|
|
true => None,
|
|
|
|
false => Some(rule.tags.into_iter().map(TagId::Some).collect::<Vec<_>>()),
|
|
|
|
};
|
|
|
|
let floating_or_tiled = rule.floating.map(|floating| match floating {
|
|
|
|
true => crate::window::rules::FloatingOrTiled::Floating,
|
|
|
|
false => crate::window::rules::FloatingOrTiled::Tiled,
|
|
|
|
});
|
|
|
|
let size = rule.width.and_then(|w| {
|
|
|
|
rule.height.and_then(|h| {
|
|
|
|
Some((
|
|
|
|
NonZeroU32::try_from(w as u32).ok()?,
|
|
|
|
NonZeroU32::try_from(h as u32).ok()?,
|
|
|
|
))
|
|
|
|
})
|
|
|
|
});
|
|
|
|
let location = rule.x.and_then(|x| rule.y.map(|y| (x, y)));
|
|
|
|
|
|
|
|
crate::window::rules::WindowRule {
|
|
|
|
output,
|
|
|
|
tags,
|
|
|
|
floating_or_tiled,
|
|
|
|
fullscreen_or_maximized,
|
|
|
|
size,
|
|
|
|
location,
|
|
|
|
}
|
|
|
|
}
|
2023-09-22 00:12:16 +02:00
|
|
|
}
|