Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion data/rc.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,16 @@
<!-- Theme for compositor-drawn chrome (the root menu, and titlebars once
server-side decorations land). <name> resolves an Openbox theme under
<datadir>/themes/<name>/openbox-3/themerc (e.g. Clearlooks). Omit (as
below) to use the built-in default; uncomment and set a name to theme it. -->
below) to use the built-in default; uncomment and set a name to theme it.
Per-place fonts live here too (Openbox <font place="..."> convention):
places are ActiveWindow, InactiveWindow, MenuHeader, MenuItem and
ActiveOnScreenDisplay. The titlebar height tracks the title font size. -->
<!-- <theme>
<name>Clearlooks</name>
<font place="ActiveWindow"><name>Sans</name><size>10</size><weight>bold</weight></font>
<font place="InactiveWindow"><name>Sans</name><size>10</size><weight>normal</weight></font>
<font place="MenuItem"><name>Sans</name><size>10</size><weight>normal</weight></font>
<font place="ActiveOnScreenDisplay"><name>Sans</name><size>10</size><weight>bold</weight></font>
</theme> -->
<!-- waybox-only extensions (Openbox ignores this block). Menu behaviour:
submenuOpen="hover|click", hoverDelay (ms before a submenu opens on
Expand Down
25 changes: 24 additions & 1 deletion include/waybox/theme.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ struct Texture {
/* A font, by Pango family/size. Lives here (not render.hpp) so the pure theme
* and style layers can reference it without pulling in Cairo. */
struct FontSpec {
const char *family = "sans";
std::string family = "sans";
int size_pt = 10;
bool bold = false;
};
Expand Down Expand Up @@ -116,6 +116,15 @@ struct Theme {
WindowColors window_active;
WindowColors window_inactive;
MenuColors menu;

/* Per-place fonts (rc.xml <theme><font place="...">). Openbox keeps fonts
* in rc.xml rather than themerc; config.cpp fills these in. All default to
* the FontSpec default (sans 10pt). */
FontSpec font_active_title; /* place="ActiveWindow" */
FontSpec font_inactive_title; /* place="InactiveWindow" */
FontSpec font_menu_header; /* place="MenuHeader" */
FontSpec font_menu_item; /* place="MenuItem" */
FontSpec font_osd; /* place="ActiveOnScreenDisplay" */
};

/*
Expand All @@ -128,6 +137,20 @@ std::optional<Color> parse_color(std::string_view spec);
/* Parse a themerc justify value ("left"/"center"/"centre"/"right"). */
std::optional<Justify> justify_from_name(std::string_view name);

/* The configurable text "places" for fonts (rc.xml <theme><font place="...">),
* mapping Openbox's place names to the Theme's font fields. */
enum class FontPlace {
ActiveWindow,
InactiveWindow,
MenuHeader,
MenuItem,
OnScreenDisplay,
};

/* Map an Openbox font place name to a FontPlace (case-insensitive). Recognises
* the Openbox names plus the OSD aliases; returns nullopt if unknown. */
std::optional<FontPlace> font_place_from_name(std::string_view name);

/* A Theme with the built-in defaults (a neutral fallback). */
Theme default_theme();

Expand Down
8 changes: 8 additions & 0 deletions include/waybox/wbconf.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ struct WayboxSettings {
std::optional<std::string> switcher_order; /* mru | stacking | spatial */
std::optional<bool> switcher_osd;
std::optional<bool> switcher_wrap;

/* Per-place fonts (theme/font place="..."). Each is "Family Size" in the
* Pango convention (e.g. "Sans Bold 11"); std::nullopt means unset. */
std::optional<std::string> font_active_window; /* place="ActiveWindow" */
std::optional<std::string> font_inactive_window; /* place="InactiveWindow" */
std::optional<std::string> font_menu_header; /* place="MenuHeader" */
std::optional<std::string> font_menu_item; /* place="MenuItem" */
std::optional<std::string> font_osd; /* place="OnScreenDisplay" */
};

/*
Expand Down
19 changes: 19 additions & 0 deletions test/style_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,22 @@ WB_TEST(enum_parsers_round_trip_names) {
WB_CHECK(wb::justify_from_name("right") == Justify::Right);
WB_CHECK(!wb::justify_from_name("bogus").has_value());
}

WB_TEST(theme_fonts_flow_into_resolved_styles) {
wb::Theme t = wb::default_theme();
t.font_active_title = {"Inter", 13, true};
t.font_inactive_title = {"Inter", 11, false};
t.font_menu_item = {"Cantarell", 12, false};
t.font_osd = {"Mono", 9, false};

wb::FrameStyle fa = wb::frame_style_from_theme(t, true);
WB_CHECK(fa.label.font.family == "Inter");
WB_CHECK(fa.label.font.size_pt == 13 && fa.label.font.bold == true);
wb::FrameStyle fi = wb::frame_style_from_theme(t, false);
WB_CHECK(fi.label.font.size_pt == 11 && fi.label.font.bold == false);

wb::MenuStyle ms = wb::menu_style_from_theme(t);
WB_CHECK(ms.item_text.font.family == "Cantarell");
wb::SwitcherStyle ss = wb::switcher_style_from_theme(t);
WB_CHECK(ss.item_text.font.family == "Mono" && ss.item_text.font.size_pt == 9);
}
17 changes: 17 additions & 0 deletions test/theme_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,20 @@ WB_TEST(search_paths_default_data_dirs_and_no_xdg_home) {
WB_CHECK(paths[0] == "/home/u/.local/share/themes/Onyx/openbox-3/themerc");
WB_CHECK(paths[3] == "/usr/share/themes/Onyx/openbox-3/themerc");
}

WB_TEST(font_place_names_map_to_places) {
using wb::FontPlace;
WB_CHECK(wb::font_place_from_name("ActiveWindow") == FontPlace::ActiveWindow);
WB_CHECK(wb::font_place_from_name("InactiveWindow") == FontPlace::InactiveWindow);
WB_CHECK(wb::font_place_from_name("MenuHeader") == FontPlace::MenuHeader);
WB_CHECK(wb::font_place_from_name("MenuItem") == FontPlace::MenuItem);
/* case-insensitive + OSD aliases collapse to one place */
WB_CHECK(wb::font_place_from_name("activewindow") == FontPlace::ActiveWindow);
WB_CHECK(wb::font_place_from_name("ActiveOnScreenDisplay") ==
FontPlace::OnScreenDisplay);
WB_CHECK(wb::font_place_from_name("InactiveOnScreenDisplay") ==
FontPlace::OnScreenDisplay);
WB_CHECK(wb::font_place_from_name("OnScreenDisplay") ==
FontPlace::OnScreenDisplay);
WB_CHECK(!wb::font_place_from_name("Nonsense").has_value());
}
28 changes: 28 additions & 0 deletions test/wbconf_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,31 @@ WB_TEST(clearing_removes_nodes_and_attrs) {
WB_CHECK(xml.find("icons=") == std::string::npos);
WB_CHECK(xml.find("<name>") == std::string::npos);
}

WB_TEST(round_trips_fonts) {
auto doc = RcDocument::from_string(kMinimalRc);
WB_CHECK(doc.has_value());
WayboxSettings s = doc->read();
s.font_active_window = "Inter Bold 12";
s.font_menu_item = "Cantarell 11";
doc->apply(s);

std::string xml = doc->to_string();
/* Structured font element written under <theme>. */
WB_CHECK(xml.find("place=\"ActiveWindow\"") != std::string::npos);
WB_CHECK(xml.find("<name>Inter</name>") != std::string::npos);
WB_CHECK(xml.find("<size>12</size>") != std::string::npos);
WB_CHECK(xml.find("<weight>bold</weight>") != std::string::npos);

auto doc2 = RcDocument::from_string(xml);
WB_CHECK(doc2.has_value());
WayboxSettings r = doc2->read();
WB_CHECK(r.font_active_window && *r.font_active_window == "Inter Bold 12");
WB_CHECK(r.font_menu_item && *r.font_menu_item == "Cantarell 11");
WB_CHECK(!r.font_osd.has_value());

/* Clearing removes the font element. */
r.font_active_window = std::nullopt;
doc2->apply(r);
WB_CHECK(doc2->to_string().find("place=\"ActiveWindow\"") == std::string::npos);
}
63 changes: 63 additions & 0 deletions waybox/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,65 @@ static void parse_titlebar(struct wb_config *config, xmlXPathContextPtr ctxt) {
xmlXPathFreeObject(object);
}

/* Parse Openbox font configuration: <theme><font place="...">[<name>, <size>,
* <weight>bold</weight>]</font></theme>. Each place maps to a Theme font field;
* fonts live in rc.xml (not themerc), so this runs after the theme is loaded. */
static void parse_fonts(struct wb_config *config, xmlXPathContextPtr ctxt) {
xmlXPathObjectPtr object = xmlXPathEvalExpression(
(const xmlChar *) "//ob:theme/ob:font", ctxt);
if (object == NULL)
return;
if (object->nodesetval) {
for (int i = 0; i < object->nodesetval->nodeNr; i++) {
xmlNode *node = object->nodesetval->nodeTab[i];
if (node == NULL)
continue;
const char *place = (const char *) get_attribute(node, "place");
if (place == NULL)
continue;
auto fp = wb::font_place_from_name(place);
if (!fp)
continue;
wb::FontSpec *target = nullptr;
switch (*fp) {
case wb::FontPlace::ActiveWindow:
target = &config->theme.font_active_title; break;
case wb::FontPlace::InactiveWindow:
target = &config->theme.font_inactive_title; break;
case wb::FontPlace::MenuHeader:
target = &config->theme.font_menu_header; break;
case wb::FontPlace::MenuItem:
target = &config->theme.font_menu_item; break;
case wb::FontPlace::OnScreenDisplay:
target = &config->theme.font_osd; break;
}
if (target == nullptr)
continue;
for (xmlNode *c = node->children; c != NULL; c = c->next) {
if (c->type != XML_ELEMENT_NODE)
continue;
xmlChar *content = xmlNodeGetContent(c);
const char *v = content ? (const char *) content : "";
if (xmlStrcmp(c->name, (const xmlChar *) "name") == 0 &&
v[0] != '\0') {
target->family = v;
} else if (xmlStrcmp(c->name, (const xmlChar *) "size") == 0 &&
v[0] != '\0') {
int s = atoi(v);
if (s > 0)
target->size_pt = s;
} else if (xmlStrcmp(c->name, (const xmlChar *) "weight") == 0) {
target->bold = (xmlStrcasecmp((const xmlChar *) v,
(const xmlChar *) "bold") == 0);
}
if (content)
xmlFree(content);
}
}
}
xmlXPathFreeObject(object);
}

bool init_config(struct wb_server *server) {
struct wb_config *config = new (std::nothrow) wb_config{};
if (config == NULL)
Expand Down Expand Up @@ -554,6 +613,10 @@ bool init_config(struct wb_server *server) {
config->theme = wb::default_theme();
}

/* Per-place fonts: <theme><font place="...">. Applied onto the loaded
* theme so they override its defaults. */
parse_fonts(config, ctxt);

/* Menu behaviour: waybox extension block <waybox><menu .../></waybox>, which
* Openbox ignores. Attributes: submenuOpen="hover|click", hoverDelay="ms",
* wrap="yes|no". */
Expand Down
2 changes: 1 addition & 1 deletion waybox/render.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ PangoLayout *make_layout(cairo_t *cr, std::string_view text,
const FontSpec &font) {
PangoLayout *layout = pango_cairo_create_layout(cr);
PangoFontDescription *desc = pango_font_description_new();
pango_font_description_set_family(desc, font.family);
pango_font_description_set_family(desc, font.family.c_str());
pango_font_description_set_weight(desc,
font.bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL);
pango_font_description_set_size(desc, font.size_pt * PANGO_SCALE);
Expand Down
4 changes: 4 additions & 0 deletions waybox/style.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ MenuStyle menu_style_from_theme(const Theme &theme) {

s.title_bar.fill = paint_from_texture(m.title_bg);
s.title_text.color = m.title_text;
s.title_text.font = theme.font_menu_header;

s.item.normal = StateStyle{paint_from_texture(m.items_bg), m.items_text};
s.item.hover = StateStyle{paint_from_texture(m.items_active_bg), m.items_active_text};
s.item.pressed = s.item.hover;
s.item.disabled = StateStyle{paint_from_texture(m.items_bg), m.separator};
s.item_text.color = m.items_text;
s.item_text.font = theme.font_menu_item;

s.separator = m.separator;
s.item_spacing = theme.menu_item_spacing;
Expand All @@ -62,6 +64,7 @@ FrameStyle frame_style_from_theme(const Theme &theme, bool active) {
theme.padding_y, theme.padding_x};
f.label.color = w.label_text;
f.label.justify = theme.label_justify;
f.label.font = active ? theme.font_active_title : theme.font_inactive_title;

f.handle.fill = paint_from_texture(w.handle_bg);
f.grip.fill = paint_from_texture(w.grip_bg);
Expand Down Expand Up @@ -129,6 +132,7 @@ SwitcherStyle switcher_style_from_theme(const Theme &theme) {
s.item.pressed = s.item.hover;
s.item.disabled = StateStyle{paint_from_texture(m.items_bg), m.separator};
s.item_text.color = m.items_text;
s.item_text.font = theme.font_osd;
return s;
}

Expand Down
21 changes: 21 additions & 0 deletions waybox/theme.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,27 @@ std::optional<Justify> justify_from_name(std::string_view name) {
return std::nullopt;
}

std::optional<FontPlace> font_place_from_name(std::string_view name) {
std::string n;
n.reserve(name.size());
for (char c : name)
n.push_back(static_cast<char>(std::tolower(
static_cast<unsigned char>(c))));
if (n == "activewindow")
return FontPlace::ActiveWindow;
if (n == "inactivewindow")
return FontPlace::InactiveWindow;
if (n == "menuheader")
return FontPlace::MenuHeader;
if (n == "menuitem")
return FontPlace::MenuItem;
/* Openbox distinguishes active/inactive OSD; waybox uses one OSD font. */
if (n == "activeonscreendisplay" || n == "inactiveonscreendisplay" ||
n == "onscreendisplay")
return FontPlace::OnScreenDisplay;
return std::nullopt;
}

namespace {

void apply_color(std::string_view value, Color &out) {
Expand Down
Loading