Daylight bar chrome + embedded launcher, v0.7.5 pins #5
1 changed files with 93 additions and 1 deletions
94
src/main.rs
94
src/main.rs
|
|
@ -1705,7 +1705,31 @@ impl SimpleComponent for App {
|
||||||
self.panels.hide_all();
|
self.panels.hide_all();
|
||||||
}
|
}
|
||||||
AppInput::OpenLauncher => {
|
AppInput::OpenLauncher => {
|
||||||
self.launcher_entry.grab_focus();
|
// Only the primary instance subscribes to the open command
|
||||||
|
// (`launcher_command::spawn`), but `self.monitor` here is
|
||||||
|
// whichever output was focused ONCE, at this instance's own
|
||||||
|
// `init()` — baked in at process start, not re-resolved on
|
||||||
|
// every keybind press. If the user has since moved focus to
|
||||||
|
// a different monitor, blindly grabbing focus on `self`
|
||||||
|
// would open the capsule on the wrong screen. Re-resolve
|
||||||
|
// the focused monitor now and route to whichever instance
|
||||||
|
// actually owns it.
|
||||||
|
let satellite_names: Vec<&str> =
|
||||||
|
self.satellites.iter().map(|(n, _)| n.as_str()).collect();
|
||||||
|
let focused = primary_hypr_monitor();
|
||||||
|
match resolve_launcher_route(focused.as_deref(), &self.monitor, &satellite_names)
|
||||||
|
{
|
||||||
|
LauncherRoute::Satellite(name) => {
|
||||||
|
// resolve_launcher_route only returns a name present in
|
||||||
|
// satellite_names, so this lookup cannot miss.
|
||||||
|
if let Some((_, ctrl)) = self.satellites.iter().find(|(n, _)| *n == name) {
|
||||||
|
ctrl.sender().emit(AppInput::OpenLauncher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LauncherRoute::Local => {
|
||||||
|
self.launcher_entry.grab_focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2847,6 +2871,35 @@ fn primary_hypr_monitor() -> Option<String> {
|
||||||
.map(|m| m.name.clone())
|
.map(|m| m.name.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Where `AppInput::OpenLauncher` should be actually handled: locally (this
|
||||||
|
/// instance grabs its own capsule's focus), or forwarded to a specific
|
||||||
|
/// satellite instance. Pure decision logic, split out of the `update` match
|
||||||
|
/// arm so it's unit-testable without a live `App`/GTK/Hyprland stack.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
enum LauncherRoute {
|
||||||
|
Local,
|
||||||
|
Satellite(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `focused`: the currently-focused Hyprland monitor, re-queried at
|
||||||
|
/// keybind-fire time (`None` if Hyprland's monitor query failed, e.g.
|
||||||
|
/// screenshot mode). `own`: this instance's own monitor, fixed at `init()`.
|
||||||
|
/// `satellites`: names of monitors this (necessarily primary) instance
|
||||||
|
/// tracks a `Controller<App>` for.
|
||||||
|
///
|
||||||
|
/// Falls back to `Local` whenever forwarding isn't possible or isn't
|
||||||
|
/// needed, so a caller can always make forward progress: no focused
|
||||||
|
/// monitor, the focused monitor is this instance's own, or the focused
|
||||||
|
/// monitor has no tracked satellite yet.
|
||||||
|
fn resolve_launcher_route(focused: Option<&str>, own: &str, satellites: &[&str]) -> LauncherRoute {
|
||||||
|
match focused {
|
||||||
|
Some(name) if name != own && satellites.contains(&name) => {
|
||||||
|
LauncherRoute::Satellite(name.to_string())
|
||||||
|
}
|
||||||
|
_ => LauncherRoute::Local,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn hypr_monitor_names() -> Vec<String> {
|
fn hypr_monitor_names() -> Vec<String> {
|
||||||
hypr_monitors_live()
|
hypr_monitors_live()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
@ -2937,3 +2990,42 @@ fn drop_satellite(satellites: &mut Vec<(String, Controller<App>)>, name: &str) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod launcher_route_tests {
|
||||||
|
use super::{resolve_launcher_route, LauncherRoute};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn focused_monitor_is_own_stays_local() {
|
||||||
|
assert_eq!(
|
||||||
|
resolve_launcher_route(Some("eDP-1"), "eDP-1", &["DVI-I-1"]),
|
||||||
|
LauncherRoute::Local
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn focused_monitor_is_tracked_satellite_forwards() {
|
||||||
|
assert_eq!(
|
||||||
|
resolve_launcher_route(Some("DVI-I-1"), "eDP-1", &["DVI-I-1"]),
|
||||||
|
LauncherRoute::Satellite("DVI-I-1".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn focused_monitor_with_no_tracked_satellite_falls_back_local() {
|
||||||
|
// e.g. reconcile hasn't caught up with a very recent hotplug yet.
|
||||||
|
assert_eq!(
|
||||||
|
resolve_launcher_route(Some("HDMI-A-1"), "eDP-1", &["DVI-I-1"]),
|
||||||
|
LauncherRoute::Local
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_focused_monitor_falls_back_local() {
|
||||||
|
// Hyprland's monitor query failed (screenshot mode, hyprctl missing).
|
||||||
|
assert_eq!(
|
||||||
|
resolve_launcher_route(None, "eDP-1", &["DVI-I-1"]),
|
||||||
|
LauncherRoute::Local
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue