mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
refactor(project): .hpp => .h
This commit is contained in:
+101
-104
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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; }
|
||||
|
||||
@@ -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:
|
||||
+862
-1003
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -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:
|
||||
@@ -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
-1
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "notification/NotificationManager.hpp"
|
||||
#include "notification/NotificationManager.h"
|
||||
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
#include <chrono>
|
||||
+35
-51
@@ -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
@@ -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
@@ -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
-1
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "notification/NotificationManager.hpp"
|
||||
#include "notification/NotificationManager.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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
@@ -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};
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+2
-2
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+2
-2
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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>
|
||||
@@ -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
@@ -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,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "render/scene/Node.hpp"
|
||||
#include "render/scene/Node.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
+25
-27
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
@@ -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
Reference in New Issue
Block a user