# Build and run Configuring, building and launching udpscope, the desktop entry target, the command-line options and the two vendored dependencies. Part of [CLAUDE.md](../../CLAUDE.md). ## Build and run ```sh git submodule update --init --recursive # deps/plotter is a submodule cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo cmake --build build ./build/udpscope --server 127.0.0.1 --port 8000 ./build/udpscope --server 10.0.0.5 --port 8000 --multicast 239.1.2.3 --interface 10.0.0.9 ``` The app can be launched from the shell rather than only from a terminal, but only on request: ```sh cmake --build build --target desktop-entry ``` That writes `org.udpscope.app.desktop` plus both app icons under `$XDG_DATA_HOME` (default `~/.local/share`), runs `desktop-file-validate` as a **hard failure** (when it is on `PATH`; absent, it says so and installs anyway), refreshes `update-desktop-database` and `gtk4-update-icon-cache` when they are on `PATH`, and prints the three paths it wrote — which is the uninstall instructions, since there is no uninstall target. `cmake --build build` alone never touches `$XDG_DATA_HOME`. The entry's `Exec` is the built binary's **absolute path in the build tree**, so **moving or deleting the build directory breaks it.** That is the price of having no install step and it is deliberate: the entry then always launches exactly the binary that was last built. No `StartupWMClass` is needed — GTK takes the WM class from the application ID, and the desktop file's basename is that same string, which is what associates a running window with the entry. Options: `--server`, `--port`, `--multicast`, `--interface`, `--buffer-mb` (per buffer, default 200), `--max-window`, `--holdoff`, `--recv-buffer`, `--plots` (the row count to *start* with, 1..`RowSet::kMaxRows`; the layout editor owns it afterwards). Run with no `--server` and udpscope asks which source to open: the sessions it has stored, plus a form for a new host. Every probe below passes `--server`, so none of them sees it. `deps/plotter` is built from source with only the GTK4 adapter enabled (`PLOT_BUILD_GTK4`); its tests and examples are forced off. `deps/udpsclient` is a vendored C99 source pair compiled into a static lib. **libadwaita ≥ 1.8 is a system dependency** (`pkg_check_modules(ADWAITA ... libadwaita-1>=1.8)`), for two widgets: the shortcut overview (`AdwShortcutsDialog`) and the About dialog (`AdwAboutDialog`). GTK4's own `GtkShortcutsWindow` is deprecated as of 4.18 and `AdwShortcutsDialog` is its replacement; `GtkAboutDialog` still exists but `adw_about_window_new()` is deprecated in favour of `AdwAboutDialog`, which shows the application icon as the page's own header. Two consequences worth knowing before editing. `adw_init()` is called — in `run_ui()` and, since the chooser runs first and must be styled the same, in `choose_source()` in place of its `gtk_init()` (both are idempotent) — so **the whole app is drawn with libadwaita's stylesheet**, not GTK's: a metric change there reaches every widget, and `layout over N frames` / `axes.w changes` is what says whether the layout still settles. And an `AdwDialog` is **not** a `GtkWindow`: both `show_about()` and `show_shortcuts()` keep an `AdwDialog*`, cleared on `::closed` (there is no `::close-request`) and closed with `adw_dialog_close()`. They share one `on_dialog_closed()` handler keyed by a pointer-to-pointer (`gpointer slot` cast to `AdwDialog**`), so one handler clears either pointer without knowing which dialog it is. Presenting either on this app's plain `GtkApplicationWindow` is supported and warning-free; it was checked. `deps/plotter` is upstream's own repository and this app's figure support was added there rather than worked around here: `Figure::set_min_y_gutter()` (a floor under the common left gutter), `Figure::set_right_gutter()` / `Plot::set_right_gutter()` (an empty band on the *other* side, which the layout had no concept of — `axes` reached the viewport's right edge, so the rightmost x tick label was centred on a line it had half of itself outside of; the guard is `figure_rows_share_one_right_gutter`), and in the GTK4 adapter `PlotArea`'s **figure mode** — `set_figure()`, one `input::Controller` per row, nearest-row event routing, a figure-wide crosshair — plus `PlotArea::row_at()` for naming the row a pointer is over. The render-quality work went there for the same reason: `kTextFS` now **filters** the glyph atlas (a 2×2 bilinear clamped to the instance's own cell) instead of point-sampling it, and `LineProgram::set_snap()` / `u_snap` rounds an axis-aligned segment's ink onto whole pixels for the chrome that frames a plot (grid and axis lines), never for anything drawn at a data position. `generate_ticks()` also stores the tick at the origin as exactly `0.0` — the walk starts at `ceil(min/stride)*stride` and so never lands on zero when the stride is not a power of two, which labelled it `-139a`. `format_time()` picks its unit rung by the **rounded** mantissa (`t < u.scale * 0.9995`, and the same slack on the whole-second branch) — the `>= 999.5` spill guard `format_si()` in the same file always had — because a tick a hair under a rung's top rounds to 1000 at three significant figures and `%.3g` writes that `1e+03`, which is how a one-second tick read `1e+3ms`. All four carry upstream tests — `the_atlas_is_filtered_not_point_sampled`, `grid_hairlines_land_on_whole_pixels`, `the_tick_at_the_origin_reads_zero` and `format_time_never_lets_a_field_reach_its_radix`. The minor grid's look is set there too, not here: `grid_minor` carries 50% alpha, `grid_minor_width` is 0.75 (sub-pixel, expressed as coverage at 1x) and `grid_minor_dash_on`/`_off` are 3/3 — a fragment discard, no extra geometry — so the subdivisions read as texture under the labelled grid instead of as a second set of ticks (`the_minor_grid_is_thinner_dashed_and_dimmer_than_the_major`). README records what they cured. Changing any of this means committing in the submodule and moving its pin, and running the library's own suite as well as the udpscope probes (see [verification.md](verification.md)): ```sh cmake --build deps/plotter/build/debug xvfb-run -a deps/plotter/build/debug/tests/plot_tests # expect "0 failed" ```