Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Dynamic Libraries (Linux)
#1
I will add SO files (Linux dynamic libraries) to this thread.

A question to the developers first. In the following program, I spent an hour searching for the reason why the IDE (4.2.0) in Linux (virtualized Ubuntu in Windows 10) does not see the SO file. How is it possible that the library name must be different from the actual library name on disk? Why? I don't understand!

I will gradually add more things here, but please be patient.

Zip file contains BAS code, C code for SO library and SO library.


Attached Files Thumbnail(s)
   

.zip   ListPick_Linux.zip (Size: 9.25 KB / Downloads: 12)


Reply
#2
What's the reason for using a dynamic library rather than compiling with a header into the code?

It works just fine by removing the EXPORT tags on the file, making it a .h header, adding the flags to the compiler in QB64, and then building.
   

Code: (Select All)
'Declare Dynamic Library "listpick"
'    Function PickFromListUTF8& (title As String, prompt As String, items As String, ByVal startIndex As Long)
'    Function PickFromListUTF8_Text& (title As String, prompt As String, items As String, ByVal startIndex As Long, outText As String, ByVal outBytes As Long)
'End Declare

Declare CustomType Library "ListPick_GTK"
    Function PickFromListUTF8& (title As String, prompt As String, items As String, ByVal startIndex As Long)
    Function PickFromListUTF8_Text& (title As String, prompt As String, items As String, ByVal startIndex As Long, outText As String, ByVal outBytes As Long)
End Declare


' --- use ---
title$ = "Select item" + Chr$(0)
prompt$ = "Write for filter, Enter=OK, Esc=Cancel" + Chr$(0)

item:
Data "Alpha","Beta","Gamma","Delta","Etha","Theta","Omikron","Mi","Psi","Kocici","Kachni","Rozum","Zelenej","Linux","To je ale dilo"
Restore item
For f = 1 To 15
    Read i$
    items$ = items$ + i$ + Chr$(10)
Next f

items$ = items$ + Chr$(0)

out$ = Space$(2048) ' buffer, in which C write text
idx& = PickFromListUTF8_Text&(title$, prompt$, items$, 1, out$, Len(out$))

out$ = ZTrim$(out$)

Print "idx="; idx&, " text="; out$



Function ZTrim$ (s$)
    Dim p As Long
    p = InStr(s$, Chr$(0))
    If p > 0 Then ZTrim$ = Left$(s$, p - 1) Else ZTrim$ = s$
End Function

Code: (Select All)
// listpick_gtk.c  (GTK3, UTF-8)
// build (bash/zsh):
//   gcc -O2 -fPIC -shared -o liblistpick.so listpick_gtk.c $(pkg-config --cflags --libs gtk+-3.0)
// build (fish):
//   gcc -O2 -fPIC -shared -o liblistpick.so listpick_gtk.c (pkg-config --cflags --libs gtk+-3.0)

#include <gtk/gtk.h>
#include <string.h>

typedef struct {
    GtkWidget *dialog;
    GtkWidget *entry;
    GtkWidget *list;     // GtkListBox
    GPtrArray *rows;     // index(orig) -> GtkListBoxRow*
    int bestOrig;        // preferovaný origin index
} Picker;

static int gtk_ready = 0;

static gboolean row_is_effectively_visible(GtkWidget *w) {
    // u GtkListBox filtrování typicky schovává řádky přes child_visible, ne přes visible
    return gtk_widget_get_child_visible(w);
}

static gboolean contains_nocase_utf8(const char *hay, const char *needle) {
    if (!needle || !*needle) return TRUE;
    if (!hay) return FALSE;

    char *h = g_utf8_casefold(hay, -1);
    char *n = g_utf8_casefold(needle, -1);
    gboolean ok = (g_strstr_len(h, -1, n) != NULL);
    g_free(h);
    g_free(n);
    return ok;
}

static gboolean row_filter(GtkListBoxRow *row, gpointer user_data) {
    Picker *p = (Picker *)user_data;
    const char *needle = gtk_entry_get_text(GTK_ENTRY(p->entry));
    if (!needle || !*needle) return TRUE;

    GtkWidget *child = gtk_bin_get_child(GTK_BIN(row)); // GtkLabel
    const char *text = gtk_label_get_text(GTK_LABEL(child));
    return contains_nocase_utf8(text, needle);
}

