feat: good framelesswindow for mac

This commit is contained in:
rankun 2020-10-15 13:17:44 +08:00
parent 1e15a305e7
commit 80851675f2
16 changed files with 460 additions and 93 deletions

View file

@ -11,7 +11,7 @@
#include "dialog.h"
#include "mousetap/mousetap.h"
#include "stream.h"
#include "windowframelesshelper.h"
#include "windowframelesshelper/windowframelesshelper.h"
static Dialog *g_mainDlg = Q_NULLPTR;

View file

@ -2,24 +2,22 @@ FORMS +=
HEADERS += \
$$PWD/keepratiowidget.h \
$$PWD/magneticwidget.h \
$$PWD/windowframelesshelper.h \
$$PWD/windownativeeventfilter.h
$$PWD/magneticwidget.h
SOURCES += \
$$PWD/keepratiowidget.cpp \
$$PWD/magneticwidget.cpp \
$$PWD/windownativeeventfilter.cpp
$$PWD/magneticwidget.cpp
win32 {
SOURCES += $$PWD/windowframelesshelper.cpp
SOURCES +=
}
mac {
SOURCES += $$PWD/windowframelesshelper.mm
SOURCES +=
}
linux {
SOURCES += $$PWD/windowframelesshelper.cpp
SOURCES +=
}
include ($$PWD/windowframelesshelper/windowframelesshelper.pri)

View file

@ -0,0 +1,5 @@
#include "nativewindowutils.h"
quint64 NativeWindowUtils::GetHandleByWId(WId id) {
return (quint64)id;
}

View file

@ -0,0 +1,8 @@
#pragma once
#include <QWindow>
class NativeWindowUtils
{
public:
static quint64 GetHandleByWId(WId id);
};

View file

@ -0,0 +1,15 @@
#include "nativewindowutils.h"
#include <Cocoa/Cocoa.h>
quint64 NativeWindowUtils::GetHandleByWId(WId id) {
NSView* view = (NSView*)id;
if (nullptr == view) {
return 0;
}
NSWindow *window = view.window;
if (nullptr == window) {
return 0;
}
return (quint64)window;
}

View file

@ -1,9 +1,17 @@
#include "windowframelesshelper.h"
#include "windownativeeventfilter.h"
#include "windowframelessmanager.h"
WindowFramelessHelper::WindowFramelessHelper(QObject *parent) : QObject(parent)
{
WindowFramelessManager::Instance()->addWindow(this);
#ifdef Q_OS_WIN32
WindowNativeEventFilter::Instance()->Init();
#endif
}
WindowFramelessHelper::~WindowFramelessHelper()
{
WindowFramelessManager::Instance()->removeWindow(this);
}
QQuickWindow *WindowFramelessHelper::target() const
@ -13,18 +21,10 @@ QQuickWindow *WindowFramelessHelper::target() const
void WindowFramelessHelper::setTarget(QQuickWindow *target)
{
if (target == m_target) {
if (target == nullptr || target == m_target) {
return;
}
m_target = target;
#ifdef Q_OS_WIN32
WindowNativeEventFilter::Instance()->Init();
#endif
emit targetChanged();
}
void WindowFramelessHelper::updateStyle() {
}

View file

@ -1,5 +1,4 @@
#ifndef WindowFramelessHelper_H
#define WindowFramelessHelper_H
#pragma once
#include <QQuickWindow>
@ -10,18 +9,14 @@ class WindowFramelessHelper : public QObject
public:
explicit WindowFramelessHelper(QObject *parent = nullptr);
virtual ~WindowFramelessHelper();
QQuickWindow *target() const;
void setTarget(QQuickWindow *target);
protected:
void updateStyle();
signals:
void targetChanged();
private:
QQuickWindow* m_target = nullptr;
};
#endif // WindowFramelessHelper_H

View file

@ -0,0 +1,24 @@
HEADERS += \
$$PWD/nativewindowutils.h \
$$PWD/windowframelesshelper.h \
$$PWD/windowframelessmanager.h
SOURCES += \
$$PWD/windowframelesshelper.cpp \
$$PWD/windowframelessmanager.cpp
win32 {
HEADERS += $$PWD/windownativeeventfilterwin.h
SOURCES += $$PWD/windownativeeventfilterwin.cpp \
$$PWD/nativewindowutils.cpp \
}
mac {
HEADERS += $$PWD/windownativeeventfiltermac.h
SOURCES += $$PWD/windownativeeventfiltermac.mm \
$$PWD/nativewindowutils.mm
}
linux {
}

View file

