diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 4bf2955..0000000 --- a/.editorconfig +++ /dev/null @@ -1,33 +0,0 @@ -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 - diff --git a/.envrc b/.envrc deleted file mode 100644 index 3550a30..0000000 --- a/.envrc +++ /dev/null @@ -1 +0,0 @@ -use flake diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index d52fcb6..0000000 --- a/.gitattributes +++ /dev/null @@ -1,8 +0,0 @@ -# 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 diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 002cfd3..0000000 --- a/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -/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 diff --git a/LICENSE b/LICENSE deleted file mode 100644 index baa49b9..0000000 --- a/LICENSE +++ /dev/null @@ -1,328 +0,0 @@ -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. diff --git a/Makefile b/Makefile deleted file mode 100644 index 64aaaf8..0000000 --- a/Makefile +++ /dev/null @@ -1,173 +0,0 @@ -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 '$*=$($*)' diff --git a/README.md b/README.md deleted file mode 100644 index 3bada58..0000000 --- a/README.md +++ /dev/null @@ -1,211 +0,0 @@ -# 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/). diff --git a/chroma.conf.sample b/chroma.conf.sample deleted file mode 100644 index cd52968..0000000 --- a/chroma.conf.sample +++ /dev/null @@ -1,48 +0,0 @@ -# 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 - diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 6967c23..0000000 --- a/flake.lock +++ /dev/null @@ -1,26 +0,0 @@ -{ - "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 deleted file mode 100644 index 42d5d3f..0000000 --- a/flake.nix +++ /dev/null @@ -1,22 +0,0 @@ -{ - 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/include/chroma.h b/include/chroma.h index ba36ce5..e15946a 100644 --- a/include/chroma.h +++ b/include/chroma.h @@ -62,14 +62,6 @@ 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 @@ -158,7 +150,6 @@ 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, @@ -228,4 +219,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 +#endif // CHROMA_H \ No newline at end of file diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 54e94ef..0000000 --- a/shell.nix +++ /dev/null @@ -1,51 +0,0 @@ -{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; - }; -} diff --git a/src/core.c b/src/core.c index f07b8ee..b97537f 100644 --- a/src/core.c +++ b/src/core.c @@ -87,14 +87,6 @@ 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 e267728..c8c9770 100644 --- a/src/image.c +++ b/src/image.c @@ -49,12 +49,11 @@ int chroma_image_load(chroma_image_t *image, const char *path) { (double)file_size / (1024.0 * 1024.0)); } - // Load image data using stb_image, force RGBA format to avoid conversion - stbi_set_flip_vertically_on_load(0); // keep images right-side up + // Load image data using stb_image + stbi_set_flip_vertically_on_load(0); // Keep images right-side up image->data = - stbi_load(path, &image->width, &image->height, &image->channels, 4); - image->channels = 4; // always RGBA after forced conversion + stbi_load(path, &image->width, &image->height, &image->channels, 0); if (!image->data) { chroma_log("ERROR", "Failed to load image %s: %s", path, stbi_failure_reason()); @@ -69,14 +68,39 @@ int chroma_image_load(chroma_image_t *image, const char *path) { return CHROMA_ERROR_IMAGE; } - // 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", + // Check supported formats + if (image->channels < 3 || image->channels > 4) { + chroma_log("ERROR", + "Unsupported image format: %d channels (need RGB or RGBA)", 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, @@ -94,8 +118,13 @@ void chroma_image_free(chroma_image_t *image) { } if (image->data) { - // Always use stbi_image_free since we load directly with stbi_load - stbi_image_free(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); + } image->data = NULL; } @@ -236,4 +265,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 dda4030..1537fe5 100644 --- a/src/render.c +++ b/src/render.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -6,7 +7,6 @@ #include #include "../include/chroma.h" -#include "../include/stb_image.h" // Vertex shader for simple texture rendering static const char *vertex_shader_source = @@ -31,17 +31,18 @@ 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) { @@ -65,6 +66,7 @@ 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) { @@ -106,145 +108,6 @@ 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, @@ -429,9 +292,6 @@ 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; @@ -455,7 +315,33 @@ void chroma_surface_destroy(chroma_output_t *output) { chroma_log("DEBUG", "Destroyed surface for output %u", output->id); } -// Render wallpaper to output using cached resources +// 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 int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) { if (!state || !output || !output->image || !output->image->loaded) { return CHROMA_ERROR_INIT; @@ -469,17 +355,6 @@ 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); @@ -487,21 +362,42 @@ 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); - // Use cached shader program - glUseProgram(output->shader_program); + // 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; + } - // Bind cached texture glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, output->texture_id); - glUniform1i(glGetUniformLocation(output->shader_program, "texture"), 0); + glBindTexture(GL_TEXTURE_2D, texture); + glUniform1i(glGetUniformLocation(program, "texture"), 0); - // Use cached VBO/EBO - glBindBuffer(GL_ARRAY_BUFFER, output->vbo); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, output->ebo); + // 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); // Set vertex attributes - GLint position_attr = glGetAttribLocation(output->shader_program, "position"); - GLint texcoord_attr = glGetAttribLocation(output->shader_program, "texcoord"); + GLint position_attr = glGetAttribLocation(program, "position"); + GLint texcoord_attr = glGetAttribLocation(program, "texcoord"); glEnableVertexAttribArray(position_attr); glVertexAttribPointer(position_attr, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), @@ -514,11 +410,11 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) { // Draw glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); - // Unbind resources - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glBindTexture(GL_TEXTURE_2D, 0); - glUseProgram(0); + // Cleanup + glDeleteBuffers(1, &vbo); + glDeleteBuffers(1, &ebo); + glDeleteTextures(1, &texture); + glDeleteProgram(program); // Swap buffers if (!eglSwapBuffers(state->egl_display, output->egl_surface)) { @@ -530,21 +426,6 @@ 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 (cached resources)", - output->id); + chroma_log("DEBUG", "Rendered wallpaper to output %u", output->id); return CHROMA_OK; -} - -// 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); - } -} +} \ No newline at end of file diff --git a/systemd/chroma.service b/systemd/chroma.service deleted file mode 100644 index 54a09a5..0000000 --- a/systemd/chroma.service +++ /dev/null @@ -1,24 +0,0 @@ -[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