The officially official Devuan Forum!

You are not logged in.

#1 2025-06-25 01:52:19

greenjeans
Member
Registered: 2017-04-07
Posts: 970  
Website

Working on a new app, the learning curve continues...

EDIT: App fininshed and packaged and uploaded for testing! https://sourceforge.net/projects/vuu-do … -App-Info/
You'll need to have yad installed as well as imagemagick. Updated pic of how it looks in post #15 in this thread.

Been screwing around most of the day with this, had never messed with C before, or compiling it so this is a first for me, awesome learning experience but omg does it ever take me forever to figure out how to do simple stuff, lol, RRQ probably could do it in 5 minutes on leafpad!

Simple app to list applications installed and a description, also has a search function that starts working as soon as you start typing, it scrapes /usr/share/applications then cross-references that list with /var/lib/dpkg/status for the description. Simple but possibly useful to the new folks I convert to linux around here. Here's a screenshot, nothing fancy and this is just the working prototype, I have a lot of work to do yet polishing up the text area as it's kinda shoved all together right now:

1bbuzs.png

Last edited by greenjeans (Yesterday 17:55:33)


https://sourceforge.net/projects/vuu-do/ New Vuu-do isos uploaded April 2025!
Vuu-do GNU/Linux, minimal Devuan-based Openbox and Mate systems to build on. Also a max version for OB.
Devuan 5 mate-mini iso, pure Devuan, 100% no-vuu-do. wink Devuan 6 version also available for testing.
Please donate to support Devuan and init freedom! https://devuan.org/os/donate

Online

#2 2025-06-25 18:35:52

swanson
Member
Registered: 2020-04-22
Posts: 118  

Re: Working on a new app, the learning curve continues...

Looks interesting!

Offline

#3 2025-06-25 19:29:06

brocashelm
Member
Registered: 2020-06-29
Posts: 147  

Re: Working on a new app, the learning curve continues...

Looks cool. What toolkit are you using?

Apart from probably the search bar, you could also create this using YAD (maybe even Zenity). I've been studying up on some custom GUI boxes using this method. This example for reading system information is one of my favorites I've come across so far (a nice drop-in replacement for Hardinfo). It looks good on GTK2.

Offline

#4 2025-06-25 23:01:21

greenjeans
Member
Registered: 2017-04-07
Posts: 970  
Website

Re: Working on a new app, the learning curve continues...

Thanks guys!

Yeah Yad + shellscript was my first instinct as it's my go-to for so many things, but just couldn't get it to look and act right. Also tried using webkit to make a little browser basically as i've had some luck on another app doing that and python for the rest, but still no luck. Finally used C and just plain ol gtk and that worked insanely well and fast for the GUI, took a fair amount of tries but i'm happy with it. The separate scraper script is done in bash.

Got to looking at yad while trying to work that out, fired up the yad-icon-browser which was super-close to what I wanted, and I always figured since it came packaged with yad that it was made with yad, lol, nope. Binary file written in C so I really don't understand why it's packaged with yad as there is almost no yad in it whatsoever. But....

The source code for it is very small, and it was just a matter of stripping out a lot of it that I didn't need, then re-writing it a bit to scrape a text file for content rather than the icons. I'm using that text file created by the scraper script instead of sqlite for a database. One of the goals here was to NOT have to install extra depends for this over and above what's typically on a basic system, keeping it simple, small, and fast and doing one job well.

Still need to fine-tune the scraper script, it's missing the description portion in about 10% of the apps, mainly ones with weird names for their .desktop files instead of straightforward ones, like Handbrake ought to be handbrake.desktop, but instead it's "fr.handbrake.ghb.desktop" and Hexchat is "io.github.hexchat.desktop", somehow that's defeating me, currently trying some new mods to the script.

Last edited by greenjeans (2025-06-25 23:02:42)


https://sourceforge.net/projects/vuu-do/ New Vuu-do isos uploaded April 2025!
Vuu-do GNU/Linux, minimal Devuan-based Openbox and Mate systems to build on. Also a max version for OB.
Devuan 5 mate-mini iso, pure Devuan, 100% no-vuu-do. wink Devuan 6 version also available for testing.
Please donate to support Devuan and init freedom! https://devuan.org/os/donate

Online

#5 2025-06-25 23:19:55

greenjeans
Member
Registered: 2017-04-07
Posts: 970  
Website

Re: Working on a new app, the learning curve continues...

Here's the scraper script if anyone has any suggestions for my little issue with it not grabbing the description for some apps, ignore the paths at the top, it's not an Appimage, that's just a directory that wound up to be my area for all experiments:

EDIT: Been messing with this all day today, and am soooooo close to now to 100% with the scraper, fixed like 90% of the issues. But a few minutes ago I had a forehead slapper moment, I think i've been going about this the hard way, gonna try a little different tack.

Last edited by greenjeans (2025-06-26 21:38:21)


https://sourceforge.net/projects/vuu-do/ New Vuu-do isos uploaded April 2025!
Vuu-do GNU/Linux, minimal Devuan-based Openbox and Mate systems to build on. Also a max version for OB.
Devuan 5 mate-mini iso, pure Devuan, 100% no-vuu-do. wink Devuan 6 version also available for testing.
Please donate to support Devuan and init freedom! https://devuan.org/os/donate

Online

#6 2025-06-27 01:28:16

greenjeans
Member
Registered: 2017-04-07
Posts: 970  
Website

Re: Working on a new app, the learning curve continues...

Holy cow has it ever been a day, I don't know how y'all that do this for a living do it, just grateful that you do!

After tons of adding functions of various types I finally have it nailed down 99.99%. Had to add some Mate-specific exclusions along with others to get rid of the simple utilities that are self-explanatory, Openbox won't have those issues but I imagine if someone tried this with another DE that more exclusions would have to be added. Scraper script is pretty complex now, and takes quite a while to run, it's only a one-time run needed for this, honestly I could have just typed out the database in short order, but that's not the point of this exercise.

End goal is to integrate this with the release notes/manual I do in Vuu-do, and provide an all-in-one help app for folks brand new to linux to get oriented quickly.

Still needs a little work on text-formatting, i'm tempted to try and see if parallel-processing can speed up the scraper script, but that's low priority since it only needs to run once, just considering it for the experience and furthering my knowledge-base.

If anybody is interested let me know and i'll post up the two scripts, you'll have to compile the C yourself but that's super-easy and only takes a few seconds, just need GCC and libgtk3-dev.


https://sourceforge.net/projects/vuu-do/ New Vuu-do isos uploaded April 2025!
Vuu-do GNU/Linux, minimal Devuan-based Openbox and Mate systems to build on. Also a max version for OB.
Devuan 5 mate-mini iso, pure Devuan, 100% no-vuu-do. wink Devuan 6 version also available for testing.
Please donate to support Devuan and init freedom! https://devuan.org/os/donate

Online

#7 2025-06-27 17:13:47

rbit
Member
Registered: 2018-06-12
Posts: 62  

Re: Working on a new app, the learning curve continues...

Scraper script is pretty complex now, and takes quite a while to run, it's only a one-time run needed for this,

Next step, rewrite the scraper in C
:-)

Offline

#8 2025-06-27 20:04:14

greenjeans
Member
Registered: 2017-04-07
Posts: 970  
Website

Re: Working on a new app, the learning curve continues...

