From a18b9856bb5368cff8356cbe56f2d881b44fa7b7 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Sat, 9 Sep 2023 04:01:55 -0500 Subject: [PATCH 1/8] Simplify layout cycling, fix wins not updating on maximize --- api/lua/example_config.lua | 56 ++++------------------ api/lua/tag.lua | 98 ++++++++++++++++++++++++++++++++++++++ src/handlers/xdg_shell.rs | 7 ++- 3 files changed, 114 insertions(+), 47 deletions(-) diff --git a/api/lua/example_config.lua b/api/lua/example_config.lua index 66619c4..68679b3 100644 --- a/api/lua/example_config.lua +++ b/api/lua/example_config.lua @@ -112,8 +112,11 @@ require("pinnacle").setup(function(pinnacle) -- }) end) - ---@type Layout[] - local layouts = { + -- Layout cycling + + -- Create a layout cycler to cycle your tag layouts. This will store which layout each tag has + -- and change to the next or previous one in the array when the respective function is called. + local layout_cycler = tag.layout_cycler({ "MasterStack", "Dwindle", "Spiral", @@ -121,56 +124,17 @@ require("pinnacle").setup(function(pinnacle) "CornerTopRight", "CornerBottomLeft", "CornerBottomRight", - } - local indices = {} + }) - -- Layout cycling - -- Yes, this is overly complicated and yes, I'll cook up a way to make it less so. input.keybind({ mod_key }, keys.space, function() - local tags = output.get_focused():tags() - for _, tg in pairs(tags) do - if tg:active() then - local name = tg:name() - if name == nil then - return - end - tg:set_layout(layouts[indices[name] or 1]) - if indices[name] == nil then - indices[name] = 2 - else - if indices[name] + 1 > #layouts then - indices[name] = 1 - else - indices[name] = indices[name] + 1 - end - end - break - end - end + layout_cycler.next() end) input.keybind({ mod_key, "Shift" }, keys.space, function() - local tags = output.get_focused():tags() - for _, tg in pairs(tags) do - if tg:active() then - local name = tg:name() - if name == nil then - return - end - tg:set_layout(layouts[indices[name] or #layouts]) - if indices[name] == nil then - indices[name] = #layouts - 1 - else - if indices[name] - 1 < 1 then - indices[name] = #layouts - else - indices[name] = indices[name] - 1 - end - end - break - end - end + layout_cycler.prev() end) + -- Tag manipulation + input.keybind({ mod_key }, keys.KEY_1, function() tag.switch_to("1") end) diff --git a/api/lua/tag.lua b/api/lua/tag.lua index a7fdb45..a8b7a5d 100644 --- a/api/lua/tag.lua +++ b/api/lua/tag.lua @@ -472,4 +472,102 @@ function tag_module.output(t) return require("output").get_for_tag(t) end +---@class LayoutCycler +---@field next fun(output: (Output|OutputName)?) Change the first active tag on `output` to its next layout. If `output` is empty, the focused output is used. +---@field prev fun(output: (Output|OutputName)?) Change the first active tag on `output` to its previous layout. If `output` is empty, the focused output is used. + +---Given an array of layouts, this will create two functions; one will cycle forward the layout +---for the provided tag, and one will cycle backward. +---@param layouts Layout[] The available layouts. +---@return LayoutCycler layout_cycler A table with the functions `next` and `prev`, which will cycle layouts for the given tag. +function tag_module.layout_cycler(layouts) + local indices = {} + + -- Return empty functions if layouts is empty + if #layouts == 0 then + return { + next = function(_) end, + prev = function(_) end, + } + end + + return { + ---@param output (Output|OutputName)? + next = function(output) + if type(output) == "string" then + output = require("output").get_by_name(output) + end + + output = output or require("output").get_focused() + + if output == nil then + return + end + + local tags = output:tags() + for _, tg in pairs(tags) do + if tg:active() then + local id = tg:id() + if id == nil then + return + end + + if #layouts == 1 then + indices[id] = 1 + elseif indices[id] == nil then + indices[id] = 2 + else + if indices[id] + 1 > #layouts then + indices[id] = 1 + else + indices[id] = indices[id] + 1 + end + end + + tg:set_layout(layouts[indices[id]]) + break + end + end + end, + + ---@param output (Output|OutputName)? + prev = function(output) + if type(output) == "string" then + output = require("output").get_by_name(output) + end + + output = output or require("output").get_focused() + + if output == nil then + return + end + + local tags = output:tags() + for _, tg in pairs(tags) do + if tg:active() then + local id = tg:id() + if id == nil then + return + end + + if #layouts == 1 then + indices[id] = 1 + elseif indices[id] == nil then + indices[id] = #layouts - 1 + else + if indices[id] - 1 < 1 then + indices[id] = #layouts + else + indices[id] = indices[id] - 1 + end + end + + tg:set_layout(layouts[indices[id]]) + break + end + end + end, + } +end + return tag_module diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs index 64a9741..27cad85 100644 --- a/src/handlers/xdg_shell.rs +++ b/src/handlers/xdg_shell.rs @@ -333,7 +333,9 @@ impl XdgShellHandler for State { if !window.with_state(|state| state.fullscreen_or_maximized.is_maximized()) { window.toggle_maximized(); } - // TODO: might need to update_windows here + + let Some(output) = window.output(self) else { return }; + self.update_windows(&output); } fn unmaximize_request(&mut self, surface: ToplevelSurface) { @@ -344,6 +346,9 @@ impl XdgShellHandler for State { if window.with_state(|state| state.fullscreen_or_maximized.is_maximized()) { window.toggle_maximized(); } + + let Some(output) = window.output(self) else { return }; + self.update_windows(&output); } // fn minimize_request(&mut self, surface: ToplevelSurface) { From 9c3df5ce886152ec2c9420c6a3074ebcc7761673 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Sat, 9 Sep 2023 04:09:28 -0500 Subject: [PATCH 2/8] Fix documentation --- api/lua/tag.lua | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/api/lua/tag.lua b/api/lua/tag.lua index a8b7a5d..ad4736f 100644 --- a/api/lua/tag.lua +++ b/api/lua/tag.lua @@ -168,14 +168,9 @@ end ---local op = output.get_by_name("DP-1") --- ---tag.toggle("1") -- Toggle tag 1 on the focused output ----tag.toggle({ "1" }) -- Same as above --- ----tag.toggle({ "1", "DP-1" }) -- Toggle tag 1 on DP-1 ----tag.toggle({ "1", op }) -- Same as above ---- ---- -- Verbose versions of the two above ----tag.toggle({ name = "1", output = "DP-1" }) ----tag.toggle({ name = "1", output = op }) +---tag.toggle({ name = "1", output = "DP-1" }) -- Toggle tag 1 on "DP-1" +---tag.toggle({ name = "1", output = op }) -- Same as above --- --- -- Using a tag object ---local t = tag.get_by_name("1")[1] -- `t` is the first tag with the name "1" @@ -205,14 +200,9 @@ end ---local op = output.get_by_name("DP-1") --- ---tag.switch_to("1") -- Switch to tag 1 on the focused output ----tag.switch_to({ "1" }) -- Same as above --- ----tag.switch_to({ "1", "DP-1" }) -- Switch to tag 1 on DP-1 ----tag.switch_to({ "1", op }) -- Same as above ---- ---- -- Verbose versions of the two above ----tag.switch_to({ name = "1", output = "DP-1" }) ----tag.switch_to({ name = "1", output = op }) +---tag.switch_to({ name = "1", output = "DP-1" }) -- Switch to tag 1 on "DP-1" +---tag.switch_to({ name = "1", output = op }) -- Same as above --- --- -- Using a tag object ---local t = tag.get_by_name("1")[1] -- `t` is the first tag with the name "1" From 866f9eec5d8b4764013d24b33a48b72918e76950 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Sat, 9 Sep 2023 04:34:17 -0500 Subject: [PATCH 3/8] Fix window render order --- src/backend/udev.rs | 12 ++++++++++-- src/backend/winit.rs | 10 +++++++++- src/input.rs | 1 + src/layout.rs | 10 +++++++++- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/backend/udev.rs b/src/backend/udev.rs index 320cd1e..2022341 100644 --- a/src/backend/udev.rs +++ b/src/backend/udev.rs @@ -72,7 +72,7 @@ use smithay::{ backend::GlobalId, protocol::wl_surface::WlSurface, Display, DisplayHandle, }, }, - utils::{Clock, DeviceFd, Logical, Monotonic, Physical, Point, Rectangle, Transform}, + utils::{Clock, DeviceFd, IsAlive, Logical, Monotonic, Physical, Point, Rectangle, Transform}, wayland::{ dmabuf::{DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufState}, input_method::{InputMethodHandle, InputMethodSeat}, @@ -1332,10 +1332,18 @@ impl State { return; }; + let windows = self + .focus_state + .focus_stack + .iter() + .filter(|win| win.alive()) + .cloned() + .collect::>(); + let result = render_surface( &mut self.cursor_status, &self.space, - &self.windows, + &windows, self.dnd_icon.as_ref(), &self.focus_state.focus_stack, surface, diff --git a/src/backend/winit.rs b/src/backend/winit.rs index d7a2307..d8aa2a5 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -248,9 +248,17 @@ pub fn run_winit() -> anyhow::Result<()> { state.focus_state.fix_up_focus(&mut state.space); + let windows = state + .focus_state + .focus_stack + .iter() + .filter(|win| win.alive()) + .cloned() + .collect::>(); + let output_render_elements = crate::render::generate_render_elements( &state.space, - &state.windows, + &windows, state.pointer_location, &mut state.cursor_status, state.dnd_icon.as_ref(), diff --git a/src/input.rs b/src/input.rs index d7b2482..176f932 100644 --- a/src/input.rs +++ b/src/input.rs @@ -20,6 +20,7 @@ use smithay::{ input::{ keyboard::{keysyms, FilterResult}, pointer::{AxisFrame, ButtonEvent, MotionEvent}, + SeatHandler, }, reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge, utils::{Logical, Point, SERIAL_COUNTER}, diff --git a/src/layout.rs b/src/layout.rs index ec66bd2..933de52 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -166,12 +166,20 @@ impl State { // schedule on all idle self.schedule( move |_dt| { - // tracing::debug!("Waiting for all to be idle"); + tracing::debug!("Waiting for all to be idle"); let all_idle = pending_wins .iter() .filter(|(_, win)| win.alive()) .all(|(_, win)| win.with_state(|state| state.loc_request_state.is_idle())); + let num_not_idle = pending_wins + .iter() + .filter(|(_, win)| win.alive()) + .filter(|(_, win)| !win.with_state(|state| state.loc_request_state.is_idle())) + .count(); + + tracing::debug!("{num_not_idle} not idle"); + all_idle }, move |dt| { From 3b65ba5c1da102ba3426e44ba56fd4e9910cdf4a Mon Sep 17 00:00:00 2001 From: Ottatop Date: Sat, 9 Sep 2023 16:28:58 -0500 Subject: [PATCH 4/8] Send frame callbacks when not rendering --- src/backend/winit.rs | 35 ++++++++++++++++++++++++++++++++--- src/input.rs | 1 - src/layout.rs | 4 ++-- src/state.rs | 4 ++-- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/backend/winit.rs b/src/backend/winit.rs index d8aa2a5..3d898c3 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -12,7 +12,10 @@ use smithay::{ }, winit::{WinitError, WinitEvent, WinitGraphicsBackend}, }, - desktop::{layer_map_for_output, utils::send_frames_surface_tree}, + desktop::{ + layer_map_for_output, + utils::{send_frames_surface_tree, surface_primary_scanout_output}, + }, input::pointer::CursorImageStatus, output::{Output, Subpixel}, reexports::{ @@ -32,7 +35,7 @@ use smithay::{ use crate::{ render::pointer::PointerElement, - state::{take_presentation_feedback, Backend, CalloopData, State}, + state::{take_presentation_feedback, Backend, CalloopData, State, WithState}, }; use super::BackendData; @@ -232,16 +235,42 @@ pub fn run_winit() -> anyhow::Result<()> { pointer_element.set_status(state.cursor_status.clone()); - if state.pause_rendering { + // TODO: make a pending_windows state, when pending_windows increases, + // | pause rendering. + // | If it goes down, push a frame, then repeat until no pending_windows are left. + + let pending_win_count = state + .windows + .iter() + .filter(|win| win.alive()) + .filter(|win| win.with_state(|state| !state.loc_request_state.is_idle())) + .count() as u32; + + if pending_win_count > 0 { + for win in state.windows.iter() { + win.send_frame( + &output, + state.clock.now(), + Some(Duration::ZERO), + surface_primary_scanout_output, + ); + } + state.space.refresh(); state.popup_manager.cleanup(); display .flush_clients() .expect("failed to flush client buffers"); + state.prev_pending_win_count = pending_win_count; + + // TODO: still draw the cursor here + return TimeoutAction::ToDuration(Duration::from_millis(1)); } + state.prev_pending_win_count = pending_win_count; + let Backend::Winit(backend) = &mut state.backend else { unreachable!() }; let full_redraw = &mut backend.full_redraw; *full_redraw = full_redraw.saturating_sub(1); diff --git a/src/input.rs b/src/input.rs index 176f932..d7b2482 100644 --- a/src/input.rs +++ b/src/input.rs @@ -20,7 +20,6 @@ use smithay::{ input::{ keyboard::{keysyms, FilterResult}, pointer::{AxisFrame, ButtonEvent, MotionEvent}, - SeatHandler, }, reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge, utils::{Logical, Point, SERIAL_COUNTER}, diff --git a/src/layout.rs b/src/layout.rs index 933de52..0c1336b 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -161,7 +161,7 @@ impl State { // // This *will* cause everything to freeze for a few frames, but it shouldn't impact // anything meaningfully. - self.pause_rendering = true; + // self.pause_rendering = true; // schedule on all idle self.schedule( @@ -186,7 +186,7 @@ impl State { for (loc, win) in non_pending_wins { dt.state.space.map_element(win, loc, false); } - dt.state.pause_rendering = false; + // dt.state.pause_rendering = false; }, ); } diff --git a/src/state.rs b/src/state.rs index 41ad6fd..30c58ac 100644 --- a/src/state.rs +++ b/src/state.rs @@ -139,7 +139,7 @@ pub struct State { pub xwm: Option, pub xdisplay: Option, - pub pause_rendering: bool, + pub prev_pending_win_count: u32, } impl State { @@ -360,7 +360,7 @@ impl State { xwm: None, xdisplay: None, - pause_rendering: false, + prev_pending_win_count: 0, }) } From 8fd0469b825dd227218d8a1e4f26afc543a7404d Mon Sep 17 00:00:00 2001 From: Ottatop Date: Sat, 9 Sep 2023 16:58:07 -0500 Subject: [PATCH 5/8] Apply rendering fixes to udev --- src/backend/udev.rs | 49 +++++++++++++++++++++++++++++--------------- src/backend/winit.rs | 4 ---- src/state.rs | 4 ---- 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/backend/udev.rs b/src/backend/udev.rs index 2022341..dd49b64 100644 --- a/src/backend/udev.rs +++ b/src/backend/udev.rs @@ -86,7 +86,9 @@ use smithay_drm_extras::{ use crate::{ api::msg::{Args, OutgoingMsg}, render::{pointer::PointerElement, CustomRenderElements}, - state::{take_presentation_feedback, Backend, CalloopData, State, SurfaceDmabufFeedback}, + state::{ + take_presentation_feedback, Backend, CalloopData, State, SurfaceDmabufFeedback, WithState, + }, window::WindowElement, }; @@ -151,14 +153,10 @@ pub fn run_udev() -> anyhow::Result<()> { let mut event_loop = EventLoop::try_new().unwrap(); let mut display = Display::new().unwrap(); - /* - * Initialize session - */ + // Initialize session let (session, notifier) = LibSeatSession::new()?; - /* - * Initialize the compositor - */ + // Initialize the compositor let primary_gpu = if let Ok(var) = std::env::var("ANVIL_DRM_DEVICE") { DrmNode::from_path(var).expect("Invalid drm device path") } else { @@ -202,9 +200,7 @@ pub fn run_udev() -> anyhow::Result<()> { event_loop.handle(), )?; - /* - * Initialize the udev backend - */ + // Initialize the udev backend let udev_backend = UdevBackend::new(state.seat.name())?; // Create DrmNodes from already connected GPUs @@ -245,9 +241,7 @@ pub fn run_udev() -> anyhow::Result<()> { }) .unwrap(); - /* - * Initialize libinput backend - */ + // Initialize libinput backend let mut libinput_context = Libinput::new_with_udev::>( backend.session.clone().into(), ); @@ -256,9 +250,7 @@ pub fn run_udev() -> anyhow::Result<()> { .unwrap(); let libinput_backend = LibinputInputBackend::new(libinput_context.clone()); - /* - * Bind all our objects that get driven by the event loop - */ + // Bind all our objects that get driven by the event loop let insert_ret = event_loop .handle() .insert_source(libinput_backend, move |event, _, data| { @@ -1460,6 +1452,31 @@ fn render_surface<'a>( pointer_location: Point, clock: &Clock, ) -> Result { + let pending_win_count = windows + .iter() + .filter(|win| win.alive()) + .filter(|win| win.with_state(|state| !state.loc_request_state.is_idle())) + .count() as u32; + + tracing::debug!("pending_win_count is {pending_win_count}"); + + if pending_win_count > 0 { + for win in windows.iter() { + win.send_frame(output, clock.now(), Some(Duration::ZERO), |_, _| { + Some(output.clone()) + }); + } + + surface + .compositor + .queue_frame(None, None, None) + .map_err(Into::::into)?; + + // TODO: still draw the cursor here + + return Ok(true); + } + let output_render_elements = crate::render::generate_render_elements( space, windows, diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 3d898c3..c76b944 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -262,15 +262,11 @@ pub fn run_winit() -> anyhow::Result<()> { .flush_clients() .expect("failed to flush client buffers"); - state.prev_pending_win_count = pending_win_count; - // TODO: still draw the cursor here return TimeoutAction::ToDuration(Duration::from_millis(1)); } - state.prev_pending_win_count = pending_win_count; - let Backend::Winit(backend) = &mut state.backend else { unreachable!() }; let full_redraw = &mut backend.full_redraw; *full_redraw = full_redraw.saturating_sub(1); diff --git a/src/state.rs b/src/state.rs index 30c58ac..9c66e6c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -138,8 +138,6 @@ pub struct State { pub xwayland: XWayland, pub xwm: Option, pub xdisplay: Option, - - pub prev_pending_win_count: u32, } impl State { @@ -359,8 +357,6 @@ impl State { xwayland, xwm: None, xdisplay: None, - - prev_pending_win_count: 0, }) } From 89fc2919f3acbf81e2b63c9ab169858f3dc3dcc0 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Sat, 9 Sep 2023 19:27:05 -0500 Subject: [PATCH 6/8] Simplify input API --- Cargo.toml | 1 + api/lua/example_config.lua | 82 ++++++++------------------------------ api/lua/input.lua | 13 +++++- api/lua/msg.lua | 2 +- api/lua/stylua.toml | 2 +- src/api/msg.rs | 8 +++- src/input.rs | 12 ++---- src/state/api_handlers.rs | 24 ++++++++++- 8 files changed, 64 insertions(+), 80 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4f6a041..a5d7e1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ shellexpand = "3.1.0" toml = "0.7.7" anyhow = { version = "1.0.75", features = ["backtrace"] } clap = { version = "4.4.2", features = ["derive"] } +xkbcommon = "0.6.0" [features] default = ["egl", "winit", "udev", "xwayland"] diff --git a/api/lua/example_config.lua b/api/lua/example_config.lua index 68679b3..b9e8b37 100644 --- a/api/lua/example_config.lua +++ b/api/lua/example_config.lua @@ -85,10 +85,12 @@ require("pinnacle").setup(function(pinnacle) -- Tags --------------------------------------------------------------------------- + local tags = { "1", "2", "3", "4", "5" } + output.connect_for_all(function(op) -- Add tags 1, 2, 3, 4 and 5 on all monitors, and toggle tag 1 active by default - op:add_tags("1", "2", "3", "4", "5") + op:add_tags(tags) -- Same as tag.add(op, "1", "2", "3", "4", "5") tag.toggle({ name = "1", output = op }) @@ -135,68 +137,18 @@ require("pinnacle").setup(function(pinnacle) -- Tag manipulation - input.keybind({ mod_key }, keys.KEY_1, function() - tag.switch_to("1") - end) - input.keybind({ mod_key }, keys.KEY_2, function() - tag.switch_to("2") - end) - input.keybind({ mod_key }, keys.KEY_3, function() - tag.switch_to("3") - end) - input.keybind({ mod_key }, keys.KEY_4, function() - tag.switch_to("4") - end) - input.keybind({ mod_key }, keys.KEY_5, function() - tag.switch_to("5") - end) - - input.keybind({ mod_key, "Shift" }, keys.KEY_1, function() - tag.toggle("1") - end) - input.keybind({ mod_key, "Shift" }, keys.KEY_2, function() - tag.toggle("2") - end) - input.keybind({ mod_key, "Shift" }, keys.KEY_3, function() - tag.toggle("3") - end) - input.keybind({ mod_key, "Shift" }, keys.KEY_4, function() - tag.toggle("4") - end) - input.keybind({ mod_key, "Shift" }, keys.KEY_5, function() - tag.toggle("5") - end) - - -- I check for nil this way because I don't want stylua to take up like 80 lines on `if win ~= nil` - input.keybind({ mod_key, "Alt" }, keys.KEY_1, function() - local _ = window.get_focused() and window:get_focused():move_to_tag("1") - end) - input.keybind({ mod_key, "Alt" }, keys.KEY_2, function() - local _ = window.get_focused() and window:get_focused():move_to_tag("2") - end) - input.keybind({ mod_key, "Alt" }, keys.KEY_3, function() - local _ = window.get_focused() and window:get_focused():move_to_tag("3") - end) - input.keybind({ mod_key, "Alt" }, keys.KEY_4, function() - local _ = window.get_focused() and window:get_focused():move_to_tag("4") - end) - input.keybind({ mod_key, "Alt" }, keys.KEY_5, function() - local _ = window.get_focused() and window:get_focused():move_to_tag("5") - end) - - input.keybind({ mod_key, "Shift", "Alt" }, keys.KEY_1, function() - local _ = window.get_focused() and window.get_focused():toggle_tag("1") - end) - input.keybind({ mod_key, "Shift", "Alt" }, keys.KEY_2, function() - local _ = window.get_focused() and window.get_focused():toggle_tag("2") - end) - input.keybind({ mod_key, "Shift", "Alt" }, keys.KEY_3, function() - local _ = window.get_focused() and window.get_focused():toggle_tag("3") - end) - input.keybind({ mod_key, "Shift", "Alt" }, keys.KEY_4, function() - local _ = window.get_focused() and window.get_focused():toggle_tag("4") - end) - input.keybind({ mod_key, "Shift", "Alt" }, keys.KEY_5, function() - local _ = window.get_focused() and window.get_focused():toggle_tag("5") - end) + for _, tag_name in pairs(tags) do + input.keybind({ mod_key }, tag_name, function() + tag.switch_to(tag_name) + end) + input.keybind({ mod_key, "Shift" }, tag_name, function() + tag.toggle(tag_name) + end) + input.keybind({ mod_key, "Alt" }, tag_name, function() + local _ = window.get_focused() and window:get_focused():move_to_tag(tag_name) + end) + input.keybind({ mod_key, "Shift", "Alt" }, tag_name, function() + local _ = window.get_focused() and window.get_focused():toggle_tag(tag_name) + end) + end end) diff --git a/api/lua/input.lua b/api/lua/input.lua index c486eea..820b7f3 100644 --- a/api/lua/input.lua +++ b/api/lua/input.lua @@ -15,15 +15,24 @@ local input_module = { --- process.spawn("Alacritty") ---end) ---``` ----@param key Keys The key for the keybind. +---@param key Keys|string The key for the keybind. ---@param modifiers (Modifier)[] Which modifiers need to be pressed for the keybind to trigger. ---@param action fun() What to do. function input_module.keybind(modifiers, key, action) table.insert(CallbackTable, action) + + local k = {} + + if type(key) == "string" then + k.String = key + else + k.Int = key + end + SendMsg({ SetKeybind = { modifiers = modifiers, - key = key, + key = k, callback_id = #CallbackTable, }, }) diff --git a/api/lua/msg.lua b/api/lua/msg.lua index 66138b5..c3c2ff9 100644 --- a/api/lua/msg.lua +++ b/api/lua/msg.lua @@ -3,7 +3,7 @@ ---@meta _ ---@class _Msg ----@field SetKeybind { key: Keys, modifiers: Modifier[], callback_id: integer }? +---@field SetKeybind { key: { Int: Keys?, String: string? }, modifiers: Modifier[], callback_id: integer }? ---@field SetMousebind { button: integer }? --Windows ---@field CloseWindow { window_id: WindowId }? diff --git a/api/lua/stylua.toml b/api/lua/stylua.toml index 2e04cdc..66ae018 100644 --- a/api/lua/stylua.toml +++ b/api/lua/stylua.toml @@ -1,2 +1,2 @@ indent_type = "Spaces" -column_width = 80 +column_width = 120 diff --git a/src/api/msg.rs b/src/api/msg.rs index 98005c1..75627f5 100644 --- a/src/api/msg.rs +++ b/src/api/msg.rs @@ -17,11 +17,17 @@ use self::window_rules::{WindowRule, WindowRuleCondition}; #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy)] pub struct CallbackId(pub u32); +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] +pub enum KeyIntOrString { + Int(u32), + String(String), +} + #[derive(Debug, serde::Serialize, serde::Deserialize)] pub enum Msg { // Input SetKeybind { - key: u32, + key: KeyIntOrString, modifiers: Vec, callback_id: CallbackId, }, diff --git a/src/input.rs b/src/input.rs index d7b2482..2fc2c30 100644 --- a/src/input.rs +++ b/src/input.rs @@ -183,21 +183,17 @@ impl State { modifier_mask.push(Modifier::Super); } let modifier_mask = ModifierMask::from(modifier_mask); - let raw_sym = if keysym.raw_syms().len() == 1 { - keysym.raw_syms()[0] - } else { - keysyms::KEY_NoSymbol - }; + let sym = keysym.modified_sym(); if let Some(callback_id) = state .input_state .keybinds - .get(&(modifier_mask, raw_sym)) + .get(&(modifier_mask, sym)) { return FilterResult::Intercept(KeyAction::CallCallback(*callback_id)); - } else if (modifier_mask, raw_sym) == kill_keybind { + } else if (modifier_mask, sym) == kill_keybind { return FilterResult::Intercept(KeyAction::Quit); - } else if (modifier_mask, raw_sym) == reload_keybind { + } else if (modifier_mask, sym) == reload_keybind { return FilterResult::Intercept(KeyAction::ReloadConfig); } else if let mut vt @ keysyms::KEY_XF86Switch_VT_1..=keysyms::KEY_XF86Switch_VT_12 = keysym.modified_sym() { diff --git a/src/state/api_handlers.rs b/src/state/api_handlers.rs index 29f9c6b..31ce6df 100644 --- a/src/state/api_handlers.rs +++ b/src/state/api_handlers.rs @@ -9,7 +9,9 @@ use smithay::{ }; use crate::{ - api::msg::{Args, CallbackId, Msg, OutgoingMsg, Request, RequestId, RequestResponse}, + api::msg::{ + Args, CallbackId, KeyIntOrString, Msg, OutgoingMsg, Request, RequestId, RequestResponse, + }, tag::Tag, window::WindowElement, }; @@ -25,7 +27,25 @@ impl State { modifiers, callback_id, } => { - tracing::info!("set keybind: {:?}, {}", modifiers, key); + let key = match key { + KeyIntOrString::Int(num) => num, + KeyIntOrString::String(s) => { + if s.chars().count() == 1 { + let Some(ch) = s.chars().next() else { unreachable!() }; + tracing::info!("set keybind: {:?}, {:?}", modifiers, ch); + xkbcommon::xkb::Keysym::from_char(ch).raw() + } else { + let raw = xkbcommon::xkb::keysym_from_name( + &s, + xkbcommon::xkb::KEYSYM_NO_FLAGS, + ) + .raw(); + tracing::info!("set keybind: {:?}, {:?}", modifiers, raw); + raw + } + } + }; + self.input_state .keybinds .insert((modifiers.into(), key), callback_id); From 465e66067d0f172fdfa40899ffdfe05e82f6410d Mon Sep 17 00:00:00 2001 From: Ottatop Date: Sat, 9 Sep 2023 20:41:21 -0500 Subject: [PATCH 7/8] Update keybind handling and API docs --- api/lua/example_config.lua | 13 ++++++------- api/lua/input.lua | 21 +++++++++++++++++++++ src/input.rs | 27 ++++++++++++++++++++------- src/state/api_handlers.rs | 5 +++-- 4 files changed, 50 insertions(+), 16 deletions(-) diff --git a/api/lua/example_config.lua b/api/lua/example_config.lua index b9e8b37..6999c22 100644 --- a/api/lua/example_config.lua +++ b/api/lua/example_config.lua @@ -18,7 +18,6 @@ require("pinnacle").setup(function(pinnacle) local output = pinnacle.output -- Output management -- Every key supported by xkbcommon. - -- Support for just putting in a string of a key is intended. local keys = input.keys ---@type Modifier @@ -128,25 +127,25 @@ require("pinnacle").setup(function(pinnacle) "CornerBottomRight", }) - input.keybind({ mod_key }, keys.space, function() - layout_cycler.next() - end) - input.keybind({ mod_key, "Shift" }, keys.space, function() - layout_cycler.prev() - end) + input.keybind({ mod_key }, keys.space, layout_cycler.next) + input.keybind({ mod_key, "Shift" }, keys.space, layout_cycler.prev) -- Tag manipulation for _, tag_name in pairs(tags) do + -- mod_key + 1-5 switches tags input.keybind({ mod_key }, tag_name, function() tag.switch_to(tag_name) end) + -- mod_key + Shift + 1-5 toggles tags input.keybind({ mod_key, "Shift" }, tag_name, function() tag.toggle(tag_name) end) + -- mod_key + Alt + 1-5 moves windows to tags input.keybind({ mod_key, "Alt" }, tag_name, function() local _ = window.get_focused() and window:get_focused():move_to_tag(tag_name) end) + -- mod_key + Shift + Alt + 1-5 toggles tags on windows input.keybind({ mod_key, "Shift", "Alt" }, tag_name, function() local _ = window.get_focused() and window.get_focused():toggle_tag(tag_name) end) diff --git a/api/lua/input.lua b/api/lua/input.lua index 820b7f3..b807191 100644 --- a/api/lua/input.lua +++ b/api/lua/input.lua @@ -7,6 +7,27 @@ local input_module = { ---Set a keybind. If called with an already existing keybind, it gets replaced. --- +---You must provide three arguments: +--- +--- - `modifiers`: An array of `Modifier`s. If you don't want any, provide an empty table. +--- - `key`: The key that will trigger `action`. You can provide three types of key: +--- - Something from the `Keys` table in `input.keys`, which lists every xkbcommon key. The naming pattern is the xkbcommon key without the `KEY_` prefix, unless that would make it start with a number or the reserved lua keyword `function`, in which case the `KEY_` prefix is included. +--- - A single character representing your key. This can be something like "g", "$", "~", "1", and so on. +--- - A string of the key's name. This is the name of the xkbcommon key without the `KEY_` prefix. +--- - `action`: The function that will be run when the keybind is pressed. +--- +---It is important to note that `"a"` is different than `"A"`. Similarly, `keys.a` is different than `keys.A`. +---Usually, it's best to use the non-modified key to prevent confusion and unintended behavior. +--- +---```lua +---input.keybind({ "Shift" }, "a", function() end) -- This is preferred +---input.keybind({ "Shift" }, "A", function() end) -- over this +--- +--- -- And in fact, this keybind won't work at all because it expects no modifiers, +--- -- but you can't get "A" without using `Shift`. +---input.keybind({}, "A", function() end) +---``` +--- ---### Example --- ---```lua diff --git a/src/input.rs b/src/input.rs index 2fc2c30..594d7ff 100644 --- a/src/input.rs +++ b/src/input.rs @@ -183,17 +183,30 @@ impl State { modifier_mask.push(Modifier::Super); } let modifier_mask = ModifierMask::from(modifier_mask); - let sym = keysym.modified_sym(); + let raw_sym = keysym.raw_syms().iter().next(); + let mod_sym = keysym.modified_sym(); - if let Some(callback_id) = state + let cb_id_mod = state .input_state .keybinds - .get(&(modifier_mask, sym)) - { - return FilterResult::Intercept(KeyAction::CallCallback(*callback_id)); - } else if (modifier_mask, sym) == kill_keybind { + .get(&(modifier_mask, mod_sym)); + + let cb_id_raw = if let Some(raw_sym) = raw_sym { + state.input_state.keybinds.get(&(modifier_mask, *raw_sym)) + } else { + None + }; + + match (cb_id_mod, cb_id_raw) { + (Some(cb_id), _) | (None, Some(cb_id)) => { + return FilterResult::Intercept(KeyAction::CallCallback(*cb_id)); + } + (None, None) => () + } + + if (modifier_mask, mod_sym) == kill_keybind { return FilterResult::Intercept(KeyAction::Quit); - } else if (modifier_mask, sym) == reload_keybind { + } else if (modifier_mask, mod_sym) == reload_keybind { return FilterResult::Intercept(KeyAction::ReloadConfig); } else if let mut vt @ keysyms::KEY_XF86Switch_VT_1..=keysyms::KEY_XF86Switch_VT_12 = keysym.modified_sym() { diff --git a/src/state/api_handlers.rs b/src/state/api_handlers.rs index 31ce6df..63fe377 100644 --- a/src/state/api_handlers.rs +++ b/src/state/api_handlers.rs @@ -32,8 +32,9 @@ impl State { KeyIntOrString::String(s) => { if s.chars().count() == 1 { let Some(ch) = s.chars().next() else { unreachable!() }; - tracing::info!("set keybind: {:?}, {:?}", modifiers, ch); - xkbcommon::xkb::Keysym::from_char(ch).raw() + let raw = xkbcommon::xkb::Keysym::from_char(ch).raw(); + tracing::info!("set keybind: {:?}, {:?} (raw {})", modifiers, ch, raw); + raw } else { let raw = xkbcommon::xkb::keysym_from_name( &s, From 9d73151ca91b13af91daed1ad5a19eab80bdca91 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Sat, 9 Sep 2023 20:52:52 -0500 Subject: [PATCH 8/8] Update API docs --- api/lua/input.lua | 4 ++++ api/lua/process.lua | 3 +++ api/lua/tag.lua | 11 +++++++++++ 3 files changed, 18 insertions(+) diff --git a/api/lua/input.lua b/api/lua/input.lua index b807191..c4f4e2c 100644 --- a/api/lua/input.lua +++ b/api/lua/input.lua @@ -1,7 +1,11 @@ -- SPDX-License-Identifier: GPL-3.0-or-later +---Input management. +--- +---This module provides utilities to set keybinds. ---@class InputModule local input_module = { + --- A table with every key provided by xkbcommon. keys = require("keys"), } diff --git a/api/lua/process.lua b/api/lua/process.lua index f63b353..89d2017 100644 --- a/api/lua/process.lua +++ b/api/lua/process.lua @@ -2,6 +2,9 @@ ---@diagnostic disable: redefined-local +---Process management. +--- +---This module provides utilities to spawn processes and capture their output. ---@class ProcessModule local process_module = {} diff --git a/api/lua/tag.lua b/api/lua/tag.lua index ad4736f..3dec48f 100644 --- a/api/lua/tag.lua +++ b/api/lua/tag.lua @@ -468,6 +468,17 @@ end ---Given an array of layouts, this will create two functions; one will cycle forward the layout ---for the provided tag, and one will cycle backward. +--- +--- ### Example +---```lua +---local layout_cycler = tag.layout_cycler({ "Dwindle", "Spiral", "MasterStack" }) +--- +---layout_cycler.next() -- Go to the next layout on the first tag of the focused output +---layout_cycler.prev() -- Go to the previous layout on the first tag of the focused output +--- +---layout_cycler.next("DP-1") -- Do the above but on "DP-1" instead +---layout_cycler.prev(output.get_by_name("DP-1")) -- With an output object +---``` ---@param layouts Layout[] The available layouts. ---@return LayoutCycler layout_cycler A table with the functions `next` and `prev`, which will cycle layouts for the given tag. function tag_module.layout_cycler(layouts)