DNA.
wyrm-macosx
Version.
1.0.9
System-requirement.
os=Darwin
Namespace.
::wyrm::macosx, ::wyrm::wif
Language.
c
Manpage.
macosx-bundle (1WY)
Manpage.
macosx-resource (1WY)
Manpage.
macosx-parse (1WY)
Manpage.
macosx-download (1WY)
Manpage.
macosx-upload (1WY)
Manpage.
macosx-UIscript_check (1WY)
Manpage.
macosx-dumpUI (1WY)
Manpage.
macosx-attachfolderscript (1WY)
Manpage.
macosx-removefolderscript (1WY)
Manpage.
macosx-enablefolderactions (1WY)
Manpage.
macosx-disablefolderactions (1WY)
Manpage.
macosx-beep (1WY)
Manpage.
macosx-choose (1WY)
Manpage.
macosx-dialog (1WY)
Manpage.
macosx-info (1WY)
Manpage.
macosx-mount (1WY)
Manpage.
macosx-summarise (1WY)
Manpage.
macosx-clipboard (1WY)
Manpage.
macosx-sound (1WY)
Manpage.
macosx-updatefolder (1WY)
Testbase.
Test Script
Test Report
Import.
Interface.
wyrmwif
wyrm-io
Package.
wyrmwif
wyrm-assoc
Export.
Implementation.
wyrm-macosx-conflicts.c
wyrm-macosx-clean.c
Interface.
wyrm-macosx-tcl.h
Object.
wyrm-macosx-conflicts.o
wyrm-macosx-clean.o
Package.
wyrmmacosx.dylib
Tclgen.
wyrm-macosx-tcl.gen

Mac OS 10 extensions for Tcl

Sections.
Files
Accessibility
Bundles
Apple Scripts
Package Initialisation
Make.
Interface.
rule $include/wyrm-macosx-tcl.h $include/wyrm-macosx-tcl.gen "
  sed <$include/wyrm-macosx-tcl.gen >$include/wyrm-macosx-tcl.h \
    's/\\\\/\\\\\\\\/g; s/\"/\\\\\"/g; s/^/\"/; s/$$/\\\\n\",/'
"
Object.
compile -c -o $obj/$base-conflicts.o $source/$base-conflicts.c
compile -c -o $obj/$base-clean.o $source/$base-clean.c -- -list [
  import interface
] [
  export interface
]
Package.
if {[string equal $::tcl_platform(platform)/$::tcl_platform(os) unix/Darwin]} {
  compile -ld -o [export package] [export object]
} else {
  puts stderr "wyrm-macosx is for Mac OS 10 only."
}
Script.
rule clean :: {} "
  -rm $test/wyrm-macosx.TESTING
"
   
top

1 :: Provide Mac OS 10 capabilities as Tcl commands that are not otherwise available. This package is specify for os Darwin and will not compile for other systems.

Copyright (C) 2004 SM Ryan

Wyrmwif Tcl extensions. For non-profit uses only, provided this copyright is preserved on all copies, this work may be freely copied, modified, redistributed, compiled, and incorporated in other works. This work is distributed with no warranty of any kind; no author or distributor accepts any responsibility for the consequences of using it, or for whether it serves any particular purpose or works at all, unless he or she says so in writing.

   
   
top

#include <CoreFoundation/CoreFoundation.h>
#include <ApplicationServices/ApplicationServices.h>
#include <tcl.h>

#define cfobj(obj) (CFStringCreateWithCString(NULL,Tcl_GetString((obj)),kCFStringEncodingUTF8))
static Tcl_Obj *objcf(CFStringRef str) {
	char *s = CFStringGetCStringPtr(str,kCFStringEncodingUTF8);
	if (s) {
		return Tcl_NewStringObj(s,-1);
	}else {
		int n = CFStringGetLength(str);
		s = Tcl_Alloc(n+1);
		CFStringGetCString(str,s,n+1,kCFStringEncodingUTF8);
		Tcl_Obj *obj = Tcl_NewStringObj(s,n);
		Tcl_Free(s);
		return obj;
	}
}

		
   
   
top

#include <string.h>
#include "wyrmwif.h"
#include "wyrm-io.h"

#define objeq(string,object) streq(string,Tcl_GetString((object)))

		
   
   

Files

   
top

static int pathcommand(ClientData clientData,Intr intr,int N,Tcl_Obj *const P[]) {
	Tcl_Obj *wyrmPathcommand(int hfs,Tcl_Obj *path);
	bool hfs;
	switch (N) {
		case 2:
			hfs = true;
			break;
		case 3:
			switch (Tcl_GetString(P[1])[0]) {
				case 'h': hfs = true; break;
				case 'p': hfs = false; break;
				default: goto usage;
			}
			break;
		default: usage:
			Tcl_WrongNumArgs(intr,1,P,"[hfs|posix] path");
			return TCL_ERROR;
	}
	Tcl_SetObjResult(intr,wyrmPathcommand(hfs,P[N-1]));
	return TCL_OK;
}

		
 
section    top

Tcl_Obj *wyrmPathcommand(int hfs,Tcl_Obj *tclpath) {
	CFStringRef path = cfobj(tclpath);
	CFURLRef url = CFURLCreateWithFileSystemPath(NULL,path,
			hfs?kCFURLPOSIXPathStyle:kCFURLHFSPathStyle,0);
	CFStringRef altpath = CFURLCopyFileSystemPath(url,
			hfs?kCFURLHFSPathStyle:kCFURLPOSIXPathStyle);
	Tcl_Obj *r = objcf(altpath);
	CFRelease(path);
	CFRelease(url);
	CFRelease(altpath);
	return r;
}

		
 
section    top

Tcl_CreateObjCommand(intr,"::wyrm::macosx::path",pathcommand,0,0);

		
 
section    top
 
section    top

static int aliascommand(ClientData clientData,Intr intr,int N,Tcl_Obj *const P[]) {
	Tcl_Obj *wyrmAliascommand(Tcl_Obj *path);
	switch (N) {
		case 2: {
			/*
				Adapted and taken from Apple Cocoa File Manager documentation.
			*/
			Tcl_SetObjResult(intr,wyrmAliascommand(P[1]));
			return TCL_OK;
		}
		case 3: {
			Obj E[3];
			E[0] = Tcl_NewStringObj("::wyrm::wif::macosx-alias",-1);
			E[1] = P[1];
			E[2] = P[2];
			Obj cmd = incr(Tcl_NewListObj(3,E));
			int rc = Tcl_EvalObjEx(intr,cmd,0);
			decr(cmd);
			return rc;
		}
		default:
			Tcl_WrongNumArgs(intr,1,P,"aliasfile [originalfile]");
			return TCL_ERROR;
	}
}

static int isaliascommand(ClientData clientData,Intr intr,int N,Tcl_Obj *const P[]) {
	int wyrmIsaliascommand(Tcl_Obj *path);
	switch (N) {
		case 2: {
			bool aliassed = false;
			Tcl_SetObjResult(intr,Tcl_NewBooleanObj(wyrmIsaliascommand(P[1])));
			return TCL_OK;
		}
		default:
			Tcl_WrongNumArgs(intr,1,P,"file");
			return TCL_ERROR;
	}
}

		
 
section    top

Tcl_Obj *wyrmAliascommand(Tcl_Obj *tclpath) {
	CFStringRef path = cfobj(tclpath);
	CFStringRef resolvedPath = 0;
	CFURLRef url = CFURLCreateWithFileSystemPath(NULL,path,kCFURLPOSIXPathStyle,0);
	if (url) {
		FSRef fsRef;
		if (CFURLGetFSRef(url,&fsRef)) {
			Boolean targetIsFolder,wasAliased;
			if (!FSResolveAliasFile(&fsRef,true,&targetIsFolder,&wasAliased) && wasAliased) {
				CFURLRef resolvedUrl = CFURLCreateFromFSRef(NULL, &fsRef);
				if (resolvedUrl) {
					resolvedPath = CFURLCopyFileSystemPath(resolvedUrl,kCFURLPOSIXPathStyle);
					CFRelease(resolvedUrl);
				}
			}
		}
		CFRelease(url);
	}
	Tcl_Obj *r = objcf(resolvedPath?resolvedPath:path);
	if (resolvedPath) CFRelease(resolvedPath);
	CFRelease(path);
	return r;
}

int wyrmIsaliascommand(Tcl_Obj *tclpath) {
	CFStringRef path = cfobj(tclpath); int aliassed = 0;
	CFURLRef url = CFURLCreateWithFileSystemPath(NULL,path,kCFURLPOSIXPathStyle,0);
	if (url) {
		FSRef fsRef;
		if (CFURLGetFSRef(url,&fsRef)) {
			Boolean isAlias,isFolder;
			aliassed =  FSIsAliasFile(&fsRef,&isAlias,&isFolder)==noErr && isAlias;
		}
		CFRelease(url);
	}
	CFRelease(path);
	return aliassed;
}

		
 
section    top

Tcl_CreateObjCommand(intr,"::wyrm::macosx::alias",aliascommand,0,0);
Tcl_CreateObjCommand(intr,"::wyrm::macosx::isalias",isaliascommand,0,0);

		
 
section    top

proc ::wyrm::wif::macosx-alias {alias original} {
	set alias [file normalize $alias]
	set original [file normalize $original]
	file delete -force $alias
	if {[file isdirectory $original]} {
		set original "folder \"[wyrm::macosx::path hfs $original]\""
	} else {
		set original "file \"[wyrm::macosx::path hfs $original]\""
	}
	set container "\"[wyrm::macosx::path hfs [file dir $alias]]\""
	set name "\"[file tail $alias]\""
	set script "
		tell application \"Finder\"
			set _X to $original
			set _Y to $container
			set _Z to make alias at _Y to _X
			set name of _Z to $name
		end tell
	"
	exec osascript << $script
}

		
 
