From 578a764ec03366ac2fb9fa0d99abaf41fa5eb4f9 Mon Sep 17 00:00:00 2001 From: amass <168062547@qq.com> Date: Thu, 5 Sep 2024 22:05:05 +0800 Subject: [PATCH] adapt linux. --- CMakeLists.txt | 25 + Examples/FluentWindow/qml/Main.qml | 72 +- Fluent/Colors.cpp | 1 + Fluent/Frameless.cpp | 1614 +++++++++++++--------------- Fluent/Frameless.h | 271 +++-- Fluent/QHotkey/CMakeLists.txt | 5 +- Fluent/Utilities.cpp | 468 +++++--- Fluent/Utilities.h | 283 +++-- Fluent/qml/ApplicationWindow.qml | 2 +- 9 files changed, 1552 insertions(+), 1189 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 219fda7..b3d074d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,8 +2,33 @@ cmake_minimum_required(VERSION 3.15) project(Kylin) +option(INDEPENDENT_BUILD "build self." OFF) option(UNIT_TEST "do unit test" OFF) +if(INDEPENDENT_BUILD) + set(KYLIN_WITH_FLUENT ON) + if(WIN32) + set(Projects_ROOT E:/Projects) + set(Libraries_ROOT ${Projects_ROOT}/Libraries) + set(BOOST_ROOT ${Libraries_ROOT}/boost_1_86_0_msvc2022_64bit) + set(Boost_INCLUDE_DIR ${BOOST_ROOT}/include/boost-1_86) + add_compile_definitions( + BOOST_USE_WINAPI_VERSION=BOOST_WINAPI_VERSION_WIN10 + ) + else() + execute_process( + COMMAND sh -c "echo $HOME" + OUTPUT_VARIABLE USER_HOME + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + set(Projects_ROOT ${USER_HOME}/Projects) + set(Libraries_ROOT /opt/Libraries) + set(BOOST_ROOT ${Libraries_ROOT}/boost_1_86_0) + set(Boost_INCLUDE_DIR ${BOOST_ROOT}/include) + endif() + add_subdirectory(Examples) +endif() + find_package(Boost REQUIRED COMPONENTS log serialization) set(OpenSSL_LIBRARY ssl crypto) diff --git a/Examples/FluentWindow/qml/Main.qml b/Examples/FluentWindow/qml/Main.qml index 329be65..38fb5dd 100644 --- a/Examples/FluentWindow/qml/Main.qml +++ b/Examples/FluentWindow/qml/Main.qml @@ -1,11 +1,26 @@ import QtQuick import Fluent as Fluent -Fluent.Window { +Fluent.ApplicationWindow { width: 640 height: 480 visible: true title: qsTr("FluentWindow") + appBar { + height: 30 + showDark: true + darkClickListener:(button)=>handleDarkChanged(button) + closeClickListener: ()=>{dialog_close.open()} + z:7 + } + + Fluent.Button { + text: "成功" + property int dd: 0 + onClicked: { + showSuccess("This is a success message %1".arg(dd++),2000) + } + } Fluent.Rectangle { x:100 @@ -15,4 +30,59 @@ Fluent.Window { color:"red" radius:[10,0,10,0] } + + Fluent.ContentDialog{ + id: dialog_close + title: qsTr("Quit") + message: qsTr("Are you sure you want to exit the program?") + negativeText: qsTr("Minimize") + buttonFlags: Fluent.ContentDialogType.NegativeButton | Fluent.ContentDialogType.NeutralButton | Fluent.ContentDialogType.PositiveButton + onNegativeClicked: { + system_tray.showMessage(qsTr("Friendly Reminder"),qsTr("FluentUI is hidden from the tray, click on the tray to activate the window again")); + timer_window_hide_delay.restart() + } + positiveText: qsTr("Quit") + neutralText: qsTr("Cancel") + onPositiveClicked:{ + Fluent.Router.exit(0) + } + } + + function changeDark(){ + if(Fluent.Theme.dark){ + Fluent.Theme.darkMode = Fluent.ThemeType.Light + }else{ + Fluent.Theme.darkMode = Fluent.ThemeType.Dark + } + } + + function handleDarkChanged(button){ + if(Fluent.Utilities.isMacos() || !Fluent.Theme.animationEnabled || !fitsAppBarWindows){ + changeDark() + }else{ + loader_reveal.sourceComponent = com_reveal + var target = window.containerItem() + var pos = button.mapToItem(target,0,0) + var mouseX = pos.x + button.width / 2 + var mouseY = pos.y + button.height / 2 + var radius = Math.max(distance(mouseX,mouseY,0,0),distance(mouseX,mouseY,target.width,0),distance(mouseX,mouseY,0,target.height),distance(mouseX,mouseY,target.width,target.height)) + var reveal = loader_reveal.item + reveal.start(reveal.width*Screen.devicePixelRatio,reveal.height*Screen.devicePixelRatio,Qt.point(mouseX,mouseY),radius) + } + } + + Fluent.FpsItem { + id:fps_item + } + + Fluent.Text{ + text: "fps %1".arg(fps_item.fps) + opacity: 0.3 + anchors{ + bottom: parent.bottom + right: parent.right + bottomMargin: 5 + rightMargin: 5 + } + } } diff --git a/Fluent/Colors.cpp b/Fluent/Colors.cpp index edf32f9..b711d82 100644 --- a/Fluent/Colors.cpp +++ b/Fluent/Colors.cpp @@ -1,4 +1,5 @@ #include "Colors.h" +#include Colors *Colors::instance() { static Colors *self = nullptr; diff --git a/Fluent/Frameless.cpp b/Fluent/Frameless.cpp index 7632e8d..06d3f38 100644 --- a/Fluent/Frameless.cpp +++ b/Fluent/Frameless.cpp @@ -1,854 +1,760 @@ -#include "Frameless.h" -#include "Theme.h" -#include "Utilities.h" -#include -#include -#include -#include - -static inline void setShadow(HWND hwnd) { - const MARGINS shadow = {1, 0, 0, 0}; - typedef HRESULT(WINAPI * DwmExtendFrameIntoClientAreaPtr)(HWND hWnd, const MARGINS *pMarInset); - HMODULE module = LoadLibrary(L"dwmapi.dll"); - if (module) { - DwmExtendFrameIntoClientAreaPtr dwm_extendframe_into_client_area_; - dwm_extendframe_into_client_area_ = - reinterpret_cast(GetProcAddress(module, "DwmExtendFrameIntoClientArea")); - if (dwm_extendframe_into_client_area_) { - dwm_extendframe_into_client_area_(hwnd, &shadow); - } - } -} - -enum WINDOWCOMPOSITIONATTRIB { - 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 -}; - -enum _DWM_SYSTEMBACKDROP_TYPE { - _DWMSBT_AUTO, // [Default] Let DWM automatically decide the system-drawn backdrop for this - // window. - _DWMSBT_NONE, // [Disable] Do not draw any system backdrop. - _DWMSBT_MAINWINDOW, // [Mica] Draw the backdrop material effect corresponding to a - // long-lived window. - _DWMSBT_TRANSIENTWINDOW, // [Acrylic] Draw the backdrop material effect corresponding to a - // transient window. - _DWMSBT_TABBEDWINDOW, // [Mica Alt] Draw the backdrop material effect corresponding to a - // window with a tabbed title bar. -}; - -enum ACCENT_STATE { - ACCENT_DISABLED = 0, - ACCENT_ENABLE_GRADIENT = 1, - ACCENT_ENABLE_TRANSPARENTGRADIENT = 2, - ACCENT_ENABLE_BLURBEHIND = 3, // Traditional DWM blur - ACCENT_ENABLE_ACRYLICBLURBEHIND = 4, // RS4 1803 - ACCENT_ENABLE_HOST_BACKDROP = 5, // RS5 1809 - ACCENT_INVALID_STATE = 6 // Using this value will remove the window background -}; - -enum ACCENT_FLAG { - ACCENT_NONE = 0, - ACCENT_ENABLE_ACRYLIC = 1, - ACCENT_ENABLE_ACRYLIC_WITH_LUMINOSITY = 482, -}; - -struct WINDOWCOMPOSITIONATTRIBDATA { - WINDOWCOMPOSITIONATTRIB Attrib; - PVOID pvData; - SIZE_T cbData; -}; -using PWINDOWCOMPOSITIONATTRIBDATA = WINDOWCOMPOSITIONATTRIBDATA *; - -struct ACCENT_POLICY { - DWORD dwAccentState; - DWORD dwAccentFlags; - DWORD dwGradientColor; // #AABBGGRR - DWORD dwAnimationId; -}; -using PACCENT_POLICY = ACCENT_POLICY *; - -typedef HRESULT(WINAPI *DwmSetWindowAttributeFunc)(HWND hwnd, DWORD dwAttribute, LPCVOID pvAttribute, - DWORD cbAttribute); -typedef HRESULT(WINAPI *DwmExtendFrameIntoClientAreaFunc)(HWND hwnd, const MARGINS *pMarInset); -typedef HRESULT(WINAPI *DwmIsCompositionEnabledFunc)(BOOL *pfEnabled); -typedef HRESULT(WINAPI *DwmEnableBlurBehindWindowFunc)(HWND hWnd, const DWM_BLURBEHIND *pBlurBehind); -typedef BOOL(WINAPI *SetWindowCompositionAttributeFunc)(HWND hwnd, const WINDOWCOMPOSITIONATTRIBDATA *); - -static DwmSetWindowAttributeFunc pDwmSetWindowAttribute = nullptr; -static DwmExtendFrameIntoClientAreaFunc pDwmExtendFrameIntoClientArea = nullptr; -static DwmIsCompositionEnabledFunc pDwmIsCompositionEnabled = nullptr; -static DwmEnableBlurBehindWindowFunc pDwmEnableBlurBehindWindow = nullptr; -static SetWindowCompositionAttributeFunc pSetWindowCompositionAttribute = nullptr; -static RTL_OSVERSIONINFOW GetRealOSVersionImpl() { - HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll"); - using RtlGetVersionPtr = NTSTATUS(WINAPI *)(PRTL_OSVERSIONINFOW); - auto pRtlGetVersion = reinterpret_cast(::GetProcAddress(hMod, "RtlGetVersion")); - RTL_OSVERSIONINFOW rovi{}; - rovi.dwOSVersionInfoSize = sizeof(rovi); - pRtlGetVersion(&rovi); - return rovi; -} - -RTL_OSVERSIONINFOW GetRealOSVersion() { - static const auto result = GetRealOSVersionImpl(); - return result; -} - -static inline bool isWin7Only() { - RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); - return rovi.dwMajorVersion = 7; -} - -static inline bool isWin11OrGreater() { - RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); - return (rovi.dwMajorVersion > 10) || - (rovi.dwMajorVersion == 10 && rovi.dwMinorVersion >= 0 && rovi.dwBuildNumber >= 22000); -} - -static bool containsCursorToItem(QQuickItem *item) { - auto window = item->window(); - if ((window == nullptr) || !item || !item->isVisible()) { - return false; - } - auto point = window->mapFromGlobal(QCursor::pos()); - auto rect = QRectF(item->mapToItem(window->contentItem(), QPointF(0, 0)), item->size()); - if (rect.contains(point)) { - return true; - } - return false; -} - -static inline bool initializeFunctionPointers() { - HMODULE module = LoadLibraryW(L"dwmapi.dll"); - if (module) { - if (!pDwmSetWindowAttribute) { - pDwmSetWindowAttribute = - reinterpret_cast(GetProcAddress(module, "DwmSetWindowAttribute")); - if (!pDwmSetWindowAttribute) { - return false; - } - } - if (!pDwmExtendFrameIntoClientArea) { - pDwmExtendFrameIntoClientArea = reinterpret_cast( - GetProcAddress(module, "DwmExtendFrameIntoClientArea")); - if (!pDwmExtendFrameIntoClientArea) { - return false; - } - } - if (!pDwmIsCompositionEnabled) { - pDwmIsCompositionEnabled = - reinterpret_cast(::GetProcAddress(module, "DwmIsCompositionEnabled")); - if (!pDwmIsCompositionEnabled) { - return false; - } - } - if (!pDwmEnableBlurBehindWindow) { - pDwmEnableBlurBehindWindow = - reinterpret_cast(GetProcAddress(module, "DwmEnableBlurBehindWindow")); - if (!pDwmEnableBlurBehindWindow) { - return false; - } - } - if (!pSetWindowCompositionAttribute) { - HMODULE user32 = LoadLibraryW(L"user32.dll"); - if (!user32) { - return false; - } - pSetWindowCompositionAttribute = reinterpret_cast( - GetProcAddress(user32, "SetWindowCompositionAttribute")); - if (!pSetWindowCompositionAttribute) { - return false; - } - } - } - return true; -} - -static inline bool isWin1122H2OrGreater() { - RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); - return (rovi.dwMajorVersion > 10) || - (rovi.dwMajorVersion == 10 && rovi.dwMinorVersion >= 0 && rovi.dwBuildNumber >= 22621); -} - -static inline bool setWindowDarkMode(HWND hwnd, const BOOL enable) { - if (!initializeFunctionPointers()) { - return false; - } - return bool(pDwmSetWindowAttribute(hwnd, 20, &enable, sizeof(BOOL))); -} - -static inline bool isCompositionEnabled() { - if (initializeFunctionPointers()) { - BOOL composition_enabled = false; - pDwmIsCompositionEnabled(&composition_enabled); - return composition_enabled; - } - return false; -} - -static inline bool isWin8OrGreater() { - RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); - return (rovi.dwMajorVersion > 6) || (rovi.dwMajorVersion == 6 && rovi.dwMinorVersion >= 2); -} - -static inline bool setWindowEffect(HWND hwnd, const QString &key, const bool &enable) { - static constexpr const MARGINS extendedMargins = {-1, -1, -1, -1}; - if (key == QStringLiteral("mica")) { - if (!isWin11OrGreater() || !initializeFunctionPointers()) { - return false; - } - if (enable) { - pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins); - if (isWin1122H2OrGreater()) { - const DWORD backdropType = _DWMSBT_MAINWINDOW; - pDwmSetWindowAttribute(hwnd, 38, &backdropType, sizeof(backdropType)); - } else { - const BOOL enable = TRUE; - pDwmSetWindowAttribute(hwnd, 1029, &enable, sizeof(enable)); - } - } else { - if (isWin1122H2OrGreater()) { - const DWORD backdropType = _DWMSBT_AUTO; - pDwmSetWindowAttribute(hwnd, 38, &backdropType, sizeof(backdropType)); - } else { - const BOOL enable = FALSE; - pDwmSetWindowAttribute(hwnd, 1029, &enable, sizeof(enable)); - } - } - BOOL isDark = Theme::instance()->dark(); - setWindowDarkMode(hwnd, isDark); - return true; - } - - if (key == QStringLiteral("mica-alt")) { - if (!isWin1122H2OrGreater() || !initializeFunctionPointers()) { - return false; - } - if (enable) { - pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins); - const DWORD backdropType = _DWMSBT_TABBEDWINDOW; - pDwmSetWindowAttribute(hwnd, 38, &backdropType, sizeof(backdropType)); - } else { - const DWORD backdropType = _DWMSBT_AUTO; - pDwmSetWindowAttribute(hwnd, 38, &backdropType, sizeof(backdropType)); - } - BOOL isDark = Theme::instance()->dark(); - setWindowDarkMode(hwnd, isDark); - return true; - } - - if (key == QStringLiteral("acrylic")) { - if (!isWin11OrGreater() || !initializeFunctionPointers()) { - return false; - } - if (enable) { - pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins); - DWORD system_backdrop_type = _DWMSBT_TRANSIENTWINDOW; - pDwmSetWindowAttribute(hwnd, 38, &system_backdrop_type, sizeof(DWORD)); - } else { - const DWORD backdropType = _DWMSBT_AUTO; - pDwmSetWindowAttribute(hwnd, 38, &backdropType, sizeof(backdropType)); - } - BOOL isDark = Theme::instance()->dark(); - setWindowDarkMode(hwnd, isDark); - return true; - } - - if (key == QStringLiteral("dwm-blur")) { - if ((isWin7Only() && !isCompositionEnabled()) || !initializeFunctionPointers()) { - return false; - } - BOOL isDark = Theme::instance()->dark(); - setWindowDarkMode(hwnd, isDark && enable); - if (enable) { - if (isWin8OrGreater()) { - ACCENT_POLICY policy{}; - policy.dwAccentState = ACCENT_ENABLE_BLURBEHIND; - policy.dwAccentFlags = ACCENT_NONE; - WINDOWCOMPOSITIONATTRIBDATA wcad{}; - wcad.Attrib = WCA_ACCENT_POLICY; - wcad.pvData = &policy; - wcad.cbData = sizeof(policy); - pSetWindowCompositionAttribute(hwnd, &wcad); - } else { - DWM_BLURBEHIND bb{}; - bb.fEnable = TRUE; - bb.dwFlags = DWM_BB_ENABLE; - pDwmEnableBlurBehindWindow(hwnd, &bb); - } - } else { - if (isWin8OrGreater()) { - ACCENT_POLICY policy{}; - policy.dwAccentState = ACCENT_DISABLED; - policy.dwAccentFlags = ACCENT_NONE; - WINDOWCOMPOSITIONATTRIBDATA wcad{}; - wcad.Attrib = WCA_ACCENT_POLICY; - wcad.pvData = &policy; - wcad.cbData = sizeof(policy); - pSetWindowCompositionAttribute(hwnd, &wcad); - } else { - DWM_BLURBEHIND bb{}; - bb.fEnable = FALSE; - bb.dwFlags = DWM_BB_ENABLE; - pDwmEnableBlurBehindWindow(hwnd, &bb); - } - } - return true; - } - return false; -} - -Frameless::Frameless(QQuickItem *parent) : QQuickItem{parent} { - m_isWindows11OrGreater = Utilities::instance()->isWindows11OrGreater(); - m_effect = "normal"; -} - -QQuickItem *Frameless::appBar() const { - return m_appBar; -} - -void Frameless::setAppBar(QQuickItem *appBar) { - if (m_appBar != appBar) { - m_appBar = appBar; - emit appBarChanged(); - } -} - -QQuickItem *Frameless::maximizeButton() const { - return m_maximizeButton; -} - -void Frameless::setMaximizeButton(QQuickItem *button) { - if (m_maximizeButton != button) { - m_maximizeButton = button; - emit maximizeButtonChanged(); - } -} - -QQuickItem *Frameless::minimizedButton() const { - return m_minimizedButton; -} - -void Frameless::setMinimizedButton(QQuickItem *button) { - if (m_minimizedButton != button) { - m_minimizedButton = button; - emit minimizedButtonChanged(); - } -} - -QQuickItem *Frameless::closeButton() const { - return m_closeButton; -} - -void Frameless::setCloseButton(QQuickItem *button) { - if (m_closeButton != button) { - m_closeButton = button; - emit closeButtonChanged(); - } -} - -bool Frameless::fixSize() const { - return m_fixSize; -} - -void Frameless::setFixSize(bool fix) { - if (m_fixSize != fix) { - m_fixSize = fix; - emit fixSizeChanged(); - } -} - -bool Frameless::topmost() const { - return m_topmost; -} - -void Frameless::setTopmost(bool topmost) { - if (m_topmost != topmost) { - m_topmost = topmost; - emit topmostChanged(); - } -} - -bool Frameless::disabled() const { - return m_disabled; -} - -void Frameless::setDisabled(bool disabled) { - if (m_disabled != disabled) { - m_disabled = disabled; - emit disabledChanged(); - } -} - -void Frameless::setHitTestVisible(QQuickItem *item) { - if (!m_hitTestList.contains(item)) { - m_hitTestList.append(item); - } -} - -void Frameless::showMaximized() { -#ifdef Q_OS_WIN - HWND hwnd = reinterpret_cast(window()->winId()); - ::ShowWindow(hwnd, 3); -#else - window()->setVisibility(QQuickWindow::Maximized); -#endif -} - -void Frameless::showMinimized() { -#ifdef Q_OS_WIN - HWND hwnd = reinterpret_cast(window()->winId()); - ::ShowWindow(hwnd, 2); -#else - window()->setVisibility(QQuickWindow::Minimized); -#endif -} - -void Frameless::showNormal() { - window()->setVisibility(QQuickWindow::Windowed); -} - -void Frameless::onDestruction() { - QGuiApplication::instance()->removeNativeEventFilter(this); -} - -void Frameless::componentComplete() { - if (m_disabled) { - return; - } - int w = window()->width(); - int h = window()->height(); - m_current = window()->winId(); - window()->setFlags((window()->flags()) | Qt::CustomizeWindowHint | Qt::WindowMinimizeButtonHint | - Qt::WindowCloseButtonHint); - if (!m_fixSize) { - window()->setFlag(Qt::WindowMaximizeButtonHint); - } - window()->installEventFilter(this); - QGuiApplication::instance()->installNativeEventFilter(this); - if (m_maximizeButton) { - setHitTestVisible(m_maximizeButton); - } - if (m_minimizedButton) { - setHitTestVisible(m_minimizedButton); - } - if (m_closeButton) { - setHitTestVisible(m_closeButton); - } -#ifdef Q_OS_WIN -#if (QT_VERSION == QT_VERSION_CHECK(6, 5, 3)) - qWarning() << "Qt's own frameless bug, currently only exist in 6.5.3, please use other versions"; -#endif - HWND hwnd = reinterpret_cast(window()->winId()); - DWORD style = ::GetWindowLongPtr(hwnd, GWL_STYLE); -#if (QT_VERSION == QT_VERSION_CHECK(6, 7, 2)) - style &= ~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); -#endif - if (m_fixSize) { - ::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_THICKFRAME | WS_CAPTION); - ; - for (int i = 0; i <= QGuiApplication::screens().count() - 1; ++i) { - connect(QGuiApplication::screens().at(i), &QScreen::logicalDotsPerInchChanged, this, [=] { - SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, - SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_FRAMECHANGED); - }); - } - } else { - ::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION); - } - SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, - SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); - connect(window(), &QQuickWindow::screenChanged, this, [hwnd] { - ::SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOOWNERZORDER); - ::RedrawWindow(hwnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW); - }); - if (!window()->property("_hideShadow").toBool()) { - setShadow(hwnd); - } - if (isWin11OrGreater()) { - availableEffects({"mica", "mica-alt", "acrylic", "dwm-blur", "normal"}); - } else if (isWin7Only()) { - availableEffects({"dwm-blur", "normal"}); - } - if (!m_effect.isEmpty()) { - effective(setWindowEffect(hwnd, m_effect, true)); - if (effective()) { - m_currentEffect = effect(); - } - } - connect(this, &Frameless::effectChanged, this, [hwnd, this] { - if (effect() == m_currentEffect) { - return; - } - if (effective()) { - setWindowEffect(hwnd, m_currentEffect, false); - } - effective(setWindowEffect(hwnd, effect(), true)); - if (effective()) { - m_currentEffect = effect(); - } else { - m_effect = "normal"; - m_currentEffect = "normal"; - } - }); - connect(Theme::instance(), &Theme::blurBehindWindowEnabledChanged, this, [this] { - if (Theme::instance()->blurBehindWindowEnabled()) { - effect("normal"); - } - }); - connect(Theme::instance(), &Theme::darkChanged, this, [hwnd, this] { - if (effective() && !m_currentEffect.isEmpty() && m_currentEffect != "normal") { - setWindowDarkMode(hwnd, Theme::instance()->dark()); - } - }); -#endif - auto appBarHeight = m_appBar->height(); - h = qRound(h + appBarHeight); - if (m_fixSize) { - window()->setMaximumSize(QSize(w, h)); - window()->setMinimumSize(QSize(w, h)); - } else { - window()->setMinimumHeight(window()->minimumHeight() + appBarHeight); - window()->setMaximumHeight(window()->maximumHeight() + appBarHeight); - } - window()->resize(QSize(w, h)); - connect(this, &Frameless::topmostChanged, this, [this] { setWindowTopmost(topmost()); }); - setWindowTopmost(topmost()); -} - -bool Frameless::eventFilter(QObject *obj, QEvent *event) { -#ifndef Q_OS_WIN - switch (ev->type()) { - case QEvent::MouseButtonPress: - if (_edges != 0) { - QMouseEvent *event = static_cast(ev); - if (event->button() == Qt::LeftButton) { - _updateCursor(_edges); - window()->startSystemResize(Qt::Edges(_edges)); - } - } else { - if (_hitAppBar()) { - qint64 clickTimer = QDateTime::currentMSecsSinceEpoch(); - qint64 offset = clickTimer - this->_clickTimer; - this->_clickTimer = clickTimer; - if (offset < 300) { - if (_isMaximized()) { - showNormal(); - } else { - showMaximized(); - } - } else { - window()->startSystemMove(); - } - } - } - break; - case QEvent::MouseButtonRelease: - _edges = 0; - break; - case QEvent::MouseMove: { - if (_isMaximized() || _isFullScreen()) { - break; - } - if (_fixSize) { - break; - } - QMouseEvent *event = static_cast(ev); - QPoint p = -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - event->pos(); -#else - event->position().toPoint(); -#endif - if (p.x() >= _margins && p.x() <= (window()->width() - _margins) && p.y() >= _margins && - p.y() <= (window()->height() - _margins)) { - if (_edges != 0) { - _edges = 0; - _updateCursor(_edges); - } - break; - } - _edges = 0; - if (p.x() < _margins) { - _edges |= Qt::LeftEdge; - } - if (p.x() > (window()->width() - _margins)) { - _edges |= Qt::RightEdge; - } - if (p.y() < _margins) { - _edges |= Qt::TopEdge; - } - if (p.y() > (window()->height() - _margins)) { - _edges |= Qt::BottomEdge; - } - _updateCursor(_edges); - break; - } - default: - break; - } -#endif - return QObject::eventFilter(obj, event); -} - -bool Frameless::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) { -#ifdef Q_OS_WIN - if ((eventType != "windows_generic_MSG") || !message) { - return false; - } - const auto msg = static_cast(message); - auto hwnd = msg->hwnd; - if (!hwnd) { - return false; - } - const quint64 wid = reinterpret_cast(hwnd); - if (wid != m_current) { - return false; - } - const auto uMsg = msg->message; - const auto wParam = msg->wParam; - const auto lParam = msg->lParam; - if (uMsg == WM_WINDOWPOSCHANGING) { - auto *wp = reinterpret_cast(lParam); - if (wp != nullptr && (wp->flags & SWP_NOSIZE) == 0) { - wp->flags |= SWP_NOCOPYBITS; - *result = static_cast(::DefWindowProcW(hwnd, uMsg, wParam, lParam)); - return true; - } - return false; - } else if (uMsg == WM_NCCALCSIZE && wParam == TRUE) { - bool isMaximum = ::IsZoomed(hwnd); - if (isMaximum) { - window()->setProperty("__margins", 7); - } else { - window()->setProperty("__margins", 0); - } - setMaximizeHovered(false); - *result = WVR_REDRAW; - return true; - } else if (uMsg == WM_NCHITTEST) { - if (m_isWindows11OrGreater) { - if (hitMaximizeButton()) { - if (*result == HTNOWHERE) { - *result = HTZOOM; - } - setMaximizeHovered(true); - return true; - } - setMaximizeHovered(false); - setMaximizePressed(false); - } - *result = 0; - POINT nativeGlobalPos{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; - POINT nativeLocalPos = nativeGlobalPos; - ::ScreenToClient(hwnd, &nativeLocalPos); - RECT clientRect{0, 0, 0, 0}; - ::GetClientRect(hwnd, &clientRect); - auto clientWidth = clientRect.right - clientRect.left; - auto clientHeight = clientRect.bottom - clientRect.top; - bool left = nativeLocalPos.x < m_margins; - bool right = nativeLocalPos.x > clientWidth - m_margins; - bool top = nativeLocalPos.y < m_margins; - bool bottom = nativeLocalPos.y > clientHeight - m_margins; - *result = 0; - if (!m_fixSize && !isFullScreen() && !isMaximized()) { - if (left && top) { - *result = HTTOPLEFT; - } else if (left && bottom) { - *result = HTBOTTOMLEFT; - } else if (right && top) { - *result = HTTOPRIGHT; - } else if (right && bottom) { - *result = HTBOTTOMRIGHT; - } else if (left) { - *result = HTLEFT; - } else if (right) { - *result = HTRIGHT; - } else if (top) { - *result = HTTOP; - } else if (bottom) { - *result = HTBOTTOM; - } - } - if (0 != *result) { - return true; - } - if (hitAppBar()) { - *result = HTCAPTION; - return true; - } - *result = HTCLIENT; - return true; - } else if (uMsg == WM_NCPAINT) { - *result = FALSE; - return false; - } else if (uMsg == WM_NCACTIVATE) { - *result = TRUE; - return true; - } else if (m_isWindows11OrGreater && (uMsg == WM_NCLBUTTONDBLCLK || uMsg == WM_NCLBUTTONDOWN)) { - if (hitMaximizeButton()) { - QMouseEvent event = QMouseEvent(QEvent::MouseButtonPress, QPoint(), QPoint(), Qt::LeftButton, - Qt::LeftButton, Qt::NoModifier); - QGuiApplication::sendEvent(m_maximizeButton, &event); - setMaximizePressed(true); - return true; - } - } else if (m_isWindows11OrGreater && (uMsg == WM_NCLBUTTONUP || uMsg == WM_NCRBUTTONUP)) { - if (hitMaximizeButton()) { - QMouseEvent event = QMouseEvent(QEvent::MouseButtonRelease, QPoint(), QPoint(), Qt::LeftButton, - Qt::LeftButton, Qt::NoModifier); - QGuiApplication::sendEvent(m_maximizeButton, &event); - setMaximizePressed(false); - return true; - } - } else if (uMsg == WM_NCRBUTTONDOWN) { - if (wParam == HTCAPTION) { - auto pos = window()->position(); - auto offset = window()->mapFromGlobal(QCursor::pos()); - showSystemMenu(QPoint(pos.x() + offset.x(), pos.y() + offset.y())); - } - } else if (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN) { - const bool altPressed = ((wParam == VK_MENU) || (::GetKeyState(VK_MENU) < 0)); - const bool spacePressed = ((wParam == VK_SPACE) || (::GetKeyState(VK_SPACE) < 0)); - if (altPressed && spacePressed) { - auto pos = window()->position(); - showSystemMenu(QPoint(pos.x(), qRound(pos.y() + m_appBar->height()))); - } - } else if (uMsg == WM_SYSCOMMAND) { - if (wParam == SC_MINIMIZE) { - if (window()->transientParent()) { - auto _hwnd = reinterpret_cast(window()->transientParent()->winId()); - ::ShowWindow(_hwnd, 2); - } else { - auto _hwnd = reinterpret_cast(window()->winId()); - ::ShowWindow(_hwnd, 2); - } - return true; - } - return false; - } - return false; -#else - return false; -#endif -} - -bool Frameless::isFullScreen() { - return window()->visibility() == QWindow::FullScreen; -} - -bool Frameless::isMaximized() { - return window()->visibility() == QWindow::Maximized; -} - -void Frameless::setMaximizeHovered(bool val) { - if (m_maximizeButton) { - m_maximizeButton->setProperty("hover", val); - } -} - -void Frameless::setMaximizePressed(bool val) { - if (m_maximizeButton) { - m_maximizeButton->setProperty("down", val); - } -} - -void Frameless::setWindowTopmost(bool topmost) { -#ifdef Q_OS_WIN - HWND hwnd = reinterpret_cast(window()->winId()); - if (topmost) { - ::SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); - } else { - ::SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); - } -#else - window()->setFlag(Qt::WindowStaysOnTopHint, topmost); -#endif -} - -bool Frameless::hitMaximizeButton() { - if (containsCursorToItem(m_maximizeButton)) { - return true; - } - return false; -} - -bool Frameless::hitAppBar() { - for (int i = 0; i <= m_hitTestList.size() - 1; ++i) { - auto item = m_hitTestList.at(i); - if (containsCursorToItem(item)) { - return false; - } - } - if ((m_appBar != nullptr) && containsCursorToItem(m_appBar)) { - return true; - } - return false; -} - -void Frameless::showSystemMenu(QPoint point) { -#ifdef Q_OS_WIN - QScreen *screen = window()->screen(); - if (!screen) { - screen = QGuiApplication::primaryScreen(); - } - if (!screen) { - return; - } - const QPoint origin = screen->geometry().topLeft(); - auto nativePos = QPointF(QPointF(point - origin) * window()->devicePixelRatio()).toPoint() + origin; - HWND hwnd = reinterpret_cast(window()->winId()); - auto hMenu = ::GetSystemMenu(hwnd, FALSE); - if (isMaximized() || isFullScreen()) { - ::EnableMenuItem(hMenu, SC_MOVE, MFS_DISABLED); - ::EnableMenuItem(hMenu, SC_RESTORE, MFS_ENABLED); - } else { - ::EnableMenuItem(hMenu, SC_MOVE, MFS_ENABLED); - ::EnableMenuItem(hMenu, SC_RESTORE, MFS_DISABLED); - } - if (!m_fixSize && !isMaximized() && !isFullScreen()) { - ::EnableMenuItem(hMenu, SC_SIZE, MFS_ENABLED); - ::EnableMenuItem(hMenu, SC_MAXIMIZE, MFS_ENABLED); - } else { - ::EnableMenuItem(hMenu, SC_SIZE, MFS_DISABLED); - ::EnableMenuItem(hMenu, SC_MAXIMIZE, MFS_DISABLED); - } - const int result = - ::TrackPopupMenu(hMenu, (TPM_RETURNCMD | (QGuiApplication::isRightToLeft() ? TPM_RIGHTALIGN : TPM_LEFTALIGN)), - nativePos.x(), nativePos.y(), 0, hwnd, nullptr); - if (result) { - ::PostMessageW(hwnd, WM_SYSCOMMAND, result, 0); - } -#endif -} +#include "Frameless.h" +#include "Utilities.h" +#include +#include +#include +#include + +#ifdef Q_OS_WIN + +static DwmSetWindowAttributeFunc pDwmSetWindowAttribute = nullptr; +static DwmExtendFrameIntoClientAreaFunc pDwmExtendFrameIntoClientArea = nullptr; +static DwmIsCompositionEnabledFunc pDwmIsCompositionEnabled = nullptr; +static DwmEnableBlurBehindWindowFunc pDwmEnableBlurBehindWindow = nullptr; +static SetWindowCompositionAttributeFunc pSetWindowCompositionAttribute = nullptr; + +static RTL_OSVERSIONINFOW GetRealOSVersionImpl() { + HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll"); + using RtlGetVersionPtr = NTSTATUS(WINAPI *)(PRTL_OSVERSIONINFOW); + auto pRtlGetVersion = + reinterpret_cast(::GetProcAddress(hMod, "RtlGetVersion")); + RTL_OSVERSIONINFOW rovi{}; + rovi.dwOSVersionInfoSize = sizeof(rovi); + pRtlGetVersion(&rovi); + return rovi; +} + +RTL_OSVERSIONINFOW GetRealOSVersion() { + static const auto result = GetRealOSVersionImpl(); + return result; +} + +static inline bool isWin8OrGreater() { + RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); + return (rovi.dwMajorVersion > 6) || (rovi.dwMajorVersion == 6 && rovi.dwMinorVersion >= 2); +} + +static inline bool isWin8Point1OrGreater() { + RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); + return (rovi.dwMajorVersion > 6) || (rovi.dwMajorVersion == 6 && rovi.dwMinorVersion >= 3); +} + +static inline bool isWin10OrGreater() { + RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); + return (rovi.dwMajorVersion > 10) || (rovi.dwMajorVersion == 10 && rovi.dwMinorVersion >= 0); +} + +static inline bool isWin101809OrGreater() { + RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); + return (rovi.dwMajorVersion > 10) || + (rovi.dwMajorVersion == 10 && rovi.dwMinorVersion >= 0 && rovi.dwBuildNumber >= 17763); +} + +static inline bool isWin101903OrGreater() { + RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); + return (rovi.dwMajorVersion > 10) || + (rovi.dwMajorVersion == 10 && rovi.dwMinorVersion >= 0 && rovi.dwBuildNumber >= 18362); +} + +static inline bool isWin11OrGreater() { + RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); + return (rovi.dwMajorVersion > 10) || + (rovi.dwMajorVersion == 10 && rovi.dwMinorVersion >= 0 && rovi.dwBuildNumber >= 22000); +} + +static inline bool isWin1122H2OrGreater() { + RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); + return (rovi.dwMajorVersion > 10) || + (rovi.dwMajorVersion == 10 && rovi.dwMinorVersion >= 0 && rovi.dwBuildNumber >= 22621); +} + +static inline bool isWin10Only() { + return isWin10OrGreater() && !isWin11OrGreater(); +} + +static inline bool isWin7Only() { + RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); + return rovi.dwMajorVersion == 7; +} + +static inline QByteArray qtNativeEventType() { + static const auto result = "windows_generic_MSG"; + return result; +} + +static inline bool initializeFunctionPointers() { + HMODULE module = LoadLibraryW(L"dwmapi.dll"); + if (module) { + if (!pDwmSetWindowAttribute) { + pDwmSetWindowAttribute = reinterpret_cast( + GetProcAddress(module, "DwmSetWindowAttribute")); + if (!pDwmSetWindowAttribute) { + return false; + } + } + if (!pDwmExtendFrameIntoClientArea) { + pDwmExtendFrameIntoClientArea = reinterpret_cast( + GetProcAddress(module, "DwmExtendFrameIntoClientArea")); + if (!pDwmExtendFrameIntoClientArea) { + return false; + } + } + if (!pDwmIsCompositionEnabled) { + pDwmIsCompositionEnabled = reinterpret_cast( + ::GetProcAddress(module, "DwmIsCompositionEnabled")); + if (!pDwmIsCompositionEnabled) { + return false; + } + } + if (!pDwmEnableBlurBehindWindow) { + pDwmEnableBlurBehindWindow = + reinterpret_cast( + GetProcAddress(module, "DwmEnableBlurBehindWindow")); + if (!pDwmEnableBlurBehindWindow) { + return false; + } + } + if (!pSetWindowCompositionAttribute) { + HMODULE user32 = LoadLibraryW(L"user32.dll"); + if (!user32) { + return false; + } + pSetWindowCompositionAttribute = reinterpret_cast( + GetProcAddress(user32, "SetWindowCompositionAttribute")); + if (!pSetWindowCompositionAttribute) { + return false; + } + } + } + return true; +} + +static inline bool isCompositionEnabled() { + if(initializeFunctionPointers()){ + BOOL composition_enabled = false; + pDwmIsCompositionEnabled(&composition_enabled); + return composition_enabled; + } + return false; +} + +static inline void setShadow(HWND hwnd) { + const MARGINS shadow = {1, 0, 0, 0}; + if (initializeFunctionPointers()) { + pDwmExtendFrameIntoClientArea(hwnd, &shadow); + } + if(isWin7Only()){ + SetClassLong(hwnd, GCL_STYLE, GetClassLong(hwnd, GCL_STYLE) | CS_DROPSHADOW); + } +} + +static inline bool setWindowDarkMode(HWND hwnd, const BOOL enable) { + if (!initializeFunctionPointers()) { + return false; + } + return bool(pDwmSetWindowAttribute(hwnd, 20, &enable, sizeof(BOOL))); +} + +static inline bool setWindowEffect(HWND hwnd, const QString &key, const bool &enable) { + static constexpr const MARGINS extendedMargins = {-1, -1, -1, -1}; + if (key == QStringLiteral("mica")) { + if (!isWin11OrGreater() || !initializeFunctionPointers()) { + return false; + } + if (enable) { + pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins); + if (isWin1122H2OrGreater()) { + const DWORD backdropType = _DWMSBT_MAINWINDOW; + pDwmSetWindowAttribute(hwnd, 38, &backdropType, sizeof(backdropType)); + } else { + const BOOL enable = TRUE; + pDwmSetWindowAttribute(hwnd, 1029, &enable, sizeof(enable)); + } + } else { + if (isWin1122H2OrGreater()) { + const DWORD backdropType = _DWMSBT_AUTO; + pDwmSetWindowAttribute(hwnd, 38, &backdropType, sizeof(backdropType)); + } else { + const BOOL enable = FALSE; + pDwmSetWindowAttribute(hwnd, 1029, &enable, sizeof(enable)); + } + } + return true; + } + + if (key == QStringLiteral("mica-alt")) { + if (!isWin1122H2OrGreater() || !initializeFunctionPointers()) { + return false; + } + if (enable) { + pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins); + const DWORD backdropType = _DWMSBT_TABBEDWINDOW; + pDwmSetWindowAttribute(hwnd, 38, &backdropType, sizeof(backdropType)); + } else { + const DWORD backdropType = _DWMSBT_AUTO; + pDwmSetWindowAttribute(hwnd, 38, &backdropType, sizeof(backdropType)); + } + return true; + } + + if (key == QStringLiteral("acrylic")) { + if (!isWin11OrGreater() || !initializeFunctionPointers()) { + return false; + } + if (enable) { + pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins); + DWORD system_backdrop_type = _DWMSBT_TRANSIENTWINDOW; + pDwmSetWindowAttribute(hwnd, 38, &system_backdrop_type, sizeof(DWORD)); + } else { + const DWORD backdropType = _DWMSBT_AUTO; + pDwmSetWindowAttribute(hwnd, 38, &backdropType, sizeof(backdropType)); + } + return true; + } + + if (key == QStringLiteral("dwm-blur")) { + if ((isWin7Only() && !isCompositionEnabled()) || !initializeFunctionPointers()) { + return false; + } + if (enable) { + if (isWin8OrGreater()) { + ACCENT_POLICY policy{}; + policy.dwAccentState = ACCENT_ENABLE_BLURBEHIND; + policy.dwAccentFlags = ACCENT_NONE; + WINDOWCOMPOSITIONATTRIBDATA wcad{}; + wcad.Attrib = WCA_ACCENT_POLICY; + wcad.pvData = &policy; + wcad.cbData = sizeof(policy); + pSetWindowCompositionAttribute(hwnd, &wcad); + } else { + DWM_BLURBEHIND bb{}; + bb.fEnable = TRUE; + bb.dwFlags = DWM_BB_ENABLE; + pDwmEnableBlurBehindWindow(hwnd, &bb); + } + } else { + if (isWin8OrGreater()) { + ACCENT_POLICY policy{}; + policy.dwAccentState = ACCENT_DISABLED; + policy.dwAccentFlags = ACCENT_NONE; + WINDOWCOMPOSITIONATTRIBDATA wcad{}; + wcad.Attrib = WCA_ACCENT_POLICY; + wcad.pvData = &policy; + wcad.cbData = sizeof(policy); + pSetWindowCompositionAttribute(hwnd, &wcad); + } else { + DWM_BLURBEHIND bb{}; + bb.fEnable = FALSE; + bb.dwFlags = DWM_BB_ENABLE; + pDwmEnableBlurBehindWindow(hwnd, &bb); + } + } + return true; + } + return false; +} + +#endif + +bool containsCursorToItem(QQuickItem *item) { + if (!item || !item->isVisible()) { + return false; + } + auto point = item->window()->mapFromGlobal(QCursor::pos()); + auto rect = QRectF(item->mapToItem(item->window()->contentItem(), QPointF(0, 0)), item->size()); + if (rect.contains(point)) { + return true; + } + return false; +} + +Frameless::Frameless(QQuickItem *parent) : QQuickItem{parent} { + m_fixSize = false; + m_appBar = nullptr; + m_maximizeButton = nullptr; + m_minimizedButton = nullptr; + m_closeButton = nullptr; + m_topmost = false; + m_disabled = false; + m_effect = "normal"; + m_effective = false; + m_isWindows11OrGreater = Utilities::instance()->isWindows11OrGreater(); +} + +Frameless::~Frameless() = default; + +[[maybe_unused]] void Frameless::onDestruction() { + QGuiApplication::instance()->removeNativeEventFilter(this); +} + +void Frameless::componentComplete() { +#ifdef Q_OS_WIN + HWND hwnd = reinterpret_cast(window()->winId()); + if (isWin11OrGreater()) { + availableEffects({"mica", "mica-alt", "acrylic", "dwm-blur", "normal"}); + } else { + availableEffects({"dwm-blur","normal"}); + } + if (!_effect.isEmpty() && _useSystemEffect) { + effective(setWindowEffect(hwnd, _effect, true)); + if (effective()) { + _currentEffect = effect(); + } + } + connect(this, &Frameless::effectChanged, this, [hwnd, this] { + if (effect() == _currentEffect) { + return; + } + if (effective()) { + setWindowEffect(hwnd, _currentEffect, false); + } + effective(setWindowEffect(hwnd, effect(), true)); + if (effective()) { + _currentEffect = effect(); + _useSystemEffect = true; + } else { + _effect = "normal"; + _currentEffect = "normal"; + _useSystemEffect = false; + } + }); + connect(this, &Frameless::useSystemEffectChanged, this, [this] { + if (!_useSystemEffect) { + effect("normal"); + } + }); + connect(this, &Frameless::isDarkModeChanged, this, [hwnd, this] { + if (effective() && !_currentEffect.isEmpty() && _currentEffect != "normal") { + setWindowDarkMode(hwnd, _isDarkMode); + } + }); +#endif + if (m_disabled) { + return; + } + int w = window()->width(); + int h = window()->height(); + m_current = window()->winId(); + window()->setFlags((window()->flags()) | Qt::CustomizeWindowHint | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint); + if (!m_fixSize) { + window()->setFlag(Qt::WindowMaximizeButtonHint); + } + window()->installEventFilter(this); + QGuiApplication::instance()->installNativeEventFilter(this); + if (m_maximizeButton) { + setHitTestVisible(m_maximizeButton); + } + if (m_minimizedButton) { + setHitTestVisible(m_minimizedButton); + } + if (m_closeButton) { + setHitTestVisible(m_closeButton); + } +#ifdef Q_OS_WIN +#if (QT_VERSION == QT_VERSION_CHECK(6, 5, 3)) + qWarning()<<"Qt's own frameless bug, currently only exist in 6.5.3, please use other versions"; +#endif + if(!hwnd){ + hwnd = reinterpret_cast(window()->winId()); + } + DWORD style = ::GetWindowLongPtr(hwnd, GWL_STYLE); +# if (QT_VERSION == QT_VERSION_CHECK(6, 7, 2)) + style &= ~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); +# endif + if (_fixSize) { + ::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_THICKFRAME | WS_CAPTION);; + for (int i = 0; i <= QGuiApplication::screens().count() - 1; ++i) { + connect(QGuiApplication::screens().at(i), &QScreen::logicalDotsPerInchChanged, this, [=] { + SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_FRAMECHANGED); + }); + } + } else { + ::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION); + } + SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); + connect(window(), &QQuickWindow::screenChanged, this, [hwnd] { + ::SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOOWNERZORDER); + ::RedrawWindow(hwnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW); + }); + if (!window()->property("_hideShadow").toBool()) { + setShadow(hwnd); + } +#endif + auto appBarHeight = m_appBar->height(); + h = qRound(h + appBarHeight); + if (m_fixSize) { + window()->setMaximumSize(QSize(w, h)); + window()->setMinimumSize(QSize(w, h)); + } else { + window()->setMinimumHeight(window()->minimumHeight() + appBarHeight); + window()->setMaximumHeight(window()->maximumHeight() + appBarHeight); + } + window()->resize(QSize(w, h)); + connect(this, &Frameless::topmostChanged, this, [this] { _setWindowTopmost(topmost()); }); + _setWindowTopmost(topmost()); +} + +[[maybe_unused]] bool Frameless::nativeEventFilter(const QByteArray &eventType, void *message, + QT_NATIVE_EVENT_RESULT_TYPE *result) { +#ifdef Q_OS_WIN + if ((eventType != qtNativeEventType()) || !message) { + return false; + } + const auto msg = static_cast(message); + auto hwnd = msg->hwnd; + if (!hwnd) { + return false; + } + const quint64 wid = reinterpret_cast(hwnd); + if (wid != _current) { + return false; + } + const auto uMsg = msg->message; + const auto wParam = msg->wParam; + const auto lParam = msg->lParam; + if (uMsg == WM_WINDOWPOSCHANGING) { + auto *wp = reinterpret_cast(lParam); + if (wp != nullptr && (wp->flags & SWP_NOSIZE) == 0) { + wp->flags |= SWP_NOCOPYBITS; + *result = static_cast(::DefWindowProcW(hwnd, uMsg, wParam, lParam)); + return true; + } + return false; + } else if (uMsg == WM_NCCALCSIZE && wParam == TRUE) { + bool isMaximum = ::IsZoomed(hwnd); + if (isMaximum) { + window()->setProperty("__margins",7); + }else{ + window()->setProperty("__margins",0); + } + _setMaximizeHovered(false); + *result = WVR_REDRAW; + return true; + } else if (uMsg == WM_NCHITTEST) { + if (_isWindows11OrGreater) { + if (_hitMaximizeButton()) { + if (*result == HTNOWHERE) { + *result = HTZOOM; + } + _setMaximizeHovered(true); + return true; + } + _setMaximizeHovered(false); + _setMaximizePressed(false); + } + *result = 0; + POINT nativeGlobalPos{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; + POINT nativeLocalPos = nativeGlobalPos; + ::ScreenToClient(hwnd, &nativeLocalPos); + RECT clientRect{0, 0, 0, 0}; + ::GetClientRect(hwnd, &clientRect); + auto clientWidth = clientRect.right - clientRect.left; + auto clientHeight = clientRect.bottom - clientRect.top; + bool left = nativeLocalPos.x < _margins; + bool right = nativeLocalPos.x > clientWidth - _margins; + bool top = nativeLocalPos.y < _margins; + bool bottom = nativeLocalPos.y > clientHeight - _margins; + *result = 0; + if (!_fixSize && !_isFullScreen() && !_isMaximized()) { + if (left && top) { + *result = HTTOPLEFT; + } else if (left && bottom) { + *result = HTBOTTOMLEFT; + } else if (right && top) { + *result = HTTOPRIGHT; + } else if (right && bottom) { + *result = HTBOTTOMRIGHT; + } else if (left) { + *result = HTLEFT; + } else if (right) { + *result = HTRIGHT; + } else if (top) { + *result = HTTOP; + } else if (bottom) { + *result = HTBOTTOM; + } + } + if (0 != *result) { + return true; + } + if (_hitAppBar()) { + *result = HTCAPTION; + return true; + } + *result = HTCLIENT; + return true; + } else if (uMsg == WM_NCPAINT) { + *result = FALSE; + return false; + } else if (uMsg == WM_NCACTIVATE) { + *result = TRUE; + if (effective() || (!effect().isEmpty() && _currentEffect!="normal")) { + return false; + } + return true; + } else if (_isWindows11OrGreater && (uMsg == WM_NCLBUTTONDBLCLK || uMsg == WM_NCLBUTTONDOWN)) { + if (_hitMaximizeButton()) { + QMouseEvent event = QMouseEvent(QEvent::MouseButtonPress, QPoint(), QPoint(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + QGuiApplication::sendEvent(_maximizeButton, &event); + _setMaximizePressed(true); + return true; + } + } else if (_isWindows11OrGreater && (uMsg == WM_NCLBUTTONUP || uMsg == WM_NCRBUTTONUP)) { + if (_hitMaximizeButton()) { + QMouseEvent event = QMouseEvent(QEvent::MouseButtonRelease, QPoint(), QPoint(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + QGuiApplication::sendEvent(_maximizeButton, &event); + _setMaximizePressed(false); + return true; + } + } else if (uMsg == WM_NCRBUTTONDOWN) { + if (wParam == HTCAPTION) { + auto pos = window()->position(); + auto offset = window()->mapFromGlobal(QCursor::pos()); + _showSystemMenu(QPoint(pos.x() + offset.x(), pos.y() + offset.y())); + } + } else if (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN) { + const bool altPressed = ((wParam == VK_MENU) || (::GetKeyState(VK_MENU) < 0)); + const bool spacePressed = ((wParam == VK_SPACE) || (::GetKeyState(VK_SPACE) < 0)); + if (altPressed && spacePressed) { + auto pos = window()->position(); + _showSystemMenu(QPoint(pos.x(), qRound(pos.y() + _appbar->height()))); + } + } else if (uMsg == WM_SYSCOMMAND) { + if (wParam == SC_MINIMIZE) { + if (window()->transientParent()) { + auto _hwnd = reinterpret_cast(window()->transientParent()->winId()); + ::ShowWindow(_hwnd, 2); + } else { + auto _hwnd = reinterpret_cast(window()->winId()); + ::ShowWindow(_hwnd, 2); + } + return true; + } + return false; + } + return false; +#else + return false; +#endif +} + +bool Frameless::_isMaximized() { + return window()->visibility() == QWindow::Maximized; +} + +bool Frameless::_isFullScreen() { + return window()->visibility() == QWindow::FullScreen; +} + +void Frameless::_showSystemMenu(QPoint point) { +#ifdef Q_OS_WIN + QScreen *screen = window()->screen(); + if (!screen) { + screen = QGuiApplication::primaryScreen(); + } + if (!screen) { + return; + } + const QPoint origin = screen->geometry().topLeft(); + auto nativePos = QPointF(QPointF(point - origin) * window()->devicePixelRatio()).toPoint() + origin; + HWND hwnd = reinterpret_cast(window()->winId()); + auto hMenu = ::GetSystemMenu(hwnd, FALSE); + if (_isMaximized() || _isFullScreen()) { + ::EnableMenuItem(hMenu, SC_MOVE, MFS_DISABLED); + ::EnableMenuItem(hMenu, SC_RESTORE, MFS_ENABLED); + } else { + ::EnableMenuItem(hMenu, SC_MOVE, MFS_ENABLED); + ::EnableMenuItem(hMenu, SC_RESTORE, MFS_DISABLED); + } + if (!_fixSize && !_isMaximized() && !_isFullScreen()) { + ::EnableMenuItem(hMenu, SC_SIZE, MFS_ENABLED); + ::EnableMenuItem(hMenu, SC_MAXIMIZE, MFS_ENABLED); + } else { + ::EnableMenuItem(hMenu, SC_SIZE, MFS_DISABLED); + ::EnableMenuItem(hMenu, SC_MAXIMIZE, MFS_DISABLED); + } + const int result = ::TrackPopupMenu(hMenu, (TPM_RETURNCMD | (QGuiApplication::isRightToLeft() ? TPM_RIGHTALIGN : TPM_LEFTALIGN)), nativePos.x(), + nativePos.y(), 0, hwnd, nullptr); + if (result) { + ::PostMessageW(hwnd, WM_SYSCOMMAND, result, 0); + } +#endif +} + +bool Frameless::_hitAppBar() { + for (int i = 0; i <= m_hitTestList.size() - 1; ++i) { + auto item = m_hitTestList.at(i); + if (containsCursorToItem(item)) { + return false; + } + } + if (containsCursorToItem(m_appBar)) { + return true; + } + return false; +} + +bool Frameless::_hitMaximizeButton() { + if (containsCursorToItem(m_maximizeButton)) { + return true; + } + return false; +} + +void Frameless::_setMaximizePressed(bool val) { + if (m_maximizeButton) { + m_maximizeButton->setProperty("down", val); + } +} + +void Frameless::_setMaximizeHovered(bool val) { + if (m_maximizeButton) { + m_maximizeButton->setProperty("hover", val); + } +} + +void Frameless::_updateCursor(int edges) { + switch (edges) { + case 0: + window()->setCursor(Qt::ArrowCursor); + break; + case Qt::LeftEdge: + case Qt::RightEdge: + window()->setCursor(Qt::SizeHorCursor); + break; + case Qt::TopEdge: + case Qt::BottomEdge: + window()->setCursor(Qt::SizeVerCursor); + break; + case Qt::LeftEdge | Qt::TopEdge: + case Qt::RightEdge | Qt::BottomEdge: + window()->setCursor(Qt::SizeFDiagCursor); + break; + case Qt::RightEdge | Qt::TopEdge: + case Qt::LeftEdge | Qt::BottomEdge: + window()->setCursor(Qt::SizeBDiagCursor); + break; + default: + break; + } +} + +[[maybe_unused]] void Frameless::showFullScreen() { + window()->showFullScreen(); +} + +void Frameless::showMaximized() { +#ifdef Q_OS_WIN + HWND hwnd = reinterpret_cast(window()->winId()); + ::ShowWindow(hwnd, 3); +#else + window()->setVisibility(QQuickWindow::Maximized); +#endif +} + +[[maybe_unused]] void Frameless::showMinimized() { +#ifdef Q_OS_WIN + HWND hwnd = reinterpret_cast(window()->winId()); + ::ShowWindow(hwnd, 2); +#else + window()->setVisibility(QQuickWindow::Minimized); +#endif +} + +void Frameless::showNormal() { + window()->setVisibility(QQuickWindow::Windowed); +} + +void Frameless::setHitTestVisible(QQuickItem *val) { + if (!m_hitTestList.contains(val)) { + m_hitTestList.append(val); + } +} + +void Frameless::_setWindowTopmost(bool topmost) { +#ifdef Q_OS_WIN + HWND hwnd = reinterpret_cast(window()->winId()); + if (topmost) { + ::SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + } else { + ::SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + } +#else + window()->setFlag(Qt::WindowStaysOnTopHint,topmost); +#endif +} + +bool Frameless::eventFilter(QObject *obj, QEvent *ev) { +#ifndef Q_OS_WIN + switch (ev->type()) { + case QEvent::MouseButtonPress: + if (m_edges != 0) { + QMouseEvent *event = static_cast(ev); + if(event->button() == Qt::LeftButton){ + _updateCursor(m_edges); + window()->startSystemResize(Qt::Edges(m_edges)); + } + } else { + if(_hitAppBar()){ + qint64 clickTimer = QDateTime::currentMSecsSinceEpoch(); + qint64 offset = clickTimer - this->m_clickTimer; + this->m_clickTimer = clickTimer; + if(offset<300){ + if(_isMaximized()){ + showNormal(); + }else{ + showMaximized(); + } + }else{ + window()->startSystemMove(); + } + } + } + break; + case QEvent::MouseButtonRelease: + m_edges = 0; + break; + case QEvent::MouseMove: { + if(_isMaximized() || _isFullScreen()){ + break; + } + if (m_fixSize) { + break; + } + QMouseEvent *event = static_cast(ev); + QPoint p = +#if QT_VERSION < QT_VERSION_CHECK(6,0,0) + event->pos(); +#else + event->position().toPoint(); +#endif + if (p.x() >= m_margins && p.x() <= (window()->width() - m_margins) && p.y() >= m_margins && + p.y() <= (window()->height() - m_margins)) { + if (m_edges != 0) { + m_edges = 0; + _updateCursor(m_edges); + } + break; + } + m_edges = 0; + if (p.x() < m_margins) { + m_edges |= Qt::LeftEdge; + } + if (p.x() > (window()->width() - m_margins)) { + m_edges |= Qt::RightEdge; + } + if (p.y() < m_margins) { + m_edges |= Qt::TopEdge; + } + if (p.y() > (window()->height() - m_margins)) { + m_edges |= Qt::BottomEdge; + } + _updateCursor(m_edges); + break; + } + default: + break; + } +#endif + return QObject::eventFilter(obj, ev); +} diff --git a/Fluent/Frameless.h b/Fluent/Frameless.h index 856eb1d..69420e9 100644 --- a/Fluent/Frameless.h +++ b/Fluent/Frameless.h @@ -1,91 +1,180 @@ -#ifndef FRAMELESS_H -#define FRAMELESS_H - -#include "Utilities.h" -#include -#include - -class Frameless : public QQuickItem, QAbstractNativeEventFilter { - Q_OBJECT - QML_ELEMENT - Q_PROPERTY_AUTO(QString, effect) - Q_PROPERTY_READONLY_AUTO(bool, effective) - Q_PROPERTY_READONLY_AUTO(QStringList, availableEffects) - Q_PROPERTY(QQuickItem *appBar READ appBar WRITE setAppBar NOTIFY appBarChanged) - Q_PROPERTY(QQuickItem *maximizeButton READ maximizeButton WRITE setMaximizeButton NOTIFY maximizeButtonChanged) - Q_PROPERTY(QQuickItem *minimizedButton READ minimizedButton WRITE setMinimizedButton NOTIFY minimizedButtonChanged) - Q_PROPERTY(QQuickItem *closeButton READ closeButton WRITE setCloseButton NOTIFY closeButtonChanged) - - Q_PROPERTY(bool fixSize READ fixSize WRITE setFixSize NOTIFY fixSizeChanged) - Q_PROPERTY(bool topmost READ topmost WRITE setTopmost NOTIFY topmostChanged) - Q_PROPERTY(bool disabled READ disabled WRITE setDisabled NOTIFY disabledChanged) - -public: - Frameless(QQuickItem *parent = nullptr); - QQuickItem *appBar() const; - void setAppBar(QQuickItem *appBar); - - QQuickItem *maximizeButton() const; - void setMaximizeButton(QQuickItem *button); - - QQuickItem *minimizedButton() const; - void setMinimizedButton(QQuickItem *button); - - QQuickItem *closeButton() const; - void setCloseButton(QQuickItem *button); - - bool fixSize() const; - void setFixSize(bool fix); - - bool topmost() const; - void setTopmost(bool topmost); - - bool disabled() const; - void setDisabled(bool disabled); - - Q_INVOKABLE void showMaximized(); - Q_INVOKABLE void showMinimized(); - Q_INVOKABLE void showNormal(); - - Q_INVOKABLE void setHitTestVisible(QQuickItem *item); - Q_INVOKABLE void onDestruction(); - void componentComplete() final; - - bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) final; - -signals: - void appBarChanged(); - void maximizeButtonChanged(); - void minimizedButtonChanged(); - void closeButtonChanged(); - void fixSizeChanged(); - void topmostChanged(); - void disabledChanged(); - -protected: - bool isFullScreen(); - bool isMaximized(); - void setMaximizeHovered(bool val); - void setMaximizePressed(bool val); - void setWindowTopmost(bool topmost); - bool hitMaximizeButton(); - bool hitAppBar(); - void showSystemMenu(QPoint point); - bool eventFilter(QObject *obj, QEvent *event) final; - -private: - quint64 m_current = 0; - QQuickItem *m_appBar = nullptr; - QQuickItem *m_maximizeButton = nullptr; - QQuickItem *m_minimizedButton = nullptr; - QQuickItem *m_closeButton = nullptr; - bool m_fixSize = false; - bool m_topmost = false; - bool m_disabled = false; - int m_margins = 8; - QList> m_hitTestList; - bool m_isWindows11OrGreater = false; - QString m_currentEffect; -}; - -#endif // FRAMELESS_H +#ifndef __FRAMELESS_H__ +#define __FRAMELESS_H__ + +#include "Utilities.h" +#include +#include +#include +#include + +#ifdef Q_OS_WIN + +#pragma comment (lib, "user32.lib") +#pragma comment (lib, "dwmapi.lib") + +#include +#include +#include +enum _DWM_SYSTEMBACKDROP_TYPE { + _DWMSBT_AUTO, // [Default] Let DWM automatically decide the system-drawn backdrop for this + // window. + _DWMSBT_NONE, // [Disable] Do not draw any system backdrop. + _DWMSBT_MAINWINDOW, // [Mica] Draw the backdrop material effect corresponding to a + // long-lived window. + _DWMSBT_TRANSIENTWINDOW, // [Acrylic] Draw the backdrop material effect corresponding to a + // transient window. + _DWMSBT_TABBEDWINDOW, // [Mica Alt] Draw the backdrop material effect corresponding to a + // window with a tabbed title bar. +}; +enum WINDOWCOMPOSITIONATTRIB { + 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 +}; + +enum ACCENT_STATE { + ACCENT_DISABLED = 0, + ACCENT_ENABLE_GRADIENT = 1, + ACCENT_ENABLE_TRANSPARENTGRADIENT = 2, + ACCENT_ENABLE_BLURBEHIND = 3, // Traditional DWM blur + ACCENT_ENABLE_ACRYLICBLURBEHIND = 4, // RS4 1803 + ACCENT_ENABLE_HOST_BACKDROP = 5, // RS5 1809 + ACCENT_INVALID_STATE = 6 // Using this value will remove the window background +}; + +enum ACCENT_FLAG { + ACCENT_NONE = 0, + ACCENT_ENABLE_ACRYLIC = 1, + ACCENT_ENABLE_ACRYLIC_WITH_LUMINOSITY = 482 +}; + +struct ACCENT_POLICY { + DWORD dwAccentState; + DWORD dwAccentFlags; + DWORD dwGradientColor; // #AABBGGRR + DWORD dwAnimationId; +}; +using PACCENT_POLICY = ACCENT_POLICY *; +struct WINDOWCOMPOSITIONATTRIBDATA { + WINDOWCOMPOSITIONATTRIB Attrib; + PVOID pvData; + SIZE_T cbData; +}; +using PWINDOWCOMPOSITIONATTRIBDATA = WINDOWCOMPOSITIONATTRIBDATA *; + +typedef HRESULT (WINAPI *DwmSetWindowAttributeFunc)(HWND hwnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute); +typedef HRESULT (WINAPI *DwmExtendFrameIntoClientAreaFunc)(HWND hwnd, const MARGINS *pMarInset); +typedef HRESULT (WINAPI *DwmIsCompositionEnabledFunc)(BOOL *pfEnabled); +typedef HRESULT (WINAPI *DwmEnableBlurBehindWindowFunc)(HWND hWnd, const DWM_BLURBEHIND *pBlurBehind); +typedef BOOL (WINAPI *SetWindowCompositionAttributeFunc)(HWND hwnd, const WINDOWCOMPOSITIONATTRIBDATA *); + +#endif + +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) +using QT_NATIVE_EVENT_RESULT_TYPE = qintptr; +using QT_ENTER_EVENT_TYPE = QEnterEvent; +#else +using QT_NATIVE_EVENT_RESULT_TYPE = long; +using QT_ENTER_EVENT_TYPE = QEvent; +#endif + +class Frameless : public QQuickItem, QAbstractNativeEventFilter { + Q_OBJECT + QML_ELEMENT + Q_PROPERTY_AUTO_P(QQuickItem *, appBar) + Q_PROPERTY_AUTO_P(QQuickItem *, maximizeButton) + Q_PROPERTY_AUTO_P(QQuickItem *, minimizedButton) + Q_PROPERTY_AUTO_P(QQuickItem *, closeButton) + Q_PROPERTY_AUTO(bool, topmost) + Q_PROPERTY_AUTO(bool, disabled) + Q_PROPERTY_AUTO(bool, fixSize) + Q_PROPERTY_AUTO(QString, effect) + Q_PROPERTY_READONLY_AUTO(bool, effective) + Q_PROPERTY_READONLY_AUTO(QStringList, availableEffects) + Q_PROPERTY_AUTO(bool, isDarkMode) + Q_PROPERTY_AUTO(bool, useSystemEffect) + +public: + explicit Frameless(QQuickItem *parent = nullptr); + + ~Frameless() override; + + void componentComplete() override; + + [[maybe_unused]] bool nativeEventFilter(const QByteArray &eventType, void *message, + QT_NATIVE_EVENT_RESULT_TYPE *result) override; + + [[maybe_unused]] Q_INVOKABLE void showFullScreen(); + + Q_INVOKABLE void showMaximized(); + + [[maybe_unused]] Q_INVOKABLE void showMinimized(); + + Q_INVOKABLE void showNormal(); + + Q_INVOKABLE void setHitTestVisible(QQuickItem *); + + [[maybe_unused]] Q_INVOKABLE void onDestruction(); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + +private: + bool _isFullScreen(); + + bool _isMaximized(); + + void _updateCursor(int edges); + + void _setWindowTopmost(bool topmost); + + void _showSystemMenu(QPoint point); + + bool _hitAppBar(); + + bool _hitMaximizeButton(); + + void _setMaximizePressed(bool val); + + void _setMaximizeHovered(bool val); + + +private: + quint64 m_current = 0; + int m_edges = 0; + int m_margins = 8; + quint64 m_clickTimer = 0; + bool m_isWindows11OrGreater = false; + QList> m_hitTestList; + QString m_currentEffect; +}; + +#endif // __FRAMELESS_H__ \ No newline at end of file diff --git a/Fluent/QHotkey/CMakeLists.txt b/Fluent/QHotkey/CMakeLists.txt index 2618864..4687119 100644 --- a/Fluent/QHotkey/CMakeLists.txt +++ b/Fluent/QHotkey/CMakeLists.txt @@ -1,7 +1,9 @@ add_library(QHotkey qhotkey.h qhotkey.cpp - qhotkey_p.h qhotkey_win.cpp + qhotkey_p.h + $<$:qhotkey_win.cpp> + $<$:qhotkey_x11.cpp> ) target_include_directories(QHotkey @@ -10,4 +12,5 @@ target_include_directories(QHotkey target_link_libraries(QHotkey PUBLIC Qt${QT_VERSION_MAJOR}::Gui + $<$:X11> ) diff --git a/Fluent/Utilities.cpp b/Fluent/Utilities.cpp index dd43438..d1c9a8b 100644 --- a/Fluent/Utilities.cpp +++ b/Fluent/Utilities.cpp @@ -1,133 +1,335 @@ -#include "Utilities.h" -#include -#include - -Utilities *Utilities::instance() { - static Utilities *self = nullptr; - if (self == nullptr) { - self = new Utilities(); - } - return self; -} - -Utilities *Utilities::create(QQmlEngine *, QJSEngine *) { - auto ret = instance(); - QJSEngine::setObjectOwnership(ret, QJSEngine::CppOwnership); - return ret; -} - -Utilities::Utilities(QObject *parent) : QObject{parent} { -} - -bool Utilities::isSoftware() { - return QQuickWindow::sceneGraphBackend() == "software"; -} - -void Utilities::deleteLater(QObject *p) { - if (p) { - p->deleteLater(); - } -} - -QColor Utilities::withOpacity(const QColor &color, qreal opacity) { - int alpha = qRound(opacity * 255) & 0xff; - return QColor::fromRgba((alpha << 24) | (color.rgba() & 0xffffff)); -} - -QRect Utilities::desktopAvailableGeometry(QQuickWindow *window) { - return window->screen()->availableGeometry(); -} - -QString Utilities::getWallpaperFilePath() { -#if defined(Q_OS_WIN) - wchar_t path[MAX_PATH] = {}; - if (::SystemParametersInfoW(SPI_GETDESKWALLPAPER, MAX_PATH, path, FALSE) == FALSE) { - return {}; - } - return QString::fromWCharArray(path); -#elif defined(Q_OS_LINUX) - auto type = QSysInfo::productType(); - if (type == "uos") { - QProcess process; - QStringList args; - args << "--session"; - args << "--type=method_call"; - args << "--print-reply"; - args << "--dest=com.deepin.wm"; - args << "/com/deepin/wm"; - args << "com.deepin.wm.GetCurrentWorkspaceBackgroundForMonitor"; - args << QString("string:'%1'").arg(currentTimestamp()); - process.start("dbus-send", args); - process.waitForFinished(); - QByteArray result = process.readAllStandardOutput().trimmed(); - int startIndex = result.indexOf("file:///"); - if (startIndex != -1) { - auto path = result.mid(startIndex + 7, result.length() - startIndex - 8); - return path; - } - } -#elif defined(Q_OS_MACOS) - QProcess process; - QStringList args; - args << "-e"; - args << R"(tell application "Finder" to get POSIX path of (desktop picture as alias))"; - process.start("osascript", args); - process.waitForFinished(); - QByteArray result = process.readAllStandardOutput().trimmed(); - if (result.isEmpty()) { - return "/System/Library/CoreServices/DefaultDesktop.heic"; - } - return result; -#else - return {}; -#endif -} - -QUrl Utilities::getUrlByFilePath(const QString &path) { - return QUrl::fromLocalFile(path); -} - -bool Utilities::isMacos() { -#if defined(Q_OS_MACOS) - return true; -#else - return false; -#endif -} - -bool Utilities::isWin() { -#if defined(Q_OS_WIN) - return true; -#else - return false; -#endif -} - -int Utilities::windowBuildNumber() { -#if defined(Q_OS_WIN) - QSettings regKey{QString::fromUtf8(R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)"), - QSettings::NativeFormat}; - if (regKey.contains(QString::fromUtf8("CurrentBuildNumber"))) { - auto buildNumber = regKey.value(QString::fromUtf8("CurrentBuildNumber")).toInt(); - return buildNumber; - } -#endif - return -1; -} - -bool Utilities::isWindows11OrGreater() { - static QVariant var; - if (var.isNull()) { -#if defined(Q_OS_WIN) - auto buildNumber = windowBuildNumber(); - if (buildNumber >= 22000) { - var = QVariant::fromValue(true); - return true; - } -#endif - var = QVariant::fromValue(false); - return false; - } else { - return var.toBool(); - } -} +#include "Utilities.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_WIN +# pragma comment(lib, "user32.lib") + +# include +# include + +#endif + +Utilities *Utilities::instance() { + static Utilities *self = nullptr; + if (self == nullptr) { + self = new Utilities(); + } + return self; +} + +Utilities *Utilities::create(QQmlEngine *, QJSEngine *) { + auto ret = instance(); + QJSEngine::setObjectOwnership(ret, QJSEngine::CppOwnership); + return ret; +} + +Utilities::Utilities(QObject *parent) : QObject{parent} { +} + +void Utilities::clipText(const QString &text) { + QGuiApplication::clipboard()->setText(text); +} + +QString Utilities::uuid() { + return QUuid::createUuid().toString().remove('-').remove('{').remove('}'); +} + +QString Utilities::readFile(const QString &fileName) { + QString content; + QFile file(fileName); + if (file.open(QIODevice::ReadOnly)) { + QTextStream stream(&file); + content = stream.readAll(); + } + return content; +} + +bool Utilities::isMacos() { +#if defined(Q_OS_MACOS) + return true; +#else + return false; +#endif +} + +bool Utilities::isLinux() { +#if defined(Q_OS_LINUX) + return true; +#else + return false; +#endif +} + +bool Utilities::isWin() { +#if defined(Q_OS_WIN) + return true; +#else + return false; +#endif +} + +int Utilities::qtMajor() { + const QString qtVersion = QString::fromLatin1(qVersion()); + const QStringList versionParts = qtVersion.split('.'); + return versionParts[0].toInt(); +} + +int Utilities::qtMinor() { + const QString qtVersion = QString::fromLatin1(qVersion()); + const QStringList versionParts = qtVersion.split('.'); + return versionParts[1].toInt(); +} + +void Utilities::setQuitOnLastWindowClosed(bool val) { + QGuiApplication::setQuitOnLastWindowClosed(val); +} + +void Utilities::setOverrideCursor(Qt::CursorShape shape) { + QGuiApplication::setOverrideCursor(QCursor(shape)); +} + +void Utilities::restoreOverrideCursor() { + QGuiApplication::restoreOverrideCursor(); +} + +void Utilities::deleteLater(QObject *p) { + if (p) { + p->deleteLater(); + } +} + +QString Utilities::toLocalPath(const QUrl &url) { + return url.toLocalFile(); +} + +QString Utilities::getFileNameByUrl(const QUrl &url) { + return QFileInfo(url.toLocalFile()).fileName(); +} + +QString Utilities::html2PlantText(const QString &html) { + QTextDocument textDocument; + textDocument.setHtml(html); + return textDocument.toPlainText(); +} + +QRect Utilities::getVirtualGeometry() { + return QGuiApplication::primaryScreen()->virtualGeometry(); +} + +QString Utilities::getApplicationDirPath() { + return QGuiApplication::applicationDirPath(); +} + +QUrl Utilities::getUrlByFilePath(const QString &path) { + return QUrl::fromLocalFile(path); +} + +QColor Utilities::withOpacity(const QColor &color, qreal opacity) { + int alpha = qRound(opacity * 255) & 0xff; + return QColor::fromRgba((alpha << 24) | (color.rgba() & 0xffffff)); +} + +QString Utilities::md5(const QString &text) { + return QCryptographicHash::hash(text.toUtf8(), QCryptographicHash::Md5).toHex(); +} + +QString Utilities::toBase64(const QString &text) { + return text.toUtf8().toBase64(); +} + +QString Utilities::fromBase64(const QString &text) { + return QByteArray::fromBase64(text.toUtf8()); +} + +bool Utilities::removeDir(const QString &dirPath) { + QDir qDir(dirPath); + return qDir.removeRecursively(); +} + +bool Utilities::removeFile(const QString &filePath) { + QFile file(filePath); + return file.remove(); +} + +QString Utilities::sha256(const QString &text) { + return QCryptographicHash::hash(text.toUtf8(), QCryptographicHash::Sha256).toHex(); +} + +void Utilities::showFileInFolder(const QString &path) { +#if defined(Q_OS_WIN) + QProcess::startDetached("explorer.exe", {"/select,", QDir::toNativeSeparators(path)}); +#endif +#if defined(Q_OS_LINUX) + QFileInfo fileInfo(path); + auto process = "xdg-open"; + auto arguments = {fileInfo.absoluteDir().absolutePath()}; + QProcess::startDetached(process, arguments); +#endif +#if defined(Q_OS_MACOS) + QProcess::execute("/usr/bin/osascript", + {"-e", "tell application \"Finder\" to reveal POSIX file \"" + path + "\""}); + QProcess::execute("/usr/bin/osascript", {"-e", "tell application \"Finder\" to activate"}); +#endif +} + +bool Utilities::isSoftware() { + return QQuickWindow::sceneGraphBackend() == "software"; +} + +QPoint Utilities::cursorPos() { + return QCursor::pos(); +} + +qint64 Utilities::currentTimestamp() { + return QDateTime::currentMSecsSinceEpoch(); +} + +QIcon Utilities::windowIcon() { + return QGuiApplication::windowIcon(); +} + +int Utilities::cursorScreenIndex() { + int screenIndex = 0; + int screenCount = QGuiApplication::screens().count(); + if (screenCount > 1) { + QPoint pos = QCursor::pos(); + for (int i = 0; i <= screenCount - 1; ++i) { + if (QGuiApplication::screens().at(i)->geometry().contains(pos)) { + screenIndex = i; + break; + } + } + } + return screenIndex; +} + +int Utilities::windowBuildNumber() { +#if defined(Q_OS_WIN) + QSettings regKey{ + QString::fromUtf8(R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)"), + QSettings::NativeFormat}; + if (regKey.contains(QString::fromUtf8("CurrentBuildNumber"))) { + auto buildNumber = regKey.value(QString::fromUtf8("CurrentBuildNumber")).toInt(); + return buildNumber; + } +#endif + return -1; +} + +bool Utilities::isWindows11OrGreater() { + static QVariant var; + if (var.isNull()) { +#if defined(Q_OS_WIN) + auto buildNumber = windowBuildNumber(); + if (buildNumber >= 22000) { + var = QVariant::fromValue(true); + return true; + } +#endif + var = QVariant::fromValue(false); + return false; + } else { + return var.toBool(); + } +} + +bool Utilities::isWindows10OrGreater() { + static QVariant var; + if (var.isNull()) { +#if defined(Q_OS_WIN) + auto buildNumber = windowBuildNumber(); + if (buildNumber >= 10240) { + var = QVariant::fromValue(true); + return true; + } +#endif + var = QVariant::fromValue(false); + return false; + } else { + return var.toBool(); + } +} + +QRect Utilities::desktopAvailableGeometry(QQuickWindow *window) { + return window->screen()->availableGeometry(); +} + +QString Utilities::getWallpaperFilePath() { +#if defined(Q_OS_WIN) + wchar_t path[MAX_PATH] = {}; + if (::SystemParametersInfoW(SPI_GETDESKWALLPAPER, MAX_PATH, path, FALSE) == FALSE) { + return {}; + } + return QString::fromWCharArray(path); +#elif defined(Q_OS_LINUX) + auto type = QSysInfo::productType(); + if (type == "uos") { + QProcess process; + QStringList args; + args << "--session"; + args << "--type=method_call"; + args << "--print-reply"; + args << "--dest=com.deepin.wm"; + args << "/com/deepin/wm"; + args << "com.deepin.wm.GetCurrentWorkspaceBackgroundForMonitor"; + args << QString("string:'%1'").arg(currentTimestamp()); + process.start("dbus-send", args); + process.waitForFinished(); + QByteArray result = process.readAllStandardOutput().trimmed(); + int startIndex = result.indexOf("file:///"); + if (startIndex != -1) { + auto path = result.mid(startIndex + 7, result.length() - startIndex - 8); + return path; + } + } +#elif defined(Q_OS_MACOS) + QProcess process; + QStringList args; + args << "-e"; + args << R"(tell application "Finder" to get POSIX path of (desktop picture as alias))"; + process.start("osascript", args); + process.waitForFinished(); + QByteArray result = process.readAllStandardOutput().trimmed(); + if (result.isEmpty()) { + return "/System/Library/CoreServices/DefaultDesktop.heic"; + } + return result; +#else + return {}; +#endif +} + +QColor Utilities::imageMainColor(const QImage &image, double bright) { + int step = 20; + int t = 0; + int r = 0, g = 0, b = 0; + for (int i = 0; i < image.width(); i += step) { + for (int j = 0; j < image.height(); j += step) { + if (image.valid(i, j)) { + t++; + QColor c = image.pixel(i, j); + r += c.red(); + b += c.blue(); + g += c.green(); + } + } + } + return QColor(int(bright * r / t) > 255 ? 255 : int(bright * r / t), + int(bright * g / t) > 255 ? 255 : int(bright * g / t), + int(bright * b / t) > 255 ? 255 : int(bright * b / t)); +} diff --git a/Fluent/Utilities.h b/Fluent/Utilities.h index f6242dd..943ff65 100644 --- a/Fluent/Utilities.h +++ b/Fluent/Utilities.h @@ -1,108 +1,175 @@ -#ifndef UTILITIES_H -#define UTILITIES_H - -#include -#include -#include - -#define Q_PROPERTY_AUTO(TYPE, M) \ - Q_PROPERTY(TYPE M MEMBER m_##M NOTIFY M##Changed) \ -public: \ - Q_SIGNAL void M##Changed(); \ - void M(const TYPE &in_##M) { \ - m_##M = in_##M; \ - Q_EMIT M##Changed(); \ - } \ - TYPE M() { \ - return m_##M; \ - } \ - \ -private: \ - TYPE m_##M; - -#define Q_PROPERTY_READONLY_AUTO(TYPE, M) \ - Q_PROPERTY(TYPE M READ M NOTIFY M##Changed FINAL) \ -public: \ - Q_SIGNAL void M##Changed(); \ - void M(const TYPE &in_##M) { \ - m_##M = in_##M; \ - Q_EMIT M##Changed(); \ - } \ - TYPE M() { \ - return m_##M; \ - } \ - \ -private: \ - TYPE m_##M; - -#define Q_PROPERTY_AUTO_P(TYPE, M) \ - Q_PROPERTY(TYPE M MEMBER m_##M NOTIFY M##Changed) \ -public: \ - Q_SIGNAL void M##Changed(); \ - void M(TYPE in_##M) { \ - m_##M = in_##M; \ - Q_EMIT M##Changed(); \ - } \ - TYPE M() { \ - return m_##M; \ - } \ - \ -private: \ - TYPE m_##M; - -namespace WindowType { -Q_NAMESPACE -enum LaunchMode { - Standard = 0x0000, - SingleTask = 0x0001, - SingleInstance = 0x0002, -}; -Q_ENUM_NS(LaunchMode) -QML_ELEMENT -} // namespace WindowType - -namespace ThemeType { -Q_NAMESPACE -enum DarkMode { - System = 0x0000, - Light = 0x0001, - Dark = 0x0002, -}; -Q_ENUM_NS(DarkMode) -QML_ELEMENT -} // namespace ThemeType - -namespace ContentDialogType { -Q_NAMESPACE -enum ButtonFlag { - NeutralButton = 0x0001, - NegativeButton = 0x0002, - PositiveButton = 0x0004, -}; -Q_ENUM_NS(ButtonFlag) -QML_ELEMENT -} // namespace ContentDialogType - -class Utilities : public QObject { - Q_OBJECT - QML_ELEMENT - QML_SINGLETON -public: - static Utilities *instance(); - static Utilities *create(QQmlEngine *, QJSEngine *); - Q_INVOKABLE int windowBuildNumber(); - Q_INVOKABLE bool isWindows11OrGreater(); - Q_INVOKABLE bool isWin(); - Q_INVOKABLE bool isMacos(); - Q_INVOKABLE QRect desktopAvailableGeometry(QQuickWindow *window); - Q_INVOKABLE QUrl getUrlByFilePath(const QString &path); - Q_INVOKABLE bool isSoftware(); - Q_INVOKABLE void deleteLater(QObject *p); - Q_INVOKABLE QColor withOpacity(const QColor &, qreal alpha); - Q_INVOKABLE QString getWallpaperFilePath(); - -protected: - Utilities(QObject *parent = nullptr); -}; - -#endif // UTILITIES_H +#ifndef __UTILITIES_H__ +#define __UTILITIES_H__ + +#include +#include +#include +#include +#include + +#define Q_PROPERTY_AUTO(TYPE, M) \ +Q_PROPERTY(TYPE M MEMBER m_##M NOTIFY M##Changed) \ + public: \ + Q_SIGNAL void M##Changed(); \ + void M(const TYPE &in_##M) { \ + m_##M = in_##M; \ + Q_EMIT M##Changed(); \ +} \ + TYPE M() { \ + return m_##M; \ +} \ + \ + private: \ + TYPE m_##M; + +#define Q_PROPERTY_READONLY_AUTO(TYPE, M) \ +Q_PROPERTY(TYPE M READ M NOTIFY M##Changed FINAL) \ + public: \ + Q_SIGNAL void M##Changed(); \ + void M(const TYPE &in_##M) { \ + m_##M = in_##M; \ + Q_EMIT M##Changed(); \ +} \ + TYPE M() { \ + return m_##M; \ +} \ + \ + private: \ + TYPE m_##M; + +#define Q_PROPERTY_AUTO_P(TYPE, M) \ +Q_PROPERTY(TYPE M MEMBER m_##M NOTIFY M##Changed) \ + public: \ + Q_SIGNAL void M##Changed(); \ + void M(TYPE in_##M) { \ + m_##M = in_##M; \ + Q_EMIT M##Changed(); \ +} \ + TYPE M() { \ + return m_##M; \ +} \ + \ + private: \ + TYPE m_##M; + +namespace WindowType { +Q_NAMESPACE +enum LaunchMode { + Standard = 0x0000, + SingleTask = 0x0001, + SingleInstance = 0x0002, +}; +Q_ENUM_NS(LaunchMode) +QML_ELEMENT +} // namespace WindowType + +namespace ThemeType { +Q_NAMESPACE +enum DarkMode { + System = 0x0000, + Light = 0x0001, + Dark = 0x0002, +}; +Q_ENUM_NS(DarkMode) +QML_ELEMENT +} // namespace ThemeType + +namespace ContentDialogType { +Q_NAMESPACE +enum ButtonFlag { + NeutralButton = 0x0001, + NegativeButton = 0x0002, + PositiveButton = 0x0004, +}; +Q_ENUM_NS(ButtonFlag) +QML_ELEMENT +} // namespace ContentDialogType + +class Utilities : public QObject { + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + +private: + explicit Utilities(QObject *parent = nullptr); + +public: + static Utilities *instance(); + static Utilities *create(QQmlEngine *, QJSEngine *); + + Q_INVOKABLE int qtMajor(); + + Q_INVOKABLE int qtMinor(); + + Q_INVOKABLE bool isMacos(); + + Q_INVOKABLE bool isLinux(); + + Q_INVOKABLE bool isWin(); + + Q_INVOKABLE void clipText(const QString &text); + + Q_INVOKABLE QString uuid(); + + Q_INVOKABLE QString readFile(const QString &fileName); + + Q_INVOKABLE void setQuitOnLastWindowClosed(bool val); + + Q_INVOKABLE void setOverrideCursor(Qt::CursorShape shape); + + Q_INVOKABLE void restoreOverrideCursor(); + + Q_INVOKABLE QString html2PlantText(const QString &html); + + Q_INVOKABLE QString toLocalPath(const QUrl &url); + + Q_INVOKABLE void deleteLater(QObject *p); + + Q_INVOKABLE QString getFileNameByUrl(const QUrl &url); + + Q_INVOKABLE QRect getVirtualGeometry(); + + Q_INVOKABLE QString getApplicationDirPath(); + + Q_INVOKABLE QUrl getUrlByFilePath(const QString &path); + + Q_INVOKABLE QColor withOpacity(const QColor &, qreal alpha); + + Q_INVOKABLE QString md5(const QString &text); + + Q_INVOKABLE QString sha256(const QString &text); + + Q_INVOKABLE QString toBase64(const QString &text); + + Q_INVOKABLE QString fromBase64(const QString &text); + + Q_INVOKABLE bool removeDir(const QString &dirPath); + + Q_INVOKABLE bool removeFile(const QString &filePath); + + Q_INVOKABLE void showFileInFolder(const QString &path); + + Q_INVOKABLE bool isSoftware(); + + Q_INVOKABLE qint64 currentTimestamp(); + + Q_INVOKABLE QPoint cursorPos(); + + Q_INVOKABLE QIcon windowIcon(); + + Q_INVOKABLE int cursorScreenIndex(); + + Q_INVOKABLE int windowBuildNumber(); + + Q_INVOKABLE bool isWindows11OrGreater(); + + Q_INVOKABLE bool isWindows10OrGreater(); + + Q_INVOKABLE QRect desktopAvailableGeometry(QQuickWindow *window); + + Q_INVOKABLE QString getWallpaperFilePath(); + + Q_INVOKABLE QColor imageMainColor(const QImage &image, double bright = 1); +}; + +#endif // __UTILITIES_H__ \ No newline at end of file diff --git a/Fluent/qml/ApplicationWindow.qml b/Fluent/qml/ApplicationWindow.qml index da66dfa..3342560 100644 --- a/Fluent/qml/ApplicationWindow.qml +++ b/Fluent/qml/ApplicationWindow.qml @@ -316,7 +316,7 @@ Quick.Window { id:loader_border anchors.fill: parent sourceComponent: { - if(window.useSystemAppBar || Utilities.isWin() || window.visibility === Window.Maximized || window.visibility === Window.FullScreen){ + if(window.useSystemAppBar || Utilities.isWin() || window.visibility === Quick.Window.Maximized || window.visibility === Quick.Window.FullScreen){ return undefined } return com_border