From 533d174abceb62faa74850040792a9ef601240b2 Mon Sep 17 00:00:00 2001 From: helywin Date: Thu, 12 Sep 2019 10:44:12 +0800 Subject: [PATCH] Finished implementing maximize for linux. Added FloatingContainerForc*TitleBar to switch between native and custom titlebar. Co-authored-by: SleepProgger --- src/DockFocusController.cpp | 3 + src/DockManager.cpp | 53 +++++++- src/DockManager.h | 12 ++ src/FloatingDockContainer.cpp | 169 ++++++++++++++++++----- src/FloatingDockContainer.h | 41 +++++- src/ads_globals.cpp | 193 +++++++++++++++++++++++++++ src/ads_globals.h | 32 +++++ src/linux/FloatingWidgetTitleBar.cpp | 55 +++++++- src/linux/FloatingWidgetTitleBar.h | 11 ++ src/src.pro | 1 + 10 files changed, 533 insertions(+), 37 deletions(-) diff --git a/src/DockFocusController.cpp b/src/DockFocusController.cpp index c4a9332..702a00a 100644 --- a/src/DockFocusController.cpp +++ b/src/DockFocusController.cpp @@ -83,6 +83,9 @@ static void updateDockAreaFocusStyle(CDockAreaWidget* DockArea, bool Focused) #ifdef Q_OS_LINUX static void updateFloatingWidgetFocusStyle(CFloatingDockContainer* FloatingWidget, bool Focused) { + if(FloatingWidget->hasNativeTitleBar()){ + return; + } auto TitleBar = qobject_cast(FloatingWidget->titleBarWidget()); if (!TitleBar) { diff --git a/src/DockManager.cpp b/src/DockManager.cpp index e21dbdf..3fcac69 100644 --- a/src/DockManager.cpp +++ b/src/DockManager.cpp @@ -368,8 +368,6 @@ void DockManagerPrivate::restoreDockAreasIndices() } } - - //============================================================================ void DockManagerPrivate::emitTopLevelEvents() { @@ -476,6 +474,10 @@ CDockManager::CDockManager(QWidget *parent) : { d->FocusController = new CDockFocusController(this); } + +#ifdef Q_OS_LINUX + window()->installEventFilter(this); +#endif } //============================================================================ @@ -489,6 +491,53 @@ CDockManager::~CDockManager() delete d; } +//============================================================================ +#ifdef Q_OS_LINUX +bool CDockManager::eventFilter(QObject *obj, QEvent *e){ + // Emulate Qt:Tool behaviour. + // Required because on some WMs Tool windows can't be maximized. + + // Window always on top of the MainWindow. + if(e->type() == QEvent::WindowActivate){ + for(auto _window : floatingWidgets()){ + if(!_window->isVisible() || window()->isMinimized()){ + continue; + } + // setWindowFlags(Qt::WindowStaysOnTopHint) will hide the window and thus requires a show call. + // This then leads to flickering and a nasty endless loop (also buggy behaviour on Ubuntu). + // So we just do it ourself. + internal::xcb_update_prop(true, _window->window()->winId(), "_NET_WM_STATE", "_NET_WM_STATE_ABOVE", "_NET_WM_STATE_STAYS_ON_TOP"); + } + } + else if(e->type() == QEvent::WindowDeactivate){ + for(auto _window : floatingWidgets()){ + if(!_window->isVisible() || window()->isMinimized()){ + continue; + } + internal::xcb_update_prop(false, _window->window()->winId(), "_NET_WM_STATE", "_NET_WM_STATE_ABOVE", "_NET_WM_STATE_STAYS_ON_TOP"); + _window->raise(); + } + } + + // Sync minimize with MainWindow + if(e->type() == QEvent::WindowStateChange){ + for(auto _window : floatingWidgets()){ + if(! _window->isVisible()){ + continue; + } + if(window()->isMinimized()){ + _window->showMinimized(); + } else { + _window->setWindowState(_window->windowState() & (~Qt::WindowMinimized)); + } + } + if(!window()->isMinimized()){ + QApplication::setActiveWindow(window()); + } + } + return Super::eventFilter(obj, e); +} +#endif //============================================================================ void CDockManager::registerFloatingWidget(CFloatingDockContainer* FloatingWidget) diff --git a/src/DockManager.h b/src/DockManager.h index f56fc13..addbc25 100644 --- a/src/DockManager.h +++ b/src/DockManager.h @@ -178,9 +178,17 @@ public: FloatingContainerHasWidgetIcon = 0x80000, //!< If set, the Floating Widget icon reflects the icon of the current dock widget otherwise it displays application icon HideSingleCentralWidgetTitleBar = 0x100000, //!< If there is only one single visible dock widget in the main dock container (the dock manager) and if this flag is set, then the titlebar of this dock widget will be hidden //!< this only makes sense for non draggable and non floatable widgets and enables the creation of some kind of "central" widget + FocusHighlighting = 0x200000, //!< enables styling of focused dock widget tabs or floating widget titlebar EqualSplitOnInsertion = 0x400000, ///!< if enabled, the space is equally distributed to all widgets in a splitter + FloatingContainerForceNativeTitleBar = 0x800000, //!< Linux only ! Forces all FloatingContainer to use the native title bar. This might break docking for FloatinContainer on some Window Managers (like Kwin/KDE). + //!< If neither this nor FloatingContainerForceCustomTitleBar is set (the default) native titlebars are used except on known bad systems. + //! Users can overwrite this by setting the environment variable ADS_UseNativeTitle to "1" or "0". + FloatingContainerForceCustomTitleBar = 0x1000000,//!< Linux only ! Forces all FloatingContainer to use a custom title bar. + //!< If neither this nor FloatingContainerForceNativeTitleBar is set (the default) native titlebars are used except on known bad systems. + //! Users can overwrite this by setting the environment variable ADS_UseNativeTitle to "1" or "0". + DefaultDockAreaButtons = DockAreaHasCloseButton | DockAreaHasUndockButton | DockAreaHasTabsMenuButton,///< default configuration of dock area title bar buttons @@ -464,6 +472,10 @@ public: widget->setFocus(Qt::OtherFocusReason); } +#ifdef Q_OS_LINUX + bool eventFilter(QObject *obj, QEvent *e) override; +#endif + public slots: /** * Opens the perspective with the given name. diff --git a/src/FloatingDockContainer.cpp b/src/FloatingDockContainer.cpp index 13c175a..cfe66fd 100644 --- a/src/FloatingDockContainer.cpp +++ b/src/FloatingDockContainer.cpp @@ -376,6 +376,7 @@ struct FloatingDockContainerPrivate #ifdef Q_OS_LINUX QWidget* MouseEventHandler = nullptr; CFloatingWidgetTitleBar* TitleBar = nullptr; + bool IsResizing = false; #endif /** @@ -410,10 +411,11 @@ struct FloatingDockContainerPrivate void setWindowTitle(const QString &Text) { #ifdef Q_OS_LINUX - TitleBar->setTitle(Text); -#else - _this->setWindowTitle(Text); + if(TitleBar){ + TitleBar->setTitle(Text); + } #endif + _this->setWindowTitle(Text); } /** @@ -604,13 +606,36 @@ CFloatingDockContainer::CFloatingDockContainer(CDockManager *DockManager) : SLOT(onDockAreasAddedOrRemoved())); #ifdef Q_OS_LINUX - d->TitleBar = new CFloatingWidgetTitleBar(this); - setWindowFlags(windowFlags() | Qt::Tool); - QDockWidget::setWidget(d->DockContainer); - QDockWidget::setFloating(true); - QDockWidget::setFeatures(QDockWidget::AllDockWidgetFeatures); - setTitleBarWidget(d->TitleBar); - connect(d->TitleBar, SIGNAL(closeRequested()), SLOT(close())); + QDockWidget::setWidget(d->DockContainer); + QDockWidget::setFloating(true); + QDockWidget::setFeatures(QDockWidget::AllDockWidgetFeatures); + + // KDE doesn't seem to fire MoveEvents while moving windows, so for now no native titlebar for everything using KWin. + QString window_manager = internal::windowManager().toUpper().split(" ")[0]; + bool native_window = window_manager != "KWIN"; + // FloatingContainerForce*TitleBar is overwritten by the "ADS_UseNativeTitle" environment variable if set. + auto env = qgetenv("ADS_UseNativeTitle").toUpper(); + if (env == "1"){ + native_window = true; + } else if (env == "0"){ + native_window = false; + } else if ( DockManager->testConfigFlag( CDockManager::FloatingContainerForceNativeTitleBar )){ + native_window = true; + } else if ( DockManager->testConfigFlag( CDockManager::FloatingContainerForceCustomTitleBar )){ + native_window = false; + } + if(native_window){ + setTitleBarWidget(new QWidget()); + setWindowFlags(Qt::Window | Qt::WindowMaximizeButtonHint | Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint); + } else { + d->TitleBar = new CFloatingWidgetTitleBar(this); + setTitleBarWidget(d->TitleBar); + setWindowFlags(Qt::Window | Qt::WindowMinMaxButtonsHint | Qt::FramelessWindowHint); + d->TitleBar->enableCloseButton(isClosable()); + connect(d->TitleBar, SIGNAL(closeRequested()), SLOT(close())); + connect(d->TitleBar, &CFloatingWidgetTitleBar::maximizeRequested, + this, &CFloatingDockContainer::onMaximizeRequest); + } #else setWindowFlags( Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint); @@ -629,9 +654,7 @@ CFloatingDockContainer::CFloatingDockContainer(CDockAreaWidget *DockArea) : CFloatingDockContainer(DockArea->dockManager()) { d->DockContainer->addDockArea(DockArea); -#ifdef Q_OS_LINUX - d->TitleBar->enableCloseButton(isClosable()); -#endif + auto TopLevelDockWidget = topLevelDockWidget(); if (TopLevelDockWidget) { @@ -646,9 +669,6 @@ CFloatingDockContainer::CFloatingDockContainer(CDockWidget *DockWidget) : CFloatingDockContainer(DockWidget->dockManager()) { d->DockContainer->addDockWidget(CenterDockWidgetArea, DockWidget); -#ifdef Q_OS_LINUX - d->TitleBar->enableCloseButton(isClosable()); -#endif auto TopLevelDockWidget = topLevelDockWidget(); if (TopLevelDockWidget) { @@ -678,12 +698,18 @@ CDockContainerWidget* CFloatingDockContainer::dockContainer() const //============================================================================ void CFloatingDockContainer::changeEvent(QEvent *event) { - QWidget::changeEvent(event); + Super::changeEvent(event); if ((event->type() == QEvent::ActivationChange) && isActiveWindow()) { ADS_PRINT("FloatingWidget::changeEvent QEvent::ActivationChange "); d->zOrderIndex = ++zOrderCounter; - return; + +#ifdef Q_OS_LINUX + if(d->DraggingState == DraggingFloatingWidget){ + d->titleMouseReleaseEvent(); + d->DraggingState = DraggingInactive; + } +#endif } } @@ -825,13 +851,19 @@ void CFloatingDockContainer::startFloating(const QPoint &DragStartMousePos, #ifndef Q_OS_LINUX Q_UNUSED(MouseEventHandler) #endif +#ifdef Q_OS_LINUX + if (!isMaximized()) { + resize(Size); + d->DragStartMousePosition = DragStartMousePos; + } +#else resize(Size); - d->setState(DragState); d->DragStartMousePosition = DragStartMousePos; +#endif + d->setState(DragState); #ifdef Q_OS_LINUX if (DraggingFloatingWidget == DragState) { - setAttribute(Qt::WA_X11NetWmWindowTypeDock, true); d->MouseEventHandler = MouseEventHandler; if (d->MouseEventHandler) { @@ -839,7 +871,13 @@ void CFloatingDockContainer::startFloating(const QPoint &DragStartMousePos, } } #endif +#ifdef Q_OS_LINUX + if (!isMaximized()) { + moveFloating(); + } +#else moveFloating(); +#endif show(); } @@ -850,7 +888,6 @@ void CFloatingDockContainer::moveFloating() const QPoint moveToPos = QCursor::pos() - d->DragStartMousePosition - QPoint(BorderSize, 0); move(moveToPos); - switch (d->DraggingState) { case DraggingMousePressed: @@ -949,11 +986,16 @@ bool CFloatingDockContainer::restoreState(CDockingStateReader &Stream, { return false; } - onDockAreasAddedOrRemoved(); +#ifdef Q_OS_LINUX + if(d->TitleBar){ + d->TitleBar->setMaximizedIcon(windowState() == Qt::WindowMaximized); + } +#endif return true; } + //============================================================================ bool CFloatingDockContainer::hasTopLevelDockWidget() const { @@ -977,19 +1019,17 @@ void CFloatingDockContainer::finishDragging() { ADS_PRINT("CFloatingDockContainer::finishDragging"); #ifdef Q_OS_LINUX - setAttribute(Qt::WA_X11NetWmWindowTypeDock, false); - setWindowOpacity(1); - activateWindow(); - if (d->MouseEventHandler) - { - d->MouseEventHandler->releaseMouse(); - d->MouseEventHandler = nullptr; - } + setWindowOpacity(1); + activateWindow(); + if (d->MouseEventHandler) + { + d->MouseEventHandler->releaseMouse(); + d->MouseEventHandler = nullptr; + } #endif - d->titleMouseReleaseEvent(); + d->titleMouseReleaseEvent(); } - #ifdef Q_OS_MACOS //============================================================================ bool CFloatingDockContainer::event(QEvent *e) @@ -1092,6 +1132,71 @@ void CFloatingDockContainer::moveEvent(QMoveEvent *event) } #endif + +#ifdef Q_OS_LINUX +void CFloatingDockContainer::onMaximizeRequest() +{ + if(windowState() == Qt::WindowMaximized){ + showNormal(); + }else{ + showMaximized(); + } +} + +void CFloatingDockContainer::showNormal(bool fixGeometry) +{ + if (windowState() == Qt::WindowMaximized) + { + QRect oldNormal = normalGeometry(); + Super::showNormal(); + if(fixGeometry) + { + setGeometry(oldNormal); + } + } + if(d->TitleBar){ + d->TitleBar->setMaximizedIcon(false); + } +} + +void CFloatingDockContainer::showMaximized() +{ + Super::showMaximized(); + if(d->TitleBar){ + d->TitleBar->setMaximizedIcon(true); + } +} + +bool CFloatingDockContainer::isMaximized() const +{ + return windowState() == Qt::WindowMaximized; +} + +void CFloatingDockContainer::show(){ + // Prevent this window from showing in the taskbar and pager (alt+tab) + internal::xcb_add_prop(true, winId(), "_NET_WM_STATE", "_NET_WM_STATE_SKIP_TASKBAR"); + internal::xcb_add_prop(true, winId(), "_NET_WM_STATE", "_NET_WM_STATE_SKIP_PAGER"); + Super::show(); +} +void CFloatingDockContainer::resizeEvent(QResizeEvent *event){ + d->IsResizing = true; + Super::resizeEvent(event); +} + +void CFloatingDockContainer::moveEvent(QMoveEvent *event){ + Super::moveEvent(event); + if(!d->IsResizing && event->spontaneous()){ + d->DraggingState = DraggingFloatingWidget; + d->updateDropOverlays(QCursor::pos()); + } + d->IsResizing = false; +} + +bool CFloatingDockContainer::hasNativeTitleBar(){ + return d->TitleBar == nullptr; +} +#endif + } // namespace ads //--------------------------------------------------------------------------- diff --git a/src/FloatingDockContainer.h b/src/FloatingDockContainer.h index 0099e2a..6062315 100644 --- a/src/FloatingDockContainer.h +++ b/src/FloatingDockContainer.h @@ -185,6 +185,11 @@ protected: // reimplements QWidget virtual void moveEvent(QMoveEvent *event) override; #endif +#ifdef Q_OS_LINUX + virtual void moveEvent(QMoveEvent *event) override; + virtual void resizeEvent(QResizeEvent *event) override; +#endif + #ifdef Q_OS_WIN /** * Native event filter for handling WM_MOVING messages on Windows @@ -194,7 +199,7 @@ protected: // reimplements QWidget public: - using Super = QWidget; + using Super = tFloatingWidgetBase; /** * Create empty floating widget - required for restore state @@ -248,6 +253,40 @@ public: * function of the internal container widget. */ QList dockWidgets() const; + +#ifdef Q_OS_LINUX + /** + * This is a function that responds to FloatingWidgetTitleBar::maximizeRequest() + * Maximize or normalize the container size. + */ + void onMaximizeRequest(); + + /** + * Normalize (Unmaximize) the window. + * fixGeometry parameter fixes a "bug" in QT where immediately after calling showNormal + * geometry is not set properly. + * Set this true when moving the window immediately after normalizing. + */ + void showNormal(bool fixGeometry=false); + + /** + * Maximizes the window. + */ + void showMaximized(); + + /** + * Returns if the window is currently maximized or not. + */ + bool isMaximized() const; + + /** + * Patched show to prevent the window from appearing in the taskbar. + */ + void show(); + + bool hasNativeTitleBar(); +#endif + }; // class FloatingDockContainer } // namespace ads diff --git a/src/ads_globals.cpp b/src/ads_globals.cpp index b185e5e..ee72d4e 100644 --- a/src/ads_globals.cpp +++ b/src/ads_globals.cpp @@ -38,12 +38,205 @@ #include "IconProvider.h" #include "ads_globals.h" +#ifdef Q_OS_LINUX +#include +#include +#include +#endif + + +#include namespace ads { namespace internal { +#ifdef Q_OS_LINUX + static QString _window_manager; + static QHash _xcb_atom_cache; + +xcb_atom_t xcb_get_atom(const char *name){ + if (!QX11Info::isPlatformX11()){ + return XCB_ATOM_NONE; + } + auto key = QString(name); + if(_xcb_atom_cache.contains(key)){ + return _xcb_atom_cache[key]; + } + xcb_connection_t *connection = QX11Info::connection(); + xcb_intern_atom_cookie_t request = xcb_intern_atom(connection, 1, strlen(name), name); + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(connection, request, NULL); + if(!reply){ + return XCB_ATOM_NONE; + } + xcb_atom_t atom = reply->atom; + if(atom == XCB_ATOM_NONE){ + ADS_PRINT("Unknown Atom response from XServer: " << name); + } else { + _xcb_atom_cache.insert(key, atom); + } + free(reply); + return atom; +} + +void xcb_update_prop(bool set, WId window, const char *type, const char *prop, const char *prop2) +{ + auto connection = QX11Info::connection(); + xcb_atom_t type_atom = xcb_get_atom(type); + xcb_atom_t prop_atom = xcb_get_atom(prop); + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.sequence = 0; + event.window = window; + event.type = type_atom; + event.data.data32[0] = set ? 1 : 0; + event.data.data32[1] = prop_atom; + event.data.data32[2] = prop2 ? xcb_get_atom(prop2) : 0; + event.data.data32[3] = 0; + event.data.data32[4] = 0; + + xcb_send_event(connection, 0, window, + XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_PROPERTY_CHANGE, + (const char *)&event); + xcb_flush(connection); +} + +xcb_get_property_reply_t* _xcb_get_props(WId window, const char *type, unsigned int atom_type){ + if (!QX11Info::isPlatformX11()){ + return nullptr; + } + xcb_connection_t *connection = QX11Info::connection(); + xcb_atom_t type_atom = xcb_get_atom(type); + if (type_atom == XCB_ATOM_NONE){ + return nullptr; + } + xcb_get_property_cookie_t request = xcb_get_property_unchecked(connection, 0, window, type_atom, atom_type, 0, 1024); + xcb_get_property_reply_t *reply = xcb_get_property_reply(connection, request, nullptr); + if(reply && reply->type != atom_type){ + ADS_PRINT("ATOM TYPE MISMATCH (" << type <<"). Expected: " << atom_type << " but got " << reply->type); + free(reply); + return nullptr; + } + return reply; +} + +template +void xcb_get_prop_list(WId window, const char *type, QVector &ret, unsigned int atom_type){ + xcb_get_property_reply_t *reply = _xcb_get_props(window, type, atom_type); + if (reply && reply->format == 32 && reply->type == atom_type && reply->value_len > 0) { + const xcb_atom_t *data = static_cast(xcb_get_property_value(reply)); + ret.resize(reply->value_len); + memcpy((void *)&ret.first(), (void *)data, reply->value_len * sizeof(T)); + } + free(reply); +} + +QString xcb_get_prop_string(WId window, const char *type){ + QString ret; + // try utf8 first + xcb_atom_t utf_atom = xcb_get_atom("UTF8_STRING"); + if(utf_atom != XCB_ATOM_NONE){ + xcb_get_property_reply_t *reply = _xcb_get_props(window, type, utf_atom); + if (reply && reply->format == 8 && reply->type == utf_atom) { + const char *value = reinterpret_cast(xcb_get_property_value(reply)); + ret = QString::fromUtf8(value, xcb_get_property_value_length(reply)); + free(reply); + return ret; + } + free(reply); + } + // Fall back to XCB_ATOM_STRING + xcb_get_property_reply_t *reply = _xcb_get_props(window, type, XCB_ATOM_STRING); + if (reply && reply->format == 8 && reply->type == XCB_ATOM_STRING) { + const char *value = reinterpret_cast(xcb_get_property_value(reply)); + ret = QString::fromLatin1(value, xcb_get_property_value_length(reply)); + } + free(reply); + return ret; +} + +bool xcb_dump_props(WId window, const char *type){ + QVector atoms; + xcb_get_prop_list(window, type, atoms, XCB_ATOM_ATOM); + qDebug() << "\n\n!!!" << type << " - " << atoms.length(); + xcb_connection_t *connection = QX11Info::connection(); + for(auto atom : atoms){ + auto foo = xcb_get_atom_name(connection, atom); + auto bar = xcb_get_atom_name_reply(connection, foo, nullptr); + qDebug() << "\t" << xcb_get_atom_name_name(bar); + free(bar); + } + return true; +} + +void xcb_add_prop(bool state, WId window, const char *type, const char *prop){ + if (!QX11Info::isPlatformX11()){ + return; + } + xcb_atom_t prop_atom = xcb_get_atom(prop); + xcb_atom_t type_atom = xcb_get_atom(type); + if(prop_atom == XCB_ATOM_NONE || type_atom == XCB_ATOM_NONE){ + return; + } + QVector atoms; + xcb_get_prop_list(window, type, atoms, XCB_ATOM_ATOM); + int index = atoms.indexOf(prop_atom); + if(state && index == -1){ + atoms.push_back(prop_atom); + } else if(!state && index >= 0){ + atoms.remove(index); + } + xcb_connection_t *connection = QX11Info::connection(); + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, window, type_atom, XCB_ATOM_ATOM, 32, atoms.count(), atoms.constData()); + xcb_flush(connection); +} + +QString detectWindowManagerX11(){ + // Tries to detect the windowmanager via X11. + // See: https://specifications.freedesktop.org/wm-spec/1.3/ar01s03.html#idm46018259946000 + if (!QX11Info::isPlatformX11()){ + return "UNKNOWN"; + } + xcb_connection_t *connection = QX11Info::connection(); + xcb_screen_t *first_screen = xcb_setup_roots_iterator (xcb_get_setup (connection)).data; + if(!first_screen){ + ADS_PRINT("No screen found via XCB."); + return "UNKNOWN"; + } + // Get supporting window () + xcb_window_t root = first_screen->root; + xcb_window_t support_win = 0; + QVector sup_windows; + xcb_get_prop_list(root, "_NET_SUPPORTING_WM_CHECK", sup_windows, XCB_ATOM_WINDOW); + if(sup_windows.length() == 0){ + // This doesn't seem to be in use anymore, but wmctrl does the same so lets play safe. + // Both XCB_ATOM_CARDINAL and XCB_ATOM_WINDOW break down to a uint32_t, so reusing sup_windows should be fine. + xcb_get_prop_list(root, "_WIN_SUPPORTING_WM_CHECK", sup_windows, XCB_ATOM_CARDINAL); + } + if(sup_windows.length() == 0){ + ADS_PRINT("Failed to get the supporting window on non EWMH comform WM."); + return "UNKNOWN"; + } + support_win = sup_windows[0]; + QString ret = xcb_get_prop_string(support_win, "_NET_WM_NAME"); + if(ret.length() == 0){ + ADS_PRINT("Empty WM name occured."); + return "UNKNOWN"; + } + return ret; +} + +QString windowManager(){ + if(_window_manager.length() == 0){ + _window_manager = detectWindowManagerX11(); + } + return _window_manager; +} +#endif + + //============================================================================ void replaceSplitterWidget(QSplitter* Splitter, QWidget* From, QWidget* To) { diff --git a/src/ads_globals.h b/src/ads_globals.h index 3523be5..dc278bf 100644 --- a/src/ads_globals.h +++ b/src/ads_globals.h @@ -37,6 +37,10 @@ #include #include +#ifdef Q_OS_LINUX +#include +#endif + QT_FORWARD_DECLARE_CLASS(QAbstractButton) #ifndef ADS_STATIC @@ -122,6 +126,7 @@ enum eBitwiseOperator BitwiseOr }; + namespace internal { static const bool RestoreTesting = true; @@ -129,6 +134,33 @@ static const bool Restore = false; static const char* const ClosedProperty = "close"; static const char* const DirtyProperty = "dirty"; +#ifdef Q_OS_LINUX + // Utils to directly communicate with the X server + /** + * Get atom from cache or request it from the XServer. + */ + xcb_atom_t xcb_get_atom(const char *name); + + /** + * Add a property to a window. Only works on "hidden" windows. + */ + void xcb_add_prop(bool state, WId window, const char *type, const char *prop); + /** + * Updates up to two window properties. Can be set on a visible window. + */ + void xcb_update_prop(bool set, WId window, const char *type, const char *prop, const char *prop2 = nullptr); + /** + * Only for debugging purposes. + */ + bool xcb_dump_props(WId window, const char *type); + /** + * Gets the active window manager from the X11 Server. + * Requires a EWMH conform window manager (Allmost all common used ones are). + * Returns "UNKNOWN" otherwise. + */ + QString windowManager(); +#endif + /** * Replace the from widget in the given splitter with the To widget */ diff --git a/src/linux/FloatingWidgetTitleBar.cpp b/src/linux/FloatingWidgetTitleBar.cpp index e8e077b..63f88ef 100644 --- a/src/linux/FloatingWidgetTitleBar.cpp +++ b/src/linux/FloatingWidgetTitleBar.cpp @@ -46,6 +46,7 @@ namespace ads using tTabLabel = CElidingLabel; using tCloseButton = QToolButton; +using tMaximizeButton = QToolButton; /** * @brief Private data class of public interface CFloatingWidgetTitleBar @@ -56,6 +57,7 @@ struct FloatingWidgetTitleBarPrivate QLabel *IconLabel = nullptr; tTabLabel *TitleLabel; tCloseButton *CloseButton = nullptr; + tMaximizeButton* MaximizeButton = nullptr; CFloatingDockContainer *FloatingWidget = nullptr; eDragState DragState = DraggingInactive; @@ -83,6 +85,10 @@ void FloatingWidgetTitleBarPrivate::createLayout() CloseButton->setObjectName("floatingTitleCloseButton"); CloseButton->setAutoRaise(true); + MaximizeButton = new tMaximizeButton(); + MaximizeButton->setObjectName("floatingTitleMaximizeButton"); + MaximizeButton->setAutoRaise(true); + // The standard icons do does not look good on high DPI screens QIcon CloseIcon; QPixmap normalPixmap = _this->style()->standardPixmap( @@ -97,6 +103,12 @@ void FloatingWidgetTitleBarPrivate::createLayout() CloseButton->setFocusPolicy(Qt::NoFocus); _this->connect(CloseButton, SIGNAL(clicked()), SIGNAL(closeRequested())); + _this->setMaximizedIcon(false); + MaximizeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + MaximizeButton->setVisible(true); + MaximizeButton->setFocusPolicy(Qt::NoFocus); + _this->connect(MaximizeButton, &QPushButton::clicked, _this, &CFloatingWidgetTitleBar::maximizeRequested); + QFontMetrics fm(TitleLabel->font()); int Spacing = qRound(fm.height() / 4.0); @@ -107,6 +119,7 @@ void FloatingWidgetTitleBarPrivate::createLayout() _this->setLayout(Layout); Layout->addWidget(TitleLabel, 1); Layout->addSpacing(Spacing); + Layout->addWidget(MaximizeButton); Layout->addWidget(CloseButton); Layout->setAlignment(Qt::AlignCenter); @@ -147,7 +160,7 @@ void CFloatingWidgetTitleBar::mouseReleaseEvent(QMouseEvent *ev) d->DragState = DraggingInactive; if (d->FloatingWidget) { - d->FloatingWidget->finishDragging(); + d->FloatingWidget->finishDragging(); } Super::mouseReleaseEvent(ev); } @@ -165,6 +178,12 @@ void CFloatingWidgetTitleBar::mouseMoveEvent(QMouseEvent *ev) // move floating window if (DraggingFloatingWidget == d->DragState) { +#ifdef Q_OS_LINUX + if(d->FloatingWidget->isMaximized()) + { + d->FloatingWidget->showNormal(true); + } +#endif d->FloatingWidget->moveFloating(); Super::mouseMoveEvent(ev); return; @@ -186,11 +205,43 @@ void CFloatingWidgetTitleBar::setTitle(const QString &Text) d->TitleLabel->setText(Text); } - //============================================================================ void CFloatingWidgetTitleBar::updateStyle() { internal::repolishStyle(this, internal::RepolishDirectChildren); } +void CFloatingWidgetTitleBar::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (event->buttons() & Qt::LeftButton) + { + emit maximizeRequested(); + event->accept(); + } + else + { + QWidget::mouseDoubleClickEvent(event); + } +} + +void CFloatingWidgetTitleBar::setMaximizedIcon(bool maximized) +{ + if (maximized) + { + QIcon normalIcon; + auto normalPixmap = this->style()->standardPixmap(QStyle::SP_TitleBarNormalButton, 0, d->MaximizeButton); + normalIcon.addPixmap(normalPixmap, QIcon::Normal); + normalIcon.addPixmap(internal::createTransparentPixmap(normalPixmap, 0.25), QIcon::Disabled); + d->MaximizeButton->setIcon(normalIcon); + } + else + { + QIcon MaxIcon; + auto maxPixmap = this->style()->standardPixmap(QStyle::SP_TitleBarMaxButton, 0, d->MaximizeButton); + MaxIcon.addPixmap(maxPixmap, QIcon::Normal); + MaxIcon.addPixmap(internal::createTransparentPixmap(maxPixmap, 0.25), QIcon::Disabled); + d->MaximizeButton->setIcon(MaxIcon); + } +} + } // namespace ads diff --git a/src/linux/FloatingWidgetTitleBar.h b/src/linux/FloatingWidgetTitleBar.h index 13914db..ec44cb3 100644 --- a/src/linux/FloatingWidgetTitleBar.h +++ b/src/linux/FloatingWidgetTitleBar.h @@ -55,6 +55,7 @@ protected: virtual void mousePressEvent(QMouseEvent *ev) override; virtual void mouseReleaseEvent(QMouseEvent *ev) override; virtual void mouseMoveEvent(QMouseEvent *ev) override; + virtual void mouseDoubleClickEvent(QMouseEvent *event) override; public: using Super = QWidget; @@ -80,11 +81,21 @@ public: */ void updateStyle(); + /** + * Change the maximize button icon according to current windows state + */ + void setMaximizedIcon(bool maximized); + signals: /** * This signal is emitted, if the close button is clicked. */ void closeRequested(); + + /** + * This signal is emitted, if the maximize button is clicked. + */ + void maximizeRequested(); }; } // namespace ads #endif // FLOATINGWIDGETTITLEBAR_H diff --git a/src/src.pro b/src/src.pro index fba3562..9cd81f4 100644 --- a/src/src.pro +++ b/src/src.pro @@ -72,6 +72,7 @@ SOURCES += \ unix { HEADERS += linux/FloatingWidgetTitleBar.h SOURCES += linux/FloatingWidgetTitleBar.cpp +QT += x11extras } isEmpty(PREFIX){