@@ -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
@@ -12323,6 +12326,152 @@ def clear_gen_ask(self, id: int) -> None:
1232312326 self.gui.message_box_confirm_reference = (id,)
1232412327 self.show_message(_("You added tracks to a generator playlist. Do you want to clear the generator?"), mode="confirm")
1232512328
12329+ def _window_is_maximized(self) -> bool:
12330+ flags = sdl3.SDL_GetWindowFlags(self.t_window)
12331+ return bool(flags & sdl3.SDL_WINDOW_MAXIMIZED)
12332+
12333+ def _is_wayland_standalone_wm(self) -> bool:
12334+ if not self.wayland:
12335+ return False
12336+ desktop = (self.desktop or "").lower()
12337+ de_names = ("gnome", "kde", "plasma", "xfce", "cinnamon", "mate", "unity", "lxqt", "lxde", "budgie", "pantheon", "cosmic")
12338+ return not any(name in desktop for name in de_names)
12339+
12340+ def _get_wayland_wm_controller(self) -> Literal["sway", "hyprland"] | None:
12341+ if os.environ.get("SWAYSOCK"):
12342+ return "sway"
12343+ if os.environ.get("HYPRLAND_INSTANCE_SIGNATURE"):
12344+ return "hyprland"
12345+ return None
12346+
12347+ def _run_wm_command(self, cmd: list[str]) -> str | None:
12348+ binary = cmd[0]
12349+ if shutil.which(binary) is None:
12350+ if not self._wayland_wm_ipc_warned:
12351+ logging.warning("Wayland mini-mode WM integration unavailable: missing %s", binary)
12352+ self._wayland_wm_ipc_warned = True
12353+ return None
12354+ try:
12355+ result = subprocess.run(cmd, capture_output=True, text=True, check=False, timeout=0.8)
12356+ except Exception:
12357+ return None
12358+ if result.returncode != 0:
12359+ return None
12360+ return result.stdout
12361+
12362+ def _reset_mini_mode_wm_tracking(self) -> None:
12363+ self.mini_mode_wm_forced_floating = False
12364+ self.mini_mode_wm_was_floating = False
12365+
12366+ def _get_sway_focused_floating_state(self) -> bool | None:
12367+ tree_raw = self._run_wm_command(["swaymsg", "-t", "get_tree", "-r"])
12368+ if not tree_raw:
12369+ return None
12370+ try:
12371+ tree = json.loads(tree_raw)
12372+ except json.JSONDecodeError:
12373+ return None
12374+
12375+ stack = [tree]
12376+ while stack:
12377+ node = stack.pop()
12378+ if node.get("focused"):
12379+ fstate = node.get("floating")
12380+ return fstate in ("user_on", "auto_on")
12381+ stack.extend(node.get("nodes", []))
12382+ stack.extend(node.get("floating_nodes", []))
12383+ return None
12384+
12385+ def _get_hyprland_active_window(self) -> dict[str, object] | None:
12386+ active_raw = self._run_wm_command(["hyprctl", "-j", "activewindow"])
12387+ if not active_raw:
12388+ return None
12389+ try:
12390+ active = json.loads(active_raw)
12391+ except json.JSONDecodeError:
12392+ return None
12393+ if not isinstance(active, dict):
12394+ return None
12395+ return active
12396+
12397+
12398+ def _wayland_window_is_floating(self) -> bool | None:
12399+ if not self._is_wayland_standalone_wm():
12400+ return None
12401+
12402+ wm_controller = self._get_wayland_wm_controller()
12403+ if wm_controller == "sway":
12404+ return self._get_sway_focused_floating_state()
12405+
12406+ if wm_controller == "hyprland":
12407+ active = self._get_hyprland_active_window()
12408+ if active is None:
12409+ return None
12410+ return bool(active.get("floating", False))
12411+
12412+ return None
12413+
12414+ def _set_wayland_mini_mode_window_state(self, entering: bool, width: int = 0, height: int = 0, reset_tracking: bool = True) -> None:
12415+ if not self._is_wayland_standalone_wm():
12416+ return
12417+ if entering and reset_tracking:
12418+ self._reset_mini_mode_wm_tracking()
12419+
12420+ wm_controller = self._get_wayland_wm_controller()
12421+ if wm_controller is None:
12422+ if not entering:
12423+ self._reset_mini_mode_wm_tracking()
12424+ return
12425+
12426+ if wm_controller == "sway":
12427+ if entering:
12428+ if reset_tracking:
12429+ floating_state = self._get_sway_focused_floating_state()
12430+ if floating_state is not None:
12431+ self.mini_mode_wm_was_floating = floating_state
12432+
12433+ if not self.mini_mode_wm_was_floating:
12434+ self._run_wm_command(["swaymsg", "floating", "enable"])
12435+ self.mini_mode_wm_forced_floating = True
12436+ elif self.mini_mode_wm_forced_floating:
12437+ self._run_wm_command(["swaymsg", "floating", "enable"])
12438+
12439+ if width > 0 and height > 0:
12440+ self._run_wm_command(["swaymsg", "resize", "set", "width", f"{width}px", "height", f"{height}px"])
12441+ else:
12442+ if self.mini_mode_wm_forced_floating and not self.mini_mode_wm_was_floating:
12443+ self._run_wm_command(["swaymsg", "floating", "disable"])
12444+ self._reset_mini_mode_wm_tracking()
12445+ return
12446+
12447+ if wm_controller == "hyprland":
12448+ active = self._get_hyprland_active_window()
12449+ if active is None:
12450+ if not entering:
12451+ self._reset_mini_mode_wm_tracking()
12452+ return
12453+ address = str(active.get("address", ""))
12454+ if not address:
12455+ if not entering:
12456+ self._reset_mini_mode_wm_tracking()
12457+ return
12458+
12459+ if entering:
12460+ if reset_tracking:
12461+ self.mini_mode_wm_was_floating = bool(active.get("floating", False))
12462+ if not self.mini_mode_wm_was_floating:
12463+ self._run_wm_command(["hyprctl", "dispatch", "togglefloating", f"address:{address}"])
12464+ self.mini_mode_wm_forced_floating = True
12465+ elif self.mini_mode_wm_forced_floating and not bool(active.get("floating", False)):
12466+ self._run_wm_command(["hyprctl", "dispatch", "togglefloating", f"address:{address}"])
12467+
12468+ if width > 0 and height > 0:
12469+ self._run_wm_command(["hyprctl", "dispatch", "resizeactive", "exact", str(width), str(height)])
12470+ else:
12471+ if self.mini_mode_wm_forced_floating and not self.mini_mode_wm_was_floating:
12472+ self._run_wm_command(["hyprctl", "dispatch", "togglefloating", f"address:{address}"])
12473+ self._reset_mini_mode_wm_tracking()
12474+
1232612475 def set_mini_mode(self) -> None:
1232712476 if self.gui.fullscreen:
1232812477 return
@@ -12331,14 +12480,23 @@ def set_mini_mode(self) -> None:
1233112480 self.inp.mouse_up = False
1233212481 self.inp.mouse_click = False
1233312482
12334- if self.gui.maximized:
12483+ is_wayland_standalone_wm = self._is_wayland_standalone_wm()
12484+ is_floating_wayland_window = self._wayland_window_is_floating() if is_wayland_standalone_wm else None
12485+
12486+ # Standalone Wayland WMs can report maximized flags while window is already floating.
12487+ # Treat already-floating windows as non-maximized for mini-mode transitions.
12488+ self.gui.mini_mode_return_maximized = self._window_is_maximized() and not bool(is_floating_wayland_window)
12489+ if self.gui.mini_mode_return_maximized:
1233512490 sdl3.SDL_RestoreWindow(self.t_window)
12491+ sdl3.SDL_SyncWindow(self.t_window)
12492+ sdl3.SDL_PumpEvents()
12493+ self.gui.maximized = False
1233612494 self.update_layout_do()
1233712495
1233812496 if self.gui.mode == GuiMode.MAIN:
1233912497 self.old_window_position = get_window_position(self.t_window)
1234012498
12341- if self.prefs.mini_mode_on_top:
12499+ if self.prefs.mini_mode_on_top and not self.wayland :
1234212500 sdl3.SDL_SetWindowAlwaysOnTop(self.t_window, True)
1234312501
1234412502 self.gui.mode = GuiMode.MINI
@@ -12353,7 +12511,8 @@ def set_mini_mode(self) -> None:
1235312511 self.gui.save_position = (i_x.contents.value, i_y.contents.value)
1235412512
1235512513 self.mini_mode.was_borderless = self.draw_border
12356- sdl3.SDL_SetWindowBordered(self.t_window, False)
12514+ if not is_wayland_standalone_wm:
12515+ sdl3.SDL_SetWindowBordered(self.t_window, False)
1235712516
1235812517 size = (350, 429)
1235912518 if self.prefs.mini_mode_mode == MiniModeMode.MINI:
@@ -12376,9 +12535,11 @@ def set_mini_mode(self) -> None:
1237612535 self.logical_size[1] = size[1]
1237712536
1237812537 sdl3.SDL_SetWindowMinimumSize(self.t_window, 100, 80)
12379-
12380- sdl3.SDL_SetWindowResizable(self.t_window, False )
12538+ self._set_wayland_mini_mode_window_state(True, self.logical_size[0], self.logical_size[1])
12539+ sdl3.SDL_SetWindowResizable(self.t_window, True )
1238112540 sdl3.SDL_SetWindowSize(self.t_window, self.logical_size[0], self.logical_size[1])
12541+ if not is_wayland_standalone_wm:
12542+ sdl3.SDL_SetWindowResizable(self.t_window, False)
1238212543
1238312544 if self.mini_mode.save_position:
1238412545 sdl3.SDL_SetWindowPosition(self.t_window, self.mini_mode.save_position[0], self.mini_mode.save_position[1])
@@ -12413,6 +12574,7 @@ def restore_full_mode(self) -> None:
1241312574 self.restore_ignore_timer.set() # Hacky
1241412575
1241512576 self.gui.mode = GuiMode.MAIN
12577+ self._set_wayland_mini_mode_window_state(False)
1241612578
1241712579 sdl3.SDL_SyncWindow(self.t_window)
1241812580 sdl3.SDL_PumpEvents()
@@ -12421,13 +12583,15 @@ def restore_full_mode(self) -> None:
1242112583 self.inp.mouse_up = False
1242212584 self.inp.mouse_click = False
1242312585
12424- if self.gui.maximized :
12586+ if self.gui.mini_mode_return_maximized :
1242512587 sdl3.SDL_MaximizeWindow(self.t_window)
1242612588 time.sleep(0.05)
1242712589 sdl3.SDL_PumpEvents()
1242812590 sdl3.SDL_GetWindowSize(self.t_window, i_x, i_y)
1242912591 self.logical_size[0] = i_x.contents.value
1243012592 self.logical_size[1] = i_y.contents.value
12593+ self.gui.maximized = True
12594+ self.gui.mini_mode_return_maximized = False
1243112595
1243212596 #logging.info(self.window_size)
1243312597
@@ -49078,16 +49242,19 @@ def dev_mode_disable_save_state() -> None:
4907849242 if (inp.key_shift_down and inp.mouse_click) or inp.middle_click:
4907949243 if prefs.mini_mode_mode == MiniModeMode.TAB:
4908049244 prefs.mini_mode_mode = MiniModeMode.SQUARE
49081- window_size[0] = int(330 * gui.scale)
49082- window_size[1] = int(330 * gui.scale)
49083- sdl3.SDL_SetWindowMinimumSize(t_window, window_size[0], window_size[1])
49084- sdl3.SDL_SetWindowSize(t_window, window_size[0], window_size[1])
49245+ size = (int(330 * gui.scale), int(330 * gui.scale))
4908549246 else:
4908649247 prefs.mini_mode_mode = MiniModeMode.TAB
49087- window_size[0] = int(320 * gui.scale)
49088- window_size[1] = int(90 * gui.scale)
49089- sdl3.SDL_SetWindowMinimumSize(t_window, window_size[0], window_size[1])
49090- sdl3.SDL_SetWindowSize(t_window, window_size[0], window_size[1])
49248+ size = (int(320 * gui.scale), int(90 * gui.scale))
49249+
49250+ logical_size[0] = size[0]
49251+ logical_size[1] = size[1]
49252+ window_size[0] = size[0]
49253+ window_size[1] = size[1]
49254+
49255+ tauon._set_wayland_mini_mode_window_state(True, size[0], size[1], reset_tracking=False)
49256+ sdl3.SDL_SetWindowMinimumSize(t_window, size[0], size[1])
49257+ sdl3.SDL_SetWindowSize(t_window, size[0], size[1])
4909149258
4909249259 if prefs.mini_mode_mode == MiniModeMode.SLATE:
4909349260 tauon.mini_mode3.render()
0 commit comments