Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -148,16 +148,24 @@ SRCS := src/main.c \
src/vaapi_encoder.c \
src/vaapi_decoder.c \
src/nvenc_encoder.c \
src/ffmpeg_encoder.c \
src/raw_encoder.c \
src/display_sdl2.c \
src/opus_codec.c \
src/audio_capture.c \
src/audio_capture_pulse.c \
src/audio_capture_dummy.c \
src/audio_playback.c \
src/audio_playback_pulse.c \
src/audio_playback_dummy.c \
src/network.c \
src/network_tcp.c \
src/network_reconnect.c \
src/input.c \
src/crypto.c \
src/discovery.c \
src/discovery_broadcast.c \
src/discovery_manual.c \
src/tray.c \
src/service.c \
src/qrcode.c \
Expand Down
26 changes: 26 additions & 0 deletions include/rootstream.h
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,14 @@ typedef struct {
* DISCOVERY - mDNS/Avahi service discovery
* ============================================================================ */

/* Peer history entry for quick reconnection (PHASE 5) */
typedef struct {
char hostname[256];
char address[256]; /* IP:port format */
uint16_t port;
char rootstream_code[ROOTSTREAM_CODE_MAX_LEN];
} peer_history_entry_t;

typedef struct {
void *avahi_client; /* Avahi client (opaque) */
void *avahi_group; /* Avahi entry group (opaque) */
Expand Down Expand Up @@ -476,6 +484,10 @@ typedef struct rootstream_ctx {

/* Discovery */
discovery_ctx_t discovery;

/* Peer history (PHASE 5) */
peer_history_entry_t peer_history_entries[MAX_PEER_HISTORY];
int num_peer_history;

/* Input */
int uinput_kbd_fd; /* Virtual keyboard */
Expand Down Expand Up @@ -739,6 +751,20 @@ int discovery_announce(rootstream_ctx_t *ctx);
int discovery_browse(rootstream_ctx_t *ctx);
void discovery_cleanup(rootstream_ctx_t *ctx);

/* Discovery - Broadcast (PHASE 5) */
int discovery_broadcast_announce(rootstream_ctx_t *ctx);
int discovery_broadcast_listen(rootstream_ctx_t *ctx, int timeout_ms);

/* Discovery - Manual (PHASE 5) */
int discovery_manual_add_peer(rootstream_ctx_t *ctx, const char *address_or_code);
int discovery_save_peer_to_history(rootstream_ctx_t *ctx, const char *hostname,
uint16_t port, const char *rootstream_code);
void discovery_list_peer_history(rootstream_ctx_t *ctx);
int discovery_parse_address(const char *address, char *hostname, uint16_t *port);
int discovery_parse_rootstream_code(rootstream_ctx_t *ctx, const char *code,
char *hostname, uint16_t *port);


/* --- Input (existing, polished) --- */
int rootstream_input_init(rootstream_ctx_t *ctx);
int rootstream_input_process(rootstream_ctx_t *ctx, input_event_pkt_t *event);
Expand Down
1 change: 1 addition & 0 deletions src/audio_capture_pulse.c
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ int audio_capture_init_pulse(rootstream_ctx_t *ctx) {

return 0;
#else
(void)ctx;
fprintf(stderr, "ERROR: PulseAudio support not compiled\n");
return -1;
#endif
Expand Down
1 change: 1 addition & 0 deletions src/audio_playback_pulse.c
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ int audio_playback_init_pulse(rootstream_ctx_t *ctx) {

return 0;
#else
(void)ctx;
fprintf(stderr, "ERROR: PulseAudio support not compiled\n");
return -1;
#endif
Expand Down
217 changes: 124 additions & 93 deletions src/discovery.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
#include <netinet/in.h>
#include <arpa/inet.h>

/* Discovery timeout for UDP broadcast (milliseconds) */
#define BROADCAST_DISCOVERY_TIMEOUT_MS 1000

#ifdef HAVE_AVAHI
#include <avahi-client/client.h>
#include <avahi-client/publish.h>
Expand Down Expand Up @@ -261,19 +264,24 @@ static void client_callback(AvahiClient *c, AvahiClientState state,
#endif /* HAVE_AVAHI */

/*
* Initialize discovery system
* Initialize discovery system with fallback support (PHASE 5)
*/
int discovery_init(rootstream_ctx_t *ctx) {
if (!ctx) {
fprintf(stderr, "ERROR: Invalid context\n");
return -1;
}

printf("INFO: Initializing peer discovery...\n");

#ifdef HAVE_AVAHI
/* Try mDNS/Avahi first (Tier 1) */
printf("INFO: Attempting discovery backend: mDNS/Avahi\n");

avahi_ctx_t *avahi = calloc(1, sizeof(avahi_ctx_t));
if (!avahi) {
fprintf(stderr, "ERROR: Cannot allocate Avahi context\n");
return -1;
fprintf(stderr, "WARNING: Cannot allocate Avahi context\n");
goto try_broadcast;
}

avahi->ctx = ctx;
Expand All @@ -282,8 +290,8 @@ int discovery_init(rootstream_ctx_t *ctx) {
avahi->simple_poll = avahi_simple_poll_new();
if (!avahi->simple_poll) {
free(avahi);
fprintf(stderr, "ERROR: Cannot create Avahi poll object\n");
return -1;
fprintf(stderr, "WARNING: Cannot create Avahi poll object\n");
goto try_broadcast;
}

/* Create client */
Expand All @@ -296,135 +304,158 @@ int discovery_init(rootstream_ctx_t *ctx) {
&error);

if (!avahi->client) {
fprintf(stderr, "ERROR: Cannot create Avahi client: %s\n",
fprintf(stderr, "WARNING: Cannot create Avahi client: %s\n",
avahi_strerror(error));
avahi_simple_poll_free(avahi->simple_poll);
free(avahi);
return -1;
goto try_broadcast;
}

ctx->discovery.avahi_client = avahi;
ctx->discovery.running = true;

printf("✓ Discovery initialized (Avahi)\n");
printf("✓ Discovery backend 'mDNS/Avahi' initialized\n");
return 0;

#else
fprintf(stderr, "WARNING: Built without Avahi support\n");
fprintf(stderr, "INFO: Auto-discovery disabled\n");
return 0;
try_broadcast:
fprintf(stderr, "WARNING: mDNS/Avahi failed, trying next...\n");
#endif

/* Try UDP Broadcast (Tier 2) */
printf("INFO: Attempting discovery backend: UDP Broadcast\n");

/* UDP broadcast doesn't need initialization, just mark discovery as available */
ctx->discovery.running = true;

printf("✓ Discovery backend 'UDP Broadcast' initialized\n");
printf("INFO: Manual peer entry also available (--peer-add)\n");

return 0;
}

/*
* Announce service on network
* Announce service on network (with fallback support - PHASE 5)
*/
int discovery_announce(rootstream_ctx_t *ctx) {
if (!ctx) return -1;

#ifdef HAVE_AVAHI
avahi_ctx_t *avahi = (avahi_ctx_t*)ctx->discovery.avahi_client;
if (!avahi || !avahi->client) {
fprintf(stderr, "ERROR: Discovery not initialized\n");
return -1;
}

/* Create entry group if needed */
if (!avahi->group) {
avahi->group = avahi_entry_group_new(avahi->client,
entry_group_callback, avahi);
if (avahi && avahi->client) {
/* mDNS/Avahi is available, use it */

/* Create entry group if needed */
if (!avahi->group) {
fprintf(stderr, "ERROR: Cannot create Avahi entry group\n");
return -1;
avahi->group = avahi_entry_group_new(avahi->client,
entry_group_callback, avahi);
if (!avahi->group) {
fprintf(stderr, "WARNING: Cannot create Avahi entry group\n");
goto try_broadcast;
}
}
}

/* Prepare TXT records */
AvahiStringList *txt = NULL;
char version_txt[64], pubkey_txt[256];

snprintf(version_txt, sizeof(version_txt), "version=%s", ROOTSTREAM_VERSION);
txt = avahi_string_list_add(txt, version_txt);

snprintf(pubkey_txt, sizeof(pubkey_txt), "code=%s",
ctx->keypair.rootstream_code);
txt = avahi_string_list_add(txt, pubkey_txt);

/* Add service */
int ret = avahi_entry_group_add_service_strlst(
avahi->group,
AVAHI_IF_UNSPEC, /* All interfaces */
AVAHI_PROTO_UNSPEC, /* IPv4 and IPv6 */
0, /* flags */
ctx->keypair.identity, /* Service name */
"_rootstream._udp", /* Service type */
NULL, /* Domain (use default) */
NULL, /* Host (use default) */
ctx->port, /* Port */
txt); /* TXT records */

avahi_string_list_free(txt);

if (ret < 0) {
fprintf(stderr, "ERROR: Cannot add service: %s\n",
avahi_strerror(ret));
return -1;
}
/* Prepare TXT records */
AvahiStringList *txt = NULL;
char version_txt[64], pubkey_txt[256];
snprintf(version_txt, sizeof(version_txt), "version=%s", ROOTSTREAM_VERSION);
txt = avahi_string_list_add(txt, version_txt);
snprintf(pubkey_txt, sizeof(pubkey_txt), "code=%s",
ctx->keypair.rootstream_code);
txt = avahi_string_list_add(txt, pubkey_txt);

/* Add service */
int ret = avahi_entry_group_add_service_strlst(
avahi->group,
AVAHI_IF_UNSPEC, /* All interfaces */
AVAHI_PROTO_UNSPEC, /* IPv4 and IPv6 */
0, /* flags */
ctx->keypair.identity, /* Service name */
"_rootstream._udp", /* Service type */
NULL, /* Domain (use default) */
NULL, /* Host (use default) */
ctx->port, /* Port */
txt); /* TXT records */

avahi_string_list_free(txt);

if (ret < 0) {
fprintf(stderr, "WARNING: Cannot add service: %s\n",
avahi_strerror(ret));
goto try_broadcast;
}

/* Commit changes */
ret = avahi_entry_group_commit(avahi->group);
if (ret < 0) {
fprintf(stderr, "ERROR: Cannot commit entry group: %s\n",
avahi_strerror(ret));
return -1;
}
/* Commit changes */
ret = avahi_entry_group_commit(avahi->group);
if (ret < 0) {
fprintf(stderr, "WARNING: Cannot commit entry group: %s\n",
avahi_strerror(ret));
goto try_broadcast;
}

printf("→ Announcing service on network\n");
return 0;
printf("→ Announcing service on network (mDNS)\n");
return 0;
}

#else
(void)ctx;
return 0;
try_broadcast:
#endif

/* Try UDP broadcast as fallback */
if (discovery_broadcast_announce(ctx) == 0) {
printf("→ Announcing service on network (UDP broadcast)\n");
return 0;
}

fprintf(stderr, "WARNING: All discovery announce methods failed\n");
fprintf(stderr, "INFO: Peers can still connect manually (--peer-add)\n");
return 0; /* Not fatal - manual entry always works */
}

/*
* Browse for services on network
* Browse for services on network (with fallback support - PHASE 5)
*/
int discovery_browse(rootstream_ctx_t *ctx) {
if (!ctx) return -1;

#ifdef HAVE_AVAHI
avahi_ctx_t *avahi = (avahi_ctx_t*)ctx->discovery.avahi_client;
if (!avahi || !avahi->client) {
fprintf(stderr, "ERROR: Discovery not initialized\n");
return -1;
}
if (avahi && avahi->client) {
/* mDNS/Avahi is available, use it */

/* Create browser */
avahi->browser = avahi_service_browser_new(
avahi->client,
AVAHI_IF_UNSPEC, /* All interfaces */
AVAHI_PROTO_UNSPEC, /* IPv4 and IPv6 */
"_rootstream._udp", /* Service type */
NULL, /* Domain (use default) */
0, /* flags */
browse_callback,
avahi);

if (!avahi->browser) {
fprintf(stderr, "WARNING: Cannot create service browser: %s\n",
avahi_strerror(avahi_client_errno(avahi->client)));
goto try_broadcast;
}

/* Create browser */
avahi->browser = avahi_service_browser_new(
avahi->client,
AVAHI_IF_UNSPEC, /* All interfaces */
AVAHI_PROTO_UNSPEC, /* IPv4 and IPv6 */
"_rootstream._udp", /* Service type */
NULL, /* Domain (use default) */
0, /* flags */
browse_callback,
avahi);

if (!avahi->browser) {
fprintf(stderr, "ERROR: Cannot create service browser: %s\n",
avahi_strerror(avahi_client_errno(avahi->client)));
return -1;
printf("→ Browsing for RootStream peers (mDNS)...\n");
return 0;
}

printf("→ Browsing for RootStream peers...\n");
return 0;
try_broadcast:
#endif

#else
(void)ctx;
/* Try UDP broadcast as fallback */
printf("→ Browsing for RootStream peers (UDP broadcast)...\n");

/* Listen for broadcast announcements with a short timeout */
if (discovery_broadcast_listen(ctx, BROADCAST_DISCOVERY_TIMEOUT_MS) > 0) {
printf(" Found peer via broadcast\n");
}

return 0;
#endif
}

/*
Expand Down
Loading
Loading