feat: show the app icon on the window, and assert it is really there
The resource-path registration moves from build_toolbar() up into run_ui(), because the window's icon is now a second consumer of it and this is the one place ahead of both. Whichever consumer ran first would otherwise resolve its name to nothing, with no error -- GTK draws a missing-image placeholder. The new 'app icons:' dump line is the guard, computed on the first tick beside 'toolbar icons:'. It reports named-and-present at the size the file declares, and deliberately does not claim the file renders: no runtime GTK call can see a malformed SVG, which is why the manifest validates at build time instead.
This commit is contained in:
1 parent
f3d68cfdd4
commit
6deb53c458
1 file changed
+85
-9
+85
-9
@@ -148,6 +148,7 @@ struct App {
|
|||||||
/// registered is *silent* -- GTK substitutes a missing-image placeholder, the button still
|
/// registered is *silent* -- GTK substitutes a missing-image placeholder, the button still
|
||||||
/// works, and every other line of the dump still passes.
|
/// works, and every other line of the dump still passes.
|
||||||
std::string dbg_toolbar_icons;
|
std::string dbg_toolbar_icons;
|
||||||
|
std::string dbg_app_icons;
|
||||||
/// The header bar, so the dump can report the order the controls are in: a header bar is a
|
/// The header bar, so the dump can report the order the controls are in: a header bar is a
|
||||||
/// *titlebar*, not a child of the window, so walking down from the window does not reach it.
|
/// *titlebar*, not a child of the window, so walking down from the window does not reach it.
|
||||||
GtkWidget* toolbar = nullptr;
|
GtkWidget* toolbar = nullptr;
|
||||||
@@ -4386,6 +4387,61 @@ void rebuild_signal_panels(App& app) {
|
|||||||
app.signal_generation = app.model->generation();
|
app.signal_generation = app.model->generation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The width and height the root `<svg>` element of `data` declares, or 0x0 when it has none.
|
||||||
|
/// GMarkup rather than a string scan because the attributes may be in any order and on any
|
||||||
|
/// line; it is *not* a validity check -- GMarkup is a deliberately lenient subset parser and
|
||||||
|
/// accepts a double hyphen inside a comment, which is exactly the file librsvg refuses. That
|
||||||
|
/// check is `xml-stripblanks` in the GResource manifest, at build time.
|
||||||
|
void svg_declared_size(const char* data, gsize size, int* w, int* h) {
|
||||||
|
struct Found { int w = 0; int h = 0; };
|
||||||
|
Found found;
|
||||||
|
GMarkupParser parser{};
|
||||||
|
parser.start_element = [](GMarkupParseContext*, const char* element, const char** names,
|
||||||
|
const char** values, gpointer user, GError**) {
|
||||||
|
if (std::strcmp(element, "svg") != 0) return;
|
||||||
|
Found* f = static_cast<Found*>(user);
|
||||||
|
for (int i = 0; names[i] != nullptr; ++i) {
|
||||||
|
if (std::strcmp(names[i], "width") == 0) f->w = std::atoi(values[i]);
|
||||||
|
if (std::strcmp(names[i], "height") == 0) f->h = std::atoi(values[i]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
GMarkupParseContext* ctx = g_markup_parse_context_new(&parser, GMarkupParseFlags(0), &found,
|
||||||
|
nullptr);
|
||||||
|
if (g_markup_parse_context_parse(ctx, data, static_cast<gssize>(size), nullptr)) {
|
||||||
|
g_markup_parse_context_end_parse(ctx, nullptr);
|
||||||
|
}
|
||||||
|
g_markup_parse_context_free(ctx);
|
||||||
|
*w = found.w;
|
||||||
|
*h = found.h;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One entry of the `app icons:` line: the icon is *named* (the theme resolved it, so the
|
||||||
|
/// resource path was registered) and *present* (the bytes the name resolved to are really in
|
||||||
|
/// the binary), at the size its own root element declares.
|
||||||
|
///
|
||||||
|
/// It deliberately does not claim the file renders. `gtk_icon_theme_lookup_icon()` never
|
||||||
|
/// returns NULL, its paintable's intrinsic size is the size that was asked for, and a valid
|
||||||
|
/// icon, a malformed one and a name that does not exist at all all snapshot to the same
|
||||||
|
/// texture node -- GTK substitutes `image-missing` and there is nothing left to compare. The
|
||||||
|
/// malformed case is a build error instead; see the GResource manifest.
|
||||||
|
std::string icon_report(GtkIconTheme* theme, const char* name, const char* resource) {
|
||||||
|
const bool named = gtk_icon_theme_has_icon(theme, name) != FALSE;
|
||||||
|
GBytes* bytes = g_resources_lookup_data(resource, G_RESOURCE_LOOKUP_FLAGS_NONE, nullptr);
|
||||||
|
const bool present = bytes != nullptr;
|
||||||
|
int w = 0;
|
||||||
|
int h = 0;
|
||||||
|
if (present) {
|
||||||
|
gsize size = 0;
|
||||||
|
const char* data = static_cast<const char*>(g_bytes_get_data(bytes, &size));
|
||||||
|
svg_declared_size(data, size, &w, &h);
|
||||||
|
g_bytes_unref(bytes);
|
||||||
|
}
|
||||||
|
std::string out = std::string(name) + "(";
|
||||||
|
out += (named && present) ? "ok " : "MISSING ";
|
||||||
|
out += std::to_string(w) + "x" + std::to_string(h) + ")";
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
/// Where the panel controls live, as their parents say: the three pane toggles are toolbar buttons
|
/// Where the panel controls live, as their parents say: the three pane toggles are toolbar buttons
|
||||||
/// and Reset is inside the cursors pane. Recorded on the first tick -- the widgets exist and the
|
/// and Reset is inside the cursors pane. Recorded on the first tick -- the widgets exist and the
|
||||||
/// window is alive, neither of which is true when the dump runs -- so the dump can print a fact
|
/// window is alive, neither of which is true when the dump runs -- so the dump can print a fact
|
||||||
@@ -4440,9 +4496,11 @@ void note_panel_controls(App& app) {
|
|||||||
|
|
||||||
// ... and the icon each of them carries, asked of the theme rather than of our own
|
// ... and the icon each of them carries, asked of the theme rather than of our own
|
||||||
// string. Four of these are drawn for this app and live in a GResource inside the
|
// string. Four of these are drawn for this app and live in a GResource inside the
|
||||||
// binary, which the theme only looks in because `build_toolbar()` adds the resource
|
// binary, which the theme only looks in because `run_ui()` adds the resource path
|
||||||
// path -- and a path that was never added fails *silently*: the button keeps working,
|
// before building anything -- and a path that was never added fails *silently*: the
|
||||||
// the layout is unchanged, and GTK draws a missing-image placeholder no probe can see.
|
// button keeps working, the layout is unchanged, and GTK draws a missing-image
|
||||||
|
// placeholder no probe can see. The two app icons below come out of the same
|
||||||
|
// registration, which is why a MISSING on either line is one defect.
|
||||||
GtkIconTheme* theme = gtk_icon_theme_get_for_display(gdk_display_get_default());
|
GtkIconTheme* theme = gtk_icon_theme_get_for_display(gdk_display_get_default());
|
||||||
for (const auto& [widget, name] : named) {
|
for (const auto& [widget, name] : named) {
|
||||||
const char* icon = nullptr;
|
const char* icon = nullptr;
|
||||||
@@ -4455,6 +4513,15 @@ void note_panel_controls(App& app) {
|
|||||||
app.dbg_toolbar_icons += std::string(app.dbg_toolbar_icons.empty() ? "" : " ") + name + "=" +
|
app.dbg_toolbar_icons += std::string(app.dbg_toolbar_icons.empty() ? "" : " ") + name + "=" +
|
||||||
icon + (gtk_icon_theme_has_icon(theme, icon) ? "(ok)" : "(MISSING)");
|
icon + (gtk_icon_theme_has_icon(theme, icon) ? "(ok)" : "(MISSING)");
|
||||||
}
|
}
|
||||||
|
// The application icon, the same two ways and for the same reason: a resource path that was
|
||||||
|
// never registered is not an error, it is a missing-image placeholder. This one is read on
|
||||||
|
// the first tick like the line above, because the dump runs after the window is destroyed.
|
||||||
|
app.dbg_app_icons =
|
||||||
|
icon_report(theme, "org.udpscope.app",
|
||||||
|
"/org/udpscope/icons/scalable/apps/org.udpscope.app.svg") +
|
||||||
|
" " +
|
||||||
|
icon_report(theme, "org.udpscope.app-symbolic",
|
||||||
|
"/org/udpscope/icons/scalable/apps/org.udpscope.app-symbolic.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The event-zoom sweep's two halves: a gesture zoom, then the same walk through the window
|
/// The event-zoom sweep's two halves: a gesture zoom, then the same walk through the window
|
||||||
@@ -5582,12 +5649,6 @@ GtkWidget* build_toolbar(App& app) {
|
|||||||
GtkWidget* bar = gtk_header_bar_new();
|
GtkWidget* bar = gtk_header_bar_new();
|
||||||
app.toolbar = bar;
|
app.toolbar = bar;
|
||||||
|
|
||||||
// The app's own icons, before the first button asks for one. They are a GResource
|
|
||||||
// compiled into the binary (see CMakeLists), and this is what makes the theme look
|
|
||||||
// there: an unregistered path is not an error, it is a missing-image placeholder.
|
|
||||||
gtk_icon_theme_add_resource_path(gtk_icon_theme_get_for_display(gdk_display_get_default()),
|
|
||||||
"/org/udpscope/icons");
|
|
||||||
|
|
||||||
gtk_header_bar_pack_start(GTK_HEADER_BAR(bar),
|
gtk_header_bar_pack_start(GTK_HEADER_BAR(bar),
|
||||||
labeled_box_fixed("window", build_window_control(app)));
|
labeled_box_fixed("window", build_window_control(app)));
|
||||||
refresh_window_limits(app);
|
refresh_window_limits(app);
|
||||||
@@ -5764,6 +5825,10 @@ void on_activate(GtkApplication* application, gpointer user) {
|
|||||||
}),
|
}),
|
||||||
app);
|
app);
|
||||||
gtk_window_set_title(GTK_WINDOW(app->window), "udpscope");
|
gtk_window_set_title(GTK_WINDOW(app->window), "udpscope");
|
||||||
|
// The shell takes a window's icon from this name, and GTK takes the WM class from the
|
||||||
|
// application ID -- which is the same string, which is what associates this window with the
|
||||||
|
// desktop entry.
|
||||||
|
gtk_window_set_icon_name(GTK_WINDOW(app->window), "org.udpscope.app");
|
||||||
gtk_window_set_default_size(GTK_WINDOW(app->window), 1280, 800);
|
gtk_window_set_default_size(GTK_WINDOW(app->window), 1280, 800);
|
||||||
gtk_window_set_titlebar(GTK_WINDOW(app->window), build_toolbar(*app));
|
gtk_window_set_titlebar(GTK_WINDOW(app->window), build_toolbar(*app));
|
||||||
|
|
||||||
@@ -7525,6 +7590,16 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
|||||||
// Installs libadwaita's stylesheet, which the shortcut overview's `AdwShortcutsDialog`
|
// Installs libadwaita's stylesheet, which the shortcut overview's `AdwShortcutsDialog`
|
||||||
// needs to be drawn at all. Idempotent, and `choose_source()` may already have run it.
|
// needs to be drawn at all. Idempotent, and `choose_source()` may already have run it.
|
||||||
adw_init();
|
adw_init();
|
||||||
|
// The app's own icons, before anything asks for one. They are a GResource compiled into the
|
||||||
|
// binary (see CMakeLists), and this is what makes the theme look there: an unregistered path
|
||||||
|
// is not an error, it is a missing-image placeholder.
|
||||||
|
//
|
||||||
|
// It is here rather than in `build_toolbar()` because there are now two consumers -- the
|
||||||
|
// window's own icon and the toolbar's buttons -- and this is the one place that is ahead of
|
||||||
|
// both. `adw_init()` calls `gtk_init()`, which opens the default display, so the line above
|
||||||
|
// is what makes `gdk_display_get_default()` valid here.
|
||||||
|
gtk_icon_theme_add_resource_path(gtk_icon_theme_get_for_display(gdk_display_get_default()),
|
||||||
|
"/org/udpscope/icons");
|
||||||
App app;
|
App app;
|
||||||
app.model = &model;
|
app.model = &model;
|
||||||
app.acq = &acq;
|
app.acq = &acq;
|
||||||
@@ -7799,6 +7874,7 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
|||||||
// critical (which is how the first version of this line earned four of them).
|
// critical (which is how the first version of this line earned four of them).
|
||||||
std::printf("[udpscope] panel controls: %s\n", app.dbg_panel_controls.c_str());
|
std::printf("[udpscope] panel controls: %s\n", app.dbg_panel_controls.c_str());
|
||||||
std::printf("[udpscope] toolbar icons: %s\n", app.dbg_toolbar_icons.c_str());
|
std::printf("[udpscope] toolbar icons: %s\n", app.dbg_toolbar_icons.c_str());
|
||||||
|
std::printf("[udpscope] app icons: %s\n", app.dbg_app_icons.c_str());
|
||||||
// The measurements pane, with the numbers the engine computed: a probe adds measurements and
|
// The measurements pane, with the numbers the engine computed: a probe adds measurements and
|
||||||
// this is where their values are read, so a wrong threshold or a window that moved under a
|
// this is where their values are read, so a wrong threshold or a window that moved under a
|
||||||
// stale result is visible in the run rather than only on screen.
|
// stale result is visible in the run rather than only on screen.
|
||||||
|
|||||||
Reference in new issue
Block a user