diff --git a/.SRCINFO b/.SRCINFO
index e3f944f..215beed 100644
--- a/.SRCINFO
+++ b/.SRCINFO
@@ -1,6 +1,6 @@
pkgbase = coolerdash
pkgdesc = Monitor telemetry data on an AIO liquid cooler with an integrated LCD display
- pkgver = 2.2.2
+ pkgver = 2.2.4
pkgrel = 1
url = https://github.com/damachine/coolerdash
install = coolerdash.install
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..4a46c20
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,36 @@
+# Automatic line ending normalization
+* text=auto
+
+# Shell scripts always with LF (Unix line endings)
+*.sh text eol=lf
+*.bash text eol=lf
+
+# Markdown files
+*.md text eol=lf linguist-documentation
+
+# Configuration files
+*.yml text eol=lf
+*.yaml text eol=lf
+*.cfg text eol=lf
+*.conf text eol=lf
+
+# Git configuration
+.gitignore text eol=lf
+.gitattributes text eol=lf
+
+# Documentation
+docs/* linguist-documentation
+*.md linguist-documentation
+README.md linguist-documentation
+LICENSE text eol=lf
+
+# GitHub Workflows/Actions
+.github/workflows/* linguist-generated
+.github/FUNDING.yml text eol=lf
+
+# Export behavior (for git archive)
+.gitattributes export-ignore
+.gitignore export-ignore
+.github export-ignore
+docs export-ignore
+*.md export-ignore
diff --git a/.gitignore b/.gitignore
index 97fced8..90e6945 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,3 @@
-!VERSION
-!.md
-!.SRCINFO
-!coolerdash.install
-!PKGBUILD
-!Makefile
-!.github
-!.gitignore
-!.gitattributes
coolerdash-bin/
*~
.*
@@ -47,3 +38,12 @@ packaging/debian/coolerdash/
packaging/debian/files
packaging/debian/*.substvars
packaging/debian/*.log
+!VERSION
+!.md
+!.SRCINFO
+!coolerdash.install
+!PKGBUILD
+!Makefile
+!.github
+!.gitignore
+!.gitattributes
diff --git a/Makefile b/Makefile
index 700f267..620c6e2 100644
--- a/Makefile
+++ b/Makefile
@@ -35,26 +35,16 @@ CYAN = \033[0;36m
WHITE = \033[1;37m
RESET = \033[0m
-# Icons (Unicode)
-ICON_BUILD = π¨
-ICON_INSTALL = π¦
-ICON_SERVICE = βοΈ
-ICON_SUCCESS = β
-ICON_WARNING = β οΈ
-ICON_INFO = βΉοΈ
-ICON_CLEAN = π§Ή
-ICON_UNINSTALL = ποΈ
-
# Standard Build Target - Standard C99 project structure
$(TARGET): $(OBJDIR) $(BINDIR) $(OBJECTS) $(MAIN_SOURCE)
@printf "\n$(PURPLE)Manual Installation Check:$(RESET)\n"
@printf "If you see errors about 'conflicting files' or manual installation, run 'make uninstall' and remove leftover files in /opt/coolerdash, /etc/coolerdash, /etc/systemd/system/coolerdash.service.\n\n"
- @printf "$(ICON_BUILD) $(CYAN)Compiling $(TARGET) (Standard C99 structure)...$(RESET)\n"
+ @printf "$(CYAN)Compiling $(TARGET) (Standard C99 structure)...$(RESET)\n"
@printf "$(BLUE)Structure:$(RESET) src/ include/ build/ bin/\n"
@printf "$(BLUE)CFLAGS:$(RESET) $(CFLAGS)\n"
@printf "$(BLUE)LIBS:$(RESET) $(LIBS)\n"
$(CC) $(CFLAGS) -o $(BINDIR)/$(TARGET) $(MAIN_SOURCE) $(OBJECTS) $(LIBS)
- @printf "$(ICON_SUCCESS) $(GREEN)Build successful: $(BINDIR)/$(TARGET)$(RESET)\n"
+ @printf "$(GREEN)Build successful: $(BINDIR)/$(TARGET)$(RESET)\n"
# Create build directory
$(OBJDIR):
@@ -70,7 +60,7 @@ $(BINDIR):
# Compile object files from src/ and subdirectories
$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
@mkdir -p $(dir $@)
- @printf "$(ICON_BUILD) $(YELLOW)Compiling module: $<$(RESET)\n"
+ @printf "$(YELLOW)Compiling module: $<$(RESET)\n"
@$(CC) $(CFLAGS) -MMD -MP -c $< -o $@
-include $(OBJECTS:.o=.d)
@@ -93,10 +83,10 @@ banner:
# Clean Target
clean:
$(MAKE) banner
- @printf "$(ICON_CLEAN) $(YELLOW)Cleaning up...$(RESET)\n"
+ @printf "$(YELLOW)Cleaning up...$(RESET)\n"
rm -f $(BINDIR)/$(TARGET) $(OBJECTS) *.o
rm -rf $(OBJDIR) $(BINDIR)
- @printf "$(ICON_SUCCESS) $(GREEN)Cleanup completed$(RESET)\n"
+ @printf "$(GREEN)Cleanup completed$(RESET)\n"
# Detect Linux distro via release files, os-release as fallback
detect-distro:
@@ -119,47 +109,47 @@ install-deps:
@DISTRO=$$($(MAKE) detect-distro); \
case $$DISTRO in \
arch) \
- printf "$(ICON_INSTALL) $(GREEN)Installing dependencies for Arch Linux/Manjaro...$(RESET)\n"; \
+ printf "$(GREEN)Installing dependencies for Arch Linux/Manjaro...$(RESET)\n"; \
$(SUDO) pacman -S --needed cairo libcurl-gnutls gcc make pkg-config ttf-roboto jansson || { \
- printf "$(ICON_WARNING) $(RED)Error installing dependencies!$(RESET)\n"; \
+ printf "$(RED)Error installing dependencies!$(RESET)\n"; \
printf "$(YELLOW)Please run manually:$(RESET) $(SUDO) pacman -S cairo libcurl-gnutls gcc make pkg-config ttf-roboto jansson\n"; \
exit 1; \
}; \
;; \
debian) \
- printf "$(ICON_INSTALL) $(GREEN)Installing dependencies for Ubuntu/Debian...$(RESET)\n"; \
+ printf "$(GREEN)Installing dependencies for Ubuntu/Debian...$(RESET)\n"; \
$(SUDO) apt update && $(SUDO) apt install -y libcairo2-dev libcurl4-openssl-dev gcc make pkg-config fonts-roboto libjansson-dev || { \
- printf "$(ICON_WARNING) $(RED)Error installing dependencies!$(RESET)\n"; \
+ printf "$(RED)Error installing dependencies!$(RESET)\n"; \
printf "$(YELLOW)Please run manually:$(RESET) $(SUDO) apt install libcairo2-dev libcurl4-openssl-dev gcc make pkg-config fonts-roboto libjansson-dev\n"; \
exit 1; \
}; \
;; \
fedora) \
- printf "$(ICON_INSTALL) $(GREEN)Installing dependencies for Fedora...$(RESET)\n"; \
+ printf "$(GREEN)Installing dependencies for Fedora...$(RESET)\n"; \
$(SUDO) dnf install -y cairo-devel libcurl-devel gcc make pkg-config google-roboto-fonts jansson-devel || { \
- printf "$(ICON_WARNING) $(RED)Error installing dependencies!$(RESET)\n"; \
+ printf "$(RED)Error installing dependencies!$(RESET)\n"; \
printf "$(YELLOW)Please run manually:$(RESET) $(SUDO) dnf install cairo-devel libcurl-devel gcc make pkg-config google-roboto-fonts jansson-devel\n"; \
exit 1; \
}; \
;; \
rhel) \
- printf "$(ICON_INSTALL) $(GREEN)Installing dependencies for RHEL/CentOS...$(RESET)\n"; \
+ printf "$(GREEN)Installing dependencies for RHEL/CentOS...$(RESET)\n"; \
$(SUDO) yum install -y cairo-devel libcurl-devel gcc make pkg-config google-roboto-fonts jansson-devel || { \
- printf "$(ICON_WARNING) $(RED)Error installing dependencies!$(RESET)\n"; \
+ printf "$(RED)Error installing dependencies!$(RESET)\n"; \
printf "$(YELLOW)Please run manually:$(RESET) $(SUDO) yum install cairo-devel libcurl-devel gcc make pkg-config google-roboto-fonts jansson-devel\n"; \
exit 1; \
}; \
;; \
opensuse) \
- printf "$(ICON_INSTALL) $(GREEN)Installing dependencies for openSUSE...$(RESET)\n"; \
+ printf "$(GREEN)Installing dependencies for openSUSE...$(RESET)\n"; \
$(SUDO) zypper install -y cairo-devel libcurl-devel gcc make pkg-config google-roboto-fonts libjansson-devel || { \
- printf "$(ICON_WARNING) $(RED)Error installing dependencies!$(RESET)\n"; \
+ printf "$(RED)Error installing dependencies!$(RESET)\n"; \
printf "$(YELLOW)Please run manually:$(RESET) $(SUDO) zypper install cairo-devel libcurl-devel gcc make pkg-config google-roboto-fonts libjansson-devel\n"; \
exit 1; \
}; \
;; \
*) \
- printf "$(ICON_WARNING) $(RED)Unknown distribution detected!$(RESET)\n"; \
+ printf "$(RED)Unknown distribution detected!$(RESET)\n"; \
printf "\n"; \
printf "$(YELLOW)Please install the following dependencies manually:$(RESET)\n"; \
printf "\n"; \
@@ -191,19 +181,19 @@ check-deps:
fi; \
done; \
if [ -n "$$MISSING" ]; then \
- printf "$(ICON_WARNING) $(YELLOW)Missing dependencies:$$MISSING$(RESET)\n"; \
+ printf "$(YELLOW)Missing dependencies:$$MISSING$(RESET)\n"; \
$(MAKE) install-deps; \
else \
- printf "$(ICON_SUCCESS) $(GREEN)All dependencies found$(RESET)\n"; \
+ printf "$(GREEN)All dependencies found$(RESET)\n"; \
fi
# Install binary to /usr/libexec, plugin data to /etc/coolercontrol/plugins/coolerdash/
install: check-deps $(TARGET)
@printf "\n"
- @printf "$(ICON_INSTALL) $(WHITE)βββ COOLERDASH INSTALLATION βββ$(RESET)\n"
+ @printf "$(WHITE)=== COOLERDASH INSTALLATION ===$(RESET)\n"
@printf "\n"
@if [ "$(REALOS)" = "yes" ]; then \
- printf "$(ICON_SERVICE) $(CYAN)Migration: Checking for legacy files and services...$(RESET)\n"; \
+ printf "$(CYAN)Migration: Checking for legacy files and services...$(RESET)\n"; \
LEGACY_FOUND=0; \
if $(SUDO) systemctl is-active --quiet coolerdash.service 2>/dev/null; then \
$(SUDO) systemctl stop coolerdash.service; \
@@ -258,29 +248,29 @@ install: check-deps $(TARGET)
LEGACY_FOUND=1; \
fi; \
if [ "$$LEGACY_FOUND" -eq 1 ]; then \
- printf " $(GREEN)β$(RESET) Legacy cleanup complete\n"; \
+ printf " $(GREEN)OK$(RESET) Legacy cleanup complete\n"; \
else \
- printf " $(BLUE)β$(RESET) No legacy files found (clean install)\n"; \
+ printf " $(BLUE)->$(RESET) No legacy files found (clean install)\n"; \
fi; \
printf "\n"; \
COOLERDASH_COUNT=$$(pgrep -x coolerdash 2>/dev/null | wc -l); \
if [ "$$COOLERDASH_COUNT" -gt 0 ]; then \
- printf "$(ICON_SERVICE) $(CYAN)Terminating running coolerdash process(es)...$(RESET)\n"; \
+ printf "$(CYAN)Terminating running coolerdash process(es)...$(RESET)\n"; \
$(SUDO) killall -TERM coolerdash 2>/dev/null || true; \
sleep 2; \
REMAINING_COUNT=$$(pgrep -x coolerdash 2>/dev/null | wc -l); \
if [ "$$REMAINING_COUNT" -gt 0 ]; then \
- printf " $(YELLOW)β$(RESET) Force killing $$REMAINING_COUNT remaining process(es)...\n"; \
+ printf " $(YELLOW)->$(RESET) Force killing $$REMAINING_COUNT remaining process(es)...\n"; \
$(SUDO) killall -KILL coolerdash 2>/dev/null || true; \
fi; \
- printf " $(GREEN)β$(RESET) Processes terminated\n"; \
+ printf " $(GREEN)->$(RESET) Processes terminated\n"; \
printf "\n"; \
fi; \
else \
- printf "$(ICON_INFO) $(YELLOW)Migration skipped (CI environment).$(RESET)\n"; \
+ printf "$(YELLOW)Migration skipped (CI environment).$(RESET)\n"; \
fi
@printf "\n"
- @printf "$(ICON_INFO) $(CYAN)Installing plugin files...$(RESET)\n"
+ @printf "$(CYAN)Installing plugin files...$(RESET)\n"
@install -dm755 "$(DESTDIR)/etc/coolercontrol/plugins/coolerdash"
@install -Dm755 $(BINDIR)/$(TARGET) "$(DESTDIR)/usr/libexec/coolerdash/coolerdash"
@install -m644 $(README) "$(DESTDIR)/etc/coolercontrol/plugins/coolerdash/README.md"
@@ -293,6 +283,9 @@ install: check-deps $(TARGET)
@install -m644 images/shutdown.png "$(DESTDIR)/etc/coolercontrol/plugins/coolerdash/shutdown.png"
@install -m644 $(MANIFEST) "$(DESTDIR)/etc/coolercontrol/plugins/coolerdash/manifest.toml"
@sed -i 's/{{VERSION}}/$(VERSION)/g' "$(DESTDIR)/etc/coolercontrol/plugins/coolerdash/manifest.toml"
+ @sed -i 's/{{VERSION}}/$(VERSION)/g' "$(DESTDIR)/etc/coolercontrol/plugins/coolerdash/ui/index.html"
+ @OS_RELEASE=$$(grep -m1 '^PRETTY_NAME=' /etc/os-release 2>/dev/null | cut -d= -f2 | tr -d '"' || echo "Linux"); \
+ sed -i "s/{{OS_RELEASE}}/$$OS_RELEASE/g" "$(DESTDIR)/etc/coolercontrol/plugins/coolerdash/ui/index.html"
@install -Dm644 etc/systemd/cc-plugin-coolerdash.service.d/startup-delay.conf "$(DESTDIR)/etc/systemd/system/cc-plugin-coolerdash.service.d/startup-delay.conf"
@install -Dm644 etc/systemd/coolerdash-helperd.service "$(DESTDIR)/usr/lib/systemd/system/coolerdash-helperd.service"
@printf " $(GREEN)Drop-in:$(RESET) $(DESTDIR)/etc/systemd/system/cc-plugin-coolerdash.service.d/startup-delay.conf\n"
@@ -305,30 +298,30 @@ install: check-deps $(TARGET)
@printf " $(GREEN)Image:$(RESET) shutdown.png (coolerdash.png)\n"
@printf " $(GREEN)Documentation:$(RESET) README.md, LICENSE, CHANGELOG.md, VERSION\n"
@printf "\n"
- @printf "$(ICON_INFO) $(CYAN)Note: Plugin binary is available at /usr/libexec/coolerdash/coolerdash$(RESET)\\n"
+ @printf "$(CYAN)Note: Plugin binary is available at /usr/libexec/coolerdash/coolerdash$(RESET)\\n"
@printf "\n"
- @printf "$(ICON_SERVICE) $(CYAN)Installing documentation...$(RESET)\n"
+ @printf "$(CYAN)Installing documentation...$(RESET)\n"
@install -Dm644 $(MANPAGE) "$(DESTDIR)/usr/share/man/man1/coolerdash.1"
@printf " $(GREEN)Manual:$(RESET) $(DESTDIR)/usr/share/man/man1/coolerdash.1\n"
- @printf "$(ICON_SERVICE) $(CYAN)Installing license...$(RESET)\n"
+ @printf "$(CYAN)Installing license...$(RESET)\n"
@install -Dm644 LICENSE "$(DESTDIR)/usr/share/licenses/coolerdash/LICENSE"
@printf " $(GREEN)License:$(RESET) $(DESTDIR)/usr/share/licenses/coolerdash/LICENSE\n"
- @printf "$(ICON_SERVICE) $(CYAN)Installing desktop shortcut...$(RESET)\n"
+ @printf "$(CYAN)Installing desktop shortcut...$(RESET)\n"
@install -Dm644 etc/applications/coolerdash.desktop "$(DESTDIR)/usr/share/applications/coolerdash.desktop"
@printf " $(GREEN)Shortcut:$(RESET) $(DESTDIR)/usr/share/applications/coolerdash.desktop\n"
- @printf "$(ICON_SERVICE) $(CYAN)Installing icon...$(RESET)\n"
+ @printf "$(CYAN)Installing icon...$(RESET)\n"
@install -Dm644 etc/icons/coolerdash.svg "$(DESTDIR)/usr/share/icons/hicolor/scalable/apps/coolerdash.svg"
@printf " $(GREEN)Icon:$(RESET) $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/coolerdash.svg\n"
- @printf "$(ICON_SERVICE) $(CYAN)Installing udev rules for USB power management...$(RESET)\n"
+ @printf "$(CYAN)Installing udev rules for USB power management...$(RESET)\n"
@install -Dm644 etc/udev/rules.d/99-coolerdash.rules "$(DESTDIR)/usr/lib/udev/rules.d/99-coolerdash.rules"
@printf " $(GREEN)udev rule:$(RESET) $(DESTDIR)/usr/lib/udev/rules.d/99-coolerdash.rules\n"
@if [ "$(REALOS)" = "yes" ]; then \
$(SUDO) udevadm control --reload-rules 2>/dev/null || true; \
$(SUDO) udevadm trigger --subsystem-match=usb 2>/dev/null || true; \
- printf " $(GREEN)β$(RESET) USB power management configured\n"; \
+ printf " $(GREEN)OK$(RESET) USB power management configured\n"; \
fi
@printf "\n"
- @printf "$(ICON_SUCCESS) $(WHITE)INSTALLATION SUCCESSFUL$(RESET)\n"
+ @printf "$(WHITE)INSTALLATION SUCCESSFUL$(RESET)\n"
@printf "\n"
@printf "$(YELLOW)Next steps:$(RESET)\n"
@if [ "$(REALOS)" = "yes" ]; then \
@@ -345,10 +338,10 @@ install: check-deps $(TARGET)
# Uninstall Target
uninstall:
@printf "\n"
- @printf "$(ICON_INSTALL) $(WHITE)βββ COOLERDASH UNINSTALLATION βββ$(RESET)\n"
+ @printf "$(WHITE)=== COOLERDASH UNINSTALLATION ===$(RESET)\n"
@printf "\n"
@if [ "$(REALOS)" = "yes" ]; then \
- printf "$(ICON_SERVICE) $(CYAN)Stopping and disabling services...$(RESET)\n"; \
+ printf "$(CYAN)Stopping and disabling services...$(RESET)\n"; \
$(SUDO) systemctl stop cc-plugin-coolerdash.service >/dev/null 2>&1 || true; \
$(SUDO) systemctl disable cc-plugin-coolerdash.service >/dev/null 2>&1 || true; \
fi
@@ -417,12 +410,12 @@ uninstall:
@$(SUDO) rm -rf "$(DESTDIR)/usr/share/licenses/coolerdash"
@$(SUDO) rm -f "$(DESTDIR)/usr/share/man/man1/coolerdash.1"
@$(SUDO) rm -f "$(DESTDIR)/usr/share/applications/coolerdash.desktop"
- @printf "$(ICON_CLEAN) $(CYAN)Removing udev rule...$(RESET)\n"
+ @printf "$(CYAN)Removing udev rule...$(RESET)\n"
@$(SUDO) rm -f "$(DESTDIR)/usr/lib/udev/rules.d/99-coolerdash.rules"
@if [ "$(REALOS)" = "yes" ]; then \
$(SUDO) udevadm control --reload-rules 2>/dev/null || true; \
$(SUDO) udevadm trigger --subsystem-match=usb 2>/dev/null || true; \
- printf " $(GREEN)β$(RESET) udev rules reloaded\n"; \
+ printf " $(GREEN)OK$(RESET) udev rules reloaded\n"; \
fi
@$(SUDO) rm -f "$(DESTDIR)/usr/share/icons/hicolor/scalable/apps/coolerdash.svg"
@if [ "$(REALOS)" = "yes" ]; then \
@@ -433,50 +426,50 @@ uninstall:
$(SUDO) systemctl daemon-reload >/dev/null 2>&1 || true; \
$(SUDO) systemctl restart coolercontrold.service >/dev/null 2>&1 || true; \
fi
- @printf "\n$(ICON_SUCCESS) $(GREEN)Uninstallation completed successfully$(RESET)\n"
+ @printf "\n$(GREEN)Uninstallation completed successfully$(RESET)\n"
@printf "\n"
# Debug Build
debug: CFLAGS += -g -DDEBUG -fsanitize=address
debug: LIBS += -fsanitize=address
debug: $(TARGET)
- @printf "$(ICON_SUCCESS) $(GREEN)Debug build created with AddressSanitizer: $(BINDIR)/$(TARGET)$(RESET)\n"
+ @printf "$(GREEN)Debug build created with AddressSanitizer: $(BINDIR)/$(TARGET)$(RESET)\n"
logs:
- @printf "$(ICON_INFO) $(CYAN)Live logs (Ctrl+C to exit):$(RESET)\n"
+ @printf "$(CYAN)Live logs (Ctrl+C to exit):$(RESET)\n"
journalctl -u cc-plugin-coolerdash.service -f
# Help
help:
@printf "\n"
- @printf "$(WHITE)ββββββββββββββββββββββββββββββββββββββββ$(RESET)\n"
+ @printf "$(WHITE)========================================$(RESET)\n"
@printf "$(WHITE) COOLERDASH BUILD SYSTEM $(RESET)\n"
- @printf "$(WHITE)ββββββββββββββββββββββββββββββββββββββββ$(RESET)\n"
+ @printf "$(WHITE)========================================$(RESET)\n"
@printf "\n"
- @printf "$(YELLOW)π¨ Build Targets:$(RESET)\n"
+ @printf "$(YELLOW)Build Targets:$(RESET)\n"
@printf " $(GREEN)make$(RESET) - Compiles the program\n"
@printf " $(GREEN)make clean$(RESET) - Removes compiled files\n"
@printf " $(GREEN)make debug$(RESET) - Debug build with AddressSanitizer\n"
@printf "\n"
- @printf "$(YELLOW)π¦ Installation:$(RESET)\n"
+ @printf "$(YELLOW)Installation:$(RESET)\n"
@printf " $(GREEN)make install$(RESET) - Installs binary + plugin data + systemd units\n"
@printf " $(GREEN)make uninstall$(RESET)- Uninstalls the program\n"
@printf "\n"
- @printf "$(YELLOW)βοΈ Plugin Management:$(RESET)\n"
+ @printf "$(YELLOW)Plugin Management:$(RESET)\n"
@printf " $(GREEN)systemctl enable --now coolercontrold.service$(RESET) - Active CoolerControl service\n"
@printf " $(GREEN)systemctl status coolercontrold.service$(RESET) - Shows CoolerControl status\n"
@printf " $(GREEN)journalctl -u coolercontrold.service -f$(RESET) - Shows live logs\n"
@printf " $(BLUE)Note:$(RESET) CoolerControl automatically manages coolerdash lifecycle\n"
@printf " $(BLUE)Shutdown:$(RESET) Plugin automatically displays shutdown.png when stopped\n"
@printf "\n"
- @printf "$(YELLOW)π Documentation:$(RESET)\n"
+ @printf "$(YELLOW)Documentation:$(RESET)\n"
@printf " $(GREEN)man coolerdash$(RESET) - Shows manual page\n"
@printf " $(GREEN)make help$(RESET) - Shows this help\n"
@printf "\n"
- @printf "$(YELLOW)π README:$(RESET)\n"
- @printf " $(GREEN)README.md$(RESET) - πΊπΈ English (main documentation)\n"
+ @printf "$(YELLOW)README:$(RESET)\n"
+ @printf " $(GREEN)README.md$(RESET) - English (main documentation)\n"
@printf "\n"
- @printf "$(YELLOW)π Version Usage:$(RESET)\n"
+ @printf "$(YELLOW)Version Usage:$(RESET)\n"
@printf " $(GREEN)Program:$(RESET) /usr/libexec/coolerdash/coolerdash [mode]\n"
@printf " $(GREEN)Config:$(RESET) /etc/coolercontrol/plugins/coolerdash/config.json\n"
@printf " $(GREEN)Web UI:$(RESET) CoolerControl Plugin Settings\n"
diff --git a/PKGBUILD b/PKGBUILD
index 2309d65..4a7a1ae 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -1,4 +1,4 @@
-# Created by: damachin3 (damachine3 at proton dot me)
+# Maintainer: damachin3 (damachine3 at proton dot me)
# Website: https://github.com/damachine/coolerdash
# This PKGBUILD is for building the coolerdash package from local source.
@@ -29,19 +29,19 @@ build() {
# Fetch latest tags if in git repo
if git rev-parse --git-dir >/dev/null 2>&1; then
echo "Fetching latest tags..."
- git fetch --tags 2>/dev/null || true
+ git fetch --tags
fi
# Remove all previous tarball builds
- rm -rf coolerdash-*.pkg.* || true
+ rm -rf coolerdash-*.pkg.*
# Clean any previous builds if a Makefile exists
if [[ -f Makefile || -f GNUmakefile ]]; then
- make clean || true
+ make clean
fi
- # Build with Arch Linux specific optimizations and C99 compliance
- make || return 1
+ # Build the project
+ make
# Copy files to srcdir for packaging (fakeroot cannot access startdir)
mkdir -p "${srcdir}/bin" "${srcdir}/images" "${srcdir}/man" "${srcdir}/etc/coolercontrol/plugins/coolerdash/ui" "${srcdir}/etc/applications" "${srcdir}/etc/icons" "${srcdir}/etc/udev/rules.d"
@@ -67,9 +67,9 @@ check() {
# Verify that the binary was created successfully
if [[ -f bin/coolerdash ]]; then
- echo "Build successful - binary created"
+ msg "Build successful - binary created"
else
- echo "ERROR: Binary not found"
+ error "Build failed - binary not found"
return 1
fi
}
@@ -90,6 +90,7 @@ package() {
install -Dm644 "${srcdir}/etc/coolercontrol/plugins/coolerdash/manifest.toml" "${pkgdir}/etc/coolercontrol/plugins/coolerdash/manifest.toml"
sed -i "s/{{VERSION}}/${pkgver}/g" "${pkgdir}/etc/coolercontrol/plugins/coolerdash/manifest.toml"
+ sed -i "s/{{VERSION}}/${pkgver}/g" "${pkgdir}/etc/coolercontrol/plugins/coolerdash/ui/index.html"
install -Dm644 "${srcdir}/man/coolerdash.1" "${pkgdir}/usr/share/man/man1/coolerdash.1"
install -Dm644 "${srcdir}/etc/applications/coolerdash.desktop" "${pkgdir}/usr/share/applications/coolerdash.desktop"
diff --git a/VERSION b/VERSION
index 5859406..530cdd9 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.2.3
+2.2.4
diff --git a/aur/PKGBUILD b/aur/PKGBUILD
index 6f0dfc2..061075d 100644
--- a/aur/PKGBUILD
+++ b/aur/PKGBUILD
@@ -1,5 +1,4 @@
-
-# Created by: damachin3 (damachine3 at proton dot me)
+# Maintainer: damachin3 (damachine3 at proton dot me)
# Website: https://github.com/damachine/coolerdash
pkgname=coolerdash-git
@@ -22,35 +21,36 @@ sha256sums=('SKIP') # SKIP for git repo source builds
pkgver() {
cd "${srcdir}/coolerdash"
+ # Fetch latest tags in git repo
git fetch --tags
git describe --tags --long --match "v*" | sed -E 's/^v//; s/-([0-9]+)-g/\.r\1.g/; s/-/./g'
}
build() {
# Build inside the checked-out repository
- cd "${srcdir}/coolerdash" || return 1
+ cd "${srcdir}/coolerdash"
# Remove all previous tarball builds
- rm -rf coolerdash-*.pkg.* || true
+ rm -rf coolerdash-*.pkg.*
# Clean any previous builds if a Makefile exists
if [[ -f Makefile || -f GNUmakefile ]]; then
- make clean || true
+ make clean
fi
- # Build
- make || return 1
+ # Build the project
+ make
}
check() {
# Check in the checked-out repository
- cd "${srcdir}/coolerdash" || return 1
+ cd "${srcdir}/coolerdash"
# Verify that the binary was created successfully
if [[ -f bin/coolerdash ]]; then
- echo "Build successful - binary created"
+ msg "Build successful - binary created"
else
- echo "ERROR: Binary not found"
+ error "Build failed - binary not found"
return 1
fi
}
@@ -71,6 +71,7 @@ package() {
install -m644 "${srcdir}/coolerdash/etc/coolercontrol/plugins/coolerdash/manifest.toml" "${pkgdir}/etc/coolercontrol/plugins/coolerdash/manifest.toml"
sed -i "s/{{VERSION}}/${pkgver}/g" "${pkgdir}/etc/coolercontrol/plugins/coolerdash/manifest.toml"
+ sed -i "s/{{VERSION}}/${pkgver}/g" "${pkgdir}/etc/coolercontrol/plugins/coolerdash/ui/index.html"
install -Dm644 "${srcdir}/coolerdash/man/coolerdash.1" "${pkgdir}/usr/share/man/man1/coolerdash.1"
install -Dm644 "${srcdir}/coolerdash/etc/applications/coolerdash.desktop" "${pkgdir}/usr/share/applications/coolerdash.desktop"
diff --git a/etc/coolercontrol/plugins/coolerdash/ui/index.html b/etc/coolercontrol/plugins/coolerdash/ui/index.html
index b6cc589..ae0ac90 100644
--- a/etc/coolercontrol/plugins/coolerdash/ui/index.html
+++ b/etc/coolercontrol/plugins/coolerdash/ui/index.html
@@ -70,6 +70,102 @@
font-size: 14px;
}
+ /* Device Tab */
+ .device-info-card {
+ background: var(--bg-input);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 20px 24px;
+ margin-bottom: 16px;
+ }
+
+ .device-info-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px 0;
+ border-bottom: 1px solid rgba(45, 66, 99, 0.4);
+ }
+
+ .device-info-row:last-child {
+ border-bottom: none;
+ }
+
+ .device-info-label {
+ font-size: 13px;
+ color: var(--text-dim);
+ font-weight: 500;
+ }
+
+ .device-info-value {
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--text);
+ }
+
+ .device-info-value.mono {
+ font-family: 'SF Mono', 'Fira Code', 'Courier New', monospace;
+ }
+
+ .device-status {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 3px 10px;
+ border-radius: 12px;
+ font-size: 12px;
+ font-weight: 600;
+ }
+
+ .device-status.connected {
+ background: rgba(34, 197, 94, 0.12);
+ color: var(--success);
+ }
+
+ .device-status.disconnected {
+ background: rgba(255, 140, 0, 0.12);
+ color: #ff8c00;
+ }
+
+ .device-status.error {
+ background: rgba(233, 69, 96, 0.12);
+ color: var(--accent);
+ }
+
+ .device-status-dot {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ background: currentColor;
+ }
+
+ .info-item {
+ margin-bottom: 20px;
+ }
+
+ .info-item-label {
+ font-size: 11px;
+ color: var(--text-muted);
+ text-transform: uppercase;
+ letter-spacing: 0.8px;
+ margin-bottom: 4px;
+ }
+
+ .info-item-value {
+ font-size: 14px;
+ color: var(--text);
+ }
+
+ .info-item-value a {
+ color: var(--accent);
+ text-decoration: none;
+ }
+
+ .info-item-value a:hover {
+ color: var(--accent-hover);
+ text-decoration: underline;
+ }
+
/* Tabs */
.tabs {
display: flex;
@@ -378,7 +474,7 @@
CoolerDash Configuration
Customize your LCD temperature dashboard β’ Changes require plugin restart
- π‘ Tip: After an update, click Reset to apply new default values added by the developer.
+ π‘ Tip: After an update, click Reset to apply new default values. Feedback & bug reports are welcome β see the Info tab for links.
@@ -393,6 +489,8 @@ CoolerDash Configuration
Liquid
Position
Advanced
+ Device
+ Info
@@ -1152,6 +1367,149 @@ Backup & Restore
window.runPluginScript = function(mainFunction) { mainFunction(); };
}
+ // ===== DEVICE DETECTION =====
+ let lastApiAddress = '';
+ let lastApiPassword = '';
+
+ async function fetchDeviceInfo(apiAddress, password) {
+ lastApiAddress = apiAddress;
+ lastApiPassword = password;
+
+ const loadingEl = document.getElementById('device-loading');
+ const contentEl = document.getElementById('device-panel-content');
+ const errorEl = document.getElementById('device-error');
+
+ loadingEl.style.display = 'block';
+ contentEl.style.display = 'none';
+ errorEl.style.display = 'none';
+
+ try {
+ const headers = {};
+ if (password) {
+ headers['Authorization'] = `Basic ${btoa(`admin:${password}`)}`;
+ }
+ const response = await fetch(`${apiAddress}/devices`, { headers });
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
+ const data = await response.json();
+
+ const devices = data.devices || [];
+ let found = null;
+ for (const dev of devices) {
+ const dtype = dev.type || '';
+ if (dtype === 'Liquidctl') {
+ found = dev;
+ break;
+ }
+ }
+
+ loadingEl.style.display = 'none';
+
+ if (found) {
+ const name = found.name || 'Unknown Device';
+ const uid = found.uid || 'β';
+ document.getElementById('device-name').textContent = name;
+ document.getElementById('device-uid').textContent = uid;
+
+ // Firmware from lc_info
+ const firmware = found.lc_info?.firmware_version || 'β';
+ document.getElementById('device-firmware').textContent = firmware;
+
+ // Liquidctl version from driver_info
+ const liquidctlVer = found.info?.driver_info?.version || 'β';
+ document.getElementById('device-liquidctl').textContent = liquidctlVer;
+
+ // LCD info path: info.channels..lcd_info
+ let width = 0, height = 0;
+ const channels = found.info?.channels;
+ if (channels) {
+ for (const ch of Object.values(channels)) {
+ if (ch.lcd_info) {
+ width = ch.lcd_info.screen_width || 0;
+ height = ch.lcd_info.screen_height || 0;
+ break;
+ }
+ }
+ }
+
+ const resEl = document.getElementById('device-resolution');
+ const shapeEl = document.getElementById('device-shape');
+ if (width > 0 && height > 0) {
+ resEl.textContent = `${width} Γ ${height} px`;
+ const isCircular = name.includes('Kraken') && (width > 240 || height > 240);
+ shapeEl.textContent = isCircular ? 'Circular' : 'Rectangular';
+ } else {
+ resEl.textContent = 'Not available';
+ shapeEl.textContent = 'Unknown';
+ }
+
+ document.getElementById('device-status').innerHTML =
+ ' Connected ';
+ contentEl.style.display = 'block';
+ } else {
+ document.getElementById('device-name').textContent = 'β';
+ document.getElementById('device-uid').textContent = 'β';
+ document.getElementById('device-resolution').textContent = 'β';
+ document.getElementById('device-shape').textContent = 'β';
+ document.getElementById('device-firmware').textContent = 'β';
+ document.getElementById('device-liquidctl').textContent = 'β';
+ document.getElementById('device-status').innerHTML =
+ ' No LCD device found ';
+ contentEl.style.display = 'block';
+ }
+ } catch (error) {
+ console.warn('Device detection failed:', error);
+ loadingEl.style.display = 'none';
+ document.getElementById('device-status').innerHTML =
+ ' Detection failed ';
+ document.getElementById('device-error-text').textContent =
+ `Could not connect to CoolerControl API (${error.message}).`;
+ contentEl.style.display = 'block';
+ errorEl.style.display = 'block';
+ }
+ }
+
+ function refreshDeviceInfo() {
+ fetchDeviceInfo(lastApiAddress, lastApiPassword);
+ }
+
+ async function fetchSystemInfo(apiAddress, password) {
+ try {
+ const headers = {};
+ if (password) {
+ headers['Authorization'] = `Basic ${btoa(`admin:${password}`)}`;
+ }
+
+ // Fetch /health for CC version
+ const healthRes = await fetch(`${apiAddress}/health`, { headers });
+ if (healthRes.ok) {
+ const health = await healthRes.json();
+ const ccVersion = health.details?.version || 'β';
+ document.getElementById('sys-cc-version').textContent = ccVersion;
+ }
+
+ // Fetch /devices to extract kernel from driver_info (drv_type 'Kernel')
+ const devRes = await fetch(`${apiAddress}/devices`, { headers });
+ if (devRes.ok) {
+ const data = await devRes.json();
+ const devices = data.devices || [];
+ let kernel = '';
+ for (const dev of devices) {
+ const drvType = dev.info?.driver_info?.drv_type;
+ const ver = dev.info?.driver_info?.version;
+ if (drvType === 'Kernel' && ver) {
+ kernel = ver;
+ break;
+ }
+ }
+ if (kernel) {
+ document.getElementById('sys-kernel').textContent = kernel;
+ }
+ }
+ } catch (error) {
+ console.warn('System info fetch failed:', error);
+ }
+ }
+
// ===== UI FUNCTIONS =====
function switchTab(index) {
document.querySelectorAll('.tab').forEach((tab, i) => tab.classList.toggle('active', i === index));
@@ -1485,6 +1843,12 @@ Backup & Restore
}
populateForm(config);
+
+ // Detect device from CoolerControl API
+ const apiAddr = config.daemon?.address || 'http://localhost:11987';
+ const apiPass = config.daemon?.password || '';
+ fetchDeviceInfo(apiAddr, apiPass);
+ fetchSystemInfo(apiAddr, apiPass);
} catch (error) {
console.error("Init failed:", error);
if (DEFAULT_CONFIG) populateForm(DEFAULT_CONFIG);
diff --git a/src/device/config.c b/src/device/config.c
index e0a3015..5fcb627 100644
--- a/src/device/config.c
+++ b/src/device/config.c
@@ -24,14 +24,13 @@
#include "config.h"
#include "../srv/cc_conf.h"
-#include "../srv/cc_main.h"
// ============================================================================
// Global Logging Implementation
// ============================================================================
/**
- * @brief Global logging implementation for all modules except main.c
+ * @brief Global logging implementation for all modules except main.c.
*/
void log_message(log_level_t level, const char *format, ...)
{
@@ -63,7 +62,7 @@ void log_message(log_level_t level, const char *format, ...)
// ============================================================================
/**
- * @brief Set daemon default values
+ * @brief Set daemon default values.
*/
static void set_daemon_defaults(Config *config)
{
@@ -78,7 +77,7 @@ static void set_daemon_defaults(Config *config)
}
/**
- * @brief Set paths default values
+ * @brief Set paths default values.
*/
static void set_paths_defaults(Config *config)
{
@@ -98,7 +97,7 @@ static void set_paths_defaults(Config *config)
}
/**
- * @brief Try to set display dimensions from LCD device
+ * @brief Try to set display dimensions from LCD device.
*/
static void try_set_lcd_dimensions(Config *config)
{
@@ -119,7 +118,7 @@ static void try_set_lcd_dimensions(Config *config)
}
/**
- * @brief Set display default values with LCD device fallback
+ * @brief Set display default values with LCD device fallback.
*/
static void set_display_defaults(Config *config)
{
@@ -152,7 +151,7 @@ static void set_display_defaults(Config *config)
}
/**
- * @brief Set layout default values
+ * @brief Set layout default values.
*/
static void set_layout_defaults(Config *config)
{
@@ -183,7 +182,7 @@ static void set_layout_defaults(Config *config)
}
/**
- * @brief Set display positioning default values
+ * @brief Set display positioning default values.
*/
static void set_display_positioning_defaults(Config *config)
{
@@ -195,7 +194,7 @@ static void set_display_positioning_defaults(Config *config)
}
/**
- * @brief Set font default values with dynamic scaling
+ * @brief Set font default values with dynamic scaling.
*/
static void set_font_defaults(Config *config)
{
@@ -238,7 +237,7 @@ static void set_font_defaults(Config *config)
}
/**
- * @brief Set temperature defaults
+ * @brief Set temperature defaults.
*/
static void set_temperature_defaults(Config *config)
{
@@ -274,7 +273,7 @@ static void set_temperature_defaults(Config *config)
}
/**
- * @brief Check if color is unset
+ * @brief Check if color is unset.
* @details Uses is_set flag - all RGB values (0,0,0 to 255,255,255) are valid.
*/
static inline int is_color_unset(const Color *color)
@@ -283,7 +282,7 @@ static inline int is_color_unset(const Color *color)
}
/**
- * @brief Color default configuration entry
+ * @brief Color default configuration entry.
*/
typedef struct
{
@@ -292,7 +291,7 @@ typedef struct
} ColorDefault;
/**
- * @brief Set color default values
+ * @brief Set color default values.
*/
static void set_color_defaults(Config *config)
{
@@ -331,7 +330,7 @@ static void set_color_defaults(Config *config)
}
/**
- * @brief Check if a sensor slot value is valid
+ * @brief Check if a sensor slot value is valid.
*/
static int is_valid_sensor_slot(const char *slot)
{
@@ -344,7 +343,7 @@ static int is_valid_sensor_slot(const char *slot)
}
/**
- * @brief Check if a sensor slot is active (not "none")
+ * @brief Check if a sensor slot is active (not "none").
*/
static int slot_is_active_str(const char *slot)
{
@@ -354,7 +353,7 @@ static int slot_is_active_str(const char *slot)
}
/**
- * @brief Validate sensor slot configuration
+ * @brief Validate sensor slot configuration.
* @details Checks for duplicates (excluding "none"), ensures at least one active slot,
* and validates slot values. Resets to defaults on critical errors.
*/
@@ -428,7 +427,7 @@ static void validate_sensor_slots(Config *config)
}
/**
- * @brief Apply all system default values for missing fields
+ * @brief Apply all system default values for missing fields.
*/
static void apply_system_defaults(Config *config)
{
@@ -450,7 +449,7 @@ static void apply_system_defaults(Config *config)
// ============================================================================
/**
- * @brief Read color from JSON object
+ * @brief Read color from JSON object.
*/
static int read_color_from_json(json_t *color_obj, Color *color)
{
@@ -480,7 +479,7 @@ static int read_color_from_json(json_t *color_obj, Color *color)
}
/**
- * @brief Try to locate config.json
+ * @brief Try to locate config.json.
*/
static const char *find_config_json(const char *custom_path)
{
@@ -495,7 +494,6 @@ static const char *find_config_json(const char *custom_path)
}
static const char *possible_paths[] = {
- "~/.config/coolerdash/config.json",
"/etc/coolercontrol/plugins/coolerdash/config.json",
NULL};
@@ -513,7 +511,7 @@ static const char *find_config_json(const char *custom_path)
}
/**
- * @brief Load daemon settings from JSON
+ * @brief Load daemon settings from JSON.
*/
static void load_daemon_from_json(json_t *root, Config *config)
{
@@ -543,7 +541,7 @@ static void load_daemon_from_json(json_t *root, Config *config)
}
/**
- * @brief Load paths from JSON
+ * @brief Load paths from JSON.
*/
static void load_paths_from_json(json_t *root, Config *config)
{
@@ -583,7 +581,7 @@ static void load_paths_from_json(json_t *root, Config *config)
}
/**
- * @brief Load display settings from JSON
+ * @brief Load display settings from JSON.
*/
static void load_display_from_json(json_t *root, Config *config)
{
@@ -698,7 +696,7 @@ static void load_display_from_json(json_t *root, Config *config)
}
/**
- * @brief Load layout settings from JSON
+ * @brief Load layout settings from JSON.
*/
static void load_layout_from_json(json_t *root, Config *config)
{
@@ -791,7 +789,7 @@ static void load_layout_from_json(json_t *root, Config *config)
}
/**
- * @brief Load colors from JSON
+ * @brief Load colors from JSON.
*/
static void load_colors_from_json(json_t *root, Config *config)
{
@@ -807,7 +805,7 @@ static void load_colors_from_json(json_t *root, Config *config)
}
/**
- * @brief Load font settings from JSON
+ * @brief Load font settings from JSON.
*/
static void load_font_from_json(json_t *root, Config *config)
{
@@ -839,7 +837,7 @@ static void load_font_from_json(json_t *root, Config *config)
}
/**
- * @brief Load CPU temperature settings from JSON
+ * @brief Load CPU temperature settings from JSON.
*/
static void load_cpu_temperature_from_json(json_t *root, Config *config)
{
@@ -878,7 +876,7 @@ static void load_cpu_temperature_from_json(json_t *root, Config *config)
}
/**
- * @brief Load GPU temperature settings from JSON
+ * @brief Load GPU temperature settings from JSON.
*/
static void load_gpu_temperature_from_json(json_t *root, Config *config)
{
@@ -917,7 +915,7 @@ static void load_gpu_temperature_from_json(json_t *root, Config *config)
}
/**
- * @brief Load liquid temperature settings from JSON
+ * @brief Load liquid temperature settings from JSON.
*/
static void load_liquid_from_json(json_t *root, Config *config)
{
@@ -956,7 +954,7 @@ static void load_liquid_from_json(json_t *root, Config *config)
}
/**
- * @brief Load positioning settings from JSON
+ * @brief Load positioning settings from JSON.
*/
static void load_positioning_from_json(json_t *root, Config *config)
{
@@ -1024,7 +1022,7 @@ static void load_positioning_from_json(json_t *root, Config *config)
// ============================================================================
/**
- * @brief Load complete configuration from config.json with hardcoded defaults
+ * @brief Load complete configuration from config.json with hardcoded defaults.
*/
int load_plugin_config(Config *config, const char *config_path)
{
diff --git a/src/device/config.h b/src/device/config.h
index e7cddf6..494e8be 100644
--- a/src/device/config.h
+++ b/src/device/config.h
@@ -12,8 +12,8 @@
* @details Config struct, load/save functions, default values.
*/
-#ifndef PLUGIN_H
-#define PLUGIN_H
+#ifndef CONFIG_H
+#define CONFIG_H
// Include necessary headers
// cppcheck-suppress-begin missingIncludeSystem
@@ -152,13 +152,13 @@ typedef struct Config
} Config;
/**
- * @brief Global logging function for all modules except main.c
+ * @brief Global logging function for all modules except main.c.
* @details Provides unified log output for info, status, warning and error messages.
*/
void log_message(log_level_t level, const char *format, ...);
/**
- * @brief Global logging control from main.c
+ * @brief Global logging control from main.c.
* @details External variable controlling verbose logging behavior across all modules.
*/
extern int verbose_logging;
@@ -192,7 +192,7 @@ static inline int is_valid_orientation(int orientation)
// ============================================================================
/**
- * @brief Load complete configuration from config.json with hardcoded defaults
+ * @brief Load complete configuration from config.json with hardcoded defaults.
* @param config Pointer to Config struct to populate
* @param config_path Optional path to config.json (NULL = use default location)
* @return 1 on success (config loaded), 0 if using defaults only
@@ -208,4 +208,4 @@ static inline int is_valid_orientation(int orientation)
*/
int load_plugin_config(Config *config, const char *config_path);
-#endif // PLUGIN_H
+#endif // CONFIG_H
diff --git a/src/main.c b/src/main.c
index fcf8c49..03dfa51 100644
--- a/src/main.c
+++ b/src/main.c
@@ -19,8 +19,6 @@
// Include necessary headers
// cppcheck-suppress-begin missingIncludeSystem
#include
-#include
-#include
#include
#include
#include
@@ -28,7 +26,6 @@
#include
#include
#include
-#include
#include
#include
// cppcheck-suppress-end missingIncludeSystem
@@ -42,17 +39,9 @@
// Security and performance constants
#define DEFAULT_VERSION "unknown"
-#define MAX_ERROR_MSG_LEN 512
-#define MAX_PID_STR_LEN 32
-#define PID_READ_BUFFER_SIZE 64
#define SHUTDOWN_RETRY_COUNT 2
#define VERSION_BUFFER_SIZE 32
-// Define O_NOFOLLOW if not defined (for portability)
-#ifndef O_NOFOLLOW
-#define O_NOFOLLOW 0
-#endif
-
/**
* @brief Global variables for daemon management.
* @details Used for controlling the main daemon loop and shutdown image logic.
@@ -612,7 +601,7 @@ static int verify_plugin_dir_permissions(const char *plugin_dir)
}
/**
- * @brief Initialize configuration from plugin config.json
+ * @brief Initialize configuration from plugin config.json.
* @details Loads config using unified plugin.c system:
* 1. Initialize defaults (hardcoded)
* 2. Try to load config.json (overrides defaults)
@@ -750,6 +739,7 @@ static void perform_cleanup(const Config *config)
// Close CoolerControl session and free resources
cleanup_coolercontrol_session();
+ cleanup_sensor_curl_handle();
remove_image_file(config->paths_image_coolerdash);
running = 0;
diff --git a/src/mods/circle.c b/src/mods/circle.c
index 4482f88..2315770 100644
--- a/src/mods/circle.c
+++ b/src/mods/circle.c
@@ -15,16 +15,10 @@
// Include necessary headers
// cppcheck-suppress-begin missingIncludeSystem
#include
-#include
#include
-#include
-#include
#include
-#include
#include
-#include
#include
-#include
// cppcheck-suppress-end missingIncludeSystem
// Include project headers
@@ -35,172 +29,14 @@
#include "display.h"
#include "circle.h"
-// Circle inscribe factor for circular displays (1/β2 β 0.7071)
-#ifndef M_SQRT1_2
-#define M_SQRT1_2 0.7071067811865476
-#endif
-
/**
- * @brief Global state for sensor alternation (slot-based cycling)
+ * @brief Global state for sensor alternation (slot-based cycling).
*/
static int current_slot_index = 0; // 0=up, 1=mid, 2=down
static time_t last_switch_time = 0;
/**
- * @brief Convert color component to cairo format (0-255 to 0.0-1.0)
- */
-static inline double cairo_color_convert(uint8_t color_component)
-{
- return color_component / 255.0;
-}
-
-/**
- * @brief Set cairo color from Color structure
- */
-static inline void set_cairo_color(cairo_t *cr, const Color *color)
-{
- cairo_set_source_rgb(cr, cairo_color_convert(color->r),
- cairo_color_convert(color->g),
- cairo_color_convert(color->b));
-}
-
-/**
- * @brief Calculate temperature fill width with bounds checking
- */
-static inline int calculate_temp_fill_width(float temp_value, int max_width,
- float max_temp)
-{
- if (temp_value <= 0.0f)
- return 0;
-
- const float ratio = fminf(temp_value / max_temp, 1.0f);
- return (int)(ratio * max_width);
-}
-
-/**
- * @brief Dynamic scaling parameters structure
- */
-typedef struct
-{
- double scale_x;
- double scale_y;
- double corner_radius;
- double inscribe_factor; // 1.0 for rectangular, M_SQRT1_2 for circular
- int safe_bar_width; // Safe bar width for circular displays
- double safe_content_margin; // Horizontal margin for safe content area
- int is_circular; // 1 if circular display, 0 if rectangular
-} ScalingParams;
-
-/**
- * @brief Calculate dynamic scaling parameters based on display dimensions
- */
-static void calculate_scaling_params(const struct Config *config,
- ScalingParams *params,
- const char *device_name)
-{
- const double base_width = 240.0;
- const double base_height = 240.0;
-
- params->scale_x = config->display_width / base_width;
- params->scale_y = config->display_height / base_height;
- const double scale_avg = (params->scale_x + params->scale_y) / 2.0;
-
- // Detect circular displays
- int is_circular_by_device = is_circular_display_device(
- device_name, config->display_width, config->display_height);
-
- // Check display_shape configuration
- if (strcmp(config->display_shape, "rectangular") == 0)
- {
- // Force rectangular (inscribe_factor = 1.0)
- params->is_circular = 0;
- params->inscribe_factor = 1.0;
- log_message(LOG_INFO, "Circle mode: Display shape forced to rectangular "
- "via config (inscribe_factor: 1.0)");
- }
- else if (strcmp(config->display_shape, "circular") == 0)
- {
- // Force circular (inscribe_factor = M_SQRT1_2 β 0.7071)
- params->is_circular = 1;
- double cfg_inscribe;
- if (config->display_inscribe_factor == 0.0f)
- cfg_inscribe = M_SQRT1_2; // user 'auto'
- else if (config->display_inscribe_factor > 0.0f &&
- config->display_inscribe_factor <= 1.0f)
- cfg_inscribe = (double)config->display_inscribe_factor;
- else
- cfg_inscribe = M_SQRT1_2; // fallback
- params->inscribe_factor = cfg_inscribe;
- log_message(LOG_INFO,
- "Circle mode: Display shape forced to circular via config "
- "(inscribe_factor: %.4f)",
- params->inscribe_factor);
- }
- else if (config->force_display_circular)
- {
- // Legacy developer override (CLI --develop)
- params->is_circular = 1;
- {
- double cfg_inscribe;
- if (config->display_inscribe_factor == 0.0f)
- cfg_inscribe = M_SQRT1_2;
- else if (config->display_inscribe_factor > 0.0f &&
- config->display_inscribe_factor <= 1.0f)
- cfg_inscribe = (double)config->display_inscribe_factor;
- else
- cfg_inscribe = M_SQRT1_2;
- params->inscribe_factor = cfg_inscribe;
- }
- }
- else
- {
- // Auto-detection based on device database
- params->is_circular = is_circular_by_device;
- if (params->is_circular)
- {
- double cfg_inscribe;
- if (config->display_inscribe_factor == 0.0f)
- cfg_inscribe = M_SQRT1_2;
- else if (config->display_inscribe_factor > 0.0f &&
- config->display_inscribe_factor <= 1.0f)
- cfg_inscribe = (double)config->display_inscribe_factor;
- else
- cfg_inscribe = M_SQRT1_2;
- params->inscribe_factor = cfg_inscribe;
- }
- else
- {
- params->inscribe_factor = 1.0;
- }
- }
-
- // Calculate safe area width
- const double safe_area_width =
- config->display_width * params->inscribe_factor;
- const float content_scale = (config->display_content_scale_factor > 0.0f &&
- config->display_content_scale_factor <= 1.0f)
- ? config->display_content_scale_factor
- : 0.98f; // Fallback: 98%
- // Apply bar_width percentage (default 98% = 1% margin left+right)
- const double bar_width_factor = (config->layout_bar_width > 0)
- ? (config->layout_bar_width / 100.0)
- : 0.98;
- params->safe_bar_width =
- (int)(safe_area_width * content_scale * bar_width_factor);
- params->safe_content_margin =
- (config->display_width - params->safe_bar_width) / 2.0;
-
- params->corner_radius = 8.0 * scale_avg;
-
- log_message(LOG_INFO,
- "Circle mode scaling: safe_area=%.0fpx, bar_width=%dpx (%.0f%%), "
- "margin=%.1fpx",
- safe_area_width, params->safe_bar_width, bar_width_factor * 100.0,
- params->safe_content_margin);
-}
-
-/**
- * @brief Get the slot value for a given slot index
+ * @brief Get the slot value for a given slot index.
* @param config Configuration
* @param slot_index 0=up, 1=mid, 2=down
* @return Slot value string ("cpu", "gpu", "liquid", "none")
@@ -224,7 +60,7 @@ static const char *get_slot_value_by_index(const struct Config *config, int slot
}
/**
- * @brief Get slot name for a given slot index
+ * @brief Get slot name for a given slot index.
*/
static const char *get_slot_name_by_index(int slot_index)
{
@@ -242,7 +78,7 @@ static const char *get_slot_name_by_index(int slot_index)
}
/**
- * @brief Find next active slot index (wrapping around)
+ * @brief Find next active slot index (wrapping around).
* @param config Configuration
* @param start_index Starting slot index
* @return Next active slot index, or -1 if none found
@@ -260,7 +96,7 @@ static int find_next_active_slot(const struct Config *config, int start_index)
}
/**
- * @brief Check if sensor should switch based on configured interval
+ * @brief Check if sensor should switch based on configured interval.
*/
static void update_sensor_mode(const struct Config *config)
{
@@ -304,37 +140,7 @@ static void update_sensor_mode(const struct Config *config)
}
/**
- * @brief Draw rounded rectangle path
- */
-static void draw_rounded_rectangle_path(cairo_t *cr, int x, int y, int width,
- int height, double radius)
-{
- cairo_new_sub_path(cr);
- cairo_arc(cr, x + width - radius, y + radius, radius, -CIRCLE_M_PI_2, 0);
- cairo_arc(cr, x + width - radius, y + height - radius, radius, 0,
- CIRCLE_M_PI_2);
- cairo_arc(cr, x + radius, y + height - radius, radius, CIRCLE_M_PI_2,
- CIRCLE_M_PI);
- cairo_arc(cr, x + radius, y + radius, radius, CIRCLE_M_PI, 1.5 * CIRCLE_M_PI);
- cairo_close_path(cr);
-}
-
-/**
- * @brief Draw degree symbol at calculated position
- */
-static void draw_degree_symbol(cairo_t *cr, double x, double y,
- const struct Config *config)
-{
- if (!cr || !config)
- return;
- cairo_set_font_size(cr, config->font_size_temp / 1.66);
- cairo_move_to(cr, x, y);
- cairo_show_text(cr, "Β°");
- cairo_set_font_size(cr, config->font_size_temp);
-}
-
-/**
- * @brief Draw single sensor display based on current slot
+ * @brief Draw single sensor display based on current slot.
* @param cr Cairo context
* @param config Configuration
* @param params Scaling parameters
@@ -502,32 +308,7 @@ static void draw_single_sensor(cairo_t *cr, const struct Config *config,
}
/**
- * @brief Create cairo context and surface
- */
-static cairo_t *create_cairo_context(const struct Config *config,
- cairo_surface_t **surface)
-{
- *surface = cairo_image_surface_create(
- CAIRO_FORMAT_ARGB32, config->display_width, config->display_height);
- if (cairo_surface_status(*surface) != CAIRO_STATUS_SUCCESS)
- {
- log_message(LOG_ERROR, "Failed to create Cairo surface");
- return NULL;
- }
-
- cairo_t *cr = cairo_create(*surface);
- if (cairo_status(cr) != CAIRO_STATUS_SUCCESS)
- {
- log_message(LOG_ERROR, "Failed to create Cairo context");
- cairo_surface_destroy(*surface);
- return NULL;
- }
-
- return cr;
-}
-
-/**
- * @brief Render complete circle mode display
+ * @brief Render complete circle mode display.
*/
static void render_display_content(cairo_t *cr, const struct Config *config,
const monitor_sensor_data_t *data,
@@ -549,11 +330,12 @@ static void render_display_content(cairo_t *cr, const struct Config *config,
}
/**
- * @brief Render circle mode display and upload to LCD
+ * @brief Render circle mode display to PNG file.
+ * @details Creates PNG image with single sensor, does NOT upload.
*/
-int render_circle_display(const struct Config *config,
- const monitor_sensor_data_t *data,
- const char *device_name)
+static int render_circle_display(const struct Config *config,
+ const monitor_sensor_data_t *data,
+ const char *device_name)
{
if (!config || !data)
{
@@ -581,6 +363,16 @@ int render_circle_display(const struct Config *config,
render_display_content(cr, config, data, ¶ms);
+ cairo_surface_flush(surface);
+ if (cairo_status(cr) != CAIRO_STATUS_SUCCESS)
+ {
+ log_message(LOG_ERROR, "Cairo drawing error: %s",
+ cairo_status_to_string(cairo_status(cr)));
+ cairo_destroy(cr);
+ cairo_surface_destroy(surface);
+ return 0;
+ }
+
// Write PNG to file
cairo_status_t write_status =
cairo_surface_write_to_png(surface, config->paths_image_coolerdash);
@@ -594,41 +386,13 @@ int render_circle_display(const struct Config *config,
return 0;
}
- // Get device UID for upload
- char device_uid[128] = {0};
- char device_name_buf[128] = {0};
- int screen_width = 0, screen_height = 0;
-
- if (!get_liquidctl_data(config, device_uid, sizeof(device_uid),
- device_name_buf, sizeof(device_name_buf),
- &screen_width, &screen_height))
- {
- log_message(LOG_ERROR, "Failed to get device information");
- return 0;
- }
-
- // Upload to LCD
- if (!send_image_to_lcd(config, config->paths_image_coolerdash, device_uid))
- {
- log_message(LOG_ERROR, "Failed to upload circle mode image to LCD");
- return 0;
- }
-
- // Verbose logging only
- if (verbose_logging)
- {
- const char *slot_value = get_slot_value_by_index(config, current_slot_index);
- const char *label = get_slot_label(slot_value);
- float temp = get_slot_temperature(data, slot_value);
- log_message(LOG_STATUS, "Circle mode: %s display updated (%.1fΒ°C)",
- label ? label : "unknown", temp);
- }
-
return 1;
}
/**
- * @brief High-level entry point for circle mode rendering
+ * @brief High-level entry point for circle mode rendering.
+ * @details Collects sensor data, renders circle display using
+ * render_circle_display(), and sends to LCD device.
*/
void draw_circle_image(const struct Config *config)
{
@@ -643,12 +407,9 @@ void draw_circle_image(const struct Config *config)
char device_name[128] = {0};
int screen_width = 0, screen_height = 0;
- if (!get_liquidctl_data(config, device_uid, sizeof(device_uid), device_name,
- sizeof(device_name), &screen_width, &screen_height))
- {
- log_message(LOG_ERROR, "Circle mode: Failed to get device data");
- return;
- }
+ const int device_available =
+ get_liquidctl_data(config, device_uid, sizeof(device_uid), device_name,
+ sizeof(device_name), &screen_width, &screen_height);
// Get temperature data
monitor_sensor_data_t data = {0};
@@ -658,6 +419,27 @@ void draw_circle_image(const struct Config *config)
return;
}
- // Render and upload
- render_circle_display(config, &data, device_name);
+ // Render circle display with device name for circular display detection
+ if (!render_circle_display(config, &data, device_name))
+ {
+ log_message(LOG_ERROR, "Circle display rendering failed");
+ return;
+ }
+
+ // Send to LCD if available
+ if (is_session_initialized() && device_available && device_uid[0] != '\0')
+ {
+ const char *name =
+ (device_name[0] != '\0') ? device_name : "Unknown Device";
+ log_message(LOG_INFO, "Sending circle image to LCD: %s [%s]", name,
+ device_uid);
+
+ send_image_to_lcd(config, config->paths_image_coolerdash, device_uid);
+
+ log_message(LOG_INFO, "Circle LCD image uploaded successfully");
+ }
+ else
+ {
+ log_message(LOG_WARNING, "Skipping circle LCD upload - device not available");
+ }
}
diff --git a/src/mods/circle.h b/src/mods/circle.h
index 2468732..fdee872 100644
--- a/src/mods/circle.h
+++ b/src/mods/circle.h
@@ -27,18 +27,6 @@
// Forward declarations
struct Config;
-// Mathematical constants for Cairo graphics operations
-#ifndef M_PI
-#define CIRCLE_M_PI 3.14159265358979323846
-#else
-#define CIRCLE_M_PI M_PI
-#endif
-#ifndef M_PI_2
-#define CIRCLE_M_PI_2 1.57079632679489661923
-#else
-#define CIRCLE_M_PI_2 M_PI_2
-#endif
-
/**
* @brief Collects sensor data and renders circle mode display.
* @details High-level entry point function that retrieves temperature data and
@@ -46,13 +34,4 @@ struct Config;
*/
void draw_circle_image(const struct Config *config);
-/**
- * @brief Render single sensor display based on current mode (CPU or GPU).
- * @details Creates PNG image using Cairo graphics library showing either CPU or
- * GPU temperature, alternates every 2.5 seconds.
- */
-int render_circle_display(const struct Config *config,
- const monitor_sensor_data_t *data,
- const char *device_name);
-
#endif // CIRCLE_H
diff --git a/src/mods/display.c b/src/mods/display.c
index d08fc38..369239e 100644
--- a/src/mods/display.c
+++ b/src/mods/display.c
@@ -8,8 +8,9 @@
*/
/**
- * @brief Display mode dispatcher.
- * @details Routes to dual or circle rendering module, shared sensor helpers.
+ * @brief Display mode dispatcher and shared rendering utilities.
+ * @details Routes to dual or circle rendering module, provides shared Cairo
+ * helpers and sensor slot functions used by all display modes.
*/
// Define POSIX constants
@@ -17,22 +18,233 @@
// Include necessary headers
// cppcheck-suppress-begin missingIncludeSystem
+#include
+#include
+#include
#include
// cppcheck-suppress-end missingIncludeSystem
// Include project headers
#include "../device/config.h"
+#include "../srv/cc_conf.h"
#include "../srv/cc_sensor.h"
#include "circle.h"
#include "display.h"
#include "dual.h"
-// ============================================================================
-// Sensor Slot Helper Functions
-// ============================================================================
+// Circle inscribe factor for circular displays (1/sqrt(2) ~ 0.7071)
+#ifndef M_SQRT1_2
+#define M_SQRT1_2 0.7071067811865476
+#endif
/**
- * @brief Check if a sensor slot is active (not "none")
+ * @brief Convert color component into a 0.0-1.0 range.
+ */
+double cairo_color_convert(uint8_t color_component)
+{
+ return color_component / 255.0;
+}
+
+/**
+ * @brief Set cairo source color from Color structure.
+ */
+void set_cairo_color(cairo_t *cr, const Color *color)
+{
+ cairo_set_source_rgb(cr, cairo_color_convert(color->r),
+ cairo_color_convert(color->g),
+ cairo_color_convert(color->b));
+}
+
+/**
+ * @brief Calculate temperature fill width with bounds checking.
+ */
+int calculate_temp_fill_width(float temp_value, int max_width, float max_temp)
+{
+ if (temp_value <= 0.0f)
+ return 0;
+
+ const float ratio = fminf(temp_value / max_temp, 1.0f);
+ return (int)(ratio * max_width);
+}
+
+/**
+ * @brief Draw rounded rectangle path for temperature bars.
+ */
+void draw_rounded_rectangle_path(cairo_t *cr, int x, int y, int width,
+ int height, double radius)
+{
+ cairo_new_sub_path(cr);
+ cairo_arc(cr, x + width - radius, y + radius, radius, -DISPLAY_M_PI_2, 0);
+ cairo_arc(cr, x + width - radius, y + height - radius, radius, 0,
+ DISPLAY_M_PI_2);
+ cairo_arc(cr, x + radius, y + height - radius, radius, DISPLAY_M_PI_2,
+ DISPLAY_M_PI);
+ cairo_arc(cr, x + radius, y + radius, radius, DISPLAY_M_PI,
+ 1.5 * DISPLAY_M_PI);
+ cairo_close_path(cr);
+}
+
+/**
+ * @brief Draw degree symbol at calculated position with proper font scaling.
+ */
+void draw_degree_symbol(cairo_t *cr, double x, double y,
+ const struct Config *config)
+{
+ if (!cr || !config)
+ return;
+ cairo_set_font_size(cr, config->font_size_temp / 1.66);
+ cairo_move_to(cr, x, y);
+ cairo_show_text(cr, "Β°");
+ cairo_set_font_size(cr, config->font_size_temp);
+}
+
+/**
+ * @brief Create cairo surface and context for display rendering.
+ * @details Creates ARGB32 surface with dimensions from config.
+ */
+cairo_t *create_cairo_context(const struct Config *config,
+ cairo_surface_t **surface)
+{
+ *surface = cairo_image_surface_create(
+ CAIRO_FORMAT_ARGB32, config->display_width, config->display_height);
+ if (!*surface || cairo_surface_status(*surface) != CAIRO_STATUS_SUCCESS)
+ {
+ log_message(LOG_ERROR, "Failed to create cairo surface");
+ if (*surface)
+ cairo_surface_destroy(*surface);
+ *surface = NULL;
+ return NULL;
+ }
+
+ cairo_t *cr = cairo_create(*surface);
+ if (!cr || cairo_status(cr) != CAIRO_STATUS_SUCCESS)
+ {
+ log_message(LOG_ERROR, "Failed to create cairo context");
+ if (cr)
+ cairo_destroy(cr);
+ cairo_surface_destroy(*surface);
+ *surface = NULL;
+ return NULL;
+ }
+
+ return cr;
+}
+
+/**
+ * @brief Calculate dynamic scaling parameters based on display dimensions.
+ * @details Display shape detection: NZXT Kraken 240x240=rect, >240=circular.
+ */
+void calculate_scaling_params(const struct Config *config,
+ ScalingParams *params, const char *device_name)
+{
+ const double base_width = 240.0;
+ const double base_height = 240.0;
+
+ params->scale_x = config->display_width / base_width;
+ params->scale_y = config->display_height / base_height;
+ const double scale_avg = (params->scale_x + params->scale_y) / 2.0;
+
+ // Detect circular displays using device database with resolution info
+ int is_circular_by_device = is_circular_display_device(
+ device_name, config->display_width, config->display_height);
+
+ // Check display_shape configuration
+ if (strcmp(config->display_shape, "rectangular") == 0)
+ {
+ // Force rectangular (inscribe_factor = 1.0)
+ params->is_circular = 0;
+ params->inscribe_factor = 1.0;
+ log_message(LOG_INFO, "Display shape forced to rectangular via config "
+ "(inscribe_factor: 1.0)");
+ }
+ else if (strcmp(config->display_shape, "circular") == 0)
+ {
+ // Force circular (inscribe_factor = M_SQRT1_2 ~ 0.7071)
+ params->is_circular = 1;
+ double cfg_inscribe;
+ if (config->display_inscribe_factor == 0.0f)
+ cfg_inscribe = M_SQRT1_2;
+ else if (config->display_inscribe_factor > 0.0f &&
+ config->display_inscribe_factor <= 1.0f)
+ cfg_inscribe = (double)config->display_inscribe_factor;
+ else
+ cfg_inscribe = M_SQRT1_2;
+ params->inscribe_factor = cfg_inscribe;
+ log_message(
+ LOG_INFO,
+ "Display shape forced to circular via config (inscribe_factor: %.4f)",
+ params->inscribe_factor);
+ }
+ else if (config->force_display_circular)
+ {
+ // Legacy developer override (CLI --develop)
+ params->is_circular = 1;
+ {
+ double cfg_inscribe;
+ if (config->display_inscribe_factor == 0.0f)
+ cfg_inscribe = M_SQRT1_2;
+ else if (config->display_inscribe_factor > 0.0f &&
+ config->display_inscribe_factor <= 1.0f)
+ cfg_inscribe = (double)config->display_inscribe_factor;
+ else
+ cfg_inscribe = M_SQRT1_2;
+ params->inscribe_factor = cfg_inscribe;
+ }
+ log_message(LOG_INFO,
+ "Developer override active: forcing circular display detection "
+ "(device: %s)",
+ device_name ? device_name : "unknown");
+ }
+ else
+ {
+ // Auto-detection based on device database
+ params->is_circular = is_circular_by_device;
+ if (params->is_circular)
+ {
+ double cfg_inscribe;
+ if (config->display_inscribe_factor == 0.0f)
+ cfg_inscribe = M_SQRT1_2;
+ else if (config->display_inscribe_factor > 0.0f &&
+ config->display_inscribe_factor <= 1.0f)
+ cfg_inscribe = (double)config->display_inscribe_factor;
+ else
+ cfg_inscribe = M_SQRT1_2;
+ params->inscribe_factor = cfg_inscribe;
+ }
+ else
+ {
+ params->inscribe_factor = 1.0;
+ }
+ }
+
+ // Calculate safe area width
+ const double safe_area_width =
+ config->display_width * params->inscribe_factor;
+ const float content_scale = (config->display_content_scale_factor > 0.0f &&
+ config->display_content_scale_factor <= 1.0f)
+ ? config->display_content_scale_factor
+ : 0.98f; // Fallback: 98%
+ // Apply bar_width percentage
+ const double bar_width_factor = (config->layout_bar_width > 0)
+ ? (config->layout_bar_width / 100.0)
+ : 0.98;
+ params->safe_bar_width =
+ (int)(safe_area_width * content_scale * bar_width_factor);
+ params->safe_content_margin =
+ (config->display_width - params->safe_bar_width) / 2.0;
+
+ params->corner_radius = 8.0 * scale_avg;
+
+ // Log detailed scaling calculations
+ log_message(
+ LOG_INFO,
+ "Scaling: safe_area=%.0fpx, bar_width=%dpx (%.0f%%), margin=%.1fpx",
+ safe_area_width, params->safe_bar_width, bar_width_factor * 100.0,
+ params->safe_content_margin);
+}
+
+/**
+ * @brief Check if a sensor slot is active.
*/
int slot_is_active(const char *slot_value)
{
@@ -42,7 +254,7 @@ int slot_is_active(const char *slot_value)
}
/**
- * @brief Get temperature value for a sensor slot
+ * @brief Get temperature value for a sensor slot.
*/
float get_slot_temperature(const monitor_sensor_data_t *data, const char *slot_value)
{
@@ -60,7 +272,7 @@ float get_slot_temperature(const monitor_sensor_data_t *data, const char *slot_v
}
/**
- * @brief Get display label for a sensor slot
+ * @brief Get display label for a sensor slot.
*/
const char *get_slot_label(const char *slot_value)
{
@@ -80,7 +292,7 @@ const char *get_slot_label(const char *slot_value)
}
/**
- * @brief Get bar color for a sensor slot based on temperature
+ * @brief Get bar color for a sensor slot based on temperature.
*/
Color get_slot_bar_color(const struct Config *config, const char *slot_value, float temperature)
{
@@ -129,7 +341,7 @@ Color get_slot_bar_color(const struct Config *config, const char *slot_value, fl
}
/**
- * @brief Get maximum scale for a sensor slot
+ * @brief Get maximum scale for a sensor slot.
*/
float get_slot_max_scale(const struct Config *config, const char *slot_value)
{
@@ -149,7 +361,7 @@ float get_slot_max_scale(const struct Config *config, const char *slot_value)
}
/**
- * @brief Get bar height for a specific slot
+ * @brief Get bar height for a specific slot.
*/
uint16_t get_slot_bar_height(const struct Config *config, const char *slot_name)
{
@@ -166,10 +378,6 @@ uint16_t get_slot_bar_height(const struct Config *config, const char *slot_name)
return config->layout_bar_height; // Fallback to global
}
-// ============================================================================
-// Display Dispatcher
-// ============================================================================
-
/**
* @brief Main display dispatcher - routes to appropriate rendering mode.
* @details Examines display_mode configuration and dispatches to either dual or
diff --git a/src/mods/display.h b/src/mods/display.h
index 8832091..b8c1d46 100644
--- a/src/mods/display.h
+++ b/src/mods/display.h
@@ -8,18 +8,60 @@
*/
/**
- * @brief Display mode dispatcher.
- * @details Routes to dual or circle rendering module.
+ * @brief Display mode dispatcher and shared rendering utilities.
+ * @details Routes to dual or circle rendering module, provides common Cairo
+ * helpers and sensor slot functions shared by all display modes.
*/
#ifndef DISPLAY_DISPATCHER_H
#define DISPLAY_DISPATCHER_H
+// Include necessary headers
+// cppcheck-suppress-begin missingIncludeSystem
+#include
+#include
+#include
+// cppcheck-suppress-end missingIncludeSystem
+
// Include for Config and Color types
#include "../device/config.h"
// Include for monitor_sensor_data_t
#include "../srv/cc_sensor.h"
+// ============================================================================
+// Mathematical Constants for Cairo Graphics
+// ============================================================================
+
+#ifndef M_PI
+#define DISPLAY_M_PI 3.14159265358979323846
+#else
+#define DISPLAY_M_PI M_PI
+#endif
+#ifndef M_PI_2
+#define DISPLAY_M_PI_2 1.57079632679489661923
+#else
+#define DISPLAY_M_PI_2 M_PI_2
+#endif
+
+// ============================================================================
+// Shared Rendering Types
+// ============================================================================
+
+/**
+ * @brief Dynamic scaling parameters for display rendering.
+ * @details Calculated once per frame based on display dimensions and device type.
+ */
+typedef struct
+{
+ double scale_x;
+ double scale_y;
+ double corner_radius;
+ double inscribe_factor; /**< 1.0 for rectangular, M_SQRT1_2 for circular */
+ int safe_bar_width; /**< Safe bar width for circular displays */
+ double safe_content_margin; /**< Horizontal margin for safe content area */
+ int is_circular; /**< 1 if circular display, 0 if rectangular */
+} ScalingParams;
+
/**
* @brief Main display dispatcher - routes to appropriate rendering mode.
* @details High-level entry point that examines configuration and dispatches to
@@ -29,19 +71,74 @@
*/
void draw_display_image(const struct Config *config);
+// ============================================================================
+// Shared Cairo Rendering Helpers
+// ============================================================================
+
+/**
+ * @brief Calculate dynamic scaling parameters based on display dimensions.
+ * @details Detects circular/rectangular displays and computes safe content area.
+ * @param config Configuration with display dimensions
+ * @param params Output scaling parameters
+ * @param device_name Device name for circular display detection
+ */
+void calculate_scaling_params(const struct Config *config,
+ ScalingParams *params, const char *device_name);
+
+/**
+ * @brief Create cairo surface and context for display rendering.
+ * @details Caller must destroy both context and surface after use.
+ * @param config Configuration with display dimensions
+ * @param surface Output pointer to created surface
+ * @return Cairo context, or NULL on failure
+ */
+cairo_t *create_cairo_context(const struct Config *config,
+ cairo_surface_t **surface);
+
+/**
+ * @brief Convert color component from 0-255 to cairo 0.0-1.0 range.
+ */
+double cairo_color_convert(uint8_t color_component);
+
+/**
+ * @brief Set cairo source color from Color structure.
+ */
+void set_cairo_color(cairo_t *cr, const Color *color);
+
+/**
+ * @brief Calculate temperature fill width with bounds checking.
+ * @param temp_value Current temperature value
+ * @param max_width Maximum width of the bar in pixels
+ * @param max_temp Maximum temperature scale
+ * @return Fill width in pixels
+ */
+int calculate_temp_fill_width(float temp_value, int max_width, float max_temp);
+
+/**
+ * @brief Draw rounded rectangle path for temperature bars.
+ */
+void draw_rounded_rectangle_path(cairo_t *cr, int x, int y, int width,
+ int height, double radius);
+
+/**
+ * @brief Draw degree symbol at calculated position with proper font scaling.
+ */
+void draw_degree_symbol(cairo_t *cr, double x, double y,
+ const struct Config *config);
+
// ============================================================================
// Sensor Slot Helper Functions
// ============================================================================
/**
- * @brief Check if a sensor slot is active (not "none")
+ * @brief Check if a sensor slot is active (not "none").
* @param slot_value Slot configuration value ("cpu", "gpu", "liquid", "none")
* @return 1 if active, 0 if "none" or invalid
*/
int slot_is_active(const char *slot_value);
/**
- * @brief Get temperature value for a sensor slot
+ * @brief Get temperature value for a sensor slot.
* @param data Sensor data structure with CPU, GPU, and liquid temperatures
* @param slot_value Slot configuration value ("cpu", "gpu", "liquid")
* @return Temperature in Celsius, or 0.0 if slot is "none" or invalid
@@ -49,14 +146,14 @@ int slot_is_active(const char *slot_value);
float get_slot_temperature(const monitor_sensor_data_t *data, const char *slot_value);
/**
- * @brief Get display label for a sensor slot
+ * @brief Get display label for a sensor slot.
* @param slot_value Slot configuration value ("cpu", "gpu", "liquid", "none")
* @return Label string ("CPU", "GPU", "LIQ") or NULL if "none"
*/
const char *get_slot_label(const char *slot_value);
/**
- * @brief Get bar color for a sensor slot based on temperature
+ * @brief Get bar color for a sensor slot based on temperature.
* @param config Configuration with threshold colors
* @param slot_value Slot configuration value (determines which thresholds to use)
* @param temperature Current temperature value
@@ -65,7 +162,7 @@ const char *get_slot_label(const char *slot_value);
Color get_slot_bar_color(const struct Config *config, const char *slot_value, float temperature);
/**
- * @brief Get maximum scale for a sensor slot
+ * @brief Get maximum scale for a sensor slot.
* @param config Configuration with max scale values
* @param slot_value Slot configuration value
* @return Maximum temperature scale (liquid uses different max)
@@ -73,7 +170,7 @@ Color get_slot_bar_color(const struct Config *config, const char *slot_value, fl
float get_slot_max_scale(const struct Config *config, const char *slot_value);
/**
- * @brief Get bar height for a specific slot
+ * @brief Get bar height for a specific slot.
* @param config Configuration with bar height values
* @param slot_name Slot name: "up", "mid", or "down"
* @return Bar height in pixels for the specified slot
diff --git a/src/mods/dual.c b/src/mods/dual.c
index bdf6237..198a9c3 100644
--- a/src/mods/dual.c
+++ b/src/mods/dual.c
@@ -15,16 +15,10 @@
// Include necessary headers
// cppcheck-suppress-begin missingIncludeSystem
#include
-#include
#include
-#include
#include
#include
-#include
#include
-#include
-#include
-#include
// cppcheck-suppress-end missingIncludeSystem
// Include project headers
@@ -35,178 +29,8 @@
#include "display.h"
#include "dual.h"
-// Circle inscribe factor for circular displays (1/β2 β 0.7071)
-#ifndef M_SQRT1_2
-#define M_SQRT1_2 0.7071067811865476
-#endif
-
-// Dynamic positioning factors
-#define LABEL_MARGIN_FACTOR 0.02
-
-/**
- * @brief Convert color component to cairo format (0-255 to 0.0-1.0)
- */
-static inline double cairo_color_convert(uint8_t color_component)
-{
- return color_component / 255.0;
-}
-
-/**
- * @brief Set cairo color from Color structure
- */
-static inline void set_cairo_color(cairo_t *cr, const Color *color)
-{
- cairo_set_source_rgb(cr, cairo_color_convert(color->r),
- cairo_color_convert(color->g),
- cairo_color_convert(color->b));
-}
-
-/**
- * @brief Calculate temperature fill width with bounds checking
- * @param temp_value Current temperature value
- * @param max_width Maximum width of the bar in pixels
- * @param max_temp Maximum temperature from configuration (highest threshold)
- */
-static inline int calculate_temp_fill_width(float temp_value, int max_width,
- float max_temp)
-{
- if (temp_value <= 0.0f)
- return 0;
-
- const float ratio = fminf(temp_value / max_temp, 1.0f);
- return (int)(ratio * max_width);
-}
-
-/**
- * @brief Dynamic scaling parameters structure
- */
-typedef struct
-{
- double scale_x;
- double scale_y;
- double corner_radius;
- double inscribe_factor; // 1.0 for rectangular, M_SQRT1_2 for circular
- int safe_bar_width; // Safe bar width for circular displays
- double safe_content_margin; // Horizontal margin for safe content area
- int is_circular; // 1 if circular display, 0 if rectangular
-} ScalingParams;
-
-/**
- * @brief Calculate dynamic scaling parameters based on display dimensions
- * @details Display shape detection: NZXT Kraken 240x240=rect, >240=circular
- */
-static void calculate_scaling_params(const struct Config *config,
- ScalingParams *params,
- const char *device_name)
-{
- const double base_width = 240.0;
- const double base_height = 240.0;
-
- params->scale_x = config->display_width / base_width;
- params->scale_y = config->display_height / base_height;
- const double scale_avg = (params->scale_x + params->scale_y) / 2.0;
-
- // Detect circular displays using device database with resolution info
- int is_circular_by_device = is_circular_display_device(
- device_name, config->display_width, config->display_height);
-
- // Check display_shape configuration
- if (strcmp(config->display_shape, "rectangular") == 0)
- {
- // Force rectangular (inscribe_factor = 1.0)
- params->is_circular = 0;
- params->inscribe_factor = 1.0;
- log_message(LOG_INFO, "Display shape forced to rectangular via config "
- "(inscribe_factor: 1.0)");
- }
- else if (strcmp(config->display_shape, "circular") == 0)
- {
- // Force circular (inscribe_factor = M_SQRT1_2 β 0.7071)
- params->is_circular = 1;
- double cfg_inscribe;
- if (config->display_inscribe_factor == 0.0f)
- cfg_inscribe = M_SQRT1_2; // user 'auto'
- else if (config->display_inscribe_factor > 0.0f &&
- config->display_inscribe_factor <= 1.0f)
- cfg_inscribe = (double)config->display_inscribe_factor;
- else
- cfg_inscribe = M_SQRT1_2; // fallback
- params->inscribe_factor = cfg_inscribe;
- log_message(
- LOG_INFO,
- "Display shape forced to circular via config (inscribe_factor: %.4f)",
- params->inscribe_factor);
- }
- else if (config->force_display_circular)
- {
- // Legacy developer override (CLI --develop)
- params->is_circular = 1;
- {
- double cfg_inscribe;
- if (config->display_inscribe_factor == 0.0f)
- cfg_inscribe = M_SQRT1_2;
- else if (config->display_inscribe_factor > 0.0f &&
- config->display_inscribe_factor <= 1.0f)
- cfg_inscribe = (double)config->display_inscribe_factor;
- else
- cfg_inscribe = M_SQRT1_2;
- params->inscribe_factor = cfg_inscribe;
- }
- log_message(LOG_INFO,
- "Developer override active: forcing circular display detection "
- "(device: %s)",
- device_name ? device_name : "unknown");
- }
- else
- {
- // Auto-detection based on device database
- params->is_circular = is_circular_by_device;
- if (params->is_circular)
- {
- double cfg_inscribe;
- if (config->display_inscribe_factor == 0.0f)
- cfg_inscribe = M_SQRT1_2;
- else if (config->display_inscribe_factor > 0.0f &&
- config->display_inscribe_factor <= 1.0f)
- cfg_inscribe = (double)config->display_inscribe_factor;
- else
- cfg_inscribe = M_SQRT1_2;
- params->inscribe_factor = cfg_inscribe;
- }
- else
- {
- params->inscribe_factor = 1.0;
- }
- }
-
- // Calculate safe area width
- const double safe_area_width =
- config->display_width * params->inscribe_factor;
- const float content_scale = (config->display_content_scale_factor > 0.0f &&
- config->display_content_scale_factor <= 1.0f)
- ? config->display_content_scale_factor
- : 0.98f; // Fallback: 98%
- // Apply bar_width percentage (default 98% = 1% margin left+right)
- const double bar_width_factor = (config->layout_bar_width > 0)
- ? (config->layout_bar_width / 100.0)
- : 0.98;
- params->safe_bar_width =
- (int)(safe_area_width * content_scale * bar_width_factor);
- params->safe_content_margin =
- (config->display_width - params->safe_bar_width) / 2.0;
-
- params->corner_radius = 8.0 * scale_avg;
-
- // Log detailed scaling calculations (verbose only)
- log_message(
- LOG_INFO,
- "Scaling: safe_area=%.0fpx, bar_width=%dpx (%.0f%%), margin=%.1fpx",
- safe_area_width, params->safe_bar_width, bar_width_factor * 100.0,
- params->safe_content_margin);
-}
-
/**
- * @brief Forward declarations for internal display rendering functions
+ * @brief Forward declarations for internal display rendering functions.
*/
static void draw_temperature_displays(cairo_t *cr,
const monitor_sensor_data_t *data,
@@ -225,44 +49,12 @@ static void draw_single_temperature_bar_slot(cairo_t *cr,
static void draw_labels(cairo_t *cr, const struct Config *config,
const monitor_sensor_data_t *data,
const ScalingParams *params);
-static void draw_rounded_rectangle_path(cairo_t *cr, int x, int y, int width,
- int height, double radius);
-static cairo_t *create_cairo_context(const struct Config *config,
- cairo_surface_t **surface);
static void render_display_content(cairo_t *cr, const struct Config *config,
const monitor_sensor_data_t *data,
const ScalingParams *params);
-// Helper to draw degree symbol at calculated position with proper font scaling
-static void draw_degree_symbol(cairo_t *cr, double x, double y,
- const struct Config *config)
-{
- if (!cr || !config)
- return;
- cairo_set_font_size(cr, config->font_size_temp / 1.66);
- cairo_move_to(cr, x, y);
- cairo_show_text(cr, "Β°");
- cairo_set_font_size(cr, config->font_size_temp);
-}
-
-/**
- * @brief Draw rounded rectangle path for temperature bars
- */
-static void draw_rounded_rectangle_path(cairo_t *cr, int x, int y, int width,
- int height, double radius)
-{
- cairo_new_sub_path(cr);
- cairo_arc(cr, x + width - radius, y + radius, radius, -DISPLAY_M_PI_2, 0);
- cairo_arc(cr, x + width - radius, y + height - radius, radius, 0,
- DISPLAY_M_PI_2);
- cairo_arc(cr, x + radius, y + height - radius, radius, DISPLAY_M_PI_2,
- DISPLAY_M_PI);
- cairo_arc(cr, x + radius, y + radius, radius, DISPLAY_M_PI,
- 1.5 * DISPLAY_M_PI);
- cairo_close_path(cr);
-}
/**
- * @brief Draw temperature displays for up and down slots
+ * @brief Draw temperature displays for up and down slots.
*/
static void draw_temperature_displays(cairo_t *cr,
const monitor_sensor_data_t *data,
@@ -383,7 +175,7 @@ static void draw_temperature_displays(cairo_t *cr,
}
/**
- * @brief Draw a single temperature bar with background, fill, and border
+ * @brief Draw a single temperature bar with background, fill, and border.
*/
static void draw_single_temperature_bar_slot(cairo_t *cr,
const struct Config *config,
@@ -434,7 +226,7 @@ static void draw_single_temperature_bar_slot(cairo_t *cr,
}
/**
- * @brief Draw temperature bars for up and down slots
+ * @brief Draw temperature bars for up and down slots.
*/
static void draw_temperature_bars(cairo_t *cr,
const monitor_sensor_data_t *data,
@@ -496,7 +288,7 @@ static void draw_temperature_bars(cairo_t *cr,
}
/**
- * @brief Draw labels for up and down slots
+ * @brief Draw labels for up and down slots.
*/
static void draw_labels(cairo_t *cr, const struct Config *config,
const monitor_sensor_data_t *data,
@@ -587,34 +379,7 @@ static void draw_labels(cairo_t *cr, const struct Config *config,
}
/**
- * @brief Create cairo context and surface
- */
-static cairo_t *create_cairo_context(const struct Config *config,
- cairo_surface_t **surface)
-{
- *surface = cairo_image_surface_create(
- CAIRO_FORMAT_ARGB32, config->display_width, config->display_height);
- if (!*surface || cairo_surface_status(*surface) != CAIRO_STATUS_SUCCESS)
- {
- log_message(LOG_ERROR, "Failed to create cairo surface");
- if (*surface)
- cairo_surface_destroy(*surface);
- return NULL;
- }
-
- cairo_t *cr = cairo_create(*surface);
- if (!cr)
- {
- log_message(LOG_ERROR, "Failed to create cairo context");
- cairo_surface_destroy(*surface);
- *surface = NULL;
- }
-
- return cr;
-}
-
-/**
- * @brief Render display content to cairo context
+ * @brief Render display content to cairo context.
*/
static void render_display_content(cairo_t *cr, const struct Config *config,
const monitor_sensor_data_t *data,
@@ -647,11 +412,11 @@ static void render_display_content(cairo_t *cr, const struct Config *config,
/**
* @brief Display rendering - creates surface, renders content, saves PNG (Dual
- * mode - CPU+GPU)
+ * mode - CPU+GPU).
*/
-int render_dual_display(const struct Config *config,
- const monitor_sensor_data_t *data,
- const char *device_name)
+static int render_dual_display(const struct Config *config,
+ const monitor_sensor_data_t *data,
+ const char *device_name)
{
if (!data || !config)
{
diff --git a/src/mods/dual.h b/src/mods/dual.h
index f079f4e..5f0c29c 100644
--- a/src/mods/dual.h
+++ b/src/mods/dual.h
@@ -13,8 +13,8 @@
*/
// Include necessary headers
-#ifndef DISPLAY_H
-#define DISPLAY_H
+#ifndef DUAL_H
+#define DUAL_H
// Include necessary headers
// cppcheck-suppress-begin missingIncludeSystem
@@ -28,28 +28,6 @@
// Forward declarations
struct Config;
-// Mathematical constants for Cairo graphics operations
-#ifndef M_PI
-#define DISPLAY_M_PI 3.14159265358979323846
-#else
-#define DISPLAY_M_PI M_PI
-#endif
-#ifndef M_PI_2
-#define DISPLAY_M_PI_2 1.57079632679489661923
-#else
-#define DISPLAY_M_PI_2 M_PI_2
-#endif
-
-/**
- * @brief Render dual-sensor display (CPU+GPU simultaneously).
- * @details Low-level function that creates PNG image using Cairo graphics
- * library based on temperature sensor data and configuration settings, then
- * uploads to LCD device. Shows both sensors at once.
- */
-int render_dual_display(const struct Config *config,
- const monitor_sensor_data_t *data,
- const char *device_name);
-
/**
* @brief Main dual mode entry point.
* @details Collects sensor data, renders dual display using
@@ -58,4 +36,4 @@ int render_dual_display(const struct Config *config,
*/
void draw_dual_image(const struct Config *config);
-#endif // DISPLAY_H
+#endif // DUAL_H
diff --git a/src/srv/cc_conf.h b/src/srv/cc_conf.h
index d040370..37ad27f 100644
--- a/src/srv/cc_conf.h
+++ b/src/srv/cc_conf.h
@@ -17,7 +17,6 @@
// Include necessary headers
// cppcheck-suppress-begin missingIncludeSystem
-#include
#include
// cppcheck-suppress-end missingIncludeSystem
@@ -62,13 +61,6 @@ int init_device_cache(const struct Config *config);
*/
int update_config_from_device(struct Config *config);
-/**
- * @brief Extract device type from JSON device object.
- * @details Common helper function to extract device type string from JSON
- * device object.
- */
-const char *extract_device_type_from_json(const json_t *dev);
-
/**
* @brief Check if a device has a circular display based on device name/type.
* @details Returns true if the device is known to have a circular/round LCD
diff --git a/src/srv/cc_sensor.c b/src/srv/cc_sensor.c
index 8da0782..8739db9 100644
--- a/src/srv/cc_sensor.c
+++ b/src/srv/cc_sensor.c
@@ -16,7 +16,6 @@
// cppcheck-suppress-begin missingIncludeSystem
#include
#include
-#include
#include
#include
#include
@@ -29,8 +28,45 @@
#include "cc_sensor.h"
/**
- * @brief Extract temperature from device status history
- * @details Helper function to get temperature from the latest status entry
+ * @brief Extract device type from JSON object.
+ * @details Helper function to extract device type from JSON object.
+ */
+extern const char *extract_device_type_from_json(const json_t *dev);
+
+/** @brief Cached CURL handle for reuse across polling cycles. */
+static CURL *sensor_curl_handle = NULL;
+
+/**
+ * @brief Initialize or retrieve cached CURL handle for sensor polling.
+ * @return Cached CURL handle, or NULL on failure
+ */
+static CURL *get_sensor_curl_handle(void)
+{
+ if (!sensor_curl_handle)
+ {
+ sensor_curl_handle = curl_easy_init();
+ if (!sensor_curl_handle)
+ log_message(LOG_ERROR, "Failed to initialize sensor CURL handle");
+ }
+ return sensor_curl_handle;
+}
+
+/**
+ * @brief Cleanup cached sensor CURL handle.
+ * @details Called during daemon shutdown to free resources.
+ */
+void cleanup_sensor_curl_handle(void)
+{
+ if (sensor_curl_handle)
+ {
+ curl_easy_cleanup(sensor_curl_handle);
+ sensor_curl_handle = NULL;
+ }
+}
+
+/**
+ * @brief Extract temperature from device status history.
+ * @details Helper function to get temperature from the latest status entry.
*/
static float extract_device_temperature(const json_t *device,
const char *device_type)
@@ -189,8 +225,8 @@ static int parse_temperature_data(const char *json, float *temp_cpu,
}
/**
- * @brief Configure CURL for status API request
- * @details Helper function to set up CURL options for temperature data request
+ * @brief Configure CURL for status API request.
+ * @details Helper function to set up CURL options for temperature data request.
*/
static void configure_status_request(CURL *curl, const char *url,
struct http_response *response)
@@ -231,29 +267,25 @@ static int get_temperature_data(const Config *config, float *temp_cpu,
return 0;
}
- // Initialize CURL
- CURL *curl = curl_easy_init();
+ // Get cached CURL handle for sensor polling
+ CURL *curl = get_sensor_curl_handle();
if (!curl)
- {
- log_message(LOG_ERROR, "Failed to initialize CURL");
return 0;
- }
+
+ // Reset handle state for clean request
+ curl_easy_reset(curl);
// Build URL
char url[256];
int url_len = snprintf(url, sizeof(url), "%s/status", config->daemon_address);
if (url_len < 0 || url_len >= (int)sizeof(url))
- {
- curl_easy_cleanup(curl);
return 0;
- }
// Initialize response buffer
struct http_response response = {0};
if (!cc_init_response_buffer(&response, 8192))
{
log_message(LOG_ERROR, "Failed to allocate response buffer");
- curl_easy_cleanup(curl);
return 0;
}
@@ -297,11 +329,10 @@ static int get_temperature_data(const Config *config, float *temp_cpu,
log_message(LOG_ERROR, "CURL error: %s", curl_easy_strerror(curl_result));
}
- // Cleanup
+ // Cleanup request-specific resources
cc_cleanup_response_buffer(&response);
if (headers)
curl_slist_free_all(headers);
- curl_easy_cleanup(curl);
return result;
}
diff --git a/src/srv/cc_sensor.h b/src/srv/cc_sensor.h
index 0c3163b..9158602 100644
--- a/src/srv/cc_sensor.h
+++ b/src/srv/cc_sensor.h
@@ -44,4 +44,10 @@ typedef struct
int get_temperature_monitor_data(const struct Config *config,
monitor_sensor_data_t *data);
+/**
+ * @brief Cleanup cached sensor CURL handle.
+ * @details Called during daemon shutdown to free resources.
+ */
+void cleanup_sensor_curl_handle(void);
+
#endif // CC_SENSOR_H
diff --git a/tests/test_scaling.c b/tests/test_scaling.c
index d1c5687..35b76f9 100644
--- a/tests/test_scaling.c
+++ b/tests/test_scaling.c
@@ -9,7 +9,7 @@
#include
// cppcheck-suppress-end missingIncludeSystem
-#include "../src/device/sys.h"
+#include "../src/device/config.h"
// Use same constant
#ifndef M_SQRT1_2