' *****************************************************
' PROGRAM:  PupTray
' PURPOSE:  Quick launcher with tray icon.
'           
' AUTHOR:   mekanixx, (Puppy Linux forum)
' MODS:     vovchik, PvE
' DEPENDS:  gcc, bacon, bash
' PLATFORM: Puppy Linux (actually, any *nix)
' DATE:         23-10-10
' LICENSE:  (c) Mekanixx Software (Doyle Whisenant)
'                   GPL (http://gplv3.fsf.org/)
' NOTES:        
'                   Programmed with BaCON 1.0 build 18 
' *****************************************************

' *********************
' DECLARATIONS
' *********************

'PvE: do not forget to set the OPTION if you use memory streams!
OPTION MEMSTREAM TRUE

SETENVIRON "LANG", "C"

TRAP LOCAL

RECORD Menu_Icon[50]
  LOCAL sPath$ TYPE STRING
  LOCAL sDescription$ TYPE STRING
  LOCAL iMenuID TYPE int
END RECORD

CONST   MaxCnt = 50
GLOBAL iCurrentMenu TYPE int
GLOBAL iMaxMenus TYPE int
GLOBAL iEntries TYPE int
GLOBAL iMaxEntries TYPE int
GLOBAL mudatafile$
GLOBAL muidx, mucnt TYPE int
GLOBAL puptray_pid TYPE int
GLOBAL old_puptray_pid TYPE int

GLOBAL NUL TYPE STRING
CONST GDK_INTERP_NEAREST = 0
CONST GDK_INTERP_BILINEAR = 2

IMPORT "gtk_status_icon_set_tooltip(long,char*)" FROM "libgtk-x11-2.0.so" TYPE void
IMPORT "gtk_status_icon_set_visible(long,int)" FROM "libgtk-x11-2.0.so" TYPE void
IMPORT "gtk_status_icon_position_menu(long,int*,int*,int*,long)" FROM "libgtk-x11-2.0.so" TYPE void
IMPORT "g_signal_connect_data(long,char*,void*,long,long,int)" FROM "libgobject-2.0.so" TYPE void
IMPORT "gtk_main" FROM "libgtk-x11-2.0.so" TYPE void
IMPORT "gtk_init(int*,void*)" FROM "libgtk-x11-2.0.so" TYPE void
IMPORT "gtk_menu_new" FROM "libgtk-x11-2.0.so" TYPE long
IMPORT "gtk_menu_popup(long,long,long,void*,long,int,int)" FROM "libgtk-x11-2.0.so" TYPE void
IMPORT "gtk_menu_item_new_with_label(char*)" FROM "libgtk-x11-2.0.so" TYPE long
IMPORT "gtk_widget_show_all(long)" FROM "libgtk-x11-2.0.so" TYPE void
IMPORT "gtk_main_quit" FROM "libgtk-x11-2.0.so" TYPE void
IMPORT "gtk_status_icon_set_blinking(long,int)" FROM "libgtk-x11-2.0.so" TYPE void
IMPORT "gtk_menu_shell_append(long,long)" FROM "libgtk-x11-2.0.so" TYPE void
IMPORT "gtk_status_icon_new_from_stock(char*)" FROM "libgtk-x11-2.0.so" TYPE long
IMPORT "gtk_separator_menu_item_new" FROM "libgtk-x11-2.0.so" TYPE long
IMPORT "gtk_status_icon_new_from_file(char*)" FROM "libgtk-x11-2.0.so" TYPE long
IMPORT "gtk_widget_destroy(long)" FROM "libgtk-x11-2.0.so" TYPE void
IMPORT "gtk_dialog_run(long)" FROM "libgtk-x11-2.0.so" TYPE int
IMPORT "gtk_window_set_title(long,char*)" FROM "libgtk-x11-2.0.so" TYPE void
IMPORT "gtk_message_dialog_new(long,int,int,int,char*,...)" FROM "libgtk-x11-2.0.so" TYPE long
IMPORT "gtk_window_set_position(long,int)" FROM "libgtk-x11-2.0.so" TYPE void
IMPORT "gtk_image_set_from_file(long,char*)" FROM "libgtk-x11-2.0.so" TYPE void
IMPORT "gtk_image_new_from_file(char*)" FROM "libgtk-x11-2.0.so" TYPE long
IMPORT "gtk_container_add(long,long)" FROM "libgtk-x11-2.0.so" TYPE void
IMPORT "gtk_image_menu_item_new_with_label(char*)" FROM "libgtk-x11-2.0.so" TYPE long
IMPORT "gtk_image_new_from_stock(char*,int)" FROM "libgtk-x11-2.0.so" TYPE long
IMPORT "gtk_image_menu_item_set_image(long,long)" FROM "libgtk-x11-2.0.so" TYPE void
IMPORT "gtk_image_new_from_icon_name(char*,int)" FROM "libgtk-x11-2.0.so" TYPE long