static void ensure_selection(Picker *p) {
    GtkListBox *box = GTK_LIST_BOX(p->list);

    GtkListBoxRow *sel = gtk_list_box_get_selected_row(box);
    if (sel && row_is_effectively_visible(GTK_WIDGET(sel))) return;

    // 1) zkus bestOrig (naposledy vybraná položka), pokud je viditelná ve filtru
    if (p->bestOrig >= 0 && p->bestOrig < (int)p->rows->len) {
        GtkListBoxRow *want = (GtkListBoxRow *)g_ptr_array_index(p->rows, p->bestOrig);
        if (want && row_is_effectively_visible(GTK_WIDGET(want))) {
            gtk_list_box_select_row(box, want);
            return;
        }
    }

    // 2) první viditelný řádek
    GList *children = gtk_container_get_children(GTK_CONTAINER(box));
    for (GList *l = children; l; l = l->next) {
        GtkWidget *w = GTK_WIDGET(l->data);
        if (row_is_effectively_visible(w)) {
            gtk_list_box_select_row(box, GTK_LIST_BOX_ROW(w));
            break;
        }
    }
    g_list_free(children);
}

static void on_entry_changed(GtkEditable *editable, gpointer user_data) {
    (void)editable;
    Picker *p = (Picker *)user_data;

    gtk_list_box_invalidate_filter(GTK_LIST_BOX(p->list));
    while (gtk_events_pending()) gtk_main_iteration(); // ať se child_visible přepočte hned

    ensure_selection(p);
}

static void on_row_selected(GtkListBox *box, GtkListBoxRow *row, gpointer user_data) {
    (void)box;
    Picker *p = (Picker *)user_data;
    if (!row) return;
    int orig = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(row), "orig"));
    p->bestOrig = orig;
}

static void on_row_activated(GtkListBox *box, GtkListBoxRow *row, gpointer user_data) {
    (void)box; (void)row;
    Picker *p = (Picker *)user_data;
    gtk_dialog_response(GTK_DIALOG(p->dialog), GTK_RESPONSE_OK);
}

static gboolean on_key_press(GtkWidget *w, GdkEventKey *e, gpointer user_data) {
    Picker *p = (Picker *)user_data;

    if (e->keyval == GDK_KEY_Escape) {
        gtk_dialog_response(GTK_DIALOG(p->dialog), GTK_RESPONSE_CANCEL);
        return TRUE;
    }
    if (e->keyval == GDK_KEY_Return || e->keyval == GDK_KEY_KP_Enter) {
        // před OK ještě pro jistotu dorovnej selection podle filtru
        ensure_selection(p);
        gtk_dialog_response(GTK_DIALOG(p->dialog), GTK_RESPONSE_OK);
        return TRUE;
    }
    if (w == p->entry && (e->keyval == GDK_KEY_Down || e->keyval == GDK_KEY_Up)) {
        gtk_widget_grab_focus(p->list);
        return FALSE;
    }
    return FALSE;
}

static void copy_out(char *out, int outBytes, const char *src) {
    if (!out || outBytes <= 0) return;
    out[0] = 0;
    if (!src) return;
    g_strlcpy(out, src, (gsize)outBytes);
}

