Workbench and icon.library V44–V47 for Developers

AmigaOS 3.2 ships workbench.library 47 and icon.library 47. Together with the V44/V45 API that was introduced for OS 3.5/3.9 and is now part of the official 3.2 NDK, they give applications much more than the V37 trio of AppWindow, AppIcon and AppMenuItem: drop zones, AppIcons that answer the "Icons" menu, sub-menus in the Tools menu, programmatic drawer opening, copy/delete/rename hooks, a hit-test for Workbench objects, file-type identification through DefIcons, and outlined/shadowed icon labels.

This article is a developer's map of that API, built from workbench/workbench.h, workbench/icon.h, workbench/startup.h, prefs/workbench.h, the autodocs and the release notes. For per-function autodoc text see workbench.library Reference and icon.library Reference.

workbench.library functions

LVO Function Since
−30 UpdateWorkbench(name, lock, action) V36 (made public in 45.99)
−48 AddAppWindowA(id, userdata, window, msgport, tags) V36
−54 RemoveAppWindow(appWindow) V36
−60 AddAppIconA(id, userdata, text, msgport, lock, diskobj, tags) V36
−66 RemoveAppIcon(appIcon) V36
−72 AddAppMenuItemA(id, userdata, text, msgport, tags) V36
−78 RemoveAppMenuItem(appMenuItem) V36
−90 WBInfo(lock, name, screen) V39
−96 OpenWorkbenchObjectA(name, tags) V44
−102 CloseWorkbenchObjectA(name, tags) V44
−108 WorkbenchControlA(name, tags) V44
−114 AddAppWindowDropZoneA(aw, id, userdata, tags) V44
−120 RemoveAppWindowDropZone(aw, dropZone) V44
−126 ChangeWorkbenchSelectionA(name, hook, tags) V44
−132 MakeWorkbenchObjectVisibleA(name, tags) V44
−138 WhichWorkbenchObjectA(window, x, y, tags) V47

RemoveAppWindow(), RemoveAppIcon() and RemoveAppMenuItem() return a BOOL since workbench.library 44.99; the release notes say they used to return the pointer passed in. All Add… calls need a running Workbench (LoadWB executed and the screen open), and return NULL otherwise. All workbench.library tags use the base WBA_Dummy = TAG_USER + 0xA000.

AppMessages

struct AppMessage {
    struct Message am_Message;
    UWORD am_Type;          /* AMTYPE_* */
    ULONG am_UserData;
    ULONG am_ID;
    LONG  am_NumArgs;
    struct WBArg *am_ArgList;
    UWORD am_Version;       /* >= AM_VERSION (1) */
    UWORD am_Class;         /* AMCLASSICON_* for AppIcons (V44) */
    WORD  am_MouseX, am_MouseY;
    ULONG am_Seconds, am_Micros;
    ULONG am_Reserved[8];
};
am_Type Value Sent for
AMTYPE_APPWINDOW 3.1+ 7 icons dropped on an AppWindow (outside any drop zone)
AMTYPE_APPICON 3.1+ 8 AppIcon opened, dropped on, or menu action
AMTYPE_APPMENUITEM 3.1+ 9 Tools menu item selected
AMTYPE_APPWINDOWZONE 3.2+ 10 icons dropped on a drop zone (V44)

Reply every AppMessage quickly. Workbench is waiting on them. Since workbench.library 45.134, an AppMessage that cannot be delivered to an AppIcon or AppWindow is freed by Workbench instead of "hanging around till the next reboot".

AppIcons

AddAppIconA() tags (all BOOL unless shown):

Tag Value Default Effect
WBAPPICONA_SupportsOpen 3.2+ +1 TRUE responds to "Open"
WBAPPICONA_SupportsCopy 3.2+ +2 FALSE "Copy"
WBAPPICONA_SupportsRename 3.2+ +3 FALSE "Rename"
WBAPPICONA_SupportsInformation 3.2+ +4 FALSE "Information"
WBAPPICONA_SupportsSnapshot 3.2+ +5 FALSE "Snapshot"
WBAPPICONA_SupportsUnSnapshot 3.2+ +6 FALSE "UnSnapshot"
WBAPPICONA_SupportsLeaveOut 3.2+ +7 FALSE "Leave Out"
WBAPPICONA_SupportsPutAway 3.2+ +8 FALSE "Put Away"
WBAPPICONA_SupportsDelete 3.2+ +9 FALSE "Delete"
WBAPPICONA_SupportsFormatDisk 3.2+ +10 FALSE "Format Disk"
WBAPPICONA_SupportsEmptyTrash 3.2+ +11 FALSE "Empty Trash"
WBAPPICONA_SupportsEjectDisk 3.2+ +158 FALSE "Eject Disk" (V47)
WBAPPICONA_PropagatePosition 3.2+ +12 FALSE write position back to your DiskObject (you must keep it)
WBAPPICONA_RenderHook 3.2+ +13 NULL struct Hook * called to render the icon
WBAPPICONA_NotifySelectState 3.2+ +14 FALSE get AMCLASSICON_Selected/Unselected messages

