This page explains how to build, package, release and extend dlna-server. Work on the review branch.
| Need | Windows | Linux |
|---|---|---|
| Compiler | Visual Studio 2022 (MSVC, C++17) | GCC or Clang with C++17 |
| Build system | CMake 3.20 or newer | CMake 3.20 or newer |
| libcurl | vcpkg, static triplet (x64-windows-static or x86-windows-static) |
libcurl4-openssl-dev |
| Window toolkit | Win32 (in the SDK) | GTK 4 (libgtk-4-dev), libx11-dev |
| Scripts | PowerShell, Python 3, gh (releases) |
Bash, Python 3, gh (releases) |
| Tests | Python 3 and pytest | Python 3 and pytest |
The Windows build links the static MSVC runtime. dlna_core and the GTK program compile with /W4 /permissive- (MSVC) or -Wall -Wextra -Wpedantic (others).
| Folder | Content |
|---|---|
src/ |
All C++ code. |
resources/ |
app.rc, resource.h, icons, fonts/ (Selawik), gtk/ style sheets and SVG controls. |
packaging/ |
linux/ (launcher, desktop, metainfo, AppRun), flatpak/, macos/. |
scripts/ |
Build, packaging and release scripts, version.py, lib_identity.sh. |
tools/ |
gen_gtk_style.py. |
tests/ |
Python tests. |
identity.env |
Product name and application IDs. |
identity.env is the single source for the names the app uses. CMake and the scripts read it. It must define DLNA_APP_ID, DLNA_REVERSE_DNS_PREFIX and DLNA_PRODUCT_NAME. The GTK application ID is <prefix>.<app id>. CMake writes generated/version.h and generated/app_identity.h into the build folder. Do not hard-code these names in code or scripts.
build.bat --x64
build.bat --x86
build.bat :: both
build.bat --version v2.26.7631-build5build.bat calls scripts/build-windows.ps1. It finds vcpkg through VCPKG_ROOT, VCPKG_INSTALLATION_ROOT or %USERPROFILE%\vcpkg, installs curl if needed, runs CMake, and writes output\winx64 or output\winx86 with dlna-server.exe and a zip file.
For Visual Studio, CMakeSettings.json has x64-Debug-vcpkg and x64-Release-vcpkg configurations. Set VCPKG_ROOT first.
./build.sh # binaries in output/linux
./build.sh --deb # also a .deb
./build.sh --appimage
./build.sh --flatpak
./build.sh --install # build a .deb, remove old installs, installOutputs in output/linux: dlna-server, dlna-server-gui, dlna-server-gui-bin, share/, and any package files.
Warning.
scripts/build_linux.shstops runningdlna-server*processes, emptiesoutput/linux, and installs missing build packages withsudo. It needs passwordlesssudo, or theDLNA_SUDO_PASSWORDenvironment variable. It builds in a temporary folder under/tmp.
Build from Windows with build.bat --linux. It runs the Linux script in WSL (distribution Ubuntu). build.bat --install runs scripts/install_linux.sh in WSL.
| Option | Default | Effect |
|---|---|---|
DLNA_VERSION |
0.0.00000 |
Version. The scripts set it from version.py. |
DLNA_ENABLE_GTK4_GUI |
ON (non-Windows) |
Builds dlna-server-gui-bin. |
DLNA_FLATPAK_BUILD |
OFF |
Uses the Flatpak application ID. |
dlna_core: static library with all shared and platform code.dlna-server: Windows window program, or the Linux command-line program.dlna-server-gui-gtk4: output namedlna-server-gui-bin.dlna_gtk_style: runstools/gen_gtk_style.pyto writeresources/gtk/styles1.css.
scripts/version.py makes tags like v2.26.7631-build5.
v2.<YY>.<MM><DD><patch>-build<minute>
2is the major version, fixed in the script.MMisfloor(month × 100 / 13)andDDisfloor(day × 100 / 32). Both have two digits.patchis one optional digit. Set it with--patchor theDLNA_PATCHvariable.- If a file with that version already exists under
output/, the script raises the patch digit until the version is new. version.py --numeric-of <tag>prints the number withoutvand-build.
| Script | Output | Notes |
|---|---|---|
scripts/build_deb.sh |
.deb |
Calls build_linux.sh --deb. Uses CPack. Depends on libcurl4. |
scripts/build_appimage.sh |
.AppImage |
Downloads linuxdeploy and checks its SHA-256. |
scripts/build_flatpak.sh |
.flatpak |
Needs flatpak and flatpak-builder. Uses the GNOME 47 runtime. |
scripts/install_linux.sh |
none | Removes old installs and old desktop files, then installs the newest .deb. |
Tag the commit, build, then upload:
./build.sh --release # Linux assets
./build.sh --release --notes # with generated notes
./build.sh --release --update=<tag> # add assets to an existing releasebuild.bat --release- Needs the
ghcommand, signed in. - Windows zips come from
output\winx64andoutput\winx86. Linux files come fromoutput/linux. Only files that contain the tag's version number are uploaded. --notescalls the Gemini API with the commit list. It needsGEMINI_API_KEY. If the call fails, the notes are the commit list.scripts/release-diff.sh <prev_tag> <tag>writes asrc/diff for the notes.
- Put shared code in one file. Put platform code in a pair such as
httpserver.cppandposix_httpserver.cpp. The Windows file name has no prefix, or ends in_win. - If both platforms need the same rule, move it to a
*_common.cppor a header. Examples:http_common.cpp,ssdp_common.cpp,netutils_common.cpp,media_sources_common.cpp,server_common.cpp. - The scan code calls only the functions in
fs_backend.h. Win32 and POSIX each implement it, so both run the same scan logic.
Rules that decide behavior live in small header-only functions or classes with no window or socket types. The two user interfaces call them. Tests call them through hidden command-line flags.
| Header | Decision |
|---|---|
startup_mode.h |
Run without a window? |
server_close_policy.h, close_pending_state.h |
May the app close now? Close after a stop? |
health_check_policy.h |
How many failed checks mean "dead"? |
source_drop_policy.h |
Is a drop allowed? |
function_key_action.h |
What does F1 or F5 do? |
source_list_focus.h, hover_focus_state.h |
Delete button and highlight state. |
modal_stack.h |
Which dialog is on top? |
input_gate.h |
Is the Add button enabled? |
network_interface_policy.h |
Use an adapter that is not on the allow list? |
browse_page_cap.h, search_cache_budget.h |
Response and cache limits. |
transmitfile_chunking.h |
Chunk sizes for TransmitFile. |
copydata_validation.h |
Is a WM_COPYDATA size plausible? |
geometry_dump_policy.h |
Dump dialog geometry? |
settings_restart.cpp |
Which changed settings need a restart? |
tray_notify.cpp |
Decode a tray callback. |
access_keys.cpp |
Choose access letters for labels. |
Hidden test hooks
Flags that start with --print- or --dump- run one small check, print the result, and exit. cli_print_hooks_win.cpp and cli_print_hooks_posix.cpp implement them. IsTestOnlyFlag() in cli_flags.h recognizes them. The GTK program has its own hooks, for example --print-settings-reopen. To add a check:
- Put the logic in a pure header.
- Add a
--print-<name>branch in both hook files, or ingtk4_gui_main.cppfor GTK-only checks. - Write the Python test.
- Wrap every thread entry that can allocate or call libraries in
RunGuarded(L"name", fn)(thread_guard.h). An exception that leaves a thread function ends the process. - Pool tasks and Windows thread-pool callbacks already catch exceptions.
- Never call
LogPrintwhile holdingConfig::m_mutex.LogPrinttakes a shared lock on it and Linux throwsEDEADLKon a second lock by the same thread. - Do not do file or network I/O while holding a lock.
Config::mediaSourcesmay be read directly only on the interface thread. If any background thread writes it, change every direct read toSnapshot().- Use
Config::Mutate()for any write that can race with other threads after the server starts. - Start and stop functions guard against re-entry with an atomic busy flag. Keep that pattern.
Every cache and queue has a limit. See Architecture. When you add a cache, add a limit and a pure predicate for the eviction rule.
- Add a row to
kFormatsindlna_utils.cpp: extension, MIME type, UPnP class, DLNA profile, byte-seek flag. - Add the extension (without dot) to
GetMediaSourceFileExtensions()inmedia_source_file_types.h. This list feeds the file pickers on both platforms. It lists video, audio and playlists. Images are inkFormatsbut not in this list. - For a playlist type, also update
IsPlaylistSourcePath()innetwork_sources.cppand the parser.
- Add the field to
Configand toConfigSnapshotinconfig.h.Config::Snapshot()fillsConfigSnapshotby position, so keep the field order identical in both structs. - Read and write it in
config.cppandposix_config.cpp(Load,Save,Snapshot). - Add a row to
DetermineSettingsRequiringRestart()insettings_restart.cppif it is read only at start. - Add controls:
resource.h,app.rc,settingsdlg.cpp(Windows) andgtk4_gui_main.cpp(GTK). Keep both dialogs in step. - Add a row to
settings_help.h. - Add the key to Configuration.
- Add a row to
GetCliFlagTable()incli_flags.h. The help dialog and--helpuse it. - Handle the option in
main.cpp,posix_main.cppandgtk4_gui_main.cpp. The hook files have a short pre-scan loop that applies the same options before a hook runs. Update it too.
The Linux window loads four sheets in this order. A later layer wins over an earlier one.
| Order | File | Role | Priority |
|---|---|---|---|
| 1 | resources/gtk/styles1.css |
Generated from src/ui_tokens.h by tools/gen_gtk_style.py. Do not edit by hand. |
USER |
| 2 | styles2.css |
Square, Windows-10-style defaults for lists, entries, menus, scroll bars, switches and tooltips. | USER + 1 |
| 3 | styles3.css |
Replaces rounded GTK shapes with square ones for toolbar buttons, entries, the source list, and popovers. | USER + 1 |
| 4 | styles4.css |
Checked subset: focus ring, disabled opacity, frames, selection, flat buttons. | USER + 1 |
| 5 | styles0.css |
Overrides from the design file (title bar, toolbar, dialogs, source list). | USER + 2 |
The loader is OnAppStartup() in gtk4_gui_main.cpp. Sizes and positions for the Linux window are in ui_tokens_posix.h. They differ from ui_tokens.h on purpose, so do not "fix" one to match the other.
Settings that GTK 4 does not allow, such as a short tooltip delay, are done in code. The source list shows its full-path hint with a popover that follows the pointer.
Tests are Python and use pytest. pytest.ini defines these markers:
| Marker | Meaning |
|---|---|
posix_only |
Runs on Linux and macOS only. |
windows_only |
Runs on Windows only. |
gui_only |
Needs the GTK window and a display. Deselected by default. |
needs_xvfb |
Starts the window under its own Xvfb. Run with -p no:xvfb (already in addopts). |
needs_wm |
Needs a window manager. Deselected when none is installed. |
pytest.ini also skips the build output folders. Test details are in the separate testing document.