diff --git a/src/core/bar_helper.py b/src/core/bar_helper.py index 37f882e9..d4a587d3 100644 --- a/src/core/bar_helper.py +++ b/src/core/bar_helper.py @@ -37,7 +37,12 @@ from core.utils.win32.bindings import SetWindowPos from core.utils.win32.bindings.user32 import KillTimer, RegisterWindowMessage, SetTimer, user32 from core.utils.win32.structs import MSG -from core.utils.win32.utilities import apply_qmenu_style +from core.utils.win32.utilities import ( + apply_qmenu_style, + is_process_fullscreen_on_monitor, + is_window_fullscreen, + is_window_maximized, +) # Register TaskbarCreated message to detect Explorer restarts WM_TASKBARCREATED = RegisterWindowMessage("TaskbarCreated") @@ -551,6 +556,19 @@ def _is_foreground_excluded(self) -> bool: self.EXCLUDED_WINDOW_CLASS_SUFFIXES ): return True + if is_window_fullscreen(hwnd): + if is_window_maximized(hwnd): + # Maximized windows cover the screen but are not fullscreen apps + return True + # True fullscreen (not maximized) allow hiding the bar + return False + # Foreground not fullscreen check if a sibling window from the + # same process is fullscreen (e.g. Firefox HTML5 fullscreen video) + process_fs = is_process_fullscreen_on_monitor( + hwnd, self.EXCLUDED_WINDOW_CLASSES, self.EXCLUDED_WINDOW_CLASS_SUFFIXES + ) + if not process_fs: + return True except Exception: pass return False diff --git a/src/core/utils/win32/utilities.py b/src/core/utils/win32/utilities.py index e01abbe4..c4cb00a3 100644 --- a/src/core/utils/win32/utilities.py +++ b/src/core/utils/win32/utilities.py @@ -7,6 +7,7 @@ import win32api import win32gui +import win32process from PyQt6.QtGui import QCursor from PyQt6.QtWidgets import QApplication, QWidget from win32api import GetMonitorInfo, MonitorFromWindow @@ -281,6 +282,63 @@ def is_window_maximized(hwnd: int) -> bool: return window_placement[1] == SW_MAXIMIZE +def is_window_fullscreen(hwnd: int) -> bool: + """Check if a window covers the entire monitor it is on.""" + if win32gui.IsIconic(hwnd): + return False + rect = GetWindowRect(hwnd) + monitor_info = GetMonitorInfo(int(MonitorFromWindow(hwnd))) + mon = monitor_info["Monitor"] # (left, top, right, bottom) + return rect[0] <= mon[0] and rect[1] <= mon[1] and rect[2] >= mon[2] and rect[3] >= mon[3] + + +def is_process_fullscreen_on_monitor( + hwnd: int, + excluded_classes: set[str] | None = None, + excluded_suffixes: tuple[str, ...] | None = None, +) -> bool: + """Check if any visible window belonging to the same process as hwnd + is fullscreen on the same monitor. This should handles apps like Firefox + that use a separate child window for HTML5 fullscreen video.""" + try: + _, target_pid = win32process.GetWindowThreadProcessId(hwnd) + if target_pid == 0: + return False + + target_monitor = int(MonitorFromWindow(hwnd)) + _excluded_classes = excluded_classes or set() + _excluded_suffixes = excluded_suffixes or () + found = False + + def _enum_cb(candidate_hwnd, _): + nonlocal found + try: + if not win32gui.IsWindowVisible(candidate_hwnd): + return True + _, c_pid = win32process.GetWindowThreadProcessId(candidate_hwnd) + if c_pid != target_pid: + return True + cls = GetClassName(candidate_hwnd) + if cls in _excluded_classes or cls.endswith(_excluded_suffixes): + return True + if int(MonitorFromWindow(candidate_hwnd)) != target_monitor: + return True + if is_window_fullscreen(candidate_hwnd): + found = True + return False + except Exception: + pass + return True + + try: + win32gui.EnumWindows(_enum_cb, 0) + except Exception: + pass + return found + except Exception: + return False + + def get_hwnd_info(hwnd: int) -> dict: with suppress(Exception): monitor_hwnd = get_monitor_hwnd(hwnd)