static int pick_run_utf8(const char *title, const char *prompt, const char *items, int startIndex,
                         char *out, int outBytes) {
    if (!gtk_ready) {
        int argc = 0;
        char **argv = NULL;
        gtk_ready = gtk_init_check(&argc, &argv) ? 1 : 0;
        if (!gtk_ready) {
            copy_out(out, outBytes, "");
            return -1;
        }
    }

    Picker p;
    memset(&p, 0, sizeof(p));
    p.bestOrig = (startIndex >= 0) ? startIndex : 0;
    p.rows = g_ptr_array_new();

    p.dialog = gtk_dialog_new_with_buttons(
        title ? title : "Select",
        NULL,
        GTK_DIALOG_MODAL,
        "_Cancel", GTK_RESPONSE_CANCEL,
        "_OK", GTK_RESPONSE_OK,
        NULL
    );
    gtk_window_set_default_size(GTK_WINDOW(p.dialog), 520, 380);

    GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(p.dialog));

    GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
    gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
    gtk_widget_set_hexpand(vbox, TRUE);
    gtk_widget_set_vexpand(vbox, TRUE);
    gtk_box_pack_start(GTK_BOX(content), vbox, TRUE, TRUE, 0);

    GtkWidget *lbl = gtk_label_new(prompt ? prompt : "");
    gtk_label_set_xalign(GTK_LABEL(lbl), 0.0f);
    gtk_label_set_line_wrap(GTK_LABEL(lbl), TRUE);
    gtk_box_pack_start(GTK_BOX(vbox), lbl, FALSE, FALSE, 0);

    p.entry = gtk_entry_new();
    gtk_box_pack_start(GTK_BOX(vbox), p.entry, FALSE, FALSE, 0);

    GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    gtk_widget_set_hexpand(sw, TRUE);
    gtk_widget_set_vexpand(sw, TRUE);
    gtk_widget_set_halign(sw, GTK_ALIGN_FILL);
    gtk_widget_set_valign(sw, GTK_ALIGN_FILL);
    gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);

    p.list = gtk_list_box_new();
    gtk_list_box_set_selection_mode(GTK_LIST_BOX(p.list), GTK_SELECTION_SINGLE);
    gtk_widget_set_hexpand(p.list, TRUE);
    gtk_widget_set_vexpand(p.list, TRUE);
    gtk_widget_set_halign(p.list, GTK_ALIGN_FILL);
    gtk_widget_set_valign(p.list, GTK_ALIGN_FILL);
    gtk_container_add(GTK_CONTAINER(sw), p.list);

    // naplň řádky (items jsou UTF-8, oddělené '\n')
    const char *s = items ? items : "";
    int orig = 0;

    while (*s) {
        const char *e = strchr(s, '\n');
        size_t len = e ? (size_t)(e - s) : strlen(s);

        while (len && s[len - 1] == '\r') len--;

        char *line = g_strndup(s, len);

        GtkWidget *row = gtk_list_box_row_new();
        GtkWidget *t = gtk_label_new(line);
        gtk_label_set_xalign(GTK_LABEL(t), 0.0f);
        gtk_label_set_ellipsize(GTK_LABEL(t), PANGO_ELLIPSIZE_END);
        gtk_container_add(GTK_CONTAINER(row), t);

        g_object_set_data(G_OBJECT(row), "orig", GINT_TO_POINTER(orig));
        gtk_list_box_insert(GTK_LIST_BOX(p.list), row, -1);

        g_ptr_array_add(p.rows, row);
        g_free(line);

        orig++;
        if (!e) break;
        s = e + 1;
    }

    // filtering
    gtk_list_box_set_filter_func(GTK_LIST_BOX(p.list), row_filter, &p, NULL);

    // signály
    g_signal_connect(p.entry, "changed", G_CALLBACK(on_entry_changed), &p);
    g_signal_connect(p.list, "row-selected", G_CALLBACK(on_row_selected), &p);
    g_signal_connect(p.list, "row-activated", G_CALLBACK(on_row_activated), &p);
    g_signal_connect(p.entry, "key-press-event", G_CALLBACK(on_key_press), &p);
    g_signal_connect(p.list,  "key-press-event", G_CALLBACK(on_key_press), &p);

    gtk_widget_show_all(p.list);
    gtk_list_box_invalidate_filter(GTK_LIST_BOX(p.list));
    gtk_widget_show_all(p.dialog);

    // start selection
    if (startIndex >= 0 && startIndex < (int)p.rows->len) {
        GtkListBoxRow *r = (GtkListBoxRow *)g_ptr_array_index(p.rows, startIndex);
        gtk_list_box_select_row(GTK_LIST_BOX(p.list), r);
    } else {
        ensure_selection(&p);
    }
    gtk_widget_grab_focus(p.entry);

    int resp = gtk_dialog_run(GTK_DIALOG(p.dialog));

    // při OK ještě jednou dorovnej selection podle filtru (klik na OK myší by jinak mohl vrátit starou selection)
    if (resp == GTK_RESPONSE_OK) {
        gtk_list_box_invalidate_filter(GTK_LIST_BOX(p.list));
        while (gtk_events_pending()) gtk_main_iteration();
        ensure_selection(&p);
    }

    int result = -1;
    if (resp == GTK_RESPONSE_OK) {
        GtkListBoxRow *sel = gtk_list_box_get_selected_row(GTK_LIST_BOX(p.list));
        if (sel && row_is_effectively_visible(GTK_WIDGET(sel))) {
            result = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(sel), "orig"));
            GtkWidget *child = gtk_bin_get_child(GTK_BIN(sel));
            copy_out(out, outBytes, gtk_label_get_text(GTK_LABEL(child)));
        } else {
            copy_out(out, outBytes, "");
            result = -1;
        }
    } else {
        copy_out(out, outBytes, "");
        result = -1;
    }

    gtk_widget_destroy(p.dialog);
    while (gtk_events_pending()) gtk_main_iteration();

    g_ptr_array_free(p.rows, TRUE);
    return result;
}

