115 Commits

Author SHA1 Message Date
23c054661c Release v.18.0, with proper win package
Some checks are pending
Continuous Integration / pr-comment-flags (push) Blocked by required conditions
Continuous Integration / ci-passed (push) Blocked by required conditions
Continuous Integration / test-results (push) Blocked by required conditions
Continuous Integration / lint-check (push) Waiting to run
Continuous Integration / analyse-valgrind (push) Blocked by required conditions
Continuous Integration / analyse-sonarcloud (push) Blocked by required conditions
Continuous Integration / macos-14-arm64 (push) Blocked by required conditions
Continuous Integration / macos-13-x64 (push) Blocked by required conditions
Continuous Integration / archlinux-x86_84 (push) Blocked by required conditions
Continuous Integration / debian-13-x86_64 (push) Blocked by required conditions
Continuous Integration / fedora-40-x86_84 (push) Blocked by required conditions
Continuous Integration / fedora-41-x86_64 (push) Blocked by required conditions
Continuous Integration / opensuse-x86_84 (push) Blocked by required conditions
Continuous Integration / windows-2022-x64 (push) Blocked by required conditions
Continuous Integration / unix-freebsd (push) Blocked by required conditions
Continuous Integration / flatpak (push) Blocked by required conditions
Continuous Integration / release (push) Blocked by required conditions
Continuous Integration / winget-publish (push) Blocked by required conditions
2024-12-26 13:16:58 -05:00
098f56daee Release 1.18.0 2024-12-26 12:29:20 -05:00
ee8baeb1ff fix: qDebug doesn't work on Fedora 2024-12-26 16:48:39 +00:00
81d478632a fix: 8012 use 2/3 height on mac os for the log indicator 2024-12-26 16:32:11 +00:00
8e9925d6c8 fix: Mac os missing Quit from tray menu 2024-12-26 16:32:11 +00:00
368ccbbe4c fix: show normal mac os 2024-12-26 16:32:11 +00:00
a401c98bf6 build: fix mac os missing ; 2024-12-26 15:24:02 +00:00
f61fe00c33 ci: update lint action style 2024-12-26 15:24:02 +00:00
97890f86d3 chore: use static QApplication members where possible 2024-12-26 15:01:59 +00:00
638970d65e feat: Visiblity toggle for the log on the main window 2024-12-26 14:45:52 +00:00
98eb89255d refactor: move optional scripts to external repo 2024-12-26 14:29:25 +00:00
ccc60ff900 refactor: mv deskflow[s|c|d] to deskflow-server, deskflow-client and deskflow-deamon 2024-12-26 13:37:29 +00:00
db441eb5cf fix: win32 client taskbar remove duplicate state 2024-12-26 13:37:29 +00:00
21b345e9db fix: Create new Tray Icons for windows 2024-12-26 13:37:29 +00:00
9af37463fd refactor: single copy of app ico files for window now in src/apps/res 2024-12-26 13:37:29 +00:00
225ca23482 refactor: move all apps to one src/apps folder 2024-12-26 13:37:29 +00:00
9811586718 fixes: #7940 Use std::string insert to in place of arg sub for std::string static 2024-12-26 13:06:21 +00:00
858c41c946 fix: #7942, Correctly substitute config name part
fixes https://github.com/deskflow/deskflow/issues/7942
2024-12-26 12:28:19 +00:00
8b25e11f81 ci: combine lint-clang and lint-error into lint-check 2024-12-26 11:48:20 +00:00
d9727e60bc ci: remove unused init-python action 2024-12-26 11:48:20 +00:00
f8ddafe4a2 ci: use clang-format directly in lint-clang action 2024-12-26 11:48:20 +00:00
ab44559df6 fix(CVE-2021-42076): Enforce maximum message length to prevent memory exhaustion
based on barrier: debauchee/barrier@7ab8e01, debauchee/barrier@cc36982,
debauchee/barrier@e33c81b, debauchee/barrier@af90f39, debauchee/barrier@fd5295e
2024-12-26 10:45:21 +00:00
626e8c7364 fix(CVE-2021-42074): Handle SSL race conditions and segmentation fault
based on barrier: debauchee/barrier@8b937a4
2024-12-26 10:24:22 +00:00
041512b050 chore: remove unused scripts 2024-12-22 09:52:50 -05:00
cd0e98a480 build: remove post_config macros 2024-12-22 09:52:50 -05:00
802cfaa279 chore: removed unused scripts/daemon.py 2024-12-22 09:52:50 -05:00
aff3495e74 refactor: MainWindow clearer menu creation, remove private menu items 2024-12-22 12:39:59 +00:00
1ff7d5f4aa refactor: MainWindow remove need created / onCreated methods 2024-12-22 12:39:59 +00:00
784b2b2f6f fix: MainWindow, Use tr for use facing strings 2024-12-22 12:39:59 +00:00
927c8c146e refactor: MainWindow, use qDebug and friends correctly 2024-12-22 12:39:59 +00:00
94006f0bcd refactor: MainWindow, do not use autoconnections for the client / server radio butons 2024-12-22 12:39:59 +00:00
19c227b937 refactor: MainWindow, Do not use autoconnection for tls label 2024-12-22 12:39:59 +00:00
be5025b225 refactor: MainWindow, Do not use auto connection for the computer name label 2024-12-22 12:39:59 +00:00
4ee7d7b20f refactor: MainWindow, Do not auto connect the configure sever button 2024-12-22 12:39:59 +00:00
5032a5d275 refactor: MainWindow, no more auto connection for the host and client ip lines 2024-12-22 12:39:59 +00:00
d0dadf2112 refactor: MainWindow, better handle the toggleCore and ApplySettings buttons 2024-12-22 12:39:59 +00:00
2211caecb8 refactor: MainWindow, m_PButtonToggleStart -> btnToggleCore 2024-12-22 12:39:59 +00:00
5aa4840972 refactor: MainWindow, Create Actions in code 2024-12-22 12:39:59 +00:00
937ac053fe fix(CVE-2021-42075): Close connection on app-level handshake failure
based on barrier: debauchee/barrier@deefecc
2024-12-18 17:23:27 +00:00
cb638f4712 ci: bump mount wait time for macOS 2024-12-18 12:10:53 -05:00
751904f27c buid: add depends for deskflow core
fixes #7955
2024-12-18 12:10:53 -05:00
b9247b4c27 refactor: cleanup windows deploy files 2024-12-18 12:10:53 -05:00
cfd0bb9262 refactor: clean up mac os deploy files 2024-12-18 12:10:53 -05:00
d128623df3 refactor: clean up deploy for linux 2024-12-18 12:10:53 -05:00
b641a885d5 refactor: rm cmake/Packaging.cmake, instead do the config_linux_name macro in place 2024-12-18 12:10:53 -05:00
3817489097 chore: remove old wix building and build on ci with cpack 2024-12-18 12:10:53 -05:00
f06a789d25 build: wix generation for windows with cpack 2024-12-18 12:10:53 -05:00
b6d5095871 build: windows install step for deskflow gui 2024-12-18 12:10:53 -05:00
a06d65b1f9 build: windows, create install step for server 2024-12-18 12:10:53 -05:00
56ecd88945 build: windows, create install step for client 2024-12-18 12:10:53 -05:00
5a45e6d102 build: windows, create install step for daemon 2024-12-18 12:10:53 -05:00
e4ecbdae8a fix: macOS correctly restore window when hidden with command+H 2024-12-18 09:52:17 -05:00
bf4f513d7e chore: format *.m and *.mm files 2024-12-18 09:52:17 -05:00
af6dac9eee fix: Always show the tray menu entry for the restore action on macOS 2024-12-18 09:52:17 -05:00
ebad12a922 refactor: use QList for the tray's Action List 2024-12-18 09:52:17 -05:00
8aec0dd5bb fix: UTF-16 surrogate handling.
The data pointer needs to move before decoding the second
surrogate, and not after. The first surrogate was begin decoded
again, resulting in an invalid codepoint.

This affected clipboard operations originating on Windows machines,
where the text is encoded in UTF-16 and copying characters from a
high plane (like emojis) was broken.
2024-12-18 08:28:47 -05:00
ddb443b550 fix: Mainwindow can not start off screen
Fixes #7451
2024-12-18 07:59:56 -05:00
6a816350c1 mv .vscode to new repo github.com:deskflow/.vscode.git 2024-12-17 14:10:03 -05:00
0c26893706 fix: fixes #7949 Fixup groupboxes for settings dialog make settings dialog a fixed height 2024-12-17 09:29:09 -05:00
53b9c0908e fix: Windows, avoid encoding empty data to the clipboard
Do not attempt to convert null data to clipboard format and exit
early
2024-12-17 14:19:24 +00:00
252d11a316 feat: Windows, Use a clipboard format listener to monitor the clipboard
On Windows monitor the clipboard using a Clipboard format listener instead
of the legacy clipboard viewer chain, which was unreliable as it required
other programs to maintain the clipboard chain properly and couldn't
recover it any window in the clipboard viewer chain stopped responding
to messages.

Monitor the clipboard sequence number to not process the clipboard more
than once.
2024-12-17 14:19:24 +00:00
5026eea60a Fix: Use tray icon to hide and show from tray correctly
Remove show / hide from the tray menu
 Remove always disabled "show" action from window menu
2024-12-16 17:17:19 +00:00
482aa7a049 chore: Add minimum macOS version to readme 2024-12-16 08:59:33 -05:00
e12432d3d8 Update README.md 2024-12-06 07:40:05 -05:00
0349c06fed build: ReAdd NDEBUG define 2024-12-05 07:50:26 -05:00
6b7291f4b1 chore: src/lib/server/Config remove redundant c_str call 2024-12-04 10:40:58 -05:00
991293dd0c chore: src/gui/Action use default constructor 2024-12-04 10:40:58 -05:00
2349ce7f52 chore: replace simple cases of typedef with using 2024-12-04 10:40:58 -05:00
1d43e7c626 chore: src/gui/Action initilize items in class when possible 2024-12-04 10:40:58 -05:00
48e357f111 chore: IArchTrayBarReceiver subclass should override virtual methods not mark them virtual 2024-12-04 10:40:58 -05:00
d225be501e chore: IArchTaskBarReeiver subclasses should return nullptr for empty icon not NULL 2024-12-04 10:40:58 -05:00
37acc3b2fc chore: IArchTaskBarReciver subclasses Icon pointer should not be a const 2024-12-04 10:40:58 -05:00
b783f6e754 chore: scripts/package.py, remove unused vars in package method 2024-12-04 10:40:58 -05:00
6e01cc6ce5 ci: add config option for build step 2024-12-04 10:05:34 -05:00
6498fe5a6b refactor: use Qt Required Version rm configure_qt macro for its one use 2024-12-04 10:05:34 -05:00
4e23460c6f refactor: no configure_openssl macro 2024-12-04 10:05:34 -05:00
7ce2845c9b refactor: place simple macros where they are used 2024-12-04 10:05:34 -05:00
c8e1e4f38f refactor: rm unneed macro to flatten code coverage logic 2024-12-04 10:05:34 -05:00
922ad66aff refactor: options in place
remove use of env vars to set build options
 users should set these at cmake configure time
2024-12-04 10:05:34 -05:00
b0d22926f9 refactor: rm :cmake/Defines move last macro to Bulid.cmake 2024-12-04 10:05:34 -05:00
81631cac32 refactor: set test names directly and call only configure_options in main cmakelists.txt 2024-12-04 10:05:34 -05:00
1685c6e946 chore: rm unneeded debug define 2024-12-04 10:05:34 -05:00
1e8ff50d59 refactor: mv cmake policy settings to main CMakelists.txt 2024-12-04 10:05:34 -05:00
5d591e3518 build: rm unneeed configure_ninja steps 2024-12-04 10:05:34 -05:00
57f6248b6a build: rm configure_build macro set items in main CMakeLists.txt 2024-12-04 10:05:34 -05:00
d6c682a923 chore: rm unused src/lib/gui/gui_config.h 2024-12-04 10:05:34 -05:00
4f8ae57ffd build: always include headers 2024-12-04 10:05:34 -05:00
a92f5b0351 refactor: correct osx deployment target and move to main CMakeLists.txt 2024-12-04 10:05:34 -05:00
0f0846f011 build: mv CXX Options to the Main CMakeLists.txt 2024-12-04 10:05:34 -05:00
86cca8a0d1 refactor: don't use a macro for a single line 2024-12-04 10:05:34 -05:00
eb9e507a1f chore: rm Unused Build_Time macro and define 2024-12-04 10:05:34 -05:00
03f1408a98 refactor: mv src/lib/gui/VersionChecker => src/gui/src/ 2024-12-04 10:05:34 -05:00
7f9fd80f98 refactor: remove workarounds for unsupported ms vc versions 2024-12-04 10:05:34 -05:00
c5cfdc2b69 refactor: AboutDialog, show the version tweak only if its not 0, when showing the tweak also show the sha 2024-12-04 10:05:34 -05:00
7656b49f1b refactor: set GIT_SHA_SHORT with git command 2024-12-04 10:05:34 -05:00
c05a87b716 refactor: rm src/lib/common/copyright, add to src/lib/common/constants 2024-12-04 10:05:34 -05:00
4a83eb711f build: generate version info, use it for all sources of version 2024-12-04 10:05:34 -05:00
f321f6596b build: make an interface lib out of the common folder 2024-12-04 10:05:34 -05:00
18bc419b7a build: remove unused DESKFLOW_REVISION and DESKFLOW_VERSION_STAGE defines 2024-12-04 10:05:34 -05:00
25660049e4 Add windows requirement and flathub link to readme 2024-11-25 21:15:07 +00:00
9fcf261245 build: use CPACK_WIX_UPGRADE_GUID for wix GUID
Remove unuseable 32bit Parts
2024-11-25 20:50:58 +00:00
59d9454df1 build: install License files 2024-11-25 20:50:58 +00:00
14f66e2dee refactor: simplify linux os-release parsing 2024-11-25 20:50:58 +00:00
882b71ea84 ci: consistant upload name for flatpak fixes 7911 2024-11-25 19:37:13 +00:00
d26339d94a ci: remove unused test-actions workflow 2024-11-25 19:27:05 +00:00
079bfdc854 chore: update metadata long desc 2024-11-21 14:55:58 +00:00
3003670b94 ci: use x86_64 for job names 2024-11-21 14:55:58 +00:00
79c17e3564 ci: use master version of flatpak builder 2024-11-21 14:55:58 +00:00
397f9f0fac ci: add linux to flatpak bundle name 2024-11-21 14:55:58 +00:00
40ff14473d build: use linux for distro name is its no defined 2024-11-21 14:55:58 +00:00
b16a03c3ad fix: flatpak recipe should not clean up "lib/debug" 2024-11-21 14:55:58 +00:00
f11178327d chore: remove unused python parts 2024-11-21 14:27:01 +00:00
c6f352ac4a refactor: move most of the remaing packaging info into deploy 2024-11-21 14:27:01 +00:00
2cdbd8f491 build: use CPack for macOS to create dmg 2024-11-21 14:27:01 +00:00
a63435e64a build: use cmake to make the macOS bundle 2024-11-21 14:27:01 +00:00
301 changed files with 3952 additions and 7220 deletions

View File

@ -1,23 +0,0 @@
# Important: Do not be tempted to cache the .venv dir (Python virtual environment).
# When the runner environment changes (e.g. Python is upgraded), the venv will need to be
# re-created. Trying to upgrade a venv can be complex and it's usually more practical re-create it.
# We don't save much time anyway by caching the venv so it's not worth the added complexity.
name: "Setup Python venv"
description: "Creates a Python virtual environment (venv)"
runs:
using: "composite"
steps:
- name: Setup Python venv
run: |
if [ "${{ runner.os }}" == "Windows" ]; then
python=python
else
python=python3
fi
echo "Setting up Python venv, bin=$python"
$python -m venv .venv
shell: bash

View File

@ -95,6 +95,8 @@ runs:
- name: Install Wix
if: ${{ runner.os == 'Windows' }}
run: |
dotnet tool install --global wix --version 4.0.4
wix extension add --global WixToolset.UI.wixext/4.0.4
dotnet tool install --global wix --version 5.0.2
wix extension add --global WixToolset.UI.wixext/5.0.2
wix extension add --global WixToolset.Util.wixext/5.0.2
wix extension add --global WixToolset.Firewall.wixext/5.0.2
shell: pwsh

View File

