Compare commits
115 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 23c054661c | |||
| 098f56daee | |||
| ee8baeb1ff | |||
| 81d478632a | |||
| 8e9925d6c8 | |||
| 368ccbbe4c | |||
| a401c98bf6 | |||
| f61fe00c33 | |||
| 97890f86d3 | |||
| 638970d65e | |||
| 98eb89255d | |||
| ccc60ff900 | |||
| db441eb5cf | |||
| 21b345e9db | |||
| 9af37463fd | |||
| 225ca23482 | |||
| 9811586718 | |||
| 858c41c946 | |||
| 8b25e11f81 | |||
| d9727e60bc | |||
| f8ddafe4a2 | |||
| ab44559df6 | |||
| 626e8c7364 | |||
| 041512b050 | |||
| cd0e98a480 | |||
| 802cfaa279 | |||
| aff3495e74 | |||
| 1ff7d5f4aa | |||
| 784b2b2f6f | |||
| 927c8c146e | |||
| 94006f0bcd | |||
| 19c227b937 | |||
| be5025b225 | |||
| 4ee7d7b20f | |||
| 5032a5d275 | |||
| d0dadf2112 | |||
| 2211caecb8 | |||
| 5aa4840972 | |||
| 937ac053fe | |||
| cb638f4712 | |||
| 751904f27c | |||
| b9247b4c27 | |||
| cfd0bb9262 | |||
| d128623df3 | |||
| b641a885d5 | |||
| 3817489097 | |||
| f06a789d25 | |||
| b6d5095871 | |||
| a06d65b1f9 | |||
| 56ecd88945 | |||
| 5a45e6d102 | |||
| e4ecbdae8a | |||
| bf4f513d7e | |||
| af6dac9eee | |||
| ebad12a922 | |||
| 8aec0dd5bb | |||
| ddb443b550 | |||
| 6a816350c1 | |||
| 0c26893706 | |||
| 53b9c0908e | |||
| 252d11a316 | |||
| 5026eea60a | |||
| 482aa7a049 | |||
| e12432d3d8 | |||
| 0349c06fed | |||
| 6b7291f4b1 | |||
| 991293dd0c | |||
| 2349ce7f52 | |||
| 1d43e7c626 | |||
| 48e357f111 | |||
| d225be501e | |||
| 37acc3b2fc | |||
| b783f6e754 | |||
| 6e01cc6ce5 | |||
| 6498fe5a6b | |||
| 4e23460c6f | |||
| 7ce2845c9b | |||
| c8e1e4f38f | |||
| 922ad66aff | |||
| b0d22926f9 | |||
| 81631cac32 | |||
| 1685c6e946 | |||
| 1e8ff50d59 | |||
| 5d591e3518 | |||
| 57f6248b6a | |||
| d6c682a923 | |||
| 4f8ae57ffd | |||
| a92f5b0351 | |||
| 0f0846f011 | |||
| 86cca8a0d1 | |||
| eb9e507a1f | |||
| 03f1408a98 | |||
| 7f9fd80f98 | |||
| c5cfdc2b69 | |||
| 7656b49f1b | |||
| c05a87b716 | |||
| 4a83eb711f | |||
| f321f6596b | |||
| 18bc419b7a | |||
| 25660049e4 | |||
| 9fcf261245 | |||
| 59d9454df1 | |||
| 14f66e2dee | |||
| 882b71ea84 | |||
| d26339d94a | |||
| 079bfdc854 | |||
| 3003670b94 | |||
| 79c17e3564 | |||
| 397f9f0fac | |||
| 40ff14473d | |||
| b16a03c3ad | |||
| f11178327d | |||
| c6f352ac4a | |||
| 2cdbd8f491 | |||
| a63435e64a |
23
.github/actions/init-python/action.yml
vendored
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
21
.github/actions/lint-clang/action.yml
vendored
@ -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"
|
||||
70
.github/workflows/continuous-integration.yml
vendored
@ -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:
|
||||
|
||||
41
.github/workflows/test-actions.yml
vendored
@ -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
@ -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
|
||||
|
||||
11
.vscode/extensions.json
vendored
@ -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
@ -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
@ -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"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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()
|
||||
|
||||
@ -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.
|
||||
|
||||
[](https://github.com/deskflow/deskflow/releases/latest) [](https://github.com/deskflow/deskflow/releases/continuous)
|
||||
[](https://github.com/deskflow/deskflow/releases/latest) [](https://github.com/deskflow/deskflow/releases/continuous) [](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?
|
||||
|
||||
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
31
deploy/dist/mac/bundle/Contents/Info.plist.in
vendored
@ -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>
|
||||
1
deploy/dist/mac/bundle/Contents/PkgInfo.in
vendored
@ -1 +0,0 @@
|
||||
APPLDFLW
|
||||
69
deploy/dist/mac/dmgbuild/settings.py
vendored
@ -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",
|
||||
}
|
||||
33
deploy/dist/wix/AppBrowseDlg.wxs
vendored
@ -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>
|
||||
72
deploy/dist/wix/AppDlgSequence.wxs
vendored
@ -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>
|
||||
24
deploy/dist/wix/AppInstallDirDlg.wxs
vendored
@ -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>
|
||||
58
deploy/dist/wix/AppMaintenanceTypeDlg.wxs
vendored
@ -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>
|
||||
140
deploy/dist/wix/AppVerifyReadyDlg.wxs
vendored
@ -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>
|
||||
29
deploy/dist/wix/AppWelcome.wxs
vendored
@ -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>
|
||||
72
deploy/dist/wix/DeskflowDlgSequence.wxs
vendored
@ -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>
|
||||
29
deploy/dist/wix/Include.wxi.in
vendored
@ -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>
|
||||
31
deploy/dist/wix/Installer.sln
vendored
@ -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
|
||||
41
deploy/dist/wix/Installer.wixproj
vendored
@ -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>
|
||||
166
deploy/dist/wix/Product.wxs
vendored
@ -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>
|
||||
BIN
deploy/dist/wix/images/banner.png
vendored
|
Before Width: | Height: | Size: 11 KiB |
BIN
deploy/dist/wix/images/common_background.png
vendored
|
Before Width: | Height: | Size: 21 KiB |
BIN
deploy/dist/wix/images/welcome_background.png
vendored
|
Before Width: | Height: | Size: 154 KiB |
BIN
deploy/dist/wix/msm/Microsoft_VC142_CRT_x86.msm
vendored
107
deploy/linux/deploy.cmake
Normal 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()
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
@ -21,7 +21,6 @@ cleanup:
|
||||
- /share/cmake
|
||||
- /share/doc
|
||||
- /share/gir-1.0
|
||||
- /lib/debug
|
||||
- /lib/girepository-1.0
|
||||
modules:
|
||||
- name: python3-attrs
|
||||
@ -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
@ -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")
|
||||
45
deploy/mac/generate_ds_store.applescript
Normal 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
|
||||
59
deploy/windows/deploy.cmake
Normal 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")
|
||||
BIN
deploy/windows/wix-banner.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
26
deploy/windows/wix-patch.xml.in
Normal 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>
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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}"
|
||||
@ -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)
|
||||
@ -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,
|
||||
)
|
||||
@ -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
|
||||
@ -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)
|
||||
@ -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
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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",
|
||||
]
|
||||
@ -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.")
|
||||
@ -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 }}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
@ -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()
|
||||
@ -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()]);
|
||||
}
|
||||
@ -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:
|
||||
@ -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)
|
||||
@ -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;
|
||||
};
|
||||
@ -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)
|
||||
@ -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;
|
||||
};
|
||||
@ -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"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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()]) + "(";
|
||||
@ -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);
|
||||
38
src/apps/deskflow-gui/AppDelegate.mm
Normal 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
|
||||
110
src/apps/deskflow-gui/CMakeLists.txt
Normal 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()
|
||||
@ -72,6 +72,6 @@ private:
|
||||
ActionList m_Actions;
|
||||
};
|
||||
|
||||
typedef QList<Hotkey> HotkeyList;
|
||||
using HotkeyList = QList<Hotkey>;
|
||||
|
||||
QTextStream &operator<<(QTextStream &outStream, const Hotkey &hotkey);
|
||||
@ -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();
|
||||
@ -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;
|
||||
};
|
||||
@ -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><html><head/><body><p>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.</p></body></html></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><html><head/><body><p>TLS enabled (<a href="#"><span style=" text-decoration: underline; color:#4285f4;">fingerprint</span></a>)</p></body></html></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>&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 computer’s 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>&About Deskflow...</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</action>
|
||||
<action name="m_pActionHelp">
|
||||
<property name="text">
|
||||
<string>Visit &help site</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</action>
|
||||
<action name="m_pActionQuit">
|
||||
<property name="text">
|
||||
<string>&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>&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&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>&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>&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 &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>
|
||||