(Values are offsets from WBA_Dummy.) The menu action arrives in am_Class:

am_Class Value am_Class Value
AMCLASSICON_Open 3.5+ 0 AMCLASSICON_PutAway 7
AMCLASSICON_Copy 3.2+ 1 AMCLASSICON_Delete 8
AMCLASSICON_Rename 3.2+ 2 AMCLASSICON_FormatDisk 9
AMCLASSICON_Information 3.2+ 3 AMCLASSICON_EmptyTrash 10
AMCLASSICON_Snapshot 3.2+ 4 AMCLASSICON_Selected 11
AMCLASSICON_UnSnapshot 3.2+ 5 AMCLASSICON_Unselected 12
AMCLASSICON_LeaveOut 3.2+ 6 AMCLASSICON_EjectDisk 13

AMCLASSICON_Open covers a double-click, the "Open" menu item and icons dropped on the AppIcon. The render hook is called as LONG hookFunc(struct Hook *hook, APTR reserved, struct AppIconRenderMsg *arm) (A0/A2/A1). Return TRUE to have the normal image drawn as well. Combined with WorkbenchControl(NULL, WBCTRLA_RedrawAppIcon, appIcon, TAG_DONE) this gives animated AppIcons.

AppMenuItems, sub-menus and new menus

Tag Value Type Since
WBAPPMENUA_CommandKeyString 3.2+ +15 STRPTR V44 (first character only; ignored if already used)
WBAPPMENUA_GetKey 3.2+ +65 ULONG * V44 (workbench.library 44.1511+)
WBAPPMENUA_UseKey 3.2+ +66 ULONG V44; with a title key, V45
WBAPPMENUA_GetTitleKey 3.2+ +77 ULONG * V45

A label made only of -, _ or ~ characters becomes a separator bar (V44). A menu that already holds 63 items refuses new ones. To build a sub-menu, add the parent item with WBAPPMENUA_GetKey, then add its children with WBAPPMENUA_UseKey:

ULONG key;
struct AppMenuItem *parent, *a, *b;

parent = AddAppMenuItem(0, 0, "My Tool", port, WBAPPMENUA_GetKey, &key, TAG_DONE);
a = AddAppMenuItem(1, 0, "Do this", port, WBAPPMENUA_UseKey, key,
                   WBAPPMENUA_CommandKeyString, "T", TAG_DONE);
b = AddAppMenuItem(2, 0, "Do that", port, WBAPPMENUA_UseKey, key, TAG_DONE);

WBAPPMENUA_GetTitleKey does the same one level up: it creates a new menu title in the Workbench strip that later items attach to.

AppWindows and drop zones

Without drop zones, an icon dropped anywhere in an AppWindow produces an AMTYPE_APPWINDOW message. AddAppWindowDropZoneA() (V44) marks areas instead. Once a zone exists, drops on it arrive as AMTYPE_APPWINDOWZONE with am_ID/am_UserData set to the zone's values. Removing the AppWindow removes all its zones.

Tag Value Type Meaning
WBDZA_Left / WBDZA_RelRight +26 / +27 WORD left edge, absolute or relative to the right window edge
WBDZA_Top / WBDZA_RelBottom +28 / +29 WORD top edge, absolute or relative to the bottom
WBDZA_Width / WBDZA_RelWidth +30 / +31 WORD width, absolute or relative to window width
WBDZA_Height / WBDZA_RelHeight +32 / +33 WORD height, absolute or relative
WBDZA_Box 3.2+ +34 struct IBox * position and size in one go
WBDZA_Hook 3.2+ +35 struct Hook * highlight hook, called on enter/leave