'PvE: additional calls for loading and scaling icons
IMPORT "g_object_set(long,char*,...)" FROM "libgobject-2.0.so" TYPE void
IMPORT "gdk_pixbuf_new_from_file(char*,void*)" FROM "libgdk_pixbuf-2.0.so.0" TYPE long
IMPORT "gdk_pixbuf_scale_simple(long,int,int,int)" FROM "libgdk_pixbuf-2.0.so.0" TYPE long
IMPORT "gtk_image_new_from_pixbuf(long)" FROM "libgtk-x11-2.0.so" TYPE long

DECLARE my_menu, status_icon
DEF FN MSGBOX_OK(t,m) = MsgBox(t,m,0,1)

' *********************
' END DECLARATIONS
' *********************

' *********************
' SUBROUTINES
' *********************

'--------------
SUB MY_STATUS_MESSAGE(STRING my_message_text$)
'--------------
    LOCAL my_preamble$
    my_preamble$ = " -bg red -fg white  -title 'Pup-Tray: Status ' -center "
    ' the next line should probably be replaced by a BaCon gtk message window, but I ws lazy
    SYSTEM CONCAT$("gxmessage ", my_preamble$, my_message_text$)
END SUB

'--------------
SUB CHECK_PIDS()
'--------------
    ' for some reason, a single bacon prog generates two pids!!!!
    ' myprog_clean$ should probably be GLOBAL because it can used in DESTROY()
    ' to output "Quitting progname in a terminal.

    ' PvE: strange, the two-pid situation does not occur on any of my Linux systems? Maybe some OS setting of your distribution?

    LOCAL mypids$, my_response$
    myprog$ = ARGUMENT$
    SPLIT myprog$ BY " " TO myprog_array$ SIZE argcount
    myprog_clean$ = MID$(myprog_array$[0], INSTRREV(myprog_array$[0], "/") + 1)
    mypids$ = CHOP$(EXEC$(CONCAT$("pidof ", myprog_clean$)))
    SPLIT mypids$ BY " " TO pid_array$ SIZE pidcount
    IF pidcount > 2 THEN
        my_response$ = CONCAT$("Sorry, ", myprog_clean$, " is already running...")
        MY_STATUS_MESSAGE(CONCAT$(" -timeout 2 ", CHR$(13), "' '", my_response$))
        PRINT my_response$
        END
    ELSE
        PRINT "Starting ", myprog_clean$, " ..."
    END IF
END SUB

'--------------
REM Contributed by James C. Fuller - April 2009.
FUNCTION Tally(STRING Main$,STRING Match$)
'--------------
    LOCAL i,j,k,mlen,matchlen TYPE int
    mlen = LEN(Main$)
    matchlen = LEN(Match$)
    i = 1 :  j = 0 :  k = 0
    IF (mlen EQ 0) OR (matchlen EQ 0) THEN
        RETURN j
    END IF

    WHILE 1 DO
        IF EQUAL(MID$(Main$,i,matchlen),Match$) THEN
                j = j + 1
        END IF
        i = i + matchlen
        IF i > mlen THEN
            BREAK
        END IF
    WEND
    RETURN j
END FUNCTION

'--------------
REM Contributed by James C. Fuller
FUNCTION MsgBox(STRING title$,STRING Msg$,NUMBER typ,NUMBER but)
'--------------
    LOCAL hDlg,rv, pixbuf, errmsg

    hDlg = gtk_message_dialog_new (0, 0, typ, but, Msg$)
    gtk_window_set_position(hDlg, 1)
    gtk_window_set_title(hDlg, title$)
    rv = gtk_dialog_run (hDlg)
    gtk_widget_destroy (hDlg)

    RETURN rv

END FUNCTION

