├── .clang-format
├── .clang-tidy
├── .github
├── assets
│ ├── audio.png
│ ├── autostart-option.png
│ ├── running-buddy.png
│ ├── settings.png
│ └── sunshine.png
└── workflows
│ ├── build.yaml
│ ├── filename.yaml
│ ├── formatting.yaml
│ └── publish.yaml
├── .gitignore
├── .gitmodules
├── CMakeLists.txt
├── LICENSE
├── README.md
├── resources
├── icons
│ ├── moondeckbuddy-128.png
│ ├── moondeckbuddy-16.png
│ ├── moondeckbuddy-256.png
│ ├── moondeckbuddy-32.png
│ ├── moondeckbuddy-64.png
│ └── moondeckbuddy.ico
├── linux
│ ├── AppRun
│ └── MoonDeckBuddy.desktop
├── resources.qrc
└── windows
│ ├── innocode.iss
│ ├── innoextra.iss
│ ├── resources.rc
│ └── version.rc.in
└── src
├── CMakeLists.txt
├── buddy
├── CMakeLists.txt
├── main.cpp
├── routing.cpp
└── routing.h
├── lib
├── CMakeLists.txt
├── os
│ ├── CMakeLists.txt
│ ├── autostarthandler.cpp
│ ├── include
│ │ └── os
│ │ │ ├── autostarthandler.h
│ │ │ ├── networkinfo.h
│ │ │ ├── pccontrol.h
│ │ │ ├── pcstatehandler.h
│ │ │ ├── sleepinhibitor.h
│ │ │ ├── steamhandler.h
│ │ │ ├── streamstatehandler.h
│ │ │ ├── streamstatehandlerinterface.h
│ │ │ ├── sunshineapps.h
│ │ │ └── systemtray.h
│ ├── linux
│ │ ├── CMakeLists.txt
│ │ ├── include
│ │ │ └── os
│ │ │ │ └── linux
│ │ │ │ ├── nativeautostarthandler.h
│ │ │ │ ├── nativepcstatehandler.h
│ │ │ │ ├── nativeprocesshandler.h
│ │ │ │ └── nativesleepinhibitor.h
│ │ ├── nativeautostarthandler.cpp
│ │ ├── nativepcstatehandler.cpp
│ │ ├── nativeprocesshandler.cpp
│ │ └── nativesleepinhibitor.cpp
│ ├── networkinfo.cpp
│ ├── pccontrol.cpp
│ ├── pcstatehandler.cpp
│ ├── shared
│ │ ├── CMakeLists.txt
│ │ └── include
│ │ │ └── os
│ │ │ └── shared
│ │ │ ├── nativeautostarthandlerinterface.h
│ │ │ ├── nativepcstatehandlerinterface.h
│ │ │ ├── nativeprocesshandlerinterface.h
│ │ │ └── nativesleepinhibitorinterface.h
│ ├── sleepinhibitor.cpp
│ ├── steam
│ │ ├── CMakeLists.txt
│ │ ├── include
│ │ │ └── os
│ │ │ │ └── steam
│ │ │ │ ├── steamappwatcher.h
│ │ │ │ ├── steamcontentlogtracker.h
│ │ │ │ ├── steamgameprocesslogtracker.h
│ │ │ │ ├── steamlogtracker.h
│ │ │ │ ├── steamprocesstracker.h
│ │ │ │ ├── steamshaderlogtracker.h
│ │ │ │ └── steamwebhelperlogtracker.h
│ │ ├── steamappwatcher.cpp
│ │ ├── steamcontentlogtracker.cpp
│ │ ├── steamgameprocesslogtracker.cpp
│ │ ├── steamlogtracker.cpp
│ │ ├── steamprocesstracker.cpp
│ │ ├── steamshaderlogtracker.cpp
│ │ └── steamwebhelperlogtracker.cpp
│ ├── steamhandler.cpp
│ ├── streamstatehandler.cpp
│ ├── sunshineapps.cpp
│ ├── systemtray.cpp
│ └── win
│ │ ├── CMakeLists.txt
│ │ ├── include
│ │ └── os
│ │ │ └── win
│ │ │ ├── nativeautostarthandler.h
│ │ │ ├── nativepcstatehandler.h
│ │ │ ├── nativeprocesshandler.h
│ │ │ └── nativesleepinhibitor.h
│ │ ├── nativeautostarthandler.cpp
│ │ ├── nativepcstatehandler.cpp
│ │ ├── nativeprocesshandler.cpp
│ │ └── nativesleepinhibitor.cpp
├── server
│ ├── CMakeLists.txt
│ ├── clientids.cpp
│ ├── httpserver.cpp
│ ├── include
│ │ └── server
│ │ │ ├── clientids.h
│ │ │ ├── httpserver.h
│ │ │ └── pairingmanager.h
│ └── pairingmanager.cpp
├── shared
│ ├── CMakeLists.txt
│ ├── appmetadata.cpp
│ ├── include
│ │ └── shared
│ │ │ ├── appmetadata.h
│ │ │ ├── enums.h
│ │ │ └── loggingcategories.h
│ └── loggingcategories.cpp
└── utils
│ ├── CMakeLists.txt
│ ├── appsettings.cpp
│ ├── heartbeat.cpp
│ ├── include
│ └── utils
│ │ ├── appsettings.h
│ │ ├── heartbeat.h
│ │ ├── jsonvalueconverter.h
│ │ ├── logsettings.h
│ │ ├── pairinginput.h
│ │ ├── singleinstanceguard.h
│ │ └── unixsignalhandler.h
│ ├── logsettings.cpp
│ ├── pairinginput.cpp
│ ├── singleinstanceguard.cpp
│ └── unixsignalhandler.cpp
└── stream
├── CMakeLists.txt
└── main.cpp
/.clang-format:
--------------------------------------------------------------------------------
1 | ---
2 | AccessModifierOffset: -4
3 | AlignAfterOpenBracket: Align
4 | AlignConsecutiveMacros: true
5 | AlignConsecutiveAssignments: true
6 | AlignConsecutiveDeclarations: true
7 | AlignEscapedNewlines: Right
8 | AlignOperands: true
9 | AlignTrailingComments: true
10 | AllowAllArgumentsOnNextLine: true
11 | AllowAllConstructorInitializersOnNextLine: false
12 | AllowAllParametersOfDeclarationOnNextLine: false
13 | AllowShortBlocksOnASingleLine: 'Never'
14 | AllowShortCaseLabelsOnASingleLine: false
15 | AllowShortFunctionsOnASingleLine: None
16 | AllowShortIfStatementsOnASingleLine: Never
17 | AllowShortLambdasOnASingleLine: All
18 | AllowShortLoopsOnASingleLine: false
19 | AlwaysBreakAfterReturnType: None
20 | AlwaysBreakBeforeMultilineStrings: false
21 | AlwaysBreakTemplateDeclarations: 'Yes'
22 | BinPackArguments: true
23 | BinPackParameters: true
24 | BreakBeforeBinaryOperators: NonAssignment
25 | BreakBeforeBraces: Allman
26 | BreakBeforeTernaryOperators: true
27 | BreakConstructorInitializers: BeforeComma
28 | BreakInheritanceList: BeforeComma
29 | BreakStringLiterals: true
30 | ColumnLimit: 120
31 | CompactNamespaces: false
32 | ConstructorInitializerAllOnOneLineOrOnePerLine: false
33 | ConstructorInitializerIndentWidth: 4
34 | ContinuationIndentWidth: 4
35 | Cpp11BracedListStyle: true
36 | DerivePointerAlignment: false
37 | FixNamespaceComments: true
38 | IncludeBlocks: Preserve
39 | IndentCaseLabels: true
40 | IndentPPDirectives: BeforeHash
41 | #IndentRequiresClause: false
42 | IndentWidth: 4
43 | IndentWrappedFunctionNames: true
44 | KeepEmptyLinesAtTheStartOfBlocks: false
45 | Language: Cpp
46 | MaxEmptyLinesToKeep: 1
47 | NamespaceIndentation: None
48 | PointerAlignment: Left
49 | ReflowComments: true
50 | #RequiresClausePosition: WithPreceding
51 | SortIncludes: 'CaseSensitive'
52 | SortUsingDeclarations: true
53 | SpaceAfterCStyleCast: false
54 | SpaceAfterLogicalNot: false
55 | SpaceAfterTemplateKeyword: false
56 | SpaceBeforeAssignmentOperators: true
57 | SpaceBeforeCpp11BracedList: false
58 | SpaceBeforeCtorInitializerColon: true
59 | SpaceBeforeInheritanceColon: true
60 | SpaceBeforeParens: ControlStatements
61 | SpaceBeforeRangeBasedForLoopColon: true
62 | SpaceInEmptyParentheses: false
63 | SpacesBeforeTrailingComments: 2
64 | SpacesInAngles: false
65 | SpacesInCStyleCastParentheses: false
66 | SpacesInContainerLiterals: false
67 | SpacesInParentheses: false
68 | SpacesInSquareBrackets: false
69 | Standard: c++20
70 | TabWidth: 4
71 | UseTab: Never
72 |
73 | ...
74 |
--------------------------------------------------------------------------------
/.clang-tidy:
--------------------------------------------------------------------------------
1 | Checks: '-*,bugprone-*,modernize-*,-modernize-use-trailing-return-type,-modernize-use-nodiscard,-bugprone-suspicious-include,cert-dcl21-cpp,cert-dcl50-cpp,cert-env33-c,cert-err34-c,cert-err52-cpp,cert-err60-cpp,cert-flp30-c,cert-msc50-cpp,cert-msc51-cpp,cppcoreguidelines-*,-cppcoreguidelines-macro-usage,google-build-using-namespace,google-explicit-constructor,google-global-names-in-headers,google-readability-casting,google-runtime-int,google-runtime-operator,hicpp-*,misc-*,performance-*,readability-*'
2 | WarningsAsErrors: '*'
3 | CheckOptions:
4 | - key: bugprone-argument-comment.StrictMode
5 | value: 1
6 | - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic
7 | value: 1
8 | - key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor
9 | value: 1
10 | - key: hicpp-special-member-functions.AllowSoleDefaultDtor
11 | value: 1
12 | - key: cppcoreguidelines-special-member-functions.AllowMissingMoveFunctionsWhenCopyIsDeleted
13 | value: 1
14 | - key: hicpp-special-member-functions.AllowMissingMoveFunctionsWhenCopyIsDeleted
15 | value: 1
16 | FormatStyle: 'file'
17 | HeaderFilterRegex: 'src/.*\.(?:h|c|hpp|cpp)$'
--------------------------------------------------------------------------------
/.github/assets/audio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrogTheFrog/moondeck-buddy/376352b17e4f879b6ba19df57afddbdadb45c506/.github/assets/audio.png
--------------------------------------------------------------------------------
/.github/assets/autostart-option.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrogTheFrog/moondeck-buddy/376352b17e4f879b6ba19df57afddbdadb45c506/.github/assets/autostart-option.png
--------------------------------------------------------------------------------
/.github/assets/running-buddy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrogTheFrog/moondeck-buddy/376352b17e4f879b6ba19df57afddbdadb45c506/.github/assets/running-buddy.png
--------------------------------------------------------------------------------
/.github/assets/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrogTheFrog/moondeck-buddy/376352b17e4f879b6ba19df57afddbdadb45c506/.github/assets/settings.png
--------------------------------------------------------------------------------
/.github/assets/sunshine.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrogTheFrog/moondeck-buddy/376352b17e4f879b6ba19df57afddbdadb45c506/.github/assets/sunshine.png
--------------------------------------------------------------------------------
/.github/workflows/build.yaml:
--------------------------------------------------------------------------------
1 | name: Build
2 | on: [pull_request,workflow_call]
3 | jobs:
4 | Formatting:
5 | uses: ./.github/workflows/formatting.yaml
6 |
7 | Filename:
8 | uses: ./.github/workflows/filename.yaml
9 | with:
10 | original_event_name: "${{ github.event_name }}"
11 |
12 | Windows:
13 | runs-on: windows-latest
14 | needs: [Formatting, Filename]
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v4
18 | with:
19 | submodules: recursive
20 |
21 | - name: Install MSCV
22 | uses: ilammy/msvc-dev-cmd@v1
23 |
24 | - name: Install Qt
25 | uses: jurplel/install-qt-action@v4
26 | with:
27 | version: '6.8.*'
28 | host: 'windows'
29 | target: 'desktop'
30 | arch: 'win64_msvc2022_64'
31 | modules: 'qthttpserver qtwebsockets'
32 |
33 | - name: Configure
34 | run: cmake -DENABLE_CLANG_TIDY=True -DCMAKE_CXX_COMPILER=cl -DCMAKE_BUILD_TYPE:STRING=Release -B build -G Ninja
35 |
36 | - name: Build
37 | run: cmake --build build
38 |
39 | - name: Bundle
40 | run: cd build && cpack -C CPackConfig.cmake
41 |
42 | - name: Rename 7z
43 | working-directory: ./dist
44 | run: mv MoonDeckBuddy*.7z ${{ needs.Filename.outputs.filename }}-win64.7z
45 |
46 | - name: Rename Exe
47 | working-directory: ./dist
48 | run: mv MoonDeckBuddy*.exe ${{ needs.Filename.outputs.filename }}-win64.exe
49 |
50 | - name: Upload artifacts
51 | uses: actions/upload-artifact@v4
52 | with:
53 | name: moondeckbuddy-windows
54 | path: dist/*
55 | if-no-files-found: error
56 |
57 | Linux:
58 | runs-on: ubuntu-22.04 # Stay on 22.04 for now as the next release increments GLIBC
59 | needs: [Formatting, Filename]
60 | steps:
61 | - name: Install packages
62 | run: sudo apt-get install ninja-build libxrandr-dev libfuse2 libxcb-cursor-dev
63 |
64 | - name: Build libproc2
65 | run: |
66 | sudo apt-get install autopoint autoconf automake libtool-bin gettext libncursesw5-dev dejagnu libnuma-dev libsystemd-dev
67 | wget https://github.com/warmchang/procps/archive/refs/tags/v4.0.5.zip -O procps.zip
68 | unzip procps.zip
69 | cd procps-4.0.5
70 | ./autogen.sh
71 | ./configure
72 | make
73 | sudo make install
74 |
75 | - name: Checkout
76 | uses: actions/checkout@v4
77 | with:
78 | submodules: recursive
79 |
80 | - name: Install Qt
81 | uses: jurplel/install-qt-action@v4
82 | with:
83 | version: '6.8.*'
84 | host: 'linux'
85 | target: 'desktop'
86 | arch: 'linux_gcc_64'
87 | modules: 'qthttpserver qtwebsockets'
88 |
89 | - name: Configure
90 | run: mkdir build && cd build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE:STRING=Release -G Ninja
91 |
92 | - name: Build
93 | working-directory: ./build
94 | run: ninja
95 |
96 | - name: Install
97 | working-directory: ./build
98 | run: DESTDIR=AppDir ninja install
99 |
100 | - name: Get linuxdeploy's AppImage
101 | working-directory: ./build
102 | run: wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage && chmod +x ./linuxdeploy-x86_64.AppImage
103 |
104 | - name: Get linuxdeploy's QT AppImage
105 | working-directory: ./build
106 | run: wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage && chmod +x ./linuxdeploy-plugin-qt-x86_64.AppImage
107 |
108 | - name: Rename
109 | run: echo "OUTPUT=${{ needs.Filename.outputs.filename }}-x86_64.AppImage" >> $GITHUB_ENV
110 |
111 | - name: Package AppImage
112 | working-directory: ./build
113 | run: ./linuxdeploy-x86_64.AppImage --appdir ./AppDir --output appimage --plugin qt --custom-apprun="../resources/linux/AppRun" --library="/usr/lib/x86_64-linux-gnu/libssl.so.3" --library="/usr/lib/x86_64-linux-gnu/libcrypto.so.3"
114 |
115 | - name: Upload artifacts
116 | uses: actions/upload-artifact@v4
117 | with:
118 | name: moondeckbuddy-linux
119 | path: ./build/MoonDeckBuddy*AppImage
120 | if-no-files-found: error
121 |
--------------------------------------------------------------------------------
/.github/workflows/filename.yaml:
--------------------------------------------------------------------------------
1 | name: Generate binary filename
2 | on:
3 | workflow_call:
4 | inputs:
5 | original_event_name:
6 | required: true
7 | type: string
8 | outputs:
9 | filename:
10 | value: ${{ jobs.Filename.outputs.filename }}
11 |
12 | jobs:
13 | Filename:
14 | runs-on: ubuntu-latest
15 | outputs:
16 | version: ${{ steps.version.outputs.name }}
17 | hash: ${{ steps.hash.outputs.name }}
18 | filename: ${{ steps.filename.outputs.name }}
19 | steps:
20 | - name: Checkout
21 | uses: actions/checkout@v4
22 |
23 | - name: Using the CMakeLists version
24 | if: inputs.original_event_name != 'pull_request'
25 | id: version
26 | run: echo "name=$(grep -E "\s+VERSION" CMakeLists.txt | xargs | cut -d' ' -f 2)" >> $GITHUB_OUTPUT
27 |
28 | - name: Using the commit SHA as a version
29 | if: inputs.original_event_name == 'pull_request'
30 | id: hash
31 | run: echo "name=$(git rev-parse --short ${{ github.sha }})" >> $GITHUB_OUTPUT
32 |
33 | - name: Generate name
34 | id: filename
35 | run: echo "name=MoonDeckBuddy-${{ steps.version.outputs.name }}${{ steps.hash.outputs.name }}" >> $GITHUB_OUTPUT
36 |
37 | - name: Print the filename base
38 | run: echo "${{ steps.filename.outputs.name }}"
--------------------------------------------------------------------------------
/.github/workflows/formatting.yaml:
--------------------------------------------------------------------------------
1 | name: Formatting check
2 | on: [workflow_call]
3 | jobs:
4 | Formatting:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - name: Checkout
8 | uses: actions/checkout@v4
9 |
10 | - name: Clang-format check
11 | run: find $PWD/src -type f \( -name "*.h" -o -name "*.cpp" \) -exec clang-format -style=file --dry-run --Werror {} +
--------------------------------------------------------------------------------
/.github/workflows/publish.yaml:
--------------------------------------------------------------------------------
1 | name: Build & Publish
2 | on:
3 | push:
4 | branches:
5 | - main
6 | jobs:
7 | Build:
8 | uses: ./.github/workflows/build.yaml
9 |
10 | Publish:
11 | runs-on: ubuntu-latest
12 | needs: [Build]
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 | with:
17 | submodules: recursive
18 |
19 | - name: Fetch tags
20 | run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* || echo "No tags fetched"
21 |
22 | - name: Get project version
23 | id: project_version
24 | run: echo "VERSION=$(grep -E "\s+VERSION" CMakeLists.txt | xargs | cut -d' ' -f 2)" >> $GITHUB_OUTPUT
25 |
26 | - name: Get tag status
27 | id: tagstatus
28 | run: echo "TAG_EXISTS=$(git show-ref --tags --verify --quiet -- 'refs/tags/v${{ steps.project_version.outputs.VERSION }}' && echo 1 || echo 0)" >> $GITHUB_OUTPUT
29 |
30 | - name: Print version and status
31 | run: |
32 | echo "Version: ${{ steps.project_version.outputs.VERSION }}"
33 | echo "Aready exists: ${{ steps.tagstatus.outputs.TAG_EXISTS }}"
34 |
35 | - name: Download artifacts
36 | uses: actions/download-artifact@v4
37 | if: steps.tagstatus.outputs.TAG_EXISTS == 0
38 | with:
39 | path: dist
40 | merge-multiple: true
41 |
42 | - name: Tag release
43 | uses: rickstaa/action-create-tag@v1
44 | if: steps.tagstatus.outputs.TAG_EXISTS == 0
45 | with:
46 | tag: "v${{ steps.project_version.outputs.VERSION }}"
47 |
48 | - name: Release
49 | uses: softprops/action-gh-release@v2
50 | if: steps.tagstatus.outputs.TAG_EXISTS == 0
51 | with:
52 | tag_name: "v${{ steps.project_version.outputs.VERSION }}"
53 | files: dist/*
54 | draft: true
55 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | CMakeLists.txt.user
2 | build/
3 | cmake-build-*/
4 | dist/
5 | .vscode/
6 | .idea/
7 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "resources/ssl"]
2 | path = resources/ssl
3 | url = https://github.com/FrogTheFrog/moondeck-keys.git
4 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | #----------------------------------------------------------------------------------------------------------------------
2 | # Project config
3 | #----------------------------------------------------------------------------------------------------------------------
4 |
5 | cmake_minimum_required(VERSION 3.27)
6 | project(MoonDeckBuddy
7 | VERSION 1.8.2
8 | DESCRIPTION "A server-side buddy app to help control the PC and Steam from SteamDeck via the MoonDeck plugin."
9 | HOMEPAGE_URL "https://github.com/FrogTheFrog/moondeck-buddy"
10 | LANGUAGES C CXX)
11 |
12 | set(EXEC_NAME_BUDDY MoonDeckBuddy)
13 | set(EXEC_NAME_STREAM MoonDeckStream)
14 |
15 | set(CMAKE_CXX_STANDARD 20)
16 | set(CMAKE_CXX_STANDARD_REQUIRED ON)
17 |
18 | set(CMAKE_AUTOMOC ON)
19 | set(CMAKE_AUTORCC ON)
20 |
21 | set(CMAKE_INCLUDE_CURRENT_DIR ON)
22 | set(BUDDY_RESOURCES "${CMAKE_CURRENT_LIST_DIR}/resources")
23 | set(ENABLE_CLANG_TIDY OFF CACHE BOOL "Enable clang-tidy build (slow)")
24 |
25 | if(MSVC)
26 | # warning level 4 and all warnings as errors
27 | add_compile_options(/W4 /WX)
28 | else()
29 | # lots of warnings and all warnings as errors
30 | add_compile_options(-Wall -Wextra -pedantic -Werror)
31 | endif()
32 |
33 | #----------------------------------------------------------------------------------------------------------------------
34 | # Build type parsing
35 | #----------------------------------------------------------------------------------------------------------------------
36 |
37 | set(DEBUG_MODE FALSE)
38 | string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)
39 | if(CMAKE_BUILD_TYPE_LOWER MATCHES "debug")
40 | set(DEBUG_MODE TRUE)
41 | endif()
42 |
43 | #----------------------------------------------------------------------------------------------------------------------
44 | # CLang-Tidy
45 | #----------------------------------------------------------------------------------------------------------------------
46 |
47 | if(ENABLE_CLANG_TIDY)
48 | if(NOT MSVC)
49 | message(STATUS "Clang-tidy build is enabled!")
50 | set(CMAKE_CXX_CLANG_TIDY clang-tidy)
51 | file(WRITE "${CMAKE_BINARY_DIR}/.clang-tidy" "Checks: '-*,llvm-twine-local'")
52 | endif()
53 | endif()
54 |
55 | #----------------------------------------------------------------------------------------------------------------------
56 | # Pre-Install
57 | #----------------------------------------------------------------------------------------------------------------------
58 |
59 | IF (WIN32)
60 | # General options
61 | set(CPACK_PACKAGE_VENDOR "MoonDeck")
62 | set(CPACK_PACKAGE_INSTALL_DIRECTORY ${PROJECT_NAME})
63 | set(CPACK_PACKAGE_EXECUTABLES "${EXEC_NAME_BUDDY};${EXEC_NAME_BUDDY}")
64 | set(CPACK_OUTPUT_FILE_PREFIX "../dist")
65 | set(CPACK_GENERATOR "7Z;INNOSETUP")
66 |
67 | # INNOSETUP specific options
68 | set(CPACK_INNOSETUP_IGNORE_LICENSE_PAGE ON)
69 | set(CPACK_INNOSETUP_USE_MODERN_WIZARD ON)
70 | set(CPACK_INNOSETUP_SETUP_PrivilegesRequired "lowest")
71 | set(CPACK_INNOSETUP_SETUP_RestartApplications OFF)
72 | set(CPACK_INNOSETUP_SETUP_CloseApplications "Force")
73 | set(CPACK_INNOSETUP_SETUP_SetupIconFile "${CMAKE_CURRENT_SOURCE_DIR}/resources/icons/moondeckbuddy.ico")
74 | set(CPACK_INNOSETUP_SETUP_UninstallDisplayIcon "{app}/bin/${EXEC_NAME_BUDDY}.exe")
75 | set(CPACK_INNOSETUP_CODE_FILES "${BUDDY_RESOURCES}/windows/innocode.iss")
76 | set(CPACK_INNOSETUP_EXTRA_SCRIPTS "${BUDDY_RESOURCES}/windows/innoextra.iss")
77 | ELSEIF(NOT UNIX)
78 | message(FATAL_ERROR "OS is not supported!")
79 | ENDIF()
80 |
81 | #----------------------------------------------------------------------------------------------------------------------
82 | # Subdirectories
83 | #----------------------------------------------------------------------------------------------------------------------
84 |
85 | add_subdirectory(src)
86 |
87 | #----------------------------------------------------------------------------------------------------------------------
88 | # Install
89 | #----------------------------------------------------------------------------------------------------------------------
90 |
91 | IF(WIN32)
92 | find_package(Qt6 REQUIRED COMPONENTS Core)
93 | set(DEPLOY_SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/deploy_${PROJECT_NAME}.cmake")
94 | set(BINARIES "${EXEC_NAME_BUDDY};${EXEC_NAME_STREAM}")
95 |
96 | file(GENERATE OUTPUT ${DEPLOY_SCRIPT} CONTENT "
97 | include(\"${QT_DEPLOY_SUPPORT}\")
98 | message(STATUS \"Running Qt deploy tool in working directory '${QT_DEPLOY_PREFIX}'\")
99 |
100 | set(BINARIES \"${BINARIES}\")
101 | foreach(BINARY \${BINARIES})
102 | execute_process(
103 | COMMAND_ECHO STDOUT
104 | COMMAND \"\${__QT_DEPLOY_TOOL}\" \"\${QT_DEPLOY_BIN_DIR}/\${BINARY}.exe\" --verbose 2 --dir . --libdir bin --plugindir plugins --force --no-compiler-runtime
105 | WORKING_DIRECTORY \"\${QT_DEPLOY_PREFIX}\"
106 | RESULT_VARIABLE result
107 | )
108 | if(result)
109 | message(FATAL_ERROR \"Executing \${__QT_DEPLOY_TOOL} failed: \${result}\")
110 | endif()
111 | endforeach()
112 |
113 | qt6_deploy_qt_conf(\"\${QT_DEPLOY_PREFIX}/\${QT_DEPLOY_BIN_DIR}/qt.conf\"
114 | PREFIX \"../\"
115 | BIN_DIR \"\${QT_DEPLOY_BIN_DIR}\"
116 | LIB_DIR \"\${QT_DEPLOY_LIB_DIR}\"
117 | PLUGINS_DIR \"\${QT_DEPLOY_PLUGINS_DIR}\"
118 | QML_DIR \"\${QT_DEPLOY_QML_DIR}\"
119 | )
120 | ")
121 |
122 | foreach(BINARY ${BINARIES})
123 | install(TARGETS ${BINARY})
124 | endforeach()
125 | install(SCRIPT ${DEPLOY_SCRIPT})
126 |
127 | include(CPack)
128 | ELSEIF(UNIX)
129 | install(
130 | TARGETS ${EXEC_NAME_BUDDY} ${EXEC_NAME_STREAM}
131 | RUNTIME DESTINATION bin
132 | )
133 |
134 | install(
135 | FILES resources/linux/MoonDeckBuddy.desktop
136 | DESTINATION share/applications/
137 | )
138 |
139 | foreach(size 16;32;64;128;256)
140 | install(
141 | FILES resources/icons/moondeckbuddy-${size}.png
142 | DESTINATION share/icons/hicolor/${size}x${size}/apps/
143 | RENAME moondeckbuddy.png
144 | )
145 | endforeach()
146 | ELSE()
147 | message(FATAL_ERROR "OS is not supported!")
148 | ENDIF()
149 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MoonDeck Buddy  [](https://discord.com/invite/U88fbeHyzt)
2 |
3 | A server-side part of the [MoonDeck](https://github.com/FrogTheFrog/moondeck) plugin for the SteamDeck.
4 |
5 | The main goal is to have reliable information about Steam state:
6 | * is Steam running or not;
7 | * what Steam game is running and whether the game we want is updating or not.
8 |
9 | Additionally, it allows to:
10 | * launch a Steam game;
11 | * close the Steam process;
12 | * shutdown, restart or suspend the PC.
13 |
14 | ## How to install it?
15 |
16 | See the [wiki](https://github.com/FrogTheFrog/moondeck-buddy/wiki) page for installation steps and other information!
17 |
--------------------------------------------------------------------------------
/resources/icons/moondeckbuddy-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrogTheFrog/moondeck-buddy/376352b17e4f879b6ba19df57afddbdadb45c506/resources/icons/moondeckbuddy-128.png
--------------------------------------------------------------------------------
/resources/icons/moondeckbuddy-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrogTheFrog/moondeck-buddy/376352b17e4f879b6ba19df57afddbdadb45c506/resources/icons/moondeckbuddy-16.png
--------------------------------------------------------------------------------
/resources/icons/moondeckbuddy-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrogTheFrog/moondeck-buddy/376352b17e4f879b6ba19df57afddbdadb45c506/resources/icons/moondeckbuddy-256.png
--------------------------------------------------------------------------------
/resources/icons/moondeckbuddy-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrogTheFrog/moondeck-buddy/376352b17e4f879b6ba19df57afddbdadb45c506/resources/icons/moondeckbuddy-32.png
--------------------------------------------------------------------------------
/resources/icons/moondeckbuddy-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrogTheFrog/moondeck-buddy/376352b17e4f879b6ba19df57afddbdadb45c506/resources/icons/moondeckbuddy-64.png
--------------------------------------------------------------------------------
/resources/icons/moondeckbuddy.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrogTheFrog/moondeck-buddy/376352b17e4f879b6ba19df57afddbdadb45c506/resources/icons/moondeckbuddy.ico
--------------------------------------------------------------------------------
/resources/linux/AppRun:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 | set -e
3 |
4 | HERE="$(readlink -f "$(dirname "$0")")"
5 |
6 | # Wayland plugin is not packaged, so defining this to get rid of the warning
7 | export QT_QPA_PLATFORM=xcb
8 |
9 | if [ "$1" = "--exec" ]; then
10 | BINARY_NAME="$2"
11 | ARGS="${@:3}"
12 | if [ -e "$HERE/usr/bin/$BINARY_NAME" ] ; then
13 | exec "$HERE/usr/bin/$BINARY_NAME" $ARGS
14 | else
15 | echo "$BINARY_NAME does not exist!"
16 | exit 1
17 | fi
18 | elif [ ! -z $APPIMAGE ] ; then
19 | BINARY_NAME=$(basename "$ARGV0")
20 | ARGS="$@"
21 | if [ -e "$HERE/usr/bin/$BINARY_NAME" ] ; then
22 | # Replace the app image env with the symlink as it will be used
23 | # to create an autostart script
24 | SYMLINK_PATH="$(realpath -s "$ARGV0")"
25 | APPIMAGE=$SYMLINK_PATH exec "$HERE/usr/bin/$BINARY_NAME" $ARGS
26 | else
27 | exec "$HERE/usr/bin/MoonDeckBuddy" $ARGS
28 | fi
29 | else
30 | ARGS="$@"
31 | exec "$HERE/usr/bin/MoonDeckBuddy" $ARGS
32 | fi
--------------------------------------------------------------------------------
/resources/linux/MoonDeckBuddy.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Name=MoonDeckBuddy
3 | Type=Application
4 | Exec=MoonDeckBuddy
5 | Icon=moondeckbuddy
6 | Categories=Utility
7 |
--------------------------------------------------------------------------------
/resources/resources.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | icons/moondeckbuddy.ico
5 | ssl/moondeck_cert.pem
6 | ssl/moondeck_key.pem
7 |
8 |
--------------------------------------------------------------------------------
/resources/windows/innocode.iss:
--------------------------------------------------------------------------------
1 | function GetPreviousAppDir(): string;
2 | var InstallPath: string;
3 | var RegPath: string;
4 | begin
5 | RegPath := 'Software\Microsoft\Windows\CurrentVersion\Uninstall\MoonDeckBuddy_is1';
6 | // Check the HKCU (user-level) registry for a previous installation
7 | if RegQueryStringValue(HKCU, RegPath, 'Inno Setup: App Path', InstallPath) then
8 | begin
9 | Result := RemoveQuotes(InstallPath);
10 | end
11 | // If not found, check the HKLM (system-level) registry for previous installation with admin rights
12 | else if RegQueryStringValue(HKLM, RegPath, 'Inno Setup: App Path', InstallPath) then
13 | begin
14 | Result := RemoveQuotes(InstallPath);
15 | end
16 | else
17 | begin
18 | // If neither registry key is found, return an empty string
19 | Result := '';
20 | end;
21 | end;
22 |
23 | function InitializeSetup(): Boolean;
24 | var PreviousAppDir: string;
25 | var ResultCode: Integer;
26 | begin
27 | PreviousAppDir := GetPreviousAppDir();
28 | if PreviousAppDir <> '' then
29 | begin
30 | // Return code does not matter as the AppMutex be a fallback
31 | Exec(Format('%s\bin\MoonDeckBuddy.exe', [PreviousAppDir]), '--close-all', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)
32 | end;
33 |
34 | Result := True
35 | end;
36 |
37 | procedure CurStepChanged(CurStep: TSetupStep);
38 | var Version: String;
39 | var ResultCode: Integer;
40 | var NsisUninstaller: string;
41 | begin
42 | if CurStep = ssInstall then
43 | begin
44 | if (GetVersionNumbersString(ExpandConstant('{app}\bin\MoonDeckBuddy.exe'), Version)) then
45 | begin
46 | Log(Format('Installed version: %s', [Version]));
47 | end
48 | else begin
49 | if RegQueryStringValue(HKCU, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\MoonDeckBuddy', 'UninstallString', NsisUninstaller) then
50 | begin
51 | NsisUninstaller := RemoveQuotes(NsisUninstaller)
52 | if Exec(NsisUninstaller, Format('/S _?=%s', [ExtractFileDir(NsisUninstaller)]), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
53 | begin
54 | if (ResultCode = 0) then
55 | begin
56 | DeleteFile(NsisUninstaller);
57 | end
58 | else
59 | begin
60 | MsgBox('Failed to uninstall older version. Please do so manually!', mbError, MB_OK);
61 | Abort();
62 | end;
63 | end
64 | else
65 | begin
66 | MsgBox('Failed to uninstall older version. Please do so manually!', mbError, MB_OK);
67 | Abort();
68 | end;
69 | end;
70 | end;
71 | end;
72 | end;
73 |
74 | procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
75 | var ResultCode: Integer;
76 | begin
77 | if CurUninstallStep = usAppMutexCheck then
78 | begin
79 | // Return code does not matter as the AppMutex be a fallback
80 | Exec(ExpandConstant('{app}\bin\MoonDeckBuddy.exe'), '--close-all', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)
81 | end
82 | else if CurUninstallStep = usUninstall then
83 | begin
84 | if SuppressibleMsgBox('Do you want to remove settings?', mbConfirmation, MB_YESNO, IDYES) = IDYES then
85 | begin
86 | DeleteFile(ExpandConstant('{app}\bin\clients.json'));
87 | DeleteFile(ExpandConstant('{app}\bin\settings.json'));
88 | end;
89 | end;
90 | end;
--------------------------------------------------------------------------------
/resources/windows/innoextra.iss:
--------------------------------------------------------------------------------
1 | [Run]
2 | Filename: "{app}\bin\MoonDeckBuddy.exe"; Description: "Enable autostart"; Flags: postinstall; Parameters: "--enable-autostart"
3 | Filename: "{app}\bin\MoonDeckBuddy.exe"; Description: "Launch application"; Flags: postinstall nowait
4 |
5 | [UninstallRun]
6 | Filename: "{app}\bin\MoonDeckBuddy.exe"; Parameters: "--disable-autostart"; RunOnceId: "Cleanup"
7 |
8 | [UninstallDelete]
9 | Type: files; Name: "{app}\bin\moondeckbuddy.log"
10 | Type: files; Name: "{app}\bin\moondeckstream.log"
11 |
--------------------------------------------------------------------------------
/resources/windows/resources.rc:
--------------------------------------------------------------------------------
1 | IDI_ICON1 ICON "../icons/moondeckbuddy.ico"
2 |
--------------------------------------------------------------------------------
/resources/windows/version.rc.in:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #define VER_COMPANYNAME_STR "@CPACK_PACKAGE_VENDOR@\0"
4 | #define VER_FILEDESCRIPTION_STR "@EXEC_NAME@ - @PROJECT_DESCRIPTION@\0"
5 | #define VER_FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0
6 | #define VER_FILEVERSION_STR "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.0\0"
7 | #define VER_INTERNALNAME_STR "@EXEC_NAME@\0"
8 | #define VER_ORIGINALFILENAME_STR "@EXEC_NAME@.exe\0"
9 | #define VER_PRODUCTNAME_STR "@PROJECT_NAME@\0"
10 | #define VER_PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0
11 | #define VER_PRODUCTVERSION_STR "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.0\0"
12 |
13 | VS_VERSION_INFO VERSIONINFO
14 | FILEVERSION VER_FILEVERSION
15 | PRODUCTVERSION VER_PRODUCTVERSION
16 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
17 | FILEFLAGS 0
18 | FILEOS VOS__WINDOWS32
19 | FILETYPE VFT_APP
20 | FILESUBTYPE VFT2_UNKNOWN
21 | BEGIN
22 | BLOCK "StringFileInfo"
23 | BEGIN
24 | BLOCK "040904E4"
25 | BEGIN
26 | VALUE "CompanyName", VER_COMPANYNAME_STR
27 | VALUE "FileDescription", VER_FILEDESCRIPTION_STR
28 | VALUE "FileVersion", VER_FILEVERSION_STR
29 | VALUE "InternalName", VER_INTERNALNAME_STR
30 | VALUE "OriginalFilename", VER_ORIGINALFILENAME_STR
31 | VALUE "ProductName", VER_PRODUCTNAME_STR
32 | VALUE "ProductVersion", VER_PRODUCTVERSION_STR
33 | END
34 | END
35 |
36 | BLOCK "VarFileInfo"
37 | BEGIN
38 | /* The following line should only be modified for localized versions. */
39 | /* It consists of any number of WORD,WORD pairs, with each pair */
40 | /* describing a language,codepage combination supported by the file. */
41 | /* */
42 | /* For example, a file might have values "0x409,1252" indicating that it */
43 | /* supports English language (0x409) in the Windows ANSI codepage (1252). */
44 |
45 | VALUE "Translation", 0x409, 1252
46 |
47 | END
48 | END
--------------------------------------------------------------------------------
/src/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | #----------------------------------------------------------------------------------------------------------------------
2 | # Subdirectories
3 | #----------------------------------------------------------------------------------------------------------------------
4 |
5 | add_subdirectory(buddy)
6 | add_subdirectory(lib)
7 | add_subdirectory(stream)
8 |
--------------------------------------------------------------------------------
/src/buddy/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | #----------------------------------------------------------------------------------------------------------------------
2 | # External dependencies
3 | #----------------------------------------------------------------------------------------------------------------------
4 |
5 | find_package(Qt6 REQUIRED COMPONENTS Core Widgets Network)
6 | qt_standard_project_setup()
7 |
8 | #----------------------------------------------------------------------------------------------------------------------
9 | # Target config
10 | #----------------------------------------------------------------------------------------------------------------------
11 |
12 | set(EXEC_NAME ${EXEC_NAME_BUDDY})
13 | set(RESOURCES "${BUDDY_RESOURCES}/resources.qrc")
14 |
15 | if(WIN32)
16 | configure_file(
17 | "${BUDDY_RESOURCES}/windows/version.rc.in"
18 | "${CMAKE_CURRENT_BINARY_DIR}/version.rc"
19 | @ONLY)
20 |
21 | list(APPEND RESOURCES "${BUDDY_RESOURCES}/windows/resources.rc")
22 | list(APPEND RESOURCES "${CMAKE_CURRENT_BINARY_DIR}/version.rc")
23 | endif()
24 |
25 | add_executable(${EXEC_NAME} main.cpp routing.h routing.cpp ${RESOURCES})
26 | target_link_libraries(${EXEC_NAME} PRIVATE Qt6::Core Qt6::Widgets Qt6::Network utilslib serverlib oslib osspecificlib sharedlib)
27 | target_compile_definitions(${EXEC_NAME} PRIVATE EXEC_VERSION="${PROJECT_VERSION}")
28 |
29 | if(NOT DEBUG_MODE)
30 | if(WIN32)
31 | set_target_properties(${EXEC_NAME} PROPERTIES WIN32_EXECUTABLE TRUE)
32 | endif()
33 | endif()
34 |
--------------------------------------------------------------------------------
/src/buddy/routing.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // local includes
4 | #include "os/pccontrol.h"
5 | #include "os/sunshineapps.h"
6 | #include "server/httpserver.h"
7 | #include "server/pairingmanager.h"
8 |
9 | void setupRoutes(server::HttpServer& server, server::PairingManager& pairing_manager, os::PcControl& pc_control,
10 | os::SunshineApps& sunshine_apps, const QString& mac_address_override);
11 |
--------------------------------------------------------------------------------
/src/lib/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | #----------------------------------------------------------------------------------------------------------------------
2 | # Subdirectories
3 | #----------------------------------------------------------------------------------------------------------------------
4 |
5 | add_subdirectory(shared)
6 | add_subdirectory(server)
7 | add_subdirectory(utils)
8 | add_subdirectory(os)
9 |
--------------------------------------------------------------------------------
/src/lib/os/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | #----------------------------------------------------------------------------------------------------------------------
2 | # Lib config
3 | #----------------------------------------------------------------------------------------------------------------------
4 |
5 | set(LIBNAME oslib)
6 |
7 | #----------------------------------------------------------------------------------------------------------------------
8 | # External dependencies
9 | #----------------------------------------------------------------------------------------------------------------------
10 |
11 | find_package(Qt6 COMPONENTS Core Widgets Network REQUIRED)
12 | qt_standard_project_setup()
13 |
14 | #----------------------------------------------------------------------------------------------------------------------
15 | # Subdirectories
16 | #----------------------------------------------------------------------------------------------------------------------
17 |
18 | add_subdirectory(shared)
19 |
20 | IF(WIN32)
21 | add_subdirectory(win)
22 | ELSEIF(UNIX)
23 | add_subdirectory(linux)
24 | ELSE()
25 | message(FATAL_ERROR "OS is not supported!")
26 | ENDIF()
27 |
28 | add_subdirectory(steam)
29 |
30 | #----------------------------------------------------------------------------------------------------------------------
31 | # Header/Source files
32 | #----------------------------------------------------------------------------------------------------------------------
33 |
34 | file(GLOB HEADERS CONFIGURE_DEPENDS "include/os/*.h")
35 | file(GLOB SOURCES CONFIGURE_DEPENDS "*.cpp")
36 |
37 | #----------------------------------------------------------------------------------------------------------------------
38 | # Target config
39 | #----------------------------------------------------------------------------------------------------------------------
40 |
41 | add_library(${LIBNAME} ${HEADERS} ${SOURCES})
42 | target_link_libraries(${LIBNAME} PRIVATE Qt6::Core Qt6::Widgets Qt6::Network osspecificlib sharedlib utilslib PUBLIC ossharedlib ossteamlib)
43 | target_include_directories(${LIBNAME} PUBLIC include)
44 |
--------------------------------------------------------------------------------
/src/lib/os/autostarthandler.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/autostarthandler.h"
3 |
4 | // system/Qt includes
5 | #include
6 |
7 | // os-specific includes
8 | #if defined(Q_OS_WIN)
9 | #include "os/win/nativeautostarthandler.h"
10 | #elif defined(Q_OS_LINUX)
11 | #include "os/linux/nativeautostarthandler.h"
12 | #else
13 | #error OS is not supported!
14 | #endif
15 |
16 | namespace os
17 | {
18 | AutoStartHandler::AutoStartHandler(const shared::AppMetadata& app_meta)
19 | : m_impl{std::make_unique(app_meta)}
20 | {
21 | }
22 |
23 | AutoStartHandler::~AutoStartHandler() = default;
24 |
25 | void AutoStartHandler::setAutoStart(bool enable)
26 | {
27 | m_impl->setAutoStart(enable);
28 | }
29 |
30 | bool AutoStartHandler::isAutoStartEnabled() const
31 | {
32 | return m_impl->isAutoStartEnabled();
33 | }
34 | } // namespace os
35 |
--------------------------------------------------------------------------------
/src/lib/os/include/os/autostarthandler.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 |
6 | // forward declarations
7 | namespace shared
8 | {
9 | class AppMetadata;
10 | }
11 | namespace os
12 | {
13 | class NativeAutoStartHandlerInterface;
14 | }
15 |
16 | namespace os
17 | {
18 | class AutoStartHandler final
19 | {
20 | public:
21 | explicit AutoStartHandler(const shared::AppMetadata& app_meta);
22 | ~AutoStartHandler();
23 |
24 | void setAutoStart(bool enable);
25 | bool isAutoStartEnabled() const;
26 |
27 | private:
28 | std::unique_ptr m_impl;
29 | };
30 | } // namespace os
31 |
--------------------------------------------------------------------------------
/src/lib/os/include/os/networkinfo.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 |
6 | namespace os
7 | {
8 | class NetworkInfo
9 | {
10 | public:
11 | static QString getMacAddress(const QHostAddress& host_address);
12 | };
13 | } // namespace os
14 |
--------------------------------------------------------------------------------
/src/lib/os/include/os/pccontrol.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 | #include
6 |
7 | // local includes
8 | #include "os/autostarthandler.h"
9 | #include "os/pcstatehandler.h"
10 | #include "os/steamhandler.h"
11 |
12 | // forward declarations
13 | namespace utils
14 | {
15 | class AppSettings;
16 | } // namespace utils
17 | namespace os
18 | {
19 | class AutoStartHandlerInterface;
20 | class StreamStateHandlerInterface;
21 | } // namespace os
22 |
23 | namespace os
24 | {
25 | class PcControl : public QObject
26 | {
27 | Q_OBJECT
28 | Q_DISABLE_COPY(PcControl)
29 |
30 | public:
31 | explicit PcControl(const utils::AppSettings& app_settings);
32 | ~PcControl() override;
33 |
34 | bool launchSteam(bool big_picture_mode);
35 | enums::SteamUiMode getSteamUiMode() const;
36 | bool closeSteam();
37 |
38 | bool launchSteamApp(std::uint64_t app_id);
39 | std::optional> getAppData() const;
40 |
41 | std::optional> getNonSteamAppData(std::uint64_t user_id) const;
42 |
43 | bool shutdownPC();
44 | bool restartPC();
45 | bool suspendOrHibernatePC();
46 | bool endStream();
47 |
48 | enums::StreamState getStreamState() const;
49 | enums::PcState getPcState() const;
50 |
51 | void setAutoStart(bool enable);
52 | bool isAutoStartEnabled() const;
53 |
54 | signals:
55 | void signalShowTrayMessage(const QString& title, const QString& message, QSystemTrayIcon::MessageIcon icon,
56 | int milliseconds_timeout_hint);
57 |
58 | private slots:
59 | void slotHandleSteamClosed();
60 | void slotHandleStreamStateChange();
61 |
62 | private:
63 | const utils::AppSettings& m_app_settings;
64 | AutoStartHandler m_auto_start_handler;
65 | PcStateHandler m_pc_state_handler;
66 | SteamHandler m_steam_handler;
67 | std::unique_ptr m_stream_state_handler;
68 | };
69 | } // namespace os
70 |
--------------------------------------------------------------------------------
/src/lib/os/include/os/pcstatehandler.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // local includes
4 | #include "shared/enums.h"
5 |
6 | // forward declarations
7 | namespace os
8 | {
9 | class NativePcStateHandlerInterface;
10 | }
11 |
12 | namespace os
13 | {
14 | class PcStateHandler : public QObject
15 | {
16 | Q_OBJECT
17 | Q_DISABLE_COPY(PcStateHandler)
18 |
19 | public:
20 | explicit PcStateHandler(std::unique_ptr native_handler);
21 | ~PcStateHandler() override;
22 |
23 | enums::PcState getState() const;
24 |
25 | bool shutdownPC(uint grace_period_in_sec);
26 | bool restartPC(uint grace_period_in_sec);
27 | bool suspendPC(uint grace_period_in_sec);
28 | bool hibernatePC(uint grace_period_in_sec);
29 |
30 | private:
31 | using NativeMethod = bool (NativePcStateHandlerInterface::*)();
32 | bool doChangeState(uint grace_period_in_sec, const QString& cant_do_entry, const QString& failed_to_do_entry,
33 | NativeMethod can_do_method, NativeMethod do_method, enums::PcState new_state);
34 |
35 | enums::PcState m_state{enums::PcState::Normal};
36 | std::unique_ptr m_native_handler;
37 | };
38 | } // namespace os
39 |
--------------------------------------------------------------------------------
/src/lib/os/include/os/sleepinhibitor.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 | #include
6 |
7 | // forward declarations
8 | namespace os
9 | {
10 | class NativeSleepInhibitorInterface;
11 | }
12 |
13 | namespace os
14 | {
15 | class SleepInhibitor final
16 | {
17 | public:
18 | explicit SleepInhibitor(const QString& app_name);
19 | ~SleepInhibitor();
20 |
21 | private:
22 | std::unique_ptr m_impl;
23 | };
24 | } // namespace os
25 |
--------------------------------------------------------------------------------
/src/lib/os/include/os/steamhandler.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // local includes
4 | #include "os/steam/steamprocesstracker.h"
5 | #include "shared/enums.h"
6 |
7 | // forward declarations
8 | namespace utils
9 | {
10 | class AppSettings;
11 | } // namespace utils
12 | namespace os
13 | {
14 | class SteamAppWatcher;
15 | } // namespace os
16 |
17 | namespace os
18 | {
19 | class SteamHandler : public QObject
20 | {
21 | Q_OBJECT
22 | Q_DISABLE_COPY(SteamHandler)
23 |
24 | public:
25 | explicit SteamHandler(const utils::AppSettings& app_settings,
26 | std::unique_ptr process_handler_interface);
27 | ~SteamHandler() override;
28 |
29 | bool launchSteam(bool big_picture_mode);
30 | enums::SteamUiMode getSteamUiMode() const;
31 | bool close();
32 |
33 | std::optional> getAppData() const;
34 | bool launchApp(std::uint64_t app_id);
35 | void clearSessionData();
36 |
37 | std::optional> getNonSteamAppData(std::uint64_t user_id) const;
38 |
39 | signals:
40 | void signalSteamClosed();
41 |
42 | private slots:
43 | void slotSteamProcessStateChanged();
44 |
45 | private:
46 | struct SessionData
47 | {
48 | std::unique_ptr m_steam_app_watcher;
49 | };
50 |
51 | const utils::AppSettings& m_app_settings;
52 | SteamProcessTracker m_steam_process_tracker;
53 | SessionData m_session_data;
54 | };
55 | } // namespace os
56 |
--------------------------------------------------------------------------------
/src/lib/os/include/os/streamstatehandler.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // local includes
4 | #include "os/streamstatehandlerinterface.h"
5 | #include "shared/enums.h"
6 | #include "utils/heartbeat.h"
7 |
8 | namespace os
9 | {
10 | class StreamStateHandler : public StreamStateHandlerInterface
11 | {
12 | Q_OBJECT
13 | Q_DISABLE_COPY(StreamStateHandler)
14 |
15 | public:
16 | explicit StreamStateHandler(const QString& heartbeat_key);
17 | ~StreamStateHandler() override = default;
18 |
19 | bool endStream() override;
20 | enums::StreamState getCurrentState() const override;
21 |
22 | private slots:
23 | void slotHandleProcessStateChanges();
24 |
25 | private:
26 | enums::StreamState m_state{enums::StreamState::NotStreaming};
27 | utils::Heartbeat m_helper_heartbeat;
28 | };
29 | } // namespace os
30 |
--------------------------------------------------------------------------------
/src/lib/os/include/os/streamstatehandlerinterface.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 |
6 | // local includes
7 | #include "shared/enums.h"
8 |
9 | namespace os
10 | {
11 | class StreamStateHandlerInterface : public QObject
12 | {
13 | Q_OBJECT
14 |
15 | public:
16 | ~StreamStateHandlerInterface() override = default;
17 |
18 | virtual bool endStream() = 0;
19 | virtual enums::StreamState getCurrentState() const = 0;
20 |
21 | signals:
22 | void signalStreamStateChanged();
23 | };
24 | } // namespace os
25 |
--------------------------------------------------------------------------------
/src/lib/os/include/os/sunshineapps.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 | #include
6 |
7 | namespace os
8 | {
9 | class SunshineApps
10 | {
11 | Q_DISABLE_COPY(SunshineApps)
12 |
13 | public:
14 | explicit SunshineApps(QString filepath);
15 | virtual ~SunshineApps() = default;
16 |
17 | std::optional> load();
18 |
19 | private:
20 | QString m_filepath;
21 | };
22 | } // namespace os
23 |
--------------------------------------------------------------------------------
/src/lib/os/include/os/systemtray.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | // forward declarations
10 | namespace os
11 | {
12 | class PcControl;
13 | }
14 |
15 | namespace os
16 | {
17 | class SystemTray : public QObject
18 | {
19 | Q_OBJECT
20 | Q_DISABLE_COPY(SystemTray)
21 |
22 | public:
23 | explicit SystemTray(const QIcon& icon, QString app_name, PcControl& pc_control);
24 | ~SystemTray() override = default;
25 |
26 | signals:
27 | void signalQuitApp();
28 |
29 | public slots:
30 | void slotShowTrayMessage(const QString& title, const QString& message, QSystemTrayIcon::MessageIcon icon,
31 | int millisecondsTimeoutHint);
32 |
33 | private slots:
34 | void slotTryAttach();
35 |
36 | private:
37 | // Note: ctor/dtor order is important!
38 | QAction m_autostart_action;
39 | QAction m_quit_action;
40 | QMenu m_menu;
41 | std::unique_ptr m_tray_icon;
42 |
43 | QTimer m_tray_attach_retry_timer;
44 | uint m_retry_counter{0};
45 |
46 | const QIcon& m_icon;
47 | QString m_app_name;
48 | PcControl& m_pc_control;
49 | };
50 | } // namespace os
51 |
--------------------------------------------------------------------------------
/src/lib/os/linux/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | #----------------------------------------------------------------------------------------------------------------------
2 | # Lib config
3 | #----------------------------------------------------------------------------------------------------------------------
4 |
5 | set(LIBNAME osspecificlib)
6 |
7 | #----------------------------------------------------------------------------------------------------------------------
8 | # External dependencies
9 | #----------------------------------------------------------------------------------------------------------------------
10 |
11 | find_package(Qt6 COMPONENTS Core DBus REQUIRED)
12 | find_library(PROC2_LIBRARY NAMES proc2 REQUIRED)
13 | qt_standard_project_setup()
14 |
15 | #----------------------------------------------------------------------------------------------------------------------
16 | # Header/Source files
17 | #----------------------------------------------------------------------------------------------------------------------
18 |
19 | file(GLOB HEADERS CONFIGURE_DEPENDS "include/os/linux/*.h")
20 | file(GLOB SOURCES CONFIGURE_DEPENDS "*.cpp")
21 |
22 | #----------------------------------------------------------------------------------------------------------------------
23 | # Target config
24 | #----------------------------------------------------------------------------------------------------------------------
25 |
26 | add_library(${LIBNAME} ${HEADERS} ${SOURCES})
27 | target_link_libraries(${LIBNAME} PRIVATE Qt6::Core Qt6::DBus ${PROC2_LIBRARY} ossharedlib sharedlib)
28 | target_include_directories(${LIBNAME} PUBLIC include)
29 |
--------------------------------------------------------------------------------
/src/lib/os/linux/include/os/linux/nativeautostarthandler.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 |
6 | // local includes
7 | #include "os/shared/nativeautostarthandlerinterface.h"
8 |
9 | // forward declarations
10 | namespace shared
11 | {
12 | class AppMetadata;
13 | }
14 |
15 | namespace os
16 | {
17 | class NativeAutoStartHandler : public NativeAutoStartHandlerInterface
18 | {
19 | Q_DISABLE_COPY(NativeAutoStartHandler)
20 |
21 | public:
22 | explicit NativeAutoStartHandler(const shared::AppMetadata& app_meta);
23 | ~NativeAutoStartHandler() override = default;
24 |
25 | void setAutoStart(bool enable) override;
26 | bool isAutoStartEnabled() const override;
27 |
28 | private:
29 | const shared::AppMetadata& m_app_meta;
30 | };
31 | } // namespace os
32 |
--------------------------------------------------------------------------------
/src/lib/os/linux/include/os/linux/nativepcstatehandler.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 |
6 | // local includes
7 | #include "os/shared/nativepcstatehandlerinterface.h"
8 |
9 | namespace os
10 | {
11 | class NativePcStateHandler
12 | : public QObject
13 | , public NativePcStateHandlerInterface
14 | {
15 | Q_OBJECT
16 | Q_DISABLE_COPY(NativePcStateHandler)
17 |
18 | public:
19 | explicit NativePcStateHandler();
20 | ~NativePcStateHandler() override = default;
21 |
22 | bool canShutdownPC() override;
23 | bool canRestartPC() override;
24 | bool canSuspendPC() override;
25 | bool canHibernatePC() override;
26 |
27 | bool shutdownPC() override;
28 | bool restartPC() override;
29 | bool suspendPC() override;
30 | bool hibernatePC() override;
31 |
32 | private:
33 | QDBusInterface m_logind_bus;
34 | };
35 | } // namespace os
36 |
--------------------------------------------------------------------------------
/src/lib/os/linux/include/os/linux/nativeprocesshandler.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // local includes
4 | #include "os/shared/nativeprocesshandlerinterface.h"
5 |
6 | namespace os
7 | {
8 | class NativeProcessHandler : public NativeProcessHandlerInterface
9 | {
10 | Q_DISABLE_COPY(NativeProcessHandler)
11 |
12 | public:
13 | explicit NativeProcessHandler() = default;
14 | ~NativeProcessHandler() override = default;
15 |
16 | std::vector getPids() const override;
17 | QString getExecPath(uint pid) const override;
18 | QDateTime getStartTime(uint pid) const override;
19 | void close(uint pid) const override;
20 | void terminate(uint pid) const override;
21 | };
22 | } // namespace os
23 |
--------------------------------------------------------------------------------
/src/lib/os/linux/include/os/linux/nativesleepinhibitor.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 |
6 | // local includes
7 | #include "os/shared/nativesleepinhibitorinterface.h"
8 |
9 | namespace os
10 | {
11 | class NativeSleepInhibitor : public NativeSleepInhibitorInterface
12 | {
13 | public:
14 | explicit NativeSleepInhibitor(const QString& app_name);
15 | ~NativeSleepInhibitor() override = default;
16 |
17 | private:
18 | QDBusUnixFileDescriptor m_file_descriptor;
19 | };
20 | } // namespace os
21 |
--------------------------------------------------------------------------------
/src/lib/os/linux/nativeautostarthandler.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/linux/nativeautostarthandler.h"
3 |
4 | // system/Qt includes
5 | #include
6 |
7 | // local includes
8 | #include "shared/appmetadata.h"
9 |
10 | namespace
11 | {
12 | QString getAutoStartContents(const shared::AppMetadata& app_meta)
13 | {
14 | QString contents;
15 | QTextStream stream(&contents);
16 |
17 | stream << "[Desktop Entry]" << Qt::endl;
18 | stream << "Type=Application" << Qt::endl;
19 | stream << "Name=" << app_meta.getAppName() << Qt::endl;
20 | stream << "Exec=" << app_meta.getAutoStartExec() << Qt::endl;
21 | stream << "Icon=" << app_meta.getAppName() << Qt::endl;
22 |
23 | return contents;
24 | }
25 | } // namespace
26 |
27 | namespace os
28 | {
29 | NativeAutoStartHandler::NativeAutoStartHandler(const shared::AppMetadata& app_meta)
30 | : m_app_meta{app_meta}
31 | {
32 | }
33 |
34 | void NativeAutoStartHandler::setAutoStart(bool enable)
35 | {
36 | const auto dir{m_app_meta.getAutoStartDir()};
37 | QFile file{m_app_meta.getAutoStartPath()};
38 |
39 | if (file.exists() && !file.remove())
40 | {
41 | qFatal("Failed to remove %s", qUtf8Printable(m_app_meta.getAutoStartPath()));
42 | return;
43 | }
44 |
45 | if (enable)
46 | {
47 | const QDir autostart_dir;
48 | if (!autostart_dir.mkpath(dir))
49 | {
50 | qFatal("Failed at mkpath %s", qUtf8Printable(dir));
51 | return;
52 | }
53 |
54 | if (!file.open(QIODevice::WriteOnly))
55 | {
56 | qFatal("Failed to open %s", qUtf8Printable(m_app_meta.getAutoStartPath()));
57 | return;
58 | }
59 |
60 | file.write(getAutoStartContents(m_app_meta).toUtf8());
61 | }
62 | }
63 |
64 | bool NativeAutoStartHandler::isAutoStartEnabled() const
65 | {
66 | QFile file{m_app_meta.getAutoStartPath()};
67 |
68 | if (!file.exists())
69 | {
70 | return false;
71 | }
72 |
73 | if (!file.open(QIODevice::ReadOnly))
74 | {
75 | qFatal("Failed to open %s", qUtf8Printable(m_app_meta.getAutoStartPath()));
76 | return false;
77 | }
78 |
79 | return file.readAll() == getAutoStartContents(m_app_meta);
80 | }
81 | } // namespace os
82 |
--------------------------------------------------------------------------------
/src/lib/os/linux/nativepcstatehandler.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/linux/nativepcstatehandler.h"
3 |
4 | // system/Qt includes
5 | #include
6 |
7 | // local includes
8 | #include "shared/loggingcategories.h"
9 |
10 | namespace
11 | {
12 | // NOLINTNEXTLINE(*-swappable-parameters)
13 | bool canDoQuery(QDBusInterface& bus, const QString& log_entry, const QString& query)
14 | {
15 | if (!bus.isValid())
16 | {
17 | return false;
18 | }
19 |
20 | const QString actual_query{QStringLiteral("Can") + query};
21 | const QDBusReply reply{bus.call(QDBus::Block, actual_query)};
22 | if (!reply.isValid())
23 | {
24 | qCWarning(lc::os).nospace() << "got invalid reply for " << log_entry << " (" << actual_query
25 | << "): " << reply.error();
26 | return false;
27 | }
28 |
29 | if (reply.value() != QStringLiteral("yes"))
30 | {
31 | qCWarning(lc::os).nospace() << "got unexpected reply for " << log_entry << " (" << actual_query
32 | << "): " << reply.value();
33 | return false;
34 | }
35 |
36 | return true;
37 | }
38 |
39 | bool doQuery(QDBusInterface& bus, const QString& log_entry, const QString& query)
40 | {
41 | if (!canDoQuery(bus, log_entry, query))
42 | {
43 | return false;
44 | }
45 |
46 | const bool polkit_interactive{true};
47 | const QDBusReply reply{bus.call(QDBus::Block, query, polkit_interactive)};
48 | if (!reply.isValid())
49 | {
50 | qCWarning(lc::os).nospace() << "got invalid reply for " << log_entry << " (" << query << "): " << reply.error();
51 | return false;
52 | }
53 |
54 | return true;
55 | }
56 | } // namespace
57 |
58 | namespace os
59 | {
60 | NativePcStateHandler::NativePcStateHandler()
61 | : m_logind_bus{"org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager",
62 | QDBusConnection::systemBus()}
63 | {
64 | if (!m_logind_bus.isValid())
65 | {
66 | qCWarning(lc::os) << "logind bus is invalid!";
67 | }
68 | }
69 |
70 | bool NativePcStateHandler::canShutdownPC()
71 | {
72 | return canDoQuery(m_logind_bus, "shutdown", "PowerOff");
73 | }
74 |
75 | bool NativePcStateHandler::canRestartPC()
76 | {
77 | return canDoQuery(m_logind_bus, "restart", "Reboot");
78 | }
79 |
80 | bool NativePcStateHandler::canSuspendPC()
81 | {
82 | return canDoQuery(m_logind_bus, "suspend", "Suspend");
83 | }
84 |
85 | bool NativePcStateHandler::canHibernatePC()
86 | {
87 | return canDoQuery(m_logind_bus, "hibernate", "Hibernate");
88 | }
89 |
90 | bool NativePcStateHandler::shutdownPC()
91 | {
92 | return doQuery(m_logind_bus, "shutdown", "PowerOff");
93 | }
94 |
95 | bool NativePcStateHandler::restartPC()
96 | {
97 | return doQuery(m_logind_bus, "restart", "Reboot");
98 | }
99 |
100 | bool NativePcStateHandler::suspendPC()
101 | {
102 | return doQuery(m_logind_bus, "suspend", "Suspend");
103 | }
104 |
105 | bool NativePcStateHandler::hibernatePC()
106 | {
107 | return doQuery(m_logind_bus, "hibernate", "Hibernate");
108 | }
109 | } // namespace os
110 |
--------------------------------------------------------------------------------
/src/lib/os/linux/nativeprocesshandler.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/linux/nativeprocesshandler.h"
3 |
4 | // system/Qt includes
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 |
14 | // local includes
15 | #include "shared/loggingcategories.h"
16 |
17 | namespace
18 | {
19 | std::optional getBootTime()
20 | {
21 | static unsigned int boot_time{0};
22 | if (boot_time == 0)
23 | {
24 | stat_info* stat_info{nullptr};
25 | if (const int error = procps_stat_new(&stat_info); error < 0)
26 | {
27 | qWarning(lc::os) << "Failed at procps_stat_new: " << lc::getErrorString(error * -1);
28 | return std::nullopt;
29 | }
30 |
31 | boot_time = STAT_GET(stat_info, STAT_SYS_TIME_OF_BOOT, ul_int);
32 | procps_stat_unref(&stat_info);
33 | }
34 | return boot_time;
35 | }
36 |
37 | template
38 | ItemValue getPidItem(const uint pid, const pids_item item, const ItemValue& fallback, Getter&& getter)
39 | {
40 | std::array items{item};
41 |
42 | pids_info* info{nullptr};
43 | if (const int error = procps_pids_new(&info, items.data(), items.size()); error < 0)
44 | {
45 | qWarning(lc::os) << "Failed at procps_pids_new for" << pid << "-" << lc::getErrorString(error * -1);
46 | return fallback;
47 | }
48 |
49 | const auto cleanup{qScopeGuard([&]() { procps_pids_unref(&info); })};
50 |
51 | std::array pids{pid};
52 | const auto* result{procps_pids_select(info, pids.data(), pids.size(), PIDS_SELECT_PID)};
53 |
54 | if (!result)
55 | {
56 | qWarning(lc::os) << "Failed at procps_pids_select for" << pid << "-" << lc::getErrorString(errno);
57 | return fallback;
58 | }
59 |
60 | if (!result->counts || result->counts->total <= 0)
61 | {
62 | return fallback;
63 | }
64 |
65 | const auto* head_ptr{result->stacks && result->stacks[0]->head ? result->stacks[0]->head : nullptr};
66 | if (!head_ptr)
67 | {
68 | qWarning(lc::os) << "Failed at procps_pids_select for" << pid << "-" << lc::getErrorString(errno);
69 | return fallback;
70 | }
71 |
72 | return std::forward(getter)(head_ptr->result);
73 | }
74 |
75 | uint getParentPid(const uint pid)
76 | {
77 | return getPidItem(pid, PIDS_ID_PPID, 0u,
78 | [](const auto& result) { return result.s_int >= 0 ? static_cast(result.s_int) : 0u; });
79 | }
80 |
81 | QDateTime getStartTime(const uint pid)
82 | {
83 | return getPidItem(pid, PIDS_TIME_START, QDateTime{},
84 | [](const auto& result)
85 | {
86 | const auto boot_time{getBootTime()};
87 | if (!boot_time)
88 | {
89 | return QDateTime{};
90 | }
91 |
92 | const auto milliseconds{static_cast(std::round((result.real) * 1000.0))};
93 | const auto datetime{QDateTime::fromSecsSinceEpoch(*boot_time)};
94 | return datetime.addMSecs(milliseconds);
95 | });
96 | }
97 |
98 | std::vector getPids()
99 | {
100 | const QDir proc_dir{"/proc"};
101 | const auto dirs{proc_dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot)};
102 | std::vector pids;
103 |
104 | pids.reserve(dirs.size());
105 |
106 | for (const auto& dir : dirs)
107 | {
108 | bool converted{false};
109 | const uint pid = dir.toUInt(&converted);
110 |
111 | if (!converted)
112 | {
113 | continue;
114 | }
115 |
116 | pids.push_back(pid);
117 | }
118 |
119 | return pids;
120 | }
121 |
122 | std::vector getParentPids(const std::vector& pids)
123 | {
124 | std::vector parent_pids;
125 | std::transform(std::cbegin(pids), std::cend(pids), std::back_inserter(parent_pids), getParentPid);
126 | return parent_pids;
127 | }
128 |
129 | std::vector getRelatedPids(uint pid)
130 | {
131 | const std::vector all_pids{getPids()};
132 | const std::vector parent_pids{getParentPids(all_pids)};
133 |
134 | Q_ASSERT(all_pids.size() == parent_pids.size());
135 | std::vector related_pids;
136 |
137 | // Start searching for children from current pid
138 | related_pids.push_back(pid);
139 |
140 | for (std::size_t i = 0; i < related_pids.size(); ++i)
141 | {
142 | const uint related_pid{related_pids[i]};
143 | for (std::size_t k = 0; k < parent_pids.size(); ++k)
144 | {
145 | const uint parent_pid{parent_pids[k]};
146 | if (related_pid != parent_pid)
147 | {
148 | continue;
149 | }
150 |
151 | const uint process_pid{all_pids[k]};
152 | if (process_pid == related_pid)
153 | {
154 | // Is this even possible? To be your own parent?
155 | continue;
156 | }
157 |
158 | related_pids.push_back(process_pid);
159 | }
160 | }
161 |
162 | return related_pids;
163 | }
164 | } // namespace
165 |
166 | namespace os
167 | {
168 | std::vector NativeProcessHandler::getPids() const
169 | {
170 | return ::getPids();
171 | }
172 |
173 | QString NativeProcessHandler::getExecPath(uint pid) const
174 | {
175 | const QFileInfo info{"/proc/" + QString::number(pid) + "/exe"};
176 | return QFileInfo{info.symLinkTarget()}.canonicalFilePath();
177 | }
178 |
179 | QDateTime NativeProcessHandler::getStartTime(uint pid) const
180 | {
181 | return ::getStartTime(pid);
182 | }
183 |
184 | void NativeProcessHandler::close(uint pid) const
185 | {
186 | const auto related_pids{getRelatedPids(pid)};
187 | for (const auto related_pid : related_pids)
188 | {
189 | if (kill(static_cast(related_pid), SIGTERM) < 0)
190 | {
191 | const auto error{errno};
192 | if (error != ESRCH)
193 | {
194 | qWarning(lc::os) << "Failed to close process" << related_pid << "-" << lc::getErrorString(error);
195 | }
196 | }
197 | }
198 | }
199 |
200 | void NativeProcessHandler::terminate(uint pid) const
201 | {
202 | const auto related_pids{getRelatedPids(pid)};
203 | for (const auto related_pid : related_pids)
204 | {
205 | if (kill(static_cast(related_pid), SIGKILL) < 0)
206 | {
207 | const auto error{errno};
208 | if (error != ESRCH)
209 | {
210 | qWarning(lc::os) << "Failed to terminate process" << related_pid << "-" << lc::getErrorString(error);
211 | }
212 | }
213 | }
214 | }
215 | } // namespace os
216 |
--------------------------------------------------------------------------------
/src/lib/os/linux/nativesleepinhibitor.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/linux/nativesleepinhibitor.h"
3 |
4 | // system/Qt includes
5 | #include
6 | #include
7 |
8 | // local includes
9 | #include "shared/loggingcategories.h"
10 |
11 | namespace os
12 | {
13 | NativeSleepInhibitor::NativeSleepInhibitor(const QString& app_name)
14 | {
15 | QDBusInterface manager_bus{"org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager",
16 | QDBusConnection::systemBus()};
17 | if (!manager_bus.isValid())
18 | {
19 | qCWarning(lc::os) << "Could not create QDBusInterface instance for inhibiting sleep!";
20 | return;
21 | }
22 |
23 | const auto method{QStringLiteral("Inhibit")};
24 | const auto what{QStringLiteral("idle:sleep")};
25 | const auto who{app_name};
26 | const auto why{QStringLiteral("Gaming Session")};
27 | const auto mode{QStringLiteral("block")};
28 |
29 | const QDBusReply reply{manager_bus.call(QDBus::Block, method, what, who, why, mode)};
30 | if (!reply.isValid())
31 | {
32 | qCWarning(lc::os).nospace() << "got invalid reply for " << method << " request: " << reply.error();
33 | return;
34 | }
35 |
36 | m_file_descriptor = reply.value();
37 | }
38 | } // namespace os
39 |
--------------------------------------------------------------------------------
/src/lib/os/networkinfo.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/networkinfo.h"
3 |
4 | // system/Qt includes
5 | #if defined(Q_OS_WIN)
6 | // clang-format off
7 | #include
8 | #include
9 | #include
10 | // clang-format on
11 | #include
12 | #endif
13 | #include
14 |
15 | // local includes
16 | #include "shared/loggingcategories.h"
17 |
18 | namespace
19 | {
20 | QString getMacAddressGeneric(const QHostAddress& host_address)
21 | {
22 | static const QSet allowed_types{QNetworkInterface::Ethernet, QNetworkInterface::Wifi};
23 |
24 | for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces())
25 | {
26 | if (allowed_types.contains(iface.type()) && iface.flags().testFlag(QNetworkInterface::IsRunning))
27 | {
28 | for (const QHostAddress& address_entry : QNetworkInterface::allAddresses())
29 | {
30 | if (address_entry.isEqual(host_address))
31 | {
32 | return iface.hardwareAddress();
33 | }
34 | }
35 | }
36 | }
37 |
38 | return {};
39 | }
40 | } // namespace
41 |
42 | namespace os
43 | {
44 | QString NetworkInfo::getMacAddress(const QHostAddress& host_address)
45 | {
46 | #if defined(Q_OS_WIN)
47 | ULONG buffer_size{0};
48 | std::vector buffer;
49 | PIP_ADAPTER_ADDRESSES addresses{nullptr};
50 |
51 | ULONG result = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, addresses, &buffer_size);
52 | if (result == ERROR_BUFFER_OVERFLOW)
53 | {
54 | buffer.resize(buffer_size);
55 | addresses = reinterpret_cast(buffer.data());
56 | result = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, addresses, &buffer_size);
57 | }
58 |
59 | if (result != NO_ERROR)
60 | {
61 | qCWarning(lc::os) << "GetAdaptersAddresses failed with error (using fallback):" << lc::getErrorString(result);
62 | return getMacAddressGeneric(host_address);
63 | }
64 |
65 | while (addresses != nullptr)
66 | {
67 | for (auto* unicast_address = addresses->FirstUnicastAddress; unicast_address != nullptr;
68 | unicast_address = unicast_address->Next)
69 | {
70 | if (getnameinfo(unicast_address->Address.lpSockaddr, unicast_address->Address.iSockaddrLength, nullptr, 0,
71 | nullptr, 0, NI_NUMERICHOST)
72 | != 0)
73 | {
74 | qCWarning(lc::os) << "WSAGetLastError failed with error:" << lc::getErrorString(WSAGetLastError());
75 | return {};
76 | }
77 |
78 | const QHostAddress address_entry(unicast_address->Address.lpSockaddr);
79 | if (host_address.isEqual(address_entry))
80 | {
81 | if (addresses->PhysicalAddressLength > 0)
82 | {
83 | QString mac_address = QString("%1:%2:%3:%4:%5:%6")
84 | .arg(addresses->PhysicalAddress[0], 2, 16, QLatin1Char('0'))
85 | .arg(addresses->PhysicalAddress[1], 2, 16, QLatin1Char('0'))
86 | .arg(addresses->PhysicalAddress[2], 2, 16, QLatin1Char('0'))
87 | .arg(addresses->PhysicalAddress[3], 2, 16, QLatin1Char('0'))
88 | .arg(addresses->PhysicalAddress[4], 2, 16, QLatin1Char('0'))
89 | .arg(addresses->PhysicalAddress[5], 2, 16, QLatin1Char('0'))
90 | .toUpper();
91 |
92 | return mac_address;
93 | }
94 |
95 | qCDebug(lc::os) << "Using fallback MAC detection, because no physical address found for"
96 | << address_entry;
97 | return getMacAddressGeneric(address_entry);
98 | }
99 | }
100 | addresses = addresses->Next;
101 | }
102 | #endif
103 | return getMacAddressGeneric(host_address);
104 | }
105 | } // namespace os
106 |
--------------------------------------------------------------------------------
/src/lib/os/pccontrol.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/pccontrol.h"
3 |
4 | // os-specific includes
5 | #if defined(Q_OS_WIN)
6 | #include "os/win/nativepcstatehandler.h"
7 | #include "os/win/nativeprocesshandler.h"
8 | #elif defined(Q_OS_LINUX)
9 | #include "os/linux/nativepcstatehandler.h"
10 | #include "os/linux/nativeprocesshandler.h"
11 | #else
12 | #error OS is not supported!
13 | #endif
14 |
15 | // local includes
16 | #include "os/pcstatehandler.h"
17 | #include "os/steam/steamprocesstracker.h"
18 | #include "os/streamstatehandler.h"
19 | #include "shared/loggingcategories.h"
20 | #include "utils/appsettings.h"
21 |
22 | namespace
23 | {
24 | constexpr int DEFAULT_TIMEOUT_S{10};
25 | constexpr int DEFAULT_TIMEOUT_MS{DEFAULT_TIMEOUT_S * 1000};
26 | } // namespace
27 |
28 | namespace os
29 | {
30 | PcControl::PcControl(const utils::AppSettings& app_settings)
31 | : m_app_settings{app_settings}
32 | , m_auto_start_handler{m_app_settings.getAppMetadata()}
33 | , m_pc_state_handler{std::make_unique()}
34 | , m_steam_handler{m_app_settings, std::make_unique()}
35 | , m_stream_state_handler{std::make_unique(
36 | m_app_settings.getAppMetadata().getAppName(shared::AppMetadata::App::Stream))}
37 | {
38 | Q_ASSERT(m_stream_state_handler != nullptr);
39 |
40 | connect(&m_steam_handler, &SteamHandler::signalSteamClosed, this, &PcControl::slotHandleSteamClosed);
41 | connect(m_stream_state_handler.get(), &StreamStateHandler::signalStreamStateChanged, this,
42 | &PcControl::slotHandleStreamStateChange);
43 | }
44 |
45 | // For forward declarations
46 | PcControl::~PcControl() = default;
47 |
48 | bool PcControl::launchSteam(const bool big_picture_mode)
49 | {
50 | return m_steam_handler.launchSteam(big_picture_mode);
51 | }
52 |
53 | enums::SteamUiMode PcControl::getSteamUiMode() const
54 | {
55 | return m_steam_handler.getSteamUiMode();
56 | }
57 |
58 | bool PcControl::closeSteam()
59 | {
60 | return m_steam_handler.close();
61 | }
62 |
63 | bool PcControl::launchSteamApp(std::uint64_t app_id)
64 | {
65 | return m_steam_handler.launchApp(app_id);
66 | }
67 |
68 | std::optional> PcControl::getAppData() const
69 | {
70 | return m_steam_handler.getAppData();
71 | }
72 |
73 | std::optional> PcControl::getNonSteamAppData(const std::uint64_t user_id) const
74 | {
75 | return m_steam_handler.getNonSteamAppData(user_id);
76 | }
77 |
78 | bool PcControl::shutdownPC()
79 | {
80 | if (m_pc_state_handler.shutdownPC(DEFAULT_TIMEOUT_S))
81 | {
82 | closeSteam();
83 | endStream();
84 | emit signalShowTrayMessage("Shutdown in progress",
85 | m_app_settings.getAppMetadata().getAppName() + " is putting you to sleep :)",
86 | QSystemTrayIcon::MessageIcon::Information, DEFAULT_TIMEOUT_MS);
87 | return true;
88 | }
89 |
90 | return false;
91 | }
92 |
93 | bool PcControl::restartPC()
94 | {
95 | if (m_pc_state_handler.restartPC(DEFAULT_TIMEOUT_S))
96 | {
97 | closeSteam();
98 | endStream();
99 | emit signalShowTrayMessage("Restart in progress",
100 | m_app_settings.getAppMetadata().getAppName() + " is giving you new life :?",
101 | QSystemTrayIcon::MessageIcon::Information, DEFAULT_TIMEOUT_MS);
102 | return true;
103 | }
104 |
105 | return false;
106 | }
107 |
108 | bool PcControl::suspendOrHibernatePC()
109 | {
110 | const bool hibernation{m_app_settings.getPreferHibernation()};
111 | const bool result{hibernation ? m_pc_state_handler.hibernatePC(DEFAULT_TIMEOUT_S)
112 | : m_pc_state_handler.suspendPC(DEFAULT_TIMEOUT_S)};
113 | if (result)
114 | {
115 | if (m_app_settings.getCloseSteamBeforeSleep())
116 | {
117 | closeSteam();
118 | }
119 | endStream();
120 |
121 | emit signalShowTrayMessage(
122 | hibernation ? "Hibernation in progress" : "Suspend in progress",
123 | m_app_settings.getAppMetadata().getAppName()
124 | + (hibernation ? " is about to put you into hard sleep :O" : " is about to suspend you real hard :P"),
125 | QSystemTrayIcon::MessageIcon::Information, DEFAULT_TIMEOUT_MS);
126 | return true;
127 | }
128 |
129 | return false;
130 | }
131 |
132 | bool PcControl::endStream()
133 | {
134 | return m_stream_state_handler->endStream();
135 | }
136 |
137 | enums::StreamState PcControl::getStreamState() const
138 | {
139 | return m_stream_state_handler->getCurrentState();
140 | }
141 |
142 | enums::PcState PcControl::getPcState() const
143 | {
144 | return m_pc_state_handler.getState();
145 | }
146 |
147 | void PcControl::setAutoStart(bool enable)
148 | {
149 | m_auto_start_handler.setAutoStart(enable);
150 | }
151 |
152 | bool PcControl::isAutoStartEnabled() const
153 | {
154 | return m_auto_start_handler.isAutoStartEnabled();
155 | }
156 |
157 | void PcControl::slotHandleSteamClosed()
158 | {
159 | endStream();
160 | }
161 |
162 | void PcControl::slotHandleStreamStateChange()
163 | {
164 | switch (m_stream_state_handler->getCurrentState())
165 | {
166 | case enums::StreamState::NotStreaming:
167 | {
168 | qCInfo(lc::os) << "Stream has ended.";
169 | break;
170 | }
171 | case enums::StreamState::Streaming:
172 | {
173 | qCInfo(lc::os) << "Stream started.";
174 | break;
175 | }
176 | case enums::StreamState::StreamEnding:
177 | {
178 | qCInfo(lc::os) << "Stream is ending.";
179 | m_steam_handler.clearSessionData();
180 | break;
181 | }
182 | }
183 | }
184 | } // namespace os
185 |
--------------------------------------------------------------------------------
/src/lib/os/pcstatehandler.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/pcstatehandler.h"
3 |
4 | // system/Qt includes
5 | #include
6 |
7 | // local includes
8 | #include "os/shared/nativepcstatehandlerinterface.h"
9 | #include "shared/loggingcategories.h"
10 |
11 | namespace
12 | {
13 | const int SEC_TO_MS{1000};
14 |
15 | int getTimeoutTime(uint grace_period_in_sec)
16 | {
17 | return static_cast(grace_period_in_sec) * SEC_TO_MS;
18 | }
19 | } // namespace
20 |
21 | namespace os
22 | {
23 | PcStateHandler::PcStateHandler(std::unique_ptr native_handler)
24 | : m_native_handler{std::move(native_handler)}
25 | {
26 | Q_ASSERT(m_native_handler != nullptr);
27 | }
28 |
29 | PcStateHandler::~PcStateHandler() = default;
30 |
31 | enums::PcState PcStateHandler::getState() const
32 | {
33 | return m_state;
34 | }
35 |
36 | bool PcStateHandler::shutdownPC(uint grace_period_in_sec)
37 | {
38 | return doChangeState(grace_period_in_sec, "shut down", "shutdown", &NativePcStateHandlerInterface::canShutdownPC,
39 | &NativePcStateHandlerInterface::shutdownPC, enums::PcState::ShuttingDown);
40 | }
41 |
42 | bool PcStateHandler::restartPC(uint grace_period_in_sec)
43 | {
44 | return doChangeState(grace_period_in_sec, "restarted", "restart", &NativePcStateHandlerInterface::canRestartPC,
45 | &NativePcStateHandlerInterface::restartPC, enums::PcState::Restarting);
46 | }
47 |
48 | bool PcStateHandler::suspendPC(uint grace_period_in_sec)
49 | {
50 | return doChangeState(grace_period_in_sec, "suspended", "suspend", &NativePcStateHandlerInterface::canSuspendPC,
51 | &NativePcStateHandlerInterface::suspendPC, enums::PcState::Suspending);
52 | }
53 |
54 | bool PcStateHandler::hibernatePC(uint grace_period_in_sec)
55 | {
56 | return doChangeState(grace_period_in_sec, "hibernated", "hibernate", &NativePcStateHandlerInterface::canHibernatePC,
57 | &NativePcStateHandlerInterface::hibernatePC, enums::PcState::Suspending);
58 | }
59 |
60 | bool PcStateHandler::doChangeState(uint grace_period_in_sec, const QString& cant_do_entry,
61 | // NOLINTNEXTLINE(*-swappable-parameters)
62 | const QString& failed_to_do_entry, NativeMethod can_do_method,
63 | NativeMethod do_method, enums::PcState new_state)
64 | {
65 | if (m_state != enums::PcState::Normal)
66 | {
67 | qCDebug(lc::os) << "PC is already changing state. Aborting request.";
68 | return false;
69 | }
70 |
71 | if (!(m_native_handler.get()->*can_do_method)())
72 | {
73 | qCWarning(lc::os).nospace() << "PC cannot be " << cant_do_entry << "!";
74 | return false;
75 | }
76 |
77 | QTimer::singleShot(getTimeoutTime(grace_period_in_sec), this,
78 | [this, failed_to_do_entry, do_method]()
79 | {
80 | qCInfo(lc::os) << "Resetting PC state back to normal.";
81 | m_state = enums::PcState::Normal;
82 |
83 | if (!(m_native_handler.get()->*do_method)())
84 | {
85 | qCWarning(lc::os).nospace() << "Failed to " << failed_to_do_entry << " PC!";
86 | }
87 | });
88 |
89 | m_state = new_state;
90 | return true;
91 | }
92 | } // namespace os
93 |
--------------------------------------------------------------------------------
/src/lib/os/shared/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | #----------------------------------------------------------------------------------------------------------------------
2 | # Lib config
3 | #----------------------------------------------------------------------------------------------------------------------
4 |
5 | set(LIBNAME ossharedlib)
6 |
7 | #----------------------------------------------------------------------------------------------------------------------
8 | # External dependencies
9 | #----------------------------------------------------------------------------------------------------------------------
10 |
11 | find_package(Qt6 COMPONENTS Core REQUIRED)
12 | qt_standard_project_setup()
13 |
14 | #----------------------------------------------------------------------------------------------------------------------
15 | # Header/Source files
16 | #----------------------------------------------------------------------------------------------------------------------
17 |
18 | file(GLOB HEADERS CONFIGURE_DEPENDS "include/os/shared/*.h")
19 | file(GLOB SOURCES CONFIGURE_DEPENDS "*.cpp")
20 |
21 | #----------------------------------------------------------------------------------------------------------------------
22 | # Target config
23 | #----------------------------------------------------------------------------------------------------------------------
24 |
25 | add_library(${LIBNAME} ${HEADERS} ${SOURCES})
26 | target_link_libraries(${LIBNAME} PRIVATE Qt6::Core sharedlib)
27 | target_include_directories(${LIBNAME} PUBLIC include)
28 |
--------------------------------------------------------------------------------
/src/lib/os/shared/include/os/shared/nativeautostarthandlerinterface.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | namespace os
4 | {
5 | class NativeAutoStartHandlerInterface
6 | {
7 | public:
8 | virtual ~NativeAutoStartHandlerInterface() = default;
9 |
10 | virtual void setAutoStart(bool enable) = 0;
11 | virtual bool isAutoStartEnabled() const = 0;
12 | };
13 | } // namespace os
14 |
--------------------------------------------------------------------------------
/src/lib/os/shared/include/os/shared/nativepcstatehandlerinterface.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | namespace os
4 | {
5 | class NativePcStateHandlerInterface
6 | {
7 | public:
8 | virtual ~NativePcStateHandlerInterface() = default;
9 |
10 | virtual bool canShutdownPC() = 0;
11 | virtual bool canRestartPC() = 0;
12 | virtual bool canSuspendPC() = 0;
13 | virtual bool canHibernatePC() = 0;
14 |
15 | virtual bool shutdownPC() = 0;
16 | virtual bool restartPC() = 0;
17 | virtual bool suspendPC() = 0;
18 | virtual bool hibernatePC() = 0;
19 | };
20 | } // namespace os
21 |
--------------------------------------------------------------------------------
/src/lib/os/shared/include/os/shared/nativeprocesshandlerinterface.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 | #include
6 |
7 | namespace os
8 | {
9 | class NativeProcessHandlerInterface
10 | {
11 | public:
12 | virtual ~NativeProcessHandlerInterface() = default;
13 |
14 | virtual std::vector getPids() const = 0;
15 | virtual QString getExecPath(uint pid) const = 0;
16 | virtual QDateTime getStartTime(uint pid) const = 0;
17 | virtual void close(uint pid) const = 0;
18 | virtual void terminate(uint pid) const = 0;
19 | };
20 | } // namespace os
21 |
--------------------------------------------------------------------------------
/src/lib/os/shared/include/os/shared/nativesleepinhibitorinterface.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | namespace os
4 | {
5 | class NativeSleepInhibitorInterface
6 | {
7 | public:
8 | virtual ~NativeSleepInhibitorInterface() = default;
9 | };
10 | } // namespace os
11 |
--------------------------------------------------------------------------------
/src/lib/os/sleepinhibitor.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/sleepinhibitor.h"
3 |
4 | // system/Qt includes
5 | #include
6 |
7 | // os-specific includes
8 | #if defined(Q_OS_WIN)
9 | #include "os/win/nativesleepinhibitor.h"
10 | #elif defined(Q_OS_LINUX)
11 | #include "os/linux/nativesleepinhibitor.h"
12 | #else
13 | #error OS is not supported!
14 | #endif
15 |
16 | namespace os
17 | {
18 | SleepInhibitor::SleepInhibitor(const QString& app_name)
19 | : m_impl{std::make_unique(app_name)}
20 | {
21 | }
22 |
23 | // For forward declarations
24 | SleepInhibitor::~SleepInhibitor() = default;
25 | } // namespace os
26 |
--------------------------------------------------------------------------------
/src/lib/os/steam/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | #----------------------------------------------------------------------------------------------------------------------
2 | # Lib config
3 | #----------------------------------------------------------------------------------------------------------------------
4 |
5 | set(LIBNAME ossteamlib)
6 |
7 | #----------------------------------------------------------------------------------------------------------------------
8 | # External dependencies
9 | #----------------------------------------------------------------------------------------------------------------------
10 |
11 | find_package(Qt6 COMPONENTS Core REQUIRED)
12 | qt_standard_project_setup()
13 |
14 | #----------------------------------------------------------------------------------------------------------------------
15 | # Header/Source files
16 | #----------------------------------------------------------------------------------------------------------------------
17 |
18 | file(GLOB HEADERS CONFIGURE_DEPENDS "include/os/steam/*.h")
19 | file(GLOB SOURCES CONFIGURE_DEPENDS "*.cpp")
20 |
21 | #----------------------------------------------------------------------------------------------------------------------
22 | # Target config
23 | #----------------------------------------------------------------------------------------------------------------------
24 |
25 | add_library(${LIBNAME} ${HEADERS} ${SOURCES})
26 | target_link_libraries(${LIBNAME} PRIVATE Qt6::Core sharedlib ossharedlib)
27 | target_include_directories(${LIBNAME} PUBLIC include)
28 |
--------------------------------------------------------------------------------
/src/lib/os/steam/include/os/steam/steamappwatcher.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 |
6 | // local includes
7 | #include "shared/enums.h"
8 |
9 | // forward declarations
10 | namespace os
11 | {
12 | class SteamProcessTracker;
13 | } // namespace os
14 |
15 | namespace os
16 | {
17 | class SteamAppWatcher : public QObject
18 | {
19 | Q_OBJECT
20 |
21 | public:
22 | explicit SteamAppWatcher(const SteamProcessTracker& process_tracker, std::uint64_t app_id);
23 | ~SteamAppWatcher() override;
24 |
25 | static enums::AppState getAppState(const SteamProcessTracker& process_tracker, std::uint64_t app_id,
26 | enums::AppState prev_state = enums::AppState::Stopped);
27 |
28 | enums::AppState getAppState() const;
29 | std::uint64_t getAppId() const;
30 |
31 | private slots:
32 | void slotCheckState();
33 |
34 | private:
35 | const SteamProcessTracker& m_process_tracker;
36 | std::uint64_t m_app_id;
37 |
38 | enums::AppState m_current_state{enums::AppState::Stopped};
39 | QTimer m_check_timer;
40 | uint m_delay_counter{0};
41 | };
42 | } // namespace os
43 |
--------------------------------------------------------------------------------
/src/lib/os/steam/include/os/steam/steamcontentlogtracker.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // local includes
4 | #include "steamlogtracker.h"
5 |
6 | namespace os
7 | {
8 | class SteamContentLogTracker : public SteamLogTracker
9 | {
10 | Q_OBJECT
11 |
12 | public:
13 | enum class AppState
14 | {
15 | Stopped,
16 | Running,
17 | Updating
18 | };
19 | Q_ENUM(AppState)
20 |
21 | enum class AppStateChange
22 | {
23 | UpdateRequired,
24 | UpdateQueued,
25 | UpdateRunning,
26 | UpdateStarted,
27 | UpdateOptional,
28 | FullyInstalled,
29 | AppRunning,
30 | FilesMissing,
31 | Uninstalling,
32 | Uninstalled,
33 | ComponentInUse,
34 | Terminating,
35 | PrefetchingInfo
36 | };
37 | Q_ENUM(AppStateChange)
38 |
39 | explicit SteamContentLogTracker(const std::filesystem::path& logs_dir, QDateTime first_entry_time_filter);
40 | ~SteamContentLogTracker() override = default;
41 |
42 | AppState getAppState(std::uint64_t app_id) const;
43 |
44 | protected:
45 | void onLogChanged(const std::vector& new_lines) override;
46 |
47 | private:
48 | std::map m_app_states;
49 | };
50 | } // namespace os
51 |
--------------------------------------------------------------------------------
/src/lib/os/steam/include/os/steam/steamgameprocesslogtracker.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 |
6 | // local includes
7 | #include "steamlogtracker.h"
8 |
9 | namespace os
10 | {
11 | class SteamGameProcessLogTracker : public SteamLogTracker
12 | {
13 | Q_OBJECT
14 |
15 | public:
16 | explicit SteamGameProcessLogTracker(const std::filesystem::path& logs_dir, QDateTime first_entry_time_filter);
17 | ~SteamGameProcessLogTracker() override = default;
18 |
19 | bool isAnyProcessRunning(std::uint64_t app_id) const;
20 |
21 | protected:
22 | void onLogChanged(const std::vector& new_lines) override;
23 |
24 | private:
25 | std::map> m_app_id_to_process_ids;
26 | };
27 | } // namespace os
28 |
--------------------------------------------------------------------------------
/src/lib/os/steam/include/os/steam/steamlogtracker.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 | #include
6 | #include
7 |
8 | namespace os
9 | {
10 | class SteamLogTracker : public QObject
11 | {
12 | Q_OBJECT
13 |
14 | public:
15 | enum class TimeFormat
16 | {
17 | YYYY_MM_DD_hh_mm_ss,
18 | };
19 | Q_ENUM(TimeFormat)
20 |
21 | explicit SteamLogTracker(std::filesystem::path main_filename, std::filesystem::path backup_filename,
22 | QDateTime first_entry_time_filter,
23 | TimeFormat time_format = TimeFormat::YYYY_MM_DD_hh_mm_ss);
24 | ~SteamLogTracker() override = default;
25 |
26 | public slots:
27 | void slotCheckLog();
28 |
29 | protected:
30 | virtual void onLogChanged(const std::vector& new_lines) = 0;
31 | static std::uint64_t appIdFromString(const QString& app_id);
32 |
33 | private:
34 | std::filesystem::path m_main_filename;
35 | std::filesystem::path m_backup_filename;
36 | QDateTime m_first_entry_time_filter;
37 | TimeFormat m_time_format;
38 | qint64 m_last_prev_size{0};
39 | qint64 m_last_read_pos{0};
40 | bool m_initialized{false};
41 | };
42 | } // namespace os
43 |
--------------------------------------------------------------------------------
/src/lib/os/steam/include/os/steam/steamprocesstracker.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 |
6 | // local includes
7 | #include "os/steam/steamcontentlogtracker.h"
8 | #include "os/steam/steamgameprocesslogtracker.h"
9 | #include "os/steam/steamshaderlogtracker.h"
10 | #include "os/steam/steamwebhelperlogtracker.h"
11 |
12 | // forward declarations
13 | namespace os
14 | {
15 | class NativeProcessHandlerInterface;
16 | } // namespace os
17 |
18 | namespace os
19 | {
20 | class SteamProcessTracker : public QObject
21 | {
22 | Q_OBJECT
23 | Q_DISABLE_COPY(SteamProcessTracker)
24 |
25 | public:
26 | struct LogTrackers
27 | {
28 | QTimer m_read_timer;
29 | SteamWebHelperLogTracker m_web_helper;
30 | SteamContentLogTracker m_content_log;
31 | SteamGameProcessLogTracker m_gameprocess_log;
32 | SteamShaderLogTracker m_shader_log;
33 | };
34 |
35 | explicit SteamProcessTracker(std::unique_ptr native_handler);
36 | ~SteamProcessTracker() override;
37 |
38 | void close();
39 | void terminate();
40 |
41 | bool isRunning() const;
42 | uint getPid() const;
43 | QDateTime getStartTime() const;
44 | const LogTrackers* getLogTrackers() const;
45 | std::filesystem::path getSteamDir() const;
46 |
47 | signals:
48 | void signalProcessStateChanged();
49 |
50 | public slots:
51 | void slotCheckState();
52 |
53 | private slots:
54 | void slotCheckLogs();
55 |
56 | private:
57 | struct ProcessData
58 | {
59 | uint m_pid{0};
60 | QDateTime m_start_time;
61 | std::unique_ptr m_log_trackers;
62 | std::filesystem::path m_steam_dir;
63 | };
64 |
65 | ProcessData m_data;
66 | QTimer m_check_timer;
67 |
68 | std::unique_ptr m_native_handler;
69 | };
70 | } // namespace os
71 |
--------------------------------------------------------------------------------
/src/lib/os/steam/include/os/steam/steamshaderlogtracker.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 |
6 | // local includes
7 | #include "steamlogtracker.h"
8 |
9 | namespace os
10 | {
11 | class SteamShaderLogTracker : public SteamLogTracker
12 | {
13 | Q_OBJECT
14 |
15 | public:
16 | explicit SteamShaderLogTracker(const std::filesystem::path& logs_dir, QDateTime first_entry_time_filter);
17 | ~SteamShaderLogTracker() override = default;
18 |
19 | bool isAppCompilingShaders(std::uint64_t app_id) const;
20 |
21 | protected:
22 | void onLogChanged(const std::vector& new_lines) override;
23 |
24 | private:
25 | std::set m_apps_with_compiling_shaders;
26 | };
27 | } // namespace os
28 |
--------------------------------------------------------------------------------
/src/lib/os/steam/include/os/steam/steamwebhelperlogtracker.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // local includes
4 | #include "shared/enums.h"
5 | #include "steamlogtracker.h"
6 |
7 | namespace os
8 | {
9 | class SteamWebHelperLogTracker : public SteamLogTracker
10 | {
11 | Q_OBJECT
12 |
13 | public:
14 | explicit SteamWebHelperLogTracker(const std::filesystem::path& logs_dir, QDateTime first_entry_time_filter);
15 | ~SteamWebHelperLogTracker() override = default;
16 |
17 | enums::SteamUiMode getSteamUiMode() const;
18 |
19 | protected:
20 | void onLogChanged(const std::vector& new_lines) override;
21 |
22 | private:
23 | enums::SteamUiMode m_ui_mode{enums::SteamUiMode::Unknown};
24 | };
25 | } // namespace os
26 |
--------------------------------------------------------------------------------
/src/lib/os/steam/steamappwatcher.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/steam/steamappwatcher.h"
3 |
4 | // local includes
5 | #include "os/steam/steamprocesstracker.h"
6 | #include "shared/loggingcategories.h"
7 |
8 | #include
9 |
10 | namespace os
11 | {
12 | SteamAppWatcher::SteamAppWatcher(const SteamProcessTracker& process_tracker, const std::uint64_t app_id)
13 | : m_process_tracker{process_tracker}
14 | , m_app_id{app_id}
15 | {
16 | connect(&m_check_timer, &QTimer::timeout, this, &SteamAppWatcher::slotCheckState);
17 |
18 | m_check_timer.setInterval(500);
19 | m_check_timer.setSingleShot(true);
20 |
21 | qCInfo(lc::os) << "Started watching AppID:" << m_app_id;
22 | slotCheckState();
23 | }
24 |
25 | SteamAppWatcher::~SteamAppWatcher()
26 | {
27 | qCInfo(lc::os) << "Stopped watching AppID:" << m_app_id;
28 | }
29 |
30 | enums::AppState SteamAppWatcher::getAppState(const SteamProcessTracker& process_tracker, const std::uint64_t app_id,
31 | const enums::AppState prev_state)
32 | {
33 | const auto* log_trackers{process_tracker.getLogTrackers()};
34 | if (log_trackers == nullptr)
35 | {
36 | return enums::AppState::Stopped;
37 | }
38 |
39 | auto new_state{enums::AppState::Stopped};
40 | const auto content_state{log_trackers->m_content_log.getAppState(app_id)};
41 |
42 | if (content_state == SteamContentLogTracker::AppState::Updating
43 | || log_trackers->m_shader_log.isAppCompilingShaders(app_id))
44 | {
45 | new_state = enums::AppState::Updating;
46 | }
47 | else if (content_state == SteamContentLogTracker::AppState::Running)
48 | {
49 | new_state = enums::AppState::Running;
50 | }
51 | else if (log_trackers->m_gameprocess_log.isAnyProcessRunning(app_id))
52 | {
53 | // Try to preserve the latest state from other logs, unless this is the only data available
54 | new_state = prev_state == enums::AppState::Stopped ? enums::AppState::Running : prev_state;
55 | }
56 |
57 | return new_state;
58 | }
59 |
60 | enums::AppState SteamAppWatcher::getAppState() const
61 | {
62 | return m_current_state;
63 | }
64 |
65 | std::uint64_t SteamAppWatcher::getAppId() const
66 | {
67 | return m_app_id;
68 | }
69 |
70 | void SteamAppWatcher::slotCheckState()
71 | {
72 | const auto auto_start_timer{qScopeGuard([this]() { m_check_timer.start(); })};
73 |
74 | if (const auto new_state{getAppState(m_process_tracker, m_app_id, m_current_state)}; new_state != m_current_state)
75 | {
76 | if (new_state == enums::AppState::Stopped && m_delay_counter < 5)
77 | {
78 | ++m_delay_counter;
79 | return;
80 | }
81 |
82 | qCInfo(lc::os) << "[TRACKING] New app state for AppID" << m_app_id
83 | << "detected:" << lc::qEnumToString(m_current_state) << "->" << lc::qEnumToString(new_state);
84 | m_current_state = new_state;
85 | m_delay_counter = 0;
86 | }
87 | }
88 | } // namespace os
89 |
--------------------------------------------------------------------------------
/src/lib/os/steam/steamcontentlogtracker.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/steam/steamcontentlogtracker.h"
3 |
4 | // system/Qt includes
5 | #include
6 |
7 | // local includes
8 | #include "shared/loggingcategories.h"
9 |
10 | namespace os
11 | {
12 | namespace
13 | {
14 | std::map
15 | remapStateChanges(const std::map>& input)
16 | {
17 | using AppStateChange = SteamContentLogTracker::AppStateChange;
18 | using AppState = SteamContentLogTracker::AppState;
19 |
20 | const auto map_state{[](const AppStateChange state_change)
21 | {
22 | switch (state_change)
23 | {
24 | case AppStateChange::UpdateRunning:
25 | case AppStateChange::UpdateStarted:
26 | return AppState::Updating;
27 | case AppStateChange::AppRunning:
28 | return AppState::Running;
29 | case AppStateChange::UpdateRequired:
30 | case AppStateChange::UpdateQueued:
31 | case AppStateChange::UpdateOptional:
32 | case AppStateChange::FullyInstalled:
33 | case AppStateChange::FilesMissing:
34 | case AppStateChange::Uninstalling:
35 | case AppStateChange::Uninstalled:
36 | case AppStateChange::ComponentInUse:
37 | case AppStateChange::Terminating:
38 | case AppStateChange::PrefetchingInfo:
39 | return AppState::Stopped;
40 | }
41 |
42 | Q_UNREACHABLE();
43 | }};
44 |
45 | std::map new_app_states;
46 | for (const auto& [app_id, state_changes] : input)
47 | {
48 | qCDebug(lc::os) << "New state changes for AppID" << app_id << "detected:" << state_changes;
49 |
50 | AppState new_state{AppState::Stopped};
51 | for (const auto state_change : state_changes)
52 | {
53 | const auto state{map_state(state_change)};
54 | if (state != AppState::Stopped)
55 | {
56 | new_state = state;
57 | if (new_state == AppState::Running)
58 | {
59 | break;
60 | }
61 | }
62 | }
63 |
64 | new_app_states[app_id] = new_state;
65 | }
66 | return new_app_states;
67 | }
68 | } // namespace
69 |
70 | SteamContentLogTracker::SteamContentLogTracker(const std::filesystem::path& logs_dir, QDateTime first_entry_time_filter)
71 | : SteamLogTracker(logs_dir / "content_log.txt", logs_dir / "content_log.previous.txt",
72 | std::move(first_entry_time_filter))
73 | {
74 | }
75 |
76 | SteamContentLogTracker::AppState SteamContentLogTracker::getAppState(const std::uint64_t app_id) const
77 | {
78 | const auto it = m_app_states.find(app_id);
79 | return it != std::end(m_app_states) ? it->second : AppState::Stopped;
80 | }
81 |
82 | void SteamContentLogTracker::onLogChanged(const std::vector& new_lines)
83 | {
84 | static const auto known_states{
85 | []()
86 | {
87 | static const QRegularExpression capital_letter_regex{R"(([A-Z]))"};
88 | const auto enum_size{QMetaEnum::fromType().keyCount()};
89 | std::map states;
90 | for (int i = 0; i < enum_size; ++i)
91 | {
92 | const auto value{static_cast(QMetaEnum::fromType().value(i))};
93 | QString key{QMetaEnum::fromType().key(i)};
94 | key = key.replace(capital_letter_regex, R"( \1)").trimmed();
95 |
96 | states[key] = value;
97 | }
98 |
99 | return states;
100 | }()};
101 |
102 | std::map> new_change_states;
103 | for (const QString& line : new_lines)
104 | {
105 | static const QRegularExpression mode_regex{R"(AppID\s(\d+)\sstate\schanged\s:\s(.*),)"};
106 | const auto match{mode_regex.match(line)};
107 | if (match.hasMatch())
108 | {
109 | const auto app_id{appIdFromString(match.captured(1))};
110 | if (app_id == 0)
111 | {
112 | qCWarning(lc::os) << "Failed to get AppID from" << line;
113 | continue;
114 | }
115 |
116 | QVector mapped_change_states;
117 | const auto change_states{match.captured(2).split(',')};
118 | for (const auto& state : change_states)
119 | {
120 | auto it = known_states.find(state);
121 | if (it == std::end(known_states))
122 | {
123 | qCWarning(lc::os) << "Unmapped state for AppID" << app_id << "found:" << state;
124 | continue;
125 | }
126 |
127 | mapped_change_states.append(it->second);
128 | }
129 |
130 | new_change_states[app_id] = std::move(mapped_change_states);
131 | }
132 | }
133 |
134 | const auto new_app_states = remapStateChanges(new_change_states);
135 | for (const auto [app_id, app_state] : new_app_states)
136 | {
137 | auto it = m_app_states.find(app_id);
138 | if (it == std::end(m_app_states))
139 | {
140 | if (app_state == AppState::Stopped)
141 | {
142 | continue;
143 | }
144 |
145 | qCInfo(lc::os) << "New app state for AppID" << app_id << "detected:" << lc::qEnumToString(AppState::Stopped)
146 | << "->" << lc::qEnumToString(app_state);
147 | m_app_states[app_id] = app_state;
148 | continue;
149 | }
150 |
151 | if (it->second == app_state)
152 | {
153 | continue;
154 | }
155 |
156 | qCInfo(lc::os) << "New app state for AppID" << app_id << "detected:" << lc::qEnumToString(it->second) << "->"
157 | << lc::qEnumToString(app_state);
158 | if (app_state == AppState::Stopped)
159 | {
160 | m_app_states.erase(it);
161 | }
162 | else
163 | {
164 | it->second = app_state;
165 | }
166 | }
167 | }
168 | } // namespace os
169 |
--------------------------------------------------------------------------------
/src/lib/os/steam/steamgameprocesslogtracker.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/steam/steamgameprocesslogtracker.h"
3 |
4 | // system/Qt includes
5 | #include
6 |
7 | // local includes
8 | #include "shared/loggingcategories.h"
9 |
10 | namespace os
11 | {
12 | SteamGameProcessLogTracker::SteamGameProcessLogTracker(const std::filesystem::path& logs_dir,
13 | QDateTime first_entry_time_filter)
14 | : SteamLogTracker(logs_dir / "gameprocess_log.txt", logs_dir / "gameprocess_log.previous.txt",
15 | std::move(first_entry_time_filter))
16 | {
17 | }
18 |
19 | bool SteamGameProcessLogTracker::isAnyProcessRunning(const std::uint64_t app_id) const
20 | {
21 | return m_app_id_to_process_ids.contains(app_id);
22 | }
23 |
24 | void SteamGameProcessLogTracker::onLogChanged(const std::vector& new_lines)
25 | {
26 | std::set added_app_ids;
27 | std::set removed_app_ids;
28 |
29 | for (const QString& line : new_lines)
30 | {
31 | static const QRegularExpression add_regex{R"(AppID (\d+) adding PID (\d+))"};
32 | if (const auto match{add_regex.match(line)}; match.hasMatch())
33 | {
34 | const auto app_id{appIdFromString(match.captured(1))};
35 | if (app_id == 0)
36 | {
37 | qCWarning(lc::os) << "Failed to get AppID from" << line;
38 | continue;
39 | }
40 |
41 | const auto pid{match.captured(2).toUInt()};
42 | if (pid == 0)
43 | {
44 | qCWarning(lc::os) << "Failed to get PID from" << line;
45 | continue;
46 | }
47 |
48 | auto data_it = m_app_id_to_process_ids.find(app_id);
49 | if (data_it == std::end(m_app_id_to_process_ids))
50 | {
51 | data_it = m_app_id_to_process_ids.emplace(app_id, std::set{}).first;
52 |
53 | if (!removed_app_ids.contains(app_id))
54 | {
55 | added_app_ids.insert(app_id);
56 | }
57 | removed_app_ids.erase(app_id);
58 | }
59 |
60 | data_it->second.insert(pid);
61 | continue;
62 | }
63 |
64 | static const QRegularExpression remove_regex{
65 | R"((?:Game (\d+) going away.* PID (\d+))|(?:AppID (\d+) no longer.* PID (\d+)))"};
66 | if (const auto match{remove_regex.match(line)}; match.hasMatch())
67 | {
68 | const auto app_id{appIdFromString(match.hasCaptured(1) ? match.captured(1) : match.captured(3))};
69 | if (app_id == 0)
70 | {
71 | qCWarning(lc::os) << "Failed to get AppID from" << line;
72 | continue;
73 | }
74 |
75 | const auto pid{(match.hasCaptured(2) ? match.captured(2) : match.captured(4)).toUInt()};
76 | if (pid == 0)
77 | {
78 | qCWarning(lc::os) << "Failed to get PID from" << line;
79 | continue;
80 | }
81 |
82 | auto data_it = m_app_id_to_process_ids.find(app_id);
83 | if (data_it == std::end(m_app_id_to_process_ids))
84 | {
85 | qCWarning(lc::os) << "Trying to remove PID" << pid << "from" << app_id << "but AppID is not tracked!";
86 | continue;
87 | }
88 |
89 | data_it->second.erase(pid);
90 | if (data_it->second.empty())
91 | {
92 | m_app_id_to_process_ids.erase(data_it);
93 |
94 | if (!added_app_ids.contains(app_id))
95 | {
96 | removed_app_ids.insert(app_id);
97 | }
98 | added_app_ids.erase(app_id);
99 | }
100 | }
101 | }
102 |
103 | for (const auto& app_id : added_app_ids)
104 | {
105 | qCInfo(lc::os) << "Running processes added for AppID:" << app_id;
106 | }
107 |
108 | for (const auto& app_id : removed_app_ids)
109 | {
110 | qCInfo(lc::os) << "Running processes removed for AppID:" << app_id;
111 | }
112 | }
113 | } // namespace os
114 |
--------------------------------------------------------------------------------
/src/lib/os/steam/steamlogtracker.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/steam/steamlogtracker.h"
3 |
4 | // system/Qt includes
5 | #include
6 | #include
7 | #include
8 |
9 | // local includes
10 | #include "shared/loggingcategories.h"
11 |
12 | namespace
13 | {
14 | bool openForReading(QFile& file)
15 | {
16 | if (!file.exists())
17 | {
18 | qCDebug(lc::os) << "file" << file.fileName() << "does not exist yet.";
19 | return false;
20 | }
21 |
22 | if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
23 | {
24 | qCWarning(lc::os) << "file" << file.fileName() << "could not be opened!";
25 | return false;
26 | }
27 |
28 | return true;
29 | }
30 |
31 | QDateTime getDateTimeFromLogLine(const QString& line, const os::SteamLogTracker::TimeFormat time_format)
32 | {
33 | using enum os::SteamLogTracker::TimeFormat;
34 |
35 | switch (time_format)
36 | {
37 | case YYYY_MM_DD_hh_mm_ss:
38 | {
39 | static const QRegularExpression time_regex{R"(^\[(\d{4})-(\d{2})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})\])"};
40 | if (const auto match{time_regex.match(line)}; match.hasMatch())
41 | {
42 | const QDate date{match.captured(1).toInt(), match.captured(2).toInt(), match.captured(3).toInt()};
43 | const QTime time{match.captured(4).toInt(), match.captured(5).toInt(), match.captured(6).toInt()};
44 | return QDateTime{date, time};
45 | }
46 | }
47 | }
48 |
49 | return QDateTime{};
50 | }
51 |
52 | qint64 readAllLines(std::vector& lines, QFile& file)
53 | {
54 | QTextStream stream{&file};
55 | QString line;
56 |
57 | stream.seek(0);
58 |
59 | while (stream.readLineInto(&line))
60 | {
61 | if (!line.isEmpty())
62 | {
63 | lines.push_back(line);
64 | }
65 | }
66 |
67 | return stream.pos();
68 | }
69 |
70 | bool isLineBeforeDatetime(const QString& line, const QDateTime& datetime,
71 | const os::SteamLogTracker::TimeFormat time_format)
72 | {
73 | const QDateTime logtime{getDateTimeFromLogLine(line, time_format)};
74 | if (!logtime.isValid())
75 | {
76 | return false;
77 | }
78 |
79 | return logtime < datetime;
80 | }
81 |
82 | bool isLineAtOrAfterDatetime(const QString& line, const QDateTime& datetime,
83 | const os::SteamLogTracker::TimeFormat time_format)
84 | {
85 | const QDateTime logtime{getDateTimeFromLogLine(line, time_format)};
86 | if (!logtime.isValid())
87 | {
88 | return false;
89 | }
90 |
91 | return logtime >= datetime;
92 | }
93 |
94 | void filterLines(std::vector& lines, QDateTime& datetime, const os::SteamLogTracker::TimeFormat time_format)
95 | {
96 | auto line_rit = std::rbegin(lines);
97 | for (; line_rit != std::rend(lines); ++line_rit)
98 | {
99 | if (isLineBeforeDatetime(*line_rit, datetime, time_format))
100 | {
101 | break;
102 | }
103 | }
104 |
105 | auto line_it = line_rit == std::rend(lines) ? std::begin(lines) : line_rit.base() /* next element */;
106 | for (; line_it != std::end(lines); ++line_it)
107 | {
108 | if (isLineAtOrAfterDatetime(*line_it, datetime, time_format))
109 | {
110 | datetime = {};
111 | break;
112 | }
113 | }
114 |
115 | lines.erase(std::begin(lines), line_it);
116 | }
117 |
118 | qint64 readRemainingLines(std::vector& lines, QFile& file, QDateTime& first_entry_time_filter,
119 | const os::SteamLogTracker::TimeFormat time_format, const qint64 start_offset)
120 | {
121 | QTextStream stream{&file};
122 | QString line;
123 |
124 | // Start reading from the last position we've read.
125 | stream.seek(start_offset);
126 |
127 | while (stream.readLineInto(&line))
128 | {
129 | if (!line.isEmpty())
130 | {
131 | if (first_entry_time_filter.isValid())
132 | {
133 | if (!isLineAtOrAfterDatetime(line, first_entry_time_filter, time_format))
134 | {
135 | continue;
136 | }
137 |
138 | first_entry_time_filter = {};
139 | }
140 |
141 | lines.push_back(line);
142 | }
143 | }
144 |
145 | return stream.pos();
146 | }
147 | } // namespace
148 |
149 | namespace os
150 | {
151 | SteamLogTracker::SteamLogTracker(std::filesystem::path main_filename, std::filesystem::path backup_filename,
152 | QDateTime first_entry_time_filter, TimeFormat time_format)
153 | : m_main_filename{std::move(main_filename)}
154 | , m_backup_filename{std::move(backup_filename)}
155 | , m_first_entry_time_filter{std::move(first_entry_time_filter)}
156 | , m_time_format{time_format}
157 | {
158 | }
159 |
160 | void SteamLogTracker::slotCheckLog()
161 | {
162 | QFile main_file{m_main_filename};
163 | if (!openForReading(main_file))
164 | {
165 | return;
166 | }
167 |
168 | const auto current_main_file_size{main_file.size()};
169 | const bool was_main_file_appended{current_main_file_size > m_last_prev_size};
170 | const bool was_main_file_switched_with_backup{current_main_file_size < m_last_prev_size};
171 |
172 | if (!m_initialized)
173 | {
174 | qCInfo(lc::os) << "performing initial log read for files" << m_main_filename.generic_string() << "and"
175 | << m_backup_filename.generic_string();
176 |
177 | QFile backup_file{m_backup_filename};
178 | if (!openForReading(backup_file))
179 | {
180 | if (backup_file.exists())
181 | {
182 | // Warning already logged.
183 | return;
184 | }
185 |
186 | qCInfo(lc::os) << "skipping file" << m_backup_filename.generic_string()
187 | << "for initial read, because it does not exist.";
188 | }
189 |
190 | std::vector lines;
191 | if (backup_file.isOpen())
192 | {
193 | readAllLines(lines, backup_file);
194 | }
195 |
196 | m_last_read_pos = readAllLines(lines, main_file);
197 | m_last_prev_size = current_main_file_size;
198 |
199 | filterLines(lines, m_first_entry_time_filter, m_time_format);
200 | onLogChanged(lines);
201 | m_initialized = true;
202 | return;
203 | }
204 |
205 | if (was_main_file_appended)
206 | {
207 | qCDebug(lc::os) << "file" << m_main_filename.generic_string() << "was appended.";
208 |
209 | std::vector lines;
210 | m_last_read_pos =
211 | readRemainingLines(lines, main_file, m_first_entry_time_filter, m_time_format, m_last_read_pos);
212 | m_last_prev_size = current_main_file_size;
213 |
214 | onLogChanged(lines);
215 | return;
216 | }
217 |
218 | if (was_main_file_switched_with_backup)
219 | {
220 | qCDebug(lc::os) << "file" << m_main_filename.generic_string() << "was switched with"
221 | << m_backup_filename.generic_string();
222 |
223 | QFile backup_file{m_backup_filename};
224 | if (!openForReading(backup_file))
225 | {
226 | return;
227 | }
228 |
229 | std::vector lines;
230 | readRemainingLines(lines, backup_file, m_first_entry_time_filter, m_time_format, m_last_read_pos);
231 | m_last_read_pos = readRemainingLines(lines, main_file, m_first_entry_time_filter, m_time_format, 0);
232 | m_last_prev_size = current_main_file_size;
233 |
234 | onLogChanged(lines);
235 | return;
236 | }
237 |
238 | qCDebug(lc::os) << "file" << m_main_filename.generic_string() << "did not change.";
239 | }
240 |
241 | std::uint64_t SteamLogTracker::appIdFromString(const QString& app_id)
242 | {
243 | static_assert(sizeof(qulonglong) == sizeof(std::uint64_t));
244 | return app_id.toULongLong();
245 | }
246 | } // namespace os
247 |
--------------------------------------------------------------------------------
/src/lib/os/steam/steamprocesstracker.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/steam/steamprocesstracker.h"
3 |
4 | // system/Qt includes
5 | #include
6 | #include
7 | #include
8 |
9 | // local includes
10 | #include "os/shared/nativeprocesshandlerinterface.h"
11 | #include "shared/loggingcategories.h"
12 |
13 | namespace
14 | {
15 | std::filesystem::path getSteamDir(const QString& exec_path)
16 | {
17 | QDir dir{exec_path};
18 | for (int i = 0; i < 3; ++i) // Go up only 3 levels
19 | {
20 | static const QStringList required_dirs{"logs", "userdata", "steamui"};
21 | if (std::ranges::all_of(required_dirs, [&dir](const auto& path) { return dir.exists(path); }))
22 | {
23 | return dir.filesystemCanonicalPath();
24 | }
25 |
26 | if (!dir.cdUp())
27 | {
28 | break;
29 | }
30 | }
31 |
32 | return {};
33 | }
34 | } // namespace
35 |
36 | namespace os
37 | {
38 | SteamProcessTracker::SteamProcessTracker(std::unique_ptr native_handler)
39 | : m_native_handler{std::move(native_handler)}
40 | {
41 | Q_ASSERT(m_native_handler);
42 |
43 | connect(&m_check_timer, &QTimer::timeout, this, &SteamProcessTracker::slotCheckState);
44 |
45 | m_check_timer.setInterval(1000);
46 | m_check_timer.setSingleShot(true);
47 |
48 | QTimer::singleShot(0, this, &SteamProcessTracker::slotCheckState);
49 | }
50 |
51 | // For forward declarations
52 | SteamProcessTracker::~SteamProcessTracker() = default;
53 |
54 | void SteamProcessTracker::close()
55 | {
56 | slotCheckState();
57 | if (isRunning())
58 | {
59 | m_native_handler->close(m_data.m_pid);
60 | }
61 | }
62 |
63 | void SteamProcessTracker::terminate()
64 | {
65 | slotCheckState();
66 | if (isRunning())
67 | {
68 | m_native_handler->terminate(m_data.m_pid);
69 | }
70 | }
71 |
72 | bool SteamProcessTracker::isRunning() const
73 | {
74 | return m_data.m_pid != 0;
75 | }
76 |
77 | uint SteamProcessTracker::getPid() const
78 | {
79 | return m_data.m_pid;
80 | }
81 |
82 | QDateTime SteamProcessTracker::getStartTime() const
83 | {
84 | return m_data.m_start_time;
85 | }
86 |
87 | const SteamProcessTracker::LogTrackers* SteamProcessTracker::getLogTrackers() const
88 | {
89 | return m_data.m_log_trackers.get();
90 | }
91 |
92 | std::filesystem::path SteamProcessTracker::getSteamDir() const
93 | {
94 | return m_data.m_steam_dir;
95 | }
96 |
97 | void SteamProcessTracker::slotCheckState()
98 | {
99 | m_check_timer.stop();
100 | const auto auto_start_timer{qScopeGuard([this]() { m_check_timer.start(); })};
101 |
102 | if (isRunning())
103 | {
104 | const auto start_time = m_native_handler->getStartTime(m_data.m_pid);
105 | if (m_data.m_start_time == start_time)
106 | {
107 | return;
108 | }
109 |
110 | m_data = {};
111 | emit signalProcessStateChanged();
112 | }
113 |
114 | const auto pids{m_native_handler->getPids()};
115 | for (const auto pid : pids)
116 | {
117 | const QString exec_path{m_native_handler->getExecPath(pid)};
118 | if (exec_path.isEmpty())
119 | {
120 | continue;
121 | }
122 |
123 | // clang-format off
124 | static const QRegularExpression exec_regex{
125 | R"((?:.+?steam\.exe$))" // Windows
126 | R"(|)" // OR
127 | R"((?:.*?steam.+?steam$))", // Linux
128 | QRegularExpression::CaseInsensitiveOption};
129 | // clang-format on
130 |
131 | if (!exec_path.contains(exec_regex))
132 | {
133 | continue;
134 | }
135 | qCInfo(lc::os) << "Found a matching Steam process. PATH:" << exec_path << "| PID:" << pid;
136 |
137 | auto cleanup{qScopeGuard([this]() { m_data = {}; })};
138 |
139 | m_data.m_steam_dir = ::getSteamDir(exec_path);
140 | if (m_data.m_steam_dir.empty())
141 | {
142 | qCInfo(lc::os) << "Could not resolve steam directory for running Steam process, PID:" << pid;
143 | continue;
144 | }
145 |
146 | const auto steam_log_dir{m_data.m_steam_dir / "logs"};
147 | if (!std::filesystem::exists(steam_log_dir))
148 | {
149 | qCInfo(lc::os) << "Could not resolve steam logs directory for running Steam process, PID:" << pid;
150 | continue;
151 | }
152 |
153 | m_data.m_start_time = m_native_handler->getStartTime(pid);
154 | if (!m_data.m_start_time.isValid())
155 | {
156 | qCWarning(lc::os) << "Could not resolve start time for running Steam process! PID:" << pid;
157 | break;
158 | }
159 |
160 | cleanup.dismiss();
161 |
162 | m_data.m_pid = pid;
163 | m_data.m_log_trackers.reset(new LogTrackers{QTimer{},
164 | SteamWebHelperLogTracker{steam_log_dir, m_data.m_start_time},
165 | SteamContentLogTracker{steam_log_dir, m_data.m_start_time},
166 | SteamGameProcessLogTracker{steam_log_dir, m_data.m_start_time},
167 | SteamShaderLogTracker{steam_log_dir, m_data.m_start_time}});
168 |
169 | connect(&m_data.m_log_trackers->m_read_timer, &QTimer::timeout, this, &SteamProcessTracker::slotCheckLogs);
170 | m_data.m_log_trackers->m_read_timer.setSingleShot(true);
171 | m_data.m_log_trackers->m_read_timer.setInterval(1000);
172 | slotCheckLogs();
173 |
174 | emit signalProcessStateChanged();
175 | break;
176 | }
177 | }
178 |
179 | void SteamProcessTracker::slotCheckLogs()
180 | {
181 | if (m_data.m_log_trackers)
182 | {
183 | m_data.m_log_trackers->m_read_timer.stop();
184 | const auto auto_start_timer{qScopeGuard([this]() { m_data.m_log_trackers->m_read_timer.start(); })};
185 |
186 | m_data.m_log_trackers->m_web_helper.slotCheckLog();
187 | m_data.m_log_trackers->m_content_log.slotCheckLog();
188 | m_data.m_log_trackers->m_gameprocess_log.slotCheckLog();
189 | m_data.m_log_trackers->m_shader_log.slotCheckLog();
190 | }
191 | }
192 | } // namespace os
193 |
--------------------------------------------------------------------------------
/src/lib/os/steam/steamshaderlogtracker.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/steam/steamshaderlogtracker.h"
3 |
4 | // system/Qt includes
5 | #include
6 |
7 | // local includes
8 | #include "shared/loggingcategories.h"
9 |
10 | namespace os
11 | {
12 | SteamShaderLogTracker::SteamShaderLogTracker(const std::filesystem::path& logs_dir, QDateTime first_entry_time_filter)
13 | : SteamLogTracker(logs_dir / "shader_log.txt", logs_dir / "shader_log.previous.txt",
14 | std::move(first_entry_time_filter))
15 | {
16 | }
17 |
18 | bool SteamShaderLogTracker::isAppCompilingShaders(const std::uint64_t app_id) const
19 | {
20 | return m_apps_with_compiling_shaders.contains(app_id);
21 | }
22 |
23 | void SteamShaderLogTracker::onLogChanged(const std::vector& new_lines)
24 | {
25 | std::map new_shader_states;
26 | for (const QString& line : new_lines)
27 | {
28 | static const QRegularExpression regex{
29 | R"((?:Starting processing job for app (\d+))|(?:Destroyed compile job (\d+)))"};
30 | if (const auto match{regex.match(line)}; match.hasMatch())
31 | {
32 | const bool started{match.hasCaptured(1)};
33 | const auto app_id{appIdFromString(started ? match.captured(1) : match.captured(2))};
34 | if (app_id == 0)
35 | {
36 | qCWarning(lc::os) << "Failed to get AppID from" << line;
37 | continue;
38 | }
39 |
40 | new_shader_states[app_id] = started;
41 | }
42 | }
43 |
44 | for (const auto [app_id, state] : new_shader_states)
45 | {
46 | auto data_it{m_apps_with_compiling_shaders.find(app_id)};
47 | if (state)
48 | {
49 | if (data_it == std::end(m_apps_with_compiling_shaders))
50 | {
51 | m_apps_with_compiling_shaders.insert(app_id);
52 | qCInfo(lc::os) << "Compiling shaders for AppID:" << app_id;
53 | }
54 | }
55 | else
56 | {
57 | if (data_it != std::end(m_apps_with_compiling_shaders))
58 | {
59 | m_apps_with_compiling_shaders.erase(data_it);
60 | qCInfo(lc::os) << "Stopped compiling shaders for AppID:" << app_id;
61 | }
62 | }
63 | }
64 | }
65 | } // namespace os
66 |
--------------------------------------------------------------------------------
/src/lib/os/steam/steamwebhelperlogtracker.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/steam/steamwebhelperlogtracker.h"
3 |
4 | // system/Qt includes
5 | #include
6 |
7 | // local includes
8 | #include "shared/loggingcategories.h"
9 |
10 | namespace os
11 | {
12 | SteamWebHelperLogTracker::SteamWebHelperLogTracker(const std::filesystem::path& logs_dir,
13 | QDateTime first_entry_time_filter)
14 | : SteamLogTracker(logs_dir / "webhelper.txt", logs_dir / "webhelper.previous.txt",
15 | std::move(first_entry_time_filter))
16 | {
17 | }
18 |
19 | enums::SteamUiMode SteamWebHelperLogTracker::getSteamUiMode() const
20 | {
21 | return m_ui_mode;
22 | }
23 |
24 | void SteamWebHelperLogTracker::onLogChanged(const std::vector& new_lines)
25 | {
26 | enums::SteamUiMode new_ui_mode{m_ui_mode};
27 | for (const QString& line : new_lines)
28 | {
29 | static const QRegularExpression initial_regex{R"(SP\s(?:(Desktop)|(BPM))_)"};
30 | static const QRegularExpression default_regex{R"(SP\s(?:(Desktop)|(BPM))_.+?WasHidden\s(?:(0)|(1)))"};
31 | const auto match{(new_ui_mode == enums::SteamUiMode::Unknown ? initial_regex : default_regex).match(line)};
32 | if (match.hasMatch())
33 | {
34 | constexpr int desktop_group{1};
35 | constexpr int was_hidden_group{4};
36 | if (match.hasCaptured(desktop_group))
37 | {
38 | if (match.hasCaptured(was_hidden_group))
39 | {
40 | // Desktop was hidden, but unless BPM is visible we assume it's Desktop mode still
41 | }
42 | else
43 | {
44 | new_ui_mode = enums::SteamUiMode::Desktop;
45 | }
46 | }
47 | else
48 | {
49 | if (match.hasCaptured(was_hidden_group))
50 | {
51 | // BPM was hidden, we will fall back to Desktop by default
52 | new_ui_mode = enums::SteamUiMode::Desktop;
53 | }
54 | else
55 | {
56 | new_ui_mode = enums::SteamUiMode::BigPicture;
57 | }
58 | }
59 | }
60 | }
61 |
62 | if (new_ui_mode != m_ui_mode)
63 | {
64 | qCInfo(lc::os()) << "Steam UI mode change:" << lc::qEnumToString(m_ui_mode) << "->"
65 | << lc::qEnumToString(new_ui_mode);
66 | m_ui_mode = new_ui_mode;
67 | }
68 | }
69 | } // namespace os
70 |
--------------------------------------------------------------------------------
/src/lib/os/streamstatehandler.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/streamstatehandler.h"
3 |
4 | namespace os
5 | {
6 | StreamStateHandler::StreamStateHandler(const QString& heartbeat_key)
7 | : m_helper_heartbeat{heartbeat_key}
8 | {
9 | connect(&m_helper_heartbeat, &utils::Heartbeat::signalStateChanged, this,
10 | &StreamStateHandler::slotHandleProcessStateChanges);
11 | m_helper_heartbeat.startListening();
12 | }
13 |
14 | bool StreamStateHandler::endStream()
15 | {
16 | if (m_state == enums::StreamState::Streaming)
17 | {
18 | m_helper_heartbeat.terminate();
19 | m_state = enums::StreamState::StreamEnding;
20 | emit signalStreamStateChanged();
21 | }
22 |
23 | return true;
24 | }
25 |
26 | enums::StreamState StreamStateHandler::getCurrentState() const
27 | {
28 | return m_state;
29 | }
30 |
31 | void StreamStateHandler::slotHandleProcessStateChanges()
32 | {
33 | switch (m_state)
34 | {
35 | case enums::StreamState::NotStreaming:
36 | {
37 | if (m_helper_heartbeat.isAlive())
38 | {
39 | m_state = enums::StreamState::Streaming;
40 | emit signalStreamStateChanged();
41 | }
42 | break;
43 | }
44 | case enums::StreamState::Streaming:
45 | case enums::StreamState::StreamEnding:
46 | {
47 | if (!m_helper_heartbeat.isAlive())
48 | {
49 | m_state = enums::StreamState::NotStreaming;
50 | emit signalStreamStateChanged();
51 | }
52 | break;
53 | }
54 | }
55 | }
56 | } // namespace os
57 |
--------------------------------------------------------------------------------
/src/lib/os/sunshineapps.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/sunshineapps.h"
3 |
4 | // system/Qt includes
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | // local includes
14 | #include "shared/loggingcategories.h"
15 |
16 | namespace
17 | {
18 | #if defined(Q_OS_LINUX)
19 | QString getConfigDir()
20 | {
21 | const auto xdg_config_env = qgetenv("XDG_CONFIG_HOME");
22 | if (!xdg_config_env.isEmpty())
23 | {
24 | const QDir xdg_cnnfig_dir{xdg_config_env};
25 | if (xdg_cnnfig_dir.exists())
26 | {
27 | return xdg_cnnfig_dir.absolutePath();
28 | }
29 | }
30 |
31 | return QDir::cleanPath(QDir::homePath() + "/.config");
32 | }
33 | #endif
34 | } // namespace
35 |
36 | namespace os
37 | {
38 | SunshineApps::SunshineApps(QString filepath)
39 | : m_filepath{std::move(filepath)}
40 | {
41 | }
42 |
43 | // NOLINTNEXTLINE(*-cognitive-complexity)
44 | std::optional> SunshineApps::load()
45 | {
46 | QString filepath{m_filepath};
47 | if (filepath.isEmpty()) // Fallback to places where we could expect the file to exist
48 | {
49 | #if defined(Q_OS_WIN)
50 | const QSettings settings(R"(HKEY_LOCAL_MACHINE\Software\LizardByte\Sunshine)", QSettings::NativeFormat);
51 | filepath = settings.value("Default").toString();
52 | if (!filepath.isEmpty())
53 | {
54 | filepath = QDir::cleanPath(filepath + "/config/apps.json");
55 | }
56 | #elif defined(Q_OS_LINUX)
57 | filepath = QDir::cleanPath(getConfigDir() + "/sunshine/apps.json");
58 | #else
59 | #error OS is not supported!
60 | #endif
61 | }
62 |
63 | qCDebug(lc::os) << "selected filepath for Sunshine apps:" << filepath;
64 | if (filepath.isEmpty())
65 | {
66 | qCWarning(lc::os) << "filepath for Sunshine apps is empty!";
67 | return std::nullopt;
68 | }
69 |
70 | QFile file{filepath};
71 | if (!file.open(QFile::ReadOnly))
72 | {
73 | qCWarning(lc::os) << "file" << filepath << "could not be opened! Reason:" << file.errorString();
74 | return std::nullopt;
75 | }
76 |
77 | const auto data{file.readAll()};
78 |
79 | QJsonParseError parser_error;
80 | const QJsonDocument json_data{QJsonDocument::fromJson(data, &parser_error)};
81 | if (json_data.isNull())
82 | {
83 | qCWarning(lc::os) << "failed to decode JSON data! Reason:" << parser_error.errorString() << "| data:" << data;
84 | return std::nullopt;
85 | }
86 |
87 | qCDebug(lc::os).noquote() << "Sunshine apps file content:" << Qt::endl << json_data.toJson(QJsonDocument::Indented);
88 | if (json_data.isObject())
89 | {
90 | const auto json_object = json_data.object();
91 | const auto apps_it{json_object.find("apps")};
92 | if (apps_it != json_object.end() && apps_it->isArray())
93 | {
94 | std::set parsed_apps{};
95 |
96 | const auto apps = apps_it->toArray();
97 | if (apps.isEmpty())
98 | {
99 | qCDebug(lc::os) << "there are no Sunshine apps to parse.";
100 | return parsed_apps;
101 | }
102 |
103 | for (const auto& app : apps)
104 | {
105 | if (!app.isObject())
106 | {
107 | qCDebug(lc::os) << "skipping entry as it's not an object:" << app;
108 | continue;
109 | }
110 |
111 | const auto app_object = app.toObject();
112 | const auto name_it{app_object.find("name")};
113 | if (name_it == app_object.end())
114 | {
115 | qCDebug(lc::os) << "skipping entry as it does not contain \"name\" field:" << app_object;
116 | continue;
117 | }
118 |
119 | if (!name_it->isString())
120 | {
121 | qCDebug(lc::os) << "skipping entry as the \"name\" field does not contain a string:" << *name_it;
122 | continue;
123 | }
124 |
125 | parsed_apps.insert(name_it->toString());
126 | }
127 |
128 | qCDebug(lc::os) << "parsed the following Sunshine apps:"
129 | << QSet{std::begin(parsed_apps), std::end(parsed_apps)};
130 | return parsed_apps;
131 | }
132 | }
133 |
134 | qCWarning(lc::os) << "file" << m_filepath << "could not be parsed!";
135 | return std::nullopt;
136 | }
137 | } // namespace os
--------------------------------------------------------------------------------
/src/lib/os/systemtray.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/systemtray.h"
3 |
4 | #if defined(Q_OS_LINUX)
5 | // system/Qt includes
6 | #include
7 | #endif
8 |
9 | // local includes
10 | #include "os/pccontrol.h"
11 | #include "shared/loggingcategories.h"
12 |
13 | namespace os
14 | {
15 | SystemTray::SystemTray(const QIcon& icon, QString app_name, PcControl& pc_control)
16 | : m_autostart_action{"Start on system startup"}
17 | , m_quit_action{"Exit"}
18 | , m_icon{icon}
19 | , m_app_name{std::move(app_name)}
20 | , m_pc_control{pc_control}
21 | {
22 | connect(&m_autostart_action, &QAction::triggered, this,
23 | [this]()
24 | {
25 | m_pc_control.setAutoStart(m_autostart_action.isChecked());
26 | if (m_autostart_action.isChecked() != m_pc_control.isAutoStartEnabled())
27 | {
28 | qCWarning(lc::utils) << "failed to enable/disable autostart!";
29 | m_autostart_action.setChecked(m_pc_control.isAutoStartEnabled());
30 | }
31 | });
32 | connect(&m_menu, &QMenu::aboutToShow, this,
33 | [this]()
34 | {
35 | // Sync the state in case it was set via cmd
36 | m_autostart_action.setChecked(m_pc_control.isAutoStartEnabled());
37 | });
38 | connect(&m_quit_action, &QAction::triggered, this, &SystemTray::signalQuitApp);
39 | connect(&m_tray_attach_retry_timer, &QTimer::timeout, this, &SystemTray::slotTryAttach);
40 |
41 | m_autostart_action.setCheckable(true);
42 |
43 | m_menu.addAction(&m_autostart_action);
44 | m_menu.addAction(&m_quit_action);
45 |
46 | const auto retry_interval{5000};
47 | m_tray_attach_retry_timer.setInterval(retry_interval);
48 | m_tray_attach_retry_timer.setSingleShot(true);
49 | slotTryAttach();
50 | }
51 |
52 | void SystemTray::slotShowTrayMessage(const QString& title, const QString& message, QSystemTrayIcon::MessageIcon icon,
53 | int millisecondsTimeoutHint)
54 | {
55 | if (!m_tray_icon)
56 | {
57 | return;
58 | }
59 |
60 | m_tray_icon->showMessage(title, message, icon, millisecondsTimeoutHint);
61 | }
62 |
63 | void SystemTray::slotTryAttach()
64 | {
65 | Q_ASSERT(m_tray_icon == nullptr);
66 |
67 | #if defined(Q_OS_LINUX)
68 | // workaround for https://bugreports.qt.io/browse/QTBUG-94871
69 | const QDBusInterface systrayHost(QLatin1String("org.kde.StatusNotifierWatcher"),
70 | QLatin1String("/StatusNotifierWatcher"),
71 | QLatin1String("org.kde.StatusNotifierWatcher"));
72 | if (!systrayHost.isValid() || !systrayHost.property("IsStatusNotifierHostRegistered").toBool())
73 | {
74 | constexpr int max_retries{25};
75 | if (m_retry_counter++ < max_retries)
76 | {
77 | m_tray_attach_retry_timer.start();
78 | return;
79 | }
80 | }
81 | #endif
82 |
83 | if (!QSystemTrayIcon::isSystemTrayAvailable())
84 | {
85 | qCWarning(lc::utils) << "failed to initialize system tray...";
86 | return;
87 | }
88 |
89 | m_tray_icon = std::make_unique();
90 | m_tray_icon->setIcon(m_icon);
91 | m_tray_icon->setContextMenu(&m_menu);
92 | m_tray_icon->setVisible(true);
93 | m_tray_icon->setToolTip(m_app_name);
94 | }
95 | } // namespace os
96 |
--------------------------------------------------------------------------------
/src/lib/os/win/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | #----------------------------------------------------------------------------------------------------------------------
2 | # Lib config
3 | #----------------------------------------------------------------------------------------------------------------------
4 |
5 | set(LIBNAME osspecificlib)
6 |
7 | #----------------------------------------------------------------------------------------------------------------------
8 | # External dependencies
9 | #----------------------------------------------------------------------------------------------------------------------
10 |
11 | find_package(Qt6 COMPONENTS Core REQUIRED)
12 | qt_standard_project_setup()
13 |
14 | find_library(PSAPI Psapi REQUIRED)
15 | find_library(POWRPROF PowrProf REQUIRED)
16 | add_compile_definitions(PSAPI_VERSION=1)
17 |
18 | #----------------------------------------------------------------------------------------------------------------------
19 | # Header/Source files
20 | #----------------------------------------------------------------------------------------------------------------------
21 |
22 | file(GLOB HEADERS CONFIGURE_DEPENDS "include/os/win/*.h")
23 | file(GLOB SOURCES CONFIGURE_DEPENDS "*.cpp")
24 |
25 | #----------------------------------------------------------------------------------------------------------------------
26 | # Target config
27 | #----------------------------------------------------------------------------------------------------------------------
28 |
29 | add_library(${LIBNAME} ${HEADERS} ${SOURCES})
30 | target_link_libraries(${LIBNAME} PRIVATE Qt6::Core Psapi PowrProf ossharedlib sharedlib ws2_32 iphlpapi)
31 | target_include_directories(${LIBNAME} PUBLIC include)
32 |
--------------------------------------------------------------------------------
/src/lib/os/win/include/os/win/nativeautostarthandler.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 |
6 | // local includes
7 | #include "os/shared/nativeautostarthandlerinterface.h"
8 |
9 | // forward declarations
10 | namespace shared
11 | {
12 | class AppMetadata;
13 | }
14 |
15 | namespace os
16 | {
17 | class NativeAutoStartHandler : public NativeAutoStartHandlerInterface
18 | {
19 | Q_DISABLE_COPY(NativeAutoStartHandler)
20 |
21 | public:
22 | explicit NativeAutoStartHandler(const shared::AppMetadata& app_meta);
23 | virtual ~NativeAutoStartHandler() override = default;
24 |
25 | void setAutoStart(bool enable) override;
26 | bool isAutoStartEnabled() const override;
27 |
28 | private:
29 | const shared::AppMetadata& m_app_meta;
30 | };
31 | } // namespace os
32 |
--------------------------------------------------------------------------------
/src/lib/os/win/include/os/win/nativepcstatehandler.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 |
6 | // local includes
7 | #include "os/shared/nativepcstatehandlerinterface.h"
8 |
9 | namespace os
10 | {
11 | class NativePcStateHandler
12 | : public QObject
13 | , public NativePcStateHandlerInterface
14 | {
15 | Q_OBJECT
16 | Q_DISABLE_COPY(NativePcStateHandler)
17 |
18 | public:
19 | explicit NativePcStateHandler();
20 | ~NativePcStateHandler() override = default;
21 |
22 | bool canShutdownPC() override;
23 | bool canRestartPC() override;
24 | bool canSuspendPC() override;
25 | bool canHibernatePC() override;
26 |
27 | bool shutdownPC() override;
28 | bool restartPC() override;
29 | bool suspendPC() override;
30 | bool hibernatePC() override;
31 |
32 | private:
33 | bool m_privilege_acquired;
34 | };
35 | } // namespace os
36 |
--------------------------------------------------------------------------------
/src/lib/os/win/include/os/win/nativeprocesshandler.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // local includes
4 | #include "os/shared/nativeprocesshandlerinterface.h"
5 |
6 | namespace os
7 | {
8 | class NativeProcessHandler : public NativeProcessHandlerInterface
9 | {
10 | Q_DISABLE_COPY(NativeProcessHandler)
11 |
12 | public:
13 | explicit NativeProcessHandler() = default;
14 | ~NativeProcessHandler() override = default;
15 |
16 | std::vector getPids() const override;
17 | QString getExecPath(uint pid) const override;
18 | QDateTime getStartTime(uint pid) const override;
19 | void close(uint pid) const override;
20 | void terminate(uint pid) const override;
21 | };
22 | } // namespace os
23 |
--------------------------------------------------------------------------------
/src/lib/os/win/include/os/win/nativesleepinhibitor.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 |
6 | // local includes
7 | #include "os/shared/nativesleepinhibitorinterface.h"
8 |
9 | namespace os
10 | {
11 | class NativeSleepInhibitor : public NativeSleepInhibitorInterface
12 | {
13 | public:
14 | explicit NativeSleepInhibitor(const QString& app_name);
15 | ~NativeSleepInhibitor() override = default;
16 | };
17 | } // namespace os
18 |
--------------------------------------------------------------------------------
/src/lib/os/win/nativeautostarthandler.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/win/nativeautostarthandler.h"
3 |
4 | // system/Qt includes
5 | #include
6 | #include
7 | #include
8 |
9 | // local includes
10 | #include "shared/appmetadata.h"
11 | #include "shared/loggingcategories.h"
12 |
13 | namespace os
14 | {
15 | NativeAutoStartHandler::NativeAutoStartHandler(const shared::AppMetadata& app_meta)
16 | : m_app_meta{app_meta}
17 | {
18 | }
19 |
20 | void NativeAutoStartHandler::setAutoStart(bool enable)
21 | {
22 | const auto dir{m_app_meta.getAutoStartDir()};
23 | QFile file{m_app_meta.getAutoStartPath()};
24 |
25 | if (file.exists() && !file.remove())
26 | {
27 | qFatal("Failed to remove %s", qUtf8Printable(m_app_meta.getAutoStartPath()));
28 | return;
29 | }
30 |
31 | if (enable)
32 | {
33 | if (!QFile::link(m_app_meta.getAutoStartExec(), m_app_meta.getAutoStartPath()))
34 | {
35 | qFatal("Failed to create link for %s -> %s", qUtf8Printable(m_app_meta.getAutoStartExec()),
36 | qUtf8Printable(m_app_meta.getAutoStartPath()));
37 | return;
38 | }
39 | }
40 | }
41 |
42 | bool NativeAutoStartHandler::isAutoStartEnabled() const
43 | {
44 | if (!QFile::exists(m_app_meta.getAutoStartPath()))
45 | {
46 | return false;
47 | }
48 |
49 | const QFileInfo info(m_app_meta.getAutoStartPath());
50 | if (!info.isShortcut())
51 | {
52 | return false;
53 | }
54 |
55 | return QFileInfo(info.symLinkTarget()).canonicalFilePath()
56 | == QFileInfo(m_app_meta.getAutoStartExec()).canonicalFilePath();
57 | }
58 | } // namespace os
59 |
--------------------------------------------------------------------------------
/src/lib/os/win/nativepcstatehandler.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/win/nativepcstatehandler.h"
3 |
4 | // A SEPARATE WINDOWS INCLUDE BECAUSE OF THE SMELL!
5 | #include
6 |
7 | // system/Qt includes
8 | #include
9 |
10 | // local includes
11 | #include "shared/loggingcategories.h"
12 |
13 | namespace
14 | {
15 | bool acquirePrivilege()
16 | {
17 | HANDLE token_handle{nullptr};
18 | TOKEN_PRIVILEGES token_privileges;
19 |
20 | if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token_handle) == FALSE)
21 | {
22 | qCWarning(lc::os) << "Failed to open process token! Reason: " << lc::getErrorString(GetLastError());
23 | return false;
24 | }
25 | auto cleanup = qScopeGuard([&token_handle]() { CloseHandle(token_handle); });
26 |
27 | {
28 | token_privileges.PrivilegeCount = 1;
29 | token_privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
30 | if (LookupPrivilegeValueW(nullptr, SE_SHUTDOWN_NAME, &token_privileges.Privileges[0].Luid) == FALSE)
31 | {
32 | qCWarning(lc::os) << "Failed to lookup privilege value! Reason: " << lc::getErrorString(GetLastError());
33 | return false;
34 | }
35 | }
36 |
37 | // Ignoring the useless return value...
38 | AdjustTokenPrivileges(token_handle, FALSE, &token_privileges, 0, nullptr, nullptr);
39 |
40 | const auto result = GetLastError();
41 | if (result != ERROR_SUCCESS)
42 | {
43 | qCWarning(lc::os) << "Failed to adjust token privileges! Reason: " << lc::getErrorString(result);
44 | return false;
45 | }
46 |
47 | return true;
48 | }
49 | } // namespace
50 |
51 | namespace os
52 | {
53 | NativePcStateHandler::NativePcStateHandler()
54 | : m_privilege_acquired{acquirePrivilege()}
55 | {
56 | if (!m_privilege_acquired)
57 | {
58 | qCWarning(lc::os) << "failed to acquire shutdown/restart/suspend privilege!";
59 | }
60 | }
61 |
62 | bool NativePcStateHandler::canShutdownPC()
63 | {
64 | return m_privilege_acquired;
65 | }
66 |
67 | bool NativePcStateHandler::canRestartPC()
68 | {
69 | return m_privilege_acquired;
70 | }
71 |
72 | bool NativePcStateHandler::canSuspendPC()
73 | {
74 | return m_privilege_acquired;
75 | }
76 |
77 | bool NativePcStateHandler::canHibernatePC()
78 | {
79 | return m_privilege_acquired;
80 | }
81 |
82 | bool NativePcStateHandler::shutdownPC()
83 | {
84 | if (!canShutdownPC())
85 | {
86 | return false;
87 | }
88 |
89 | if (InitiateSystemShutdownW(nullptr, nullptr, 0, TRUE, FALSE) == FALSE)
90 | {
91 | qCWarning(lc::os) << "InitiateSystemShutdownW (shutdown) failed! Reason:" << lc::getErrorString(GetLastError());
92 | return false;
93 | }
94 |
95 | return true;
96 | }
97 |
98 | bool NativePcStateHandler::restartPC()
99 | {
100 | if (!canRestartPC())
101 | {
102 | return false;
103 | }
104 |
105 | if (InitiateSystemShutdownW(nullptr, nullptr, 0, TRUE, TRUE) == FALSE)
106 | {
107 | qCWarning(lc::os) << "InitiateSystemShutdownW (restart) failed! Reason:" << lc::getErrorString(GetLastError());
108 | return false;
109 | }
110 |
111 | return true;
112 | }
113 |
114 | bool NativePcStateHandler::suspendPC()
115 | {
116 | if (!canSuspendPC())
117 | {
118 | return false;
119 | }
120 |
121 | if (SetSuspendState(FALSE, TRUE, FALSE) == FALSE)
122 | {
123 | qCWarning(lc::os) << "SetSuspendState failed! Reason:" << lc::getErrorString(GetLastError());
124 | return false;
125 | }
126 |
127 | return true;
128 | }
129 |
130 | bool NativePcStateHandler::hibernatePC()
131 | {
132 | if (!canHibernatePC())
133 | {
134 | return false;
135 | }
136 |
137 | if (SetSuspendState(TRUE, TRUE, FALSE) == FALSE)
138 | {
139 | qCWarning(lc::os) << "SetSuspendState failed! Reason:" << lc::getErrorString(GetLastError());
140 | return false;
141 | }
142 |
143 | return true;
144 | }
145 | } // namespace os
146 |
--------------------------------------------------------------------------------
/src/lib/os/win/nativeprocesshandler.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/win/nativeprocesshandler.h"
3 |
4 | // A SEPARATE WINDOWS INCLUDE BECAUSE OF THE SMELL!
5 | #include
6 |
7 | // system/Qt includes
8 | #include
9 |
10 | // local includes
11 | #include "shared/loggingcategories.h"
12 |
13 | #include
14 |
15 | namespace
16 | {
17 | template
18 | auto useProcHandle(const uint pid, Getter&& getter)
19 | {
20 | HANDLE proc_handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, static_cast(pid));
21 | auto cleanup = qScopeGuard(
22 | [&]()
23 | {
24 | if (proc_handle != nullptr)
25 | {
26 | CloseHandle(proc_handle);
27 | }
28 | });
29 |
30 | return getter(proc_handle);
31 | }
32 | } // namespace
33 |
34 | namespace os
35 | {
36 | std::vector NativeProcessHandler::getPids() const
37 | {
38 | std::vector handles;
39 | for (auto i = 0; i < 3; ++i)
40 | {
41 | const int handle_size{1024};
42 | // NOLINTNEXTLINE(*misplaced-widening-cast)
43 | handles.resize(static_cast(handle_size * (i + 1)));
44 |
45 | DWORD bytes_needed{0};
46 | const DWORD buffer_in_bytes{static_cast(handles.size() * sizeof(decltype(handles)::value_type))};
47 |
48 | if (EnumProcesses(handles.data(), buffer_in_bytes, &bytes_needed) == FALSE)
49 | {
50 | qFatal("Failed get a list of running processes! Reason: %s",
51 | qUtf8Printable(lc::getErrorString(GetLastError())));
52 | }
53 |
54 | if (buffer_in_bytes == bytes_needed)
55 | {
56 | continue;
57 | }
58 |
59 | handles.resize(bytes_needed / sizeof(decltype(handles)::value_type));
60 |
61 | std::vector pids;
62 | std::transform(std::cbegin(handles), std::cend(handles), std::back_inserter(pids),
63 | [](const DWORD handle) { return static_cast(handle); });
64 | return pids;
65 | }
66 |
67 | qFatal("Failed get a list of running processes after 3 tries!");
68 | return {};
69 | }
70 |
71 | QString NativeProcessHandler::getExecPath(uint pid) const
72 | {
73 | return useProcHandle(
74 | pid,
75 | [pid](HANDLE handle)
76 | {
77 | static_assert(sizeof(wchar_t) == sizeof(char16_t), "Wide char is not 2 bytes :/");
78 |
79 | if (!handle)
80 | {
81 | return QString{};
82 | }
83 |
84 | DWORD data_written{MAX_PATH};
85 | std::array buffer{};
86 |
87 | const BOOL result = QueryFullProcessImageNameW(handle, 0,
88 | // NOLINTNEXTLINE(*-reinterpret-cast)
89 | reinterpret_cast(buffer.data()), &data_written);
90 | if (result == TRUE)
91 | {
92 | if (data_written > 0)
93 | {
94 | auto path{QString::fromUtf16(buffer.data(), data_written)};
95 | qDebug(lc::osVerbose) << "QueryFullProcessImageNameW - PID:" << pid << "| Path:" << path;
96 | return path;
97 | }
98 | else
99 | {
100 | qDebug(lc::osVerbose) << "QueryFullProcessImageNameW failed to write data - PID:" << pid;
101 | }
102 | }
103 | else
104 | {
105 | qDebug(lc::osVerbose) << "QueryFullProcessImageNameW failed - PID:" << pid
106 | << "| ERROR:" << lc::getErrorString(GetLastError());
107 | }
108 |
109 | return QString{};
110 | });
111 | }
112 |
113 | QDateTime NativeProcessHandler::getStartTime(uint pid) const
114 | {
115 | return useProcHandle(pid,
116 | [pid](HANDLE handle)
117 | {
118 | static_assert(sizeof(wchar_t) == sizeof(char16_t), "Wide char is not 2 bytes :/");
119 |
120 | if (!handle)
121 | {
122 | return QDateTime{};
123 | }
124 |
125 | FILETIME start_time{};
126 | FILETIME exit_time{};
127 | FILETIME kernel_time{};
128 | FILETIME user_time{};
129 | BOOL result = GetProcessTimes(handle, &start_time, &exit_time, &kernel_time, &user_time);
130 |
131 | if (result == TRUE)
132 | {
133 | SYSTEMTIME time_utc{};
134 | result = FileTimeToSystemTime(&start_time, &time_utc);
135 | if (result == TRUE)
136 | {
137 | QDateTime datetime{QDate{time_utc.wYear, time_utc.wMonth, time_utc.wDay},
138 | QTime{time_utc.wHour, time_utc.wMinute, time_utc.wSecond,
139 | time_utc.wMilliseconds},
140 | QTimeZone::UTC};
141 | datetime = datetime.toLocalTime();
142 | qDebug(lc::osVerbose)
143 | << "FileTimeToSystemTime PID:" << pid << "| Time:" << datetime;
144 | return datetime;
145 | }
146 |
147 | qDebug(lc::osVerbose) << "FileTimeToSystemTime failed - PID:" << pid
148 | << "| ERROR:" << lc::getErrorString(GetLastError());
149 | }
150 | else
151 | {
152 | qDebug(lc::osVerbose) << "GetProcessTimes failed - PID:" << pid
153 | << "| ERROR:" << lc::getErrorString(GetLastError());
154 | }
155 |
156 | return QDateTime{};
157 | });
158 | }
159 |
160 | void NativeProcessHandler::close(uint pid) const
161 | {
162 | std::vector hwnds;
163 | {
164 | HWND hwnd{nullptr};
165 | do
166 | {
167 | DWORD owner_pid{0};
168 |
169 | hwnd = FindWindowExW(nullptr, hwnd, nullptr, nullptr);
170 | GetWindowThreadProcessId(hwnd, &owner_pid);
171 |
172 | if (static_cast(pid) == owner_pid)
173 | {
174 | hwnds.push_back(hwnd);
175 | }
176 | } while (hwnd != nullptr);
177 | }
178 |
179 | for (const auto& hwnd : hwnds)
180 | {
181 | if (PostMessageW(hwnd, WM_CLOSE, 0, 0) == FALSE)
182 | {
183 | qCDebug(lc::os).nospace() << "Failed to post message to process (pid: " << pid
184 | << ")! Reason: " << lc::getErrorString(GetLastError());
185 | }
186 | }
187 | }
188 |
189 | void NativeProcessHandler::terminate(uint pid) const
190 | {
191 | HANDLE proc_handle = OpenProcess(PROCESS_TERMINATE, FALSE, static_cast(pid));
192 | auto cleanup = qScopeGuard(
193 | [&]()
194 | {
195 | if (proc_handle != nullptr)
196 | {
197 | CloseHandle(proc_handle);
198 | }
199 | });
200 |
201 | if (proc_handle == nullptr || TerminateProcess(proc_handle, 1) == FALSE)
202 | {
203 | qCWarning(lc::os).nospace() << "Failed to terminate process (pid: " << pid
204 | << ")! Reason: " << lc::getErrorString(GetLastError());
205 | }
206 | }
207 | } // namespace os
208 |
--------------------------------------------------------------------------------
/src/lib/os/win/nativesleepinhibitor.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "os/win/nativesleepinhibitor.h"
3 |
4 | namespace os
5 | {
6 | NativeSleepInhibitor::NativeSleepInhibitor(const QString& /* app_name */)
7 | {
8 | }
9 | } // namespace os
10 |
--------------------------------------------------------------------------------
/src/lib/server/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | #----------------------------------------------------------------------------------------------------------------------
2 | # Lib config
3 | #----------------------------------------------------------------------------------------------------------------------
4 |
5 | set(LIBNAME serverlib)
6 |
7 | #----------------------------------------------------------------------------------------------------------------------
8 | # External dependencies
9 | #----------------------------------------------------------------------------------------------------------------------
10 |
11 | find_package(Qt6 COMPONENTS Core HttpServer REQUIRED)
12 | qt_standard_project_setup()
13 |
14 | #----------------------------------------------------------------------------------------------------------------------
15 | # Header/Source files
16 | #----------------------------------------------------------------------------------------------------------------------
17 |
18 | file(GLOB HEADERS CONFIGURE_DEPENDS "include/server/*.h")
19 | file(GLOB SOURCES CONFIGURE_DEPENDS "*.cpp")
20 |
21 | #----------------------------------------------------------------------------------------------------------------------
22 | # Target config
23 | #----------------------------------------------------------------------------------------------------------------------
24 |
25 | add_library(${LIBNAME} ${HEADERS} ${SOURCES})
26 | target_link_libraries(${LIBNAME} PRIVATE Qt6::Core Qt6::HttpServer sharedlib)
27 | target_include_directories(${LIBNAME} PUBLIC include)
28 |
--------------------------------------------------------------------------------
/src/lib/server/clientids.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "server/clientids.h"
3 |
4 | // system/Qt includes
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | // local includes
12 | #include "shared/loggingcategories.h"
13 |
14 | namespace server
15 | {
16 | ClientIds::ClientIds(QString filepath)
17 | : m_filepath{std::move(filepath)}
18 | {
19 | }
20 |
21 | // NOLINTNEXTLINE(*-cognitive-complexity)
22 | void ClientIds::load()
23 | {
24 | m_ids.clear(); // Clear the ids regardless of whether the file exists or not
25 |
26 | QFile ids_file{m_filepath};
27 | if (ids_file.exists())
28 | {
29 | if (!ids_file.open(QFile::ReadOnly))
30 | {
31 | qFatal("File exists, but could not be opened: \"%s\"", qUtf8Printable(m_filepath));
32 | }
33 |
34 | const QByteArray data = ids_file.readAll();
35 |
36 | QJsonParseError parser_error;
37 | const QJsonDocument json_data{QJsonDocument::fromJson(data, &parser_error)};
38 | if (json_data.isNull())
39 | {
40 | qFatal("Failed to decode JSON data! Reason: %s. Read data: %s", qUtf8Printable(parser_error.errorString()),
41 | qUtf8Printable(data));
42 | }
43 | else if (!json_data.isEmpty())
44 | {
45 | if (!json_data.isArray())
46 | {
47 | qFatal("Client Ids file contains invalid JSON data!");
48 | }
49 |
50 | bool some_ids_were_skipped{false};
51 | const QJsonArray ids = json_data.array();
52 | for (const auto& client_id : ids)
53 | {
54 | const QString client_id_string{client_id.isString() ? client_id.toString() : QString{}};
55 | if (client_id_string.isEmpty())
56 | {
57 | some_ids_were_skipped = true;
58 | continue;
59 | }
60 |
61 | m_ids.emplace(client_id_string);
62 | }
63 |
64 | if (some_ids_were_skipped)
65 | {
66 | qCWarning(lc::server) << "Client Ids file contained ids that were skipped!";
67 | }
68 | }
69 | }
70 | }
71 |
72 | void ClientIds::save()
73 | {
74 | QJsonArray json_data;
75 | for (const auto& client_id : m_ids)
76 | {
77 | json_data.append(client_id);
78 | }
79 |
80 | QFile file{m_filepath};
81 | if (!file.exists())
82 | {
83 | const QFileInfo info(m_filepath);
84 | const QDir dir;
85 | if (!dir.mkpath(info.absolutePath()))
86 | {
87 | qFatal("Failed at mkpath: \"%s\".", qUtf8Printable(m_filepath));
88 | }
89 | }
90 |
91 | if (!file.open(QFile::WriteOnly))
92 | {
93 | qFatal("File could not be opened for writing: \"%s\".", qUtf8Printable(m_filepath));
94 | }
95 |
96 | const QJsonDocument file_data{json_data};
97 | file.write(file_data.toJson(QJsonDocument::Indented));
98 | qCInfo(lc::server) << "Finished saving:" << m_filepath;
99 | }
100 |
101 | bool ClientIds::containsId(const QString& client_id) const
102 | {
103 | return m_ids.contains(client_id);
104 | }
105 |
106 | void ClientIds::addId(const QString& client_id)
107 | {
108 | m_ids.emplace(client_id);
109 | }
110 |
111 | void ClientIds::removeId(const QString& client_id)
112 | {
113 | m_ids.erase(client_id);
114 | }
115 | } // namespace server
--------------------------------------------------------------------------------
/src/lib/server/httpserver.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "server/httpserver.h"
3 |
4 | // system/Qt includes
5 | #include
6 | #include
7 | #include
8 |
9 | // local includes
10 | #include "server/clientids.h"
11 | #include "shared/loggingcategories.h"
12 |
13 | namespace server
14 | {
15 | QString HttpServer::getAuthorizationId(const QHttpServerRequest& request)
16 | {
17 | auto auth = request.value("authorization").simplified();
18 |
19 | const int id_start_index{6};
20 | if (auth.size() > id_start_index && auth.first(id_start_index).toLower() == "basic ")
21 | {
22 | auto token = auth.sliced(id_start_index);
23 | auto client_id = QByteArray::fromBase64(token);
24 |
25 | if (!client_id.isEmpty())
26 | {
27 | return client_id;
28 | }
29 | }
30 |
31 | return {};
32 | }
33 |
34 | HttpServer::HttpServer(int api_version, ClientIds& client_ids)
35 | : m_api_version{api_version}
36 | , m_client_ids{client_ids}
37 | {
38 | Q_UNUSED(m_client_ids)
39 | }
40 |
41 | bool HttpServer::startServer(quint16 port, const QString& ssl_cert_file, const QString& ssl_key_file,
42 | QSsl::SslProtocol protocol)
43 | {
44 | auto ssl_server = std::make_unique();
45 | {
46 | QFile cert_file{ssl_cert_file};
47 | if (!cert_file.open(QFile::ReadOnly))
48 | {
49 | qCWarning(lc::server) << "Failed to load SSL certificate from" << ssl_cert_file;
50 | return false;
51 | }
52 |
53 | QFile key_file{ssl_key_file};
54 | if (!key_file.open(QFile::ReadOnly))
55 | {
56 | qCWarning(lc::server) << "Failed to load SSL key from" << ssl_key_file;
57 | return false;
58 | }
59 |
60 | QSslConfiguration ssl_conf{QSslConfiguration::defaultConfiguration()};
61 | ssl_conf.setLocalCertificate(QSslCertificate{cert_file.readAll()});
62 | ssl_conf.setPrivateKey(QSslKey{key_file.readAll(), QSsl::Rsa});
63 | ssl_conf.setProtocol(protocol);
64 |
65 | ssl_server->setSslConfiguration(ssl_conf);
66 | }
67 |
68 | if (!ssl_server->listen(QHostAddress::Any, port))
69 | {
70 | qCWarning(lc::server) << "Server could not start listening at port" << port;
71 | return false;
72 | }
73 |
74 | if (!m_server.bind(ssl_server.get()))
75 | {
76 | qCWarning(lc::server) << "Failed to bind the ssl server!";
77 | return false;
78 | }
79 | ssl_server.release(); // m_server has taken over the ownership!
80 |
81 | qCInfo(lc::server) << "Server started listening at port" << port;
82 | return true;
83 | }
84 |
85 | int HttpServer::getApiVersion() const
86 | {
87 | return m_api_version;
88 | }
89 |
90 | bool HttpServer::isAuthorized(const QHttpServerRequest& request) const
91 | {
92 | return m_client_ids.containsId(getAuthorizationId(request));
93 | }
94 | } // namespace server
--------------------------------------------------------------------------------
/src/lib/server/include/server/clientids.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 | #include
6 |
7 | namespace server
8 | {
9 | class ClientIds
10 | {
11 | Q_DISABLE_COPY(ClientIds)
12 |
13 | public:
14 | explicit ClientIds(QString filepath);
15 | virtual ~ClientIds() = default;
16 |
17 | void load();
18 | void save();
19 |
20 | bool containsId(const QString& client_id) const;
21 | void addId(const QString& client_id);
22 | void removeId(const QString& client_id);
23 |
24 | private:
25 | QString m_filepath;
26 | std::set m_ids;
27 | };
28 | } // namespace server
29 |
--------------------------------------------------------------------------------
/src/lib/server/include/server/httpserver.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 |
6 | // forward declaration
7 | namespace server
8 | {
9 | class ClientIds;
10 | }
11 |
12 | namespace server
13 | {
14 | class HttpServer
15 | {
16 | Q_DISABLE_COPY(HttpServer)
17 |
18 | public:
19 | static QString getAuthorizationId(const QHttpServerRequest& request);
20 |
21 | explicit HttpServer(int api_version, ClientIds& client_ids);
22 | virtual ~HttpServer() = default;
23 |
24 | bool startServer(quint16 port, const QString& ssl_cert_file, const QString& ssl_key_file,
25 | QSsl::SslProtocol protocol);
26 |
27 | int getApiVersion() const;
28 | bool isAuthorized(const QHttpServerRequest& request) const;
29 |
30 | template
31 | bool route(const QString& path_pattern, QHttpServerRequest::Methods method, Functor&& functor);
32 |
33 | template
34 | void afterRequest(ViewHandler&& view_handler);
35 |
36 | private:
37 | int m_api_version;
38 | ClientIds& m_client_ids;
39 | QHttpServer m_server;
40 | };
41 |
42 | template
43 | bool HttpServer::route(const QString& path_pattern, QHttpServerRequest::Methods method, Functor&& functor)
44 | {
45 | static_assert(!std::is_member_function_pointer_v, "Member function pointer are not allowed!");
46 | return m_server.route(path_pattern, method, std::forward(functor));
47 | }
48 |
49 | template
50 | void HttpServer::afterRequest(ViewHandler&& view_handler)
51 | {
52 | return m_server.addAfterRequestHandler(&m_server, std::forward(view_handler));
53 | }
54 | } // namespace server
55 |
--------------------------------------------------------------------------------
/src/lib/server/include/server/pairingmanager.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 |
6 | // forward declaration
7 | namespace server
8 | {
9 | class ClientIds;
10 | }
11 |
12 | namespace server
13 | {
14 | class PairingManager : public QObject
15 | {
16 | Q_OBJECT
17 |
18 | public:
19 | explicit PairingManager(ClientIds& client_ids);
20 | ~PairingManager() override = default;
21 |
22 | bool isPaired(const QString& client_id) const;
23 | bool isPairing(const QString& client_id) const;
24 | bool isPairing() const;
25 |
26 | bool startPairing(const QString& id, const QString& hashed_id);
27 | bool abortPairing(const QString& id);
28 |
29 | signals:
30 | void signalRequestUserInputForPairing();
31 | void signalAbortPairing();
32 |
33 | public slots:
34 | void slotFinishPairing(uint pin);
35 | void slotPairingRejected();
36 |
37 | private:
38 | struct PairingData
39 | {
40 | QString m_id;
41 | QString m_hashed_id;
42 | };
43 |
44 | ClientIds& m_client_ids;
45 | std::optional m_pairing_data;
46 | };
47 | } // namespace server
48 |
--------------------------------------------------------------------------------
/src/lib/server/pairingmanager.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "server/pairingmanager.h"
3 |
4 | // local includes
5 | #include "server/clientids.h"
6 | #include "shared/loggingcategories.h"
7 |
8 | namespace server
9 | {
10 | PairingManager::PairingManager(ClientIds& client_ids)
11 | : m_client_ids{client_ids}
12 | {
13 | }
14 |
15 | bool PairingManager::isPaired(const QString& client_id) const
16 | {
17 | return m_client_ids.containsId(client_id);
18 | }
19 |
20 | bool PairingManager::isPairing(const QString& client_id) const
21 | {
22 | return m_pairing_data && m_pairing_data->m_id == client_id;
23 | }
24 |
25 | bool PairingManager::isPairing() const
26 | {
27 | return static_cast(m_pairing_data);
28 | }
29 |
30 | // NOLINTNEXTLINE(*-identifier-length)
31 | bool PairingManager::startPairing(const QString& id, const QString& hashed_id)
32 | {
33 | if (m_pairing_data)
34 | {
35 | qCWarning(lc::server) << "Cannot start pairing as" << m_pairing_data->m_id << "is currently being paired!";
36 | return false;
37 | }
38 |
39 | if (id.isEmpty() || hashed_id.isEmpty())
40 | {
41 | qCWarning(lc::server) << "Invalid id or hashed_id provided for pairing!";
42 | return false;
43 | }
44 |
45 | if (m_client_ids.containsId(id))
46 | {
47 | qCWarning(lc::server) << "Id" << id << "is already paired!";
48 | return false;
49 | }
50 |
51 | m_pairing_data = {id, hashed_id};
52 | emit signalRequestUserInputForPairing();
53 | return true;
54 | }
55 |
56 | // NOLINTNEXTLINE(*-identifier-length)
57 | bool PairingManager::abortPairing(const QString& id)
58 | {
59 | if (!isPairing())
60 | {
61 | return true;
62 | }
63 |
64 | if (!isPairing(id))
65 | {
66 | qCWarning(lc::server) << "Cannot abort pairing for other id than" << id;
67 | return false;
68 | }
69 |
70 | qCDebug(lc::server) << "Aborting pairing for" << id;
71 | emit signalAbortPairing();
72 | m_pairing_data = std::nullopt;
73 | return true;
74 | }
75 |
76 | void PairingManager::slotFinishPairing(uint pin)
77 | {
78 | if (!m_pairing_data)
79 | {
80 | qCWarning(lc::server) << "Pairing is not in progress!";
81 | return;
82 | }
83 |
84 | const QString string_for_hashing{m_pairing_data->m_id + QString::number(pin)};
85 | if (string_for_hashing.toUtf8().toBase64() != m_pairing_data->m_hashed_id)
86 | {
87 | qCWarning(lc::server) << "Pairing code does not match.";
88 | return;
89 | }
90 |
91 | m_client_ids.addId(m_pairing_data->m_id);
92 | m_client_ids.save();
93 |
94 | m_pairing_data = std::nullopt;
95 | }
96 |
97 | void PairingManager::slotPairingRejected()
98 | {
99 | qCDebug(lc::server) << "Pairing was rejected.";
100 | m_pairing_data = std::nullopt;
101 | }
102 |
103 | } // namespace server
104 |
--------------------------------------------------------------------------------
/src/lib/shared/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | #----------------------------------------------------------------------------------------------------------------------
2 | # Lib config
3 | #----------------------------------------------------------------------------------------------------------------------
4 |
5 | set(LIBNAME sharedlib)
6 |
7 | #----------------------------------------------------------------------------------------------------------------------
8 | # External dependencies
9 | #----------------------------------------------------------------------------------------------------------------------
10 |
11 | find_package(Qt6 COMPONENTS Core REQUIRED)
12 | qt_standard_project_setup()
13 |
14 | #----------------------------------------------------------------------------------------------------------------------
15 | # Header/Source files
16 | #----------------------------------------------------------------------------------------------------------------------
17 |
18 | file(GLOB HEADERS CONFIGURE_DEPENDS "include/shared/*.h")
19 | file(GLOB SOURCES CONFIGURE_DEPENDS "*.cpp")
20 |
21 | #----------------------------------------------------------------------------------------------------------------------
22 | # Target config
23 | #----------------------------------------------------------------------------------------------------------------------
24 |
25 | add_library(${LIBNAME} ${HEADERS} ${SOURCES})
26 | target_link_libraries(${LIBNAME} PRIVATE Qt6::Core)
27 | target_include_directories(${LIBNAME} PUBLIC include)
28 |
--------------------------------------------------------------------------------
/src/lib/shared/appmetadata.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "shared/appmetadata.h"
3 |
4 | // system/Qt includes
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | // local includes
12 | #include "shared/loggingcategories.h"
13 |
14 | namespace
15 | {
16 | #if defined(Q_OS_LINUX)
17 | QString getConfigDir()
18 | {
19 | const auto xdg_config_env = qgetenv("XDG_CONFIG_HOME");
20 | if (!xdg_config_env.isEmpty())
21 | {
22 | const QDir xdg_cnnfig_dir{xdg_config_env};
23 | if (xdg_cnnfig_dir.exists())
24 | {
25 | return xdg_cnnfig_dir.absolutePath();
26 | }
27 | }
28 |
29 | return QDir::cleanPath(QDir::homePath() + "/.config");
30 | }
31 |
32 | QString getAppFilePath()
33 | {
34 | const auto app_image_env = qgetenv("APPIMAGE");
35 | if (!app_image_env.isEmpty())
36 | {
37 | return QString{app_image_env};
38 | }
39 |
40 | return QCoreApplication::applicationFilePath();
41 | }
42 | #endif
43 | } // namespace
44 |
45 | namespace shared
46 | {
47 | AppMetadata::AppMetadata(App app)
48 | : m_current_app{app}
49 | {
50 | // Delay logging until application start
51 | QTimer::singleShot(0, this,
52 | [this]()
53 | {
54 | qCDebug(lc::shared) << "getAppName() >> " << getAppName();
55 | qCDebug(lc::shared) << "getLogDir() >> " << getLogDir();
56 | qCDebug(lc::shared) << "getLogName() >> " << getLogName();
57 | qCDebug(lc::shared) << "getLogPath() >> " << getLogPath();
58 | qCDebug(lc::shared) << "getSettingsDir() >> " << getSettingsDir();
59 | qCDebug(lc::shared) << "getSettingsName() >> " << getSettingsName();
60 | qCDebug(lc::shared) << "getSettingsPath() >> " << getSettingsPath();
61 | qCDebug(lc::shared) << "getAutoStartDir() >> " << getAutoStartDir();
62 | qCDebug(lc::shared) << "getAutoStartPath() >> " << getAutoStartPath();
63 | qCDebug(lc::shared) << "getAutoStartExec() >> " << getAutoStartExec();
64 | });
65 | }
66 |
67 | QString AppMetadata::getAppName() const
68 | {
69 | return getAppName(m_current_app);
70 | }
71 |
72 | // NOLINTNEXTLINE(*-static)
73 | QString AppMetadata::getAppName(App app) const
74 | {
75 | switch (app)
76 | {
77 | case App::Buddy:
78 | return QStringLiteral("MoonDeckBuddy");
79 | case App::Stream:
80 | return QStringLiteral("MoonDeckStream");
81 | }
82 |
83 | Q_ASSERT(false);
84 | return {};
85 | }
86 |
87 | // NOLINTNEXTLINE(*-static)
88 | QString AppMetadata::getLogDir() const
89 | {
90 | #if defined(Q_OS_WIN)
91 | Q_ASSERT(QCoreApplication::instance() != nullptr);
92 | return QCoreApplication::applicationDirPath();
93 | #elif defined(Q_OS_LINUX)
94 | return QStringLiteral("/tmp");
95 | #else
96 | #error OS is not supported!
97 | #endif
98 | }
99 |
100 | QString AppMetadata::getLogName() const
101 | {
102 | return getAppName().toLower() + QStringLiteral(".log");
103 | }
104 |
105 | QString AppMetadata::getLogPath() const
106 | {
107 | return QDir::cleanPath(getLogDir() + "/" + getLogName());
108 | }
109 |
110 | // NOLINTNEXTLINE(*-static)
111 | QString AppMetadata::getSettingsDir() const
112 | {
113 | #if defined(Q_OS_WIN)
114 | Q_ASSERT(QCoreApplication::instance() != nullptr);
115 | return QCoreApplication::applicationDirPath();
116 | #elif defined(Q_OS_LINUX)
117 | return QDir::cleanPath(getConfigDir() + "/" + getAppName(App::Buddy).toLower());
118 | #else
119 | #error OS is not supported!
120 | #endif
121 | }
122 |
123 | // NOLINTNEXTLINE(*-static)
124 | QString AppMetadata::getSettingsName() const
125 | {
126 | return QStringLiteral("settings.json");
127 | }
128 |
129 | QString AppMetadata::getSettingsPath() const
130 | {
131 | return QDir::cleanPath(getSettingsDir() + "/" + getSettingsName());
132 | }
133 |
134 | // NOLINTNEXTLINE(*-static)
135 | QString AppMetadata::getAutoStartDir() const
136 | {
137 | #if defined(Q_OS_WIN)
138 | Q_ASSERT(QCoreApplication::instance() != nullptr);
139 | return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + "/Startup");
140 | #elif defined(Q_OS_LINUX)
141 | return QDir::cleanPath(getConfigDir() + "/autostart");
142 | #else
143 | #error OS is not supported!
144 | #endif
145 | }
146 |
147 | QString AppMetadata::getAutoStartName() const
148 | {
149 | #if defined(Q_OS_WIN)
150 | return getAppName() + ".lnk";
151 | #elif defined(Q_OS_LINUX)
152 | return getAppName().toLower() + ".desktop";
153 | #else
154 | #error OS is not supported!
155 | #endif
156 | }
157 |
158 | QString AppMetadata::getAutoStartPath() const
159 | {
160 | return QDir::cleanPath(getAutoStartDir() + "/" + getAutoStartName());
161 | }
162 |
163 | // NOLINTNEXTLINE(*-static)
164 | QString AppMetadata::getAutoStartExec() const
165 | {
166 | Q_ASSERT(QCoreApplication::instance() != nullptr);
167 | #if defined(Q_OS_WIN)
168 | return QCoreApplication::applicationFilePath();
169 | #elif defined(Q_OS_LINUX)
170 | return getAppFilePath();
171 | #else
172 | #error OS is not supported!
173 | #endif
174 | }
175 |
176 | QString AppMetadata::getDefaultSteamExecutable() const
177 | {
178 | #if defined(Q_OS_WIN)
179 | const QSettings settings(R"(HKEY_CURRENT_USER\Software\Valve\Steam)", QSettings::NativeFormat);
180 | return settings.value("SteamExe").toString();
181 | #elif defined(Q_OS_LINUX)
182 | return "/usr/bin/steam";
183 | #else
184 | #error OS is not supported!
185 | #endif
186 | }
187 | } // namespace shared
--------------------------------------------------------------------------------
/src/lib/shared/include/shared/appmetadata.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 |
6 | namespace shared
7 | {
8 | class AppMetadata final : public QObject
9 | {
10 | public:
11 | enum class App
12 | {
13 | Buddy,
14 | Stream
15 | };
16 |
17 | explicit AppMetadata(App app);
18 | ~AppMetadata() override = default;
19 |
20 | QString getAppName() const;
21 | QString getAppName(App app) const;
22 |
23 | QString getLogDir() const;
24 | QString getLogName() const;
25 | QString getLogPath() const;
26 |
27 | QString getSettingsDir() const;
28 | QString getSettingsName() const;
29 | QString getSettingsPath() const;
30 |
31 | QString getAutoStartDir() const;
32 | QString getAutoStartName() const;
33 | QString getAutoStartPath() const;
34 | QString getAutoStartExec() const;
35 |
36 | QString getDefaultSteamExecutable() const;
37 |
38 | private:
39 | App m_current_app;
40 | };
41 | } // namespace shared
--------------------------------------------------------------------------------
/src/lib/shared/include/shared/enums.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 |
6 | namespace enums
7 | {
8 | Q_NAMESPACE
9 |
10 | enum class SteamUiMode
11 | {
12 | Unknown,
13 | Desktop,
14 | BigPicture
15 | };
16 | Q_ENUM_NS(SteamUiMode)
17 |
18 | enum class AppState
19 | {
20 | Stopped,
21 | Running,
22 | Updating
23 | };
24 | Q_ENUM_NS(AppState)
25 |
26 | enum class PcState
27 | {
28 | Normal,
29 | Restarting,
30 | ShuttingDown,
31 | Suspending
32 | };
33 | Q_ENUM_NS(PcState)
34 |
35 | enum class StreamState
36 | {
37 | NotStreaming,
38 | Streaming,
39 | StreamEnding
40 | };
41 | Q_ENUM_NS(StreamState)
42 | } // namespace enums
--------------------------------------------------------------------------------
/src/lib/shared/include/shared/loggingcategories.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 | #include
6 | #include
7 |
8 | //! LC - logging categories
9 | namespace lc
10 | {
11 | template
12 | QString qEnumToString(const T value)
13 | {
14 | return QMetaEnum::fromType().valueToKey(static_cast(value));
15 | }
16 |
17 | QString getErrorString(auto&& error)
18 | {
19 | return QString::fromStdString(std::system_category().message(static_cast(error)));
20 | }
21 |
22 | Q_DECLARE_LOGGING_CATEGORY(buddyMain);
23 | Q_DECLARE_LOGGING_CATEGORY(streamMain);
24 | Q_DECLARE_LOGGING_CATEGORY(server);
25 | Q_DECLARE_LOGGING_CATEGORY(shared);
26 | Q_DECLARE_LOGGING_CATEGORY(utils);
27 | Q_DECLARE_LOGGING_CATEGORY(os);
28 | Q_DECLARE_LOGGING_CATEGORY(osVerbose);
29 | } // namespace lc
--------------------------------------------------------------------------------
/src/lib/shared/loggingcategories.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "shared/loggingcategories.h"
3 |
4 | namespace lc
5 | {
6 | Q_LOGGING_CATEGORY(buddyMain, "buddy.main", QtInfoMsg);
7 | Q_LOGGING_CATEGORY(streamMain, "buddy.stream", QtInfoMsg);
8 | Q_LOGGING_CATEGORY(server, "buddy.server", QtInfoMsg);
9 | Q_LOGGING_CATEGORY(shared, "buddy.shared", QtInfoMsg);
10 | Q_LOGGING_CATEGORY(utils, "buddy.utils", QtInfoMsg);
11 | Q_LOGGING_CATEGORY(os, "buddy.os", QtInfoMsg);
12 | Q_LOGGING_CATEGORY(osVerbose, "buddy.os.verbose", QtInfoMsg);
13 | } // namespace lc
--------------------------------------------------------------------------------
/src/lib/utils/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | #----------------------------------------------------------------------------------------------------------------------
2 | # Lib config
3 | #----------------------------------------------------------------------------------------------------------------------
4 |
5 | set(LIBNAME utilslib)
6 |
7 | #----------------------------------------------------------------------------------------------------------------------
8 | # External dependencies
9 | #----------------------------------------------------------------------------------------------------------------------
10 |
11 | find_package(Qt6 REQUIRED COMPONENTS Core Widgets)
12 | qt_standard_project_setup()
13 |
14 | #----------------------------------------------------------------------------------------------------------------------
15 | # Header/Source files
16 | #----------------------------------------------------------------------------------------------------------------------
17 |
18 | file(GLOB HEADERS CONFIGURE_DEPENDS "include/utils/*.h")
19 | file(GLOB SOURCES CONFIGURE_DEPENDS "*.cpp")
20 |
21 | #----------------------------------------------------------------------------------------------------------------------
22 | # Target config
23 | #----------------------------------------------------------------------------------------------------------------------
24 |
25 | add_library(${LIBNAME} ${HEADERS} ${SOURCES})
26 | target_link_libraries(${LIBNAME} PRIVATE Qt6::Core Qt6::Widgets sharedlib)
27 | target_include_directories(${LIBNAME} PUBLIC include)
28 |
--------------------------------------------------------------------------------
/src/lib/utils/heartbeat.cpp:
--------------------------------------------------------------------------------
1 | // header file include
2 | #include "utils/heartbeat.h"
3 |
4 | // system/Qt includes
5 | #include
6 | #include
7 | #include
8 |
9 | namespace
10 | {
11 | QString generateKeyHash(const QString& key, const QString& salt)
12 | {
13 | QByteArray data;
14 |
15 | data.append(key.toUtf8());
16 | data.append(salt.toUtf8());
17 | data = QCryptographicHash::hash(data, QCryptographicHash::Sha1).toHex();
18 |
19 | return data;
20 | }
21 |
22 | constexpr int TIME_INDEX{0};
23 | constexpr int TERMINATE_INDEX{1};
24 | constexpr int HEARTBEAT_INTERVAL{250};
25 | constexpr int HEARTBEAT_TIMEOUT{2000};
26 |
27 | class HeartbeatAccessor final
28 | {
29 | Q_DISABLE_COPY(HeartbeatAccessor)
30 |
31 | public:
32 | explicit HeartbeatAccessor(QSharedMemory& shared_mem);
33 | ~HeartbeatAccessor();
34 |
35 | void setTime(const QDateTime& time);
36 | QDateTime getTime() const;
37 |
38 | void setShouldTerminate(bool terminate);
39 | bool getShouldTerminate() const;
40 |
41 | private:
42 | QSharedMemory& m_shared_mem;
43 | };
44 |
45 | HeartbeatAccessor::HeartbeatAccessor(QSharedMemory& shared_mem)
46 | : m_shared_mem{shared_mem}
47 | {
48 | if (!m_shared_mem.lock())
49 | {
50 | qFatal("Failed to lock shared memory %s", qUtf8Printable(m_shared_mem.key()));
51 | }
52 | }
53 |
54 | HeartbeatAccessor::~HeartbeatAccessor()
55 | {
56 | if (!m_shared_mem.unlock())
57 | {
58 | qFatal("Failed to unlock shared memory %s", qUtf8Printable(m_shared_mem.key()));
59 | }
60 | }
61 |
62 | void HeartbeatAccessor::setTime(const QDateTime& time)
63 | {
64 | const qint64 time_ms{time.toMSecsSinceEpoch()};
65 | // NOLINTNEXTLINE(*-reinterpret-cast)
66 | qint64* mem_ptr{reinterpret_cast(m_shared_mem.data())};
67 | // NOLINTNEXTLINE(*-pointer-arithmetic)
68 | mem_ptr[TIME_INDEX] = time_ms;
69 | }
70 |
71 | QDateTime HeartbeatAccessor::getTime() const
72 | {
73 | // NOLINTNEXTLINE(*-reinterpret-cast)
74 | const qint64* mem_ptr{reinterpret_cast(m_shared_mem.data())};
75 | // NOLINTNEXTLINE(*-pointer-arithmetic)
76 | return QDateTime::fromMSecsSinceEpoch(mem_ptr[TIME_INDEX], QTimeZone::UTC);
77 | }
78 |
79 | void HeartbeatAccessor::setShouldTerminate(bool terminate)
80 | {
81 | // NOLINTNEXTLINE(*-reinterpret-cast)
82 | qint64* mem_ptr{reinterpret_cast(m_shared_mem.data())};
83 | // NOLINTNEXTLINE(*-pointer-arithmetic)
84 | mem_ptr[TERMINATE_INDEX] = static_cast(terminate);
85 | }
86 |
87 | bool HeartbeatAccessor::getShouldTerminate() const
88 | {
89 | // NOLINTNEXTLINE(*-reinterpret-cast)
90 | const qint64* mem_ptr{reinterpret_cast(m_shared_mem.data())};
91 | // NOLINTNEXTLINE(*-pointer-arithmetic)
92 | return static_cast(mem_ptr[TERMINATE_INDEX]);
93 | }
94 | } // namespace
95 |
96 | namespace utils
97 | {
98 | Heartbeat::Heartbeat(const QString& key)
99 | : m_shared_mem(generateKeyHash(key, "_heartbeat_key"))
100 | {
101 | m_timer.setInterval(HEARTBEAT_INTERVAL);
102 | m_timer.setSingleShot(true);
103 |
104 | // On UNIX the shared memory segment will survive a crash, so we have to make sure to clean it up in such cases.
105 | const auto try_create_shared_mem = [&]()
106 | {
107 | constexpr qsizetype size{sizeof(qint64) * 2};
108 | auto result{m_shared_mem.create(size)};
109 | #if defined(Q_OS_LINUX)
110 | if (!result)
111 | {
112 | m_shared_mem.attach();
113 | m_shared_mem.detach();
114 | result = m_shared_mem.create(size);
115 | }
116 | #endif
117 | return result;
118 | };
119 |
120 | if (try_create_shared_mem())
121 | {
122 | HeartbeatAccessor memory{m_shared_mem};
123 | memory.setShouldTerminate(false);
124 | memory.setTime(QDateTime::currentDateTimeUtc().addDays(-1));
125 | return;
126 | }
127 |
128 | if (m_shared_mem.error() != QSharedMemory::AlreadyExists)
129 | {
130 | qFatal("Failed to create shared memory for %s (%s). Reason: %s", qUtf8Printable(key),
131 | qUtf8Printable(m_shared_mem.key()), qUtf8Printable(m_shared_mem.errorString()));
132 | }
133 |
134 | if (!m_shared_mem.attach())
135 | {
136 | qFatal("Failed to attach to shared memory %s (%s). Reason: %s", qUtf8Printable(key),
137 | qUtf8Printable(m_shared_mem.key()), qUtf8Printable(m_shared_mem.errorString()));
138 | }
139 | }
140 |
141 | Heartbeat::~Heartbeat()
142 | {
143 | if (m_is_beating)
144 | {
145 | HeartbeatAccessor memory{m_shared_mem};
146 | // Set the final time so that the other process can quickly determine that the heartbeat is gone
147 | memory.setTime(QDateTime::currentDateTimeUtc().addMSecs(-HEARTBEAT_TIMEOUT).addMSecs(HEARTBEAT_INTERVAL));
148 | }
149 | }
150 |
151 | void Heartbeat::startBeating()
152 | {
153 | if (m_is_listening)
154 | {
155 | qFatal("You cannot start heartbeating if you're a listener!");
156 | }
157 |
158 | if (!m_is_beating)
159 | {
160 | m_is_beating = true;
161 | connect(&m_timer, &QTimer::timeout, this, [this]() { slotBeating(false); });
162 | slotBeating(true);
163 | }
164 | }
165 |
166 | void Heartbeat::startListening()
167 | {
168 | if (m_is_beating)
169 | {
170 | qFatal("You cannot start listening if you're the heartbeat!");
171 | }
172 |
173 | if (!m_is_listening)
174 | {
175 | m_is_listening = true;
176 | connect(&m_timer, &QTimer::timeout, this, &Heartbeat::slotListening);
177 | slotListening();
178 | }
179 | }
180 |
181 | void Heartbeat::terminate()
182 | {
183 | HeartbeatAccessor memory{m_shared_mem};
184 | memory.setShouldTerminate(true);
185 | }
186 |
187 | bool Heartbeat::isAlive() const
188 | {
189 | return m_is_beating || m_is_alive;
190 | }
191 |
192 | void Heartbeat::slotBeating(bool fresh_start)
193 | {
194 | m_timer.stop();
195 |
196 | HeartbeatAccessor memory{m_shared_mem};
197 | if (!fresh_start && memory.getShouldTerminate())
198 | {
199 | emit signalShouldTerminate();
200 | return;
201 | }
202 |
203 | memory.setShouldTerminate(false);
204 | memory.setTime(QDateTime::currentDateTimeUtc());
205 |
206 | m_timer.start();
207 | }
208 |
209 | void Heartbeat::slotListening()
210 | {
211 | m_timer.stop();
212 |
213 | const HeartbeatAccessor memory{m_shared_mem};
214 | const QDateTime last_beat{memory.getTime()};
215 | const bool is_alive{last_beat.msecsTo(QDateTime::currentDateTimeUtc()) <= HEARTBEAT_TIMEOUT};
216 |
217 | if (is_alive != m_is_alive)
218 | {
219 | m_is_alive = is_alive;
220 | emit signalStateChanged();
221 | }
222 |
223 | m_timer.start();
224 | }
225 | } // namespace utils
226 |
--------------------------------------------------------------------------------
/src/lib/utils/include/utils/appsettings.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 | #include
6 | #include
7 |
8 | // local includes
9 | #include "shared/appmetadata.h"
10 |
11 | namespace utils
12 | {
13 | class AppSettings
14 | {
15 | Q_DISABLE_COPY(AppSettings)
16 |
17 | public:
18 | explicit AppSettings(const shared::AppMetadata& app_metadata);
19 | virtual ~AppSettings() = default;
20 |
21 | const shared::AppMetadata& getAppMetadata() const;
22 | quint16 getPort() const;
23 | const QString& getLoggingRules() const;
24 | const QString& getSunshineAppsFilepath() const;
25 | bool getPreferHibernation() const;
26 | QSsl::SslProtocol getSslProtocol() const;
27 | bool getCloseSteamBeforeSleep() const;
28 | QString getSteamExecutablePath() const;
29 | const QString& getMacAddressOverride() const;
30 |
31 | private:
32 | bool parseSettingsFile(const QString& filepath);
33 | void saveDefaultFile(const QString& filepath) const;
34 |
35 | const shared::AppMetadata& m_app_metadata;
36 | quint16 m_port;
37 | QString m_logging_rules;
38 | QString m_sunshine_apps_filepath;
39 | bool m_prefer_hibernation;
40 | QSsl::SslProtocol m_ssl_protocol;
41 | bool m_close_steam_before_sleep;
42 | QString m_steam_exec_override;
43 | QString m_mac_address_override;
44 | };
45 | } // namespace utils
46 |
--------------------------------------------------------------------------------
/src/lib/utils/include/utils/heartbeat.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 | #include
6 |
7 | namespace utils
8 | {
9 | class Heartbeat final : public QObject
10 | {
11 | Q_OBJECT
12 | Q_DISABLE_COPY(Heartbeat)
13 |
14 | public:
15 | explicit Heartbeat(const QString& key);
16 | ~Heartbeat() override;
17 |
18 | void startBeating();
19 | void startListening();
20 |
21 | void terminate();
22 | bool isAlive() const;
23 |
24 | signals:
25 | void signalShouldTerminate();
26 | void signalStateChanged();
27 |
28 | private slots:
29 | void slotBeating(bool fresh_start);
30 | void slotListening();
31 |
32 | private:
33 | QSharedMemory m_shared_mem;
34 | QTimer m_timer;
35 | bool m_is_beating{false};
36 | bool m_is_listening{false};
37 | bool m_is_alive{false};
38 | };
39 | } // namespace utils
40 |
--------------------------------------------------------------------------------
/src/lib/utils/include/utils/jsonvalueconverter.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // system/Qt includes
4 | #include
5 | #include
6 | #include
7 |
8 | namespace utils
9 | {
10 |
11 | template
12 | struct JsonValueConverter;
13 |
14 | template
15 | requires std::is_enum_v
16 | struct JsonValueConverter
17 | {
18 | static std::optional convert(const QJsonValue& json)
19 | {
20 | if (!json.isString())
21 | {
22 | return std::nullopt;
23 | }
24 |
25 | bool success{false};
26 | auto&& metaEnum = QMetaEnum::fromType();
27 | const int enum_value{metaEnum.keyToValue(json.toString().toUtf8(), &success)};
28 | if (!success)
29 | {
30 | return std::nullopt;
31 | }
32 |
33 | return static_cast(enum_value);
34 | }
35 | };
36 |
37 | template<>
38 | struct JsonValueConverter
39 | {
40 | static std::optional convert(const QJsonValue& json)
41 | {
42 | return json.isString() ? std::make_optional(json.toString()) : std::nullopt;
43 | }
44 | };
45 |
46 | template<>
47 | struct JsonValueConverter
48 | {
49 | static std::optional convert(const QJsonValue& json)
50 | {
51 | return json.isBool() ? std::make_optional(json.toBool()) : std::nullopt;
52 | }
53 | };
54 |
55 | template<>
56 | struct JsonValueConverter
57 | {
58 | static std::optional convert(const QJsonValue& json, int min = std::numeric_limits::lowest(),
59 | int max = std::numeric_limits::max())
60 | {
61 | if (json.isDouble())
62 | {
63 | const int number{json.toInt()};
64 | if (number >= min && number <= max)
65 | {
66 | return number;
67 | }
68 | }
69 |
70 | return std::nullopt;
71 | }
72 | };
73 |
74 | template<>
75 | struct JsonValueConverter
76 | {
77 | static std::optional convert(const QJsonValue& json, uint min = 0,
78 | uint max = static_cast(std::numeric_limits::max()))
79 | {
80 | if (min <= static_cast(std::numeric_limits::max())
81 | && max <= static_cast(std::numeric_limits::max()))
82 | {
83 | const auto int_number{JsonValueConverter::convert(json, static_cast