^^^ Oh hell no, lol! Been fighting with the main script (C) all morning trying to get the text and bullet points properly formatted, so far all i've succeeded in doing is changing the font and spacing which does look much nicer, but still, i'm no C wiz, no way i'm ready to take on doing the scraper in C, I just now got it whipped into beautiful working shape and even that took a ton of trial and error despite shellscript being something i'm decent at now.

EDIT: The below scripts have been changed a lot and are now obsolete, just leaving them up for the heck of it.

Here is the two scripts, scraper and the raw C code for the GUI and the bit to compile if someone wants to try it out, note that you'll need to change the paths at the top of the scripts and the compile line. Just look at that scraper script and all the pattern-matching crap I had to do, this is because some folks just refuse to do some common logical naming of things. the gnome-disk-utility is one of the worst offenders in that regard (not surprising that), they literally have a different name for every identifier, .desktop name, name, exec and package name, whereas most apps use the same basic name for all those things making it a breeze to parse and connect the name to the description.

gcc -o ~/path/to/appbrowser ~/path/to/appbrowser.c $(pkg-config --cflags --libs gtk+-3.0)

GUI

#define _GNU_SOURCE
#include <gtk/gtk.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX_LINE 1024

// Struct to hold app data
typedef struct {
    char *name;
    char *description;
} AppData;

// Global app list
AppData *apps = NULL;
int num_apps = 0;

// GTK widgets
GtkWidget *window;
GtkWidget *app_list_view;
GtkListStore *app_store;
GtkWidget *desc_view;
GtkTextBuffer *desc_buffer;
GtkWidget *search_entry;

// Get dynamic data file path
char *get_data_file_path() {
    char *home = getenv("HOME");
    if (!home) {
        g_print("Error: Cannot get HOME environment variable\n");
        return NULL;
    }
    char *path = malloc(strlen(home) + strlen("/path/to/scraped_apps.txt") + 1);
    sprintf(path, "%s/path/to/scraped_apps.txt", home);
    return path;
}

// Load apps from scraped_apps.txt
void load_apps() {
    char *data_file = get_data_file_path();
    if (!data_file) {
        gtk_text_buffer_set_text(desc_buffer, "Error: Cannot get data file path", -1);
        return;
    }

    FILE *file = fopen(data_file, "r");
    if (!file) {
        char err_msg[256];
        snprintf(err_msg, 256, "Error: Cannot open %s", data_file);
        gtk_text_buffer_set_text(desc_buffer, err_msg, -1);
        free(data_file);
        return;
    }

    char line[MAX_LINE];
    int capacity = 100;
    apps = malloc(capacity * sizeof(AppData));
    num_apps = 0;

    while (fgets(line, MAX_LINE, file)) {
        // Remove trailing newline
        line[strcspn(line, "\n")] = '\0';

        // Parse line: Name|Exec|Description
        char *name = strtok(line, "|");
        if (!name) continue;
        char *exec = strtok(NULL, "|");
        if (!exec) continue;
        char *desc = strtok(NULL, "\0"); // Capture entire description
        if (!desc) desc = "No description available";

        if (num_apps >= capacity) {
            capacity *= 2;
            apps = realloc(apps, capacity * sizeof(AppData));
        }

        apps[num_apps].name = strdup(name);
        apps[num_apps].description = strdup(desc);
        num_apps++;
    }
    fclose(file);
    free(data_file);
    g_print("Loaded %d apps from %s\n", num_apps, data_file);
}

// Filter apps based on search
gboolean filter_apps(GtkTreeModel *model, GtkTreeIter *iter, gpointer data) {
    const char *search_text = gtk_entry_get_text(GTK_ENTRY(search_entry));
    if (!search_text || strlen(search_text) == 0) return TRUE;

    char *name;
    gtk_tree_model_get(model, iter, 0, &name, -1);
    if (!name) return FALSE;
    gboolean visible = strcasestr(name, search_text) != NULL;
    g_free(name);
    return visible;
}

// Update description pane
void on_app_selected(GtkTreeSelection *selection, gpointer data) {
    GtkTreeIter filter_iter;
    GtkTreeModel *filter_model;
    if (gtk_tree_selection_get_selected(selection, &filter_model, &filter_iter)) {
        GtkTreeIter child_iter;
        GtkTreeModel *child_model = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(filter_model));
        gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(filter_model), &child_iter, &filter_iter);
        char *name;
        gtk_tree_model_get(child_model, &child_iter, 0, &name, -1);
        g_print("Selected app: %s\n", name ? name : "NULL");
        if (name) {
            for (int i = 0; i < num_apps; i++) {
                if (apps[i].name && strcmp(apps[i].name, name) == 0) {
                    g_print("Setting description for %s\n", name);
                    gtk_text_buffer_set_text(desc_buffer, apps[i].description ? apps[i].description : "No description available", -1);
                    g_free(name);
                    return;
                }
            }
            g_print("No matching app found for %s\n", name);
            g_free(name);
            gtk_text_buffer_set_text(desc_buffer, "No description available", -1);
        } else {
            g_print("Failed to get app name\n");
            gtk_text_buffer_set_text(desc_buffer, "Error: Invalid selection", -1);
        }
    } else {
        g_print("No selection\n");
        gtk_text_buffer_set_text(desc_buffer, "Select an app to view its description", -1);
    }
}

// Update filter on search change
void on_search_changed(GtkEntry *entry, gpointer data) {
    gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(data));
}

int main(int argc, char *argv[]) {
    gtk_init(&argc, &argv);

    // Create window
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "Vuu-do App Info");
    gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    // Create main container
    GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
    gtk_container_add(GTK_CONTAINER(window), vbox);

    // Search entry
    search_entry = gtk_entry_new();
    gtk_entry_set_placeholder_text(GTK_ENTRY(search_entry), "Search apps by name...");
    gtk_box_pack_start(GTK_BOX(vbox), search_entry, FALSE, FALSE, 5);

    // Paned layout
    GtkWidget *paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
    gtk_paned_set_position(GTK_PANED(paned), 300); // Wide app list
    gtk_box_pack_start(GTK_BOX(vbox), paned, TRUE, TRUE, 5);

    // App list (left pane)
    app_store = gtk_list_store_new(1, G_TYPE_STRING);
    GtkTreeModel *filter_model = gtk_tree_model_filter_new(GTK_TREE_MODEL(app_store), NULL);
    gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(filter_model), filter_apps, NULL, NULL);

    app_list_view = gtk_tree_view_new_with_model(filter_model);
    GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
    GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes("Applications", renderer, "text", 0, NULL);
    gtk_tree_view_append_column(GTK_TREE_VIEW(app_list_view), column);
    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(app_list_view), TRUE);

    // Populate app list
    load_apps();
    for (int i = 0; i < num_apps; i++) {
        GtkTreeIter iter;
        gtk_list_store_append(app_store, &iter);
        gtk_list_store_set(app_store, &iter, 0, apps[i].name, -1);
    }

    GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    gtk_container_add(GTK_CONTAINER(scroll), app_list_view);
    gtk_paned_pack1(GTK_PANED(paned), scroll, FALSE, FALSE);

    // Description pane (right)
    desc_view = gtk_text_view_new();
    gtk_text_view_set_editable(GTK_TEXT_VIEW(desc_view), FALSE);
    gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(desc_view), GTK_WRAP_WORD);
    gtk_text_view_set_left_margin(GTK_TEXT_VIEW(desc_view), 10);
    gtk_text_view_set_right_margin(GTK_TEXT_VIEW(desc_view), 10);
    gtk_text_view_set_top_margin(GTK_TEXT_VIEW(desc_view), 10);
    gtk_text_view_set_bottom_margin(GTK_TEXT_VIEW(desc_view), 10);
    desc_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(desc_view));

    // Apply CSS for font and spacing
    GtkCssProvider *provider = gtk_css_provider_new();
    gtk_css_provider_load_from_data(provider,
        "textview { line-height: 1.5; padding: 10px; font-family: Sans; }", -1, NULL);
    gtk_style_context_add_provider(gtk_widget_get_style_context(desc_view),
        GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    g_object_unref(provider);

    gtk_text_buffer_set_text(desc_buffer, "Select an app to view its description", -1);

    scroll = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    gtk_container_add(GTK_CONTAINER(scroll), desc_view);
    gtk_paned_pack2(GTK_PANED(paned), scroll, TRUE, FALSE);

    // Connect signals
    GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(app_list_view));
    gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
    g_signal_connect(selection, "changed", G_CALLBACK(on_app_selected), NULL);
    g_signal_connect(search_entry, "changed", G_CALLBACK(on_search_changed), filter_model);

    // Show all
    gtk_widget_show_all(window);
    gtk_main();

    // Cleanup
    for (int i = 0; i < num_apps; i++) {
        free(apps[i].name);
        free(apps[i].description);
    }
    free(apps);
    return 0;
}

