88 Commits

Author SHA1 Message Date
abaf233a07 fix: adjust command line size to allow expanding
Some checks are pending
CodeQL Analysis / codeql (push) Waiting to run
Continuous Integration / ci-passed (push) Blocked by required conditions
Continuous Integration / test-results (push) Blocked by required conditions
Continuous Integration / lint-reuse (push) Waiting to run
Continuous Integration / lint-clang (push) Blocked by required conditions
Continuous Integration / analyze-valgrind (push) Blocked by required conditions
Continuous Integration / windows-2022-x64 (push) Blocked by required conditions
Continuous Integration / windows-2022-arm64 (push) Blocked by required conditions
Continuous Integration / macos-arm64 (push) Blocked by required conditions
Continuous Integration / macos-x64 (push) Blocked by required conditions
Continuous Integration / archlinux-x86_64 (push) Blocked by required conditions
Continuous Integration / debian-arm64 (push) Blocked by required conditions
Continuous Integration / debian-x86_64 (push) Blocked by required conditions
Continuous Integration / debian-testing-arm64 (push) Blocked by required conditions
Continuous Integration / debian-testing-x86_64 (push) Blocked by required conditions
Continuous Integration / fedora-42-arm64 (push) Blocked by required conditions
Continuous Integration / fedora-42-x86_64 (push) Blocked by required conditions
Continuous Integration / fedora-43-arm64 (push) Blocked by required conditions
Continuous Integration / fedora-43-x86_64 (push) Blocked by required conditions
Continuous Integration / opensuse-arm64 (push) Blocked by required conditions
Continuous Integration / opensuse-x86_64 (push) Blocked by required conditions
Continuous Integration / ubuntu-25.10-arm64 (push) Blocked by required conditions
Continuous Integration / ubuntu-25.10-x86_64 (push) Blocked by required conditions
Continuous Integration / ubuntu-26.04-arm64 (push) Blocked by required conditions
Continuous Integration / ubuntu-26.04-x86_64 (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
SonarCloud Analysis / sonar (push) Waiting to run
2026-03-19 10:41:22 +00:00
b251c98a4e fix: kill core orphan on gui death (linux) 2026-03-16 09:38:14 -04:00
23c8496e24 feat: allow users to run a script on entry or exit of a screen 2026-03-16 12:33:45 +00:00
18220a7847 chore: Fix Typo 2026-03-16 12:33:45 +00:00
a26698c377 repo: add an initial code of conduct 2026-03-16 07:28:03 -04:00
ac8cc42b7a fix: retry OpenClipboard() on Windows
OpenClipboard() can fail transiently when another process holds the
clipboard mutex. Add a retry loop (5 attempts, 5ms delay) so that
Deskflow handles brief contention gracefully instead of immediately
failing.
2026-03-12 08:19:43 -04:00
7126f31d3f fix: do not reset network if old interface is missing
fixes: #9552
2026-03-12 10:24:20 +00:00
44db410dad docs: rm copilotnote 2026-03-11 07:48:51 -04:00
bc7b1082e7 doc: fix ssl exception link 2026-03-11 07:48:51 -04:00
ea0bf9d56d docs: Update README.md include Copilot note 2026-03-08 18:38:07 +00:00
6657bdd9b1 build: not using cache to store global state for macOS signing 2026-03-08 18:21:34 +00:00
11871d343d refactor: check for not mac not win in placeof x or wayland in some cases 2026-03-06 08:57:31 -05:00
12cf55d4fc build: Replace WINAPI_CARBON with Q_OS_MAC 2026-03-06 08:57:31 -05:00
e4cf1392c9 build: Replace WINAPI_MSWINDOWS with Q_OS_WIN 2026-03-06 08:57:31 -05:00
f2a54f4af0 buid: replace NIH SYSAPI_WIN32 with Q_OS_WIN 2026-03-06 08:57:31 -05:00
fce1a37e97 build: remove SYSAPI_UNIX define 2026-03-06 08:57:31 -05:00
90e79dad75 refactor: SettingsDialog update ux for initial window state 2026-03-06 13:38:30 +00:00
8f8874370e refactor: SettingsDialog adjust wording of close to tray option 2026-03-06 13:38:30 +00:00
3d67865ffc refactor: SettingsDialog, new Window Behavior tab 2026-03-06 13:38:30 +00:00
22a303842e refactor: Settings Dialog Vertically center contents 2026-03-06 13:38:30 +00:00
c20671f1db build: ensuring macOS codesign is triggered for changes in core 2026-03-05 15:22:46 -05:00
844dac439a ci: macos capture build package attempt do not let it write to stdout 2026-03-04 07:58:39 -05:00
5e781e6095 i18n(es): fixing hostname field translation 2026-03-02 08:11:52 -05:00
2b379b31cd refactor: SettingsDialog adjust tab layout to have consistant top and bottom margins 2026-03-02 06:31:21 -05:00
8bb23179c4 refactor: SettingsDialog, adjust tray icon style layout 2026-03-02 06:31:21 -05:00
a8588ab507 refactor: SettingsDialog, Fix missing spacing in language combobox with some widget sets 2026-03-02 06:31:21 -05:00
c80cd56f27 fix: properly handling infinite timeout for macOS on EventQueue 2026-02-27 19:47:59 -05:00
7743d9008d refactor: checking for server config files read permissions upon start 2026-02-27 16:21:40 -05:00
2ecf91e50a refactor: adding function to check if server config file have the needed read permissions 2026-02-27 16:21:40 -05:00
d8c7367900 fix: visual error if core start emit errors that modify start/restart visibility 2026-02-27 16:21:40 -05:00
8a53b4bc8e ci: split build and package for mac os letting us retry dmg creation upto 5 times 2026-02-27 10:30:10 -05:00
fc1f3b0142 fix: macOS flicker when menu is first clicked if the app is set to autohide on startup
fixes: #9523
2026-02-27 12:25:14 +00:00
814d63af28 refactor: MainWindow::EvenFilter set keyEvent in if only 2026-02-27 12:25:14 +00:00
a9bfcf3c81 refactor: SettingsDialog remove minimum size from vertial spacer on network page 2026-02-26 13:10:24 +00:00
fb8104ff86 fix: security icon not being set for clients
fixes: #9524
2026-02-26 13:10:24 +00:00
a0a28e7a94 chore: remove untriggerable wayland warnings 2026-02-26 12:52:03 +00:00
9188d4b1c1 build: require libportal 0.9.1 2026-02-26 12:52:03 +00:00
d392547fd8 fix: ClientProxyUnknownFailure should also remove its client socket 2026-02-26 12:02:28 +00:00
220bf3178c fix: correctly deletes sockets that failed to become clients 2026-02-26 12:02:28 +00:00
c0692b6fc7 refactor: remove messages::showClientError, fix mainwindow calling showandactive for non error messages 2026-02-24 17:06:17 +00:00
47ed13868f refactor: ScreenSetupView remove unneeded include 2026-02-24 16:10:01 +00:00
09d4b1a27a refactor: Forward Declare more in gui/validators classes 2026-02-24 16:10:01 +00:00
4e641adae9 refactor: ComputerNameValidator define regex static where used 2026-02-24 16:10:01 +00:00
568f5c7fc5 refactor: SpacesValidator, use QChar::Space 2026-02-24 16:10:01 +00:00
a0ac24a42c refactor: NewScreenWidget::mousePressEvent remove remove unused arg 2026-02-24 16:10:01 +00:00
f69bd5601c refactor: make NetworkMonitor::validAddresses static 2026-02-24 16:10:01 +00:00
37728cc7a3 refactor: Settings::cleanComputerName do not store empty QString for nothing 2026-02-24 16:10:01 +00:00
b4dbce385c refactor: Mainwindow: prevent core from running while editing the host name 2026-02-24 15:57:22 +00:00
9aeb04b4a6 refactor: MainWindow do not allow the computer name to be edited if the core is running 2026-02-24 15:57:22 +00:00
3db4c60155 refactor: MainWindow add canRunCore method 2026-02-24 15:57:22 +00:00
c37c7e2354 ci: release dev docs from build/docs 2026-02-23 17:26:19 -05:00
4314159a0b Revert "chore: move LICENSE -> docs/LICENSE"
This reverts commit dd55247380.
2026-02-23 17:26:19 -05:00
7f4b942817 ci: install doxygen with depends everywhere 2026-02-23 17:02:05 -05:00
c2342d5124 docs: Included a generic Readme for the root of the documents 2026-02-23 17:02:05 -05:00
dd55247380 chore: move LICENSE -> docs/LICENSE 2026-02-23 17:02:05 -05:00
fe9407ca33 refactor: move SECURITY.md -> docs/Security.md, install it to CMAKE_INSTALL_DOCDIR 2026-02-23 17:02:05 -05:00
9376c68739 chore: move CONTRIBUTING.md => .github/CONTRIBUTING.md 2026-02-23 17:02:05 -05:00
439541fac1 chore: move README -> .github/README 2026-02-23 17:02:05 -05:00
dfae2aaf20 chore: move doc dir to docs 2026-02-23 17:02:05 -05:00
5ea8874583 refactor: move all status bar items to new status bar widget 2026-02-23 21:48:17 +00:00
07de9efe7c refactor: CoreProcess add signal for when the security level text changes 2026-02-23 21:48:17 +00:00
c791135160 refactor: move CoreProcess::ProcessState => common/Enums deskflow::core::ProcessState 2026-02-23 21:48:17 +00:00
79fdf3a1f6 refactor: mv CoreProcess::ConnectionState -> common/Enums deskflow::core::ConnectionState 2026-02-23 21:48:17 +00:00
76353eceb3 refactor: NetworkMonitor::validAddresses filter our based on humanreadable and add vEth for windows wsl to ignore list 2026-02-23 21:24:56 +00:00
fb60189078 feat: NetworkMointor::validAddresses, return the useable ip4 and ip6 address 2026-02-23 21:24:56 +00:00
3c140ed479 chore: rename NetworkMonitor::getAvalibleIpv4Addresses => NetworkMonitor::validAddresses 2026-02-23 21:24:56 +00:00
5b4dfefcf7 doc: document x / y scroll scale and invert 2026-02-23 21:24:56 +00:00
033e5530c3 refactor: remove the generic connection and hostname failures dialog
this dialog has been the cause of alot of confusion,
it will almost always show up incorrectly at least once when connecting a new client
if you accept the client side dialog before adding the screen to the server
fixes: #9497
2026-02-23 21:03:48 +00:00
727d6ff166 refactor: moving ServerApp::currentConfig to Settings::serverConfigFile 2026-02-23 07:15:49 -05:00
aa54bd329f fix: add Daemon::LogLevel to the list of valid config keys 2026-02-23 07:15:49 -05:00
692f43d8af refactor: SettingsDialog hide Advanced tab for portable version on windows 2026-02-21 07:56:47 +00:00
263a23fcaa fix: Settings dialog blocking return to Automatic
fixes:# 9501
2026-02-21 07:56:47 +00:00
bde89d4026 ci: Use Qt 6.10.2 on mac arm and windows builds 2026-02-20 14:38:42 +00:00
b3cecdde7f ci: remove unused add-kitware-repo action 2026-02-20 14:38:42 +00:00
5534232a1d i18n(ko): Update ko translation by bimppap
new string from https://github.com/deskflow/deskflow/pull/9478\#pullrequestreview-3829032105
2026-02-20 14:24:04 +00:00
4c7f546c60 fix: XWindowScreen, do not send a scroll delta for 0
fixes: #9477
2026-02-18 18:11:38 +00:00
1ca8695659 fix: Read fingerprint data before applying the initial config to prevent the fingerprint from being empty making the button hidden
fixes: #9483
2026-02-18 15:26:17 +00:00
e7363aaf8e ci: remove winget publish from our release
our last release was botched due to our repo being out of sync within a ~12hours of release a winget bot had already
completed the update and caused our future attempts with out now synced repo to fail. Lets lets winget bot do the winget release
2026-02-18 11:34:45 +00:00
6ccef50b7c refactor: SettingsDialog, hide advanced tab when running on macos 2026-02-17 13:41:47 +00:00
d7fb9bc6d2 refactor: SettingsDialog, move language to General 2026-02-17 13:41:47 +00:00
41197334f0 refactor: SettingsDialog move network items to own tab 2026-02-17 13:41:47 +00:00
74c25fdff6 refactor: SettingsDialog rename regular tab to general 2026-02-17 13:41:47 +00:00
f6348736e8 refactor: SettingsDialog, move Log settings to own tab 2026-02-17 13:41:47 +00:00
208556b5aa refactor: SettingsDialog, add reset to default button 2026-02-17 13:41:47 +00:00
58963de10f refactor: SettingsDialog add a reset button for settings, and tooltips for the buttonbox buttons 2026-02-17 13:41:47 +00:00
457c31fbaf refactor: SettingsDialog only allow save if modified 2026-02-17 13:41:47 +00:00
690d6a67ef refactor: SettingsDialog, no longer need coreProccess in the settings dialog 2026-02-17 13:41:47 +00:00
094cbadf91 i18n(ja): update Japanese(ja) translation
Check and update unfinished translation
2026-02-17 04:34:25 +00:00
99 changed files with 2665 additions and 1930 deletions

18
.github/CODE_OF_CONDUCT.md vendored Normal file
View File

@ -0,0 +1,18 @@
# Deskflow Code of Conduct
## Our Pledge
We want the Deskflow community to be one where everyone can work together. We pledge to keep our community focused on the project and the code around the project.
## Community Standards
* Keep interactions respectful and focused on the project
* Contributions are expected to follow the [contribution guide](https://github.com/deskflow/deskflow/wiki/Contributing).
## Enforcement
Enforcement will be done at the descression of the Deskflow Moderators.
1. Warning
2. Temporary ban
3. Permanent banning

View File

@ -184,4 +184,4 @@ Deskflow is made by possible by these contributors.
## License ## License
This project is licensed under [GPL-2.0](LICENSE) with an [OpenSSL exception](LICENSES/LicenseRef-OpenSSL-Exception.txt). This project is licensed under [GPL-2.0](LICENSE) with an [OpenSSL exception](../LICENSES/LicenseRef-OpenSSL-Exception.txt).

View File

@ -1,34 +0,0 @@
# SPDX-FileCopyrightText: 2024 Chris Rizzitello <sithlord48@gmail.com>
# SPDX-License-Identifier: MIT
name: "Add Kitware repo"
description: "Add Kitware repo for Debian-like distros"
inputs:
distro:
description: "Ubuntu codename, Kitware uses: noble, jammy, focal"
required: true
runs:
using: "composite"
steps:
# This mirrors instructions at https://apt.kitware.com
- name: Add repo
run: |
apt update -y -qqq
apt install ca-certificates gpg wget -y -qqq
wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null \
| gpg --dearmor - \
> /usr/share/keyrings/kitware-archive-keyring.gpg
echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ ${{ inputs.distro }} main' \
> /etc/apt/sources.list.d/kitware.list
apt update -y -qqq
env:
# Prevent apt prompting for input.
DEBIAN_FRONTEND: noninteractive
shell: bash

View File

@ -30,7 +30,7 @@ runs:
if: ${{ runner.os != 'Windows' }} if: ${{ runner.os != 'Windows' }}
run: | run: |
if [ "$RUNNER_OS" == "macOS" ]; then if [ "$RUNNER_OS" == "macOS" ]; then
brew install googletest openssl --quiet brew install googletest openssl doxygen --quiet
elif [ "$RUNNER_OS" == "Linux" ]; then elif [ "$RUNNER_OS" == "Linux" ]; then
if [ ${{inputs.like}} == "debian" ]; then if [ ${{inputs.like}} == "debian" ]; then
apt update -qqq > /dev/null apt update -qqq > /dev/null
@ -38,17 +38,17 @@ runs:
xorg-dev libx11-dev libxtst-dev libssl-dev \ xorg-dev libx11-dev libxtst-dev libssl-dev \
libglib2.0-dev libxkbfile-dev qt6-base-dev qt6-tools-dev \ libglib2.0-dev libxkbfile-dev qt6-base-dev qt6-tools-dev \
libgtk-3-dev libgtest-dev libgmock-dev \ libgtk-3-dev libgtest-dev libgmock-dev \
libei-dev libportal-dev help2man -y >/dev/null libei-dev libportal-dev help2man doxygen -y >/dev/null
elif [ ${{inputs.like}} == "fedora" ]; then elif [ ${{inputs.like}} == "fedora" ]; then
dnf install -y cmake make ninja-build gcc-c++ rpm-build openssl-devel \ 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 \ glib2-devel libXtst-devel libxkbfile-devel qt6-qtbase-devel qt6-qttools-devel \
gtk3-devel gtest-devel gmock-devel libei-devel libportal-devel help2man gtk3-devel gtest-devel gmock-devel libei-devel libportal-devel help2man doxygen
elif [ ${{inputs.like}} == "suse" ]; then elif [ ${{inputs.like}} == "suse" ]; then
zypper refresh zypper refresh
zypper install -y --force-resolution \ zypper install -y --force-resolution \
cmake make ninja gcc-c++ rpm-build libopenssl-devel \ cmake make ninja gcc-c++ rpm-build libopenssl-devel \
glib2-devel libXtst-devel libxkbfile-devel qt6-base-devel qt6-tools-devel \ glib2-devel libXtst-devel libxkbfile-devel qt6-base-devel qt6-tools-devel \
qt6-linguist-devel gtk3-devel \ qt6-linguist-devel gtk3-devel doxygen \
googletest-devel googlemock-devel libei-devel libportal-devel help2man googletest-devel googlemock-devel libei-devel libportal-devel help2man
elif [ ${{ inputs.like }} == "arch" ]; then elif [ ${{ inputs.like }} == "arch" ]; then
pacman -Syu --noconfirm base-devel cmake ninja \ pacman -Syu --noconfirm base-devel cmake ninja \
@ -83,6 +83,22 @@ runs:
token: ${{ github.token }} token: ${{ github.token }}
revision: master revision: master
- name: Cache Chocolatey
id: cache-choco
if: (runner.os == 'Windows')
uses: actions/cache@v4
with:
path: |
C:/ProgramData/chocolatey/bin/
C:/ProgramData/chocolatey/lib/doxygen.install
C:/Program*/doxygen/
key: cache-chocolatey${{ matrix.config.arch }}-doxygen
- name: Install doxygen (windows)
if: ((runner.os == 'Windows') && (steps.cache-choco.outputs.cache-hit != 'true'))
shell: bash
run: choco install doxygen.install
- name: Install Wix - name: Install Wix
if: ${{ runner.os == 'Windows' }} if: ${{ runner.os == 'Windows' }}
run: | run: |
@ -91,3 +107,4 @@ runs:
wix extension add --global WixToolset.Util.wixext/5.0.2 wix extension add --global WixToolset.Util.wixext/5.0.2
wix extension add --global WixToolset.Firewall.wixext/5.0.2 wix extension add --global WixToolset.Firewall.wixext/5.0.2
shell: pwsh shell: pwsh

View File

@ -1,33 +0,0 @@
name: Winget Publish
description: A composite action to publish packages to the Windows Package Manager (Winget) repository
inputs:
release-version:
description: "Version to publish to Winget package manager (without 'v' prefix)"
required: true
token:
description: "GitHub token with public read permissions on the source repo"
required: true
runs:
using: "composite"
steps:
- name: Submit package to Windows Package Manager Community Repository
if: ${{ runner.os == 'Windows' }}
env:
GITHUB_TOKEN: ${{ inputs.token }}
run: |
# Download latest wingetcreate
Invoke-WebRequest https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
$packageId = "Deskflow.Deskflow"
$x64Url = "https://github.com/deskflow/deskflow/releases/download/v${{ inputs.release-version }}/deskflow-${{ inputs.release-version }}-win-x64.msi"
$arm64Url = "https://github.com/deskflow/deskflow/releases/download/v${{ inputs.release-version }}/deskflow-${{ inputs.release-version }}-win-arm64.msi"
# Submit package update
.\wingetcreate.exe update "$packageId" `
--version "${{ inputs.release-version }}" `
--urls "$x64Url|x64" "$arm64Url|arm64"`
--submit `
--token "${{ inputs.token }}"
shell: pwsh

View File

@ -99,7 +99,7 @@ jobs:
config-args: "-G Ninja" config-args: "-G Ninja"
vcpkg-triplet: x64-windows-release vcpkg-triplet: x64-windows-release
arch: "amd64" arch: "amd64"
qt-version: 6.10.1 qt-version: 6.10.2
- name: "windows-2022-arm64" - name: "windows-2022-arm64"
runs-on: "windows-11-arm" runs-on: "windows-11-arm"
@ -107,12 +107,12 @@ jobs:
config-args: "-G Ninja" config-args: "-G Ninja"
vcpkg-triplet: arm64-windows vcpkg-triplet: arm64-windows
arch: "arm64" arch: "arm64"
qt-version: 6.10.1 qt-version: 6.10.2
- name: "macos-arm64" - name: "macos-arm64"
runs-on: macos-15 runs-on: macos-15
timeout: 10 timeout: 10
qt-version: 6.10.1 qt-version: 6.10.2
config-args: '-DCMAKE_OSX_ARCHITECTURES="arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET=14 -DCMAKE_OSX_SYSROOT=/Applications/Xcode_16.4.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk' config-args: '-DCMAKE_OSX_ARCHITECTURES="arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET=14 -DCMAKE_OSX_SYSROOT=/Applications/Xcode_16.4.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk'
- name: "macos-x64" - name: "macos-x64"
@ -268,6 +268,11 @@ jobs:
- name: Get version - name: Get version
uses: ./.github/actions/get-version uses: ./.github/actions/get-version
- name: Update Windows Paths
if: (runner.os == 'Windows')
shell: pwsh
run: echo "C:\Program Files\doxygen\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Configure - name: Configure
run: ${{env.CMAKE_CONFIGURE}} ${{ matrix.target.config-args }} ${{ steps.get-deps.outputs.vcpkg-cmake-config }} -DPACKAGE_VERSION_LABEL="${{env.DESKFLOW_PACKAGE_VERSION}}" run: ${{env.CMAKE_CONFIGURE}} ${{ matrix.target.config-args }} ${{ steps.get-deps.outputs.vcpkg-cmake-config }} -DPACKAGE_VERSION_LABEL="${{env.DESKFLOW_PACKAGE_VERSION}}"
@ -275,7 +280,21 @@ jobs:
shell: bash shell: bash
run: | run: |
if [[ "${{matrix.target.like}}" != "arch" ]]; then if [[ "${{matrix.target.like}}" != "arch" ]]; then
cmake --build build --config Release -j8 --target package if [ "$RUNNER_OS" != "macOS" ]; then
cmake --build build --config Release -j8 --target package
else
cmake --build build --config Release -j8
for i in $(seq 1 5); do
cmake --build build --config Release -j8 --target package 2>&1
if [ $? -eq 0 ]; then
echo "Package successful"
break
else
echo "Package attempt $i failed"
sleep 1
fi
done
fi
else else
cmake --build build --config Release -j8 cmake --build build --config Release -j8
useradd -m build useradd -m build
@ -312,7 +331,7 @@ jobs:
uses: JamesIves/github-pages-deploy-action@v4.7.3 uses: JamesIves/github-pages-deploy-action@v4.7.3
with: with:
branch: gh-pages branch: gh-pages
folder: build/doc/dev/html folder: build/docs/dev/html
- name: Upload - name: Upload
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@ -347,7 +366,7 @@ jobs:
run: | run: |
pkg install -y cmake ninja gmake gcc12 openssl glib \ pkg install -y cmake ninja gmake gcc12 openssl glib \
libX11 libXtst libxkbfile qt6-base qt6-tools gtk3 \ libX11 libXtst libxkbfile qt6-base qt6-tools gtk3 \
googletest pkgconf libei libportal googletest pkgconf libei libportal doxygen
${{env.CMAKE_CONFIGURE}} -G Ninja ${{env.CMAKE_CONFIGURE}} -G Ninja
cmake --build build -j16 cmake --build build -j16
# Integration tests are flakey by nature, make them optional. # Integration tests are flakey by nature, make them optional.
@ -457,20 +476,3 @@ jobs:
-H "X-GitHub-Api-Version: 2022-11-28" \ -H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/deskflow/homebrew-tap/dispatches \ https://api.github.com/repos/deskflow/homebrew-tap/dispatches \
-d '{"event_type":"update_tap"}' -d '{"event_type":"update_tap"}'
winget-publish:
needs: release
if: contains(github.ref, 'tags/v')
runs-on: windows-latest
steps:
- name: Fancy Checkout
uses: sithlord48/fancy-checkout@v2
- name: Get version
uses: ./.github/actions/get-version
- name: Submit
uses: ./.github/actions/winget-publish
with:
release-version: ${{env.DESKFLOW_PACKAGE_VERSION}}
token: ${{ secrets.WINGET_DEPLOY_TOKEN }}

View File

@ -99,11 +99,10 @@ message(STATUS "Building ${CMAKE_PROJECT_NAME}-${CMAKE_PROJECT_VERSION}")
# Set lib versions # Set lib versions
set(REQUIRED_OPENSSL_VERSION 3.0) set(REQUIRED_OPENSSL_VERSION 3.0)
set(REQUIRED_LIBEI_VERSION 1.3) set(REQUIRED_LIBEI_VERSION 1.3)
set(REQUIRED_LIBPORTAL_VERSION 0.8) set(REQUIRED_LIBPORTAL_VERSION 0.9.1)
set(REQUIRED_QT_VERSION 6.7.0) set(REQUIRED_QT_VERSION 6.7.0)
if (WIN32) if (WIN32)
add_definitions(-DSYSAPI_WIN32 -DWINAPI_MSWINDOWS)
# VSCMD_ARG_TGT_ARCH is set on CI # VSCMD_ARG_TGT_ARCH is set on CI
if ("$ENV{VSCMD_ARG_TGT_ARCH}" STREQUAL "") if ("$ENV{VSCMD_ARG_TGT_ARCH}" STREQUAL "")
# NOT on CI # NOT on CI
@ -162,11 +161,16 @@ endif()
include(cmake/Libraries.cmake) include(cmake/Libraries.cmake)
configure_libs() configure_libs()
if(BUILD_OSX_BUNDLE AND APPLE_CODESIGN_DEV)
include(cmake/MacCodesign.cmake)
endif()
# setup install paths # setup install paths
include(GNUInstallDirs) include(GNUInstallDirs)
if (WIN32) if (WIN32)
set(CMAKE_INSTALL_BINDIR .) set(CMAKE_INSTALL_BINDIR .)
set(CMAKE_INSTALL_LIBDIR .) set(CMAKE_INSTALL_LIBDIR .)
set(CMAKE_INSTALL_DOCDIR docs)
set(CMAKE_INSTALL_LICENSE_DIR .) set(CMAKE_INSTALL_LICENSE_DIR .)
set(CMAKE_INSTALL_I18N_DIR translations) set(CMAKE_INSTALL_I18N_DIR translations)
elseif(BUILD_OSX_BUNDLE) elseif(BUILD_OSX_BUNDLE)
@ -177,7 +181,7 @@ else()
set(CMAKE_INSTALL_I18N_DIR ${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}/translations) set(CMAKE_INSTALL_I18N_DIR ${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}/translations)
endif() endif()
add_subdirectory(doc) add_subdirectory(docs)
# build translations before source, I18N unit tests fail if they are missing # build translations before source, I18N unit tests fail if they are missing
add_subdirectory(translations) add_subdirectory(translations)

View File

@ -17,7 +17,7 @@ path = [
, "sonar-project.properties" , "sonar-project.properties"
, "cmake/vcpkg.json.in" , "cmake/vcpkg.json.in"
, "**/*.md" , "**/*.md"
, "doc/**" , "docs/**"
, "deploy/linux/flatpak/**" , "deploy/linux/flatpak/**"
, "deploy/linux/org.deskflow.deskflow.metainfo.xml" , "deploy/linux/org.deskflow.deskflow.metainfo.xml"
, "deploy/windows/wix-patch.xml.in" , "deploy/windows/wix-patch.xml.in"

View File

@ -141,8 +141,6 @@ macro(configure_unix_libs)
${lib_ScreenSaver} ${lib_IOKit} ${lib_ApplicationServices} ${lib_ScreenSaver} ${lib_IOKit} ${lib_ApplicationServices}
${lib_Foundation} ${lib_Carbon} ${lib_UserNotifications} ${lib_Foundation} ${lib_Carbon} ${lib_UserNotifications}
) )
add_definitions(-DWINAPI_CARBON=1)
else() else()
if (BUILD_X11_SUPPORT) if (BUILD_X11_SUPPORT)
@ -163,9 +161,6 @@ macro(configure_unix_libs)
message(WARNING "pkg-config not found, skipping wayland libraries") message(WARNING "pkg-config not found, skipping wayland libraries")
endif() endif()
endif() endif()
add_definitions(-DSYSAPI_UNIX=1)
endmacro() endmacro()
# #

51
cmake/MacCodesign.cmake Normal file
View File

@ -0,0 +1,51 @@
# SPDX-FileCopyrightText: (C) 2025-2026 Deskflow Contributors
# SPDX-License-Identifier: MIT
# Warning: Do not use for CI/production, as the `entitlements-dev.plist` file adds special
# entitlements that are only appropriate for local development.
#
# macOS made TCC stricter so that if you don't sign your local dev builds properly, macOS will
# nag you to remove and re-approve the app every time you make a change to the binary which is
# extremely annoying during development.
#
# If you were to use ad-hoc signing (i.e. not specify a certificate), TCC would still nag you
# because the binary identity is anchored not on the app ID, but on the CD hash (which changes
# based on the binary contents).
#
# To use, simply generate a personal certificate for free with Xcode and pass the ID to CMake.
# Full instructions are in the docs.
function(configure_mac_codesign target)
set_property(GLOBAL APPEND PROPERTY _MAC_CODESIGN_DEPENDS $<TARGET_FILE:${target}>)
get_property(deferred GLOBAL PROPERTY _MAC_CODESIGN_DEFERRED)
if(NOT deferred)
set_property(GLOBAL PROPERTY _MAC_CODESIGN_DEFERRED TRUE)
message(STATUS "Apple codesign ID for development only: ${APPLE_CODESIGN_DEV}")
cmake_language(DEFER DIRECTORY ${CMAKE_SOURCE_DIR} CALL _finalize_mac_codesign)
endif()
endfunction()
function(_finalize_mac_codesign)
get_property(depends GLOBAL PROPERTY _MAC_CODESIGN_DEPENDS)
set(stamp_file "${CMAKE_BINARY_DIR}/CMakeFiles/codesign-dev.stamp")
# Use a stamp file because codesign modifies the binaries it signs.
add_custom_command(
OUTPUT ${stamp_file}
COMMAND /usr/bin/codesign
--force
--options runtime
--entitlements "${CMAKE_SOURCE_DIR}/src/apps/res/entitlements-dev.plist"
--sign "${APPLE_CODESIGN_DEV}"
"$<TARGET_BUNDLE_DIR:${CMAKE_PROJECT_PROPER_NAME}>"
COMMAND ${CMAKE_COMMAND} -E touch ${stamp_file}
DEPENDS ${depends}
COMMENT "Codesigning ${CMAKE_PROJECT_PROPER_NAME}"
VERBATIM
)
add_custom_target(codesign-dev ALL DEPENDS ${stamp_file})
endfunction()

View File

@ -24,3 +24,12 @@ if (DOXYGEN_FOUND)
else() else()
message(STATUS "Doxygen not found, skipping docs build") message(STATUS "Doxygen not found, skipping docs build")
endif() endif()
# Show our documents in the IDE
add_custom_target(docs
SOURCES
Readme.md
Security.md
)
install(FILES Security.md Readme.md DESTINATION ${CMAKE_INSTALL_DOCDIR})

18
docs/Readme.md Normal file
View File

@ -0,0 +1,18 @@
# Deskflow
Deskflow is a free and open source keyboard and mouse sharing app. Use the keyboard, mouse, or trackpad of one computer to control nearby computers, and work seamlessly between them.
[Homepage](https://deskflow.org) [Code](https://github.com/deskflow/deskflo)
## Getting help online
- View the [wiki](https://github.com/deskflow/deskflow/wiki) Online resource
### Chat with us
- Main discussion on Matrix: [`#deskflow:matrix.org`](https://matrix.to/#/#deskflow:matrix.org) ([Matrix clients](https://matrix.org/ecosystem/clients/))
- Discussion also happens on IRC: `#deskflow` or `#deskflow-dev` on [Libera Chat](https://libera.chat/)
- Start a [new discussion](https://github.com/deskflow/deskflow/discussions) on our GitHub project.
## Reporting security issues
Check [Security](Security.md) to find out how to report security issues.

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -4,7 +4,7 @@ To build Deskflow you will a minimum of:
- [cmake] 3.24+ - [cmake] 3.24+
- [Qt] 6.7.0+ - [Qt] 6.7.0+
- [openssl] 3.0+ - [openssl] 3.0+
- [libportal] 0.8+ (linux, bsd) - [libportal] 0.9.1+ (linux, bsd)
- [libei] 1.3+ (linux, bsd) - [libei] 1.3+ (linux, bsd)
- [google_test] ^ - [google_test] ^

View File

@ -16,5 +16,4 @@ target_sources(user-docs PRIVATE
install( install(
DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/html" DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/html"
DESTINATION ${CMAKE_INSTALL_DOCDIR} DESTINATION ${CMAKE_INSTALL_DOCDIR}
COMPONENT deskflow_user_docs
) )

View File

@ -50,15 +50,15 @@ option=value
This section contains options used when in client mode. This section contains options used when in client mode.
It will begin with `[client]` It will begin with `[client]`
| Option | Valid Values | Description |
| Option | Valid Values | Description | |:------------------|:------------------:|:-----------|
|:----------------------|:------------------:|:-----------| | languageSync | `true` or `false` | Sync to server language [default: true] |
| binary | Filename | The filename of the binary to call for client mode. This binary exists in the same path as the GUI | | remoteHost | `IP` or `hostname` | The remote host(s) to connect to. Use a comma separated list when you want to try several hosts |
| invertScrollDirection | `true` or `false` | Invert scroll on this client [default: false] | | yScrollScale | Double 0.1 - 10.0 | Vertical mouse scrolling will be scaled by this amount on the client [default: 1.0] |
| languageSync | `true` or `false` | Sync to server language [default: true] | | xScrollScale | Double 0.1 - 10.0 | Horizontal mouse scrolling will be scaled by this amount on the client [default: 1.0] |
| remoteHost | `IP` or `hostname` | The remote host(s) to connect to. Use a comma seperated list when you want to try severial hosts | | invertYScroll | `true` or `false` | Invert vertical scroll on this client [default: false] |
| yScrollScale | Double 0.1 - 10.0 | Mouse scrolling will be scaled by this amount on the client [default: 1.0] | | invertXScroll | `true` or `false` | Invert horizontal scroll on this client [default: false] |
| xdpRestoreToken | UUID | Restore token provided by XDG portals | | xdpRestoreToken | UUID | Restore token provided by XDG portals |
### Core ### Core
@ -77,6 +77,10 @@ This section contains general options it will begin with `[core]`
| useHooks | `true` or `false` | If Windows uses hooks or not [default: true] | | useHooks | `true` or `false` | If Windows uses hooks or not [default: true] |
| language | 639 language | The language to display the GUI in [default: en] | | language | 639 language | The language to display the GUI in [default: en] |
| wlClipboard | `true` or `false` | When true the wl-clipboard backend will be enabled [default: false] | | wlClipboard | `true` or `false` | When true the wl-clipboard backend will be enabled [default: false] |
| enableEnterCommand | `true` or `false` | Should the enter command be triggered when the screen is entered [defaut: false] |
| enterCommand | command | A command to run when the screen is entered. |
| enableExitCommand | `true` or `false` | Should the exit command be triggered when the screen is exited [defaut: false] |
| exitCommand | command | A command to run when the screen is exited. |
### Daemon ### Daemon

View File

@ -50,6 +50,9 @@ if(BUILD_OSX_BUNDLE)
INSTALL_RPATH "@loader_path/../Libraries;@loader_path/../Frameworks" INSTALL_RPATH "@loader_path/../Libraries;@loader_path/../Frameworks"
RUNTIME_OUTPUT_DIRECTORY $<TARGET_BUNDLE_CONTENT_DIR:${CMAKE_PROJECT_PROPER_NAME}>/MacOS RUNTIME_OUTPUT_DIRECTORY $<TARGET_BUNDLE_CONTENT_DIR:${CMAKE_PROJECT_PROPER_NAME}>/MacOS
) )
if (APPLE_CODESIGN_DEV)
configure_mac_codesign(${target})
endif()
elseif (WIN32) elseif (WIN32)
install(RUNTIME_DEPENDENCY_SET coreDeps install(RUNTIME_DEPENDENCY_SET coreDeps
PRE_EXCLUDE_REGEXES ${WIN32_PRE_EXCLUDE_REGEXES} PRE_EXCLUDE_REGEXES ${WIN32_PRE_EXCLUDE_REGEXES}

View File

@ -16,7 +16,7 @@
#include "deskflow/ClientApp.h" #include "deskflow/ClientApp.h"
#include "deskflow/ServerApp.h" #include "deskflow/ServerApp.h"
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
#include "arch/win32/ArchMiscWindows.h" #include "arch/win32/ArchMiscWindows.h"
#include <QCoreApplication> #include <QCoreApplication>
#endif #endif
@ -32,7 +32,7 @@ void showHelp(const CoreArgParser &parser)
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
// HACK to make sure settings gets the correct qApp path // HACK to make sure settings gets the correct qApp path
QCoreApplication m(argc, argv); QCoreApplication m(argc, argv);
m.deleteLater(); m.deleteLater();

View File

@ -15,8 +15,7 @@
#include "common/Settings.h" #include "common/Settings.h"
#include "deskflow/ipc/DaemonIpcServer.h" #include "deskflow/ipc/DaemonIpcServer.h"
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
#include "arch/win32/ArchDaemonWindows.h" #include "arch/win32/ArchDaemonWindows.h"
#include "deskflow/Screen.h" #include "deskflow/Screen.h"
#include "platform/MSWindowsDebugOutputter.h" #include "platform/MSWindowsDebugOutputter.h"
@ -64,7 +63,7 @@ void DaemonApp::applyWatchdogCommand() const
{ {
LOG_DEBUG("applying watchdog command"); LOG_DEBUG("applying watchdog command");
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
m_pWatchdog->setProcessConfig(m_command, m_elevate); m_pWatchdog->setProcessConfig(m_command, m_elevate);
#else #else
LOG_ERR("applying watchdog command not implemented on this platform"); LOG_ERR("applying watchdog command not implemented on this platform");
@ -78,7 +77,7 @@ void DaemonApp::clearWatchdogCommand()
// Clear the setting to prevent it from being next time the daemon starts. // Clear the setting to prevent it from being next time the daemon starts.
setCommand(""); setCommand("");
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
m_pWatchdog->setProcessConfig("", false); m_pWatchdog->setProcessConfig("", false);
#else #else
LOG_ERR("clearing watchdog command not implemented on this platform"); LOG_ERR("clearing watchdog command not implemented on this platform");
@ -137,7 +136,7 @@ void DaemonApp::run(QThread &daemonThread)
LOG_DEBUG("daemon thread finished"); LOG_DEBUG("daemon thread finished");
}); });
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
m_pWatchdog = std::make_unique<MSWindowsWatchdog>(m_foreground, *m_pFileLogOutputter); m_pWatchdog = std::make_unique<MSWindowsWatchdog>(m_foreground, *m_pFileLogOutputter);
auto command = Settings::value(Settings::Daemon::Command).toString().toStdString(); auto command = Settings::value(Settings::Daemon::Command).toString().toStdString();
@ -154,17 +153,17 @@ void DaemonApp::run(QThread &daemonThread)
int DaemonApp::daemonLoop() int DaemonApp::daemonLoop()
{ {
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
// Runs the daemon through the Windows service controller, which controls the program lifecycle. // Runs the daemon through the Windows service controller, which controls the program lifecycle.
return ArchDaemonWindows::runDaemon([this]() { return mainLoop(); }); return ArchDaemonWindows::runDaemon([this]() { return mainLoop(); });
#elif SYSAPI_UNIX #else
return mainLoop(); return mainLoop();
#endif #endif
} }
int DaemonApp::mainLoop() int DaemonApp::mainLoop()
{ {
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
if (m_pWatchdog == nullptr) { if (m_pWatchdog == nullptr) {
LOG_ERR("watchdog not initialized"); LOG_ERR("watchdog not initialized");
return s_exitFailed; return s_exitFailed;
@ -173,7 +172,7 @@ int DaemonApp::mainLoop()
#endif #endif
try { try {
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
// Install the platform event queue to handle service stop events. // Install the platform event queue to handle service stop events.
// This must be done on the same thread as the event loop, otherwise the service stop // This must be done on the same thread as the event loop, otherwise the service stop
// request will not add the quit event to the event queue, and the service won't stop. // request will not add the quit event to the event queue, and the service won't stop.
@ -193,7 +192,7 @@ int DaemonApp::mainLoop()
LOG_INFO("daemon is stopping"); LOG_INFO("daemon is stopping");
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
try { try {
LOG_DEBUG("stopping process watchdog"); LOG_DEBUG("stopping process watchdog");
m_pWatchdog->stop(); m_pWatchdog->stop();
@ -221,7 +220,7 @@ void DaemonApp::setForeground()
void DaemonApp::initLogging() void DaemonApp::initLogging()
{ {
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
if (!m_foreground) { if (!m_foreground) {
// Only use MS debug outputter when the process is daemonized, since stdout won't be accessible // Only use MS debug outputter when the process is daemonized, since stdout won't be accessible
// in that case, but is accessible when running in the foreground. // in that case, but is accessible when running in the foreground.
@ -235,7 +234,7 @@ void DaemonApp::initLogging()
void DaemonApp::showConsole() void DaemonApp::showConsole()
{ {
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
// The daemon bin is compiled using the Win32 subsystem which works best for Windows services, // The daemon bin is compiled using the Win32 subsystem which works best for Windows services,
// so when running as a foreground process we need to allocate a console (or we won't see output). // so when running as a foreground process we need to allocate a console (or we won't see output).
// It is important to do this inside the arg check loop so that we can attach console ahead // It is important to do this inside the arg check loop so that we can attach console ahead

View File

@ -21,7 +21,7 @@ namespace deskflow::core::ipc {
class DaemonIpcServer; class DaemonIpcServer;
} }
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
class MSWindowsWatchdog; class MSWindowsWatchdog;
#endif #endif
@ -56,7 +56,7 @@ private:
static void showConsole(); static void showConsole();
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
std::unique_ptr<MSWindowsWatchdog> m_pWatchdog; std::unique_ptr<MSWindowsWatchdog> m_pWatchdog;
#endif #endif

View File

@ -15,13 +15,11 @@
#include "common/VersionInfo.h" #include "common/VersionInfo.h"
#include "deskflow/ipc/DaemonIpcServer.h" #include "deskflow/ipc/DaemonIpcServer.h"
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
#include "arch/win32/ArchMiscWindows.h" #include "arch/win32/ArchMiscWindows.h"
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <Windows.h> #include <Windows.h>
#endif #endif
#include <QCommandLineParser> #include <QCommandLineParser>
@ -34,7 +32,7 @@ void handleError(const char *message = "Unrecognized error.");
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
ArchMiscWindows::guardRuntimeVersion(); ArchMiscWindows::guardRuntimeVersion();
// Save window instance for later use, e.g. `GetModuleFileName` which is used when installing the daemon. // Save window instance for later use, e.g. `GetModuleFileName` which is used when installing the daemon.
@ -85,7 +83,7 @@ int main(int argc, char **argv)
try { try {
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
// Show warning if not running as admin as daemon will behave differently. // Show warning if not running as admin as daemon will behave differently.
if (!ArchMiscWindows::isProcessElevated()) { if (!ArchMiscWindows::isProcessElevated()) {
LOG_WARN("not running as admin, some features may not work"); LOG_WARN("not running as admin, some features may not work");
@ -115,8 +113,7 @@ int main(int argc, char **argv)
} }
} }
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
// Win32 subsystem entry point (simply forwards to main). // Win32 subsystem entry point (simply forwards to main).
// We need this because using regular main under the Win32 subsystem results in empty args. // We need this because using regular main under the Win32 subsystem results in empty args.
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
@ -131,7 +128,7 @@ void handleError(const char *message)
// Always print error to stdout in case run as CLI program. // Always print error to stdout in case run as CLI program.
LOG_ERR("%s", message); LOG_ERR("%s", message);
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
// Show a message box for when run from MSI in Win32 subsystem. // Show a message box for when run from MSI in Win32 subsystem.
MessageBoxA(nullptr, message, "Deskflow daemon error", MB_OK | MB_ICONERROR); MessageBoxA(nullptr, message, "Deskflow daemon error", MB_OK | MB_ICONERROR);
#endif #endif

View File

@ -95,32 +95,8 @@ elseif(APPLE)
INSTALL_RPATH "@loader_path/../Libraries;@loader_path/../Frameworks" INSTALL_RPATH "@loader_path/../Libraries;@loader_path/../Frameworks"
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_BINARY_DIR}/deskflow.plist" MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_BINARY_DIR}/deskflow.plist"
) )
if (APPLE_CODESIGN_DEV)
# Warning: Do not use for CI/production, as the `entitlements-dev.plist` file adds special configure_mac_codesign(${target})
# entitlements that are only appropriate for local development.
#
# macOS made TCC stricter so that if you don't sign your local dev builds properly, macOS will
# nag you to remove and re-approve the app every time you make a change to the binary which is
# extremely annoying during development.
#
# If you were to use ad-hoc signing (i.e. not specify a certificate), TCC would still nag you
# because the binary identity is anchored not on the app ID, but on the CD hash (which changes
# based on the binary contents).
#
# To use, simply generate a personal certificate for free with Xcode and pass the ID to CMake.
# Full instructions are in the docs.
if (NOT "${APPLE_CODESIGN_DEV}" STREQUAL "")
message(STATUS "Apple codesign ID for development only: ${APPLE_CODESIGN_DEV}")
add_custom_command(
TARGET ${target} POST_BUILD
COMMAND /usr/bin/codesign
--force
--options runtime
--entitlements "$<SHELL_PATH:${CMAKE_SOURCE_DIR}/src/apps/res/entitlements-dev.plist>"
--sign "${APPLE_CODESIGN_DEV}"
"$<TARGET_BUNDLE_DIR:${target}>"
VERBATIM
)
endif() endif()
else() else()
set_target_properties(${target} PROPERTIES MACOSX_BUNDLE FALSE) set_target_properties(${target} PROPERTIES MACOSX_BUNDLE FALSE)

View File

@ -32,7 +32,7 @@
#include <QLoggingCategory> #include <QLoggingCategory>
#endif #endif
#if defined(WINAPI_XWINDOWS) or defined(WINAPI_LIBEI) #if !defined(Q_OS_MAC) && !defined(Q_OS_WIN)
#include "platform/XDGPortalRegistry.h" #include "platform/XDGPortalRegistry.h"
#endif #endif
@ -51,7 +51,7 @@ int main(int argc, char *argv[])
QLoggingCategory::setFilterRules(QStringLiteral("*.debug=true\nqt.*=false")); QLoggingCategory::setFilterRules(QStringLiteral("*.debug=true\nqt.*=false"));
#endif #endif
#if defined(WINAPI_XWINDOWS) or defined(WINAPI_LIBEI) #if !defined(Q_OS_MAC) && !defined(Q_OS_WIN)
deskflow::platform::setAppId(); deskflow::platform::setAppId();
#endif #endif

View File

@ -10,7 +10,7 @@
#include <thread> #include <thread>
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
#include "arch/win32/ArchMiscWindows.h" #include "arch/win32/ArchMiscWindows.h"
#endif #endif
@ -26,7 +26,7 @@ Arch::Arch()
s_instance = this; s_instance = this;
} }
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
void Arch::init() void Arch::init()
{ {
ARCH_NETWORK::init(); ARCH_NETWORK::init();

View File

@ -5,7 +5,7 @@
* SPDX-FileCopyrightText: (C) 2002 Chris Schoeneman * SPDX-FileCopyrightText: (C) 2002 Chris Schoeneman
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/ */
#include <QtSystemDetection>
// Consider whether or not to use either encapsulation (as below) // Consider whether or not to use either encapsulation (as below)
// or inheritance (as it is now) for the ARCH stuff. // or inheritance (as it is now) for the ARCH stuff.
// //
@ -25,20 +25,16 @@
#pragma once #pragma once
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
#include "arch/win32/ArchDaemonWindows.h" #include "arch/win32/ArchDaemonWindows.h"
#include "arch/win32/ArchLogWindows.h" #include "arch/win32/ArchLogWindows.h"
#include "arch/win32/ArchMultithreadWindows.h" #include "arch/win32/ArchMultithreadWindows.h"
#include "arch/win32/ArchNetworkWinsock.h" #include "arch/win32/ArchNetworkWinsock.h"
#else
#elif SYSAPI_UNIX
#include "arch/ArchDaemonNone.h" #include "arch/ArchDaemonNone.h"
#include "arch/unix/ArchLogUnix.h" #include "arch/unix/ArchLogUnix.h"
#include "arch/unix/ArchMultithreadPosix.h" #include "arch/unix/ArchMultithreadPosix.h"
#include "arch/unix/ArchNetworkBSD.h" #include "arch/unix/ArchNetworkBSD.h"
#endif #endif
/*! /*!
@ -63,7 +59,7 @@ public:
Arch(); Arch();
~Arch() override = default; ~Arch() override = default;
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
//! Call init on other arch classes. //! Call init on other arch classes.
/*! /*!
Some arch classes depend on others to exist first. When init is called Some arch classes depend on others to exist first. When init is called

View File

@ -91,6 +91,11 @@ enum class EventTypes : uint32_t
/// This event is sent whenever a server accepts a client. /// This event is sent whenever a server accepts a client.
ClientListenerAccepted, ClientListenerAccepted,
/** This event is sent whenever a client disconnects/errors out before the connection
is fully accepted.
*/
ClientListenerDisconnectedOnAccept,
/** This event is sent when the client has completed the initial handshake. Until it is sent, /** This event is sent when the client has completed the initial handshake. Until it is sent,
the client is not fully connected. the client is not fully connected.
*/ */

View File

@ -21,3 +21,26 @@ enum class ErrorType : uint8_t
}; };
Q_ENUM_NS(ErrorType) Q_ENUM_NS(ErrorType)
} // namespace deskflow::client } // namespace deskflow::client
namespace deskflow::core {
Q_NAMESPACE
enum class ProcessState
{
Starting,
Started,
Stopping,
Stopped,
RetryPending
};
Q_ENUM_NS(ProcessState)
enum class ConnectionState
{
Disconnected,
Connecting,
Connected,
Listening
};
Q_ENUM_NS(ConnectionState)
} // namespace deskflow::core

View File

@ -137,12 +137,11 @@ QString Settings::cleanComputerName(const QString &name)
static const auto space = QStringLiteral(" "); static const auto space = QStringLiteral(" ");
static const auto underscore = QStringLiteral("_"); static const auto underscore = QStringLiteral("_");
static const auto period = QStringLiteral("."); static const auto period = QStringLiteral(".");
static const auto nothing = QStringLiteral("");
static const auto nameRegex = QRegularExpression(QStringLiteral("[^\\w\\-\\.]")); static const auto nameRegex = QRegularExpression(QStringLiteral("[^\\w\\-\\.]"));
QString cleanName = name.simplified(); QString cleanName = name.simplified();
cleanName.replace(space, underscore); cleanName.replace(space, underscore);
cleanName.replace(nameRegex, nothing); cleanName.replace(nameRegex, {});
while (cleanName.startsWith(hyphen) || cleanName.startsWith(underscore) || cleanName.startsWith(period)) while (cleanName.startsWith(hyphen) || cleanName.startsWith(underscore) || cleanName.startsWith(period))
cleanName.removeFirst(); cleanName.removeFirst();
while (cleanName.endsWith(hyphen) || cleanName.endsWith(underscore) || cleanName.endsWith(period)) while (cleanName.endsWith(hyphen) || cleanName.endsWith(underscore) || cleanName.endsWith(period))
@ -233,6 +232,18 @@ QStringList Settings::validKeys()
return Settings::m_validKeys; return Settings::m_validKeys;
} }
QString Settings::serverConfigFile()
{
bool useExt = value(Server::ExternalConfig).toBool();
return useExt ? value(Server::ExternalConfigFile).toString() : defaultValue(Server::ExternalConfigFile).toString();
}
bool Settings::isServerConfigFileReadable()
{
auto file = QFile(serverConfigFile());
return file.open(QFile::ReadOnly);
}
bool Settings::isWritable() bool Settings::isWritable()
{ {
return instance()->m_settings->isWritable(); return instance()->m_settings->isWritable();

View File

@ -55,6 +55,10 @@ public:
inline static const auto UseHooks = QStringLiteral("core/useHooks"); inline static const auto UseHooks = QStringLiteral("core/useHooks");
inline static const auto Language = QStringLiteral("core/language"); inline static const auto Language = QStringLiteral("core/language");
inline static const auto UseWlClipboard = QStringLiteral("core/wlClipboard"); inline static const auto UseWlClipboard = QStringLiteral("core/wlClipboard");
inline static const auto EnableEnterCommand = QStringLiteral("core/enableEnterCommand");
inline static const auto ScreenEnterCommand = QStringLiteral("core/enterCommand");
inline static const auto EnableExitCommand = QStringLiteral("core/enableExitCommand");
inline static const auto ScreenExitCommand = QStringLiteral("core/exitCommand");
// TODO: REMOVE In 2.0 // TODO: REMOVE In 2.0
inline static const auto ScreenName = QStringLiteral("core/screenName"); // Replaced By ComputerName inline static const auto ScreenName = QStringLiteral("core/screenName"); // Replaced By ComputerName
@ -77,7 +81,6 @@ public:
inline static const auto LogExpanded = QStringLiteral("gui/logExpanded"); inline static const auto LogExpanded = QStringLiteral("gui/logExpanded");
inline static const auto SymbolicTrayIcon = QStringLiteral("gui/symbolicTrayIcon"); inline static const auto SymbolicTrayIcon = QStringLiteral("gui/symbolicTrayIcon");
inline static const auto WindowGeometry = QStringLiteral("gui/windowGeometry"); inline static const auto WindowGeometry = QStringLiteral("gui/windowGeometry");
inline static const auto ShowGenericClientFailureDialog = QStringLiteral("gui/showGenericClientFailureDialog");
inline static const auto ShownFirstConnectedMessage = QStringLiteral("gui/shownFirstConnectedMessage"); inline static const auto ShownFirstConnectedMessage = QStringLiteral("gui/shownFirstConnectedMessage");
inline static const auto ShownServerFirstStartMessage = QStringLiteral("gui/shownServerFirstStartMessage"); inline static const auto ShownServerFirstStartMessage = QStringLiteral("gui/shownServerFirstStartMessage");
inline static const auto ShowVersionInTitle = QStringLiteral("gui/showVersionInTitle"); inline static const auto ShowVersionInTitle = QStringLiteral("gui/showVersionInTitle");
@ -131,10 +134,12 @@ public:
static QVariant value(const QString &key = QString()); static QVariant value(const QString &key = QString());
static void restoreDefaultSettings(); static void restoreDefaultSettings();
static QVariant defaultValue(const QString &key); static QVariant defaultValue(const QString &key);
static bool isServerConfigFileReadable();
static bool isWritable(); static bool isWritable();
static bool isPortableMode(); static bool isPortableMode();
static QString settingsFile(); static QString settingsFile();
static QString settingsPath(); static QString settingsPath();
static QString serverConfigFile();
static QString tlsDir(); static QString tlsDir();
static QString tlsTrustedServersDb(); static QString tlsTrustedServersDb();
static QString tlsTrustedClientsDb(); static QString tlsTrustedClientsDb();
@ -206,6 +211,10 @@ private:
, Settings::Core::Port , Settings::Core::Port
, Settings::Core::PreventSleep , Settings::Core::PreventSleep
, Settings::Core::ProcessMode , Settings::Core::ProcessMode
, Settings::Core::EnableEnterCommand
, Settings::Core::EnableExitCommand
, Settings::Core::ScreenEnterCommand
, Settings::Core::ScreenExitCommand
, Settings::Core::ScreenName , Settings::Core::ScreenName
, Settings::Core::ComputerName , Settings::Core::ComputerName
, Settings::Core::Display , Settings::Core::Display
@ -215,6 +224,7 @@ private:
, Settings::Daemon::Command , Settings::Daemon::Command
, Settings::Daemon::Elevate , Settings::Daemon::Elevate
, Settings::Daemon::LogFile , Settings::Daemon::LogFile
, Settings::Daemon::LogLevel
, Settings::Log::File , Settings::Log::File
, Settings::Log::Level , Settings::Log::Level
, Settings::Log::ToFile , Settings::Log::ToFile
@ -228,7 +238,6 @@ private:
, Settings::Gui::LogExpanded , Settings::Gui::LogExpanded
, Settings::Gui::SymbolicTrayIcon , Settings::Gui::SymbolicTrayIcon
, Settings::Gui::WindowGeometry , Settings::Gui::WindowGeometry
, Settings::Gui::ShowGenericClientFailureDialog
, Settings::Gui::ShownFirstConnectedMessage , Settings::Gui::ShownFirstConnectedMessage
, Settings::Gui::ShownServerFirstStartMessage , Settings::Gui::ShownServerFirstStartMessage
, Settings::Gui::ShowVersionInTitle , Settings::Gui::ShowVersionInTitle
@ -249,6 +258,8 @@ private:
, Settings::Gui::ShowVersionInTitle , Settings::Gui::ShowVersionInTitle
, Settings::Core::PreventSleep , Settings::Core::PreventSleep
, Settings::Core::UseWlClipboard , Settings::Core::UseWlClipboard
, Settings::Core::EnableEnterCommand
, Settings::Core::EnableExitCommand
, Settings::Server::ExternalConfig , Settings::Server::ExternalConfig
, Settings::Client::InvertYScroll , Settings::Client::InvertYScroll
, Settings::Client::InvertXScroll , Settings::Client::InvertXScroll
@ -264,7 +275,6 @@ private:
, Settings::Gui::CloseReminder , Settings::Gui::CloseReminder
, Settings::Gui::LogExpanded , Settings::Gui::LogExpanded
, Settings::Gui::SymbolicTrayIcon , Settings::Gui::SymbolicTrayIcon
, Settings::Gui::ShowGenericClientFailureDialog
, Settings::Security::TlsEnabled , Settings::Security::TlsEnabled
, Settings::Security::CheckPeers , Settings::Security::CheckPeers
}; };

View File

@ -16,13 +16,13 @@
#include "common/Settings.h" #include "common/Settings.h"
#include "deskflow/DeskflowException.h" #include "deskflow/DeskflowException.h"
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
#include "base/IEventQueue.h" #include "base/IEventQueue.h"
#endif #endif
#include <stdexcept> #include <stdexcept>
#if WINAPI_CARBON #if defined(Q_OS_MAC)
#include "platform/OSXCocoaApp.h" #include "platform/OSXCocoaApp.h"
#include <ApplicationServices/ApplicationServices.h> #include <ApplicationServices/ApplicationServices.h>
#endif #endif
@ -151,10 +151,7 @@ void App::handleScreenError() const
void App::runEventsLoop(const void *) void App::runEventsLoop(const void *)
{ {
m_events->loop(); m_events->loop();
#if defined(Q_OS_MAC)
#if WINAPI_CARBON
stopCocoaLoop(); stopCocoaLoop();
#endif #endif
} }

View File

@ -13,9 +13,9 @@
#include "deskflow/IApp.h" #include "deskflow/IApp.h"
#include "net/SocketMultiplexer.h" #include "net/SocketMultiplexer.h"
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
#include "deskflow/win32/AppUtilWindows.h" #include "deskflow/win32/AppUtilWindows.h"
#elif SYSAPI_UNIX #else
#include "deskflow/unix/AppUtilUnix.h" #include "deskflow/unix/AppUtilUnix.h"
#endif #endif

View File

@ -22,7 +22,7 @@
#include "net/SocketMultiplexer.h" #include "net/SocketMultiplexer.h"
#include "net/TCPSocketFactory.h" #include "net/TCPSocketFactory.h"
#if WINAPI_MSWINDOWS #if defined(Q_OS_WIN)
#include "platform/MSWindowsScreen.h" #include "platform/MSWindowsScreen.h"
#endif #endif
@ -36,7 +36,7 @@
#include "platform/EiScreen.h" #include "platform/EiScreen.h"
#endif #endif
#if WINAPI_CARBON #if defined(Q_OS_MAC)
#include "base/TMethodJob.h" #include "base/TMethodJob.h"
#include "mt/Thread.h" #include "mt/Thread.h"
#include "platform/OSXCocoaApp.h" #include "platform/OSXCocoaApp.h"
@ -104,7 +104,7 @@ const char *ClientApp::daemonName() const
deskflow::Screen *ClientApp::createScreen() deskflow::Screen *ClientApp::createScreen()
{ {
#if WINAPI_MSWINDOWS #if defined(Q_OS_WIN)
return new deskflow::Screen( return new deskflow::Screen(
new MSWindowsScreen( new MSWindowsScreen(
false, Settings::value(Settings::Core::UseHooks).toBool(), getEvents(), false, Settings::value(Settings::Core::UseHooks).toBool(), getEvents(),
@ -112,9 +112,11 @@ deskflow::Screen *ClientApp::createScreen()
), ),
getEvents() getEvents()
); );
#endif #elif defined(Q_OS_MAC)
return new deskflow::Screen(
#if defined(WINAPI_XWINDOWS) or defined(WINAPI_LIBEI) new OSXScreen(getEvents(), false, Settings::value(Settings::Client::LanguageSync).toBool()), getEvents()
);
#else
if (deskflow::platform::isWayland()) { if (deskflow::platform::isWayland()) {
#if WINAPI_LIBEI #if WINAPI_LIBEI
LOG_INFO("using ei screen for wayland"); LOG_INFO("using ei screen for wayland");
@ -123,22 +125,14 @@ deskflow::Screen *ClientApp::createScreen()
throw XNoEiSupport(); throw XNoEiSupport();
#endif #endif
} }
#endif
#if WINAPI_XWINDOWS #if WINAPI_XWINDOWS
LOG_INFO("using legacy x windows screen"); LOG_INFO("using legacy x windows screen");
return new deskflow::Screen( return new deskflow::Screen(
new XWindowsScreen(qPrintable(Settings::value(Settings::Core::Display).toString()), false, getEvents()), new XWindowsScreen(qPrintable(Settings::value(Settings::Core::Display).toString()), false, getEvents()),
getEvents() getEvents()
); );
#endif
#if WINAPI_CARBON
return new deskflow::Screen(
new OSXScreen(getEvents(), false, Settings::value(Settings::Client::LanguageSync).toBool()), getEvents()
);
#endif #endif
#endif // end os check
} }
deskflow::Screen *ClientApp::openClientScreen() deskflow::Screen *ClientApp::openClientScreen()
@ -332,8 +326,7 @@ int ClientApp::mainLoop()
// later. the timer installed by startClient() will take care of // later. the timer installed by startClient() will take care of
// that. // that.
#if WINAPI_CARBON #if defined(Q_OS_MAC)
Thread thread(new TMethodJob<ClientApp>(this, &ClientApp::runEventsLoop, nullptr)); Thread thread(new TMethodJob<ClientApp>(this, &ClientApp::runEventsLoop, nullptr));
// wait until carbon loop is ready // wait until carbon loop is ready

View File

@ -10,6 +10,8 @@
#include "base/Log.h" #include "base/Log.h"
#include "deskflow/IPlatformScreen.h" #include "deskflow/IPlatformScreen.h"
#include <QProcess>
namespace deskflow { namespace deskflow {
// //
@ -119,6 +121,14 @@ void Screen::enter(KeyModifierMask toggleMask)
} else { } else {
enterSecondary(toggleMask); enterSecondary(toggleMask);
} }
if (Settings::value(Settings::Core::EnableEnterCommand).toBool()) {
auto args = QProcess::splitCommand(Settings::value(Settings::Core::ScreenEnterCommand).toString());
const auto command = args.takeFirst();
LOG_DEBUG("running screen enter command: %s %s", qPrintable(command), qPrintable(args.join(" ")));
if (!QProcess::startDetached(command, args))
LOG_ERR("failed to run screen enter command");
}
} }
bool Screen::leave() bool Screen::leave()
@ -140,6 +150,13 @@ bool Screen::leave()
} }
m_screen->leave(); m_screen->leave();
if (Settings::value(Settings::Core::EnableExitCommand).toBool()) {
auto args = QProcess::splitCommand(Settings::value(Settings::Core::ScreenExitCommand).toString());
const auto command = args.takeFirst();
LOG_DEBUG("running screen exit command: %s %s", qPrintable(command), qPrintable(args.join(" ")));
if (!QProcess::startDetached(command, args))
LOG_ERR("failed to run screen exit command");
}
// make sure our idea of clipboard ownership is correct // make sure our idea of clipboard ownership is correct
m_screen->checkClipboards(); m_screen->checkClipboards();

View File

@ -30,7 +30,7 @@
// must be before screen header includes // must be before screen header includes
#include <QFileInfo> #include <QFileInfo>
#if WINAPI_MSWINDOWS #if defined(Q_OS_WIN)
#include "platform/MSWindowsScreen.h" #include "platform/MSWindowsScreen.h"
#endif #endif
@ -42,7 +42,7 @@
#include "platform/EiScreen.h" #include "platform/EiScreen.h"
#endif #endif
#if WINAPI_CARBON #if defined(Q_OS_MAC)
#include "base/TMethodJob.h" #include "base/TMethodJob.h"
#include "mt/Thread.h" #include "mt/Thread.h"
#include "platform/OSXCocoaApp.h" #include "platform/OSXCocoaApp.h"
@ -85,17 +85,10 @@ void ServerApp::reloadSignalHandler(Arch::ThreadSignal, void *)
events->addEvent(Event(EventTypes::ServerAppReloadConfig, events->getSystemTarget())); events->addEvent(Event(EventTypes::ServerAppReloadConfig, events->getSystemTarget()));
} }
QString ServerApp::currentConfig() const
{
bool useExt = Settings::value(Settings::Server::ExternalConfig).toBool();
return useExt ? Settings::value(Settings::Server::ExternalConfigFile).toString()
: Settings::defaultValue(Settings::Server::ExternalConfigFile).toString();
}
void ServerApp::reloadConfig() void ServerApp::reloadConfig()
{ {
LOG_DEBUG("reload configuration"); LOG_DEBUG("reload configuration");
if (loadConfig(currentConfig())) { if (loadConfig(Settings::serverConfigFile())) {
if (m_server != nullptr) { if (m_server != nullptr) {
m_server->setConfig(*m_config); m_server->setConfig(*m_config);
} }
@ -105,7 +98,7 @@ void ServerApp::reloadConfig()
void ServerApp::loadConfig() void ServerApp::loadConfig()
{ {
const auto path = currentConfig(); const auto path = Settings::serverConfigFile();
if (path.isEmpty()) { if (path.isEmpty()) {
LOG_CRIT("no configuration path provided"); LOG_CRIT("no configuration path provided");
bye(s_exitConfig); bye(s_exitConfig);
@ -123,7 +116,7 @@ bool ServerApp::loadConfig(const QString &filename)
try { try {
// load configuration // load configuration
LOG_DEBUG("opening configuration \"%s\"", path.c_str()); LOG_DEBUG("opening configuration \"%s\"", path.c_str());
#ifdef SYSAPI_WIN32 #if defined(Q_OS_WIN)
std::ifstream configStream(filename.toStdWString()); std::ifstream configStream(filename.toStdWString());
#else #else
std::ifstream configStream(path); std::ifstream configStream(path);
@ -399,13 +392,13 @@ bool ServerApp::startServer()
deskflow::Screen *ServerApp::createScreen() deskflow::Screen *ServerApp::createScreen()
{ {
#if WINAPI_MSWINDOWS #if defined(Q_OS_WIN)
return new deskflow::Screen( return new deskflow::Screen(
new MSWindowsScreen(true, Settings::value(Settings::Core::UseHooks).toBool(), getEvents()), getEvents() new MSWindowsScreen(true, Settings::value(Settings::Core::UseHooks).toBool(), getEvents()), getEvents()
); );
#endif #elif defined(Q_OS_MAC)
return new deskflow::Screen(new OSXScreen(getEvents(), true), getEvents());
#if defined(WINAPI_XWINDOWS) or defined(WINAPI_LIBEI) #else
if (deskflow::platform::isWayland()) { if (deskflow::platform::isWayland()) {
#if WINAPI_LIBEI #if WINAPI_LIBEI
LOG_INFO("using ei screen for wayland"); LOG_INFO("using ei screen for wayland");
@ -414,17 +407,14 @@ deskflow::Screen *ServerApp::createScreen()
throw XNoEiSupport(); throw XNoEiSupport();
#endif #endif
} }
#endif
#if WINAPI_XWINDOWS #if WINAPI_XWINDOWS
LOG_INFO("using legacy x windows screen"); LOG_INFO("using legacy x windows screen");
return new deskflow::Screen( return new deskflow::Screen(
new XWindowsScreen(qPrintable(Settings::value(Settings::Core::Display).toString()), true, getEvents()), new XWindowsScreen(qPrintable(Settings::value(Settings::Core::Display).toString()), true, getEvents()),
getEvents() getEvents()
); );
#elif WINAPI_CARBON
return new deskflow::Screen(new OSXScreen(getEvents(), true), getEvents());
#endif #endif
#endif // end os check
} }
PrimaryClient *ServerApp::openPrimaryClient(const std::string &name, deskflow::Screen *screen) PrimaryClient *ServerApp::openPrimaryClient(const std::string &name, deskflow::Screen *screen)
@ -553,7 +543,7 @@ int ServerApp::mainLoop()
// later. the timer installed by startServer() will take care of // later. the timer installed by startServer() will take care of
// that. // that.
#if WINAPI_CARBON #if defined(Q_OS_MAC)
Thread thread(new TMethodJob<ServerApp>(this, &ServerApp::runEventsLoop, nullptr)); Thread thread(new TMethodJob<ServerApp>(this, &ServerApp::runEventsLoop, nullptr));

View File

@ -106,7 +106,6 @@ private:
std::unique_ptr<ISocketFactory> getSocketFactory() const; std::unique_ptr<ISocketFactory> getSocketFactory() const;
NetworkAddress getAddress(const NetworkAddress &address) const; NetworkAddress getAddress(const NetworkAddress &address) const;
QString currentConfig() const;
bool m_suspended = false; bool m_suspended = false;
Server *m_server = nullptr; Server *m_server = nullptr;
ServerState m_serverState = ServerState::Uninitialized; ServerState m_serverState = ServerState::Uninitialized;

View File

@ -12,7 +12,7 @@
#if WINAPI_XWINDOWS #if WINAPI_XWINDOWS
#include "deskflow/unix/X11LayoutsParser.h" #include "deskflow/unix/X11LayoutsParser.h"
#include <X11/XKBlib.h> #include <X11/XKBlib.h>
#elif WINAPI_CARBON #elif defined(Q_OS_MAC)
#include <Carbon/Carbon.h> #include <Carbon/Carbon.h>
#include <platform/OSXAutoTypes.h> #include <platform/OSXAutoTypes.h>
#endif #endif
@ -50,7 +50,7 @@ std::vector<std::string> AppUtilUnix::getKeyboardLayoutList()
m_evdev = "/usr/share/X11/xkb/rules/evdev.xml"; m_evdev = "/usr/share/X11/xkb/rules/evdev.xml";
layoutLangCodes = X11LayoutsParser::getX11LanguageList(m_evdev); layoutLangCodes = X11LayoutsParser::getX11LanguageList(m_evdev);
#elif WINAPI_CARBON #elif defined(Q_OS_MAC)
CFStringRef keys[] = {kTISPropertyInputSourceCategory}; CFStringRef keys[] = {kTISPropertyInputSourceCategory};
CFStringRef values[] = {kTISCategoryKeyboardInputSource}; CFStringRef values[] = {kTISCategoryKeyboardInputSource};
AutoCFDictionary dict( AutoCFDictionary dict(
@ -134,7 +134,7 @@ std::string AppUtilUnix::getCurrentLanguageCode()
result = X11LayoutsParser::convertLayoutToISO(m_evdev, result); result = X11LayoutsParser::convertLayoutToISO(m_evdev, result);
#elif WINAPI_CARBON #elif defined(Q_OS_MAC)
auto layoutLanguages = auto layoutLanguages =
(CFArrayRef)TISGetInputSourceProperty(TISCopyCurrentKeyboardInputSource(), kTISPropertyInputSourceLanguages); (CFArrayRef)TISGetInputSourceProperty(TISCopyCurrentKeyboardInputSource(), kTISPropertyInputSourceLanguages);
char temporaryCString[128] = {0}; char temporaryCString[128] = {0};

View File

@ -58,8 +58,6 @@ add_library(${target} STATIC
core/ServerConnection.h core/ServerConnection.h
core/ServerMessage.cpp core/ServerMessage.cpp
core/ServerMessage.h core/ServerMessage.h
core/WaylandWarnings.cpp
core/WaylandWarnings.h
dialogs/AboutDialog.cpp dialogs/AboutDialog.cpp
dialogs/AboutDialog.h dialogs/AboutDialog.h
dialogs/AboutDialog.ui dialogs/AboutDialog.ui
@ -119,6 +117,8 @@ add_library(${target} STATIC
widgets/ScreenSetupView.h widgets/ScreenSetupView.h
widgets/SearchWidget.h widgets/SearchWidget.h
widgets/SearchWidget.cpp widgets/SearchWidget.cpp
widgets/StatusBar.cpp
widgets/StatusBar.h
widgets/TrashScreenWidget.cpp widgets/TrashScreenWidget.cpp
widgets/TrashScreenWidget.h widgets/TrashScreenWidget.h
) )

View File

@ -29,6 +29,7 @@
#include "gui/ipc/DaemonIpcClient.h" #include "gui/ipc/DaemonIpcClient.h"
#include "gui/widgets/LogDock.h" #include "gui/widgets/LogDock.h"
#include "net/FingerprintDatabase.h" #include "net/FingerprintDatabase.h"
#include "widgets/StatusBar.h"
#include <QCloseEvent> #include <QCloseEvent>
#include <QDesktopServices> #include <QDesktopServices>
@ -54,9 +55,6 @@
using namespace deskflow::gui; using namespace deskflow::gui;
using CoreConnectionState = CoreProcess::ConnectionState;
using CoreProcessState = CoreProcess::ProcessState;
MainWindow::MainWindow() MainWindow::MainWindow()
: ui{std::make_unique<Ui::MainWindow>()}, : ui{std::make_unique<Ui::MainWindow>()},
m_coreProcess(m_serverConfig), m_coreProcess(m_serverConfig),
@ -66,10 +64,7 @@ MainWindow::MainWindow()
m_guiDupeChecker{new QLocalServer(this)}, m_guiDupeChecker{new QLocalServer(this)},
m_daemonIpcClient{new ipc::DaemonIpcClient(this)}, m_daemonIpcClient{new ipc::DaemonIpcClient(this)},
m_logDock{new LogDock(this)}, m_logDock{new LogDock(this)},
m_lblSecurityStatus{new QLabel(this)}, m_statusBar{new StatusBar(this)},
m_lblStatus{new QLabel(this)},
m_btnFingerprint{new QPushButton(this)},
m_btnUpdate{new QPushButton(this)},
m_menuFile{new QMenu(this)}, m_menuFile{new QMenu(this)},
m_menuEdit{new QMenu(this)}, m_menuEdit{new QMenu(this)},
m_menuView{new QMenu(this)}, m_menuView{new QMenu(this)},
@ -141,8 +136,6 @@ MainWindow::MainWindow()
connectSlots(); connectSlots();
setupTrayIcon(); setupTrayIcon();
updateScreenName(); updateScreenName();
applyConfig();
restoreWindow();
qDebug().noquote() << "active settings path:" << Settings::settingsPath(); qDebug().noquote() << "active settings path:" << Settings::settingsPath();
@ -161,6 +154,10 @@ MainWindow::MainWindow()
m_fingerprint = {QCryptographicHash::Sha256, TlsUtility::certFingerprint()}; m_fingerprint = {QCryptographicHash::Sha256, TlsUtility::certFingerprint()};
} }
} }
applyConfig();
m_statusBar->setSecurityIcon(TlsUtility::isEnabled());
restoreWindow();
} }
MainWindow::~MainWindow() MainWindow::~MainWindow()
{ {
@ -218,6 +215,7 @@ void MainWindow::setupControls()
ui->lineEditName->setValidator(new QRegularExpressionValidator(m_nameRegEx, this)); ui->lineEditName->setValidator(new QRegularExpressionValidator(m_nameRegEx, this));
ui->lineEditName->setVisible(false); ui->lineEditName->setVisible(false);
ui->lineEditName->installEventFilter(this);
if (deskflow::platform::isMac()) { if (deskflow::platform::isMac()) {
ui->rbModeServer->setAttribute(Qt::WA_MacShowFocusRect, false); ui->rbModeServer->setAttribute(Qt::WA_MacShowFocusRect, false);
@ -226,31 +224,7 @@ void MainWindow::setupControls()
} else { } else {
ui->btnSaveServerConfig->setIconSize(QSize(22, 22)); ui->btnSaveServerConfig->setIconSize(QSize(22, 22));
} }
setStatusBar(m_statusBar);
static const auto btnHeight = ui->statusBar->height() - 2;
static const auto btnSize = QSize(btnHeight, btnHeight);
static const auto iconSize = QSize(fontMetrics().height() + 2, fontMetrics().height() + 2);
m_btnFingerprint->setFlat(true);
m_btnFingerprint->setIcon(QIcon::fromTheme(QStringLiteral("fingerprint")));
m_btnFingerprint->setFixedSize(btnSize);
m_btnFingerprint->setIconSize(iconSize);
ui->statusBar->insertPermanentWidget(0, m_btnFingerprint);
m_lblSecurityStatus->setVisible(false);
m_lblSecurityStatus->setFixedSize(iconSize);
m_lblSecurityStatus->setScaledContents(true);
ui->statusBar->insertPermanentWidget(1, m_lblSecurityStatus);
ui->statusBar->insertPermanentWidget(2, m_lblStatus, 1);
m_btnUpdate->setVisible(false);
m_btnUpdate->setFlat(true);
m_btnUpdate->setLayoutDirection(Qt::RightToLeft);
m_btnUpdate->setIcon(QIcon::fromTheme(QStringLiteral("software-updates-release")));
m_btnUpdate->setFixedHeight(btnHeight);
m_btnUpdate->setIconSize(iconSize);
ui->statusBar->insertPermanentWidget(3, m_btnUpdate);
} }
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
@ -276,6 +250,7 @@ void MainWindow::connectSlots()
connect( connect(
&m_coreProcess, &CoreProcess::daemonIpcClientConnectionFailed, this, &MainWindow::daemonIpcClientConnectionFailed &m_coreProcess, &CoreProcess::daemonIpcClientConnectionFailed, this, &MainWindow::daemonIpcClientConnectionFailed
); );
connect(&m_coreProcess, &CoreProcess::securityLevelChanged, m_statusBar, &StatusBar::setSecurityLevel);
connect(m_actionAbout, &QAction::triggered, this, &MainWindow::openAboutDialog); connect(m_actionAbout, &QAction::triggered, this, &MainWindow::openAboutDialog);
connect(m_actionClearSettings, &QAction::triggered, this, &MainWindow::clearSettings); connect(m_actionClearSettings, &QAction::triggered, this, &MainWindow::clearSettings);
@ -290,8 +265,6 @@ void MainWindow::connectSlots()
connect(m_actionRestartCore, &QAction::triggered, this, &MainWindow::resetCore); connect(m_actionRestartCore, &QAction::triggered, this, &MainWindow::resetCore);
connect(m_actionStopCore, &QAction::triggered, this, &MainWindow::stopCore); connect(m_actionStopCore, &QAction::triggered, this, &MainWindow::stopCore);
connect(&m_versionChecker, &VersionChecker::updateFound, this, &MainWindow::versionCheckerUpdateFound);
// Mac os tray will only show a menu // Mac os tray will only show a menu
if (!deskflow::platform::isMac()) if (!deskflow::platform::isMac())
connect(m_trayIcon, &QSystemTrayIcon::activated, this, &MainWindow::trayIconActivated); connect(m_trayIcon, &QSystemTrayIcon::activated, this, &MainWindow::trayIconActivated);
@ -319,14 +292,15 @@ void MainWindow::connectSlots()
connect(ui->btnConfigureServer, &QPushButton::clicked, this, [this] { showConfigureServer(""); }); connect(ui->btnConfigureServer, &QPushButton::clicked, this, [this] { showConfigureServer(""); });
connect(ui->btnConfigureClient, &QPushButton::clicked, this, [this] { showConfigureClient(); }); connect(ui->btnConfigureClient, &QPushButton::clicked, this, [this] { showConfigureClient(); });
connect(ui->lblComputerName, &QLabel::linkActivated, this, &MainWindow::openSettings); connect(ui->lblComputerName, &QLabel::linkActivated, this, &MainWindow::openSettings);
connect(m_btnFingerprint, &QPushButton::clicked, this, &MainWindow::showMyFingerprint);
connect(ui->rbModeServer, &QRadioButton::toggled, this, &MainWindow::coreModeToggled); connect(ui->rbModeServer, &QRadioButton::toggled, this, &MainWindow::coreModeToggled);
connect(ui->rbModeClient, &QRadioButton::toggled, this, &MainWindow::coreModeToggled); connect(ui->rbModeClient, &QRadioButton::toggled, this, &MainWindow::coreModeToggled);
connect(m_logDock->toggleViewAction(), &QAction::toggled, this, &MainWindow::toggleLogVisible); connect(m_logDock->toggleViewAction(), &QAction::toggled, this, &MainWindow::toggleLogVisible);
connect(m_btnUpdate, &QPushButton::clicked, this, &MainWindow::openGetNewVersionUrl); connect(m_statusBar, &StatusBar::requestShowMyFingerprints, this, &MainWindow::showMyFingerprint);
connect(m_statusBar, &StatusBar::requestUpdateVersion, this, &MainWindow::openGetNewVersionUrl);
connect(&m_versionChecker, &VersionChecker::updateFound, m_statusBar, &StatusBar::updateFound);
connect(m_guiDupeChecker, &QLocalServer::newConnection, this, &MainWindow::showAndActivate); connect(m_guiDupeChecker, &QLocalServer::newConnection, this, &MainWindow::showAndActivate);
@ -384,7 +358,7 @@ void MainWindow::settingsChanged(const QString &key)
qWarning() << tr("invalid certificate, generating a new one"); qWarning() << tr("invalid certificate, generating a new one");
TlsUtility::generateCertificate(); TlsUtility::generateCertificate();
} }
updateSecurityIcon(m_lblSecurityStatus->isVisible()); updateSecurityIcon(m_statusBar->securityIconVisible());
return; return;
} }
} }
@ -401,12 +375,6 @@ void MainWindow::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
isVisible() ? hide() : showAndActivate(); isVisible() ? hide() : showAndActivate();
} }
void MainWindow::versionCheckerUpdateFound(const QString &version)
{
m_btnUpdate->setVisible(true);
m_btnUpdate->setToolTip(tr("A new version v%1 is available").arg(version));
}
void MainWindow::coreProcessError(CoreProcess::Error error) void MainWindow::coreProcessError(CoreProcess::Error error)
{ {
if (error == CoreProcess::Error::AddressMissing) { if (error == CoreProcess::Error::AddressMissing) {
@ -415,12 +383,17 @@ void MainWindow::coreProcessError(CoreProcess::Error error)
); );
} else if (error == CoreProcess::Error::StartFailed) { } else if (error == CoreProcess::Error::StartFailed) {
show(); show();
QMessageBox::warning( auto message = tr("The Core executable could not be started.\n"
this, tr("Core cannot be started"), "Please check if you have sufficient permissions to run %1.")
tr("The Core executable could not be successfully started, " .arg(kCoreBinName);
"although it does exist. "
"Please check if you have sufficient permissions to run this program.") if (Settings::value(Settings::Core::CoreMode) == Settings::CoreMode::Server) {
); const auto mode =
Settings::value(Settings::Server::ExternalConfigFile).toBool() ? tr("read") : tr("read and write");
message.append(tr("\nAdditionally, check you are able to %1 the server config file: %2")
.arg(mode, Settings::serverConfigFile()));
}
QMessageBox::warning(this, kAppName, message);
} }
} }
@ -428,13 +401,13 @@ void MainWindow::startCore()
{ {
// Save current IP state when server starts // Save current IP state when server starts
if (m_coreProcess.mode() == CoreMode::Server && Settings::value(Settings::Core::Interface).toString().isEmpty()) { if (m_coreProcess.mode() == CoreMode::Server && Settings::value(Settings::Core::Interface).toString().isEmpty()) {
m_serverStartIPs = m_networkMonitor->getAvailableIPv4Addresses(); m_serverStartIPs = NetworkMonitor::validAddresses();
m_serverStartSuggestedIP = m_serverStartIPs.isEmpty() ? "" : m_serverStartIPs.first(); m_serverStartSuggestedIP = m_serverStartIPs.isEmpty() ? "" : m_serverStartIPs.first();
} }
m_coreProcess.start();
m_actionStartCore->setVisible(false); m_actionStartCore->setVisible(false);
m_actionRestartCore->setVisible(true); m_actionRestartCore->setVisible(true);
m_coreProcess.start();
} }
void MainWindow::stopCore() void MainWindow::stopCore()
@ -499,7 +472,7 @@ void MainWindow::openGetNewVersionUrl() const
void MainWindow::openSettings() void MainWindow::openSettings()
{ {
auto dialog = SettingsDialog(this, m_serverConfig, m_coreProcess); auto dialog = SettingsDialog(this, m_serverConfig);
if (dialog.exec() == QDialog::Accepted) { if (dialog.exec() == QDialog::Accepted) {
Settings::save(); Settings::save();
@ -559,7 +532,7 @@ void MainWindow::updateModeControls()
ui->lblIpAddresses->setVisible(isServer); ui->lblIpAddresses->setVisible(isServer);
ui->clientOptions->setVisible(isClient); ui->clientOptions->setVisible(isClient);
ui->lblNoMode->setVisible(!isServer && !isClient); ui->lblNoMode->setVisible(!isServer && !isClient);
toggleCanRunCore((isServer || isClient) && (isClient && !ui->lineHostname->text().isEmpty()) || isServer); toggleCanRunCore(canRunCore());
if (isServer) { if (isServer) {
updateNetworkInfo(); updateNetworkInfo();
@ -610,23 +583,15 @@ void MainWindow::updateModeControlLabels()
void MainWindow::updateSecurityIcon(bool visible) void MainWindow::updateSecurityIcon(bool visible)
{ {
m_lblSecurityStatus->setVisible(visible); m_statusBar->setSecurityIconVisible(visible);
if (!visible) if (!visible)
return; return;
m_statusBar->setSecurityIcon(TlsUtility::isEnabled());
bool secureSocket = TlsUtility::isEnabled();
const auto txt =
secureSocket ? tr("%1 Encryption Enabled").arg(m_coreProcess.secureSocketVersion()) : tr("Encryption Disabled");
m_lblSecurityStatus->setToolTip(txt);
const auto icon = QIcon::fromTheme(secureSocket ? QIcon::ThemeIcon::SecurityHigh : QIcon::ThemeIcon::SecurityLow);
m_lblSecurityStatus->setPixmap(icon.pixmap(QSize(32, 32)));
} }
void MainWindow::updateNetworkInfo() void MainWindow::updateNetworkInfo()
{ {
updateIpLabel(m_networkMonitor->getAvailableIPv4Addresses()); updateIpLabel(NetworkMonitor::validAddresses());
} }
void MainWindow::serverConnectionConfigureClient(const QString &clientName) void MainWindow::serverConnectionConfigureClient(const QString &clientName)
@ -645,7 +610,13 @@ void MainWindow::serverConnectionConfigureClient(const QString &clientName)
void MainWindow::open() void MainWindow::open()
{ {
Settings::value(Settings::Gui::Autohide).toBool() ? hide() : showAndActivate(); if (!Settings::value(Settings::Gui::Autohide).toBool())
showAndActivate();
else if (deskflow::platform::isMac())
// macOS to call hide after this function ends
QTimer::singleShot(1, this, &MainWindow::hide);
else
hide();
// if a critical error was shown just before the main window (i.e. on app // if a critical error was shown just before the main window (i.e. on app
// load), it will be hidden behind the main window. therefore we need to raise // load), it will be hidden behind the main window. therefore we need to raise
@ -674,11 +645,6 @@ void MainWindow::open()
} }
} }
void MainWindow::setStatus(const QString &status)
{
m_lblStatus->setText(status);
}
void MainWindow::createMenuBar() void MainWindow::createMenuBar()
{ {
m_menuFile->addAction(m_actionStartCore); m_menuFile->addAction(m_actionStartCore);
@ -902,71 +868,22 @@ void MainWindow::showFirstConnectedMessage()
void MainWindow::updateStatus() void MainWindow::updateStatus()
{ {
using enum ProcessState;
const auto connection = m_coreProcess.connectionState(); const auto connection = m_coreProcess.connectionState();
const auto process = m_coreProcess.processState(); const auto process = m_coreProcess.processState();
const bool isServer = (m_coreProcess.mode() == CoreMode::Server); const bool isServer = (m_coreProcess.mode() == CoreMode::Server);
if (process == Stopped || process == Started) {
updateSecurityIcon(false);
switch (process) {
using enum CoreProcessState;
case Starting:
setStatus(tr("%1 is starting...").arg(kAppName));
break;
case RetryPending:
setStatus(tr("%1 will retry in a moment...").arg(kAppName));
break;
case Stopping:
setStatus(tr("%1 is stopping...").arg(kAppName));
break;
case Stopped:
updateNetworkInfo(); updateNetworkInfo();
setStatus(tr("%1 is not running").arg(kAppName)); ui->btnEditName->setVisible(process == Stopped);
break;
case Started: {
updateNetworkInfo();
switch (connection) {
using enum CoreConnectionState;
case Listening: {
if (isServer) {
updateSecurityIcon(true);
setStatus(tr("%1 is waiting for clients").arg(kAppName));
}
break;
}
case Connecting:
setStatus(tr("%1 is connecting...").arg(kAppName));
break;
case Connected: {
updateSecurityIcon(true);
if (!isServer) {
setStatus(tr("%1 is connected as client of %2")
.arg(kAppName, Settings::value(Settings::Client::RemoteHost).toString()));
}
break;
}
case Disconnected:
setStatus(tr("%1 is disconnected").arg(kAppName));
break;
}
} break;
} }
m_statusBar->setStatus(connection, process, isServer);
} }
void MainWindow::coreProcessStateChanged(CoreProcessState state) void MainWindow::coreProcessStateChanged(ProcessState state)
{ {
using enum ProcessState;
updateStatus(); updateStatus();
if (state == Started) {
if (state == CoreProcessState::Started) {
qDebug() << "recording that core has started"; qDebug() << "recording that core has started";
Settings::setValue(Settings::Gui::AutoStartCore, true); Settings::setValue(Settings::Gui::AutoStartCore, true);
if (m_coreProcess.mode() == CoreMode::Server && if (m_coreProcess.mode() == CoreMode::Server &&
@ -977,8 +894,7 @@ void MainWindow::coreProcessStateChanged(CoreProcessState state)
} }
} }
if (state == CoreProcessState::Started || state == CoreProcessState::Starting || if (state == Started || state == Starting || state == RetryPending) {
state == CoreProcessState::RetryPending) {
disconnect(ui->btnToggleCore, &QPushButton::clicked, m_actionStartCore, &QAction::trigger); disconnect(ui->btnToggleCore, &QPushButton::clicked, m_actionStartCore, &QAction::trigger);
connect(ui->btnToggleCore, &QPushButton::clicked, m_actionStopCore, &QAction::trigger, Qt::UniqueConnection); connect(ui->btnToggleCore, &QPushButton::clicked, m_actionStopCore, &QAction::trigger, Qt::UniqueConnection);
@ -987,10 +903,7 @@ void MainWindow::coreProcessStateChanged(CoreProcessState state)
m_actionRestartCore->setVisible(true); m_actionRestartCore->setVisible(true);
m_actionStopCore->setEnabled(true); m_actionStopCore->setEnabled(true);
if (state == CoreProcessState::Starting) { if (state == Starting) {
if (deskflow::platform::isWayland()) {
m_waylandWarnings.showOnce(this);
}
saveSettings(); saveSettings();
} }
@ -1006,7 +919,7 @@ void MainWindow::coreProcessStateChanged(CoreProcessState state)
updateModeControlLabels(); updateModeControlLabels();
} }
void MainWindow::coreConnectionStateChanged(CoreConnectionState state) void MainWindow::coreConnectionStateChanged(ConnectionState state)
{ {
qDebug() << "core connection state changed: " << static_cast<int>(state); qDebug() << "core connection state changed: " << static_cast<int>(state);
@ -1015,7 +928,7 @@ void MainWindow::coreConnectionStateChanged(CoreConnectionState state)
// always assume connection is not secure when connection changes // always assume connection is not secure when connection changes
// to anything except connected. the only way the padlock shows is // to anything except connected. the only way the padlock shows is
// when the correct TLS version string is detected. // when the correct TLS version string is detected.
if (state != CoreConnectionState::Connected) { if (state != ConnectionState::Connected) {
secureSocket(false); secureSocket(false);
} else if (isVisible()) { } else if (isVisible()) {
showFirstConnectedMessage(); showFirstConnectedMessage();
@ -1024,7 +937,7 @@ void MainWindow::coreConnectionStateChanged(CoreConnectionState state)
void MainWindow::updateLocalFingerprint() void MainWindow::updateLocalFingerprint()
{ {
m_btnFingerprint->setVisible(TlsUtility::isEnabled() && !m_fingerprint.data.isEmpty()); m_statusBar->setBtnFingerprintVisible(TlsUtility::isEnabled() && !m_fingerprint.data.isEmpty());
} }
void MainWindow::hide() void MainWindow::hide()
@ -1055,6 +968,20 @@ void MainWindow::changeEvent(QEvent *e)
} }
} }
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj != ui->lineEditName || event->type() != QEvent::KeyPress)
return false;
if (const auto keyEvent = static_cast<QKeyEvent *>(event); keyEvent->key() != Qt::Key_Escape)
return false;
ui->lineEditName->hide();
ui->lblComputerName->show();
ui->btnEditName->show();
ui->lineEditName->setText(Settings::value(Settings::Core::ComputerName).toString());
toggleCanRunCore(canRunCore());
return true;
}
void MainWindow::updateText() void MainWindow::updateText()
{ {
m_menuFile->setTitle(tr("&File")); m_menuFile->setTitle(tr("&File"));
@ -1088,10 +1015,6 @@ void MainWindow::updateText()
m_actionQuit->setShortcut(QKeySequence(tr("Ctrl+Q"))); m_actionQuit->setShortcut(QKeySequence(tr("Ctrl+Q")));
m_actionTrayQuit->setShortcut(QKeySequence(tr("Ctrl+Q"))); m_actionTrayQuit->setShortcut(QKeySequence(tr("Ctrl+Q")));
} }
// General controls
m_btnFingerprint->setToolTip(tr("View local fingerprint"));
m_btnUpdate->setText(tr("Update available"));
} }
void MainWindow::showConfigureServer(const QString &message) void MainWindow::showConfigureServer(const QString &message)
@ -1114,7 +1037,7 @@ void MainWindow::showConfigureClient()
void MainWindow::secureSocket(bool secureSocket) void MainWindow::secureSocket(bool secureSocket)
{ {
m_secureSocket = secureSocket; m_secureSocket = secureSocket;
updateSecurityIcon(m_lblSecurityStatus->isVisible()); updateSecurityIcon(m_statusBar->securityIconVisible());
} }
void MainWindow::updateScreenName() void MainWindow::updateScreenName()
@ -1145,6 +1068,7 @@ void MainWindow::showHostNameEditor()
ui->lineEditName->show(); ui->lineEditName->show();
ui->lblComputerName->hide(); ui->lblComputerName->hide();
ui->btnEditName->hide(); ui->btnEditName->hide();
toggleCanRunCore(false);
ui->lineEditName->setFocus(); ui->lineEditName->setFocus();
} }
@ -1153,6 +1077,7 @@ void MainWindow::setHostName()
ui->lineEditName->hide(); ui->lineEditName->hide();
ui->lblComputerName->show(); ui->lblComputerName->show();
ui->btnEditName->show(); ui->btnEditName->show();
toggleCanRunCore(canRunCore());
QString text = ui->lineEditName->text(); QString text = ui->lineEditName->text();
const auto screenName = Settings::value(Settings::Core::ComputerName).toString(); const auto screenName = Settings::value(Settings::Core::ComputerName).toString();
@ -1216,21 +1141,7 @@ void MainWindow::serverClientsChanged(const QStringList &clients)
{ {
if (m_coreProcess.mode() != CoreMode::Server || !m_coreProcess.isStarted()) if (m_coreProcess.mode() != CoreMode::Server || !m_coreProcess.isStarted())
return; return;
m_statusBar->setServerClients(clients);
if (clients.isEmpty()) {
setStatus(tr("%1 is waiting for clients").arg(kAppName));
ui->statusBar->setToolTip("");
return;
}
//: Shown when in server mode and at least 1 client is connected
//: %1 is replaced by the app name
//: %2 will be a list of at least one client
//: %n will be replaced by the number of clients (n is >=1), it is not requried to be in the translation
setStatus(tr("%1 is connected, with %n client(s): %2", "", clients.size()).arg(kAppName, clients.join(", ")));
const auto toolTipString = clients.count() == 1 ? "" : tr("Clients:\n %1").arg(clients.join("\n"));
ui->statusBar->setToolTip(toolTipString);
} }
void MainWindow::daemonIpcClientConnectionFailed() void MainWindow::daemonIpcClientConnectionFailed()
@ -1262,12 +1173,22 @@ void MainWindow::remoteHostChanged(const QString &newRemoteHost)
void MainWindow::showClientError(deskflow::client::ErrorType error, const QString &address) void MainWindow::showClientError(deskflow::client::ErrorType error, const QString &address)
{ {
if (!Settings::value(Settings::Gui::ShowGenericClientFailureDialog).toBool() || !isVisible() || m_clientErrorVisible) if (!isVisible() || m_clientErrorVisible || error != deskflow::client::ErrorType::AlreadyConnected)
return; return;
m_clientErrorVisible = true; m_clientErrorVisible = true;
showAndActivate(); showAndActivate();
deskflow::gui::messages::showClientConnectError(this, error, address);
QMessageBox::warning(
this, tr("%1 Connection Error").arg(kAppName),
tr("<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>")
.arg(address)
);
m_clientErrorVisible = false; m_clientErrorVisible = false;
} }
@ -1324,3 +1245,11 @@ void MainWindow::updateIpLabel(const QStringList &addresses)
ui->lblIpAddresses->setText(labelText); ui->lblIpAddresses->setText(labelText);
ui->lblIpAddresses->setToolTip(toolTipText); ui->lblIpAddresses->setToolTip(toolTipText);
} }
bool MainWindow::canRunCore() const
{
const auto mode = m_coreProcess.mode();
const bool isServer = mode == Settings::CoreMode::Server;
const bool isClient = mode == Settings::CoreMode::Client;
return ((isServer || isClient) && (isClient && !ui->lineHostname->text().isEmpty()) || isServer);
}