The hook gets a struct AppWindowDropZoneMsg (adzm_RastPort, adzm_DropZoneBox, adzm_ID, adzm_UserData, adzm_Action = ADZMACTION_Enter 0 / ADZMACTION_Leave 1) and must return 0. Only simple graphics.library drawing is allowed inside it. The autodoc warns that refreshing gadgets or locking IntuitionBase there "will deadlock the operating system". Leaving a zone does not mean nothing will be dropped on it.

Opening drawers and launching programs

OpenWorkbenchObjectA(name, tags) acts as if the user double-clicked the icon (drawer, tool or project):

Tag Value Type Meaning
WBOPENA_ArgLock 3.2+ +16 BPTR lock for the following WBOPENA_ArgName entries (duplicated)
WBOPENA_ArgName 3.2+ +17 STRPTR add a WBArg for the launched program (duplicated)
WBOPENA_Show 3.1.4+ +75 UBYTE drawer show mode: DDFLAGS_SHOWICONS / DDFLAGS_SHOWALL (V45)
WBOPENA_ViewBy 3.1.4+ +76 UBYTE drawer view: DDVM_BYICON … DDVM_BYTYPE (V45)
/* Launch MultiView with S:Startup-Sequence (autodoc example) */
BPTR dir = Lock("S:", SHARED_LOCK);
OpenWorkbenchObject("MultiView",
    WBOPENA_ArgLock, dir,
    WBOPENA_ArgName, "Startup-Sequence",
    TAG_DONE);
UnLock(dir);   /* Workbench duplicated it */

TRUE may be returned even if the program could not actually be started. CloseWorkbenchObjectA() closes a drawer window, and MakeWorkbenchObjectVisibleA() scrolls a drawer so that an icon is visible. Workbench 47.19 reworked drawer opening so that OpenWorkbenchObjectA() sets up the same internal state as a real double-click.

Drawer icons gained a V47 sort order in DrawerData.dd_Flags: DDFLAGS_SORTMASK 0x300, DDFLAGS_SORTDEFAULT 0, DDFLAGS_SORTASC 0x100, DDFLAGS_SORTDESC 0x200. It is saved with the drawer at snapshot time.

WorkbenchControlA

BOOL WorkbenchControlA(STRPTR name, struct TagItem *tags) queries and changes Workbench options. On FALSE, IoErr() has the reason. It may only be called by a Process.

Tag Value Data Since
WBCTRLA_IsOpen 3.2+ +18 LONG * V44 — is drawer name open?
WBCTRLA_DuplicateSearchPath / …FreeSearchPath +19 / +20 BPTR * / BPTR V44
WBCTRLA_GetDefaultStackSize / …Set… +21 / +22 ULONG * / ULONG V44
WBCTRLA_RedrawAppIcon 3.2+ +23 struct AppIcon * V44
WBCTRLA_GetProgramList / …FreeProgramList +24 / +25 struct List ** / * V44
WBCTRLA_GetSelectedIconList / …Free… +36 / +37 struct List ** / * V44
WBCTRLA_GetOpenDrawerList / …Free… +38 / +39 struct List ** / * V44
WBCTRLA_GetHiddenDeviceList / …Free… +42 / +43 struct List ** / * V44
WBCTRLA_AddHiddenDeviceName / …Remove… +44 / +45 STRPTR V44
WBCTRLA_GetTypeRestartTime / …Set… +47 / +48 ULONG * / ULONG V44
WBCTRLA_GetCopyHook / …SetCopyHook +69 / +70 struct Hook ** / * V45
WBCTRLA_GetDeleteHook / …SetDeleteHook +71 / +72 struct Hook ** / * V45
WBCTRLA_GetTextInputHook / …Set… +73 / +74 struct Hook ** / * V45
WBCTRLA_AddSetupCleanupHook / …Rem… +78 / +79 struct Hook * V45
WBCTRLA_SetGlobalFlags / …GetGlobalFlags +130 / +131 ULONG / ULONG * V47
WBCTRLA_GetDiskInfoHook / …SetDiskInfoHook +159 / +160 struct Hook ** / * V47

With Workbench closed, only the stack size, hidden-device and type-restart tags (V44), plus the copy/delete/text-input hook tags (V45), are processed.

V47 global flags

