@@ -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