diff --git a/src/apps/res/deskflow.qrc b/src/apps/res/deskflow.qrc
index a1a62c517..1e108cc55 100644
--- a/src/apps/res/deskflow.qrc
+++ b/src/apps/res/deskflow.qrc
@@ -30,6 +30,7 @@
icons/deskflow-dark/actions/22/network-connect.svg
icons/deskflow-dark/actions/22/network-disconnect.svg
icons/deskflow-dark/actions/22/process-stop.svg
+ icons/deskflow-dark/actions/22/question.svg
icons/deskflow-dark/actions/22/system-run.svg
icons/deskflow-dark/actions/22/system-search.svg
icons/deskflow-dark/actions/22/tools-report-bug.svg
@@ -50,6 +51,7 @@
icons/deskflow-dark/actions/24/network-connect.svg
icons/deskflow-dark/actions/24/network-disconnect.svg
icons/deskflow-dark/actions/24/process-stop.svg
+ icons/deskflow-dark/actions/24/question.svg
icons/deskflow-dark/actions/24/system-run.svg
icons/deskflow-dark/actions/24/system-search.svg
icons/deskflow-dark/actions/24/tools-report-bug.svg
@@ -115,6 +117,7 @@
icons/deskflow-light/actions/22/network-disconnect.svg
icons/deskflow-light/actions/22/help-about.svg
icons/deskflow-light/actions/22/process-stop.svg
+ icons/deskflow-light/actions/22/question.svg
icons/deskflow-light/actions/22/system-run.svg
icons/deskflow-light/actions/22/system-search.svg
icons/deskflow-light/actions/22/tools-report-bug.svg
@@ -135,6 +138,7 @@
icons/deskflow-light/actions/24/network-connect.svg
icons/deskflow-light/actions/24/network-disconnect.svg
icons/deskflow-light/actions/24/process-stop.svg
+ icons/deskflow-light/actions/24/question.svg
icons/deskflow-light/actions/24/system-run.svg
icons/deskflow-light/actions/24/system-search.svg
icons/deskflow-light/actions/24/tools-report-bug.svg
diff --git a/src/apps/res/icons/deskflow-dark/actions/22/question.svg b/src/apps/res/icons/deskflow-dark/actions/22/question.svg
new file mode 100644
index 000000000..fcd9e2e0e
--- /dev/null
+++ b/src/apps/res/icons/deskflow-dark/actions/22/question.svg
@@ -0,0 +1,7 @@
+
+
diff --git a/src/apps/res/icons/deskflow-dark/actions/24/question.svg b/src/apps/res/icons/deskflow-dark/actions/24/question.svg
new file mode 100644
index 000000000..dc14ab401
--- /dev/null
+++ b/src/apps/res/icons/deskflow-dark/actions/24/question.svg
@@ -0,0 +1,9 @@
+
+
diff --git a/src/apps/res/icons/deskflow-light/actions/22/question.svg b/src/apps/res/icons/deskflow-light/actions/22/question.svg
new file mode 100644
index 000000000..36dba6955
--- /dev/null
+++ b/src/apps/res/icons/deskflow-light/actions/22/question.svg
@@ -0,0 +1,14 @@
+
diff --git a/src/apps/res/icons/deskflow-light/actions/24/question.svg b/src/apps/res/icons/deskflow-light/actions/24/question.svg
new file mode 100644
index 000000000..ee8c5622d
--- /dev/null
+++ b/src/apps/res/icons/deskflow-light/actions/24/question.svg
@@ -0,0 +1,12 @@
+
diff --git a/src/lib/common/UrlConstants.h b/src/lib/common/UrlConstants.h
index 0f9c6d985..95c8770ec 100644
--- a/src/lib/common/UrlConstants.h
+++ b/src/lib/common/UrlConstants.h
@@ -17,7 +17,7 @@ const auto kUrlSourceQuery = QStringLiteral("source=gui");
const auto kUrlApp = QStringLiteral("https://%1").arg(kOrgDomain);
const auto kUrlHelp = QStringLiteral("%1/help?%2").arg(kUrlApp, kUrlSourceQuery);
const auto kUrlDownload = QStringLiteral("%1/download?%2").arg(kUrlApp, kUrlSourceQuery);
-
+const auto kUrlWiki = QStringLiteral("%1/wiki?%2").arg(kUrlApp, kUrlSourceQuery);
const auto kUrlUpdateCheck = QStringLiteral("https://api.%1/version").arg(kOrgDomain);
#if defined(Q_OS_LINUX)
diff --git a/src/lib/gui/CMakeLists.txt b/src/lib/gui/CMakeLists.txt
index e3f7bd9e7..620dc2601 100644
--- a/src/lib/gui/CMakeLists.txt
+++ b/src/lib/gui/CMakeLists.txt
@@ -62,6 +62,8 @@ add_library(${target} STATIC
dialogs/ClientConfigDialog.ui
dialogs/FingerprintDialog.cpp
dialogs/FingerprintDialog.h
+ dialogs/HelpDialog.cpp
+ dialogs/HelpDialog.h
dialogs/HotkeyDialog.cpp
dialogs/HotkeyDialog.h
dialogs/HotkeyDialog.ui
diff --git a/src/lib/gui/MainWindow.cpp b/src/lib/gui/MainWindow.cpp
index ad98bed57..885604a1f 100644
--- a/src/lib/gui/MainWindow.cpp
+++ b/src/lib/gui/MainWindow.cpp
@@ -16,6 +16,7 @@
#include "dialogs/AboutDialog.h"
#include "dialogs/ClientConfigDialog.h"
#include "dialogs/FingerprintDialog.h"
+#include "dialogs/HelpDialog.h"
#include "dialogs/ServerConfigDialog.h"
#include "dialogs/SettingsDialog.h"
@@ -79,6 +80,7 @@ MainWindow::MainWindow()
m_actionStartCore{new QAction(this)},
m_actionRestartCore{new QAction(this)},
m_actionStopCore{new QAction(this)},
+ m_actionShowHelp{new QAction(this)},
m_networkMonitor{new NetworkMonitor(this)}
{
ui->setupUi(this);
@@ -124,6 +126,9 @@ MainWindow::MainWindow()
m_actionReportBug->setIcon(QIcon::fromTheme(QStringLiteral("tools-report-bug")));
m_actionReportBug->setMenuRole(QAction::NoRole);
+ m_actionShowHelp->setIcon(QIcon::fromTheme(QStringLiteral("question")));
+ m_actionShowHelp->setMenuRole(QAction::NoRole);
+
// Setup the Instance Checking
// In case of a previous crash remove first
QLocalServer::removeServer(m_guiSocketName);
@@ -135,6 +140,7 @@ MainWindow::MainWindow()
connectSlots();
setupTrayIcon();
updateScreenName();
+ setHelpFilePath();
qDebug().noquote() << "active settings path:" << Settings::settingsPath();
@@ -263,6 +269,7 @@ void MainWindow::connectSlots()
connect(m_actionStartCore, &QAction::triggered, this, &MainWindow::startCore);
connect(m_actionRestartCore, &QAction::triggered, this, &MainWindow::resetCore);
connect(m_actionStopCore, &QAction::triggered, this, &MainWindow::stopCore);
+ connect(m_actionShowHelp, &QAction::triggered, this, &MainWindow::showHelpViewer);
// Mac os tray will only show a menu
if (!deskflow::platform::isMac())
@@ -672,6 +679,7 @@ void MainWindow::createMenuBar()
m_menuView->addAction(m_logDock->toggleViewAction());
m_menuHelp->addAction(m_actionAbout);
+ m_menuHelp->addAction(m_actionShowHelp);
m_menuHelp->addAction(m_actionReportBug);
m_menuHelp->addSeparator();
m_menuHelp->addAction(m_actionClearSettings);
@@ -1063,6 +1071,8 @@ void MainWindow::updateText()
//: %1 will be the replaced with the appname
m_actionAbout->setText(tr("About %1...").arg(kAppName));
+ m_actionShowHelp->setText(tr("View &Help"));
+
//: start / restart core shortcut
m_actionStartCore->setShortcut(QKeySequence(tr("Ctrl+S")));
m_actionRestartCore->setShortcut(QKeySequence(tr("Ctrl+S")));
@@ -1282,6 +1292,36 @@ void MainWindow::updateTimeoutDelay(int newDelay)
m_statusBar->setConnectionInterval(newDelay);
}
+void MainWindow::setHelpFilePath()
+{
+ const QString appPath = QCoreApplication::applicationDirPath();
+ const auto buildPath = QDir::cleanPath(QString("%1/../docs/user/html/index.html").arg(appPath));
+ auto installPath = QString("%1/../share/doc/deskflow/html/index.html").arg(appPath);
+ if (deskflow::platform::isMac())
+ installPath = QString("%1/Contents/Resources/docs/html/index.html").arg(appPath);
+ else if (deskflow::platform::isWindows())
+ installPath = QString("%1/docs/html/index.html").arg(appPath);
+
+ installPath = QDir::cleanPath(installPath);
+
+ if (QFile::exists(installPath))
+ m_helpPath = QUrl::fromLocalFile(installPath);
+ else if (QFile::exists(buildPath))
+ m_helpPath = QUrl::fromLocalFile(buildPath);
+ else
+ m_helpPath = kUrlWiki;
+}
+
+void MainWindow::showHelpViewer() const
+{
+ if (m_helpPath.isLocalFile()) {
+ HelpDialog dialogHelp(this->centralWidget(), m_helpPath);
+ dialogHelp.exec();
+ } else {
+ QDesktopServices::openUrl(m_helpPath);
+ }
+}
+
bool MainWindow::canRunCore() const
{
const auto mode = m_coreProcess.mode();
diff --git a/src/lib/gui/MainWindow.h b/src/lib/gui/MainWindow.h
index 2160138e4..637e1fdcb 100644
--- a/src/lib/gui/MainWindow.h
+++ b/src/lib/gui/MainWindow.h
@@ -13,6 +13,7 @@
#include
#include
#include
+#include
#include "VersionChecker.h"
#include "config/ServerConfig.h"
@@ -141,6 +142,8 @@ private:
void remoteHostChanged(const QString &newRemoteHost);
void updateIpLabel(const QStringList &addresses);
void updateTimeoutDelay(int newDelay);
+ void setHelpFilePath();
+ void showHelpViewer() const;
bool canRunCore() const;
@@ -200,6 +203,7 @@ private:
QAction *m_actionStartCore = nullptr;
QAction *m_actionRestartCore = nullptr;
QAction *m_actionStopCore = nullptr;
+ QAction *m_actionShowHelp = nullptr;
// Network monitoring
NetworkMonitor *m_networkMonitor = nullptr;
@@ -208,4 +212,5 @@ private:
// Server IP strategy optimization
QStringList m_serverStartIPs;
QString m_serverStartSuggestedIP;
+ QUrl m_helpPath;
};
diff --git a/src/lib/gui/dialogs/HelpDialog.cpp b/src/lib/gui/dialogs/HelpDialog.cpp
new file mode 100644
index 000000000..1067debb1
--- /dev/null
+++ b/src/lib/gui/dialogs/HelpDialog.cpp
@@ -0,0 +1,92 @@
+/*
+ * Deskflow -- mouse and keyboard sharing utility
+ * SPDX-FileCopyrightText: (C) 2026 Deskflow Developers
+ * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
+ */
+
+#include "HelpDialog.h"
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+HelpDialog::HelpDialog(QWidget *parent, const QUrl &source)
+ : QDialog(parent),
+ m_initialSource{source},
+ m_btnBack{new QPushButton(this)},
+ m_btnHome{new QPushButton(this)},
+ m_browser{new QTextBrowser(this)},
+ m_btnClose{new QPushButton(this)}
+{
+ setWindowIcon(QIcon::fromTheme(QStringLiteral("question")));
+
+ m_btnBack->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::GoPrevious));
+ m_btnBack->setMaximumWidth(m_btnBack->height());
+ m_btnBack->setShortcut(QKeySequence::Back);
+ m_btnBack->setEnabled(false);
+ m_btnHome->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::GoHome));
+ m_btnHome->setMaximumWidth(m_btnHome->height());
+ m_btnHome->setEnabled(false);
+ m_btnClose->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::WindowClose));
+
+ updateText();
+
+ m_browser->setMinimumSize(650, 500);
+ m_browser->setOpenExternalLinks(false);
+ m_browser->setOpenLinks(false);
+
+ if (!source.isLocalFile()) {
+ QDesktopServices::openUrl(source);
+ } else {
+ m_browser->setSource(source);
+ }
+
+ auto topLayout = new QHBoxLayout;
+ topLayout->addWidget(m_btnBack);
+ topLayout->addWidget(m_btnHome);
+ topLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum));
+ auto layout = new QVBoxLayout;
+ layout->addLayout(topLayout);
+ layout->addWidget(m_browser);
+ layout->addWidget(m_btnClose);
+ setLayout(layout);
+ adjustSize();
+
+ connect(m_browser, &QTextBrowser::anchorClicked, this, &HelpDialog::linkClicked);
+ connect(m_browser, &QTextBrowser::historyChanged, this, &HelpDialog::historyChanged);
+ connect(m_btnBack, &QPushButton::clicked, m_browser, &QTextBrowser::backward);
+ connect(m_btnHome, &QPushButton::clicked, m_browser, &QTextBrowser::home);
+ connect(m_btnClose, &QPushButton::clicked, this, &QDialog::close);
+}
+
+void HelpDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ if (e->type() == QEvent::LanguageChange) {
+ updateText();
+ }
+}
+
+void HelpDialog::updateText()
+{
+ setWindowTitle(tr("%1 Help").arg(kAppName));
+ m_btnClose->setText(tr("Close"));
+}
+
+void HelpDialog::historyChanged()
+{
+ m_btnBack->setEnabled(m_browser->isBackwardAvailable());
+ m_btnHome->setEnabled(m_browser->source() != m_initialSource);
+}
+
+void HelpDialog::linkClicked(const QUrl &url)
+{
+ if (!url.isLocalFile())
+ QDesktopServices::openUrl(url);
+ else
+ m_browser->setSource(url);
+}
diff --git a/src/lib/gui/dialogs/HelpDialog.h b/src/lib/gui/dialogs/HelpDialog.h
new file mode 100644
index 000000000..0f98d0232
--- /dev/null
+++ b/src/lib/gui/dialogs/HelpDialog.h
@@ -0,0 +1,34 @@
+/*
+ * Deskflow -- mouse and keyboard sharing utility
+ * SPDX-FileCopyrightText: (C) 2026 Deskflow Developers
+ * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+class HelpDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit HelpDialog(QWidget *parent = nullptr, const QUrl &source = {});
+ ~HelpDialog() override = default;
+
+protected:
+ void changeEvent(QEvent *e) override;
+
+private:
+ void updateText();
+ void historyChanged();
+ void linkClicked(const QUrl &url);
+ QUrl m_initialSource;
+ QPushButton *m_btnBack = nullptr;
+ QPushButton *m_btnHome = nullptr;
+ QTextBrowser *m_browser = nullptr;
+ QPushButton *m_btnClose = nullptr;
+};
diff --git a/translations/deskflow_es.ts b/translations/deskflow_es.ts
index 248428eef..078974783 100644
--- a/translations/deskflow_es.ts
+++ b/translations/deskflow_es.ts
@@ -270,6 +270,17 @@ Do you want to connect to the server?
Formato hash no válido
+
+ HelpDialog
+
+ %1 Help
+ %1 Ayuda
+
+
+ Close
+ Cerca
+
+
HotkeyDialog
@@ -407,6 +418,10 @@ Do you want to connect to the server?
%1 will be the replaced with the appname
Acerca de %1...
+
+ View &Help
+ Ver &ayuda
+
Ctrl+S
start / restart core shortcut
diff --git a/translations/deskflow_it.ts b/translations/deskflow_it.ts
index 33544d09a..141454c53 100644
--- a/translations/deskflow_it.ts
+++ b/translations/deskflow_it.ts
@@ -270,6 +270,17 @@ Vuoi connetterti al server?
Formato hash non valido
+
+ HelpDialog
+
+ %1 Help
+ %1 Aiuto
+
+
+ Close
+ Vicino
+
+
HotkeyDialog
@@ -395,6 +406,10 @@ Vuoi connetterti al server?
%1 will be the replaced with the appname
Informazioni su %1...
+
+ View &Help
+ Visualizza la &guida
+
Ctrl+S
start / restart core shortcut
diff --git a/translations/deskflow_ja.ts b/translations/deskflow_ja.ts
index eda167125..5203a65f7 100644
--- a/translations/deskflow_ja.ts
+++ b/translations/deskflow_ja.ts
@@ -270,6 +270,17 @@ Do you want to connect to the server?
不正なハッシュ形式
+
+ HelpDialog
+
+ %1 Help
+ %1 ヘルプ
+
+
+ Close
+ 近い
+
+
HotkeyDialog
@@ -421,6 +432,10 @@ Do you want to connect to the server?
<p>Failed to connect to the server '%1'.</p><p>A Client with your name is already connected to the server.</p>Please ensure that you're using a unique name and that only a single instance of the client process is running.</p>
<p>サーバー '%1' への接続に失敗しました。</p><p>同じ名前のクライアントがサーバーに接続済です。</p><p>名前の重複がないことと、クライアントプロセスが多重起動していない事を確認してください。</p>
+
+ View &Help
+ ヘルプを表示 (&H)
+
No IP Detected
IPアドレスが見つかりません
diff --git a/translations/deskflow_ko.ts b/translations/deskflow_ko.ts
index a2e61bf52..1319918f0 100644
--- a/translations/deskflow_ko.ts
+++ b/translations/deskflow_ko.ts
@@ -270,6 +270,17 @@ Do you want to connect to the server?
잘못된 해시 형식
+
+ HelpDialog
+
+ %1 Help
+ %1 도움말
+
+
+ Close
+ 닫다
+
+
HotkeyDialog
@@ -421,6 +432,10 @@ Do you want to connect to the server?
<p>Failed to connect to the server '%1'.</p><p>A Client with your name is already connected to the server.</p>Please ensure that you're using a unique name and that only a single instance of the client process is running.</p>
<p>서버 '%1'에 연결하지 못했습니다.</p><p>같은 이름의 클라이언트가 이미 서버에 연결되어 있습니다.</p><p>고유한 이름을 사용하고, 클라이언트 프로세스가 하나만 실행 중인지 확인하세요.</p>
+
+ View &Help
+ 도움말 보기(&H)
+
No IP Detected
IP를 감지하지 못했습니다
diff --git a/translations/deskflow_ru.ts b/translations/deskflow_ru.ts
index 697758b65..8773dea82 100644
--- a/translations/deskflow_ru.ts
+++ b/translations/deskflow_ru.ts
@@ -270,6 +270,17 @@ Do you want to connect to the server?
Неверный формат хэша
+
+ HelpDialog
+
+ %1 Help
+ %1 Справка
+
+
+ Close
+ Закрывать
+
+
HotkeyDialog
@@ -421,6 +432,10 @@ Do you want to connect to the server?
<p>Failed to connect to the server '%1'.</p><p>A Client with your name is already connected to the server.</p>Please ensure that you're using a unique name and that only a single instance of the client process is running.</p>
<p>Не удалось подключиться к серверу '%1'.</p><p>Клиент с таким именем уже подключен к серверу.</p>Убедитесь, что вы используете уникальное имя и запущен только один процесс клиента.</p>
+
+ View &Help
+ Просмотр &справки
+
No IP Detected
IP-адрес не обнаружен
diff --git a/translations/deskflow_zh_CN.ts b/translations/deskflow_zh_CN.ts
index 78b3cdb86..074eef431 100644
--- a/translations/deskflow_zh_CN.ts
+++ b/translations/deskflow_zh_CN.ts
@@ -270,6 +270,17 @@ Do you want to connect to the server?
无效的哈希格式
+
+ HelpDialog
+
+ %1 Help
+ %1 帮助
+
+
+ Close
+ 关闭
+
+
HotkeyDialog
@@ -421,6 +432,10 @@ Do you want to connect to the server?
<p>Failed to connect to the server '%1'.</p><p>A Client with your name is already connected to the server.</p>Please ensure that you're using a unique name and that only a single instance of the client process is running.</p>
<p>连接到服务器“%1”失败。</p><p>一个同名的客户端已连接到服务器。</p>请确保您使用的名称唯一,且只有一个客户端进程实例在运行。</p>
+
+ View &Help
+ 查看帮助(&H)
+
No IP Detected
未检测到 IP