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 options.
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 options -gui, -verbose, -static work as switches, that is, when used without a value produce the inverse effect of the corresponding configuration specifier. However these options also accept #t or #f values which override toggling configuration settings. (gui options are Windows only.)
Since default gui and verbose options are #t, using the command line -<option> or -<option> '#t' forces the option off. Conversely, -<option> '#f' means option is on.
This table summarizes the (default) interaction of config and command line options:
| Config default [on/off] | Cmd line | Cmd line [force off] | Cmd line [force on] |
|---|---|---|---|
| (winflag -mwindows) [on] | -gui [off] | -gui '#t' | -gui '#f' |
| (winflag ) [off] | -gui [on] | -gui '#f' | -gui '#t' |
| (verbose #t) [on] | -verbose [off] | -verbose '#t' | -verbose '#f' |
| (static-egg-libs #f) [off] | -static [on] | -static '#f' | -static '#t' |
Note: on the command line, #t and #f have to be enclosed in single or double quotes. (In shell syntax '#' initiates a line comment.)
## static linking of egg libs (with default configuration) $ mktkgui -src myapp.scm -app myapp -static ## static linking of egg libs (default configuration) $ mktkgui -src myapp.scm -app myapp -static '#t' $ mktkgui -src myapp.scm -app myapp -static '#t' \ -config "my-config.scm" ## force shared egg lib linking $ mktkgui -src myapp.scm -app myapp -static "#f" ## this invocation turns off verbose terminal output $ mktkgui -src my-gui-app.scm -app myapp -verbose config-check: ext-config.scm myapp.obj -> OK. myapp-scheme-tk.obj -> OK. myapp.dll -> OK. myapp.exe -> OK. install: myapp.dll and myapp.exe -> OK. ## installed to configured locations.
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.
- The above object files (and egg *.o/obj files if linking statically) are linked to form a shared library (.dll or .so) that is a fully qualified Tcl/Tk extension as well as a Scheme program. IOW the intereacting 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, mktkgui generates a small wish-like binary dedicated to automatically loading 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 directly from the installation directory. Assuming the app was compiled in gui mode (the default), a console won't be rendered. Alternatively using a link or "shortcut" allows controlling program invocation. Note that attempting to launch from the source directory may produce errors about finding Tcl/Tk init files. Placing a copy of (or symbolic link to) the Tk .dll in the source directory resolves the issue.
Unix systems generally don't require anything special. Command-line invocation or shell scripts can be employed.
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 -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 list that takes no arguments. '(lambda () ...) The body of the lambda can contain any expressions including (tk ...) commands.
It's important to note that callbacks do not have lexical scope. That is, the lambda's expressions can only access internal or toplevel Scheme variables and procedures. (tk ...) returns values returned to it by Tcl.
Tcl widget callbacks do not return a value to Scheme. Tcl expects to receive a a "neutral" value from these callbacks. For this reason it's a good practice to put 'return or (tk 'return) as the final expression of widget and bind callbacks.
An exception is that -validatecommand callbacks must return a standard boolean to TclTk. That can take the form of a Scheme boolean (#f/#t) or (tk 'return 0|1).
A tkgui program can create Tcl proc commands which can later be called from (tk 'procname ...) bodies. Here's a brief example:
(import scheme.base nutils) (tk 'proc "x y" '(lambda () (let ((x (tk 'set 'x)) (y (tk 'set 'y))) (tk 'return (combinesp x y))))) (printnl (tk 'testing "hello," "out there")) hello, out there
A proc command's callback can return various types (string, number, boolean) via (tk 'return ...)).
Certain other callbacks return a value to a caller under particular circumstances. Calling 'invoke on a Button,CheckButton or RadioButton command will run the widget's callback and return its result. Calling 'validate on an Entry or Spinbutton returns a boolean (0 or 1).
Here's an example of widget contruction using (tk ...) calls to create a frame and interior button:
(define frm (tk 'frame ".frm")) ;; frm -> ".frm" (define frm.btn (tk 'button (string-append frm ".btn") -text "Press me" '-command '(lambda () (set! topvar #t) ...)))
Several builtin procedures are provided to make programming with tk more convenient:
[procedure] tk/nm widget wpath '-option ...- widget - symbol or string, the type of widget to create: 'button, 'ttk::entry, etc.
- wpath - Tcl window path.
- wpath may be a string, ".form0.entry1", or a list of symbols, '(form0 entry1) or `(,form1 btn2). (The essential dots are automatically inserted into the consructed window path name if not part of the specified path elements. IOW the path could also be written as `(,form1 .btn2), but never necessary to do so.)
- '-option ... - options vary among widget, consult the TclTk documentation.
[procedure] tk/label wpath "label text" '-option ...
[procedure] tk/labelframe wpath "label text" '-option ...
[procedure] tk/button wpath "button text" '-option ...
[procedure] tk/entry wpath "width" '-option ...
[procedure] tk/spinbox wpath '-option ...
[procedure] tk/chkbtn wpath "label text" '-option ...
[procedure] tk/grid wpath 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* is for grid subcommands. tk/grid* takes the grid subcommand name, the window to which it's applied 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.
Using a few of these additional capabilities, this is another way to write 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 0 1 '-px 22 '-py "9 19") (tk 'wm 'deiconify ".")
Given the multitude of widget (and Tcl/Tk command) options, 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 can also be downloaded from the same site.
Configuration
As of tkgui v0.8.0 changes have been made to configuration options and format of configuration files. However, the original format still works provided files are edited to update option names and values.
Specifically, these options have been completely removed: app-01, csc_stublibs. Options with changed names (from/to): exelib/libdir, libchkn/libchicken, cflag/cflags, csc-flag csc-cflag/csc-cflags. The dllflag option now has single value, '-shared' or '-mdll', '-o' is no longer included.
The new configuration format is simpler: each option is a Scheme proper list rather than dotted pair. An advantage is uniformity. All config values are parsed as lists, and subsequently transformed into strings to comply with the requirements of subprocess or system shell calls. However as noted above mktkgui will continue to work with the "old style" configs.
Callback support for TclTk elements has been expanded to include 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 these callbacks have particular TclTk requirements for return values, described in detail below.
Writing configuration files
mktkgui has platform-specific built-in configurations which are overridden by key/value settings contained in config files. Config files are per-directory, each source directory can (and should) have its own config file. A per-directory config file named "ext-config.scm" is automatically used. However any number of configs can exists in a directory. The -config command line option is used to specify the appropriate config file.
Config files are expected to contain a Scheme list whose elements are option lists. The first element (car) of the option list is its key. When a key (a symbol) matches a default key the list of elements after the first element (cdr) of the option list replaces the default value. A config value is therefore always a list which may be null, or contain one or more strings, symbols, booleans, or numbers.
Defaults are printed to the terminal using mktkgui -defaults. Configured option values are printed by invoking mktkgui -settings.
The option 'static-egg-libs' determines how extension (egg) libs are linked: as static or shared objects. By default 'static-egg-libs' is #f, that is, shared linking is the initial setting. Inserting (static-egg-libs . #t) in a local config file enables linking statically. Alternatively, the mktkgui command line -static option can be used to activate static egg library linking without changing the default. (-static merely inverts 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 in the egg's .egg file. Shared linking works with unmodified eggs and those compiled with -fPIC.
Example config:
((cc /usr/local/bin/gcc)
(csc /usr/local/bin/csc)
(includes -I. -I/usr/local/include/chicken -I/usr/local/include)
(csclinkdir /usr/local/lib/chicken/12)
(imports myegg my-other-egg)
...
(static-egg-libs #t)
(config-check =gui-prog/gui-config.scm=))
This config sets static egg linking to true by default. With this config using -static on the mktkgui command line turns off static linking. Using -static '#f' guarantees the option is off. With -static '#t' the option is always on regardless of config setting. (The same is true for -gui and -verbose options.)
Builtin options
Builtins are overridden by options in per-directory config file(s).
Unix defaults
cc /usr/bin/gcc cflags -Og -g -fPIC config-check "unix defaults" csc /usr/local/bin/csc csc-cflags -C -g -C -fPIC csclinkdir "." defs -DUSE_TCL_STUBS -DUSE_TK_STUBS dll .so dllflag -shared exe exedir /usr/local/bin 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 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 defaults
cc c:/w64devkit/bin/gcc.exe cflags -Og -g config-check "windows defaults" csc c:/usr/local/bin/csc.exe csc-cflags csclinkdir "." defs -DUSE_TCL_STUBS -DUSE_TK_STUBS dll .dll dllflag -mdll exe .exe exedir c:/usr/local/bin 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 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 include resources like icons, cursors and metadata in 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. Use the Tk icon in source distribution as a template, replace the default Tk icon with the 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 names like TK_LIB_FLAG -ltcl9tk9.1x. Put the custom tk.ico in the rc directory. Compiling as normal should give 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.0 Added (tk/*) procedures, revised configuration, expanded TclTk coverage, perforamance 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