Scraper

#!/bin/bash

# Output file for scraped data
output_file="$HOME/path/to/scraped_apps.txt"
debug_log="$HOME/path/to/scrape_debug.log"
mkdir -p "$(dirname "$output_file")"

# Check if output file exists and is non-empty
if [ -s "$output_file" ]; then
    echo "Using existing $output_file" >> "$debug_log"
    exit 0
fi

# Initialize output and debug files
: > "$output_file"
: > "$debug_log"

# Parse /var/lib/dpkg/status for package descriptions
echo "Scraping /var/lib/dpkg/status..." >> "$debug_log"
declare -A dpkg_desc
current_pkg=""
desc=""
if [ ! -r "/var/lib/dpkg/status" ]; then
    echo "Error: Cannot read /var/lib/dpkg/status" >> "$debug_log"
    exit 1
fi
while IFS= read -r line; do
    if [[ "$line" =~ ^Package:\ (.+) ]]; then
        current_pkg="${BASH_REMATCH[1]}"
    elif [[ "$line" =~ ^Description:\ (.+) ]]; then
        desc="${BASH_REMATCH[1]}"
    elif [[ "$line" =~ ^\ (.+) ]] && [[ -n "$current_pkg" ]] && [[ -n "$desc" ]]; then
        desc+=" ${BASH_REMATCH[1]}"
    elif [[ -z "$line" ]] && [[ -n "$current_pkg" ]] && [[ -n "$desc" ]]; then
        dpkg_desc["$current_pkg"]="$desc"
        current_pkg=""
        desc=""
    fi
done < /var/lib/dpkg/status
echo "Found ${#dpkg_desc[@]} packages in /var/lib/dpkg/status" >> "$debug_log"

