33 Commits

Author SHA1 Message Date
daa095461d Release 1.21.2
Some checks are pending
Continuous Integration / pr-comment-flags (push) Blocked by required conditions
Continuous Integration / ci-passed (push) Blocked by required conditions
Continuous Integration / test-results (push) Blocked by required conditions
Continuous Integration / reuse-lint (push) Waiting to run
Continuous Integration / lint-check (push) Blocked by required conditions
Continuous Integration / analyse-valgrind (push) Blocked by required conditions
Continuous Integration / analyse-sonarcloud (push) Blocked by required conditions
Continuous Integration / macos-14-arm64 (push) Blocked by required conditions
Continuous Integration / macos-13-x64 (push) Blocked by required conditions
Continuous Integration / archlinux-x86_84 (push) Blocked by required conditions
Continuous Integration / debian-13-arm64 (push) Blocked by required conditions
Continuous Integration / debian-13-x86_64 (push) Blocked by required conditions
Continuous Integration / fedora-40-arm64 (push) Blocked by required conditions
Continuous Integration / fedora-40-x86_84 (push) Blocked by required conditions
Continuous Integration / fedora-41-arm64 (push) Blocked by required conditions
Continuous Integration / fedora-41-x86_64 (push) Blocked by required conditions
Continuous Integration / opensuse-arm64 (push) Blocked by required conditions
Continuous Integration / opensuse-x86_84 (push) Blocked by required conditions
Continuous Integration / ubuntu-25.04-arm64 (push) Blocked by required conditions
Continuous Integration / ubuntu-25.04-x86_64 (push) Blocked by required conditions
Continuous Integration / windows-2022-x64 (push) Blocked by required conditions
Continuous Integration / unix-freebsd (push) Blocked by required conditions
Continuous Integration / flatpak-aarch64 (push) Blocked by required conditions
Continuous Integration / flatpak-x86_64 (push) Blocked by required conditions
Continuous Integration / release (push) Blocked by required conditions
Continuous Integration / winget-publish (push) Blocked by required conditions
2025-04-07 05:24:27 -07:00
fde880fb6f chore: add default case for EI_EVENT handling 2025-04-07 11:23:28 +01:00
a04568b25f refactor: remove unneeded using ProcessState in coreprocess::startprocess 2025-04-07 03:09:30 -07:00
6e6892b6e7 refactor: use checkbox for elevate remove Settings::Core::ElevateMode and use Daemon::Elevate to hold the value 2025-04-07 03:09:30 -07:00
5ad2c9283d refactor: add a check box to enable stop on desk switch 2025-04-07 03:09:30 -07:00
487030aade refactor: settings gui, use the service group as a checkbox for enable service 2025-04-07 03:09:30 -07:00
1ace03d4b5 feat: add help and version to gui,
move no-reset to the QCommandLineProcessor
2025-04-06 06:41:25 -07:00
5df333fae9 refactor: change project description to 'Keyboard and mouse sharing utility' 2025-04-06 06:41:25 -07:00
3b4306183c chore: deskflow-gui remove fallback for pre mavricks mac os accessibility prompt 2025-04-06 06:41:25 -07:00
4203f42363 chore: remove workaround for Qt-43022 2025-04-06 06:41:25 -07:00
7e4ac48476 chore: df-gui remove unused QThredImpl class 2025-04-06 06:41:25 -07:00
0b05b0e71d chore: deskflow-gui remove unused includes 2025-04-06 06:41:25 -07:00
cos
ebb63d8113 build: add manpages
Manual pages got removed from synergy in #7361. This commit brings them
back. They are rudimentary and autogenerated using `help2man`, with only
a message about finding the documentation at the wiki page added. Not
much, but a lot better than nothing.

Package names added to Continuous Integration are based on these web pages:

    https://archlinux.org/packages/extra/x86_64/help2man/
    https://packages.debian.org/help2man
    https://packages.fedoraproject.org/pkgs/help2man/help2man/
    https://software.opensuse.org/package/help2man
2025-04-06 06:07:52 -07:00
c1f1734943 ci: bump windows / mac builds to Qt 6.9.0 2025-04-05 13:28:27 -07:00
12bcc1a4d6 fix: build issue on Qt 6.9 2025-04-05 13:28:27 -07:00
84283a1b13 ci: correctly install depends for freebsd 2025-04-05 13:08:25 -07:00
0a33e20723 refactor: remove unused --host arg from deskflow-client 2025-04-05 12:05:23 -07:00
d4f916c365 chore: remove unused --no-wayland-ei
fixes #7680
2025-04-05 12:05:23 -07:00
6df96d4a56 refactor: Don't show drag and drop as an option on linux 2025-04-05 12:05:23 -07:00
e617e4b537 chore: deskflow-client, remove from help --use-x-window, options was unused and should not be reported as an option 2025-04-05 12:05:23 -07:00
ca5cc8211b chore: lib/deskflow/ServerApp remove duplicate print of HELP_COMMON_INFO_2 2025-04-05 12:05:23 -07:00
f01b592dad build: a windows portable package 2025-04-03 17:07:42 +01:00
46c6275c43 feature: make sure when portable settings are cleared a new blank file is created 2025-04-02 12:39:54 +01:00
b3fb8959a3 refactor: SettingsDialog, Hide the service if its not able to be enabled 2025-04-02 12:39:54 +01:00
8354a81706 refactor: Settings set default elevateMode based on settings type, for registry we use Always, ini mode uses Never 2025-04-02 12:39:54 +01:00
d9807a2693 refactor: Set default process Mode based on Settings type if we are INI Type we in desktop mode if Native (only on windows) we default to Service 2025-04-02 12:39:54 +01:00
1b8067797e refactor: settings for windows try to use registry unless portable install
new Settings::isUsingRegistry() true when using the registry
          move daemon log back to config dir
2025-04-02 12:39:54 +01:00
cf4fe32aab refactor: allow proxy to load from registry if no file is provided 2025-04-02 12:39:54 +01:00
6bbebe75f9 fixes #8423, set unit test to force external config on for server tests 2025-04-02 11:12:12 +01:00
0b9ecbc2f4 chore: move unused integtests Network tests to test/unittests 2025-04-01 13:12:40 +01:00
fc36cf6be8 refactor: move remaining platform tests to test/unittests 2025-04-01 13:12:40 +01:00
b7b295aeb6 refactor: combine XWindowsClipboards tests into one unittest 2025-04-01 13:12:40 +01:00
00b5c32fc9 refactor: combine all OSXKeyStateTests into one unittest 2025-04-01 13:12:40 +01:00
47 changed files with 518 additions and 514 deletions

View File

@ -39,25 +39,26 @@ runs:
xorg-dev libx11-dev libxtst-dev libssl-dev \
libglib2.0-dev libxkbfile-dev qt6-base-dev qt6-tools-dev \
libgtk-3-dev libgtest-dev libgmock-dev \
libei-dev libportal-dev libtomlplusplus-dev libcli11-dev -y >/dev/null
libei-dev libportal-dev libtomlplusplus-dev libcli11-dev \
help2man -y >/dev/null
elif [ ${{inputs.like}} == "fedora" ]; then
dnf install -y cmake make ninja-build gcc-c++ \
rpm-build openssl-devel glib2-devel \
libXtst-devel libxkbfile-devel qt6-qtbase-devel qt6-qttools-devel \
gtk3-devel gtest-devel gmock-devel \
libei-devel libportal-devel tomlplusplus-devel \
cli11-devel
cli11-devel help2man
elif [ ${{inputs.like}} == "suse" ]; then
zypper refresh
zypper install -y --force-resolution \
cmake make ninja gcc-c++ rpm-build libopenssl-devel \
glib2-devel libXtst-devel libxkbfile-devel qt6-base-devel qt6-tools-devel gtk3-devel \
googletest-devel googlemock-devel libei-devel \
libportal-devel tomlplusplus-devel cli11-devel
libportal-devel tomlplusplus-devel cli11-devel help2man
elif [ ${{ inputs.like }} == "arch" ]; then
pacman -Syu --noconfirm base-devel cmake ninja \
gcc openssl glib2 libxtst libxkbfile gtest libei libportal \
qt6-base qt6-tools gtk3 tomlplusplus cli11
qt6-base qt6-tools gtk3 tomlplusplus cli11 help2man
else
echo "Unknown like"
fi