#define WBF_DRAWERPOSMASK 0x00000003  /* Drawer placement mask (text mode) */
#define WBF_DRAWERPOSFREE 0x00000000  /* Place drawers mixed with files */
#define WBF_DRAWERPOSHEAD 0x00000001  /* Place drawers at top of list */
#define WBF_DRAWERPOSTAIL 0x00000002  /* Place drawers at bottom of list */
#define WBF_BOUNDTEXTVIEW 0x00000080  /* Limit scrolling in text mode drawers */
#define WBF_OLDDATESFIRST 0x00010000  /* Reverse default ordering of dates */

The release notes say that, at the time, WBF_BOUNDTEXTVIEW and the date ordering had no control in the Workbench editor: "it can only be changed programmatically via WorkbenchControlA() for now". Always read-modify-write:

ULONG flags;
if (WorkbenchControl(NULL, WBCTRLA_GetGlobalFlags, &flags, TAG_DONE))
    WorkbenchControl(NULL, WBCTRLA_SetGlobalFlags,
                     (flags & ~WBF_DRAWERPOSMASK) | WBF_DRAWERPOSHEAD
                     | WBF_BOUNDTEXTVIEW, TAG_DONE);

Disk-info hook (V47)

Workbench calls this hook when a disk is unreadable to the handler (for example an MS-DOS disk in a trackdisk drive): state = hookFunc(hook, info, msgport) with A0/A2/A1, where info is the struct InfoData * the handler already filled in and msgport is the handler's port. Return info->id_DiskState to keep the "Unreadable" icon, or ID_NO_DISK_PRESENT to hide it. Passing NULL restores the default. The hook cannot be replaced while it is in use (ERROR_OBJECT_IN_USE). These tags were renumbered during 3.2 development, so build with the final headers.

Hook message types

Hook Message(s) Actions
Copy CopyBeginMsg, CopyDataMsg, CopyEndMsg CPACTION_Begin 0, _Copy 1, _End 2
Delete DeleteBeginMsg, DeleteDataMsg, DeleteEndMsg DLACTION_BeginDiscard 0, _BeginEmptyTrash 1, _DeleteContents 3, _DeleteObject 4, _End 5
Text input TextInputMsg TIACTION_Rename 0, _RelabelVolume 1, _NewDrawer 2, _Execute 3
Setup/cleanup SetupCleanupHookMsg SCHMSTATE_TryCleanup 0, _Cleanup 1, _Setup 2

Copying is never sent to the copy hook when data can be moved by a simple rename, or when DiskCopy duplicates a whole volume. For DLACTION_DeleteContents you empty the drawer but do not delete the drawer itself.

WhichWorkbenchObjectA (V47)

ULONG WhichWorkbenchObjectA(struct Window *window, LONG x, LONG y,
                            const struct TagItem *tags);

This returns WBO_ICON (2) if a click at x,y would hit a visible Workbench icon, WBO_DRAWER (1) for a visible part of a Workbench drawer window with no icon there, and WBO_NONE (0) otherwise. Coordinates are window-relative, or screen-relative with a NULL window, in which case the topmost window is used. Information tags (all offsets from WBA_Dummy): WBOBJA_Type +86, _Left +87, _Top +88, _Width +89, _Height +90, _State +91, _IsFake +92, _Name/_NameSize +93/+94 (default 64), _FullPath/_FullPathSize +95/+96 (default 512), _IsLink +97, _DrawerPath/_DrawerPathSize +98/+99, _DrawerFlags +116, _DrawerModes +117. Only trust icon tags after WBO_ICON and drawer tags after WBO_DRAWER or WBO_ICON.

The function is meant for input helpers such as commodities, for example a ClickToFront that should ignore double-clicks on icons, or context menus. "Usage of this function by normal applications is discouraged."

UpdateWorkbench

UpdateWorkbench(name, parentLock, action) tells Workbench that an object appeared or changed (UPDATEWB_ObjectAdded 1) or was deleted (UPDATEWB_ObjectRemoved 0). Call it after writing or deleting files that may be shown in an open drawer. Since 47.11 Workbench detects links added this way and shows them with an underlined label, under their own name rather than the target's.

Workbench preferences (prefs/workbench.h)

Chunk Structure Content
WBNC WorkbenchPrefs / WorkbenchExtendedPrefs stack size, type restart time, icon precision, emboss rect, borderless, max name length, NewIcons/colour icons; V45 extensions (lock pens, disable title bar / volume gauge); V47 wbe_TitleUpdateDelay (ms), wbe_CopyBufferSize, wbe_Flags
WBHD WorkbenchHiddenDevicePrefs one NUL-terminated device name per chunk
WBTF WorkbenchTitleFormatPrefs the screen title format string (V47)

