refactor(project): .hpp => .h

This commit is contained in:
Lemmy
2026-04-03 23:28:08 -04:00
parent d388028e00
commit b6772b8d14
113 changed files with 5107 additions and 5681 deletions
+101 -104
View File
@@ -1,7 +1,7 @@
#include "Application.hpp"
#include "Application.h"
#include "app/PollSource.hpp"
#include "core/Log.hpp"
#include "app/PollSource.h"
#include "core/Log.h"
#include <csignal>
#include <stdexcept>
@@ -11,131 +11,128 @@ std::atomic<bool> Application::s_shutdownRequested{false};
namespace {
void signal_handler(int signum) {
if (signum == SIGTERM || signum == SIGINT) {
Application::s_shutdownRequested = true;
}
if (signum == SIGTERM || signum == SIGINT) {
Application::s_shutdownRequested = true;
}
}
} // namespace
} // namespace
Application::Application()
: m_internalNotifications(m_manager) {
logInfo("noctalia hello");
Application::Application() : m_internalNotifications(m_manager) {
logInfo("noctalia hello");
m_manager.setEventCallback([](const Notification& n, NotificationEvent event) {
const char* kind = (event == NotificationEvent::Added) ? "added" : "updated";
const char* origin = (n.origin == NotificationOrigin::Internal) ? "internal" : "external";
logDebug("notification {} id={} origin={}", kind, n.id, origin);
});
m_manager.setEventCallback([](const Notification& n, NotificationEvent event) {
const char* kind = (event == NotificationEvent::Added) ? "added" : "updated";
const char* origin = (n.origin == NotificationOrigin::Internal) ? "internal" : "external";
logDebug("notification {} id={} origin={}", kind, n.id, origin);
});
}
Application::~Application() {
// Explicitly clean up D-Bus services before the bus connection is destroyed
// This ensures clean disconnection and prevents blocking on shutdown
if (m_bus != nullptr) {
// Process any pending D-Bus events to ensure clean state
m_bus->processPendingEvents();
// Destroy services in reverse order they were created
m_notificationService.reset();
m_mprisService.reset();
m_debugService.reset();
// Process any final cleanup events
m_bus->processPendingEvents();
}
// MainLoop will be destroyed next, then SessionBus
// Explicitly clean up D-Bus services before the bus connection is destroyed
// This ensures clean disconnection and prevents blocking on shutdown
if (m_bus != nullptr) {
// Process any pending D-Bus events to ensure clean state
m_bus->processPendingEvents();
// Destroy services in reverse order they were created
m_notificationService.reset();
m_mprisService.reset();
m_debugService.reset();
// Process any final cleanup events
m_bus->processPendingEvents();
}
// MainLoop will be destroyed next, then SessionBus
}
void Application::run() {
// Install signal handlers for graceful shutdown
std::signal(SIGTERM, signal_handler);
std::signal(SIGINT, signal_handler);
// Install signal handlers for graceful shutdown
std::signal(SIGTERM, signal_handler);
std::signal(SIGINT, signal_handler);
// Connect to Wayland
if (!m_wayland.connect()) {
throw std::runtime_error("failed to connect to Wayland display");
// Connect to Wayland
if (!m_wayland.connect()) {
throw std::runtime_error("failed to connect to Wayland display");
}
// Set up output/workspace change callbacks
m_wayland.setOutputChangeCallback([this]() {
m_wallpaper.onOutputChange();
m_bar.onOutputChange();
});
m_wayland.setWorkspaceChangeCallback([this]() { m_bar.onWorkspaceChange(); });
// Initialize wallpaper first (background layer)
m_wallpaper.initialize(m_wayland, &m_configService, &m_stateService);
// Initialize bar (top layer)
m_bar.initialize(m_wayland, &m_configService, &m_timeService);
try {
m_systemMonitor = std::make_unique<SystemMonitorService>();
if (m_systemMonitor->isRunning()) {
logInfo("system monitor service active");
}
} catch (const std::exception& e) {
logWarn("system monitor service disabled: {}", e.what());
m_systemMonitor.reset();
}
// Set up output/workspace change callbacks
m_wayland.setOutputChangeCallback([this]() {
m_wallpaper.onOutputChange();
m_bar.onOutputChange();
});
m_wayland.setWorkspaceChangeCallback([this]() {
m_bar.onWorkspaceChange();
});
// Initialize wallpaper first (background layer)
m_wallpaper.initialize(m_wayland, &m_configService, &m_stateService);
// Initialize bar (top layer)
m_bar.initialize(m_wayland, &m_configService, &m_timeService);
try {
m_bus = std::make_unique<SessionBus>();
logInfo("connected to session bus");
} catch (const std::exception& e) {
logWarn("dbus disabled: {}", e.what());
m_internalNotifications.notify("Noctalia", "Session bus unavailable", e.what(), 8000, Urgency::Low);
}
if (m_bus != nullptr) {
try {
m_systemMonitor = std::make_unique<SystemMonitorService>();
if (m_systemMonitor->isRunning()) {
logInfo("system monitor service active");
}
m_debugService = std::make_unique<DebugService>(*m_bus, m_internalNotifications);
logInfo("debug service active on dev.noctalia.Debug");
} catch (const std::exception& e) {
logWarn("system monitor service disabled: {}", e.what());
m_systemMonitor.reset();
logWarn("debug service disabled: {}", e.what());
m_debugService.reset();
}
try {
m_bus = std::make_unique<SessionBus>();
logInfo("connected to session bus");
m_mprisService = std::make_unique<MprisService>(*m_bus);
logInfo("mpris discovery active");
} catch (const std::exception& e) {
logWarn("dbus disabled: {}", e.what());
m_internalNotifications.notify("Noctalia", "Session bus unavailable", e.what(), 8000, Urgency::Low);
logWarn("mpris disabled: {}", e.what());
m_mprisService.reset();
m_internalNotifications.notify("Noctalia", "MPRIS disabled", e.what(), 7000, Urgency::Low);
}
if (m_bus != nullptr) {
try {
m_debugService = std::make_unique<DebugService>(*m_bus, m_internalNotifications);
logInfo("debug service active on dev.noctalia.Debug");
} catch (const std::exception& e) {
logWarn("debug service disabled: {}", e.what());
m_debugService.reset();
}
try {
m_mprisService = std::make_unique<MprisService>(*m_bus);
logInfo("mpris discovery active");
} catch (const std::exception& e) {
logWarn("mpris disabled: {}", e.what());
m_mprisService.reset();
m_internalNotifications.notify("Noctalia", "MPRIS disabled", e.what(), 7000, Urgency::Low);
}
try {
m_notificationService = std::make_unique<NotificationService>(*m_bus, m_manager);
logInfo("listening on org.freedesktop.Notifications");
} catch (const std::exception& e) {
logWarn("notifications disabled: {}", e.what());
m_notificationService.reset();
m_internalNotifications.notify("Noctalia", "DBus notifications disabled", e.what(), 7000, Urgency::Low);
}
try {
m_notificationService = std::make_unique<NotificationService>(*m_bus, m_manager);
logInfo("listening on org.freedesktop.Notifications");
} catch (const std::exception& e) {
logWarn("notifications disabled: {}", e.what());
m_notificationService.reset();
m_internalNotifications.notify("Noctalia", "DBus notifications disabled", e.what(), 7000, Urgency::Low);
}
}
// Build poll sources
std::vector<PollSource*> sources;
if (m_bus != nullptr) {
m_busPollSource = std::make_unique<SessionBusPollSource>(*m_bus);
sources.push_back(m_busPollSource.get());
}
if (m_notificationService != nullptr) {
m_notificationPollSource = std::make_unique<NotificationPollSource>(*m_notificationService);
sources.push_back(m_notificationPollSource.get());
}
sources.push_back(&m_timePollSource);
sources.push_back(&m_configPollSource);
sources.push_back(&m_statePollSource);
// Build poll sources
std::vector<PollSource*> sources;
if (m_bus != nullptr) {
m_busPollSource = std::make_unique<SessionBusPollSource>(*m_bus);
sources.push_back(m_busPollSource.get());
}
if (m_notificationService != nullptr) {
m_notificationPollSource = std::make_unique<NotificationPollSource>(*m_notificationService);
sources.push_back(m_notificationPollSource.get());
}
sources.push_back(&m_timePollSource);
sources.push_back(&m_configPollSource);
sources.push_back(&m_statePollSource);
m_mainLoop = std::make_unique<MainLoop>(m_wayland, m_bar, std::move(sources));
m_mainLoop->run();
m_mainLoop = std::make_unique<MainLoop>(m_wayland, m_bar, std::move(sources));
m_mainLoop->run();
logInfo("shutdown");
logInfo("shutdown");
}
@@ -1,24 +1,24 @@
#pragma once
#include "app/MainLoop.hpp"
#include "config/ConfigPollSource.hpp"
#include "config/ConfigService.hpp"
#include "config/StatePollSource.hpp"
#include "config/StateService.hpp"
#include "dbus/SessionBus.hpp"
#include "dbus/SessionBusPollSource.hpp"
#include "dbus/mpris/MprisService.hpp"
#include "dbus/notification/NotificationPollSource.hpp"
#include "dbus/notification/NotificationService.hpp"
#include "debug/DebugService.hpp"
#include "notification/InternalNotificationService.hpp"
#include "notification/NotificationManager.hpp"
#include "shell/Bar.hpp"
#include "shell/Wallpaper.hpp"
#include "system/SystemMonitorService.hpp"
#include "time/TimePollSource.hpp"
#include "time/TimeService.hpp"
#include "wayland/WaylandConnection.hpp"
#include "app/MainLoop.h"
#include "config/ConfigPollSource.h"
#include "config/ConfigService.h"
#include "config/StatePollSource.h"
#include "config/StateService.h"
#include "dbus/SessionBus.h"
#include "dbus/SessionBusPollSource.h"
#include "dbus/mpris/MprisService.h"
#include "dbus/notification/NotificationPollSource.h"
#include "dbus/notification/NotificationService.h"
#include "debug/DebugService.h"
#include "notification/InternalNotificationService.h"
#include "notification/NotificationManager.h"
#include "shell/Bar.h"
#include "shell/Wallpaper.h"
#include "system/SystemMonitorService.h"
#include "time/TimePollSource.h"
#include "time/TimeService.h"
#include "wayland/WaylandConnection.h"
#include <atomic>
#include <memory>
+63 -65
View File
@@ -1,10 +1,10 @@
#include "app/MainLoop.hpp"
#include "app/MainLoop.h"
#include "app/Application.hpp"
#include "app/PollSource.hpp"
#include "core/Log.hpp"
#include "shell/Bar.hpp"
#include "wayland/WaylandConnection.hpp"
#include "app/Application.h"
#include "app/PollSource.h"
#include "core/Log.h"
#include "shell/Bar.h"
#include "wayland/WaylandConnection.h"
#include <cerrno>
#include <poll.h>
@@ -13,69 +13,67 @@
#include <wayland-client-core.h>
MainLoop::MainLoop(WaylandConnection& wayland, Bar& bar, std::vector<PollSource*> sources)
: m_wayland(wayland)
, m_bar(bar)
, m_sources(std::move(sources)) {}
: m_wayland(wayland), m_bar(bar), m_sources(std::move(sources)) {}
void MainLoop::run() {
while (m_bar.isRunning() && !Application::s_shutdownRequested) {
if (wl_display_dispatch_pending(m_wayland.display()) < 0) {
throw std::runtime_error("failed to dispatch pending Wayland events");
}
if (wl_display_flush(m_wayland.display()) < 0) {
throw std::runtime_error("failed to flush Wayland display");
}
// Collect poll fds and compute timeout from all sources
std::vector<pollfd> pollFds;
pollFds.push_back({.fd = wl_display_get_fd(m_wayland.display()), .events = POLLIN, .revents = 0});
int pollTimeout = -1;
std::vector<std::size_t> sourceStartIndices;
sourceStartIndices.reserve(m_sources.size());
for (auto* source : m_sources) {
sourceStartIndices.push_back(source->addPollFds(pollFds));
const int t = source->pollTimeoutMs();
if (t >= 0 && (pollTimeout < 0 || t < pollTimeout)) {
pollTimeout = t;
}
}
const int pollResult = ::poll(pollFds.data(), pollFds.size(), pollTimeout);
if (pollResult < 0) {
if (errno == EINTR) {
continue;
}
throw std::runtime_error("failed to poll fds");
}
// Dispatch Wayland events
if ((pollFds[0].revents & POLLIN) != 0) {
if (wl_display_dispatch(m_wayland.display()) < 0) {
throw std::runtime_error("failed to dispatch Wayland events");
}
} else {
if (wl_display_dispatch_pending(m_wayland.display()) < 0) {
throw std::runtime_error("failed to dispatch pending Wayland events");
}
}
// Dispatch all sources
for (std::size_t i = 0; i < m_sources.size(); ++i) {
m_sources[i]->dispatch(pollFds, sourceStartIndices[i]);
}
}
// Close all UI surfaces immediately and flush Wayland to make them disappear
logDebug("closing bar surfaces for clean shutdown");
m_bar.closeAllInstances();
while (m_bar.isRunning() && !Application::s_shutdownRequested) {
if (wl_display_dispatch_pending(m_wayland.display()) < 0) {
logWarn("failed to dispatch pending Wayland events during shutdown");
throw std::runtime_error("failed to dispatch pending Wayland events");
}
if (wl_display_flush(m_wayland.display()) < 0) {
logWarn("failed to flush Wayland display during shutdown");
throw std::runtime_error("failed to flush Wayland display");
}
// Collect poll fds and compute timeout from all sources
std::vector<pollfd> pollFds;
pollFds.push_back({.fd = wl_display_get_fd(m_wayland.display()), .events = POLLIN, .revents = 0});
int pollTimeout = -1;
std::vector<std::size_t> sourceStartIndices;
sourceStartIndices.reserve(m_sources.size());
for (auto* source : m_sources) {
sourceStartIndices.push_back(source->addPollFds(pollFds));
const int t = source->pollTimeoutMs();
if (t >= 0 && (pollTimeout < 0 || t < pollTimeout)) {
pollTimeout = t;
}
}
const int pollResult = ::poll(pollFds.data(), pollFds.size(), pollTimeout);
if (pollResult < 0) {
if (errno == EINTR) {
continue;
}
throw std::runtime_error("failed to poll fds");
}
// Dispatch Wayland events
if ((pollFds[0].revents & POLLIN) != 0) {
if (wl_display_dispatch(m_wayland.display()) < 0) {
throw std::runtime_error("failed to dispatch Wayland events");
}
} else {
if (wl_display_dispatch_pending(m_wayland.display()) < 0) {
throw std::runtime_error("failed to dispatch pending Wayland events");
}
}
// Dispatch all sources
for (std::size_t i = 0; i < m_sources.size(); ++i) {
m_sources[i]->dispatch(pollFds, sourceStartIndices[i]);
}
}
// Close all UI surfaces immediately and flush Wayland to make them disappear
logDebug("closing bar surfaces for clean shutdown");
m_bar.closeAllInstances();
if (wl_display_dispatch_pending(m_wayland.display()) < 0) {
logWarn("failed to dispatch pending Wayland events during shutdown");
}
if (wl_display_flush(m_wayland.display()) < 0) {
logWarn("failed to flush Wayland display during shutdown");
}
}
@@ -1,7 +1,7 @@
#pragma once
#include "app/PollSource.hpp"
#include "config/ConfigService.hpp"
#include "app/PollSource.h"
#include "config/ConfigService.h"
class ConfigPollSource final : public PollSource {
public:
+233 -199
View File
@@ -1,7 +1,7 @@
#include "config/ConfigService.hpp"
#include "config/ConfigService.h"
#include "core/Log.hpp"
#include "wayland/WaylandConnection.hpp"
#include "core/Log.h"
#include "wayland/WaylandConnection.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wshadow"
@@ -18,255 +18,289 @@
namespace {
std::string configPath() {
const char* xdg = std::getenv("XDG_CONFIG_HOME");
if (xdg != nullptr && xdg[0] != '\0') {
return std::string(xdg) + "/noctalia/config.toml";
}
const char* home = std::getenv("HOME");
if (home != nullptr && home[0] != '\0') {
return std::string(home) + "/.config/noctalia/config.toml";
}
return {};
const char* xdg = std::getenv("XDG_CONFIG_HOME");
if (xdg != nullptr && xdg[0] != '\0') {
return std::string(xdg) + "/noctalia/config.toml";
}
const char* home = std::getenv("HOME");
if (home != nullptr && home[0] != '\0') {
return std::string(home) + "/.config/noctalia/config.toml";
}
return {};
}
std::vector<std::string> readStringArray(const toml::node& node) {
std::vector<std::string> result;
if (auto* arr = node.as_array()) {
for (const auto& item : *arr) {
if (auto* str = item.as_string()) {
result.push_back(str->get());
}
}
std::vector<std::string> result;
if (auto* arr = node.as_array()) {
for (const auto& item : *arr) {
if (auto* str = item.as_string()) {
result.push_back(str->get());
}
}
return result;
}
return result;
}
bool matchesOutput(const std::string& match, const WaylandOutput& output) {
// Exact connector name match
if (!output.connectorName.empty() && match == output.connectorName) {
return true;
}
// Substring match on description
if (!output.description.empty() && output.description.find(match) != std::string::npos) {
return true;
}
return false;
// Exact connector name match
if (!output.connectorName.empty() && match == output.connectorName) {
return true;
}
// Substring match on description
if (!output.description.empty() && output.description.find(match) != std::string::npos) {
return true;
}
return false;
}
} // namespace
ConfigService::ConfigService() {
m_configPath = configPath();
if (!m_configPath.empty() && std::filesystem::exists(m_configPath)) {
loadFromFile(m_configPath);
} else {
logInfo("config: no config file found, using defaults");
m_config.bars.push_back(BarConfig{});
}
setupWatch();
m_configPath = configPath();
if (!m_configPath.empty() && std::filesystem::exists(m_configPath)) {
loadFromFile(m_configPath);
} else {
logInfo("config: no config file found, using defaults");
m_config.bars.push_back(BarConfig{});
}
setupWatch();
}
ConfigService::~ConfigService() {
if (m_watchFd >= 0 && m_inotifyFd >= 0) {
inotify_rm_watch(m_inotifyFd, m_watchFd);
}
if (m_inotifyFd >= 0) {
::close(m_inotifyFd);
}
if (m_watchFd >= 0 && m_inotifyFd >= 0) {
inotify_rm_watch(m_inotifyFd, m_watchFd);
}
if (m_inotifyFd >= 0) {
::close(m_inotifyFd);
}
}
void ConfigService::setReloadCallback(ReloadCallback callback) {
m_reloadCallback = std::move(callback);
}
void ConfigService::setReloadCallback(ReloadCallback callback) { m_reloadCallback = std::move(callback); }
void ConfigService::checkReload() {
if (m_inotifyFd < 0) {
return;
if (m_inotifyFd < 0) {
return;
}
// Drain inotify events, check if our config file was involved
alignas(inotify_event) char buf[4096];
bool configChanged = false;
const auto configFilename = std::filesystem::path(m_configPath).filename().string();
while (true) {
const auto n = ::read(m_inotifyFd, buf, sizeof(buf));
if (n <= 0) {
break;
}
// Drain inotify events, check if our config file was involved
alignas(inotify_event) char buf[4096];
bool configChanged = false;
const auto configFilename = std::filesystem::path(m_configPath).filename().string();
while (true) {
const auto n = ::read(m_inotifyFd, buf, sizeof(buf));
if (n <= 0) {
break;
}
// Walk through events to check filenames
std::size_t offset = 0;
while (offset < static_cast<std::size_t>(n)) {
auto* event = reinterpret_cast<inotify_event*>(buf + offset);
if (event->len > 0 && configFilename == event->name) {
configChanged = true;
}
offset += sizeof(inotify_event) + event->len;
}
// Walk through events to check filenames
std::size_t offset = 0;
while (offset < static_cast<std::size_t>(n)) {
auto* event = reinterpret_cast<inotify_event*>(buf + offset);
if (event->len > 0 && configFilename == event->name) {
configChanged = true;
}
offset += sizeof(inotify_event) + event->len;
}
}
if (!configChanged) {
return;
}
if (!configChanged) {
return;
}
m_config = Config{};
if (!m_configPath.empty() && std::filesystem::exists(m_configPath)) {
loadFromFile(m_configPath);
} else {
m_config.bars.push_back(BarConfig{});
}
m_config = Config{};
if (!m_configPath.empty() && std::filesystem::exists(m_configPath)) {
loadFromFile(m_configPath);
} else {
m_config.bars.push_back(BarConfig{});
}
if (m_reloadCallback) {
m_reloadCallback();
}
if (m_reloadCallback) {
m_reloadCallback();
}
}
void ConfigService::setupWatch() {
if (m_configPath.empty()) {
return;
}
if (m_configPath.empty()) {
return;
}
m_inotifyFd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
if (m_inotifyFd < 0) {
logWarn("config: inotify_init1 failed, hot reload disabled");
return;
}
m_inotifyFd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
if (m_inotifyFd < 0) {
logWarn("config: inotify_init1 failed, hot reload disabled");
return;
}
// Watch the directory (not just the file) to catch editors that rename+create
auto dir = std::filesystem::path(m_configPath).parent_path().string();
m_watchFd = inotify_add_watch(m_inotifyFd, dir.c_str(),
IN_MODIFY | IN_CREATE | IN_MOVED_TO);
if (m_watchFd < 0) {
logWarn("config: inotify_add_watch failed, hot reload disabled");
::close(m_inotifyFd);
m_inotifyFd = -1;
return;
}
// Watch the directory (not just the file) to catch editors that rename+create
auto dir = std::filesystem::path(m_configPath).parent_path().string();
m_watchFd = inotify_add_watch(m_inotifyFd, dir.c_str(), IN_MODIFY | IN_CREATE | IN_MOVED_TO);
if (m_watchFd < 0) {
logWarn("config: inotify_add_watch failed, hot reload disabled");
::close(m_inotifyFd);
m_inotifyFd = -1;
return;
}
logDebug("config: watching {} for changes", dir);
logDebug("config: watching {} for changes", dir);
}
void ConfigService::loadFromFile(const std::string& path) {
logInfo("config: loading {}", path);
logInfo("config: loading {}", path);
toml::table tbl;
try {
tbl = toml::parse_file(path);
} catch (const toml::parse_error& e) {
logWarn("config: parse error: {}", e.what());
m_config.bars.push_back(BarConfig{});
return;
}
toml::table tbl;
try {
tbl = toml::parse_file(path);
} catch (const toml::parse_error& e) {
logWarn("config: parse error: {}", e.what());
m_config.bars.push_back(BarConfig{});
return;
}
// Parse [[bar]] array
if (auto* barArray = tbl["bar"].as_array()) {
for (const auto& barNode : *barArray) {
auto* barTbl = barNode.as_table();
if (barTbl == nullptr) {
continue;
}
// Parse [[bar]] array
if (auto* barArray = tbl["bar"].as_array()) {
for (const auto& barNode : *barArray) {
auto* barTbl = barNode.as_table();
if (barTbl == nullptr) {
continue;
}
BarConfig bar;
if (auto v = (*barTbl)["name"].value<std::string>()) bar.name = *v;
if (auto v = (*barTbl)["position"].value<std::string>()) bar.position = *v;
if (auto v = (*barTbl)["enabled"].value<bool>()) bar.enabled = *v;
if (auto v = (*barTbl)["height"].value<int64_t>()) bar.height = static_cast<std::uint32_t>(*v);
if (auto v = (*barTbl)["padding"].value<double>()) bar.padding = static_cast<float>(*v);
if (auto v = (*barTbl)["gap"].value<double>()) bar.gap = static_cast<float>(*v);
if (auto* n = (*barTbl)["start"].as_array()) bar.startWidgets = readStringArray(*n);
if (auto* n = (*barTbl)["center"].as_array()) bar.centerWidgets = readStringArray(*n);
if (auto* n = (*barTbl)["end"].as_array()) bar.endWidgets = readStringArray(*n);
BarConfig bar;
if (auto v = (*barTbl)["name"].value<std::string>())
bar.name = *v;
if (auto v = (*barTbl)["position"].value<std::string>())
bar.position = *v;
if (auto v = (*barTbl)["enabled"].value<bool>())
bar.enabled = *v;
if (auto v = (*barTbl)["height"].value<int64_t>())
bar.height = static_cast<std::uint32_t>(*v);
if (auto v = (*barTbl)["padding"].value<double>())
bar.padding = static_cast<float>(*v);
if (auto v = (*barTbl)["gap"].value<double>())
bar.gap = static_cast<float>(*v);
if (auto* n = (*barTbl)["start"].as_array())
bar.startWidgets = readStringArray(*n);
if (auto* n = (*barTbl)["center"].as_array())
bar.centerWidgets = readStringArray(*n);
if (auto* n = (*barTbl)["end"].as_array())
bar.endWidgets = readStringArray(*n);
// Parse [[bar.monitor]] overrides
if (auto* monArray = (*barTbl)["monitor"].as_array()) {
for (const auto& monNode : *monArray) {
auto* monTbl = monNode.as_table();
if (monTbl == nullptr) {
continue;
}
// Parse [[bar.monitor]] overrides
if (auto* monArray = (*barTbl)["monitor"].as_array()) {
for (const auto& monNode : *monArray) {
auto* monTbl = monNode.as_table();
if (monTbl == nullptr) {
continue;
}
BarMonitorOverride ovr;
if (auto v = (*monTbl)["match"].value<std::string>()) {
ovr.match = *v;
} else {
continue; // match is required
}
BarMonitorOverride ovr;
if (auto v = (*monTbl)["match"].value<std::string>()) {
ovr.match = *v;
} else {
continue; // match is required
}
if (auto v = (*monTbl)["enabled"].value<bool>()) ovr.enabled = *v;
if (auto v = (*monTbl)["height"].value<int64_t>()) ovr.height = static_cast<std::uint32_t>(*v);
if (auto v = (*monTbl)["padding"].value<double>()) ovr.padding = static_cast<float>(*v);
if (auto v = (*monTbl)["gap"].value<double>()) ovr.gap = static_cast<float>(*v);
if (auto* n = (*monTbl)["start"].as_array()) ovr.startWidgets = readStringArray(*n);
if (auto* n = (*monTbl)["center"].as_array()) ovr.centerWidgets = readStringArray(*n);
if (auto* n = (*monTbl)["end"].as_array()) ovr.endWidgets = readStringArray(*n);
if (auto v = (*monTbl)["enabled"].value<bool>())
ovr.enabled = *v;
if (auto v = (*monTbl)["height"].value<int64_t>())
ovr.height = static_cast<std::uint32_t>(*v);
if (auto v = (*monTbl)["padding"].value<double>())
ovr.padding = static_cast<float>(*v);
if (auto v = (*monTbl)["gap"].value<double>())
ovr.gap = static_cast<float>(*v);
if (auto* n = (*monTbl)["start"].as_array())
ovr.startWidgets = readStringArray(*n);
if (auto* n = (*monTbl)["center"].as_array())
ovr.centerWidgets = readStringArray(*n);
if (auto* n = (*monTbl)["end"].as_array())
ovr.endWidgets = readStringArray(*n);
bar.monitorOverrides.push_back(std::move(ovr));
}
}
m_config.bars.push_back(std::move(bar));
bar.monitorOverrides.push_back(std::move(ovr));
}
}
}
// Parse [clock]
if (auto* clockTbl = tbl["clock"].as_table()) {
if (auto v = (*clockTbl)["format"].value<std::string>()) {
m_config.clock.format = *v;
}
m_config.bars.push_back(std::move(bar));
}
}
// Parse [wallpaper]
if (auto* wpTbl = tbl["wallpaper"].as_table()) {
auto& wp = m_config.wallpaper;
if (auto v = (*wpTbl)["enabled"].value<bool>()) wp.enabled = *v;
if (auto v = (*wpTbl)["fill_mode"].value<std::string>()) {
if (*v == "center") wp.fillMode = WallpaperFillMode::Center;
else if (*v == "crop") wp.fillMode = WallpaperFillMode::Crop;
else if (*v == "fit") wp.fillMode = WallpaperFillMode::Fit;
else if (*v == "stretch") wp.fillMode = WallpaperFillMode::Stretch;
else if (*v == "repeat") wp.fillMode = WallpaperFillMode::Repeat;
}
if (auto v = (*wpTbl)["transition"].value<std::string>()) {
if (*v == "fade") wp.transition = WallpaperTransition::Fade;
else if (*v == "wipe") wp.transition = WallpaperTransition::Wipe;
else if (*v == "disc") wp.transition = WallpaperTransition::Disc;
else if (*v == "stripes") wp.transition = WallpaperTransition::Stripes;
else if (*v == "pixelate") wp.transition = WallpaperTransition::Pixelate;
else if (*v == "honeycomb") wp.transition = WallpaperTransition::Honeycomb;
}
if (auto v = (*wpTbl)["transition_duration"].value<double>()) wp.transitionDurationMs = static_cast<float>(*v);
if (auto v = (*wpTbl)["edge_smoothness"].value<double>()) wp.edgeSmoothness = static_cast<float>(*v);
// Parse [clock]
if (auto* clockTbl = tbl["clock"].as_table()) {
if (auto v = (*clockTbl)["format"].value<std::string>()) {
m_config.clock.format = *v;
}
}
if (m_config.bars.empty()) {
logInfo("config: no [[bar]] defined, using defaults");
m_config.bars.push_back(BarConfig{});
// Parse [wallpaper]
if (auto* wpTbl = tbl["wallpaper"].as_table()) {
auto& wp = m_config.wallpaper;
if (auto v = (*wpTbl)["enabled"].value<bool>())
wp.enabled = *v;
if (auto v = (*wpTbl)["fill_mode"].value<std::string>()) {
if (*v == "center")
wp.fillMode = WallpaperFillMode::Center;
else if (*v == "crop")
wp.fillMode = WallpaperFillMode::Crop;
else if (*v == "fit")
wp.fillMode = WallpaperFillMode::Fit;
else if (*v == "stretch")
wp.fillMode = WallpaperFillMode::Stretch;
else if (*v == "repeat")
wp.fillMode = WallpaperFillMode::Repeat;
}
if (auto v = (*wpTbl)["transition"].value<std::string>()) {
if (*v == "fade")
wp.transition = WallpaperTransition::Fade;
else if (*v == "wipe")
wp.transition = WallpaperTransition::Wipe;
else if (*v == "disc")
wp.transition = WallpaperTransition::Disc;
else if (*v == "stripes")
wp.transition = WallpaperTransition::Stripes;
else if (*v == "pixelate")
wp.transition = WallpaperTransition::Pixelate;
else if (*v == "honeycomb")
wp.transition = WallpaperTransition::Honeycomb;
}
if (auto v = (*wpTbl)["transition_duration"].value<double>())
wp.transitionDurationMs = static_cast<float>(*v);
if (auto v = (*wpTbl)["edge_smoothness"].value<double>())
wp.edgeSmoothness = static_cast<float>(*v);
}
logInfo("config: {} bar(s) defined", m_config.bars.size());
if (m_config.bars.empty()) {
logInfo("config: no [[bar]] defined, using defaults");
m_config.bars.push_back(BarConfig{});
}
logInfo("config: {} bar(s) defined", m_config.bars.size());
}
BarConfig ConfigService::resolveForOutput(const BarConfig& base, const WaylandOutput& output) {
BarConfig resolved = base;
BarConfig resolved = base;
for (const auto& ovr : base.monitorOverrides) {
if (!matchesOutput(ovr.match, output)) {
continue;
}
logDebug("config: monitor override \"{}\" matched output {} ({})",
ovr.match, output.connectorName, output.description);
if (ovr.enabled) resolved.enabled = *ovr.enabled;
if (ovr.height) resolved.height = *ovr.height;
if (ovr.padding) resolved.padding = *ovr.padding;
if (ovr.gap) resolved.gap = *ovr.gap;
if (ovr.startWidgets) resolved.startWidgets = *ovr.startWidgets;
if (ovr.centerWidgets) resolved.centerWidgets = *ovr.centerWidgets;
if (ovr.endWidgets) resolved.endWidgets = *ovr.endWidgets;
break; // first match wins
for (const auto& ovr : base.monitorOverrides) {
if (!matchesOutput(ovr.match, output)) {
continue;
}
return resolved;
logDebug("config: monitor override \"{}\" matched output {} ({})", ovr.match, output.connectorName,
output.description);
if (ovr.enabled)
resolved.enabled = *ovr.enabled;
if (ovr.height)
resolved.height = *ovr.height;
if (ovr.padding)
resolved.padding = *ovr.padding;
if (ovr.gap)
resolved.gap = *ovr.gap;
if (ovr.startWidgets)
resolved.startWidgets = *ovr.startWidgets;
if (ovr.centerWidgets)
resolved.centerWidgets = *ovr.centerWidgets;
if (ovr.endWidgets)
resolved.endWidgets = *ovr.endWidgets;
break; // first match wins
}
return resolved;
}
@@ -1,7 +1,7 @@
#pragma once
#include "app/PollSource.hpp"
#include "config/StateService.hpp"
#include "app/PollSource.h"
#include "config/StateService.h"
class StatePollSource final : public PollSource {
public:
+112 -116
View File
@@ -1,6 +1,6 @@
#include "config/StateService.hpp"
#include "config/StateService.h"
#include "core/Log.hpp"
#include "core/Log.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wshadow"
@@ -17,162 +17,158 @@
namespace {
std::string statePath() {
const char* xdg = std::getenv("XDG_STATE_HOME");
if (xdg != nullptr && xdg[0] != '\0') {
return std::string(xdg) + "/noctalia/state.toml";
}
const char* home = std::getenv("HOME");
if (home != nullptr && home[0] != '\0') {
return std::string(home) + "/.local/state/noctalia/state.toml";
}
return {};
const char* xdg = std::getenv("XDG_STATE_HOME");
if (xdg != nullptr && xdg[0] != '\0') {
return std::string(xdg) + "/noctalia/state.toml";
}
const char* home = std::getenv("HOME");
if (home != nullptr && home[0] != '\0') {
return std::string(home) + "/.local/state/noctalia/state.toml";
}
return {};
}
} // namespace
StateService::StateService() {
m_statePath = statePath();
if (!m_statePath.empty()) {
// Create directory if it doesn't exist
auto dir = std::filesystem::path(m_statePath).parent_path();
std::error_code ec;
std::filesystem::create_directories(dir, ec);
m_statePath = statePath();
if (!m_statePath.empty()) {
// Create directory if it doesn't exist
auto dir = std::filesystem::path(m_statePath).parent_path();
std::error_code ec;
std::filesystem::create_directories(dir, ec);
if (std::filesystem::exists(m_statePath)) {
loadFromFile(m_statePath);
} else {
logInfo("state: no state file found at {}", m_statePath);
}
if (std::filesystem::exists(m_statePath)) {
loadFromFile(m_statePath);
} else {
logInfo("state: no state file found at {}", m_statePath);
}
setupWatch();
}
setupWatch();
}
StateService::~StateService() {
if (m_watchDescriptor >= 0 && m_inotifyFd >= 0) {
inotify_rm_watch(m_inotifyFd, m_watchDescriptor);
}
if (m_inotifyFd >= 0) {
::close(m_inotifyFd);
}
if (m_watchDescriptor >= 0 && m_inotifyFd >= 0) {
inotify_rm_watch(m_inotifyFd, m_watchDescriptor);
}
if (m_inotifyFd >= 0) {
::close(m_inotifyFd);
}
}
std::string StateService::getWallpaperPath(const std::string& connectorName) const {
auto it = m_monitorWallpaperPaths.find(connectorName);
if (it != m_monitorWallpaperPaths.end()) {
return it->second;
}
return m_defaultWallpaperPath;
auto it = m_monitorWallpaperPaths.find(connectorName);
if (it != m_monitorWallpaperPaths.end()) {
return it->second;
}
return m_defaultWallpaperPath;
}
std::string StateService::getDefaultWallpaperPath() const {
return m_defaultWallpaperPath;
}
std::string StateService::getDefaultWallpaperPath() const { return m_defaultWallpaperPath; }
void StateService::setWallpaperChangeCallback(ChangeCallback callback) {
m_wallpaperChangeCallback = std::move(callback);
m_wallpaperChangeCallback = std::move(callback);
}
void StateService::checkReload() {
if (m_inotifyFd < 0) {
return;
if (m_inotifyFd < 0) {
return;
}
alignas(inotify_event) char buf[4096];
bool stateChanged = false;
const auto stateFilename = std::filesystem::path(m_statePath).filename().string();
while (true) {
const auto n = ::read(m_inotifyFd, buf, sizeof(buf));
if (n <= 0) {
break;
}
alignas(inotify_event) char buf[4096];
bool stateChanged = false;
const auto stateFilename = std::filesystem::path(m_statePath).filename().string();
while (true) {
const auto n = ::read(m_inotifyFd, buf, sizeof(buf));
if (n <= 0) {
break;
}
std::size_t offset = 0;
while (offset < static_cast<std::size_t>(n)) {
auto* event = reinterpret_cast<inotify_event*>(buf + offset);
if (event->len > 0 && stateFilename == event->name) {
stateChanged = true;
}
offset += sizeof(inotify_event) + event->len;
}
std::size_t offset = 0;
while (offset < static_cast<std::size_t>(n)) {
auto* event = reinterpret_cast<inotify_event*>(buf + offset);
if (event->len > 0 && stateFilename == event->name) {
stateChanged = true;
}
offset += sizeof(inotify_event) + event->len;
}
}
if (!stateChanged) {
return;
}
if (!stateChanged) {
return;
}
logInfo("state: reloading {}", m_statePath);
logInfo("state: reloading {}", m_statePath);
auto oldDefault = m_defaultWallpaperPath;
auto oldMonitors = m_monitorWallpaperPaths;
auto oldDefault = m_defaultWallpaperPath;
auto oldMonitors = m_monitorWallpaperPaths;
m_defaultWallpaperPath.clear();
m_monitorWallpaperPaths.clear();
m_defaultWallpaperPath.clear();
m_monitorWallpaperPaths.clear();
if (std::filesystem::exists(m_statePath)) {
loadFromFile(m_statePath);
}
if (std::filesystem::exists(m_statePath)) {
loadFromFile(m_statePath);
}
// Check if wallpaper data actually changed
bool wallpaperChanged = (oldDefault != m_defaultWallpaperPath || oldMonitors != m_monitorWallpaperPaths);
if (wallpaperChanged && m_wallpaperChangeCallback) {
m_wallpaperChangeCallback();
}
// Check if wallpaper data actually changed
bool wallpaperChanged = (oldDefault != m_defaultWallpaperPath || oldMonitors != m_monitorWallpaperPaths);
if (wallpaperChanged && m_wallpaperChangeCallback) {
m_wallpaperChangeCallback();
}
}
void StateService::setupWatch() {
if (m_statePath.empty()) {
return;
}
if (m_statePath.empty()) {
return;
}
m_inotifyFd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
if (m_inotifyFd < 0) {
logWarn("state: inotify_init1 failed, state file watch disabled");
return;
}
m_inotifyFd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
if (m_inotifyFd < 0) {
logWarn("state: inotify_init1 failed, state file watch disabled");
return;
}
auto dir = std::filesystem::path(m_statePath).parent_path().string();
m_watchDescriptor = inotify_add_watch(m_inotifyFd, dir.c_str(),
IN_MODIFY | IN_CREATE | IN_MOVED_TO);
if (m_watchDescriptor < 0) {
logWarn("state: inotify_add_watch failed, state file watch disabled");
::close(m_inotifyFd);
m_inotifyFd = -1;
return;
}
auto dir = std::filesystem::path(m_statePath).parent_path().string();
m_watchDescriptor = inotify_add_watch(m_inotifyFd, dir.c_str(), IN_MODIFY | IN_CREATE | IN_MOVED_TO);
if (m_watchDescriptor < 0) {
logWarn("state: inotify_add_watch failed, state file watch disabled");
::close(m_inotifyFd);
m_inotifyFd = -1;
return;
}
logDebug("state: watching {} for changes", dir);
logDebug("state: watching {} for changes", dir);
}
void StateService::loadFromFile(const std::string& path) {
logInfo("state: loading {}", path);
logInfo("state: loading {}", path);
toml::table tbl;
try {
tbl = toml::parse_file(path);
} catch (const toml::parse_error& e) {
logWarn("state: parse error: {}", e.what());
return;
toml::table tbl;
try {
tbl = toml::parse_file(path);
} catch (const toml::parse_error& e) {
logWarn("state: parse error: {}", e.what());
return;
}
// Parse [wallpaper.default]
if (auto* wpDefault = tbl["wallpaper"]["default"].as_table()) {
if (auto v = (*wpDefault)["path"].value<std::string>()) {
m_defaultWallpaperPath = *v;
}
}
// Parse [wallpaper.default]
if (auto* wpDefault = tbl["wallpaper"]["default"].as_table()) {
if (auto v = (*wpDefault)["path"].value<std::string>()) {
m_defaultWallpaperPath = *v;
// Parse [wallpaper.monitors."connector-name"]
if (auto* monitors = tbl["wallpaper"]["monitors"].as_table()) {
for (const auto& [key, value] : *monitors) {
if (auto* monTbl = value.as_table()) {
if (auto v = (*monTbl)["path"].value<std::string>()) {
m_monitorWallpaperPaths[std::string(key.str())] = *v;
}
}
}
}
// Parse [wallpaper.monitors."connector-name"]
if (auto* monitors = tbl["wallpaper"]["monitors"].as_table()) {
for (const auto& [key, value] : *monitors) {
if (auto* monTbl = value.as_table()) {
if (auto v = (*monTbl)["path"].value<std::string>()) {
m_monitorWallpaperPaths[std::string(key.str())] = *v;
}
}
}
}
logInfo("state: wallpaper default=\"{}\" monitors={}",
m_defaultWallpaperPath, m_monitorWallpaperPaths.size());
logInfo("state: wallpaper default=\"{}\" monitors={}", m_defaultWallpaperPath, m_monitorWallpaperPaths.size());
}
+22 -23
View File
@@ -1,4 +1,4 @@
#include "core/Log.hpp"
#include "core/Log.h"
#include <cstdio>
#include <ctime>
@@ -9,13 +9,17 @@ namespace {
LogLevel gMinLevel = LogLevel::Info;
const char* levelTag(LogLevel level) {
switch (level) {
case LogLevel::Debug: return "\033[36mDBG\033[0m";
case LogLevel::Info: return "\033[32mINF\033[0m";
case LogLevel::Warn: return "\033[33mWRN\033[0m";
case LogLevel::Error: return "\033[31mERR\033[0m";
}
return "???";
switch (level) {
case LogLevel::Debug:
return "\033[36mDBG\033[0m";
case LogLevel::Info:
return "\033[32mINF\033[0m";
case LogLevel::Warn:
return "\033[33mWRN\033[0m";
case LogLevel::Error:
return "\033[31mERR\033[0m";
}
return "???";
}
} // namespace
@@ -23,24 +27,19 @@ const char* levelTag(LogLevel level) {
namespace detail {
void logMessage(LogLevel level, std::string_view msg) {
if (level < gMinLevel) {
return;
}
if (level < gMinLevel) {
return;
}
std::timespec ts{};
std::timespec_get(&ts, TIME_UTC);
std::tm tm{};
localtime_r(&ts.tv_sec, &tm);
std::timespec ts{};
std::timespec_get(&ts, TIME_UTC);
std::tm tm{};
localtime_r(&ts.tv_sec, &tm);
std::fprintf(stderr, "%02d:%02d:%02d.%03ld [%s] %.*s\n",
tm.tm_hour, tm.tm_min, tm.tm_sec,
ts.tv_nsec / 1'000'000,
levelTag(level),
static_cast<int>(msg.size()), msg.data());
std::fprintf(stderr, "%02d:%02d:%02d.%03ld [%s] %.*s\n", tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec / 1'000'000,
levelTag(level), static_cast<int>(msg.size()), msg.data());
}
} // namespace detail
void setLogLevel(LogLevel level) {
gMinLevel = level;
}
void setLogLevel(LogLevel level) { gMinLevel = level; }
View File
+5 -7
View File
@@ -1,12 +1,10 @@
#include "dbus/SessionBus.hpp"
#include "dbus/SessionBus.h"
SessionBus::SessionBus()
: m_connection(sdbus::createSessionBusConnection()) {}
SessionBus::SessionBus() : m_connection(sdbus::createSessionBusConnection()) {}
sdbus::IConnection::PollData SessionBus::getPollData() const {
return m_connection->getEventLoopPollData();
}
sdbus::IConnection::PollData SessionBus::getPollData() const { return m_connection->getEventLoopPollData(); }
void SessionBus::processPendingEvents() {
while (m_connection->processPendingEvent()) {}
while (m_connection->processPendingEvent()) {
}
}
@@ -1,7 +1,7 @@
#pragma once
#include "app/PollSource.hpp"
#include "dbus/SessionBus.hpp"
#include "app/PollSource.h"
#include "dbus/SessionBus.h"
class SessionBusPollSource final : public PollSource {
public:
File diff suppressed because it is too large Load Diff
@@ -1,7 +1,7 @@
#pragma once
#include "app/PollSource.hpp"
#include "dbus/notification/NotificationService.hpp"
#include "app/PollSource.h"
#include "dbus/notification/NotificationService.h"
class NotificationPollSource final : public PollSource {
public:
+161 -199
View File
@@ -1,278 +1,240 @@
#include "NotificationService.hpp"
#include "NotificationService.h"
#include "core/Log.hpp"
#include "dbus/SessionBus.hpp"
#include "core/Log.h"
#include "dbus/SessionBus.h"
#include <algorithm>
#include <stdexcept>
static const sdbus::ServiceName k_bus_name {"org.freedesktop.Notifications"};
static const sdbus::ObjectPath k_object_path{"/org/freedesktop/Notifications"};
static constexpr auto k_interface = "org.freedesktop.Notifications";
static const sdbus::ServiceName k_bus_name{"org.freedesktop.Notifications"};
static const sdbus::ObjectPath k_object_path{"/org/freedesktop/Notifications"};
static constexpr auto k_interface = "org.freedesktop.Notifications";
NotificationService::NotificationService(SessionBus& bus, NotificationManager& manager)
: m_manager(manager)
{
bus.connection().requestName(k_bus_name);
m_object = sdbus::createObject(bus.connection(), k_object_path);
NotificationService::NotificationService(SessionBus& bus, NotificationManager& manager) : m_manager(manager) {
bus.connection().requestName(k_bus_name);
m_object = sdbus::createObject(bus.connection(), k_object_path);
m_object->addVTable(
sdbus::registerMethod("Notify")
.withInputParamNames("app_name", "replaces_id", "app_icon",
"summary", "body", "actions", "hints",
"expire_timeout")
.withOutputParamNames("id")
.implementedAs([this](const std::string& app_name,
uint32_t replaces_id,
const std::string& app_icon,
const std::string& summary,
const std::string& body,
const std::vector<std::string>& actions,
const std::map<std::string, sdbus::Variant>& hints,
int32_t expire_timeout) {
return onNotify(app_name, replaces_id, app_icon,
summary, body, actions, hints, expire_timeout);
}),
m_object
->addVTable(
sdbus::registerMethod("Notify")
.withInputParamNames("app_name", "replaces_id", "app_icon", "summary", "body", "actions", "hints",
"expire_timeout")
.withOutputParamNames("id")
.implementedAs([this](const std::string& app_name, uint32_t replaces_id, const std::string& app_icon,
const std::string& summary, const std::string& body,
const std::vector<std::string>& actions,
const std::map<std::string, sdbus::Variant>& hints, int32_t expire_timeout) {
return onNotify(app_name, replaces_id, app_icon, summary, body, actions, hints, expire_timeout);
}),
sdbus::registerMethod("GetCapabilities")
.withOutputParamNames("capabilities")
.implementedAs([this]() {
return onGetCapabilities();
}),
sdbus::registerMethod("GetCapabilities").withOutputParamNames("capabilities").implementedAs([this]() {
return onGetCapabilities();
}),
sdbus::registerMethod("GetNotifications")
.withOutputParamNames("notifications")
.implementedAs([this]() {
return onGetNotifications();
}),
sdbus::registerMethod("GetNotifications").withOutputParamNames("notifications").implementedAs([this]() {
return onGetNotifications();
}),
sdbus::registerMethod("GetServerInformation")
.withOutputParamNames("name", "vendor", "version", "spec_version")
.implementedAs([this]() {
return onGetServerInformation();
}),
sdbus::registerMethod("GetServerInformation")
.withOutputParamNames("name", "vendor", "version", "spec_version")
.implementedAs([this]() { return onGetServerInformation(); }),
sdbus::registerMethod("CloseNotification")
.withInputParamNames("id")
.implementedAs([this](uint32_t id) {
onCloseNotification(id);
}),
sdbus::registerMethod("CloseNotification").withInputParamNames("id").implementedAs([this](uint32_t id) {
onCloseNotification(id);
}),
sdbus::registerMethod("InvokeAction")
.withInputParamNames("id", "action_key")
.implementedAs([this](uint32_t id, const std::string& action_key) {
onInvokeAction(id, action_key);
}),
sdbus::registerMethod("InvokeAction")
.withInputParamNames("id", "action_key")
.implementedAs([this](uint32_t id, const std::string& action_key) { onInvokeAction(id, action_key); }),
sdbus::registerSignal("NotificationClosed")
.withParameters<uint32_t, uint32_t>("id", "reason"),
sdbus::registerSignal("NotificationClosed").withParameters<uint32_t, uint32_t>("id", "reason"),
sdbus::registerSignal("ActionInvoked")
.withParameters<uint32_t, std::string>("id", "action_key")
).forInterface(k_interface);
sdbus::registerSignal("ActionInvoked").withParameters<uint32_t, std::string>("id", "action_key"))
.forInterface(k_interface);
}
namespace {
int computeExpiryTimeoutMs(const NotificationManager& manager) {
int expiry_ms = -1;
const auto now = Clock::now();
for (const auto& n : manager.all()) {
if (n.expiry_time) {
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
*n.expiry_time - now).count();
const int clamped = static_cast<int>(std::max<long long>(0, ms));
if (expiry_ms < 0 || clamped < expiry_ms) {
expiry_ms = clamped;
}
}
int expiry_ms = -1;
const auto now = Clock::now();
for (const auto& n : manager.all()) {
if (n.expiry_time) {
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(*n.expiry_time - now).count();
const int clamped = static_cast<int>(std::max<long long>(0, ms));
if (expiry_ms < 0 || clamped < expiry_ms) {
expiry_ms = clamped;
}
}
return expiry_ms;
}
return expiry_ms;
}
} // namespace
int NotificationService::nextExpiryTimeoutMs() const {
return computeExpiryTimeoutMs(m_manager);
}
int NotificationService::nextExpiryTimeoutMs() const { return computeExpiryTimeoutMs(m_manager); }
void NotificationService::processExpiredNotifications() {
for (const uint32_t id : m_manager.expiredIds()) {
emitClose(id, CloseReason::Expired);
m_manager.close(id, CloseReason::Expired);
}
for (const uint32_t id : m_manager.expiredIds()) {
emitClose(id, CloseReason::Expired);
m_manager.close(id, CloseReason::Expired);
}
}
static constexpr size_t k_max_string_len = 1024;
static constexpr int32_t k_min_timeout = -1;
static constexpr size_t k_max_string_len = 1024;
static constexpr int32_t k_min_timeout = -1;
namespace {
std::string clamp_str(std::string s) {
if (s.size() > k_max_string_len)
s.resize(k_max_string_len);
return s;
if (s.size() > k_max_string_len)
s.resize(k_max_string_len);
return s;
}
std::vector<std::string> sanitize_actions(const std::vector<std::string>& actions) {
std::vector<std::string> sanitized;
sanitized.reserve(actions.size() - (actions.size() % 2));
std::vector<std::string> sanitized;
sanitized.reserve(actions.size() - (actions.size() % 2));
for (size_t i = 0; i + 1 < actions.size(); i += 2) {
std::string action_key = clamp_str(actions[i]);
std::string label = clamp_str(actions[i + 1]);
for (size_t i = 0; i + 1 < actions.size(); i += 2) {
std::string action_key = clamp_str(actions[i]);
std::string label = clamp_str(actions[i + 1]);
if (action_key.empty()) {
continue;
}
sanitized.push_back(std::move(action_key));
sanitized.push_back(std::move(label));
if (action_key.empty()) {
continue;
}
return sanitized;
sanitized.push_back(std::move(action_key));
sanitized.push_back(std::move(label));
}
return sanitized;
}
bool notification_has_action(const Notification& notification, const std::string& action_key) {
for (size_t i = 0; i + 1 < notification.actions.size(); i += 2) {
if (notification.actions[i] == action_key) {
return true;
}
for (size_t i = 0; i + 1 < notification.actions.size(); i += 2) {
if (notification.actions[i] == action_key) {
return true;
}
return false;
}
return false;
}
} // namespace
uint32_t NotificationService::onNotify(const std::string& app_name,
uint32_t replaces_id,
const std::string& app_icon,
const std::string& summary,
const std::string& body,
const std::vector<std::string>& actions,
const std::map<std::string, sdbus::Variant>& hints,
int32_t expire_timeout) {
// Sanitize scalar inputs
const int32_t timeout = std::max(expire_timeout, k_min_timeout);
const auto sanitized_actions = sanitize_actions(actions);
uint32_t NotificationService::onNotify(const std::string& app_name, uint32_t replaces_id, const std::string& app_icon,
const std::string& summary, const std::string& body,
const std::vector<std::string>& actions,
const std::map<std::string, sdbus::Variant>& hints, int32_t expire_timeout) {
// Sanitize scalar inputs
const int32_t timeout = std::max(expire_timeout, k_min_timeout);
const auto sanitized_actions = sanitize_actions(actions);
// Urgency: default Normal, reject out-of-range byte values
Urgency urgency = Urgency::Normal;
if (auto it = hints.find("urgency"); it != hints.end()) {
try {
const uint8_t raw = it->second.get<uint8_t>();
if (raw <= static_cast<uint8_t>(Urgency::Critical)) {
urgency = static_cast<Urgency>(raw);
}
} catch (...) {}
// Urgency: default Normal, reject out-of-range byte values
Urgency urgency = Urgency::Normal;
if (auto it = hints.find("urgency"); it != hints.end()) {
try {
const uint8_t raw = it->second.get<uint8_t>();
if (raw <= static_cast<uint8_t>(Urgency::Critical)) {
urgency = static_cast<Urgency>(raw);
}
} catch (...) {
}
}
std::optional<std::string> icon;
if (!app_icon.empty()) {
icon = clamp_str(app_icon);
}
if (auto it = hints.find("image-path"); it != hints.end()) {
try {
icon = clamp_str(it->second.get<std::string>());
} catch (...) {}
std::optional<std::string> icon;
if (!app_icon.empty()) {
icon = clamp_str(app_icon);
}
if (auto it = hints.find("image-path"); it != hints.end()) {
try {
icon = clamp_str(it->second.get<std::string>());
} catch (...) {
}
}
std::optional<std::string> category;
if (auto it = hints.find("category"); it != hints.end()) {
try {
category = clamp_str(it->second.get<std::string>());
} catch (...) {}
std::optional<std::string> category;
if (auto it = hints.find("category"); it != hints.end()) {
try {
category = clamp_str(it->second.get<std::string>());
} catch (...) {
}
}
std::optional<std::string> desktop_entry;
if (auto it = hints.find("desktop-entry"); it != hints.end()) {
try {
desktop_entry = clamp_str(it->second.get<std::string>());
} catch (...) {}
std::optional<std::string> desktop_entry;
if (auto it = hints.find("desktop-entry"); it != hints.end()) {
try {
desktop_entry = clamp_str(it->second.get<std::string>());
} catch (...) {
}
}
return m_manager.addOrReplace(replaces_id,
clamp_str(app_name),
clamp_str(summary),
clamp_str(body),
timeout,
urgency,
NotificationOrigin::External,
sanitized_actions,
icon,
category,
desktop_entry);
return m_manager.addOrReplace(replaces_id, clamp_str(app_name), clamp_str(summary), clamp_str(body), timeout, urgency,
NotificationOrigin::External, sanitized_actions, icon, category, desktop_entry);
}
std::vector<std::string> NotificationService::onGetCapabilities() {
return {"body", "actions"};
}
std::vector<std::string> NotificationService::onGetCapabilities() { return {"body", "actions"}; }
std::vector<std::map<std::string, sdbus::Variant>> NotificationService::onGetNotifications() {
std::vector<std::map<std::string, sdbus::Variant>> result;
for (const auto& n : m_manager.all()) {
std::map<std::string, sdbus::Variant> notif;
notif["id"] = sdbus::Variant(n.id);
notif["app_name"] = sdbus::Variant(n.app_name);
notif["summary"] = sdbus::Variant(n.summary);
notif["body"] = sdbus::Variant(n.body);
notif["timeout"] = sdbus::Variant(n.timeout);
notif["urgency"] = sdbus::Variant(static_cast<uint8_t>(n.urgency));
notif["actions"] = sdbus::Variant(n.actions);
notif["icon"] = sdbus::Variant(n.icon.value_or(""));
notif["category"] = sdbus::Variant(n.category.value_or(""));
notif["desktop_entry"] = sdbus::Variant(n.desktop_entry.value_or(""));
result.push_back(notif);
}
return result;
std::vector<std::map<std::string, sdbus::Variant>> result;
for (const auto& n : m_manager.all()) {
std::map<std::string, sdbus::Variant> notif;
notif["id"] = sdbus::Variant(n.id);
notif["app_name"] = sdbus::Variant(n.app_name);
notif["summary"] = sdbus::Variant(n.summary);
notif["body"] = sdbus::Variant(n.body);
notif["timeout"] = sdbus::Variant(n.timeout);
notif["urgency"] = sdbus::Variant(static_cast<uint8_t>(n.urgency));
notif["actions"] = sdbus::Variant(n.actions);
notif["icon"] = sdbus::Variant(n.icon.value_or(""));
notif["category"] = sdbus::Variant(n.category.value_or(""));
notif["desktop_entry"] = sdbus::Variant(n.desktop_entry.value_or(""));
result.push_back(notif);
}
return result;
}
void NotificationService::onCloseNotification(uint32_t id) {
if (!m_manager.close(id, CloseReason::ClosedByCall)) {
throw sdbus::Error(sdbus::Error::Name{"org.freedesktop.Notifications.Error.NotFound"},
"notification id was not found");
}
emitClose(id, CloseReason::ClosedByCall);
if (!m_manager.close(id, CloseReason::ClosedByCall)) {
throw sdbus::Error(sdbus::Error::Name{"org.freedesktop.Notifications.Error.NotFound"},
"notification id was not found");
}
emitClose(id, CloseReason::ClosedByCall);
}
void NotificationService::emitClose(uint32_t id, CloseReason reason) {
m_object->emitSignal("NotificationClosed")
.onInterface(k_interface)
.withArguments(id, static_cast<uint32_t>(reason));
m_object->emitSignal("NotificationClosed").onInterface(k_interface).withArguments(id, static_cast<uint32_t>(reason));
}
void NotificationService::onInvokeAction(uint32_t id, const std::string& action_key) {
const auto& notifs = m_manager.all();
for (const auto& n : notifs) {
if (n.id == id) {
const std::string sanitized_key = clamp_str(action_key);
if (sanitized_key.empty()) {
throw sdbus::Error(sdbus::Error::Name{"org.freedesktop.Notifications.Error.InvalidAction"},
"action_key must not be empty");
}
const auto& notifs = m_manager.all();
for (const auto& n : notifs) {
if (n.id == id) {
const std::string sanitized_key = clamp_str(action_key);
if (sanitized_key.empty()) {
throw sdbus::Error(sdbus::Error::Name{"org.freedesktop.Notifications.Error.InvalidAction"},
"action_key must not be empty");
}
if (!notification_has_action(n, sanitized_key)) {
throw sdbus::Error(sdbus::Error::Name{"org.freedesktop.Notifications.Error.InvalidAction"},
"action_key is not available for this notification");
}
if (!notification_has_action(n, sanitized_key)) {
throw sdbus::Error(sdbus::Error::Name{"org.freedesktop.Notifications.Error.InvalidAction"},
"action_key is not available for this notification");
}
logDebug("notification action #{} key='{}'", id, action_key);
emitActionInvoked(id, sanitized_key);
return;
}
logDebug("notification action #{} key='{}'", id, action_key);
emitActionInvoked(id, sanitized_key);
return;
}
}
throw sdbus::Error(sdbus::Error::Name{"org.freedesktop.Notifications.Error.NotFound"},
"notification id was not found");
throw sdbus::Error(sdbus::Error::Name{"org.freedesktop.Notifications.Error.NotFound"},
"notification id was not found");
}
void NotificationService::emitActionInvoked(uint32_t id, const std::string& action_key) {
m_object->emitSignal("ActionInvoked")
.onInterface(k_interface)
.withArguments(id, action_key);
m_object->emitSignal("ActionInvoked").onInterface(k_interface).withArguments(id, action_key);
}
std::tuple<std::string, std::string, std::string, std::string>
NotificationService::onGetServerInformation() {
return {"noctalia", "noctalia-dev", "0.1.0", "1.2"};
std::tuple<std::string, std::string, std::string, std::string> NotificationService::onGetServerInformation() {
return {"noctalia", "noctalia-dev", "0.1.0", "1.2"};
}
@@ -1,6 +1,6 @@
#pragma once
#include "notification/NotificationManager.hpp"
#include "notification/NotificationManager.h"
#include <sdbus-c++/sdbus-c++.h>
#include <chrono>
+35 -51
View File
@@ -1,7 +1,7 @@
#include "debug/DebugService.hpp"
#include "debug/DebugService.h"
#include "core/Log.hpp"
#include "dbus/SessionBus.hpp"
#include "core/Log.h"
#include "dbus/SessionBus.h"
namespace {
@@ -10,67 +10,51 @@ static const sdbus::ObjectPath k_debug_object_path{"/dev/noctalia/Debug"};
static constexpr auto k_debug_interface = "dev.noctalia.Debug";
Urgency clamp_urgency(uint8_t urgency) {
if (urgency > static_cast<uint8_t>(Urgency::Critical)) {
return Urgency::Normal;
}
return static_cast<Urgency>(urgency);
if (urgency > static_cast<uint8_t>(Urgency::Critical)) {
return Urgency::Normal;
}
return static_cast<Urgency>(urgency);
}
} // namespace
DebugService::DebugService(SessionBus& bus, InternalNotificationService& internal_notifications)
: m_internal_notifications(internal_notifications) {
bus.connection().requestName(k_debug_bus_name);
m_object = sdbus::createObject(bus.connection(), k_debug_object_path);
bus.connection().requestName(k_debug_bus_name);
m_object = sdbus::createObject(bus.connection(), k_debug_object_path);
m_object->addVTable(
sdbus::registerMethod("EmitInternalNotification")
.withInputParamNames("app_name", "summary", "body", "timeout", "urgency")
.withOutputParamNames("id")
.implementedAs([this](const std::string& app_name,
const std::string& summary,
const std::string& body,
int32_t timeout,
uint8_t urgency) {
return onEmitInternalNotification(app_name, summary, body, timeout, urgency);
}),
m_object
->addVTable(sdbus::registerMethod("EmitInternalNotification")
.withInputParamNames("app_name", "summary", "body", "timeout", "urgency")
.withOutputParamNames("id")
.implementedAs([this](const std::string& app_name, const std::string& summary,
const std::string& body, int32_t timeout, uint8_t urgency) {
return onEmitInternalNotification(app_name, summary, body, timeout, urgency);
}),
sdbus::registerMethod("SetVerboseLogs")
.withInputParamNames("enabled")
.withOutputParamNames("success")
.implementedAs([this](bool enabled) {
return onSetVerboseLogs(enabled);
}),
sdbus::registerMethod("SetVerboseLogs")
.withInputParamNames("enabled")
.withOutputParamNames("success")
.implementedAs([this](bool enabled) { return onSetVerboseLogs(enabled); }),
sdbus::registerMethod("GetVerboseLogs")
.withOutputParamNames("enabled")
.implementedAs([this]() {
return onGetVerboseLogs();
})
).forInterface(k_debug_interface);
sdbus::registerMethod("GetVerboseLogs").withOutputParamNames("enabled").implementedAs([this]() {
return onGetVerboseLogs();
}))
.forInterface(k_debug_interface);
}
uint32_t DebugService::onEmitInternalNotification(const std::string& app_name,
const std::string& summary,
const std::string& body,
int32_t timeout,
uint8_t urgency) {
const uint32_t id = m_internal_notifications.notify(app_name,
summary,
body,
timeout,
clamp_urgency(urgency));
logInfo("debug internal notification emitted id={} app=\"{}\"", id, app_name);
return id;
uint32_t DebugService::onEmitInternalNotification(const std::string& app_name, const std::string& summary,
const std::string& body, int32_t timeout, uint8_t urgency) {
const uint32_t id = m_internal_notifications.notify(app_name, summary, body, timeout, clamp_urgency(urgency));
logInfo("debug internal notification emitted id={} app=\"{}\"", id, app_name);
return id;
}
bool DebugService::onSetVerboseLogs(bool enabled) {
m_verbose_logs = enabled;
setLogLevel(enabled ? LogLevel::Debug : LogLevel::Info);
logInfo("debug verbose logs {}", enabled ? "enabled" : "disabled");
return true;
m_verbose_logs = enabled;
setLogLevel(enabled ? LogLevel::Debug : LogLevel::Info);
logInfo("debug verbose logs {}", enabled ? "enabled" : "disabled");
return true;
}
bool DebugService::onGetVerboseLogs() const {
return m_verbose_logs;
}
bool DebugService::onGetVerboseLogs() const { return m_verbose_logs; }
@@ -1,6 +1,6 @@
#pragma once
#include "notification/InternalNotificationService.hpp"
#include "notification/InternalNotificationService.h"
#include <memory>
#include <sdbus-c++/sdbus-c++.h>
+78 -79
View File
@@ -1,105 +1,104 @@
#include "font/FontService.hpp"
#include "font/FontService.h"
#include "core/Log.hpp"
#include "core/Log.h"
#include <stdexcept>
FontService::FontService() {
m_config = FcInitLoadConfigAndFonts();
if (m_config == nullptr) {
throw std::runtime_error("FcInitLoadConfigAndFonts failed");
}
m_config = FcInitLoadConfigAndFonts();
if (m_config == nullptr) {
throw std::runtime_error("FcInitLoadConfigAndFonts failed");
}
}
FontService::~FontService() {
if (m_config != nullptr) {
FcConfigDestroy(m_config);
m_config = nullptr;
}
if (m_config != nullptr) {
FcConfigDestroy(m_config);
m_config = nullptr;
}
}
std::string FontService::resolvePath(const std::string& family) const {
FcPattern* pattern = FcNameParse(reinterpret_cast<const FcChar8*>(family.c_str()));
if (pattern == nullptr) {
throw std::runtime_error("FcNameParse failed for: " + family);
}
FcPattern* pattern = FcNameParse(reinterpret_cast<const FcChar8*>(family.c_str()));
if (pattern == nullptr) {
throw std::runtime_error("FcNameParse failed for: " + family);
}
FcConfigSubstitute(m_config, pattern, FcMatchPattern);
FcDefaultSubstitute(pattern);
FcConfigSubstitute(m_config, pattern, FcMatchPattern);
FcDefaultSubstitute(pattern);
FcResult result = FcResultNoMatch;
FcPattern* match = FcFontMatch(m_config, pattern, &result);
FcPatternDestroy(pattern);
FcResult result = FcResultNoMatch;
FcPattern* match = FcFontMatch(m_config, pattern, &result);
FcPatternDestroy(pattern);
if (match == nullptr || result != FcResultMatch) {
throw std::runtime_error("no font found for: " + family);
}
if (match == nullptr || result != FcResultMatch) {
throw std::runtime_error("no font found for: " + family);
}
FcChar8* filePath = nullptr;
if (FcPatternGetString(match, FC_FILE, 0, &filePath) != FcResultMatch || filePath == nullptr) {
FcPatternDestroy(match);
throw std::runtime_error("fontconfig matched but returned no file path for: " + family);
}
std::string path(reinterpret_cast<const char*>(filePath));
FcChar8* filePath = nullptr;
if (FcPatternGetString(match, FC_FILE, 0, &filePath) != FcResultMatch || filePath == nullptr) {
FcPatternDestroy(match);
return path;
throw std::runtime_error("fontconfig matched but returned no file path for: " + family);
}
std::string path(reinterpret_cast<const char*>(filePath));
FcPatternDestroy(match);
return path;
}
std::vector<ResolvedFont> FontService::resolveFallbackChain(const std::string& family, int limit) const {
FcPattern* pattern = FcNameParse(reinterpret_cast<const FcChar8*>(family.c_str()));
if (pattern == nullptr) {
throw std::runtime_error("FcNameParse failed for: " + family);
FcPattern* pattern = FcNameParse(reinterpret_cast<const FcChar8*>(family.c_str()));
if (pattern == nullptr) {
throw std::runtime_error("FcNameParse failed for: " + family);
}
FcConfigSubstitute(m_config, pattern, FcMatchPattern);
FcDefaultSubstitute(pattern);
FcResult result = FcResultNoMatch;
FcFontSet* fontSet = FcFontSort(m_config, pattern, FcTrue, nullptr, &result);
FcPatternDestroy(pattern);
if (fontSet == nullptr || result != FcResultMatch) {
throw std::runtime_error("no fonts found for: " + family);
}
std::vector<ResolvedFont> chain;
for (int i = 0; i < fontSet->nfont && static_cast<int>(chain.size()) < limit; ++i) {
FcChar8* filePath = nullptr;
int faceIndex = 0;
if (FcPatternGetString(fontSet->fonts[i], FC_FILE, 0, &filePath) != FcResultMatch || filePath == nullptr) {
continue;
}
FcPatternGetInteger(fontSet->fonts[i], FC_INDEX, 0, &faceIndex);
std::string path(reinterpret_cast<const char*>(filePath));
// Skip duplicates (same file + face index)
bool duplicate = false;
for (const auto& existing : chain) {
if (existing.path == path && existing.faceIndex == faceIndex) {
duplicate = true;
break;
}
}
if (duplicate) {
continue;
}
FcConfigSubstitute(m_config, pattern, FcMatchPattern);
FcDefaultSubstitute(pattern);
chain.push_back(ResolvedFont{.path = std::move(path), .faceIndex = faceIndex});
}
FcResult result = FcResultNoMatch;
FcFontSet* fontSet = FcFontSort(m_config, pattern, FcTrue, nullptr, &result);
FcPatternDestroy(pattern);
FcFontSetDestroy(fontSet);
if (fontSet == nullptr || result != FcResultMatch) {
throw std::runtime_error("no fonts found for: " + family);
}
if (chain.empty()) {
throw std::runtime_error("no fonts resolved for: " + family);
}
std::vector<ResolvedFont> chain;
for (int i = 0; i < fontSet->nfont && static_cast<int>(chain.size()) < limit; ++i) {
FcChar8* filePath = nullptr;
int faceIndex = 0;
if (FcPatternGetString(fontSet->fonts[i], FC_FILE, 0, &filePath) != FcResultMatch ||
filePath == nullptr) {
continue;
}
FcPatternGetInteger(fontSet->fonts[i], FC_INDEX, 0, &faceIndex);
logDebug("font fallback chain for \"{}\" ({} fonts):", family, chain.size());
for (const auto& font : chain) {
logDebug(" {} [{}]", font.path, font.faceIndex);
}
std::string path(reinterpret_cast<const char*>(filePath));
// Skip duplicates (same file + face index)
bool duplicate = false;
for (const auto& existing : chain) {
if (existing.path == path && existing.faceIndex == faceIndex) {
duplicate = true;
break;
}
}
if (duplicate) {
continue;
}
chain.push_back(ResolvedFont{.path = std::move(path), .faceIndex = faceIndex});
}
FcFontSetDestroy(fontSet);
if (chain.empty()) {
throw std::runtime_error("no fonts resolved for: " + family);
}
logDebug("font fallback chain for \"{}\" ({} fonts):", family, chain.size());
for (const auto& font : chain) {
logDebug(" {} [{}]", font.path, font.faceIndex);
}
return chain;
return chain;
}
+9 -9
View File
@@ -1,14 +1,14 @@
#include "app/Application.hpp"
#include "core/Log.hpp"
#include "app/Application.h"
#include "core/Log.h"
#include <stdexcept>
int main() {
try {
Application app;
app.run();
} catch (const std::exception& e) {
logError("fatal: {}", e.what());
return 1;
}
try {
Application app;
app.run();
} catch (const std::exception& e) {
logError("fatal: {}", e.what());
return 1;
}
}
@@ -1,22 +1,11 @@
#include "notification/InternalNotificationService.hpp"
#include "notification/InternalNotificationService.h"
InternalNotificationService::InternalNotificationService(NotificationManager& manager)
: m_manager(manager) {}
InternalNotificationService::InternalNotificationService(NotificationManager& manager) : m_manager(manager) {}
uint32_t InternalNotificationService::notify(std::string app_name,
std::string summary,
std::string body,
int32_t timeout,
Urgency urgency,
std::optional<std::string> icon,
uint32_t InternalNotificationService::notify(std::string app_name, std::string summary, std::string body,
int32_t timeout, Urgency urgency, std::optional<std::string> icon,
std::optional<std::string> category,
std::optional<std::string> desktop_entry) {
return m_manager.addInternal(std::move(app_name),
std::move(summary),
std::move(body),
timeout,
urgency,
std::move(icon),
std::move(category),
std::move(desktop_entry));
return m_manager.addInternal(std::move(app_name), std::move(summary), std::move(body), timeout, urgency,
std::move(icon), std::move(category), std::move(desktop_entry));
}
@@ -1,6 +1,6 @@
#pragma once
#include "notification/NotificationManager.hpp"
#include "notification/NotificationManager.h"
#include <cstdint>
#include <optional>
+120 -140
View File
@@ -1,171 +1,151 @@
#include "NotificationManager.hpp"
#include "NotificationManager.h"
#include "core/Log.hpp"
#include "core/Log.h"
#include <string_view>
namespace {
constexpr std::string_view urgency_str(Urgency u) noexcept {
switch (u) {
case Urgency::Low: return "low";
case Urgency::Normal: return "normal";
case Urgency::Critical: return "critical";
}
return "unknown";
switch (u) {
case Urgency::Low:
return "low";
case Urgency::Normal:
return "normal";
case Urgency::Critical:
return "critical";
}
return "unknown";
}
constexpr std::string_view origin_str(NotificationOrigin o) noexcept {
switch (o) {
case NotificationOrigin::External: return "external";
case NotificationOrigin::Internal: return "internal";
}
return "unknown";
switch (o) {
case NotificationOrigin::External:
return "external";
case NotificationOrigin::Internal:
return "internal";
}
return "unknown";
}
std::optional<TimePoint> schedule_expiry(int32_t timeout_ms) noexcept {
if (timeout_ms > 0) {
return Clock::now() + std::chrono::milliseconds(timeout_ms);
if (timeout_ms > 0) {
return Clock::now() + std::chrono::milliseconds(timeout_ms);
}
return std::nullopt; // 0 = persistent, -1 = server default (treat as persistent for now)
}
} // namespace
void NotificationManager::setEventCallback(EventCallback callback) { m_event_callback = std::move(callback); }
uint32_t NotificationManager::addOrReplace(uint32_t replaces_id, std::string app_name, std::string summary,
std::string body, int32_t timeout, Urgency urgency,
NotificationOrigin origin, std::vector<std::string> actions,
std::optional<std::string> icon, std::optional<std::string> category,
std::optional<std::string> desktop_entry) {
auto log_notification = [](const Notification& n, std::string_view action) {
logDebug("notification {} #{} origin={} from=\"{}\" urgency={} summary=\"{}\" body=\"{}\" timeout={}ms", action,
n.id, origin_str(n.origin), n.app_name, urgency_str(n.urgency), n.summary, n.body, n.timeout);
};
if (replaces_id != 0) {
if (const auto it = m_id_to_index.find(replaces_id); it != m_id_to_index.end()) {
auto& n = m_notifications[it->second];
// Check if anything changed to avoid duplicate events
const bool changed = (n.app_name != app_name || n.summary != summary || n.body != body || n.timeout != timeout ||
n.urgency != urgency || n.origin != origin || n.actions != actions || n.icon != icon ||
n.category != category || n.desktop_entry != desktop_entry);
n.origin = origin;
n.app_name = std::move(app_name);
n.summary = std::move(summary);
n.body = std::move(body);
n.timeout = timeout;
n.urgency = urgency;
n.actions = std::move(actions);
n.icon = std::move(icon);
n.category = std::move(category);
n.desktop_entry = std::move(desktop_entry);
n.expiry_time = schedule_expiry(timeout);
log_notification(n, "updated");
if (changed && m_event_callback) {
m_event_callback(n, NotificationEvent::Updated);
}
return n.id;
}
return std::nullopt; // 0 = persistent, -1 = server default (treat as persistent for now)
}
const uint32_t id = m_next_id++;
m_notifications.push_back(Notification{
.id = id,
.origin = origin,
.app_name = std::move(app_name),
.summary = std::move(summary),
.body = std::move(body),
.timeout = timeout,
.urgency = urgency,
.actions = std::move(actions),
.icon = std::move(icon),
.category = std::move(category),
.desktop_entry = std::move(desktop_entry),
.expiry_time = schedule_expiry(timeout),
});
m_id_to_index.emplace(id, m_notifications.size() - 1);
const auto& n = m_notifications.back();
log_notification(n, "added");
if (m_event_callback) {
m_event_callback(n, NotificationEvent::Added);
}
return n.id;
}
}
void NotificationManager::setEventCallback(EventCallback callback) {
m_event_callback = std::move(callback);
}
uint32_t NotificationManager::addOrReplace(uint32_t replaces_id,
std::string app_name,
std::string summary,
std::string body,
int32_t timeout,
Urgency urgency,
NotificationOrigin origin,
std::vector<std::string> actions,
std::optional<std::string> icon,
std::optional<std::string> category,
std::optional<std::string> desktop_entry) {
auto log_notification = [](const Notification& n, std::string_view action) {
logDebug("notification {} #{} origin={} from=\"{}\" urgency={} summary=\"{}\" body=\"{}\" timeout={}ms",
action, n.id, origin_str(n.origin), n.app_name, urgency_str(n.urgency), n.summary, n.body, n.timeout);
};
if (replaces_id != 0) {
if (const auto it = m_id_to_index.find(replaces_id); it != m_id_to_index.end()) {
auto& n = m_notifications[it->second];
// Check if anything changed to avoid duplicate events
const bool changed = (n.app_name != app_name || n.summary != summary ||
n.body != body || n.timeout != timeout ||
n.urgency != urgency || n.origin != origin ||
n.actions != actions ||
n.icon != icon || n.category != category ||
n.desktop_entry != desktop_entry);
n.origin = origin;
n.app_name = std::move(app_name);
n.summary = std::move(summary);
n.body = std::move(body);
n.timeout = timeout;
n.urgency = urgency;
n.actions = std::move(actions);
n.icon = std::move(icon);
n.category = std::move(category);
n.desktop_entry = std::move(desktop_entry);
n.expiry_time = schedule_expiry(timeout);
log_notification(n, "updated");
if (changed && m_event_callback) {
m_event_callback(n, NotificationEvent::Updated);
}
return n.id;
}
}
const uint32_t id = m_next_id++;
m_notifications.push_back(Notification{
.id = id,
.origin = origin,
.app_name = std::move(app_name),
.summary = std::move(summary),
.body = std::move(body),
.timeout = timeout,
.urgency = urgency,
.actions = std::move(actions),
.icon = std::move(icon),
.category = std::move(category),
.desktop_entry = std::move(desktop_entry),
.expiry_time = schedule_expiry(timeout),
});
m_id_to_index.emplace(id, m_notifications.size() - 1);
const auto& n = m_notifications.back();
log_notification(n, "added");
if (m_event_callback) {
m_event_callback(n, NotificationEvent::Added);
}
return n.id;
}
uint32_t NotificationManager::addInternal(std::string app_name,
std::string summary,
std::string body,
int32_t timeout,
Urgency urgency,
std::optional<std::string> icon,
uint32_t NotificationManager::addInternal(std::string app_name, std::string summary, std::string body, int32_t timeout,
Urgency urgency, std::optional<std::string> icon,
std::optional<std::string> category,
std::optional<std::string> desktop_entry) {
return addOrReplace(0,
std::move(app_name),
std::move(summary),
std::move(body),
timeout,
urgency,
NotificationOrigin::Internal,
{},
std::move(icon),
std::move(category),
std::move(desktop_entry));
return addOrReplace(0, std::move(app_name), std::move(summary), std::move(body), timeout, urgency,
NotificationOrigin::Internal, {}, std::move(icon), std::move(category), std::move(desktop_entry));
}
bool NotificationManager::close(uint32_t id, CloseReason reason) {
const auto it = m_id_to_index.find(id);
if (it == m_id_to_index.end()) {
return false;
}
const auto it = m_id_to_index.find(id);
if (it == m_id_to_index.end()) {
return false;
}
const size_t index = it->second;
const char* reason_str = (reason == CloseReason::Expired) ? "expired" :
(reason == CloseReason::Dismissed) ? "dismissed" : "closed";
logDebug("notification {} #{}", reason_str, id);
const size_t index = it->second;
const char* reason_str = (reason == CloseReason::Expired) ? "expired"
: (reason == CloseReason::Dismissed) ? "dismissed"
: "closed";
logDebug("notification {} #{}", reason_str, id);
m_notifications.erase(m_notifications.begin() + static_cast<std::ptrdiff_t>(index));
m_id_to_index.erase(it);
m_notifications.erase(m_notifications.begin() + static_cast<std::ptrdiff_t>(index));
m_id_to_index.erase(it);
for (size_t i = index; i < m_notifications.size(); ++i) {
m_id_to_index[m_notifications[i].id] = i;
}
for (size_t i = index; i < m_notifications.size(); ++i) {
m_id_to_index[m_notifications[i].id] = i;
}
return true;
return true;
}
const std::deque<Notification>& NotificationManager::all() const noexcept {
return m_notifications;
}
const std::deque<Notification>& NotificationManager::all() const noexcept { return m_notifications; }
std::vector<uint32_t> NotificationManager::expiredIds() const {
const auto now = Clock::now();
std::vector<uint32_t> ids;
for (const auto& n : m_notifications) {
if (n.expiry_time && now >= *n.expiry_time) {
ids.push_back(n.id);
}
const auto now = Clock::now();
std::vector<uint32_t> ids;
for (const auto& n : m_notifications) {
if (n.expiry_time && now >= *n.expiry_time) {
ids.push_back(n.id);
}
return ids;
}
return ids;
}
@@ -1,6 +1,6 @@
#pragma once
#include "Notification.hpp"
#include "Notification.h"
#include <cstdint>
#include <deque>
+190 -200
View File
@@ -1,9 +1,9 @@
#include "render/GlRenderer.hpp"
#include "render/scene/IconNode.hpp"
#include "render/scene/Node.hpp"
#include "render/scene/RectNode.hpp"
#include "render/scene/TextNode.hpp"
#include "render/scene/ImageNode.hpp"
#include "render/GlRenderer.h"
#include "render/scene/IconNode.h"
#include "render/scene/ImageNode.h"
#include "render/scene/Node.h"
#include "render/scene/RectNode.h"
#include "render/scene/TextNode.h"
#include <cstdint>
#include <stdexcept>
@@ -14,17 +14,24 @@
namespace {
constexpr EGLint kConfigAttributes[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_SURFACE_TYPE,
EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE,
EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_BLUE_SIZE,
8,
EGL_ALPHA_SIZE,
8,
EGL_NONE,
};
constexpr EGLint kContextAttributes[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_CONTEXT_CLIENT_VERSION,
2,
EGL_NONE,
};
@@ -32,249 +39,232 @@ constexpr EGLint kContextAttributes[] = {
GlRenderer::GlRenderer() = default;
GlRenderer::~GlRenderer() {
cleanup();
}
GlRenderer::~GlRenderer() { cleanup(); }
const char* GlRenderer::name() const noexcept {
return "gl";
}
const char* GlRenderer::name() const noexcept { return "gl"; }
void GlRenderer::bind(wl_display* display, wl_surface* surface) {
if (display == nullptr || surface == nullptr) {
throw std::runtime_error("OpenGL renderer requires a valid Wayland display and surface");
}
if (display == nullptr || surface == nullptr) {
throw std::runtime_error("OpenGL renderer requires a valid Wayland display and surface");
}
m_display = display;
m_surface = surface;
m_display = display;
m_surface = surface;
m_eglDisplay = eglGetDisplay(reinterpret_cast<EGLNativeDisplayType>(display));
if (m_eglDisplay == EGL_NO_DISPLAY) {
throw std::runtime_error("eglGetDisplay failed");
}
m_eglDisplay = eglGetDisplay(reinterpret_cast<EGLNativeDisplayType>(display));
if (m_eglDisplay == EGL_NO_DISPLAY) {
throw std::runtime_error("eglGetDisplay failed");
}
EGLint major = 0;
EGLint minor = 0;
if (eglInitialize(m_eglDisplay, &major, &minor) != EGL_TRUE) {
throw std::runtime_error("eglInitialize failed");
}
EGLint major = 0;
EGLint minor = 0;
if (eglInitialize(m_eglDisplay, &major, &minor) != EGL_TRUE) {
throw std::runtime_error("eglInitialize failed");
}
if (eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE) {
throw std::runtime_error("eglBindAPI failed");
}
if (eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE) {
throw std::runtime_error("eglBindAPI failed");
}
EGLint configCount = 0;
if (eglChooseConfig(m_eglDisplay, kConfigAttributes, &m_eglConfig, 1, &configCount) != EGL_TRUE ||
configCount != 1) {
throw std::runtime_error("eglChooseConfig failed");
}
EGLint configCount = 0;
if (eglChooseConfig(m_eglDisplay, kConfigAttributes, &m_eglConfig, 1, &configCount) != EGL_TRUE || configCount != 1) {
throw std::runtime_error("eglChooseConfig failed");
}
m_eglContext = eglCreateContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, kContextAttributes);
if (m_eglContext == EGL_NO_CONTEXT) {
throw std::runtime_error("eglCreateContext failed");
}
m_eglContext = eglCreateContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, kContextAttributes);
if (m_eglContext == EGL_NO_CONTEXT) {
throw std::runtime_error("eglCreateContext failed");
}
}
void GlRenderer::makeCurrent() {
if (m_eglDisplay != EGL_NO_DISPLAY && m_eglSurface != EGL_NO_SURFACE && m_eglContext != EGL_NO_CONTEXT) {
eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext);
}
if (m_eglDisplay != EGL_NO_DISPLAY && m_eglSurface != EGL_NO_SURFACE && m_eglContext != EGL_NO_CONTEXT) {
eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext);
}
}
void GlRenderer::resize(std::uint32_t bufferWidth, std::uint32_t bufferHeight,
std::uint32_t logicalWidth, std::uint32_t logicalHeight) {
if (bufferWidth == 0 || bufferHeight == 0) {
return;
}
void GlRenderer::resize(std::uint32_t bufferWidth, std::uint32_t bufferHeight, std::uint32_t logicalWidth,
std::uint32_t logicalHeight) {
if (bufferWidth == 0 || bufferHeight == 0) {
return;
}
if (m_surface == nullptr || m_eglDisplay == EGL_NO_DISPLAY || m_eglContext == EGL_NO_CONTEXT) {
throw std::runtime_error("OpenGL renderer is not bound");
}
if (m_surface == nullptr || m_eglDisplay == EGL_NO_DISPLAY || m_eglContext == EGL_NO_CONTEXT) {
throw std::runtime_error("OpenGL renderer is not bound");
}
if (m_window == nullptr) {
m_window = wl_egl_window_create(m_surface, static_cast<int>(bufferWidth), static_cast<int>(bufferHeight));
if (m_window == nullptr) {
m_window = wl_egl_window_create(
m_surface,
static_cast<int>(bufferWidth),
static_cast<int>(bufferHeight));
if (m_window == nullptr) {
throw std::runtime_error("wl_egl_window_create failed");
}
} else {
wl_egl_window_resize(m_window, static_cast<int>(bufferWidth), static_cast<int>(bufferHeight), 0, 0);
throw std::runtime_error("wl_egl_window_create failed");
}
} else {
wl_egl_window_resize(m_window, static_cast<int>(bufferWidth), static_cast<int>(bufferHeight), 0, 0);
}
if (m_eglSurface == EGL_NO_SURFACE) {
m_eglSurface =
eglCreateWindowSurface(m_eglDisplay, m_eglConfig, reinterpret_cast<EGLNativeWindowType>(m_window), nullptr);
if (m_eglSurface == EGL_NO_SURFACE) {
m_eglSurface = eglCreateWindowSurface(
m_eglDisplay,
m_eglConfig,
reinterpret_cast<EGLNativeWindowType>(m_window),
nullptr);
if (m_eglSurface == EGL_NO_SURFACE) {
throw std::runtime_error("eglCreateWindowSurface failed");
}
throw std::runtime_error("eglCreateWindowSurface failed");
}
}
if (eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext) != EGL_TRUE) {
throw std::runtime_error("eglMakeCurrent failed during resize");
}
if (eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext) != EGL_TRUE) {
throw std::runtime_error("eglMakeCurrent failed during resize");
}
m_bufferWidth = bufferWidth;
m_bufferHeight = bufferHeight;
m_logicalWidth = logicalWidth;
m_logicalHeight = logicalHeight;
m_imageProgram.ensureInitialized();
m_linearGradientProgram.ensureInitialized();
m_roundedRectProgram.ensureInitialized();
const auto fonts = m_fontService.resolveFallbackChain("sans-serif");
m_textRenderer.initialize(fonts);
m_iconTextRenderer.initialize({{NOCTALIA_ASSETS_DIR "/fonts/tabler-icons.ttf", 0}});
m_bufferWidth = bufferWidth;
m_bufferHeight = bufferHeight;
m_logicalWidth = logicalWidth;
m_logicalHeight = logicalHeight;
m_imageProgram.ensureInitialized();
m_linearGradientProgram.ensureInitialized();
m_roundedRectProgram.ensureInitialized();
const auto fonts = m_fontService.resolveFallbackChain("sans-serif");
m_textRenderer.initialize(fonts);
m_iconTextRenderer.initialize({{NOCTALIA_ASSETS_DIR "/fonts/tabler-icons.ttf", 0}});
}
void GlRenderer::render() {
if (m_eglDisplay == EGL_NO_DISPLAY || m_eglSurface == EGL_NO_SURFACE || m_eglContext == EGL_NO_CONTEXT) {
throw std::runtime_error("OpenGL renderer is not ready");
}
if (m_eglDisplay == EGL_NO_DISPLAY || m_eglSurface == EGL_NO_SURFACE || m_eglContext == EGL_NO_CONTEXT) {
throw std::runtime_error("OpenGL renderer is not ready");
}
if (eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext) != EGL_TRUE) {
throw std::runtime_error("eglMakeCurrent failed");
}
if (eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext) != EGL_TRUE) {
throw std::runtime_error("eglMakeCurrent failed");
}
glViewport(0, 0, static_cast<GLint>(m_bufferWidth), static_cast<GLint>(m_bufferHeight));
glEnable(GL_BLEND);
glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glViewport(0, 0, static_cast<GLint>(m_bufferWidth), static_cast<GLint>(m_bufferHeight));
glEnable(GL_BLEND);
glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
if (m_sceneRoot != nullptr) {
renderNode(m_sceneRoot, 0.0f, 0.0f, 1.0f);
}
if (m_sceneRoot != nullptr) {
renderNode(m_sceneRoot, 0.0f, 0.0f, 1.0f);
}
if (eglSwapBuffers(m_eglDisplay, m_eglSurface) != EGL_TRUE) {
throw std::runtime_error("eglSwapBuffers failed");
}
if (eglSwapBuffers(m_eglDisplay, m_eglSurface) != EGL_TRUE) {
throw std::runtime_error("eglSwapBuffers failed");
}
}
void GlRenderer::setScene(Node* root) {
m_sceneRoot = root;
}
void GlRenderer::setScene(Node* root) { m_sceneRoot = root; }
TextureManager& GlRenderer::textureManager() {
return m_textureManager;
}
TextureManager& GlRenderer::textureManager() { return m_textureManager; }
TextMetrics GlRenderer::measureText(std::string_view text, float fontSize) {
auto m = m_textRenderer.measure(text, fontSize);
return TextMetrics{.width = m.width, .top = m.top, .bottom = m.bottom};
auto m = m_textRenderer.measure(text, fontSize);
return TextMetrics{.width = m.width, .top = m.top, .bottom = m.bottom};
}
TextMetrics GlRenderer::measureGlyph(char32_t codepoint, float fontSize) {
auto m = m_iconTextRenderer.measureGlyph(codepoint, fontSize);
return TextMetrics{.width = m.width, .top = m.top, .bottom = m.bottom};
auto m = m_iconTextRenderer.measureGlyph(codepoint, fontSize);
return TextMetrics{.width = m.width, .top = m.top, .bottom = m.bottom};
}
void GlRenderer::renderNode(const Node* node, float parentX, float parentY, float parentOpacity) {
if (!node->visible()) {
return;
}
if (!node->visible()) {
return;
}
const float absX = parentX + node->x();
const float absY = parentY + node->y();
const float effectiveOpacity = parentOpacity * node->opacity();
const float absX = parentX + node->x();
const float absY = parentY + node->y();
const float effectiveOpacity = parentOpacity * node->opacity();
const auto sw = static_cast<float>(m_logicalWidth);
const auto sh = static_cast<float>(m_logicalHeight);
const auto sw = static_cast<float>(m_logicalWidth);
const auto sh = static_cast<float>(m_logicalHeight);
switch (node->type()) {
case NodeType::Rect: {
const auto* rect = static_cast<const RectNode*>(node);
auto style = rect->style();
style.fill.a *= effectiveOpacity;
style.fillEnd.a *= effectiveOpacity;
style.border.a *= effectiveOpacity;
m_roundedRectProgram.draw(sw, sh, absX, absY, node->width(), node->height(), style);
break;
switch (node->type()) {
case NodeType::Rect: {
const auto* rect = static_cast<const RectNode*>(node);
auto style = rect->style();
style.fill.a *= effectiveOpacity;
style.fillEnd.a *= effectiveOpacity;
style.border.a *= effectiveOpacity;
m_roundedRectProgram.draw(sw, sh, absX, absY, node->width(), node->height(), style);
break;
}
case NodeType::Text: {
const auto* text = static_cast<const TextNode*>(node);
if (!text->text().empty()) {
auto color = text->color();
color.a *= effectiveOpacity;
if (text->maxWidth() > 0.0f) {
auto truncated = m_textRenderer.truncate(text->text(), text->fontSize(), text->maxWidth());
m_textRenderer.draw(sw, sh, absX, absY, truncated.text, text->fontSize(), color);
} else {
m_textRenderer.draw(sw, sh, absX, absY, text->text(), text->fontSize(), color);
}
}
case NodeType::Text: {
const auto* text = static_cast<const TextNode*>(node);
if (!text->text().empty()) {
auto color = text->color();
color.a *= effectiveOpacity;
if (text->maxWidth() > 0.0f) {
auto truncated = m_textRenderer.truncate(text->text(), text->fontSize(), text->maxWidth());
m_textRenderer.draw(sw, sh, absX, absY, truncated.text, text->fontSize(), color);
} else {
m_textRenderer.draw(sw, sh, absX, absY, text->text(), text->fontSize(), color);
}
}
break;
break;
}
case NodeType::Image: {
const auto* img = static_cast<const ImageNode*>(node);
if (img->textureId() != 0) {
auto tint = img->tint();
tint.a *= effectiveOpacity;
m_imageProgram.draw(img->textureId(), sw, sh, absX, absY, node->width(), node->height(), tint, effectiveOpacity);
}
case NodeType::Image: {
const auto* img = static_cast<const ImageNode*>(node);
if (img->textureId() != 0) {
auto tint = img->tint();
tint.a *= effectiveOpacity;
m_imageProgram.draw(img->textureId(), sw, sh, absX, absY,
node->width(), node->height(), tint, effectiveOpacity);
}
break;
}
case NodeType::Icon: {
const auto* icon = static_cast<const IconNode*>(node);
if (icon->codepoint() != 0) {
auto color = icon->color();
color.a *= effectiveOpacity;
m_iconTextRenderer.drawGlyph(sw, sh, absX, absY,
icon->codepoint(), icon->fontSize(), color);
}
break;
}
case NodeType::Base:
break;
break;
}
case NodeType::Icon: {
const auto* icon = static_cast<const IconNode*>(node);
if (icon->codepoint() != 0) {
auto color = icon->color();
color.a *= effectiveOpacity;
m_iconTextRenderer.drawGlyph(sw, sh, absX, absY, icon->codepoint(), icon->fontSize(), color);
}
break;
}
case NodeType::Base:
break;
}
for (const auto& child : node->children()) {
renderNode(child.get(), absX, absY, effectiveOpacity);
}
for (const auto& child : node->children()) {
renderNode(child.get(), absX, absY, effectiveOpacity);
}
}
void GlRenderer::cleanup() {
m_textureManager.cleanup();
m_imageProgram.destroy();
m_linearGradientProgram.destroy();
m_roundedRectProgram.destroy();
m_textRenderer.cleanup();
m_iconTextRenderer.cleanup();
m_bufferWidth = 0;
m_bufferHeight = 0;
m_logicalWidth = 0;
m_logicalHeight = 0;
m_textureManager.cleanup();
m_imageProgram.destroy();
m_linearGradientProgram.destroy();
m_roundedRectProgram.destroy();
m_textRenderer.cleanup();
m_iconTextRenderer.cleanup();
m_bufferWidth = 0;
m_bufferHeight = 0;
m_logicalWidth = 0;
m_logicalHeight = 0;
if (m_eglDisplay != EGL_NO_DISPLAY) {
eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
if (m_eglDisplay != EGL_NO_DISPLAY) {
eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
if (m_eglSurface != EGL_NO_SURFACE) {
eglDestroySurface(m_eglDisplay, m_eglSurface);
m_eglSurface = EGL_NO_SURFACE;
}
if (m_eglSurface != EGL_NO_SURFACE) {
eglDestroySurface(m_eglDisplay, m_eglSurface);
m_eglSurface = EGL_NO_SURFACE;
}
if (m_window != nullptr) {
wl_egl_window_destroy(m_window);
m_window = nullptr;
}
if (m_window != nullptr) {
wl_egl_window_destroy(m_window);
m_window = nullptr;
}
if (m_eglContext != EGL_NO_CONTEXT) {
eglDestroyContext(m_eglDisplay, m_eglContext);
m_eglContext = EGL_NO_CONTEXT;
}
if (m_eglContext != EGL_NO_CONTEXT) {
eglDestroyContext(m_eglDisplay, m_eglContext);
m_eglContext = EGL_NO_CONTEXT;
}
if (m_eglDisplay != EGL_NO_DISPLAY) {
eglTerminate(m_eglDisplay);
m_eglDisplay = EGL_NO_DISPLAY;
}
if (m_eglDisplay != EGL_NO_DISPLAY) {
eglTerminate(m_eglDisplay);
m_eglDisplay = EGL_NO_DISPLAY;
}
m_eglConfig = nullptr;
m_display = nullptr;
m_surface = nullptr;
m_eglConfig = nullptr;
m_display = nullptr;
m_surface = nullptr;
}
@@ -1,12 +1,12 @@
#pragma once
#include "font/FontService.hpp"
#include "render/programs/ImageProgram.hpp"
#include "render/programs/LinearGradientProgram.hpp"
#include "render/programs/RoundedRectProgram.hpp"
#include "render/core/Renderer.hpp"
#include "render/text/MsdfTextRenderer.hpp"
#include "render/core/TextureManager.hpp"
#include "font/FontService.h"
#include "render/programs/ImageProgram.h"
#include "render/programs/LinearGradientProgram.h"
#include "render/programs/RoundedRectProgram.h"
#include "render/core/Renderer.h"
#include "render/text/MsdfTextRenderer.h"
#include "render/core/TextureManager.h"
#include <EGL/egl.h>
+118 -134
View File
@@ -1,4 +1,4 @@
#include "render/WallpaperRenderer.hpp"
#include "render/WallpaperRenderer.h"
#include <stdexcept>
@@ -8,17 +8,24 @@
namespace {
constexpr EGLint kConfigAttributes[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_SURFACE_TYPE,
EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE,
EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_BLUE_SIZE,
8,
EGL_ALPHA_SIZE,
8,
EGL_NONE,
};
constexpr EGLint kContextAttributes[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_CONTEXT_CLIENT_VERSION,
2,
EGL_NONE,
};
@@ -26,173 +33,150 @@ constexpr EGLint kContextAttributes[] = {
WallpaperRenderer::WallpaperRenderer() = default;
WallpaperRenderer::~WallpaperRenderer() {
cleanup();
}
WallpaperRenderer::~WallpaperRenderer() { cleanup(); }
const char* WallpaperRenderer::name() const noexcept {
return "wallpaper";
}
const char* WallpaperRenderer::name() const noexcept { return "wallpaper"; }
void WallpaperRenderer::bind(wl_display* display, wl_surface* surface) {
if (display == nullptr || surface == nullptr) {
throw std::runtime_error("wallpaper renderer requires a valid Wayland display and surface");
}
if (display == nullptr || surface == nullptr) {
throw std::runtime_error("wallpaper renderer requires a valid Wayland display and surface");
}
m_display = display;
m_surface = surface;
m_display = display;
m_surface = surface;
m_eglDisplay = eglGetDisplay(reinterpret_cast<EGLNativeDisplayType>(display));
if (m_eglDisplay == EGL_NO_DISPLAY) {
throw std::runtime_error("eglGetDisplay failed");
}
m_eglDisplay = eglGetDisplay(reinterpret_cast<EGLNativeDisplayType>(display));
if (m_eglDisplay == EGL_NO_DISPLAY) {
throw std::runtime_error("eglGetDisplay failed");
}
EGLint major = 0;
EGLint minor = 0;
if (eglInitialize(m_eglDisplay, &major, &minor) != EGL_TRUE) {
throw std::runtime_error("eglInitialize failed");
}
EGLint major = 0;
EGLint minor = 0;
if (eglInitialize(m_eglDisplay, &major, &minor) != EGL_TRUE) {
throw std::runtime_error("eglInitialize failed");
}
if (eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE) {
throw std::runtime_error("eglBindAPI failed");
}
if (eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE) {
throw std::runtime_error("eglBindAPI failed");
}
EGLint configCount = 0;
if (eglChooseConfig(m_eglDisplay, kConfigAttributes, &m_eglConfig, 1, &configCount) != EGL_TRUE ||
configCount != 1) {
throw std::runtime_error("eglChooseConfig failed");
}
EGLint configCount = 0;
if (eglChooseConfig(m_eglDisplay, kConfigAttributes, &m_eglConfig, 1, &configCount) != EGL_TRUE || configCount != 1) {
throw std::runtime_error("eglChooseConfig failed");
}
m_eglContext = eglCreateContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, kContextAttributes);
if (m_eglContext == EGL_NO_CONTEXT) {
throw std::runtime_error("eglCreateContext failed");
}
m_eglContext = eglCreateContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, kContextAttributes);
if (m_eglContext == EGL_NO_CONTEXT) {
throw std::runtime_error("eglCreateContext failed");
}
}
void WallpaperRenderer::makeCurrent() {
if (m_eglDisplay != EGL_NO_DISPLAY && m_eglSurface != EGL_NO_SURFACE && m_eglContext != EGL_NO_CONTEXT) {
eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext);
}
if (m_eglDisplay != EGL_NO_DISPLAY && m_eglSurface != EGL_NO_SURFACE && m_eglContext != EGL_NO_CONTEXT) {
eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext);
}
}
void WallpaperRenderer::resize(std::uint32_t bufferWidth, std::uint32_t bufferHeight,
std::uint32_t logicalWidth, std::uint32_t logicalHeight) {
if (bufferWidth == 0 || bufferHeight == 0) {
return;
}
void WallpaperRenderer::resize(std::uint32_t bufferWidth, std::uint32_t bufferHeight, std::uint32_t logicalWidth,
std::uint32_t logicalHeight) {
if (bufferWidth == 0 || bufferHeight == 0) {
return;
}
if (m_surface == nullptr || m_eglDisplay == EGL_NO_DISPLAY || m_eglContext == EGL_NO_CONTEXT) {
throw std::runtime_error("wallpaper renderer is not bound");
}
if (m_surface == nullptr || m_eglDisplay == EGL_NO_DISPLAY || m_eglContext == EGL_NO_CONTEXT) {
throw std::runtime_error("wallpaper renderer is not bound");
}
if (m_window == nullptr) {
m_window = wl_egl_window_create(m_surface, static_cast<int>(bufferWidth), static_cast<int>(bufferHeight));
if (m_window == nullptr) {
m_window = wl_egl_window_create(
m_surface,
static_cast<int>(bufferWidth),
static_cast<int>(bufferHeight));
if (m_window == nullptr) {
throw std::runtime_error("wl_egl_window_create failed");
}
m_eglSurface = eglCreateWindowSurface(
m_eglDisplay, m_eglConfig,
reinterpret_cast<EGLNativeWindowType>(m_window),
nullptr);
if (m_eglSurface == EGL_NO_SURFACE) {
throw std::runtime_error("eglCreateWindowSurface failed");
}
} else {
wl_egl_window_resize(m_window,
static_cast<int>(bufferWidth),
static_cast<int>(bufferHeight), 0, 0);
throw std::runtime_error("wl_egl_window_create failed");
}
eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext);
m_eglSurface =
eglCreateWindowSurface(m_eglDisplay, m_eglConfig, reinterpret_cast<EGLNativeWindowType>(m_window), nullptr);
if (m_eglSurface == EGL_NO_SURFACE) {
throw std::runtime_error("eglCreateWindowSurface failed");
}
} else {
wl_egl_window_resize(m_window, static_cast<int>(bufferWidth), static_cast<int>(bufferHeight), 0, 0);
}
m_bufferWidth = bufferWidth;
m_bufferHeight = bufferHeight;
m_logicalWidth = logicalWidth;
m_logicalHeight = logicalHeight;
eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext);
glViewport(0, 0, static_cast<GLsizei>(bufferWidth), static_cast<GLsizei>(bufferHeight));
m_bufferWidth = bufferWidth;
m_bufferHeight = bufferHeight;
m_logicalWidth = logicalWidth;
m_logicalHeight = logicalHeight;
m_program.ensureInitialized();
glViewport(0, 0, static_cast<GLsizei>(bufferWidth), static_cast<GLsizei>(bufferHeight));
m_program.ensureInitialized();
}
void WallpaperRenderer::render() {
if (m_eglSurface == EGL_NO_SURFACE || m_tex1 == 0) {
return;
}
if (m_eglSurface == EGL_NO_SURFACE || m_tex1 == 0) {
return;
}
eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext);
glViewport(0, 0, static_cast<GLsizei>(m_bufferWidth), static_cast<GLsizei>(m_bufferHeight));
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext);
glViewport(0, 0, static_cast<GLsizei>(m_bufferWidth), static_cast<GLsizei>(m_bufferHeight));
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
auto sw = static_cast<float>(m_logicalWidth);
auto sh = static_cast<float>(m_logicalHeight);
auto sw = static_cast<float>(m_logicalWidth);
auto sh = static_cast<float>(m_logicalHeight);
// If no second texture, just draw the first using fade at progress 0
GLuint tex2 = (m_tex2 != 0) ? m_tex2 : m_tex1;
float progress = (m_tex2 != 0) ? m_progress : 0.0f;
// If no second texture, just draw the first using fade at progress 0
GLuint tex2 = (m_tex2 != 0) ? m_tex2 : m_tex1;
float progress = (m_tex2 != 0) ? m_progress : 0.0f;
m_program.draw(m_transition, m_tex1, tex2,
sw, sh,
m_imgW1, m_imgH1,
m_imgW2, m_imgH2,
progress,
static_cast<float>(m_fillMode),
m_params);
m_program.draw(m_transition, m_tex1, tex2, sw, sh, m_imgW1, m_imgH1, m_imgW2, m_imgH2, progress,
static_cast<float>(m_fillMode), m_params);
eglSwapBuffers(m_eglDisplay, m_eglSurface);
eglSwapBuffers(m_eglDisplay, m_eglSurface);
}
void WallpaperRenderer::setScene(Node* /*root*/) {
// Not used by wallpaper renderer
// Not used by wallpaper renderer
}
TextureManager& WallpaperRenderer::textureManager() {
return m_textureManager;
}
TextureManager& WallpaperRenderer::textureManager() { return m_textureManager; }
void WallpaperRenderer::setTransitionState(GLuint tex1, GLuint tex2,
float imgW1, float imgH1,
float imgW2, float imgH2,
float progress,
WallpaperTransition transition,
WallpaperFillMode fillMode,
const TransitionParams& params) {
m_tex1 = tex1;
m_tex2 = tex2;
m_imgW1 = imgW1;
m_imgH1 = imgH1;
m_imgW2 = imgW2;
m_imgH2 = imgH2;
m_progress = progress;
m_transition = transition;
m_fillMode = fillMode;
m_params = params;
void WallpaperRenderer::setTransitionState(GLuint tex1, GLuint tex2, float imgW1, float imgH1, float imgW2, float imgH2,
float progress, WallpaperTransition transition, WallpaperFillMode fillMode,
const TransitionParams& params) {
m_tex1 = tex1;
m_tex2 = tex2;
m_imgW1 = imgW1;
m_imgH1 = imgH1;
m_imgW2 = imgW2;
m_imgH2 = imgH2;
m_progress = progress;
m_transition = transition;
m_fillMode = fillMode;
m_params = params;
}
void WallpaperRenderer::cleanup() {
m_program.destroy();
m_textureManager.cleanup();
m_program.destroy();
m_textureManager.cleanup();
if (m_eglDisplay != EGL_NO_DISPLAY) {
eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (m_eglSurface != EGL_NO_SURFACE) {
eglDestroySurface(m_eglDisplay, m_eglSurface);
}
if (m_eglContext != EGL_NO_CONTEXT) {
eglDestroyContext(m_eglDisplay, m_eglContext);
}
eglTerminate(m_eglDisplay);
if (m_eglDisplay != EGL_NO_DISPLAY) {
eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (m_eglSurface != EGL_NO_SURFACE) {
eglDestroySurface(m_eglDisplay, m_eglSurface);
}
if (m_eglContext != EGL_NO_CONTEXT) {
eglDestroyContext(m_eglDisplay, m_eglContext);
}
eglTerminate(m_eglDisplay);
}
if (m_window != nullptr) {
wl_egl_window_destroy(m_window);
}
if (m_window != nullptr) {
wl_egl_window_destroy(m_window);
}
}
@@ -1,9 +1,9 @@
#pragma once
#include "config/ConfigService.hpp"
#include "render/core/Renderer.hpp"
#include "render/core/TextureManager.hpp"
#include "render/programs/WallpaperProgram.hpp"
#include "config/ConfigService.h"
#include "render/core/Renderer.h"
#include "render/core/TextureManager.h"
#include "render/programs/WallpaperProgram.h"
#include <EGL/egl.h>
+39 -39
View File
@@ -1,47 +1,47 @@
#include "render/animation/Animation.hpp"
#include "render/animation/Animation.h"
#include <algorithm>
#include <cmath>
float applyEasing(Easing easing, float t) {
t = std::clamp(t, 0.0f, 1.0f);
switch (easing) {
case Easing::Linear:
return t;
case Easing::EaseInQuad:
return t * t;
case Easing::EaseOutQuad:
return t * (2.0f - t);
case Easing::EaseInOutQuad:
if (t < 0.5f) {
return 2.0f * t * t;
}
return -1.0f + (4.0f - 2.0f * t) * t;
case Easing::EaseOutCubic: {
const float f = t - 1.0f;
return f * f * f + 1.0f;
}
case Easing::EaseInOutCubic:
if (t < 0.5f) {
return 4.0f * t * t * t;
} else {
const float f = 2.0f * t - 2.0f;
return 0.5f * f * f * f + 1.0f;
}
case Easing::EaseOutBack: {
constexpr float c1 = 1.70158f;
constexpr float c3 = c1 + 1.0f;
const float f = t - 1.0f;
return 1.0f + c3 * f * f * f + c1 * f * f;
}
}
t = std::clamp(t, 0.0f, 1.0f);
switch (easing) {
case Easing::Linear:
return t;
case Easing::EaseInQuad:
return t * t;
case Easing::EaseOutQuad:
return t * (2.0f - t);
case Easing::EaseInOutQuad:
if (t < 0.5f) {
return 2.0f * t * t;
}
return -1.0f + (4.0f - 2.0f * t) * t;
case Easing::EaseOutCubic: {
const float f = t - 1.0f;
return f * f * f + 1.0f;
}
case Easing::EaseInOutCubic:
if (t < 0.5f) {
return 4.0f * t * t * t;
} else {
const float f = 2.0f * t - 2.0f;
return 0.5f * f * f * f + 1.0f;
}
case Easing::EaseOutBack: {
constexpr float c1 = 1.70158f;
constexpr float c3 = c1 + 1.0f;
const float f = t - 1.0f;
return 1.0f + c3 * f * f * f + c1 * f * f;
}
}
return t;
}
+44 -47
View File
@@ -1,60 +1,57 @@
#include "render/animation/AnimationManager.hpp"
#include "render/animation/AnimationManager.h"
#include <algorithm>
AnimationManager::Id AnimationManager::animate(float from, float to, float durationMs,
Easing easing,
std::function<void(float)> setter,
std::function<void()> onComplete) {
Id id = m_nextId++;
m_animations.push_back(Entry{
.id = id,
.animation = Animation{
.startValue = from,
.endValue = to,
.durationMs = durationMs,
.easing = easing,
.setter = std::move(setter),
.onComplete = std::move(onComplete),
},
});
return id;
AnimationManager::Id AnimationManager::animate(float from, float to, float durationMs, Easing easing,
std::function<void(float)> setter, std::function<void()> onComplete) {
Id id = m_nextId++;
m_animations.push_back(Entry{
.id = id,
.animation =
Animation{
.startValue = from,
.endValue = to,
.durationMs = durationMs,
.easing = easing,
.setter = std::move(setter),
.onComplete = std::move(onComplete),
},
});
return id;
}
void AnimationManager::cancel(Id id) {
std::erase_if(m_animations, [id](const Entry& e) { return e.id == id; });
std::erase_if(m_animations, [id](const Entry& e) { return e.id == id; });
}
void AnimationManager::tick(float deltaMs) {
for (auto& entry : m_animations) {
auto& anim = entry.animation;
if (anim.finished) {
continue;
}
anim.elapsedMs += deltaMs;
float t = anim.durationMs > 0.0f ? anim.elapsedMs / anim.durationMs : 1.0f;
if (t >= 1.0f) {
t = 1.0f;
anim.finished = true;
}
float easedT = applyEasing(anim.easing, t);
float value = anim.startValue + (anim.endValue - anim.startValue) * easedT;
if (anim.setter) {
anim.setter(value);
}
if (anim.finished && anim.onComplete) {
anim.onComplete();
}
for (auto& entry : m_animations) {
auto& anim = entry.animation;
if (anim.finished) {
continue;
}
std::erase_if(m_animations, [](const Entry& e) { return e.animation.finished; });
anim.elapsedMs += deltaMs;
float t = anim.durationMs > 0.0f ? anim.elapsedMs / anim.durationMs : 1.0f;
if (t >= 1.0f) {
t = 1.0f;
anim.finished = true;
}
float easedT = applyEasing(anim.easing, t);
float value = anim.startValue + (anim.endValue - anim.startValue) * easedT;
if (anim.setter) {
anim.setter(value);
}
if (anim.finished && anim.onComplete) {
anim.onComplete();
}
}
std::erase_if(m_animations, [](const Entry& e) { return e.animation.finished; });
}
bool AnimationManager::hasActive() const {
return !m_animations.empty();
}
bool AnimationManager::hasActive() const { return !m_animations.empty(); }
@@ -1,6 +1,6 @@
#pragma once
#include "render/animation/Animation.hpp"
#include "render/animation/Animation.h"
#include <cstdint>
#include <functional>
+57 -66
View File
@@ -1,4 +1,4 @@
#include "render/core/ShaderProgram.hpp"
#include "render/core/ShaderProgram.h"
#include <stdexcept>
#include <string>
@@ -7,93 +7,84 @@
namespace {
GLuint compileShader(GLenum type, const char* source) {
const GLuint shader = glCreateShader(type);
if (shader == 0) {
throw std::runtime_error("glCreateShader failed");
}
const GLuint shader = glCreateShader(type);
if (shader == 0) {
throw std::runtime_error("glCreateShader failed");
}
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
GLint compiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (compiled == GL_FALSE) {
GLint logLength = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
std::string log(static_cast<std::size_t>(std::max(logLength, 1)), '\0');
glGetShaderInfoLog(shader, logLength, nullptr, log.data());
glDeleteShader(shader);
throw std::runtime_error("shader compilation failed: " + log);
}
GLint compiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (compiled == GL_FALSE) {
GLint logLength = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
std::string log(static_cast<std::size_t>(std::max(logLength, 1)), '\0');
glGetShaderInfoLog(shader, logLength, nullptr, log.data());
glDeleteShader(shader);
throw std::runtime_error("shader compilation failed: " + log);
}
return shader;
return shader;
}
} // namespace
ShaderProgram::~ShaderProgram() {
destroy();
}
ShaderProgram::~ShaderProgram() { destroy(); }
ShaderProgram::ShaderProgram(ShaderProgram&& other) noexcept
: m_program(other.m_program) {
other.m_program = 0;
}
ShaderProgram::ShaderProgram(ShaderProgram&& other) noexcept : m_program(other.m_program) { other.m_program = 0; }
ShaderProgram& ShaderProgram::operator=(ShaderProgram&& other) noexcept {
if (this == &other) {
return *this;
}
destroy();
m_program = other.m_program;
other.m_program = 0;
if (this == &other) {
return *this;
}
destroy();
m_program = other.m_program;
other.m_program = 0;
return *this;
}
void ShaderProgram::create(const char* vertexSource, const char* fragmentSource) {
destroy();
destroy();
const GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vertexSource);
const GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentSource);
m_program = glCreateProgram();
if (m_program == 0) {
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
throw std::runtime_error("glCreateProgram failed");
}
glAttachShader(m_program, vertexShader);
glAttachShader(m_program, fragmentShader);
glLinkProgram(m_program);
const GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vertexSource);
const GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentSource);
m_program = glCreateProgram();
if (m_program == 0) {
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
throw std::runtime_error("glCreateProgram failed");
}
GLint linked = 0;
glGetProgramiv(m_program, GL_LINK_STATUS, &linked);
if (linked == GL_FALSE) {
GLint logLength = 0;
glGetProgramiv(m_program, GL_INFO_LOG_LENGTH, &logLength);
std::string log(static_cast<std::size_t>(std::max(logLength, 1)), '\0');
glGetProgramInfoLog(m_program, logLength, nullptr, log.data());
destroy();
throw std::runtime_error("shader link failed: " + log);
}
glAttachShader(m_program, vertexShader);
glAttachShader(m_program, fragmentShader);
glLinkProgram(m_program);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
GLint linked = 0;
glGetProgramiv(m_program, GL_LINK_STATUS, &linked);
if (linked == GL_FALSE) {
GLint logLength = 0;
glGetProgramiv(m_program, GL_INFO_LOG_LENGTH, &logLength);
std::string log(static_cast<std::size_t>(std::max(logLength, 1)), '\0');
glGetProgramInfoLog(m_program, logLength, nullptr, log.data());
destroy();
throw std::runtime_error("shader link failed: " + log);
}
}
void ShaderProgram::destroy() {
if (m_program != 0) {
glDeleteProgram(m_program);
m_program = 0;
}
if (m_program != 0) {
glDeleteProgram(m_program);
m_program = 0;
}
}
bool ShaderProgram::isValid() const noexcept {
return m_program != 0;
}
bool ShaderProgram::isValid() const noexcept { return m_program != 0; }
GLuint ShaderProgram::id() const noexcept {
return m_program;
}
GLuint ShaderProgram::id() const noexcept { return m_program; }
+106 -111
View File
@@ -1,6 +1,6 @@
#include "render/core/TextureManager.hpp"
#include "render/core/TextureManager.h"
#include "core/Log.hpp"
#include "core/Log.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
@@ -8,9 +8,9 @@
#pragma GCC diagnostic ignored "-Wfloat-conversion"
#pragma GCC diagnostic ignored "-Wold-style-cast"
#pragma GCC diagnostic ignored "-Wshadow"
#include <stb_image.h>
#include <nanosvg.h>
#include <nanosvgrast.h>
#include <stb_image.h>
#pragma GCC diagnostic pop
#include <algorithm>
@@ -23,143 +23,138 @@
namespace {
bool endsWith(const std::string& str, const std::string& suffix) {
if (suffix.size() > str.size()) {
return false;
}
return std::equal(suffix.rbegin(), suffix.rend(), str.rbegin());
if (suffix.size() > str.size()) {
return false;
}
return std::equal(suffix.rbegin(), suffix.rend(), str.rbegin());
}
std::vector<std::uint8_t> readFile(const std::string& path) {
std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file) {
return {};
}
const auto size = file.tellg();
if (size <= 0) {
return {};
}
std::vector<std::uint8_t> data(static_cast<std::size_t>(size));
file.seekg(0);
file.read(reinterpret_cast<char*>(data.data()), size);
return data;
std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file) {
return {};
}
const auto size = file.tellg();
if (size <= 0) {
return {};
}
std::vector<std::uint8_t> data(static_cast<std::size_t>(size));
file.seekg(0);
file.read(reinterpret_cast<char*>(data.data()), size);
return data;
}
} // namespace
TextureManager::~TextureManager() {
cleanup();
}
TextureManager::~TextureManager() { cleanup(); }
TextureHandle TextureManager::loadFromFile(const std::string& path, int targetSize) {
if (endsWith(path, ".svg") || endsWith(path, ".SVG")) {
// SVG rasterization via nanosvg
auto fileData = readFile(path);
if (fileData.empty()) {
logWarn("failed to read SVG: {}", path);
return {};
}
// nsvgParse needs null-terminated mutable string
fileData.push_back(0);
auto* image = nsvgParse(reinterpret_cast<char*>(fileData.data()), "px", 96.0f);
if (image == nullptr) {
logWarn("failed to parse SVG: {}", path);
return {};
}
int w = targetSize > 0 ? targetSize : static_cast<int>(image->width);
int h = targetSize > 0 ? targetSize : static_cast<int>(image->height);
if (w <= 0 || h <= 0) {
nsvgDelete(image);
return {};
}
float scaleX = static_cast<float>(w) / image->width;
float scaleY = static_cast<float>(h) / image->height;
float scale = std::min(scaleX, scaleY);
auto* rast = nsvgCreateRasterizer();
if (rast == nullptr) {
nsvgDelete(image);
return {};
}
std::vector<std::uint8_t> pixels(static_cast<std::size_t>(w * h * 4));
nsvgRasterize(rast, image, 0, 0, scale, pixels.data(), w, h, w * 4);
nsvgDeleteRasterizer(rast);
nsvgDelete(image);
return uploadRgba(pixels.data(), w, h);
}
// PNG/JPEG via stb_image
if (endsWith(path, ".svg") || endsWith(path, ".SVG")) {
// SVG rasterization via nanosvg
auto fileData = readFile(path);
if (fileData.empty()) {
logWarn("failed to read image: {}", path);
return {};
logWarn("failed to read SVG: {}", path);
return {};
}
int w = 0, h = 0, channels = 0;
auto* pixels = stbi_load_from_memory(
fileData.data(), static_cast<int>(fileData.size()),
&w, &h, &channels, 4);
if (pixels == nullptr) {
logWarn("failed to decode image: {}", path);
return {};
// nsvgParse needs null-terminated mutable string
fileData.push_back(0);
auto* image = nsvgParse(reinterpret_cast<char*>(fileData.data()), "px", 96.0f);
if (image == nullptr) {
logWarn("failed to parse SVG: {}", path);
return {};
}
auto handle = uploadRgba(pixels, w, h);
stbi_image_free(pixels);
return handle;
int w = targetSize > 0 ? targetSize : static_cast<int>(image->width);
int h = targetSize > 0 ? targetSize : static_cast<int>(image->height);
if (w <= 0 || h <= 0) {
nsvgDelete(image);
return {};
}
float scaleX = static_cast<float>(w) / image->width;
float scaleY = static_cast<float>(h) / image->height;
float scale = std::min(scaleX, scaleY);
auto* rast = nsvgCreateRasterizer();
if (rast == nullptr) {
nsvgDelete(image);
return {};
}
std::vector<std::uint8_t> pixels(static_cast<std::size_t>(w * h * 4));
nsvgRasterize(rast, image, 0, 0, scale, pixels.data(), w, h, w * 4);
nsvgDeleteRasterizer(rast);
nsvgDelete(image);
return uploadRgba(pixels.data(), w, h);
}
// PNG/JPEG via stb_image
auto fileData = readFile(path);
if (fileData.empty()) {
logWarn("failed to read image: {}", path);
return {};
}
int w = 0, h = 0, channels = 0;
auto* pixels = stbi_load_from_memory(fileData.data(), static_cast<int>(fileData.size()), &w, &h, &channels, 4);
if (pixels == nullptr) {
logWarn("failed to decode image: {}", path);
return {};
}
auto handle = uploadRgba(pixels, w, h);
stbi_image_free(pixels);
return handle;
}
TextureHandle TextureManager::loadFromArgbPixmap(const std::uint8_t* data, int width, int height) {
if (data == nullptr || width <= 0 || height <= 0) {
return {};
}
if (data == nullptr || width <= 0 || height <= 0) {
return {};
}
const auto pixelCount = static_cast<std::size_t>(width * height);
std::vector<std::uint8_t> rgba(pixelCount * 4);
const auto pixelCount = static_cast<std::size_t>(width * height);
std::vector<std::uint8_t> rgba(pixelCount * 4);
for (std::size_t i = 0; i < pixelCount; ++i) {
const std::size_t srcIdx = i * 4;
const std::size_t dstIdx = i * 4;
// ARGB → RGBA
rgba[dstIdx + 0] = data[srcIdx + 1]; // R
rgba[dstIdx + 1] = data[srcIdx + 2]; // G
rgba[dstIdx + 2] = data[srcIdx + 3]; // B
rgba[dstIdx + 3] = data[srcIdx + 0]; // A
}
for (std::size_t i = 0; i < pixelCount; ++i) {
const std::size_t srcIdx = i * 4;
const std::size_t dstIdx = i * 4;
// ARGB → RGBA
rgba[dstIdx + 0] = data[srcIdx + 1]; // R
rgba[dstIdx + 1] = data[srcIdx + 2]; // G
rgba[dstIdx + 2] = data[srcIdx + 3]; // B
rgba[dstIdx + 3] = data[srcIdx + 0]; // A
}
return uploadRgba(rgba.data(), width, height);
return uploadRgba(rgba.data(), width, height);
}
void TextureManager::unload(TextureHandle& handle) {
if (handle.id != 0) {
glDeleteTextures(1, &handle.id);
std::erase(m_textures, handle.id);
handle = {};
}
if (handle.id != 0) {
glDeleteTextures(1, &handle.id);
std::erase(m_textures, handle.id);
handle = {};
}
}
void TextureManager::cleanup() {
if (!m_textures.empty()) {
glDeleteTextures(static_cast<GLsizei>(m_textures.size()), m_textures.data());
m_textures.clear();
}
if (!m_textures.empty()) {
glDeleteTextures(static_cast<GLsizei>(m_textures.size()), m_textures.data());
m_textures.clear();
}
}
TextureHandle TextureManager::uploadRgba(const std::uint8_t* data, int width, int height) {
GLuint tex = 0;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, data);
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);
GLuint tex = 0;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
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);
m_textures.push_back(tex);
return TextureHandle{.id = tex, .width = width, .height = height};
m_textures.push_back(tex);
return TextureHandle{.id = tex, .width = width, .height = height};
}
+52 -74
View File
@@ -1,4 +1,4 @@
#include "render/programs/ImageProgram.hpp"
#include "render/programs/ImageProgram.h"
#include <array>
#include <stdexcept>
@@ -44,87 +44,65 @@ void main() {
} // namespace
void ImageProgram::ensureInitialized() {
if (m_program.isValid()) {
return;
}
if (m_program.isValid()) {
return;
}
m_program.create(kVertexShaderSource, kFragmentShaderSource);
m_positionLocation = glGetAttribLocation(m_program.id(), "a_position");
m_texCoordLocation = glGetAttribLocation(m_program.id(), "a_texcoord");
m_surfaceSizeLocation = glGetUniformLocation(m_program.id(), "u_surface_size");
m_rectLocation = glGetUniformLocation(m_program.id(), "u_rect");
m_tintLocation = glGetUniformLocation(m_program.id(), "u_tint");
m_opacityLocation = glGetUniformLocation(m_program.id(), "u_opacity");
m_samplerLocation = glGetUniformLocation(m_program.id(), "u_texture");
m_program.create(kVertexShaderSource, kFragmentShaderSource);
m_positionLocation = glGetAttribLocation(m_program.id(), "a_position");
m_texCoordLocation = glGetAttribLocation(m_program.id(), "a_texcoord");
m_surfaceSizeLocation = glGetUniformLocation(m_program.id(), "u_surface_size");
m_rectLocation = glGetUniformLocation(m_program.id(), "u_rect");
m_tintLocation = glGetUniformLocation(m_program.id(), "u_tint");
m_opacityLocation = glGetUniformLocation(m_program.id(), "u_opacity");
m_samplerLocation = glGetUniformLocation(m_program.id(), "u_texture");
if (m_positionLocation < 0 ||
m_texCoordLocation < 0 ||
m_surfaceSizeLocation < 0 ||
m_rectLocation < 0 ||
m_tintLocation < 0 ||
m_opacityLocation < 0 ||
m_samplerLocation < 0) {
throw std::runtime_error("failed to query image shader locations");
}
if (m_positionLocation < 0 || m_texCoordLocation < 0 || m_surfaceSizeLocation < 0 || m_rectLocation < 0 ||
m_tintLocation < 0 || m_opacityLocation < 0 || m_samplerLocation < 0) {
throw std::runtime_error("failed to query image shader locations");
}
}
void ImageProgram::destroy() {
m_program.destroy();
m_positionLocation = -1;
m_texCoordLocation = -1;
m_surfaceSizeLocation = -1;
m_rectLocation = -1;
m_tintLocation = -1;
m_opacityLocation = -1;
m_samplerLocation = -1;
m_program.destroy();
m_positionLocation = -1;
m_texCoordLocation = -1;
m_surfaceSizeLocation = -1;
m_rectLocation = -1;
m_tintLocation = -1;
m_opacityLocation = -1;
m_samplerLocation = -1;
}
void ImageProgram::draw(GLuint texture,
float surfaceWidth,
float surfaceHeight,
float x,
float y,
float width,
float height,
const Color& tint,
float opacity) const {
if (!m_program.isValid() || texture == 0 || width <= 0.0f || height <= 0.0f) {
return;
}
void ImageProgram::draw(GLuint texture, float surfaceWidth, float surfaceHeight, float x, float y, float width,
float height, const Color& tint, float opacity) const {
if (!m_program.isValid() || texture == 0 || width <= 0.0f || height <= 0.0f) {
return;
}
const std::array<GLfloat, 12> positions = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
0.0f, 1.0f,
1.0f, 0.0f,
1.0f, 1.0f,
};
const std::array<GLfloat, 12> positions = {
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
};
const std::array<GLfloat, 12> texcoords = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
0.0f, 1.0f,
1.0f, 0.0f,
1.0f, 1.0f,
};
const std::array<GLfloat, 12> texcoords = {
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
};
glUseProgram(m_program.id());
glUniform2f(m_surfaceSizeLocation, surfaceWidth, surfaceHeight);
glUniform4f(m_rectLocation, x, y, width, height);
glUniform4f(m_tintLocation, tint.r, tint.g, tint.b, tint.a);
glUniform1f(m_opacityLocation, opacity);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(m_samplerLocation, 0);
const auto posAttr = static_cast<GLuint>(m_positionLocation);
const auto texAttr = static_cast<GLuint>(m_texCoordLocation);
glVertexAttribPointer(posAttr, 2, GL_FLOAT, GL_FALSE, 0, positions.data());
glVertexAttribPointer(texAttr, 2, GL_FLOAT, GL_FALSE, 0, texcoords.data());
glEnableVertexAttribArray(posAttr);
glEnableVertexAttribArray(texAttr);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableVertexAttribArray(posAttr);
glDisableVertexAttribArray(texAttr);
glUseProgram(m_program.id());
glUniform2f(m_surfaceSizeLocation, surfaceWidth, surfaceHeight);
glUniform4f(m_rectLocation, x, y, width, height);
glUniform4f(m_tintLocation, tint.r, tint.g, tint.b, tint.a);
glUniform1f(m_opacityLocation, opacity);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(m_samplerLocation, 0);
const auto posAttr = static_cast<GLuint>(m_positionLocation);
const auto texAttr = static_cast<GLuint>(m_texCoordLocation);
glVertexAttribPointer(posAttr, 2, GL_FLOAT, GL_FALSE, 0, positions.data());
glVertexAttribPointer(texAttr, 2, GL_FLOAT, GL_FALSE, 0, texcoords.data());
glEnableVertexAttribArray(posAttr);
glEnableVertexAttribArray(texAttr);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableVertexAttribArray(posAttr);
glDisableVertexAttribArray(texAttr);
}
@@ -1,7 +1,7 @@
#pragma once
#include "render/core/Color.hpp"
#include "render/core/ShaderProgram.hpp"
#include "render/core/Color.h"
#include "render/core/ShaderProgram.h"
#include <GLES2/gl2.h>
+39 -53
View File
@@ -1,4 +1,4 @@
#include "render/programs/LinearGradientProgram.hpp"
#include "render/programs/LinearGradientProgram.h"
#include <array>
#include <stdexcept>
@@ -43,66 +43,52 @@ void main() {
} // namespace
void LinearGradientProgram::ensureInitialized() {
if (m_program.isValid()) {
return;
}
if (m_program.isValid()) {
return;
}
m_program.create(kVertexShaderSource, kFragmentShaderSource);
m_positionLocation = glGetAttribLocation(m_program.id(), "a_position");
m_surfaceSizeLocation = glGetUniformLocation(m_program.id(), "u_surface_size");
m_rectLocation = glGetUniformLocation(m_program.id(), "u_rect");
m_startColorLocation = glGetUniformLocation(m_program.id(), "u_start_color");
m_endColorLocation = glGetUniformLocation(m_program.id(), "u_end_color");
m_directionLocation = glGetUniformLocation(m_program.id(), "u_direction");
m_program.create(kVertexShaderSource, kFragmentShaderSource);
m_positionLocation = glGetAttribLocation(m_program.id(), "a_position");
m_surfaceSizeLocation = glGetUniformLocation(m_program.id(), "u_surface_size");
m_rectLocation = glGetUniformLocation(m_program.id(), "u_rect");
m_startColorLocation = glGetUniformLocation(m_program.id(), "u_start_color");
m_endColorLocation = glGetUniformLocation(m_program.id(), "u_end_color");
m_directionLocation = glGetUniformLocation(m_program.id(), "u_direction");
if (m_positionLocation < 0 ||
m_surfaceSizeLocation < 0 ||
m_rectLocation < 0 ||
m_startColorLocation < 0 ||
m_endColorLocation < 0 ||
m_directionLocation < 0) {
throw std::runtime_error("failed to query linear-gradient shader locations");
}
if (m_positionLocation < 0 || m_surfaceSizeLocation < 0 || m_rectLocation < 0 || m_startColorLocation < 0 ||
m_endColorLocation < 0 || m_directionLocation < 0) {
throw std::runtime_error("failed to query linear-gradient shader locations");
}
}
void LinearGradientProgram::destroy() {
m_program.destroy();
m_positionLocation = -1;
m_surfaceSizeLocation = -1;
m_rectLocation = -1;
m_startColorLocation = -1;
m_endColorLocation = -1;
m_directionLocation = -1;
m_program.destroy();
m_positionLocation = -1;
m_surfaceSizeLocation = -1;
m_rectLocation = -1;
m_startColorLocation = -1;
m_endColorLocation = -1;
m_directionLocation = -1;
}
void LinearGradientProgram::draw(float surfaceWidth,
float surfaceHeight,
float x,
float y,
float width,
float height,
void LinearGradientProgram::draw(float surfaceWidth, float surfaceHeight, float x, float y, float width, float height,
const LinearGradientStyle& style) const {
if (!m_program.isValid() || width <= 0.0f || height <= 0.0f) {
return;
}
if (!m_program.isValid() || width <= 0.0f || height <= 0.0f) {
return;
}
const std::array<GLfloat, 12> vertices = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
0.0f, 1.0f,
1.0f, 0.0f,
1.0f, 1.0f,
};
const std::array<GLfloat, 12> vertices = {
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
};
glUseProgram(m_program.id());
glUniform2f(m_surfaceSizeLocation, surfaceWidth, surfaceHeight);
glUniform4f(m_rectLocation, x, y, width, height);
glUniform4f(m_startColorLocation, style.start.r, style.start.g, style.start.b, style.start.a);
glUniform4f(m_endColorLocation, style.end.r, style.end.g, style.end.b, style.end.a);
glUniform2f(m_directionLocation, style.horizontal ? 1.0f : 0.0f, style.horizontal ? 0.0f : 1.0f);
glVertexAttribPointer(m_positionLocation, 2, GL_FLOAT, GL_FALSE, 0, vertices.data());
glEnableVertexAttribArray(m_positionLocation);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableVertexAttribArray(m_positionLocation);
glUseProgram(m_program.id());
glUniform2f(m_surfaceSizeLocation, surfaceWidth, surfaceHeight);
glUniform4f(m_rectLocation, x, y, width, height);
glUniform4f(m_startColorLocation, style.start.r, style.start.g, style.start.b, style.start.a);
glUniform4f(m_endColorLocation, style.end.r, style.end.g, style.end.b, style.end.a);
glUniform2f(m_directionLocation, style.horizontal ? 1.0f : 0.0f, style.horizontal ? 0.0f : 1.0f);
glVertexAttribPointer(m_positionLocation, 2, GL_FLOAT, GL_FALSE, 0, vertices.data());
glEnableVertexAttribArray(m_positionLocation);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableVertexAttribArray(m_positionLocation);
}
@@ -1,7 +1,7 @@
#pragma once
#include "render/core/Color.hpp"
#include "render/core/ShaderProgram.hpp"
#include "render/core/Color.h"
#include "render/core/ShaderProgram.h"
#include <GLES2/gl2.h>
+52 -77
View File
@@ -1,4 +1,4 @@
#include "render/programs/MsdfTextProgram.hpp"
#include "render/programs/MsdfTextProgram.h"
#include <array>
#include <stdexcept>
@@ -51,91 +51,66 @@ void main() {
} // namespace
void MsdfTextProgram::ensureInitialized() {
if (m_program.isValid()) {
return;
}
if (m_program.isValid()) {
return;
}
m_program.create(kVertexShaderSource, kFragmentShaderSource);
m_positionLocation = glGetAttribLocation(m_program.id(), "a_position");
m_texCoordLocation = glGetAttribLocation(m_program.id(), "a_texcoord");
m_surfaceSizeLocation = glGetUniformLocation(m_program.id(), "u_surface_size");
m_rectLocation = glGetUniformLocation(m_program.id(), "u_rect");
m_pxRangeLocation = glGetUniformLocation(m_program.id(), "u_px_range");
m_colorLocation = glGetUniformLocation(m_program.id(), "u_color");
m_samplerLocation = glGetUniformLocation(m_program.id(), "u_texture");
m_program.create(kVertexShaderSource, kFragmentShaderSource);
m_positionLocation = glGetAttribLocation(m_program.id(), "a_position");
m_texCoordLocation = glGetAttribLocation(m_program.id(), "a_texcoord");
m_surfaceSizeLocation = glGetUniformLocation(m_program.id(), "u_surface_size");
m_rectLocation = glGetUniformLocation(m_program.id(), "u_rect");
m_pxRangeLocation = glGetUniformLocation(m_program.id(), "u_px_range");
m_colorLocation = glGetUniformLocation(m_program.id(), "u_color");
m_samplerLocation = glGetUniformLocation(m_program.id(), "u_texture");
if (m_positionLocation < 0 ||
m_texCoordLocation < 0 ||
m_surfaceSizeLocation < 0 ||
m_rectLocation < 0 ||
m_pxRangeLocation < 0 ||
m_colorLocation < 0 ||
m_samplerLocation < 0) {
throw std::runtime_error("failed to query MSDF text shader locations");
}
if (m_positionLocation < 0 || m_texCoordLocation < 0 || m_surfaceSizeLocation < 0 || m_rectLocation < 0 ||
m_pxRangeLocation < 0 || m_colorLocation < 0 || m_samplerLocation < 0) {
throw std::runtime_error("failed to query MSDF text shader locations");
}
}
void MsdfTextProgram::destroy() {
m_program.destroy();
m_positionLocation = -1;
m_texCoordLocation = -1;
m_surfaceSizeLocation = -1;
m_rectLocation = -1;
m_pxRangeLocation = -1;
m_colorLocation = -1;
m_samplerLocation = -1;
m_program.destroy();
m_positionLocation = -1;
m_texCoordLocation = -1;
m_surfaceSizeLocation = -1;
m_rectLocation = -1;
m_pxRangeLocation = -1;
m_colorLocation = -1;
m_samplerLocation = -1;
}
void MsdfTextProgram::draw(GLuint texture,
float surfaceWidth,
float surfaceHeight,
float x,
float y,
float width,
float height,
float u0,
float v0,
float u1,
float v1,
float pxRange,
void MsdfTextProgram::draw(GLuint texture, float surfaceWidth, float surfaceHeight, float x, float y, float width,
float height, float u0, float v0, float u1, float v1, float pxRange,
const Color& color) const {
if (!m_program.isValid() || texture == 0 || width <= 0.0f || height <= 0.0f) {
return;
}
if (!m_program.isValid() || texture == 0 || width <= 0.0f || height <= 0.0f) {
return;
}
const std::array<GLfloat, 12> positions = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
0.0f, 1.0f,
1.0f, 0.0f,
1.0f, 1.0f,
};
const std::array<GLfloat, 12> positions = {
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
};
const std::array<GLfloat, 12> texcoords = {
u0, v0,
u1, v0,
u0, v1,
u0, v1,
u1, v0,
u1, v1,
};
const std::array<GLfloat, 12> texcoords = {
u0, v0, u1, v0, u0, v1, u0, v1, u1, v0, u1, v1,
};
glUseProgram(m_program.id());
glUniform2f(m_surfaceSizeLocation, surfaceWidth, surfaceHeight);
glUniform4f(m_rectLocation, x, y, width, height);
glUniform1f(m_pxRangeLocation, pxRange);
glUniform4f(m_colorLocation, color.r, color.g, color.b, color.a);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(m_samplerLocation, 0);
const auto posAttr = static_cast<GLuint>(m_positionLocation);
const auto texAttr = static_cast<GLuint>(m_texCoordLocation);
glVertexAttribPointer(posAttr, 2, GL_FLOAT, GL_FALSE, 0, positions.data());
glVertexAttribPointer(texAttr, 2, GL_FLOAT, GL_FALSE, 0, texcoords.data());
glEnableVertexAttribArray(posAttr);
glEnableVertexAttribArray(texAttr);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableVertexAttribArray(posAttr);
glDisableVertexAttribArray(texAttr);
glUseProgram(m_program.id());
glUniform2f(m_surfaceSizeLocation, surfaceWidth, surfaceHeight);
glUniform4f(m_rectLocation, x, y, width, height);
glUniform1f(m_pxRangeLocation, pxRange);
glUniform4f(m_colorLocation, color.r, color.g, color.b, color.a);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(m_samplerLocation, 0);
const auto posAttr = static_cast<GLuint>(m_positionLocation);
const auto texAttr = static_cast<GLuint>(m_texCoordLocation);
glVertexAttribPointer(posAttr, 2, GL_FLOAT, GL_FALSE, 0, positions.data());
glVertexAttribPointer(texAttr, 2, GL_FLOAT, GL_FALSE, 0, texcoords.data());
glEnableVertexAttribArray(posAttr);
glEnableVertexAttribArray(texAttr);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableVertexAttribArray(posAttr);
glDisableVertexAttribArray(texAttr);
}
@@ -1,7 +1,7 @@
#pragma once
#include "render/core/Color.hpp"
#include "render/core/ShaderProgram.hpp"
#include "render/core/Color.h"
#include "render/core/ShaderProgram.h"
#include <GLES2/gl2.h>
+64 -92
View File
@@ -1,4 +1,4 @@
#include "render/programs/RoundedRectProgram.hpp"
#include "render/programs/RoundedRectProgram.h"
#include <array>
#include <stdexcept>
@@ -91,106 +91,78 @@ void main() {
} // namespace
void RoundedRectProgram::ensureInitialized() {
if (m_program.isValid()) {
return;
}
if (m_program.isValid()) {
return;
}
m_program.create(kVertexShaderSource, kFragmentShaderSource);
m_positionLocation = glGetAttribLocation(m_program.id(), "a_position");
m_surfaceSizeLocation = glGetUniformLocation(m_program.id(), "u_surface_size");
m_quadRectLocation = glGetUniformLocation(m_program.id(), "u_quad_rect");
m_rectLocation = glGetUniformLocation(m_program.id(), "u_rect");
m_colorLocation = glGetUniformLocation(m_program.id(), "u_color");
m_fillEndColorLocation = glGetUniformLocation(m_program.id(), "u_fill_end_color");
m_borderColorLocation = glGetUniformLocation(m_program.id(), "u_border_color");
m_fillModeLocation = glGetUniformLocation(m_program.id(), "u_fill_mode");
m_gradientDirectionLocation = glGetUniformLocation(m_program.id(), "u_gradient_direction");
m_radiusLocation = glGetUniformLocation(m_program.id(), "u_radius");
m_softnessLocation = glGetUniformLocation(m_program.id(), "u_softness");
m_borderWidthLocation = glGetUniformLocation(m_program.id(), "u_border_width");
m_program.create(kVertexShaderSource, kFragmentShaderSource);
m_positionLocation = glGetAttribLocation(m_program.id(), "a_position");
m_surfaceSizeLocation = glGetUniformLocation(m_program.id(), "u_surface_size");
m_quadRectLocation = glGetUniformLocation(m_program.id(), "u_quad_rect");
m_rectLocation = glGetUniformLocation(m_program.id(), "u_rect");
m_colorLocation = glGetUniformLocation(m_program.id(), "u_color");
m_fillEndColorLocation = glGetUniformLocation(m_program.id(), "u_fill_end_color");
m_borderColorLocation = glGetUniformLocation(m_program.id(), "u_border_color");
m_fillModeLocation = glGetUniformLocation(m_program.id(), "u_fill_mode");
m_gradientDirectionLocation = glGetUniformLocation(m_program.id(), "u_gradient_direction");
m_radiusLocation = glGetUniformLocation(m_program.id(), "u_radius");
m_softnessLocation = glGetUniformLocation(m_program.id(), "u_softness");
m_borderWidthLocation = glGetUniformLocation(m_program.id(), "u_border_width");
if (m_positionLocation < 0 ||
m_surfaceSizeLocation < 0 ||
m_quadRectLocation < 0 ||
m_rectLocation < 0 ||
m_colorLocation < 0 ||
m_fillEndColorLocation < 0 ||
m_borderColorLocation < 0 ||
m_fillModeLocation < 0 ||
m_gradientDirectionLocation < 0 ||
m_radiusLocation < 0 ||
m_softnessLocation < 0 ||
m_borderWidthLocation < 0) {
throw std::runtime_error("failed to query rounded-rect shader locations");
}
if (m_positionLocation < 0 || m_surfaceSizeLocation < 0 || m_quadRectLocation < 0 || m_rectLocation < 0 ||
m_colorLocation < 0 || m_fillEndColorLocation < 0 || m_borderColorLocation < 0 || m_fillModeLocation < 0 ||
m_gradientDirectionLocation < 0 || m_radiusLocation < 0 || m_softnessLocation < 0 || m_borderWidthLocation < 0) {
throw std::runtime_error("failed to query rounded-rect shader locations");
}
}
void RoundedRectProgram::destroy() {
m_program.destroy();
m_positionLocation = -1;
m_surfaceSizeLocation = -1;
m_quadRectLocation = -1;
m_rectLocation = -1;
m_colorLocation = -1;
m_fillEndColorLocation = -1;
m_borderColorLocation = -1;
m_fillModeLocation = -1;
m_gradientDirectionLocation = -1;
m_radiusLocation = -1;
m_softnessLocation = -1;
m_borderWidthLocation = -1;
m_program.destroy();
m_positionLocation = -1;
m_surfaceSizeLocation = -1;
m_quadRectLocation = -1;
m_rectLocation = -1;
m_colorLocation = -1;
m_fillEndColorLocation = -1;
m_borderColorLocation = -1;
m_fillModeLocation = -1;
m_gradientDirectionLocation = -1;
m_radiusLocation = -1;
m_softnessLocation = -1;
m_borderWidthLocation = -1;
}
void RoundedRectProgram::draw(float surfaceWidth,
float surfaceHeight,
float x,
float y,
float width,
float height,
void RoundedRectProgram::draw(float surfaceWidth, float surfaceHeight, float x, float y, float width, float height,
const RoundedRectStyle& style) const {
if (!m_program.isValid() || width <= 0.0f || height <= 0.0f) {
return;
}
if (!m_program.isValid() || width <= 0.0f || height <= 0.0f) {
return;
}
const std::array<GLfloat, 12> vertices = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
0.0f, 1.0f,
1.0f, 0.0f,
1.0f, 1.0f,
};
const std::array<GLfloat, 12> vertices = {
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
};
const float padding = std::max(style.borderWidth + style.softness + 2.0f, 2.0f);
const float quadX = x - padding;
const float quadY = y - padding;
const float quadWidth = width + padding * 2.0f;
const float quadHeight = height + padding * 2.0f;
const float padding = std::max(style.borderWidth + style.softness + 2.0f, 2.0f);
const float quadX = x - padding;
const float quadY = y - padding;
const float quadWidth = width + padding * 2.0f;
const float quadHeight = height + padding * 2.0f;
glUseProgram(m_program.id());
glUniform2f(m_surfaceSizeLocation, surfaceWidth, surfaceHeight);
glUniform4f(m_quadRectLocation, quadX, quadY, quadWidth, quadHeight);
glUniform4f(m_rectLocation, x, y, width, height);
glUniform4f(m_colorLocation, style.fill.r, style.fill.g, style.fill.b, style.fill.a);
glUniform4f(m_fillEndColorLocation,
style.fillEnd.r,
style.fillEnd.g,
style.fillEnd.b,
style.fillEnd.a);
glUniform4f(m_borderColorLocation,
style.border.r,
style.border.g,
style.border.b,
style.border.a);
glUniform1i(m_fillModeLocation, style.fillMode == FillMode::Solid ? 0 : 1);
glUniform2f(m_gradientDirectionLocation,
style.gradientDirection == GradientDirection::Horizontal ? 1.0f : 0.0f,
style.gradientDirection == GradientDirection::Vertical ? 1.0f : 0.0f);
glUniform1f(m_radiusLocation, style.radius);
glUniform1f(m_softnessLocation, style.softness);
glUniform1f(m_borderWidthLocation, style.borderWidth);
glVertexAttribPointer(m_positionLocation, 2, GL_FLOAT, GL_FALSE, 0, vertices.data());
glEnableVertexAttribArray(m_positionLocation);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableVertexAttribArray(m_positionLocation);
glUseProgram(m_program.id());
glUniform2f(m_surfaceSizeLocation, surfaceWidth, surfaceHeight);
glUniform4f(m_quadRectLocation, quadX, quadY, quadWidth, quadHeight);
glUniform4f(m_rectLocation, x, y, width, height);
glUniform4f(m_colorLocation, style.fill.r, style.fill.g, style.fill.b, style.fill.a);
glUniform4f(m_fillEndColorLocation, style.fillEnd.r, style.fillEnd.g, style.fillEnd.b, style.fillEnd.a);
glUniform4f(m_borderColorLocation, style.border.r, style.border.g, style.border.b, style.border.a);
glUniform1i(m_fillModeLocation, style.fillMode == FillMode::Solid ? 0 : 1);
glUniform2f(m_gradientDirectionLocation, style.gradientDirection == GradientDirection::Horizontal ? 1.0f : 0.0f,
style.gradientDirection == GradientDirection::Vertical ? 1.0f : 0.0f);
glUniform1f(m_radiusLocation, style.radius);
glUniform1f(m_softnessLocation, style.softness);
glUniform1f(m_borderWidthLocation, style.borderWidth);
glVertexAttribPointer(m_positionLocation, 2, GL_FLOAT, GL_FALSE, 0, vertices.data());
glEnableVertexAttribArray(m_positionLocation);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableVertexAttribArray(m_positionLocation);
}
@@ -1,7 +1,7 @@
#pragma once
#include "render/core/Color.hpp"
#include "render/core/ShaderProgram.hpp"
#include "render/core/Color.h"
#include "render/core/ShaderProgram.h"
#include <GLES2/gl2.h>
+108 -105
View File
@@ -1,4 +1,4 @@
#include "render/programs/WallpaperProgram.hpp"
#include "render/programs/WallpaperProgram.h"
#include <stdexcept>
@@ -306,130 +306,133 @@ void main() {
} // namespace
void WallpaperProgram::ensureInitialized() {
if (m_programs[0].program.isValid()) {
return;
}
if (m_programs[0].program.isValid()) {
return;
}
initProgram(static_cast<std::size_t>(WallpaperTransition::Fade), kFadeFragment);
initProgram(static_cast<std::size_t>(WallpaperTransition::Wipe), kWipeFragment);
initProgram(static_cast<std::size_t>(WallpaperTransition::Disc), kDiscFragment);
initProgram(static_cast<std::size_t>(WallpaperTransition::Stripes), kStripesFragment);
initProgram(static_cast<std::size_t>(WallpaperTransition::Pixelate), kPixelateFragment);
initProgram(static_cast<std::size_t>(WallpaperTransition::Honeycomb), kHoneycombFragment);
initProgram(static_cast<std::size_t>(WallpaperTransition::Fade), kFadeFragment);
initProgram(static_cast<std::size_t>(WallpaperTransition::Wipe), kWipeFragment);
initProgram(static_cast<std::size_t>(WallpaperTransition::Disc), kDiscFragment);
initProgram(static_cast<std::size_t>(WallpaperTransition::Stripes), kStripesFragment);
initProgram(static_cast<std::size_t>(WallpaperTransition::Pixelate), kPixelateFragment);
initProgram(static_cast<std::size_t>(WallpaperTransition::Honeycomb), kHoneycombFragment);
}
void WallpaperProgram::destroy() {
for (auto& pd : m_programs) {
pd.program.destroy();
}
for (auto& pd : m_programs) {
pd.program.destroy();
}
}
void WallpaperProgram::initProgram(std::size_t index, const char* fragSource) {
std::string fullFrag = std::string(kCommonFunctions) + fragSource;
std::string fullFrag = std::string(kCommonFunctions) + fragSource;
auto& pd = m_programs[index];
pd.program.create(kVertexShader, fullFrag.c_str());
auto& pd = m_programs[index];
pd.program.create(kVertexShader, fullFrag.c_str());
auto id = pd.program.id();
pd.positionLoc = glGetAttribLocation(id, "a_position");
pd.texCoordLoc = -1; // texcoord derived from position in vertex shader
pd.source1Loc = glGetUniformLocation(id, "u_source1");
pd.source2Loc = glGetUniformLocation(id, "u_source2");
pd.progressLoc = glGetUniformLocation(id, "u_progress");
pd.fillModeLoc = glGetUniformLocation(id, "u_fillMode");
pd.imageWidth1Loc = glGetUniformLocation(id, "u_imageWidth1");
pd.imageHeight1Loc = glGetUniformLocation(id, "u_imageHeight1");
pd.imageWidth2Loc = glGetUniformLocation(id, "u_imageWidth2");
pd.imageHeight2Loc = glGetUniformLocation(id, "u_imageHeight2");
pd.screenWidthLoc = glGetUniformLocation(id, "u_screenWidth");
pd.screenHeightLoc = glGetUniformLocation(id, "u_screenHeight");
pd.fillColorLoc = glGetUniformLocation(id, "u_fillColor");
auto id = pd.program.id();
pd.positionLoc = glGetAttribLocation(id, "a_position");
pd.texCoordLoc = -1; // texcoord derived from position in vertex shader
pd.source1Loc = glGetUniformLocation(id, "u_source1");
pd.source2Loc = glGetUniformLocation(id, "u_source2");
pd.progressLoc = glGetUniformLocation(id, "u_progress");
pd.fillModeLoc = glGetUniformLocation(id, "u_fillMode");
pd.imageWidth1Loc = glGetUniformLocation(id, "u_imageWidth1");
pd.imageHeight1Loc = glGetUniformLocation(id, "u_imageHeight1");
pd.imageWidth2Loc = glGetUniformLocation(id, "u_imageWidth2");
pd.imageHeight2Loc = glGetUniformLocation(id, "u_imageHeight2");
pd.screenWidthLoc = glGetUniformLocation(id, "u_screenWidth");
pd.screenHeightLoc = glGetUniformLocation(id, "u_screenHeight");
pd.fillColorLoc = glGetUniformLocation(id, "u_fillColor");
// Per-transition (may be -1 if not used by this shader)
pd.directionLoc = glGetUniformLocation(id, "u_direction");
pd.smoothnessLoc = glGetUniformLocation(id, "u_smoothness");
pd.centerXLoc = glGetUniformLocation(id, "u_centerX");
pd.centerYLoc = glGetUniformLocation(id, "u_centerY");
pd.aspectRatioLoc = glGetUniformLocation(id, "u_aspectRatio");
pd.stripeCountLoc = glGetUniformLocation(id, "u_stripeCount");
pd.angleLoc = glGetUniformLocation(id, "u_angle");
pd.maxBlockSizeLoc = glGetUniformLocation(id, "u_maxBlockSize");
pd.cellSizeLoc = glGetUniformLocation(id, "u_cellSize");
// Per-transition (may be -1 if not used by this shader)
pd.directionLoc = glGetUniformLocation(id, "u_direction");
pd.smoothnessLoc = glGetUniformLocation(id, "u_smoothness");
pd.centerXLoc = glGetUniformLocation(id, "u_centerX");
pd.centerYLoc = glGetUniformLocation(id, "u_centerY");
pd.aspectRatioLoc = glGetUniformLocation(id, "u_aspectRatio");
pd.stripeCountLoc = glGetUniformLocation(id, "u_stripeCount");
pd.angleLoc = glGetUniformLocation(id, "u_angle");
pd.maxBlockSizeLoc = glGetUniformLocation(id, "u_maxBlockSize");
pd.cellSizeLoc = glGetUniformLocation(id, "u_cellSize");
if (pd.positionLoc < 0 || pd.source1Loc < 0 || pd.progressLoc < 0) {
throw std::runtime_error("failed to query wallpaper shader locations");
}
if (pd.positionLoc < 0 || pd.source1Loc < 0 || pd.progressLoc < 0) {
throw std::runtime_error("failed to query wallpaper shader locations");
}
}
void WallpaperProgram::draw(WallpaperTransition type,
GLuint texture1,
GLuint texture2,
float surfaceWidth,
float surfaceHeight,
float imageWidth1,
float imageHeight1,
float imageWidth2,
float imageHeight2,
float progress,
float fillMode,
const TransitionParams& params) const {
auto idx = static_cast<std::size_t>(type);
if (idx >= kTransitionCount || !m_programs[idx].program.isValid()) {
return;
}
void WallpaperProgram::draw(WallpaperTransition type, GLuint texture1, GLuint texture2, float surfaceWidth,
float surfaceHeight, float imageWidth1, float imageHeight1, float imageWidth2,
float imageHeight2, float progress, float fillMode, const TransitionParams& params) const {
auto idx = static_cast<std::size_t>(type);
if (idx >= kTransitionCount || !m_programs[idx].program.isValid()) {
return;
}
const auto& pd = m_programs[idx];
const auto& pd = m_programs[idx];
static constexpr float kQuad[] = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
0.0f, 1.0f,
1.0f, 0.0f,
1.0f, 1.0f,
};
static constexpr float kQuad[] = {
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
};
glUseProgram(pd.program.id());
glUseProgram(pd.program.id());
// Textures
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glUniform1i(pd.source1Loc, 0);
// Textures
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glUniform1i(pd.source1Loc, 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
if (pd.source2Loc >= 0) {
glUniform1i(pd.source2Loc, 1);
}
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
if (pd.source2Loc >= 0) {
glUniform1i(pd.source2Loc, 1);
}
// Common uniforms
glUniform1f(pd.progressLoc, progress);
if (pd.fillModeLoc >= 0) glUniform1f(pd.fillModeLoc, fillMode);
if (pd.imageWidth1Loc >= 0) glUniform1f(pd.imageWidth1Loc, imageWidth1);
if (pd.imageHeight1Loc >= 0) glUniform1f(pd.imageHeight1Loc, imageHeight1);
if (pd.imageWidth2Loc >= 0) glUniform1f(pd.imageWidth2Loc, imageWidth2);
if (pd.imageHeight2Loc >= 0) glUniform1f(pd.imageHeight2Loc, imageHeight2);
if (pd.screenWidthLoc >= 0) glUniform1f(pd.screenWidthLoc, surfaceWidth);
if (pd.screenHeightLoc >= 0) glUniform1f(pd.screenHeightLoc, surfaceHeight);
if (pd.fillColorLoc >= 0) glUniform4f(pd.fillColorLoc, 0.0f, 0.0f, 0.0f, 1.0f);
// Common uniforms
glUniform1f(pd.progressLoc, progress);
if (pd.fillModeLoc >= 0)
glUniform1f(pd.fillModeLoc, fillMode);
if (pd.imageWidth1Loc >= 0)
glUniform1f(pd.imageWidth1Loc, imageWidth1);
if (pd.imageHeight1Loc >= 0)
glUniform1f(pd.imageHeight1Loc, imageHeight1);
if (pd.imageWidth2Loc >= 0)
glUniform1f(pd.imageWidth2Loc, imageWidth2);
if (pd.imageHeight2Loc >= 0)
glUniform1f(pd.imageHeight2Loc, imageHeight2);
if (pd.screenWidthLoc >= 0)
glUniform1f(pd.screenWidthLoc, surfaceWidth);
if (pd.screenHeightLoc >= 0)
glUniform1f(pd.screenHeightLoc, surfaceHeight);
if (pd.fillColorLoc >= 0)
glUniform4f(pd.fillColorLoc, 0.0f, 0.0f, 0.0f, 1.0f);
// Per-transition uniforms
if (pd.directionLoc >= 0) glUniform1f(pd.directionLoc, params.direction);
if (pd.smoothnessLoc >= 0) glUniform1f(pd.smoothnessLoc, params.smoothness);
if (pd.centerXLoc >= 0) glUniform1f(pd.centerXLoc, params.centerX);
if (pd.centerYLoc >= 0) glUniform1f(pd.centerYLoc, params.centerY);
if (pd.aspectRatioLoc >= 0) glUniform1f(pd.aspectRatioLoc, params.aspectRatio);
if (pd.stripeCountLoc >= 0) glUniform1f(pd.stripeCountLoc, params.stripeCount);
if (pd.angleLoc >= 0) glUniform1f(pd.angleLoc, params.angle);
if (pd.maxBlockSizeLoc >= 0) glUniform1f(pd.maxBlockSizeLoc, params.maxBlockSize);
if (pd.cellSizeLoc >= 0) glUniform1f(pd.cellSizeLoc, params.cellSize);
// Per-transition uniforms
if (pd.directionLoc >= 0)
glUniform1f(pd.directionLoc, params.direction);
if (pd.smoothnessLoc >= 0)
glUniform1f(pd.smoothnessLoc, params.smoothness);
if (pd.centerXLoc >= 0)
glUniform1f(pd.centerXLoc, params.centerX);
if (pd.centerYLoc >= 0)
glUniform1f(pd.centerYLoc, params.centerY);
if (pd.aspectRatioLoc >= 0)
glUniform1f(pd.aspectRatioLoc, params.aspectRatio);
if (pd.stripeCountLoc >= 0)
glUniform1f(pd.stripeCountLoc, params.stripeCount);
if (pd.angleLoc >= 0)
glUniform1f(pd.angleLoc, params.angle);
if (pd.maxBlockSizeLoc >= 0)
glUniform1f(pd.maxBlockSizeLoc, params.maxBlockSize);
if (pd.cellSizeLoc >= 0)
glUniform1f(pd.cellSizeLoc, params.cellSize);
// Draw fullscreen quad
auto posAttr = static_cast<GLuint>(pd.positionLoc);
glVertexAttribPointer(posAttr, 2, GL_FLOAT, GL_FALSE, 0, kQuad);
glEnableVertexAttribArray(posAttr);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableVertexAttribArray(posAttr);
// Draw fullscreen quad
auto posAttr = static_cast<GLuint>(pd.positionLoc);
glVertexAttribPointer(posAttr, 2, GL_FLOAT, GL_FALSE, 0, kQuad);
glEnableVertexAttribArray(posAttr);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableVertexAttribArray(posAttr);
glActiveTexture(GL_TEXTURE0);
glActiveTexture(GL_TEXTURE0);
}
@@ -1,7 +1,7 @@
#pragma once
#include "config/ConfigService.hpp"
#include "render/core/ShaderProgram.hpp"
#include "config/ConfigService.h"
#include "render/core/ShaderProgram.h"
#include <GLES2/gl2.h>
@@ -1,7 +1,7 @@
#pragma once
#include "render/core/Color.hpp"
#include "render/scene/Node.hpp"
#include "render/core/Color.h"
#include "render/scene/Node.h"
class IconNode : public Node {
public:
@@ -1,7 +1,7 @@
#pragma once
#include "render/core/Color.hpp"
#include "render/scene/Node.hpp"
#include "render/core/Color.h"
#include "render/scene/Node.h"
#include <cstdint>
+77 -84
View File
@@ -1,125 +1,118 @@
#include "render/scene/Node.hpp"
#include "render/scene/Node.h"
#include <algorithm>
Node::Node(NodeType type)
: m_type(type) {}
Node::Node(NodeType type) : m_type(type) {}
Node::~Node() = default;
void Node::setPosition(float x, float y) {
if (m_x == x && m_y == y) {
return;
}
m_x = x;
m_y = y;
markDirty();
if (m_x == x && m_y == y) {
return;
}
m_x = x;
m_y = y;
markDirty();
}
void Node::setSize(float width, float height) {
if (m_width == width && m_height == height) {
return;
}
m_width = width;
m_height = height;
markDirty();
if (m_width == width && m_height == height) {
return;
}
m_width = width;
m_height = height;
markDirty();
}
void Node::setOpacity(float opacity) {
if (m_opacity == opacity) {
return;
}
m_opacity = opacity;
markDirty();
if (m_opacity == opacity) {
return;
}
m_opacity = opacity;
markDirty();
}
void Node::setVisible(bool visible) {
if (m_visible == visible) {
return;
}
m_visible = visible;
markDirty();
if (m_visible == visible) {
return;
}
m_visible = visible;
markDirty();
}
Node* Node::addChild(std::unique_ptr<Node> child) {
child->m_parent = this;
auto* raw = child.get();
m_children.push_back(std::move(child));
markDirty();
return raw;
child->m_parent = this;
auto* raw = child.get();
m_children.push_back(std::move(child));
markDirty();
return raw;
}
Node* Node::insertChildAt(std::size_t index, std::unique_ptr<Node> child) {
child->m_parent = this;
auto* raw = child.get();
if (index >= m_children.size()) {
m_children.push_back(std::move(child));
} else {
m_children.insert(m_children.begin() + static_cast<std::ptrdiff_t>(index), std::move(child));
}
markDirty();
return raw;
child->m_parent = this;
auto* raw = child.get();
if (index >= m_children.size()) {
m_children.push_back(std::move(child));
} else {
m_children.insert(m_children.begin() + static_cast<std::ptrdiff_t>(index), std::move(child));
}
markDirty();
return raw;
}
std::unique_ptr<Node> Node::removeChild(Node* child) {
auto it = std::find_if(m_children.begin(), m_children.end(),
[child](const auto& ptr) { return ptr.get() == child; });
auto it = std::find_if(m_children.begin(), m_children.end(), [child](const auto& ptr) { return ptr.get() == child; });
if (it == m_children.end()) {
return nullptr;
}
if (it == m_children.end()) {
return nullptr;
}
auto removed = std::move(*it);
m_children.erase(it);
removed->m_parent = nullptr;
markDirty();
return removed;
auto removed = std::move(*it);
m_children.erase(it);
removed->m_parent = nullptr;
markDirty();
return removed;
}
void Node::markDirty() {
m_dirty = true;
if (m_parent != nullptr && !m_parent->m_dirty) {
m_parent->markDirty();
}
m_dirty = true;
if (m_parent != nullptr && !m_parent->m_dirty) {
m_parent->markDirty();
}
}
void Node::clearDirty() {
m_dirty = false;
}
void Node::clearDirty() { m_dirty = false; }
Node* Node::hitTest(Node* root, float x, float y) {
return hitTestImpl(root, x, y, 0.0f, 0.0f);
}
Node* Node::hitTest(Node* root, float x, float y) { return hitTestImpl(root, x, y, 0.0f, 0.0f); }
Node* Node::hitTestImpl(Node* node, float px, float py, float offsetX, float offsetY) {
if (node == nullptr || !node->m_visible) {
return nullptr;
if (node == nullptr || !node->m_visible) {
return nullptr;
}
const float nodeX = offsetX + node->m_x;
const float nodeY = offsetY + node->m_y;
if (px < nodeX || px >= nodeX + node->m_width || py < nodeY || py >= nodeY + node->m_height) {
return nullptr;
}
// Traverse children in reverse (topmost/last-added first)
for (auto it = node->m_children.rbegin(); it != node->m_children.rend(); ++it) {
auto* hit = hitTestImpl(it->get(), px, py, nodeX, nodeY);
if (hit != nullptr) {
return hit;
}
}
const float nodeX = offsetX + node->m_x;
const float nodeY = offsetY + node->m_y;
if (px < nodeX || px >= nodeX + node->m_width ||
py < nodeY || py >= nodeY + node->m_height) {
return nullptr;
}
// Traverse children in reverse (topmost/last-added first)
for (auto it = node->m_children.rbegin(); it != node->m_children.rend(); ++it) {
auto* hit = hitTestImpl(it->get(), px, py, nodeX, nodeY);
if (hit != nullptr) {
return hit;
}
}
return node;
return node;
}
void Node::absolutePosition(const Node* node, float& outX, float& outY) {
outX = 0.0f;
outY = 0.0f;
for (const Node* n = node; n != nullptr; n = n->m_parent) {
outX += n->m_x;
outY += n->m_y;
}
outX = 0.0f;
outY = 0.0f;
for (const Node* n = node; n != nullptr; n = n->m_parent) {
outX += n->m_x;
outY += n->m_y;
}
}
@@ -1,7 +1,7 @@
#pragma once
#include "render/programs/RoundedRectProgram.hpp"
#include "render/scene/Node.hpp"
#include "render/programs/RoundedRectProgram.h"
#include "render/scene/Node.h"
class RectNode : public Node {
public:
@@ -1,7 +1,7 @@
#pragma once
#include "render/core/Color.hpp"
#include "render/scene/Node.hpp"
#include "render/core/Color.h"
#include "render/scene/Node.h"
#include <string>
File diff suppressed because it is too large Load Diff
@@ -1,7 +1,7 @@
#pragma once
#include "render/core/Color.hpp"
#include "render/programs/MsdfTextProgram.hpp"
#include "render/core/Color.h"
#include "render/programs/MsdfTextProgram.h"
#include <ft2build.h>
#include FT_FREETYPE_H
+361 -380
View File
@@ -1,14 +1,14 @@
#include "shell/Bar.hpp"
#include "shell/Bar.h"
#include "config/ConfigService.hpp"
#include "core/Log.hpp"
#include "ui/style/Palette.hpp"
#include "ui/style/Style.hpp"
#include "render/scene/RectNode.hpp"
#include "time/TimeService.hpp"
#include "shell/Widget.hpp"
#include "ui/controls/Box.hpp"
#include "wayland/WaylandConnection.hpp"
#include "config/ConfigService.h"
#include "core/Log.h"
#include "render/scene/RectNode.h"
#include "shell/Widget.h"
#include "time/TimeService.h"
#include "ui/controls/Box.h"
#include "ui/style/Palette.h"
#include "ui/style/Style.h"
#include "wayland/WaylandConnection.h"
#include <algorithm>
@@ -17,17 +17,17 @@
namespace {
std::uint32_t positionToAnchor(const std::string& position) {
if (position == "bottom") {
return LayerShellAnchor::Bottom | LayerShellAnchor::Left | LayerShellAnchor::Right;
}
if (position == "left") {
return LayerShellAnchor::Top | LayerShellAnchor::Bottom | LayerShellAnchor::Left;
}
if (position == "right") {
return LayerShellAnchor::Top | LayerShellAnchor::Bottom | LayerShellAnchor::Right;
}
// Default: top
return LayerShellAnchor::Top | LayerShellAnchor::Left | LayerShellAnchor::Right;
if (position == "bottom") {
return LayerShellAnchor::Bottom | LayerShellAnchor::Left | LayerShellAnchor::Right;
}
if (position == "left") {
return LayerShellAnchor::Top | LayerShellAnchor::Bottom | LayerShellAnchor::Left;
}
if (position == "right") {
return LayerShellAnchor::Top | LayerShellAnchor::Bottom | LayerShellAnchor::Right;
}
// Default: top
return LayerShellAnchor::Top | LayerShellAnchor::Left | LayerShellAnchor::Right;
}
} // namespace
@@ -35,277 +35,306 @@ std::uint32_t positionToAnchor(const std::string& position) {
Bar::Bar() = default;
bool Bar::initialize(WaylandConnection& wayland, ConfigService* config, TimeService* timeService) {
m_wayland = &wayland;
m_config = config;
m_time = timeService;
m_wayland = &wayland;
m_config = config;
m_time = timeService;
m_widgetFactory = std::make_unique<WidgetFactory>(*m_wayland, m_time, m_config->config());
m_widgetFactory = std::make_unique<WidgetFactory>(*m_wayland, m_time, m_config->config());
if (timeService != nullptr) {
timeService->setTickSecondCallback([this]() {
for (auto& inst : m_instances) {
if (inst->surface == nullptr || inst->surface->renderer() == nullptr) {
continue;
}
inst->surface->renderer()->makeCurrent();
updateWidgets(*inst);
if (inst->sceneRoot != nullptr && inst->sceneRoot->dirty()) {
inst->surface->requestRedraw();
}
}
});
}
m_wayland->setPointerEventCallback([this](const PointerEvent& event) {
onPointerEvent(event);
});
m_config->setReloadCallback([this]() {
reload();
});
syncInstances();
return true;
}
void Bar::reload() {
logInfo("bar: reloading config");
m_widgetFactory = std::make_unique<WidgetFactory>(*m_wayland, m_time, m_config->config());
m_instances.clear();
m_surfaceMap.clear();
m_hoveredInstance = nullptr;
m_hoveredWidget = nullptr;
syncInstances();
}
void Bar::closeAllInstances() {
m_surfaceMap.clear();
m_hoveredInstance = nullptr;
m_hoveredWidget = nullptr;
m_instances.clear();
}
void Bar::onOutputChange() {
syncInstances();
}
void Bar::onWorkspaceChange() {
for (auto& inst : m_instances) {
if (timeService != nullptr) {
timeService->setTickSecondCallback([this]() {
for (auto& inst : m_instances) {
if (inst->surface == nullptr || inst->surface->renderer() == nullptr) {
continue;
continue;
}
inst->surface->renderer()->makeCurrent();
updateWidgets(*inst);
inst->surface->renderNow();
if (inst->sceneRoot != nullptr && inst->sceneRoot->dirty()) {
inst->surface->requestRedraw();
}
}
});
}
m_wayland->setPointerEventCallback([this](const PointerEvent& event) { onPointerEvent(event); });
m_config->setReloadCallback([this]() { reload(); });
syncInstances();
return true;
}
void Bar::reload() {
logInfo("bar: reloading config");
m_widgetFactory = std::make_unique<WidgetFactory>(*m_wayland, m_time, m_config->config());
m_instances.clear();
m_surfaceMap.clear();
m_hoveredInstance = nullptr;
m_hoveredWidget = nullptr;
syncInstances();
}
void Bar::closeAllInstances() {
m_surfaceMap.clear();
m_hoveredInstance = nullptr;
m_hoveredWidget = nullptr;
m_instances.clear();
}
void Bar::onOutputChange() { syncInstances(); }
void Bar::onWorkspaceChange() {
for (auto& inst : m_instances) {
if (inst->surface == nullptr || inst->surface->renderer() == nullptr) {
continue;
}
inst->surface->renderer()->makeCurrent();
updateWidgets(*inst);
inst->surface->renderNow();
}
}
bool Bar::isRunning() const noexcept {
return std::any_of(m_instances.begin(), m_instances.end(),
[](const auto& inst) { return inst->surface && inst->surface->isRunning(); });
return std::any_of(m_instances.begin(), m_instances.end(),
[](const auto& inst) { return inst->surface && inst->surface->isRunning(); });
}
void Bar::syncInstances() {
const auto& outputs = m_wayland->outputs();
const auto& bars = m_config->config().bars;
const auto& outputs = m_wayland->outputs();
const auto& bars = m_config->config().bars;
// Remove instances for outputs that no longer exist
std::erase_if(m_instances, [&outputs](const auto& inst) {
bool found = std::any_of(outputs.begin(), outputs.end(),
[&inst](const auto& out) { return out.name == inst->outputName; });
if (!found) {
logInfo("bar: removing instance for output {}", inst->outputName);
}
return !found;
});
// Create instances for each bar definition × each output
for (std::size_t barIdx = 0; barIdx < bars.size(); ++barIdx) {
for (const auto& output : outputs) {
bool exists = std::any_of(m_instances.begin(), m_instances.end(),
[&output, barIdx](const auto& inst) {
return inst->outputName == output.name && inst->barIndex == barIdx;
});
if (!exists) {
auto resolved = ConfigService::resolveForOutput(bars[barIdx], output);
if (!resolved.enabled) {
continue;
}
createInstance(output, resolved);
m_instances.back()->barIndex = barIdx;
}
}
// Remove instances for outputs that no longer exist
std::erase_if(m_instances, [&outputs](const auto& inst) {
bool found =
std::any_of(outputs.begin(), outputs.end(), [&inst](const auto& out) { return out.name == inst->outputName; });
if (!found) {
logInfo("bar: removing instance for output {}", inst->outputName);
}
return !found;
});
// Create instances for each bar definition × each output
for (std::size_t barIdx = 0; barIdx < bars.size(); ++barIdx) {
for (const auto& output : outputs) {
bool exists = std::any_of(m_instances.begin(), m_instances.end(), [&output, barIdx](const auto& inst) {
return inst->outputName == output.name && inst->barIndex == barIdx;
});
if (!exists) {
auto resolved = ConfigService::resolveForOutput(bars[barIdx], output);
if (!resolved.enabled) {
continue;
}
createInstance(output, resolved);
m_instances.back()->barIndex = barIdx;
}
}
}
}
void Bar::createInstance(const WaylandOutput& output, const BarConfig& barConfig) {
logInfo("bar: creating \"{}\" on {} ({}), height={} position={}",
barConfig.name, output.connectorName, output.description,
barConfig.height, barConfig.position);
logInfo("bar: creating \"{}\" on {} ({}), height={} position={}", barConfig.name, output.connectorName,
output.description, barConfig.height, barConfig.position);
auto instance = std::make_unique<BarInstance>();
instance->outputName = output.name;
instance->output = output.output;
instance->scale = output.scale;
instance->barConfig = barConfig;
auto instance = std::make_unique<BarInstance>();
instance->outputName = output.name;
instance->output = output.output;
instance->scale = output.scale;
instance->barConfig = barConfig;
const auto anchor = positionToAnchor(barConfig.position);
const bool vertical = (barConfig.position == "left" || barConfig.position == "right");
const auto anchor = positionToAnchor(barConfig.position);
const bool vertical = (barConfig.position == "left" || barConfig.position == "right");
auto surfaceConfig = LayerSurfaceConfig{
.nameSpace = "noctalia-" + barConfig.name,
.layer = LayerShellLayer::Top,
.anchor = anchor,
.width = vertical ? barConfig.height : 0,
.height = vertical ? 0 : barConfig.height,
.exclusiveZone = static_cast<std::int32_t>(barConfig.height),
.defaultHeight = vertical ? 0 : barConfig.height,
};
auto surfaceConfig = LayerSurfaceConfig{
.nameSpace = "noctalia-" + barConfig.name,
.layer = LayerShellLayer::Top,
.anchor = anchor,
.width = vertical ? barConfig.height : 0,
.height = vertical ? 0 : barConfig.height,
.exclusiveZone = static_cast<std::int32_t>(barConfig.height),
.defaultHeight = vertical ? 0 : barConfig.height,
};
instance->surface = std::make_unique<LayerSurface>(*m_wayland, std::move(surfaceConfig));
instance->surface = std::make_unique<LayerSurface>(*m_wayland, std::move(surfaceConfig));
auto* inst = instance.get();
instance->surface->setConfigureCallback(
[this, inst](std::uint32_t width, std::uint32_t height) {
buildScene(*inst, width, height);
});
auto* inst = instance.get();
instance->surface->setConfigureCallback(
[this, inst](std::uint32_t width, std::uint32_t height) { buildScene(*inst, width, height); });
instance->surface->setAnimationManager(&instance->animations);
populateWidgets(*instance);
instance->surface->setAnimationManager(&instance->animations);
populateWidgets(*instance);
if (!instance->surface->initialize(output.output, output.scale)) {
logWarn("bar: failed to initialize surface for output {}", output.name);
return;
}
if (!instance->surface->initialize(output.output, output.scale)) {
logWarn("bar: failed to initialize surface for output {}", output.name);
return;
}
m_surfaceMap[instance->surface->wlSurface()] = instance.get();
m_instances.push_back(std::move(instance));
m_surfaceMap[instance->surface->wlSurface()] = instance.get();
m_instances.push_back(std::move(instance));
}
void Bar::destroyInstance(std::uint32_t outputName) {
std::erase_if(m_instances, [outputName](const auto& inst) {
return inst->outputName == outputName;
});
std::erase_if(m_instances, [outputName](const auto& inst) { return inst->outputName == outputName; });
}
void Bar::populateWidgets(BarInstance& instance) {
auto createWidgets = [&](const std::vector<std::string>& names,
std::vector<std::unique_ptr<Widget>>& dest) {
for (const auto& name : names) {
auto widget = m_widgetFactory->create(name, instance.output);
if (widget != nullptr) {
dest.push_back(std::move(widget));
}
}
};
auto createWidgets = [&](const std::vector<std::string>& names, std::vector<std::unique_ptr<Widget>>& dest) {
for (const auto& name : names) {
auto widget = m_widgetFactory->create(name, instance.output);
if (widget != nullptr) {
dest.push_back(std::move(widget));
}
}
};
createWidgets(instance.barConfig.startWidgets, instance.startWidgets);
createWidgets(instance.barConfig.centerWidgets, instance.centerWidgets);
createWidgets(instance.barConfig.endWidgets, instance.endWidgets);
createWidgets(instance.barConfig.startWidgets, instance.startWidgets);
createWidgets(instance.barConfig.centerWidgets, instance.centerWidgets);
createWidgets(instance.barConfig.endWidgets, instance.endWidgets);
}
void Bar::buildScene(BarInstance& instance, std::uint32_t width, std::uint32_t height) {
auto* renderer = instance.surface->renderer();
if (renderer == nullptr) {
return;
}
auto* renderer = instance.surface->renderer();
if (renderer == nullptr) {
return;
}
const auto w = static_cast<float>(width);
const auto h = static_cast<float>(height);
const float padding = instance.barConfig.padding;
const float gap = instance.barConfig.gap;
const auto w = static_cast<float>(width);
const auto h = static_cast<float>(height);
const float padding = instance.barConfig.padding;
const float gap = instance.barConfig.gap;
if (instance.sceneRoot == nullptr) {
instance.sceneRoot = std::make_unique<Node>();
instance.sceneRoot->setSize(w, h);
// Bar background
auto bg = std::make_unique<RectNode>();
bg->setStyle(RoundedRectStyle{
.fill = palette.surface,
.border = palette.outline,
.fillMode = FillMode::Solid,
.radius = Style::radiusMd,
.softness = 1.2f,
.borderWidth = Style::borderWidth,
});
instance.sceneRoot->addChild(std::move(bg));
// Create section boxes
auto makeSection = [gap]() {
auto box = std::make_unique<Box>();
box->setDirection(BoxDirection::Horizontal);
box->setGap(gap);
box->setAlign(BoxAlign::Center);
return box;
};
instance.startSection = static_cast<Box*>(instance.sceneRoot->addChild(makeSection()));
instance.centerSection = static_cast<Box*>(instance.sceneRoot->addChild(makeSection()));
instance.endSection = static_cast<Box*>(instance.sceneRoot->addChild(makeSection()));
// Create widgets and transfer their roots to section boxes
auto initWidgets = [&](std::vector<std::unique_ptr<Widget>>& widgets, Box* section) {
for (auto& widget : widgets) {
widget->setAnimationManager(&instance.animations);
widget->create(*renderer);
if (widget->root() != nullptr) {
section->addChild(widget->releaseRoot());
}
}
};
initWidgets(instance.startWidgets, instance.startSection);
initWidgets(instance.centerWidgets, instance.centerSection);
initWidgets(instance.endWidgets, instance.endSection);
// Build widget node map for input dispatch
auto mapWidgets = [&](std::vector<std::unique_ptr<Widget>>& widgets) {
for (auto& widget : widgets) {
if (widget->root() != nullptr) {
instance.widgetNodeMap[widget->root()] = widget.get();
}
}
};
mapWidgets(instance.startWidgets);
mapWidgets(instance.centerWidgets);
mapWidgets(instance.endWidgets);
// Fade-in animation
instance.sceneRoot->setOpacity(0.0f);
instance.animations.animate(0.0f, 1.0f, 400.0f, Easing::EaseOutCubic,
[root = instance.sceneRoot.get()](float v) { root->setOpacity(v); });
renderer->setScene(instance.sceneRoot.get());
instance.surface->setSceneRoot(instance.sceneRoot.get());
}
// Update root size on reconfigure
if (instance.sceneRoot == nullptr) {
instance.sceneRoot = std::make_unique<Node>();
instance.sceneRoot->setSize(w, h);
// Layout
auto& children = instance.sceneRoot->children();
// Bar background
auto bg = std::make_unique<RectNode>();
bg->setStyle(RoundedRectStyle{
.fill = palette.surface,
.border = palette.outline,
.fillMode = FillMode::Solid,
.radius = Style::radiusMd,
.softness = 1.2f,
.borderWidth = Style::borderWidth,
});
instance.sceneRoot->addChild(std::move(bg));
// Background rect
children[0]->setPosition(10.0f, 6.0f);
children[0]->setSize(w - 20.0f, h - 12.0f);
// Layout widgets
auto layoutWidgets = [&](std::vector<std::unique_ptr<Widget>>& widgets) {
for (auto& widget : widgets) {
widget->layout(*renderer, w, h);
}
// Create section boxes
auto makeSection = [gap]() {
auto box = std::make_unique<Box>();
box->setDirection(BoxDirection::Horizontal);
box->setGap(gap);
box->setAlign(BoxAlign::Center);
return box;
};
layoutWidgets(instance.startWidgets);
layoutWidgets(instance.centerWidgets);
layoutWidgets(instance.endWidgets);
// Layout section boxes
instance.startSection->layout(*renderer);
instance.centerSection->layout(*renderer);
instance.endSection->layout(*renderer);
instance.startSection = static_cast<Box*>(instance.sceneRoot->addChild(makeSection()));
instance.centerSection = static_cast<Box*>(instance.sceneRoot->addChild(makeSection()));
instance.endSection = static_cast<Box*>(instance.sceneRoot->addChild(makeSection()));
// Position sections
// Create widgets and transfer their roots to section boxes
auto initWidgets = [&](std::vector<std::unique_ptr<Widget>>& widgets, Box* section) {
for (auto& widget : widgets) {
widget->setAnimationManager(&instance.animations);
widget->create(*renderer);
if (widget->root() != nullptr) {
section->addChild(widget->releaseRoot());
}
}
};
initWidgets(instance.startWidgets, instance.startSection);
initWidgets(instance.centerWidgets, instance.centerSection);
initWidgets(instance.endWidgets, instance.endSection);
// Build widget node map for input dispatch
auto mapWidgets = [&](std::vector<std::unique_ptr<Widget>>& widgets) {
for (auto& widget : widgets) {
if (widget->root() != nullptr) {
instance.widgetNodeMap[widget->root()] = widget.get();
}
}
};
mapWidgets(instance.startWidgets);
mapWidgets(instance.centerWidgets);
mapWidgets(instance.endWidgets);
// Fade-in animation
instance.sceneRoot->setOpacity(0.0f);
instance.animations.animate(0.0f, 1.0f, 400.0f, Easing::EaseOutCubic,
[root = instance.sceneRoot.get()](float v) { root->setOpacity(v); });
renderer->setScene(instance.sceneRoot.get());
instance.surface->setSceneRoot(instance.sceneRoot.get());
}
// Update root size on reconfigure
instance.sceneRoot->setSize(w, h);
// Layout
auto& children = instance.sceneRoot->children();
// Background rect
children[0]->setPosition(10.0f, 6.0f);
children[0]->setSize(w - 20.0f, h - 12.0f);
// Layout widgets
auto layoutWidgets = [&](std::vector<std::unique_ptr<Widget>>& widgets) {
for (auto& widget : widgets) {
widget->layout(*renderer, w, h);
}
};
layoutWidgets(instance.startWidgets);
layoutWidgets(instance.centerWidgets);
layoutWidgets(instance.endWidgets);
// Layout section boxes
instance.startSection->layout(*renderer);
instance.centerSection->layout(*renderer);
instance.endSection->layout(*renderer);
// Position sections
const float contentY = (h - instance.startSection->height()) * 0.5f;
instance.startSection->setPosition(padding, contentY);
const float centerX = (w - instance.centerSection->width()) * 0.5f;
const float centerY = (h - instance.centerSection->height()) * 0.5f;
instance.centerSection->setPosition(centerX, centerY);
const float endX = w - instance.endSection->width() - padding;
const float endY = (h - instance.endSection->height()) * 0.5f;
instance.endSection->setPosition(endX, endY);
}
void Bar::updateWidgets(BarInstance& instance) {
auto* renderer = instance.surface->renderer();
if (renderer == nullptr) {
return;
}
const auto w = static_cast<float>(instance.surface->width());
const auto h = static_cast<float>(instance.surface->height());
const float padding = instance.barConfig.padding;
auto updateSection = [&](std::vector<std::unique_ptr<Widget>>& widgets, Box* section) {
bool changed = false;
for (auto& widget : widgets) {
widget->update(*renderer);
if (widget->root() != nullptr && widget->root()->dirty()) {
changed = true;
widget->layout(*renderer, w, h);
}
}
if (changed) {
section->layout(*renderer);
}
};
updateSection(instance.startWidgets, instance.startSection);
updateSection(instance.centerWidgets, instance.centerSection);
updateSection(instance.endWidgets, instance.endSection);
// Reposition sections if sizes changed
if (instance.startSection->dirty() || instance.centerSection->dirty() || instance.endSection->dirty()) {
const float contentY = (h - instance.startSection->height()) * 0.5f;
instance.startSection->setPosition(padding, contentY);
@@ -316,150 +345,102 @@ void Bar::buildScene(BarInstance& instance, std::uint32_t width, std::uint32_t h
const float endX = w - instance.endSection->width() - padding;
const float endY = (h - instance.endSection->height()) * 0.5f;
instance.endSection->setPosition(endX, endY);
}
void Bar::updateWidgets(BarInstance& instance) {
auto* renderer = instance.surface->renderer();
if (renderer == nullptr) {
return;
}
const auto w = static_cast<float>(instance.surface->width());
const auto h = static_cast<float>(instance.surface->height());
const float padding = instance.barConfig.padding;
auto updateSection = [&](std::vector<std::unique_ptr<Widget>>& widgets, Box* section) {
bool changed = false;
for (auto& widget : widgets) {
widget->update(*renderer);
if (widget->root() != nullptr && widget->root()->dirty()) {
changed = true;
widget->layout(*renderer, w, h);
}
}
if (changed) {
section->layout(*renderer);
}
};
updateSection(instance.startWidgets, instance.startSection);
updateSection(instance.centerWidgets, instance.centerSection);
updateSection(instance.endWidgets, instance.endSection);
// Reposition sections if sizes changed
if (instance.startSection->dirty() || instance.centerSection->dirty() || instance.endSection->dirty()) {
const float contentY = (h - instance.startSection->height()) * 0.5f;
instance.startSection->setPosition(padding, contentY);
const float centerX = (w - instance.centerSection->width()) * 0.5f;
const float centerY = (h - instance.centerSection->height()) * 0.5f;
instance.centerSection->setPosition(centerX, centerY);
const float endX = w - instance.endSection->width() - padding;
const float endY = (h - instance.endSection->height()) * 0.5f;
instance.endSection->setPosition(endX, endY);
}
}
}
Widget* Bar::findWidgetAtPosition(BarInstance& instance, float x, float y) {
if (instance.sceneRoot == nullptr) {
return nullptr;
}
auto* hitNode = Node::hitTest(instance.sceneRoot.get(), x, y);
if (hitNode == nullptr) {
return nullptr;
}
// Walk up from hit node to find a widget root
for (auto* node = hitNode; node != nullptr; node = node->parent()) {
auto it = instance.widgetNodeMap.find(node);
if (it != instance.widgetNodeMap.end()) {
return it->second;
}
}
if (instance.sceneRoot == nullptr) {
return nullptr;
}
auto* hitNode = Node::hitTest(instance.sceneRoot.get(), x, y);
if (hitNode == nullptr) {
return nullptr;
}
// Walk up from hit node to find a widget root
for (auto* node = hitNode; node != nullptr; node = node->parent()) {
auto it = instance.widgetNodeMap.find(node);
if (it != instance.widgetNodeMap.end()) {
return it->second;
}
}
return nullptr;
}
void Bar::onPointerEvent(const PointerEvent& event) {
switch (event.type) {
case PointerEvent::Type::Enter: {
auto it = m_surfaceMap.find(event.surface);
if (it == m_surfaceMap.end()) {
break;
}
m_hoveredInstance = it->second;
m_lastPointerSerial = event.serial;
switch (event.type) {
case PointerEvent::Type::Enter: {
auto it = m_surfaceMap.find(event.surface);
if (it == m_surfaceMap.end()) {
break;
}
m_hoveredInstance = it->second;
m_lastPointerSerial = event.serial;
auto* widget = findWidgetAtPosition(*m_hoveredInstance,
static_cast<float>(event.sx), static_cast<float>(event.sy));
if (widget != m_hoveredWidget) {
if (m_hoveredWidget != nullptr) m_hoveredWidget->onPointerLeave();
m_hoveredWidget = widget;
if (m_hoveredWidget != nullptr) {
float absX = 0.0f, absY = 0.0f;
Node::absolutePosition(m_hoveredWidget->root(), absX, absY);
m_hoveredWidget->onPointerEnter(
static_cast<float>(event.sx) - absX,
static_cast<float>(event.sy) - absY);
auto shape = m_hoveredWidget->cursorShape();
m_wayland->setCursorShape(event.serial, shape != 0 ? shape : 1);
} else {
m_wayland->setCursorShape(event.serial, 1);
}
}
break;
auto* widget = findWidgetAtPosition(*m_hoveredInstance, static_cast<float>(event.sx), static_cast<float>(event.sy));
if (widget != m_hoveredWidget) {
if (m_hoveredWidget != nullptr)
m_hoveredWidget->onPointerLeave();
m_hoveredWidget = widget;
if (m_hoveredWidget != nullptr) {
float absX = 0.0f, absY = 0.0f;
Node::absolutePosition(m_hoveredWidget->root(), absX, absY);
m_hoveredWidget->onPointerEnter(static_cast<float>(event.sx) - absX, static_cast<float>(event.sy) - absY);
auto shape = m_hoveredWidget->cursorShape();
m_wayland->setCursorShape(event.serial, shape != 0 ? shape : 1);
} else {
m_wayland->setCursorShape(event.serial, 1);
}
}
case PointerEvent::Type::Leave: {
if (m_hoveredWidget != nullptr) {
m_hoveredWidget->onPointerLeave();
m_hoveredWidget = nullptr;
}
m_hoveredInstance = nullptr;
break;
break;
}
case PointerEvent::Type::Leave: {
if (m_hoveredWidget != nullptr) {
m_hoveredWidget->onPointerLeave();
m_hoveredWidget = nullptr;
}
case PointerEvent::Type::Motion: {
if (m_hoveredInstance == nullptr) break;
m_hoveredInstance = nullptr;
break;
}
case PointerEvent::Type::Motion: {
if (m_hoveredInstance == nullptr)
break;
auto* widget = findWidgetAtPosition(*m_hoveredInstance,
static_cast<float>(event.sx), static_cast<float>(event.sy));
if (widget != m_hoveredWidget) {
if (m_hoveredWidget != nullptr) m_hoveredWidget->onPointerLeave();
m_hoveredWidget = widget;
if (m_hoveredWidget != nullptr) {
float absX = 0.0f, absY = 0.0f;
Node::absolutePosition(m_hoveredWidget->root(), absX, absY);
m_hoveredWidget->onPointerEnter(
static_cast<float>(event.sx) - absX,
static_cast<float>(event.sy) - absY);
auto shape = m_hoveredWidget->cursorShape();
m_wayland->setCursorShape(m_lastPointerSerial, shape != 0 ? shape : 1);
} else {
m_wayland->setCursorShape(m_lastPointerSerial, 1);
}
} else if (m_hoveredWidget != nullptr) {
float absX = 0.0f, absY = 0.0f;
Node::absolutePosition(m_hoveredWidget->root(), absX, absY);
m_hoveredWidget->onPointerMotion(
static_cast<float>(event.sx) - absX,
static_cast<float>(event.sy) - absY);
}
break;
}
case PointerEvent::Type::Button: {
if (m_hoveredWidget != nullptr) {
bool pressed = (event.state == 1); // WL_POINTER_BUTTON_STATE_PRESSED
m_hoveredWidget->onPointerButton(event.button, pressed);
}
break;
auto* widget = findWidgetAtPosition(*m_hoveredInstance, static_cast<float>(event.sx), static_cast<float>(event.sy));
if (widget != m_hoveredWidget) {
if (m_hoveredWidget != nullptr)
m_hoveredWidget->onPointerLeave();
m_hoveredWidget = widget;
if (m_hoveredWidget != nullptr) {
float absX = 0.0f, absY = 0.0f;
Node::absolutePosition(m_hoveredWidget->root(), absX, absY);
m_hoveredWidget->onPointerEnter(static_cast<float>(event.sx) - absX, static_cast<float>(event.sy) - absY);
auto shape = m_hoveredWidget->cursorShape();
m_wayland->setCursorShape(m_lastPointerSerial, shape != 0 ? shape : 1);
} else {
m_wayland->setCursorShape(m_lastPointerSerial, 1);
}
} else if (m_hoveredWidget != nullptr) {
float absX = 0.0f, absY = 0.0f;
Node::absolutePosition(m_hoveredWidget->root(), absX, absY);
m_hoveredWidget->onPointerMotion(static_cast<float>(event.sx) - absX, static_cast<float>(event.sy) - absY);
}
break;
}
case PointerEvent::Type::Button: {
if (m_hoveredWidget != nullptr) {
bool pressed = (event.state == 1); // WL_POINTER_BUTTON_STATE_PRESSED
m_hoveredWidget->onPointerButton(event.button, pressed);
}
break;
}
}
// Trigger redraw if any widget changed visual state
if (m_hoveredInstance != nullptr &&
m_hoveredInstance->sceneRoot != nullptr &&
m_hoveredInstance->sceneRoot->dirty()) {
m_hoveredInstance->surface->requestRedraw();
}
// Trigger redraw if any widget changed visual state
if (m_hoveredInstance != nullptr && m_hoveredInstance->sceneRoot != nullptr &&
m_hoveredInstance->sceneRoot->dirty()) {
m_hoveredInstance->surface->requestRedraw();
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
#pragma once
#include "shell/BarInstance.hpp"
#include "shell/WidgetFactory.hpp"
#include "shell/BarInstance.h"
#include "shell/WidgetFactory.h"
#include <memory>
#include <unordered_map>
@@ -1,10 +1,10 @@
#pragma once
#include "config/ConfigService.hpp"
#include "render/animation/AnimationManager.hpp"
#include "render/scene/Node.hpp"
#include "shell/Widget.hpp"
#include "wayland/LayerSurface.hpp"
#include "config/ConfigService.h"
#include "render/animation/AnimationManager.h"
#include "render/scene/Node.h"
#include "shell/Widget.h"
#include "wayland/LayerSurface.h"
#include <cstdint>
#include <memory>
+206 -220
View File
@@ -1,13 +1,13 @@
#include "shell/Wallpaper.hpp"
#include "shell/Wallpaper.h"
#include "config/ConfigService.hpp"
#include "config/StateService.hpp"
#include "core/Log.hpp"
#include "render/WallpaperRenderer.hpp"
#include "shell/WallpaperSurface.hpp"
#include "wayland/WaylandConnection.hpp"
#include "config/ConfigService.h"
#include "config/StateService.h"
#include "core/Log.h"
#include "render/WallpaperRenderer.h"
#include "shell/WallpaperSurface.h"
#include "wayland/WaylandConnection.h"
#include "core/Random.hpp"
#include "core/Random.h"
#include <algorithm>
#include <cmath>
@@ -17,36 +17,36 @@ using Random::randomFloat;
namespace {
TransitionParams randomizeParams(WallpaperTransition type, float smoothness, float aspectRatio) {
TransitionParams params;
params.smoothness = smoothness;
params.aspectRatio = aspectRatio;
TransitionParams params;
params.smoothness = smoothness;
params.aspectRatio = aspectRatio;
switch (type) {
case WallpaperTransition::Wipe:
params.direction = std::floor(randomFloat(0.0f, 4.0f));
break;
case WallpaperTransition::Disc:
params.centerX = randomFloat(0.2f, 0.8f);
params.centerY = randomFloat(0.2f, 0.8f);
break;
case WallpaperTransition::Stripes:
params.stripeCount = std::round(randomFloat(4.0f, 24.0f));
params.angle = randomFloat(0.0f, 360.0f);
break;
case WallpaperTransition::Pixelate:
params.maxBlockSize = std::round(randomFloat(32.0f, 112.0f));
break;
case WallpaperTransition::Honeycomb:
params.cellSize = randomFloat(0.02f, 0.06f);
params.centerX = randomFloat(0.2f, 0.8f);
params.centerY = randomFloat(0.2f, 0.8f);
break;
case WallpaperTransition::Fade:
default:
break;
}
switch (type) {
case WallpaperTransition::Wipe:
params.direction = std::floor(randomFloat(0.0f, 4.0f));
break;
case WallpaperTransition::Disc:
params.centerX = randomFloat(0.2f, 0.8f);
params.centerY = randomFloat(0.2f, 0.8f);
break;
case WallpaperTransition::Stripes:
params.stripeCount = std::round(randomFloat(4.0f, 24.0f));
params.angle = randomFloat(0.0f, 360.0f);
break;
case WallpaperTransition::Pixelate:
params.maxBlockSize = std::round(randomFloat(32.0f, 112.0f));
break;
case WallpaperTransition::Honeycomb:
params.cellSize = randomFloat(0.02f, 0.06f);
params.centerX = randomFloat(0.2f, 0.8f);
params.centerY = randomFloat(0.2f, 0.8f);
break;
case WallpaperTransition::Fade:
default:
break;
}
return params;
return params;
}
} // namespace
@@ -54,233 +54,219 @@ TransitionParams randomizeParams(WallpaperTransition type, float smoothness, flo
Wallpaper::Wallpaper() = default;
bool Wallpaper::initialize(WaylandConnection& wayland, ConfigService* config, StateService* state) {
m_wayland = &wayland;
m_config = config;
m_state = state;
m_wayland = &wayland;
m_config = config;
m_state = state;
if (!m_config->config().wallpaper.enabled) {
logInfo("wallpaper: disabled in config");
return true;
}
m_state->setWallpaperChangeCallback([this]() {
onStateChange();
});
syncInstances();
if (!m_config->config().wallpaper.enabled) {
logInfo("wallpaper: disabled in config");
return true;
}
m_state->setWallpaperChangeCallback([this]() { onStateChange(); });
syncInstances();
return true;
}
void Wallpaper::onOutputChange() {
if (m_config == nullptr || !m_config->config().wallpaper.enabled) {
return;
}
syncInstances();
if (m_config == nullptr || !m_config->config().wallpaper.enabled) {
return;
}
syncInstances();
}
void Wallpaper::onStateChange() {
logInfo("wallpaper: state file changed, checking for updates");
logInfo("wallpaper: state file changed, checking for updates");
for (auto& inst : m_instances) {
auto newPath = m_state->getWallpaperPath(inst->connectorName);
if (newPath.empty() || newPath == inst->currentPath) {
continue;
}
logInfo("wallpaper: changing {} → {}", inst->connectorName, newPath);
inst->pendingPath = newPath;
if (inst->surface == nullptr || inst->surface->renderer() == nullptr) {
continue;
}
inst->surface->renderer()->makeCurrent();
loadWallpaper(*inst, newPath);
for (auto& inst : m_instances) {
auto newPath = m_state->getWallpaperPath(inst->connectorName);
if (newPath.empty() || newPath == inst->currentPath) {
continue;
}
logInfo("wallpaper: changing {} → {}", inst->connectorName, newPath);
inst->pendingPath = newPath;
if (inst->surface == nullptr || inst->surface->renderer() == nullptr) {
continue;
}
inst->surface->renderer()->makeCurrent();
loadWallpaper(*inst, newPath);
}
}
bool Wallpaper::hasInstances() const noexcept {
return !m_instances.empty();
}
bool Wallpaper::hasInstances() const noexcept { return !m_instances.empty(); }
void Wallpaper::syncInstances() {
const auto& outputs = m_wayland->outputs();
const auto& outputs = m_wayland->outputs();
// Remove instances for outputs that no longer exist
std::erase_if(m_instances, [&outputs](const auto& inst) {
bool found = std::any_of(outputs.begin(), outputs.end(),
[&inst](const auto& out) { return out.name == inst->outputName; });
if (!found) {
logInfo("wallpaper: removing instance for output {}", inst->outputName);
}
return !found;
});
// Create instances for new outputs
for (const auto& output : outputs) {
bool exists = std::any_of(m_instances.begin(), m_instances.end(),
[&output](const auto& inst) { return inst->outputName == output.name; });
if (!exists) {
createInstance(output);
}
// Remove instances for outputs that no longer exist
std::erase_if(m_instances, [&outputs](const auto& inst) {
bool found =
std::any_of(outputs.begin(), outputs.end(), [&inst](const auto& out) { return out.name == inst->outputName; });
if (!found) {
logInfo("wallpaper: removing instance for output {}", inst->outputName);
}
return !found;
});
// Create instances for new outputs
for (const auto& output : outputs) {
bool exists = std::any_of(m_instances.begin(), m_instances.end(),
[&output](const auto& inst) { return inst->outputName == output.name; });
if (!exists) {
createInstance(output);
}
}
}
void Wallpaper::createInstance(const WaylandOutput& output) {
auto wallpaperPath = m_state->getWallpaperPath(output.connectorName);
logInfo("wallpaper: creating on {} ({}), path={}",
output.connectorName, output.description, wallpaperPath);
auto wallpaperPath = m_state->getWallpaperPath(output.connectorName);
logInfo("wallpaper: creating on {} ({}), path={}", output.connectorName, output.description, wallpaperPath);
auto instance = std::make_unique<WallpaperInstance>();
instance->outputName = output.name;
instance->output = output.output;
instance->scale = output.scale;
instance->connectorName = output.connectorName;
auto instance = std::make_unique<WallpaperInstance>();
instance->outputName = output.name;
instance->output = output.output;
instance->scale = output.scale;
instance->connectorName = output.connectorName;
auto surfaceConfig = LayerSurfaceConfig{
.nameSpace = "noctalia-wallpaper",
.layer = LayerShellLayer::Background,
.anchor = LayerShellAnchor::Top | LayerShellAnchor::Bottom |
LayerShellAnchor::Left | LayerShellAnchor::Right,
.width = 0,
.height = 0,
.exclusiveZone = -1,
};
auto surfaceConfig = LayerSurfaceConfig{
.nameSpace = "noctalia-wallpaper",
.layer = LayerShellLayer::Background,
.anchor = LayerShellAnchor::Top | LayerShellAnchor::Bottom | LayerShellAnchor::Left | LayerShellAnchor::Right,
.width = 0,
.height = 0,
.exclusiveZone = -1,
};
instance->surface = std::make_unique<WallpaperSurface>(*m_wayland, std::move(surfaceConfig));
instance->surface = std::make_unique<WallpaperSurface>(*m_wayland, std::move(surfaceConfig));
auto* inst = instance.get();
instance->surface->setConfigureCallback(
[this, inst, wallpaperPath](std::uint32_t /*width*/, std::uint32_t /*height*/) {
if (inst->currentPath.empty() && !wallpaperPath.empty()) {
inst->surface->renderer()->makeCurrent();
loadWallpaper(*inst, wallpaperPath);
}
});
auto* inst = instance.get();
instance->surface->setConfigureCallback(
[this, inst, wallpaperPath](std::uint32_t /*width*/, std::uint32_t /*height*/) {
if (inst->currentPath.empty() && !wallpaperPath.empty()) {
inst->surface->renderer()->makeCurrent();
loadWallpaper(*inst, wallpaperPath);
}
});
instance->surface->setAnimationManager(&instance->animations);
instance->surface->setAnimationManager(&instance->animations);
// Set an update callback so renderer state is refreshed each frame
instance->surface->setUpdateCallback([this, inst]() {
updateRendererState(*inst);
});
// Set an update callback so renderer state is refreshed each frame
instance->surface->setUpdateCallback([this, inst]() { updateRendererState(*inst); });
if (!instance->surface->initialize(output.output, output.scale)) {
logWarn("wallpaper: failed to initialize surface for output {}", output.name);
return;
}
if (!instance->surface->initialize(output.output, output.scale)) {
logWarn("wallpaper: failed to initialize surface for output {}", output.name);
return;
}
m_instances.push_back(std::move(instance));
m_instances.push_back(std::move(instance));
}
void Wallpaper::loadWallpaper(WallpaperInstance& instance, const std::string& path) {
auto* renderer = dynamic_cast<WallpaperRenderer*>(instance.surface->renderer());
if (renderer == nullptr) {
return;
}
auto* renderer = dynamic_cast<WallpaperRenderer*>(instance.surface->renderer());
if (renderer == nullptr) {
return;
}
auto& texMgr = renderer->textureManager();
auto newTex = texMgr.loadFromFile(path);
if (newTex.id == 0) {
logWarn("wallpaper: failed to load {}", path);
return;
}
auto& texMgr = renderer->textureManager();
auto newTex = texMgr.loadFromFile(path);
if (newTex.id == 0) {
logWarn("wallpaper: failed to load {}", path);
return;
}
if (instance.currentTexture.id == 0) {
// First wallpaper — display immediately, no transition
instance.currentTexture = newTex;
instance.currentPath = path;
instance.pendingPath.clear();
updateRendererState(instance);
instance.surface->requestRedraw();
} else {
// Transition from current to new
instance.nextTexture = newTex;
instance.pendingPath = path;
startTransition(instance);
}
if (instance.currentTexture.id == 0) {
// First wallpaper — display immediately, no transition
instance.currentTexture = newTex;
instance.currentPath = path;
instance.pendingPath.clear();
updateRendererState(instance);
instance.surface->requestRedraw();
} else {
// Transition from current to new
instance.nextTexture = newTex;
instance.pendingPath = path;
startTransition(instance);
}
}
void Wallpaper::startTransition(WallpaperInstance& instance) {
const auto& wpConfig = m_config->config().wallpaper;
const auto& wpConfig = m_config->config().wallpaper;
// Cancel any in-progress transition
if (instance.transitioning) {
instance.animations.cancel(0);
// Commit the pending state
auto* renderer = dynamic_cast<WallpaperRenderer*>(instance.surface->renderer());
if (renderer != nullptr && instance.nextTexture.id != 0) {
if (instance.currentTexture.id != 0) {
renderer->textureManager().unload(instance.currentTexture);
}
instance.currentTexture = instance.nextTexture;
instance.nextTexture = {};
instance.currentPath = instance.pendingPath;
}
instance.transitionProgress = 0.0f;
instance.transitioning = false;
// Cancel any in-progress transition
if (instance.transitioning) {
instance.animations.cancel(0);
// Commit the pending state
auto* renderer = dynamic_cast<WallpaperRenderer*>(instance.surface->renderer());
if (renderer != nullptr && instance.nextTexture.id != 0) {
if (instance.currentTexture.id != 0) {
renderer->textureManager().unload(instance.currentTexture);
}
instance.currentTexture = instance.nextTexture;
instance.nextTexture = {};
instance.currentPath = instance.pendingPath;
}
// Re-load pending into nextTexture if we just committed it above
if (instance.nextTexture.id == 0 && !instance.pendingPath.empty()) {
auto* renderer = dynamic_cast<WallpaperRenderer*>(instance.surface->renderer());
if (renderer == nullptr) return;
auto tex = renderer->textureManager().loadFromFile(instance.pendingPath);
if (tex.id == 0) return;
instance.nextTexture = tex;
}
float aspectRatio = 1.777f;
if (instance.surface->height() > 0) {
aspectRatio = static_cast<float>(instance.surface->width()) /
static_cast<float>(instance.surface->height());
}
instance.transitionParams = randomizeParams(wpConfig.transition, wpConfig.edgeSmoothness, aspectRatio);
instance.transitioning = true;
instance.transitionProgress = 0.0f;
instance.transitioning = false;
}
auto* inst = &instance;
instance.animations.animate(0.0f, 1.0f, wpConfig.transitionDurationMs, Easing::EaseInOutCubic,
[inst](float v) {
inst->transitionProgress = v;
},
[this, inst]() {
// Transition complete — swap textures
auto* renderer = dynamic_cast<WallpaperRenderer*>(inst->surface->renderer());
if (renderer != nullptr && inst->currentTexture.id != 0) {
renderer->textureManager().unload(inst->currentTexture);
}
inst->currentTexture = inst->nextTexture;
inst->nextTexture = {};
inst->currentPath = inst->pendingPath;
inst->pendingPath.clear();
inst->transitionProgress = 0.0f;
inst->transitioning = false;
updateRendererState(*inst);
});
// Re-load pending into nextTexture if we just committed it above
if (instance.nextTexture.id == 0 && !instance.pendingPath.empty()) {
auto* renderer = dynamic_cast<WallpaperRenderer*>(instance.surface->renderer());
if (renderer == nullptr)
return;
auto tex = renderer->textureManager().loadFromFile(instance.pendingPath);
if (tex.id == 0)
return;
instance.nextTexture = tex;
}
updateRendererState(instance);
instance.surface->requestRedraw();
float aspectRatio = 1.777f;
if (instance.surface->height() > 0) {
aspectRatio = static_cast<float>(instance.surface->width()) / static_cast<float>(instance.surface->height());
}
instance.transitionParams = randomizeParams(wpConfig.transition, wpConfig.edgeSmoothness, aspectRatio);
instance.transitioning = true;
instance.transitionProgress = 0.0f;
auto* inst = &instance;
instance.animations.animate(
0.0f, 1.0f, wpConfig.transitionDurationMs, Easing::EaseInOutCubic,
[inst](float v) { inst->transitionProgress = v; },
[this, inst]() {
// Transition complete — swap textures
auto* renderer = dynamic_cast<WallpaperRenderer*>(inst->surface->renderer());
if (renderer != nullptr && inst->currentTexture.id != 0) {
renderer->textureManager().unload(inst->currentTexture);
}
inst->currentTexture = inst->nextTexture;
inst->nextTexture = {};
inst->currentPath = inst->pendingPath;
inst->pendingPath.clear();
inst->transitionProgress = 0.0f;
inst->transitioning = false;
updateRendererState(*inst);
});
updateRendererState(instance);
instance.surface->requestRedraw();
}
void Wallpaper::updateRendererState(WallpaperInstance& instance) {
auto* renderer = dynamic_cast<WallpaperRenderer*>(instance.surface->renderer());
if (renderer == nullptr) {
return;
}
auto* renderer = dynamic_cast<WallpaperRenderer*>(instance.surface->renderer());
if (renderer == nullptr) {
return;
}
const auto& wpConfig = m_config->config().wallpaper;
const auto& wpConfig = m_config->config().wallpaper;
renderer->setTransitionState(
instance.currentTexture.id,
instance.nextTexture.id,
static_cast<float>(instance.currentTexture.width),
static_cast<float>(instance.currentTexture.height),
static_cast<float>(instance.nextTexture.width),
static_cast<float>(instance.nextTexture.height),
instance.transitionProgress,
wpConfig.transition,
wpConfig.fillMode,
instance.transitionParams);
renderer->setTransitionState(
instance.currentTexture.id, instance.nextTexture.id, static_cast<float>(instance.currentTexture.width),
static_cast<float>(instance.currentTexture.height), static_cast<float>(instance.nextTexture.width),
static_cast<float>(instance.nextTexture.height), instance.transitionProgress, wpConfig.transition,
wpConfig.fillMode, instance.transitionParams);
}
@@ -1,6 +1,6 @@
#pragma once
#include "shell/WallpaperInstance.hpp"
#include "shell/WallpaperInstance.h"
#include <memory>
#include <vector>
@@ -1,9 +1,9 @@
#pragma once
#include "render/animation/AnimationManager.hpp"
#include "render/core/TextureManager.hpp"
#include "render/programs/WallpaperProgram.hpp"
#include "shell/WallpaperSurface.hpp"
#include "render/animation/AnimationManager.h"
#include "render/core/TextureManager.h"
#include "render/programs/WallpaperProgram.h"
#include "shell/WallpaperSurface.h"
#include <cstdint>
#include <memory>
+3 -5
View File
@@ -1,7 +1,5 @@
#include "shell/WallpaperSurface.hpp"
#include "shell/WallpaperSurface.h"
#include "render/WallpaperRenderer.hpp"
#include "render/WallpaperRenderer.h"
std::unique_ptr<Renderer> WallpaperSurface::createRenderer() {
return std::make_unique<WallpaperRenderer>();
}
std::unique_ptr<Renderer> WallpaperSurface::createRenderer() { return std::make_unique<WallpaperRenderer>(); }
@@ -1,6 +1,6 @@
#pragma once
#include "wayland/LayerSurface.hpp"
#include "wayland/LayerSurface.h"
#include <memory>
+10 -18
View File
@@ -1,4 +1,4 @@
#include "shell/Widget.hpp"
#include "shell/Widget.h"
void Widget::update(Renderer& /*renderer*/) {}
@@ -8,29 +8,21 @@ void Widget::onPointerMotion(float /*localX*/, float /*localY*/) {}
bool Widget::onPointerButton(std::uint32_t /*button*/, bool /*pressed*/) { return false; }
std::uint32_t Widget::cursorShape() const { return 0; }
float Widget::width() const noexcept {
return m_rootPtr ? m_rootPtr->width() : 0.0f;
}
float Widget::width() const noexcept { return m_rootPtr ? m_rootPtr->width() : 0.0f; }
float Widget::height() const noexcept {
return m_rootPtr ? m_rootPtr->height() : 0.0f;
}
float Widget::height() const noexcept { return m_rootPtr ? m_rootPtr->height() : 0.0f; }
std::unique_ptr<Node> Widget::releaseRoot() {
m_rootPtr = m_root.get();
return std::move(m_root);
m_rootPtr = m_root.get();
return std::move(m_root);
}
void Widget::setAnimationManager(AnimationManager* mgr) noexcept {
m_animations = mgr;
}
void Widget::setAnimationManager(AnimationManager* mgr) noexcept { m_animations = mgr; }
void Widget::setRedrawCallback(RedrawCallback callback) {
m_redrawCallback = std::move(callback);
}
void Widget::setRedrawCallback(RedrawCallback callback) { m_redrawCallback = std::move(callback); }
void Widget::requestRedraw() {
if (m_redrawCallback) {
m_redrawCallback();
}
if (m_redrawCallback) {
m_redrawCallback();
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include "render/scene/Node.hpp"
#include "render/scene/Node.h"
#include <functional>
#include <memory>
+25 -27
View File
@@ -1,38 +1,36 @@
#include "shell/WidgetFactory.hpp"
#include "shell/WidgetFactory.h"
#include "config/ConfigService.hpp"
#include "core/Log.hpp"
#include "shell/widgets/ClockWidget.hpp"
#include "shell/widgets/NotificationWidget.hpp"
#include "shell/widgets/SpacerWidget.hpp"
#include "shell/widgets/WorkspacesWidget.hpp"
#include "config/ConfigService.h"
#include "core/Log.h"
#include "shell/widgets/ClockWidget.h"
#include "shell/widgets/NotificationWidget.h"
#include "shell/widgets/SpacerWidget.h"
#include "shell/widgets/WorkspacesWidget.h"
WidgetFactory::WidgetFactory(WaylandConnection& wayland, TimeService* time, const Config& config)
: m_wayland(wayland)
, m_time(time)
, m_config(config) {}
: m_wayland(wayland), m_time(time), m_config(config) {}
std::unique_ptr<Widget> WidgetFactory::create(const std::string& name, wl_output* output) const {
if (name == "clock") {
if (m_time == nullptr) {
logWarn("widget factory: clock requires TimeService");
return nullptr;
}
return std::make_unique<ClockWidget>(*m_time, m_config.clock.format);
if (name == "clock") {
if (m_time == nullptr) {
logWarn("widget factory: clock requires TimeService");
return nullptr;
}
return std::make_unique<ClockWidget>(*m_time, m_config.clock.format);
}
if (name == "workspaces") {
return std::make_unique<WorkspacesWidget>(m_wayland, output);
}
if (name == "workspaces") {
return std::make_unique<WorkspacesWidget>(m_wayland, output);
}
if (name == "notifications") {
return std::make_unique<NotificationWidget>();
}
if (name == "notifications") {
return std::make_unique<NotificationWidget>();
}
if (name == "spacer") {
return std::make_unique<SpacerWidget>(8.0f);
}
if (name == "spacer") {
return std::make_unique<SpacerWidget>(8.0f);
}
logWarn("widget factory: unknown widget \"{}\"", name);
return nullptr;
logWarn("widget factory: unknown widget \"{}\"", name);
return nullptr;
}
@@ -1,6 +1,6 @@
#pragma once
#include "shell/Widget.hpp"
#include "shell/Widget.h"
#include <memory>
#include <string>
+17 -18
View File
@@ -1,31 +1,30 @@
#include "shell/widgets/ClockWidget.hpp"
#include "shell/widgets/ClockWidget.h"
#include "render/core/Renderer.hpp"
#include "time/TimeService.hpp"
#include "ui/controls/Label.hpp"
#include "render/core/Renderer.h"
#include "time/TimeService.h"
#include "ui/controls/Label.h"
ClockWidget::ClockWidget(const TimeService& timeService, std::string format)
: m_time(timeService)
, m_format(std::move(format)) {}
: m_time(timeService), m_format(std::move(format)) {}
void ClockWidget::create(Renderer& renderer) {
auto label = std::make_unique<Label>();
label->setCaptionStyle();
m_label = label.get();
m_root = std::move(label);
update(renderer);
auto label = std::make_unique<Label>();
label->setCaptionStyle();
m_label = label.get();
m_root = std::move(label);
update(renderer);
}
void ClockWidget::layout(Renderer& renderer, float /*containerWidth*/, float /*containerHeight*/) {
m_label->measure(renderer);
m_label->measure(renderer);
}
void ClockWidget::update(Renderer& renderer) {
auto text = m_time.format(m_format.c_str());
auto text = m_time.format(m_format.c_str());
if (text != m_lastText) {
m_lastText = std::move(text);
m_label->setText(m_lastText);
m_label->measure(renderer);
}
if (text != m_lastText) {
m_lastText = std::move(text);
m_label->setText(m_lastText);
m_label->measure(renderer);
}
}
@@ -1,6 +1,6 @@
#pragma once
#include "shell/Widget.hpp"
#include "shell/Widget.h"
#include <string>
+8 -8
View File
@@ -1,15 +1,15 @@
#include "shell/widgets/NotificationWidget.hpp"
#include "shell/widgets/NotificationWidget.h"
#include "ui/controls/Icon.hpp"
#include "ui/controls/Icon.h"
void NotificationWidget::create(Renderer& renderer) {
auto icon = std::make_unique<Icon>();
icon->setIcon("bell");
m_icon = icon.get();
m_root = std::move(icon);
m_icon->measure(renderer);
auto icon = std::make_unique<Icon>();
icon->setIcon("bell");
m_icon = icon.get();
m_root = std::move(icon);
m_icon->measure(renderer);
}
void NotificationWidget::layout(Renderer& renderer, float /*containerWidth*/, float /*containerHeight*/) {
m_icon->measure(renderer);
m_icon->measure(renderer);
}
@@ -1,6 +1,6 @@
#pragma once
#include "shell/Widget.hpp"
#include "shell/Widget.h"
class Icon;
+7 -8
View File
@@ -1,16 +1,15 @@
#include "shell/widgets/SpacerWidget.hpp"
#include "shell/widgets/SpacerWidget.h"
#include "ui/controls/Box.hpp"
#include "ui/controls/Box.h"
SpacerWidget::SpacerWidget(float width)
: m_fixedWidth(width) {}
SpacerWidget::SpacerWidget(float width) : m_fixedWidth(width) {}
void SpacerWidget::create(Renderer& /*renderer*/) {
auto box = std::make_unique<Box>();
box->setSize(m_fixedWidth, 0.0f);
m_root = std::unique_ptr<Node>(box.release());
auto box = std::make_unique<Box>();
box->setSize(m_fixedWidth, 0.0f);
m_root = std::unique_ptr<Node>(box.release());
}
void SpacerWidget::layout(Renderer& /*renderer*/, float /*containerWidth*/, float containerHeight) {
m_root->setSize(m_fixedWidth, containerHeight);
m_root->setSize(m_fixedWidth, containerHeight);
}
@@ -1,6 +1,6 @@
#pragma once
#include "shell/Widget.hpp"
#include "shell/Widget.h"
class SpacerWidget : public Widget {
public:
+83 -92
View File
@@ -1,130 +1,121 @@
#include "shell/widgets/WorkspacesWidget.hpp"
#include "shell/widgets/WorkspacesWidget.h"
#include "core/Log.hpp"
#include "render/core/Renderer.hpp"
#include "render/scene/Node.hpp"
#include "ui/controls/Box.hpp"
#include "ui/controls/Chip.hpp"
#include "core/Log.h"
#include "render/core/Renderer.h"
#include "render/scene/Node.h"
#include "ui/controls/Box.h"
#include "ui/controls/Chip.h"
#include "cursor-shape-v1-client-protocol.h"
#include <linux/input-event-codes.h>
WorkspacesWidget::WorkspacesWidget(WaylandConnection& connection, wl_output* output)
: m_connection(connection)
, m_output(output) {}
: m_connection(connection), m_output(output) {}
void WorkspacesWidget::create(Renderer& renderer) {
auto container = std::make_unique<Box>();
container->setRowLayout();
m_container = container.get();
m_root = std::move(container);
auto container = std::make_unique<Box>();
container->setRowLayout();
m_container = container.get();
m_root = std::move(container);
rebuild(renderer);
rebuild(renderer);
}
void WorkspacesWidget::layout(Renderer& renderer, float /*containerWidth*/, float /*containerHeight*/) {
m_container->layout(renderer);
m_container->layout(renderer);
}
void WorkspacesWidget::update(Renderer& renderer) {
auto current = m_connection.workspaces(m_output);
if (m_cachedState.empty() && current.empty()) {
return;
auto current = m_connection.workspaces(m_output);
if (m_cachedState.empty() && current.empty()) {
return;
}
bool changed = current.size() != m_cachedState.size();
if (!changed) {
for (std::size_t i = 0; i < current.size(); ++i) {
if (current[i].name != m_cachedState[i].name || current[i].active != m_cachedState[i].active) {
changed = true;
break;
}
}
}
if (changed) {
logDebug("workspaces widget: state changed, rebuilding ({} workspaces)", current.size());
m_cachedState.clear();
m_cachedState.reserve(current.size());
for (const auto& ws : current) {
m_cachedState.push_back(
Workspace{.id = ws.id, .name = ws.name, .coordinates = ws.coordinates, .active = ws.active});
}
bool changed = current.size() != m_cachedState.size();
if (!changed) {
for (std::size_t i = 0; i < current.size(); ++i) {
if (current[i].name != m_cachedState[i].name ||
current[i].active != m_cachedState[i].active) {
changed = true;
break;
}
}
}
if (changed) {
logDebug("workspaces widget: state changed, rebuilding ({} workspaces)", current.size());
m_cachedState.clear();
m_cachedState.reserve(current.size());
for (const auto& ws : current) {
m_cachedState.push_back(Workspace{.id = ws.id, .name = ws.name, .coordinates = ws.coordinates, .active = ws.active});
}
rebuild(renderer);
}
rebuild(renderer);
}
}
void WorkspacesWidget::onPointerEnter(float localX, float localY) {
m_hoveredPill = pillIndexAt(localX, localY);
}
void WorkspacesWidget::onPointerEnter(float localX, float localY) { m_hoveredPill = pillIndexAt(localX, localY); }
void WorkspacesWidget::onPointerLeave() {
m_hoveredPill = -1;
}
void WorkspacesWidget::onPointerLeave() { m_hoveredPill = -1; }
void WorkspacesWidget::onPointerMotion(float localX, float localY) {
m_hoveredPill = pillIndexAt(localX, localY);
}
void WorkspacesWidget::onPointerMotion(float localX, float localY) { m_hoveredPill = pillIndexAt(localX, localY); }
bool WorkspacesWidget::onPointerButton(std::uint32_t button, bool pressed) {
if (button == BTN_LEFT && pressed && m_hoveredPill >= 0 &&
static_cast<std::size_t>(m_hoveredPill) < m_workspaceIds.size()) {
m_connection.activateWorkspace(m_workspaceIds[static_cast<std::size_t>(m_hoveredPill)]);
return true;
}
return false;
if (button == BTN_LEFT && pressed && m_hoveredPill >= 0 &&
static_cast<std::size_t>(m_hoveredPill) < m_workspaceIds.size()) {
m_connection.activateWorkspace(m_workspaceIds[static_cast<std::size_t>(m_hoveredPill)]);
return true;
}
return false;
}
std::uint32_t WorkspacesWidget::cursorShape() const {
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER;
}
std::uint32_t WorkspacesWidget::cursorShape() const { return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER; }
int WorkspacesWidget::pillIndexAt(float localX, float localY) const {
const auto& kids = m_container->children();
// Skip background rect (child 0 if Box has one)
std::size_t start = 0;
if (!kids.empty() && kids[0]->type() == NodeType::Rect) {
start = 1;
const auto& kids = m_container->children();
// Skip background rect (child 0 if Box has one)
std::size_t start = 0;
if (!kids.empty() && kids[0]->type() == NodeType::Rect) {
start = 1;
}
for (std::size_t i = start; i < kids.size(); ++i) {
const auto* child = kids[i].get();
float absX = 0.0f, absY = 0.0f;
Node::absolutePosition(child, absX, absY);
float containerAbsX = 0.0f, containerAbsY = 0.0f;
Node::absolutePosition(m_container, containerAbsX, containerAbsY);
float childLocalX = absX - containerAbsX;
float childLocalY = absY - containerAbsY;
if (localX >= childLocalX && localX < childLocalX + child->width() && localY >= childLocalY &&
localY < childLocalY + child->height()) {
return static_cast<int>(i - start);
}
}
for (std::size_t i = start; i < kids.size(); ++i) {
const auto* child = kids[i].get();
float absX = 0.0f, absY = 0.0f;
Node::absolutePosition(child, absX, absY);
float containerAbsX = 0.0f, containerAbsY = 0.0f;
Node::absolutePosition(m_container, containerAbsX, containerAbsY);
float childLocalX = absX - containerAbsX;
float childLocalY = absY - containerAbsY;
if (localX >= childLocalX && localX < childLocalX + child->width() &&
localY >= childLocalY && localY < childLocalY + child->height()) {
return static_cast<int>(i - start);
}
}
return -1;
return -1;
}
void WorkspacesWidget::rebuild(Renderer& renderer) {
while (!m_container->children().empty()) {
m_container->removeChild(m_container->children().back().get());
}
while (!m_container->children().empty()) {
m_container->removeChild(m_container->children().back().get());
}
m_workspaceIds.clear();
m_hoveredPill = -1;
m_workspaceIds.clear();
m_hoveredPill = -1;
auto workspaces = m_connection.workspaces(m_output);
auto workspaces = m_connection.workspaces(m_output);
for (const auto& ws : workspaces) {
m_workspaceIds.push_back(ws.id);
for (const auto& ws : workspaces) {
m_workspaceIds.push_back(ws.id);
auto pill = std::make_unique<Chip>();
pill->setText(ws.name);
pill->setWorkspaceActive(ws.active);
pill->layout(renderer);
m_container->addChild(std::move(pill));
}
auto pill = std::make_unique<Chip>();
pill->setText(ws.name);
pill->setWorkspaceActive(ws.active);
pill->layout(renderer);
m_container->addChild(std::move(pill));
}
}
@@ -1,7 +1,7 @@
#pragma once
#include "shell/Widget.hpp"
#include "wayland/WaylandConnection.hpp"
#include "shell/Widget.h"
#include "wayland/WaylandConnection.h"
#include <string>
#include <vector>
+217 -239
View File
@@ -1,6 +1,6 @@
#include "system/SystemMonitorService.hpp"
#include "system/SystemMonitorService.h"
#include "core/Log.hpp"
#include "core/Log.h"
#include <algorithm>
#include <chrono>
@@ -13,312 +13,290 @@
namespace {
std::optional<std::string> readSmallTextFile(const std::filesystem::path& path) {
std::ifstream file{path};
if (!file.is_open()) {
return std::nullopt;
}
std::ifstream file{path};
if (!file.is_open()) {
return std::nullopt;
}
std::string text;
std::getline(file, text);
if (text.empty()) {
return std::nullopt;
}
std::string text;
std::getline(file, text);
if (text.empty()) {
return std::nullopt;
}
while (!text.empty() && (text.back() == '\n' || text.back() == '\r' || text.back() == ' ' || text.back() == '\t')) {
text.pop_back();
}
return text;
while (!text.empty() && (text.back() == '\n' || text.back() == '\r' || text.back() == ' ' || text.back() == '\t')) {
text.pop_back();
}
return text;
}
std::optional<double> readTempInputCelsius(const std::filesystem::path& path) {
std::ifstream file{path};
if (!file.is_open()) {
return std::nullopt;
}
std::ifstream file{path};
if (!file.is_open()) {
return std::nullopt;
}
long long raw = 0;
file >> raw;
if (file.fail() || raw <= 0) {
return std::nullopt;
}
long long raw = 0;
file >> raw;
if (file.fail() || raw <= 0) {
return std::nullopt;
}
// Most Linux temp files are millidegrees Celsius.
if (raw >= 1000) {
return static_cast<double>(raw) / 1000.0;
}
return static_cast<double>(raw);
// Most Linux temp files are millidegrees Celsius.
if (raw >= 1000) {
return static_cast<double>(raw) / 1000.0;
}
return static_cast<double>(raw);
}
std::string toLower(std::string value) {
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) {
return static_cast<char>(std::tolower(c));
});
return value;
std::transform(value.begin(), value.end(), value.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return value;
}
int scoreHwmonSensor(const std::string& hwmon_name, const std::string& label) {
int score = 0;
const std::string name = toLower(hwmon_name);
const std::string lbl = toLower(label);
int score = 0;
const std::string name = toLower(hwmon_name);
const std::string lbl = toLower(label);
if (name.find("coretemp") != std::string::npos ||
name.find("k10temp") != std::string::npos ||
name.find("zenpower") != std::string::npos ||
name.find("cpu") != std::string::npos) {
score += 20;
}
if (name.find("coretemp") != std::string::npos || name.find("k10temp") != std::string::npos ||
name.find("zenpower") != std::string::npos || name.find("cpu") != std::string::npos) {
score += 20;
}
if (lbl.find("package") != std::string::npos ||
lbl.find("tctl") != std::string::npos ||
lbl.find("tdie") != std::string::npos ||
lbl.find("cpu") != std::string::npos) {
score += 30;
}
if (lbl.find("package") != std::string::npos || lbl.find("tctl") != std::string::npos ||
lbl.find("tdie") != std::string::npos || lbl.find("cpu") != std::string::npos) {
score += 30;
}
return score;
return score;
}
bool isCpuThermalZoneType(const std::string& type) {
const std::string t = toLower(type);
return t.find("x86_pkg_temp") != std::string::npos ||
t.find("cpu") != std::string::npos ||
t.find("soc") != std::string::npos ||
t.find("package") != std::string::npos;
const std::string t = toLower(type);
return t.find("x86_pkg_temp") != std::string::npos || t.find("cpu") != std::string::npos ||
t.find("soc") != std::string::npos || t.find("package") != std::string::npos;
}
} // namespace
SystemMonitorService::SystemMonitorService() {
start();
}
SystemMonitorService::SystemMonitorService() { start(); }
SystemMonitorService::~SystemMonitorService() {
stop();
}
SystemMonitorService::~SystemMonitorService() { stop(); }
bool SystemMonitorService::isRunning() const noexcept {
return m_running.load();
}
bool SystemMonitorService::isRunning() const noexcept { return m_running.load(); }
SystemStats SystemMonitorService::latest() const {
std::lock_guard lock{m_stats_mutex};
return m_latest;
std::lock_guard lock{m_stats_mutex};
return m_latest;
}
void SystemMonitorService::start() {
if (m_running.load()) {
return;
}
if (m_running.load()) {
return;
}
m_running = true;
m_thread = std::thread([this]() {
samplingLoop();
});
m_running = true;
m_thread = std::thread([this]() { samplingLoop(); });
}
void SystemMonitorService::stop() {
m_running = false;
if (m_thread.joinable()) {
m_thread.join();
}
m_running = false;
if (m_thread.joinable()) {
m_thread.join();
}
}
void SystemMonitorService::samplingLoop() {
auto prev_cpu = readCpuTotals();
auto prev_cpu = readCpuTotals();
while (m_running.load()) {
SystemStats next{};
while (m_running.load()) {
SystemStats next{};
const auto current_cpu = readCpuTotals();
if (prev_cpu.has_value() && current_cpu.has_value()) {
const std::uint64_t total_delta = current_cpu->total - prev_cpu->total;
const std::uint64_t idle_delta = current_cpu->idle - prev_cpu->idle;
if (total_delta > 0) {
next.cpu_usage_percent = 100.0 * (1.0 - static_cast<double>(idle_delta) / static_cast<double>(total_delta));
}
}
if (current_cpu.has_value()) {
prev_cpu = current_cpu;
}
const auto ram_kb = readRamKb();
if (ram_kb.has_value()) {
const std::uint64_t total_kb = ram_kb->first;
const std::uint64_t used_kb = ram_kb->second;
next.ram_total_mb = total_kb / 1024;
next.ram_used_mb = used_kb / 1024;
if (total_kb > 0) {
next.ram_usage_percent = 100.0 * static_cast<double>(used_kb) / static_cast<double>(total_kb);
}
}
next.cpu_temp_c = readCpuTempCelsius();
{
std::lock_guard lock{m_stats_mutex};
m_latest = next;
}
if (next.cpu_temp_c.has_value()) {
logDebug("system monitor cpu={:.1f}% ram={:.1f}% ({}/{} MB) temp={:.1f}C",
next.cpu_usage_percent,
next.ram_usage_percent,
next.ram_used_mb,
next.ram_total_mb,
*next.cpu_temp_c);
} else {
logDebug("system monitor cpu={:.1f}% ram={:.1f}% ({}/{} MB) temp=n/a",
next.cpu_usage_percent,
next.ram_usage_percent,
next.ram_used_mb,
next.ram_total_mb);
}
std::this_thread::sleep_for(std::chrono::seconds(2));
const auto current_cpu = readCpuTotals();
if (prev_cpu.has_value() && current_cpu.has_value()) {
const std::uint64_t total_delta = current_cpu->total - prev_cpu->total;
const std::uint64_t idle_delta = current_cpu->idle - prev_cpu->idle;
if (total_delta > 0) {
next.cpu_usage_percent = 100.0 * (1.0 - static_cast<double>(idle_delta) / static_cast<double>(total_delta));
}
}
if (current_cpu.has_value()) {
prev_cpu = current_cpu;
}
const auto ram_kb = readRamKb();
if (ram_kb.has_value()) {
const std::uint64_t total_kb = ram_kb->first;
const std::uint64_t used_kb = ram_kb->second;
next.ram_total_mb = total_kb / 1024;
next.ram_used_mb = used_kb / 1024;
if (total_kb > 0) {
next.ram_usage_percent = 100.0 * static_cast<double>(used_kb) / static_cast<double>(total_kb);
}
}
next.cpu_temp_c = readCpuTempCelsius();
{
std::lock_guard lock{m_stats_mutex};
m_latest = next;
}
if (next.cpu_temp_c.has_value()) {
logDebug("system monitor cpu={:.1f}% ram={:.1f}% ({}/{} MB) temp={:.1f}C", next.cpu_usage_percent,
next.ram_usage_percent, next.ram_used_mb, next.ram_total_mb, *next.cpu_temp_c);
} else {
logDebug("system monitor cpu={:.1f}% ram={:.1f}% ({}/{} MB) temp=n/a", next.cpu_usage_percent,
next.ram_usage_percent, next.ram_used_mb, next.ram_total_mb);
}
std::this_thread::sleep_for(std::chrono::seconds(2));
}
}
std::optional<SystemMonitorService::CpuTotals> SystemMonitorService::readCpuTotals() {
std::ifstream file{"/proc/stat"};
if (!file.is_open()) {
return std::nullopt;
}
std::ifstream file{"/proc/stat"};
if (!file.is_open()) {
return std::nullopt;
}
std::string line;
if (!std::getline(file, line)) {
return std::nullopt;
}
std::string line;
if (!std::getline(file, line)) {
return std::nullopt;
}
std::istringstream iss{line};
std::string cpu_label;
std::uint64_t user = 0;
std::uint64_t nice = 0;
std::uint64_t system = 0;
std::uint64_t idle = 0;
std::uint64_t iowait = 0;
std::uint64_t irq = 0;
std::uint64_t softirq = 0;
std::uint64_t steal = 0;
std::istringstream iss{line};
std::string cpu_label;
std::uint64_t user = 0;
std::uint64_t nice = 0;
std::uint64_t system = 0;
std::uint64_t idle = 0;
std::uint64_t iowait = 0;
std::uint64_t irq = 0;
std::uint64_t softirq = 0;
std::uint64_t steal = 0;
iss >> cpu_label >> user >> nice >> system >> idle >> iowait >> irq >> softirq >> steal;
if (cpu_label != "cpu") {
return std::nullopt;
}
iss >> cpu_label >> user >> nice >> system >> idle >> iowait >> irq >> softirq >> steal;
if (cpu_label != "cpu") {
return std::nullopt;
}
CpuTotals totals{};
totals.idle = idle + iowait;
totals.total = user + nice + system + idle + iowait + irq + softirq + steal;
return totals;
CpuTotals totals{};
totals.idle = idle + iowait;
totals.total = user + nice + system + idle + iowait + irq + softirq + steal;
return totals;
}
std::optional<std::pair<std::uint64_t, std::uint64_t>> SystemMonitorService::readRamKb() {
std::ifstream file{"/proc/meminfo"};
if (!file.is_open()) {
return std::nullopt;
std::ifstream file{"/proc/meminfo"};
if (!file.is_open()) {
return std::nullopt;
}
std::string key;
std::uint64_t value_kb = 0;
std::string unit;
std::uint64_t total_kb = 0;
std::uint64_t available_kb = 0;
while (file >> key >> value_kb >> unit) {
if (key == "MemTotal:") {
total_kb = value_kb;
} else if (key == "MemAvailable:") {
available_kb = value_kb;
}
std::string key;
std::uint64_t value_kb = 0;
std::string unit;
std::uint64_t total_kb = 0;
std::uint64_t available_kb = 0;
while (file >> key >> value_kb >> unit) {
if (key == "MemTotal:") {
total_kb = value_kb;
} else if (key == "MemAvailable:") {
available_kb = value_kb;
}
if (total_kb > 0 && available_kb > 0) {
break;
}
if (total_kb > 0 && available_kb > 0) {
break;
}
}
if (total_kb == 0 || available_kb == 0 || available_kb > total_kb) {
return std::nullopt;
}
if (total_kb == 0 || available_kb == 0 || available_kb > total_kb) {
return std::nullopt;
}
const std::uint64_t used_kb = total_kb - available_kb;
return std::make_pair(total_kb, used_kb);
const std::uint64_t used_kb = total_kb - available_kb;
return std::make_pair(total_kb, used_kb);
}
std::optional<double> SystemMonitorService::readCpuTempCelsius() {
namespace fs = std::filesystem;
namespace fs = std::filesystem;
const fs::path hwmon_root{"/sys/class/hwmon"};
if (fs::exists(hwmon_root) && fs::is_directory(hwmon_root)) {
int best_score = -1;
std::optional<double> best_temp;
const fs::path hwmon_root{"/sys/class/hwmon"};
if (fs::exists(hwmon_root) && fs::is_directory(hwmon_root)) {
int best_score = -1;
std::optional<double> best_temp;
for (const auto& hwmon_entry : fs::directory_iterator{hwmon_root}) {
if (!hwmon_entry.is_directory()) {
continue;
}
for (const auto& hwmon_entry : fs::directory_iterator{hwmon_root}) {
if (!hwmon_entry.is_directory()) {
continue;
}
const std::string hwmon_name = readSmallTextFile(hwmon_entry.path() / "name").value_or("");
for (const auto& file_entry : fs::directory_iterator{hwmon_entry.path()}) {
if (!file_entry.is_regular_file()) {
continue;
}
const std::string file_name = file_entry.path().filename().string();
if (!file_name.starts_with("temp") || !file_name.ends_with("_input")) {
continue;
}
const std::string base = file_name.substr(0, file_name.size() - 6);
const std::string label = readSmallTextFile(hwmon_entry.path() / (base + "_label")).value_or("");
const auto temp_c = readTempInputCelsius(file_entry.path());
if (!temp_c.has_value()) {
continue;
}
const int score = scoreHwmonSensor(hwmon_name, label);
if (score > best_score) {
best_score = score;
best_temp = *temp_c;
}
}
const std::string hwmon_name = readSmallTextFile(hwmon_entry.path() / "name").value_or("");
for (const auto& file_entry : fs::directory_iterator{hwmon_entry.path()}) {
if (!file_entry.is_regular_file()) {
continue;
}
if (best_temp.has_value()) {
return best_temp;
}
}
const fs::path thermal_root{"/sys/class/thermal"};
if (!fs::exists(thermal_root) || !fs::is_directory(thermal_root)) {
return std::nullopt;
}
std::optional<double> fallback_temp;
for (const auto& entry : fs::directory_iterator{thermal_root}) {
if (!entry.is_directory()) {
continue;
const std::string file_name = file_entry.path().filename().string();
if (!file_name.starts_with("temp") || !file_name.ends_with("_input")) {
continue;
}
const auto zone_name = entry.path().filename().string();
if (!zone_name.starts_with("thermal_zone")) {
continue;
}
const std::string zone_type = readSmallTextFile(entry.path() / "type").value_or("");
const fs::path temp_path = entry.path() / "temp";
const auto temp_c = readTempInputCelsius(temp_path);
const std::string base = file_name.substr(0, file_name.size() - 6);
const std::string label = readSmallTextFile(hwmon_entry.path() / (base + "_label")).value_or("");
const auto temp_c = readTempInputCelsius(file_entry.path());
if (!temp_c.has_value()) {
continue;
continue;
}
if (isCpuThermalZoneType(zone_type)) {
return temp_c;
}
if (!fallback_temp.has_value()) {
fallback_temp = temp_c;
const int score = scoreHwmonSensor(hwmon_name, label);
if (score > best_score) {
best_score = score;
best_temp = *temp_c;
}
}
}
return fallback_temp;
if (best_temp.has_value()) {
return best_temp;
}
}
const fs::path thermal_root{"/sys/class/thermal"};
if (!fs::exists(thermal_root) || !fs::is_directory(thermal_root)) {
return std::nullopt;
}
std::optional<double> fallback_temp;
for (const auto& entry : fs::directory_iterator{thermal_root}) {
if (!entry.is_directory()) {
continue;
}
const auto zone_name = entry.path().filename().string();
if (!zone_name.starts_with("thermal_zone")) {
continue;
}
const std::string zone_type = readSmallTextFile(entry.path() / "type").value_or("");
const fs::path temp_path = entry.path() / "temp";
const auto temp_c = readTempInputCelsius(temp_path);
if (!temp_c.has_value()) {
continue;
}
if (isCpuThermalZoneType(zone_type)) {
return temp_c;
}
if (!fallback_temp.has_value()) {
fallback_temp = temp_c;
}
}
return fallback_temp;
}
@@ -1,7 +1,7 @@
#pragma once
#include "app/PollSource.hpp"
#include "time/TimeService.hpp"
#include "app/PollSource.h"
#include "time/TimeService.h"
class TimePollSource final : public PollSource {
public:
+16 -18
View File
@@ -1,32 +1,30 @@
#include "time/TimeService.hpp"
#include "time/TimeService.h"
#include <format>
void TimeService::setTickSecondCallback(TickCallback callback) {
m_secondCallback = std::move(callback);
}
void TimeService::setTickSecondCallback(TickCallback callback) { m_secondCallback = std::move(callback); }
int TimeService::pollTimeoutMs() const {
using namespace std::chrono;
const auto now = system_clock::now();
const auto ms = duration_cast<milliseconds>(now.time_since_epoch()).count() % 1000;
return static_cast<int>(1000 - ms);
using namespace std::chrono;
const auto now = system_clock::now();
const auto ms = duration_cast<milliseconds>(now.time_since_epoch()).count() % 1000;
return static_cast<int>(1000 - ms);
}
void TimeService::tick() {
using namespace std::chrono;
m_now = system_clock::now();
const auto floored = floor<seconds>(m_now);
using namespace std::chrono;
m_now = system_clock::now();
const auto floored = floor<seconds>(m_now);
if (floored != m_nowSeconds) {
m_nowSeconds = floored;
if (m_secondCallback) {
m_secondCallback();
}
if (floored != m_nowSeconds) {
m_nowSeconds = floored;
if (m_secondCallback) {
m_secondCallback();
}
}
}
std::string TimeService::format(const char* fmt) const {
const auto local = std::chrono::current_zone()->to_local(m_nowSeconds);
return std::vformat(fmt, std::make_format_args(local));
const auto local = std::chrono::current_zone()->to_local(m_nowSeconds);
return std::vformat(fmt, std::make_format_args(local));
}
+130 -132
View File
@@ -1,11 +1,11 @@
#include "ui/controls/Box.hpp"
#include "ui/controls/Box.h"
#include "render/core/Renderer.hpp"
#include "render/programs/RoundedRectProgram.hpp"
#include "render/scene/RectNode.hpp"
#include "ui/controls/Label.hpp"
#include "ui/style/Palette.hpp"
#include "ui/style/Style.hpp"
#include "render/core/Renderer.h"
#include "render/programs/RoundedRectProgram.h"
#include "render/scene/RectNode.h"
#include "ui/controls/Label.h"
#include "ui/style/Palette.h"
#include "ui/style/Style.h"
#include <algorithm>
#include <memory>
@@ -13,177 +13,175 @@
Box::Box() = default;
void Box::setDirection(BoxDirection direction) {
if (m_direction == direction) {
return;
}
m_direction = direction;
markDirty();
if (m_direction == direction) {
return;
}
m_direction = direction;
markDirty();
}
void Box::setGap(float gap) {
if (m_gap == gap) {
return;
}
m_gap = gap;
markDirty();
if (m_gap == gap) {
return;
}
m_gap = gap;
markDirty();
}
void Box::setAlign(BoxAlign align) {
if (m_align == align) {
return;
}
m_align = align;
markDirty();
if (m_align == align) {
return;
}
m_align = align;
markDirty();
}
void Box::setPadding(float top, float right, float bottom, float left) {
m_paddingTop = top;
m_paddingRight = right;
m_paddingBottom = bottom;
m_paddingLeft = left;
markDirty();
m_paddingTop = top;
m_paddingRight = right;
m_paddingBottom = bottom;
m_paddingLeft = left;
markDirty();
}
void Box::setPadding(float all) {
setPadding(all, all, all, all);
}
void Box::setPadding(float all) { setPadding(all, all, all, all); }
void Box::setBackground(const Color& color) {
ensureBackground();
auto style = m_background->style();
style.fill = color;
style.fillMode = FillMode::Solid;
m_background->setStyle(style);
ensureBackground();
auto style = m_background->style();
style.fill = color;
style.fillMode = FillMode::Solid;
m_background->setStyle(style);
}
void Box::setRadius(float radius) {
ensureBackground();
auto style = m_background->style();
style.radius = radius;
m_background->setStyle(style);
ensureBackground();
auto style = m_background->style();
style.radius = radius;
m_background->setStyle(style);
}
void Box::setBorderColor(const Color& color) {
ensureBackground();
auto style = m_background->style();
style.border = color;
m_background->setStyle(style);
ensureBackground();
auto style = m_background->style();
style.border = color;
m_background->setStyle(style);
}
void Box::setBorderWidth(float bw) {
ensureBackground();
auto style = m_background->style();
style.borderWidth = bw;
m_background->setStyle(style);
ensureBackground();
auto style = m_background->style();
style.borderWidth = bw;
m_background->setStyle(style);
}
void Box::setSoftness(float softness) {
ensureBackground();
auto style = m_background->style();
style.softness = softness;
m_background->setStyle(style);
ensureBackground();
auto style = m_background->style();
style.softness = softness;
m_background->setStyle(style);
}
void Box::setCardSurface() {
setRadius(Style::radiusMd);
setBorderColor(palette.outline);
setBorderWidth(Style::borderWidth);
setBackground(palette.surface);
setRadius(Style::radiusMd);
setBorderColor(palette.outline);
setBorderWidth(Style::borderWidth);
setBackground(palette.surface);
}
void Box::setRowLayout() {
setDirection(BoxDirection::Horizontal);
setGap(Style::spaceXs);
setAlign(BoxAlign::Center);
setDirection(BoxDirection::Horizontal);
setGap(Style::spaceXs);
setAlign(BoxAlign::Center);
}
void Box::ensureBackground() {
if (m_background != nullptr) {
return;
}
auto rect = std::make_unique<RectNode>();
m_background = static_cast<RectNode*>(insertChildAt(0, std::move(rect)));
if (m_background != nullptr) {
return;
}
auto rect = std::make_unique<RectNode>();
m_background = static_cast<RectNode*>(insertChildAt(0, std::move(rect)));
}
void Box::layout(Renderer& renderer) {
auto& kids = children();
auto& kids = children();
// First pass: measure all children (skip background)
for (auto& child : kids) {
if (!child->visible() || child.get() == m_background) {
continue;
}
if (auto* label = dynamic_cast<Label*>(child.get())) {
label->measure(renderer);
}
if (auto* box = dynamic_cast<Box*>(child.get())) {
box->layout(renderer);
}
// First pass: measure all children (skip background)
for (auto& child : kids) {
if (!child->visible() || child.get() == m_background) {
continue;
}
if (auto* label = dynamic_cast<Label*>(child.get())) {
label->measure(renderer);
}
if (auto* box = dynamic_cast<Box*>(child.get())) {
box->layout(renderer);
}
}
// Second pass: position children along main axis
float cursor = (m_direction == BoxDirection::Horizontal) ? m_paddingLeft : m_paddingTop;
float crossMax = 0.0f;
bool first = true;
for (auto& child : kids) {
if (!child->visible() || child.get() == m_background) {
continue;
}
// Second pass: position children along main axis
float cursor = (m_direction == BoxDirection::Horizontal) ? m_paddingLeft : m_paddingTop;
float crossMax = 0.0f;
bool first = true;
for (auto& child : kids) {
if (!child->visible() || child.get() == m_background) {
continue;
}
if (!first) {
cursor += m_gap;
}
first = false;
if (m_direction == BoxDirection::Horizontal) {
child->setPosition(cursor, child->y());
cursor += child->width();
crossMax = std::max(crossMax, child->height());
} else {
child->setPosition(child->x(), cursor);
cursor += child->height();
crossMax = std::max(crossMax, child->width());
}
if (!first) {
cursor += m_gap;
}
first = false;
// Set own size
if (m_direction == BoxDirection::Horizontal) {
setSize(cursor + m_paddingRight, crossMax + m_paddingTop + m_paddingBottom);
child->setPosition(cursor, child->y());
cursor += child->width();
crossMax = std::max(crossMax, child->height());
} else {
setSize(crossMax + m_paddingLeft + m_paddingRight, cursor + m_paddingBottom);
child->setPosition(child->x(), cursor);
cursor += child->height();
crossMax = std::max(crossMax, child->width());
}
}
// Set own size
if (m_direction == BoxDirection::Horizontal) {
setSize(cursor + m_paddingRight, crossMax + m_paddingTop + m_paddingBottom);
} else {
setSize(crossMax + m_paddingLeft + m_paddingRight, cursor + m_paddingBottom);
}
// Third pass: cross-axis alignment
for (auto& child : kids) {
if (!child->visible() || child.get() == m_background) {
continue;
}
// Third pass: cross-axis alignment
for (auto& child : kids) {
if (!child->visible() || child.get() == m_background) {
continue;
}
if (m_direction == BoxDirection::Horizontal) {
float space = height() - m_paddingTop - m_paddingBottom - child->height();
float offset = m_paddingTop;
if (m_align == BoxAlign::Center) {
offset += space * 0.5f;
} else if (m_align == BoxAlign::End) {
offset += space;
}
child->setPosition(child->x(), offset);
} else {
float space = width() - m_paddingLeft - m_paddingRight - child->width();
float offset = m_paddingLeft;
if (m_align == BoxAlign::Center) {
offset += space * 0.5f;
} else if (m_align == BoxAlign::End) {
offset += space;
}
child->setPosition(offset, child->y());
}
if (m_direction == BoxDirection::Horizontal) {
float space = height() - m_paddingTop - m_paddingBottom - child->height();
float offset = m_paddingTop;
if (m_align == BoxAlign::Center) {
offset += space * 0.5f;
} else if (m_align == BoxAlign::End) {
offset += space;
}
child->setPosition(child->x(), offset);
} else {
float space = width() - m_paddingLeft - m_paddingRight - child->width();
float offset = m_paddingLeft;
if (m_align == BoxAlign::Center) {
offset += space * 0.5f;
} else if (m_align == BoxAlign::End) {
offset += space;
}
child->setPosition(offset, child->y());
}
}
// Size background to match box
if (m_background != nullptr) {
m_background->setPosition(0.0f, 0.0f);
m_background->setSize(width(), height());
}
// Size background to match box
if (m_background != nullptr) {
m_background->setPosition(0.0f, 0.0f);
m_background->setSize(width(), height());
}
}
@@ -1,7 +1,7 @@
#pragma once
#include "render/core/Color.hpp"
#include "render/scene/Node.hpp"
#include "render/core/Color.h"
#include "render/scene/Node.h"
class Renderer;
class RectNode;
+51 -55
View File
@@ -1,71 +1,67 @@
#include "ui/controls/Button.hpp"
#include "ui/controls/Button.h"
#include "render/core/Color.hpp"
#include "ui/controls/Label.hpp"
#include "ui/style/Palette.hpp"
#include "ui/style/Style.hpp"
#include "render/core/Color.h"
#include "ui/controls/Label.h"
#include "ui/style/Palette.h"
#include "ui/style/Style.h"
#include <memory>
Button::Button() {
setAlign(BoxAlign::Center);
setPadding(Style::paddingV, Style::paddingH, Style::paddingV, Style::paddingH);
setRadius(Style::radiusMd);
setAlign(BoxAlign::Center);
setPadding(Style::paddingV, Style::paddingH, Style::paddingV, Style::paddingH);
setRadius(Style::radiusMd);
auto label = std::make_unique<Label>();
m_label = static_cast<Label*>(addChild(std::move(label)));
applyVariant();
auto label = std::make_unique<Label>();
m_label = static_cast<Label*>(addChild(std::move(label)));
applyVariant();
}
void Button::setText(std::string_view text) {
m_label->setText(text);
}
void Button::setText(std::string_view text) { m_label->setText(text); }
void Button::setFontSize(float size) {
m_label->setFontSize(size);
}
void Button::setFontSize(float size) { m_label->setFontSize(size); }
void Button::setVariant(ButtonVariant variant) {
if (m_variant == variant) {
return;
}
m_variant = variant;
applyVariant();
if (m_variant == variant) {
return;
}
m_variant = variant;
applyVariant();
}
void Button::applyVariant() {
setPadding(Style::paddingV, Style::paddingH, Style::paddingV, Style::paddingH);
setRadius(Style::radiusMd);
setPadding(Style::paddingV, Style::paddingH, Style::paddingV, Style::paddingH);
setRadius(Style::radiusMd);
switch (m_variant) {
case ButtonVariant::Default:
setBackground(palette.primary);
m_label->setColor(palette.onPrimary);
setBorderColor(palette.outline);
setBorderWidth(0.0f);
break;
case ButtonVariant::Secondary:
setBackground(palette.secondary);
m_label->setColor(palette.onSecondary);
setBorderColor(palette.outline);
setBorderWidth(0.0f);
break;
case ButtonVariant::Destructive:
setBackground(palette.error);
m_label->setColor(palette.onError);
setBorderColor(palette.outline);
setBorderWidth(0.0f);
break;
case ButtonVariant::Outline:
setBackground(rgba(0.0f, 0.0f, 0.0f, 0.0f));
m_label->setColor(palette.onSurface);
setBorderColor(palette.outline);
setBorderWidth(Style::borderWidth);
break;
case ButtonVariant::Ghost:
setBackground(rgba(0.0f, 0.0f, 0.0f, 0.0f));
m_label->setColor(palette.onSurface);
setBorderWidth(0.0f);
break;
}
switch (m_variant) {
case ButtonVariant::Default:
setBackground(palette.primary);
m_label->setColor(palette.onPrimary);
setBorderColor(palette.outline);
setBorderWidth(0.0f);
break;
case ButtonVariant::Secondary:
setBackground(palette.secondary);
m_label->setColor(palette.onSecondary);
setBorderColor(palette.outline);
setBorderWidth(0.0f);
break;
case ButtonVariant::Destructive:
setBackground(palette.error);
m_label->setColor(palette.onError);
setBorderColor(palette.outline);
setBorderWidth(0.0f);
break;
case ButtonVariant::Outline:
setBackground(rgba(0.0f, 0.0f, 0.0f, 0.0f));
m_label->setColor(palette.onSurface);
setBorderColor(palette.outline);
setBorderWidth(Style::borderWidth);
break;
case ButtonVariant::Ghost:
setBackground(rgba(0.0f, 0.0f, 0.0f, 0.0f));
m_label->setColor(palette.onSurface);
setBorderWidth(0.0f);
break;
}
}
@@ -1,6 +1,6 @@
#pragma once
#include "ui/controls/Box.hpp"
#include "ui/controls/Box.h"
#include <string_view>
+22 -24
View File
@@ -1,35 +1,33 @@
#include "ui/controls/Chip.hpp"
#include "ui/controls/Chip.h"
#include "ui/controls/Label.hpp"
#include "ui/style/Palette.hpp"
#include "ui/style/Style.hpp"
#include "ui/controls/Label.h"
#include "ui/style/Palette.h"
#include "ui/style/Style.h"
#include <memory>
Chip::Chip() {
setAlign(BoxAlign::Center);
setPadding(Style::paddingV, Style::paddingH, Style::paddingV, Style::paddingH);
setRadius(Style::radiusMd);
setAlign(BoxAlign::Center);
setPadding(Style::paddingV, Style::paddingH, Style::paddingV, Style::paddingH);
setRadius(Style::radiusMd);
auto label = std::make_unique<Label>();
m_label = static_cast<Label*>(addChild(std::move(label)));
m_label->setFontSize(Style::fontSizeXs);
setWorkspaceActive(false);
auto label = std::make_unique<Label>();
m_label = static_cast<Label*>(addChild(std::move(label)));
m_label->setFontSize(Style::fontSizeXs);
setWorkspaceActive(false);
}
void Chip::setText(std::string_view text) {
m_label->setText(text);
}
void Chip::setText(std::string_view text) { m_label->setText(text); }
void Chip::setWorkspaceActive(bool active) {
if (active) {
setBackground(palette.primary);
m_label->setColor(palette.onPrimary);
setBorderWidth(0.0f);
} else {
setBackground(palette.surfaceVariant);
m_label->setColor(palette.onSurfaceVariant);
setBorderColor(palette.outline);
setBorderWidth(Style::borderWidth);
}
if (active) {
setBackground(palette.primary);
m_label->setColor(palette.onPrimary);
setBorderWidth(0.0f);
} else {
setBackground(palette.surfaceVariant);
m_label->setColor(palette.onSurfaceVariant);
setBorderColor(palette.outline);
setBorderWidth(Style::borderWidth);
}
}
@@ -1,6 +1,6 @@
#pragma once
#include "ui/controls/Box.hpp"
#include "ui/controls/Box.h"
#include <string_view>
+20 -26
View File
@@ -1,41 +1,35 @@
#include "ui/controls/Icon.hpp"
#include "ui/controls/Icon.h"
#include "render/core/Renderer.hpp"
#include "render/scene/IconNode.hpp"
#include "ui/icons/IconRegistry.hpp"
#include "ui/style/Palette.hpp"
#include "ui/style/Style.hpp"
#include "render/core/Renderer.h"
#include "render/scene/IconNode.h"
#include "ui/icons/IconRegistry.h"
#include "ui/style/Palette.h"
#include "ui/style/Style.h"
#include <memory>
Icon::Icon() {
auto iconNode = std::make_unique<IconNode>();
m_iconNode = static_cast<IconNode*>(addChild(std::move(iconNode)));
m_iconNode->setFontSize(Style::fontSizeSm);
m_iconNode->setColor(palette.onSurface);
auto iconNode = std::make_unique<IconNode>();
m_iconNode = static_cast<IconNode*>(addChild(std::move(iconNode)));
m_iconNode->setFontSize(Style::fontSizeSm);
m_iconNode->setColor(palette.onSurface);
}
void Icon::setIcon(std::string_view name) {
char32_t cp = IconRegistry::lookup(name);
if (cp != 0) {
m_iconNode->setCodepoint(cp);
}
char32_t cp = IconRegistry::lookup(name);
if (cp != 0) {
m_iconNode->setCodepoint(cp);
}
}
void Icon::setCodepoint(char32_t codepoint) {
m_iconNode->setCodepoint(codepoint);
}
void Icon::setCodepoint(char32_t codepoint) { m_iconNode->setCodepoint(codepoint); }
void Icon::setSize(float size) {
m_iconNode->setFontSize(size);
}
void Icon::setSize(float size) { m_iconNode->setFontSize(size); }
void Icon::setColor(const Color& color) {
m_iconNode->setColor(color);
}
void Icon::setColor(const Color& color) { m_iconNode->setColor(color); }
void Icon::measure(Renderer& renderer) {
auto metrics = renderer.measureGlyph(m_iconNode->codepoint(), m_iconNode->fontSize());
Node::setSize(metrics.width, metrics.bottom - metrics.top);
m_iconNode->setPosition(0.0f, -metrics.top);
auto metrics = renderer.measureGlyph(m_iconNode->codepoint(), m_iconNode->fontSize());
Node::setSize(metrics.width, metrics.bottom - metrics.top);
m_iconNode->setPosition(0.0f, -metrics.top);
}
@@ -1,7 +1,7 @@
#pragma once
#include "render/core/Color.hpp"
#include "render/scene/Node.hpp"
#include "render/core/Color.h"
#include "render/scene/Node.h"
#include <string_view>
+23 -39
View File
@@ -1,60 +1,44 @@
#include "ui/controls/Label.hpp"
#include "ui/controls/Label.h"
#include "render/core/Renderer.hpp"
#include "render/scene/TextNode.hpp"
#include "ui/style/Palette.hpp"
#include "ui/style/Style.hpp"
#include "render/core/Renderer.h"
#include "render/scene/TextNode.h"
#include "ui/style/Palette.h"
#include "ui/style/Style.h"
#include <memory>
Label::Label() {
auto textNode = std::make_unique<TextNode>();
m_textNode = static_cast<TextNode*>(addChild(std::move(textNode)));
m_textNode->setFontSize(Style::fontSizeSm);
m_textNode->setColor(palette.onSurface);
auto textNode = std::make_unique<TextNode>();
m_textNode = static_cast<TextNode*>(addChild(std::move(textNode)));
m_textNode->setFontSize(Style::fontSizeSm);
m_textNode->setColor(palette.onSurface);
}
void Label::setText(std::string_view text) {
m_textNode->setText(std::string(text));
}
void Label::setText(std::string_view text) { m_textNode->setText(std::string(text)); }
void Label::setFontSize(float size) {
m_textNode->setFontSize(size);
}
void Label::setFontSize(float size) { m_textNode->setFontSize(size); }
void Label::setColor(const Color& color) {
m_textNode->setColor(color);
}
void Label::setColor(const Color& color) { m_textNode->setColor(color); }
void Label::setMaxWidth(float maxWidth) {
m_textNode->setMaxWidth(maxWidth);
}
void Label::setMaxWidth(float maxWidth) { m_textNode->setMaxWidth(maxWidth); }
const std::string& Label::text() const noexcept {
return m_textNode->text();
}
const std::string& Label::text() const noexcept { return m_textNode->text(); }
float Label::fontSize() const noexcept {
return m_textNode->fontSize();
}
float Label::fontSize() const noexcept { return m_textNode->fontSize(); }
const Color& Label::color() const noexcept {
return m_textNode->color();
}
const Color& Label::color() const noexcept { return m_textNode->color(); }
float Label::maxWidth() const noexcept {
return m_textNode->maxWidth();
}
float Label::maxWidth() const noexcept { return m_textNode->maxWidth(); }
void Label::setCaptionStyle() {
m_textNode->setFontSize(Style::fontSizeCaption);
m_textNode->setColor(palette.onSurface);
m_textNode->setFontSize(Style::fontSizeCaption);
m_textNode->setColor(palette.onSurface);
}
void Label::measure(Renderer& renderer) {
auto metrics = renderer.measureText(m_textNode->text(), m_textNode->fontSize());
setSize(metrics.width, metrics.bottom - metrics.top);
auto metrics = renderer.measureText(m_textNode->text(), m_textNode->fontSize());
setSize(metrics.width, metrics.bottom - metrics.top);
// Position the TextNode at the baseline offset within this Label's bounds
m_textNode->setPosition(0.0f, -metrics.top);
// Position the TextNode at the baseline offset within this Label's bounds
m_textNode->setPosition(0.0f, -metrics.top);
}
@@ -1,7 +1,7 @@
#pragma once
#include "render/core/Color.hpp"
#include "render/scene/Node.hpp"
#include "render/core/Color.h"
#include "render/scene/Node.h"
#include <string>
#include <string_view>
+6 -6
View File
@@ -1,4 +1,4 @@
#include "ui/icons/IconRegistry.hpp"
#include "ui/icons/IconRegistry.h"
#include <string>
#include <unordered_map>
@@ -200,9 +200,9 @@ const std::unordered_map<std::string, char32_t> kIcons = {
} // namespace
char32_t IconRegistry::lookup(std::string_view name) {
auto it = kIcons.find(std::string(name));
if (it != kIcons.end()) {
return it->second;
}
return 0;
auto it = kIcons.find(std::string(name));
if (it != kIcons.end()) {
return it->second;
}
return 0;
}

Some files were not shown because too many files have changed in this diff Show More