diff --git a/window/transparency/main.py b/window/transparency/main.py new file mode 100644 index 0000000..af18161 --- /dev/null +++ b/window/transparency/main.py @@ -0,0 +1,73 @@ +# Credit @Tensor - https://discord.com/channels/736279277242417272/1068600047090016397/1070025491895029760 +# Tested on Windows 11 +# Check that you have "Transparency effects" enabled in Windows Settings + +import ctypes + +import dearpygui.dearpygui as dpg + +import tools +from windoweffect import window_effect + +dpg.create_context() +dpg.create_viewport(decorated=True, resizable=True, clear_color=[0, 0, 0, 0]) +dpg.setup_dearpygui() + + +class MARGINS(ctypes.Structure): + _fields_ = [ + ("cxLeftWidth", ctypes.c_int), + ("cxRightWidth", ctypes.c_int), + ("cyTopHeight", ctypes.c_int), + ("cyBottomHeight", ctypes.c_int) + ] + + +def removeBackground(): + margins = MARGINS(-1, -1, -1, -1) + ctypes.windll.dwmapi.DwmExtendFrameIntoClientArea(tools.get_hwnd(), margins) + + +def restoreBackground(): + margins = MARGINS(0, 0, 0, 0) + ctypes.windll.dwmapi.DwmExtendFrameIntoClientArea(tools.get_hwnd(), margins) + + +def removeBackgroundEffect(): + window_effect.removeBackgroundEffect(tools.get_hwnd()) + + +def setAeroEffect(): + window_effect.setAeroEffect(tools.get_hwnd()) + + +def setAcrylicEffect(): + gradientColor = list(map(lambda item: int(item), dpg.get_value('color_picker'))) + gradientColor = bytearray(gradientColor).hex().upper() + enableShadow = dpg.get_value('enable_shadow') + window_effect.setAcrylicEffect(tools.get_hwnd(), gradientColor=gradientColor, enableShadow=enableShadow) + + +def setMicaEffect(): + isDarkMode = dpg.get_value('is_dark_mode') + window_effect.setMicaEffect(tools.get_hwnd(), isDarkMode=isDarkMode) + + +with dpg.window(label="Background Effect Test", height=500): + dpg.add_checkbox(label="decorated", default_value=True, callback=lambda _, flag: dpg.set_viewport_decorated(flag)) + with dpg.group(horizontal=True): + dpg.add_button(label="removeBackground (Transparency)", callback=removeBackground) + dpg.add_button(label="Restore", callback=restoreBackground) + dpg.add_button(label="removeBackgroundEffect", callback=removeBackgroundEffect) + dpg.add_button(label="setAeroEffect", callback=setAeroEffect) + with dpg.group(horizontal=True): + dpg.add_button(label="setAcrylicEffect", callback=setAcrylicEffect) + dpg.add_checkbox(label="enableShadow", default_value=False, tag="enable_shadow") + with dpg.group(horizontal=True): + dpg.add_button(label="setMicaEffect", callback=setMicaEffect) + dpg.add_checkbox(label="isDarkMode", default_value=False, tag="is_dark_mode") + dpg.add_color_picker(display_type=dpg.mvColorEdit_uint8, picker_mode=dpg.mvColorPicker_bar, alpha_bar=True, tag='color_picker') + +dpg.show_viewport() +dpg.start_dearpygui() +dpg.destroy_context() diff --git a/window/transparency/tools.py b/window/transparency/tools.py new file mode 100644 index 0000000..521d8bc --- /dev/null +++ b/window/transparency/tools.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +import ctypes +import ctypes.wintypes +import os + +user32 = ctypes.windll.user32 +WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.wintypes.BOOL, + ctypes.wintypes.HWND, + ctypes.wintypes.LPARAM) +user32.EnumWindows.argtypes = [WNDENUMPROC, + ctypes.wintypes.LPARAM] + + +def get_hwnd_from_pid(pid: int) -> int | None: + result = None + + def callback(hwnd, _): + nonlocal result + lpdw_PID = ctypes.c_ulong() + user32.GetWindowThreadProcessId(hwnd, ctypes.byref(lpdw_PID)) + hwnd_PID = lpdw_PID.value + + if hwnd_PID == pid: + result = hwnd + return False + return True + + cb_worker = WNDENUMPROC(callback) + user32.EnumWindows(cb_worker, 0) + return result + + +def get_hwnd() -> int | None: + return get_hwnd_from_pid(os.getpid()) diff --git a/window/transparency/windoweffect/__init__.py b/window/transparency/windoweffect/__init__.py new file mode 100644 index 0000000..0a0f78a --- /dev/null +++ b/window/transparency/windoweffect/__init__.py @@ -0,0 +1,5 @@ +# https://github.com/zhiyiYo/PyQt-Frameless-Window/tree/master/qframelesswindow/windows + +from .window_effect import WindowsWindowEffect + +window_effect = WindowsWindowEffect() diff --git a/window/transparency/windoweffect/c_structures.py b/window/transparency/windoweffect/c_structures.py new file mode 100644 index 0000000..568b93e --- /dev/null +++ b/window/transparency/windoweffect/c_structures.py @@ -0,0 +1,144 @@ +# coding:utf-8 +from ctypes import POINTER, Structure, c_int +from ctypes.wintypes import DWORD, HWND, ULONG, POINT, RECT, UINT +from enum import Enum + + +class WINDOWCOMPOSITIONATTRIB(Enum): + WCA_UNDEFINED = 0 + WCA_NCRENDERING_ENABLED = 1 + WCA_NCRENDERING_POLICY = 2 + WCA_TRANSITIONS_FORCEDISABLED = 3 + WCA_ALLOW_NCPAINT = 4 + WCA_CAPTION_BUTTON_BOUNDS = 5 + WCA_NONCLIENT_RTL_LAYOUT = 6 + WCA_FORCE_ICONIC_REPRESENTATION = 7 + WCA_EXTENDED_FRAME_BOUNDS = 8 + WCA_HAS_ICONIC_BITMAP = 9 + WCA_THEME_ATTRIBUTES = 10 + WCA_NCRENDERING_EXILED = 11 + WCA_NCADORNMENTINFO = 12 + WCA_EXCLUDED_FROM_LIVEPREVIEW = 13 + WCA_VIDEO_OVERLAY_ACTIVE = 14 + WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15 + WCA_DISALLOW_PEEK = 16 + WCA_CLOAK = 17 + WCA_CLOAKED = 18 + WCA_ACCENT_POLICY = 19 + WCA_FREEZE_REPRESENTATION = 20 + WCA_EVER_UNCLOAKED = 21 + WCA_VISUAL_OWNER = 22 + WCA_HOLOGRAPHIC = 23 + WCA_EXCLUDED_FROM_DDA = 24 + WCA_PASSIVEUPDATEMODE = 25 + WCA_USEDARKMODECOLORS = 26 + WCA_CORNER_STYLE = 27 + WCA_PART_COLOR = 28 + WCA_DISABLE_MOVESIZE_FEEDBACK = 29 + WCA_LAST = 30 + + +class ACCENT_STATE(Enum): + """ Client area status enumeration class """ + ACCENT_DISABLED = 0 + ACCENT_ENABLE_GRADIENT = 1 + ACCENT_ENABLE_TRANSPARENTGRADIENT = 2 + ACCENT_ENABLE_BLURBEHIND = 3 # Aero effect + ACCENT_ENABLE_ACRYLICBLURBEHIND = 4 # Acrylic effect + ACCENT_ENABLE_HOSTBACKDROP = 5 # Mica effect + ACCENT_INVALID_STATE = 6 + + +class ACCENT_POLICY(Structure): + """ Specific attributes of client area """ + + _fields_ = [ + ("AccentState", DWORD), + ("AccentFlags", DWORD), + ("GradientColor", DWORD), + ("AnimationId", DWORD), + ] + + +class WINDOWCOMPOSITIONATTRIBDATA(Structure): + _fields_ = [ + ("Attribute", DWORD), + # Pointer() receives any ctypes type and returns a pointer type + ("Data", POINTER(ACCENT_POLICY)), + ("SizeOfData", ULONG), + ] + + +class DWMNCRENDERINGPOLICY(Enum): + DWMNCRP_USEWINDOWSTYLE = 0 + DWMNCRP_DISABLED = 1 + DWMNCRP_ENABLED = 2 + DWMNCRP_LAS = 3 + + +class DWMWINDOWATTRIBUTE(Enum): + DWMWA_NCRENDERING_ENABLED = 1 + DWMWA_NCRENDERING_POLICY = 2 + DWMWA_TRANSITIONS_FORCEDISABLED = 3 + DWMWA_ALLOW_NCPAINT = 4 + DWMWA_CAPTION_BUTTON_BOUNDS = 5 + DWMWA_NONCLIENT_RTL_LAYOUT = 6 + DWMWA_FORCE_ICONIC_REPRESENTATION = 7 + DWMWA_FLIP3D_POLICY = 8 + DWMWA_EXTENDED_FRAME_BOUNDS = 9 + DWMWA_HAS_ICONIC_BITMAP = 10 + DWMWA_DISALLOW_PEEK = 11 + DWMWA_EXCLUDED_FROM_PEEK = 12 + DWMWA_CLOAK = 13 + DWMWA_CLOAKED = 14 + DWMWA_FREEZE_REPRESENTATION = 15 + DWMWA_PASSIVE_UPDATE_MODE = 16 + DWMWA_USE_HOSTBACKDROPBRUSH = 17 + DWMWA_USE_IMMERSIVE_DARK_MODE = 18 + DWMWA_WINDOW_CORNER_PREFERENCE = 19 + DWMWA_BORDER_COLOR = 20 + DWMWA_CAPTION_COLOR = 21 + DWMWA_TEXT_COLOR = 22 + DWMWA_VISIBLE_FRAME_BORDER_THICKNESS = 23 + DWMWA_LAST = 24 + + +class MARGINS(Structure): + _fields_ = [ + ("cxLeftWidth", c_int), + ("cxRightWidth", c_int), + ("cyTopHeight", c_int), + ("cyBottomHeight", c_int), + ] + + +class MINMAXINFO(Structure): + _fields_ = [ + ("ptReserved", POINT), + ("ptMaxSize", POINT), + ("ptMaxPosition", POINT), + ("ptMinTrackSize", POINT), + ("ptMaxTrackSize", POINT), + ] + + +class PWINDOWPOS(Structure): + _fields_ = [ + ('hWnd', HWND), + ('hwndInsertAfter', HWND), + ('x', c_int), + ('y', c_int), + ('cx', c_int), + ('cy', c_int), + ('flags', UINT) + ] + + +class NCCALCSIZE_PARAMS(Structure): + _fields_ = [ + ('rgrc', RECT*3), + ('lppos', POINTER(PWINDOWPOS)) + ] + + +LPNCCALCSIZE_PARAMS = POINTER(NCCALCSIZE_PARAMS) \ No newline at end of file diff --git a/window/transparency/windoweffect/window_effect.py b/window/transparency/windoweffect/window_effect.py new file mode 100644 index 0000000..f44031c --- /dev/null +++ b/window/transparency/windoweffect/window_effect.py @@ -0,0 +1,230 @@ +# coding:utf-8 +import sys +import warnings +from ctypes import POINTER, byref, c_bool, c_int, cdll, pointer, sizeof +from ctypes.wintypes import DWORD, LONG, LPCVOID +from platform import platform + +import win32api +import win32con +import win32gui + +from .c_structures import (ACCENT_POLICY, ACCENT_STATE, DWMNCRENDERINGPOLICY, + DWMWINDOWATTRIBUTE, MARGINS, + WINDOWCOMPOSITIONATTRIB, + WINDOWCOMPOSITIONATTRIBDATA) + + +class WindowsWindowEffect: + """ Windows window effect """ + + def __init__(self): + # Declare the function signature of the API + self.user32 = cdll.LoadLibrary("user32") + self.dwmapi = cdll.LoadLibrary("dwmapi") + self.SetWindowCompositionAttribute = self.user32.SetWindowCompositionAttribute + self.DwmExtendFrameIntoClientArea = self.dwmapi.DwmExtendFrameIntoClientArea + self.DwmSetWindowAttribute = self.dwmapi.DwmSetWindowAttribute + self.SetWindowCompositionAttribute.restype = c_bool + self.DwmExtendFrameIntoClientArea.restype = LONG + self.DwmSetWindowAttribute.restype = LONG + self.SetWindowCompositionAttribute.argtypes = [ + c_int, + POINTER(WINDOWCOMPOSITIONATTRIBDATA), + ] + self.DwmSetWindowAttribute.argtypes = [c_int, DWORD, LPCVOID, DWORD] + self.DwmExtendFrameIntoClientArea.argtypes = [c_int, POINTER(MARGINS)] + + # Initialize structure + self.accentPolicy = ACCENT_POLICY() + self.winCompAttrData = WINDOWCOMPOSITIONATTRIBDATA() + self.winCompAttrData.Attribute = WINDOWCOMPOSITIONATTRIB.WCA_ACCENT_POLICY.value + self.winCompAttrData.SizeOfData = sizeof(self.accentPolicy) + self.winCompAttrData.Data = pointer(self.accentPolicy) + + def setAcrylicEffect(self, hWnd, gradientColor="F2F2F299", enableShadow=True, animationId=0): + """ Add the acrylic effect to the window + + Parameters + ---------- + hWnd: int or `sip.voidptr` + Window handle + + gradientColor: str + Hexadecimal acrylic mixed color, corresponding to four RGBA channels + + isEnableShadow: bool + Enable window shadows + + animationId: int + Turn on matte animation + """ + if "Windows-7" in platform(): + warnings.warn("The acrylic effect is only available on Win10+") + return + + hWnd = int(hWnd) + gradientColor = ''.join(gradientColor[i:i + 2] for i in range(6, -1, -2)) + gradientColor = DWORD(int(gradientColor, base=16)) + animationId = DWORD(animationId) + accentFlags = DWORD(0x20 | 0x40 | 0x80 | 0x100) if enableShadow else DWORD(0) + self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_ENABLE_ACRYLICBLURBEHIND.value + self.accentPolicy.GradientColor = gradientColor + self.accentPolicy.AccentFlags = accentFlags + self.accentPolicy.AnimationId = animationId + self.winCompAttrData.Attribute = WINDOWCOMPOSITIONATTRIB.WCA_ACCENT_POLICY.value + self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData)) + + def setMicaEffect(self, hWnd, isDarkMode=False): + """ Add the mica effect to the window (Win11 only) + + Parameters + ---------- + hWnd: int or `sip.voidptr` + Window handle + + isDarkMode: bool + whether to use dark mode mica effect + """ + if sys.getwindowsversion().build < 22000: + warnings.warn("The mica effect is only available on Win11") + return + + hWnd = int(hWnd) + margins = MARGINS(-1, -1, -1, -1) + self.DwmExtendFrameIntoClientArea(hWnd, byref(margins)) + + self.winCompAttrData.Attribute = WINDOWCOMPOSITIONATTRIB.WCA_ACCENT_POLICY.value + self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_ENABLE_HOSTBACKDROP.value + self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData)) + + if isDarkMode: + self.winCompAttrData.Attribute = WINDOWCOMPOSITIONATTRIB.WCA_USEDARKMODECOLORS.value + self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData)) + + if sys.getwindowsversion().build < 22523: + self.DwmSetWindowAttribute(hWnd, 1029, byref(c_int(1)), 4) + else: + self.DwmSetWindowAttribute(hWnd, 38, byref(c_int(2)), 4) + + def setAeroEffect(self, hWnd): + """ Add the aero effect to the window + + Parameters + ---------- + hWnd: int or `sip.voidptr` + Window handle + """ + hWnd = int(hWnd) + self.winCompAttrData.Attribute = WINDOWCOMPOSITIONATTRIB.WCA_ACCENT_POLICY.value + self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_ENABLE_BLURBEHIND.value + self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData)) + + def removeBackgroundEffect(self, hWnd): + """ Remove background effect + + Parameters + ---------- + hWnd: int or `sip.voidptr` + Window handle + """ + hWnd = int(hWnd) + self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_DISABLED.value + self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData)) + + @staticmethod + def moveWindow(hWnd): + """ Move the window + + Parameters + ---------- + hWnd: int or `sip.voidptr` + Window handle + """ + hWnd = int(hWnd) + win32gui.ReleaseCapture() + win32api.SendMessage( + hWnd, win32con.WM_SYSCOMMAND, win32con.SC_MOVE + win32con.HTCAPTION, 0 + ) + + def addShadowEffect(self, hWnd): + """ Add DWM shadow to window + + Parameters + ---------- + hWnd: int or `sip.voidptr` + Window handle + """ + hWnd = int(hWnd) + margins = MARGINS(-1, -1, -1, -1) + self.DwmExtendFrameIntoClientArea(hWnd, byref(margins)) + + def addMenuShadowEffect(self, hWnd): + """ Add DWM shadow to menu + + Parameters + ---------- + hWnd: int or `sip.voidptr` + Window handle + """ + hWnd = int(hWnd) + self.DwmSetWindowAttribute( + hWnd, + DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY.value, + byref(c_int(DWMNCRENDERINGPOLICY.DWMNCRP_ENABLED.value)), + 4, + ) + margins = MARGINS(-1, -1, -1, -1) + self.DwmExtendFrameIntoClientArea(hWnd, byref(margins)) + + def removeShadowEffect(self, hWnd): + """ Remove DWM shadow from the window + + Parameters + ---------- + hWnd: int or `sip.voidptr` + Window handle + """ + hWnd = int(hWnd) + self.DwmSetWindowAttribute( + hWnd, + DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY.value, + byref(c_int(DWMNCRENDERINGPOLICY.DWMNCRP_DISABLED.value)), + 4, + ) + + @staticmethod + def removeMenuShadowEffect(hWnd): + """ Remove shadow from pop-up menu + + Parameters + ---------- + hWnd: int or `sip.voidptr` + Window handle + """ + hWnd = int(hWnd) + style = win32gui.GetClassLong(hWnd, win32con.GCL_STYLE) + style &= ~0x00020000 # CS_DROPSHADOW + win32api.SetClassLong(hWnd, win32con.GCL_STYLE, style) + + @staticmethod + def addWindowAnimation(hWnd): + """ Enables the maximize and minimize animation of the 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_MINIMIZEBOX + | win32con.WS_MAXIMIZEBOX + | win32con.WS_CAPTION + | win32con.CS_DBLCLKS + | win32con.WS_THICKFRAME, + )