section    top
 
section    top
13. macosx::folder ::

NAME

macosx::folder — Return the path to system folder.

synopsis

wyrm::macosx::folder [ domain ] folder

domain ::= system | local | network | user | classic
folder ::= system | desktop | systemdesktop | trash | systemtrash | emptytrash | printmonitor | startup | shutdown | applemenu | controlpanel | systemcontrolpanel | extensions | fonts | preferences | systempreferences | temporary | extdisabled | controldisabled | sysextdisabled | startupdisabled | shutdowndisabled | applications | documents | root | chewable | applicationsupport | textencodings | stationery | opendoc | opendocshellplugin | editors | opendoceditors | opendoclibraries | geneditors | help | internetplugins | modemscripts | printerdescriptions | printerdriver | scriptingadditions | sharedlibraries | voices | controlstrip | assistants | utilities | appleextras | contextualmenus | readme | colorsync | themes | favorites | internet | appearance | soundsets | desktoppictures | internetsearch | findsupport | findbycontent | installerlogs | scripts | folderactions | launcheritems | recentapplications | recentdocuments | recentservers | speakableitems | keychain | quicktimeextensions | displayextensions | multiprocessing | printingplugins

description

Return the path to the folder. If the domain is omitted, existing folders of all domains are returned.


Tcl_Obj *wyrmFoldercommand(int domainindex,int folderindex,long *dom,long *fol);
static char *domainstring[] = {"user","local","network","system","classic",0};

static int foldercommand(ClientData clientData,Intr intr,int N,Tcl_Obj *const P[]) {
	int domainindex;
	int folderindex;
	static char *folderstring[] = {
			"frameworks",
			"os9","desktop","systemdesktop","trash",
			"systemtrash","emptytrash","printmonitor",
			"startup","shutdown","applemenu","controlpanel",
			"systemcontrolpanel","extensions","fonts",
			"preferences","systempreferences","temporary",
			"extdisabled","controldisabled",
			"sysextdisabled","startupdisabled",
			"shutdowndisabled","applications","documents",
			"root","chewable","applicationsupport",
			"textencodings","stationery","opendoc",
			"opendocshellplugin","editors","opendoceditors",
			"opendoclibraries","geneditors","help",
			"internetplugins","modemscripts","printerdescriptions",
			"printerdriver","scriptingadditions","sharedlibraries",
			"voices","controlstrip","assistants",
			"utilities","appleextras","contextualmenus",
			"readme","colorsyncprofiles","themes",
			"favorites","internet","appearance",
			"soundsets","desktoppictures","internetsearch","findsupport",
			"findbycontent","installerlogs","scripts",
			"folderactions","launcheritems","recentapplications",
			"recentdocuments","recentservers","speakableitems",
			"keychain","quicktimeextensions","displayextensions",
			"multiprocessing","printingplugins","coloursyncprofiles",
			"favourites",
			"toplevel","library","colorsync","coloursync",
			"colorsynccmm","colorsyncscripting","printers",
			"speech","carbonlibrary","documentation",
			"developerdocs","developerhelp","issdownloads",
			"userspecifictmp","cacheddata",
			"privateframeworks","classicdesktop",
			"developer","systemsounds","components",
			"quicktimecomponents","coreservices","pictures",
			"movies","music","internetsites",
			"public","audiosupport","audiosounds",
			"audiosoundbanks","audioalertsounds","audioplugins",
			"audiocomponents","kernelextensions","directoryservices",
			"directoryservicesplugins","installerreceipts","filesystemsupport",
			"applesharesupport","appleshareauthentication","mididrivers",
			"keyboardlayouts","indexfiles","findbycontentindexes",
			"manageditems","boottimestartupitems",
			0
	};
	Obj L,f; long dom,fol;
	switch (N) {
		case 2:
			if (Tcl_GetIndexFromObj(intr,P[1],(CONST char**)folderstring,"folder",0,&folderindex)==TCL_ERROR)
				return TCL_ERROR;
			L = incr(Tcl_NewObj());
			for (domainindex=0; domainstring[domainindex]!=0; domainindex++) {
				f = wyrmFoldercommand(domainindex,folderindex,&dom,&fol);
				if (f) {
					Tcl_ListObjAppendElement(0,L,f);
				}
			}
			Tcl_SetObjResult(intr,L);
			return TCL_OK;
		case 3:
			if (Tcl_GetIndexFromObj(intr,P[1],(CONST char**)domainstring,"domain",0,&domainindex)==TCL_ERROR)
				return TCL_ERROR;
			if (Tcl_GetIndexFromObj(intr,P[2],(CONST char**)folderstring,"folder",0,&folderindex)==TCL_ERROR)
				return TCL_ERROR;
			f = wyrmFoldercommand(domainindex,folderindex,&dom,&fol);
			if (f) {
				Tcl_SetObjResult(intr,f);
				return TCL_OK;
			}else {
				return rprintf(intr,"%!no such folder",TCL_ERROR);
			}
		default: usage:
			Tcl_WrongNumArgs(intr,1,P,"[domain] folder");
			return TCL_ERROR;
	}
	return TCL_OK;
}

		
 
section    top

Tcl_Obj *wyrmFoldercommand(int domainindex,int folderindex,long *dom,long *fol) {
	static long domainnumber [] = {kUserDomain,kLocalDomain,kNetworkDomain,kSystemDomain,kClassicDomain};
	static long foldernumber[] = {
			kFrameworksFolderType,
			kSystemFolderType,kDesktopFolderType,kSystemDesktopFolderType,kTrashFolderType,
			kSystemTrashFolderType,kWhereToEmptyTrashFolderType,kPrintMonitorDocsFolderType,
			kStartupFolderType,kShutdownFolderType,kAppleMenuFolderType,kControlPanelFolderType,
			kSystemControlPanelFolderType,kExtensionFolderType,kFontsFolderType,
			kPreferencesFolderType,kSystemPreferencesFolderType,kTemporaryFolderType,
			kExtensionDisabledFolderType,kControlPanelDisabledFolderType,
			kSystemExtensionDisabledFolderType,kStartupItemsDisabledFolderType,
			kShutdownItemsDisabledFolderType,kApplicationsFolderType,kDocumentsFolderType,
			kVolumeRootFolderType,kChewableItemsFolderType,kApplicationSupportFolderType,
			kTextEncodingsFolderType,kStationeryFolderType,kOpenDocFolderType,
			kOpenDocShellPlugInsFolderType,kEditorsFolderType,kOpenDocEditorsFolderType,
			kOpenDocLibrariesFolderType,kGenEditorsFolderType,kHelpFolderType,
			kInternetPlugInFolderType,kModemScriptsFolderType,kPrinterDescriptionFolderType,
			kPrinterDriverFolderType,kScriptingAdditionsFolderType,kSharedLibrariesFolderType,
			kVoicesFolderType,kControlStripModulesFolderType,kAssistantsFolderType,
			kUtilitiesFolderType,kAppleExtrasFolderType,kContextualMenuItemsFolderType,
			kMacOSReadMesFolderType,kColorSyncProfilesFolderType,kThemesFolderType,
			kFavoritesFolderType,kInternetFolderType,kAppearanceFolderType,
			kSoundSetsFolderType,kDesktopPicturesFolderType,kInternetSearchSitesFolderType,kFindSupportFolderType,
			kFindByContentFolderType,kInstallerLogsFolderType,kScriptsFolderType,
			kFolderActionsFolderType,kLauncherItemsFolderType,kRecentApplicationsFolderType,
			kRecentDocumentsFolderType,kRecentServersFolderType,kSpeakableItemsFolderType,
			kKeychainFolderType,kQuickTimeExtensionsFolderType,kDisplayExtensionsFolderType,
			kMultiprocessingFolderType,kPrintingPlugInsFolderType,kColorSyncProfilesFolderType,
			kFavoritesFolderType,
			kDomainTopLevelFolderType,kDomainLibraryFolderType,kColorSyncFolderType,kColorSyncFolderType,
			kColorSyncCMMFolderType,kColorSyncScriptingFolderType,kPrintersFolderType,
			kSpeechFolderType,kCarbonLibraryFolderType,kDocumentationFolderType,
			kDeveloperDocsFolderType,kDeveloperHelpFolderType,kISSDownloadsFolderType,
			kUserSpecificTmpFolderType,kCachedDataFolderType,
			kPrivateFrameworksFolderType,kClassicDesktopFolderType,
			kDeveloperFolderType,kSystemSoundsFolderType,kComponentsFolderType,
			kQuickTimeComponentsFolderType,kCoreServicesFolderType,kPictureDocumentsFolderType,
			kMovieDocumentsFolderType,kMusicDocumentsFolderType,kInternetSitesFolderType,
			kPublicFolderType,kAudioSupportFolderType,kAudioSoundsFolderType,
			kAudioSoundBanksFolderType,kAudioAlertSoundsFolderType,kAudioPlugInsFolderType,
			kAudioComponentsFolderType,kKernelExtensionsFolderType,kDirectoryServicesFolderType,
			kDirectoryServicesPlugInsFolderType,kInstallerReceiptsFolderType,kFileSystemSupportFolderType,
			kAppleShareSupportFolderType,kAppleShareAuthenticationFolderType,kMIDIDriversFolderType,
			kKeyboardLayoutsFolderType,kIndexFilesFolderType,kFindByContentIndexesFolderType,
			kManagedItemsFolderType,kBootTimeStartupItemsFolderType,
	};
	FSRef fr; CFURLRef url; CFStringRef path = 0; Tcl_Obj *r = 0;
	if (dom) *dom = domainnumber[domainindex];
	if (fol) *fol = foldernumber[folderindex];
	if (FSFindFolder(domainnumber[domainindex],foldernumber[folderindex],FALSE,&fr))
		return 0;
	url = CFURLCreateFromFSRef(NULL,&fr);
	if (url) {
		path = CFURLCopyFileSystemPath(url,kCFURLPOSIXPathStyle);
		CFRelease(url);
		if (path) {
			r = objcf(path);
			CFRelease(path);
		}
	}
	return r;
}

		
 
