Tryst
The Tcl-major-version auto-detection probe (TCL_VERSION=8/9 forces a
choice; anything else auto-detects - see interp.cr's header comment for
the full policy and why this needs to be a shell probe at all) used to
be hand-copied verbatim at every {% if %}/{% unless %} site that needs
it: twice in interp.cr, once in event_source.cr. Crystal macro locals
don't persist across separate top-level {% %} blocks (confirmed
directly - a {% x = ... %} in one block is invisible to the next),
so sharing a computed result isn't an option. A plain Crystal
constant doesn't have that limitation, though, and a macro backtick
literal accepts #{...} interpolation of one directly - `#{X.id}`
splices X's raw text into the backtick before it runs - so the SCRIPT
TEXT lives here exactly once, and every call site becomes an identical
one-liner: `#{Tryst::TCL_VERSION_PROBE.id}`.stringify.chomp == "9"
(still re-executed at every site - that part of the duplication is a
real Crystal limitation, not a style choice - just no longer
re-typed, so a bug in the probe itself only needs fixing once).
Two variants, chosen once here rather than at every call site, for the
same reason expand_lib_flags's Windows @[Link] ldflags need their own
variant (see tcltk_link_windows.cr's header comment for the full
explanation): a macro backtick literal evaluates through the same
Process.run(shell: true) that never goes through an actual shell on
Windows, so the POSIX case/if script below has to be handed to a real
sh.exe explicitly there, with pkg-config resolved relative to the
running crystal binary rather than trusted from PATH search order
(a stale, unrelated MSYS/Cygwin install earlier in Machine PATH will
otherwise silently win and misreport - confirmed directly).
Constants
Which Tcl/Tk major version this build was compiled to link against - see the @[Link] lines at the top of this file for why that has to be a compile-time choice rather than something one binary picks at runtime, and for what TCL_VERSION=8/TCL_VERSION=9/auto-detect actually do. Has to compute the exact same answer as every {% if %} above, so it runs the identical probe rather than just hardcoding a default here
- see the @[Link] block's own comment for what it checks.
Interp#initialize cross-checks this against the version the runtime library actually reports and raises a friendly TclError on a mismatch
- the lookups the @[Link] ldflags do to find the right library are heuristic (a system with both installed could resolve the "wrong" one, particularly on the 9.x arm - see the comment there), so this is the backstop that turns a silent ABI mismatch into an immediate, readable error instead of undefined behavior the first time a version-sensitive call is made.
Tk widget-creation commands #command/#create_widget recognize - used to auto-record a widget's type at its path (#record_widget_type) and to decide where a Proc-valued kwarg's ownership is scoped (#track_widget_option_callbacks). Mirrors ruby-tryst's Tryst::WIDGET_COMMANDS (lib/tryst.rb). A Set, not a list: every #command call asks whether the command it was handed is one of these, twice.
Class methods
Converts a Ruby-ish truthy/falsy value to a Tcl boolean string ("1" or "0") - only nil/false are falsy, same as Crystal's own if/ternary truthiness. Pure Crystal, no Tcl interpreter involved. Mirrors ruby-tryst's Tryst.bool_to_tcl (lib/tryst.rb).
Tcl's internal string representation never contains a raw NUL byte - an embedded NUL is instead encoded as the two bytes 0xC0 0x80, Tcl's "modified UTF-8" (the same trick Java uses internally). 0xC0/0xC1 can never start a real UTF-8 sequence (both would only ever produce an overlong encoding, which UTF-8 forbids), so any 0xC0 byte here is unambiguously this escape, never misread genuine UTF-8 content. Shared by Interp#obj_to_string and tryst_crystal_callback_dispatch's id/arg extraction (interp.cr, below) - the two boundaries where Tcl hands string bytes back to Crystal.
Whether THIS fiber is the one running the open callback - the question App#off_thread actually needs answered, and a different one from .in_callback?: a fiber spawned from inside a callback is scheduled while that callback is still open (the moment the callback's own fiber suspends - see Interp#spin_until), so .in_callback? reads true on it too, but its C stack is inside no Tcl call at all and it must not act as if it were.
Same as the splat overload above, for a runtime-sized collection (e.g. already have an Array(String) in hand, rather than individual arguments) - a splat can't be applied to a runtime Array directly (verified directly: "argument to splat must be a tuple"), the same reason Interp#tcl_invoke has this same pair of overloads.
A typed splat (*args : String) rejects a zero-argument call outright
(verified directly) - unlike an untyped splat, which just yields an
empty Enumerable - so the empty case needs its own overload instead of
an args.empty? guard inside the main one below.
Builds a properly Tcl-quoted list string from the given elements (only where quoting is actually needed - Tcl's own list-formatting rules). Mirrors ruby-tryst's Tryst.make_list (ext/tryst/tcltkbridge.c).
:root as a widget/window target, resolved to Tk's own "." spelling - the one raw-path lore App's otherwise-untyped widget/window/path parameters still forced on a caller. Anything else (a Widget, a raw path String) passes through via #to_s exactly as it always has.
Parses a Tcl list string into an array of strings - does not recursively parse nested lists. nil/empty input returns an empty array. Mirrors ruby-tryst's Tryst.split_list (ext/tryst/tcltkbridge.c).
Nested types
- Tryst::AfterHandle
- Tryst::AmbiguousCommandError
- Tryst::App
- Tryst::BackgroundControl
- Tryst::BackgroundDone
- Tryst::BackgroundFailure
- Tryst::BackgroundMessage
- Tryst::BackgroundProgress(Result)
- Tryst::BackgroundUserMessage
- Tryst::BackgroundWork(Data, Result)
- Tryst::CallbackRegistry(App)
- Tryst::CallbackSignal
- Tryst::CanvasBindInterceptor
- Tryst::Clipboard
- Tryst::CommandInterceptors
- Tryst::Easing
- Tryst::ErrorHandler
- Tryst::ErrorPolicy
- Tryst::EventArg
- Tryst::EventSource
- Tryst::EventSpec
- Tryst::FileTypes
- Tryst::FontHandle
- Tryst::Interp
- Tryst::MenuInterceptor
- Tryst::NativeWindow
- Tryst::NativeWindowKind
- Tryst::NotViewableError
- Tryst::Notifier
- Tryst::OffThreadWorker
- Tryst::OwnerDrawnWidget
- Tryst::Photo
- Tryst::PhotoComposite
- Tryst::PixelFormat
- Tryst::Platform
- Tryst::RepeatingTimer
- Tryst::Style
- Tryst::SubsArg
- Tryst::TagBindInterceptor
- Tryst::TaskContext(Result)
- Tryst::TclArgValue
- Tryst::TclError
- Tryst::Theme
- Tryst::Tween
- Tryst::UI
- Tryst::Widget
- Tryst::WidgetInfo
- Tryst::Window
- Tryst::Winfo
- Tryst::WrongThreadError