506 lines
13 KiB
C++
506 lines
13 KiB
C++
// This file is part of Desktop App Toolkit,
|
|
// a set of libraries for developing nice desktop applications.
|
|
//
|
|
// For license and copyright information please follow this link:
|
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
|
//
|
|
#include "ui/platform/win/ui_window_win.h"
|
|
|
|
#include "ui/inactive_press.h"
|
|
#include "ui/platform/win/ui_window_title_win.h"
|
|
#include "base/platform/base_platform_info.h"
|
|
#include "styles/palette.h"
|
|
|
|
#include <QtCore/QAbstractNativeEventFilter>
|
|
#include <QtGui/QWindow>
|
|
#include <QtWidgets/QStyleFactory>
|
|
#include <QtWidgets/QApplication>
|
|
#include <qpa/qplatformnativeinterface.h>
|
|
|
|
#include <dwmapi.h>
|
|
#include <uxtheme.h>
|
|
|
|
Q_DECLARE_METATYPE(QMargins);
|
|
|
|
namespace Ui {
|
|
namespace Platform {
|
|
namespace {
|
|
|
|
bool IsCompositionEnabled() {
|
|
auto result = BOOL(FALSE);
|
|
const auto success = (DwmIsCompositionEnabled(&result) == S_OK);
|
|
return success && result;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class WindowHelper::NativeFilter final : public QAbstractNativeEventFilter {
|
|
public:
|
|
void registerWindow(HWND handle, not_null<WindowHelper*> helper);
|
|
void unregisterWindow(HWND handle);
|
|
|
|
bool nativeEventFilter(
|
|
const QByteArray &eventType,
|
|
void *message,
|
|
long *result) override;
|
|
|
|
private:
|
|
base::flat_map<HWND, not_null<WindowHelper*>> _windowByHandle;
|
|
|
|
};
|
|
|
|
void WindowHelper::NativeFilter::registerWindow(
|
|
HWND handle,
|
|
not_null<WindowHelper*> helper) {
|
|
_windowByHandle.emplace(handle, helper);
|
|
}
|
|
|
|
void WindowHelper::NativeFilter::unregisterWindow(HWND handle) {
|
|
_windowByHandle.remove(handle);
|
|
}
|
|
|
|
bool WindowHelper::NativeFilter::nativeEventFilter(
|
|
const QByteArray &eventType,
|
|
void *message,
|
|
long *result) {
|
|
const auto msg = static_cast<MSG*>(message);
|
|
const auto i = _windowByHandle.find(msg->hwnd);
|
|
return (i != end(_windowByHandle))
|
|
? i->second->handleNativeEvent(
|
|
msg->message,
|
|
msg->wParam,
|
|
msg->lParam,
|
|
static_cast<LRESULT*>(result))
|
|
: false;
|
|
}
|
|
|
|
WindowHelper::WindowHelper(not_null<RpWidget*> window)
|
|
: _window(window)
|
|
, _handle(GetWindowHandle(_window))
|
|
, _title(Ui::CreateChild<TitleWidget>(_window.get()))
|
|
, _body(Ui::CreateChild<RpWidget>(_window.get()))
|
|
, _shadow(_window, st::windowShadowFg->c) {
|
|
Expects(_handle != nullptr);
|
|
|
|
GetNativeFilter()->registerWindow(_handle, this);
|
|
init();
|
|
}
|
|
|
|
WindowHelper::~WindowHelper() {
|
|
GetNativeFilter()->unregisterWindow(_handle);
|
|
}
|
|
|
|
not_null<RpWidget*> WindowHelper::body() {
|
|
return _body;
|
|
}
|
|
|
|
void WindowHelper::setTitle(const QString &title) {
|
|
_title->setText(title);
|
|
_window->setWindowTitle(title);
|
|
}
|
|
|
|
void WindowHelper::setTitleStyle(const style::WindowTitle &st) {
|
|
_title->setStyle(st);
|
|
}
|
|
|
|
void WindowHelper::setMinimumSize(QSize size) {
|
|
_window->setMinimumSize(size.width(), _title->height() + size.height());
|
|
}
|
|
|
|
void WindowHelper::setFixedSize(QSize size) {
|
|
_window->setFixedSize(size.width(), _title->height() + size.height());
|
|
_title->setResizeEnabled(false);
|
|
_shadow.setResizeEnabled(false);
|
|
}
|
|
|
|
void WindowHelper::setGeometry(QRect rect) {
|
|
_window->setGeometry(rect.marginsAdded({ 0, _title->height(), 0, 0 }));
|
|
}
|
|
|
|
void WindowHelper::init() {
|
|
style::PaletteChanged(
|
|
) | rpl::start_with_next([=] {
|
|
_shadow.setColor(st::windowShadowFg->c);
|
|
}, _window->lifetime());
|
|
|
|
rpl::combine(
|
|
_window->sizeValue(),
|
|
_title->heightValue()
|
|
) | rpl::start_with_next([=](QSize size, int titleHeight) {
|
|
_body->setGeometry(
|
|
0,
|
|
titleHeight,
|
|
size.width(),
|
|
size.height() - titleHeight);
|
|
}, _body->lifetime());
|
|
|
|
updateMargins();
|
|
|
|
if (!::Platform::IsWindows8OrGreater()) {
|
|
SetWindowTheme(_handle, L" ", L" ");
|
|
QApplication::setStyle(QStyleFactory::create("Windows"));
|
|
}
|
|
|
|
_menu = GetSystemMenu(_handle, FALSE);
|
|
updateSystemMenu();
|
|
|
|
const auto handleStateChanged = [=](Qt::WindowState state) {
|
|
updateSystemMenu(state);
|
|
if (fixedSize() && (state & Qt::WindowMaximized)) {
|
|
crl::on_main(_window.get(), [=] {
|
|
_window->setWindowState(
|
|
_window->windowState() & ~Qt::WindowMaximized);
|
|
});
|
|
}
|
|
};
|
|
Ui::Connect(
|
|
_window->windowHandle(),
|
|
&QWindow::windowStateChanged,
|
|
handleStateChanged);
|
|
}
|
|
|
|
bool WindowHelper::handleNativeEvent(
|
|
UINT msg,
|
|
WPARAM wParam,
|
|
LPARAM lParam,
|
|
LRESULT *result) {
|
|
switch (msg) {
|
|
|
|
case WM_ACTIVATE: {
|
|
if (LOWORD(wParam) == WA_CLICKACTIVE) {
|
|
Ui::MarkInactivePress(_window, true);
|
|
}
|
|
if (LOWORD(wParam) != WA_INACTIVE) {
|
|
_shadow.update(WindowShadow::Change::Activate);
|
|
} else {
|
|
_shadow.update(WindowShadow::Change::Deactivate);
|
|
}
|
|
_window->update();
|
|
} return false;
|
|
|
|
case WM_NCPAINT: {
|
|
if (::Platform::IsWindows8OrGreater()) {
|
|
return false;
|
|
}
|
|
if (result) *result = 0;
|
|
} return true;
|
|
|
|
case WM_NCCALCSIZE: {
|
|
WINDOWPLACEMENT wp;
|
|
wp.length = sizeof(WINDOWPLACEMENT);
|
|
if (GetWindowPlacement(_handle, &wp)
|
|
&& (wp.showCmd == SW_SHOWMAXIMIZED)) {
|
|
const auto params = (LPNCCALCSIZE_PARAMS)lParam;
|
|
const auto r = (wParam == TRUE)
|
|
? ¶ms->rgrc[0]
|
|
: (LPRECT)lParam;
|
|
const auto hMonitor = MonitorFromPoint(
|
|
{ (r->left + r->right) / 2, (r->top + r->bottom) / 2 },
|
|
MONITOR_DEFAULTTONEAREST);
|
|
if (hMonitor) {
|
|
MONITORINFO mi;
|
|
mi.cbSize = sizeof(mi);
|
|
if (GetMonitorInfo(hMonitor, &mi)) {
|
|
*r = mi.rcWork;
|
|
}
|
|
}
|
|
}
|
|
if (result) *result = 0;
|
|
return true;
|
|
}
|
|
|
|
case WM_NCLBUTTONDBLCLK:
|
|
case WM_NCMBUTTONDBLCLK:
|
|
case WM_NCRBUTTONDBLCLK:
|
|
case WM_NCXBUTTONDBLCLK: {
|
|
if (!fixedSize()) {
|
|
return false;
|
|
}
|
|
if (result) *result = 0;
|
|
} return true;
|
|
|
|
case WM_NCACTIVATE: {
|
|
if (IsCompositionEnabled()) {
|
|
const auto res = DefWindowProc(_handle, msg, wParam, -1);
|
|
if (result) *result = res;
|
|
} else {
|
|
// Thanks https://github.com/melak47/BorderlessWindow
|
|
if (result) *result = 1;
|
|
}
|
|
} return true;
|
|
|
|
case WM_WINDOWPOSCHANGING:
|
|
case WM_WINDOWPOSCHANGED: {
|
|
WINDOWPLACEMENT wp;
|
|
wp.length = sizeof(WINDOWPLACEMENT);
|
|
if (GetWindowPlacement(_handle, &wp)
|
|
&& (wp.showCmd == SW_SHOWMAXIMIZED
|
|
|| wp.showCmd == SW_SHOWMINIMIZED)) {
|
|
_shadow.update(WindowShadow::Change::Hidden);
|
|
} else {
|
|
_shadow.update(
|
|
WindowShadow::Change::Moved | WindowShadow::Change::Resized,
|
|
(WINDOWPOS*)lParam);
|
|
}
|
|
} return false;
|
|
|
|
case WM_SIZE: {
|
|
if (wParam == SIZE_MAXIMIZED
|
|
|| wParam == SIZE_RESTORED
|
|
|| wParam == SIZE_MINIMIZED) {
|
|
if (wParam != SIZE_RESTORED
|
|
|| _window->windowState() != Qt::WindowNoState) {
|
|
Qt::WindowState state = Qt::WindowNoState;
|
|
if (wParam == SIZE_MAXIMIZED) {
|
|
state = Qt::WindowMaximized;
|
|
} else if (wParam == SIZE_MINIMIZED) {
|
|
state = Qt::WindowMinimized;
|
|
}
|
|
emit _window->windowHandle()->windowStateChanged(state);
|
|
}
|
|
updateMargins();
|
|
const auto changes = (wParam == SIZE_MINIMIZED
|
|
|| wParam == SIZE_MAXIMIZED)
|
|
? WindowShadow::Change::Hidden
|
|
: (WindowShadow::Change::Resized
|
|
| WindowShadow::Change::Shown);
|
|
_shadow.update(changes);
|
|
}
|
|
} return false;
|
|
|
|
case WM_SHOWWINDOW: {
|
|
const auto style = GetWindowLong(_handle, GWL_STYLE);
|
|
const auto changes = WindowShadow::Change::Resized
|
|
| ((wParam && !(style & (WS_MAXIMIZE | WS_MINIMIZE)))
|
|
? WindowShadow::Change::Shown
|
|
: WindowShadow::Change::Hidden);
|
|
_shadow.update(changes);
|
|
} return false;
|
|
|
|
case WM_MOVE: {
|
|
_shadow.update(WindowShadow::Change::Moved);
|
|
} return false;
|
|
|
|
case WM_NCHITTEST: {
|
|
if (!result) {
|
|
return false;
|
|
}
|
|
|
|
const auto p = MAKEPOINTS(lParam);
|
|
auto r = RECT();
|
|
GetWindowRect(_handle, &r);
|
|
const auto mapped = QPoint(
|
|
p.x - r.left + _marginsDelta.left(),
|
|
p.y - r.top + _marginsDelta.top());
|
|
if (!_window->rect().contains(mapped)) {
|
|
*result = HTTRANSPARENT;
|
|
} else if (!_title->geometry().contains(mapped)) {
|
|
*result = HTCLIENT;
|
|
} else switch (_title->hitTest(_title->pos() + mapped)) {
|
|
case HitTestResult::Client:
|
|
case HitTestResult::SysButton: *result = HTCLIENT; break;
|
|
case HitTestResult::Caption: *result = HTCAPTION; break;
|
|
case HitTestResult::Top: *result = HTTOP; break;
|
|
case HitTestResult::TopRight: *result = HTTOPRIGHT; break;
|
|
case HitTestResult::Right: *result = HTRIGHT; break;
|
|
case HitTestResult::BottomRight: *result = HTBOTTOMRIGHT; break;
|
|
case HitTestResult::Bottom: *result = HTBOTTOM; break;
|
|
case HitTestResult::BottomLeft: *result = HTBOTTOMLEFT; break;
|
|
case HitTestResult::Left: *result = HTLEFT; break;
|
|
case HitTestResult::TopLeft: *result = HTTOPLEFT; break;
|
|
case HitTestResult::None:
|
|
default: *result = HTTRANSPARENT; break;
|
|
};
|
|
} return true;
|
|
|
|
case WM_NCRBUTTONUP: {
|
|
SendMessage(_handle, WM_SYSCOMMAND, SC_MOUSEMENU, lParam);
|
|
} return true;
|
|
|
|
case WM_SYSCOMMAND: {
|
|
if (wParam == SC_MOUSEMENU && !fixedSize()) {
|
|
POINTS p = MAKEPOINTS(lParam);
|
|
updateSystemMenu(_window->windowHandle()->windowState());
|
|
TrackPopupMenu(
|
|
_menu,
|
|
TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON,
|
|
p.x,
|
|
p.y,
|
|
0,
|
|
_handle,
|
|
0);
|
|
}
|
|
} return false;
|
|
|
|
case WM_COMMAND: {
|
|
if (HIWORD(wParam)) {
|
|
return false;
|
|
}
|
|
const auto command = LOWORD(wParam);
|
|
switch (command) {
|
|
case SC_CLOSE: _window->close(); return true;
|
|
case SC_MINIMIZE:
|
|
_window->setWindowState(Qt::WindowMinimized);
|
|
return true;
|
|
case SC_MAXIMIZE:
|
|
if (!fixedSize()) {
|
|
_window->setWindowState(Qt::WindowMaximized);
|
|
}
|
|
return true;
|
|
case SC_RESTORE:
|
|
_window->setWindowState(Qt::WindowNoState);
|
|
return true;
|
|
}
|
|
} return true;
|
|
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool WindowHelper::fixedSize() const {
|
|
return _window->minimumSize() == _window->maximumSize();
|
|
}
|
|
|
|
void WindowHelper::updateMargins() {
|
|
if (_updatingMargins) return;
|
|
|
|
_updatingMargins = true;
|
|
const auto guard = gsl::finally([&] { _updatingMargins = false; });
|
|
|
|
RECT r, a;
|
|
|
|
GetClientRect(_handle, &r);
|
|
a = r;
|
|
|
|
const auto style = GetWindowLong(_handle, GWL_STYLE);
|
|
const auto styleEx = GetWindowLong(_handle, GWL_EXSTYLE);
|
|
AdjustWindowRectEx(&a, style, false, styleEx);
|
|
auto margins = QMargins(
|
|
a.left - r.left,
|
|
a.top - r.top,
|
|
r.right - a.right,
|
|
r.bottom - a.bottom);
|
|
if (style & WS_MAXIMIZE) {
|
|
RECT w, m;
|
|
GetWindowRect(_handle , &w);
|
|
m = w;
|
|
|
|
HMONITOR hMonitor = MonitorFromRect(&w, MONITOR_DEFAULTTONEAREST);
|
|
if (hMonitor) {
|
|
MONITORINFO mi;
|
|
mi.cbSize = sizeof(mi);
|
|
GetMonitorInfo(hMonitor, &mi);
|
|
m = mi.rcWork;
|
|
}
|
|
|
|
_marginsDelta = QMargins(
|
|
w.left - m.left,
|
|
w.top - m.top,
|
|
m.right - w.right,
|
|
m.bottom - w.bottom);
|
|
|
|
margins.setLeft(margins.left() - _marginsDelta.left());
|
|
margins.setRight(margins.right() - _marginsDelta.right());
|
|
margins.setBottom(margins.bottom() - _marginsDelta.bottom());
|
|
margins.setTop(margins.top() - _marginsDelta.top());
|
|
} else if (!_marginsDelta.isNull()) {
|
|
RECT w;
|
|
GetWindowRect(_handle, &w);
|
|
SetWindowPos(
|
|
_handle,
|
|
0,
|
|
0,
|
|
0,
|
|
w.right - w.left - _marginsDelta.left() - _marginsDelta.right(),
|
|
w.bottom - w.top - _marginsDelta.top() - _marginsDelta.bottom(),
|
|
(SWP_NOMOVE
|
|
| SWP_NOSENDCHANGING
|
|
| SWP_NOZORDER
|
|
| SWP_NOACTIVATE
|
|
| SWP_NOREPOSITION));
|
|
_marginsDelta = QMargins();
|
|
}
|
|
|
|
if (const auto native = QGuiApplication::platformNativeInterface()) {
|
|
native->setWindowProperty(
|
|
_window->windowHandle()->handle(),
|
|
"WindowsCustomMargins",
|
|
QVariant::fromValue<QMargins>(margins));
|
|
}
|
|
}
|
|
|
|
void WindowHelper::updateSystemMenu() {
|
|
updateSystemMenu(_window->windowHandle()->windowState());
|
|
}
|
|
|
|
void WindowHelper::updateSystemMenu(Qt::WindowState state) {
|
|
if (!_menu) {
|
|
return;
|
|
}
|
|
|
|
const auto menuToDisable = (state == Qt::WindowMaximized)
|
|
? SC_MAXIMIZE
|
|
: (state == Qt::WindowMinimized)
|
|
? SC_MINIMIZE
|
|
: SC_RESTORE;
|
|
const auto itemCount = GetMenuItemCount(_menu);
|
|
for (int i = 0; i < itemCount; ++i) {
|
|
MENUITEMINFO itemInfo = { 0 };
|
|
itemInfo.cbSize = sizeof(itemInfo);
|
|
itemInfo.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
|
|
if (!GetMenuItemInfo(_menu, i, TRUE, &itemInfo)) {
|
|
break;
|
|
}
|
|
if (itemInfo.fType & MFT_SEPARATOR) {
|
|
continue;
|
|
} else if (!itemInfo.wID || itemInfo.wID == SC_CLOSE) {
|
|
continue;
|
|
}
|
|
UINT fOldState = itemInfo.fState;
|
|
UINT fState = itemInfo.fState & ~(MFS_DISABLED | MFS_DEFAULT);
|
|
if (itemInfo.wID == menuToDisable
|
|
|| (itemInfo.wID != SC_MINIMIZE
|
|
&& itemInfo.wID != SC_MAXIMIZE
|
|
&& itemInfo.wID != SC_RESTORE)) {
|
|
fState |= MFS_DISABLED;
|
|
}
|
|
itemInfo.fMask = MIIM_STATE;
|
|
itemInfo.fState = fState;
|
|
if (!SetMenuItemInfo(_menu, i, TRUE, &itemInfo)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
not_null<WindowHelper::NativeFilter*> WindowHelper::GetNativeFilter() {
|
|
Expects(QCoreApplication::instance() != nullptr);
|
|
|
|
static const auto GlobalFilter = [&] {
|
|
const auto application = QCoreApplication::instance();
|
|
const auto filter = Ui::CreateChild<NativeFilter>(application);
|
|
application->installNativeEventFilter(filter);
|
|
return filter;
|
|
}();
|
|
return GlobalFilter;
|
|
}
|
|
|
|
HWND GetWindowHandle(not_null<RpWidget*> widget) {
|
|
widget->window()->createWinId();
|
|
|
|
const auto window = widget->window()->windowHandle();
|
|
const auto native = QGuiApplication::platformNativeInterface();
|
|
Assert(window != nullptr);
|
|
Assert(native != nullptr);
|
|
|
|
return static_cast<HWND>(native->nativeResourceForWindow(
|
|
QByteArrayLiteral("handle"),
|
|
window));
|
|
}
|
|
|
|
std::unique_ptr<BasicWindowHelper> CreateWindowHelper(
|
|
not_null<RpWidget*> window) {
|
|
return std::make_unique<WindowHelper>(window);
|
|
}
|
|
|
|
} // namespace Platform
|
|
} // namespace Ui
|