section    top

Tcl_CreateObjCommand(intr,"::wyrm::macosx::folder",foldercommand,0,0);

		
 
section    top
   
   

Accessibility

   
top
17. macosx::speak ::

NAME

macosx::speak — Speak some text.

synopsis

wyrm::macosx::speak [ -over | -voice voice | -finish script | -phoneme script | -word script | -sync script ] ... [ -- ] spoken-string

wyrm::macosx::speak -stop token [ -word|-sentence|-now ]

wyrm::macosx::speak -pause token [ -word|-sentence|-now ]

wyrm::macosx::speak -resume token

wyrm::macosx::speak -speaking token

wyrm::macosx::speak -voice [ voice ]

wyrm::macosx::speak -wait

description

Speak a string, or return information about speech synthesis. If the string is given, the speech synthesis commences. Otherwise options are used to query. This code is not thread safe. It should only be used in one thread at a time; it can be used in different interpretters of the thread.

spoken-string
Speak this string, unless the speech synthesiser is busy. The command returns a token if it commenced speaking the string and an error if it is already busy. The command can return before the speech is finished (or even started). Once the speech completes (or is stopped), the token is no longer useful.
-over
Speak over any other speech. The command always return a token and speaking commences.
-voice voice
Speak with this voice. If omitted, the default voice is used. If no spoken-string is specified, this instead returns information about the selected voice.
-voice
With no spoken-string is specified, this returns a list of voices, or information about a voice.
-finish script
Evaluates the script when the speech is completed/ The speech token is appended to the script before evaluation. The token is no longer usable after this script returns.
-phoneme script
Evaluates the script when the phoneme is pronounced. The speech token and phoneme code are appended to the script before evaluation.
-word script
Evaluates the script when a string is pronounced. The speech token and string are appended to the script before evaluation.
-sync script
Evaluates the script when a sync command is evaluated. The speech token and sync value (as an integer) are appended to the script before evaluation.
-stop token
Stop speaking.
-pause token
Pause speaking.
-resume token
Resume speaking.
-speaking token
If still speaking.
-wait token
Waits for all speaking in the system to halt. Background tasks are run while waiting.

The variable $wyrm::macosx::ipa is a list that can be used with string map to convert IPA phonetic symbols to Apple's ASCII symbols; and $wyrm::macosx::apple is the invese mapping.


static Tcl_HashTable speechChannels; static int speechCount = 0;
typedef struct {
	Obj token;
	Intr intr;
	Obj finishscript,phonemescript,syncscript,wordscript;
	Obj text;
	Obj phmap;
	void *channel;
} SpeechEntry,*pSpeechEntry;

static void speechExit(ClientData clientData) {
	void wyrmSpeechExit(void *speechChannel);
	pSpeechEntry E = (pSpeechEntry)clientData;
	wyrmSpeechExit(E->channel);
}

static void speechDone(void *channel,long refCon) {
	void wyrmSpeechDone(void *channel);
	pSpeechEntry E = (pSpeechEntry)refCon;
	Tcl_DeleteExitHandler(speechExit,(ClientData)E);
	if (E->intr && !Tcl_InterpDeleted(E->intr) && E->finishscript) {
		Obj script = incr(Tcl_DuplicateObj(E->finishscript));
		Tcl_SavedResult state; Tcl_SaveResult(E->intr,&state);
		int rc = Tcl_ListObjAppendElement(E->intr,script,E->token);
		if (rc==TCL_OK) rc = Tcl_EvalObjEx(E->intr,script,TCL_EVAL_DIRECT);
		decr(script);
		if (rc!=TCL_OK) Tcl_BackgroundError(E->intr);
		Tcl_RestoreResult(E->intr,&state);
	}
	wyrmSpeechDone(channel);
	if (E->intr) Tcl_Release(E->intr); E->intr = 0;
	Tcl_HashEntry *e = Tcl_FindHashEntry(&speechChannels,Tcl_GetString(E->token));
	Tcl_DeleteHashEntry(e);
	decr(E->token); decr(E->text); decr(E->phmap);
	decr(E->finishscript); decr(E->phonemescript); decr(E->wordscript); decr(E->syncscript);
	dispose(E);
}

static void speechError(void *channel,long refCon,int ec,long off) {
	pSpeechEntry E = (pSpeechEntry)refCon;
	if (E->intr && !Tcl_InterpDeleted(E->intr)) {
		Tcl_SavedResult state; Tcl_SaveResult(E->intr,&state);
		rprintf(E->intr,"speech %{y}s: error %d offset %d",E->token,ec,off);
		Tcl_BackgroundError(E->intr);
		Tcl_RestoreResult(E->intr,&state);
	}else if (E->intr) {
		Tcl_Release(E->intr); E->intr = 0;
	}
}

static void speechPhoneme(void *channel,long refCon,short code) {
	pSpeechEntry E = (pSpeechEntry)refCon;
	if (E->intr && !Tcl_InterpDeleted(E->intr)) {
		Obj script = incr(Tcl_DuplicateObj(E->phonemescript));
		Tcl_SavedResult state; Tcl_SaveResult(E->intr,&state);
		int rc = Tcl_ListObjAppendElement(E->intr,script,E->token);
		Obj phoneme;
		if (Tcl_ListObjIndex(0,E->phmap,code,&phoneme)!=TCL_OK) phoneme = Tcl_NewStringObj("?",1);
		incr(phoneme);
		if (rc==TCL_OK) Tcl_ListObjAppendElement(E->intr,script,phoneme);
		decr(phoneme);
		if (rc==TCL_OK) rc = Tcl_EvalObjEx(E->intr,script,TCL_EVAL_DIRECT);
		decr(script);
		if (rc!=TCL_OK) Tcl_BackgroundError(E->intr);
		Tcl_RestoreResult(E->intr,&state);
	}else if (E->intr) {
		Tcl_Release(E->intr); E->intr = 0;
	}
}

static void speechSync(void *channel,long refCon,long syncMessage) {
	pSpeechEntry E = (pSpeechEntry)refCon;
	if (E->intr && !Tcl_InterpDeleted(E->intr)) {
		Obj script = incr(Tcl_DuplicateObj(E->syncscript));
		Tcl_SavedResult state; Tcl_SaveResult(E->intr,&state);
		int rc = Tcl_ListObjAppendElement(E->intr,script,E->token);
		Obj mark = incr(Tcl_NewLongObj(syncMessage));
		if (rc==TCL_OK) rc = Tcl_ListObjAppendElement(E->intr,script,mark);
		decr(mark);
		if (rc==TCL_OK) rc = Tcl_EvalObjEx(E->intr,script,TCL_EVAL_DIRECT);
		decr(script);
		if (rc!=TCL_OK) Tcl_BackgroundError(E->intr);
		Tcl_RestoreResult(E->intr,&state);
	}else if (E->intr) {
		Tcl_Release(E->intr); E->intr = 0;
	}
}

static void speechTextDone(void *channel,long refCon,ptr *next,unsigned long *len,long *flags) {
	pSpeechEntry E = (pSpeechEntry)refCon;
	decr(E->text); E->text = 0;
	*next = 0; *len = 0; *flags = 0;
}

static void speechWord(void *channel,long refCon,unsigned long off,unsigned short len) {
	pSpeechEntry E = (pSpeechEntry)refCon;
	if (E->intr && !Tcl_InterpDeleted(E->intr)) {
		Obj script = incr(Tcl_DuplicateObj(E->wordscript));
		Tcl_SavedResult state; Tcl_SaveResult(E->intr,&state);
		int rc = Tcl_ListObjAppendElement(E->intr,script,E->token);
		int n; chars s = Tcl_GetStringFromObj(E->text,&n);
		if (off+len>n) len = n-off;
		Obj word = incr(Tcl_NewStringObj(s+off,len));
		if (rc==TCL_OK) rc = Tcl_ListObjAppendElement(E->intr,script,word);
		decr(word);
		if (rc==TCL_OK) rc = Tcl_EvalObjEx(E->intr,script,TCL_EVAL_DIRECT);
		decr(script);
		if (rc!=TCL_OK) Tcl_BackgroundError(E->intr);
		Tcl_RestoreResult(E->intr,&state);
	}else if (E->intr) {
		Tcl_Release(E->intr); E->intr = 0;
	}
}

int wyrmSpeakcommandBusy(void);
static bool waiting = false;
static void wakeup(ClientData clientData) {waiting = false;}
static void waitForSumpin(void) {
	while (wyrmSpeakcommandBusy())
		if (!Tcl_DoOneEvent(TCL_DONT_WAIT)==0) {
			if (waiting) {
				Tcl_DoOneEvent(0);
			}else {
				Tcl_TimerToken token = Tcl_CreateTimerHandler(250,wakeup,0);
				Tcl_DoOneEvent(0);
				if (waiting) Tcl_DeleteTimerHandler(token);
			}
		}
}

