1Tcl/Tk macOS README
2----------------------
3
4This is the README file for the macOS/Darwin version of Tcl/Tk.
5
61. Where to go for support
7--------------------------
8
9- The tcl-mac mailing list on sourceforge is the best place to ask questions
10specific to Tcl & Tk on macOS:
11 http://lists.sourceforge.net/lists/listinfo/tcl-mac
12(this page also has a link to searchable archives of the list, please check them
13before asking on the list, many questions have already been answered).
14
15- For general Tcl/Tk questions, the newsgroup comp.lang.tcl is your best bet:
16 http://groups.google.com/group/comp.lang.tcl/
17
18- The Tcl'ers Wiki also has many pages dealing with Tcl & Tk on macOS, see
19 https://wiki.tcl-lang.org/page/MacOS
20
21- Please report bugs with Tk on macOS to the tracker:
22 https://core.tcl-lang.org/tk/reportlist
23
242. Using Tcl/Tk on macOS
25---------------------------
26
27- There are two versions of Tk available on macOS: TkAqua using the native
28aqua widgets and look&feel, and TkX11 using the traditional unix X11 widgets.
29TkX11 requires an X11 server to be installed, such as XQuartz (available from www.xquartz.org).
30TkAqua and TkX11 can be distinguished at runtime via [tk windowingsystem].
31
32- At a minimum, macOS 10.3 is required to run Tcl and TkX11.
33TkAqua requires macOS 10.6 or later.
34
35- Unless weak-linking is used, Tcl/Tk built on macOS 10.x will not run on
3610.y with y < x; on the other hand Tcl/Tk built on 10.y will always run on 10.x
37with y <= x (but without any of the fixes and optimizations that would be
38available in a binary built on 10.x).
39Weak-linking is available on OS X 10.2 or later, it additionally allows Tcl/Tk
40built on 10.x to run on any 10.y with x > y >= z (for a chosen z >= 2).
41
42- Wish checks the Resources/Scripts directory in its application bundle for a
43file called AppMain.tcl, if found it is used as the startup script and the
44Scripts folder is added to the auto_path. This can be used to emulate the old
45OS9 TclTk droplets.
46
47- If standard input is a special file of zero length (e.g. /dev/null), Wish
48brings up the Tk console window at startup. This is the case when double
49clicking Wish in the Finder (or using 'open Wish.app' from the Terminal).
50
51- Tcl extensions can be installed in any of:
52 $HOME/Library/Tcl /Library/Tcl
53 $HOME/Library/Frameworks /Library/Frameworks
54 (searched in that order).
55Given a potential package directory $pkg, Tcl on OSX checks for the file
56$pkg/Resources/Scripts/pkgIndex.tcl as well as the usual $pkg/pkgIndex.tcl.
57This allows building extensions as frameworks with all script files contained in
58the Resources/Scripts directory of the framework.
59
60- The 'deploy' target of macosx/GNUmakefile installs the html manpages into the
61standard documentation location in the Tcl/Tk frameworks:
62 Tcl.framework/Resources/Documentation/Reference/Tcl
63 Tk.framework/Resources/Documentation/Reference/Tk
64No nroff manpages are installed by default by the GNUmakefile.
65
66- The Tcl and Tk frameworks can be installed in any of the system's standard
67framework directories:
68 $HOME/Library/Frameworks /Library/Frameworks
69
70- ${prefix}/bin/wish8.x is a script that calls a copy of 'Wish' contained in
71 Tk.framework/Resources
72
73- if 'Wish' is started from the Finder or via 'open', $argv may contain a
74"-psn_XXXX" argument. This is the process serial number, you may need to filter
75it out for cross platform compatibility of your scripts.
76
77- the env array is different when Wish is started from the Finder (i.e. via
78LaunchServices) than when it (or tclsh) is invoked from the Terminal, in
79particular PATH may not be what you expect. (Wish started by LaunchServices
80inherits loginwindow's environment variables, which are essentially those set in
81$HOME/.MacOSX/environment.plist, and are unrelated to those set in your shell).
82
83- TkAqua provides access to native OS X images via the Tk native bitmap facility
84(including any image file readable by NSImage). A native bitmap name is
85interpreted as follows (in order):
86 - predefined builtin 32x32 icon name (stop, caution, document, etc)
87 - name defined by [tk::mac::iconBitmap]
88 - NSImage named image name
89 - NSImage url string
90 - 4-char OSType of IconServices icon
91the syntax of [tk::mac::iconBitmap] is as follows:
92 tk::mac::iconBitmap name width height -kind value
93where -kind is one of
94 -file icon of file at given path
95 -fileType icon of given file type
96 -osType icon of given 4-char OSType file type
97 -systemType icon for given IconServices 4-char OSType
98 -namedImage named NSImage for given name
99 -imageFile image at given path
100This support was added with the Cocoa-based Tk 8.5.7.
101
102- TkAqua cursor names are interpred as follows (in order):
103 - standard or platform-specific Tk cursor name (c.f. cursors.n)
104 - @path to any image file readable by NSImage
105 - NSImage named image name
106Support for the latter two was added with the Cocoa-based Tk 8.5.7.
107
108- The standard Tk dialog commands [tk_getOpenFile], [tk_chooseDirectory],
109[tk_getSaveFile] and [tk_messageBox] all take an additional optional -command
110parameter on TkAqua. If it is present, the given command prefix is evaluated at
111the global level when the dialog closes, with the dialog command's result
112appended (the dialog command itself returning an emtpy result). If the -parent
113option is also present, the dialog is configured as a modeless (window-modal)
114sheet attached to the parent window and the dialog command returns immediately.
115Support for -command was added with the Cocoa-based Tk 8.5.7.
116
117- The TkAqua-specific [tk::mac::standardAboutPanel] command brings the standard
118Cocoa about panel to the front, with all its information filled in from your
119application bundle files (i.e. standard about panel with no options specified).
120See Apple Technote TN2179 and the AppKit documentation for -[NSApplication
121orderFrontStandardAboutPanelWithOptions:] for details on the Info.plist keys and
122app bundle files used by the about panel.
123This support was added with the Cocoa-based Tk 8.5.7.
124
125- TkAqua has three special menu names that give access to the standard
126Application, Window and Help menus, see menu.n for details. By default, the
127platform-specific standard Help menu item "YourApp Help" performs the default
128Cocoa action of showing the Help Book configured in the application's
129Info.plist (or displaying an alert if no Help Book is set). This action can be
130customized by defining a procedure named [tk::mac::ShowHelp]. If present, this
131procedure is invoked instead by the standard Help menu item. Support for the
132Window menu and [tk::mac::ShowHelp] was added with the Cocoa-based Tk 8.5.7.
133
134- The TkAqua-specific command [tk::unsupported::MacWindowStyle style] is used to
135get and set macOS-specific toplevel window class and attributes. Note that
136the window class and many attributes have to be set before the window is first
137mapped for the change to have any effect.
138The command has the following syntax:
139 tk::unsupported::MacWindowStyle style window ?class? ?attributes?
140The 2 argument form returns a list of the current class and attributes for the
141given window. The 3 argument form sets the class for the given window using the
142default attributes for that class. The 4 argument form sets the class and the
143list of attributes for the given window.
144Window class names:
145 document, modal, floating, utility, toolbar, simple, help, overlay
146Window attribute names:
147 standardDocument, standardFloating, resizable, fullZoom, horizontalZoom,
148 verticalZoom, closeBox, collapseBox, toolbarButton, sideTitlebar,
149 noTitleBar, unifiedTitleAndToolbar, metal, hud, noShadow, doesNotCycle,
150 noActivates, hideOnSuspend, inWindowMenu, ignoreClicks, doesNotHide,
151 canJoinAllSpaces, moveToActiveSpace, nonActivating
152
153Note that not all attributes are valid for all window classes. Support for the
1543 argument form was added with the Cocoa-based Tk 8.5.7, at the same time
155support for some legacy Carbon-specific classes and attributes was removed
156(they are still accepted by the command but no longer have any effect).
157
158- Another command available in the tk::unsupported::MacWindowStyle namespace is:
159 tk::unsupported::MacWindowStyle tabbingid window ?newId?
160which can be used to get or set the tabbingIdentifier for the NSWindow
161associated with a Tk Window. See section 3 for details.
162
163- The command:
164 tk::unsupported::MacWindowStyle appearance window ?newAppearance?
165is available when Tk is built and run on macOS 10.14 (Mojave) or later. In
166that case the Ttk widgets all support the "Dark Mode" appearance which was
167introduced in 10.14. The command accepts the following values for the optional
168newAppearance option: "aqua", "darkaqua", or "auto". If the appearance is set
169to aqua or darkaqua then the window will be displayed with the corresponding
170appearance independent of any preferences settings. If it is set to "auto"
171the appearance will be determined by the preferences. This command can be
172used to opt out of Dark Mode on a per-window basis. It may be best to run the "update" command before setting the appearance property, to allow the event loop to run.
173
174
175- To determine the current appearance of a window in macOS 10.14 (Mojave) and
176higher, one can use the command:
177 tk::unsupported::MacWindowStyle isdark window?
178The boolean return value is true if the window is currently displayed with the
179dark appearance.
180
181- If you want to use Remote Debugging with Xcode, you need to set the
182environment variable XCNOSTDIN to 1 in the Executable editor for Wish. That will
183cause us to force closing stdin & stdout. Otherwise, given how Xcode launches
184Wish remotely, they will be left open and then Wish & gdb will fight for stdin.
185
1863. FullScreen, Split View and Tabbed Windows
187--------------------------------------------
188
189Since the release of OSX 10.6 (Snow Leopard) a steadily expanding sequence of
190high level window operations have been added to Apple's window manager. These
191operations are launched by user actions which are handled directly by the
192window manager; they are not initiated by the application. In some, but not
193all cases, the application is notified before and after the operations are
194carried out.
195
196In OSX releases up to and including 10.6 there were three buttons with
197stoplight colors located on the left side of a window's title bar. The
198function of the green button was to "zoom" or "maximize" the window, i.e. to
199expand the window so that it fills the entire screen, while preserving the
200appearance of the window including its title bar. The release of OSX 10.7
201(Lion) introduced the "FullScreen" window which not only filled the screen but
202also hid the window's title bar and the menu bar which normally appears at the
203top of the screen. These hidden objects would only become visible when the
204mouse hovered near the top of the screen. FullScreen mode was initiated by
205pressing a button showing two outward pointing arrows located on the right side
206of the title bar; it was terminated by pressing a similar button with inward
207pointing arrows on the right hand side of the menu bar. In OSX 10.10
208(Yosemite) the FullScreen button was removed. The green button was repurposed
209to cause a window to become a FullScreen window. To zoom a window the user had
210to hold down the option key while pressing the green button. The release of
211OSX 10.11 added a third function to the green button: to create two half-screen
212windows with hidden title bars and a hidden menu bar, called Split View
213windows. If the green button is held down for one second its window expands to
214fill half of the screen. It can be moved to one side or the other with the
215mouse. The opposite side shows thumbnail images of other windows. Selecting
216one of the thumbnails expands its window to fill that half of the screen. The
217divider between the two windows can be moved to adjust the percentage of the
218screen occupied by each of the two tiles. In OSX 10.12 (Sierra) Tabbed windows
219were introduced. These allow an application with multiple windows to display
220its windows as tabs within a single window frame. Clicking on a tab brings its
221window into view. Tabs can be rearranged by dragging. Dragging a tab to the
222desktop turns it into a separate window. Items in the Window menu can be used
223to cycle through the tabs, move tabbed windows to separate windows, or merge a
224set of separate windows as tabs in the same window frame.
225
226Tk now fully supports all of these high level window operations on any system
227where the operation exists. The FullScreen and Split View windows are handled
228automatically with no action required on the part of the programmer. Tabbed
229windows, on the other hand, require some attention from the programmer.
230Because many of the operations with tabs are handled through the application's
231Window menu, it is essential that an application provide a Windows menu to
232avoid presenting a confusing interface to the user. This cannot be ignored, in
233part because the systemwide Dock Preferences offers an option to always attempt
234to open application windows as tabs. An application which does not provide a
235Window menu will necessarily present a confusing interface to any user who has
236selected this option.
237
238A further complication is that it is not neccessarily appropriate for all of an
239application's windows to be grouped together as tabs in the same frame. In
240fact, the Apple guidelines insist that windows which are grouped together as
241tabs should be similar to each other. The mechanism provided for arranging
242this was to assign to each NSwindow a tabbingIdentifier, and to require that
243all windows grouped together as tabs in the same window frame must have the
244same tabbingIdentifier. A tabbingIdentifier is implemented as an arbitrary
245string, and a system-generated default tabbingIdentifier is provided to all new
246windows.
247
248Tk provides a means for getting and setting the tabbingIdentifier of
249the NSWindow underlying a Tk Window. This is handled by the command
250
251tk::unsupported::MacWindowStyle tabbingid window ?newId?
252
253(This command generates an error if used on OSX 10.11 or earlier, since the
254tabbingIdentifier does not exist on those systems.) The command returns the
255tabbingIdentifier which had been assigned to the window prior to execution of
256the command. If the optional newId argument is omitted, the window's
257tabbingIdentifier is not changed. Otherwise it is set to the string specified
258by the argument.
259
260Since NSWindows can only be grouped together as tabs if they all have the same
261tabbingIdentifier, one can prevent a window from becoming a tab by giving it a
262unique tabbingIdentifier. This is independent of any preferences setting. To
263ensure that we maintain consistency, changing the tabbingIdentifier of a window
264which is already displayed as a tab will also cause it to become a separate
265window.
266
2674. Ttk, Dark Mode and semantic colors
268---------------------------------------
269
270With the release of OSX 10.14 (Mojave), Apple introduced the DarkAqua
271appearance. Part of the implementation of the Dark Mode was to make
272some of the named NSColors have dynamic values. Apple calls these
273"semantic colors" because the name does not specify a specific color,
274but rather refers to the context in which the color should be used.
275Tk now provides the following semantic colors as system colors:
276systemTextColor, systemTextBackgroundColor, systemSelectedTextColor,
277systemSelectedTextBackgroundColor, systemControlTextColor,
278systemDisabledControlTextColor, systemLabelColor, systemLinkColor, and
279systemControlAccentColor. All of these except the last three were
280present in OSX 10.0 (and those three are simulated in systems where they
281do not exist). The change in 10.14 was that the RGB color value of
282these colors became dynamic, meaning that the color value can change
283when the application appearance changes. In particular, when a user
284selects Dark Mode in the system preferences these colors change
285appearance. For example systemTextColor is dark in Aqua and light in
286DarkAqua. One additional color, systemSelectedTabTextColor, does not
287exist in macOS but is used by Tk to match the different colors used
288for Notebook tab text in different OS versions.
289
290The default background and foreground colors of most of the Tk widgets
291have been set to semantic colors, which means that the widgets will change
292appearance, and remain usable, when Dark Mode is selected in the system
293preferences. However, to get a close match to the native Dark Mode style it
294is recommended to use Ttk widgets when possible.
295
296Apple's tab view and GroupBox objects delimit their content by
297displaying it within a rounded rectangle with a background color that
298contrasts with the background of the containing object. This means
299that the background color of a Ttk widget depends on how deeply it is
300nested inside of other widgets that use contrasting backgrounds. To
301support this, there are 8 contrasting system colors named
302systemWindowBackgroundColor, and systemWindowBackgroundColor1 - 7.
303The systemWindowBackgroundColor is the standard background for a
304dialog window and the others match the contrasting background colors
305used in ttk::notebooks and ttk::labelframes which are nested to the
306corresponding depth.
307
3085. Building Tcl/Tk on macOS
309------------------------------
310
311- macOS 10.6 is required to build TkAqua and TkX11. The XCode application provides everything needed to build Tk, but it is not necessary to install the full XCode.
312It suffices to install the Command Line Tools package, which can be done
313by running the command:
314xcode-select --install
315
316- Tcl/Tk are most easily built as macOS frameworks via GNUmakefile in
317tcl/macosx and tk/macosx (see below for details), but can also be built with the
318standard unix configure and make buildsystem in tcl/unix resp. tk/unix as on any
319other unix platform (indeed, the GNUmakefiles are just wrappers around the unix
320buildsystem).
321The macOS specific configure flags are --enable-aqua, --enable-framework and
322--disable-corefoundation (which disables CF and notably reverts to the standard
323select based notifier). Note that --enable-aqua is incompatible with
324--disable-corefoundation (for both Tcl and Tk configure).
325
326- It was once possible to build with the Xcode IDE via the projects in
327tk/macosx, but this has not been tested recently. Take care to use the
328project matching your DevTools and OS version:
329 Tk.xcode: for Xcode 3.1 on 10.5
330 Tk.xcodeproj: for Xcode 3.2 on 10.6
331These have the following targets:
332 Tk: calls through to tk/macosx/GNUMakefile,
333 requires a corresponding build of the Tcl
334 target of tcl/macosx/Tcl.xcode.
335 tktest: static build of TkAqua tktest for debugging.
336 tktest-X11: static build of TkX11 tktest for debugging.
337The following build configurations are available:
338 Debug: debug build for the active architecture,
339 with Fix & Continue enabled.
340 Debug clang: use clang compiler.
341 Debug llvm-gcc: use llvm-gcc compiler.
342 Debug gcc40: use gcc 4.0 compiler.
343 DebugNoGC: disable Objective-C garbage collection.
344 DebugNoFixAndContinue: disable Fix & Continue.
345 DebugUnthreaded: disable threading.
346 DebugNoCF: disable corefoundation (X11 only).
347 DebugNoCFUnthreaded: disable corefoundation an threading.
348 DebugMemCompile: enable memory and bytecode debugging.
349 DebugLeaks: define PURIFY.
350 DebugGCov: enable generation of gcov data files.
351 Debug64bit: configure with --enable-64bit (requires
352 building on a 64bit capable processor).
353 Release: release build for the active architecture.
354 ReleaseUniversal: 32/64-bit universal build.
355 ReleaseUniversal clang: use clang compiler.
356 ReleaseUniversal llvm-gcc: use llvm-gcc compiler.
357 ReleaseUniversal gcc40: use gcc 4.0 compiler.
358 ReleaseUniversal10.5SDK: build against the 10.5 SDK (with 10.5
359 deployment target).
360 Note that the non-SDK configurations have their deployment target set to
361 10.5 (Tk.xcode) resp. 10.6 (Tk.xcodeproj).
362The Xcode projects refer to the toplevel tcl and tk source directories via the
363the TCL_SRCROOT and TK_SRCROOT user build settings, by default these are set to
364the project-relative paths '../../tcl' and '../../tk', if your source
365directories are named differently, e.g. '../../tcl8.6' and '../../tk8.6', you
366need to manually change the TCL_SRCROOT and TK_SRCROOT settings by editing your
367${USER}.pbxuser file (located inside the Tk.xcodeproj bundle directory) with a
368text editor.
369
370- To enable weak-linking, set the MACOSX_DEPLOYMENT_TARGET environment variable
371to the minimal OS version the binaries should be able to run on, e.g:
372 export MACOSX_DEPLOYMENT_TARGET=10.6
373This requires at least gcc 3.1; with gcc 4 or later, set/add to CFLAGS instead:
374 export CFLAGS="-mmacosx-version-min=10.6"
375Support for weak-linking was added with 8.4.14/8.5a5.
376
377Detailed Instructions for building with macosx/GNUmakefile
378----------------------------------------------------------
379
380- Unpack the Tcl and Tk source release archives and place the tcl and tk source
381trees in a common parent directory.
382[ If you don't want have the two source trees in one directory, you'll need to ]
383[ create the following symbolic link for the build to work as setup by default ]
384[ ln -fs /path_to_tcl/build /path_to_tk/build ]
385[ (where /path_to_{tcl,tk} is the directory containing the tcl resp. tk tree) ]
386[ or you can pass an argument of BUILD_DIR=/somewhere to the tcl and tk make. ]
387
388- The following instructions assume the Tcl and Tk source trees are named
389"tcl${ver}" and "tk${ver}" (where ${ver} is a shell variable containing the
390Tcl/Tk version number, e.g. '8.6').
391Setup this shell variable as follows:
392 ver="8.6"
393If you are building from CVS, omit this step (CVS source tree names usually do
394not contain a version number).
395
396- Setup environment variables as desired, e.g. for a universal build on 10.5:
397 CFLAGS="-arch i386 -arch x86_64 -arch ppc -mmacosx-version-min=10.5"
398 export CFLAGS
399
400- Change to the directory containing the Tcl and Tk source trees and build:
401 make -C tcl${ver}/macosx
402 make -C tk${ver}/macosx
403
404- Install Tcl and Tk onto the root volume (admin password required):
405 sudo make -C tcl${ver}/macosx install
406 sudo make -C tk${ver}/macosx install
407if you don't have an admin password, you can install into your home directory
408instead by passing an INSTALL_ROOT argument to make:
409 make -C tcl${ver}/macosx install INSTALL_ROOT="${HOME}/"
410 make -C tk${ver}/macosx install INSTALL_ROOT="${HOME}/"
411
412- The default GNUmakefile targets will build _both_ debug and optimized versions
413of the Tcl and Tk frameworks with the standard convention of naming the debug
414library Tcl.framework/Tcl_debug resp. Tk.framework/Tk_debug.
415This allows switching to the debug libraries at runtime by setting
416 export DYLD_IMAGE_SUFFIX=_debug
417(c.f. man dyld for more details)
418
419If you only want to build and install the debug or optimized build, use the
420'develop' or 'deploy' target variants of the GNUmakefile, respectively.
421For example, to build and install only the optimized versions:
422 make -C tcl${ver}/macosx deploy
423 make -C tk${ver}/macosx deploy
424 sudo make -C tcl${ver}/macosx install-deploy
425 sudo make -C tk${ver}/macosx install-deploy
426
427- The GNUmakefile can also build a version of Wish.app that has the Tcl and Tk
428frameworks embedded in its application package. This allows for standalone
429deployment of the application with no installation required, e.g. from read-only
430media. To build & install in this manner, use the 'embedded' variants of
431the GNUmakefile targets.
432For example, to build a standalone 'Wish.app' in ./emb/Applications/Utilities:
433 make -C tcl${ver}/macosx embedded
434 make -C tk${ver}/macosx embedded
435 sudo make -C tcl${ver}/macosx install-embedded INSTALL_ROOT=`pwd`/emb/
436 sudo make -C tk${ver}/macosx install-embedded INSTALL_ROOT=`pwd`/emb/
437Notes:
438 * if you've already built standard TclTkAqua, building embedded does not
439 require any new compiling or linking, so you can skip the first two makes.
440 (making relinking unnecessary was added with 8.4.2)
441 * the embedded frameworks include only optimized builds and no documentation.
442 * the standalone Wish has the directory Wish.app/Contents/lib in its
443 auto_path. Thus you can place tcl extensions in this directory (i.e. embed
444 them in the app package) and load them with [package require].
445
446- It is possible to build Tk against an installed Tcl.framework; but you will
447still need a tcl sourcetree in the location specified in TCL_SRC_DIR in
448Tcl.framework/tclConfig.sh. Also, linking with Tcl.framework has to work exactly
449as indicated in TCL_LIB_SPEC in Tcl.framework/tclConfig.sh.
450If you used non-default install locations for Tcl.framework, specify them as
451make overrides to the tk/macosx GNUmakefile, e.g.
452 make -C tk${ver}/macosx \
453 TCL_FRAMEWORK_DIR=$HOME/Library/Frameworks TCLSH_DIR=$HOME/usr/bin
454 sudo make -C tk${ver}/macosx install \
455 TCL_FRAMEWORK_DIR=$HOME/Library/Frameworks TCLSH_DIR=$HOME/usr/bin
456The Makefile variables TCL_FRAMEWORK_DIR and TCLSH_DIR were added with Tk 8.4.3.
457
458- To build a Tcl.framework and Tk.framework for use as subframeworks in another
459framework, use the install-embedded target and set SUBFRAMEWORK=1. Set the
460DYLIB_INSTALL_DIR variable to the path which should be the install_name path of
461the shared library and set the DESTDIR variable to the pathname of a staging
462directory where the frameworks will be written. The Tcl framework must be
463built first.
464For example, running the commands:
465 make -C ../tcl8.6/macosx install-embedded SUBFRAMEWORK=1 DESTDIR=/tmp/tcltk \
466 DYLIB_INSTALL_DIR=/Library/Frameworks/Some.framework/Versions/X.Y/Frameworks/Tcl.framework
467 make -C macosx install-embedded SUBFRAMEWORK=1 DESTDIR=/tmp/tcltk \
468 DYLIB_INSTALL_DIR=/Library/Frameworks/Some.framework/Versions/X.Y/Frameworks/Tk.framework
469will produce a Tcl.framework and a Tk.framework usable as subframeworks of
470Some.framework. The frameworks will be found in /tmp/tcltk/Frameworks/
471
4725. Details regarding the macOS port of Tk.
473-------------------------------------------
474
4755.1 About the event loop
476~~~~~~~~~~~~~~~~~~~~~~~~
477
478The main program in a typical OSX application looks like this (see
479https://developer.apple.com/library/mac/documentation/Cocoa/\
480Reference/ApplicationKit/Classes/NSApplication_Class)
481
482 void NSApplicationMain(int argc, char *argv[]) {
483 [NSApplication sharedApplication];
484 [NSBundle loadNibNamed:@"myMain" owner:NSApp];
485 [NSApp run];
486 }
487Here NSApp is a standard global variable, initialized by the OS, which
488points to an object in a subclass of NSApplication (called
489TKApplication in the case of the macOS port of Tk).
490
491The [NSApp run] method implements the event loop for a typical Mac
492application. There are three key steps in the run method. First it
493calls [NSApp finishLaunching], which creates the bouncing application
494icon and does other mysterious things. Second it creates an
495NSAutoreleasePool. Third, it starts an event loop which drains the
496NSAutoreleasePool every time the queue is empty, and replaces the
497drained pool with a new one. This third step is essential to
498preventing memory leaks, since the internal methods of Appkit objects
499all assume that an autorelease pool is in scope and will be drained
500when the event processing cycle ends.
501
502The macOS Tk application does not call the [NSApp run] method at
503all. Instead it uses the event loop built in to Tk. So the
504application must take care to replicate the important features of the
505method ourselves. The way that autorelease pools are handled is
506discussed in 5.2 below. Here we discuss the event handling itself.
507
508The Tcl event loop simply consists of repeated calls to TclDoOneEvent.
509Each call to TclDoOneEvent begins by collecting all pending events from
510an "event source", converting them to Tcl events and adding them
511to the Tcl event queue. For macOS, the event source is the NSApp
512object, which maintains an event queue even though its run method
513will never be called to process them. The NSApp provides methods for
514inspecting the queue and removing events from it as well as the
515[NSApp sendevent] which sends an event to all of the application's
516NSWindows which can then send it to subwindows, etc.
517
518The event collection process consists of first calling a platform
519specific SetupProc and then a platform specific CheckProc. In
520the macOS port, these are named TkMacOSXEventsSetupProc and
521TkMacOSXEventsCheckProc.
522
523It is important to understand that the Apple window manager does not
524have the concept of an expose event. Their replacement for an expose
525event is to have the window manager call the [NSView drawRect] method
526in any situation where an expose event for that NSView would be
527generated in X11. The [NSView drawRect] method is a no-op which is
528expected to be overridden by any application. In the case of Tcl, the
529replacement [NSView drawRect] method creates a Tcl expose event
530for each dirty rectangle of the NSView, and then adds the expose
531event to the Tcl queue.
532
533
5345.2 Autorelease pools
535~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
536
537In order to carry out the job of managing autorelease pools, which
538would normally be handled by the [NSApp run] method, a private
539NSAutoreleasePool* property is added to the TkApplication subclass of
540NSApplication. The TkpInit function calls [NSApp _setup] which
541initializes this property by creating an NSAutoreleasePool prior to
542calling [NSApp finishLaunching]. This mimics the behavior of the
543[NSApp run] method, which calls [NSApp finishLaunching] just before
544starting the event loop.
545
546Since the CheckProc function gets called for every Tk event, it is an
547appropriate place to drain the main NSAutoreleasePool and replace it
548with a new pool. This is done by calling the method [NSApp
549_resetAutoreleasePool], where _resetAutoreleasePool is a method which
550we define for the subclass. Unfortunately, by itself this is not
551sufficient for safe memory managememt because, as was made painfully
552evident with the release of OS X 10.13, it is possible for calls to
553TclDoOneEvent, and hence to CheckProc, to be nested. Draining the
554autorelease pool in a nested call leads to crashes as objects in use
555by the outer call can get freed by the inner call and then reused later.
556One particular situation where this happens is when a modal dialogue
557gets posted by a Tk Application. To address this, the NSApp object
558also implements a semaphore to prevent draining the autorelease pool
559in nested calls to CheckProc.
560
561One additional minor caveat for developers is that there are several
562steps of the Tk initialization which precede the call to TkpInit.
563Notably, the font package is initialized first. Since there is no
564NSAutoreleasePool in scope prior to calling TkpInit, the functions
565called in these preliminary stages need to create and drain their own
566NSAutoreleasePools whenever they call methods of Appkit objects
567(e.g. NSFont).
568
5695.3 Clipping regions and "ghost windows"
570~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
571
572Another unusual aspect of the macOS port is its use of clipping
573regions. It was part of Daniel Steffen's original design that the
574TkWindowPrivate struct maintains three HIShapeRef regions, named
575visRgn, aboveVisRgn and drawRgn. These regions are used as clipping
576masks whenever drawing into an NSView. The visRgn is the bounding box
577of the window with a rectangle removed for each subwindow and for each
578sibling window at a higher stacking level. The drawRgn is the
579intersection of the visRgn with the clipping rectangle of the
580window. (Normally, the clipping rectangle is the same as the bounding
581rectangle, but drawing can be clipped to a smaller rectangle by
582calling TkpClipDrawableToRect.) The aboveVisRgn is the intersection of
583the window's bounding rectangle with the bounding rectangle of the
584parent window. Much of the code in tkMacOSXSubwindows.c is devoted to
585rebuilding these clipping regions whenever something changes in the
586layout of the windows. This turns out to be a tricky thing to do and
587it is extremely prone to errors which can be difficult to trace.
588
589It is not entirely clear what the original reason for using these
590clipping regions was. But one benefit is that if they are correctly
591maintained then it allows windows to be drawn in any order. You do
592not have to draw them in the order of the window hierarchy. Each
593window can draw its entire rectangle through its own mask and never
594have to worry about drawing in the wrong place. It is likely that
595the need for using clipping regions arose because, as Apple explicitly
596states in the documentation for [NSView subviews],
597
598 "The order of the subviews may be considered as being
599 back-to-front, but this does not imply invalidation and drawing
600 behavior."
601
602In the early versions of the macOS port, buttons were implemented as
603subviews of class TkButton. This probably exacerbated the likelihood
604that Tk windows would need to be drawn in arbitrary order.
605
606The most obvious side effect caused by not maintaining the clipping
607regions is the appearance of so-called "ghost windows". A common
608situation where these may arise is when a window containing buttons
609is being scrolled. A user may see two images of the same button on
610the screen, one in the pre-scroll location and one in the post-scroll
611location.
612
613To see how these 'ghost windows' can arise, think about what happens if
614the clipping regions are not maintained correctly. A window might
615have a rectangle missing from its clipping region because that
616rectangle is the bounding rectangle for a subwindow, say a button.
617The parent should not draw in the missing rectangle since doing so
618would trash the button. The button is responsible for drawing
619there. Now imagine that the button gets moved, say by a scroll, but
620the missing rectangle in the parent's clipping region does not get
621moved correctly, or it gets moved later on, after the parent has
622redrawn itself. The parent would still not be allowed to draw in the
623old rectangle, so the user would continue to see the image of the
624button in its old location, as well as another image in the new
625location. This is a prototypical example of a "ghost window".
626Anytime you see a "ghost window", you should suspect problems with the
627updates to the clipping region visRgn. It is natural to look for
628timing issues, race conditions, or other "event loop problems". But
629in fact, the whole design of the code is to make those timing issues
630irrelevant. As long as the clipping regions are correctly maintained
631the timing does not matter. And if they are not correctly maintained
632then you will see "ghost windows".
633
634It is worth including a detailed description of one specific place
635where the failure to correctly maintain clipping regions caused "ghost
636window" artifacts that plagued the macOS port for years. These
637occurred when scrolling a Text widget which contained embedded
638subwindows. It involved some specific differences between the
639low-level behavior of Apple's window manager versus those of the other
640platforms, and the fix ultimately required changes in the generic Tk
641implementation (documented in the comments in the DisplayText
642function).
643
644The Text widget attempts to improve perfomance when scrolling by
645minimizing the number of text lines which need to be redisplayed. It
646does this by calling the platform-specific TkScrollWindow function
647which uses a low-level routine to map one rectangle of the window to
648another. The TkScrollWindow function returns a damage region which is
649then used by the Text widget's DisplayText function to determine which
650text lines need to be redrawn. On the unix and win platforms, this
651damage region includes bounding rectangles for all embedded windows
652inside the Text widget. The way that this works is system dependent.
653On unix, the low level scrolling is done by XCopyRegion, which
654generates a GraphicsExpose event for each embedded window. These
655GraphicsExposed events are processsed within TkScrollWindow, using a
656special handler which adds the bounding rectangle of each subwindow to
657the damage region. On the win platform the damage region is built by
658the low level function ScrollWindowEx, and it also includes bounding
659rectangles for all embedded windows. This is possible because on X11
660and Windows every Tk widget is also known to the window manager as a
661window. The situation is different on macOS. The underlying object
662for a top level window on macOS is the NSView. However, Apple
663explicitly warns in its documentation that performance degradation
664occurs when an NSView has more than about 100 subviews. A Text widget
665with thousands of lines of text could easily contain more than 100
666embedded windows. In fact, while the original Cocoa port of Tk did
667use the NSButton object, which is derived from NSView, as the basis
668for its Tk Buttons, that was changed in order to improve performance.
669Moreover, the low level routine used for scrolling on macOS, namely
670[NSView scrollrect:by], does not provide any damage information. So
671TkScrollWindow needs to work differently on macOS. Since it would be
672inefficient to iterate through all embedded windows in a Text widget,
673looking for those which meet the scrolling area, the damage region
674constructed by TkScrollWindow contains only the difference between the
675source and destination rectangles for the scrolling. The embedded
676windows are redrawn within the DisplayText function by some
677conditional code which is only used for macOS.
678
6796.0 Virtual events on macOS 10.14 and later
680~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
681
682The 10.14 release added support for system appearance changes,
683including a "Dark Mode" that renders all window frames and menus in
684dark colors. Tk 8.6 provides three virtual events <<LightAqua>>,
685<<DarkAqua>> and <<AppearanceChanged>>, to allow you to update your Tk
686app's appearance when the system appearance changes. These events are
687generated in [NSView effectiveAppearanceChanged], which is called by
688the Apple window manager when the General Preferences is changed
689either by switching between Light Mode and Dark Mode or by changing
690the Accent Color or Highlight Color.
691
692The <<AppearanceChanged>> virtual event has a data string which can be
693accessed with the %d substitution. The format of the data string is
694that it consists of 6 words:
695 "Appearance XXXX Accent YYYY Highlight ZZZZ"
696For example, the following code will print the current appearance
697name, accent color and highlight color when the <<AppearanceChanged>>
698virtual event fires:
699
700bind . <<AppearanceChanged>> {
701 array set data [split %d]
702 puts " Appearance: $data(Appearance)"
703 puts " Accent: $data(Accent)"
704 puts " Highlight: $data(Highlight)\n"
705}
706
707
708
7097.0 Mac Services
710~~~~~~~~~~~~~~~~~~~~~~~~~~~
711
712With 8.6.10, Tk supports the Mac's NSServices API, documented at
713https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/SysServices/introduction.html#//apple_ref/doc/uid/10000101-SW1
714and in TIP 536 and Tk's man page. Tk presents a simple,
715straightforward API to implement the Services functionality.
716
717The Tk implementation of the NSServices API is intended for standalone
718applications, such as one wrapped by the standalone version of Wish
719and re-named into a different application. In particular such an
720application would specify its own unique CFBundleIdentifier in its
721Info.plist file. During development, however, if Wish itself is being
722used as the receiver, it may be necessary to take some care to ensure
723that the correct version of Wish.app is available as a receiver of
724NSServices data.
725
726When one macOS app uses NSServices to send data to another app that is
727not running, LaunchServices will launch the receiver. LaunchServices
728assumes that the CFBundleIdentifier uniquely identifies an app among
729all of the apps installed on a system. But this may not be the case
730for Wish.app if, for example, you have compiled Tk from source at some
731time in the past. In that case the Tk build directory will contain
732its own copy of Wish.app that will be visible to LaunchServices. It
733may be necessary when testing your app to take some steps to ensure
734that LaunchServices is launching the correct Wish.app. Instructions
735for doing this are provided below.
736
737The command line tool which manages the LaunchServices database has
738an amazingly unwieldy path name. So, first, run this command:
739
740alias lsregister='/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister'
741
742Then you can reset the LaunchServices database like this:
743
744$ lsregister -kill
745$ lsregister -seed
746
747To find out which versions of Wish.app have been located by
748LaunchServices, run:
749
750$ lsregister -dump | grep path | grep Wish
751
752If more than one version of Wish is showing up in this list, eliminate
753all of the unintended targets by running
754
755lsregister -u /path/to/bad/Wish.app
756
757Continue this until only the correct version of Wish shows up in the
758list.
759