Skip to content

Commit 7b800f2

Browse files
authored
Fix mini-mode maximizing issue on Wayland WMs
1 parent 26986cb commit 7b800f2

File tree

1 file changed

+181
-14
lines changed

1 file changed

+181
-14
lines changed

src/tauon/t_modules/t_main.py

Lines changed: 181 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5811,6 +5811,9 @@ def __init__(self, holder: Holder, bag: Bag, gui: GuiVar) -> None:
58115811
self.overlay_texture_texture = bag.overlay_texture_texture
58125812
self.de_notify_support: bool = bag.de_notify_support
58135813
self.old_window_position: tuple[int, int] = bag.old_window_position
5814+
self.mini_mode_wm_forced_floating: bool = False
5815+
self.mini_mode_wm_was_floating: bool = False
5816+
self._wayland_wm_ipc_warned: bool = False
58145817
self.cache_directory: Path = bag.dirs.cache_directory
58155818
self.config_directory: Path = bag.dirs.config_directory
58165819
self.user_directory: Path = bag.dirs.user_directory
@@ -12322,6 +12325,152 @@ def clear_gen_ask(self, id: int) -> None:
1232212325
self.gui.message_box_confirm_reference = (id,)
1232312326
self.show_message(_("You added tracks to a generator playlist. Do you want to clear the generator?"), mode="confirm")
1232412327