static int speakcommand(ClientData clientData,Intr intr,int N,Tcl_Obj *const P[]) {
	int state = 0,i,ec;
	Obj voice = 0;
	bool over = false;
	pSpeechEntry E = 0; Tcl_HashEntry *e;
	Obj finisher = 0,phonemer = 0,worder = 0,syncer = 0,text = 0,token = 0;
	int where = 0;
	for (i=1; i<N; i++) {
		switch (wyrm_stringToC(Tcl_GetString(P[i]))&0xFFFFFF00 | '0' | state) {
			case '-ov0': case '-ov2': over = true; goto entry;
			case '-ov1': over = true; break;
			case '-vo0':
				if (i+1==N) {state = 3;}
				else {voice = P[i+1]; i++; state = 2;}
				break;
			case '-vo1': if (i+1>=N) goto usage; voice = P[i+1]; i++; break;
			case '-fi0': case '-fi2': if (i+1>=N) goto usage; finisher = P[i+1]; i++; goto entry;
			case '-fi1': if (i+1>=N) goto usage; finisher = P[i+1]; i++; break;
			case '-ph0': case '-ph2': if (i+1>=N) goto usage; phonemer = P[i+1]; i++; goto entry;
			case '-ph1': if (i+1>=N) goto usage; phonemer = P[i+1]; i++; break;
			case '-wo0': case '-wo2': if (i+1>=N) goto usage; worder = P[i+1]; i++; goto entry;
			case '-wo1': if (i+1>=N) goto usage; worder = P[i+1]; i++; break;
			case '-sy0': case '-sy2': if (i+1>=N) goto usage; syncer = P[i+1]; i++; goto entry;
			case '-sy1': if (i+1>=N) goto usage; syncer = P[i+1]; i++; break;
			case '-st0': case '-st8': if (i+1>=N) goto usage; token = P[i+1]; i++; state = 4; goto find;
			case '-pa0': case '-pa8': if (i+1>=N) goto usage; token = P[i+1]; i++; state = 5; goto find;
			case '-re0': if (i+1>=N) goto usage; token = P[i+1]; i++; state = 6; goto find;
			case '-sp0': if (i+1>=N) goto usage; token = P[i+1]; i++; state = 7; goto find;
			case '--0': case '--2': if (i+1>=N) goto usage; text = P[i+1]; i++; goto entry;
			case '--1': if (i+1>=N) goto usage; text = P[i+1]; i++; break;
			case '-wo4': case '-wo5': where = 1; break;
			case '-se0': state = 8; case '-se4': case '-se5': where = 2; break;
			case '-no0': state = 8; case '-no4': case '-no5': where = 0; break;
			case '-wa0': state = 9; break;
			usage:
				rprintf(intr,"incompatiable or unknown parameter: %{y}s",P[i]);
				goto error;
			entry: {
				if (!speechCount) Tcl_InitHashTable(&speechChannels,TCL_STRING_KEYS);
				token = oprintf("speech-%d",++speechCount);
				int new; e = Tcl_CreateHashEntry(&speechChannels,Tcl_GetString(token),&new);
				E = heap(SpeechEntry); zero(1,SpeechEntry,E);
				Tcl_SetHashValue(e,(ClientData)E);
				E->token = token;
				Tcl_Preserve(intr); E->intr = intr;
				state = 1;
			}   break;
			find: {
				e = Tcl_FindHashEntry(&speechChannels,Tcl_GetString(token));
				E = e ? (pSpeechEntry)Tcl_GetHashValue(e) : 0;
			}   break;
			default:
				if (Tcl_GetString(P[i])[0]=='-' || state>2) goto usage;
				text = P[i]; if (state==1) break; else goto entry;
		}
	}
	if (voice) {
		int wyrmSpeakcommandVoiceCheck(Tcl_Obj *voice);
		switch ((ec=wyrmSpeakcommandVoiceCheck(voice))) {
			case 0: rprintf(intr,"unknown voice: %{y}s",voice); goto error;
			case 1: break;
			default: goto macerror;
		}
	}
	switch (state) {
		case 0: /* */
			rprintf(intr,"no parameters");
			goto error;
		case 1: /* spoken-string */ {
			int wyrmSpeakcommandCreate(
				int voiced,ptr *channel,
				ptr speechDone,ptr speechError,
				ptr speechPhoneme,Tcl_Obj **phmap,
				ptr speechSync,ptr speechTextDone,ptr speechWord,
				ptr refCon,Tcl_Obj *speech
			);
			if (!text) {
				rprintf(intr,"no spoken-text given");
				goto error;
			}
			if (!over && wyrmSpeakcommandBusy()) {
				rprintf(intr,"speech is busy");
				goto error;
			}
			ec = wyrmSpeakcommandCreate(
				!!voice,&E->channel,
				(void*)speechDone,(void*)speechError,
				phonemer ? (void*)speechPhoneme : 0,&E->phmap,
				syncer ? (void*)speechSync : 0,
				!worder ? (void*)speechTextDone : 0,
				worder ? (void*)speechWord : 0,
				(void*)E,text
			);
			if (ec) goto macerror;
			E->text = incr(text);
			E->finishscript = incr(finisher);
			E->phonemescript = incr(phonemer);
			E->wordscript = incr(worder);
			E->syncscript = incr(syncer);
			Tcl_CreateExitHandler(speechExit,(ClientData)E);
			Tcl_SetObjResult(intr,token);
		}   return TCL_OK;
		case 2: /* -voice v */ {
			void wyrmSpeakcommandVoiceGet(Intr);
			wyrmSpeakcommandVoiceGet(intr);
		}	return TCL_OK;
		case 3: /* -voice */ {
			int wyrmSpeakcommandVoiceList(Tcl_Obj **L);
			Obj L; ec = wyrmSpeakcommandVoiceList(&L);
			if (!ec) Tcl_SetObjResult(intr,L); decr(L);
			if (ec) goto macerror;
		}	return TCL_OK;
		case 4: /* -stop x */
			if (E) {
				if ((ec=wyrmSpeakcommandStop(E->channel,where))) goto macerror;
				Tcl_SetObjResult(intr,token);
			}else
				Tcl_ResetResult(intr);
			return TCL_OK;
		case 5: /* -pause x */
			if (E) {
				if ((ec=wyrmSpeakcommandPause(E->channel,where))) goto macerror;
				Tcl_SetObjResult(intr,token);
			}else
				Tcl_ResetResult(intr);
			return TCL_OK;
		case 6: /* -resume x */
			if (E) {
				if ((ec=wyrmSpeakcommandContinue(E->channel))) goto macerror;
				Tcl_SetObjResult(intr,token);
			}else
				Tcl_ResetResult(intr);
			return TCL_OK;
		case 7: /* -speaking x */
			Tcl_SetObjResult(intr,Tcl_NewBooleanObj(E!=0));
			return TCL_OK;
		case 8:
			rprintf(intr,"need -stop or -pause");
			goto error;
		case 9:
			waitForSumpin();
			return TCL_OK;

	}
macerror:
	rprintf(intr,"macintosh error code %d",ec);
error:
	if (state==1) {
		if (E->channel) wyrmSpeechDone(E->channel);
		Tcl_Release(intr);
		Tcl_DeleteHashEntry(e);
		decr(E->token); decr(E->text); decr(E->phmap);
		decr(E->finishscript); decr(E->phonemescript); decr(E->wordscript); decr(E->syncscript);
		dispose(E);
	}
	return TCL_ERROR;
}

		
 
section    top

Tcl_CreateObjCommand(intr,"::wyrm::macosx::speak",speakcommand,0,0);

		
 
section    top

void wyrmSpeechExit(void *speechChannel) {StopSpeech((SpeechChannel)speechChannel);}

void wyrmSpeechDone(void *speechChannel) {DisposeSpeechChannel((SpeechChannel)speechChannel);}

int wyrmSpeakcommandBusy(void) {return SpeechBusySystemWide()>0;}

int wyrmSpeakcommandStop(void *speechChannel,int where) {
	return StopSpeechAt((SpeechChannel)speechChannel,where);
}

int wyrmSpeakcommandPause(void *speechChannel,int where) {
	return PauseSpeechAt((SpeechChannel)speechChannel,where);
}

int wyrmSpeakcommandContinue(void *speechChannel) {
	return ContinueSpeech((SpeechChannel)speechChannel);
}

static VoiceSpec vs; static VoiceDescription desc;

int wyrmSpeakcommandVoiceCheck(Tcl_Obj *voice) {
		OSErr ec; int found; short nv; int i;
		int n; char *s = Tcl_GetStringFromObj(voice,&n);
		if ((ec=CountVoices(&nv))) return ec;
		for (i=1,found=0; i<=nv && !found; i++) {
			if ((ec=GetIndVoice(i,&vs))) return ec;
			if ((ec=GetVoiceDescription(&vs,&desc,sizeof desc))) return ec;
			found = n==desc.name[0] && memcmp(s,desc.name+1,n)==0;
		}
		return found;
}

