@@ -302,202 +302,228 @@ set(WINDOWS_SOURCES
302302 src/service.c
303303)
304304
305+ # =============================================================================
306+ # PHASE-93: rootstream_core — Linkable Static Library
307+ #
308+ # ALL protocol/crypto/decode/encode/network/audio logic lives here.
309+ # Executables (rootstream, rstr-player, rootstream-client) and the KDE client
310+ # link this single library instead of re-compiling the same sources N times.
311+ #
312+ # WHY A LIBRARY?
313+ # --------------
314+ # Before PHASE-93, each executable compiled every source file independently.
315+ # That meant:
316+ # 1. Duplicate compilation = longer build times.
317+ # 2. The KDE client could not link the real backend — it had no library to
318+ # link against, forcing it to stub or duplicate all protocol logic.
319+ # 3. Adding src/client_session.c (PHASE-94) would have to be added to every
320+ # executable's source list separately.
321+ #
322+ # With rootstream_core STATIC:
323+ # - One authoritative compiled object for all streaming logic.
324+ # - KDE client links it via add_subdirectory (see clients/kde-plasma-client).
325+ # - Tests can link specific subsets without re-compiling the world.
326+ # - PUBLIC include/ means any downstream target automatically gets the
327+ # correct include paths with no extra configuration.
328+ # =============================================================================
329+
305330# =============================================================================
306331# Targets
307332# =============================================================================
308333
309334if (UNIX AND NOT APPLE )
310- # Linux: Full host + client
311- add_executable (rootstream
312- src/main.c
335+ # ── rootstream_core: the shared backend library ────────────────────────
336+ #
337+ # Contains everything EXCEPT:
338+ # - src/main.c (CLI entry point — stays in rootstream exe)
339+ # - tools/rstr-player.c (player tool entry point)
340+ # - src/tray*.c (tray UI — only needed by the host executable)
341+ #
342+ # display_sdl2.c is included because service_run_client() (which is in
343+ # service.c → rootstream_core) still calls display_init() for the SDL
344+ # fallback path. Once PHASE-94 is complete, service_run_client() becomes
345+ # a thin wrapper and display_sdl2.c can be moved to the executable.
346+ add_library (rootstream_core STATIC
313347 ${COMMON_SOURCES}
314348 ${LINUX_SOURCES}
315349 ${PLATFORM_SOURCES}
350+ src/client_session.c
316351 )
317352
318- target_include_directories (rootstream PRIVATE
319- ${CMAKE_SOURCE_DIR} /include
320- ${SDL2_INCLUDE_DIRS}
321- ${DRM_INCLUDE_DIRS}
322- ${GTK3_INCLUDE_DIRS}
353+ # PUBLIC include dir: any target that links rootstream_core automatically
354+ # receives the correct include path for rootstream.h and
355+ # rootstream_client_session.h without needing a separate
356+ # target_include_directories call.
357+ target_include_directories (rootstream_core
358+ PUBLIC
359+ ${CMAKE_SOURCE_DIR} /include
360+ PRIVATE
361+ ${CMAKE_SOURCE_DIR} /src
362+ ${SDL2_INCLUDE_DIRS}
363+ ${DRM_INCLUDE_DIRS}
364+ ${GTK3_INCLUDE_DIRS}
323365 )
324366
325- target_link_libraries (rootstream PRIVATE
367+ # Link all system libraries against rootstream_core (PUBLIC where needed
368+ # by downstream, PRIVATE for internal use only).
369+ target_link_libraries (rootstream_core PUBLIC
326370 ${SDL2_LIBRARIES}
327371 ${DRM_LIBRARIES}
328372 m pthread
329373 )
330374
331- # Conditional libraries
332375 if (unofficial-sodium_FOUND)
333- target_link_libraries (rootstream PRIVATE unofficial-sodium::sodium )
376+ target_link_libraries (rootstream_core PUBLIC unofficial-sodium::sodium )
334377 else ()
335- target_link_libraries (rootstream PRIVATE ${SODIUM_LIBRARIES} )
336- target_include_directories (rootstream PRIVATE ${SODIUM_INCLUDE_DIRS} )
378+ target_link_libraries (rootstream_core PUBLIC ${SODIUM_LIBRARIES} )
379+ target_include_directories (rootstream_core PUBLIC ${SODIUM_INCLUDE_DIRS} )
337380 endif ()
338381
339382 if (Opus_FOUND)
340- target_link_libraries (rootstream PRIVATE Opus::opus )
383+ target_link_libraries (rootstream_core PUBLIC Opus::opus )
341384 else ()
342- target_link_libraries (rootstream PRIVATE ${OPUS_LIBRARIES} )
343- target_include_directories (rootstream PRIVATE ${OPUS_INCLUDE_DIRS} )
385+ target_link_libraries (rootstream_core PUBLIC ${OPUS_LIBRARIES} )
386+ target_include_directories (rootstream_core PUBLIC ${OPUS_INCLUDE_DIRS} )
344387 endif ()
345388
346389 if (VAAPI_FOUND)
347- target_link_libraries (rootstream PRIVATE ${VAAPI_LIBRARIES} )
348- target_include_directories (rootstream PRIVATE ${VAAPI_INCLUDE_DIRS} )
390+ target_link_libraries (rootstream_core PUBLIC ${VAAPI_LIBRARIES} )
391+ target_include_directories (rootstream_core PUBLIC ${VAAPI_INCLUDE_DIRS} )
349392 endif ()
350393
351394 if (ALSA_FOUND)
352- target_link_libraries (rootstream PRIVATE ${ALSA_LIBRARIES} )
353- target_include_directories (rootstream PRIVATE ${ALSA_INCLUDE_DIRS} )
395+ target_link_libraries (rootstream_core PRIVATE ${ALSA_LIBRARIES} )
396+ target_include_directories (rootstream_core PRIVATE ${ALSA_INCLUDE_DIRS} )
354397 endif ()
355398
356399 if (PULSEAUDIO_FOUND)
357- target_link_libraries (rootstream PRIVATE ${PULSEAUDIO_LIBRARIES} )
358- target_include_directories (rootstream PRIVATE ${PULSEAUDIO_INCLUDE_DIRS} )
400+ target_link_libraries (rootstream_core PRIVATE ${PULSEAUDIO_LIBRARIES} )
401+ target_include_directories (rootstream_core PRIVATE ${PULSEAUDIO_INCLUDE_DIRS} )
359402 endif ()
360403
361404 if (PIPEWIRE_FOUND)
362- target_link_libraries (rootstream PRIVATE ${PIPEWIRE_LIBRARIES} )
363- target_include_directories (rootstream PRIVATE ${PIPEWIRE_INCLUDE_DIRS} )
405+ target_link_libraries (rootstream_core PRIVATE ${PIPEWIRE_LIBRARIES} )
406+ target_include_directories (rootstream_core PRIVATE ${PIPEWIRE_INCLUDE_DIRS} )
364407 endif ()
365408
366409 if (NOT HEADLESS AND GTK3_FOUND)
367- target_link_libraries (rootstream PRIVATE ${GTK3_LIBRARIES} )
368- target_include_directories (rootstream PRIVATE ${GTK3_INCLUDE_DIRS} )
410+ target_link_libraries (rootstream_core PRIVATE ${GTK3_LIBRARIES} )
411+ target_include_directories (rootstream_core PRIVATE ${GTK3_INCLUDE_DIRS} )
369412 endif ()
370413
371414 if (AVAHI_FOUND)
372- target_link_libraries (rootstream PRIVATE ${AVAHI_LIBRARIES} )
373- target_include_directories (rootstream PRIVATE ${AVAHI_INCLUDE_DIRS} )
415+ target_link_libraries (rootstream_core PRIVATE ${AVAHI_LIBRARIES} )
416+ target_include_directories (rootstream_core PRIVATE ${AVAHI_INCLUDE_DIRS} )
374417 endif ()
375418
376419 if (X11_FOUND)
377- target_link_libraries (rootstream PRIVATE ${X11_LIBRARIES} )
378- target_include_directories (rootstream PRIVATE ${X11_INCLUDE_DIRS} )
420+ target_link_libraries (rootstream_core PRIVATE ${X11_LIBRARIES} )
421+ target_include_directories (rootstream_core PRIVATE ${X11_INCLUDE_DIRS} )
379422 endif ()
380423
381424 if (QRENCODE_FOUND)
382- target_link_libraries (rootstream PRIVATE ${QRENCODE_LIBRARIES} )
383- target_include_directories (rootstream PRIVATE ${QRENCODE_INCLUDE_DIRS} )
425+ target_link_libraries (rootstream_core PRIVATE ${QRENCODE_LIBRARIES} )
426+ target_include_directories (rootstream_core PRIVATE ${QRENCODE_INCLUDE_DIRS} )
384427 endif ()
385428
386429 if (PNG_FOUND)
387- target_link_libraries (rootstream PRIVATE ${PNG_LIBRARIES} )
388- target_include_directories (rootstream PRIVATE ${PNG_INCLUDE_DIRS} )
430+ target_link_libraries (rootstream_core PRIVATE ${PNG_LIBRARIES} )
431+ target_include_directories (rootstream_core PRIVATE ${PNG_INCLUDE_DIRS} )
389432 endif ()
390-
433+
391434 if (NCURSES_FOUND)
392- target_link_libraries (rootstream PRIVATE ${NCURSES_LIBRARIES} )
393- target_include_directories (rootstream PRIVATE ${NCURSES_INCLUDE_DIRS} )
435+ target_link_libraries (rootstream_core PRIVATE ${NCURSES_LIBRARIES} )
436+ target_include_directories (rootstream_core PRIVATE ${NCURSES_INCLUDE_DIRS} )
394437 endif ()
395-
438+
396439 if (FFMPEG_FOUND)
397- target_link_libraries (rootstream PRIVATE ${FFMPEG_LIBRARIES} )
398- target_include_directories (rootstream PRIVATE ${FFMPEG_INCLUDE_DIRS} )
440+ target_link_libraries (rootstream_core PUBLIC ${FFMPEG_LIBRARIES} )
441+ target_include_directories (rootstream_core PUBLIC ${FFMPEG_INCLUDE_DIRS} )
399442 endif ()
400443
401- # Recording player tool
402- add_executable (rstr-player
403- tools/rstr-player.c
404- src/recording.c
405- src/vaapi_decoder.c
406- src/display_sdl2.c
407- src/network.c
408- src/network_tcp.c
409- src/network_reconnect.c
410- src/packet_validate.c
411- src/crypto.c
412- src/config.c
413- src/input.c
414- src/opus_codec.c
415- src/audio_playback.c
416- src/audio_playback_pulse.c
417- src/audio_playback_pipewire.c
418- src/audio_playback_dummy.c
419- src/latency.c
420- ${PLATFORM_SOURCES}
444+ # ── rootstream: host + client CLI executable ───────────────────────────
445+ #
446+ # src/main.c is the ONLY source here — all real logic is in rootstream_core.
447+ # This keeps the executable thin and makes it easy to verify that no
448+ # protocol logic accidentally lives only in main.c.
449+ add_executable (rootstream
450+ src/main.c
421451 )
422452
423- target_include_directories (rstr-player PRIVATE
424- ${CMAKE_SOURCE_DIR} /include
425- ${SDL2_INCLUDE_DIRS}
426- )
453+ target_link_libraries (rootstream PRIVATE rootstream_core )
427454
428- target_link_libraries (rstr-player PRIVATE
429- ${SDL2_LIBRARIES}
430- ${VAAPI_LIBRARIES}
431- ${ALSA_LIBRARIES}
432- ${SODIUM_LIBRARIES}
433- ${AVAHI_LIBRARIES}
434- m pthread
455+ # ── rstr-player: recording playback tool ──────────────────────────────
456+ #
457+ # Links rootstream_core for all decode/display/audio logic.
458+ # Only the player tool's own main (tools/rstr-player.c) is compiled here.
459+ add_executable (rstr-player
460+ tools/rstr-player.c
435461 )
436462
437- if (PULSEAUDIO_FOUND)
438- target_link_libraries (rstr-player PRIVATE ${PULSEAUDIO_LIBRARIES} )
439- target_include_directories (rstr-player PRIVATE ${PULSEAUDIO_INCLUDE_DIRS} )
440- endif ()
463+ target_link_libraries (rstr-player PRIVATE rootstream_core )
441464
442- if (PIPEWIRE_FOUND)
443- target_link_libraries (rstr-player PRIVATE ${PIPEWIRE_LIBRARIES} )
444- target_include_directories (rstr-player PRIVATE ${PIPEWIRE_INCLUDE_DIRS} )
445- endif ()
446-
447- if (Opus_FOUND)
448- target_link_libraries (rstr-player PRIVATE Opus::opus )
449- else ()
450- target_link_libraries (rstr-player PRIVATE ${OPUS_LIBRARIES} )
451- endif ()
465+ # KDE Plasma client is built via its own CMakeLists.txt which uses
466+ # add_subdirectory(../.. rootstream_build) to pull in rootstream_core.
467+ # See: clients/kde-plasma-client/CMakeLists.txt (PHASE-93.2)
452468
453469endif ()
454470
455471if (WIN32 )
456- # Windows: Client only
457- add_executable (rootstream-client WIN32
458- src/main_client.c
472+ # ── Windows: rootstream_core_win (Windows backend library) ────────────
473+ #
474+ # Same principle as the Linux library: all backend logic in one library,
475+ # thin executable on top.
476+ add_library (rootstream_core_win STATIC
459477 ${COMMON_SOURCES}
460478 ${WINDOWS_SOURCES}
461479 ${PLATFORM_SOURCES}
480+ src/client_session.c
462481 )
463482
464- target_include_directories (rootstream-client PRIVATE
465- ${CMAKE_SOURCE_DIR} /include
483+ target_include_directories (rootstream_core_win
484+ PUBLIC
485+ ${CMAKE_SOURCE_DIR} /include
486+ PRIVATE
487+ ${CMAKE_SOURCE_DIR} /src
466488 )
467489
468- # Windows system libraries
469- target_link_libraries (rootstream-client PRIVATE
470- ws2_32 # Winsock
471- mfplat # Media Foundation
472- mfuuid # Media Foundation GUIDs
473- mf # Media Foundation
474- ole32 # COM
475- d3d11 # Direct3D 11
476- dxgi # DXGI
490+ target_link_libraries (rootstream_core_win PUBLIC
491+ ws2_32 mfplat mfuuid mf ole32 d3d11 dxgi
477492 )
478493
479- # vcpkg dependencies
480494 if (unofficial-sodium_FOUND)
481- target_link_libraries (rootstream-client PRIVATE unofficial-sodium::sodium )
495+ target_link_libraries (rootstream_core_win PUBLIC unofficial-sodium::sodium )
482496 endif ()
483-
484497 if (SDL2_FOUND)
485- target_link_libraries (rootstream-client PRIVATE SDL2::SDL2 SDL2::SDL2main )
498+ target_link_libraries (rootstream_core_win PUBLIC SDL2::SDL2 SDL2::SDL2main )
486499 endif ()
487-
488500 if (Opus_FOUND)
489- target_link_libraries (rootstream-client PRIVATE Opus::opus )
501+ target_link_libraries (rootstream_core_win PUBLIC Opus::opus )
490502 endif ()
503+
504+ # Windows client executable — thin, links the library
505+ add_executable (rootstream-client WIN32
506+ src/main_client.c
507+ )
508+
509+ target_link_libraries (rootstream-client PRIVATE rootstream_core_win )
491510endif ()
492511
493512# =============================================================================
494513# Installation
495514# =============================================================================
496515
497516if (UNIX AND NOT APPLE )
517+ install (TARGETS rootstream_core ARCHIVE DESTINATION lib)
498518 install (TARGETS rootstream RUNTIME DESTINATION bin)
499519 install (TARGETS rstr-player RUNTIME DESTINATION bin)
500520
521+ # Public headers (needed by downstream consumers like KDE client)
522+ install (FILES
523+ include/rootstream.h
524+ include/rootstream_client_session.h
525+ DESTINATION include/rootstream)
526+
501527 # Desktop file
502528 install (FILES assets/rootstream.desktop
503529 DESTINATION share/applications)
@@ -508,6 +534,7 @@ if(UNIX AND NOT APPLE)
508534endif ()
509535
510536if (WIN32 )
537+ install (TARGETS rootstream_core_win ARCHIVE DESTINATION lib)
511538 install (TARGETS rootstream-client RUNTIME DESTINATION bin)
512539endif ()
513540
@@ -517,7 +544,8 @@ endif()
517544
518545enable_testing ()
519546
520- # Unit tests
547+ # Unit tests — link against rootstream_core to avoid re-listing source files
548+ # and to ensure the tests exercise the same compiled code as the executables.
521549add_executable (test_crypto tests/unit/test_crypto.c src/crypto.c ${PLATFORM_SOURCES} )
522550target_include_directories (test_crypto PRIVATE ${CMAKE_SOURCE_DIR} /include )
523551if (unofficial-sodium_FOUND)
0 commit comments