diff --git a/clients/quadraticpc/fht-compositor.nix b/clients/quadraticpc/fht-compositor.nix new file mode 100644 index 0000000..53eab9d --- /dev/null +++ b/clients/quadraticpc/fht-compositor.nix @@ -0,0 +1,112 @@ +{ inputs, pkgs, ... }: +let + filename = "fht-compositor.toml"; +in +{ + imports = [ inputs.fht-compositor.nixosModules.default ]; + environment = { + systemPackages = with pkgs; [ + grim + slurp + ]; + + etc.${filename}.source = (pkgs.formats.toml { }).generate "config.toml" { + general = { + focus-follows-mouse = true; + inner-gaps = 10; + }; + + decorations = { + border = { + thickness = 4; + radius = 12; + }; + blur.disable = true; + }; + + keybinds = { + Alt-Tab = "focus-next-window"; + Super-f = "fullscreen-focused-window"; + Super-Ctrl-q = "quit"; + Super-Ctrl-r = "reload-config"; + + Print = { + action = "run-command"; + arg = "grim -g \"$(slurp)\" - | gradia"; + }; + + Super-t = { + action = "run-command"; + arg = "ptyxis --new-window"; + }; + Super-e = { + action = "run-command"; + arg = "nautilus"; + }; + + Super-Down = { + action = "change-window-proportion"; + arg = 0.5; + }; + Super-Up = { + action = "change-window-proportion"; + arg = -0.5; + }; + Super-Left = { + action = "change-mwfact"; + arg = -0.1; + }; + Super-Right = { + action = "change-mwfact"; + arg = 0.1; + }; + } + // builtins.listToAttrs ( + builtins.genList (i: { + name = "Super-${toString (i + 1)}"; + value = { + action = "focus-workspace"; + arg = i; + }; + }) 9 + ); + + mousebinds = { + Super-Left = "swap-tile"; + Super-wheeldown = "focus-previous-workspace"; + Super-wheelup = "focus-next-workspace"; + }; + + rules = [ + { + match-app-id = [ ".*" ]; + floating = false; + } + ]; + + outputs = { + HDMI-A-1.position.x = 0; + DP-3.position.x = 1920; + }; + + cursor = { + name = "GoogleDot-Blue"; + size = 24; + }; + }; + }; + + programs.fht-compositor = { + enable = true; + package = + inputs.fht-compositor.packages.${pkgs.stdenv.hostPlatform.system}.default.overrideAttrs + (old: { + nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ pkgs.makeWrapper ]; + + postInstall = (old.postInstall or "") + '' + wrapProgram $out/bin/fht-compositor \ + --add-flags "-c /etc/${filename}" + ''; + }); + }; +} diff --git a/clients/quadraticpc/fht-compositor/default.nix b/clients/quadraticpc/fht-compositor/default.nix deleted file mode 100644 index d084a62..0000000 --- a/clients/quadraticpc/fht-compositor/default.nix +++ /dev/null @@ -1,163 +0,0 @@ -{ - inputs, - pkgs, - lib, - ... -}: -let - filename = "fht-compositor.toml"; - - session-programs = lib.getExe ( - pkgs.writeShellApplication { - name = "session-programs"; - runtimeInputs = with pkgs; [ - procps - util-linux - ]; - text = '' - restart() { - pattern=$1 - shift - pkill -f "$pattern" || true - for _ in $(seq 50); do - pgrep -f "$pattern" >/dev/null || break - sleep 0.1 - done - setsid "$@" >/dev/null 2>&1 , -+ // A modifier key that was pressed on its own, with no other key, button or scroll since. -+ // Keybinds on just a modifier key (e.g. `Super_L`) run when it's released in that state. -+ pub lone_modifier: Option, - // To focus a layer-surface with an OnDemand keyboard exclusivity mode, the user must click - // on the layer surface. Then, when we update the keyboard focus, we check against the clicked - // layer surface -@@ -962,6 +965,7 @@ - clock, - suppressed_keys: HashMap::new(), - repeated_keyaction_timer: None, -+ lone_modifier: None, - seat, - devices: vec![], - seat_state, ---- a/src/input/mod.rs -+++ b/src/input/mod.rs -@@ -244,6 +244,7 @@ - }) - .map(|inhibitor| inhibitor.is_active()) - .unwrap_or(false); -+ let mut lone_modifier_action = None; - let action = keyboard.input( - self, - keycode, -@@ -272,6 +273,20 @@ - return FilterResult::Forward; - } - -+ // Lone modifier taps. The key release is still forwarded to clients, and the -+ // action is run after the input is processed. -+ if let Some(raw) = raw { -+ if key_state == KeyState::Pressed { -+ state.fht.lone_modifier = raw.is_modifier_key().then_some(raw); -+ } else if state.fht.lone_modifier.take() == Some(raw) { -+ lone_modifier_action = find_keyaction( -+ &state.fht.config.keybinds, -+ &ModifiersState::default(), -+ raw, -+ ); -+ } -+ } -+ - // Handle repeating keybinds - // FIXME: handle this properly, this breaks if there are two repeating keybinds - if key_state == KeyState::Released { -@@ -331,6 +346,9 @@ - if let Some((key_pattern, key_action)) = action { - self.process_key_action(key_action, key_pattern); - } -+ if let Some((key_pattern, key_action)) = lone_modifier_action { -+ self.process_key_action(key_action, key_pattern); -+ } - } - - fn on_pointer_motion(&mut self, event: B::PointerMotionEvent) { -@@ -505,6 +523,11 @@ - let state = wl_pointer::ButtonState::from(event.state()); - let pointer = self.fht.pointer.clone(); - -+ if state == wl_pointer::ButtonState::Pressed { -+ // e.g. Super+click shouldn't count as a lone Super tap -+ self.fht.lone_modifier = None; -+ } -+ - if state == wl_pointer::ButtonState::Pressed && !pointer.is_grabbed() { - let pointer_loc = pointer.current_location(); - let focus = self.fht.get_pointer_focus(pointer_loc); -@@ -555,6 +578,9 @@ - } - - fn on_pointer_axis(&mut self, event: B::PointerAxisEvent) { -+ // e.g. Super+scroll shouldn't count as a lone Super tap -+ self.fht.lone_modifier = None; -+ - let horizontal_amount_discrete = event.amount_v120(Axis::Horizontal); - let vertical_amount_discrete = event.amount_v120(Axis::Vertical); - let horizontal_amount = event diff --git a/clients/quadraticpc/fht-compositor/resize-tiled-windows.patch b/clients/quadraticpc/fht-compositor/resize-tiled-windows.patch deleted file mode 100644 index 50c8e84..0000000 --- a/clients/quadraticpc/fht-compositor/resize-tiled-windows.patch +++ /dev/null @@ -1,191 +0,0 @@ ---- a/src/space/workspace.rs -+++ b/src/space/workspace.rs -@@ -59,6 +59,11 @@ - window: Window, - initial_window_geometry: Rectangle, - edges: ResizeEdge, -+ /// The layout values when the resize started, used when resizing a tiled window. -+ initial_mwfact: f64, -+ initial_proportion: f64, -+ /// The sum of the proportions of the other tiles in the window's stack. -+ initial_peers_proportion: f64, - } - - #[derive(Debug)] -@@ -2009,23 +2014,70 @@ - return false; - }; - -- match (window.tiled(), self.current_layout()) { -- (_, WorkspaceLayout::Floating) | (false, _) => (), -- // We only do interactive resizes on floating windows -- (true, _) => return false, -- } -- -+ // Tiled windows are resized by changing the layout (see handle_interactive_resize_motion) - let loc = tile.visual_location(); - let size = window.size(); -+ let initial_proportion = tile.proportion(); -+ let initial_peers_proportion = self -+ .stack_peers(window) -+ .into_iter() -+ .map(|idx| self.tiles[idx].proportion()) -+ .sum(); - self.interactive_resize = Some(InteractiveResize { - window: window.clone(), - initial_window_geometry: Rectangle::new(loc, size), - edges, -+ initial_mwfact: self.mwfact, -+ initial_proportion, -+ initial_peers_proportion, - }); - - true - } - -+ /// Get the indices of the other tiles that share a stack with this window in the current layout, -+ /// IE. that split the stack's length with it according to their proportions. -+ fn stack_peers(&self, window: &Window) -> Vec { -+ // Matches how arrange_tiles orders and splits tiles. -+ let tiled: Vec = (0..self.tiles.len()) -+ .filter(|&idx| { -+ let window = self.tiles[idx].window(); -+ window.tiled() && !window.maximized() -+ }) -+ .collect(); -+ let Some(position) = tiled -+ .iter() -+ .position(|&idx| self.tiles[idx].window() == window) -+ else { -+ return vec![]; -+ }; -+ -+ let nmaster = self.nmaster.min(tiled.len()); -+ let layout = self.current_layout(); -+ let same_stack = |other: usize| match layout { -+ WorkspaceLayout::Tile | WorkspaceLayout::BottomStack => { -+ (position < nmaster) == (other < nmaster) -+ } -+ // The non-master tiles alternate between the left and right stacks -+ WorkspaceLayout::CenteredMaster => { -+ if position < nmaster { -+ other < nmaster -+ } else { -+ other >= nmaster && (other - nmaster) % 2 == (position - nmaster) % 2 -+ } -+ } -+ // The other layouts don't use proportions -+ _ => false, -+ }; -+ -+ tiled -+ .into_iter() -+ .enumerate() -+ .filter(|&(other, _)| other != position && same_stack(other)) -+ .map(|(_, idx)| idx) -+ .collect() -+ } -+ - /// Handle an interactive resize grab motion. - /// - /// `delta` is how much the cursor moved from its initial window location. -@@ -2052,11 +2104,53 @@ - return None; - } - -- match (window.tiled(), self.current_layout()) { -- (_, WorkspaceLayout::Floating) | (false, _) => (), -- // We switched from floating to tiled between the motion events -- // Can happen if the user uses a key action bound to toggle-window-floating -- (true, _) => return None, -+ let layout = self.current_layout(); -+ if window.tiled() && layout != WorkspaceLayout::Floating { -+ // For tiled windows, dragging along the master/stack split moves the split (mwfact) -+ // so it follows the cursor, and dragging across it grows/shrinks the window in its -+ // stack (proportion). Which axis is which depends on the layout. -+ let initial_size = interactive_resize.initial_window_geometry.size; -+ let work_area_size = self.work_area.size; -+ let (split_delta, split_length, stack_delta, stack_length, grow_edge) = match layout { -+ WorkspaceLayout::BottomStack => { -+ (delta.y, work_area_size.h, delta.x, initial_size.w, ResizeEdge::LEFT) -+ } -+ _ => (delta.x, work_area_size.w, delta.y, initial_size.h, ResizeEdge::TOP), -+ }; -+ // Grabbing the top (or left) part of the window grows it in that direction instead -+ let stack_delta = if interactive_resize.edges.intersects(grow_edge) { -+ -stack_delta -+ } else { -+ stack_delta -+ }; -+ -+ let mwfact = -+ interactive_resize.initial_mwfact + f64::from(split_delta) / f64::from(split_length.max(1)); -+ -+ // Solve for the proportion that gives the window exactly the length it was dragged to. -+ // The other tiles in the stack keep their proportions (`peers`) during the drag, and -+ // the window's length is `total * p / (p + peers)`, so `p = len * peers / (total - len)` -+ let initial_proportion = interactive_resize.initial_proportion; -+ let peers = interactive_resize.initial_peers_proportion; -+ // Nothing to resize against if the window is alone in its stack -+ if peers > 0.0 && initial_proportion > 0.0 && stack_length > 0 { -+ let initial_length = f64::from(stack_length); -+ let total_length = initial_length * (initial_proportion + peers) / initial_proportion; -+ // Always leave some room for the window and the other tiles in the stack -+ let min_length = (total_length * 0.05).max(1.0); -+ let peer_count = self.stack_peers(window).len() as f64; -+ let max_length = (total_length - min_length * peer_count.max(1.0)).max(min_length); -+ let length = (initial_length + f64::from(stack_delta)).clamp(min_length, max_length); -+ let proportion = length * peers / (total_length - length); -+ -+ if let Some(tile) = self.tiles.iter_mut().find(|tile| tile.window() == window) { -+ tile.set_proportion(proportion); -+ } -+ } -+ // This also re-arranges the tiles -+ self.set_mwfact(mwfact, false); -+ -+ return Some(true); - } - - let mut new_size = interactive_resize.initial_window_geometry.size; -@@ -2140,6 +2234,24 @@ - return false; - } - -+ // Rescale the proportions in the window's stack so they average out to 1. This keeps the -+ // same layout, but stops the values from drifting to extremes over repeated resizes, -+ // where fixed steps (like change-window-proportion) would barely do anything anymore. -+ if window.tiled() && self.current_layout() != WorkspaceLayout::Floating { -+ let mut stack = self.stack_peers(window); -+ if let Some(idx) = self.tiles.iter().position(|tile| tile.window() == window) { -+ stack.push(idx); -+ } -+ let total: f64 = stack.iter().map(|&idx| self.tiles[idx].proportion()).sum(); -+ if stack.len() > 1 && total > 0.0 { -+ let factor = stack.len() as f64 / total; -+ for idx in stack { -+ let proportion = self.tiles[idx].proportion(); -+ self.tiles[idx].set_proportion(proportion * factor); -+ } -+ } -+ } -+ - true - } - ---- a/src/input/actions.rs -+++ b/src/input/actions.rs -@@ -605,12 +605,8 @@ - } - MouseAction::ResizeTile => { - if let Some(window) = focus.and_then(|focus| focus.window) { -+ // Tiled windows are resized too, by changing the workspace layout - let workspace = self.fht.space.workspace_for_window(&window).unwrap(); -- match (window.tiled(), workspace.current_layout()) { -- (_, WorkspaceLayout::Floating) | (false, _) => (), -- // We only do interactive resizes on floating windows -- (true, _) => return, -- } - - let pointer_loc = self.fht.pointer.current_location(); - let loc = self.fht.space.window_location(&window).unwrap().to_f64(); diff --git a/clients/quadraticpc/fht-compositor/scroll-accumulation.patch b/clients/quadraticpc/fht-compositor/scroll-accumulation.patch deleted file mode 100644 index 9c70f45..0000000 --- a/clients/quadraticpc/fht-compositor/scroll-accumulation.patch +++ /dev/null @@ -1,132 +0,0 @@ ---- a/src/state.rs -+++ b/src/state.rs -@@ -719,6 +719,9 @@ - pub seat: Seat, - pub keyboard: KeyboardHandle, - pub pointer: PointerHandle, -+ // Accumulated high-resolution scroll (in 1/120ths of a wheel notch), horizontal then vertical, -+ // for scroll bindings, so that they trigger once per notch rather than once per event. -+ pub scroll_binding_accumulator: (f64, f64), - pub clock: Clock, - pub suppressed_keys: HashMap, - // We store both the timer and the keysym used to trigger the key action. -@@ -967,6 +970,7 @@ - seat_state, - keyboard, - pointer, -+ scroll_binding_accumulator: (0.0, 0.0), - mapped_layer_surfaces: HashMap::new(), - lock_state: LockState::Unlocked, - focused_on_demand_layer_shell: None, ---- a/src/input/mod.rs -+++ b/src/input/mod.rs -@@ -569,56 +569,41 @@ - let mut handled = false; - - // Check vertical axis bindings using discrete amounts -- if let Some(discrete) = vertical_amount_discrete { -- if discrete > 0.0 { -- let mouse_pattern = fht_compositor_config::MousePattern( -- modifiers, -- fht_compositor_config::MouseInput::Axis( -- fht_compositor_config::MouseAxis::WheelUp, -- ), -- ); -- if let Some(action) = self.fht.config.mousebinds.get(&mouse_pattern).cloned() { -- self.process_mouse_action(0, action, SERIAL_COUNTER.next_serial()); -- handled = true; -- } -- } else if discrete < 0.0 { -- let mouse_pattern = fht_compositor_config::MousePattern( -- modifiers, -- fht_compositor_config::MouseInput::Axis( -- fht_compositor_config::MouseAxis::WheelDown, -- ), -- ); -- if let Some(action) = self.fht.config.mousebinds.get(&mouse_pattern).cloned() { -+ if let Some(discrete) = vertical_amount_discrete.filter(|discrete| *discrete != 0.0) { -+ let mouse_axis = if discrete > 0.0 { -+ fht_compositor_config::MouseAxis::WheelUp -+ } else { -+ fht_compositor_config::MouseAxis::WheelDown -+ }; -+ let mouse_pattern = fht_compositor_config::MousePattern( -+ modifiers, -+ fht_compositor_config::MouseInput::Axis(mouse_axis), -+ ); -+ if let Some(action) = self.fht.config.mousebinds.get(&mouse_pattern).cloned() { -+ handled = true; -+ if accumulate_scroll(&mut self.fht.scroll_binding_accumulator.1, discrete) { - self.process_mouse_action(0, action, SERIAL_COUNTER.next_serial()); -- handled = true; - } - } - } - - // Check horizontal axis bindings using discrete amounts - if !handled { -- if let Some(discrete) = horizontal_amount_discrete { -- if discrete > 0.0 { -- let mouse_pattern = fht_compositor_config::MousePattern( -- modifiers, -- fht_compositor_config::MouseInput::Axis( -- fht_compositor_config::MouseAxis::WheelRight, -- ), -- ); -- if let Some(action) = self.fht.config.mousebinds.get(&mouse_pattern).cloned() { -- self.process_mouse_action(0, action, SERIAL_COUNTER.next_serial()); -- handled = true; -- } -- } else if discrete < 0.0 { -- let mouse_pattern = fht_compositor_config::MousePattern( -- modifiers, -- fht_compositor_config::MouseInput::Axis( -- fht_compositor_config::MouseAxis::WheelLeft, -- ), -- ); -- if let Some(action) = self.fht.config.mousebinds.get(&mouse_pattern).cloned() { -+ if let Some(discrete) = horizontal_amount_discrete.filter(|discrete| *discrete != 0.0) -+ { -+ let mouse_axis = if discrete > 0.0 { -+ fht_compositor_config::MouseAxis::WheelRight -+ } else { -+ fht_compositor_config::MouseAxis::WheelLeft -+ }; -+ let mouse_pattern = fht_compositor_config::MousePattern( -+ modifiers, -+ fht_compositor_config::MouseInput::Axis(mouse_axis), -+ ); -+ if let Some(action) = self.fht.config.mousebinds.get(&mouse_pattern).cloned() { -+ handled = true; -+ if accumulate_scroll(&mut self.fht.scroll_binding_accumulator.0, discrete) { - self.process_mouse_action(0, action, SERIAL_COUNTER.next_serial()); -- handled = true; - } - } - } -@@ -1080,6 +1065,25 @@ - } - - /// Returns `true` if a VT switch key has been handled. -+/// Accumulate high-resolution scroll for a scroll binding. -+/// -+/// High-resolution wheels send several events per notch, each with part of the 120 units of a -+/// notch, so we accumulate them and only trigger the binding once per full notch. Returns whether -+/// the binding should trigger. -+fn accumulate_scroll(accumulated: &mut f64, v120: f64) -> bool { -+ if accumulated.signum() != v120.signum() { -+ // Changed direction, start over -+ *accumulated = 0.0; -+ } -+ *accumulated += v120; -+ if accumulated.abs() >= 120.0 { -+ *accumulated -= 120.0 * accumulated.signum(); -+ true -+ } else { -+ false -+ } -+} -+ - fn handle_vt_switch(backend: &mut Backend, key_state: KeyState, modified: Keysym) -> bool { - if key_state == KeyState::Released { - // We only do VT switch on presses. diff --git a/flake.lock b/flake.lock index d048959..7601d55 100644 --- a/flake.lock +++ b/flake.lock @@ -306,11 +306,11 @@ "rust-overlay": [] }, "locked": { - "lastModified": 1792507568, - "narHash": "sha256-RGXq8SkxpxU0TaMk3oXbJ5SwkRDY90tvN2zIiofAVMQ=", + "lastModified": 1791704129, + "narHash": "sha256-rSK4/dNK6AOEsQ4EozZgv07fTyi4q4RF9KoXIAlwcz0=", "owner": "nferhat", "repo": "fht-compositor", - "rev": "458dc48d96aa6d8aa0b834a8052f33c09700ffe8", + "rev": "fc0243b7468d6ee16171c2f0ba19e1b6ddddd1a0", "type": "github" }, "original": { @@ -656,26 +656,6 @@ "type": "github" } }, - "gaypanel": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1791581716, - "narHash": "sha256-XlLNC+8X7arvQHc0n3cJQfm11THhV8zCH8QofcQDxEE=", - "ref": "refs/heads/main", - "rev": "acea989ace01a2408d51a27e030ac15df148d08c", - "revCount": 298, - "type": "git", - "url": "https://codeberg.org/pastthepixels/gaypanel" - }, - "original": { - "type": "git", - "url": "https://codeberg.org/pastthepixels/gaypanel" - } - }, "git-hooks": { "inputs": { "flake-compat": "flake-compat_3", @@ -1109,7 +1089,6 @@ "fht-compositor": "fht-compositor", "firefox-gnome-theme": "firefox-gnome-theme", "flake-parts": "flake-parts_5", - "gaypanel": "gaypanel", "home-manager": "home-manager", "lasuite-docs-proxy": "lasuite-docs-proxy", "lk-jwt-service-module": "lk-jwt-service-module", diff --git a/flake.nix b/flake.nix index ad9c2e8..35f2ac2 100755 --- a/flake.nix +++ b/flake.nix @@ -71,10 +71,6 @@ rust-overlay.follows = ""; }; }; - gaypanel = { - url = "git+https://codeberg.org/pastthepixels/gaypanel"; - inputs.nixpkgs.follows = "nixpkgs"; - }; firefox-gnome-theme = { url = "github:rafaelmardojai/firefox-gnome-theme"; flake = false; diff --git a/wrappers/desktop/gaypanel/default.nix b/wrappers/desktop/gaypanel/default.nix deleted file mode 100644 index e8b3e7c..0000000 --- a/wrappers/desktop/gaypanel/default.nix +++ /dev/null @@ -1,89 +0,0 @@ -{ - inputs, - pkgs, - ... -}: -let - bar = pkgs.writeText "top.blp" '' - using Gtk 4.0; - using Adw 1; - - $GayPanel panel { - anchor-direction: top; - monitor: "all"; - - CenterBox { - margin-start: 4; - margin-end: 4; - margin-top: 4; - margin-bottom: 4; - - [start] - $WorkspacesWidget { - show-labels: false; - } - - [center] - $ClockWidget { - styles [ - "pill", - "flat", - ] - - format: "%b %d %H:%M"; - } - - [end] - $SystemControlsWidget { - styles [ - "pill", - "flat", - ] - } - } - } - ''; - - # GNOME-style workspace dots, with the active one stretched into a pill - style = pkgs.writeText "style.css" '' - .workspaces button { - min-width: 8px; - min-height: 8px; - padding: 0; - border-radius: 99px; - background: alpha(var(--window-fg-color), 0.4); - transition: min-width 0.2s ease, background 0.2s ease; - } - - .workspaces button.selected { - min-width: 32px; - padding: 0; - background: var(--window-fg-color); - } - - .workspaces button label { - font-size: 0; - } - ''; -in -{ - wrappers.gaypanel = { - basePackage = - inputs.gaypanel.packages.${pkgs.stdenv.hostPlatform.system}.default.overrideAttrs - (old: { - patches = (old.patches or [ ]) ++ [ - ./no-sound-panic.patch - ]; - }); - - prependFlags = [ - "--config" - # Copied rather than linked, since gaypanel ignores symlinks in bars/ - "${pkgs.runCommand "gaypanel-config" { } '' - mkdir -p $out/bars - cp ${bar} $out/bars/top.blp - cp ${style} $out/style.css - ''}" - ]; - }; -} diff --git a/wrappers/desktop/gaypanel/no-sound-panic.patch b/wrappers/desktop/gaypanel/no-sound-panic.patch deleted file mode 100644 index 8092b74..0000000 --- a/wrappers/desktop/gaypanel/no-sound-panic.patch +++ /dev/null @@ -1,104 +0,0 @@ ---- a/src/system/sound.rs -+++ b/src/system/sound.rs -@@ -6,25 +6,21 @@ - - use crate::{senders, system::SystemModule}; - --/// Gets the default mixer. Throws an error if it doesn't exist (this means you probably don't have ALSA installed) -+/// Gets the default mixer. Returns None if it doesn't exist (yet), e.g. if the sound server hasn't started - macro_rules! mixer { - () => { -- Mixer::new("default", false).expect("[fatal] alsa: Could not find a mixer named \"default\". gaypanel assumes this is the name of the default mixer. \n[fatal] alsa: **If you have ALSA installed and running and you're *sure* of it, open an issue at codeberg.org/pastthepixels/gaypanel!**") -+ Mixer::new("default", false).ok() - }; - } - - /// Finds the first selem with a playback volume in a mixer. Assumes this is the "default" - /// e.g. this reflects the actual output volume. I don't know how alsa works. --/// You must have a speaker of some kind or else this'll crap out -+/// Returns None if there's no speaker of some kind (yet) - macro_rules! sink { - ($m: expr) => { - $m.iter() - .flat_map(|e| Selem::new(e)) - .find(|s| s.has_playback_volume()) -- .expect(&format!( -- "[fatal] alsa: Could not find default sink for mixer {:?}", -- $m -- )) - }; - } - -@@ -54,9 +50,9 @@ - senders!(VolumeModule); - - fn get() -> Option { -- let mixer = mixer!(); -+ let mixer = mixer!()?; - // Sink stats -- let sink = sink!(mixer); -+ let sink = sink!(mixer)?; - let vol = { - let max = sink.get_playback_volume_range().1 as f64; - let raw_vol = sink -@@ -101,8 +97,12 @@ - - fn set(val: Self::ReturnType) { - // Sets sink volume -- let mixer = mixer!(); -- let sink = sink!(mixer); -+ let Some(mixer) = mixer!() else { -+ return; -+ }; -+ let Some(sink) = sink!(mixer) else { -+ return; -+ }; - let max = sink.get_playback_volume_range().1 as f64; - sink.set_playback_volume_all((val.0.volume * max) as i64) - .unwrap(); -@@ -118,8 +118,17 @@ - - fn listen() { - std::thread::spawn(|| { -- let mixer = mixer!(); -- let sink = sink!(mixer); -+ // Wait for the sound server to provide a mixer with a sink -+ let mixer = loop { -+ if let Some(mixer) = mixer!() { -+ let has_sink = sink!(mixer).is_some(); -+ if has_sink { -+ break mixer; -+ } -+ } -+ std::thread::sleep(std::time::Duration::from_secs(1)); -+ }; -+ let sink = sink!(mixer).unwrap(); - let source = source!(mixer); - let mut pollfds = mixer.get().unwrap(); - ---- a/src/widgets/controls.rs -+++ b/src/widgets/controls.rs -@@ -400,7 +400,10 @@ - gtk::EventControllerScroll::new(gtk::EventControllerScrollFlags::VERTICAL); - controller.connect_scroll(|_, _, b| { - glib::spawn_future_local(async move { -- let sound = (system::sound::VolumeModule::get().unwrap().0.volume -+ let Some(stats) = system::sound::VolumeModule::get() else { -+ return; -+ }; -+ let sound = (stats.0.volume - - b.signum() * VOLUME_SCROLL_STEP) - .clamp(0., 1.); - system::sound::VolumeModule::set(( -@@ -420,7 +423,10 @@ - // Setup volume - let this = glib::clone::Downgrade::downgrade(&self); - let rx = system::sound::VolumeModule::rx(); -- self.update_sound(system::sound::VolumeModule::get().unwrap()); -+ // The listener sends an update once sound is available, so it's fine to skip this -+ if let Some(stats) = system::sound::VolumeModule::get() { -+ self.update_sound(stats); -+ } - glib::spawn_future_local(async move { - while let Ok(stats) = rx.recv().await { - if let Some(this) = this.upgrade() { diff --git a/wrappers/desktop/walker.nix b/wrappers/desktop/walker.nix deleted file mode 100644 index 8dce699..0000000 --- a/wrappers/desktop/walker.nix +++ /dev/null @@ -1,8 +0,0 @@ -{ pkgs, ... }: -{ - wrappers.walker = { - basePackage = pkgs.walker; - # Walker refuses to run unless elephant is in PATH - pathAdd = [ pkgs.elephant ]; - }; -}