int wyrmSpeakcommandCreate(
	int voiced,void **channel,
	void *speechDone,void *speechError,
	void *speechPhoneme,Tcl_Obj **phmap,
	void *speechSync,void *speechTextDone,void *speechWord,
	void *refCon,Tcl_Obj *speech
) {
	OSErr ec; SpeechChannel speechChannel; int i,n;
	if ((ec=NewSpeechChannel(voiced?&vs:0,&speechChannel))) return ec;
	*channel = speechChannel;
	if ((ec=SetSpeechInfo(speechChannel,'sdcb',speechDone))) return ec;
	if ((ec=SetSpeechInfo(speechChannel,'ercb',speechError))) return ec;
	if (speechPhoneme) {
		if ((ec=GetSpeechInfo(speechChannel,'phcb',speechPhoneme))) return ec;
		Handle phh;
		if ((ec=GetSpeechInfo(speechChannel,'phsy',&phh))) return ec;
		PhonemeDescriptor *ph = ((PhonemeDescriptor**)phh)[0];
		for (i=n=0; i<ph->phonemeCount; i++)
			if (ph->thePhonemes[i].opcode>n)
				n = ph->thePhonemes[i].opcode;
		Tcl_Obj **l = (Tcl_Obj**)Tcl_Alloc((n+1)*sizeof(Tcl_Obj*));
		memset(l,(n+1)*sizeof(Tcl_Obj*),0);
		for (i=0; i<ph->phonemeCount; i++)
			l[ph->thePhonemes[i].opcode] =
					Tcl_NewStringObj(ph->thePhonemes[i].phStr+1,ph->thePhonemes[i].phStr[0]);
		for (i=0; i<ph->phonemeCount; i++)
			if (!l[i]) l[i] = Tcl_NewStringObj("?",1);
		*phmap = Tcl_NewListObj(n+1,l); Tcl_IncrRefCount(*phmap);
		Tcl_Free((char*)l);
	}
	if (speechSync && (ec=SetSpeechInfo(speechChannel,'sycb',speechSync))) return ec;
	if (speechTextDone && (ec=SetSpeechInfo(speechChannel,'tdcb',speechTextDone))) return ec;
	if (speechWord && (ec=SetSpeechInfo(speechChannel,'wdcb',speechWord))) return ec;
	if ((ec=SetSpeechInfo(speechChannel,'refc',refCon))) return ec;
	char *s = Tcl_GetStringFromObj(speech,&n);
	if ((ec=SpeakText(speechChannel,s,n))) return ec;
	return 0;
}

void wyrmSpeakcommandVoiceGet(Tcl_Interp *intr) {
	int rprintf(Tcl_Interp*,char*,...);
	rprintf(intr,
			"voicespec %{i}s/%d version %d name {%.*s} comment {%.*s} gender %s"
			" age %d script %d language %d region %d",
			vs.creator,vs.id,desc.version,
			desc.name[0],desc.name+1,
			desc.comment[0],desc.comment+1,
			desc.gender==0?"neuter":desc.gender==1?"male":"female",
			desc.age,desc.script,desc.language,desc.region);
}

int wyrmSpeakcommandVoiceList(Tcl_Obj **L) {
	VoiceSpec vs; OSErr ec; int i;
	short nv; VoiceDescription desc;
	*L = Tcl_NewObj(); Tcl_IncrRefCount(*L);
	if ((ec=CountVoices(&nv))) return ec;
	for (i=1; i<=nv; i++) {
		if ((ec=GetIndVoice(i,&vs))) return ec;
		if ((ec=GetVoiceDescription(&vs,&desc,sizeof desc))) return ec;
		Tcl_ListObjAppendElement(0,*L,Tcl_NewStringObj(desc.name+1,desc.name[0]));
	}
	return 0;
}

		
 
section    top

namespace eval ::wyrm::macosx {
	variable ipa {
		% %
		@ @
		
		p p						t t		C t\u0283 C \u02A7		k k
		b b						d d		J d\u0292 J \u02A4		g g
			f f		T \u03B8	s s		S \u0283						h h
			v v		D \u00F0	z z		Z \u0292
		m m						n n								N \014B
		w w						r \0279					y J
								l l

		IH i		IX \u0268		AX \u0259		UH u
		EH e				UX \u028C				AA o
		AE \u00E6									AO \u0251

		IY ij										UW uw
		EY ej							OY oj		OW ow
						AY aj		AW aw

		1 \u02C8	2 \u02CC	= =			~ ~		- -		+ +
		/ \u2191	\ \u2193	> \u02D0	< \u02D1

		& &		: :		, ,		É É		! !		Ð Ð
		( (		) )		. .		? ?		; ;
	}
		
	variable apple {
		% %
		@ @

		p p						t t		t\u0283 C		k k
		b b						d d		d\u0292 J		g h
			f f		\u03B8 T	s s		\u0283 S					h h
			v v		\u00F0 D	z z		\u0292 Z
		m m						n n						\014B N
		w w						\0279 r					J y
								l l

		i IH		\u0268 IX		\u0259 AX		u UH
		e EH				\u028C UX				o AA
		\u00E6 AE									\u0251 AO

		ij IY										uw UW
		ej EY							oj OY		ow OW
						aj AY		aw AW

		\u02C8 1	\u02CC 2	= =			~ ~		- -		+ +
		\u2191 /	\u2193 \ 	\u02D0 >	\02D1 <

		& &		: :		, ,		É É		! !		Ð Ð
		( (		) )		. .		? ?		; ;
	}
}

		
 
section    top
   
   

Bundles

   
top

static int bundlecommand(ClientData clientData,Intr intr,int N,Tcl_Obj *const P[]) {
	switch (N) {
		case 1: {
			Tcl_Obj *wyrmBundlecommand(void);
			Obj bundle = wyrmBundlecommand();
			if (bundle) Tcl_SetObjResult(intr,bundle);
			else Tcl_ResetResult(intr);
		}	return TCL_OK;
		case 2: case 3: {
			extern int TclGetLoadedPackages(Intr intr,char *targetName);
			if (TclGetLoadedPackages(intr,0)!=TCL_OK) return TCL_ERROR;
			Obj list = incr(Tcl_GetObjResult(intr)); int NF; Obj *PF;
			if (Tcl_ListObjGetElements(intr,list,&NF,&PF)!=TCL_OK) {decr(list); return TCL_ERROR;}
			Obj loaded = 0;
			for (; !loaded && NF>0; NF--,PF++) {
				int NE; Obj *PE;
				if (Tcl_ListObjGetElements(0,*PF,&NE,&PE)!=TCL_OK || NE<2) continue;
				if (streq(Tcl_GetString(P[1]),Tcl_GetString(PE[1]))) loaded = incr(PE[0]);
			}
			decr(list);
			if (!loaded) {
				int domainindex; Obj f,fr;
				for (domainindex=0; domainstring[domainindex]!=0 && !loaded; domainindex++) {
					f = incr(wyrmFoldercommand(domainindex,0,0,0));
					if (f) {
						fr = oprintf("%{y-}s/%{y}s.framework",f,P[1]);
						if (Tcl_FSAccess(fr,0)==0) loaded = fr;
						else decr(fr);
					}
				}
			}
			if (!loaded) return rprintf(intr,"%!package not loaded: %{y}s",TCL_ERROR,P[1]);
			Obj pieces = incr(Tcl_FSSplitPath(loaded,0));
			if (Tcl_ListObjGetElements(0,pieces,&NF,&PF)!=TCL_OK) NF = 0;
			if (N==3) {
				chars suffix = Tcl_GetString(P[2]); if (*suffix=='.') suffix++;
				for (NF-=2; NF>=0; NF--) {
					chars piece = Tcl_GetString(PF[NF]);
					chars dot = strrchr(piece,'.');
					if (dot && streq(dot+1,suffix)) break;
				}
			}else {
				Obj *QF = nheap(NF,Obj); memcpy(QF,PF,NF*sizeof(Obj));
				Obj infoplist = incr(Tcl_NewStringObj("Info.plist",-1));
				for (NF-=2; NF>=0; NF--) {
					QF[NF+1] = infoplist;
					Obj t = incr(Tcl_NewListObj(NF+1,QF));
					Obj u = incr(Tcl_FSJoinPath(t,-1));
					int ok = Tcl_FSAccess(u,0);
					decr(t); decr(u);
					if (ok==0) break;
				}
				if (NF>0 & objeq("Contents",PF[NF])) NF--;
				decr(infoplist);
			}
			if (NF>=0) {
				Tcl_SetObjResult(intr,Tcl_FSJoinPath(pieces,NF+1));
			}else {
				Tcl_ResetResult(intr);
			}
			decr(loaded); decr(pieces);
		}	return TCL_OK;
		default: usage:
			Tcl_WrongNumArgs(intr,1,P,"[package [suffix]]");
			return TCL_ERROR;
	}
	return TCL_OK;
}

		
 
section    top

Tcl_Obj *wyrmBundlecommand(void) {
	CFBundleRef bundle = CFBundleGetMainBundle();
	CFURLRef url = bundle ? CFBundleCopyBundleURL(bundle) : 0;
	CFStringRef path = url ? CFURLCopyFileSystemPath(url,kCFURLPOSIXPathStyle) : 0;
	Tcl_Obj *r = path ? objcf(path) : 0;
	if (bundle) CFRelease(bundle);
	if (url) CFRelease(url);
	if (path) CFRelease(path);
	return r;
}

		
 
section    top

Tcl_CreateObjCommand(intr,"::wyrm::macosx::bundle",bundlecommand,0,0);

		
 
section    top
 
section    top
26. macosx::resource ::

NAME

macosx::resource — Get resource strings or paths from a bundle.

synopsis

wyrm::macosx::resource bundle-path [ -- ] [ resource ] [ .extension ] [ directory ]

wyrm::macosx::resource bundle-path -plugins|-frameworks|-shared|-support|-resource

wyrm::macosx::resource bundle-path -string key [ -default value ] [ -table name ]