Title Format Items

The Workbench screen title is configurable (workbench.library 46.1+). The string may hold text and these items:

Item Meaning
%e <envname> value of an environment variable
%m + type + block + unit memory: type c chip / f fast; block e free, u used, t total, l largest; unit b bytes, k KB, m MB, q percent of total. Example: %mcuq = chip memory in use as % of total
%vk, %vw Kickstart revision, Workbench version
%c, %f CPU, FPU
%r release version information (from the Versions/Release variable)
%i + p/s/d/i/r/a default public screen name, number of screens, active drawer name, selected icons, running programs, active disks

Users edit this in the Workbench preferences editor, which has a helper for inserting items. Programs should not depend on the title's contents.

icon.library V44–V47

LVO Function
−144 FreeFree(fl, address)
−150 DupDiskObjectA(diskObject, tags)
−156 IconControlA(icon, tags)
−162 DrawIconStateA(rp, icon, label, left, top, state, tags)
−168 GetIconRectangleA(rp, icon, label, rect, tags)
−174 NewDiskObject(type)
−180 GetIconTagList(name, tags)
−186 PutIconTagList(name, icon, tags)
−192 LayoutIconA(icon, screen, tags)
−198 ChangeToSelectedIconColor(cr)
−204 BumpRevisionLength(newname, oldname, maxLength) (V45)

Tag base: ICONA_Dummy = TAG_USER + 0x9000.

File-type identification: ICONGETA_IdentifyBuffer (V47)

Tag Value Type Meaning
ICONGETA_IdentifyBuffer 3.2+ +122 STRPTR buffer of at least 256 bytes that receives the file type from the global identification hook, even if the file has a real icon
ICONGETA_IdentifyOnly 3.2+ +123 BOOL with a valid buffer: do not load an icon at all; GetIconTagList() returns NULL with error code 0
  • Only works for files (tools and projects), not volumes, drawers or trashcans.
  • An unidentified file leaves an empty string in the buffer.
  • Needs DefIcons 47.8 or later, which installs the identification hook (ICONCTRLA_SetGlobalIdentifyHook). DefIcons also offers the same through its ARexx port DEFICONS with the command Identify NAME/A.

Example: AppWindow that identifies dropped files

#include <exec/types.h>
#include <workbench/workbench.h>
#include <workbench/icon.h>
#include <workbench/startup.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/intuition.h>
#include <proto/wb.h>
#include <proto/icon.h>

struct IntuitionBase *IntuitionBase;
struct Library *WorkbenchBase, *IconBase;

static void identify(struct WBArg *arg)
{
    UBYTE type[256];
    LONG err = 0;
    BPTR old = CurrentDir(arg->wa_Lock);

    type[0] = '\0';
    GetIconTags(arg->wa_Name,
        ICONGETA_IdentifyBuffer, type,
        ICONGETA_IdentifyOnly,   TRUE,
        ICONA_ErrorCode,         &err,
        TAG_DONE);                  /* returns NULL by design */
    CurrentDir(old);

    Printf("%s: %s\n", arg->wa_Name, type[0] ? (char *)type : "(unknown)");
}