# Function to find a matching term from .desktop filename, Name, and Exec fields
find_matching_term() {
    local desktop_file="$1"
    local package_name="$2"

    # Extract Name and Exec fields, convert to lowercase
    name_field=$(grep -m1 -i "^Name=" "$desktop_file" | sed 's/^Name=//' | tr '[:upper:]' '[:lower:]' | tr -s ' ')
    exec_field=$(grep -m1 -i "^Exec=" "$desktop_file" | sed 's/^Exec=//' | tr '[:upper:]' '[:lower:]' | awk '{print $1}' | xargs basename)

    # Get the .desktop filename without extension, converted to lowercase
    desktop_base=$(basename "$desktop_file" .desktop | tr '[:upper:]' '[:lower:]')

    # Split filename on dots and dashes
    desktop_terms=(${desktop_base//[.-]/ }) # Split on . or -

    # Find common terms across fields
    for term in "${desktop_terms[@]}"; do
        if [[ "$name_field" =~ $term || "$exec_field" =~ $term ]]; then
            echo "Found matching term '$term' for $desktop_file" >> "$debug_log"
            if [[ -n "${dpkg_desc[$term]}" ]]; then
                echo "$term"
                return
            fi
        fi
    done

    # Try common suffixes
    for suffix in "-all" "-gui" "-common"; do
        local test_pkg="${package_name}${suffix}"
        if [[ -n "${dpkg_desc[$test_pkg]}" ]]; then
            echo "Found package with suffix '$test_pkg' for $desktop_file" >> "$debug_log"
            echo "$test_pkg"
            return
        fi
    done

    # Fallback to dpkg -S
    local dpkg_pkg
    dpkg_pkg=$(dpkg -S "$(basename "$desktop_file")" 2>/dev/null | cut -d':' -f1)
    if [[ -n "$dpkg_pkg" && -n "${dpkg_desc[$dpkg_pkg]}" ]]; then
        echo "Found package '$dpkg_pkg' via dpkg -S for $desktop_file" >> "$debug_log"
        echo "$dpkg_pkg"
        return
    fi

    # Final fallback: return original package name
    echo "No matching term, suffix, or dpkg -S found for $desktop_file, using $package_name" >> "$debug_log"
    echo "$package_name"
}

# Scrape .desktop files
desktop_dir="/usr/share/applications"
echo "Scraping $desktop_dir..." >> "$debug_log"
if [ ! -d "$desktop_dir" ]; then
    echo "Error: Directory $desktop_dir does not exist" >> "$debug_log"
    exit 1
fi
count=0
shopt -s nullglob # Handle case where no .desktop files exist
for desktop_file in "$desktop_dir"/*.desktop; do
    if [ ! -f "$desktop_file" ] || [ ! -r "$desktop_file" ]; then
        echo "Skipping $desktop_file: File does not exist or is not readable" >> "$debug_log"
        continue
    fi
    echo "Processing $desktop_file" >> "$debug_log"
    
    # Check owning package with dpkg -S
    owning_pkg=$(dpkg -S "$(basename "$desktop_file")" 2>/dev/null | cut -d':' -f1)
    if [[ "$owning_pkg" == "mate-utils" || "$owning_pkg" == "mate-control-center" || "$owning_pkg" == "mate-desktop" ]]; then
        echo "Skipping $desktop_file: Owned by $owning_pkg" >> "$debug_log"
        continue
    fi
    
    # Extract Name, Exec, Comment, NoDisplay, and Categories using grep
    name=$(grep -m1 -i "^Name=" "$desktop_file" | sed 's/^Name=//')
    exec_cmd=$(grep -m1 -i "^Exec=" "$desktop_file" | sed 's/^Exec=//')
    comment=$(grep -m1 -i "^Comment=" "$desktop_file" | sed 's/^Comment=//')
    nodisplay=$(grep -m1 -i "^NoDisplay=" "$desktop_file" | sed 's/^NoDisplay=//')
    categories=$(grep -m1 -i "^Categories=" "$desktop_file" | sed 's/^Categories=//')
    
    # Skip if Name or Exec is empty
    if [ -z "$name" ] || [ -z "$exec_cmd" ]; then
        echo "Skipping $desktop_file: Missing Name or Exec" >> "$debug_log"
        continue
    fi
    
    # Skip if NoDisplay=true
    if [ "$nodisplay" = "true" ]; then
        echo "Skipping $desktop_file: NoDisplay=true" >> "$debug_log"
        continue
    fi
    
    # Skip if Categories contains "Settings" (case-insensitive)
    if echo "$categories" | grep -qi "Settings"; then
        echo "Skipping $desktop_file: Categories contains 'Settings'" >> "$debug_log"
        continue
    fi
    
    # Get package name (strip .desktop)
    pkg_name=$(basename "$desktop_file" .desktop | tr '[:upper:]' '[:lower:]')
    
    # Get description (dpkg or fallback to Comment)
    desc="${dpkg_desc[$pkg_name]:-$comment}"
    
    # If no description from dpkg or falls back to comment, try matching term
    if [ -z "$desc" ] || [ "$desc" = "$comment" ]; then
        new_pkg_name=$(find_matching_term "$desktop_file" "$pkg_name")
        desc="${dpkg_desc[$new_pkg_name]:-$comment}"
        echo "Retried description for $name with package $new_pkg_name" >> "$debug_log"
    fi
    
    # Final fallback
    desc="${desc:-No description available}"
    
    # Write to output file (format: Name|Exec|Description)
    echo "$name|$exec_cmd|$desc" >> "$output_file"
    ((count++))
    echo "Added $name to output" >> "$debug_log"
done
shopt -u nullglob

echo "Scraped $count apps from $desktop_dir" >> "$debug_log"
echo "Output written to $output_file" >> "$debug_log"
echo "Debug log: $debug_log"

# Add test app if no apps were scraped
if [ "$count" -eq 0 ]; then
    echo "No apps scraped, adding test app" >> "$debug_log"
    echo "TestApp|echo 'Test App'|Test application" >> "$output_file"
    ((count++))
    echo "Added TestApp to output" >> "$debug_log"
fi

echo "Total apps scraped: $count" >> "$debug_log"

Last edited by greenjeans (Today 14:26:09)


https://sourceforge.net/projects/vuu-do/ New Vuu-do isos uploaded April 2025!
Vuu-do GNU/Linux, minimal Devuan-based Openbox and Mate systems to build on. Also a max version for OB.
Devuan 5 mate-mini iso, pure Devuan, 100% no-vuu-do. wink Devuan 6 version also available for testing.
Please donate to support Devuan and init freedom! https://devuan.org/os/donate

Online

#9 2025-06-27 20:18:51

greenjeans
Member
Registered: 2017-04-07
Posts: 970  
Website

Re: Working on a new app, the learning curve continues...

I got rid of all the utilities that didn't need to be on there with some creative exclusions, so that cleaned up a lot.

cdkyjs.png


https://sourceforge.net/projects/vuu-do/ New Vuu-do isos uploaded April 2025!
Vuu-do GNU/Linux, minimal Devuan-based Openbox and Mate systems to build on. Also a max version for OB.
Devuan 5 mate-mini iso, pure Devuan, 100% no-vuu-do. wink Devuan 6 version also available for testing.
Please donate to support Devuan and init freedom! https://devuan.org/os/donate

Online

#10 2025-06-28 16:09:31

greenjeans
Member
Registered: 2017-04-07
Posts: 970  
Website

Re: Working on a new app, the learning curve continues...

Dang it's so close now, after messing with it for a long time yesterday I finally got some formatting going on in the description field, some paragraph separation and bullet points properly listed, but it wrecked the word wrap function for some reason I don't know yet. Still it's progress! You can see both the success in formatting and the fail in word wrap here:

i1bz4x.png


https://sourceforge.net/projects/vuu-do/ New Vuu-do isos uploaded April 2025!
Vuu-do GNU/Linux, minimal Devuan-based Openbox and Mate systems to build on. Also a max version for OB.
Devuan 5 mate-mini iso, pure Devuan, 100% no-vuu-do. wink Devuan 6 version also available for testing.
Please donate to support Devuan and init freedom! https://devuan.org/os/donate

Online

#11 2025-06-29 02:52:03

greenjeans
Member
Registered: 2017-04-07
Posts: 970  
Website

Re: Working on a new app, the learning curve continues...

Winning! Thumbnailing this screenie, I should really have done it for the others:

avxs9n.png

Tons of work but I figured out the word-wrap issue, bullet points etc. Had to also do some functions to fix the first line of the description in /var/lib/dpkg/status, most don't caplitalize the first letter of the first word and none of them add a a period at the end of it. Whole buncha little things like that took a lot of time to sort out, but i'm pretty tickled about where it's at now, got some things to do still though.

Check out that formatting now!


https://sourceforge.net/projects/vuu-do/ New Vuu-do isos uploaded April 2025!
Vuu-do GNU/Linux, minimal Devuan-based Openbox and Mate systems to build on. Also a max version for OB.
Devuan 5 mate-mini iso, pure Devuan, 100% no-vuu-do. wink Devuan 6 version also available for testing.
Please donate to support Devuan and init freedom! https://devuan.org/os/donate

Online

#12 2025-06-29 03:48:14

greenjeans
Member
Registered: 2017-04-07
Posts: 970  
Website

Re: Working on a new app, the learning curve continues...

It would be great if somebody was willing to test what I have so far. I am posting the latest scripts but first you need to create the folder ~/.local/share/vai and drop all these scripts in there and drop the .desktop file into ~/.local/share/applications, and here is the order i'm posting them:

EDIT: These scripts are also now obsolete, the final versions have changed them, just leaving for interesting reference to progress.

1. The wrapper script that must be named vai.sh
2. The scraper script that must be named scrape12.sh
3. The GUI raw C code, vai12.c you'll need to name vai12.c and compile with :gcc -o ~/.local/share/vai/vai12 ~/.local/share/vai/vai12.c $(pkg-config --cflags --libs gtk+-3.0)
4. The .desktop that goes in ~/.local/share/applications, you'll need to edit this file's path to use your username. But after that it should appear in your menu.
Make sure the scripts are made executable. Be aware that the scraper takes a long time, about 3.5 minutes on my ancient low-spec machine, probably a LOT faster on a modern machine with 4 cores or more. After that first run though, the app opens super fast.

Scripts:

#!/bin/sh

# Wrapper script for Vuu-do App Info (VAI)
# This application is for viewing information about installed apps.
# Copyleft 2025 greenjeans. Use as you see fit.
# This is free software with no warranty, use at your own risk.

# Paths
VAI_DIR="$HOME/.local/share/vai"
SCRAPED_APPS="$VAI_DIR/scraped_apps.txt"
SCRAPER="$HOME/.local/share/vai/scrape12.sh"
GUI="$HOME/.local/share/vai/vai12"

# Check if scraped_apps.txt exists, run scraper if not
if [ ! -f "$SCRAPED_APPS" ]; then
    if [ ! -x "$SCRAPER" ]; then
        echo "Error: Scraper script $SCRAPER not found or not executable" >&2
        exit 1
    fi
    "$SCRAPER" || {
        echo "Error: Scraper failed" >&2
        exit 1
    }
fi

# Run the GUI
if [ ! -x "$GUI" ]; then
    echo "Error: GUI binary $GUI not found or not executable" >&2
    exit 1
fi
exec "$GUI"
#!/bin/bash

# Copyleft 2025 greenjeans. Use as you see fit.
# This script scrapes your application files, parses their name and description
# and outputs the info into a text database to be read by the Vuu-do App Info GUI app.
# This is free software with no warranty, use at your own risk.

# Output file for scraped data
output_file="$HOME/.local/share/vai/scraped_apps.txt"
debug_log="$HOME/.local/share/vai/scrape_debug.log"
ensure_vai_dir
mkdir -p "$(dirname "$output_file")"

# Check and create ~/.local/share/vai directory
ensure_vai_dir() {
    local dir="$HOME/.local/share/vai"
    if [ ! -d "$dir" ]; then
        mkdir -p "$dir" || {
            echo "Error: Failed to create directory $dir" >&2
            exit 1
        }
        echo "Created directory $dir" >> "$debug_log"
    fi
}

# Check if output file exists and is non-empty
if [ -s "$output_file" ]; then
    echo "Using existing $output_file" >> "$debug_log"
    exit 0
fi

# Initialize output and debug files
: > "$output_file"
: > "$debug_log"

# Parse /var/lib/dpkg/status for package descriptions
echo "Scraping /var/lib/dpkg/status..." >> "$debug_log"
declare -A dpkg_desc
current_pkg=""
desc=""
if [ ! -r "/var/lib/dpkg/status" ]; then
    echo "Error: Cannot read /var/lib/dpkg/status" >> "$debug_log"
    exit 1
fi
while IFS= read -r line; do
    if [[ "$line" =~ ^Package:\ (.+) ]]; then
        if [[ -n "$current_pkg" && -n "$desc" ]]; then
            dpkg_desc["$current_pkg"]="$desc"
        fi
        current_pkg="${BASH_REMATCH[1]}"
        desc=""
    elif [[ "$line" =~ ^Description:\ (.+) ]]; then
        desc="${BASH_REMATCH[1]}"
    elif [[ "$line" =~ ^\ (.+) ]] && [[ -n "$current_pkg" ]] && [[ -n "$desc" ]]; then
        # Normalize bullet markers to *
        line="${BASH_REMATCH[1]}"
        if [[ "$line" =~ ^[-.] ]]; then
            line="*${line:1}"
        fi
        desc+=$'\n'"$line"
    elif [[ -z "$line" ]] && [[ -n "$current_pkg" ]] && [[ -n "$desc" ]]; then
        dpkg_desc["$current_pkg"]="$desc"
        current_pkg=""
        desc=""
    fi
done < /var/lib/dpkg/status
if [[ -n "$current_pkg" && -n "$desc" ]]; then
    dpkg_desc["$current_pkg"]="$desc"
fi
echo "Found ${#dpkg_desc[@]} packages in /var/lib/dpkg/status" >> "$debug_log"

# Function to find a matching term from .desktop filename, Name, and Exec fields
find_matching_term() {
    local desktop_file="$1"
    local package_name="$2"

    # Extract Name and Exec fields, convert to lowercase
    name_field=$(grep -m1 -i "^Name=" "$desktop_file" | sed 's/^Name=//' | tr '[:upper:]' '[:lower:]' | tr -s ' ')
    exec_field=$(grep -m1 -i "^Exec=" "$desktop_file" | sed 's/^Exec=//' | tr '[:upper:]' '[:lower:]' | awk '{print $1}' | xargs basename)

    # Get the .desktop filename without extension, converted to lowercase
    desktop_base=$(basename "$desktop_file" .desktop | tr '[:upper:]' '[:lower:]')

    # Split filename on dots and dashes
    desktop_terms=(${desktop_base//[.-]/ }) # Split on . or -

    # Find common terms across fields
    for term in "${desktop_terms[@]}"; do
        if [[ "$name_field" =~ $term || "$exec_field" =~ $term ]]; then
            echo "Found matching term '$term' for $desktop_file" >> "$debug_log"
            if [[ -n "${dpkg_desc[$term]}" ]]; then
                echo "$term"
                return
            fi
        fi
    done

    # Try common suffixes
    for suffix in "-all" "-gui" "-common"; do
        local test_pkg="${package_name}${suffix}"
        if [[ -n "${dpkg_desc[$test_pkg]}" ]]; then
            echo "Found package with suffix '$test_pkg' for $desktop_file" >> "$debug_log"
            echo "$test_pkg"
            return
        fi
    done

    # Fallback to dpkg -S
    local dpkg_pkg
    dpkg_pkg=$(dpkg -S "$(basename "$desktop_file")" 2>/dev/null | cut -d':' -f1)
    if [[ -n "$dpkg_pkg" && -n "${dpkg_desc[$dpkg_pkg]}" ]]; then
        echo "Found package '$dpkg_pkg' via dpkg -S for $desktop_file" >> "$debug_log"
        echo "$dpkg_pkg"
        return
    fi

    # Final fallback: return original package name
    echo "No matching term, suffix, or dpkg -S found for $desktop_file, using $package_name" >> "$debug_log"
    echo "$package_name"
}

# Scrape .desktop files
desktop_dir="/usr/share/applications"
echo "Scraping $desktop_dir..." >> "$debug_log"
if [ ! -d "$desktop_dir" ]; then
    echo "Error: Directory $desktop_dir does not exist" >> "$debug_log"
    exit 1
fi
count=0
shopt -s nullglob # Handle case where no .desktop files exist
for desktop_file in "$desktop_dir"/*.desktop; do
    if [ ! -f "$desktop_file" ] || [ ! -r "$desktop_file" ]; then
        echo "Skipping $desktop_file: File does not exist or is not readable" >> "$debug_log"
        continue
    fi
    echo "Processing $desktop_file" >> "$debug_log"
    
    # Check owning package with dpkg -S
    owning_pkg=$(dpkg -S "$(basename "$desktop_file")" 2>/dev/null | cut -d':' -f1)
    if [[ "$owning_pkg" == "mate-utils" || "$owning_pkg" == "mate-control-center" || "$owning_pkg" == "mate-desktop" ]]; then
        echo "Skipping $desktop_file: Owned by $owning_pkg" >> "$debug_log"
        continue
    fi
    
    # Extract Name, Exec, Comment, NoDisplay, and Categories using grep
    name=$(grep -m1 -i "^Name=" "$desktop_file" | sed 's/^Name=//')
    exec_cmd=$(grep -m1 -i "^Exec=" "$desktop_file" | sed 's/^Exec=//')
    comment=$(grep -m1 -i "^Comment=" "$desktop_file" | sed 's/^Comment=//')
    nodisplay=$(grep -m1 -i "^NoDisplay=" "$desktop_file" | sed 's/^NoDisplay=//')
    categories=$(grep -m1 -i "^Categories=" "$desktop_file" | sed 's/^Categories=//')
    
    # Skip if Name or Exec is empty
    if [ -z "$name" ] || [ -z "$exec_cmd" ]; then
        echo "Skipping $desktop_file: Missing Name or Exec" >> "$debug_log"
        continue
    fi
    
    # Skip if NoDisplay=true
    if [ "$nodisplay" = "true" ]; then
        echo "Skipping $desktop_file: NoDisplay=true" >> "$debug_log"
        continue
    fi
    
    # Skip if Categories contains "Settings" (case-insensitive)
    if echo "$categories" | grep -qi "Settings"; then
        echo "Skipping $desktop_file: Categories contains 'Settings'" >> "$debug_log"
        continue
    fi
    
    # Get package name (strip .desktop)
    pkg_name=$(basename "$desktop_file" .desktop | tr '[:upper:]' '[:lower:]')
    
    # Get description (dpkg or fallback to Comment)
    desc="${dpkg_desc[$pkg_name]:-$comment}"
    
    # If no description from dpkg or falls back to comment, try matching term
    if [ -z "$desc" ] || [ "$desc" = "$comment" ]; then
        new_pkg_name=$(find_matching_term "$desktop_file" "$pkg_name")
        desc="${dpkg_desc[$new_pkg_name]:-$comment}"
        echo "Retried description for $name with package $new_pkg_name" >> "$debug_log"
    fi
    
    # Final fallback
    desc="${desc:-No description available}"
    
    # Add period to first line if missing
    first_line=$(echo "$desc" | head -n 1)
    if [[ ! "$first_line" =~ \.$ ]]; then
        desc=$(echo -e "$first_line.\n$(echo "$desc" | tail -n +2)")
    fi
    
    # Escape newlines for storage
    desc=$(echo "$desc" | tr '\n' '\v' | sed 's/\v/\\n/g')
    
    # Write to output file (format: Name|Exec|Description)
    echo "$name|$exec_cmd|$desc" >> "$output_file"
    ((count++))
    echo "Added $name to output" >> "$debug_log"
done
shopt -u nullglob

echo "Scraped $count apps from $desktop_dir" >> "$debug_log"
echo "Output written to $output_file" >> "$debug_log"
echo "Debug log: $debug_log"

# Add test app if no apps were scraped
if [ "$count" -eq 0 ]; then
    echo "No apps scraped, adding test app" >> "$debug_log"
    echo "TestApp|echo 'Test App'|Test application" >> "$output_file"
    ((count++))
    echo "Added TestApp to output" >> "$debug_log"
fi

echo "Total apps scraped: $count" >> "$debug_log"
#define _GNU_SOURCE
#include <gtk/gtk.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#define MAX_LINE 4096

// Struct to hold app data
typedef struct {
    char *name;
    char *description;
} AppData;

// Global app list
AppData *apps = NULL;
int num_apps = 0;

// GTK widgets
GtkWidget *window;
GtkWidget *app_list_view;
GtkListStore *app_store;
GtkWidget *desc_view;
GtkTextBuffer *desc_buffer;
GtkWidget *search_entry;

// Get dynamic data file path
char *get_data_file_path() {
    char *home = getenv("HOME");
    if (!home) {
        g_print("Error: Cannot get HOME environment variable\n");
        return NULL;
    }
    char *path = malloc(strlen(home) + strlen("/.local/share/vai/scraped_apps.txt") + 1);
    sprintf(path, "%s/.local/share/vai/scraped_apps.txt", home);
    return path;
}

// Capitalize first letter of the description
char *capitalize_first_letter(const char *desc) {
    if (!desc || strlen(desc) == 0) return strdup(desc);
    char *result = strdup(desc);
    if (islower(result[0])) {
        result[0] = toupper(result[0]);
    }
    return result;
}

// Process description to format bullet points and paragraph separators
char *format_description(const char *desc) {
    if (!desc || strlen(desc) == 0) return capitalize_first_letter("No description available");

    // Allocate enough space for formatted description
    char *result = malloc(strlen(desc) * 2 + MAX_LINE);
    char *current = result;
    const char *p = desc;
    int line_start = 1;
    char indent[] = "    ";
    char bullet[] = "• ";

    while (*p) {
        if (line_start && (*p == '*' || *p == '.')) {
            // Peek ahead to check if this is a bullet point or paragraph separator
            const char *next = p + 1;
            int spaces = 0;
            while (*next == ' ') {
                spaces++;
                next++;
            }
            if (*next == '\n' || *next == '\0' || (*next == '\\' && *(next + 1) == 'n')) {
                // Paragraph separator: add fat bullet without indent
                strcpy(current, bullet);
                current += strlen(bullet);
                p += 1 + spaces; // Skip marker and spaces
                if (*p == '\\' && *(p + 1) == 'n') {
                    p += 2; // Skip \n
                    *current++ = '\n'; // Add newline after separator
                }
                line_start = 1;
            } else {
                // Bullet point: add indent and bullet
                strcpy(current, indent);
                current += strlen(indent);
                strcpy(current, bullet);
                current += strlen(bullet);
                p += 1 + spaces; // Skip marker and spaces
                line_start = 0;
                // Copy the rest of the line
                while (*p && !(*p == '\\' && *(p + 1) == 'n') && *p != '\n') {
                    *current++ = *p++;
                }
                *current++ = '\n'; // Add newline after bullet point
                line_start = 1;
            }
        } else if (*p == '\\' && *(p + 1) == 'n') {
            // Handle explicit newlines
            p += 2; // Skip \n
            *current++ = '\n';
            line_start = 1;
        } else {
            // Copy character as-is, preserving spaces
            *current++ = *p++;
            line_start = 0;
        }
    }
    *current = '\0';

    // Capitalize first letter
    char *capitalized = capitalize_first_letter(result);
    free(result);

    // Debug output
    g_print("Formatted description:\n%s\n---\n", capitalized);

    return capitalized;
}

// Load apps from scraped_apps.txt
void load_apps() {
    char *data_file = get_data_file_path();
    if (!data_file) {
        gtk_text_buffer_set_text(desc_buffer, "Error: Cannot get data file path", -1);
        return;
    }

    FILE *file = fopen(data_file, "r");
    if (!file) {
        char err_msg[256];
        snprintf(err_msg, 256, "Error: Cannot open %s", data_file);
        gtk_text_buffer_set_text(desc_buffer, err_msg, -1);
        free(data_file);
        return;
    }

    char line[MAX_LINE];
    int capacity = 100;
    apps = malloc(capacity * sizeof(AppData));
    num_apps = 0;

    while (fgets(line, MAX_LINE, file)) {
        // Remove trailing newline
        line[strcspn(line, "\n")] = '\0';

        // Parse line: Name|Exec|Description
        char *name = strtok(line, "|");
        if (!name) continue;
        char *exec = strtok(NULL, "|");
        if (!exec) continue;
        char *desc = strtok(NULL, "\0"); // Capture entire description
        if (!desc) desc = "No description available";

        // Debug raw description
        g_print("Raw description for %s:\n%s\n---\n", name, desc);

        // Format description
        char *clean_desc = format_description(desc);

        if (num_apps >= capacity) {
            capacity *= 2;
            apps = realloc(apps, capacity * sizeof(AppData));
        }

        apps[num_apps].name = strdup(name);
        apps[num_apps].description = clean_desc;
        num_apps++;
    }
    fclose(file);
    free(data_file);
    g_print("Loaded %d apps from %s\n", num_apps, data_file);
}

// Filter apps based on search
gboolean filter_apps(GtkTreeModel *model, GtkTreeIter *iter, gpointer data) {
    const char *search_text = gtk_entry_get_text(GTK_ENTRY(search_entry));
    if (!search_text || strlen(search_text) == 0) return TRUE;

    char *name;
    gtk_tree_model_get(model, iter, 0, &name, -1);
    if (!name) return FALSE;
    gboolean visible = strcasestr(name, search_text) != NULL;
    g_free(name);
    return visible;
}

// Update description pane
void on_app_selected(GtkTreeSelection *selection, gpointer data) {
    GtkTreeIter filter_iter;
    GtkTreeModel *filter_model;
    if (gtk_tree_selection_get_selected(selection, &filter_model, &filter_iter)) {
        GtkTreeIter child_iter;
        GtkTreeModel *child_model = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(filter_model));
        gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(filter_model), &child_iter, &filter_iter);
        char *name;
        gtk_tree_model_get(child_model, &child_iter, 0, &name, -1);
        g_print("Selected app: %s\n", name ? name : "NULL");
        if (name) {
            for (int i = 0; i < num_apps; i++) {
                if (apps[i].name && strcmp(apps[i].name, name) == 0) {
                    g_print("Setting description for %s\n", name);
                    gtk_text_buffer_set_text(desc_buffer, apps[i].description ? apps[i].description : "No description available", -1);
                    g_free(name);
                    return;
                }
            }
            g_print("No matching app found for %s\n", name);
            g_free(name);
            gtk_text_buffer_set_text(desc_buffer, "No description available", -1);
        } else {
            g_print("Failed to get app name\n");
            gtk_text_buffer_set_text(desc_buffer, "Error: Invalid selection", -1);
        }
    } else {
        g_print("No selection\n");
        gtk_text_buffer_set_text(desc_buffer, "Select an app to view its description", -1);
    }
}

// Update filter on search change
void on_search_changed(GtkEntry *entry, gpointer data) {
    gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(data));
}

int main(int argc, char *argv[]) {
    gtk_init(&argc, &argv);

    // Create window
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "Vuu-do App Info");
    gtk_window_set_default_size(GTK_WINDOW(window), 950, 600);
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    // Create main container
    GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
    gtk_container_add(GTK_CONTAINER(window), vbox);

    // Search entry
    search_entry = gtk_entry_new();
    gtk_entry_set_placeholder_text(GTK_ENTRY(search_entry), "Search apps by name...");
    gtk_box_pack_start(GTK_BOX(vbox), search_entry, FALSE, FALSE, 5);

    // Paned layout
    GtkWidget *paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
    gtk_paned_set_position(GTK_PANED(paned), 300); // Wide app list
    gtk_box_pack_start(GTK_BOX(vbox), paned, TRUE, TRUE, 5);

    // App list (left pane)
    app_store = gtk_list_store_new(1, G_TYPE_STRING);
    GtkTreeModel *filter_model = gtk_tree_model_filter_new(GTK_TREE_MODEL(app_store), NULL);
    gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(filter_model), filter_apps, NULL, NULL);

    app_list_view = gtk_tree_view_new_with_model(filter_model);
    GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
    GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes("Applications", renderer, "text", 0, NULL);
    gtk_tree_view_append_column(GTK_TREE_VIEW(app_list_view), column);
    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(app_list_view), TRUE);

    // Populate app list
    load_apps();
    for (int i = 0; i < num_apps; i++) {
        GtkTreeIter iter;
        gtk_list_store_append(app_store, &iter);
        gtk_list_store_set(app_store, &iter, 0, apps[i].name, -1);
    }

    GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    gtk_container_add(GTK_CONTAINER(scroll), app_list_view);
    gtk_paned_pack1(GTK_PANED(paned), scroll, FALSE, FALSE);

    // Description pane (right)
    desc_view = gtk_text_view_new();
    gtk_text_view_set_editable(GTK_TEXT_VIEW(desc_view), FALSE);
    gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(desc_view), GTK_WRAP_WORD); // Use WORD wrap for natural text flow
    gtk_text_view_set_left_margin(GTK_TEXT_VIEW(desc_view), 10);
    gtk_text_view_set_right_margin(GTK_TEXT_VIEW(desc_view), 10);
    gtk_text_view_set_top_margin(GTK_TEXT_VIEW(desc_view), 10);
    gtk_text_view_set_bottom_margin(GTK_TEXT_VIEW(desc_view), 10);
    desc_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(desc_view));

    // Apply CSS for font and spacing
    GtkCssProvider *provider = gtk_css_provider_new();
    gtk_css_provider_load_from_data(provider,
        "textview { line-height: 1.5; padding: 10px; font-family: Sans; }", -1, NULL);
    gtk_style_context_add_provider(gtk_widget_get_style_context(desc_view),
        GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    g_object_unref(provider);

    gtk_text_buffer_set_text(desc_buffer, "Select an app to view its description", -1);

    scroll = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    gtk_container_add(GTK_CONTAINER(scroll), desc_view);
    gtk_paned_pack2(GTK_PANED(paned), scroll, TRUE, FALSE);

    // Connect signals
    GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(app_list_view));
    gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
    g_signal_connect(selection, "changed", G_CALLBACK(on_app_selected), NULL);
    g_signal_connect(search_entry, "changed", G_CALLBACK(on_search_changed), filter_model);

    // Show all
    gtk_widget_show_all(window);
    gtk_main();

    // Cleanup
    for (int i = 0; i < num_apps; i++) {
        free(apps[i].name);
        free(apps[i].description);
    }
    free(apps);
    return 0;
}
[Desktop Entry]
Version=1.0
Type=Application
Terminal=false
Name=Vuu-do App Info
Exec=/home/(username)/.local/share/vai/vai.sh
Icon=edit-find
Name=Vuu-do App Info
Comment=Information about installed applications.

Last edited by greenjeans (Today 14:28:01)


https://sourceforge.net/projects/vuu-do/ New Vuu-do isos uploaded April 2025!
Vuu-do GNU/Linux, minimal Devuan-based Openbox and Mate systems to build on. Also a max version for OB.
Devuan 5 mate-mini iso, pure Devuan, 100% no-vuu-do. wink Devuan 6 version also available for testing.
Please donate to support Devuan and init freedom! https://devuan.org/os/donate

Online

#13 2025-06-29 12:41:58

delgado
Member
Registered: 2022-07-14
Posts: 247  

Re: Working on a new app, the learning curve continues...

Nice work and install tutorial!

Here is a short collection of incedences on my excalibur:
Compiling: pkg-config --cflags --libs gtk+-3.0 throwed an error, solved by installing libgtk-3-dev .
Scraping: There were two or three instances of the script running, beside free cpu cores.
You mentioned "/var/lib/dpkg/status" and I thought this text file would be processed to get package related information.
dpkg was querried a lot (multiple ps ax in the terminal to kill time, e.g.: dpkg-query --search -- galculator.desktop )

#-#-#-#-#-#

Sorting:
A doubled "ParaView" entry cought my attention - see code section.

Guess is:
The list is sorted by the name of the *.desktop file. The shown "Application Name" is from the 'Name="Application Name"'-field.
*Highly accurate* entries like "Document Viewer" are existing too.
I'm missing a reference to the executed binary itself.
Or maybe executable name for sorting?

**imaginary screen shot of the program list**

(... "O" section here?!)
ParaView
QjackCtl
qpwgraph
samplev1
Wireshark
Mousepad
ParaView Cilent
(... more "P" entries)

**end of imaginary screen shot**

$ find | grep desktop$ | grep -i paraview
/usr/share/applications/org.paraview.ParaView.desktop
/usr/share/applications/paraview.desktop

$ diff  /usr/share/applications/paraview.desktop /usr/share/applications/org.paraview.ParaView.desktop
3,5d2
< Name=ParaView Client
< GenericName=Data Viewer
< Comment=ParaView allows viewing of large data sets
7c4,7
< Terminal=false
---
> Name=ParaView
> Comment=Parallel visualization application
> Exec=paraview %f
> TryExec=paraview
9,11c9,10
< MimeType=application/x-paraview;
< Categories=Education;Math;Science;
< Exec=paraview
---
> StartupWMClass=paraview
> Categories=Qt;Science;DataVisualization;

Offline

#14 2025-06-29 16:35:48

greenjeans
Member
Registered: 2017-04-07
Posts: 970  
Website

Re: Working on a new app, the learning curve continues...

Thanks so much for testing and posting! Interesting that you tired it on excalibur, the whole thing is built on a daedalus system, have not tested it in my own excalibur setups. Glad you figured out the libgtk3-dev package, I did mention it earlier in the thread but should ave included that as part of the install tutorial. I should have also mentioned that the scraper also creates a debug log which i'm leaving in for now until I get this thing fully tested.

Yeah the calls to dpkg would not have been necessary at all if all applications followed a simple naming convention across the board, i.e same name of the .desktop, the app itself, it's excecutable, and the name of the package itself. As I mentioned before many of these apps have one or more of those factors different than the others, in the case of some apps like the gnome-disk-utility all four were completely different, it took some hella pattern matching to get those to show up and slowed the scraper waaaaaay down.

And packages like Calibre that own multiple .desktops (in calibre's case 4 total), will currently all show the main description of calibre itself.

I tried last night to implement one of the tweaks I want to do, adding a yad progress dialog and an "all done" dialog when it finished, which should have been a piece of cake as I work with yad dialogs in shellscripts all the time, but my first attempt when I ran it caused some kind of looping error that caused the debug log to fill up rapidly way over what it should have been and not stopping! Lol. that's when I decided my brain was too tired and just going to screw up further so I shut it down for the night. But i'll get that worked out hopefully today.

My two other goals are to add icons to the left pane in front of the app names similar to the way it looks in the main menu, pain in the butt to do using the same types of parsing/matching/scraping i'm doing now, but I have an idea about using the cached icons that should already be present in ~/.cache.

Last one is implementing something to speed up the scraper, still pondering how to do that.

All this code is rough as hell, but by golly I got it to a place of good basic functionality, been one heck of a learning experience, which was the primary goal in the first place, so i'm happy. big_smile

ETA: Got the dialogs working now! Simple visual verification that something is happening, and when it's finished.

Last edited by greenjeans (2025-06-29 18:33:10)


https://sourceforge.net/projects/vuu-do/ New Vuu-do isos uploaded April 2025!
Vuu-do GNU/Linux, minimal Devuan-based Openbox and Mate systems to build on. Also a max version for OB.
Devuan 5 mate-mini iso, pure Devuan, 100% no-vuu-do. wink Devuan 6 version also available for testing.
Please donate to support Devuan and init freedom! https://devuan.org/os/donate

Online

#15 Yesterday 00:15:06

greenjeans
Member
Registered: 2017-04-07
Posts: 970  
Website

Re: Working on a new app, the learning curve continues...

Right now, I smell like VICTORY! Smell it! Smell my victory!!

oxmy84.png

BOOM! *mic drop* This puppy is DONE! My first ever stand-alone application with a real GUI, and man what a learning experience!

Time to go after bigger projects now, I have it in mind to fork the crap out of the gnome-disk-utility, get rid of the bullshit CSD and fix their failure to properly wipe empty space before creating secondary partitions.

I'll post the final scripts here in a bit, kinda wiped out right now and popping the top on a cold one to take a victory lap. wink

Thank you Devuan and Dev1galaxy and all it's users for making my education possible! Beers for everyone!


https://sourceforge.net/projects/vuu-do/ New Vuu-do isos uploaded April 2025!
Vuu-do GNU/Linux, minimal Devuan-based Openbox and Mate systems to build on. Also a max version for OB.
Devuan 5 mate-mini iso, pure Devuan, 100% no-vuu-do. wink Devuan 6 version also available for testing.
Please donate to support Devuan and init freedom! https://devuan.org/os/donate

Online

#16 Yesterday 02:06:36

greenjeans
Member
Registered: 2017-04-07
Posts: 970  
Website

Re: Working on a new app, the learning curve continues...

Now working on doing yet another thing I haven't done before, making this into a .deb package...*sigh* looks like another day ahead of trial-and-error. lol.


https://sourceforge.net/projects/vuu-do/ New Vuu-do isos uploaded April 2025!
Vuu-do GNU/Linux, minimal Devuan-based Openbox and Mate systems to build on. Also a max version for OB.
Devuan 5 mate-mini iso, pure Devuan, 100% no-vuu-do. wink Devuan 6 version also available for testing.
Please donate to support Devuan and init freedom! https://devuan.org/os/donate

Online

#17 Yesterday 17:52:28

greenjeans
Member
Registered: 2017-04-07
Posts: 970  
Website

Re: Working on a new app, the learning curve continues...

Updated the OP, app done and packaged and uploaded! Would love it if some of you kind folks might test it out!
https://sourceforge.net/projects/vuu-do … -App-Info/

You'll need to have yad installed as well as imagemagick.

Last edited by greenjeans (Yesterday 17:53:54)


https://sourceforge.net/projects/vuu-do/ New Vuu-do isos uploaded April 2025!
Vuu-do GNU/Linux, minimal Devuan-based Openbox and Mate systems to build on. Also a max version for OB.
Devuan 5 mate-mini iso, pure Devuan, 100% no-vuu-do. wink Devuan 6 version also available for testing.
Please donate to support Devuan and init freedom! https://devuan.org/os/donate

Online

#18 Yesterday 23:00:01

GlennW
Member
From: Brisbane, Australia
Registered: 2019-07-18
Posts: 672  

Re: Working on a new app, the learning curve continues...

Nice work greenjeans, My first thought... how come the package managers don't have this kind of info(?).

Nice, really nice. Thank you.


pic from 1993, new guitar day.

Offline

#19 Yesterday 23:18:10

greenjeans
Member
Registered: 2017-04-07
Posts: 970  
Website

Re: Working on a new app, the learning curve continues...

Thanks Glenn! The thing is the package managers do have this info, at least in Synaptic, the descriptions in the app are the same ones you'd see if you opened Synaptic and went through your list of installed applications. This is just a quick and easy way to view just the apps that need explaining without having to wade through some 1200-2000 listings of all installed packages in Synaptic.

The idea is, when I convert someone to Linux, I always query them about what kinds of apps they need/want, and I roll them up a custom iso with everything they need already installed, but to a new user all the new names may be confusing, so rather than use Synaptic or open each and every program one by one, they can just click this and get a quick explanation. Obviously after that if folks install new programs from Synaptic they'll read the description there with no need really to re-run the app scraper as they chose those programs and already read the info. So after a time once they get used to using their new apps, this program will be obsoleted, but no biggie, I think it's a handy little reference to help them get up to speed quickly.


https://sourceforge.net/projects/vuu-do/ New Vuu-do isos uploaded April 2025!
Vuu-do GNU/Linux, minimal Devuan-based Openbox and Mate systems to build on. Also a max version for OB.
Devuan 5 mate-mini iso, pure Devuan, 100% no-vuu-do. wink Devuan 6 version also available for testing.
Please donate to support Devuan and init freedom! https://devuan.org/os/donate

Online

#20 Today 11:14:20

stargate-sg1-cheyenne-mtn
Member
Registered: 2023-11-27
Posts: 297  

Re: Working on a new app, the learning curve continues...

speaking of synaptic:

https://bkhome.org/news/202507/pkgget-now-syncs-with-apt.html

easyos now uses devuan, neat.


Be Excellent to each other and Party On!
https://www.youtube.com/watch?v=rph_1DODXDU
https://en.wikipedia.org/wiki/Bill_%26_Ted%27s_Excellent_Adventure
Do unto others as you would have them do instantaneously back to you!

Offline

Board footer