wyrm::macosx::resource bundle-path -info [ key ]

description

Returns the path to a resource. If a package name is given, the resource whence it was loaded is returned, otherwise the resource of the main executable is returned. If the package was not in a resource, an empty string is returned.

If suffix is given, the resource is the innermost directory with that suffix. Otherwise the resource is the innermost directory with a Contents/info.plist or info.plist.


static int resourcecommand(ClientData clientData,Intr intr,int N,Tcl_Obj *const P[]) {
	int rc = TCL_ERROR; int x; Obj r;
	chars option[] = {"-plugins","-frameworks","-shared","-support","-resource","-string","-info","--",0};
	enum {o_plugins,o_frameworks,o_shared,o_support,o_resource,o_string,o_info,o_separator,o_file};
	chars resource,directory;
	if (N<3) {
		Tcl_WrongNumArgs(intr,1,P,"bundle-path ... resource");
		return TCL_ERROR;
	}
	if (Tcl_GetIndexFromObj(intr,P[2],(CONST char**)option,"option",TCL_EXACT,&x)==TCL_ERROR) x = o_file;
	switch (x) {
		case o_plugins:
			if (N==3) {
				Tcl_Obj *wyrmResourcecommandPlugins(Tcl_Obj *bundleObj);
				if (!(r=wyrmResourcecommandPlugins(P[1]))) {
					rprintf(intr,"bundle not found: %{y}s",P[1]); goto exit;
				}
				Tcl_SetObjResult(intr,r);
			}else {
				Tcl_WrongNumArgs(intr,1,P,"bundle-path -plugins"); goto exit;
			}
			break;
		case o_frameworks:
			if (N==3) {
				Tcl_Obj *wyrmResourcecommandPrivateFrameworksPath(Tcl_Obj *bundleObj);
				if (!(r=wyrmResourcecommandPrivateFrameworksPath(P[1]))) {
					rprintf(intr,"bundle not found: %{y}s",P[1]); goto exit;
				}
				Tcl_SetObjResult(intr,r);
			}else {
				Tcl_WrongNumArgs(intr,1,P,"bundle-path -frameworks"); goto exit;
			}
			break;
		case o_shared:
			if (N==3) {
				Tcl_Obj *wyrmResourcecommandSharedFrameworksPath(Tcl_Obj *bundleObj);
				if (!(r=wyrmResourcecommandSharedFrameworksPath(P[1]))) {
					rprintf(intr,"bundle not found: %{y}s",P[1]); goto exit;
				}
				Tcl_SetObjResult(intr,r);
			}else {
				Tcl_WrongNumArgs(intr,1,P,"bundle-path -shared"); goto exit;
			}
			break;
		case o_support:
			if (N==3) {
				Tcl_Obj *wyrmResourcecommandSharedSupportPath(Tcl_Obj *bundleObj);
				if (!(r=wyrmResourcecommandSharedSupportPath(P[1]))) {
					rprintf(intr,"bundle not found: %{y}s",P[1]); goto exit;
				}
				Tcl_SetObjResult(intr,r);
			}else {
				Tcl_WrongNumArgs(intr,1,P,"bundle-path -support"); goto exit;
			}
			break;
		case o_resource:
			if (N==3) {
				Tcl_Obj *wyrmResourcecommandResourcePath(Tcl_Obj *bundleObj);
				if (!(r=wyrmResourcecommandResourcePath(P[1]))) {
					rprintf(intr,"bundle not found: %{y}s",P[1]); goto exit;
				}
				Tcl_SetObjResult(intr,r);
			}else {
				Tcl_WrongNumArgs(intr,1,P,"bundle-path -resource"); goto exit;
			}
			break;
		case o_string:
			if (N<4) {
				Tcl_WrongNumArgs(intr,1,P,"bundle-path -string key..."); goto exit;
			}else {
				Tcl_Obj *wyrmResourcecommandStringForKey(Obj bundleObj,Obj keyObj,Obj valueObj,Obj tableObj);
				Obj bundle = P[1],key = P[3],value = 0,table = 0;
				for (N-=4,P+=4; N>0; N-=2,P+2) {
					if (N==1) {rprintf(intr,"missing value for %{y}s",*P); goto exit;}
					else if (objeq("-default",P[0])) value = P[1];
					else if (objeq("-table",P[0])) table = P[1];
					else {rprintf(intr,"unrecognised option: %{y}s",*P); goto exit;}
				}
				if (!(r=wyrmResourcecommandStringForKey(bundle,key,value,table))) {
					rprintf(intr,"bundle not found: %{y}s",P[1]); goto exit;
				}
				Tcl_SetObjResult(intr,r);
			}
			break;
		case o_info:
			if (N==3) {
				Tcl_Obj *wyrmResourcecommandInfoDictionary(Tcl_Obj *bundleObj);
				if (!(r=wyrmResourcecommandInfoDictionary(P[1]))) {
					rprintf(intr,"bundle not found: %{y}s",P[1]); goto exit;
				}
				Tcl_SetObjResult(intr,r);
			}else if (N==4) {
				Tcl_Obj *wyrmResourcecommandInfoDictionaryKey(Tcl_Obj *bundleObj,Tcl_Obj *keyObj);
				if (!(r=wyrmResourcecommandInfoDictionaryKey(P[1],P[3]))) {
					rprintf(intr,"bundle not found: %{y}s",P[1]); goto exit;
				}
				Tcl_SetObjResult(intr,r);
			}else {
				Tcl_WrongNumArgs(intr,1,P,"bundle-path -info key"); goto exit;
			}
		case o_separator:
			if (N!=4 && N!=5) {Tcl_WrongNumArgs(intr,1,P,"bundle-path -- resource [directory]"); goto exit;}
			if (N==4) {resource = Tcl_GetString(P[N-1]); directory = 0;}
			else {resource = Tcl_GetString(P[N-2]); directory = Tcl_GetString(P[N-1]);}
			goto file;
		case o_file:
			if (N!=3 && N!=4) {Tcl_WrongNumArgs(intr,1,P,"bundle-path resource [directory]"); goto exit;}
			if (N==3) {resource = Tcl_GetString(P[N-1]); directory = 0;}
			else {resource = Tcl_GetString(P[N-2]); directory = Tcl_GetString(P[N-1]);}
		file: {
			chars dot = strrchr(resource,'.');
			if (dot && dot==resource) {
				Tcl_Obj *wyrmResourcecommandPaths(Tcl_Obj *bundleObj,char *extension,char *directory);
				Obj L = wyrmResourcecommandPaths(P[1],dot,directory);
				Tcl_SetObjResult(intr,L); decr(L);
			}else {
				Tcl_Obj *wyrmResourcecommandPath(Tcl_Obj *bundleObj,char *resource,char *dot,char *directory);
				if (!(r=wyrmResourcecommandPath(P[1],resource,dot,directory))) {
					rprintf(intr,"bundle not found: %{y}s",P[1]); goto exit;
				}
				Tcl_SetObjResult(intr,r);
			}
		}   break;
	}
	rc = TCL_OK;
exit:
	return rc;
}

		
 
section    top

Tcl_Obj *wyrmResourcecommandPlugins(Tcl_Obj *bundleObj) {
	CFStringRef bundlepath = cfobj(bundleObj);
	CFURLRef bundleurl = bundlepath ? CFURLCreateWithFileSystemPath(NULL,bundlepath,kCFURLPOSIXPathStyle,0) : 0;
	CFBundleRef bundle = bundleurl ? CFBundleCreate(NULL,bundleurl) : 0;
	CFURLRef url = bundle ? CFBundleCopyBuiltInPlugInsURL(bundle) : 0;
	CFStringRef path = url ? CFURLCopyFileSystemPath(url,kCFURLPOSIXPathStyle) : 0;
	Tcl_Obj *r = path ? objcf(path) : 0;
	if (bundlepath) CFRelease(bundlepath);
	if (bundleurl) CFRelease(bundleurl);
	if (bundle) CFRelease(bundle);
	if (url) CFRelease(url);
	if (path) CFRelease(path);
	return r;
}

Tcl_Obj *wyrmResourcecommandPrivateFrameworksPath(Tcl_Obj *bundleObj) {
	CFStringRef bundlepath = cfobj(bundleObj);
	CFURLRef bundleurl = bundlepath ? CFURLCreateWithFileSystemPath(NULL,bundlepath,kCFURLPOSIXPathStyle,0) : 0;
	CFBundleRef bundle = bundleurl ? CFBundleCreate(NULL,bundleurl) : 0;
	CFURLRef url = bundle ? CFBundleCopyPrivateFrameworksURL(bundle) : 0;
	CFStringRef path = url ? CFURLCopyFileSystemPath(url,kCFURLPOSIXPathStyle) : 0;
	Tcl_Obj *r = path ? objcf(path) : 0;
	if (bundlepath) CFRelease(bundlepath);
	if (bundleurl) CFRelease(bundleurl);
	if (bundle) CFRelease(bundle);
	if (url) CFRelease(url);
	if (path) CFRelease(path);
	return r;
}

Tcl_Obj *wyrmResourcecommandSharedFrameworksPath(Tcl_Obj *bundleObj) {
	CFStringRef bundlepath = cfobj(bundleObj);
	CFURLRef bundleurl = bundlepath ? CFURLCreateWithFileSystemPath(NULL,bundlepath,kCFURLPOSIXPathStyle,0) : 0;
	CFBundleRef bundle = bundleurl ? CFBundleCreate(NULL,bundleurl) : 0;
	CFURLRef url = bundle ? CFBundleCopySharedFrameworksURL(bundle) : 0;
	CFStringRef path = url ? CFURLCopyFileSystemPath(url,kCFURLPOSIXPathStyle) : 0;
	Tcl_Obj *r = path ? objcf(path) : 0;
	if (bundlepath) CFRelease(bundlepath);
	if (bundleurl) CFRelease(bundleurl);
	if (bundle) CFRelease(bundle);
	if (url) CFRelease(url);
	if (path) CFRelease(path);
	return r;
}

