From 48fa1cc8525bbb05a245b13dba7ec49afa48077a Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 29 Sep 2025 16:57:50 +0300 Subject: [PATCH 01/10] init nix tooling Signed-off-by: NotAShelf Change-Id: I6a6a6964a17ac94c2a40f0fd34f339a965abfc9a --- .envrc | 1 + flake.lock | 26 ++++++++++++++++++++++++++ flake.nix | 22 ++++++++++++++++++++++ shell.nix | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+) create mode 100644 .envrc create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 shell.nix diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..6967c23 --- /dev/null +++ b/flake.lock @@ -0,0 +1,26 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1759155593, + "narHash": "sha256-potIJyEY7ExgfiMzr44/KBODFednyzRUAE2vs4aThHs=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "13ca7febc86978bdb67c0ae94f568b189ae84eef", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..42d5d3f --- /dev/null +++ b/flake.nix @@ -0,0 +1,22 @@ +{ + description = "Wayland Wallpaper Daemon"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs?ref?nixos-unstable"; + outputs = {nixpkgs, ...}: let + systems = ["x86_64-linux" "aarch64-linux"]; + forAllSystems = f: + builtins.listToAttrs (map (system: { + name = system; + value = f system; + }) + systems); + + pkgsFor = system: nixpkgs.legacyPackages.${system}; + in { + devShells = forAllSystems (system: let + pkgs = pkgsFor system; + in { + default = pkgs.callPackage ./shell.nix {}; + }); + }; +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..54e94ef --- /dev/null +++ b/shell.nix @@ -0,0 +1,51 @@ +{pkgs ? import {}}: +pkgs.mkShell { + name = "chroma"; + buildInputs = with pkgs; [ + gnumake + + # Optional development tools + gdb + valgrind + strace + + # Code formatting and analysis + clang-tools # includes clang-format + cppcheck + + # Wayland libraries + wayland.dev + wayland-protocols + wayland-scanner + libxkbcommon + + # EGL/OpenGL libraries + libGL + mesa + + # System libraries + glibc.dev + ]; + + nativeBuildInputs = with pkgs; [ + pkg-config + ]; + + shellHook = '' + echo "Available commands:" + echo " make - Build the project" + echo " make debug - Build with debug symbols" + echo " make clean - Clean build artifacts" + echo " make install - Install to ~/.local" + echo " make help - Show all available targets" + echo + echo "Run 'make check-deps' to verify all dependencies are available." + echo + ''; + + # Environment variables for the build system + env = { + CHROMA_VERSION = "1.0.0"; + WAYLAND_DEBUG = 1; + }; +} From adc2bb2bafb3f424e260277c3fb953265cb09bd6 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 29 Sep 2025 17:44:17 +0300 Subject: [PATCH 02/10] meta: set up editorconfig Signed-off-by: NotAShelf Change-Id: I6a6a6964b4f245552027208a2650c8d60fa557a4 --- .editorconfig | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4bf2955 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,33 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true + +[*.c] +indent_style = space +indent_size = 2 +tab_width = 2 + +[*.h] +indent_style = space +indent_size = 2 +tab_width = 2 + +[Makefile] +indent_style = tab +tab_width = 4 +trim_trailing_whitespace = false + +[*.mk] +indent_style = tab +tab_width = 4 +trim_trailing_whitespace = false + +[*.sh] +indent_style = space +indent_size = 2 +tab_width = 2 + From b4fcdab7f48a0a954d51cee9750f03b70ea7f548 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 29 Sep 2025 17:55:49 +0300 Subject: [PATCH 03/10] meta: correctly ignore corrected files Signed-off-by: NotAShelf Change-Id: I6a6a6964a2b8fd7724999f28432a8b12c6143e54 --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..002cfd3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/bin +/obj + +# Generated by wayland-scanner +include/xdg-shell.h +include/wlr-layer-shell-unstable-v1.h +src/xdg-shell.c +src/wlr-layer-shell-unstable-v1.c From 87d445340a666b2f700aedcc26fabe8f771b7272 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 29 Sep 2025 17:56:08 +0300 Subject: [PATCH 04/10] chore: set up Makefile for building Signed-off-by: NotAShelf Change-Id: I6a6a6964b70cec53a4e831118fabed1ecbd5c317 --- Makefile | 173 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..64aaaf8 --- /dev/null +++ b/Makefile @@ -0,0 +1,173 @@ +PROJECT_NAME = chroma +VERSION = 1.0.0 + +# Directories +SRCDIR = src +INCDIR = include +OBJDIR = obj +BINDIR = bin +SYSTEMD_DIR = systemd +PROTOCOLDIR = protocols + +# Install directories +PREFIX ?= /usr/local +BINDIR_INSTALL = $(PREFIX)/bin +SYSTEMD_INSTALL = $(HOME)/.config/systemd/user + +# Compiler and flags +CC = gcc +CFLAGS = -std=c11 -Wall -Wextra -Werror -pedantic -O2 -g +CFLAGS += -D_GNU_SOURCE -DCHROMA_VERSION=\"$(VERSION)\" +CPPFLAGS = -I$(INCDIR) + +# Debug build flags +DEBUG_CFLAGS = -std=c11 -Wall -Wextra -Werror -pedantic -Og -g3 -DDEBUG +DEBUG_CFLAGS += -D_GNU_SOURCE -DCHROMA_VERSION=\"$(VERSION)-debug\" +DEBUG_CFLAGS += -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer + +# Libraries using pkg-config +PKG_DEPS = wayland-client wayland-egl egl glesv2 wayland-protocols +CFLAGS += $(shell pkg-config --cflags $(PKG_DEPS)) +LDFLAGS += $(shell pkg-config --libs $(PKG_DEPS)) + +# Protocol files +PROTOCOL_XML = $(PROTOCOLDIR)/xdg-shell.xml $(PROTOCOLDIR)/wlr-layer-shell-unstable-v1.xml +PROTOCOL_HEADERS = $(PROTOCOL_XML:$(PROTOCOLDIR)/%.xml=$(INCDIR)/%.h) +PROTOCOL_SOURCES = $(PROTOCOL_XML:$(PROTOCOLDIR)/%.xml=$(SRCDIR)/%.c) +PROTOCOL_OBJECTS = $(PROTOCOL_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) + +# Additional libraries +LDFLAGS += -lm -ldl + +# Source files (excluding generated protocol files) +SOURCES = $(filter-out $(PROTOCOL_SOURCES), $(wildcard $(SRCDIR)/*.c)) +OBJECTS = $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(PROTOCOL_OBJECTS) +DEPENDS = $(OBJECTS:.o=.d) + +# Default target +TARGET = $(BINDIR)/$(PROJECT_NAME) +all: $(TARGET) + +# Create directories +$(OBJDIR): + @mkdir -p $(OBJDIR) + +$(BINDIR): + @mkdir -p $(BINDIR) + +# Protocol generation +$(INCDIR)/%.h: $(PROTOCOLDIR)/%.xml | $(INCDIR) + @echo " PROTO $<" + @wayland-scanner client-header $< $@ + +$(SRCDIR)/%.c: $(PROTOCOLDIR)/%.xml + @echo " PROTO $<" + @wayland-scanner private-code $< $@ + +# Make sure include directory exists +$(INCDIR): + @mkdir -p $(INCDIR) + +# Build main executable +$(TARGET): $(PROTOCOL_HEADERS) $(OBJECTS) | $(BINDIR) + @echo " LINK $@" + @$(CC) $(OBJECTS) -o $@ $(LDFLAGS) + +# Compile source files +$(OBJDIR)/%.o: $(SRCDIR)/%.c $(PROTOCOL_HEADERS) | $(OBJDIR) + @echo " CC $<" + @$(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@ + +# Debug build +debug: CFLAGS = $(DEBUG_CFLAGS) +debug: $(TARGET) + +# Static build (for distribution) +static: LDFLAGS += -static +static: $(TARGET) + +# Check if required dependencies are available +check-deps: + @echo "Checking dependencies..." + @pkg-config --exists $(PKG_DEPS) || (echo "Missing dependencies. Install: wayland-protocols libwayland-dev libegl1-mesa-dev libgl1-mesa-dev wayland-scanner" && exit 1) + @which wayland-scanner >/dev/null || (echo "wayland-scanner not found. Please install wayland-scanner." && exit 1) + @echo "All dependencies found." + +# Install +install: $(TARGET) + @echo "Installing $(PROJECT_NAME)..." + install -Dm755 $(TARGET) $(DESTDIR)$(BINDIR_INSTALL)/$(PROJECT_NAME) + @if [ -f $(SYSTEMD_DIR)/$(PROJECT_NAME).service ]; then \ + echo "Installing systemd service..."; \ + install -Dm644 $(SYSTEMD_DIR)/$(PROJECT_NAME).service $(DESTDIR)$(SYSTEMD_INSTALL)/$(PROJECT_NAME).service; \ + fi + @echo "Installation complete." + +# Uninstall +uninstall: + @echo "Uninstalling $(PROJECT_NAME)..." + rm -f $(DESTDIR)$(BINDIR_INSTALL)/$(PROJECT_NAME) + rm -f $(DESTDIR)$(SYSTEMD_INSTALL)/$(PROJECT_NAME).service + @echo "Uninstall complete." + +# Create systemd service file +systemd-service: $(SYSTEMD_DIR)/$(PROJECT_NAME).service + + +# Clean build artifacts +clean: + @echo "Cleaning build artifacts..." + rm -rf $(OBJDIR) $(BINDIR) + rm -f $(PROTOCOL_HEADERS) $(PROTOCOL_SOURCES) + +# Format source code (requires clang-format) +format: + @echo "Formatting source code..." + @find $(SRCDIR) $(INCDIR) -name "*.c" -o -name "*.h" | xargs clang-format -i + +# Static analysis (requires cppcheck) +analyze: + @echo "Running static analysis..." + @cppcheck --enable=all --std=c11 --language=c \ + --include=$(INCDIR) \ + --suppress=missingIncludeSystem \ + --quiet \ + $(SRCDIR) + +# Run tests +# FIXME: add tests +test: $(TARGET) + @echo "Running tests..." + @echo "Tests not implemented yet." + +# Help target +help: + @echo "Available targets:" + @echo " all - Build the main executable (default)" + @echo " debug - Build with debug symbols and sanitizers" + @echo " static - Build statically linked executable" + @echo " check-deps - Check if all dependencies are available" + @echo " install - Install the executable and systemd service" + @echo " uninstall - Remove installed files" + @echo " clean - Remove build artifacts" + @echo " distclean - Remove all generated files" + @echo " format - Format source code (requires clang-format)" + @echo " analyze - Run static analysis (requires cppcheck)" + @echo " test - Run tests" + @echo " help - Show this help message" + @echo "" + @echo "Examples:" + @echo " make # Build with default settings" + @echo " make debug # Build debug version" + @echo " make PREFIX=/usr install # Install to /usr instead of /usr/local" + @echo " make CC=clang # Use clang instead of gcc" + +# Include dependency files +-include $(DEPENDS) + +# Phony targets +.PHONY: all debug static check-deps install uninstall systemd-service sample-config clean distclean format analyze test help + +# Print variables +print-%: + @echo '$*=$($*)' From b47928cbab547c724bfa704eed0bebde2dca130f Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 29 Sep 2025 17:57:06 +0300 Subject: [PATCH 05/10] meta: release under MPL v2.0 Signed-off-by: NotAShelf Change-Id: I6a6a6964c2608feafccbc9ec9214a7bdb845dbb5 --- LICENSE | 328 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..baa49b9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,328 @@ +Mozilla Public License, version 2.0 + +1. Definitions + + 1.1. “Contributor” + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + + 1.2. “Contributor Version” + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + + 1.3. “Contribution” + means Covered Software of a particular Contributor. + + 1.4. “Covered Software” + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, + and Modifications of such Source Code Form, in each case + including portions thereof. + + 1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the terms + of a Secondary License. + + 1.6. “Executable Form” + means any form of the work other than Source Code Form. + + 1.7. “Larger Work” + means a work that combines Covered Software with other material, + in a separate file or files, that is not Covered Software. + + 1.8. “License” + means this document. + + 1.9. “Licensable” + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, + any and all of the rights conveyed by this License. + + 1.10. “Modifications” + means any of the following: + + a. any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + + 1.11. “Patent Claims” of a Contributor + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + + 1.12. “Secondary License” + means either the GNU General Public License, Version 2.0, the + GNU Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those licenses. + + 1.13. “Source Code Form” + means the form of the work preferred for making modifications. + + 1.14. “You” (or “Your”) + means an individual or a legal entity exercising rights under this License. + For legal entities, “You” includes any entity that controls, + is controlled by, or is under common control with You. For purposes of + this definition, “control” means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by contract + or otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + +2. License Grants and Conditions + + 2.1. Grants + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, + or as part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, + offer for sale, have made, import, and otherwise transfer either + its Contributions or its Contributor Version. + + 2.2. Effective Date + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor + first distributes such Contribution. + + 2.3. Limitations on Grant Scope + The licenses granted in this Section 2 are the only rights granted + under this License. No additional rights or licenses will be implied + from the distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted + by a Contributor: + + a. for any code that a Contributor has removed from + Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its + Contributor Version); or + + c. under Patent Claims infringed by Covered Software in the + absence of its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + + 2.4. Subsequent Licenses + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License + (if permitted under the terms of Section 3.3). + + 2.5. Representation + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights + to grant the rights to its Contributions conveyed by this License. + + 2.6. Fair Use + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, + or other equivalents. + + 2.7. Conditions + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the + licenses granted in Section 2.1. + +3. Responsibilities + + 3.1. Distribution of Source Form + All distribution of Covered Software in Source Code Form, including + any Modifications that You create or to which You contribute, must be + under the terms of this License. You must inform recipients that the + Source Code Form of the Covered Software is governed by the terms + of this License, and how they can obtain a copy of this License. + You may not attempt to alter or restrict the recipients’ rights + in the Source Code Form. + + 3.2. Distribution of Executable Form + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more than + the cost of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients’ rights in the Source Code Form under this License. + + 3.3. Distribution of a Larger Work + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of + Covered Software with a work governed by one or more Secondary Licenses, + and the Covered Software is not Incompatible With Secondary Licenses, + this License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the + Covered Software under the terms of either this License or such + Secondary License(s). + + 3.4. Notices + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, + or limitations of liability) contained within the Source Code Form of + the Covered Software, except that You may alter any license notices to + the extent required to remedy known factual inaccuracies. + + 3.5. Application of Additional Terms + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of + Covered Software. However, You may do so only on Your own behalf, + and not on behalf of any Contributor. You must make it absolutely clear + that any such warranty, support, indemnity, or liability obligation is + offered by You alone, and You hereby agree to indemnify every Contributor + for any liability incurred by such Contributor as a result of warranty, + support, indemnity or liability terms You offer. You may include + additional disclaimers of warranty and limitations of liability + specific to any jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + +If it is impossible for You to comply with any of the terms of this License +with respect to some or all of the Covered Software due to statute, +judicial order, or regulation then You must: (a) comply with the terms of +this License to the maximum extent possible; and (b) describe the limitations +and the code they affect. Such description must be placed in a text file +included with all distributions of the Covered Software under this License. +Except to the extent prohibited by statute or regulation, such description +must be sufficiently detailed for a recipient of ordinary skill +to be able to understand it. + +5. Termination + + 5.1. The rights granted under this License will terminate automatically + if You fail to comply with any of its terms. However, if You become + compliant, then the rights granted under this License from a particular + Contributor are reinstated (a) provisionally, unless and until such + Contributor explicitly and finally terminates Your grants, and (b) on an + ongoing basis, if such Contributor fails to notify You of the + non-compliance by some reasonable means prior to 60 days after You have + come back into compliance. Moreover, Your grants from a particular + Contributor are reinstated on an ongoing basis if such Contributor + notifies You of the non-compliance by some reasonable means, + this is the first time You have received notice of non-compliance with + this License from such Contributor, and You become compliant prior to + 30 days after Your receipt of the notice. + + 5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted + to You by any and all Contributors for the Covered Software under + Section 2.1 of this License shall terminate. + + 5.3. In the event of termination under Sections 5.1 or 5.2 above, all + end user license agreements (excluding distributors and resellers) which + have been validly granted by You or Your distributors under this License + prior to termination shall survive termination. + +6. Disclaimer of Warranty + +Covered Software is provided under this License on an “as is” basis, without +warranty of any kind, either expressed, implied, or statutory, including, +without limitation, warranties that the Covered Software is free of defects, +merchantable, fit for a particular purpose or non-infringing. The entire risk +as to the quality and performance of the Covered Software is with You. +Should any Covered Software prove defective in any respect, You +(not any Contributor) assume the cost of any necessary servicing, repair, +or correction. This disclaimer of warranty constitutes an essential part of +this License. No use of any Covered Software is authorized under this +License except under this disclaimer. + +7. Limitation of Liability + +Under no circumstances and under no legal theory, whether tort +(including negligence), contract, or otherwise, shall any Contributor, or +anyone who distributes Covered Software as permitted above, be liable to +You for any direct, indirect, special, incidental, or consequential damages +of any character including, without limitation, damages for lost profits, +loss of goodwill, work stoppage, computer failure or malfunction, or any and +all other commercial damages or losses, even if such party shall have been +informed of the possibility of such damages. This limitation of liability +shall not apply to liability for death or personal injury resulting from +such party’s negligence to the extent applicable law prohibits such +limitation. Some jurisdictions do not allow the exclusion or limitation of +incidental or consequential damages, so this exclusion and limitation may +not apply to You. + +8. Litigation + +Any litigation relating to this License may be brought only in the courts of +a jurisdiction where the defendant maintains its principal place of business +and such litigation shall be governed by laws of that jurisdiction, without +reference to its conflict-of-law provisions. Nothing in this Section shall +prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + +This License represents the complete agreement concerning the subject matter +hereof. If any provision of this License is held to be unenforceable, +such provision shall be reformed only to the extent necessary to make it +enforceable. Any law or regulation which provides that the language of a +contract shall be construed against the drafter shall not be used to construe +this License against a Contributor. + +10. Versions of the License + + 10.1. New Versions + Mozilla Foundation is the license steward. Except as provided in + Section 10.3, no one other than the license steward has the right to + modify or publish new versions of this License. Each version will be + given a distinguishing version number. + + 10.2. Effect of New Versions + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published + by the license steward. + + 10.3. Modified Versions + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + + 10.4. Distributing Source Code Form that is + Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is + Incompatible With Secondary Licenses under the terms of this version of + the License, the notice described in Exhibit B of this + License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the terms of the + Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed + with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to +look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible With Secondary Licenses”, + as defined by the Mozilla Public License, v. 2.0. From 3cbf6d56457be861472bea470fc4920e5d067989 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 29 Sep 2025 18:38:55 +0300 Subject: [PATCH 06/10] chore: add systemd service Signed-off-by: NotAShelf Change-Id: I6a6a6964d88b3de5f79306867e38dfbfef2ee1af --- systemd/chroma.service | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 systemd/chroma.service diff --git a/systemd/chroma.service b/systemd/chroma.service new file mode 100644 index 0000000..54a09a5 --- /dev/null +++ b/systemd/chroma.service @@ -0,0 +1,24 @@ +[Unit] +Description=Chroma Wallpaper Daemon +After=graphical-session.target +Wants=graphical-session.target +PartOf=graphical-session.target + +[Service] +Type=simple +ExecStart=chroma +ExecReload=kill -HUP $MAINPID +Restart=on-failure +RestartSec=1 +KillMode=mixed +TimeoutStopSec=5 + +# Security +NoNewPrivileges=true +PrivateDevices=true +ProtectSystem=strict +ProtectHome=read-only +ReadWritePaths=%h/.config/chroma + +[Install] +WantedBy=multi-useer.target From d1116e772101b82b426c7ea946b726022a9271e5 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 29 Sep 2025 18:40:39 +0300 Subject: [PATCH 07/10] render: add OpenGL resource caching; optimize texture handling Mildly improves rendering performance by caching OpenGL resources. Namely: - Cache shader program, VBO/EBO, and textures per output - Automatically free image data after GPU upload - Force RGBA format for consistent texture handling - Track texture state across output changes - Add texture invalidation on image changes This reduces the memory usage by a solid 30MB, but it's still not quite enough. I expect (or rather, hope) that we can cut it by half. Signed-off-by: NotAShelf Change-Id: I6a6a6964eebc783c5bc07b1fef7548a8d49f529c --- include/chroma.h | 11 +- src/core.c | 8 ++ src/image.c | 49 ++------- src/render.c | 267 ++++++++++++++++++++++++++++++++++------------- 4 files changed, 221 insertions(+), 114 deletions(-) diff --git a/include/chroma.h b/include/chroma.h index e15946a..ba36ce5 100644 --- a/include/chroma.h +++ b/include/chroma.h @@ -62,6 +62,14 @@ typedef struct { // Associated wallpaper chroma_image_t *image; + + // OpenGL resource cache + GLuint texture_id; + GLuint shader_program; + GLuint vbo; + GLuint ebo; + bool gl_resources_initialized; + bool texture_uploaded; } chroma_output_t; // Config mapping structure @@ -150,6 +158,7 @@ void chroma_egl_cleanup(chroma_state_t *state); int chroma_surface_create(chroma_state_t *state, chroma_output_t *output); void chroma_surface_destroy(chroma_output_t *output); int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output); +void chroma_output_invalidate_texture(chroma_output_t *output); // Layer shell functions void chroma_layer_surface_configure(void *data, @@ -219,4 +228,4 @@ extern const struct zwlr_layer_surface_v1_listener // Global state for signal handling extern volatile sig_atomic_t chroma_should_quit; -#endif // CHROMA_H \ No newline at end of file +#endif // CHROMA_H diff --git a/src/core.c b/src/core.c index b97537f..f07b8ee 100644 --- a/src/core.c +++ b/src/core.c @@ -87,6 +87,14 @@ static int assign_wallpaper_to_output(chroma_state_t *state, return CHROMA_ERROR_IMAGE; } + // Check if image changed and invalidate texture cache if neceessary + bool image_changed = (output->image != image); + if (image_changed && output->image) { + chroma_output_invalidate_texture(output); + chroma_log("DEBUG", "Image changed for output %u, invalidated texture", + output->id); + } + // Assign image to output output->image = image; diff --git a/src/image.c b/src/image.c index c8c9770..e267728 100644 --- a/src/image.c +++ b/src/image.c @@ -49,11 +49,12 @@ int chroma_image_load(chroma_image_t *image, const char *path) { (double)file_size / (1024.0 * 1024.0)); } - // Load image data using stb_image - stbi_set_flip_vertically_on_load(0); // Keep images right-side up + // Load image data using stb_image, force RGBA format to avoid conversion + stbi_set_flip_vertically_on_load(0); // keep images right-side up image->data = - stbi_load(path, &image->width, &image->height, &image->channels, 0); + stbi_load(path, &image->width, &image->height, &image->channels, 4); + image->channels = 4; // always RGBA after forced conversion if (!image->data) { chroma_log("ERROR", "Failed to load image %s: %s", path, stbi_failure_reason()); @@ -68,39 +69,14 @@ int chroma_image_load(chroma_image_t *image, const char *path) { return CHROMA_ERROR_IMAGE; } - // Check supported formats - if (image->channels < 3 || image->channels > 4) { - chroma_log("ERROR", - "Unsupported image format: %d channels (need RGB or RGBA)", + // Validate we have RGBA data (should always be true with forced conversion) + if (image->channels != 4) { + chroma_log("ERROR", "Failed to load image as RGBA: got %d channels", image->channels); chroma_image_free(image); return CHROMA_ERROR_IMAGE; } - // Convert RGB to RGBA if necessary for consistent handling - if (image->channels == 3) { - int pixel_count = image->width * image->height; - unsigned char *rgba_data = malloc(pixel_count * 4); - if (!rgba_data) { - chroma_log("ERROR", "Failed to allocate memory for RGBA conversion"); - chroma_image_free(image); - return CHROMA_ERROR_MEMORY; - } - - // Convert RGB to RGBA - for (int i = 0; i < pixel_count; i++) { - rgba_data[i * 4 + 0] = image->data[i * 3 + 0]; // R - rgba_data[i * 4 + 1] = image->data[i * 3 + 1]; // G - rgba_data[i * 4 + 2] = image->data[i * 3 + 2]; // B - rgba_data[i * 4 + 3] = 255; // A - } - - // Replace original data - stbi_image_free(image->data); - image->data = rgba_data; - image->channels = 4; - } - image->loaded = true; chroma_log("INFO", "Loaded image: %s (%dx%d, %d channels, %.2f MB)", path, @@ -118,13 +94,8 @@ void chroma_image_free(chroma_image_t *image) { } if (image->data) { - if (image->channels == 4 && strlen(image->path) > 0) { - // If we converted from RGB to RGBA, use regular free() - free(image->data); - } else { - // If loaded directly by stb_image, use stbi_image_free() - stbi_image_free(image->data); - } + // Always use stbi_image_free since we load directly with stbi_load + stbi_image_free(image->data); image->data = NULL; } @@ -265,4 +236,4 @@ void chroma_image_init_stb(void) { stbi_ldr_to_hdr_scale(1.0f); chroma_log("DEBUG", "Initialized stb_image library"); -} \ No newline at end of file +} diff --git a/src/render.c b/src/render.c index 1537fe5..dda4030 100644 --- a/src/render.c +++ b/src/render.c @@ -1,4 +1,3 @@ -#include #include #include #include @@ -7,6 +6,7 @@ #include #include "../include/chroma.h" +#include "../include/stb_image.h" // Vertex shader for simple texture rendering static const char *vertex_shader_source = @@ -31,18 +31,17 @@ static const char *fragment_shader_source = // Vertices for a fullscreen quad static const float vertices[] = { // Position Texcoord - -1.0f, -1.0f, 0.0f, 1.0f, // Bottom left - 1.0f, -1.0f, 1.0f, 1.0f, // Bottom right - 1.0f, 1.0f, 1.0f, 0.0f, // Top right - -1.0f, 1.0f, 0.0f, 0.0f, // Top left + -1.0f, -1.0f, 0.0f, 1.0f, // bottom left + 1.0f, -1.0f, 1.0f, 1.0f, // bottom right + 1.0f, 1.0f, 1.0f, 0.0f, // top right + -1.0f, 1.0f, 0.0f, 0.0f, // top left }; static const unsigned int indices[] = { - 0, 1, 2, // First triangle - 2, 3, 0 // Second triangle + 0, 1, 2, // first triangle + 2, 3, 0 // second triangle }; -// Shader compilation helper static GLuint compile_shader(GLenum type, const char *source) { GLuint shader = glCreateShader(type); if (!shader) { @@ -66,7 +65,6 @@ static GLuint compile_shader(GLenum type, const char *source) { return shader; } -// Shader program creation helper static GLuint create_shader_program(void) { GLuint vertex_shader = compile_shader(GL_VERTEX_SHADER, vertex_shader_source); if (!vertex_shader) { @@ -108,6 +106,145 @@ static GLuint create_shader_program(void) { return program; } +// Initialize OpenGL resources for output +static int init_gl_resources(chroma_output_t *output) { + if (!output || output->gl_resources_initialized) { + return CHROMA_OK; + } + + // Create shader prog + output->shader_program = create_shader_program(); + if (!output->shader_program) { + chroma_log("ERROR", "Failed to create shader program for output %u", + output->id); + return CHROMA_ERROR_EGL; + } + + // Create and setup VBO/EBO + glGenBuffers(1, &output->vbo); + glGenBuffers(1, &output->ebo); + + glBindBuffer(GL_ARRAY_BUFFER, output->vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, output->ebo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, + GL_STATIC_DRAW); + + output->texture_id = 0; // will be created when image is assigned + output->gl_resources_initialized = true; + + chroma_log("DEBUG", "Initialized GL resources for output %u", output->id); + return CHROMA_OK; +} + +// Create or update texture from image data +static int update_texture_from_image(chroma_output_t *output, + chroma_image_t *image) { + if (!output || !image || !image->loaded) { + return CHROMA_ERROR_INIT; + } + + // If image data was already freed after previous GPU upload, we can't upload + // again + if (!image->data) { + chroma_log("ERROR", + "Cannot create texture: image data already freed for %s", + image->path); + return CHROMA_ERROR_IMAGE; + } + + // Delete existing texture if it exists + if (output->texture_id != 0) { + glDeleteTextures(1, &output->texture_id); + output->texture_id = 0; + } + + // Create new texture + glGenTextures(1, &output->texture_id); + glBindTexture(GL_TEXTURE_2D, output->texture_id); + + // Set texture parameters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Upload texture data (always RGBA now) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image->width, image->height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, image->data); + + glBindTexture(GL_TEXTURE_2D, 0); + + // Mark this output as having uploaded its texture + output->texture_uploaded = true; + + // Free system RAM copy only when ALL outputs using this image have uploaded + // to GPU + if (image->data) { + // Count total outputs using this image and how many have uploaded + int total_using = 0; + int uploaded_count = 0; + + chroma_state_t *state = output->state; + for (int i = 0; i < state->output_count; i++) { + if (state->outputs[i].active && state->outputs[i].image == image) { + total_using++; + if (state->outputs[i].texture_uploaded) { + uploaded_count++; + } + } + } + + // Only free image data when ALL outputs using it have uploaded + if (total_using > 0 && uploaded_count >= total_using) { + size_t freed_bytes = + (size_t)image->width * image->height * image->channels; + stbi_image_free(image->data); + image->data = NULL; + chroma_log("INFO", + "Freed %.2f MB of image data after all %d outputs uploaded to " + "GPU: %s", + (double)freed_bytes / (1024.0 * 1024.0), total_using, + image->path); + } + } + + chroma_log("DEBUG", "Updated texture for output %u (%dx%d)", output->id, + image->width, image->height); + return CHROMA_OK; +} + +// Cleanup OpenGL resources for output +static void cleanup_gl_resources(chroma_output_t *output) { + if (!output || !output->gl_resources_initialized) { + return; + } + + if (output->texture_id != 0) { + glDeleteTextures(1, &output->texture_id); + output->texture_id = 0; + } + + if (output->shader_program != 0) { + glDeleteProgram(output->shader_program); + output->shader_program = 0; + } + + if (output->vbo != 0) { + glDeleteBuffers(1, &output->vbo); + output->vbo = 0; + } + + if (output->ebo != 0) { + glDeleteBuffers(1, &output->ebo); + output->ebo = 0; + } + + output->gl_resources_initialized = false; + chroma_log("DEBUG", "Cleaned up GL resources for output %u", output->id); +} + // EGL configuration selection static int choose_egl_config(EGLDisplay display, EGLConfig *config) { EGLint attributes[] = {EGL_SURFACE_TYPE, @@ -292,6 +429,9 @@ void chroma_surface_destroy(chroma_output_t *output) { return; } + // Clean up OpenGL resources first + cleanup_gl_resources(output); + if (output->egl_surface != EGL_NO_SURFACE) { eglDestroySurface(eglGetCurrentDisplay(), output->egl_surface); output->egl_surface = EGL_NO_SURFACE; @@ -315,33 +455,7 @@ void chroma_surface_destroy(chroma_output_t *output) { chroma_log("DEBUG", "Destroyed surface for output %u", output->id); } -// Create texture from image data -static GLuint create_texture_from_image(chroma_image_t *image) { - if (!image || !image->loaded || !image->data) { - return 0; - } - - GLuint texture; - glGenTextures(1, &texture); - glBindTexture(GL_TEXTURE_2D, texture); - - // Set texture parameters - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - // Upload texture data - GLenum format = (image->channels == 4) ? GL_RGBA : GL_RGB; - glTexImage2D(GL_TEXTURE_2D, 0, format, image->width, image->height, 0, format, - GL_UNSIGNED_BYTE, image->data); - - glBindTexture(GL_TEXTURE_2D, 0); - - return texture; -} - -// Render wallpaper to output +// Render wallpaper to output using cached resources int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) { if (!state || !output || !output->image || !output->image->loaded) { return CHROMA_ERROR_INIT; @@ -355,6 +469,17 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) { return CHROMA_ERROR_EGL; } + if (init_gl_resources(output) != CHROMA_OK) { + return CHROMA_ERROR_EGL; + } + + if (output->texture_id == 0) { + if (update_texture_from_image(output, output->image) != CHROMA_OK) { + chroma_log("ERROR", "Failed to update texture for output %u", output->id); + return CHROMA_ERROR_EGL; + } + } + // Set viewport glViewport(0, 0, output->width, output->height); @@ -362,42 +487,21 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); - // Create shader program (should be cached in real implementation) - GLuint program = create_shader_program(); - if (!program) { - return CHROMA_ERROR_EGL; - } - - // Use shader program - glUseProgram(program); - - // Create and bind texture - GLuint texture = create_texture_from_image(output->image); - if (!texture) { - chroma_log("ERROR", "Failed to create texture for output %u", output->id); - glDeleteProgram(program); - return CHROMA_ERROR_EGL; - } + // Use cached shader program + glUseProgram(output->shader_program); + // Bind cached texture glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture); - glUniform1i(glGetUniformLocation(program, "texture"), 0); + glBindTexture(GL_TEXTURE_2D, output->texture_id); + glUniform1i(glGetUniformLocation(output->shader_program, "texture"), 0); - // Create vertex buffer objects (should be cached in real implementation) - GLuint vbo, ebo; - glGenBuffers(1, &vbo); - glGenBuffers(1, &ebo); - - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, - GL_STATIC_DRAW); + // Use cached VBO/EBO + glBindBuffer(GL_ARRAY_BUFFER, output->vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, output->ebo); // Set vertex attributes - GLint position_attr = glGetAttribLocation(program, "position"); - GLint texcoord_attr = glGetAttribLocation(program, "texcoord"); + GLint position_attr = glGetAttribLocation(output->shader_program, "position"); + GLint texcoord_attr = glGetAttribLocation(output->shader_program, "texcoord"); glEnableVertexAttribArray(position_attr); glVertexAttribPointer(position_attr, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), @@ -410,11 +514,11 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) { // Draw glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); - // Cleanup - glDeleteBuffers(1, &vbo); - glDeleteBuffers(1, &ebo); - glDeleteTextures(1, &texture); - glDeleteProgram(program); + // Unbind resources + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); + glUseProgram(0); // Swap buffers if (!eglSwapBuffers(state->egl_display, output->egl_surface)) { @@ -426,6 +530,21 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) { // Commit surface wl_surface_commit(output->surface); - chroma_log("DEBUG", "Rendered wallpaper to output %u", output->id); + chroma_log("DEBUG", "Rendered wallpaper to output %u (cached resources)", + output->id); return CHROMA_OK; -} \ No newline at end of file +} + +// Invalidate texture cache for output +void chroma_output_invalidate_texture(chroma_output_t *output) { + if (!output || !output->gl_resources_initialized) { + return; + } + + if (output->texture_id != 0) { + glDeleteTextures(1, &output->texture_id); + output->texture_id = 0; + output->texture_uploaded = false; // reset upload flag + chroma_log("DEBUG", "Invalidated texture cache for output %u", output->id); + } +} From e72da82b32d46daf27c4ff663e5f918058b65fd7 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 29 Sep 2025 18:45:09 +0300 Subject: [PATCH 08/10] meta: set vendored code manually Signed-off-by: NotAShelf Change-Id: I6a6a6964fbd3dbcc49825f381f980d3fd76e8766 --- .gitattributes | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d52fcb6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +# Vendored headers are vendored code, to the surprise of absolutely noone. +# See: +# +/include/chroma.h linguist-vendored + +# Don't think linguist can detect XML, but let's tell it that the protocols are +# vendored *anyway*. +/protocols/*.xml linguist-vendored From edc7552b5c606bbe5f23c24ba4f57ef54c51ecc4 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 29 Sep 2025 18:45:18 +0300 Subject: [PATCH 09/10] add sample configuration Signed-off-by: NotAShelf Change-Id: I6a6a696450749c525482d2eab12de1a0e520e973 --- chroma.conf.sample | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 chroma.conf.sample diff --git a/chroma.conf.sample b/chroma.conf.sample new file mode 100644 index 0000000..cd52968 --- /dev/null +++ b/chroma.conf.sample @@ -0,0 +1,48 @@ +# This is a sample configuration file for the Chroma wallpaper daemon. +# Lines starting with # are comments and are ignored. +# +# Configuration file locations (checked in order): +# 1. ~/.config/chroma/chroma.conf +# 2. $XDG_CONFIG_HOME/chroma/chroma.conf +# 3. ./chroma.conf (current directory) +# +# To get started: +# 1. Copy this file to ~/.config/chroma/chroma.conf +# 2. Modify the paths to point to your wallpaper images +# 3. Use 'wlr-randr' or similar tools to find your output names +# 4. Restart chroma or send SIGHUP to reload configuration + +# This image will be used for any output that doesn't have a specific mapping. +# Supports: JPEG, PNG, BMP, TGA, PSD, GIF, HDR, PIC, PPM, PGM +# Paths can be absolute or relative, ~ expansion is supported. +# +# Alternative examples: +# default_image = ~/Pictures/wallpapers/default.png +# default_image = /usr/share/wallpapers/default.jpg +# default_image = ./wallpapers/fallback.jpg +default_image = ~/.config/chroma/default.jpg + + +# Whether to run as a background daemon +# Usually set via command line option --daemon, but can be set here too +daemon_mode = false + +# Output-specific wallpaper mappings +# ================================== +# Format: output.OUTPUT_NAME = /path/to/image.ext +# +# To find your output names, run one of these commands: +# - wlr-randr (for wlroots-based compositors like Sway, Hyprland) +# - wayland-info | grep wl_output +# - kanshi list-outputs +# +# Common output name patterns: +# - DP-1, DP-2, DP-3, etc. (DisplayPort) +# - HDMI-A-1, HDMI-A-2, etc. (HDMI) +# - eDP-1 (embedded DisplayPort, laptops) +# - DVI-D-1, DVI-I-1 (DVI) +# - VGA-1 (VGA, legacy) +# +# Example: +# output.HDMI-A-1 = ~/Pictures/wallpaper.jpg + From 0bc2decb7c29cd684c9e1237f305b0d29d8aa469 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Tue, 30 Sep 2025 20:09:07 +0300 Subject: [PATCH 10/10] docs: add project README Signed-off-by: NotAShelf Change-Id: I6a6a696411e599829afb123a5f3c241768470163 --- README.md | 211 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..3bada58 --- /dev/null +++ b/README.md @@ -0,0 +1,211 @@ +# Chroma + +A simple, lightweight and efficientt wallpaper daeemon for Wayland compositors +with multi-monitor & automatic hotplugging support. Born from my woes with +Hyprpaper, swaybg and most ironically SWWW, which turned out to be NOT a +solution to my Wayland wallpaper woes. + +## Features + +I did not expect to be writing something too feature-rich, but I still ended up +with something relatively convoluted. Chroma (mostly) reliably supports: + +- **Multi-monitors**: Automatically detects and manages wallpapers for all + connected displays +- **Hotplugging**: Dynamically handles monitor connection/disconnection events +- **Per-output configuration**: Set different wallpapers for each monitor +- **Multiple image formats**: Supports JPEG, PNG, BMP, TGA, PSD, GIF, HDR, PIC, + PPM, PGM +- **EGL/OpenGL rendering**: Hardware-accelerated wallpaper rendering +- **Configuration file support**: Easy setup with INI-style config files +- **Signal handling**: Graceful shutdown and configuration reload (SIGHUP) + +## Usage + +### Requirements + +#### Dependencies + +- **Wayland**: wayland-client, wayland-egl +- **Graphics**: EGL, OpenGL +- **System**: glibc, libm, libdl + +#### Development Dependencies + +- GCC or Clang compiler +- Make +- pkg-config +- Wayland development headers +- EGL/OpenGL development headers + +### Building + +#### Quick Build + +```bash +# Check dependencies +make check-deps + +# Build the daemon +make + +# Or build debug version +make debug +``` + +### Installation + +```bash +# Install to /usr/local (default) +sudo make install + +# Install to /usr +sudo make PREFIX=/usr install + +# Create systemd service file +make systemd-service + +# Create sample configuration +make sample-config +``` + +### Configuration + +#### Configuration File + +Chroma looks for configuration files in this order: + +1. `~/.config/chroma/chroma.conf` +2. `$XDG_CONFIG_HOME/chroma/chroma.conf` +3. `./chroma.conf` (current directory) + +#### Sample Configuration + +```ini +# Default wallpaper for outputs without specific mapping +default_image = ~/.config/chroma/default.jpg + +# Output-specific wallpapers +# Format: output.OUTPUT_NAME = /path/to/image.jpg +output.DP-1 = ~/Pictures/monitor1.jpg +output.DP-2 = ~/Pictures/monitor2.png +output.HDMI-A-1 = ~/Pictures/hdmi_wallpaper.jpg +``` + +### Finding Output Names + +Use `wlr-randr` or similar tools to find your output names: + +```bash +wlr-randr +``` + +### Command Line Options + +```plaintext +Usage: chroma [OPTIONS] + +Options: + -c, --config FILE Configuration file path + -d, --daemon Run as daemon + -v, --verbose Verbose logging + -h, --help Show help + --version Show version information + +Examples: + chroma -c ~/.config/chroma/chroma.conf + chroma --daemon +``` + +### Running Manually + +```bash +# Run in foreground +chroma + +# Run as daemon +chroma --daemon + +# Use custom config +chroma -c /path/to/config.conf +``` + +### Systemd Service + +```bash +# Enable and start the service +systemctl --user enable chroma.service +systemctl --user start chroma.service + +# Check status +systemctl --user status chroma.service + +# View logs +journalctl --user -u chroma.service -f + +# Reload configuration +systemctl --user reload chroma.service +``` + +### Auto-start with Desktop Session + +```bash +# Enable the service for auto-start +systemctl --user enable chroma.service + +# The service will start automatically with your graphical session +``` + +### Supported Wayland Compositors + +Chroma works with any Wayland compositor that supports: + +- `wl_compositor` interface +- `wl_output` interface +- EGL window surface creation + +Tested only on Hyprland. + +## Development + +### Building Debug Version + +```bash +make debug +``` + +### Code Formatting + +```bash +make format # requires clang-format +``` + +### Static Analysis + +```bash +make analyze # requires cppcheck +``` + +## Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Test thoroughly +5. Submit a pull request + +### Code Style + +- C11 standard +- 2-space indentation +- No tabs +- Function names: `chroma_function_name` +- Constants: `CHROMA_CONSTANT_NAME` + +## License + + + +This project is made available under Mozilla Public License (MPL) version 2.0. +See [LICENSE](LICENSE) for more details on the exact conditions. An online copy +is provided [here](https://www.mozilla.org/en-US/MPL/2.0/).