Extension Modules > GTK-Server
IDLL
Support:
GTK-Server is a flexible, easy to use, scriptable interface to dynamic link libraries via a DynaCall or FFI link. Peter van Eerten (author) has created an extensive library of Gtk based library definitions, macros and a function wrapper library he calls HUG. You can interface with GTK-Server with just about any scripting language, interpreter or compiler using a DLL, TCP/UDP, FIFO or named pipes connection. The following examples demonstrate dynamic scripting of libraries and low level resource with GTK-Server and providing real time API syntax checking against it's definitions. Feel free to use any of the following API definition as a start in the language of your choice.
[*]GTK-Server
[*]GTK-Server Manual
[*]GTK-Server Tutorial
[*]GTK-Server Configuration
[*]gtk-server.cfg
[*]GTK-Server.org
[/list]
I have registered the IDLL.INFO domain as a repository for interpreted dynamic link library definitions that could be used by any language using the GTK-Server interface.
--- Code: Text ---DECLARE SUB DLL ALIAS "_gtk" LIB "gtk-server" DLL("gtk_server_require libcurses.so") DLL("gtk_server_define initscr NONE LONG 0")DLL("gtk_server_define move NONE INT 2 INT INT")DLL("gtk_server_define addstr NONE INT 1 STRING")DLL("gtk_server_define start_color NONE INT 0")DLL("gtk_server_define init_pair NONE INT 3 INT INT INT")DLL("gtk_server_define color_set NONE INT 2 INT NULL")DLL("gtk_server_define getch NONE INT 0")DLL("gtk_server_define endwin NONE INT 0") DLL("initscr")DLL("move 0 25")DLL("addstr \"ScriptBasic Console Color Test\"")DLL("start_color")display_line = 1FOR color = 0 to 255 DLL("init_pair " & color & " " & 256 - color & " " & color) DLL("color_set " & color) IF color % 16 = 0 THEN display_line += 1 DLL("move " & display_line & " 3") END IF DLL("addstr \"" & FORMAT("%~ 000 ~", color) & "\"")NEXTDLL("move 22 0")DLL("getch")DLL("endwin")
--- Code: Text ---' SQLite3 Demo Script DECLARE SUB DLL ALIAS "_gtk" LIB "gtk-server"DECLARE SUB VARPTR ALIAS "varptr" LIB "gtk-server" DLL("gtk_server_require libsqlite3.so") DLL("gtk_server_define sqlite3_open NONE INT 2 STRING LONG")DLL("gtk_server_define sqlite3_exec NONE INT 5 LONG STRING INT NULL PTR_STRING")DLL("gtk_server_define sqlite3_prepare_v2 NONE INT 5 LONG STRING INT PTR_LONG NULL")DLL("gtk_server_define sqlite3_step NONE INT 1 LONG")DLL("gtk_server_define sqlite3_column_text NONE STRING 2 LONG INT")DLL("gtk_server_define sqlite3_close NONE INT 1 LONG") CONST SQLITE_ROW = 100db = 0dberr = 0stmt = 0 DLL("sqlite3_open \"testsql\" " & VARPTR(db))DLL("sqlite3_exec " & db & " \"CREATE TABLE demo(someval INTEGER, sometxt TEXT);\" 0 0 " & VARPTR(dberr))DLL("sqlite3_exec " & db & " \"INSERT INTO demo VALUES (123, 'Hello');\" 0 0 " & VARPTR(dberr))DLL("sqlite3_exec " & db & " \"INSERT INTO demo VALUES (234, 'cruel');\" 0 0 " & VARPTR(dberr))DLL("sqlite3_exec " & db & " \"INSERT INTO demo VALUES (345, 'world');\" 0 0 " & VARPTR(dberr))result = DLL("sqlite3_prepare_v2 " & db & " \"SELECT * FROM demo;\" -1 " & VARPTR(stmt) & " 0")SPLIT result BY " " TO ok, stmt WHILE DLL("sqlite3_step " & stmt) = SQLITE_ROW PRINT DLL("sqlite3_column_text " & stmt & " " & 0) & " - " & DLL("sqlite3_column_text " & stmt & " " & 1),"\n"WEND DLL("sqlite3_close " & db)
Results:
jrs@Laptop:~/SB/test$ scriba sqlite3.sb
123 - Hello
234 - cruel
345 - world
jrs@Laptop:~/SB/test$
Support:
Here is a more challenging use of API scripting. This example shows the new VARPTR (language level addition) and STRPTR. (implemented at the script level with IDLL calls to low level API functions) For those that like the more traditional function argument format, I added a _DLL function wrapper in the included .sbh file to allow this. (see second gda.sb example)
Here is the C code example that this code challenge was based on. (GNOME-DB project site)
--- Code: Text ---' GNOME-DB GRID ' INTERFACEDECLARE SUB DLL ALIAS "_gtk" LIB "gtk-server"DECLARE SUB VARPTR ALIAS "varptr" LIB "gtk-server" ' GDA4DLL("gtk_server_require libgda-4.0.so")DLL("gtk_server_require libgda-ui-4.0.so") ' DECLAREDLL("gtk_server_define gtk_init NONE NONE 2 NULL NULL")DLL("gtk_server_define gtk_dialog_new_with_buttons NONE WIDGET 8 STRING WIDGET INT STRING INT STRING INT NULL")DLL("gtk_server_define gdaui_login_new NONE WIDGET 1 STRING")DLL("gtk_server_define gtk_box_pack_start NONE NONE 5 WIDGET WIDGET BOOL BOOL INT")DLL("gtk_server_define gtk_dialog_get_content_area NONE WIDGET 1 WIDGET")DLL("gtk_server_define gtk_widget_show NONE NONE 1 WIDGET")DLL("gtk_server_define gtk_dialog_run delete-event INT 1 WIDGET")DLL("gtk_server_define gdaui_login_get_connection_information NONE WIDGET 1 WIDGET")' Use the following format definition if you are passing actual strings not pointers to them.' DLL("gtk_server_define gda_connection_open_from_string NONE WIDGET 5 STRING STRING POINTER INT NULL")DLL("gtk_server_define gda_connection_open_from_string NONE WIDGET 5 POINTER POINTER POINTER INT NULL")DLL("gtk_server_define gtk_widget_destroy NONE NONE 1 WIDGET")DLL("gtk_server_define gda_execute_select_command NONE WIDGET 3 WIDGET STRING NULL")DLL("gtk_server_define g_object_unref NONE NONE 1 WIDGET")DLL("gtk_server_define gtk_window_new delete-event WIDGET 1 INT")DLL("gtk_server_define gdaui_grid_new NONE WIDGET 1 WIDGET")DLL("gtk_server_define gtk_container_add NONE NONE 2 WIDGET WIDGET")DLL("gtk_server_define gtk_widget_show_all NONE NONE 1 WIDGET")DLL("gtk_server_define gtk_server_callback NONE STRING 1 STRING")DLL("gtk_server_define gtk_window_set_default_size NONE NONE 3 WIDGET INT INT")DLL("gtk_server_define gtk_window_set_title NONE NONE 2 WIDGET STRING")DLL("gtk_server_define strncpy NONE STRING 3 POINTER POINTER INT")DLL("gtk_server_define strdup NONE STRING 1 POINTER") CONST GTK_STOCK_CANCEL = "gtk-cancel"CONST GTK_STOCK_OK = "gtk-ok"CONST GTK_RESPONSE_NONE = -1CONST GTK_RESPONSE_OK = -5CONST GDA_CONNECTION_OPTIONS_NONE = 0CONST GTK_WINDOW_TOPLEVEL = 0CONST GTK_DIALOG_MODAL = 1CONST provider = 4CONST cnc_string = 12 FUNCTION _DLL(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9)LOCAL arg_str arg_str = STR(a0) & " " & STR(a1) & " " & STR(a2) & " " & STR(a3) & " " & STR(a4) & " " & STR(a5) & " " & STR(a6) & " " & STR(a7) & " " & STR(a8) & " " & STR(a9) _DLL = DLL(arg_str)END FUNCTION FUNCTION STRPTR(pointer)LOCAL info_str info_str = 0DLL("strncpy " & VARPTR(info_str) & " " & pointer & " 4")' Enabling the next line of code will return the actual string in info_str and not just the pointer to it.' info_str = DLL("strdup " & info_str)STRPTR = info_str END FUNCTION SUB gtk_mainLOCAL event REPEAT event = DLL("gtk_server_callback wait")UNTIL event = window END SUB
--- Code: Text ---' GNOME-DB GRID INCLUDE "db-grid.sbh" DLL("gtk_init 0 0")DLL("gdaui_init") ' DB Control Windowwindow = DLL("gtk_dialog_new_with_buttons \"Select the Data Source to connect to\" 0 " & GTK_DIALOG_MODAL & " " & GTK_STOCK_CANCEL & " " & GTK_RESPONSE_NONE & " " & GTK_STOCK_OK & " " & GTK_RESPONSE_OK & " 0")login = DLL("gdaui_login_new NULL")DLL("gtk_box_pack_start " & DLL("gtk_dialog_get_content_area " & window) & " " & login & " TRUE TRUE 0")DLL("gtk_widget_show " & login)IF DLL("gtk_dialog_run " & window) <> GTK_RESPONSE_OK THEN PRINT "Cancelled!\n" ENDEND IFdsninfo = DLL("gdaui_login_get_connection_information " & login)cnc = DLL("gda_connection_open_from_string " & STRPTR(dsninfo + provider) & " " & STRPTR(dsninfo + cnc_string) & " " & " 0 0 0")data_model = DLL("gda_execute_select_command " & cnc & " \"SELECT * FROM customers\" 0")DLL("gtk_widget_destroy " & window) ' DB Data Gridwindow = DLL("gtk_window_new 0")DLL("gtk_window_set_title " & window & " \"GNOME-DB Grid Control Example\"")DLL("gtk_window_set_default_size " & window & " 400 200")grid = DLL("gdaui_grid_new " & data_model)DLL("g_object_unref " & data_model)DLL("gtk_container_add " & window & " " & grid)DLL("gtk_widget_show_all " & window) gtk_main() DLL("g_object_unref " & cnc)
--- Code: Text ---' GNOME-DB GRID INCLUDE "db-grid.sbh" _DLL("gtk_init", 0, 0)_DLL("gdaui_init") ' DB Control Windowwindow = _DLL("gtk_dialog_new_with_buttons","\"Select the Data Source to connect to\"", 0, GTK_DIALOG_MODAL, GTK_STOCK_CANCEL, GTK_RESPONSE_NONE, GTK_STOCK_OK, GTK_RESPONSE_OK, 0)login = _DLL("gdaui_login_new", "NULL")_DLL("gtk_box_pack_start", _DLL("gtk_dialog_get_content_area", window), login, TRUE, TRUE, 0)_DLL("gtk_widget_show", login)IF _DLL("gtk_dialog_run", window) <> GTK_RESPONSE_OK THEN PRINT "Cancelled!\n" ENDEND IFdsninfo = _DLL("gdaui_login_get_connection_information", login)cnc = _DLL("gda_connection_open_from_string", STRPTR(dsninfo + provider), STRPTR(dsninfo + cnc_string), 0, 0, 0)data_model = _DLL("gda_execute_select_command", cnc, "\"SELECT * FROM customers\"", 0)_DLL("gtk_widget_destroy", window) ' DB Data Gridwindow = _DLL("gtk_window_new", 0)_DLL("gtk_window_set_title", window, "\"GNOME-DB Grid Control Example\"")_DLL("gtk_window_set_default_size", window, 400, 200)grid = _DLL("gdaui_grid_new", data_model)_DLL("g_object_unref", data_model)_DLL("gtk_container_add", window, grid)_DLL("gtk_widget_show_all", window) gtk_main() _DLL("g_object_unref", cnc)
jrs@Laptop:~/SB/test$ scriba gda.sb
Trying to load plugins in /usr/local/lib/libgda-4.0/plugins...
Loading file /usr/local/lib/libgda-4.0/plugins/libgda-ui-plugins.so...
- loaded filesel (File selection entry): Entry
- loaded cird (Entry to hold an IPv4 network specification): Entry
- loaded text (Multiline text entry): Entry
- loaded picture (Picture entry): Entry Cell
- loaded picture_as_string (Picture entry for data stored as a string): Entry Cell
jrs@Laptop:~/SB/test$
The GDA-Browser is a common admin/SQL browser for multiple providers. (SQLite3, PostgreSQL and MySQL)
The GdaBrowser program is a graphical tool to get a quick access to a database's structure and contents. This is a work in progress and more features will be added with time.
This tool aims at being simple to use, visually appealing, and full of features usefull to the database administrator or to discover a new database.
The main features are:
[*]it is possible to open any number of connections withing a running instance
[*]can access any database Libgda supports
[*]is fully multi-threaded to the UI never locks on an operation
[*]is organized around the notion of perspective which correspond to activities, and is easy to extend adding new perspectives
[*]fullsreen support
[*]integrated help
[/list]
Support:
For those of you interested in the GNOME-DB solution and would like to use the interface in a console mode setting, gda-sql-4.0 is an easy to use tool to access multiple database providers (multiple connections in one session supported) from a common interface.
--- Code: ---jrs@Laptop:~/gtk-server$ gda-sql-4.0 -L
Installed providers list
Provider │ Description
───────────┼──────────────────────────────────
SQLite │ Provider for SQLite databases
PostgreSQL │ Provider for PostgreSQL databases
SQLCipher │ Provider for SQLCipher
MySQL │ Provider for MySQL databases
(4 rows)
jrs@Laptop:~/gtk-server$
jrs@Laptop:~/gtk-server$ gda-sql-4.0 -l
List of defined data sources
DSN │ Provider │ Description │ Connection string │ Username │ Global
──────────┼────────────┼──────────────────────────────────────┼────────────────────────────────────────────────────────────┼──────────┼───────
Planner │ PostgreSQL │ GNOME Planner DB │ DB_NAME=plannerdb │ jrs │ FALSE
SalesTest │ SQLite │ Test database for a sales department │ DB_DIR=/home/jrs/.local/share/libgda;DB_NAME=sales_test.db │ │ FALSE
SBMySQL │ MySQL │ SB testing DB │ DB_NAME=sbtest │ root │ FALSE
(3 rows)
jrs@Laptop:~/gtk-server$ gda-sql-4.0 --help
Usage:
gda-sql-4.0 [OPTION...] [DSN|connection string]...
Help Options:
-h, --help Show help options
Application Options:
-v, --version Show version information and exit
-o, --output-file=output file Output file
-C, --command=command Run only single command (SQL or internal) and exit
-f, --commands-file=filename Execute commands from file, then exit (except if -i specified)
-i, --interactive Keep the console opened after executing a file (-f option)
-l, --list-dsn List configured data sources and exit
-L, --list-providers List installed database providers and exit
jrs@Laptop:~/gtk-server$ gda-sql-4.0 SalesTest
Welcome to the GDA SQL console, version 4.2.0
Type: .copyright to show usage and distribution terms
.? for help with internal commands
.q (or CTRL-D) to quit
(the '.' can be replaced by a '\')
or any query terminated by a semicolon
Opening connection 'SalesTest' for: SalesTest
All the meta data associated to the 'SalesTest' connection will be stored in the '/home/jrs/.local/share/libgda/gda-sql-SalesTest.db' file
SalesTest> select * from customers;
id │ name │ default_served_by │ country │ city
───┼─────────────────┼───────────────────┼─────────┼─────
2 │ Ed Lamton │ 4 │ SP │ MDR
3 │ Lew Bonito │ 1 │ FR │ TLS
4 │ Mark Lawrencep │ │ SP │ MDR
9 │ Greg Popoff │ 2 │ SP │ MDR
10 │ Vladimir Zirkov │ 4 │ │
(5 rows)
SalesTest>
--- End code ---
Here is a standalone, non-GUI (console mode) example using GDA.
--- Code: ---' GNOME-DB console mode example
DECLARE SUB DLL ALIAS "_gtk" LIB "gtk-server"
DLL("gtk_server_require libgda-4.0.so")
DLL("gtk_server_define gda_init NONE NONE 0")
DLL("gtk_server_define gda_connection_open_from_string NONE WIDGET 4 STRING STRING NULL INT")
DLL("gtk_server_define gda_execute_select_command NONE WIDGET 2 WIDGET STRING")
DLL("gtk_server_define gda_data_model_dump NONE NONE 2 WIDGET NULL")
DLL("gtk_server_define g_object_unref NONE NONE 1 WIDGET")
DLL("gda_init")
cnc = DLL("gda_connection_open_from_string \"SQLite\" \"DB_DIR=/home/jrs/.local/share/libgda;DB_NAME=sales_test.db\" NULL 0")
data_model = DLL("gda_execute_select_command " & cnc & " \"SELECT * FROM customers\"")
DLL("gda_data_model_dump " & data_model & " NULL")
DLL("g_object_unref " & data_model)
DLL("g_object_unref " & cnc)
--- End code ---
Results
--- Code: ---jrs@Laptop:~/SB/test$ scriba db-con.sb
id │ name │ default_served_by │ country │ city
───┼─────────────────┼───────────────────┼─────────┼─────
2 │ Ed Lamton │ 4 │ SP │ MDR
3 │ Lew Bonito │ 1 │ FR │ TLS
4 │ Mark Lawrencep │ NULL │ SP │ MDR
9 │ Greg Popoff │ 2 │ SP │ MDR
10 │ Vladimir Zirkov │ 4 │ NULL │ NULL
(5 rows)
jrs@Laptop:~/SB/test$
--- End code ---
GDA supports dumping to a XML file as an option as well as a comma delimited file. You can always access the table by row/column if you need that granularity.
--- Code: ---' GNOME-DB console mode example
DECLARE SUB DLL ALIAS "_gtk" LIB "gtk-server"
DLL("gtk_server_require libgda-4.0.so")
DLL("gtk_server_define gda_init NONE NONE 0")
DLL("gtk_server_define gda_connection_open_from_string NONE WIDGET 4 STRING STRING NULL INT")
DLL("gtk_server_define gda_execute_select_command NONE WIDGET 2 WIDGET STRING")
DLL("gtk_server_define gda_data_model_export_to_file NONE BOOL 9 WIDGET INT STRING NULL INT NULL INT NULL NULL")
DLL("gtk_server_define g_object_unref NONE NONE 1 WIDGET")
DLL("gda_init")
cnc = DLL("gda_connection_open_from_string \"SQLite\" \"DB_DIR=/home/jrs/.local/share/libgda;DB_NAME=sales_test.db\" NULL 0")
data_model = DLL("gda_execute_select_command " & cnc & " \"SELECT * FROM customers\"")
DLL("gda_data_model_export_to_file " & data_model & " 0 \"SalesTest.xml\" NULL 0 NULL 0 NULL NULL")
DLL("g_object_unref " & data_model)
DLL("g_object_unref " & cnc)
--- End code ---
Results
--- Code: ---jrs@Laptop:~/SB/test$ ls -l SalesTest.xml
-rw-r--r-- 1 jrs jrs 1712 2010-12-15 19:06 SalesTest.xml
jrs@Laptop:~/SB/test$ cat SalesTest.xml
<?xml version="1.0"?>
<gda_array id="EXPORT" name="Exported Data">
<gda_array_field id="FI0" name="id" title="id" dbms_type="integer" gdatype="int" nullok="TRUE"/>
<gda_array_field id="FI1" name="name" title="name" dbms_type="string" gdatype="string" nullok="TRUE"/>
<gda_array_field id="FI2" name="default_served_by" title="default_served_by" dbms_type="integer" gdatype="int" nullok="TRUE"/>
<gda_array_field id="FI3" name="country" title="country" dbms_type="string" gdatype="string" nullok="TRUE"/>
<gda_array_field id="FI4" name="city" title="city" dbms_type="string" gdatype="string" nullok="TRUE"/>
<gda_array_data>
<gda_array_row>
<gda_value>2</gda_value>
<gda_value>Ed Lamton</gda_value>
<gda_value>4</gda_value>
<gda_value>SP</gda_value>
<gda_value>MDR</gda_value>
</gda_array_row>
<gda_array_row>
<gda_value>3</gda_value>
<gda_value>Lew Bonito</gda_value>
<gda_value>1</gda_value>
<gda_value>FR</gda_value>
<gda_value>TLS</gda_value>
</gda_array_row>
<gda_array_row>
<gda_value>4</gda_value>
<gda_value>Mark Lawrencep</gda_value>
<gda_value isnull="t"/>
<gda_value>SP</gda_value>
<gda_value>MDR</gda_value>
</gda_array_row>
<gda_array_row>
<gda_value>9</gda_value>
<gda_value>Greg Popoff</gda_value>
<gda_value>2</gda_value>
<gda_value>SP</gda_value>
<gda_value>MDR</gda_value>
</gda_array_row>
<gda_array_row>
<gda_value>10</gda_value>
<gda_value>Vladimir Zirkov</gda_value>
<gda_value>4</gda_value>
<gda_value isnull="t"/>
<gda_value isnull="t"/>
</gda_array_row>
</gda_array_data>
</gda_array>
jrs@Laptop:~/SB/test$
--- End code ---
Support:
This is an ongoing project as GTK-Server definitions are created and tested under ScriptBasic.
--- Code: ---' ScriptBasic API helper functions and declarations
' INTERFACE
DECLARE SUB DLL ALIAS "_gtk" LIB "gtk-server"
DECLARE SUB VARPTR ALIAS "varptr" LIB "gtk-server"
' Convert standard function calls to a space delimited command string
FUNCTION _DLL(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9)
LOCAL arg_str
arg_str = STR(a0) & " " & STR(a1) & " " & STR(a2) & " " & STR(a3) & " " & STR(a4) & " " & STR(a5) & " " & STR(a6) & " " & STR(a7) & " " & STR(a8) & " " & STR(a9)
_DLL = DLL(arg_str)
END FUNCTION
' Returns a data pointer address of a variable or element in a structure as a text based numeric value that GTK-Server understands.
FUNCTION STRPTR(mem_addr)
LOCAL tmp_ptr
tmp_ptr = 0
DLL("strncpy " & VARPTR(tmp_ptr) & " " & mem_addr & " 4")
STRPTR = tmp_ptr
END FUNCTION
' Returns a ScriptBasic string variable given the memory address in numeric text format of a null terminated string variable or element of a structure.
FUNCTION GETSTR(mem_addr)
LOCAL tmp_ptr
tmp_ptr = 0
DLL("strncpy " & VARPTR(tmp_ptr) & " " & mem_addr & " 4")
tmp_ptr = DLL("strdup " & tmp_ptr)
GETSTR = tmp_ptr
END FUNCTION
' Returns a string of data given the memory address of a data pointer and the length in bytes requested.
FUNCTION GETMEM(mem_addr, bytes)
LOCAL tmp_ptr, tmp_data
tmp_ptr = 0
DLL("strncpy " & VARPTR(tmp_ptr) & " " & mem_addr & " 4")
tmp_data = 0
tmp_data = DLL("strncpy " & VARPTR(tmp_data) & " " & tmp_ptr & " " & bytes)
GETMEM = tmp_data
END FUNCTION
' Convert a string to a hex representation of it.
FUNCTION Str2Hex(str_arg)
LOCAL str_len, pos, hex_str
str_len = LEN(str_arg)
IF str_len THEN
FOR pos = 1 TO str_len
hex_str &= RIGHT("0" & HEX(ASC(MID(str_arg, pos, 1))),2)
NEXT
ELSE
hex_str = ""
END IF
Str2Hex = hex_str
END FUNCTION
--- End code ---
Support:
Here is an example of ScriptBasic being embedded in itself.
--- Code: ---DECLARE SUB DLL ALIAS "_gtk" LIB "gtk-server"
DLL("gtk_server_require libiDLL.so")
DLL("gtk_server_define sb_new NONE POINTER 0")
DLL("gtk_server_define scriba_LoadConfiguration NONE INT 2 POINTER STRING")
DLL("gtk_server_define scriba_SetFileName NONE INT 2 POINTER STRING")
DLL("gtk_server_define scriba_Run NONE INT 2 POINTER STRING")
DLL("gtk_server_define scriba_LoadSourceProgram NONE INT 1 POINTER")
DLL("gtk_server_define scriba_destroy NONE NONE 1 POINTER")
pProgram = DLL("sb_new")
DLL("scriba_LoadConfiguration " & pProgram & " \"/etc/scriba/basic.conf\"")
DLL("scriba_SetFileName " & pProgram & " \"E01.bas\"")
DLL("scriba_LoadSourceProgram " & pProgram)
DLL("scriba_Run " & pProgram & " \"JRS\"")
DLL("scriba_destroy " & pProgram)
--- End code ---
--- Code: Text ---cmd = COMMAND() PRINT "ARG = ",cmd,"\n" FOR x = 1 TO 10 PRINT x,"\n"NEXT
rs@Laptop:~/SB/test$ scriba embed.sb
ARG = JRS
1
2
3
4
5
6
7
8
9
10
jrs@Laptop:~/SB/test$
Navigation
[0] Message Index
[#] Next page
Go to full version