pax_global_header00006660000000000000000000000064151332010600014501gustar00rootroot0000000000000052 comment=daa5694998ad21b7be3117e99dcb049588bcc935 budgie-desktop-services/000077500000000000000000000000001513320106000155745ustar00rootroot00000000000000budgie-desktop-services/.clang-format000066400000000000000000000146241513320106000201560ustar00rootroot00000000000000--- Language: Cpp # BasedOnStyle: Chromium AccessModifierOffset: -1 AlignAfterOpenBracket: AlwaysBreak AlignArrayOfStructures: None AlignConsecutiveAssignments: Enabled: true AcrossEmptyLines: false AcrossComments: false AlignCompound: false PadOperators: true AlignConsecutiveBitFields: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCompound: false PadOperators: false AlignConsecutiveDeclarations: Enabled: true AcrossEmptyLines: false AcrossComments: false AlignCompound: false PadOperators: false AlignConsecutiveMacros: Enabled: true AcrossEmptyLines: false AcrossComments: false AlignCompound: false PadOperators: false AlignEscapedNewlines: Left AlignOperands: Align AlignTrailingComments: Kind: Always OverEmptyLines: 0 AllowAllArgumentsOnNextLine: false AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: Always AllowShortCaseLabelsOnASingleLine: false AllowShortEnumsOnASingleLine: false AllowShortFunctionsOnASingleLine: Inline AllowShortIfStatementsOnASingleLine: WithoutElse AllowShortLambdasOnASingleLine: All AllowShortLoopsOnASingleLine: true AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: Yes AttributeMacros: - __capability BinPackArguments: true BinPackParameters: false BitFieldColonSpacing: Both BraceWrapping: AfterCaseLabel: false AfterClass: false AfterControlStatement: Never AfterEnum: false AfterExternBlock: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false BeforeCatch: false BeforeElse: false BeforeLambdaBody: false BeforeWhile: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakAfterAttributes: Never BreakAfterJavaFieldAnnotations: false BreakArrays: true BreakBeforeBinaryOperators: None BreakBeforeConceptDeclarations: Always BreakBeforeBraces: Attach BreakBeforeInlineASMColon: OnlyMultiline BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeColon BreakInheritanceList: BeforeColon BreakStringLiterals: true ColumnLimit: 160 CommentPragmas: "^ IWYU pragma:" CompactNamespaces: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DisableFormat: false EmptyLineAfterAccessModifier: Never EmptyLineBeforeAccessModifier: LogicalBlock ExperimentalAutoDetectBinPacking: false FixNamespaceComments: false ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IfMacros: - KJ_IF_MAYBE IncludeBlocks: Regroup IncludeCategories: - Regex: '^' Priority: 2 SortPriority: 0 CaseSensitive: false - Regex: '^<.*\.h>' Priority: 1 SortPriority: 0 CaseSensitive: false - Regex: "^<.*" Priority: 2 SortPriority: 0 CaseSensitive: false - Regex: ".*" Priority: 3 SortPriority: 0 CaseSensitive: false IncludeIsMainRegex: "([-_](test|unittest))?$" IncludeIsMainSourceRegex: "" IndentAccessModifiers: true IndentCaseBlocks: false IndentCaseLabels: true IndentExternBlock: AfterExternBlock IndentGotoLabels: true IndentPPDirectives: None IndentRequiresClause: true IndentWidth: 2 IndentWrappedFunctionNames: false InsertBraces: false InsertNewlineAtEOF: true InsertTrailingCommas: None IntegerLiteralSeparator: Binary: 0 BinaryMinDigits: 0 Decimal: 0 DecimalMinDigits: 0 Hex: 0 HexMinDigits: 0 JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false LambdaBodyIndentation: Signature LineEnding: LF MacroBlockBegin: "" MacroBlockEnd: "" MaxEmptyLinesToKeep: 1 NamespaceIndentation: All ObjCBinPackProtocolList: Never ObjCBlockIndentWidth: 2 ObjCBreakBeforeNestedBlockParam: true ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PackConstructorInitializers: NextLine PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 1 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakOpenParenthesis: 0 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyIndentedWhitespace: 0 PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Left PPIndentWidth: -1 QualifierAlignment: Leave RawStringFormats: - Language: Cpp Delimiters: - cc - CC - cpp - Cpp - CPP - "c++" - "C++" CanonicalDelimiter: "" BasedOnStyle: google - Language: TextProto Delimiters: - pb - PB - proto - PROTO EnclosingFunctions: - EqualsProto - EquivToProto - PARSE_PARTIAL_TEXT_PROTO - PARSE_TEST_PROTO - PARSE_TEXT_PROTO - ParseTextOrDie - ParseTextProtoOrDie - ParseTestProto - ParsePartialTestProto CanonicalDelimiter: pb BasedOnStyle: google ReferenceAlignment: Pointer ReflowComments: true RemoveBracesLLVM: false RemoveSemicolon: false RequiresClausePosition: OwnLine RequiresExpressionIndentation: OuterScope SeparateDefinitionBlocks: Leave ShortNamespaceLines: 1 SortIncludes: CaseSensitive SortJavaStaticImport: Before SortUsingDeclarations: LexicographicNumeric SpaceAfterCStyleCast: true SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceAroundPointerQualifiers: Default SpaceBeforeAssignmentOperators: true SpaceBeforeCaseColon: false SpaceBeforeCpp11BracedList: true SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeParensOptions: AfterControlStatements: true AfterForeachMacros: true AfterFunctionDefinitionName: false AfterFunctionDeclarationName: false AfterIfMacros: true AfterOverloadedOperator: false AfterRequiresInClause: false AfterRequiresInExpression: false BeforeNonEmptyParentheses: false SpaceBeforeRangeBasedForLoopColon: true SpaceBeforeSquareBrackets: false SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 SpacesInAngles: Never SpacesInConditionalStatement: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInLineCommentPrefix: Minimum: 1 Maximum: -1 SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Auto StatementAttributeLikeMacros: - Q_EMIT StatementMacros: - Q_UNUSED - QT_REQUIRE_VERSION TabWidth: 8 UseTab: Never WhitespaceSensitiveMacros: - BOOST_PP_STRINGIZE - CF_SWIFT_NAME - NS_SWIFT_NAME - PP_STRINGIZE - STRINGIZE --- budgie-desktop-services/.clangd000066400000000000000000000000421513320106000170210ustar00rootroot00000000000000CompileFlags: Add: [-std=c++20] budgie-desktop-services/.github/000077500000000000000000000000001513320106000171345ustar00rootroot00000000000000budgie-desktop-services/.github/README.md000066400000000000000000000011251513320106000204120ustar00rootroot00000000000000# Budgie Desktop Services Budgie Desktop Services is the future central hub and orchestrator for Budgie Desktop (with a focus on Budgie 11). Today, it primarily provides Wayland-native display configuration for Budgie 10.10; over time it will coordinate broader desktop logic for Budgie 11. ## Mirror Only For the full README, please see our codebase on [the Modern Desktop Initiative forge](https://forge.moderndesktop.dev/BuddiesOfBudgie/budgie-desktop-services). This repository is a **mirror-only**, thus any issues, pull requests, or other matters filed against repository will be ignored.budgie-desktop-services/.gitignore000066400000000000000000000000721513320106000175630ustar00rootroot00000000000000.cache .idea .qmlls.ini .zed cmake-build-debug build main budgie-desktop-services/.vscode/000077500000000000000000000000001513320106000171355ustar00rootroot00000000000000budgie-desktop-services/.vscode/launch.json000066400000000000000000000012521513320106000213020ustar00rootroot00000000000000{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "lldb", "request": "launch", "name": "Services", "program": "${workspaceFolder}/build/bin/org.buddiesofbudgie.Services", "args": [], "cwd": "${workspaceFolder}", "env": { "QT_LOGGING_RULES": "*.debug=true" }, "preLaunchTask": "build", "reverseDebugging": true } ] }budgie-desktop-services/.vscode/settings.json000066400000000000000000000006451513320106000216750ustar00rootroot00000000000000{ "files.associations": { "*.bu": "yaml", "*.css": "tailwindcss", "optional": "cpp", "charconv": "cpp", "ranges": "cpp", "ratio": "cpp", "array": "cpp", "functional": "cpp", "tuple": "cpp", "type_traits": "cpp", "utility": "cpp", "compare": "cpp", "cstdint": "cpp", "string_view": "cpp", "format": "cpp", "initializer_list": "cpp", "span": "cpp" } }budgie-desktop-services/.vscode/tasks.json000066400000000000000000000006621513320106000211610ustar00rootroot00000000000000{ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "label": "build", "type": "shell", "command": "go-task build", "dependsOn": "setup" }, { "label": "setup", "type": "shell", "command": "go-task setup" } ] }budgie-desktop-services/.woodpecker/000077500000000000000000000000001513320106000200145ustar00rootroot00000000000000budgie-desktop-services/.woodpecker/build.yaml000066400000000000000000000005221513320106000217760ustar00rootroot00000000000000when: - event: push branch: main - event: manual branch: main - event: pull_request branch: main steps: - name: Compilation Test image: quay.io/buddiesofbudgie/ci-optimized-images:budgie-desktop-services commands: - cmake -DCMAKE_INSTALL_PREFIX=/usr -S . -B build -G Ninja - cmake --build build -v budgie-desktop-services/AGENTS.md000066400000000000000000000112641513320106000171030ustar00rootroot00000000000000### AGENTS: Contributor Guide for Budgie Daemon This document is a quick, action-oriented guide for agents and contributors working on Budgie Daemon. It summarizes common workflows (especially around D-Bus schema changes and code generation), build tasks, and conventions. See `README.md` for full background, build prerequisites, and project scope. ### Project quick facts - Core purpose: Budgie Desktop Services is the future central hub and orchestrator for Budgie Desktop (with a focus on Budgie 11). Today, it primarily provides Wayland-native display configuration for Budgie 10.10; over time it will coordinate broader desktop logic for Budgie 11. - Wayland protocol: `wlr-output-management-unstable-v1`. - DBus API: XML schemas in `src/dbus/schemas/` → generated adaptors in `src/dbus/generated/`. - Output model: meta heads/modes exposed via services under `org.buddiesofbudgie.Services`. ### Command cheatsheet (Taskfile) - Task is used as a helpful task runner (single binary, no deps) Invoke tasks with the `task` command. See the Taskfile docs [here](https://taskfile.dev/). - Configure + build: - `task setup` - `task build` - Or together: `task cook` - Install (autostart assets, systemd user unit depending on CMake options): - `sudo task install` - Format code: - `task fmt` - Wayland debug (run built binary under `wldbg`): - `task wldbg-build` ### Typical D-Bus workflow 1) Edit or add a schema in `src/dbus/schemas/*.xml`. - Define interface, properties, methods, signals. - For QVariantMap outputs, include the Qt DBus type annotation: - `` 2) Rebuild using `task build` as we generate adaptor code via cmake + qdbusgen 3) Implement the service methods/properties: - Add declarations to the corresponding header in `src/dbus/*.hpp` under `public slots` or as properties. - Implement logic in `src/dbus/*.cpp`. - Prefer using `State::instance().getManager()->getHeads()` to enumerate outputs. - Primary selection: when asked to resolve a "primary" output, prefer heads where `isPrimary()` is true; fall back to the first available head. 4) Register new objects if needed: - See `DisplayObjectManager.cpp` and `main.cpp` for service and object registration patterns. 5) Build and test: - `task cook` - Optionally run from build tree: `./build/bin/org.buddiesofbudgie.Services` ### Example: Adding primary-output helpers (pattern) - Schema changes (Display service examples): - Add `GetPrimaryOutput` returning a string serial/identifier. - Add `GetPrimaryOutputRect` returning a QVariantMap with keys: `X`, `Y`, `Width`, `Height`. - Regenerate adaptors: `task build`. - Implement methods in `dbus/OutputsService.hpp/.cpp`: - Use `State` → `WaylandOutputManager` → heads. - Choose primary via `head->isPrimary()`; otherwise first head. - For geometry, use `head->getPosition()` and its current mode size (`getCurrentMode()->getSize()`), mirroring the QVariantMap style used by `OutputModeService::GetModeInfo()`. ### Code and API conventions - C++ style: Prefer clear, descriptive names. Keep methods short and guard conditions early. - D-Bus types: - Use QString, QStringList, bool, int, qulonglong, QVariantMap as appropriate. - Add `org.qtproject.QtDBus.QtTypeName.*` annotations when returning complex Qt types. - Generated code: - Never modify `src/dbus/generated/*` by hand. Regenerate from XML. ### Git and commit message conventions We strive to follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for commit messages. - Common types we use: `feat`, `fix`, `docs`, `chore`, `ci`. - Breaking changes: append `!` after type/scope (e.g., `feat(api)!:`) or include a `BREAKING CHANGE:` footer. - Examples: ``` feat(displays): add GetPrimaryOutput and GetPrimaryOutputRect methods docs(readme): document Conventional Commits usage fix(displays): correct refresh rate conversion to qulonglong ``` ### Configuration and profiles (summary) - On Wayland ready, a display profile group is selected and applied atomically via the batch system. - Users can store profiles at `$XDG_CONFIG_HOME/budgie-desktop/display-config.toml` (or `~/.config/...`). - Group schema supports `primary_output`, anchors, relative positioning, scale, rotation, adaptive sync, and enable/disable flags. ### Troubleshooting - Missing or stale D-Bus methods: - Ensure the XML schema is updated and `task qdbus-gen` has been run. - Rebuild (`task build`) and restart the daemon. - Wayland issues: - Use `task wldbg-build` (sets `QT_LOGGING_RULES=*.debug=true` and runs under `wldbg` to trace Wayland events). - Formatting/lints: - `task fmt` ### License - MPL-2.0 (see `COPYING`). budgie-desktop-services/CMakeLists.txt000066400000000000000000000032271513320106000203400ustar00rootroot00000000000000# SPDX-FileCopyrightText: Budgie Desktop Developers # # SPDX-License-Identifier: MPL-2.0 cmake_minimum_required(VERSION 3.20) project(budgie-desktop-services VERSION 1.0.2) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_WARNINGS_IGNORE "deprecated-literal-operators") set(KDE_COMPILERSETTINGS_LEVEL 6.0) set(PROJECT_DEP_VERSION "6.1.80") set(QT_MIN_VERSION "6.7") set(KF6_MIN_VERSION "6.6.0") find_package(ECM ${KF6_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH}) include(KDEClangFormat) include(KDEGitCommitHooks) include(FeatureSummary) include(ECMSetupVersion) include(KDEInstallDirs) include(KDECMakeSettings) # include(KDECompilerSettings NO_POLICY_SCOPE) include(GenerateExportHeader) include(ECMGenerateHeaders) include(ECMConfiguredInstall) find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core DBus WaylandClient) set_package_properties( Qt6 PROPERTIES TYPE REQUIRED PURPOSE "Basic application components") find_package(Wayland REQUIRED) find_package(QtWaylandScanner REQUIRED) find_package(KWayland REQUIRED) find_package(toml11 REQUIRED) option(INSTALL_DESKTOP_FILE "Install desktop file" OFF) option(INSTALL_SERVICE_FILES "Install service files for autostarting" OFF) option(INSTALL_LABWC "Install autostart files for labwc" OFF) add_subdirectory(src) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) #file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.hpp) #kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) #kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT) budgie-desktop-services/COPYING000066400000000000000000000405261513320106000166360ustar00rootroot00000000000000Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. budgie-desktop-services/README.md000066400000000000000000000106541513320106000170610ustar00rootroot00000000000000# Budgie Desktop Services Budgie Desktop Services is the future central hub and orchestrator for Budgie Desktop (with a focus on Budgie 11). Today, it primarily provides Wayland-native display configuration for Budgie 10.10; over time it will coordinate broader desktop logic for Budgie 11. ### Highlights - **Wayland-native**: Uses `wlr-output-management-unstable-v1` to discover and configure outputs - **Display Configuration Batch System**: Build atomic display configuration changes and apply them safely in one go - **Profiles via TOML**: Persist and auto-apply display groups matched to current hardware - **Future scope**: Will evolve into Budgie 11's core orchestrator beyond displays ### TODO - [ ] Improve signal handling between meta objects and DBus signals - [ ] Literally everything else post Budgie 10.10 release, such as... - [ ] Implement group swapping - [ ] General code refactoring to pave way for more modules - [ ] Implement plugin architecture for display system so wlr support can be swapped for alternatives and open door to supporting more compositors than just those based on wlroots or supporting those protocols - [ ] Notification (non-graphical) support for 11 - [ ] Bluetooth - [ ] Power management - [ ] Surface / window tracking (possibly) ### Displays #### Configuration file On startup (after Wayland is ready), the daemon matches current outputs to a preferred `group` and applies the batch described by the entries in that group. If no configuration is present, one will be generated. Location: - `$XDG_CONFIG_HOME/budgie-desktop/display-config.toml`, or - `~/.config/budgie-desktop/display-config.toml` Schema (subset): ```toml [preferences] automatic_attach_outputs_relative_position = "right" # one of: left/right/above/below/none [[group]] name = "Laptop + Monitor" preferred = true identifiers = ["", ""] primary_output = "" [[group.output]] identifier = "" width = 1920 height = 1080 refresh = 60.0 relative_output = "" # optional horizontal_anchor = "left" # none/left/right/center vertical_anchor = "middle" # none/above/top/middle/bottom/below scale = 1.0 rotation = 0 adaptive_sync = false disabled = false [[group.output]] identifier = "" width = 2560 height = 1440 refresh = 144.0 scale = 1.0 rotation = 0 adaptive_sync = true disabled = false ``` ### Dependencies - Qt 6 (Core, DBus, WaylandClient) >= 6.7 - KDE Frameworks 6: KWayland >= 6.6 - Wayland, QtWaylandScanner - Extra CMake Modules (ECM) - CMake >= 3.20, Ninja - toml11 >= 4.4.0 Optional for development: - `task` (Taskfile runner), `watchman` (for `build-watch`), `wldbg` ### Build With plain CMake/Ninja: ```bash cmake -S . -B build -G Ninja cmake --build build ``` Install (autostart + optional systemd user unit depending on CMake options): ```bash sudo ninja install -C build ``` Using Taskfile: ```bash task cook # configure + build sudo task install ``` ### Run - After install, the binary is `org.buddiesofbudgie.Services` and is autostarted for the user session - For ad-hoc runs from the build tree: ```bash ./build/bin/org.buddiesofbudgie.Services ``` Wayland debugging (example from Taskfile): ```bash wldbg -r ./build/src/org.buddiesofbudgie.Services -g ``` ### Development notes - Generated D-Bus adaptors live in `src/dbus/generated/` and are produced from XML in `src/dbus/schemas/` - Regenerate with: ```bash task qdbus-gen ``` - Do not hand-edit generated files - Code style: run clang-format ```bash task fmt ``` ### Conventional Commits We strive to follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for commit messages. - Common types we use: `feat`, `fix`, `docs`, `chore`, `ci`. - Breaking changes: append `!` after type/scope (e.g., `feat(api)!:`) or include a `BREAKING CHANGE:` footer. - Examples: ``` feat(displays): add GetPrimaryOutput and GetPrimaryOutputRect methods docs(readme): document Conventional Commits usage fix(displays): correct refresh rate conversion to qulonglong ``` ### Status and compatibility - Project version: `0.0.1` (early). Some features are intentionally stubbed/not yet implemented (e.g., primary output selection, full mirroring semantics). - Compatible with Budgie 10.10 and 11 on Wayland compositors that implement `wlr-output-management-unstable-v1`. ### License MPL-2.0. See `COPYING`. budgie-desktop-services/Taskfile.yml000066400000000000000000000017151513320106000200650ustar00rootroot00000000000000version: "3" tasks: fmt: desc: "Run clang-format" cmds: - clang-format -i src/**/*.cpp src/**/*.hpp setup: desc: "Run cmake configuration" cmds: - cmake -DCMAKE_INSTALL_PREFIX=/usr -S . -B build -G Ninja build: desc: "Run cmake build" cmds: - cmake --build build -v build-watch: desc: "Run cmake build on file change using watchman" cmds: - watchman-make -p '**/*.cpp' '**/*.h' --run "task cook" cook: desc: "Run cmake setup and build" cmds: - task: setup - task: build install: desc: "Run ninja install" cmds: - sudo ninja install -C build fmt: aliases: [clang-format, format] desc: "Run clang-format" dir: src cmds: - clang-format -i main.cpp **/*.{cpp,hpp} wldbg-build: desc: "Run wldbg (wayland-debug) against build" env: QT_LOGGING_RULES: "*.debug=true" cmds: - wldbg -r ./build/bin/org.buddiesofbudgie.Services -g budgie-desktop-services/cmake/000077500000000000000000000000001513320106000166545ustar00rootroot00000000000000budgie-desktop-services/cmake/ECMFindModuleHelpersStub.cmake000066400000000000000000000000751513320106000244140ustar00rootroot00000000000000include("/usr/share/ECM/modules//ECMFindModuleHelpers.cmake")budgie-desktop-services/cmake/FindWayland.cmake000066400000000000000000000114211513320106000220550ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2014 Alex Merry # SPDX-FileCopyrightText: 2014 Martin Gräßlin # # SPDX-License-Identifier: BSD-3-Clause #[=======================================================================[.rst: FindWayland ----------- Try to find Wayland. This is a component-based find module, which makes use of the COMPONENTS and OPTIONAL_COMPONENTS arguments to find_module. The following components are available:: Client Server Cursor Egl If no components are specified, this module will act as though all components were passed to OPTIONAL_COMPONENTS. This module will define the following variables, independently of the components searched for or found: ``Wayland_FOUND`` TRUE if (the requested version of) Wayland is available ``Wayland_VERSION`` Found Wayland version ``Wayland_TARGETS`` A list of all targets imported by this module (note that there may be more than the components that were requested) ``Wayland_LIBRARIES`` This can be passed to target_link_libraries() instead of the imported targets ``Wayland_INCLUDE_DIRS`` This should be passed to target_include_directories() if the targets are not used for linking ``Wayland_DEFINITIONS`` This should be passed to target_compile_options() if the targets are not used for linking ``Wayland_DATADIR`` The core wayland protocols data directory Since 5.73.0 For each searched-for components, ``Wayland__FOUND`` will be set to TRUE if the corresponding Wayland library was found, and FALSE otherwise. If ``Wayland__FOUND`` is TRUE, the imported target ``Wayland::`` will be defined. This module will also attempt to determine ``Wayland_*_VERSION`` variables for each imported target, although ``Wayland_VERSION`` should normally be sufficient. In general we recommend using the imported targets, as they are easier to use and provide more control. Bear in mind, however, that if any target is in the link interface of an exported library, it must be made available by the package config file. Since pre-1.0.0. #]=======================================================================] include(${CMAKE_CURRENT_LIST_DIR}/ECMFindModuleHelpersStub.cmake) ecm_find_package_version_check(Wayland) set(Wayland_known_components Client Server Cursor Egl ) foreach(_comp ${Wayland_known_components}) string(TOLOWER "${_comp}" _lc_comp) set(Wayland_${_comp}_component_deps) set(Wayland_${_comp}_pkg_config "wayland-${_lc_comp}") set(Wayland_${_comp}_lib "wayland-${_lc_comp}") set(Wayland_${_comp}_header "wayland-${_lc_comp}.h") endforeach() set(Wayland_Egl_component_deps Client) ecm_find_package_parse_components(Wayland RESULT_VAR Wayland_components KNOWN_COMPONENTS ${Wayland_known_components} ) ecm_find_package_handle_library_components(Wayland COMPONENTS ${Wayland_components} ) # If pkg-config didn't provide us with version information, # try to extract it from wayland-version.h # (Note that the version from wayland-egl.pc will probably be # the Mesa version, rather than the Wayland version, but that # version will be ignored as we always find wayland-client.pc # first). if(NOT Wayland_VERSION) find_file(Wayland_VERSION_HEADER NAMES wayland-version.h HINTS ${Wayland_INCLUDE_DIRS} ) mark_as_advanced(Wayland_VERSION_HEADER) if(Wayland_VERSION_HEADER) file(READ ${Wayland_VERSION_HEADER} _wayland_version_header_contents) string(REGEX REPLACE "^.*[ \t]+WAYLAND_VERSION[ \t]+\"([0-9.]*)\".*$" "\\1" Wayland_VERSION "${_wayland_version_header_contents}" ) unset(_wayland_version_header_contents) endif() endif() find_package_handle_standard_args(Wayland FOUND_VAR Wayland_FOUND REQUIRED_VARS Wayland_LIBRARIES VERSION_VAR Wayland_VERSION HANDLE_COMPONENTS ) pkg_get_variable(Wayland_DATADIR wayland-scanner pkgdatadir) if (CMAKE_CROSSCOMPILING AND (NOT EXISTS "${Wayland_DATADIR}/wayland.xml")) # PKG_CONFIG_SYSROOT_DIR only applies to -I and -L flags, so pkg-config # does not prepend CMAKE_SYSROOT when cross-compiling unless you pass # --define-prefix explicitly. Therefore we have to manually do prepend # it here when cross-compiling. # See https://gitlab.kitware.com/cmake/cmake/-/issues/16647#note_844761 set(Wayland_DATADIR ${CMAKE_SYSROOT}${Wayland_DATADIR}) endif() if (NOT EXISTS "${Wayland_DATADIR}/wayland.xml") message(WARNING "Could not find wayland.xml in ${Wayland_DATADIR}") endif() include(FeatureSummary) set_package_properties(Wayland PROPERTIES URL "https://wayland.freedesktop.org/" DESCRIPTION "C library implementation of the Wayland protocol: a protocol for a compositor to talk to its clients" ) budgie-desktop-services/cmake/FindWaylandScanner.cmake000066400000000000000000000146651513320106000234040ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2012-2014 Pier Luigi Fiorini # # SPDX-License-Identifier: BSD-3-Clause #[=======================================================================[.rst: FindWaylandScanner ------------------ Try to find wayland-scanner. If the wayland-scanner executable is not in your PATH, you can provide an alternative name or full path location with the ``WaylandScanner_EXECUTABLE`` variable. This will define the following variables: ``WaylandScanner_FOUND`` True if wayland-scanner is available. ``WaylandScanner_EXECUTABLE`` The wayland-scanner executable. If ``WaylandScanner_FOUND`` is TRUE, it will also define the following imported target: ``Wayland::Scanner`` The wayland-scanner executable. This module provides the following functions to generate C protocol implementations: - ``ecm_add_wayland_client_protocol`` - ``ecm_add_wayland_server_protocol`` :: ecm_add_wayland_client_protocol( PROTOCOL BASENAME [PRIVATE_CODE]) ecm_add_wayland_client_protocol( PROTOCOL BASENAME [PRIVATE_CODE]) Generate Wayland client protocol files from ```` XML definition for the ```` interface and append those files to ```` or ````. ``PRIVATE_CODE`` instructs wayland-scanner to hide marshalling code from the compiled DSO for use in other DSOs. The default is to export this code. :: ecm_add_wayland_server_protocol( PROTOCOL BASENAME [PRIVATE_CODE]) ecm_add_wayland_server_protocol( PROTOCOL BASENAME [PRIVATE_CODE]) Generate Wayland server protocol files from ```` XML definition for the ```` interface and append those files to ```` or ````. ``PRIVATE_CODE`` instructs wayland-scanner to hide marshalling code from the compiled DSO for use in other DSOs. The default is to export this code. Since 1.4.0. #]=======================================================================] include(${CMAKE_CURRENT_LIST_DIR}/ECMFindModuleHelpersStub.cmake) ecm_find_package_version_check(WaylandScanner) # Find wayland-scanner find_program(WaylandScanner_EXECUTABLE NAMES wayland-scanner) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(WaylandScanner FOUND_VAR WaylandScanner_FOUND REQUIRED_VARS WaylandScanner_EXECUTABLE ) mark_as_advanced(WaylandScanner_EXECUTABLE) if(NOT TARGET Wayland::Scanner AND WaylandScanner_FOUND) add_executable(Wayland::Scanner IMPORTED) set_target_properties(Wayland::Scanner PROPERTIES IMPORTED_LOCATION "${WaylandScanner_EXECUTABLE}" ) endif() include(FeatureSummary) set_package_properties(WaylandScanner PROPERTIES URL "https://wayland.freedesktop.org/" DESCRIPTION "Executable that converts XML protocol files to C code" ) function(ecm_add_wayland_client_protocol target_or_sources_var) # Parse arguments set(options PRIVATE_CODE) set(oneValueArgs PROTOCOL BASENAME) cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "" ${ARGN}) if(ARGS_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Unknown keywords given to ecm_add_wayland_client_protocol(): \"${ARGS_UNPARSED_ARGUMENTS}\"") endif() get_filename_component(_infile ${ARGS_PROTOCOL} ABSOLUTE) set(_client_header "${CMAKE_CURRENT_BINARY_DIR}/wayland-${ARGS_BASENAME}-client-protocol.h") set(_code "${CMAKE_CURRENT_BINARY_DIR}/wayland-${ARGS_BASENAME}-protocol.c") if(ARGS_PRIVATE_CODE) set(_code_type private-code) else() set(_code_type public-code) endif() set_source_files_properties(${_client_header} GENERATED) set_source_files_properties(${_code} GENERATED) set_property(SOURCE ${_client_header} ${_code} PROPERTY SKIP_AUTOMOC ON) add_custom_command(OUTPUT "${_client_header}" COMMAND ${WaylandScanner_EXECUTABLE} client-header ${_infile} ${_client_header} DEPENDS ${_infile} VERBATIM) add_custom_command(OUTPUT "${_code}" COMMAND ${WaylandScanner_EXECUTABLE} ${_code_type} ${_infile} ${_code} DEPENDS ${_infile} ${_client_header} VERBATIM) if (TARGET ${target_or_sources_var}) target_sources(${target_or_sources_var} PRIVATE "${_client_header}" "${_code}") else() list(APPEND ${target_or_sources_var} "${_client_header}" "${_code}") set(${target_or_sources_var} ${${target_or_sources_var}} PARENT_SCOPE) endif() endfunction() function(ecm_add_wayland_server_protocol target_or_sources_var) # Parse arguments set(options PRIVATE_CODE) set(oneValueArgs PROTOCOL BASENAME) cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "" ${ARGN}) if(ARGS_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Unknown keywords given to ecm_add_wayland_server_protocol(): \"${ARGS_UNPARSED_ARGUMENTS}\"") endif() if(ARGS_PRIVATE_CODE) set(_private_code_option PRIVATE_CODE) endif() ecm_add_wayland_client_protocol(${target_or_sources_var} PROTOCOL ${ARGS_PROTOCOL} BASENAME ${ARGS_BASENAME} ${_private_code_option}) get_filename_component(_infile ${ARGS_PROTOCOL} ABSOLUTE) set(_server_header "${CMAKE_CURRENT_BINARY_DIR}/wayland-${ARGS_BASENAME}-server-protocol.h") set(_server_code "${CMAKE_CURRENT_BINARY_DIR}/wayland-${ARGS_BASENAME}-protocol.c") set_property(SOURCE ${_server_header} ${_server_code} PROPERTY SKIP_AUTOMOC ON) set_source_files_properties(${_server_header} GENERATED) add_custom_command(OUTPUT "${_server_header}" COMMAND ${WaylandScanner_EXECUTABLE} server-header ${_infile} ${_server_header} DEPENDS ${_infile} VERBATIM) if (TARGET ${target_or_sources_var}) target_sources(${target_or_sources_var} PRIVATE "${_server_header}") else() list(APPEND ${target_or_sources_var} "${_server_header}") set(${target_or_sources_var} ${${target_or_sources_var}} PARENT_SCOPE) endif() endfunction() budgie-desktop-services/src/000077500000000000000000000000001513320106000163635ustar00rootroot00000000000000budgie-desktop-services/src/CMakeLists.txt000066400000000000000000000101121513320106000211160ustar00rootroot00000000000000# SPDX-FileCopyrightText: Budgie Desktop Developers # # SPDX-License-Identifier: MPL-2.0 add_library(WaylandProtocols_xml OBJECT) set_property(TARGET WaylandProtocols_xml PROPERTY POSITION_INDEPENDENT_CODE ON) target_link_libraries(WaylandProtocols_xml PUBLIC Qt::Core Wayland::Client Plasma::KWaylandClient) set_target_properties(WaylandProtocols_xml PROPERTIES LINKER_LANGUAGE C) ecm_add_qtwayland_client_protocol( WaylandProtocols_xml PRIVATE_CODE PROTOCOL protocols/wlr-output-management-unstable-v1.xml BASENAME wlr-output-management-unstable-v1) set(budgie-desktop-services_SRCS # Config config/outputs/global_preferences.cpp config/outputs/global_preferences.hpp config/outputs/group.cpp config/outputs/group.hpp config/outputs/output.cpp config/outputs/output.hpp config/outputs/state.cpp config/outputs/state.hpp config/utils.cpp config/utils.hpp # DBus Services dbus/ConfigService.cpp dbus/ConfigService.hpp # Batch System outputs/config/enums/actiontype.hpp outputs/config/enums/anchors.hpp outputs/config/action.cpp outputs/config/action.hpp outputs/config/model.cpp outputs/config/model.hpp outputs/config/result.cpp outputs/config/result.hpp outputs/config/targetstate.cpp outputs/config/targetstate.hpp # Output outputs/types.cpp outputs/types.hpp # Output (wlroots-specific) outputs/wlr/configuration.cpp outputs/wlr/configuration.hpp outputs/wlr/configurationhead.cpp outputs/wlr/configurationhead.hpp outputs/wlr/enums.hpp outputs/wlr/head.cpp outputs/wlr/head.hpp outputs/wlr/metahead.cpp outputs/wlr/metahead.hpp outputs/wlr/metamode.cpp outputs/wlr/metamode.hpp outputs/wlr/mode.cpp outputs/wlr/mode.hpp outputs/wlr/outputmanager.cpp outputs/wlr/outputmanager.hpp # Output State outputs/state.cpp outputs/state.hpp # System sys/SysInfo.cpp sys/SysInfo.hpp ) qt_add_dbus_adaptor(budgie-desktop-services_SRCS dbus/schemas/DisplaySchema.Config.xml dbus/ConfigService.hpp bd::ConfigService) qt_add_dbus_adaptor(budgie-desktop-services_SRCS dbus/schemas/DisplaySchema.Outputs.xml outputs/state.hpp bd::Outputs::State) qt_add_dbus_adaptor(budgie-desktop-services_SRCS dbus/schemas/DisplaySchema.Output.xml outputs/wlr/metahead.hpp bd::Outputs::Wlr::MetaHead) qt_add_dbus_adaptor(budgie-desktop-services_SRCS dbus/schemas/DisplaySchema.OutputMode.xml outputs/wlr/metamode.hpp bd::Outputs::Wlr::MetaMode) add_library( budgie-desktop-services STATIC ${budgie-desktop-services_SRCS} ) add_executable(budgie-desktop-services-app main.cpp) target_include_directories( budgie-desktop-services-app PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/includes ${CMAKE_CURRENT_BINARY_DIR}/dbus ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(budgie-desktop-services-app PRIVATE budgie-desktop-services) target_include_directories( budgie-desktop-services PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/includes ${CMAKE_CURRENT_SOURCE_DIR}/config ${CMAKE_CURRENT_SOURCE_DIR}/dbus ${CMAKE_CURRENT_SOURCE_DIR}/displays ${CMAKE_CURRENT_SOURCE_DIR}/sys) target_link_libraries( budgie-desktop-services PUBLIC Qt::Core Qt::DBus Qt::WaylandClient toml11::toml11 Wayland::Client WaylandProtocols_xml) set_target_properties( budgie-desktop-services-app PROPERTIES OUTPUT_NAME "org.buddiesofbudgie.Services") install(TARGETS budgie-desktop-services-app ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES dbus/org.buddiesofbudgie.Services.conf DESTINATION ${CMAKE_INSTALL_DATADIR}/dbus-1/system.d) if(INSTALL_DESKTOP_FILE) ecm_install_configured_files( INPUT data/org.buddiesofbudgie.Services.desktop.in DESTINATION ${KDE_INSTALL_AUTOSTARTDIR} ) endif() if(INSTALL_LABWC) install(FILES data/labwc/autostart DESTINATION ${KDE_INSTALL_SYSCONFDIR}/labwc) endif() if(INSTALL_SERVICE_FILES) ecm_install_configured_files( INPUT data/org.buddiesofbudgie.Services.service.in DESTINATION ${KDE_INSTALL_SYSTEMDUSERUNITDIR}) endif() budgie-desktop-services/src/config/000077500000000000000000000000001513320106000176305ustar00rootroot00000000000000budgie-desktop-services/src/config/outputs/000077500000000000000000000000001513320106000213535ustar00rootroot00000000000000budgie-desktop-services/src/config/outputs/global_preferences.cpp000066400000000000000000000047351513320106000257110ustar00rootroot00000000000000#include "global_preferences.hpp" #include namespace bd::Config::Outputs { GlobalPreferences::GlobalPreferences(QObject* parent) : QObject(parent) , m_automaticAttachOutputsRelativePosition(GlobalPreferences::None) { } GlobalPreferences::DisplayRelativePosition GlobalPreferences::automaticAttachOutputsRelativePosition() const { return m_automaticAttachOutputsRelativePosition; } void GlobalPreferences::setAutomaticAttachOutputsRelativePosition(GlobalPreferences::DisplayRelativePosition position) { m_automaticAttachOutputsRelativePosition = position; } QString GlobalPreferences::toString(GlobalPreferences::DisplayRelativePosition value) { switch (value) { case GlobalPreferences::Left: return QStringLiteral("left"); case GlobalPreferences::Right: return QStringLiteral("right"); case GlobalPreferences::Above: return QStringLiteral("above"); case GlobalPreferences::Below: return QStringLiteral("below"); case GlobalPreferences::None: default: return QStringLiteral("none"); } } GlobalPreferences::DisplayRelativePosition GlobalPreferences::fromString(const QString& str) { QString lower = str.toLower(); if (lower == QLatin1String("left")) return GlobalPreferences::Left; if (lower == QLatin1String("right")) return GlobalPreferences::Right; if (lower == QLatin1String("above")) return GlobalPreferences::Above; if (lower == QLatin1String("below")) return GlobalPreferences::Below; if (lower == QLatin1String("none")) return GlobalPreferences::None; // Try QMetaEnum lookup for PascalCase (e.g., "Left", "Right") QMetaEnum metaEnum = QMetaEnum::fromType(); bool ok; int value = metaEnum.keyToValue(str.toLatin1().constData(), &ok); if (ok) { return static_cast(value); } return GlobalPreferences::None; } GlobalPreferences::DisplayRelativePosition GlobalPreferences::fromString(const std::string& str) { return fromString(QString::fromStdString(str)); } std::string GlobalPreferences::toStringStd(GlobalPreferences::DisplayRelativePosition value) { return toString(value).toStdString(); } } budgie-desktop-services/src/config/outputs/global_preferences.hpp000066400000000000000000000030451513320106000257070ustar00rootroot00000000000000#pragma once #include #include #include namespace bd::Config::Outputs { class GlobalPreferences : public QObject { Q_OBJECT public: enum DisplayRelativePosition { None = 0, Left, Right, Above, Below, }; Q_ENUM(DisplayRelativePosition) Q_PROPERTY(DisplayRelativePosition automaticAttachOutputsRelativePosition READ automaticAttachOutputsRelativePosition WRITE setAutomaticAttachOutputsRelativePosition) explicit GlobalPreferences(QObject* parent = nullptr); ~GlobalPreferences() = default; // Property getter DisplayRelativePosition automaticAttachOutputsRelativePosition() const; // Property setter void setAutomaticAttachOutputsRelativePosition(DisplayRelativePosition position); // Convert enum to string (lowercase for compatibility with config files) static QString toString(DisplayRelativePosition value); // Convert string to enum (handles both lowercase and PascalCase) static DisplayRelativePosition fromString(const QString& str); // Convert std::string to enum (for compatibility) static DisplayRelativePosition fromString(const std::string& str); // Convert enum to std::string (for compatibility) static std::string toStringStd(DisplayRelativePosition value); private: DisplayRelativePosition m_automaticAttachOutputsRelativePosition; }; } budgie-desktop-services/src/config/outputs/group.cpp000066400000000000000000000236341513320106000232230ustar00rootroot00000000000000#include "group.hpp" #include "outputs/config/model.hpp" #include "outputs/state.hpp" #include "sys/SysInfo.hpp" namespace bd::Config::Outputs { Group::Group(QObject* parent) : QObject(parent), m_name(""), m_stored_primary_output_identifier(""), m_preferred(false) {} Group::Group(const toml::value& v, QObject* parent) : QObject(parent) { m_name = QString::fromStdString(toml::find(v, "name")); QList output_identifiers; for (const auto& serial : toml::find_or>(v, "identifiers", {})) { output_identifiers.append(QString::fromStdString(serial)); } // Toplevel values m_stored_identifiers = output_identifiers; m_stored_primary_output_identifier = QString::fromStdString(toml::find(v, "primary_output")); m_preferred = toml::find_or(v, "preferred", false); // Iterate over the outputs and create the output configs auto outputs = toml::find_or>(v, "output", {}); if (outputs.empty()) return; for (const toml::value& output : outputs) { auto output_config = new Output(output); m_output_configs.append(QSharedPointer(output_config)); } } // Property getters QString Group::name() const { return this->m_name; } bool Group::preferred() const { return this->m_preferred; } QString Group::storedPrimaryOutputIdentifier() const { return this->m_stored_primary_output_identifier; } QStringList Group::storedIdentifiers() const { return this->m_stored_identifiers; } QList> Group::outputConfigs() const { return this->m_output_configs; } // Property setters void Group::setName(const QString& name) { this->m_name = name; emit nameChanged(name); } void Group::setPreferred(bool preferred) { this->m_preferred = preferred; emit preferredChanged(preferred); } void Group::setStoredPrimaryOutputIdentifier(const QString& storedPrimaryOutputIdentifier) { this->m_stored_primary_output_identifier = storedPrimaryOutputIdentifier; emit storedPrimaryOutputIdentifierChanged(storedPrimaryOutputIdentifier); } void Group::setStoredIdentifiers(QStringList storedIdentifiers) { this->m_stored_identifiers = storedIdentifiers; emit storedIdentifiersChanged(storedIdentifiers); } void Group::setOutputConfigs(const QList>& outputConfigs) { this->m_output_configs = outputConfigs; emit outputConfigsChanged(outputConfigs); } // Other methods void Group::apply() { if (this->m_output_configs.isEmpty()) { qWarning() << "No output configs to apply for group:" << this->m_name; return; } auto &orchestrator = bd::Outputs::State::instance(); auto manager = orchestrator.getManager(); if (manager.isNull()) { qWarning() << "WaylandOutputManager is not available"; return; } // Reset the batch system and prepare for new configuration auto& batchSystem = bd::Outputs::Config::Model::instance(); batchSystem.reset(); // Connect to batch system completion signals connect( &batchSystem, &bd::Outputs::Config::Model::configurationApplied, this, [this, &batchSystem](bool success) { if (success) { qDebug() << "Display configuration applied successfully via batch system"; } else { qWarning() << "Display configuration failed via batch system"; } // Disconnect to avoid duplicate signals on subsequent uses disconnect(&batchSystem, &bd::Outputs::Config::Model::configurationApplied, this, nullptr); }, Qt::SingleShotConnection ); for (const auto& output : this->m_output_configs) { auto identifier = output->identifier(); qDebug() << "Creating batch actions for output:" << identifier; if (output->disabled()) { // Create action to disable this output auto offAction = bd::Outputs::Config::Action::explicitOff(output->identifier()); batchSystem.addAction(offAction); qDebug() << " - Disable output action created"; continue; // Skip the rest of the loop for this output } else { // Create action to enable this output auto onAction = bd::Outputs::Config::Action::explicitOn(output->identifier()); batchSystem.addAction(onAction); qDebug() << " - Enable output action created"; } // Set mode (dimensions and refresh) auto modeAction = bd::Outputs::Config::Action::mode(output->identifier(), QSize(output->width(), output->height()), output->refresh()); batchSystem.addAction(modeAction); qDebug() << " - Set mode action created with mode:" << output->width() << "x" << output->height() << "@" << output->refresh() << "Hz"; // Set anchoring if specified; also update the meta head so defaults propagate if (!SysInfo::instance().isShimMode()) { auto relativeOutput = output->relativeOutput(); if (!relativeOutput.isEmpty()) { auto horizontalAnchor = output->horizontalAnchor(); auto verticalAnchor = output->verticalAnchor(); auto anchorAction = bd::Outputs::Config::Action::positionAnchor(output->identifier(), relativeOutput, horizontalAnchor, verticalAnchor); batchSystem.addAction(anchorAction); qDebug() << " - Set anchoring relative to:" << relativeOutput << "with horizontal anchor:" << bd::Outputs::Config::HorizontalAnchor::toString(horizontalAnchor) << "and vertical anchor:" << bd::Outputs::Config::VerticalAnchor::toString(verticalAnchor); // Update meta head anchoring auto head = manager->getOutputHead(output->identifier()); if (!head.isNull()) { head->setRelativeOutput(relativeOutput); head->setHorizontalAnchoring(horizontalAnchor); head->setVerticalAnchoring(verticalAnchor); } } else { // Clear meta head anchoring explicitly auto head = manager->getOutputHead(identifier); if (!head.isNull()) { head->setRelativeOutput(""); head->setHorizontalAnchoring(bd::Outputs::Config::HorizontalAnchor::None); head->setVerticalAnchoring(bd::Outputs::Config::VerticalAnchor::None); } qDebug() << " - No anchoring set"; } } else { auto absolutePosition = QPoint(output->x(), output->y()); auto absolutePositionAction = bd::Outputs::Config::Action::absolutePosition(identifier, absolutePosition); batchSystem.addAction(absolutePositionAction); qDebug() << " - Set absolute position:" << output->x() << "," << output->y(); } // Set scale auto scaleAction = bd::Outputs::Config::Action::scale(identifier, output->scale()); batchSystem.addAction(scaleAction); qDebug() << " - Set scale:" << output->scale(); // Set transform (rotation) auto transformAction = bd::Outputs::Config::Action::transform(identifier, output->transform()); batchSystem.addAction(transformAction); qDebug() << " - Set transform:" << output->transform(); // Set adaptive sync auto adaptiveSyncAction = bd::Outputs::Config::Action::adaptiveSync(identifier, output->adaptiveSync()); batchSystem.addAction(adaptiveSyncAction); qDebug() << " - Set adaptive sync:" << output->adaptiveSync(); } // Set primary output if specified auto primaryOutput = storedPrimaryOutputIdentifier(); if (!primaryOutput.isEmpty()) { qDebug() << "Primary output:" << primaryOutput; for (const auto& head : manager->getHeads()) { if (head.isNull()) continue; head->setPrimary(head->getIdentifier() == primaryOutput); } } // Calculate and apply the configuration // batchSystem.calculate(); batchSystem.apply(); } QSharedPointer Group::getOutputForIdentifier(const QString& identifier) { for (const auto& output : this->m_output_configs) { if (output->identifier() == identifier) { return output; } } return QSharedPointer(nullptr); } toml::ordered_value Group::toToml() { toml::ordered_value group_table(toml::ordered_table {}); group_table.as_table_fmt().fmt = toml::table_format::multiline; std::vector output_identifiers; for (const auto& identifier : m_stored_identifiers) { output_identifiers.push_back(identifier.toStdString()); } group_table["name"] = this->m_name.toStdString(); group_table["preferred"] = this->m_preferred; group_table["identifiers"] = output_identifiers; group_table["primary_output"] = this->m_stored_primary_output_identifier.toStdString(); toml::ordered_value outputs(toml::ordered_array {}); outputs.as_array_fmt().fmt = toml::array_format::array_of_tables; for (const auto& output : this->m_output_configs) { outputs.push_back(output->toToml()); } group_table.as_table().emplace_back("output", outputs); return group_table; } }budgie-desktop-services/src/config/outputs/group.hpp000066400000000000000000000052261513320106000232250ustar00rootroot00000000000000#pragma once #include #include #include #include "outputs/wlr/metahead.hpp" #include "output.hpp" namespace bd::Config::Outputs { class Group : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) Q_PROPERTY(bool preferred READ preferred WRITE setPreferred NOTIFY preferredChanged) // Stored identifiers Q_PROPERTY(QString storedPrimaryOutputIdentifier READ storedPrimaryOutputIdentifier WRITE setStoredPrimaryOutputIdentifier NOTIFY storedPrimaryOutputIdentifierChanged) Q_PROPERTY(QList storedIdentifiers READ storedIdentifiers WRITE setStoredIdentifiers NOTIFY storedIdentifiersChanged) // Internal config representations for stored identifiers Q_PROPERTY(QList> outputConfigs READ outputConfigs WRITE setOutputConfigs NOTIFY outputConfigsChanged) Q_SIGNALS: void nameChanged(const QString& name); void storedPrimaryOutputIdentifierChanged(const QString& storedPrimaryOutputIdentifier); void storedIdentifiersChanged(const QStringList& storedIdentifiers); void outputConfigsChanged(const QList>& outputConfigs); void preferredChanged(bool preferred); public: Group(QObject* parent = nullptr); Group(const toml::value& v, QObject* parent = nullptr); ~Group() = default; // Property getters QString name() const; QString storedPrimaryOutputIdentifier() const; QStringList storedIdentifiers() const; bool preferred() const; QList> outputConfigs() const; // Property setters void setName(const QString& name); void setPreferred(bool preferred); void setStoredPrimaryOutputIdentifier(const QString& storedPrimaryOutputIdentifier); void setStoredIdentifiers(QStringList storedIdentifiers); void setOutputConfigs(const QList>& outputConfigs); // Other methods void addMetaHead(QSharedPointer metaHead); void removeMetaHead(QSharedPointer metaHead); void setPrimaryMetaHead(QSharedPointer metaHead); void apply(); toml::ordered_value toToml(); private: QSharedPointer getOutputForIdentifier(const QString& identifier); QString m_name; bool m_preferred; QString m_stored_primary_output_identifier; QStringList m_stored_identifiers; QList> m_output_configs; }; }budgie-desktop-services/src/config/outputs/output.cpp000066400000000000000000000156511513320106000234270ustar00rootroot00000000000000#include "outputs/state.hpp" #include "sys/SysInfo.hpp" #include "output.hpp" namespace bd::Config::Outputs { Output::Output(QObject* parent) : QObject(parent), m_identifier(""), m_width(0), m_height(0), m_refresh(0), m_x(0), m_y(0), m_relativeOutput(""), m_horizontalAnchor(bd::Outputs::Config::HorizontalAnchor::Type::None), m_verticalAnchor(bd::Outputs::Config::VerticalAnchor::Type::None), m_transform(0), m_adaptiveSync(0), m_primary(false), m_scale(1.0), m_disabled(false) {} Output::Output(const toml::value& v, QObject* parent) : QObject(parent) { m_identifier = QString::fromStdString(toml::find(v, "identifier")); m_width = toml::find(v, "width"); m_height = toml::find(v, "height"); m_refresh = toml::find(v, "refresh"); m_x = toml::find_or(v, "x", 0); m_y = toml::find_or(v, "y", 0); m_relativeOutput = QString::fromStdString(toml::find_or(v, "relative_output", "")); m_horizontalAnchor = bd::Outputs::Config::HorizontalAnchor::fromString(toml::find_or(v, "horizontal_anchor", "none")); m_verticalAnchor = bd::Outputs::Config::VerticalAnchor::fromString(toml::find_or(v, "vertical_anchor", "none")); m_transform = toml::find_or(v, "rotation", 0); m_adaptiveSync = toml::find_or(v, "adaptive_sync", 0); m_primary = toml::find_or(v, "primary", false); m_disabled = toml::find_or(v, "disabled", false); m_scale = toml::find_or(v, "scale", 1.0); } // Property getters QSharedPointer Output::getMetaHead() { // TODO(JoshStrobl): Consider tweaking this in future with reference setting the head instead of getting it from the manager. auto man = bd::Outputs::State::instance().getManager(); if (!man) return nullptr; return man->getOutputHead(this->m_identifier); } QString Output::identifier() const { return this->m_identifier; } int Output::width() { return this->m_width; } int Output::height() { return this->m_height; } qulonglong Output::refresh() { return this->m_refresh; } int Output::x() { return this->m_x; } int Output::y() { return this->m_y; } QString Output::relativeOutput() { return this->m_relativeOutput; } bd::Outputs::Config::HorizontalAnchor::Type Output::horizontalAnchor() { return this->m_horizontalAnchor; } bd::Outputs::Config::VerticalAnchor::Type Output::verticalAnchor() { return this->m_verticalAnchor; } quint16 Output::transform() { return this->m_transform; } uint32_t Output::adaptiveSync() { return this->m_adaptiveSync; } bool Output::primary() { return this->m_primary; } bool Output::disabled() { return this->m_disabled; } qreal Output::scale() { return this->m_scale; } // Property setters void Output::setIdentifier(const QString& identifier) { this->m_identifier = identifier; emit identifierChanged(identifier); } void Output::setWidth(int width) { this->m_width = width; emit widthChanged(width); } void Output::setHeight(int height) { this->m_height = height; emit heightChanged(height); } void Output::setRefresh(qulonglong refresh) { this->m_refresh = refresh; emit refreshChanged(refresh); } void Output::setX(int x) { this->m_x = x; emit xChanged(x); } void Output::setY(int y) { this->m_y = y; emit yChanged(y); } void Output::setRelativeOutput(const QString& relativeOutput) { this->m_relativeOutput = relativeOutput; emit relativeOutputChanged(relativeOutput); } void Output::setHorizontalAnchor(bd::Outputs::Config::HorizontalAnchor::Type horizontalAnchor) { this->m_horizontalAnchor = horizontalAnchor; emit horizontalAnchorChanged(horizontalAnchor); } void Output::setVerticalAnchor(bd::Outputs::Config::VerticalAnchor::Type verticalAnchor) { this->m_verticalAnchor = verticalAnchor; emit verticalAnchorChanged(verticalAnchor); } void Output::setTransform(quint16 transform) { this->m_transform = transform; emit transformChanged(transform); } void Output::setAdaptiveSync(uint32_t adaptiveSync) { this->m_adaptiveSync = adaptiveSync; emit adaptiveSyncChanged(adaptiveSync); } void Output::setScale(qreal scale) { this->m_scale = scale; emit scaleChanged(scale); } void Output::setPrimary(bool primary) { this->m_primary = primary; emit primaryChanged(primary); } void Output::setDisabled(bool disabled) { this->m_disabled = disabled; emit disabledChanged(disabled); } // Other methods toml::ordered_value Output::toToml() { toml::ordered_value config_table(toml::ordered_table {}); config_table.as_table_fmt().fmt = toml::table_format::multiline; config_table["identifier"] = this->identifier().toStdString(); config_table["width"] = this->width(); config_table["height"] = this->height(); config_table["refresh"] = static_cast(this->refresh()); if (SysInfo::instance().isShimMode()) { config_table["x"] = this->x(); config_table["y"] = this->y(); } else { config_table["relative_output"] = this->relativeOutput().toStdString(); config_table["horizontal_anchor"] = bd::Outputs::Config::HorizontalAnchor::toStringStd(this->horizontalAnchor()); config_table["vertical_anchor"] = bd::Outputs::Config::VerticalAnchor::toStringStd(this->verticalAnchor()); } config_table["scale"] = this->scale(); config_table["rotation"] = this->transform(); config_table["adaptive_sync"] = this->adaptiveSync(); config_table["primary"] = this->primary(); config_table["disabled"] = this->disabled(); return config_table; } void Output::updateFromHead() { auto head = getMetaHead(); if (!head) return; this->setWidth(head->width()); this->setHeight(head->height()); this->setRefresh(head->refreshRate()); this->setX(head->x()); this->setY(head->y()); this->setRelativeOutput(head->relativeTo()); this->setHorizontalAnchor(head->getHorizontalAnchor()); this->setVerticalAnchor(head->getVerticalAnchor()); this->setTransform(head->transform()); this->setAdaptiveSync(head->adaptiveSync()); this->setScale(head->scale()); this->setPrimary(head->primary()); this->setDisabled(!head->enabled()); } }budgie-desktop-services/src/config/outputs/output.hpp000066400000000000000000000111301513320106000234200ustar00rootroot00000000000000#pragma once #include #include #include #include "outputs/config/enums/anchors.hpp" #include "outputs/wlr/metahead.hpp" namespace bd::Config::Outputs { class Output : public QObject { Q_OBJECT Q_PROPERTY(QString identifier READ identifier WRITE setIdentifier NOTIFY identifierChanged) // Dimensions Q_PROPERTY(int width READ width WRITE setWidth NOTIFY widthChanged) Q_PROPERTY(int height READ height WRITE setHeight NOTIFY heightChanged) // Refresh rate Q_PROPERTY(qulonglong refresh READ refresh WRITE setRefresh NOTIFY refreshChanged) // Absolute positioning (shim mode) Q_PROPERTY(int x READ x WRITE setX NOTIFY xChanged) Q_PROPERTY(int y READ y WRITE setY NOTIFY yChanged) // Relative positioning Q_PROPERTY(QString relativeOutput READ relativeOutput WRITE setRelativeOutput NOTIFY relativeOutputChanged) Q_PROPERTY(bd::Outputs::Config::HorizontalAnchor::Type horizontalAnchor READ horizontalAnchor WRITE setHorizontalAnchor NOTIFY horizontalAnchorChanged) Q_PROPERTY(bd::Outputs::Config::VerticalAnchor::Type verticalAnchor READ verticalAnchor WRITE setVerticalAnchor NOTIFY verticalAnchorChanged) // Transform Q_PROPERTY(quint16 transform READ transform WRITE setTransform NOTIFY transformChanged) // Adaptive Sync Q_PROPERTY(uint32_t adaptiveSync READ adaptiveSync WRITE setAdaptiveSync NOTIFY adaptiveSyncChanged) // Primary Q_PROPERTY(bool primary READ primary WRITE setPrimary NOTIFY primaryChanged) // Scale Q_PROPERTY(qreal scale READ scale WRITE setScale NOTIFY scaleChanged) // Disabled Q_PROPERTY(bool disabled READ disabled WRITE setDisabled NOTIFY disabledChanged) // Property getters public: QString identifier() const; int width(); int height(); qulonglong refresh(); int x(); int y(); QString relativeOutput(); bd::Outputs::Config::HorizontalAnchor::Type horizontalAnchor(); bd::Outputs::Config::VerticalAnchor::Type verticalAnchor(); quint16 transform(); uint32_t adaptiveSync(); qreal scale(); bool primary(); bool disabled(); // Property setters void setAdaptiveSync(uint32_t adaptiveSync); void setDisabled(bool disabled); void setHeight(int height); void setHorizontalAnchor(bd::Outputs::Config::HorizontalAnchor::Type horizontalAnchor); void setIdentifier(const QString& identifier); void setPrimary(bool primary); void setRefresh(qulonglong refresh); void setRelativeOutput(const QString& relativeOutput); void setScale(qreal scale); void setTransform(quint16 transform); void setVerticalAnchor(bd::Outputs::Config::VerticalAnchor::Type verticalAnchor); void setWidth(int width); void setX(int x); void setY(int y); // Other methods toml::ordered_value toToml(); void updateFromHead(); Q_SIGNALS: void adaptiveSyncChanged(uint32_t adaptiveSync); void disabledChanged(bool disabled); void heightChanged(int height); void horizontalAnchorChanged(bd::Outputs::Config::HorizontalAnchor::Type horizontalAnchor); void identifierChanged(const QString& identifier); void primaryChanged(bool primary); void refreshChanged(qulonglong refresh); void relativeOutputChanged(const QString& relativeOutput); void scaleChanged(qreal scale); void transformChanged(quint16 transform); void verticalAnchorChanged(bd::Outputs::Config::VerticalAnchor::Type verticalAnchor); void widthChanged(int width); void xChanged(int x); void yChanged(int y); public: Output(QObject* parent = nullptr); Output(const toml::value& v, QObject* parent = nullptr); ~Output() = default; private: QSharedPointer getMetaHead(); QString m_identifier; int m_width; int m_height; qulonglong m_refresh; int m_x; int m_y; QString m_relativeOutput; bd::Outputs::Config::HorizontalAnchor::Type m_horizontalAnchor; bd::Outputs::Config::VerticalAnchor::Type m_verticalAnchor; quint16 m_transform; uint32_t m_adaptiveSync; qreal m_scale; bool m_primary; bool m_disabled; }; }budgie-desktop-services/src/config/outputs/state.cpp000066400000000000000000000207511513320106000232040ustar00rootroot00000000000000#include #include #include "state.hpp" #include "outputs/state.hpp" #include "sys/SysInfo.hpp" #include "utils.hpp" namespace bd::Config::Outputs { State::State(QObject* parent) : QObject(parent), m_activeGroup(nullptr), m_matchingGroup(nullptr), m_preferences(new GlobalPreferences(this)), m_groups(QList>()) {} State& State::instance() { static State _instance(nullptr); return _instance; } QSharedPointer State::activeGroup() const { return m_activeGroup; } QList> State::groups() const { return m_groups; } QSharedPointer State::matchingGroup() const { return m_matchingGroup; } QSharedPointer State::preferences() const { return m_preferences; } void State::setActiveGroup(QSharedPointer activeGroup) { m_activeGroup = activeGroup; emit activeGroupChanged(activeGroup); } void State::setMatchingGroup(QSharedPointer matchingGroup) { m_matchingGroup = matchingGroup; emit matchingGroupChanged(matchingGroup); } void State::setGroups(const QList>& groups) { m_groups = groups; } void State::apply() { auto matching_group = getMatchingGroup(); // If we don't have a matching group, create a default one and dump its state so we have a default if (matching_group.isNull()) { qDebug() << "No matching group found, creating a default one"; m_matchingGroup = createDefaultGroup(); matching_group = m_matchingGroup; m_groups.append(m_matchingGroup); } // Apply the configuration for the matching group matching_group->apply(); // Set the active group to the matching group m_activeGroup = matching_group; } void State::deserialize() { bool isShimMode = SysInfo::instance().isShimMode(); auto config_location = ConfigUtils::getConfigPath(isShimMode ? "display-config-shim.toml" : "display-config.toml"); ConfigUtils::ensureConfigPathExists(config_location); try { auto data = toml::parse(config_location); if (data.contains("preferences")) { auto position = data.at("preferences").at("automatic_attach_outputs_relative_position"); if (position.is_string()) { auto pos = std::string_view {position.as_string()}; m_preferences->setAutomaticAttachOutputsRelativePosition( Config::Outputs::GlobalPreferences::fromString(std::string(pos))); } } // Iterate over each group and create the Group objects for (const auto& group_toml : toml::find>(data, "group")) { auto group = new bd::Config::Outputs::Group(group_toml); m_groups.append(QSharedPointer(group)); } m_matchingGroup = getMatchingGroup(); } catch (const std::exception& e) { if (QString(e.what()).contains("error opening file")) return; qWarning() << "Error deserializing display config: " << e.what(); } } void State::save() { // Ensure bool isShimMode = SysInfo::instance().isShimMode(); toml::ordered_value config(toml::ordered_table {}); config.as_table_fmt().fmt = toml::table_format::multiline; toml::ordered_value preferences_table(toml::ordered_table {}); preferences_table["automatic_attach_outputs_relative_position"] = Config::Outputs::GlobalPreferences::toStringStd(m_preferences->automaticAttachOutputsRelativePosition()); config["preferences"] = preferences_table; toml::ordered_value groups(toml::ordered_array {}); groups.as_array_fmt().fmt = toml::array_format::array_of_tables; for (const auto& group : m_groups) { groups.push_back(group->toToml()); } config.as_table().emplace_back("group", groups); auto serialized_config = toml::format(config); auto config_location = ConfigUtils::getConfigPath(isShimMode ? "display-config-shim.toml" : "display-config.toml"); auto config_file = QFile(config_location); if (config_file.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream stream(&config_file); stream << serialized_config.c_str(); config_file.close(); } else { qWarning() << "Failed to open display-config.toml for writing"; } } QSharedPointer State::createDefaultGroup() { auto &orchestrator = bd::Outputs::State::instance(); auto manager = orchestrator.getManager(); if (manager.isNull()) { qWarning() << "WaylandOutputManager is not available"; return QSharedPointer(nullptr); } auto heads = manager->getHeads(); auto group = new Group(); QStringList names_of_active_outputs; QList> output_configs; // For each existing head in our state, add it to our names and also create a output config for it for (const auto& head : heads) { if (head->getIdentifier() == nullptr) continue; names_of_active_outputs.append(head->getIdentifier()); auto output_config = new Output(); output_config->setDisabled(false); output_config->setIdentifier(head->getIdentifier()); output_configs.append(QSharedPointer(output_config)); } group->setName(names_of_active_outputs.join(", ").append(" (Auto Generated)")); // Set our name to an autogenerated one group->setOutputConfigs(output_configs); // Add all of our new output configs group->setStoredIdentifiers(names_of_active_outputs); // Add all of our new output identifiers group->setStoredPrimaryOutputIdentifier(names_of_active_outputs.first()); // Set our primary output identifier to the first one return QSharedPointer(group); } QSharedPointer State::getMatchingGroup() { QSharedPointer matching_group = QSharedPointer(nullptr); auto &orchestrator = bd::Outputs::State::instance(); auto manager = orchestrator.getManager(); if (manager.isNull()) { qWarning() << "WaylandOutputManager is not available"; return matching_group; } // Find any matching group auto heads = manager->getHeads(); auto heads_size = heads.size(); qDebug() << "Found" << heads_size << "heads"; auto matching_groups = QList>(); for (auto group : m_groups) { if (group->storedIdentifiers().size() != heads_size) { qDebug() << "Group" << group->name() << "has" << group->storedIdentifiers().size() << "identifiers, skipping"; continue; } bool match = true; for (const auto& qIdentifier : group->storedIdentifiers()) { qDebug() << "Checking if head" << qIdentifier << "is in group" << group->name(); bool found = false; for (const auto& head : heads) { if (head->getIdentifier() == qIdentifier) { qDebug() << "Head" << qIdentifier << "found in group" << group->name(); found = true; break; } } if (!found) { match = false; break; } } if (match) { qDebug() << "Group" << group->name() << "matches, adding to matching groups"; matching_groups.append(group); } } if (!matching_groups.isEmpty()) { qDebug() << "Found" << matching_groups.size() << "matching groups"; for (const auto& group : matching_groups) { if (group->preferred()) { qDebug() << "Group" << group->name() << "is preferred, setting as matching group"; matching_group = group; break; } } if (!matching_group) { qDebug() << "No preferred group found, setting first matching group as matching group"; matching_group = matching_groups.first(); } } return matching_group; } }budgie-desktop-services/src/config/outputs/state.hpp000066400000000000000000000034471513320106000232140ustar00rootroot00000000000000#pragma once #include #include #include "group.hpp" #include "global_preferences.hpp" namespace bd::Config::Outputs { class State : public QObject { Q_OBJECT Q_PROPERTY(QSharedPointer activeGroup READ activeGroup WRITE setActiveGroup NOTIFY activeGroupChanged) Q_PROPERTY(QSharedPointer matchingGroup READ matchingGroup WRITE setMatchingGroup NOTIFY matchingGroupChanged) Q_PROPERTY(QList> groups READ groups) Q_PROPERTY(QSharedPointer preferences READ preferences) public: State(QObject* parent = nullptr); static State& instance(); static State* create() { return &instance(); } ~State() = default; // Property getters QSharedPointer activeGroup() const; QSharedPointer matchingGroup() const; QSharedPointer preferences() const; QList> groups() const; // Property setters void setActiveGroup(QSharedPointer Group); void setMatchingGroup(QSharedPointer MatchingGroup); void setGroups(const QList>& Groups); public Q_SLOTS: void apply(); void deserialize(); void save(); Q_SIGNALS: void activeGroupChanged(QSharedPointer ActiveGroup); void matchingGroupChanged(QSharedPointer MatchingGroup); void saved(); private: QSharedPointer createDefaultGroup(); QSharedPointer getMatchingGroup(); QSharedPointer m_preferences; QSharedPointer m_activeGroup; QSharedPointer m_matchingGroup; QList> m_groups; }; }budgie-desktop-services/src/config/utils.cpp000066400000000000000000000012741513320106000215000ustar00rootroot00000000000000#include "utils.hpp" #include namespace fs = std::filesystem; void bd::ConfigUtils::ensureConfigPathExists(const fs::path& p) { auto dir = p.parent_path(); if (!fs::exists(dir)) { fs::create_directories(dir); } } fs::path bd::ConfigUtils::getConfigPath(const std::string& config_name) { const char* xdg_config_home = std::getenv("XDG_CONFIG_HOME"); fs::path path {}; if (xdg_config_home) path /= xdg_config_home; if (xdg_config_home == nullptr) { const char* home = std::getenv("HOME"); if (!home) { qFatal("HOME environment variable not set"); } path /= home; path /= ".config"; } path /= "budgie-desktop"; path /= config_name; return path; } budgie-desktop-services/src/config/utils.hpp000066400000000000000000000004001513320106000214730ustar00rootroot00000000000000#pragma once #include #include #include namespace bd::ConfigUtils { void ensureConfigPathExists(const std::filesystem::path& p); std::filesystem::path getConfigPath(const std::string& config_name); } budgie-desktop-services/src/data/000077500000000000000000000000001513320106000172745ustar00rootroot00000000000000budgie-desktop-services/src/data/labwc/000077500000000000000000000000001513320106000203645ustar00rootroot00000000000000budgie-desktop-services/src/data/labwc/autostart000066400000000000000000000000351513320106000223330ustar00rootroot00000000000000org.buddiesofbudgie.Services budgie-desktop-services/src/data/org.buddiesofbudgie.Services.desktop.in000066400000000000000000000006001513320106000267640ustar00rootroot00000000000000[Desktop Entry] Name=Budgie Desktop Services Comment=Budgie Desktop Services Exec=org.buddiesofbudgie.Services TryExec=org.buddiesofbudgie.Services Terminal=false NoDisplay=true StartupNotify=false OnlyShowIn=Budgie; Type=Application X-GNOME-Autostart-Phase=Application X-GNOME-Autostart-Notify=false X-GNOME-Autostart-enabled=true X-GNOME-HiddenUnderSystemd=true X-systemd-skip=true budgie-desktop-services/src/data/org.buddiesofbudgie.Services.service.in000066400000000000000000000004321513320106000267560ustar00rootroot00000000000000[Unit] Description=Budgie Desktop Services PartOf=graphical-session.target ConditionEnvironment=WAYLAND_DISPLAY [Service] ExecStart=org.buddiesofbudgie.Services BusName=org.buddiesofbudgie.Services Restart=on-failure Slice=session.slice [Install] WantedBy=graphical-session.targetbudgie-desktop-services/src/dbus/000077500000000000000000000000001513320106000173205ustar00rootroot00000000000000budgie-desktop-services/src/dbus/ConfigService.cpp000066400000000000000000000122711513320106000225550ustar00rootroot00000000000000#include "ConfigService.hpp" #include #include "outputs/config/action.hpp" #include "outputs/config/enums/actiontype.hpp" #include "outputs/config/model.hpp" #include "outputs/config/result.hpp" namespace bd { ConfigService::ConfigService(QObject* parent) : QObject(parent) { if (!QDBusConnection::sessionBus().registerObject(OUTPUT_CONFIG_SERVICE_PATH, this, QDBusConnection::ExportAllContents)) { qCritical() << "Failed to register DBus object at path" << OUTPUT_CONFIG_SERVICE_PATH; } connect(&bd::Outputs::Config::Model::instance(), &bd::Outputs::Config::Model::configurationApplied, this, &ConfigService::ConfigurationApplied); } void ConfigService::ResetConfiguration() { bd::Outputs::Config::Model::instance().reset(); } void ConfigService::SetOutputEnabled(const QString& serial, bool enabled) { auto action = enabled ? bd::Outputs::Config::Action::explicitOn(serial) : bd::Outputs::Config::Action::explicitOff(serial); bd::Outputs::Config::Model::instance().addAction(action); } void ConfigService::SetOutputMode(const QString& serial, int width, int height, qulonglong refreshRate) { auto action = bd::Outputs::Config::Action::mode(serial, QSize(width, height), refreshRate); bd::Outputs::Config::Model::instance().addAction(action); } void ConfigService::SetOutputPositionAnchor(const QString& serial, const QString& relativeSerial, const QString& horizontalAnchor, const QString& verticalAnchor) { auto hAnchor = bd::Outputs::Config::HorizontalAnchor::fromString(horizontalAnchor); auto vAnchor = bd::Outputs::Config::VerticalAnchor::fromString(verticalAnchor); auto action = bd::Outputs::Config::Action::positionAnchor(serial, relativeSerial, hAnchor, vAnchor); bd::Outputs::Config::Model::instance().addAction(action); } void ConfigService::SetOutputScale(const QString& serial, double scale) { auto action = bd::Outputs::Config::Action::scale(serial, scale); bd::Outputs::Config::Model::instance().addAction(action); } void ConfigService::SetOutputTransform(const QString& serial, quint16 transform) { auto action = bd::Outputs::Config::Action::transform(serial, static_cast(transform)); bd::Outputs::Config::Model::instance().addAction(action); } void ConfigService::SetOutputAdaptiveSync(const QString& serial, uint adaptiveSync) { auto action = bd::Outputs::Config::Action::adaptiveSync(serial, static_cast(adaptiveSync)); bd::Outputs::Config::Model::instance().addAction(action); } void ConfigService::SetOutputPrimary(const QString& serial) { auto action = bd::Outputs::Config::Action::primary(serial); bd::Outputs::Config::Model::instance().addAction(action); } void ConfigService::SetOutputMirrorOf(const QString& serial, const QString& mirrorSerial) { auto action = bd::Outputs::Config::Action::mirrorOf(serial, mirrorSerial); bd::Outputs::Config::Model::instance().addAction(action); } QVariantMap ConfigService::CalculateConfiguration() { bd::Outputs::Config::Model::instance().calculate(); auto result = bd::Outputs::Config::Model::instance().getCalculationResult(); if (result) { return result->toVariantMap(); } return QVariantMap {}; } bool ConfigService::ApplyConfiguration() { bd::Outputs::Config::Model::instance().apply(); // The result will be emitted via ConfigurationApplied signal return true; } QVariantList ConfigService::GetActions() { QVariantList result; auto actions = bd::Outputs::Config::Model::instance().getActions(); for (const auto& action : actions) { QVariantMap map; map["type"] = bd::Outputs::Config::ActionType::toString(action->getActionType()); map["serial"] = action->getSerial(); switch (action->getActionType()) { case bd::Outputs::Config::ActionType::Type::SetOnOff: map["on"] = action->isOn(); break; case bd::Outputs::Config::ActionType::Type::SetMode: map["dimensions"] = QVariant::fromValue(action->getDimensions()); map["refresh"] = action->getRefresh(); break; case bd::Outputs::Config::ActionType::Type::SetPositionAnchor: map["relative"] = action->getRelative(); map["horizontalAnchor"] = bd::Outputs::Config::HorizontalAnchor::toString(action->getHorizontalAnchor()); map["verticalAnchor"] = bd::Outputs::Config::VerticalAnchor::toString(action->getVerticalAnchor()); break; case bd::Outputs::Config::ActionType::Type::SetScale: map["scale"] = action->getScale(); break; case bd::Outputs::Config::ActionType::Type::SetTransform: map["transform"] = action->getTransform(); break; case bd::Outputs::Config::ActionType::Type::SetAdaptiveSync: map["adaptiveSync"] = action->getAdaptiveSync(); break; case bd::Outputs::Config::ActionType::Type::SetPrimary: // No extra fields break; case bd::Outputs::Config::ActionType::Type::SetMirrorOf: map["relative"] = action->getRelative(); break; default: break; } result << map; } return result; } } // namespace bd budgie-desktop-services/src/dbus/ConfigService.hpp000066400000000000000000000025471513320106000225670ustar00rootroot00000000000000#pragma once #include #include #define OUTPUT_CONFIG_SERVICE_PATH "/org/buddiesofbudgie/Services/Outputs/Config" namespace bd { class ConfigService : public QObject, protected QDBusContext { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.buddiesofbudgie.Services.Config") public: explicit ConfigService(QObject* parent = nullptr); ~ConfigService() = default; public Q_SLOTS: void ResetConfiguration(); void SetOutputEnabled(const QString& serial, bool enabled); void SetOutputMode(const QString& serial, int width, int height, qulonglong refreshRate); void SetOutputPositionAnchor(const QString& serial, const QString& relativeSerial, const QString& horizontalAnchor, const QString& verticalAnchor); void SetOutputScale(const QString& serial, double scale); void SetOutputTransform(const QString& serial, quint16 transform); void SetOutputAdaptiveSync(const QString& serial, uint adaptiveSync); void SetOutputPrimary(const QString& serial); void SetOutputMirrorOf(const QString& serial, const QString& mirrorSerial); QVariantMap CalculateConfiguration(); bool ApplyConfiguration(); QVariantList GetActions(); Q_SIGNALS: void ConfigurationApplied(bool success); }; } budgie-desktop-services/src/dbus/org.buddiesofbudgie.Services.conf000066400000000000000000000105051513320106000256640ustar00rootroot00000000000000 budgie-desktop-services/src/dbus/schemas/000077500000000000000000000000001513320106000207435ustar00rootroot00000000000000budgie-desktop-services/src/dbus/schemas/DisplaySchema.Config.xml000066400000000000000000000047011513320106000254210ustar00rootroot00000000000000 budgie-desktop-services/src/dbus/schemas/DisplaySchema.Output.xml000066400000000000000000000047351513320106000255230ustar00rootroot00000000000000 budgie-desktop-services/src/dbus/schemas/DisplaySchema.OutputMode.xml000066400000000000000000000022601513320106000263170ustar00rootroot00000000000000 budgie-desktop-services/src/dbus/schemas/DisplaySchema.Outputs.xml000066400000000000000000000012151513320106000256740ustar00rootroot00000000000000 budgie-desktop-services/src/main.cpp000066400000000000000000000023111513320106000200100ustar00rootroot00000000000000#include #include #include #include #include "config/outputs/state.hpp" #include "dbus/ConfigService.hpp" #include "outputs/state.hpp" #include "outputs/types.hpp" int main(int argc, char* argv[]) { QGuiApplication app(argc, argv); // Register meta types qDBusRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); qSetMessagePattern("[%{type}] %{if-debug}[%{file}:%{line} %{function}]%{endif}%{message}"); if (!QDBusConnection::sessionBus().isConnected()) { qCritical() << "Cannot connect to the session bus"; return EXIT_FAILURE; } auto& state = bd::Config::Outputs::State::instance(); state.deserialize(); auto& orchestrator = bd::Outputs::State::instance(); app.connect(&orchestrator, &bd::Outputs::State::orchestratorInitFailed, [](const QString& error) { qFatal() << "Failed to initialize Wayland Orchestrator: " << error; }); app.connect(&orchestrator, &bd::Outputs::State::ready, &state, &bd::Config::Outputs::State::apply); bd::ConfigService configService; orchestrator.init(); return app.exec(); } budgie-desktop-services/src/outputs/000077500000000000000000000000001513320106000201065ustar00rootroot00000000000000budgie-desktop-services/src/outputs/config/000077500000000000000000000000001513320106000213535ustar00rootroot00000000000000budgie-desktop-services/src/outputs/config/action.cpp000066400000000000000000000122431513320106000233360ustar00rootroot00000000000000#include "action.hpp" #include namespace bd::Outputs::Config { Action::Action(ActionType::Type action_type, QString serial, QObject *parent) : QObject(parent), m_action_type(action_type), m_serial(QString {serial}), m_on(false), m_dimensions(QSize()), m_refresh(0), m_horizontal_anchor(HorizontalAnchor::None), m_vertical_anchor(VerticalAnchor::None), m_scale(1.0), m_transform(0), m_adaptive_sync(0), m_primary(false) { } QSharedPointer Action::explicitOn(const QString& serial, QObject *parent) { qDebug() << "Action::explicitOn" << serial; auto action = QSharedPointer(new Action(ActionType::Type::SetOnOff, serial, parent)); action->m_on = true; return action; } QSharedPointer Action::explicitOff(const QString& serial, QObject *parent) { qDebug() << "Action::explicitOff" << serial; return QSharedPointer(new Action(ActionType::Type::SetOnOff, serial, parent)); } QSharedPointer Action::mirrorOf(const QString& serial, QString relative, QObject *parent) { qDebug() << "Action::mirrorOf" << serial << relative; auto action = QSharedPointer(new Action(ActionType::Type::SetMirrorOf, serial, parent)); action->m_relative = QString { relative }; return action; } QSharedPointer Action::mode(const QString& serial, QSize dimensions, qulonglong refresh, QObject *parent) { qDebug() << "Action::mode" << serial << dimensions << refresh; auto action = QSharedPointer(new Action(ActionType::Type::SetMode, serial, parent)); action->m_dimensions = QSize {dimensions}; action->m_refresh = refresh; return action; } QSharedPointer Action::absolutePosition(const QString& serial, QPoint position, QObject *parent) { qDebug() << "Action::setAbsolutePosition" << serial << position; auto action = QSharedPointer(new Action(ActionType::Type::SetAbsolutePosition, serial, parent)); action->m_absolute_position = position; return action; } QSharedPointer Action::positionAnchor(const QString& serial, QString relative, bd::Outputs::Config::HorizontalAnchor::Type horizontal, bd::Outputs::Config::VerticalAnchor::Type vertical, QObject *parent) { qDebug() << "Action::positionAnchor" << serial << relative << bd::Outputs::Config::HorizontalAnchor::toString(horizontal) << bd::Outputs::Config::VerticalAnchor::toString(vertical); auto action = QSharedPointer(new Action(ActionType::SetPositionAnchor, serial, parent)); action->m_relative = QString { relative }; action->m_horizontal_anchor = horizontal; action->m_vertical_anchor = vertical; return action; } QSharedPointer Action::scale(const QString& serial, qreal scale, QObject *parent) { qDebug() << "Action::scale" << serial << scale; auto action = QSharedPointer(new Action(ActionType::SetScale, serial, parent)); action->m_scale = scale; return action; } QSharedPointer Action::transform(const QString& serial, quint16 transform, QObject *parent) { qDebug() << "Action::transform" << serial << transform; auto action = QSharedPointer(new Action(ActionType::SetTransform, serial, parent)); action->m_transform = transform; return action; } QSharedPointer Action::adaptiveSync(const QString& serial, uint32_t adaptiveSync, QObject *parent) { qDebug() << "Action::adaptiveSync" << serial << adaptiveSync; auto action = QSharedPointer(new Action(ActionType::SetAdaptiveSync, serial, parent)); action->m_adaptive_sync = adaptiveSync; return action; } QSharedPointer Action::primary(const QString& serial, QObject *parent) { qDebug() << "Action::primary" << serial; auto action = QSharedPointer(new Action(ActionType::SetPrimary, serial, parent)); action->m_primary = true; return action; } ActionType::Type Action::getActionType() const { return m_action_type; } QString Action::getSerial() const { return m_serial; } bool Action::isOn() const { return m_on; } bool Action::isPrimary() const { return m_primary; } QString Action::getRelative() const { return m_relative; } QSize Action::getDimensions() const { return m_dimensions; } qulonglong Action::getRefresh() const { return m_refresh; } QPoint Action::getAbsolutePosition() const { return m_absolute_position; } HorizontalAnchor::Type Action::getHorizontalAnchor() const { return m_horizontal_anchor; } VerticalAnchor::Type Action::getVerticalAnchor() const { return m_vertical_anchor; } qreal Action::getScale() const { return m_scale; } quint16 Action::getTransform() const { return m_transform; } uint32_t Action::getAdaptiveSync() const { return m_adaptive_sync; } } budgie-desktop-services/src/outputs/config/action.hpp000066400000000000000000000056661513320106000233560ustar00rootroot00000000000000#pragma once #include #include #include #include #include "enums/actiontype.hpp" #include "enums/anchors.hpp" namespace bd::Outputs::Config { class Action : public QObject { Q_OBJECT public: static QSharedPointer explicitOn(const QString& serial, QObject *parent = nullptr); static QSharedPointer explicitOff(const QString& serial, QObject *parent = nullptr); static QSharedPointer mirrorOf(const QString& serial, QString relative, QObject *parent = nullptr); static QSharedPointer mode(const QString& serial, QSize dimensions, qulonglong refresh, QObject *parent = nullptr); static QSharedPointer scale(const QString& serial, qreal scale, QObject *parent = nullptr); static QSharedPointer transform(const QString& serial, quint16 transform, QObject *parent = nullptr); static QSharedPointer adaptiveSync(const QString& serial, uint32_t adaptiveSync, QObject *parent = nullptr); static QSharedPointer primary(const QString& serial, QObject *parent = nullptr); static QSharedPointer absolutePosition(const QString& serial, QPoint position, QObject *parent = nullptr); static QSharedPointer positionAnchor(const QString& serial, QString relative, HorizontalAnchor::Type horizontal, VerticalAnchor::Type vertical, QObject *parent = nullptr); ~Action() = default; ActionType::Type getActionType() const; QString getSerial() const; bool isOn() const; bool isPrimary() const; QString getRelative() const; QSize getDimensions() const; QPoint getAbsolutePosition() const; qulonglong getRefresh() const; HorizontalAnchor::Type getHorizontalAnchor() const; VerticalAnchor::Type getVerticalAnchor() const; qreal getScale() const; quint16 getTransform() const; uint32_t getAdaptiveSync() const; protected: explicit Action(ActionType::Type action_type, QString serial, QObject *parent = nullptr); private: ActionType::Type m_action_type; QString m_serial; // Explicit On/Off (otherwise uses whatever current state of WlrOutputMetaHead is) bool m_on; // Shared by mirrorOf and setAnchorTo QString m_relative; // Mode QSize m_dimensions; qulonglong m_refresh; // Position Anchor (relative) HorizontalAnchor::Type m_horizontal_anchor; VerticalAnchor::Type m_vertical_anchor; // Position (absolute) QPoint m_absolute_position; // Scale qreal m_scale; // Transform quint16 m_transform; // Adaptive Sync uint32_t m_adaptive_sync; // Primary bool m_primary; }; } // bdbudgie-desktop-services/src/outputs/config/enums/000077500000000000000000000000001513320106000225025ustar00rootroot00000000000000budgie-desktop-services/src/outputs/config/enums/actiontype.hpp000066400000000000000000000023551513320106000253770ustar00rootroot00000000000000#pragma once #include #include #include namespace bd::Outputs::Config { class ActionType : public QObject { Q_OBJECT public: enum Type { SetAbsolutePosition, SetAdaptiveSync, SetGamma, SetMirrorOf, SetMode, SetOnOff, SetPrimary, SetPositionAnchor, SetScale, SetTransform, }; Q_ENUM(Type) // Convert enum to string using QMetaEnum static QString toString(Type value) { QMetaEnum metaEnum = QMetaEnum::fromType(); const char* key = metaEnum.valueToKey(static_cast(value)); if (key) { return QString::fromLatin1(key); } return QString(); } // Convert string to enum using QMetaEnum static Type fromString(const QString& str) { QMetaEnum metaEnum = QMetaEnum::fromType(); bool ok; int value = metaEnum.keyToValue(str.toLatin1().constData(), &ok); if (ok) { return static_cast(value); } return SetOnOff; // Default fallback } }; }budgie-desktop-services/src/outputs/config/enums/anchors.hpp000066400000000000000000000103321513320106000246470ustar00rootroot00000000000000#pragma once #include #include #include #include namespace bd::Outputs::Config { class HorizontalAnchor : public QObject { Q_OBJECT public: enum Type { None, // No horizontal anchor set Left, // Right edge of serial is at the left edge of relative Right, // Left edge of serial is at the right edge of relative Center, // Center of serial is at the center of relative }; Q_ENUM(Type) // Convert enum to string (lowercase for compatibility with config files) static QString toString(Type value) { switch (value) { case Left: return QStringLiteral("left"); case Right: return QStringLiteral("right"); case Center: return QStringLiteral("center"); case None: default: return QStringLiteral("none"); } } // Convert string to enum (handles both lowercase and PascalCase) static Type fromString(const QString& str) { if (str.isEmpty()) return None; QMetaEnum metaEnum = QMetaEnum::fromType(); bool ok; int value = metaEnum.keyToValue(str.toLatin1().constData(), &ok); return ok ? static_cast(value) : None; } // Convert std::string to enum (for compatibility) static Type fromString(const std::string& str) { return fromString(QString::fromStdString(str)); } // Convert enum to std::string (for compatibility) static std::string toStringStd(Type value) { return toString(value).toStdString(); } }; class VerticalAnchor : public QObject { Q_OBJECT public: enum Type { None, // No vertical anchor set Above, // Bottom edge of serial is at the top edge of relative Top, // Top edge of serial is at the top edge of relative Middle, // Middle of serial is at the middle of relative Bottom, // Bottom edge of serial is at the bottom edge of relative Below // Top edge of serial is at the bottom edge of relative }; Q_ENUM(Type) // Convert enum to string (lowercase for compatibility with config files) static QString toString(Type value) { switch (value) { case Above: return QStringLiteral("above"); case Top: return QStringLiteral("top"); case Middle: return QStringLiteral("middle"); case Bottom: return QStringLiteral("bottom"); case Below: return QStringLiteral("below"); case None: default: return QStringLiteral("none"); } } // Convert string to enum (handles both lowercase and PascalCase) static Type fromString(const QString& str) { QString lower = str.toLower(); if (lower == QLatin1String("above")) return Above; if (lower == QLatin1String("top")) return Top; if (lower == QLatin1String("middle")) return Middle; if (lower == QLatin1String("bottom")) return Bottom; if (lower == QLatin1String("below")) return Below; if (lower == QLatin1String("none")) return None; // Try QMetaEnum lookup for PascalCase (e.g., "Above", "Top") QMetaEnum metaEnum = QMetaEnum::fromType(); bool ok; int value = metaEnum.keyToValue(str.toLatin1().constData(), &ok); if (ok) { return static_cast(value); } return None; } // Convert std::string to enum (for compatibility) static Type fromString(const std::string& str) { return fromString(QString::fromStdString(str)); } // Convert enum to std::string (for compatibility) static std::string toStringStd(Type value) { return toString(value).toStdString(); } }; }budgie-desktop-services/src/outputs/config/model.cpp000066400000000000000000000570241513320106000231670ustar00rootroot00000000000000#include #include #include #include #include "config/outputs/state.hpp" #include "outputs/state.hpp" #include "sys/SysInfo.hpp" #include "model.hpp" namespace bd::Outputs::Config { Model::Model(QObject *parent) : QObject(parent), m_calculation_result(QSharedPointer()), m_actions(QList>()) { } Model& Model::instance() { static Model _instance(nullptr); return _instance; } void Model::addAction(QSharedPointer action) { // If the action is to turn off the head, remove any actions related to the head if (action->getActionType() == ActionType::Type::SetOnOff && !action->isOn()) { for (auto action : m_actions) { if (action->getSerial() == action->getSerial()) { removeAction(action->getSerial(), action->getActionType()); } } } else { // Remove any identical action for the action's serial removeAction(action->getSerial(), action->getActionType()); } // If the action is to set the primary head, remove any other primary head actions if (action->getActionType() == ActionType::Type::SetPrimary) { for (auto action : m_actions) { if (action->getActionType() == ActionType::SetPrimary) { m_actions.removeOne(action); break; } } } // If the action is to set the absolute position, remove any relative positioning actions if (action->getActionType() == ActionType::Type::SetAbsolutePosition) { for (auto action : m_actions) { if (action->getActionType() == ActionType::SetPositionAnchor) { m_actions.removeOne(action); break; } } } m_actions.append(action); } void Model::removeAction(QString serial, ActionType::Type action_type) { for (auto action : m_actions) { if (action->getSerial() == serial && action->getActionType() == action_type) { m_actions.removeOne(action); break; } } } void Model::apply() { // Always recalculate before applying so the latest actions are reflected calculate(); auto &orchestrator = bd::Outputs::State::instance(); auto manager = orchestrator.getManager(); if (manager.isNull()) { qWarning() << "WaylandOutputManager is not available"; return; } // Create a new configuration auto config = manager->configure(); if (config.isNull()) { qWarning() << "Failed to create WaylandOutputConfiguration"; return; } // Get all output states from calculation result auto outputStates = m_calculation_result->getOutputStates(); // Validate that all heads have corresponding output target states auto allHeads = manager->getHeads(); for (auto const& headPtr : allHeads) { if (headPtr.isNull()) continue; auto head = headPtr.data(); auto serial = head->getIdentifier(); if (!outputStates.contains(serial)) { qWarning() << "Model error: Head" << serial << "does not have a corresponding TargetState. This indicates a bug in the calculation logic."; return; } else { qDebug() << "Model: Head" << serial << "has a corresponding TargetState"; } } // Process each output state for (auto serial : outputStates.keys()) { auto outputState = outputStates[serial]; if (outputState.isNull()) continue; // Get the corresponding head auto head = manager->getOutputHead(serial); if (head.isNull()) { qWarning() << "Could not find head for serial:" << serial; continue; } qDebug() << "Processing output" << serial << "on:" << outputState->isOn(); if (outputState->isOn()) { // Enable the output and configure it auto configHead = config->enable(head.data()); if (configHead.isNull()) { qWarning() << "Failed to enable head for serial:" << serial; continue; } qDebug() << "Enabled output" << serial; // Set position auto position = outputState->getPosition(); configHead->setPosition(position.x(), position.y()); qDebug() << "Set position for output" << serial << "to:" << position; // Set scale auto scale = outputState->getScale(); configHead->setScale(scale); qDebug() << "Set scale for output" << serial << "to:" << scale; // Set transform auto transform = outputState->getTransform(); configHead->setTransform(transform); qDebug() << "Set transform for output" << serial << "to:" << transform; // Set adaptive sync auto adaptiveSync = outputState->getAdaptiveSync(); configHead->setAdaptiveSync(adaptiveSync); qDebug() << "Set adaptive sync for output" << serial << "to:" << adaptiveSync; // Set mode (dimensions and refresh rate) auto dimensions = outputState->getDimensions(); auto refresh = outputState->getRefresh(); if (!dimensions.isEmpty() && refresh > 0) { qDebug() << "Setting mode for output" << serial << "Dimensions:" << dimensions << "Refresh:" << refresh; // Try to find an existing mode that matches auto mode = head->getModeForOutputHead(dimensions.width(), dimensions.height(), refresh); // If the head is not built-in and we found an existing mode matching these dimensions if (!head->builtIn() && !mode.isNull()) { qDebug() << "Found existing mode for output" << serial << "Setting mode"; configHead->setMode(mode.data()); } else { qDebug() << "No existing mode found for output" << serial << "Setting custom mode"; // Use custom mode if no existing mode matches configHead->setCustomMode(dimensions.width(), dimensions.height(), refresh); } } qDebug() << "Configured output" << serial << "- Position:" << position << "Scale:" << scale << "Transform:" << transform << "AdaptiveSync:" << adaptiveSync << "Dimensions:" << dimensions << "Refresh:" << refresh; } else { // Disable the output config->disable(head.data()); qDebug() << "Disabled output" << serial; } } // Connect to configuration result signals connect(config.data(), &bd::Outputs::Wlr::Configuration::succeeded, this, [this, config]() { qDebug() << "Configuration applied successfully"; // Update and save the configuration auto& outputConfigState = bd::Config::Outputs::State::instance(); outputConfigState.save(); emit configurationApplied(true); config->release(); }); connect(config.data(), &bd::Outputs::Wlr::Configuration::failed, this, [this, config]() { qWarning() << "Configuration application failed"; emit configurationApplied(false); config->release(); }); connect(config.data(), &bd::Outputs::Wlr::Configuration::cancelled, this, [this, config]() { qWarning() << "Configuration application was cancelled"; emit configurationApplied(false); config->release(); }); // Apply the configuration qDebug() << "Applying configuration for" << outputStates.size() << "outputs"; config->applySelf(); } void Model::calculate() { m_calculation_result = QSharedPointer(new Result(this)); // Create our output target states for each serial first auto &orchestrator = bd::Outputs::State::instance(); auto manager = orchestrator.getManager(); auto pendingOutputStates = QMap>(); for (auto const& headPtr : manager->getHeads()) { // Skip null heads if (headPtr.isNull()) continue; auto head = headPtr.data(); auto outputState = new TargetState(head->getIdentifier()); outputState->setDefaultValues(headPtr); // Set some default values for the head auto outputStatePtr = QSharedPointer(outputState); pendingOutputStates.insert(head->getIdentifier(), outputStatePtr); } auto actionsBySerial = QMap>>(); // Group actions by serial for (auto action : m_actions) { if (actionsBySerial.contains(action->getSerial())) { actionsBySerial[action->getSerial()].append(action); } else { actionsBySerial.insert(action->getSerial(), QList>{action}); } } // Apply all configuration actions to output states for (auto serial : actionsBySerial.keys()) { auto actions = actionsBySerial[serial]; auto outputState = pendingOutputStates[serial]; if (outputState.isNull()) continue; qDebug() << "Applying actions for output" << serial; for (auto action : actions) { switch (action->getActionType()) { case ActionType::SetOnOff: outputState->setOn(action->isOn()); break; case ActionType::SetMode: outputState->setDimensions(action->getDimensions()); outputState->setRefresh(action->getRefresh()); break; case ActionType::SetScale: outputState->setScale(action->getScale()); break; case ActionType::SetTransform: outputState->setTransform(action->getTransform()); break; case ActionType::SetAdaptiveSync: outputState->setAdaptiveSync(action->getAdaptiveSync()); break; case ActionType::SetPrimary: outputState->setPrimary(true); break; case ActionType::SetPositionAnchor: outputState->setRelative(action->getRelative()); outputState->setHorizontalAnchor(action->getHorizontalAnchor()); outputState->setVerticalAnchor(action->getVerticalAnchor()); break; case ActionType::SetMirrorOf: outputState->setMirrorOf(action->getRelative()); break; case ActionType::SetAbsolutePosition: outputState->setPosition(action->getAbsolutePosition()); break; default: break; } } } // Update resulting dimensions for all outputs for (auto outputState : pendingOutputStates.values()) { if (!outputState.isNull()) { outputState->updateResultingDimensions(); } } // Not in shim mode, need to calculate positions, anchors, mirroring, etc. ourselves if (!SysInfo::instance().isShimMode()) { // Identify mirroring relationships - mirrored outputs only share position, not configuration auto mirrorActions = QMap(); // serial -> mirrored_serial for (auto action : m_actions) { if (action->getActionType() == ActionType::SetMirrorOf) { mirrorActions.insert(action->getSerial(), action->getRelative()); } } // Build anchor relationships auto anchorMap = QMap(); // serial -> relative_serial auto unanchoredOutputs = QList(); for (auto action : m_actions) { if (action->getActionType() == ActionType::SetPositionAnchor) { anchorMap.insert(action->getSerial(), action->getRelative()); } } for (auto serial : pendingOutputStates.keys()) { auto outputState = pendingOutputStates[serial]; if (!outputState.isNull() && outputState->isOn() && !anchorMap.contains(serial)) { unanchoredOutputs.append(serial); } } // Build horizontal chain for positioning QList horizontalChain = buildHorizontalChain(pendingOutputStates, m_actions); qDebug() << "Horizontal output chain order:" << horizontalChain; // Position outputs in horizontal chain from left to right auto positionedOutputs = QSet(); QPoint nextPosition(0, 0); for (const auto& serial : horizontalChain) { auto outputState = pendingOutputStates[serial]; // If the output is enabled and not mirroring, position it in the chain if (!outputState.isNull() && outputState->isOn() && !outputState->isMirroring()) { outputState->setPosition(nextPosition); positionedOutputs.insert(serial); // Move next position to the right auto dimensions = outputState->getResultingDimensions(); nextPosition.setX(nextPosition.x() + dimensions.width()); } } // Position anchored outputs iteratively (vertical and other anchors) bool progressMade = true; while (progressMade && positionedOutputs.size() < pendingOutputStates.size()) { progressMade = false; for (auto serial : anchorMap.keys()) { if (positionedOutputs.contains(serial)) continue; auto relativeSerial = anchorMap[serial]; if (!positionedOutputs.contains(relativeSerial)) continue; auto outputState = pendingOutputStates[serial]; auto relativeState = pendingOutputStates[relativeSerial]; if (outputState.isNull() || relativeState.isNull() || !outputState->isOn() || outputState->isMirroring()) continue; // Calculate position based on anchor (mirrored outputs will be handled separately) QPoint newPosition = calculateAnchoredPosition(outputState, relativeState); outputState->setPosition(newPosition); positionedOutputs.insert(serial); progressMade = true; } } // Position mirrored outputs for (auto serial : mirrorActions.keys()) { auto mirroredSerial = mirrorActions[serial]; auto outputState = pendingOutputStates[serial]; auto mirroredState = pendingOutputStates[mirroredSerial]; if (!outputState.isNull() && !mirroredState.isNull() && outputState->isOn() && outputState->isMirroring()) { // Only inherit position if this output has no explicit anchoring and hasn't been positioned yet if (!positionedOutputs.contains(serial) && !anchorMap.contains(serial)) { QPoint newPosition = calculateAnchoredPosition(outputState, mirroredState); outputState->setPosition(newPosition); positionedOutputs.insert(serial); } } } } // Calculate global bounding rectangle QRect globalRect; bool firstOutput = true; for (auto outputState : pendingOutputStates.values()) { if (!outputState.isNull() && outputState->isOn()) { auto position = outputState->getPosition(); auto dimensions = outputState->getResultingDimensions(); QRect outputRect(position, dimensions); if (firstOutput) { globalRect = outputRect; firstOutput = false; } else { globalRect = globalRect.united(outputRect); } } } // Store results m_calculation_result->getOutputStates().clear(); // Clear existing output states for (auto serial : pendingOutputStates.keys()) { auto outputState = pendingOutputStates[serial]; m_calculation_result->setOutputState(serial, outputState); // Set the new output state for the given serial } // Update global space if (!globalRect.isEmpty()) { auto globalSpace = m_calculation_result->getGlobalSpace(); *globalSpace = globalRect; } } QPoint Model::calculateAnchoredPosition(QSharedPointer outputState, QSharedPointer relativeState) { if (outputState.isNull() || relativeState.isNull()) return QPoint(0, 0); auto relativePos = relativeState->getPosition(); auto relativeDimensions = relativeState->getResultingDimensions(); auto outputDimensions = outputState->getResultingDimensions(); QPoint newPosition = relativePos; // Calculate horizontal position switch (outputState->getHorizontalAnchor()) { case HorizontalAnchor::Type::Left: // Right edge of output aligns with left edge of relative newPosition.setX(relativePos.x()); break; case HorizontalAnchor::Type::Right: // Left edge of output aligns with right edge of relative newPosition.setX(relativePos.x() + relativeDimensions.width() - outputDimensions.width()); break; case HorizontalAnchor::Type::Center: // Center of output aligns with center of relative newPosition.setX(relativePos.x() + (relativeDimensions.width() - outputDimensions.width()) / 2); break; default: // Default behavior: for mirrors, align left; otherwise place to the right newPosition.setX(outputState->isMirroring() ? relativePos.x() : relativePos.x() + relativeDimensions.width()); break; } // Calculate vertical position switch (outputState->getVerticalAnchor()) { case VerticalAnchor::Type::Above: // Bottom edge of output is at top edge of relative newPosition.setY(relativePos.y() - outputDimensions.height()); break; case VerticalAnchor::Type::Top: // Top edge of output aligns with top edge of relative newPosition.setY(relativePos.y()); break; case VerticalAnchor::Type::Middle: // Middle of output aligns with middle of relative newPosition.setY(relativePos.y() + (relativeDimensions.height() - outputDimensions.height()) / 2); break; case VerticalAnchor::Type::Bottom: // Bottom edge of output aligns with bottom edge of relative newPosition.setY(relativePos.y() + relativeDimensions.height() - outputDimensions.height()); break; case VerticalAnchor::Type::Below: // Top edge of output is at bottom edge of relative newPosition.setY(relativePos.y() + relativeDimensions.height()); break; default: // Default behavior: for mirrors, align top; otherwise keep same Y newPosition.setY(outputState->isMirroring() ? relativePos.y() : relativePos.y()); break; } return newPosition; } void Model::reset() { m_calculation_result.clear(); // Clear the calculation result m_actions.clear(); // Clear the actions } QList> Model::getActions() const { return m_actions; } QSharedPointer Model::getCalculationResult() const { return m_calculation_result; } QList Model::buildHorizontalChain(const QMap>& pendingOutputStates, const QList>& actions) { // Map: serial -> relative_serial (for Right anchors only, not Above/Below) QMap rightOfMap; // Reverse map: relative_serial -> serial QMap referencedByMap; QSet allSerials; QSet allRelatives; for (const auto& action : actions) { if (action->getActionType() == ActionType::SetPositionAnchor && action->getHorizontalAnchor() == HorizontalAnchor::Right && action->getVerticalAnchor() != VerticalAnchor::Above && action->getVerticalAnchor() != VerticalAnchor::Below) { rightOfMap.insert(action->getSerial(), action->getRelative()); referencedByMap.insert(action->getRelative(), action->getSerial()); allSerials.insert(action->getSerial()); allRelatives.insert(action->getRelative()); } } // All outputs for (const auto& serial : pendingOutputStates.keys()) { allSerials.insert(serial); } // Find the leftmost output: one that is referenced as a relative, but is not a serial in rightOfMap // (i.e., appears as a relative, but not as a serial) QString leftmost; for (const auto& rel : allRelatives) { if (!rightOfMap.contains(rel)) { leftmost = rel; break; } } // If not found, fallback: pick any output not a serial in rightOfMap if (leftmost.isEmpty()) { for (const auto& serial : pendingOutputStates.keys()) { if (!rightOfMap.contains(serial)) { leftmost = serial; break; } } } QList chain; QSet visited; // Walk the chain from leftmost to rightmost QString current = leftmost; while (!current.isEmpty() && !visited.contains(current)) { chain.append(current); visited.insert(current); // Find who is right of current if (referencedByMap.contains(current)) { current = referencedByMap[current]; } else { break; } } // Append unanchored outputs (not in chain) for (const auto& serial : pendingOutputStates.keys()) { if (!visited.contains(serial)) { chain.append(serial); } } return chain; } }budgie-desktop-services/src/outputs/config/model.hpp000066400000000000000000000027471513320106000231760ustar00rootroot00000000000000#pragma once #include #include #include #include #include "action.hpp" #include "result.hpp" namespace bd::Outputs::Config { class Model : public QObject { Q_OBJECT public: Model(QObject* parent = nullptr); static Model& instance(); static Model* create() { return &instance(); } void addAction(QSharedPointer action); void removeAction(QString serial, ActionType::Type action_type); // Performs a calculation if necessary and applies them void apply(); // Calculate potential resulting state from all actions // This does not apply the actions. void calculate(); QSharedPointer getCalculationResult() const; QList> getActions() const; // Clears any actions, resets any state void reset(); signals: void configurationApplied(bool success); private: QSharedPointer m_calculation_result; QList> m_actions; // Helper method for calculating anchored positions QPoint calculateAnchoredPosition(QSharedPointer outputState, QSharedPointer relativeState); // Helper to build the horizontal chain for output positioning QList buildHorizontalChain(const QMap>& pendingOutputStates, const QList>& actions); }; }budgie-desktop-services/src/outputs/config/result.cpp000066400000000000000000000041611513320106000233770ustar00rootroot00000000000000#include #include #include "result.hpp" namespace bd::Outputs::Config { Result::Result(QObject *parent) : QObject(parent), m_global_space(QSharedPointer(new QRect(0, 0, 0, 0))), m_output_states(QMap>()) { } QSharedPointer Result::getGlobalSpace() const { return m_global_space; } QMap> Result::getOutputStates() const { return m_output_states; } void Result::setOutputState(QString serial, QSharedPointer output_state) { m_output_states.insert(serial, output_state); } QVariantMap Result::toVariantMap() const { QVariantMap map; // Serialize globalSpace if (m_global_space) { QVariantMap gs; gs["x"] = m_global_space->x(); gs["y"] = m_global_space->y(); gs["width"] = m_global_space->width(); gs["height"] = m_global_space->height(); map["globalSpace"] = gs; } // Serialize outputs QVariantMap outputs; for (auto it = m_output_states.begin(); it != m_output_states.end(); ++it) { QVariantMap out; auto state = it.value(); out["on"] = state->isOn(); out["dimensions"] = QVariant::fromValue(state->getDimensions()); out["refresh"] = state->getRefresh(); out["horizontalAnchor"] = bd::Outputs::Config::HorizontalAnchor::toString(state->getHorizontalAnchor()); out["verticalAnchor"] = bd::Outputs::Config::VerticalAnchor::toString(state->getVerticalAnchor()); out["position"] = QVariant::fromValue(state->getPosition()); out["primary"] = state->isPrimary(); out["scale"] = state->getScale(); out["transform"] = state->getTransform(); out["resultingDimensions"] = QVariant::fromValue(state->getResultingDimensions()); out["adaptiveSync"] = state->getAdaptiveSync(); outputs[it.key()] = out; } map["outputs"] = outputs; return map; } }budgie-desktop-services/src/outputs/config/result.hpp000066400000000000000000000012571513320106000234070ustar00rootroot00000000000000#pragma once #include #include #include #include #include "targetstate.hpp" namespace bd::Outputs::Config { class Result : public QObject { Q_OBJECT public: Result(QObject *parent = nullptr); ~Result() = default; QSharedPointer getGlobalSpace() const; QMap> getOutputStates() const; QVariantMap toVariantMap() const; void setOutputState(QString serial, QSharedPointer output_state); private: QSharedPointer m_global_space; QMap> m_output_states; }; }budgie-desktop-services/src/outputs/config/targetstate.cpp000066400000000000000000000150741513320106000244150ustar00rootroot00000000000000#include "targetstate.hpp" namespace bd::Outputs::Config { TargetState::TargetState(QString serial, QObject *parent) : QObject(parent), m_serial(serial), m_on(false), m_dimensions(QSize(0, 0)), m_refresh(0), m_mirrorOf(""), m_relative(""), m_horizontal_anchor(HorizontalAnchor::None), m_vertical_anchor(VerticalAnchor::None), m_primary(false), m_position(QPoint(0, 0)), m_scale(1.0), m_transform(0), m_adaptive_sync(0) { } QString TargetState::getSerial() const { return m_serial; } bool TargetState::isOn() const { return m_on; } QSize TargetState::getDimensions() const { return m_dimensions; } qulonglong TargetState::getRefresh() const { return m_refresh; } QString TargetState::getRelative() const { return m_relative; } HorizontalAnchor::Type TargetState::getHorizontalAnchor() const { return m_horizontal_anchor; } VerticalAnchor::Type TargetState::getVerticalAnchor() const { return m_vertical_anchor; } QPoint TargetState::getPosition() const { return m_position; } bool TargetState::isMirroring() const { return !m_mirrorOf.isEmpty(); } bool TargetState::isPrimary() const { return m_primary; } qreal TargetState::getScale() const { return m_scale; } quint16 TargetState::getTransform() const { return m_transform; } QSize TargetState::getResultingDimensions() const { return m_resulting_dimensions; } uint32_t TargetState::getAdaptiveSync() const { return m_adaptive_sync; } void TargetState::setDefaultValues(QSharedPointer head) { if (head.isNull()) return; qDebug() << "TargetState::setDefaultValues" << m_serial; auto headData = head.data(); m_on = headData->enabled(); auto modePtr = headData->getCurrentMode(); if (!modePtr.isNull()) { auto mode = modePtr.data(); auto dimensionsOpt = mode->getSize(); if (dimensionsOpt.has_value()) { m_dimensions = QSize(dimensionsOpt.value()); } auto refreshOpt = mode->getRefresh(); if (refreshOpt.has_value()) { m_refresh = static_cast(refreshOpt.value()); } qDebug() << "dimensions" << m_dimensions << "refresh" << m_refresh; } auto position = headData->getPosition(); m_position = QPoint(position); qDebug() << "position" << m_position; m_scale = headData->scale(); m_transform = headData->transform(); auto adaptiveSync = headData->getAdaptiveSync(); m_adaptive_sync = static_cast(adaptiveSync); // Default anchoring from meta head if present (user or config provided) m_relative = headData->relativeTo(); m_horizontal_anchor = headData->getHorizontalAnchor(); m_vertical_anchor = headData->getVerticalAnchor(); m_primary = headData->primary(); qDebug() << "horizontalAnchor" << bd::Outputs::Config::HorizontalAnchor::toString(m_horizontal_anchor) << "\n" << "verticalAnchor" << bd::Outputs::Config::VerticalAnchor::toString(m_vertical_anchor) << "\n" << "primary" << m_primary; } void TargetState::setOn(bool on) { qDebug() << "TargetState::setOn" << m_serial << on; m_on = on; } void TargetState::setDimensions(QSize dimensions) { qDebug() << "TargetState::setDimensions" << m_serial << dimensions; m_dimensions = dimensions; } void TargetState::setRefresh(qulonglong refresh) { qDebug() << "TargetState::setRefresh" << m_serial << refresh; m_refresh = refresh; } void TargetState::setMirrorOf(const QString& mirrorOf) { qDebug() << "TargetState::setMirrorOf" << m_serial << mirrorOf; m_mirrorOf = mirrorOf; if (!m_mirrorOf.isEmpty()) { // If mirroring, unset any explicit relative target if (!m_relative.isEmpty()) { qDebug() << "Clearing relative due to mirrorOf being set for" << m_serial; } m_relative.clear(); } } void TargetState::setRelative(const QString& relative) { qDebug() << "TargetState::setRelative" << m_serial << relative; m_relative = relative; if (!m_relative.isEmpty()) { // If relative is set, unset any mirror target if (!m_mirrorOf.isEmpty()) { qDebug() << "Clearing mirrorOf due to relative being set for" << m_serial; } m_mirrorOf.clear(); } } void TargetState::setHorizontalAnchor(HorizontalAnchor::Type horizontal_anchor) { qDebug() << "TargetState::setHorizontalAnchor" << m_serial << bd::Outputs::Config::HorizontalAnchor::toString(horizontal_anchor); m_horizontal_anchor = horizontal_anchor; } void TargetState::setVerticalAnchor(VerticalAnchor::Type vertical_anchor) { qDebug() << "TargetState::setVerticalAnchor" << m_serial << bd::Outputs::Config::VerticalAnchor::toString(vertical_anchor); m_vertical_anchor = vertical_anchor; } void TargetState::setPosition(QPoint position) { qDebug() << "TargetState::setPosition" << m_serial << position; m_position = position; } void TargetState::setPrimary(bool primary) { qDebug() << "TargetState::setPrimary" << m_serial << primary; m_primary = primary; } void TargetState::setScale(qreal scale) { qDebug() << "TargetState::setScale" << m_serial << scale; m_scale = scale; } void TargetState::setTransform(quint16 transform) { qDebug() << "TargetState::setTransform" << m_serial << transform; m_transform = transform; } void TargetState::setAdaptiveSync(uint32_t adaptiveSync) { qDebug() << "TargetState::setAdaptiveSync" << m_serial << adaptiveSync; m_adaptive_sync = adaptiveSync; } void TargetState::updateResultingDimensions() { // TODO: See if this adjustment is even needed m_resulting_dimensions = (m_scale == 1.0) ? m_dimensions : m_dimensions * m_scale; if (m_transform == 0 || m_transform == 180) { m_resulting_dimensions.setWidth(m_dimensions.width()); m_resulting_dimensions.setHeight(m_dimensions.height()); } else if (m_transform == 90 || m_transform == 270) { m_resulting_dimensions.setWidth(m_dimensions.height()); m_resulting_dimensions.setHeight(m_dimensions.width()); } } }budgie-desktop-services/src/outputs/config/targetstate.hpp000066400000000000000000000041261513320106000244160ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "outputs/wlr/metahead.hpp" #include "enums/anchors.hpp" namespace bd::Outputs::Config { class TargetState : public QObject { Q_OBJECT public: TargetState(QString serial, QObject *parent = nullptr); ~TargetState() = default; QString getSerial() const; bool isOn() const; QSize getDimensions() const; QString getMirrorOf() const; qulonglong getRefresh() const; QString getRelative() const; HorizontalAnchor::Type getHorizontalAnchor() const; VerticalAnchor::Type getVerticalAnchor() const; QPoint getPosition() const; bool isMirroring() const; bool isPrimary() const; qreal getScale() const; quint16 getTransform() const; QSize getResultingDimensions() const; uint32_t getAdaptiveSync() const; void setDefaultValues(QSharedPointer head); void setOn(bool on); void setDimensions(QSize dimensions); void setRefresh(qulonglong refresh); void setMirrorOf(const QString& mirrorOf); void setRelative(const QString& relative); void setHorizontalAnchor(HorizontalAnchor::Type horizontal_anchor); void setVerticalAnchor(VerticalAnchor::Type vertical_anchor); void setPosition(QPoint position); void setPrimary(bool primary); void setScale(qreal scale); void setTransform(quint16 transform); void setAdaptiveSync(uint32_t adaptiveSync); void updateResultingDimensions(); private: QString m_serial; bool m_on; QSize m_dimensions; QSize m_resulting_dimensions; qulonglong m_refresh; QString m_mirrorOf; QString m_relative; HorizontalAnchor::Type m_horizontal_anchor; VerticalAnchor::Type m_vertical_anchor; bool m_primary; QPoint m_position; qreal m_scale; quint16 m_transform; uint32_t m_adaptive_sync; }; }budgie-desktop-services/src/outputs/state.cpp000066400000000000000000000213321513320106000217330ustar00rootroot00000000000000#include "state.hpp" #include #include // #include #include #include #include #include #include #include #include "config/outputs/state.hpp" #include "outputs/config/model.hpp" #include "outputs/wlr/metahead.hpp" #include "outputs/wlr/metamode.hpp" #include "sys/SysInfo.hpp" namespace bd::Outputs { State::State(QObject* parent) : QObject(parent), m_connection(nullptr), m_registry(nullptr), m_display(nullptr), m_manager(nullptr), m_has_serial(false), m_serial(0), m_has_initted(false), m_cached_primary_output(QString()), m_cached_global_rect(QVariantMap()), m_cached_primary_output_rect(QVariantMap()) {} State& State::instance() { static State _instance(nullptr); return _instance; } void State::init() { m_connection = KWayland::Client::ConnectionThread::fromApplication(); m_registry = new KWayland::Client::Registry(); m_registry->create(m_connection); connect(m_registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this](const QByteArray& interface, quint32 name, quint32 version) { if (std::strcmp(interface, QtWayland::zwlr_output_manager_v1::interface()->name) == 0) { auto manager = new bd::Outputs::Wlr::OutputManager(nullptr, m_registry, name, QtWayland::zwlr_output_manager_v1::interface()->version); connect(manager, &Wlr::OutputManager::done, this, &State::outputManagerDone); connect(manager, &Wlr::OutputManager::headAdded, this, &State::onHeadAdded); connect(manager, &Wlr::OutputManager::headRemoved, this, &State::onHeadRemoved); m_manager = QSharedPointer(manager); } }); m_registry->setup(); } QSharedPointer State::getManager() { return m_manager; } wl_display* State::getDisplay() { if (m_connection) { return m_connection->display(); } return nullptr; } KWayland::Client::Registry* State::getRegistry() { return m_registry; } KWayland::Client::ConnectionThread* State::getConnection() { return m_connection; } bool State::hasSerial() { return m_has_serial; } int State::getSerial() { return m_serial; } QStringList State::availableOutputs() const { auto outputs = QStringList {}; if (!m_manager) return outputs; for (const auto& output : m_manager->getHeads()) { if (output) outputs.append(output->serial()); } return outputs; } static QSharedPointer getPrimaryOrFirstHead() { auto manager = bd::Outputs::State::instance().getManager(); if (!manager) return nullptr; const auto heads = manager->getHeads(); if (heads.isEmpty()) return nullptr; for (const auto& head : heads) { if (head && head->primary()) return head; } return heads.first(); } QString State::primaryOutput() const { auto head = getPrimaryOrFirstHead(); if (!head) return QString(); return head->serial(); } QVariantMap State::primaryOutputRect() const { QVariantMap rect; auto head = getPrimaryOrFirstHead(); if (!head) return rect; // Populate QRect-like map similar to GetModeInfo pattern int x = head->x(); int y = head->y(); int w = 0; int h = 0; auto mode = head->getCurrentMode(); if (mode) { auto sizeOpt = mode->getSize(); if (sizeOpt.has_value()) { w = sizeOpt->width(); h = sizeOpt->height(); } } rect["X"] = x; rect["Y"] = y; rect["Width"] = w; rect["Height"] = h; return rect; } QVariantMap State::globalRect() const { QVariantMap rect; auto calculationResult = bd::Outputs::Config::Model::instance().getCalculationResult(); if (!calculationResult) return rect; auto globalSpace = calculationResult->getGlobalSpace(); if (!globalSpace) return rect; rect["X"] = globalSpace->x(); rect["Y"] = globalSpace->y(); rect["Width"] = globalSpace->width(); rect["Height"] = globalSpace->height(); return rect; } void State::registerDbusService() { const QString OUTPUTS_SERVICE_PATH = "/org/buddiesofbudgie/Services/Outputs"; qInfo() << "Registering DBus object at path" << OUTPUTS_SERVICE_PATH; if (!QDBusConnection::sessionBus().registerObject(OUTPUTS_SERVICE_PATH, this, QDBusConnection::ExportAllContents)) { qCritical() << "Failed to register DBus object at path" << OUTPUTS_SERVICE_PATH; } } void State::outputManagerDone() { if (!m_has_initted) { // First initialization - register D-Bus service and all output/mode objects qInfo() << "Wayland Orchestrator ready"; qInfo() << "Starting Display DBus Service now (outputs/modes)"; if (!QDBusConnection::sessionBus().registerService("org.buddiesofbudgie.Services")) { qCritical() << "Failed to acquire DBus service name org.buddiesofbudgie.Services"; return; } qInfo() << "Registering DBus services for outputs and modes"; // Register the Outputs service (this object) registerDbusService(); // Register all output objects (which will also register their modes) and connect signals if (m_manager) { QMap m_outputServices; for (const auto& output : m_manager->getHeads()) { if (!output) continue; QString outputId = output->getIdentifier(); if (m_outputServices.contains(outputId)) continue; // Connect to head signals connectHeadSignals(output); // This will also register all modes for this output output->registerDbusService(); m_outputServices[outputId] = output.data(); } } // Initialize cached values m_cached_primary_output = getCurrentPrimaryOutput(); m_cached_global_rect = getCurrentGlobalRect(); m_cached_primary_output_rect = getCurrentPrimaryOutputRect(); // Connect to Model's configurationApplied signal to update global rect connect(&bd::Outputs::Config::Model::instance(), &bd::Outputs::Config::Model::configurationApplied, this, &State::checkAndEmitSignals); emit ready(); // Haven't done our first init, emit that we are ready } m_has_initted = true; emit done(); } void State::onHeadAdded(QSharedPointer head) { if (!head) return; connectHeadSignals(head); checkAndEmitSignals(); } void State::onHeadRemoved(QSharedPointer head) { if (!head) return; disconnectHeadSignals(head); checkAndEmitSignals(); } void State::checkAndEmitSignals() { // Check available outputs emit availableOutputsChanged(); // Check primary output QString currentPrimary = getCurrentPrimaryOutput(); if (currentPrimary != m_cached_primary_output) { m_cached_primary_output = currentPrimary; emit primaryOutputChanged(); } // Check primary output rect QVariantMap currentPrimaryRect = getCurrentPrimaryOutputRect(); if (currentPrimaryRect != m_cached_primary_output_rect) { m_cached_primary_output_rect = currentPrimaryRect; emit primaryOutputRectChanged(); } // Check global rect QVariantMap currentGlobalRect = getCurrentGlobalRect(); if (currentGlobalRect != m_cached_global_rect) { m_cached_global_rect = currentGlobalRect; emit globalRectChanged(); } // If we are in shim mode, save the state since a head has triggered a change if (bd::SysInfo::instance().isShimMode()) { // Update the output configs from the heads auto activeGroup = bd::Config::Outputs::State::instance().activeGroup(); if (activeGroup) { qDebug() << "Saving state since a head has triggered a change in shim mode"; for (const auto& output : activeGroup->outputConfigs()) { output->updateFromHead(); } // Save the state bd::Config::Outputs::State::instance().save(); } } } void State::connectHeadSignals(QSharedPointer head) { if (!head) return; connect(head.data(), &Wlr::MetaHead::stateChanged, this, &State::checkAndEmitSignals); } void State::disconnectHeadSignals(QSharedPointer head) { if (!head) return; // Disconnect all signals from this head to this object disconnect(head.data(), nullptr, this, nullptr); } QString State::getCurrentPrimaryOutput() const { return primaryOutput(); } QVariantMap State::getCurrentGlobalRect() const { return globalRect(); } QVariantMap State::getCurrentPrimaryOutputRect() const { return primaryOutputRect(); } } budgie-desktop-services/src/outputs/state.hpp000066400000000000000000000055311513320106000217430ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include "outputs/wlr/outputmanager.hpp" namespace bd::Outputs { class State; class State : public QObject, protected QDBusContext { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.buddiesofbudgie.Services.Outputs") Q_PROPERTY(QStringList availableOutputs READ availableOutputs NOTIFY availableOutputsChanged) Q_PROPERTY(QVariantMap globalRect READ globalRect NOTIFY globalRectChanged) Q_PROPERTY(QString primaryOutput READ primaryOutput NOTIFY primaryOutputChanged) Q_PROPERTY(QVariantMap primaryOutputRect READ primaryOutputRect NOTIFY primaryOutputRectChanged) public: State(QObject* parent); static State& instance(); static State* create() { return &instance(); } void init(); QSharedPointer getManager(); wl_display* getDisplay(); KWayland::Client::Registry* getRegistry(); KWayland::Client::ConnectionThread* getConnection(); bool hasSerial(); int getSerial(); // Property getters QStringList availableOutputs() const; QVariantMap globalRect() const; QString primaryOutput() const; QVariantMap primaryOutputRect() const; // D-Bus registration void registerDbusService(); signals: void ready(); void done(); void orchestratorInitFailed(QString error); void availableOutputsChanged(); void globalRectChanged(); void primaryOutputChanged(); void primaryOutputRectChanged(); public Q_SLOTS: void outputManagerDone(); private Q_SLOTS: void onHeadAdded(QSharedPointer head); void onHeadRemoved(QSharedPointer head); void checkAndEmitSignals(); private: void connectHeadSignals(QSharedPointer head); void disconnectHeadSignals(QSharedPointer head); QString getCurrentPrimaryOutput() const; QVariantMap getCurrentGlobalRect() const; QVariantMap getCurrentPrimaryOutputRect() const; KWayland::Client::ConnectionThread* m_connection; KWayland::Client::Registry* m_registry; wl_display* m_display; QSharedPointer m_manager; bool m_has_initted; bool m_has_serial; int m_serial; QString m_cached_primary_output; QVariantMap m_cached_global_rect; QVariantMap m_cached_primary_output_rect; }; } budgie-desktop-services/src/outputs/types.cpp000066400000000000000000000027031513320106000217600ustar00rootroot00000000000000#include "types.hpp" #include QDBusArgument& operator<<(QDBusArgument& argument, const bd::Outputs::OutputModeInfo& modeInfo) { argument.beginStructure(); argument << modeInfo.id << modeInfo.width << modeInfo.height << modeInfo.refreshRate << modeInfo.preferred; argument.endStructure(); return argument; } const QDBusArgument& operator>>(const QDBusArgument& argument, bd::Outputs::OutputModeInfo& modeInfo) { argument.beginStructure(); argument >> modeInfo.id >> modeInfo.width >> modeInfo.height >> modeInfo.refreshRate >> modeInfo.preferred; argument.endStructure(); return argument; } QDBusArgument& operator<<(QDBusArgument& argument, const bd::Outputs::OutputModesMap& modesMap) { argument.beginMap(QMetaType::fromType().id(), QMetaType::fromType().id()); for (auto it = modesMap.constBegin(); it != modesMap.constEnd(); ++it) { argument.beginMapEntry(); argument << it.key() << it.value(); argument.endMapEntry(); } argument.endMap(); return argument; } const QDBusArgument& operator>>(const QDBusArgument& argument, bd::Outputs::OutputModesMap& modesMap) { modesMap.clear(); argument.beginMap(); while (!argument.atEnd()) { argument.beginMapEntry(); QString key; bd::Outputs::OutputModeInfo value; argument >> key >> value; modesMap.insert(key, value); argument.endMapEntry(); } argument.endMap(); return argument; } budgie-desktop-services/src/outputs/types.hpp000066400000000000000000000022411513320106000217620ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace bd::Outputs { typedef QMap NestedKvMap; struct OutputModeInfo { QString id; int width; int height; qulonglong refreshRate; bool preferred; bool operator==(const OutputModeInfo& other) const { return id == other.id && width == other.width && height == other.height && refreshRate == other.refreshRate && preferred == other.preferred; } }; typedef QMap OutputModesMap; } Q_DECLARE_METATYPE(bd::Outputs::NestedKvMap); Q_DECLARE_METATYPE(bd::Outputs::OutputModeInfo); Q_DECLARE_METATYPE(bd::Outputs::OutputModesMap); QDBusArgument& operator<<(QDBusArgument& argument, const bd::Outputs::OutputModeInfo& modeInfo); const QDBusArgument& operator>>(const QDBusArgument& argument, bd::Outputs::OutputModeInfo& modeInfo); QDBusArgument& operator<<(QDBusArgument& argument, const bd::Outputs::OutputModesMap& modesMap); const QDBusArgument& operator>>(const QDBusArgument& argument, bd::Outputs::OutputModesMap& modesMap); budgie-desktop-services/src/outputs/wlr/000077500000000000000000000000001513320106000207125ustar00rootroot00000000000000budgie-desktop-services/src/outputs/wlr/configuration.cpp000066400000000000000000000030331513320106000242640ustar00rootroot00000000000000#include "configuration.hpp" #include "outputs/state.hpp" namespace bd::Outputs::Wlr { Configuration::Configuration(QObject* parent, ::zwlr_output_configuration_v1* config) : QObject(parent), zwlr_output_configuration_v1(config) {} QSharedPointer Configuration::enable(bd::Outputs::Wlr::MetaHead* head) { auto wlrHeadOpt = head->getWlrHead(); if (!wlrHeadOpt.has_value()) { qWarning() << "Tried to enable head, but wlr_head is not available"; return nullptr; } auto zwlr_config_head = enable_head(wlrHeadOpt.value()); auto config_head = new ConfigurationHead(head, zwlr_config_head); return QSharedPointer(config_head); } void Configuration::applySelf() { apply(); wl_display_roundtrip(bd::Outputs::State::instance().getDisplay()); } void Configuration::release() { destroy(); } void Configuration::disable(bd::Outputs::Wlr::MetaHead* head) { auto wlrHeadOpt = head->getWlrHead(); if (!wlrHeadOpt.has_value()) { qWarning() << "Tried to disable head, but wlr_head is not available"; return; } disable_head(wlrHeadOpt.value()); } void Configuration::zwlr_output_configuration_v1_succeeded() { emit succeeded(); } void Configuration::zwlr_output_configuration_v1_failed() { emit failed(); } void Configuration::zwlr_output_configuration_v1_cancelled() { emit cancelled(); } }budgie-desktop-services/src/outputs/wlr/configuration.hpp000066400000000000000000000017731513320106000243020ustar00rootroot00000000000000#pragma once #include #include #include "qwayland-wlr-output-management-unstable-v1.h" #include "configurationhead.hpp" #include "metahead.hpp" namespace bd::Outputs::Wlr { class Configuration : public QObject, QtWayland::zwlr_output_configuration_v1 { Q_OBJECT public: Configuration(QObject* parent, ::zwlr_output_configuration_v1* config); void applySelf(); QSharedPointer enable(bd::Outputs::Wlr::MetaHead* head); void disable(bd::Outputs::Wlr::MetaHead* head); void release(); signals: void succeeded(); void failed(); void cancelled(); protected: void zwlr_output_configuration_v1_succeeded() override; void zwlr_output_configuration_v1_failed() override; void zwlr_output_configuration_v1_cancelled() override; }; }budgie-desktop-services/src/outputs/wlr/configurationhead.cpp000066400000000000000000000030301513320106000251030ustar00rootroot00000000000000#include "configurationhead.hpp" namespace bd::Outputs::Wlr { ConfigurationHead::ConfigurationHead( bd::Outputs::Wlr::MetaHead* head, ::zwlr_output_configuration_head_v1* wlr_head, QObject* parent) : QObject(parent), zwlr_output_configuration_head_v1(wlr_head), m_head(head) {} bd::Outputs::Wlr::MetaHead* ConfigurationHead::getHead() { return m_head; } void ConfigurationHead::release() { // TODO: change from being a no-op for now } void ConfigurationHead::setAdaptiveSync(uint32_t state) { set_adaptive_sync(state); } void ConfigurationHead::setMode(bd::Outputs::Wlr::MetaMode* mode) { auto wlrModeOpt = mode->getWlrMode(); if (wlrModeOpt == nullptr || (wlrModeOpt != nullptr && !wlrModeOpt.has_value())) { qWarning() << "Tried to set mode on configuration head, but mode is not available"; return; } set_mode(const_cast<::zwlr_output_mode_v1*>(wlrModeOpt.value())); } void ConfigurationHead::setCustomMode(signed int width, signed int height, qulonglong refresh) { set_custom_mode(width, height, static_cast(refresh)); } void ConfigurationHead::setPosition(int32_t x, int32_t y) { set_position(x, y); } void ConfigurationHead::setScale(double scale) { set_scale(wl_fixed_from_double(scale)); } void ConfigurationHead::setTransform(quint16 transform) { set_transform(static_cast(transform)); } }budgie-desktop-services/src/outputs/wlr/configurationhead.hpp000066400000000000000000000017661513320106000251260ustar00rootroot00000000000000#pragma once #include #include #include "qwayland-wlr-output-management-unstable-v1.h" #include "metahead.hpp" namespace bd::Outputs::Wlr { class ConfigurationHead : public QObject, QtWayland::zwlr_output_configuration_head_v1 { Q_OBJECT public: ConfigurationHead(bd::Outputs::Wlr::MetaHead* head, ::zwlr_output_configuration_head_v1* config_head, QObject* parent = nullptr); bd::Outputs::Wlr::MetaHead* getHead(); void release(); void setAdaptiveSync(uint32_t state); void setMode(bd::Outputs::Wlr::MetaMode* mode); void setCustomMode(int32_t width, int32_t height, qulonglong refresh); void setPosition(int32_t x, int32_t y); void setTransform(quint16 transform); void setScale(double scale); private: bd::Outputs::Wlr::MetaHead* m_head; }; }budgie-desktop-services/src/outputs/wlr/enums.hpp000066400000000000000000000041361513320106000225560ustar00rootroot00000000000000#pragma once #include #include #include #include namespace bd::Outputs::Wlr { class MetaHeadProperty : public QObject { Q_OBJECT public: enum Property { None, // Invalid property AdaptiveSync, Description, Enabled, Make, Model, Name, Position, Scale, SerialNumber, Transform, }; Q_ENUM(Property) static QString toString(Property value) { QMetaEnum metaEnum = QMetaEnum::fromType(); return QString::fromLatin1(metaEnum.valueToKey(static_cast(value))); } static Property fromString(const QString& str) { if (str.isEmpty()) return None; QMetaEnum metaEnum = QMetaEnum::fromType(); bool ok; int value = metaEnum.keyToValue(str.toLatin1().constData(), &ok); return ok ? static_cast(value) : None; } static Property fromString(const std::string& str) { return fromString(QString::fromStdString(str)); } static std::string toStringStd(Property value) { return toString(value).toStdString(); } }; class MetaModeProperty : public QObject { Q_OBJECT public: enum Property { None, // Invalid property Preferred, Refresh, Size, }; Q_ENUM(Property) static QString toString(Property value) { QMetaEnum metaEnum = QMetaEnum::fromType(); return QString::fromLatin1(metaEnum.valueToKey(static_cast(value))); } static Property fromString(const QString& str) { QMetaEnum metaEnum = QMetaEnum::fromType(); bool ok; int value = metaEnum.keyToValue(str.toLatin1().constData(), &ok); return ok ? static_cast(value) : None; } static Property fromString(const std::string& str) { return fromString(QString::fromStdString(str)); } static std::string toStringStd(Property value) { return toString(value).toStdString(); } }; } budgie-desktop-services/src/outputs/wlr/head.cpp000066400000000000000000000054561513320106000223310ustar00rootroot00000000000000#include "head.hpp" #include namespace bd::Outputs::Wlr { Head::Head(QObject* parent, ::zwlr_output_head_v1* wlr_head) : QObject(parent), zwlr_output_head_v1(wlr_head), m_wlr_head(wlr_head) {} ::zwlr_output_head_v1* Head::getWlrHead() { return m_wlr_head; } void Head::zwlr_output_head_v1_name(const QString& name) { qDebug() << "Head name changed to: " << name; emit propertyChanged(MetaHeadProperty::Property::Name, QVariant {name}); } void Head::zwlr_output_head_v1_description(const QString& description) { qDebug() << "Head description changed to: " << description; emit propertyChanged(MetaHeadProperty::Property::Description, QVariant {description}); } void Head::zwlr_output_head_v1_make(const QString& make) { qDebug() << "Head make changed to: " << make; emit propertyChanged(MetaHeadProperty::Property::Make, QVariant {make}); } void Head::zwlr_output_head_v1_model(const QString& model) { qDebug() << "Head model changed to: " << model; emit propertyChanged(MetaHeadProperty::Property::Model, QVariant {model}); } void Head::zwlr_output_head_v1_mode(::zwlr_output_mode_v1* mode) { qDebug() << "Head mode added: " << mode; emit modeAdded(mode); } void Head::zwlr_output_head_v1_enabled(int32_t enabled) { qDebug() << "Head enabled state changed to: " << enabled; emit propertyChanged(MetaHeadProperty::Property::Enabled, QVariant {enabled}); } void Head::zwlr_output_head_v1_current_mode(::zwlr_output_mode_v1* mode) { qDebug() << "Head current mode changed to: " << mode; emit modeChanged(mode); } void Head::zwlr_output_head_v1_finished() { qDebug() << "Head finished"; emit headFinished(); } void Head::zwlr_output_head_v1_position(int32_t x, int32_t y) { qDebug() << "Head position changed to: " << x << ", " << y; emit propertyChanged(MetaHeadProperty::Property::Position, QVariant {QPoint(x, y)}); } void Head::zwlr_output_head_v1_transform(int32_t transform) { qDebug() << "Head transform changed to: " << transform; emit propertyChanged(MetaHeadProperty::Property::Transform, QVariant {transform}); } void Head::zwlr_output_head_v1_scale(wl_fixed_t scale) { qDebug() << "Head scale changed to: " << wl_fixed_to_double(scale); emit propertyChanged(MetaHeadProperty::Property::Scale, QVariant {wl_fixed_to_double(scale)}); } void Head::zwlr_output_head_v1_serial_number(const QString& serial) { qDebug() << "Head serial number changed to: " << serial; emit propertyChanged(MetaHeadProperty::Property::SerialNumber, QVariant {serial}); } void Head::zwlr_output_head_v1_adaptive_sync(uint32_t state) { qDebug() << "Head adaptive sync state changed to: " << state; emit propertyChanged(MetaHeadProperty::Property::AdaptiveSync, QVariant {state}); } } budgie-desktop-services/src/outputs/wlr/head.hpp000066400000000000000000000030611513320106000223240ustar00rootroot00000000000000#pragma once #include #include #include "enums.hpp" #include "qwayland-wlr-output-management-unstable-v1.h" namespace bd::Outputs::Wlr { class Head : public QObject, QtWayland::zwlr_output_head_v1 { Q_OBJECT public: Head(QObject* parent, ::zwlr_output_head_v1* wlr_head); ::zwlr_output_head_v1* getWlrHead(); signals: void propertyChanged(MetaHeadProperty::Property property, const QVariant& value); void headFinished(); void modeAdded(::zwlr_output_mode_v1* mode); void modeChanged(::zwlr_output_mode_v1* mode); protected: void zwlr_output_head_v1_name(const QString& name) override; void zwlr_output_head_v1_description(const QString& description) override; void zwlr_output_head_v1_make(const QString& make) override; void zwlr_output_head_v1_model(const QString& model) override; void zwlr_output_head_v1_mode(::zwlr_output_mode_v1* mode) override; void zwlr_output_head_v1_enabled(int32_t enabled) override; void zwlr_output_head_v1_current_mode(::zwlr_output_mode_v1* mode) override; void zwlr_output_head_v1_position(int32_t x, int32_t y) override; void zwlr_output_head_v1_transform(int32_t transform) override; void zwlr_output_head_v1_scale(wl_fixed_t scale) override; void zwlr_output_head_v1_serial_number(const QString& serial) override; void zwlr_output_head_v1_adaptive_sync(uint32_t state) override; void zwlr_output_head_v1_finished() override; private: ::zwlr_output_head_v1* m_wlr_head; }; } budgie-desktop-services/src/outputs/wlr/metahead.cpp000066400000000000000000000434411513320106000231740ustar00rootroot00000000000000#include #include #include #include #include #include "metahead.hpp" #include "head.hpp" #include "config/outputs/state.hpp" #include "outputs/config/enums/anchors.hpp" #include "sys/SysInfo.hpp" namespace bd::Outputs::Wlr { MetaHead::MetaHead(QObject *parent) : QObject(parent), m_built_in(true), m_current_mode(nullptr), m_head(nullptr), m_position(QPoint{0, 0}), m_transform(0), m_scale(1.0), m_is_available(false), m_enabled(false), m_adaptive_sync(), m_relative_output(""), m_horizontal_anchor(bd::Outputs::Config::HorizontalAnchor::None), m_vertical_anchor(bd::Outputs::Config::VerticalAnchor::None), m_primary(false) { } MetaHead::~MetaHead() { for (auto &mode: m_output_modes) { if (!mode) continue; auto mode_ptr = mode.data(); mode_ptr->deleteLater(); } m_output_modes.clear(); } // Getters QtWayland::zwlr_output_head_v1::adaptive_sync_state MetaHead::getAdaptiveSync() { return m_adaptive_sync; } QSharedPointer MetaHead::getCurrentMode() { return m_current_mode; } QSharedPointer MetaHead::getHead() { return m_head; } QString MetaHead::getIdentifier() { // Have a valid serial, use that as the identifier if (!m_serial.isNull() && !m_serial.isEmpty()) { return m_serial; } // Already generated an identifier if (!m_identifier.isNull() && !m_identifier.isEmpty()) { return m_identifier; } // Default to unique name being machine ID + name auto unique_name = QString{SysInfo::instance().getMachineId() + "_" + m_name}; if (!m_make.isNull() && !m_model.isNull() && !m_make.isEmpty() && !m_model.isEmpty()) { unique_name = QString {m_make + " " + m_model + " (" + m_name + ")"}; } auto hash = QCryptographicHash::hash(unique_name.toUtf8(), QCryptographicHash::Md5); m_identifier = QString{hash.toHex()}; return m_identifier; } QSharedPointer MetaHead::getModeForOutputHead(int width, int height, qulonglong refresh) { for (const auto& mode_ptr: m_output_modes) { if (!mode_ptr) continue; auto mode = mode_ptr.data(); auto modeSizeOpt = mode->getSize(); auto modeRefreshOpt = mode->getRefresh(); if (!modeSizeOpt.has_value() || !modeRefreshOpt.has_value()) continue; if (!modeSizeOpt.value().isValid()) continue; auto modeSize = modeSizeOpt.value(); auto modeRefresh = static_cast(modeRefreshOpt.value()); if (modeSize.width() == width && modeSize.height() == height && modeRefresh == refresh) { return mode_ptr; } } return nullptr; } QList> MetaHead::getModes() { return m_output_modes; } QPoint MetaHead::getPosition() { return m_position; } std::optional<::zwlr_output_head_v1*> MetaHead::getWlrHead() { if (!m_head) return std::nullopt; auto head = m_head.data()->getWlrHead(); if (head == nullptr) return std::nullopt; return std::make_optional(head); } bool MetaHead::isAvailable() { return m_is_available; } bool MetaHead::builtIn() { // Generate identifier if necessary getIdentifier(); // Return if identifier exists return m_serial.isNull() || m_serial.isEmpty(); } bd::Outputs::OutputModeInfo MetaHead::currentMode() const { if (!m_current_mode) { bd::Outputs::OutputModeInfo empty; empty.id = QString(); empty.width = 0; empty.height = 0; empty.refreshRate = 0; empty.preferred = false; return empty; } return m_current_mode->toDBusStruct(); } bd::Outputs::OutputModesMap MetaHead::modes() const { bd::Outputs::OutputModesMap modes; for (const auto& mode_ptr: m_output_modes) { if (!mode_ptr) continue; modes.insert(mode_ptr->id(), mode_ptr->toDBusStruct()); } return modes; } QString MetaHead::serial() const { return const_cast(this)->getIdentifier(); } QString MetaHead::name() const { return m_name; } QString MetaHead::description() const { return m_description; } QString MetaHead::make() const { return m_make; } QString MetaHead::model() const { return m_model; } bool MetaHead::enabled() const { return m_enabled; } int MetaHead::width() const { auto mode = m_current_mode; if (mode) return mode->getSize().value_or(QSize(0, 0)).width(); return 0; } int MetaHead::height() const { auto mode = m_current_mode; if (mode) return mode->getSize().value_or(QSize(0, 0)).height(); return 0; } int MetaHead::x() const { return m_position.x(); } int MetaHead::y() const { return m_position.y(); } double MetaHead::scale() const { return m_scale; } qulonglong MetaHead::refreshRate() const { auto mode = m_current_mode; if (mode) return static_cast(mode->getRefresh().value_or(0.0)); return 0; } quint16 MetaHead::transform() const { return static_cast(m_transform); } uint MetaHead::adaptiveSync() const { return static_cast(m_adaptive_sync); } bool MetaHead::primary() const { return m_primary; } QString MetaHead::mirrorOf() const { return QString(); /* TODO: implement if available */ } QString MetaHead::horizontalAnchor() const { return bd::Outputs::Config::HorizontalAnchor::toString(m_horizontal_anchor); } QString MetaHead::verticalAnchor() const { return bd::Outputs::Config::VerticalAnchor::toString(m_vertical_anchor); } QString MetaHead::relativeTo() const { return m_relative_output; } // Setters void MetaHead::setHead(::zwlr_output_head_v1 *wlr_head) { auto head = new bd::Outputs::Wlr::Head(this, wlr_head); m_head = QSharedPointer(head); m_is_available = true; emit headAvailable(); connect(head, &bd::Outputs::Wlr::Head::headFinished, this, &MetaHead::headDisconnected); connect(head, &bd::Outputs::Wlr::Head::modeAdded, this, &MetaHead::addMode); connect(head, &bd::Outputs::Wlr::Head::modeChanged, this, &MetaHead::currentZwlrModeChanged); connect(head, &bd::Outputs::Wlr::Head::propertyChanged, this, &MetaHead::setProperty); } void MetaHead::setPosition(QPoint position) { if (position.isNull()) { qWarning() << "Invalid position provided, not setting position on head: " << getIdentifier(); return; } if (m_position == position) { qWarning() << "Position is already set to" << position.x() << position.y() << ", not changing it."; return; } qDebug() << "Setting position on head" << getIdentifier() << "to" << m_position.x() << m_position.y(); m_position.setX(position.x()); m_position.setY(position.y()); emit positionChanged(m_position); emit stateChanged(); } void MetaHead::setPrimary(bool primary) { if (m_primary == primary) return; m_primary = primary; emit primaryChanged(m_primary); emit stateChanged(); } void MetaHead::unsetModes() { qDebug() << "Unsetting modes for head: " << getIdentifier(); for (const auto& mode_ptr: m_output_modes) { if (!mode_ptr) continue; auto mode = mode_ptr.data(); mode->unsetMode(); // Unset the mode so it no longer holds a reference to a zwlr_output_mode_v1 (WaylandOutputMode) } } // Slots QSharedPointer MetaHead::addMode(::zwlr_output_mode_v1 *mode) { auto output_mode = new bd::Outputs::Wlr::MetaMode(this, mode); auto shared_ptr = QSharedPointer(output_mode); connect(output_mode, &bd::Outputs::Wlr::MetaMode::done, this, [this, output_mode, shared_ptr]() { // Check if this already exists qDebug() << "Done triggered for mode" << output_mode->id() << "on head" << getIdentifier(); auto found_matching_mode = false; auto matching_mode_is_current = false; qDebug() << "Checking existing modes for any matches to this one."; for (const auto &mode_ptr: m_output_modes) { if (!mode_ptr) continue; auto existing_mode = mode_ptr.data(); qDebug() << "Checking existing mode" << existing_mode->id() << "for a match."; // Already exists, delete the existing mode and add the new one if (existing_mode->isSameAs(output_mode)) { qDebug() << "Found an output mode (ID: " << existing_mode->id() << ") that matches one we already have, deleting the old one."; found_matching_mode = true; m_output_modes.removeOne(mode_ptr); matching_mode_is_current = (m_current_mode == mode_ptr); break; } } // Doesn't already exist, add it qDebug() << "Adding new output mode (ID: " << output_mode->id() << ") to head: " << getIdentifier() << " with size: " << output_mode->getSize().value_or(QSize(0, 0)) << " and refresh: " << static_cast(output_mode->getRefresh().value_or(0)); m_output_modes.append(shared_ptr); emit modesChanged(); if (found_matching_mode && matching_mode_is_current) { qDebug() << "The old matching mode was the current mode, setting the current mode to the new one."; m_current_mode = shared_ptr; emit currentModeChanged(currentMode()); } emit stateChanged(); }); return shared_ptr; } void MetaHead::currentZwlrModeChanged(::zwlr_output_mode_v1 *mode) { qDebug() << "Current mode changed for output: " << getIdentifier(); for (const auto &output_mode_ptr: m_output_modes) { if (!output_mode_ptr || output_mode_ptr.isNull()) continue; auto output_mode = output_mode_ptr.data(); auto output_mode_opt = output_mode->getWlrMode(); if (!output_mode_opt || !output_mode_opt.has_value()) { qWarning() << "Output mode is not available, skipping."; continue; } if (output_mode_opt.value() == mode) { auto outputModeSizeOpt = output_mode->getSize(); if (!outputModeSizeOpt.has_value()) return; if (!outputModeSizeOpt.value().isValid()) return; auto refreshOpt = output_mode->getRefresh(); if (!refreshOpt.has_value()) return; auto outputModeSize = outputModeSizeOpt.value(); auto refresh = refreshOpt.value(); qDebug() << "Setting current mode to" << outputModeSize.width() << "x" << outputModeSize.height() << "@" << refresh; m_current_mode = output_mode_ptr; // Set m_current_mode to same QSharedPointer as iterated output mode emit modesChanged(); emit widthChanged(outputModeSize.width()); emit heightChanged(outputModeSize.height()); emit refreshRateChanged(refresh); emit currentModeChanged(currentMode()); emit stateChanged(); return; } } } void MetaHead::headDisconnected() { qDebug() << "Head disconnected for output: " << getIdentifier(); m_head.clear(); m_is_available = false; emit headNoLongerAvailable(); emit stateChanged(); } void MetaHead::setProperty(MetaHeadProperty::Property property, const QVariant &value) { bool changed = true; switch (property) { case MetaHeadProperty::Property::AdaptiveSync: m_adaptive_sync = static_cast(value.toInt()); qDebug() << "Setting adaptive sync on head" << getIdentifier() << "to" << m_adaptive_sync; emit adaptiveSyncChanged(m_adaptive_sync); emit stateChanged(); break; case MetaHeadProperty::Property::Description: m_description = value.toString(); qDebug() << "Setting description on head" << getIdentifier() << "to" << m_description; emit descriptionChanged(m_description); break; case MetaHeadProperty::Property::Enabled: m_enabled = value.toBool(); qInfo() << "Setting enabled state on head" << getIdentifier() << "to" << m_enabled; emit enabledChanged(m_enabled); emit stateChanged(); break; case MetaHeadProperty::Property::Make: m_make = value.toString(); qDebug() << "Setting make on head" << getIdentifier() << "to" << m_make; emit makeChanged(m_make); break; case MetaHeadProperty::Property::Model: m_model = value.toString(); qDebug() << "Setting model on head" << getIdentifier() << "to" << m_model; emit modelChanged(m_model); break; case MetaHeadProperty::Property::Name: m_name = value.toString(); qDebug() << "Setting name on head" << getIdentifier() << "to" << m_name; emit nameChanged(m_name); break; case MetaHeadProperty::Property::Position: m_position = value.toPoint(); qDebug() << "Setting position on head" << getIdentifier() << "to" << m_position.x() << m_position.y(); emit positionChanged(m_position); emit stateChanged(); break; case MetaHeadProperty::Property::Scale: m_scale = value.toDouble(); qDebug() << "Setting scale on head" << getIdentifier() << "to" << m_scale; emit scaleChanged(m_scale); emit stateChanged(); break; case MetaHeadProperty::Property::SerialNumber: m_serial = value.toString(); qDebug() << "Setting serial number on head" << getIdentifier() << "to" << m_serial; emit serialChanged(m_serial); break; case MetaHeadProperty::Property::Transform: m_transform = value.toInt(); qDebug() << "Setting transform on head" << getIdentifier() << "to" << m_transform; emit transformChanged(m_transform); emit stateChanged(); break; // None or invalid property case MetaHeadProperty::Property::None: default: qWarning() << "Unknown property" << property << "for output head"; changed = false; break; } // If the property was not changed, do nothing if (!changed) return; // If we are in shim mode, immediately save the state whenever the head changes if (SysInfo::instance().isShimMode()) { bd::Config::Outputs::State::instance().save(); } } // Anchoring/relative configuration accessors bd::Outputs::Config::HorizontalAnchor::Type MetaHead::getHorizontalAnchor() const { return m_horizontal_anchor; } bd::Outputs::Config::VerticalAnchor::Type MetaHead::getVerticalAnchor() const { return m_vertical_anchor; } void MetaHead::setRelativeOutput(const QString &relative) { m_relative_output = relative; qDebug() << "Relative output set for head" << getIdentifier() << "relative:" << m_relative_output; } void MetaHead::setHorizontalAnchoring(bd::Outputs::Config::HorizontalAnchor::Type horizontal) { if (m_horizontal_anchor == horizontal) return; m_horizontal_anchor = horizontal; qDebug() << "Horizontal anchoring set for head" << getIdentifier() << "h:" << bd::Outputs::Config::HorizontalAnchor::toString(m_horizontal_anchor); } void MetaHead::setVerticalAnchoring(bd::Outputs::Config::VerticalAnchor::Type vertical) { if (m_vertical_anchor == vertical) return; m_vertical_anchor = vertical; qDebug() << "Vertical anchoring set for head" << getIdentifier() << "v:" << bd::Outputs::Config::VerticalAnchor::toString(m_vertical_anchor); } // D-Bus registration void MetaHead::registerDbusService() { QString objectPath = QString("/org/buddiesofbudgie/Services/Outputs/%1").arg(getIdentifier()); qInfo() << "Registering DBus service for output" << getIdentifier() << "at path" << objectPath; if (!QDBusConnection::sessionBus().registerObject(objectPath, this, QDBusConnection::ExportAllContents)) { qCritical() << "Failed to register DBus object at path" << objectPath; return; } // Register all modes for this output for (const auto& mode : m_output_modes) { if (!mode) continue; mode->registerDbusService(); } } } budgie-desktop-services/src/outputs/wlr/metahead.hpp000066400000000000000000000152231513320106000231760ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include "head.hpp" #include "outputs/wlr/metamode.hpp" #include "enums.hpp" #include "outputs/config/enums/anchors.hpp" #include "outputs/types.hpp" namespace bd::Outputs::Wlr { class MetaHead : public QObject, protected QDBusContext { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.buddiesofbudgie.Services.Output") Q_PROPERTY(uint adaptiveSync READ adaptiveSync NOTIFY adaptiveSyncChanged) Q_PROPERTY(bool builtIn READ builtIn) Q_PROPERTY(bd::Outputs::OutputModeInfo currentMode READ currentMode NOTIFY currentModeChanged) Q_PROPERTY(QString description READ description NOTIFY descriptionChanged) Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged) Q_PROPERTY(int height READ height NOTIFY heightChanged) Q_PROPERTY(QString horizontalAnchor READ horizontalAnchor NOTIFY horizontalAnchorChanged) Q_PROPERTY(QString make READ make NOTIFY makeChanged) Q_PROPERTY(bd::Outputs::OutputModesMap modes READ modes NOTIFY modesChanged) Q_PROPERTY(QString mirrorOf READ mirrorOf NOTIFY mirrorOfChanged) Q_PROPERTY(QString model READ model NOTIFY modelChanged) Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(bool primary READ primary NOTIFY primaryChanged) Q_PROPERTY(qulonglong refreshRate READ refreshRate NOTIFY refreshRateChanged) Q_PROPERTY(QString relativeTo READ relativeTo NOTIFY relativeToChanged) Q_PROPERTY(double scale READ scale NOTIFY scaleChanged) Q_PROPERTY(QString serial READ serial NOTIFY serialChanged) Q_PROPERTY(quint16 transform READ transform NOTIFY transformChanged) Q_PROPERTY(QString verticalAnchor READ verticalAnchor NOTIFY verticalAnchorChanged) Q_PROPERTY(int width READ width NOTIFY widthChanged) Q_PROPERTY(int x READ x NOTIFY xChanged) Q_PROPERTY(int y READ y NOTIFY yChanged) public: MetaHead(QObject *parent); ~MetaHead() override; QtWayland::zwlr_output_head_v1::adaptive_sync_state getAdaptiveSync(); QSharedPointer getCurrentMode(); QSharedPointer getHead(); QSharedPointer getModeForOutputHead(int width, int height, qulonglong refresh); QList> getModes(); uint adaptiveSync() const; bool builtIn(); bd::Outputs::OutputModeInfo currentMode() const; QString description() const; bool enabled() const; int height() const; QString horizontalAnchor() const; QString make() const; QString mirrorOf() const; bd::Outputs::OutputModesMap modes() const; QString model() const; QString name() const; bool primary() const; qulonglong refreshRate() const; QString relativeTo() const; double scale() const; QString serial() const; // Maps to wl_output transform enum. See https://wayland.app/protocols/wayland#wl_output:enum:transform quint16 transform() const; int width() const; int x() const; int y() const; QString verticalAnchor() const; // Internal getters (used by Q_PROPERTY getters or for special return types) QString getIdentifier(); // Used by serial() Q_PROPERTY getter QPoint getPosition(); // Returns QPoint (x()/y() return int) bd::Outputs::Config::HorizontalAnchor::Type getHorizontalAnchor() const; // Returns Type (horizontalAnchor() returns QString) bd::Outputs::Config::VerticalAnchor::Type getVerticalAnchor() const; // Returns Type (verticalAnchor() returns QString) std::optional<::zwlr_output_head_v1*> getWlrHead(); bool isAvailable(); void setHead(::zwlr_output_head_v1 *head); void setHorizontalAnchoring(bd::Outputs::Config::HorizontalAnchor::Type horizontal); void setPosition(QPoint position); void setRelativeOutput(const QString &relative); void setPrimary(bool primary); void setVerticalAnchoring(bd::Outputs::Config::VerticalAnchor::Type vertical); void unsetModes(); // D-Bus registration void registerDbusService(); Q_SIGNALS: void headAvailable(); void headNoLongerAvailable(); void stateChanged(); void adaptiveSyncChanged(uint adaptiveSync); void currentModeChanged(const bd::Outputs::OutputModeInfo ¤tMode); void descriptionChanged(const QString &description); void enabledChanged(bool enabled); void heightChanged(int height); void horizontalAnchorChanged(const QString &horizontalAnchor); void makeChanged(const QString &make); void mirrorOfChanged(const QString &mirrorOf); void modelChanged(const QString &model); void modesChanged(); void nameChanged(const QString &name); void positionChanged(const QPoint &position); void primaryChanged(bool primary); void refreshRateChanged(qulonglong refreshRate); void relativeToChanged(const QString &relativeTo); void scaleChanged(double scale); void serialChanged(const QString &serial); void transformChanged(quint16 transform); void verticalAnchorChanged(const QString &verticalAnchor); void widthChanged(int width); void xChanged(int x); void yChanged(int y); private Q_SLOTS: QSharedPointer addMode(::zwlr_output_mode_v1 *mode); void currentZwlrModeChanged(::zwlr_output_mode_v1 *mode); void headDisconnected(); void setProperty(MetaHeadProperty::Property property, const QVariant &value); private: KWayland::Client::Registry *m_registry; QSharedPointer m_head; QString m_make; QString m_model; QString m_name; QString m_description; QString m_identifier; QList> m_output_modes; QString m_serial; QSharedPointer m_current_mode; QPoint m_position; qint16 m_transform; qreal m_scale; bool m_built_in; bool m_is_available; bool m_enabled; QtWayland::zwlr_output_head_v1::adaptive_sync_state m_adaptive_sync; // Non-protocol metadata persisted via config QString m_relative_output; bd::Outputs::Config::HorizontalAnchor::Type m_horizontal_anchor; bd::Outputs::Config::VerticalAnchor::Type m_vertical_anchor; bool m_primary; }; } budgie-desktop-services/src/outputs/wlr/metamode.cpp000066400000000000000000000145611513320106000232200ustar00rootroot00000000000000#include #include #include "metahead.hpp" #include "metamode.hpp" namespace bd::Outputs::Wlr { MetaMode::MetaMode(QObject *parent, ::zwlr_output_mode_v1 *wlr_mode) : QObject(parent), m_id(QString()), m_mode(QSharedPointer(nullptr)), m_refresh(0), m_preferred(std::nullopt), m_size(QSize{0, 0}), m_is_available(std::nullopt) { setMode(wlr_mode); } MetaMode::~MetaMode() { unsetMode(); // Unset the mode to ensure no references are held m_preferred = std::nullopt; // Clear preferred state } bool MetaMode::available() const { return m_is_available.has_value() && m_is_available.value(); } bool MetaMode::current() const { auto head = qobject_cast(parent()); if (!head) return false; auto currentMode = head->getCurrentMode(); if (!currentMode) return false; // Compare pointer identity return currentMode.data() == this; } int MetaMode::height() const { auto size = m_size; if (size.isEmpty() || size.isNull()) return 0; return size.height(); } QString MetaMode::id() const { return m_id; } bool MetaMode::preferred() const { return m_preferred.has_value() && m_preferred.value(); } qulonglong MetaMode::refreshRate() const { return m_refresh; } int MetaMode::width() const { auto size = m_size; if (size.isEmpty() || size.isNull()) return 0; return size.width(); } std::optional MetaMode::getRefresh() { if (m_refresh == 0) return std::nullopt; return std::make_optional(m_refresh); } std::optional MetaMode::getSize() { if (m_size.isEmpty() || m_size.isNull()) return std::nullopt; return std::make_optional(m_size); } std::optional MetaMode::getWlrMode() { if (!m_mode || m_mode.isNull()) return std::nullopt; return m_mode->getWlrMode(); } std::optional MetaMode::isAvailable() { return m_is_available; } std::optional MetaMode::isPreferred() { return m_preferred; } bool MetaMode::isSameAs(MetaMode *mode) { if (mode == nullptr) { return false; } auto r_refresh = mode->getRefresh(); if (!r_refresh) { return false; } auto r_size = mode->getSize(); if (!r_size) { return false; } auto refresh = getRefresh(); if (!refresh) { return false; } auto size = getSize(); if (!size) { return false; } auto same = r_refresh.value() == refresh.value() && r_size.value() == size.value(); if (same) { qDebug() << "Mode is same as ours, with refresh:" << r_refresh.value() << "and size:" << r_size.value(); } return same; } // D-Bus registration void MetaMode::registerDbusService() { auto head = qobject_cast(parent()); if (!head) return; auto outputId = head->getIdentifier(); QString objectPath = QString("/org/buddiesofbudgie/Services/Outputs/%1/Modes/%2").arg(outputId).arg(m_id); qDebug() << "Registering DBus service for mode" << m_id << "at path" << objectPath; if (!QDBusConnection::sessionBus().registerObject(objectPath, this, QDBusConnection::ExportAllContents)) { qWarning() << "Failed to register DBus object at path" << objectPath; } } // Setters void MetaMode::setMode(::zwlr_output_mode_v1 *wlr_mode) { if (wlr_mode == nullptr) { qWarning() << "Received null wlr_mode, doing nothing."; return; } unsetMode(); // Unset any existing mode qDebug() << "Creating new Mode with wlr mode:" << (void*)wlr_mode; auto mode = new Mode(wlr_mode); m_mode = QSharedPointer(mode); connect(mode, &Mode::propertyChanged, this, &MetaMode::setProperty); } void MetaMode::unsetMode() { if (!m_mode.isNull()) { m_mode.clear(); } m_is_available = std::make_optional(false); } bd::Outputs::OutputModeInfo MetaMode::toDBusStruct() const { bd::Outputs::OutputModeInfo info; info.id = m_id; info.width = m_size.width(); info.height = m_size.height(); info.refreshRate = m_refresh; info.preferred = m_preferred.has_value() && m_preferred.value(); return info; } // Slots void MetaMode::modeDisconnected() { unsetMode(); emit availabilityChanged(false); m_is_available = std::make_optional(false); } void MetaMode::setPreferred(bool preferred) { m_preferred = std::make_optional(preferred); emit preferredChanged(preferred); } void MetaMode::setProperty(MetaModeProperty::Property property, const QVariant &value) { bool changed = true; switch (property) { case MetaModeProperty::Property::Preferred: m_preferred = std::make_optional(value.toBool()); emit preferredChanged(m_preferred.value()); break; case MetaModeProperty::Property::Refresh: m_refresh = static_cast(value.toULongLong()); break; case MetaModeProperty::Property::Size: m_size = value.toSize(); break; // None or invalid property case MetaModeProperty::Property::None: default: changed = false; break; } // If the property was not changed, do nothing if (!changed) return; auto refresh = getRefresh(); auto size = getSize(); if (!refresh.has_value()) return; if (!size.has_value()) return; if (!size.value().isValid()) return; emit refreshRateChanged(refresh.value()); emit sizeChanged(size.value()); // Compute ID string as {width}_{height}_{refresh} and ensure it's DBus object-path safe const auto sz = size.value(); const auto ref = refresh.value(); m_id = QString("%1_%2_%3").arg(sz.width()).arg(sz.height()).arg(ref); m_is_available = std::make_optional(true); emit availabilityChanged(true); emit done(); } } budgie-desktop-services/src/outputs/wlr/metamode.hpp000066400000000000000000000042051513320106000232170ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include "mode.hpp" #include "enums.hpp" #include "outputs/types.hpp" namespace bd::Outputs::Wlr { class MetaMode : public QObject, protected QDBusContext { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.buddiesofbudgie.Services.OutputMode") Q_PROPERTY(bool available READ available) Q_PROPERTY(bool current READ current) Q_PROPERTY(int height READ height) Q_PROPERTY(QString id READ id) Q_PROPERTY(bool preferred READ preferred) Q_PROPERTY(qulonglong refreshRate READ refreshRate) Q_PROPERTY(int width READ width) public: MetaMode(QObject *parent, ::zwlr_output_mode_v1 *wlr_mode); ~MetaMode() override; // Property getters bool available() const; bool current() const; int height() const; QString id() const; bool preferred() const; qulonglong refreshRate() const; int width() const; bd::Outputs::OutputModeInfo toDBusStruct() const; std::optional getRefresh(); std::optional getSize(); std::optional getWlrMode(); std::optional isAvailable(); std::optional isPreferred(); bool isSameAs(MetaMode *mode); void registerDbusService(); void setMode(::zwlr_output_mode_v1 *wlr_mode); void setPreferred(bool preferred); void unsetMode(); Q_SIGNALS: void availabilityChanged(bool available); void done(); void preferredChanged(bool preferred); void refreshRateChanged(qulonglong refreshRate); void sizeChanged(QSize size); public Q_SLOTS: void modeDisconnected(); private Q_SLOTS: void setProperty(MetaModeProperty::Property property, const QVariant &value); private: QSharedPointer m_mode; QString m_id; QSize m_size; qulonglong m_refresh; std::optional m_preferred; std::optional m_is_available; }; } budgie-desktop-services/src/outputs/wlr/mode.cpp000066400000000000000000000016411513320106000223440ustar00rootroot00000000000000#include "mode.hpp" namespace bd::Outputs::Wlr { Mode::Mode(::zwlr_output_mode_v1* mode) : zwlr_output_mode_v1(mode) {} std::optional<::zwlr_output_mode_v1*> Mode::getWlrMode() { if (isInitialized() && object()) { return std::make_optional(object()); } return std::nullopt; } void Mode::zwlr_output_mode_v1_size(int32_t width, int32_t height) { qDebug() << "Mode size changed to: " << width << "x" << height; emit propertyChanged(MetaModeProperty::Property::Size, QVariant {QSize(width, height)}); } void Mode::zwlr_output_mode_v1_refresh(int32_t refresh) { qDebug() << "Mode refresh changed to: " << refresh; auto val = QVariant::fromValue(refresh); emit propertyChanged(MetaModeProperty::Property::Refresh, val); } void Mode::zwlr_output_mode_v1_preferred() { emit propertyChanged(MetaModeProperty::Property::Preferred, QVariant::fromValue(true)); } } budgie-desktop-services/src/outputs/wlr/mode.hpp000066400000000000000000000014131513320106000223460ustar00rootroot00000000000000#pragma once #include #include #include #include "enums.hpp" #include "qwayland-wlr-output-management-unstable-v1.h" namespace bd::Outputs::Wlr { class Mode : public QObject, public QtWayland::zwlr_output_mode_v1 { Q_OBJECT public: Mode(::zwlr_output_mode_v1* mode); std::optional<::zwlr_output_mode_v1*> getWlrMode(); signals: void propertyChanged(MetaModeProperty::Property property, const QVariant& value); void modeFinished(); protected: void zwlr_output_mode_v1_size(int32_t width, int32_t height) override; void zwlr_output_mode_v1_refresh(int32_t refresh) override; void zwlr_output_mode_v1_preferred() override; // void zwlr_output_mode_v1_finished() override; }; } budgie-desktop-services/src/outputs/wlr/outputmanager.cpp000066400000000000000000000122151513320106000243120ustar00rootroot00000000000000#include "outputmanager.hpp" #include #include #include namespace bd::Outputs::Wlr { OutputManager::OutputManager(QObject* parent, KWayland::Client::Registry* registry, uint32_t serial, uint32_t version) : QObject(parent), zwlr_output_manager_v1(registry->registry(), serial, static_cast(version)), m_registry(registry), m_serial(serial), m_has_serial(true), m_version(version) {} // Overridden methods from QtWayland::zwlr_output_manager_v1 void OutputManager::zwlr_output_manager_v1_head(zwlr_output_head_v1* wlr_head) { auto head = new bd::Outputs::Wlr::MetaHead(nullptr); qInfo() << "OutputManager::zwlr_output_manager_v1_head with id:" << head->getIdentifier() << ", description:" << head->description(); connect(head, &bd::Outputs::Wlr::MetaHead::headAvailable, this, [this, head]() { qDebug() << "Head available for output: " << head->getIdentifier(); bool headAlreadyExists = false; for (const auto& existingHead : m_heads) { qDebug() << "Checking existing head: " << existingHead->getIdentifier(); if (existingHead->getIdentifier() == head->getIdentifier()) { qDebug() << "Head already exists for output: " << head->getIdentifier(); headAlreadyExists = true; existingHead->setHead(head->getWlrHead().value()); break; } } if (!headAlreadyExists) { qDebug() << "Adding new head for output: " << head->getIdentifier(); auto sharedHead = QSharedPointer(head); m_heads.append(sharedHead); emit headAdded(sharedHead); } }); head->setHead(wlr_head); } void OutputManager::zwlr_output_manager_v1_finished() { qInfo() << "OutputManager::zwlr_output_manager_v1_finished"; } void OutputManager::zwlr_output_manager_v1_done(uint32_t serial) { qDebug() << "OutputManager::zwlr_output_manager_v1_done with serial:" << serial; m_serial = serial; m_has_serial = true; emit done(); } // applyNoOpConfigurationForNonSpecifiedHeads is a bit of a funky function, but effectively it applies a configuration that does nothing for every output // excluding the ones we are wanting to change (specified by the serial). This is to ensure we don't create protocol errors when performing output // configurations, as it is a protocol error to not specify everything else. QList> OutputManager::applyNoOpConfigurationForNonSpecifiedHeads( Configuration* config, const QStringList& serials) { auto configHeads = QList> {}; qDebug() << "Applying no-op configuration for non-specified heads. Ignoring:" << serials.join(", "); for (const auto& o : m_heads) { qDebug() << "Checking head " << o->getIdentifier() << ": " << o->description(); // Skip the output for the serial we are changing if (serials.contains(o->getIdentifier())) { qDebug() << "Skipping head " << o->getIdentifier(); continue; } if (o->enabled()) { qDebug() << "Ensuring head " << o->getIdentifier() << " is enabled"; auto head = config->enable(o.data()); if (!head) { qWarning() << "Failed to enable head " << o->getIdentifier() << ", wlr_head is not available"; continue; } configHeads.append(head); } else { qDebug() << "Ensuring head " << o->getIdentifier() << " is disabled"; config->disable(o.data()); } } return configHeads; } QSharedPointer OutputManager::configure() { auto wlr_output_configuration = create_configuration(m_serial); auto config = new Configuration(nullptr, wlr_output_configuration); connect(config, &Configuration::cancelled, this, [this, config]() { qDebug() << "Configuration cancelled"; // config->deleteLater(); }); connect(config, &Configuration::succeeded, this, [this, config]() { qDebug() << "Configuration succeeded"; // config->deleteLater(); }); connect(config, &Configuration::failed, this, [this, config]() { qDebug() << "Configuration failed"; // config->deleteLater(); }); return QSharedPointer(config); } QList> OutputManager::getHeads() { return m_heads; } QSharedPointer OutputManager::getOutputHead(const QString& str) { for (auto head : m_heads) { if (head->getIdentifier() == str) { return head; } } return nullptr; } uint32_t OutputManager::getSerial() { return m_serial; } uint32_t OutputManager::getVersion() { return m_version; } } budgie-desktop-services/src/outputs/wlr/outputmanager.hpp000066400000000000000000000034641513320106000243250ustar00rootroot00000000000000#pragma once #include #include #include #include #include "qwayland-wlr-output-management-unstable-v1.h" #include "configuration.hpp" #include "configurationhead.hpp" #include "metahead.hpp" namespace bd::Outputs::Wlr { class OutputManager : public QObject, QtWayland::zwlr_output_manager_v1 { Q_OBJECT public: OutputManager(QObject* parent, KWayland::Client::Registry* registry, uint32_t serial, uint32_t version); // static WaylandOutputManager& instance(); QSharedPointer configure(); QList> getHeads(); QSharedPointer getOutputHead(const QString& str); QList> applyNoOpConfigurationForNonSpecifiedHeads( Configuration* config, const QStringList& identifiers); uint32_t getSerial(); uint32_t getVersion(); signals: void done(); void headAdded(QSharedPointer head); void headRemoved(QSharedPointer head); protected: void zwlr_output_manager_v1_head(zwlr_output_head_v1* head) override; void zwlr_output_manager_v1_finished() override; void zwlr_output_manager_v1_done(uint32_t serial) override; private: KWayland::Client::Registry* m_registry; QList> m_heads; uint32_t m_serial; bool m_has_serial; uint32_t m_version; }; }budgie-desktop-services/src/protocols/000077500000000000000000000000001513320106000204075ustar00rootroot00000000000000budgie-desktop-services/src/protocols/wlr-output-management-unstable-v1.xml000066400000000000000000000622661513320106000275600ustar00rootroot00000000000000 Copyright © 2019 Purism SPC Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holders not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. This protocol exposes interfaces to obtain and modify output device configuration. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. This interface is a manager that allows reading and writing the current output device configuration. Output devices that display pixels (e.g. a physical monitor or a virtual output in a window) are represented as heads. Heads cannot be created nor destroyed by the client, but they can be enabled or disabled and their properties can be changed. Each head may have one or more available modes. Whenever a head appears (e.g. a monitor is plugged in), it will be advertised via the head event. Immediately after the output manager is bound, all current heads are advertised. Whenever a head's properties change, the relevant wlr_output_head events will be sent. Not all head properties will be sent: only properties that have changed need to. Whenever a head disappears (e.g. a monitor is unplugged), a wlr_output_head.finished event will be sent. After one or more heads appear, change or disappear, the done event will be sent. It carries a serial which can be used in a create_configuration request to update heads properties. The information obtained from this protocol should only be used for output configuration purposes. This protocol is not designed to be a generic output property advertisement protocol for regular clients. Instead, protocols such as xdg-output should be used. This event introduces a new head. This happens whenever a new head appears (e.g. a monitor is plugged in) or after the output manager is bound. This event is sent after all information has been sent after binding to the output manager object and after any subsequent changes. This applies to child head and mode objects as well. In other words, this event is sent whenever a head or mode is created or destroyed and whenever one of their properties has been changed. Not all state is re-sent each time the current configuration changes: only the actual changes are sent. This allows changes to the output configuration to be seen as atomic, even if they happen via multiple events. A serial is sent to be used in a future create_configuration request. Create a new output configuration object. This allows to update head properties. Indicates the client no longer wishes to receive events for output configuration changes. However the compositor may emit further events, until the finished event is emitted. The client must not send any more requests after this one. This event indicates that the compositor is done sending manager events. The compositor will destroy the object immediately after sending this event, so it will become invalid and the client should release any resources associated with it. A head is an output device. The difference between a wl_output object and a head is that heads are advertised even if they are turned off. A head object only advertises properties and cannot be used directly to change them. A head has some read-only properties: modes, name, description and physical_size. These cannot be changed by clients. Other properties can be updated via a wlr_output_configuration object. Properties sent via this interface are applied atomically via the wlr_output_manager.done event. No guarantees are made regarding the order in which properties are sent. This event describes the head name. The naming convention is compositor defined, but limited to alphanumeric characters and dashes (-). Each name is unique among all wlr_output_head objects, but if a wlr_output_head object is destroyed the same name may be reused later. The names will also remain consistent across sessions with the same hardware and software configuration. Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do not assume that the name is a reflection of an underlying DRM connector, X11 connection, etc. If the compositor implements the xdg-output protocol and this head is enabled, the xdg_output.name event must report the same name. The name event is sent after a wlr_output_head object is created. This event is only sent once per object, and the name does not change over the lifetime of the wlr_output_head object. This event describes a human-readable description of the head. The description is a UTF-8 string with no convention defined for its contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11 output via :1'. However, do not assume that the name is a reflection of the make, model, serial of the underlying DRM connector or the display name of the underlying X11 connection, etc. If the compositor implements xdg-output and this head is enabled, the xdg_output.description must report the same description. The description event is sent after a wlr_output_head object is created. This event is only sent once per object, and the description does not change over the lifetime of the wlr_output_head object. This event describes the physical size of the head. This event is only sent if the head has a physical size (e.g. is not a projector or a virtual device). This event introduces a mode for this head. It is sent once per supported mode. This event describes whether the head is enabled. A disabled head is not mapped to a region of the global compositor space. When a head is disabled, some properties (current_mode, position, transform and scale) are irrelevant. This event describes the mode currently in use for this head. It is only sent if the output is enabled. This events describes the position of the head in the global compositor space. It is only sent if the output is enabled. This event describes the transformation currently applied to the head. It is only sent if the output is enabled. This events describes the scale of the head in the global compositor space. It is only sent if the output is enabled. This event indicates that the head is no longer available. The head object becomes inert. Clients should send a destroy request and release any resources associated with it. This event describes the manufacturer of the head. This must report the same make as the wl_output interface does in its geometry event. Together with the model and serial_number events the purpose is to allow clients to recognize heads from previous sessions and for example load head-specific configurations back. It is not guaranteed this event will be ever sent. A reason for that can be that the compositor does not have information about the make of the head or the definition of a make is not sensible in the current setup, for example in a virtual session. Clients can still try to identify the head by available information from other events but should be aware that there is an increased risk of false positives. It is not recommended to display the make string in UI to users. For that the string provided by the description event should be preferred. This event describes the model of the head. This must report the same model as the wl_output interface does in its geometry event. Together with the make and serial_number events the purpose is to allow clients to recognize heads from previous sessions and for example load head-specific configurations back. It is not guaranteed this event will be ever sent. A reason for that can be that the compositor does not have information about the model of the head or the definition of a model is not sensible in the current setup, for example in a virtual session. Clients can still try to identify the head by available information from other events but should be aware that there is an increased risk of false positives. It is not recommended to display the model string in UI to users. For that the string provided by the description event should be preferred. This event describes the serial number of the head. Together with the make and model events the purpose is to allow clients to recognize heads from previous sessions and for example load head- specific configurations back. It is not guaranteed this event will be ever sent. A reason for that can be that the compositor does not have information about the serial number of the head or the definition of a serial number is not sensible in the current setup. Clients can still try to identify the head by available information from other events but should be aware that there is an increased risk of false positives. It is not recommended to display the serial_number string in UI to users. For that the string provided by the description event should be preferred. This request indicates that the client will no longer use this head object. This event describes whether adaptive sync is currently enabled for the head or not. Adaptive sync is also known as Variable Refresh Rate or VRR. This object describes an output mode. Some heads don't support output modes, in which case modes won't be advertised. Properties sent via this interface are applied atomically via the wlr_output_manager.done event. No guarantees are made regarding the order in which properties are sent. This event describes the mode size. The size is given in physical hardware units of the output device. This is not necessarily the same as the output size in the global compositor space. For instance, the output may be scaled or transformed. This event describes the mode's fixed vertical refresh rate. It is only sent if the mode has a fixed refresh rate. This event advertises this mode as preferred. This event indicates that the mode is no longer available. The mode object becomes inert. Clients should send a destroy request and release any resources associated with it. This request indicates that the client will no longer use this mode object. This object is used by the client to describe a full output configuration. First, the client needs to setup the output configuration. Each head can be either enabled (and configured) or disabled. It is a protocol error to send two enable_head or disable_head requests with the same head. It is a protocol error to omit a head in a configuration. Then, the client can apply or test the configuration. The compositor will then reply with a succeeded, failed or cancelled event. Finally the client should destroy the configuration object. Enable a head. This request creates a head configuration object that can be used to change the head's properties. Disable a head. Apply the new output configuration. In case the configuration is successfully applied, there is no guarantee that the new output state matches completely the requested configuration. For instance, a compositor might round the scale if it doesn't support fractional scaling. After this request has been sent, the compositor must respond with an succeeded, failed or cancelled event. Sending a request that isn't the destructor is a protocol error. Test the new output configuration. The configuration won't be applied, but will only be validated. Even if the compositor succeeds to test a configuration, applying it may fail. After this request has been sent, the compositor must respond with an succeeded, failed or cancelled event. Sending a request that isn't the destructor is a protocol error. Sent after the compositor has successfully applied the changes or tested them. Upon receiving this event, the client should destroy this object. If the current configuration has changed, events to describe the changes will be sent followed by a wlr_output_manager.done event. Sent if the compositor rejects the changes or failed to apply them. The compositor should revert any changes made by the apply request that triggered this event. Upon receiving this event, the client should destroy this object. Sent if the compositor cancels the configuration because the state of an output changed and the client has outdated information (e.g. after an output has been hotplugged). The client can create a new configuration with a newer serial and try again. Upon receiving this event, the client should destroy this object. Using this request a client can tell the compositor that it is not going to use the configuration object anymore. Any changes to the outputs that have not been applied will be discarded. This request also destroys wlr_output_configuration_head objects created via this object. This object is used by the client to update a single head's configuration. It is a protocol error to set the same property twice. This request sets the head's mode. This request assigns a custom mode to the head. The size is given in physical hardware units of the output device. If set to zero, the refresh rate is unspecified. It is a protocol error to set both a mode and a custom mode. This request sets the head's position in the global compositor space. This request sets the head's transform. This request sets the head's scale. This request enables/disables adaptive sync. Adaptive sync is also known as Variable Refresh Rate or VRR. budgie-desktop-services/src/sys/000077500000000000000000000000001513320106000172015ustar00rootroot00000000000000budgie-desktop-services/src/sys/SysInfo.cpp000066400000000000000000000013541513320106000213020ustar00rootroot00000000000000#include "SysInfo.hpp" #include namespace bd { SysInfo::SysInfo(QObject* parent) : QObject(parent) { m_machine_id = QString {QSysInfo::machineUniqueId()}; m_shim_mode = false; QByteArray budgie_session_version = qgetenv("BUDGIE_SESSION_VERSION"); if (!budgie_session_version.isEmpty() && !budgie_session_version.isNull()) { QString version = QString::fromLocal8Bit(budgie_session_version); m_shim_mode = version.startsWith("10.10", Qt::CaseInsensitive); } } SysInfo& SysInfo::instance() { static SysInfo _instance(nullptr); return _instance; } bool SysInfo::isShimMode() { return m_shim_mode; } QString SysInfo::getMachineId() { return m_machine_id; } } // bd budgie-desktop-services/src/sys/SysInfo.hpp000066400000000000000000000007361513320106000213120ustar00rootroot00000000000000#pragma once #include namespace bd { class SysInfo : public QObject { Q_OBJECT Q_PROPERTY(QString machineId READ getMachineId) Q_PROPERTY(bool shimMode READ isShimMode) public: SysInfo(QObject* parent); static SysInfo& instance(); static SysInfo* create() { return &instance(); } QString getMachineId(); bool isShimMode(); private: QString m_machine_id; bool m_shim_mode; }; } // bd