@ -1,26 +1,21 @@
name: "Lint error"
name: "Lint Check"
description: "Checks for lint errors and posts a helpful comment"
inputs:
format-command:
description: "The command to run to fix lint errors"
required: true
format-tool:
description: "The name of the linting tool"
required: true
runs:
using: "composite"
steps:
- name: Install Dependencies
run: pipx install --global clang_format
shell: bash
- name: Run format command
run: ${{ inputs.format-command }}
run: find src/ -regex '.*\.\(cpp\|hpp\|cc\|cxx\|h\|c\|m\|mm\)' -exec clang-format -i {} \;
shell: bash
- name: Find changes
id: changes
run: |
file=${{ inputs.format-tool }}.diff
file=clang-format.diff
diff=$(git diff | tee $file)
if [ -z "$diff" ]; then
@ -43,7 +38,7 @@ runs:
if: steps.changes.outputs.diff
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.format-tool }}-diff
name: clang-format-diff
path: ${{ steps.changes.outputs.file }}
if-no-files-found: error
@ -54,9 +49,9 @@ runs:
code_block="\`\`\`"
summary=$(cat<<EOF
❌ \`${{ inputs.format-tool }}\`: It looks like your changes don't match our code style.
❌ \`clang-format\`: It looks like your changes don't match our code style.
🛠️ Please either run \`${{ inputs.format-command }}\` or apply this patch with \`git apply\`:
🛠️ Please either run \`clang-format -i\` on the file or apply this patch with \`git apply\`:
[\`${{ steps.changes.outputs.file }}\`](${{ steps.upload.outputs.artifact-url }})
$code_block diff
@ -67,7 +62,7 @@ runs:
echo "$summary" >> $GITHUB_STEP_SUMMARY
file="ci_summary.md"
echo "❌🛠️ \`${{ inputs.format-tool }}\`: Lint errors, fix available." >> $file
echo "❌🛠️ \`clang-format\`: Lint errors, fix available." >> $file
echo "file=$file" >> $GITHUB_OUTPUT
shell: bash
@ -75,7 +70,7 @@ runs:
if: steps.summary.outputs.file
uses: actions/upload-artifact@v4
with:
name: summary-${{ inputs.format-tool }}
name: summary-clang-format
path: ${{ steps.summary.outputs.file }}
if-no-files-found: error

View File

@ -1,21 +0,0 @@
name: "Lint clang"
description: "Lint with Clang formatter"
runs:
using: "composite"
steps:
- name: Setup Python venv
uses: ./.github/actions/init-python
- name: Install dependencies
shell: bash
run: |
source .venv/bin/activate
pip install pyyaml clang_format
- name: Linting with Clang formatter
uses: ./.github/actions/lint-error
with:
format-command: ./scripts/lint_clang.py -f
format-tool: "clang-format"

View File

@ -34,7 +34,7 @@ jobs:
# Always run this job, even if not on PR, since other jobs need it.
pr-comment-flags:
runs-on: ubuntu-latest
needs: lint-clang
needs: lint-check
outputs:
no-sonar: ${{ steps.check.outputs.no-sonar }}
@ -82,7 +82,7 @@ jobs:
- name: Test summary
uses: ./.github/actions/test-summary
lint-clang:
lint-check:
runs-on: ubuntu-latest
timeout-minutes: 5
@ -90,11 +90,11 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Linting Clang
uses: ./.github/actions/lint-clang
- name: Lint Checker
uses: ./.github/actions/lint-check
analyse-valgrind:
needs: lint-clang
needs: lint-check
if: ${{ github.event_name == 'pull_request' }}
uses: ./.github/workflows/valgrind-analysis.yml
@ -106,7 +106,7 @@ jobs:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
main-build:
needs: lint-clang
needs: lint-check
name: ${{ matrix.target.name }}
runs-on: ${{ matrix.target.runs-on }}
container: ${{ matrix.target.container }}
@ -127,42 +127,42 @@ jobs:
runs-on: "macos-14"
timeout: 10
arch: arm64
config-args: "-DCMAKE_OSX_ARCHITECTURES=\"arm64\" -DMACOSX_DEPLOYMENT_TARGET=12"
config-args: "-DCMAKE_OSX_ARCHITECTURES=\"arm64\""
- name: "macos-13-x64"
runs-on: macos-13
timeout: 20
config-args: "-DCMAKE_OSX_ARCHITECTURES=\"x86_64\" -DMACOSX_DEPLOYMENT_TARGET=12"
config-args: "-DCMAKE_OSX_ARCHITECTURES=\"x86_64\""
- name: "debian-13-amd64"
- name: "debian-13-x86_64"
runs-on: ubuntu-latest
container: debian:trixie-slim
like: "debian"
timeout: 20
config-args: "-G Ninja -DCMAKE_INSTALL_PREFIX=/usr"
- name: "fedora-41-amd64"
- name: "fedora-41-x86_64"
runs-on: ubuntu-latest
container: fedora:41
like: "fedora"
timeout: 20
config-args: "-G Ninja -DCMAKE_INSTALL_PREFIX=/usr"
- name: "fedora-40-amd64"
- name: "fedora-40-x86_84"
runs-on: ubuntu-latest
container: fedora:40
like: "fedora"
timeout: 20
config-args: "-G Ninja -DCMAKE_INSTALL_PREFIX=/usr"
- name: "opensuse-amd64"
- name: "opensuse-x86_84"
runs-on: ubuntu-latest
container: opensuse/tumbleweed:latest
like: "suse"
timeout: 20
config-args: "-G Ninja -DCMAKE_INSTALL_PREFIX=/usr"
- name: "archlinux-amd64"
- name: "archlinux-x86_84"
runs-on: ubuntu-latest
container: archlinux:latest
like: "arch"
@ -205,26 +205,19 @@ jobs:
mac-qt-version: 6.7.2
like: ${{ matrix.target.like }}
- name: Setup Python
if: ${{runner.os != 'Linux' }}
run: python ./scripts/setup_venv.py
- name: Get version
uses: ./.github/actions/get-version
- 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}}"
- name: Build
shell: bash
run: |
if [[ "$RUNNER_OS" == "Linux" && "${{matrix.target.like}}" != "arch" ]]; then
cmake --build build -j8 --target package
if [[ "${{matrix.target.like}}" != "arch" ]]; then
cmake --build build --config Release -j8 --target package
else
cmake --build build -j8
fi
if [ ${{ matrix.target.like }} == "arch" ];then
cmake --build build --config Release -j8
useradd -m build
sudo chown -R build build
cd build
@ -241,23 +234,6 @@ jobs:
with:
job: ${{ matrix.target.name }}
- name: Package
if: ${{ runner.os != 'Linux' }}
shell: bash
run: |
python ./scripts/package.py --package-version ${{env.DESKFLOW_PACKAGE_VERSION}}
mv dist/deskflow* build/
env:
WINDOWS_PFX_CERTIFICATE: ${{ secrets.WINDOWS_PFX }}
WINDOWS_PFX_PASSWORD: ${{ secrets.WINDOWS_PFX_PASS }}
APPLE_CODESIGN_ID: ${{ secrets.APPLE_CODESIGN_ID }}
APPLE_P12_CERTIFICATE: ${{ secrets.APPLE_P12_CERTIFICATE }}
APPLE_P12_PASSWORD: ${{ secrets.APPLE_P12_PASSWORD }}
APPLE_NOTARY_USER: ${{ secrets.APPLE_NOTARY_USER }}
APPLE_NOTARY_PASSWORD: ${{ secrets.APPLE_NOTARY_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
- name: Upload
uses: actions/upload-artifact@v4
with:
@ -266,7 +242,7 @@ jobs:
# Technically, "unix" is a misnomer, but we use it here to mean "Unix-like BSD-derived".
unix:
needs: lint-clang
needs: lint-check
name: unix-${{ matrix.distro.name }}
runs-on: ${{ vars.CI_UNIX_RUNNER || 'ubuntu-24.04' }}
timeout-minutes: 20
@ -299,7 +275,7 @@ jobs:
./build/bin/unittests
./build/bin/integtests || true
flatpak:
needs: lint-clang
needs: lint-check
runs-on: ubuntu-latest
container:
image: bilelmoussaoui/flatpak-github-actions:kde-6.7
@ -313,18 +289,18 @@ jobs:
- name: Get version
uses: ./.github/actions/get-version
- uses: flatpak/flatpak-github-actions/flatpak-builder@v6
- uses: flatpak/flatpak-github-actions/flatpak-builder@master
name: "Build"
with:
bundle: deskflow-${{env.DESKFLOW_PACKAGE_VERSION}}-x86_64.flatpak
manifest-path: deploy/dist/flatpak/org.deskflow.deskflow.yml
bundle: deskflow-${{env.DESKFLOW_PACKAGE_VERSION}}-linux-x86_64.flatpak
manifest-path: deploy/linux/flatpak/org.deskflow.deskflow.yml
cache-key: flatpak-builder-${{ github.sha }}
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
with:
name: package-${{ env.PACKAGE_PREFIX }}-flatpak
name: package-${{ env.PACKAGE_PREFIX }}-flatpak-x86_64
path: ${{github.workspace}}/deskflow[-_]*.flatpak
release:

View File

@ -1,41 +0,0 @@
name: Test actions
# Intended to only be run manually for testing actions in isolation.
on:
workflow_dispatch:
jobs:
test-summary:
runs-on: ubuntu-latest
name: Test summary (${{ matrix.message.name }})
strategy:
matrix:
message:
- name: success
text: "|Success|✅|✅|"
expect: ""
- name: failure
text: "|Failure|❌|❌|"
expect: "❌🔬 Tests failed: 2"
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Generate
shell: bash
run: |
echo "${{ matrix.message.text }}" > test-result.md
- name: Upload
uses: actions/upload-artifact@v4
with:
path: test-result.md
name: test-result-${{ matrix.message.name }}
- name: Test
uses: ./.github/actions/test-summary
with:
download-pattern: test-result-${{ matrix.message.name }}
upload-name: summary-${{ matrix.message.name }}

7
.gitignore vendored
View File

@ -10,7 +10,6 @@
/tmp
/vcpkg
/vcpkg_installed
/scripts/**/*.pyc
/.cache
/.venv
aqtinstall.log
@ -34,3 +33,9 @@ CMakeLists.txt.user
CMakeCache.txt
CMakeUserPresets.json
CMakeFiles/*
# vscode folder
/.vscode
# scripts folder
/scripts

View File

@ -1,11 +0,0 @@
{
"recommendations": [
"ms-vscode.cmake-tools",
"twxs.cmake",
"llvm-vs-code-extensions.vscode-clangd",
"ms-vscode.cpptools",
"vadimcn.vscode-lldb",
"cheshirekow.cmake-format",
"jacqueslucke.gcov-viewer"
]
}

135
.vscode/launch.json vendored
View File

@ -1,135 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "unix - gui",
"type": "lldb",
"cwd": "${workspaceRoot}",
"request": "launch",
"program": "${workspaceFolder}/build/bin/deskflow",
"preLaunchTask": "kill-build"
},
{
"name": "unix - unittests",
"type": "lldb",
"cwd": "${workspaceRoot}",
"request": "launch",
"program": "${workspaceFolder}/build/bin/unittests",
"args": [ "${input:gtest-args}" ],
"preLaunchTask": "build"
},
{
"name": "unix - integtests",
"type": "lldb",
"cwd": "${workspaceRoot}",
"request": "launch",
"program": "${workspaceFolder}/build/bin/integtests",
"args": [ "${input:gtest-args}" ],
"preLaunchTask": "build",
},
{
"name": "unix - server",
"type": "lldb",
"cwd": "${workspaceRoot}",
"request": "launch",
"program": "${workspaceFolder}/build/bin/deskflow-server",
"args": ["--config-toml", "deskflow-config.toml"],
"preLaunchTask": "kill-build"
},
{
"name": "unix - client",
"type": "lldb",
"cwd": "${workspaceRoot}",
"request": "launch",
"program": "${workspaceFolder}/build/bin/deskflow-client",
"args": ["--config-toml", "deskflow-config.toml"],
"preLaunchTask": "kill-build"
},
{
"name": "unix - attach",
"type": "lldb",
"request": "attach",
"pid": "${command:pickProcess}"
},
{
"name": "windows - gui",
"type": "cppvsdbg",
"cwd": "${workspaceRoot}",
"request": "launch",
"program": "${workspaceFolder}/build/bin/deskflow",
"internalConsoleOptions": "openOnSessionStart",
"preLaunchTask": "kill-build"
},
{
"name": "windows - unittests",
"type": "cppvsdbg",
"cwd": "${workspaceRoot}",
"request": "launch",
"program": "${workspaceFolder}/build/bin/unittests",
"args": [ "${input:gtest-args}" ],
"internalConsoleOptions": "openOnSessionStart",
"preLaunchTask": "build"
},
{
"name": "windows - integtests",
"type": "cppvsdbg",
"cwd": "${workspaceRoot}",
"request": "launch",
"program": "${workspaceFolder}/build/bin/integtests",
"args": [ "${input:gtest-args}" ],
"internalConsoleOptions": "openOnSessionStart",
"preLaunchTask": "build"
},
{
"name": "windows - server",
"type": "cppvsdbg",
"cwd": "${workspaceRoot}",
"request": "launch",
"program": "${workspaceFolder}/build/bin/deskflow-server",
"args": ["--config-toml", "deskflow-config.toml"],
"internalConsoleOptions": "openOnSessionStart",
"preLaunchTask": "kill-build"
},
{
"name": "windows - client",
"type": "cppvsdbg",
"cwd": "${workspaceRoot}",
"request": "launch",
"program": "${workspaceFolder}/build/bin/deskflow-client",
"args": ["--config-toml", "deskflow-config.toml"],
"internalConsoleOptions": "openOnSessionStart",
"preLaunchTask": "kill-build"
},
{
"name": "windows - daemon",
"type": "cppvsdbg",
"cwd": "${workspaceRoot}",
"request": "launch",
"program": "${workspaceFolder}/build/bin/deskflow-daemon",
"args": ["-f"],
"internalConsoleOptions": "openOnSessionStart",
"preLaunchTask": "build"
},
{
"name": "windows - attach",
"type": "cppvsdbg",
"request": "attach",
"processId": "${command:pickProcess}"
},
{
"name": "install_deps.py",
"type": "debugpy",
"request": "launch",
"program": "scripts/install_deps.py",
"console": "integratedTerminal"
}
],
"inputs": [
{
"id": "gtest-args",
"type": "promptString",
"description": "Test arguments",
"default": "--gtest_filter=*"
}
]
}

172
.vscode/tasks.json vendored
View File

@ -1,172 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "cmake",
"command": "build",
"targets": ["all"],
"preset": "${command:cmake.activeBuildPresetName}",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": {
"base": "$gcc",
"fileLocation": ["absolute"]
}
},
{
"label": "clean",
"type": "cmake",
"command": "build",
"targets": ["clean"],
"preset": "${command:cmake.activeBuildPresetName}",
"group": "build"
},
{
"label": "clean-gcda",
"type": "shell",
"command": "find . -name '*.gcda' -delete",
"windows": {
"command": "$null"
},
"presentation": {
"reveal": "silent"
}
},
{
"label": "clean-qt",
"type": "shell",
"command": "rm -r build/src/gui build/src/lib/gui",
"windows": {
"command": "remove-item -recurse build/src/gui,build/src/lib/gui"
}
},
{
"label": "clean-config",
"type": "shell",
"linux": {
"command": "rm -r ~/.config/Deskflow/Deskflow.conf"
},
"windows": {
"command": "remove-item -recurse $env:APPDATA\\Deskflow\\Deskflow"
},
"osx": {
"command": "rm -r ~/Library/Application\\ Support/Deskflow/Deskflow"
}
},
{
"label": "tests",
"dependsOn": ["integtests", "unittests"],
"problemMatcher": []
},
{
"label": "kill",
"type": "shell",
"command": "killall deskflow; killall deskflow-client; killall deskflow-server || true",
"windows": {
"command": "taskkill /F /IM deskflow.exe /IM deskflow-client.exe /IM deskflow-client.exe; $true"
},
"presentation": {
"reveal": "silent"
}
},
{
"label": "kill-build",
"dependsOn": ["kill", "build"],
"problemMatcher": []
},
{
"label": "gui",
"type": "process",
"command": "${workspaceFolder}/build/bin/deskflow",
"dependsOn": ["build", "kill"],
"problemMatcher": [],
"windows": {
"command": "${workspaceFolder}/build/bin/deskflow.exe"
}
},
{
"label": "restart daemon",
"type": "shell",
"command": "python scripts/daemon.py --restart",
"dependsOn": ["build"]
},
{
"label": "reinstall daemon",
"type": "shell",
"command": "python scripts/daemon.py --reinstall",
"dependsOn": ["build"]
},
{
"label": "stop daemon",
"type": "shell",
"command": "python scripts/daemon.py --stop"
},
{
"label": "unittests (current)",
"type": "shell",
"command": "python",
"args": [
"./scripts/tests.py",
"--unit-tests",
"--ignore-return-code",
"--filter-file=${file}"
],
"dependsOn": ["build", "clean-gcda"]
},
{
"label": "integtests (current)",
"type": "shell",
"command": "python",
"args": [
"./scripts/tests.py",
"--integ-tests",
"--ignore-return-code",
"--filter-file=${file}"
],
"dependsOn": ["build", "clean-gcda"]
},
{
"label": "unittests",
"type": "shell",
"command": "python",
"args": ["./scripts/tests.py", "--unit-tests", "--ignore-return-code"],
"dependsOn": ["build", "clean-gcda"]
},
{
"label": "integtests",
"type": "shell",
"command": "python",
"args": ["./scripts/tests.py", "--integ-tests", "--ignore-return-code"],
"dependsOn": ["build", "clean-gcda"]
},
{
"label": "unittests (current, valgrind)",
"type": "shell",
"command": "python",
"args": [
"./scripts/tests.py",
"--unit-tests",
"--ignore-return-code",
"--filter-file=${file}",
"--valgrind"
],
"dependsOn": ["build", "clean-gcda"]
},
{
"label": "integtests (current, valgrind)",
"type": "shell",
"command": "python",
"args": [
"./scripts/tests.py",
"--integ-tests",
"--ignore-return-code",
"--filter-file=${file}",
"--valgrind"
],
"dependsOn": ["build", "clean-gcda"]
}
]
}

View File

@ -21,10 +21,21 @@
# > See https://reproducible-builds.org/specs/source-date-epoch/ for details.
cmake_minimum_required(VERSION 3.24)
# Link items by fill path
cmake_policy(SET CMP0003 NEW)
# Fix define escaping
cmake_policy(SET CMP0005 NEW)
# Set CXX Requirements
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Fallback for when git can not be found
set(DESKFLOW_VERSION_MAJOR 1)
set(DESKFLOW_VERSION_MINOR 17)
set(DESKFLOW_VERSION_PATCH 2)
set(DESKFLOW_VERSION_MINOR 18)
set(DESKFLOW_VERSION_PATCH 0)
set(DESKFLOW_VERSION_TWEAK 0)
# Get the version from git if it's a git repository
@ -32,6 +43,12 @@ set(DESKFLOW_VERSION_TWEAK 0)
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/.git)
find_package(Git)
if(GIT_FOUND)
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-parse --short=8 HEAD
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
OUTPUT_VARIABLE GIT_SHA_SHORT
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE
)
execute_process(
COMMAND ${GIT_EXECUTABLE} describe --long --match v* --always
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
@ -64,8 +81,6 @@ set(DESKFLOW_VERSION_MS_CSV
"${DESKFLOW_VERSION_MAJOR},${DESKFLOW_VERSION_MINOR},${DESKFLOW_VERSION_PATCH},${DESKFLOW_VERSION_TWEAK}"
)
add_definitions(-DDESKFLOW_VERSION="${DESKFLOW_VERSION}")
#Define our project
project(
deskflow
@ -75,20 +90,55 @@ project(
message(STATUS "Building ${CMAKE_PROJECT_NAME}-${CMAKE_PROJECT_VERSION}")
include(cmake/Definitions.cmake)
include(cmake/Build.cmake)
include(cmake/Libraries.cmake)
include(cmake/Packaging.cmake)
# Set lib versions
set(REQUIRED_OPENSSL_VERSION 3.0)
set(REQUIRED_LIBEI_VERSION 1.3)
set(REQUIRED_LIBPORTAL_VERSION 0.8)
set(REQUIRED_QT_VERSION 6.7.0)
# Control debug item visibility
# When not set logging is forced to DEBUG and show code locations
# Also exposes a test menu
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
message(STATUS "Disabling debug build")
add_definitions(-DNDEBUG)
endif()
# Set required macOS SDK
if(APPLE)
set(CMAKE_OSX_DEPLOYMENT_TARGET 12)
endif()
# Set Output Folders
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib")
include(cmake/Libraries.cmake)
include(GNUInstallDirs)
configure_definitions()
configure_build()
configure_libs()
configure_packaging()
add_subdirectory(doc)
add_subdirectory(src)
add_subdirectory(deploy)
post_config_all()
# Install License, License is in the App Bundle on mac os (src/gui)
if(WIN32)
install(
FILES
${PROJECT_SOURCE_DIR}/LICENSE
${PROJECT_SOURCE_DIR}/LICENSE_EXCEPTION
DESTINATION .
)
elseif(UNIX AND NOT APPLE)
install(
FILES
${PROJECT_SOURCE_DIR}/LICENSE
${PROJECT_SOURCE_DIR}/LICENSE_EXCEPTION
DESTINATION share/licenses/deskflow
)
endif()
option(BUILD_INSTALLER "Build installer" ON)
if(BUILD_INSTALLER)
add_subdirectory(deploy)
endif()

View File

@ -15,7 +15,7 @@ and work seamlessly between them.
It's like a software KVM (but without the video).
TLS encryption is enabled by default. Wayland is supported. Clipboard sharing is supported.
[![Downloads: Stable Release](https://img.shields.io/github/downloads/deskflow/deskflow/latest/total?style=for-the-badge&logo=github&label=Download%20Stable)](https://github.com/deskflow/deskflow/releases/latest)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[![Downloads: Continuous Build](https://img.shields.io/github/downloads/deskflow/deskflow/continuous/total?style=for-the-badge&logo=github&label=Download%20Continuous)](https://github.com/deskflow/deskflow/releases/continuous)
[![Downloads: Stable Release](https://img.shields.io/github/downloads/deskflow/deskflow/latest/total?style=for-the-badge&logo=github&label=Download%20Stable)](https://github.com/deskflow/deskflow/releases/latest)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[![Downloads: Continuous Build](https://img.shields.io/github/downloads/deskflow/deskflow/continuous/total?style=for-the-badge&logo=github&label=Download%20Continuous)](https://github.com/deskflow/deskflow/releases/continuous)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[![Download From Flathub](https://img.shields.io/flathub/downloads/org.deskflow.deskflow?style=for-the-badge&logo=flathub&label=Download%20from%20flathub)](https://flathub.org/apps/org.deskflow.deskflow)
To use Deskflow you can use one of our [packages](https://github.com/deskflow/deskflow/releases), install `deskflow` (if available in your package repository), or [build it](#build-quick-start) yourself from source.
@ -63,6 +63,10 @@ For instructions on building Deskflow, use the wiki page: [Building](https://git
We support all major operating systems, including Windows, macOS, Linux, and Unix-like BSD-derived.
Windows 10 or higher is required.
macOS 12 or higher is required.
All Linux distributions are supported, primarily focusing on:
Debian, Ubuntu, Linux Mint, Fedora, RHEL, AlmaLinux, Rocky Linux, Arch Linux, openSUSE, Gentoo.
@ -136,7 +140,7 @@ Yes, Deskflow has network compatibility with all forks:
### Is Deskflow compatible with Lan Mouse?
We would love to see compatibility with Lan Mouse. This maybe quite an effort as currently they way they handle the generated input is very different.
We would love to see compatibility with Lan Mouse. This may be quite an effort as currently the way they handle the generated input is very different.
### If I want to solve issues in Deskflow do I need to contribute to a fork?

View File

@ -1,104 +0,0 @@
# Deskflow -- mouse and keyboard sharing utility
# Copyright (C) 2024 Symless Ltd.
#
# This package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# found in the file LICENSE that should have accompanied this file.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
macro(configure_build)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib")
if(APPLE)
message(STATUS "Configuring for Apple")
set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0")
endif()
set_build_date()
configure_file_shared()
endmacro()
macro(set_build_date)
# Since CMake 3.8.0, `string(TIMESTAMP ...)` respects `SOURCE_DATE_EPOCH` env var if set,
# which allows package maintainers to create reproducible builds.
# We require CMake 3.8.0 in the root `CMakeLists.txt` for this reason.
string(TIMESTAMP BUILD_DATE "%Y-%m-%d" UTC)
message(STATUS "Build date: ${BUILD_DATE}")
add_definitions(-DBUILD_DATE="${BUILD_DATE}")
endmacro()
macro(configure_file_shared)
configure_file(${PROJECT_SOURCE_DIR}/src/lib/gui/gui_config.h.in
${PROJECT_BINARY_DIR}/config/gui_config.h)
endmacro()
macro(post_config)
# Build to a temp bin dir on Windows and then copy to the final bin dir
# (ignore copy fail). It is neccesary to do this. Since the binary may already
# be running and you can't write to a running binary (on Windows). It's common
# to use Deskflow to develop Deskflow (i.e. eating your own dog food immediately
# making it).
if(WIN32)
if(NOT target)
message(FATAL_ERROR "target not set")
endif()
set_target_properties(${target} PROPERTIES RUNTIME_OUTPUT_DIRECTORY
${BIN_TEMP_DIR})
endif()
endmacro()
macro(post_config_all)
if(WIN32)
# Always try to copy the files to the bin directory after every build and deliberatly ignore
# copy errors, usually the error is because a running process has locked the file.
#
# It is useful to copy every time because the copy may have failed last time due to the file
# being in use, and we'll usually want to try again (after killing the process).
#
# Yes, this looks like a ridiculous thing to do, but it really is necessary to
# use a Python script to copy files on Windows. Why? Two reasons:
#
# 1. Windows file locks (on running processes) creates a very painful development
# experience; you can't overwrite the binary you're running it.
# Why not just stop the process? Windows services are an abject PITA to manage,
# and we don't always care about overwriting all binaries.
#
# 2. The Windows copy command is limited and gives vague/misleading errors.
# The CMake copy command also has similar shortfalls.
#
# Patches welcome! :)
add_custom_target(
run_post_build ALL
COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/scripts/fancy_copy.py
${BIN_TEMP_DIR} ${PROJECT_BINARY_DIR}/bin --ignore-errors
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
VERBATIM
COMMENT "Copying files to bin dir")
add_dependencies(
run_post_build
deskflow
deskflow-client
deskflow-server
deskflow-daemon)
endif()
endmacro()

View File

@ -1,101 +0,0 @@
# Deskflow -- mouse and keyboard sharing utility
# Copyright (C) 2012-2024 Symless Ltd.
# Copyright (C) 2009-2012 Nick Bolton
#
# This package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# found in the file LICENSE that should have accompanied this file.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
macro(configure_definitions)
configure_ninja()
configure_options()
set(INTEG_TESTS_BIN integtests)
set(UNIT_TESTS_BIN unittests)
if(NOT "$ENV{GIT_SHA}" STREQUAL "")
# Shorten the Git SHA to 8 chars for readability
string(SUBSTRING "$ENV{GIT_SHA}" 0 8 GIT_SHA_SHORT)
message(STATUS "Short Git SHA: ${GIT_SHA_SHORT}")
add_definitions(-DGIT_SHA_SHORT="${GIT_SHA_SHORT}")
endif()
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
message(STATUS "Disabling debug build")
add_definitions(-DNDEBUG)
endif()
# TODO: find out why we need these, and remove them if we don't.
if(COMMAND cmake_policy)
cmake_policy(SET CMP0003 NEW)
cmake_policy(SET CMP0005 NEW)
endif()
# TODO: explain why we're adding headers to sources.
if(${CMAKE_GENERATOR} STREQUAL "Unix Makefiles")
set(ADD_HEADERS_TO_SOURCES FALSE)
else()
set(ADD_HEADERS_TO_SOURCES TRUE)
endif()
set(BIN_TEMP_DIR ${PROJECT_BINARY_DIR}/temp/bin)
endmacro()
macro(configure_ninja)
# use response files so that ninja can compile on windows, otherwise you get
# an error when linking qt: "The input line is too long."
set(CMAKE_C_USE_RESPONSE_FILE_FOR_OBJECTS 1)
set(CMAKE_CXX_USE_RESPONSE_FILE_FOR_OBJECTS 1)
set(CMAKE_C_RESPONSE_FILE_LINK_FLAG "@")
set(CMAKE_CXX_RESPONSE_FILE_LINK_FLAG "@")
set(CMAKE_NINJA_FORCE_RESPONSE_FILE
1
CACHE INTERNAL "")
endmacro()
macro(configure_options)
set(DEFAULT_BUILD_GUI ON)
set(DEFAULT_BUILD_INSTALLER ON)
set(DEFAULT_BUILD_TESTS ON)
# unified binary is off by default for now, for backwards compatibility.
set(DEFAULT_BUILD_UNIFIED OFF)
# coverage is off by default because it's GCC only and a developer preference.
set(DEFAULT_ENABLE_COVERAGE OFF)
if("$ENV{DESKFLOW_BUILD_MINIMAL}" STREQUAL "true")
set(DEFAULT_BUILD_GUI OFF)
set(DEFAULT_BUILD_INSTALLER OFF)
endif()
if("$ENV{DESKFLOW_BUILD_TESTS}" STREQUAL "false")
set(DEFAULT_BUILD_TESTS OFF)
endif()
if("$ENV{DESKFLOW_BUILD_UNIFIED}" STREQUAL "true")
set(DEFAULT_BUILD_UNIFIED ON)
endif()
if("$ENV{DESKFLOW_ENABLE_COVERAGE}" STREQUAL "true")
set(DEFAULT_ENABLE_COVERAGE ON)
endif()
option(BUILD_GUI "Build GUI" ${DEFAULT_BUILD_GUI})
option(BUILD_INSTALLER "Build installer" ${DEFAULT_BUILD_INSTALLER})
option(BUILD_TESTS "Build tests" ${DEFAULT_BUILD_TESTS})
option(BUILD_UNIFIED "Build unified binary" ${DEFAULT_BUILD_UNIFIED})
option(ENABLE_COVERAGE "Enable test coverage" ${DEFAULT_ENABLE_COVERAGE})
endmacro()

View File

@ -13,22 +13,80 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(LIBEI_MIN_VERSION 1.3)
set(LIBPORTAL_MIN_VERSION 0.8)
macro(configure_libs)
set(libs)
if(UNIX)
configure_unix_libs()
elseif(WIN32)
configure_windows_libs()
find_package(Python REQUIRED QUIET)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /D _BIND_TO_CURRENT_VCLIBS_VERSION=1")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MD /O2 /Ob2")
list(APPEND libs Wtsapi32 Userenv Wininet comsuppw Shlwapi)
add_definitions(
/DWIN32
/D_WINDOWS
/D_CRT_SECURE_NO_WARNINGS
/D_XKEYCHECK_H
)
endif()
configure_qt()
configure_openssl()
configure_coverage()
find_package(Qt6 ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Core Widgets Network)
message(STATUS "Qt version: ${Qt6_VERSION}")
# TODO SSL check can happen in lib/net when don't have to deploy it any longer on windows
# Apple has to use static libraries because "Use of the Apple-provided OpenSSL
# libraries by apps is strongly discouraged."
# https://developer.apple.com/library/archive/documentation/Security/Conceptual/cryptoservices/SecureNetworkCommunicationAPIs/SecureNetworkCommunicationAPIs.html
if(APPLE)
set(OPENSSL_USE_STATIC_LIBS TRUE)
endif()
find_package(OpenSSL ${REQUIRED_OPENSSL_VERSION} REQUIRED COMPONENTS SSL Crypto)
if(WIN32) #Used for dev in TLS and WIX
cmake_path(SET OPENSSL_ROOT_DIR NORMALIZE "${OPENSSL_INCLUDE_DIR}/..")
message(VERBOSE "Set OPENSSL_ROOT_DIR: ${OPENSSL_ROOT_DIR}")
set(OPENSSL_EXE_DIR "${OPENSSL_ROOT_DIR}/tools/openssl")
add_definitions(-DOPENSSL_EXE_DIR="${OPENSSL_EXE_DIR}")
# HACK Install a copy of openssl on windows
install(
FILES
${OPENSSL_EXE_DIR}/openssl.exe
${OPENSSL_EXE_DIR}/openssl.cnf
DESTINATION .
)
endif()
option(ENABLE_COVERAGE "Enable test coverage" OFF)
if(ENABLE_COVERAGE)
message(STATUS "Enabling code coverage")
include(cmake/CodeCoverage.cmake)
append_coverage_compiler_flags()
set(test_exclude subprojects/* build/* src/test/*)
set(test_src ${PROJECT_SOURCE_DIR}/src)
# Apparently solves the bug in gcov where it returns negative counts and confuses gcovr.
# > Got negative hit value in gcov line 'branch 2 taken -1' caused by a bug in gcov tool
# Bug report: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68080
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-update=atomic")
setup_target_for_coverage_gcovr_xml(
NAME coverage-integtests
EXECUTABLE integtests
BASE_DIRECTORY ${test_src}
EXCLUDE ${test_exclude}
)
setup_target_for_coverage_gcovr_xml(
NAME coverage-unittests
EXECUTABLE unittests
BASE_DIRECTORY ${test_src}
EXCLUDE ${test_exclude}
)
endif()
endmacro()
@ -114,11 +172,35 @@ macro(configure_unix_libs)
endif()
if(APPLE)
configure_mac_libs()
set(CMAKE_CXX_FLAGS "--sysroot ${CMAKE_OSX_SYSROOT} ${CMAKE_CXX_FLAGS} -DGTEST_USE_OWN_TR1_TUPLE=1")
find_library(lib_ScreenSaver ScreenSaver)
find_library(lib_IOKit IOKit)
find_library(lib_ApplicationServices ApplicationServices)
find_library(lib_Foundation Foundation)
find_library(lib_Carbon Carbon)
find_library(lib_UserNotifications UserNotifications)
list(APPEND libs
${lib_ScreenSaver} ${lib_IOKit} ${lib_ApplicationServices}
${lib_Foundation} ${lib_Carbon} ${lib_UserNotifications}
)
add_definitions(-DWINAPI_CARBON=1 -D_THREAD_SAFE)
else()
configure_xorg_libs()
configure_wayland_libs()
include(FindPkgConfig)
if(PKG_CONFIG_FOUND)
pkg_check_modules(LIBXKBCOMMON REQUIRED xkbcommon)
pkg_check_modules(GLIB2 REQUIRED glib-2.0 gio-2.0)
find_library(LIBM m)
include_directories(${LIBXKBCOMMON_INCLUDE_DIRS} ${GLIB2_INCLUDE_DIRS}
${LIBM_INCLUDE_DIRS})
else()
message(WARNING "pkg-config not found, skipping wayland libraries")
endif()
find_package(pugixml REQUIRED)
@ -164,53 +246,6 @@ macro(configure_unix_libs)
endmacro()
#
# Apple macOS
#
macro(configure_mac_libs)
set(CMAKE_CXX_FLAGS
"--sysroot ${CMAKE_OSX_SYSROOT} ${CMAKE_CXX_FLAGS} -DGTEST_USE_OWN_TR1_TUPLE=1"
)
find_library(lib_ScreenSaver ScreenSaver)
find_library(lib_IOKit IOKit)
find_library(lib_ApplicationServices ApplicationServices)
find_library(lib_Foundation Foundation)
find_library(lib_Carbon Carbon)
list(
APPEND
libs
${lib_ScreenSaver}
${lib_IOKit}
${lib_ApplicationServices}
${lib_Foundation}
${lib_Carbon})
find_library(lib_UserNotifications UserNotifications)
list(APPEND libs ${lib_UserNotifications})
add_definitions(-DWINAPI_CARBON=1 -D_THREAD_SAFE)
endmacro()
macro(configure_wayland_libs)
include(FindPkgConfig)
if(PKG_CONFIG_FOUND)
pkg_check_modules(LIBXKBCOMMON REQUIRED xkbcommon)
pkg_check_modules(GLIB2 REQUIRED glib-2.0 gio-2.0)
find_library(LIBM m)
include_directories(${LIBXKBCOMMON_INCLUDE_DIRS} ${GLIB2_INCLUDE_DIRS}
${LIBM_INCLUDE_DIRS})
else()
message(WARNING "pkg-config not found, skipping wayland libraries")
endif()
endmacro()
#
# X.org/X11 for Linux, BSD, etc
#
@ -312,101 +347,3 @@ macro(configure_xorg_libs)
add_definitions(-DWINAPI_XWINDOWS=1)
endmacro()
#
# Windows
#
macro(configure_windows_libs)
set(CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} /MP /D _BIND_TO_CURRENT_VCLIBS_VERSION=1")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MD /O2 /Ob2")
list(
APPEND
libs
Wtsapi32
Userenv
Wininet
comsuppw
Shlwapi)
add_definitions(
/DWIN32
/D_WINDOWS
/D_CRT_SECURE_NO_WARNINGS
/DDESKFLOW_VERSION=\"${DESKFLOW_VERSION}\"
/D_XKEYCHECK_H)
configure_openssl()
endmacro()
macro(configure_qt)
find_package(
Qt6
COMPONENTS Core Widgets Network
REQUIRED)
message(STATUS "Qt version: ${Qt6_VERSION}")
endmacro()
macro(configure_openssl)
# Apple has to use static libraries because "Use of the Apple-provided OpenSSL
# libraries by apps is strongly discouraged."
# https://developer.apple.com/library/archive/documentation/Security/Conceptual/cryptoservices/SecureNetworkCommunicationAPIs/SecureNetworkCommunicationAPIs.html
# TODO: How about bundling the OpenSSL .dylib files with the app so they can be updated?
if(APPLE)
set(OPENSSL_USE_STATIC_LIBS TRUE)
endif()
find_package(OpenSSL 3.0 REQUIRED COMPONENTS SSL Crypto)
if(WIN32) #Used for dev in TLS and WIX
cmake_path(SET OPENSSL_ROOT_DIR NORMALIZE "${OPENSSL_INCLUDE_DIR}/..")
message(VERBOSE "Set OPENSSL_ROOT_DIR: ${OPENSSL_ROOT_DIR}")
set(OPENSSL_EXE_DIR "${OPENSSL_ROOT_DIR}/tools/openssl")
add_definitions(-DOPENSSL_EXE_DIR="${OPENSSL_EXE_DIR}")
endif()
endmacro()
macro(configure_coverage)
if(ENABLE_COVERAGE)
message(STATUS "Enabling code coverage")
include(cmake/CodeCoverage.cmake)
append_coverage_compiler_flags()
set(test_exclude subprojects/* build/* src/test/*)
set(test_src ${PROJECT_SOURCE_DIR}/src)
# Apparently solves the bug in gcov where it returns negative counts and confuses gcovr.
# > Got negative hit value in gcov line 'branch 2 taken -1' caused by a bug in gcov tool
# Bug report: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68080
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-update=atomic")
setup_target_for_coverage_gcovr_xml(
NAME
coverage-${INTEG_TESTS_BIN}
EXECUTABLE
${INTEG_TESTS_BIN}
BASE_DIRECTORY
${test_src}
EXCLUDE
${test_exclude})
setup_target_for_coverage_gcovr_xml(
NAME
coverage-${UNIT_TESTS_BIN}
EXECUTABLE
${UNIT_TESTS_BIN}
BASE_DIRECTORY
${test_src}
EXCLUDE
${test_exclude})
else()
message(STATUS "Code coverage is disabled")
endif()
endmacro()

View File

@ -1,235 +0,0 @@
# SPDX-FileCopyrightText: (C) 2024 Chris Rizzitello <sithlord48@gmail.com>
# SPDX-FileCopyrightText: (C) 2012 - 2024 Symless Ltd.
# SPDX-FileCopyrightText: (C) 2009 - 2012 Nick Bolton
# SPDX-License-Identifier: MIT
#
# If enabled, configure packaging based on OS.
#
macro(configure_packaging)
message(VERBOSE "Configuring Packaging")
set(DESKFLOW_PROJECT_RES_DIR ${PROJECT_SOURCE_DIR}/res)
if(${BUILD_INSTALLER})
set(CPACK_PACKAGE_NAME ${CMAKE_PROJECT_NAME})
set(CPACK_PACKAGE_CONTACT "Deskflow <maintainers@deskflow.org>")
set(CPACK_PACKAGE_DESCRIPTION ${CMAKE_PROJECT_DESCRIPTION})
set(CPACK_PACKAGE_VENDOR "Deskflow")
set(CPACK_RESOURCE_FILE_LICENSE ${PROJECT_SOURCE_DIR}/LICENSE)
set(CPACK_PACKAGE_VERSION ${CMAKE_PROJECT_VERSION})
#Prevent this override from being written in the package
if(NOT PACKAGE_VERSION_LABEL)
set (PACKAGE_VERSION_LABEL "${CPACK_PACKAGE_VERSION}")
endif()
if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
configure_windows_packaging()
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
configure_mac_packaging()
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
configure_linux_packaging()
elseif(${CMAKE_SYSTEM_NAME} MATCHES "|.*BSD")
message(STATUS "BSD packaging not yet supported")
set(OS_STRING ${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR})
endif()
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${PACKAGE_VERSION_LABEL}-${OS_STRING}")
message(STATUS "Package Basename: ${CPACK_PACKAGE_FILE_NAME}")
include(CPack)
else()
message(STATUS "Not configuring installer")
endif()
endmacro()
#
# Windows installer
#
macro(configure_windows_packaging)
cmake_path(SET QT_PATH NORMALIZE "${Qt6_DIR}../../")
set(DESKFLOW_MSI_64_GUID
"027D1C8A-E7A5-4754-BB93-B2D45BFDBDC8"
CACHE STRING "GUID for 64-bit MSI installer")
set(DESKFLOW_MSI_32_GUID
"8F57C657-BC87-45E6-840E-41242A93511C"
CACHE STRING "GUID for 32-bit MSI installer")
configure_files(${PROJECT_SOURCE_DIR}/deploy/dist/wix
${PROJECT_BINARY_DIR}/installer)
if(CMAKE_SYSTEM_PROCESSOR MATCHES AMD64)
set(OS_STRING "win-x64")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES ARM64)
set(OS_STRING "win-arm64")
else()
set(OS_STRING "win-${CMAKE_SYSTEM_PROCESSOR}")
endif()
endmacro()
#
# macOS app bundle
#
macro(configure_mac_packaging)
set(CMAKE_INSTALL_RPATH
"@loader_path/../Libraries;@loader_path/../Frameworks")
set(DESKFLOW_BUNDLE_SOURCE_DIR
${PROJECT_SOURCE_DIR}/deploy/dist/mac/bundle
CACHE PATH "Path to the macOS app bundle")
set(DESKFLOW_BUNDLE_DIR ${PROJECT_BINARY_DIR}/bundle/Deskflow.app)
set(DESKFLOW_BUNDLE_BINARY_DIR ${DESKFLOW_BUNDLE_DIR}/Contents/MacOS)
configure_files(${DESKFLOW_BUNDLE_SOURCE_DIR} ${DESKFLOW_BUNDLE_DIR})
set(OS_STRING "macos-${CMAKE_SYSTEM_PROCESSOR}")
endmacro()
#
# Linux packages
#
macro(configure_linux_package_name)
# Get Distro name information
execute_process(
COMMAND bash "-c" "cat /etc/os-release | grep ^ID= | sed 's/ID=//g'"
OUTPUT_VARIABLE _DISTRO_NAME
OUTPUT_STRIP_TRAILING_WHITESPACE)
string(REPLACE "\"" "" DISTRO_NAME "${_DISTRO_NAME}")
message(STATUS "Distro Name: ${DISTRO_NAME}")
execute_process(
COMMAND bash "-c"
"cat /etc/os-release | grep ^ID_LIKE= | sed 's/ID_LIKE=//g'"
OUTPUT_VARIABLE _DISTRO_LIKE
OUTPUT_STRIP_TRAILING_WHITESPACE)
string(REPLACE "\"" "" DISTRO_LIKE "${_DISTRO_LIKE}")
message(STATUS "Distro Like: ${DISTRO_LIKE}")
execute_process(
COMMAND
bash "-c"
"cat /etc/os-release | grep ^VERSION_CODENAME= | sed 's/VERSION_CODENAME=//g'"
OUTPUT_VARIABLE _DISTRO_CODENAME
OUTPUT_STRIP_TRAILING_WHITESPACE)
string(REPLACE "\"" "" DISTRO_CODENAME "${_DISTRO_CODENAME}")
message(STATUS "Distro Codename: ${DISTRO_CODENAME}")
execute_process(
COMMAND bash "-c"
"cat /etc/os-release | grep ^VERSION_ID= | sed 's/VERSION_ID=//g'"
OUTPUT_VARIABLE _DISTRO_VERSION_ID
OUTPUT_STRIP_TRAILING_WHITESPACE)
string(REPLACE "\"" "" DISTRO_VERSION_ID "${_DISTRO_VERSION_ID}")
message(STATUS "Distro ID: ${DISTRO_VERSION_ID}")
# Check if Debian-link
string(REGEX MATCH debian|buntu DEBTYPE "${DISTRO_LIKE}")
if((NOT ("${DEBTYPE}" STREQUAL "")) OR ("${DISTRO_NAME}" STREQUAL "debian"))
set(CPACK_GENERATOR "DEB")
endif()
# Check if Rpm-like
string(REGEX MATCH suse|fedora|rhel RPMTYPE "${DISTRO_LIKE}")
string(REGEX MATCH fedora|suse|rhel RPMNAME "${DISTRO_NAME}")
if((NOT ("${RPMTYPE}" STREQUAL "")) OR (NOT ("${RPMNAME}" STREQUAL "")))
set(CPACK_GENERATOR "RPM")
endif()
# Disto specific name adjustments
if("${DISTRO_NAME}" STREQUAL "opensuse-tumbleweed")
set(DISTRO_NAME "opensuse")
set(DISTRO_CODENAME "tumbleweed")
elseif("${DISTRO_NAME}" STREQUAL "arch")
# Arch linux is rolling the version id reported is the date of last iso.
set(DISTRO_VERSION_ID "")
endif()
# Determain the code name to be used if any
if(NOT "${DISTRO_VERSION_ID}" STREQUAL "")
set(CN_STRING "${DISTRO_VERSION_ID}-")
endif()
if(NOT "${DISTRO_CODENAME}" STREQUAL "")
set(CN_STRING "${DISTRO_CODENAME}-")
endif()
set(OS_STRING "${DISTRO_NAME}-${CN_STRING}${CMAKE_SYSTEM_PROCESSOR}")
endmacro()
macro(configure_linux_packaging)
# Gather distro info
# This is used in package names
configure_linux_package_name()
set(CPACK_DEBIAN_PACKAGE_SECTION "utils")
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_RPM_PACKAGE_LICENSE "GPLv2")
set(CPACK_RPM_PACKAGE_GROUP "Applications/System")
# HACK: The GUI depends on the Qt6 QPA plugins package, but that's not picked
# up by shlibdeps on Ubuntu 22 (though not a problem on Ubuntu 24 and Debian
# 12), so we must add it manually.
set(CPACK_DEBIAN_PACKAGE_DEPENDS "qt6-qpa-plugins")
endmacro()
#
# Same as the `configure_file` command but for directories recursively.
#
macro(configure_files srcDir destDir)
message(VERBOSE "Configuring directory ${destDir}")
make_directory(${destDir})
file(
GLOB_RECURSE sourceFiles
RELATIVE ${srcDir}
${srcDir}/*)
file(
GLOB_RECURSE templateFiles
LIST_DIRECTORIES false
RELATIVE ${srcDir}
${srcDir}/*.in)
list(REMOVE_ITEM sourceFiles ${templateFiles})
foreach(sourceFile ${sourceFiles})
set(sourceFilePath ${srcDir}/${sourceFile})
if(IS_DIRECTORY ${sourceFilePath})
message(VERBOSE "Copying directory ${sourceFile}")
make_directory(${destDir}/${sourceFile})
else()
message(VERBOSE "Copying file ${sourceFile}")
configure_file(${sourceFilePath} ${destDir}/${sourceFile} COPYONLY)
endif()
endforeach(sourceFile)
foreach(templateFile ${templateFiles})
set(sourceTemplateFilePath ${srcDir}/${templateFile})
string(REGEX REPLACE "\.in$" "" templateFile ${templateFile})
message(VERBOSE "Configuring file ${templateFile}")
configure_file(${sourceTemplateFilePath} ${destDir}/${templateFile} @ONLY)
endforeach(templateFile)
endmacro(configure_files)
macro(check_is_rpm)
# Check if RPM-like.
endmacro()

View File

@ -1,41 +1,41 @@
# SPDX-FileCopyrightText: 2024 Chris Rizzitello <sithlord48@gmail.com>
# SPDX-License-Identifier: MIT
# Handle Pre install Items
if(WIN32)
# Copy License with txt ext for picky package creation tools
file(COPY_FILE
${CMAKE_SOURCE_DIR}/LICENSE
${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt
ONLY_IF_DIFFERENT
)
# Configure the windows version rc file
configure_file(
${CMAKE_CURRENT_LIST_DIR}/version.rc.in
${PROJECT_BINARY_DIR}/src/version.rc @ONLY
)
elseif(NOT APPLE)
# Generic Package Items
set(CPACK_STRIP_FILES TRUE)
set(CPACK_PACKAGE_NAME ${CMAKE_PROJECT_NAME})
set(CPACK_PACKAGE_CONTACT "Deskflow <maintainers@deskflow.org>")
set(CPACK_PACKAGE_DESCRIPTION ${CMAKE_PROJECT_DESCRIPTION})
set(CPACK_PACKAGE_VENDOR "Deskflow")
set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt)
# Install our desktop file
install(
FILES ${CMAKE_CURRENT_LIST_DIR}/org.deskflow.deskflow.desktop
DESTINATION share/applications
)
# Install our icon
install(
FILES ${CMAKE_CURRENT_LIST_DIR}/deskflow.png
DESTINATION share/icons/hicolor/512x512/apps/
RENAME org.deskflow.deskflow.png
)
# Install our metainfo
install(
FILES ${CMAKE_CURRENT_SOURCE_DIR}/org.deskflow.deskflow.metainfo.xml
DESTINATION share/metainfo/
)
# Prepare PKGBUILD for Arch Linux
configure_file(
${CMAKE_CURRENT_LIST_DIR}/dist/arch/PKGBUILD.in
${CMAKE_BINARY_DIR}/PKGBUILD
@ONLY
)
set(CPACK_PACKAGE_VERSION ${CMAKE_PROJECT_VERSION})
#Prevent this override from being written in the package
if(NOT PACKAGE_VERSION_LABEL)
set (PACKAGE_VERSION_LABEL "${CPACK_PACKAGE_VERSION}")
endif()
if(WIN32)
include(windows/deploy.cmake)
elseif(UNIX AND NOT APPLE)
include(linux/deploy.cmake)
elseif(APPLE)
include(mac/deploy.cmake)
else()
message(STATUS "UNKNOWN System: ${CMAKE_SYSTEM_NAME}")
endif()
# Always use "deskflow" for start of name
set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${PACKAGE_VERSION_LABEL}-${OS_STRING}")
message(STATUS "Package Basename: ${CPACK_PACKAGE_FILE_NAME}")
include(CPack)

View File

@ -1,31 +0,0 @@
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDisplayName</key>
<string>Deskflow</string>
<key>CFBundleExecutable</key>
<string>deskflow</string>
<key>CFBundleIconFile</key>
<string>Deskflow.icns</string>
<key>CFBundleIdentifier</key>
<string>org.deskflow.deskflow</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Deskflow</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>DFLW</string>
<key>CFBundleShortVersionString</key>
<string>@DESKFLOW_VERSION@</string>
<key>CFBundleVersion</key>
<string>@DESKFLOW_VERSION@</string>
<key>NSHumanReadableCopyright</key>
<string>© 2024 Deskflow Developers</string>
<key>LSMinimumSystemVersion</key>
<string>10.9.0</string>
</dict>
</plist>

View File

@ -1 +0,0 @@
APPLDFLW

View File

@ -1,69 +0,0 @@
# -*- coding: utf-8 -*-
# Example: https://dmgbuild.readthedocs.io/en/latest/example.html
from __future__ import unicode_literals
import os.path
app = defines.get("app")
app_basename = os.path.basename(app)
format = defines.get("format", "UDBZ")
size = defines.get("size", None)
files = [app]
symlinks = {"Applications": "/Applications"}
icon = os.path.join(app, "Contents/Resources/Volume.icns")
icon_locations = {
app_basename: (144, 190),
"Applications": (455, 190),
}
background = os.path.join(app, "Contents/Resources/Background.tiff")
show_status_bar = False
show_tab_view = False
show_toolbar = False
show_pathbar = False
show_sidebar = False
sidebar_width = 180
window_rect = ((200, 120), (620, 420))
default_view = "icon-view"
show_icon_preview = False
include_icon_view_settings = "auto"
include_list_view_settings = "auto"
arrange_by = None
grid_offset = (0, 0)
grid_spacing = 100
scroll_position = (0, 0)
label_pos = "bottom"
text_size = 16
icon_size = 100
list_icon_size = 16
list_text_size = 12
list_scroll_position = (0, 0)
list_sort_by = "name"
list_use_relative_dates = True
list_calculate_all_sizes = (False,)
list_columns = ("name", "date-modified", "size", "kind", "date-added")
list_column_widths = {
"name": 300,
"date-modified": 181,
"date-created": 181,
"date-added": 181,
"date-last-opened": 181,
"size": 97,
"kind": 115,
"label": 100,
"version": 75,
"comments": 300,
}
list_column_sort_directions = {
"name": "ascending",
"date-modified": "descending",
"date-created": "descending",
"date-added": "descending",
"date-last-opened": "descending",
"size": "descending",
"kind": "ascending",
"label": "ascending",
"version": "ascending",
"comments": "ascending",
}

View File

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<UI>
<Dialog Id="AppBrowseDlg" Width="370" Height="270" Title="!(loc.BrowseDlg_Title)">
<Control Id="PathEdit" Type="PathEdit" X="25" Y="202" Width="320" Height="18" Property="_BrowseProperty" Indirect="yes" />
<Control Id="OK" Type="PushButton" X="240" Y="243" Width="56" Height="17" Default="yes" Text="!(loc.WixUIOK)">
<Publish Event="SetTargetPath" Value="[_BrowseProperty]">1</Publish>
<Publish Event="EndDialog" Value="Return">1</Publish>
</Control>
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="!(loc.WixUICancel)">
<Publish Event="Reset" Value="0">1</Publish>
<Publish Event="EndDialog" Value="Return">1</Publish>
</Control>
<Control Id="ComboLabel" Type="Text" X="25" Y="58" Width="44" Height="10" TabSkip="no" Text="!(loc.BrowseDlgComboLabel)" />
<Control Id="DirectoryCombo" Type="DirectoryCombo" X="70" Y="55" Width="220" Height="80" Property="_BrowseProperty" Indirect="yes" Fixed="yes" Remote="yes">
<Subscribe Event="IgnoreChange" Attribute="IgnoreChange" />
</Control>
<Control Id="WixUI_Bmp_Up" Type="PushButton" X="298" Y="55" Width="19" Height="19" ToolTip="!(loc.BrowseDlgWixUI_Bmp_UpTooltip)" Icon="yes" FixedSize="yes" IconSize="16" Text="!(loc.BrowseDlgWixUI_Bmp_Up)">
<Publish Event="DirectoryListUp" Value="0">1</Publish>
</Control>
<Control Id="NewFolder" Type="PushButton" X="325" Y="55" Width="19" Height="19" ToolTip="!(loc.BrowseDlgNewFolderTooltip)" Icon="yes" FixedSize="yes" IconSize="16" Text="!(loc.BrowseDlgNewFolder)">
<Publish Event="DirectoryListNew" Value="0">1</Publish>
</Control>
<Control Id="DirectoryList" Type="DirectoryList" X="25" Y="83" Width="320" Height="98" Property="_BrowseProperty" Sunken="yes" Indirect="yes" TabSkip="no" />
<Control Id="PathLabel" Type="Text" X="25" Y="190" Width="320" Height="10" TabSkip="no" Text="!(loc.BrowseDlgPathLabel)" />
<Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="!(loc.BrowseDlgBannerBitmap)" />
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
<Control Id="Title" Type="Text" X="15" Y="15" Width="200" Height="15" Transparent="yes" NoPrefix="yes" Text="!(loc.BrowseDlgTitle)" />
</Dialog>
</UI>
</Fragment>
</Wix>

View File

@ -1,72 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
First-time install dialog sequence:
- WixUI_WelcomeDlg
- WixUI_LicenseAgreementDlg
- WixUI_InstallDirDlg
- WixUI_VerifyReadyDlg
- WixUI_DiskCostDlg
Maintenance dialog sequence:
- WixUI_MaintenanceWelcomeDlg
- WixUI_MaintenanceTypeDlg
- WixUI_InstallDirDlg
- WixUI_VerifyReadyDlg
Patch dialog sequence:
- WixUI_WelcomeDlg
- WixUI_VerifyReadyDlg
-->
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<UI Id="AppDlgSequence">
<TextStyle Id="WixUI_Font_Normal" FaceName="Roboto" Size="9" />
<TextStyle Id="WixUI_Font_Bigger" FaceName="Roboto" Size="14" />
<TextStyle Id="WixUI_Font_Title" FaceName="Roboto" Size="12" Bold="yes" Blue="255" Red="255" Green="255"/>
<Property Id="DefaultUIFont" Value="WixUI_Font_Normal" />
<Property Id="WixUI_Mode" Value="InstallDir" />
<DialogRef Id="BrowseDlg" />
<DialogRef Id="DiskCostDlg" />
<DialogRef Id="ErrorDlg" />
<DialogRef Id="FatalError" />
<DialogRef Id="FilesInUse" />
<DialogRef Id="MsiRMFilesInUse" />
<DialogRef Id="PrepareDlg" />
<DialogRef Id="ProgressDlg" />
<DialogRef Id="ResumeDlg" />
<DialogRef Id="UserExit" />
<Publish Dialog="AppBrowseDlg" Control="OK" Event="DoAction" Value="WixUIValidatePath" Order="3">1</Publish>
<Publish Dialog="AppBrowseDlg" Control="OK" Event="SpawnDialog" Value="InvalidDirDlg" Order="4"><![CDATA[WIXUI_INSTALLDIR_VALID<>"1"]]></Publish>
<Publish Dialog="ExitDialog" Control="Finish" Event="EndDialog" Value="Return" Order="999">1</Publish>
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="StartGui">NOT Installed</Publish>
<Publish Dialog="AppWelcome" Control="Next" Event="NewDialog" Value="AppInstallDirDlg" Order="1">1</Publish>
<Publish Dialog="AppWelcome" Control="Next" Event="NewDialog" Value="AppVerifyReadyDlg">Installed AND PATCH</Publish>
<Publish Dialog="AppInstallDirDlg" Control="Next" Event="SetTargetPath" Value="[WIXUI_INSTALLDIR]" Order="1">1</Publish>
<Publish Dialog="AppInstallDirDlg" Control="Next" Event="DoAction" Value="WixUIValidatePath" Order="2">NOT WIXUI_DONTVALIDATEPATH</Publish>
<Publish Dialog="AppInstallDirDlg" Control="Next" Event="SpawnDialog" Value="InvalidDirDlg" Order="3"><![CDATA[NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"]]></Publish>
<Publish Dialog="AppInstallDirDlg" Control="Next" Event="NewDialog" Value="AppVerifyReadyDlg" Order="4">WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1"</Publish>
<Publish Dialog="AppInstallDirDlg" Control="ChangeFolder" Property="_BrowseProperty" Value="[WIXUI_INSTALLDIR]" Order="1">1</Publish>
<Publish Dialog="AppInstallDirDlg" Control="ChangeFolder" Event="SpawnDialog" Value="AppBrowseDlg" Order="2">1</Publish>
<Publish Dialog="AppInstallDirDlg" Control="Back" Event="NewDialog" Value="AppWelcome" Order="2">1</Publish>
<Publish Dialog="AppVerifyReadyDlg" Control="Back" Event="NewDialog" Value="AppInstallDirDlg" Order="1">NOT Installed</Publish>
<Publish Dialog="AppVerifyReadyDlg" Control="Back" Event="NewDialog" Value="AppMaintenanceTypeDlg" Order="2">Installed AND NOT PATCH</Publish>
<Publish Dialog="AppVerifyReadyDlg" Control="Back" Event="NewDialog" Value="AppInstallDirDlg" Order="2">Installed AND PATCH</Publish>
<Publish Dialog="MaintenanceWelcomeDlg" Control="Next" Event="NewDialog" Value="AppMaintenanceTypeDlg">1</Publish>
<Publish Dialog="AppMaintenanceTypeDlg" Control="RepairButton" Event="NewDialog" Value="AppVerifyReadyDlg">1</Publish>
<Publish Dialog="AppMaintenanceTypeDlg" Control="RemoveButton" Event="NewDialog" Value="AppVerifyReadyDlg">1</Publish>
<Publish Dialog="AppMaintenanceTypeDlg" Control="Back" Event="NewDialog" Value="MaintenanceWelcomeDlg">1</Publish>
<Property Id="ARPNOMODIFY" Value="1" />
</UI>
<UIRef Id="WixUI_Common" />
</Fragment>
</Wix>

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?include Include.wxi?>
<Fragment>
<UI Id="AppInstallDirDlg">
<Dialog Id="AppInstallDirDlg" Width="370" Height="270" Title="!(loc.InstallDirDlg_Title)">
<Control Id="Title" Type="Text" X="15" Y="15" Width="200" Height="15" Transparent="yes" NoPrefix="yes" Text="!(loc.InstallDirDlgTitle)" />
<Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="!(loc.WixUINext)" />
<Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="!(loc.WixUIBack)" />
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="!(loc.WixUICancel)">
<Publish Event="SpawnDialog" Value="CancelDlg" />
</Control>
<Control Id="InstallDirDlgBackground" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="no" Text="[CommonBackground]" />
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
<Control Id="FolderLabel" Transparent="yes" Type="Text" X="20" Y="70" Width="290" Height="30" NoPrefix="yes" Text="!(loc.InstallDirDlgFolderLabel)" />
<Control Id="Folder" Type="PathEdit" X="20" Y="90" Width="320" Height="18" Property="WIXUI_INSTALLDIR" Indirect="yes" />
<Control Id="ChangeFolder" Type="PushButton" X="20" Y="112" Width="56" Height="17" Text="!(loc.InstallDirDlgChange)" />
</Dialog>
</UI>
</Fragment>
</Wix>

View File

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) Microsoft Corporation. All rights reserved.
The use and distribution terms for this software are covered by the
Common Public License 1.0 (http://opensource.org/licenses/cpl1.0.php)
which can be found in the file CPL.TXT at the root of this distribution.
By using this software in any fashion, you are agreeing to be bound by
the terms of this license.
You must not remove this notice, or any other, from this software.
-->
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<UI>
<Dialog Id="AppMaintenanceTypeDlg" Width="370" Height="270" Title="!(loc.MaintenanceTypeDlg_Title)">
<Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="no" Text="[CommonBackground]" />
<Control Id="ChangeButton" Type="PushButton" X="40" Y="65" Width="80" Height="17" ToolTip="!(loc.MaintenanceTypeDlgChangeButtonTooltip)" Default="yes" Text="!(loc.MaintenanceTypeDlgChangeButton)">
<Publish Property="WixUI_InstallMode" Value="Change">1</Publish>
<Condition Action="disable">ARPNOMODIFY</Condition>
</Control>
<Control Id="ChangeText" Transparent="yes" Type="Text" X="60" Y="85" Width="280" Height="20" Text="!(loc.MaintenanceTypeDlgChangeText)">
<Condition Action="hide">ARPNOMODIFY</Condition>
</Control>
<Control Id="ChangeDisabledText" Transparent="yes" Type="Text" X="60" Y="85" Width="280" Height="20" NoPrefix="yes" Text="!(loc.MaintenanceTypeDlgChangeDisabledText)" Hidden="yes">
<Condition Action="show">ARPNOMODIFY</Condition>
</Control>
<Control Id="RepairButton" Type="PushButton" X="40" Y="118" Width="80" Height="17" ToolTip="!(loc.MaintenanceTypeDlgRepairButtonTooltip)" Text="!(loc.MaintenanceTypeDlgRepairButton)">
<Publish Property="WixUI_InstallMode" Value="Repair">1</Publish>
<Condition Action="disable">ARPNOREPAIR</Condition>
</Control>
<Control Id="RepairText" Transparent="yes" Type="Text" X="60" Y="138" Width="280" Height="30" Text="!(loc.MaintenanceTypeDlgRepairText)">
<Condition Action="hide">ARPNOREPAIR</Condition>
</Control>
<Control Id="RepairDisabledText" Transparent="yes" Type="Text" X="60" Y="138" Width="280" Height="30" NoPrefix="yes" Text="!(loc.MaintenanceTypeDlgRepairDisabledText)" Hidden="yes">
<Condition Action="show">ARPNOREPAIR</Condition>
</Control>
<Control Id="RemoveButton" Type="PushButton" X="40" Y="171" Width="80" Height="17" ToolTip="!(loc.MaintenanceTypeDlgRemoveButtonTooltip)" Text="!(loc.MaintenanceTypeDlgRemoveButton)">
<Publish Property="WixUI_InstallMode" Value="Remove">1</Publish>
<Condition Action="disable">ARPNOREMOVE</Condition>
</Control>
<Control Id="RemoveText" Transparent="yes" Type="Text" X="60" Y="191" Width="280" Height="20" NoPrefix="yes" Text="!(loc.MaintenanceTypeDlgRemoveText)">
<Condition Action="hide">ARPNOREMOVE</Condition>
</Control>
<Control Id="RemoveDisabledText" Transparent="yes" Type="Text" X="60" Y="191" Width="280" Height="20" NoPrefix="yes" Text="!(loc.MaintenanceTypeDlgRemoveDisabledText)" Hidden="yes">
<Condition Action="show">ARPNOREMOVE</Condition>
</Control>
<Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="!(loc.WixUIBack)" />
<Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Disabled="yes" Text="!(loc.WixUINext)" />
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="!(loc.WixUICancel)">
<Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
</Control>
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
<Control Id="Title" Type="Text" X="15" Y="15" Width="340" Height="15" Transparent="yes" NoPrefix="yes" Text="!(loc.MaintenanceTypeDlgTitle)" />
</Dialog>
</UI>
</Fragment>
</Wix>

View File

@ -1,140 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?include Include.wxi?>
<Fragment>
<UI>
<Dialog Id="AppVerifyReadyDlg" Width="370" Height="270" Title="!(loc.VerifyReadyDlg_Title)" TrackDiskSpace="yes">
<Control Id="Install" Type="PushButton" ElevationShield="yes" X="212" Y="243" Width="80" Height="17" Default="yes" Hidden="yes" Disabled="yes" Text="!(loc.VerifyReadyDlgInstall)">
<Condition Action="show">NOT Installed AND ALLUSERS</Condition>
<Condition Action="enable">NOT Installed</Condition>
<Condition Action="default">NOT Installed</Condition>
<Publish Event="EndDialog" Value="Return"><![CDATA[OutOfDiskSpace <> 1]]></Publish>
<Publish Event="SpawnDialog" Value="OutOfRbDiskDlg">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND (PROMPTROLLBACKCOST="P" OR NOT PROMPTROLLBACKCOST)</Publish>
<Publish Event="EndDialog" Value="Return">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"</Publish>
<Publish Event="EnableRollback" Value="False">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"</Publish>
<Publish Event="SpawnDialog" Value="OutOfDiskDlg">(OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 1) OR (OutOfDiskSpace = 1 AND PROMPTROLLBACKCOST="F")</Publish>
</Control>
<Control Id="InstallNoShield" Type="PushButton" ElevationShield="no" X="212" Y="243" Width="80" Height="17" Default="yes" Hidden="yes" Disabled="yes" Text="!(loc.VerifyReadyDlgInstall)">
<Condition Action="show">NOT Installed AND NOT ALLUSERS</Condition>
<Condition Action="enable">NOT Installed</Condition>
<Condition Action="default">NOT Installed</Condition>
<Publish Event="EndDialog" Value="Return"><![CDATA[OutOfDiskSpace <> 1]]></Publish>
<Publish Event="SpawnDialog" Value="OutOfRbDiskDlg">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND (PROMPTROLLBACKCOST="P" OR NOT PROMPTROLLBACKCOST)</Publish>
<Publish Event="EndDialog" Value="Return">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"</Publish>
<Publish Event="EnableRollback" Value="False">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"</Publish>
<Publish Event="SpawnDialog" Value="OutOfDiskDlg">(OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 1) OR (OutOfDiskSpace = 1 AND PROMPTROLLBACKCOST="F")</Publish>
</Control>
<Control Id="Change" Type="PushButton" ElevationShield="yes" X="212" Y="243" Width="80" Height="17" Default="yes" Hidden="yes" Disabled="yes" Text="!(loc.VerifyReadyDlgChange)">
<Condition Action="show">WixUI_InstallMode = "Change" AND ALLUSERS AND (ADDLOCAL OR REMOVE)</Condition>
<Condition Action="enable">WixUI_InstallMode = "Change"</Condition>
<Condition Action="default">WixUI_InstallMode = "Change"</Condition>
<Publish Event="EndDialog" Value="Return"><![CDATA[OutOfDiskSpace <> 1]]></Publish>
<Publish Event="SpawnDialog" Value="OutOfRbDiskDlg">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND (PROMPTROLLBACKCOST="P" OR NOT PROMPTROLLBACKCOST)</Publish>
<Publish Event="EndDialog" Value="Return">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"</Publish>
<Publish Event="EnableRollback" Value="False">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"</Publish>
<Publish Event="SpawnDialog" Value="OutOfDiskDlg">(OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 1) OR (OutOfDiskSpace = 1 AND PROMPTROLLBACKCOST="F")</Publish>
</Control>
<Control Id="ChangeNoShield" Type="PushButton" ElevationShield="no" X="212" Y="243" Width="80" Height="17" Default="yes" Hidden="yes" Disabled="yes" Text="!(loc.VerifyReadyDlgChange)">
<Condition Action="show">WixUI_InstallMode = "Change" AND (NOT ALLUSERS OR (NOT ADDLOCAL AND NOT REMOVE))</Condition>
<Condition Action="enable">WixUI_InstallMode = "Change"</Condition>
<Condition Action="default">WixUI_InstallMode = "Change"</Condition>
<Publish Event="EndDialog" Value="Return"><![CDATA[OutOfDiskSpace <> 1]]></Publish>
<Publish Event="SpawnDialog" Value="OutOfRbDiskDlg">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND (PROMPTROLLBACKCOST="P" OR NOT PROMPTROLLBACKCOST)</Publish>
<Publish Event="EndDialog" Value="Return">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"</Publish>
<Publish Event="EnableRollback" Value="False">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"</Publish>
<Publish Event="SpawnDialog" Value="OutOfDiskDlg">(OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 1) OR (OutOfDiskSpace = 1 AND PROMPTROLLBACKCOST="F")</Publish>
</Control>
<Control Id="Repair" Type="PushButton" X="212" Y="243" Width="80" Height="17" Default="yes" Hidden="yes" Disabled="yes" Text="!(loc.VerifyReadyDlgRepair)">
<Condition Action="show">WixUI_InstallMode = "Repair"</Condition>
<Condition Action="enable">WixUI_InstallMode = "Repair"</Condition>
<Condition Action="default">WixUI_InstallMode = "Repair"</Condition>
<Publish Event="ReinstallMode" Value="ecmus"><![CDATA[OutOfDiskSpace <> 1]]></Publish>
<Publish Event="Reinstall" Value="All"><![CDATA[OutOfDiskSpace <> 1]]></Publish>
<Publish Event="EndDialog" Value="Return"><![CDATA[OutOfDiskSpace <> 1]]></Publish>
<Publish Event="SpawnDialog" Value="OutOfRbDiskDlg">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND (PROMPTROLLBACKCOST="P" OR NOT PROMPTROLLBACKCOST)</Publish>
<Publish Event="EndDialog" Value="Return">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"</Publish>
<Publish Event="EnableRollback" Value="False">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"</Publish>
<Publish Event="SpawnDialog" Value="OutOfDiskDlg">(OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 1) OR (OutOfDiskSpace = 1 AND PROMPTROLLBACKCOST="F")</Publish>
</Control>
<Control Id="Remove" Type="PushButton" ElevationShield="yes" X="212" Y="243" Width="80" Height="17" Hidden="yes" Disabled="yes" Text="!(loc.VerifyReadyDlgRemove)">
<Condition Action="show">WixUI_InstallMode = "Remove" AND ALLUSERS</Condition>
<Condition Action="enable">WixUI_InstallMode = "Remove"</Condition>
<Publish Event="Remove" Value="All"><![CDATA[OutOfDiskSpace <> 1]]></Publish>
<Publish Event="EndDialog" Value="Return"><![CDATA[OutOfDiskSpace <> 1]]></Publish>
<Publish Event="SpawnDialog" Value="OutOfRbDiskDlg">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND (PROMPTROLLBACKCOST="P" OR NOT PROMPTROLLBACKCOST)</Publish>
<Publish Event="EndDialog" Value="Return">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"</Publish>
<Publish Event="EnableRollback" Value="False">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"</Publish>
<Publish Event="SpawnDialog" Value="OutOfDiskDlg">(OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 1) OR (OutOfDiskSpace = 1 AND PROMPTROLLBACKCOST="F")</Publish>
</Control>
<Control Id="RemoveNoShield" Type="PushButton" ElevationShield="no" X="212" Y="243" Width="80" Height="17" Hidden="yes" Disabled="yes" Text="!(loc.VerifyReadyDlgRemove)">
<Condition Action="show">WixUI_InstallMode = "Remove" AND NOT ALLUSERS</Condition>
<Condition Action="enable">WixUI_InstallMode = "Remove"</Condition>
<Publish Event="Remove" Value="All"><![CDATA[OutOfDiskSpace <> 1]]></Publish>
<Publish Event="EndDialog" Value="Return"><![CDATA[OutOfDiskSpace <> 1]]></Publish>
<Publish Event="SpawnDialog" Value="OutOfRbDiskDlg">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND (PROMPTROLLBACKCOST="P" OR NOT PROMPTROLLBACKCOST)</Publish>
<Publish Event="EndDialog" Value="Return">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"</Publish>
<Publish Event="EnableRollback" Value="False">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"</Publish>
<Publish Event="SpawnDialog" Value="OutOfDiskDlg">(OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 1) OR (OutOfDiskSpace = 1 AND PROMPTROLLBACKCOST="F")</Publish>
</Control>
<Control Id="Update" Type="PushButton" ElevationShield="yes" X="212" Y="243" Width="80" Height="17" Hidden="yes" Disabled="yes" Text="!(loc.VerifyReadyDlgUpdate)">
<Condition Action="show">WixUI_InstallMode = "Update" AND ALLUSERS</Condition>
<Condition Action="enable">WixUI_InstallMode = "Update"</Condition>
<Publish Event="EndDialog" Value="Return"><![CDATA[OutOfDiskSpace <> 1]]></Publish>
<Publish Event="SpawnDialog" Value="OutOfRbDiskDlg">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND (PROMPTROLLBACKCOST="P" OR NOT PROMPTROLLBACKCOST)</Publish>
<Publish Event="EndDialog" Value="Return">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"</Publish>
<Publish Event="EnableRollback" Value="False">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"</Publish>
<Publish Event="SpawnDialog" Value="OutOfDiskDlg">(OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 1) OR (OutOfDiskSpace = 1 AND PROMPTROLLBACKCOST="F")</Publish>
</Control>
<Control Id="UpdateNoShield" Type="PushButton" ElevationShield="no" X="212" Y="243" Width="80" Height="17" Hidden="yes" Disabled="yes" Text="!(loc.VerifyReadyDlgUpdate)">
<Condition Action="show">WixUI_InstallMode = "Update" AND NOT ALLUSERS</Condition>
<Condition Action="enable">WixUI_InstallMode = "Update"</Condition>
<Publish Event="EndDialog" Value="Return"><![CDATA[OutOfDiskSpace <> 1]]></Publish>
<Publish Event="SpawnDialog" Value="OutOfRbDiskDlg">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND (PROMPTROLLBACKCOST="P" OR NOT PROMPTROLLBACKCOST)</Publish>
<Publish Event="EndDialog" Value="Return">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"</Publish>
<Publish Event="EnableRollback" Value="False">OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"</Publish>
<Publish Event="SpawnDialog" Value="OutOfDiskDlg">(OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 1) OR (OutOfDiskSpace = 1 AND PROMPTROLLBACKCOST="F")</Publish>
</Control>
<Control Id="InstallTitle" Type="Text" X="15" Y="15" Width="300" Height="15" Transparent="yes" NoPrefix="yes" Hidden="yes" Text="{\WixUI_Font_Title}Ready to install">
<Condition Action="show">NOT Installed</Condition>
</Control>
<Control Id="InstallText" Transparent="yes" Type="Text" X="25" Y="70" Width="320" Height="80" Hidden="yes" Text="!(loc.VerifyReadyDlgInstallText)">
<Condition Action="show">NOT Installed</Condition>
</Control>
<Control Id="ChangeTitle" Type="Text" X="15" Y="15" Width="300" Height="15" Transparent="yes" NoPrefix="yes" Hidden="yes" Text="!(loc.VerifyReadyDlgChangeTitle)">
<Condition Action="show">WixUI_InstallMode = "Change"</Condition>
</Control>
<Control Id="ChangeText" Transparent="yes" Type="Text" X="25" Y="70" Width="320" Height="80" Hidden="yes" Text="!(loc.VerifyReadyDlgChangeText)">
<Condition Action="show">WixUI_InstallMode = "Change"</Condition>
</Control>
<Control Id="RepairTitle" Type="Text" X="15" Y="15" Width="300" Height="15" Transparent="yes" NoPrefix="yes" Hidden="yes" Text="!(loc.VerifyReadyDlgRepairTitle)">
<Condition Action="show">WixUI_InstallMode = "Repair"</Condition>
</Control>
<Control Id="RepairText" Transparent="yes" Type="Text" X="25" Y="70" Width="320" Height="80" Hidden="yes" NoPrefix="yes" Text="!(loc.VerifyReadyDlgRepairText)">
<Condition Action="show">WixUI_InstallMode = "Repair"</Condition>
</Control>
<Control Id="RemoveTitle" Type="Text" X="15" Y="15" Width="300" Height="15" Transparent="yes" NoPrefix="yes" Hidden="yes" Text="!(loc.VerifyReadyDlgRemoveTitle)">
<Condition Action="show">WixUI_InstallMode = "Remove"</Condition>
</Control>
<Control Id="RemoveText" Transparent="yes" Type="Text" X="25" Y="70" Width="320" Height="80" Hidden="yes" NoPrefix="yes" Text="!(loc.VerifyReadyDlgRemoveText)">
<Condition Action="show">WixUI_InstallMode = "Remove"</Condition>
</Control>
<Control Id="UpdateTitle" Type="Text" X="15" Y="15" Width="300" Height="15" Transparent="yes" NoPrefix="yes" Hidden="yes" Text="!(loc.VerifyReadyDlgUpdateTitle)">
<Condition Action="show">WixUI_InstallMode = "Update"</Condition>
</Control>
<Control Id="UpdateText" Transparent="yes" Type="Text" X="25" Y="70" Width="320" Height="80" Hidden="yes" NoPrefix="yes" Text="!(loc.VerifyReadyDlgUpdateText)">
<Condition Action="show">WixUI_InstallMode = "Update"</Condition>
</Control>
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="!(loc.WixUICancel)">
<Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
</Control>
<Control Id="Back" Type="PushButton" X="156" Y="243" Width="56" Height="17" Text="!(loc.WixUIBack)">
<Condition Action="default">WixUI_InstallMode = "Remove"</Condition>
</Control>
<Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="no" Text="[CommonBackground]" />
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
</Dialog>
</UI>
</Fragment>
</Wix>

View File

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?include Include.wxi?>
<Fragment>
<UI Id="AppWelcome">
<Property Id="AppWelcomeBackground">welcome_background</Property>
<Binary Id="welcome_background" SourceFile="$(var.DeployResDir)\dist\wix\images\welcome_background.png"/>
<Dialog Id="AppWelcome" Width="370" Height="270" Title="!(loc.WelcomeDlg_Title)">
<Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="!(loc.WixUINext)">
<Publish Property="WixUI_InstallMode" Value="Update">Installed AND PATCH</Publish>
</Control>
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="!(loc.WixUICancel)">
<Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
</Control>
<Control Id="Bitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="no" Text="[AppWelcomeBackground]" />
<Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Disabled="yes" Text="!(loc.WixUIBack)" />
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
</Dialog>
<InstallUISequence>
<Show Dialog="AppWelcome" Before="ProgressDlg" Overridable="yes">NOT Installed OR PATCH</Show>
</InstallUISequence>
</UI>
</Fragment>
</Wix>

View File

@ -1,72 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
First-time install dialog sequence:
- WixUI_WelcomeDlg
- WixUI_LicenseAgreementDlg
- WixUI_InstallDirDlg
- WixUI_VerifyReadyDlg
- WixUI_DiskCostDlg
Maintenance dialog sequence:
- WixUI_MaintenanceWelcomeDlg
- WixUI_MaintenanceTypeDlg
- WixUI_InstallDirDlg
- WixUI_VerifyReadyDlg
Patch dialog sequence:
- WixUI_WelcomeDlg
- WixUI_VerifyReadyDlg
-->
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<UI Id="DeskflowDlgSequence">
<TextStyle Id="WixUI_Font_Normal" FaceName="Roboto" Size="9" />
<TextStyle Id="WixUI_Font_Bigger" FaceName="Roboto" Size="14" />
<TextStyle Id="WixUI_Font_Title" FaceName="Roboto" Size="12" Bold="yes" Blue="255" Red="255" Green="255"/>
<Property Id="DefaultUIFont" Value="WixUI_Font_Normal" />
<Property Id="WixUI_Mode" Value="InstallDir" />
<DialogRef Id="BrowseDlg" />
<DialogRef Id="DiskCostDlg" />
<DialogRef Id="ErrorDlg" />
<DialogRef Id="FatalError" />
<DialogRef Id="FilesInUse" />
<DialogRef Id="MsiRMFilesInUse" />
<DialogRef Id="PrepareDlg" />
<DialogRef Id="ProgressDlg" />
<DialogRef Id="ResumeDlg" />
<DialogRef Id="UserExit" />
<Publish Dialog="DeskflowBrowseDlg" Control="OK" Event="DoAction" Value="WixUIValidatePath" Order="3">1</Publish>
<Publish Dialog="DeskflowBrowseDlg" Control="OK" Event="SpawnDialog" Value="InvalidDirDlg" Order="4"><![CDATA[WIXUI_INSTALLDIR_VALID<>"1"]]></Publish>
<Publish Dialog="ExitDialog" Control="Finish" Event="EndDialog" Value="Return" Order="999">1</Publish>
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="StartGui">NOT Installed</Publish>
<Publish Dialog="DeskflowWelcome" Control="Next" Event="NewDialog" Value="DeskflowInstallDirDlg" Order="1">1</Publish>
<Publish Dialog="DeskflowWelcome" Control="Next" Event="NewDialog" Value="DeskflowVerifyReadyDlg">Installed AND PATCH</Publish>
<Publish Dialog="DeskflowInstallDirDlg" Control="Next" Event="SetTargetPath" Value="[WIXUI_INSTALLDIR]" Order="1">1</Publish>
<Publish Dialog="DeskflowInstallDirDlg" Control="Next" Event="DoAction" Value="WixUIValidatePath" Order="2">NOT WIXUI_DONTVALIDATEPATH</Publish>
<Publish Dialog="DeskflowInstallDirDlg" Control="Next" Event="SpawnDialog" Value="InvalidDirDlg" Order="3"><![CDATA[NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"]]></Publish>
<Publish Dialog="DeskflowInstallDirDlg" Control="Next" Event="NewDialog" Value="DeskflowVerifyReadyDlg" Order="4">WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1"</Publish>
<Publish Dialog="DeskflowInstallDirDlg" Control="ChangeFolder" Property="_BrowseProperty" Value="[WIXUI_INSTALLDIR]" Order="1">1</Publish>
<Publish Dialog="DeskflowInstallDirDlg" Control="ChangeFolder" Event="SpawnDialog" Value="DeskflowBrowseDlg" Order="2">1</Publish>
<Publish Dialog="DeskflowInstallDirDlg" Control="Back" Event="NewDialog" Value="DeskflowWelcome" Order="2">1</Publish>
<Publish Dialog="DeskflowVerifyReadyDlg" Control="Back" Event="NewDialog" Value="DeskflowInstallDirDlg" Order="1">NOT Installed</Publish>
<Publish Dialog="DeskflowVerifyReadyDlg" Control="Back" Event="NewDialog" Value="DeskflowMaintenanceTypeDlg" Order="2">Installed AND NOT PATCH</Publish>
<Publish Dialog="DeskflowVerifyReadyDlg" Control="Back" Event="NewDialog" Value="DeskflowInstallDirDlg" Order="2">Installed AND PATCH</Publish>
<Publish Dialog="MaintenanceWelcomeDlg" Control="Next" Event="NewDialog" Value="DeskflowMaintenanceTypeDlg">1</Publish>
<Publish Dialog="DeskflowMaintenanceTypeDlg" Control="RepairButton" Event="NewDialog" Value="DeskflowVerifyReadyDlg">1</Publish>
<Publish Dialog="DeskflowMaintenanceTypeDlg" Control="RemoveButton" Event="NewDialog" Value="DeskflowVerifyReadyDlg">1</Publish>
<Publish Dialog="DeskflowMaintenanceTypeDlg" Control="Back" Event="NewDialog" Value="MaintenanceWelcomeDlg">1</Publish>
<Property Id="ARPNOMODIFY" Value="1" />
</UI>
<UIRef Id="WixUI_Common" />
</Fragment>
</Wix>

View File

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Include>
<?define AppId="deskflow"?>
<?define Name="Deskflow"?>
<?define Version="@DESKFLOW_VERSION@"?>
<?define Author="Deskflow"?>
<?define BinDir="@CMAKE_RUNTIME_OUTPUT_DIRECTORY@"?>
<?define ProjectIcon="@CMAKE_SOURCE_DIR@/src/gui/src/deskflow.ico"?>
<?define DeployResDir="@CMAKE_SOURCE_DIR@/deploy"?>
<?define QtDir="@QT_PATH@"?>
<?define QtBinDir="$(var.QtDir)\bin"?>
<?if $(var.Platform) = "x64"?>
<?define ProgramFilesFolder="ProgramFiles64Folder"?>
<?define PlatformSimpleName="64-bit"?>
<?define UpgradeGuid="@DESKFLOW_MSI_64_GUID@"?>
<?else?>
<?define ProgramFilesFolder="ProgramFilesFolder"?>
<?define PlatformSimpleName="32-bit"?>
<?define UpgradeGuid="@DESKFLOW_MSI_32_GUID@"?>
<?endif?>
<?define QtPluginsPath="$(var.QtDir)\plugins"?>
<?define OpenSslExeDir="@OPENSSL_EXE_DIR@"?>
<?define OpenSslDllDir="@OPENSSL_ROOT_DIR@/bin"?>
<?define GuiBin="deskflow.exe"?>
<?define ServerBin="deskflow-server.exe"?>
<?define ClientBin="deskflow-client.exe"?>
<?define CoreBin="deskflow-core.exe"?>
<?define DaemonBin="deskflow-daemon.exe"?>
</Include>

View File

@ -1,31 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29411.108
MinimumVisualStudioVersion = 10.0.40219.1
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "Installer", "Installer.wixproj", "{D4BA9F39-6A35-4C8F-9CB2-67FCBE5CAB17}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Debug|x64 = Debug|x64
Release|x86 = Release|x86
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D4BA9F39-6A35-4C8F-9CB2-67FCBE5CAB17}.Debug|x86.ActiveCfg = Debug|x86
{D4BA9F39-6A35-4C8F-9CB2-67FCBE5CAB17}.Debug|x86.Build.0 = Debug|x86
{D4BA9F39-6A35-4C8F-9CB2-67FCBE5CAB17}.Debug|x64.ActiveCfg = Debug|x64
{D4BA9F39-6A35-4C8F-9CB2-67FCBE5CAB17}.Debug|x64.Build.0 = Debug|x64
{D4BA9F39-6A35-4C8F-9CB2-67FCBE5CAB17}.Release|x86.ActiveCfg = Release|x86
{D4BA9F39-6A35-4C8F-9CB2-67FCBE5CAB17}.Release|x86.Build.0 = Release|x86
{D4BA9F39-6A35-4C8F-9CB2-67FCBE5CAB17}.Release|x64.ActiveCfg = Release|x64
{D4BA9F39-6A35-4C8F-9CB2-67FCBE5CAB17}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2E0AA1C9-0F14-4FE4-8F18-430484EFBACE}
EndGlobalSection
EndGlobal

View File

@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProductVersion>3.11</ProductVersion>
<ProjectGuid>{d4ba9f39-6a35-4c8f-9cb2-67fcbe5cab17}</ProjectGuid>
<SchemaVersion>2.0</SchemaVersion>
<OutputName>Installer</OutputName>
<OutputType>Package</OutputType>
<OutputPath>bin\$(Configuration)\</OutputPath>
<IntermediateOutputPath>wix\obj\$(Configuration)\</IntermediateOutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(SolutionDir)/AppWelcome.wxs" />
<Compile Include="$(SolutionDir)/AppInstallDirDlg.wxs" />
<Compile Include="$(SolutionDir)/AppBrowseDlg.wxs" />
<Compile Include="$(SolutionDir)/AppVerifyReadyDlg.wxs" />
<Compile Include="$(SolutionDir)/AppMaintenanceTypeDlg.wxs" />
<Compile Include="$(SolutionDir)/AppDlgSequence.wxs" />
<Compile Include="$(SolutionDir)/Product.wxs" />
<Content Include="$(SolutionDir)/Include.wxi" />
</ItemGroup>
<ItemGroup>
<WixExtension Include="WixUtilExtension">
<HintPath>C:\Program Files (x86)\WiX Toolset v3.11\bin\WixUtilExtension.dll</HintPath>
<Name>WixUtilExtension</Name>
</WixExtension>
<WixExtension Include="WixUIExtension">
<HintPath>C:\Program Files (x86)\WiX Toolset v3.11\bin\WixUIExtension.dll</HintPath>
<Name>WixUIExtension</Name>
</WixExtension>
<WixExtension Include="WixFirewallExtension">
<HintPath>C:\Program Files (x86)\WiX Toolset v3.11\bin\WixFirewallExtension.dll</HintPath>
<Name>WixFirewallExtension</Name>
</WixExtension>
</ItemGroup>
<Import Project="$(WixTargetsPath)" Condition=" '$(WixTargetsPath)' != '' " />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets"
Condition=" '$(WixTargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets') " />
</Project>

View File

@ -1,166 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:fire="http://schemas.microsoft.com/wix/FirewallExtension" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<?include Include.wxi?>
<Product Id="*" Language="1033" Manufacturer="$(var.Author)" Name="$(var.Name) ($(var.PlatformSimpleName))" UpgradeCode="$(var.UpgradeGuid)" Version="$(var.Version)">
<Package Compressed="yes" InstallScope="perMachine" InstallerVersion="301"/>
<MajorUpgrade DowngradeErrorMessage="A newer version of $(var.Name) is already installed."/>
<MediaTemplate EmbedCab="yes"/>
<!-- causes ICE61 warning, but stops user from installing many instances from nightly builds. -->
<Upgrade Id="$(var.UpgradeGuid)">
<UpgradeVersion Minimum="0.0.0.0" Property="UPGRADE"/>
</Upgrade>
<Feature Id="ProductFeature" Title="$(var.Name)">
<ComponentGroupRef Id="ProductComponents"/>
<ComponentGroupRef Id="OpenSSLComponents"/>
<ComponentGroupRef Id="ProductQtStylesComponents"/>
<ComponentGroupRef Id="ProductQtPluginComponents"/>
<ComponentRef Id="RegistryEntries"/>
<MergeRef Id="VC_Redist"/>
</Feature>
<DirectoryRef Id="TARGETDIR">
<Component Guid="7CF3564D-1F8E-4D3D-9781-E1EE22D5BD67" Id="RegistryEntries">
<RegistryKey Id="$(var.AppId)_server" ForceCreateOnInstall="yes" ForceDeleteOnUninstall="yes" Key="Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers" Root="HKLM">
<RegistryValue Name="[INSTALLFOLDER]$(var.ServerBin)" Type="string" Value="~ HIGHDPIAWARE WIN7RTM"/>
</RegistryKey>
<RegistryKey Id="$(var.AppId)_client" Root="HKLM"
Key="Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers"
ForceCreateOnInstall="yes" ForceDeleteOnUninstall="yes">
<RegistryValue Type="string" Name="[INSTALLFOLDER]$(var.ClientBin)" Value="~ HIGHDPIAWARE WIN7RTM"/>
</RegistryKey>
<RegistryKey Id="$(var.AppId)" Root="HKLM"
Key="Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers"
ForceCreateOnInstall="yes" ForceDeleteOnUninstall="yes">
<RegistryValue Type="string" Name="[INSTALLFOLDER]$(var.GuiBin)" Value="~ HIGHDPIAWARE WIN7RTM"/>
</RegistryKey>
<!-- Windows 8 and later only -->
<Condition><![CDATA[Installed OR (VersionNT >= 602)]]></Condition>
</Component>
<?if $(var.Platform) = x64 ?>
<Merge Id="VC_Redist" SourceFile="$(var.DeployResDir)\dist\wix\msm\Microsoft_VC142_CRT_x64.msm" DiskId="1" Language="0"/>
<?else ?>
<Merge Id="VC_Redist" SourceFile="$(var.DeployResDir)\dist\wix\msm\Microsoft_VC142_CRT_x86.msm" DiskId="1" Language="0"/>
<?endif ?>
</DirectoryRef>
<Property Id="CommonBackground">CommonBackground</Property>
<Binary Id="CommonBackground" SourceFile="$(var.DeployResDir)\dist\wix\images\common_background.png"/>
<Icon Id="AppIcon" SourceFile="$(var.ProjectIcon)"/>
<WixVariable Id="WixUIBannerBmp" Value="$(var.DeployResDir)\dist\wix\images\banner.png"/>
<WixVariable Id="WixUIDialogBmp" Value="$(var.DeployResDir)\dist\wix\images\dialog.png"/>
<Property Id="ARPPRODUCTICON" Value="AppIcon"/>
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER"/>
<Property Id="LEGACY_UNINSTALL_EXISTS">
<RegistrySearch Id="LegacyRegistrySearch" Key="SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\$(var.Name)" Name="UninstallString" Root="HKLM" Type="file" Win64="no">
<FileSearch Id="LegacyFileSearch" Name="uninstall.exe"/>
</RegistrySearch>
</Property>
<Condition Message="An existing installation of $(var.Name) was detected, please uninstall it before continuing.">NOT LEGACY_UNINSTALL_EXISTS
</Condition>
<CustomAction ExeCommand="" FileKey="GuiProgram" Id="StartGui" Return="asyncNoWait"/>
<UI>
<UIRef Id="AppDlgSequence" />
</UI>
</Product>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="$(var.ProgramFilesFolder)">
<Directory Id="INSTALLFOLDER" Name="$(var.Name)">
<Directory Id="OpenSSLDir" Name="OpenSSL"/>
<Directory Id="PlatformsDir" Name="Platforms"/>
<Directory Id="QTStylesDir" Name="styles"/>
</Directory>
</Directory>
<Directory Id="ProgramMenuFolder"/>
</Directory>
</Fragment>
<Fragment>
<ComponentGroup Directory="INSTALLFOLDER" Id="ProductComponents">
<Component Guid="EC9AD3B0-277C-4157-B5C8-5FD5B6A5F4AD" Id="Core">
<File KeyPath="yes" Source="$(var.BinDir)/$(var.DaemonBin)"/>
<ServiceInstall Description="Controls the $(var.Name) foreground processes." DisplayName="$(var.Name)" ErrorControl="normal" Id="ServiceInstall" Name="$(var.Name)" Start="auto" Type="ownProcess">
<util:ServiceConfig FirstFailureActionType="restart" ResetPeriodInDays="1" RestartServiceDelayInSeconds="1" SecondFailureActionType="restart" ThirdFailureActionType="restart"/>
</ServiceInstall>
<ServiceControl Id="ServiceControl" Name="$(var.Name)" Remove="uninstall" Start="install" Stop="both"/>
<File Source="$(var.BinDir)/$(var.ServerBin)">
<fire:FirewallException Id="ServerFirewallException" IgnoreFailure="yes" Name="$(var.Name) Server" Scope="any"/>
</File>
<File Source="$(var.BinDir)/$(var.ClientBin)">
<fire:FirewallException Id="ClientFirewallException" IgnoreFailure="yes" Name="$(var.Name) Client" Scope="any"/>
</File>
<?if $(var.Platform) = x64 ?>
<File Source="$(var.OpenSslDllDir)/libssl-3-x64.dll"/>
<File Source="$(var.OpenSslDllDir)/libcrypto-3-x64.dll"/>
<?else ?>
<File Source="$(var.OpenSslDllDir)/libssl-3.dll"/>
<File Source="$(var.OpenSslDllDir)/libcrypto-3.dll"/>
<?endif ?>
</Component>
<Component Guid="BAC8149B-6287-45BF-9C27-43D71ED40214" Id="Gui">
<File Id="GuiProgram" KeyPath="yes" Source="$(var.BinDir)/$(var.GuiBin)">
<Shortcut Advertise="yes" Directory="ProgramMenuFolder" Icon="$(var.GuiBin)" Id="GuiShortcut" Name="$(var.Name)">
<Icon Id="$(var.GuiBin)" SourceFile="$(var.ProjectIcon)"/>
</Shortcut>
<fire:FirewallException Id="GuiFirewallException" IgnoreFailure="yes" Name="$(var.Name)" Scope="any"/>
</File>
<?if $(var.Configuration) = "Debug" ?>
<File Source="$(var.BinDir)\Qt6Cored.dll"/>
<File Source="$(var.BinDir)\Qt6Guid.dll"/>
<File Source="$(var.BinDir)\Qt6Networkd.dll"/>
<File Source="$(var.BinDir)\Qt6Svgd.dll"/>
<File Source="$(var.BinDir)\Qt6Widgetsd.dll"/>
<File Source="$(var.BinDir)\styles\qmodernwindowsstyle.dll"/>
<!-- HACK: Normally the C++ redistributable solves this dependency, including it can cause problems -->
<File Source="C:\Program Files (x86)\Windows Kits\10\bin\$(var.Platform)\ucrt\ucrtbased.dll"/>
<?else ?>
<File Source="$(var.BinDir)\brotlicommon.dll"/>
<File Source="$(var.BinDir)\brotlidec.dll"/>
<File Source="$(var.BinDir)\bz2.dll"/>
<File Source="$(var.BinDir)\double-conversion.dll"/>
<File Source="$(var.BinDir)\freetype.dll"/>
<File Source="$(var.BinDir)\harfbuzz.dll"/>
<File Source="$(var.BinDir)\icudt74.dll"/>
<File Source="$(var.BinDir)\icuin74.dll"/>
<File Source="$(var.BinDir)\icuuc74.dll"/>
<File Source="$(var.BinDir)\libpng16.dll"/>
<File Source="$(var.BinDir)\pcre2-16.dll"/>
<File Source="$(var.BinDir)\Qt6Core.dll"/>
<File Source="$(var.BinDir)\Qt6Gui.dll"/>
<File Source="$(var.BinDir)\Qt6Network.dll"/>
<File Source="$(var.BinDir)\Qt6Svg.dll"/>
<File Source="$(var.BinDir)\Qt6Widgets.dll"/>
<File Source="$(var.BinDir)\zlib1.dll"/>
<File Source="$(var.BinDir)\zstd.dll"/>
<File Source="$(var.BinDir)\styles\qmodernwindowsstyle.dll"/>
<?endif ?>
</Component>
</ComponentGroup>
<ComponentGroup Directory="QTStylesDir" Id="ProductQtStylesComponents">
<Component Guid="96E0F8D8-64FD-4CE8-94D1-F6EDCBBB4995" Id="Styles">
<File Id="qmodernwindowsstyle" Source="$(var.BinDir)\styles\qmodernwindowsstyle.dll"/>
</Component>
</ComponentGroup>
<ComponentGroup Directory="PlatformsDir" Id="ProductQtPluginComponents">
<Component Guid="684EFA14-856B-440E-A5E6-E90E04E36B41" Id="QtPlatformPlugin">
<?if $(var.Configuration) = "Debug" ?>
<File Source="$(var.BinDir)\platforms\qwindowsd.dll"/>
<?else ?>
<File Source="$(var.BinDir)\platforms\qwindows.dll"/>
<?endif ?>
</Component>
</ComponentGroup>
<ComponentGroup Directory="OpenSSLDir" Id="OpenSSLComponents">
<Component Guid="92648F77-65A6-4B16-AC59-A1F37BD341B1" Id="OpenSSL">
<?if $(var.Platform) = x64 ?>
<File Id="OpenSSLDll1" Source="$(var.OpenSslDllDir)/libcrypto-3-x64.dll"/>
<File Id="OpenSSLDll2" Source="$(var.OpenSslDllDir)/libssl-3-x64.dll"/>
<?else ?>
<File Id="OpenSSLDll1" Source="$(var.OpenSslDllDir)/libcrypto-3.dll"/>
<File Id="OpenSSLDll2" Source="$(var.OpenSslDllDir)/libssl-3.dll"/>
<?endif ?>
<File Source="$(var.OpenSslExeDir)/openssl.exe"/>
<File Source="$(var.OpenSslExeDir)/openssl.cnf"/>
</Component>
</ComponentGroup>
</Fragment>
</Wix>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

107
deploy/linux/deploy.cmake Normal file
View File

@ -0,0 +1,107 @@
# SPDX-FileCopyrightText: 2024 Chris Rizzitello <sithlord48@gmail.com>
# SPDX-License-Identifier: MIT
# HACK This is set when the files is included so its the real path
# calling CMAKE_CURRENT_LIST_DIR after include would return the wrong scope var
set(MY_DIR ${CMAKE_CURRENT_LIST_DIR})
# Install our desktop file
install(
FILES ${MY_DIR}/org.deskflow.deskflow.desktop
DESTINATION share/applications
)
# Install our icon
install(
FILES ${MY_DIR}/deskflow.png
DESTINATION share/icons/hicolor/512x512/apps/
RENAME org.deskflow.deskflow.png
)
# Install our metainfo
install(
FILES ${MY_DIR}/org.deskflow.deskflow.metainfo.xml
DESTINATION share/metainfo/
)
# Prepare PKGBUILD for Arch Linux
configure_file(
${MY_DIR}/arch/PKGBUILD.in
${CMAKE_BINARY_DIR}/PKGBUILD
@ONLY
)
set(CPACK_DEBIAN_PACKAGE_SECTION "utils")
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_RPM_PACKAGE_LICENSE "GPLv2")
set(CPACK_RPM_PACKAGE_GROUP "Applications/System")
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
# Get Distro name information
if(EXISTS "/etc/os-release")
FILE(STRINGS "/etc/os-release" RELEASE_FILE_CONTENTS)
else()
message(FATAL_ERROR "Unable to read file /etc/os-release")
endif()
foreach(LINE IN LISTS RELEASE_FILE_CONTENTS)
if( "${LINE}" MATCHES "^ID=")
string(REGEX REPLACE "^ID=" "" DISTRO_NAME ${LINE})
string(REGEX REPLACE "\"" "" DISTRO_NAME ${DISTRO_NAME})
message(DEBUG "Distro Name :${DISTRO_NAME}")
elseif( "${LINE}" MATCHES "^ID_LIKE=")
string(REGEX REPLACE "^ID_LIKE=" "" DISTRO_LIKE "${LINE}")
string(REGEX REPLACE "\"" "" DISTRO_LIKE ${DISTRO_LIKE})
message(DEBUG "Distro Like :${DISTRO_LIKE}")
elseif( "${LINE}" MATCHES "^VERSION_CODENAME=")
string(REGEX REPLACE "^VERSION_CODENAME=" "" DISTRO_CODENAME "${LINE}")
string(REGEX REPLACE "\"" "" DISTRO_CODENAME "${DISTRO_CODENAME}")
message(DEBUG "Distro Codename:${DISTRO_CODENAME}")
elseif( "${LINE}" MATCHES "^VERSION_ID=")
string(REGEX REPLACE "^VERSION_ID=" "" DISTRO_VERSION_ID "${LINE}")
string(REGEX REPLACE "\"" "" DISTRO_VERSION_ID "${DISTRO_VERSION_ID}")
message(DEBUG "Distro VersionID:${DISTRO_VERSION_ID}")
endif()
endforeach()
# Check if Debian-link
string(REGEX MATCH debian|buntu DEBTYPE "${DISTRO_LIKE}")
if((NOT ("${DEBTYPE}" STREQUAL "")) OR ("${DISTRO_NAME}" STREQUAL "debian"))
set(CPACK_GENERATOR "DEB")
endif()
# Check if Rpm-like
string(REGEX MATCH suse|fedora|rhel RPMTYPE "${DISTRO_LIKE}")
string(REGEX MATCH fedora|suse|rhel RPMNAME "${DISTRO_NAME}")
if((NOT ("${RPMTYPE}" STREQUAL "")) OR (NOT ("${RPMNAME}" STREQUAL "")))
set(CPACK_GENERATOR "RPM")
endif()
# Disto specific name adjustments
if("${DISTRO_NAME}" STREQUAL "opensuse-tumbleweed")
set(DISTRO_NAME "opensuse")
set(DISTRO_CODENAME "tumbleweed")
elseif("${DISTRO_NAME}" STREQUAL "arch")
# Arch linux is rolling the version id reported is the date of last iso.
set(DISTRO_VERSION_ID "")
endif()
# Determain the code name to be used if any
if(NOT "${DISTRO_VERSION_ID}" STREQUAL "")
set(CN_STRING "${DISTRO_VERSION_ID}-")
endif()
if(NOT "${DISTRO_CODENAME}" STREQUAL "")
set(CN_STRING "${DISTRO_CODENAME}-")
endif()
if("${DISTRO_NAME}" STREQUAL "")
set(DISTRO_NAME "linux")
endif()
set(OS_STRING "${DISTRO_NAME}-${CN_STRING}${CMAKE_SYSTEM_PROCESSOR}")
elseif(${CMAKE_SYSTEM_NAME} MATCHES "|.*BSD")
message(STATUS "BSD packaging not yet supported")
set(OS_STRING ${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR})
endif()

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -21,7 +21,6 @@ cleanup:
- /share/cmake
- /share/doc
- /share/gir-1.0
- /lib/debug
- /lib/girepository-1.0
modules:
- name: python3-attrs

View File

@ -8,7 +8,7 @@
<summary>Software Keyboard and mouse sharing</summary>
<description>
<p>
Use your keyboard and mouse to control other machines on the network or be controle
Use the keyboard, mouse, or trackpad of one computer to control nearby computers, and work seamlessly between them.
</p>
</description>
<launchable type="desktop-id">org.deskflow.deskflow.desktop</launchable>
@ -35,6 +35,25 @@
</branding>
<content_rating type="oars-1.0" />
<releases>
<release version="1.18.0" date="2024-12-26" urgency="high">
<description>
<p>This stable Release Fixes a few security issues, additionally fixes several bugs and adds a few new features. For the full changelog see the relase page.</p>
<ul>
<li>Fix CVE-2021-42075: Close connection on app-level handshake failure</li>
<li>Fix CVE-2021-42074: Handle SSL race conditions and segmentation fault</li>
<li>Fix CVE-2021-42076: Enforce maximum message length </li>
<li>Add a visiblity toggle for the log</li>
<li>Fix deskflow will now hide or show when the tray icon is clicked</li>
<li>Fix how Utf-16 surrogates are handled</li>
<li>Avoid encoding empty data to the clipboard on Windows</li>
<li>Create new Tray Icons for windows</li>
<li>Always show the tray menu entry for the restore action on macOS</li>
<li>Correctly restore window when hidden with command+H on macOS</li>
<li>Update the windows clipboard format listener to monitor the clipboard</li>
</ul>
</description>
<url>https://github.com/deskflow/deskflow/releases/tag/v1.18.0</url>
</release>
<release version="1.17.2" date="2024-11-20" urgency="medium">
<description>
<p>This stable Release contains alot of internal refactoring.</p>

15
deploy/mac/deploy.cmake Normal file
View File

@ -0,0 +1,15 @@
# SPDX-FileCopyrightText: 2024 Chris Rizzitello <sithlord48@gmail.com>
# SPDX-License-Identifier: MIT
# HACK This is set when the files is included so its the real path
# calling CMAKE_CURRENT_LIST_DIR after include would return the wrong scope var
set(MY_DIR ${CMAKE_CURRENT_LIST_DIR})
set(OS_STRING "macos-${CMAKE_SYSTEM_PROCESSOR}")
set(CMAKE_INSTALL_RPATH "@loader_path/../Libraries;@loader_path/../Frameworks")
set(CPACK_PACKAGE_ICON "${MY_DIR}/dmg-volume.icns")
set(CPACK_DMG_BACKGROUND_IMAGE "${MY_DIR}/dmg-background.tiff")
set(CPACK_DMG_DS_STORE_SETUP_SCRIPT "${MY_DIR}/generate_ds_store.applescript")
set(CPACK_DMG_VOLUME_NAME "Deskflow")
set(CPACK_DMG_SLA_USE_RESOURCE_FILE_LICENSE ON)
set(CPACK_GENERATOR "DragNDrop")

View File

@ -0,0 +1,45 @@
on run argv
set image_name to item 1 of argv
tell application "Finder"
tell disk image_name
-- wait for the image to finish mounting
set open_attempts to 0
repeat while open_attempts < 5
try
open
delay 5
set open_attempts to 5
close
on error errStr number errorNumber
set open_attempts to open_attempts + 1
delay 10
end try
end repeat
-- open the image the first time and save a DS_Store with just
-- background and icon setup
open
set current view of container window to icon view
set theViewOptions to the icon view options of container window
set background picture of theViewOptions to file ".background:background.tiff"
set arrangement of theViewOptions to not arranged
set icon size of theViewOptions to 100
set text size of theViewOptions to 16
close
open
tell container window
set sidebar width to 0
set statusbar visible to false
set toolbar visible to false
set pathbar visible to false
set the bounds to { 200, 120, 800, 520 }
set position of item "Deskflow.app" to { 144, 190 }
set position of item "Applications" to { 455, 190 }
end tell
close
end tell
end tell
end run

View File

@ -0,0 +1,59 @@
# SPDX-FileCopyrightText: 2024 Chris Rizzitello <sithlord48@gmail.com>
# SPDX-License-Identifier: MIT
# HACK This is set when the files is included so its the real path
# calling CMAKE_CURRENT_LIST_DIR after include would return the wrong scope var
set(MY_DIR ${CMAKE_CURRENT_LIST_DIR})
# Configure the windows version rc file
configure_file(
${MY_DIR}/version.rc.in
${PROJECT_BINARY_DIR}/src/version.rc @ONLY
)
# Setup OS_STRING
if(CMAKE_SYSTEM_PROCESSOR MATCHES AMD64)
set(OS_STRING "win-x64")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES ARM64)
set(OS_STRING "win-arm64")
else()
set(OS_STRING "win-${CMAKE_SYSTEM_PROCESSOR}")
endif()
# If Wix4+ is installed make a package
find_program(WIX_APP wix)
if (NOT "${WIX_APP}" STREQUAL "")
set(CPACK_WIX_VERSION 4)
list(APPEND CPACK_GENERATOR "WIX")
endif()
set(CPACK_PACKAGE_NAME "Deskflow")
# Menu Entry
set(CPACK_WIX_PROGRAM_MENU_FOLDER "Deskflow")
set(CPACK_PACKAGE_EXECUTABLES "deskflow" "Deskflow")
# Default Install Path
set(CPACK_PACKAGE_INSTALL_DIRECTORY "Deskflow")
# Wix Specific Values
set(CPACK_WIX_UPGRADE_GUID "027D1C8A-E7A5-4754-BB93-B2D45BFDBDC8")
set(CPACK_WIX_UI_BANNER "${MY_DIR}/wix-banner.png")
set(CPACK_WIX_UI_DIALOG "${MY_DIR}/wix-dialog.png")
# Required Extra Extenstions
list(APPEND CPACK_WIX_EXTENSIONS "WixToolset.Util.wixext" "WixToolset.Firewall.wixext")
# Make sure to also put the xmlns for the ext into the wix block on generated files
list(APPEND CPACK_WIX_CUSTOM_XMLNS "util=http://wixtoolset.org/schemas/v4/wxs/util" "firewall=http://wixtoolset.org/schemas/v4/wxs/firewall")
# The patch has to know the full path of our msm file
set(CPACK_WIX_MSM_FILE "${MY_DIR}/Microsoft_VC142_CRT_x64.msm")
configure_file(
${MY_DIR}/wix-patch.xml.in
${CMAKE_CURRENT_BINARY_DIR}/wix-patch.xml @ONLY
)
# This patch set ups filewall rules, the service and msm module
set(CPACK_WIX_PATCH_FILE "${CMAKE_CURRENT_BINARY_DIR}/wix-patch.xml")

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

View File

@ -0,0 +1,26 @@
<CPackWiXPatch>
<CPackWiXFragment Id="CM_CP_deskflow_daemon.exe">
<ServiceInstall Description="Controls the Deskflow foreground processes." DisplayName="Deskflow" ErrorControl="normal" Id="ServiceInstall" Name="Deskflow" Start="auto" Type="ownProcess">
<util:ServiceConfig FirstFailureActionType="restart" ResetPeriodInDays="1" RestartServiceDelayInSeconds="1" SecondFailureActionType="restart" ThirdFailureActionType="restart"/>
</ServiceInstall>
<ServiceControl Id="ServiceControl" Name="Deskflow" Remove="uninstall" Start="install" Stop="both"/>
</CPackWiXFragment>
<CPackWiXFragment Id="CM_CP_deskflow_server.exe">
<firewall:FirewallException Id="ServerFirewallException" Name="Deskflow Server" Program="[INSTALL_ROOT]deskflow-server.exe" Scope="any"/>
</CPackWiXFragment>
<CPackWiXFragment Id="CM_CP_deskflow_client.exe">
<firewall:FirewallException Id="ClientFirewallException" Name="Deskflow Client" Program="[INSTALL_ROOT]deskflow-client.exe" Scope="any"/>
</CPackWiXFragment>
<CPackWiXFragment Id="#PRODUCT">
<StandardDirectory Id="TARGETDIR">
<Merge Id="VCRedist" SourceFile="@CPACK_WIX_MSM_FILE@" DiskId="1" Language="0"/>
</StandardDirectory >
<Feature Id="VCRedist" Title="Visual C++ Runtime" AllowAbsent="no" AllowAdvertise="yes" Display="hidden" InstallDefault="local" TypicalDefault="install">
<MergeRef Id="VCRedist" Primary="yes"/>
</Feature>
<CustomAction Id="Run_Deskflow" ExeCommand="Deskflow" FileRef="CM_FP_deskflow.exe" Return="asyncNoWait"/>
<InstallExecuteSequence>
<Custom Action="Run_Deskflow" OnExit="success" Condition="NOT Installed"/>
</InstallExecuteSequence>
</CPackWiXFragment>
</CPackWiXPatch>

View File

@ -1,76 +0,0 @@
#!/usr/bin/env python3
# Deskflow -- mouse and keyboard sharing utility
# Copyright (C) 2024 Symless Ltd.
#
# This package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# found in the file LICENSE that should have accompanied this file.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import lib.env as env
env.ensure_in_venv(__file__)
import os, sys, argparse
import lib.windows as windows
import lib.colors as colors
DEFAULT_SERVICE_ID = "deskflow"
DEFAULT_BIN_NAME = "deskflow-daemon"
DEFAULT_SOURCE_DIR = os.path.join("build", "temp", "bin")
DEFAULT_TARGET_DIR = os.path.join("build", "bin")
IGNORE_PROCESSES = ["deskflow.exe"]
def main():
"""Entry point for the script."""
parser = argparse.ArgumentParser()
parser.add_argument("--reinstall", action="store_true")
parser.add_argument("--stop", action="store_true")
parser.add_argument("--restart", action="store_true")
parser.add_argument("--pause-on-exit", action="store_true")
parser.add_argument("--source-dir", default=DEFAULT_SOURCE_DIR)
parser.add_argument("--target-dir", default=DEFAULT_TARGET_DIR)
parser.add_argument("--bin-name", default=DEFAULT_BIN_NAME)
parser.add_argument("--ignore-processes", nargs="+", default=IGNORE_PROCESSES)
parser.add_argument("--service-id", default=DEFAULT_SERVICE_ID)
parser.add_argument("--verbose", action="store_true")
args = parser.parse_args()
if not env.is_windows():
print(
f"{colors.ERROR_TEXT} This script is only supported on Windows",
file=sys.stderr,
)
sys.exit(1)
service = windows.WindowsService(__file__, args)
try:
if args.reinstall:
service.reinstall()
elif args.stop:
service.stop()
elif args.restart:
service.restart()
else:
print("No action specified", file=sys.stderr)
exit(1)
except Exception as e:
print(f"{colors.ERROR_TEXT} {e}", file=sys.stderr)
if args.pause_on_exit:
input("Press enter to continue...")
if __name__ == "__main__":
main()

View File

@ -1,57 +0,0 @@
#!/usr/bin/env python3
# Deskflow -- mouse and keyboard sharing utility
# Copyright (C) 2024 Symless Ltd.
#
# This package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# found in the file LICENSE that should have accompanied this file.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import lib.env as env
env.ensure_in_venv(__file__)
import argparse
import lib.file_utils as file_utils
import lib.colors as colors
def main():
"""
Cross platform script to copy files and directories.
This script was mostly created beause the default `copy` command on Windows is too noisy.
If this becomes complex it must be replaced with a library.
"""
parser = argparse.ArgumentParser()
parser.add_argument("source", help="Source pattern to copy from")
parser.add_argument("target", help="Destination pattern to copy to")
parser.add_argument(
"--ignore-errors", action="store_true", help="Ignore errors when copying"
)
parser.add_argument(
"--verbose", action="store_true", help="Print more information to the console"
)
args = parser.parse_args()
options = file_utils.CopyOptions(args.ignore_errors, args.verbose)
try:
file_utils.copy(args.source, args.target, options)
except Exception as e:
if not args.ignore_errors:
raise e
else:
print(f"{colors.ERROR_TEXT} {e}")
if __name__ == "__main__":
main()

View File

@ -1,55 +0,0 @@
import os, base64
# Deskflow -- mouse and keyboard sharing utility
# Copyright (C) 2024 Symless Ltd.
#
# This package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# found in the file LICENSE that should have accompanied this file.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
temp_path = "tmp/certificate"
class Certificate:
"""
Installs a certificate from a base64 string, and returns the path to the certificate.
Once the context is exited, the certificate is removed from the filesystem.
Example usage:
with Certificate(base64) as cert_path:
print(f"Certificate path: {cert_path}")
"""
def __init__(self, base64, file_ext):
self.base64 = base64
self.temp_filename = f"{temp_path}.{file_ext}"
def __enter__(self):
print(f"Decoding certificate to temporary path: {self.temp_filename}")
try:
cert_bytes = base64.b64decode(self.base64)
except Exception as e:
raise ValueError("Failed to decode certificate base64") from e
os.makedirs(os.path.dirname(self.temp_filename), exist_ok=True)
with open(self.temp_filename, "wb") as cert_file:
cert_file.write(cert_bytes)
return self.temp_filename
def __exit__(self, _exc_type, _exc_value, _traceback):
# not strictly necessary for ci, but when run on a dev machine, it reduces the risk
# that private keys are left on the filesystem
print(f"Removing temporary certificate file: {self.temp_filename}")
os.remove(self.temp_filename)
# propagate exceptions
return False

View File

@ -1,156 +0,0 @@
# Deskflow -- mouse and keyboard sharing utility
# Copyright (C) 2024 Symless Ltd.
#
# This package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# found in the file LICENSE that should have accompanied this file.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import subprocess
import sys
import lib.env as env
try:
import colorama # type: ignore
from colorama import Fore # type: ignore
colorama.init()
except ImportError:
class Fore:
RESET = ""
YELLOW = ""
def has_command(command):
platform = sys.platform
if platform == "win32":
cmd = f"where {command}"
else:
cmd = f"which {command}"
try:
subprocess.check_output(cmd, shell=True)
return True
except subprocess.CalledProcessError:
return False
def strip_continuation_sequences(command, strip_newlines=True):
"""
Remove the continuation sequences (\\) from a command.
To spread strings over multiple lines in YAML files, like in bash, a backslash is used at
the end of each line as continuation character.
"""
if isinstance(command, list):
raise ValueError("List commands are not supported")
cmd_continuation = " \\"
command = command.replace(cmd_continuation, "")
# Some versions of pyyaml will remove the newlines already, so always stripping
# makes the output more consistent.
if strip_newlines:
command = command.replace("\n", " ")
return command
def run(
command,
check=True, # true by default to fail fast
shell=False, # false by default for security
get_output=False,
print_cmd=False, # false by default for security
):
"""
Convenience wrapper around `subprocess.run` to:
- print the command before running it (if `print_cmd` is True)
This differs to `subprocess.run` in that by default it:
- checks the return code by default
- prints list commands as a readable string on failure
This is the same as `subprocess.run` in that it:
- does not use shell by default for security (shell is less secure)
Args:
command (str or list): The command to run.
check (bool): Raise an exception if the command fails.
shell (bool): Run the command in a shell (false by default for security)
get_output (bool): Return the output of the command.
print_cmd (bool): Print the command before running it (false by default for security)
"""
is_list_cmd = isinstance(command, list)
# create string version of list command, only for debugging purposes
command_str = command
if is_list_cmd:
command_str = " ".join(command)
if print_cmd:
print(f"Running: {command_str}")
else:
print("Running command...")
command_str = "***"
# TODO: You can definitely use a list command with shell=True on Windows,
# but can you use a string command with shell=False on Windows?
#
# The `subprocess.run` function has a little gotcha:
# - a string command must be used when `shell=True`
# - a list command must be used when shell isn't or `shell=False`
# however, it allows you to pass a string command when shell isn't used or `shell=False`
# then fails with a vague error message. same problem with list commands and `shell=True`
if not env.is_windows() and is_list_cmd and shell:
raise ValueError("List commands cannot be used when shell=True on Unix systems")
elif not is_list_cmd and not shell:
raise ValueError("String commands cannot be used when shell=False or not set")
# Flush the output to ensure the command is printed before the output of the command,
# which seems to happen in the GitHub runner logs.
sys.stdout.flush()
sys.stderr.flush()
try:
if get_output:
result = subprocess.run(
command,
shell=shell,
check=check,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
else:
result = subprocess.run(command, check=check, shell=shell)
except subprocess.CalledProcessError as e:
# Take control of how failed commands are printed:
# - if `print_cmd` is false, it will print `***` instead of the command
# - if the command was a list, the command is printed as a readable string
raise RuntimeError(
f"Command exited with code {e.returncode}: {command_str}"
) from None
except Exception:
# Take control of how failed commands are printed:
# - if `print_cmd` is false, it will print `***` instead of the command
# - if the command was a list, the command is printed as a readable string
raise RuntimeError(f"Command failed: {command_str}")
if result.returncode != 0:
print(
f"{Fore.YELLOW}Command exited with code {result.returncode}:{Fore.RESET} {command_str}",
file=sys.stderr,
)
return result

View File

@ -1,24 +0,0 @@
# Deskflow -- mouse and keyboard sharing utility
# Copyright (C) 2024 Symless Ltd.
#
# This package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# found in the file LICENSE that should have accompanied this file.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import colorama # type: ignore
from colorama import Fore # type: ignore
colorama.init()
SUCCESS_TEXT = f"{Fore.LIGHTGREEN_EX}Success:{Fore.RESET}"
ERROR_TEXT = f"{Fore.LIGHTRED_EX}Error:{Fore.RESET}"
WARNING_TEXT = f"{Fore.LIGHTYELLOW_EX}Warning:{Fore.RESET}"
HINT_TEXT = f"{Fore.LIGHTBLUE_EX}Hint:{Fore.RESET}"

View File

@ -1,269 +0,0 @@
# Deskflow -- mouse and keyboard sharing utility
# Copyright (C) 2024 Symless Ltd.
#
# This package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# found in the file LICENSE that should have accompanied this file.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os, sys, subprocess
import lib.cmd_utils as cmd_utils
# The `.venv` dir seems to be most common for virtual environments.
VENV_DIR = ".venv"
def check_module(module):
try:
__import__(module)
return True
except ImportError:
print(f"Python is missing {module} module", file=sys.stderr)
return False
def get_os():
"""Detects the operating system."""
if sys.platform == "win32":
return "windows"
elif sys.platform == "darwin":
return "mac"
elif sys.platform.startswith("linux"):
return "linux"
else:
raise RuntimeError(f"Unsupported platform: {sys.platform}")
def is_windows():
return get_os() == "windows"
def is_mac():
return get_os() == "mac"
def is_linux():
return get_os() == "linux"
def get_linux_distro():
"""Detects the Linux distro."""
os_file = "/etc/os-release"
name = None
name_like = None
version_id = None
version_codename = None
if os.path.isfile(os_file):
with open(os_file) as f:
for line in f:
if line.startswith("ID="):
name = line.strip().split("=")[1].strip('"')
elif line.startswith("ID_LIKE="):
name_like = line.strip().split("=")[1].strip('"')
elif line.startswith("VERSION_ID="):
version_id = line.strip().split("=")[1].strip('"')
elif line.startswith("VERSION_CODENAME="):
version_codename = line.strip().split("=")[1].strip('"')
return name, name_like, version_id or version_codename
def get_env(name, required=True, default=None):
"""
Returns an env var (stripped) or optionally raises an error if not set.
If `default` is set, it will be returned even if `required` is True.
"""
value = os.getenv(name)
if value:
value = value.strip()
if not value:
if default:
return default
elif required:
raise ValueError(f"Required env var not set: {name}")
return value
def get_env_bool(name, default=False):
"""Returns a boolean value from an env var (stripped)."""
value = os.getenv(name)
if value:
value = value.strip()
if value is None:
return default
return value.lower() in ["true", "1", "yes"]
def get_venv_executable(binary="python"):
if sys.platform == "win32":
return os.path.join(VENV_DIR, "Scripts", binary)
else:
return os.path.join(VENV_DIR, "bin", binary)
def in_venv():
"""Returns True if the script is running in a Python virtual environment."""
return sys.prefix != sys.base_prefix
def ensure_in_venv(script_file, create_venv=False):
"""
Ensures the script is running in a Python virtual environment (venv).
If the script is not running in a venv, it will create one and re-run the script in the venv.
"""
check_dependencies(raise_error=True)
import venv
if in_venv():
print(f"Running in venv, executable: {sys.executable}", flush=True)
return
if create_venv and not os.path.exists(VENV_DIR):
print(f"Creating virtual environment at {VENV_DIR}")
venv.create(VENV_DIR, with_pip=True)
if os.path.exists(VENV_DIR):
script_file_abs = os.path.abspath(script_file)
print(f"Using virtual environment for: {script_file_abs}", flush=True)
python_executable = get_venv_executable()
result = subprocess.run([python_executable, script_file_abs] + sys.argv[1:])
sys.exit(result.returncode)
else:
print(
"The Python virtual environment (.venv) needs to be created before you can "
"run this script.\n"
"Please run: scripts/setup_venv.py"
)
sys.exit(1)
def install_requirements():
"""
Uses `pip` to install required Python modules from the `requirements.txt` file.
"""
check_dependencies(raise_error=True)
print("Updating pip...")
cmd_utils.run(
[sys.executable, "-m", "pip", "install", "--upgrade", "pip"],
shell=False,
print_cmd=True,
)
print("Installing required modules...")
cmd_utils.run(
[sys.executable, "-m", "pip", "install", "-e", "scripts"],
shell=False,
print_cmd=True,
)
def check_dependencies(raise_error=False):
"""
Returns True if pip and venv are available.
"""
has_pip = check_module("pip")
has_venv = check_module("venv")
if raise_error:
if not has_pip:
raise RuntimeError("Python is missing pip")
if not has_venv:
raise RuntimeError("Python is missing venv")
else:
return has_pip and has_venv
def ensure_dependencies():
"""
Ensures that pip and venv are available, and installs them if they are not.
This is normally only installs on Linux, as Windows and Mac usually come with pip and venv.
"""
if check_dependencies():
return
print("Installing Python dependencies...")
os = get_os()
if os != "linux":
# should not be a problem, since windows and mac come with pip and venv
raise RuntimeError(f"Unable to install Python dependencies on {os}")
has_sudo = cmd_utils.has_command("sudo")
sudo = "sudo" if has_sudo else ""
distro, distro_like, _version = get_linux_distro()
if not distro_like:
distro_like = distro
update_cmd = None
install_cmd = None
if distro == "rhel" or "rhel" in distro_like:
update_cmd = "yum check-update"
install_cmd = "yum install -y python3-pip" # rhel-like has venv already
elif "debian" in distro_like:
update_cmd = "apt update"
install_cmd = "apt install -y python3-pip python3-venv"
elif "fedora" in distro_like:
update_cmd = "dnf check-update"
install_cmd = "dnf install -y python3-pip python3-virtualenv"
elif "arch" in distro_like:
install_cmd = "pacman -Syu --noconfirm python-pip python-virtualenv"
elif "opensuse" in distro_like:
update_cmd = "zypper refresh"
install_cmd = "zypper install -y python3-pip python3-virtualenv"
else:
raise RuntimeError(f"Unable to install Python dependencies on {distro}")
if update_cmd:
# don't check the return code, as some package managers return non-zero exit codes
# under normal circumstances (e.g. dnf check-update returns 100 when there are
# updates available).
cmd_utils.run(
f"{sudo} {update_cmd}".strip(), check=False, shell=True, print_cmd=True
)
cmd_utils.run(f"{sudo} {install_cmd}".strip(), shell=True, print_cmd=True)
def import_colors():
import lib.colors as colors
return colors
def persist_lock_file(path):
"""
Persists a lock file and ensures the directory part of the path exists.
"""
dir_path = os.path.dirname(path)
if not os.path.exists(dir_path):
os.makedirs(dir_path, exist_ok=True)
with open(path, "w") as f:
f.write(str(os.getpid()))
def remove_lock_file(path):
"""
Removes a lock file if it exists.
"""
if os.path.exists(path):
os.remove(path)

View File

@ -1,104 +0,0 @@
# Deskflow -- mouse and keyboard sharing utility
# Copyright (C) 2024 Symless Ltd.
#
# This package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# found in the file LICENSE that should have accompanied this file.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import glob, os, shutil, sys
import lib.env as env
import lib.colors as colors
class CopyOptions:
def __init__(self, ignore_errors, verbose):
self.ignore_errors = ignore_errors
self.verbose = verbose
class CopyContext:
def __init__(self):
self.errors = 0
self.permission_error = False
def copy(source, target, options):
"""Copy files and directories from source to target."""
context = CopyContext()
if options.verbose:
print(f"Copying files from {source} to {target}")
try:
for match in glob.glob(source):
if os.path.isfile(match):
copy_file(match, target, options, context)
elif os.path.isdir(match):
copy_dir(match, target, options, context)
else:
raise RuntimeError(f"Path {match} is not a file or directory")
finally:
if context.errors and options.ignore_errors:
print(f"{colors.WARNING_TEXT} Ignored {context.errors} copy error(s)")
if context.permission_error and env.is_windows():
print(
f"{colors.HINT_TEXT} A Windows file permission error may mean that the file is in use"
)
def copy_dir(match, target, options, context):
if options.verbose:
print(f"Copying directory {match} to {target}")
try:
shutil.copytree(match, target, dirs_exist_ok=True)
except Exception as e:
handle_all_copy_errors(e, options, context)
def copy_file(match, target, options, context):
if options.verbose:
print(f"Copying file {match} to {target}")
try:
shutil.copy(match, target)
except Exception as e:
handle_all_copy_errors(e, options, context)
def handle_all_copy_errors(error, options, context):
if isinstance(error, shutil.Error):
for _, _, file_error in error.args[0]:
handle_copy_error(file_error, options, context)
else:
handle_copy_error(error, options, context)
if not options.ignore_errors:
raise error
def handle_copy_error(error, options, context):
if isinstance(error, PermissionError):
context.permission_error = True
if isinstance(error, str):
context.permission_error = error.startswith("[Errno 13] Permission denied")
context.errors += 1
if options.ignore_errors:
print(
f"{colors.WARNING_TEXT} Copy failed: {error}",
file=sys.stderr,
)

View File

@ -1,29 +0,0 @@
# Deskflow -- mouse and keyboard sharing utility
# Copyright (C) 2024 Symless Ltd.
#
# This package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# found in the file LICENSE that should have accompanied this file.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os, fnmatch
def find_files(search_dirs, include_files, exclude_dirs=[]):
"""Recursively find files, excluding specified directories"""
matches = []
for dir in search_dirs:
for root, dirnames, filenames in os.walk(dir):
dirnames[:] = [d for d in dirnames if d not in exclude_dirs]
for pattern in include_files:
for filename in fnmatch.filter(filenames, pattern):
matches.append(os.path.join(root, filename))
return matches

View File

@ -1,347 +0,0 @@
# Deskflow -- mouse and keyboard sharing utility
# Copyright (C) 2024 Symless Ltd.
#
# This package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# found in the file LICENSE that should have accompanied this file.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import dmgbuild # type: ignore
import os, time, json, shutil, sys
import lib.cmd_utils as cmd_utils
import lib.env as env
from lib.certificate import Certificate
CERT_P12_ENV = "APPLE_P12_CERTIFICATE"
NOTARY_USER_ENV = "APPLE_NOTARY_USER"
CODESIGN_ENV = "APPLE_CODESIGN_ID"
SHELL_RC = "~/.zshrc"
SETTINGS_FILE = "deploy/dist/mac/dmgbuild/settings.py"
SECURITY_PATH = "/usr/bin/security"
SUDO_PATH = "/usr/bin/sudo"
NOTARYTOOL_PATH = "/usr/bin/notarytool"
CODESIGN_PATH = "/usr/bin/codesign"
XCODE_SELECT_PATH = "/usr/bin/xcode-select"
KEYCHAIN_PATH = "/Library/Keychains/System.keychain"
def set_env_var(name, value):
"""
Adds to an environment variable in the shell rc file.
Returns True if the variable was added, False if it already exists.
"""
text = f'export {name}="{value}:${name}"'
file = os.path.expanduser(SHELL_RC)
if os.path.exists(file):
with open(file, "r") as f:
if text in f.read():
return False
print(f"Setting environment variable: {name}={name}")
with open(file, "a") as f:
f.write(f"\n{text}\n")
print(f"Appended to {SHELL_RC}: {text}")
return True
def package(filename_base, source_dir, build_dir, dist_dir, product_name):
"""
Package the application for macOS.
The app bundle must be signed, or an error will occur:
> EXC_BAD_ACCESS (SIGKILL (Code Signature Invalid))
An "Apple Development" certificate is sufficient for local development.
"""
(
codesign_id,
cert_base64,
cert_password,
notary_user,
notary_password,
notary_team_id,
) = package_env_vars()
if cert_base64:
install_certificate(cert_base64, cert_password)
else:
print(
f"Warning: Skipped certificate installation, env var {CERT_P12_ENV} not set",
file=sys.stderr,
)
bundle_source_dir = os.path.join(
build_dir, os.path.join("bundle", product_name + ".app")
)
build_bundle(bundle_source_dir)
if codesign_id:
sign_bundle(bundle_source_dir, codesign_id)
else:
print(
f"Warning: Skipped code signing, env var {CODESIGN_ENV} not set",
file=sys.stderr,
)
dmg_path = build_dmg(
bundle_source_dir, filename_base, source_dir, dist_dir, product_name
)
if notary_user:
notarize_package(dmg_path, notary_user, notary_password, notary_team_id)
else:
print(
f"Warning: Skipped notarization, env var {NOTARY_USER_ENV} not set",
file=sys.stderr,
)
def package_env_vars():
codesign_id = env.get_env(CODESIGN_ENV, required=False)
cert_base64 = env.get_env(CERT_P12_ENV, required=False)
notary_user = env.get_env(NOTARY_USER_ENV, required=False)
if notary_user:
notary_password = env.get_env("APPLE_NOTARY_PASSWORD")
notary_team_id = env.get_env("APPLE_TEAM_ID")
else:
notary_password = None
notary_team_id = None
if cert_base64:
cert_password = env.get_env("APPLE_P12_PASSWORD")
else:
cert_password = None
return (
codesign_id,
cert_base64,
cert_password,
notary_user,
notary_password,
notary_team_id,
)
def build_bundle(bundle_source_dir):
# it's important to build a new bundle every time, so that we catch bugs with fresh builds.
if os.path.exists(bundle_source_dir):
print(f"Bundle already exists, deleting: {bundle_source_dir}")
shutil.rmtree(bundle_source_dir)
print("Building bundle...")
# cmake build install target should run macdeployqt
cmd_utils.run("cmake --build build --target install", shell=True, print_cmd=True)
def sign_bundle(bundle_source_dir, codesign_id):
print(f"Signing bundle {bundle_source_dir}...")
assert_certificate_installed(codesign_id)
cmd_utils.run(
[
CODESIGN_PATH,
"-f",
"--options",
"runtime",
"--deep",
"-s",
codesign_id,
bundle_source_dir,
]
)
def assert_certificate_installed(codesign_id):
print(f"Checking certificate: {codesign_id}")
installed = cmd_utils.run(
"security find-identity -v -p codesigning",
get_output=True,
shell=True,
print_cmd=True,
)
if codesign_id not in installed.stdout:
raise RuntimeError("Code signing certificate not installed or has expired")
def build_dmg(bundle_source_dir, filename_base, source_dir, dist_dir, product_name):
settings_path = (
SETTINGS_FILE if source_dir is None else os.path.join(source_dir, SETTINGS_FILE)
)
settings_path_abs = os.path.abspath(settings_path)
app_path_abs = os.path.abspath(bundle_source_dir)
# cwd for dmgbuild, since setting the dmg filename to a path (include the dist dir) seems to
# make the dmg disappear and never writes to the specified path. the dmgbuild module also
# creates a temporary file in cwd, so it makes sense to change to the dist dir.
print(f"Changing directory to: {os.path.abspath(dist_dir)}")
cwd = os.getcwd()
os.makedirs(dist_dir, exist_ok=True)
os.chdir(dist_dir)
try:
dmg_filename = f"{filename_base}.dmg"
dmg_path = os.path.join(dist_dir, dmg_filename)
print(f"Building package {dmg_path}...")
dmgbuild.build_dmg(
dmg_filename,
product_name,
settings_file=settings_path_abs,
defines={
"app": app_path_abs,
},
)
finally:
print(f"Changing directory back to: {cwd}")
os.chdir(cwd)
return dmg_path
def install_certificate(cert_base64, cert_password):
if not cert_base64:
raise ValueError("Certificate base 64 not provided")
if not cert_password:
raise ValueError("Certificate password not provided")
with Certificate(cert_base64, "p12") as cert_path:
print(f"Installing certificate: {cert_path}")
# WARNING: contains private key password, never print this command
cmd_utils.run(
[
SUDO_PATH,
SECURITY_PATH,
"import",
cert_path,
"-k",
KEYCHAIN_PATH,
"-P",
cert_password,
"-T",
CODESIGN_PATH,
"-T",
SECURITY_PATH,
],
)
def notarize_package(dmg_path, user, password, team_id):
print(f"Notarizing package {dmg_path}...")
notary_tool = NotaryTool()
notary_tool.store_credentials(user, password, team_id)
notary_tool.submit_and_wait(dmg_path)
def get_xcode_path():
result = cmd_utils.run(
[XCODE_SELECT_PATH, "-p"], get_output=True, shell=False, print_cmd=True
)
return result.stdout.strip()
class NotaryTool:
"""
Provides a wrapper around the notarytool command line tool.
"""
def __init__(self):
self.xcode_path = get_xcode_path()
def get_path(self):
return f"{self.xcode_path}{NOTARYTOOL_PATH}"
def store_credentials(self, user, password, team_id):
print("Storing credentials for notary tool...")
notarytool_path = self.get_path()
# WARNING: contains password, never print this command
cmd_utils.run(
[
notarytool_path,
"store-credentials",
"notarytool-password",
"--team-id",
team_id,
"--apple-id",
user,
"--password",
password,
]
)
def submit_and_wait(self, dmg_filename):
print("Submitting notarization request...")
submit_result = self.run_submit_command(dmg_filename)
request_id = submit_result["id"]
print(f"Notary submitted, waiting for request: {request_id}")
start = time.time()
wait_result = self.run_wait_command(request_id)
status = wait_result["status"]
time_taken = time.time() - start
print(f"Notary complete in {time_taken:.2f}s, status: {status}")
if status == "Accepted":
print("Notarization successful.")
elif status == "Invalid" or status == "Rejected":
raise ValueError(f"Notarization failed, status: {status}")
else:
raise ValueError(f"Unknown status: {status}")
def run_submit_command(self, dmg_filename):
if not os.path.exists(dmg_filename):
raise FileNotFoundError(f"File not found: {dmg_filename}")
result = cmd_utils.run(
[
self.get_path(),
"submit",
dmg_filename,
"--keychain-profile",
"notarytool-password",
"--output-format",
"json",
],
get_output=True,
shell=False,
print_cmd=True,
)
if result.stderr:
return json.loads(result.stderr)
else:
return json.loads(result.stdout)
def run_wait_command(self, request_id):
result = cmd_utils.run(
[
self.get_path(),
"wait",
request_id,
"--keychain-profile",
"notarytool-password",
"--output-format",
"json",
],
get_output=True,
shell=False,
print_cmd=True,
)
if result.stderr:
return json.loads(result.stderr)
else:
return json.loads(result.stdout)

View File

@ -1,329 +0,0 @@
# Deskflow -- mouse and keyboard sharing utility
# Copyright (C) 2024 Symless Ltd.
#
# This package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# found in the file LICENSE that should have accompanied this file.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import ctypes, sys, os, shutil, time, subprocess
import xml.etree.ElementTree as ET
import lib.cmd_utils as cmd_utils
import lib.env as env
import psutil # type: ignore
from lib.certificate import Certificate
import lib.colors as colors
import lib.file_utils as file_utils
LOCK_FILE = "tmp/elevated.lock"
MSBUILD_CMD = "msbuild"
SIGNTOOL_CMD = "signtool"
CERTUTIL_CMD = "certutil"
RUNNER_TEMP_ENV = "RUNNER_TEMP"
SERVICE_NOT_RUNNING_ERROR = 2
ERROR_ACCESS_VIOLATION = 0xC0000005
def run_elevated(script, args=None, use_sys_argv=True, wait_for_exit=False):
if not args and use_sys_argv:
args = " ".join(sys.argv[1:])
if wait_for_exit:
args += f" --lock-file {LOCK_FILE}"
env.persist_lock_file(LOCK_FILE)
command = f"{script} {args} --pause-on-exit"
print(f"Running script with elevated privileges: {command}")
WINDOW_HANDLE = None
OPERATION = "runas"
DIRECTORY = None
SHOW_CMD = 1
instance = ctypes.windll.shell32.ShellExecuteW(
WINDOW_HANDLE, OPERATION, sys.executable, command, DIRECTORY, SHOW_CMD
)
ERROR_ACCESS_DENIED = 5
if instance == ERROR_ACCESS_DENIED:
raise RuntimeError(
f"Failed to run script with elevated privileges, access denied (code {instance})"
)
ERROR_MAX = 32
if instance <= ERROR_MAX:
raise RuntimeError(
f"Failed to run script with elevated privileges, error code: {instance}"
)
print("Script is running with elevated privileges")
if wait_for_exit:
with open(LOCK_FILE, "r") as f:
pid = f.read()
print(f"Waiting for elevated process to exit: {pid}")
while os.path.exists(LOCK_FILE):
# Intentionally wait forever, since this code should not run where a developer
# has no control, such as in a CI environment.
pass
def is_admin():
"""Returns True if the current process has admin privileges."""
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except ctypes.WinError:
return False
def set_env_var(name, value):
"""
Sets or updates an environment variable. Appends the value if it doesn't already exist.
Args:
name (str): The name of the environment variable.
value (str): The value of the environment variable.
"""
current_value = os.getenv(name, "")
if value not in current_value:
new_value = f"{current_value}{os.pathsep}{value}" if current_value else value
os.environ[name] = new_value
print(f"Setting environment variable: {name}={value}")
cmd_utils.run(["setx", name, new_value], check=True, shell=True, print_cmd=True)
def package(filename_base, build_dir, dist_dir):
cert_env_key = "WINDOWS_PFX_CERTIFICATE"
cert_base64 = env.get_env(cert_env_key, required=False)
packager = WindowsPackager(filename_base, build_dir, dist_dir)
if cert_base64:
cert_password = env.get_env("WINDOWS_PFX_PASSWORD")
packager.sign_binaries(cert_base64, cert_password)
packager.build_msi()
if cert_base64:
packager.sign_msi(cert_base64, cert_password)
else:
print(f"Skipped code signing, env var not set: {cert_env_key}")
def assert_vs_cmd(cmd):
has_cmd = cmd_utils.has_command(cmd)
if not has_cmd:
raise RuntimeError(
f"The '{cmd}' command was not found, "
"re-run from 'Developer Command Prompt for VS'"
)
def run_codesign(path, cert_base64, cert_password):
time_server = "http://timestamp.digicert.com"
hashing_algorithm = "SHA256"
with Certificate(cert_base64, "pfx") as cert_path:
print("Signing MSI installer...")
assert_vs_cmd(SIGNTOOL_CMD)
# WARNING: contains private key password, never print this command
cmd_utils.run(
[
SIGNTOOL_CMD,
"sign",
"/f",
cert_path,
"/p",
cert_password,
"/t",
time_server,
"/fd",
hashing_algorithm,
path,
]
)
class WindowsPackager:
def __init__(self, filename_base, build_dir, dist_dir):
self.filename_base = filename_base
self.build_dir = build_dir
self.dist_dir = dist_dir
self.wix_file = f"{build_dir}/installer/Installer.sln"
self.msi_file = f"{build_dir}/installer/bin/Release/Installer.msi"
def build_msi(self):
print("Building MSI installer...")
configuration = "Release"
platform = "x64"
assert_vs_cmd(MSBUILD_CMD)
cmd_utils.run(
[
MSBUILD_CMD,
self.wix_file,
f"/p:Configuration={configuration}",
f"/p:Platform={platform}",
],
shell=True,
print_cmd=True,
)
path = self.get_package_path()
print(f"Copying MSI installer to {self.dist_dir}")
os.makedirs(self.dist_dir, exist_ok=True)
shutil.copy(self.msi_file, path)
def get_package_path(self):
return f"{self.dist_dir}/{self.filename_base}.msi"
def sign_binaries(self, cert_base64, cert_password):
exe_pattern = f"{self.build_dir}/bin/*.exe"
run_codesign(exe_pattern, cert_base64, cert_password)
def sign_msi(self, cert_base64, cert_password):
path = self.get_package_path()
run_codesign(path, cert_base64, cert_password)
class WindowsService:
def __init__(self, script, args):
self.script = script
self.verbose = args.verbose
self.bin_name = args.bin_name
self.source_dir = os.path.abspath(args.source_dir)
self.target_dir = os.path.abspath(args.target_dir)
self.service_id = args.service_id
self.ignore_processes = args.ignore_processes
def print_verbose(self, message):
if self.verbose:
print(message)
def ensure_admin(self):
if not is_admin():
run_elevated(self.script)
sys.exit()
def restart(self):
"""Stops the daemon service, copies files, and restarts the daemon service."""
self.ensure_admin()
self.stop()
self.copy_files()
self.start()
def reinstall(self):
"""Stops and uninstalls daemon service, copies files, and reinstalls the daemon service."""
self.ensure_admin()
self.stop()
source_bin_path = f"{os.path.join(self.source_dir, self.bin_name)}.exe"
self.copy_files()
print("Removing old daemon service")
try:
subprocess.run([source_bin_path, "/uninstall"], shell=True, check=True)
except subprocess.CalledProcessError as e:
self.check_access_violation(e.returncode, source_bin_path)
if e.returncode != 0:
print(
f"{colors.WARNING_TEXT} Uninstall failed, return code: {e.returncode}",
file=sys.stderr,
)
target_bin_path = os.path.join(self.target_dir, self.bin_name + ".exe")
try:
print("Installing daemon service")
subprocess.run([target_bin_path, "/install"], shell=True, check=True)
except subprocess.CalledProcessError as e:
self.check_access_violation(e.returncode, target_bin_path)
if e.returncode != 0:
print(
f"{colors.WARNING_TEXT} Install failed, return code: {e.returncode}"
)
def copy_files(self):
options = file_utils.CopyOptions(ignore_errors=True, verbose=False)
print(f"Copying files from {self.source_dir} to {self.target_dir}")
file_utils.copy(f"{self.source_dir}/*", self.target_dir, options)
def stop(self):
self.ensure_admin()
print("Stopping daemon service")
try:
subprocess.run(["net", "stop", self.service_id], shell=True, check=True)
except subprocess.CalledProcessError as e:
if e.returncode == SERVICE_NOT_RUNNING_ERROR:
self.print_verbose("Daemon service not running")
else:
raise e
# Wait for Windows to release the file handles after process termination.
self.wait_for_stop()
def start(self):
self.ensure_admin()
print("Starting daemon service")
subprocess.run(["net", "start", self.service_id], shell=True, check=True)
def wait_for_stop(self):
if self.is_any_process_running(self.target_dir):
print("Waiting for file handles to release...", end="", flush=True)
while self.is_any_process_running(self.target_dir):
if not self.verbose:
print(".", end="", flush=True)
time.sleep(1)
if not self.verbose:
print()
def check_access_violation(self, return_code, bin_path):
if return_code == ERROR_ACCESS_VIOLATION:
print(
f"{colors.WARNING_TEXT} Process crashed with memory access violation: {bin_path}",
file=sys.stderr,
)
def is_ignored_process(self, exe):
for ignore_process in self.ignore_processes:
if exe.endswith(ignore_process):
return True
return False
def is_any_process_running(self, dir):
"""Check if there is any running process that contains the given directory."""
self.print_verbose(f"Checking if any process is running in: {dir}")
for proc in psutil.process_iter(attrs=["name", "exe"]):
exe = proc.info["exe"]
if not exe:
self.print_verbose(f"Skipping process with no exe: {proc}")
continue
if self.is_ignored_process(exe):
self.print_verbose(f"Ignoring process: {exe}")
continue
try:
if dir.lower() in exe.lower():
self.print_verbose(f"Process found: {exe}")
return True
except (psutil.NoSuchProcess, psutil.AccessDenied):
pass
return False

View File

@ -1,74 +0,0 @@
#!/usr/bin/env python3
# Deskflow -- mouse and keyboard sharing utility
# Copyright (C) 2024 Symless Ltd.
#
# This package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# found in the file LICENSE that should have accompanied this file.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import lib.env as env
env.ensure_in_venv(__file__)
import argparse, sys
import lib.fs as fs
from clang_format import clang_format # type: ignore
include_files = [
"*.h",
"*.c",
"*.hpp",
"*.cpp",
]
dirs = ["src"]
def main():
"""
Cross-platform equivalent of using find and xargs with clang-format.
Lints by performing a dry run (--dry-run) which fails when formatting is needed.
"""
parser = argparse.ArgumentParser()
parser.add_argument(
"-f",
"--format",
action="store_true",
help="In-place format all files",
)
args = parser.parse_args()
cmd_args = ["-i"] if args.format else ["--dry-run", "--Werror"]
files_recursive = fs.find_files(dirs, include_files)
if args.format:
print("Formatting files with Clang formatter:")
else:
print("Checking files with Clang formatter:")
for file in files_recursive:
print(file)
if files_recursive:
sys.argv = [""] + cmd_args + files_recursive
result = clang_format()
if result == 0:
print("Clang lint passed")
sys.exit(result)
else:
print("No files for Clang to process", file=sys.stderr)
sys.exit(0)
if __name__ == "__main__":
main()

View File

@ -1,109 +0,0 @@
#!/usr/bin/env python3
# Deskflow -- mouse and keyboard sharing utility
# Copyright (C) 2024 Symless Ltd.
#
# This package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# found in the file LICENSE that should have accompanied this file.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import lib.env as env
env.ensure_in_venv(__file__)
import argparse
import platform
from dotenv import load_dotenv # type: ignore
ENV_FILE = ".env"
DEFAULT_PRODUCT_NAME = "Deskflow"
DEFAULT_FILENAME_BASE = "deskflow"
DEFAULT_PROJECT_BUILD_DIR = "build"
DEFAULT_DIST_DIR = "dist"
DEFAULT_PACKAGE_NAME = "deskflow"
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"--package-version",
help="Set the Package Version",
required=True)
parser.add_argument(
"--leave-test-installed",
action="store_true",
help="Leave test package installed",
)
args = parser.parse_args()
load_dotenv(dotenv_path=ENV_FILE)
package(
DEFAULT_FILENAME_BASE,
DEFAULT_PROJECT_BUILD_DIR,
DEFAULT_DIST_DIR,
DEFAULT_PRODUCT_NAME,
version=args.package_version,
)
def package(
filename_prefix,
project_build_dir,
dist_dir,
product_name,
version,
source_dir=None,
):
filename_base = get_filename_base(version, filename_prefix)
print(f"Package filename base: {filename_base}")
if env.is_windows():
windows_package(filename_base, project_build_dir, dist_dir)
elif env.is_mac():
mac_package(
filename_base, source_dir, project_build_dir, dist_dir, product_name
)
else:
raise RuntimeError(f"Unsupported platform: {env.get_os()}")
def get_filename_base(version, prefix):
os = env.get_os()
machine = platform.machine().lower()
os_part = os
if os == "windows":
# Some Windows users get confused by 'amd64' and think it's 'arm64',
# so we'll use Intel's 'x64' branding (even though it's wrong).
# Also replace 'x86_64' with 'x64' for consistency.
os_part= "win"
if machine == "amd64" or machine == "x86_64":
machine = "x64"
elif os == "mac":
os_part = "macos"
# Add '-' between our name parts we do not want spaces in the filename
return f"{prefix}-{version}-{os_part}-{machine}"
def windows_package(filename_base, project_build_dir, dist_dir):
import lib.windows as windows
windows.package(filename_base, project_build_dir, dist_dir)
def mac_package(filename_base, source_dir, project_build_dir, dist_dir, product_name):
import lib.mac as mac
mac.package(filename_base, source_dir, project_build_dir, dist_dir, product_name)
if __name__ == "__main__":
main()

View File

@ -1,14 +0,0 @@
[project]
name = "scripts"
version = "0.0.1"
description = "Scripts to assist with development of Deskflow"
requires-python = ">=3.9"
dependencies = [
"clang-format",
"python-dotenv",
"pyyaml",
"dmgbuild; sys_platform == 'darwin'",
"colorama",
"gitpython",
"psutil",
]

View File

@ -1,25 +0,0 @@
#!/usr/bin/env python3
# Deskflow -- mouse and keyboard sharing utility
# Copyright (C) 2024 Symless Ltd.
#
# This package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# found in the file LICENSE that should have accompanied this file.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import lib.env as env
env.ensure_in_venv(__file__, create_venv=True)
env.install_requirements()
import lib.colors as colors
print(colors.SUCCESS_TEXT, "Python virtual environment is ready.")

View File

@ -1,9 +1,9 @@
sonar.organization=deskflow
sonar.projectKey=deskflow_deskflow
sonar.sources=scripts,src/cmd,src/gui,src/lib
sonar.sources=src/apps,src/lib
sonar.tests=src/test
sonar.exclusions=subprojects/**,build/**
sonar.coverage.exclusions=subprojects/**,scripts/**,src/test/**,src/gui/**
sonar.coverage.exclusions=subprojects/**,src/test/**,src/apps/deskflow-gui/**,src/apps/res/**
sonar.cpd.exclusions=**/*Test*.cpp
sonar.host.url=https://sonarcloud.io
sonar.coverageReportPaths=${{ steps.coverage-paths.outputs.csv }}

View File

@ -18,12 +18,9 @@ include_directories(./lib)
include_directories(${CMAKE_CURRENT_BINARY_DIR}/lib)
add_subdirectory(lib)
add_subdirectory(cmd)
if(BUILD_GUI)
add_subdirectory(gui)
endif(BUILD_GUI)
add_subdirectory(apps)
option(BUILD_TESTS "Build tests" ON)
if(BUILD_TESTS)
add_subdirectory(test)
endif()

View File

@ -14,13 +14,18 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
option(BUILD_UNIFIED "Build unified binary" OFF)
if(BUILD_UNIFIED)
add_subdirectory(deskflow-core)
else()
add_subdirectory(deskflowc)
add_subdirectory(deskflows)
add_subdirectory(deskflow-client)
add_subdirectory(deskflow-server)
endif(BUILD_UNIFIED)
## Only used on windows
add_subdirectory(deskflowd)
add_subdirectory(deskflow-daemon)
option(BUILD_GUI "Build GUI" ON)
if(BUILD_GUI)
add_subdirectory(deskflow-gui)
endif(BUILD_GUI)

View File

@ -19,16 +19,15 @@ set(target deskflow-client)
if(WIN32)
set(PLATFORM_SOURCES
deskflowc.exe.manifest
deskflowc.ico
deskflowc.rc
deskflow-client.exe.manifest
deskflow-client.rc
MSWindowsClientTaskBarReceiver.cpp
MSWindowsClientTaskBarReceiver.h
resource.h
tb_error.ico
tb_idle.ico
tb_run.ico
tb_wait.ico
${PROJECT_SOURCE_DIR}/src/apps/res/deskflow.ico
${PROJECT_SOURCE_DIR}/src/apps/res/tb_error.ico
${PROJECT_SOURCE_DIR}/src/apps/res/tb_idle.ico
${PROJECT_SOURCE_DIR}/src/apps/res/tb_run.ico
${PROJECT_BINARY_DIR}/src/version.rc
)
elseif(APPLE)
@ -43,7 +42,7 @@ elseif(UNIX)
)
endif()
add_executable(${target} ${PLATFORM_SOURCES} deskflowc.cpp)
add_executable(${target} ${PLATFORM_SOURCES} deskflow-client.cpp)
target_link_libraries(
${target}
@ -60,9 +59,23 @@ target_link_libraries(
${libs})
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
install(TARGETS ${target} DESTINATION ${DESKFLOW_BUNDLE_BINARY_DIR})
set_target_properties(${target} PROPERTIES RUNTIME_OUTPUT_DIRECTORY $<TARGET_BUNDLE_CONTENT_DIR:Deskflow>/MacOS)
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
install(TARGETS ${target} DESTINATION bin)
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
install(
TARGETS ${target}
RUNTIME_DEPENDENCY_SET clientDeps
DESTINATION .
)
install(RUNTIME_DEPENDENCY_SET clientDeps
PRE_EXCLUDE_REGEXES
"api-ms-win-.*"
"ext-ms-.*"
"^hvsifiletrust\\.dll$"
POST_EXCLUDE_REGEXES
".*system32.*"
RUNTIME DESTINATION .
)
endif()
post_config()

View File

@ -34,8 +34,7 @@
//
const UINT MSWindowsClientTaskBarReceiver::s_stateToIconID[kMaxState] = {
IDI_TASKBAR_NOT_RUNNING, IDI_TASKBAR_NOT_WORKING, IDI_TASKBAR_NOT_CONNECTED, IDI_TASKBAR_NOT_CONNECTED,
IDI_TASKBAR_CONNECTED
IDI_TASKBAR_NOT_RUNNING, IDI_TASKBAR_NOT_WORKING, IDI_TASKBAR_NOT_CONNECTED, IDI_TASKBAR_CONNECTED
};
MSWindowsClientTaskBarReceiver::MSWindowsClientTaskBarReceiver(
@ -203,7 +202,7 @@ void MSWindowsClientTaskBarReceiver::primaryAction()
showStatus();
}
const IArchTaskBarReceiver::Icon MSWindowsClientTaskBarReceiver::getIcon() const
IArchTaskBarReceiver::Icon MSWindowsClientTaskBarReceiver::getIcon() const
{
return static_cast<Icon>(m_icon[getStatus()]);
}

View File

@ -34,10 +34,10 @@ public:
virtual ~MSWindowsClientTaskBarReceiver();
// IArchTaskBarReceiver overrides
virtual void showStatus();
virtual void runMenu(int x, int y);
virtual void primaryAction();
virtual const Icon getIcon() const;
void showStatus() override;
void runMenu(int x, int y) override;
void primaryAction() override;
Icon getIcon() const override;
void cleanup();
protected:

View File

@ -50,9 +50,9 @@ void OSXClientTaskBarReceiver::primaryAction()
// do nothing
}
const IArchTaskBarReceiver::Icon OSXClientTaskBarReceiver::getIcon() const
IArchTaskBarReceiver::Icon OSXClientTaskBarReceiver::getIcon() const
{
return NULL;
return nullptr;
}
IArchTaskBarReceiver *createTaskBarReceiver(const BufferedLogOutputter *logBuffer, IEventQueue *events)

View File

@ -31,8 +31,8 @@ public:
virtual ~OSXClientTaskBarReceiver();
// IArchTaskBarReceiver overrides
virtual void showStatus();
virtual void runMenu(int x, int y);
virtual void primaryAction();
virtual const Icon getIcon() const;
void showStatus() override;
void runMenu(int x, int y) override;
void primaryAction() override;
Icon getIcon() const override;
};

View File

@ -50,9 +50,9 @@ void CXWindowsClientTaskBarReceiver::primaryAction()
// do nothing
}
const IArchTaskBarReceiver::Icon CXWindowsClientTaskBarReceiver::getIcon() const
IArchTaskBarReceiver::Icon CXWindowsClientTaskBarReceiver::getIcon() const
{
return NULL;
return nullptr;
}
IArchTaskBarReceiver *createTaskBarReceiver(const BufferedLogOutputter *logBuffer, IEventQueue *events)

View File

@ -36,8 +36,8 @@ public:
CXWindowsClientTaskBarReceiver &operator=(CXWindowsClientTaskBarReceiver &&) = delete;
// IArchTaskBarReceiver overrides
virtual void showStatus();
virtual void runMenu(int x, int y);
virtual void primaryAction();
virtual const Icon getIcon() const;
void showStatus() override;
void runMenu(int x, int y) override;
void primaryAction() override;
Icon getIcon() const override;
};

View File

@ -57,11 +57,11 @@ END
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_DESKFLOW ICON DISCARDABLE "deskflowc.ico"
IDI_TASKBAR_NOT_RUNNING ICON DISCARDABLE "tb_idle.ico"
IDI_TASKBAR_NOT_WORKING ICON DISCARDABLE "tb_error.ico"
IDI_TASKBAR_NOT_CONNECTED ICON DISCARDABLE "tb_wait.ico"
IDI_TASKBAR_CONNECTED ICON DISCARDABLE "tb_run.ico"
IDI_DESKFLOW ICON DISCARDABLE "../res/deskflow.ico"
IDI_TASKBAR_NOT_RUNNING ICON DISCARDABLE "../res/tb_idle.ico"
IDI_TASKBAR_NOT_WORKING ICON DISCARDABLE "../res/tb_error.ico"
IDI_TASKBAR_NOT_CONNECTED ICON DISCARDABLE "../res/deskflow.ico"
IDI_TASKBAR_CONNECTED ICON DISCARDABLE "../res/tb_run.ico"
/////////////////////////////////////////////////////////////////////////////
//

View File

@ -40,7 +40,22 @@ target_link_libraries(
${libs})
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
install(TARGETS ${target} DESTINATION ${DESKFLOW_BUNDLE_BINARY_DIR})
set_target_properties(${target} PROPERTIES RUNTIME_OUTPUT_DIRECTORY $<TARGET_BUNDLE_CONTENT_DIR:Deskflow>/MacOS)
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
install(TARGETS ${target} DESTINATION bin)
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
install(
TARGETS ${target}
RUNTIME_DEPENDENCY_SET coreDeps
DESTINATION .
)
install(RUNTIME_DEPENDENCY_SET coreDeps
PRE_EXCLUDE_REGEXES
"api-ms-win-.*"
"ext-ms-.*"
"^hvsifiletrust\\.dll$"
POST_EXCLUDE_REGEXES
".*system32.*"
RUNTIME DESTINATION .
)
endif()

View File

@ -19,7 +19,7 @@
if(WIN32)
set(target deskflow-daemon)
add_executable(${target} WIN32
deskflowd.cpp
deskflow-daemon.cpp
"${PROJECT_BINARY_DIR}/src/version.rc"
)
@ -35,6 +35,18 @@ if(WIN32)
app
${libs})
post_config()
install(
TARGETS ${target}
RUNTIME_DEPENDENCY_SET deamonDeps
DESTINATION .
)
install(RUNTIME_DEPENDENCY_SET daemonDeps
PRE_EXCLUDE_REGEXES
"api-ms-win-.*"
"ext-ms-.*"
"^hvsifiletrust\\.dll$"
POST_EXCLUDE_REGEXES
".*system32.*"
RUNTIME DESTINATION .
)
endif()

View File

@ -29,18 +29,6 @@ const char *Action::m_ActionTypeNames[] = {
const char *Action::m_SwitchDirectionNames[] = {"left", "right", "up", "down"};
const char *Action::m_LockCursorModeNames[] = {"toggle", "on", "off"};
Action::Action()
: m_KeySequence(),
m_Type(keystroke),
m_TypeScreenNames(),
m_SwitchScreenName(),
m_SwitchDirection(switchLeft),
m_LockCursorMode(lockCursorToggle),
m_ActiveOnRelease(false),
m_HasScreens(false)
{
}
QString Action::text() const
{
QString text = QString(m_ActionTypeNames[keySequence().isMouseButton() ? type() + 6 : type()]) + "(";

View File

@ -62,7 +62,7 @@ public:
};
public:
Action();
Action() = default;
public:
QString text() const;
@ -151,13 +151,13 @@ protected:
private:
KeySequence m_KeySequence;
int m_Type;
QStringList m_TypeScreenNames;
QString m_SwitchScreenName;
int m_SwitchDirection;
int m_LockCursorMode;
bool m_ActiveOnRelease;
bool m_HasScreens;
int m_Type = keystroke;
QStringList m_TypeScreenNames = QStringList();
QString m_SwitchScreenName = QString();
int m_SwitchDirection = switchLeft;
int m_LockCursorMode = lockCursorToggle;
bool m_ActiveOnRelease = false;
bool m_HasScreens = false;
bool m_restartServer;
static const char *m_ActionTypeNames[];
@ -165,6 +165,6 @@ private:
static const char *m_LockCursorModeNames[];
};
typedef QList<Action> ActionList;
using ActionList = QList<Action>;
QTextStream &operator<<(QTextStream &outStream, const Action &action);

View File

@ -0,0 +1,38 @@
#import "AppDelegate.h"
@interface AppDelegate ()
@property(strong) IBOutlet NSWindow *window;
@end
@implementation AppDelegate {
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];
#if OSX_DEPLOYMENT_TARGET >= 1014
[[UNUserNotificationCenter currentNotificationCenter] setDelegate:self];
#endif
}
- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center
shouldPresentNotification:(NSUserNotification *)notification
{
return YES;
}
#if OSX_DEPLOYMENT_TARGET >= 1014
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
{
UNNotificationPresentationOptions presentationOptions = UNNotificationPresentationOptionSound |
UNNotificationPresentationOptionAlert |
UNNotificationPresentationOptionBadge;
completionHandler(presentationOptions);
}
#endif
@end

View File

@ -0,0 +1,110 @@
# Deskflow -- mouse and keyboard sharing utility
# Copyright (C) 2024 Symless Ltd.
#
# This package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# found in the file LICENSE that should have accompanied this file.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
if(APPLE)
set(target Deskflow)
else()
set(target deskflow)
endif()
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
file(
GLOB
sources
../res/deskflow.qrc
*.cpp
*.h
dialogs/*.h
dialogs/*.cpp
validators/*
widgets/*)
file(GLOB ui_files *.ui dialogs/*.ui)
if(WIN32)
set(platform_extra deskflow.rc ${PROJECT_BINARY_DIR}/src/version.rc)
elseif(APPLE)
set(platform_extra Deskflow.icns ${PROJECT_SOURCE_DIR}/LICENSE ${PROJECT_SOURCE_DIR}/LICENSE_EXCEPTION)
set_source_files_properties(${platform_extra} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
endif()
# gui library autogen headers:
# qt doesn't seem to auto include the autogen headers for libraries.
include_directories(${PROJECT_BINARY_DIR}/src/lib/gui/gui_autogen/include)
# generated includes
include_directories(${PROJECT_BINARY_DIR}/config)
add_executable(${target} WIN32 MACOSX_BUNDLE ${sources} ${ui_files} ${platform_extra})
target_link_libraries(
${target}
${DESKFLOW_GUI_HOOK_LIB}
gui
Qt6::Core
Qt6::Widgets
Qt6::Network)
if(WIN32)
set_target_properties(${target} PROPERTIES LINK_FLAGS "/NODEFAULTLIB:LIBCMT")
install(
TARGETS ${target}
RUNTIME_DEPENDENCY_SET guiDeps
DESTINATION .
)
install(RUNTIME_DEPENDENCY_SET guiDeps
PRE_EXCLUDE_REGEXES
"api-ms-win-.*"
"ext-ms-.*"
"^hvsifiletrust\\.dll$"
POST_EXCLUDE_REGEXES
".*system32.*"
RUNTIME DESTINATION .
)
find_program(DEPLOYQT windeployqt6)
add_custom_command(
TARGET ${target} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/qtDeploy
COMMAND ${DEPLOYQT} --no-compiler-runtime --no-system-d3d-compiler --no-quick-import -network --dir ${CMAKE_BINARY_DIR}/qtDeploy $<TARGET_FILE:${target}>
)
install(
DIRECTORY ${CMAKE_BINARY_DIR}/qtDeploy/
DESTINATION .
FILES_MATCHING PATTERN "*.*"
)
elseif(APPLE)
set_target_properties(${target} PROPERTIES
MACOSX_BUNDLE_BUNDLE_NAME "Deskflow"
MACOSX_BUNDLE_DISPLAY_NAME "Deskflow"
MACOSX_BUNDLE_GUI_IDENTIFIER "org.deskflow.deskflow"
MACOSX_BUNDLE_ICON_FILE Deskflow.icns
MACOSX_BUNDLE_INFO_STRING "${CMAKE_PROJECT_DESCRIPTION}"
MACOSX_BUNDLE_COPYRIGHT "© 2024 Deskflow Developers"
MACOSX_BUNDLE_BUNDLE_VERSION ${CMAKE_PROJECT_VERSION}
MACOSX_BUNDLE_LONG_VERSION_STRING ${CMAKE_PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${CMAKE_PROJECT_VERSION}
)
find_program(MACDEPLOYQT_BIN macdeployqt6)
add_custom_command(
TARGET ${target} POST_BUILD
COMMAND ${MACDEPLOYQT_BIN} "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target}.app"
)
install(TARGETS ${target} BUNDLE DESTINATION .)
else()
install(TARGETS ${target} DESTINATION bin)
endif()

View File

@ -72,6 +72,6 @@ private:
ActionList m_Actions;
};
typedef QList<Hotkey> HotkeyList;
using HotkeyList = QList<Hotkey>;
QTextStream &operator<<(QTextStream &outStream, const Hotkey &hotkey);

View File

@ -1,5 +1,6 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* Copyright (C) 2024 Chris Rizzitello <sithlord48@gmail.com>
* Copyright (C) 2012 Symless Ltd.
* Copyright (C) 2008 Volker Lanz (vl@fidra.de)
*
@ -82,16 +83,62 @@ MainWindow::MainWindow(ConfigScopes &configScopes, AppConfig &appConfig)
m_ServerConnection(this, appConfig, m_ServerConfig, m_ServerConfigDialogState),
m_ClientConnection(this, appConfig),
m_TlsUtility(appConfig),
m_WindowSaveTimer(this)
m_WindowSaveTimer(this),
m_actionAbout{new QAction(this)},
m_actionClearSettings{new QAction(tr("Clear settings"), this)},
m_actionHelp{new QAction(tr("Report a Bug"), this)},
m_actionMinimize{new QAction(tr("&Hide"), this)},
m_actionQuit{new QAction(tr("&Quit"), this)},
m_actionRestore{new QAction(tr("Show"), this)},
m_actionSave{new QAction(tr("Save configuration &as..."), this)},
m_actionSettings{new QAction(tr("Preferences"), this)},
m_actionStartCore{new QAction(tr("&Start"), this)},
m_actionStopCore{new QAction(tr("S&top"), this)},
m_actionTestCriticalError{new QAction(tr("Test Critical Error"), this)},
m_actionTestFatalError{new QAction(tr("Test Fatal Error"), this)}
{
ui->setupUi(this);
// Setup Actions
m_actionAbout->setText(tr("About %1...").arg(kAppName));
m_actionAbout->setMenuRole(QAction::AboutRole);
m_actionQuit->setShortcut(QKeySequence::Quit);
m_actionQuit->setMenuRole(QAction::QuitRole);
m_actionSettings->setMenuRole(QAction::PreferencesRole);
m_actionSave->setShortcut(QKeySequence(tr("Ctrl+Alt+S")));
m_actionStartCore->setShortcut(QKeySequence(tr("Ctrl+S")));
m_actionStopCore->setShortcut(QKeySequence(tr("Ctrl+T")));
#ifdef Q_OS_MAC
ui->btnToggleLog->setFixedHeight(ui->lblLog->height() * 0.6);
#endif
ui->btnToggleLog->setStyleSheet(QStringLiteral("background:rgba(0,0,0,0);"));
if (m_AppConfig.logExpanded())
ui->btnToggleLog->click();
toggleLogVisible(m_AppConfig.logExpanded());
createMenuBar();
setupControls();
connectSlots();
// handled by `onCreated`
Q_EMIT created();
setIcon();
m_ConfigScopes.signalReady();
applyCloseToTray();
updateScreenName();
applyConfig();
restoreWindow();
qDebug().noquote() << "active settings path:" << m_ConfigScopes.activeFilePath();
updateSize();
}
MainWindow::~MainWindow()
@ -101,17 +148,31 @@ MainWindow::~MainWindow()
void MainWindow::restoreWindow()
{
const auto &windowSize = m_AppConfig.mainWindowSize();
if (windowSize.has_value()) {
qDebug("restoring main window size");
qDebug() << "restoring main window size";
resize(windowSize.value());
}
const auto &windowPosition = m_AppConfig.mainWindowPosition();
if (windowPosition.has_value()) {
qDebug("restoring main window position");
move(windowPosition.value());
int x = 0;
int y = 0;
int w = 0;
int h = 0;
for (auto screen : QGuiApplication::screens()) {
auto geo = screen->geometry();
x = std::min(geo.x(), x);
y = std::min(geo.y(), y);
w = std::max(geo.x() + geo.width(), w);
h = std::max(geo.y() + geo.height(), h);
}
const QSize totalScreenSize(w, h);
const QPoint point = windowPosition.value();
if (point.x() < totalScreenSize.width() && point.y() < totalScreenSize.height()) {
qDebug() << "restoring main window position";
move(point);
}
} else {
// center main window in middle of screen
const auto screen = QGuiApplication::primaryScreen();
@ -128,11 +189,11 @@ void MainWindow::restoreWindow()
void MainWindow::saveWindow()
{
if (!m_SaveWindow) {
qDebug("not yet ready to save window size and position, skipping");
qDebug() << "not yet ready to save window size and position, skipping";
return;
}
qDebug("saving window size and position");
qDebug() << "saving window size and position";
m_AppConfig.setMainWindowSize(size());
m_AppConfig.setMainWindowPosition(pos());
m_ConfigScopes.save();
@ -142,8 +203,6 @@ void MainWindow::setupControls()
{
setWindowTitle(kAppName);
ui->m_pActionHelp->setText(tr("Report a Bug"));
secureSocket(false);
updateLocalFingerprint();
@ -153,16 +212,16 @@ void MainWindow::setupControls()
ui->m_pLabelNotice->setStyleSheet(kStyleNoticeLabel);
ui->m_pLabelNotice->hide();
ui->m_pLabelIpAddresses->setText(QString("This computer's IP addresses: %1").arg(getIPAddresses()));
ui->m_pLabelIpAddresses->setText(tr("This computer's IP addresses: %1").arg(getIPAddresses()));
if (m_AppConfig.lastVersion() != DESKFLOW_VERSION) {
m_AppConfig.setLastVersion(DESKFLOW_VERSION);
if (m_AppConfig.lastVersion() != kVersion) {
m_AppConfig.setLastVersion(kVersion);
}
#if defined(Q_OS_MAC)
ui->m_pRadioGroupServer->setAttribute(Qt::WA_MacShowFocusRect, 0);
ui->m_pRadioGroupClient->setAttribute(Qt::WA_MacShowFocusRect, 0);
ui->rbModeServer->setAttribute(Qt::WA_MacShowFocusRect, 0);
ui->rbModeClient->setAttribute(Qt::WA_MacShowFocusRect, 0);
#endif
}
@ -182,8 +241,6 @@ void MainWindow::connectSlots()
[this](const QString &line) { handleLogLine(line); }
);
connect(this, &MainWindow::created, this, &MainWindow::onCreated);
connect(this, &MainWindow::shown, this, &MainWindow::onShown, Qt::QueuedConnection);
connect(&m_ConfigScopes, &ConfigScopes::saving, this, &MainWindow::onConfigScopesSaving, Qt::DirectConnection);
@ -209,32 +266,60 @@ void MainWindow::connectSlots()
connect(&m_CoreProcess, &CoreProcess::secureSocket, this, &MainWindow::onCoreProcessSecureSocket);
connect(ui->m_pActionMinimize, &QAction::triggered, this, &MainWindow::hide);
connect(m_actionAbout, &QAction::triggered, this, &MainWindow::openAboutDialog);
connect(m_actionClearSettings, &QAction::triggered, this, &MainWindow::clearSettings);
connect(m_actionHelp, &QAction::triggered, this, &MainWindow::openHelpUrl);
connect(m_actionMinimize, &QAction::triggered, this, &MainWindow::hide);
connect(
ui->m_pActionRestore, &QAction::triggered, this, //
[this]() { showAndActivate(); }
);
connect(ui->m_pActionQuit, &QAction::triggered, qApp, [this] {
qDebug("quitting application");
connect(m_actionQuit, &QAction::triggered, qApp, [this] {
qDebug() << "quitting application";
m_Quitting = true;
QApplication::quit();
});
connect(m_actionRestore, &QAction::triggered, this, &MainWindow::showAndActivate);
connect(m_actionSave, &QAction::triggered, this, &MainWindow::saveConfig);
connect(m_actionSettings, &QAction::triggered, this, &MainWindow::openSettings);
connect(m_actionStartCore, &QAction::triggered, this, &MainWindow::startCore);
connect(m_actionStopCore, &QAction::triggered, this, &MainWindow::stopCore);
connect(m_actionTestFatalError, &QAction::triggered, this, &MainWindow::testFatalError);
connect(m_actionTestCriticalError, &QAction::triggered, this, &MainWindow::testCriticalError);
connect(&m_VersionChecker, &VersionChecker::updateFound, this, &MainWindow::onVersionCheckerUpdateFound);
connect(&m_WindowSaveTimer, &QTimer::timeout, this, &MainWindow::onWindowSaveTimerTimeout);
// Mac os tray will only show a menu
#ifndef Q_OS_MAC
connect(&m_TrayIcon, &TrayIcon::activated, this, &MainWindow::onTrayIconActivated);
#endif
connect(
&m_ServerConnection, &ServerConnection::configureClient, this, &MainWindow::onServerConnectionConfigureClient
);
connect(&m_ServerConnection, &ServerConnection::messageShowing, this, [this]() { showAndActivate(); });
connect(&m_ServerConnection, &ServerConnection::messageShowing, this, &MainWindow::showAndActivate);
connect(&m_ClientConnection, &ClientConnection::messageShowing, this, &MainWindow::showAndActivate);
connect(&m_ClientConnection, &ClientConnection::messageShowing, this, [this]() { showAndActivate(); });
connect(ui->btnToggleCore, &QPushButton::clicked, m_actionStartCore, &QAction::trigger, Qt::UniqueConnection);
connect(ui->btnApplySettings, &QPushButton::clicked, this, &MainWindow::resetCore);
connect(ui->btnConnect, &QPushButton::clicked, this, &MainWindow::resetCore);
connect(ui->btnConnectToClient, &QPushButton::clicked, this, &MainWindow::resetCore);
connect(ui->lineHostname, &QLineEdit::returnPressed, ui->btnConnect, &QPushButton::click);
connect(ui->lineHostname, &QLineEdit::textChanged, &m_CoreProcess, &deskflow::gui::CoreProcess::setAddress);
connect(ui->lineClientIp, &QLineEdit::returnPressed, ui->btnConnectToClient, &QPushButton::click);
connect(ui->lineClientIp, &QLineEdit::textChanged, &m_CoreProcess, &deskflow::gui::CoreProcess::setAddress);
connect(ui->btnConfigureServer, &QPushButton::clicked, this, [this] { showConfigureServer(""); });
connect(ui->lblComputerName, &QLabel::linkActivated, this, &MainWindow::openSettings);
connect(ui->lblMyFingerprint, &QLabel::linkActivated, this, &MainWindow::showMyFingerprint);
connect(ui->rbModeServer, &QRadioButton::clicked, this, &MainWindow::setModeServer);
connect(ui->rbModeClient, &QRadioButton::clicked, this, &MainWindow::setModeClient);
connect(ui->btnToggleLog, &QAbstractButton::toggled, this, &MainWindow::toggleLogVisible);
}
void MainWindow::onAppAboutToQuit()
@ -244,20 +329,20 @@ void MainWindow::onAppAboutToQuit()
}
}
void MainWindow::onCreated()
void MainWindow::toggleLogVisible(bool visible)
{
setIcon();
m_ConfigScopes.signalReady();
applyCloseToTray();
updateScreenName();
applyConfig();
restoreWindow();
qDebug().noquote() << "active settings path:" << m_ConfigScopes.activeFilePath();
if (visible) {
ui->btnToggleLog->setArrowType(Qt::DownArrow);
ui->textLog->setVisible(true);
m_AppConfig.setLogExpanded(true);
} else {
ui->btnToggleLog->setArrowType(Qt::RightArrow);
m_expandedSize = size();
ui->textLog->setVisible(false);
m_AppConfig.setLogExpanded(false);
}
// 10 ms is long enough to process events and quick enough to not see the visual change.
QTimer::singleShot(10, this, &MainWindow::updateSize);
}
void MainWindow::onShown()
@ -288,20 +373,15 @@ void MainWindow::onAppConfigTlsChanged()
void MainWindow::onTrayIconActivated(QSystemTrayIcon::ActivationReason reason)
{
if (reason == QSystemTrayIcon::DoubleClick) {
if (isVisible()) {
hide();
} else {
showAndActivate();
}
}
if (reason != QSystemTrayIcon::Trigger)
return;
isVisible() ? hide() : showAndActivate();
}
void MainWindow::onVersionCheckerUpdateFound(const QString &version)
{
const auto link = QString(kLinkDownload).arg(kUrlDownload, kColorWhite);
const auto text = QString("A new version is available (v%1). %2").arg(version, link);
const auto text = tr("A new version is available (v%1). %2").arg(version, link);
ui->m_pLabelUpdate->show();
ui->m_pLabelUpdate->setText(text);
@ -321,12 +401,12 @@ void MainWindow::onCoreProcessError(CoreProcess::Error error)
{
if (error == CoreProcess::Error::AddressMissing) {
QMessageBox::warning(
this, QString("Address missing"), QString("Please enter the hostname or IP address of the other computer.")
this, tr("Address missing"), tr("Please enter the hostname or IP address of the other computer.")
);
} else if (error == CoreProcess::Error::StartFailed) {
show();
QMessageBox::warning(
this, QString("Core cannot be started"),
this, tr("Core cannot be started"),
"The Core executable could not be successfully started, "
"although it does exist. "
"Please check if you have sufficient permissions to run this program."
@ -334,32 +414,32 @@ void MainWindow::onCoreProcessError(CoreProcess::Error error)
}
}
void MainWindow::on_m_pActionStartCore_triggered()
void MainWindow::startCore()
{
m_ClientConnection.setShowMessage();
m_CoreProcess.start();
}
void MainWindow::on_m_pActionStopCore_triggered()
void MainWindow::stopCore()
{
qDebug("stopping core process");
qDebug() << "stopping core process";
m_CoreProcess.stop();
}
void MainWindow::on_m_pActionTestFatalError_triggered() const
void MainWindow::testFatalError() const
{
qFatal("test fatal error");
qFatal() << "test fatal error";
}
void MainWindow::on_m_pActionTestCriticalError_triggered() const
void MainWindow::testCriticalError() const
{
qCritical("test critical error");
qCritical() << "test critical error";
}
void MainWindow::on_m_pActionClearSettings_triggered()
void MainWindow::clearSettings()
{
if (!messages::showClearSettings(this)) {
qDebug("clear settings cancelled");
qDebug() << "clear settings cancelled";
return;
}
@ -370,30 +450,30 @@ void MainWindow::on_m_pActionClearSettings_triggered()
diagnostic::clearSettings(m_ConfigScopes, true);
}
bool MainWindow::on_m_pActionSave_triggered()
bool MainWindow::saveConfig()
{
QString fileName = QFileDialog::getSaveFileName(this, QString("Save configuration as..."));
QString fileName = QFileDialog::getSaveFileName(this, tr("Save configuration as..."));
if (!fileName.isEmpty() && !m_ServerConfig.save(fileName)) {
QMessageBox::warning(this, QString("Save failed"), QString("Could not save configuration to file."));
QMessageBox::warning(this, tr("Save failed"), tr("Could not save configuration to file."));
return true;
}
return false;
}
void MainWindow::on_m_pActionAbout_triggered()
void MainWindow::openAboutDialog()
{
AboutDialog about(this);
about.exec();
}
void MainWindow::on_m_pActionHelp_triggered() const
void MainWindow::openHelpUrl() const
{
QDesktopServices::openUrl(QUrl(kUrlHelp));
}
void MainWindow::on_m_pActionSettings_triggered()
void MainWindow::openSettings()
{
auto dialog = SettingsDialog(this, m_AppConfig, m_ServerConfig, m_CoreProcess);
@ -409,71 +489,51 @@ void MainWindow::on_m_pActionSettings_triggered()
}
}
void MainWindow::on_m_pButtonConfigureServer_clicked()
{
showConfigureServer();
}
void MainWindow::on_m_pLineEditHostname_returnPressed()
{
ui->m_pButtonConnect->click();
}
void MainWindow::on_m_pLineEditClientIp_returnPressed()
{
ui->m_pButtonConnectToClient->click();
}
void MainWindow::on_m_pLineEditHostname_textChanged(const QString &text)
{
m_CoreProcess.setAddress(text);
}
void MainWindow::on_m_pLineEditClientIp_textChanged(const QString &text)
{
m_CoreProcess.setAddress(text);
}
void MainWindow::on_m_pButtonApply_clicked()
void MainWindow::resetCore()
{
m_ClientConnection.setShowMessage();
m_CoreProcess.restart();
}
void MainWindow::on_m_pLabelComputerName_linkActivated(const QString &)
void MainWindow::updateSize()
{
ui->m_pActionSettings->trigger();
#ifdef Q_OS_MAC
// On mac os the titlebar is part of the height so we need to adjust our Y coord to avoid moving the window up
const auto kTitleBarOffset = 28;
#else
const auto kTitleBarOffset = 0;
#endif
if (ui->textLog->isVisible()) {
setMaximumHeight(16777215);
setMaximumWidth(16777215);
setGeometry(x(), y() + kTitleBarOffset, m_expandedSize.width(), m_expandedSize.height());
} else {
adjustSize();
// Prevent Resize with log collapsed
setMaximumHeight(height());
setMaximumWidth(width());
}
}
void MainWindow::on_m_pLabelFingerprint_linkActivated(const QString &)
void MainWindow::showMyFingerprint()
{
QMessageBox::information(this, "TLS fingerprint", TlsFingerprint::local().readFirst());
}
void MainWindow::on_m_pRadioGroupServer_clicked(bool)
void MainWindow::setModeServer()
{
enableServer(true);
enableClient(false);
m_ConfigScopes.save();
}
void MainWindow::on_m_pRadioGroupClient_clicked(bool)
void MainWindow::setModeClient()
{
enableClient(true);
enableServer(false);
m_ConfigScopes.save();
}
void MainWindow::on_m_pButtonConnect_clicked()
{
on_m_pButtonApply_clicked();
}
void MainWindow::on_m_pButtonConnectToClient_clicked()
{
on_m_pButtonApply_clicked();
}
void MainWindow::onWindowSaveTimerTimeout()
{
saveWindow();
@ -514,11 +574,19 @@ void MainWindow::moveEvent(QMoveEvent *event)
void MainWindow::open()
{
std::vector<QAction *> trayMenu = {ui->m_pActionStartCore, ui->m_pActionStopCore, nullptr,
ui->m_pActionMinimize, ui->m_pActionRestore, nullptr,
ui->m_pActionQuit};
QList<QAction *> trayActions{m_actionStartCore, m_actionStopCore, nullptr, m_actionQuit};
m_TrayIcon.create(trayMenu);
#ifdef Q_OS_MAC
// Duplicate quit needed for mac os tray menu
QAction *actionTrayQuit = new QAction(tr("Quit Deskflow"), this);
actionTrayQuit->setShortcut(QKeySequence::Quit);
m_actionRestore->setText(tr("Open Deskflow"));
trayActions.insert(3, m_actionRestore);
trayActions.append(nullptr);
trayActions.append(actionTrayQuit);
#endif
m_TrayIcon.create(trayActions);
if (m_AppConfig.autoHide()) {
hide();
@ -534,7 +602,7 @@ void MainWindow::open()
if (m_AppConfig.enableUpdateCheck().value()) {
m_VersionChecker.checkLatest();
} else {
qDebug("update check disabled");
qDebug() << "update check disabled";
}
if (m_AppConfig.startedBefore()) {
@ -557,48 +625,43 @@ void MainWindow::setStatus(const QString &status)
void MainWindow::createMenuBar()
{
m_pMenuBar = new QMenuBar(this);
m_pMenuFile = new QMenu("File", m_pMenuBar);
m_pMenuEdit = new QMenu("Edit", m_pMenuBar);
m_pMenuWindow = new QMenu("Window", m_pMenuBar);
m_pMenuHelp = new QMenu("Help", m_pMenuBar);
auto menuFile = new QMenu(tr("File"));
menuFile->addAction(m_actionStartCore);
menuFile->addAction(m_actionStopCore);
menuFile->addSeparator();
menuFile->addAction(m_actionSave);
menuFile->addSeparator();
menuFile->addAction(m_actionQuit);
m_pMenuBar->addAction(m_pMenuFile->menuAction());
m_pMenuBar->addAction(m_pMenuEdit->menuAction());
auto menuEdit = new QMenu(tr("Edit"));
menuEdit->addAction(m_actionSettings);
auto menuWindow = new QMenu(tr("Window"));
menuWindow->addAction(m_actionMinimize);
auto menuHelp = new QMenu(tr("Help"));
menuHelp->addAction(m_actionAbout);
menuHelp->addAction(m_actionHelp);
menuHelp->addSeparator();
menuHelp->addAction(m_actionClearSettings);
auto menuBar = new QMenuBar(this);
menuBar->addMenu(menuFile);
menuBar->addMenu(menuEdit);
#if !defined(Q_OS_MAC)
m_pMenuBar->addAction(m_pMenuWindow->menuAction());
menuBar->addMenu(menuWindow);
#endif
m_pMenuBar->addAction(m_pMenuHelp->menuAction());
m_pMenuFile->addAction(ui->m_pActionStartCore);
m_pMenuFile->addAction(ui->m_pActionStopCore);
m_pMenuFile->addSeparator();
m_pMenuFile->addAction(ui->m_pActionSave);
m_pMenuFile->addSeparator();
m_pMenuFile->addAction(ui->m_pActionQuit);
m_pMenuEdit->addAction(ui->m_pActionSettings);
m_pMenuWindow->addAction(ui->m_pActionMinimize);
m_pMenuWindow->addAction(ui->m_pActionRestore);
m_pMenuHelp->addAction(ui->m_pActionAbout);
m_pMenuHelp->addAction(ui->m_pActionHelp);
m_pMenuFile->addSeparator();
m_pMenuHelp->addAction(ui->m_pActionClearSettings);
ui->m_pActionAbout->setText(QString("About %1...").arg(kAppName));
menuBar->addMenu(menuHelp);
const auto enableTestMenu = strToTrue(qEnvironmentVariable("DESKFLOW_TEST_MENU"));
if (enableTestMenu || kDebugBuild) {
auto testMenu = new QMenu("Test", m_pMenuBar);
m_pMenuBar->addMenu(testMenu);
testMenu->addAction(ui->m_pActionTestFatalError);
testMenu->addAction(ui->m_pActionTestCriticalError);
auto testMenu = new QMenu(tr("Test"));
menuBar->addMenu(testMenu);
testMenu->addAction(m_actionTestFatalError);
testMenu->addAction(m_actionTestCriticalError);
}
setMenuBar(m_pMenuBar);
setMenuBar(menuBar);
}
void MainWindow::applyConfig()
@ -606,8 +669,8 @@ void MainWindow::applyConfig()
enableServer(m_AppConfig.serverGroupChecked());
enableClient(m_AppConfig.clientGroupChecked());
ui->m_pLineEditHostname->setText(m_AppConfig.serverHostname());
ui->m_pLineEditClientIp->setText(m_ServerConfig.getClientAddress());
ui->lineHostname->setText(m_AppConfig.serverHostname());
ui->lineClientIp->setText(m_ServerConfig.getClientAddress());
}
void MainWindow::applyCloseToTray() const
@ -617,10 +680,10 @@ void MainWindow::applyCloseToTray() const
void MainWindow::saveSettings()
{
m_AppConfig.setServerGroupChecked(ui->m_pRadioGroupServer->isChecked());
m_AppConfig.setClientGroupChecked(ui->m_pRadioGroupClient->isChecked());
m_AppConfig.setServerHostname(ui->m_pLineEditHostname->text());
m_ServerConfig.setClientAddress(ui->m_pLineEditClientIp->text());
m_AppConfig.setServerGroupChecked(ui->rbModeServer->isChecked());
m_AppConfig.setClientGroupChecked(ui->rbModeClient->isChecked());
m_AppConfig.setServerHostname(ui->lineHostname->text());
m_ServerConfig.setClientAddress(ui->lineClientIp->text());
m_ConfigScopes.save();
}
@ -653,18 +716,18 @@ void MainWindow::handleLogLine(const QString &line)
{
const int kScrollBottomThreshold = 2;
QScrollBar *verticalScroll = ui->m_pLogOutput->verticalScrollBar();
QScrollBar *verticalScroll = ui->textLog->verticalScrollBar();
int currentScroll = verticalScroll->value();
int maxScroll = verticalScroll->maximum();
const auto scrollAtBottom = qAbs(currentScroll - maxScroll) <= kScrollBottomThreshold;
// only trim end instead of the whole line to prevent tab-indented debug
// filenames from losing their indentation.
ui->m_pLogOutput->appendPlainText(trimEnd(line));
ui->textLog->appendPlainText(trimEnd(line));
if (scrollAtBottom) {
verticalScroll->setValue(verticalScroll->maximum());
ui->m_pLogOutput->horizontalScrollBar()->setValue(0);
ui->textLog->horizontalScrollBar()->setValue(0);
}
updateFromLogLine(line);
@ -678,7 +741,7 @@ void MainWindow::updateFromLogLine(const QString &line)
void MainWindow::checkConnected(const QString &line)
{
if (ui->m_pRadioGroupServer->isChecked()) {
if (ui->rbModeServer->isChecked()) {
m_ServerConnection.handleLogLine(line);
ui->m_pLabelServerState->updateServerState(line);
} else {
@ -707,15 +770,15 @@ void MainWindow::checkFingerprint(const QString &line)
messageBoxAlreadyShown = true;
QMessageBox::StandardButton fingerprintReply = QMessageBox::information(
this, QString("Security question"),
QString("<p>You are connecting to a server.</p>"
"<p>Here is it's TLS fingerprint:</p>"
"<p>%1</p>"
"<p>Compare this fingerprint to the one on your server's screen. "
"If the two don't match exactly, then it's probably not the server "
"you're expecting (it could be a malicious user).</p>"
"<p>Do you want to trust this fingerprint for future "
"connections? If you don't, a connection cannot be made.</p>")
this, tr("Security question"),
tr("<p>You are connecting to a server.</p>"
"<p>Here is it's TLS fingerprint:</p>"
"<p>%1</p>"
"<p>Compare this fingerprint to the one on your server's screen. "
"If the two don't match exactly, then it's probably not the server "
"you're expecting (it could be a malicious user).</p>"
"<p>Do you want to trust this fingerprint for future "
"connections? If you don't, a connection cannot be made.</p>")
.arg(fingerprint),
QMessageBox::Yes | QMessageBox::No
);
@ -732,8 +795,7 @@ void MainWindow::checkFingerprint(const QString &line)
QString MainWindow::getTimeStamp() const
{
QDateTime current = QDateTime::currentDateTime();
return '[' + current.toString(Qt::ISODate) + ']';
return QStringLiteral("[%1]").arg(QDateTime::currentDateTime().toString(Qt::ISODate));
}
void MainWindow::showEvent(QShowEvent *event)
@ -745,12 +807,12 @@ void MainWindow::showEvent(QShowEvent *event)
void MainWindow::closeEvent(QCloseEvent *event)
{
if (m_Quitting) {
qDebug("skipping close event handle on quit");
qDebug() << "skipping close event handle on quit";
return;
}
if (!m_AppConfig.closeToTray()) {
qDebug("window will not hide to tray");
qDebug() << "window will not hide to tray";
return;
}
@ -760,7 +822,7 @@ void MainWindow::closeEvent(QCloseEvent *event)
}
m_ConfigScopes.save();
qDebug("window should hide to tray");
qDebug() << "window should hide to tray";
}
void MainWindow::showFirstConnectedMessage()
@ -790,19 +852,19 @@ void MainWindow::updateStatus()
using enum CoreProcessState;
case Starting:
setStatus(QString("%1 is starting...").arg(kAppName));
setStatus(tr("%1 is starting...").arg(kAppName));
break;
case RetryPending:
setStatus(QString("%1 will retry in a moment...").arg(kAppName));
setStatus(tr("%1 will retry in a moment...").arg(kAppName));
break;
case Stopping:
setStatus(QString("%1 is stopping...").arg(kAppName));
setStatus(tr("%1 is stopping...").arg(kAppName));
break;
case Stopped:
setStatus(QString("%1 is not running").arg(kAppName));
setStatus(tr("%1 is not running").arg(kAppName));
break;
case Started: {
@ -811,27 +873,27 @@ void MainWindow::updateStatus()
case Listening: {
if (m_CoreProcess.mode() == CoreMode::Server) {
setStatus(QString("%1 is waiting for clients").arg(kAppName));
setStatus(tr("%1 is waiting for clients").arg(kAppName));
}
break;
}
case Connecting:
setStatus(QString("%1 is connecting...").arg(kAppName));
setStatus(tr("%1 is connecting...").arg(kAppName));
break;
case Connected: {
if (m_SecureSocket) {
setStatus(QString("%1 is connected (with %2)").arg(kAppName, m_CoreProcess.secureSocketVersion()));
setStatus(tr("%1 is connected (with %2)").arg(kAppName, m_CoreProcess.secureSocketVersion()));
} else {
setStatus(QString("%1 is connected (without TLS encryption)").arg(kAppName));
setStatus(tr("%1 is connected (without TLS encryption)").arg(kAppName));
}
break;
}
case Disconnected:
setStatus(QString("%1 is disconnected").arg(kAppName));
setStatus(tr("%1 is disconnected").arg(kAppName));
break;
}
} break;
@ -843,37 +905,37 @@ void MainWindow::onCoreProcessStateChanged(CoreProcessState state)
updateStatus();
if (state == CoreProcessState::Started) {
qDebug("recording that core has started");
qDebug() << "recording that core has started";
m_AppConfig.setStartedBefore(true);
m_ConfigScopes.save();
}
if (state == CoreProcessState::Started || state == CoreProcessState::Starting ||
state == CoreProcessState::RetryPending) {
disconnect(ui->m_pButtonToggleStart, &QPushButton::clicked, ui->m_pActionStartCore, &QAction::trigger);
connect(ui->m_pButtonToggleStart, &QPushButton::clicked, ui->m_pActionStopCore, &QAction::trigger);
disconnect(ui->btnToggleCore, &QPushButton::clicked, m_actionStartCore, &QAction::trigger);
connect(ui->btnToggleCore, &QPushButton::clicked, m_actionStopCore, &QAction::trigger, Qt::UniqueConnection);
ui->m_pButtonToggleStart->setText(QString("&Stop"));
ui->m_pButtonApply->setEnabled(true);
ui->btnToggleCore->setText(tr("&Stop"));
ui->btnApplySettings->setEnabled(true);
ui->m_pActionStartCore->setEnabled(false);
ui->m_pActionStopCore->setEnabled(true);
m_actionStartCore->setEnabled(false);
m_actionStopCore->setEnabled(true);
} else {
disconnect(ui->m_pButtonToggleStart, &QPushButton::clicked, ui->m_pActionStopCore, &QAction::trigger);
connect(ui->m_pButtonToggleStart, &QPushButton::clicked, ui->m_pActionStartCore, &QAction::trigger);
disconnect(ui->btnToggleCore, &QPushButton::clicked, m_actionStopCore, &QAction::trigger);
connect(ui->btnToggleCore, &QPushButton::clicked, m_actionStartCore, &QAction::trigger, Qt::UniqueConnection);
ui->m_pButtonToggleStart->setText(QString("&Start"));
ui->m_pButtonApply->setEnabled(false);
ui->btnToggleCore->setText(tr("&Start"));
ui->btnApplySettings->setEnabled(false);
ui->m_pActionStartCore->setEnabled(true);
ui->m_pActionStopCore->setEnabled(false);
m_actionStartCore->setEnabled(true);
m_actionStopCore->setEnabled(false);
}
}
void MainWindow::onCoreConnectionStateChanged(CoreConnectionState state)
{
qDebug("core connection state changed: %d", static_cast<int>(state));
qDebug() << "core connection state changed: " << static_cast<int>(state);
updateStatus();
@ -890,10 +952,10 @@ void MainWindow::onCoreConnectionStateChanged(CoreConnectionState state)
void MainWindow::setVisible(bool visible)
{
QMainWindow::setVisible(visible);
ui->m_pActionMinimize->setEnabled(visible);
ui->m_pActionRestore->setEnabled(!visible);
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 // lion
m_actionMinimize->setEnabled(visible);
#ifndef Q_OS_MAC
m_actionRestore->setEnabled(!visible);
#else
// dock hide only supported on lion :(
ProcessSerialNumber psn = {0, kCurrentProcess};
#pragma GCC diagnostic push
@ -944,13 +1006,13 @@ void MainWindow::updateLocalFingerprint()
fingerprintExists = TlsFingerprint::local().fileExists();
} catch (const std::exception &e) {
qDebug() << e.what();
qFatal("failed to check if fingerprint exists");
qFatal() << "failed to check if fingerprint exists";
}
if (m_AppConfig.tlsEnabled() && fingerprintExists && ui->m_pRadioGroupServer->isChecked()) {
ui->m_pLabelFingerprint->setVisible(true);
if (m_AppConfig.tlsEnabled() && fingerprintExists && ui->rbModeServer->isChecked()) {
ui->lblMyFingerprint->setVisible(true);
} else {
ui->m_pLabelFingerprint->setVisible(false);
ui->lblMyFingerprint->setVisible(false);
}
}
@ -961,12 +1023,12 @@ void MainWindow::autoAddScreen(const QString name)
if (r != kAutoAddScreenOk) {
switch (r) {
case kAutoAddScreenManualServer:
showConfigureServer(QString("Please add the server (%1) to the grid.").arg(m_AppConfig.screenName()));
showConfigureServer(tr("Please add the server (%1) to the grid.").arg(m_AppConfig.screenName()));
break;
case kAutoAddScreenManualClient:
showConfigureServer(QString("Please drag the new client screen (%1) "
"to the desired position on the grid.")
showConfigureServer(tr("Please drag the new client screen (%1) "
"to the desired position on the grid.")
.arg(name));
break;
}
@ -994,30 +1056,31 @@ void MainWindow::secureSocket(bool secureSocket)
void MainWindow::updateScreenName()
{
ui->m_pLabelComputerName->setText(QString("This computer's name: %1 "
R"((<a href="#" style="color: %2">change</a>))")
.arg(m_AppConfig.screenName(), kColorSecondary));
ui->lblComputerName->setText(tr("This computer's name: %1 "
R"((<a href="#" style="color: %2">change</a>))")
.arg(m_AppConfig.screenName(), kColorSecondary));
m_ServerConfig.updateServerName();
}
void MainWindow::enableServer(bool enable)
{
qDebug(enable ? "server enabled" : "server disabled");
QString serverStr = enable ? QStringLiteral("server enabled") : QStringLiteral("server disabled");
qDebug() << serverStr;
m_AppConfig.setServerGroupChecked(enable);
ui->m_pRadioGroupServer->setChecked(enable);
ui->rbModeServer->setChecked(enable);
ui->m_pWidgetServer->setEnabled(enable);
ui->m_pWidgetServerInput->setVisible(m_AppConfig.invertConnection());
if (enable) {
ui->m_pButtonToggleStart->setEnabled(true);
ui->m_pActionStartCore->setEnabled(true);
ui->btnToggleCore->setEnabled(true);
m_actionStartCore->setEnabled(true);
m_CoreProcess.setMode(CoreProcess::Mode::Server);
// The server can run without any clients configured, and this is actually
// what you'll want to do the first time since you'll be prompted when an
// unrecognized client tries to connect.
if (!m_AppConfig.startedBefore()) {
qDebug("auto-starting core server for first time");
qDebug() << "auto-starting core server for first time";
m_CoreProcess.start();
messages::showFirstServerStartMessage(this);
}
@ -1026,26 +1089,25 @@ void MainWindow::enableServer(bool enable)
void MainWindow::enableClient(bool enable)
{
qDebug(enable ? "client enabled" : "client disabled");
QString clientStr = enable ? QStringLiteral("client enabled") : QStringLiteral("client disabled");
qDebug() << clientStr;
m_AppConfig.setClientGroupChecked(enable);
ui->m_pRadioGroupClient->setChecked(enable);
ui->rbModeClient->setChecked(enable);
ui->m_pWidgetClientInput->setEnabled(enable);
ui->m_pWidgetClientInput->setVisible(!m_AppConfig.invertConnection());
if (enable) {
ui->m_pButtonToggleStart->setEnabled(true);
ui->m_pActionStartCore->setEnabled(true);
ui->btnToggleCore->setEnabled(true);
m_actionStartCore->setEnabled(true);
m_CoreProcess.setMode(CoreProcess::Mode::Client);
}
}
void MainWindow::showAndActivate()
{
if (!isMinimized() && !isHidden()) {
qDebug("window already visible");
return;
}
#ifdef Q_OS_MAC
forceAppActive();
#endif
showNormal();
raise();
activateWindow();

View File

@ -1,5 +1,6 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* Copyright (C) 2024 Chris Rizzitello <sithlord48@gmail.com>
* Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2008 Volker Lanz (vl@fidra.de)
*
@ -27,8 +28,8 @@
#include "ServerConfig.h"
#include "TrayIcon.h"
#include "VersionChecker.h"
#include "common/ipc.h"
#include "gui/VersionChecker.h"
#include "gui/config/AppConfig.h"
#include "gui/config/ConfigScopes.h"
#include "gui/config/ServerConfigDialogState.h"
@ -94,17 +95,16 @@ public:
void autoAddScreen(const QString name);
signals:
void created();
void shown();
public slots:
void onAppAboutToQuit();
private slots:
void toggleLogVisible(bool visible);
//
// Manual slots
//
void onCreated();
void onShown();
void onConfigScopesSaving();
void onAppConfigTlsChanged();
@ -120,34 +120,25 @@ private slots:
void onWindowSaveTimerTimeout();
void onServerConnectionConfigureClient(const QString &clientName);
//
// Auto-connect slots
//
void on_m_pButtonApply_clicked();
void on_m_pLabelComputerName_linkActivated(const QString &link);
void on_m_pLabelFingerprint_linkActivated(const QString &link);
void on_m_pButtonConnect_clicked();
void on_m_pButtonConnectToClient_clicked();
void on_m_pRadioGroupServer_clicked(bool);
void on_m_pRadioGroupClient_clicked(bool);
void on_m_pButtonConfigureServer_clicked();
bool on_m_pActionSave_triggered();
void on_m_pActionAbout_triggered();
void on_m_pActionHelp_triggered() const;
void on_m_pActionSettings_triggered();
void on_m_pActionStartCore_triggered();
void on_m_pActionStopCore_triggered();
void on_m_pActionTestFatalError_triggered() const;
void on_m_pActionTestCriticalError_triggered() const;
void on_m_pActionClearSettings_triggered();
void on_m_pLineEditHostname_returnPressed();
void on_m_pLineEditClientIp_returnPressed();
void on_m_pLineEditHostname_textChanged(const QString &text);
void on_m_pLineEditClientIp_textChanged(const QString &text);
void clearSettings();
void openAboutDialog();
void openHelpUrl() const;
void openSettings();
void startCore();
void stopCore();
bool saveConfig();
void testFatalError() const;
void testCriticalError() const;
void resetCore();
void showMyFingerprint();
void setModeServer();
void setModeClient();
private:
std::unique_ptr<Ui::MainWindow> ui;
void updateSize();
AppConfig &appConfig()
{
return m_AppConfig;
@ -184,10 +175,6 @@ private:
void saveSettings();
QString configFilename();
void showConfigureServer(const QString &message);
void showConfigureServer()
{
showConfigureServer("");
}
void restoreWindow();
void saveWindow();
void setupControls();
@ -199,11 +186,6 @@ private:
VersionChecker m_VersionChecker;
deskflow::gui::TrayIcon m_TrayIcon;
QMenuBar *m_pMenuBar = nullptr;
QMenu *m_pMenuFile = nullptr;
QMenu *m_pMenuEdit = nullptr;
QMenu *m_pMenuWindow = nullptr;
QMenu *m_pMenuHelp = nullptr;
QAbstractButton *m_pCancelButton = nullptr;
bool m_SecureSocket = false;
bool m_SaveWindow = false;
@ -220,4 +202,19 @@ private:
deskflow::gui::ClientConnection m_ClientConnection;
deskflow::gui::TlsUtility m_TlsUtility;
QTimer m_WindowSaveTimer;
QSize m_expandedSize = QSize();
// Window Actions
QAction *m_actionAbout = nullptr;
QAction *m_actionClearSettings = nullptr;
QAction *m_actionHelp = nullptr;
QAction *m_actionMinimize = nullptr;
QAction *m_actionQuit = nullptr;
QAction *m_actionRestore = nullptr;
QAction *m_actionSave = nullptr;
QAction *m_actionSettings = nullptr;
QAction *m_actionStartCore = nullptr;
QAction *m_actionStopCore = nullptr;
QAction *m_actionTestCriticalError = nullptr;
QAction *m_actionTestFatalError = nullptr;
};

View File

@ -6,22 +6,16 @@
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
<width>883</width>
<height>690</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>750</width>
<height>550</height>
</size>
</property>
<property name="windowTitle">
<string>Deskflow</string>
</property>
@ -32,8 +26,17 @@
</property>
<item>
<layout class="QHBoxLayout" name="m_layoutName">
<property name="sizeConstraint">
<enum>QLayout::SizeConstraint::SetMinAndMaxSize</enum>
</property>
<item>
<widget class="QLabel" name="m_pLabelComputerName">
<widget class="QLabel" name="lblComputerName">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">This computer's name:</string>
</property>
@ -42,7 +45,7 @@
<item>
<spacer name="m_pSpacerUpdate">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -54,6 +57,12 @@
</item>
<item>
<widget class="QLabel" name="m_pLabelUpdate">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">m_pLabelUpdate</string>
</property>
@ -66,6 +75,12 @@
</item>
<item>
<widget class="QLabel" name="m_pLabelIpAddresses">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The highlighted IP is the one we think you should use. The server listens on all IPs, so the other IPs may work as well.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
@ -110,7 +125,7 @@
<property name="spacing">
<number>15</number>
</property>
<item alignment="Qt::AlignTop">
<item alignment="Qt::AlignmentFlag::AlignTop">
<widget class="QWidget" name="m_pWidgetServerRadio" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
@ -135,7 +150,7 @@
<number>0</number>
</property>
<item>
<widget class="QRadioButton" name="m_pRadioGroupServer">
<widget class="QRadioButton" name="rbModeServer">
<property name="text">
<string>Use this computer's keyboard and mouse</string>
</property>
@ -154,7 +169,7 @@
</layout>
</widget>
</item>
<item alignment="Qt::AlignTop">
<item alignment="Qt::AlignmentFlag::AlignTop">
<widget class="QWidget" name="m_pWidgetServer" native="true">
<layout class="QVBoxLayout" name="m_pLayoutServer">
<property name="spacing">
@ -206,10 +221,10 @@
<number>20</number>
</property>
<item>
<widget class="QLineEdit" name="m_pLineEditClientIp"/>
<widget class="QLineEdit" name="lineClientIp"/>
</item>
<item>
<widget class="QPushButton" name="m_pButtonConnectToClient">
<widget class="QPushButton" name="btnConnectToClient">
<property name="text">
<string>Connect</string>
</property>
@ -231,12 +246,12 @@
</widget>
</item>
<item>
<widget class="QLabel" name="m_pLabelFingerprint">
<widget class="QLabel" name="lblMyFingerprint">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;TLS enabled (&lt;a href=&quot;#&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#4285f4;&quot;&gt;fingerprint&lt;/span&gt;&lt;/a&gt;)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
<enum>Qt::TextFormat::RichText</enum>
</property>
<property name="indent">
<number>20</number>
@ -246,7 +261,7 @@
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -261,7 +276,7 @@
<item>
<spacer name="spacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -272,7 +287,7 @@
</spacer>
</item>
<item>
<widget class="QPushButton" name="m_pButtonConfigureServer">
<widget class="QPushButton" name="btnConfigureServer">
<property name="text">
<string>&amp;Configure</string>
</property>
@ -298,7 +313,7 @@
<property name="spacing">
<number>15</number>
</property>
<item alignment="Qt::AlignTop">
<item alignment="Qt::AlignmentFlag::AlignTop">
<widget class="QWidget" name="m_pWidgetClientRadio" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
@ -323,7 +338,7 @@
<number>0</number>
</property>
<item>
<widget class="QRadioButton" name="m_pRadioGroupClient">
<widget class="QRadioButton" name="rbModeClient">
<property name="text">
<string>Use another computers mouse and keyboard</string>
</property>
@ -342,7 +357,7 @@
</layout>
</widget>
</item>
<item alignment="Qt::AlignTop">
<item alignment="Qt::AlignmentFlag::AlignTop">
<widget class="QWidget" name="m_pWidgetClientInput" native="true">
<layout class="QVBoxLayout" name="m_pLayoutClient">
<property name="spacing">
@ -376,7 +391,7 @@
<number>20</number>
</property>
<item>
<widget class="QLineEdit" name="m_pLineEditHostname">
<widget class="QLineEdit" name="lineHostname">
<property name="minimumSize">
<size>
<width>0</width>
@ -389,7 +404,7 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="m_pButtonConnect">
<widget class="QPushButton" name="btnConnect">
<property name="text">
<string>Connect</string>
</property>
@ -410,19 +425,6 @@
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
@ -430,31 +432,55 @@
</widget>
</item>
<item>
<widget class="QGroupBox" name="m_pGroupLog">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
<widget class="QFrame" name="frameLog">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Logs</string>
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="leftMargin">
<number>8</number>
</property>
<property name="topMargin">
<number>8</number>
</property>
<property name="rightMargin">
<number>8</number>
</property>
<property name="bottomMargin">
<number>8</number>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPlainTextEdit" name="m_pLogOutput">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="sizeConstraint">
<enum>QLayout::SizeConstraint::SetMinAndMaxSize</enum>
</property>
<item>
<widget class="QToolButton" name="btnToggleLog">
<property name="text">
<string>...</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="arrowType">
<enum>Qt::ArrowType::RightArrow</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblLog">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Log</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPlainTextEdit" name="textLog">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
@ -471,7 +497,7 @@
<bool>false</bool>
</property>
<property name="lineWrapMode">
<enum>QPlainTextEdit::NoWrap</enum>
<enum>QPlainTextEdit::LineWrapMode::NoWrap</enum>
</property>
<property name="readOnly">
<bool>true</bool>
@ -490,19 +516,13 @@
<number>6</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
<enum>QLayout::SizeConstraint::SetDefaultConstraint</enum>
</property>
<item>
<widget class="QLabel" name="m_pLabelPadlock">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../../../../res/gui/deskflow.qrc">:/icons/64x64/padlock.png</pixmap>
</property>
<property name="minimumSize">
<size>
<width>32</width>
@ -515,10 +535,16 @@
<height>32</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../res/deskflow.qrc">:/icons/64x64/padlock.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</widget>
</item>
<item>
<widget class="QLabel" name="m_pStatusLabel">
@ -530,7 +556,7 @@
<item>
<spacer name="spacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -549,7 +575,7 @@
<string notr="true">m_pLabelNotice</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter</set>
</property>
<property name="margin">
<number>0</number>
@ -562,10 +588,10 @@
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
<enum>QSizePolicy::Policy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -576,7 +602,7 @@
</spacer>
</item>
<item>
<widget class="QPushButton" name="m_pButtonApply">
<widget class="QPushButton" name="btnApplySettings">
<property name="enabled">
<bool>false</bool>
</property>
@ -586,7 +612,7 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="m_pButtonToggleStart">
<widget class="QPushButton" name="btnToggleCore">
<property name="enabled">
<bool>false</bool>
</property>
@ -599,129 +625,6 @@
</item>
</layout>
</widget>
<action name="m_pActionAbout">
<property name="text">
<string>&amp;About Deskflow...</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_pActionHelp">
<property name="text">
<string>Visit &amp;help site</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_pActionQuit">
<property name="text">
<string>&amp;Quit</string>
</property>
<property name="statusTip">
<string>Quit</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+Q</string>
</property>
</action>
<action name="m_pActionStartCore">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Start</string>
</property>
<property name="statusTip">
<string>Run</string>
</property>
<property name="shortcut">
<string>Ctrl+S</string>
</property>
</action>
<action name="m_pActionStopCore">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>S&amp;top</string>
</property>
<property name="statusTip">
<string>Stop</string>
</property>
<property name="shortcut">
<string>Ctrl+T</string>
</property>
</action>
<action name="m_pActionMinimize">
<property name="text">
<string>&amp;Hide</string>
</property>
<property name="toolTip">
<string>Hide</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_pActionRestore">
<property name="text">
<string>&amp;Show</string>
</property>
<property name="toolTip">
<string>Show</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_pActionSave">
<property name="text">
<string>Save configuration &amp;as...</string>
</property>
<property name="statusTip">
<string>Save the interactively generated server configuration to a file.</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+Alt+S</string>
</property>
</action>
<action name="m_pActionSettings">
<property name="text">
<string>Preferences</string>
</property>
<property name="toolTip">
<string>Preferences</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_pActionTestFatalError">
<property name="text">
<string>Test fatal error</string>
</property>
<property name="menuRole">
<enum>QAction::TextHeuristicRole</enum>
</property>
</action>
<action name="m_pActionTestCriticalError">
<property name="text">
<string>Test critical error</string>
</property>
<property name="menuRole">
<enum>QAction::TextHeuristicRole</enum>
</property>
</action>
<action name="m_pActionClearSettings">
<property name="text">
<string>Clear settings</string>
</property>
<property name="menuRole">
<enum>QAction::TextHeuristicRole</enum>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
@ -736,33 +639,20 @@
</customwidget>
</customwidgets>
<tabstops>
<tabstop>m_pRadioGroupServer</tabstop>
<tabstop>m_pRadioGroupClient</tabstop>
<tabstop>m_pLineEditClientIp</tabstop>
<tabstop>m_pButtonConnectToClient</tabstop>
<tabstop>m_pButtonConfigureServer</tabstop>
<tabstop>m_pLineEditHostname</tabstop>
<tabstop>m_pButtonConnect</tabstop>
<tabstop>m_pLogOutput</tabstop>
<tabstop>m_pButtonApply</tabstop>
<tabstop>m_pButtonToggleStart</tabstop>
<tabstop>rbModeServer</tabstop>
<tabstop>lineClientIp</tabstop>
<tabstop>btnConnectToClient</tabstop>
<tabstop>btnConfigureServer</tabstop>
<tabstop>rbModeClient</tabstop>
<tabstop>lineHostname</tabstop>
<tabstop>btnConnect</tabstop>
<tabstop>btnToggleLog</tabstop>
<tabstop>textLog</tabstop>
<tabstop>btnApplySettings</tabstop>
<tabstop>btnToggleCore</tabstop>
</tabstops>
<connections>
<connection>
<sender>m_pButtonToggleStart</sender>
<signal>clicked()</signal>
<receiver>m_pActionStartCore</receiver>
<slot>trigger()</slot>
<hints>
<hint type="sourcelabel">
<x>361</x>
<y>404</y>
</hint>
<hint type="destinationlabel">
<x>-1</x>
<y>-1</y>
</hint>
</hints>
</connection>
</connections>
<resources>
<include location="../res/deskflow.qrc"/>
</resources>
<connections/>
</ui>

Some files were not shown because too many files have changed in this diff Show More