@ -0,0 +1,54 @@
#include "windowframelessmanager.h"
#include "windowframelesshelper.h"
#include "nativewindowutils.h"
static QVector<WindowFramelessHelper*> s_windowFramelessHelpers;
WindowFramelessManager::WindowFramelessManager()
{
}
WindowFramelessManager::~WindowFramelessManager()
{
}
WindowFramelessManager *WindowFramelessManager::Instance()
{
static WindowFramelessManager windowNativeEventFilter;
return &windowNativeEventFilter;
}
void WindowFramelessManager::addWindow(WindowFramelessHelper* win)
{
if (nullptr == win) {
return;
}
s_windowFramelessHelpers.push_back(win);
}
void WindowFramelessManager::removeWindow(WindowFramelessHelper* win)
{
if (nullptr == win) {
return;
}
s_windowFramelessHelpers.removeOne(win);
}
WindowFramelessHelper* WindowFramelessManager::getWindowByHandle(quint64 handle)
{
quint64 targetHandle = 0;
for (auto i = s_windowFramelessHelpers.begin(); i != s_windowFramelessHelpers.end(); i++) {
if ((*i)->target() == nullptr) {
continue;
}
targetHandle = NativeWindowUtils::GetHandleByWId((*i)->target()->winId());
if (targetHandle == handle) {
return (*i);
}
}
return nullptr;
}

View file

@ -0,0 +1,20 @@
#pragma once
#include <QQuickWindow>
#include <QVector>
class WindowFramelessHelper;
class WindowFramelessManager
{
private:
WindowFramelessManager();
public:
virtual ~WindowFramelessManager();
public:
static WindowFramelessManager* Instance();
void addWindow(WindowFramelessHelper* win);
void removeWindow(WindowFramelessHelper* win);
WindowFramelessHelper* getWindowByHandle(quint64 handle);
};

View file

@ -0,0 +1,7 @@
#pragma once
class windownativeeventfiltermac
{
public:
windownativeeventfiltermac();
};

View file

