Commit eb480a56 authored by dmMaze's avatar dmMaze
Browse files

update frameless widget on windows

parent 284f18bc
Loading
Loading
Loading
Loading
+66 −1
Original line number Diff line number Diff line
@@ -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 """
+26 −29
Original line number Diff line number Diff line
@@ -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
@@ -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

@@ -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)
@@ -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):
@@ -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:
@@ -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
@@ -142,10 +143,6 @@ class WindowsFramelessWindow(QWidget):







class AcrylicWindow(WindowsFramelessWindow):
    """ A frameless window with acrylic effect """

@@ -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 """
+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
@@ -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:
@@ -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
@@ -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

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

@@ -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
@@ -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))
@@ -175,6 +183,9 @@ class WindowsWindowEffect:
        hWnd: int or `sip.voidptr`
            Window handle
        """
        if not IsCompositionEnabled():
            return

        hWnd = int(hWnd)
        self.DwmSetWindowAttribute(
            hWnd,
@@ -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