pax_global_header 0000666 0000000 0000000 00000000064 15174464141 0014521 g ustar 00root root 0000000 0000000 52 comment=20fc0fa6c2056c388a4cd69cb394a9f989dd27c0
hyprpaper-0.8.4/ 0000775 0000000 0000000 00000000000 15174464141 0013544 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.4/.clang-format 0000664 0000000 0000000 00000003416 15174464141 0016123 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.4/.github/ 0000775 0000000 0000000 00000000000 15174464141 0015104 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.4/.github/workflows/ 0000775 0000000 0000000 00000000000 15174464141 0017141 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.4/.github/workflows/nix-build.yaml 0000664 0000000 0000000 00000000526 15174464141 0021723 0 ustar 00root root 0000000 0000000 name: Build
on: [push, pull_request, workflow_dispatch]
jobs:
nix:
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)
uses: hyprwm/actions/.github/workflows/nix.yml@main
secrets: inherit
with:
submodules: recursive
command: nix build --print-build-logs --accept-flake-config
hyprpaper-0.8.4/.gitignore 0000664 0000000 0000000 00000000542 15174464141 0015535 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.4/CMakeLists.txt 0000664 0000000 0000000 00000012731 15174464141 0016310 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.4/LICENSE 0000664 0000000 0000000 00000002764 15174464141 0014562 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.4/README.md 0000664 0000000 0000000 00000002101 15174464141 0015015 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.4/VERSION 0000664 0000000 0000000 00000000006 15174464141 0014610 0 ustar 00root root 0000000 0000000 0.8.4
hyprpaper-0.8.4/flake.lock 0000664 0000000 0000000 00000013145 15174464141 0015504 0 ustar 00root root 0000000 0000000 {
"nodes": {
"aquamarine": {
"inputs": {
"hyprutils": [
"hyprutils"
],
"hyprwayland-scanner": [
"hyprwayland-scanner"
],
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1772460678,
"narHash": "sha256-NYaWs8fYJ38IgFld0hGSdT2LEVhrgO8SiRReBjIH7YY=",
"owner": "hyprwm",
"repo": "aquamarine",
"rev": "5d2cb726b16ee349df443f84b64cff53221b6983",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "aquamarine",
"type": "github"
}
},
"hyprgraphics": {
"inputs": {
"hyprutils": [
"hyprutils"
],
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1772461523,
"narHash": "sha256-mI6A51do+hEUzeJKk9YSWfVHdI/SEEIBi2tp5Whq5mI=",
"owner": "hyprwm",
"repo": "hyprgraphics",
"rev": "7d63c04b4a2dd5e59ef943b4b143f46e713df804",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprgraphics",
"type": "github"
}
},
"hyprlang": {
"inputs": {
"hyprutils": [
"hyprutils"
],
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1772459629,
"narHash": "sha256-/iwvNUYShmmnwmz/czEUh6+0eF5vCMv0xtDW0STPIuM=",
"owner": "hyprwm",
"repo": "hyprlang",
"rev": "7615ee388de18239a4ab1400946f3d0e498a8186",
"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": 1772462885,
"narHash": "sha256-5pHXrQK9zasMnIo6yME6EOXmWGFMSnCITcfKshhKJ9I=",
"owner": "hyprwm",
"repo": "hyprtoolkit",
"rev": "9af245a69fa6b286b88ddfc340afd288e00a6998",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprtoolkit",
"type": "github"
}
},
"hyprutils": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1772459870,
"narHash": "sha256-xxkK2Cvqxpf/4UGcJ/TyCwrvmiNWsKsJfFzHMp2bxis=",
"owner": "hyprwm",
"repo": "hyprutils",
"rev": "e63f3a79334dec49f8eb1691f66f18115df04085",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprutils",
"type": "github"
}
},
"hyprwayland-scanner": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1772459835,
"narHash": "sha256-978jRz/y/9TKmZb/qD4lEYHCQGHpEXGqy+8X2lFZsak=",
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"rev": "0a692d4a645165eebd65f109146b8861e3a925e7",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"type": "github"
}
},
"hyprwire": {
"inputs": {
"hyprutils": [
"hyprutils"
],
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1772463520,
"narHash": "sha256-GIjASzYnV4fK19HnyJKmHyqyxHxIpjusK9foEA4Yo+4=",
"owner": "hyprwm",
"repo": "hyprwire",
"rev": "4e1933ae5602b350c5b6633f5c932549c9b8aca2",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprwire",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1772433332,
"narHash": "sha256-izhTDFKsg6KeVBxJS9EblGeQ8y+O8eCa6RcW874vxEc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "cf59864ef8aa2e178cccedbe2c178185b0365705",
"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.4/flake.nix 0000664 0000000 0000000 00000005241 15174464141 0015350 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-with-deps ];
}
);
in
{
overlays = import ./nix/overlays.nix { inherit inputs lib self; };
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}.nixfmt-tree);
};
}
hyprpaper-0.8.4/hw-protocols/ 0000775 0000000 0000000 00000000000 15174464141 0016204 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.4/hw-protocols/hyprpaper_core.xml 0000664 0000000 0000000 00000014465 15174464141 0021762 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.4/nix/ 0000775 0000000 0000000 00000000000 15174464141 0014342 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.4/nix/default.nix 0000664 0000000 0000000 00000002710 15174464141 0016506 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,
libXdmcp,
libwebp,
pango,
pcre,
pcre2,
util-linux,
wayland,
wayland-protocols,
wayland-scanner,
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
libXdmcp
pango
pcre
pcre2
wayland
wayland-protocols
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.4/nix/overlays.nix 0000664 0000000 0000000 00000002073 15174464141 0016730 0 ustar 00root root 0000000 0000000 {
lib,
inputs,
self,
...
}:
let
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
{
default = self.overlays.hyprpaper;
hyprpaper-with-deps = 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
self.overlays.hyprpaper
];
hyprpaper = final: prev: rec {
hyprpaper = final.callPackage ./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; };
};
}
hyprpaper-0.8.4/protocols/ 0000775 0000000 0000000 00000000000 15174464141 0015570 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.4/protocols/wlr-layer-shell-unstable-v1.xml 0000664 0000000 0000000 00000032074 15174464141 0023502 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.4/src/ 0000775 0000000 0000000 00000000000 15174464141 0014333 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.4/src/config/ 0000775 0000000 0000000 00000000000 15174464141 0015600 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.4/src/config/ConfigManager.cpp 0000664 0000000 0000000 00000025326 15174464141 0021014 0 ustar 00root root 0000000 0000000 #include "ConfigManager.hpp"
#include
#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.addSpecialConfigValue("wallpaper", "order", Hyprlang::STRING{"default"});
m_config.addSpecialConfigValue("wallpaper", "recursive", 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, const bool recursive) {
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)) {
auto processEntry = [&result](const auto& entry) {
if (entry.is_regular_file() && isImage(entry.path()))
result.push_back(entry.path());
};
if (recursive) {
for (const auto& entry :
std::filesystem::recursive_directory_iterator(resolvedPath, std::filesystem::directory_options::skip_permission_denied)) {
processEntry(entry);
if (result.size() >= maxImagesCount)
break;
}
} else {
for (const auto& entry : std::filesystem::directory_iterator(resolvedPath, std::filesystem::directory_options::skip_permission_denied)) {
processEntry(entry);
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, order;
int timeout, recursive;
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()));
order = std::any_cast(m_config.getSpecialConfigValue("wallpaper", "order", key.c_str()));
recursive = std::any_cast(m_config.getSpecialConfigValue("wallpaper", "recursive", key.c_str()));
} catch (...) {
g_logger->log(LOG_ERR, "Failed parsing wallpaper for key {}", key);
continue;
}
const auto RESOLVE_PATH = getFullPath(path, recursive != 0);
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;
}
auto resolvedPaths = RESOLVE_PATH.value();
if (resolvedPaths.size() > 1) {
if (order != "default" && order != "random" && order != "random-shuffle") {
g_logger->log(LOG_WARN, "Invalid order value '{}', falling back to default", order);
order = "default";
}
if (order == "random" || order == "random-shuffle") {
std::random_device rd;
std::mt19937 g(rd());
std::shuffle(resolvedPaths.begin(), resolvedPaths.end(), g);
}
}
result.emplace_back(SSetting{.monitor = std::move(monitor), .fitMode = std::move(fitMode), .paths = std::move(resolvedPaths), .order = std::move(order), .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.4/src/config/ConfigManager.hpp 0000664 0000000 0000000 00000002005 15174464141 0021006 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;
std::string order = "default";
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.4/src/config/WallpaperMatcher.cpp 0000664 0000000 0000000 00000007264 15174464141 0021550 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 (isWildcard(s.monitor))
continue;
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, .currentID = CConfigManager::SETTING_INVALID};
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.4/src/config/WallpaperMatcher.hpp 0000664 0000000 0000000 00000003753 15174464141 0021554 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.4/src/defines.hpp 0000664 0000000 0000000 00000002313 15174464141 0016460 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.4/src/helpers/ 0000775 0000000 0000000 00000000000 15174464141 0015775 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.4/src/helpers/GlobalState.hpp 0000664 0000000 0000000 00000000226 15174464141 0020707 0 ustar 00root root 0000000 0000000 #pragma once
#include "Memory.hpp"
struct SGlobalState {
bool verbose = false;
};
inline UP g_state = makeUnique(); hyprpaper-0.8.4/src/helpers/Logger.hpp 0000664 0000000 0000000 00000000567 15174464141 0017735 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.4/src/helpers/Memory.hpp 0000664 0000000 0000000 00000000435 15174464141 0017760 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.4/src/ipc/ 0000775 0000000 0000000 00000000000 15174464141 0015106 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.4/src/ipc/HyprlandSocket.cpp 0000664 0000000 0000000 00000004672 15174464141 0020555 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.4/src/ipc/HyprlandSocket.hpp 0000664 0000000 0000000 00000000274 15174464141 0020554 0 ustar 00root root 0000000 0000000 #pragma once
#include
#include
#include
namespace HyprlandSocket {
std::expected getFromSocket(const std::string& cmd);
}; hyprpaper-0.8.4/src/ipc/IPC.cpp 0000664 0000000 0000000 00000012646 15174464141 0016236 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";
constexpr const size_t HP_PROTO_VERSION = 2;
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(HP_PROTO_VERSION, [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))));
});
manager->setGetStatusObject([this, weak = WP{manager}](uint32_t id) {
if (!weak)
return;
auto x =
m_statusObjects.emplace_back(makeShared(m_socket->createObject(weak->getObject()->client(), weak->getObject(), "hyprpaper_status", id)));
for (const auto& m : g_ui->targets()) {
x->sendActiveWallpaper(m->m_monitorName.c_str(), m->m_lastPath.c_str());
}
});
});
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());
}
}
void CSocket::onWallpaperChanged(const std::string& mon, const std::string& path) {
for (const auto& so : m_statusObjects) {
so->sendActiveWallpaper(mon.c_str(), path.c_str());
}
}
hyprpaper-0.8.4/src/ipc/IPC.hpp 0000664 0000000 0000000 00000002520 15174464141 0016231 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);
void onWallpaperChanged(const std::string& mon, const std::string& path);
private:
SP m_socket;
std::string m_socketPath = "";
std::vector> m_managers;
std::vector> m_wallpaperObjects;
std::vector> m_statusObjects;
friend class CWallpaperObject;
};
inline UP g_IPCSocket;
}; hyprpaper-0.8.4/src/main.cpp 0000664 0000000 0000000 00000003042 15174464141 0015762 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.4/src/ui/ 0000775 0000000 0000000 00000000000 15174464141 0014750 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.4/src/ui/UI.cpp 0000664 0000000 0000000 00000021656 15174464141 0016003 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
#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, std::vector images, const int timeout = 0, std::string order = "default") :
fitMode(fitMode), images(std::move(images)), order(std::move(order)), timeout(timeout > 0 ? timeout : 30) {}
const Hyprtoolkit::eImageFitMode fitMode;
std::vector images;
const std::string order;
const int timeout;
std::string nextImage() {
if (order == "random-shuffle" && current + 1 >= images.size()) {
std::random_device rd;
std::mt19937 g(rd());
std::shuffle(images.begin(), images.end(), g);
current = 0;
return images[current];
}
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, const std::string& order) : 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_lastPath = path.front();
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, std::vector(path), timeout, order);
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_lastPath = m_imagesData->nextImage();
m_image->rebuild()
->path(std::string{m_lastPath})
->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);
IPC::g_IPCSocket->onWallpaperChanged(m_monitorName, m_lastPath);
}
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, TARGET->get().order));
}
const std::vector>& CUI::targets() {
return m_targets;
}
hyprpaper-0.8.4/src/ui/UI.hpp 0000664 0000000 0000000 00000004172 15174464141 0016002 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, const std::string& order = "default");
~CWallpaperTarget();
CWallpaperTarget(const CWallpaperTarget&) = delete;
CWallpaperTarget(CWallpaperTarget&) = delete;
CWallpaperTarget(CWallpaperTarget&&) = delete;
std::string m_monitorName, m_lastPath;
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();
const std::vector>& targets();
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.4/systemd/ 0000775 0000000 0000000 00000000000 15174464141 0015234 5 ustar 00root root 0000000 0000000 hyprpaper-0.8.4/systemd/hyprpaper.service.in 0000664 0000000 0000000 00000000652 15174464141 0021240 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