12328+
def _window_is_maximized(self) -> bool:
12329+
flags = sdl3.SDL_GetWindowFlags(self.t_window)
12330+
return bool(flags & sdl3.SDL_WINDOW_MAXIMIZED)
12331+
12332+
def _is_wayland_standalone_wm(self) -> bool:
12333+
if not self.wayland:
12334+
return False
12335+
desktop = (self.desktop or "").lower()
12336+
de_names = ("gnome", "kde", "plasma", "xfce", "cinnamon", "mate", "unity", "lxqt", "lxde", "budgie", "pantheon", "cosmic")
12337+
return not any(name in desktop for name in de_names)
12338+
12339+
def _get_wayland_wm_controller(self) -> Literal["sway", "hyprland"] | None:
12340+
if os.environ.get("SWAYSOCK"):
12341+
return "sway"
12342+
if os.environ.get("HYPRLAND_INSTANCE_SIGNATURE"):
12343+
return "hyprland"
12344+
return None
12345+
12346+
def _run_wm_command(self, cmd: list[str]) -> str | None:
12347+
binary = cmd[0]
12348+
if shutil.which(binary) is None:
12349+
if not self._wayland_wm_ipc_warned:
12350+
logging.warning("Wayland mini-mode WM integration unavailable: missing %s", binary)
12351+
self._wayland_wm_ipc_warned = True
12352+
return None
12353+
try:
12354+
result = subprocess.run(cmd, capture_output=True, text=True, check=False, timeout=0.8)
12355+
except Exception:
12356+
return None
12357+
if result.returncode != 0:
12358+
return None
12359+
return result.stdout
12360+
12361+
def _reset_mini_mode_wm_tracking(self) -> None:
12362+
self.mini_mode_wm_forced_floating = False
12363+
self.mini_mode_wm_was_floating = False
12364+
12365+
def _get_sway_focused_floating_state(self) -> bool | None:
12366+
tree_raw = self._run_wm_command(["swaymsg", "-t", "get_tree", "-r"])
12367+
if not tree_raw:
12368+
return None
12369+
try:
12370+
tree = json.loads(tree_raw)
12371+
except json.JSONDecodeError:
12372+
return None
12373+
12374+
stack = [tree]
12375+
while stack:
12376+
node = stack.pop()
12377+
if node.get("focused"):
12378+
fstate = node.get("floating")
12379+
return fstate in ("user_on", "auto_on")
12380+
stack.extend(node.get("nodes", []))
12381+
stack.extend(node.get("floating_nodes", []))
12382+
return None
12383+
12384+
def _get_hyprland_active_window(self) -> dict[str, object] | None:
12385+
active_raw = self._run_wm_command(["hyprctl", "-j", "activewindow"])
12386+
if not active_raw:
12387+
return None
12388+
try:
12389+
active = json.loads(active_raw)
12390+
except json.JSONDecodeError:
12391+
return None
12392+
if not isinstance(active, dict):
12393+
return None
12394+
return active
12395+
12396+
12397+
def _wayland_window_is_floating(self) -> bool | None:
12398+
if not self._is_wayland_standalone_wm():
12399+
return None
12400+
12401+
wm_controller = self._get_wayland_wm_controller()
12402+
if wm_controller == "sway":
12403+
return self._get_sway_focused_floating_state()
12404+
12405+
if wm_controller == "hyprland":
12406+
active = self._get_hyprland_active_window()
12407+
if active is None:
12408+
return None
12409+
return bool(active.get("floating", False))
12410+
12411+
return None
12412+
12413+
def _set_wayland_mini_mode_window_state(self, entering: bool, width: int = 0, height: int = 0, reset_tracking: bool = True) -> None:
12414+
if not self._is_wayland_standalone_wm():
12415+
return
12416+
if entering and reset_tracking:
12417+
self._reset_mini_mode_wm_tracking()
12418+
12419+
wm_controller = self._get_wayland_wm_controller()
12420+
if wm_controller is None:
12421+
if not entering:
12422+
self._reset_mini_mode_wm_tracking()
12423+
return
12424+
12425+
if wm_controller == "sway":
12426+
if entering:
12427+
if reset_tracking:
12428+
floating_state = self._get_sway_focused_floating_state()
12429+
if floating_state is not None:
12430+
self.mini_mode_wm_was_floating = floating_state
12431+
12432+
if not self.mini_mode_wm_was_floating:
12433+
self._run_wm_command(["swaymsg", "floating", "enable"])
12434+
self.mini_mode_wm_forced_floating = True
12435+
elif self.mini_mode_wm_forced_floating:
12436+
self._run_wm_command(["swaymsg", "floating", "enable"])
12437+
12438+
if width > 0 and height > 0:
12439+
self._run_wm_command(["swaymsg", "resize", "set", "width", f"{width}px", "height", f"{height}px"])
12440+
else:
12441+
if self.mini_mode_wm_forced_floating and not self.mini_mode_wm_was_floating:
12442+
self._run_wm_command(["swaymsg", "floating", "disable"])
12443+
self._reset_mini_mode_wm_tracking()
12444+
return
12445+
12446+
if wm_controller == "hyprland":
12447+
active = self._get_hyprland_active_window()
12448+
if active is None:
12449+
if not entering:
12450+
self._reset_mini_mode_wm_tracking()
12451+
return
12452+
address = str(active.get("address", ""))
12453+
if not address:
12454+
if not entering:
12455+
self._reset_mini_mode_wm_tracking()
12456+
return
12457+
12458+
if entering:
12459+
if reset_tracking:
12460+
self.mini_mode_wm_was_floating = bool(active.get("floating", False))
12461+
if not self.mini_mode_wm_was_floating:
12462+
self._run_wm_command(["hyprctl", "dispatch", "togglefloating", f"address:{address}"])
12463+
self.mini_mode_wm_forced_floating = True
12464+
elif self.mini_mode_wm_forced_floating and not bool(active.get("floating", False)):
12465+
self._run_wm_command(["hyprctl", "dispatch", "togglefloating", f"address:{address}"])
12466+
12467+
if width > 0 and height > 0:
12468+
self._run_wm_command(["hyprctl", "dispatch", "resizeactive", "exact", str(width), str(height)])
12469+
else:
12470+
if self.mini_mode_wm_forced_floating and not self.mini_mode_wm_was_floating:
12471+
self._run_wm_command(["hyprctl", "dispatch", "togglefloating", f"address:{address}"])
12472+
self._reset_mini_mode_wm_tracking()
12473+
1232512474
def set_mini_mode(self) -> None:
1232612475
if self.gui.fullscreen:
1232712476
return
@@ -12330,14 +12479,23 @@ def set_mini_mode(self) -> None:
1233012479
self.inp.mouse_up = False
1233112480
self.inp.mouse_click = False
1233212481

12333-
if self.gui.maximized:
12482+
is_wayland_standalone_wm = self._is_wayland_standalone_wm()
12483+
is_floating_wayland_window = self._wayland_window_is_floating() if is_wayland_standalone_wm else None
12484+
12485+
# Standalone Wayland WMs can report maximized flags while window is already floating.
12486+
# Treat already-floating windows as non-maximized for mini-mode transitions.
12487+
self.gui.mini_mode_return_maximized = self._window_is_maximized() and not bool(is_floating_wayland_window)
12488+
if self.gui.mini_mode_return_maximized:
1233412489
sdl3.SDL_RestoreWindow(self.t_window)
12490+
sdl3.SDL_SyncWindow(self.t_window)
12491+
sdl3.SDL_PumpEvents()
12492+
self.gui.maximized = False
1233512493
self.update_layout_do()
1233612494

1233712495
if self.gui.mode == GuiMode.MAIN:
1233812496
self.old_window_position = get_window_position(self.t_window)
1233912497

12340-
if self.prefs.mini_mode_on_top:
12498+
if self.prefs.mini_mode_on_top and not self.wayland:
1234112499
sdl3.SDL_SetWindowAlwaysOnTop(self.t_window, True)
1234212500

