Skip to content

Commit 3b8f8af

Browse files
authored
Merge pull request #2038 from BEST8OY/master
WM specific mini-mode maximizing issue on Wayland
2 parents df34ced + 7b800f2 commit 3b8f8af

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

Comments
 (0)