Loading ui/framelesswindow/fw_qt6/utils/win32_utils.py +66 −1 Original line number Diff line number Diff line Loading @@ -203,6 +203,71 @@ class APPBARDATA(Structure): ] class Taskbar: LEFT = 0 TOP = 1 RIGHT = 2 BOTTOM = 3 NO_POSITION = 4 AUTO_HIDE_THICKNESS = 2 @staticmethod def isAutoHide(): """ detect whether the taskbar is hidden automatically """ appbarData = APPBARDATA(sizeof(APPBARDATA), 0, 0, 0, RECT(0, 0, 0, 0), 0) taskbarState = windll.shell32.SHAppBarMessage( shellcon.ABM_GETSTATE, byref(appbarData)) return taskbarState == shellcon.ABS_AUTOHIDE @classmethod def getPosition(cls, hWnd): """ get the position of auto-hide task bar Parameters ---------- hWnd: int or `sip.voidptr` window handle """ if isGreaterEqualWin8_1(): monitorInfo = getMonitorInfo( hWnd, win32con.MONITOR_DEFAULTTONEAREST) if not monitorInfo: return cls.NO_POSITION monitor = RECT(*monitorInfo['Monitor']) appbarData = APPBARDATA(sizeof(APPBARDATA), 0, 0, 0, monitor, 0) positions = [cls.LEFT, cls.TOP, cls.RIGHT, cls.BOTTOM] for position in positions: appbarData.uEdge = position if windll.shell32.SHAppBarMessage(11, byref(appbarData)): return position return cls.NO_POSITION appbarData = APPBARDATA(sizeof(APPBARDATA), win32gui.FindWindow( "Shell_TrayWnd", None), 0, 0, RECT(0, 0, 0, 0), 0) if appbarData.hWnd: windowMonitor = win32api.MonitorFromWindow( hWnd, win32con.MONITOR_DEFAULTTONEAREST) if not windowMonitor: return cls.NO_POSITION taskbarMonitor = win32api.MonitorFromWindow( appbarData.hWnd, win32con.MONITOR_DEFAULTTOPRIMARY) if not taskbarMonitor: return cls.NO_POSITION if taskbarMonitor == windowMonitor: windll.shell32.SHAppBarMessage( shellcon.ABM_GETTASKBARPOS, byref(appbarData)) return appbarData.uEdge return cls.NO_POSITION class WindowsMoveResize: """ Tool class for moving and resizing Mac OS window """ Loading ui/framelesswindow/fw_qt6/windows/__init__.py +26 −29 Original line number Diff line number Diff line Loading @@ -2,8 +2,6 @@ import sys from ctypes import cast from ctypes.wintypes import LPRECT, MSG from platform import platform from PyQt6 import QtCore import win32con import win32gui Loading @@ -11,8 +9,9 @@ from PyQt6.QtCore import Qt from PyQt6.QtGui import QCloseEvent, QCursor from PyQt6.QtWidgets import QApplication, QWidget # from ..titlebar import TitleBar from ..utils import win32_utils as win_utils # from ..utils.win32_utils import Taskbar from ..utils.win32_utils import Taskbar from .c_structures import LPNCCALCSIZE_PARAMS from .window_effect import WindowsWindowEffect Loading @@ -30,7 +29,7 @@ class WindowsFramelessWindow(QWidget): # remove window border if not win_utils.isWin7(): self.setWindowFlags(self.windowFlags() | Qt.WindowType.FramelessWindowHint | Qt.WindowType.WindowMinMaxButtonsHint) self.setWindowFlags(self.windowFlags() | Qt.WindowType.FramelessWindowHint) else: self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.WindowMinMaxButtonsHint) Loading @@ -43,7 +42,7 @@ class WindowsFramelessWindow(QWidget): # solve issue #5 self.windowHandle().screenChanged.connect(self.__onScreenChanged) # self.resize(500, 500) self.resize(500, 500) # self.titleBar.raise_() # def setTitleBar(self, titleBar): Loading Loading @@ -78,10 +77,13 @@ class WindowsFramelessWindow(QWidget): xPos = pos.x() - self.x() yPos = pos.y() - self.y() w, h = self.width(), self.height() lx = xPos < self.BORDER_WIDTH rx = xPos > w - self.BORDER_WIDTH ty = yPos < self.BORDER_WIDTH by = yPos > h - self.BORDER_WIDTH # fixes https://github.com/zhiyiYo/PyQt-Frameless-Window/issues/98 bw = 0 if win_utils.isMaximized(msg.hWnd) or win_utils.isFullScreen(msg.hWnd) else self.BORDER_WIDTH lx = xPos < bw rx = xPos > w - bw ty = yPos < bw by = yPos > h - bw if lx and ty: return True, win32con.HTTOPLEFT elif rx and by: Loading Loading @@ -118,19 +120,18 @@ class WindowsFramelessWindow(QWidget): rect.right -= tx # handle the situation that an auto-hide taskbar is enabled # if (isMax or isFull) and Taskbar.isAutoHide(): # position = Taskbar.getPosition(msg.hWnd) # if position == Taskbar.LEFT: # rect.top += Taskbar.AUTO_HIDE_THICKNESS # elif position == Taskbar.BOTTOM: # rect.bottom -= Taskbar.AUTO_HIDE_THICKNESS # elif position == Taskbar.LEFT: # rect.left += Taskbar.AUTO_HIDE_THICKNESS # elif position == Taskbar.RIGHT: # rect.right -= Taskbar.AUTO_HIDE_THICKNESS # 768 0 0 1600 2560 if (isMax or isFull) and Taskbar.isAutoHide(): position = Taskbar.getPosition(msg.hWnd) if position == Taskbar.LEFT: rect.top += Taskbar.AUTO_HIDE_THICKNESS elif position == Taskbar.BOTTOM: rect.bottom -= Taskbar.AUTO_HIDE_THICKNESS elif position == Taskbar.LEFT: rect.left += Taskbar.AUTO_HIDE_THICKNESS elif position == Taskbar.RIGHT: rect.right -= Taskbar.AUTO_HIDE_THICKNESS result = 0 if not msg.wParam else win32con.WVR_REDRAW # print(result, rect.top, rect.left, rect.bottom, rect.right) return True, result return False, 0 Loading @@ -142,10 +143,6 @@ class WindowsFramelessWindow(QWidget): class AcrylicWindow(WindowsFramelessWindow): """ A frameless window with acrylic effect """ Loading @@ -157,15 +154,15 @@ class AcrylicWindow(WindowsFramelessWindow): self.windowEffect.enableBlurBehindWindow(self.winId()) self.windowEffect.addWindowAnimation(self.winId()) if "Windows-7" in platform(): if win_utils.isWin7(): self.windowEffect.addShadowEffect(self.winId()) self.windowEffect.setAeroEffect(self.winId()) else: self.windowEffect.setAcrylicEffect(self.winId()) if sys.getwindowsversion().build >= 22000: if win_utils.isGreaterEqualWin11(): self.windowEffect.addShadowEffect(self.winId()) self.setStyleSheet("background:transparent") self.setStyleSheet("AcrylicWindow{background:transparent}") def nativeEvent(self, eventType, message): """ Handle the Windows message """ Loading ui/framelesswindow/fw_qt6/windows/window_effect.py +36 −8 Original line number Diff line number Diff line # coding:utf-8 import sys import warnings from ctypes import POINTER, byref, c_bool, c_int, cdll, pointer, sizeof from ctypes import POINTER, byref, c_bool, c_int, pointer, sizeof, WinDLL from ctypes.wintypes import DWORD, LONG, LPCVOID from platform import platform import win32api import win32con Loading @@ -13,6 +12,7 @@ from .c_structures import (ACCENT_POLICY, ACCENT_STATE, DWMNCRENDERINGPOLICY, DWMWINDOWATTRIBUTE, MARGINS, WINDOWCOMPOSITIONATTRIB, WINDOWCOMPOSITIONATTRIBDATA, DWM_BLURBEHIND) from ..utils.win32_utils import isGreaterEqualWin10, isGreaterEqualWin11, IsCompositionEnabled class WindowsWindowEffect: Loading @@ -22,8 +22,8 @@ class WindowsWindowEffect: self.window = window # Declare the function signature of the API self.user32 = cdll.LoadLibrary("user32") self.dwmapi = cdll.LoadLibrary("dwmapi") self.user32 = WinDLL("user32") self.dwmapi = WinDLL("dwmapi") self.SetWindowCompositionAttribute = self.user32.SetWindowCompositionAttribute self.DwmExtendFrameIntoClientArea = self.dwmapi.DwmExtendFrameIntoClientArea self.DwmEnableBlurBehindWindow = self.dwmapi.DwmEnableBlurBehindWindow Loading Loading @@ -65,7 +65,7 @@ class WindowsWindowEffect: animationId: int Turn on matte animation """ if "Windows-7" in platform(): if not isGreaterEqualWin10(): warnings.warn("The acrylic effect is only available on Win10+") return Loading @@ -82,7 +82,7 @@ class WindowsWindowEffect: self.winCompAttrData.Attribute = WINDOWCOMPOSITIONATTRIB.WCA_ACCENT_POLICY.value self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData)) def setMicaEffect(self, hWnd, isDarkMode=False): def setMicaEffect(self, hWnd, isDarkMode=False, isAlt=False): """ Add the mica effect to the window (Win11 only) Parameters Loading @@ -92,8 +92,11 @@ class WindowsWindowEffect: isDarkMode: bool whether to use dark mode mica effect isAlt: bool whether to enable mica alt effect """ if sys.getwindowsversion().build < 22000: if not isGreaterEqualWin11(): warnings.warn("The mica effect is only available on Win11") return Loading @@ -113,7 +116,9 @@ class WindowsWindowEffect: if sys.getwindowsversion().build < 22523: self.DwmSetWindowAttribute(hWnd, 1029, byref(c_int(1)), 4) else: self.DwmSetWindowAttribute(hWnd, 38, byref(c_int(2)), 4) self.DwmSetWindowAttribute(hWnd, 38, byref(c_int(4 if isAlt else 2)), 4) self.DwmSetWindowAttribute(hWnd, 20, byref(c_int(1*isDarkMode)), 4) def setAeroEffect(self, hWnd): """ Add the aero effect to the window Loading Loading @@ -163,6 +168,9 @@ class WindowsWindowEffect: hWnd: int or `sip.voidptr` Window handle """ if not IsCompositionEnabled(): return hWnd = int(hWnd) margins = MARGINS(-1, -1, -1, -1) self.DwmExtendFrameIntoClientArea(hWnd, byref(margins)) Loading @@ -175,6 +183,9 @@ class WindowsWindowEffect: hWnd: int or `sip.voidptr` Window handle """ if not IsCompositionEnabled(): return hWnd = int(hWnd) self.DwmSetWindowAttribute( hWnd, Loading Loading @@ -237,6 +248,23 @@ class WindowsWindowEffect: | win32con.WS_THICKFRAME, ) @staticmethod def disableMaximizeButton(hWnd): """ Disable the maximize button of window Parameters ---------- hWnd : int or `sip.voidptr` Window handle """ hWnd = int(hWnd) style = win32gui.GetWindowLong(hWnd, win32con.GWL_STYLE) win32gui.SetWindowLong( hWnd, win32con.GWL_STYLE, style & ~win32con.WS_MAXIMIZEBOX, ) def enableBlurBehindWindow(self, hWnd): """ enable the blur effect behind the whole client Loading Loading
ui/framelesswindow/fw_qt6/utils/win32_utils.py +66 −1 Original line number Diff line number Diff line Loading @@ -203,6 +203,71 @@ class APPBARDATA(Structure): ] class Taskbar: LEFT = 0 TOP = 1 RIGHT = 2 BOTTOM = 3 NO_POSITION = 4 AUTO_HIDE_THICKNESS = 2 @staticmethod def isAutoHide(): """ detect whether the taskbar is hidden automatically """ appbarData = APPBARDATA(sizeof(APPBARDATA), 0, 0, 0, RECT(0, 0, 0, 0), 0) taskbarState = windll.shell32.SHAppBarMessage( shellcon.ABM_GETSTATE, byref(appbarData)) return taskbarState == shellcon.ABS_AUTOHIDE @classmethod def getPosition(cls, hWnd): """ get the position of auto-hide task bar Parameters ---------- hWnd: int or `sip.voidptr` window handle """ if isGreaterEqualWin8_1(): monitorInfo = getMonitorInfo( hWnd, win32con.MONITOR_DEFAULTTONEAREST) if not monitorInfo: return cls.NO_POSITION monitor = RECT(*monitorInfo['Monitor']) appbarData = APPBARDATA(sizeof(APPBARDATA), 0, 0, 0, monitor, 0) positions = [cls.LEFT, cls.TOP, cls.RIGHT, cls.BOTTOM] for position in positions: appbarData.uEdge = position if windll.shell32.SHAppBarMessage(11, byref(appbarData)): return position return cls.NO_POSITION appbarData = APPBARDATA(sizeof(APPBARDATA), win32gui.FindWindow( "Shell_TrayWnd", None), 0, 0, RECT(0, 0, 0, 0), 0) if appbarData.hWnd: windowMonitor = win32api.MonitorFromWindow( hWnd, win32con.MONITOR_DEFAULTTONEAREST) if not windowMonitor: return cls.NO_POSITION taskbarMonitor = win32api.MonitorFromWindow( appbarData.hWnd, win32con.MONITOR_DEFAULTTOPRIMARY) if not taskbarMonitor: return cls.NO_POSITION if taskbarMonitor == windowMonitor: windll.shell32.SHAppBarMessage( shellcon.ABM_GETTASKBARPOS, byref(appbarData)) return appbarData.uEdge return cls.NO_POSITION class WindowsMoveResize: """ Tool class for moving and resizing Mac OS window """ Loading
ui/framelesswindow/fw_qt6/windows/__init__.py +26 −29 Original line number Diff line number Diff line Loading @@ -2,8 +2,6 @@ import sys from ctypes import cast from ctypes.wintypes import LPRECT, MSG from platform import platform from PyQt6 import QtCore import win32con import win32gui Loading @@ -11,8 +9,9 @@ from PyQt6.QtCore import Qt from PyQt6.QtGui import QCloseEvent, QCursor from PyQt6.QtWidgets import QApplication, QWidget # from ..titlebar import TitleBar from ..utils import win32_utils as win_utils # from ..utils.win32_utils import Taskbar from ..utils.win32_utils import Taskbar from .c_structures import LPNCCALCSIZE_PARAMS from .window_effect import WindowsWindowEffect Loading @@ -30,7 +29,7 @@ class WindowsFramelessWindow(QWidget): # remove window border if not win_utils.isWin7(): self.setWindowFlags(self.windowFlags() | Qt.WindowType.FramelessWindowHint | Qt.WindowType.WindowMinMaxButtonsHint) self.setWindowFlags(self.windowFlags() | Qt.WindowType.FramelessWindowHint) else: self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.WindowMinMaxButtonsHint) Loading @@ -43,7 +42,7 @@ class WindowsFramelessWindow(QWidget): # solve issue #5 self.windowHandle().screenChanged.connect(self.__onScreenChanged) # self.resize(500, 500) self.resize(500, 500) # self.titleBar.raise_() # def setTitleBar(self, titleBar): Loading Loading @@ -78,10 +77,13 @@ class WindowsFramelessWindow(QWidget): xPos = pos.x() - self.x() yPos = pos.y() - self.y() w, h = self.width(), self.height() lx = xPos < self.BORDER_WIDTH rx = xPos > w - self.BORDER_WIDTH ty = yPos < self.BORDER_WIDTH by = yPos > h - self.BORDER_WIDTH # fixes https://github.com/zhiyiYo/PyQt-Frameless-Window/issues/98 bw = 0 if win_utils.isMaximized(msg.hWnd) or win_utils.isFullScreen(msg.hWnd) else self.BORDER_WIDTH lx = xPos < bw rx = xPos > w - bw ty = yPos < bw by = yPos > h - bw if lx and ty: return True, win32con.HTTOPLEFT elif rx and by: Loading Loading @@ -118,19 +120,18 @@ class WindowsFramelessWindow(QWidget): rect.right -= tx # handle the situation that an auto-hide taskbar is enabled # if (isMax or isFull) and Taskbar.isAutoHide(): # position = Taskbar.getPosition(msg.hWnd) # if position == Taskbar.LEFT: # rect.top += Taskbar.AUTO_HIDE_THICKNESS # elif position == Taskbar.BOTTOM: # rect.bottom -= Taskbar.AUTO_HIDE_THICKNESS # elif position == Taskbar.LEFT: # rect.left += Taskbar.AUTO_HIDE_THICKNESS # elif position == Taskbar.RIGHT: # rect.right -= Taskbar.AUTO_HIDE_THICKNESS # 768 0 0 1600 2560 if (isMax or isFull) and Taskbar.isAutoHide(): position = Taskbar.getPosition(msg.hWnd) if position == Taskbar.LEFT: rect.top += Taskbar.AUTO_HIDE_THICKNESS elif position == Taskbar.BOTTOM: rect.bottom -= Taskbar.AUTO_HIDE_THICKNESS elif position == Taskbar.LEFT: rect.left += Taskbar.AUTO_HIDE_THICKNESS elif position == Taskbar.RIGHT: rect.right -= Taskbar.AUTO_HIDE_THICKNESS result = 0 if not msg.wParam else win32con.WVR_REDRAW # print(result, rect.top, rect.left, rect.bottom, rect.right) return True, result return False, 0 Loading @@ -142,10 +143,6 @@ class WindowsFramelessWindow(QWidget): class AcrylicWindow(WindowsFramelessWindow): """ A frameless window with acrylic effect """ Loading @@ -157,15 +154,15 @@ class AcrylicWindow(WindowsFramelessWindow): self.windowEffect.enableBlurBehindWindow(self.winId()) self.windowEffect.addWindowAnimation(self.winId()) if "Windows-7" in platform(): if win_utils.isWin7(): self.windowEffect.addShadowEffect(self.winId()) self.windowEffect.setAeroEffect(self.winId()) else: self.windowEffect.setAcrylicEffect(self.winId()) if sys.getwindowsversion().build >= 22000: if win_utils.isGreaterEqualWin11(): self.windowEffect.addShadowEffect(self.winId()) self.setStyleSheet("background:transparent") self.setStyleSheet("AcrylicWindow{background:transparent}") def nativeEvent(self, eventType, message): """ Handle the Windows message """ Loading
ui/framelesswindow/fw_qt6/windows/window_effect.py +36 −8 Original line number Diff line number Diff line # coding:utf-8 import sys import warnings from ctypes import POINTER, byref, c_bool, c_int, cdll, pointer, sizeof from ctypes import POINTER, byref, c_bool, c_int, pointer, sizeof, WinDLL from ctypes.wintypes import DWORD, LONG, LPCVOID from platform import platform import win32api import win32con Loading @@ -13,6 +12,7 @@ from .c_structures import (ACCENT_POLICY, ACCENT_STATE, DWMNCRENDERINGPOLICY, DWMWINDOWATTRIBUTE, MARGINS, WINDOWCOMPOSITIONATTRIB, WINDOWCOMPOSITIONATTRIBDATA, DWM_BLURBEHIND) from ..utils.win32_utils import isGreaterEqualWin10, isGreaterEqualWin11, IsCompositionEnabled class WindowsWindowEffect: Loading @@ -22,8 +22,8 @@ class WindowsWindowEffect: self.window = window # Declare the function signature of the API self.user32 = cdll.LoadLibrary("user32") self.dwmapi = cdll.LoadLibrary("dwmapi") self.user32 = WinDLL("user32") self.dwmapi = WinDLL("dwmapi") self.SetWindowCompositionAttribute = self.user32.SetWindowCompositionAttribute self.DwmExtendFrameIntoClientArea = self.dwmapi.DwmExtendFrameIntoClientArea self.DwmEnableBlurBehindWindow = self.dwmapi.DwmEnableBlurBehindWindow Loading Loading @@ -65,7 +65,7 @@ class WindowsWindowEffect: animationId: int Turn on matte animation """ if "Windows-7" in platform(): if not isGreaterEqualWin10(): warnings.warn("The acrylic effect is only available on Win10+") return Loading @@ -82,7 +82,7 @@ class WindowsWindowEffect: self.winCompAttrData.Attribute = WINDOWCOMPOSITIONATTRIB.WCA_ACCENT_POLICY.value self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData)) def setMicaEffect(self, hWnd, isDarkMode=False): def setMicaEffect(self, hWnd, isDarkMode=False, isAlt=False): """ Add the mica effect to the window (Win11 only) Parameters Loading @@ -92,8 +92,11 @@ class WindowsWindowEffect: isDarkMode: bool whether to use dark mode mica effect isAlt: bool whether to enable mica alt effect """ if sys.getwindowsversion().build < 22000: if not isGreaterEqualWin11(): warnings.warn("The mica effect is only available on Win11") return Loading @@ -113,7 +116,9 @@ class WindowsWindowEffect: if sys.getwindowsversion().build < 22523: self.DwmSetWindowAttribute(hWnd, 1029, byref(c_int(1)), 4) else: self.DwmSetWindowAttribute(hWnd, 38, byref(c_int(2)), 4) self.DwmSetWindowAttribute(hWnd, 38, byref(c_int(4 if isAlt else 2)), 4) self.DwmSetWindowAttribute(hWnd, 20, byref(c_int(1*isDarkMode)), 4) def setAeroEffect(self, hWnd): """ Add the aero effect to the window Loading Loading @@ -163,6 +168,9 @@ class WindowsWindowEffect: hWnd: int or `sip.voidptr` Window handle """ if not IsCompositionEnabled(): return hWnd = int(hWnd) margins = MARGINS(-1, -1, -1, -1) self.DwmExtendFrameIntoClientArea(hWnd, byref(margins)) Loading @@ -175,6 +183,9 @@ class WindowsWindowEffect: hWnd: int or `sip.voidptr` Window handle """ if not IsCompositionEnabled(): return hWnd = int(hWnd) self.DwmSetWindowAttribute( hWnd, Loading Loading @@ -237,6 +248,23 @@ class WindowsWindowEffect: | win32con.WS_THICKFRAME, ) @staticmethod def disableMaximizeButton(hWnd): """ Disable the maximize button of window Parameters ---------- hWnd : int or `sip.voidptr` Window handle """ hWnd = int(hWnd) style = win32gui.GetWindowLong(hWnd, win32con.GWL_STYLE) win32gui.SetWindowLong( hWnd, win32con.GWL_STYLE, style & ~win32con.WS_MAXIMIZEBOX, ) def enableBlurBehindWindow(self, hWnd): """ enable the blur effect behind the whole client Loading