Compare commits
10 commits
fcc080871a
...
0bc2decb7c
Author | SHA1 | Date | |
---|---|---|---|
0bc2decb7c |
|||
edc7552b5c |
|||
e72da82b32 |
|||
d1116e7721 |
|||
3cbf6d5645 |
|||
b47928cbab |
|||
87d445340a |
|||
b4fcdab7f4 |
|||
adc2bb2baf |
|||
48fa1cc852 |
16 changed files with 1154 additions and 114 deletions
33
.editorconfig
Normal file
33
.editorconfig
Normal file
|
@ -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
|
||||
|
1
.envrc
Normal file
1
.envrc
Normal file
|
@ -0,0 +1 @@
|
|||
use flake
|
8
.gitattributes
vendored
Normal file
8
.gitattributes
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Vendored headers are vendored code, to the surprise of absolutely noone.
|
||||
# See:
|
||||
# <https://github.com/github-linguist/linguist/blob/main/docs/overrides.md#vendored-code>
|
||||
/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
|
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
@ -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
|
328
LICENSE
Normal file
328
LICENSE
Normal file
|
@ -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.
|
173
Makefile
Normal file
173
Makefile
Normal file
|
@ -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 '$*=$($*)'
|
211
README.md
Normal file
211
README.md
Normal file
|
@ -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
|
||||
|
||||
<!--markdownlint-disable MD059 -->
|
||||
|
||||
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/).
|
48
chroma.conf.sample
Normal file
48
chroma.conf.sample
Normal file
|
@ -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
|
||||
|
26
flake.lock
generated
Normal file
26
flake.lock
generated
Normal file
|
@ -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
|
||||
}
|
22
flake.nix
Normal file
22
flake.nix
Normal file
|
@ -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 {};
|
||||
});
|
||||
};
|
||||
}
|
9
include/chroma.h
vendored
9
include/chroma.h
vendored
|
@ -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,
|
||||
|
|
51
shell.nix
Normal file
51
shell.nix
Normal file
|
@ -0,0 +1,51 @@
|
|||
{pkgs ? import <nixpkgs> {}}:
|
||||
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;
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
47
src/image.c
47
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
src/render.c
265
src/render.c
|
@ -1,4 +1,3 @@
|
|||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
@ -7,6 +6,7 @@
|
|||
#include <GLES2/gl2.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
|
24
systemd/chroma.service
Normal file
24
systemd/chroma.service
Normal file
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue