The officially official Devuan Forum!

You are not logged in.

#51 Re: Installation » [SOLVED] excalibur netinstall iso does not boot from USB stick » 2026-02-08 16:13:19

@grebulos, if you'd like a reliable GUI method for writing to USB sticks, try Mintstick, it's in the repo and has been flawless for me over hundreds of uses.

#52 Re: DIY » New Project, a simple music player. And now a video player!! » 2026-02-08 02:22:46

@greenjeans I don't see those ones in Hyperbola. Although something else might exist, not sure.

Its also possible they might have used to exist and I have them still, but I never deleted them. sorry I can't be much more helpful.

No worries my friend, I am so thankful that you tried to test it in the first place! FYI it works swimmingly in Vuu-do, maybe download it and give it a try. smile

#53 Re: DIY » New Project, a simple music player. And now a video player!! » 2026-02-07 21:20:40

@zapper, no that's the gstreamer plugin for ffmpeg, what I was referring to in general are the ffmpeg libraries, libavformat, libavcodec and such. In daedalus we're using 59 i.e. libavformat59, in excalibur it's libavformat61.

#54 Re: Devuan » Kde and Systemd - In the News » 2026-02-06 15:09:24

The usage of GTK4 by HB is certainly unfortunate,

Is that in excalibur/trixie? Because it's still gtk3 in daedalus.

#55 Re: DIY » New Project, a simple music player. And now a video player!! » 2026-02-06 15:00:48

Okay, so I had a quick look at Hyperbola, and it seems kinda unusual the way they do things so I don't even know where to start, I build everything with Devuan packages on a Devuan machine. I don't know if it will work at all on Hyperbola, the libraries they use may be completely different. What versions of libav do they use?

In general to do a manual install, extract the .deb, drop the 3 scripts in /usr/local/bin (assuming that's in your $PATH), drop the .desktop in /usr/share/applications and that's it, works great on Devuan (daedalus).

The .deb will draw in the depends, but doing it manually make sure you have gtk3, ffmpeg, yad, and bash.

#56 Off-topic » A blast from the past... » 2026-02-06 14:44:05

greenjeans
Replies: 4

A single 32k core plane from the IBM 2361 large capacity storage. This one came from the IBM 7094 system at NASA that was used during the later Apollo missions. This is what 1 mb of memory looked like in the mid '60's.

storage-jpg.3097540

#57 Re: Desktop and Multimedia » [SOLVED] Devuan Excalibur install and Pipewire. » 2026-02-05 23:46:41

The issue is, the Devuan community isn’t really focused on MIDI and audio production.

The issue is you posted a detailed how-to about another distro, forum faux pas bro.

Maybe midi not so much, but there's a number of Devuan users who are actively working on Devuan sound, speaking of which, Ralph was nice enough to do a complete set-up of fftrate on Devuan's git, thus handing it to you on a silver platter after you posted a good 100,000 words on the subject over the course of a few months, are you going to take him up on his offer to let you maintain it after all that?

#58 Re: Off-topic » XFCE is building a new WM and compositor for Wayland » 2026-02-05 22:59:18

CAFE (fork of MATE) is doing, particularly CTK

Oh yeah, I have the strangest b.......never mind, i'm just excited.

They need to roll back some Mate goofiness, particularly in Caja. And dump the damn mixer.

#59 Re: Off-topic » XFCE is building a new WM and compositor for Wayland » 2026-02-05 22:56:07

Yeah, from recent experience it seems to go like this:

1. Hey we're adding support for wayland but we're still shipping X11.
2. Hey we're still shipping X11 but wayland is now the default.
3. Hey we're all in on wayland, but you can still use X11 if you jump through these 30 hoops and re-compile some stuff.
4. Hey we are discontinuing support for X11 for (your own) good.

#60 Re: DIY » New Project, a simple music player. And now a video player!! » 2026-02-05 22:51:04

Hold on, did you download the .deb and extract it and install the other scripts? I doubt it or you'd have that file since it's the wrapper script that creates it.

Srsly bro, just install the .deb, it will put everything where it needs to be and set proper permissions.

#61 Re: DIY » New Project, a simple music player. And now a video player!! » 2026-02-05 18:09:04

I have no idea what would work in Hyperbola as i've never used it, and keep in mind this is built on Devuan 5 (daedalus) libraries, I have not tried compiling it or running on the newer excalibur based libraries. The app is already compressed with everything in the .deb but the source code for the single small binary, the other two scripts are tiny shellscripts, and a .desktop. If you want to try compiling the binary yourself:

1. You'll need GCC and pkg-config and their deps, common build stuff.
2. You'll need to install libgtk-3-dev, in Devuan that pulls in the other dev packages you'll need (libx11-dev, libxext-dev etc.), in hyperbola I don't know if it does.
3. Compile command:
gcc -Wall -o vsvp vsvp.c `pkg-config --cflags --libs gtk+-3.0 x11 xext`

Source for the binary, the source script is 30.9 kb, compiled 43.5 kb, vsvp.c:

#include <gtk/gtk.h>
#include <gtk/gtkx.h>
#include <gdk/gdkx.h>              // For GDK X11 functions
#include <X11/Xlib.h>              // For XSelectInput
#include <X11/extensions/shape.h>  // Secret shape sauce
#include <X11/keysym.h>            // For keysyms like XK_Up
#include <stdlib.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include <glib.h>

#define SCRAPER_CMD "/usr/local/bin/vsvps" // For re-scan button

typedef struct {
    pid_t ffplay_pid;
    GtkWidget *play_button, *pause_button, *stop_button, *rebuild_button;
    GtkWidget *socket;            // For embedding ffplay window
    GtkWidget *placeholder_label; // Placeholder when no video is playing
    GtkWidget *socket_container;  // Overlay container
    GtkWidget *paused_overlay;    // Semi-transparent paused cover
    GtkWidget *stopped_overlay;   // Opaque stopped/select screen
    GtkWidget *placeholder_pane;  // Initial right pane
    GtkWidget *paned;             // Paned for swapping
    GtkTreeStore *video_store;
    GtkTreeIter current_video_iter;
    gboolean is_playing;
    gboolean is_paused;
    GtkWidget *video_tree;
    GtkWidget *control_box;
    gboolean socket_active;
    GtkWidget *external_play_button; // Launch external ffplay window
    unsigned long ffplay_wid;        // For sending key events
} AppData;

typedef struct {
    GList *path_components;
    gchar *title;
    gchar *fullpath;
    gboolean is_folder; // set later
} Entry;

// Forward declarations
void on_stop_clicked(GtkButton *button, AppData *app_data);
void populate_treeview(AppData *app_data);

// Send simulated key to ffplay
static void send_key_to_ffplay(AppData *app_data, KeySym keysym) {
    if (app_data->ffplay_wid == 0) return;

    Display *display = gdk_x11_get_default_xdisplay();
    if (!display) return;

    // Optional: Temp focus if needed (uncomment the below if seeks don't work),
	// so far it's working without it, but keeping this here pending further testing.

    // Window original_focus = XGetInputFocus(display, &original_focus, &revert_to);
    // XSetInputFocus(display, app_data->ffplay_wid, RevertToParent, CurrentTime);
    // XFlush(display);

    XEvent event = {0};
    event.xkey.display = display;
    event.xkey.window = app_data->ffplay_wid;
    event.xkey.root = RootWindow(display, DefaultScreen(display));
    event.xkey.subwindow = None;
    event.xkey.time = CurrentTime;
    event.xkey.x = 1; event.xkey.y = 1; // Dummy pos
    event.xkey.x_root = 1; event.xkey.y_root = 1;
    event.xkey.state = 0;
    event.xkey.keycode = XKeysymToKeycode(display, keysym);
    event.xkey.same_screen = True;

    // Press
    event.type = KeyPress;
    XSendEvent(display, app_data->ffplay_wid, True, KeyPressMask, &event);
    XFlush(display);

    // Release
    event.type = KeyRelease;
    XSendEvent(display, app_data->ffplay_wid, True, KeyReleaseMask, &event);
    XFlush(display);

    // Optional: Revert focus, uncomment this too if uncommenting the other focus lines above.

    // XSetInputFocus(display, original_focus, RevertToParent, CurrentTime);
    // XFlush(display);
}

// Seek button clicked
static void on_seek_clicked(GtkButton *button, AppData *app_data) {
    const char *label = gtk_button_get_label(button);
    KeySym keysym = 0;

    if (g_strcmp0(label, "Previous chapter") == 0) keysym = XK_Page_Down;
    else if (g_strcmp0(label, "<<1m") == 0) keysym = XK_Down;
    else if (g_strcmp0(label, "1m>>") == 0) keysym = XK_Up;
    else if (g_strcmp0(label, "Next chapter") == 0) keysym = XK_Page_Up;

    if (keysym != 0) {
        send_key_to_ffplay(app_data, keysym);
    }
}

// Helper to free Entry
static void free_entry(Entry *e) {
    g_list_free_full(e->path_components, g_free);
    g_free(e->title);
    g_free(e->fullpath);
    g_free(e);
}

// Sort function (folders before files, alpha within level)
static gint compare_entries(gconstpointer a_ptr, gconstpointer b_ptr) {
    const Entry *a = a_ptr;
    const Entry *b = b_ptr;
    GString *key_a = g_string_new("");
    GList *p;
    for (p = a->path_components; p; p = p->next) {
        if (key_a->len) g_string_append_c(key_a, '/');
        g_string_append(key_a, p->data);
    }
    if (key_a->len) g_string_append_c(key_a, '/');
    g_string_append(key_a, a->title);
    GString *key_b = g_string_new("");
    for (p = b->path_components; p; p = p->next) {
        if (key_b->len) g_string_append_c(key_b, '/');
        g_string_append(key_b, p->data);
    }
    if (key_b->len) g_string_append_c(key_b, '/');
    g_string_append(key_b, b->title);
    gint result = g_strcmp0(key_a->str, key_b->str);
    g_string_free(key_a, TRUE);
    g_string_free(key_b, TRUE);
    return result;
}

void recreate_socket(AppData *app_data) {
    if (app_data->socket && GTK_IS_WIDGET(app_data->socket)) {
        gtk_widget_destroy(app_data->socket);
    }
    app_data->socket = gtk_socket_new();
    gtk_widget_set_size_request(app_data->socket, 650, 400);
    gtk_container_add(GTK_CONTAINER(app_data->socket_container), app_data->socket);
    if (GTK_IS_WIDGET(app_data->socket)) gtk_widget_hide(app_data->socket);
}

void play_video(AppData *app_data, const char *file_path, GtkTreeIter *iter) {
    // First, ensure overlays are hidden BEFORE anything else
    if (GTK_IS_WIDGET(app_data->paused_overlay)) gtk_widget_hide(app_data->paused_overlay);
    if (GTK_IS_WIDGET(app_data->stopped_overlay)) gtk_widget_hide(app_data->stopped_overlay);
    if (!app_data->socket_active) {
        gtk_container_remove(GTK_CONTAINER(app_data->paned), app_data->placeholder_pane);
        gtk_paned_pack2(GTK_PANED(app_data->paned), app_data->socket_container, TRUE, FALSE);
        gtk_widget_show_all(app_data->socket_container);
        // Force overlays hidden AGAIN after container is shown
        if (GTK_IS_WIDGET(app_data->paused_overlay)) gtk_widget_hide(app_data->paused_overlay);
        if (GTK_IS_WIDGET(app_data->stopped_overlay)) gtk_widget_hide(app_data->stopped_overlay);
        app_data->socket_active = TRUE;
        gtk_widget_queue_draw(app_data->paned);
        gtk_widget_queue_resize(app_data->paned);
    }
    if (app_data->ffplay_pid > 0) {
        if (app_data->is_paused) {
            kill(app_data->ffplay_pid, SIGCONT);
            app_data->is_paused = FALSE;
        }
        kill(app_data->ffplay_pid, SIGTERM);
        waitpid(app_data->ffplay_pid, NULL, 0);
        app_data->ffplay_pid = 0;
        app_data->is_playing = FALSE;
        if (GTK_IS_WIDGET(app_data->socket)) gtk_widget_hide(app_data->socket);
        recreate_socket(app_data);
    }
    pid_t pid = fork();
    if (pid == 0) {
        execlp("ffplay", "ffplay", "-autoexit", "-x", "650", "-y", "400", "-noborder",
               "-loglevel", "quiet", "-hide_banner",  // Prevent log spam, ffplay is extremely verbose
               "-bufsize", "128k", "-af", "atempo=1.0",
               file_path, NULL);
        exit(1);
    } else if (pid > 0) {
        app_data->ffplay_pid = pid;
        app_data->is_playing = TRUE;
        app_data->is_paused = FALSE;
        if (iter) {
            app_data->current_video_iter = *iter;
        }
        unsigned long wid = 0;
        int attempts = 80;
        for (int i = 0; i < attempts; i++) {
            usleep(100000);
            char command[512];
            FILE *fp;
            snprintf(command, sizeof(command),
                     "xwininfo -name 'ffplay' -int 2>/dev/null | grep 'Window id' | awk '{print $4}'");
            fp = popen(command, "r");
            if (fp) {
                char buf[32];
                if (fgets(buf, sizeof(buf), fp)) wid = strtoul(buf, NULL, 10);
                pclose(fp);
            }
            if (wid == 0) {
                snprintf(command, sizeof(command),
                         "xwininfo -root -tree 2>/dev/null | grep -i ffplay | head -1 | awk '{print $1}'");
                fp = popen(command, "r");
                if (fp) {
                    char buf[32];
                    if (fgets(buf, sizeof(buf), fp)) wid = strtoul(buf, NULL, 16);
                    pclose(fp);
                }
            }
            if (wid > 0) {
                usleep(50000);
                gtk_socket_add_id(GTK_SOCKET(app_data->socket), wid);
                if (GTK_IS_WIDGET(app_data->socket)) gtk_widget_show(app_data->socket);
                // Secret sauce X11 hack, set empty input shape to block mouse clicks in embedded window
                Display *display = gdk_x11_get_default_xdisplay();
                if (display) {
                    Pixmap empty_shape = XCreatePixmap(display, wid, 1, 1, 1);
                    XShapeCombineMask(display, wid, ShapeInput, 0, 0, empty_shape, ShapeSet);
                    XFreePixmap(display, empty_shape);
                    XSync(display, False);
                } else {
                    g_print("Failed to get X display for shape hack\n");
                }
                app_data->ffplay_wid = wid;  // Store for key sends
                break;
            }
        }
        if (wid == 0) g_print("Error: Could not find ffplay window ID after %d attempts\n", attempts);
    }
}

void play_video_external(AppData *app_data, const char *file_path) {
    pid_t pid = fork();
    if (pid == 0) {
        // Launch ffplay standalone, no size constraints, autoexit when done, keyboard hints in title, no log spam
        execlp("ffplay", "ffplay", "-autoexit", "-loglevel", "quiet", "-hide_banner",
	           "-window_title", "Keyboard controls:  f=fullscreen,  p=pause/play,  q=quit",
               file_path, NULL);
        exit(1);
    } else if (pid > 0) {
    } else {
        g_print("Failed to fork for external ffplay\n");
    }
}

gboolean check_video_end(gpointer user_data) {
    AppData *app_data = (AppData *)user_data;
    if (app_data->ffplay_pid <= 0 || !app_data->is_playing || app_data->is_paused) {
        return TRUE;
    }
    int status;
    pid_t result = waitpid(app_data->ffplay_pid, &status, WNOHANG);
    if (result == app_data->ffplay_pid) {
        app_data->ffplay_pid = 0;
        app_data->is_playing = FALSE;
        app_data->is_paused = FALSE;
        if (GTK_IS_WIDGET(app_data->socket)) gtk_widget_hide(app_data->socket);
        if (GTK_IS_WIDGET(app_data->paused_overlay)) gtk_widget_hide(app_data->paused_overlay);
        if (GTK_IS_WIDGET(app_data->stopped_overlay)) gtk_widget_show(app_data->stopped_overlay);
        recreate_socket(app_data);
    }
    return TRUE;
}

void on_play_clicked(GtkButton *button, AppData *app_data) {
    GtkTreeView *video_tree = g_object_get_data(G_OBJECT(button), "video_tree");
    GtkTreeSelection *selection = gtk_tree_view_get_selection(video_tree);
    GtkTreeIter iter;
    GtkTreeModel *model;
    if (!gtk_tree_selection_get_selected(selection, &model, &iter)) {
        return; // nothing selected
    }
    gchar *file_path = NULL;
    gtk_tree_model_get(model, &iter, 1, &file_path, -1);
    if (!file_path || file_path[0] == '\0') {
        g_free(file_path);
        return; // clicked a folder - do nothing
    }
    // Visually indicate selection
    gtk_tree_selection_select_iter(selection, &iter);
    play_video(app_data, file_path, &iter);
    g_free(file_path);
}

void on_external_play_clicked(GtkButton *button, AppData *app_data) {
    GtkTreeView *video_tree = g_object_get_data(G_OBJECT(button), "video_tree");
    GtkTreeSelection *selection = gtk_tree_view_get_selection(video_tree);
    GtkTreeIter iter;
    GtkTreeModel *model;
    if (!gtk_tree_selection_get_selected(selection, &model, &iter)) {
        return; // nothing selected
    }
    gchar *file_path = NULL;
    gtk_tree_model_get(model, &iter, 1, &file_path, -1);
    // Guard: ignore if it's a folder (no path)
    if (!file_path || file_path[0] == '\0') {
        g_free(file_path);
        return; // clicked a folder - do nothing, no need to stop anything
    }
    // If something is currently playing in the embedded player - stop it first
    if (app_data->ffplay_pid > 0) {
        on_stop_clicked(NULL, app_data); // reuse existing stop logic
    }
    // Now it's safe to launch external
    play_video_external(app_data, file_path);
    g_free(file_path);
}

void on_pause_clicked(GtkButton *button, AppData *app_data) {
    if (app_data->ffplay_pid > 0) {
        if (app_data->is_playing && !app_data->is_paused) {
            kill(app_data->ffplay_pid, SIGSTOP);
            app_data->is_playing = FALSE;
            app_data->is_paused = TRUE;
            if (GTK_IS_WIDGET(app_data->paused_overlay)) {
                gtk_widget_show(app_data->paused_overlay);
            }
            if (GTK_IS_WIDGET(app_data->stopped_overlay)) {
                gtk_widget_hide(app_data->stopped_overlay);
            }
        } else if (app_data->is_paused) {
            kill(app_data->ffplay_pid, SIGCONT);
            app_data->is_playing = TRUE;
            app_data->is_paused = FALSE;
            if (GTK_IS_WIDGET(app_data->paused_overlay)) gtk_widget_hide(app_data->paused_overlay);
        }
    }
}

void on_stop_clicked(GtkButton *button, AppData *app_data) {
    if (app_data->ffplay_pid > 0) {
        if (app_data->is_paused) {
            kill(app_data->ffplay_pid, SIGCONT);
            app_data->is_paused = FALSE;
        }
        kill(app_data->ffplay_pid, SIGTERM);
        waitpid(app_data->ffplay_pid, NULL, 0);
        app_data->ffplay_pid = 0;
        app_data->is_playing = FALSE;
        if (GTK_IS_WIDGET(app_data->socket)) gtk_widget_hide(app_data->socket);
        if (GTK_IS_WIDGET(app_data->paused_overlay)) gtk_widget_hide(app_data->paused_overlay);
        if (GTK_IS_WIDGET(app_data->stopped_overlay)) gtk_widget_show(app_data->stopped_overlay);
        recreate_socket(app_data);
    }
}

void on_rebuild_clicked(GtkButton *button, AppData *app_data) {
    if (system(SCRAPER_CMD) != 0) {
        g_print("Warning: Failed to run scraper %s\n", SCRAPER_CMD);
    }
    gtk_tree_store_clear(app_data->video_store);
    populate_treeview(app_data);
    app_data->is_playing = FALSE;
    app_data->is_paused = FALSE;
    if (GTK_IS_WIDGET(app_data->socket)) gtk_widget_hide(app_data->socket);
    if (GTK_IS_WIDGET(app_data->paused_overlay)) gtk_widget_hide(app_data->paused_overlay);
    if (GTK_IS_WIDGET(app_data->stopped_overlay)) gtk_widget_show(app_data->stopped_overlay);
    recreate_socket(app_data);
}

void populate_treeview(AppData *app_data) {
    const char *home = getenv("HOME");
    if (!home) {
        g_print("Error: Could not determine home directory\n");
        return;
    }
    gchar *db_path = g_strdup_printf("%s/.local/share/vsvp/vsvp_db", home);

    FILE *db = fopen(db_path, "r");
    if (!db) {
        g_print("Error: Could not open database %s\n", db_path);
        g_free(db_path);
        return;
    }

    gtk_tree_store_clear(app_data->video_store);
    char line[1024];
    if (!fgets(line, sizeof(line), db)) { // skip header
        fclose(db);
        g_free(db_path);
        return;
    }
    GList *entries = NULL;
    while (fgets(line, sizeof(line), db)) {
        char path_str[512] = {0};
        char title[256] = {0};
        char fullpath[768] = {0};
        if (sscanf(line, "%511[^|]|%255[^|]|%767[^\n]", path_str, title, fullpath) != 3)
            continue;
        GList *components = NULL;
        if (strlen(path_str) > 0) {
            char *saveptr;
            char *token = strtok_r(path_str, "/", &saveptr);
            while (token) {
                components = g_list_append(components, g_strdup(token));
                token = strtok_r(NULL, "/", &saveptr);
            }
        }
        Entry *e = g_new0(Entry, 1);
        e->path_components = components;
        e->title = g_strdup(title);
        e->fullpath = g_strdup(fullpath);
        entries = g_list_prepend(entries, e);
    }
    fclose(db);
    // Sort once - folders before files + alpha within same level
    entries = g_list_sort(entries, compare_entries);
    // Build the actual tree
    GHashTable *folder_iters = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
    GList *l;
    for (l = entries; l != NULL; l = l->next) {
        Entry *e = l->data;
        GtkTreeIter *parent = NULL;
        GtkTreeIter iter;
        GString *current_path = g_string_new("");
        GList *comp;
        for (comp = e->path_components; comp != NULL; comp = comp->next) {
            gchar *folder_name = comp->data;
            if (current_path->len > 0)
                g_string_append_c(current_path, '/');
            g_string_append(current_path, folder_name);
            GtkTreeIter *existing = g_hash_table_lookup(folder_iters, current_path->str);
            if (existing) {
                parent = existing;
                continue;
            }
            // New folder node
            if (parent) {
                gtk_tree_store_append(app_data->video_store, &iter, parent);
            } else {
                gtk_tree_store_append(app_data->video_store, &iter, NULL);
            }
            gtk_tree_store_set(app_data->video_store, &iter,
                               0, folder_name,
                               1, NULL,
                               -1);
            // Store a copy of the iter
            GtkTreeIter *stored = g_new(GtkTreeIter, 1);
            *stored = iter;
            g_hash_table_insert(folder_iters, g_strdup(current_path->str), stored);
            parent = stored;
        }
        // Finally the file leaf
        gtk_tree_store_append(app_data->video_store, &iter, parent);
        gtk_tree_store_set(app_data->video_store, &iter,
                           0, e->title,
                           1, e->fullpath,
                           -1);
    }
    // Cleanup
    g_list_free_full(entries, (GDestroyNotify)free_entry);
    g_hash_table_destroy(folder_iters);
    g_free(db_path);
}

// Hide treeview and borders when maximized for quasi-theater-mode
gboolean on_window_state_changed(GtkWidget *widget, GdkEventWindowState *event, gpointer user_data) {
    AppData *app_data = (AppData *)user_data;
    if (event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) {
        gboolean is_maximized = (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED);
        // Playlist hide/show
        if (is_maximized) {
            gtk_widget_hide(app_data->video_tree);
        } else {
            gtk_widget_show(app_data->video_tree);
        }
        // Window border padding
        gtk_container_set_border_width(GTK_CONTAINER(widget), is_maximized ? 0 : 10);
        // Get vbox from window
        GtkWidget *vbox = gtk_bin_get_child(GTK_BIN(widget));
        if (GTK_IS_BOX(vbox)) {
            // Zero spacing (covers the tiny 1px if any)
            gtk_box_set_spacing(GTK_BOX(vbox), is_maximized ? 0 : 1);
            // KEY: Dynamically change paned's packing padding
            gtk_box_set_child_packing(GTK_BOX(vbox),
                                      app_data->paned,
                                      TRUE, // expand (keep as is)
                                      TRUE, // fill (keep as is)
                                      is_maximized ? 0 : 10, // padding
                                      GTK_PACK_START); // pack_type (keep as is)
        } else {
            g_warning("Main vbox not found or not a GtkBox!");
        }
    }
    return FALSE;
}

void on_window_destroy(GtkWidget *widget, AppData *app_data) {
    if (app_data->ffplay_pid > 0) {
        if (app_data->is_paused) {
            kill(app_data->ffplay_pid, SIGCONT);
            app_data->is_paused = FALSE;
        }
        kill(app_data->ffplay_pid, SIGTERM);
        waitpid(app_data->ffplay_pid, NULL, 0);
        app_data->ffplay_pid = 0;
        app_data->is_playing = FALSE;
    }
    gtk_main_quit();
}

int main(int argc, char *argv[]) {
    gtk_init(&argc, &argv);
    if (!getenv("HOME")) {
        g_print("Error: Could not determine home directory\n");
        return 1;
    }
    AppData app_data = {0};
    app_data.video_store = gtk_tree_store_new(2, G_TYPE_STRING, G_TYPE_STRING); // 0 = Title, 1 = Fullpath (NULL for folders)
    app_data.is_playing = FALSE;
    app_data.is_paused = FALSE;
    app_data.socket_active = FALSE;

    g_timeout_add(500, check_video_end, &app_data);
    
    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "Vuu-do Simple Video Player");
    gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
    gtk_window_set_hide_titlebar_when_maximized(GTK_WINDOW(window), FALSE);
    gtk_window_set_icon_name(GTK_WINDOW(window), "applications-multimedia");
    g_signal_connect(window, "destroy", G_CALLBACK(on_window_destroy), &app_data);
    g_signal_connect(window, "window-state-event", G_CALLBACK(on_window_state_changed), &app_data);
    gtk_container_set_border_width(GTK_CONTAINER(window), 10);
    
    GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 1);
    gtk_container_add(GTK_CONTAINER(window), vbox);
    
    app_data.paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
    gtk_box_pack_start(GTK_BOX(vbox), app_data.paned, TRUE, TRUE, 10);
    
    app_data.video_tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(app_data.video_store));
    gtk_tree_view_set_model(GTK_TREE_VIEW(app_data.video_tree), GTK_TREE_MODEL(app_data.video_store));
    GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
    gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(app_data.video_tree), -1, "Videos", renderer, "text", 0, NULL);
    gtk_paned_pack1(GTK_PANED(app_data.paned), app_data.video_tree, FALSE, FALSE);
    gtk_widget_set_size_request(app_data.video_tree, 200, -1);
    
    // Placeholder pane (initial state)
    app_data.placeholder_pane = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
    gtk_widget_set_name(app_data.placeholder_pane, "placeholder-pane");
    app_data.placeholder_label = gtk_label_new("Select a video");
    gtk_widget_set_margin_start(app_data.placeholder_label, 10);
    gtk_widget_set_margin_end(app_data.placeholder_label, 10);
    gtk_widget_set_margin_top(app_data.placeholder_label, 10);
    gtk_widget_set_margin_bottom(app_data.placeholder_label, 10);
    gtk_box_pack_start(GTK_BOX(app_data.placeholder_pane), app_data.placeholder_label, TRUE, TRUE, 0);    
    GtkCssProvider *provider = gtk_css_provider_new();
    gtk_css_provider_load_from_data(provider,
        "#placeholder-pane {"
        " background-color: @theme_base_color;"
        " background-image: none;"
        "}"
        "#placeholder-pane label {"
        " color: @theme_text_color;"
        "}",
        -1, NULL);
    GtkStyleContext *pane_ctx = gtk_widget_get_style_context(app_data.placeholder_pane);
    gtk_style_context_add_provider(pane_ctx, GTK_STYLE_PROVIDER(provider),
                                   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    GtkStyleContext *label_ctx = gtk_widget_get_style_context(app_data.placeholder_label);
    gtk_style_context_add_provider(label_ctx, GTK_STYLE_PROVIDER(provider),
                                   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    gtk_paned_pack2(GTK_PANED(app_data.paned), app_data.placeholder_pane, TRUE, FALSE);
    gtk_widget_show_all(app_data.placeholder_pane);
    
    // Socket container as overlay host
    app_data.socket_container = gtk_overlay_new();
    gtk_widget_set_name(app_data.socket_container, "socket-container");
    
    // Main child: socket
    app_data.socket = gtk_socket_new();
    gtk_widget_set_size_request(app_data.socket, 650, 400);
    gtk_container_add(GTK_CONTAINER(app_data.socket_container), app_data.socket);
    gtk_widget_hide(app_data.socket);
    
    // Paused overlay (semi-transparent "Paused")
    app_data.paused_overlay = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
    gtk_widget_set_halign(app_data.paused_overlay, GTK_ALIGN_FILL);
    gtk_widget_set_valign(app_data.paused_overlay, GTK_ALIGN_FILL);
    GtkWidget *paused_label = gtk_label_new("Paused");
    gtk_box_pack_start(GTK_BOX(app_data.paused_overlay), paused_label, TRUE, TRUE, 0);
    gtk_widget_set_name(app_data.paused_overlay, "paused-overlay");
    gtk_overlay_add_overlay(GTK_OVERLAY(app_data.socket_container), app_data.paused_overlay);
    gtk_widget_hide(app_data.paused_overlay);
    
    // Stopped overlay (opaque, "Select a video")
    app_data.stopped_overlay = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
    gtk_widget_set_halign(app_data.stopped_overlay, GTK_ALIGN_FILL);
    gtk_widget_set_valign(app_data.stopped_overlay, GTK_ALIGN_FILL);
    GtkWidget *stopped_label = gtk_label_new("Select a video");
    gtk_box_pack_start(GTK_BOX(app_data.stopped_overlay), stopped_label, TRUE, TRUE, 0);
    gtk_widget_set_name(app_data.stopped_overlay, "stopped-overlay");
    gtk_overlay_add_overlay(GTK_OVERLAY(app_data.socket_container), app_data.stopped_overlay);
    gtk_widget_hide(app_data.stopped_overlay); // Start hidden
    
    // CSS for overlays and container
    GtkCssProvider *overlay_provider = gtk_css_provider_new();
    gtk_css_provider_load_from_data(overlay_provider,
        "#paused-overlay {"
        " background-color: alpha(@theme_base_color, 0.50);"
        " background-image: none;"
        "}"
        "#paused-overlay label {"
        " color: @theme_fg_color;"
        " font-weight: bold;"
        "}"
        "#stopped-overlay {"
        " background-color: @theme_base_color;"
        " background-image: none;"
        "}"
        "#stopped-overlay label {"
        " color: @theme_text_color;"
        " font-weight: normal;"
        "}"
        "#socket-container {"
        " background-color: @theme_base_color;"
        " background-image: none;"
        "}",
        -1, NULL);
    gtk_style_context_add_provider(gtk_widget_get_style_context(app_data.paused_overlay),
                                   GTK_STYLE_PROVIDER(overlay_provider),
                                   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    gtk_style_context_add_provider(gtk_widget_get_style_context(app_data.stopped_overlay),
                                   GTK_STYLE_PROVIDER(overlay_provider),
                                   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    gtk_style_context_add_provider(gtk_widget_get_style_context(app_data.socket_container),
                                   GTK_STYLE_PROVIDER(overlay_provider),
                                   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    g_object_unref(overlay_provider);
    g_object_unref(provider);

	//CSS for bottom buttons
	GtkCssProvider *button_provider = gtk_css_provider_new();
	gtk_css_provider_load_from_data(button_provider,
    "#control-bar button {"
    "  border-width: 1px;"
    "  border-style: solid;"
    "  border-color: #333333;"
    "  box-shadow: none;"
    "  border-radius: 4px;"
    "}",
    -1, NULL);

	gtk_style_context_add_provider_for_screen(gdk_screen_get_default(),
                                         GTK_STYLE_PROVIDER(button_provider),
                                         GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);    

    // Bottom controls 
    app_data.control_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_box_pack_start(GTK_BOX(vbox), app_data.control_box, FALSE, FALSE, 0);
    gtk_widget_set_margin_top(app_data.control_box, 5);
    gtk_widget_set_margin_bottom(app_data.control_box, 5);
    gtk_widget_set_name(app_data.control_box, "control-bar");
    app_data.play_button = gtk_button_new_with_label("Play");
    app_data.pause_button = gtk_button_new_with_label("Pause");
    app_data.stop_button = gtk_button_new_with_label("Stop");
    GtkWidget *seek_back10 = gtk_button_new_with_label("Previous chapter");
    GtkWidget *seek_back1 = gtk_button_new_with_label("<<1m");
    GtkWidget *seek_fwd1 = gtk_button_new_with_label("1m>>");
    GtkWidget *seek_fwd10 = gtk_button_new_with_label("Next chapter");
    gtk_widget_set_tooltip_text(seek_back10, "Go back 1 chapter");
    gtk_widget_set_tooltip_text(seek_back1, "Go back 1 minute");
    gtk_widget_set_tooltip_text(seek_fwd1, "Go forward 1 minute");
    gtk_widget_set_tooltip_text(seek_fwd10, "Go forward 1 chapter");
    app_data.rebuild_button = gtk_button_new_with_label("Re-scan Videos");
    gtk_widget_set_tooltip_text(app_data.rebuild_button, "Re-scan the ~/Videos folder after adding new media");
    app_data.external_play_button = gtk_button_new_with_label("Fullscreen window");
    gtk_widget_set_tooltip_text(app_data.external_play_button,
        "Play video in a separate fullscreen-capable window.\n"
        "Keyboard shortcuts only for that window :\n\n"
        " f  -  toggle fullscreen\n"
        " p  -  toggle pause/play\n"
        " m  -  toggle mute\n\n"
        " left/right  -  seek ±10s\n"
        " up/down  -  seek ±1min\n"
        " pg up/pg dn  -  seek ±1 chapter\n\n"
        " q or ESC  -  quit and close window");
       
    gtk_box_pack_start(GTK_BOX(app_data.control_box), app_data.play_button, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(app_data.control_box), app_data.pause_button, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(app_data.control_box), app_data.stop_button, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(app_data.control_box), seek_back10, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(app_data.control_box), seek_back1, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(app_data.control_box), seek_fwd1, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(app_data.control_box), seek_fwd10, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(app_data.control_box), app_data.rebuild_button, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(app_data.control_box), app_data.external_play_button, FALSE, FALSE, 5);
   
    g_object_set_data(G_OBJECT(app_data.play_button), "video_tree", app_data.video_tree);
    g_object_set_data(G_OBJECT(app_data.external_play_button), "video_tree", app_data.video_tree);
   
    g_signal_connect(app_data.play_button, "clicked", G_CALLBACK(on_play_clicked), &app_data);
    g_signal_connect(app_data.pause_button, "clicked", G_CALLBACK(on_pause_clicked), &app_data);
    g_signal_connect(app_data.stop_button, "clicked", G_CALLBACK(on_stop_clicked), &app_data);
    g_signal_connect(seek_back10, "clicked", G_CALLBACK(on_seek_clicked), &app_data);
    g_signal_connect(seek_back1, "clicked", G_CALLBACK(on_seek_clicked), &app_data);
    g_signal_connect(seek_fwd1, "clicked", G_CALLBACK(on_seek_clicked), &app_data);
    g_signal_connect(seek_fwd10, "clicked", G_CALLBACK(on_seek_clicked), &app_data);
    g_signal_connect(app_data.rebuild_button, "clicked", G_CALLBACK(on_rebuild_clicked), &app_data);
    g_signal_connect(app_data.external_play_button, "clicked", G_CALLBACK(on_external_play_clicked), &app_data);
   
    populate_treeview(&app_data);
    gtk_widget_show_all(window);
    gtk_main();
    return 0;
}

#62 Off-topic » XFCE is building a new WM and compositor for Wayland » 2026-02-05 16:10:08

greenjeans
Replies: 17

Building from the ground up they say, using Rust. Not dumping X11 though...for now.
https://blog.xfce.org/

"This initiative will utilize a significant portion of the project’s donated funds"

Do tell. wink

What say ye mousers about this? I don't use it myself so I don't have a dog in this hunt.

#64 Re: Desktop and Multimedia » [SOLVED] Devuan Excalibur install and Pipewire. » 2026-02-04 22:14:17

Interesting issue, and made more difficult by being intermittent. Sorry I can't be more help, I have zero experience with KDE.

I can tell you this factoid: When using AlsaTune on a pure alsa system, I have every control available for my soundcard. Some 7-8 volume sliders and a couple of toggles. But if I install it on a Mate system (Mate has an onboard mixer too) and try to use it, EVERYTHING but the main volume slider is gone completely. The onboard mixers grab control right at boot-up, and they are NOT sophisticated and you lose a lot of control. That's why in most cases people throw Pulse or Pipe at it in hopes that it all works out. Mate's onboard mixer is the main reason i'm not doing any more work with Mate, that and relentless Caja bugs.

#65 Re: Devuan » Kde and Systemd - In the News » 2026-02-04 16:48:58

I have my encoding scripts that do the same thing using the ffmpeg.

Man it's great ain't it? I'm in the big love right now with ffmpeg, so many things you can do.

@brocashelm, I probably understand your frustration with gtk 3 better than most right now due to immersion for the last year, it's a real pain to deal with. But it's likely the best candidate for forking gtk, the right group of smart people could fix a lot of it's shortcomings and unneeded complexities.

#66 Re: Desktop and Multimedia » [SOLVED] Devuan Excalibur install and Pipewire. » 2026-02-04 16:33:09

Haven't used it, but KDE has kmix which is it's onboard mixer, perhaps the OP can try that? I would say try AlsaTune but it's not going to work with KDE due to that onboard mixer.

#67 Re: Desktop and Multimedia » Devuan Excalibur install LXDE, Alsa and only internal sound card. » 2026-02-03 22:40:34

Igor, how does installing fftrate have anything at all to do with the OP's question?

#68 Re: Installation » [SOLVED] Weirdness with runit and slim/xfce; slim starts even when disabled? » 2026-02-03 22:36:50

Well Mercury, perhaps the dev like most these days is simply overworked and needs some extra hands, you sound like you have a good working knowledge of runit (one that obviously expanded the last few due to these unfortunate errors). Perhaps you could take a little time and write up some better docs? I know, you shouldn't have to and the pay sucks, lol, but still you'd be a hero for doing it.

Anger is always more useful than despair, so be angry, but maybe channel that energy into preventing other folks from enduring what you had to go through? I'd be grateful for it, I know diddly squat about runit or I would have already tried it. Docs are definitely bigly important, I read multiples of them every day.

#69 Re: Devuan » Proposal "Devuan User Repository" » 2026-02-03 22:23:59

Meanwhile (yet still on topic), I have just packaged and uploaded another piece of free software (my apologies for it not being vital to Devuan's core infrastructure).

So yeah, Sourceforge is just too easy, and I don't see a compelling alternative right now other than this idea forming here. Lots of people are sick of git and all the weird pageantry one must endure to create an "official" package.

#70 Re: DIY » New Project, a simple music player. And now a video player!! » 2026-02-03 22:04:01

Uploaded the first test version, looks good, test installs all went well and no issues playing.

https://sourceforge.net/projects/vuu-do … apps/VSVP/

#71 Re: Desktop and Multimedia » [SOLVED] Devuan Excalibur install and Pipewire. » 2026-02-03 17:40:16

Just the first thought in my head which may not be helpful, but what you describe now seems like it may be a timing issue during start-up? In my autostart folders I sometimes have to shuffle some things around and/or add timeouts.

#72 Re: DIY » Thumbnail caching script for PcmanFM » 2026-02-03 17:25:36

^^^^ For sure PcmanFM is nice, lightweight and fast, been using it for a long time. And the bug is more of an annoyance than an issue, but still deserves to be fixed. And the feature I want to add is similar to a feature in Caja I really like, which is the ability to display the size of the folder/file underneath the name in icon-view. Many months ago when I made this script I had no real working knowledge of C, so patching it was out of the question. But now i'm about ready to try. Any chance I can get you in on testing a patched version when it happens?

I combined two "hobby" projects devuan and vuudo.

That is very gratifying, it's exactly what I intended for Vuu-do from the get-go, to be a great base people could build their own perfect system from, and same way now with the apps. It's the MIYO way (pretty much the opposite of the Gnome way, lol), take it, break it, have fun with it and make it your own. wink

#73 Re: DIY » New Project, a simple music player. And now a video player!! » 2026-02-02 23:51:42

Hi zappy! Are you referring to the video player? If so i'm about to package tomorrow probably, just a plain .deb using dpkg-deb --build like my other ones. This app was 100% built on an Alsa-only system, it plays nice with AlsaTune too. That being said I don't know how it would play if at all with sndio. It also is built on Daedalus libraries like the music player, and won't work on Excalibur until I build it against the newer libav. It is possible to install the 59 packages alongside the 61 packages and have it work, but that's a lot of extra depends. You could try building it yourself, it's just the main script that needs doing, the wrapper and the scraper are just bash/yad and i'm happy to share the source code, build depends and compile command that i'm currently using.

Also just in general, probably not going to work right on Wayland, the script includes an X11-specific hack and headers.

If you're talking about the music player the .deb is already up on my sourceforge.

#75 Re: Devuan » Proposal "Devuan User Repository" » 2026-02-02 21:46:36

And you have, and will (hopefully) keep doing that! I don't currently have a use case for the software and scripts you've released at this time, but it's seriously cool to see more love for YAD (I see you're a regular at the BunsenLabs forums, too wink).

Appreciate that good buddy, yeah I hang out over there sometimes, great group of folks who do some really interesting work.

Board footer

Forum Software