summaryrefslogtreecommitdiffstats
path: root/macosx/tkMacOSXInit.c
diff options
context:
space:
mode:
Diffstat (limited to 'macosx/tkMacOSXInit.c')
-rw-r--r--macosx/tkMacOSXInit.c243
1 files changed, 187 insertions, 56 deletions
diff --git a/macosx/tkMacOSXInit.c b/macosx/tkMacOSXInit.c
index 881fb1b..06ff367 100644
--- a/macosx/tkMacOSXInit.c
+++ b/macosx/tkMacOSXInit.c
@@ -14,10 +14,9 @@
*/
#include "tkMacOSXPrivate.h"
-
-#include <sys/stat.h>
#include <dlfcn.h>
#include <objc/objc-auto.h>
+#include <sys/stat.h>
static char tkLibPath[PATH_MAX + 1] = "";
@@ -39,8 +38,9 @@ static int TkMacOSXGetAppPathCmd(ClientData cd, Tcl_Interp *ip,
@implementation TKApplication
@synthesize poolLock = _poolLock;
-@synthesize macMinorVersion = _macMinorVersion;
+@synthesize macOSVersion = _macOSVersion;
@synthesize isDrawing = _isDrawing;
+@synthesize needsToDraw = _needsToDraw;
@end
/*
@@ -113,6 +113,7 @@ static int TkMacOSXGetAppPathCmd(ClientData cd, Tcl_Interp *ip,
/*
* Initialize event processing.
*/
+
TkMacOSXInitAppleEvents(_eventInterp);
/*
@@ -144,7 +145,7 @@ static int TkMacOSXGetAppPathCmd(ClientData cd, Tcl_Interp *ip,
*/
[NSApp _lockAutoreleasePool];
- while (Tcl_DoOneEvent(TCL_WINDOW_EVENTS| TCL_DONT_WAIT)) {}
+ while (Tcl_DoOneEvent(TCL_WINDOW_EVENTS|TCL_DONT_WAIT)) {}
[NSApp _unlockAutoreleasePool];
}
@@ -164,15 +165,18 @@ static int TkMacOSXGetAppPathCmd(ClientData cd, Tcl_Interp *ip,
/*
* Record the OS version we are running on.
*/
- int minorVersion;
+
+ int minorVersion, majorVersion;
#if MAC_OS_X_VERSION_MAX_ALLOWED < 101000
Gestalt(gestaltSystemVersionMinor, (SInt32*)&minorVersion);
+ majorVersion = 10;
#else
NSOperatingSystemVersion systemVersion;
systemVersion = [[NSProcessInfo processInfo] operatingSystemVersion];
+ majorVersion = systemVersion.majorVersion;
minorVersion = systemVersion.minorVersion;
#endif
- [NSApp setMacMinorVersion: minorVersion];
+ [NSApp setMacOSVersion: 10000*majorVersion + 100*minorVersion];
/*
* We are not drawing right now.
@@ -267,6 +271,80 @@ static int TkMacOSXGetAppPathCmd(ClientData cd, Tcl_Interp *ip,
*----------------------------------------------------------------------
*/
+/*
+ * Helper function which closes the shared NSFontPanel and NSColorPanel.
+ */
+
+static void closePanels(
+ void)
+{
+ if ([NSFontPanel sharedFontPanelExists]) {
+ [[NSFontPanel sharedFontPanel] orderOut:nil];
+ }
+ if ([NSColorPanel sharedColorPanelExists]) {
+ [[NSColorPanel sharedColorPanel] orderOut:nil];
+ }
+}
+
+/*
+ * This custom exit procedure is called by Tcl_Exit in place of the exit
+ * function from the C runtime. It calls the terminate method of the
+ * NSApplication class (superTerminate for a TKApplication). The purpose of
+ * doing this is to ensure that the NSFontPanel and the NSColorPanel are closed
+ * before the process exits, and that the application state is recorded
+ * correctly for all termination scenarios.
+ *
+ * TkpWantsExitProc tells Tcl_AppInit whether to install our custom exit proc,
+ * which terminates the process by calling [NSApplication terminate]. This
+ * does not work correctly if the process is part of an exec pipeline, so it is
+ * only done if the process was launched by the launcher or if both stdin and
+ * stdout are ttys. To disable using the custom exit proc altogether, undefine
+ * USE_CUSTOM_EXIT_PROC.
+ */
+
+#if defined(USE_CUSTOM_EXIT_PROC)
+static Bool doCleanupFromExit = NO;
+
+int TkpWantsExitProc(void) {
+ return doCleanupFromExit == YES;
+}
+
+TCL_NORETURN void TkpExitProc(
+ void *clientdata)
+{
+ Bool doCleanup = doCleanupFromExit;
+ if (doCleanupFromExit) {
+ doCleanupFromExit = NO; /* prevent possible recursive call. */
+ closePanels();
+ }
+
+ /*
+ * Tcl_Exit does not call Tcl_Finalize if there is an exit proc installed.
+ */
+
+ Tcl_Finalize();
+ if (doCleanup == YES) {
+ [(TKApplication *)NSApp superTerminate:nil]; /* Should not return. */
+ }
+ exit((long)clientdata); /* Convince the compiler that we don't return. */
+}
+#endif
+
+/*
+ * This signal handler is installed for the SIGINT, SIGHUP and SIGTERM signals
+ * so that normal finalization occurs when a Tk app is killed by one of these
+ * signals (e.g when ^C is pressed while running Wish in the shell). It calls
+ * Tcl_Exit instead of the C runtime exit function called by the default handler.
+ * This is consistent with the Tcl_Exit manual page, which says that Tcl_Exit
+ * should always be called instead of exit. When Tk is killed by a signal we
+ * return exit status 1.
+ */
+
+static void TkMacOSXSignalHandler(TCL_UNUSED(int)) {
+
+ Tcl_Exit(1);
+}
+
int
TkpInit(
Tcl_Interp *interp)
@@ -274,15 +352,15 @@ TkpInit(
static int initialized = 0;
/*
- * Since it is possible for TkInit to be called multiple times and we
- * don't want to do the following initialization multiple times we protect
- * against doing it more than once.
+ * TkpInit can be called multiple times with different interpreters. But
+ * The application initialization should only be done onece.
*/
if (!initialized) {
struct stat st;
-
- initialized = 1;
+ Bool shouldOpenConsole = NO;
+ Bool stdinIsNullish = (!isatty(0) &&
+ (fstat(0, &st) || (S_ISCHR(st.st_mode) && st.st_blocks == 0)));
/*
* Initialize/check OS version variable for runtime checks.
@@ -292,7 +370,10 @@ TkpInit(
# error Mac OS X 10.6 required
#endif
+ initialized = 1;
+
#ifdef TK_FRAMEWORK
+
/*
* When Tk is in a framework, force tcl_findLibrary to look in the
* framework scripts directory.
@@ -309,16 +390,6 @@ TkpInit(
#endif
/*
- * FIXME: Close stdin & stdout for remote debugging otherwise we will
- * fight with gdb for stdin & stdout
- */
-
- if (getenv("XCNOSTDIN") != NULL) {
- close(0);
- close(1);
- }
-
- /*
* Instantiate our NSApplication object. This needs to be done before
* we check whether to open a console window.
*/
@@ -333,20 +404,20 @@ TkpInit(
nil]];
[TKApplication sharedApplication];
[pool drain];
- [NSApp _setup:interp];
/*
- * WARNING: The finishLaunching method runs asynchronously, apparently
- * in a separate thread. This creates a race between the
- * initialization of the NSApplication and the initialization of Tk.
- * If Tk wins the race bad things happen with the root window (see
- * below). If the NSApplication wins then an AppleEvent created during
- * launch, e.g. by dropping a file icon on the application icon, will
- * be delivered before the procedure meant to to handle the AppleEvent
- * has been defined. This is now handled by processing the AppleEvent
- * as an idle task. See tkMacOSXHLEvents.c.
+ * WARNING: The finishLaunching method runs asynchronously. This
+ * creates a race between the initialization of the NSApplication and
+ * the initialization of Tk. If Tk wins the race bad things happen
+ * with the root window (see below). If the NSApplication wins then an
+ * AppleEvent created during launch, e.g. by dropping a file icon on
+ * the application icon, will be delivered before the procedure meant
+ * to to handle the AppleEvent has been defined. This is handled in
+ * tkMacOSXHLEvents.c by scheduling a timer event to handle the
+ * ApplEvent later, after the required procedure has been defined.
*/
+ [NSApp _setup:interp];
[NSApp finishLaunching];
/*
@@ -373,36 +444,60 @@ TkpInit(
Tcl_DoOneEvent(TCL_WINDOW_EVENTS | TCL_DONT_WAIT);
/*
- * If we don't have a TTY and stdin is a special character file of
- * length 0, (e.g. /dev/null, which is what Finder sets when double
- * clicking Wish) then use the Tk based console interpreter.
+ * Decide whether to open a console window. If the TK_CONSOLE
+ * environment variable is not defined we only show the console if
+ * stdin is not a tty and there is no startup script.
*/
- if (getenv("TK_CONSOLE") ||
- (!isatty(0) && (fstat(0, &st) ||
- (S_ISCHR(st.st_mode) && st.st_blocks == 0)))) {
+ if (getenv("TK_CONSOLE")) {
+ shouldOpenConsole = YES;
+ } else if (stdinIsNullish && Tcl_GetStartupScript(NULL) == NULL) {
+ const char *intvar = Tcl_GetVar2(interp, "tcl_interactive",
+ NULL, TCL_GLOBAL_ONLY);
+ if (intvar == NULL) {
+ Tcl_SetVar2(interp, "tcl_interactive", NULL, "1",
+ TCL_GLOBAL_ONLY);
+ }
+
+#if defined(USE_CUSTOM_EXIT_PROC)
+ doCleanupFromExit = YES;
+#endif
+
+ shouldOpenConsole = YES;
+ }
+ if (shouldOpenConsole) {
Tk_InitConsoleChannels(interp);
Tcl_RegisterChannel(interp, Tcl_GetStdChannel(TCL_STDIN));
Tcl_RegisterChannel(interp, Tcl_GetStdChannel(TCL_STDOUT));
Tcl_RegisterChannel(interp, Tcl_GetStdChannel(TCL_STDERR));
+ if (Tk_CreateConsoleWindow(interp) == TCL_ERROR) {
+ return TCL_ERROR;
+ }
+ } else if (stdinIsNullish) {
/*
- * Only show the console if we don't have a startup script and
- * tcl_interactive hasn't been set already.
+ * When launched as a macOS application with no console,
+ * redirect stderr and stdout to /dev/null. This avoids waiting
+ * forever for those files to become writable if the underlying
+ * Tcl program tries to write to them with a puts command.
*/
- if (Tcl_GetStartupScript(NULL) == NULL) {
- const char *intvar = Tcl_GetVar2(interp,
- "tcl_interactive", NULL, TCL_GLOBAL_ONLY);
+ FILE *null = fopen("/dev/null", "w");
+ dup2(fileno(null), STDOUT_FILENO);
+ dup2(fileno(null), STDERR_FILENO);
+#if defined(USE_CUSTOM_EXIT_PROC)
+ doCleanupFromExit = YES;
+#endif
+ }
- if (intvar == NULL) {
- Tcl_SetVar2(interp, "tcl_interactive", NULL, "1",
- TCL_GLOBAL_ONLY);
- }
- }
- if (Tk_CreateConsoleWindow(interp) == TCL_ERROR) {
- return TCL_ERROR;
- }
+ /*
+ * FIXME: Close stdin & stdout for remote debugging if XCNOSTDIN is
+ * set. Otherwise we will fight with gdb for stdin & stdout
+ */
+
+ if (getenv("XCNOSTDIN") != NULL) {
+ close(0);
+ close(1);
}
/*
@@ -412,8 +507,46 @@ TkpInit(
*/
TkMacOSXServices_Init(interp);
+
+ /*
+ * The root window has been created and mapped, but XMapWindow deferred its
+ * call to makeKeyAndOrderFront because the first call to XMapWindow
+ * occurs too early in the initialization process for that. Process idle
+ * tasks now, so the root window is configured, then order it front.
+ */
+
+ while(Tcl_DoOneEvent(TCL_IDLE_EVENTS)) {};
+ for (NSWindow *window in [NSApp windows]) {
+ TkWindow *winPtr = TkMacOSXGetTkWindow(window);
+ if (winPtr && Tk_IsMapped(winPtr)) {
+ [window makeKeyAndOrderFront:NSApp];
+ break;
+ }
+ }
+
+# if defined(USE_CUSTOM_EXIT_PROC)
+
+ if ((isatty(0) && isatty(1))) {
+ doCleanupFromExit = YES;
+ }
+
+# endif
+
+ /*
+ * Install a signal handler for SIGINT, SIGHUP and SIGTERM which uses
+ * Tcl_Exit instead of exit so that normal cleanup takes place if a TK
+ * application is killed with one of these signals.
+ */
+
+ signal(SIGINT, TkMacOSXSignalHandler);
+ signal(SIGHUP, TkMacOSXSignalHandler);
+ signal(SIGTERM, TkMacOSXSignalHandler);
}
+ /*
+ * Initialization steps that are needed for all interpreters.
+ */
+
if (tkLibPath[0] != '\0') {
Tcl_SetVar2(interp, "tk_library", NULL, tkLibPath, TCL_GLOBAL_ONLY);
}
@@ -429,6 +562,7 @@ TkpInit(
TkMacOSXIconBitmapObjCmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "::tk::mac::GetAppPath",
TkMacOSXGetAppPathCmd, NULL, NULL);
+
return TCL_OK;
}
@@ -487,13 +621,11 @@ TkpGetAppName(
static int
TkMacOSXGetAppPathCmd(
- ClientData dummy,
+ TCL_UNUSED(void *),
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[])
{
- (void)dummy;
-
if (objc != 1) {
Tcl_WrongNumArgs(interp, 1, objv, NULL);
return TCL_ERROR;
@@ -622,11 +754,10 @@ TkMacOSXDefaultStartupScript(void)
MODULE_SCOPE void*
TkMacOSXGetNamedSymbol(
- const char* module,
- const char* symbol)
+ TCL_UNUSED(const char *),
+ const char *symbol)
{
void *addr = dlsym(RTLD_NEXT, symbol);
- (void)module;
if (!addr) {
(void) dlerror(); /* Clear dlfcn error state */