@ -1,9 +1,15 @@
#include "windowframelesshelper.h"
#include "windownativeeventfiltermac.h"
#include <Cocoa/Cocoa.h>
#include <QOperatingSystemVersion>
#import <objc/runtime.h>
#include <QDebug>
#include "windowframelessmanager.h"
windownativeeventfiltermac::windownativeeventfiltermac()
{
}
void SwizzleSelector(Class originalCls, SEL originalSelector, Class swizzledCls, SEL swizzledSelector) {
Method originalMethod = class_getInstanceMethod(originalCls, originalSelector);
@ -48,16 +54,20 @@ void SwizzleSelector(Class originalCls, SEL originalSelector, Class swizzledCls,
{
[self filter_setStyleMask:styleMask];
//设置标题文字和图标为不可见
self.titleVisibility = NSWindowTitleHidden;
//设置标题栏为透明
self.titlebarAppearsTransparent = YES;
//设置不可由标题栏拖动,避免与自定义拖动冲突
self.movable = NO;
self.hasShadow = YES;
//设置view扩展到标题栏
if (!(self.styleMask & NSWindowStyleMaskFullSizeContentView)) {
self.styleMask |= NSWindowStyleMaskFullSizeContentView;
WindowFramelessHelper* win = WindowFramelessManager::Instance()->getWindowByHandle((quint64)self);
if (win) {
//设置标题文字和图标为不可见
self.titleVisibility = NSWindowTitleHidden;
//设置标题栏为透明
self.titlebarAppearsTransparent = YES;
//设置不可由标题栏拖动,避免与自定义拖动冲突
self.movable = NO;
self.hasShadow = YES;
//设置view扩展到标题栏
if (!(self.styleMask & NSWindowStyleMaskFullSizeContentView)) {
self.styleMask |= NSWindowStyleMaskFullSizeContentView;
}
}
}
@ -66,58 +76,3 @@ void SwizzleSelector(Class originalCls, SEL originalSelector, Class swizzledCls,
[self filter_setTitlebarAppearsTransparent:titlebarAppearsTransparent];
}
@end
WindowFramelessHelper::WindowFramelessHelper(QObject *parent) : QObject(parent)
{
}
QQuickWindow *WindowFramelessHelper::target() const
{
return m_target;
}
void WindowFramelessHelper::setTarget(QQuickWindow *target)
{
if (target == m_target) {
return;
}
m_target = target;
//updateStyle();
emit targetChanged();
}
/*
void WindowFramelessHelper::updateStyle() {
if (!m_target) {
return;
}
//如果当前osx版本老于10.9,则后续代码不可用。转为使用定制的系统按钮,不支持自由缩放窗口及窗口阴影
if (QOperatingSystemVersion::current() < QOperatingSystemVersion::OSXYosemite) {
return;
}
NSView* view = (NSView*)m_target->winId();
if (nullptr == view) {
return;
}
NSWindow *window = view.window;
if (nullptr == window) {
return;
}
//设置标题文字和图标为不可见
window.titleVisibility = NSWindowTitleHidden;
//设置标题栏为透明
window.titlebarAppearsTransparent = YES;
//设置不可由标题栏拖动,避免与自定义拖动冲突
window.movable = NO;
window.hasShadow = YES;
//设置view扩展到标题栏
window.styleMask |= NSWindowStyleMaskFullSizeContentView;
}
*/

View file

@ -0,0 +1,254 @@
#include "windownativeeventfilter.h"
#if defined(Q_OS_WIN)
#include <QCursor>
#include <QDebug>
#include <QGuiApplication>
#include <windows.h>
#include <windowsx.h>
WindowNativeEventFilter::WindowNativeEventFilter() {}
WindowNativeEventFilter::~WindowNativeEventFilter()
{
// do nothing, because this object is static instance
}
WindowNativeEventFilter *WindowNativeEventFilter::Instance()
{
static WindowNativeEventFilter g_windowNativeEventFilter;
return &g_windowNativeEventFilter;
}
void WindowNativeEventFilter::Init()
{
if (!m_inited) {
m_inited = true;
QGuiApplication::instance()->installNativeEventFilter(this);
}
}
bool WindowNativeEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
{
Q_UNUSED(eventType);
MSG *param = static_cast<MSG *>(message);
if (!param) {
return false;
}
switch (param->message) {
case WM_NCHITTEST: {
if (processNcHitTest(message, result)) {
return true;
}
} break;
case WM_NCLBUTTONDOWN: {
if (processNcLButtonDown(message, result)) {
return true;
}
} break;
case WM_SETCURSOR: {
if (processSetCursor(message, result)) {
return true;
}
} break;
default:
break;
}
return false;
}
bool WindowNativeEventFilter::processNcHitTest(void *message, long *result)
{
MSG *param = static_cast<MSG *>(message);
if (!param) {
return false;
}
QWindow *window = getWindow((WId)param->hwnd);
if (!window) {
return false;
}
// 没有最大化&全屏按钮,则认为是固定尺寸窗口
if (!(window->flags() & Qt::WindowMaximizeButtonHint) && !(window->flags() & Qt::WindowFullscreenButtonHint)) {
return false;
}
qreal dpi = window->devicePixelRatio();
if (dpi <= 0.99) {
dpi = 1.0;
}
QPoint ptCursor = QCursor::pos();
int nX = ptCursor.x() - window->geometry().x();
int nY = ptCursor.y() - window->geometry().y();
*result = HTCLIENT;
if (Qt::WindowMaximized == window->windowState()) {
return false;
}
if ((window->windowStates() & Qt::WindowMaximized) || (window->windowStates() & Qt::WindowFullScreen)) {
return false;
}
qDebug() << "processNcHitTest:" << ptCursor << window->geometry() << "d:" << nX;
int borderWidth = 5;
if ((nX > m_windowMargin.left()) && (nX < m_windowMargin.left() + borderWidth) && (nY > m_windowMargin.top())
&& (nY < m_windowMargin.top() + borderWidth)) {
*result = HTTOPLEFT;
} else if (
(nX > window->width() - m_windowMargin.right() - borderWidth) && (nX < window->width() - m_windowMargin.right()) && (nY > m_windowMargin.top())
&& (nY < m_windowMargin.top() + borderWidth)) {
*result = HTTOPRIGHT;
} else if (
(nX > m_windowMargin.left()) && (nX < m_windowMargin.left() + borderWidth) && (nY > window->height() - m_windowMargin.bottom() - borderWidth)
&& (nY < window->height() - m_windowMargin.bottom())) {
*result = HTBOTTOMLEFT;
} else if (
(nX > window->width() - m_windowMargin.right() - borderWidth) && (nX < window->width() - m_windowMargin.right())
&& (nY > window->height() - m_windowMargin.bottom() - borderWidth) && (nY < window->height() - m_windowMargin.bottom())) {
*result = HTBOTTOMRIGHT;
} else if ((nX > m_windowMargin.left()) && (nX < m_windowMargin.left() + borderWidth)) {
*result = HTLEFT;
} else if ((nX > window->width() - m_windowMargin.right() - borderWidth) && (nX < window->width() - m_windowMargin.right())) {
*result = HTRIGHT;
} else if ((nY > m_windowMargin.top()) && (nY < m_windowMargin.top() + borderWidth)) {
*result = HTTOP;
} else if ((nY > window->height() - m_windowMargin.bottom() - borderWidth) && (nY < window->height() - m_windowMargin.bottom())) {
*result = HTBOTTOM;
}
return true;
}
bool WindowNativeEventFilter::processNcLButtonDown(void *message, long *result)
{
Q_UNUSED(result);
MSG *param = static_cast<MSG *>(message);
if (!param) {
return false;
}
QWindow *window = getWindow((WId)param->hwnd);
if (!window) {
return false;
}
if (!(window->flags() & Qt::WindowMaximizeButtonHint) && !(window->flags() & Qt::WindowFullscreenButtonHint)) {
return false;
}
if ((window->windowStates() & Qt::WindowMaximized) || (window->windowStates() & Qt::WindowFullScreen)) {
return false;
}
HWND hwnd = param->hwnd;
WPARAM nHitTest = param->wParam;
if (nHitTest == HTTOP) {
::PostMessage(hwnd, WM_SYSCOMMAND, SC_SIZE | WMSZ_TOP, param->lParam);
} else if (nHitTest == HTBOTTOM) {
::PostMessage(hwnd, WM_SYSCOMMAND, SC_SIZE | WMSZ_BOTTOM, param->lParam);
} else if (nHitTest == HTLEFT) {
::PostMessage(hwnd, WM_SYSCOMMAND, SC_SIZE | WMSZ_LEFT, param->lParam);
} else if (nHitTest == HTRIGHT) {
::PostMessage(hwnd, WM_SYSCOMMAND, SC_SIZE | WMSZ_RIGHT, param->lParam);
} else if (nHitTest == HTTOPLEFT) {
::PostMessage(hwnd, WM_SYSCOMMAND, SC_SIZE | WMSZ_TOPLEFT, param->lParam);
} else if (nHitTest == HTTOPRIGHT) {
::PostMessage(hwnd, WM_SYSCOMMAND, SC_SIZE | WMSZ_TOPRIGHT, param->lParam);
} else if (nHitTest == HTBOTTOMLEFT) {
::PostMessage(hwnd, WM_SYSCOMMAND, SC_SIZE | WMSZ_BOTTOMLEFT, param->lParam);
} else if (nHitTest == HTBOTTOMRIGHT) {
::PostMessage(hwnd, WM_SYSCOMMAND, SC_SIZE | WMSZ_BOTTOMRIGHT, param->lParam);
} else if (nHitTest == HTCAPTION) {
::PostMessage(hwnd, WM_SYSCOMMAND, SC_MOVE + 1, 0);
}
return false;
}
bool WindowNativeEventFilter::processSetCursor(void *message, long *result)
{
Q_UNUSED(result);
MSG *param = static_cast<MSG *>(message);
if (!param) {
return false;
}
QWindow *window = getWindow((WId)param->hwnd);
if (!window) {
return false;
}
if (!(window->flags() & Qt::WindowMaximizeButtonHint) && !(window->flags() & Qt::WindowFullscreenButtonHint)) {
return false;
}
if ((window->windowStates() & Qt::WindowMaximized) || (window->windowStates() & Qt::WindowFullScreen)) {
return false;
}
// is invisible window
HWND hwnd = param->hwnd;
if (!::IsWindowVisible(hwnd)) {
return true;
}
// is not enabled window
if (!::IsWindowEnabled(hwnd)) {
return true;
}
// set cursor
HCURSOR hCursor = nullptr;
LPARAM hittest = LOWORD(param->lParam);
switch (hittest) {
case HTRIGHT:
case HTLEFT:
hCursor = LoadCursor(nullptr, IDC_SIZEWE);
break;
case HTTOP:
case HTBOTTOM:
hCursor = LoadCursor(nullptr, IDC_SIZENS);
break;
case HTTOPLEFT:
case HTBOTTOMRIGHT:
hCursor = LoadCursor(nullptr, IDC_SIZENWSE);
break;
case HTTOPRIGHT:
case HTBOTTOMLEFT:
hCursor = LoadCursor(nullptr, IDC_SIZENESW);
break;
default:
break;
}
if (!hCursor) {
return false;
}
::SetCursor(hCursor);
::DestroyCursor(hCursor);
return true;
}
QWindow *WindowNativeEventFilter::getWindow(WId wndId)
{
QWindowList windows = QGuiApplication::topLevelWindows();
for (int i = 0; i < windows.size(); ++i) {
if (windows.at(i)->winId() == wndId) {
return windows.at(i);
}
}
return nullptr;
}
#endif //(Q_OS_WIN)

View file

@ -0,0 +1,32 @@
#pragma once
#include <QWindow>
#if defined(Q_OS_WIN)
#include <QAbstractNativeEventFilter>
#include <QMargins>
class WindowNativeEventFilter : public QAbstractNativeEventFilter
{
protected:
WindowNativeEventFilter();
~WindowNativeEventFilter() override;
public:
static WindowNativeEventFilter *Instance();
void Init();
bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override;
private:
bool processNcHitTest(void *message, long *result);
bool processNcLButtonDown(void *message, long *result);
bool processSetCursor(void *message, long *result);
QWindow *getWindow(WId wndId);
private:
bool m_inited = false;
QMargins m_windowMargin = QMargins(0, 0, 0, 0);
};
#endif // Q_OS_WIN