Compare commits

...

100 commits
v0.4.0 ... main

Author SHA1 Message Date
Fazzi
4d775beee5 flake.lock: Update
Flake lock file updates:

• Updated input 'aquamarine':
    'github:hyprwm/aquamarine/a43bedcceced5c21ad36578ed823e6099af78214?narHash=sha256-AjcMlM3UoavFoLzr0YrcvsIxALShjyvwe%2Bo7ikibpCM%3D' (2025-12-02)
  → 'github:hyprwm/aquamarine/d83c97f8f5c0aae553c1489c7d9eff3eadcadace?narHash=sha256-%2Bhn8v9jkkLP9m%2Bo0Nm5SiEq10W0iWDSotH2XfjU45fA%3D' (2025-12-16)
• Updated input 'hyprtoolkit':
    'github:hyprwm/hyprtoolkit/a07c89acce89709bed02160136a612e70021cd91?narHash=sha256-IAGHOvvuCnJ6zWB3efxboJ/HUfSaXJ6pzrMwOMS2lJY%3D' (2025-12-03)
  → 'github:hyprwm/hyprtoolkit/0a5d2c25d018112434e802212a1ad57ca1e24819?narHash=sha256-C0tMHhVMEN2XlgMVeuJSbY64h9UhR2AKk5Hxxlxx6cA%3D' (2025-12-17)
• Updated input 'hyprutils':
    'github:hyprwm/hyprutils/2f2413801beee37303913fc3c964bbe92252a963?narHash=sha256-vSyiKCzSY48kA3v39GFu6qgRfigjKCU/9k1KTK475gg%3D' (2025-12-02)
  → 'github:hyprwm/hyprutils/1c527b30feb7bed959ac07ae034a6105e6b65fd3?narHash=sha256-Ji2ty5d6yuToq59SZfpG0T5B5SkF3UiHoDl8VMyQp14%3D' (2025-12-17)
• Updated input 'hyprwire':
    'github:hyprwm/hyprwire/3f1997d6aeced318fb141810fded2255da811293?narHash=sha256-9UcCdwe7vPgEcJJ64JseBQL0ZJZoxp/2iFuvfRI%2B9zk%3D' (2025-12-03)
  → 'github:hyprwm/hyprwire/b8ca85082fd5c3cdd3d11027492cd0332b517078?narHash=sha256-jTJQy1m2XkcZJABajYVP249fCWPl3GbLe3Z8KiQmZqg%3D' (2025-12-14)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/2d293cbfa5a793b4c50d17c05ef9e385b90edf6c?narHash=sha256-pp3uT4hHijIC8JUK5MEqeAWmParJrgBVzHLNfJDZxg4%3D' (2025-11-30)
  → 'github:NixOS/nixpkgs/1306659b587dc277866c7b69eb97e5f07864d8c4?narHash=sha256-KJ2wa/BLSrTqDjbfyNx70ov/HdgNBCBBSQP3BIzKnv4%3D' (2025-12-15)
