pax_global_header 0000666 0000000 0000000 00000000064 15136700726 0014521 g ustar 00root root 0000000 0000000 52 comment=64b991cb917e28a51a50987a113ac4bf014ad0b7
hyprpaper-0.8.3/ 0000775 0000000 0000000 00000000000 15136700726 0013543 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.3/.clang-format 0000664 0000000 0000000 00000003416 15136700726 0016122 0 ustar 00root root 0000000 0000000 ---
Language: Cpp
BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: true
AlignConsecutiveAssignments: true
AlignEscapedNewlines: Right
AlignOperands: false
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: false
BreakConstructorInitializers: AfterColon
ColumnLimit: 180
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
IncludeBlocks: Preserve
IndentCaseLabels: true
IndentWidth: 4
PointerAlignment: Left
ReflowComments: false
SortIncludes: false
SortUsingDeclarations: false
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 4
UseTab: Never
AllowShortEnumsOnASingleLine: false
BraceWrapping:
AfterEnum: false
AlignConsecutiveDeclarations: AcrossEmptyLines
NamespaceIndentation: All
hyprpaper-0.8.3/.github/ 0000775 0000000 0000000 00000000000 15136700726 0015103 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.3/.github/workflows/ 0000775 0000000 0000000 00000000000 15136700726 0017140 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.3/.github/workflows/nix-build.yaml 0000664 0000000 0000000 00000003263 15136700726 0021723 0 ustar 00root root 0000000 0000000 name: Build Hyprpaper (Nix)
on: [push, pull_request, workflow_dispatch]
jobs:
nix:
name: "Build"
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v3
with:
submodules: recursive
- 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 }}-${{ 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
- uses: cachix/cachix-action@v12
with:
name: hyprland
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Build Hyprpaper with default settings
run: nix build --print-build-logs --accept-flake-config
hyprpaper-0.8.3/.gitignore 0000664 0000000 0000000 00000000542 15136700726 0015534 0 ustar 00root root 0000000 0000000 CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
build/
result
/.vscode/
/.idea
*.o
*-protocol.c
*-protocol.h
.ccls-cache
protocols/*.hpp
protocols/*.cpp
hw-protocols/*.hpp
hw-protocols/*.cpp
.cache/
hyprctl/hyprctl
gmon.out
*.out
*.tar.gz
hyprpaper-0.8.3/CMakeLists.txt 0000664 0000000 0000000 00000012731 15136700726 0016307 0 ustar 00root root 0000000 0000000 cmake_minimum_required(VERSION 3.12)
file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW)
string(STRIP ${VER_RAW} VERSION)
project(
hyprpaper
DESCRIPTION "A blazing fast wayland wallpaper utility"
VERSION ${VERSION})
add_compile_definitions(HYPRPAPER_VERSION="${VERSION}")
set(CMAKE_MESSAGE_LOG_LEVEL "STATUS")
message(STATUS "Configuring hyprpaper!")
configure_file(systemd/hyprpaper.service.in systemd/hyprpaper.service @ONLY)
# Get git info hash and branch
execute_process(
COMMAND git rev-parse --abbrev-ref HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_BRANCH
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(
COMMAND git rev-parse HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_COMMIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(
COMMAND bash -c "git show ${GIT_COMMIT_HASH} | head -n 5 | tail -n 1 | sed -s 's/\#//g; s/^[[:space:]]*//'"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_COMMIT_MESSAGE
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(
COMMAND bash -c "git diff-index --quiet HEAD -- || echo \"dirty\""
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_DIRTY
OUTPUT_STRIP_TRAILING_WHITESPACE)
include_directories(. hw-protocols protocols)
set(CMAKE_CXX_STANDARD 23)
add_compile_options(-DWLR_USE_UNSTABLE)
add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value
-Wno-missing-field-initializers -Wno-narrowing)
find_package(Threads REQUIRED)
find_package(PkgConfig REQUIRED)
find_package(hyprwayland-scanner 0.4.0 REQUIRED)
pkg_check_modules(
deps
REQUIRED
IMPORTED_TARGET
hyprlang>=0.6.0
hyprutils>=0.2.4
wayland-client
hyprtoolkit>=0.4.1
hyprwire
pixman-1
libdrm)
file(GLOB_RECURSE SRCFILES "src/*.cpp")
add_executable(hyprpaper ${SRCFILES})
# Wayland
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}")
pkg_get_variable(WAYLAND_SCANNER_PKGDATA_DIR wayland-scanner pkgdatadir)
message(
STATUS "Found wayland-scanner pkgdatadir at ${WAYLAND_SCANNER_PKGDATA_DIR}")
function(protocolnew protoPath protoName external)
if(external)
set(path ${CMAKE_SOURCE_DIR}/${protoPath})
else()
set(path ${WAYLAND_PROTOCOLS_DIR}/${protoPath})
endif()
add_custom_command(
OUTPUT ${CMAKE_SOURCE_DIR}/protocols/${protoName}.cpp
${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp
COMMAND hyprwayland-scanner --client ${path}/${protoName}.xml
${CMAKE_SOURCE_DIR}/protocols/
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
target_sources(hyprpaper PRIVATE protocols/${protoName}.cpp
protocols/${protoName}.hpp)
endfunction()
function(protocolWayland)
add_custom_command(
OUTPUT ${CMAKE_SOURCE_DIR}/protocols/wayland.cpp
${CMAKE_SOURCE_DIR}/protocols/wayland.hpp
COMMAND hyprwayland-scanner --wayland-enums --client
${WAYLAND_SCANNER_PKGDATA_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
target_sources(hyprpaper PRIVATE protocols/wayland.cpp protocols/wayland.hpp)
endfunction()
protocolwayland()
protocolnew("protocols" "wlr-layer-shell-unstable-v1" true)
protocolnew("stable/linux-dmabuf" "linux-dmabuf-v1" false)
protocolnew("staging/fractional-scale" "fractional-scale-v1" false)
protocolnew("stable/viewporter" "viewporter" false)
protocolnew("stable/xdg-shell" "xdg-shell" false)
protocolnew("staging/cursor-shape" "cursor-shape-v1" false)
protocolnew("stable/tablet" "tablet-v2" false)
# Hyprwire
function(hyprprotocolServer protoPath protoName)
set(path ${CMAKE_SOURCE_DIR}/${protoPath})
add_custom_command(
OUTPUT ${CMAKE_SOURCE_DIR}/hw-protocols/${protoName}-server.cpp
${CMAKE_SOURCE_DIR}/hw-protocols/${protoName}-server.hpp
COMMAND hyprwire-scanner ${path}/${protoName}.xml
${CMAKE_SOURCE_DIR}/hw-protocols/
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
target_sources(hyprpaper PRIVATE hw-protocols/${protoName}-server.cpp
hw-protocols/${protoName}-server.hpp
)
endfunction()
hyprprotocolServer(hw-protocols hyprpaper_core)
#
string(REPLACE "\"" " " GIT_COMMIT_MESSAGE_ESCAPED "${GIT_COMMIT_MESSAGE}")
target_compile_definitions(hyprpaper
PRIVATE "-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"")
target_compile_definitions(hyprpaper PRIVATE "-DGIT_BRANCH=\"${GIT_BRANCH}\"")
target_compile_definitions(hyprpaper
PRIVATE "-DGIT_COMMIT_MESSAGE=\"${GIT_COMMIT_MESSAGE_ESCAPED}\"")
target_compile_definitions(hyprpaper PRIVATE "-DGIT_DIRTY=\"${GIT_DIRTY}\"")
target_link_libraries(hyprpaper rt)
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)
target_link_libraries(hyprpaper PkgConfig::deps)
target_link_libraries(
hyprpaper
OpenGL
GLESv2
pthread
magic
${CMAKE_THREAD_LIBS_INIT}
wayland-cursor)
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg -no-pie -fno-builtin")
set(CMAKE_EXE_LINKER_FLAGS
"${CMAKE_EXE_LINKER_FLAGS} -pg -no-pie -fno-builtin")
set(CMAKE_SHARED_LINKER_FLAGS
"${CMAKE_SHARED_LINKER_FLAGS} -pg -no-pie -fno-builtin")
endif(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
include(GNUInstallDirs)
install(TARGETS hyprpaper)
install(FILES ${CMAKE_BINARY_DIR}/systemd/hyprpaper.service DESTINATION "lib/systemd/user")
hyprpaper-0.8.3/LICENSE 0000664 0000000 0000000 00000002764 15136700726 0014561 0 ustar 00root root 0000000 0000000 BSD 3-Clause License
Copyright (c) 2022, Hypr Development
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
hyprpaper-0.8.3/README.md 0000664 0000000 0000000 00000002101 15136700726 0015014 0 ustar 00root root 0000000 0000000 # hyprpaper
Hyprpaper is a simple and fast wallpaper utility for Hyprland with the ability to dynamically change wallpapers through sockets.
# Features
- Per-output wallpapers
- fill, tile, cover or contain modes
- fractional scaling support
- IPC for fast wallpaper switches
# Installation
[Arch Linux](https://archlinux.org/packages/extra/x86_64/hyprpaper/): `pacman -S hyprpaper`
[OpenSuse Linux](https://software.opensuse.org/package/hyprpaper): `zypper install hyprpaper`
## Manual:
### Dependencies
The development files of these packages need to be installed on the system for `hyprpaper` to build correctly.
(Development packages are usually suffixed with `-dev` or `-devel` in most distros' repos).
- hyprtoolkit
- hyprlang
- hyprutils
- hyprwire
### Building
Building is done via CMake:
```sh
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build
cmake --build ./build --config Release --target hyprpaper -j`nproc 2>/dev/null || getconf _NPROCESSORS_CONF`
```
Install with:
```sh
cmake --install ./build
``` hyprpaper-0.8.3/VERSION 0000664 0000000 0000000 00000000006 15136700726 0014607 0 ustar 00root root 0000000 0000000 0.8.3
hyprpaper-0.8.3/flake.lock 0000664 0000000 0000000 00000013145 15136700726 0015503 0 ustar 00root root 0000000 0000000 {
"nodes": {
"aquamarine": {
"inputs": {
"hyprutils": [
"hyprutils"
],
"hyprwayland-scanner": [
"hyprwayland-scanner"
],
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1765900596,
"narHash": "sha256-+hn8v9jkkLP9m+o0Nm5SiEq10W0iWDSotH2XfjU45fA=",
"owner": "hyprwm",
"repo": "aquamarine",
"rev": "d83c97f8f5c0aae553c1489c7d9eff3eadcadace",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "aquamarine",
"type": "github"
}
},
"hyprgraphics": {
"inputs": {
"hyprutils": [
"hyprutils"
],
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1763733840,
"narHash": "sha256-JnET78yl5RvpGuDQy3rCycOCkiKoLr5DN1fPhRNNMco=",
"owner": "hyprwm",
"repo": "hyprgraphics",
"rev": "8f1bec691b2d198c60cccabca7a94add2df4ed1a",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprgraphics",
"type": "github"
}
},
"hyprlang": {
"inputs": {
"hyprutils": [
"hyprutils"
],
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1764612430,
"narHash": "sha256-54ltTSbI6W+qYGMchAgCR6QnC1kOdKXN6X6pJhOWxFg=",
"owner": "hyprwm",
"repo": "hyprlang",
"rev": "0d00dc118981531aa731150b6ea551ef037acddd",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprlang",
"type": "github"
}
},
"hyprtoolkit": {
"inputs": {
"aquamarine": [
"aquamarine"
],
"hyprgraphics": [
"hyprgraphics"
],
"hyprlang": [
"hyprlang"
],
"hyprutils": [
"hyprutils"
],
"hyprwayland-scanner": [
"hyprwayland-scanner"
],
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1765999299,
"narHash": "sha256-C0tMHhVMEN2XlgMVeuJSbY64h9UhR2AKk5Hxxlxx6cA=",
"owner": "hyprwm",
"repo": "hyprtoolkit",
"rev": "0a5d2c25d018112434e802212a1ad57ca1e24819",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprtoolkit",
"type": "github"
}
},
"hyprutils": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1766002182,
"narHash": "sha256-Ji2ty5d6yuToq59SZfpG0T5B5SkF3UiHoDl8VMyQp14=",
"owner": "hyprwm",
"repo": "hyprutils",
"rev": "1c527b30feb7bed959ac07ae034a6105e6b65fd3",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprutils",
"type": "github"
}
},
"hyprwayland-scanner": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1763640274,
"narHash": "sha256-Uan1Nl9i4TF/kyFoHnTq1bd/rsWh4GAK/9/jDqLbY5A=",
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"rev": "f6cf414ca0e16a4d30198fd670ec86df3c89f671",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"type": "github"
}
},
"hyprwire": {
"inputs": {
"hyprutils": [
"hyprutils"
],
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1765741352,
"narHash": "sha256-jTJQy1m2XkcZJABajYVP249fCWPl3GbLe3Z8KiQmZqg=",
"owner": "hyprwm",
"repo": "hyprwire",
"rev": "b8ca85082fd5c3cdd3d11027492cd0332b517078",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprwire",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1765779637,
"narHash": "sha256-KJ2wa/BLSrTqDjbfyNx70ov/HdgNBCBBSQP3BIzKnv4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1306659b587dc277866c7b69eb97e5f07864d8c4",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"aquamarine": "aquamarine",
"hyprgraphics": "hyprgraphics",
"hyprlang": "hyprlang",
"hyprtoolkit": "hyprtoolkit",
"hyprutils": "hyprutils",
"hyprwayland-scanner": "hyprwayland-scanner",
"hyprwire": "hyprwire",
"nixpkgs": "nixpkgs",
"systems": "systems"
}
},
"systems": {
"locked": {
"lastModified": 1689347949,
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
"owner": "nix-systems",
"repo": "default-linux",
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default-linux",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
hyprpaper-0.8.3/flake.nix 0000664 0000000 0000000 00000007037 15136700726 0015354 0 ustar 00root root 0000000 0000000 {
description = "Hyprpaper is a blazing fast Wayland wallpaper utility with IPC controls";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
systems.url = "github:nix-systems/default-linux";
aquamarine = {
url = "github:hyprwm/aquamarine";
inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems";
inputs.hyprutils.follows = "hyprutils";
inputs.hyprwayland-scanner.follows = "hyprwayland-scanner";
};
hyprgraphics = {
url = "github:hyprwm/hyprgraphics";
inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems";
inputs.hyprutils.follows = "hyprutils";
};
hyprutils = {
url = "github:hyprwm/hyprutils";
inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems";
};
hyprlang = {
url = "github:hyprwm/hyprlang";
inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems";
inputs.hyprutils.follows = "hyprutils";
};
hyprwayland-scanner = {
url = "github:hyprwm/hyprwayland-scanner";
inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems";
};
hyprwire = {
url = "github:hyprwm/hyprwire";
inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems";
inputs.hyprutils.follows = "hyprutils";
};
hyprtoolkit = {
url = "github:hyprwm/hyprtoolkit";
inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems";
inputs.aquamarine.follows = "aquamarine";
inputs.hyprutils.follows = "hyprutils";
inputs.hyprlang.follows = "hyprlang";
inputs.hyprgraphics.follows = "hyprgraphics";
inputs.hyprwayland-scanner.follows = "hyprwayland-scanner";
};
};
outputs = {
self,
nixpkgs,
systems,
...
} @ inputs: let
inherit (nixpkgs) lib;
eachSystem = lib.genAttrs (import systems);
pkgsFor = eachSystem (system:
import nixpkgs {
localSystem.system = system;
overlays = with self.overlays; [hyprpaper];
});
mkDate = longDate: (lib.concatStringsSep "-" [
(builtins.substring 0 4 longDate)
(builtins.substring 4 2 longDate)
(builtins.substring 6 2 longDate)
]);
version = lib.removeSuffix "\n" (builtins.readFile ./VERSION);
in {
overlays = {
default = self.overlays.hyprpaper;
hyprpaper = lib.composeManyExtensions [
inputs.aquamarine.overlays.default
inputs.hyprgraphics.overlays.default
inputs.hyprlang.overlays.default
inputs.hyprutils.overlays.default
inputs.hyprwayland-scanner.overlays.default
inputs.hyprtoolkit.overlays.default
inputs.hyprwire.overlays.default
(final: prev: rec {
hyprpaper = final.callPackage ./nix/default.nix {
stdenv = final.gcc15Stdenv;
version = version + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
commit = self.rev or "";
};
hyprpaper-debug = hyprpaper.override {debug = true;};
})
];
};
packages = eachSystem (system: {
default = self.packages.${system}.hyprpaper;
inherit (pkgsFor.${system}) hyprpaper hyprpaper-debug;
});
homeManagerModules = {
default = self.homeManagerModules.hyprpaper;
hyprpaper = builtins.throw "hyprpaper: the flake HM module has been removed. Use the module from Home Manager upstream.";
};
formatter = eachSystem (system: pkgsFor.${system}.alejandra);
};
}
hyprpaper-0.8.3/hw-protocols/ 0000775 0000000 0000000 00000000000 15136700726 0016203 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.3/hw-protocols/hyprpaper_core.xml 0000664 0000000 0000000 00000012601 15136700726 0021747 0 ustar 00root root 0000000 0000000
BSD 3-Clause License
Copyright (c) 2025, Hypr Development
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
hyprpaper-0.8.3/nix/ 0000775 0000000 0000000 00000000000 15136700726 0014341 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.3/nix/default.nix 0000664 0000000 0000000 00000002725 15136700726 0016513 0 ustar 00root root 0000000 0000000 {
lib,
stdenv,
pkg-config,
cmake,
aquamarine,
cairo,
expat,
file,
fribidi,
hyprgraphics,
hyprlang,
hyprutils,
hyprtoolkit,
hyprwire,
hyprwayland-scanner,
libGL,
libdatrie,
libdrm,
libjpeg,
libjxl,
libselinux,
libsepol,
libthai,
libwebp,
pango,
pcre,
pcre2,
util-linux,
wayland,
wayland-protocols,
wayland-scanner,
xorg,
commit,
debug ? false,
version ? "git",
}:
stdenv.mkDerivation {
pname = "hyprpaper" + lib.optionalString debug "-debug";
inherit version;
src = ../.;
prePatch = ''
substituteInPlace src/main.cpp \
--replace GIT_COMMIT_HASH '"${commit}"'
'';
depsBuildBuild = [
pkg-config
];
cmakeBuildType =
if debug
then "Debug"
else "Release";
nativeBuildInputs = [
cmake
hyprwayland-scanner
hyprwire
pkg-config
wayland-scanner
];
buildInputs = [
aquamarine
cairo
expat
file
fribidi
hyprgraphics
hyprlang
hyprutils
hyprtoolkit
hyprwire
libGL
libdatrie
libdrm
libjpeg
libjxl
libselinux
libsepol
libthai
libwebp
pango
pcre
pcre2
wayland
wayland-protocols
xorg.libXdmcp
util-linux
];
meta = with lib; {
description = "A blazing fast wayland wallpaper utility with IPC controls";
homepage = "https://github.com/hyprwm/hyprpaper";
license = licenses.bsd3;
mainProgram = "hyprpaper";
platforms = platforms.linux;
};
}
hyprpaper-0.8.3/protocols/ 0000775 0000000 0000000 00000000000 15136700726 0015567 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.3/protocols/wlr-layer-shell-unstable-v1.xml 0000664 0000000 0000000 00000032074 15136700726 0023501 0 ustar 00root root 0000000 0000000
Copyright © 2017 Drew DeVault
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
Clients can use this interface to assign the surface_layer role to
wl_surfaces. Such surfaces are assigned to a "layer" of the output and
rendered with a defined z-depth respective to each other. They may also be
anchored to the edges and corners of a screen and specify input handling
semantics. This interface should be suitable for the implementation of
many desktop shell components, and a broad number of other applications
that interact with the desktop.
Create a layer surface for an existing surface. This assigns the role of
layer_surface, or raises a protocol error if another role is already
assigned.
Creating a layer surface from a wl_surface which has a buffer attached
or committed is a client error, and any attempts by a client to attach
or manipulate a buffer prior to the first layer_surface.configure call
must also be treated as errors.
You may pass NULL for output to allow the compositor to decide which
output to use. Generally this will be the one that the user most
recently interacted with.
Clients can specify a namespace that defines the purpose of the layer
surface.
These values indicate which layers a surface can be rendered in. They
are ordered by z depth, bottom-most first. Traditional shell surfaces
will typically be rendered between the bottom and top layers.
Fullscreen shell surfaces are typically rendered at the top layer.
Multiple surfaces can share a single layer, and ordering within a
single layer is undefined.
An interface that may be implemented by a wl_surface, for surfaces that
are designed to be rendered as a layer of a stacked desktop-like
environment.
Layer surface state (size, anchor, exclusive zone, margin, interactivity)
is double-buffered, and will be applied at the time wl_surface.commit of
the corresponding wl_surface is called.
Sets the size of the surface in surface-local coordinates. The
compositor will display the surface centered with respect to its
anchors.
If you pass 0 for either value, the compositor will assign it and
inform you of the assignment in the configure event. You must set your
anchor to opposite edges in the dimensions you omit; not doing so is a
protocol error. Both values are 0 by default.
Size is double-buffered, see wl_surface.commit.
Requests that the compositor anchor the surface to the specified edges
and corners. If two orthoginal edges are specified (e.g. 'top' and
'left'), then the anchor point will be the intersection of the edges
(e.g. the top left corner of the output); otherwise the anchor point
will be centered on that edge, or in the center if none is specified.
Anchor is double-buffered, see wl_surface.commit.
Requests that the compositor avoids occluding an area of the surface
with other surfaces. The compositor's use of this information is
implementation-dependent - do not assume that this region will not
actually be occluded.
A positive value is only meaningful if the surface is anchored to an
edge, rather than a corner. The zone is the number of surface-local
coordinates from the edge that are considered exclusive.
Surfaces that do not wish to have an exclusive zone may instead specify
how they should interact with surfaces that do. If set to zero, the
surface indicates that it would like to be moved to avoid occluding
surfaces with a positive excluzive zone. If set to -1, the surface
indicates that it would not like to be moved to accommodate for other
surfaces, and the compositor should extend it all the way to the edges
it is anchored to.
For example, a panel might set its exclusive zone to 10, so that
maximized shell surfaces are not shown on top of it. A notification
might set its exclusive zone to 0, so that it is moved to avoid
occluding the panel, but shell surfaces are shown underneath it. A
wallpaper or lock screen might set their exclusive zone to -1, so that
they stretch below or over the panel.
The default value is 0.
Exclusive zone is double-buffered, see wl_surface.commit.
Requests that the surface be placed some distance away from the anchor
point on the output, in surface-local coordinates. Setting this value
for edges you are not anchored to has no effect.
The exclusive zone includes the margin.
Margin is double-buffered, see wl_surface.commit.
Set to 1 to request that the seat send keyboard events to this layer
surface. For layers below the shell surface layer, the seat will use
normal focus semantics. For layers above the shell surface layers, the
seat will always give exclusive keyboard focus to the top-most layer
which has keyboard interactivity set to true.
Layer surfaces receive pointer, touch, and tablet events normally. If
you do not want to receive them, set the input region on your surface
to an empty region.
Events is double-buffered, see wl_surface.commit.
This assigns an xdg_popup's parent to this layer_surface. This popup
should have been created via xdg_surface::get_popup with the parent set
to NULL, and this request must be invoked before committing the popup's
initial state.
See the documentation of xdg_popup for more details about what an
xdg_popup is and how it is used.
When a configure event is received, if a client commits the
surface in response to the configure event, then the client
must make an ack_configure request sometime before the commit
request, passing along the serial of the configure event.
If the client receives multiple configure events before it
can respond to one, it only has to ack the last configure event.
A client is not required to commit immediately after sending
an ack_configure request - it may even ack_configure several times
before its next surface commit.
A client may send multiple ack_configure requests before committing, but
only the last request sent before a commit indicates which configure
event the client really is responding to.
This request destroys the layer surface.
The configure event asks the client to resize its surface.
Clients should arrange their surface for the new states, and then send
an ack_configure request with the serial sent in this configure event at
some point before committing the new surface.
The client is free to dismiss all but the last configure event it
received.
The width and height arguments specify the size of the window in
surface-local coordinates.
The size is a hint, in the sense that the client is free to ignore it if
it doesn't resize, pick a smaller size (to satisfy aspect ratio or
resize in steps of NxM pixels). If the client picks a smaller size and
is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
surface will be centered on this axis.
If the width or height arguments are zero, it means the client should
decide its own window dimension.
The closed event is sent by the compositor when the surface will no
longer be shown. The output may have been destroyed or the user may
have asked for it to be removed. Further changes to the surface will be
ignored. The client should destroy the resource after receiving this
event, and create a new surface if they so choose.
hyprpaper-0.8.3/src/ 0000775 0000000 0000000 00000000000 15136700726 0014332 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.3/src/config/ 0000775 0000000 0000000 00000000000 15136700726 0015577 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.3/src/config/ConfigManager.cpp 0000664 0000000 0000000 00000022234 15136700726 0021006 0 ustar 00root root 0000000 0000000 #include "ConfigManager.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#include "../helpers/Logger.hpp"
#include "WallpaperMatcher.hpp"
#include
using namespace std::string_literals;
[[nodiscard]] static bool isImage(const std::filesystem::path& path) {
static constexpr std::array exts{".jpg", ".jpeg", ".png", ".bmp", ".webp", ".svg"};
auto ext = path.extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
if (std::ranges::any_of(exts, [&ext](const auto& e) { return ext == e; }))
return true;
magic_t magic = magic_open(MAGIC_MIME_TYPE);
if (magic == nullptr)
return false;
Hyprutils::Utils::CScopeGuard guard{[&magic] { magic_close(magic); }};
if (magic_load(magic, nullptr) != 0)
return false;
const auto* result = magic_file(magic, path.string().c_str());
if (result == nullptr)
return false;
return std::string(result).starts_with("image/");
}
// Forward declaration for the source handler
static Hyprlang::CParseResult handleSource(const char* COMMAND, const char* VALUE);
static std::string getMainConfigPath() {
static const auto paths = Hyprutils::Path::findConfig("hyprpaper");
return paths.first.value_or("");
}
CConfigManager::CConfigManager(const std::string& configPath) :
m_config(configPath.empty() ? getMainConfigPath().c_str() : configPath.c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = true}) {
m_currentConfigPath = configPath.empty() ? getMainConfigPath() : configPath;
}
bool CConfigManager::init() {
m_config.addConfigValue("splash", Hyprlang::INT{1});
m_config.addConfigValue("splash_offset", Hyprlang::INT{20});
m_config.addConfigValue("splash_opacity", Hyprlang::FLOAT{0.8});
m_config.addConfigValue("ipc", Hyprlang::INT{1});
m_config.addSpecialCategory("wallpaper", Hyprlang::SSpecialCategoryOptions{.key = "monitor"});
m_config.addSpecialConfigValue("wallpaper", "monitor", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("wallpaper", "path", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("wallpaper", "fit_mode", Hyprlang::STRING{"cover"});
m_config.addSpecialConfigValue("wallpaper", "timeout", Hyprlang::INT{0});
m_config.registerHandler(&handleSource, "source", Hyprlang::SHandlerOptions{});
m_config.commence();
auto result = m_config.parse();
if (result.error) {
g_logger->log(LOG_ERR, "Config has errors:\n{}", result.getError());
return false;
}
g_matcher->addStates(getSettings());
return true;
}
Hyprlang::CConfig* CConfigManager::hyprlang() {
return &m_config;
}
const std::string& CConfigManager::getCurrentConfigPath() const {
return m_currentConfigPath;
}
static std::expected resolvePath(const std::string_view& sv) {
std::error_code ec;
const auto CAN = std::filesystem::canonical(sv, ec);
if (ec)
return std::unexpected(std::format("invalid path: {}", ec.message()));
return CAN;
}
static std::expected getPath(const std::string_view& sv, const std::string& basePath = "") {
if (sv.empty())
return std::unexpected("empty path");
std::string path{sv};
if (sv[0] == '~') {
static auto HOME = getenv("HOME");
if (!HOME || HOME[0] == '\0')
return std::unexpected("home path but no $HOME");
path = std::string{HOME} + "/"s + std::string{sv.substr(1)};
} else if (!std::filesystem::path(sv).is_absolute() && !basePath.empty()) {
// Make relative paths relative to the base path's directory
auto baseDir = std::filesystem::path(basePath).parent_path();
path = (baseDir / sv).string();
}
return resolvePath(path);
}
static std::expected, std::string> getFullPath(const std::string& sv) {
if (sv.empty())
return std::unexpected("empty path");
static constexpr const size_t maxImagesCount{1024};
std::vector result;
const auto resolved = getPath(sv);
if (!resolved)
return std::unexpected(resolved.error());
const auto resolvedPath = resolved.value();
if (!std::filesystem::exists(resolvedPath))
return std::unexpected(std::format("File '{}' does not exist", resolvedPath));
if (std::filesystem::is_directory(resolvedPath))
for (const auto& entry : std::filesystem::directory_iterator(resolvedPath, std::filesystem::directory_options::skip_permission_denied)) {
if (entry.is_regular_file() && isImage(entry.path()))
result.push_back(entry.path());
if (result.size() >= maxImagesCount)
break;
}
else if (isImage(resolvedPath))
result.push_back(resolvedPath);
else
return std::unexpected(std::format("File '{}' is neither an image nor a directory", resolvedPath));
return result;
}
std::vector CConfigManager::getSettings() {
std::vector result;
auto keys = m_config.listKeysForSpecialCategory("wallpaper");
result.reserve(keys.size());
for (auto& key : keys) {
std::string monitor, fitMode, path;
int timeout;
try {
monitor = std::any_cast(m_config.getSpecialConfigValue("wallpaper", "monitor", key.c_str()));
fitMode = std::any_cast(m_config.getSpecialConfigValue("wallpaper", "fit_mode", key.c_str()));
path = std::any_cast(m_config.getSpecialConfigValue("wallpaper", "path", key.c_str()));
timeout = std::any_cast(m_config.getSpecialConfigValue("wallpaper", "timeout", key.c_str()));
} catch (...) {
g_logger->log(LOG_ERR, "Failed parsing wallpaper for key {}", key);
continue;
}
const auto RESOLVE_PATH = getFullPath(path);
if (!RESOLVE_PATH) {
g_logger->log(LOG_ERR, "Failed to resolve path {}: {}", path, RESOLVE_PATH.error());
continue;
}
if (RESOLVE_PATH.value().empty()) {
g_logger->log(LOG_ERR, "Provided path(s) '{}' does not contain a valid image", path);
continue;
}
result.emplace_back(SSetting{.monitor = std::move(monitor), .fitMode = std::move(fitMode), .paths = RESOLVE_PATH.value(), .timeout = timeout});
}
return result;
}
static Hyprlang::CParseResult handleSource(const char* COMMAND, const char* VALUE) {
Hyprlang::CParseResult result;
const auto value = Hyprutils::String::trim(VALUE);
if (value.empty()) {
result.setError("source= requires a file path");
return result;
}
const auto RESOLVED_PATH = getPath(value, g_config->getCurrentConfigPath());
if (!RESOLVED_PATH) {
result.setError(std::format("source= path error: {}", RESOLVED_PATH.error()).c_str());
return result;
}
const auto& PATH = RESOLVED_PATH.value();
g_logger->log(LOG_DEBUG, "source: including '{}'", PATH);
// Support glob patterns
glob_t globResult;
Hyprutils::Utils::CScopeGuard scopeGuard([&globResult]() { globfree(&globResult); });
int globStatus = glob(PATH.c_str(), GLOB_TILDE | GLOB_NOSORT, nullptr, &globResult);
if (globStatus == GLOB_NOMATCH) {
// No glob match - try as a literal path
std::error_code ec;
const auto exists = std::filesystem::exists(PATH, ec);
if (ec || !exists) {
result.setError(std::format("source file '{}' not found", PATH).c_str());
return result;
}
// Parse the single file
g_logger->log(LOG_DEBUG, "source: parsing file '{}'", PATH);
auto parseResult = g_config->hyprlang()->parseFile(PATH.c_str());
if (parseResult.error)
result.setError(std::format("error parsing '{}': {}", PATH, parseResult.getError()).c_str());
return result;
}
if (globStatus != 0) {
result.setError(std::format("glob error for pattern '{}'", PATH).c_str());
return result;
}
// Process all matched files
g_logger->log(LOG_DEBUG, "source: glob matched {} file(s)", globResult.gl_pathc);
for (size_t i = 0; i < globResult.gl_pathc; i++) {
const std::string matchedPath = globResult.gl_pathv[i];
std::error_code ec;
const auto isFile = std::filesystem::is_regular_file(matchedPath, ec);
if (ec || !isFile) {
g_logger->log(LOG_WARN, "source: skipping non-regular file '{}'", matchedPath);
continue;
}
g_logger->log(LOG_DEBUG, "source: parsing file '{}'", matchedPath);
auto parseResult = g_config->hyprlang()->parseFile(matchedPath.c_str());
if (parseResult.error)
g_logger->log(LOG_ERR, "error parsing '{}': {}", matchedPath, parseResult.getError());
}
return result;
}
hyprpaper-0.8.3/src/config/ConfigManager.hpp 0000664 0000000 0000000 00000001717 15136700726 0021016 0 ustar 00root root 0000000 0000000 #pragma once
#include "../helpers/Memory.hpp"
#include
#include
class CConfigManager {
public:
CConfigManager(const std::string& configPath);
~CConfigManager() = default;
CConfigManager(const CConfigManager&) = delete;
CConfigManager(CConfigManager&) = delete;
CConfigManager(CConfigManager&&) = delete;
struct SSetting {
std::string monitor, fitMode;
std::vector paths;
int timeout = 0;
uint32_t id = 0;
};
constexpr static const uint32_t SETTING_INVALID = 0;
bool init();
Hyprlang::CConfig* hyprlang();
std::vector getSettings();
const std::string& getCurrentConfigPath() const;
private:
Hyprlang::CConfig m_config;
std::string m_currentConfigPath;
};
inline UP g_config;
hyprpaper-0.8.3/src/config/WallpaperMatcher.cpp 0000664 0000000 0000000 00000007115 15136700726 0021542 0 ustar 00root root 0000000 0000000 #include "WallpaperMatcher.hpp"
#include
using namespace std::string_literals;
// Check if a monitor string represents a wildcard (matches all monitors)
// Empty string or "*" are both treated as wildcards.
// "*" is preferred since hyprlang's special category system doesn't properly
// return entries with empty string keys from listKeysForSpecialCategory().
static bool isWildcard(const std::string& monitor) {
return monitor.empty() || monitor == "*";
}
void CWallpaperMatcher::addState(CConfigManager::SSetting&& s) {
s.id = ++m_maxId;
std::erase_if(m_settings, [&s](const auto& e) { return e.monitor == s.monitor; });
m_settings.emplace_back(std::move(s));
recalcStates();
}
void CWallpaperMatcher::addStates(std::vector&& s) {
for (auto& ss : s) {
ss.id = ++m_maxId;
}
std::erase_if(m_settings, [&s](const auto& e) { return std::ranges::any_of(s, [&e](const auto& el) { return el.monitor == e.monitor; }); });
m_settings.append_range(std::move(s));
recalcStates();
}
void CWallpaperMatcher::registerOutput(const std::string_view& s, const std::string_view& desc) {
m_monitorNames.emplace_back(std::make_pair<>(s, desc));
recalcStates();
}
void CWallpaperMatcher::unregisterOutput(const std::string_view& s) {
std::erase_if(m_monitorNames, [&s](const auto& e) { return e.first == s; });
std::erase_if(m_monitorStates, [&s](const auto& e) { return e.name == s; });
recalcStates();
}
bool CWallpaperMatcher::outputExists(const std::string_view& s) {
return std::ranges::any_of(m_monitorNames, [&s](const auto& e) { return e.first == s || "desc:" + e.second == s; });
}
std::optional> CWallpaperMatcher::getSetting(const std::string_view& monName, const std::string_view& monDesc) {
for (const auto& m : m_monitorStates) {
if (m.name != monName)
continue;
for (const auto& s : m_settings) {
if (s.id != m.currentID)
continue;
return s;
}
return std::nullopt;
}
return std::nullopt;
}
std::optional> CWallpaperMatcher::matchSetting(const std::string_view& monName, const std::string_view& monDesc) {
// match explicit
for (const auto& s : m_settings) {
if (s.monitor != monName && !("desc:"s + std::string{monDesc}).starts_with(s.monitor))
continue;
return s;
}
// match wildcard (empty string or "*")
for (const auto& s : m_settings) {
if (isWildcard(s.monitor))
return s;
}
return std::nullopt;
}
CWallpaperMatcher::SMonitorState& CWallpaperMatcher::getState(const std::string_view& monName) {
for (auto& s : m_monitorStates) {
if (s.name == monName)
return s;
}
return m_monitorStates.emplace_back();
}
void CWallpaperMatcher::recalcStates() {
std::vector namesChanged;
for (const auto& [name, desc] : m_monitorNames) {
const auto STATE = matchSetting(name, desc);
auto& activeState = getState(name);
if (!STATE)
activeState = {.name = name, .desc = desc};
else {
activeState.name = name;
activeState.desc = desc;
if (activeState.currentID != STATE->get().id) {
activeState.currentID = STATE->get().id;
namesChanged.emplace_back(name);
}
}
}
for (const auto& n : namesChanged) {
m_events.monitorConfigChanged.emit(n);
}
}
hyprpaper-0.8.3/src/config/WallpaperMatcher.hpp 0000664 0000000 0000000 00000003753 15136700726 0021553 0 ustar 00root root 0000000 0000000 #pragma once
#include
#include
#include "ConfigManager.hpp"
#include
class CWallpaperMatcher {
public:
template
using rw = std::reference_wrapper;
CWallpaperMatcher() = default;
~CWallpaperMatcher() = default;
CWallpaperMatcher(const CWallpaperMatcher&) = delete;
CWallpaperMatcher(CWallpaperMatcher&) = delete;
CWallpaperMatcher(CWallpaperMatcher&&) = delete;
void addState(CConfigManager::SSetting&&);
void addStates(std::vector&&);
void registerOutput(const std::string_view&, const std::string_view&);
void unregisterOutput(const std::string_view&);
bool outputExists(const std::string_view&);
std::optional> getSetting(const std::string_view& monName, const std::string_view& monDesc);
struct {
Hyprutils::Signal::CSignalT monitorConfigChanged;
} m_events;
private:
void recalcStates();
std::optional> matchSetting(const std::string_view& monName, const std::string_view& monDesc);
std::vector m_settings;
struct SMonitorState {
std::string name, desc;
uint32_t currentID = CConfigManager::SETTING_INVALID;
};
std::vector> m_monitorNames;
std::vector m_monitorStates;
uint32_t m_maxId = 0;
SMonitorState& getState(const std::string_view& monName);
};
inline UP g_matcher = makeUnique();
hyprpaper-0.8.3/src/defines.hpp 0000664 0000000 0000000 00000002313 15136700726 0016457 0 ustar 00root root 0000000 0000000 #pragma once
#include
#include
#include "helpers/Logger.hpp"
// git stuff
#ifndef GIT_COMMIT_HASH
#define GIT_COMMIT_HASH "?"
#endif
#ifndef GIT_BRANCH
#define GIT_BRANCH "?"
#endif
#ifndef GIT_COMMIT_MESSAGE
#define GIT_COMMIT_MESSAGE "?"
#endif
#ifndef GIT_DIRTY
#define GIT_DIRTY "?"
#endif
#define ASSERT(expr) \
if (!(expr)) { \
g_logger->log(LOG_CRIT, "Failed assertion at line {} in {}: {} was false", __LINE__, \
([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find("/src/") + 1); })(), #expr); \
std::abort(); \
}
hyprpaper-0.8.3/src/helpers/ 0000775 0000000 0000000 00000000000 15136700726 0015774 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.3/src/helpers/GlobalState.hpp 0000664 0000000 0000000 00000000226 15136700726 0020706 0 ustar 00root root 0000000 0000000 #pragma once
#include "Memory.hpp"
struct SGlobalState {
bool verbose = false;
};
inline UP g_state = makeUnique(); hyprpaper-0.8.3/src/helpers/Logger.hpp 0000664 0000000 0000000 00000000567 15136700726 0017734 0 ustar 00root root 0000000 0000000 #pragma once
#include
#include "Memory.hpp"
#define LOG_DEBUG Hyprutils::CLI::LOG_DEBUG
#define LOG_ERR Hyprutils::CLI::LOG_ERR
#define LOG_WARN Hyprutils::CLI::LOG_WARN
#define LOG_TRACE Hyprutils::CLI::LOG_TRACE
#define LOG_CRIT Hyprutils::CLI::LOG_CRIT
inline UP g_logger = makeUnique();
hyprpaper-0.8.3/src/helpers/Memory.hpp 0000664 0000000 0000000 00000000435 15136700726 0017757 0 ustar 00root root 0000000 0000000 #pragma once
#include
#include
#include
using namespace Hyprutils::Memory;
#define SP CSharedPointer
#define WP CWeakPointer
#define UP CUniquePointer
#define ASP CAtomicSharedPointer hyprpaper-0.8.3/src/ipc/ 0000775 0000000 0000000 00000000000 15136700726 0015105 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.3/src/ipc/HyprlandSocket.cpp 0000664 0000000 0000000 00000004672 15136700726 0020554 0 ustar 00root root 0000000 0000000 #include "HyprlandSocket.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
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::expected HyprlandSocket::getFromSocket(const std::string& cmd) {
static const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
if (!HIS || HIS[0] == '\0')
return std::unexpected("HYPRLAND_INSTANCE_SIGNATURE empty: are we under hyprland?");
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)
return std::unexpected("couldn't open a socket (1)");
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(&serverAddress), SUN_LEN(&serverAddress)) < 0)
return std::unexpected(std::format("couldn't connect to the hyprland socket at {}", socketPath));
auto sizeWritten = write(SERVERSOCKET, cmd.c_str(), cmd.length());
if (sizeWritten < 0)
return std::unexpected("couldn't write (4)");
std::string reply = "";
char buffer[8192] = {0};
sizeWritten = read(SERVERSOCKET, buffer, 8192);
if (sizeWritten < 0) {
if (errno == EWOULDBLOCK)
return std::unexpected("Hyprland IPC didn't respond in time");
return std::unexpected("couldn't read (5)");
}
reply += std::string(buffer, sizeWritten);
while (sizeWritten == 8192) {
sizeWritten = read(SERVERSOCKET, buffer, 8192);
if (sizeWritten < 0) {
return std::unexpected("couldn't read (5)");
}
reply += std::string(buffer, sizeWritten);
}
close(SERVERSOCKET);
return reply;
} hyprpaper-0.8.3/src/ipc/HyprlandSocket.hpp 0000664 0000000 0000000 00000000274 15136700726 0020553 0 ustar 00root root 0000000 0000000 #pragma once
#include
#include
#include
namespace HyprlandSocket {
std::expected getFromSocket(const std::string& cmd);
}; hyprpaper-0.8.3/src/ipc/IPC.cpp 0000664 0000000 0000000 00000011215 15136700726 0016224 0 ustar 00root root 0000000 0000000 #include "IPC.hpp"
#include "../helpers/Logger.hpp"
#include "../config/WallpaperMatcher.hpp"
#include "../ui/UI.hpp"
#include
using namespace IPC;
using namespace std::string_literals;
constexpr const char* SOCKET_NAME = ".hyprpaper.sock";
static SP g_coreImpl;
CWallpaperObject::CWallpaperObject(SP&& obj) : m_object(std::move(obj)) {
m_object->setDestroy([this]() { std::erase_if(g_IPCSocket->m_wallpaperObjects, [this](const auto& e) { return e.get() == this; }); });
m_object->setOnDestroy([this]() { std::erase_if(g_IPCSocket->m_wallpaperObjects, [this](const auto& e) { return e.get() == this; }); });
m_object->setPath([this](const char* s) {
if (m_inert)
m_object->error(HYPRPAPER_CORE_WALLPAPER_ERRORS_INERT_WALLPAPER_OBJECT, "Object is inert");
m_path = s;
});
m_object->setFitMode([this](hyprpaperCoreWallpaperFitMode f) {
if (m_inert)
m_object->error(HYPRPAPER_CORE_WALLPAPER_ERRORS_INERT_WALLPAPER_OBJECT, "Object is inert");
if (f > HYPRPAPER_CORE_WALLPAPER_FIT_MODE_TILE)
m_object->error(HYPRPAPER_CORE_APPLYING_ERROR_UNKNOWN_ERROR, "Invalid fit mode");
m_fitMode = f;
});
m_object->setMonitorName([this](const char* s) {
if (m_inert)
m_object->error(HYPRPAPER_CORE_WALLPAPER_ERRORS_INERT_WALLPAPER_OBJECT, "Object is inert");
m_monitor = s;
});
m_object->setApply([this]() {
if (m_inert)
m_object->error(HYPRPAPER_CORE_WALLPAPER_ERRORS_INERT_WALLPAPER_OBJECT, "Object is inert");
apply();
});
}
static std::string fitModeToStr(hyprpaperCoreWallpaperFitMode m) {
switch (m) {
case HYPRPAPER_CORE_WALLPAPER_FIT_MODE_CONTAIN: return "contain";
case HYPRPAPER_CORE_WALLPAPER_FIT_MODE_COVER: return "cover";
case HYPRPAPER_CORE_WALLPAPER_FIT_MODE_TILE: return "tile";
case HYPRPAPER_CORE_WALLPAPER_FIT_MODE_STRETCH: return "fit";
default: return "cover";
}
}
void CWallpaperObject::apply() {
m_inert = true;
if (!m_monitor.empty() && !g_matcher->outputExists(m_monitor)) {
m_object->sendFailed(HYPRPAPER_CORE_APPLYING_ERROR_INVALID_MONITOR);
return;
}
if (m_path.empty()) {
m_object->sendFailed(HYPRPAPER_CORE_APPLYING_ERROR_INVALID_PATH);
return;
}
if (m_path[0] != '/') {
m_object->sendFailed(HYPRPAPER_CORE_APPLYING_ERROR_INVALID_PATH);
return;
}
std::error_code ec;
if (!std::filesystem::exists(m_path, ec) || ec) {
m_object->sendFailed(HYPRPAPER_CORE_APPLYING_ERROR_INVALID_PATH);
return;
}
g_matcher->addState(CConfigManager::SSetting{
.monitor = std::move(m_monitor),
.fitMode = fitModeToStr(m_fitMode),
.paths = std::vector{std::move(m_path)},
});
m_object->sendSuccess();
}
CSocket::CSocket() {
const auto RTDIR = getenv("XDG_RUNTIME_DIR");
if (!RTDIR)
return;
const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
if (!HIS) {
g_logger->log(LOG_WARN, "not running under hyprland, IPC will be disabled.");
return;
}
m_socketPath = RTDIR + "/hypr/"s + HIS + "/"s + SOCKET_NAME;
std::error_code ec;
std::filesystem::remove(m_socketPath, ec);
m_socket = Hyprwire::IServerSocket::open(m_socketPath);
if (!m_socket)
return;
g_coreImpl = makeShared(1, [this](SP obj) {
auto manager = m_managers.emplace_back(makeShared(std::move(obj)));
manager->setDestroy([this, weak = WP{manager}] { std::erase(m_managers, weak); });
manager->setOnDestroy([this, weak = WP{manager}] { std::erase(m_managers, weak); });
manager->setGetWallpaperObject([this, weak = WP{manager}](uint32_t id) {
if (!weak)
return;
m_wallpaperObjects.emplace_back(makeShared(
makeShared(m_socket->createObject(weak->getObject()->client(), weak->getObject(), "hyprpaper_wallpaper", id))));
});
});
m_socket->addImplementation(g_coreImpl);
g_ui->backend()->addFd(m_socket->extractLoopFD(), [this]() { m_socket->dispatchEvents(); });
}
void CSocket::onNewDisplay(const std::string& sv) {
for (const auto& m : m_managers) {
m->sendAddMonitor(sv.c_str());
}
}
void CSocket::onRemovedDisplay(const std::string& sv) {
for (const auto& m : m_managers) {
m->sendRemoveMonitor(sv.c_str());
}
}
hyprpaper-0.8.3/src/ipc/IPC.hpp 0000664 0000000 0000000 00000002270 15136700726 0016232 0 ustar 00root root 0000000 0000000 #pragma once
#include "../helpers/Memory.hpp"
#include
#include
namespace IPC {
class CWallpaperObject {
public:
CWallpaperObject(SP&& obj);
~CWallpaperObject() = default;
private:
void apply();
SP m_object;
std::string m_path;
hyprpaperCoreWallpaperFitMode m_fitMode = HYPRPAPER_CORE_WALLPAPER_FIT_MODE_COVER;
std::string m_monitor;
bool m_inert = false;
};
class CSocket {
public:
CSocket();
~CSocket() = default;
void onNewDisplay(const std::string& sv);
void onRemovedDisplay(const std::string& sv);
private:
SP m_socket;
std::string m_socketPath = "";
std::vector> m_managers;
std::vector> m_wallpaperObjects;
friend class CWallpaperObject;
};
inline UP g_IPCSocket;
}; hyprpaper-0.8.3/src/main.cpp 0000664 0000000 0000000 00000003042 15136700726 0015761 0 ustar 00root root 0000000 0000000 #include "defines.hpp"
#include "helpers/Logger.hpp"
#include "helpers/GlobalState.hpp"
#include "ui/UI.hpp"
#include "config/ConfigManager.hpp"
#include
using namespace Hyprutils::CLI;
int main(int argc, const char** argv, const char** envp) {
CArgumentParser parser({argv, argc});
ASSERT(parser.registerStringOption("config", "c", "Set a custom config path"));
ASSERT(parser.registerBoolOption("verbose", "", "Enable more logging"));
ASSERT(parser.registerBoolOption("version", "v", "Show hyprpaper's version"));
ASSERT(parser.registerBoolOption("help", "h", "Show the help menu"));
if (const auto ret = parser.parse(); !ret) {
g_logger->log(LOG_ERR, "Failed parsing arguments: {}", ret.error());
return 1;
}
if (parser.getBool("help").value_or(false)) {
std::println("{}", parser.getDescription(std::format("hyprpaper v{}", HYPRPAPER_VERSION)));
return 0;
}
if (parser.getBool("version").value_or(false)) {
std::println("hyprpaper v{}", HYPRPAPER_VERSION);
return 0;
}
if (parser.getBool("verbose").value_or(false)) {
g_logger->setLogLevel(LOG_TRACE);
g_state->verbose = true;
}
g_logger->log(LOG_DEBUG, "Welcome to hyprpaper!\nbuilt from commit {} ({})", GIT_COMMIT_HASH, GIT_COMMIT_MESSAGE);
g_config = makeUnique(std::string{parser.getString("config").value_or("")});
if (!g_config->init())
return 1;
g_ui = makeUnique();
g_ui->run();
return 0;
}
hyprpaper-0.8.3/src/ui/ 0000775 0000000 0000000 00000000000 15136700726 0014747 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.3/src/ui/UI.cpp 0000664 0000000 0000000 00000020324 15136700726 0015771 0 ustar 00root root 0000000 0000000 #include "UI.hpp"
#include "../defines.hpp"
#include "../helpers/Logger.hpp"
#include "../helpers/GlobalState.hpp"
#include "../ipc/HyprlandSocket.hpp"
#include "../ipc/IPC.hpp"
#include "../config/WallpaperMatcher.hpp"
#include
#include
CUI::CUI() = default;
CUI::~CUI() {
m_targets.clear();
}
static std::string_view pruneDesc(const std::string_view& sv) {
if (sv.contains('('))
return Hyprutils::String::trim(sv.substr(0, sv.find_last_of('(')));
return sv;
}
class CWallpaperTarget::CImagesData {
public:
CImagesData(Hyprtoolkit::eImageFitMode fitMode, const std::vector& images, const int timeout = 0) :
fitMode(fitMode), images(images), timeout(timeout > 0 ? timeout : 30) {}
const Hyprtoolkit::eImageFitMode fitMode;
const std::vector images;
const int timeout;
std::string nextImage() {
current = (current + 1) % images.size();
return images[current];
}
private:
size_t current = 0;
};
CWallpaperTarget::CWallpaperTarget(SP backend, SP output, const std::vector& path, Hyprtoolkit::eImageFitMode fitMode,
const int timeout) : m_monitorName(output->port()), m_backend(backend) {
static const auto SPLASH_REPLY = HyprlandSocket::getFromSocket("/splash");
static const auto PENABLESPLASH = Hyprlang::CSimpleConfigValue(g_config->hyprlang(), "splash");
static const auto PSPLASHOFFSET = Hyprlang::CSimpleConfigValue(g_config->hyprlang(), "splash_offset");
static const auto PSPLASHALPHA = Hyprlang::CSimpleConfigValue(g_config->hyprlang(), "splash_opacity");
ASSERT(path.size() > 0);
m_window = Hyprtoolkit::CWindowBuilder::begin()
->type(Hyprtoolkit::HT_WINDOW_LAYER)
->prefferedOutput(output)
->anchor(0xF)
->layer(0)
->preferredSize({0, 0})
->exclusiveZone(-1)
->appClass("hyprpaper")
->commence();
m_bg = Hyprtoolkit::CRectangleBuilder::begin()
->size({Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, {1, 1}})
->color([] { return Hyprtoolkit::CHyprColor{0xFF000000}; })
->commence();
m_null = Hyprtoolkit::CNullBuilder::begin()->size({Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, {1, 1}})->commence();
m_image = Hyprtoolkit::CImageBuilder::begin()
->path(std::string{path.front()})
->size({Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}})
->sync(true)
->fitMode(fitMode)
->commence();
m_image->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE);
m_image->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_CENTER, true);
if (path.size() > 1) {
m_imagesData = makeUnique(fitMode, path, timeout);
m_timer =
m_backend->addTimer(std::chrono::milliseconds(std::chrono::seconds(m_imagesData->timeout)), [this](ASP self, void*) { onRepeatTimer(); }, nullptr);
}
m_window->m_rootElement->addChild(m_bg);
m_window->m_rootElement->addChild(m_null);
m_null->addChild(m_image);
if (!SPLASH_REPLY)
g_logger->log(LOG_ERR, "Can't get splash: {}", SPLASH_REPLY.error());
if (SPLASH_REPLY && *PENABLESPLASH) {
m_splash = Hyprtoolkit::CTextBuilder::begin()
->text(std::string{SPLASH_REPLY.value()})
->fontSize({Hyprtoolkit::CFontSize::HT_FONT_TEXT, 1.15F})
->color([] { return g_ui->backend()->getPalette()->m_colors.text; })
->a(*PSPLASHALPHA)
->commence();
m_splash->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE);
m_splash->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_HCENTER, true);
m_splash->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_BOTTOM, true);
m_splash->setAbsolutePosition({0.F, sc(-*PSPLASHOFFSET)});
m_null->addChild(m_splash);
}
m_window->open();
}
CWallpaperTarget::~CWallpaperTarget() {
if (m_timer && !m_timer->passed())
m_timer->cancel();
}
void CWallpaperTarget::onRepeatTimer() {
ASSERT(m_imagesData);
m_image->rebuild()
->path(m_imagesData->nextImage())
->size({Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}})
->sync(true)
->fitMode(m_imagesData->fitMode)
->commence();
m_timer =
m_backend->addTimer(std::chrono::milliseconds(std::chrono::seconds(m_imagesData->timeout)), [this](ASP self, void*) { onRepeatTimer(); }, nullptr);
}
void CUI::registerOutput(const SP& mon) {
g_matcher->registerOutput(mon->port(), pruneDesc(mon->desc()));
if (IPC::g_IPCSocket)
IPC::g_IPCSocket->onNewDisplay(mon->port());
mon->m_events.removed.listenStatic([this, m = WP{mon}] {
g_matcher->unregisterOutput(m->port());
if (IPC::g_IPCSocket)
IPC::g_IPCSocket->onRemovedDisplay(m->port());
std::erase_if(m_targets, [&m](const auto& e) { return e->m_monitorName == m->port(); });
});
}
bool CUI::run() {
static const auto PENABLEIPC = Hyprlang::CSimpleConfigValue(g_config->hyprlang(), "ipc");
//
Hyprtoolkit::IBackend::SBackendCreationData data;
data.pLogConnection = makeShared(*g_logger);
data.pLogConnection->setName("hyprtoolkit");
data.pLogConnection->setLogLevel(g_state->verbose ? LOG_TRACE : LOG_ERR);
m_backend = Hyprtoolkit::IBackend::createWithData(data);
if (!m_backend)
return false;
if (*PENABLEIPC)
IPC::g_IPCSocket = makeUnique();
const auto MONITORS = m_backend->getOutputs();
for (const auto& m : MONITORS) {
registerOutput(m);
}
m_listeners.newMon = m_backend->m_events.outputAdded.listen([this](SP mon) { registerOutput(mon); });
g_logger->log(LOG_DEBUG, "Found {} output(s)", MONITORS.size());
// load the config now, then bind
for (const auto& m : MONITORS) {
targetChanged(m);
}
m_listeners.targetChanged = g_matcher->m_events.monitorConfigChanged.listen([this](const std::string_view& m) { targetChanged(m); });
m_backend->enterLoop();
return true;
}
SP CUI::backend() {
return m_backend;
}
static Hyprtoolkit::eImageFitMode toFitMode(const std::string_view& sv) {
if (sv.starts_with("contain"))
return Hyprtoolkit::IMAGE_FIT_MODE_CONTAIN;
if (sv.starts_with("cover"))
return Hyprtoolkit::IMAGE_FIT_MODE_COVER;
if (sv.starts_with("tile"))
return Hyprtoolkit::IMAGE_FIT_MODE_TILE;
if (sv.starts_with("fill"))
return Hyprtoolkit::IMAGE_FIT_MODE_STRETCH;
return Hyprtoolkit::IMAGE_FIT_MODE_COVER;
}
void CUI::targetChanged(const std::string_view& monName) {
const auto MONITORS = m_backend->getOutputs();
SP monitor;
for (const auto& m : MONITORS) {
if (m->port() != monName)
continue;
monitor = m;
}
if (!monitor) {
g_logger->log(LOG_ERR, "targetChanged but {} has no output?", monName);
return;
}
targetChanged(monitor);
}
void CUI::targetChanged(const SP& mon) {
const auto TARGET = g_matcher->getSetting(mon->port(), pruneDesc(mon->desc()));
if (!TARGET) {
g_logger->log(LOG_DEBUG, "Monitor {} has no target: no wp will be created", mon->port());
return;
}
std::erase_if(m_targets, [&mon](const auto& e) { return e->m_monitorName == mon->port(); });
m_targets.emplace_back(makeShared(m_backend, mon, TARGET->get().paths, toFitMode(TARGET->get().fitMode), TARGET->get().timeout));
}
hyprpaper-0.8.3/src/ui/UI.hpp 0000664 0000000 0000000 00000003762 15136700726 0016005 0 ustar 00root root 0000000 0000000 #pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "../helpers/Memory.hpp"
class CWallpaperTarget {
public:
CWallpaperTarget(SP backend, SP output, const std::vector& path,
Hyprtoolkit::eImageFitMode fitMode = Hyprtoolkit::IMAGE_FIT_MODE_COVER, const int timeout = 0);
~CWallpaperTarget();
CWallpaperTarget(const CWallpaperTarget&) = delete;
CWallpaperTarget(CWallpaperTarget&) = delete;
CWallpaperTarget(CWallpaperTarget&&) = delete;
std::string m_monitorName;
private:
void onRepeatTimer();
class CImagesData;
UP m_imagesData;
ASP m_timer;
SP m_backend;
SP m_window;
SP m_null;
SP m_bg;
SP m_image;
SP m_splash;
};
class CUI {
public:
CUI();
~CUI();
bool run();
SP backend();
private:
void targetChanged(const SP& mon);
void targetChanged(const std::string_view& monName);
void registerOutput(const SP& mon);
SP m_backend;
std::vector> m_targets;
struct {
Hyprutils::Signal::CHyprSignalListener targetChanged;
Hyprutils::Signal::CHyprSignalListener newMon;
} m_listeners;
};
inline UP g_ui;
hyprpaper-0.8.3/systemd/ 0000775 0000000 0000000 00000000000 15136700726 0015233 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.3/systemd/hyprpaper.service.in 0000664 0000000 0000000 00000000652 15136700726 0021237 0 ustar 00root root 0000000 0000000 [Unit]
Description=Fast, IPC-controlled wallpaper utility for Hyprland.
Documentation=https://wiki.hyprland.org/Hypr-Ecosystem/hyprpaper/
PartOf=graphical-session.target
Requires=graphical-session.target
After=graphical-session.target
ConditionEnvironment=WAYLAND_DISPLAY
[Service]
Type=simple
ExecStart=@CMAKE_INSTALL_PREFIX@/bin/hyprpaper
Slice=session.slice
Restart=on-failure
[Install]
WantedBy=graphical-session.target