// -------- exporty pro QB64PE (UTF-8) --------

int PickFromListUTF8(const char *titleUtf8, const char *promptUtf8,
                            const char *itemsUtf8, int startIndex) {
    return pick_run_utf8(titleUtf8, promptUtf8, itemsUtf8, startIndex, NULL, 0);
}

int PickFromListUTF8_Text(const char *titleUtf8, const char *promptUtf8,
                                 const char *itemsUtf8, int startIndex,
                                 char *outUtf8, int outBytes) {
    return pick_run_utf8(titleUtf8, promptUtf8, itemsUtf8, startIndex, outUtf8, outBytes);
}
The noticing will continue
Reply
#3
(12-15-2025, 11:53 AM)Petr Wrote: How is it possible that the library name must be different from the actual library name on disk? Why? I don't understand!
There is an extremely strong Linux (and other Unix?) convention that the dynamic library "foo" can be found on disk in the file "libfoo.so" and QB64 follows suit here.
Reply
#4
Thank you for the explanation regarding Linux conventions.

Could you explain why you chose .h over .so? that the code simply doesn't run  on my machine. SO works correctly.

Of course, there are always multiple ways to approach this.


Reply
#5
Isn't .so similar to the equivalent to a windows .dll?  

The .h is the text code before compilation.  The .so the (shared object) library after compilation.

Or am I completely off the mark here on this?
Reply
#6
@SMcNeill

You're not wrong, it's just as you write. What surprises me is that the compiler reports a missing GTK. But according to Linux, GTK is installed there. It lists the version, it's installed correctly. But I doesn't have deep knowledge about Linux, so I can only guess why it doesn't work for me. H files works fine for me in Windows.


Reply
#7
Input Box for Linux. This time use H file.

Code: (Select All)

Declare CustomType Library "./IbLNX"
    Function InputBoxUTF8_Text& (title As String, prompt As String, def As String, outText As String, ByVal outBytes As Long)
End Declare

_Title "Linux InputBox"

title$ = "Insert text" + Chr$(0)
prompt$ = "Write value:" + Chr$(0)
def$ = "default" + Chr$(0)

outt$ = Space$(2048)
ok& = InputBoxUTF8_Text&(title$, prompt$, def$, outt$, Len(outt$))
If ok& = 1 Then
    outt$ = ZTrim$(outt$)
    Print outt$
Else
    Print "cancel/error"; ok&
End If

Function ZTrim$ (s$)
    Dim p As Long
    p = InStr(s$, Chr$(0))
    If p > 0 Then
        ZTrim$ = Left$(s$, p - 1)
    Else
        ZTrim$ = s$
    End If
End Function


Attached Files Thumbnail(s)
   

.h   IbLNX.h (Size: 4.82 KB / Downloads: 9)


Reply
#8
(12-15-2025, 04:32 PM)Petr Wrote: Thank you for the explanation regarding Linux conventions.

Could you explain why you chose .h over .so? that the code simply doesn't run  on my machine. SO works correctly.

Of course, there are always multiple ways to approach this.

Did you make sure to add the flags to the compiler in QB64? If you don't, compilation will fail.
The noticing will continue
Reply
#9
No. Well, do you really expect users to feed the compiler extra flags every time they want to use this utility? That's highly impractical.


Reply
#10
It's a library. With requirements. Once it is embedded in a program, that's it. You wouldn't have to make sure you are deploying your program with another file. You just have a comment in the library or a readme file telling them how to use it. That's it. "Add these flags to QB64 to build with this header". If they have a problem with that, then they're incredibly lazy. Besides, they'd still need the packages installed to use it anyways, regardless if it is a header or SO.
The noticing will continue
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Dynamic Libraries (Windows) Petr 50 3,270 02-24-2026, 06:38 PM
Last Post: Petr

Forum Jump:


Users browsing this thread: 1 Guest(s)