mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2025-01-17 18:11:30 +01:00
Add server side window rules API
This commit is contained in:
parent
d9ce324606
commit
4f8d662dd3
5 changed files with 137 additions and 19 deletions
|
@ -3,7 +3,7 @@
|
||||||
// The MessagePack format for these is a one-element map where the element's key is the enum name and its
|
// The MessagePack format for these is a one-element map where the element's key is the enum name and its
|
||||||
// value is a map of the enum's values
|
// value is a map of the enum's values
|
||||||
|
|
||||||
mod window_rules;
|
pub mod window_rules;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
layout::Layout,
|
layout::Layout,
|
||||||
|
@ -12,6 +12,8 @@ use crate::{
|
||||||
window::window_state::{FullscreenOrMaximized, WindowId},
|
window::window_state::{FullscreenOrMaximized, WindowId},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use self::window_rules::{WindowRule, WindowRuleCondition};
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy)]
|
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy)]
|
||||||
pub struct CallbackId(pub u32);
|
pub struct CallbackId(pub u32);
|
||||||
|
|
||||||
|
@ -55,6 +57,10 @@ pub enum Msg {
|
||||||
ToggleMaximized {
|
ToggleMaximized {
|
||||||
window_id: WindowId,
|
window_id: WindowId,
|
||||||
},
|
},
|
||||||
|
AddWindowRule {
|
||||||
|
cond: WindowRuleCondition,
|
||||||
|
rule: Vec<WindowRule>,
|
||||||
|
},
|
||||||
|
|
||||||
// Tag management
|
// Tag management
|
||||||
ToggleTag {
|
ToggleTag {
|
||||||
|
|
|
@ -83,20 +83,20 @@ impl WindowRuleCondition {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum WindowRule {
|
pub struct WindowRule {
|
||||||
/// Set the output the window will open on.
|
/// Set the output the window will open on.
|
||||||
Output(OutputName),
|
pub output: Option<OutputName>,
|
||||||
/// Set the tags the output will have on open.
|
/// Set the tags the output will have on open.
|
||||||
Tags(Vec<TagId>),
|
pub tags: Option<Vec<TagId>>,
|
||||||
/// Set the window to floating or tiled on open.
|
/// Set the window to floating or tiled on open.
|
||||||
FloatingOrTiled(FloatingOrTiled),
|
pub floating_or_tiled: Option<FloatingOrTiled>,
|
||||||
/// Set the window to fullscreen, maximized, or force it to neither.
|
/// Set the window to fullscreen, maximized, or force it to neither.
|
||||||
FullscreenOrMaximized(FullscreenOrMaximized),
|
pub fullscreen_or_maximized: Option<FullscreenOrMaximized>,
|
||||||
/// Set the window's initial size.
|
/// Set the window's initial size.
|
||||||
Size(NonZeroU32, NonZeroU32),
|
pub size: Option<(NonZeroU32, NonZeroU32)>,
|
||||||
/// Set the window's initial location. If the window is tiled, it will snap to this position
|
/// Set the window's initial location. If the window is tiled, it will snap to this position
|
||||||
/// when set to floating.
|
/// when set to floating.
|
||||||
Location(i32, i32),
|
pub location: Option<(i32, i32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: just skip serializing fields on the other FloatingOrTiled
|
// TODO: just skip serializing fields on the other FloatingOrTiled
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use smithay::{
|
use smithay::{
|
||||||
delegate_xdg_shell,
|
delegate_xdg_shell,
|
||||||
desktop::{
|
desktop::{
|
||||||
find_popup_root_surface, layer_map_for_output, PopupKeyboardGrab, PopupKind,
|
find_popup_root_surface, layer_map_for_output, space::SpaceElement, PopupKeyboardGrab,
|
||||||
PopupPointerGrab, PopupUngrabStrategy, Window, WindowSurfaceType,
|
PopupKind, PopupPointerGrab, PopupUngrabStrategy, Window, WindowSurfaceType,
|
||||||
},
|
},
|
||||||
input::{pointer::Focus, Seat},
|
input::{pointer::Focus, Seat},
|
||||||
output::Output,
|
output::Output,
|
||||||
|
@ -13,7 +13,7 @@ use smithay::{
|
||||||
Resource,
|
Resource,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
utils::{Serial, SERIAL_COUNTER},
|
utils::{Point, Rectangle, Serial, SERIAL_COUNTER},
|
||||||
wayland::{
|
wayland::{
|
||||||
compositor::{self, CompositorHandler},
|
compositor::{self, CompositorHandler},
|
||||||
shell::xdg::{
|
shell::xdg::{
|
||||||
|
@ -24,9 +24,13 @@ use smithay::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
api::msg::window_rules::{self, WindowRule},
|
||||||
focus::FocusTarget,
|
focus::FocusTarget,
|
||||||
state::{State, WithState},
|
state::{State, WithState},
|
||||||
window::{window_state::LocationRequestState, WindowElement, BLOCKER_COUNTER},
|
window::{
|
||||||
|
window_state::{FloatingOrTiled, LocationRequestState},
|
||||||
|
WindowElement, BLOCKER_COUNTER,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
impl XdgShellHandler for State {
|
impl XdgShellHandler for State {
|
||||||
|
@ -112,6 +116,104 @@ impl XdgShellHandler for State {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|data| {
|
|data| {
|
||||||
|
for (cond, rules) in data.state.window_rules.iter() {
|
||||||
|
if cond.is_met(&data.state, &window) {
|
||||||
|
for rule in rules {
|
||||||
|
let WindowRule {
|
||||||
|
output,
|
||||||
|
tags,
|
||||||
|
floating_or_tiled,
|
||||||
|
fullscreen_or_maximized,
|
||||||
|
size,
|
||||||
|
location,
|
||||||
|
} = rule;
|
||||||
|
|
||||||
|
if let Some(_output_name) = output {
|
||||||
|
// TODO:
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(tag_ids) = tags {
|
||||||
|
let tags = tag_ids
|
||||||
|
.iter()
|
||||||
|
.filter_map(|tag_id| tag_id.tag(&data.state))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
window.with_state(|state| state.tags = tags.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(floating_or_tiled) = floating_or_tiled {
|
||||||
|
match floating_or_tiled {
|
||||||
|
window_rules::FloatingOrTiled::Floating => {
|
||||||
|
if window
|
||||||
|
.with_state(|state| state.floating_or_tiled.is_tiled())
|
||||||
|
{
|
||||||
|
window.toggle_floating();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window_rules::FloatingOrTiled::Tiled => {
|
||||||
|
if window.with_state(|state| {
|
||||||
|
state.floating_or_tiled.is_floating()
|
||||||
|
}) {
|
||||||
|
window.toggle_floating();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(fs_or_max) = fullscreen_or_maximized {
|
||||||
|
window
|
||||||
|
.with_state(|state| state.fullscreen_or_maximized = *fs_or_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((w, h)) = size {
|
||||||
|
// TODO: tiled vs floating
|
||||||
|
// FIXME: this will map unmapped windows at 0,0
|
||||||
|
let window_loc = data
|
||||||
|
.state
|
||||||
|
.space
|
||||||
|
.element_location(&window)
|
||||||
|
.unwrap_or((0, 0).into());
|
||||||
|
let mut window_size = window.geometry().size;
|
||||||
|
window_size.w = u32::from(*w) as i32;
|
||||||
|
window_size.h = u32::from(*h) as i32;
|
||||||
|
|
||||||
|
// FIXME: this will resize tiled windows
|
||||||
|
window.request_size_change(
|
||||||
|
&mut data.state.space,
|
||||||
|
window_loc,
|
||||||
|
window_size,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(loc) = location {
|
||||||
|
match window.with_state(|state| state.floating_or_tiled) {
|
||||||
|
FloatingOrTiled::Floating(mut rect) => {
|
||||||
|
rect.loc = (*loc).into();
|
||||||
|
window.with_state(|state| {
|
||||||
|
state.floating_or_tiled =
|
||||||
|
FloatingOrTiled::Floating(rect)
|
||||||
|
});
|
||||||
|
data.state.space.map_element(window.clone(), *loc, false);
|
||||||
|
}
|
||||||
|
FloatingOrTiled::Tiled(rect) => {
|
||||||
|
// If the window is tiled, don't set the size. Instead, set
|
||||||
|
// what the size will be when it gets set to floating.
|
||||||
|
let rect = rect.unwrap_or_else(|| {
|
||||||
|
let size = window.geometry().size;
|
||||||
|
Rectangle::from_loc_and_size(Point::from(*loc), size)
|
||||||
|
});
|
||||||
|
|
||||||
|
window.with_state(|state| {
|
||||||
|
state.floating_or_tiled =
|
||||||
|
FloatingOrTiled::Tiled(Some(rect))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(focused_output) = data.state.focus_state.focused_output.clone() {
|
if let Some(focused_output) = data.state.focus_state.focused_output.clone() {
|
||||||
data.state.update_windows(&focused_output);
|
data.state.update_windows(&focused_output);
|
||||||
BLOCKER_COUNTER.store(1, std::sync::atomic::Ordering::SeqCst);
|
BLOCKER_COUNTER.store(1, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
|
21
src/state.rs
21
src/state.rs
|
@ -12,7 +12,10 @@ use std::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{
|
api::{
|
||||||
msg::{CallbackId, ModifierMask, Msg},
|
msg::{
|
||||||
|
window_rules::{WindowRule, WindowRuleCondition},
|
||||||
|
CallbackId, ModifierMask, Msg,
|
||||||
|
},
|
||||||
PinnacleSocketSource, DEFAULT_SOCKET_DIR,
|
PinnacleSocketSource, DEFAULT_SOCKET_DIR,
|
||||||
},
|
},
|
||||||
backend::{udev::Udev, winit::Winit, BackendData},
|
backend::{udev::Udev, winit::Winit, BackendData},
|
||||||
|
@ -123,6 +126,7 @@ pub struct State {
|
||||||
pub dnd_icon: Option<WlSurface>,
|
pub dnd_icon: Option<WlSurface>,
|
||||||
|
|
||||||
pub windows: Vec<WindowElement>,
|
pub windows: Vec<WindowElement>,
|
||||||
|
pub window_rules: Vec<(WindowRuleCondition, Vec<WindowRule>)>,
|
||||||
|
|
||||||
pub async_scheduler: Scheduler<()>,
|
pub async_scheduler: Scheduler<()>,
|
||||||
pub config_process: async_process::Child,
|
pub config_process: async_process::Child,
|
||||||
|
@ -145,10 +149,6 @@ where
|
||||||
for window in windows.iter().filter(|win| win.alive()) {
|
for window in windows.iter().filter(|win| win.alive()) {
|
||||||
if window.with_state(|state| !matches!(state.loc_request_state, LocationRequestState::Idle))
|
if window.with_state(|state| !matches!(state.loc_request_state, LocationRequestState::Idle))
|
||||||
{
|
{
|
||||||
// tracing::debug!(
|
|
||||||
// "window state is {:?}",
|
|
||||||
// window.with_state(|state| state.loc_request_state.clone())
|
|
||||||
// );
|
|
||||||
data.state.loop_handle.insert_idle(|data| {
|
data.state.loop_handle.insert_idle(|data| {
|
||||||
schedule_on_commit(data, windows, on_commit);
|
schedule_on_commit(data, windows, on_commit);
|
||||||
});
|
});
|
||||||
|
@ -370,6 +370,7 @@ impl State {
|
||||||
config_process: config_child_handle,
|
config_process: config_child_handle,
|
||||||
|
|
||||||
windows: vec![],
|
windows: vec![],
|
||||||
|
window_rules: vec![],
|
||||||
output_callback_ids: vec![],
|
output_callback_ids: vec![],
|
||||||
|
|
||||||
xwayland,
|
xwayland,
|
||||||
|
@ -391,7 +392,7 @@ impl State {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schedule something to be done when `condition` returns true.
|
/// Schedule something to be done when `condition` returns true.
|
||||||
fn schedule_inner<F1, F2>(data: &mut CalloopData, condition: F1, run: F2)
|
fn schedule_inner<F1, F2>(data: &mut CalloopData, condition: F1, run: F2)
|
||||||
where
|
where
|
||||||
F1: Fn(&mut CalloopData) -> bool + 'static,
|
F1: Fn(&mut CalloopData) -> bool + 'static,
|
||||||
|
@ -418,6 +419,7 @@ fn get_config_dir() -> PathBuf {
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.to_string()
|
.to_string()
|
||||||
});
|
});
|
||||||
|
|
||||||
PathBuf::from(shellexpand::tilde(&config_dir).to_string())
|
PathBuf::from(shellexpand::tilde(&config_dir).to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -444,6 +446,7 @@ fn start_config(metaconfig: Metaconfig, config_dir: &Path) -> anyhow::Result<Con
|
||||||
shellexpand::full_with_context(
|
shellexpand::full_with_context(
|
||||||
&string,
|
&string,
|
||||||
|| std::env::var("HOME").ok(),
|
|| std::env::var("HOME").ok(),
|
||||||
|
// Expand nonexistent vars to an empty string instead of crashing
|
||||||
|var| Ok::<_, ()>(Some(std::env::var(var).unwrap_or("".to_string()))),
|
|var| Ok::<_, ()>(Some(std::env::var(var).unwrap_or("".to_string()))),
|
||||||
)
|
)
|
||||||
.ok()?
|
.ok()?
|
||||||
|
@ -457,6 +460,8 @@ fn start_config(metaconfig: Metaconfig, config_dir: &Path) -> anyhow::Result<Con
|
||||||
|
|
||||||
tracing::debug!("Config envs are {:?}", envs);
|
tracing::debug!("Config envs are {:?}", envs);
|
||||||
|
|
||||||
|
// Using async_process's Child instead of std::process because I don't have to spawn my own
|
||||||
|
// thread to wait for the child
|
||||||
let child = async_process::Command::new(arg1)
|
let child = async_process::Command::new(arg1)
|
||||||
.args(command)
|
.args(command)
|
||||||
.envs(envs)
|
.envs(envs)
|
||||||
|
@ -494,6 +499,7 @@ impl State {
|
||||||
tracing::debug!("Clearing mouse- and keybinds");
|
tracing::debug!("Clearing mouse- and keybinds");
|
||||||
self.input_state.keybinds.clear();
|
self.input_state.keybinds.clear();
|
||||||
self.input_state.mousebinds.clear();
|
self.input_state.mousebinds.clear();
|
||||||
|
self.window_rules.clear();
|
||||||
|
|
||||||
tracing::debug!("Killing old config");
|
tracing::debug!("Killing old config");
|
||||||
if let Err(err) = self.config_process.kill() {
|
if let Err(err) = self.config_process.kill() {
|
||||||
|
@ -528,6 +534,7 @@ pub struct CalloopData {
|
||||||
pub struct ClientState {
|
pub struct ClientState {
|
||||||
pub compositor_state: CompositorClientState,
|
pub compositor_state: CompositorClientState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientData for ClientState {
|
impl ClientData for ClientState {
|
||||||
fn initialized(&self, _client_id: ClientId) {}
|
fn initialized(&self, _client_id: ClientId) {}
|
||||||
|
|
||||||
|
@ -588,7 +595,7 @@ impl ApiState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait WithState {
|
pub trait WithState {
|
||||||
type State: Default;
|
type State;
|
||||||
fn with_state<F, T>(&self, func: F) -> T
|
fn with_state<F, T>(&self, func: F) -> T
|
||||||
where
|
where
|
||||||
F: FnMut(&mut Self::State) -> T;
|
F: FnMut(&mut Self::State) -> T;
|
||||||
|
|
|
@ -117,6 +117,9 @@ impl State {
|
||||||
let Some(output) = window.output(self) else { return };
|
let Some(output) = window.output(self) else { return };
|
||||||
self.update_windows(&output);
|
self.update_windows(&output);
|
||||||
}
|
}
|
||||||
|
Msg::AddWindowRule { cond, rule } => {
|
||||||
|
self.window_rules.push((cond, rule));
|
||||||
|
}
|
||||||
|
|
||||||
// Tags ----------------------------------------
|
// Tags ----------------------------------------
|
||||||
Msg::ToggleTag { tag_id } => {
|
Msg::ToggleTag { tag_id } => {
|
||||||
|
|
Loading…
Reference in a new issue