diff --git a/src/apps/res/deskflow.qrc b/src/apps/res/deskflow.qrc
index b013d52f1..c81ec246d 100644
--- a/src/apps/res/deskflow.qrc
+++ b/src/apps/res/deskflow.qrc
@@ -11,7 +11,10 @@
icons/deskflow-dark/actions/16/process-stop.svg
icons/deskflow-dark/actions/16/system-run.svg
icons/deskflow-dark/actions/16/tools-report-bug.svg
+ icons/deskflow-dark/actions/16/view-close.svg
icons/deskflow-dark/actions/16/view-refresh.svg
+ icons/deskflow-dark/actions/16/window-minimize-pip.svg
+ icons/deskflow-dark/actions/16/window-restore-pip.svg
icons/deskflow-dark/actions/22/configure.svg
icons/deskflow-dark/actions/22/edit-copy.svg
icons/deskflow-dark/actions/22/document-edit.svg
@@ -24,7 +27,10 @@
icons/deskflow-dark/actions/22/process-stop.svg
icons/deskflow-dark/actions/22/system-run.svg
icons/deskflow-dark/actions/22/tools-report-bug.svg
+ icons/deskflow-dark/actions/22/view-close.svg
icons/deskflow-dark/actions/22/view-refresh.svg
+ icons/deskflow-dark/actions/22/window-minimize-pip.svg
+ icons/deskflow-dark/actions/22/window-restore-pip.svg
icons/deskflow-dark/actions/24/configure.svg
icons/deskflow-dark/actions/24/edit-copy.svg
icons/deskflow-dark/actions/24/document-edit.svg
@@ -38,7 +44,10 @@
icons/deskflow-dark/actions/24/process-stop.svg
icons/deskflow-dark/actions/24/system-run.svg
icons/deskflow-dark/actions/24/tools-report-bug.svg
+ icons/deskflow-dark/actions/24/view-close.svg
icons/deskflow-dark/actions/24/view-refresh.svg
+ icons/deskflow-dark/actions/24/window-minimize-pip.svg
+ icons/deskflow-dark/actions/24/window-restore-pip.svg
icons/deskflow-dark/actions/32/configure.svg
icons/deskflow-dark/actions/32/application-exit.svg
icons/deskflow-dark/actions/32/dialog-cancel.svg
@@ -74,7 +83,10 @@
icons/deskflow-light/actions/16/process-stop.svg
icons/deskflow-light/actions/16/system-run.svg
icons/deskflow-light/actions/16/tools-report-bug.svg
+ icons/deskflow-light/actions/16/view-close.svg
icons/deskflow-light/actions/16/view-refresh.svg
+ icons/deskflow-light/actions/16/window-minimize-pip.svg
+ icons/deskflow-light/actions/16/window-restore-pip.svg
icons/deskflow-light/actions/22/configure.svg
icons/deskflow-light/actions/22/edit-clear-all.svg
icons/deskflow-light/actions/22/edit-copy.svg
@@ -88,7 +100,10 @@
icons/deskflow-light/actions/22/process-stop.svg
icons/deskflow-light/actions/22/system-run.svg
icons/deskflow-light/actions/22/tools-report-bug.svg
+ icons/deskflow-light/actions/22/view-close.svg
icons/deskflow-light/actions/22/view-refresh.svg
+ icons/deskflow-light/actions/22/window-minimize-pip.svg
+ icons/deskflow-light/actions/22/window-restore-pip.svg
icons/deskflow-light/actions/24/configure.svg
icons/deskflow-light/actions/24/edit-clear-all.svg
icons/deskflow-light/actions/24/edit-copy.svg
@@ -102,7 +117,10 @@
icons/deskflow-light/actions/24/process-stop.svg
icons/deskflow-light/actions/24/system-run.svg
icons/deskflow-light/actions/24/tools-report-bug.svg
+ icons/deskflow-light/actions/24/view-close.svg
icons/deskflow-light/actions/24/view-refresh.svg
+ icons/deskflow-light/actions/24/window-minimize-pip.svg
+ icons/deskflow-light/actions/24/window-restore-pip.svg
icons/deskflow-light/actions/32/configure.svg
icons/deskflow-light/actions/32/application-exit.svg
icons/deskflow-light/actions/32/dialog-cancel.svg
diff --git a/src/apps/res/icons/deskflow-dark/actions/16/view-close.svg b/src/apps/res/icons/deskflow-dark/actions/16/view-close.svg
new file mode 100644
index 000000000..85fc1a802
--- /dev/null
+++ b/src/apps/res/icons/deskflow-dark/actions/16/view-close.svg
@@ -0,0 +1,8 @@
+
+
diff --git a/src/apps/res/icons/deskflow-dark/actions/16/window-minimize-pip.svg b/src/apps/res/icons/deskflow-dark/actions/16/window-minimize-pip.svg
new file mode 100644
index 000000000..c0806971a
--- /dev/null
+++ b/src/apps/res/icons/deskflow-dark/actions/16/window-minimize-pip.svg
@@ -0,0 +1,8 @@
+
+
diff --git a/src/apps/res/icons/deskflow-dark/actions/16/window-restore-pip.svg b/src/apps/res/icons/deskflow-dark/actions/16/window-restore-pip.svg
new file mode 100644
index 000000000..737fa4916
--- /dev/null
+++ b/src/apps/res/icons/deskflow-dark/actions/16/window-restore-pip.svg
@@ -0,0 +1,8 @@
+
+
diff --git a/src/apps/res/icons/deskflow-dark/actions/22/view-close.svg b/src/apps/res/icons/deskflow-dark/actions/22/view-close.svg
new file mode 100644
index 000000000..79787ec99
--- /dev/null
+++ b/src/apps/res/icons/deskflow-dark/actions/22/view-close.svg
@@ -0,0 +1,8 @@
+
+
diff --git a/src/apps/res/icons/deskflow-dark/actions/22/window-minimize-pip.svg b/src/apps/res/icons/deskflow-dark/actions/22/window-minimize-pip.svg
new file mode 100644
index 000000000..47940038f
--- /dev/null
+++ b/src/apps/res/icons/deskflow-dark/actions/22/window-minimize-pip.svg
@@ -0,0 +1,8 @@
+
+
diff --git a/src/apps/res/icons/deskflow-dark/actions/22/window-restore-pip.svg b/src/apps/res/icons/deskflow-dark/actions/22/window-restore-pip.svg
new file mode 100644
index 000000000..967a3cfa6
--- /dev/null
+++ b/src/apps/res/icons/deskflow-dark/actions/22/window-restore-pip.svg
@@ -0,0 +1,8 @@
+
+
diff --git a/src/apps/res/icons/deskflow-dark/actions/24/view-close.svg b/src/apps/res/icons/deskflow-dark/actions/24/view-close.svg
new file mode 100644
index 000000000..ce152e0b8
--- /dev/null
+++ b/src/apps/res/icons/deskflow-dark/actions/24/view-close.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/apps/res/icons/deskflow-dark/actions/24/window-minimize-pip.svg b/src/apps/res/icons/deskflow-dark/actions/24/window-minimize-pip.svg
new file mode 100644
index 000000000..5f8abd67d
--- /dev/null
+++ b/src/apps/res/icons/deskflow-dark/actions/24/window-minimize-pip.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/apps/res/icons/deskflow-dark/actions/24/window-restore-pip.svg b/src/apps/res/icons/deskflow-dark/actions/24/window-restore-pip.svg
new file mode 100644
index 000000000..01933edf6
--- /dev/null
+++ b/src/apps/res/icons/deskflow-dark/actions/24/window-restore-pip.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/apps/res/icons/deskflow-light/actions/16/view-close.svg b/src/apps/res/icons/deskflow-light/actions/16/view-close.svg
new file mode 100644
index 000000000..96b3c53bb
--- /dev/null
+++ b/src/apps/res/icons/deskflow-light/actions/16/view-close.svg
@@ -0,0 +1,21 @@
+
diff --git a/src/apps/res/icons/deskflow-light/actions/16/window-minimize-pip.svg b/src/apps/res/icons/deskflow-light/actions/16/window-minimize-pip.svg
new file mode 100644
index 000000000..b55f8fe6b
--- /dev/null
+++ b/src/apps/res/icons/deskflow-light/actions/16/window-minimize-pip.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/apps/res/icons/deskflow-light/actions/16/window-restore-pip.svg b/src/apps/res/icons/deskflow-light/actions/16/window-restore-pip.svg
new file mode 100644
index 000000000..f458f0728
--- /dev/null
+++ b/src/apps/res/icons/deskflow-light/actions/16/window-restore-pip.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/apps/res/icons/deskflow-light/actions/22/view-close.svg b/src/apps/res/icons/deskflow-light/actions/22/view-close.svg
new file mode 100644
index 000000000..f543cdffe
--- /dev/null
+++ b/src/apps/res/icons/deskflow-light/actions/22/view-close.svg
@@ -0,0 +1,21 @@
+
diff --git a/src/apps/res/icons/deskflow-light/actions/22/window-minimize-pip.svg b/src/apps/res/icons/deskflow-light/actions/22/window-minimize-pip.svg
new file mode 100644
index 000000000..ebe604512
--- /dev/null
+++ b/src/apps/res/icons/deskflow-light/actions/22/window-minimize-pip.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/apps/res/icons/deskflow-light/actions/22/window-restore-pip.svg b/src/apps/res/icons/deskflow-light/actions/22/window-restore-pip.svg
new file mode 100644
index 000000000..6377c2057
--- /dev/null
+++ b/src/apps/res/icons/deskflow-light/actions/22/window-restore-pip.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/apps/res/icons/deskflow-light/actions/24/view-close.svg b/src/apps/res/icons/deskflow-light/actions/24/view-close.svg
new file mode 100644
index 000000000..91ef19982
--- /dev/null
+++ b/src/apps/res/icons/deskflow-light/actions/24/view-close.svg
@@ -0,0 +1,16 @@
+
diff --git a/src/apps/res/icons/deskflow-light/actions/24/window-minimize-pip.svg b/src/apps/res/icons/deskflow-light/actions/24/window-minimize-pip.svg
new file mode 100644
index 000000000..8196d5dca
--- /dev/null
+++ b/src/apps/res/icons/deskflow-light/actions/24/window-minimize-pip.svg
@@ -0,0 +1,11 @@
+
diff --git a/src/apps/res/icons/deskflow-light/actions/24/window-restore-pip.svg b/src/apps/res/icons/deskflow-light/actions/24/window-restore-pip.svg
new file mode 100644
index 000000000..7766a6154
--- /dev/null
+++ b/src/apps/res/icons/deskflow-light/actions/24/window-restore-pip.svg
@@ -0,0 +1,11 @@
+
diff --git a/src/lib/gui/CMakeLists.txt b/src/lib/gui/CMakeLists.txt
index f5c86ad34..1aabcda25 100644
--- a/src/lib/gui/CMakeLists.txt
+++ b/src/lib/gui/CMakeLists.txt
@@ -114,6 +114,8 @@ add_library(${target} STATIC
widgets/FingerprintPreview.h
widgets/KeySequenceWidget.cpp
widgets/KeySequenceWidget.h
+ widgets/LogDock.cpp
+ widgets/LogDock.h
widgets/LogWidget.h
widgets/LogWidget.cpp
widgets/NewScreenWidget.cpp
diff --git a/src/lib/gui/MainWindow.cpp b/src/lib/gui/MainWindow.cpp
index 093d0b202..8116b4a6d 100644
--- a/src/lib/gui/MainWindow.cpp
+++ b/src/lib/gui/MainWindow.cpp
@@ -25,7 +25,7 @@
#include "gui/Styles.h"
#include "gui/core/CoreProcess.h"
#include "gui/ipc/DaemonIpcClient.h"
-#include "gui/widgets/LogWidget.h"
+#include "gui/widgets/LogDock.h"
#include "net/FingerprintDatabase.h"
#include "platform/Wayland.h"
@@ -71,7 +71,7 @@ MainWindow::MainWindow()
m_trayIcon{new QSystemTrayIcon(this)},
m_guiDupeChecker{new QLocalServer(this)},
m_daemonIpcClient{new ipc::DaemonIpcClient(this)},
- m_logWidget{new LogWidget(this)},
+ m_logDock{new LogDock(this)},
m_lblSecurityStatus{new QLabel(this)},
m_lblStatus{new QLabel(this)},
m_btnFingerprint{new QToolButton(this)},
@@ -92,9 +92,7 @@ MainWindow::MainWindow()
setWindowIcon(QIcon::fromTheme(QStringLiteral("deskflow")));
- auto dockLayout = new QVBoxLayout();
- dockLayout->addWidget(m_logWidget);
- ui->dockLogContents->setLayout(dockLayout);
+ addDockWidget(Qt::BottomDockWidgetArea, m_logDock);
// Setup Actions
m_actionAbout->setText(tr("About %1...").arg(kAppName));
@@ -211,7 +209,7 @@ void MainWindow::setupControls()
}
if (!Settings::value(Settings::Gui::LogExpanded).toBool()) {
- ui->dockLog->hide();
+ m_logDock->hide();
}
ui->serverOptions->setVisible(false);
@@ -327,7 +325,7 @@ void MainWindow::connectSlots()
connect(ui->rbModeServer, &QRadioButton::toggled, this, &MainWindow::coreModeToggled);
connect(ui->rbModeClient, &QRadioButton::toggled, this, &MainWindow::coreModeToggled);
- connect(ui->dockLog->toggleViewAction(), &QAction::toggled, this, &MainWindow::toggleLogVisible);
+ connect(m_logDock->toggleViewAction(), &QAction::toggled, this, &MainWindow::toggleLogVisible);
connect(m_btnUpdate, &QPushButton::clicked, this, &MainWindow::openGetNewVersionUrl);
@@ -350,11 +348,20 @@ void MainWindow::toggleLogVisible(bool visible)
setFixedSize(16777215, 16777215);
Settings::setValue(Settings::Gui::LogExpanded, visible);
if (visible) {
- setGeometry(x(), y(), m_expandedSize.width(), m_expandedSize.height());
+ if (m_logDock->isFloating()) {
+ adjustSize();
+ setFixedSize(size());
+ } else {
+ QTimer::singleShot(15, this, [&] { resize(m_expandedSize); });
+ }
} else {
- m_expandedSize = geometry().size();
- ui->dockLog->hide();
- adjustSize();
+ if (!m_logDock->isFloating()) {
+ m_expandedSize = geometry().size();
+ }
+ m_logDock->hide();
+ if (!m_logDock->isFloating()) {
+ adjustSize();
+ }
setFixedSize(size());
}
Settings::setValue(Settings::Gui::WindowGeometry, geometry());
@@ -705,7 +712,7 @@ void MainWindow::createMenuBar()
menuEdit->addAction(m_actionSettings);
auto menuView = new QMenu(tr("&View"), this);
- menuView->addAction(ui->dockLog->toggleViewAction());
+ menuView->addAction(m_logDock->toggleViewAction());
auto menuHelp = new QMenu(tr("&Help"), this);
menuHelp->addAction(m_actionAbout);
@@ -785,7 +792,7 @@ void MainWindow::setIcon()
void MainWindow::handleLogLine(const QString &line)
{
- m_logWidget->appendLine(line);
+ m_logDock->appendLine(line);
updateFromLogLine(line);
}
@@ -882,7 +889,7 @@ void MainWindow::closeEvent(QCloseEvent *event)
// any connected dock view acitons will be triggered
// disconnect them before accepting the event
- disconnect(ui->dockLog->toggleViewAction(), &QAction::toggled, nullptr, nullptr);
+ disconnect(m_logDock->toggleViewAction(), &QAction::toggled, nullptr, nullptr);
event->accept();
QApplication::quit();
diff --git a/src/lib/gui/MainWindow.h b/src/lib/gui/MainWindow.h
index bb3fcf57b..79fbcf2d4 100644
--- a/src/lib/gui/MainWindow.h
+++ b/src/lib/gui/MainWindow.h
@@ -46,7 +46,7 @@ class QAbstractButton;
class QLocalServer;
class DeskflowApplication;
-class LogWidget;
+class LogDock;
namespace Ui {
class MainWindow;
@@ -185,7 +185,7 @@ private:
QLocalServer *m_guiDupeChecker = nullptr;
deskflow::gui::ipc::DaemonIpcClient *m_daemonIpcClient = nullptr;
- LogWidget *m_logWidget = nullptr;
+ LogDock *m_logDock;
QLabel *m_lblSecurityStatus = nullptr;
QLabel *m_lblStatus = nullptr;
QToolButton *m_btnFingerprint = nullptr;
diff --git a/src/lib/gui/MainWindow.ui b/src/lib/gui/MainWindow.ui
index 4c75ca3d0..485868fe6 100644
--- a/src/lib/gui/MainWindow.ui
+++ b/src/lib/gui/MainWindow.ui
@@ -2,14 +2,6 @@
MainWindow
-
-
- 0
- 0
- 758
- 305
-
-
Deskflow
@@ -468,21 +460,6 @@
-
-
- QDockWidget::DockWidgetFeature::DockWidgetClosable
-
-
- Qt::DockWidgetArea::BottomDockWidgetArea
-
-
- Log
-
-
- 8
-
-
-
diff --git a/src/lib/gui/widgets/LogDock.cpp b/src/lib/gui/widgets/LogDock.cpp
new file mode 100644
index 000000000..82c978f7d
--- /dev/null
+++ b/src/lib/gui/widgets/LogDock.cpp
@@ -0,0 +1,85 @@
+/*
+ * Deskflow -- mouse and keyboard sharing utility
+ * SPDX-FileCopyrightText: (C) 2025 Chris Rizzitello
+ * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
+ */
+
+#include "LogDock.h"
+#include "LogWidget.h"
+#include "gui/Styles.h"
+
+#include
+#include
+#include
+#include
+#include
+
+LogDock::LogDock(QWidget *parent)
+ : QDockWidget(tr("Log"), parent),
+ m_textLog{new LogWidget(this)},
+ m_btnClose{new QToolButton(this)},
+ m_btnFloat{new QToolButton(this)},
+ m_lblTitle{new QLabel(tr("Log"), this)}
+{
+ const auto iconSize = QSize(fontMetrics().height() - 2, fontMetrics().height() - 2);
+
+ m_btnFloat->setStyleSheet(deskflow::gui::kStyleFlatButtonHoverable);
+ m_btnFloat->setCheckable(true);
+ m_btnFloat->setIcon(QIcon::fromTheme(QStringLiteral("window-minimize-pip")));
+ m_btnFloat->setIconSize(iconSize);
+ m_btnFloat->setToolTip(tr("Detach from window"));
+ connect(m_btnFloat, &QToolButton::toggled, this, &LogDock::setFloating);
+
+ m_btnClose->setStyleSheet(deskflow::gui::kStyleFlatButtonHoverable);
+ m_btnClose->setIcon(QIcon::fromTheme(QStringLiteral("view-close")));
+ m_btnClose->setIconSize(iconSize);
+ m_btnClose->setToolTip(tr("Close Log"));
+ connect(m_btnClose, &QToolButton::clicked, this, &QDockWidget::hide);
+
+ auto titleWidget = new QWidget(this);
+ titleWidget->installEventFilter(this);
+
+ auto titleLayout = new QHBoxLayout(titleWidget);
+ titleLayout->addWidget(m_lblTitle, Qt::AlignLeft | Qt::AlignVCenter);
+ titleLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Fixed));
+ titleLayout->addWidget(m_btnFloat, Qt::AlignRight | Qt::AlignVCenter);
+ titleLayout->addWidget(m_btnClose, Qt::AlignRight | Qt::AlignVCenter);
+ setTitleBarWidget(titleWidget);
+
+ auto bodyWidget = new QWidget(this);
+ auto bodyLayout = new QVBoxLayout(bodyWidget);
+ bodyLayout->addWidget(m_textLog);
+ setWidget(bodyWidget);
+
+ setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable);
+ setAllowedAreas(Qt::BottomDockWidgetArea);
+}
+
+void LogDock::appendLine(const QString &msg)
+{
+ m_textLog->appendLine(msg);
+}
+
+void LogDock::setFloating(bool floating)
+{
+ if (floating) {
+ m_btnFloat->setToolTip(tr("Attach to window"));
+ m_btnFloat->setIcon(QIcon::fromTheme(QStringLiteral("window-restore-pip")));
+ setWindowFlags(Qt::Dialog);
+ } else {
+ m_btnFloat->setToolTip(tr("Detach from window"));
+ m_btnFloat->setIcon(QIcon::fromTheme(QStringLiteral("window-minimize-pip")));
+ setWindowFlags(Qt::Widget);
+ }
+ m_lblTitle->setVisible(!floating);
+ m_btnClose->setVisible(!floating);
+ show();
+}
+
+bool LogDock::eventFilter(QObject *watched, QEvent *event)
+{
+ // Filter out doubleclick on the titlebar, we only want the dock to float if the user users the button on the dock
+ if (watched == titleBarWidget() && event->type() == QEvent::MouseButtonDblClick)
+ return true;
+ return false;
+}
diff --git a/src/lib/gui/widgets/LogDock.h b/src/lib/gui/widgets/LogDock.h
new file mode 100644
index 000000000..2d952bc74
--- /dev/null
+++ b/src/lib/gui/widgets/LogDock.h
@@ -0,0 +1,31 @@
+/*
+ * Deskflow -- mouse and keyboard sharing utility
+ * SPDX-FileCopyrightText: (C) 2025 Chris Rizzitello
+ * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
+ */
+
+#pragma once
+
+#include
+
+class LogWidget;
+class QLabel;
+class QToolButton;
+
+class LogDock : public QDockWidget
+{
+ Q_OBJECT
+public:
+ explicit LogDock(QWidget *parent = nullptr);
+ void appendLine(const QString &msg);
+ void setFloating(bool floating);
+
+protected:
+ bool eventFilter(QObject *watched, QEvent *event);
+
+private:
+ LogWidget *m_textLog = nullptr;
+ QToolButton *m_btnClose = nullptr;
+ QToolButton *m_btnFloat = nullptr;
+ QLabel *m_lblTitle = nullptr;
+};