Add server side window rules API

This commit is contained in:
Ottatop 2023-09-05 20:45:29 -05:00
parent d9ce324606
commit 4f8d662dd3
5 changed files with 137 additions and 19 deletions

View file

@ -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
// value is a map of the enum's values
mod window_rules;
pub mod window_rules;
use crate::{
layout::Layout,
@ -12,6 +12,8 @@ use crate::{
window::window_state::{FullscreenOrMaximized, WindowId},
};
use self::window_rules::{WindowRule, WindowRuleCondition};
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy)]
pub struct CallbackId(pub u32);
@ -55,6 +57,10 @@ pub enum Msg {
ToggleMaximized {
window_id: WindowId,
},
AddWindowRule {
cond: WindowRuleCondition,
rule: Vec<WindowRule>,
},
// Tag management
ToggleTag {

View file

@ -83,20 +83,20 @@ impl WindowRuleCondition {
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum WindowRule {
pub struct WindowRule {
/// Set the output the window will open on.
Output(OutputName),
pub output: Option<OutputName>,
/// 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.
FloatingOrTiled(FloatingOrTiled),
pub floating_or_tiled: Option<FloatingOrTiled>,
/// 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.
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
/// when set to floating.
Location(i32, i32),
pub location: Option<(i32, i32)>,
}
// TODO: just skip serializing fields on the other FloatingOrTiled

View file

@ -1,8 +1,8 @@
use smithay::{
delegate_xdg_shell,
desktop::{
find_popup_root_surface, layer_map_for_output, PopupKeyboardGrab, PopupKind,
PopupPointerGrab, PopupUngrabStrategy, Window, WindowSurfaceType,
find_popup_root_surface, layer_map_for_output, space::SpaceElement, PopupKeyboardGrab,
PopupKind, PopupPointerGrab, PopupUngrabStrategy, Window, WindowSurfaceType,
},
input::{pointer::Focus, Seat},
output::Output,
@ -13,7 +13,7 @@ use smithay::{
Resource,
},
},
utils::{Serial, SERIAL_COUNTER},
utils::{Point, Rectangle, Serial, SERIAL_COUNTER},
wayland::{
compositor::{self, CompositorHandler},
shell::xdg::{
@ -24,9 +24,13 @@ use smithay::{
};
use crate::{
api::msg::window_rules::{self, WindowRule},
focus::FocusTarget,
state::{State, WithState},
window::{window_state::LocationRequestState, WindowElement, BLOCKER_COUNTER},
window::{
window_state::{FloatingOrTiled, LocationRequestState},
WindowElement, BLOCKER_COUNTER,
},
};
impl XdgShellHandler for State {
@ -112,6 +116,104 @@ impl XdgShellHandler for State {
}
},
|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() {
data.state.update_windows(&focused_output);
BLOCKER_COUNTER.store(1, std::sync::atomic::Ordering::SeqCst);

View file

@ -12,7 +12,10 @@ use std::{
use crate::{
api::{
msg::{CallbackId, ModifierMask, Msg},
msg::{
window_rules::{WindowRule, WindowRuleCondition},
CallbackId, ModifierMask, Msg,
},
PinnacleSocketSource, DEFAULT_SOCKET_DIR,
},
backend::{udev::Udev, winit::Winit, BackendData},
@ -123,6 +126,7 @@ pub struct State {
pub dnd_icon: Option<WlSurface>,
pub windows: Vec<WindowElement>,
pub window_rules: Vec<(WindowRuleCondition, Vec<WindowRule>)>,
pub async_scheduler: Scheduler<()>,
pub config_process: async_process::Child,
@ -145,10 +149,6 @@ where
for window in windows.iter().filter(|win| win.alive()) {
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| {
schedule_on_commit(data, windows, on_commit);
});
@ -370,6 +370,7 @@ impl State {
config_process: config_child_handle,
windows: vec![],
window_rules: vec![],
output_callback_ids: vec![],
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)
where
F1: Fn(&mut CalloopData) -> bool + 'static,
@ -418,6 +419,7 @@ fn get_config_dir() -> PathBuf {
.to_string_lossy()
.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(
&string,
|| 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()))),
)
.ok()?
@ -457,6 +460,8 @@ fn start_config(metaconfig: Metaconfig, config_dir: &Path) -> anyhow::Result<Con
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)
.args(command)
.envs(envs)
@ -494,6 +499,7 @@ impl State {
tracing::debug!("Clearing mouse- and keybinds");
self.input_state.keybinds.clear();
self.input_state.mousebinds.clear();
self.window_rules.clear();
tracing::debug!("Killing old config");
if let Err(err) = self.config_process.kill() {
@ -528,6 +534,7 @@ pub struct CalloopData {
pub struct ClientState {
pub compositor_state: CompositorClientState,
}
impl ClientData for ClientState {
fn initialized(&self, _client_id: ClientId) {}
@ -588,7 +595,7 @@ impl ApiState {
}
pub trait WithState {
type State: Default;
type State;
fn with_state<F, T>(&self, func: F) -> T
where
F: FnMut(&mut Self::State) -> T;

View file

@ -117,6 +117,9 @@ impl State {
let Some(output) = window.output(self) else { return };
self.update_windows(&output);
}
Msg::AddWindowRule { cond, rule } => {
self.window_rules.push((cond, rule));
}
// Tags ----------------------------------------
Msg::ToggleTag { tag_id } => {