Commit fbd8964d authored by dmMaze's avatar dmMaze
Browse files

update for win7 compatibility #507

parent a008399b
Loading
Loading
Loading
Loading
+15 −7
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ import re
import subprocess
import importlib.util
import pkg_resources
from platform import platform

BRANCH = 'dev'
VERSION = '1.4.0'
@@ -16,7 +17,7 @@ python = sys.executable
git = os.environ.get('GIT', "git")
skip_install = False
index_url = os.environ.get('INDEX_URL', "")
QT_APIS = ['pyqt6', 'pyside6']
QT_APIS = ['pyqt6', 'pyside6', 'pyqt5', 'pyside2']
stored_commit_hash = None

REQ_WIN = [
@@ -26,11 +27,16 @@ REQ_WIN = [
PATH_ROOT=Path(__file__).parent  
PATH_FONTS=PATH_ROOT/'fonts'

IS_WIN7 = "Windows-7" in platform()


parser = argparse.ArgumentParser()
parser.add_argument("--reinstall-torch", action='store_true', help="launch.py argument: install the appropriate version of torch even if you have some version already installed")
parser.add_argument("--proj-dir", default='', type=str, help='Open project directory on startup')
parser.add_argument("--qt-api", default='', choices=QT_APIS, help='Set qt api')
if IS_WIN7:
    parser.add_argument("--qt-api", default='pyqt5', choices=QT_APIS, help='Set qt api')
else:
    parser.add_argument("--qt-api", default='pyqt6', choices=QT_APIS, help='Set qt api')
parser.add_argument("--debug", action='store_true')
parser.add_argument("--requirements", default='requirements.txt')
parser.add_argument("--headless", action='store_true', help='run without GUI')
@@ -133,9 +139,6 @@ def main():
    if args.debug:
        os.environ['BALLOONTRANS_DEBUG'] = '1'

    if not args.qt_api in QT_APIS:
        os.environ['QT_API'] = 'pyqt6'
    else:
    os.environ['QT_API'] = args.qt_api

    commit = commit_hash()
@@ -219,7 +222,12 @@ def main():
            if fnt_idx >= 0:
                shared.CUSTOM_FONTS.append(QFontDatabase.applicationFontFamilies(fnt_idx)[0])
    
    if shared.FLAG_QT6:
        shared.FONT_FAMILIES = set(f for f in QFontDatabase.families())
    else:
        fdb = QFontDatabase()
        shared.FONT_FAMILIES = set(fdb.families())

    yahei = QFont('Microsoft YaHei UI')
    if yahei.exactMatch() and not sys.platform == 'darwin':
        QGuiApplication.setFont(yahei)
+5 −3
Original line number Diff line number Diff line
# To install pytorch cuda (gpu) version, please look https://pytorch.org/

PyQt6-Qt6>=6.6.2,<6.7.0
PyQt6>=6.6.1,<6.7.0
PyQt6-Qt6>=6.6.2,<6.7.0 ; python_version > "3.8"
PyQt6>=6.6.1,<6.7.0 ; python_version > "3.8"
PyQt5-Qt5>=5.15.2 ; python_version <= "3.8"
PyQt5>=5.15.10 ; python_version <= "3.8"
numpy<2
urllib3==1.25.11; sys_platform == 'win32' # https://github.com/psf/requests/issues/5740
urllib3; sys_platform == 'darwin' # fix urllib3.package.six.move module not found error
+69 −7
Original line number Diff line number Diff line
# coding:utf-8
from ctypes import Structure, byref, sizeof, windll
from ctypes import Structure, byref, sizeof, windll, c_int
from ctypes.wintypes import DWORD, HWND, LPARAM, RECT, UINT
from platform import platform
import sys

import win32api
import win32con
import win32gui
import win32print
from PyQt5.QtCore import QOperatingSystemVersion
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtWinExtras import QtWin
from win32comext.shell import shellcon


@@ -50,6 +52,13 @@ def isFullScreen(hWnd):
    return all(i == j for i, j in zip(winRect, monitorRect))


def isCompositionEnabled():
    """ detect if dwm composition is enabled """
    bResult = c_int(0)
    windll.dwmapi.DwmIsCompositionEnabled(byref(bResult))
    return bool(bResult.value)


def getMonitorInfo(hWnd, dwFlags):
    """ get monitor info, return `None` if failed

@@ -68,7 +77,7 @@ def getMonitorInfo(hWnd, dwFlags):
    return win32api.GetMonitorInfo(monitor)


def getResizeBorderThickness(hWnd):
def getResizeBorderThickness(hWnd, horizontal=True):
    """ get resize border thickness of widget

    Parameters
@@ -83,16 +92,54 @@ def getResizeBorderThickness(hWnd):
    if not window:
        return 0

    result = win32api.GetSystemMetrics(
        win32con.SM_CXSIZEFRAME) + win32api.GetSystemMetrics(92)
    frame = win32con.SM_CXSIZEFRAME if horizontal else win32con.SM_CYSIZEFRAME
    result = getSystemMetrics(hWnd, frame, horizontal) + getSystemMetrics(hWnd, 92, horizontal)

    if result > 0:
        return result

    thickness = 8 if QtWin.isCompositionEnabled() else 4
    thickness = 8 if isCompositionEnabled() else 4
    return round(thickness*window.devicePixelRatio())


def getSystemMetrics(hWnd, index, horizontal):
    """ get system metrics """
    if not hasattr(windll.user32, 'GetSystemMetricsForDpi'):
        return win32api.GetSystemMetrics(index)

    dpi = getDpiForWindow(hWnd, horizontal)
    return windll.user32.GetSystemMetricsForDpi(index, dpi)


def getDpiForWindow(hWnd, horizontal=True):
    """ get dpi for window

    Parameters
    ----------
    hWnd: int or `sip.voidptr`
        window handle

    dpiScale: bool
        whether to use dpi scale
    """
    if hasattr(windll.user32, 'GetDpiForWindow'):
        return windll.user32.GetDpiForWindow(hWnd)

    hdc = win32gui.GetDC(hWnd)
    if not hdc:
        return 96

    dpiX = win32print.GetDeviceCaps(hdc, win32con.LOGPIXELSX)
    dpiY = win32print.GetDeviceCaps(hdc, win32con.LOGPIXELSY)
    win32gui.ReleaseDC(hWnd, hdc)
    if dpiX > 0 and horizontal:
        return dpiX
    elif dpiY > 0 and not horizontal:
        return dpiY

    return 96


def findWindow(hWnd):
    """ find window by hWnd, return `None` if not found

@@ -130,6 +177,21 @@ def isGreaterEqualWin8_1():
    return isGreaterEqualVersion(QOperatingSystemVersion.Windows8_1)


def isGreaterEqualWin10():
    """ determine if the windows version ≥ Win10 """
    return isGreaterEqualVersion(QOperatingSystemVersion.Windows10)


def isGreaterEqualWin11():
    """ determine if the windows version ≥ Win10 """
    return isGreaterEqualVersion(QOperatingSystemVersion.Windows10) and sys.getwindowsversion().build >= 22000


def isWin7():
    """ determine if the windows version is Win7 """
    return "Windows-7" in platform()


class APPBARDATA(Structure):
    _fields_ = [
        ('cbSize',            DWORD),
+41 −36
Original line number Diff line number Diff line
@@ -9,7 +9,6 @@ import win32gui
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QCloseEvent, QCursor
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow
from PyQt5.QtWinExtras import QtWin

# from ..titlebar import TitleBar
from ..utils import win32_utils as win_utils
@@ -23,13 +22,20 @@ class WindowsFramelessWindow(QWidget):

    BORDER_WIDTH = 5


    def __init__(self, parent=None):
        super().__init__(parent=parent)
        self.windowEffect = WindowsWindowEffect(self)
        # self.titleBar = TitleBar(self)
        self._isResizeEnabled = True

        # remove window border
        if not win_utils.isWin7():
            self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
        elif parent:
            self.setWindowFlags(parent.windowFlags() | Qt.FramelessWindowHint | Qt.WindowMinMaxButtonsHint)
        else:
            self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowMinMaxButtonsHint)

        # add DWM shadow and window animation
        self.windowEffect.addWindowAnimation(self.winId())
@@ -55,6 +61,10 @@ class WindowsFramelessWindow(QWidget):
    #     self.titleBar.setParent(self)
    #     self.titleBar.raise_()

    def setResizeEnabled(self, isEnabled: bool):
        """ set whether resizing is enabled """
        self._isResizeEnabled = isEnabled

    # def resizeEvent(self, e):
    #     super().resizeEvent(e)
    #     self.titleBar.resize(self.width(), self.titleBar.height())
@@ -65,47 +75,35 @@ class WindowsFramelessWindow(QWidget):
        if not msg.hWnd:
            return super().nativeEvent(eventType, message)

        if msg.message == win32con.WM_NCHITTEST:
        if msg.message == win32con.WM_NCHITTEST and self._isResizeEnabled:
            pos = QCursor.pos()
            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
            if lx and ty:
                return True, win32con.HTTOPLEFT
            elif rx and by:
                return True, win32con.HTBOTTOMRIGHT
            elif rx and ty:
                return True, win32con.HTTOPRIGHT
            elif lx and by:
                return True, win32con.HTBOTTOMLEFT
            elif ty:
            if yPos < self.BORDER_WIDTH:
                return True, win32con.HTTOP
            elif by:
                return True, win32con.HTBOTTOM
            elif lx:
                return True, win32con.HTLEFT
            elif rx:
                return True, win32con.HTRIGHT

        elif msg.message == win32con.WM_NCCALCSIZE:
            if msg.wParam:
                rect = cast(msg.lParam, LPNCCALCSIZE_PARAMS).contents.rgrc[0]
            else:
                rect = cast(msg.lParam, LPRECT).contents

            top = rect.top

            # make window resizable
            ret = win32gui.DefWindowProc(msg.hWnd, win32con.WM_NCCALCSIZE, msg.wParam, msg.lParam)
            if ret != 0:
                return True, ret

            # restore top to remove title bar
            rect.top = top

            isMax = win_utils.isMaximized(msg.hWnd)
            isFull = win_utils.isFullScreen(msg.hWnd)

            # adjust the size of client rect
            if isMax and not isFull:
                thickness = win_utils.getResizeBorderThickness(msg.hWnd)
                rect.top += thickness
                rect.left += thickness
                rect.right -= thickness
                rect.bottom -= thickness
                ty = win_utils.getResizeBorderThickness(msg.hWnd, False)
                rect.top += ty

            # handle the situation that an auto-hide taskbar is enabled
            if (isMax or isFull) and Taskbar.isAutoHide():
@@ -137,20 +135,27 @@ class AcrylicWindow(WindowsFramelessWindow):
        super().__init__(parent=parent)
        self.__closedByKey = False

        QtWin.enableBlurBehindWindow(self)
        self.setWindowFlags(Qt.FramelessWindowHint |
                            Qt.WindowMinMaxButtonsHint)
        self.windowEffect.enableBlurBehindWindow(self.winId())

        if win_utils.isWin7() and parent:
            self.setWindowFlags(parent.windowFlags() | Qt.FramelessWindowHint | Qt.WindowMinMaxButtonsHint)
        else:
            self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowMinMaxButtonsHint)

        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}")

        # don't remove this line
        self.resize(400, 400)

    def nativeEvent(self, eventType, message):
        """ Handle the Windows message """
+11 −2
Original line number Diff line number Diff line
# coding:utf-8
from ctypes import POINTER, Structure, c_int
from ctypes.wintypes import DWORD, HWND, ULONG, POINT, RECT, UINT
from ctypes.wintypes import DWORD, HWND, ULONG, POINT, RECT, UINT, BOOL, HRGN
from enum import Enum


@@ -142,3 +142,12 @@ class NCCALCSIZE_PARAMS(Structure):


LPNCCALCSIZE_PARAMS = POINTER(NCCALCSIZE_PARAMS)


class DWM_BLURBEHIND(Structure):
    _fields_ = [
        ('dwFlags',                DWORD),
        ('fEnable',                BOOL),
        ('hRgnBlur',               HRGN),
        ('fTransitionOnMaximized', BOOL),
    ]
 No newline at end of file
Loading