Compare commits

..

18 Commits

Author SHA1 Message Date
vaxerski
12f9a0d0b9 [gha] Nix: update inputs 2024-11-19 21:47:18 +00:00
Vaxry
fab0f426b5 version: bump to 0.45.2 2024-11-19 21:45:40 +00:00
Vaxry
e735eae4ad xdg-shell: don't report invalid min/max sizes on unset
fixes #8522
2024-11-19 21:45:20 +00:00
Aqa-Ib
57cf6d81a9 internal: fix changeWindowZOrder reordering incorrectly (#8494) 2024-11-19 21:45:20 +00:00
Vaxry
77b9d03c3f version: bump to 0.45.1 2024-11-18 14:27:21 +00:00
johannes hanika
53e8513000 constraints: don't warp pointer position on release (#8491)
this was annoying for nuklear properties/ui slider elements that grab
the pointer via GLFW_CURSOR_DISABLE to allow more range and finer control.
upon mouse release, the pointer is reset to the middle of the window
without this patch, making long mouse movements necessary to go back
to the original position for readjustments. fwiw the new behaviour
is consistent with x11 and weston.
2024-11-18 14:26:44 +00:00
Vaxry
7976bfa2df shell: don't use fgrep, prefer grep -F 2024-11-18 14:26:44 +00:00
Vaxry
a77ffa8cb8 windows/xdg: minor cleanup of min/max size calculations
fixes #8495
2024-11-18 14:26:44 +00:00
Vaxry
a4a1ad1f9b hyprpm: fix format crash
ref #8487
2024-11-18 14:26:44 +00:00
Vaxry
737b51d032 renderer: don't render unmapped popups
fixes #8485
2024-11-18 14:26:44 +00:00
staz
b3251f2961 workspacerules: Do not check 'on-created-empty' if using a workspace windowrule (#8486) 2024-11-18 14:26:44 +00:00
Vaxry
c4a77b8da7 core: guard pmonitor in focuswindow
may be null

fixes #8483
2024-11-18 14:26:44 +00:00
sslater11
38b6f3babb workspace: fix missing name via focusworkspaceoncurrentmonitor (#8484) 2024-11-18 14:26:44 +00:00
Vaxry
ace7ece4f2 protocols: mark primarySelection as not privileged
fixes #8479
2024-11-18 14:26:44 +00:00
Tom Englund
0557b2ed8c xcursors: store themes in a std:set to order it (#8474)
using a unordered_set means its store based on a hash_value meaning
currently it can end up loading inherited themes before the actual theme
itself depending on the hash of the theme name used, reason for using
set at all over vector is to keep unique members and not foreverever
looping broken inherit themeing.
2024-11-18 14:26:44 +00:00
Vaxry
9728a39b2e makefile: add stub to discourage direct make 2024-11-18 14:26:44 +00:00
dawsers
7120dde3d1 renderer: scaled surfaces could have zero area (#8423) 2024-11-18 14:26:44 +00:00
Vaxry
e7ab2d8533 defaultConfig: fixup smart gaps rules 2024-11-18 14:26:44 +00:00
505 changed files with 24841 additions and 45190 deletions

View File

@@ -1,101 +0,0 @@
WarningsAsErrors: '*'
HeaderFilterRegex: '.*\.hpp'
FormatStyle: file
Checks: >
-*,
bugprone-*,
-bugprone-easily-swappable-parameters,
-bugprone-forward-declararion-namespace,
-bugprone-forward-declararion-namespace,
-bugprone-macro-parentheses,
-bugprone-narrowing-conversions,
-bugprone-branch-clone,
-bugprone-assignment-in-if-condition,
concurrency-*,
-concurrency-mt-unsafe,
cppcoreguidelines-*,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-avoid-const-or-ref-data-members,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-avoid-goto,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-avoid-do-while,
-cppcoreguidelines-avoid-non-const-global-variables,
-cppcoreguidelines-special-member-functions,
-cppcoreguidelines-explicit-virtual-functions,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-member-init,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-macro-to-enum,
-cppcoreguidelines-init-variables,
-cppcoreguidelines-pro-type-cstyle-cast,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-pro-type-reinterpret-cast,
google-global-names-in-headers,
-google-readability-casting,
google-runtime-operator,
misc-*,
-misc-unused-parameters,
-misc-no-recursion,
-misc-non-private-member-variables-in-classes,
-misc-include-cleaner,
-misc-use-anonymous-namespace,
-misc-const-correctness,
modernize-*,
-modernize-return-braced-init-list,
-modernize-use-trailing-return-type,
-modernize-use-using,
-modernize-use-override,
-modernize-avoid-c-arrays,
-modernize-macro-to-enum,
-modernize-loop-convert,
-modernize-use-nodiscard,
-modernize-pass-by-value,
-modernize-use-auto,
performance-*,
-performance-avoid-endl,
-performance-unnecessary-value-param,
portability-std-allocator-const,
readability-*,
-readability-function-cognitive-complexity,
-readability-function-size,
-readability-identifier-length,
-readability-magic-numbers,
-readability-uppercase-literal-suffix,
-readability-braces-around-statements,
-readability-redundant-access-specifiers,
-readability-else-after-return,
-readability-container-data-pointer,
-readability-implicit-bool-conversion,
-readability-avoid-nested-conditional-operator,
-readability-redundant-member-init,
-readability-redundant-string-init,
-readability-avoid-const-params-in-decls,
-readability-named-parameter,
-readability-convert-member-functions-to-static,
-readability-qualified-auto,
-readability-make-member-function-const,
-readability-isolate-declaration,
-readability-inconsistent-declaration-parameter-name,
-clang-diagnostic-error,
CheckOptions:
performance-for-range-copy.WarnOnAllAutoCopies: true
performance-inefficient-string-concatenation.StrictMode: true
readability-braces-around-statements.ShortStatementLines: 0
readability-identifier-naming.ClassCase: CamelCase
readability-identifier-naming.ClassIgnoredRegexp: I.*
readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!?
readability-identifier-naming.EnumCase: CamelCase
readability-identifier-naming.EnumPrefix: e
readability-identifier-naming.EnumConstantCase: UPPER_CASE
readability-identifier-naming.FunctionCase: camelBack
readability-identifier-naming.NamespaceCase: CamelCase
readability-identifier-naming.NamespacePrefix: N
readability-identifier-naming.StructPrefix: S
readability-identifier-naming.StructCase: CamelCase

View File

@@ -1,15 +1,75 @@
name: Do not open issues, go to discussions please! name: Bug Report
description: Do not open an issue description: Something is not working right
labels: ["bug"]
body: body:
- type: checkboxes - type: checkboxes
attributes: attributes:
label: Please close this issue. label: Already reported ? *
description: Users cannot open issues. I want my issue to be closed. description: Before opening a new bug report, please take a moment to search through the current open and closed issues to check if it already exists.
options: options:
- label: Yes, I want this issue to be closed. - label: I have searched the existing open and closed issues.
required: true required: true
- type: dropdown
id: type
attributes:
label: Regression?
description: |
Regression means that something used to work but no longer does.
**BEFORE CONTINUING**, please check if this bug is a regression or not, and if it is, we need you to bisect with the help of the wiki: https://wiki.hyprland.org/Crashes-and-Bugs/#bisecting-an-issue
multiple: true
options:
- "Yes"
- "No"
validations:
required: true
- type: textarea - type: textarea
id: body id: ver
attributes: attributes:
label: Issue body label: System Info and Version
description: |
Paste the output of `hyprctl systeminfo -c` here. If you can't
launch Hyprland, paste the output of `Hyprland --systeminfo`.
If `Hyprland --systeminfo` errors out (added in 0.44.0), find
and paste the Hyprland version manually.
value: "<details>
<summary>System/Version info</summary>
```sh
<Paste the output of the command here>
```
</details>"
validations:
required: true
- type: textarea
id: desc
attributes:
label: Description
description: "What went wrong?"
validations:
required: true
- type: textarea
id: repro
attributes:
label: How to reproduce
description: "How can someone else reproduce the issue?"
validations:
required: true
- type: textarea
id: logs
attributes:
label: Crash reports, logs, images, videos
description: |
Anything that can help. Please always ATTACH and not paste them.
Logs can be found in $XDG_RUNTIME_DIR/hypr
Crash reports are stored in ~/.cache/hyprland or $XDG_CACHE_HOME/hyprland

19
.github/ISSUE_TEMPLATE/feature.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: Feature Request
description: I'd like to request additional functionality
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Before opening a new issue, take a moment to search through the current open ones.
---
- type: textarea
id: desc
attributes:
label: Description
description: "Describe your idea"
validations:
required: true

View File

@@ -20,7 +20,6 @@ runs:
clang \ clang \
cmake \ cmake \
git \ git \
glaze \
glm \ glm \
glslang \ glslang \
go \ go \
@@ -34,10 +33,7 @@ runs:
libfontenc \ libfontenc \
libglvnd \ libglvnd \
libinput \ libinput \
libjxl \
libliftoff \ libliftoff \
libspng \
libwebp \
libxcursor \ libxcursor \
libxcvt \ libxcvt \
libxfont2 \ libxfont2 \
@@ -62,8 +58,7 @@ runs:
xcb-util \ xcb-util \
xcb-util-image \ xcb-util-image \
libzip \ libzip \
librsvg \ librsvg
re2
- name: Get hyprwayland-scanner-git - name: Get hyprwayland-scanner-git
shell: bash shell: bash
@@ -74,11 +69,6 @@ runs:
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
cmake --install build cmake --install build
- name: Get hyprgraphics-git
shell: bash
run: |
git clone https://github.com/hyprwm/hyprgraphics && cd hyprgraphics && cmake -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -B build && cmake --build build --target hyprgraphics && cmake --install build
- name: Get hyprutils-git - name: Get hyprutils-git
shell: bash shell: bash
run: | run: |

View File

@@ -1,9 +1,3 @@
<!--
BEFORE you submit your PR, please check out the PR guidelines
on our wiki: https://wiki.hyprland.org/Contributing-and-Debugging/PR-Guidelines/
-->
#### Describe your PR, what does it fix/add? #### Describe your PR, what does it fix/add?

View File

@@ -21,7 +21,7 @@ jobs:
- name: Build Hyprland - name: Build Hyprland
run: | run: |
CFLAGS=-Werror CXXFLAGS=-Werror make all make all
- name: Compress and package artifacts - name: Compress and package artifacts
run: | run: |
@@ -36,7 +36,7 @@ jobs:
cp build/Hyprland hyprland/ cp build/Hyprland hyprland/
cp -r example/ hyprland/ cp -r example/ hyprland/
cp -r assets/ hyprland/ cp -r assets/ hyprland/
tar -cvJf Hyprland.tar.xz hyprland tar -cvf Hyprland.tar.xz hyprland
- name: Release - name: Release
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -107,7 +107,6 @@ jobs:
run: make release run: make release
clang-format: clang-format:
permissions: read-all
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork
name: "Code Style (Arch)" name: "Code Style (Arch)"
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -1,48 +0,0 @@
name: clang-format
on: pull_request_target
jobs:
clang-format:
permissions: write-all
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork
name: "Code Style (Arch)"
runs-on: ubuntu-latest
container:
image: archlinux
steps:
- name: Checkout repository actions
uses: actions/checkout@v4
with:
sparse-checkout: .github/actions
- name: Setup base
uses: ./.github/actions/setup_base
- name: Configure
run: meson setup build -Ddefault_library=static
- name: clang-format check
run: ninja -C build clang-format-check
- name: clang-format apply
if: ${{ failure() && github.event_name == 'pull_request' }}
run: ninja -C build clang-format
- name: Create patch
if: ${{ failure() && github.event_name == 'pull_request' }}
run: |
echo 'Please fix the formatting issues by running [`clang-format`](https://wiki.hyprland.org/Contributing-and-Debugging/PR-Guidelines/#code-style), or directly apply this patch:' > clang-format.patch
echo '<details>' >> clang-format.patch
echo '<summary>clang-format.patch</summary>' >> clang-format.patch
echo >> clang-format.patch
echo '```diff' >> clang-format.patch
git diff >> clang-format.patch
echo '```' >> clang-format.patch
echo >> clang-format.patch
echo '</details>' >> clang-format.patch
- name: Comment patch
if: ${{ failure() && github.event_name == 'pull_request' }}
uses: mshick/add-pr-comment@v2
with:
message-path: |
clang-format.patch

View File

@@ -1,101 +0,0 @@
name: Close Unauthorized Issues
on:
workflow_dispatch:
issues:
types: [opened]
jobs:
close-unauthorized-issues:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
# XXX: This *could* be done in Bash by abusing GitHub's own tool to interact with its API
# but that's too much of a hack, and we'll be adding a layer of abstraction. github-script
# is a workflow that eases interaction with GitHub API in the workflow run context.
- name: "Close 'unauthorized' issues"
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const ALLOWED_USERS = ['vaxerski', 'fufexan', 'NotAShelf'];
const CLOSING_COMMENT = 'Users are no longer allowed to open issues themselves, please open a discussion instead.\n\nPlease see the [wiki](https://wiki.hyprland.org/Contributing-and-Debugging/Issue-Guidelines/) on why this is the case.\n\nWe are volunteers, and we need your cooperation to make the best software we can. Thank you for understanding! ❤️\n\n[Open a discussion here](https://github.com/hyprwm/Hyprland/discussions)';
async function closeUnauthorizedIssue(issueNumber, userName) {
if (ALLOWED_USERS.includes(userName)) {
console.log(`Issue #${issueNumber} - Created by authorized user ${userName}`);
return;
}
console.log(`Issue #${issueNumber} - Unauthorized, closing`);
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
state: 'closed',
state_reason: 'not_planned'
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: CLOSING_COMMENT
});
}
if (context.eventName === 'issues' && context.payload.action === 'opened') {
// Direct access to the issue that triggered the workflow
const issue = context.payload.issue;
// Skip if this is a PR
if (issue.pull_request) {
console.log(`Issue #${issue.number} - Skipping, this is a pull request`);
return;
}
// Process the single issue that triggered the workflow
await closeUnauthorizedIssue(issue.number, issue.user.login);
} else {
// For manual runs, we need to handle pagination
async function* fetchAllOpenIssues() {
let page = 1;
let hasNextPage = true;
while (hasNextPage) {
const response = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100,
page: page
});
if (response.data.length === 0) {
hasNextPage = false;
} else {
for (const issue of response.data) {
yield issue;
}
page++;
}
}
}
// Process issues one by one
for await (const issue of fetchAllOpenIssues()) {
try {
// Skip pull requests
if (issue.pull_request) {
console.log(`Issue #${issue.number} - Skipping, this is a pull request`);
continue;
}
await closeUnauthorizedIssue(issue.number, issue.user.login);
} catch (error) {
console.error(`Error processing issue #${issue.number}: ${error.message}`);
}
}
}

28
.github/workflows/nix-build.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: Nix (Build)
on:
workflow_call:
secrets:
CACHIX_AUTH_TOKEN:
required: false
jobs:
build:
strategy:
matrix:
package:
- hyprland
- hyprland-cross
- xdg-desktop-portal-hyprland
runs-on: ubuntu-latest
steps:
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- uses: cachix/cachix-action@v15
with:
name: hyprland
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- run: nix build 'github:hyprwm/Hyprland?ref=${{ github.ref }}#${{ matrix.package }}' -L --extra-substituters "https://hyprland.cachix.org"

View File

@@ -1,4 +1,4 @@
name: Nix name: Nix (CI)
on: [push, pull_request, workflow_dispatch] on: [push, pull_request, workflow_dispatch]
@@ -8,23 +8,7 @@ jobs:
uses: ./.github/workflows/nix-update-inputs.yml uses: ./.github/workflows/nix-update-inputs.yml
secrets: inherit secrets: inherit
hyprland: build:
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork) if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)
uses: ./.github/workflows/nix.yml uses: ./.github/workflows/nix-build.yml
secrets: inherit
with:
command: nix build 'github:hyprwm/Hyprland?ref=${{ github.ref }}' -L --extra-substituters "https://hyprland.cachix.org"
xdph:
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)
needs: hyprland
uses: ./.github/workflows/nix.yml
secrets: inherit
with:
command: nix build 'github:hyprwm/Hyprland?ref=${{ github.ref }}#xdg-desktop-portal-hyprland' -L --extra-substituters "https://hyprland.cachix.org"
test:
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)
needs: hyprland
uses: ./.github/workflows/nix-test.yml
secrets: inherit secrets: inherit

View File

@@ -1,59 +0,0 @@
name: Nix (Test)
on:
workflow_call:
secrets:
CACHIX_AUTH_TOKEN:
required: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Install Nix
uses: nixbuild/nix-quick-install-action@v31
with:
nix_conf: |
keep-env-derivations = true
keep-outputs = true
- name: Restore and save Nix store
uses: nix-community/cache-nix-action@v6
with:
# restore and save a cache using this key
primary-key: nix-${{ runner.os }}
# if there's no cache hit, restore a cache by this prefix
restore-prefixes-first-match: nix-${{ runner.os }}
# collect garbage until the Nix store size (in bytes) is at most this number
# before trying to save a new cache
# 1G = 1073741824
gc-max-store-size-linux: 5G
# do purge caches
purge: true
# purge all versions of the cache
purge-prefixes: nix-${{ runner.os }}
# created more than this number of seconds ago
purge-created: 0
# or, last accessed more than this number of seconds ago
# relative to the start of the `Post Restore and save Nix store` phase
purge-last-accessed: 0
# except any version with the key that is the same as the `primary-key`
purge-primary-key: never
- uses: cachix/cachix-action@v15
with:
name: hyprland
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Run test VM
run: nix build 'github:hyprwm/Hyprland?ref=${{ github.ref }}#checks.x86_64-linux.tests' -L --extra-substituters "https://hyprland.cachix.org"
- name: Check exit status
run: grep 0 result/exit_status
- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: logs
path: result

View File

@@ -17,36 +17,7 @@ jobs:
with: with:
token: ${{ secrets.PAT }} token: ${{ secrets.PAT }}
- name: Install Nix - uses: DeterminateSystems/nix-installer-action@main
uses: nixbuild/nix-quick-install-action@v31
with:
nix_conf: |
keep-env-derivations = true
keep-outputs = true
- name: Restore and save Nix store
uses: nix-community/cache-nix-action@v6
with:
# restore and save a cache using this key
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
# if there's no cache hit, restore a cache by this prefix
restore-prefixes-first-match: nix-${{ runner.os }}-
# collect garbage until the Nix store size (in bytes) is at most this number
# before trying to save a new cache
# 1G = 1073741824
gc-max-store-size-linux: 1G
# do purge caches
purge: true
# purge all versions of the cache
purge-prefixes: nix-${{ runner.os }}-
# created more than this number of seconds ago
purge-created: 0
# or, last accessed more than this number of seconds ago
# relative to the start of the `Post Restore and save Nix store` phase
purge-last-accessed: 0
# except any version with the key that is the same as the `primary-key`
purge-primary-key: never
- name: Update inputs - name: Update inputs
run: nix/update-inputs.sh run: nix/update-inputs.sh

View File

@@ -1,53 +0,0 @@
name: Build
on:
workflow_call:
inputs:
command:
required: true
type: string
description: Command to run
secrets:
CACHIX_AUTH_TOKEN:
required: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Install Nix
uses: nixbuild/nix-quick-install-action@v31
with:
nix_conf: |
keep-env-derivations = true
keep-outputs = true
- name: Restore and save Nix store
uses: nix-community/cache-nix-action@v6
with:
# restore and save a cache using this key
primary-key: nix-${{ runner.os }}
# if there's no cache hit, restore a cache by this prefix
restore-prefixes-first-match: nix-${{ runner.os }}
# collect garbage until the Nix store size (in bytes) is at most this number
# before trying to save a new cache
# 1G = 1073741824
gc-max-store-size-linux: 5G
# do purge caches
purge: true
# purge all versions of the cache
purge-prefixes: nix-${{ runner.os }}
# created more than this number of seconds ago
purge-created: 0
# or, last accessed more than this number of seconds ago
# relative to the start of the `Post Restore and save Nix store` phase
purge-last-accessed: 0
# except any version with the key that is the same as the `primary-key`
purge-primary-key: never
- uses: cachix/cachix-action@v15
with:
name: hyprland
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- run: ${{ inputs.command }}

28
.github/workflows/stale.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
#
# You can adjust the behavior by modifying this file.
# For more information, see:
# https://github.com/actions/stale
name: Mark stale issues and pull requests
on:
schedule:
- cron: "7 */4 * * *"
workflow_dispatch:
jobs:
stale:
if: github.repository == 'hyprwm/Hyprland'
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.STALEBOT_PAT }}
stale-issue-label: "stale"
stale-pr-label: "stale"
operations-per-run: 40
days-before-close: -1

2
.gitignore vendored
View File

@@ -28,8 +28,6 @@ protocols/*.c*
protocols/*.h* protocols/*.h*
.ccls-cache .ccls-cache
*.so *.so
src/render/shaders/*.inc
src/render/shaders/Shaders.hpp
hyprctl/hyprctl hyprctl/hyprctl

View File

@@ -9,7 +9,6 @@ project(
DESCRIPTION "A Modern C++ Wayland Compositor" DESCRIPTION "A Modern C++ Wayland Compositor"
VERSION ${VER}) VERSION ${VER})
include(CTest)
include(CheckIncludeFile) include(CheckIncludeFile)
include(GNUInstallDirs) include(GNUInstallDirs)
@@ -26,9 +25,6 @@ message(STATUS "Gathering git info")
# Get git info hash and branch # Get git info hash and branch
execute_process(COMMAND ./scripts/generateVersion.sh execute_process(COMMAND ./scripts/generateVersion.sh
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
# Make shader files includable
execute_process(COMMAND ./scripts/generateShaderIncludes.sh
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
@@ -81,18 +77,14 @@ add_compile_definitions(HYPRLAND_VERSION="${HYPRLAND_VERSION}")
include_directories(. "src/" "protocols/") include_directories(. "src/" "protocols/")
set(CMAKE_CXX_STANDARD 26) set(CMAKE_CXX_STANDARD 26)
set(CXX_STANDARD_REQUIRED ON)
add_compile_options( add_compile_options(
-Wall -Wall
-Wextra -Wextra
-Wpedantic
-Wno-unused-parameter -Wno-unused-parameter
-Wno-unused-value -Wno-unused-value
-Wno-missing-field-initializers -Wno-missing-field-initializers
-Wno-gnu-zero-variadic-macro-arguments
-Wno-narrowing -Wno-narrowing
-Wno-pointer-arith -Wno-pointer-arith
-Wno-clobbered
-fmacro-prefix-map=${CMAKE_SOURCE_DIR}/=) -fmacro-prefix-map=${CMAKE_SOURCE_DIR}/=)
set(CMAKE_EXECUTABLE_ENABLE_EXPORTS TRUE) set(CMAKE_EXECUTABLE_ENABLE_EXPORTS TRUE)
@@ -102,28 +94,18 @@ message(STATUS "Checking deps...")
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
set(GLES_VERSION "GLES3") if(LEGACY_RENDERER)
set(GLES_VERSION "GLES2")
else()
set(GLES_VERSION "GLES3")
endif()
find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION}) find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION})
pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=0.9.0) pkg_check_modules(hyprctl_deps REQUIRED IMPORTED_TARGET hyprutils>=0.2.1)
pkg_check_modules(hyprlang_dep REQUIRED IMPORTED_TARGET hyprlang>=0.3.2)
pkg_check_modules(hyprcursor_dep REQUIRED IMPORTED_TARGET hyprcursor>=0.1.7)
pkg_check_modules(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=0.8.2)
pkg_check_modules(hyprgraphics_dep REQUIRED IMPORTED_TARGET hyprgraphics>=0.1.3)
string(REPLACE "." ";" AQ_VERSION_LIST ${aquamarine_dep_VERSION}) pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=0.4.2)
list(GET AQ_VERSION_LIST 0 AQ_VERSION_MAJOR)
list(GET AQ_VERSION_LIST 1 AQ_VERSION_MINOR)
list(GET AQ_VERSION_LIST 2 AQ_VERSION_PATCH)
add_compile_definitions(AQUAMARINE_VERSION="${aquamarine_dep_VERSION}") add_compile_definitions(AQUAMARINE_VERSION="${aquamarine_dep_VERSION}")
add_compile_definitions(AQUAMARINE_VERSION_MAJOR=${AQ_VERSION_MAJOR})
add_compile_definitions(AQUAMARINE_VERSION_MINOR=${AQ_VERSION_MINOR})
add_compile_definitions(AQUAMARINE_VERSION_PATCH=${AQ_VERSION_PATCH})
add_compile_definitions(HYPRLANG_VERSION="${hyprlang_dep_VERSION}")
add_compile_definitions(HYPRUTILS_VERSION="${hyprutils_dep_VERSION}")
add_compile_definitions(HYPRCURSOR_VERSION="${hyprcursor_dep_VERSION}")
add_compile_definitions(HYPRGRAPHICS_VERSION="${hyprgraphics_dep_VERSION}")
pkg_check_modules( pkg_check_modules(
deps deps
@@ -132,17 +114,19 @@ pkg_check_modules(
xkbcommon xkbcommon
uuid uuid
wayland-server>=1.22.90 wayland-server>=1.22.90
wayland-protocols>=1.43 wayland-protocols
cairo cairo
pango pango
pangocairo pangocairo
pixman-1 pixman-1
xcursor xcursor
libdrm libdrm
libinput>=1.28 libinput
gbm gbm
gio-2.0 gio-2.0
re2) hyprlang>=0.3.2
hyprcursor>=0.1.7
hyprutils>=0.2.3)
find_package(hyprwayland-scanner 0.3.10 REQUIRED) find_package(hyprwayland-scanner 0.3.10 REQUIRED)
@@ -156,7 +140,7 @@ endif()
add_executable(Hyprland ${SRCFILES} ${TRACY_CPP_FILES}) add_executable(Hyprland ${SRCFILES} ${TRACY_CPP_FILES})
set(USE_GPROF OFF) set(USE_GPROF ON)
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
message(STATUS "Setting debug flags") message(STATUS "Setting debug flags")
@@ -209,10 +193,9 @@ if(NOT HAS_TIMERFD AND epoll_FOUND)
target_link_libraries(Hyprland PkgConfig::epoll) target_link_libraries(Hyprland PkgConfig::epoll)
endif() endif()
check_include_file("sys/inotify.h" HAS_INOTIFY) if(LEGACY_RENDERER)
pkg_check_modules(inotify IMPORTED_TARGET libinotify) message(STATUS "Using the legacy GLES2 renderer!")
if(NOT HAS_INOTIFY AND inotify_FOUND) add_compile_definitions(LEGACY_RENDERER)
target_link_libraries(Hyprland PkgConfig::inotify)
endif() endif()
if(NO_XWAYLAND) if(NO_XWAYLAND)
@@ -239,15 +222,16 @@ if(NO_SYSTEMD)
else() else()
message(STATUS "SYSTEMD support is requested (NO_SYSTEMD not defined)...") message(STATUS "SYSTEMD support is requested (NO_SYSTEMD not defined)...")
add_compile_definitions(USES_SYSTEMD) add_compile_definitions(USES_SYSTEMD)
configure_file(systemd/hyprland-session.service.in
systemd/hyprland-session.service @ONLY)
# session file -uwsm # session file -systemd
if(NO_UWSM) install(FILES ${CMAKE_SOURCE_DIR}/systemd/hyprland-systemd.desktop
message(STATUS "UWSM support is disabled...") DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/wayland-sessions)
else()
message(STATUS "UWSM support is enabled (NO_UWSM not defined)...") # install systemd service
install(FILES ${CMAKE_SOURCE_DIR}/systemd/hyprland-uwsm.desktop install(FILES ${CMAKE_BINARY_DIR}/systemd/hyprland-session.service
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/wayland-sessions) DESTINATION ${CMAKE_INSTALL_LIBDIR}/systemd/user)
endif()
endif() endif()
set(CPACK_PROJECT_NAME ${PROJECT_NAME}) set(CPACK_PROJECT_NAME ${PROJECT_NAME})
@@ -261,15 +245,7 @@ target_precompile_headers(Hyprland PRIVATE
message(STATUS "Setting link libraries") message(STATUS "Setting link libraries")
target_link_libraries( target_link_libraries(Hyprland rt PkgConfig::aquamarine_dep PkgConfig::deps)
Hyprland
rt
PkgConfig::aquamarine_dep
PkgConfig::hyprlang_dep
PkgConfig::hyprutils_dep
PkgConfig::hyprcursor_dep
PkgConfig::hyprgraphics_dep
PkgConfig::deps)
if(udis_dep_FOUND) if(udis_dep_FOUND)
target_link_libraries(Hyprland PkgConfig::udis_dep) target_link_libraries(Hyprland PkgConfig::udis_dep)
else() else()
@@ -311,7 +287,7 @@ endfunction()
target_link_libraries(Hyprland OpenGL::EGL OpenGL::GL Threads::Threads) target_link_libraries(Hyprland OpenGL::EGL OpenGL::GL Threads::Threads)
pkg_check_modules(hyprland_protocols_dep hyprland-protocols>=0.6.4) pkg_check_modules(hyprland_protocols_dep hyprland-protocols>=0.4.0)
if(hyprland_protocols_dep_FOUND) if(hyprland_protocols_dep_FOUND)
pkg_get_variable(HYPRLAND_PROTOCOLS hyprland-protocols pkgdatadir) pkg_get_variable(HYPRLAND_PROTOCOLS hyprland-protocols pkgdatadir)
message(STATUS "hyprland-protocols dependency set to ${HYPRLAND_PROTOCOLS}") message(STATUS "hyprland-protocols dependency set to ${HYPRLAND_PROTOCOLS}")
@@ -337,14 +313,8 @@ protocolnew("protocols" "kde-server-decoration" true)
protocolnew("protocols" "wlr-data-control-unstable-v1" true) protocolnew("protocols" "wlr-data-control-unstable-v1" true)
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-focus-grab-v1" true) protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-focus-grab-v1" true)
protocolnew("protocols" "wlr-layer-shell-unstable-v1" true) protocolnew("protocols" "wlr-layer-shell-unstable-v1" true)
protocolnew("protocols" "xx-color-management-v4" true)
protocolnew("protocols" "frog-color-management-v1" true)
protocolnew("protocols" "wayland-drm" true) protocolnew("protocols" "wayland-drm" true)
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-ctm-control-v1" true) protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-ctm-control-v1" true)
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-surface-v1" true)
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-lock-notify-v1" true)
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-toplevel-mapping-v1"
true)
protocolnew("staging/tearing-control" "tearing-control-v1" false) protocolnew("staging/tearing-control" "tearing-control-v1" false)
protocolnew("staging/fractional-scale" "fractional-scale-v1" false) protocolnew("staging/fractional-scale" "fractional-scale-v1" false)
@@ -377,31 +347,12 @@ protocolnew("staging/linux-drm-syncobj" "linux-drm-syncobj-v1" false)
protocolnew("staging/xdg-dialog" "xdg-dialog-v1" false) protocolnew("staging/xdg-dialog" "xdg-dialog-v1" false)
protocolnew("staging/single-pixel-buffer" "single-pixel-buffer-v1" false) protocolnew("staging/single-pixel-buffer" "single-pixel-buffer-v1" false)
protocolnew("staging/security-context" "security-context-v1" false) protocolnew("staging/security-context" "security-context-v1" false)
protocolnew("staging/content-type" "content-type-v1" false)
protocolnew("staging/color-management" "color-management-v1" false)
protocolnew("staging/xdg-toplevel-tag" "xdg-toplevel-tag-v1" false)
protocolnew("staging/xdg-system-bell" "xdg-system-bell-v1" false)
protocolnew("staging/ext-workspace" "ext-workspace-v1" false)
protocolnew("staging/ext-data-control" "ext-data-control-v1" false)
protocolwayland() protocolwayland()
# tools # tools
add_subdirectory(hyprctl) add_subdirectory(hyprctl)
add_subdirectory(hyprpm)
if(NO_HYPRPM)
message(STATUS "hyprpm is disabled")
else()
add_subdirectory(hyprpm)
message(STATUS "hyprpm is enabled (NO_HYPRPM not defined)")
endif()
if(NO_TESTS)
message(STATUS "building tests is disabled")
else()
message(STATUS "building tests is enabled (NO_TESTS not defined)")
add_subdirectory(hyprtester)
endif()
# binary and symlink # binary and symlink
install(TARGETS Hyprland) install(TARGETS Hyprland)
@@ -412,6 +363,7 @@ install(
${CMAKE_INSTALL_FULL_BINDIR}/Hyprland \ ${CMAKE_INSTALL_FULL_BINDIR}/Hyprland \
\"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_BINDIR}/hyprland\" \ \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_BINDIR}/hyprland\" \
)") )")
# session file # session file
install(FILES ${CMAKE_SOURCE_DIR}/example/hyprland.desktop install(FILES ${CMAKE_SOURCE_DIR}/example/hyprland.desktop
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/wayland-sessions) DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/wayland-sessions)
@@ -455,19 +407,4 @@ install(
DIRECTORY ${HEADERS_SRC} DIRECTORY ${HEADERS_SRC}
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hyprland DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hyprland
FILES_MATCHING FILES_MATCHING
PATTERN "*.h" PATTERN "*.h*")
PATTERN "*.hpp"
PATTERN "*.inc")
if(TESTS)
enable_testing()
add_custom_target(tests)
add_subdirectory(hyprtester)
add_test(
NAME "Main Test"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/hyprtester
COMMAND hyprtester)
add_dependencies(tests hyprtester)
endif()

View File

@@ -1,6 +1,6 @@
BSD 3-Clause License BSD 3-Clause License
Copyright (c) 2022-2025, vaxerski Copyright (c) 2022-2024, vaxerski
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

View File

@@ -3,6 +3,14 @@ PREFIX = /usr/local
stub: stub:
@echo "Do not run $(MAKE) directly without any arguments. Please refer to the wiki on how to compile Hyprland." @echo "Do not run $(MAKE) directly without any arguments. Please refer to the wiki on how to compile Hyprland."
legacyrenderer:
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -DLEGACY_RENDERER:BOOL=true -S . -B ./build
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
legacyrendererdebug:
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -DLEGACY_RENDERER:BOOL=true -S . -B ./build
cmake --build ./build --config Debug --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
release: release:
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -S . -B ./build cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -S . -B ./build
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
@@ -44,7 +52,7 @@ installheaders:
cmake --build ./build --config Release --target generate-protocol-headers cmake --build ./build --config Release --target generate-protocol-headers
find src -type f \( -name '*.hpp' -o -name '*.h' -o -name '*.inc' \) -print0 | cpio --quiet -0dump ${PREFIX}/include/hyprland find src -name '*.h*' -print0 | cpio --quiet -0dump ${PREFIX}/include/hyprland
cp ./protocols/*.h* ${PREFIX}/include/hyprland/protocols cp ./protocols/*.h* ${PREFIX}/include/hyprland/protocols
cp ./build/hyprland.pc ${PREFIX}/share/pkgconfig cp ./build/hyprland.pc ${PREFIX}/share/pkgconfig
if [ -d /usr/share/pkgconfig ]; then cp ./build/hyprland.pc /usr/share/pkgconfig 2>/dev/null || true; fi if [ -d /usr/share/pkgconfig ]; then cp ./build/hyprland.pc /usr/share/pkgconfig 2>/dev/null || true; fi
@@ -92,7 +100,3 @@ asan:
@echo "Hyprland done" @echo "Hyprland done"
ASAN_OPTIONS="detect_odr_violation=0,log_path=asan.log" HYPRLAND_NO_CRASHREPORTER=1 ./build/Hyprland -c ~/.config/hypr/hyprland.conf ASAN_OPTIONS="detect_odr_violation=0,log_path=asan.log" HYPRLAND_NO_CRASHREPORTER=1 ./build/Hyprland -c ~/.config/hypr/hyprland.conf
test:
$(MAKE) debug
./build/hyprtester/hyprtester -c hyprtester/test.conf -b ./build/Hyprland -p hyprtester/plugin/hyprtestplugin.so

View File

@@ -4,7 +4,7 @@
<br> <br>
[![Badge Workflow]][Workflow] ![Badge Workflow]
[![Badge License]][License] [![Badge License]][License]
![Badge Language] ![Badge Language]
[![Badge Pull Requests]][Pull Requests] [![Badge Pull Requests]][Pull Requests]
@@ -100,7 +100,7 @@ easy IPC, much more QoL stuff than other compositors and more...
<!-----------------------------------------------------------------------------> <!----------------------------------------------------------------------------->
[Configure]: https://wiki.hypr.land/Configuring/ [Configure]: https://wiki.hyprland.org/Configuring/
[Stars]: https://starchart.cc/hyprwm/Hyprland [Stars]: https://starchart.cc/hyprwm/Hyprland
[Hypr]: https://github.com/hyprwm/Hypr [Hypr]: https://github.com/hyprwm/Hypr
@@ -108,10 +108,9 @@ easy IPC, much more QoL stuff than other compositors and more...
[Issues]: https://github.com/hyprwm/Hyprland/issues [Issues]: https://github.com/hyprwm/Hyprland/issues
[Todo]: https://github.com/hyprwm/Hyprland/projects?type=beta [Todo]: https://github.com/hyprwm/Hyprland/projects?type=beta
[Contribute]: https://wiki.hypr.land/Contributing-and-Debugging/ [Contribute]: https://wiki.hyprland.org/Contributing-and-Debugging/
[Install]: https://wiki.hypr.land/Getting-Started/Installation/ [Install]: https://wiki.hyprland.org/Getting-Started/Installation/
[Quick Start]: https://wiki.hypr.land/Getting-Started/Master-Tutorial/ [Quick Start]: https://wiki.hyprland.org/Getting-Started/Master-Tutorial/
[Workflow]: https://github.com/hyprwm/Hyprland/actions/workflows/ci.yaml
[License]: LICENSE [License]: LICENSE
@@ -126,9 +125,9 @@ easy IPC, much more QoL stuff than other compositors and more...
<!----------------------------------{ Images }---------------------------------> <!----------------------------------{ Images }--------------------------------->
[Preview A]: https://i.ibb.co/XxFY75Mk/greerggergerhtrytghjnyhjn.png [Preview A]: https://i.ibb.co/C1yTb0r/falf.png
[Preview B]: https://i.ibb.co/C1yTb0r/falf.png [Preview B]: https://linfindel.github.io/cdn/hyprland-preview-b.png
[Preview C]: https://i.ibb.co/2Yc4q835/hyprland-preview-b.png [Preview C]: https://i.ibb.co/B3GJg28/20221126-20h53m26s-grim.png
<!----------------------------------{ Badges }---------------------------------> <!----------------------------------{ Badges }--------------------------------->

View File

@@ -1,32 +0,0 @@
# Hyprland Development Security Policy
If you have a bug that affects the security of your system, you may
want to privately disclose it instead of making it immediately public.
## Supported versions
_Only_ the most recent release on Github is supported. There are no LTS releases.
## What is not a security issue
Some examples of issues that should not be reported as security issues:
- An app can execute a command when ran outside of a sandbox
- An app can write / read hyprland sockets when ran outside of a sandbox
- Crashes
- Things that are protected via permissions when the permission system is disabled
## What is a security issue
Some examples of issues that should be reported as security issues:
- Sandboxed application executing arbitrary code via Hyprland
- Application being able to modify Hyprland's code on the fly
- Application being able to keylog / track user's activity beyond what the wayland protocols allow
## How to report security issues
Please report your security issues via either of these channels:
- Mail: `vaxry [at] vaxry [dot] net`
- Matrix: `@vaxry:matrix.vaxry.net`
- Discord: `@vaxry`

View File

@@ -1 +1 @@
0.50.0 0.45.2

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 506 KiB

View File

@@ -1,19 +1,5 @@
.\" Automatically generated by Pandoc 3.1.3 .\" Automatically generated by Pandoc 2.9.2.1
.\" .\"
.\" Define V font for inline verbatim, using C font in formats
.\" that render this, and otherwise B font.
.ie "\f[CB]x\f[]"x" \{\
. ftr V B
. ftr VI BI
. ftr VB B
. ftr VBI BI
.\}
.el \{\
. ftr V CR
. ftr VI CI
. ftr VB CB
. ftr VBI CBI
.\}
.TH "Hyprland" "1" "" "" "Hyprland User Manual" .TH "Hyprland" "1" "" "" "Hyprland User Manual"
.hy .hy
.SH NAME .SH NAME

View File

@@ -2,15 +2,15 @@
First of all, please remember to: First of all, please remember to:
- Check that your issue is not a duplicate - Check that your issue is not a duplicate
- Read the [FAQ](https://wiki.hypr.land/FAQ/) - Read the [FAQ](https://wiki.hyprland.org/FAQ/)
- Read the [Configuring Page](https://wiki.hypr.land/Configuring/) - Read the [Configuring Page](https://wiki.hyprland.org/Configuring/)
<br/> <br/>
# Reporting suggestions # Reporting suggestions
Suggestions are welcome. Suggestions are welcome.
Many features can be implemented using bash scripts and Hyprland sockets, read up on those [Here](https://wiki.hypr.land/IPC). Please do not suggest features that can be implemented as such. Many features can be implemented using bash scripts and Hyprland sockets, read up on those [Here](https://wiki.hyprland.org/IPC). Please do not suggest features that can be implemented as such.
<br/> <br/>
@@ -70,7 +70,7 @@ A debug coredump provides more information for debugging and may speed up the pr
Make sure you're on latest git. Run `git pull --recurse-submodules` to sync everything. Make sure you're on latest git. Run `git pull --recurse-submodules` to sync everything.
1. [Compile Hyprland with debug mode](http://wiki.hypr.land/Contributing-and-Debugging/#build-in-debug-mode) 1. [Compile Hyprland with debug mode](http://wiki.hyprland.org/Contributing-and-Debugging/#build-in-debug-mode)
> Note: The config file used will be `hyprlandd.conf` instead of `hyprland.conf` > Note: The config file used will be `hyprlandd.conf` instead of `hyprland.conf`
2. `cd ~` 2. `cd ~`

View File

@@ -1,19 +1,5 @@
.\" Automatically generated by Pandoc 3.1.3 .\" Automatically generated by Pandoc 2.9.2.1
.\" .\"
.\" Define V font for inline verbatim, using C font in formats
.\" that render this, and otherwise B font.
.ie "\f[CB]x\f[]"x" \{\
. ftr V B
. ftr VI BI
. ftr VB B
. ftr VBI BI
.\}
.el \{\
. ftr V CR
. ftr VI CI
. ftr VB CB
. ftr VBI CBI
.\}
.TH "hyprctl" "1" "" "" "hyprctl User Manual" .TH "hyprctl" "1" "" "" "hyprctl User Manual"
.hy .hy
.SH NAME .SH NAME

View File

@@ -1,6 +1,6 @@
# This is an example Hyprland config file. # This is an example Hyprland config file.
# Refer to the wiki for more information. # Refer to the wiki for more information.
# https://wiki.hypr.land/Configuring/ # https://wiki.hyprland.org/Configuring/
# Please note not all available settings / options are set here. # Please note not all available settings / options are set here.
# For a full list, see the wiki # For a full list, see the wiki
@@ -14,7 +14,7 @@
### MONITORS ### ### MONITORS ###
################ ################
# See https://wiki.hypr.land/Configuring/Monitors/ # See https://wiki.hyprland.org/Configuring/Monitors/
monitor=,preferred,auto,auto monitor=,preferred,auto,auto
@@ -22,7 +22,7 @@ monitor=,preferred,auto,auto
### MY PROGRAMS ### ### MY PROGRAMS ###
################### ###################
# See https://wiki.hypr.land/Configuring/Keywords/ # See https://wiki.hyprland.org/Configuring/Keywords/
# Set programs that you use # Set programs that you use
$terminal = kitty $terminal = kitty
@@ -46,59 +46,41 @@ $menu = wofi --show drun
### ENVIRONMENT VARIABLES ### ### ENVIRONMENT VARIABLES ###
############################# #############################
# See https://wiki.hypr.land/Configuring/Environment-variables/ # See https://wiki.hyprland.org/Configuring/Environment-variables/
env = XCURSOR_SIZE,24 env = XCURSOR_SIZE,24
env = HYPRCURSOR_SIZE,24 env = HYPRCURSOR_SIZE,24
###################
### PERMISSIONS ###
###################
# See https://wiki.hypr.land/Configuring/Permissions/
# Please note permission changes here require a Hyprland restart and are not applied on-the-fly
# for security reasons
# ecosystem {
# enforce_permissions = 1
# }
# permission = /usr/(bin|local/bin)/grim, screencopy, allow
# permission = /usr/(lib|libexec|lib64)/xdg-desktop-portal-hyprland, screencopy, allow
# permission = /usr/(bin|local/bin)/hyprpm, plugin, allow
##################### #####################
### LOOK AND FEEL ### ### LOOK AND FEEL ###
##################### #####################
# Refer to https://wiki.hypr.land/Configuring/Variables/ # Refer to https://wiki.hyprland.org/Configuring/Variables/
# https://wiki.hypr.land/Configuring/Variables/#general # https://wiki.hyprland.org/Configuring/Variables/#general
general { general {
gaps_in = 5 gaps_in = 5
gaps_out = 20 gaps_out = 20
border_size = 2 border_size = 2
# https://wiki.hypr.land/Configuring/Variables/#variable-types for info about colors # https://wiki.hyprland.org/Configuring/Variables/#variable-types for info about colors
col.active_border = rgba(33ccffee) rgba(00ff99ee) 45deg col.active_border = rgba(33ccffee) rgba(00ff99ee) 45deg
col.inactive_border = rgba(595959aa) col.inactive_border = rgba(595959aa)
# Set to true enable resizing windows by clicking and dragging on borders and gaps # Set to true enable resizing windows by clicking and dragging on borders and gaps
resize_on_border = false resize_on_border = false
# Please see https://wiki.hypr.land/Configuring/Tearing/ before you turn this on # Please see https://wiki.hyprland.org/Configuring/Tearing/ before you turn this on
allow_tearing = false allow_tearing = false
layout = dwindle layout = dwindle
} }
# https://wiki.hypr.land/Configuring/Variables/#decoration # https://wiki.hyprland.org/Configuring/Variables/#decoration
decoration { decoration {
rounding = 10 rounding = 10
rounding_power = 2
# Change transparency of focused and unfocused windows # Change transparency of focused and unfocused windows
active_opacity = 1.0 active_opacity = 1.0
@@ -111,7 +93,7 @@ decoration {
color = rgba(1a1a1aee) color = rgba(1a1a1aee)
} }
# https://wiki.hypr.land/Configuring/Variables/#blur # https://wiki.hyprland.org/Configuring/Variables/#blur
blur { blur {
enabled = true enabled = true
size = 3 size = 3
@@ -121,61 +103,58 @@ decoration {
} }
} }
# https://wiki.hypr.land/Configuring/Variables/#animations # https://wiki.hyprland.org/Configuring/Variables/#animations
animations { animations {
enabled = yes, please :) enabled = yes, please :)
# Default curves, see https://wiki.hypr.land/Configuring/Animations/#curves # Default animations, see https://wiki.hyprland.org/Configuring/Animations/ for more
# NAME, X0, Y0, X1, Y1
bezier = easeOutQuint, 0.23, 1, 0.32, 1
bezier = easeInOutCubic, 0.65, 0.05, 0.36, 1
bezier = linear, 0, 0, 1, 1
bezier = almostLinear, 0.5, 0.5, 0.75, 1
bezier = quick, 0.15, 0, 0.1, 1
# Default animations, see https://wiki.hypr.land/Configuring/Animations/ bezier = easeOutQuint,0.23,1,0.32,1
# NAME, ONOFF, SPEED, CURVE, [STYLE] bezier = easeInOutCubic,0.65,0.05,0.36,1
animation = global, 1, 10, default bezier = linear,0,0,1,1
animation = border, 1, 5.39, easeOutQuint bezier = almostLinear,0.5,0.5,0.75,1.0
animation = windows, 1, 4.79, easeOutQuint bezier = quick,0.15,0,0.1,1
animation = windowsIn, 1, 4.1, easeOutQuint, popin 87%
animation = windowsOut, 1, 1.49, linear, popin 87% animation = global, 1, 10, default
animation = fadeIn, 1, 1.73, almostLinear animation = border, 1, 5.39, easeOutQuint
animation = fadeOut, 1, 1.46, almostLinear animation = windows, 1, 4.79, easeOutQuint
animation = fade, 1, 3.03, quick animation = windowsIn, 1, 4.1, easeOutQuint, popin 87%
animation = layers, 1, 3.81, easeOutQuint animation = windowsOut, 1, 1.49, linear, popin 87%
animation = layersIn, 1, 4, easeOutQuint, fade animation = fadeIn, 1, 1.73, almostLinear
animation = layersOut, 1, 1.5, linear, fade animation = fadeOut, 1, 1.46, almostLinear
animation = fadeLayersIn, 1, 1.79, almostLinear animation = fade, 1, 3.03, quick
animation = fadeLayersOut, 1, 1.39, almostLinear animation = layers, 1, 3.81, easeOutQuint
animation = workspaces, 1, 1.94, almostLinear, fade animation = layersIn, 1, 4, easeOutQuint, fade
animation = workspacesIn, 1, 1.21, almostLinear, fade animation = layersOut, 1, 1.5, linear, fade
animation = workspacesOut, 1, 1.94, almostLinear, fade animation = fadeLayersIn, 1, 1.79, almostLinear
animation = zoomFactor, 1, 7, quick animation = fadeLayersOut, 1, 1.39, almostLinear
animation = workspaces, 1, 1.94, almostLinear, fade
animation = workspacesIn, 1, 1.21, almostLinear, fade
animation = workspacesOut, 1, 1.94, almostLinear, fade
} }
# Ref https://wiki.hypr.land/Configuring/Workspace-Rules/ # Ref https://wiki.hyprland.org/Configuring/Workspace-Rules/
# "Smart gaps" / "No gaps when only" # "Smart gaps" / "No gaps when only"
# uncomment all if you wish to use that. # uncomment all if you wish to use that.
# workspace = w[tv1], gapsout:0, gapsin:0 # workspace = w[tv1], gapsout:0, gapsin:0
# workspace = f[1], gapsout:0, gapsin:0 # workspace = f[1], gapsout:0, gapsin:0
# windowrule = bordersize 0, floating:0, onworkspace:w[tv1] # windowrulev2 = bordersize 0, floating:0, onworkspace:w[tv1]
# windowrule = rounding 0, floating:0, onworkspace:w[tv1] # windowrulev2 = rounding 0, floating:0, onworkspace:w[tv1]
# windowrule = bordersize 0, floating:0, onworkspace:f[1] # windowrulev2 = bordersize 0, floating:0, onworkspace:f[1]
# windowrule = rounding 0, floating:0, onworkspace:f[1] # windowrulev2 = rounding 0, floating:0, onworkspace:f[1]
# See https://wiki.hypr.land/Configuring/Dwindle-Layout/ for more # See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more
dwindle { dwindle {
pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
preserve_split = true # You probably want this preserve_split = true # You probably want this
} }
# See https://wiki.hypr.land/Configuring/Master-Layout/ for more # See https://wiki.hyprland.org/Configuring/Master-Layout/ for more
master { master {
new_status = master new_status = master
} }
# https://wiki.hypr.land/Configuring/Variables/#misc # https://wiki.hyprland.org/Configuring/Variables/#misc
misc { misc {
force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers
disable_hyprland_logo = false # If true disables the random hyprland logo / anime girl background. :( disable_hyprland_logo = false # If true disables the random hyprland logo / anime girl background. :(
@@ -186,7 +165,7 @@ misc {
### INPUT ### ### INPUT ###
############# #############
# https://wiki.hypr.land/Configuring/Variables/#input # https://wiki.hyprland.org/Configuring/Variables/#input
input { input {
kb_layout = us kb_layout = us
kb_variant = kb_variant =
@@ -203,13 +182,13 @@ input {
} }
} }
# https://wiki.hypr.land/Configuring/Variables/#gestures # https://wiki.hyprland.org/Configuring/Variables/#gestures
gestures { gestures {
workspace_swipe = false workspace_swipe = false
} }
# Example per-device config # Example per-device config
# See https://wiki.hypr.land/Configuring/Keywords/#per-device-input-configs for more # See https://wiki.hyprland.org/Configuring/Keywords/#per-device-input-configs for more
device { device {
name = epic-mouse-v1 name = epic-mouse-v1
sensitivity = -0.5 sensitivity = -0.5
@@ -220,10 +199,10 @@ device {
### KEYBINDINGS ### ### KEYBINDINGS ###
################### ###################
# See https://wiki.hypr.land/Configuring/Keywords/ # See https://wiki.hyprland.org/Configuring/Keywords/
$mainMod = SUPER # Sets "Windows" key as main modifier $mainMod = SUPER # Sets "Windows" key as main modifier
# Example binds, see https://wiki.hypr.land/Configuring/Binds/ for more # Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more
bind = $mainMod, Q, exec, $terminal bind = $mainMod, Q, exec, $terminal
bind = $mainMod, C, killactive, bind = $mainMod, C, killactive,
bind = $mainMod, M, exit, bind = $mainMod, M, exit,
@@ -280,8 +259,8 @@ bindel = ,XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@
bindel = ,XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- bindel = ,XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
bindel = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle bindel = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
bindel = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle bindel = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle
bindel = ,XF86MonBrightnessUp, exec, brightnessctl -e4 -n2 set 5%+ bindel = ,XF86MonBrightnessUp, exec, brightnessctl s 10%+
bindel = ,XF86MonBrightnessDown, exec, brightnessctl -e4 -n2 set 5%- bindel = ,XF86MonBrightnessDown, exec, brightnessctl s 10%-
# Requires playerctl # Requires playerctl
bindl = , XF86AudioNext, exec, playerctl next bindl = , XF86AudioNext, exec, playerctl next
@@ -293,14 +272,17 @@ bindl = , XF86AudioPrev, exec, playerctl previous
### WINDOWS AND WORKSPACES ### ### WINDOWS AND WORKSPACES ###
############################## ##############################
# See https://wiki.hypr.land/Configuring/Window-Rules/ for more # See https://wiki.hyprland.org/Configuring/Window-Rules/ for more
# See https://wiki.hypr.land/Configuring/Workspace-Rules/ for workspace rules # See https://wiki.hyprland.org/Configuring/Workspace-Rules/ for workspace rules
# Example windowrule # Example windowrule v1
# windowrule = float,class:^(kitty)$,title:^(kitty)$ # windowrule = float, ^(kitty)$
# Example windowrule v2
# windowrulev2 = float,class:^(kitty)$,title:^(kitty)$
# Ignore maximize requests from apps. You'll probably like this. # Ignore maximize requests from apps. You'll probably like this.
windowrule = suppressevent maximize, class:.* windowrulev2 = suppressevent maximize, class:.*
# Fix some dragging issues with XWayland # Fix some dragging issues with XWayland
windowrule = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0 windowrulev2 = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0

View File

@@ -22,6 +22,5 @@
} }
] ]
}, },
] ]
} }

View File

@@ -2,18 +2,15 @@
// Example blue light filter shader. // Example blue light filter shader.
// //
#version 300 es
precision mediump float; precision mediump float;
in vec2 v_texcoord; varying vec2 v_texcoord;
layout(location = 0) out vec4 fragColor;
uniform sampler2D tex; uniform sampler2D tex;
void main() { void main() {
vec4 pixColor = texture(tex, v_texcoord); vec4 pixColor = texture2D(tex, v_texcoord);
pixColor[2] *= 0.8; pixColor[2] *= 0.8;
fragColor = pixColor; gl_FragColor = pixColor;
} }

168
flake.lock generated
View File

@@ -16,11 +16,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1753216019, "lastModified": 1731959031,
"narHash": "sha256-zik7WISrR1ks2l6T1MZqZHb/OqroHdJnSnAehkE0kCk=", "narHash": "sha256-TGcvIjftziC1CjuiHCzrYDwmOoSFYIhdiKmLetzB5L0=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "aquamarine", "repo": "aquamarine",
"rev": "be166e11d86ba4186db93e10c54a141058bdce49", "rev": "4468981c1c50999f315baa1508f0e53c4ee70c52",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -32,11 +32,11 @@
"flake-compat": { "flake-compat": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1747046372, "lastModified": 1696426674,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra", "owner": "edolstra",
"repo": "flake-compat", "repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -79,11 +79,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1753964049, "lastModified": 1728669738,
"narHash": "sha256-lIqabfBY7z/OANxHoPeIrDJrFyYy9jAM4GQLzZ2feCM=", "narHash": "sha256-EDNAU9AYcx8OupUzbTbWE1d3HYdeG0wO6Msg3iL1muk=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprcursor", "repo": "hyprcursor",
"rev": "44e91d467bdad8dcf8bbd2ac7cf49972540980a5", "rev": "0264e698149fcb857a66a53018157b41f8d97bb0",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -92,32 +92,6 @@
"type": "github" "type": "github"
} }
}, },
"hyprgraphics": {
"inputs": {
"hyprutils": [
"hyprutils"
],
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1754305013,
"narHash": "sha256-u+M2f0Xf1lVHzIPQ7DsNCDkM1NYxykOSsRr4t3TbSM4=",
"owner": "hyprwm",
"repo": "hyprgraphics",
"rev": "4c1d63a0f22135db123fc789f174b89544c6ec2d",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprgraphics",
"type": "github"
}
},
"hyprland-protocols": { "hyprland-protocols": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@@ -128,11 +102,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1749046714, "lastModified": 1728345020,
"narHash": "sha256-kymV5FMnddYGI+UjwIw8ceDjdeg7ToDVjbHCvUlhn14=", "narHash": "sha256-xGbkc7U/Roe0/Cv3iKlzijIaFBNguasI31ynL2IlEoM=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprland-protocols", "repo": "hyprland-protocols",
"rev": "613878cb6f459c5e323aaafe1e6f388ac8a36330", "rev": "a7c183800e74f337753de186522b9017a07a8cee",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -141,67 +115,6 @@
"type": "github" "type": "github"
} }
}, },
"hyprland-qt-support": {
"inputs": {
"hyprlang": [
"hyprland-qtutils",
"hyprlang"
],
"nixpkgs": [
"hyprland-qtutils",
"nixpkgs"
],
"systems": [
"hyprland-qtutils",
"systems"
]
},
"locked": {
"lastModified": 1749154592,
"narHash": "sha256-DO7z5CeT/ddSGDEnK9mAXm1qlGL47L3VAHLlLXoCjhE=",
"owner": "hyprwm",
"repo": "hyprland-qt-support",
"rev": "4c8053c3c888138a30c3a6c45c2e45f5484f2074",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprland-qt-support",
"type": "github"
}
},
"hyprland-qtutils": {
"inputs": {
"hyprland-qt-support": "hyprland-qt-support",
"hyprlang": [
"hyprlang"
],
"hyprutils": [
"hyprland-qtutils",
"hyprlang",
"hyprutils"
],
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1753819801,
"narHash": "sha256-tHe6XeNeVeKapkNM3tcjW4RuD+tB2iwwoogWJOtsqTI=",
"owner": "hyprwm",
"repo": "hyprland-qtutils",
"rev": "b308a818b9dcaa7ab8ccab891c1b84ebde2152bc",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprland-qtutils",
"type": "github"
}
},
"hyprlang": { "hyprlang": {
"inputs": { "inputs": {
"hyprutils": [ "hyprutils": [
@@ -215,11 +128,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1753622892, "lastModified": 1728168612,
"narHash": "sha256-0K+A+gmOI8IklSg5It1nyRNv0kCNL51duwnhUO/B8JA=", "narHash": "sha256-AnB1KfiXINmuiW7BALYrKqcjCnsLZPifhb/7BsfPbns=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprlang", "repo": "hyprlang",
"rev": "23f0debd2003f17bd65f851cd3f930cff8a8c809", "rev": "f054f2e44d6a0b74607a6bc0f52dba337a3db38e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -238,11 +151,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1754481650, "lastModified": 1731702627,
"narHash": "sha256-6u6HdEFJh5gY6VfyMQbhP7zDdVcqOrCDTkbiHJmAtMI=", "narHash": "sha256-+JeO9gevnXannQxMfR5xzZtF4sYmSlWkX/BPmPx0mWk=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprutils", "repo": "hyprutils",
"rev": "df6b8820c4a0835d83d0c7c7be86fbc555f1f7fd", "rev": "e911361a687753bbbdfe3b6a9eab755ecaf1d9e1",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -261,11 +174,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1751897909, "lastModified": 1726874836,
"narHash": "sha256-FnhBENxihITZldThvbO7883PdXC/2dzW4eiNvtoV5Ao=", "narHash": "sha256-VKR0sf0PSNCB0wPHVKSAn41mCNVCnegWmgkrneKDhHM=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprwayland-scanner", "repo": "hyprwayland-scanner",
"rev": "fcca0c61f988a9d092cbb33e906775014c61579d", "rev": "500c81a9e1a76760371049a8d99e008ea77aa59e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -276,11 +189,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1754725699, "lastModified": 1731676054,
"narHash": "sha256-iAcj9T/Y+3DBy2J0N+yF9XQQQ8IEb5swLFzs23CdP88=", "narHash": "sha256-OZiZ3m8SCMfh3B6bfGC/Bm4x3qc1m2SVEAlkV6iY7Yg=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "85dbfc7aaf52ecb755f87e577ddbe6dbbdbc1054", "rev": "5e4fbfb6b3de1aa2872b76d49fafc942626e2add",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -290,20 +203,37 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs-stable": {
"locked": {
"lastModified": 1730741070,
"narHash": "sha256-edm8WG19kWozJ/GqyYx2VjW99EdhjKwbY3ZwdlPAAlo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d063c1dd113c91ab27959ba540c0d9753409edf3",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": { "pre-commit-hooks": {
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"gitignore": "gitignore", "gitignore": "gitignore",
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
] ],
"nixpkgs-stable": "nixpkgs-stable"
}, },
"locked": { "locked": {
"lastModified": 1754416808, "lastModified": 1732021966,
"narHash": "sha256-c6yg0EQ9xVESx6HGDOCMcyRSjaTpNJP10ef+6fRcofA=", "narHash": "sha256-mnTbjpdqF0luOkou8ZFi2asa1N3AA2CchR/RqCNmsGE=",
"owner": "cachix", "owner": "cachix",
"repo": "git-hooks.nix", "repo": "git-hooks.nix",
"rev": "9c52372878df6911f9afc1e2a1391f55e4dfc864", "rev": "3308484d1a443fc5bc92012435d79e80458fe43c",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -316,9 +246,7 @@
"inputs": { "inputs": {
"aquamarine": "aquamarine", "aquamarine": "aquamarine",
"hyprcursor": "hyprcursor", "hyprcursor": "hyprcursor",
"hyprgraphics": "hyprgraphics",
"hyprland-protocols": "hyprland-protocols", "hyprland-protocols": "hyprland-protocols",
"hyprland-qtutils": "hyprland-qtutils",
"hyprlang": "hyprlang", "hyprlang": "hyprlang",
"hyprutils": "hyprutils", "hyprutils": "hyprutils",
"hyprwayland-scanner": "hyprwayland-scanner", "hyprwayland-scanner": "hyprwayland-scanner",
@@ -365,11 +293,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1753633878, "lastModified": 1731703417,
"narHash": "sha256-js2sLRtsOUA/aT10OCDaTjO80yplqwOIaLUqEe0nMx0=", "narHash": "sha256-rheDc/7C+yI+QspYr9J2z9kQ5P9F4ATapI7qyFAe1XA=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "xdg-desktop-portal-hyprland", "repo": "xdg-desktop-portal-hyprland",
"rev": "371b96bd11ad2006ed4f21229dbd1be69bed3e8a", "rev": "8070f36deec723de71e7557441acb17e478204d3",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -22,26 +22,12 @@
inputs.hyprlang.follows = "hyprlang"; inputs.hyprlang.follows = "hyprlang";
}; };
hyprgraphics = {
url = "github:hyprwm/hyprgraphics";
inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems";
inputs.hyprutils.follows = "hyprutils";
};
hyprland-protocols = { hyprland-protocols = {
url = "github:hyprwm/hyprland-protocols"; url = "github:hyprwm/hyprland-protocols";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems"; inputs.systems.follows = "systems";
}; };
hyprland-qtutils = {
url = "github:hyprwm/hyprland-qtutils";
inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems";
inputs.hyprlang.follows = "hyprlang";
};
hyprlang = { hyprlang = {
url = "github:hyprwm/hyprlang"; url = "github:hyprwm/hyprlang";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
@@ -102,21 +88,6 @@
hyprland-extras hyprland-extras
]; ];
}); });
pkgsDebugFor = eachSystem (system:
import nixpkgs {
localSystem = system;
overlays = with self.overlays; [
hyprland-debug
];
});
pkgsDebugCrossFor = eachSystem (system: crossSystem:
import nixpkgs {
localSystem = system;
inherit crossSystem;
overlays = with self.overlays; [
hyprland-debug
];
});
in { in {
overlays = import ./nix/overlays.nix {inherit self lib inputs;}; overlays = import ./nix/overlays.nix {inherit self lib inputs;};
@@ -138,23 +109,23 @@
}; };
}; };
}; };
} });
// (import ./nix/tests inputs pkgsFor.${system}));
packages = eachSystem (system: { packages = eachSystem (system: {
default = self.packages.${system}.hyprland; default = self.packages.${system}.hyprland;
inherit inherit
(pkgsFor.${system}) (pkgsFor.${system})
# hyprland-packages # hyprland-packages
hyprland hyprland
hyprland-debug
hyprland-legacy-renderer
hyprland-unwrapped hyprland-unwrapped
hyprtester
# hyprland-extras # hyprland-extras
xdg-desktop-portal-hyprland xdg-desktop-portal-hyprland
; ;
inherit (pkgsDebugFor.${system}) hyprland-debug;
hyprland-cross = (pkgsCrossFor.${system} "aarch64-linux").hyprland; hyprland-cross = (pkgsCrossFor.${system} "aarch64-linux").hyprland;
hyprland-debug-cross = (pkgsDebugCrossFor.${system} "aarch64-linux").hyprland-debug;
}); });
devShells = eachSystem (system: { devShells = eachSystem (system: {
@@ -174,11 +145,5 @@
nixosModules.default = import ./nix/module.nix inputs; nixosModules.default = import ./nix/module.nix inputs;
homeManagerModules.default = import ./nix/hm-module.nix self; homeManagerModules.default = import ./nix/hm-module.nix self;
# Hydra build jobs
# Recent versions of Hydra can aggregate jobsets from 'hydraJobs' instead of a release.nix
# or similar. Remember to filter large or incompatible attributes here. More eval jobs can
# be added by merging, e.g., self.packages // self.devShells.
hydraJobs = self.packages;
}; };
} }

View File

@@ -5,7 +5,7 @@ project(
DESCRIPTION "Control utility for Hyprland" DESCRIPTION "Control utility for Hyprland"
) )
pkg_check_modules(hyprctl_deps REQUIRED IMPORTED_TARGET hyprutils>=0.2.4 re2) pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprutils>=0.1.1)
add_executable(hyprctl "main.cpp") add_executable(hyprctl "main.cpp")

View File

@@ -22,7 +22,6 @@ commands:
getoption <option> Gets the config option status (values) getoption <option> Gets the config option status (values)
globalshortcuts Lists all global shortcuts globalshortcuts Lists all global shortcuts
hyprpaper ... Issue a hyprpaper request hyprpaper ... Issue a hyprpaper request
hyprsunset ... Issue a hyprsunset request
instances Lists all running instances of Hyprland with instances Lists all running instances of Hyprland with
their info their info
keyword <name> <value> Issue a keyword to call a config keyword keyword <name> <value> Issue a keyword to call a config keyword
@@ -49,7 +48,6 @@ commands:
the same format as in colors in config. Will reset the same format as in colors in config. Will reset
when Hyprland's config is reloaded when Hyprland's config is reloaded
setprop ... Sets a window property setprop ... Sets a window property
getprop ... Gets a window property
splash Get the current splash splash Get the current splash
switchxkblayout ... Sets the xkb layout index for a keyboard switchxkblayout ... Sets the xkb layout index for a keyboard
systeminfo Get system info systeminfo Get system info
@@ -83,16 +81,6 @@ requests:
flags: flags:
See 'hyprctl --help')#"; See 'hyprctl --help')#";
const std::string_view HYPRSUNSET_HELP = R"#(usage: hyprctl [flags] hyprsunset <request>
requests:
temperature <temp> Enable blue-light filter
identity Disable blue-light filter
gamma <gamma> Enable gamma filter
flags:
See 'hyprctl --help')#";
const std::string_view NOTIFY_HELP = R"#(usage: hyprctl [flags] notify <icon> <time_ms> <color> <message...> const std::string_view NOTIFY_HELP = R"#(usage: hyprctl [flags] notify <icon> <time_ms> <color> <message...>
icon: icon:
@@ -147,7 +135,7 @@ regex:
Regular expression by which a window will be searched Regular expression by which a window will be searched
property: property:
See https://wiki.hypr.land/Configuring/Using-hyprctl/#setprop for list See https://wiki.hyprland.org/Configuring/Using-hyprctl/#setprop for list
of properties of properties
value: value:
@@ -160,18 +148,6 @@ lock:
flags: flags:
See 'hyprctl --help')#"; See 'hyprctl --help')#";
const std::string_view GETPROP_HELP = R"#(usage: hyprctl [flags] getprop <regex> <property>
regex:
Regular expression by which a window will be searched
property:
See https://wiki.hypr.land/Configuring/Using-hyprctl/#setprop for list
of properties
flags:
See 'hyprctl --help')#";
const std::string_view SWITCHXKBLAYOUT_HELP = R"#(usage: [flags] switchxkblayout <device> <cmd> const std::string_view SWITCHXKBLAYOUT_HELP = R"#(usage: [flags] switchxkblayout <device> <cmd>
device: device:

View File

@@ -23,7 +23,7 @@ _hyprctl () {
local words cword local words cword
_get_comp_words_by_ref -n "$COMP_WORDBREAKS" words cword _get_comp_words_by_ref -n "$COMP_WORDBREAKS" words cword
declare -a literals=(resizeactive 2 changegroupactive -r moveintogroup forceallowsinput 4 ::= systeminfo all layouts setprop animationstyle switchxkblayout create denywindowfromgroup headless activebordercolor exec setcursor wayland focusurgentorlast workspacerules movecurrentworkspacetomonitor movetoworkspacesilent hyprpaper alpha inactivebordercolor movegroupwindow movecursortocorner movewindowpixel prev movewindow globalshortcuts clients dimaround setignoregrouplock splash execr monitors 0 forcenoborder -q animations 1 nomaxsize splitratio moveactive pass swapnext devices layers rounding lockactivegroup 5 moveworkspacetomonitor -f -i --quiet forcenodim pin 0 1 forceopaque forcenoshadow setfloating minsize alphaoverride sendshortcut workspaces cyclenext alterzorder togglegroup lockgroups bordersize dpms focuscurrentorlast -1 --batch notify remove instances 1 3 moveoutofgroup killactive 2 movetoworkspace movecursor configerrors closewindow swapwindow tagwindow forcerendererreload centerwindow auto focuswindow seterror nofocus alphafullscreen binds version -h togglespecialworkspace fullscreen windowdancecompat 0 keyword toggleopaque 3 --instance togglefloating renameworkspace alphafullscreenoverride activeworkspace x11 kill forceopaqueoverriden output global dispatch reload forcenoblur -j event --help disable -1 activewindow keepaspectratio dismissnotify focusmonitor movefocus plugin exit workspace fullscreenstate getoption alphainactiveoverride alphainactive decorations settiled config-only descriptions resizewindowpixel fakefullscreen rollinglog swapactiveworkspaces submap next movewindoworgroup cursorpos forcenoanims focusworkspaceoncurrentmonitor maxsize sendkeystate) declare -a literals=(resizeactive 2 changegroupactive -r moveintogroup forceallowsinput 4 ::= systeminfo all layouts setprop animationstyle switchxkblayout create denywindowfromgroup headless activebordercolor exec setcursor wayland focusurgentorlast workspacerules movecurrentworkspacetomonitor movetoworkspacesilent hyprpaper alpha inactivebordercolor movegroupwindow movecursortocorner movewindowpixel prev movewindow globalshortcuts clients dimaround setignoregrouplock splash execr monitors 0 forcenoborder -q animations 1 nomaxsize splitratio moveactive pass swapnext devices layers rounding lockactivegroup 5 moveworkspacetomonitor -f -i --quiet forcenodim pin 0 1 forceopaque forcenoshadow setfloating minsize alphaoverride sendshortcut workspaces cyclenext alterzorder togglegroup lockgroups bordersize dpms focuscurrentorlast -1 --batch notify remove instances 1 3 moveoutofgroup killactive 2 movetoworkspace movecursor configerrors closewindow swapwindow tagwindow forcerendererreload centerwindow auto focuswindow seterror nofocus alphafullscreen binds version -h togglespecialworkspace fullscreen windowdancecompat 0 keyword toggleopaque 3 --instance togglefloating renameworkspace alphafullscreenoverride activeworkspace x11 kill forceopaqueoverriden output global dispatch reload forcenoblur -j event --help disable -1 activewindow keepaspectratio dismissnotify focusmonitor movefocus plugin exit workspace fullscreenstate getoption alphainactiveoverride alphainactive decorations settiled config-only descriptions resizewindowpixel fakefullscreen rollinglog swapactiveworkspaces submap next movewindoworgroup cursorpos forcenoanims focusworkspaceoncurrentmonitor maxsize)
declare -A literal_transitions declare -A literal_transitions
literal_transitions[0]="([120]=14 [43]=2 [125]=21 [81]=2 [3]=21 [51]=2 [50]=2 [128]=2 [89]=2 [58]=21 [8]=2 [10]=2 [11]=3 [130]=4 [13]=5 [97]=6 [101]=2 [102]=21 [133]=7 [100]=2 [137]=2 [22]=2 [19]=2 [140]=8 [25]=2 [143]=2 [107]=9 [146]=10 [69]=2 [33]=2 [34]=2 [78]=21 [114]=2 [37]=2 [151]=2 [116]=2 [121]=13 [123]=21 [39]=11 [42]=21 [79]=15 [118]=12)" literal_transitions[0]="([120]=14 [43]=2 [125]=21 [81]=2 [3]=21 [51]=2 [50]=2 [128]=2 [89]=2 [58]=21 [8]=2 [10]=2 [11]=3 [130]=4 [13]=5 [97]=6 [101]=2 [102]=21 [133]=7 [100]=2 [137]=2 [22]=2 [19]=2 [140]=8 [25]=2 [143]=2 [107]=9 [146]=10 [69]=2 [33]=2 [34]=2 [78]=21 [114]=2 [37]=2 [151]=2 [116]=2 [121]=13 [123]=21 [39]=11 [42]=21 [79]=15 [118]=12)"
literal_transitions[1]="([81]=2 [51]=2 [50]=2 [128]=2 [8]=2 [89]=2 [10]=2 [11]=3 [130]=4 [13]=5 [97]=6 [101]=2 [133]=7 [100]=2 [22]=2 [19]=2 [137]=2 [140]=8 [25]=2 [143]=2 [107]=9 [146]=10 [69]=2 [33]=2 [34]=2 [114]=2 [37]=2 [151]=2 [116]=2 [39]=11 [118]=12 [121]=13 [120]=14 [79]=15 [43]=2)" literal_transitions[1]="([81]=2 [51]=2 [50]=2 [128]=2 [8]=2 [89]=2 [10]=2 [11]=3 [130]=4 [13]=5 [97]=6 [101]=2 [133]=7 [100]=2 [22]=2 [19]=2 [137]=2 [140]=8 [25]=2 [143]=2 [107]=9 [146]=10 [69]=2 [33]=2 [34]=2 [114]=2 [37]=2 [151]=2 [116]=2 [39]=11 [118]=12 [121]=13 [120]=14 [79]=15 [43]=2)"

View File

@@ -29,7 +29,7 @@ function _hyprctl
set COMP_CWORD (count $COMP_WORDS) set COMP_CWORD (count $COMP_WORDS)
end end
set literals "resizeactive" "2" "changegroupactive" "-r" "moveintogroup" "forceallowsinput" "4" "::=" "systeminfo" "all" "layouts" "setprop" "animationstyle" "switchxkblayout" "create" "denywindowfromgroup" "headless" "activebordercolor" "exec" "setcursor" "wayland" "focusurgentorlast" "workspacerules" "movecurrentworkspacetomonitor" "movetoworkspacesilent" "hyprpaper" "alpha" "inactivebordercolor" "movegroupwindow" "movecursortocorner" "movewindowpixel" "prev" "movewindow" "globalshortcuts" "clients" "dimaround" "setignoregrouplock" "splash" "execr" "monitors" "0" "forcenoborder" "-q" "animations" "1" "nomaxsize" "splitratio" "moveactive" "pass" "swapnext" "devices" "layers" "rounding" "lockactivegroup" "5" "moveworkspacetomonitor" "-f" "-i" "--quiet" "forcenodim" "pin" "0" "1" "forceopaque" "forcenoshadow" "setfloating" "minsize" "alphaoverride" "sendshortcut" "workspaces" "cyclenext" "alterzorder" "togglegroup" "lockgroups" "bordersize" "dpms" "focuscurrentorlast" "-1" "--batch" "notify" "remove" "instances" "1" "3" "moveoutofgroup" "killactive" "2" "movetoworkspace" "movecursor" "configerrors" "closewindow" "swapwindow" "tagwindow" "forcerendererreload" "centerwindow" "auto" "focuswindow" "seterror" "nofocus" "alphafullscreen" "binds" "version" "-h" "togglespecialworkspace" "fullscreen" "windowdancecompat" "0" "keyword" "toggleopaque" "3" "--instance" "togglefloating" "renameworkspace" "alphafullscreenoverride" "activeworkspace" "x11" "kill" "forceopaqueoverriden" "output" "global" "dispatch" "reload" "forcenoblur" "-j" "event" "--help" "disable" "-1" "activewindow" "keepaspectratio" "dismissnotify" "focusmonitor" "movefocus" "plugin" "exit" "workspace" "fullscreenstate" "getoption" "alphainactiveoverride" "alphainactive" "decorations" "settiled" "config-only" "descriptions" "resizewindowpixel" "fakefullscreen" "rollinglog" "swapactiveworkspaces" "submap" "next" "movewindoworgroup" "cursorpos" "forcenoanims" "focusworkspaceoncurrentmonitor" "maxsize" "sendkeystate" set literals "resizeactive" "2" "changegroupactive" "-r" "moveintogroup" "forceallowsinput" "4" "::=" "systeminfo" "all" "layouts" "setprop" "animationstyle" "switchxkblayout" "create" "denywindowfromgroup" "headless" "activebordercolor" "exec" "setcursor" "wayland" "focusurgentorlast" "workspacerules" "movecurrentworkspacetomonitor" "movetoworkspacesilent" "hyprpaper" "alpha" "inactivebordercolor" "movegroupwindow" "movecursortocorner" "movewindowpixel" "prev" "movewindow" "globalshortcuts" "clients" "dimaround" "setignoregrouplock" "splash" "execr" "monitors" "0" "forcenoborder" "-q" "animations" "1" "nomaxsize" "splitratio" "moveactive" "pass" "swapnext" "devices" "layers" "rounding" "lockactivegroup" "5" "moveworkspacetomonitor" "-f" "-i" "--quiet" "forcenodim" "pin" "0" "1" "forceopaque" "forcenoshadow" "setfloating" "minsize" "alphaoverride" "sendshortcut" "workspaces" "cyclenext" "alterzorder" "togglegroup" "lockgroups" "bordersize" "dpms" "focuscurrentorlast" "-1" "--batch" "notify" "remove" "instances" "1" "3" "moveoutofgroup" "killactive" "2" "movetoworkspace" "movecursor" "configerrors" "closewindow" "swapwindow" "tagwindow" "forcerendererreload" "centerwindow" "auto" "focuswindow" "seterror" "nofocus" "alphafullscreen" "binds" "version" "-h" "togglespecialworkspace" "fullscreen" "windowdancecompat" "0" "keyword" "toggleopaque" "3" "--instance" "togglefloating" "renameworkspace" "alphafullscreenoverride" "activeworkspace" "x11" "kill" "forceopaqueoverriden" "output" "global" "dispatch" "reload" "forcenoblur" "-j" "event" "--help" "disable" "-1" "activewindow" "keepaspectratio" "dismissnotify" "focusmonitor" "movefocus" "plugin" "exit" "workspace" "fullscreenstate" "getoption" "alphainactiveoverride" "alphainactive" "decorations" "settiled" "config-only" "descriptions" "resizewindowpixel" "fakefullscreen" "rollinglog" "swapactiveworkspaces" "submap" "next" "movewindoworgroup" "cursorpos" "forcenoanims" "focusworkspaceoncurrentmonitor" "maxsize"
set descriptions set descriptions
set descriptions[1] "Resize the active window" set descriptions[1] "Resize the active window"
@@ -48,7 +48,7 @@ function _hyprctl
set descriptions[22] "Focus the urgent window or the last window" set descriptions[22] "Focus the urgent window or the last window"
set descriptions[23] "Get the list of defined workspace rules" set descriptions[23] "Get the list of defined workspace rules"
set descriptions[24] "Move the active workspace to a monitor" set descriptions[24] "Move the active workspace to a monitor"
set descriptions[25] "Move window doesn't switch to the workspace" set descriptions[25] "Move window doesnt switch to the workspace"
set descriptions[26] "Interact with hyprpaper if present" set descriptions[26] "Interact with hyprpaper if present"
set descriptions[29] "Swap the active window with the next or previous in a group" set descriptions[29] "Swap the active window with the next or previous in a group"
set descriptions[30] "Move the cursor to the corner of the active window" set descriptions[30] "Move the cursor to the corner of the active window"

View File

@@ -1,4 +1,4 @@
# This is a file fed to complgen to generate bash/fish/zsh completions # This is a file feeded to complgen to generate bash/fish/zsh completions
# Repo: https://github.com/adaszko/complgen # Repo: https://github.com/adaszko/complgen
# Generate completion scripts: "complgen aot --bash-script hyprctl.bash --fish-script hyprctl.fish --zsh-script hyprctl.zsh ./hyprctl.usage" # Generate completion scripts: "complgen aot --bash-script hyprctl.bash --fish-script hyprctl.fish --zsh-script hyprctl.zsh ./hyprctl.usage"
@@ -106,12 +106,11 @@ hyprctl [<OPTIONS>]... <ARGUMENTS>
| (execr) "Execute a raw shell command" | (execr) "Execute a raw shell command"
| (pass) "Pass the key to a specified window" | (pass) "Pass the key to a specified window"
| (sendshortcut) "On shortcut X sends shortcut Y to a specified window" | (sendshortcut) "On shortcut X sends shortcut Y to a specified window"
| (sendkeystate) "Send a key with specific state (down/repeat/up) to a specified window (window must keep focus for events to continue)"
| (killactive) "Close the active window" | (killactive) "Close the active window"
| (closewindow) "Close a specified window" | (closewindow) "Close a specified window"
| (workspace) "Change the workspace" | (workspace) "Change the workspace"
| (movetoworkspace) "Move the focused window to a workspace" | (movetoworkspace) "Move the focused window to a workspace"
| (movetoworkspacesilent) "Move window doesn't switch to the workspace" | (movetoworkspacesilent) "Move window doesnt switch to the workspace"
| (togglefloating) "Toggle the current window's floating state" | (togglefloating) "Toggle the current window's floating state"
| (setfloating) "Set the current window's floating state to true" | (setfloating) "Set the current window's floating state to true"
| (settiled) "Set the current window's floating state to false" | (settiled) "Set the current window's floating state to false"

View File

@@ -17,7 +17,7 @@ _hyprctl_cmd_0 () {
} }
_hyprctl () { _hyprctl () {
local -a literals=("resizeactive" "2" "changegroupactive" "-r" "moveintogroup" "forceallowsinput" "4" "::=" "systeminfo" "all" "layouts" "setprop" "animationstyle" "switchxkblayout" "create" "denywindowfromgroup" "headless" "activebordercolor" "exec" "setcursor" "wayland" "focusurgentorlast" "workspacerules" "movecurrentworkspacetomonitor" "movetoworkspacesilent" "hyprpaper" "alpha" "inactivebordercolor" "movegroupwindow" "movecursortocorner" "movewindowpixel" "prev" "movewindow" "globalshortcuts" "clients" "dimaround" "setignoregrouplock" "splash" "execr" "monitors" "0" "forcenoborder" "-q" "animations" "1" "nomaxsize" "splitratio" "moveactive" "pass" "swapnext" "devices" "layers" "rounding" "lockactivegroup" "5" "moveworkspacetomonitor" "-f" "-i" "--quiet" "forcenodim" "pin" "0" "1" "forceopaque" "forcenoshadow" "setfloating" "minsize" "alphaoverride" "sendshortcut" "workspaces" "cyclenext" "alterzorder" "togglegroup" "lockgroups" "bordersize" "dpms" "focuscurrentorlast" "-1" "--batch" "notify" "remove" "instances" "1" "3" "moveoutofgroup" "killactive" "2" "movetoworkspace" "movecursor" "configerrors" "closewindow" "swapwindow" "tagwindow" "forcerendererreload" "centerwindow" "auto" "focuswindow" "seterror" "nofocus" "alphafullscreen" "binds" "version" "-h" "togglespecialworkspace" "fullscreen" "windowdancecompat" "0" "keyword" "toggleopaque" "3" "--instance" "togglefloating" "renameworkspace" "alphafullscreenoverride" "activeworkspace" "x11" "kill" "forceopaqueoverriden" "output" "global" "dispatch" "reload" "forcenoblur" "-j" "event" "--help" "disable" "-1" "activewindow" "keepaspectratio" "dismissnotify" "focusmonitor" "movefocus" "plugin" "exit" "workspace" "fullscreenstate" "getoption" "alphainactiveoverride" "alphainactive" "decorations" "settiled" "config-only" "descriptions" "resizewindowpixel" "fakefullscreen" "rollinglog" "swapactiveworkspaces" "submap" "next" "movewindoworgroup" "cursorpos" "forcenoanims" "focusworkspaceoncurrentmonitor" "maxsize" "sendkeystate") local -a literals=("resizeactive" "2" "changegroupactive" "-r" "moveintogroup" "forceallowsinput" "4" "::=" "systeminfo" "all" "layouts" "setprop" "animationstyle" "switchxkblayout" "create" "denywindowfromgroup" "headless" "activebordercolor" "exec" "setcursor" "wayland" "focusurgentorlast" "workspacerules" "movecurrentworkspacetomonitor" "movetoworkspacesilent" "hyprpaper" "alpha" "inactivebordercolor" "movegroupwindow" "movecursortocorner" "movewindowpixel" "prev" "movewindow" "globalshortcuts" "clients" "dimaround" "setignoregrouplock" "splash" "execr" "monitors" "0" "forcenoborder" "-q" "animations" "1" "nomaxsize" "splitratio" "moveactive" "pass" "swapnext" "devices" "layers" "rounding" "lockactivegroup" "5" "moveworkspacetomonitor" "-f" "-i" "--quiet" "forcenodim" "pin" "0" "1" "forceopaque" "forcenoshadow" "setfloating" "minsize" "alphaoverride" "sendshortcut" "workspaces" "cyclenext" "alterzorder" "togglegroup" "lockgroups" "bordersize" "dpms" "focuscurrentorlast" "-1" "--batch" "notify" "remove" "instances" "1" "3" "moveoutofgroup" "killactive" "2" "movetoworkspace" "movecursor" "configerrors" "closewindow" "swapwindow" "tagwindow" "forcerendererreload" "centerwindow" "auto" "focuswindow" "seterror" "nofocus" "alphafullscreen" "binds" "version" "-h" "togglespecialworkspace" "fullscreen" "windowdancecompat" "0" "keyword" "toggleopaque" "3" "--instance" "togglefloating" "renameworkspace" "alphafullscreenoverride" "activeworkspace" "x11" "kill" "forceopaqueoverriden" "output" "global" "dispatch" "reload" "forcenoblur" "-j" "event" "--help" "disable" "-1" "activewindow" "keepaspectratio" "dismissnotify" "focusmonitor" "movefocus" "plugin" "exit" "workspace" "fullscreenstate" "getoption" "alphainactiveoverride" "alphainactive" "decorations" "settiled" "config-only" "descriptions" "resizewindowpixel" "fakefullscreen" "rollinglog" "swapactiveworkspaces" "submap" "next" "movewindoworgroup" "cursorpos" "forcenoanims" "focusworkspaceoncurrentmonitor" "maxsize")
local -A descriptions local -A descriptions
descriptions[1]="Resize the active window" descriptions[1]="Resize the active window"
@@ -36,7 +36,7 @@ _hyprctl () {
descriptions[22]="Focus the urgent window or the last window" descriptions[22]="Focus the urgent window or the last window"
descriptions[23]="Get the list of defined workspace rules" descriptions[23]="Get the list of defined workspace rules"
descriptions[24]="Move the active workspace to a monitor" descriptions[24]="Move the active workspace to a monitor"
descriptions[25]="Move window doesn't switch to the workspace" descriptions[25]="Move window doesnt switch to the workspace"
descriptions[26]="Interact with hyprpaper if present" descriptions[26]="Interact with hyprpaper if present"
descriptions[29]="Swap the active window with the next or previous in a group" descriptions[29]="Swap the active window with the next or previous in a group"
descriptions[30]="Move the cursor to the corner of the active window" descriptions[30]="Move the cursor to the corner of the active window"

View File

@@ -1,5 +1,3 @@
#include <re2/re2.h>
#include <cctype> #include <cctype>
#include <netdb.h> #include <netdb.h>
#include <netinet/in.h> #include <netinet/in.h>
@@ -12,26 +10,30 @@
#include <sys/un.h> #include <sys/un.h>
#include <pwd.h> #include <pwd.h>
#include <unistd.h> #include <unistd.h>
#include <ranges>
#include <algorithm> #include <algorithm>
#include <csignal> #include <csignal>
#include <ranges> #include <format>
#include <optional>
#include <charconv>
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <print> #include <print>
#include <fstream> #include <fstream>
#include <string>
#include <vector> #include <vector>
#include <deque>
#include <filesystem> #include <filesystem>
#include <cstdarg> #include <cstdarg>
#include <regex>
#include <sys/socket.h>
#include <hyprutils/string/String.hpp> #include <hyprutils/string/String.hpp>
#include <hyprutils/memory/Casts.hpp> #include <cstring>
using namespace Hyprutils::String; using namespace Hyprutils::String;
using namespace Hyprutils::Memory;
#include "Strings.hpp" #include "Strings.hpp"
#define PAD
std::string instanceSignature; std::string instanceSignature;
bool quiet = false; bool quiet = false;
@@ -40,100 +42,63 @@ struct SInstanceData {
uint64_t time; uint64_t time;
uint64_t pid; uint64_t pid;
std::string wlSocket; std::string wlSocket;
bool valid = true;
}; };
void log(const std::string_view str) { void log(const std::string& str) {
if (quiet) if (quiet)
return; return;
std::println("{}", str); std::println("{}", str);
} }
static int getUID() {
const auto UID = getuid();
const auto PWUID = getpwuid(UID);
return PWUID ? PWUID->pw_uid : UID;
}
std::string getRuntimeDir() { std::string getRuntimeDir() {
const auto XDG = getenv("XDG_RUNTIME_DIR"); const auto XDG = getenv("XDG_RUNTIME_DIR");
if (!XDG) { if (!XDG) {
const std::string USERID = std::to_string(getUID()); const std::string USERID = std::to_string(getpwuid(getuid())->pw_uid);
return "/run/user/" + USERID + "/hypr"; return "/run/user/" + USERID + "/hypr";
} }
return std::string{XDG} + "/hypr"; return std::string{XDG} + "/hypr";
} }
static std::optional<uint64_t> toUInt64(const std::string_view str) {
uint64_t value = 0;
const auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), value);
if (ec != std::errc() || ptr != str.data() + str.size())
return std::nullopt;
return value;
}
static std::optional<SInstanceData> parseInstance(const std::filesystem::directory_entry& entry) {
if (!entry.is_directory())
return std::nullopt;
const auto lockPath = entry.path() / "hyprland.lock";
std::ifstream ifs(lockPath);
if (!ifs.is_open())
return std::nullopt;
SInstanceData data;
data.id = entry.path().filename().string();
const auto first = std::string_view{data.id}.find_first_of('_');
const auto last = std::string_view{data.id}.find_last_of('_');
if (first == std::string_view::npos || last == std::string_view::npos || last <= first)
return std::nullopt;
auto time = toUInt64(std::string_view{data.id}.substr(first + 1, last - first - 1));
if (!time)
return std::nullopt;
data.time = *time;
std::string line;
if (!std::getline(ifs, line))
return std::nullopt;
auto pid = toUInt64(std::string_view{line});
if (!pid)
return std::nullopt;
data.pid = *pid;
if (!std::getline(ifs, data.wlSocket))
return std::nullopt;
if (std::getline(ifs, line) && !line.empty())
return std::nullopt; // more lines than expected
return data;
}
std::vector<SInstanceData> instances() { std::vector<SInstanceData> instances() {
std::vector<SInstanceData> result; std::vector<SInstanceData> result;
std::error_code ec; for (const auto& el : std::filesystem::directory_iterator(getRuntimeDir())) {
const auto runtimeDir = getRuntimeDir(); if (!el.is_directory() || !std::filesystem::exists(el.path().string() + "/hyprland.lock"))
if (!std::filesystem::exists(runtimeDir, ec) || ec) continue;
return result;
std::filesystem::directory_iterator it(runtimeDir, std::filesystem::directory_options::skip_permission_denied, ec); // read lock
if (ec) SInstanceData* data = &result.emplace_back();
return result; data->id = el.path().filename().string();
for (const auto& el : it) { try {
if (auto instance = parseInstance(el)) data->time = std::stoull(data->id.substr(data->id.find_first_of('_') + 1, data->id.find_last_of('_') - (data->id.find_first_of('_') + 1)));
result.emplace_back(std::move(*instance)); } catch (std::exception& e) { continue; }
// read file
std::ifstream ifs(el.path().string() + "/hyprland.lock");
int i = 0;
for (std::string line; std::getline(ifs, line); ++i) {
if (i == 0) {
try {
data->pid = std::stoull(line);
} catch (std::exception& e) { continue; }
} else if (i == 1) {
data->wlSocket = line;
} else
break;
}
ifs.close();
} }
std::erase_if(result, [](const auto& el) { return kill(el.pid, 0) != 0 && errno == ESRCH; }); std::erase_if(result, [&](const auto& el) { return kill(el.pid, 0) != 0 && errno == ESRCH; });
std::ranges::sort(result, {}, &SInstanceData::time); std::sort(result.begin(), result.end(), [&](const auto& a, const auto& b) { return a.time < b.time; });
return result; return result;
} }
@@ -175,19 +140,11 @@ int rollingRead(const int socket) {
return 0; return 0;
} }
int request(std::string_view arg, int minArgs = 0, bool needRoll = false) { int request(std::string arg, int minArgs = 0, bool needRoll = false) {
const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0); const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);
if (SERVERSOCKET < 0) { auto t = timeval{.tv_sec = 5, .tv_usec = 0};
log("Couldn't open a socket (1)"); setsockopt(SERVERSOCKET, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(struct timeval));
return 1;
}
auto t = timeval{.tv_sec = 5, .tv_usec = 0};
if (setsockopt(SERVERSOCKET, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(struct timeval)) < 0) {
log("Couldn't set socket timeout (2)");
return 2;
}
const auto ARGS = std::count(arg.begin(), arg.end(), ' '); const auto ARGS = std::count(arg.begin(), arg.end(), ' ');
@@ -196,53 +153,59 @@ int request(std::string_view arg, int minArgs = 0, bool needRoll = false) {
return -1; return -1;
} }
if (instanceSignature.empty()) { if (SERVERSOCKET < 0) {
log("HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?) (3)"); log("Couldn't open a socket (1)");
return 3; return 1;
} }
sockaddr_un serverAddress = {0}; if (instanceSignature.empty()) {
serverAddress.sun_family = AF_UNIX; log("HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?)");
return 2;
}
const std::string USERID = std::to_string(getpwuid(getuid())->pw_uid);
sockaddr_un serverAddress = {0};
serverAddress.sun_family = AF_UNIX;
std::string socketPath = getRuntimeDir() + "/" + instanceSignature + "/.socket.sock"; std::string socketPath = getRuntimeDir() + "/" + instanceSignature + "/.socket.sock";
strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1); strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);
if (connect(SERVERSOCKET, rc<sockaddr*>(&serverAddress), SUN_LEN(&serverAddress)) < 0) { if (connect(SERVERSOCKET, (sockaddr*)&serverAddress, SUN_LEN(&serverAddress)) < 0) {
log("Couldn't connect to " + socketPath + ". (4)"); log("Couldn't connect to " + socketPath + ". (3)");
return 4; return 3;
} }
auto sizeWritten = write(SERVERSOCKET, arg.data(), arg.size()); auto sizeWritten = write(SERVERSOCKET, arg.c_str(), arg.length());
if (sizeWritten < 0) { if (sizeWritten < 0) {
log("Couldn't write (5)"); log("Couldn't write (4)");
return 5; return 4;
} }
if (needRoll) if (needRoll)
return rollingRead(SERVERSOCKET); return rollingRead(SERVERSOCKET);
std::string reply = ""; std::string reply = "";
constexpr size_t BUFFER_SIZE = 8192; char buffer[8192] = {0};
char buffer[BUFFER_SIZE] = {0};
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE); sizeWritten = read(SERVERSOCKET, buffer, 8192);
if (sizeWritten < 0) { if (sizeWritten < 0) {
if (errno == EWOULDBLOCK) if (errno == EWOULDBLOCK)
log("Hyprland IPC didn't respond in time\n"); log("Hyprland IPC didn't respond in time\n");
log("Couldn't read (6)"); log("Couldn't read (5)");
return 6; return 5;
} }
reply += std::string(buffer, sizeWritten); reply += std::string(buffer, sizeWritten);
while (sizeWritten == BUFFER_SIZE) { while (sizeWritten == 8192) {
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE); sizeWritten = read(SERVERSOCKET, buffer, 8192);
if (sizeWritten < 0) { if (sizeWritten < 0) {
log("Couldn't read (6)"); log("Couldn't read (5)");
return 6; return 5;
} }
reply += std::string(buffer, sizeWritten); reply += std::string(buffer, sizeWritten);
} }
@@ -254,7 +217,7 @@ int request(std::string_view arg, int minArgs = 0, bool needRoll = false) {
return 0; return 0;
} }
int requestIPC(std::string_view filename, std::string_view arg) { int requestHyprpaper(std::string arg) {
const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0); const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);
if (SERVERSOCKET < 0) { if (SERVERSOCKET < 0) {
@@ -270,11 +233,13 @@ int requestIPC(std::string_view filename, std::string_view arg) {
sockaddr_un serverAddress = {0}; sockaddr_un serverAddress = {0};
serverAddress.sun_family = AF_UNIX; serverAddress.sun_family = AF_UNIX;
std::string socketPath = getRuntimeDir() + "/" + instanceSignature + "/" + filename; const std::string USERID = std::to_string(getpwuid(getuid())->pw_uid);
std::string socketPath = getRuntimeDir() + "/" + instanceSignature + "/.hyprpaper.sock";
strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1); strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);
if (connect(SERVERSOCKET, rc<sockaddr*>(&serverAddress), SUN_LEN(&serverAddress)) < 0) { if (connect(SERVERSOCKET, (sockaddr*)&serverAddress, SUN_LEN(&serverAddress)) < 0) {
log("Couldn't connect to " + socketPath + ". (3)"); log("Couldn't connect to " + socketPath + ". (3)");
return 3; return 3;
} }
@@ -282,16 +247,16 @@ int requestIPC(std::string_view filename, std::string_view arg) {
arg = arg.substr(arg.find_first_of('/') + 1); // strip flags arg = arg.substr(arg.find_first_of('/') + 1); // strip flags
arg = arg.substr(arg.find_first_of(' ') + 1); // strip "hyprpaper" arg = arg.substr(arg.find_first_of(' ') + 1); // strip "hyprpaper"
auto sizeWritten = write(SERVERSOCKET, arg.data(), arg.size()); auto sizeWritten = write(SERVERSOCKET, arg.c_str(), arg.length());
if (sizeWritten < 0) { if (sizeWritten < 0) {
log("Couldn't write (4)"); log("Couldn't write (4)");
return 4; return 4;
} }
constexpr size_t BUFFER_SIZE = 8192;
char buffer[BUFFER_SIZE] = {0};
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE); char buffer[8192] = {0};
sizeWritten = read(SERVERSOCKET, buffer, 8192);
if (sizeWritten < 0) { if (sizeWritten < 0) {
log("Couldn't read (5)"); log("Couldn't read (5)");
@@ -305,20 +270,11 @@ int requestIPC(std::string_view filename, std::string_view arg) {
return 0; return 0;
} }
int requestHyprpaper(std::string_view arg) { void batchRequest(std::string arg, bool json) {
return requestIPC(".hyprpaper.sock", arg); std::string commands = arg.substr(arg.find_first_of(" ") + 1);
}
int requestHyprsunset(std::string_view arg) {
return requestIPC(".hyprsunset.sock", arg);
}
void batchRequest(std::string_view arg, bool json) {
std::string commands(arg.substr(arg.find_first_of(' ') + 1));
if (json) { if (json) {
RE2::GlobalReplace(&commands, ";\\s*", ";j/"); commands = "j/" + std::regex_replace(commands, std::regex(";\\s*"), ";j/");
commands.insert(0, "j/");
} }
std::string rq = "[[BATCH]]" + commands; std::string rq = "[[BATCH]]" + commands;
@@ -355,11 +311,11 @@ void instancesRequest(bool json) {
log(result + "\n"); log(result + "\n");
} }
std::vector<std::string> splitArgs(int argc, char** argv) { std::deque<std::string> splitArgs(int argc, char** argv) {
std::vector<std::string> result; std::deque<std::string> result;
for (auto i = 1 /* skip the executable */; i < argc; ++i) for (auto i = 1 /* skip the executable */; i < argc; ++i)
result.emplace_back(argv[i]); result.push_back(std::string(argv[i]));
return result; return result;
} }
@@ -385,7 +341,7 @@ int main(int argc, char** argv) {
parseArgs = false; parseArgs = false;
continue; continue;
} }
if (parseArgs && (ARGS[i][0] == '-') && !(isNumber(ARGS[i], true) || isNumber(ARGS[i].substr(0, ARGS[i].length() - 1), true)) /* For stuff like -2 or -2, */) { if (parseArgs && (ARGS[i][0] == '-') && !isNumber(ARGS[i], true) /* For stuff like -2 */) {
// parse // parse
if (ARGS[i] == "-j" && !fullArgs.contains("j")) { if (ARGS[i] == "-j" && !fullArgs.contains("j")) {
fullArgs += "j"; fullArgs += "j";
@@ -417,8 +373,6 @@ int main(int argc, char** argv) {
if (cmd == "hyprpaper") { if (cmd == "hyprpaper") {
std::println("{}", HYPRPAPER_HELP); std::println("{}", HYPRPAPER_HELP);
} else if (cmd == "hyprsunset") {
std::println("{}", HYPRSUNSET_HELP);
} else if (cmd == "notify") { } else if (cmd == "notify") {
std::println("{}", NOTIFY_HELP); std::println("{}", NOTIFY_HELP);
} else if (cmd == "output") { } else if (cmd == "output") {
@@ -427,8 +381,6 @@ int main(int argc, char** argv) {
std::println("{}", PLUGIN_HELP); std::println("{}", PLUGIN_HELP);
} else if (cmd == "setprop") { } else if (cmd == "setprop") {
std::println("{}", SETPROP_HELP); std::println("{}", SETPROP_HELP);
} else if (cmd == "getprop") {
std::println("{}", GETPROP_HELP);
} else if (cmd == "switchxkblayout") { } else if (cmd == "switchxkblayout") {
std::println("{}", SWITCHXKBLAYOUT_HELP); std::println("{}", SWITCHXKBLAYOUT_HELP);
} else { } else {
@@ -479,7 +431,7 @@ int main(int argc, char** argv) {
const auto INSTANCES = instances(); const auto INSTANCES = instances();
if (INSTANCENO < 0 || sc<std::size_t>(INSTANCENO) >= INSTANCES.size()) { if (INSTANCENO < 0 || static_cast<std::size_t>(INSTANCENO) >= INSTANCES.size()) {
log("no such instance\n"); log("no such instance\n");
return 1; return 1;
} }
@@ -502,8 +454,6 @@ int main(int argc, char** argv) {
batchRequest(fullRequest, json); batchRequest(fullRequest, json);
else if (fullRequest.contains("/hyprpaper")) else if (fullRequest.contains("/hyprpaper"))
exitStatus = requestHyprpaper(fullRequest); exitStatus = requestHyprpaper(fullRequest);
else if (fullRequest.contains("/hyprsunset"))
exitStatus = requestHyprsunset(fullRequest);
else if (fullRequest.contains("/switchxkblayout")) else if (fullRequest.contains("/switchxkblayout"))
exitStatus = request(fullRequest, 2); exitStatus = request(fullRequest, 2);
else if (fullRequest.contains("/seterror")) else if (fullRequest.contains("/seterror"))

View File

@@ -3,7 +3,6 @@ executable(
'main.cpp', 'main.cpp',
dependencies: [ dependencies: [
dependency('hyprutils', version: '>= 0.1.1'), dependency('hyprutils', version: '>= 0.1.1'),
dependency('re2', required: true)
], ],
install: true, install: true,
) )

View File

@@ -9,25 +9,11 @@ file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)
pkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.7.0) pkg_check_modules(deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.1.1)
find_package(glaze QUIET)
if (NOT glaze_FOUND)
set(GLAZE_VERSION v5.1.1)
message(STATUS "glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent")
include(FetchContent)
FetchContent_Declare(
glaze
GIT_REPOSITORY https://github.com/stephenberry/glaze.git
GIT_TAG ${GLAZE_VERSION}
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(glaze)
endif()
add_executable(hyprpm ${SRCFILES}) add_executable(hyprpm ${SRCFILES})
target_link_libraries(hyprpm PUBLIC PkgConfig::hyprpm_deps glaze::glaze) target_link_libraries(hyprpm PUBLIC PkgConfig::deps)
# binary # binary
install(TARGETS hyprpm) install(TARGETS hyprpm)

View File

@@ -29,8 +29,8 @@ function _hyprpm
set descriptions[6] "Show help menu" set descriptions[6] "Show help menu"
set descriptions[7] "Check and update all plugins if needed" set descriptions[7] "Check and update all plugins if needed"
set descriptions[8] "Install a new plugin repository from git" set descriptions[8] "Install a new plugin repository from git"
set descriptions[9] "Enable too much logging" set descriptions[9] "Enable too much loggin"
set descriptions[10] "Enable too much logging" set descriptions[10] "Enable too much loggin"
set descriptions[11] "Force an operation ignoring checks (e.g. update -f)" set descriptions[11] "Force an operation ignoring checks (e.g. update -f)"
set descriptions[12] "Disable shallow cloning of Hyprland sources" set descriptions[12] "Disable shallow cloning of Hyprland sources"
set descriptions[13] "Remove a plugin repository" set descriptions[13] "Remove a plugin repository"

View File

@@ -3,7 +3,7 @@ hyprpm [<FLAGS>]... <ARGUMENT>
<FLAGS> ::= (--notify | -n) "Send a hyprland notification for important events (e.g. load fail)" <FLAGS> ::= (--notify | -n) "Send a hyprland notification for important events (e.g. load fail)"
| (--help | -h) "Show help menu" | (--help | -h) "Show help menu"
| (--verbose | -v) "Enable too much logging" | (--verbose | -v) "Enable too much loggin"
| (--force | -f) "Force an operation ignoring checks (e.g. update -f)" | (--force | -f) "Force an operation ignoring checks (e.g. update -f)"
| (--no-shallow | -s) "Disable shallow cloning of Hyprland sources" | (--no-shallow | -s) "Disable shallow cloning of Hyprland sources"
; ;
@@ -14,7 +14,7 @@ hyprpm [<FLAGS>]... <ARGUMENT>
| (list) "List all installed plugins" | (list) "List all installed plugins"
| (enable <PLUGINS>) "Load a plugin" | (enable <PLUGINS>) "Load a plugin"
| (disable <PLUGINS>) "Unload a plugin" | (disable <PLUGINS>) "Unload a plugin"
| (reload) "Reload plugins to match the enabled/disabled state. Use -f to force reload." | (reload) "Reload all plugins"
; ;
<PLUGINS> ::= {{{ hyprpm list | awk '/Plugin/{print $4}' }}}; <PLUGINS> ::= {{{ hyprpm list | awk '/Plugin/{print $4}' }}};

View File

@@ -19,8 +19,8 @@ _hyprpm () {
descriptions[6]="Show help menu" descriptions[6]="Show help menu"
descriptions[7]="Check and update all plugins if needed" descriptions[7]="Check and update all plugins if needed"
descriptions[8]="Install a new plugin repository from git" descriptions[8]="Install a new plugin repository from git"
descriptions[9]="Enable too much logging" descriptions[9]="Enable too much loggin"
descriptions[10]="Enable too much logging" descriptions[10]="Enable too much loggin"
descriptions[11]="Force an operation ignoring checks (e.g. update -f)" descriptions[11]="Force an operation ignoring checks (e.g. update -f)"
descriptions[12]="Disable shallow cloning of Hyprland sources" descriptions[12]="Disable shallow cloning of Hyprland sources"
descriptions[13]="Remove a plugin repository" descriptions[13]="Remove a plugin repository"

View File

@@ -1,91 +1,45 @@
#include "DataState.hpp" #include "DataState.hpp"
#include <sys/stat.h>
#include <toml++/toml.hpp> #include <toml++/toml.hpp>
#include <print> #include <print>
#include <sstream> #include <filesystem>
#include <fstream> #include <fstream>
#include "PluginManager.hpp" #include "PluginManager.hpp"
#include "../helpers/Die.hpp"
#include "../helpers/Sys.hpp"
#include "../helpers/StringUtils.hpp"
static std::string getTempRoot() { std::string DataState::getDataStatePath() {
static auto ENV = getenv("XDG_RUNTIME_DIR"); const auto HOME = getenv("HOME");
if (!ENV) { if (!HOME) {
std::cerr << "\nERROR: XDG_RUNTIME_DIR not set!\n"; std::println(stderr, "DataState: no $HOME");
exit(1); throw std::runtime_error("no $HOME");
return "";
} }
const auto STR = ENV + std::string{"/hyprpm/"}; const auto XDG_DATA_HOME = getenv("XDG_DATA_HOME");
return STR; if (XDG_DATA_HOME)
} return std::string{XDG_DATA_HOME} + "/hyprpm";
return std::string{HOME} + "/.local/share/hyprpm";
// write the state to a file
static bool writeState(const std::string& str, const std::string& to) {
// create temp file in a safe temp root
std::ofstream of(getTempRoot() + ".temp-state", std::ios::trunc);
if (!of.good())
return false;
of << str;
of.close();
return NSys::root::install(getTempRoot() + ".temp-state", to, "644");
}
std::filesystem::path DataState::getDataStatePath() {
return std::filesystem::path("/var/cache/hyprpm/" + g_pPluginManager->m_szUsername);
} }
std::string DataState::getHeadersPath() { std::string DataState::getHeadersPath() {
return getDataStatePath() / "headersRoot"; return getDataStatePath() + "/headersRoot";
}
std::vector<std::filesystem::path> DataState::getPluginStates() {
ensureStateStoreExists();
std::vector<std::filesystem::path> states;
for (const auto& entry : std::filesystem::directory_iterator(getDataStatePath())) {
if (!entry.is_directory() || entry.path().stem() == "headersRoot")
continue;
const auto stateFile = entry.path() / "state.toml";
if (!std::filesystem::exists(stateFile))
continue;
states.emplace_back(stateFile);
}
return states;
} }
void DataState::ensureStateStoreExists() { void DataState::ensureStateStoreExists() {
std::error_code ec; const auto PATH = getDataStatePath();
if (!std::filesystem::exists(getHeadersPath(), ec) || ec) {
std::println("{}", infoString("The hyprpm state store doesn't exist. Creating now...")); if (!std::filesystem::exists(PATH))
if (!std::filesystem::exists("/var/cache/hyprpm/", ec) || ec) { std::filesystem::create_directories(PATH);
if (!NSys::root::createDirectory("/var/cache/hyprpm", "755"))
Debug::die("ensureStateStoreExists: Failed to run a superuser cmd"); if (!std::filesystem::exists(getHeadersPath()))
} std::filesystem::create_directories(getHeadersPath());
if (!std::filesystem::exists(getDataStatePath(), ec) || ec) {
if (!NSys::root::createDirectory(getDataStatePath().string(), "755"))
Debug::die("ensureStateStoreExists: Failed to run a superuser cmd");
}
if (!NSys::root::createDirectory(getHeadersPath(), "755"))
Debug::die("ensureStateStoreExists: Failed to run a superuser cmd");
}
} }
void DataState::addNewPluginRepo(const SPluginRepository& repo) { void DataState::addNewPluginRepo(const SPluginRepository& repo) {
ensureStateStoreExists(); ensureStateStoreExists();
const auto PATH = getDataStatePath() / repo.name; const auto PATH = getDataStatePath() + "/" + repo.name;
std::error_code ec; std::filesystem::create_directories(PATH);
if (!std::filesystem::exists(PATH, ec) || ec) {
if (!NSys::root::createDirectory(PATH.string(), "755"))
Debug::die("addNewPluginRepo: failed to create cache dir");
}
// clang-format off // clang-format off
auto DATA = toml::table{ auto DATA = toml::table{
{"repository", toml::table{ {"repository", toml::table{
@@ -96,36 +50,39 @@ void DataState::addNewPluginRepo(const SPluginRepository& repo) {
}} }}
}; };
for (auto const& p : repo.plugins) { for (auto const& p : repo.plugins) {
const auto filename = p.name + ".so"; // copy .so to the good place
if (std::filesystem::exists(p.filename))
// copy .so to the good place and chmod 755 std::filesystem::copy_file(p.filename, PATH + "/" + p.name + ".so");
if (std::filesystem::exists(p.filename)) {
if (!NSys::root::install(p.filename, (PATH / filename).string(), "0755"))
Debug::die("addNewPluginRepo: failed to install so file");
}
DATA.emplace(p.name, toml::table{ DATA.emplace(p.name, toml::table{
{"filename", filename}, {"filename", p.name + ".so"},
{"enabled", p.enabled}, {"enabled", p.enabled},
{"failed", p.failed} {"failed", p.failed}
}); });
} }
// clang-format on // clang-format on
std::stringstream ss; std::ofstream ofs(PATH + "/state.toml", std::ios::trunc);
ss << DATA; ofs << DATA;
ofs.close();
if (!writeState(ss.str(), (PATH / "state.toml").string()))
Debug::die("{}", failureString("Failed to write plugin state"));
} }
bool DataState::pluginRepoExists(const std::string& urlOrName) { bool DataState::pluginRepoExists(const std::string& urlOrName) {
ensureStateStoreExists(); ensureStateStoreExists();
for (const auto& stateFile : getPluginStates()) { const auto PATH = getDataStatePath();
const auto STATE = toml::parse_file(stateFile.c_str());
const auto NAME = STATE["repository"]["name"].value_or(""); for (const auto& entry : std::filesystem::directory_iterator(PATH)) {
const auto URL = STATE["repository"]["url"].value_or(""); if (!entry.is_directory() || entry.path().stem() == "headersRoot")
continue;
if (!std::filesystem::exists(entry.path().string() + "/state.toml"))
continue;
auto STATE = toml::parse_file(entry.path().string() + "/state.toml");
const auto NAME = STATE["repository"]["name"].value_or("");
const auto URL = STATE["repository"]["url"].value_or("");
if (URL == urlOrName || NAME == urlOrName) if (URL == urlOrName || NAME == urlOrName)
return true; return true;
@@ -137,29 +94,31 @@ bool DataState::pluginRepoExists(const std::string& urlOrName) {
void DataState::removePluginRepo(const std::string& urlOrName) { void DataState::removePluginRepo(const std::string& urlOrName) {
ensureStateStoreExists(); ensureStateStoreExists();
for (const auto& stateFile : getPluginStates()) { const auto PATH = getDataStatePath();
const auto STATE = toml::parse_file(stateFile.c_str());
const auto NAME = STATE["repository"]["name"].value_or(""); for (const auto& entry : std::filesystem::directory_iterator(PATH)) {
const auto URL = STATE["repository"]["url"].value_or(""); if (!entry.is_directory() || entry.path().stem() == "headersRoot")
continue;
if (!std::filesystem::exists(entry.path().string() + "/state.toml"))
continue;
auto STATE = toml::parse_file(entry.path().string() + "/state.toml");
const auto NAME = STATE["repository"]["name"].value_or("");
const auto URL = STATE["repository"]["url"].value_or("");
if (URL == urlOrName || NAME == urlOrName) { if (URL == urlOrName || NAME == urlOrName) {
// unload the plugins!! // unload the plugins!!
for (const auto& file : std::filesystem::directory_iterator(stateFile.parent_path())) { for (const auto& file : std::filesystem::directory_iterator(entry.path())) {
if (!file.path().string().ends_with(".so")) if (!file.path().string().ends_with(".so"))
continue; continue;
g_pPluginManager->loadUnloadPlugin(std::filesystem::absolute(file.path()), false); g_pPluginManager->loadUnloadPlugin(std::filesystem::absolute(file.path()), false);
} }
const auto PATH = stateFile.parent_path().string(); std::filesystem::remove_all(entry.path());
if (!PATH.starts_with("/var/cache/hyprpm") || PATH.contains('\''))
return; // WTF?
// scary!
if (!NSys::root::removeRecursive(PATH))
Debug::die("removePluginRepo: failed to remove dir");
return; return;
} }
} }
@@ -168,13 +127,9 @@ void DataState::removePluginRepo(const std::string& urlOrName) {
void DataState::updateGlobalState(const SGlobalState& state) { void DataState::updateGlobalState(const SGlobalState& state) {
ensureStateStoreExists(); ensureStateStoreExists();
const auto PATH = getDataStatePath(); const auto PATH = getDataStatePath();
std::error_code ec; std::filesystem::create_directories(PATH);
if (!std::filesystem::exists(PATH, ec) || ec) {
if (!NSys::root::createDirectory(PATH.string(), "755"))
Debug::die("updateGlobalState: failed to create dir");
}
// clang-format off // clang-format off
auto DATA = toml::table{ auto DATA = toml::table{
{"state", toml::table{ {"state", toml::table{
@@ -184,23 +139,20 @@ void DataState::updateGlobalState(const SGlobalState& state) {
}; };
// clang-format on // clang-format on
std::stringstream ss; std::ofstream ofs(PATH + "/state.toml", std::ios::trunc);
ss << DATA; ofs << DATA;
ofs.close();
if (!writeState(ss.str(), (PATH / "state.toml").string()))
Debug::die("{}", failureString("Failed to write plugin state"));
} }
SGlobalState DataState::getGlobalState() { SGlobalState DataState::getGlobalState() {
ensureStateStoreExists(); ensureStateStoreExists();
const auto stateFile = getDataStatePath() / "state.toml"; const auto PATH = getDataStatePath();
std::error_code ec; if (!std::filesystem::exists(PATH + "/state.toml"))
if (!std::filesystem::exists(stateFile, ec) || ec)
return SGlobalState{}; return SGlobalState{};
auto DATA = toml::parse_file(stateFile.c_str()); auto DATA = toml::parse_file(PATH + "/state.toml");
SGlobalState state; SGlobalState state;
state.headersHashCompiled = DATA["state"]["hash"].value_or(""); state.headersHashCompiled = DATA["state"]["hash"].value_or("");
@@ -212,9 +164,18 @@ SGlobalState DataState::getGlobalState() {
std::vector<SPluginRepository> DataState::getAllRepositories() { std::vector<SPluginRepository> DataState::getAllRepositories() {
ensureStateStoreExists(); ensureStateStoreExists();
const auto PATH = getDataStatePath();
std::vector<SPluginRepository> repos; std::vector<SPluginRepository> repos;
for (const auto& stateFile : getPluginStates()) {
const auto STATE = toml::parse_file(stateFile.c_str()); for (const auto& entry : std::filesystem::directory_iterator(PATH)) {
if (!entry.is_directory() || entry.path().stem() == "headersRoot")
continue;
if (!std::filesystem::exists(entry.path().string() + "/state.toml"))
continue;
auto STATE = toml::parse_file(entry.path().string() + "/state.toml");
const auto NAME = STATE["repository"]["name"].value_or(""); const auto NAME = STATE["repository"]["name"].value_or("");
const auto URL = STATE["repository"]["url"].value_or(""); const auto URL = STATE["repository"]["url"].value_or("");
@@ -247,8 +208,17 @@ std::vector<SPluginRepository> DataState::getAllRepositories() {
bool DataState::setPluginEnabled(const std::string& name, bool enabled) { bool DataState::setPluginEnabled(const std::string& name, bool enabled) {
ensureStateStoreExists(); ensureStateStoreExists();
for (const auto& stateFile : getPluginStates()) { const auto PATH = getDataStatePath();
const auto STATE = toml::parse_file(stateFile.c_str());
for (const auto& entry : std::filesystem::directory_iterator(PATH)) {
if (!entry.is_directory() || entry.path().stem() == "headersRoot")
continue;
if (!std::filesystem::exists(entry.path().string() + "/state.toml"))
continue;
auto STATE = toml::parse_file(entry.path().string() + "/state.toml");
for (const auto& [key, val] : STATE) { for (const auto& [key, val] : STATE) {
if (key == "repository") if (key == "repository")
continue; continue;
@@ -261,14 +231,11 @@ bool DataState::setPluginEnabled(const std::string& name, bool enabled) {
if (FAILED) if (FAILED)
return false; return false;
auto modifiedState = STATE; (*STATE[key].as_table()).insert_or_assign("enabled", enabled);
(*modifiedState[key].as_table()).insert_or_assign("enabled", enabled);
std::stringstream ss; std::ofstream state(entry.path().string() + "/state.toml", std::ios::trunc);
ss << modifiedState; state << STATE;
state.close();
if (!writeState(ss.str(), stateFile.string()))
Debug::die("{}", failureString("Failed to write plugin state"));
return true; return true;
} }
@@ -276,18 +243,3 @@ bool DataState::setPluginEnabled(const std::string& name, bool enabled) {
return false; return false;
} }
void DataState::purgeAllCache() {
std::error_code ec;
if (!std::filesystem::exists(getDataStatePath()) && !ec) {
std::println("{}", infoString("Nothing to do"));
return;
}
const auto PATH = getDataStatePath().string();
if (PATH.contains('\''))
return;
// scary!
if (!NSys::root::removeRecursive(PATH))
Debug::die("Failed to run a superuser cmd");
}

View File

@@ -1,5 +1,4 @@
#pragma once #pragma once
#include <filesystem>
#include <string> #include <string>
#include <vector> #include <vector>
#include "Plugin.hpp" #include "Plugin.hpp"
@@ -10,16 +9,14 @@ struct SGlobalState {
}; };
namespace DataState { namespace DataState {
std::filesystem::path getDataStatePath(); std::string getDataStatePath();
std::string getHeadersPath(); std::string getHeadersPath();
std::vector<std::filesystem::path> getPluginStates(); void ensureStateStoreExists();
void ensureStateStoreExists(); void addNewPluginRepo(const SPluginRepository& repo);
void addNewPluginRepo(const SPluginRepository& repo); void removePluginRepo(const std::string& urlOrName);
void removePluginRepo(const std::string& urlOrName); bool pluginRepoExists(const std::string& urlOrName);
bool pluginRepoExists(const std::string& urlOrName); void updateGlobalState(const SGlobalState& state);
void updateGlobalState(const SGlobalState& state); SGlobalState getGlobalState();
void purgeAllCache(); bool setPluginEnabled(const std::string& name, bool enabled);
SGlobalState getGlobalState(); std::vector<SPluginRepository> getAllRepositories();
bool setPluginEnabled(const std::string& name, bool enabled);
std::vector<SPluginRepository> getAllRepositories();
}; };

View File

@@ -1,89 +0,0 @@
#include "HyprlandSocket.hpp"
#include <pwd.h>
#include <sys/socket.h>
#include "../helpers/StringUtils.hpp"
#include <print>
#include <sys/un.h>
#include <unistd.h>
#include <cstring>
#include <hyprutils/memory/Casts.hpp>
using namespace Hyprutils::Memory;
static int getUID() {
const auto UID = getuid();
const auto PWUID = getpwuid(UID);
return PWUID ? PWUID->pw_uid : UID;
}
static std::string getRuntimeDir() {
const auto XDG = getenv("XDG_RUNTIME_DIR");
if (!XDG) {
const std::string USERID = std::to_string(getUID());
return "/run/user/" + USERID + "/hypr";
}
return std::string{XDG} + "/hypr";
}
std::string NHyprlandSocket::send(const std::string& cmd) {
const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);
if (SERVERSOCKET < 0) {
std::println("{}", failureString("Couldn't open a socket (1)"));
return "";
}
const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
if (!HIS) {
std::println("{}", failureString("HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?) (3)"));
return "";
}
sockaddr_un serverAddress = {0};
serverAddress.sun_family = AF_UNIX;
std::string socketPath = getRuntimeDir() + "/" + HIS + "/.socket.sock";
strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);
if (connect(SERVERSOCKET, rc<sockaddr*>(&serverAddress), SUN_LEN(&serverAddress)) < 0) {
std::println("{}", failureString("Couldn't connect to " + socketPath + ". (4)"));
return "";
}
auto sizeWritten = write(SERVERSOCKET, cmd.c_str(), cmd.length());
if (sizeWritten < 0) {
std::println("{}", failureString("Couldn't write (5)"));
return "";
}
std::string reply = "";
constexpr size_t BUFFER_SIZE = 8192;
char buffer[BUFFER_SIZE] = {0};
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);
if (sizeWritten < 0) {
std::println("{}", failureString("Couldn't read (6)"));
return "";
}
reply += std::string(buffer, sizeWritten);
while (sizeWritten == BUFFER_SIZE) {
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);
if (sizeWritten < 0) {
std::println("{}", failureString("Couldn't read (7)"));
return "";
}
reply += std::string(buffer, sizeWritten);
}
close(SERVERSOCKET);
return reply;
}

View File

@@ -1,7 +0,0 @@
#pragma once
#include <string>
namespace NHyprlandSocket {
std::string send(const std::string& cmd);
};

View File

@@ -1,12 +1,6 @@
#include "Manifest.hpp" #include "Manifest.hpp"
#include <toml++/toml.hpp> #include <toml++/toml.hpp>
#include <algorithm> #include <iostream>
// Alphanumerics and -_ allowed for plugin names. No magic names.
// [A-Za-z0-9\-_]*
static bool validManifestName(const std::string_view& n) {
return std::ranges::all_of(n, [](const char& c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '-' || c == '_' || c == '=' || (c >= '0' && c <= '9'); });
}
CManifest::CManifest(const eManifestType type, const std::string& path) { CManifest::CManifest(const eManifestType type, const std::string& path) {
auto manifest = toml::parse_file(path); auto manifest = toml::parse_file(path);
@@ -17,17 +11,11 @@ CManifest::CManifest(const eManifestType type, const std::string& path) {
continue; continue;
CManifest::SManifestPlugin plugin; CManifest::SManifestPlugin plugin;
if (!validManifestName(key.str())) {
m_good = false;
return;
}
plugin.name = key; plugin.name = key;
m_plugins.push_back(plugin); m_vPlugins.push_back(plugin);
} }
for (auto& plugin : m_plugins) { for (auto& plugin : m_vPlugins) {
plugin.description = manifest[plugin.name]["description"].value_or("?"); plugin.description = manifest[plugin.name]["description"].value_or("?");
plugin.version = manifest[plugin.name]["version"].value_or("?"); plugin.version = manifest[plugin.name]["version"].value_or("?");
plugin.output = manifest[plugin.name]["build"]["output"].value_or("?"); plugin.output = manifest[plugin.name]["build"]["output"].value_or("?");
@@ -49,21 +37,21 @@ CManifest::CManifest(const eManifestType type, const std::string& path) {
} }
if (plugin.output.empty() || plugin.buildSteps.empty()) { if (plugin.output.empty() || plugin.buildSteps.empty()) {
m_good = false; m_bGood = false;
return; return;
} }
} }
} else if (type == MANIFEST_HYPRPM) { } else if (type == MANIFEST_HYPRPM) {
m_repository.name = manifest["repository"]["name"].value_or(""); m_sRepository.name = manifest["repository"]["name"].value_or("");
auto authors = manifest["repository"]["authors"].as_array(); auto authors = manifest["repository"]["authors"].as_array();
if (authors) { if (authors) {
for (auto&& a : *authors) { for (auto&& a : *authors) {
m_repository.authors.push_back(a.as_string()->value_or("?")); m_sRepository.authors.push_back(a.as_string()->value_or("?"));
} }
} else { } else {
auto author = manifest["repository"]["author"].value_or(""); auto author = manifest["repository"]["author"].value_or("");
if (!std::string{author}.empty()) if (!std::string{author}.empty())
m_repository.authors.push_back(author); m_sRepository.authors.push_back(author);
} }
auto pins = manifest["repository"]["commit_pins"].as_array(); auto pins = manifest["repository"]["commit_pins"].as_array();
@@ -71,7 +59,7 @@ CManifest::CManifest(const eManifestType type, const std::string& path) {
for (auto&& pin : *pins) { for (auto&& pin : *pins) {
auto pinArr = pin.as_array(); auto pinArr = pin.as_array();
if (pinArr && pinArr->get(1)) if (pinArr && pinArr->get(1))
m_repository.commitPins.push_back(std::make_pair<>(pinArr->get(0)->as_string()->get(), pinArr->get(1)->as_string()->get())); m_sRepository.commitPins.push_back(std::make_pair<>(pinArr->get(0)->as_string()->get(), pinArr->get(1)->as_string()->get()));
} }
} }
@@ -80,17 +68,11 @@ CManifest::CManifest(const eManifestType type, const std::string& path) {
continue; continue;
CManifest::SManifestPlugin plugin; CManifest::SManifestPlugin plugin;
if (!validManifestName(key.str())) {
m_good = false;
return;
}
plugin.name = key; plugin.name = key;
m_plugins.push_back(plugin); m_vPlugins.push_back(plugin);
} }
for (auto& plugin : m_plugins) { for (auto& plugin : m_vPlugins) {
plugin.description = manifest[plugin.name]["description"].value_or("?"); plugin.description = manifest[plugin.name]["description"].value_or("?");
plugin.output = manifest[plugin.name]["output"].value_or("?"); plugin.output = manifest[plugin.name]["output"].value_or("?");
plugin.since = manifest[plugin.name]["since_hyprland"].value_or(0); plugin.since = manifest[plugin.name]["since_hyprland"].value_or(0);
@@ -112,12 +94,12 @@ CManifest::CManifest(const eManifestType type, const std::string& path) {
} }
if (plugin.output.empty() || plugin.buildSteps.empty()) { if (plugin.output.empty() || plugin.buildSteps.empty()) {
m_good = false; m_bGood = false;
return; return;
} }
} }
} else { } else {
// ??? // ???
m_good = false; m_bGood = false;
} }
} }

View File

@@ -27,8 +27,8 @@ class CManifest {
std::string name; std::string name;
std::vector<std::string> authors; std::vector<std::string> authors;
std::vector<std::pair<std::string, std::string>> commitPins; std::vector<std::pair<std::string, std::string>> commitPins;
} m_repository; } m_sRepository;
std::vector<SManifestPlugin> m_plugins; std::vector<SManifestPlugin> m_vPlugins;
bool m_good = true; bool m_bGood = true;
}; };

View File

@@ -4,14 +4,13 @@
#include "../progress/CProgressBar.hpp" #include "../progress/CProgressBar.hpp"
#include "Manifest.hpp" #include "Manifest.hpp"
#include "DataState.hpp" #include "DataState.hpp"
#include "HyprlandSocket.hpp"
#include "../helpers/Sys.hpp"
#include "../helpers/Die.hpp"
#include <cstdio> #include <cstdio>
#include <iostream> #include <iostream>
#include <array>
#include <filesystem> #include <filesystem>
#include <print> #include <print>
#include <thread>
#include <fstream> #include <fstream>
#include <algorithm> #include <algorithm>
#include <format> #include <format>
@@ -22,65 +21,37 @@
#include <unistd.h> #include <unistd.h>
#include <toml++/toml.hpp> #include <toml++/toml.hpp>
#include <glaze/glaze.hpp>
#include <hyprutils/string/String.hpp> #include <hyprutils/string/String.hpp>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/Casts.hpp>
using namespace Hyprutils::String; using namespace Hyprutils::String;
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
static std::string execAndGet(std::string cmd) { static std::string execAndGet(std::string cmd) {
cmd += " 2>&1"; cmd += " 2>&1";
std::array<char, 128> buffer;
std::string result;
using PcloseType = int (*)(FILE*);
const std::unique_ptr<FILE, PcloseType> pipe(popen(cmd.c_str(), "r"), static_cast<PcloseType>(pclose));
if (!pipe)
return "";
CProcess proc("/bin/sh", {"-c", cmd}); result.reserve(buffer.size());
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
if (!proc.runSync()) result += buffer.data();
return "error";
return proc.stdOut();
}
static std::string getTempRoot() {
static auto ENV = getenv("XDG_RUNTIME_DIR");
if (!ENV) {
std::cerr << "\nERROR: XDG_RUNTIME_DIR not set!\n";
exit(1);
} }
return result;
const auto STR = ENV + std::string{"/hyprpm/"};
return STR;
} }
CPluginManager::CPluginManager() { SHyprlandVersion CPluginManager::getHyprlandVersion() {
if (NSys::isSuperuser()) static SHyprlandVersion ver;
Debug::die("Don't run hyprpm as a superuser."); static bool once = false;
m_szUsername = getpwuid(NSys::getUID())->pw_name; if (once)
} return ver;
SHyprlandVersion CPluginManager::getHyprlandVersion(bool running) { once = true;
static bool onceRunning = false; const auto HLVERCALL = execAndGet("hyprctl version");
static bool onceInstalled = false;
static SHyprlandVersion verRunning;
static SHyprlandVersion verInstalled;
if (onceRunning && running)
return verRunning;
if (onceInstalled && !running)
return verInstalled;
if (running)
onceRunning = true;
else
onceInstalled = true;
const auto HLVERCALL = running ? NHyprlandSocket::send("/version") : execAndGet("Hyprland --version");
if (m_bVerbose) if (m_bVerbose)
std::println("{}", verboseString("{} version returned: {}", running ? "running" : "installed", HLVERCALL)); std::println("{}", verboseString("version returned: {}", HLVERCALL));
if (!HLVERCALL.contains("Tag:")) { if (!HLVERCALL.contains("Tag:")) {
std::println(stderr, "\n{}", failureString("You don't seem to be running Hyprland.")); std::println(stderr, "\n{}", failureString("You don't seem to be running Hyprland."));
@@ -94,13 +65,13 @@ SHyprlandVersion CPluginManager::getHyprlandVersion(bool running) {
hlbranch = hlbranch.substr(0, hlbranch.find(" at commit ")); hlbranch = hlbranch.substr(0, hlbranch.find(" at commit "));
std::string hldate = HLVERCALL.substr(HLVERCALL.find("Date: ") + 6); std::string hldate = HLVERCALL.substr(HLVERCALL.find("Date: ") + 6);
hldate = hldate.substr(0, hldate.find('\n')); hldate = hldate.substr(0, hldate.find("\n"));
std::string hlcommits; std::string hlcommits;
if (HLVERCALL.contains("commits:")) { if (HLVERCALL.contains("commits:")) {
hlcommits = HLVERCALL.substr(HLVERCALL.find("commits:") + 9); hlcommits = HLVERCALL.substr(HLVERCALL.find("commits:") + 9);
hlcommits = hlcommits.substr(0, hlcommits.find(' ')); hlcommits = hlcommits.substr(0, hlcommits.find(" "));
} }
int commits = 0; int commits = 0;
@@ -111,18 +82,12 @@ SHyprlandVersion CPluginManager::getHyprlandVersion(bool running) {
if (m_bVerbose) if (m_bVerbose)
std::println("{}", verboseString("parsed commit {} at branch {} on {}, commits {}", hlcommit, hlbranch, hldate, commits)); std::println("{}", verboseString("parsed commit {} at branch {} on {}, commits {}", hlcommit, hlbranch, hldate, commits));
auto ver = SHyprlandVersion{hlbranch, hlcommit, hldate, commits}; ver = SHyprlandVersion{hlbranch, hlcommit, hldate, commits};
if (running)
verRunning = ver;
else
verInstalled = ver;
return ver; return ver;
} }
bool CPluginManager::createSafeDirectory(const std::string& path) { bool CPluginManager::createSafeDirectory(const std::string& path) {
if (path.empty() || !path.starts_with(getTempRoot())) if (path.empty() || !path.starts_with("/tmp"))
return false; return false;
if (std::filesystem::exists(path)) if (std::filesystem::exists(path))
@@ -141,8 +106,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
const auto HLVER = getHyprlandVersion(); const auto HLVER = getHyprlandVersion();
if (!hasDeps()) { if (!hasDeps()) {
std::println(stderr, "\n{}", std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio"));
failureString("Could not clone the plugin repository. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio, pkg-config, git, g++, gcc"));
return false; return false;
} }
@@ -154,18 +118,13 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
auto GLOBALSTATE = DataState::getGlobalState(); auto GLOBALSTATE = DataState::getGlobalState();
if (!GLOBALSTATE.dontWarnInstall) { if (!GLOBALSTATE.dontWarnInstall) {
std::println("{}!{} Disclaimer: {}", Colors::YELLOW, Colors::RED, Colors::RESET); std::println("{}!{} Disclaimer: {}", Colors::YELLOW, Colors::RED, Colors::RESET);
std::println("plugins, especially not official, have no guarantee of stability, availability or security.\n" std::println("plugins, especially not official, have no guarantee of stability, availablity or security.\n"
"Run them at your own risk.\n" "Run them at your own risk.\n"
"This message will not appear again."); "This message will not appear again.");
GLOBALSTATE.dontWarnInstall = true; GLOBALSTATE.dontWarnInstall = true;
DataState::updateGlobalState(GLOBALSTATE); DataState::updateGlobalState(GLOBALSTATE);
} }
if (GLOBALSTATE.headersHashCompiled.empty()) {
std::println("\n{}", failureString("Cannot find headers in the global state. Try running hyprpm update first."));
return false;
}
std::cout << Colors::GREEN << "" << Colors::RESET << Colors::RED << " adding a new plugin repository " << Colors::RESET << "from " << url << "\n " << Colors::RED std::cout << Colors::GREEN << "" << Colors::RESET << Colors::RED << " adding a new plugin repository " << Colors::RESET << "from " << url << "\n " << Colors::RED
<< "MAKE SURE" << Colors::RESET << " that you trust the authors. " << Colors::RED << "DO NOT" << Colors::RESET << "MAKE SURE" << Colors::RESET << " that you trust the authors. " << Colors::RED << "DO NOT" << Colors::RESET
<< " install random plugins without verifying the code and author.\n " << " install random plugins without verifying the code and author.\n "
@@ -186,17 +145,17 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
progress.print(); progress.print();
if (!std::filesystem::exists(getTempRoot())) { if (!std::filesystem::exists("/tmp/hyprpm")) {
std::filesystem::create_directory(getTempRoot()); std::filesystem::create_directory("/tmp/hyprpm");
std::filesystem::permissions(getTempRoot(), std::filesystem::perms::owner_all, std::filesystem::perm_options::replace); std::filesystem::permissions("/tmp/hyprpm", std::filesystem::perms::all, std::filesystem::perm_options::replace);
} else if (!std::filesystem::is_directory(getTempRoot())) { } else if (!std::filesystem::is_directory("/tmp/hyprpm")) {
std::println(stderr, "\n{}", failureString("Could not prepare working dir for hyprpm")); std::println(stderr, "\n{}", failureString("Could not prepare working dir for hyprpm"));
return false; return false;
} }
const std::string USERNAME = getpwuid(getuid())->pw_name; const std::string USERNAME = getpwuid(getuid())->pw_name;
m_szWorkingPluginDirectory = getTempRoot() + USERNAME; m_szWorkingPluginDirectory = "/tmp/hyprpm/" + USERNAME;
if (!createSafeDirectory(m_szWorkingPluginDirectory)) { if (!createSafeDirectory(m_szWorkingPluginDirectory)) {
std::println(stderr, "\n{}", failureString("Could not prepare working dir for repo")); std::println(stderr, "\n{}", failureString("Could not prepare working dir for repo"));
@@ -205,7 +164,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
progress.printMessageAbove(infoString("Cloning {}", url)); progress.printMessageAbove(infoString("Cloning {}", url));
std::string ret = execAndGet(std::format("cd {} && git clone --recursive {} {}", getTempRoot(), url, USERNAME)); std::string ret = execAndGet("cd /tmp/hyprpm && git clone --recursive " + url + " " + USERNAME);
if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) { if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) {
std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. shell returned:\n{}", ret)); std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. shell returned:\n{}", ret));
@@ -243,14 +202,14 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
return false; return false;
} }
if (!pManifest->m_good) { if (!pManifest->m_bGood) {
std::println(stderr, "\n{}", failureString("The provided plugin repository has a corrupted manifest")); std::println(stderr, "\n{}", failureString("The provided plugin repository has a corrupted manifest"));
return false; return false;
} }
progress.m_iSteps = 2; progress.m_iSteps = 2;
progress.printMessageAbove(successString("parsed manifest, found " + std::to_string(pManifest->m_plugins.size()) + " plugins:")); progress.printMessageAbove(successString("parsed manifest, found " + std::to_string(pManifest->m_vPlugins.size()) + " plugins:"));
for (auto const& pl : pManifest->m_plugins) { for (auto const& pl : pManifest->m_vPlugins) {
std::string message = "" + pl.name + " by "; std::string message = "" + pl.name + " by ";
for (auto const& a : pl.authors) { for (auto const& a : pl.authors) {
message += a + ", "; message += a + ", ";
@@ -263,12 +222,12 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
progress.printMessageAbove(message); progress.printMessageAbove(message);
} }
if (rev.empty() && !pManifest->m_repository.commitPins.empty()) { if (!pManifest->m_sRepository.commitPins.empty()) {
// check commit pins // check commit pins
progress.printMessageAbove(infoString("Manifest has {} pins, checking", pManifest->m_repository.commitPins.size())); progress.printMessageAbove(infoString("Manifest has {} pins, checking", pManifest->m_sRepository.commitPins.size()));
for (auto const& [hl, plugin] : pManifest->m_repository.commitPins) { for (auto const& [hl, plugin] : pManifest->m_sRepository.commitPins) {
if (hl != HLVER.hash) if (hl != HLVER.hash)
continue; continue;
@@ -291,7 +250,6 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
if (HEADERSSTATUS != HEADERS_OK) { if (HEADERSSTATUS != HEADERS_OK) {
std::println("\n{}", headerError(HEADERSSTATUS)); std::println("\n{}", headerError(HEADERSSTATUS));
std::println("\n{}", infoString("if the problem persists, try running hyprpm purge-cache."));
return false; return false;
} }
@@ -300,7 +258,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
progress.m_szCurrentMessage = "Building plugin(s)"; progress.m_szCurrentMessage = "Building plugin(s)";
progress.print(); progress.print();
for (auto& p : pManifest->m_plugins) { for (auto& p : pManifest->m_vPlugins) {
std::string out; std::string out;
if (p.since > HLVER.commits && HLVER.commits >= 1 /* for --depth 1 clones, we can't check this. */) { if (p.since > HLVER.commits && HLVER.commits >= 1 /* for --depth 1 clones, we can't check this. */) {
@@ -343,11 +301,11 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
std::string repohash = execAndGet("cd " + m_szWorkingPluginDirectory + " && git rev-parse HEAD"); std::string repohash = execAndGet("cd " + m_szWorkingPluginDirectory + " && git rev-parse HEAD");
if (repohash.length() > 0) if (repohash.length() > 0)
repohash.pop_back(); repohash.pop_back();
repo.name = pManifest->m_repository.name.empty() ? url.substr(url.find_last_of('/') + 1) : pManifest->m_repository.name; repo.name = pManifest->m_sRepository.name.empty() ? url.substr(url.find_last_of('/') + 1) : pManifest->m_sRepository.name;
repo.url = url; repo.url = url;
repo.rev = rev; repo.rev = rev;
repo.hash = repohash; repo.hash = repohash;
for (auto const& p : pManifest->m_plugins) { for (auto const& p : pManifest->m_vPlugins) {
repo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, false, p.failed}); repo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, false, p.failed});
} }
DataState::addNewPluginRepo(repo); DataState::addNewPluginRepo(repo);
@@ -389,14 +347,14 @@ bool CPluginManager::removePluginRepo(const std::string& urlOrName) {
} }
eHeadersErrors CPluginManager::headersValid() { eHeadersErrors CPluginManager::headersValid() {
const auto HLVER = getHyprlandVersion(false); const auto HLVER = getHyprlandVersion();
if (!std::filesystem::exists(DataState::getHeadersPath() + "/share/pkgconfig/hyprland.pc")) if (!std::filesystem::exists(DataState::getHeadersPath() + "/share/pkgconfig/hyprland.pc"))
return HEADERS_MISSING; return HEADERS_MISSING;
// find headers commit // find headers commit
const std::string& cmd = std::format("PKG_CONFIG_PATH=\"{}/share/pkgconfig\" pkgconf --cflags --keep-system-cflags hyprland", DataState::getHeadersPath()); const std::string& cmd = std::format("PKG_CONFIG_PATH=\"{}/share/pkgconfig\" pkgconf --cflags --keep-system-cflags hyprland", DataState::getHeadersPath());
auto headers = execAndGet(cmd); auto headers = execAndGet(cmd.c_str());
if (!headers.contains("-I/")) if (!headers.contains("-I/"))
return HEADERS_MISSING; return HEADERS_MISSING;
@@ -451,16 +409,16 @@ bool CPluginManager::updateHeaders(bool force) {
DataState::ensureStateStoreExists(); DataState::ensureStateStoreExists();
const auto HLVER = getHyprlandVersion(false); const auto HLVER = getHyprlandVersion();
if (!hasDeps()) { if (!hasDeps()) {
std::println("\n{}", failureString("Could not update. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio, pkg-config, git, g++, gcc")); std::println("\n{}", failureString("Could not update. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio"));
return false; return false;
} }
if (!std::filesystem::exists(getTempRoot())) { if (!std::filesystem::exists("/tmp/hyprpm")) {
std::filesystem::create_directory(getTempRoot()); std::filesystem::create_directory("/tmp/hyprpm");
std::filesystem::permissions(getTempRoot(), std::filesystem::perms::owner_all, std::filesystem::perm_options::replace); std::filesystem::permissions("/tmp/hyprpm", std::filesystem::perms::all, std::filesystem::perm_options::replace);
} }
if (!force && headersValid() == HEADERS_OK) { if (!force && headersValid() == HEADERS_OK) {
@@ -475,16 +433,14 @@ bool CPluginManager::updateHeaders(bool force) {
progress.print(); progress.print();
const std::string USERNAME = getpwuid(getuid())->pw_name; const std::string USERNAME = getpwuid(getuid())->pw_name;
const auto WORKINGDIR = getTempRoot() + "hyprland-" + USERNAME; const auto WORKINGDIR = "/tmp/hyprpm/hyprland-" + USERNAME;
if (!createSafeDirectory(WORKINGDIR)) { if (!createSafeDirectory(WORKINGDIR)) {
std::println("\n{}", failureString("Could not prepare working dir for hl")); std::println("\n{}", failureString("Could not prepare working dir for hl"));
return false; return false;
} }
const auto& HL_URL = m_szCustomHlUrl.empty() ? "https://github.com/hyprwm/Hyprland" : m_szCustomHlUrl; progress.printMessageAbove(statusString("!", Colors::YELLOW, "Cloning https://github.com/hyprwm/Hyprland, this might take a moment."));
progress.printMessageAbove(statusString("!", Colors::YELLOW, "Cloning {}, this might take a moment.", HL_URL));
const bool bShallow = (HLVER.branch == "main") && !m_bNoShallow; const bool bShallow = (HLVER.branch == "main") && !m_bNoShallow;
@@ -496,11 +452,11 @@ bool CPluginManager::updateHeaders(bool force) {
progress.printMessageAbove(verboseString("will shallow since: {}", SHALLOW_DATE)); progress.printMessageAbove(verboseString("will shallow since: {}", SHALLOW_DATE));
std::string ret = std::string ret =
execAndGet(std::format("cd {} && git clone --recursive {} hyprland-{}{}", getTempRoot(), HL_URL, USERNAME, (bShallow ? " --shallow-since='" + SHALLOW_DATE + "'" : ""))); execAndGet("cd /tmp/hyprpm && git clone --recursive https://github.com/hyprwm/Hyprland hyprland-" + USERNAME + (bShallow ? " --shallow-since='" + SHALLOW_DATE + "'" : ""));
if (!std::filesystem::exists(WORKINGDIR)) { if (!std::filesystem::exists(WORKINGDIR)) {
progress.printMessageAbove(failureString("Clone failed. Retrying without shallow.")); progress.printMessageAbove(failureString("Clone failed. Retrying without shallow."));
ret = execAndGet(std::format("cd {} && git clone --recursive {} hyprland-{}", getTempRoot(), HL_URL, USERNAME)); ret = execAndGet("cd /tmp/hyprpm && git clone --recursive https://github.com/hyprwm/hyprland hyprland-" + USERNAME);
} }
if (!std::filesystem::exists(WORKINGDIR + "/.git")) { if (!std::filesystem::exists(WORKINGDIR + "/.git")) {
@@ -567,21 +523,13 @@ bool CPluginManager::updateHeaders(bool force) {
progress.m_szCurrentMessage = "Installing sources"; progress.m_szCurrentMessage = "Installing sources";
progress.print(); progress.print();
std::string cmd = std::format("sed -i -e \"s#PREFIX = /usr/local#PREFIX = {}#\" {}/Makefile", DataState::getHeadersPath(), WORKINGDIR); const std::string& cmd =
std::format("sed -i -e \"s#PREFIX = /usr/local#PREFIX = {}#\" {}/Makefile && cd {} && make installheaders", DataState::getHeadersPath(), WORKINGDIR, WORKINGDIR);
if (m_bVerbose) if (m_bVerbose)
progress.printMessageAbove(verboseString("prepare install will run: {}", cmd)); progress.printMessageAbove(verboseString("installation will run: {}", cmd));
ret = execAndGet(cmd); ret = execAndGet(cmd);
cmd = std::format("make -C '{}' installheaders && chmod -R 644 '{}' && find '{}' -type d -exec chmod a+x {{}} \\;", WORKINGDIR, DataState::getHeadersPath(),
DataState::getHeadersPath());
if (m_bVerbose)
progress.printMessageAbove(verboseString("install will run as sudo: {}", cmd));
// WORKINGDIR and headersPath should not contain anything unsafe. Usernames can't contain cmd exec parts.
ret = NSys::root::runAsSuperuserUnsafe(cmd);
if (m_bVerbose) if (m_bVerbose)
std::println("{}", verboseString("installer returned: {}", ret)); std::println("{}", verboseString("installer returned: {}", ret));
@@ -595,14 +543,9 @@ bool CPluginManager::updateHeaders(bool force) {
progress.m_szCurrentMessage = "Done!"; progress.m_szCurrentMessage = "Done!";
progress.print(); progress.print();
auto GLOBALSTATE = DataState::getGlobalState();
GLOBALSTATE.headersHashCompiled = HLVER.hash;
DataState::updateGlobalState(GLOBALSTATE);
std::print("\n"); std::print("\n");
} else { } else {
progress.printMessageAbove(failureString("failed to install headers with error code {} ({})", sc<int>(HEADERSVALID), headerErrorShort(HEADERSVALID))); progress.printMessageAbove(failureString("failed to install headers with error code {} ({})", (int)HEADERSVALID, headerErrorShort(HEADERSVALID)));
progress.printMessageAbove(infoString("if the problem persists, try running hyprpm purge-cache."));
progress.m_iSteps = 5; progress.m_iSteps = 5;
progress.m_szCurrentMessage = "Failed"; progress.m_szCurrentMessage = "Failed";
progress.print(); progress.print();
@@ -628,7 +571,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
return true; return true;
} }
const auto HLVER = getHyprlandVersion(false); const auto HLVER = getHyprlandVersion();
CProgressBar progress; CProgressBar progress;
progress.m_iMaxSteps = REPOS.size() * 2 + 2; progress.m_iMaxSteps = REPOS.size() * 2 + 2;
@@ -637,7 +580,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
progress.print(); progress.print();
const std::string USERNAME = getpwuid(getuid())->pw_name; const std::string USERNAME = getpwuid(getuid())->pw_name;
m_szWorkingPluginDirectory = getTempRoot() + USERNAME; m_szWorkingPluginDirectory = "/tmp/hyprpm/" + USERNAME;
for (auto const& repo : REPOS) { for (auto const& repo : REPOS) {
bool update = forceUpdateAll; bool update = forceUpdateAll;
@@ -652,7 +595,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
progress.printMessageAbove(infoString("Cloning {}", repo.url)); progress.printMessageAbove(infoString("Cloning {}", repo.url));
std::string ret = execAndGet(std::format("cd {} && git clone --recursive {} {}", getTempRoot(), repo.url, USERNAME)); std::string ret = execAndGet("cd /tmp/hyprpm && git clone --recursive " + repo.url + " " + USERNAME);
if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) { if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) {
std::println("{}", failureString("could not clone repo: shell returned: {}", ret)); std::println("{}", failureString("could not clone repo: shell returned: {}", ret));
@@ -709,17 +652,17 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
continue; continue;
} }
if (!pManifest->m_good) { if (!pManifest->m_bGood) {
std::println(stderr, "\n{}", failureString("The provided plugin repository has a bad manifest")); std::println(stderr, "\n{}", failureString("The provided plugin repository has a corrupted manifest"));
continue; continue;
} }
if (repo.rev.empty() && !pManifest->m_repository.commitPins.empty()) { if (repo.rev.empty() && !pManifest->m_sRepository.commitPins.empty()) {
// check commit pins unless a revision is specified // check commit pins unless a revision is specified
progress.printMessageAbove(infoString("Manifest has {} pins, checking", pManifest->m_repository.commitPins.size())); progress.printMessageAbove(infoString("Manifest has {} pins, checking", pManifest->m_sRepository.commitPins.size()));
for (auto const& [hl, plugin] : pManifest->m_repository.commitPins) { for (auto const& [hl, plugin] : pManifest->m_sRepository.commitPins) {
if (hl != HLVER.hash) if (hl != HLVER.hash)
continue; continue;
@@ -729,7 +672,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
} }
} }
for (auto& p : pManifest->m_plugins) { for (auto& p : pManifest->m_vPlugins) {
std::string out; std::string out;
if (p.since > HLVER.commits && HLVER.commits >= 1000 /* for shallow clones, we can't check this. 1000 is an arbitrary number I chose. */) { if (p.since > HLVER.commits && HLVER.commits >= 1000 /* for shallow clones, we can't check this. 1000 is an arbitrary number I chose. */) {
@@ -771,7 +714,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
if (repohash.length() > 0) if (repohash.length() > 0)
repohash.pop_back(); repohash.pop_back();
newrepo.hash = repohash; newrepo.hash = repohash;
for (auto const& p : pManifest->m_plugins) { for (auto const& p : pManifest->m_vPlugins) {
const auto OLDPLUGINIT = std::find_if(repo.plugins.begin(), repo.plugins.end(), [&](const auto& other) { return other.name == p.name; }); const auto OLDPLUGINIT = std::find_if(repo.plugins.begin(), repo.plugins.end(), [&](const auto& other) { return other.name == p.name; });
newrepo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false}); newrepo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false});
} }
@@ -814,7 +757,7 @@ bool CPluginManager::disablePlugin(const std::string& name) {
return ret; return ret;
} }
ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState(bool forceReload) { ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState() {
if (headersValid() != HEADERS_OK) { if (headersValid() != HEADERS_OK) {
std::println(stderr, "\n{}", failureString("headers are not up-to-date, please run hyprpm update.")); std::println(stderr, "\n{}", failureString("headers are not up-to-date, please run hyprpm update."));
return LOADSTATE_HEADERS_OUTDATED; return LOADSTATE_HEADERS_OUTDATED;
@@ -823,28 +766,35 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState(bool forceReload)
const auto HOME = getenv("HOME"); const auto HOME = getenv("HOME");
const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE"); const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
if (!HOME || !HIS) { if (!HOME || !HIS) {
std::println(stderr, "PluginManager: no $HOME or $HYPRLAND_INSTANCE_SIGNATURE"); std::println(stderr, "PluginManager: no $HOME or HIS");
return LOADSTATE_FAIL; return LOADSTATE_FAIL;
} }
const auto HYPRPMPATH = DataState::getDataStatePath(); const auto HYPRPMPATH = DataState::getDataStatePath() + "/";
const auto json = glz::read_json<glz::json_t::array_t>(NHyprlandSocket::send("j/plugins list")); auto pluginLines = execAndGet("hyprctl plugins list | grep Plugin");
if (!json) {
std::println(stderr, "PluginManager: couldn't parse plugin list output");
return LOADSTATE_FAIL;
}
std::vector<std::string> loadedPlugins; std::vector<std::string> loadedPlugins;
for (const auto& plugin : json.value()) {
if (!plugin.is_object() || !plugin.contains("name")) {
std::println(stderr, "PluginManager: couldn't parse plugin object");
return LOADSTATE_FAIL;
}
loadedPlugins.emplace_back(plugin["name"].get<std::string>());
}
std::println("{}", successString("Ensuring plugin load state")); std::println("{}", successString("Ensuring plugin load state"));
// iterate line by line
while (!pluginLines.empty()) {
auto plLine = pluginLines.substr(0, pluginLines.find('\n'));
if (pluginLines.find('\n') != std::string::npos)
pluginLines = pluginLines.substr(pluginLines.find('\n') + 1);
else
pluginLines = "";
if (plLine.back() != ':')
continue;
plLine = plLine.substr(7);
plLine = plLine.substr(0, plLine.find(" by "));
loadedPlugins.push_back(plLine);
}
// get state // get state
const auto REPOS = DataState::getAllRepositories(); const auto REPOS = DataState::getAllRepositories();
@@ -870,20 +820,12 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState(bool forceReload)
return ""; return "";
}; };
// if any of the loadUnloadPlugin calls return false, this is true // unload disabled plugins
// bcs that means the header version doesn't match the running version
// (and Hyprland needs to restart)
bool hyprlandVersionMismatch = false;
// unload disabled plugins (or all if forceReload is true)
for (auto const& p : loadedPlugins) { for (auto const& p : loadedPlugins) {
if (forceReload || !enabled(p)) { if (!enabled(p)) {
// unload // unload
if (!loadUnloadPlugin(HYPRPMPATH / repoForName(p) / (p + ".so"), false)) { loadUnloadPlugin(HYPRPMPATH + repoForName(p) + "/" + p + ".so", false);
std::println("{}", infoString("{} will be unloaded after restarting Hyprland", p)); std::println("{}", successString("Unloaded {}", p));
hyprlandVersionMismatch = true;
} else
std::println("{}", successString("Unloaded {}", p));
} }
} }
@@ -893,36 +835,24 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState(bool forceReload)
if (!p.enabled) if (!p.enabled)
continue; continue;
if (!forceReload && std::find_if(loadedPlugins.begin(), loadedPlugins.end(), [&](const auto& other) { return other == p.name; }) != loadedPlugins.end()) if (std::find_if(loadedPlugins.begin(), loadedPlugins.end(), [&](const auto& other) { return other == p.name; }) != loadedPlugins.end())
continue; continue;
if (!loadUnloadPlugin(HYPRPMPATH / repoForName(p.name) / p.filename, true)) { loadUnloadPlugin(HYPRPMPATH + repoForName(p.name) + "/" + p.filename, true);
std::println("{}", infoString("{} will be loaded after restarting Hyprland", p.name)); std::println("{}", successString("Loaded {}", p.name));
hyprlandVersionMismatch = true;
} else
std::println("{}", successString("Loaded {}", p.name));
} }
} }
std::println("{}", successString("Plugin load state ensured")); std::println("{}", successString("Plugin load state ensured"));
return hyprlandVersionMismatch ? LOADSTATE_HYPRLAND_UPDATED : LOADSTATE_OK; return LOADSTATE_OK;
} }
bool CPluginManager::loadUnloadPlugin(const std::string& path, bool load) { bool CPluginManager::loadUnloadPlugin(const std::string& path, bool load) {
auto state = DataState::getGlobalState();
auto HLVER = getHyprlandVersion(true);
if (state.headersHashCompiled != HLVER.hash) {
if (load)
std::println("{}", infoString("Running Hyprland version ({}) differs from plugin state ({}), please restart Hyprland.", HLVER.hash, state.headersHashCompiled));
return false;
}
if (load) if (load)
NHyprlandSocket::send("/plugin load " + path); execAndGet("hyprctl plugin load " + path);
else else
NHyprlandSocket::send("/plugin unload " + path); execAndGet("hyprctl plugin unload " + path);
return true; return true;
} }
@@ -947,7 +877,7 @@ void CPluginManager::listAllPlugins() {
} }
void CPluginManager::notify(const eNotifyIcons icon, uint32_t color, int durationMs, const std::string& message) { void CPluginManager::notify(const eNotifyIcons icon, uint32_t color, int durationMs, const std::string& message) {
NHyprlandSocket::send("/notify " + std::to_string(icon) + " " + std::to_string(durationMs) + " " + std::to_string(color) + " " + message); execAndGet("hyprctl notify " + std::to_string((int)icon) + " " + std::to_string(durationMs) + " " + std::to_string(color) + " " + message);
} }
std::string CPluginManager::headerError(const eHeadersErrors err) { std::string CPluginManager::headerError(const eHeadersErrors err) {
@@ -980,15 +910,11 @@ std::string CPluginManager::headerErrorShort(const eHeadersErrors err) {
} }
bool CPluginManager::hasDeps() { bool CPluginManager::hasDeps() {
bool hasAllDeps = true; std::vector<std::string> deps = {"meson", "cpio", "cmake", "pkg-config"};
std::vector<std::string> deps = {"meson", "cpio", "cmake", "pkg-config", "g++", "gcc", "git"};
for (auto const& d : deps) { for (auto const& d : deps) {
if (!execAndGet("command -v " + d).contains("/")) { if (!execAndGet("command -v " + d).contains("/"))
std::println(stderr, "{}", failureString("Missing dependency: {}", d)); return false;
hasAllDeps = false;
}
} }
return hasAllDeps; return true;
} }

View File

@@ -1,6 +1,5 @@
#pragma once #pragma once
#include <cstdint>
#include <memory> #include <memory>
#include <string> #include <string>
#include <utility> #include <utility>
@@ -28,8 +27,7 @@ enum ePluginLoadStateReturn {
LOADSTATE_OK = 0, LOADSTATE_OK = 0,
LOADSTATE_FAIL, LOADSTATE_FAIL,
LOADSTATE_PARTIAL_FAIL, LOADSTATE_PARTIAL_FAIL,
LOADSTATE_HEADERS_OUTDATED, LOADSTATE_HEADERS_OUTDATED
LOADSTATE_HYPRLAND_UPDATED
}; };
struct SHyprlandVersion { struct SHyprlandVersion {
@@ -41,8 +39,6 @@ struct SHyprlandVersion {
class CPluginManager { class CPluginManager {
public: public:
CPluginManager();
bool addNewPluginRepo(const std::string& url, const std::string& rev); bool addNewPluginRepo(const std::string& url, const std::string& rev);
bool removePluginRepo(const std::string& urlOrName); bool removePluginRepo(const std::string& urlOrName);
@@ -54,10 +50,10 @@ class CPluginManager {
bool enablePlugin(const std::string& name); bool enablePlugin(const std::string& name);
bool disablePlugin(const std::string& name); bool disablePlugin(const std::string& name);
ePluginLoadStateReturn ensurePluginsLoadState(bool forceReload = false); ePluginLoadStateReturn ensurePluginsLoadState();
bool loadUnloadPlugin(const std::string& path, bool load); bool loadUnloadPlugin(const std::string& path, bool load);
SHyprlandVersion getHyprlandVersion(bool running = true); SHyprlandVersion getHyprlandVersion();
void notify(const eNotifyIcons icon, uint32_t color, int durationMs, const std::string& message); void notify(const eNotifyIcons icon, uint32_t color, int durationMs, const std::string& message);
@@ -65,7 +61,6 @@ class CPluginManager {
bool m_bVerbose = false; bool m_bVerbose = false;
bool m_bNoShallow = false; bool m_bNoShallow = false;
std::string m_szCustomHlUrl, m_szUsername;
// will delete recursively if exists!! // will delete recursively if exists!!
bool createSafeDirectory(const std::string& path); bool createSafeDirectory(const std::string& path);

View File

@@ -1,15 +0,0 @@
#pragma once
#include <format>
#include <iostream>
// NOLINTNEXTLINE
namespace Debug {
template <typename... Args>
void die(std::format_string<Args...> fmt, Args&&... args) {
const std::string logMsg = std::vformat(fmt.get(), std::make_format_args(args...));
std::cout << "\n[ERR] " << logMsg << "\n";
exit(1);
}
};

View File

@@ -1,169 +0,0 @@
#include "Sys.hpp"
#include "Die.hpp"
#include "StringUtils.hpp"
#include <pwd.h>
#include <unistd.h>
#include <sstream>
#include <print>
#include <filesystem>
#include <algorithm>
#include <sstream>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/string/VarList.hpp>
using namespace Hyprutils::OS;
using namespace Hyprutils::String;
inline constexpr std::array<std::string_view, 3> SUPERUSER_BINARIES = {
"sudo",
"doas",
"run0",
};
static std::string validSubinsAsStr() {
std::ostringstream oss;
auto it = SUPERUSER_BINARIES.begin();
if (it != SUPERUSER_BINARIES.end()) {
oss << *it++;
for (; it != SUPERUSER_BINARIES.end(); ++it)
oss << ", " << *it;
}
return oss.str();
}
static bool executableExistsInPath(const std::string& exe) {
const char* PATHENV = std::getenv("PATH");
if (!PATHENV)
return false;
CVarList paths(PATHENV, 0, ':', true);
std::error_code ec;
for (const auto& PATH : paths) {
std::filesystem::path candidate = std::filesystem::path(PATH) / exe;
if (!std::filesystem::exists(candidate, ec) || ec)
continue;
if (!std::filesystem::is_regular_file(candidate, ec) || ec)
continue;
auto perms = std::filesystem::status(candidate, ec).permissions();
if (ec)
continue;
if ((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none)
return true;
}
return false;
}
static std::string subin() {
static std::string bin;
static bool once = true;
if (!once)
return bin;
for (const auto& BIN : SUPERUSER_BINARIES) {
if (!executableExistsInPath(std::string{BIN}))
continue;
bin = BIN;
break;
}
once = false;
if (bin.empty())
Debug::die("{}", failureString("No valid superuser binary present. Supported: {}", validSubinsAsStr()));
return bin;
}
static bool verifyStringValid(const std::string& s) {
return std::ranges::none_of(s, [](const char& c) { return c == '`' || c == '$' || c == '(' || c == ')' || c == '\'' || c == '"'; });
}
int NSys::getUID() {
const auto UID = getuid();
const auto PWUID = getpwuid(UID);
return PWUID ? PWUID->pw_uid : UID;
}
int NSys::getEUID() {
const auto UID = geteuid();
const auto PWUID = getpwuid(UID);
return PWUID ? PWUID->pw_uid : UID;
}
bool NSys::isSuperuser() {
return getuid() != geteuid() || geteuid() == 0;
}
void NSys::root::cacheSudo() {
// "caches" the sudo so that the prompt later doesn't pop up in a weird spot
// sudo will not ask us again for a moment
CProcess proc(subin(), {"echo", "hyprland"});
proc.runSync();
}
void NSys::root::dropSudo() {
if (subin() != "sudo") {
std::println("{}", infoString("Don't know how to drop timestamp for '{}', ignoring.", subin()));
return;
}
CProcess proc(subin(), {"-k"});
proc.runSync();
}
bool NSys::root::createDirectory(const std::string& path, const std::string& mode) {
if (!verifyStringValid(path))
return false;
if (!std::ranges::all_of(mode, [](const char& c) { return c >= '0' && c <= '9'; }))
return false;
CProcess proc(subin(), {"mkdir", "-p", "-m", mode, path});
return proc.runSync() && proc.exitCode() == 0;
}
bool NSys::root::removeRecursive(const std::string& path) {
if (!verifyStringValid(path))
return false;
std::error_code ec;
const std::string PATH_ABSOLUTE = std::filesystem::canonical(path, ec);
if (ec)
return false;
if (!PATH_ABSOLUTE.starts_with("/var/cache/hyprpm"))
return false;
CProcess proc(subin(), {"rm", "-fr", PATH_ABSOLUTE});
return proc.runSync() && proc.exitCode() == 0;
}
bool NSys::root::install(const std::string& what, const std::string& where, const std::string& mode) {
if (!verifyStringValid(what) || !verifyStringValid(where))
return false;
if (!std::ranges::all_of(mode, [](const char& c) { return c >= '0' && c <= '9'; }))
return false;
CProcess proc(subin(), {"install", "-m" + mode, "-o", "0", "-g", "0", what, where});
return proc.runSync() && proc.exitCode() == 0;
}
std::string NSys::root::runAsSuperuserUnsafe(const std::string& cmd) {
CProcess proc(subin(), {"/bin/sh", "-c", cmd});
if (!proc.runSync())
return "";
return proc.stdOut();
}

View File

@@ -1,23 +0,0 @@
#pragma once
#include <string>
namespace NSys {
bool isSuperuser();
int getUID();
int getEUID();
// NOLINTNEXTLINE
namespace root {
void cacheSudo();
void dropSudo();
//
[[nodiscard("Discarding could lead to vulnerabilities and bugs")]] bool createDirectory(const std::string& path, const std::string& mode);
[[nodiscard("Discarding could lead to vulnerabilities and bugs")]] bool removeRecursive(const std::string& path);
[[nodiscard("Discarding could lead to vulnerabilities and bugs")]] bool install(const std::string& what, const std::string& where, const std::string& mode);
// Do not use this unless absolutely necessary!
std::string runAsSuperuserUnsafe(const std::string& cmd);
};
};

View File

@@ -2,36 +2,33 @@
#include "helpers/StringUtils.hpp" #include "helpers/StringUtils.hpp"
#include "core/PluginManager.hpp" #include "core/PluginManager.hpp"
#include "core/DataState.hpp" #include "core/DataState.hpp"
#include "helpers/Sys.hpp"
#include <cstdio>
#include <vector> #include <vector>
#include <string> #include <string>
#include <print> #include <print>
#include <chrono>
#include <hyprutils/utils/ScopeGuard.hpp> #include <thread>
using namespace Hyprutils::Utils;
constexpr std::string_view HELP = R"#(┏ hyprpm, a Hyprland Plugin Manager constexpr std::string_view HELP = R"#(┏ hyprpm, a Hyprland Plugin Manager
add [url] [git rev] Install a new plugin repository from git. Git revision add [url] [git rev] Install a new plugin repository from git. Git revision
is optional, when set, commit locks are ignored. is optional, when set, commit locks are ignored.
remove [url/name] Remove an installed plugin repository. remove [url/name] Remove an installed plugin repository
enable [name] Enable a plugin. enable [name] Enable a plugin
disable [name] Disable a plugin. disable [name] Disable a plugin
update Check and update all plugins if needed. update Check and update all plugins if needed
reload Reload hyprpm state. Ensure all enabled plugins are loaded. reload Reload hyprpm state. Ensure all enabled plugins are loaded.
list List all installed plugins. list List all installed plugins
purge-cache Remove the entire hyprpm cache, built plugins, hyprpm settings and headers.
Flags: Flags:
--notify | -n Send a hyprland notification for important events (including both successes and fail events). --notify | -n Send a hyprland notification for important events (including both successes and fail events)
--notify-fail | -nn Send a hyprland notification for fail events only. --notify-fail | -nn Send a hyprland notification for fail events only
--help | -h Show this menu. --help | -h Show this menu
--verbose | -v Enable too much logging. --verbose | -v Enable too much logging
--force | -f Force an operation ignoring checks (e.g. update -f). --force | -f Force an operation ignoring checks (e.g. update -f)
--no-shallow | -s Disable shallow cloning of Hyprland sources. --no-shallow | -s Disable shallow cloning of Hyprland sources
--hl-url | Pass a custom hyprland source url.
)#"; )#";
@@ -48,7 +45,6 @@ int main(int argc, char** argv, char** envp) {
std::vector<std::string> command; std::vector<std::string> command;
bool notify = false, notifyFail = false, verbose = false, force = false, noShallow = false; bool notify = false, notifyFail = false, verbose = false, force = false, noShallow = false;
std::string customHlUrl;
for (int i = 1; i < argc; ++i) { for (int i = 1; i < argc; ++i) {
if (ARGS[i].starts_with("-")) { if (ARGS[i].starts_with("-")) {
@@ -63,13 +59,6 @@ int main(int argc, char** argv, char** envp) {
verbose = true; verbose = true;
} else if (ARGS[i] == "--no-shallow" || ARGS[i] == "-s") { } else if (ARGS[i] == "--no-shallow" || ARGS[i] == "-s") {
noShallow = true; noShallow = true;
} else if (ARGS[i] == "--hl-url") {
if (i + 1 >= argc) {
std::println(stderr, "Missing argument for --hl-url");
return 1;
}
customHlUrl = ARGS[i + 1];
i++;
} else if (ARGS[i] == "--force" || ARGS[i] == "-f") { } else if (ARGS[i] == "--force" || ARGS[i] == "-f") {
force = true; force = true;
std::println("{}", statusString("!", Colors::RED, "Using --force, I hope you know what you are doing.")); std::println("{}", statusString("!", Colors::RED, "Using --force, I hope you know what you are doing."));
@@ -77,19 +66,19 @@ int main(int argc, char** argv, char** envp) {
std::println(stderr, "Unrecognized option {}", ARGS[i]); std::println(stderr, "Unrecognized option {}", ARGS[i]);
return 1; return 1;
} }
} else } else {
command.push_back(ARGS[i]); command.push_back(ARGS[i]);
}
} }
if (command.empty()) { if (command.empty()) {
std::println(stderr, "{}", HELP); std::println(stderr, "{}", HELP);
return 1; return 0;
} }
g_pPluginManager = std::make_unique<CPluginManager>(); g_pPluginManager = std::make_unique<CPluginManager>();
g_pPluginManager->m_bVerbose = verbose; g_pPluginManager->m_bVerbose = verbose;
g_pPluginManager->m_bNoShallow = noShallow; g_pPluginManager->m_bNoShallow = noShallow;
g_pPluginManager->m_szCustomHlUrl = customHlUrl;
if (command[0] == "add") { if (command[0] == "add") {
if (command.size() < 2) { if (command.size() < 2) {
@@ -98,42 +87,23 @@ int main(int argc, char** argv, char** envp) {
} }
std::string rev = ""; std::string rev = "";
if (command.size() >= 3) if (command.size() >= 3) {
rev = command[2]; rev = command[2];
const auto HLVER = g_pPluginManager->getHyprlandVersion();
auto GLOBALSTATE = DataState::getGlobalState();
if (GLOBALSTATE.headersHashCompiled != HLVER.hash) {
std::println(stderr, "{}", failureString("Headers outdated, please run hyprpm update."));
return 1;
} }
NSys::root::cacheSudo();
CScopeGuard x([] { NSys::root::dropSudo(); });
g_pPluginManager->updateHeaders(false);
return g_pPluginManager->addNewPluginRepo(command[1], rev) ? 0 : 1; return g_pPluginManager->addNewPluginRepo(command[1], rev) ? 0 : 1;
} else if (command[0] == "remove") { } else if (command[0] == "remove") {
if (command.size() < 2) { if (ARGS.size() < 2) {
std::println(stderr, "{}", failureString("Not enough args for remove.")); std::println(stderr, "{}", failureString("Not enough args for remove."));
return 1; return 1;
} }
NSys::root::cacheSudo();
CScopeGuard x([] { NSys::root::dropSudo(); });
return g_pPluginManager->removePluginRepo(command[1]) ? 0 : 1; return g_pPluginManager->removePluginRepo(command[1]) ? 0 : 1;
} else if (command[0] == "update") { } else if (command[0] == "update") {
NSys::root::cacheSudo(); bool headersValid = g_pPluginManager->headersValid() == HEADERS_OK;
CScopeGuard x([] { NSys::root::dropSudo(); }); bool headers = g_pPluginManager->updateHeaders(force);
bool headersValid = g_pPluginManager->headersValid() == HEADERS_OK;
bool headers = g_pPluginManager->updateHeaders(force);
if (headers) { if (headers) {
const auto HLVER = g_pPluginManager->getHyprlandVersion(false); const auto HLVER = g_pPluginManager->getHyprlandVersion();
auto GLOBALSTATE = DataState::getGlobalState(); auto GLOBALSTATE = DataState::getGlobalState();
const auto COMPILEDOUTDATED = HLVER.hash != GLOBALSTATE.headersHashCompiled; const auto COMPILEDOUTDATED = HLVER.hash != GLOBALSTATE.headersHashCompiled;
@@ -144,15 +114,12 @@ int main(int argc, char** argv, char** envp) {
auto ret2 = g_pPluginManager->ensurePluginsLoadState(); auto ret2 = g_pPluginManager->ensurePluginsLoadState();
if (ret2 == LOADSTATE_HYPRLAND_UPDATED)
g_pPluginManager->notify(ICON_INFO, 0, 10000, "[hyprpm] Updated plugins, but Hyprland was updated. Please restart Hyprland.");
if (ret2 != LOADSTATE_OK) if (ret2 != LOADSTATE_OK)
return 1; return 1;
} else if (notify) } else if (notify)
g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Couldn't update headers"); g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Couldn't update headers");
} else if (command[0] == "enable") { } else if (command[0] == "enable") {
if (command.size() < 2) { if (ARGS.size() < 2) {
std::println(stderr, "{}", failureString("Not enough args for enable.")); std::println(stderr, "{}", failureString("Not enough args for enable."));
return 1; return 1;
} }
@@ -162,14 +129,7 @@ int main(int argc, char** argv, char** envp) {
return 1; return 1;
} }
NSys::root::cacheSudo(); auto ret = g_pPluginManager->ensurePluginsLoadState();
CScopeGuard x([] { NSys::root::dropSudo(); });
auto ret = g_pPluginManager->ensurePluginsLoadState();
if (ret == LOADSTATE_HYPRLAND_UPDATED)
g_pPluginManager->notify(ICON_INFO, 0, 10000, "[hyprpm] Enabled plugin, but Hyprland was updated. Please restart Hyprland.");
if (ret != LOADSTATE_OK) if (ret != LOADSTATE_OK)
return 1; return 1;
} else if (command[0] == "disable") { } else if (command[0] == "disable") {
@@ -183,36 +143,24 @@ int main(int argc, char** argv, char** envp) {
return 1; return 1;
} }
NSys::root::cacheSudo(); auto ret = g_pPluginManager->ensurePluginsLoadState();
CScopeGuard x([] { NSys::root::dropSudo(); });
auto ret = g_pPluginManager->ensurePluginsLoadState();
if (ret != LOADSTATE_OK) if (ret != LOADSTATE_OK)
return 1; return 1;
} else if (command[0] == "reload") { } else if (command[0] == "reload") {
auto ret = g_pPluginManager->ensurePluginsLoadState(force); auto ret = g_pPluginManager->ensurePluginsLoadState();
if (ret != LOADSTATE_OK) { if (ret != LOADSTATE_OK && notify) {
if (notify) { switch (ret) {
switch (ret) { case LOADSTATE_FAIL:
case LOADSTATE_FAIL: case LOADSTATE_PARTIAL_FAIL: g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins"); break;
case LOADSTATE_PARTIAL_FAIL: g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins"); break; case LOADSTATE_HEADERS_OUTDATED:
case LOADSTATE_HEADERS_OUTDATED: g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins: Outdated headers. Please run hyprpm update manually.");
g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins: Outdated headers. Please run hyprpm update manually."); break;
break; default: break;
default: break;
}
} }
return 1;
} else if (notify && !notifyFail) { } else if (notify && !notifyFail) {
g_pPluginManager->notify(ICON_OK, 0, 4000, "[hyprpm] Loaded plugins"); g_pPluginManager->notify(ICON_OK, 0, 4000, "[hyprpm] Loaded plugins");
} }
} else if (command[0] == "purge-cache") {
NSys::root::cacheSudo();
CScopeGuard x([] { NSys::root::dropSudo(); });
DataState::purgeAllCache();
} else if (command[0] == "list") { } else if (command[0] == "list") {
g_pPluginManager->listAllPlugins(); g_pPluginManager->listAllPlugins();
} else { } else {

View File

@@ -8,7 +8,6 @@ executable(
dependency('hyprutils', version: '>= 0.1.1'), dependency('hyprutils', version: '>= 0.1.1'),
dependency('threads'), dependency('threads'),
dependency('tomlplusplus'), dependency('tomlplusplus'),
dependency('glaze', method: 'cmake'),
], ],
install: true, install: true,
) )

View File

@@ -1,80 +1,82 @@
#include "CProgressBar.hpp" #include "CProgressBar.hpp"
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <unistd.h> #include <algorithm>
#include <cmath> #include <cmath>
#include <format> #include <format>
#include <print>
#include <cstdio>
#include <algorithm> #include <print>
#include <sstream> #include <stdio.h>
#include <hyprutils/memory/Casts.hpp> #include <unistd.h>
#include "../helpers/Colors.hpp" #include "../helpers/Colors.hpp"
using namespace Hyprutils::Memory;
static winsize getTerminalSize() {
winsize w{};
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
return w;
}
static void clearCurrentLine() {
std::print("\r\33[2K"); // ansi escape sequence to clear entire line
}
void CProgressBar::printMessageAbove(const std::string& msg) { void CProgressBar::printMessageAbove(const std::string& msg) {
clearCurrentLine(); struct winsize w;
std::print("\r{}\n", msg); ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
print(); // reprint bar underneath std::string spaces;
spaces.reserve(w.ws_col);
for (size_t i = 0; i < w.ws_col; ++i) {
spaces += ' ';
}
std::println("\r{}\r{}", spaces, msg);
print();
} }
void CProgressBar::print() { void CProgressBar::print() {
const auto w = getTerminalSize(); struct winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
if (m_bFirstPrint) { if (m_bFirstPrint)
std::print("\n"); std::print("\n");
m_bFirstPrint = false; m_bFirstPrint = false;
std::string spaces;
spaces.reserve(w.ws_col);
for (size_t i = 0; i < w.ws_col; ++i) {
spaces += ' ';
} }
clearCurrentLine(); std::print("\r{}\r", spaces);
float percentDone = 0.0f; std::string message = "";
if (m_fPercentage >= 0.0f)
float percentDone = 0;
if (m_fPercentage >= 0)
percentDone = m_fPercentage; percentDone = m_fPercentage;
else { else
// check for divide-by-zero percentDone = (float)m_iSteps / (float)m_iMaxSteps;
percentDone = m_iMaxSteps > 0 ? sc<float>(m_iSteps) / m_iMaxSteps : 0.0f;
const auto BARWIDTH = std::clamp(w.ws_col - static_cast<unsigned long>(m_szCurrentMessage.length()) - 2, 0UL, 50UL);
// draw bar
message += std::string{" "} + Colors::GREEN;
size_t i = 0;
for (; i < std::floor(percentDone * BARWIDTH); ++i) {
message += "";
} }
// clamp to ensure no overflows (sanity check)
percentDone = std::clamp(percentDone, 0.0f, 1.0f);
const size_t BARWIDTH = std::clamp<size_t>(w.ws_col - m_szCurrentMessage.length() - 2, 0, 50);
std::ostringstream oss;
oss << ' ' << Colors::GREEN;
size_t filled = std::floor(percentDone * BARWIDTH);
size_t i = 0;
for (; i < filled; ++i)
oss << "";
if (i < BARWIDTH) { if (i < BARWIDTH) {
oss << "" << Colors::RESET; i++;
++i;
for (; i < BARWIDTH; ++i) message += std::string{""} + Colors::RESET;
oss << "";
for (; i < BARWIDTH; ++i) {
message += "";
}
} else } else
oss << Colors::RESET; message += Colors::RESET;
if (m_fPercentage >= 0.0f) // draw progress
oss << " " << std::format("{}%", sc<int>(percentDone * 100.0)) << ' '; if (m_fPercentage >= 0)
message += " " + std::format("{}%", static_cast<int>(percentDone * 100.0)) + " ";
else else
oss << " " << std::format("{} / {}", m_iSteps, m_iMaxSteps) << ' '; message += " " + std::format("{} / {}", m_iSteps, m_iMaxSteps) + " ";
// draw message
std::print("{} {}", message, m_szCurrentMessage);
std::print("{} {}", oss.str(), m_szCurrentMessage);
std::fflush(stdout); std::fflush(stdout);
} }

View File

@@ -1,30 +0,0 @@
cmake_minimum_required(VERSION 3.19)
project(hyprtester DESCRIPTION "Hyprland test suite")
include(GNUInstallDirs)
set(CMAKE_CXX_STANDARD 26)
find_package(PkgConfig REQUIRED)
pkg_check_modules(hyprtester_deps REQUIRED IMPORTED_TARGET hyprutils>=0.5.0)
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
add_executable(hyprtester ${SRCFILES})
add_custom_command(
TARGET hyprtester
POST_BUILD
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/plugin/build.sh
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/plugin)
target_link_libraries(hyprtester PUBLIC PkgConfig::hyprtester_deps)
install(TARGETS hyprtester)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/test.conf
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/hypr)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/plugin/hyprtestplugin.so
DESTINATION ${CMAKE_INSTALL_PREFIX}/lib)

View File

@@ -1,16 +0,0 @@
CXXFLAGS = -shared -fPIC -g -std=c++2b -Wno-c++11-narrowing
INCLUDES = `pkg-config --cflags pixman-1 libdrm pangocairo libinput libudev wayland-server xkbcommon`
LIBS = `pkg-config --libs pangocairo`
SRC = src/main.cpp
TARGET = hyprtestplugin.so
all: $(TARGET)
$(TARGET): $(SRC)
$(CXX) $(CXXFLAGS) -I../.. -I../../protocols $(INCLUDES) $^ $> -o $@ $(LIBS) -O2
clean:
rm -f ./$(TARGET)
.PHONY: all clean

View File

@@ -1,4 +0,0 @@
#!/bin/sh
make clean
make all

View File

@@ -1,5 +0,0 @@
#pragma once
#include <src/plugins/PluginAPI.hpp>
inline HANDLE PHANDLE = nullptr;

View File

@@ -1,150 +0,0 @@
#include <unistd.h>
#include <src/includes.hpp>
#include <sstream>
#include <any>
#define private public
#include <src/config/ConfigManager.hpp>
#include <src/config/ConfigDescriptions.hpp>
#include <src/layout/IHyprLayout.hpp>
#include <src/managers/LayoutManager.hpp>
#include <src/managers/input/InputManager.hpp>
#include <src/Compositor.hpp>
#undef private
#include <hyprutils/utils/ScopeGuard.hpp>
using namespace Hyprutils::Utils;
#include "globals.hpp"
// Do NOT change this function.
APICALL EXPORT std::string PLUGIN_API_VERSION() {
return HYPRLAND_API_VERSION;
}
static SDispatchResult test(std::string in) {
bool success = true;
std::string errors = "";
if (g_pConfigManager->m_configValueNumber != CONFIG_OPTIONS.size() + 1 /* autogenerated is special */) {
errors += "config value number mismatches descriptions size\n";
success = false;
}
return SDispatchResult{
.success = success,
.error = errors,
};
}
// Trigger a snap move event for the active window
static SDispatchResult snapMove(std::string in) {
const auto PLASTWINDOW = g_pCompositor->m_lastWindow.lock();
if (!PLASTWINDOW->m_isFloating)
return {.success = false, .error = "Window must be floating"};
Vector2D pos = PLASTWINDOW->m_realPosition->goal();
Vector2D size = PLASTWINDOW->m_realSize->goal();
g_pLayoutManager->getCurrentLayout()->performSnap(pos, size, PLASTWINDOW, MBIND_MOVE, -1, size);
*PLASTWINDOW->m_realPosition = pos.round();
return {};
}
class CTestKeyboard : public IKeyboard {
public:
static SP<CTestKeyboard> create(bool isVirtual) {
auto keeb = SP<CTestKeyboard>(new CTestKeyboard());
keeb->m_self = keeb;
keeb->m_isVirtual = isVirtual;
keeb->m_shareStates = !isVirtual;
return keeb;
}
virtual bool isVirtual() {
return m_isVirtual;
}
virtual SP<Aquamarine::IKeyboard> aq() {
return nullptr;
}
void sendKey(uint32_t key, bool pressed) {
auto event = IKeyboard::SKeyEvent{
.timeMs = sc<uint32_t>(Time::millis(Time::steadyNow())),
.keycode = key,
.state = pressed ? WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED,
};
updatePressed(event.keycode, pressed);
m_keyboardEvents.key.emit(event);
}
void destroy() {
m_events.destroy.emit();
}
private:
bool m_isVirtual;
};
static SDispatchResult vkb(std::string in) {
auto tkb0 = CTestKeyboard::create(false);
auto tkb1 = CTestKeyboard::create(false);
auto vkb0 = CTestKeyboard::create(true);
g_pInputManager->newKeyboard(tkb0);
g_pInputManager->newKeyboard(tkb1);
g_pInputManager->newKeyboard(vkb0);
CScopeGuard x([&] {
tkb0->destroy();
tkb1->destroy();
vkb0->destroy();
});
const auto& PRESSED = g_pInputManager->getKeysFromAllKBs();
const uint32_t TESTKEY = 1;
tkb0->sendKey(TESTKEY, true);
if (!std::ranges::contains(PRESSED, TESTKEY)) {
return {
.success = false,
.error = "Expected pressed key not found",
};
}
tkb1->sendKey(TESTKEY, true);
tkb0->sendKey(TESTKEY, false);
if (!std::ranges::contains(PRESSED, TESTKEY)) {
return {
.success = false,
.error = "Expected pressed key not found (kb share state)",
};
}
vkb0->sendKey(TESTKEY, true);
tkb1->sendKey(TESTKEY, false);
if (std::ranges::contains(PRESSED, TESTKEY)) {
return {
.success = false,
.error = "Expected released key found in pressed (vkb no share state)",
};
}
return {};
}
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
PHANDLE = handle;
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:test", ::test);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:snapmove", ::snapMove);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:vkb", ::vkb);
return {"hyprtestplugin", "hyprtestplugin", "Vaxry", "1.0"};
}
APICALL EXPORT void PLUGIN_EXIT() {
;
}

View File

@@ -1,17 +0,0 @@
#pragma once
#include <string>
#include <format>
#include <print>
namespace NLog {
template <typename... Args>
//NOLINTNEXTLINE
void log(std::format_string<Args...> fmt, Args&&... args) {
std::string logMsg = "";
logMsg += std::vformat(fmt.get(), std::make_format_args(args...));
std::println("{}", logMsg);
std::fflush(stdout);
}
}

View File

@@ -1,138 +0,0 @@
#include "hyprctlCompat.hpp"
#include "shared.hpp"
#include <pwd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <filesystem>
#include <fstream>
#include <algorithm>
#include <csignal>
#include <cerrno>
#include <print>
#include <hyprutils/memory/Casts.hpp>
using namespace Hyprutils::Memory;
static int getUID() {
const auto UID = getuid();
const auto PWUID = getpwuid(UID);
return PWUID ? PWUID->pw_uid : UID;
}
static std::string getRuntimeDir() {
const auto XDG = getenv("XDG_RUNTIME_DIR");
if (!XDG) {
const std::string USERID = std::to_string(getUID());
return "/run/user/" + USERID + "/hypr";
}
return std::string{XDG} + "/hypr";
}
std::vector<SInstanceData> instances() {
std::vector<SInstanceData> result;
try {
if (!std::filesystem::exists(getRuntimeDir()))
return {};
} catch (std::exception& e) { return {}; }
for (const auto& el : std::filesystem::directory_iterator(getRuntimeDir())) {
if (!el.is_directory() || !std::filesystem::exists(el.path().string() + "/hyprland.lock"))
continue;
// read lock
SInstanceData* data = &result.emplace_back();
data->id = el.path().filename().string();
try {
data->time = std::stoull(data->id.substr(data->id.find_first_of('_') + 1, data->id.find_last_of('_') - (data->id.find_first_of('_') + 1)));
} catch (std::exception& e) { continue; }
// read file
std::ifstream ifs(el.path().string() + "/hyprland.lock");
int i = 0;
for (std::string line; std::getline(ifs, line); ++i) {
if (i == 0) {
try {
data->pid = std::stoull(line);
} catch (std::exception& e) { continue; }
} else if (i == 1) {
data->wlSocket = line;
} else
break;
}
ifs.close();
}
std::erase_if(result, [&](const auto& el) { return kill(el.pid, 0) != 0 && errno == ESRCH; });
std::sort(result.begin(), result.end(), [&](const auto& a, const auto& b) { return a.time < b.time; });
return result;
}
std::string getFromSocket(const std::string& cmd) {
const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);
auto t = timeval{.tv_sec = 5, .tv_usec = 0};
setsockopt(SERVERSOCKET, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(struct timeval));
if (SERVERSOCKET < 0) {
std::println("socket: Couldn't open a socket (1)");
return "";
}
sockaddr_un serverAddress = {0};
serverAddress.sun_family = AF_UNIX;
std::string socketPath = getRuntimeDir() + "/" + HIS + "/.socket.sock";
strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);
if (connect(SERVERSOCKET, rc<sockaddr*>(&serverAddress), SUN_LEN(&serverAddress)) < 0) {
std::println("Couldn't connect to {}. (3)", socketPath);
return "";
}
auto sizeWritten = write(SERVERSOCKET, cmd.c_str(), cmd.length());
if (sizeWritten < 0) {
std::println("Couldn't write (4)");
return "";
}
std::string reply = "";
char buffer[8192] = {0};
sizeWritten = read(SERVERSOCKET, buffer, 8192);
if (sizeWritten < 0) {
if (errno == EWOULDBLOCK)
std::println("Hyprland IPC didn't respond in time");
std::println("Couldn't read (5)");
return "";
}
reply += std::string(buffer, sizeWritten);
while (sizeWritten == 8192) {
sizeWritten = read(SERVERSOCKET, buffer, 8192);
if (sizeWritten < 0) {
std::println("Couldn't read (5)");
return "";
}
reply += std::string(buffer, sizeWritten);
}
close(SERVERSOCKET);
return reply;
}

View File

@@ -1,16 +0,0 @@
#pragma once
#include <vector>
#include <string>
#include <cstdint>
struct SInstanceData {
std::string id;
uint64_t time;
uint64_t pid;
std::string wlSocket;
bool valid = true;
};
std::vector<SInstanceData> instances();
std::string getFromSocket(const std::string& cmd);

View File

@@ -1,252 +0,0 @@
// This is a tester for Hyprland. It will launch the built binary in ./build/Hyprland
// in headless mode and test various things.
// for now it's quite basic and limited, but will be expanded in the future.
// NOTE: This tester has to be ran from its directory!!
// Some TODO:
// - Add a plugin built alongside so that we can do more detailed tests (e.g. simulating keystrokes)
// - test coverage
// - maybe figure out a way to do some visual tests too?
// Required runtime deps for checks:
// - kitty
// - xeyes
#include "shared.hpp"
#include "hyprctlCompat.hpp"
#include "tests/main/tests.hpp"
#include "tests/plugin/plugin.hpp"
#include <filesystem>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/memory/Casts.hpp>
using namespace Hyprutils::Memory;
#include <csignal>
#include <cerrno>
#include <chrono>
#include <thread>
#include <print>
#include <string_view>
#include <span>
#include "Log.hpp"
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
#define SP CSharedPointer
static int ret = 0;
static SP<CProcess> hyprlandProc;
static const std::string cwd = std::filesystem::current_path().string();
//
static bool launchHyprland(std::string configPath, std::string binaryPath) {
if (binaryPath == "") {
std::error_code ec;
if (!std::filesystem::exists(cwd + "/../build/Hyprland", ec) || ec) {
NLog::log("{}No Hyprland binary", Colors::RED);
return false;
}
binaryPath = cwd + "/../build/Hyprland";
}
if (configPath == "") {
std::error_code ec;
if (!std::filesystem::exists(cwd + "/test.conf", ec) || ec) {
NLog::log("{}No test config", Colors::RED);
return false;
}
configPath = cwd + "/test.conf";
}
NLog::log("{}Launching Hyprland", Colors::YELLOW);
hyprlandProc = makeShared<CProcess>(binaryPath, std::vector<std::string>{"--config", configPath});
hyprlandProc->addEnv("HYPRLAND_HEADLESS_ONLY", "1");
NLog::log("{}Launched async process", Colors::YELLOW);
return hyprlandProc->runAsync();
}
static bool hyprlandAlive() {
NLog::log("{}hyprlandAlive", Colors::YELLOW);
kill(hyprlandProc->pid(), 0);
return errno != ESRCH;
}
static void help() {
NLog::log("usage: hyprtester [arg [...]].\n");
NLog::log(R"(Arguments:
--help -h - Show this message again
--config FILE -c FILE - Specify config file to use
--binary FILE -b FILE - Specify Hyprland binary to use
--plugin FILE -p FILE - Specify the location of the test plugin)");
}
int main(int argc, char** argv, char** envp) {
std::string configPath = "";
std::string binaryPath = "";
std::string pluginPath = std::filesystem::current_path().string();
if (argc > 1) {
std::span<char*> args{argv + 1, sc<std::size_t>(argc - 1)};
for (auto it = args.begin(); it != args.end(); it++) {
std::string_view value = *it;
if (value == "--config" || value == "-c") {
if (std::next(it) == args.end()) {
help();
return 1;
}
configPath = *std::next(it);
try {
configPath = std::filesystem::canonical(configPath);
if (!std::filesystem::is_regular_file(configPath)) {
throw std::exception();
}
} catch (...) {
std::println(stderr, "[ ERROR ] Config file '{}' doesn't exist!", configPath);
help();
return 1;
}
it++;
continue;
} else if (value == "--binary" || value == "-b") {
if (std::next(it) == args.end()) {
help();
return 1;
}
binaryPath = *std::next(it);
try {
binaryPath = std::filesystem::canonical(binaryPath);
if (!std::filesystem::is_regular_file(binaryPath)) {
throw std::exception();
}
} catch (...) {
std::println(stderr, "[ ERROR ] Binary '{}' doesn't exist!", binaryPath);
help();
return 1;
}
it++;
continue;
} else if (value == "--plugin" || value == "-p") {
if (std::next(it) == args.end()) {
help();
return 1;
}
pluginPath = *std::next(it);
try {
pluginPath = std::filesystem::canonical(pluginPath);
if (!std::filesystem::is_regular_file(pluginPath)) {
throw std::exception();
}
} catch (...) {
std::println(stderr, "[ ERROR ] plugin '{}' doesn't exist!", pluginPath);
help();
return 1;
}
it++;
continue;
} else if (value == "--help" || value == "-h") {
help();
return 0;
} else {
std::println(stderr, "[ ERROR ] Unknown option '{}' !", *it);
help();
return 1;
}
}
}
NLog::log("{}launching hl", Colors::YELLOW);
if (!launchHyprland(configPath, binaryPath)) {
NLog::log("{}well it failed", Colors::RED);
return 1;
}
// hyprland has launched, let's check if it's alive after 10s
std::this_thread::sleep_for(std::chrono::milliseconds(10000));
NLog::log("{}slept for 10s", Colors::YELLOW);
if (!hyprlandAlive()) {
NLog::log("{}Hyprland failed to launch", Colors::RED);
return 1;
}
// wonderful, we are in. Let's get the instance signature.
NLog::log("{}trying to get INSTANCES", Colors::YELLOW);
const auto INSTANCES = instances();
if (INSTANCES.empty()) {
NLog::log("{}Hyprland failed to launch (2)", Colors::RED);
return 1;
}
HIS = INSTANCES.back().id;
WLDISPLAY = INSTANCES.back().wlSocket;
NLog::log("{}trying to get create headless output", Colors::YELLOW);
getFromSocket("/output create headless");
NLog::log("{}trying to load plugin", Colors::YELLOW);
if (const auto R = getFromSocket(std::format("/plugin load {}", pluginPath)); R != "ok") {
NLog::log("{}Failed to load the test plugin: {}", Colors::RED, R);
getFromSocket("/dispatch exit 1");
return 1;
}
NLog::log("{}Loaded plugin", Colors::YELLOW);
for (const auto& fn : testFns) {
EXPECT(fn(), true);
}
NLog::log("{}running plugin test", Colors::YELLOW);
EXPECT(testPlugin(), true);
NLog::log("{}running vkb test from plugin", Colors::YELLOW);
EXPECT(testVkb(), true);
// kill hyprland
NLog::log("{}dispatching exit", Colors::YELLOW);
getFromSocket("/dispatch exit");
NLog::log("\n{}Summary:\n\tPASSED: {}{}{}/{}\n\tFAILED: {}{}{}/{}\n{}", Colors::RESET, Colors::GREEN, TESTS_PASSED, Colors::RESET, TESTS_PASSED + TESTS_FAILED, Colors::RED,
TESTS_FAILED, Colors::RESET, TESTS_PASSED + TESTS_FAILED, (TESTS_FAILED > 0 ? std::string{Colors::RED} + "\nSome tests failed.\n" : ""));
kill(hyprlandProc->pid(), SIGKILL);
hyprlandProc.reset();
return ret || TESTS_FAILED;
}

View File

@@ -1,90 +0,0 @@
// Stolen from hyprutils
#pragma once
#include <iostream>
inline std::string HIS = "";
inline std::string WLDISPLAY = "";
inline int TESTS_PASSED = 0;
inline int TESTS_FAILED = 0;
namespace Colors {
constexpr const char* RED = "\x1b[31m";
constexpr const char* GREEN = "\x1b[32m";
constexpr const char* YELLOW = "\x1b[33m";
constexpr const char* BLUE = "\x1b[34m";
constexpr const char* MAGENTA = "\x1b[35m";
constexpr const char* CYAN = "\x1b[36m";
constexpr const char* RESET = "\x1b[0m";
};
#define EXPECT(expr, val) \
if (const auto RESULT = expr; RESULT != (val)) { \
NLog::log("{}Failed: {}{}, expected {}, got {}. Source: {}@{}.", Colors::RED, Colors::RESET, #expr, val, RESULT, __FILE__, __LINE__); \
ret = 1; \
TESTS_FAILED++; \
} else { \
NLog::log("{}Passed: {}{}. Got {}", Colors::GREEN, Colors::RESET, #expr, val); \
TESTS_PASSED++; \
}
#define EXPECT_VECTOR2D(expr, val) \
do { \
const auto& RESULT = expr; \
const auto& EXPECTED = val; \
if (!(std::abs(RESULT.x - EXPECTED.x) < 1e-6 && std::abs(RESULT.y - EXPECTED.y) < 1e-6)) { \
NLog::log("{}Failed: {}{}, expected [{}, {}], got [{}, {}]. Source: {}@{}.", Colors::RED, Colors::RESET, #expr, EXPECTED.x, EXPECTED.y, RESULT.x, RESULT.y, __FILE__, \
__LINE__); \
ret = 1; \
TESTS_FAILED++; \
} else { \
NLog::log("{}Passed: {}{}. Got [{}, {}].", Colors::GREEN, Colors::RESET, #expr, RESULT.x, RESULT.y); \
TESTS_PASSED++; \
} \
} while (0)
#define EXPECT_CONTAINS(haystack, needle) \
if (const auto EXPECTED = needle; !std::string{haystack}.contains(EXPECTED)) { \
NLog::log("{}Failed: {}{} should contain {} but doesn't. Source: {}@{}. Haystack is:\n{}", Colors::RED, Colors::RESET, #haystack, #needle, __FILE__, __LINE__, \
std::string{haystack}); \
ret = 1; \
TESTS_FAILED++; \
} else { \
NLog::log("{}Passed: {}{} contains {}.", Colors::GREEN, Colors::RESET, #haystack, EXPECTED); \
TESTS_PASSED++; \
}
#define EXPECT_NOT_CONTAINS(haystack, needle) \
if (std::string{haystack}.contains(needle)) { \
NLog::log("{}Failed: {}{} shouldn't contain {} but does. Source: {}@{}. Haystack is:\n{}", Colors::RED, Colors::RESET, #haystack, #needle, __FILE__, __LINE__, \
std::string{haystack}); \
ret = 1; \
TESTS_FAILED++; \
} else { \
NLog::log("{}Passed: {}{} doesn't contain {}.", Colors::GREEN, Colors::RESET, #haystack, #needle); \
TESTS_PASSED++; \
}
#define EXPECT_STARTS_WITH(str, what) \
if (!std::string{str}.starts_with(what)) { \
NLog::log("{}Failed: {}{} should start with {} but doesn't. Source: {}@{}. String is:\n{}", Colors::RED, Colors::RESET, #str, #what, __FILE__, __LINE__, \
std::string{str}); \
ret = 1; \
TESTS_FAILED++; \
} else { \
NLog::log("{}Passed: {}{} starts with {}.", Colors::GREEN, Colors::RESET, #str, #what); \
TESTS_PASSED++; \
}
#define EXPECT_COUNT_STRING(str, what, no) \
if (Tests::countOccurrences(str, what) != no) { \
NLog::log("{}Failed: {}{} should contain {} {} times, but doesn't. Source: {}@{}. String is:\n{}", Colors::RED, Colors::RESET, #str, #what, no, __FILE__, __LINE__, \
std::string{str}); \
ret = 1; \
TESTS_FAILED++; \
} else { \
NLog::log("{}Passed: {}{} contains {} {} times.", Colors::GREEN, Colors::RESET, #str, #what, no); \
TESTS_PASSED++; \
}
#define OK(x) EXPECT(x, "ok")

View File

@@ -1,22 +0,0 @@
#include "../../Log.hpp"
#include "tests.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
static int ret = 0;
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
static bool test() {
NLog::log("{}Testing animations", Colors::GREEN);
auto str = getFromSocket("/animations");
NLog::log("{}Testing bezier curve output from `hyprctl animations`", Colors::YELLOW);
{EXPECT_CONTAINS(str, std::format("beziers:\n\n\tname: quick\n\t\tX0: 0.15\n\t\tY0: 0.00\n\t\tX1: 0.10\n\t\tY1: 1.00"))};
return !ret;
}
REGISTER_TEST_FN(test)

View File

@@ -1,179 +0,0 @@
#include "tests.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <print>
#include <thread>
#include <chrono>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <csignal>
#include <cerrno>
#include "../shared.hpp"
static int ret = 0;
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
#define UP CUniquePointer
#define SP CSharedPointer
static bool test() {
NLog::log("{}Testing groups", Colors::GREEN);
// test on workspace "window"
NLog::log("{}Dispatching workspace `groups`", Colors::YELLOW);
getFromSocket("/dispatch workspace name:groups");
NLog::log("{}Spawning kittyProcA", Colors::YELLOW);
auto kittyProcA = Tests::spawnKitty();
if (!kittyProcA) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
NLog::log("{}Expecting 1 window", Colors::YELLOW);
EXPECT(Tests::windowCount(), 1);
// check kitty properties. One kitty should take the entire screen, minus the gaps.
NLog::log("{}Check kitty dimensions", Colors::YELLOW);
{
auto str = getFromSocket("/clients");
EXPECT_COUNT_STRING(str, "at: 22,22", 1);
EXPECT_COUNT_STRING(str, "size: 1876,1036", 1);
EXPECT_COUNT_STRING(str, "fullscreen: 0", 1);
}
// group the kitty
NLog::log("{}Enable group and groupbar", Colors::YELLOW);
OK(getFromSocket("/dispatch togglegroup"));
OK(getFromSocket("/keyword group:groupbar:enabled 1"));
// check the height of the window now
NLog::log("{}Recheck kitty dimensions", Colors::YELLOW);
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 22,43");
EXPECT_CONTAINS(str, "size: 1876,1015");
}
// disable the groupbar for ease of testing for now
NLog::log("{}Disable groupbar", Colors::YELLOW);
OK(getFromSocket("r/keyword group:groupbar:enabled 0"));
// kill all
NLog::log("{}Kill windows", Colors::YELLOW);
Tests::killAllWindows();
NLog::log("{}Spawn kitty again", Colors::YELLOW);
kittyProcA = Tests::spawnKitty();
if (!kittyProcA) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
NLog::log("{}Group kitty", Colors::YELLOW);
OK(getFromSocket("/dispatch togglegroup"));
// check the height of the window now
NLog::log("{}Check kitty dimensions 2", Colors::YELLOW);
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 22,22");
EXPECT_CONTAINS(str, "size: 1876,1036");
}
NLog::log("{}Spawn kittyProcB", Colors::YELLOW);
auto kittyProcB = Tests::spawnKitty();
if (!kittyProcB) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
NLog::log("{}Expecting 2 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 2);
size_t lastActiveKittyIdx = 0;
NLog::log("{}Get last active kitty id", Colors::YELLOW);
try {
auto str = getFromSocket("/activewindow");
lastActiveKittyIdx = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
} catch (...) {
NLog::log("{}Fail at getting prop", Colors::RED);
ret = 1;
}
// test cycling through
NLog::log("{}Test cycling through grouped windows", Colors::YELLOW);
OK(getFromSocket("/dispatch changegroupactive f"));
try {
auto str = getFromSocket("/activewindow");
EXPECT(lastActiveKittyIdx != std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16), true);
} catch (...) {
NLog::log("{}Fail at getting prop", Colors::RED);
ret = 1;
}
getFromSocket("/dispatch changegroupactive f");
try {
auto str = getFromSocket("/activewindow");
EXPECT(lastActiveKittyIdx, std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16));
} catch (...) {
NLog::log("{}Fail at getting prop", Colors::RED);
ret = 1;
}
NLog::log("{}Disable autogrouping", Colors::YELLOW);
OK(getFromSocket("/keyword group:auto_group false"));
NLog::log("{}Spawn kittyProcC", Colors::YELLOW);
auto kittyProcC = Tests::spawnKitty();
if (!kittyProcC) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
NLog::log("{}Expecting 3 windows 2", Colors::YELLOW);
EXPECT(Tests::windowCount(), 3);
{
auto str = getFromSocket("/clients");
EXPECT_COUNT_STRING(str, "at: 22,22", 2);
}
OK(getFromSocket("/dispatch movefocus l"));
OK(getFromSocket("/dispatch changegroupactive 1"));
OK(getFromSocket("/keyword group:auto_group true"));
OK(getFromSocket("/keyword group:insert_after_current false"));
NLog::log("{}Spawn kittyProcD", Colors::YELLOW);
auto kittyProcD = Tests::spawnKitty();
if (!kittyProcD) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
NLog::log("{}Expecting 4 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 4);
OK(getFromSocket("/dispatch changegroupactive 3"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, std::format("pid: {}", kittyProcD->pid()));
}
// kill all
NLog::log("{}Kill windows", Colors::YELLOW);
Tests::killAllWindows();
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);
return !ret;
}
REGISTER_TEST_FN(test)

View File

@@ -1,163 +0,0 @@
#include "tests.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <print>
#include <thread>
#include <chrono>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <csignal>
#include <cerrno>
#include "../shared.hpp"
static int ret = 0;
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
#define UP CUniquePointer
#define SP CSharedPointer
static std::string getCommandStdOut(std::string command) {
CProcess process("bash", {"-c", command});
process.addEnv("HYPRLAND_INSTANCE_SIGNATURE", HIS);
process.runSync();
const std::string& stdOut = process.stdOut();
// Remove trailing new line
return stdOut.substr(0, stdOut.length() - 1);
}
static bool testGetprop() {
NLog::log("{}Testing hyprctl getprop", Colors::GREEN);
if (!Tests::spawnKitty()) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
// animationstyle
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animationstyle"), "(unset)");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animationstyle -j"), R"({"animationstyle": ""})");
getFromSocket("/dispatch setprop class:kitty animationstyle teststyle");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animationstyle"), "teststyle");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animationstyle -j"), R"({"animationstyle": "teststyle"})");
// maxsize
EXPECT(getCommandStdOut("hyprctl getprop class:kitty maxsize"), "inf inf");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty maxsize -j"), R"({"maxsize": [null,null]})");
getFromSocket("/dispatch setprop class:kitty maxsize 200 150");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty maxsize"), "200 150");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty maxsize -j"), R"({"maxsize": [200,150]})");
// minsize
EXPECT(getCommandStdOut("hyprctl getprop class:kitty minsize"), "20 20");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty minsize -j"), R"({"minsize": [20,20]})");
getFromSocket("/dispatch setprop class:kitty minsize 100 50");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty minsize"), "100 50");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty minsize -j"), R"({"minsize": [100,50]})");
// alpha
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alpha"), "1");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alpha -j"), R"({"alpha": 1})");
getFromSocket("/dispatch setprop class:kitty alpha 0.3");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alpha"), "0.3");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alpha -j"), R"({"alpha": 0.3})");
// alphainactive
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactive"), "1");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactive -j"), R"({"alphainactive": 1})");
getFromSocket("/dispatch setprop class:kitty alphainactive 0.5");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactive"), "0.5");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactive -j"), R"({"alphainactive": 0.5})");
// alphafullscreen
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreen"), "1");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreen -j"), R"({"alphafullscreen": 1})");
getFromSocket("/dispatch setprop class:kitty alphafullscreen 0.75");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreen"), "0.75");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreen -j"), R"({"alphafullscreen": 0.75})");
// alphaoverride
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphaoverride"), "false");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphaoverride -j"), R"({"alphaoverride": false})");
getFromSocket("/dispatch setprop class:kitty alphaoverride true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphaoverride"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphaoverride -j"), R"({"alphaoverride": true})");
// alphainactiveoverride
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactiveoverride"), "false");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactiveoverride -j"), R"({"alphainactiveoverride": false})");
getFromSocket("/dispatch setprop class:kitty alphainactiveoverride true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactiveoverride"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactiveoverride -j"), R"({"alphainactiveoverride": true})");
// alphafullscreenoverride
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreenoverride"), "false");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreenoverride -j"), R"({"alphafullscreenoverride": false})");
getFromSocket("/dispatch setprop class:kitty alphafullscreenoverride true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreenoverride"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreenoverride -j"), R"({"alphafullscreenoverride": true})");
// activebordercolor
EXPECT(getCommandStdOut("hyprctl getprop class:kitty activebordercolor"), "ee33ccff ee00ff99 45deg");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty activebordercolor -j"), R"({"activebordercolor": "ee33ccff ee00ff99 45deg"})");
getFromSocket("/dispatch setprop class:kitty activebordercolor rgb(abcdef)");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty activebordercolor"), "ffabcdef 0deg");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty activebordercolor -j"), R"({"activebordercolor": "ffabcdef 0deg"})");
// bool window properties
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allowsinput"), "false");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allowsinput -j"), R"({"allowsinput": false})");
getFromSocket("/dispatch setprop class:kitty allowsinput true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allowsinput"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allowsinput -j"), R"({"allowsinput": true})");
// int window properties
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding"), "10");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding -j"), R"({"rounding": 10})");
getFromSocket("/dispatch setprop class:kitty rounding 4");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding"), "4");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding -j"), R"({"rounding": 4})");
// float window properties
EXPECT(getCommandStdOut("hyprctl getprop class:kitty roundingpower"), "2");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty roundingpower -j"), R"({"roundingpower": 2})");
getFromSocket("/dispatch setprop class:kitty roundingpower 1.25");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty roundingpower"), "1.25");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty roundingpower -j"), R"({"roundingpower": 1.25})");
// errors
EXPECT(getCommandStdOut("hyprctl getprop"), "not enough args");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty"), "not enough args");
EXPECT(getCommandStdOut("hyprctl getprop class:nonexistantclass animationstyle"), "window not found");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty nonexistantprop"), "prop not found");
// kill all
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);
return true;
}
static bool test() {
NLog::log("{}Testing hyprctl", Colors::GREEN);
{
NLog::log("{}Testing hyprctl descriptions for any json errors", Colors::GREEN);
CProcess jqProc("bash", {"-c", "hyprctl descriptions | jq"});
jqProc.addEnv("HYPRLAND_INSTANCE_SIGNATURE", HIS);
jqProc.runSync();
EXPECT(jqProc.exitCode(), 0);
}
if (!testGetprop())
return false;
return !ret;
}
REGISTER_TEST_FN(test);

View File

@@ -1,71 +0,0 @@
#include "../shared.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include "tests.hpp"
static int ret = 0;
static void focusMasterPrevious() {
// setup
NLog::log("{}Spawning 1 master and 3 slave windows", Colors::YELLOW);
// order of windows set according to new_status = master (set in test.conf)
for (auto const& win : {"slave1", "slave2", "slave3", "master"}) {
if (!Tests::spawnKitty(win)) {
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
++TESTS_FAILED;
ret = 1;
return;
}
}
NLog::log("{}Ensuring focus is on master before testing", Colors::YELLOW);
OK(getFromSocket("/dispatch layoutmsg focusmaster master"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master");
// test
NLog::log("{}Testing fallback to focusmaster auto", Colors::YELLOW);
OK(getFromSocket("/dispatch layoutmsg focusmaster previous"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: slave1");
NLog::log("{}Testing focusing from slave to master", Colors::YELLOW);
OK(getFromSocket("/dispatch layoutmsg cyclenext noloop"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: slave2");
OK(getFromSocket("/dispatch layoutmsg focusmaster previous"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master");
NLog::log("{}Testing focusing on previous window", Colors::YELLOW);
OK(getFromSocket("/dispatch layoutmsg focusmaster previous"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: slave2");
NLog::log("{}Testing focusing back to master", Colors::YELLOW);
OK(getFromSocket("/dispatch layoutmsg focusmaster previous"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master");
// clean up
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
}
static bool test() {
NLog::log("{}Testing Master layout", Colors::GREEN);
// setup
OK(getFromSocket("/dispatch workspace name:master"));
OK(getFromSocket("/keyword general:layout master"));
// test
NLog::log("{}Testing `focusmaster previous` layoutmsg", Colors::GREEN);
focusMasterPrevious();
// clean up
NLog::log("Cleaning up", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/reload"));
return !ret;
}
REGISTER_TEST_FN(test);

View File

@@ -1,144 +0,0 @@
#include "tests.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <print>
#include <thread>
#include <chrono>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <csignal>
#include <cerrno>
#include "../shared.hpp"
static int ret = 0;
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
#define UP CUniquePointer
#define SP CSharedPointer
static bool test() {
NLog::log("{}Testing config: misc:", Colors::GREEN);
NLog::log("{}Testing close_special_on_empty", Colors::YELLOW);
OK(getFromSocket("/keyword misc:close_special_on_empty false"));
OK(getFromSocket("/dispatch workspace special:test"));
Tests::spawnKitty();
{
auto str = getFromSocket("/monitors");
EXPECT_CONTAINS(str, "special workspace: -");
}
Tests::killAllWindows();
{
auto str = getFromSocket("/monitors");
EXPECT_CONTAINS(str, "special workspace: -");
}
Tests::spawnKitty();
OK(getFromSocket("/keyword misc:close_special_on_empty true"));
Tests::killAllWindows();
{
auto str = getFromSocket("/monitors");
EXPECT_NOT_CONTAINS(str, "special workspace: -");
}
NLog::log("{}Testing new_window_takes_over_fullscreen", Colors::YELLOW);
OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 0"));
Tests::spawnKitty("kitty_A");
OK(getFromSocket("/dispatch fullscreen 0"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 2");
EXPECT_CONTAINS(str, "kitty_A");
}
Tests::spawnKitty("kitty_B");
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 2");
EXPECT_CONTAINS(str, "kitty_A");
}
OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 1"));
Tests::spawnKitty("kitty_C");
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 2");
EXPECT_CONTAINS(str, "kitty_C");
}
OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 2"));
Tests::spawnKitty("kitty_D");
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 0");
EXPECT_CONTAINS(str, "kitty_D");
}
OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 0"));
Tests::killAllWindows();
NLog::log("{}Testing exit_window_retains_fullscreen", Colors::YELLOW);
OK(getFromSocket("/keyword misc:exit_window_retains_fullscreen false"));
Tests::spawnKitty("kitty_A");
Tests::spawnKitty("kitty_B");
OK(getFromSocket("/dispatch fullscreen 0"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 2");
}
OK(getFromSocket("/dispatch killwindow activewindow"));
Tests::waitUntilWindowsN(1);
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 0");
}
Tests::spawnKitty("kitty_B");
OK(getFromSocket("/dispatch fullscreen 0"));
OK(getFromSocket("/keyword misc:exit_window_retains_fullscreen true"));
OK(getFromSocket("/dispatch killwindow activewindow"));
Tests::waitUntilWindowsN(1);
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 2");
}
// kill all
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);
return !ret;
}
REGISTER_TEST_FN(test);

View File

@@ -1,85 +0,0 @@
#include "tests.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <print>
#include <thread>
#include <chrono>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <csignal>
#include <cerrno>
#include "../shared.hpp"
static int ret = 0;
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
#define UP CUniquePointer
#define SP CSharedPointer
static bool test() {
NLog::log("{}Testing persistent workspaces", Colors::GREEN);
EXPECT(Tests::windowCount(), 0);
// test on workspace "window"
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
getFromSocket("/dispatch workspace 1"); // no OK: we might be on 1 already
OK(getFromSocket("/keyword workspace 5, monitor:HEADLESS-2, persistent:1"));
OK(getFromSocket("/keyword workspace 6, monitor:HEADLESS-PERSISTENT-TEST, persistent:1"));
OK(getFromSocket("/keyword workspace name:PERSIST, monitor:HEADLESS-PERSISTENT-TEST, persistent:1"));
OK(getFromSocket("/keyword workspace name:PERSIST-2, monitor:HEADLESS-PERSISTENT-TEST, persistent:1"));
{
auto str = getFromSocket("/workspaces");
EXPECT_CONTAINS(str, "ID 5 (5)");
EXPECT_COUNT_STRING(str, "workspace ID ", 2);
}
OK(getFromSocket("/output create headless HEADLESS-PERSISTENT-TEST"));
{
auto str = getFromSocket("/monitors");
EXPECT_CONTAINS(str, "HEADLESS-PERSISTENT-TEST");
}
OK(getFromSocket("/dispatch focusmonitor HEADLESS-PERSISTENT-TEST"));
{
auto str = getFromSocket("/workspaces");
EXPECT_CONTAINS(str, "ID 2 (2)"); // this should be automatically generated by hl
EXPECT_CONTAINS(str, "ID 5 (5)");
EXPECT_CONTAINS(str, "ID 6 (6)");
EXPECT_CONTAINS(str, "(PERSIST) on monitor");
EXPECT_CONTAINS(str, "(PERSIST-2) on monitor");
EXPECT_COUNT_STRING(str, "workspace ID ", 6);
}
OK(getFromSocket("/reload"));
{
auto str = getFromSocket("/workspaces");
EXPECT_NOT_CONTAINS(str, "ID 5 (5)");
EXPECT_NOT_CONTAINS(str, "ID 6 (6)");
EXPECT_NOT_CONTAINS(str, "(PERSIST) on monitor");
EXPECT_COUNT_STRING(str, "workspace ID ", 2);
}
OK(getFromSocket("/output remove HEADLESS-PERSISTENT-TEST"));
// kill all
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);
// reload cfg
OK(getFromSocket("/reload"));
return !ret;
}
REGISTER_TEST_FN(test)

View File

@@ -1,176 +0,0 @@
#include <hyprutils/math/Vector2D.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/os/Process.hpp>
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include "../shared.hpp"
#include "tests.hpp"
using Hyprutils::Math::Vector2D;
static int ret = 0;
static bool spawnFloatingKitty() {
if (!Tests::spawnKitty()) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
OK(getFromSocket("/dispatch setfloating active"));
OK(getFromSocket("/dispatch resizeactive exact 100 100"));
return true;
}
static void expectSocket(const std::string& CMD) {
if (const auto RESULT = getFromSocket(CMD); RESULT != "ok") {
NLog::log("{}Failed: {}getFromSocket({}), expected ok, got {}. Source: {}@{}.", Colors::RED, Colors::RESET, CMD, RESULT, __FILE__, __LINE__);
ret = 1;
TESTS_FAILED++;
} else {
NLog::log("{}Passed: {}getFromSocket({}). Got ok", Colors::GREEN, Colors::RESET, CMD);
TESTS_PASSED++;
}
}
static void expectSnapMove(const Vector2D FROM, const Vector2D* TO) {
const Vector2D& A = FROM;
const Vector2D& B = TO ? *TO : FROM;
if (TO)
NLog::log("{}Expecting snap to ({},{}) when window is moved to ({},{})", Colors::YELLOW, B.x, B.y, A.x, A.y);
else
NLog::log("{}Expecting no snap when window is moved to ({},{})", Colors::YELLOW, A.x, A.y);
expectSocket(std::format("/dispatch moveactive exact {} {}", A.x, A.y));
expectSocket("/dispatch plugin:test:snapmove");
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("at: {},{}", B.x, B.y));
}
static void testWindowSnap(const bool RESPECTGAPS) {
const int BORDERSIZE = 2;
const int WINDOWSIZE = 100;
const int OTHER = 500;
const int WINDOWGAP = 8;
const int GAPSIN = 5;
const int GAP = (RESPECTGAPS ? 2 * GAPSIN : 0) + (2 * BORDERSIZE);
const int END = GAP + WINDOWSIZE;
int x;
Vector2D predict;
x = WINDOWGAP + END;
expectSnapMove({OTHER + x, OTHER}, nullptr);
expectSnapMove({OTHER - x, OTHER}, nullptr);
expectSnapMove({OTHER, OTHER + x}, nullptr);
expectSnapMove({OTHER, OTHER - x}, nullptr);
x -= 1;
expectSnapMove({OTHER + x, OTHER}, &(predict = {OTHER + END, OTHER}));
expectSnapMove({OTHER - x, OTHER}, &(predict = {OTHER - END, OTHER}));
expectSnapMove({OTHER, OTHER + x}, &(predict = {OTHER, OTHER + END}));
expectSnapMove({OTHER, OTHER - x}, &(predict = {OTHER, OTHER - END}));
}
static void testMonitorSnap(const bool RESPECTGAPS, const bool OVERLAP) {
const int BORDERSIZE = 2;
const int WINDOWSIZE = 100;
const int MONITORGAP = 10;
const int GAPSOUT = 20;
const int RESP = (RESPECTGAPS ? GAPSOUT : 0);
const int GAP = RESP + (OVERLAP ? 0 : BORDERSIZE);
const int END = GAP + WINDOWSIZE;
int x;
Vector2D predict;
x = MONITORGAP + GAP;
expectSnapMove({x, x}, nullptr);
x -= 1;
expectSnapMove({x, x}, &(predict = {GAP, GAP}));
x = MONITORGAP + END;
expectSnapMove({1920 - x, 1080 - x}, nullptr);
x -= 1;
expectSnapMove({1920 - x, 1080 - x}, &(predict = {1920 - END, 1080 - END}));
// test reserved area
const int RESERVED = 200;
const int RGAP = RESERVED + RESP + BORDERSIZE;
const int REND = RGAP + WINDOWSIZE;
x = MONITORGAP + RGAP;
expectSnapMove({x, x}, nullptr);
x -= 1;
expectSnapMove({x, x}, &(predict = {RGAP, RGAP}));
x = MONITORGAP + REND;
expectSnapMove({1920 - x, 1080 - x}, nullptr);
x -= 1;
expectSnapMove({1920 - x, 1080 - x}, &(predict = {1920 - REND, 1080 - REND}));
}
static bool test() {
NLog::log("{}Testing snap", Colors::GREEN);
// move to monitor HEADLESS-2
NLog::log("{}Moving to monitor HEADLESS-2", Colors::YELLOW);
OK(getFromSocket("/dispatch focusmonitor HEADLESS-2"));
NLog::log("{}Adding reserved monitor area to HEADLESS-2", Colors::YELLOW);
OK(getFromSocket("/keyword monitor HEADLESS-2,addreserved,200,200,200,200"));
// test on workspace "snap"
NLog::log("{}Dispatching workspace `snap`", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace name:snap"));
// spawn a kitty terminal and move to (500,500)
NLog::log("{}Spawning kittyProcA", Colors::YELLOW);
if (!spawnFloatingKitty())
return false;
NLog::log("{}Expecting 1 window", Colors::YELLOW);
EXPECT(Tests::windowCount(), 1);
NLog::log("{}Move the kitty window to (500,500)", Colors::YELLOW);
OK(getFromSocket("/dispatch moveactive exact 500 500"));
// spawn a second kitty terminal
NLog::log("{}Spawning kittyProcB", Colors::YELLOW);
if (!spawnFloatingKitty())
return false;
NLog::log("{}Expecting 2 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 2);
NLog::log("");
testWindowSnap(false);
testMonitorSnap(false, false);
NLog::log("\n{}Turning on respect_gaps", Colors::YELLOW);
OK(getFromSocket("/keyword general:snap:respect_gaps true"));
testWindowSnap(true);
testMonitorSnap(true, false);
NLog::log("\n{}Turning on border_overlap", Colors::YELLOW);
OK(getFromSocket("/keyword general:snap:respect_gaps false"));
OK(getFromSocket("/keyword general:snap:border_overlap true"));
testMonitorSnap(false, true);
NLog::log("\n{}Turning on both border_overlap and respect_gaps", Colors::YELLOW);
OK(getFromSocket("/keyword general:snap:respect_gaps true"));
testMonitorSnap(true, true);
// kill all
NLog::log("\n{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);
NLog::log("{}Reloading the config", Colors::YELLOW);
OK(getFromSocket("/reload"));
OK(getFromSocket("/dispatch workspace 1"));
return !ret;
}
REGISTER_TEST_FN(test)

View File

@@ -1,76 +0,0 @@
#include "tests.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <thread>
#include <chrono>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include "../shared.hpp"
static int ret = 0;
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
#define UP CUniquePointer
#define SP CSharedPointer
static bool test() {
NLog::log("{}Testing solitary clients", Colors::GREEN);
OK(getFromSocket("/keyword general:allow_tearing false"));
OK(getFromSocket("/keyword render:direct_scanout 0"));
OK(getFromSocket("/keyword cursor:no_hardware_cursors 1"));
NLog::log("{}Expecting blocked solitary/DS/tearing", Colors::YELLOW);
{
auto str = getFromSocket("/monitors");
EXPECT_CONTAINS(str, "solitary: 0\n");
EXPECT_CONTAINS(str, "solitaryBlockedBy: windowed mode,missing candidate");
EXPECT_CONTAINS(str, "activelyTearing: false");
EXPECT_CONTAINS(str, "tearingBlockedBy: next frame is not torn,user settings,not supported by monitor,missing candidate");
EXPECT_CONTAINS(str, "directScanoutTo: 0\n");
EXPECT_CONTAINS(str, "directScanoutBlockedBy: user settings,software renders/cursors,missing candidate");
}
// FIXME: need a reliable client with solitary opaque surface in fullscreen. kitty doesn't work all the time
// NLog::log("{}Spawning kittyProcA", Colors::YELLOW);
// auto kittyProcA = Tests::spawnKitty();
// if (!kittyProcA) {
// NLog::log("{}Error: kitty did not spawn", Colors::RED);
// return false;
// }
// OK(getFromSocket("/keyword general:allow_tearing true"));
// OK(getFromSocket("/keyword render:direct_scanout 1"));
// NLog::log("{}", getFromSocket("/clients"));
// OK(getFromSocket("/dispatch fullscreen"));
// NLog::log("{}", getFromSocket("/clients"));
// std::this_thread::sleep_for(std::chrono::milliseconds(100));
// NLog::log("{}Expecting kitty to almost pass for solitary/DS/tearing", Colors::YELLOW);
// {
// auto str = getFromSocket("/monitors");
// EXPECT_NOT_CONTAINS(str, "solitary: 0\n");
// EXPECT_CONTAINS(str, "solitaryBlockedBy: null");
// EXPECT_CONTAINS(str, "activelyTearing: false");
// EXPECT_CONTAINS(str, "tearingBlockedBy: next frame is not torn,not supported by monitor,window settings");
// }
// OK(getFromSocket("/dispatch setprop active immediate 1"));
// NLog::log("{}Expecting kitty to almost pass for tearing", Colors::YELLOW);
// {
// auto str = getFromSocket("/monitors");
// EXPECT_CONTAINS(str, "tearingBlockedBy: next frame is not torn,not supported by monitor\n");
// }
// // kill all
// NLog::log("{}Killing all windows", Colors::YELLOW);
// Tests::killAllWindows();
NLog::log("{}Reloading the config", Colors::YELLOW);
OK(getFromSocket("/reload"));
return !ret;
}
REGISTER_TEST_FN(test)

View File

@@ -1,12 +0,0 @@
#pragma once
#include <vector>
#include <functional>
inline std::vector<std::function<bool()>> testFns;
#define REGISTER_TEST_FN(fn) \
static auto _register_fn = [] { \
testFns.emplace_back(fn); \
return 1; \
}();

View File

@@ -1,121 +0,0 @@
#include <cmath>
#include <thread>
#include <chrono>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include "../shared.hpp"
#include "tests.hpp"
static int ret = 0;
static bool spawnKitty(const std::string& class_) {
NLog::log("{}Spawning {}", Colors::YELLOW, class_);
if (!Tests::spawnKitty(class_)) {
NLog::log("{}Error: {} did not spawn", Colors::RED, class_);
return false;
}
return true;
}
static bool test() {
NLog::log("{}Testing windows", Colors::GREEN);
// test on workspace "window"
NLog::log("{}Switching to workspace `window`", Colors::YELLOW);
getFromSocket("/dispatch workspace name:window");
if (!spawnKitty("kitty_A"))
return false;
// check kitty properties. One kitty should take the entire screen, as this is smart gaps
NLog::log("{}Expecting kitty_A to take up the whole screen", Colors::YELLOW);
{
auto str = getFromSocket("/clients");
EXPECT(str.contains("at: 0,0"), true);
EXPECT(str.contains("size: 1920,1080"), true);
EXPECT(str.contains("fullscreen: 0"), true);
}
NLog::log("{}Testing window split ratios", Colors::YELLOW);
{
const double RATIO = 1.25;
const double PERCENT = RATIO / 2.0 * 100.0;
const int GAPSIN = 5;
const int GAPSOUT = 20;
const int BORDERS = 2 * 2;
const int WTRIM = BORDERS + GAPSIN + GAPSOUT;
const int HEIGHT = 1080 - (BORDERS + (GAPSOUT * 2));
const int WIDTH1 = std::round(1920.0 / 2.0 * (2 - RATIO)) - WTRIM;
const int WIDTH2 = std::round(1920.0 / 2.0 * RATIO) - WTRIM;
OK(getFromSocket("/keyword dwindle:default_split_ratio 1.25"));
if (!spawnKitty("kitty_B"))
return false;
NLog::log("{}Expecting kitty_B to take up roughly {}% of screen width", Colors::YELLOW, 100 - PERCENT);
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH1, HEIGHT));
OK(getFromSocket("/dispatch killwindow activewindow"));
Tests::waitUntilWindowsN(1);
NLog::log("{}Inverting the split ratio", Colors::YELLOW);
OK(getFromSocket("/keyword dwindle:default_split_ratio 0.75"));
if (!spawnKitty("kitty_B"))
return false;
NLog::log("{}Expecting kitty_B to take up roughly {}% of screen width", Colors::YELLOW, PERCENT);
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH2, HEIGHT));
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
NLog::log("{}Expecting kitty_A to have the same width as the previous kitty_B", Colors::YELLOW);
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH1, HEIGHT));
OK(getFromSocket("/keyword dwindle:default_split_ratio 1"));
}
// open xeyes
NLog::log("{}Spawning xeyes", Colors::YELLOW);
getFromSocket("/dispatch exec xeyes");
NLog::log("{}Keep checking if xeyes spawned", Colors::YELLOW);
int counter = 0;
while (Tests::windowCount() != 3) {
counter++;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (counter > 50) {
EXPECT(Tests::windowCount(), 3);
return !ret;
}
}
NLog::log("{}Expecting 3 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 3);
NLog::log("{}Checking props of xeyes", Colors::YELLOW);
// check some window props of xeyes, try to tile them
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "floating: 1");
getFromSocket("/dispatch settiled class:XEyes");
std::this_thread::sleep_for(std::chrono::milliseconds(200));
str = getFromSocket("/clients");
EXPECT_NOT_CONTAINS(str, "floating: 1");
}
// kill all
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);
return !ret;
}
REGISTER_TEST_FN(test)

View File

@@ -1,358 +0,0 @@
#include "tests.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <print>
#include <thread>
#include <chrono>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <csignal>
#include <cerrno>
#include "../shared.hpp"
static int ret = 0;
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
#define UP CUniquePointer
#define SP CSharedPointer
static bool test() {
NLog::log("{}Testing workspaces", Colors::GREEN);
EXPECT(Tests::windowCount(), 0);
// test on workspace "window"
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
NLog::log("{}Spawning kittyProc on ws 1", Colors::YELLOW);
auto kittyProcA = Tests::spawnKitty();
if (!kittyProcA) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
NLog::log("{}Switching to workspace 3", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 3"));
NLog::log("{}Spawning kittyProc on ws 3", Colors::YELLOW);
auto kittyProcB = Tests::spawnKitty();
if (!kittyProcB) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
NLog::log("{}Switching to workspace +1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace +1"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 2 (2)");
}
// check if the other workspaces are alive
{
auto str = getFromSocket("/workspaces");
EXPECT_CONTAINS(str, "workspace ID 3 (3)");
EXPECT_CONTAINS(str, "workspace ID 1 (1)");
}
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
{
auto str = getFromSocket("/workspaces");
EXPECT_NOT_CONTAINS(str, "workspace ID 2 (2)");
}
NLog::log("{}Switching to workspace m+1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace m+1"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
}
NLog::log("{}Switching to workspace -1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace -1"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 2 (2)");
}
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
NLog::log("{}Switching to workspace r+1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace r+1"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 2 (2)");
}
NLog::log("{}Switching to workspace r+1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace r+1"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
}
NLog::log("{}Switching to workspace r~1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace r~1"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 1 (1)");
}
NLog::log("{}Switching to workspace empty", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace empty"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 2 (2)");
}
NLog::log("{}Switching to workspace previous", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace previous"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 1 (1)");
}
NLog::log("{}Switching to workspace name:TEST_WORKSPACE_NULL", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace name:TEST_WORKSPACE_NULL"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID -1337 (TEST_WORKSPACE_NULL)");
}
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
// add a new monitor
NLog::log("{}Adding a new monitor", Colors::YELLOW);
EXPECT(getFromSocket("/output create headless"), "ok")
// should take workspace 2
{
auto str = getFromSocket("/monitors");
EXPECT_CONTAINS(str, "active workspace: 2 (2)");
EXPECT_CONTAINS(str, "active workspace: 1 (1)");
EXPECT_CONTAINS(str, "HEADLESS-3");
}
// focus the first monitor
OK(getFromSocket("/dispatch focusmonitor 0"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 1 (1)");
}
NLog::log("{}Switching to workspace r+1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace r+1"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
}
NLog::log("{}Switching to workspace r~2", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch workspace r~2"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
}
NLog::log("{}Switching to workspace m+1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace m+1"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 1 (1)");
}
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
// no OK: this will throw an error as it should
getFromSocket("/dispatch workspace 1");
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 1 (1)");
}
NLog::log("{}Testing back_and_forth", Colors::YELLOW);
OK(getFromSocket("/keyword binds:workspace_back_and_forth true"));
OK(getFromSocket("/dispatch workspace 1"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
}
OK(getFromSocket("/keyword binds:workspace_back_and_forth false"));
NLog::log("{}Testing hide_special_on_workspace_change", Colors::YELLOW);
OK(getFromSocket("/keyword binds:hide_special_on_workspace_change true"));
OK(getFromSocket("/dispatch workspace special:HELLO"));
{
auto str = getFromSocket("/monitors");
EXPECT_CONTAINS(str, "special workspace: -");
EXPECT_CONTAINS(str, "special:HELLO");
}
// no OK: will err (it shouldn't prolly but oh well)
getFromSocket("/dispatch workspace 3");
{
auto str = getFromSocket("/monitors");
EXPECT_COUNT_STRING(str, "special workspace: 0 ()", 2);
}
OK(getFromSocket("/keyword binds:hide_special_on_workspace_change false"));
NLog::log("{}Testing allow_workspace_cycles", Colors::YELLOW);
OK(getFromSocket("/keyword binds:allow_workspace_cycles true"));
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch workspace 3"));
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch workspace previous"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
}
OK(getFromSocket("/dispatch workspace previous"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 1 (1)");
}
OK(getFromSocket("/dispatch workspace previous"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
}
OK(getFromSocket("/keyword binds:allow_workspace_cycles false"));
OK(getFromSocket("/dispatch workspace 1"));
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
// spawn 3 kitties
NLog::log("{}Testing focus_preferred_method", Colors::YELLOW);
OK(getFromSocket("/keyword dwindle:force_split 2"));
Tests::spawnKitty("kitty_A");
Tests::spawnKitty("kitty_B");
Tests::spawnKitty("kitty_C");
OK(getFromSocket("/keyword dwindle:force_split 0"));
// focus kitty 2: will be top right (dwindle)
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
// resize it to be a bit taller
OK(getFromSocket("/dispatch resizeactive +20 +20"));
// now we test focus methods.
OK(getFromSocket("/keyword binds:focus_preferred_method 0"));
OK(getFromSocket("/dispatch focuswindow class:kitty_C"));
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch movefocus r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_C");
}
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/keyword binds:focus_preferred_method 1"));
OK(getFromSocket("/dispatch movefocus r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_B");
}
NLog::log("{}Testing movefocus_cycles_fullscreen", Colors::YELLOW);
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch focusmonitor HEADLESS-3"));
Tests::spawnKitty("kitty_D");
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_D");
}
OK(getFromSocket("/dispatch focusmonitor l"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_A");
}
OK(getFromSocket("/keyword binds:movefocus_cycles_fullscreen false"));
OK(getFromSocket("/dispatch fullscreen 0"));
OK(getFromSocket("/dispatch movefocus r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_D");
}
OK(getFromSocket("/dispatch focusmonitor l"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_A");
}
OK(getFromSocket("/keyword binds:movefocus_cycles_fullscreen true"));
OK(getFromSocket("/dispatch movefocus r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_B");
}
// destroy the headless output
OK(getFromSocket("/output remove HEADLESS-3"));
// kill all
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);
return !ret;
}
REGISTER_TEST_FN(test)

View File

@@ -1,31 +0,0 @@
#include "plugin.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <print>
#include <thread>
#include <chrono>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <csignal>
#include <cerrno>
#include "../shared.hpp"
bool testPlugin() {
const auto RESPONSE = getFromSocket("/dispatch plugin:test:test");
if (RESPONSE != "ok") {
NLog::log("{}Plugin tests failed, plugin returned:\n{}{}", Colors::RED, Colors::RESET, RESPONSE);
return false;
}
return true;
}
bool testVkb() {
const auto RESPONSE = getFromSocket("/dispatch plugin:test:vkb");
if (RESPONSE != "ok") {
NLog::log("{}Vkb tests failed, tests returned:\n{}{}", Colors::RED, Colors::RESET, RESPONSE);
return false;
}
return true;
}

View File

@@ -1,4 +0,0 @@
#pragma once
bool testPlugin();
bool testVkb();

View File

@@ -1,92 +0,0 @@
#include "shared.hpp"
#include <csignal>
#include <cerrno>
#include <thread>
#include <print>
#include "../shared.hpp"
#include "../hyprctlCompat.hpp"
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
CUniquePointer<CProcess> Tests::spawnKitty(const std::string& class_) {
const auto COUNT_BEFORE = windowCount();
CUniquePointer<CProcess> kitty = makeUnique<CProcess>("kitty", class_.empty() ? std::vector<std::string>{} : std::vector<std::string>{"--class", class_});
kitty->addEnv("WAYLAND_DISPLAY", WLDISPLAY);
kitty->runAsync();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
// wait while kitty spawns
int counter = 0;
while (processAlive(kitty->pid()) && windowCount() == COUNT_BEFORE) {
counter++;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (counter > 50)
return nullptr;
}
if (!processAlive(kitty->pid()))
return nullptr;
return kitty;
}
bool Tests::processAlive(pid_t pid) {
errno = 0;
int ret = kill(pid, 0);
return ret != -1 || errno != ESRCH;
}
int Tests::windowCount() {
return countOccurrences(getFromSocket("/clients"), "focusHistoryID: ");
}
int Tests::countOccurrences(const std::string& in, const std::string& what) {
int cnt = 0;
auto pos = in.find(what);
while (pos != std::string::npos) {
cnt++;
pos = in.find(what, pos + what.length() - 1);
}
return cnt;
}
bool Tests::killAllWindows() {
auto str = getFromSocket("/clients");
auto pos = str.find("Window ");
while (pos != std::string::npos) {
auto pos2 = str.find(" -> ", pos);
getFromSocket("/dispatch killwindow address:0x" + str.substr(pos + 7, pos2 - pos - 7));
pos = str.find("Window ", pos + 5);
}
int counter = 0;
while (Tests::windowCount() != 0) {
counter++;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (counter > 50) {
std::println("{}Timed out waiting for windows to close", Colors::RED);
return false;
}
}
return true;
}
void Tests::waitUntilWindowsN(int n) {
int counter = 0;
while (Tests::windowCount() != n) {
counter++;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (counter > 50) {
std::println("{}Timed out waiting for windows", Colors::RED);
return;
}
}
}

View File

@@ -1,17 +0,0 @@
#pragma once
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <sys/types.h>
#include "../Log.hpp"
//NOLINTNEXTLINE
namespace Tests {
Hyprutils::Memory::CUniquePointer<Hyprutils::OS::CProcess> spawnKitty(const std::string& class_ = "");
bool processAlive(pid_t pid);
int windowCount();
int countOccurrences(const std::string& in, const std::string& what);
bool killAllWindows();
void waitUntilWindowsN(int n);
};

View File

@@ -1,315 +0,0 @@
# This is an example Hyprland config file.
# Refer to the wiki for more information.
# https://wiki.hyprland.org/Configuring/
# Please note not all available settings / options are set here.
# For a full list, see the wiki
# You can split this configuration into multiple files
# Create your files separately and then link them to this file like this:
# source = ~/.config/hypr/myColors.conf
################
### MONITORS ###
################
# See https://wiki.hyprland.org/Configuring/Monitors/
monitor=HEADLESS-1,1920x1080@60,auto-right,1
monitor=HEADLESS-2,1920x1080@60,auto-right,1
monitor=HEADLESS-3,1920x1080@60,auto-right,1
monitor=HEADLESS-4,1920x1080@60,auto-right,1
monitor=HEADLESS-5,1920x1080@60,auto-right,1
monitor=HEADLESS-6,1920x1080@60,auto-right,1
monitor=HEADLESS-PERSISTENT-TEST,1920x1080@60,auto-right,1
monitor=,disabled
###################
### MY PROGRAMS ###
###################
# See https://wiki.hyprland.org/Configuring/Keywords/
# Set programs that you use
$terminal = kitty
$fileManager = dolphin
$menu = wofi --show drun
#################
### AUTOSTART ###
#################
# Autostart necessary processes (like notifications daemons, status bars, etc.)
# Or execute your favorite apps at launch like this:
# exec-once = $terminal
# exec-once = nm-applet &
# exec-once = waybar & hyprpaper & firefox
#############################
### ENVIRONMENT VARIABLES ###
#############################
# See https://wiki.hyprland.org/Configuring/Environment-variables/
env = XCURSOR_SIZE,24
env = HYPRCURSOR_SIZE,24
#####################
### LOOK AND FEEL ###
#####################
# Refer to https://wiki.hyprland.org/Configuring/Variables/
# https://wiki.hyprland.org/Configuring/Variables/#general
general {
gaps_in = 5
gaps_out = 20
border_size = 2
snap {
enabled = true
window_gap = 8
monitor_gap = 10
respect_gaps = false
border_overlap = false
}
# https://wiki.hyprland.org/Configuring/Variables/#variable-types for info about colors
col.active_border = rgba(33ccffee) rgba(00ff99ee) 45deg
col.inactive_border = rgba(595959aa)
# Set to true enable resizing windows by clicking and dragging on borders and gaps
resize_on_border = false
# Please see https://wiki.hyprland.org/Configuring/Tearing/ before you turn this on
allow_tearing = false
layout = dwindle
}
# https://wiki.hyprland.org/Configuring/Variables/#decoration
decoration {
rounding = 10
rounding_power = 2
# Change transparency of focused and unfocused windows
active_opacity = 1.0
inactive_opacity = 1.0
shadow {
enabled = true
range = 4
render_power = 3
color = rgba(1a1a1aee)
}
# https://wiki.hyprland.org/Configuring/Variables/#blur
blur {
enabled = true
size = 3
passes = 1
vibrancy = 0.1696
}
}
# https://wiki.hyprland.org/Configuring/Variables/#animations
animations {
enabled = 0
# Default animations, see https://wiki.hyprland.org/Configuring/Animations/ for more
bezier = easeOutQuint,0.23,1,0.32,1
bezier = easeInOutCubic,0.65,0.05,0.36,1
bezier = linear,0,0,1,1
bezier = almostLinear,0.5,0.5,0.75,1.0
bezier = quick,0.15,0,0.1,1
animation = global, 1, 10, default
animation = border, 1, 5.39, easeOutQuint
animation = windows, 1, 4.79, easeOutQuint
animation = windowsIn, 1, 4.1, easeOutQuint, popin 87%
animation = windowsOut, 1, 1.49, linear, popin 87%
animation = fadeIn, 1, 1.73, almostLinear
animation = fadeOut, 1, 1.46, almostLinear
animation = fade, 1, 3.03, quick
animation = layers, 1, 3.81, easeOutQuint
animation = layersIn, 1, 4, easeOutQuint, fade
animation = layersOut, 1, 1.5, linear, fade
animation = fadeLayersIn, 1, 1.79, almostLinear
animation = fadeLayersOut, 1, 1.39, almostLinear
animation = workspaces, 1, 1.94, almostLinear, fade
animation = workspacesIn, 1, 1.21, almostLinear, fade
animation = workspacesOut, 1, 1.94, almostLinear, fade
}
# Ref https://wiki.hyprland.org/Configuring/Workspace-Rules/
# "Smart gaps" / "No gaps when only"
# uncomment all if you wish to use that.
# workspace = w[tv1], gapsout:0, gapsin:0
# workspace = f[1], gapsout:0, gapsin:0
# windowrulev2 = bordersize 0, floating:0, onworkspace:w[tv1]
# windowrulev2 = rounding 0, floating:0, onworkspace:w[tv1]
# windowrulev2 = bordersize 0, floating:0, onworkspace:f[1]
# windowrulev2 = rounding 0, floating:0, onworkspace:f[1]
# See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more
dwindle {
pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
preserve_split = true # You probably want this
split_bias = 1
}
# See https://wiki.hyprland.org/Configuring/Master-Layout/ for more
master {
new_status = master
}
# https://wiki.hyprland.org/Configuring/Variables/#misc
misc {
force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers
disable_hyprland_logo = false # If true disables the random hyprland logo / anime girl background. :(
}
#############
### INPUT ###
#############
# https://wiki.hyprland.org/Configuring/Variables/#input
input {
kb_layout = us
kb_variant =
kb_model =
kb_options =
kb_rules =
follow_mouse = 1
sensitivity = 0 # -1.0 - 1.0, 0 means no modification.
touchpad {
natural_scroll = false
}
}
# https://wiki.hyprland.org/Configuring/Variables/#gestures
gestures {
workspace_swipe = false
}
# Example per-device config
# See https://wiki.hyprland.org/Configuring/Keywords/#per-device-input-configs for more
device {
name = epic-mouse-v1
sensitivity = -0.5
}
###################
### KEYBINDINGS ###
###################
# See https://wiki.hyprland.org/Configuring/Keywords/
$mainMod = SUPER # Sets "Windows" key as main modifier
# Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more
bind = $mainMod, Q, exec, $terminal
bind = $mainMod, C, killactive,
bind = $mainMod, M, exit,
bind = $mainMod, E, exec, $fileManager
bind = $mainMod, V, togglefloating,
bind = $mainMod, R, exec, $menu
bind = $mainMod, P, pseudo, # dwindle
bind = $mainMod, J, togglesplit, # dwindle
# Move focus with mainMod + arrow keys
bind = $mainMod, left, movefocus, l
bind = $mainMod, right, movefocus, r
bind = $mainMod, up, movefocus, u
bind = $mainMod, down, movefocus, d
# Switch workspaces with mainMod + [0-9]
bind = $mainMod, 1, workspace, 1
bind = $mainMod, 2, workspace, 2
bind = $mainMod, 3, workspace, 3
bind = $mainMod, 4, workspace, 4
bind = $mainMod, 5, workspace, 5
bind = $mainMod, 6, workspace, 6
bind = $mainMod, 7, workspace, 7
bind = $mainMod, 8, workspace, 8
bind = $mainMod, 9, workspace, 9
bind = $mainMod, 0, workspace, 10
# Move active window to a workspace with mainMod + SHIFT + [0-9]
bind = $mainMod SHIFT, 1, movetoworkspace, 1
bind = $mainMod SHIFT, 2, movetoworkspace, 2
bind = $mainMod SHIFT, 3, movetoworkspace, 3
bind = $mainMod SHIFT, 4, movetoworkspace, 4
bind = $mainMod SHIFT, 5, movetoworkspace, 5
bind = $mainMod SHIFT, 6, movetoworkspace, 6
bind = $mainMod SHIFT, 7, movetoworkspace, 7
bind = $mainMod SHIFT, 8, movetoworkspace, 8
bind = $mainMod SHIFT, 9, movetoworkspace, 9
bind = $mainMod SHIFT, 0, movetoworkspace, 10
# Example special workspace (scratchpad)
bind = $mainMod, S, togglespecialworkspace, magic
bind = $mainMod SHIFT, S, movetoworkspace, special:magic
# Scroll through existing workspaces with mainMod + scroll
bind = $mainMod, mouse_down, workspace, e+1
bind = $mainMod, mouse_up, workspace, e-1
# Move/resize windows with mainMod + LMB/RMB and dragging
bindm = $mainMod, mouse:272, movewindow
bindm = $mainMod, mouse:273, resizewindow
# Laptop multimedia keys for volume and LCD brightness
bindel = ,XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+
bindel = ,XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
bindel = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
bindel = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle
bindel = ,XF86MonBrightnessUp, exec, brightnessctl s 10%+
bindel = ,XF86MonBrightnessDown, exec, brightnessctl s 10%-
# Requires playerctl
bindl = , XF86AudioNext, exec, playerctl next
bindl = , XF86AudioPause, exec, playerctl play-pause
bindl = , XF86AudioPlay, exec, playerctl play-pause
bindl = , XF86AudioPrev, exec, playerctl previous
##############################
### WINDOWS AND WORKSPACES ###
##############################
# See https://wiki.hyprland.org/Configuring/Window-Rules/ for more
# See https://wiki.hyprland.org/Configuring/Workspace-Rules/ for workspace rules
# Example windowrule v1
# windowrule = float, ^(kitty)$
# Example windowrule v2
# windowrulev2 = float,class:^(kitty)$,title:^(kitty)$
# Ignore maximize requests from apps. You'll probably like this.
windowrulev2 = suppressevent maximize, class:.*
# Fix some dragging issues with XWayland
windowrulev2 = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0
# Workspace "windows" is a smart gaps one
workspace = n[s:window] w[tv1], gapsout:0, gapsin:0
workspace = n[s:window] f[1], gapsout:0, gapsin:0
windowrulev2 = bordersize 0, floating:0, onworkspace:n[s:window] w[tv1]
windowrulev2 = rounding 0, floating:0, onworkspace:n[s:window] w[tv1]
windowrulev2 = bordersize 0, floating:0, onworkspace:n[s:window] f[1]
windowrulev2 = rounding 0, floating:0, onworkspace:n[s:window] f[1]

View File

@@ -31,20 +31,8 @@ if cpp_compiler.check_header('execinfo.h')
add_project_arguments('-DHAS_EXECINFO', language: 'cpp') add_project_arguments('-DHAS_EXECINFO', language: 'cpp')
endif endif
aquamarine = dependency('aquamarine', version: '>=0.9.0') aquamarine = dependency('aquamarine', version: '>=0.4.2')
hyprcursor = dependency('hyprcursor', version: '>=0.1.7')
hyprgraphics = dependency('hyprgraphics', version: '>= 0.1.3')
hyprlang = dependency('hyprlang', version: '>= 0.3.2')
hyprutils = dependency('hyprutils', version: '>= 0.8.2')
aquamarine_version_list = aquamarine.version().split('.')
add_project_arguments(['-DAQUAMARINE_VERSION="@0@"'.format(aquamarine.version())], language: 'cpp') add_project_arguments(['-DAQUAMARINE_VERSION="@0@"'.format(aquamarine.version())], language: 'cpp')
add_project_arguments(['-DAQUAMARINE_VERSION_MAJOR=@0@'.format(aquamarine_version_list.get(0))], language: 'cpp')
add_project_arguments(['-DAQUAMARINE_VERSION_MINOR=@0@'.format(aquamarine_version_list.get(1))], language: 'cpp')
add_project_arguments(['-DAQUAMARINE_VERSION_PATCH=@0@'.format(aquamarine_version_list.get(2))], language: 'cpp')
add_project_arguments(['-DHYPRCURSOR_VERSION="@0@"'.format(hyprcursor.version())], language: 'cpp')
add_project_arguments(['-DHYPRGRAPHICS_VERSION="@0@"'.format(hyprgraphics.version())], language: 'cpp')
add_project_arguments(['-DHYPRLANG_VERSION="@0@"'.format(hyprlang.version())], language: 'cpp')
add_project_arguments(['-DHYPRUTILS_VERSION="@0@"'.format(hyprutils.version())], language: 'cpp')
xcb_dep = dependency('xcb', required: get_option('xwayland')) xcb_dep = dependency('xcb', required: get_option('xwayland'))
xcb_composite_dep = dependency('xcb-composite', required: get_option('xwayland')) xcb_composite_dep = dependency('xcb-composite', required: get_option('xwayland'))
@@ -62,32 +50,27 @@ endif
backtrace_dep = cpp_compiler.find_library('execinfo', required: false) backtrace_dep = cpp_compiler.find_library('execinfo', required: false)
epoll_dep = dependency('epoll-shim', required: false) # timerfd on BSDs epoll_dep = dependency('epoll-shim', required: false) # timerfd on BSDs
inotify_dep = dependency('libinotify', required: false) # inotify on BSDs
re2 = dependency('re2', required: true)
# Handle options # Handle options
systemd_option = get_option('systemd') if get_option('systemd').enabled()
systemd = dependency('systemd', required: systemd_option) systemd = dependency('systemd')
systemd_option.enable_auto_if(systemd.found())
if (systemd_option.enabled())
message('Enabling systemd integration')
add_project_arguments('-DUSES_SYSTEMD', language: 'cpp') add_project_arguments('-DUSES_SYSTEMD', language: 'cpp')
subdir('systemd') subdir('systemd')
endif endif
if get_option('legacy_renderer').enabled()
add_project_arguments('-DLEGACY_RENDERER', language: 'cpp')
endif
if get_option('buildtype') == 'debug' if get_option('buildtype') == 'debug'
add_project_arguments('-DHYPRLAND_DEBUG', language: 'cpp') add_project_arguments('-DHYPRLAND_DEBUG', language: 'cpp')
endif endif
# Generate hyprland version and populate version.h # Generate hyprland version and populate version.h
run_command('sh', '-c', 'scripts/generateVersion.sh', check: true) run_command('sh', '-c', 'scripts/generateVersion.sh', check: true)
# Make shader files includable
run_command('sh', '-c', 'scripts/generateShaderIncludes.sh', check: true)
# Install headers # Install headers
globber = run_command('find', 'src', '-name', '*.h*', '-o', '-name', '*.inc', check: true) globber = run_command('find', 'src', '-name', '*.h*', check: true)
headers = globber.stdout().strip().split('\n') headers = globber.stdout().strip().split('\n')
foreach file : headers foreach file : headers
install_headers(file, subdir: 'hyprland', preserve_path: true) install_headers(file, subdir: 'hyprland', preserve_path: true)
@@ -104,14 +87,11 @@ endif
subdir('protocols') subdir('protocols')
subdir('src') subdir('src')
subdir('hyprctl') subdir('hyprctl')
subdir('hyprpm/src')
subdir('assets') subdir('assets')
subdir('example') subdir('example')
subdir('docs') subdir('docs')
if get_option('hyprpm').enabled()
subdir('hyprpm/src')
endif
# Generate hyprland.pc # Generate hyprland.pc
pkg_install_dir = join_paths(get_option('datadir'), 'pkgconfig') pkg_install_dir = join_paths(get_option('datadir'), 'pkgconfig')

View File

@@ -1,5 +1,4 @@
option('xwayland', type: 'feature', value: 'auto', description: 'Enable support for X11 applications') option('xwayland', type: 'feature', value: 'auto', description: 'Enable support for X11 applications')
option('systemd', type: 'feature', value: 'auto', description: 'Enable systemd integration') option('systemd', type: 'feature', value: 'auto', description: 'Enable systemd integration')
option('uwsm', type: 'feature', value: 'enabled', description: 'Enable uwsm integration (only if systemd is enabled)') option('legacy_renderer', type: 'feature', value: 'disabled', description: 'Enable legacy renderer')
option('hyprpm', type: 'feature', value: 'enabled', description: 'Enable hyprpm')
option('tracy_enable', type: 'boolean', value: false , description: 'Enable profiling') option('tracy_enable', type: 'boolean', value: false , description: 'Enable profiling')

View File

@@ -5,19 +5,14 @@
pkg-config, pkg-config,
pkgconf, pkgconf,
makeWrapper, makeWrapper,
cmake,
meson, meson,
ninja, ninja,
aquamarine, aquamarine,
binutils, binutils,
cairo, cairo,
epoll-shim,
git, git,
glaze,
hyprcursor, hyprcursor,
hyprgraphics,
hyprland-protocols, hyprland-protocols,
hyprland-qtutils,
hyprlang, hyprlang,
hyprutils, hyprutils,
hyprwayland-scanner, hyprwayland-scanner,
@@ -27,10 +22,9 @@
libinput, libinput,
libxkbcommon, libxkbcommon,
libuuid, libuuid,
libgbm, mesa,
pango, pango,
pciutils, pciutils,
re2,
systemd, systemd,
tomlplusplus, tomlplusplus,
udis86-hyprland, udis86-hyprland,
@@ -41,6 +35,7 @@
xwayland, xwayland,
debug ? false, debug ? false,
enableXWayland ? true, enableXWayland ? true,
legacyRenderer ? false,
withSystemd ? lib.meta.availableOn stdenv.hostPlatform systemd, withSystemd ? lib.meta.availableOn stdenv.hostPlatform systemd,
wrapRuntimeDeps ? true, wrapRuntimeDeps ? true,
version ? "git", version ? "git",
@@ -51,14 +46,13 @@
enableNvidiaPatches ? false, enableNvidiaPatches ? false,
nvidiaPatches ? false, nvidiaPatches ? false,
hidpiXWayland ? false, hidpiXWayland ? false,
legacyRenderer ? false,
}: let }: let
inherit (builtins) foldl' readFile; inherit (builtins) baseNameOf foldl';
inherit (lib.asserts) assertMsg; inherit (lib.asserts) assertMsg;
inherit (lib.attrsets) mapAttrsToList; inherit (lib.attrsets) mapAttrsToList;
inherit (lib.lists) flatten concatLists optional optionals; inherit (lib.lists) flatten concatLists optional optionals;
inherit (lib.strings) makeBinPath optionalString mesonBool mesonEnable trim; inherit (lib.sources) cleanSourceWith cleanSource;
fs = lib.fileset; inherit (lib.strings) hasSuffix makeBinPath optionalString mesonBool mesonEnable;
adapters = flatten [ adapters = flatten [
stdenvAdapters.useMoldLinker stdenvAdapters.useMoldLinker
@@ -69,34 +63,17 @@
in in
assert assertMsg (!nvidiaPatches) "The option `nvidiaPatches` has been removed."; assert assertMsg (!nvidiaPatches) "The option `nvidiaPatches` has been removed.";
assert assertMsg (!enableNvidiaPatches) "The option `enableNvidiaPatches` has been removed."; assert assertMsg (!enableNvidiaPatches) "The option `enableNvidiaPatches` has been removed.";
assert assertMsg (!hidpiXWayland) "The option `hidpiXWayland` has been removed. Please refer https://wiki.hypr.land/Configuring/XWayland"; assert assertMsg (!hidpiXWayland) "The option `hidpiXWayland` has been removed. Please refer https://wiki.hyprland.org/Configuring/XWayland";
assert assertMsg (!legacyRenderer) "The option `legacyRenderer` has been removed. Legacy renderer is no longer supported."; customStdenv.mkDerivation {
customStdenv.mkDerivation (finalAttrs: {
pname = "hyprland${optionalString debug "-debug"}"; pname = "hyprland${optionalString debug "-debug"}";
inherit version; inherit version;
src = fs.toSource { src = cleanSourceWith {
root = ../.; filter = name: _type: let
fileset = baseName = baseNameOf (toString name);
fs.intersection in
# allows non-flake builds to only include files tracked by git ! (hasSuffix ".nix" baseName);
(fs.gitTracked ../.) src = cleanSource ../.;
(fs.unions [
../assets/hyprland-portals.conf
../assets/install
../hyprctl
../hyprland.pc.in
../LICENSE
../meson_options.txt
../protocols
../src
../systemd
../VERSION
(fs.fileFilter (file: file.hasExt "1") ../docs)
(fs.fileFilter (file: file.hasExt "conf" || file.hasExt "desktop") ../example)
(fs.fileFilter (file: file.hasExt "sh") ../scripts)
(fs.fileFilter (file: file.name == "meson.build") ../.)
]);
}; };
postPatch = '' postPatch = ''
@@ -111,7 +88,6 @@ in
DATE = date; DATE = date;
DIRTY = optionalString (commit == "") "dirty"; DIRTY = optionalString (commit == "") "dirty";
HASH = commit; HASH = commit;
TAG = "v${trim (readFile "${finalAttrs.src}/VERSION")}";
depsBuildBuild = [ depsBuildBuild = [
pkg-config pkg-config
@@ -122,7 +98,6 @@ in
makeWrapper makeWrapper
meson meson
ninja ninja
cmake # needed for glaze
pkg-config pkg-config
]; ];
@@ -137,9 +112,7 @@ in
aquamarine aquamarine
cairo cairo
git git
glaze
hyprcursor hyprcursor
hyprgraphics
hyprland-protocols hyprland-protocols
hyprlang hyprlang
hyprutils hyprutils
@@ -148,10 +121,9 @@ in
libinput libinput
libuuid libuuid
libxkbcommon libxkbcommon
libgbm mesa
pango pango
pciutils pciutils
re2
tomlplusplus tomlplusplus
udis86-hyprland udis86-hyprland
wayland wayland
@@ -159,7 +131,6 @@ in
wayland-scanner wayland-scanner
xorg.libXcursor xorg.libXcursor
] ]
(optionals customStdenv.hostPlatform.isBSD [epoll-shim])
(optionals customStdenv.hostPlatform.isMusl [libexecinfo]) (optionals customStdenv.hostPlatform.isMusl [libexecinfo])
(optionals enableXWayland [ (optionals enableXWayland [
xorg.libxcb xorg.libxcb
@@ -172,19 +143,16 @@ in
(optional withSystemd systemd) (optional withSystemd systemd)
]; ];
strictDeps = true;
mesonBuildType = mesonBuildType =
if debug if debug
then "debug" then "debugoptimized"
else "release"; else "release";
mesonFlags = flatten [ mesonFlags = flatten [
(mapAttrsToList mesonEnable { (mapAttrsToList mesonEnable {
"xwayland" = enableXWayland; "xwayland" = enableXWayland;
"legacy_renderer" = legacyRenderer;
"systemd" = withSystemd; "systemd" = withSystemd;
"uwsm" = false;
"hyprpm" = false;
}) })
(mapAttrsToList mesonBool { (mapAttrsToList mesonBool {
"b_pch" = false; "b_pch" = false;
@@ -197,7 +165,6 @@ in
wrapProgram $out/bin/Hyprland \ wrapProgram $out/bin/Hyprland \
--suffix PATH : ${makeBinPath [ --suffix PATH : ${makeBinPath [
binutils binutils
hyprland-qtutils
pciutils pciutils
pkgconf pkgconf
]} ]}
@@ -213,4 +180,4 @@ in
platforms = lib.platforms.linux; platforms = lib.platforms.linux;
mainProgram = "Hyprland"; mainProgram = "Hyprland";
}; };
}) }

View File

@@ -1,60 +0,0 @@
{
lib,
stdenv,
stdenvAdapters,
cmake,
pkg-config,
hyprland,
hyprwayland-scanner,
version ? "git",
}: let
inherit (lib.lists) flatten foldl';
inherit (lib.sources) cleanSourceWith cleanSource;
inherit (lib.strings) hasSuffix cmakeBool;
adapters = flatten [
stdenvAdapters.useMoldLinker
stdenvAdapters.keepDebugInfo
];
customStdenv = foldl' (acc: adapter: adapter acc) stdenv adapters;
in
customStdenv.mkDerivation (finalAttrs: {
pname = "hyprtester";
inherit version;
src = cleanSourceWith {
filter = name: _type: let
baseName = baseNameOf (toString name);
in
! (hasSuffix ".nix" baseName);
src = cleanSource ../.;
};
nativeBuildInputs = [
cmake
pkg-config
hyprwayland-scanner
];
buildInputs = hyprland.buildInputs;
preConfigure = ''
cmake -S . -B .
cmake --build . --target generate-protocol-headers -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
cd hyprtester
'';
cmakeBuildType = "Debug";
cmakeFlags = [(cmakeBool "TESTS" true)];
meta = {
homepage = "https://github.com/hyprwm/Hyprland";
description = "Hyprland testing framework";
license = lib.licenses.bsd3;
platforms = hyprland.meta.platforms;
mainProgram = "hyprtester";
};
})

View File

@@ -1,201 +0,0 @@
lib: let
inherit (lib)
attrNames
filterAttrs
foldl
generators
partition
;
inherit (lib.strings)
concatMapStrings
hasPrefix
;
/**
Convert a structured Nix attribute set into Hyprland's configuration format.
This function takes a nested attribute set and converts it into Hyprland-compatible
configuration syntax, supporting top, bottom, and regular command sections.
Commands are flattened using the `flattenAttrs` function, and attributes are formatted as
`key = value` pairs. Lists are expanded as duplicate keys to match Hyprland's expected format.
Configuration:
* `topCommandsPrefixes` - A list of prefixes to define **top** commands (default: `["$"]`).
* `bottomCommandsPrefixes` - A list of prefixes to define **bottom** commands (default: `[]`).
Attention:
- The function ensures top commands appear **first** and bottom commands **last**.
- The generated configuration is a **single string**, suitable for writing to a config file.
- Lists are converted into multiple entries, ensuring compatibility with Hyprland.
# Inputs
Structured function argument:
: topCommandsPrefixes (optional, default: `["$"]`)
: A list of prefixes that define **top** commands. Any key starting with one of these
prefixes will be placed at the beginning of the configuration.
: bottomCommandsPrefixes (optional, default: `[]`)
: A list of prefixes that define **bottom** commands. Any key starting with one of these
prefixes will be placed at the end of the configuration.
Value:
: The attribute set to be converted to Hyprland configuration format.
# Type
```
toHyprlang :: AttrSet -> AttrSet -> String
```
# Examples
:::{.example}
```nix
let
config = {
"$mod" = "SUPER";
monitor = {
"HDMI-A-1" = "1920x1080@60,0x0,1";
};
exec = [
"waybar"
"dunst"
];
};
in lib.toHyprlang {} config
```
**Output:**
```nix
"$mod = SUPER"
"monitor:HDMI-A-1 = 1920x1080@60,0x0,1"
"exec = waybar"
"exec = dunst"
```
:::
*/
toHyprlang = {
topCommandsPrefixes ? ["$" "bezier"],
bottomCommandsPrefixes ? [],
}: attrs: let
toHyprlang' = attrs: let
# Specially configured `toKeyValue` generator with support for duplicate keys
# and a legible key-value separator.
mkCommands = generators.toKeyValue {
mkKeyValue = generators.mkKeyValueDefault {} " = ";
listsAsDuplicateKeys = true;
indent = ""; # No indent, since we don't have nesting
};
# Flatten the attrset, combining keys in a "path" like `"a:b:c" = "x"`.
# Uses `flattenAttrs` with a colon separator.
commands = flattenAttrs (p: k: "${p}:${k}") attrs;
# General filtering function to check if a key starts with any prefix in a given list.
filterCommands = list: n:
foldl (acc: prefix: acc || hasPrefix prefix n) false list;
# Partition keys into top commands and the rest
result = partition (filterCommands topCommandsPrefixes) (attrNames commands);
topCommands = filterAttrs (n: _: builtins.elem n result.right) commands;
remainingCommands = removeAttrs commands result.right;
# Partition remaining commands into bottom commands and regular commands
result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong;
bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands;
regularCommands = removeAttrs remainingCommands result2.right;
in
# Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands.
concatMapStrings mkCommands [
topCommands
regularCommands
bottomCommands
];
in
toHyprlang' attrs;
/**
Flatten a nested attribute set into a flat attribute set, using a custom key separator function.
This function recursively traverses a nested attribute set and produces a flat attribute set
where keys are joined using a user-defined function (`pred`). It allows transforming deeply
nested structures into a single-level attribute set while preserving key-value relationships.
Configuration:
* `pred` - A function `(string -> string -> string)` defining how keys should be concatenated.
# Inputs
Structured function argument:
: pred (required)
: A function that determines how parent and child keys should be combined into a single key.
It takes a `prefix` (parent key) and `key` (current key) and returns the joined key.
Value:
: The nested attribute set to be flattened.
# Type
```
flattenAttrs :: (String -> String -> String) -> AttrSet -> AttrSet
```
# Examples
:::{.example}
```nix
let
nested = {
a = "3";
b = { c = "4"; d = "5"; };
};
separator = (prefix: key: "${prefix}.${key}"); # Use dot notation
in lib.flattenAttrs separator nested
```
**Output:**
```nix
{
"a" = "3";
"b.c" = "4";
"b.d" = "5";
}
```
:::
*/
flattenAttrs = pred: attrs: let
flattenAttrs' = prefix: attrs:
builtins.foldl' (
acc: key: let
value = attrs.${key};
newKey =
if prefix == ""
then key
else pred prefix key;
in
acc
// (
if builtins.isAttrs value
then flattenAttrs' newKey value
else {"${newKey}" = value;}
)
) {} (builtins.attrNames attrs);
in
flattenAttrs' "" attrs;
in
{
inherit flattenAttrs toHyprlang;
}

View File

@@ -5,149 +5,17 @@ inputs: {
... ...
}: let }: let
inherit (pkgs.stdenv.hostPlatform) system; inherit (pkgs.stdenv.hostPlatform) system;
selflib = import ./lib.nix lib;
cfg = config.programs.hyprland; cfg = config.programs.hyprland;
package = inputs.self.packages.${system}.hyprland;
portalPackage = inputs.self.packages.${system}.xdg-desktop-portal-hyprland.override {
hyprland = cfg.finalPackage;
};
in { in {
options = { config = {
programs.hyprland = { programs.hyprland = {
plugins = lib.mkOption { package = lib.mkDefault package;
type = with lib.types; listOf (either package path); portalPackage = lib.mkDefault portalPackage;
default = [];
description = ''
List of Hyprland plugins to use. Can either be packages or
absolute plugin paths.
'';
};
settings = lib.mkOption {
type = with lib.types; let
valueType =
nullOr (oneOf [
bool
int
float
str
path
(attrsOf valueType)
(listOf valueType)
])
// {
description = "Hyprland configuration value";
};
in
valueType;
default = {};
description = ''
Hyprland configuration written in Nix. Entries with the same key
should be written as lists. Variables' and colors' names should be
quoted. See <https://wiki.hypr.land> for more examples.
Special categories (e.g `devices`) should be written as
`"devices[device-name]"`.
::: {.note}
Use the [](#programs.hyprland.plugins) option to
declare plugins.
:::
'';
example = lib.literalExpression ''
{
decoration = {
shadow_offset = "0 5";
"col.shadow" = "rgba(00000099)";
};
"$mod" = "SUPER";
bindm = [
# mouse movements
"$mod, mouse:272, movewindow"
"$mod, mouse:273, resizewindow"
"$mod ALT, mouse:272, resizewindow"
];
}
'';
};
extraConfig = lib.mkOption {
type = lib.types.lines;
default = "";
example = ''
# window resize
bind = $mod, S, submap, resize
submap = resize
binde = , right, resizeactive, 10 0
binde = , left, resizeactive, -10 0
binde = , up, resizeactive, 0 -10
binde = , down, resizeactive, 0 10
bind = , escape, submap, reset
submap = reset
'';
description = ''
Extra configuration lines to add to `/etc/xdg/hypr/hyprland.conf`.
'';
};
topPrefixes = lib.mkOption {
type = with lib.types; listOf str;
default = ["$" "bezier"];
example = ["$" "bezier" "source"];
description = ''
List of prefix of attributes to put at the top of the config.
'';
};
bottomPrefixes = lib.mkOption {
type = with lib.types; listOf str;
default = [];
example = ["source"];
description = ''
List of prefix of attributes to put at the bottom of the config.
'';
};
}; };
}; };
config = lib.mkMerge [
{
programs.hyprland = {
package = lib.mkDefault inputs.self.packages.${system}.hyprland;
portalPackage = lib.mkDefault inputs.self.packages.${system}.xdg-desktop-portal-hyprland;
};
}
(lib.mkIf cfg.enable {
environment.etc."xdg/hypr/hyprland.conf" = let
shouldGenerate = cfg.extraConfig != "" || cfg.settings != {} || cfg.plugins != [];
pluginsToHyprlang = plugins:
selflib.toHyprlang {
topCommandsPrefixes = cfg.topPrefixes;
bottomCommandsPrefixes = cfg.bottomPrefixes;
}
{
"exec-once" = let
mkEntry = entry:
if lib.types.package.check entry
then "${entry}/lib/lib${entry.pname}.so"
else entry;
hyprctl = lib.getExe' config.programs.hyprland.package "hyprctl";
in
map (p: "${hyprctl} plugin load ${mkEntry p}") cfg.plugins;
};
in
lib.mkIf shouldGenerate {
text =
lib.optionalString (cfg.plugins != [])
(pluginsToHyprlang cfg.plugins)
+ lib.optionalString (cfg.settings != {})
(selflib.toHyprlang {
topCommandsPrefixes = cfg.topPrefixes;
bottomCommandsPrefixes = cfg.bottomPrefixes;
}
cfg.settings)
+ lib.optionalString (cfg.extraConfig != "") cfg.extraConfig;
};
})
];
} }

View File

@@ -8,7 +8,7 @@
(builtins.substring 4 2 longDate) (builtins.substring 4 2 longDate)
(builtins.substring 6 2 longDate) (builtins.substring 6 2 longDate)
]); ]);
ver = lib.removeSuffix "\n" (builtins.readFile ../VERSION); version = lib.removeSuffix "\n" (builtins.readFile ../VERSION);
in { in {
# Contains what a user is most likely to care about: # Contains what a user is most likely to care about:
# Hyprland itself, XDPH and the Share Picker. # Hyprland itself, XDPH and the Share Picker.
@@ -22,9 +22,7 @@ in {
# Dependencies # Dependencies
inputs.aquamarine.overlays.default inputs.aquamarine.overlays.default
inputs.hyprcursor.overlays.default inputs.hyprcursor.overlays.default
inputs.hyprgraphics.overlays.default
inputs.hyprland-protocols.overlays.default inputs.hyprland-protocols.overlays.default
inputs.hyprland-qtutils.overlays.default
inputs.hyprlang.overlays.default inputs.hyprlang.overlays.default
inputs.hyprutils.overlays.default inputs.hyprutils.overlays.default
inputs.hyprwayland-scanner.overlays.default inputs.hyprwayland-scanner.overlays.default
@@ -33,28 +31,25 @@ in {
# Hyprland packages themselves # Hyprland packages themselves
(final: _prev: let (final: _prev: let
date = mkDate (self.lastModifiedDate or "19700101"); date = mkDate (self.lastModifiedDate or "19700101");
version = "${ver}+date=${date}_${self.shortRev or "dirty"}";
in { in {
hyprland = final.callPackage ./default.nix { hyprland = final.callPackage ./default.nix {
stdenv = final.gcc15Stdenv; stdenv = final.gcc14Stdenv;
version = "${version}+date=${date}_${self.shortRev or "dirty"}";
commit = self.rev or ""; commit = self.rev or "";
revCount = self.sourceInfo.revCount or ""; revCount = self.sourceInfo.revCount or "";
inherit date version; inherit date;
}; };
hyprland-unwrapped = final.hyprland.override {wrapRuntimeDeps = false;}; hyprland-unwrapped = final.hyprland.override {wrapRuntimeDeps = false;};
hyprtester = final.callPackage ./hyprtester.nix { # Build major libs with debug to get as much info as possible in a stacktrace
inherit version; hyprland-debug = final.hyprland.override {
aquamarine = final.aquamarine.override {debug = true;};
hyprutils = final.hyprutils.override {debug = true;};
debug = true;
}; };
hyprland-legacy-renderer = final.hyprland.override {legacyRenderer = true;};
# deprecated packages # deprecated packages
hyprland-legacy-renderer =
builtins.trace ''
hyprland-legacy-renderer was removed. Please use the hyprland package.
Legacy renderer is no longer supported.
''
final.hyprland;
hyprland-nvidia = hyprland-nvidia =
builtins.trace '' builtins.trace ''
hyprland-nvidia was removed. Please use the hyprland package. hyprland-nvidia was removed. Please use the hyprland package.
@@ -65,24 +60,12 @@ in {
hyprland-hidpi = hyprland-hidpi =
builtins.trace '' builtins.trace ''
hyprland-hidpi was removed. Please use the hyprland package. hyprland-hidpi was removed. Please use the hyprland package.
For more information, refer to https://wiki.hypr.land/Configuring/XWayland. For more information, refer to https://wiki.hyprland.org/Configuring/XWayland.
'' ''
final.hyprland; final.hyprland;
}) })
]; ];
# Debug
hyprland-debug = lib.composeManyExtensions [
# Dependencies
self.overlays.hyprland-packages
(final: prev: {
aquamarine = prev.aquamarine.override {debug = true;};
hyprutils = prev.hyprutils.override {debug = true;};
hyprland-debug = prev.hyprland.override {debug = true;};
})
];
# Packages for extra software recommended for usage with Hyprland, # Packages for extra software recommended for usage with Hyprland,
# including forked or patched packages for compatibility. # including forked or patched packages for compatibility.
hyprland-extras = lib.composeManyExtensions [ hyprland-extras = lib.composeManyExtensions [

View File

@@ -1,87 +0,0 @@
inputs: pkgs: let
flake = inputs.self.packages.${pkgs.stdenv.hostPlatform.system};
hyprland = flake.hyprland;
in {
tests = pkgs.testers.runNixOSTest {
name = "hyprland-tests";
nodes.machine = {pkgs, ...}: {
environment.systemPackages = with pkgs; [
flake.hyprtester
# Programs needed for tests
jq
kitty
xorg.xeyes
];
# Enabled by default for some reason
services.speechd.enable = false;
environment.variables = {
"AQ_TRACE" = "1";
"HYPRLAND_TRACE" = "1";
"XDG_RUNTIME_DIR" = "/tmp";
"XDG_CACHE_HOME" = "/tmp";
};
programs.hyprland = {
enable = true;
package = hyprland;
# We don't need portals in this test, so we don't set portalPackage
};
# Test configuration
environment.etc."test.conf".source = "${flake.hyprtester}/share/hypr/test.conf";
# Disable portals
xdg.portal.enable = pkgs.lib.mkForce false;
# Autologin root into tty
services.getty.autologinUser = "alice";
system.stateVersion = "24.11";
users.users.alice = {
isNormalUser = true;
};
virtualisation = {
cores = 4;
# Might crash with less
memorySize = 8192;
resolution = {
x = 1920;
y = 1080;
};
# Doesn't seem to do much, thought it would fix XWayland crashing
qemu.options = ["-vga none -device virtio-gpu-pci"];
};
};
testScript = ''
# Wait for tty to be up
machine.wait_for_unit("multi-user.target")
# Run hyprtester testing framework/suite
print("Running hyprtester")
exit_status, _out = machine.execute("su - alice -c 'hyprtester -b ${hyprland}/bin/Hyprland -c /etc/test.conf -p ${flake.hyprtester}/lib/hyprtestplugin.so 2>&1 | tee /tmp/testerlog; exit ''${PIPESTATUS[0]}'")
print(f"Hyprtester exited with {exit_status}")
# Copy logs to host
machine.execute('cp "$(find /tmp/hypr -name *.log | head -1)" /tmp/hyprlog')
machine.execute(f'echo {exit_status} > /tmp/exit_status')
machine.copy_from_vm("/tmp/testerlog")
machine.copy_from_vm("/tmp/hyprlog")
machine.copy_from_vm("/tmp/exit_status")
# Print logs for visibility in CI
_, out = machine.execute("cat /tmp/testerlog")
print(f"Hyprtester log:\n{out}")
# Finally - shutdown
machine.shutdown()
'';
};
}

View File

@@ -1,25 +1,16 @@
#!/usr/bin/env -S nix shell nixpkgs#jq -c bash #!/usr/bin/env -S nix shell nixpkgs#jq -c bash
# Update inputs when the Mesa or QT version is outdated. We don't want # Update inputs when the Mesa version is outdated. We don't want
# incompatibilities between the user's system and Hyprland. # incompatibilities between the user's system and Hyprland.
# get the current Nixpkgs revision # get the current Nixpkgs revision
REV=$(jq <flake.lock '.nodes.nixpkgs.locked.rev' -r) REV=$(jq <flake.lock '.nodes.nixpkgs.locked.rev' -r)
# check versions for current and remote nixpkgs' mesa
CRT_VER=$(nix eval --raw github:nixos/nixpkgs/"$REV"#mesa.version)
NEW_VER=$(nix eval --raw github:nixos/nixpkgs/nixos-unstable#mesa.version)
get_ver() { if [ "$CRT_VER" != "$NEW_VER" ]; then
nix eval --raw "github:nixos/nixpkgs/$1#$2" echo "Updating Mesa $CRT_VER -> $NEW_VER and flake inputs"
}
# check versions for current and remote nixpkgs'
MESA_OLD=$(get_ver "$REV" mesa.version)
MESA_NEW=$(get_ver nixos-unstable mesa.version)
QT_OLD=$(get_ver "$REV" kdePackages.qtbase.version)
QT_NEW=$(get_ver nixos-unstable kdePackages.qtbase.version)
if [ "$MESA_OLD" != "$MESA_NEW" ] || [ "$QT_OLD" != "$QT_NEW" ]; then
echo "Updating flake inputs..."
echo "Mesa: $MESA_OLD -> $MESA_NEW"
echo "Qt: $QT_OLD -> $QT_NEW"
# update inputs to latest versions # update inputs to latest versions
nix flake update nix flake update

View File

@@ -1,366 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="frog_color_management_v1">
<copyright>
Copyright © 2023 Joshua Ashton for Valve Software
Copyright © 2023 Xaver Hugl
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<description summary="experimental color management protocol">
The aim of this color management extension is to get HDR games working quickly,
and have an easy way to test implementations in the wild before the upstream
protocol is ready to be merged.
For that purpose it's intentionally limited and cut down and does not serve
all uses cases.
</description>
<interface name="frog_color_management_factory_v1" version="1">
<description summary="color management factory">
The color management factory singleton creates color managed surface objects.
</description>
<request name="destroy" type="destructor"></request>
<request name="get_color_managed_surface">
<description summary="create color management interface for surface">
</description>
<arg name="surface" type="object" interface="wl_surface"
summary="target surface" />
<arg name="callback" type="new_id" interface="frog_color_managed_surface"
summary="new color managed surface object" />
</request>
</interface>
<interface name="frog_color_managed_surface" version="1">
<description summary="color managed surface">
Interface for changing surface color management and HDR state.
An implementation must: support every part of the version
of the frog_color_managed_surface interface it exposes.
Including all known enums associated with a given version.
</description>
<request name="destroy" type="destructor">
<description summary="destroy color managed surface">
Destroying the color managed surface resets all known color
state for the surface back to 'undefined' implementation-specific
values.
</description>
</request>
<enum name="transfer_function">
<description summary="known transfer functions">
Extended information on the transfer functions described
here can be found in the Khronos Data Format specification:
https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html
</description>
<entry name="undefined" value="0"
summary="specifies undefined, implementation-specific handling of the surface's transfer function." />
<entry name="srgb" value="1"
summary="specifies the sRGB non-linear EOTF. An implementation may: display this as Gamma 2.2 for the purposes of being consistent with content rendering across displays, rendering_intent and user expectations." />
<entry name="gamma_22" value="2" summary="specifies gamma 2.2 power curve as the EOTF" />
<entry name="st2084_pq" value="3"
summary="specifies the SMPTE ST2084 Perceptual Quantizer (PQ) EOTF" />
<entry name="scrgb_linear" value="4"
summary="specifies the scRGB (extended sRGB) linear EOTF. Note: Primaries outside the gamut triangle specified can be expressed with negative values for this transfer function." />
</enum>
<request name="set_known_transfer_function">
<description summary="sets a known transfer function for a surface" />
<arg name="transfer_function" type="uint" enum="transfer_function"
summary="transfer function for the surface" />
</request>
<enum name="primaries">
<description summary="known primaries" />
<entry name="undefined" value="0"
summary="specifies undefined, implementation-specific handling" />
<entry name="rec709" value="1" summary="specifies Rec.709/sRGB primaries with D65 white point" />
<entry name="rec2020" value="2"
summary="specifies Rec.2020/HDR10 primaries with D65 white point" />
</enum>
<request name="set_known_container_color_volume">
<description summary="sets the container color volume (primaries) for a surface" />
<arg name="primaries" type="uint" enum="primaries" summary="primaries for the surface" />
</request>
<enum name="render_intent">
<description summary="known render intents">
Extended information on render intents described
here can be found in ICC.1:2022:
https://www.color.org/specification/ICC.1-2022-05.pdf
</description>
<entry name="perceptual" value="0" summary="perceptual" />
</enum>
<request name="set_render_intent">
<description summary="sets the render intent for a surface">
NOTE: On a surface with "perceptual" (default) render intent, handling of the container's
color volume
is implementation-specific, and may differ between different transfer functions it is paired
with:
ie. sRGB + 709 rendering may have it's primaries widened to more of the available display's
gamut
to be be more pleasing for the viewer.
Compared to scRGB Linear + 709 being treated faithfully as 709
(including utilizing negatives out of the 709 gamut triangle)
</description>
<arg name="render_intent" type="uint" enum="render_intent"
summary="render intent for the surface" />
</request>
<request name="set_hdr_metadata">
<description summary="set HDR metadata for a surface">
Forwards HDR metadata from the client to the compositor.
HDR Metadata Infoframe as per CTA 861.G spec.
Usage of this HDR metadata is implementation specific and
outside of the scope of this protocol.
</description>
<arg name="mastering_display_primary_red_x" type="uint">
<description summary="red primary x coordinate">
Mastering Red Color Primary X Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="mastering_display_primary_red_y" type="uint">
<description summary="red primary y coordinate">
Mastering Red Color Primary Y Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="mastering_display_primary_green_x" type="uint">
<description summary="green primary x coordinate">
Mastering Green Color Primary X Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="mastering_display_primary_green_y" type="uint">
<description summary="green primary y coordinate">
Mastering Green Color Primary Y Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="mastering_display_primary_blue_x" type="uint">
<description summary="blue primary x coordinate">
Mastering Blue Color Primary X Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="mastering_display_primary_blue_y" type="uint">
<description summary="blue primary y coordinate">
Mastering Blue Color Primary Y Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="mastering_white_point_x" type="uint">
<description summary="white point x coordinate">
Mastering White Point X Coordinate of the Data.
These are coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="mastering_white_point_y" type="uint">
<description summary="white point y coordinate">
Mastering White Point Y Coordinate of the Data.
These are coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="max_display_mastering_luminance" type="uint">
<description summary="max display mastering luminance">
Max Mastering Display Luminance.
This value is coded as an unsigned 16-bit value in units of 1 cd/m2,
where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2.
</description>
</arg>
<arg name="min_display_mastering_luminance" type="uint">
<description summary="min display mastering luminance">
Min Mastering Display Luminance.
This value is coded as an unsigned 16-bit value in units of
0.0001 cd/m2, where 0x0001 represents 0.0001 cd/m2 and 0xFFFF
represents 6.5535 cd/m2.
</description>
</arg>
<arg name="max_cll" type="uint">
<description summary="max content light level">
Max Content Light Level.
This value is coded as an unsigned 16-bit value in units of 1 cd/m2,
where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2.
</description>
</arg>
<arg name="max_fall" type="uint">
<description summary="max frame average light level">
Max Frame Average Light Level.
This value is coded as an unsigned 16-bit value in units of 1 cd/m2,
where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2.
</description>
</arg>
</request>
<event name="preferred_metadata">
<description summary="preferred metadata for a surface">
Current preferred metadata for a surface.
The application should use this information to tone-map its buffers
to this target before committing.
This metadata does not necessarily correspond to any physical output, but
rather what the compositor thinks would be best for a given surface.
</description>
<arg name="transfer_function" type="uint" enum="transfer_function">
<description summary="output's current transfer function">
Specifies a known transfer function that corresponds to the
output the surface is targeting.
</description>
</arg>
<arg name="output_display_primary_red_x" type="uint">
<description summary="red primary x coordinate">
Output Red Color Primary X Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="output_display_primary_red_y" type="uint">
<description summary="red primary y coordinate">
Output Red Color Primary Y Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="output_display_primary_green_x" type="uint">
<description summary="green primary x coordinate">
Output Green Color Primary X Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="output_display_primary_green_y" type="uint">
<description summary="green primary y coordinate">
Output Green Color Primary Y Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="output_display_primary_blue_x" type="uint">
<description summary="blue primary x coordinate">
Output Blue Color Primary X Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="output_display_primary_blue_y" type="uint">
<description summary="blue primary y coordinate">
Output Blue Color Primary Y Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="output_white_point_x" type="uint">
<description summary="white point x coordinate">
Output White Point X Coordinate of the Data.
These are coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="output_white_point_y" type="uint">
<description summary="white point y coordinate">
Output White Point Y Coordinate of the Data.
These are coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="max_luminance" type="uint">
<description summary="maximum luminance">
Max Output Luminance
The max luminance in nits that the output is capable of rendering in small areas.
Content should: not exceed this value to avoid clipping.
This value is coded as an unsigned 16-bit value in units of 1 cd/m2,
where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2.
</description>
</arg>
<arg name="min_luminance" type="uint">
<description summary="minimum luminance">
Min Output Luminance
The min luminance that the output is capable of rendering.
Content should: not exceed this value to avoid clipping.
This value is coded as an unsigned 16-bit value in units of
0.0001 cd/m2, where 0x0001 represents 0.0001 cd/m2 and 0xFFFF
represents 6.5535 cd/m2.
</description>
</arg>
<arg name="max_full_frame_luminance" type="uint">
<description summary="maximum full frame luminance">
Max Full Frame Luminance
The max luminance in nits that the output is capable of rendering for the
full frame sustained.
This value is coded as an unsigned 16-bit value in units of 1 cd/m2,
where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2.
</description>
</arg>
</event>
</interface>
</protocol>

View File

@@ -1,13 +1,13 @@
wayland_protos = dependency( wayland_protos = dependency(
'wayland-protocols', 'wayland-protocols',
version: '>=1.43', version: '>=1.32',
fallback: 'wayland-protocols', fallback: 'wayland-protocols',
default_options: ['tests=false'], default_options: ['tests=false'],
) )
hyprland_protos = dependency( hyprland_protos = dependency(
'hyprland-protocols', 'hyprland-protocols',
version: '>=0.6.4', version: '>=0.4',
fallback: 'hyprland-protocols', fallback: 'hyprland-protocols',
) )
@@ -33,15 +33,10 @@ protocols = [
'wayland-drm.xml', 'wayland-drm.xml',
'wlr-data-control-unstable-v1.xml', 'wlr-data-control-unstable-v1.xml',
'wlr-screencopy-unstable-v1.xml', 'wlr-screencopy-unstable-v1.xml',
'xx-color-management-v4.xml',
'frog-color-management-v1.xml',
hyprland_protocol_dir / 'protocols/hyprland-global-shortcuts-v1.xml', hyprland_protocol_dir / 'protocols/hyprland-global-shortcuts-v1.xml',
hyprland_protocol_dir / 'protocols/hyprland-toplevel-export-v1.xml', hyprland_protocol_dir / 'protocols/hyprland-toplevel-export-v1.xml',
hyprland_protocol_dir / 'protocols/hyprland-toplevel-mapping-v1.xml',
hyprland_protocol_dir / 'protocols/hyprland-focus-grab-v1.xml', hyprland_protocol_dir / 'protocols/hyprland-focus-grab-v1.xml',
hyprland_protocol_dir / 'protocols/hyprland-ctm-control-v1.xml', hyprland_protocol_dir / 'protocols/hyprland-ctm-control-v1.xml',
hyprland_protocol_dir / 'protocols/hyprland-surface-v1.xml',
hyprland_protocol_dir / 'protocols/hyprland-lock-notify-v1.xml',
wayland_protocol_dir / 'staging/tearing-control/tearing-control-v1.xml', wayland_protocol_dir / 'staging/tearing-control/tearing-control-v1.xml',
wayland_protocol_dir / 'staging/fractional-scale/fractional-scale-v1.xml', wayland_protocol_dir / 'staging/fractional-scale/fractional-scale-v1.xml',
wayland_protocol_dir / 'unstable/xdg-output/xdg-output-unstable-v1.xml', wayland_protocol_dir / 'unstable/xdg-output/xdg-output-unstable-v1.xml',
@@ -71,12 +66,6 @@ protocols = [
wayland_protocol_dir / 'staging/xdg-dialog/xdg-dialog-v1.xml', wayland_protocol_dir / 'staging/xdg-dialog/xdg-dialog-v1.xml',
wayland_protocol_dir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml', wayland_protocol_dir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml',
wayland_protocol_dir / 'staging/security-context/security-context-v1.xml', wayland_protocol_dir / 'staging/security-context/security-context-v1.xml',
wayland_protocol_dir / 'staging/content-type/content-type-v1.xml',
wayland_protocol_dir / 'staging/color-management/color-management-v1.xml',
wayland_protocol_dir / 'staging/xdg-toplevel-tag/xdg-toplevel-tag-v1.xml',
wayland_protocol_dir / 'staging/xdg-system-bell/xdg-system-bell-v1.xml',
wayland_protocol_dir / 'staging/ext-workspace/ext-workspace-v1.xml',
wayland_protocol_dir / 'staging/ext-data-control/ext-data-control-v1.xml',
] ]
wl_protocols = [] wl_protocols = []
@@ -92,7 +81,7 @@ foreach protocol : protocols
endforeach endforeach
# wayland.xml generation # wayland.xml generation
wayland_scanner = dependency('wayland-scanner', native: true) wayland_scanner = dependency('wayland-scanner')
wayland_scanner_datadir = wayland_scanner.get_variable('pkgdatadir') wayland_scanner_datadir = wayland_scanner.get_variable('pkgdatadir')
wayland_xml = wayland_scanner_datadir / 'wayland.xml' wayland_xml = wayland_scanner_datadir / 'wayland.xml'

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +0,0 @@
#!/bin/sh
SHADERS_SRC="./src/render/shaders/glsl"
echo "-- Generating shader includes"
if [ ! -d ./src/render/shaders ]; then
mkdir ./src/render/shaders
fi
echo '#pragma once' > ./src/render/shaders/Shaders.hpp
echo '#include <map>' >> ./src/render/shaders/Shaders.hpp
echo 'static const std::map<std::string, std::string> SHADERS = {' >> ./src/render/shaders/Shaders.hpp
for filename in `ls ${SHADERS_SRC}`; do
echo "-- ${filename}"
{ echo 'R"#('; cat ${SHADERS_SRC}/${filename}; echo ')#"'; } > ./src/render/shaders/${filename}.inc
echo "{\"${filename}\"," >> ./src/render/shaders/Shaders.hpp
echo "#include \"./${filename}.inc\"" >> ./src/render/shaders/Shaders.hpp
echo "}," >> ./src/render/shaders/Shaders.hpp
done
echo '};' >> ./src/render/shaders/Shaders.hpp

View File

@@ -1,13 +1,4 @@
#!/bin/sh #!/bin/sh
# if the git directory doesn't exist, don't gather data to avoid overwriting, unless
# the version file is missing altogether (otherwise compiling will fail)
if [ ! -d ./.git ]; then
if [ -f ./src/version.h ]; then
exit 0
fi
fi
cp -fr ./src/version.h.in ./src/version.h cp -fr ./src/version.h.in ./src/version.h
HASH=${HASH-$(git rev-parse HEAD)} HASH=${HASH-$(git rev-parse HEAD)}

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +1,41 @@
#pragma once #pragma once
#include <memory>
#include <deque>
#include <list>
#include <sys/resource.h> #include <sys/resource.h>
#include <ranges> #include "defines.hpp"
#include "debug/Log.hpp"
#include "events/Events.hpp"
#include "config/ConfigManager.hpp"
#include "managers/ThreadManager.hpp"
#include "managers/XWaylandManager.hpp" #include "managers/XWaylandManager.hpp"
#include "managers/input/InputManager.hpp"
#include "managers/LayoutManager.hpp"
#include "managers/KeybindManager.hpp" #include "managers/KeybindManager.hpp"
#include "managers/AnimationManager.hpp"
#include "managers/EventManager.hpp"
#include "managers/ProtocolManager.hpp"
#include "managers/SessionLockManager.hpp" #include "managers/SessionLockManager.hpp"
#include "managers/HookSystemManager.hpp"
#include "debug/HyprDebugOverlay.hpp"
#include "debug/HyprNotificationOverlay.hpp"
#include "helpers/Monitor.hpp"
#include "desktop/Workspace.hpp"
#include "desktop/Window.hpp" #include "desktop/Window.hpp"
#include "protocols/types/ColorManagement.hpp" #include "render/Renderer.hpp"
#include "render/OpenGL.hpp"
#include "hyprerror/HyprError.hpp"
#include "plugins/PluginSystem.hpp"
#include "helpers/Watchdog.hpp"
#include <aquamarine/backend/Backend.hpp> #include <aquamarine/backend/Backend.hpp>
#include <aquamarine/output/Output.hpp> #include <aquamarine/output/Output.hpp>
class CWLSurfaceResource; class CWLSurfaceResource;
struct SWorkspaceRule;
enum eManagersInitStage : uint8_t { enum eManagersInitStage {
STAGE_PRIORITY = 0, STAGE_PRIORITY = 0,
STAGE_BASICINIT, STAGE_BASICINIT,
STAGE_LATE STAGE_LATE
@@ -24,77 +43,71 @@ enum eManagersInitStage : uint8_t {
class CCompositor { class CCompositor {
public: public:
CCompositor(bool onlyConfig = false); CCompositor();
~CCompositor(); ~CCompositor();
wl_display* m_wlDisplay = nullptr; wl_display* m_sWLDisplay;
wl_event_loop* m_wlEventLoop = nullptr; wl_event_loop* m_sWLEventLoop;
int m_drmFD = -1; int m_iDRMFD = -1;
bool m_initialized = false; bool m_bInitialized = false;
SP<Aquamarine::CBackend> m_aqBackend; SP<Aquamarine::CBackend> m_pAqBackend;
std::string m_hyprTempDataRoot = ""; std::string m_szHyprTempDataRoot = "";
std::string m_wlDisplaySocket = ""; std::string m_szWLDisplaySocket = "";
std::string m_instanceSignature = ""; std::string m_szInstanceSignature = "";
std::string m_instancePath = ""; std::string m_szInstancePath = "";
std::string m_currentSplash = "error"; std::string m_szCurrentSplash = "error";
std::vector<PHLMONITOR> m_monitors; std::vector<PHLMONITOR> m_vMonitors;
std::vector<PHLMONITOR> m_realMonitors; // for all monitors, even those turned off std::vector<PHLMONITOR> m_vRealMonitors; // for all monitors, even those turned off
std::vector<PHLWINDOW> m_windows; std::vector<PHLWINDOW> m_vWindows;
std::vector<PHLLS> m_layers; std::vector<PHLLS> m_vLayers;
std::vector<PHLWINDOWREF> m_windowsFadingOut; std::vector<PHLWORKSPACE> m_vWorkspaces;
std::vector<PHLLSREF> m_surfacesFadingOut; std::vector<PHLWINDOWREF> m_vWindowsFadingOut;
std::vector<PHLLSREF> m_vSurfacesFadingOut;
std::unordered_map<std::string, MONITORID> m_monitorIDMap; std::unordered_map<std::string, MONITORID> m_mMonitorIDMap;
std::unordered_map<std::string, WORKSPACEID> m_seenMonitorWorkspaceMap; // map of seen monitor names to workspace IDs
void initServer(std::string socketName, int socketFd); void initServer(std::string socketName, int socketFd);
void startCompositor(); void startCompositor();
void stopCompositor(); void stopCompositor();
void cleanup(); void cleanup();
void bumpNofile(); void createLockFile();
void restoreNofile(); void removeLockFile();
void bumpNofile();
void restoreNofile();
WP<CWLSurfaceResource> m_lastFocus; WP<CWLSurfaceResource> m_pLastFocus;
PHLWINDOWREF m_lastWindow; PHLWINDOWREF m_pLastWindow;
PHLMONITORREF m_lastMonitor; PHLMONITORREF m_pLastMonitor;
std::vector<PHLWINDOWREF> m_windowFocusHistory; // first element is the most recently focused std::vector<PHLWINDOWREF> m_vWindowFocusHistory; // first element is the most recently focused.
bool m_readyToProcess = false; bool m_bReadyToProcess = false;
bool m_sessionActive = true; bool m_bSessionActive = true;
bool m_dpmsStateOn = true; bool m_bDPMSStateON = true;
bool m_unsafeState = false; // unsafe state is when there is no monitors bool m_bUnsafeState = false; // unsafe state is when there is no monitors.
PHLMONITORREF m_unsafeOutput; // fallback output for the unsafe state bool m_bNextIsUnsafe = false;
bool m_isShuttingDown = false; PHLMONITORREF m_pUnsafeOutput; // fallback output for the unsafe state
bool m_finalRequests = false; bool m_bIsShuttingDown = false;
bool m_desktopEnvSet = false; bool m_bFinalRequests = false;
bool m_wantsXwayland = true; bool m_bDesktopEnvSet = false;
bool m_onlyConfigVerification = false; bool m_bEnableXwayland = true;
// ------------------------------------------------- // // ------------------------------------------------- //
auto getWorkspaces() {
return std::views::filter(m_workspaces, [](const auto& e) { return e; });
}
std::vector<PHLWORKSPACE> getWorkspacesCopy();
void registerWorkspace(PHLWORKSPACE w);
//
PHLMONITOR getMonitorFromID(const MONITORID&); PHLMONITOR getMonitorFromID(const MONITORID&);
PHLMONITOR getMonitorFromName(const std::string&); PHLMONITOR getMonitorFromName(const std::string&);
PHLMONITOR getMonitorFromDesc(const std::string&); PHLMONITOR getMonitorFromDesc(const std::string&);
PHLMONITOR getMonitorFromCursor(); PHLMONITOR getMonitorFromCursor();
PHLMONITOR getMonitorFromVector(const Vector2D&); PHLMONITOR getMonitorFromVector(const Vector2D&);
void removeWindowFromVectorSafe(PHLWINDOW); void removeWindowFromVectorSafe(PHLWINDOW);
void focusWindow(PHLWINDOW, SP<CWLSurfaceResource> pSurface = nullptr, bool preserveFocusHistory = false); void focusWindow(PHLWINDOW, SP<CWLSurfaceResource> pSurface = nullptr);
void focusSurface(SP<CWLSurfaceResource>, PHLWINDOW pWindowOwner = nullptr); void focusSurface(SP<CWLSurfaceResource>, PHLWINDOW pWindowOwner = nullptr);
bool monitorExists(PHLMONITOR); bool monitorExists(PHLMONITOR);
PHLWINDOW vectorToWindowUnified(const Vector2D&, uint8_t properties, PHLWINDOW pIgnoreWindow = nullptr); PHLWINDOW vectorToWindowUnified(const Vector2D&, uint8_t properties, PHLWINDOW pIgnoreWindow = nullptr);
SP<CWLSurfaceResource> vectorToLayerSurface(const Vector2D&, std::vector<PHLLSREF>*, Vector2D*, PHLLS*, bool aboveLockscreen = false); SP<CWLSurfaceResource> vectorToLayerSurface(const Vector2D&, std::vector<PHLLSREF>*, Vector2D*, PHLLS*);
SP<CWLSurfaceResource> vectorToLayerPopupSurface(const Vector2D&, PHLMONITOR monitor, Vector2D*, PHLLS*); SP<CWLSurfaceResource> vectorToLayerPopupSurface(const Vector2D&, PHLMONITOR monitor, Vector2D*, PHLLS*);
SP<CWLSurfaceResource> vectorWindowToSurface(const Vector2D&, PHLWINDOW, Vector2D& sl); SP<CWLSurfaceResource> vectorWindowToSurface(const Vector2D&, PHLWINDOW, Vector2D& sl);
Vector2D vectorToSurfaceLocal(const Vector2D&, PHLWINDOW, SP<CWLSurfaceResource>); Vector2D vectorToSurfaceLocal(const Vector2D&, PHLWINDOW, SP<CWLSurfaceResource>);
@@ -102,23 +115,34 @@ class CCompositor {
PHLMONITOR getRealMonitorFromOutput(SP<Aquamarine::IOutput>); PHLMONITOR getRealMonitorFromOutput(SP<Aquamarine::IOutput>);
PHLWINDOW getWindowFromSurface(SP<CWLSurfaceResource>); PHLWINDOW getWindowFromSurface(SP<CWLSurfaceResource>);
PHLWINDOW getWindowFromHandle(uint32_t); PHLWINDOW getWindowFromHandle(uint32_t);
bool isWorkspaceVisible(PHLWORKSPACE);
bool isWorkspaceVisibleNotCovered(PHLWORKSPACE);
PHLWORKSPACE getWorkspaceByID(const WORKSPACEID&); PHLWORKSPACE getWorkspaceByID(const WORKSPACEID&);
PHLWORKSPACE getWorkspaceByName(const std::string&); PHLWORKSPACE getWorkspaceByName(const std::string&);
PHLWORKSPACE getWorkspaceByString(const std::string&); PHLWORKSPACE getWorkspaceByString(const std::string&);
void sanityCheckWorkspaces();
void updateWorkspaceWindowDecos(const WORKSPACEID&);
void updateWorkspaceWindowData(const WORKSPACEID&);
int getWindowsOnWorkspace(const WORKSPACEID& id, std::optional<bool> onlyTiled = {}, std::optional<bool> onlyVisible = {});
int getGroupsOnWorkspace(const WORKSPACEID& id, std::optional<bool> onlyTiled = {}, std::optional<bool> onlyVisible = {});
PHLWINDOW getUrgentWindow(); PHLWINDOW getUrgentWindow();
bool hasUrgentWindowOnWorkspace(const WORKSPACEID&);
PHLWINDOW getFirstWindowOnWorkspace(const WORKSPACEID&);
PHLWINDOW getTopLeftWindowOnWorkspace(const WORKSPACEID&);
PHLWINDOW getFullscreenWindowOnWorkspace(const WORKSPACEID&);
bool isWindowActive(PHLWINDOW); bool isWindowActive(PHLWINDOW);
void changeWindowZOrder(PHLWINDOW, bool); void changeWindowZOrder(PHLWINDOW, bool);
void cleanupFadingOut(const MONITORID& monid); void cleanupFadingOut(const MONITORID& monid);
PHLWINDOW getWindowInDirection(PHLWINDOW, char); PHLWINDOW getWindowInDirection(PHLWINDOW, char);
PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, char dir, PHLWINDOW ignoreWindow = nullptr, bool useVectorAngles = false); PHLWINDOW getNextWindowOnWorkspace(PHLWINDOW, bool focusableOnly = false, std::optional<bool> floating = {});
PHLWINDOW getWindowCycle(PHLWINDOW cur, bool focusableOnly = false, std::optional<bool> floating = std::nullopt, bool visible = false, bool prev = false); PHLWINDOW getPrevWindowOnWorkspace(PHLWINDOW, bool focusableOnly = false, std::optional<bool> floating = {});
PHLWINDOW getWindowCycleHist(PHLWINDOWREF cur, bool focusableOnly = false, std::optional<bool> floating = std::nullopt, bool visible = false, bool next = false);
WORKSPACEID getNextAvailableNamedWorkspace(); WORKSPACEID getNextAvailableNamedWorkspace();
bool isPointOnAnyMonitor(const Vector2D&); bool isPointOnAnyMonitor(const Vector2D&);
bool isPointOnReservedArea(const Vector2D& point, const PHLMONITOR monitor = nullptr); bool isPointOnReservedArea(const Vector2D& point, const PHLMONITOR monitor = nullptr);
PHLMONITOR getMonitorInDirection(const char&); PHLMONITOR getMonitorInDirection(const char&);
PHLMONITOR getMonitorInDirection(PHLMONITOR, const char&); PHLMONITOR getMonitorInDirection(PHLMONITOR, const char&);
void updateAllWindowsAnimatedDecorationValues(); void updateAllWindowsAnimatedDecorationValues();
void updateWorkspaceWindows(const WORKSPACEID& id);
void updateWindowAnimatedDecorationValues(PHLWINDOW); void updateWindowAnimatedDecorationValues(PHLWINDOW);
MONITORID getNextAvailableMonitorID(std::string const& name); MONITORID getNextAvailableMonitorID(std::string const& name);
void moveWorkspaceToMonitor(PHLWORKSPACE, PHLMONITOR, bool noWarpCursor = false); void moveWorkspaceToMonitor(PHLWORKSPACE, PHLMONITOR, bool noWarpCursor = false);
@@ -127,7 +151,8 @@ class CCompositor {
bool workspaceIDOutOfBounds(const WORKSPACEID&); bool workspaceIDOutOfBounds(const WORKSPACEID&);
void setWindowFullscreenInternal(const PHLWINDOW PWINDOW, const eFullscreenMode MODE); void setWindowFullscreenInternal(const PHLWINDOW PWINDOW, const eFullscreenMode MODE);
void setWindowFullscreenClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE); void setWindowFullscreenClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE);
void setWindowFullscreenState(const PHLWINDOW PWINDOW, const SFullscreenState state); void setWindowFullscreenState(const PHLWINDOW PWINDOW, const sFullscreenState state);
void changeWindowFullscreenModeInternal(const PHLWINDOW PWINDOW, const eFullscreenMode MODE, const bool ON);
void changeWindowFullscreenModeClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE, const bool ON); void changeWindowFullscreenModeClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE, const bool ON);
void updateFullscreenFadeOnWorkspace(PHLWORKSPACE); void updateFullscreenFadeOnWorkspace(PHLWORKSPACE);
PHLWINDOW getX11Parent(PHLWINDOW); PHLWINDOW getX11Parent(PHLWINDOW);
@@ -140,48 +165,38 @@ class CCompositor {
PHLLS getLayerSurfaceFromSurface(SP<CWLSurfaceResource>); PHLLS getLayerSurfaceFromSurface(SP<CWLSurfaceResource>);
void closeWindow(PHLWINDOW); void closeWindow(PHLWINDOW);
Vector2D parseWindowVectorArgsRelative(const std::string&, const Vector2D&); Vector2D parseWindowVectorArgsRelative(const std::string&, const Vector2D&);
[[nodiscard]] PHLWORKSPACE createNewWorkspace(const WORKSPACEID&, const MONITORID&, const std::string& name = "", void forceReportSizesToWindowsOnWorkspace(const WORKSPACEID&);
bool isEmpty = true); // will be deleted next frame if left empty and unfocused! PHLWORKSPACE createNewWorkspace(const WORKSPACEID&, const MONITORID&, const std::string& name = "",
void setActiveMonitor(PHLMONITOR); bool isEmpty = true); // will be deleted next frame if left empty and unfocused!
bool isWorkspaceSpecial(const WORKSPACEID&); void renameWorkspace(const WORKSPACEID&, const std::string& name = "");
WORKSPACEID getNewSpecialID(); void setActiveMonitor(PHLMONITOR);
void performUserChecks(); bool isWorkspaceSpecial(const WORKSPACEID&);
void moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWorkspace); WORKSPACEID getNewSpecialID();
PHLWINDOW getForceFocus(); void performUserChecks();
void arrangeMonitors(); void moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWorkspace);
void enterUnsafeState(); PHLWINDOW getForceFocus();
void leaveUnsafeState(); void arrangeMonitors();
void setPreferredScaleForSurface(SP<CWLSurfaceResource> pSurface, double scale); void enterUnsafeState();
void setPreferredTransformForSurface(SP<CWLSurfaceResource> pSurface, wl_output_transform transform); void leaveUnsafeState();
void updateSuspendedStates(); void setPreferredScaleForSurface(SP<CWLSurfaceResource> pSurface, double scale);
void onNewMonitor(SP<Aquamarine::IOutput> output); void setPreferredTransformForSurface(SP<CWLSurfaceResource> pSurface, wl_output_transform transform);
void ensurePersistentWorkspacesPresent(const std::vector<SWorkspaceRule>& rules, PHLWORKSPACE pWorkspace = nullptr); void updateSuspendedStates();
std::optional<unsigned int> getVTNr(); PHLWINDOW windowForCPointer(CWindow*);
void onNewMonitor(SP<Aquamarine::IOutput> output);
NColorManagement::SImageDescription getPreferredImageDescription(); std::string explicitConfigPath;
bool shouldChangePreferredImageDescription();
bool supportsDrmSyncobjTimeline() const;
std::string m_explicitConfigPath;
private: private:
void initAllSignals(); void initAllSignals();
void removeAllSignals(); void removeAllSignals();
void cleanEnvironment(); void cleanEnvironment();
void setRandomSplash(); void setRandomSplash();
void initManagers(eManagersInitStage stage); void initManagers(eManagersInitStage stage);
void prepareFallbackOutput(); void prepareFallbackOutput();
void createLockFile();
void removeLockFile();
void setMallocThreshold();
bool m_bDrmSyncobjTimelineSupported = false; uint64_t m_iHyprlandPID = 0;
wl_event_source* m_critSigSource = nullptr;
uint64_t m_hyprlandPID = 0; rlimit m_sOriginalNofile = {0};
wl_event_source* m_critSigSource = nullptr;
rlimit m_originalNofile = {};
std::vector<PHLWORKSPACEREF> m_workspaces;
}; };
inline UP<CCompositor> g_pCompositor; inline std::unique_ptr<CCompositor> g_pCompositor;

View File

@@ -3,11 +3,11 @@
#include "helpers/math/Math.hpp" #include "helpers/math/Math.hpp"
#include <functional> #include <functional>
#include <any> #include <any>
#include <string>
#include <algorithm>
#include <hyprutils/math/Box.hpp> #include <hyprutils/math/Box.hpp>
enum eIcons : uint8_t { using namespace Hyprutils::Math;
enum eIcons {
ICON_WARNING = 0, ICON_WARNING = 0,
ICON_INFO, ICON_INFO,
ICON_HINT, ICON_HINT,
@@ -17,20 +17,19 @@ enum eIcons : uint8_t {
ICON_NONE ICON_NONE
}; };
enum eRenderStage : uint8_t { enum eRenderStage {
RENDER_PRE = 0, /* Before binding the gl context */ RENDER_PRE = 0, /* Before binding the gl context */
RENDER_BEGIN, /* Just when the rendering begins, nothing has been rendered yet. Damage, current render data in opengl valid. */ RENDER_BEGIN, /* Just when the rendering begins, nothing has been rendered yet. Damage, current render data in opengl valid. */
RENDER_POST_WALLPAPER, /* After background layer, but before bottom and overlay layers */ RENDER_PRE_WINDOWS, /* Pre windows, post bottom and overlay layers */
RENDER_PRE_WINDOWS, /* Pre windows, post bottom and overlay layers */ RENDER_POST_WINDOWS, /* Post windows, pre top/overlay layers, etc */
RENDER_POST_WINDOWS, /* Post windows, pre top/overlay layers, etc */ RENDER_LAST_MOMENT, /* Last moment to render with the gl context */
RENDER_LAST_MOMENT, /* Last moment to render with the gl context */ RENDER_POST, /* After rendering is finished, gl context not available anymore */
RENDER_POST, /* After rendering is finished, gl context not available anymore */ RENDER_POST_MIRROR, /* After rendering a mirror */
RENDER_POST_MIRROR, /* After rendering a mirror */ RENDER_PRE_WINDOW, /* Before rendering a window (any pass) Note some windows (e.g. tiled) may have 2 passes (main & popup) */
RENDER_PRE_WINDOW, /* Before rendering a window (any pass) Note some windows (e.g. tiled) may have 2 passes (main & popup) */ RENDER_POST_WINDOW, /* After rendering a window (any pass) */
RENDER_POST_WINDOW, /* After rendering a window (any pass) */
}; };
enum eInputType : uint8_t { enum eInputType {
INPUT_TYPE_AXIS = 0, INPUT_TYPE_AXIS = 0,
INPUT_TYPE_BUTTON, INPUT_TYPE_BUTTON,
INPUT_TYPE_DRAG_START, INPUT_TYPE_DRAG_START,
@@ -42,7 +41,7 @@ struct SCallbackInfo {
bool cancelled = false; /* on cancellable events, will cancel the event. */ bool cancelled = false; /* on cancellable events, will cancel the event. */
}; };
enum eHyprCtlOutputFormat : uint8_t { enum eHyprCtlOutputFormat {
FORMAT_NORMAL = 0, FORMAT_NORMAL = 0,
FORMAT_JSON FORMAT_JSON
}; };
@@ -53,14 +52,8 @@ struct SHyprCtlCommand {
std::function<std::string(eHyprCtlOutputFormat, std::string)> fn; std::function<std::string(eHyprCtlOutputFormat, std::string)> fn;
}; };
struct SDispatchResult { typedef int64_t WINDOWID;
bool passEvent = false; typedef int64_t MONITORID;
bool success = true; typedef int64_t WORKSPACEID;
std::string error;
};
using WINDOWID = int64_t; typedef std::function<void(void*, SCallbackInfo&, std::any)> HOOK_CALLBACK_FN;
using MONITORID = int64_t;
using WORKSPACEID = int64_t;
using HOOK_CALLBACK_FN = std::function<void(void*, SCallbackInfo&, std::any)>;

View File

@@ -2,18 +2,16 @@
#include "../defines.hpp" #include "../defines.hpp"
#include "../helpers/varlist/VarList.hpp" #include "../helpers/varlist/VarList.hpp"
#include <vector> #include <vector>
#include <map>
enum eConfigValueDataTypes : int8_t { enum eConfigValueDataTypes {
CVD_TYPE_INVALID = -1, CVD_TYPE_INVALID = -1,
CVD_TYPE_GRADIENT = 0, CVD_TYPE_GRADIENT = 0,
CVD_TYPE_CSS_VALUE = 1, CVD_TYPE_CSS_VALUE = 1
CVD_TYPE_FONT_WEIGHT = 2,
}; };
class ICustomConfigValueData { class ICustomConfigValueData {
public: public:
virtual ~ICustomConfigValueData() = default; virtual ~ICustomConfigValueData() = 0;
virtual eConfigValueDataTypes getDataType() = 0; virtual eConfigValueDataTypes getDataType() = 0;
@@ -22,51 +20,35 @@ class ICustomConfigValueData {
class CGradientValueData : public ICustomConfigValueData { class CGradientValueData : public ICustomConfigValueData {
public: public:
CGradientValueData() = default; CGradientValueData() {};
CGradientValueData(CHyprColor col) { CGradientValueData(CColor col) {
m_colors.push_back(col); m_vColors.push_back(col);
updateColorsOk();
}; };
virtual ~CGradientValueData() = default; virtual ~CGradientValueData() {};
virtual eConfigValueDataTypes getDataType() { virtual eConfigValueDataTypes getDataType() {
return CVD_TYPE_GRADIENT; return CVD_TYPE_GRADIENT;
} }
void reset(CHyprColor col) { void reset(CColor col) {
m_colors.clear(); m_vColors.clear();
m_colors.emplace_back(col); m_vColors.emplace_back(col);
m_angle = 0; m_fAngle = 0;
updateColorsOk();
}
void updateColorsOk() {
m_colorsOkLabA.clear();
for (auto& c : m_colors) {
const auto OKLAB = c.asOkLab();
m_colorsOkLabA.emplace_back(OKLAB.l);
m_colorsOkLabA.emplace_back(OKLAB.a);
m_colorsOkLabA.emplace_back(OKLAB.b);
m_colorsOkLabA.emplace_back(c.a);
}
} }
/* Vector containing the colors */ /* Vector containing the colors */
std::vector<CHyprColor> m_colors; std::vector<CColor> m_vColors;
/* Vector containing pure colors for shoving into opengl */
std::vector<float> m_colorsOkLabA;
/* Float corresponding to the angle (rad) */ /* Float corresponding to the angle (rad) */
float m_angle = 0; float m_fAngle = 0;
// //
bool operator==(const CGradientValueData& other) const { bool operator==(const CGradientValueData& other) const {
if (other.m_colors.size() != m_colors.size() || m_angle != other.m_angle) if (other.m_vColors.size() != m_vColors.size() || m_fAngle != other.m_fAngle)
return false; return false;
for (size_t i = 0; i < m_colors.size(); ++i) for (size_t i = 0; i < m_vColors.size(); ++i)
if (m_colors[i] != other.m_colors[i]) if (m_vColors[i] != other.m_vColors[i])
return false; return false;
return true; return true;
@@ -74,28 +56,28 @@ class CGradientValueData : public ICustomConfigValueData {
virtual std::string toString() { virtual std::string toString() {
std::string result; std::string result;
for (auto& c : m_colors) { for (auto& c : m_vColors) {
result += std::format("{:x} ", c.getAsHex()); result += std::format("{:x} ", c.getAsHex());
} }
result += std::format("{}deg", sc<int>(m_angle * 180.0 / M_PI)); result += std::format("{}deg", (int)(m_fAngle * 180.0 / M_PI));
return result; return result;
} }
}; };
class CCssGapData : public ICustomConfigValueData { class CCssGapData : public ICustomConfigValueData {
public: public:
CCssGapData() : m_top(0), m_right(0), m_bottom(0), m_left(0) {}; CCssGapData() : top(0), right(0), bottom(0), left(0) {};
CCssGapData(int64_t global) : m_top(global), m_right(global), m_bottom(global), m_left(global) {}; CCssGapData(int64_t global) : top(global), right(global), bottom(global), left(global) {};
CCssGapData(int64_t vertical, int64_t horizontal) : m_top(vertical), m_right(horizontal), m_bottom(vertical), m_left(horizontal) {}; CCssGapData(int64_t vertical, int64_t horizontal) : top(vertical), right(horizontal), bottom(vertical), left(horizontal) {};
CCssGapData(int64_t top, int64_t horizontal, int64_t bottom) : m_top(top), m_right(horizontal), m_bottom(bottom), m_left(horizontal) {}; CCssGapData(int64_t top, int64_t horizontal, int64_t bottom) : top(top), right(horizontal), bottom(bottom), left(horizontal) {};
CCssGapData(int64_t top, int64_t right, int64_t bottom, int64_t left) : m_top(top), m_right(right), m_bottom(bottom), m_left(left) {}; CCssGapData(int64_t top, int64_t right, int64_t bottom, int64_t left) : top(top), right(right), bottom(bottom), left(left) {};
/* Css like directions */ /* Css like directions */
int64_t m_top; int64_t top;
int64_t m_right; int64_t right;
int64_t m_bottom; int64_t bottom;
int64_t m_left; int64_t left;
void parseGapData(CVarList varlist) { void parseGapData(CVarList varlist) {
switch (varlist.size()) { switch (varlist.size()) {
@@ -124,10 +106,10 @@ class CCssGapData : public ICustomConfigValueData {
} }
void reset(int64_t global) { void reset(int64_t global) {
m_top = global; top = global;
m_right = global; right = global;
m_bottom = global; bottom = global;
m_left = global; left = global;
} }
virtual eConfigValueDataTypes getDataType() { virtual eConfigValueDataTypes getDataType() {
@@ -135,44 +117,6 @@ class CCssGapData : public ICustomConfigValueData {
} }
virtual std::string toString() { virtual std::string toString() {
return std::format("{} {} {} {}", m_top, m_right, m_bottom, m_left); return std::format("{} {} {} {}", top, right, bottom, left);
}
};
class CFontWeightConfigValueData : public ICustomConfigValueData {
public:
CFontWeightConfigValueData() = default;
CFontWeightConfigValueData(const char* weight) {
parseWeight(weight);
}
int64_t m_value = 400; // default to normal weight
virtual eConfigValueDataTypes getDataType() {
return CVD_TYPE_FONT_WEIGHT;
}
virtual std::string toString() {
return std::format("{}", m_value);
}
void parseWeight(const std::string& strWeight) {
auto lcWeight{strWeight};
std::ranges::transform(strWeight, lcWeight.begin(), ::tolower);
// values taken from Pango weight enums
const auto WEIGHTS = std::map<std::string, int>{
{"thin", 100}, {"ultralight", 200}, {"light", 300}, {"semilight", 350}, {"book", 380}, {"normal", 400},
{"medium", 500}, {"semibold", 600}, {"bold", 700}, {"ultrabold", 800}, {"heavy", 900}, {"ultraheavy", 1000},
};
auto weight = WEIGHTS.find(lcWeight);
if (weight != WEIGHTS.end())
m_value = weight->second;
else {
int w_i = std::stoi(strWeight);
if (w_i < 100 || w_i > 1000)
m_value = 400;
}
} }
}; };

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