diff --git a/metadata/pixdecor.xml b/metadata/pixdecor.xml index 5ce4729..843cc1b 100644 --- a/metadata/pixdecor.xml +++ b/metadata/pixdecor.xml @@ -6,6 +6,11 @@ Effects <_short>Window frames + + <_short>Follow shell theme colors + <_long>Use the current shell palette for frames, title text, buttons and pinstripe hue. Custom colors are retained and used when disabled or when the shell is unavailable. Border geometry, shading and pinstripe opacity stay unchanged. + false + <_short>Border size <_long>Sets the border size in pixels. diff --git a/src/deco-effects.cpp b/src/deco-effects.cpp index f092244..5563f7e 100644 --- a/src/deco-effects.cpp +++ b/src/deco-effects.cpp @@ -32,6 +32,7 @@ #include #include "deco-effects.hpp" +#include "shell-theme.hpp" #include "smoke-shaders.hpp" #include "sculpted-overlay.hpp" @@ -2592,7 +2593,7 @@ void smoke_t::step_effect(const wf::scene::render_instruction_t& data, wf::geome GL_CALL(glUniform4fv(9, 1, shadow_color_f)); if (std::string(overlay_engine) == "sculpted") { - wf::color_t rim = pinstripe_color; + wf::color_t rim = shell_pinstripe_color(wf::color_t(pinstripe_color)); GLfloat rim_f[4] = {GLfloat(rim.r), GLfloat(rim.g), GLfloat(rim.b), GLfloat(rim.a)}; GL_CALL(glUniform1f(10, border_depth)); GL_CALL(glUniform4fv(11, 1, rim_f)); diff --git a/src/deco-theme.cpp b/src/deco-theme.cpp index 32a2ced..9ae7166 100644 --- a/src/deco-theme.cpp +++ b/src/deco-theme.cpp @@ -1,4 +1,5 @@ #include "deco-theme.hpp" +#include "shell-theme.hpp" #include #include #include @@ -43,6 +44,13 @@ void pixdecor_theme_t::update_colors(void) bg = wf::color_t(bg_color); fg_text = wf::color_t(fg_text_color); bg_text = wf::color_t(bg_text_color); + if (shell_colors_active()) + { + fg = shell_palette->surface; + bg = shell_palette->background; + fg_text = shell_palette->foreground; + bg_text = shell_palette->muted; + } } std::unique_ptr +#include +#include +#include +#include "shell-theme.hpp" namespace wf { @@ -28,6 +32,20 @@ namespace pixdecor { class wayfire_pixdecor : public wf::plugin_interface_t { + wf::option_wrapper_t follow_theme; + wf::shared_data::ref_ptr_t theme_methods; + wf::ipc::client_interface_t *theme_owner = nullptr; + std::string theme_instance, theme_signature; + wf::signal::connection_t theme_disconnected = + [this] (wf::ipc::client_disconnected_signal *event) + { + if (event->client != theme_owner) return; + theme_owner = nullptr; + shell_palette.reset(); + theme_signature.clear(); + update_colors(); + recreate_frames(); + }; wf::option_wrapper_t border_size{"pixdecor/border_size"}; wf::option_wrapper_t title_font{"pixdecor/title_font"}; wf::option_wrapper_t title_text_align{"pixdecor/title_text_align"}; @@ -240,6 +258,53 @@ class wayfire_pixdecor : public wf::plugin_interface_t } }; promote("border_depth", 0.65); + promote("follow_shell_theme", false); + follow_theme.load_option("pixdecor/follow_shell_theme"); + follow_shell_theme = follow_theme; + follow_theme.set_callback([this] + { + follow_shell_theme = follow_theme; + update_colors(); + recreate_frames(); + }); + theme_instance = std::to_string(getpid()) + ":" + std::to_string( + std::chrono::steady_clock::now().time_since_epoch().count()); + theme_methods->connect(&theme_disconnected); + theme_methods->register_method("pixdecor/theme-status", [this] (wf::json_t) + { + auto result = wf::ipc::json_ok(); + result["version"] = 1; + result["instance"] = theme_instance; + result["following"] = bool(follow_theme); + result["received"] = shell_palette.has_value(); + result["signature"] = theme_signature; + return result; + }); + theme_methods->register_method("pixdecor/theme-configure", + [this] (wf::json_t data, wf::ipc::client_interface_t *client) + { + if (!client || !data["version"].is_int64() || data["version"].as_int64() != 1 || + !data["instance"].is_string() || data["instance"].as_string() != theme_instance || + (theme_owner && theme_owner != client)) + return wf::ipc::json_error("Unavailable theme owner or instance"); + try + { + const auto next = parse_shell_palette(data); + const auto signature = data.serialize(); + if (signature != theme_signature) + { + shell_palette = next; + theme_owner = client; + theme_signature = signature; + update_colors(); + recreate_frames(); + } + return wf::ipc::json_ok(); + } catch (const std::exception& error) + { + return wf::ipc::json_error(error.what()); + } + }); promote("pinstripe_color", wf::color_t{90.0 / 255, 113.0 / 255, 129.0 / 255, 36.0 / 255}); border_depth.load_option("pixdecor/border_depth"); pinstripe_color.load_option("pixdecor/pinstripe_color"); @@ -550,6 +615,12 @@ class wayfire_pixdecor : public wf::plugin_interface_t void fini() override { + theme_methods->unregister_method("pixdecor/theme-status"); + theme_methods->unregister_method("pixdecor/theme-configure"); + theme_disconnected.disconnect(); + shell_palette.reset(); + follow_shell_theme = false; + theme_owner = nullptr; for (auto view : wf::get_core().get_all_views()) { if (auto toplevel = wf::toplevel_cast(view)) diff --git a/src/shell-theme.hpp b/src/shell-theme.hpp new file mode 100644 index 0000000..5bc7877 --- /dev/null +++ b/src/shell-theme.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace wf::pixdecor +{ +struct shell_palette_t +{ + wf::color_t background, surface, foreground, muted, border; +}; + +inline wf::color_t shell_color(const wf::json_t& value) +{ + if (!value.is_string()) throw std::invalid_argument("Expected a theme color"); + const auto text = value.as_string(); + if (text.size() != 7 || text.front() != '#' || + text.find_first_not_of("0123456789abcdefABCDEF", 1) != std::string::npos) + throw std::invalid_argument("Expected #RRGGBB"); + return {std::stoul(text.substr(1, 2), nullptr, 16) / 255.0, + std::stoul(text.substr(3, 2), nullptr, 16) / 255.0, + std::stoul(text.substr(5, 2), nullptr, 16) / 255.0, 1.0}; +} + +inline shell_palette_t parse_shell_palette(const wf::json_t& data) +{ + return {shell_color(data["background"]), shell_color(data["surface"]), + shell_color(data["foreground"]), shell_color(data["muted"]), + shell_color(data["border"])}; +} + +// Live presentation only. Saved custom colors remain in wayfire.ini. +inline std::optional shell_palette; +inline bool follow_shell_theme = false; +inline bool shell_colors_active() { return follow_shell_theme && shell_palette.has_value(); } +inline wf::color_t shell_button_color(wf::color_t custom) +{ + return shell_colors_active() ? shell_palette->foreground : custom; +} +inline wf::color_t shell_pinstripe_color(wf::color_t custom) +{ + if (!shell_colors_active()) return custom; + auto themed = shell_palette->border; + themed.a = custom.a; + return themed; +} +}