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
16 changes: 16 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ else
ifeq ($(GTK_FOUND),yes)
CFLAGS += $(shell pkg-config --cflags $(GTK_PKG))
LIBS += $(shell pkg-config --libs $(GTK_PKG))
CFLAGS += -DHAVE_GTK
else
$(error GTK3 development files not found. Install gtk3 and pkg-config (e.g., libgtk-3-dev or gtk3-devel) or run `make deps` for checks. Alternatively set HEADLESS=1 for a non-GUI build.)
endif
Expand Down Expand Up @@ -99,6 +100,16 @@ else
$(info X11 not found - X11 capture backend will be disabled)
endif

# ncurses (optional, for Terminal UI fallback)
NCURSES_FOUND := $(shell pkg-config --exists ncurses && echo yes)
ifeq ($(NCURSES_FOUND),yes)
CFLAGS += $(shell pkg-config --cflags ncurses)
LIBS += $(shell pkg-config --libs ncurses)
CFLAGS += -DHAVE_NCURSES
else
$(info ncurses not found - Terminal UI will be disabled)
endif

# NVENC (optional, for NVIDIA GPU encoding)
# Check both CUDA and NVENC SDK headers
CUDA_FOUND := $(shell pkg-config --exists cuda && echo yes)
Expand Down Expand Up @@ -162,16 +173,21 @@ SRCS := src/main.c \
src/network_tcp.c \
src/network_reconnect.c \
src/input.c \
src/input_xdotool.c \
src/input_logging.c \
src/crypto.c \
src/discovery.c \
src/discovery_broadcast.c \
src/discovery_manual.c \
src/tray.c \
src/tray_tui.c \
src/tray_cli.c \
src/service.c \
src/qrcode.c \
src/config.c \
src/latency.c \
src/recording.c \
src/diagnostics.c \
src/platform/platform_linux.c \
src/packet_validate.c

Expand Down
46 changes: 46 additions & 0 deletions include/rootstream.h
Original file line number Diff line number Diff line change
Expand Up @@ -520,14 +520,23 @@ typedef struct rootstream_ctx {
const char *audio_play_name; /* Name of active audio playback backend */
const char *decoder_name; /* Name of active decoder backend */
const char *display_name; /* Name of active display backend */
const char *discovery_name; /* Name of active discovery backend (PHASE 6) */
const char *input_name; /* Name of active input backend (PHASE 6) */
const char *gui_name; /* Name of active GUI backend (PHASE 6) */
} active_backend;

/* User preferences for backend override */
struct {
const char *capture_override; /* User-specified capture backend */
const char *encoder_override; /* User-specified encoder backend */
const char *input_override; /* User-specified input backend (PHASE 6) */
const char *gui_override; /* User-specified GUI backend (PHASE 6) */
bool verbose; /* Print fallback attempts */
} backend_prefs;

/* Private data pointers for fallback backends (PHASE 6) */
void *input_priv; /* Input backend private data */
void *tray_priv; /* Tray/GUI backend private data */
} rootstream_ctx_t;

/* Encoder backend abstraction for multi-tier fallback */
Expand Down Expand Up @@ -778,6 +787,43 @@ void tray_show_peers(rootstream_ctx_t *ctx);
void tray_run(rootstream_ctx_t *ctx);
void tray_cleanup(rootstream_ctx_t *ctx);

/* --- Tray UI Backends (PHASE 6) --- */
int tray_init_tui(rootstream_ctx_t *ctx, int argc, char **argv);
void tray_update_status_tui(rootstream_ctx_t *ctx, tray_status_t status);
void tray_show_qr_code_tui(rootstream_ctx_t *ctx);
void tray_show_peers_tui(rootstream_ctx_t *ctx);
void tray_run_tui(rootstream_ctx_t *ctx);
void tray_cleanup_tui(rootstream_ctx_t *ctx);

int tray_init_cli(rootstream_ctx_t *ctx, int argc, char **argv);
void tray_update_status_cli(rootstream_ctx_t *ctx, tray_status_t status);
void tray_show_qr_code_cli(rootstream_ctx_t *ctx);
void tray_show_peers_cli(rootstream_ctx_t *ctx);
void tray_run_cli(rootstream_ctx_t *ctx);
void tray_cleanup_cli(rootstream_ctx_t *ctx);

/* --- Input Injection Backends (PHASE 6) --- */
int input_init_xdotool(rootstream_ctx_t *ctx);
int input_inject_key_xdotool(uint32_t keycode, bool press);
int input_inject_mouse_xdotool(int x, int y, uint32_t buttons);
void input_cleanup_xdotool(rootstream_ctx_t *ctx);
bool input_xdotool_available(void);