'--------------
FUNCTION IdxCount(char* fname)
'--------------

    LOCAL filelen, buffer

    filelen = FILELEN(fname)
    buffer = MEMORY(filelen)

    OPEN fname FOR READING AS fh
        GETBYTE buffer FROM fh SIZE filelen
    CLOSE FILE fh

    OPEN buffer FOR MEMORY AS str$
        iMaxEntries = Tally(str$,NL$)
    CLOSE MEMORY str$

    FREE buffer
    RETURN iMaxEntries
END FUNCTION

'--------------
SUB destroy(NUMBER widget, void* data)
'--------------
    gtk_main_quit

END SUB

'--------------
SUB on_menu_click(NUMBER widget, void* data)
'--------------
    LOCAL click

    click = (long)data
    SYSTEM CONCAT$(Menu_Icon[click].sPath$, " &")
END SUB

'--------------
SUB help()
'--------------
LOCAL msg$, title$

    title$ = "PupTray Help"

    msg$ = "PupTray is a small application that resides in the system\n"
    msg$ = CONCAT$(msg$, "tray and provides a way for you to quickly launch your favorite programs.")
    msg$ = CONCAT$(msg$, "There's even a menu item to quickly edit your configuration file built in.")
    msg$ = CONCAT$(msg$, "To use just place the PupTray binary file in your startup folder so it runs ")
    msg$ = CONCAT$(msg$, "on startup.\n\n")
    msg$ = CONCAT$(msg$, "Config file format: Name=Application [app parameters]\n\n")
    msg$ = CONCAT$(msg$, "Example:",NL$,NL$)
    msg$ = CONCAT$(msg$, "Geany=geany ","[\"some text file\"]",NL$)
    msg$ = CONCAT$(msg$, "Browser=seamonkey ","[\"some url\"]",NL$,NL$)
    msg$ = CONCAT$(msg$, "Anywhere you want a menu seperator, just add ",CHR$(34),"+++",CHR$(34),NL$,NL$)
    msg$ = CONCAT$(msg$, "Have Fun!\n")

    MSGBOX_OK(title$,msg$)

END SUB

'--------------
FUNCTION find_icon$(STRING iconame$)
'--------------
LOCAL tmpname$ TYPE STRING
LOCAL dirname$[5]
LOCAL i

    dirname$[0] = "/usr/local/PupTray/icons/"
    dirname$[1] = "/usr/share/pixmaps/"
    dirname$[2] = "/usr/local/lib/X11/mini-icons/"
    dirname$[3] = "/usr/local/lib/X11/pixmaps/"
    dirname$[4] = "/usr/share/icons/hicolor/16x16/apps/"

    'if the name is "default***..." then open the 
    'script file and read the name of the program.
    IF EQUAL(LEFT$(iconame$, 7), "default") THEN
        OPEN CONCAT$("/usr/local/bin/",iconame$) FOR READING AS FP1
        WHILE NOT(ENDFILE(FP1)) DO
            READLN szInput$ FROM FP1
            szInput$ = CHOP$(szInput$)
            IF NOT(ENDFILE(FP1)) THEN
                IF EQUAL(LEFT$(szInput$, 4), "exec") THEN
                    SPLIT szInput$ BY " " TO array$ SIZE dim
                    iconame$ = array$[1]
                END IF
            ENDIF
        WEND
        CLOSE FILE FP1
    END IF

    FOR i = 0 TO 4
        tmpname$ = CONCAT$(dirname$[i],iconame$,".png")
        IF FILEEXISTS(tmpname$) THEN RETURN tmpname$
        tmpname$ = CONCAT$(dirname$[i],iconame$,".xpm")
        IF FILEEXISTS(tmpname$) THEN RETURN tmpname$
    NEXT i

    tmpname$ = "/usr/local/PupTray/icons/default.png"
    IF FILEEXISTS(tmpname$) THEN RETURN tmpname$

    RETURN ""
END FUNCTION

