Notes From The Dork Web

Cross-platform development with Haiku OS and BaCon

I've been trying out Haiku OS lately, and so far I've really enjoyed it, way more than I expected. For what some would call a fringe OS it's surprisingly functional. Unlike it's spiritual predecessor, Haiku has a full POSIX layer, and a functioning port of QT, which means many KDE apps are available on the platform. I've been using nightly builds, so my experience apps have been quite crashy but I can live with that. I don't know if it's there for Daily Driver status, but I'm interested in learning more.

I discovered the Inside Haiku Apps YouTube channel last night, which is a fantastic series on how to Haiku. One of the channel's videos introduced an interesting BASIC dialect called BaCon.

I'd especially like to thank Michel Clasquin-Johnson for his work on porting BaCon and his videos. I'd also like to thank Solène Rapenne for giving me the nudge to try to make something useful when running Haiku. It was the kick up the arse I needed :)

Enter BaCon

Instead of being a BASIC interpreter, BaCon is a BASIC Converter. It translates it's own dialect of BASIC into C, which is then compiled locally. The results are native binaries, but with a cross-platform abstraction. Haiku support is especially interesting if I can use BaCon on other platforms such as Linux and OpenBSD.

Notes From The Dork Web

Installing BaCon consists of downloading BaCon for Haiku and optional sample code haiku packages, and installing them.

Because BaCon translates into C, it's able to invoke C functions and libraries. This makes the dialect a little strange compared to other BASIC languages I've used, but I'm also sure a lot of it is just me being unfamiliar with BaCon. I've pasted my code for the command line version, adapted from the curl3.bac example in the demo programs package.

'
' Connect to ipify.org API and find public IPv4 address
'

PRAGMA INCLUDE <curl/curl.h>
PRAGMA LDFLAGS `pkg-config --libs libcurl`

PROTO curl_version curl_global_init curl_easy_init curl_easy_setopt curl_easy_perform curl_easy_strerror curl_easy_cleanup curl_global_cleanup

DECLARE curl TYPE CURL*
DECLARE result TYPE CURLcode

RECORD Result 
    LOCAL content$
    LOCAL size
ENDRECORD

FUNCTION CurlCallback(void *contents, size_t size, size_t nmemb, void *user_data)

    LOCAL realsize
    LOCAL page TYPE Result_type*

    realsize = size * nmemb

    page = user_data

    page->content$ = page->content$ & contents
    INCR page->size, realsize

    RETURN realsize

ENDFUNCTION

Result.size = 0

' Initialize CURL
curl_global_init(CURL_GLOBAL_ALL)

' Create the handle
curl = curl_easy_init()

IF curl THEN

    ' Provide the URL to use in the request
    curl_easy_setopt(curl, CURLOPT_URL, "https://api.ipify.org/")

    ' Send all data to this function
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlCallback)

    ' We pass our RECORD to the callback function
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Result);

    ' Some servers don't like requests that are made without a user-agent field, so we provide one
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0")

    ' Tell to follow redirects
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, TRUE)

    ' Perform the GET
    result = curl_easy_perform(curl)

    ' Check result
    IF result <> CURLE_OK THEN PRINT curl_easy_strerror(result) FORMAT "curl_easy_perform() failed: %s\n"

    ' Print results
    Ip_addr = Result.content$
    PRINT Ip_addr FORMAT "IP: %s\n"

    ' Always cleanup
    curl_easy_cleanup(curl)
    curl_global_cleanup()
ENDIF

The code above uses libcurl to connect to the ipify API. There's a lot of initialization, then setting curl options before making the request. To compile the result, I entered bacon getip.bac in a terminal. BaCon used flex and linked against libcurl. The result was a 39kb binary that when run printed out "IP: " and my public IP address.

Getting GUI

I wanted to see if this was suitable for cross-platform GUI tool programming. On most systems I tend to use GTK for GUI work instead of QT. This is in part because of QT's historical licensing complications, and also because most distributions will then pull in a full KDE environment's worth of dependencies.