Tcl_Obj *wyrmResourcecommandSharedSupportPath(Tcl_Obj *bundleObj) {
	CFStringRef bundlepath = cfobj(bundleObj);
	CFURLRef bundleurl = bundlepath ? CFURLCreateWithFileSystemPath(NULL,bundlepath,kCFURLPOSIXPathStyle,0) : 0;
	CFBundleRef bundle = bundleurl ? CFBundleCreate(NULL,bundleurl) : 0;
	CFURLRef url = bundle ? CFBundleCopySharedSupportURL(bundle) : 0;
	CFStringRef path = url ? CFURLCopyFileSystemPath(url,kCFURLPOSIXPathStyle) : 0;
	Tcl_Obj *r = path ? objcf(path) : 0;
	if (bundlepath) CFRelease(bundlepath);
	if (bundleurl) CFRelease(bundleurl);
	if (bundle) CFRelease(bundle);
	if (url) CFRelease(url);
	if (path) CFRelease(path);
	return r;
}

Tcl_Obj *wyrmResourcecommandResourcePath(Tcl_Obj *bundleObj) {
	CFStringRef bundlepath = cfobj(bundleObj);
	CFURLRef bundleurl = bundlepath ? CFURLCreateWithFileSystemPath(NULL,bundlepath,kCFURLPOSIXPathStyle,0) : 0;
	CFBundleRef bundle = bundleurl ? CFBundleCreate(NULL,bundleurl) : 0;
	CFURLRef url = bundle ? CFBundleCopyResourcesDirectoryURL(bundle) : 0;
	CFStringRef path = url ? CFURLCopyFileSystemPath(url,kCFURLPOSIXPathStyle) : 0;
	Tcl_Obj *r = path ? objcf(path) : 0;
	if (bundlepath) CFRelease(bundlepath);
	if (bundleurl) CFRelease(bundleurl);
	if (bundle) CFRelease(bundle);
	if (url) CFRelease(url);
	if (path) CFRelease(path);
	return r;
}

Tcl_Obj *wyrmResourcecommandStringForKey(Tcl_Obj *bundleObj,Tcl_Obj *keyObj,Tcl_Obj *valueObj,Tcl_Obj *tableObj) {
	CFStringRef bundlepath = cfobj(bundleObj);
	CFStringRef keyStr = cfobj(keyObj);
	CFStringRef valueStr = valueObj ? cfobj(valueObj) : 0;
	CFStringRef tableStr = tableObj ? cfobj(tableObj) : 0;
	CFURLRef bundleurl = bundlepath ? CFURLCreateWithFileSystemPath(NULL,bundlepath,kCFURLPOSIXPathStyle,0) : 0;
	CFBundleRef bundle = bundleurl ? CFBundleCreate(NULL,bundleurl) : 0;
	CFStringRef result = bundle ? CFBundleCopyLocalizedString(bundle,keyStr,valueStr,tableStr) : 0;
	Tcl_Obj *r = result ? objcf(result) : 0;
	if (bundlepath) CFRelease(bundlepath);
	if (bundleurl) CFRelease(bundleurl);
	if (bundle) CFRelease(bundle);
	CFRelease(keyStr);
	if (valueStr) CFRelease(valueStr);
	if (tableStr) CFRelease(tableStr);
	if (result) CFRelease(result);
	return r;
}

Tcl_Obj *wyrmResourcecommandInfoDictionary(Tcl_Obj *bundleObj) {
	CFStringRef bundlepath = cfobj(bundleObj);
	CFURLRef bundleurl = bundlepath ? CFURLCreateWithFileSystemPath(NULL,bundlepath,kCFURLPOSIXPathStyle,0) : 0;
	CFBundleRef bundle = bundleurl ? CFBundleCreate(NULL,bundleurl) : 0;
	CFTypeRef result = bundle ? CFBundleGetLocalInfoDictionary(bundle) : 0;
	CFStringRef desc = result ? CFCopyDescription(result) : 0;
	Tcl_Obj *r = desc ? objcf(desc) : 0;
	if (bundlepath) CFRelease(bundlepath);
	if (bundleurl) CFRelease(bundleurl);
	if (bundle) CFRelease(bundle);
	if (result) CFRelease(result);
	if (desc) CFRelease(desc);
	return r;
}

Tcl_Obj *wyrmResourcecommandInfoDictionaryKey(Tcl_Obj *bundleObj,Tcl_Obj *keyObj) {
	CFStringRef bundlepath = cfobj(bundleObj);
	CFStringRef keyStr = cfobj(keyObj);
	CFURLRef bundleurl = bundlepath ? CFURLCreateWithFileSystemPath(NULL,bundlepath,kCFURLPOSIXPathStyle,0) : 0;
	CFBundleRef bundle = bundleurl ? CFBundleCreate(NULL,bundleurl) : 0;
	CFTypeRef result = bundle ? CFBundleGetValueForInfoDictionaryKey(bundle,keyStr) : 0;
	CFStringRef desc = result ? CFCopyDescription(result) : 0;
	Tcl_Obj *r = desc ? objcf(desc) : 0;
	if (bundlepath) CFRelease(bundlepath);
	if (bundleurl) CFRelease(bundleurl);
	if (bundle) CFRelease(bundle);
	CFRelease(keyStr);
	if (result) CFRelease(result);
	if (desc) CFRelease(desc);
	return r;
}

Tcl_Obj *wyrmResourcecommandPaths(Tcl_Obj *bundleObj,char *extension,char *directory) {
	CFStringRef bundlepath = cfobj(bundleObj);
	CFStringRef extensionStr = extension
					? CFStringCreateWithCString(NULL,extension+1,kCFStringEncodingUTF8)
					: 0;
	CFStringRef directoryStr = directory
					? CFStringCreateWithCString(NULL,directory,kCFStringEncodingUTF8)
					: 0;
	CFURLRef bundleurl = bundlepath ? CFURLCreateWithFileSystemPath(NULL,bundlepath,kCFURLPOSIXPathStyle,0) : 0;
	CFBundleRef bundle = bundleurl ? CFBundleCreate(NULL,bundleurl) : 0;
	CFArrayRef array = CFBundleCopyResourceURLsOfType(bundle,extensionStr,directoryStr);
	Tcl_Obj *L = Tcl_NewObj(); Tcl_IncrRefCount(L); int i;
	for (i=0; i<CFArrayGetCount(array); i++) {
		void *element = CFArrayGetValueAtIndex(array,i);
		CFStringRef desc = element ? CFCopyDescription(element) : 0;
		Tcl_Obj *r = desc ? objcf(desc) : 0;
		if (r) Tcl_ListObjAppendElement(0,L,r);
		if (element) CFRelease(element);
		if (desc) CFRelease(desc);
	}
	if (bundlepath) CFRelease(bundlepath);
	if (extensionStr) CFRelease(extensionStr);
	if (directoryStr) CFRelease(directoryStr);
	if (bundleurl) CFRelease(bundleurl);
	if (bundle) CFRelease(bundle);
	if (array) CFRelease(array);
	return L;
}

Tcl_Obj *wyrmResourcecommandPath(Tcl_Obj *bundleObj,char *resource,char *extension,char *directory) {
	CFStringRef bundlepath = cfobj(bundleObj);
	CFStringRef resourceStr = extension
					? CFStringCreateWithBytes(0,resource,extension-resource,kCFStringEncodingUTF8,false)
					: CFStringCreateWithCString(NULL,resource,kCFStringEncodingUTF8);
	CFStringRef extensionStr = extension
					? CFStringCreateWithCString(NULL,extension+1,kCFStringEncodingUTF8)
					: 0;
	CFStringRef directoryStr = directory
					? CFStringCreateWithCString(NULL,directory,kCFStringEncodingUTF8)
					: 0;
	CFURLRef bundleurl = bundlepath ? CFURLCreateWithFileSystemPath(NULL,bundlepath,kCFURLPOSIXPathStyle,0) : 0;
	CFBundleRef bundle = bundleurl ? CFBundleCreate(NULL,bundleurl) : 0;
	CFURLRef url = bundle ? CFBundleCopyResourceURL(bundle,resourceStr,extensionStr,directoryStr) : 0;
	CFStringRef path = url ? CFURLCopyFileSystemPath(url,kCFURLPOSIXPathStyle) : 0;
	Tcl_Obj *r = path ? objcf(path) : 0;
	if (bundlepath) CFRelease(bundlepath);
	if (resourceStr) CFRelease(resourceStr);
	if (extensionStr) CFRelease(extensionStr);
	if (directoryStr) CFRelease(directoryStr);
	if (bundleurl) CFRelease(bundleurl);
	if (bundle) CFRelease(bundle);
	if (url) CFRelease(url);
	if (path) CFRelease(path);
	return r;
}

		
 
section    top

Tcl_CreateObjCommand(intr,"::wyrm::macosx::resource",resourcecommand,0,0);

		
 
section    top
   
   

Apple Scripts

   
top
30. macosx::parse ::

NAME

macosx::parse — Parse the output of osascript -s.

synopsis

wyrm::macosx::parse [exec osascript -s s << applescript]