2025-12-18 12:36:36 +02:00
b431a94cfb
config: fix default value for fit_mode to match wiki 2025-12-17 15:14:43 +02:00
07eb33e65d
ui: add namespace 2025-12-05 18:57:16 +00:00
Vaxry
1d8df14fce
core: migrate to hyprtoolkit (#288)
---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2025-12-04 17:58:54 +00:00
1733e0025b
version: bump to 0.7.6 2025-10-10 19:20:48 +01:00
tht2005
bcb1ffa322
cmake: Strip leading whitespace from git commit message (#271) 2025-08-06 14:00:42 +02:00
Blake-sama
86f6217029
cmake: Escape quotes in commit names (#269) 2025-07-27 23:05:07 +02:00
Blake-sama
6502c87e9c
config: Added "source=" capability to Hyprpaper .conf file (#267) 2025-07-27 18:32:49 +02:00
a88e0e066e
flake.lock: update 2025-06-22 22:46:17 +03:00
7d63e15c09
CI/Nix: add cache-nix-action
Use nixbuild/nix-quick-install-action which pairs well with
nix-community/cache-nix-action.

Should help with build times by reducing the number of packages needing
to be re-downloaded on each run.

Parameters are taken from https://github.com/nix-community/cache-nix-action
and may be tweaked later.
2025-06-20 01:25:44 +03:00
Matt Vykol
81dc1fe4f0
config: Return empty string instead of throwing if unable to find default config (#253) 2025-06-10 08:19:18 +01:00
Friday
79e0992927
nix: update flake.lock (#252)
bumps hyprgraphics and nixpkgs
2025-06-08 15:58:36 +01:00
Friday
c6981ac490 nix: use gcc15
also updated dependencies
2025-06-06 01:26:58 +03:00
Amadej Kastelic
99213a1854 flake.lock: update 2025-05-09 00:14:03 +03:00
753ffa7fe9
version: bump to 0.7.5 2025-05-06 20:18:13 +01:00
Biniam Bekele
05337a4595
CMake: require wayland-protocols>=1.35 (#243)
tablet-v2 was moved to stable in 1.35. Hyprpaper will fail to build if a
earlier version is used.
2025-03-20 16:52:40 +02:00
Constantin Piber
6f4ba43163
CMake: move systemd service install (#240) 2025-02-28 19:56:02 +02:00
7e18ebc843 core: add mallopt to modify trim threshold 2025-02-09 17:40:50 +00:00
f827dc3197
flake.lock: update 2025-01-29 22:41:18 +02:00
7efb4a0346 version: bump to 0.7.4 2025-01-23 15:12:54 +00:00
4d2e2b7f07
flake.lock: update 2025-01-23 14:28:44 +02:00
solopasha
251e8e2593
README: Remove unused deps (#231)
Seems unused after the move to hyprgraphics
2025-01-06 16:33:44 +00:00
505e447b6c core: update for hw-s 0.4.4 2024-12-29 18:35:40 +01:00
2f305d5f48
flake.lock: update 2024-12-23 00:24:58 +02:00
caffeine
f15e678507
core: conform to output transforms (#224) 2024-12-19 20:58:28 +01:00
eb9db3b815 version: bump to 0.7.3 2024-12-15 21:58:46 +00:00
0e38c982d7 core: fixup execAndGet not running correctly 2024-12-15 21:58:26 +00:00
4d5b68b7ad core: avoid the use of pop_back on empty string
fixes #222
2024-12-15 21:58:11 +00:00
b17d32fdd2 logging: fix some missed logs 2024-12-15 21:55:41 +00:00
5b763f1618 version: bump to 0.7.2 2024-12-14 15:10:30 +00:00
Vaxry
85e850bca3
Core: modernize internals (#219)
* flake.nix: use gcc14Stdenv, update

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2024-12-14 16:09:40 +01:00
a3ceb20095 README: add hyprgraphics dep 2024-11-28 15:58:20 +00:00
Vaxry
b3ee62fe4a
Core: Move to hyprgraphics for image parsing (#216)
* move to hyprgraphics

* Nix: add hyprgraphics

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2024-11-28 15:56:01 +00:00
11d91446f1 cmake: drop # from commit messages 2024-11-28 15:40:37 +00:00
Adrià
0b5e350011
core: use C++ streams to load Jpeg and Webp (#214) 2024-11-22 13:18:04 +00:00
Adrià
dbea6cdf0c
core: Add support for JPEG-XL (#212) 2024-11-22 13:17:50 +00:00
izmyname
3f8cc92109
Add systemd service (#208) 2024-10-26 00:14:47 +03:00
gkdwoe
e6e5c471e2
core: add tile as an image mode (#207)
---------

Co-authored-by: gkdwoe <gkdwoe>
2024-10-25 12:29:16 +01:00
1c18ad6503 flake.lock: update 2024-09-01 17:20:38 +03:00
36e83af943 nix: add pkg-config to depsBuildBuild 2024-09-01 17:20:38 +03:00
André Silva
6c6e54faa8 CMakeLists: look for wayland.xml protocol in wayland-scanner pkgdata 2024-09-01 17:20:38 +03:00
Markus Volk
91e17e12ff CMakeLists: require native hyprwayland-scanner
This would fix a cross-compile issue where hyprwayland-scanner is pulled
in for target but needs to run on host.

Signed-off-by: Markus Volk <f_l_k@t-online.de>
2024-08-17 19:00:56 +03:00
808d88e574 version: bump to 0.7.1 2024-08-16 21:26:53 +02:00
Yury Shvedov
e32a2c8d24
config: Fix desc: wallpaper priority (#189)
There was misbehaviour from config. Say, we have next config:
```
...

wallpaper = , /path/to/generic.jpg
wallpaper = DP-1, /path/to/port.jpg
wallpaper = desc:My Monitor, /path/to/desc.jpg

```

Here the `DP-1` and `desc:My Monitor` are different monitors.

_EXPECTED_: The `desc:My Monitor` renders `/path/to/desc.jpg` wallpaper
_ACTUAL_: The `desc:My Monitor` renders `/path/to/generic.jpg` wallpaper

Change-Id: I02c9495524bd620d5a58b9d934b07aded051f6c2
2024-08-12 18:31:02 +01:00
f1f7fc60f5 core: bind to wl_seat v7
ref #187
2024-07-23 00:08:55 +02:00
87791c0a99
flake.lock: update 2024-07-18 20:49:41 +03:00
6197552f28
CMake: fmt 2024-07-18 20:49:30 +03:00
06b93bbb77
CMake, Nix: add VERSION file 2024-07-18 20:48:30 +03:00
cbc16f939f
flake.lock: update 2024-07-17 17:32:55 +03:00
3cbc90bf94 core: migrate to hyprwayland-scanner
Additionally:
 - format
 - yeet clang-tidy
 - fixup clang-format
2024-07-17 16:25:07 +02:00
f3a6e51d92
Config: use hyprutils helper (#183)
* config: use hyprutils helper

CMake: add version, add hyprutils

* Nix: add hyprutils dep

flake.lock: update

* Nix: add hyprwayland-scanner dep
2024-07-17 16:21:50 +02:00
13fcdd79ef fractional: reload monitor on new fs scale notifs
fixes #181
2024-07-02 18:54:11 +02:00
Mykola Perehudov
f4abf5902f
config: produce error instead fs::exists exceptions (#177) 2024-06-09 09:50:27 +02:00
Barguzin
374d6e2a9d
core: Bitmap image support (#175)
* Add handler "reload" to do a change of wallpaper by one hyprctl execution

* fixed contain parameter handling in "handleReload"

* added bitmap (.bmp) image support

* refactored

* reserve -> resize
2024-06-07 17:19:58 +02:00
Barguzin
2c57525de8
internal: Add handler "reload" to do a change of wallpaper by one hyprctl exec (#173)
* Add handler "reload" to do a change of wallpaper by one hyprctl execution

* fixed contain parameter handling in "handleReload"
2024-05-25 19:40:11 +02:00
678d0e8959
nix/hm-module: remove 2024-05-21 20:17:16 +03:00
Daniel Horton
b2f8274e1d
README: Fixed getconf command in build instructions (#172)
getconf NPROCESSORS_CONF isn't a valid command. The correct command is getconf _NPROCESSORS_CONF.
2024-05-14 16:13:41 +01:00
1e1fc79d84
Nix: add commit hash 2024-05-05 15:04:53 +03:00
728243cef0
flake.lock: update 2024-05-05 14:54:11 +03:00
03cd362f49 ipc: use XDG_RUNTIME_DIR if available
fixes #169
2024-05-04 22:01:40 +01:00
Vaxry
d50f0eda6c
core: move socket to runtime dir (#167) 2024-04-28 22:25:36 +01:00
GGORG
4b3843e283
Nix/HM module: fix typo (#166) 2024-04-27 17:38:42 +03:00
XiaowenHu
02ee7ff3bb
core: remove comma from monitor description (#163) 2024-04-12 12:01:36 +01:00
Imanol Fotia
07e8e6cfd9
README: Add missing dependency for Fedora (#162) 2024-04-09 08:09:14 +03:00
LOSEARDES77
437ac0530b
core: splash_color configuration option (#160) 2024-04-04 20:52:48 +01:00
GabrielePuliti
5838c90cd2
README: Add OpenSuse to the installer method list (#155)
* feat(README): add opensuse installation method

* fix(README): minor indentations issue resolve
2024-03-27 16:13:56 +00:00
Mihai Fufezan
ef2ab4ae27
Nix: overhaul flake 2024-03-24 14:39:17 +02:00
staz
518adcaf90
cmake: Set standard exclusively for c++ (#150) 2024-03-17 16:12:38 +00:00
Mihai Fufezan
f57d991e3a
Makefile: remove
No longer used. Functionality moved to CMake.
2024-03-12 22:24:10 +02:00
Gaetan Lepage
2691b41830 Nix: add home-manager module 2024-03-11 22:45:56 +02:00
kiecla
06638eeddc
README: Updated link to reflect arch package movement from community to extra (#147) 2024-03-11 01:42:01 +00:00
09c4062659 config: add unload unused 2024-02-27 21:45:28 +00:00
79765e1bdf core: make unload all do what it says 2024-02-27 21:44:11 +00:00
Stephen Toth
dfd3d090dc
core: fix being able to assign a wallpaper to a nonexistent monitor (#141)
clarified README to specifics of the handleWallpaper function
2024-02-25 23:05:58 +00:00
897cf0ae26 config: add explicit ctors for config variables
fixes #139
2024-02-21 16:28:39 +00:00
Stephen Toth
1013a80608
ipc: Added listloaded and listactive requests (#132) 2024-02-05 01:07:31 +00:00
Jesal
43b6e9d2e2
readme: fix typos (#124) 2024-01-04 23:22:23 +01:00
Stephen Toth
ce829bd51d
config: Disable splash message by default (#123)
* Change default value of splash to false

* put splash and ipc options into example config in README
2024-01-04 16:20:47 +01:00
c022069390 ipc: fix ipc with wildcards
fixes #122
2024-01-03 13:47:49 +01:00
Mihai Fufezan
9e2a2670e1 Fix Nix 2024-01-02 23:22:34 +02:00
Mihai Fufezan
c1ca04cb4a Nix: add hyprlang 2024-01-02 23:22:34 +02:00
122aaa2182 internal: update to hyprlang 0.2.0
fixes #87
2024-01-01 13:34:50 +01:00
75270a9b38 core: fix splash options parsing 2023-12-31 14:06:11 +01:00
39ad021c75 internal: move to libhyprlang for config handling 2023-12-31 01:41:32 +01:00
tobiichi3227
ef0e051255
core: fix build failed in big-endian system (#117)
Close #40
2023-12-25 18:08:13 +01:00
tobiichi3227
f8cab3c370
fix: check if path is empty (#116) 2023-12-23 18:12:34 +01:00
Louie Orcinolo
c447f1195c
README: add missing libwebp-devel for fedora (#114) 2023-12-22 12:29:31 +01:00
tobiichi3227
ae4f498fda
feat: Add webp support (#113)
* feat: add webp support

* readme: add libwebp dependency

* refactor: move including webp to source file

* style: fix using tab

* style: make const value upper-case

* Nix: add libwebp

---------

Co-authored-by: Mihai Fufezan <fufexan@protonmail.com>
2023-12-21 22:15:05 +01:00
Dmitriy Kholkin
a5e21e2d4a fix build on nix 2023-12-18 12:24:02 +02:00
Dmitriy Kholkin
21c0ed883b update flake.lock 2023-12-18 12:24:02 +02:00
cdec32da63 config: do not require config file
fixes #109
2023-12-14 19:56:49 +00:00
Arijit Dey
b94f84605d
README: Write about required dependencies (#111)
* doc(README): Write about required dependencies

* doc(README): Fix typo
2023-11-27 11:06:59 +00:00
f3837e9d59 surface: set fully opaque 2023-11-26 15:25:38 +00:00
czadowanie
38e18b7077
fix: respect PREFIX in Makefile (#106) 2023-11-14 13:52:08 +00:00
Charbonnier Cylian
d6856adaff
ipc: Allow multiple read from IPC + some refactor (#102)
* Allow multiple read from IPC

* bring back old indentation

* format with clang-format

---------

Co-authored-by: cylian charbonnier <cylian.charbonnier1@gmail.com>
2023-10-23 22:39:38 +01:00
Michał Lewandowski
1c009491b5
render: Always draw a black background first (#100) 2023-10-13 21:23:24 +01:00
Soc Virnyl S. Estela
d96581a23c
readme: Update opensuse deps (#99)
yes file-devel needed
2023-10-13 17:24:50 +01:00
MightyPlaza
72735ae635
feat: add splash_offset (#98)
closes: #96
2023-10-08 01:29:15 +01:00
LamprosPitsillos
e5a18a171d
internal: fix dupliacate include of math (#95) 2023-09-13 11:18:29 +01:00
slowsage
5e73eb6055
fix: Read absolute path of symlinks (#90) 2023-08-19 23:28:55 +02:00
48 changed files with 1591 additions and 2194 deletions

View file

@ -1,114 +1,65 @@
---
Language: Cpp
Standard: Auto
AccessModifierOffset: -4
AlignAfterOpenBracket: DontAlign
AlignArrayOfStructures: None
AlignConsecutiveAssignments: None
AlignConsecutiveBitFields: Consecutive
AlignConsecutiveDeclarations: None
AlignConsecutiveMacros: None
BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: true
AlignConsecutiveAssignments: true
AlignEscapedNewlines: Right
AlignOperands: DontAlign
AlignOperands: false
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: false
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: All
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
BinPackArguments: true
BinPackParameters: true
BitFieldColonSpacing: Both
BreakAfterJavaFieldAnnotations: true
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeConceptDeclarations: true
BreakBeforeInheritanceComma: false
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeComma
BreakConstructorInitializersBeforeComma: false
BreakInheritanceList: BeforeColon
BreakStringLiterals: true
ColumnLimit: 0
CompactNamespaces: true
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: false
BreakConstructorInitializers: AfterColon
ColumnLimit: 180
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 8
ContinuationIndentWidth: 4
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
IncludeBlocks: Preserve
IncludeIsMainRegex: (Test)?$
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: true
IndentExternBlock: AfterExternBlock
IndentGotoLabels: true
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
LambdaBodyIndentation: Signature
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
PPIndentWidth: -1
PackConstructorInitializers: BinPack
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Left
QualifierAlignment: Leave
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: true
ReflowComments: false
SortIncludes: false
SortUsingDeclarations: false
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 4
UseCRLF: false
UseTab: Never
...
AllowShortEnumsOnASingleLine: false
BraceWrapping:
AfterEnum: false
AlignConsecutiveDeclarations: AcrossEmptyLines
NamespaceIndentation: All

View file

@ -1,6 +0,0 @@
---
Checks: '-*,clang-diagnostic-*,bugprone-*,cert-*,misc-*,modernize-*,performance-*,readability-*,hicpp-exception-baseclass,hicpp-avoid-goto,-clang-diagnostic-address-of-packed-member,-cert-dcl16-c,-cert-dcl21-cpp,-cert-err58-cpp,-misc-unused-parameters,-misc-non-private-member-variables-in-classes,-modernize-avoid-c-arrays,-modernize-concat-nested-namespaces,-modernize-raw-string-literal,-modernize-use-default-member-init,-modernize-use-nodiscard,-modernize-use-override,-modernize-use-trailing-return-type,-readability-else-after-return,-readability-identifier-naming,-readability-implicit-bool-cast,-readability-implicit-bool-conversion,-readability-magic-numbers,-readability-named-parameter,-readability-qualified-auto,-readability-redundant-access-specifiers,-readability-redundant-member-init,-readability-uppercase-literal-suffix'
WarningsAsErrors: '*'
HeaderFilterRegex: ''
FormatStyle: none
...

View file

@ -10,17 +10,41 @@ jobs:
uses: actions/checkout@v3
with:
submodules: recursive
- name: Install nix
uses: cachix/install-nix-action@v20
- name: Install Nix
uses: nixbuild/nix-quick-install-action@v31
with:
install_url: https://nixos.org/nix/install
extra_nix_config: |
auto-optimise-store = true
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
experimental-features = nix-command flakes
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

8
.gitignore vendored
View file

@ -19,6 +19,14 @@ result
*-protocol.h
.ccls-cache
protocols/*.hpp
protocols/*.cpp
hw-protocols/*.hpp
hw-protocols/*.cpp
.cache/
hyprctl/hyprctl
gmon.out

View file

@ -1,14 +1,22 @@
cmake_minimum_required(VERSION 3.4)
project(hyprpaper
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!")
# Get git info
# hash and branch
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}
@ -22,7 +30,7 @@ execute_process(
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(
COMMAND bash -c "git show ${GIT_COMMIT_HASH} | head -n 5 | tail -n 1"
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)
@ -32,24 +40,101 @@ execute_process(
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_DIRTY
OUTPUT_STRIP_TRAILING_WHITESPACE)
#
#
include_directories(.)
add_compile_options(-std=c++2b -DWLR_USE_UNSTABLE )
add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value -Wno-missing-field-initializers -Wno-narrowing)
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)
pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols cairo pango pangocairo libjpeg)
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})
target_compile_definitions(hyprpaper PRIVATE "-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"")
# 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}\"")
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)
@ -60,21 +145,24 @@ include(CPack)
target_link_libraries(hyprpaper PkgConfig::deps)
target_link_libraries(hyprpaper
target_link_libraries(
hyprpaper
OpenGL
GLESv2
pthread
magic
${CMAKE_THREAD_LIBS_INIT}
${CMAKE_SOURCE_DIR}/wlr-layer-shell-unstable-v1-protocol.o
${CMAKE_SOURCE_DIR}/xdg-shell-protocol.o
${CMAKE_SOURCE_DIR}/fractional-scale-v1-protocol.o
${CMAKE_SOURCE_DIR}/viewporter-protocol.o
wayland-cursor
)
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)
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")

View file

@ -1,74 +0,0 @@
PREFIX = /usr/local
CFLAGS ?= -g -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare -Wno-unused-function -Wno-unused-variable -Wno-unused-result -Wdeclaration-after-statement
CFLAGS += -I. -DWLR_USE_UNSTABLE -std=c99
WAYLAND_PROTOCOLS=$(shell pkg-config --variable=pkgdatadir wayland-protocols)
WAYLAND_SCANNER=$(shell pkg-config --variable=wayland_scanner wayland-scanner)
PKGS = wlroots wayland-server
CFLAGS += $(foreach p,$(PKGS),$(shell pkg-config --cflags $(p)))
LDLIBS += $(foreach p,$(PKGS),$(shell pkg-config --libs $(p)))
wlr-layer-shell-unstable-v1-protocol.h:
$(WAYLAND_SCANNER) client-header \
protocols/wlr-layer-shell-unstable-v1.xml $@
wlr-layer-shell-unstable-v1-protocol.c:
$(WAYLAND_SCANNER) private-code \
protocols/wlr-layer-shell-unstable-v1.xml $@
wlr-layer-shell-unstable-v1-protocol.o: wlr-layer-shell-unstable-v1-protocol.h
xdg-shell-protocol.h:
$(WAYLAND_SCANNER) client-header \
$(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@
xdg-shell-protocol.c:
$(WAYLAND_SCANNER) private-code \
$(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@
xdg-shell-protocol.o: xdg-shell-protocol.h
fractional-scale-v1-protocol.h:
$(WAYLAND_SCANNER) client-header \
$(WAYLAND_PROTOCOLS)/staging/fractional-scale/fractional-scale-v1.xml $@
fractional-scale-v1-protocol.c:
$(WAYLAND_SCANNER) private-code \
$(WAYLAND_PROTOCOLS)/staging/fractional-scale/fractional-scale-v1.xml $@
fractional-scale-v1-protocol.o: fractional-scale-v1-protocol.h
viewporter-protocol.h:
$(WAYLAND_SCANNER) client-header \
$(WAYLAND_PROTOCOLS)/stable/viewporter/viewporter.xml $@
viewporter-protocol.c:
$(WAYLAND_SCANNER) private-code \
$(WAYLAND_PROTOCOLS)/stable/viewporter/viewporter.xml $@
viewporter-protocol.o: viewporter-protocol.h
protocols: wlr-layer-shell-unstable-v1-protocol.o xdg-shell-protocol.o fractional-scale-v1-protocol.o viewporter-protocol.o
clear:
rm -rf build
rm -f *.o *-protocol.h *-protocol.c
release:
mkdir -p build && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -H./ -B./build -G Ninja
cmake --build ./build --config Release --target all -j 10
debug:
mkdir -p build && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -H./ -B./build -G Ninja
cmake --build ./build --config Debug --target all -j 10
all:
make clear
make protocols
make release
install:
make all
cp ./build/hyprpaper /usr/bin -f

146
README.md
View file

@ -1,136 +1,40 @@
# hyprpaper
Hyprpaper is a blazing fast wallpaper utility for Hyprland with the ability to dynamically change wallpapers through sockets. It will work on all wlroots-based compositors, though.
Hyprpaper is a simple and fast wallpaper utility for Hyprland with the ability to dynamically change wallpapers through sockets.
# Features
- Per-output wallpapers
- fill or contain modes
- fill, tile, cover or contain modes
- fractional scaling support
- IPC for blazing fast wallpaper switches
- preloading targets into memory
- IPC for fast wallpaper switches
# Installation
[Arch Linux](https://archlinux.org/packages/community/x86_64/hyprpaper/): `pacman -S hyprpaper`
[Arch Linux](https://archlinux.org/packages/extra/x86_64/hyprpaper/): `pacman -S hyprpaper`
### Manual:
```
git clone https://github.com/hyprwm/hyprpaper
cd hyprpaper
make all
```
*the output binary will be in `./build/`, copy it to your PATH, e.g. `/usr/bin`*
[OpenSuse Linux](https://software.opensuse.org/package/hyprpaper): `zypper install hyprpaper`
#### Dependencies
On OpenSUSE:
```
sudo zypper install ninja gcc-c++ wayland-protocols-devel Mesa-libGLESv3-devel
## 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`
```
# Usage
Install with:
Hyprpaper is controlled by the config, like this:
*~/.config/hypr/hyprpaper.conf*
```sh
cmake --install ./build
```
preload = /path/to/image.png
#if more than one preload is desired then continue to preload other backgrounds
preload = /path/to/next_image.png
# .. more preloads
#set the default wallpaper(s) seen on inital workspace(s) --depending on the number of monitors used
wallpaper = monitor1,/path/to/image.png
#if more than one monitor in use, can load a 2nd image
wallpaper = monitor2,/path/to/next_image.png
# .. more monitors
```
Preload will tell Hyprland to load a particular image (supported formats: png, jpg, jpeg). Wallpaper will apply the wallpaper to the selected output (`monitor` is the monitor's name, easily can be retrieved with `hyprctl monitors`. You can leave it empty for a wildcard (aka fallback). You can also use `desc:` followed by the monitor's description without the (PORT) at the end)
You may add `contain:` before the file path in `wallpaper=` to set the mode to contain instead of cover:
```
wallpaper = monitor,contain:/path/to/image.jpg
```
A Wallpaper ***cannot*** be applied without preloading. The config is ***not*** reloaded dynamically.
## Important note to the inner workings
Preload does exactly what it says. It loads the entire wallpaper into memory. This can result in around 8 - 20MB of mem usage. It is not recommended to preload every wallpaper you have, as it will be a) taking a couple seconds at the beginning to load and b) take 100s of MBs of disk and RAM usage.
Preload is meant only for situations in which you want a wallpaper to switch INSTANTLY when you issue a wallpaper keyword (e.g. wallpaper per workspace)
In any and all cases when you don't mind waiting 300ms for the wallpaper to change, consider making a script that:
- preloads the new wallpaper
- sets the new wallpaper
- unloads the old wallpaper (to free memory)
# IPC
You can use `hyprctl hyprpaper` (if on Hyprland) to issue a keyword, for example
Example:
If your wallpapers are stored in *~/Pictures*, then make sure you have already preloaded the desired wallpapers in hyprpaper.conf.
*~/.config/hypr/hyprpaper.conf*
```
preload = ~/Pictures/myepicpng.png
preload = ~/Pictures/myepicpngToo.png
preload = ~/Pictures/myepicpngAlso.png
#... continue as desired, but be mindful of the impact on memory.
```
In the actual configuration for Hyprland, *hyprland.conf*, variables can be set for ease of reading and to be used as shortcuts in the bind command. The following example uses $w shorthand wallpaper variables:
*~/.config/hypr/hyprland.conf*
```
$w1 = hyprctl hyprpaper wallpaper "DP-1,~/Pictures/myepicpng.png"
$w2 = hyprctl hyprpaper wallpaper "DP-1,~/Pictures/myepicpngToo.png"
$w3 = hyprctl hyprpaper wallpaper "DP-1,~/Pictures/myepicpngAlso.png"
#yes use quotes around desired monitor and wallpaper
#... continued with desired amount
```
With the varibles created we can now "exec" the actions.
Remember in Hyprland we can bind more than one action to a key so in the case where we'd like to change the wallpaper when we switch workspace we have to ensure that the actions are bound to the same key such as...
*~/.config/hypr/hyprland.conf*
```
bind=SUPER,1,workspace,1 #Superkey + 1 switches to workspace 1
bind=SUPER,1,exec,$w1 #SuperKey + 1 switches to wallpaper $w1 on DP-1 as defined in the variable
bind=SUPER,2,workspace,2 #Superkey + 2 switches to workspace 2
bind=SUPER,2,exec,$w2 #SuperKey + 2 switches to wallpaper $w2 on DP-1 as defined in the variable
bind=SUPER,3,workspace,3 #Superkey + 3 switches to workspace 3
bind=SUPER,3,exec,$w3 #SuperKey + 3 switches to wallpaper $w3 on DP-1 as defined in the variable
#... and so on
```
Because the default behavior in Hyprland is to also switch the workspace whenever movetoworkspace is used to move a window to another workspace you may want to include the following:
```
bind=SUPERSHIFT,1,movetoworkspace,1 #Superkey + Shift + 1 moves windows and switches to workspace 1
bind=SUPERSHIFT,1,exec,$w1 #SuperKey + Shift + 1 switches to wallpaper $w1 on DP-1 as defined in the variable
```
# Battery life
Since the IPC has to tick every now and then, and poll in the background, battery life might be a tiny bit worse with IPC on. If you want to fully disable it, use
```
ipc = off
```
in the config.
# Misc
You can set `splash = true` to enable the splash rendering over the wallpaper.
## Unloading
If you use a lot of wallpapers, consider unloading those that you no longer need. This will mean you need to load them again if you wish to use them for a second time, but will free the memory used by the preloaded bitmap. (Usually 8 - 20MB, depending on the resolution)
You can issue a `hyprctl hyprpaper unload [PATH]` to do that.
You can also issue a `hyprctl hyprpaper unload all` to unload all inactive wallpapers.
<br/>
For other compositors, the socket works like socket1 of Hyprland, and is located in `/tmp/hypr/.hyprpaper.sock` (this path only when Hyprland is not running!)

1
VERSION Normal file
View file

@ -0,0 +1 @@
0.7.6

222
flake.lock generated
View file

@ -1,12 +1,203 @@
{
"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": 1683014792,
"narHash": "sha256-6Va9iVtmmsw4raBc3QKvQT2KT/NGRWlvUlJj46zN8B8=",
"lastModified": 1765779637,
"narHash": "sha256-KJ2wa/BLSrTqDjbfyNx70ov/HdgNBCBBSQP3BIzKnv4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1a411f23ba299db155a5b45d5e145b85a7aafc42",
"rev": "1306659b587dc277866c7b69eb97e5f07864d8c4",
"type": "github"
},
"original": {
@ -18,7 +209,30 @@
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
"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"
}
}
},

116
flake.nix
View file

@ -1,39 +1,115 @@
{
description = "Hyprpaper is a blazing fast Wayland wallpaper utility with IPC controls";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
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,
...
}: let
} @ inputs: let
inherit (nixpkgs) lib;
genSystems = lib.genAttrs [
# Add more systems if they are supported
"x86_64-linux"
"aarch64-linux"
];
pkgsFor = nixpkgs.legacyPackages;
eachSystem = lib.genAttrs (import systems);
pkgsFor = eachSystem (system:
import nixpkgs {
localSystem.system = system;
overlays = with self.overlays; [hyprpaper];
});
mkDate = longDate: (lib.concatStringsSep "-" [
(__substring 0 4 longDate)
(__substring 4 2 longDate)
(__substring 6 2 longDate)
(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 = _: prev: rec {
hyprpaper = prev.callPackage ./nix/default.nix {
stdenv = prev.gcc12Stdenv;
version = "0.pre" + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
inherit (prev.xorg) libXdmcp;
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 = genSystems (system:
(self.overlays.default null pkgsFor.${system})
// {default = self.packages.${system}.hyprpaper;});
packages = eachSystem (system: {
default = self.packages.${system}.hyprpaper;
inherit (pkgsFor.${system}) hyprpaper hyprpaper-debug;
});
formatter = genSystems (system: pkgsFor.${system}.alejandra);
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);
};
}

View file

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="hyprpaper_core" version="1">
<copyright>
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.
</copyright>
<object name="hyprpaper_core_manager" version="1">
<description summary="manager object">
This is the core manager object for hyprpaper operations
</description>
<c2s name="get_wallpaper_object">
<description summary="Get a wallpaper object">
Creates a wallpaper object
</description>
<returns iface="hyprpaper_wallpaper"/>
</c2s>
<s2c name="add_monitor">
<description summary="New monitor added">
Emitted when a new monitor is added.
</description>
<arg name="monitor_name" type="varchar" summary="the monitor's name"/>
</s2c>
<s2c name="remove_monitor">
<description summary="A monitor was removed">
Emitted when a monitor is removed.
</description>
<arg name="monitor_name" type="varchar" summary="the monitor's name"/>
</s2c>
<c2s name="destroy" destructor="true">
<description summary="Destroy this object">
Destroys this object. Children remain alive until destroyed.
</description>
</c2s>
</object>
<enum name="wallpaper_fit_mode">
<value idx="0" name="stretch"/>
<value idx="1" name="cover"/>
<value idx="2" name="contain"/>
<value idx="3" name="tile"/>
</enum>
<enum name="wallpaper_errors">
<value idx="0" name="inert_wallpaper_object" description="attempted to use an inert wallpaper object"/>
</enum>
<enum name="applying_error">
<value idx="0" name="invalid_path" description="path provided was invalid"/>
<value idx="1" name="invalid_monitor" description="monitor provided was invalid"/>
<value idx="2" name="unknown_error" description="unknown error"/>
</enum>
<object name="hyprpaper_wallpaper" version="1">
<description summary="wallpaper object">
This is an object describing a wallpaper
</description>
<c2s name="path">
<description summary="Set a path">
Set a file path for the wallpaper. This has to be an absolute path from the fs root.
This is required.
</description>
<arg name="wallpaper" type="varchar" summary="path"/>
</c2s>
<c2s name="fit_mode">
<description summary="Set a fit mode">
Set a fit mode for the wallpaper. This is set to cover by default.
</description>
<arg name="fit_mode" type="enum" interface="wallpaper_fit_mode" summary="path"/>
</c2s>
<c2s name="monitor_name">
<description summary="Set the monitor name">
Set a monitor for the wallpaper. Setting this to empty (or not setting at all) will
treat this as a wildcard fallback.
See hyprpaper_core_manager.add_monitor and hyprpaper_core_manager.remove_monitor
for tracking monitor names.
</description>
<arg name="monitor_name" type="varchar" summary="monitor name"/>
</c2s>
<c2s name="apply">
<description summary="Apply this wallpaper">
Applies this object's state to the wallpaper state. Will emit .success on success,
and .failed on failure.
This object becomes inert after .succeess or .failed, the only valid operation
is to destroy it afterwards.
</description>
</c2s>
<s2c name="success">
<description summary="Operation succeeded">
Wallpaper was applied successfully.
</description>
</s2c>
<s2c name="failed">
<description summary="Operation failed">
Wallpaper was not applied. See the error field for more information.
</description>
<arg name="error" type="enum" interface="hyprpaper_wallpaper_application_error" summary="path"/>
</s2c>
<c2s name="destroy" destructor="true">
<description summary="Destroy this object">
Destroys this object.
</description>
</c2s>
</object>
</protocol>

View file

@ -3,86 +3,100 @@
stdenv,
pkg-config,
cmake,
ninja,
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,
libXdmcp,
xorg,
commit,
debug ? false,
version ? "git",
}:
stdenv.mkDerivation {
pname = "hyprpaper" + lib.optionalString debug "-debug";
inherit version;
src = ../.;
nativeBuildInputs = [
cmake
ninja
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
wayland-scanner
libXdmcp
xorg.libXdmcp
util-linux
];
configurePhase = ''
runHook preConfigure
make protocols
runHook postConfigure
'';
buildPhase = ''
runHook preBuild
make release
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out/{bin,share/licenses}
install -Dm755 build/hyprpaper -t $out/bin
install -Dm644 LICENSE -t $out/share/licenses/hyprpaper
runHook postInstall
'';
meta = with lib; {
homepage = "https://github.com/hyprwm/hyprpaper";
description = "A blazing fast wayland wallpaper utility with IPC controls";
homepage = "https://github.com/hyprwm/hyprpaper";
license = licenses.bsd3;
platforms = platforms.linux;
mainProgram = "hyprpaper";
platforms = platforms.linux;
};
}

View file

@ -1,608 +0,0 @@
#include "Hyprpaper.hpp"
#include <filesystem>
#include <fstream>
#include <sys/types.h>
#include <signal.h>
CHyprpaper::CHyprpaper() = default;
void CHyprpaper::init() {
if (!lockSingleInstance()) {
Debug::log(CRIT, "Cannot launch multiple instances of Hyprpaper at once!");
exit(1);
}
removeOldHyprpaperImages();
g_pConfigManager = std::make_unique<CConfigManager>();
g_pIPCSocket = std::make_unique<CIPCSocket>();
m_sDisplay = (wl_display*)wl_display_connect(nullptr);
if (!m_sDisplay) {
Debug::log(CRIT, "No wayland compositor running!");
exit(1);
}
preloadAllWallpapersFromConfig();
if (m_bIPCEnabled)
g_pIPCSocket->initialize();
// run
wl_registry* registry = wl_display_get_registry(m_sDisplay);
wl_registry_add_listener(registry, &Events::registryListener, nullptr);
while (wl_display_dispatch(m_sDisplay) != -1) {
std::lock_guard<std::mutex> lg(m_mtTickMutex);
tick(true);
}
unlockSingleInstance();
}
void CHyprpaper::tick(bool force) {
bool reload = g_pIPCSocket->mainThreadParseRequest();
if (!reload && !force)
return;
preloadAllWallpapersFromConfig();
ensurePoolBuffersPresent();
recheckAllMonitors();
}
bool CHyprpaper::isPreloaded(const std::string& path) {
for (auto& [pt, wt] : m_mWallpaperTargets) {
if (pt == path)
return true;
}
return false;
}
void CHyprpaper::unloadWallpaper(const std::string& path) {
bool found = false;
for (auto& [ewp, cls] : m_mWallpaperTargets) {
if (ewp == path) {
// found
found = true;
break;
}
}
if (!found) {
Debug::log(LOG, "Cannot unload a target that was not loaded!");
return;
}
// clean buffers
for (auto it = m_vBuffers.begin(); it != m_vBuffers.end();) {
if (it->get()->target != path) {
it++;
continue;
}
const auto PRELOADPATH = it->get()->name;
Debug::log(LOG, "Unloading target %s, preload path %s", path.c_str(), PRELOADPATH.c_str());
std::filesystem::remove(PRELOADPATH);
destroyBuffer(it->get());
it = m_vBuffers.erase(it);
}
m_mWallpaperTargets.erase(path); // will free the cairo surface
}
void CHyprpaper::preloadAllWallpapersFromConfig() {
if (g_pConfigManager->m_dRequestedPreloads.empty())
return;
for (auto& wp : g_pConfigManager->m_dRequestedPreloads) {
// check if it doesnt exist
bool exists = false;
for (auto& [ewp, cls] : m_mWallpaperTargets) {
if (ewp == wp) {
Debug::log(LOG, "Ignoring request to preload %s as it already is preloaded!", ewp.c_str());
exists = true;
break;
}
}
if (exists)
continue;
m_mWallpaperTargets[wp] = CWallpaperTarget();
if (std::filesystem::is_symlink(wp)) {
auto real_wp = std::filesystem::read_symlink(wp);
m_mWallpaperTargets[wp].create(real_wp);
} else {
m_mWallpaperTargets[wp].create(wp);
}
}
g_pConfigManager->m_dRequestedPreloads.clear();
}
void CHyprpaper::recheckAllMonitors() {
for (auto& m : m_vMonitors) {
recheckMonitor(m.get());
}
}
void CHyprpaper::createSeat(wl_seat* pSeat) {
wl_seat_add_listener(pSeat, &Events::seatListener, pSeat);
}
void CHyprpaper::recheckMonitor(SMonitor* pMonitor) {
ensureMonitorHasActiveWallpaper(pMonitor);
if (pMonitor->wantsACK) {
pMonitor->wantsACK = false;
zwlr_layer_surface_v1_ack_configure(pMonitor->pCurrentLayerSurface->pLayerSurface, pMonitor->configureSerial);
if (!pMonitor->pCurrentLayerSurface->pCursorImg) {
int XCURSOR_SIZE = 24;
if (const auto CURSORSIZENV = getenv("XCURSOR_SIZE"); CURSORSIZENV) {
try {
if (XCURSOR_SIZE = std::stoi(CURSORSIZENV); XCURSOR_SIZE <= 0) {
throw std::exception();
}
} catch (...) {
Debug::log(WARN, "XCURSOR_SIZE environment variable is set incorrectly");
XCURSOR_SIZE = 24;
}
}
pMonitor->pCurrentLayerSurface->pCursorTheme = wl_cursor_theme_load(getenv("XCURSOR_THEME"), XCURSOR_SIZE * pMonitor->scale, m_sSHM);
pMonitor->pCurrentLayerSurface->pCursorImg = wl_cursor_theme_get_cursor(pMonitor->pCurrentLayerSurface->pCursorTheme, "left_ptr")->images[0];
}
}
if (pMonitor->wantsReload) {
pMonitor->wantsReload = false;
renderWallpaperForMonitor(pMonitor);
}
}
void CHyprpaper::removeOldHyprpaperImages() {
int cleaned = 0;
uint64_t memoryFreed = 0;
for (const auto& entry : std::filesystem::directory_iterator(std::string(getenv("XDG_RUNTIME_DIR")))) {
if (entry.is_directory())
continue;
const auto FILENAME = entry.path().filename().string();
if (FILENAME.contains(".hyprpaper_")) {
// unlink it
memoryFreed += entry.file_size();
if (!std::filesystem::remove(entry.path()))
Debug::log(LOG, "Couldn't remove %s", entry.path().string().c_str());
cleaned++;
}
}
if (cleaned != 0) {
Debug::log(LOG, "Cleaned old hyprpaper preloads (%i), removing %.1fMB", cleaned, ((float)memoryFreed) / 1000000.f);
}
}
SMonitor* CHyprpaper::getMonitorFromName(const std::string& monname) {
bool useDesc = false;
std::string desc = "";
if (monname.find("desc:") == 0) {
useDesc = true;
desc = monname.substr(5);
}
for (auto& m : m_vMonitors) {
if (useDesc && m->description.find(desc) == 0)
return m.get();
if (m->name == monname)
return m.get();
}
return nullptr;
}
void CHyprpaper::ensurePoolBuffersPresent() {
bool anyNewBuffers = false;
for (auto& [file, wt] : m_mWallpaperTargets) {
for (auto& m : m_vMonitors) {
if (m->size == Vector2D())
continue;
auto it = std::find_if(m_vBuffers.begin(), m_vBuffers.end(), [wt = &wt, &m](const std::unique_ptr<SPoolBuffer>& el) {
auto scale = std::round((m->pCurrentLayerSurface && m->pCurrentLayerSurface->pFractionalScaleInfo ? m->pCurrentLayerSurface->fScale : m->scale) * 120.0) / 120.0;
return el->target == wt->m_szPath && vectorDeltaLessThan(el->pixelSize, m->size * scale, 1);
});
if (it == m_vBuffers.end()) {
// create
const auto PBUFFER = m_vBuffers.emplace_back(std::make_unique<SPoolBuffer>()).get();
auto scale = std::round((m->pCurrentLayerSurface && m->pCurrentLayerSurface->pFractionalScaleInfo ? m->pCurrentLayerSurface->fScale : m->scale) * 120.0) / 120.0;
createBuffer(PBUFFER, m->size.x * scale, m->size.y * scale, WL_SHM_FORMAT_ARGB8888);
PBUFFER->target = wt.m_szPath;
Debug::log(LOG, "Buffer created for target %s, Shared Memory usage: %.1fMB", wt.m_szPath.c_str(), PBUFFER->size / 1000000.f);
anyNewBuffers = true;
}
}
}
if (anyNewBuffers) {
uint64_t bytesUsed = 0;
for (auto& bf : m_vBuffers) {
bytesUsed += bf->size;
}
Debug::log(LOG, "Total SM usage for all buffers: %.1fMB", bytesUsed / 1000000.f);
}
}
void CHyprpaper::clearWallpaperFromMonitor(const std::string& monname) {
const auto PMONITOR = getMonitorFromName(monname);
if (!PMONITOR)
return;
auto it = m_mMonitorActiveWallpaperTargets.find(PMONITOR);
if (it != m_mMonitorActiveWallpaperTargets.end())
m_mMonitorActiveWallpaperTargets.erase(it);
PMONITOR->hasATarget = true;
if (PMONITOR->pCurrentLayerSurface) {
PMONITOR->pCurrentLayerSurface = nullptr;
PMONITOR->wantsACK = false;
PMONITOR->wantsReload = false;
PMONITOR->initialized = false;
PMONITOR->readyForLS = true;
}
}
void CHyprpaper::ensureMonitorHasActiveWallpaper(SMonitor* pMonitor) {
if (!pMonitor->readyForLS || !pMonitor->hasATarget)
return;
auto it = m_mMonitorActiveWallpaperTargets.find(pMonitor);
if (it == m_mMonitorActiveWallpaperTargets.end()) {
m_mMonitorActiveWallpaperTargets[pMonitor] = nullptr;
it = m_mMonitorActiveWallpaperTargets.find(pMonitor);
}
if (it->second)
return; // has
// get the target
for (auto& [mon, path1] : m_mMonitorActiveWallpapers) {
if (mon.find("desc:") != 0)
continue;
if (pMonitor->description.find(mon.substr(5)) == 0) {
for (auto& [path2, target] : m_mWallpaperTargets) {
if (path1 == path2) {
it->second = &target;
break;
}
}
break;
}
}
for (auto& [mon, path1] : m_mMonitorActiveWallpapers) {
if (mon == pMonitor->name) {
for (auto& [path2, target] : m_mWallpaperTargets) {
if (path1 == path2) {
it->second = &target;
break;
}
}
break;
}
}
if (!it->second) {
// try to find a wildcard
for (auto& [mon, path1] : m_mMonitorActiveWallpapers) {
if (mon.empty()) {
for (auto& [path2, target] : m_mWallpaperTargets) {
if (path1 == path2) {
it->second = &target;
break;
}
}
break;
}
}
}
if (!it->second) {
pMonitor->hasATarget = false;
Debug::log(WARN, "Monitor %s does not have a target! A wallpaper will not be created.", pMonitor->name.c_str());
return;
}
// create it for thy if it doesnt have
if (!pMonitor->pCurrentLayerSurface)
createLSForMonitor(pMonitor);
else
pMonitor->wantsReload = true;
}
void CHyprpaper::createLSForMonitor(SMonitor* pMonitor) {
pMonitor->pCurrentLayerSurface = pMonitor->layerSurfaces.emplace_back(std::make_unique<CLayerSurface>(pMonitor)).get();
}
bool CHyprpaper::setCloexec(const int& FD) {
long flags = fcntl(FD, F_GETFD);
if (flags == -1) {
return false;
}
if (fcntl(FD, F_SETFD, flags | FD_CLOEXEC) == -1) {
return false;
}
return true;
}
int CHyprpaper::createPoolFile(size_t size, std::string& name) {
const auto XDGRUNTIMEDIR = getenv("XDG_RUNTIME_DIR");
if (!XDGRUNTIMEDIR) {
Debug::log(CRIT, "XDG_RUNTIME_DIR not set!");
exit(1);
}
name = std::string(XDGRUNTIMEDIR) + "/.hyprpaper_XXXXXX";
const auto FD = mkstemp((char*)name.c_str());
if (FD < 0) {
Debug::log(CRIT, "createPoolFile: fd < 0");
exit(1);
}
if (!setCloexec(FD)) {
close(FD);
Debug::log(CRIT, "createPoolFile: !setCloexec");
exit(1);
}
if (ftruncate(FD, size) < 0) {
close(FD);
Debug::log(CRIT, "createPoolFile: ftruncate < 0");
exit(1);
}
return FD;
}
void CHyprpaper::createBuffer(SPoolBuffer* pBuffer, int32_t w, int32_t h, uint32_t format) {
const size_t STRIDE = w * 4;
const size_t SIZE = STRIDE * h;
std::string name;
const auto FD = createPoolFile(SIZE, name);
if (FD == -1) {
Debug::log(CRIT, "Unable to create pool file!");
exit(1);
}
const auto DATA = mmap(nullptr, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, FD, 0);
const auto POOL = wl_shm_create_pool(g_pHyprpaper->m_sSHM, FD, SIZE);
pBuffer->buffer = wl_shm_pool_create_buffer(POOL, 0, w, h, STRIDE, format);
wl_shm_pool_destroy(POOL);
close(FD);
pBuffer->size = SIZE;
pBuffer->data = DATA;
pBuffer->surface = cairo_image_surface_create_for_data((unsigned char*)DATA, CAIRO_FORMAT_ARGB32, w, h, STRIDE);
pBuffer->cairo = cairo_create(pBuffer->surface);
pBuffer->pixelSize = Vector2D(w, h);
pBuffer->name = name;
}
void CHyprpaper::destroyBuffer(SPoolBuffer* pBuffer) {
wl_buffer_destroy(pBuffer->buffer);
cairo_destroy(pBuffer->cairo);
cairo_surface_destroy(pBuffer->surface);
munmap(pBuffer->data, pBuffer->size);
pBuffer->buffer = nullptr;
}
SPoolBuffer* CHyprpaper::getPoolBuffer(SMonitor* pMonitor, CWallpaperTarget* pWallpaperTarget) {
const auto IT = std::find_if(m_vBuffers.begin(), m_vBuffers.end(), [&](const std::unique_ptr<SPoolBuffer>& el) {
auto scale = std::round((pMonitor->pCurrentLayerSurface && pMonitor->pCurrentLayerSurface->pFractionalScaleInfo ? pMonitor->pCurrentLayerSurface->fScale : pMonitor->scale) * 120.0) / 120.0;
return el->target == pWallpaperTarget->m_szPath && vectorDeltaLessThan(el->pixelSize, pMonitor->size * scale, 1);
});
if (IT == m_vBuffers.end())
return nullptr;
return IT->get();
}
void CHyprpaper::renderWallpaperForMonitor(SMonitor* pMonitor) {
const auto PWALLPAPERTARGET = m_mMonitorActiveWallpaperTargets[pMonitor];
const auto CONTAIN = m_mMonitorWallpaperRenderData[pMonitor->name].contain;
if (!PWALLPAPERTARGET) {
Debug::log(CRIT, "wallpaper target null in render??");
exit(1);
}
auto* PBUFFER = getPoolBuffer(pMonitor, PWALLPAPERTARGET);
if (!PBUFFER) {
Debug::log(LOG, "Pool buffer missing for available target??");
ensurePoolBuffersPresent();
PBUFFER = getPoolBuffer(pMonitor, PWALLPAPERTARGET);
if (!PBUFFER) {
Debug::log(LOG, "Pool buffer failed #2. Ignoring WP.");
return;
}
}
const double SURFACESCALE = pMonitor->pCurrentLayerSurface && pMonitor->pCurrentLayerSurface->pFractionalScaleInfo ? pMonitor->pCurrentLayerSurface->fScale : pMonitor->scale;
const Vector2D DIMENSIONS = Vector2D{std::round(pMonitor->size.x * SURFACESCALE), std::round(pMonitor->size.y * SURFACESCALE)};
const auto PCAIRO = PBUFFER->cairo;
cairo_save(PCAIRO);
cairo_set_operator(PCAIRO, CAIRO_OPERATOR_CLEAR);
cairo_paint(PCAIRO);
cairo_restore(PCAIRO);
if (CONTAIN) {
cairo_set_source_rgb(PCAIRO, 0, 0, 0);
cairo_rectangle(PCAIRO, 0, 0, DIMENSIONS.x, DIMENSIONS.y);
cairo_fill(PCAIRO);
cairo_surface_flush(PBUFFER->surface);
}
// get scale
// we always do cover
float scale;
Vector2D origin;
if (!CONTAIN) {
if (pMonitor->size.x / pMonitor->size.y > PWALLPAPERTARGET->m_vSize.x / PWALLPAPERTARGET->m_vSize.y) {
scale = DIMENSIONS.x / PWALLPAPERTARGET->m_vSize.x;
origin.y = -(PWALLPAPERTARGET->m_vSize.y * scale - DIMENSIONS.y) / 2.f / scale;
} else {
scale = DIMENSIONS.y / PWALLPAPERTARGET->m_vSize.y;
origin.x = -(PWALLPAPERTARGET->m_vSize.x * scale - DIMENSIONS.x) / 2.f / scale;
}
} else {
if (pMonitor->size.x / pMonitor->size.y > PWALLPAPERTARGET->m_vSize.x / PWALLPAPERTARGET->m_vSize.y) {
scale = (DIMENSIONS.y) / PWALLPAPERTARGET->m_vSize.y;
origin.x = (DIMENSIONS.x - PWALLPAPERTARGET->m_vSize.x * scale) / 2.0 / scale;
} else {
scale = (DIMENSIONS.x) / PWALLPAPERTARGET->m_vSize.x;
origin.y = (DIMENSIONS.y - PWALLPAPERTARGET->m_vSize.y * scale) / 2.0 / scale;
}
}
Debug::log(LOG, "Image data for %s: %s at [%.2f, %.2f], scale: %.2f (original image size: [%i, %i])", pMonitor->name.c_str(), PWALLPAPERTARGET->m_szPath.c_str(), origin.x, origin.y, scale, (int)PWALLPAPERTARGET->m_vSize.x, (int)PWALLPAPERTARGET->m_vSize.y);
cairo_scale(PCAIRO, scale, scale);
cairo_set_source_surface(PCAIRO, PWALLPAPERTARGET->m_pCairoSurface, origin.x, origin.y);
cairo_paint(PCAIRO);
if (g_pHyprpaper->m_bRenderSplash && getenv("HYPRLAND_INSTANCE_SIGNATURE")) {
auto SPLASH = execAndGet("hyprctl splash");
SPLASH.pop_back();
Debug::log(LOG, "Rendering splash: %s", SPLASH.c_str());
cairo_select_font_face(PCAIRO, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
const auto FONTSIZE = (int)(DIMENSIONS.y / 76.0 / scale);
cairo_set_font_size(PCAIRO, FONTSIZE);
cairo_set_source_rgba(PCAIRO, 1.0, 1.0, 1.0, 0.32);
cairo_text_extents_t textExtents;
cairo_text_extents(PCAIRO, SPLASH.c_str(), &textExtents);
cairo_move_to(PCAIRO, ((DIMENSIONS.x - textExtents.width * scale) / 2.0) / scale, (DIMENSIONS.y * 0.99 - textExtents.height * scale) / scale);
Debug::log(LOG, "Splash font size: %d, pos: %.2f, %.2f", FONTSIZE, (DIMENSIONS.x - textExtents.width) / 2.0 / scale, DIMENSIONS.y * 0.95 - textExtents.height / scale);
cairo_show_text(PCAIRO, SPLASH.c_str());
cairo_surface_flush(PWALLPAPERTARGET->m_pCairoSurface);
}
cairo_restore(PCAIRO);
if (pMonitor->pCurrentLayerSurface) {
wl_surface_attach(pMonitor->pCurrentLayerSurface->pSurface, PBUFFER->buffer, 0, 0);
wl_surface_set_buffer_scale(pMonitor->pCurrentLayerSurface->pSurface, pMonitor->pCurrentLayerSurface->pFractionalScaleInfo ? 1 : pMonitor->scale);
wl_surface_damage_buffer(pMonitor->pCurrentLayerSurface->pSurface, 0, 0, 0xFFFF, 0xFFFF);
if (pMonitor->pCurrentLayerSurface->pFractionalScaleInfo) {
Debug::log(LOG, "Submitting viewport dest size %ix%i for %x", static_cast<int>(std::round(pMonitor->size.x)), static_cast<int>(std::round(pMonitor->size.y)), pMonitor->pCurrentLayerSurface);
wp_viewport_set_destination(pMonitor->pCurrentLayerSurface->pViewport, static_cast<int>(std::round(pMonitor->size.x)), static_cast<int>(std::round(pMonitor->size.y)));
}
wl_surface_commit(pMonitor->pCurrentLayerSurface->pSurface);
}
// check if we dont need to remove a wallpaper
if (pMonitor->layerSurfaces.size() > 1) {
for (auto it = pMonitor->layerSurfaces.begin(); it != pMonitor->layerSurfaces.end(); it++) {
if (pMonitor->pCurrentLayerSurface != it->get()) {
pMonitor->layerSurfaces.erase(it);
break;
}
}
}
}
bool CHyprpaper::lockSingleInstance() {
const std::string XDG_RUNTIME_DIR = getenv("XDG_RUNTIME_DIR");
const auto LOCKFILE = XDG_RUNTIME_DIR + "/hyprpaper.lock";
if (std::filesystem::exists(LOCKFILE)) {
std::ifstream ifs(LOCKFILE);
std::string content((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
try {
kill(std::stoull(content), 0);
if (errno != ESRCH)
return false;
} catch (std::exception& e) {
;
}
}
// create lockfile
std::ofstream ofs(LOCKFILE, std::ios::trunc);
ofs << std::to_string(getpid());
ofs.close();
return true;
}
void CHyprpaper::unlockSingleInstance() {
const std::string XDG_RUNTIME_DIR = getenv("XDG_RUNTIME_DIR");
const auto LOCKFILE = XDG_RUNTIME_DIR + "/hyprpaper.lock";
unlink(LOCKFILE.c_str());
}

View file

@ -1,73 +0,0 @@
#pragma once
#include "defines.hpp"
#include "config/ConfigManager.hpp"
#include "render/WallpaperTarget.hpp"
#include "helpers/Monitor.hpp"
#include "events/Events.hpp"
#include "helpers/PoolBuffer.hpp"
#include "helpers/MiscFunctions.hpp"
#include "ipc/Socket.hpp"
#include <mutex>
struct SWallpaperRenderData {
bool contain = false;
};
class CHyprpaper {
public:
// important
wl_display* m_sDisplay; // assured
wl_compositor* m_sCompositor; // assured
wl_shm* m_sSHM; // assured
zwlr_layer_shell_v1* m_sLayerShell = nullptr; // expected
wp_fractional_scale_manager_v1* m_sFractionalScale = nullptr; // will remain null if not bound
wp_viewporter* m_sViewporter = nullptr; // expected
// init the utility
CHyprpaper();
void init();
void tick(bool force);
std::unordered_map<std::string, CWallpaperTarget> m_mWallpaperTargets;
std::unordered_map<std::string, std::string> m_mMonitorActiveWallpapers;
std::unordered_map<std::string, SWallpaperRenderData> m_mMonitorWallpaperRenderData;
std::unordered_map<SMonitor*, CWallpaperTarget*> m_mMonitorActiveWallpaperTargets;
std::vector<std::unique_ptr<SPoolBuffer>> m_vBuffers;
std::vector<std::unique_ptr<SMonitor>> m_vMonitors;
bool m_bIPCEnabled = true;
bool m_bRenderSplash = false;
std::string m_szExplicitConfigPath;
bool m_bNoFractionalScale = false;
void removeOldHyprpaperImages();
void preloadAllWallpapersFromConfig();
void recheckAllMonitors();
void ensureMonitorHasActiveWallpaper(SMonitor*);
void createLSForMonitor(SMonitor*);
void renderWallpaperForMonitor(SMonitor*);
void createBuffer(SPoolBuffer*, int32_t, int32_t, uint32_t);
void destroyBuffer(SPoolBuffer*);
int createPoolFile(size_t, std::string&);
bool setCloexec(const int&);
void clearWallpaperFromMonitor(const std::string&);
SMonitor* getMonitorFromName(const std::string&);
bool isPreloaded(const std::string&);
void recheckMonitor(SMonitor*);
void ensurePoolBuffersPresent();
SPoolBuffer* getPoolBuffer(SMonitor*, CWallpaperTarget*);
void unloadWallpaper(const std::string&);
void createSeat(wl_seat*);
bool lockSingleInstance(); // fails on multi-instance
void unlockSingleInstance();
std::mutex m_mtTickMutex;
SMonitor* m_pLastMonitor = nullptr;
private:
bool m_bShouldExit = false;
};
inline std::unique_ptr<CHyprpaper> g_pHyprpaper;

View file

@ -1,217 +1,102 @@
#include "ConfigManager.hpp"
#include "../Hyprpaper.hpp"
#include <filesystem>
#include <hyprlang.hpp>
#include <hyprutils/path/Path.hpp>
#include <string>
#include <sys/ucontext.h>
#include "../helpers/Logger.hpp"
#include "WallpaperMatcher.hpp"
CConfigManager::CConfigManager() {
// Initialize the configuration
// Read file from default location
// or from an explicit location given by user
using namespace std::string_literals;
std::string configPath = getMainConfigPath();
static std::string getMainConfigPath() {
static const auto paths = Hyprutils::Path::findConfig("hyprpaper");
std::ifstream ifs;
ifs.open(configPath);
if (!ifs.good()) {
Debug::log(CRIT, "Config file `%s` couldn't be opened.", configPath.c_str());
exit(1);
return paths.first.value_or("");
}
std::string line = "";
int linenum = 1;
if (ifs.is_open()) {
while (std::getline(ifs, line)) {
// Read line by line
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;
}
void 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.commence();
auto result = m_config.parse();
if (result.error)
g_logger->log(LOG_ERR, "Config has errors:\n{}\nProceeding ignoring faulty entries", result.getError());
g_matcher->addStates(getSettings());
}
Hyprlang::CConfig* CConfigManager::hyprlang() {
return &m_config;
}
static std::expected<std::string, std::string> 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<std::string, std::string> getFullPath(const std::string_view& sv) {
if (sv.empty())
return std::unexpected("empty path");
if (sv[0] == '~') {
static auto HOME = getenv("HOME");
if (!HOME || HOME[0] == '\0')
return std::unexpected("home path but no $HOME");
return resolvePath(std::string{HOME} + "/"s + std::string{sv.substr(1)});
}
return resolvePath(sv);
}
std::vector<CConfigManager::SSetting> CConfigManager::getSettings() {
std::vector<CConfigManager::SSetting> result;
auto keys = m_config.listKeysForSpecialCategory("wallpaper");
result.reserve(keys.size());
for (auto& key : keys) {
std::string monitor, fitMode, path;
try {
parseLine(line);
monitor = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("wallpaper", "monitor", key.c_str()));
fitMode = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("wallpaper", "fit_mode", key.c_str()));
path = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("wallpaper", "path", key.c_str()));
} catch (...) {
Debug::log(ERR, "Error reading line from config. Line:");
Debug::log(NONE, "%s", line.c_str());
parseError += "Config error at line " + std::to_string(linenum) + ": Line parsing error.";
}
if (!parseError.empty()) {
parseError = "Config error at line " + std::to_string(linenum) + ": " + parseError;
break;
}
++linenum;
}
ifs.close();
}
if (!parseError.empty()) {
Debug::log(CRIT, "Exiting because of config parse errors!\n%s", parseError.c_str());
exit(1);
return;
}
}
std::string CConfigManager::getMainConfigPath() {
if (!g_pHyprpaper->m_szExplicitConfigPath.empty())
return g_pHyprpaper->m_szExplicitConfigPath;
static const char* xdgConfigHome = getenv("XDG_CONFIG_HOME");
std::string configPath;
if (!xdgConfigHome)
configPath = getenv("HOME") + std::string("/.config");
else
configPath = xdgConfigHome;
return configPath + "/hypr/hyprpaper.conf";
}
std::string CConfigManager::removeBeginEndSpacesTabs(std::string str) {
while (str[0] == ' ' || str[0] == '\t') {
str = str.substr(1);
}
while (str.length() != 0 && (str[str.length() - 1] == ' ' || str[str.length() - 1] == '\t')) {
str = str.substr(0, str.length() - 1);
}
return str;
}
void CConfigManager::parseLine(std::string& line) {
// first check if its not a comment
const auto COMMENTSTART = line.find_first_of('#');
if (COMMENTSTART == 0)
return;
// now, cut the comment off
if (COMMENTSTART != std::string::npos)
line = line.substr(0, COMMENTSTART);
// Strip line
while (line[0] == ' ' || line[0] == '\t') {
line = line.substr(1);
}
// And parse
// check if command
const auto EQUALSPLACE = line.find_first_of('=');
if (EQUALSPLACE == std::string::npos)
return;
const auto COMMAND = removeBeginEndSpacesTabs(line.substr(0, EQUALSPLACE));
const auto VALUE = removeBeginEndSpacesTabs(line.substr(EQUALSPLACE + 1));
parseKeyword(COMMAND, VALUE);
}
void CConfigManager::parseKeyword(const std::string& COMMAND, const std::string& VALUE) {
if (COMMAND == "wallpaper")
handleWallpaper(COMMAND, VALUE);
else if (COMMAND == "preload")
handlePreload(COMMAND, VALUE);
else if (COMMAND == "unload")
handleUnload(COMMAND, VALUE);
else if (COMMAND == "ipc")
g_pHyprpaper->m_bIPCEnabled = VALUE == "1" || VALUE == "yes" || VALUE == "on" || VALUE == "true";
else if (COMMAND == "splash")
g_pHyprpaper->m_bRenderSplash = VALUE == "1" || VALUE == "yes" || VALUE == "on" || VALUE == "true";
else
parseError = "unknown keyword " + COMMAND;
}
void CConfigManager::handleWallpaper(const std::string& COMMAND, const std::string& VALUE) {
if (VALUE.find_first_of(',') == std::string::npos) {
parseError = "wallpaper failed (syntax)";
return;
}
auto MONITOR = VALUE.substr(0, VALUE.find_first_of(','));
auto WALLPAPER = trimPath(VALUE.substr(VALUE.find_first_of(',') + 1));
bool contain = false;
if (WALLPAPER.find("contain:") == 0) {
WALLPAPER = WALLPAPER.substr(8);
contain = true;
}
if (WALLPAPER[0] == '~') {
static const char* const ENVHOME = getenv("HOME");
WALLPAPER = std::string(ENVHOME) + WALLPAPER.substr(1);
}
if (!std::filesystem::exists(WALLPAPER)) {
parseError = "wallpaper failed (no such file)";
return;
}
if (std::find(m_dRequestedPreloads.begin(), m_dRequestedPreloads.end(), WALLPAPER) == m_dRequestedPreloads.end() && !g_pHyprpaper->isPreloaded(WALLPAPER)) {
parseError = "wallpaper failed (not preloaded)";
return;
}
g_pHyprpaper->clearWallpaperFromMonitor(MONITOR);
g_pHyprpaper->m_mMonitorActiveWallpapers[MONITOR] = WALLPAPER;
g_pHyprpaper->m_mMonitorWallpaperRenderData[MONITOR].contain = contain;
}
void CConfigManager::handlePreload(const std::string& COMMAND, const std::string& VALUE) {
auto WALLPAPER = VALUE;
if (WALLPAPER[0] == '~') {
static const char* const ENVHOME = getenv("HOME");
WALLPAPER = std::string(ENVHOME) + WALLPAPER.substr(1);
}
if (!std::filesystem::exists(WALLPAPER)) {
parseError = "preload failed (no such file)";
return;
}
m_dRequestedPreloads.emplace_back(WALLPAPER);
}
void CConfigManager::handleUnload(const std::string& COMMAND, const std::string& VALUE) {
auto WALLPAPER = VALUE;
if (VALUE == "all") {
handleUnloadAll(COMMAND, VALUE);
return;
}
if (WALLPAPER[0] == '~') {
static const char* const ENVHOME = getenv("HOME");
WALLPAPER = std::string(ENVHOME) + WALLPAPER.substr(1);
}
g_pHyprpaper->unloadWallpaper(WALLPAPER);
}
void CConfigManager::handleUnloadAll(const std::string& COMMAND, const std::string& VALUE) {
std::vector<std::string> toUnload;
for (auto& [name, target] : g_pHyprpaper->m_mWallpaperTargets) {
bool exists = false;
for (auto& [mon, target2] : g_pHyprpaper->m_mMonitorActiveWallpaperTargets) {
if (&target == target2) {
exists = true;
break;
}
}
if (exists)
g_logger->log(LOG_ERR, "Failed parsing wallpaper for key {}", key);
continue;
toUnload.emplace_back(name);
}
for (auto& tu : toUnload)
g_pHyprpaper->unloadWallpaper(tu);
const auto RESOLVE_PATH = getFullPath(path);
if (!RESOLVE_PATH) {
g_logger->log(LOG_ERR, "Failed to resolve path {}: {}", path, RESOLVE_PATH.error());
continue;
}
// trim from both ends
std::string CConfigManager::trimPath(std::string path) {
// trims whitespaces, tabs and new line feeds
size_t pathStartIndex = path.find_first_not_of(" \t\r\n");
size_t pathEndIndex = path.find_last_not_of(" \t\r\n");
return path.substr(pathStartIndex, pathEndIndex - pathStartIndex + 1);
result.emplace_back(SSetting{.monitor = std::move(monitor), .fitMode = std::move(fitMode), .path = RESOLVE_PATH.value()});
}
return result;
}

View file

@ -1,30 +1,34 @@
#pragma once
#include "../defines.hpp"
class CIPCSocket;
#include "../helpers/Memory.hpp"
#include <hyprlang.hpp>
#include <vector>
class CConfigManager {
public:
// gets all the data from the config
CConfigManager();
CConfigManager(const std::string& configPath);
~CConfigManager() = default;
std::deque<std::string> m_dRequestedPreloads;
std::string getMainConfigPath();
CConfigManager(const CConfigManager&) = delete;
CConfigManager(CConfigManager&) = delete;
CConfigManager(CConfigManager&&) = delete;
private:
std::string parseError;
void parseLine(std::string&);
std::string removeBeginEndSpacesTabs(std::string in);
void parseKeyword(const std::string&, const std::string&);
void handleWallpaper(const std::string&, const std::string&);
void handlePreload(const std::string&, const std::string&);
void handleUnload(const std::string&, const std::string&);
void handleUnloadAll(const std::string&, const std::string&);
std::string trimPath(std::string path);
friend class CIPCSocket;
struct SSetting {
std::string monitor, fitMode, path;
uint32_t id = 0;
};
inline std::unique_ptr<CConfigManager> g_pConfigManager;
constexpr static const uint32_t SETTING_INVALID = 0;
void init();
Hyprlang::CConfig* hyprlang();
std::vector<SSetting> getSettings();
private:
Hyprlang::CConfig m_config;
std::string m_currentConfigPath;
};
inline UP<CConfigManager> g_config;

View file

@ -0,0 +1,103 @@
#include "WallpaperMatcher.hpp"
#include <algorithm>
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<CConfigManager::SSetting>&& 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) {
m_monitorNames.emplace_back(s);
recalcStates();
}
void CWallpaperMatcher::unregisterOutput(const std::string_view& s) {
std::erase(m_monitorNames, 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::contains(m_monitorNames, s);
}
std::optional<CWallpaperMatcher::rw<const CConfigManager::SSetting>> CWallpaperMatcher::getSetting(const std::string_view& monName) {
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::rw<const CConfigManager::SSetting>> CWallpaperMatcher::matchSetting(const std::string_view& monName) {
// match explicit
for (const auto& s : m_settings) {
if (s.monitor != monName)
continue;
return s;
}
// match wildcard
for (const auto& s : m_settings) {
if (s.monitor.empty())
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<std::string_view> namesChanged;
for (const auto& name : m_monitorNames) {
const auto STATE = matchSetting(name);
auto& activeState = getState(name);
if (!STATE)
activeState = {.name = name};
else {
activeState.name = name;
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);
}
}

View file

@ -0,0 +1,53 @@
#pragma once
#include <optional>
#include "ConfigManager.hpp"
#include <hyprutils/signal/Signal.hpp>
class CWallpaperMatcher {
public:
template <typename T>
using rw = std::reference_wrapper<T>;
CWallpaperMatcher() = default;
~CWallpaperMatcher() = default;
CWallpaperMatcher(const CWallpaperMatcher&) = delete;
CWallpaperMatcher(CWallpaperMatcher&) = delete;
CWallpaperMatcher(CWallpaperMatcher&&) = delete;
void addState(CConfigManager::SSetting&&);
void addStates(std::vector<CConfigManager::SSetting>&&);
void registerOutput(const std::string_view&);
void unregisterOutput(const std::string_view&);
bool outputExists(const std::string_view&);
std::optional<rw<const CConfigManager::SSetting>> getSetting(const std::string_view& monName);
struct {
Hyprutils::Signal::CSignalT<const std::string_view&> monitorConfigChanged;
} m_events;
private:
void recalcStates();
std::optional<rw<const CConfigManager::SSetting>> matchSetting(const std::string_view& monName);
std::vector<CConfigManager::SSetting> m_settings;
struct SMonitorState {
std::string name;
uint32_t currentID = CConfigManager::SETTING_INVALID;
};
std::vector<std::string> m_monitorNames;
std::vector<SMonitorState> m_monitorStates;
uint32_t m_maxId = 0;
SMonitorState& getState(const std::string_view& monName);
};
inline UP<CWallpaperMatcher> g_matcher = makeUnique<CWallpaperMatcher>();

View file

@ -1,60 +0,0 @@
#include "Log.hpp"
#include "../includes.hpp"
#include <fstream>
#include <iostream>
void Debug::log(LogLevel level, const char* fmt, ...) {
std::string levelstr = "";
switch (level) {
case LOG:
levelstr = "[LOG] ";
break;
case WARN:
levelstr = "[WARN] ";
break;
case ERR:
levelstr = "[ERR] ";
break;
case CRIT:
levelstr = "[CRITICAL] ";
break;
case INFO:
levelstr = "[INFO] ";
break;
default:
break;
}
char buf[LOGMESSAGESIZE] = "";
char* outputStr;
int logLen;
va_list args;
va_start(args, fmt);
logLen = vsnprintf(buf, sizeof buf, fmt, args);
va_end(args);
if ((long unsigned int)logLen < sizeof buf) {
outputStr = strdup(buf);
} else {
outputStr = (char*)malloc(logLen + 1);
if (!outputStr) {
printf("CRITICAL: Cannot alloc size %d for log! (Out of memory?)", logLen + 1);
return;
}
va_start(args, fmt);
vsnprintf(outputStr, logLen + 1U, fmt, args);
va_end(args);
}
// hyprpaper only logs to stdout
std::cout << levelstr << outputStr << "\n";
// free the log
free(outputStr);
}

View file

@ -1,17 +0,0 @@
#pragma once
#include <string>
#define LOGMESSAGESIZE 1024
enum LogLevel {
NONE = -1,
LOG = 0,
WARN,
ERR,
CRIT,
INFO
};
namespace Debug {
void log(LogLevel level, const char* fmt, ...);
}

View file

@ -1,8 +1,8 @@
#pragma once
#include "includes.hpp"
#include "debug/Log.hpp"
#include "helpers/Vector2D.hpp"
#include <cstdlib>
#include <string>
#include "helpers/Logger.hpp"
// git stuff
#ifndef GIT_COMMIT_HASH
@ -17,3 +17,10 @@
#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(); \
}

View file

@ -1,160 +0,0 @@
#include "Events.hpp"
#include "../Hyprpaper.hpp"
void Events::geometry(void *data, wl_output *output, int32_t x, int32_t y, int32_t width_mm, int32_t height_mm, int32_t subpixel, const char *make, const char *model, int32_t transform) {
// ignored
}
void Events::mode(void *data, wl_output *output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
const auto PMONITOR = (SMonitor*)data;
PMONITOR->size = Vector2D(width, height);
}
void Events::done(void *data, wl_output *wl_output) {
const auto PMONITOR = (SMonitor*)data;
PMONITOR->readyForLS = true;
std::lock_guard<std::mutex> lg(g_pHyprpaper->m_mtTickMutex);
g_pHyprpaper->tick(true);
}
void Events::scale(void *data, wl_output *wl_output, int32_t scale) {
const auto PMONITOR = (SMonitor*)data;
PMONITOR->scale = scale;
}
void Events::name(void *data, wl_output *wl_output, const char *name) {
const auto PMONITOR = (SMonitor*)data;
PMONITOR->name = name;
}
void Events::description(void *data, wl_output *wl_output, const char *description) {
const auto PMONITOR = (SMonitor*)data;
PMONITOR->description = description;
}
void Events::handleCapabilities(void *data, wl_seat *wl_seat, uint32_t capabilities) {
if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
wl_pointer_add_listener(wl_seat_get_pointer(wl_seat), &pointerListener, wl_seat);
}
}
void Events::handlePointerLeave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) {
// ignored
wl_surface_commit(surface);
g_pHyprpaper->m_pLastMonitor = nullptr;
}
void Events::handlePointerAxis(void *data, wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) {
// ignored
}
void Events::handlePointerMotion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
// ignored
if (g_pHyprpaper->m_pLastMonitor) {
wl_surface_commit(g_pHyprpaper->m_pLastMonitor->pCurrentLayerSurface->pSurface);
}
}
void Events::handlePointerButton(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t button_state) {
// ignored
}
void Events::handlePointerEnter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) {
for (auto& mon : g_pHyprpaper->m_vMonitors) {
if (mon->pCurrentLayerSurface->pSurface == surface) {
g_pHyprpaper->m_pLastMonitor = mon.get();
wl_surface_set_buffer_scale(mon->pCurrentLayerSurface->pCursorSurface, mon->scale);
wl_surface_attach(mon->pCurrentLayerSurface->pCursorSurface, wl_cursor_image_get_buffer(mon->pCurrentLayerSurface->pCursorImg), 0, 0);
wl_pointer_set_cursor(wl_pointer, serial, mon->pCurrentLayerSurface->pCursorSurface, mon->pCurrentLayerSurface->pCursorImg->hotspot_x / mon->scale, mon->pCurrentLayerSurface->pCursorImg->hotspot_y / mon->scale);
wl_surface_commit(mon->pCurrentLayerSurface->pCursorSurface);
}
}
}
void Events::ls_configure(void *data, zwlr_layer_surface_v1 *surface, uint32_t serial, uint32_t width, uint32_t height) {
const auto PLAYERSURFACE = (CLayerSurface*)data;
PLAYERSURFACE->m_pMonitor->size = Vector2D(width, height);
PLAYERSURFACE->m_pMonitor->wantsReload = true;
PLAYERSURFACE->m_pMonitor->configureSerial = serial;
PLAYERSURFACE->m_pMonitor->wantsACK = true;
PLAYERSURFACE->m_pMonitor->initialized = true;
Debug::log(LOG, "configure for %s", PLAYERSURFACE->m_pMonitor->name.c_str());
}
void Events::handleLSClosed(void *data, zwlr_layer_surface_v1 *zwlr_layer_surface_v1) {
const auto PLAYERSURFACE = (CLayerSurface*)data;
for (auto& m : g_pHyprpaper->m_vMonitors) {
std::erase_if(m->layerSurfaces, [&](const auto& other) { return other.get() == PLAYERSURFACE; });
if (m->pCurrentLayerSurface == PLAYERSURFACE) {
if (m->layerSurfaces.empty()) {
m->pCurrentLayerSurface = nullptr;
} else {
m->pCurrentLayerSurface = m->layerSurfaces.begin()->get();
g_pHyprpaper->recheckMonitor(m.get());
}
}
}
}
void Events::handleGlobal(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) {
if (strcmp(interface, wl_compositor_interface.name) == 0) {
g_pHyprpaper->m_sCompositor = (wl_compositor *)wl_registry_bind(registry, name, &wl_compositor_interface, 4);
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
g_pHyprpaper->m_sSHM = (wl_shm *)wl_registry_bind(registry, name, &wl_shm_interface, 1);
} else if (strcmp(interface, wl_output_interface.name) == 0) {
g_pHyprpaper->m_mtTickMutex.lock();
const auto PMONITOR = g_pHyprpaper->m_vMonitors.emplace_back(std::make_unique<SMonitor>()).get();
PMONITOR->wayland_name = name;
PMONITOR->name = "";
PMONITOR->output = (wl_output *)wl_registry_bind(registry, name, &wl_output_interface, 4);
wl_output_add_listener(PMONITOR->output, &Events::outputListener, PMONITOR);
g_pHyprpaper->m_mtTickMutex.unlock();
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
g_pHyprpaper->createSeat((wl_seat*)wl_registry_bind(registry, name, &wl_seat_interface, 1));
} else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
g_pHyprpaper->m_sLayerShell = (zwlr_layer_shell_v1*)wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1);
} else if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0 && !g_pHyprpaper->m_bNoFractionalScale) {
g_pHyprpaper->m_sFractionalScale = (wp_fractional_scale_manager_v1*)wl_registry_bind(registry, name, &wp_fractional_scale_manager_v1_interface, 1);
} else if (strcmp(interface, wp_viewporter_interface.name) == 0) {
g_pHyprpaper->m_sViewporter = (wp_viewporter*)wl_registry_bind(registry, name, &wp_viewporter_interface, 1);
}
}
void Events::handleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name) {
for (auto& m : g_pHyprpaper->m_vMonitors) {
if (m->wayland_name == name) {
Debug::log(LOG, "Destroying output %s", m->name.c_str());
g_pHyprpaper->clearWallpaperFromMonitor(m->name);
std::erase_if(g_pHyprpaper->m_vMonitors, [&](const auto& other) { return other->wayland_name == name; });
return;
}
}
}
void Events::handlePreferredScale(void *data, wp_fractional_scale_v1* fractionalScaleInfo, uint32_t scale) {
const double SCALE = scale / 120.0;
CLayerSurface *const pLS = (CLayerSurface*)data;
Debug::log(LOG, "handlePreferredScale: %.2lf for %lx", SCALE, pLS);
if (pLS->fScale != SCALE) {
pLS->fScale = SCALE;
std::lock_guard<std::mutex> lg(g_pHyprpaper->m_mtTickMutex);
g_pHyprpaper->tick(true);
}
}

View file

@ -1,51 +0,0 @@
#pragma once
#include "../defines.hpp"
namespace Events {
void geometry(void *data, wl_output *output, int32_t x, int32_t y, int32_t width_mm, int32_t height_mm, int32_t subpixel, const char *make, const char *model, int32_t transform);
void mode(void *data, wl_output *output, uint32_t flags, int32_t width, int32_t height, int32_t refresh);
void done(void *data, wl_output *wl_output);
void scale(void *data, wl_output *wl_output, int32_t scale);
void name(void *data, wl_output *wl_output, const char *name);
void description(void *data, wl_output *wl_output, const char *description);
void ls_configure(void *data, zwlr_layer_surface_v1 *surface, uint32_t serial, uint32_t width, uint32_t height);
void handleLSClosed(void *data, zwlr_layer_surface_v1 *zwlr_layer_surface_v1);
void handleGlobal(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version);
void handleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name);
void handleCapabilities(void *data, wl_seat *wl_seat, uint32_t capabilities);
void handlePointerMotion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y);
void handlePointerButton(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t button_state);
void handlePointerAxis(void *data, wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value);
void handlePointerEnter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y);
void handlePointerLeave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface);
void handlePreferredScale(void *data, wp_fractional_scale_v1 *wp_fractional_scale_v1, uint32_t scale);
inline const wl_output_listener outputListener = {.geometry = geometry, .mode = mode, .done = done, .scale = scale, .name = name, .description = description};
inline const zwlr_layer_surface_v1_listener layersurfaceListener = { .configure = ls_configure, .closed = handleLSClosed };
inline const struct wl_registry_listener registryListener = { .global = handleGlobal, .global_remove = handleGlobalRemove };
inline const wl_pointer_listener pointerListener = { .enter = handlePointerEnter, .leave = handlePointerLeave, .motion = handlePointerMotion, .button = handlePointerButton, .axis = handlePointerAxis };
inline const wl_seat_listener seatListener = { .capabilities = handleCapabilities };
inline const wp_fractional_scale_v1_listener scaleListener = { .preferred_scale = handlePreferredScale };
}

View file

@ -1,75 +0,0 @@
#include "Jpeg.hpp"
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
#error "your system is not little endian, jpeg will not work, ping vaxry or something"
#endif
cairo_surface_t* JPEG::createSurfaceFromJPEG(const std::string& path) {
if (!std::filesystem::exists(path)) {
Debug::log(ERR, "createSurfaceFromJPEG: file doesn't exist??");
exit(1);
}
if (__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__) {
Debug::log(CRIT, "tried to load a jpeg on a big endian system! ping vaxry he is lazy.");
exit(1);
}
void* imageRawData;
struct stat fileInfo = {};
const auto FD = open(path.c_str(), O_RDONLY);
fstat(FD, &fileInfo);
imageRawData = malloc(fileInfo.st_size);
read(FD, imageRawData, fileInfo.st_size);
close(FD);
// now the JPEG is in the memory
jpeg_decompress_struct decompressStruct = {};
jpeg_error_mgr errorManager = {};
decompressStruct.err = jpeg_std_error(&errorManager);
jpeg_create_decompress(&decompressStruct);
jpeg_mem_src(&decompressStruct, (const unsigned char*)imageRawData, fileInfo.st_size);
jpeg_read_header(&decompressStruct, true);
decompressStruct.out_color_space = JCS_EXT_BGRA;
// decompress
jpeg_start_decompress(&decompressStruct);
auto cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, decompressStruct.output_width, decompressStruct.output_height);
if (cairo_surface_status(cairoSurface) != CAIRO_STATUS_SUCCESS) {
Debug::log(ERR, "createSurfaceFromJPEG: Cairo Failed (?)");
exit(1);
}
const auto CAIRODATA = cairo_image_surface_get_data(cairoSurface);
const auto CAIROSTRIDE = cairo_image_surface_get_stride(cairoSurface);
JSAMPROW rowRead;
while (decompressStruct.output_scanline < decompressStruct.output_height) {
const auto PROW = CAIRODATA + (decompressStruct.output_scanline * CAIROSTRIDE);
rowRead = PROW;
jpeg_read_scanlines(&decompressStruct, &rowRead, 1);
}
cairo_surface_mark_dirty(cairoSurface);
cairo_surface_set_mime_data(cairoSurface, CAIRO_MIME_TYPE_JPEG, (const unsigned char*)imageRawData, fileInfo.st_size, free, imageRawData);
jpeg_finish_decompress(&decompressStruct);
jpeg_destroy_decompress(&decompressStruct);
return cairoSurface;
}

View file

@ -1,8 +0,0 @@
#pragma once
#include "../defines.hpp"
#include <jpeglib.h>
namespace JPEG {
cairo_surface_t* createSurfaceFromJPEG(const std::string&);
};

12
src/helpers/Logger.hpp Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include <hyprutils/cli/Logger.hpp>
#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<Hyprutils::CLI::CLogger> g_logger = makeUnique<Hyprutils::CLI::CLogger>();

12
src/helpers/Memory.hpp Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include <hyprutils/memory/SharedPtr.hpp>
#include <hyprutils/memory/UniquePtr.hpp>
#include <hyprutils/memory/Atomic.hpp>
using namespace Hyprutils::Memory;
#define SP CSharedPointer
#define WP CWeakPointer
#define UP CUniquePointer
#define ASP CAtomicSharedPointer

View file

@ -1,26 +0,0 @@
#include "MiscFunctions.hpp"
#include <array>
#include "../debug/Log.hpp"
#include <memory>
bool vectorDeltaLessThan(const Vector2D& a, const Vector2D& b, const float& delta) {
return std::abs(a.x - b.x) < delta && std::abs(a.y - b.y) < delta;
}
bool vectorDeltaLessThan(const Vector2D& a, const Vector2D& b, const Vector2D& delta) {
return std::abs(a.x - b.x) < delta.x && std::abs(a.y - b.y) < delta.y;
}
std::string execAndGet(const char* cmd) {
std::array<char, 128> buffer;
std::string result;
const std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
if (!pipe) {
Debug::log(ERR, "execAndGet: failed in pipe");
return "";
}
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
result += buffer.data();
}
return result;
}

View file

@ -1,7 +0,0 @@
#pragma once
#include <string>
#include "Vector2D.hpp"
bool vectorDeltaLessThan(const Vector2D& a, const Vector2D& b, const float& delta);
bool vectorDeltaLessThan(const Vector2D& a, const Vector2D& b, const Vector2D& delta);
std::string execAndGet(const char*);

View file

@ -1,27 +0,0 @@
#pragma once
#include "../defines.hpp"
#include "PoolBuffer.hpp"
#include "../render/LayerSurface.hpp"
struct SMonitor {
std::string name = "";
std::string description = "";
wl_output* output = nullptr;
uint32_t wayland_name = 0;
Vector2D size;
int scale;
bool readyForLS = false;
bool hasATarget = true;
uint32_t configureSerial = 0;
SPoolBuffer buffer;
bool wantsReload = false;
bool wantsACK = false;
bool initialized = false;
std::vector<std::unique_ptr<CLayerSurface>> layerSurfaces;
CLayerSurface* pCurrentLayerSurface = nullptr;
};

View file

@ -1,17 +0,0 @@
#pragma once
#include "../defines.hpp"
class CWallpaperTarget;
struct SPoolBuffer {
wl_buffer* buffer = nullptr;
cairo_surface_t* surface = nullptr;
cairo_t* cairo = nullptr;
void* data = nullptr;
size_t size = 0;
std::string name = "";
std::string target = "";
Vector2D pixelSize;
};

View file

@ -1,23 +0,0 @@
#include "Vector2D.hpp"
Vector2D::Vector2D(double xx, double yy) {
x = xx;
y = yy;
}
Vector2D::Vector2D() { x = 0; y = 0; }
Vector2D::~Vector2D() = default;
double Vector2D::normalize() {
// get max abs
const auto max = abs(x) > abs(y) ? abs(x) : abs(y);
x /= max;
y /= max;
return max;
}
Vector2D Vector2D::floor() const {
return {static_cast<int>(x), static_cast<int>(y)};
}

View file

@ -1,39 +0,0 @@
#pragma once
#include <cmath>
class Vector2D {
public:
Vector2D(double, double);
Vector2D();
~Vector2D();
double x = 0;
double y = 0;
// returns the scale
double normalize();
Vector2D operator+(const Vector2D a) const {
return Vector2D(this->x + a.x, this->y + a.y);
}
Vector2D operator-(const Vector2D a) const {
return Vector2D(this->x - a.x, this->y - a.y);
}
Vector2D operator*(const float a) const {
return Vector2D(this->x * a, this->y * a);
}
Vector2D operator/(const float a) const {
return Vector2D(this->x / a, this->y / a);
}
bool operator==(const Vector2D& a) const {
return a.x == x && a.y == y;
}
bool operator!=(const Vector2D& a) const {
return a.x != x || a.y != y;
}
Vector2D floor() const;
};

View file

@ -1,47 +0,0 @@
#pragma once
#include <vector>
#include <deque>
#include <iostream>
#include <fstream>
#include <cstring>
#include <string>
#include <pthread.h>
#include <cmath>
#include <cmath>
#define class _class
#define namespace _namespace
#define static
extern "C" {
#include "wlr-layer-shell-unstable-v1-protocol.h"
#include "xdg-shell-protocol.h"
#include "fractional-scale-v1-protocol.h"
#include "viewporter-protocol.h"
#include <wayland-client.h>
#include <wayland-cursor.h>
}
#undef class
#undef namespace
#undef static
#include <GLES3/gl32.h>
#include <GLES3/gl3ext.h>
#include <cassert>
#include <cairo.h>
#include <cairo/cairo.h>
#include <fcntl.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-client.h>
#include <algorithm>
#include <filesystem>
#include <thread>
#include <unordered_map>

View file

@ -0,0 +1,86 @@
#include "HyprlandSocket.hpp"
#include <pwd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <format>
#include <hyprutils/memory/Casts.hpp>
using namespace Hyprutils::Memory;
static int getUID() {
const auto UID = getuid();
const auto PWUID = getpwuid(UID);
return PWUID ? PWUID->pw_uid : UID;
}
static std::string getRuntimeDir() {
const auto XDG = getenv("XDG_RUNTIME_DIR");
if (!XDG) {
const std::string USERID = std::to_string(getUID());
return "/run/user/" + USERID + "/hypr";
}
return std::string{XDG} + "/hypr";
}
std::expected<std::string, std::string> 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<sockaddr*>(&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;
}

View file

@ -0,0 +1,9 @@
#pragma once
#include <string_view>
#include <string>
#include <expected>
namespace HyprlandSocket {
std::expected<std::string, std::string> getFromSocket(const std::string& cmd);
};

147
src/ipc/IPC.cpp Normal file
View file

@ -0,0 +1,147 @@
#include "IPC.hpp"
#include "../helpers/Logger.hpp"
#include "../config/WallpaperMatcher.hpp"
#include "../ui/UI.hpp"
#include <filesystem>
using namespace IPC;
using namespace std::string_literals;
constexpr const char* SOCKET_NAME = ".hyprpaper.sock";
static SP<CHyprpaperCoreImpl> g_coreImpl;
CWallpaperObject::CWallpaperObject(SP<CHyprpaperWallpaperObject>&& 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),
.path = 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<CHyprpaperCoreImpl>(1, [this](SP<Hyprwire::IObject> obj) {
auto manager = m_managers.emplace_back(makeShared<CHyprpaperCoreManagerObject>(std::move(obj)));
manager->setDestroy([this, weak = WP<CHyprpaperCoreManagerObject>{manager}] { std::erase(m_managers, weak); });
manager->setOnDestroy([this, weak = WP<CHyprpaperCoreManagerObject>{manager}] { std::erase(m_managers, weak); });
manager->setGetWallpaperObject([this, weak = WP<CHyprpaperCoreManagerObject>{manager}](uint32_t id) {
if (!weak)
return;
m_wallpaperObjects.emplace_back(makeShared<CWallpaperObject>(
makeShared<CHyprpaperWallpaperObject>(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());
}
}

46
src/ipc/IPC.hpp Normal file
View file

@ -0,0 +1,46 @@
#pragma once
#include "../helpers/Memory.hpp"
#include <hyprwire/hyprwire.hpp>
#include <hyprpaper_core-server.hpp>
namespace IPC {
class CWallpaperObject {
public:
CWallpaperObject(SP<CHyprpaperWallpaperObject>&& obj);
~CWallpaperObject() = default;
private:
void apply();
SP<CHyprpaperWallpaperObject> 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<Hyprwire::IServerSocket> m_socket;
std::string m_socketPath = "";
std::vector<SP<CHyprpaperCoreManagerObject>> m_managers;
std::vector<SP<CWallpaperObject>> m_wallpaperObjects;
friend class CWallpaperObject;
};
inline UP<CSocket> g_IPCSocket;
};

View file

@ -1,130 +0,0 @@
#include "Socket.hpp"
#include "../Hyprpaper.hpp"
#include <netinet/in.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <cerrno>
void CIPCSocket::initialize() {
std::thread([&]() {
const auto SOCKET = socket(AF_UNIX, SOCK_STREAM, 0);
if (SOCKET < 0) {
Debug::log(ERR, "Couldn't start the hyprpaper Socket. (1) IPC will not work.");
return;
}
sockaddr_un SERVERADDRESS = {.sun_family = AF_UNIX};
const auto HISenv = getenv("HYPRLAND_INSTANCE_SIGNATURE");
std::string socketPath = HISenv ? "/tmp/hypr/" + std::string(HISenv) + "/.hyprpaper.sock" : "/tmp/hypr/.hyprpaper.sock";
if (!HISenv) {
mkdir("/tmp/hypr", S_IRWXU | S_IRWXG);
}
unlink(socketPath.c_str());
strcpy(SERVERADDRESS.sun_path, socketPath.c_str());
bind(SOCKET, (sockaddr*)&SERVERADDRESS, SUN_LEN(&SERVERADDRESS));
// 10 max queued.
listen(SOCKET, 10);
sockaddr_in clientAddress = {};
socklen_t clientSize = sizeof(clientAddress);
char readBuffer[1024] = {0};
Debug::log(LOG, "hyprpaper socket started at %s (fd: %i)", socketPath.c_str(), SOCKET);
while(1) {
const auto ACCEPTEDCONNECTION = accept(SOCKET, (sockaddr*)&clientAddress, &clientSize);
Debug::log(LOG, "Accepted incoming socket connection request on fd %i", ACCEPTEDCONNECTION);
if (ACCEPTEDCONNECTION < 0) {
Debug::log(ERR, "Couldn't listen on the hyprpaper Socket. (3) IPC will not work.");
break;
}
std::lock_guard<std::mutex> lg(g_pHyprpaper->m_mtTickMutex);
auto messageSize = read(ACCEPTEDCONNECTION, readBuffer, 1024);
readBuffer[messageSize == 1024 ? 1023 : messageSize] = '\0';
std::string request(readBuffer);
m_szRequest = request;
m_bRequestReady = true;
g_pHyprpaper->tick(true);
while (1) {
if (m_bReplyReady)
break;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
write(ACCEPTEDCONNECTION, m_szReply.c_str(), m_szReply.length());
close(ACCEPTEDCONNECTION);
m_bReplyReady = false;
m_szReply = "";
}
close(SOCKET);
}).detach();
}
bool CIPCSocket::mainThreadParseRequest() {
if (!m_bRequestReady)
return false;
std::string copy = m_szRequest;
// now we can work on the copy
if (copy == "")
return false;
Debug::log(LOG, "Received a request: %s", copy.c_str());
// parse
if (copy.find("wallpaper") == 0 || copy.find("preload") == 0 || copy.find("unload") == 0) {
g_pConfigManager->parseError = ""; // reset parse error
g_pConfigManager->parseKeyword(copy.substr(0, copy.find_first_of(' ')), copy.substr(copy.find_first_of(' ') + 1));
if (!g_pConfigManager->parseError.empty()) {
m_szReply = g_pConfigManager->parseError;
m_bReplyReady = true;
m_bRequestReady = false;
return false;
}
}
else {
m_szReply = "invalid command";
m_bReplyReady = true;
m_bRequestReady = false;
return false;
}
m_szReply = "ok";
m_bReplyReady = true;
m_bRequestReady = false;
return true;
}

View file

@ -1,23 +0,0 @@
#pragma once
#include "../defines.hpp"
#include <mutex>
class CIPCSocket {
public:
void initialize();
bool mainThreadParseRequest();
private:
std::mutex m_mtRequestMutex;
std::string m_szRequest = "";
std::string m_szReply = "";
bool m_bRequestReady = false;
bool m_bReplyReady = false;
};
inline std::unique_ptr<CIPCSocket> g_pIPCSocket;

View file

@ -1,34 +1,40 @@
#include <iostream>
#include "defines.hpp"
#include "Hyprpaper.hpp"
#include "helpers/Logger.hpp"
#include "ui/UI.hpp"
#include "config/ConfigManager.hpp"
int main(int argc, char** argv, char** envp) {
Debug::log(LOG, "Welcome to hyprpaper!\nbuilt from commit %s (%s)", GIT_COMMIT_HASH, GIT_COMMIT_MESSAGE);
#include <hyprutils/cli/ArgumentParser.hpp>
// parse some args
std::string configPath;
bool noFractional = false;
for (int i = 1; i < argc; ++i) {
if ((!strcmp(argv[i], "-c") || !strcmp(argv[i], "--config")) && argc >= i + 2) {
configPath = std::string(argv[++i]);
Debug::log(LOG, "Using config location %s.", configPath.c_str());
} else if (!strcmp(argv[i], "--no-fractional") || !strcmp(argv[i], "-n")) {
noFractional = true;
Debug::log(LOG, "Disabling fractional scaling support!");
} else {
std::cout << "Hyprpaper usage: hyprpaper [arg [...]].\n\nArguments:\n" <<
"--help -h | Show this help message\n" <<
"--config -c | Specify config file to use\n" <<
"--no-fractional -n | Disable fractional scaling support\n";
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("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;
}
// starts
g_pHyprpaper = std::make_unique<CHyprpaper>();
g_pHyprpaper->m_szExplicitConfigPath = configPath;
g_pHyprpaper->m_bNoFractionalScale = noFractional;
g_pHyprpaper->init();
if (parser.getBool("verbose").value_or(false))
g_logger->setLogLevel(LOG_TRACE);
g_logger->log(LOG_DEBUG, "Welcome to hyprpaper!\nbuilt from commit {} ({})", GIT_COMMIT_HASH, GIT_COMMIT_MESSAGE);
g_config = makeUnique<CConfigManager>(std::string{parser.getString("config").value_or("")});
g_config->init();
g_ui = makeUnique<CUI>();
g_ui->run();
return 0;
}

View file

@ -1,66 +0,0 @@
#include "LayerSurface.hpp"
#include "../Hyprpaper.hpp"
CLayerSurface::CLayerSurface(SMonitor* pMonitor) {
m_pMonitor = pMonitor;
pSurface = wl_compositor_create_surface(g_pHyprpaper->m_sCompositor);
pCursorSurface = wl_compositor_create_surface(g_pHyprpaper->m_sCompositor);
if (!pSurface) {
Debug::log(CRIT, "The compositor did not allow hyprpaper a surface!");
exit(1);
}
const auto PINPUTREGION = wl_compositor_create_region(g_pHyprpaper->m_sCompositor);
if (!PINPUTREGION) {
Debug::log(CRIT, "The compositor did not allow hyprpaper a region!");
exit(1);
}
wl_surface_set_input_region(pSurface, PINPUTREGION);
pLayerSurface = zwlr_layer_shell_v1_get_layer_surface(g_pHyprpaper->m_sLayerShell, pSurface, pMonitor->output, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, "hyprpaper");
if (!pLayerSurface) {
Debug::log(CRIT, "The compositor did not allow hyprpaper a layersurface!");
exit(1);
}
zwlr_layer_surface_v1_set_size(pLayerSurface, 0, 0);
zwlr_layer_surface_v1_set_anchor(pLayerSurface, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
zwlr_layer_surface_v1_set_exclusive_zone(pLayerSurface, -1);
zwlr_layer_surface_v1_add_listener(pLayerSurface, &Events::layersurfaceListener, this);
wl_surface_commit(pSurface);
wl_region_destroy(PINPUTREGION);
// fractional scale, if supported by the compositor
if (g_pHyprpaper->m_sFractionalScale) {
pFractionalScaleInfo = wp_fractional_scale_manager_v1_get_fractional_scale(g_pHyprpaper->m_sFractionalScale, pSurface);
wp_fractional_scale_v1_add_listener(pFractionalScaleInfo, &Events::scaleListener, this);
pViewport = wp_viewporter_get_viewport(g_pHyprpaper->m_sViewporter, pSurface);
wl_surface_commit(pSurface);
}
wl_display_flush(g_pHyprpaper->m_sDisplay);
}
CLayerSurface::~CLayerSurface() {
if (pCursorTheme)
wl_cursor_theme_destroy(pCursorTheme);
if (g_pHyprpaper->m_sFractionalScale && pFractionalScaleInfo) {
wp_fractional_scale_v1_destroy(pFractionalScaleInfo);
wp_viewport_destroy(pViewport);
}
zwlr_layer_surface_v1_destroy(pLayerSurface);
wl_surface_destroy(pSurface);
wl_display_flush(g_pHyprpaper->m_sDisplay);
}

View file

@ -1,24 +0,0 @@
#pragma once
#include "../defines.hpp"
struct SMonitor;
class CLayerSurface {
public:
explicit CLayerSurface(SMonitor*);
~CLayerSurface();
SMonitor* m_pMonitor = nullptr;
zwlr_layer_surface_v1* pLayerSurface = nullptr;
wl_surface* pSurface = nullptr;
wl_cursor_theme* pCursorTheme = nullptr;
wl_cursor_image* pCursorImg = nullptr;
wl_surface* pCursorSurface = nullptr;
wp_fractional_scale_v1* pFractionalScaleInfo = nullptr;
wp_viewport* pViewport = nullptr;
double fScale = 1.0;
};

View file

@ -1,52 +0,0 @@
#include "WallpaperTarget.hpp"
#include <magic.h>
CWallpaperTarget::~CWallpaperTarget() {
cairo_surface_destroy(m_pCairoSurface);
}
void CWallpaperTarget::create(const std::string& path) {
m_szPath = path;
const auto BEGINLOAD = std::chrono::system_clock::now();
cairo_surface_t* CAIROSURFACE = nullptr;
const auto len = path.length();
if (path.find(".png") == len - 4 || path.find(".PNG") == len - 4) {
CAIROSURFACE = cairo_image_surface_create_from_png(path.c_str());
} else if (path.find(".jpg") == len - 4 || path.find(".JPG") == len - 4 || path.find(".jpeg") == len - 5 || path.find(".JPEG") == len - 5) {
CAIROSURFACE = JPEG::createSurfaceFromJPEG(path);
m_bHasAlpha = false;
} else {
// magic is slow, so only use it when no recognized extension is found
auto handle = magic_open(MAGIC_NONE|MAGIC_COMPRESS);
magic_load(handle, nullptr);
const auto type_str = std::string(magic_file(handle, path.c_str()));
const auto first_word = type_str.substr(0, type_str.find(" "));
if (first_word == "PNG") {
CAIROSURFACE = cairo_image_surface_create_from_png(path.c_str());
} else if (first_word == "JPEG") {
CAIROSURFACE = JPEG::createSurfaceFromJPEG(path);
m_bHasAlpha = false;
} else {
Debug::log(CRIT, "unrecognized image %s", path.c_str());
exit(1);
}
}
if (cairo_surface_status(CAIROSURFACE) != CAIRO_STATUS_SUCCESS) {
Debug::log(CRIT, "Failed to read image %s because of:\n%s", path.c_str(), cairo_status_to_string(cairo_surface_status(CAIROSURFACE)));
exit(1);
}
m_vSize = { cairo_image_surface_get_width(CAIROSURFACE), cairo_image_surface_get_height(CAIROSURFACE) };
const auto MS = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - BEGINLOAD).count() / 1000.f;
Debug::log(LOG, "Preloaded target %s in %.2fms -> Pixel size: [%i, %i]", path.c_str(), MS, (int)m_vSize.x, (int)m_vSize.y);
m_pCairoSurface = CAIROSURFACE;
}

View file

@ -1,20 +0,0 @@
#pragma once
#include "../defines.hpp"
#include "../helpers/Jpeg.hpp"
class CWallpaperTarget {
public:
~CWallpaperTarget();
void create(const std::string& path);
std::string m_szPath;
Vector2D m_vSize;
bool m_bHasAlpha = true;
cairo_surface_t* m_pCairoSurface;
};

162
src/ui/UI.cpp Normal file
View file

@ -0,0 +1,162 @@
#include "UI.hpp"
#include "../helpers/Logger.hpp"
#include "../ipc/HyprlandSocket.hpp"
#include "../ipc/IPC.hpp"
#include "../config/WallpaperMatcher.hpp"
#include <hyprtoolkit/core/Output.hpp>
CUI::CUI() = default;
CUI::~CUI() {
m_targets.clear();
}
CWallpaperTarget::CWallpaperTarget(SP<Hyprtoolkit::IOutput> output, const std::string_view& path, Hyprtoolkit::eImageFitMode fitMode) : m_monitorName(output->port()) {
static const auto SPLASH_REPLY = HyprlandSocket::getFromSocket("/splash");
static const auto PENABLESPLASH = Hyprlang::CSimpleConfigValue<Hyprlang::INT>(g_config->hyprlang(), "splash");
static const auto PSPLASHOFFSET = Hyprlang::CSimpleConfigValue<Hyprlang::INT>(g_config->hyprlang(), "splash_offset");
static const auto PSPLASHALPHA = Hyprlang::CSimpleConfigValue<Hyprlang::FLOAT>(g_config->hyprlang(), "splash_opacity");
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})
->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);
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<float>(-*PSPLASHOFFSET)});
m_null->addChild(m_splash);
}
m_window->open();
}
void CUI::registerOutput(const SP<Hyprtoolkit::IOutput>& mon) {
g_matcher->registerOutput(mon->port());
if (IPC::g_IPCSocket)
IPC::g_IPCSocket->onNewDisplay(mon->port());
mon->m_events.removed.listenStatic([this, m = WP<Hyprtoolkit::IOutput>{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<Hyprlang::INT>(g_config->hyprlang(), "ipc");
m_backend = Hyprtoolkit::IBackend::create();
if (!m_backend)
return false;
if (*PENABLEIPC)
IPC::g_IPCSocket = makeUnique<IPC::CSocket>();
const auto MONITORS = m_backend->getOutputs();
for (const auto& m : MONITORS) {
registerOutput(m);
}
m_listeners.newMon = m_backend->m_events.outputAdded.listen([this](SP<Hyprtoolkit::IOutput> 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<Hyprtoolkit::IBackend> 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<Hyprtoolkit::IOutput> 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<Hyprtoolkit::IOutput>& mon) {
const auto TARGET = g_matcher->getSetting(mon->port());
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<CWallpaperTarget>(mon, TARGET->get().path, toFitMode(TARGET->get().fitMode)));
}

58
src/ui/UI.hpp Normal file
View file

@ -0,0 +1,58 @@
#pragma once
#include <vector>
#include <hyprtoolkit/core/Backend.hpp>
#include <hyprtoolkit/window/Window.hpp>
#include <hyprtoolkit/element/Text.hpp>
#include <hyprtoolkit/element/Null.hpp>
#include <hyprtoolkit/element/Image.hpp>
#include <hyprtoolkit/element/Rectangle.hpp>
#include <hyprutils/signal/Listener.hpp>
#include "../helpers/Memory.hpp"
class CWallpaperTarget {
public:
CWallpaperTarget(SP<Hyprtoolkit::IOutput> output, const std::string_view& path, Hyprtoolkit::eImageFitMode fitMode = Hyprtoolkit::IMAGE_FIT_MODE_COVER);
~CWallpaperTarget() = default;
CWallpaperTarget(const CWallpaperTarget&) = delete;
CWallpaperTarget(CWallpaperTarget&) = delete;
CWallpaperTarget(CWallpaperTarget&&) = delete;
std::string m_monitorName;
private:
SP<Hyprtoolkit::IWindow> m_window;
SP<Hyprtoolkit::CNullElement> m_null;
SP<Hyprtoolkit::CRectangleElement> m_bg;
SP<Hyprtoolkit::CImageElement> m_image;
SP<Hyprtoolkit::CTextElement> m_splash;
};
class CUI {
public:
CUI();
~CUI();
bool run();
SP<Hyprtoolkit::IBackend> backend();
private:
void targetChanged(const SP<Hyprtoolkit::IOutput>& mon);
void targetChanged(const std::string_view& monName);
void registerOutput(const SP<Hyprtoolkit::IOutput>& mon);
SP<Hyprtoolkit::IBackend> m_backend;
std::vector<SP<CWallpaperTarget>> m_targets;
struct {
Hyprutils::Signal::CHyprSignalListener targetChanged;
Hyprutils::Signal::CHyprSignalListener newMon;
} m_listeners;
};
inline UP<CUI> g_ui;

View file

@ -0,0 +1,16 @@
[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