View File

@ -258,7 +258,7 @@ jobs:
id: get-deps
uses: ./.github/actions/install-dependencies
with:
qt-version: 6.8.2
qt-version: 6.9.0
qt-install-dir: ${{matrix.target.qt-install-dir}}
like: ${{ matrix.target.like }}
@ -316,17 +316,17 @@ jobs:
# it also makes sure we can use git --describe correctly
- name: Fancy Checkout
uses: sithlord48/fancy-checkout@v1
- name: Build on FreeBSD
if: ${{ matrix.distro.name == 'freebsd' }}
uses: vmactions/freebsd-vm@v1
with:
usesh: true
run: |
./scripts/install_deps.sh
pkg install -y cmake ninja gmake gcc12 openssl glib \
libX11 libXtst libxkbfile qt6-base qt6-tools gtk3 googletest \
tomlplusplus cli11 pkgconf libei libportal
${{env.CMAKE_CONFIGURE}} -G Ninja
cmake --build build -j16
# Integration tests are flakey by nature, make them optional.
export QT_QPA_PLATFORM=offscreen
./build/bin/unittests

View File

@ -19,7 +19,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Fallback for when git can not be found
set(DESKFLOW_VERSION_MAJOR 1)
set(DESKFLOW_VERSION_MINOR 21)
set(DESKFLOW_VERSION_PATCH 1)
set(DESKFLOW_VERSION_PATCH 2)
set(DESKFLOW_VERSION_TWEAK 0)
# Get the version from git if it's a git repository
@ -72,7 +72,7 @@ endif()
project(
deskflow
VERSION "${DESKFLOW_VERSION_MAJOR}.${DESKFLOW_VERSION_MINOR}.${DESKFLOW_VERSION_PATCH}.${DESKFLOW_VERSION_TWEAK}"
DESCRIPTION "Mouse and keyboard sharing utility"
DESCRIPTION "Keyboard and mouse sharing utility"
LANGUAGES C CXX)
# Define Additional "PROJECT" vars for packaging and metadata

View File

@ -145,6 +145,12 @@ precedence = "override"
SPDX-FileCopyrightText = "Deskflow Developers"
SPDX-License-Identifier = "MIT"
[[annotations]]
path = "src/apps/res/manpage.txt"
precedence = "override"
SPDX-FileCopyrightText = "Deskflow Developers"
SPDX-License-Identifier = "MIT"
[[annotations]]
path = "src/apps/res/icons/deskflow-**/**/**/**.svg"
precedence = "override"

View File

@ -42,6 +42,16 @@
</branding>
<content_rating type="oars-1.0" />
<releases>
<release version="1.21.2" date="2025-04-07" urgency="high">
<description>
<p>This stable release fixes a few critical bugs in 1.21.1. For the full changelog see the release page.</p>
<ul>
<li>Fix: Crash with Qt 6.9</li>
<li>Fix: Windows settings in wrong locations</li>
</ul>
</description>
<url>https://github.com/deskflow/deskflow/releases/tag/v1.21.2</url>
</release>
<release version="1.21.1" date="2025-03-31" urgency="high">
<description>
<p>This stable release fixes a few critical bugs in 1.21.0. For the full changelog see the release page.</p>

View File

@ -0,0 +1,6 @@
#SPDX-FileCopyrightText: 2025 Chris Rizzitello <sithlord48@gmail.com>
#SPDX-License-Identifier: MIT
if(CPACK_GENERATOR MATCHES 7Z|ZIP)
set(CPACK_PACKAGE_FILE_NAME ${CPACK_PACKAGE_FILE_NAME}-portable)
endif()

View File

@ -5,10 +5,20 @@
# calling CMAKE_CURRENT_LIST_DIR after include would return the wrong scope var
set(MY_DIR ${CMAKE_CURRENT_LIST_DIR})
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE)
set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION .)
include(InstallRequiredSystemLibraries)
install(CODE "execute_process(
COMMAND ${DEPLOYQT} --no-compiler-runtime --no-system-d3d-compiler --no-quick-import -network \"\${CMAKE_INSTALL_PREFIX}/deskflow.exe\"
)")
configure_file(${MY_DIR}/pre-cpack.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/pre-cpack.cmake @ONLY)
set(CPACK_PRE_BUILD_SCRIPTS ${CMAKE_CURRENT_BINARY_DIR}/pre-cpack.cmake)
configure_file(${MY_DIR}/cpack-options.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/cpack-options.cmake @ONLY)
set(CPACK_PROJECT_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/cpack-options.cmake)
# Setup OS_STRING
if(CMAKE_SYSTEM_PROCESSOR MATCHES AMD64)
set(OS_STRING "win-x64")
@ -18,6 +28,8 @@ else()
set(OS_STRING "win-${CMAKE_SYSTEM_PROCESSOR}")
endif()
list(APPEND CPACK_GENERATOR "7Z")
# If Wix4+ is installed make a package
find_program(WIX_APP wix)
if (NOT "${WIX_APP}" STREQUAL "")

View File