int input_init_logging(rootstream_ctx_t *ctx);
int input_inject_key_logging(uint32_t keycode, bool press);
int input_inject_mouse_logging(int x, int y, uint32_t buttons);
void input_cleanup_logging(rootstream_ctx_t *ctx);
bool input_logging_available(void);

/* --- Diagnostics (PHASE 6) --- */
void diagnostics_print_report(rootstream_ctx_t *ctx);
void diagnostics_print_header(void);
void diagnostics_print_system_info(void);
void diagnostics_print_display_info(void);
void diagnostics_print_available_backends(rootstream_ctx_t *ctx);
void diagnostics_print_active_backends(rootstream_ctx_t *ctx);
void diagnostics_print_recommendations(rootstream_ctx_t *ctx);

/* --- Service/Daemon --- */
int service_daemonize(void);
int service_run_host(rootstream_ctx_t *ctx);
Expand Down
209 changes: 209 additions & 0 deletions src/diagnostics.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/*
* diagnostics.c - System capabilities and feature report
*
* Prints detailed information about available backends and system capabilities
* at startup. Useful for debugging and verification.
*/

#include "../include/rootstream.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

void diagnostics_print_header(void) {
printf("\n");
printf("╔═══════════════════════════════════════════════════════════════╗\n");
printf("║ RootStream System Diagnostics Report ║\n");
printf("╚═══════════════════════════════════════════════════════════════╝\n");
printf("\n");
}

void diagnostics_print_system_info(void) {
printf("System Information:\n");
printf(" Hostname: ");

char hostname[256];
if (gethostname(hostname, sizeof(hostname)) == 0) {
printf("%s\n", hostname);
} else {
printf("(unknown)\n");
}

printf(" PID: %d\n", getpid());
printf(" UID: %d (running as %s)\n", getuid(), getuid() == 0 ? "root" : "user");

/* Check for render group (gid 44) */
if (getuid() != 0) {
printf(" GPU Group Access: ");

/* Get list of supplementary groups */
gid_t groups[64];
int ngroups = sizeof(groups) / sizeof(groups[0]);
if (getgroups(ngroups, groups) >= 0) {
bool has_render_group = false;
for (int i = 0; i < ngroups; i++) {
if (groups[i] == 44) { /* 44 = render group */
has_render_group = true;
break;
}
}
printf("%s\n", has_render_group ? "YES (can use DRM)" : "NO (DRM may be limited)");
} else {
printf("(unknown)\n");
}
}
printf("\n");
}

void diagnostics_print_display_info(void) {
printf("Display Information:\n");

const char *display = getenv("DISPLAY");
printf(" DISPLAY: %s\n", display ? display : "(none - headless)");

const char *wayland = getenv("WAYLAND_DISPLAY");
printf(" WAYLAND: %s\n", wayland ? wayland : "(none)");

printf("\n");
}

void diagnostics_print_available_backends(rootstream_ctx_t *ctx) {
(void)ctx;
printf("Available Backends:\n");

printf("\n Capture:\n");
printf(" Primary (DRM/KMS): %s\n",
access("/dev/dri/renderD128", F_OK) == 0 ? "✓ Available" : "✗ Not available");
#ifdef HAVE_X11
printf(" Fallback 1 (X11): ✓ Compiled in\n");
#else
printf(" Fallback 1 (X11): ✗ Not compiled\n");
#endif
printf(" Fallback 2 (Dummy): ✓ Always available\n");

printf("\n Encoder:\n");
printf(" Primary (NVENC): %s\n",
access("/proc/driver/nvidia/gpus", F_OK) == 0 ? "✓ Available" : "✗ Not available");
printf(" Primary (VA-API): ");
#ifdef HAVE_VAAPI
printf("✓ Compiled in\n");
#else
printf("✗ Not compiled\n");
#endif
printf(" Fallback (FFmpeg): ");
#ifdef HAVE_FFMPEG
printf("✓ Compiled in\n");
#else
printf("✗ Not compiled\n");
#endif
printf(" Fallback (Raw): ✓ Always available\n");

printf("\n Audio Capture:\n");
printf(" Primary (ALSA): ✓ Compiled in\n");
printf(" Fallback (PulseAudio): ");
#ifdef HAVE_PULSE
printf("✓ Compiled in\n");
#else
printf("✗ Not compiled\n");
#endif
printf(" Fallback (Dummy): ✓ Always available\n");

printf("\n Input Injection:\n");
printf(" Primary (uinput): %s\n",
access("/dev/uinput", F_OK) == 0 ? "✓ Available" : "✗ Not available");
/* Check for xdotool in common paths */
bool xdotool_found = (access("/usr/bin/xdotool", X_OK) == 0 ||
access("/usr/local/bin/xdotool", X_OK) == 0 ||
access("/bin/xdotool", X_OK) == 0);
printf(" Fallback (xdotool): %s\n",
xdotool_found ? "✓ Installed" : "✗ Not installed");
printf(" Fallback (Logging): ✓ Always available\n");

printf("\n GUI:\n");
#ifdef HAVE_GTK
printf(" Primary (GTK Tray): ✓ Compiled in\n");
#else
printf(" Primary (GTK Tray): ✗ Not compiled\n");
#endif
#ifdef HAVE_NCURSES
printf(" Fallback (TUI): ✓ Compiled in\n");
#else
printf(" Fallback (TUI): ✗ Not compiled\n");
#endif
printf(" Fallback (CLI): ✓ Always available\n");

printf("\n Discovery:\n");
#ifdef HAVE_AVAHI
printf(" Primary (mDNS/Avahi): ✓ Compiled in\n");
#else
printf(" Primary (mDNS/Avahi): ✗ Not compiled\n");
#endif
printf(" Fallback (Broadcast): ✓ Always available\n");
printf(" Fallback (Manual): ✓ Always available\n");

printf("\n Network:\n");
printf(" Primary (UDP): ✓ Always available\n");
printf(" Fallback (TCP): ✓ Always available\n");

printf("\n");
}

