feat(app): wire the app icon into the GUI, waybar, and SDL window
screencast_icon/ holds the single source icon set; meson install ships the 256 px PNG into the hicolor theme for the GTK panel (themed icon name) and the waybar CSS, and a custom target embeds it as icon_png_data.h so the SDL receiver window sets it via SDL_SetWindowIcon with an optional SDL3_image dependency (built-in find_library, since no pkg-config file ships with it).
@@ -65,6 +65,23 @@ loopback; current phase is Phase 7.
|
||||
before the fix). Cross-compiling on the dev machine was considered and
|
||||
dropped — the on-Pi build works and the toolchain/container effort was
|
||||
not needed.
|
||||
- **App icons wired up** (`screencast_icon/`): single 256 px PNG used
|
||||
everywhere. `meson install` ships it to
|
||||
`share/icons/hicolor/256x256/apps/screencast.png`; the GTK panel sets it
|
||||
via `set_icon_name("screencast")` (GTK4 removed the pixel-buffer window
|
||||
icon — themed names only; `gtk_icon_theme_has_icon` verified OK); the
|
||||
SDL receiver window embeds the PNG at build time (`icon_png_data.h` via
|
||||
`scripts/icon_to_header.py` custom_target) and `SDL_SetWindowIcon` with
|
||||
an optional SDL3_image dependency (no pkg-config ships with it →
|
||||
`meson.get_compiler('cpp').find_library('SDL3_image', required: false)`
|
||||
+ disabler; `-DSC_HAS_WINDOW_ICON` gate; Pi builds without it still
|
||||
work). The waybar widget now shows the icon as `background-image` in
|
||||
`~/.config/waybar/style.css` (▶/⏸ text hidden; `.idle` dimmed,
|
||||
`.streaming` green tint) instead of text glyphs; backup
|
||||
`style.css.bak-20260909-*`. Lesson: icons appear in compositor
|
||||
taskbars/switchers, not title bars (GTK4 CSD and Hyprland decorations
|
||||
don't draw them), so validate via theme lookup, not title-bar
|
||||
screenshots. `sudo pacman -S sdl3_image` done (extra/sdl3_image 3.4.6).
|
||||
- **GTK panel + waybar widget**: `screencast-gui` (gtkmm-4.0, behind
|
||||
`-Dgui=true`; app internals now live in the `sc_app_core` static lib so
|
||||
CLI and GUI share pipelines/session/state). `screencast waybar [--toggle]`
|
||||
|
||||
@@ -76,10 +76,13 @@ screencast waybar # one JSON line for a waybar custom module
|
||||
screencast waybar --toggle
|
||||
```
|
||||
|
||||
The waybar module shows `▶` while streaming (tooltip: receiver, bitrate,
|
||||
elapsed) and `⏸` while idle; left-click toggles streaming to the last
|
||||
receiver, right-click opens the panel. All front-ends agree on state because
|
||||
the sender publishes it to `$XDG_RUNTIME_DIR/screencast/sender.json`.
|
||||
The waybar module shows the app icon (dimmed while idle, highlighted while
|
||||
streaming; tooltip: receiver, bitrate, elapsed); left-click toggles
|
||||
streaming to the last receiver, right-click opens the panel. All front-ends
|
||||
agree on state because the sender publishes it to
|
||||
`$XDG_RUNTIME_DIR/screencast/sender.json`. The icon (from `screencast_icon/`)
|
||||
is installed into the hicolor theme by `meson install`, used by the GTK
|
||||
panel and the waybar CSS, and embedded in the SDL receiver window.
|
||||
|
||||
## Under the hood: resilience
|
||||
|
||||
|
||||
@@ -146,11 +146,24 @@ screencast waybar --toggle # stop a running sender / restart the last
|
||||
```
|
||||
|
||||
The waybar module (installed to this machine's `~/.config/waybar/` — see
|
||||
`custom/screencast` there): `▶` while streaming (tooltip: receiver, bitrate,
|
||||
elapsed), `⏸` while idle; left-click toggles streaming to the last receiver
|
||||
via the state file, right-click opens the panel. The sender publishes its
|
||||
state to `$XDG_RUNTIME_DIR/screencast/sender.json`, so every front-end —
|
||||
CLI, GUI, widget — agrees on what is running.
|
||||
`custom/screencast` there): the app icon as the module face (dimmed while
|
||||
idle, green tint while streaming; the `▶`/`⏸` text from the JSON is hidden
|
||||
in the CSS), tooltip with receiver, bitrate, elapsed; left-click toggles
|
||||
streaming to the last receiver via the state file, right-click opens the
|
||||
panel. The sender publishes its state to `$XDG_RUNTIME_DIR/screencast/sender.json`,
|
||||
so every front-end — CLI, GUI, widget — agrees on what is running.
|
||||
|
||||
**Icons**: `screencast_icon/screencast_256.png` is the single app icon.
|
||||
`meson install` puts it in `share/icons/hicolor/256x256/apps/screencast.png`;
|
||||
the GTK panel uses it by themed name (`set_icon_name` — GTK4 removed the
|
||||
pixel-buffer window icon, so themed icons only), and the waybar CSS
|
||||
references the installed path. The SDL receiver window embeds the PNG at
|
||||
build time (`icon_png_data.h` generated by `scripts/icon_to_header.py`) and
|
||||
sets it via `SDL_SetWindowIcon` when SDL3_image is available (optional
|
||||
dependency; no pkg-config ships with it, so meson finds the library
|
||||
directly). Icons surface in compositor taskbars/switchers, not in title
|
||||
bars (neither GTK4's CSD nor Hyprland's decorations draw them).
|
||||
Restart waybar after changing `style.css` to pick up icon changes.
|
||||
|
||||
## Under the hood: resilience (Phase 7)
|
||||
|
||||
|
||||
@@ -26,6 +26,12 @@ if enable_tests
|
||||
subdir('tests')
|
||||
endif
|
||||
|
||||
# Install the application icon into the hicolor theme: the waybar widget CSS
|
||||
# and any future .desktop file reference it.
|
||||
install_data('screencast_icon/screencast_256.png',
|
||||
install_dir : get_option('datadir') / 'icons/hicolor/256x256/apps',
|
||||
rename : 'screencast.png')
|
||||
|
||||
# Summary for the user
|
||||
summary({
|
||||
'tests': enable_tests,
|
||||
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 398 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate a C++ header that embeds an image file as a byte array.
|
||||
|
||||
Usage: icon_to_header.py INPUT_IMAGE OUTPUT_HEADER
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) != 3:
|
||||
print(f"usage: {sys.argv[0]} INPUT_IMAGE OUTPUT_HEADER", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
with open(sys.argv[1], "rb") as src:
|
||||
data = src.read()
|
||||
|
||||
words = ", ".join(f"0x{byte:02x}" for byte in data)
|
||||
header = f"""// GENERATED FILE - do not edit by hand.
|
||||
// Produced by scripts/icon_to_header.py from screencast_icon/screencast_256.png.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace sc {{
|
||||
inline constexpr std::array<std::uint8_t, {len(data)}> app_icon_png = {{
|
||||
{words}
|
||||
}};
|
||||
}} // namespace sc
|
||||
"""
|
||||
with open(sys.argv[2], "w") as dst:
|
||||
dst.write(header)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -59,6 +59,9 @@ class SenderWindow : public Gtk::ApplicationWindow {
|
||||
SenderWindow() {
|
||||
set_title("screencast");
|
||||
set_default_size(440, 440);
|
||||
// GTK4 only supports themed icons: the PNG is installed into the
|
||||
// hicolor theme (see meson.build) and picked up by name.
|
||||
set_icon_name("screencast");
|
||||
|
||||
auto* box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 8);
|
||||
set_child(*box);
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
# Core public headers / include dependency.
|
||||
sc_core_inc = include_directories('../include')
|
||||
|
||||
# Embed the application icon so the GUI window and the SDL window icon work
|
||||
# from the build tree, after install, and on headless targets without any
|
||||
# runtime path lookup.
|
||||
icon_to_header = find_program('../scripts/icon_to_header.py')
|
||||
icon_png_data = custom_target('icon_png_data',
|
||||
input : files('../screencast_icon/screencast_256.png'),
|
||||
output : 'icon_png_data.h',
|
||||
command : [icon_to_header, '@INPUT@', '@OUTPUT@'])
|
||||
sc_icon_dep = declare_dependency(sources : [icon_png_data])
|
||||
|
||||
# The sender needs the PipeWire / xdg-desktop-portal capture backend;
|
||||
# receiver-only builds skip it entirely.
|
||||
build_sender = get_option('sender')
|
||||
|
||||
@@ -2,14 +2,30 @@
|
||||
|
||||
dep_sdl3 = dependency('sdl3')
|
||||
|
||||
# SDL3_image (window icon) is optional: no pkg-config file ships with it,
|
||||
# so find the library directly. Builds without it (e.g. the Pi receiver)
|
||||
# simply skip the window icon.
|
||||
cc = meson.get_compiler('cpp')
|
||||
dep_sdl3_image = cc.find_library('SDL3_image', required: false)
|
||||
if not dep_sdl3_image.found()
|
||||
dep_sdl3_image = disabler()
|
||||
endif
|
||||
|
||||
sc_render_args = []
|
||||
if dep_sdl3_image.found() and cc.has_header('SDL3_image/SDL_image.h', dependencies : dep_sdl3_image)
|
||||
sc_render_args += ['-DSC_HAS_WINDOW_ICON=1']
|
||||
endif
|
||||
|
||||
sc_render_sources = files('sdl_renderer.cpp')
|
||||
|
||||
sc_render = static_library('sc_render',
|
||||
sc_render_sources,
|
||||
include_directories : sc_core_inc,
|
||||
dependencies : [dep_sdl3])
|
||||
cpp_args : sc_render_args,
|
||||
dependencies : [dep_sdl3, dep_sdl3_image, sc_icon_dep])
|
||||
|
||||
sc_render_dep = declare_dependency(
|
||||
link_with : sc_render,
|
||||
include_directories : sc_core_inc,
|
||||
dependencies : [dep_sdl3])
|
||||
compile_args : sc_render_args,
|
||||
dependencies : [dep_sdl3, dep_sdl3_image])
|
||||
@@ -4,6 +4,16 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#ifdef SC_HAS_WINDOW_ICON
|
||||
|
||||
#include <SDL3_image/SDL_image.h>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "icon_png_data.h"
|
||||
|
||||
#endif
|
||||
|
||||
namespace sc {
|
||||
namespace {
|
||||
|
||||
@@ -52,6 +62,10 @@ class SdlRenderer final : public Renderer {
|
||||
(void)SDL_HideCursor();
|
||||
}
|
||||
|
||||
#ifdef SC_HAS_WINDOW_ICON
|
||||
set_window_icon();
|
||||
#endif
|
||||
|
||||
renderer_ = SDL_CreateRenderer(window_, nullptr);
|
||||
if (renderer_ == nullptr) {
|
||||
last_error_ = std::string{"SDL_CreateRenderer failed: "} + SDL_GetError();
|
||||
@@ -155,6 +169,29 @@ class SdlRenderer final : public Renderer {
|
||||
}
|
||||
|
||||
private:
|
||||
#ifdef SC_HAS_WINDOW_ICON
|
||||
// The icon is embedded at build time (see icon_png_data.h), so it works
|
||||
// without any runtime file lookup. Harmless under KMSDRM, where there is
|
||||
// no window manager to display it.
|
||||
void set_window_icon() {
|
||||
auto* stream = SDL_IOFromConstMem(sc::app_icon_png.data(), sc::app_icon_png.size());
|
||||
if (stream == nullptr) {
|
||||
std::fprintf(stderr, "screencast: failed to create icon stream: %s\n", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
// closeio=true: SDL_image consumes the stream, success or failure.
|
||||
SDL_Surface* icon_surface = IMG_Load_IO(stream, true);
|
||||
if (icon_surface == nullptr) {
|
||||
std::fprintf(stderr, "screencast: failed to load window icon: %s\n", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
if (SDL_SetWindowIcon(window_, icon_surface) != 0) {
|
||||
std::fprintf(stderr, "screencast: failed to set window icon: %s\n", SDL_GetError());
|
||||
}
|
||||
SDL_DestroySurface(icon_surface);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool recreate_texture(int width, int height) {
|
||||
if (texture_ != nullptr) {
|
||||
SDL_DestroyTexture(texture_);
|
||||
|
||||