@ -0,0 +1,19 @@
# SPDX-FileCopyrightText: 2025 Chris Rizzitello <sithlord48@gmail.com>
# SPDX-License-Identifier: MIT
if(CPACK_GENERATOR MATCHES 7Z|ZIP)
string(REPLACE " " "*" _TEMP_LIST "@CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS@")
set(${PORTABLE_LIBS} "")
foreach(ITEM ${_TEMP_LIST})
string(REPLACE "*" " " _ITEM ${ITEM})
file(COPY ${_ITEM} DESTINATION ${CPACK_TEMPORARY_INSTALL_DIRECTORY})
endforeach()
file(WRITE ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/settings/Deskflow.conf " ")
file(REMOVE ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/deskflow-daemon.exe)
file(WRITE ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/README.txt
" Portable Deskflow: @CMAKE_PROJECT_VERSION@
The portable version must have the settings/Deskflow.conf file to save settings, or it will try to use the system settings location.
The portable version does not include the daemon, so the client will not work at UAC prompts or the login screen.
")
endif()

View File

@ -1,18 +1,17 @@
# GUI Config
Deskflow will automaticlly figure out where to save settings and other files.
## Unix Systems
The search order for a setting file is:
1. `<install-path>/settings/Deskflow.conf`
1. `<XDG_CONFIG_HOME>/Deskflow/Deskflow.conf`
1. A user settings file
1. A system settings file
A new settings file will be created in the user path if no settings file is found.
The path of the settings file will be used as the base for all other config files.
### Windows
- System: `C:\ProgramData\Deskflow\Deskflow.conf`
- User: `C:\Users\userName\AppData\Local\Deskflow\Deskflow.conf`
### Linux
- System: `/etc/Deskflow/Deskflow.conf`
- User: `~/.config/Deskflow/Deskflow.conf`
@ -20,6 +19,17 @@
### macOS
- System: `/Library/Deskflow/Deskflow.conf`
- User: `~/Library/Deskflow/Deskflow.conf`
## Windows
The search order for a setting file is:
1. `<install-path>/settings/Deskflow.conf`
1. Windows Registry `HKCU\Software\Deskflow\Deskflow`
Windows will save to the install dir if settings are loaded from there. If not, it saves any other config files in: `C:\ProgramData\Deskflow\`
When using settings from the install dir, the service mode will not be available.
# Server Config Examples

View File

@ -3,6 +3,30 @@
# SPDX-FileCopyrightText: 2009 - 2012 Nick Bolton
# SPDX-License-Identifier: MIT
if(UNIX AND NOT APPLE)
find_program(HELP2MAN help2man)
if(NOT HELP2MAN)
message(STATUS "Man page tool (help2man) not found, man pages will not be generated")
endif()
endif()
function(generate_app_man TARGET)
if(HELP2MAN)
add_custom_command(
TARGET ${target} POST_BUILD
COMMAND QT_QPA_PLATFORM=minimal ${HELP2MAN}
--include ${CMAKE_SOURCE_DIR}/src/apps/res/manpage.txt
--no-info
$<TARGET_FILE:${target}>
-o $<TARGET_FILE_DIR:${target}>/${target}.1
)
install(
FILES $<TARGET_FILE_DIR:${target}>/${target}.1
DESTINATION ${CMAKE_INSTALL_MANDIR}/man1
)
endif()
endfunction()
option(BUILD_UNIFIED "Build unified binary" OFF)
if(BUILD_UNIFIED)
add_subdirectory(deskflow-core)

View File

@ -45,6 +45,7 @@ if(APPLE)
)
elseif(UNIX)
install(TARGETS ${target} DESTINATION bin)
generate_app_man(${target})
elseif(WIN32)
install(
TARGETS ${target}

View File

@ -40,6 +40,7 @@ if(APPLE)
)
elseif(UNIX)
install(TARGETS ${target} DESTINATION bin)
generate_app_man(${target})
elseif(WIN32)
install(
TARGETS ${target}

View File

@ -84,4 +84,5 @@ elseif(APPLE)
install(TARGETS ${target} BUNDLE DESTINATION .)
else()
install(TARGETS ${target} DESTINATION bin)
generate_app_man(${target})
endif()

View File

@ -16,13 +16,11 @@
#include "gui/StyleUtils.h"
#include <QApplication>
#include <QDebug>
#include <QCommandLineParser>
#include <QGuiApplication>
#include <QLocalSocket>
#include <QMessageBox>
#include <QObject>
#include <QSharedMemory>
#include <QtGlobal>
#if defined(Q_OS_MAC)
#include <Carbon/Carbon.h>
@ -35,24 +33,10 @@
using namespace deskflow::gui;
class QThreadImpl : public QThread
{
public:
static void msleep(unsigned long msecs)
{
QThread::msleep(msecs);
}
};
#if defined(Q_OS_MAC)
bool checkMacAssistiveDevices();
#endif
bool hasArg(const QString &arg, const QStringList &args)
{
return std::ranges::any_of(args, [&arg](const QString &a) { return a == arg; });
}
int main(int argc, char *argv[])
{
#if defined(Q_OS_UNIX) && defined(QT_DEBUG)
@ -60,21 +44,39 @@ int main(int argc, char *argv[])
QLoggingCategory::setFilterRules(QStringLiteral("*.debug=true\nqt.*=false"));
#endif
#if defined(Q_OS_MAC)
/* Workaround for QTBUG-40332 - "High ping when QNetworkAccessManager is
* instantiated" */
::setenv("QT_BEARER_POLL_TIMEOUT", "-1", 1);
#endif
QCoreApplication::setApplicationName(kAppName);
QCoreApplication::setOrganizationName(kAppName);
QCoreApplication::setApplicationVersion(kVersion);
QCoreApplication::setOrganizationDomain(kOrgDomain); // used in prefix, can't be a url
QGuiApplication::setDesktopFileName(QStringLiteral("org.deskflow.deskflow"));
// used as a prefix for settings paths, and must not be a url.
QCoreApplication::setOrganizationDomain(kOrgDomain);
QApplication app(argc, argv);
// Add Command Line Options
QCommandLineOption helpOption = QCommandLineOption("help", "Display Help on the command line");
QCommandLineOption versionOption = QCommandLineOption("version", "Display version information");
QCommandLineOption noResetOption =
QCommandLineOption("no-reset", "Prevent settings reset if DESKFLOW_RESET_ALL is set");
QCommandLineParser parser;
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
parser.addOption(helpOption);
parser.addOption(versionOption);
parser.addOption(noResetOption);
parser.parse(QCoreApplication::arguments());
const auto header = QStringLiteral("%1: %2\n").arg(kAppName, kDisplayVersion);
if (parser.isSet(helpOption) || !parser.unknownOptionNames().isEmpty() || !parser.errorText().isEmpty()) {
QTextStream(stdout) << header << QStringLiteral(" %1\n\n").arg(kAppDescription)
<< parser.helpText().replace(QApplication::applicationFilePath(), kAppId);
return 0;
}
if (parser.isSet(versionOption)) {
QTextStream(stdout) << header << kCopyright << Qt::endl;
return 0;
}
// Create a shared memory segment with a unique key
// This is to prevent a new instance from running if one is already running
QSharedMemory sharedMemory("deskflow-gui");
@ -131,10 +133,8 @@ int main(int argc, char *argv[])
#endif
// --no-reset
QStringList arguments = QCoreApplication::arguments();
const auto noReset = hasArg("--no-reset", arguments);
const auto resetEnvVar = QVariant(qEnvironmentVariable("DESKFLOW_RESET_ALL")).toBool();
if (resetEnvVar && !noReset) {
if (resetEnvVar && !parser.isSet(noResetOption)) {
diagnostic::clearSettings(false);
}
@ -147,8 +147,6 @@ int main(int argc, char *argv[])
#if defined(Q_OS_MAC)
bool checkMacAssistiveDevices()
{
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090 // mavericks
// new in mavericks, applications are trusted individually
// with use of the accessibility api. this call will show a
// prompt which can show the security/privacy/accessibility
@ -166,21 +164,5 @@ bool checkMacAssistiveDevices()
bool result = AXIsProcessTrustedWithOptions(options);
CFRelease(options);
return result;
#else
// now deprecated in mavericks.
bool result = AXAPIEnabled();
if (!result) {
QString msgBody = QString(
"Please enable access to assistive devices "
"System Preferences -> Security & Privacy -> "
"Privacy -> Accessibility, then re-open %1."
);
QMessageBox::information(NULL, kAppName, msgBody.arg(kAppName));
}
return result;
#endif
}
#endif

View File

@ -45,6 +45,7 @@ if(APPLE)
)
elseif(UNIX)
install(TARGETS ${target} DESTINATION bin)
generate_app_man(${target})
elseif(WIN32)
install(
TARGETS ${target}

5
src/apps/res/manpage.txt Normal file
View File

@ -0,0 +1,5 @@
[SEE ALSO]
deskflow(1), deskflow-client(1), deskflow-server(2)
All documentation is on the web, so please point your browser at
<https://github.com/deskflow/deskflow/wiki> and surf away.

View File

@ -134,24 +134,20 @@ void Client::connect(size_t addressIndex)
auto securityLevel = m_useSecureNetwork ? SecurityLevel::PeerAuth : SecurityLevel::PlainText;
try {
if (m_args.m_hostMode) {
LOG((CLOG_NOTE "waiting for server connection on %i port", m_serverAddress.getPort()));
} else {
// resolve the server hostname. do this every time we connect
// in case we couldn't resolve the address earlier or the address
// has changed (which can happen frequently if this is a laptop
// being shuttled between various networks). patch by Brent
// Priddy.
m_resolvedAddressesCount = m_serverAddress.resolve(addressIndex);
// resolve the server hostname. do this every time we connect
// in case we couldn't resolve the address earlier or the address
// has changed (which can happen frequently if this is a laptop
// being shuttled between various networks). patch by Brent
// Priddy.
m_resolvedAddressesCount = m_serverAddress.resolve(addressIndex);
// m_serverAddress will be null if the hostname address is not reolved
if (m_serverAddress.getAddress() != nullptr) {
// to help users troubleshoot, show server host name (issue: 60)
LOG(
(CLOG_NOTE "connecting to '%s': %s:%i", m_serverAddress.getHostname().c_str(),
ARCH->addrToString(m_serverAddress.getAddress()).c_str(), m_serverAddress.getPort())
);
}
// m_serverAddress will be null if the hostname address is not reolved
if (m_serverAddress.getAddress() != nullptr) {
// to help users troubleshoot, show server host name (issue: 60)
LOG(
(CLOG_NOTE "connecting to '%s': %s:%i", m_serverAddress.getHostname().c_str(),
ARCH->addrToString(m_serverAddress.getAddress()).c_str(), m_serverAddress.getPort())
);
}
// create the socket
@ -509,11 +505,8 @@ void Client::setupScreen()
void Client::setupTimer()
{
assert(m_timer == NULL);
if (!m_args.m_hostMode) {
m_timer = m_events->newOneShotTimer(2.0, NULL);
m_events->adoptHandler(Event::kTimer, m_timer, new TMethodEventJob<Client>(this, &Client::handleConnectTimeout));
}
m_timer = m_events->newOneShotTimer(2.0, NULL);
m_events->adoptHandler(Event::kTimer, m_timer, new TMethodEventJob<Client>(this, &Client::handleConnectTimeout));
}
void Client::cleanup()

View File

@ -25,7 +25,13 @@ QString getSystemSettingsBaseDir()
void QSettingsProxy::load(const QString &fileName)
{
m_pSettings = std::make_unique<QSettings>(fileName, QSettings::IniFormat);
if (m_pSettings)
m_pSettings.get()->deleteLater();
if (fileName.isEmpty())
m_pSettings = std::make_unique<QSettings>(QSettings::NativeFormat, QSettings::UserScope, kAppName, kAppName);
else
m_pSettings = std::make_unique<QSettings>(fileName, QSettings::IniFormat);
}
int QSettingsProxy::beginReadArray(const QString &prefix)

View File

@ -35,22 +35,27 @@ void Settings::setSettingFile(const QString &settingsFile)
Settings::Settings(QObject *parent) : QObject(parent)
{
m_portableSettingsFile = m_portableSettingsFile.arg(QCoreApplication::applicationDirPath(), kAppName);
QString fileToLoad;
#ifdef Q_OS_WIN
m_portableSettingsFile = m_portableSettingsFile.arg(QCoreApplication::applicationDirPath(), kAppName);
if (QFile(m_portableSettingsFile).exists()) {
fileToLoad = m_portableSettingsFile;
m_settings = new QSettings(fileToLoad, QSettings::IniFormat);
} else {
if (!qEnvironmentVariable("XDG_CONFIG_HOME").isEmpty())
fileToLoad = QStringLiteral("%1/%2/%2.conf").arg(qEnvironmentVariable("XDG_CONFIG_HOME"), kAppName);
else if (QFile(UserSettingFile).exists())
fileToLoad = UserSettingFile;
else if (QFile(SystemSettingFile).exists())
fileToLoad = SystemSettingFile;
else
fileToLoad = UserSettingFile;
m_settings = new QSettings(QSettings::NativeFormat, QSettings::UserScope, kAppName, kAppName);
}
#else
if (!qEnvironmentVariable("XDG_CONFIG_HOME").isEmpty())
fileToLoad = QStringLiteral("%1/%2/%2.conf").arg(qEnvironmentVariable("XDG_CONFIG_HOME"), kAppName);
else if (QFile(UserSettingFile).exists())
fileToLoad = UserSettingFile;
else if (QFile(SystemSettingFile).exists())
fileToLoad = SystemSettingFile;
else
fileToLoad = UserSettingFile;
m_settings = new QSettings(fileToLoad, QSettings::IniFormat);
#endif
m_settingsProxy = std::make_shared<QSettingsProxy>();
m_settingsProxy->load(fileToLoad);
qInfo().noquote() << "settings file:" << m_settings->fileName();
@ -70,7 +75,8 @@ void Settings::cleanSettings()
QVariant Settings::defaultValue(const QString &key)
{
if ((key == Gui::Autohide) || (key == Core::StartedBefore) || (key == Core::PreventSleep) ||
(key == Server::ExternalConfig) || (key == Client::InvertScrollDirection) || (key == Log::ToFile)) {
(key == Server::ExternalConfig) || (key == Client::InvertScrollDirection) || (key == Log::ToFile) ||
(key == Core::StopOnDeskSwitch)) {
return false;
}
@ -104,8 +110,8 @@ QVariant Settings::defaultValue(const QString &key)
if (key == Server::Binary)
return kServerBinName;
if (key == Core::ElevateMode)
return Settings::ElevateMode::Always;
if (key == Daemon::Elevate)
return instance()->isNativeMode();
if (key == Core::UpdateUrl)
return kUrlUpdateCheck;
@ -116,8 +122,12 @@ QVariant Settings::defaultValue(const QString &key)
if (key == Core::Port)
return 24800;
if (key == Core::ProcessMode)
return defaultProcessMode;
if (key == Core::ProcessMode) {
if (instance()->isNativeMode())
return Settings::ProcessMode::Service;
else
return Settings::ProcessMode::Desktop;
}
if (key == Daemon::LogFile) {
#ifdef Q_OS_WIN
@ -127,9 +137,6 @@ QVariant Settings::defaultValue(const QString &key)
#endif
}
if (key == Daemon::Elevate)
return true;
return QVariant();
}
@ -157,9 +164,16 @@ const QStringList Settings::validKeys()
bool Settings::isWritable()
{
if (instance()->isNativeMode())
return true;
return instance()->m_settings->isWritable();
}
bool Settings::isNativeMode()
{
return instance()->m_settings->format() == QSettings::NativeFormat;
}
const QString Settings::settingsFile()
{
return instance()->m_settings->fileName();
@ -167,6 +181,8 @@ const QString Settings::settingsFile()
const QString Settings::settingsPath()
{
if (instance()->isNativeMode())
return SystemDir;
return QFileInfo(instance()->m_settings->fileName()).absolutePath();
}

View File

@ -41,13 +41,13 @@ public:
struct Core
{
inline static const auto CoreMode = QStringLiteral("core/coreMode");
inline static const auto ElevateMode = QStringLiteral("core/elevateMode");
inline static const auto Interface = QStringLiteral("core/interface");
inline static const auto LastVersion = QStringLiteral("core/lastVersion");
inline static const auto Port = QStringLiteral("core/port");
inline static const auto PreventSleep = QStringLiteral("core/preventSleep");
inline static const auto ProcessMode = QStringLiteral("core/processMode");
inline static const auto ScreenName = QStringLiteral("core/screenName");
inline static const auto StopOnDeskSwitch = QStringLiteral("core/stopOnDeskSwitch");
inline static const auto StartedBefore = QStringLiteral("core/startedBefore");
inline static const auto UpdateUrl = QStringLiteral("core/updateUrl");
};
@ -103,28 +103,6 @@ public:
};
Q_ENUM(ProcessMode)
/**
* @brief The elevate mode tristate determines two behaviors on Windows.
* The matrix for these two behaviors is as follows:
* | sods | elevate |
* |-----------|------------|
* kAutomatic | true | false |
* kAlways | false | true |
* kNever | false | false |
* The first, --stop-on-desk-switch (sods), is passed through the daemon as a
* command line argument to the server/client, and determines if it restarts
* when switching Windows desktops (e.g. when Windows UAC dialog pops up).
* The second, elevate, is passed as a boolean flag to the daemon over IPC,
* and determines whether the server/client should be started with elevated privileges.
*/
enum ElevateMode
{
Automatic = 0,
Always = 1,
Never = 2
};
Q_ENUM(ElevateMode)
enum CoreMode
{
None,
@ -140,6 +118,7 @@ public:
static void restoreDefaultSettings();
static QVariant defaultValue(const QString &key);
static bool isWritable();
static bool isNativeMode();
static const QString settingsFile();
static const QString settingsPath();
static const QString tlsDir();
@ -180,7 +159,6 @@ private:
, Settings::Client::LanguageSync
, Settings::Client::RemoteHost
, Settings::Core::CoreMode
, Settings::Core::ElevateMode
, Settings::Core::Interface
, Settings::Core::LastVersion
, Settings::Core::Port
@ -188,6 +166,7 @@ private:
, Settings::Core::ProcessMode
, Settings::Core::ScreenName
, Settings::Core::StartedBefore
, Settings::Core::StopOnDeskSwitch
, Settings::Core::UpdateUrl
, Settings::Daemon::Command
, Settings::Daemon::Elevate
@ -212,10 +191,4 @@ private:
, Settings::Server::ExternalConfigFile
};
// clang-format on
#ifdef Q_OS_WIN
inline static const auto defaultProcessMode = Settings::ProcessMode::Service;
#else
inline static const auto defaultProcessMode = Settings::ProcessMode::Desktop;
#endif
};

View File

@ -173,10 +173,11 @@ private:
" -1, --no-restart do not try to restart on failure.\n" \
"* --restart restart the server automatically if it fails.\n" \
" -l --log <file> write log messages to file.\n" \
" --enable-drag-drop enable file drag & drop.\n" \
" --enable-crypto enable TLS encryption.\n" \
" --tls-cert specify the path to the TLS certificate file.\n"
#define DRAG_AND_DROP " --enable-drag-drop enable file drag & drop.\n"
#define HELP_COMMON_INFO_2 \
" -h, --help display this help and exit.\n" \
" --version display version information and exit.\n"

View File

@ -88,8 +88,6 @@ bool ArgParser::parseClientArgs(deskflow::ClientArgs &args, int argc, const char
args.m_enableLangSync = true;
} else if (isArg(i, argc, argv, nullptr, "--invert-scroll")) {
args.m_clientScrollDirection = deskflow::ClientScrollDirection::INVERT_SERVER;
} else if (isArg(i, argc, argv, nullptr, "--host")) {
args.m_hostMode = true;
} else if (isArg(i, argc, argv, nullptr, "client")) {
++i;
continue;

View File

@ -113,13 +113,9 @@ void ClientApp::help()
<< " [--yscroll <delta>]"
<< " [--sync-language]"
<< " [--invert-scroll]"
<< " [--host]"
#ifdef WINAPI_XWINDOWS
<< " [--display <display>]"
<< " [--no-xinitthreads]"
#endif
#ifdef WINAPI_LIBEI
<< " [--use-x-window]"
#endif
<< HELP_SYS_ARGS << HELP_COMMON_ARGS << " <server-address>"
<< "\n\n"
@ -128,11 +124,12 @@ void ClientApp::help()
<< " -a, --address <address> local network interface address.\n"
<< HELP_COMMON_INFO_1 << HELP_SYS_INFO << " --yscroll <delta> defines the vertical scrolling delta,\n"
<< " which is 120 by default.\n"
#ifndef WINAPI_XWINDOWS
<< DRAG_AND_DROP "\n"
#endif
<< " --sync-language enable language synchronization.\n"
<< " --invert-scroll invert scroll direction on this\n"
<< " computer.\n"
<< " --host act as a host; invert server/client mode\n"
<< " and listen instead of connecting.\n"
#if WINAPI_XWINDOWS
<< " --display <display> when in X mode, connect to the X server\n"
<< " at <display>.\n"

View File

@ -34,12 +34,6 @@ public:
*/
ClientScrollDirection m_clientScrollDirection = ClientScrollDirection::SERVER;
/**
* @brief m_hostMode - activates host mode.
* Client starts a listener and waits for a server connection.
*/
bool m_hostMode = false;
/**
* @brief m_serverAddress stores deskflow server address
*/

View File

@ -128,11 +128,11 @@ void ServerApp::help()
<< " [--display <display>] [--no-xinitthreads]"
#endif
#ifdef WINAPI_LIBEI
<< " [--no-wayland-ei]"
<< HELP_SYS_ARGS HELP_COMMON_ARGS "\n"
#ifndef WINAPI_XWINDOWS
<< DRAG_AND_DROP "\n"
#endif
<< HELP_SYS_ARGS HELP_COMMON_ARGS "\n\n"
<< "\n"
<< "Start the " << kAppName << " mouse/keyboard sharing server.\n"
<< "\n"
<< " -a, --address <address> listen for clients on the given address.\n"
@ -148,7 +148,6 @@ void ServerApp::help()
<< " --no-xinitthreads do not call XInitThreads()\n"
#endif
<< HELP_SYS_INFO HELP_COMMON_INFO_2 "\n"
<< "* marks defaults.\n"
<< kHelpNoWayland

View File

@ -42,6 +42,18 @@ void clearSettings(bool enableRestart)
qDebug("removing profile dir: %s", qPrintable(profileDir.absolutePath()));
profileDir.removeRecursively();
#ifdef Q_OS_WIN
if (Settings::isNativeMode()) {
// make a new empty portable settings file
if (profileDir.mkpath(Settings::settingsPath())) {
QFile file(Settings::settingsFile());
file.open(QIODevice::WriteOnly);
file.write(" ", 1);
file.close();
}
}
#endif
if (enableRestart) {
qDebug("restarting");
restart();

View File

@ -207,8 +207,14 @@ QString KeySequence::keyToString(int key)
}
// representable in ucs2?
if (key < 0x10000)
return QString("\\u%1").arg(QChar(key).toLower().unicode(), 4, 16, QChar('0'));
if (key < 0x10000) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
const uint16_t keyHex = QChar(key).toLower().unicode();
return QString("\\u%1").arg(keyHex, kStrSize, kBase, kFillChar);
#else
return QString("\\u%1").arg(QChar(key).toLower().unicode(), kStrSize, kBase, kFillChar);
#endif
}
// give up, deskflow probably won't handle this
return "";

View File

@ -58,5 +58,9 @@ private:
int m_Modifiers;
bool m_IsValid;
inline static const int kStrSize = 4;
inline static const int kBase = 16;
inline static const QChar kFillChar = QChar('0');
static QString keyToString(int key);
};

View File

@ -254,9 +254,7 @@ void CoreProcess::startForegroundProcess(const QString &app, const QStringList &
void CoreProcess::startProcessFromDaemon(const QString &app, const QStringList &args)
{
using enum ProcessState;
if (m_processState != Starting) {
if (m_processState != ProcessState::Starting) {
qFatal("core process must be in starting state");
}
@ -264,13 +262,12 @@ void CoreProcess::startProcessFromDaemon(const QString &app, const QStringList &
qInfo("running command: %s", qPrintable(commandQuoted));
auto elevateMode = Settings::value(Settings::Core::ElevateMode).value<Settings::ElevateMode>();
if (!m_daemonIpcClient->sendStartProcess(commandQuoted, elevateMode)) {
if (!m_daemonIpcClient->sendStartProcess(commandQuoted, Settings::value(Settings::Daemon::Elevate).toBool())) {
qCritical("cannot start process, ipc command failed");
return;
}
setProcessState(Started);
setProcessState(ProcessState::Started);
}
void CoreProcess::stopForegroundProcess() const
@ -470,8 +467,7 @@ bool CoreProcess::addGenericArgs(QStringList &args, const ProcessMode processMod
// unnecessary restarts when deskflow was started elevated or
// when it is not allowed to elevate. In these cases restarting
// the server is fruitless.
auto elevateMode = Settings::value(Settings::Core::ElevateMode).value<Settings::ElevateMode>();
if (elevateMode == Settings::ElevateMode::Automatic) {
if (Settings::value(Settings::Core::StopOnDeskSwitch).toBool()) {
args << "--stop-on-desk-switch";
}
#endif

View File

@ -62,12 +62,31 @@ void SettingsDialog::initConnections()
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(ui->groupSecurity, &QGroupBox::toggled, this, &SettingsDialog::updateTlsControlsEnabled);
connect(ui->cbServiceEnabled, &QCheckBox::toggled, this, &SettingsDialog::updateControls);
connect(ui->groupService, &QGroupBox::toggled, this, &SettingsDialog::updateControls);
connect(ui->btnTlsRegenCert, &QPushButton::clicked, this, &SettingsDialog::regenCertificates);
connect(ui->comboTlsKeyLength, &QComboBox::currentIndexChanged, this, &SettingsDialog::updateRequestedKeySize);
connect(ui->btnTlsCertPath, &QPushButton::clicked, this, &SettingsDialog::browseCertificatePath);
connect(ui->btnBrowseLog, &QPushButton::clicked, this, &SettingsDialog::browseLogPath);
connect(ui->cbLogToFile, &QCheckBox::toggled, this, &SettingsDialog::setLogToFile);
connect(ui->cbElevateDaemon, &QCheckBox::toggled, this, [&](bool checked) {
if (!checked)
return;
if (ui->cbStopOnDeskSwitch->isChecked()) {
blockSignals(true);
ui->cbStopOnDeskSwitch->setChecked(false);
blockSignals(false);
}
});
connect(ui->cbStopOnDeskSwitch, &QCheckBox::toggled, this, [&](bool checked) {
if (!checked)
return;
if (ui->cbElevateDaemon->isChecked()) {
blockSignals(true);
ui->cbElevateDaemon->setChecked(false);
blockSignals(false);
}
});
}
void SettingsDialog::regenCertificates()
@ -132,7 +151,8 @@ void SettingsDialog::accept()
Settings::setValue(Settings::Log::Level, ui->comboLogLevel->currentIndex());
Settings::setValue(Settings::Log::ToFile, ui->cbLogToFile->isChecked());
Settings::setValue(Settings::Log::File, ui->lineLogFilename->text());
Settings::setValue(Settings::Core::ElevateMode, ui->comboElevate->currentIndex());
Settings::setValue(Settings::Core::StopOnDeskSwitch, ui->cbStopOnDeskSwitch->isChecked());
Settings::setValue(Settings::Daemon::Elevate, ui->cbElevateDaemon->isChecked());
Settings::setValue(Settings::Gui::Autohide, ui->cbAutoHide->isChecked());
Settings::setValue(Settings::Gui::AutoUpdateCheck, ui->cbAutoUpdate->isChecked());
Settings::setValue(Settings::Core::PreventSleep, ui->cbPreventSleep->isChecked());
@ -146,7 +166,7 @@ void SettingsDialog::accept()
Settings::setValue(Settings::Security::CheckPeers, ui->cbRequireClientCert->isChecked());
Settings::ProcessMode mode;
if (ui->cbServiceEnabled->isChecked())
if (ui->groupService->isChecked())
mode = Settings::ProcessMode::Service;
else
mode = Settings::ProcessMode::Desktop;
@ -167,11 +187,12 @@ void SettingsDialog::loadFromConfig()
ui->cbLanguageSync->setChecked(Settings::value(Settings::Client::LanguageSync).toBool());
ui->cbScrollDirection->setChecked(Settings::value(Settings::Client::InvertScrollDirection).toBool());
ui->cbCloseToTray->setChecked(Settings::value(Settings::Gui::CloseToTray).toBool());
ui->comboElevate->setCurrentIndex(Settings::value(Settings::Core::ElevateMode).toInt());
ui->cbStopOnDeskSwitch->setChecked(Settings::value(Settings::Core::StopOnDeskSwitch).toBool());
ui->cbElevateDaemon->setChecked(Settings::value(Settings::Daemon::Elevate).toBool());
ui->cbAutoUpdate->setChecked(Settings::value(Settings::Gui::AutoUpdateCheck).toBool());
const auto processMode = Settings::value(Settings::Core::ProcessMode).value<Settings::ProcessMode>();
ui->cbServiceEnabled->setChecked(processMode == Settings::ProcessMode::Service);
ui->groupService->setChecked(processMode == Settings::ProcessMode::Service);
if (Settings::value(Settings::Gui::SymbolicTrayIcon).toBool())
ui->rbIconMono->setChecked(true);
@ -248,17 +269,8 @@ void SettingsDialog::updateKeyLengthOnFile(const QString &path)
void SettingsDialog::updateControls()
{
#if defined(Q_OS_WIN)
const auto serviceAvailable = true;
#else
// service not supported on unix yet, so always disable.
const auto serviceAvailable = false;
ui->groupService->setTitle("Service (Windows only)");
#endif
const bool writable = Settings::isWritable();
const bool serviceChecked = ui->cbServiceEnabled->isChecked();
const bool serviceChecked = ui->groupService->isChecked();
const bool logToFile = ui->cbLogToFile->isChecked();
ui->sbPort->setEnabled(writable);
@ -272,8 +284,17 @@ void SettingsDialog::updateControls()
ui->comboTlsKeyLength->setEnabled(writable);
ui->cbCloseToTray->setEnabled(writable);
ui->cbServiceEnabled->setEnabled(writable && serviceAvailable);
ui->widgetElevate->setEnabled(writable && serviceChecked && serviceAvailable);
// Handle enable and disable of service items
if (Settings::isNativeMode()) {
ui->groupService->setEnabled(writable);
ui->cbElevateDaemon->setEnabled(writable && serviceChecked);
ui->cbStopOnDeskSwitch->setEnabled(writable && serviceChecked);
} else if (ui->groupService->isVisibleTo(ui->tabAdvanced)) {
ui->groupService->setVisible(false);
const int bottomMargin = ui->tabAdvanced->layout()->contentsMargins().bottom() +
(ui->tabAdvanced->layout()->spacing() * 2) + ui->groupService->height();
ui->tabAdvanced->layout()->setContentsMargins(9, 9, 9, bottomMargin);
}
ui->cbLanguageSync->setEnabled(writable && isClientMode());
ui->cbScrollDirection->setEnabled(writable && isClientMode());

View File

@ -147,6 +147,9 @@
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="_11">
<item>
<widget class="QWidget" name="widgetTlsCert" native="true">
@ -538,6 +541,9 @@
</item>
<item>
<widget class="QGroupBox" name="groupService">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
@ -545,103 +551,36 @@
</sizepolicy>
</property>
<property name="title">
<string>Service</string>
<string>Use background service (daemon)</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="_6">
<item row="0" column="0">
<widget class="QCheckBox" name="cbServiceEnabled">
<item row="2" column="0">
<widget class="QCheckBox" name="cbStopOnDeskSwitch">
<property name="toolTip">
<string>Whether to launch the server or client process through the Windows background
service or direct from the GUI.</string>
</property>
<property name="whatsThis">
<string>
&lt;p&gt;The background service is only available on Windows.&lt;/p&gt;
&lt;p&gt;The Windows background service is used to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start the server or client automatically when the computer starts.&lt;/li&gt;
&lt;li&gt;Run the server or client in an elevated mode (e.g. on login screen, UAC dialogs, etc).&lt;/li&gt;
&lt;/ul&gt;
</string>
<string/>
</property>
<property name="text">
<string>Use background service (daemon)</string>
<string>Run as system only when at the login screen and UAC prompt</string>
</property>
<property name="autoExclusive">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QWidget" name="widgetElevate" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<widget class="QCheckBox" name="cbElevateDaemon">
<property name="text">
<string>Always run as system (work at login screen and UAC)</string>
</property>
<property name="autoExclusive">
<bool>false</bool>
</property>
<layout class="QHBoxLayout" name="_7">
<item>
<widget class="QLabel" name="lblElevate">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Launch with elevated privileges</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboElevate">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Specify when the Windows background service should run the server or client
process at an elevated privilege level.</string>
</property>
<property name="whatsThis">
<string>
&lt;p&gt;You may want to alter whether or not the privilege level of the server or client
process is automatically changed depending on your use case. In some cases it can help
to diagnose or solve some problems related to elevated processes in Windows.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Automatic: Elevate when the window session changes to secure mode&lt;/li&gt;
&lt;li&gt;Always elevate: Always run in elevated mode (could be unsafe)&lt;/li&gt;
&lt;li&gt;Never elevate: Turn off compatibility with login screen and UAC&lt;/li&gt;
&lt;/ul&gt;
</string>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text">
<string>Automatic (as needed)</string>
</property>
</item>
<item>
<property name="text">
<string>Always elevate</string>
</property>
</item>
<item>
<property name="text">
<string>Never elevate</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
@ -685,8 +624,6 @@
<tabstop>comboLogLevel</tabstop>
<tabstop>lineLogFilename</tabstop>
<tabstop>btnBrowseLog</tabstop>
<tabstop>cbServiceEnabled</tabstop>
<tabstop>comboElevate</tabstop>
</tabstops>
<resources/>
<connections/>

View File

@ -132,13 +132,12 @@ bool DaemonIpcClient::sendLogLevel(const QString &logLevel)
return true;
}
bool DaemonIpcClient::sendStartProcess(const QString &command, Settings::ElevateMode elevateMode)
bool DaemonIpcClient::sendStartProcess(const QString &command, bool elevate)
{
if (!keepAlive())
return false;
using ElevateMode = Settings::ElevateMode;
if (!sendMessage("elevate=" + (elevateMode == ElevateMode::Always ? QStringLiteral("yes") : QStringLiteral("no")))) {
if (!sendMessage("elevate=" + (elevate ? QStringLiteral("yes") : QStringLiteral("no")))) {
return false;
}

View File

@ -22,7 +22,7 @@ public:
explicit DaemonIpcClient(QObject *parent = nullptr);
bool connectToServer();
bool sendLogLevel(const QString &logLevel);
bool sendStartProcess(const QString &command, Settings::ElevateMode elevateMode);
bool sendStartProcess(const QString &command, bool elevate);
bool sendStopProcess();
bool sendClearSettings();
QString requestLogPath();

View File

@ -820,6 +820,8 @@ void EiScreen::handleSystemEvent(const Event &sysevent, void *)
case EI_EVENT_SCROLL_STOP:
case EI_EVENT_SCROLL_CANCEL:
break;
default:
break;
}
ei_event_unref(event);
}

View File

@ -1,107 +0,0 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2012 - 2016 Symless Ltd.
* SPDX-FileCopyrightText: (C) 2011 Nick Bolton
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "base/Log.h"
#include "platform/OSXKeyState.h"
#include "test/mock/deskflow/MockEventQueue.h"
#include "test/mock/deskflow/MockKeyMap.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#define SHIFT_ID_L kKeyShift_L
#define SHIFT_ID_R kKeyShift_R
#define SHIFT_BUTTON 57
#define A_CHAR_ID 0x00000061
#define A_CHAR_BUTTON 001
class OSXKeyStateTests : public ::testing::Test
{
public:
static bool isKeyPressed(const OSXKeyState &keyState, KeyButton button);
};
// fakeAndPoll_shift seems to always fail on osx10.6
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 1060
TEST_F(OSXKeyStateTests, fakeAndPoll_shift)
{
deskflow::KeyMap keyMap;
MockEventQueue eventQueue;
OSXKeyState keyState(&eventQueue, keyMap, {"en"}, true);
keyState.updateKeyMap();
keyState.fakeKeyDown(SHIFT_ID_L, 0, 1, "en");
EXPECT_TRUE(isKeyPressed(keyState, SHIFT_BUTTON));
keyState.fakeKeyUp(1);
EXPECT_TRUE(!isKeyPressed(keyState, SHIFT_BUTTON));
keyState.fakeKeyDown(SHIFT_ID_R, 0, 2, "en");
EXPECT_TRUE(isKeyPressed(keyState, SHIFT_BUTTON));
keyState.fakeKeyUp(2);
EXPECT_TRUE(!isKeyPressed(keyState, SHIFT_BUTTON));
}
TEST_F(OSXKeyStateTests, fakeAndPoll_charKey)
{
deskflow::KeyMap keyMap;
MockEventQueue eventQueue;
OSXKeyState keyState(&eventQueue, keyMap, {"en"}, true);
keyState.updateKeyMap();
keyState.fakeKeyDown(A_CHAR_ID, 0, 1, "en");
EXPECT_TRUE(isKeyPressed(keyState, A_CHAR_BUTTON));
keyState.fakeKeyUp(1);
EXPECT_TRUE(!isKeyPressed(keyState, A_CHAR_BUTTON));
// HACK: delete the key in case it was typed into a text editor.
// we should really set focus to an invisible window.
keyState.fakeKeyDown(kKeyBackSpace, 0, 2, "en");
keyState.fakeKeyUp(2);
}
TEST_F(OSXKeyStateTests, fakeAndPoll_charKeyAndModifier)
{
deskflow::KeyMap keyMap;
MockEventQueue eventQueue;
OSXKeyState keyState(&eventQueue, keyMap, {"en"}, true);
keyState.updateKeyMap();
keyState.fakeKeyDown(A_CHAR_ID, KeyModifierShift, 1, "en");
EXPECT_TRUE(isKeyPressed(keyState, A_CHAR_BUTTON));
keyState.fakeKeyUp(1);
EXPECT_TRUE(!isKeyPressed(keyState, A_CHAR_BUTTON));
// HACK: delete the key in case it was typed into a text editor.
// we should really set focus to an invisible window.
keyState.fakeKeyDown(kKeyBackSpace, 0, 2, "en");
keyState.fakeKeyUp(2);
}
bool OSXKeyStateTests::isKeyPressed(const OSXKeyState &keyState, KeyButton button)
{
// HACK: allow os to realize key state changes.
ARCH->sleep(.2);
IKeyState::KeyButtonSet pressed;
keyState.pollPressedKeys(pressed);
IKeyState::KeyButtonSet::const_iterator it;
for (it = pressed.begin(); it != pressed.end(); ++it) {
LOG((CLOG_DEBUG "checking key %d", *it));
if (*it == button) {
return true;
}
}
return false;
}
#endif

View File

@ -1,131 +0,0 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2012 - 2016 Symless Ltd.
* SPDX-FileCopyrightText: (C) 2011 Nick Bolton
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "platform/XWindowsClipboard.h"
#include "test/shared/undef_x11_macros.h"
#include <gtest/gtest.h>
#include <memory>
// TODO: fix segfault in release mode
#if 0
class XWindowsClipboardTests : public ::testing::Test {
protected:
void SetUp() override {
m_display = XOpenDisplay(nullptr);
int screen = DefaultScreen(m_display);
Window root = XRootWindow(m_display, screen);
XSetWindowAttributes attr;
attr.do_not_propagate_mask = 0;
attr.override_redirect = True;
attr.cursor = Cursor();
m_window = XCreateWindow(
m_display, root, 0, 0, 1, 1, 0, 0, InputOnly, nullptr, 0, &attr);
m_clipboard = std::make_unique<XWindowsClipboard>(m_display, m_window, 0);
m_clipboard->open(0);
m_clipboard->empty();
}
void TearDown() override {
XDestroyWindow(m_display, m_window);
XCloseDisplay(m_display);
}
XWindowsClipboard &getClipboard() { return *m_clipboard; }
private:
Display *m_display;
Window m_window;
std::unique_ptr<XWindowsClipboard> m_clipboard;
};
TEST_F(XWindowsClipboardTests, empty_openCalled_returnsTrue) {
XWindowsClipboard &clipboard = getClipboard();
bool actual = clipboard.empty();
EXPECT_EQ(true, actual);
}
TEST_F(XWindowsClipboardTests, empty_singleFormat_hasReturnsFalse) {
XWindowsClipboard &clipboard = getClipboard();
clipboard.add(XWindowsClipboard::kText, "synergy rocks!");
clipboard.empty();
bool actual = clipboard.has(XWindowsClipboard::kText);
EXPECT_FALSE(actual);
}
TEST_F(XWindowsClipboardTests, add_newValue_valueWasStored) {
XWindowsClipboard &clipboard = getClipboard();
clipboard.add(IClipboard::kText, "synergy rocks!");
std::string actual = clipboard.get(IClipboard::kText);
EXPECT_EQ("synergy rocks!", actual);
}
TEST_F(XWindowsClipboardTests, add_replaceValue_valueWasReplaced) {
XWindowsClipboard &clipboard = getClipboard();
clipboard.add(IClipboard::kText, "synergy rocks!");
clipboard.add(IClipboard::kText, "maxivista sucks"); // haha, just kidding.
std::string actual = clipboard.get(IClipboard::kText);
EXPECT_EQ("maxivista sucks", actual);
}
TEST_F(XWindowsClipboardTests, close_isOpen_noErrors) {
XWindowsClipboard &clipboard = getClipboard();
// clipboard opened in createClipboard()
clipboard.close();
// can't assert anything
}
TEST_F(XWindowsClipboardTests, has_withFormatAdded_returnsTrue) {
XWindowsClipboard &clipboard = getClipboard();
clipboard.add(IClipboard::kText, "synergy rocks!");
bool actual = clipboard.has(IClipboard::kText);
EXPECT_EQ(true, actual);
}
TEST_F(XWindowsClipboardTests, has_withNoFormats_returnsFalse) {
XWindowsClipboard &clipboard = getClipboard();
bool actual = clipboard.has(IClipboard::kText);
EXPECT_FALSE(actual);
}
TEST_F(XWindowsClipboardTests, get_withNoFormats_returnsEmpty) {
XWindowsClipboard &clipboard = getClipboard();
std::string actual = clipboard.get(IClipboard::kText);
EXPECT_EQ("", actual);
}
TEST_F(XWindowsClipboardTests, get_withFormatAdded_returnsExpected) {
XWindowsClipboard &clipboard = getClipboard();
clipboard.add(IClipboard::kText, "synergy rocks!");
std::string actual = clipboard.get(IClipboard::kText);
EXPECT_EQ("synergy rocks!", actual);
}
#endif

View File

@ -27,6 +27,7 @@ int main(int argc, char **argv)
std::filesystem::create_directories(testDir);
Settings::setSettingFile("tmp/test/settings.ini");
Settings::setValue(Settings::Server::ExternalConfig, true);
ExitTimeout exitTimeout(1, "Unit tests");

View File

@ -5,6 +5,7 @@
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "base/Log.h"
#include "platform/OSXKeyState.h"
#include "test/mock/deskflow/MockEventQueue.h"
#include "test/mock/deskflow/MockKeyMap.h"
@ -12,7 +13,19 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
TEST(OSXKeyStateTests, mapModifiersFromOSX_OSXMask_returnDeskflowMask)
#define SHIFT_ID_L kKeyShift_L
#define SHIFT_ID_R kKeyShift_R
#define SHIFT_BUTTON 57
#define A_CHAR_ID 0x00000061
#define A_CHAR_BUTTON 001
class OSXKeyStateTests : public ::testing::Test
{
public:
static bool isKeyPressed(const OSXKeyState &keyState, KeyButton button);
};
TEST_F(OSXKeyStateTests, mapModifiersFromOSX_OSXMask_returnDeskflowMask)
{
deskflow::KeyMap keyMap;
MockEventQueue eventQueue;
@ -44,3 +57,79 @@ TEST(OSXKeyStateTests, mapModifiersFromOSX_OSXMask_returnDeskflowMask)
outMask = keyState.mapModifiersFromOSX(numMask);
EXPECT_EQ(KeyModifierNumLock, outMask);
}
TEST_F(OSXKeyStateTests, fakeAndPoll_shift)
{
deskflow::KeyMap keyMap;
MockEventQueue eventQueue;
OSXKeyState keyState(&eventQueue, keyMap, {"en"}, true);
keyState.updateKeyMap();
keyState.fakeKeyDown(SHIFT_ID_L, 0, 1, "en");
EXPECT_TRUE(isKeyPressed(keyState, SHIFT_BUTTON));
keyState.fakeKeyUp(1);
EXPECT_TRUE(!isKeyPressed(keyState, SHIFT_BUTTON));
keyState.fakeKeyDown(SHIFT_ID_R, 0, 2, "en");
EXPECT_TRUE(isKeyPressed(keyState, SHIFT_BUTTON));
keyState.fakeKeyUp(2);
EXPECT_TRUE(!isKeyPressed(keyState, SHIFT_BUTTON));
}
TEST_F(OSXKeyStateTests, fakeAndPoll_charKey)
{
deskflow::KeyMap keyMap;
MockEventQueue eventQueue;
OSXKeyState keyState(&eventQueue, keyMap, {"en"}, true);
keyState.updateKeyMap();
keyState.fakeKeyDown(A_CHAR_ID, 0, 1, "en");
EXPECT_TRUE(isKeyPressed(keyState, A_CHAR_BUTTON));
keyState.fakeKeyUp(1);
EXPECT_TRUE(!isKeyPressed(keyState, A_CHAR_BUTTON));
// HACK: delete the key in case it was typed into a text editor.
// we should really set focus to an invisible window.
keyState.fakeKeyDown(kKeyBackSpace, 0, 2, "en");
keyState.fakeKeyUp(2);
}
TEST_F(OSXKeyStateTests, fakeAndPoll_charKeyAndModifier)
{
deskflow::KeyMap keyMap;
MockEventQueue eventQueue;
OSXKeyState keyState(&eventQueue, keyMap, {"en"}, true);
keyState.updateKeyMap();
keyState.fakeKeyDown(A_CHAR_ID, KeyModifierShift, 1, "en");
EXPECT_TRUE(isKeyPressed(keyState, A_CHAR_BUTTON));
keyState.fakeKeyUp(1);
EXPECT_TRUE(!isKeyPressed(keyState, A_CHAR_BUTTON));
// HACK: delete the key in case it was typed into a text editor.
// we should really set focus to an invisible window.
keyState.fakeKeyDown(kKeyBackSpace, 0, 2, "en");
keyState.fakeKeyUp(2);
}
bool OSXKeyStateTests::isKeyPressed(const OSXKeyState &keyState, KeyButton button)
{
// HACK: allow os to realize key state changes.
ARCH->sleep(.2);
IKeyState::KeyButtonSet pressed;
keyState.pollPressedKeys(pressed);
IKeyState::KeyButtonSet::const_iterator it;
for (it = pressed.begin(); it != pressed.end(); ++it) {
LOG((CLOG_DEBUG "checking key %d", *it));
if (*it == button) {
return true;
}
}
return false;
}

View File

@ -1,6 +1,7 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2024 Symless Ltd.
* SPDX-FileCopyrightText: (C) 2012 - 2016, 2024 Symless Ltd.
* SPDX-FileCopyrightText: (C) 2011 Nick Bolton
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
@ -30,3 +31,121 @@ TEST(XWindowsClipboardTests_CICCCMGetClipboard, ctor_default_errorNone)
EXPECT_EQ(None, clipboard.error());
}
// TODO: fix segfault in release mode
#if 0
class XWindowsClipboardTests : public ::testing::Test {
protected:
void SetUp() override {
m_display = XOpenDisplay(nullptr);
int screen = DefaultScreen(m_display);
Window root = XRootWindow(m_display, screen);
XSetWindowAttributes attr;
attr.do_not_propagate_mask = 0;
attr.override_redirect = True;
attr.cursor = Cursor();
m_window = XCreateWindow(
m_display, root, 0, 0, 1, 1, 0, 0, InputOnly, nullptr, 0, &attr);
m_clipboard = std::make_unique<XWindowsClipboard>(m_display, m_window, 0);
m_clipboard->open(0);
m_clipboard->empty();
}
void TearDown() override {
XDestroyWindow(m_display, m_window);
XCloseDisplay(m_display);
}
XWindowsClipboard &getClipboard() { return *m_clipboard; }
private:
Display *m_display;
Window m_window;
std::unique_ptr<XWindowsClipboard> m_clipboard;
};
TEST_F(XWindowsClipboardTests, empty_openCalled_returnsTrue) {
XWindowsClipboard &clipboard = getClipboard();
bool actual = clipboard.empty();
EXPECT_EQ(true, actual);
}
TEST_F(XWindowsClipboardTests, empty_singleFormat_hasReturnsFalse) {
XWindowsClipboard &clipboard = getClipboard();
clipboard.add(XWindowsClipboard::kText, "synergy rocks!");
clipboard.empty();
bool actual = clipboard.has(XWindowsClipboard::kText);
EXPECT_FALSE(actual);
}
TEST_F(XWindowsClipboardTests, add_newValue_valueWasStored) {
XWindowsClipboard &clipboard = getClipboard();
clipboard.add(IClipboard::kText, "synergy rocks!");
std::string actual = clipboard.get(IClipboard::kText);
EXPECT_EQ("synergy rocks!", actual);
}
TEST_F(XWindowsClipboardTests, add_replaceValue_valueWasReplaced) {
XWindowsClipboard &clipboard = getClipboard();
clipboard.add(IClipboard::kText, "synergy rocks!");
clipboard.add(IClipboard::kText, "maxivista sucks"); // haha, just kidding.
std::string actual = clipboard.get(IClipboard::kText);
EXPECT_EQ("maxivista sucks", actual);
}
TEST_F(XWindowsClipboardTests, close_isOpen_noErrors) {
XWindowsClipboard &clipboard = getClipboard();
// clipboard opened in createClipboard()
clipboard.close();
// can't assert anything
}
TEST_F(XWindowsClipboardTests, has_withFormatAdded_returnsTrue) {
XWindowsClipboard &clipboard = getClipboard();
clipboard.add(IClipboard::kText, "synergy rocks!");
bool actual = clipboard.has(IClipboard::kText);
EXPECT_EQ(true, actual);
}
TEST_F(XWindowsClipboardTests, has_withNoFormats_returnsFalse) {
XWindowsClipboard &clipboard = getClipboard();
bool actual = clipboard.has(IClipboard::kText);
EXPECT_FALSE(actual);
}
TEST_F(XWindowsClipboardTests, get_withNoFormats_returnsEmpty) {
XWindowsClipboard &clipboard = getClipboard();
std::string actual = clipboard.get(IClipboard::kText);
EXPECT_EQ("", actual);
}
TEST_F(XWindowsClipboardTests, get_withFormatAdded_returnsExpected) {
XWindowsClipboard &clipboard = getClipboard();
clipboard.add(IClipboard::kText, "synergy rocks!");
std::string actual = clipboard.get(IClipboard::kText);
EXPECT_EQ("synergy rocks!", actual);
}
#endif