mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-28 22:23:47 +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
|
||||
// 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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
21
src/state.rs
21
src/state.rs
|
@ -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;
|
||||
|
|
|
@ -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 } => {
|
||||
|
|
Loading…
Reference in a new issue