diff --git a/CMakeLists.txt b/CMakeLists.txt index 55310c8..dc98be1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -253,6 +253,7 @@ elseif(LINUX) target_link_libraries(lib_ui PUBLIC desktop-app::lib_waylandshells + desktop-app::external_kwayland ) endif() diff --git a/ui/platform/linux/ui_linux_wayland_integration.cpp b/ui/platform/linux/ui_linux_wayland_integration.cpp index 4d78307..01f1e80 100644 --- a/ui/platform/linux/ui_linux_wayland_integration.cpp +++ b/ui/platform/linux/ui_linux_wayland_integration.cpp @@ -24,23 +24,71 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +#include +#include + Q_DECLARE_METATYPE(QMargins); using QtWaylandClient::QWaylandIntegration; using QtWaylandClient::QWaylandWindow; +using namespace KWayland::Client; namespace Ui { namespace Platform { -WaylandIntegration::WaylandIntegration() { +struct WaylandIntegration::Private { + std::unique_ptr connection; + Registry registry; + QEventLoop interfacesLoop; + bool interfacesAnnounced = false; +}; + +WaylandIntegration::WaylandIntegration() +: _private(std::make_unique()) { + _private->connection = std::unique_ptr{ + ConnectionThread::fromApplication(), + }; + + _private->registry.create(_private->connection.get()); + _private->registry.setup(); + + QObject::connect( + _private->connection.get(), + &ConnectionThread::connectionDied, + &_private->registry, + &Registry::destroy); + + QObject::connect( + &_private->registry, + &Registry::interfacesAnnounced, + [=] { + _private->interfacesAnnounced = true; + if (_private->interfacesLoop.isRunning()) { + _private->interfacesLoop.quit(); + } + }); } +WaylandIntegration::~WaylandIntegration() = default; + WaylandIntegration *WaylandIntegration::Instance() { if (!::Platform::IsWayland()) return nullptr; static WaylandIntegration instance; return &instance; } +void WaylandIntegration::waitForInterfaceAnnounce() { + Expects(!_private->interfacesLoop.isRunning()); + if (!_private->interfacesAnnounced) { + _private->interfacesLoop.exec(); + } +} + +bool WaylandIntegration::xdgDecorationSupported() { + return _private->registry.hasInterface( + Registry::Interface::XdgDecorationUnstableV1); +} + bool WaylandIntegration::windowExtentsSupported() { // initialize shell integration before querying if (const auto integration = static_cast( diff --git a/ui/platform/linux/ui_linux_wayland_integration.h b/ui/platform/linux/ui_linux_wayland_integration.h index ed877f2..c975539 100644 --- a/ui/platform/linux/ui_linux_wayland_integration.h +++ b/ui/platform/linux/ui_linux_wayland_integration.h @@ -14,15 +14,21 @@ namespace Platform { class WaylandIntegration { public: - static WaylandIntegration *Instance(); + [[nodiscard]] static WaylandIntegration *Instance(); - bool windowExtentsSupported(); + void waitForInterfaceAnnounce(); + [[nodiscard]] bool xdgDecorationSupported(); + [[nodiscard]] bool windowExtentsSupported(); void setWindowExtents(QWindow *window, const QMargins &extents); void unsetWindowExtents(QWindow *window); bool showWindowMenu(QWindow *window); private: WaylandIntegration(); + ~WaylandIntegration(); + + struct Private; + const std::unique_ptr _private; }; } // namespace Platform diff --git a/ui/platform/linux/ui_linux_wayland_integration_dummy.cpp b/ui/platform/linux/ui_linux_wayland_integration_dummy.cpp index 972b09c..13fafdb 100644 --- a/ui/platform/linux/ui_linux_wayland_integration_dummy.cpp +++ b/ui/platform/linux/ui_linux_wayland_integration_dummy.cpp @@ -12,6 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { namespace Platform { +struct WaylandIntegration::Private { +}; + WaylandIntegration::WaylandIntegration() { } @@ -21,6 +24,13 @@ WaylandIntegration *WaylandIntegration::Instance() { return &instance; } +void WaylandIntegration::waitForInterfaceAnnounce() { +} + +bool WaylandIntegration::xdgDecorationSupported() { + return false; +} + bool WaylandIntegration::windowExtentsSupported() { return false; } diff --git a/ui/platform/linux/ui_window_linux.cpp b/ui/platform/linux/ui_window_linux.cpp index c98869a..a353c32 100644 --- a/ui/platform/linux/ui_window_linux.cpp +++ b/ui/platform/linux/ui_window_linux.cpp @@ -6,6 +6,8 @@ // #include "ui/platform/linux/ui_window_linux.h" +#include "ui/platform/linux/ui_linux_wayland_integration.h" + namespace Ui { namespace Platform { @@ -14,5 +16,11 @@ std::unique_ptr CreateSpecialWindowHelper( return nullptr; } +bool NativeWindowFrameSupported() { + const auto waylandIntegration = WaylandIntegration::Instance(); + return !waylandIntegration + || waylandIntegration->xdgDecorationSupported(); +} + } // namespace Platform } // namespace Ui diff --git a/ui/platform/mac/ui_window_mac.mm b/ui/platform/mac/ui_window_mac.mm index 9ce90e5..c8d8372 100644 --- a/ui/platform/mac/ui_window_mac.mm +++ b/ui/platform/mac/ui_window_mac.mm @@ -412,5 +412,9 @@ std::unique_ptr CreateSpecialWindowHelper( return std::make_unique(window); } +bool NativeWindowFrameSupported() { + return false; +} + } // namespace Platform } // namespace Ui diff --git a/ui/platform/ui_platform_window.cpp b/ui/platform/ui_platform_window.cpp index 674394a..7583efb 100644 --- a/ui/platform/ui_platform_window.cpp +++ b/ui/platform/ui_platform_window.cpp @@ -44,6 +44,9 @@ void BasicWindowHelper::setTitle(const QString &title) { void BasicWindowHelper::setTitleStyle(const style::WindowTitle &st) { } +void BasicWindowHelper::setNativeFrame(bool enabled) { +} + void BasicWindowHelper::setMinimumSize(QSize size) { _window->setMinimumSize(size); } @@ -161,17 +164,21 @@ void DefaultWindowHelper::init() { rpl::combine( window()->sizeValue(), - _title->heightValue() - ) | rpl::start_with_next([=](QSize size, int titleHeight) { + _title->heightValue(), + _title->shownValue() + ) | rpl::start_with_next([=]( + QSize size, + int titleHeight, + bool titleShown) { const auto area = resizeArea(); const auto sizeWithoutMargins = size - .shrunkBy({ 0, titleHeight, 0, 0 }) + .shrunkBy({ 0, titleShown ? titleHeight : 0, 0, 0 }) .shrunkBy(area); const auto topLeft = QPoint( area.left(), - area.top() + titleHeight); + area.top() + (titleShown ? titleHeight : 0)); _body->setGeometry(QRect(topLeft, sizeWithoutMargins)); }, _body->lifetime()); @@ -233,7 +240,9 @@ bool DefaultWindowHelper::hasShadow() const { } QMargins DefaultWindowHelper::resizeArea() const { - if (window()->isMaximized() || window()->isFullScreen()) { + if (window()->isMaximized() + || window()->isFullScreen() + || _nativeFrame) { return QMargins(); } @@ -310,16 +319,23 @@ void DefaultWindowHelper::setTitleStyle(const style::WindowTitle &st) { _title->st()->height); } +void DefaultWindowHelper::setNativeFrame(bool enabled) { + _nativeFrame = enabled; + window()->windowHandle()->setFlag(Qt::FramelessWindowHint, !enabled); + _title->setVisible(!enabled); + updateWindowExtents(); +} + void DefaultWindowHelper::setMinimumSize(QSize size) { const auto sizeWithMargins = size - .grownBy({ 0, _title->height(), 0, 0 }) + .grownBy({ 0, _title->isVisible() ? _title->height() : 0, 0, 0 }) .grownBy(resizeArea()); window()->setMinimumSize(sizeWithMargins); } void DefaultWindowHelper::setFixedSize(QSize size) { const auto sizeWithMargins = size - .grownBy({ 0, _title->height(), 0, 0 }) + .grownBy({ 0, _title->isVisible() ? _title->height() : 0, 0, 0 }) .grownBy(resizeArea()); window()->setFixedSize(sizeWithMargins); _title->setResizeEnabled(false); @@ -327,7 +343,7 @@ void DefaultWindowHelper::setFixedSize(QSize size) { void DefaultWindowHelper::setGeometry(QRect rect) { window()->setGeometry(rect - .marginsAdded({ 0, _title->height(), 0, 0 }) + .marginsAdded({ 0, _title->isVisible() ? _title->height() : 0, 0, 0 }) .marginsAdded(resizeArea())); } @@ -376,7 +392,7 @@ void DefaultWindowHelper::paintBorders(QPainter &p) { } void DefaultWindowHelper::updateWindowExtents() { - if (hasShadow()) { + if (hasShadow() && !_nativeFrame) { Platform::SetWindowExtents( window()->windowHandle(), resizeArea()); diff --git a/ui/platform/ui_platform_window.h b/ui/platform/ui_platform_window.h index 58a1c8a..60e102d 100644 --- a/ui/platform/ui_platform_window.h +++ b/ui/platform/ui_platform_window.h @@ -30,6 +30,7 @@ public: [[nodiscard]] virtual not_null body(); virtual void setTitle(const QString &title); virtual void setTitleStyle(const style::WindowTitle &st); + virtual void setNativeFrame(bool enabled); virtual void setMinimumSize(QSize size); virtual void setFixedSize(QSize size); virtual void setStaysOnTop(bool enabled); @@ -67,6 +68,7 @@ public: not_null body() override; void setTitle(const QString &title) override; void setTitleStyle(const style::WindowTitle &st) override; + void setNativeFrame(bool enabled) override; void setMinimumSize(QSize size) override; void setFixedSize(QSize size) override; void setGeometry(QRect rect) override; @@ -86,6 +88,7 @@ private: const not_null _title; const not_null _body; bool _extentsSet = false; + bool _nativeFrame = false; }; @@ -100,5 +103,7 @@ private: return std::make_unique(window); } +bool NativeWindowFrameSupported(); + } // namespace Platform } // namespace Ui diff --git a/ui/platform/win/ui_window_win.cpp b/ui/platform/win/ui_window_win.cpp index 2f853c3..26bad2b 100644 --- a/ui/platform/win/ui_window_win.cpp +++ b/ui/platform/win/ui_window_win.cpp @@ -148,7 +148,7 @@ WindowHelper::WindowHelper(not_null window) , _handle(GetWindowHandle(window)) , _title(Ui::CreateChild(window.get())) , _body(Ui::CreateChild(window.get())) -, _shadow(window, st::windowShadowFg->c) { +, _shadow(std::in_place, window, st::windowShadowFg->c) { Expects(_handle != nullptr); GetNativeFilter()->registerWindow(_handle, this); @@ -172,18 +172,36 @@ void WindowHelper::setTitleStyle(const style::WindowTitle &st) { _title->setStyle(st); } +void WindowHelper::setNativeFrame(bool enabled) { + _nativeFrame = enabled; + _title->setVisible(!enabled); + if (enabled) { + _shadow.reset(); + } else { + _shadow.emplace(window(), st::windowShadowFg->c); + _shadow->setResizeEnabled(!fixedSize()); + } + updateMargins(); + fixMaximizedWindow(); +} + void WindowHelper::setMinimumSize(QSize size) { - window()->setMinimumSize(size.width(), _title->height() + size.height()); + const auto titleHeight = _title->isVisible() ? _title->height() : 0; + window()->setMinimumSize(size.width(), titleHeight + size.height()); } void WindowHelper::setFixedSize(QSize size) { - window()->setFixedSize(size.width(), _title->height() + size.height()); + const auto titleHeight = _title->isVisible() ? _title->height() : 0; + window()->setFixedSize(size.width(), titleHeight + size.height()); _title->setResizeEnabled(false); - _shadow.setResizeEnabled(false); + if (_shadow) { + _shadow->setResizeEnabled(false); + } } void WindowHelper::setGeometry(QRect rect) { - window()->setGeometry(rect.marginsAdded({ 0, _title->height(), 0, 0 })); + const auto titleHeight = _title->isVisible() ? _title->height() : 0; + window()->setGeometry(rect.marginsAdded({ 0, titleHeight, 0, 0 })); } void WindowHelper::showFullScreen() { @@ -205,19 +223,25 @@ void WindowHelper::showNormal() { void WindowHelper::init() { style::PaletteChanged( ) | rpl::start_with_next([=] { - _shadow.setColor(st::windowShadowFg->c); + if (_shadow) { + _shadow->setColor(st::windowShadowFg->c); + } Ui::ForceFullRepaint(window()); }, window()->lifetime()); rpl::combine( window()->sizeValue(), - _title->heightValue() - ) | rpl::start_with_next([=](QSize size, int titleHeight) { + _title->heightValue(), + _title->shownValue() + ) | rpl::start_with_next([=]( + QSize size, + int titleHeight, + bool titleShown) { _body->setGeometry( 0, - titleHeight, + titleShown ? titleHeight : 0, size.width(), - size.height() - titleHeight); + size.height() - (titleShown ? titleHeight : 0)); }, _body->lifetime()); updateMargins(); @@ -256,22 +280,27 @@ bool WindowHelper::handleNativeEvent( 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); + if (_shadow) { + 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()) { + if (::Platform::IsWindows8OrGreater() || _nativeFrame) { return false; } if (result) *result = 0; } return true; case WM_NCCALCSIZE: { + if (_nativeFrame) { + return false; + } WINDOWPLACEMENT wp; wp.length = sizeof(WINDOWPLACEMENT); if (GetWindowPlacement(_handle, &wp) @@ -315,6 +344,9 @@ bool WindowHelper::handleNativeEvent( } return true; case WM_NCACTIVATE: { + if (_nativeFrame) { + return false; + } if (IsCompositionEnabled()) { const auto res = DefWindowProc(_handle, msg, wParam, -1); if (result) *result = res; @@ -326,16 +358,18 @@ bool WindowHelper::handleNativeEvent( 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); + if (_shadow) { + 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; @@ -354,30 +388,36 @@ bool WindowHelper::handleNativeEvent( 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); + if (_shadow) { + 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 = GetWindowLongPtr(_handle, GWL_STYLE); - const auto changes = WindowShadow::Change::Resized - | ((wParam && !(style & (WS_MAXIMIZE | WS_MINIMIZE))) - ? WindowShadow::Change::Shown - : WindowShadow::Change::Hidden); - _shadow.update(changes); + if (_shadow) { + const auto style = GetWindowLongPtr(_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); + if (_shadow) { + _shadow->update(WindowShadow::Change::Moved); + } } return false; case WM_NCHITTEST: { - if (!result) { + if (!result || _nativeFrame) { return false; } @@ -389,7 +429,7 @@ bool WindowHelper::handleNativeEvent( p.y - r.top + _marginsDelta.top()); if (!window()->rect().contains(mapped)) { *result = HTTRANSPARENT; - } else if (!_title->geometry().contains(mapped)) { + } else if (!_title->isVisible() || !_title->geometry().contains(mapped)) { *result = HTCLIENT; } else switch (_title->hitTest(_title->pos() + mapped)) { case HitTestResult::Client: @@ -409,6 +449,9 @@ bool WindowHelper::handleNativeEvent( } return true; case WM_NCRBUTTONUP: { + if (_nativeFrame) { + return false; + } SendMessage(_handle, WM_SYSCOMMAND, SC_MOUSEMENU, lParam); } return true; @@ -519,8 +562,11 @@ void WindowHelper::updateMargins() { _marginsDelta = QMargins(); } - if (_isFullScreen) { + if (_isFullScreen || _nativeFrame) { margins = QMargins(); + if (_nativeFrame) { + _marginsDelta = QMargins(); + } } if (const auto native = QGuiApplication::platformNativeInterface()) { native->setWindowProperty( @@ -573,6 +619,25 @@ void WindowHelper::updateSystemMenu(Qt::WindowState state) { } } +void WindowHelper::fixMaximizedWindow() { + auto r = RECT(); + GetClientRect(_handle, &r); + const auto style = GetWindowLongPtr(_handle, GWL_STYLE); + const auto styleEx = GetWindowLongPtr(_handle, GWL_EXSTYLE); + AdjustWindowRectEx(&r, style, false, styleEx); + if (style & WS_MAXIMIZE) { + auto w = RECT(); + GetWindowRect(_handle, &w); + if (const auto hMonitor = MonitorFromRect(&w, MONITOR_DEFAULTTONEAREST)) { + MONITORINFO mi; + mi.cbSize = sizeof(mi); + GetMonitorInfo(hMonitor, &mi); + const auto m = mi.rcWork; + SetWindowPos(_handle, 0, 0, 0, m.right - m.left - _marginsDelta.left() - _marginsDelta.right(), m.bottom - m.top - _marginsDelta.top() - _marginsDelta.bottom(), SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION); + } + } +} + not_null WindowHelper::GetNativeFilter() { Expects(QCoreApplication::instance() != nullptr); @@ -619,5 +684,9 @@ std::unique_ptr CreateSpecialWindowHelper( return std::make_unique(window); } +bool NativeWindowFrameSupported() { + return true; +} + } // namespace Platform } // namespace Ui diff --git a/ui/platform/win/ui_window_win.h b/ui/platform/win/ui_window_win.h index 9abfe60..cee1b22 100644 --- a/ui/platform/win/ui_window_win.h +++ b/ui/platform/win/ui_window_win.h @@ -24,6 +24,7 @@ public: not_null body() override; void setTitle(const QString &title) override; void setTitleStyle(const style::WindowTitle &st) override; + void setNativeFrame(bool enabled) override; void setMinimumSize(QSize size) override; void setFixedSize(QSize size) override; void setGeometry(QRect rect) override; @@ -38,6 +39,7 @@ private: void updateMargins(); void updateSystemMenu(); void updateSystemMenu(Qt::WindowState state); + void fixMaximizedWindow(); [[nodiscard]] bool handleNativeEvent( UINT msg, WPARAM wParam, @@ -50,11 +52,12 @@ private: const HWND _handle = nullptr; const not_null _title; const not_null _body; - WindowShadow _shadow; + std::optional _shadow; bool _updatingMargins = false; QMargins _marginsDelta; HMENU _menu = nullptr; bool _isFullScreen = false; + bool _nativeFrame = false; }; diff --git a/ui/widgets/window.cpp b/ui/widgets/window.cpp index 3423701..90888b3 100644 --- a/ui/widgets/window.cpp +++ b/ui/widgets/window.cpp @@ -36,6 +36,10 @@ void Window::setTitleStyle(const style::WindowTitle &st) { _helper->setTitleStyle(st); } +void Window::setNativeFrame(bool enabled) { + _helper->setNativeFrame(enabled); +} + void Window::setMinimumSize(QSize size) { _helper->setMinimumSize(size); } diff --git a/ui/widgets/window.h b/ui/widgets/window.h index 336ff4f..2962c07 100644 --- a/ui/widgets/window.h +++ b/ui/widgets/window.h @@ -39,6 +39,7 @@ public: void setTitle(const QString &title); void setTitleStyle(const style::WindowTitle &st); + void setNativeFrame(bool enabled); void setMinimumSize(QSize size); void setFixedSize(QSize size); void setStaysOnTop(bool enabled);