BaCon will work with anything. Indeed, Michel wrote a nice GUI tool using BaCon to call out to hdialog - a Haiku-specific port of Dialog. I chose GTK because there was sample code available, and GTK is available for Haiku.

In HaikuDepot I ticked Show -> Develop Packages from the menu and made sure both gtk3 and gtk3_devel packages were installed. After looking at some of the GTK examples I think I'd learned enough to be dangerous, and came up with this monstrosity:

'
' Connect to ipify.org API and find public IPv4 address
'

OPTION GUI TRUE
PRAGMA GUI gtk3
PRAGMA INCLUDE <curl/curl.h>
PRAGMA LDFLAGS `pkg-config --libs libcurl`

PROTO curl_version curl_global_init curl_easy_init curl_easy_setopt curl_easy_perform curl_easy_strerror curl_easy_cleanup curl_global_cleanup inet_ntop

DECLARE curl TYPE CURL*
DECLARE result TYPE CURLcode
DECLARE ipaddr TYPE STRING 

RECORD Result 
    LOCAL content$
    LOCAL size
ENDRECORD

gui = GUIDEFINE(" \
        { type=WINDOW name=window callback=delete-event title=\"GetIP\" width-request=300 } \
        { type=BOX name=box parent=window orientation=GTK_ORIENTATION_VERTICAL } \
        { type=LABEL name=label parent=box height-request=50 label=\"Click the button!\" } \
        { type=BUTTON name=button parent=box callback=clicked label=\"What's my IP?\" }")

FUNCTION CurlCallback(void *contents, size_t size, size_t nmemb, void *user_data)

    LOCAL realsize
    LOCAL page TYPE Result_type*

    realsize = size * nmemb

    page = user_data

    page->content$ = page->content$ & contents
    INCR page->size, realsize

    RETURN realsize

ENDFUNCTION

Result.size = 0

' Initialize CURL
curl_global_init(CURL_GLOBAL_ALL)

' Create the handle
curl = curl_easy_init()

IF curl THEN

    ' GUI Loop
    WHILE TRUE
        SELECT GUIEVENT$(gui)
            CASE "window"
                BREAK
            CASE "button"
                ' Provide the URL to use in the request
                curl_easy_setopt(curl, CURLOPT_URL, "https://api.ipify.org/")
                ' Send all data to this function
                curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlCallback)
                ' We pass our RECORD to the callback function
                curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Result);
                ' Some servers don't like requests that are made without a user-agent field, so we provide one
                curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0")
                ' Tell to follow redirects
                curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, TRUE)
                ' Perform the GET
                result = curl_easy_perform(curl)
                ' Check result
                IF result <> CURLE_OK THEN PRINT curl_easy_strerror(result) FORMAT "curl_easy_perform() failed: %s\n"
                ' Parse results
    	ipaddr = Result.content$
    	'PRINT Result.content$ 
                ' Always cleanup
                curl_easy_cleanup(curl)
                curl_global_cleanup()
                CALL GUISET(gui, "label", "label", "Public IP: " &ipaddr )
                Result.content$ = ""
    	Result.size = 0
                curl = curl_easy_init()
        ENDSELECT
    WEND

ENDIF

Compiling this was a simple matter of entering bacon gui_getip.bac and everything compiled fine. Of course there was a long intermediary stage of getting anything to compile while I beat this into minimal functional shape.

Notes From The Dork Web

The resulting app works, is native, and 45kb in size. The next step for me on Haiku is to learn how to package the binaries into a .hpkg file. Outside of Haiku, the next step is to try this code on Linux OpenBSD and see what, if any changes are needed for it to run.

There is a GUI tool called HUG for BaCon that appears to be written in BaCon. I've only been able to find it posted on the BaCon forums which need user registration to download. If it's behind a registration-wall I'm less likely to use it, as others won't necessarily be able to compile the code should I distribute anything. Although QT looks better, I think I'll stick to GTK for now, although I might try FLTK.

#bacon #haiku #openbsd #programming