void diagnostics_print_active_backends(rootstream_ctx_t *ctx) {
printf("Active Backends (Runtime Selection):\n\n");

printf(" Capture: %s\n", ctx->active_backend.capture_name);
printf(" Encoder: %s\n", ctx->active_backend.encoder_name);
printf(" Audio Capture: %s\n", ctx->active_backend.audio_cap_name ?
ctx->active_backend.audio_cap_name : "disabled");
printf(" Audio Playback: %s\n", ctx->active_backend.audio_play_name ?
ctx->active_backend.audio_play_name : "disabled");
printf(" Discovery: %s\n", ctx->active_backend.discovery_name ?
ctx->active_backend.discovery_name : "uninitialized");
printf(" Input: %s\n", ctx->active_backend.input_name ?
ctx->active_backend.input_name : "uninitialized");
printf(" GUI: %s\n", ctx->active_backend.gui_name ?
ctx->active_backend.gui_name : "uninitialized");

printf("\n");
}

void diagnostics_print_recommendations(rootstream_ctx_t *ctx) {
(void)ctx;
printf("Recommendations:\n");

int recommendations = 0;

if (access("/dev/uinput", F_OK) != 0) {
printf(" • Install input support: sudo apt install xdotool\n");
recommendations++;
}

#ifndef HAVE_FFMPEG
printf(" • Install software encoder: apt-get install libavcodec-dev libx264-dev\n");
recommendations++;
#endif

#ifndef HAVE_PULSE
printf(" • Install PulseAudio support: apt-get install libpulse-dev\n");
recommendations++;
#endif

if (recommendations == 0) {
printf(" ✓ System is fully configured!\n");
}

printf("\n");
}

/*
* Print complete diagnostic report
*/
void diagnostics_print_report(rootstream_ctx_t *ctx) {
diagnostics_print_header();
diagnostics_print_system_info();
diagnostics_print_display_info();
diagnostics_print_available_backends(ctx);
diagnostics_print_active_backends(ctx);
diagnostics_print_recommendations(ctx);
}
48 changes: 48 additions & 0 deletions src/input_logging.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* input_logging.c - Debug input logging (no injection)
*
* Perfect for headless testing and validation.
* Just prints input events without attempting injection.
* Never fails - always available.
*/

#include "../include/rootstream.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int input_init_logging(rootstream_ctx_t *ctx) {
(void)ctx;
printf("✓ Input logging backend initialized (debug mode)\n");
printf(" Input events will be logged but NOT injected\n");
return 0;
}

int input_inject_key_logging(uint32_t keycode, bool press) {
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char time_buf[32];
strftime(time_buf, sizeof(time_buf), "%H:%M:%S", tm_info);

printf("[%s] INPUT: Key %u %s\n", time_buf, keycode, press ? "DOWN" : "UP");
return 0;
}

int input_inject_mouse_logging(int x, int y, uint32_t buttons) {
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char time_buf[32];
strftime(time_buf, sizeof(time_buf), "%H:%M:%S", tm_info);

printf("[%s] INPUT: Mouse at %d,%d buttons=0x%x\n", time_buf, x, y, buttons);
return 0;
}

void input_cleanup_logging(rootstream_ctx_t *ctx) {
(void)ctx;
}

bool input_logging_available(void) {
return true; /* Always available */
}
Loading
Loading