int main(void)
{
    struct MsgPort *port;
    struct Window *win;
    struct AppWindow *aw;

    IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 39);
    WorkbenchBase = OpenLibrary("workbench.library", 44);
    IconBase      = OpenLibrary("icon.library", 47);

    if (IntuitionBase && WorkbenchBase && IconBase &&
        (port = CreateMsgPort()))
    {
        win = OpenWindowTags(NULL, WA_Title, "Drop files here",
            WA_Width, 300, WA_Height, 120, WA_DragBar, TRUE,
            WA_CloseGadget, TRUE, WA_DepthGadget, TRUE,
            WA_IDCMP, IDCMP_CLOSEWINDOW, TAG_DONE);

        if (win && (aw = AddAppWindow(1, 0, win, port, TAG_DONE)))
        {
            ULONG wsig = 1UL << win->UserPort->mp_SigBit;
            ULONG asig = 1UL << port->mp_SigBit;
            BOOL done = FALSE;
            struct Message *m;

            while (!done)
            {
                ULONG s = Wait(wsig | asig | SIGBREAKF_CTRL_C);

                if (s & SIGBREAKF_CTRL_C) done = TRUE;
                while ((m = GetMsg(port)))
                {
                    struct AppMessage *am = (struct AppMessage *)m;
                    LONG i;
                    for (i = 0; i < am->am_NumArgs; i++)
                        identify(&am->am_ArgList[i]);
                    ReplyMsg(m);
                }
                while ((m = GetMsg(win->UserPort)))
                {
                    if (((struct IntuiMessage *)m)->Class == IDCMP_CLOSEWINDOW)
                        done = TRUE;
                    ReplyMsg(m);
                }
            }
            RemoveAppWindow(aw);
            /* drain AppMessages that arrived meanwhile */
            while ((m = GetMsg(port))) ReplyMsg(m);
        }
        if (win) CloseWindow(win);
        DeleteMsgPort(port);
    }
    if (IconBase)      CloseLibrary(IconBase);
    if (WorkbenchBase) CloseLibrary(WorkbenchBase);
    if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase);
    return 0;
}

Each WBArg is a lock plus a name relative to it, which is why the example calls CurrentDir() before GetIconTags(). Dropped drawers and disks are not identified, as noted above.

Outlined and shadowed labels (V47)

Tag Value Type
ICONDRAWA_LabelShadow 3.2+ +93 BOOL — draw the label with a shadow
ICONDRAWA_LabelOutline 3.2+ +94 BOOL — draw the label with an outline

icon.library 47.3 / workbench.library 47.13 added these for DrawIconStateA(). The release note says the background colour is used for the outline/shadow, the icon font settings must be in normal text mode, and the user enables it on Workbench with the WBCtrl commands LABELSHADOW or LABELOUTLINE. Pass these tags on (together with arm_Tags) if your AppIcon render hook calls DrawIconStateA() itself, so that your icon matches the others.

ICONDRAWA_IsLink (+89) draws a link's label underlined; Workbench uses it for soft links.

Icon file name rules

  • ICONCTRLA_SetGlobalMaxNameLength sets the file name limit icon.library applies, in the range 8–255, default 25. If an icon name would exceed it, PutIconTagList() silently pretends success, to avoid overwriting the data file with the icon file. Pass ICONA_ErrorCode to see this: the error is ERROR_TOO_MANY_LEVELS.
  • The 45.20 release note gives the maximum file name size as 106 characters, so 101 for the base name plus .info. New icons must end in the full .info suffix, not a truncated one.
  • Since 47.2, writing an icon through a soft link (for example to make RAM:Disk.info changes permanent) is no longer mistaken for name truncation.
  • BumpRevisionLength(newbuf, oldname, maxLength) (V45) builds "copy_of_…" names with a length limit and returns NULL when even a truncated name will not fit. It replaces the unbounded BumpRevision(), which could overflow its buffer.

Starting from Workbench: WBStartup

A program started by Workbench receives a struct WBStartup message, and every argument in it is a struct WBArg:

struct WBStartup {
    struct Message  sm_Message;   /* a standard message structure */
    struct MsgPort *sm_Process;   /* the process descriptor for you */
    BPTR            sm_Segment;   /* a descriptor for your code */
    LONG            sm_NumArgs;   /* the number of elements in ArgList */
    STRPTR          sm_ToolWindow;/* description of window */
    struct WBArg   *sm_ArgList;   /* the arguments themselves */
};
struct WBArg { BPTR wa_Lock; STRPTR wa_Name; };  /* name relative to lock */

The same WBArg pair appears in AppMessages (am_ArgList) and is what WBOPENA_ArgLock/WBOPENA_ArgName build when you launch a program with OpenWorkbenchObjectA(). When a tool is launched from a project icon, Workbench now reads the tool's own icon to get its stack size, instead of falling back to a default when the project has none (45.134). 47.20 fixed this for default tools given as a relative path.

See Also


Sources: NDK 3.2 R4 Include_H/workbench/{workbench.h,icon.h,startup.h}, Include_H/prefs/workbench.h, SFD/wb_lib.sfd, SFD/icon_lib.sfd, FD/wb_lib.fd, FD/icon_lib.fd, Autodocs/wb.doc, Autodocs/icon.doc, ReleaseNotes/{wb,icon,Workbench,DefIcons}-RelNotes Updated: 2026-11-13