forked from github_mirror/framelesshelper
parent
9e2e0bffed
commit
bc4d034a26
42
README.md
42
README.md
|
@ -1,5 +1,13 @@
|
||||||
# FramelessHelper
|
# FramelessHelper
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Frameless but have frame shadow.
|
||||||
|
- Drag and resize.
|
||||||
|
- High DPI scaling.
|
||||||
|
- Multi-monitor support (different resolution and DPI).
|
||||||
|
- Windows platform: act like a normal window, such as have animations when minimizing and maximizing, support tile windows, etc ...
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
|
@ -28,20 +36,12 @@ Notes
|
||||||
- Only top level windows can be frameless. Applying this code to child windows or widgets will result in unexpected behavior.
|
- Only top level windows can be frameless. Applying this code to child windows or widgets will result in unexpected behavior.
|
||||||
- If you want to use your own border width, border height, titlebar height or minimum window size, just use the original numbers, no need to scale them according to DPI, this code will do the scaling automatically.
|
- If you want to use your own border width, border height, titlebar height or minimum window size, just use the original numbers, no need to scale them according to DPI, this code will do the scaling automatically.
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Frameless but have frame shadow.
|
|
||||||
- Drag and resize.
|
|
||||||
- High DPI scaling.
|
|
||||||
- Multi-monitor support (different resolution and DPI).
|
|
||||||
- Windows: act like a normal window, such as have animations when minimizing and maximizing, support tile windows, etc ...
|
|
||||||
|
|
||||||
## Tested Platforms
|
## Tested Platforms
|
||||||
|
|
||||||
- Windows 7 ~ 10
|
- Windows 7 ~ 10
|
||||||
- Should work on X11, Wayland and macOS, but not tested.
|
- Should work on X11, Wayland and macOS, but not tested.
|
||||||
|
|
||||||
## For Windows developers
|
## Notes for Windows developers
|
||||||
|
|
||||||
- The `FramelessHelper` class is just a simple wrapper of the `WinNativeEventFilter` class, you can use the latter directly instead if you encounter with some strange problems.
|
- The `FramelessHelper` class is just a simple wrapper of the `WinNativeEventFilter` class, you can use the latter directly instead if you encounter with some strange problems.
|
||||||
- If you are using `WinNativeEventFilter` directly, don't forget to call `FramelessHelper::updateQtFrame` everytime after you make a widget or window become frameless, it will make the new frame margins work correctly if `setGeometry` or `frameGeometry` is called.
|
- If you are using `WinNativeEventFilter` directly, don't forget to call `FramelessHelper::updateQtFrame` everytime after you make a widget or window become frameless, it will make the new frame margins work correctly if `setGeometry` or `frameGeometry` is called.
|
||||||
|
@ -53,13 +53,33 @@ Notes
|
||||||
- The border width (8 if not scaled), border height (8 if not scaled) and titlebar height (30 if not scaled) are acquired by Win32 APIs and are the same with other standard windows, and thus you should not modify them.
|
- The border width (8 if not scaled), border height (8 if not scaled) and titlebar height (30 if not scaled) are acquired by Win32 APIs and are the same with other standard windows, and thus you should not modify them.
|
||||||
- You can also copy all the code to `[virtual protected] bool QWidget::nativeEvent(const QByteArray &eventType, void *message, long *result)` or `[virtual protected] bool QWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)`, it's the same with install a native event filter to the application.
|
- You can also copy all the code to `[virtual protected] bool QWidget::nativeEvent(const QByteArray &eventType, void *message, long *result)` or `[virtual protected] bool QWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)`, it's the same with install a native event filter to the application.
|
||||||
|
|
||||||
## References
|
## References for Windows developers
|
||||||
|
|
||||||
|
### Microsoft Docs
|
||||||
|
|
||||||
|
- <https://docs.microsoft.com/en-us/archive/blogs/wpfsdk/custom-window-chrome-in-wpf>
|
||||||
|
- <https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize>
|
||||||
|
- <https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-nchittest>
|
||||||
|
- <https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-getminmaxinfo>
|
||||||
|
- <https://docs.microsoft.com/en-us/windows/win32/dwm/customframe>
|
||||||
|
- <https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows>
|
||||||
|
|
||||||
|
### Chromium
|
||||||
|
|
||||||
|
- <https://github.com/chromium/chromium/blob/master/ui/base/win/hwnd_metrics.cc>
|
||||||
|
- <https://github.com/chromium/chromium/blob/master/ui/display/win/screen_win.cc>
|
||||||
|
- <https://github.com/chromium/chromium/blob/master/ui/views/win/hwnd_message_handler.cc>
|
||||||
|
|
||||||
|
### Mozilla Firefox
|
||||||
|
|
||||||
|
- <https://github.com/mozilla/gecko-dev/blob/master/widget/windows/nsWindow.cpp>
|
||||||
|
|
||||||
|
### GitHub
|
||||||
|
|
||||||
- <https://github.com/rossy/borderless-window>
|
- <https://github.com/rossy/borderless-window>
|
||||||
- <https://github.com/Bringer-of-Light/Qt-Nice-Frameless-Window>
|
- <https://github.com/Bringer-of-Light/Qt-Nice-Frameless-Window>
|
||||||
- <https://github.com/dfct/TrueFramelessWindow>
|
- <https://github.com/dfct/TrueFramelessWindow>
|
||||||
- <https://github.com/qtdevs/FramelessHelper>
|
- <https://github.com/qtdevs/FramelessHelper>
|
||||||
- <https://docs.microsoft.com/en-us/archive/blogs/wpfsdk/custom-window-chrome-in-wpf>
|
|
||||||
|
|
||||||
## Special Thanks
|
## Special Thanks
|
||||||
|
|
||||||
|
|
|
@ -205,6 +205,10 @@ BOOL isCompositionEnabled() {
|
||||||
return SUCCEEDED(m_lpDwmIsCompositionEnabled(&enabled)) && enabled;
|
return SUCCEEDED(m_lpDwmIsCompositionEnabled(&enabled)) && enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The thickness of an auto-hide taskbar in pixels.
|
||||||
|
const int kAutoHideTaskbarThicknessPx = 2;
|
||||||
|
const int kAutoHideTaskbarThicknessPy = kAutoHideTaskbarThicknessPx;
|
||||||
|
|
||||||
const UINT m_defaultDotsPerInch = USER_DEFAULT_SCREEN_DPI;
|
const UINT m_defaultDotsPerInch = USER_DEFAULT_SCREEN_DPI;
|
||||||
|
|
||||||
const qreal m_defaultDevicePixelRatio = 1.0;
|
const qreal m_defaultDevicePixelRatio = 1.0;
|
||||||
|
@ -219,7 +223,7 @@ QVector<HWND> m_framelessWindows;
|
||||||
|
|
||||||
WinNativeEventFilter::WinNativeEventFilter() { initWin32Api(); }
|
WinNativeEventFilter::WinNativeEventFilter() { initWin32Api(); }
|
||||||
|
|
||||||
WinNativeEventFilter::~WinNativeEventFilter() { removeUserData(); };
|
WinNativeEventFilter::~WinNativeEventFilter() = default;
|
||||||
|
|
||||||
void WinNativeEventFilter::install() {
|
void WinNativeEventFilter::install() {
|
||||||
if (m_instance.isNull()) {
|
if (m_instance.isNull()) {
|
||||||
|
@ -399,59 +403,78 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
|
||||||
// entry, the structure contains the proposed window rectangle for the
|
// entry, the structure contains the proposed window rectangle for the
|
||||||
// window. On exit, the structure should contain the screen coordinates
|
// window. On exit, the structure should contain the screen coordinates
|
||||||
// of the corresponding window client area.
|
// of the corresponding window client area.
|
||||||
|
const auto getClientAreaInsets = [](HWND _hWnd) -> RECT {
|
||||||
|
if (IsMaximized(_hWnd)) {
|
||||||
|
// Windows automatically adds a standard width border to all
|
||||||
|
// sides when a window is maximized.
|
||||||
|
int frameThickness_x =
|
||||||
|
getSystemMetricsForWindow(_hWnd, SM_CXSIZEFRAME) +
|
||||||
|
getSystemMetricsForWindow(_hWnd, SM_CXPADDEDBORDER);
|
||||||
|
int frameThickness_y =
|
||||||
|
getSystemMetricsForWindow(_hWnd, SM_CYSIZEFRAME) +
|
||||||
|
getSystemMetricsForWindow(_hWnd, SM_CXPADDEDBORDER);
|
||||||
|
// TODO: Chromium: HWNDMessageHandlerDelegate::HasFrame()
|
||||||
|
const bool hasFrame = false;
|
||||||
|
if (!hasFrame) {
|
||||||
|
frameThickness_x -= 1;
|
||||||
|
frameThickness_y -= 1;
|
||||||
|
}
|
||||||
|
RECT rect;
|
||||||
|
rect.top = frameThickness_y;
|
||||||
|
rect.bottom = frameThickness_y;
|
||||||
|
rect.left = frameThickness_x;
|
||||||
|
rect.right = frameThickness_x;
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
return {0, 0, 0, 0};
|
||||||
|
};
|
||||||
|
const RECT insets = getClientAreaInsets(msg->hwnd);
|
||||||
const auto mode = static_cast<BOOL>(msg->wParam);
|
const auto mode = static_cast<BOOL>(msg->wParam);
|
||||||
// If the window bounds change, we're going to relayout and repaint
|
// If the window bounds change, we're going to relayout and repaint
|
||||||
// anyway. Returning WVR_REDRAW avoids an extra paint before that of the
|
// anyway. Returning WVR_REDRAW avoids an extra paint before that of the
|
||||||
// old client pixels in the (now wrong) location, and thus makes actions
|
// old client pixels in the (now wrong) location, and thus makes actions
|
||||||
// like resizing a window from the left edge look slightly less broken.
|
// like resizing a window from the left edge look slightly less broken.
|
||||||
*result = mode ? WVR_REDRAW : 0;
|
*result = mode ? WVR_REDRAW : 0;
|
||||||
|
// We special case when left or top insets are 0, since these conditions
|
||||||
|
// actually require another repaint to correct the layout after glass
|
||||||
|
// gets turned on and off.
|
||||||
|
if ((insets.left == 0) || (insets.top == 0)) {
|
||||||
|
*result = 0;
|
||||||
|
}
|
||||||
|
const auto clientRect = mode
|
||||||
|
? &(reinterpret_cast<LPNCCALCSIZE_PARAMS>(msg->lParam)->rgrc[0])
|
||||||
|
: reinterpret_cast<LPRECT>(msg->lParam);
|
||||||
|
clientRect->top += insets.top;
|
||||||
|
clientRect->bottom -= insets.bottom;
|
||||||
|
clientRect->left += insets.left;
|
||||||
|
clientRect->right -= insets.right;
|
||||||
if (IsMaximized(msg->hwnd)) {
|
if (IsMaximized(msg->hwnd)) {
|
||||||
const HMONITOR windowMonitor =
|
APPBARDATA abd;
|
||||||
m_lpMonitorFromWindow(msg->hwnd, MONITOR_DEFAULTTONEAREST);
|
SecureZeroMemory(&abd, sizeof(abd));
|
||||||
MONITORINFO monitorInfo;
|
abd.cbSize = sizeof(abd);
|
||||||
SecureZeroMemory(&monitorInfo, sizeof(monitorInfo));
|
const UINT taskbarState = m_lpSHAppBarMessage(ABM_GETSTATE, &abd);
|
||||||
monitorInfo.cbSize = sizeof(monitorInfo);
|
if (taskbarState & ABS_AUTOHIDE) {
|
||||||
m_lpGetMonitorInfoW(windowMonitor, &monitorInfo);
|
int edge = -1;
|
||||||
const auto rect = mode
|
abd.hWnd = m_lpFindWindowW(L"Shell_TrayWnd", nullptr);
|
||||||
? &(reinterpret_cast<LPNCCALCSIZE_PARAMS>(msg->lParam)->rgrc[0])
|
if (abd.hWnd) {
|
||||||
: reinterpret_cast<LPRECT>(msg->lParam);
|
const HMONITOR windowMonitor = m_lpMonitorFromWindow(
|
||||||
*rect = monitorInfo.rcWork;
|
msg->hwnd, MONITOR_DEFAULTTONEAREST);
|
||||||
// If the client rectangle is the same as the monitor's
|
const HMONITOR taskbarMonitor = m_lpMonitorFromWindow(
|
||||||
// rectangle, the shell assumes that the window has gone
|
abd.hWnd, MONITOR_DEFAULTTOPRIMARY);
|
||||||
// fullscreen, so it removes the topmost attribute from any
|
if (taskbarMonitor == windowMonitor) {
|
||||||
// auto-hide appbars, making them inaccessible. To avoid
|
m_lpSHAppBarMessage(ABM_GETTASKBARPOS, &abd);
|
||||||
// this, reduce the size of the client area by one pixel on
|
edge = abd.uEdge;
|
||||||
// a certain edge. The edge is chosen based on which side of
|
|
||||||
// the monitor is likely to contain an auto-hide appbar, so
|
|
||||||
// the missing client area is covered by it.
|
|
||||||
if (m_lpEqualRect(&monitorInfo.rcWork, &monitorInfo.rcMonitor)) {
|
|
||||||
APPBARDATA abd;
|
|
||||||
SecureZeroMemory(&abd, sizeof(abd));
|
|
||||||
abd.cbSize = sizeof(abd);
|
|
||||||
const UINT taskbarState =
|
|
||||||
m_lpSHAppBarMessage(ABM_GETSTATE, &abd);
|
|
||||||
if (taskbarState & ABS_AUTOHIDE) {
|
|
||||||
int edge = -1;
|
|
||||||
abd.hWnd = m_lpFindWindowW(L"Shell_TrayWnd", nullptr);
|
|
||||||
if (abd.hWnd) {
|
|
||||||
const HMONITOR taskbarMonitor = m_lpMonitorFromWindow(
|
|
||||||
abd.hWnd, MONITOR_DEFAULTTOPRIMARY);
|
|
||||||
if (taskbarMonitor &&
|
|
||||||
(taskbarMonitor == windowMonitor)) {
|
|
||||||
m_lpSHAppBarMessage(ABM_GETTASKBARPOS, &abd);
|
|
||||||
edge = abd.uEdge;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (edge == ABE_BOTTOM) {
|
|
||||||
rect->bottom--;
|
|
||||||
} else if (edge == ABE_LEFT) {
|
|
||||||
rect->left++;
|
|
||||||
} else if (edge == ABE_TOP) {
|
|
||||||
rect->top++;
|
|
||||||
} else if (edge == ABE_RIGHT) {
|
|
||||||
rect->right--;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (edge == ABE_TOP) {
|
||||||
|
clientRect->top += kAutoHideTaskbarThicknessPy;
|
||||||
|
} else if (edge == ABE_BOTTOM) {
|
||||||
|
clientRect->bottom -= kAutoHideTaskbarThicknessPy;
|
||||||
|
} else if (edge == ABE_LEFT) {
|
||||||
|
clientRect->left += kAutoHideTaskbarThicknessPx;
|
||||||
|
} else if (edge == ABE_RIGHT) {
|
||||||
|
clientRect->right -= kAutoHideTaskbarThicknessPx;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// We cannot return WVR_REDRAW when there is nonclient area, or
|
// We cannot return WVR_REDRAW when there is nonclient area, or
|
||||||
// Windows exhibits bugs where client pixels and child HWNDs are
|
// Windows exhibits bugs where client pixels and child HWNDs are
|
||||||
|
@ -516,8 +539,9 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
|
||||||
const LONG ww = clientRect.right;
|
const LONG ww = clientRect.right;
|
||||||
const LONG wh = clientRect.bottom;
|
const LONG wh = clientRect.bottom;
|
||||||
POINT mouse;
|
POINT mouse;
|
||||||
// Don't use HIWORD or LOWORD because their results are unsigned numbers
|
// Don't use HIWORD(lParam) and LOWORD(lParam) to get cursor
|
||||||
// however the cursor position may be negative due to in a different
|
// coordinates because their results are unsigned numbers, however
|
||||||
|
// the cursor position may be negative due to in a different
|
||||||
// monitor.
|
// monitor.
|
||||||
mouse.x = GET_X_LPARAM(_lParam);
|
mouse.x = GET_X_LPARAM(_lParam);
|
||||||
mouse.y = GET_Y_LPARAM(_lParam);
|
mouse.y = GET_Y_LPARAM(_lParam);
|
||||||
|
@ -637,8 +661,7 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
|
||||||
case WM_SETTEXT: {
|
case WM_SETTEXT: {
|
||||||
// Disable painting while these messages are handled to prevent them
|
// Disable painting while these messages are handled to prevent them
|
||||||
// from drawing a window caption over the client area.
|
// from drawing a window caption over the client area.
|
||||||
const LONG_PTR oldStyle =
|
const LONG_PTR oldStyle = m_lpGetWindowLongPtrW(msg->hwnd, GWL_STYLE);
|
||||||
m_lpGetWindowLongPtrW(msg->hwnd, GWL_STYLE);
|
|
||||||
// Prevent Windows from drawing the default title bar by temporarily
|
// Prevent Windows from drawing the default title bar by temporarily
|
||||||
// toggling the WS_VISIBLE style.
|
// toggling the WS_VISIBLE style.
|
||||||
m_lpSetWindowLongPtrW(msg->hwnd, GWL_STYLE, oldStyle & ~WS_VISIBLE);
|
m_lpSetWindowLongPtrW(msg->hwnd, GWL_STYLE, oldStyle & ~WS_VISIBLE);
|
||||||
|
@ -655,7 +678,7 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
|
||||||
case WM_ERASEBKGND: {
|
case WM_ERASEBKGND: {
|
||||||
// Prevent the system from erasing the background of our window
|
// Prevent the system from erasing the background of our window
|
||||||
// to avoid weired flashing problems.
|
// to avoid weired flashing problems.
|
||||||
*result = 1; // Any non-zero content is OK.
|
*result = 1; // Any non-zero value is OK.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
@ -682,6 +705,8 @@ void WinNativeEventFilter::updateGlass(HWND handle) {
|
||||||
margins = {-1, -1, -1, -1};
|
margins = {-1, -1, -1, -1};
|
||||||
}
|
}
|
||||||
m_lpDwmExtendFrameIntoClientArea(handle, &margins);
|
m_lpDwmExtendFrameIntoClientArea(handle, &margins);
|
||||||
|
m_lpRedrawWindow(handle, nullptr, nullptr,
|
||||||
|
RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN);
|
||||||
}
|
}
|
||||||
|
|
||||||
UINT WinNativeEventFilter::getDotsPerInchForWindow(HWND handle) {
|
UINT WinNativeEventFilter::getDotsPerInchForWindow(HWND handle) {
|
||||||
|
@ -914,15 +939,3 @@ qreal WinNativeEventFilter::getPreferedNumber(qreal num) {
|
||||||
#endif
|
#endif
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WinNativeEventFilter::removeUserData() {
|
|
||||||
// TODO: all top level windows of QGuiApplication.
|
|
||||||
if (!m_framelessWindows.isEmpty()) {
|
|
||||||
for (auto &&window : std::as_const(m_framelessWindows)) {
|
|
||||||
const auto userData = reinterpret_cast<WINDOW *>(m_lpGetWindowLongPtrW(window, GWLP_USERDATA));
|
|
||||||
if (userData) {
|
|
||||||
delete userData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -110,5 +110,4 @@ private:
|
||||||
static UINT getDotsPerInchForWindow(HWND handle);
|
static UINT getDotsPerInchForWindow(HWND handle);
|
||||||
static qreal getDevicePixelRatioForWindow(HWND handle);
|
static qreal getDevicePixelRatioForWindow(HWND handle);
|
||||||
static int getSystemMetricsForWindow(HWND handle, int index);
|
static int getSystemMetricsForWindow(HWND handle, int index);
|
||||||
void removeUserData();
|
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue