finish the linux implementation

Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
Yuhang Zhao 2022-04-16 16:55:51 +08:00
parent 859912ae25
commit 2c0ec868ab
17 changed files with 570 additions and 111 deletions

View File

@ -36,17 +36,7 @@ MainWindow::MainWindow(QWidget *parent, const Qt::WindowFlags flags) : Frameless
setupUi();
}
MainWindow::~MainWindow()
{
if (titleBar) {
delete titleBar;
titleBar = nullptr;
}
if (mainWindow) {
delete mainWindow;
mainWindow = nullptr;
}
}
MainWindow::~MainWindow() = default;
void MainWindow::changeEvent(QEvent *event)
{
@ -58,11 +48,11 @@ void MainWindow::changeEvent(QEvent *event)
void MainWindow::setupUi()
{
mainWindow = new Ui::MainWindow;
mainWindow.reset(new Ui::MainWindow);
mainWindow->setupUi(this);
const auto titleBarWidget = new QWidget(this);
titleBar = new Ui::TitleBar;
titleBar.reset(new Ui::TitleBar);
titleBar->setupUi(titleBarWidget);
const SystemTheme theme = SystemTheme::Light;

View File

@ -51,6 +51,6 @@ Q_SIGNALS:
void windowStateChanged();
private:
Ui::TitleBar *titleBar = nullptr;
Ui::MainWindow *mainWindow = nullptr;
QScopedPointer<Ui::TitleBar> titleBar;
QScopedPointer<Ui::MainWindow> mainWindow;
};

View File

@ -55,7 +55,7 @@ int main(int argc, char *argv[])
QQmlApplicationEngine engine;
// Don't forget to register our custom QML types!
// Don't forget to register our own custom QML types!
FramelessHelper::Quick::registerTypes(&engine);
// This line is not relevant to FramelessHelper, we change the default

View File

@ -66,7 +66,7 @@ void Widget::setupUi()
{
setWindowTitle(tr("Hello, World! - Qt Widgets"));
resize(800, 600);
m_clockLabel = new QLabel(this);
m_clockLabel.reset(new QLabel(this));
m_clockLabel->setFrameShape(QFrame::NoFrame);
QFont clockFont = font();
clockFont.setBold(true);
@ -78,7 +78,7 @@ void Widget::setupUi()
contentLayout->setContentsMargins(0, 0, 0, 0);
contentLayout->setSpacing(0);
contentLayout->addStretch();
contentLayout->addWidget(m_clockLabel);
contentLayout->addWidget(m_clockLabel.data());
contentLayout->addStretch();
widget->setLayout(contentLayout);
setContentWidget(widget);

View File

@ -49,5 +49,5 @@ private Q_SLOTS:
void updateStyleSheet();
private:
QLabel *m_clockLabel = nullptr;
QScopedPointer<QLabel> m_clockLabel;
};

View File

@ -55,6 +55,7 @@ FRAMELESSHELPER_CORE_API void moveWindowToDesktopCenter(
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isThemeChangeEvent(const QEvent * const event);
[[nodiscard]] FRAMELESSHELPER_CORE_API QColor calculateSystemButtonBackgroundColor(
const Global::SystemButtonType button, const Global::ButtonState state);
[[nodiscard]] FRAMELESSHELPER_CORE_API bool shouldAppsUseDarkMode();
#ifdef Q_OS_WINDOWS
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin8OrGreater();
@ -79,7 +80,6 @@ FRAMELESSHELPER_CORE_API void showSystemMenu(
const Global::Options options,
const Global::IsWindowFixedSizeCallback &isWindowFixedSize);
[[nodiscard]] FRAMELESSHELPER_CORE_API QColor getDwmColorizationColor();
[[nodiscard]] FRAMELESSHELPER_CORE_API bool shouldAppsUseDarkMode();
[[nodiscard]] FRAMELESSHELPER_CORE_API Global::DwmColorizationArea getDwmColorizationArea();
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isHighContrastModeEnabled();
[[nodiscard]] FRAMELESSHELPER_CORE_API quint32 getPrimaryScreenDpi(const bool horizontal);

View File

@ -49,6 +49,9 @@ public:
explicit FramelessWidgetsHelper(QWidget *q, const Global::UserSettings &settings = {});
~FramelessWidgetsHelper() override;
Q_NODISCARD static FramelessWidgetsHelper *get(QWidget *pub);
Q_NODISCARD static const FramelessWidgetsHelper *get(const QWidget *pub);
Q_NODISCARD Q_INVOKABLE bool isNormal() const;
Q_NODISCARD Q_INVOKABLE bool isZoomed() const;
Q_NODISCARD Q_INVOKABLE bool isFixedSize() const;
@ -97,21 +100,21 @@ private Q_SLOTS:
void updateSystemMaximizeButton();
private:
QWidget *q = nullptr;
QPointer<QWidget> q = nullptr;
bool m_initialized = false;
QWidget *m_systemTitleBarWidget = nullptr;
QLabel *m_systemWindowTitleLabel = nullptr;
StandardSystemButton *m_systemMinimizeButton = nullptr;
StandardSystemButton *m_systemMaximizeButton = nullptr;
StandardSystemButton *m_systemCloseButton = nullptr;
QWidget *m_userTitleBarWidget = nullptr;
QWidget *m_userContentWidget = nullptr;
QVBoxLayout *m_mainLayout = nullptr;
QScopedPointer<QWidget> m_systemTitleBarWidget;
QScopedPointer<QLabel> m_systemWindowTitleLabel;
QScopedPointer<StandardSystemButton> m_systemMinimizeButton;
QScopedPointer<StandardSystemButton> m_systemMaximizeButton;
QScopedPointer<StandardSystemButton> m_systemCloseButton;
QPointer<QWidget> m_userTitleBarWidget = nullptr;
QPointer<QWidget> m_userContentWidget = nullptr;
QScopedPointer<QVBoxLayout> m_mainLayout;
QWidgetList m_hitTestVisibleWidgets = {};
QWidget *m_userContentContainerWidget = nullptr;
QVBoxLayout *m_userContentContainerLayout = nullptr;
QScopedPointer<QWidget> m_userContentContainerWidget;
QScopedPointer<QVBoxLayout> m_userContentContainerLayout;
Qt::WindowState m_savedWindowState = {};
QWindow *m_window = nullptr;
QPointer<QWindow> m_window = nullptr;
Global::UserSettings m_settings = {};
Global::SystemParameters m_params = {};
bool m_windowExposed = false;

View File

@ -100,10 +100,10 @@ void FramelessWindowsManager::addWindow(const UserSettings &settings, const Syst
g_helper()->windowIds.append(params.windowId);
g_helper()->mutex.unlock();
static const bool pureQt = usePureQtImplementation();
QWindow *window = params.getWindowHandle();
#ifdef Q_OS_WINDOWS
if (!pureQt) {
// Work-around Win32 multi-monitor artifacts.
QWindow *window = params.getWindowHandle();
connect(window, &QWindow::screenChanged, window, [&params, window](QScreen *screen){
Q_UNUSED(screen);
// Force a WM_NCCALCSIZE event to inform Windows about our custom window frame,

View File

@ -27,6 +27,10 @@
#include <QtGui/qwindow.h>
#include <QtGui/qscreen.h>
#include <QtGui/qguiapplication.h>
#if (QT_VERSION >= QT_VERSION_CHECK(6, 2, 1))
# include <QtGui/qpa/qplatformtheme.h>
# include <QtGui/private/qguiapplication_p.h>
#endif
// The "Q_INIT_RESOURCE()" macro can't be used within a namespace,
// so we wrap it into a separate function outside of the namespace and
@ -53,6 +57,16 @@ FRAMELESSHELPER_STRING_CONSTANT(light)
FRAMELESSHELPER_STRING_CONSTANT(dark)
FRAMELESSHELPER_STRING_CONSTANT(highcontrast)
#ifdef Q_OS_WINDOWS
[[nodiscard]] extern bool shouldAppsUseDarkMode_windows();
#endif
#ifdef Q_OS_LINUX
[[nodiscard]] extern bool shouldAppsUseDarkMode_linux();
#endif
#ifdef Q_OS_MACOS
[[nodiscard]] extern bool shouldAppsUseDarkMode_macos();
#endif
Qt::CursorShape Utils::calculateCursorShape(const QWindow *window, const QPoint &pos)
{
Q_ASSERT(window);
@ -247,4 +261,24 @@ QColor Utils::calculateSystemButtonBackgroundColor(const SystemButtonType button
return ((state == ButtonState::Hovered) ? result.lighter(110) : result.lighter(105));
}
bool Utils::shouldAppsUseDarkMode()
{
#if (QT_VERSION >= QT_VERSION_CHECK(6, 2, 1))
if (const QPlatformTheme * const theme = QGuiApplicationPrivate::platformTheme()) {
return (theme->appearance() == QPlatformTheme::Appearance::Dark);
}
return false;
#else
# ifdef Q_OS_WINDOWS
return shouldAppsUseDarkMode_windows();
# elif defined(Q_OS_LINUX)
return shouldAppsUseDarkMode_linux();
# elif defined(Q_OS_MACOS)
return shouldAppsUseDarkMode_macos();
# else
return false;
# endif
#endif
}
FRAMELESSHELPER_END_NAMESPACE

View File

@ -23,9 +23,389 @@
*/
#include "utils.h"
#include <QtCore/qdebug.h>
#include <QtCore/qregularexpression.h>
#include <QtGui/qcursor.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qwindow.h>
#include <QtGui/qscreen.h>
#include <QtGui/qpa/qplatformnativeinterface.h>
#if ((QT_VERSION >= QT_VERSION_CHECK(5, 9, 1)) && (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)))
# include <QtPlatformHeaders/qxcbscreenfunctions.h>
#endif
//#include <gtk/gtk.h>
#include <X11/Xlib.h>
FRAMELESSHELPER_BEGIN_NAMESPACE
using namespace Global;
#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
static constexpr const auto _NET_WM_MOVERESIZE_SIZE_TOPLEFT = 0;
static constexpr const auto _NET_WM_MOVERESIZE_SIZE_TOP = 1;
static constexpr const auto _NET_WM_MOVERESIZE_SIZE_TOPRIGHT = 2;
static constexpr const auto _NET_WM_MOVERESIZE_SIZE_RIGHT = 3;
static constexpr const auto _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT = 4;
static constexpr const auto _NET_WM_MOVERESIZE_SIZE_BOTTOM = 5;
static constexpr const auto _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT = 6;
static constexpr const auto _NET_WM_MOVERESIZE_SIZE_LEFT = 7;
static constexpr const auto _NET_WM_MOVERESIZE_MOVE = 8;
#endif
FRAMELESSHELPER_BYTEARRAY_CONSTANT(display)
FRAMELESSHELPER_BYTEARRAY_CONSTANT(x11screen)
FRAMELESSHELPER_BYTEARRAY_CONSTANT(rootwindow)
#if 0
template<typename T>
[[nodiscard]] static inline T gtkSetting(const gchar *propertyName)
{
Q_ASSERT(propertyName);
if (!propertyName) {
return {};
}
GtkSettings * const settings = gtk_settings_get_default();
Q_ASSERT(settings);
if (!settings) {
return {};
}
T result = {};
g_object_get(settings, propertyName, &result, nullptr);
return result;
}
[[nodiscard]] static inline QString gtkSetting(const gchar *propertyName)
{
Q_ASSERT(propertyName);
if (!propertyName) {
return {};
}
const auto value = gtkSetting<gchararray>(propertyName);
const QString result = QString::fromUtf8(value);
g_free(value);
return result;
}
#endif
#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
[[nodiscard]] static inline Qt::WindowFrameSection qtEdgesToQtWindowFrameSection(const Qt::Edges edges)
{
if (edges == Qt::Edges{}) {
return Qt::NoSection;
}
if (edges & Qt::TopEdge) {
if (edges & Qt::LeftEdge) {
return Qt::TopLeftSection;
}
if (edges & Qt::RightEdge) {
return Qt::TopRightSection;
}
return Qt::TopSection;
}
if (edges & Qt::BottomEdge) {
if (edges & Qt::LeftEdge) {
return Qt::BottomLeftSection;
}
if (edges & Qt::RightEdge) {
return Qt::BottomRightSection;
}
return Qt::BottomSection;
}
if (edges & Qt::LeftEdge) {
return Qt::LeftSection;
}
if (edges & Qt::RightEdge) {
return Qt::RightSection;
}
return Qt::NoSection;
}
#endif
[[nodiscard]] bool shouldAppsUseDarkMode_linux()
{
#if 0
/*
https://docs.gtk.org/gtk3/running.html
It's possible to set a theme variant after the theme name when using GTK_THEME:
GTK_THEME=Adwaita:dark
Some themes also have "-dark" as part of their name.
We test this environment variable first because the documentation says
it's mainly used for easy debugging, so it should be possible to use it
to override any other settings.
*/
QString themeName = qEnvironmentVariable("GTK_THEME");
const QRegularExpression darkRegex(QStringLiteral("[:-]dark"), QRegularExpression::CaseInsensitiveOption);
if (!themeName.isEmpty()) {
return darkRegex.match(themeName).hasMatch();
}
/*
https://docs.gtk.org/gtk3/property.Settings.gtk-application-prefer-dark-theme.html
This setting controls which theme is used when the theme specified by
gtk-theme-name provides both light and dark variants. We can save a
regex check by testing this property first.
*/
const auto preferDark = gtkSetting<bool>("gtk-application-prefer-dark-theme");
if (preferDark) {
return true;
}
/*
https://docs.gtk.org/gtk3/property.Settings.gtk-theme-name.html
*/
themeName = gtkSetting("gtk-theme-name");
if (!themeName.isEmpty()) {
return darkRegex.match(themeName).hasMatch();
}
return false;
#else
return false;
#endif
}
[[nodiscard]] static inline Display *x11_get_display()
{
if (!qGuiApp) {
return nullptr;
}
QPlatformNativeInterface * const iface = qGuiApp->platformNativeInterface();
if (!iface) {
return nullptr;
}
const auto display = iface->nativeResourceForIntegration(kdisplay);
if (!display) {
return nullptr;
}
return static_cast<Display *>(display);
}
[[nodiscard]] static inline qintptr x11_get_desktop()
{
if (!qGuiApp) {
return 0;
}
QPlatformNativeInterface * const iface = qGuiApp->platformNativeInterface();
if (!iface) {
return 0;
}
const auto screen = iface->nativeResourceForIntegration(kx11screen);
if (!screen) {
return 0;
}
return reinterpret_cast<qintptr>(screen);
}
[[nodiscard]] static inline QScreen *x11_getScreenForVirtualDesktop(const int virtualDesktopNumber)
{
#if ((QT_VERSION >= QT_VERSION_CHECK(5, 9, 1)) && (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)))
if (virtualDesktopNumber == -1) {
return QGuiApplication::primaryScreen();
}
const QList<QScreen *> screens = QGuiApplication::screens();
if (screens.isEmpty()) {
return nullptr;
}
for (auto &&screen : qAsConst(screens)) {
if (QXcbScreenFunctions::virtualDesktopNumber(screen) == virtualDesktopNumber) {
return screen;
}
}
return nullptr;
#else
Q_UNUSED(virtualDesktopNumber);
return QGuiApplication::primaryScreen();
#endif
}
[[nodiscard]] static inline quintptr x11_get_window(const int desktop)
{
if (!qGuiApp) {
return 0;
}
QPlatformNativeInterface * const iface = qGuiApp->platformNativeInterface();
if (!iface) {
return 0;
}
QScreen * const screen = x11_getScreenForVirtualDesktop(desktop);
if (!screen) {
return 0;
}
const auto rootWindow = iface->nativeResourceForScreen(krootwindow, screen);
if (!rootWindow) {
return 0;
}
return reinterpret_cast<quintptr>(rootWindow);
}
static inline void x11_emulateButtonRelease(const WId windowId, const QPoint &globalPos, const QPoint &localPos)
{
Q_ASSERT(windowId);
if (!windowId) {
return;
}
const Window window = windowId;
Display * const display = x11_get_display();
XEvent event;
memset(&event, 0, sizeof(event));
event.xbutton.button = 0;
event.xbutton.same_screen = True;
event.xbutton.send_event = True;
event.xbutton.window = window;
event.xbutton.root = x11_get_window(x11_get_desktop());
event.xbutton.x_root = globalPos.x();
event.xbutton.y_root = globalPos.y();
event.xbutton.x = localPos.x();
event.xbutton.y = localPos.y();
event.xbutton.type = ButtonRelease;
event.xbutton.time = CurrentTime;
if (XSendEvent(display, window, True, ButtonReleaseMask, &event) == 0) {
qWarning() << "Failed to send ButtonRelease event for native dragging.";
}
XFlush(display);
}
#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
static inline void x11_moveOrResizeWindow(const WId windowId, const QPoint &pos, const int section)
{
Q_ASSERT(windowId);
if (!windowId) {
return;
}
Display * const display = x11_get_display();
static const Atom netMoveResize = XInternAtom(display, "_NET_WM_MOVERESIZE", False);
// First we need to ungrab the pointer that may have been
// automatically grabbed by Qt on ButtonPressEvent
XUngrabPointer(display, CurrentTime);
XEvent event;
memset(&event, 0, sizeof(event));
event.xclient.type = ClientMessage;
event.xclient.window = windowId;
event.xclient.message_type = netMoveResize;
event.xclient.serial = 0;
event.xclient.display = display;
event.xclient.send_event = True;
event.xclient.format = 32;
event.xclient.data.l[0] = pos.x();
event.xclient.data.l[1] = pos.y();
event.xclient.data.l[2] = section;
event.xclient.data.l[3] = Button1;
event.xclient.data.l[4] = 0; // unused
if (XSendEvent(display, x11_get_window(x11_get_desktop()),
False, (SubstructureRedirectMask | SubstructureNotifyMask), &event) == 0) {
qWarning() << "Failed to send _NET_WM_MOVERESIZE event for native dragging.";
}
XFlush(display);
}
static inline void x11_windowStartNativeDrag(const WId windowId, const QPoint &globalPos, const QPoint &localPos, const Qt::WindowFrameSection frameSection)
{
Q_ASSERT(windowId);
if (!windowId) {
return;
}
int nativeSection = -1;
switch (frameSection)
{
case Qt::LeftSection:
nativeSection = _NET_WM_MOVERESIZE_SIZE_LEFT;
break;
case Qt::TopLeftSection:
nativeSection = _NET_WM_MOVERESIZE_SIZE_TOPLEFT;
break;
case Qt::TopSection:
nativeSection = _NET_WM_MOVERESIZE_SIZE_TOP;
break;
case Qt::TopRightSection:
nativeSection = _NET_WM_MOVERESIZE_SIZE_TOPRIGHT;
break;
case Qt::RightSection:
nativeSection = _NET_WM_MOVERESIZE_SIZE_RIGHT;
break;
case Qt::BottomRightSection:
nativeSection = _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT;
break;
case Qt::BottomSection:
nativeSection = _NET_WM_MOVERESIZE_SIZE_BOTTOM;
break;
case Qt::BottomLeftSection:
nativeSection = _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT;
break;
case Qt::TitleBarArea:
nativeSection = _NET_WM_MOVERESIZE_MOVE;
break;
default:
break;
}
if (nativeSection == -1) {
return;
}
// Before start the drag we need to tell Qt that the mouse is Released!
x11_emulateButtonRelease(windowId, globalPos, localPos);
x11_moveOrResizeWindow(windowId, globalPos, nativeSection);
}
#endif
SystemTheme Utils::getSystemTheme()
{
// ### TODO: how to detect high contrast mode on Linux?
return (shouldAppsUseDarkMode() ? SystemTheme::Dark : SystemTheme::Light);
}
void Utils::startSystemMove(QWindow *window)
{
Q_ASSERT(window);
if (!window) {
return;
}
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
window->startSystemMove();
#else
const qreal dpr = window->devicePixelRatio();
const QPoint globalPos = QPointF(QPointF(QCursor::pos(window->screen())) * dpr).toPoint();
const QPoint localPos = QPointF(QPointF(window->mapFromGlobal(globalPos)) * dpr).toPoint();
x11_windowStartNativeDrag(window->winId(), globalPos, localPos, Qt::TitleBarArea);
#endif
}
void Utils::startSystemResize(QWindow *window, const Qt::Edges edges)
{
Q_ASSERT(window);
if (!window) {
return;
}
if (edges == Qt::Edges{}) {
return;
}
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
window->startSystemResize(edges);
#else
const qreal dpr = window->devicePixelRatio();
const QPoint globalPos = QPointF(QPointF(QCursor::pos(window->screen())) * dpr).toPoint();
const QPoint localPos = QPointF(QPointF(window->mapFromGlobal(globalPos)) * dpr).toPoint();
x11_windowStartNativeDrag(window->winId(), globalPos, localPos, qtEdgesToQtWindowFrameSection(edges));
#endif
}
void Utils::sendMouseReleaseEvent()
{
const QWindow * const window = QGuiApplication::focusWindow();
Q_ASSERT(window);
if (!window) {
return;
}
const qreal dpr = window->devicePixelRatio();
const QPoint globalPos = QPointF(QPointF(QCursor::pos(window->screen())) * dpr).toPoint();
const QPoint localPos = QPointF(QPointF(window->mapFromGlobal(globalPos)) * dpr).toPoint();
x11_emulateButtonRelease(window->winId(), globalPos, localPos);
}
FRAMELESSHELPER_END_NAMESPACE

View File

@ -214,6 +214,24 @@ FRAMELESSHELPER_STRING_CONSTANT(ReleaseCapture)
}
#endif
[[nodiscard]] bool shouldAppsUseDarkMode_windows()
{
// The global dark mode was first introduced in Windows 10 1607.
if (!Utils::isWin101607OrGreater()) {
return false;
}
const auto resultFromRegistry = []() -> bool {
const QWinRegistryKey registry(HKEY_CURRENT_USER, qPersonalizeRegistryKey);
const auto result = registry.dwordValue(kAppsUseLightTheme);
return (result.second && (result.first == 0));
};
// Starting from Windows 10 1903, ShouldAppsUseDarkMode() always return "TRUE"
// (actually, a random non-zero number at runtime), so we can't use it due to
// this unreliability. In this case, we just simply read the user's setting from
// the registry instead, it's not elegant but at least it works well.
return resultFromRegistry();
}
[[nodiscard]] static inline LRESULT CALLBACK SystemMenuHookWindowProc
(const HWND hWnd, const UINT uMsg, const WPARAM wParam, const LPARAM lParam)
{
@ -495,24 +513,6 @@ QColor Utils::getDwmColorizationColor()
return QColor::fromRgba(color);
}
bool Utils::shouldAppsUseDarkMode()
{
// The global dark mode was first introduced in Windows 10 1607.
if (!isWin101607OrGreater()) {
return false;
}
const auto resultFromRegistry = []() -> bool {
const QWinRegistryKey registry(HKEY_CURRENT_USER, qPersonalizeRegistryKey);
const auto result = registry.dwordValue(kAppsUseLightTheme);
return (result.second && (result.first == 0));
};
// Starting from Windows 10 1903, ShouldAppsUseDarkMode() always return "TRUE"
// (actually, a random non-zero number at runtime), so we can't use it due to
// this unreliability. In this case, we just simply read the user's setting from
// the registry instead, it's not elegant but at least it works well.
return resultFromRegistry();
}
DwmColorizationArea Utils::getDwmColorizationArea()
{
// It's a Win10 only feature. (TO BE VERIFIED)

View File

@ -23,10 +23,6 @@
*/
#include "framelessquickutils.h"
#if (QT_VERSION >= QT_VERSION_CHECK(6, 2, 1))
# include <QtGui/qpa/qplatformtheme.h>
# include <QtGui/private/qguiapplication_p.h>
#endif
#include <framelesswindowsmanager.h>
#include <utils.h>
@ -68,23 +64,16 @@ bool FramelessQuickUtils::frameBorderVisible()
qreal FramelessQuickUtils::frameBorderThickness()
{
#ifdef Q_OS_WINDOWS
return 1.0;
#else
return 0.0;
#endif
}
bool FramelessQuickUtils::darkModeEnabled()
{
#if (QT_VERSION >= QT_VERSION_CHECK(6, 2, 1))
if (const QPlatformTheme * const theme = QGuiApplicationPrivate::platformTheme()) {
return (theme->appearance() == QPlatformTheme::Appearance::Dark);
}
return false;
#else
# ifdef Q_OS_WINDOWS
return Utils::shouldAppsUseDarkMode();
# else
return false;
# endif
#endif
}
QColor FramelessQuickUtils::systemAccentColor()

View File

@ -365,6 +365,8 @@ void FramelessQuickWindowPrivate::showSystemMenu(const QPoint &pos)
const QPoint nativePos = QPointF(QPointF(globalPos) * q->effectiveDevicePixelRatio()).toPoint();
Utils::showSystemMenu(m_params.windowId, nativePos, m_settings.systemMenuOffset,
false, m_settings.options, m_params.isWindowFixedSize);
#else
Q_UNUSED(pos);
#endif
}
@ -448,6 +450,7 @@ void FramelessQuickWindowPrivate::initialize()
return (Utils::isWindowFrameBorderVisible() && !Utils::isWin11OrGreater()
&& !(m_settings.options & Option::DontDrawTopWindowFrameBorder));
#else
Q_UNUSED(this);
return false;
#endif
}();
@ -562,11 +565,19 @@ bool FramelessQuickWindowPrivate::isInTitleBarDraggableArea(const QPoint &pos) c
bool FramelessQuickWindowPrivate::shouldIgnoreMouseEvents(const QPoint &pos) const
{
Q_Q(const FramelessQuickWindow);
return (isNormal()
&& ((pos.y() < kDefaultResizeBorderThickness)
|| (Utils::isWindowFrameBorderVisible()
? false : ((pos.x() < kDefaultResizeBorderThickness)
|| (pos.x() >= (q->width() - kDefaultResizeBorderThickness))))));
const bool withinFrameBorder = [&pos, q]() -> bool {
if (pos.y() < kDefaultResizeBorderThickness) {
return true;
}
#ifdef Q_OS_WINDOWS
if (Utils::isWindowFrameBorderVisible()) {
return false;
}
#endif
return ((pos.x() < kDefaultResizeBorderThickness)
|| (pos.x() >= (q->width() - kDefaultResizeBorderThickness)));
}();
return (isNormal() && withinFrameBorder);
}
void FramelessQuickWindowPrivate::showEventHandler(QShowEvent *event)

View File

@ -107,7 +107,7 @@ private:
Global::UserSettings m_settings = {};
Global::SystemParameters m_params = {};
bool m_windowExposed = false;
QQuickItem *m_titleBarItem = nullptr;
QPointer<QQuickItem> m_titleBarItem = nullptr;
QList<QQuickItem *> m_hitTestVisibleItems = {};
};

View File

@ -37,6 +37,7 @@ FRAMELESSHELPER_BEGIN_NAMESPACE
using namespace Global;
static constexpr const char FRAMELESSHELPER_PROP_NAME[] = "__wwx190_FramelessWidgetsHelper_instance";
static constexpr const char QT_MAINWINDOW_CLASS_NAME[] = "QMainWindow";
FRAMELESSHELPER_STRING_CONSTANT2(StyleSheetColorTemplate, "color: %1;")
@ -55,6 +56,24 @@ FramelessWidgetsHelper::FramelessWidgetsHelper(QWidget *q, const UserSettings &s
FramelessWidgetsHelper::~FramelessWidgetsHelper() = default;
FramelessWidgetsHelper *FramelessWidgetsHelper::get(QWidget *pub)
{
Q_ASSERT(pub);
if (!pub) {
return nullptr;
}
return qvariant_cast<FramelessWidgetsHelper *>(pub->property(FRAMELESSHELPER_PROP_NAME));
}
const FramelessWidgetsHelper *FramelessWidgetsHelper::get(const QWidget *pub)
{
Q_ASSERT(pub);
if (!pub) {
return nullptr;
}
return qvariant_cast<const FramelessWidgetsHelper *>(pub->property(FRAMELESSHELPER_PROP_NAME));
}
bool FramelessWidgetsHelper::isNormal() const
{
return (m_params.getWindowState() == Qt::WindowNoState);
@ -114,7 +133,7 @@ void FramelessWidgetsHelper::setTitleBarWidget(QWidget *widget)
}
if (m_settings.options & Option::CreateStandardWindowLayout) {
if (m_systemTitleBarWidget && m_systemTitleBarWidget->isVisible()) {
m_mainLayout->removeWidget(m_systemTitleBarWidget);
m_mainLayout->removeWidget(m_systemTitleBarWidget.data());
m_systemTitleBarWidget->hide();
}
if (m_userTitleBarWidget) {
@ -226,6 +245,7 @@ void FramelessWidgetsHelper::changeEventHandler(QEvent *event)
void FramelessWidgetsHelper::paintEventHandler(QPaintEvent *event)
{
#ifdef Q_OS_WINDOWS
Q_ASSERT(event);
if (!event) {
return;
@ -241,6 +261,9 @@ void FramelessWidgetsHelper::paintEventHandler(QPaintEvent *event)
painter.setPen(pen);
painter.drawLine(0, 0, (q->width() - 1), 0);
painter.restore();
#else
Q_UNUSED(event);
#endif
}
void FramelessWidgetsHelper::mouseMoveEventHandler(QMouseEvent *event)
@ -324,6 +347,8 @@ void FramelessWidgetsHelper::initialize()
return;
}
m_initialized = true;
// Let the user be able to get the helper class instance from outside.
q->setProperty(FRAMELESSHELPER_PROP_NAME, QVariant::fromValue(this));
// Without this flag, Qt will always create an invisible native parent window
// for any native widgets which will intercept some win32 messages and confuse
// our own native event filter, so to prevent some weired bugs from happening,
@ -395,18 +420,18 @@ void FramelessWidgetsHelper::initialize()
switch (button) {
case SystemButtonType::Minimize: {
if (m_systemMinimizeButton) {
updateSystemButtonState(m_systemMinimizeButton);
updateSystemButtonState(m_systemMinimizeButton.data());
}
} break;
case SystemButtonType::Maximize:
case SystemButtonType::Restore: {
if (m_systemMaximizeButton) {
updateSystemButtonState(m_systemMaximizeButton);
updateSystemButtonState(m_systemMaximizeButton.data());
}
} break;
case SystemButtonType::Close: {
if (m_systemCloseButton) {
updateSystemButtonState(m_systemCloseButton);
updateSystemButtonState(m_systemCloseButton.data());
}
} break;
default:
@ -422,16 +447,20 @@ void FramelessWidgetsHelper::initialize()
" Enabling this option will mess up with your main window's layout.";
}
}
#ifdef Q_OS_WINDOWS
if (m_settings.options & Option::TransparentWindowBackground) {
m_settings.options |= Option::BeCompatibleWithQtFramelessWindowHint;
}
#endif
if (m_settings.options & Option::DisableResizing) {
setFixedSize(true, true);
}
#ifdef Q_OS_WINDOWS
if (m_settings.options & Option::BeCompatibleWithQtFramelessWindowHint) {
Utils::tryToBeCompatibleWithQtFramelessWindowHint(windowId, m_params.getWindowFlags,
m_params.setWindowFlags, true);
}
#endif
if (m_settings.options & Option::TransparentWindowBackground) {
q->setAttribute(Qt::WA_NoSystemBackground);
q->setAttribute(Qt::WA_TranslucentBackground);
@ -453,9 +482,9 @@ void FramelessWidgetsHelper::initialize()
});
setupInitialUi();
if (m_settings.options & Option::CreateStandardWindowLayout) {
m_settings.minimizeButton = m_systemMinimizeButton;
m_settings.maximizeButton = m_systemMaximizeButton;
m_settings.closeButton = m_systemCloseButton;
m_settings.minimizeButton = m_systemMinimizeButton.data();
m_settings.maximizeButton = m_systemMaximizeButton.data();
m_settings.closeButton = m_systemCloseButton.data();
}
}
@ -464,40 +493,40 @@ void FramelessWidgetsHelper::createSystemTitleBar()
if (!(m_settings.options & Option::CreateStandardWindowLayout)) {
return;
}
m_systemTitleBarWidget = new QWidget(q);
m_systemTitleBarWidget.reset(new QWidget(q));
m_systemTitleBarWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
m_systemTitleBarWidget->setFixedHeight(kDefaultTitleBarHeight);
m_systemWindowTitleLabel = new QLabel(m_systemTitleBarWidget);
m_systemWindowTitleLabel.reset(new QLabel(m_systemTitleBarWidget.data()));
m_systemWindowTitleLabel->setFrameShape(QFrame::NoFrame);
QFont windowTitleFont = q->font();
windowTitleFont.setPointSize(11);
windowTitleFont.setPointSize(kDefaultTitleBarFontPointSize);
m_systemWindowTitleLabel->setFont(windowTitleFont);
m_systemWindowTitleLabel->setText(q->windowTitle());
connect(q, &QWidget::windowTitleChanged, m_systemWindowTitleLabel, &QLabel::setText);
m_systemMinimizeButton = new StandardSystemButton(SystemButtonType::Minimize, m_systemTitleBarWidget);
connect(q, &QWidget::windowTitleChanged, m_systemWindowTitleLabel.data(), &QLabel::setText);
m_systemMinimizeButton.reset(new StandardSystemButton(SystemButtonType::Minimize, m_systemTitleBarWidget.data()));
m_systemMinimizeButton->setFixedSize(kDefaultSystemButtonSize);
m_systemMinimizeButton->setIconSize(kDefaultSystemButtonIconSize);
m_systemMinimizeButton->setToolTip(tr("Minimize"));
connect(m_systemMinimizeButton, &StandardSystemButton::clicked, q, &QWidget::showMinimized);
m_systemMaximizeButton = new StandardSystemButton(SystemButtonType::Maximize, m_systemTitleBarWidget);
connect(m_systemMinimizeButton.data(), &StandardSystemButton::clicked, q, &QWidget::showMinimized);
m_systemMaximizeButton.reset(new StandardSystemButton(SystemButtonType::Maximize, m_systemTitleBarWidget.data()));
m_systemMaximizeButton->setFixedSize(kDefaultSystemButtonSize);
m_systemMaximizeButton->setIconSize(kDefaultSystemButtonIconSize);
connect(m_systemMaximizeButton, &StandardSystemButton::clicked, this, &FramelessWidgetsHelper::toggleMaximized);
m_systemCloseButton = new StandardSystemButton(SystemButtonType::Close, m_systemTitleBarWidget);
connect(m_systemMaximizeButton.data(), &StandardSystemButton::clicked, this, &FramelessWidgetsHelper::toggleMaximized);
m_systemCloseButton.reset(new StandardSystemButton(SystemButtonType::Close, m_systemTitleBarWidget.data()));
m_systemCloseButton->setFixedSize(kDefaultSystemButtonSize);
m_systemCloseButton->setIconSize(kDefaultSystemButtonIconSize);
m_systemCloseButton->setToolTip(tr("Close"));
connect(m_systemCloseButton, &StandardSystemButton::clicked, q, &QWidget::close);
connect(m_systemCloseButton.data(), &StandardSystemButton::clicked, q, &QWidget::close);
updateSystemMaximizeButton();
const auto systemTitleBarLayout = new QHBoxLayout(m_systemTitleBarWidget);
const auto systemTitleBarLayout = new QHBoxLayout(m_systemTitleBarWidget.data());
systemTitleBarLayout->setContentsMargins(0, 0, 0, 0);
systemTitleBarLayout->setSpacing(0);
systemTitleBarLayout->addSpacerItem(new QSpacerItem(10, 10));
systemTitleBarLayout->addWidget(m_systemWindowTitleLabel);
systemTitleBarLayout->addWidget(m_systemWindowTitleLabel.data());
systemTitleBarLayout->addStretch();
systemTitleBarLayout->addWidget(m_systemMinimizeButton);
systemTitleBarLayout->addWidget(m_systemMaximizeButton);
systemTitleBarLayout->addWidget(m_systemCloseButton);
systemTitleBarLayout->addWidget(m_systemMinimizeButton.data());
systemTitleBarLayout->addWidget(m_systemMaximizeButton.data());
systemTitleBarLayout->addWidget(m_systemCloseButton.data());
m_systemTitleBarWidget->setLayout(systemTitleBarLayout);
}
@ -506,12 +535,12 @@ void FramelessWidgetsHelper::createUserContentContainer()
if (!(m_settings.options & Option::CreateStandardWindowLayout)) {
return;
}
m_userContentContainerWidget = new QWidget(q);
m_userContentContainerWidget.reset(new QWidget(q));
m_userContentContainerWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_userContentContainerLayout = new QVBoxLayout(m_userContentContainerWidget);
m_userContentContainerLayout.reset(new QVBoxLayout(m_userContentContainerWidget.data()));
m_userContentContainerLayout->setContentsMargins(0, 0, 0, 0);
m_userContentContainerLayout->setSpacing(0);
m_userContentContainerWidget->setLayout(m_userContentContainerLayout);
m_userContentContainerWidget->setLayout(m_userContentContainerLayout.data());
}
void FramelessWidgetsHelper::setupInitialUi()
@ -519,12 +548,12 @@ void FramelessWidgetsHelper::setupInitialUi()
if (m_settings.options & Option::CreateStandardWindowLayout) {
createSystemTitleBar();
createUserContentContainer();
m_mainLayout = new QVBoxLayout(q);
m_mainLayout.reset(new QVBoxLayout(q));
m_mainLayout->setContentsMargins(0, 0, 0, 0);
m_mainLayout->setSpacing(0);
m_mainLayout->addWidget(m_systemTitleBarWidget);
m_mainLayout->addWidget(m_userContentContainerWidget);
q->setLayout(m_mainLayout);
m_mainLayout->addWidget(m_systemTitleBarWidget.data());
m_mainLayout->addWidget(m_userContentContainerWidget.data());
q->setLayout(m_mainLayout.data());
updateSystemTitleBarStyleSheet();
q->update();
}
@ -603,10 +632,10 @@ bool FramelessWidgetsHelper::isInTitleBarDraggableArea(const QPoint &pos) const
return region;
}
if (m_settings.options & Option::CreateStandardWindowLayout) {
QRegion region = mapWidgetGeometryToScene(m_systemTitleBarWidget);
region -= mapWidgetGeometryToScene(m_systemMinimizeButton);
region -= mapWidgetGeometryToScene(m_systemMaximizeButton);
region -= mapWidgetGeometryToScene(m_systemCloseButton);
QRegion region = mapWidgetGeometryToScene(m_systemTitleBarWidget.data());
region -= mapWidgetGeometryToScene(m_systemMinimizeButton.data());
region -= mapWidgetGeometryToScene(m_systemMaximizeButton.data());
region -= mapWidgetGeometryToScene(m_systemCloseButton.data());
return region;
}
return {};
@ -626,11 +655,19 @@ bool FramelessWidgetsHelper::shouldDrawFrameBorder() const
bool FramelessWidgetsHelper::shouldIgnoreMouseEvents(const QPoint &pos) const
{
return (isNormal()
&& ((pos.y() < kDefaultResizeBorderThickness)
|| (Utils::isWindowFrameBorderVisible()
? false : ((pos.x() < kDefaultResizeBorderThickness)
|| (pos.x() >= (q->width() - kDefaultResizeBorderThickness))))));
const bool withinFrameBorder = [&pos, this]() -> bool {
if (pos.y() < kDefaultResizeBorderThickness) {
return true;
}
#ifdef Q_OS_WINDOWS
if (Utils::isWindowFrameBorderVisible()) {
return false;
}
#endif
return ((pos.x() < kDefaultResizeBorderThickness)
|| (pos.x() >= (q->width() - kDefaultResizeBorderThickness)));
}();
return (isNormal() && withinFrameBorder);
}
void FramelessWidgetsHelper::updateContentsMargins()
@ -647,18 +684,29 @@ void FramelessWidgetsHelper::updateSystemTitleBarStyleSheet()
}
const bool active = q->isActiveWindow();
const bool dark = Utils::shouldAppsUseDarkMode();
#ifdef Q_OS_WINDOWS
const bool colorizedTitleBar = Utils::isTitleBarColorized();
#else
constexpr const bool colorizedTitleBar = false;
#endif
const QColor systemTitleBarWidgetBackgroundColor = [active, colorizedTitleBar, dark]() -> QColor {
#ifndef Q_OS_WINDOWS
Q_UNUSED(colorizedTitleBar);
#endif
if (active) {
#ifdef Q_OS_WINDOWS
if (colorizedTitleBar) {
return Utils::getDwmColorizationColor();
} else {
#endif
if (dark) {
return kDefaultBlackColor;
} else {
return kDefaultWhiteColor;
}
#ifdef Q_OS_WINDOWS
}
#endif
} else {
if (dark) {
return kDefaultSystemDarkColor;
@ -733,6 +781,8 @@ void FramelessWidgetsHelper::showSystemMenu(const QPoint &pos)
const QPoint nativePos = QPointF(QPointF(globalPos) * q->devicePixelRatioF()).toPoint();
Utils::showSystemMenu(m_params.windowId, nativePos, m_settings.systemMenuOffset,
false, m_settings.options, m_params.isWindowFixedSize);
#else
Q_UNUSED(pos);
#endif
}

View File

@ -73,9 +73,11 @@ void StandardSystemButtonPrivate::refreshButtonTheme(const bool force)
return;
}
const SystemTheme systemTheme = []() -> SystemTheme {
#ifdef Q_OS_WINDOWS
if (Utils::isTitleBarColorized()) {
return SystemTheme::Dark;
}
#endif
return Utils::getSystemTheme();
}();
if ((m_buttonTheme == systemTheme) && !force) {

View File

@ -77,7 +77,7 @@ private:
void initialize();
private:
StandardSystemButton *q_ptr;
StandardSystemButton *q_ptr = nullptr;
Global::SystemTheme m_buttonTheme = Global::SystemTheme::Unknown;
Global::SystemButtonType m_buttonType = Global::SystemButtonType::Unknown;
QPixmap m_icon = {};