'--------------
SUB popup(NUMBER status_icon, int button, int activate_time, void* user_data)
'--------------

    LOCAL item, i

    my_menu = 0
    CALL fillarray ()

    IF NOT(my_menu) THEN
        my_menu = gtk_menu_new()

        FOR i = 0 TO mucnt-1
            IF INSTR(Menu_Icon[i].sDescription$, "+++") > 0 THEN
                item = gtk_separator_menu_item_new()
            ELSE
                item = gtk_image_menu_item_new_with_label(Menu_Icon[i].sDescription$)
                Menu_Icon[i].sPath$ = CONCAT$(Menu_Icon[i].sPath$," ")
                pos = INSTR(Menu_Icon[i].sPath$," ")
                icon$ = find_icon$(CHOP$(LEFT$(Menu_Icon[i].sPath$, pos)))

                ' PvE: Lets scale the images correctly.
                gdk = gdk_pixbuf_new_from_file(icon$, NUL)
                scaled_gdk = gdk_pixbuf_scale_simple(gdk, 16, 16, GDK_INTERP_BILINEAR)
                stock = gtk_image_new_from_pixbuf(scaled_gdk)
                gtk_image_menu_item_set_image(item, stock)
                g_object_set(item, "always-show-image", TRUE, 0)
            END IF
            gtk_menu_shell_append(my_menu, item)
            g_signal_connect_data(item, "activate", on_menu_click, Menu_Icon[i].iMenuID, 0, 0)
        NEXT i

        item = gtk_separator_menu_item_new()
        gtk_menu_shell_append(my_menu, item)

        item = gtk_image_menu_item_new_with_label("Help")
        stock = gtk_image_new_from_stock("gtk-help", 1)
        gtk_image_menu_item_set_image(item, stock)
        g_object_set(item, "always-show-image", TRUE, 0)
        gtk_menu_shell_append(my_menu, item)
        g_signal_connect_data(item, "activate", help, 0, 0, 0)

        item = gtk_image_menu_item_new_with_label("Quit")
        stock = gtk_image_new_from_stock("gtk-quit", 1)
        gtk_image_menu_item_set_image(item, stock)
        g_object_set(item, "always-show-image", TRUE, 0)
        gtk_menu_shell_append(my_menu, item)
        g_signal_connect_data(item, "activate", destroy, 0, 0, 0)
    END IF

    gtk_widget_show_all(my_menu)
    gtk_menu_popup(my_menu, 0, 0, gtk_status_icon_position_menu, status_icon, button, activate_time)
END SUB

'--------------
SUB fillarray()
'--------------
    mudatafile$ = CONCAT$(GETENVIRON$("HOME"), "/.puptray.cfg")
    muidx = 0

    mucnt = IdxCount(mudatafile$)

    IF FILEEXISTS(mudatafile$) THEN
        OPEN mudatafile$ FOR READING AS myfile
            WHILE NOT(ENDFILE(myfile)) DO
                READLN txt$ FROM myfile
                txt$ = CHOP$(txt$)
                    IF INSTR(txt$, "=") > 0 THEN
                        SPLIT txt$ BY "=" TO array$ SIZE dim
                            'this is a menu item
                            WITH Menu_Icon[muidx]
                                .sPath$ = array$[1]
                                .sDescription$ = array$[0]
                                .iMenuID = muidx
                            END WITH
                            INCR muidx
                    END IF
                    IF INSTR(txt$, "+++") > 0 THEN
                            'this is a seperator
                            WITH Menu_Icon[muidx]
                                .sPath$ = ""
                                .sDescription$ = CHOP$(txt$)
                                .iMenuID = muidx
                            END WITH
                            INCR muidx
                    END IF
            WEND
        CLOSE FILE myfile
    END IF
END SUB

' *********************
' END SUBROUTINES
' *********************


' *********************
' MAIN
' *********************
CHECK_PIDS
gtk_init(0, 0)

'if no configuration file, create one
IF NOT(FILEEXISTS(CONCAT$(GETENVIRON$("HOME"), "/.puptray.cfg"))) THEN
    OPEN CONCAT$(GETENVIRON$("HOME"), "/.puptray.cfg") FOR WRITING AS settings
    WRITELN "BaCon Website=firefox ",CHR$(34),"http://www.basic-converter.org/",CHR$(34) TO settings
    WRITELN "+++" TO settings
    WRITELN "Edit Configuration=", "gedit ",CHR$(34), GETENVIRON$("HOME"), "/.puptray.cfg",CHR$(34) TO settings
    WRITELN "+++" TO settings
    CLOSE FILE settings
END IF

status_icon = gtk_status_icon_new_from_file("/usr/share/pixmaps/puptray.png")
gtk_status_icon_set_visible(status_icon, TRUE)
gtk_status_icon_set_tooltip(status_icon, "Puppy Linux Quick Launcher\n\tright click for menu")

' Connect signals
g_signal_connect_data(status_icon, "popup-menu", popup, 0, 0, 0)
gtk_main

' *********************
' END MAIN
' *********************