Sarnold This is a GUI tool for launching system commands. You can type command arguments in a text field like you would in a shell.


You can use it as a GUI front-end for tools like bc, and pressing Escape cause the last result to be recalled as command argument. It stores a table of command prefixes in its configuration. You can use a few command modifiers to perform special tasks:

@dir Title
-> prompts for a directory choice and replace them by this choice
@temp ....
-> stores remaining arguments in a temporary file and replace them by its path (currently under /tmp)
-> ignores further arguments
-> runs command in background (appends '&' to the command line)
@wdir /path/to/dir
-> runs command with /path/to/dir as work directory, only for the current command


2010-Apr 4: Sarnold releases v0.3.


The source is there:

proc DefaultConfig {args} {
        global HANDLER HELP ASK
        set HANDLER ""
        set HELP ""
        foreach {key val usage} {
                {Calculator} {bc @temp} {Enter a math expression\nlike 3+2*4 (requires the program 'bc')}
                {Tfu} {tfu @dir Source @dir Destination @end} {The tfu script which keeps two directories in sync.}
        } {
                dict set HANDLER $key $val
                dict set HELP $key $usage
        return "Configuration reset done"

# intercepte l'erreur et relance $msg
proc Rethrow {body msg} {
        if {[catch {uplevel 1 $body} res]} {error $msg}
        set res

proc Debug {message {level INFO}} {
        set level [dict get {INFO 0 WARNING 1 ERROR 2} $level]
        if {$level < 1} return
        tk_messageBox -message $message
        set message

proc CommandList {} {lsort [dict keys $::HANDLER]}

set DIALOG() ""

proc Dialog {name {cmd ""}} {
        destroy $name
        if {$cmd ne ""} {
                eval $cmd
        array unset ::DIALOG *

proc CreateAction {} {
        global HANDLER DIALOG HELP
        llength $DIALOG(prefix)
        set cmd $DIALOG(prefix)
        dict set HANDLER $DIALOG(name) $cmd
        dict set HELP $DIALOG(name) $DIALOG(usage)
proc ModifyAction {} {
        NewAction [GetAction $::Current]

proc GetAction {name} {
        global HANDLER HELP
        list name $name prefix [dict get $HANDLER $name] usage [dict get $HELP $name]

proc NewAction {{dict ""} {frtitle "Edit action"}} {
        toplevel .new
        wm title .new $frtitle
        global DIALOG
        foreach {name key} {{Command name} name {Command line prefix} prefix {Usage (help)} usage} {
                set fr [ttk::frame .new.$key]
                set DIALOG($key) [Get $dict $key]
                ttk::label $fr.label -text $name -width 20
                ttk::entry $fr.entry -textvariable ::DIALOG($key) -width 36
                pack $fr.label $fr.entry -side left -padx 2 -pady 4 -fill both
                pack $fr
        pack [set fr [ttk::frame .new.confirm]]
        pack [ttk::button $fr.ok -text Ok -command {Dialog .new CreateAction}]\
                [ttk::button $fr.delete -text Delete -command {Dialog .new DeleteAction}]\
                [ttk::button $fr.cancel -text Cancel -command {Dialog .new}] -side left -padx 2 -pady 4 -fill both

proc Get {dict key {default ""}} {
        if {[dict exists $dict $key]} {return [dict get $dict $key]}
        set default

proc SaveConfig {} {
        catch {
                set fd [open ~/.blauncherrc w]
                foreach {key val} $::HANDLER {
                        puts $fd [list $key $val [Get $::HELP $key]]
                close $fd

proc LoadConfig {} {
        if {![file exists ~/.blauncherrc]} {
        global HANDLER HELP
        set HELP ""
        catch {
                set fd [open ~/.blauncherrc]
                while {![eof $fd]} {
                        lassign [gets $fd] key val usage
                        if {$key eq ""} continue
                        dict set HANDLER $key $val
                        dict set HELP $key $usage
                close $fd

proc ResetConfig {} {
        Confirm [txt "Sure you want to reset?"] {
                Show [txt "Reset done"]

proc Cwd {} {
        set dir [Ask [txt "Choose working directory"] on]
        if {$dir ne ""} {cd $dir}

proc Show {text} {
        tk_messageBox -type ok -message $text -title [txt Message]

# creates a temporary file
# with an id (unique within a command Exec)
# and a text to fill with
proc MakeTemp {id text} {
        set fname /tmp/blaunch[pid].$id
        set fd [open $fname w]
        puts $fd $text
        close $fd
        set fname

proc txt arg {set arg};#i18n

proc Exec args {
        set tmp 0
        set background no
        set wdir [pwd]
        for {set i 0} {$i<[llength $args]} {incr i} {
                lassign [lrange $args $i end] descr txt
                if {$descr eq "@dir"} {
                        set f [Ask $txt]
                        if {$f eq ""} {error "Directory choice aborted by user."}
                        set args [lreplace $args $i [expr {$i+1}] $f]
                if {$descr eq "@wdir"} {
                        cd $txt
                        set args [lreplace $args $i [expr {$i+1}]]
                        incr i -1
                if {$descr eq "@temp"} {
                        set args [lreplace $args $i end [MakeTemp [incr ::tmp] [lrange $args [incr i] end]]]
                if {$descr eq "@end"} {
                        #ignores remaining arguments
                        set args [lrange $args 0 [expr {$i-1}]]
                if {$descr eq "@bg"} {
                        set args [lreplace $args $i $i]
                        set background yes
                        incr i -1
        if {$background} {lappend args &}
        exec {*}$args
        cd $wdir

proc ? {val def} {expr {$val eq ""?$def:$val}}
proc txt? {val def} {? $val [txt $def]}

proc Ask {txt {mustexist no}} {
        if {![info exists ::choosedir]} {
                tk_chooseDirectory -initialdir [pwd] -title [txt? $txt "Choose directory"] -mustexist $mustexist
        } else {
                exec $::choosedir $txt

proc Blaunch {} {
        global Current Command HANDLER HISTORY
        lappend HISTORY $Current $Command
        if {[catch {Exec {*}[dict get $HANDLER $Current] {*}$Command} result]} {
                Show "An error occured, see the message below."
                PutText .result $result
        lappend HISTORY $result
        PutText .result $result
        set Command ""

proc PutText {path text} {
        $path delete 1.0 end
        $path insert end $text

proc Paste {} {
        lappend ::Command {*}[split [lindex $::HISTORY end] \n]
        .command.entry icursor end
proc Quit {} {SaveConfig; exit}
proc Exit {} {
        Confirm [txt "Sure you want to quit?"] Quit

proc UpdateCombobox {args} {
        .actions.choose configure -values [CommandList]
        if {$::Current ni [CommandList]} {set ::Current [lindex $::HANDLER 0]}

proc DeleteAction {} {
        Confirm [format [txt "Are you sure you want to drop action '%s'?"] $::Current]        {dict unset ::HANDLER $::Current}

proc Confirm {txt body} {
        if {[tk_messageBox -message $txt -type okcancel -title [txt Question]] eq "ok"} {eval $body}

proc History {} {Show "Not implemented yet"}

proc Clear {} {set ::Command ""}

proc Help {} {Show "$::VERSION
An enhanced command launcher written in Tcl/Tk.

(c) Stéphane Arnold 2010
New BSD license (like Tcl)

Press Enter to launch, and Escape to paste last result."} 

proc Usage {} {
        global HELP Current
        Show [dict get $HELP $Current]

proc Focus {} {focus .command.entry}

set HISTORY ""
set VERSION "Blauncher v0.3"
set Current [lindex $HANDLER 0]
set Command ""
# option database
option add *button.padx 7
option add *button.pady 7
option add *button.width 12
option add *entry.font {Courier 10}

wm title . $VERSION
pack [ttk::frame .actions]
pack [ttk::combobox .actions.choose -textvariable ::Current -state readonly -values [CommandList] -width 30]\
 [ttk::button -text "New action" -command NewAction] \
 [ttk::button .actions.modify -text [txt Edit/Delete] -command ModifyAction]\
 [ttk::button .actions.reset -text Reset -command ResetConfig]\
 [ttk::button .actions.history -text "Usage" -command Usage] -side left -fill both -padx 2 -pady 4
pack [ttk::frame .command]
pack [ttk::button .command.clr -text Clear -command Clear]\
 [ttk::entry .command.entry -textvariable ::Command -width 40] \
 [ttk::button .command.ok -text Blaunch! -command Blaunch] -side left -fill both -padx 2 -pady 4
pack [text .result -width 64 -height 8 -background #fafafa -font {Courier 10}]
pack [ttk::frame .sys]
pack [ttk::button .sys.recall -text "Paste result" -command Paste] \
 [ttk::button .sys.cwd -text "Work directory" -command Cwd] \
 [ttk::button .sys.exit -text Exit -command Exit] \
 [ttk::button -text Help -command Help] -side left -padx 2 -pady 4

trace add variable ::HANDLER write UpdateCombobox
bind . <FocusIn> Focus
bind . <Key-Return> Blaunch
bind . <Key-Escape> Paste
# avoids selection in combobox (default Tk behavior)
bind .actions.choose <<ComboboxSelected>> {.actions.choose selection clear}
# program name for choosing a directory
# comment it for standard tk_chooseDirectory
#set choosedir choosedir

# Save config on exit (when delete main window)
wm protocol . WM_DELETE_WINDOW Exit