description

Converts the typed structured returned value from osascript into a form that is, perhaps, easier to process for typeless Tcl.

List: {element, element, ... element}
or [element, element, ... element]
becomes {#list element element ... element}.
Record: {tag:value, , ... tag:value}
becomes {#record tag value ... tag value}.
String: "escaped-string"
becomes #string unescaped-string. Escape backslashes in escaped-string are removed in unescaped-string.
Phrases and numbers
The same. In a list or record, each phrase is a single list element even when comprised of multiple words.
Typed string: word ... word "escaped-string"
becomes {word ... word #string unescaped-string}. For example, application proces "TextEdit" becomes {application process #string TextEdit}.

Example

{1, 2, 3, {a: file "central dogma:adam",aba cab: missing value}}

would become

{#list 1 2 3 {#record a {file {#string {central dogma:adam}}} {aba cab} {missing value}}}


proc ::wyrm::macosx::parse {string args} {
	if {[llength $args]} {
		::wyrm::setp o $args
	} else {
		set tokens {}
		set token ""
		set string [split $string {}]
		for {set i 0} {$i<[llength $string]} {incr i} {
			set ch [lindex $string $i]
			switch -- $ch {
				\[ - \] - \{ - \} {
					if {[string length $token]} {lappend tokens $token; set token ""}
					lappend tokens #[scan $ch %c]#
				}
				, - : {
					if {[string length $token]} {lappend tokens $token; set token ""}
					lappend tokens $ch
				}
				" " - "\t" - "\n" {
					if {[string length $token]} {lappend tokens $token; set token ""}
				}
				\" {
					if {[string length $token]} {lappend tokens $token; set token ""}
					lappend tokens \"
					set token ""
					for {incr i} {$i<[llength $string]} {incr i} {
						if {[string equal [lindex $string $i] \\]} {
							incr i; append token [lindex $string $i]
						} elseif {[string equal [lindex $string $i] \"]} {
							break
						} else {
							append token [lindex $string $i]
						}
					}
					lappend tokens $token; set token ""
				}
				default {
					append token $ch
				}
			}
		}
		set string $tokens
		set o 0
	}
	for {set value {}} {$o<[llength $string]} {incr o} {
		switch -- [lindex $string $o] {
			#123#  - #91# {
				lappend value #list
				while {$o<[llength $string]} {
					::wyrm::setp {element o} [::wyrm::macosx::parse $string [expr {$o+1}]]
					if {[llength $element]} {lappend value $element}
					switch [lindex $string $o] {
						: {lset value 0 #record}
						#125# - #93# break
					}
				}
			}
			\" {
				lappend value #string
				incr o; lappend value [lindex $string $o]
			}
			, - : - #125# - #93# {
				break
			}
			default {
				lappend value [lindex $string $o]
			}
		}
	}
	if {[llength $args]} {
		return [list $value $o]
	} else {
		return $value
	}
}

		
 
section    top
 
section    top

proc ::wyrm::macosx::download {url destination args} {
	set destination [string map {\\ \\\\ \" \\\"} [::wyrm::macosx::path hfs [file normalize $destination]]]
	set message "download \"$url\" to file \"$destination\""
	while {[llength $args]} {
		set args [::wyrm::setp option $args]
		switch -glob -- $option {
			-r* {append message " replacing yes"}
			-pa* {append message " without unpacking"}
			-pr* {append message " with progress"}
			-f* {
				set args [::wyrm::setp form $args]
				set form [string map {\\ \\\\ \" \\\"} $form]
				append message " form data \"$form\""
			}
			-l* {append message " with directory listing"}
			-d* {append message " with download directory"}
			-a* {append message " with authentication"}
			default {error "unknown option: $option"}
		}
	}
	set script "tell application \"URL Access Scripting\" to $message"
	exec osascript << $script
}

		
 
section    top
 
section    top

proc ::wyrm::macosx::upload {source url args} {
	set source [string map {\\ \\\\ \" \\\"} [::wyrm::macosx::path hfs $source]]
	set message "upload to file \"$destination\" to \"$url\""
	while {[llength $args]} {
		set args [::wyrm::setp option $args]
		switch -glob -- $option {
			-r* {append message " replacing yes"}
			-un* {append message " without bin hexing"}
			-p* {append message " with progress"}
			-d* {append message " with upload directory"}
			-a* {append message " with authentication"}
			default {error "unknown option: $option"}
		}
	}
	exec osascript << "tell application \"URL Access Scripting\" to $message"
}

		
 
section    top
 
section    top
36. macosx::UIscript_check ::

NAME

macosx::UIscript_check — Verify UI scripting is available.

synopsis

wyrm::macosx::UIscript_check [ -dialog ]

description

Verify UI scripting is possible. Returns a true or false value. If -dialog is specified, a dialog will be displayed prompting the user to turn it on.

This code is adapted from http://www.apple.com/applescript/uiscripting/01.html .

Quoth:

Checking Accessibility Status
In order for UI scripts to function, the accessibility frameworks must be active. The System Events application has a special property named UI elements enabled that returns a value of true or false depending upon the current accessibility state. Include the following sub-routine in your scripts to check for the presence of Mac OS X v.10.3 and an activated GUI Scripting architecture.
If you're using Mac OS X version 10.3, click this icon to automatically open the script example in the Script Editor application.

The Apple code has been modified to make it a Tcl proc instead of an Applescript subroutine, and to make the dialog optional.


proc ::wyrm::macosx::UIscript_check args {
	switch [llength $args] {
		0 {set dialog false}
		1 {
			if {![string match -d* [lindex $args 0]]} {
				error {usage: ::wyrm::macosx::UIscript_check -dialog}
			}
			set dialog true
		}
		default {
			error {wrong number of parameters: ::wyrm::macosx::UIscript_check [-dialog]}
		}
	}
	expr {![string equal false [exec osascript << "
		-- get the system version
		set the hexData to system attribute \"sysv\"
		set hexString to {}
		repeat 4 times
			set hexString to ((hexData mod 16) as string) & hexString
			set hexData to hexData div 16
		end repeat
		set the OS_version to the hexString as string
		if the OS_version is less than \"1030\" then
			return false
		end if
		-- check to see if assistive devices is enabled
		tell application \"System Events\"
			set UI_enabled to UI elements enabled
		end tell
		set showDialog to $dialog
		if UI_enabled is false and showDialog then
			tell application \"System Preferences\"
				activate
				set current pane to pane \"com.apple.preference.universalaccess\"
				display dialog \"This script utilizes the built-in Graphic User\
Interface Scripting architecture of Mac OS X which is currently\
disabled.\" & return & return & \"You can activate GUI Scripting by\
selecting the checkbox \\\"Enable access for assistive devices\\\" in the\
Universal Access preference pane.\" with icon 1 buttons {\"Cancel\"} default button 1
			end tell
		end if
		tell application \"System Events\"
			set UI_enabled to UI elements enabled
		end tell
		return UI_enabled
	"]]}
}

		
 
section    top
 
section    top
38. macosx::dumpUI ::

NAME

macosx::dumpUI — Dump the controls of a running process.

synopsis

wyrm::macosx::dumpUI application-name

description

Analyze the controls, open windows, dialogs, menus, and so forth of a running program and dump them out in the form of nested AppleScript tell statements. The accessibility framework must be active.

To use this command, start the target application from the Finder and open whatever windows and dialogs you wish to capture. (Menus do not have to be open to be dumped.) Then call wyrm::macosx::dumpUI with the application name, such as

wyrm::macosx::dumpUI TextEdit
The command will then interrogate the running program and return the controls in a form compatiable with the UI scripting. The result is in the general form
tell application "System Events"
tell application process "application-name"
tell window "window-name"
tell UI-element "element-name" or element-number
...
click innermost-UI-element element-name
...
end tell
end tell
...
tell menu bar 1
menu items
end tell
end tell
end tell
Additional identifying information is added as comments (with --). The click lines identify bottommost elements of the hierarchy with no subelements. tell lines identify hierarchial elements and their subelements.

To create a UI script, the above output can be editted to include only those elements and their surrounding tells and end tells. Replace the click with the appropriate command (just click or perhaps select or set a property of the element.) Where actions are defined for an element, a perform of that action is included.

caveats

This is slow. If you think it is hung, it is not. It is just a slow way to get the information out of the application.


proc ::wyrm::macosx::appleify {x} {
	upvar 1 ui ui
	set class $ui($x.class)
	set identity "$class "
	set sep " -- "; set d ""
	if {[info exists ui($x.index)]} {
		append identity $ui($x.index)
		if {[info exists ui($x.name)]} {
			append d "$sep$ui($x.name)"; set sep " "
		}
	} else {
		append identity $ui($x.name)
	}
	foreach field {role subrole description title} {
		if {[info exists ui($x.$field)]} {
			append d "$sep$field: [string map {\" {} AX {}} $ui($x.$field)]"
			set sep " "
		}
	}
	if {[info exists ui($x.children)] && [llength $ui($x.children)]} {
		set a "tell $identity$d\n"
		if {[info exist ui($x.actions)]} {
			foreach action $ui($x.actions) {
				append a " perform action $action\n"
			}
		}
		append a " " [string map {\n "\n "} [join $ui($x.children) \n]] \n
		append a "end tell -- $identity"
	} else {
		set a "click $identity$d"
		if {[info exist ui($x.actions)]} {
			foreach action $ui($x.actions) {
				append a "\nperform action $action of $identity"
			}
		}
	}
	return $a
}

proc ::wyrm::macosx::dumpUI {process ele