mirror of
https://github.com/moons-14/dotfiles.git
synced 2026-10-06 07:28:12 +09:00
252 lines
9.5 KiB
Diff
252 lines
9.5 KiB
Diff
--- a/src/chooser_cli.rs
|
|
+++ b/src/chooser_cli.rs
|
|
@@ -79,0 +80,4 @@
|
|
+ app_id_filter: None,
|
|
+ initial_cycle: None,
|
|
+ snapshot: false,
|
|
+ cycle_socket: None,
|
|
--- a/src/lib.rs
|
|
+++ b/src/lib.rs
|
|
@@ -33,3 +33,3 @@
|
|
-/// Returns the held lock file (keep it alive), or `None` if another instance owns
|
|
-/// it — sway processes its own keybinding even over our exclusive keyboard grab,
|
|
-/// so re-pressing the bind would otherwise stack overlays.
|
|
+/// Returns the held lock and command socket for the first instance. Later
|
|
+/// invocations forward their requested cycle direction through the socket and
|
|
+/// return `None`, which also supports compositors that keep handling the binding.
|
|
@@ -36 +36,3 @@
|
|
-pub fn acquire_switch_lock() -> Option<std::fs::File> {
|
|
+pub fn acquire_switch_lock(
|
|
+ forward: bool,
|
|
+) -> Option<(std::fs::File, std::os::unix::net::UnixDatagram)> {
|
|
@@ -38,0 +41 @@
|
|
+ let socket_path = dir.join("wlr-switcher.sock");
|
|
@@ -45,2 +49,10 @@
|
|
- flock(&f, FlockOperation::NonBlockingLockExclusive).ok()?;
|
|
- Some(f)
|
|
+ if flock(&f, FlockOperation::NonBlockingLockExclusive).is_err() {
|
|
+ let socket = std::os::unix::net::UnixDatagram::unbound().ok()?;
|
|
+ let command = if forward { b"next" } else { b"prev" };
|
|
+ let _ = socket.send_to(command, socket_path);
|
|
+ return None;
|
|
+ }
|
|
+ let _ = std::fs::remove_file(&socket_path);
|
|
+ let socket = std::os::unix::net::UnixDatagram::bind(socket_path).ok()?;
|
|
+ socket.set_nonblocking(true).ok()?;
|
|
+ Some((f, socket))
|
|
@@ -56 +68,2 @@
|
|
- std::thread::spawn(move || ui::capture_thread(tx));
|
|
+ let snapshot = opts.snapshot;
|
|
+ std::thread::spawn(move || ui::capture_thread(tx, snapshot));
|
|
--- a/src/shell.rs
|
|
+++ b/src/shell.rs
|
|
@@ -617,0 +618,3 @@
|
|
+ Keysym::plus | Keysym::KP_Add => Key::Plus,
|
|
+ Keysym::equal => Key::Equals,
|
|
+ Keysym::minus | Keysym::KP_Subtract => Key::Minus,
|
|
--- a/src/switcher_cli.rs
|
|
+++ b/src/switcher_cli.rs
|
|
@@ -75,0 +76,6 @@
|
|
+ /// Show only windows whose Wayland app-id exactly matches this value.
|
|
+ #[arg(long)]
|
|
+ app_id: Option<String>,
|
|
+ /// Select the previous window when the overview first opens.
|
|
+ #[arg(long)]
|
|
+ reverse: bool,
|
|
@@ -96,2 +102,2 @@
|
|
- let _lock = match acquire_switch_lock() {
|
|
- Some(lock) => lock,
|
|
+ let (_lock, cycle_socket) = match acquire_switch_lock(!cli.reverse) {
|
|
+ Some(instance) => instance,
|
|
@@ -121,0 +128,4 @@
|
|
+ app_id_filter: cli.app_id,
|
|
+ initial_cycle: Some(!cli.reverse),
|
|
+ snapshot: true,
|
|
+ cycle_socket: Some(cycle_socket),
|
|
--- a/src/ui.rs
|
|
+++ b/src/ui.rs
|
|
@@ -8,0 +9 @@
|
|
+use std::path::PathBuf;
|
|
@@ -41,0 +43,32 @@
|
|
+const DEFAULT_OVERVIEW_SCALE: f32 = 0.85;
|
|
+const MIN_OVERVIEW_SCALE: f32 = 0.55;
|
|
+const MAX_OVERVIEW_SCALE: f32 = 1.0;
|
|
+
|
|
+fn overview_scale_path() -> Option<PathBuf> {
|
|
+ if let Some(path) = std::env::var_os("XDG_STATE_HOME") {
|
|
+ return Some(PathBuf::from(path).join("window-overview/scale"));
|
|
+ }
|
|
+ std::env::var_os("HOME")
|
|
+ .map(PathBuf::from)
|
|
+ .map(|path| path.join(".local/state/window-overview/scale"))
|
|
+}
|
|
+
|
|
+fn load_overview_scale() -> f32 {
|
|
+ overview_scale_path()
|
|
+ .and_then(|path| std::fs::read_to_string(path).ok())
|
|
+ .and_then(|value| value.trim().parse::<f32>().ok())
|
|
+ .unwrap_or(DEFAULT_OVERVIEW_SCALE)
|
|
+ .clamp(MIN_OVERVIEW_SCALE, MAX_OVERVIEW_SCALE)
|
|
+}
|
|
+
|
|
+fn save_overview_scale(scale: f32) {
|
|
+ let Some(path) = overview_scale_path() else {
|
|
+ return;
|
|
+ };
|
|
+ let Some(parent) = path.parent() else {
|
|
+ return;
|
|
+ };
|
|
+ if std::fs::create_dir_all(parent).is_ok() {
|
|
+ let _ = std::fs::write(path, format!("{scale:.2}\n"));
|
|
+ }
|
|
+}
|
|
@@ -177 +210 @@
|
|
-pub fn capture_thread(tx: Sender<Msg>) {
|
|
+pub fn capture_thread(tx: Sender<Msg>, snapshot: bool) {
|
|
@@ -189,0 +223 @@
|
|
+ let mut captured: HashSet<String> = HashSet::new();
|
|
@@ -236,0 +271 @@
|
|
+ captured.retain(|key| present.contains(key.as_str()));
|
|
@@ -255 +290 @@
|
|
- if sessions.contains_key(&s.key) {
|
|
+ if sessions.contains_key(&s.key) || (snapshot && captured.contains(&s.key)) {
|
|
@@ -295 +330,3 @@
|
|
- let Some(key) = by_id.get(&id) else { continue };
|
|
+ let Some(key) = by_id.get(&id).cloned() else {
|
|
+ continue;
|
|
+ };
|
|
@@ -313,0 +351,6 @@
|
|
+ if snapshot {
|
|
+ captured.insert(key.clone());
|
|
+ sessions.remove(&key);
|
|
+ by_id.remove(&id);
|
|
+ client.close_session(&id);
|
|
+ }
|
|
@@ -508,0 +552,8 @@
|
|
+ /// Exact Wayland app-id filter, used by taskbar launches.
|
|
+ pub app_id_filter: Option<String>,
|
|
+ /// Initial cycle direction. `None` leaves the first item selected.
|
|
+ pub initial_cycle: Option<bool>,
|
|
+ /// Capture each window once instead of continuously refreshing previews.
|
|
+ pub snapshot: bool,
|
|
+ /// Receives next/previous commands from repeated compositor keybind launches.
|
|
+ pub cycle_socket: Option<std::os::unix::net::UnixDatagram>,
|
|
@@ -533,0 +585,5 @@
|
|
+ app_id_filter: Option<String>,
|
|
+ initial_forward: bool,
|
|
+ overview_scale: f32,
|
|
+ cycle_socket: Option<std::os::unix::net::UnixDatagram>,
|
|
+ queued_cycles: isize,
|
|
@@ -567 +623 @@
|
|
- pending_initial_select: false,
|
|
+ pending_initial_select: opts.initial_cycle.is_some(),
|
|
@@ -571,0 +628,5 @@
|
|
+ app_id_filter: opts.app_id_filter,
|
|
+ initial_forward: opts.initial_cycle.unwrap_or(true),
|
|
+ overview_scale: load_overview_scale(),
|
|
+ cycle_socket: opts.cycle_socket,
|
|
+ queued_cycles: 0,
|
|
@@ -604 +665,4 @@
|
|
- return; // nothing to cycle yet; keep the pending initial jump
|
|
+ self.queued_cycles = self
|
|
+ .queued_cycles
|
|
+ .saturating_add(if forward { 1 } else { -1 });
|
|
+ return;
|
|
@@ -639,0 +704,12 @@
|
|
+ loop {
|
|
+ let command = self.cycle_socket.as_ref().and_then(|socket| {
|
|
+ let mut buf = [0_u8; 8];
|
|
+ socket.recv(&mut buf).ok().map(|len| (buf, len))
|
|
+ });
|
|
+ match command {
|
|
+ Some((buf, len)) if &buf[..len] == b"next" => self.cycle(true),
|
|
+ Some((buf, len)) if &buf[..len] == b"prev" => self.cycle(false),
|
|
+ Some(_) => {}
|
|
+ None => break,
|
|
+ }
|
|
+ }
|
|
@@ -698,0 +775,5 @@
|
|
+ .filter(|s| {
|
|
+ self.app_id_filter
|
|
+ .as_ref()
|
|
+ .is_none_or(|app_id| s.app_id.eq_ignore_ascii_case(app_id))
|
|
+ })
|
|
@@ -713,2 +793,0 @@
|
|
- // Exposé covers the whole screen: dim almost to opaque so the real windows
|
|
- // behind are hidden (a client can't move them; this hides them instead).
|
|
@@ -716 +795 @@
|
|
- c[3] = c[3].max(0.96);
|
|
+ c[3] = c[3].max(0.88);
|
|
@@ -734 +813,5 @@
|
|
- self.selected = if n > 1 { 1 } else { 0 };
|
|
+ self.selected = if n > 1 {
|
|
+ if self.initial_forward { 1 } else { n - 1 }
|
|
+ } else {
|
|
+ 0
|
|
+ };
|
|
@@ -737,0 +821,8 @@
|
|
+ if self.queued_cycles != 0 {
|
|
+ let n = self.visible().len();
|
|
+ if n > 0 {
|
|
+ self.selected = (self.selected as isize + self.queued_cycles)
|
|
+ .rem_euclid(n as isize) as usize;
|
|
+ self.queued_cycles = 0;
|
|
+ }
|
|
+ }
|
|
@@ -745 +836 @@
|
|
- let (esc, next, prev, enter) = ctx.input(|i| {
|
|
+ let (esc, next, prev, enter, zoom_in, zoom_out, wheel_zoom) = ctx.input(|i| {
|
|
@@ -746,0 +838,8 @@
|
|
+ let wheel_zoom = i
|
|
+ .events
|
|
+ .iter()
|
|
+ .filter_map(|event| match event {
|
|
+ egui::Event::MouseWheel { delta, .. } => Some(delta.y),
|
|
+ _ => None,
|
|
+ })
|
|
+ .sum::<f32>();
|
|
@@ -755,0 +855,3 @@
|
|
+ i.key_pressed(egui::Key::Plus) || i.key_pressed(egui::Key::Equals),
|
|
+ i.key_pressed(egui::Key::Minus),
|
|
+ wheel_zoom,
|
|
@@ -771,0 +874,12 @@
|
|
+ if self.view == View::Grid && (zoom_in || zoom_out || wheel_zoom != 0.0) {
|
|
+ let delta = if zoom_in {
|
|
+ 0.05
|
|
+ } else if zoom_out {
|
|
+ -0.05
|
|
+ } else {
|
|
+ wheel_zoom.signum() * 0.05
|
|
+ };
|
|
+ self.overview_scale =
|
|
+ (self.overview_scale + delta).clamp(MIN_OVERVIEW_SCALE, MAX_OVERVIEW_SCALE);
|
|
+ save_overview_scale(self.overview_scale);
|
|
+ }
|
|
@@ -1020 +1134,5 @@
|
|
- let area = ctx.content_rect().shrink(24.0);
|
|
+ let full_area = ctx.content_rect().shrink(24.0);
|
|
+ let area = egui::Rect::from_center_size(
|
|
+ full_area.center(),
|
|
+ full_area.size() * self.overview_scale,
|
|
+ );
|
|
@@ -1048,0 +1167 @@
|
|
+ let selected = *i == self.selected;
|
|
@@ -1062,9 +1181 @@
|
|
- rect.size() * (0.86 + 0.14 * ease),
|
|
- );
|
|
- self.paint_expose_tile(
|
|
- ui,
|
|
- s,
|
|
- scaled,
|
|
- *i == self.selected,
|
|
- resp.hovered(),
|
|
- ease,
|
|
+ rect.size() * (0.86 + 0.14 * ease),
|
|
@@ -1071,0 +1185 @@
|
|
+ self.paint_expose_tile(ui, s, scaled, selected, resp.hovered(), ease);
|
|
@@ -1107 +1229,3 @@
|
|
- let white = egui::Color32::WHITE.gamma_multiply(a);
|
|
+ let white = egui::Color32::WHITE.gamma_multiply(
|
|
+ a * if selected || hovered { 1.0 } else { 0.7 },
|
|
+ );
|