tkgui
Description
tkgui is a Chicken extension for creating GUI applications on Windows and unix platforms. tkgui leverages the venerable Tcl/Tk toolkit through its C-api enabling good performance and uncomplicated deployment.
The egg compiles to an executable, mktkgui, described in this document.
Author
Jules Altfas
Repository
https://codeberg.org/jrapdx/tkgui
Requirements
mktkgui
mktkgui is easy to use, with only 2 required command line arguments.
mktkgui {options}
-src string Space-separated list of Scheme source files [required]
-app name Output app basename (without extension) [required]
-imports string Space-separated list of additional extension imports
-stop 1..4/symbol Stop @stage: 1/obj, 2/stk, 3/dll/so, 4/exe, #f/don't stop
-install appname (Re)installs compiled <app>(.exe) and (lib)<app>(.dll/so)
-uninstall appname Uninstalls previously installed <app basename>
-gui Windows only: toggles 'console'/'gui', default="gui"
-verbose Toggles verbose output, default="on"
-static Toggles static egg libs (default="off")
-config filename Config file to use, default="ext-config.scm"
-defaults Prints built-in default settings.
-settings Prints configured settings (e.g., ext-config.scm)
-help Prints this text and exits
Command line arguments -gui, -verbose, -static work as switches, that is, when used without a value produce the inverse effect of the corresponding configuration setting. However these options also accept #t or #f values which override toggling configuration settings. (-gui option has effect only in Windows.)
By default gui and verbose options are #t, using the command line -<option> forces the option off. Conversely, with #f config setting, -<option> means option is on. However, when the command line includes -<option> #t the option is unconditionally on, and if #f, unconditionally off.
Note: on the command line #t and #f have to be enclosed in single or double quotes. (In shell syntax '#' initiates a line comment.)
How mktkgui compiles applications
mktkgui manages 4 major compiled stages without user intervention:
- Creates <app-name>.c, Tcl extension code, and compiled (by $(CC)) to an obj file (*.o or *.obj).
- Creates <app-name>-scheme-tk.c, containing source and 'glue' code. Then $(CSC) compiles this to a second obj file, using -link files if static egg libs linking is specified.
- $(CSC) invokes the linker which links the above object files (and egg *.o/obj files if linking statically) producing a shared library (.dll or .so) that is a fully qualified Tcl/Tk extension as well as a Scheme program. IOW the shared library's interacting Scheme and Tcl code are effectively a "hybrid" application. (The shared object can be loaded into a running wish program which starts the GUI.)
- Lastly, $(CC) generates a small dedicated wish-like binary that automatically loads the dll/so from stage 3. The GUI program starts immediately when the library is loaded.
Running compiled programs
On Windows, applications can be launched from anyk directory. Assuming the app was compiled in gui mode (the default), a console won't be rendered. Alternatively using a symbolic link or "shortcut" allows controlling program invocation. Note that attempting to launch the app from the source directory may produce errors about not finding Tcl/Tk init files. Placing a copy of (or symbolic link to) the Tk .dll in the source directory resolves the issue. However, calling the installed (vs. local) program from any directory works as expected.
Unix systems generally don't require anything special. Command-line invocation or shell scripts can be employed. Note that a Tcl/Tk REPL prompt is provided. Running Tcl/Tk commands can modify the program's UI exactly like wish*. It's a way to see effect of commands. Observed effects could potentially be incorporated into the program. On Windows using '-gui' (turn off gui mode) produces a console program like its unix counterpart.
Scheme/Tk GUI syntax
All Scheme to Tcl/Tk translations are handled by the tk procdure:
[procedure] tk 'widget-or-Tcl-command 'argument|"argument" ... '(lambda () ...)Arguments to tk are symbols or strings, except callbacks which are lists and last argument on the command line. (Except when a -validatecommand callback is being used with entry or spinbox widgets. In this case, the widget -command callback sequence can be placed before or after -validatecommand callback.) A callback is a Scheme lambda that takes no arguments. '(lambda () ...) The body of the lambda can contain any expressions including (tk ...) commands.
Be aware that callbacks do not have lexical scope. That is, the lambda's expressions can only access procedures and variables local to the lambda or at toplevel. (tk ...) returns values returned to it by Tcl.
Widget callbacks can have return values but do not return a value to Scheme. Tcl ignores values returned from callbacks with some exceptions. Entry validation callbacks -validatecommand are required to return a boolean value (#t/#f, 0/1, indicating success or failure of validation procedures). Some bind callbacks return values. Also a widget command callback can be invoked with <widget path> invoke which does return values to its caller.
A tkgui program can create Tcl proc commands which can later be called from callback bodies or other code. Here's a simple example:
(import scheme.base nutils) (tk 'proc "x y" '(lambda () (let ((x (tk 'set 'x)) (y (tk 'set 'y))) (combinesp x y)))) ;; call the Tcl proc (define r (tk 'testing "hello," "out there") (writenl r) "hello, out there"
A proc command body can return numbers, strings or symbols. Values are returned as strings. Conversion to other types is straightforward.
tk can also create Tcl/Tk entities entirely in Tcl/Tk:
(tk "proc test2 {x y} { puts \"x == $x, y == $y\" return [expr {$x + $y}] }") (printnl 'test2 (tk 'test2 43 54)) ;; -> x == 43, y == 54 ;; -> test2 97
Here's an example of using (tk ...) calls to show a frame and button:
(define topvar) ... (define frm (tk 'frame ".frm")) ;; frm -> ".frm" (define frm.btn (tk 'button (string-append frm ".btn") -text "Press me" '-command '(lambda () (set! topvar #t) ...)))
Procedures are provided to make tk programming more convenient:
[procedure] tk/nm widget wgtpath '-option ...- widget - symbol or string, the type of widget to create: 'button, 'ttk::entry, etc.
- wgtpath - Tcl window path.
- wgtpath may be a string, ".form0.entry1", a list of symbols, '(form0 entry1) or `(,form1 btn2). In Tk, the root window is represented by a dot, "." and child windows by a string with a leading dot as in ".frm", or ".frm2.btn0". Using tk/nm or derived procedures, symbol lists are converted to a widget path string. Dots are automatically inserted into the constructed window path name as needed.
- '-option ... - options vary among widget, consult the TclTk documentation.
tk/nm derived procedures:
[procedure] tk/frame wgtpath '-option ...[procedure] tk/label wgtpath "label text" '-option ...
[procedure] tk/labelframe wgtpath "label text" '-option ...
[procedure] tk/button wgtpath "button text" '-option ...
[procedure] tk/entry wgtpath "width" '-option ...
[procedure] tk/spinbox wgtpath '-option ...
[procedure] tk/chkbtn wgtpath "label text" '-option ...
[procedure] tk/radiobtn wgtpath label '-option ...
Tcl/Tk provides window managers to specify widget layout. Two most used are 'pack' and 'grid'. Each has its merits, but 'grid' becomes more advantageous as UIs start to become elaborate. Of course (tk ...) works with any Tk window manager, but 'grid' has been successful and at this point well integrated into the project.
[procedure] tk/grid wgtpath column row '-px '-py '-sy '-rsp '-csp '-option ...- column and row mandatory arguments specify placement within a widget's container.
- frequently used options (abbreviated):
- '-px, abbreviation for '-padx with default value of 0 or no horizontal padding. A list can be provided such as "5 10" meaning padding of 5 pixels to the left and 10 pixels to right of widget.
- '-py or '-pady, vertical padding. Follows same rules as -padx.
- '-sy or '-sticky with values of "" (default), or 'e, 'w, 'n, 's in any combination. This option puts the widget to one or more of the 4 sides if its container (when there's surplus space available) or centered (with "").
- '-rsp is '-rowspan, how many rows to occupy. Default is 1.
- '-csp is '-columnspan, number of columns to occupy. Default is 1.
- a number of less common options can also be applied. See Tcl/Tk documentation.
- tk/grid* takes the grid subcommand name, the window it applies to and subcommand options. See Tcl/Tk documentation.
- tk/bind applies to a particular widget or all widgets of a given type or "class". The details of events and bind callbacks are well-described in the Tk documentation.
This is equivalent to the prior example:
(define frm (tk/frame '(frm))) (define frm.btn (tk/button `(,frm btn) "Press me" '-command '(lambda () ...)))
Here's a complete example (example.scm):
(import scheme.base nutils ) (tk 'wm 'withdraw ".") (define rr 122) (tk 'font 'configure 'TkDefaultFont '-family "helvetica" '-size "10") (tk 'font 'configure 'TkCaptionFont '-family "helvetica" '-size "10") (define frm0 (tk/labelframe '(frm0) "Msgs:")) (define msgs (tk/nm 'text `(,frm0 outmsg) '-font "-family helvetica -size 10" '-wrap 'word '-width 32 '-height 9)) (printnl 'expr (tk "expr {122 * 3}") ) (define (update-msg newmsg) (tk msgs 'replace "@0,0" 'end newmsg)) (update-msg "Start...") (define btn1 (tk/button `(,frm0 btn) "Press here" '-command '(lambda () (tk frm0 'configure '-text "Output:") (tk 'tk_messageBox '-message (combine "button works!\nrr is " rr)) (update-msg (combine "Great job! Now at " rr)) (set! rr (+ rr 1))))) (tk/grid frm0 0 0 '-px 20 '-py 20) (tk/grid msgs 0 0 '-px 10 '-py 12) (tk/grid btn1 1 0 '-px 22 '-py "9 19") (tk 'wm 'deiconify ".")
Installing the Tcl/Tk html documentation is highly recommended. It's available at Sourceforge. These URLs are for TclTk/9.0 docs but other versions are available.
Configuration
As of tkgui v0.8.0 format of configuration files was changed. The original format is still supported.
Specifically, removed option: app-01. Options with changed names (from/to): exelib/libdir, libchkn/libchicken, cflag/cflags, csc-flag csc-cflag/csc-cflags. The dllflag option is used with the c-compiler, '-shared' (unix) or '-mdll' (windows). When csc produces the dll/shared library, csc-dllflag is '-dll' (windows) and '-s' (unix). (Currently csc compiles dll/shared lib using 'csc-dllflag'.)
Config values are parsed as lists, and transformed into strings to meet requirements of process or system shell calls.
Callback support for TclTk entities includes widget commands, validation callbacks for entry/spinbox widgets, and callbacks for bind, wm and other commands. In addition, (tk 'proc ...) commands have callback support. Note that callbacks may have particular TclTk requirements for return values.
Writing configuration files
mktkgui has platform-specific built-in configurations that are overridden by config file key/value settings. Each source directory can (and should) have its own config file. If a config file named "ext-config.scm" exists it's used by default. However when a directory has multiple configs the -config command line option can be used to select the appropriate config file.
Config files consist of a Scheme list containing ~30 key-value lists. The first element (car) of an option list is its key. When a key (a symbol) matches a default key the (cdr) of the option list replaces the default value fpr that key. A config value can be null, or contain one or more strings, symbols, booleans, or numbers.
Defaults are printed to the terminal using mktkgui -defaults. Use mktkgui -settingsfor configured settings (config overrides).
The option 'static-egg-libs' determines how extension (egg) libraries are linked: static or shared. By default 'static-egg-libs' is #f, that is, shared linking is the initial setting. Setting (static-egg-libs #t) turns on static linking. Alternatively, the mktkgui command line -static option can be used to activate static egg library linking without changing the default. (-static toggles the currently configured setting unless followed by '#f' or '#t'.)
Note: on unix platforms static linking of egg libs requires compiling the egg with "-C" "-fPIC" added to 'csc-options'. Shared linking doesn't require '-fPIC' but having it does no harm.
Example config:
((cc /usr/local/bin/gcc)
(csc /usr/local/bin/csc)
(includes -I. -I/usr/local/include/chicken -I/usr/local/include)
(linkdir /usr/local/lib/chicken/12)
(imports myegg my-other-egg)
...
(static-egg-libs #t)
...
Using -static '#f' on the command line guarantees the option is off. With -static '#t' the option is on overriding config file settings. (Also true for -gui and -verbose options.)
Builtin setting defaults
Unix platforms
cc /usr/bin/gcc cflags -Og -g -fPIC config-check "unix defaults" csc /usr/local/bin/csc csc-cflags -C -g -C -fPIC csc-dllflag -s csc-libchicken -L -lchicken) csc-stublibs -L -ltclstub9.0 -L -ltkstub9.0 csc-tcltklibs -L -ltcl9.0 -L -ltcl9tk9.0 csc-winflag defs -DUSE_TCL_STUBS -DUSE_TK_STUBS dll .so dllflag -shared exedir /usr/local/bin gui #f imports includes -I. -I/usr/local/include/chicken -I/usr/local/include ldconfig ldconfig ldflags -L/usr/local/lib -L. libchicken -lchicken libdir /usr/local/lib libprefix lib linkdir "." link-list srfi-1 srfi-13 srfi-14 nutils obj .o platform unix rc static-egg-libs #f stublibs -ltclstub9.0 -ltkstub9.0 sudo sudo tcltklibs (-ltcl9.0 -ltcl9tk9.0) verbose #t winflag
Windows platform
cc c:/w64devkit/bin/gcc.exe cflags -Og -g config-check "windows defaults" csc c:/usr/local/bin/csc.exe csc-cflags csc-dllflag -dll csc-libchicken -L -lchicken) csc-stublibs -L -ltclstub9.0 -L -ltkstub9.0 csc-tcltklibs -L -ltcl9.0 -L -ltcl9tk9.0 csc-winflag -gui defs -DUSE_TCL_STUBS -DUSE_TK_STUBS dll .dll dllflag -mdll exe .exe exedir c:/usr/local/bin gui #t imports includes -I. -Ic:/usr/local/include/chicken -Ic:/usr/local/include ldconfig ldflags -Lc:/usr/local/lib -L. libchicken -lchicken libdir c:/usr/local/bin libprefix linkdir "." link-list srfi-1 srfi-13 srfi-14 nutils obj .obj platform windows rc ./rc static-egg-libs #f stublibs (-ltclstub -ltkstub) sudo tcltklibs -ltcl91 -ltcl9tk91 verbose #t winflag -mwindows
Windows *.rc files
Windows uses *rc files to assign resources like icons, cursors and metadata to a library or executable. In a Tk application, .rc data isn't really optional as it contains rules defining geometry of widget layout.
Fortunately it's not difficult to manage. The Tk source distribution includes an 'rc' directory under 'win' in the source tree. Run the './configure' script in the win directory to generate 'wish.exe.manifest'. If not already copied, copy wish.exe.manifest to the rc directory and copy the rc dir to your GUI program location.
Creating a unique icon for an app is a good idea. Using the Tk icon in source distribution as a template, replace the default Tk icon with a new one also named 'tk.ico'. To complete this customization, place your app 'tk.ico' in the Tk rc directory before compiling Tk from source. Finally, rename the tcl9tk9x.dll and libtcl9tk9x.dll.a files. This requires editing the Makefile, not too difficult a procedure.
Obtain the latest sources (Github) and after running the ./configure script a Makefile file should be located in the unix or windows directory. In an editor look for TK_LIB_FILE which should be like libtcl9tk9.1.so depending on exact version. Modify the file name, perhaps libtcl9tk9.1x.so. Use the same name modification for related variables like TK_LIB_FLAG -ltcl9tk9.1x. Put the custom tk.ico in the rc directory. Compiling as normal gives a custom-named shared lib.
License
Copyright (c) 2025 Jules Altfas
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Version History
- v0.8.5 Fixed issue with return values from command procedures.
- v0.8.4 New features for Scheme-Tk, better performance, bugfixes
- v0.8.0 Added (tk/*) procedures, revised configuration, expanded TclTk coverage, performance enhancements, added basic tests
- v0.7.4 Allow -validatecommand callbacks to work with TclTk
- v0.7.3 Update documentation, additional command line option
- v0.7.2 Option for static vs. shared linking of extensions
- v0.7.0 Ported to FreeBSD
- v0.6.4 Removed dependencies, simplify build.
- v0.6.2 Revised documentation
- v0.6.1/0.6.1.1 Minor corrections to info files
- v0.6.0 Initial public release