1234312501
self.gui.mode = GuiMode.MINI
@@ -12352,7 +12510,8 @@ def set_mini_mode(self) -> None:
1235212510
self.gui.save_position = (i_x.contents.value, i_y.contents.value)
1235312511

1235412512
self.mini_mode.was_borderless = self.draw_border
12355-
sdl3.SDL_SetWindowBordered(self.t_window, False)
12513+
if not is_wayland_standalone_wm:
12514+
sdl3.SDL_SetWindowBordered(self.t_window, False)
1235612515

1235712516
size = (350, 429)
1235812517
if self.prefs.mini_mode_mode == MiniModeMode.MINI:
@@ -12375,9 +12534,11 @@ def set_mini_mode(self) -> None:
1237512534
self.logical_size[1] = size[1]
1237612535

1237712536
sdl3.SDL_SetWindowMinimumSize(self.t_window, 100, 80)
12378-
12379-
sdl3.SDL_SetWindowResizable(self.t_window, False)
12537+
self._set_wayland_mini_mode_window_state(True, self.logical_size[0], self.logical_size[1])
12538+
sdl3.SDL_SetWindowResizable(self.t_window, True)
1238012539
sdl3.SDL_SetWindowSize(self.t_window, self.logical_size[0], self.logical_size[1])
12540+
if not is_wayland_standalone_wm:
12541+
sdl3.SDL_SetWindowResizable(self.t_window, False)
1238112542

1238212543
if self.mini_mode.save_position:
1238312544
sdl3.SDL_SetWindowPosition(self.t_window, self.mini_mode.save_position[0], self.mini_mode.save_position[1])
@@ -12412,6 +12573,7 @@ def restore_full_mode(self) -> None:
1241212573
self.restore_ignore_timer.set() # Hacky
1241312574

1241412575
self.gui.mode = GuiMode.MAIN
12576+
self._set_wayland_mini_mode_window_state(False)
1241512577

1241612578
sdl3.SDL_SyncWindow(self.t_window)
1241712579
sdl3.SDL_PumpEvents()
@@ -12420,13 +12582,15 @@ def restore_full_mode(self) -> None:
1242012582
self.inp.mouse_up = False
1242112583
self.inp.mouse_click = False
1242212584

12423-
if self.gui.maximized:
12585+
if self.gui.mini_mode_return_maximized:
1242412586
sdl3.SDL_MaximizeWindow(self.t_window)
1242512587
time.sleep(0.05)
1242612588
sdl3.SDL_PumpEvents()
1242712589
sdl3.SDL_GetWindowSize(self.t_window, i_x, i_y)
1242812590
self.logical_size[0] = i_x.contents.value
1242912591
self.logical_size[1] = i_y.contents.value
12592+
self.gui.maximized = True
12593+
self.gui.mini_mode_return_maximized = False
1243012594

1243112595
#logging.info(self.window_size)
1243212596

@@ -49027,16 +49191,19 @@ def dev_mode_disable_save_state() -> None:
4902749191
if (inp.key_shift_down and inp.mouse_click) or inp.middle_click:
4902849192
if prefs.mini_mode_mode == MiniModeMode.TAB:
4902949193
prefs.mini_mode_mode = MiniModeMode.SQUARE
49030-
window_size[0] = int(330 * gui.scale)
49031-
window_size[1] = int(330 * gui.scale)
49032-
sdl3.SDL_SetWindowMinimumSize(t_window, window_size[0], window_size[1])
49033-
sdl3.SDL_SetWindowSize(t_window, window_size[0], window_size[1])
49194+
size = (int(330 * gui.scale), int(330 * gui.scale))
4903449195
else:
4903549196
prefs.mini_mode_mode = MiniModeMode.TAB
49036-
window_size[0] = int(320 * gui.scale)
49037-
window_size[1] = int(90 * gui.scale)
49038-
sdl3.SDL_SetWindowMinimumSize(t_window, window_size[0], window_size[1])
49039-
sdl3.SDL_SetWindowSize(t_window, window_size[0], window_size[1])
49197+
size = (int(320 * gui.scale), int(90 * gui.scale))
49198+
49199+
logical_size[0] = size[0]
49200+
logical_size[1] = size[1]
49201+
window_size[0] = size[0]
49202+
window_size[1] = size[1]
49203+
49204+
tauon._set_wayland_mini_mode_window_state(True, size[0], size[1], reset_tracking=False)
49205+
sdl3.SDL_SetWindowMinimumSize(t_window, size[0], size[1])
49206+
sdl3.SDL_SetWindowSize(t_window, size[0], size[1])
4904049207

4904149208
if prefs.mini_mode_mode == MiniModeMode.SLATE:
4904249209
tauon.mini_mode3.render()

0 commit comments

Comments
 (0)