/* * shellicon.c -- * * This is a Tk extension that adds a new element type to TkTreeCtrl. * The element type's name is "shellicon". A shellicon element can * display the icon for a file or folder using Win32 Shell API calls. * * Copyright (c) 2005 Tim Baker * * RCS: @(#) $Id: shellicon.c,v 1.1 2005/07/10 22:44:12 treectrl Exp $ */ #include "tkTreeCtrl.h" #include "tkTreeElem.h" #include "tkWinInt.h" #ifndef _WIN32_IE #define _WIN32_IE 0x0501 #endif #include #include #include #include #ifndef SHGFI_ADDOVERLAYS #if (_WIN32_IE >= 0x0500) #define SHGFI_ADDOVERLAYS 0x000000020 #define SHGFI_OVERLAYINDEX 0x000000040 #endif #endif #ifndef SHIL_LARGE #define SHIL_LARGE 0 #define SHIL_SMALL 1 #define SHIL_EXTRALARGE 2 #if 0 const GUID IID_IImageList = {0x2C247F21, 0x8591, 0x11D1,{ 0xB1, 0x6A, 0x00,0xC0, 0xF0, 0x28,0x36, 0x28} }; HRESULT (*SHGetImageListProc)(int iImageList, REFIID riid, void **ppv); #endif #endif HIMAGELIST gImgListSmall = NULL; HIMAGELIST gImgListLarge = NULL; TreeCtrlStubs *stubs; #define TreeCtrl_RegisterElementType(i,t) \ stubs->TreeCtrl_RegisterElementType(i,t) #define Tree_RedrawElement(t,i,e) \ stubs->Tree_RedrawElement(t,i,e) #define Tree_ElementIterateBegin(t,et) \ stubs->Tree_ElementIterateBegin(t,et) #define Tree_ElementIterateNext(i) \ stubs->Tree_ElementIterateNext(i) #define Tree_ElementIterateGet(i) \ stubs->Tree_ElementIterateGet(i) #define Tree_ElementIterateChanged(i,m) \ stubs->Tree_ElementIterateChanged(i,m) #define PerStateInfo_Free(t,ty,in) \ stubs->PerStateInfo_Free(t,ty,in) #define PerStateInfo_FromObj(t,pr,ty,in) \ stubs->PerStateInfo_FromObj(t,pr,ty,in) #define PerStateInfo_Undefine(t,ty,in,st) \ stubs->PerStateInfo_Undefine(t,ty,in,st) #define pstBoolean \ (*stubs->pstBoolean) #define PerStateBoolean_ForState(t,in,st,ma) \ stubs->PerStateBoolean_ForState(t,in,st,ma) #define PSTSave(in,sa) \ stubs->PSTSave(in,sa) #define PSTRestore(t,ty,in,sa) \ stubs->PSTRestore(t,ty,in,sa) #define TreeStateFromObj \ stubs->TreeStateFromObj #define BooleanCO_Init(ot,on) \ stubs->BooleanCO_Init(ot,on) #define StringTableCO_Init(ot,on,ta) \ stubs->StringTableCO_Init(ot,on,ta) static void AdjustForSticky(int sticky, int cavityWidth, int cavityHeight, int expandX, int expandY, int *xPtr, int *yPtr, int *widthPtr, int *heightPtr) { int dx = 0; int dy = 0; if (cavityWidth > *widthPtr) { dx = cavityWidth - *widthPtr; } if (cavityHeight > *heightPtr) { dy = cavityHeight - *heightPtr; } if ((sticky & STICKY_W) && (sticky & STICKY_E)) { if (expandX) *widthPtr += dx; else sticky &= ~(STICKY_W | STICKY_E); } if ((sticky & STICKY_N) && (sticky & STICKY_S)) { if (expandY) *heightPtr += dy; else sticky &= ~(STICKY_N | STICKY_S); } if (!(sticky & STICKY_W)) { *xPtr += (sticky & STICKY_E) ? dx : dx / 2; } if (!(sticky & STICKY_N)) { *yPtr += (sticky & STICKY_S) ? dy : dy / 2; } } /* This macro gets the value of a per-state option for an element, then * looks for a better match from the master element if it exists */ #define OPTION_FOR_STATE(xFUNC,xTYPE,xVAR,xFIELD,xSTATE) \ xVAR = xFUNC(tree, &elemX->xFIELD, xSTATE, &match); \ if ((match != MATCH_EXACT) && (masterX != NULL)) { \ xTYPE varM = xFUNC(tree, &masterX->xFIELD, xSTATE, &match2); \ if (match2 > match) \ xVAR = varM; \ } #define BOOLEAN_FOR_STATE(xVAR,xFIELD,xSTATE) \ OPTION_FOR_STATE(PerStateBoolean_ForState,int,xVAR,xFIELD,xSTATE) typedef struct ElementShellIcon ElementShellIcon; struct ElementShellIcon { Element header; PerStateInfo draw; Tcl_Obj *pathObj; /* path of file or directory */ char *path; Tcl_Obj *widthObj; int width; Tcl_Obj *heightObj; int height; #define TYPE_DIRECTORY 0 #define TYPE_FILE 1 int type; /* If specified, 'path' is assumed to exist */ #define SIZE_LARGE 0 #define SIZE_SMALL 1 int size; /* SIZE_LARGE if unspecified */ HIMAGELIST hImgList; /* the system image list */ int iIcon; /* index into hImgList */ int addOverlays; /* only when useImgList is FALSE */ int useImgList; /* if false, create icons */ HICON hIcon; /* icon */ HICON hIconSel; /* selected icon */ }; #define SHELLICON_CONF_ICON 0x0001 #define SHELLICON_CONF_SIZE 0x0002 #define SHELLICON_CONF_DRAW 0x0004 static CONST char *sizeST[] = { "large", "small", (char *) NULL }; static CONST char *typeST[] = { "directory", "file", (char *) NULL }; static Tk_OptionSpec shellIconOptionSpecs[] = { {TK_OPTION_CUSTOM, "-addoverlays", (char *) NULL, (char *) NULL, (char *) NULL, -1, Tk_Offset(ElementShellIcon, addOverlays), TK_OPTION_NULL_OK, (ClientData) NULL, SHELLICON_CONF_ICON}, {TK_OPTION_STRING, "-draw", (char *) NULL, (char *) NULL, (char *) NULL, Tk_Offset(ElementShellIcon, draw.obj), -1, TK_OPTION_NULL_OK, (ClientData) NULL, SHELLICON_CONF_DRAW}, {TK_OPTION_PIXELS, "-height", (char *) NULL, (char *) NULL, (char *) NULL, Tk_Offset(ElementShellIcon, heightObj), Tk_Offset(ElementShellIcon, height), TK_OPTION_NULL_OK, (ClientData) NULL, SHELLICON_CONF_SIZE}, {TK_OPTION_STRING, "-path", (char *) NULL, (char *) NULL, (char *) NULL, Tk_Offset(ElementShellIcon, pathObj), Tk_Offset(ElementShellIcon, path), TK_OPTION_NULL_OK, (ClientData) NULL, SHELLICON_CONF_ICON}, {TK_OPTION_CUSTOM, "-size",(char *) NULL, (char *) NULL, (char *) NULL, -1, Tk_Offset(ElementShellIcon, size), TK_OPTION_NULL_OK, (ClientData) NULL, SHELLICON_CONF_ICON}, {TK_OPTION_CUSTOM, "-type", (char *) NULL, (char *) NULL, (char *) NULL, -1, Tk_Offset(ElementShellIcon, type), TK_OPTION_NULL_OK, (ClientData) NULL, SHELLICON_CONF_ICON}, {TK_OPTION_CUSTOM, "-useimagelist", (char *) NULL, (char *) NULL, (char *) NULL, -1, Tk_Offset(ElementShellIcon, useImgList), TK_OPTION_NULL_OK, (ClientData) NULL, SHELLICON_CONF_ICON}, {TK_OPTION_PIXELS, "-width", (char *) NULL, (char *) NULL, (char *) NULL, Tk_Offset(ElementShellIcon, widthObj), Tk_Offset(ElementShellIcon, width), TK_OPTION_NULL_OK, (ClientData) NULL, SHELLICON_CONF_SIZE}, {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL, (char *) NULL, 0, -1, 0, (ClientData) NULL, 0} }; static void LoadIconIfNeeded(ElementArgs *args) { TreeCtrl *tree = args->tree; Element *elem = args->elem; ElementShellIcon *elemX = (ElementShellIcon *) elem; ElementShellIcon *masterX = (ElementShellIcon *) elem->master; SHFILEINFO sfi; Tcl_DString dString1, dString2; UINT uFlags = SHGFI_SYSICONINDEX; DWORD dwFileAttributes = 0; char *nativePath; int size = SIZE_LARGE; int overlays = 1; int type = -1; int useImgList = TRUE; HRESULT result; /* -useimagelist boolean */ if (elemX->useImgList != -1) useImgList = elemX->useImgList; else if (masterX != NULL && masterX->useImgList != -1) useImgList = masterX->useImgList; /* Not using the system image list. */ if (!useImgList) { /* Already have an icon, or no path is given so no icon used */ if ((elemX->hIcon != NULL) || (elemX->path == NULL)) return; /* Equivalent to "file nativename $path" */ nativePath = Tcl_TranslateFileName(tree->interp, elemX->path, &dString1); if (nativePath == NULL) return; /* This will be passed to system calls, so convert from UTF8 */ nativePath = Tcl_UtfToExternalDString(NULL, nativePath, -1, &dString2); uFlags = SHGFI_ICON; /* -addoverlays boolean */ if (elemX->addOverlays != -1) overlays = elemX->addOverlays; else if (masterX != NULL && masterX->addOverlays != -1) overlays = masterX->addOverlays; if (overlays) uFlags |= SHGFI_ADDOVERLAYS; /* -size small or large */ if (elemX->size != -1) size = elemX->size; else if (masterX != NULL && masterX->size != -1) size = masterX->size; switch (size) { case SIZE_LARGE: uFlags |= SHGFI_LARGEICON | SHGFI_SHELLICONSIZE; break; case SIZE_SMALL: uFlags |= SHGFI_SMALLICON | SHGFI_SHELLICONSIZE; break; } /* -type file or -type directory */ if (elemX->type != -1) type = elemX->type; else if (masterX != NULL && masterX->type != -1) type = masterX->type; /* If SHGFI_USEFILEATTRIBUTES is set, SHGetFileInfo is supposed to * assume that the file is real but not look for it on disk. This * can be used to get the icon for a certain type of file, ex *.exe. * In practice, lots of files get a non-generic icon when a * valid file path is given. */ if (type != -1) { dwFileAttributes = (type == TYPE_FILE) ? FILE_ATTRIBUTE_NORMAL : FILE_ATTRIBUTE_DIRECTORY; uFlags |= SHGFI_USEFILEATTRIBUTES; } /* MSDN says SHGFI_OPENICON returns the image list containing the * small open icon. In practice the large open icon gets returned * for SHGFI_LARGEICON, so we support it. */ if (/*(size == SIZE_SMALL) && */(args->state & STATE_OPEN)) uFlags |= SHGFI_OPENICON; CoInitialize(NULL); result = SHGetFileInfo( nativePath, dwFileAttributes, &sfi, sizeof(sfi), uFlags); if (result) { elemX->hIcon = sfi.hIcon; /* Remember the image list so we can get the icon size */ elemX->hImgList = (size == SIZE_LARGE) ? gImgListLarge : gImgListSmall; } result = SHGetFileInfo( nativePath, dwFileAttributes, &sfi, sizeof(sfi), uFlags | SHGFI_SELECTED); if (result) elemX->hIconSel = sfi.hIcon; CoUninitialize(); Tcl_DStringFree(&dString1); Tcl_DStringFree(&dString2); return; } /* Using the system image list */ if ((elemX->hImgList == NULL) && (elemX->path != NULL)) { /* Equivalent to "file nativename $path" */ nativePath = Tcl_TranslateFileName(tree->interp, elemX->path, &dString1); if (nativePath == NULL) return; /* This will be passed to system calls, so convert from UTF8 */ nativePath = Tcl_UtfToExternalDString(NULL, nativePath, -1, &dString2); /* -size small or large */ if (elemX->size != -1) size = elemX->size; else if (masterX != NULL && masterX->size != -1) size = masterX->size; switch (size) { case SIZE_SMALL: uFlags |= SHGFI_SMALLICON | SHGFI_SHELLICONSIZE; break; case SIZE_LARGE: uFlags |= SHGFI_LARGEICON | SHGFI_SHELLICONSIZE; break; } /* -type file or -type directory */ if (elemX->type != -1) type = elemX->type; else if (masterX != NULL && masterX->type != -1) type = masterX->type; /* If SHGFI_USEFILEATTRIBUTES is set, SHGetFileInfo is supposed to * assume that the file is real but not look for it on disk. This * can be used to get the icon for a certain type of file, ex *.exe. * In practice, lots of files get a non-generic icon when a * valid file path is given. */ if (type != -1) { dwFileAttributes = (type == TYPE_FILE) ? FILE_ATTRIBUTE_NORMAL : FILE_ATTRIBUTE_DIRECTORY; uFlags |= SHGFI_USEFILEATTRIBUTES; } /* MSDN says SHGFI_OPENICON returns the image list containing the * small open icon. In practice the large open icon gets returned * for SHGFI_LARGEICON. */ if (/*(size == SIZE_SMALL) && */(args->state & STATE_OPEN)) uFlags |= SHGFI_OPENICON; CoInitialize(NULL); elemX->hImgList = (HIMAGELIST) SHGetFileInfo( nativePath, dwFileAttributes, &sfi, sizeof(sfi), uFlags); if (elemX->hImgList != NULL) { elemX->iIcon = sfi.iIcon; } CoUninitialize(); Tcl_DStringFree(&dString1); Tcl_DStringFree(&dString2); } } static void ForgetIcon(ElementShellIcon *elemX) { if (elemX->hIcon != NULL) DestroyIcon(elemX->hIcon); if (elemX->hIconSel != NULL) DestroyIcon(elemX->hIconSel); elemX->hImgList = NULL; elemX->hIcon = elemX->hIconSel = NULL; } static void DeleteProcShellIcon(ElementArgs *args) { TreeCtrl *tree = args->tree; Element *elem = args->elem; ElementShellIcon *elemX = (ElementShellIcon *) elem; PerStateInfo_Free(tree, &pstBoolean, &elemX->draw); ForgetIcon(elemX); } static int WorldChangedProcShellIcon(ElementArgs *args) { ElementShellIcon *elemX = (ElementShellIcon *) args->elem; int flagM = args->change.flagMaster; int flagS = args->change.flagSelf; int mask = 0; if ((flagS | flagM) & (SHELLICON_CONF_ICON | SHELLICON_CONF_SIZE)) mask |= CS_DISPLAY | CS_LAYOUT; if ((flagS | flagM) & (SHELLICON_CONF_DRAW)) mask |= CS_DISPLAY; if ((flagS | flagM) & SHELLICON_CONF_ICON) { ForgetIcon(elemX); } return mask; } static int ConfigProcShellIcon(ElementArgs *args) { TreeCtrl *tree = args->tree; Element *elem = args->elem; ElementShellIcon *elemX = (ElementShellIcon *) elem; ElementShellIcon savedX; Tk_SavedOptions savedOptions; int error; Tcl_Obj *errorResult = NULL; for (error = 0; error <= 1; error++) { if (error == 0) { if (Tk_SetOptions(tree->interp, (char *) elemX, elem->typePtr->optionTable, args->config.objc, args->config.objv, tree->tkwin, &savedOptions, &args->config.flagSelf) != TCL_OK) { args->config.flagSelf = 0; continue; } if (args->config.flagSelf & SHELLICON_CONF_DRAW) PSTSave(&elemX->draw, &savedX.draw); if (args->config.flagSelf & SHELLICON_CONF_DRAW) { if (PerStateInfo_FromObj(tree, TreeStateFromObj, &pstBoolean, &elemX->draw) != TCL_OK) continue; } if (args->config.flagSelf & SHELLICON_CONF_DRAW) PerStateInfo_Free(tree, &pstBoolean, &savedX.draw); Tk_FreeSavedOptions(&savedOptions); break; } else { errorResult = Tcl_GetObjResult(tree->interp); Tcl_IncrRefCount(errorResult); Tk_RestoreSavedOptions(&savedOptions); if (args->config.flagSelf & SHELLICON_CONF_DRAW) PSTRestore(tree, &pstBoolean, &elemX->draw, &savedX.draw); Tcl_SetObjResult(tree->interp, errorResult); Tcl_DecrRefCount(errorResult); return TCL_ERROR; } } return TCL_OK; } static int CreateProcShellIcon(ElementArgs *args) { ElementShellIcon *elemX = (ElementShellIcon *) args->elem; elemX->type = -1; elemX->size = -1; elemX->addOverlays = -1; elemX->useImgList = -1; return TCL_OK; } static void DisplayProcShellIcon(ElementArgs *args) { TreeCtrl *tree = args->tree; Element *elem = args->elem; ElementShellIcon *elemX = (ElementShellIcon *) elem; ElementShellIcon *masterX = (ElementShellIcon *) elem->master; int state = args->state; int x = args->display.x, y = args->display.y; int width, height; int match, match2; int draw; HDC hDC; TkWinDCState dcState; UINT fStyle = ILD_TRANSPARENT; HICON hIcon; BOOLEAN_FOR_STATE(draw, draw, state) if (!draw) return; LoadIconIfNeeded(args); if (elemX->hImgList == NULL) return; (void) ImageList_GetIconSize(elemX->hImgList, &width, &height); AdjustForSticky(args->display.sticky, args->display.width, args->display.height, FALSE, FALSE, &x, &y, &width, &height); hDC = TkWinGetDrawableDC(tree->display, args->display.drawable, &dcState); hIcon = (state & STATE_SELECTED) ? elemX->hIconSel : elemX->hIcon; if (hIcon != NULL) { #if 1 /* If DI_DEFAULTSIZE is used, small icons get stretched */ DrawIconEx(hDC, x, y, hIcon, 0, 0, FALSE, NULL, DI_IMAGE | DI_MASK /*| DI_DEFAULTSIZE*/); #else /* Works fine for large, but not for small (they get stretched) */ DrawIcon(hDC, x, y, hIcon); #endif } else { if (state & STATE_SELECTED) fStyle |= ILD_SELECTED; ImageList_Draw(elemX->hImgList, elemX->iIcon, hDC, x, y, fStyle); } TkWinReleaseDrawableDC(args->display.drawable, hDC, &dcState); /* If this is a master element, forget the icon being used because the * appearance may change between items based on the open state */ if (masterX == NULL) { ForgetIcon(elemX); } } static void NeededProcShellIcon(ElementArgs *args) { Element *elem = args->elem; ElementShellIcon *elemX = (ElementShellIcon *) elem; ElementShellIcon *masterX = (ElementShellIcon *) elem->master; int width = 0, height = 0; int size = SIZE_LARGE; HIMAGELIST hImgList = NULL; /* The icon is not loaded until it is actually going to be displayed. * This is a good thing since loading the icons can take a while */ /* LoadIconIfNeeded(args);*/ if (elemX->hImgList != NULL) { hImgList = elemX->hImgList; } else if (elemX->path != NULL) { if (elemX->size != -1) size = elemX->size; else if (masterX != NULL && masterX->size != -1) size = masterX->size; switch (size) { case SIZE_SMALL: hImgList = gImgListSmall; break; case SIZE_LARGE: hImgList = gImgListLarge; break; } } if (hImgList != NULL) { (void) ImageList_GetIconSize(hImgList, &width, &height); } if (elemX->widthObj != NULL) width = elemX->width; else if ((masterX != NULL) && (masterX->widthObj != NULL)) width = masterX->width; if (elemX->heightObj != NULL) height = elemX->height; else if ((masterX != NULL) && (masterX->heightObj != NULL)) height = masterX->height; args->needed.width = width; args->needed.height = height; } static int StateProcShellIcon(ElementArgs *args) { TreeCtrl *tree = args->tree; Element *elem = args->elem; ElementShellIcon *elemX = (ElementShellIcon *) elem; ElementShellIcon *masterX = (ElementShellIcon *) elem->master; int match, match2; int draw1, draw2; int sel1, sel2; int open1, open2; int mask = 0; BOOLEAN_FOR_STATE(draw1, draw, args->states.state1) if (draw1 == -1) draw1 = 1; open1 = (args->states.state1 & STATE_OPEN) != 0; sel1 = (args->states.state1 & STATE_SELECTED) != 0; BOOLEAN_FOR_STATE(draw2, draw, args->states.state2) if (draw2 == -1) draw2 = 1; open2 = (args->states.state2 & STATE_OPEN) != 0; sel2 = (args->states.state2 & STATE_SELECTED) != 0; if (elemX->path == NULL) open1 = open2 = sel1 = sel2 = 0; if ((draw1 != draw2) || (sel1 != sel2) || (open1 != open2)) mask |= CS_DISPLAY; /* Directories may have an open and closed icon. */ if (open1 != open2) { ForgetIcon(elemX); } return mask; } static int UndefProcShellIcon(ElementArgs *args) { TreeCtrl *tree = args->tree; ElementShellIcon *elemX = (ElementShellIcon *) args->elem; int modified = 0; modified |= PerStateInfo_Undefine(tree, &pstBoolean, &elemX->draw, args->state); return modified; } static int ActualProcShellIcon(ElementArgs *args) { return TCL_OK; } ElementType elemTypeShellIcon = { "shellicon", sizeof(ElementShellIcon), shellIconOptionSpecs, NULL, CreateProcShellIcon, DeleteProcShellIcon, ConfigProcShellIcon, DisplayProcShellIcon, NeededProcShellIcon, NULL, /* heightProc */ WorldChangedProcShellIcon, StateProcShellIcon, UndefProcShellIcon, ActualProcShellIcon, NULL /* onScreenProc */ }; DLLEXPORT int Shellicon_Init(Tcl_Interp *interp) { #if 1 SHFILEINFO sfi; #else HMODULE hLib; #endif #ifdef USE_TCL_STUBS if (Tcl_InitStubs(interp, "8.4", 0) == NULL) { return TCL_ERROR; } #endif #ifdef USE_TK_STUBS if (Tk_InitStubs(interp, "8.4", 0) == NULL) { return TCL_ERROR; } #endif /* InitCommonControlsEx must be called to use the ImageList functions */ /* This is already done by Tk on NT */ if (TkWinGetPlatformId() != VER_PLATFORM_WIN32_NT) { INITCOMMONCONTROLSEX comctl; ZeroMemory(&comctl, sizeof(comctl)); (void) InitCommonControlsEx(&comctl); } #if 1 /* Get the sytem image lists (small and large) */ CoInitialize(NULL); gImgListSmall = (HIMAGELIST) SHGetFileInfo(".exe", FILE_ATTRIBUTE_NORMAL, &sfi, sizeof(sfi), SHGFI_USEFILEATTRIBUTES | SHGFI_SYSICONINDEX | SHGFI_SMALLICON); gImgListLarge = (HIMAGELIST) SHGetFileInfo(".exe", FILE_ATTRIBUTE_NORMAL, &sfi, sizeof(sfi), SHGFI_USEFILEATTRIBUTES | SHGFI_SYSICONINDEX | SHGFI_LARGEICON); CoUninitialize(); #else /* FIXME: WinXP only */ /* This is broken somewhere */ hLib = LoadLibraryA("shell32"); SHGetImageListProc = (FARPROC) GetProcAddress(hLib, (LPCSTR) 727); SHGetImageListProc(SHIL_SMALL, &IID_IImageList, &gImgListSmall); SHGetImageListProc(SHIL_LARGE, &IID_IImageList, &gImgListLarge); SHGetImageListProc(SHIL_EXTRALARGE, &IID_IImageList, &gImgListXtraLarge); dbwin("small %p large %p xtralarge %p\n", gImgListSmall, gImgListLarge, gImgListXtraLarge); FreeLibrary(hLib); #endif /* Load TkTreeCtrl */ if (Tcl_PkgRequire(interp, "treectrl", "2.0.1", TRUE) == NULL) return TCL_ERROR; /* Get the stubs table from TkTreeCtrl */ stubs = Tcl_GetAssocData(interp, "TreeCtrlStubs", NULL); if (stubs == NULL) return TCL_ERROR; /* Initialize the options table */ BooleanCO_Init(shellIconOptionSpecs, "-addoverlays"); BooleanCO_Init(shellIconOptionSpecs, "-useimagelist"); StringTableCO_Init(shellIconOptionSpecs, "-size", sizeST); StringTableCO_Init(shellIconOptionSpecs, "-type", typeST); /* Add the "shellicon" element type */ if (TreeCtrl_RegisterElementType(interp, &elemTypeShellIcon) != TCL_OK) return TCL_ERROR; if (Tcl_PkgProvide(interp, PACKAGE_NAME, PACKAGE_PATCHLEVEL) != TCL_OK) { return TCL_ERROR; } return TCL_OK; } DLLEXPORT int Shellicon_SafeInit(Tcl_Interp *interp) { return Shellicon_Init(interp); }