Alt+Tab window switcher (MRU), snap/maximize, panel separators
- window_switcher action: overlay listing windows in most-recently-used order; Tab/arrows cycle, Enter confirms, Esc cancels. Bound to alt+tab with alt+w as fallback for terminals that swallow alt+tab - snap_left/snap_right actions (alt+left/alt+right) tile the focused window to half the content area; dragging a window to the screen edge snaps it too - maximize action (alt+up) toggles maximize/restore, double-click on the title bar does the same; restore returns the pre-snap geometry - Panel: separator between focus dot and buttons, separators between right-docked status widgets, and item widths now use display width (fixes rects for åäö/wide labels) - Integration suite: 23 checks, all passing Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
33
config.toml
33
config.toml
@@ -44,6 +44,39 @@ scope = "global"
|
||||
label = "Ny terminal"
|
||||
action = { type = "spawn_terminal" }
|
||||
|
||||
# Fönsterväxlare (Alt+Tab-overlay i MRU-ordning).
|
||||
# OBS: vissa terminaler/skrivbord fångar alt+tab själva — alt+w är reserv.
|
||||
[[keybind]]
|
||||
key = "alt+tab"
|
||||
scope = "global"
|
||||
label = "Fönsterväxlare"
|
||||
action = { type = "window_switcher" }
|
||||
|
||||
[[keybind]]
|
||||
key = "alt+w"
|
||||
scope = "global"
|
||||
action = { type = "window_switcher" }
|
||||
|
||||
# Fäst fokuserat fönster som vänster/höger halvskärm
|
||||
[[keybind]]
|
||||
key = "alt+left"
|
||||
scope = "global"
|
||||
label = "Fäst vänster"
|
||||
action = { type = "snap_left" }
|
||||
|
||||
[[keybind]]
|
||||
key = "alt+right"
|
||||
scope = "global"
|
||||
label = "Fäst höger"
|
||||
action = { type = "snap_right" }
|
||||
|
||||
# Maximera/återställ fokuserat fönster (även dubbelklick på titelraden)
|
||||
[[keybind]]
|
||||
key = "alt+up"
|
||||
scope = "global"
|
||||
label = "Maximera/återställ"
|
||||
action = { type = "maximize" }
|
||||
|
||||
# Avsluta TUI-WM när skrivbordet är fokuserat
|
||||
[[keybind]]
|
||||
key = "ctrl+q"
|
||||
|
||||
170
src/app.rs
170
src/app.rs
@@ -103,6 +103,12 @@ pub struct App {
|
||||
pub config: Config,
|
||||
/// Aktivt tema (laddas från config.theme, hot-reloadas med config)
|
||||
pub theme: Theme,
|
||||
/// Fokushistorik, senast använda fönstret först (för Alt+Tab)
|
||||
pub focus_history: Vec<usize>,
|
||||
/// Öppen fönsterväxlare (Alt+Tab-overlay)
|
||||
pub switcher: Option<SwitcherState>,
|
||||
/// Senaste titelradsklick (fönster-id, tidpunkt) för dubbelklick
|
||||
last_title_click: Option<(usize, std::time::Instant)>,
|
||||
|
||||
// Layout – uppdateras varje frame
|
||||
pub panel_rects: Vec<Rect>,
|
||||
@@ -174,6 +180,16 @@ pub struct FloatingWindow {
|
||||
/// Totalt antal rader i scrollback-historiken (uppdateras i tick,
|
||||
/// används av scrollbaren).
|
||||
pub scrollback_total: usize,
|
||||
/// Geometri före snap/maximering — används för återställning.
|
||||
pub prev_geom: Option<(i32, i32, u16, u16)>,
|
||||
}
|
||||
|
||||
/// Tillstånd för fönsterväxlaren (Alt+Tab).
|
||||
pub struct SwitcherState {
|
||||
/// Fönster-id i MRU-ordning (senast använda först)
|
||||
pub order: Vec<usize>,
|
||||
/// Markerat index i `order`
|
||||
pub selected: usize,
|
||||
}
|
||||
|
||||
/// En Kitty graphics-sekvens extraherad från PTY-data, redo att vidarebefordras.
|
||||
@@ -464,6 +480,9 @@ impl App {
|
||||
parsed_keybinds,
|
||||
tui_wm_btn_rects: Vec::new(),
|
||||
theme,
|
||||
focus_history: Vec::new(),
|
||||
switcher: None,
|
||||
last_title_click: None,
|
||||
hovered_tui_btn: false,
|
||||
socket_path: None,
|
||||
ipc_out: Vec::new(),
|
||||
@@ -495,7 +514,7 @@ impl App {
|
||||
// Items börjar efter logo (9) + fokus-dot (1) + mellanrum (1) = x+12
|
||||
let mut x = rect.x + 12;
|
||||
for item in &panel.items {
|
||||
let w = item.label.len() as u16 + 2;
|
||||
let w = unicode_width::UnicodeWidthStr::width(item.label.as_str()) as u16 + 2;
|
||||
item_rects.push(Rect::new(x, rect.y, w, 1));
|
||||
x += w + 1;
|
||||
}
|
||||
@@ -617,6 +636,8 @@ impl App {
|
||||
WindowContent::RunDialog { .. } => true,
|
||||
WindowContent::PopupDialog { .. } => true,
|
||||
});
|
||||
let alive_ids: Vec<usize> = self.windows.iter().map(|w| w.id).collect();
|
||||
self.focus_history.retain(|h| alive_ids.contains(h));
|
||||
if let Some(fid) = prev_focused {
|
||||
if !self.windows.iter().any(|w| w.id == fid) {
|
||||
self.focused_id = self.windows.last().map(|w| w.id);
|
||||
@@ -685,6 +706,11 @@ impl App {
|
||||
}
|
||||
|
||||
fn handle_key(&mut self, key: KeyEvent) {
|
||||
// 0a. Fönsterväxlaren fångar alla tangenter medan den är öppen
|
||||
if self.switcher.is_some() {
|
||||
self.handle_switcher_key(key);
|
||||
return;
|
||||
}
|
||||
// 0. Ctrl+Shift+C → kopiera markerad text
|
||||
if key.modifiers.contains(KeyModifiers::CONTROL | KeyModifiers::SHIFT)
|
||||
&& key.code == KeyCode::Char('C')
|
||||
@@ -968,10 +994,22 @@ impl App {
|
||||
}
|
||||
}
|
||||
MouseEventKind::Up(btn) => {
|
||||
// Släpptes ett fönster-drag vid skärmkanten? → snap till halva
|
||||
let was_dragging = self.windows.iter().find(|w| w.dragging.is_some()).map(|w| w.id);
|
||||
for w in &mut self.windows {
|
||||
w.dragging = None;
|
||||
w.resizing = None;
|
||||
}
|
||||
if let Some(id) = was_dragging {
|
||||
let ca = self.content_area;
|
||||
if col <= ca.x {
|
||||
self.focused_id = Some(id);
|
||||
self.snap_focused(true);
|
||||
} else if col >= ca.x + ca.width.saturating_sub(1) {
|
||||
self.focused_id = Some(id);
|
||||
self.snap_focused(false);
|
||||
}
|
||||
}
|
||||
self.forward_mouse_to_focused_terminal(MouseEventKind::Up(btn), col, row);
|
||||
}
|
||||
MouseEventKind::Down(MouseButton::Left) => self.handle_click(col, row),
|
||||
@@ -1136,6 +1174,17 @@ impl App {
|
||||
.map(|w| (w.id, col as i32 - w.x, row as i32 - w.y));
|
||||
if let Some((id, ox, oy)) = title_hit {
|
||||
self.focus_window(id);
|
||||
// Dubbelklick på titelraden → maximera/återställ
|
||||
let now = std::time::Instant::now();
|
||||
let is_double = self
|
||||
.last_title_click
|
||||
.map(|(lid, t)| lid == id && now.duration_since(t).as_millis() < 400)
|
||||
.unwrap_or(false);
|
||||
self.last_title_click = Some((id, now));
|
||||
if is_double {
|
||||
self.toggle_maximize_focused();
|
||||
return;
|
||||
}
|
||||
if let Some(w) = self.windows.iter_mut().find(|w| w.id == id) {
|
||||
w.dragging = Some((ox, oy));
|
||||
}
|
||||
@@ -1294,6 +1343,10 @@ impl App {
|
||||
}
|
||||
MenuAction::SpawnRunDialog => self.spawn_run_dialog(),
|
||||
MenuAction::Submenu { .. } => {}
|
||||
MenuAction::WindowSwitcher => self.switcher_open_or_advance(),
|
||||
MenuAction::SnapLeft => self.snap_focused(true),
|
||||
MenuAction::SnapRight => self.snap_focused(false),
|
||||
MenuAction::Maximize => self.toggle_maximize_focused(),
|
||||
MenuAction::NoOp => {}
|
||||
}
|
||||
}
|
||||
@@ -1324,6 +1377,7 @@ impl App {
|
||||
kitty_gfx_carry: Vec::new(),
|
||||
pending_graphics: Vec::new(),
|
||||
scrollback_total: 0,
|
||||
prev_geom: None,
|
||||
});
|
||||
self.focus_window(id);
|
||||
}
|
||||
@@ -1365,6 +1419,7 @@ impl App {
|
||||
kitty_gfx_carry: Vec::new(),
|
||||
pending_graphics: Vec::new(),
|
||||
scrollback_total: 0,
|
||||
prev_geom: None,
|
||||
});
|
||||
self.focus_window(id);
|
||||
}
|
||||
@@ -1508,6 +1563,7 @@ impl App {
|
||||
kitty_gfx_carry: Vec::new(),
|
||||
pending_graphics: Vec::new(),
|
||||
scrollback_total: 0,
|
||||
prev_geom: None,
|
||||
});
|
||||
self.focus_window(id);
|
||||
}
|
||||
@@ -1516,6 +1572,111 @@ impl App {
|
||||
id
|
||||
}
|
||||
|
||||
/// Öppna fönsterväxlaren, eller stega framåt om den redan är öppen.
|
||||
fn switcher_open_or_advance(&mut self) {
|
||||
if self.windows.is_empty() {
|
||||
return;
|
||||
}
|
||||
match &mut self.switcher {
|
||||
Some(sw) => sw.selected = (sw.selected + 1) % sw.order.len(),
|
||||
None => {
|
||||
// MRU-ordning: historiken först, sedan övriga fönster
|
||||
let mut order: Vec<usize> = self
|
||||
.focus_history
|
||||
.iter()
|
||||
.filter(|id| self.windows.iter().any(|w| w.id == **id))
|
||||
.copied()
|
||||
.collect();
|
||||
for w in self.windows.iter().rev() {
|
||||
if !order.contains(&w.id) {
|
||||
order.push(w.id);
|
||||
}
|
||||
}
|
||||
let selected = if order.len() > 1 { 1 } else { 0 };
|
||||
self.switcher = Some(SwitcherState { order, selected });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_switcher_key(&mut self, key: KeyEvent) {
|
||||
let Some(sw) = &mut self.switcher else { return };
|
||||
let len = sw.order.len().max(1);
|
||||
match key.code {
|
||||
KeyCode::Tab | KeyCode::Down | KeyCode::Right => {
|
||||
sw.selected = (sw.selected + 1) % len;
|
||||
}
|
||||
KeyCode::BackTab | KeyCode::Up | KeyCode::Left => {
|
||||
sw.selected = (sw.selected + len - 1) % len;
|
||||
}
|
||||
KeyCode::Enter | KeyCode::Char(' ') => {
|
||||
let id = sw.order[sw.selected];
|
||||
self.switcher = None;
|
||||
self.focus_window(id);
|
||||
}
|
||||
KeyCode::Esc => {
|
||||
self.switcher = None;
|
||||
}
|
||||
// alt+tab / alt+w igen stegar vidare
|
||||
KeyCode::Char('w') if key.modifiers.contains(KeyModifiers::ALT) => {
|
||||
sw.selected = (sw.selected + 1) % len;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fäst fokuserat fönster mot vänster/höger halva av innehållsytan.
|
||||
fn snap_focused(&mut self, left: bool) {
|
||||
let ca = self.content_area;
|
||||
let Some(id) = self.focused_id else { return };
|
||||
let half_w = ca.width / 2;
|
||||
if let Some(w) = self.windows.iter_mut().find(|w| w.id == id) {
|
||||
if !w.resizable {
|
||||
return;
|
||||
}
|
||||
if w.prev_geom.is_none() {
|
||||
w.prev_geom = Some((w.x, w.y, w.width, w.height));
|
||||
}
|
||||
w.x = if left { ca.x as i32 } else { (ca.x + half_w) as i32 };
|
||||
w.y = ca.y as i32;
|
||||
w.width = if left { half_w } else { ca.width - half_w };
|
||||
w.height = ca.height;
|
||||
}
|
||||
}
|
||||
|
||||
/// Maximera fokuserat fönster till hela innehållsytan, eller
|
||||
/// återställ till geometrin det hade innan snap/maximering.
|
||||
fn toggle_maximize_focused(&mut self) {
|
||||
let ca = self.content_area;
|
||||
let Some(id) = self.focused_id else { return };
|
||||
if let Some(w) = self.windows.iter_mut().find(|w| w.id == id) {
|
||||
if !w.resizable {
|
||||
return;
|
||||
}
|
||||
let is_maximized = w.x == ca.x as i32
|
||||
&& w.y == ca.y as i32
|
||||
&& w.width == ca.width
|
||||
&& w.height == ca.height;
|
||||
if is_maximized {
|
||||
// Återställ till geometrin före snap/maximering
|
||||
if let Some((px, py, pw, ph)) = w.prev_geom.take() {
|
||||
w.x = px;
|
||||
w.y = py;
|
||||
w.width = pw;
|
||||
w.height = ph;
|
||||
}
|
||||
} else {
|
||||
// Behåll ev. redan sparad ursprungsgeometri (t.ex. från snap)
|
||||
if w.prev_geom.is_none() {
|
||||
w.prev_geom = Some((w.x, w.y, w.width, w.height));
|
||||
}
|
||||
w.x = ca.x as i32;
|
||||
w.y = ca.y as i32;
|
||||
w.width = ca.width;
|
||||
w.height = ca.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn close_window(&mut self, id: usize) {
|
||||
self.windows.retain(|w| w.id != id);
|
||||
if self.focused_id == Some(id) {
|
||||
@@ -1529,6 +1690,9 @@ impl App {
|
||||
self.windows.push(w);
|
||||
}
|
||||
self.focused_id = Some(id);
|
||||
// MRU-historik för Alt+Tab
|
||||
self.focus_history.retain(|&h| h != id);
|
||||
self.focus_history.insert(0, id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1878,6 +2042,10 @@ fn action_display(action: &MenuAction) -> &'static str {
|
||||
MenuAction::RunProgram { .. } => "Kör program",
|
||||
MenuAction::Submenu { .. } => "Undermeny",
|
||||
MenuAction::SpawnRunDialog => "Kommandodialog",
|
||||
MenuAction::WindowSwitcher => "Fönsterväxlare",
|
||||
MenuAction::SnapLeft => "Fäst vänster",
|
||||
MenuAction::SnapRight => "Fäst höger",
|
||||
MenuAction::Maximize => "Maximera/återställ",
|
||||
MenuAction::NoOp => "",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +115,14 @@ pub enum MenuAction {
|
||||
},
|
||||
/// Öppnar Tui-run kommandodialog
|
||||
SpawnRunDialog,
|
||||
/// Öppnar fönsterväxlaren (Alt+Tab-overlay, MRU-ordning)
|
||||
WindowSwitcher,
|
||||
/// Fäst fokuserat fönster mot vänster halva
|
||||
SnapLeft,
|
||||
/// Fäst fokuserat fönster mot höger halva
|
||||
SnapRight,
|
||||
/// Maximera/återställ fokuserat fönster
|
||||
Maximize,
|
||||
/// Gör ingenting – används för display-only rader i menyer
|
||||
NoOp,
|
||||
}
|
||||
@@ -160,6 +168,36 @@ fn default_keybinds() -> Vec<KeybindConfig> {
|
||||
action: MenuAction::SpawnTerminal { shell: None },
|
||||
label: Some("Ny terminal".to_string()),
|
||||
},
|
||||
KeybindConfig {
|
||||
key: "alt+tab".to_string(),
|
||||
scope: KeybindScope::Global,
|
||||
action: MenuAction::WindowSwitcher,
|
||||
label: Some("Fönsterväxlare".to_string()),
|
||||
},
|
||||
KeybindConfig {
|
||||
key: "alt+w".to_string(),
|
||||
scope: KeybindScope::Global,
|
||||
action: MenuAction::WindowSwitcher,
|
||||
label: None,
|
||||
},
|
||||
KeybindConfig {
|
||||
key: "alt+left".to_string(),
|
||||
scope: KeybindScope::Global,
|
||||
action: MenuAction::SnapLeft,
|
||||
label: Some("Fäst vänster".to_string()),
|
||||
},
|
||||
KeybindConfig {
|
||||
key: "alt+right".to_string(),
|
||||
scope: KeybindScope::Global,
|
||||
action: MenuAction::SnapRight,
|
||||
label: Some("Fäst höger".to_string()),
|
||||
},
|
||||
KeybindConfig {
|
||||
key: "alt+up".to_string(),
|
||||
scope: KeybindScope::Global,
|
||||
action: MenuAction::Maximize,
|
||||
label: Some("Maximera/återställ".to_string()),
|
||||
},
|
||||
KeybindConfig {
|
||||
key: "ctrl+q".to_string(),
|
||||
scope: KeybindScope::Wm,
|
||||
|
||||
@@ -53,6 +53,74 @@ pub fn render(frame: &mut Frame, app: &App) {
|
||||
if let Some(dd) = &app.dropdown {
|
||||
render_dropdown(frame, dd, &app.theme);
|
||||
}
|
||||
|
||||
// Fönsterväxlaren (Alt+Tab) – ovanpå allt
|
||||
if let Some(sw) = &app.switcher {
|
||||
render_switcher(frame, app, sw);
|
||||
}
|
||||
}
|
||||
|
||||
/// Alt+Tab-overlay: fönsterlista i MRU-ordning, centrerad.
|
||||
fn render_switcher(frame: &mut Frame, app: &App, sw: &crate::app::SwitcherState) {
|
||||
let th = &app.theme;
|
||||
let area = frame.area();
|
||||
let titles: Vec<String> = sw
|
||||
.order
|
||||
.iter()
|
||||
.map(|id| {
|
||||
app.windows
|
||||
.iter()
|
||||
.find(|w| w.id == *id)
|
||||
.map(|w| match &w.content {
|
||||
WindowContent::Terminal { title, .. } => {
|
||||
format!("{} ({}x{})", title, w.width, w.height)
|
||||
}
|
||||
WindowContent::RunDialog { .. } => "Kommandodialog".to_string(),
|
||||
WindowContent::PopupDialog { .. } => "Popup".to_string(),
|
||||
})
|
||||
.unwrap_or_else(|| format!("fönster {}", id))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let inner_w = titles
|
||||
.iter()
|
||||
.map(|t| unicode_width::UnicodeWidthStr::width(t.as_str()))
|
||||
.max()
|
||||
.unwrap_or(10)
|
||||
.clamp(20, area.width.saturating_sub(6) as usize) as u16
|
||||
+ 4;
|
||||
let inner_h = titles.len() as u16;
|
||||
let w = inner_w + 2;
|
||||
let h = inner_h + 2;
|
||||
let x = area.x + area.width.saturating_sub(w) / 2;
|
||||
let y = area.y + area.height.saturating_sub(h) / 2;
|
||||
let rect = Rect::new(x, y, w, h).intersection(area);
|
||||
|
||||
frame.render_widget(Clear, rect);
|
||||
frame.render_widget(
|
||||
Block::default()
|
||||
.title(Span::styled(" Fönster ", th.logo()))
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(th.accent))
|
||||
.style(Style::default().bg(th.menu_bg)),
|
||||
rect,
|
||||
);
|
||||
for (i, title) in titles.iter().enumerate() {
|
||||
let row = Rect::new(rect.x + 1, rect.y + 1 + i as u16, rect.width.saturating_sub(2), 1);
|
||||
if row.y >= rect.y + rect.height.saturating_sub(1) {
|
||||
break;
|
||||
}
|
||||
let style = if i == sw.selected {
|
||||
th.hover_highlight()
|
||||
} else {
|
||||
Style::default().fg(th.text).bg(th.menu_bg)
|
||||
};
|
||||
frame.render_widget(
|
||||
Paragraph::new(Span::styled(format!(" {} ", title), style)),
|
||||
row,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -93,6 +161,12 @@ fn render_panel(
|
||||
Rect::new(rect.x + 10, rect.y, 1, 1),
|
||||
);
|
||||
|
||||
// Separator mellan fokus-punkten och knapparna
|
||||
frame.render_widget(
|
||||
Paragraph::new(Span::styled("│", Style::default().fg(th.hint).bg(th.panel_bg))),
|
||||
Rect::new(rect.x + 11, rect.y, 1, 1).intersection(frame.area()),
|
||||
);
|
||||
|
||||
// Vänsterjusterade knappar
|
||||
for (ii, item) in items.iter().enumerate() {
|
||||
let Some(&ir) = item_rects.get(ii) else { continue };
|
||||
@@ -111,10 +185,20 @@ fn render_panel(
|
||||
// Status-widgets
|
||||
// Höger-dockade renderas från höger kant inåt
|
||||
let mut right_x = rect.x + rect.width;
|
||||
let mut first_right = true;
|
||||
for (si, sw_cfg) in status_widgets.iter().enumerate().rev() {
|
||||
if sw_cfg.align != StatusAlign::Right {
|
||||
continue;
|
||||
}
|
||||
if !first_right {
|
||||
// Separator mellan status-widgets
|
||||
right_x = right_x.saturating_sub(2);
|
||||
frame.render_widget(
|
||||
Paragraph::new(Span::styled(" │", Style::default().fg(th.hint).bg(th.panel_bg))),
|
||||
Rect::new(right_x, rect.y, 2, 1).intersection(frame.area()),
|
||||
);
|
||||
}
|
||||
first_right = false;
|
||||
right_x = right_x.saturating_sub(sw_cfg.width);
|
||||
let text = status_states
|
||||
.get(si)
|
||||
|
||||
@@ -274,6 +274,38 @@ def main():
|
||||
if top_win:
|
||||
app.send({"type": "close_window", "window_id": top_win["id"]})
|
||||
|
||||
# ── 4e. Alt+Tab-växlare + snap/maximera ──────────────────────────
|
||||
# Två fönster finns (mouse-test + top stängdes; spawna ett till)
|
||||
app.send({"type": "spawn_window", "command": "exec cat",
|
||||
"request_id": "w-sw"})
|
||||
sw_open = app.recv_until("window_opened")
|
||||
disp.send(key_event(code="Tab", modifiers="ALT"))
|
||||
text = disp.frame_text(timeout=5.0, contains="Fönster")
|
||||
check("alt+tab öppnar fönsterväxlaren", "Fönster" in text)
|
||||
disp.send(key_event(code="Esc"))
|
||||
time.sleep(0.2)
|
||||
|
||||
# Snap vänster: fokuserat fönster ska bli halva bredden
|
||||
disp.send(key_event(code="Left", modifiers="ALT"))
|
||||
time.sleep(0.3)
|
||||
app.send({"type": "list_windows", "request_id": "lsnap"})
|
||||
wsn = app.recv_until("window_list")
|
||||
snapped = next(w for w in wsn["windows"] if w["id"] == sw_open["id"])
|
||||
# innehållsytan är 100 bred (minus panel högst upp) → halvan ~50
|
||||
check("alt+left fäster fönstret som halvskärm",
|
||||
45 <= snapped["width"] <= 55 and snapped["x"] <= 1,
|
||||
f"geometri: {snapped}")
|
||||
|
||||
# Maximera/återställ: alt+up två gånger ska ge tillbaka snappad storlek
|
||||
disp.send(key_event(code="Up", modifiers="ALT"))
|
||||
time.sleep(0.3)
|
||||
app.send({"type": "list_windows", "request_id": "lmax"})
|
||||
wmx = app.recv_until("window_list")
|
||||
maxed = next(w for w in wmx["windows"] if w["id"] == sw_open["id"])
|
||||
check("alt+up maximerar fönstret", maxed["width"] >= 95,
|
||||
f"geometri: {maxed}")
|
||||
app.send({"type": "close_window", "window_id": sw_open["id"]})
|
||||
|
||||
# ── 5. Popup + popup_result via Enter ───────────────────────────
|
||||
app.send({
|
||||
"type": "spawn_popup", "message": "Integrationstest?",
|
||||
|
||||
Reference in New Issue
Block a user