/* * Extended system tray widget for XCenter/eCenter * * Implementation * * Made by netlabs.org * * Author: Dmitry A. Kuminov * * This software is public domain. * * WITHOUT ANY WARRANTY..., AT YOUR OWN RISC... ETC. * */ /* * This code is based on the code template for a minimal XCenter widget * from the XWorkplace project (c) by Ulrich Moeller. */ #pragma strings(readonly) /* * Suggested #include order: * 1) os2.h * 2) C library headers * 3) setup.h (code generation and debugging options) * 4) headers in helpers\ * 5) at least one SOM implementation header (*.ih) * 6) dlgids.h, headers in shared\ (as needed) * 7) headers in implementation dirs (e.g. filesys\, as needed) * 8) #pragma hdrstop and then more SOM headers which crash with precompiled headers */ #define INCL_BASE #define INCL_PM #include // C library headers #include #include #include #include // generic headers // If this file were part of the XWorkplace sources, we'd now include // the generic "setup.h" file, which has common set up code for every // single XWorkplace code file. But it's not, so we won't include that. // #include "setup.h" // code generation and debugging options // headers in /helpers // This would be the place to include headers from the "XWorkplace helpers". // But since we do a minimal sample here, we can't include those helpful // routines... again, see the src\widgets in the XWorkplace source code // for how these functions can be imported from XFLDR.DLL to avoid duplicate // code. // #include "helpers\dosh.h" // Control Program helper routines // #include "helpers\gpih.h" // GPI helper routines // #include "helpers\prfh.h" // INI file helper routines; // this include is required for some // of the structures in shared\center.h // #include "helpers\winh.h" // PM helper routines // #include "helpers\xstring.h" // extended string helpers // XWorkplace implementation headers // If this file were part of the XCenter sources, we'd now include // "center.h" from the "include\shared" directory. Since we're not // part of the XCenter sources here, we include that file from the // "toolkit" directory in the binary release. That file is identical // to "include\shared\center.h" in the XWorkplace sources. #include "shared\center.h" // public XCenter interfaces #include "xsystray.h" #pragma hdrstop // VAC++ keeps crashing otherwise // primitive debug logging to a file #if 1 static void __LOG_WORKER(const char *fmt, ...) { static FILE *f = NULL; if (f == NULL) { f = fopen("c:\\xsystray.dbg", "a"); setbuf(f, NULL); } if (f != NULL) { va_list vl; va_start(vl, fmt); vfprintf(f, fmt, vl); va_end(vl); } } #define LOG(m) do { __LOG_WORKER m; } while(0) #define LOGF(m) do { __LOG_WORKER("%s: ", __FUNCTION__); __LOG_WORKER m; } while(0) #else #define LOG(m) do {} while (0) #define LOGF(m) do {} while (0) #endif /* ****************************************************************** * * Private definitions * ********************************************************************/ /* *@@ ICONDATA: * Per-icon data. */ typedef struct _ICONDATA { HWND hwnd; // associated window ULONG ulId; // icon ID HPOINTER hIcon; // icon handle ULONG ulMsgId; // message ID for notifications PSZ pszToolTip; // icon tooltip (NULL if none) } ICONDATA, *PICONDATA; /* *@@ SYSTRAYDATA: * Global system tray data. */ typedef struct { PICONDATA pIcons; // array of icons currently shown in the system tray // (left to right) size_t cIcons; // number of icons in the pIcons array size_t cIconsMax; // maximum number of icons pIcons can fit } SYSTRAYDATA, *PSYSTRAYDATA; ULONG QWL_USER_SERVER_DATA = 0; /* ****************************************************************** * * XCenter widget class definition * ********************************************************************/ /* * This contains the name of the PM window class and * the XCENTERWIDGETCLASS definition(s) for the widget * class(es) in this DLL. * * The address of this structure (or an array of these * structures, if there were several widget classes in * this plugin) is returned by the "init" export * (WgtInitModule). */ static const XCENTERWIDGETCLASS G_WidgetClasses[] = { { WNDCLASS_WIDGET_XSYSTRAY, // PM window class name 0, // additional flag, not used here INTCLASS_WIDGET_XSYSTRAY, // internal widget class name HUMANSTR_WIDGET_XSYSTRAY, // widget class name displayed to user WGTF_UNIQUEGLOBAL, // widget class flags NULL // no settings dialog } }; /* ****************************************************************** * * Function imports from XFLDR.DLL * ********************************************************************/ /* * To reduce the size of the widget DLL, it can * be compiled with the VAC subsystem libraries. * In addition, instead of linking frequently * used helpers against the DLL again, you can * import them from XFLDR.DLL, whose module handle * is given to you in the INITMODULE export. * * Note that importing functions from XFLDR.DLL * is _not_ a requirement. We can't do this in * this minimal sample anyway without having access * to the full XWorkplace source code. * * If you want to know how you can import the useful * functions from XFLDR.DLL to use them in your widget * plugin, again, see src\widgets in the XWorkplace sources. * The actual imports would then be made by WgtInitModule. */ /* ****************************************************************** * * Private widget instance data * ********************************************************************/ // None presently. The samples in src\widgets in the XWorkplace // sources cleanly separate setup string data from other widget // instance data to allow for easier manipulation with settings // dialogs. We have skipped this for the minimal sample. /* ****************************************************************** * * Widget setup management * ********************************************************************/ // None presently. See above. /* ****************************************************************** * * Widget settings dialog * ********************************************************************/ // None currently. To see how a setup dialog can be done, // see the "window list" widget in the XWorkplace sources // (src\widgets\w_winlist.c). /* ****************************************************************** * * Callbacks stored in XCENTERWIDGETCLASS * ********************************************************************/ // If you implement a settings dialog, you must write a // "show settings dlg" function and store its function pointer // in XCENTERWIDGETCLASS. /* ****************************************************************** * * PM window class implementation * ********************************************************************/ /* * This code has the actual PM window class. * */ /* *@@ MwgtControl: * implementation for WM_CONTROL in fnwpXSysTray. * * The XCenter communicates with widgets thru * WM_CONTROL messages. At the very least, the * widget should respond to XN_QUERYSIZE because * otherwise it will be given some dumb default * size. */ static BOOL WgtControl(PXCENTERWIDGET pWidget, MPARAM mp1, MPARAM mp2) { BOOL brc = FALSE; USHORT usID = SHORT1FROMMP(mp1), usNotifyCode = SHORT2FROMMP(mp1); // is this from the XCenter client? if (usID == ID_XCENTER_CLIENT) { // yes: switch (usNotifyCode) { /* * XN_QUERYSIZE: * XCenter wants to know our size. */ case XN_QUERYSIZE: { PSIZEL pszl = (PSIZEL)mp2; pszl->cx = 30; // desired width pszl->cy = 20; // desired minimum height brc = TRUE; } break; } // end switch (usNotifyCode) } // end if (usID == ID_XCENTER_CLIENT) return brc; } /* *@@ WgtPaint: * implementation for WM_PAINT in fnwpXSysTray. * * This really does nothing, except painting a * 3D rectangle and printing a question mark. */ static VOID WgtPaint(HWND hwnd, PXCENTERWIDGET pWidget) { HPS hps; if ((hps = WinBeginPaint(hwnd, NULLHANDLE, NULL))) { RECTL rclWin; // switch HPS to RGB mode GpiCreateLogColorTable(hps, 0, LCOLF_RGB, 0, 0, NULL); // now paint WinQueryWindowRect(hwnd, &rclWin); // exclusive WinFillRect(hps, &rclWin, // exclusive WinQuerySysColor(HWND_DESKTOP, SYSCLR_DIALOGBACKGROUND, 0)); // print question mark WinDrawText(hps, 1, "?", &rclWin, // exclusive WinQuerySysColor(HWND_DESKTOP, SYSCLR_WINDOWSTATICTEXT, 0), WinQuerySysColor(HWND_DESKTOP, SYSCLR_DIALOGBACKGROUND, 0), DT_CENTER | DT_VCENTER); WinEndPaint(hps); } } /* *@@ fnwpXSysTray: * window procedure for the Extended system tray widget class. * * There are a few rules which widget window procs * must follow. See XCENTERWIDGETCLASS in center.h * for details. * * Other than that, this is a regular window procedure * which follows the basic rules for a PM window class. */ MRESULT EXPENTRY fnwpXSysTray(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2) { MRESULT mrc = 0; // get widget data from QWL_USER (stored there by WM_CREATE) PXCENTERWIDGET pWidget = (PXCENTERWIDGET)WinQueryWindowPtr(hwnd, QWL_USER); // this ptr is valid after WM_CREATE switch (msg) { /* * WM_CREATE: * as with all widgets, we receive a pointer to the * XCENTERWIDGET in mp1, which was created for us. * * The first thing the widget MUST do on WM_CREATE * is to store the XCENTERWIDGET pointer (from mp1) * in the QWL_USER window word by calling: * * WinSetWindowPtr(hwnd, QWL_USER, mp1); * * We could use XCENTERWIDGET.pUser for allocating * another private memory block for our own stuff, * for example to be able to store fonts and colors. * We ain't doing this in the minimal sample. */ case WM_CREATE: { PSYSTRAYDATA pSysTrayData = NULL; mrc = (MPARAM)TRUE; // being pessimistic gives more compact code WinSetWindowPtr(hwnd, QWL_USER, mp1); if ( (!(pWidget = (PXCENTERWIDGET)mp1)) || (!pWidget->pfnwpDefWidgetProc) ) // shouldn't happen... stop window creation!! break; pSysTrayData = malloc(sizeof(*pSysTrayData)); if (pSysTrayData == NULL) break; // initialize the SYSTRAYDATA structure pSysTrayData->cIconsMax = 4; pSysTrayData->pIcons = malloc(sizeof(*pSysTrayData->pIcons) * pSysTrayData->cIconsMax); if (pSysTrayData->pIcons == NULL) { free(pSysTrayData); break; } pSysTrayData->cIcons = 0; pWidget->pUser = pSysTrayData; // create the "server" window (note that we pass the XCENTERWIDGET // pointer on to it) HWND hwndServer = WinCreateWindow(HWND_DESKTOP, WNDCLASS_WIDGET_XSYSTRAY_SERVER, NULL, WS_MINIMIZED, 0, 0, 0, 0, HWND_DESKTOP, HWND_BOTTOM, 0, mp1, NULL); if (hwndServer == NULLHANDLE) break; mrc = FALSE; // confirm success } break; /* * WM_CONTROL: * process notifications/queries from the XCenter. */ case WM_CONTROL: mrc = (MPARAM)WgtControl(pWidget, mp1, mp2); break; /* * WM_PAINT: * well, paint the widget. */ case WM_PAINT: WgtPaint(hwnd, pWidget); break; /* * WM_PRESPARAMCHANGED: * A well-behaved widget would intercept * this and store fonts and colors. */ /* case WM_PRESPARAMCHANGED: break; */ /* * WM_DESTROY: * clean up. This _must_ be passed on to * ctrDefWidgetProc. */ case WM_DESTROY: { // free all system tray data PSYSTRAYDATA pSysTrayData = (PSYSTRAYDATA)pWidget->pUser; size_t i; for (i = 0; i < pSysTrayData->cIcons; ++i) { if (pSysTrayData->pIcons[i].pszToolTip) free(pSysTrayData->pIcons[i].pszToolTip); } free(pSysTrayData->pIcons); free(pSysTrayData); pWidget->pUser = NULL; // We _MUST_ pass this on, or the default widget proc // cannot clean up. mrc = pWidget->pfnwpDefWidgetProc(hwnd, msg, mp1, mp2); } break; default: mrc = pWidget->pfnwpDefWidgetProc(hwnd, msg, mp1, mp2); } // end switch(msg) return mrc; } /* *@@ WgtXSysTrayControl: * implementation for WM_XST_CONTROL in fnwpXSysTrayServer. * * Serves as an entry point for all client-side requests to the Extended * system tray. * * Note that this message is being sent from another process which is * awaiting for an answer, so it must return as far as possible (by * rescheduling any potentially long term operation for another cycle). */ static BOOL WgtXSysTrayControl(HWND hwnd, PXCENTERWIDGET pWidget, PSYSTRAYCTLDATA pCtlData) { BOOL brc = FALSE; switch (pCtlData->ulCommand) { case SYSTRAYCMD_GETVERSION: { LOGF(("SYSTRAYCMD_GETVERSION\n")); pCtlData->bAcknowledged = TRUE; pCtlData->u.version.ulMajor = XSYSTRAY_VERSION_MAJOR; pCtlData->u.version.ulMajor = XSYSTRAY_VERSION_MINOR; pCtlData->u.version.ulRevision = XSYSTRAY_VERSION_REVISION; brc = TRUE; } break; default: break; } return brc; } /* *@@ fnwpXSysTrayServer: * window procedure for the Extended system tray server window class. * * A separate "server" class is necessary because we need a CS_FRAME * top-level window for DDE (which we need to support to be backward * compatible with the System tray wdget from the SysTray/WPS package) and * also to make ourselves discoverable for the client-side implementation * of our new extended API (which queries the class of each top-level * window to find the system tray server). */ static MRESULT EXPENTRY fnwpXSysTrayServer(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2) { MRESULT mrc = 0; // get widget data from QWL_USER_SERVER_DATA (stored there by WM_CREATE) PXCENTERWIDGET pWidget = (PXCENTERWIDGET)WinQueryWindowPtr(hwnd, QWL_USER_SERVER_DATA); // this ptr is valid after WM_CREATE switch (msg) { case WM_CREATE: WinSetWindowPtr(hwnd, QWL_USER_SERVER_DATA, mp1); break; /* * WM_XST_CONTROL: * This is the message sent to us by the clinet-side implementation * of the API to request some function. mp1 points to a * SYSTRAYCTLDATA structure. */ case WM_XST_CONTROL: mrc = (MRESULT)WgtXSysTrayControl(hwnd, pWidget, (PSYSTRAYCTLDATA)mp1); break; default: mrc = WinDefWindowProc(hwnd, msg, mp1, mp2); } // end switch(msg) return mrc; } /* ****************************************************************** * * Exported procedures * ********************************************************************/ /* *@@ WgtInitModule: * required export with ordinal 1, which must tell * the XCenter how many widgets this DLL provides, * and give the XCenter an array of XCENTERWIDGETCLASS * structures describing the widgets. * * With this call, you are given the module handle of * XFLDR.DLL. For convenience, and if you have the full * XWorkplace source code, you could resolve imports * for some useful functions which are exported thru * src\shared\xwp.def. We don't do this here. * * This function must also register the PM window classes * which are specified in the XCENTERWIDGETCLASS array * entries. For this, you are given a HAB which you * should pass to WinRegisterClass. For the window * class style (4th param to WinRegisterClass), * you should specify * + CS_PARENTCLIP | CS_SIZEREDRAW | CS_SYNCPAINT * * Your widget window _will_ be resized by the XCenter, * even if you're not planning it to be. * * This function only gets called _once_ when the widget * DLL has been successfully loaded by the XCenter. If * there are several instances of a widget running (in * the same or in several XCenters), this function does * not get called again. However, since the XCenter unloads * the widget DLLs again if they are no longer referenced * by any XCenter, this might get called again when the * DLL is re-loaded. * * There will ever be only one load occurence of the DLL. * The XCenter manages sharing the DLL between several * XCenters. As a result, it doesn't matter if the DLL * has INITINSTANCE etc. set or not. * * If this returns 0, this is considered an error, and the * DLL will be unloaded again immediately. * * If this returns any value > 0, *ppaClasses must be * set to a static array (best placed in the DLL's * global data) of XCENTERWIDGETCLASS structures, * which must have as many entries as the return value. */ ULONG EXPENTRY WgtInitModule(HAB hab, // XCenter's anchor block HMODULE hmodPlugin, // module handle of the widget DLL HMODULE hmodXFLDR, // XFLDR.DLL module handle PCXCENTERWIDGETCLASS *ppaClasses, PSZ pszErrorMsg) // if 0 is returned, 500 bytes of error msg { ULONG ulrc = 0; CLASSINFO ClassInfo; LOGF(("hmodPlugin %x\n", hmodPlugin)); do { // register our PM window class if (!WinRegisterClass(hab, WNDCLASS_WIDGET_XSYSTRAY, fnwpXSysTray, CS_PARENTCLIP | CS_SIZEREDRAW | CS_SYNCPAINT, sizeof(PVOID)) // extra memory to reserve for QWL_USER ) { LOG(("WinRegisterClass(%s) failed with %lX", WNDCLASS_WIDGET_XSYSTRAY, WinGetLastError(hab))); break; } // get the window data size for the WC_FRAME class (any window class // that specifies CS_FRAME must have at least this number, otherise // WinRegisterClass returns 0x1003 if (!WinQueryClassInfo(hab, (PSZ)WC_FRAME, &ClassInfo)) break; QWL_USER_SERVER_DATA = ClassInfo.cbWindowData; if (!WinRegisterClass(hab, WNDCLASS_WIDGET_XSYSTRAY_SERVER, fnwpXSysTrayServer, CS_FRAME, QWL_USER_SERVER_DATA + sizeof(PVOID)) // extra memory to reserve for QWL_USER ) { // error registering class: report error then snprintf(pszErrorMsg, 500, "WinRegisterClass(%s) failed with %lX", WNDCLASS_WIDGET_XSYSTRAY_SERVER, WinGetLastError(hab)); break; } // no error: // return widget classes array *ppaClasses = G_WidgetClasses; // return no. of classes in this DLL (one here): ulrc = sizeof(G_WidgetClasses) / sizeof(G_WidgetClasses[0]); } while (0); LOGF(("pszErrorMsg %s\n", pszErrorMsg)); LOGF(("ulrc %d\n", ulrc)); return ulrc; } /* *@@ WgtUnInitModule: * optional export with ordinal 2, which can clean * up global widget class data. * * This gets called by the XCenter right before * a widget DLL gets unloaded. Note that this * gets called even if the "init module" export * returned 0 (meaning an error) and the DLL * gets unloaded right away. */ VOID EXPENTRY WgtUnInitModule(VOID) { } /* *@@ WgtQueryVersion: * this new export with ordinal 3 can return the * XWorkplace version number which is required * for this widget to run. For example, if this * returns 0.9.10, this widget will not run on * earlier XWorkplace versions. * * NOTE: This export was mainly added because the * prototype for the "Init" export was changed * with V0.9.9. If this returns 0.9.9, it is * assumed that the INIT export understands * the new FNWGTINITMODULE_099 format (see center.h). */ VOID EXPENTRY WgtQueryVersion(PULONG pulMajor, PULONG pulMinor, PULONG pulRevision) { *pulMajor = 0; *pulMinor = 9; *pulRevision = 9; }