View File

@ -24,7 +24,6 @@
#include "gui/core/CoreProcess.h" #include "gui/core/CoreProcess.h"
#include "gui/core/NetworkMonitor.h" #include "gui/core/NetworkMonitor.h"
#include "gui/core/ServerConnection.h" #include "gui/core/ServerConnection.h"
#include "gui/core/WaylandWarnings.h"
#include "net/Fingerprint.h" #include "net/Fingerprint.h"
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
@ -48,6 +47,7 @@ class QLocalServer;
class DeskflowApplication; class DeskflowApplication;
class LogDock; class LogDock;
class StatusBar;
namespace Ui { namespace Ui {
class MainWindow; class MainWindow;
@ -59,9 +59,11 @@ class DaemonIpcClient;
class MainWindow : public QMainWindow class MainWindow : public QMainWindow
{ {
using ConnectionState = deskflow::core::ConnectionState;
using CoreMode = Settings::CoreMode; using CoreMode = Settings::CoreMode;
using CoreProcess = deskflow::gui::CoreProcess; using CoreProcess = deskflow::gui::CoreProcess;
using NetworkMonitor = deskflow::gui::NetworkMonitor; using NetworkMonitor = deskflow::gui::NetworkMonitor;
using ProcessState = deskflow::core::ProcessState;
Q_OBJECT Q_OBJECT
@ -93,6 +95,7 @@ public:
protected: protected:
void changeEvent(QEvent *e) override; void changeEvent(QEvent *e) override;
bool eventFilter(QObject *obj, QEvent *event) override;
private: private:
/** /**
@ -104,9 +107,9 @@ private:
void settingsChanged(const QString &key = QString()); void settingsChanged(const QString &key = QString());
void serverConfigSaving(); void serverConfigSaving();
void coreProcessError(CoreProcess::Error error); void coreProcessError(CoreProcess::Error error);
void coreConnectionStateChanged(CoreProcess::ConnectionState state); void coreConnectionStateChanged(ConnectionState state);
void coreProcessStateChanged(CoreProcess::ProcessState state); void coreProcessStateChanged(ProcessState state);
void versionCheckerUpdateFound(const QString &version);
void trayIconActivated(QSystemTrayIcon::ActivationReason reason); void trayIconActivated(QSystemTrayIcon::ActivationReason reason);
void serverConnectionConfigureClient(const QString &clientName); void serverConnectionConfigureClient(const QString &clientName);
@ -133,7 +136,6 @@ private:
void setupTrayIcon(); void setupTrayIcon();
void applyConfig(); void applyConfig();
void setTrayIcon(); void setTrayIcon();
void setStatus(const QString &status);
void updateFromLogLine(const QString &line); void updateFromLogLine(const QString &line);
void checkConnected(const QString &line); void checkConnected(const QString &line);
void checkFingerprint(const QString &line); void checkFingerprint(const QString &line);
@ -159,6 +161,7 @@ private:
void handleNewClientPromptRequest(const QString &clientName, bool usePeerAuth); void handleNewClientPromptRequest(const QString &clientName, bool usePeerAuth);
void updateIpLabel(const QStringList &addresses); void updateIpLabel(const QStringList &addresses);
bool canRunCore() const;
/** /**
* @brief showClientError * @brief showClientError
* @param error Error Type * @param error Error Type
@ -189,7 +192,6 @@ private:
bool m_secureSocket = false; bool m_secureSocket = false;
bool m_saveOnExit = true; bool m_saveOnExit = true;
bool m_clientErrorVisible = false; bool m_clientErrorVisible = false;
deskflow::gui::core::WaylandWarnings m_waylandWarnings;
ServerConfig m_serverConfig; ServerConfig m_serverConfig;
deskflow::gui::CoreProcess m_coreProcess; deskflow::gui::CoreProcess m_coreProcess;
deskflow::gui::ServerConnection m_serverConnection; deskflow::gui::ServerConnection m_serverConnection;
@ -202,10 +204,7 @@ private:
deskflow::gui::ipc::DaemonIpcClient *m_daemonIpcClient = nullptr; deskflow::gui::ipc::DaemonIpcClient *m_daemonIpcClient = nullptr;
LogDock *m_logDock; LogDock *m_logDock;
QLabel *m_lblSecurityStatus = nullptr; StatusBar *m_statusBar = nullptr;
QLabel *m_lblStatus = nullptr;
QPushButton *m_btnFingerprint = nullptr;
QPushButton *m_btnUpdate = nullptr;
// Window Menu // Window Menu
QMenu *m_menuFile = nullptr; QMenu *m_menuFile = nullptr;

View File

@ -497,7 +497,6 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QStatusBar" name="statusBar"/>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>

View File

@ -8,7 +8,6 @@
#include "Logger.h" #include "Logger.h"
#include "common/Enums.h"
#include "common/Settings.h" #include "common/Settings.h"
#include "common/UrlConstants.h" #include "common/UrlConstants.h"
#include "common/VersionInfo.h" #include "common/VersionInfo.h"
@ -183,61 +182,6 @@ void showFirstConnectedMessage(QWidget *parent, bool closeToTray, bool enableSer
QMessageBox::information(parent, title, message); QMessageBox::information(parent, title, message);
} }
void showClientConnectError(QWidget *parent, deskflow::client::ErrorType error, const QString &address)
{
using enum deskflow::client::ErrorType;
if (error == NoError)
return;
auto message = QObject::tr("<p>Failed to connect to the server '%1'.</p>").arg(address);
if (error == AlreadyConnected) {
message.append(
QObject::tr( //
"<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>"
)
);
} else if (error == HostnameError) {
message.append(
QObject::tr( //
"<p>Please try to connect to the server using the server IP address "
"instead of the hostname. </p>"
"<p>If that doesn't work, please check your TLS and "
"firewall settings.</p>"
)
);
} else if (error == GenericError) {
message.append(QObject::tr("<p>Please check your TLS and firewall settings.</p>"));
} else {
qFatal("unknown client error");
}
auto title = QObject::tr("%1 Connection Error").arg(kAppName);
if (error != HostnameError) {
QMessageBox::warning(parent, title, message);
return;
}
auto dialog = QMessageBox(parent);
dialog.setWindowTitle(title);
dialog.setText(message);
dialog.setWindowModality(Qt::ApplicationModal);
dialog.setIcon(QMessageBox::Information);
auto cbNoShowAgain = new QCheckBox(QObject::tr("Do not show this message again"));
QObject::connect(cbNoShowAgain, &QCheckBox::toggled, [](bool enabled) {
Settings::setValue(Settings::Gui::ShowGenericClientFailureDialog, !enabled);
});
dialog.setCheckBox(cbNoShowAgain);
dialog.setDefaultButton(QMessageBox::Ok);
dialog.exec();
}
bool showNewClientPrompt(QWidget *parent, const QString &clientName, bool serverRequiresPeerAuth) bool showNewClientPrompt(QWidget *parent, const QString &clientName, bool serverRequiresPeerAuth)
{ {
if (serverRequiresPeerAuth) { if (serverRequiresPeerAuth) {
@ -280,24 +224,6 @@ void showReadOnlySettings(QWidget *parent, const QString &systemSettingsPath)
QMessageBox::information(parent, title, message); QMessageBox::information(parent, title, message);
} }
void showWaylandLibraryError(QWidget *parent)
{
QMessageBox::critical(
parent, kAppName,
QObject::tr(
"<p>Sorry, while this version of %1 does support Wayland, "
"this build was not linked with one or more of the required "
"libraries.</p>"
"<p>Please either switch to X from your login screen or use a build "
"that uses the correct libraries.</p>"
"<p>If you think this is incorrect, please "
R"(<a href="%2">report a bug</a>.</p>)"
"<p>Please check the logs for more information.</p>"
)
.arg(kAppName, kUrlHelp)
);
}
bool showUpdateCheckOption(QWidget *parent) bool showUpdateCheckOption(QWidget *parent)
{ {
QMessageBox message(parent); QMessageBox message(parent);

View File

@ -25,16 +25,12 @@ void showFirstConnectedMessage(QWidget *parent, bool closeToTray, bool enableSer
void showCloseReminder(QWidget *parent); void showCloseReminder(QWidget *parent);
void showClientConnectError(QWidget *parent, deskflow::client::ErrorType error, const QString &address);
bool showNewClientPrompt(QWidget *parent, const QString &clientName, bool serverRequiresPeerAuth = false); bool showNewClientPrompt(QWidget *parent, const QString &clientName, bool serverRequiresPeerAuth = false);
bool showClearSettings(QWidget *parent); bool showClearSettings(QWidget *parent);
void showReadOnlySettings(QWidget *parent, const QString &systemSettingsPath); void showReadOnlySettings(QWidget *parent, const QString &systemSettingsPath);
void showWaylandLibraryError(QWidget *parent);
bool showUpdateCheckOption(QWidget *parent); bool showUpdateCheckOption(QWidget *parent);
bool showDaemonOffline(QWidget *parent); bool showDaemonOffline(QWidget *parent);

View File

@ -14,6 +14,11 @@
#include "OSXHelpers.h" #include "OSXHelpers.h"
#endif #endif
#ifdef Q_OS_LINUX
#include <signal.h>
#include <sys/prctl.h>
#endif
#include <QCoreApplication> #include <QCoreApplication>
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
@ -203,6 +208,14 @@ void CoreProcess::startForegroundProcess(const QStringList &args)
const auto quoted = makeQuotedArgs(m_appPath, args); const auto quoted = makeQuotedArgs(m_appPath, args);
qInfo("running command: %s", qPrintable(quoted)); qInfo("running command: %s", qPrintable(quoted));
#ifdef Q_OS_LINUX
m_process->setChildProcessModifier([] {
// the core process becomes orphaned when the gui process exits abruptly (e.g. with kill -9),
// so ensure the os also kills the core when that happens to the gui.
prctl(PR_SET_PDEATHSIG, SIGTERM);
});
#endif
m_process->start(m_appPath, args); m_process->start(m_appPath, args);
if (m_process->waitForStarted()) { if (m_process->waitForStarted()) {
@ -330,11 +343,17 @@ void CoreProcess::start(std::optional<ProcessMode> processModeOption)
QStringList args = {coreMode}; QStringList args = {coreMode};
if (m_mode == Settings::CoreMode::Server) { if (m_mode == Settings::CoreMode::Server) {
const auto configFilename = persistServerConfig(); const auto [hasNeededPermissions, configFilename] = persistServerConfig();
if (configFilename.isEmpty()) { if (configFilename.isEmpty()) {
qFatal("config file name empty for server args"); qFatal("config file name empty for server args");
return; return;
} }
if (!hasNeededPermissions) {
setProcessState(ProcessState::Stopped);
setConnectionState(ConnectionState::Disconnected);
Q_EMIT error(Error::StartFailed);
return;
}
qInfo("core config file: %s", qPrintable(configFilename)); qInfo("core config file: %s", qPrintable(configFilename));
} }
@ -417,21 +436,22 @@ void CoreProcess::cleanup()
} }
} }
QString CoreProcess::persistServerConfig() const QPair<bool, QString> CoreProcess::persistServerConfig() const
{ {
if (Settings::value(Settings::Server::ExternalConfig).toBool()) { if (Settings::value(Settings::Server::ExternalConfig).toBool()) {
return Settings::value(Settings::Server::ExternalConfigFile).toString(); return {Settings::isServerConfigFileReadable(), Settings::value(Settings::Server::ExternalConfigFile).toString()};
} }
const auto configFilePath = Settings::defaultValue(Settings::Server::ExternalConfigFile).toString(); const auto configFilePath = Settings::defaultValue(Settings::Server::ExternalConfigFile).toString();
QFile configFile(configFilePath); QFile configFile(configFilePath);
if (!configFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { if (!configFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
qFatal("failed to open core config file for write: %s", qPrintable(configFile.fileName())); qWarning() << "failed to open core config file for write:" << configFilePath;
return {false, configFile.fileName()};
} }
m_serverConfig.save(configFile); m_serverConfig.save(configFile);
configFile.close(); configFile.close();
return configFile.fileName(); return {Settings::isServerConfigFileReadable(), configFile.fileName()};
} }
void CoreProcess::setConnectionState(ConnectionState state) void CoreProcess::setConnectionState(ConnectionState state)
@ -498,7 +518,11 @@ bool CoreProcess::checkSecureSocket(const QString &line)
} }
Q_EMIT secureSocket(true); Q_EMIT secureSocket(true);
m_secureSocketVersion = line.mid(index + tlsCheckString.size()); if (const auto ssv = line.mid(index + tlsCheckString.size()); ssv != m_secureSocketVersion) {
m_secureSocketVersion = ssv;
Q_EMIT securityLevelChanged(ssv);
}
return true; return true;
} }

View File

@ -7,6 +7,7 @@
#pragma once #pragma once
#include "common/Enums.h"
#include "common/Settings.h" #include "common/Settings.h"
#include "gui/FileTail.h" #include "gui/FileTail.h"
#include "gui/config/IServerConfig.h" #include "gui/config/IServerConfig.h"
@ -24,7 +25,9 @@ class DaemonIpcClient;
class CoreProcess : public QObject class CoreProcess : public QObject
{ {
using ConnectionState = deskflow::core::ConnectionState;
using ProcessMode = Settings::ProcessMode; using ProcessMode = Settings::ProcessMode;
using ProcessState = deskflow::core::ProcessState;
using IServerConfig = deskflow::gui::IServerConfig; using IServerConfig = deskflow::gui::IServerConfig;
Q_OBJECT Q_OBJECT
@ -35,23 +38,6 @@ public:
AddressMissing, AddressMissing,
StartFailed StartFailed
}; };
enum class ProcessState
{
Starting,
Started,
Stopping,
Stopped,
RetryPending
};
Q_ENUM(ProcessState)
enum class ConnectionState
{
Disconnected,
Connecting,
Connected,
Listening
};
explicit CoreProcess(const IServerConfig &serverConfig); explicit CoreProcess(const IServerConfig &serverConfig);
@ -98,10 +84,11 @@ public:
Q_SIGNALS: Q_SIGNALS:
void error(deskflow::gui::CoreProcess::Error error); void error(deskflow::gui::CoreProcess::Error error);
void logLine(const QString &line); void logLine(const QString &line);
void connectionStateChanged(deskflow::gui::CoreProcess::ConnectionState state); void connectionStateChanged(deskflow::core::ConnectionState state);
void processStateChanged(deskflow::gui::CoreProcess::ProcessState state); void processStateChanged(deskflow::core::ProcessState state);
void secureSocket(bool enabled); void secureSocket(bool enabled);
void daemonIpcClientConnectionFailed(); void daemonIpcClientConnectionFailed();
void securityLevelChanged(QString securityLevel);
private Q_SLOTS: private Q_SLOTS:
void onProcessFinished(int exitCode, QProcess::ExitStatus); void onProcessFinished(int exitCode, QProcess::ExitStatus);
@ -114,7 +101,7 @@ private:
void startProcessFromDaemon(const QStringList &args); void startProcessFromDaemon(const QStringList &args);
void stopForegroundProcess() const; void stopForegroundProcess() const;
void stopProcessFromDaemon(); void stopProcessFromDaemon();
QString persistServerConfig() const; QPair<bool, QString> persistServerConfig() const;
void setConnectionState(ConnectionState state); void setConnectionState(ConnectionState state);
void setProcessState(ProcessState state); void setProcessState(ProcessState state);
void checkLogLine(const QString &line); void checkLogLine(const QString &line);

View File

@ -19,7 +19,7 @@ bool NetworkMonitor::isVirtualInterface(const QString &interfaceName)
{ {
// Common virtual network interface patterns // Common virtual network interface patterns
static const auto virtualRegEx = QRegularExpression( static const auto virtualRegEx = QRegularExpression(
QStringLiteral("^vboxnet|vmnet|docker|virbr|veth|br\\-|tun|utun|awdl|p2p|llw|anpi|tap"), QStringLiteral("^vboxnet|vmnet|docker|virbr|veth|br\\-|tun|utun|awdl|p2p|llw|anpi|tap|vEth"),
QRegularExpression::CaseInsensitiveOption QRegularExpression::CaseInsensitiveOption
); );
return virtualRegEx.match(interfaceName).hasMatch(); return virtualRegEx.match(interfaceName).hasMatch();
@ -52,10 +52,12 @@ void NetworkMonitor::stopMonitoring()
m_isMonitoring = false; m_isMonitoring = false;
} }
QStringList NetworkMonitor::getAvailableIPv4Addresses() const QStringList NetworkMonitor::validAddresses()
{ {
QList<QHostAddress> physicalIPs; QList<QHostAddress> physicalIP4;
QList<QHostAddress> virtualIPs; QList<QHostAddress> physicalIP6;
QList<QHostAddress> virtualIP4;
QList<QHostAddress> virtualIP6;
QSet<QHostAddress> uniqueAddresses; QSet<QHostAddress> uniqueAddresses;
const auto allInterfaces = QNetworkInterface::allInterfaces(); const auto allInterfaces = QNetworkInterface::allInterfaces();
@ -66,39 +68,57 @@ QStringList NetworkMonitor::getAvailableIPv4Addresses() const
} }
const bool isP2P = (interface.flags() & QNetworkInterface::IsPointToPoint); const bool isP2P = (interface.flags() & QNetworkInterface::IsPointToPoint);
const bool isVirtual = isVirtualInterface(interface.name()) || isP2P; const bool isVirtualType = interface.type() == QNetworkInterface::Virtual;
const bool isVirtual = isVirtualInterface(interface.humanReadableName()) || isP2P || isVirtualType;
const auto addressEntries = interface.addressEntries(); const auto addressEntries = interface.addressEntries();
for (const auto &entry : addressEntries) { for (const auto &entry : addressEntries) {
const QHostAddress address = entry.ip(); const QHostAddress address = entry.ip();
if (address.protocol() != QAbstractSocket::IPv4Protocol || address.isLinkLocal() || address.isLoopback() || if (address.isLinkLocal() || address.isLoopback() || uniqueAddresses.contains(address)) {
uniqueAddresses.contains(address)) {
continue; continue;
} }
uniqueAddresses.insert(address); uniqueAddresses.insert(address);
if (isVirtual) { if (address.protocol() == QHostAddress::IPv6Protocol) {
virtualIPs.append(address); if (isVirtual)
virtualIP6.append(address);
else
physicalIP6.append(address);
} else { } else {
physicalIPs.append(address); if (isVirtual)
virtualIP4.append(address);
else
physicalIP4.append(address);
} }
} }
} }
std::ranges::sort(physicalIPs, [](const QHostAddress &a, const QHostAddress &b) { std::ranges::sort(physicalIP4, [](const QHostAddress &a, const QHostAddress &b) {
if (a.isPrivateUse() != b.isPrivateUse()) if (a.isPrivateUse() != b.isPrivateUse())
return a.isPrivateUse(); return a.isPrivateUse();
return a.toIPv4Address() < b.toIPv4Address(); return a.toIPv4Address() < b.toIPv4Address();
}); });
std::ranges::sort(virtualIPs, [](const QHostAddress &a, const QHostAddress &b) { std::ranges::sort(virtualIP4, [](const QHostAddress &a, const QHostAddress &b) {
return a.toIPv4Address() < b.toIPv4Address(); return a.toIPv4Address() < b.toIPv4Address();
}); });
auto result = physicalIPs; std::ranges::sort(physicalIP6, [](const QHostAddress &a, const QHostAddress &b) {
result.append(virtualIPs); if (a.isPrivateUse() != b.isPrivateUse())
return a.isPrivateUse();
return a.toString() < b.toString();
});
std::ranges::sort(virtualIP6, [](const QHostAddress &a, const QHostAddress &b) {
return a.toString() < b.toString();
});
auto result = physicalIP4;
result.append(virtualIP4);
result.append(physicalIP6);
result.append(virtualIP6);
QStringList ipList; QStringList ipList;
for (const auto &host : result) { for (const auto &host : result) {
@ -117,7 +137,7 @@ void NetworkMonitor::setIpAddresses(const QStringList &newAddresses)
void NetworkMonitor::updateNetworkState() void NetworkMonitor::updateNetworkState()
{ {
setIpAddresses(getAvailableIPv4Addresses()); setIpAddresses(validAddresses());
} }
} // namespace deskflow::gui } // namespace deskflow::gui

View File

@ -47,10 +47,10 @@ public:
void stopMonitoring(); void stopMonitoring();
/** /**
* @brief Get list of all available IPv4 addresses (excluding local and link-local addresses) * @brief Get list of all available IP addresses (excluding local and link-local addresses)
* @return IPv4 address list * @return IP address list
*/ */
QStringList getAvailableIPv4Addresses() const; static QStringList validAddresses();
/** /**
* @brief Check if a network interface is virtual * @brief Check if a network interface is virtual

View File

@ -1,31 +0,0 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Chris Rizzitello <sithlord48@gmail.com>
* SPDX-FileCopyrightText: (C) 2024 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "WaylandWarnings.h"
#include "Messages.h"
#include "common/Settings.h"
namespace deskflow::gui::core {
void WaylandWarnings::showOnce(QWidget *parent)
{
const auto mode = Settings::value(Settings::Core::CoreMode).value<Settings::CoreMode>();
const bool portalIcProblem = !m_hasPortalInputCapture && mode == Settings::CoreMode::Server;
if (!m_hasEi || !m_hasPortal || portalIcProblem) {
if (!m_errorShown) {
m_errorShown = true;
messages::showWaylandLibraryError(parent);
} else {
qWarning("missing required wayland lib(s) or feature");
}
return;
}
}
} // namespace deskflow::gui::core

View File

@ -1,43 +0,0 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Chris Rizzitello <sithlord48@gmail.com>
* SPDX-FileCopyrightText: (C) 2024 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#pragma once
#include <QWidget>
namespace deskflow::gui::core {
class WaylandWarnings
{
public:
explicit WaylandWarnings() = default;
void showOnce(QWidget *parent);
private:
bool m_errorShown{false};
#if WINAPI_LIBEI
const bool m_hasEi = true;
#else
const bool m_hasEi = false;
#endif
#if WINAPI_LIBPORTAL
const bool m_hasPortal = true;
#else
const bool m_hasPortal = false;
#endif
#if HAVE_LIBPORTAL_INPUTCAPTURE
const bool m_hasPortalInputCapture = true;
#else
const bool m_hasPortalInputCapture = false;
#endif
};
} // namespace deskflow::gui::core

View File

@ -23,15 +23,18 @@
using namespace deskflow::gui; using namespace deskflow::gui;
SettingsDialog::SettingsDialog(QWidget *parent, const IServerConfig &serverConfig, const CoreProcess &coreProcess) SettingsDialog::SettingsDialog(QWidget *parent, const IServerConfig &serverConfig)
: QDialog(parent), : QDialog(parent),
ui{std::make_unique<Ui::SettingsDialog>()}, ui{std::make_unique<Ui::SettingsDialog>()},
m_serverConfig(serverConfig), m_serverConfig(serverConfig)
m_coreProcess(coreProcess)
{ {
ui->setupUi(this); ui->setupUi(this);
// these are enabled by the control next to them
ui->lineCommandEnter->setEnabled(false);
ui->lineCommandExit->setEnabled(false);
// set up the language combo // set up the language combo
I18N::reDetectLanguages(); I18N::reDetectLanguages();
ui->comboLanguage->addItems(I18N::detectedLanguages()); ui->comboLanguage->addItems(I18N::detectedLanguages());
@ -51,14 +54,19 @@ SettingsDialog::SettingsDialog(QWidget *parent, const IServerConfig &serverConfi
ui->tabWidget->setCurrentIndex(0); ui->tabWidget->setCurrentIndex(0);
// Populate the list of IP addresses // Populate the list of IP addresses
NetworkMonitor networkMonitor(this); const auto validAddresses = NetworkMonitor::validAddresses();
for (const auto &address : networkMonitor.getAvailableIPv4Addresses()) { for (const auto &address : validAddresses) {
QString ipString = address; QString ipString = address;
if (ui->comboInterface->findText(ipString) == -1) { if (ui->comboInterface->findText(ipString) == -1) {
ui->comboInterface->addItem(ipString, ipString); ui->comboInterface->addItem(ipString, ipString);
} }
} }
if (const auto interface = Settings::value(Settings::Core::Interface).toString();
!interface.isEmpty() && (ui->comboInterface->findData(interface) == -1)) {
ui->comboInterface->addItem(interface, interface);
}
loadFromConfig(); loadFromConfig();
adjustSize(); adjustSize();
@ -66,6 +74,7 @@ SettingsDialog::SettingsDialog(QWidget *parent, const IServerConfig &serverConfi
setFixedHeight(height()); setFixedHeight(height());
setWindowFlags((windowFlags() | Qt::CustomizeWindowHint) & ~Qt::WindowMinMaxButtonsHint); setWindowFlags((windowFlags() | Qt::CustomizeWindowHint) & ~Qt::WindowMinMaxButtonsHint);
setButtonBoxEnabledButtons();
initConnections(); initConnections();
} }
@ -84,6 +93,14 @@ void SettingsDialog::initConnections() const
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &SettingsDialog::accept); connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &SettingsDialog::accept);
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(ui->buttonBox->button(QDialogButtonBox::Reset), &QPushButton::clicked, this, &SettingsDialog::loadFromConfig);
connect(
ui->buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this,
&SettingsDialog::resetToDefault
);
connect(ui->cbRunEnterCommand, &QCheckBox::toggled, ui->lineCommandEnter, &QLineEdit::setEnabled);
connect(ui->cbRunExitCommand, &QCheckBox::toggled, ui->lineCommandExit, &QLineEdit::setEnabled);
connect(ui->groupSecurity, &QGroupBox::toggled, this, &SettingsDialog::updateTlsControlsEnabled); connect(ui->groupSecurity, &QGroupBox::toggled, this, &SettingsDialog::updateTlsControlsEnabled);
connect(ui->groupService, &QGroupBox::toggled, this, &SettingsDialog::updateControls); connect(ui->groupService, &QGroupBox::toggled, this, &SettingsDialog::updateControls);
@ -91,12 +108,38 @@ void SettingsDialog::initConnections() const
connect(ui->comboTlsKeyLength, &QComboBox::currentIndexChanged, this, &SettingsDialog::updateRequestedKeySize); connect(ui->comboTlsKeyLength, &QComboBox::currentIndexChanged, this, &SettingsDialog::updateRequestedKeySize);
connect(ui->btnTlsCertPath, &QPushButton::clicked, this, &SettingsDialog::browseCertificatePath); connect(ui->btnTlsCertPath, &QPushButton::clicked, this, &SettingsDialog::browseCertificatePath);
connect(ui->btnBrowseLog, &QPushButton::clicked, this, &SettingsDialog::browseLogPath); connect(ui->btnBrowseLog, &QPushButton::clicked, this, &SettingsDialog::browseLogPath);
connect(ui->cbLogToFile, &QCheckBox::toggled, this, &SettingsDialog::setLogToFile); connect(ui->groupLogToFile, &QGroupBox::toggled, this, &SettingsDialog::setLogToFile);
connect(ui->comboLogLevel, &QComboBox::currentIndexChanged, this, &SettingsDialog::logLevelChanged); connect(ui->comboLogLevel, &QComboBox::currentIndexChanged, this, &SettingsDialog::logLevelChanged);
connect(ui->comboLanguage, &QComboBox::currentTextChanged, this, [](const QString &lang) { connect(ui->comboLanguage, &QComboBox::currentTextChanged, this, [](const QString &lang) {
const auto shortName = I18N::nativeTo639Name(lang); const auto shortName = I18N::nativeTo639Name(lang);
I18N::setLanguage(shortName); I18N::setLanguage(shortName);
}); });
// Connect modifiable controls
connect(ui->rbIconMono, &QRadioButton::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->sbPort, &QSpinBox::valueChanged, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->comboLogLevel, &QComboBox::currentIndexChanged, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->comboInterface, &QComboBox::currentIndexChanged, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->comboTlsKeyLength, &QComboBox::currentIndexChanged, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->comboLanguage, &QComboBox::currentIndexChanged, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->rbAutoHide, &QRadioButton::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->cbPreventSleep, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->rbCloseToTray, &QRadioButton::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->cbElevateDaemon, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->cbAutoUpdate, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->cbGuiDebug, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->cbUseWlClipboard, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->cbShowVersion, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->cbRequireClientCert, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->groupLogToFile, &QGroupBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->groupService, &QGroupBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->groupSecurity, &QGroupBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->lineLogFilename, &QLineEdit::textChanged, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->lineTlsCertPath, &QLineEdit::textChanged, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->cbRunEnterCommand, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->cbRunExitCommand, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->lineCommandEnter, &QLineEdit::textChanged, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->lineCommandExit, &QLineEdit::textChanged, this, &SettingsDialog::setButtonBoxEnabledButtons);
} }
void SettingsDialog::regenCertificates() void SettingsDialog::regenCertificates()
@ -165,6 +208,10 @@ void SettingsDialog::updateText()
ui->comboLogLevel->setItemData(5, tr("Debug entries"), Qt::ToolTipRole); ui->comboLogLevel->setItemData(5, tr("Debug entries"), Qt::ToolTipRole);
ui->comboLogLevel->setItemData(6, tr("More debug output"), Qt::ToolTipRole); ui->comboLogLevel->setItemData(6, tr("More debug output"), Qt::ToolTipRole);
ui->comboLogLevel->setItemData(7, tr("Verbose debug output"), Qt::ToolTipRole); ui->comboLogLevel->setItemData(7, tr("Verbose debug output"), Qt::ToolTipRole);
ui->buttonBox->button(QDialogButtonBox::Save)->setToolTip(tr("Close and save changes"));
ui->buttonBox->button(QDialogButtonBox::Cancel)->setToolTip(tr("Close and forget changes"));
ui->buttonBox->button(QDialogButtonBox::Reset)->setToolTip(tr("Reset to stored values"));
ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setToolTip(tr("Reset to default values"));
} }
void SettingsDialog::accept() void SettingsDialog::accept()
@ -172,22 +219,26 @@ void SettingsDialog::accept()
Settings::setValue(Settings::Core::Port, ui->sbPort->value()); Settings::setValue(Settings::Core::Port, ui->sbPort->value());
Settings::setValue(Settings::Core::Interface, ui->comboInterface->currentData()); Settings::setValue(Settings::Core::Interface, ui->comboInterface->currentData());
Settings::setValue(Settings::Log::Level, ui->comboLogLevel->currentIndex()); Settings::setValue(Settings::Log::Level, ui->comboLogLevel->currentIndex());
Settings::setValue(Settings::Log::ToFile, ui->cbLogToFile->isChecked()); Settings::setValue(Settings::Log::ToFile, ui->groupLogToFile->isChecked());
Settings::setValue(Settings::Log::File, ui->lineLogFilename->text()); Settings::setValue(Settings::Log::File, ui->lineLogFilename->text());
Settings::setValue(Settings::Daemon::Elevate, ui->cbElevateDaemon->isChecked()); Settings::setValue(Settings::Daemon::Elevate, ui->cbElevateDaemon->isChecked());
Settings::setValue(Settings::Gui::Autohide, ui->cbAutoHide->isChecked()); Settings::setValue(Settings::Gui::Autohide, ui->rbAutoHide->isChecked());
Settings::setValue(Settings::Gui::AutoUpdateCheck, ui->cbAutoUpdate->isChecked()); Settings::setValue(Settings::Gui::AutoUpdateCheck, ui->cbAutoUpdate->isChecked());
Settings::setValue(Settings::Core::PreventSleep, ui->cbPreventSleep->isChecked()); Settings::setValue(Settings::Core::PreventSleep, ui->cbPreventSleep->isChecked());
Settings::setValue(Settings::Security::Certificate, ui->lineTlsCertPath->text()); Settings::setValue(Settings::Security::Certificate, ui->lineTlsCertPath->text());
Settings::setValue(Settings::Security::KeySize, ui->comboTlsKeyLength->currentText().toInt()); Settings::setValue(Settings::Security::KeySize, ui->comboTlsKeyLength->currentText().toInt());
Settings::setValue(Settings::Security::TlsEnabled, ui->groupSecurity->isChecked()); Settings::setValue(Settings::Security::TlsEnabled, ui->groupSecurity->isChecked());
Settings::setValue(Settings::Gui::CloseToTray, ui->cbCloseToTray->isChecked()); Settings::setValue(Settings::Gui::CloseToTray, ui->rbCloseToTray->isChecked());
Settings::setValue(Settings::Gui::SymbolicTrayIcon, ui->rbIconMono->isChecked()); Settings::setValue(Settings::Gui::SymbolicTrayIcon, ui->rbIconMono->isChecked());
Settings::setValue(Settings::Security::CheckPeers, ui->cbRequireClientCert->isChecked()); Settings::setValue(Settings::Security::CheckPeers, ui->cbRequireClientCert->isChecked());
Settings::setValue(Settings::Core::Language, I18N::nativeTo639Name(ui->comboLanguage->currentText())); Settings::setValue(Settings::Core::Language, I18N::nativeTo639Name(ui->comboLanguage->currentText()));
Settings::setValue(Settings::Log::GuiDebug, ui->cbGuiDebug->isChecked()); Settings::setValue(Settings::Log::GuiDebug, ui->cbGuiDebug->isChecked());
Settings::setValue(Settings::Core::UseWlClipboard, ui->cbUseWlClipboard->isChecked()); Settings::setValue(Settings::Core::UseWlClipboard, ui->cbUseWlClipboard->isChecked());
Settings::setValue(Settings::Gui::ShowVersionInTitle, ui->cbShowVersion->isChecked()); Settings::setValue(Settings::Gui::ShowVersionInTitle, ui->cbShowVersion->isChecked());
Settings::setValue(Settings::Core::EnableEnterCommand, ui->cbRunEnterCommand->isChecked());
Settings::setValue(Settings::Core::EnableExitCommand, ui->cbRunExitCommand->isChecked());
Settings::setValue(Settings::Core::ScreenEnterCommand, ui->lineCommandEnter->text());
Settings::setValue(Settings::Core::ScreenExitCommand, ui->lineCommandExit->text());
Settings::ProcessMode mode; Settings::ProcessMode mode;
if (ui->groupService->isChecked()) if (ui->groupService->isChecked())
@ -203,16 +254,18 @@ void SettingsDialog::loadFromConfig()
{ {
ui->sbPort->setValue(Settings::value(Settings::Core::Port).toInt()); ui->sbPort->setValue(Settings::value(Settings::Core::Port).toInt());
ui->comboLogLevel->setCurrentIndex(Settings::value(Settings::Log::Level).toInt()); ui->comboLogLevel->setCurrentIndex(Settings::value(Settings::Log::Level).toInt());
ui->cbLogToFile->setChecked(Settings::value(Settings::Log::ToFile).toBool()); ui->groupLogToFile->setChecked(Settings::value(Settings::Log::ToFile).toBool());
ui->lineLogFilename->setText(Settings::value(Settings::Log::File).toString()); ui->lineLogFilename->setText(Settings::value(Settings::Log::File).toString());
ui->cbAutoHide->setChecked(Settings::value(Settings::Gui::Autohide).toBool());
ui->cbPreventSleep->setChecked(Settings::value(Settings::Core::PreventSleep).toBool()); ui->cbPreventSleep->setChecked(Settings::value(Settings::Core::PreventSleep).toBool());
ui->cbCloseToTray->setChecked(Settings::value(Settings::Gui::CloseToTray).toBool());
ui->cbElevateDaemon->setChecked(Settings::value(Settings::Daemon::Elevate).toBool()); ui->cbElevateDaemon->setChecked(Settings::value(Settings::Daemon::Elevate).toBool());
ui->cbAutoUpdate->setChecked(Settings::value(Settings::Gui::AutoUpdateCheck).toBool()); ui->cbAutoUpdate->setChecked(Settings::value(Settings::Gui::AutoUpdateCheck).toBool());
ui->cbGuiDebug->setChecked(Settings::value(Settings::Log::GuiDebug).toBool()); ui->cbGuiDebug->setChecked(Settings::value(Settings::Log::GuiDebug).toBool());
ui->cbUseWlClipboard->setChecked(Settings::value(Settings::Core::UseWlClipboard).toBool()); ui->cbUseWlClipboard->setChecked(Settings::value(Settings::Core::UseWlClipboard).toBool());
ui->cbShowVersion->setChecked(Settings::value(Settings::Gui::ShowVersionInTitle).toBool()); ui->cbShowVersion->setChecked(Settings::value(Settings::Gui::ShowVersionInTitle).toBool());
ui->cbRunEnterCommand->setChecked(Settings::value(Settings::Core::EnableEnterCommand).toBool());
ui->cbRunExitCommand->setChecked(Settings::value(Settings::Core::EnableExitCommand).toBool());
ui->lineCommandEnter->setText(Settings::value(Settings::Core::ScreenEnterCommand).toString());
ui->lineCommandExit->setText(Settings::value(Settings::Core::ScreenExitCommand).toString());
const auto processMode = Settings::value(Settings::Core::ProcessMode).value<Settings::ProcessMode>(); const auto processMode = Settings::value(Settings::Core::ProcessMode).value<Settings::ProcessMode>();
ui->groupService->setChecked(processMode == Settings::ProcessMode::Service); ui->groupService->setChecked(processMode == Settings::ProcessMode::Service);
@ -225,13 +278,26 @@ void SettingsDialog::loadFromConfig()
else else
ui->rbIconColorful->setChecked(true); ui->rbIconColorful->setChecked(true);
const auto autoHide = Settings::value(Settings::Gui::Autohide).toBool();
ui->rbAutoHide->setChecked(autoHide);
ui->rbShowOnStart->setChecked(!autoHide);
const auto closeToTray = Settings::value(Settings::Gui::CloseToTray).toBool();
ui->rbCloseToTray->setChecked(closeToTray);
ui->rbExitOnClose->setChecked(!closeToTray);
ui->lblDebugWarning->setVisible(Settings::value(Settings::Log::Level).toInt() > 4); ui->lblDebugWarning->setVisible(Settings::value(Settings::Log::Level).toInt() > 4);
ui->comboInterface->setCurrentText(Settings::value(Settings::Core::Interface).toString()); ui->comboInterface->setCurrentText(Settings::value(Settings::Core::Interface).toString());
if (ui->comboInterface->currentIndex() < 0) if (ui->comboInterface->currentIndex() <= 0) {
ui->comboInterface->setCurrentIndex(0); ui->comboInterface->setCurrentIndex(0);
m_interfaceSetOnLoad = false;
} else {
m_interfaceSetOnLoad = true;
}
qDebug() << "load from config done"; qDebug() << "load from config done";
updateControls(); updateControls();
} }
@ -269,7 +335,7 @@ void SettingsDialog::updateTlsControlsEnabled()
bool SettingsDialog::isClientMode() const bool SettingsDialog::isClientMode() const
{ {
return m_coreProcess.mode() == Settings::CoreMode::Client; return Settings::value(Settings::Core::CoreMode) == Settings::CoreMode::Client;
} }
void SettingsDialog::updateKeyLengthOnFile(const QString &path) void SettingsDialog::updateKeyLengthOnFile(const QString &path)
@ -293,20 +359,26 @@ void SettingsDialog::updateControls()
{ {
const bool writable = Settings::isWritable(); const bool writable = Settings::isWritable();
const bool serviceChecked = ui->groupService->isChecked(); const bool serviceChecked = ui->groupService->isChecked();
const bool logToFile = ui->cbLogToFile->isChecked(); const bool logToFile = ui->groupLogToFile->isChecked();
ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(writable); ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(writable);
ui->sbPort->setEnabled(writable); ui->sbPort->setEnabled(writable);
ui->comboInterface->setEnabled(writable); ui->comboInterface->setEnabled(writable);
ui->comboLogLevel->setEnabled(writable); ui->comboLogLevel->setEnabled(writable);
ui->cbLogToFile->setEnabled(writable); ui->groupLogToFile->setEnabled(writable);
ui->cbAutoHide->setEnabled(writable); ui->rbAutoHide->setEnabled(writable);
ui->rbShowOnStart->setEnabled(writable);
ui->cbAutoUpdate->setEnabled(writable); ui->cbAutoUpdate->setEnabled(writable);
ui->cbPreventSleep->setEnabled(writable); ui->cbPreventSleep->setEnabled(writable);
ui->lineTlsCertPath->setEnabled(writable); ui->lineTlsCertPath->setEnabled(writable);
ui->comboTlsKeyLength->setEnabled(writable); ui->comboTlsKeyLength->setEnabled(writable);
ui->cbCloseToTray->setEnabled(writable); ui->rbCloseToTray->setEnabled(writable);
ui->rbExitOnClose->setEnabled(writable);
ui->cbRunEnterCommand->setEnabled(writable);
ui->cbRunExitCommand->setEnabled(writable);
ui->lineCommandEnter->setEnabled(writable && ui->cbRunEnterCommand->isChecked());
ui->lineCommandExit->setEnabled(writable && ui->cbRunExitCommand->isChecked());
// Portable mode only ever applies to Windows. // Portable mode only ever applies to Windows.
// Daemon options should only be available on Windows when *not* in portable mode. // Daemon options should only be available on Windows when *not* in portable mode.
@ -342,4 +414,124 @@ void SettingsDialog::logLevelChanged()
ui->lblDebugWarning->setVisible(ui->comboLogLevel->currentIndex() > 4); ui->lblDebugWarning->setVisible(ui->comboLogLevel->currentIndex() > 4);
} }
bool SettingsDialog::isModified() const
{
const auto processMode = Settings::value(Settings::Core::ProcessMode).value<Settings::ProcessMode>();
const bool ignoreInterface = !m_interfaceSetOnLoad && (ui->comboInterface->currentIndex() == 0);
bool modified =
(ui->sbPort->value() != Settings::value(Settings::Core::Port).toInt()) ||
(ui->comboLogLevel->currentIndex() != Settings::value(Settings::Log::Level).toInt()) ||
(ui->groupLogToFile->isChecked() != Settings::value(Settings::Log::ToFile).toBool()) ||
(ui->lineLogFilename->text() != Settings::value(Settings::Log::File).toString()) ||
(ui->rbAutoHide->isChecked() != Settings::value(Settings::Gui::Autohide).toBool()) ||
(ui->cbPreventSleep->isChecked() != Settings::value(Settings::Core::PreventSleep).toBool()) ||
(ui->rbCloseToTray->isChecked() != Settings::value(Settings::Gui::CloseToTray).toBool()) ||
(ui->cbElevateDaemon->isChecked() != Settings::value(Settings::Daemon::Elevate).toBool()) ||
(ui->cbAutoUpdate->isChecked() != Settings::value(Settings::Gui::AutoUpdateCheck).toBool()) ||
(ui->cbGuiDebug->isChecked() != Settings::value(Settings::Log::GuiDebug).toBool()) ||
(ui->cbUseWlClipboard->isChecked() != Settings::value(Settings::Core::UseWlClipboard).toBool()) ||
(ui->cbShowVersion->isChecked() != Settings::value(Settings::Gui::ShowVersionInTitle).toBool()) ||
(ui->rbIconMono->isChecked() != Settings::value(Settings::Gui::SymbolicTrayIcon).toBool()) ||
(ui->groupService->isChecked() != (processMode == Settings::ProcessMode::Service)) ||
(ui->lineTlsCertPath->text() != Settings::value(Settings::Security::Certificate).toString()) ||
(ui->comboTlsKeyLength->currentText() != Settings::value(Settings::Security::KeySize).toString()) ||
(ui->groupSecurity->isChecked() != Settings::value(Settings::Security::TlsEnabled).toBool()) ||
(ui->cbRequireClientCert->isChecked() != Settings::value(Settings::Security::CheckPeers).toBool()) ||
(ui->cbRunEnterCommand->isChecked() != Settings::value(Settings::Core::EnableEnterCommand).toBool()) ||
(ui->cbRunExitCommand->isChecked() != Settings::value(Settings::Core::EnableExitCommand).toBool()) ||
(ui->lineCommandEnter->text() != Settings::value(Settings::Core::ScreenEnterCommand).toString()) ||
(ui->lineCommandExit->text() != Settings::value(Settings::Core::ScreenExitCommand).toString()) ||
(I18N::nativeTo639Name(ui->comboLanguage->currentText()) != Settings::value(Settings::Core::Language).toString());
if (!ignoreInterface)
modified = modified || ui->comboInterface->currentText() != Settings::value(Settings::Core::Interface).toString();
return modified;
}
bool SettingsDialog::isDefault() const
{
const auto processMode = Settings::defaultValue(Settings::Core::ProcessMode).value<Settings::ProcessMode>();
return (
(ui->sbPort->value() == Settings::defaultValue(Settings::Core::Port).toInt()) &&
(ui->comboLogLevel->currentIndex() == Settings::defaultValue(Settings::Log::Level).toInt()) &&
(ui->groupLogToFile->isChecked() == Settings::defaultValue(Settings::Log::ToFile).toBool()) &&
(ui->lineLogFilename->text() == Settings::defaultValue(Settings::Log::File).toString()) &&
(ui->rbAutoHide->isChecked() == Settings::defaultValue(Settings::Gui::Autohide).toBool()) &&
(ui->cbPreventSleep->isChecked() == Settings::defaultValue(Settings::Core::PreventSleep).toBool()) &&
(ui->rbCloseToTray->isChecked() == Settings::defaultValue(Settings::Gui::CloseToTray).toBool()) &&
(ui->cbElevateDaemon->isChecked() == Settings::defaultValue(Settings::Daemon::Elevate).toBool()) &&
(ui->cbAutoUpdate->isChecked() == Settings::defaultValue(Settings::Gui::AutoUpdateCheck).toBool()) &&
(ui->cbGuiDebug->isChecked() == Settings::defaultValue(Settings::Log::GuiDebug).toBool()) &&
(ui->cbUseWlClipboard->isChecked() == Settings::defaultValue(Settings::Core::UseWlClipboard).toBool()) &&
(ui->cbShowVersion->isChecked() == Settings::defaultValue(Settings::Gui::ShowVersionInTitle).toBool()) &&
(ui->rbIconMono->isChecked() == Settings::defaultValue(Settings::Gui::SymbolicTrayIcon).toBool()) &&
(ui->groupService->isChecked() == (processMode == Settings::ProcessMode::Service)) &&
(ui->comboInterface->currentIndex() == 0) &&
(ui->lineTlsCertPath->text() == Settings::defaultValue(Settings::Security::Certificate).toString()) &&
(ui->comboTlsKeyLength->currentText() == Settings::defaultValue(Settings::Security::KeySize).toString()) &&
(ui->groupSecurity->isChecked() == Settings::defaultValue(Settings::Security::TlsEnabled).toBool()) &&
(ui->cbRequireClientCert->isChecked() == Settings::defaultValue(Settings::Security::CheckPeers).toBool()) &&
(ui->lineCommandEnter->text() == Settings::defaultValue(Settings::Core::ScreenEnterCommand).toString()) &&
(ui->lineCommandExit->text() == Settings::defaultValue(Settings::Core::ScreenExitCommand).toString()) &&
(ui->cbRunEnterCommand->isChecked() == Settings::defaultValue(Settings::Core::EnableEnterCommand).toBool()) &&
(ui->cbRunExitCommand->isChecked() == Settings::defaultValue(Settings::Core::EnableExitCommand).toBool()) &&
(ui->comboLanguage->currentText() == "English")
);
}
void SettingsDialog::resetToDefault()
{
ui->sbPort->setValue(Settings::defaultValue(Settings::Core::Port).toInt());
ui->comboLogLevel->setCurrentIndex(Settings::defaultValue(Settings::Log::Level).toInt());
ui->groupLogToFile->setChecked(Settings::defaultValue(Settings::Log::ToFile).toBool());
ui->lineLogFilename->setText(Settings::defaultValue(Settings::Log::File).toString());
ui->cbPreventSleep->setChecked(Settings::defaultValue(Settings::Core::PreventSleep).toBool());
ui->cbElevateDaemon->setChecked(Settings::defaultValue(Settings::Daemon::Elevate).toBool());
ui->cbAutoUpdate->setChecked(Settings::defaultValue(Settings::Gui::AutoUpdateCheck).toBool());
ui->cbGuiDebug->setChecked(Settings::defaultValue(Settings::Log::GuiDebug).toBool());
ui->cbUseWlClipboard->setChecked(Settings::defaultValue(Settings::Core::UseWlClipboard).toBool());
ui->cbShowVersion->setChecked(Settings::defaultValue(Settings::Gui::ShowVersionInTitle).toBool());
ui->cbRunEnterCommand->setChecked(Settings::defaultValue(Settings::Core::EnableEnterCommand).toBool());
ui->cbRunExitCommand->setChecked(Settings::defaultValue(Settings::Core::EnableExitCommand).toBool());
ui->lineCommandEnter->setText(Settings::defaultValue(Settings::Core::ScreenEnterCommand).toString());
ui->lineCommandExit->setText(Settings::defaultValue(Settings::Core::ScreenExitCommand).toString());
const auto autoHide = Settings::defaultValue(Settings::Gui::Autohide).toBool();
ui->rbCloseToTray->setChecked(autoHide);
ui->rbExitOnClose->setChecked(!autoHide);
const auto closeToTray = Settings::defaultValue(Settings::Gui::CloseToTray).toBool();
ui->rbCloseToTray->setChecked(closeToTray);
ui->rbExitOnClose->setChecked(!closeToTray);
const auto processMode = Settings::defaultValue(Settings::Core::ProcessMode).value<Settings::ProcessMode>();
ui->groupService->setChecked(processMode == Settings::ProcessMode::Service);
if (!deskflow::platform::isWindows())
ui->groupService->setVisible(false);
if (Settings::defaultValue(Settings::Gui::SymbolicTrayIcon).toBool())
ui->rbIconMono->setChecked(true);
else
ui->rbIconColorful->setChecked(true);
ui->lblDebugWarning->setVisible(false);
ui->comboInterface->setCurrentIndex(0);
qDebug() << "reset to default values";
updateControls();
setButtonBoxEnabledButtons();
}
void SettingsDialog::setButtonBoxEnabledButtons() const
{
const bool modified = isModified();
ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(modified);
ui->buttonBox->button(QDialogButtonBox::Reset)->setEnabled(modified);
ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(!isDefault());
}
SettingsDialog::~SettingsDialog() = default; SettingsDialog::~SettingsDialog() = default;

View File

@ -10,7 +10,6 @@
#include <QDialog> #include <QDialog>
#include "gui/config/IServerConfig.h" #include "gui/config/IServerConfig.h"
#include "gui/core/CoreProcess.h"
namespace Ui { namespace Ui {
class SettingsDialog; class SettingsDialog;
@ -19,13 +18,12 @@ class SettingsDialog;
class SettingsDialog : public QDialog class SettingsDialog : public QDialog
{ {
using IServerConfig = deskflow::gui::IServerConfig; using IServerConfig = deskflow::gui::IServerConfig;
using CoreProcess = deskflow::gui::CoreProcess;
Q_OBJECT Q_OBJECT
public: public:
void extracted(); void extracted();
SettingsDialog(QWidget *parent, const IServerConfig &serverConfig, const CoreProcess &coreProcess); SettingsDialog(QWidget *parent, const IServerConfig &serverConfig);
~SettingsDialog() override; ~SettingsDialog() override;
Q_SIGNALS: Q_SIGNALS:
@ -63,7 +61,30 @@ private:
/// @brief update if the log level warning is shown /// @brief update if the log level warning is shown
void logLevelChanged(); void logLevelChanged();
/**
* @brief isModified
* @return true when any client settings in the gui do not match the stored settings values.
*/
bool isModified() const;
/**
* @brief isDefault
* @return true if all client settings match the default values
*/
bool isDefault() const;
/**
* @brief Set the gui values to the defalut values for all client settings
*/
void resetToDefault();
/**
* @brief setButtonBoxEnabledButtons
* Enable / Disable the button box buttons based on the state of the gui
*/
void setButtonBoxEnabledButtons() const;
bool m_interfaceSetOnLoad = false;
std::unique_ptr<Ui::SettingsDialog> ui; std::unique_ptr<Ui::SettingsDialog> ui;
const IServerConfig &m_serverConfig; const IServerConfig &m_serverConfig;
const CoreProcess &m_coreProcess;
}; };

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,9 @@
#include "ComputerNameValidator.h" #include "ComputerNameValidator.h"
#include "IpAddressValidator.h" #include "IpAddressValidator.h"
#include "SpacesValidator.h" #include "SpacesValidator.h"
#include "ValidationError.h"
#include <QLineEdit>
#include <QRegularExpression> #include <QRegularExpression>
namespace validators { namespace validators {

View File

@ -7,9 +7,9 @@
#pragma once #pragma once
#include "LineEditValidator.h" #include "LineEditValidator.h"
#include "ValidationError.h"
namespace validators { namespace validators {
class ValidationError;
class AliasValidator : public LineEditValidator class AliasValidator : public LineEditValidator
{ {

View File

@ -17,7 +17,9 @@ ComputerNameValidator::ComputerNameValidator(const QString &message) : IStringVa
bool ComputerNameValidator::validate(const QString &input) const bool ComputerNameValidator::validate(const QString &input) const
{ {
auto match = m_nameValidator.match(input); static const auto s_nameValidator =
QRegularExpression(QStringLiteral("^[\\w\\._-]{0,255}$"), QRegularExpression::CaseInsensitiveOption);
auto match = s_nameValidator.match(input);
return match.hasMatch(); return match.hasMatch();
} }

View File

@ -8,8 +8,6 @@
#include "IStringValidator.h" #include "IStringValidator.h"
#include <QRegularExpression>
namespace validators { namespace validators {
class ComputerNameValidator : public IStringValidator class ComputerNameValidator : public IStringValidator
@ -17,10 +15,6 @@ class ComputerNameValidator : public IStringValidator
public: public:
explicit ComputerNameValidator(const QString &message); explicit ComputerNameValidator(const QString &message);
bool validate(const QString &input) const override; bool validate(const QString &input) const override;
private:
inline static const QRegularExpression m_nameValidator =
QRegularExpression(QStringLiteral("^[\\w\\._-]{0,255}$"), QRegularExpression::CaseInsensitiveOption);
}; };
} // namespace validators } // namespace validators

View File

@ -5,6 +5,7 @@
*/ */
#include "IpAddressValidator.h" #include "IpAddressValidator.h"
#include <QRegularExpression>
namespace validators { namespace validators {

View File

@ -7,7 +7,6 @@
#pragma once #pragma once
#include "IStringValidator.h" #include "IStringValidator.h"
#include <QRegularExpression>
namespace validators { namespace validators {

View File

@ -6,8 +6,10 @@
*/ */
#include "LineEditValidator.h" #include "LineEditValidator.h"
#include "ValidationError.h"
#include <QApplication> #include <QApplication>
#include <QLineEdit>
#include <QStyle> #include <QStyle>
#include <QValidator> #include <QValidator>

View File

@ -8,15 +8,14 @@
#include "IStringValidator.h" #include "IStringValidator.h"
#include <QLabel>
#include <QLineEdit>
#include <QValidator> #include <QValidator>
#include <memory> #include <memory>
#include <vector> #include <vector>
#include "ValidationError.h" class QLineEdit;
namespace validators { namespace validators {
class ValidationError;
class LineEditValidator : public QValidator class LineEditValidator : public QValidator
{ {

View File

@ -5,6 +5,7 @@
*/ */
#include "ScreenDuplicationsValidator.h" #include "ScreenDuplicationsValidator.h"
#include "gui/config/ScreenList.h"
namespace validators { namespace validators {

View File

@ -8,7 +8,7 @@
#include "IStringValidator.h" #include "IStringValidator.h"
#include "gui/config/ScreenList.h" class ScreenList;
namespace validators { namespace validators {

View File

@ -12,6 +12,9 @@
#include "SpacesValidator.h" #include "SpacesValidator.h"
#include "ValidationError.h" #include "ValidationError.h"
#include "gui/config/ScreenList.h"
#include <QLineEdit>
#include <QRegularExpression> #include <QRegularExpression>
#include <memory> #include <memory>

View File

@ -7,11 +7,11 @@
#pragma once #pragma once
#include "LineEditValidator.h" #include "LineEditValidator.h"
#include "ValidationError.h"
#include "gui/config/ScreenList.h" class ScreenList;
namespace validators { namespace validators {
class ValidationError;
class ScreenNameValidator : public LineEditValidator class ScreenNameValidator : public LineEditValidator
{ {

View File

@ -15,7 +15,7 @@ SpacesValidator::SpacesValidator(const QString &message) : IStringValidator(mess
bool SpacesValidator::validate(const QString &input) const bool SpacesValidator::validate(const QString &input) const
{ {
return !input.contains(' '); return !input.contains(QChar::Space);
} }
} // namespace validators } // namespace validators

View File

@ -7,6 +7,7 @@
#include "ValidationError.h" #include "ValidationError.h"
#include <QLabel>
#include <QPalette> #include <QPalette>
namespace validators { namespace validators {

View File

@ -7,10 +7,11 @@
#pragma once #pragma once
#include <QLabel>
#include <QObject> #include <QObject>
#include <QString> #include <QString>
class QLabel;
namespace validators { namespace validators {
class ValidationError : public QObject class ValidationError : public QObject

View File

@ -19,7 +19,7 @@ NewScreenWidget::NewScreenWidget(QWidget *parent) : QLabel(parent)
// do nothing // do nothing
} }
void NewScreenWidget::mousePressEvent(QMouseEvent *event) void NewScreenWidget::mousePressEvent(QMouseEvent *)
{ {
//: Used as the hostname. Translation may not contain spaces //: Used as the hostname. Translation may not contain spaces
Screen newScreen(tr("Unnamed")); Screen newScreen(tr("Unnamed"));

View File

@ -20,5 +20,5 @@ public:
explicit NewScreenWidget(QWidget *parent); explicit NewScreenWidget(QWidget *parent);
protected: protected:
void mousePressEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *) override;
}; };

View File

@ -10,8 +10,6 @@
#include <QFlags> #include <QFlags>
#include <QTableView> #include <QTableView>
#include "gui/config/Screen.h"
class QWidget; class QWidget;
class QMouseEvent; class QMouseEvent;
class QResizeEvent; class QResizeEvent;

View File

@ -0,0 +1,178 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 - 2026 Deskflow Developers
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "StatusBar.h"
#include "common/Constants.h"
#include "common/Settings.h"
#include <QEvent>
#include <QLabel>
#include <QPushButton>
StatusBar::StatusBar(QWidget *parent)
: QStatusBar{parent},
m_btnFingerprint{new QPushButton(this)},
m_lblSecurityIcon{new QLabel(this)},
m_lblStatus{new QLabel(this)},
m_btnUpdate{new QPushButton(this)}
{
static const auto btnHeight = height() - 2;
static const auto btnSize = QSize(btnHeight, btnHeight);
static const auto iconSize = QSize(fontMetrics().height() + 2, fontMetrics().height() + 2);
m_btnFingerprint->setFlat(true);
m_btnFingerprint->setIcon(QIcon::fromTheme(QStringLiteral("fingerprint")));
m_btnFingerprint->setFixedSize(btnSize);
m_btnFingerprint->setIconSize(iconSize);
insertPermanentWidget(0, m_btnFingerprint);
connect(m_btnFingerprint, &QPushButton::clicked, this, &StatusBar::requestShowMyFingerprints);
m_lblSecurityIcon->setVisible(false);
m_lblSecurityIcon->setFixedSize(iconSize);
m_lblSecurityIcon->setScaledContents(true);
insertPermanentWidget(1, m_lblSecurityIcon);
m_lblStatus->setText(tr("%1 is not running").arg(kAppName));
insertPermanentWidget(2, m_lblStatus, 1);
m_btnUpdate->setVisible(false);
m_btnUpdate->setFlat(true);
m_btnUpdate->setLayoutDirection(Qt::RightToLeft);
m_btnUpdate->setIcon(QIcon::fromTheme(QStringLiteral("software-updates-release")));
m_btnUpdate->setFixedHeight(btnHeight);
m_btnUpdate->setIconSize(iconSize);
insertPermanentWidget(3, m_btnUpdate);
connect(m_btnUpdate, &QPushButton::clicked, this, &StatusBar::requestUpdateVersion);
updateText();
adjustSize();
}
// clang-format off
void StatusBar::setStatus(ConnectionState connectionState, ProcessState processState, bool isServer)
{
setSecurityIconVisible(false);
switch (processState) {
using enum ProcessState;
case Starting:
m_lblStatus->setText(tr("%1 is starting...").arg(kAppName));
break;
case RetryPending:
m_lblStatus->setText(tr("%1 will retry in a moment...").arg(kAppName));
break;
case Stopping:
m_lblStatus->setText(tr("%1 is stopping...").arg(kAppName));
break;
case Stopped:
m_lblStatus->setText(tr("%1 is not running").arg(kAppName));
break;
case Started: {
switch (connectionState) {
using enum ConnectionState;
case Listening: {
if (isServer) {
setSecurityIconVisible(true);
m_lblStatus->setText(tr("%1 is waiting for clients").arg(kAppName));
}
break;
}
case Connecting:
m_lblStatus->setText(tr("%1 is connecting...").arg(kAppName));
break;
case Connected: {
setSecurityIconVisible(true);
if (!isServer) {
m_lblStatus->setText(tr("%1 is connected as client of %2")
.arg(kAppName, Settings::value(Settings::Client::RemoteHost).toString()));
}
break;
}
case Disconnected:
m_lblStatus->setText(tr("%1 is disconnected").arg(kAppName));
break;
}
}
}
}
// clang-format on
void StatusBar::setServerClients(const QStringList &clients)
{
if (clients.isEmpty()) {
m_lblStatus->setText(tr("%1 is waiting for clients").arg(kAppName));
m_lblStatus->setToolTip("");
return;
}
const auto clientCount = static_cast<int>(clients.size());
static const auto comma = QStringLiteral(", ");
static const auto newLine = QStringLiteral("\n");
//: Shown when in server mode and at least 1 client is connected
//: %1 is replaced by the app name
//: %2 will be a list of at least one client
//: %n will be replaced by the number of clients (n is >=1), it is not requried to be in the translation
const auto text = tr("%1 is connected, with %n client(s): %2", "", clientCount).arg(kAppName, clients.join(comma));
m_lblStatus->setText(text);
const auto toolTipString = clientCount == 1 ? "" : tr("Clients:\n %1").arg(clients.join(newLine));
m_lblStatus->setToolTip(toolTipString);
}
void StatusBar::setSecurityIconVisible(bool visible)
{
m_lblSecurityIcon->setVisible(visible);
}
bool StatusBar::securityIconVisible() const
{
return m_lblSecurityIcon->isVisible();
}
void StatusBar::setBtnFingerprintVisible(bool visible)
{
m_btnFingerprint->setVisible(visible);
}
void StatusBar::updateFound(const QString &version)
{
m_btnUpdate->setVisible(true);
m_btnUpdate->setToolTip(tr("A new version v%1 is available").arg(version));
}
void StatusBar::changeEvent(QEvent *e)
{
QStatusBar::changeEvent(e);
if (e->type() == QEvent::LanguageChange)
updateText();
}
void StatusBar::updateText()
{
m_btnFingerprint->setToolTip(tr("View local fingerprint"));
m_btnUpdate->setText(tr("Update available"));
setSecurityLevel(m_securityLevel);
}
void StatusBar::setSecurityIcon(bool encrypted)
{
const auto icon = QIcon::fromTheme(encrypted ? QIcon::ThemeIcon::SecurityHigh : QIcon::ThemeIcon::SecurityLow);
m_lblSecurityIcon->setPixmap(icon.pixmap(QSize(32, 32)));
m_encrypted = encrypted;
setSecurityLevel(m_securityLevel);
}
void StatusBar::setSecurityLevel(const QString &securityLevel)
{
m_securityLevel = securityLevel;
const auto txt = m_encrypted ? tr("%1 Encryption Enabled").arg(m_securityLevel) : tr("Encryption Disabled");
m_lblSecurityIcon->setToolTip(txt);
}

View File

@ -0,0 +1,49 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 - 2026 Deskflow Developers
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#pragma once
#include <QStatusBar>
#include "common/Enums.h"
class QPushButton;
class QLabel;
using ProcessState = deskflow::core::ProcessState;
using ConnectionState = deskflow::core::ConnectionState;
class StatusBar : public QStatusBar
{
Q_OBJECT
public:
explicit StatusBar(QWidget *parent = nullptr);
void setStatus(ConnectionState connectionState, ProcessState processState, bool isServer);
void setServerClients(const QStringList &clients);
void setSecurityIconVisible(bool visible);
bool securityIconVisible() const;
void updateSecurityInfo(bool encrypted);
void setSecurityIcon(bool encrypted);
void setSecurityLevel(const QString &securityLevel);
void setBtnFingerprintVisible(bool visible);
void updateFound(const QString &version);
Q_SIGNALS:
void requestShowMyFingerprints();
void requestUpdateVersion();
protected:
void changeEvent(QEvent *e) override;
private:
void updateText();
QPushButton *m_btnFingerprint = nullptr;
QLabel *m_lblSecurityIcon = nullptr;
QLabel *m_lblStatus = nullptr;
QPushButton *m_btnUpdate = nullptr;
bool m_encrypted = false;
QString m_securityLevel;
};

View File

@ -81,6 +81,11 @@ deskflow::IStream *StreamFilter::getStream() const
return m_stream; return m_stream;
} }
bool StreamFilter::adoptedStream() const
{
return m_adopted;
}
void StreamFilter::filterEvent(const Event &event) void StreamFilter::filterEvent(const Event &event)
{ {
m_events->dispatchEvent(Event(event.getType(), getEventTarget(), event.getData())); m_events->dispatchEvent(Event(event.getType(), getEventTarget(), event.getData()));

View File

@ -51,6 +51,8 @@ public:
*/ */
deskflow::IStream *getStream() const; deskflow::IStream *getStream() const;
bool adoptedStream() const;
protected: protected:
//! Handle events from source stream //! Handle events from source stream
/*! /*!

View File

@ -658,9 +658,9 @@ ISocketMultiplexerJob *SecureSocket::serviceConnect(ISocketMultiplexerJob *const
Lock lock(&getMutex()); Lock lock(&getMutex());
int status = 0; int status = 0;
#ifdef SYSAPI_WIN32 #if defined(Q_OS_WIN)
status = secureConnect(static_cast<int>(getSocket()->m_socket)); status = secureConnect(static_cast<int>(getSocket()->m_socket));
#elif SYSAPI_UNIX #else
status = secureConnect(getSocket()->m_fd); status = secureConnect(getSocket()->m_fd);
#endif #endif
@ -686,13 +686,14 @@ ISocketMultiplexerJob *SecureSocket::serviceAccept(ISocketMultiplexerJob *const,
Lock lock(&getMutex()); Lock lock(&getMutex());
int status = 0; int status = 0;
#ifdef SYSAPI_WIN32 #if defined(Q_OS_WIN)
status = secureAccept(static_cast<int>(getSocket()->m_socket)); status = secureAccept(static_cast<int>(getSocket()->m_socket));
#elif SYSAPI_UNIX #else
status = secureAccept(getSocket()->m_fd); status = secureAccept(getSocket()->m_fd);
#endif #endif
// If status < 0, error happened // If status < 0, error happened
if (status < 0) { if (status < 0) {
sendEvent(EventTypes::ClientListenerDisconnectedOnAccept);
return nullptr; return nullptr;
} }

View File

@ -92,7 +92,7 @@ void generatePemSelfSignedCert(const QString &path, int keyLength)
X509_sign(cert, privateKey, EVP_sha256()); X509_sign(cert, privateKey, EVP_sha256());
const std::filesystem::path fsPath = path.toStdString(); const std::filesystem::path fsPath = path.toStdString();
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
auto fp = _wfopen(fsPath.native().c_str(), L"w"); auto fp = _wfopen(fsPath.native().c_str(), L"w");
#else #else
auto fp = std::fopen(fsPath.native().c_str(), "w"); auto fp = std::fopen(fsPath.native().c_str(), "w");

View File

@ -54,7 +54,7 @@ void TCPListenSocket::bind(const NetworkAddress &addr)
try { try {
std::scoped_lock lock{m_mutex}; std::scoped_lock lock{m_mutex};
#if SYSAPI_UNIX #if defined(Q_OS_UNIX)
// Only reuse socket addr on Unix so we can restart the server quickly (Unix holds the port // Only reuse socket addr on Unix so we can restart the server quickly (Unix holds the port
// in TIME_WAIT for a few mins after close). This is not needed on Windows and can cause issues // in TIME_WAIT for a few mins after close). This is not needed on Windows and can cause issues
// because binding to a re-use port makes it look like the server is listening when it is not. // because binding to a re-use port makes it look like the server is listening when it is not.

View File

@ -177,7 +177,7 @@ if(UNIX)
if(${LIBXKBCOMMON_VERSION} VERSION_GREATER_EQUAL "1.10") if(${LIBXKBCOMMON_VERSION} VERSION_GREATER_EQUAL "1.10")
target_compile_definitions(platform PRIVATE HAVE_XKB_KEYMAP_MOD_GET_MASK=1) target_compile_definitions(platform PRIVATE HAVE_XKB_KEYMAP_MOD_GET_MASK=1)
endif() endif()
target_compile_definitions(platform PUBLIC WINAPI_LIBEI WINAPI_LIBPORTAL HAVE_LIBPORTAL_INPUTCAPTURE) target_compile_definitions(platform PUBLIC WINAPI_LIBEI WINAPI_LIBPORTAL)
target_include_directories(platform PUBLIC ${LIBEI_INCLUDE_DIRS} ${LIBPORTAL_INCLUDE_DIRS}) target_include_directories(platform PUBLIC ${LIBEI_INCLUDE_DIRS} ${LIBPORTAL_INCLUDE_DIRS})
target_link_libraries( target_link_libraries(
platform platform

View File

@ -1,5 +1,6 @@
/* /*
* Deskflow -- mouse and keyboard sharing utility * Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2026 Deskflow Developers
* SPDX-FileCopyrightText: (C) 2012 - 2016 Symless Ltd. * SPDX-FileCopyrightText: (C) 2012 - 2016 Symless Ltd.
* SPDX-FileCopyrightText: (C) 2002 Chris Schoeneman * SPDX-FileCopyrightText: (C) 2002 Chris Schoeneman
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
@ -116,14 +117,26 @@ bool MSWindowsClipboard::open(Time time) const
{ {
LOG_DEBUG("open clipboard"); LOG_DEBUG("open clipboard");
if (!OpenClipboard(m_window)) { // The clipboard is a global mutex on Windows. We aren't always going to
LOG_WARN("failed to open clipboard: %d", GetLastError()); // get the lock on the first try, so try a few times before giving up.
return false; // Based on Chromium's ScopedClipboard::Acquire() retry loop.
static const int kMaxRetries = 5;
static const int kRetryDelayMs = 5;
for (int i = 0; i < kMaxRetries; ++i) {
if (OpenClipboard(m_window)) {
m_time = time;
return true;
}
if (i < kMaxRetries - 1) {
LOG_DEBUG("failed to open clipboard (attempt %d/%d, error=%d), retrying", i + 1, kMaxRetries, GetLastError());
Sleep(kRetryDelayMs);
}
} }
m_time = time; LOG_WARN("failed to open clipboard after %d attempts: %d", kMaxRetries, GetLastError());
return false;
return true;
} }
void MSWindowsClipboard::close() const void MSWindowsClipboard::close() const

View File

@ -5,7 +5,6 @@
*/ */
#pragma once #pragma once
#if WINAPI_CARBON
#include <Carbon/Carbon.h> #include <Carbon/Carbon.h>
#include <memory> #include <memory>
@ -13,5 +12,3 @@ using CFDeallocator = decltype(&CFRelease);
using AutoCFArray = std::unique_ptr<const __CFArray, CFDeallocator>; using AutoCFArray = std::unique_ptr<const __CFArray, CFDeallocator>;
using AutoCFDictionary = std::unique_ptr<const __CFDictionary, CFDeallocator>; using AutoCFDictionary = std::unique_ptr<const __CFDictionary, CFDeallocator>;
using AutoTISInputSourceRef = std::unique_ptr<__TISInputSource, CFDeallocator>; using AutoTISInputSourceRef = std::unique_ptr<__TISInputSource, CFDeallocator>;
#endif

View File

@ -31,9 +31,10 @@ void OSXEventQueueBuffer::waitForEvent(double timeout)
{ {
std::unique_lock lock(m_mutex); std::unique_lock lock(m_mutex);
if (m_dataQueue.empty()) { if (m_dataQueue.empty()) {
auto duration = std::chrono::duration<double>(timeout);
LOG_DEBUG2("waiting for event, timeout: %f seconds", timeout); LOG_DEBUG2("waiting for event, timeout: %f seconds", timeout);
m_cond.wait_for(lock, duration, [this] { return !m_dataQueue.empty(); }); auto end = timeout < 0 ? std::chrono::steady_clock::time_point::max()
: std::chrono::steady_clock::now() + std::chrono::duration<double>(timeout);
m_cond.wait_until(lock, end, [this] { return !m_dataQueue.empty(); });
} else { } else {
LOG_DEBUG2("found events in the queue"); LOG_DEBUG2("found events in the queue");
} }

View File

@ -815,7 +815,7 @@ void XWindowsScreen::fakeMouseWheel(ScrollDelta delta) const
} }
// send as many clicks as necessary // send as many clicks as necessary
while (axisDelta >= 0) { while (axisDelta > 0) {
XTestFakeButtonEvent(m_display, button, True, CurrentTime); XTestFakeButtonEvent(m_display, button, True, CurrentTime);
XTestFakeButtonEvent(m_display, button, False, CurrentTime); XTestFakeButtonEvent(m_display, button, False, CurrentTime);
axisDelta -= s_scrollDelta; axisDelta -= s_scrollDelta;

View File

@ -138,6 +138,14 @@ void ClientListener::handleClientConnecting()
[this, rawSocketPointer](const auto &) { handleClientAccepted(rawSocketPointer); } [this, rawSocketPointer](const auto &) { handleClientAccepted(rawSocketPointer); }
); );
m_events->addHandler(
EventTypes::ClientListenerDisconnectedOnAccept, rawSocketPointer->getEventTarget(),
[this, rawSocketPointer](const auto &) {
LOG_DEBUG("disconnected client before accept");
removeClientSocket(rawSocketPointer);
}
);
// When using non SSL, server accepts clients immediately, while SSL // When using non SSL, server accepts clients immediately, while SSL
// has to call secure accept which may require retry // has to call secure accept which may require retry
if (m_securityLevel == SecurityLevel::PlainText) { if (m_securityLevel == SecurityLevel::PlainText) {
@ -163,7 +171,15 @@ void ClientListener::handleClientAccepted(IDataSocket *socket)
handleUnknownClient(client); handleUnknownClient(client);
}); });
m_events->addHandler(EventTypes::ClientProxyUnknownFailure, client, [this, client](const auto &) { m_events->addHandler(EventTypes::ClientProxyUnknownFailure, client, [this, client](const auto &) {
auto *filter = dynamic_cast<StreamFilter *>(client->getStream());
IDataSocket *socket = nullptr;
if (filter && !filter->adoptedStream()) {
socket = dynamic_cast<IDataSocket *>(filter->getStream());
}
removeUnknownClient(client); removeUnknownClient(client);
if (socket) {
removeClientSocket(socket);
}
}); });
} }
@ -205,14 +221,20 @@ void ClientListener::handleClientDisconnected(ClientProxy *client)
// we know which socket we no longer need // we know which socket we no longer need
auto *socket = static_cast<IDataSocket *>(client->getStream()); auto *socket = static_cast<IDataSocket *>(client->getStream());
delete client; delete client;
m_clientSockets.erase(socket); removeClientSocket(socket);
delete socket;
break; break;
} }
} }
} }
void ClientListener::removeClientSocket(IDataSocket *socket)
{
m_clientSockets.erase(socket);
m_events->removeHandlers(socket->getEventTarget());
delete socket;
}
void ClientListener::cleanupListenSocket() void ClientListener::cleanupListenSocket()
{ {
delete m_listen; delete m_listen;

View File

@ -71,6 +71,7 @@ private:
void handleUnknownClient(ClientProxyUnknown *unknownClient); void handleUnknownClient(ClientProxyUnknown *unknownClient);
void handleClientDisconnected(ClientProxy *client); void handleClientDisconnected(ClientProxy *client);
void removeClientSocket(IDataSocket *socket);
void cleanupListenSocket(); void cleanupListenSocket();
void cleanupClientSockets(); void cleanupClientSockets();
void start(); void start();

View File

@ -199,7 +199,7 @@ public:
//! Add screen //! Add screen
/*! /*!
Adds a screen, returning true iff successful. If a screen or Adds a screen, returning true if successful. If a screen or
alias with the given name exists then it fails. alias with the given name exists then it fails.
*/ */
bool addScreen(const std::string &name); bool addScreen(const std::string &name);

View File

@ -9,7 +9,7 @@
#include "base/Log.h" #include "base/Log.h"
#include "unittests/legacytests/shared/ExitTimeout.h" #include "unittests/legacytests/shared/ExitTimeout.h"
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
#include "arch/win32/ArchMiscWindows.h" #include "arch/win32/ArchMiscWindows.h"
#endif #endif
@ -23,7 +23,7 @@ int main(int argc, char **argv)
{ {
ExitTimeout exitTimeout(1, "Unit tests"); ExitTimeout exitTimeout(1, "Unit tests");
#if SYSAPI_WIN32 #if defined(Q_OS_WIN)
// HACK: shouldn't be needed, but logging fails without this. // HACK: shouldn't be needed, but logging fails without this.
ArchMiscWindows::setInstanceWin32(GetModuleHandle(nullptr)); ArchMiscWindows::setInstanceWin32(GetModuleHandle(nullptr));
#endif #endif

View File

@ -2,7 +2,7 @@
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1" language="en_US"> <TS version="2.1" language="en_US">
<context> <context>
<name>MainWindow</name> <name>StatusBar</name>
<message numerus="yes"> <message numerus="yes">
<source>%1 is connected, with %n client(s): %2</source> <source>%1 is connected, with %n client(s): %2</source>
<extracomment>Shown when in server mode and at least 1 client is connected %1 is replaced by the app name %2 will be a list of at least one client %n will be replaced by the number of clients (n is &gt;=1), it is not requried to be in the translation</extracomment> <extracomment>Shown when in server mode and at least 1 client is connected %1 is replaced by the app name %2 will be a list of at least one client %n will be replaced by the number of clients (n is &gt;=1), it is not requried to be in the translation</extracomment>

View File

@ -386,22 +386,14 @@ Do you want to connect to the server?
<extracomment>stop core shortcut</extracomment> <extracomment>stop core shortcut</extracomment>
<translation type="unfinished">Ctrl+T</translation> <translation type="unfinished">Ctrl+T</translation>
</message> </message>
<message>
<source>&lt;p&gt;Failed to connect to the server &apos;%1&apos;.&lt;/p&gt;&lt;p&gt;A Client with your name is already connected to the server.&lt;/p&gt;Please ensure that you&apos;re using a unique name and that only a single instance of the client process is running.&lt;/p&gt;</source>
<translation>&lt;p&gt;Error al conectar con el servidor &apos;%1&apos;.&lt;/p&gt;&lt;p&gt;A Client with your name is already connected to the server.&lt;/p&gt;Please ensure that you&apos;re using a unique name and that only a single instance of the client process is running.&lt;/p&gt;</translation>
</message>
<message> <message>
<source>Your current TLS key is smaller than the minimum allowed size, A new key 2048-bit key will be generated.</source> <source>Your current TLS key is smaller than the minimum allowed size, A new key 2048-bit key will be generated.</source>
<translation type="unfinished">Su clave TLS actual es más pequeña que el tamaño mínimo permitido. Se generará una nueva clave de 2048 bits.</translation> <translation type="unfinished">Su clave TLS actual es más pequeña que el tamaño mínimo permitido. Se generará una nueva clave de 2048 bits.</translation>
</message> </message>
<message>
<source>View local fingerprint</source>
<translation type="unfinished">Ver huella digital local</translation>
</message>
<message>
<source>Update available</source>
<translation type="unfinished">Actualización disponible</translation>
</message>
<message>
<source>A new version v%1 is available</source>
<translation type="unfinished">Ya está disponible una nueva versión v%1</translation>
</message>
<message> <message>
<source>Address missing</source> <source>Address missing</source>
<translation type="unfinished">Dirección faltante</translation> <translation type="unfinished">Dirección faltante</translation>
@ -410,10 +402,6 @@ Do you want to connect to the server?
<source>Please enter the hostname or IP address of the other computer.</source> <source>Please enter the hostname or IP address of the other computer.</source>
<translation type="unfinished">Introduzca el nombre de host o la dirección IP de la otra computadora.</translation> <translation type="unfinished">Introduzca el nombre de host o la dirección IP de la otra computadora.</translation>
</message> </message>
<message>
<source>Core cannot be started</source>
<translation type="unfinished">No se puede iniciar el núcleo</translation>
</message>
<message> <message>
<source>Save server configuration as...</source> <source>Save server configuration as...</source>
<translation type="unfinished">Guardar la configuración del servidor como...</translation> <translation type="unfinished">Guardar la configuración del servidor como...</translation>
@ -443,12 +431,8 @@ Do you want to connect to the server?
<translation type="unfinished">Desconectar</translation> <translation type="unfinished">Desconectar</translation>
</message> </message>
<message> <message>
<source>%1 Encryption Enabled</source> <source>%1 Connection Error</source>
<translation type="unfinished">%1 Cifrado habilitado</translation> <translation>%1 Error de conexión</translation>
</message>
<message>
<source>Encryption Disabled</source>
<translation type="unfinished">Cifrado deshabilitado</translation>
</message> </message>
<message> <message>
<source>No IP Detected</source> <source>No IP Detected</source>
@ -500,42 +484,10 @@ La dirección IP asignada ahora no es válida; es posible que deba reiniciar el
<source>&amp;Help</source> <source>&amp;Help</source>
<translation type="unfinished">&amp;Ayuda</translation> <translation type="unfinished">&amp;Ayuda</translation>
</message> </message>
<message>
<source>%1 is starting...</source>
<translation type="unfinished">%1 está iniciando...</translation>
</message>
<message> <message>
<source>invalid certificate, generating a new one</source> <source>invalid certificate, generating a new one</source>
<translation type="unfinished">certificado no válido, generando uno nuevo</translation> <translation type="unfinished">certificado no válido, generando uno nuevo</translation>
</message> </message>
<message>
<source>%1 will retry in a moment...</source>
<translation type="unfinished">%1 lo intentará nuevamente en un momento...</translation>
</message>
<message>
<source>%1 is stopping...</source>
<translation type="unfinished">%1 se está deteniendo...</translation>
</message>
<message>
<source>%1 is not running</source>
<translation type="unfinished">%1 no se está ejecutando</translation>
</message>
<message>
<source>%1 is waiting for clients</source>
<translation type="unfinished">%1 está esperando clientes</translation>
</message>
<message>
<source>%1 is connecting...</source>
<translation type="unfinished">%1 se está conectando...</translation>
</message>
<message>
<source>%1 is connected as client of %2</source>
<translation type="unfinished">%1 está conectado como cliente de %2</translation>
</message>
<message>
<source>%1 is disconnected</source>
<translation type="unfinished">%1 está desconectado</translation>
</message>
<message> <message>
<source>Ctrl+Q</source> <source>Ctrl+Q</source>
<extracomment>Quit shortcut</extracomment> <extracomment>Quit shortcut</extracomment>
@ -562,29 +514,11 @@ Nombres válidos:
Use letras y números Use letras y números
También puede usar _ o - También puede usar _ o -
Tengan entre 1 y 255 caracteres</translation> Tengan entre 1 y 255 caracteres</translation>
</message>
<message numerus="yes">
<source>%1 is connected, with %n client(s): %2</source>
<extracomment>Shown when in server mode and at least 1 client is connected %1 is replaced by the app name %2 will be a list of at least one client %n will be replaced by the number of clients (n is &gt;=1), it is not requried to be in the translation</extracomment>
<translation type="unfinished">
<numerusform>%1 está conectado, con un cliente: %2</numerusform>
<numerusform>%1 está conectado, con %n clientes: %2</numerusform>
</translation>
</message>
<message>
<source>Clients:
%1</source>
<translation type="unfinished">Clientes:
%1</translation>
</message> </message>
<message> <message>
<source>This computer&apos;s name:</source> <source>This computer&apos;s name:</source>
<translation type="unfinished">Nombre de esta computadora:</translation> <translation type="unfinished">Nombre de esta computadora:</translation>
</message> </message>
<message>
<source>The Core executable could not be successfully started, although it does exist. Please check if you have sufficient permissions to run this program.</source>
<translation type="unfinished">No se pudo iniciar el archivo ejecutable principal, aunque existe. Compruebe si tiene permisos suficientes para ejecutar este programa.</translation>
</message>
<message> <message>
<source>&amp;Configure Client</source> <source>&amp;Configure Client</source>
<translation type="unfinished">&amp;Configurar cliente</translation> <translation type="unfinished">&amp;Configurar cliente</translation>
@ -595,7 +529,27 @@ Nombres válidos:
</message> </message>
<message> <message>
<source>&lt;html&gt;Hostname or IP address of the server computer.&lt;br/&gt;May contain a comma seperated list.&lt;/html&gt;</source> <source>&lt;html&gt;Hostname or IP address of the server computer.&lt;br/&gt;May contain a comma seperated list.&lt;/html&gt;</source>
<translation type="unfinished">&lt;html&gt;Nome host o indirizzo IP del computer server.&lt;br/&gt;Può contenere un elenco separato da virgole.&lt;/html&gt;</translation> <translation type="unfinished">&lt;html&gt;Nombre de host o dirección IP del servidor.&lt;br/&gt;Puede contener una lista separada por comas.&lt;/html&gt;</translation>
</message>
<message>
<source>read</source>
<translation type="unfinished">leer</translation>
</message>
<message>
<source>read and write</source>
<translation type="unfinished">leer y escribir</translation>
</message>
<message>
<source>The Core executable could not be started.
Please check if you have sufficient permissions to run %1.</source>
<translation type="unfinished">No se pudo iniciar el ejecutable Core.
Verifique si tiene permisos suficientes para ejecutar %1.</translation>
</message>
<message>
<source>
Additionally, check you are able to %1 the server config file: %2</source>
<translation type="unfinished">
Además, verifique que puede %1 el archivo de configuración del servidor: %2</translation>
</message> </message>
</context> </context>
<context> <context>
@ -676,30 +630,6 @@ Nombres válidos:
<source>%1 Connected</source> <source>%1 Connected</source>
<translation type="unfinished">%1 Conectado</translation> <translation type="unfinished">%1 Conectado</translation>
</message> </message>
<message>
<source>&lt;p&gt;Failed to connect to the server &apos;%1&apos;.&lt;/p&gt;</source>
<translation type="unfinished">&lt;p&gt;Error al conectar con el servidor &apos;%1&apos;.&lt;/p&gt;</translation>
</message>
<message>
<source>&lt;p&gt;A Client with your name is already connected to the server.&lt;/p&gt;Please ensure that you&apos;re using a unique name and that only a single instance of the client process is running.&lt;/p&gt;</source>
<translation type="unfinished">&lt;p&gt;Ya hay un cliente conectado al servidor con su nombre.&lt;/p&gt;Asegúrese de utilizar un nombre único y de que solo se esté ejecutando una única instancia del proceso del cliente.&lt;/p&gt;</translation>
</message>
<message>
<source>&lt;p&gt;Please try to connect to the server using the server IP address instead of the hostname. &lt;/p&gt;&lt;p&gt;If that doesn&apos;t work, please check your TLS and firewall settings.&lt;/p&gt;</source>
<translation type="unfinished">&lt;p&gt;Intente conectarse al servidor usando la dirección IP del servidor en lugar del nombre de host.&lt;/p&gt;&lt;p&gt;Si eso no funciona, verifique la configuración de TLS y firewall.&lt;/p&gt;</translation>
</message>
<message>
<source>&lt;p&gt;Please check your TLS and firewall settings.&lt;/p&gt;</source>
<translation type="unfinished">&lt;p&gt;Por favor revise su configuración de TLS y firewall.&lt;/p&gt;</translation>
</message>
<message>
<source>%1 Connection Error</source>
<translation type="unfinished">%1 Error de conexión</translation>
</message>
<message>
<source>Do not show this message again</source>
<translation type="unfinished">No volver a mostrar este mensaje</translation>
</message>
<message> <message>
<source>%1 - New Client</source> <source>%1 - New Client</source>
<translation type="unfinished">%1 - Nuevo cliente</translation> <translation type="unfinished">%1 - Nuevo cliente</translation>
@ -736,10 +666,6 @@ Nombres válidos:
<source>&lt;p&gt;Settings are read-only because you only have read access to the file:&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</source> <source>&lt;p&gt;Settings are read-only because you only have read access to the file:&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</source>
<translation type="unfinished">&lt;p&gt;Las configuraciones son de solo lectura porque solo tiene acceso de lectura al archivo:&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</translation> <translation type="unfinished">&lt;p&gt;Las configuraciones son de solo lectura porque solo tiene acceso de lectura al archivo:&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</translation>
</message> </message>
<message>
<source>&lt;p&gt;Sorry, while this version of %1 does support Wayland, this build was not linked with one or more of the required libraries.&lt;/p&gt;&lt;p&gt;Please either switch to X from your login screen or use a build that uses the correct libraries.&lt;/p&gt;&lt;p&gt;If you think this is incorrect, please &lt;a href=&quot;%2&quot;&gt;report a bug&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Please check the logs for more information.&lt;/p&gt;</source>
<translation>&lt;p&gt;Lo sentimos, aunque esta versión de %1 es compatible con Wayland, esta compilación no se vinculó con una o más de las bibliotecas requeridas.&lt;/p&gt;&lt;p&gt;Cambie a X desde su pantalla de inicio de sesión o use una compilación que use las bibliotecas correctas.&lt;/p&gt;&lt;p&gt;Si cree que esto es incorrecto, &lt;a href=&quot;%2&quot;&gt;informe un error&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Consulte los registros para obtener más información.&lt;/p&gt;</translation>
</message>
<message> <message>
<source>No thanks</source> <source>No thanks</source>
<translation type="unfinished">No, gracias</translation> <translation type="unfinished">No, gracias</translation>
@ -1151,30 +1077,14 @@ Al habilitar esta opción, se deshabilitará la interfaz gráfica de usuario (GU
<source>Preferences</source> <source>Preferences</source>
<translation type="unfinished">Preferencias</translation> <translation type="unfinished">Preferencias</translation>
</message> </message>
<message>
<source>&amp;Regular</source>
<translation type="unfinished">&amp;Regular</translation>
</message>
<message>
<source>App</source>
<translation type="unfinished">Aplicación</translation>
</message>
<message> <message>
<source>Check for updates on startup</source> <source>Check for updates on startup</source>
<translation type="unfinished">Buscar actualizaciones al iniciar</translation> <translation type="unfinished">Buscar actualizaciones al iniciar</translation>
</message> </message>
<message>
<source>Hide the window when the app starts</source>
<translation type="unfinished">Ocultar la ventana cuando se inicia la aplicación</translation>
</message>
<message> <message>
<source>Prevent this computer from going to sleep</source> <source>Prevent this computer from going to sleep</source>
<translation type="unfinished">Evitar que esta computadora entre en modo de suspensión</translation> <translation type="unfinished">Evitar que esta computadora entre en modo de suspensión</translation>
</message> </message>
<message>
<source>Leave app running in notification area when the window is closed</source>
<translation type="unfinished">Dejar la aplicación ejecutándose en el área de notificación cuando la ventana esté cerrada</translation>
</message>
<message> <message>
<source>Tray icon style</source> <source>Tray icon style</source>
<translation type="unfinished">Estilo de icono de bandeja</translation> <translation type="unfinished">Estilo de icono de bandeja</translation>
@ -1211,10 +1121,6 @@ Al habilitar esta opción, se deshabilitará la interfaz gráfica de usuario (GU
<source>&amp;Advanced</source> <source>&amp;Advanced</source>
<translation type="unfinished">&amp;Avanzado</translation> <translation type="unfinished">&amp;Avanzado</translation>
</message> </message>
<message>
<source>Networking</source>
<translation type="unfinished">Redes</translation>
</message>
<message> <message>
<source>Port</source> <source>Port</source>
<translation type="unfinished">Puerto</translation> <translation type="unfinished">Puerto</translation>
@ -1223,18 +1129,10 @@ Al habilitar esta opción, se deshabilitará la interfaz gráfica de usuario (GU
<source>Network IP</source> <source>Network IP</source>
<translation type="unfinished">IP de red</translation> <translation type="unfinished">IP de red</translation>
</message> </message>
<message>
<source>Logs</source>
<translation type="unfinished">Registros</translation>
</message>
<message> <message>
<source>Log path</source> <source>Log path</source>
<translation type="unfinished">Ruta de registro</translation> <translation type="unfinished">Ruta de registro</translation>
</message> </message>
<message>
<source>Log to file</source>
<translation type="unfinished">Registrar en archivo</translation>
</message>
<message> <message>
<source>Fatal</source> <source>Fatal</source>
<translation type="unfinished">Fatal</translation> <translation type="unfinished">Fatal</translation>
@ -1315,6 +1213,22 @@ Al habilitar esta opción, se deshabilitará la interfaz gráfica de usuario (GU
<source>Verbose debug output</source> <source>Verbose debug output</source>
<translation type="unfinished">Salida de depuración detallada</translation> <translation type="unfinished">Salida de depuración detallada</translation>
</message> </message>
<message>
<source>Close and save changes</source>
<translation type="unfinished">Cerrar y guardar los cambios</translation>
</message>
<message>
<source>Close and forget changes</source>
<translation type="unfinished">Cerrar y olvidar los cambios</translation>
</message>
<message>
<source>Reset to stored values</source>
<translation type="unfinished">Restablecer los valores almacenados</translation>
</message>
<message>
<source>Reset to default values</source>
<translation type="unfinished">Restablecer valores predeterminados</translation>
</message>
<message> <message>
<source>TLS Certificate Regenerated</source> <source>TLS Certificate Regenerated</source>
<translation type="unfinished">Certificado TLS regenerado</translation> <translation type="unfinished">Certificado TLS regenerado</translation>
@ -1335,10 +1249,6 @@ Al habilitar esta opción, se deshabilitará la interfaz gráfica de usuario (GU
<source>Language</source> <source>Language</source>
<translation type="unfinished">Idioma</translation> <translation type="unfinished">Idioma</translation>
</message> </message>
<message>
<source>Force a language to be used for the GUI.</source>
<translation type="unfinished">Fuerza el uso de un idioma para la interfaz gráfica de usuario.</translation>
</message>
<message> <message>
<source>Enable GUI debug messages</source> <source>Enable GUI debug messages</source>
<translation type="unfinished">Habilitar mensajes de depuración de la interfaz gráfica de usuario</translation> <translation type="unfinished">Habilitar mensajes de depuración de la interfaz gráfica de usuario</translation>
@ -1359,6 +1269,123 @@ Al habilitar esta opción, se deshabilitará la interfaz gráfica de usuario (GU
<source>Include version in the window title</source> <source>Include version in the window title</source>
<translation type="unfinished">Incluir la versión en el título de la ventana</translation> <translation type="unfinished">Incluir la versión en el título de la ventana</translation>
</message> </message>
<message>
<source>Log to file</source>
<translation>Registrar en archivo</translation>
</message>
<message>
<source>&amp;Logs</source>
<translation>&amp;Registro</translation>
</message>
<message>
<source>&amp;General</source>
<translation type="unfinished">&amp;General</translation>
</message>
<message>
<source>&amp;Network</source>
<translation>R&amp;ed</translation>
</message>
<message>
<source>&amp;Window</source>
<translation type="unfinished">&amp;Ventana</translation>
</message>
<message>
<source>When the main window is closed</source>
<translation type="unfinished">Cuando la ventana principal está cerrada</translation>
</message>
<message>
<source>Exit</source>
<translation type="unfinished">Salir</translation>
</message>
<message>
<source>Send to background</source>
<translation type="unfinished">Enviar a segundo plano</translation>
</message>
<message>
<source>When the application starts</source>
<translation type="unfinished">Cuando se inicia la aplicación</translation>
</message>
<message>
<source>Show the main window</source>
<translation type="unfinished">Mostrar la ventana principal</translation>
</message>
<message>
<source>Run command on enter</source>
<translation type="unfinished">Ejecutar comando al presionar Enter</translation>
</message>
<message>
<source>Run command on exit</source>
<translation type="unfinished">Ejecutar comando al salir</translation>
</message>
</context>
<context>
<name>StatusBar</name>
<message>
<source>%1 is not running</source>
<translation type="unfinished">%1 no se está ejecutando</translation>
</message>
<message>
<source>%1 is starting...</source>
<translation type="unfinished">%1 está iniciando...</translation>
</message>
<message>
<source>%1 will retry in a moment...</source>
<translation type="unfinished">%1 lo intentará nuevamente en un momento...</translation>
</message>
<message>
<source>%1 is stopping...</source>
<translation type="unfinished">%1 se está deteniendo...</translation>
</message>
<message>
<source>%1 is waiting for clients</source>
<translation type="unfinished">%1 está esperando clientes</translation>
</message>
<message>
<source>%1 is connecting...</source>
<translation type="unfinished">%1 se está conectando...</translation>
</message>
<message>
<source>%1 is connected as client of %2</source>
<translation type="unfinished">%1 está conectado como cliente de %2</translation>
</message>
<message>
<source>%1 is disconnected</source>
<translation type="unfinished">%1 está desconectado</translation>
</message>
<message numerus="yes">
<source>%1 is connected, with %n client(s): %2</source>
<extracomment>Shown when in server mode and at least 1 client is connected %1 is replaced by the app name %2 will be a list of at least one client %n will be replaced by the number of clients (n is &gt;=1), it is not requried to be in the translation</extracomment>
<translation>
<numerusform>%1 está conectado, con un cliente: %2</numerusform>
<numerusform>%1 está conectado, con %n clientes: %2</numerusform>
</translation>
</message>
<message>
<source>Clients:
%1</source>
<translation type="unfinished">Clientes:
%1</translation>
</message>
<message>
<source>A new version v%1 is available</source>
<translation type="unfinished">Ya está disponible una nueva versión v%1</translation>
</message>
<message>
<source>View local fingerprint</source>
<translation type="unfinished">Ver huella digital local</translation>
</message>
<message>
<source>Update available</source>
<translation type="unfinished">Actualización disponible</translation>
</message>
<message>
<source>%1 Encryption Enabled</source>
<translation type="unfinished">%1 Cifrado habilitado</translation>
</message>
<message>
<source>Encryption Disabled</source>
<translation type="unfinished">Cifrado deshabilitado</translation>
</message>
</context> </context>
<context> <context>
<name>i18n</name> <name>i18n</name>

View File

@ -374,22 +374,14 @@ Vuoi connetterti al server?
<extracomment>stop core shortcut</extracomment> <extracomment>stop core shortcut</extracomment>
<translation>Ctrl+F</translation> <translation>Ctrl+F</translation>
</message> </message>
<message>
<source>&lt;p&gt;Failed to connect to the server &apos;%1&apos;.&lt;/p&gt;&lt;p&gt;A Client with your name is already connected to the server.&lt;/p&gt;Please ensure that you&apos;re using a unique name and that only a single instance of the client process is running.&lt;/p&gt;</source>
<translation>&lt;p&gt;Impossibile connettersi al server &quot;%1&quot;.&lt;/p&gt;&lt;p&gt;Un client con il tuo nome è già connesso al server.&lt;/p&gt;Assicurati di utilizzare un nome univoco e che sia in esecuzione una sola istanza del processo client.&lt;/p&gt;</translation>
</message>
<message> <message>
<source>Your current TLS key is smaller than the minimum allowed size, A new key 2048-bit key will be generated.</source> <source>Your current TLS key is smaller than the minimum allowed size, A new key 2048-bit key will be generated.</source>
<translation>La tua chiave TLS attuale è più piccola della dimensione minima consentita. Verrà generata una nuova chiave a 2048 bit.</translation> <translation>La tua chiave TLS attuale è più piccola della dimensione minima consentita. Verrà generata una nuova chiave a 2048 bit.</translation>
</message> </message>
<message>
<source>View local fingerprint</source>
<translation>Visualizza impronta digitale locale</translation>
</message>
<message>
<source>Update available</source>
<translation>Aggiornamento disponibile</translation>
</message>
<message>
<source>A new version v%1 is available</source>
<translation>È disponibile una nuova versione v%1</translation>
</message>
<message> <message>
<source>Address missing</source> <source>Address missing</source>
<translation>Indirizzo mancante</translation> <translation>Indirizzo mancante</translation>
@ -398,10 +390,6 @@ Vuoi connetterti al server?
<source>Please enter the hostname or IP address of the other computer.</source> <source>Please enter the hostname or IP address of the other computer.</source>
<translation>Inserisci l&apos;hostname o l&apos;indirizzo IP dell&apos;altro computer.</translation> <translation>Inserisci l&apos;hostname o l&apos;indirizzo IP dell&apos;altro computer.</translation>
</message> </message>
<message>
<source>Core cannot be started</source>
<translation>Impossibile avviare il core</translation>
</message>
<message> <message>
<source>Save server configuration as...</source> <source>Save server configuration as...</source>
<translation>Salva configurazione server come...</translation> <translation>Salva configurazione server come...</translation>
@ -431,12 +419,8 @@ Vuoi connetterti al server?
<translation>Disconnetti</translation> <translation>Disconnetti</translation>
</message> </message>
<message> <message>
<source>%1 Encryption Enabled</source> <source>%1 Connection Error</source>
<translation>%1 Crittografia abilitata</translation> <translation>Errore di connessione %1</translation>
</message>
<message>
<source>Encryption Disabled</source>
<translation>Crittografia disabilitata</translation>
</message> </message>
<message> <message>
<source>No IP Detected</source> <source>No IP Detected</source>
@ -502,48 +486,10 @@ Nomi validi:
Può anche usare _ o - Può anche usare _ o -
Deve essere compreso tra 1 e 255 caratteri</translation> Deve essere compreso tra 1 e 255 caratteri</translation>
</message> </message>
<message>
<source>Clients:
%1</source>
<translation>Client:
%1</translation>
</message>
<message>
<source>%1 is starting...</source>
<translation>%1 si sta avviando...</translation>
</message>
<message> <message>
<source>invalid certificate, generating a new one</source> <source>invalid certificate, generating a new one</source>
<translation type="unfinished">certificato non valido, ne viene generato uno nuovo</translation> <translation type="unfinished">certificato non valido, ne viene generato uno nuovo</translation>
</message> </message>
<message>
<source>%1 will retry in a moment...</source>
<translation>%1 riproverà tra un momento...</translation>
</message>
<message>
<source>%1 is stopping...</source>
<translation>%1 si sta arrestando...</translation>
</message>
<message>
<source>%1 is not running</source>
<translation>%1 non è in esecuzione</translation>
</message>
<message>
<source>%1 is waiting for clients</source>
<translation>%1 è in attesa di client</translation>
</message>
<message>
<source>%1 is connecting...</source>
<translation>%1 è in connessione...</translation>
</message>
<message>
<source>%1 is connected as client of %2</source>
<translation>%1 è connesso come client di %2</translation>
</message>
<message>
<source>%1 is disconnected</source>
<translation>%1 è disconnesso</translation>
</message>
<message> <message>
<source>Ctrl+Q</source> <source>Ctrl+Q</source>
<extracomment>Quit shortcut</extracomment> <extracomment>Quit shortcut</extracomment>
@ -557,14 +503,6 @@ Nomi validi:
<source>Screen name already exists</source> <source>Screen name already exists</source>
<translation>Il nome dello schermo esiste già</translation> <translation>Il nome dello schermo esiste già</translation>
</message> </message>
<message numerus="yes">
<source>%1 is connected, with %n client(s): %2</source>
<extracomment>Shown when in server mode and at least 1 client is connected %1 is replaced by the app name %2 will be a list of at least one client %n will be replaced by the number of clients (n is &gt;=1), it is not requried to be in the translation</extracomment>
<translation>
<numerusform>%1 è connesso con un client %2</numerusform>
<numerusform>%1 è connesso con %n client: %2</numerusform>
</translation>
</message>
<message> <message>
<source>This computer&apos;s name:</source> <source>This computer&apos;s name:</source>
<translation>Nome di questo computer:</translation> <translation>Nome di questo computer:</translation>
@ -581,10 +519,6 @@ Nomi validi:
<translation>Usa mouse e tastiera di un altro computer <translation>Usa mouse e tastiera di un altro computer
(imposta questo computer come client)</translation> (imposta questo computer come client)</translation>
</message> </message>
<message>
<source>The Core executable could not be successfully started, although it does exist. Please check if you have sufficient permissions to run this program.</source>
<translation type="unfinished">Non è stato possibile avviare correttamente l&apos;eseguibile Core, sebbene esista. Verifica di disporre delle autorizzazioni necessarie per eseguire questo programma.</translation>
</message>
<message> <message>
<source>&amp;Configure Client</source> <source>&amp;Configure Client</source>
<translation type="unfinished">&amp;Configurare il client</translation> <translation type="unfinished">&amp;Configurare il client</translation>
@ -597,6 +531,26 @@ Nomi validi:
<source>&lt;html&gt;Hostname or IP address of the server computer.&lt;br/&gt;May contain a comma seperated list.&lt;/html&gt;</source> <source>&lt;html&gt;Hostname or IP address of the server computer.&lt;br/&gt;May contain a comma seperated list.&lt;/html&gt;</source>
<translation type="unfinished">&lt;html&gt;Nome host o indirizzo IP del computer server.&lt;br/&gt;Può contenere un elenco separato da virgole.&lt;/html&gt;</translation> <translation type="unfinished">&lt;html&gt;Nome host o indirizzo IP del computer server.&lt;br/&gt;Può contenere un elenco separato da virgole.&lt;/html&gt;</translation>
</message> </message>
<message>
<source>read</source>
<translation type="unfinished">leggere</translation>
</message>
<message>
<source>read and write</source>
<translation type="unfinished">leggere e scrivere</translation>
</message>
<message>
<source>The Core executable could not be started.
Please check if you have sufficient permissions to run %1.</source>
<translation type="unfinished">Impossibile avviare l&apos;eseguibile Core.
Verifica di avere i permessi sufficienti per eseguire %1.</translation>
</message>
<message>
<source>
Additionally, check you are able to %1 the server config file: %2</source>
<translation type="unfinished">
Inoltre, verifica di poter %1 il file di configurazione del server: %2</translation>
</message>
</context> </context>
<context> <context>
<name>NewScreenWidget</name> <name>NewScreenWidget</name>
@ -676,30 +630,6 @@ Nomi validi:
<source>%1 Connected</source> <source>%1 Connected</source>
<translation>%1 Connesso</translation> <translation>%1 Connesso</translation>
</message> </message>
<message>
<source>&lt;p&gt;Failed to connect to the server &apos;%1&apos;.&lt;/p&gt;</source>
<translation>&lt;p&gt;Impossibile connettersi al server &quot;%1&quot;.&lt;/p&gt;</translation>
</message>
<message>
<source>&lt;p&gt;A Client with your name is already connected to the server.&lt;/p&gt;Please ensure that you&apos;re using a unique name and that only a single instance of the client process is running.&lt;/p&gt;</source>
<translation>&lt;p&gt;Un client con il tuo nome è già connesso al server.&lt;/p&gt;Assicurati di utilizzare un nome univoco e che sia in esecuzione una sola istanza del processo client.&lt;/p&gt;</translation>
</message>
<message>
<source>&lt;p&gt;Please try to connect to the server using the server IP address instead of the hostname. &lt;/p&gt;&lt;p&gt;If that doesn&apos;t work, please check your TLS and firewall settings.&lt;/p&gt;</source>
<translation>&lt;p&gt;Prova a connetterti al server usando l&apos;indirizzo IP del server invece del nome host.&lt;/p&gt;&lt;p&gt;Se non funziona, controlla le impostazioni TLS e del firewall.&lt;/p&gt;</translation>
</message>
<message>
<source>&lt;p&gt;Please check your TLS and firewall settings.&lt;/p&gt;</source>
<translation>&lt;p&gt;Controlla le impostazioni TLS e del firewall.&lt;/p&gt;</translation>
</message>
<message>
<source>%1 Connection Error</source>
<translation>Errore di connessione %1</translation>
</message>
<message>
<source>Do not show this message again</source>
<translation>Non mostrare più questo messaggio</translation>
</message>
<message> <message>
<source>%1 - New Client</source> <source>%1 - New Client</source>
<translation>%1 - Nuovo Client</translation> <translation>%1 - Nuovo Client</translation>
@ -736,10 +666,6 @@ Nomi validi:
<source>&lt;p&gt;Settings are read-only because you only have read access to the file:&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</source> <source>&lt;p&gt;Settings are read-only because you only have read access to the file:&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</source>
<translation>&lt;p&gt;Le impostazioni sono di sola lettura perché hai solo accesso in lettura al file:&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</translation> <translation>&lt;p&gt;Le impostazioni sono di sola lettura perché hai solo accesso in lettura al file:&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</translation>
</message> </message>
<message>
<source>&lt;p&gt;Sorry, while this version of %1 does support Wayland, this build was not linked with one or more of the required libraries.&lt;/p&gt;&lt;p&gt;Please either switch to X from your login screen or use a build that uses the correct libraries.&lt;/p&gt;&lt;p&gt;If you think this is incorrect, please &lt;a href=&quot;%2&quot;&gt;report a bug&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Please check the logs for more information.&lt;/p&gt;</source>
<translation>&lt;p&gt;Siamo spiacenti, sebbene questa versione di %1 supporti Wayland, questa build non è stata collegata a una o più librerie richieste.&lt;/p&gt;&lt;p&gt;Passa a X dalla schermata di accesso o usa una build che utilizzi le librerie corrette.&lt;/p&gt;&lt;p&gt;Se ritieni che ciò sia errato, &lt;a href=&quot;%2&quot;&gt;segnala un bug&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Controlla i log per maggiori informazioni.&lt;/p&gt;</translation>
</message>
<message> <message>
<source>No thanks</source> <source>No thanks</source>
<translation>No, grazie</translation> <translation>No, grazie</translation>
@ -1151,30 +1077,14 @@ L&apos;abilitazione di questa impostazione disabiliterà l&apos;interfaccia graf
<source>Preferences</source> <source>Preferences</source>
<translation>Preferenze</translation> <translation>Preferenze</translation>
</message> </message>
<message>
<source>&amp;Regular</source>
<translation>&amp;Regolare</translation>
</message>
<message>
<source>App</source>
<translation>App</translation>
</message>
<message> <message>
<source>Check for updates on startup</source> <source>Check for updates on startup</source>
<translation>Controlla aggiornamenti all&apos;avvio</translation> <translation>Controlla aggiornamenti all&apos;avvio</translation>
</message> </message>
<message>
<source>Hide the window when the app starts</source>
<translation>Nascondi la finestra all&apos;avvio dell&apos;app</translation>
</message>
<message> <message>
<source>Prevent this computer from going to sleep</source> <source>Prevent this computer from going to sleep</source>
<translation>Impedisci a questo computer di andare in sospensione</translation> <translation>Impedisci a questo computer di andare in sospensione</translation>
</message> </message>
<message>
<source>Leave app running in notification area when the window is closed</source>
<translation>Lascia l&apos;app in esecuzione nell&apos;area di notifica quando la finestra viene chiusa</translation>
</message>
<message> <message>
<source>Tray icon style</source> <source>Tray icon style</source>
<translation>Stile icona di notifica</translation> <translation>Stile icona di notifica</translation>
@ -1211,10 +1121,6 @@ L&apos;abilitazione di questa impostazione disabiliterà l&apos;interfaccia graf
<source>&amp;Advanced</source> <source>&amp;Advanced</source>
<translation>&amp;Avanzate</translation> <translation>&amp;Avanzate</translation>
</message> </message>
<message>
<source>Networking</source>
<translation>Rete</translation>
</message>
<message> <message>
<source>Port</source> <source>Port</source>
<translation>Porta</translation> <translation>Porta</translation>
@ -1223,18 +1129,10 @@ L&apos;abilitazione di questa impostazione disabiliterà l&apos;interfaccia graf
<source>Network IP</source> <source>Network IP</source>
<translation>IP di rete</translation> <translation>IP di rete</translation>
</message> </message>
<message>
<source>Logs</source>
<translation>Log</translation>
</message>
<message> <message>
<source>Log path</source> <source>Log path</source>
<translation>Percorso log</translation> <translation>Percorso log</translation>
</message> </message>
<message>
<source>Log to file</source>
<translation>Salva log su file</translation>
</message>
<message> <message>
<source>Fatal</source> <source>Fatal</source>
<translation>Fatale</translation> <translation>Fatale</translation>
@ -1315,6 +1213,22 @@ L&apos;abilitazione di questa impostazione disabiliterà l&apos;interfaccia graf
<source>Verbose debug output</source> <source>Verbose debug output</source>
<translation>Output di debug dettagliato</translation> <translation>Output di debug dettagliato</translation>
</message> </message>
<message>
<source>Close and save changes</source>
<translation type="unfinished">Chiudi e salva le modifiche</translation>
</message>
<message>
<source>Close and forget changes</source>
<translation type="unfinished">Chiudi e dimentica le modifiche</translation>
</message>
<message>
<source>Reset to stored values</source>
<translation type="unfinished">Ripristina i valori memorizzati</translation>
</message>
<message>
<source>Reset to default values</source>
<translation type="unfinished">Ripristina i valori predefiniti</translation>
</message>
<message> <message>
<source>TLS Certificate Regenerated</source> <source>TLS Certificate Regenerated</source>
<translation>Certificato TLS rigenerato</translation> <translation>Certificato TLS rigenerato</translation>
@ -1335,10 +1249,6 @@ L&apos;abilitazione di questa impostazione disabiliterà l&apos;interfaccia graf
<source>Language</source> <source>Language</source>
<translation>Lingua</translation> <translation>Lingua</translation>
</message> </message>
<message>
<source>Force a language to be used for the GUI.</source>
<translation>Forza l&apos;utilizzo di una lingua per la GUI.</translation>
</message>
<message> <message>
<source>Enable GUI debug messages</source> <source>Enable GUI debug messages</source>
<translation>Abilita i messaggi di debug della GUI</translation> <translation>Abilita i messaggi di debug della GUI</translation>
@ -1359,6 +1269,123 @@ L&apos;abilitazione di questa impostazione disabiliterà l&apos;interfaccia graf
<source>Include version in the window title</source> <source>Include version in the window title</source>
<translation type="unfinished">Includi la versione nel titolo della finestra</translation> <translation type="unfinished">Includi la versione nel titolo della finestra</translation>
</message> </message>
<message>
<source>Log to file</source>
<translation>Salva log su file</translation>
</message>
<message>
<source>&amp;Logs</source>
<translation>&amp;Logs</translation>
</message>
<message>
<source>&amp;General</source>
<translation type="unfinished">&amp;Generale</translation>
</message>
<message>
<source>&amp;Network</source>
<translation>&amp;Rete</translation>
</message>
<message>
<source>&amp;Window</source>
<translation type="unfinished">&amp;Finestra</translation>
</message>
<message>
<source>When the main window is closed</source>
<translation type="unfinished">Quando la finestra principale è chiusa</translation>
</message>
<message>
<source>Exit</source>
<translation type="unfinished">Esci</translation>
</message>
<message>
<source>Send to background</source>
<translation type="unfinished">Invia in background</translation>
</message>
<message>
<source>When the application starts</source>
<translation type="unfinished">Quando l&apos;applicazione si avvia</translation>
</message>
<message>
<source>Show the main window</source>
<translation type="unfinished">Mostra la finestra principale</translation>
</message>
<message>
<source>Run command on enter</source>
<translation type="unfinished">Esegui il comando alla pressione di Invio</translation>
</message>
<message>
<source>Run command on exit</source>
<translation type="unfinished">Esegui comando all&apos;uscita</translation>
</message>
</context>
<context>
<name>StatusBar</name>
<message>
<source>%1 is not running</source>
<translation>%1 non è in esecuzione</translation>
</message>
<message>
<source>%1 is starting...</source>
<translation>%1 si sta avviando...</translation>
</message>
<message>
<source>%1 will retry in a moment...</source>
<translation>%1 riproverà tra un momento...</translation>
</message>
<message>
<source>%1 is stopping...</source>
<translation>%1 si sta arrestando...</translation>
</message>
<message>
<source>%1 is waiting for clients</source>
<translation>%1 è in attesa di client</translation>
</message>
<message>
<source>%1 is connecting...</source>
<translation>%1 è in connessione...</translation>
</message>
<message>
<source>%1 is connected as client of %2</source>
<translation>%1 è connesso come client di %2</translation>
</message>
<message>
<source>%1 is disconnected</source>
<translation>%1 è disconnesso</translation>
</message>
<message numerus="yes">
<source>%1 is connected, with %n client(s): %2</source>
<extracomment>Shown when in server mode and at least 1 client is connected %1 is replaced by the app name %2 will be a list of at least one client %n will be replaced by the number of clients (n is &gt;=1), it is not requried to be in the translation</extracomment>
<translation>
<numerusform>%1 è connesso con un client %2</numerusform>
<numerusform>%1 è connesso con %n client: %2</numerusform>
</translation>
</message>
<message>
<source>Clients:
%1</source>
<translation>Client:
%1</translation>
</message>
<message>
<source>A new version v%1 is available</source>
<translation>È disponibile una nuova versione v%1</translation>
</message>
<message>
<source>View local fingerprint</source>
<translation>Visualizza impronta digitale locale</translation>
</message>
<message>
<source>Update available</source>
<translation>Aggiornamento disponibile</translation>
</message>
<message>
<source>%1 Encryption Enabled</source>
<translation>%1 Crittografia abilitata</translation>
</message>
<message>
<source>Encryption Disabled</source>
<translation>Crittografia disabilitata</translation>
</message>
</context> </context>
<context> <context>
<name>i18n</name> <name>i18n</name>

View File

@ -143,47 +143,47 @@ p, li { white-space: pre-wrap; }
<name>ClientConfigDialog</name> <name>ClientConfigDialog</name>
<message> <message>
<source>Client Configuration</source> <source>Client Configuration</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Use server&apos;s keyboard language on this computer</source> <source>Use server&apos;s keyboard language on this computer</source>
<translation type="unfinished">使</translation> <translation>使</translation>
</message> </message>
<message> <message>
<source>Scroll Modifiers</source> <source>Scroll Modifiers</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Invert</source> <source>Invert</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Horizontal Scroll</source> <source>Horizontal Scroll</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Vertical Scroll</source> <source>Vertical Scroll</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Scale</source> <source>Scale</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Close and save changes</source> <source>Close and save changes</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Close and forget changes</source> <source>Close and forget changes</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Reset to stored values</source> <source>Reset to stored values</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Reset to default values</source> <source>Reset to default values</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
</context> </context>
<context> <context>
@ -324,7 +324,7 @@ Do you want to connect to the server?
</message> </message>
<message> <message>
<source>&amp;Configure Server</source> <source>&amp;Configure Server</source>
<translation>(&amp;C)</translation> <translation>(&amp;C)</translation>
</message> </message>
<message> <message>
<source>Export server configuration</source> <source>Export server configuration</source>
@ -348,11 +348,7 @@ Do you want to connect to the server?
</message> </message>
<message> <message>
<source>invalid certificate, generating a new one</source> <source>invalid certificate, generating a new one</source>
<translation type="unfinished"></translation> <translation></translation>
</message>
<message>
<source>A new version v%1 is available</source>
<translation>(v%1)</translation>
</message> </message>
<message> <message>
<source>Address missing</source> <source>Address missing</source>
@ -362,10 +358,6 @@ Do you want to connect to the server?
<source>Please enter the hostname or IP address of the other computer.</source> <source>Please enter the hostname or IP address of the other computer.</source>
<translation>IPアドレスかホスト名を入力してください</translation> <translation>IPアドレスかホスト名を入力してください</translation>
</message> </message>
<message>
<source>Core cannot be started</source>
<translation></translation>
</message>
<message> <message>
<source>Save server configuration as...</source> <source>Save server configuration as...</source>
<translation></translation> <translation></translation>
@ -395,12 +387,8 @@ Do you want to connect to the server?
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>%1 Encryption Enabled</source> <source>&lt;p&gt;Failed to connect to the server &apos;%1&apos;.&lt;/p&gt;&lt;p&gt;A Client with your name is already connected to the server.&lt;/p&gt;Please ensure that you&apos;re using a unique name and that only a single instance of the client process is running.&lt;/p&gt;</source>
<translation>%1 </translation> <translation>&lt;p&gt; &apos;%1&apos; &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;</translation>
</message>
<message>
<source>Encryption Disabled</source>
<translation></translation>
</message> </message>
<message> <message>
<source>No IP Detected</source> <source>No IP Detected</source>
@ -412,7 +400,7 @@ Do you want to connect to the server?
</message> </message>
<message> <message>
<source>Using IP: </source> <source>Using IP: </source>
<translation type="unfinished">IPアドレスを使用する </translation> <translation>使IPアドレス </translation>
</message> </message>
<message> <message>
<source>&lt;p&gt;If connecting via the hostname fails, try %1&lt;/p&gt;</source> <source>&lt;p&gt;If connecting via the hostname fails, try %1&lt;/p&gt;</source>
@ -420,7 +408,7 @@ Do you want to connect to the server?
</message> </message>
<message> <message>
<source>the suggested IP.</source> <source>the suggested IP.</source>
<translation>IPアドレス</translation> <translation>IPアドレス</translation>
</message> </message>
<message> <message>
<source>one of the following IPs:&lt;br/&gt;%1</source> <source>one of the following IPs:&lt;br/&gt;%1</source>
@ -429,41 +417,9 @@ Do you want to connect to the server?
<message> <message>
<source> <source>
A bound IP is now invalid, you may need to restart the server.</source> A bound IP is now invalid, you may need to restart the server.</source>
<translation type="unfinished"> <translation>
IPアドレスが無効になりました</translation> IPアドレスが無効になりました</translation>
</message> </message>
<message>
<source>%1 is starting...</source>
<translation>%1 </translation>
</message>
<message>
<source>%1 will retry in a moment...</source>
<translation>%1 </translation>
</message>
<message>
<source>%1 is stopping...</source>
<translation>%1 </translation>
</message>
<message>
<source>%1 is not running</source>
<translation>%1 </translation>
</message>
<message>
<source>%1 is waiting for clients</source>
<translation>%1 </translation>
</message>
<message>
<source>%1 is connecting...</source>
<translation>%1 </translation>
</message>
<message>
<source>%1 is connected as client of %2</source>
<translation>%1 %2 </translation>
</message>
<message>
<source>%1 is disconnected</source>
<translation>%1 </translation>
</message>
<message> <message>
<source>&amp;File</source> <source>&amp;File</source>
<translation>(&amp;F)</translation> <translation>(&amp;F)</translation>
@ -533,14 +489,6 @@ A bound IP is now invalid, you may need to restart the server.</source>
<extracomment>Quit shortcut</extracomment> <extracomment>Quit shortcut</extracomment>
<translation>Ctrl+Q</translation> <translation>Ctrl+Q</translation>
</message> </message>
<message>
<source>View local fingerprint</source>
<translation></translation>
</message>
<message>
<source>Update available</source>
<translation></translation>
</message>
<message> <message>
<source>Invalid Screen Name</source> <source>Invalid Screen Name</source>
<translation></translation> <translation></translation>
@ -563,38 +511,45 @@ Valid names:
_ - _ -
1255</translation> 1255</translation>
</message> </message>
<message numerus="yes">
<source>%1 is connected, with %n client(s): %2</source>
<extracomment>Shown when in server mode and at least 1 client is connected %1 is replaced by the app name %2 will be a list of at least one client %n will be replaced by the number of clients (n is &gt;=1), it is not requried to be in the translation</extracomment>
<translation>
<numerusform>%1 %n台のクライアントと接続中: %2</numerusform>
</translation>
</message>
<message> <message>
<source>Clients: <source>%1 Connection Error</source>
%1</source> <translation>%1 </translation>
<translation>:
%1</translation>
</message> </message>
<message> <message>
<source>Suggested IP: </source> <source>Suggested IP: </source>
<translation type="unfinished">IPアドレス: </translation> <translation>IPアドレス: </translation>
</message>
<message>
<source>The Core executable could not be successfully started, although it does exist. Please check if you have sufficient permissions to run this program.</source>
<translation></translation>
</message> </message>
<message> <message>
<source>&amp;Configure Client</source> <source>&amp;Configure Client</source>
<translation type="unfinished"> (&amp;C)</translation> <translation> (&amp;C)</translation>
</message> </message>
<message> <message>
<source>Connect to:</source> <source>Connect to:</source>
<translation type="unfinished">:</translation> <translation>:</translation>
</message> </message>
<message> <message>
<source>&lt;html&gt;Hostname or IP address of the server computer.&lt;br/&gt;May contain a comma seperated list.&lt;/html&gt;</source> <source>&lt;html&gt;Hostname or IP address of the server computer.&lt;br/&gt;May contain a comma seperated list.&lt;/html&gt;</source>
<translation type="unfinished">&lt;html&gt; IP &lt;br/&gt;&lt;/html&gt;</translation> <translation>&lt;html&gt; IP &lt;br/&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>read</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>read and write</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>The Core executable could not be started.
Please check if you have sufficient permissions to run %1.</source>
<translation type="unfinished">Coreの実行ファイルを起動できませんでした
%1</translation>
</message>
<message>
<source>
Additionally, check you are able to %1 the server config file: %2</source>
<translation type="unfinished">
%1: %2</translation>
</message> </message>
</context> </context>
<context> <context>
@ -677,30 +632,6 @@ Valid names:
<source>%1 Connected</source> <source>%1 Connected</source>
<translation>%1 </translation> <translation>%1 </translation>
</message> </message>
<message>
<source>&lt;p&gt;Failed to connect to the server &apos;%1&apos;.&lt;/p&gt;</source>
<translation>&lt;p&gt; &apos;%1&apos; &lt;/p&gt;</translation>
</message>
<message>
<source>&lt;p&gt;A Client with your name is already connected to the server.&lt;/p&gt;Please ensure that you&apos;re using a unique name and that only a single instance of the client process is running.&lt;/p&gt;</source>
<translation>&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;</translation>
</message>
<message>
<source>&lt;p&gt;Please try to connect to the server using the server IP address instead of the hostname. &lt;/p&gt;&lt;p&gt;If that doesn&apos;t work, please check your TLS and firewall settings.&lt;/p&gt;</source>
<translation>&lt;p&gt;IPアドレスで接続してみてください&lt;/p&gt;&lt;p&gt;TLS&lt;/p&gt;</translation>
</message>
<message>
<source>&lt;p&gt;Please check your TLS and firewall settings.&lt;/p&gt;</source>
<translation>&lt;p&gt;TLSとファイアウォールの設定を確認してください&lt;/p&gt;</translation>
</message>
<message>
<source>%1 Connection Error</source>
<translation>%1 </translation>
</message>
<message>
<source>Do not show this message again</source>
<translation></translation>
</message>
<message> <message>
<source>%1 - New Client</source> <source>%1 - New Client</source>
<translation>%1 - </translation> <translation>%1 - </translation>
@ -737,10 +668,6 @@ Valid names:
<source>&lt;p&gt;Settings are read-only because you only have read access to the file:&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</source> <source>&lt;p&gt;Settings are read-only because you only have read access to the file:&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</source>
<translation>&lt;p&gt;:&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</translation> <translation>&lt;p&gt;:&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</translation>
</message> </message>
<message>
<source>&lt;p&gt;Sorry, while this version of %1 does support Wayland, this build was not linked with one or more of the required libraries.&lt;/p&gt;&lt;p&gt;Please either switch to X from your login screen or use a build that uses the correct libraries.&lt;/p&gt;&lt;p&gt;If you think this is incorrect, please &lt;a href=&quot;%2&quot;&gt;report a bug&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Please check the logs for more information.&lt;/p&gt;</source>
<translation>&lt;p&gt; %1 Wayland &lt;/p&gt;&lt;p&gt; X 使&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;%2&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;</translation>
</message>
<message> <message>
<source>No thanks</source> <source>No thanks</source>
<translation></translation> <translation></translation>
@ -772,21 +699,21 @@ Valid names:
<message> <message>
<source>failed to read key from certificate file: %1</source> <source>failed to read key from certificate file: %1</source>
<extracomment>%1 will be replaced by the certificate path</extracomment> <extracomment>%1 will be replaced by the certificate path</extracomment>
<translation type="unfinished">: %1</translation> <translation>: %1</translation>
</message> </message>
<message> <message>
<source>failed to parse certificate file: %1</source> <source>failed to parse certificate file: %1</source>
<extracomment>%1 will be replaced by the certificate path</extracomment> <extracomment>%1 will be replaced by the certificate path</extracomment>
<translation type="unfinished">: %1</translation> <translation>: %1</translation>
</message> </message>
<message> <message>
<source>key detected is the incorrect size</source> <source>key detected is the incorrect size</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>failed to read RSA key from certificate file: %1</source> <source>failed to read RSA key from certificate file: %1</source>
<extracomment>%1 will be replaced by the certificate path</extracomment> <extracomment>%1 will be replaced by the certificate path</extracomment>
<translation type="unfinished"> RSA : %1</translation> <translation> RSA : %1</translation>
</message> </message>
<message> <message>
<source>%1 is already running</source> <source>%1 is already running</source>
@ -939,19 +866,19 @@ Valid names:
<name>SearchWidget</name> <name>SearchWidget</name>
<message> <message>
<source>Search</source> <source>Search</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Find next</source> <source>Find next</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Find previous</source> <source>Find previous</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Find...</source> <source>Find...</source>
<translation type="unfinished">...</translation> <translation>...</translation>
</message> </message>
</context> </context>
<context> <context>
@ -1152,30 +1079,14 @@ Enabling this setting will disable the server config GUI.</source>
<source>Preferences</source> <source>Preferences</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>&amp;Regular</source>
<translation>(&amp;R)</translation>
</message>
<message>
<source>App</source>
<translation></translation>
</message>
<message> <message>
<source>Check for updates on startup</source> <source>Check for updates on startup</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Hide the window when the app starts</source>
<translation></translation>
</message>
<message> <message>
<source>Prevent this computer from going to sleep</source> <source>Prevent this computer from going to sleep</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Leave app running in notification area when the window is closed</source>
<translation></translation>
</message>
<message> <message>
<source>Tray icon style</source> <source>Tray icon style</source>
<translation></translation> <translation></translation>
@ -1212,10 +1123,6 @@ Enabling this setting will disable the server config GUI.</source>
<source>&amp;Advanced</source> <source>&amp;Advanced</source>
<translation>(&amp;A)</translation> <translation>(&amp;A)</translation>
</message> </message>
<message>
<source>Networking</source>
<translation></translation>
</message>
<message> <message>
<source>Port</source> <source>Port</source>
<translation></translation> <translation></translation>
@ -1224,10 +1131,6 @@ Enabling this setting will disable the server config GUI.</source>
<source>Network IP</source> <source>Network IP</source>
<translation>IPアドレス</translation> <translation>IPアドレス</translation>
</message> </message>
<message>
<source>Logs</source>
<translation></translation>
</message>
<message> <message>
<source>Level</source> <source>Level</source>
<translation></translation> <translation></translation>
@ -1268,10 +1171,6 @@ Enabling this setting will disable the server config GUI.</source>
<source>Log path</source> <source>Log path</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Log to file</source>
<translation></translation>
</message>
<message> <message>
<source>Using a Debug log level may affect performance. Only use a Debug level if you are attempting to debug an issue or are gathering logs to submit with a bug report.</source> <source>Using a Debug log level may affect performance. Only use a Debug level if you are attempting to debug an issue or are gathering logs to submit with a bug report.</source>
<translation>使</translation> <translation>使</translation>
@ -1288,10 +1187,6 @@ Enabling this setting will disable the server config GUI.</source>
<source>Always run as system (work at login screen and UAC)</source> <source>Always run as system (work at login screen and UAC)</source>
<translation>system権限で実行する(UACで必要)</translation> <translation>system権限で実行する(UACで必要)</translation>
</message> </message>
<message>
<source>Force a language to be used for the GUI.</source>
<translation>GUIの使用言語を設定します</translation>
</message>
<message> <message>
<source>Language</source> <source>Language</source>
<translation></translation> <translation></translation>
@ -1344,6 +1239,22 @@ Enabling this setting will disable the server config GUI.</source>
<source>Verbose debug output</source> <source>Verbose debug output</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Close and save changes</source>
<translation></translation>
</message>
<message>
<source>Close and forget changes</source>
<translation></translation>
</message>
<message>
<source>Reset to stored values</source>
<translation></translation>
</message>
<message>
<source>Reset to default values</source>
<translation></translation>
</message>
<message> <message>
<source>Enable wl-clipboard support</source> <source>Enable wl-clipboard support</source>
<translation>wl-clipboard </translation> <translation>wl-clipboard </translation>
@ -1354,11 +1265,127 @@ Enabling this setting will disable the server config GUI.</source>
</message> </message>
<message> <message>
<source>Automatic</source> <source>Automatic</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Include version in the window title</source> <source>Include version in the window title</source>
<translation type="unfinished"></translation> <translation></translation>
</message>
<message>
<source>Log to file</source>
<translation></translation>
</message>
<message>
<source>&amp;Logs</source>
<translation>(&amp;L)</translation>
</message>
<message>
<source>&amp;General</source>
<translation>(&amp;G)</translation>
</message>
<message>
<source>&amp;Network</source>
<translation>(&amp;N)</translation>
</message>
<message>
<source>&amp;Window</source>
<translation type="unfinished">(&amp;W)</translation>
</message>
<message>
<source>When the main window is closed</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Exit</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Send to background</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>When the application starts</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Show the main window</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Run command on enter</source>
<translation type="unfinished">Enterキーでコマンドを実行</translation>
</message>
<message>
<source>Run command on exit</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>StatusBar</name>
<message>
<source>%1 is not running</source>
<translation>%1 </translation>
</message>
<message>
<source>%1 is starting...</source>
<translation>%1 </translation>
</message>
<message>
<source>%1 will retry in a moment...</source>
<translation>%1 </translation>
</message>
<message>
<source>%1 is stopping...</source>
<translation>%1 </translation>
</message>
<message>
<source>%1 is waiting for clients</source>
<translation>%1 </translation>
</message>
<message>
<source>%1 is connecting...</source>
<translation>%1 </translation>
</message>
<message>
<source>%1 is connected as client of %2</source>
<translation>%1 %2 </translation>
</message>
<message>
<source>%1 is disconnected</source>
<translation>%1 </translation>
</message>
<message numerus="yes">
<source>%1 is connected, with %n client(s): %2</source>
<extracomment>Shown when in server mode and at least 1 client is connected %1 is replaced by the app name %2 will be a list of at least one client %n will be replaced by the number of clients (n is &gt;=1), it is not requried to be in the translation</extracomment>
<translation>
<numerusform>%1 %n台のクライアントと接続中: %2</numerusform>
</translation>
</message>
<message>
<source>Clients:
%1</source>
<translation>:
%1</translation>
</message>
<message>
<source>A new version v%1 is available</source>
<translation>(v%1)</translation>
</message>
<message>
<source>View local fingerprint</source>
<translation></translation>
</message>
<message>
<source>Update available</source>
<translation></translation>
</message>
<message>
<source>%1 Encryption Enabled</source>
<translation>%1 </translation>
</message>
<message>
<source>Encryption Disabled</source>
<translation></translation>
</message> </message>
</context> </context>
<context> <context>

View File

@ -151,11 +151,11 @@ p, li { white-space: pre-wrap; }
</message> </message>
<message> <message>
<source>Scroll Modifiers</source> <source>Scroll Modifiers</source>
<translation type="unfinished"> </translation> <translation> </translation>
</message> </message>
<message> <message>
<source>Invert</source> <source>Invert</source>
<translation type="unfinished"> </translation> <translation></translation>
</message> </message>
<message> <message>
<source>Horizontal Scroll</source> <source>Horizontal Scroll</source>
@ -171,11 +171,11 @@ p, li { white-space: pre-wrap; }
</message> </message>
<message> <message>
<source>Close and save changes</source> <source>Close and save changes</source>
<translation type="unfinished"> </translation> <translation> </translation>
</message> </message>
<message> <message>
<source>Close and forget changes</source> <source>Close and forget changes</source>
<translation type="unfinished"> </translation> <translation> </translation>
</message> </message>
<message> <message>
<source>Reset to stored values</source> <source>Reset to stored values</source>
@ -190,16 +190,16 @@ p, li { white-space: pre-wrap; }
<name>FingerprintDialog</name> <name>FingerprintDialog</name>
<message> <message>
<source>Local Fingerprints</source> <source>Local Fingerprints</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
<source>Local computer&apos;s fingerprint</source> <source>Local computer&apos;s fingerprint</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
<source>Compare the fingerprints in this dialog to those on the %1. <source>Compare the fingerprints in this dialog to those on the %1.
Only connect if they match!</source> Only connect if they match!</source>
<translation> %1 . <translation> %1 .
!</translation> !</translation>
</message> </message>
<message> <message>
@ -236,11 +236,11 @@ Do you want to connect to the server?
</message> </message>
<message> <message>
<source>Server Fingerprint</source> <source>Server Fingerprint</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
<source>Client Fingerprint</source> <source>Client Fingerprint</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
<source>Show image</source> <source>Show image</source>
@ -252,11 +252,11 @@ Do you want to connect to the server?
</message> </message>
<message> <message>
<source>Display the fingerprint as an image</source> <source>Display the fingerprint as an image</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
<source>Display the fingerprint as a hash</source> <source>Display the fingerprint as a hash</source>
<translation> </translation> <translation> </translation>
</message> </message>
</context> </context>
<context> <context>
@ -350,10 +350,6 @@ Do you want to connect to the server?
<source>invalid certificate, generating a new one</source> <source>invalid certificate, generating a new one</source>
<translation type="unfinished"> . </translation> <translation type="unfinished"> . </translation>
</message> </message>
<message>
<source>A new version v%1 is available</source>
<translation> (v%1) </translation>
</message>
<message> <message>
<source>Address missing</source> <source>Address missing</source>
<translation> </translation> <translation> </translation>
@ -362,10 +358,6 @@ Do you want to connect to the server?
<source>Please enter the hostname or IP address of the other computer.</source> <source>Please enter the hostname or IP address of the other computer.</source>
<translation> IP .</translation> <translation> IP .</translation>
</message> </message>
<message>
<source>Core cannot be started</source>
<translation> </translation>
</message>
<message> <message>
<source>Save server configuration as...</source> <source>Save server configuration as...</source>
<translation> ...</translation> <translation> ...</translation>
@ -395,12 +387,8 @@ Do you want to connect to the server?
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
<source>%1 Encryption Enabled</source> <source>&lt;p&gt;Failed to connect to the server &apos;%1&apos;.&lt;/p&gt;&lt;p&gt;A Client with your name is already connected to the server.&lt;/p&gt;Please ensure that you&apos;re using a unique name and that only a single instance of the client process is running.&lt;/p&gt;</source>
<translation>%1 </translation> <translation>&lt;p&gt; &apos;%1&apos; .&lt;/p&gt;&lt;p&gt; .&lt;/p&gt;&lt;p&gt; , .&lt;/p&gt;</translation>
</message>
<message>
<source>Encryption Disabled</source>
<translation> </translation>
</message> </message>
<message> <message>
<source>No IP Detected</source> <source>No IP Detected</source>
@ -429,40 +417,8 @@ Do you want to connect to the server?
<message> <message>
<source> <source>
A bound IP is now invalid, you may need to restart the server.</source> A bound IP is now invalid, you may need to restart the server.</source>
<translation type="unfinished"> <translation>
IP가 . .</translation> IP가 . .</translation>
</message>
<message>
<source>%1 is starting...</source>
<translation>%1 ...</translation>
</message>
<message>
<source>%1 will retry in a moment...</source>
<translation>%1 ...</translation>
</message>
<message>
<source>%1 is stopping...</source>
<translation>%1 ...</translation>
</message>
<message>
<source>%1 is not running</source>
<translation>%1 </translation>
</message>
<message>
<source>%1 is waiting for clients</source>
<translation>%1 </translation>
</message>
<message>
<source>%1 is connecting...</source>
<translation>%1 ...</translation>
</message>
<message>
<source>%1 is connected as client of %2</source>
<translation>%1() %2 </translation>
</message>
<message>
<source>%1 is disconnected</source>
<translation>%1 </translation>
</message> </message>
<message> <message>
<source>&amp;File</source> <source>&amp;File</source>
@ -533,14 +489,6 @@ A bound IP is now invalid, you may need to restart the server.</source>
<extracomment>Quit shortcut</extracomment> <extracomment>Quit shortcut</extracomment>
<translation>Ctrl+Q</translation> <translation>Ctrl+Q</translation>
</message> </message>
<message>
<source>View local fingerprint</source>
<translation> </translation>
</message>
<message>
<source>Update available</source>
<translation> </translation>
</message>
<message> <message>
<source>Invalid Screen Name</source> <source>Invalid Screen Name</source>
<translation> </translation> <translation> </translation>
@ -563,27 +511,14 @@ Valid names:
_ - _ -
1~255</translation> 1~255</translation>
</message> </message>
<message numerus="yes">
<source>%1 is connected, with %n client(s): %2</source>
<extracomment>Shown when in server mode and at least 1 client is connected %1 is replaced by the app name %2 will be a list of at least one client %n will be replaced by the number of clients (n is &gt;=1), it is not requried to be in the translation</extracomment>
<translation>
<numerusform>%1() %n대의 : %2</numerusform>
</translation>
</message>
<message> <message>
<source>Clients: <source>%1 Connection Error</source>
%1</source> <translation>%1 </translation>
<translation>:
%1</translation>
</message> </message>
<message> <message>
<source>Suggested IP: </source> <source>Suggested IP: </source>
<translation type="unfinished"> IP: </translation> <translation type="unfinished"> IP: </translation>
</message> </message>
<message>
<source>The Core executable could not be successfully started, although it does exist. Please check if you have sufficient permissions to run this program.</source>
<translation> . .</translation>
</message>
<message> <message>
<source>&amp;Configure Client</source> <source>&amp;Configure Client</source>
<translation type="unfinished"> (&amp;C)</translation> <translation type="unfinished"> (&amp;C)</translation>
@ -594,7 +529,27 @@ Valid names:
</message> </message>
<message> <message>
<source>&lt;html&gt;Hostname or IP address of the server computer.&lt;br/&gt;May contain a comma seperated list.&lt;/html&gt;</source> <source>&lt;html&gt;Hostname or IP address of the server computer.&lt;br/&gt;May contain a comma seperated list.&lt;/html&gt;</source>
<translation type="unfinished">&lt;html&gt; IP .&lt;br/&gt; .&lt;/html&gt;</translation> <translation>&lt;html&gt; IP .&lt;br/&gt; .&lt;/html&gt;</translation>
</message>
<message>
<source>read</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>read and write</source>
<translation type="unfinished"> </translation>
</message>
<message>
<source>The Core executable could not be started.
Please check if you have sufficient permissions to run %1.</source>
<translation type="unfinished">Core .
%1 .</translation>
</message>
<message>
<source>
Additionally, check you are able to %1 the server config file: %2</source>
<translation type="unfinished">
%1 : %2</translation>
</message> </message>
</context> </context>
<context> <context>
@ -602,7 +557,7 @@ Valid names:
<message> <message>
<source>Unnamed</source> <source>Unnamed</source>
<extracomment>Used as the hostname. Translation may not contain spaces</extracomment> <extracomment>Used as the hostname. Translation may not contain spaces</extracomment>
<translation>Unnamed</translation> <translation></translation>
</message> </message>
</context> </context>
<context> <context>
@ -618,13 +573,13 @@ Valid names:
<message> <message>
<source>&lt;p&gt;Sorry, a fatal error has occurred and the application must now exit.&lt;/p&gt; <source>&lt;p&gt;Sorry, a fatal error has occurred and the application must now exit.&lt;/p&gt;
</source> </source>
<translation>&lt;p&gt;. .&lt;/p&gt; <translation>&lt;p&gt; .&lt;/p&gt;
</translation> </translation>
</message> </message>
<message> <message>
<source>&lt;p&gt;Sorry, a critical error has occurred.&lt;/p&gt; <source>&lt;p&gt;Sorry, a critical error has occurred.&lt;/p&gt;
</source> </source>
<translation>&lt;p&gt;. .&lt;/p&gt; <translation>&lt;p&gt; &lt;/p&gt;
</translation> </translation>
</message> </message>
<message> <message>
@ -641,7 +596,7 @@ Valid names:
</message> </message>
<message> <message>
<source>&lt;p&gt;On Linux systems using GNOME 3, the notification area might be disabled. You may need to &lt;a href=&quot;%1&quot;&gt;enable an extension&lt;/a&gt; to see the %2 tray icon.&lt;/p&gt;</source> <source>&lt;p&gt;On Linux systems using GNOME 3, the notification area might be disabled. You may need to &lt;a href=&quot;%1&quot;&gt;enable an extension&lt;/a&gt; to see the %2 tray icon.&lt;/p&gt;</source>
<translation>&lt;p&gt;GNOME 3 Linux . %2 &lt;a href=&quot;%1&quot;&gt; &lt;/a&gt; .&lt;/p&gt;</translation> <translation>&lt;p&gt;GNOME 3 Linux . %2 &lt;a href=&quot;%1&quot;&gt; &lt;/a&gt; .&lt;/p&gt;</translation>
</message> </message>
<message> <message>
<source>%1 Server</source> <source>%1 Server</source>
@ -653,11 +608,11 @@ Valid names:
</message> </message>
<message> <message>
<source>&lt;p&gt;%1 is now connected!&lt;/p&gt;</source> <source>&lt;p&gt;%1 is now connected!&lt;/p&gt;</source>
<translation>&lt;p&gt;%1() !&lt;/p&gt;</translation> <translation>&lt;p&gt;%1() !&lt;/p&gt;</translation>
</message> </message>
<message> <message>
<source>&lt;p&gt;Try moving your mouse to your other computer. Once there, go ahead and type something.&lt;/p&gt;&lt;p&gt;Don&apos;t forget, you can copy and paste between computers too.&lt;/p&gt;</source> <source>&lt;p&gt;Try moving your mouse to your other computer. Once there, go ahead and type something.&lt;/p&gt;&lt;p&gt;Don&apos;t forget, you can copy and paste between computers too.&lt;/p&gt;</source>
<translation>&lt;p&gt; . .&lt;/p&gt;&lt;p&gt; / .&lt;/p&gt;</translation> <translation>&lt;p&gt; . .&lt;/p&gt;&lt;p&gt; / .&lt;/p&gt;</translation>
</message> </message>
<message> <message>
<source>&lt;p&gt;Try controlling this computer remotely.&lt;/p&gt;</source> <source>&lt;p&gt;Try controlling this computer remotely.&lt;/p&gt;</source>
@ -675,37 +630,13 @@ Valid names:
<source>%1 Connected</source> <source>%1 Connected</source>
<translation>%1 </translation> <translation>%1 </translation>
</message> </message>
<message>
<source>&lt;p&gt;Failed to connect to the server &apos;%1&apos;.&lt;/p&gt;</source>
<translation>&lt;p&gt; &apos;%1&apos; .&lt;/p&gt;</translation>
</message>
<message>
<source>&lt;p&gt;A Client with your name is already connected to the server.&lt;/p&gt;Please ensure that you&apos;re using a unique name and that only a single instance of the client process is running.&lt;/p&gt;</source>
<translation>&lt;p&gt; .&lt;/p&gt;&lt;p&gt; , .&lt;/p&gt;</translation>
</message>
<message>
<source>&lt;p&gt;Please try to connect to the server using the server IP address instead of the hostname. &lt;/p&gt;&lt;p&gt;If that doesn&apos;t work, please check your TLS and firewall settings.&lt;/p&gt;</source>
<translation>&lt;p&gt; IP .&lt;/p&gt;&lt;p&gt; TLS .&lt;/p&gt;</translation>
</message>
<message>
<source>&lt;p&gt;Please check your TLS and firewall settings.&lt;/p&gt;</source>
<translation>&lt;p&gt;TLS .&lt;/p&gt;</translation>
</message>
<message>
<source>%1 Connection Error</source>
<translation>%1 </translation>
</message>
<message>
<source>Do not show this message again</source>
<translation> </translation>
</message>
<message> <message>
<source>%1 - New Client</source> <source>%1 - New Client</source>
<translation>%1 - </translation> <translation>%1 - </translation>
</message> </message>
<message> <message>
<source>A new client called &apos;%1&apos; has been accepted. You&apos;ll need to add it to your server&apos;s screen layout.</source> <source>A new client called &apos;%1&apos; has been accepted. You&apos;ll need to add it to your server&apos;s screen layout.</source>
<translation> &apos;%1&apos;() . .</translation> <translation> &apos;%1&apos;() . .</translation>
</message> </message>
<message> <message>
<source>Ignore</source> <source>Ignore</source>
@ -725,7 +656,7 @@ Valid names:
</message> </message>
<message> <message>
<source>&lt;p&gt;Are you sure you want to clear all settings and restart %1?&lt;/p&gt;&lt;p&gt;This action cannot be undone.&lt;/p&gt;</source> <source>&lt;p&gt;Are you sure you want to clear all settings and restart %1?&lt;/p&gt;&lt;p&gt;This action cannot be undone.&lt;/p&gt;</source>
<translation>&lt;p&gt; %1() ?&lt;/p&gt;&lt;p&gt; .&lt;/p&gt;</translation> <translation>&lt;p&gt; %1() ?&lt;/p&gt;&lt;p&gt; .&lt;/p&gt;</translation>
</message> </message>
<message> <message>
<source>%1 Read-only settings</source> <source>%1 Read-only settings</source>
@ -735,10 +666,6 @@ Valid names:
<source>&lt;p&gt;Settings are read-only because you only have read access to the file:&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</source> <source>&lt;p&gt;Settings are read-only because you only have read access to the file:&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</source>
<translation>&lt;p&gt; :&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</translation> <translation>&lt;p&gt; :&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</translation>
</message> </message>
<message>
<source>&lt;p&gt;Sorry, while this version of %1 does support Wayland, this build was not linked with one or more of the required libraries.&lt;/p&gt;&lt;p&gt;Please either switch to X from your login screen or use a build that uses the correct libraries.&lt;/p&gt;&lt;p&gt;If you think this is incorrect, please &lt;a href=&quot;%2&quot;&gt;report a bug&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Please check the logs for more information.&lt;/p&gt;</source>
<translation>&lt;p&gt;. %1() Wayland를 , .&lt;/p&gt;&lt;p&gt; X .&lt;/p&gt;&lt;p&gt; &lt;a href=&quot;%2&quot;&gt; &lt;/a&gt; .&lt;/p&gt;&lt;p&gt; .&lt;/p&gt;</translation>
</message>
<message> <message>
<source>No thanks</source> <source>No thanks</source>
<translation></translation> <translation></translation>
@ -765,7 +692,7 @@ Valid names:
</message> </message>
<message> <message>
<source>&lt;p&gt;There was a problem finding the %1 background service (daemon).&lt;/p&gt;&lt;p&gt;The background service makes %1 work with UAC prompts and the login screen.&lt;/p&gt;&lt;p&gt;If don&apos;t want to use the background service and intentionally stopped it, you can prevent it&apos;s use by disabling this feature.&lt;/p&gt;&lt;p&gt;If you did not stop the background service intentionally, there may be a problem with it. Please retry or try restarting the %1 service from the Windows services program.&lt;/p&gt;</source> <source>&lt;p&gt;There was a problem finding the %1 background service (daemon).&lt;/p&gt;&lt;p&gt;The background service makes %1 work with UAC prompts and the login screen.&lt;/p&gt;&lt;p&gt;If don&apos;t want to use the background service and intentionally stopped it, you can prevent it&apos;s use by disabling this feature.&lt;/p&gt;&lt;p&gt;If you did not stop the background service intentionally, there may be a problem with it. Please retry or try restarting the %1 service from the Windows services program.&lt;/p&gt;</source>
<translation>&lt;p&gt;%1 () .&lt;/p&gt;&lt;p&gt; %1() UAC .&lt;/p&gt;&lt;p&gt; , .&lt;/p&gt;&lt;p&gt; . Windows %1 .&lt;/p&gt;</translation> <translation>&lt;p&gt;%1 (daemon) .&lt;/p&gt;&lt;p&gt; %1() UAC .&lt;/p&gt;&lt;p&gt; , .&lt;/p&gt;&lt;p&gt; . Windows %1 .&lt;/p&gt;</translation>
</message> </message>
<message> <message>
<source>failed to read key from certificate file: %1</source> <source>failed to read key from certificate file: %1</source>
@ -855,7 +782,7 @@ Valid names:
</message> </message>
<message> <message>
<source>Dead Corners</source> <source>Dead Corners</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
<source>Top Left</source> <source>Top Left</source>
@ -964,7 +891,7 @@ Valid names:
</message> </message>
<message> <message>
<source>Drag a computer from the grid to the trashcan to remove it.</source> <source>Drag a computer from the grid to the trashcan to remove it.</source>
<translation> .</translation> <translation> .</translation>
</message> </message>
<message> <message>
<source>Configure the layout of your computer displays by dragging to where you want.</source> <source>Configure the layout of your computer displays by dragging to where you want.</source>
@ -1016,7 +943,7 @@ Valid names:
</message> </message>
<message> <message>
<source>&amp;Dead corners (for this computer)</source> <source>&amp;Dead corners (for this computer)</source>
<translation> (&amp;D) ( )</translation> <translation> (&amp;D) ( )</translation>
</message> </message>
<message> <message>
<source>&amp;Bottom-left</source> <source>&amp;Bottom-left</source>
@ -1118,7 +1045,7 @@ Valid names:
<source>Use a server config file to create complex computer layouts that are not possible with the simple grid-based computer layout editor. <source>Use a server config file to create complex computer layouts that are not possible with the simple grid-based computer layout editor.
Enabling this setting will disable the server config GUI.</source> Enabling this setting will disable the server config GUI.</source>
<translation> . <translation> .
GUI가 .</translation> GUI가 .</translation>
</message> </message>
@ -1150,30 +1077,14 @@ Enabling this setting will disable the server config GUI.</source>
<source>Preferences</source> <source>Preferences</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>&amp;Regular</source>
<translation>(&amp;R)</translation>
</message>
<message>
<source>App</source>
<translation></translation>
</message>
<message> <message>
<source>Check for updates on startup</source> <source>Check for updates on startup</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message>
<source>Hide the window when the app starts</source>
<translation> </translation>
</message>
<message> <message>
<source>Prevent this computer from going to sleep</source> <source>Prevent this computer from going to sleep</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message>
<source>Leave app running in notification area when the window is closed</source>
<translation> </translation>
</message>
<message> <message>
<source>Tray icon style</source> <source>Tray icon style</source>
<translation> </translation> <translation> </translation>
@ -1210,10 +1121,6 @@ Enabling this setting will disable the server config GUI.</source>
<source>&amp;Advanced</source> <source>&amp;Advanced</source>
<translation>(&amp;A)</translation> <translation>(&amp;A)</translation>
</message> </message>
<message>
<source>Networking</source>
<translation></translation>
</message>
<message> <message>
<source>Port</source> <source>Port</source>
<translation></translation> <translation></translation>
@ -1222,10 +1129,6 @@ Enabling this setting will disable the server config GUI.</source>
<source>Network IP</source> <source>Network IP</source>
<translation> IP</translation> <translation> IP</translation>
</message> </message>
<message>
<source>Logs</source>
<translation></translation>
</message>
<message> <message>
<source>Level</source> <source>Level</source>
<translation></translation> <translation></translation>
@ -1266,10 +1169,6 @@ Enabling this setting will disable the server config GUI.</source>
<source>Log path</source> <source>Log path</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message>
<source>Log to file</source>
<translation> </translation>
</message>
<message> <message>
<source>Using a Debug log level may affect performance. Only use a Debug level if you are attempting to debug an issue or are gathering logs to submit with a bug report.</source> <source>Using a Debug log level may affect performance. Only use a Debug level if you are attempting to debug an issue or are gathering logs to submit with a bug report.</source>
<translation> . .</translation> <translation> . .</translation>
@ -1280,16 +1179,12 @@ Enabling this setting will disable the server config GUI.</source>
</message> </message>
<message> <message>
<source>Use background service (daemon)</source> <source>Use background service (daemon)</source>
<translation> () </translation> <translation> (daemon) </translation>
</message> </message>
<message> <message>
<source>Always run as system (work at login screen and UAC)</source> <source>Always run as system (work at login screen and UAC)</source>
<translation> ( UAC에서 )</translation> <translation> ( UAC에서 )</translation>
</message> </message>
<message>
<source>Force a language to be used for the GUI.</source>
<translation>GUI에서 .</translation>
</message>
<message> <message>
<source>Language</source> <source>Language</source>
<translation></translation> <translation></translation>
@ -1342,13 +1237,29 @@ Enabling this setting will disable the server config GUI.</source>
<source>Verbose debug output</source> <source>Verbose debug output</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message>
<source>Close and save changes</source>
<translation> </translation>
</message>
<message>
<source>Close and forget changes</source>
<translation> </translation>
</message>
<message>
<source>Reset to stored values</source>
<translation type="unfinished"> </translation>
</message>
<message>
<source>Reset to default values</source>
<translation type="unfinished"> </translation>
</message>
<message> <message>
<source>Enable wl-clipboard support</source> <source>Enable wl-clipboard support</source>
<translation>wl-clipboard </translation> <translation>wl-clipboard </translation>
</message> </message>
<message> <message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Requires the wl-clipboard package&lt;/p&gt;&lt;p&gt;When using wl-clipboard v2.2.1, there is a focus stealing bug that may make Deskflow harder to use. This has been fixed when using the wl-clipboard master branch, unless your Compositor lacks wlroots-data-control protocol support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source> <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Requires the wl-clipboard package&lt;/p&gt;&lt;p&gt;When using wl-clipboard v2.2.1, there is a focus stealing bug that may make Deskflow harder to use. This has been fixed when using the wl-clipboard master branch, unless your Compositor lacks wlroots-data-control protocol support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;wl-clipboard .&lt;/p&gt;&lt;p&gt;wl-clipboard v2.2.1 Deskflow . wl-clipboard master , wlroots-data-control .&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation> <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;wl-clipboard .&lt;/p&gt;&lt;p&gt;wl-clipboard v2.2.1 Deskflow . wl-clipboard master , wlroots-data-control .&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message> </message>
<message> <message>
<source>Automatic</source> <source>Automatic</source>
@ -1358,6 +1269,122 @@ Enabling this setting will disable the server config GUI.</source>
<source>Include version in the window title</source> <source>Include version in the window title</source>
<translation type="unfinished"> </translation> <translation type="unfinished"> </translation>
</message> </message>
<message>
<source>Log to file</source>
<translation> </translation>
</message>
<message>
<source>&amp;Logs</source>
<translation> (&amp;L)</translation>
</message>
<message>
<source>&amp;General</source>
<translation>(&amp;G)</translation>
</message>
<message>
<source>&amp;Network</source>
<translation>(&amp;N)</translation>
</message>
<message>
<source>&amp;Window</source>
<translation type="unfinished">(&amp;W)</translation>
</message>
<message>
<source>When the main window is closed</source>
<translation type="unfinished"> </translation>
</message>
<message>
<source>Exit</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Send to background</source>
<translation type="unfinished"> </translation>
</message>
<message>
<source>When the application starts</source>
<translation type="unfinished"> </translation>
</message>
<message>
<source>Show the main window</source>
<translation type="unfinished"> </translation>
</message>
<message>
<source>Run command on enter</source>
<translation type="unfinished">Enter </translation>
</message>
<message>
<source>Run command on exit</source>
<translation type="unfinished"> </translation>
</message>
</context>
<context>
<name>StatusBar</name>
<message>
<source>%1 is not running</source>
<translation>%1 </translation>
</message>
<message>
<source>%1 is starting...</source>
<translation>%1 ...</translation>
</message>
<message>
<source>%1 will retry in a moment...</source>
<translation>%1 ...</translation>
</message>
<message>
<source>%1 is stopping...</source>
<translation>%1 ...</translation>
</message>
<message>
<source>%1 is waiting for clients</source>
<translation>%1 </translation>
</message>
<message>
<source>%1 is connecting...</source>
<translation>%1 ...</translation>
</message>
<message>
<source>%1 is connected as client of %2</source>
<translation>%1() %2 </translation>
</message>
<message>
<source>%1 is disconnected</source>
<translation>%1 </translation>
</message>
<message numerus="yes">
<source>%1 is connected, with %n client(s): %2</source>
<extracomment>Shown when in server mode and at least 1 client is connected %1 is replaced by the app name %2 will be a list of at least one client %n will be replaced by the number of clients (n is &gt;=1), it is not requried to be in the translation</extracomment>
<translation>
<numerusform>%1() %n대의 : %2</numerusform>
</translation>
</message>
<message>
<source>Clients:
%1</source>
<translation>:
%1</translation>
</message>
<message>
<source>A new version v%1 is available</source>
<translation> (v%1) </translation>
</message>
<message>
<source>View local fingerprint</source>
<translation> </translation>
</message>
<message>
<source>Update available</source>
<translation> </translation>
</message>
<message>
<source>%1 Encryption Enabled</source>
<translation>%1 </translation>
</message>
<message>
<source>Encryption Disabled</source>
<translation> </translation>
</message>
</context> </context>
<context> <context>
<name>i18n</name> <name>i18n</name>
@ -1398,7 +1425,7 @@ Enabling this setting will disable the server config GUI.</source>
</message> </message>
<message> <message>
<source>A computer with this name already exists</source> <source>A computer with this name already exists</source>
<translation> </translation> <translation> </translation>
</message> </message>
</context> </context>
</TS> </TS>

View File

@ -350,10 +350,6 @@ Do you want to connect to the server?
<source>invalid certificate, generating a new one</source> <source>invalid certificate, generating a new one</source>
<translation>недействительный сертификат, создание нового</translation> <translation>недействительный сертификат, создание нового</translation>
</message> </message>
<message>
<source>A new version v%1 is available</source>
<translation>Доступна новая версия v%1</translation>
</message>
<message> <message>
<source>Address missing</source> <source>Address missing</source>
<translation>Адрес не указан</translation> <translation>Адрес не указан</translation>
@ -362,10 +358,6 @@ Do you want to connect to the server?
<source>Please enter the hostname or IP address of the other computer.</source> <source>Please enter the hostname or IP address of the other computer.</source>
<translation>Пожалуйста, введите имя хоста или IP-адрес другого компьютера.</translation> <translation>Пожалуйста, введите имя хоста или IP-адрес другого компьютера.</translation>
</message> </message>
<message>
<source>Core cannot be started</source>
<translation>Не удалось запустить ядро</translation>
</message>
<message> <message>
<source>Save server configuration as...</source> <source>Save server configuration as...</source>
<translation>Сохранить конфигурацию сервера как...</translation> <translation>Сохранить конфигурацию сервера как...</translation>
@ -395,12 +387,8 @@ Do you want to connect to the server?
<translation>Отключиться</translation> <translation>Отключиться</translation>
</message> </message>
<message> <message>
<source>%1 Encryption Enabled</source> <source>&lt;p&gt;Failed to connect to the server &apos;%1&apos;.&lt;/p&gt;&lt;p&gt;A Client with your name is already connected to the server.&lt;/p&gt;Please ensure that you&apos;re using a unique name and that only a single instance of the client process is running.&lt;/p&gt;</source>
<translation>Шифрование %1 включено</translation> <translation>&lt;p&gt;Не удалось подключиться к серверу &apos;%1&apos;.&lt;/p&gt;&lt;p&gt;Клиент с таким именем уже подключен к серверу.&lt;/p&gt;Убедитесь, что вы используете уникальное имя и запущен только один процесс клиента.&lt;/p&gt;</translation>
</message>
<message>
<source>Encryption Disabled</source>
<translation>Шифрование отключено</translation>
</message> </message>
<message> <message>
<source>No IP Detected</source> <source>No IP Detected</source>
@ -432,38 +420,6 @@ A bound IP is now invalid, you may need to restart the server.</source>
<translation> <translation>
Привязанный IP-адрес стал недействительным. Возможно требуется перезапуск сервера.</translation> Привязанный IP-адрес стал недействительным. Возможно требуется перезапуск сервера.</translation>
</message> </message>
<message>
<source>%1 is starting...</source>
<translation>%1 запускается...</translation>
</message>
<message>
<source>%1 will retry in a moment...</source>
<translation>%1 скоро повторит попытку...</translation>
</message>
<message>
<source>%1 is stopping...</source>
<translation>%1 останавливается...</translation>
</message>
<message>
<source>%1 is not running</source>
<translation>%1 не запущен</translation>
</message>
<message>
<source>%1 is waiting for clients</source>
<translation>%1 ожидает подключения клиентов</translation>
</message>
<message>
<source>%1 is connecting...</source>
<translation>%1 подключается...</translation>
</message>
<message>
<source>%1 is connected as client of %2</source>
<translation>%1 подключен как клиент к %2</translation>
</message>
<message>
<source>%1 is disconnected</source>
<translation>%1 отключен</translation>
</message>
<message> <message>
<source>&amp;File</source> <source>&amp;File</source>
<translation>&amp;Файл</translation> <translation>&amp;Файл</translation>
@ -533,14 +489,6 @@ A bound IP is now invalid, you may need to restart the server.</source>
<extracomment>Quit shortcut</extracomment> <extracomment>Quit shortcut</extracomment>
<translation>Ctrl+Q</translation> <translation>Ctrl+Q</translation>
</message> </message>
<message>
<source>View local fingerprint</source>
<translation>Показать локальный отпечаток</translation>
</message>
<message>
<source>Update available</source>
<translation>Доступно обновление</translation>
</message>
<message> <message>
<source>Invalid Screen Name</source> <source>Invalid Screen Name</source>
<translation>Недопустимое имя экрана</translation> <translation>Недопустимое имя экрана</translation>
@ -563,29 +511,14 @@ Valid names:
Можно использовать _ или - Можно использовать _ или -
Длина от 1 до 255 символов</translation> Длина от 1 до 255 символов</translation>
</message> </message>
<message numerus="yes">
<source>%1 is connected, with %n client(s): %2</source>
<extracomment>Shown when in server mode and at least 1 client is connected %1 is replaced by the app name %2 will be a list of at least one client %n will be replaced by the number of clients (n is &gt;=1), it is not requried to be in the translation</extracomment>
<translation>
<numerusform>%1 подключен к %n клиенту: %2</numerusform>
<numerusform>%1 подключен к %n клиентам: %2</numerusform>
<numerusform>%1 подключен к %n клиентам: %2</numerusform>
</translation>
</message>
<message> <message>
<source>Clients: <source>%1 Connection Error</source>
%1</source> <translation>Ошибка соединения %1</translation>
<translation>Клиенты:
%1</translation>
</message> </message>
<message> <message>
<source>Suggested IP: </source> <source>Suggested IP: </source>
<translation>Рекомендуемый IP-адрес: </translation> <translation>Рекомендуемый IP-адрес: </translation>
</message> </message>
<message>
<source>The Core executable could not be successfully started, although it does exist. Please check if you have sufficient permissions to run this program.</source>
<translation>Не удалось запустить исполняемый файл ядра, хотя он существует. Проверьте наличие прав на запуск программы.</translation>
</message>
<message> <message>
<source>&amp;Configure Client</source> <source>&amp;Configure Client</source>
<translation type="unfinished">&amp;Настройка клиента</translation> <translation type="unfinished">&amp;Настройка клиента</translation>
@ -598,6 +531,26 @@ Valid names:
<source>&lt;html&gt;Hostname or IP address of the server computer.&lt;br/&gt;May contain a comma seperated list.&lt;/html&gt;</source> <source>&lt;html&gt;Hostname or IP address of the server computer.&lt;br/&gt;May contain a comma seperated list.&lt;/html&gt;</source>
<translation type="unfinished">&lt;html&gt;Имя хоста или IP-адрес серверного компьютера.&lt;br/&gt;Может содержать список, разделенный запятыми.&lt;/html&gt;</translation> <translation type="unfinished">&lt;html&gt;Имя хоста или IP-адрес серверного компьютера.&lt;br/&gt;Может содержать список, разделенный запятыми.&lt;/html&gt;</translation>
</message> </message>
<message>
<source>read</source>
<translation type="unfinished">прочитать</translation>
</message>
<message>
<source>read and write</source>
<translation type="unfinished">читать и записывать</translation>
</message>
<message>
<source>The Core executable could not be started.
Please check if you have sufficient permissions to run %1.</source>
<translation type="unfinished">Не удалось запустить исполняемый файл Core.
Проверьте, достаточно ли у вас прав для запуска %1.</translation>
</message>
<message>
<source>
Additionally, check you are able to %1 the server config file: %2</source>
<translation type="unfinished">
Также убедитесь, что вы можете %1 файл конфигурации сервера: %2</translation>
</message>
</context> </context>
<context> <context>
<name>NewScreenWidget</name> <name>NewScreenWidget</name>
@ -677,30 +630,6 @@ Valid names:
<source>%1 Connected</source> <source>%1 Connected</source>
<translation>%1 подключено</translation> <translation>%1 подключено</translation>
</message> </message>
<message>
<source>&lt;p&gt;Failed to connect to the server &apos;%1&apos;.&lt;/p&gt;</source>
<translation>&lt;p&gt;Не удалось подключиться к серверу &apos;%1&apos;.&lt;/p&gt;</translation>
</message>
<message>
<source>&lt;p&gt;A Client with your name is already connected to the server.&lt;/p&gt;Please ensure that you&apos;re using a unique name and that only a single instance of the client process is running.&lt;/p&gt;</source>
<translation>&lt;p&gt;Клиент с таким именем уже подключен к серверу.&lt;/p&gt;Убедитесь, что вы используете уникальное имя и запущен только один процесс клиента.&lt;/p&gt;</translation>
</message>
<message>
<source>&lt;p&gt;Please try to connect to the server using the server IP address instead of the hostname. &lt;/p&gt;&lt;p&gt;If that doesn&apos;t work, please check your TLS and firewall settings.&lt;/p&gt;</source>
<translation>&lt;p&gt;Попробуйте подключиться к серверу по IP-адресу вместо имени хоста. &lt;/p&gt;&lt;p&gt;Если это не поможет, проверьте настройки TLS и брандмауэра.&lt;/p&gt;</translation>
</message>
<message>
<source>&lt;p&gt;Please check your TLS and firewall settings.&lt;/p&gt;</source>
<translation>&lt;p&gt;Пожалуйста, проверьте настройки TLS и брандмауэра.&lt;/p&gt;</translation>
</message>
<message>
<source>%1 Connection Error</source>
<translation>Ошибка соединения %1</translation>
</message>
<message>
<source>Do not show this message again</source>
<translation>Больше не показывать это сообщение</translation>
</message>
<message> <message>
<source>%1 - New Client</source> <source>%1 - New Client</source>
<translation>%1 - Новый клиент</translation> <translation>%1 - Новый клиент</translation>
@ -737,10 +666,6 @@ Valid names:
<source>&lt;p&gt;Settings are read-only because you only have read access to the file:&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</source> <source>&lt;p&gt;Settings are read-only because you only have read access to the file:&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</source>
<translation>&lt;p&gt;Настройки доступны только для чтения, так как у вас есть доступ только на чтение к файлу:&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</translation> <translation>&lt;p&gt;Настройки доступны только для чтения, так как у вас есть доступ только на чтение к файлу:&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</translation>
</message> </message>
<message>
<source>&lt;p&gt;Sorry, while this version of %1 does support Wayland, this build was not linked with one or more of the required libraries.&lt;/p&gt;&lt;p&gt;Please either switch to X from your login screen or use a build that uses the correct libraries.&lt;/p&gt;&lt;p&gt;If you think this is incorrect, please &lt;a href=&quot;%2&quot;&gt;report a bug&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Please check the logs for more information.&lt;/p&gt;</source>
<translation>&lt;p&gt;Хотя эта версия %1 поддерживает Wayland, данная сборка не была слинкована с необходимыми библиотеками.&lt;/p&gt;&lt;p&gt;Пожалуйста, переключитесь на сессию X11 или используйте сборку с поддержкой нужных библиотек.&lt;/p&gt;&lt;p&gt;Если вы считаете это ошибкой, пожалуйста, &lt;a href=&quot;%2&quot;&gt;сообщите о ней&lt;/a&gt;.&lt;/p&gt;</translation>
</message>
<message> <message>
<source>No thanks</source> <source>No thanks</source>
<translation>Нет, спасибо</translation> <translation>Нет, спасибо</translation>
@ -1150,30 +1075,14 @@ Enabling this setting will disable the server config GUI.</source>
<source>Preferences</source> <source>Preferences</source>
<translation>Настройки</translation> <translation>Настройки</translation>
</message> </message>
<message>
<source>&amp;Regular</source>
<translation>&amp;Основные</translation>
</message>
<message>
<source>App</source>
<translation>Приложение</translation>
</message>
<message> <message>
<source>Check for updates on startup</source> <source>Check for updates on startup</source>
<translation>Проверять обновления при запуске</translation> <translation>Проверять обновления при запуске</translation>
</message> </message>
<message>
<source>Hide the window when the app starts</source>
<translation>Скрывать окно при запуске</translation>
</message>
<message> <message>
<source>Prevent this computer from going to sleep</source> <source>Prevent this computer from going to sleep</source>
<translation>Запретить компьютеру переходить в спящий режим</translation> <translation>Запретить компьютеру переходить в спящий режим</translation>
</message> </message>
<message>
<source>Leave app running in notification area when the window is closed</source>
<translation>Оставлять приложение в области уведомлений при закрытии окна</translation>
</message>
<message> <message>
<source>Tray icon style</source> <source>Tray icon style</source>
<translation>Стиль иконки трея</translation> <translation>Стиль иконки трея</translation>
@ -1210,10 +1119,6 @@ Enabling this setting will disable the server config GUI.</source>
<source>&amp;Advanced</source> <source>&amp;Advanced</source>
<translation>&amp;Расширенные</translation> <translation>&amp;Расширенные</translation>
</message> </message>
<message>
<source>Networking</source>
<translation>Сеть</translation>
</message>
<message> <message>
<source>Port</source> <source>Port</source>
<translation>Порт</translation> <translation>Порт</translation>
@ -1222,10 +1127,6 @@ Enabling this setting will disable the server config GUI.</source>
<source>Network IP</source> <source>Network IP</source>
<translation>Сетевой IP-адрес</translation> <translation>Сетевой IP-адрес</translation>
</message> </message>
<message>
<source>Logs</source>
<translation>Журналы</translation>
</message>
<message> <message>
<source>Level</source> <source>Level</source>
<translation>Уровень</translation> <translation>Уровень</translation>
@ -1266,10 +1167,6 @@ Enabling this setting will disable the server config GUI.</source>
<source>Log path</source> <source>Log path</source>
<translation>Путь к журналам</translation> <translation>Путь к журналам</translation>
</message> </message>
<message>
<source>Log to file</source>
<translation>Записывать в файл</translation>
</message>
<message> <message>
<source>Using a Debug log level may affect performance. Only use a Debug level if you are attempting to debug an issue or are gathering logs to submit with a bug report.</source> <source>Using a Debug log level may affect performance. Only use a Debug level if you are attempting to debug an issue or are gathering logs to submit with a bug report.</source>
<translation>Уровень отладки может повлиять на производительность. Используйте его только для поиска неисправностей.</translation> <translation>Уровень отладки может повлиять на производительность. Используйте его только для поиска неисправностей.</translation>
@ -1286,10 +1183,6 @@ Enabling this setting will disable the server config GUI.</source>
<source>Always run as system (work at login screen and UAC)</source> <source>Always run as system (work at login screen and UAC)</source>
<translation>Всегда запускать от имени системы (работает на экране входа и UAC)</translation> <translation>Всегда запускать от имени системы (работает на экране входа и UAC)</translation>
</message> </message>
<message>
<source>Force a language to be used for the GUI.</source>
<translation>Принудительно использовать язык для интерфейса.</translation>
</message>
<message> <message>
<source>Language</source> <source>Language</source>
<translation>Язык</translation> <translation>Язык</translation>
@ -1346,6 +1239,22 @@ Enabling this setting will disable the server config GUI.</source>
<source>Verbose debug output</source> <source>Verbose debug output</source>
<translation>Подробный вывод отладки</translation> <translation>Подробный вывод отладки</translation>
</message> </message>
<message>
<source>Close and save changes</source>
<translation type="unfinished">Закрыть и сохранить изменения</translation>
</message>
<message>
<source>Close and forget changes</source>
<translation type="unfinished">Закройте изменения и забудьте о них</translation>
</message>
<message>
<source>Reset to stored values</source>
<translation type="unfinished">Сбросить до сохраненных значений</translation>
</message>
<message>
<source>Reset to default values</source>
<translation type="unfinished">Сбросить до значений по умолчанию</translation>
</message>
<message> <message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Requires the wl-clipboard package&lt;/p&gt;&lt;p&gt;When using wl-clipboard v2.2.1, there is a focus stealing bug that may make Deskflow harder to use. This has been fixed when using the wl-clipboard master branch, unless your Compositor lacks wlroots-data-control protocol support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source> <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Requires the wl-clipboard package&lt;/p&gt;&lt;p&gt;When using wl-clipboard v2.2.1, there is a focus stealing bug that may make Deskflow harder to use. This has been fixed when using the wl-clipboard master branch, unless your Compositor lacks wlroots-data-control protocol support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Требуется пакет wl-clipboard. В версии 2.2.1 есть ошибка перехвата фокуса.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation> <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Требуется пакет wl-clipboard. В версии 2.2.1 есть ошибка перехвата фокуса.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
@ -1358,6 +1267,124 @@ Enabling this setting will disable the server config GUI.</source>
<source>Include version in the window title</source> <source>Include version in the window title</source>
<translation type="unfinished">Включить номер версии в заголовок окна</translation> <translation type="unfinished">Включить номер версии в заголовок окна</translation>
</message> </message>
<message>
<source>Log to file</source>
<translation>Записывать в файл</translation>
</message>
<message>
<source>&amp;Logs</source>
<translation>&amp;Журнал</translation>
</message>
<message>
<source>&amp;General</source>
<translation type="unfinished">&amp;Общий</translation>
</message>
<message>
<source>&amp;Network</source>
<translation>&amp;Сеть</translation>
</message>
<message>
<source>&amp;Window</source>
<translation type="unfinished">&amp;Окно</translation>
</message>
<message>
<source>When the main window is closed</source>
<translation type="unfinished">Когда главное окно закрыто</translation>
</message>
<message>
<source>Exit</source>
<translation type="unfinished">Выход</translation>
</message>
<message>
<source>Send to background</source>
<translation type="unfinished">Отправить в фоновый режим</translation>
</message>
<message>
<source>When the application starts</source>
<translation type="unfinished">Когда приложение запускается</translation>
</message>
<message>
<source>Show the main window</source>
<translation type="unfinished">Показать главное окно</translation>
</message>
<message>
<source>Run command on enter</source>
<translation type="unfinished">Выполнять команду по нажатию Enter</translation>
</message>
<message>
<source>Run command on exit</source>
<translation type="unfinished">Выполнить команду при выходе</translation>
</message>
</context>
<context>
<name>StatusBar</name>
<message>
<source>%1 is not running</source>
<translation>%1 не запущен</translation>
</message>
<message>
<source>%1 is starting...</source>
<translation>%1 запускается...</translation>
</message>
<message>
<source>%1 will retry in a moment...</source>
<translation>%1 скоро повторит попытку...</translation>
</message>
<message>
<source>%1 is stopping...</source>
<translation>%1 останавливается...</translation>
</message>
<message>
<source>%1 is waiting for clients</source>
<translation>%1 ожидает подключения клиентов</translation>
</message>
<message>
<source>%1 is connecting...</source>
<translation>%1 подключается...</translation>
</message>
<message>
<source>%1 is connected as client of %2</source>
<translation>%1 подключен как клиент к %2</translation>
</message>
<message>
<source>%1 is disconnected</source>
<translation>%1 отключен</translation>
</message>
<message numerus="yes">
<source>%1 is connected, with %n client(s): %2</source>
<extracomment>Shown when in server mode and at least 1 client is connected %1 is replaced by the app name %2 will be a list of at least one client %n will be replaced by the number of clients (n is &gt;=1), it is not requried to be in the translation</extracomment>
<translation>
<numerusform>%1 подключен к %n клиенту: %2</numerusform>
<numerusform>%1 подключен к %n клиентам: %2</numerusform>
<numerusform>%1 подключен к %n клиентам: %2</numerusform>
</translation>
</message>
<message>
<source>Clients:
%1</source>
<translation>Клиенты:
%1</translation>
</message>
<message>
<source>A new version v%1 is available</source>
<translation>Доступна новая версия v%1</translation>
</message>
<message>
<source>View local fingerprint</source>
<translation>Показать локальный отпечаток</translation>
</message>
<message>
<source>Update available</source>
<translation>Доступно обновление</translation>
</message>
<message>
<source>%1 Encryption Enabled</source>
<translation>Шифрование %1 включено</translation>
</message>
<message>
<source>Encryption Disabled</source>
<translation>Шифрование отключено</translation>
</message>
</context> </context>
<context> <context>
<name>i18n</name> <name>i18n</name>

View File

@ -175,7 +175,7 @@ p, li { white-space: pre-wrap; }
</message> </message>
<message> <message>
<source>Close and forget changes</source> <source>Close and forget changes</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Reset to stored values</source> <source>Reset to stored values</source>
@ -350,10 +350,6 @@ Do you want to connect to the server?
<source>invalid certificate, generating a new one</source> <source>invalid certificate, generating a new one</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>A new version v%1 is available</source>
<translation> v%1 </translation>
</message>
<message> <message>
<source>Address missing</source> <source>Address missing</source>
<translation></translation> <translation></translation>
@ -362,10 +358,6 @@ Do you want to connect to the server?
<source>Please enter the hostname or IP address of the other computer.</source> <source>Please enter the hostname or IP address of the other computer.</source>
<translation> IP </translation> <translation> IP </translation>
</message> </message>
<message>
<source>Core cannot be started</source>
<translation></translation>
</message>
<message> <message>
<source>Save server configuration as...</source> <source>Save server configuration as...</source>
<translation>...</translation> <translation>...</translation>
@ -395,12 +387,8 @@ Do you want to connect to the server?
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>%1 Encryption Enabled</source> <source>&lt;p&gt;Failed to connect to the server &apos;%1&apos;.&lt;/p&gt;&lt;p&gt;A Client with your name is already connected to the server.&lt;/p&gt;Please ensure that you&apos;re using a unique name and that only a single instance of the client process is running.&lt;/p&gt;</source>
<translation>%1 </translation> <translation>&lt;p&gt;%1&lt;/p&gt;&lt;p&gt;&lt;/p&gt;使&lt;/p&gt;</translation>
</message>
<message>
<source>Encryption Disabled</source>
<translation></translation>
</message> </message>
<message> <message>
<source>No IP Detected</source> <source>No IP Detected</source>
@ -432,38 +420,6 @@ A bound IP is now invalid, you may need to restart the server.</source>
<translation type="unfinished"> <translation type="unfinished">
IP地址现在无效</translation> IP地址现在无效</translation>
</message> </message>
<message>
<source>%1 is starting...</source>
<translation>%1 ...</translation>
</message>
<message>
<source>%1 will retry in a moment...</source>
<translation>%1 ...</translation>
</message>
<message>
<source>%1 is stopping...</source>
<translation>%1 ...</translation>
</message>
<message>
<source>%1 is not running</source>
<translation>%1 </translation>
</message>
<message>
<source>%1 is waiting for clients</source>
<translation>%1 </translation>
</message>
<message>
<source>%1 is connecting...</source>
<translation>%1 ...</translation>
</message>
<message>
<source>%1 is connected as client of %2</source>
<translation>%1 %2 </translation>
</message>
<message>
<source>%1 is disconnected</source>
<translation>%1 </translation>
</message>
<message> <message>
<source>&amp;File</source> <source>&amp;File</source>
<translation>(&amp;F)</translation> <translation>(&amp;F)</translation>
@ -533,14 +489,6 @@ A bound IP is now invalid, you may need to restart the server.</source>
<extracomment>Quit shortcut</extracomment> <extracomment>Quit shortcut</extracomment>
<translation>Ctrl+Q</translation> <translation>Ctrl+Q</translation>
</message> </message>
<message>
<source>View local fingerprint</source>
<translation></translation>
</message>
<message>
<source>Update available</source>
<translation></translation>
</message>
<message> <message>
<source>Invalid Screen Name</source> <source>Invalid Screen Name</source>
<translation></translation> <translation></translation>
@ -563,27 +511,14 @@ Valid names:
使 _ - 使 _ -
1 255 </translation> 1 255 </translation>
</message> </message>
<message numerus="yes">
<source>%1 is connected, with %n client(s): %2</source>
<extracomment>Shown when in server mode and at least 1 client is connected %1 is replaced by the app name %2 will be a list of at least one client %n will be replaced by the number of clients (n is &gt;=1), it is not requried to be in the translation</extracomment>
<translation>
<numerusform>%1 %n %2</numerusform>
</translation>
</message>
<message> <message>
<source>Clients: <source>%1 Connection Error</source>
%1</source> <translation>%1 </translation>
<translation>
%1</translation>
</message> </message>
<message> <message>
<source>Suggested IP: </source> <source>Suggested IP: </source>
<translation type="unfinished"> IP </translation> <translation type="unfinished"> IP </translation>
</message> </message>
<message>
<source>The Core executable could not be successfully started, although it does exist. Please check if you have sufficient permissions to run this program.</source>
<translation type="unfinished">Core </translation>
</message>
<message> <message>
<source>&amp;Configure Client</source> <source>&amp;Configure Client</source>
<translation type="unfinished">(&amp;C)</translation> <translation type="unfinished">(&amp;C)</translation>
@ -596,6 +531,26 @@ Valid names:
<source>&lt;html&gt;Hostname or IP address of the server computer.&lt;br/&gt;May contain a comma seperated list.&lt;/html&gt;</source> <source>&lt;html&gt;Hostname or IP address of the server computer.&lt;br/&gt;May contain a comma seperated list.&lt;/html&gt;</source>
<translation type="unfinished">&lt;html&gt; IP &lt;br/&gt;&lt;/html&gt;</translation> <translation type="unfinished">&lt;html&gt; IP &lt;br/&gt;&lt;/html&gt;</translation>
</message> </message>
<message>
<source>read</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>read and write</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>The Core executable could not be started.
Please check if you have sufficient permissions to run %1.</source>
<translation type="unfinished">Core可执行文件
%1</translation>
</message>
<message>
<source>
Additionally, check you are able to %1 the server config file: %2</source>
<translation type="unfinished">
%1%2</translation>
</message>
</context> </context>
<context> <context>
<name>NewScreenWidget</name> <name>NewScreenWidget</name>
@ -677,30 +632,6 @@ Valid names:
<source>%1 Connected</source> <source>%1 Connected</source>
<translation>%1 </translation> <translation>%1 </translation>
</message> </message>
<message>
<source>&lt;p&gt;Failed to connect to the server &apos;%1&apos;.&lt;/p&gt;</source>
<translation>&lt;p&gt;%1&lt;/p&gt;</translation>
</message>
<message>
<source>&lt;p&gt;A Client with your name is already connected to the server.&lt;/p&gt;Please ensure that you&apos;re using a unique name and that only a single instance of the client process is running.&lt;/p&gt;</source>
<translation type="unfinished">&lt;p&gt;&lt;/p&gt;使&lt;/p&gt;</translation>
</message>
<message>
<source>&lt;p&gt;Please try to connect to the server using the server IP address instead of the hostname. &lt;/p&gt;&lt;p&gt;If that doesn&apos;t work, please check your TLS and firewall settings.&lt;/p&gt;</source>
<translation>&lt;p&gt;使 IP &lt;/p&gt;&lt;p&gt; TLS &lt;/p&gt;</translation>
</message>
<message>
<source>&lt;p&gt;Please check your TLS and firewall settings.&lt;/p&gt;</source>
<translation>&lt;p&gt; TLS &lt;/p&gt;</translation>
</message>
<message>
<source>%1 Connection Error</source>
<translation>%1 </translation>
</message>
<message>
<source>Do not show this message again</source>
<translation></translation>
</message>
<message> <message>
<source>%1 - New Client</source> <source>%1 - New Client</source>
<translation>%1 - </translation> <translation>%1 - </translation>
@ -737,10 +668,6 @@ Valid names:
<source>&lt;p&gt;Settings are read-only because you only have read access to the file:&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</source> <source>&lt;p&gt;Settings are read-only because you only have read access to the file:&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</source>
<translation>&lt;p&gt;&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</translation> <translation>&lt;p&gt;&lt;/p&gt;&lt;p&gt;%1&lt;/p&gt;</translation>
</message> </message>
<message>
<source>&lt;p&gt;Sorry, while this version of %1 does support Wayland, this build was not linked with one or more of the required libraries.&lt;/p&gt;&lt;p&gt;Please either switch to X from your login screen or use a build that uses the correct libraries.&lt;/p&gt;&lt;p&gt;If you think this is incorrect, please &lt;a href=&quot;%2&quot;&gt;report a bug&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Please check the logs for more information.&lt;/p&gt;</source>
<translation>&lt;p&gt; %1 Wayland&lt;/p&gt;&lt;p&gt; X使&lt;/p&gt;&lt;p&gt; &lt;a href=&quot;%2&quot;&gt; Bug&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;</translation>
</message>
<message> <message>
<source>No thanks</source> <source>No thanks</source>
<translation></translation> <translation></translation>
@ -1152,30 +1079,14 @@ Enabling this setting will disable the server config GUI.</source>
<source>Preferences</source> <source>Preferences</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>&amp;Regular</source>
<translation>(&amp;R)</translation>
</message>
<message>
<source>App</source>
<translation></translation>
</message>
<message> <message>
<source>Check for updates on startup</source> <source>Check for updates on startup</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Hide the window when the app starts</source>
<translation></translation>
</message>
<message> <message>
<source>Prevent this computer from going to sleep</source> <source>Prevent this computer from going to sleep</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Leave app running in notification area when the window is closed</source>
<translation></translation>
</message>
<message> <message>
<source>Tray icon style</source> <source>Tray icon style</source>
<translation></translation> <translation></translation>
@ -1212,10 +1123,6 @@ Enabling this setting will disable the server config GUI.</source>
<source>&amp;Advanced</source> <source>&amp;Advanced</source>
<translation>(&amp;A)</translation> <translation>(&amp;A)</translation>
</message> </message>
<message>
<source>Networking</source>
<translation></translation>
</message>
<message> <message>
<source>Port</source> <source>Port</source>
<translation></translation> <translation></translation>
@ -1224,10 +1131,6 @@ Enabling this setting will disable the server config GUI.</source>
<source>Network IP</source> <source>Network IP</source>
<translation> IP</translation> <translation> IP</translation>
</message> </message>
<message>
<source>Logs</source>
<translation></translation>
</message>
<message> <message>
<source>Level</source> <source>Level</source>
<translation></translation> <translation></translation>
@ -1268,10 +1171,6 @@ Enabling this setting will disable the server config GUI.</source>
<source>Log path</source> <source>Log path</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Log to file</source>
<translation></translation>
</message>
<message> <message>
<source>Using a Debug log level may affect performance. Only use a Debug level if you are attempting to debug an issue or are gathering logs to submit with a bug report.</source> <source>Using a Debug log level may affect performance. Only use a Debug level if you are attempting to debug an issue or are gathering logs to submit with a bug report.</source>
<translation>使 Bug 使</translation> <translation>使 Bug 使</translation>
@ -1288,10 +1187,6 @@ Enabling this setting will disable the server config GUI.</source>
<source>Always run as system (work at login screen and UAC)</source> <source>Always run as system (work at login screen and UAC)</source>
<translation type="unfinished"> ( UAC )</translation> <translation type="unfinished"> ( UAC )</translation>
</message> </message>
<message>
<source>Force a language to be used for the GUI.</source>
<translation> GUI 使</translation>
</message>
<message> <message>
<source>Language</source> <source>Language</source>
<translation></translation> <translation></translation>
@ -1344,6 +1239,22 @@ Enabling this setting will disable the server config GUI.</source>
<source>Verbose debug output</source> <source>Verbose debug output</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Close and save changes</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Close and forget changes</source>
<translation></translation>
</message>
<message>
<source>Reset to stored values</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Reset to default values</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>Enable wl-clipboard support</source> <source>Enable wl-clipboard support</source>
<translation> wl-clipboard </translation> <translation> wl-clipboard </translation>
@ -1360,6 +1271,122 @@ Enabling this setting will disable the server config GUI.</source>
<source>Include version in the window title</source> <source>Include version in the window title</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Log to file</source>
<translation></translation>
</message>
<message>
<source>&amp;Logs</source>
<translation type="unfinished">(&amp;L)</translation>
</message>
<message>
<source>&amp;General</source>
<translation>(&amp;G)</translation>
</message>
<message>
<source>&amp;Network</source>
<translation>(&amp;N)</translation>
</message>
<message>
<source>&amp;Window</source>
<translation type="unfinished">(&amp;W)</translation>
</message>
<message>
<source>When the main window is closed</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Exit</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Send to background</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>When the application starts</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Show the main window</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Run command on enter</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Run command on exit</source>
<translation type="unfinished">退</translation>
</message>
</context>
<context>
<name>StatusBar</name>
<message>
<source>%1 is not running</source>
<translation>%1 </translation>
</message>
<message>
<source>%1 is starting...</source>
<translation>%1 ...</translation>
</message>
<message>
<source>%1 will retry in a moment...</source>
<translation>%1 ...</translation>
</message>
<message>
<source>%1 is stopping...</source>
<translation>%1 ...</translation>
</message>
<message>
<source>%1 is waiting for clients</source>
<translation>%1 </translation>
</message>
<message>
<source>%1 is connecting...</source>
<translation>%1 ...</translation>
</message>
<message>
<source>%1 is connected as client of %2</source>
<translation>%1 %2 </translation>
</message>
<message>
<source>%1 is disconnected</source>
<translation>%1 </translation>
</message>
<message numerus="yes">
<source>%1 is connected, with %n client(s): %2</source>
<extracomment>Shown when in server mode and at least 1 client is connected %1 is replaced by the app name %2 will be a list of at least one client %n will be replaced by the number of clients (n is &gt;=1), it is not requried to be in the translation</extracomment>
<translation>
<numerusform>%1 %n %2</numerusform>
</translation>
</message>
<message>
<source>Clients:
%1</source>
<translation>
%1</translation>
</message>
<message>
<source>A new version v%1 is available</source>
<translation> v%1 </translation>
</message>
<message>
<source>View local fingerprint</source>
<translation></translation>
</message>
<message>
<source>Update available</source>
<translation></translation>
</message>
<message>
<source>%1 Encryption Enabled</source>
<translation>%1 </translation>
</message>
<message>
<source>Encryption Disabled</source>
<translation></translation>
</message>
</context> </context>
<context> <context>
<name>i18n</name> <name>i18n</name>