═══ 1. XWorkplace Programmer's Guide and Reference ═══  Notices  Introduction -- start here  National Language Support  The XWorkplace source code  Source code details  Adding features to XWorkplace  Miscellaneous WPS programming notes ═══ 2. Notices and Legalese ═══ Please see the XWorkplace User Guide for the "Notices" section: the GNU General Public Licence (GPL), the development team, credits, and thank you's. ═══ 3. Introduction -- Start Here ═══ Welcome to the XWorkplace source code! This book has been written by XFolder's and XWorkplace's original author, Ulrich MФller, to introduce you to the world of XWorkplace programming. This book covers two different things: 1. Information about XWorkplace National Language Support (NLS). This is of interest for people who would like to translate XWorkplace to a new language. No knowledge of C programming is required for translations, but knowing HTML helps. See "National Language Support" for more information. 2. Information about the XWorkplace C source code. This is mainly of interest for programmers who would like to contribute to XWorkplace, find bugs, or who are just interested in learning how the thing is functioning. See "The XWorkplace Source Code" for a general introduction and how to compile. See "Source Code Details" for information about specific XWorkplace features and their implementation. This is useful for contributors. See "Adding Features to XWorkplace" for information about how to hook your own code into XWorkplace. If you have questions, please post a message to xworkplace-dev@yahoogroups.com, which is the mailing list for XWorkplace developers. But if you want to start writing WPS classes, please don't start asking things like "What is an IDL file?". If you have never created a WPS class, please read the excellent courses at EDM/2 (www.edm2.com) first, which explain a lot of things to get you started. As far as I know, XWorkplace is the most complex WPS source code available, and it probably does not serve very well as a WPS beginner's tutorial. Thanks! ═══ 4. National Language Support (NLS) ═══  Overview  Requirements for translations  Preparations  The main NLS DLL (XFLDRxxx.DLL)  The INF and HLP files  The h2i utility  Images and screenshots  Other files  Testing your NLS files ═══ 4.1. Overview ═══ National Language Support (NLS) is done by separating everything that is language-dependent from the actual program logic. The way XWorkplace does this is that all the NLS stuff is packaged into files that are completely separate from XFLDR.DLL so you can change XWorkplace's language without having to recompile XWorkplace itself. XWorkplace NLS packages identify themselves to the XWorkplace core via country codes, as described on the "COUNTRY" page in the OS/2 Command Reference (CMDREF.INF). XWorkplace's language depends on exactly one setting in OS2.INI (application "XWorkplace", key "LanguageCode"). If that setting is changed, XWorkplace assumes a whole new set of language files to be present. The setting is only checked once at WPS startup or later if you explicitly load a new language in the XWorkplace Setup object. If no code is present, "001" for US English is assumed. The XWorkplace sources contain everything for creating the US English NLS package, as it comes with the standard XWorkplace binary release. The language code for US English is "001". Everything that is related to English NLS is therefore in the 001\ directory tree. The first step you'll have to take is finding out your country code. For example, Italian would be 039. Unfortunately, XWorkplace's National Language Support (NLS) is spread across quite a number of files, which have different file formats. Basically, XWorkplace's NLS can be separated into three parts: 1. Run-time NLS: this is all the language-dependent things that XWorkplace will ever display to the user, such as menus, dialogs, messages, and notebook pages. Here we have:  The NLS DLL to be put into the XWorkplace bin subdirectory. This file contains all language-dependent Presentation Manager (PM) resources, mostly dialogs and menus. Also, a lot of strings are stored here. The NLS DLL is called xfldrXXX.dll, with "xxx" being your three-digit language code. The files neccessary to translate this DLL are in the 001\dll directory. All this goes into bin\xfldr001.dll in the binary release. See "The main NLS DLL" for details.  The XWorkplace message file, help\xfldrXXX.tmf. This file holds all kinds of messages which are mostly displayed in XWorkplace's message boxes and are too large to be put into the resources of the NLS DLL (because PM string resources are limited to 256 characters, unfortunately). The file has a format similar to that of the source files for the IBM MKMSGF.EXE utility, but using that utility is not necessary for XWorkplace. There are several *.tmf files in the 001\misc directory in the sources, which are combined to form help\xfldr001.tmf in the binary release. See "Other files" for details.  The WPS class descriptions which are displayed on the "WPS Classes" settings page in the "Workplace Shell" object. This was introduced with XFolder 0.80 also. This file is called XFCLSxxx.TXT, with "xxx" being your three-digit language code. It also resides in the 001\misc directory. See "Other files" for details.  A number of REXX .CMD and text files for XWorkplace's installation. The files to be translated are in the 001\misc subdirectory and go into install\ in the binary release. See "Other files" for details. 2. Documentation: the XWorkplace User Guide (INF file) and online help (HLP file). This is not absolutely necessary and probably the larger part of the translation work, since I have written so much text over time. Here we have:  The XWorkplace Online Reference (INF) in the XWorkplace main directory. This file is called xfldrXXX.inf, with "xxx" being your three-digit language code. The HTML source files neccessary to translate this file are in the 001/inf.001 subdirectory. See "The INF and HLP files" for details.  The XWorkplace HLP file in the HELP subdirectory. This file holds all the help panels that are displayed when you press F1. This file is called xfldrXXX.hlp, with "xxx" being your three-digit language code. The files neccessary to translate this file are in the 001/xwphelp2 subdirectory. See "The INF and HLP files" for details. (Yes, I know the directory naming conventions might seem strange, but if you have ever tried to rename a directory with CVS, you will understand.) 3. Installation: a number of files which are only used while XWorkplace is being installed. These set up XWorkplace objects and INI keys in a certain language. Please see the README in the MISC directory for details. 4. The Warp 4 SmartGuide script which was present with XFolder and XWorkplace before V0.9.7 is gone now. ═══ 4.2. Required Tools ═══ Even though all the source files in this package can be edited using any editor (because they're all plain text files), you will need some extra utilities to create the actual NLS files. In any case, a C compiler is not required. To be more precise:  I have written my own utility which converts the HTML sources for the INF and HLP files to IPF format for IPFC (see below). This is in the xwphelpers repository on Netlabs, so in addition to the XWorkplace sources, you will need to check out xwphelpers as well. The utility is called h2i.exe and gets called automatically by the makefiles.  The makefiles also convert the GIF files for the INF and HLP files to BMP format automatically. For this to work, you will need the "Generalized Bitmap Module" (GBM) package from Hobbes. The makefiles assume that gbmsize.exe is on your PATH.  In order to create INF and HLP files, you need IBM's "Information Presentation Facility Compiler" (IPFC.EXE). This thing is part of the OS/2 Developer's Toolkit, which is finally included with eComStation and the IBM Convenience Packs. If you don't have the Toolkit, IPFC is contained in the Device Driver Development Kit (DDK), which IBM has put online at http://service.boulder.ibm.com/dl/ddk/priv/ddk-d. You will need to register, but then downloads are free. Download combase.zip, which has IPFC plus a number of other tools. (Note: I was reportes that apparently this is now in tools.zip.) After unpacking, IPFC is in the DDK\base\tools directory, which you should include in your PATH.  The makefiles in this package were written for IBM NMAKE, which comes with all the IBM compilers (and also with the DDK mentioned above). I'm not sure these will work with other MAKE utilities, such as DMAKE or GNU make. I'd be grateful for feedback. The makefiles are only for conveniently updating the target NLS files. If you don't have NMAKE, you can still assemble the files manually. See the following pages for details.  XWorkplace V0.9.0 and above can read the TMF message file directly, so MKMSGF.EXE is no longer needed to compile message files. ═══ 4.3. Preparations ═══ XWorkplace strictly separates language-dependent resources from the rest of the program. To make things easier, all language-dependent resources are in the "001" subtree of the main XWorkplace directory, which holds all English NLS files. Here's a list of things you need to do before you can begin translations. 1. The first thing you need to do is find out your three-digit language code. Open the OS/2 Command Reference, "Country" page, and find the code for your country. 2. Make a copy of the entire 001 directory tree within the XWorkplace source tree, renaming it to your country code. For example, if your language is Italian, you should have a 039 tree next to the 001 tree. 3. Some files in the 001 directory carry a three-digit language code in their respective names. For your language, you need to change all the filenames with "001" in their names to your country code (e.g. 039 for Italian). For example, rename dll\xfldr001.dlg to dll\xfldr039.dlg. 4. You will also have to change the dll\*.def file and dll\makefile, which assume a country code of 001 at this point. Required changes are noted in the files themselves. 5. Open dll\xfldrXXX.rc (with "xxx" being your country code) and find the strings "ID_XSSI_DLLLANGUAGE" and "ID_XSSI_NLS_AUTHOR". Change those two to match your language and name; this is the information that is displayed to the user in the "Language" drop-down box in the "XWorkplace Setup" settings object. You should now be ready to recompile the NLS DLL for the first time. If you have IBM VAC++, you can simply use MAKE.CMD to have the DLL recreated. Open MAKE.CMD and check somewhere in the middle where the script checks for the presence of the 049 directory, and change the variables to point to your new directory instead. Running MAKE.CMD should then work fine, since all the neccessary files are included. Otherwise, things get a bit more complicated. Since the resource compiler (RC.EXE) is already included with every OS/2 installation, you can try the following: 1. Copy an existing XWorkplace resource DLL (e.g. xfldr001.dll) into your new NLS directory; rename it so that it contains your language code (e.g. xfldr039.dll). 2. Open a command line in that directory. 3. Type rc xfldr039.rc xfldr039.dll (replace "039" with your language code), which should create a new .RES file and link it against the existing DLL. After recompiling, you can test the DLL as described in "Testing the DLL". ═══ 4.4. The NLS DLL ═══ Files that need to be changed:  xfldr001.def: The module definition file. Required changes are noted in the file itself.  makefile: Makefile for IBM NMAKE. Required changes are noted in the file itself.  xfldr001.rc, xfldr001.dlg: These are the main resource files which need lots of changes. I've documented everything you need to change in the .RC file. The .DLG file is included when the RC files is recompiled. It contains all the XWorkplace dialogs (i.e. notebook pages and other dialog windows). Sorry, there are no comments in there because I'm using DLGEDIT.EXE to create the dialogs, which rewrites the DLG file at every change, so all comments in there get lost. Note: Because of this stupid limitation, I am currently trying to get rid of all the dialog resources and eventually the .DLG file altogether. Starting with V0.9.16, more and more dialog resources are converted so that XWorkplace only loads the strings for the dialog elements from the string tables in the .RC file any more and creates the dialog dynamically at run time. This also saves you from realigning the controls if you don't have enough space. For the remaining dialogs, you can use DLGEDIT.EXE, the IBM dialog editor from the Developer's Toolkit, to change the dialogs. (This will not affect the .RC file.) For this, you will need to create the .RES file first. This is done by MAKE.CMD; alternatively, you can start RC.EXE with the "-r" option, which creates a .RES instead of linking the resources to an executable. If you then open the .RES file with the dialog editor, you can choose include/dlgids.h as an include file so that all the numerical ID's have a more meaningful name. When you then save the file, the .RES and .DLG files will be recreated by DLGEDIT. The .RC file remains untouched though. The dialog editor has a helpful "Translate mode" in its "Options" menu which disables a lot of menu items so you don't accidentally change dlg ID's or other important stuff. I don't know if you can use the URE editor also, I have not tried that. ═══ 4.5. The INF and HLP Files ═══ The sources for the INF and HLP files are written using a slightly extended HTML syntax. Creating INF and HLP files is therefore a two-step process: 1. The HTML sources are translated into an .IPF source file, using my the h2i utility. I am no longer using html2ipf.cmd because it's so damn slow and doesn't support string replacements. 2. Next, the .IPF source is fed into IBM's ipfc from the Developer's Toolkit. See "Requirements" for where to get this utility. Please use the make.cmd file on the top level of the XWorkplace source files to have the doc files created. I recommend translating all the .HTML files and keep using make.cmd to have the HTML files converted into a single .IPF file (using my h2i utility), which can then be fed into ipfc. This has the advantage that you'll only have to change some panels in future versions, of which I will keep track. Also, I always note changes to the INF and HLP files in the HTML source files in HTML comments tags, so you can easily search for what's changed. The "root" file in each of the two source directories is called xfldrXXX.html, respectively, with "xxx" being your country code (which you should change in this one filename because h2i will use this name for the target IPF file also). In any case, mind these important notes:  The (slightly awkward, I admit) structure of the files is the following, in both directories: xfldrXXX.html is the "root" file. All other files are somwhere below in the INF/HLP table-of-contents hierarchy, depending on the SUBLINKS tags in the HTML source files.  Do not change filenames! h2i sorts the IPF pages internally alphabetically according to the filenames, that's why I've implemented these strange naming conventions. I admit that since the structure has developed over time, it may not seem very logical to you, but it works. Changing things here will result in a lot of work, since all the links in the HTML files would have to be adjusted also.  Within the HTML files, do not change anything within angle brackets (""). Translate only the text outside of these. Even though some tags might not be good HTML style and the HTML files might not look pretty when viewed with Netscape, certain tag combinations (especially the
  • combinations) are neccessary to make the pages look good for IPF.  Don't forget to translate the page titles (in between the tags). This is what appears in the title of a panel window and in the "Table of contents" tree. I've forgotten this many times...  I do not recommend translating everything for the INF book. For the old XFolder, I did the German translation myself, and I think that the "Revision history" section makes no sense translating, because it is frequently updated and NLS versions didn't exist for the older versions anyway. Also, the "XWorkplace Internals" pages apply to programmers, which need to know English anyway, because otherwise they won't find their way through the required Toolkit docs either. So I think you can save yourself some work there. ═══ 4.6. The h2i Utility ═══ Older versions of XFolder and XWorkplace used the very valuable html2ipf.cmd script by Andrew Pavel Zabolotny which has served me quite well over the years. However, this has now been replaced with my own h2i.exe utility, which is both faster and more flexible. h2i is contained in the xwphelpers repository at netlabs (see "Requirements"). Start h2i to have its syntax explained. It works quite similar to html2ipf.cmd, so you can also check HTML2IPF.INF for the general principle. In addition, h2i supports the following:  The <HTML> tag has new attributes: XPOS= and YPOS= work just like IPF's "x" and "y" tags; WIDTH= and HEIGHT= are the same as in IPF.  The <A> tag accepts a new AUTO= attribute, which works just like HREF=, but automatically opens and closes the window (this is, for example, used on the "Introduction" page of the XWorkplace Online Reference).  The <CITE> and </CITE> formatting tags are now set to use a non-proportional font, which is used extensively.  Some formatting changes (<UL>, <BR>, <B> etc.).  As opposed to html2ipf, h2i supports the resid attribute to the <HTML> tag to allow setting the resid explicitly, which is very helpful for composing help panels that can actually get called by ID from XWorkplace.  In addition, h2i supports string replacements via entities. An entity always has the form &entity; and whatever is between the "&" and and ";" characters can be dynamically replaced. Since h2i can also parse C include files for C preprocessor #define's, this allows for including the same headers in XWorkplace and h2i to share definitions and resids.  Finally, h2i supports the new <IFDEF> and <IFNDEF> tags for conditional compiles. ═══ 4.7. Images and Screenshots ═══ The images in both the INF and HELP directory are in GIF format. Unfortunately, HTML does not support the OS/2 BMP format, and ipfc does not support GIF. So the makefiles in INF and HELP automatically convert all GIF images to BMP format, if required. This requires gbmsize.exe to be on your PATH (see "Requirements") If you do not want to use gbmsize, you can convert all the GIFs to BMPs manually, e.g. using PMView. Be careful though: IPFC only supports uncompressed OS/2 1.3 bitmaps. That is, neither compressed bitmaps nor Windows or OS/2 2.0 bitmaps work. This is annoying, because IPFC's own image compression is totally outdated, but there's nothing we can do about this. I'd be very grateful if you could create your own screenshots for the online documentation to reflect your language. I have used the following settings for the screenshots (just for your information, you don't have to use these): Fonts used: Titlebars: Humanist 521, 13 points (available on the CorelDraw 4 CD in Type 1 format) All the other fonts are set to 9.WarpSans. CandyBarZ installed, colors: Active: top 191/0/0, bottom 52/0/0 Inactive: top 160/160/130, bottom 40/40/40 Oh yes, XWorkplace installed. ;-) Now, if you create your own screenshots, save them as GIFs with the exact filenames of the originals (e.g. trunc.gif); you must then DELETE the respective BMP file, because HTML2IPF will only call the image converter for BMP if no BMP file of the same name exists. Tricks to reduce file sizes: Keep in mind that HLP/INF files have their own compression scheme which works best when much redundant data is in the bitmap files. For those who know what that is: IPFC compresses using a modified Lempel-Ziv-Welch algorithm (similar to the GIF format). In plain English, large areas which have the same color can be compressed best. This leads to the following recommendations:  Do not take screenshots of folders with background bitmaps. There will be practically no compression on those.  Use as few colors as possible. You can use PMView to reduce the number of colors to, say, 12 colors, which usually still looks alright.  When converting images, make sure you don't have "dithering" or "error diffusion" enabled, because this will add lots of extra pixels which will make compression less efficient.  Also, if you need to scale images to a smaller size, make sure not to enable any "interpolation", i.e. introducing extra pixels to make colors smoother. This adds lots more colors to the file, which cannot be compressed well either. ═══ 4.8. Directory "MISC" ═══ The MISC directory contains files used by XWorkplace's installation script (INSTALL.CMD) plus the SmartGuide Script used for the XWorkplace introduction.  INSTxxx.MSG (with "xxx" being your country code) was used for installation messages before the WarpIN installer was used. Even though the old INSTALL.CMD still works for XWorkplace, it really doesn't make much sense to translate this message file any more. The file was used by XHELP.CMD (in the XWorkplace package, taken from my CommandPak), which is capable of extracting single text messages in between the <TOPIC>; and </TOPIC> tags in this file. The text between these tags is then displayed on the screen. If you still wish to translate this file, what you need to do here is simply translate all the text which follows a <TOPIC> tag. The text is displayed "as is", and no formatting is performed; as a result, you must take care that no more than 80 characters are contained in a line. You also should take care of the line breaks: it makes a difference in output whether a </TOPIC> end tag is found at the end of a line or at the beginning of a new line, because in the latter case, the line break is still printed to the screen. Just one more note: Do not change the keys mentioned in this file ("X", "Y", "N"), even if your language does not use "Y" for saying "Yes". Unfortunately, INSTALL.CMD relies on these keys. :-(  INSTLxxx.CMD is a straightforward REXX script which creates the default XWorkplace installation objects (the "XWorkplace" folder plus all the new settings objects). Even if you don't know REXX, don't worry: you only have to change the strings on top of the file, which contain all the language-dependent things. Be careful with the quotes. Do not change anything else, because XWorkplace relies on it. Even though XWorkplace uses WarpIN for installation, this REXX script gets called after the first WPS restart after installation.  Similarly, CROBJxxx.CMD creates the default XWorkplace Configuration Folder. This REXX script gets called by INSTLxxx.CMD. Also, this gets called independently when the "Recreate config folder" button is pressed on the "Objects" page in the "XWorkplace Setup" object.  SOUNDxxx.CMD is the REXX script which creates the neccessary INI entries for having the new XWorkplace system sounds in your "Sound" object. Only change the strings at the top of the file to your language. This REXX script gets called from the "Features" page in the "XWorkplace Setup" object when extended system sounds are enabled.  XFLDRxxx.SGS used to be a Warp 4 SmartGuide script to display the "Welcome" window after XWorkplace has been installed and the WPS has been restarted. This is no longer distributed starting XWorkplace V0.9.7, since SmartGuide is no longer supported with OS/2. Do not translate it. ═══ 4.9. Testing Your NLS Files ═══ XWorkplace's NLS depends entirely on a single entry in OS2.INI ("XFolder"::"LanguageCode"). This entry defaults to a "001" string and can be changed either at installation time (if the install script does it) or by using the "Language" setting in the "XWorkplace Setup" object. All NLS resources are loaded depending on this one setting. That is, if you change the language, XWorkplace expects not only your NLS DLL to be present in /BIN, but also the TMF, HLP, and WPS class description files in /HELP. So if you have not translated these yet, you might want to create a copy of the English ones with your language code in the filename. To test a new version of your NLS files:  Make sure you do not currently have your new NLS DLL selected on the "XWorkplace Internals" settings page, because if you do, the DLL is locked by XWorkplace and cannot be replaced. Select "US English" instead, which will unlock the previously used DLL, which can then be deleted from the XWorkplace directory. Then put your new DLL into the BIN subdirectory and open the settings again; select your DLL and see if things work.  Additional caveat for INF/HLP files: Before compiling the .IPF to the .HLP/.INF files, you should make sure that the target file is not currently in use. With INF files, that's easy: simply close it if it's open. With HLP files, you have to keep in mind that the WPS keeps these files locked even after you've closed a help panel window. However, the WPS only ever keeps a single HLP file locked at a time, so in order to unlock the XWorkplace HLP file, simply open a default WPS help panel, e.g. by selecting "Extended Help" for the Desktop window. You can then copy the new XWorkplace .HLP file to the XWorkplace HELP directory. (MAKE.CMD will do this automatically.) ═══ 5. The XWorkplace Source Code ═══  Overview  Requirements for compiling  Build setup  Making (building) XWorkplace  The config.in file  XWorkplace makefiles  Troubleshooting  Creating the code documentation  The SRC\CLASSES\ directory  The SRC\HELPERS\ directory  XWorkplace function prefixes  Debugging XWorkplace ═══ 5.1. Overview ═══ Before going into the details of source code, which is complex enough to get even the more experienced programmer confused, please read the chapters in the "XWorkplace internals" chapter of the XWorkplace Online reference for a first introduction to XWorkplace's basic inner workings. This will give you a vague idea of what XWorkplace is doing where and deals with the most important concepts only, so you don't get overwhelmed by all the details which come in this file. The different subdirectories contain the different parts of XWorkplace. The layout of the subdirectories has been changed with XWorkplace V0.9.0 to allow for easier addition of new features by people other than me. Also, since XWorkplace will be on the Netlabs CVS server, many changes had to be made to the makefiles and code layout.  The 001\ directory tree still contains all the National Language Support (NLS) for English.  The BIN\ directory tree contains binary object files which are created from the sources (below) by the various makefiles. If that directory doesn't exist (it's not part of the CVS tree, for many reasons), it will be created automatically by the makefiles. The directory structure and the makefiles are designed to not depend on the BIN directory tree. As a result, you can always delete the entire BIN directory or any single files in there to enforce a complete or partial recompilation by the makefiles.  The IDL\ directory contains SOM IDL source files for the various XWorkplace classes. The .DEF files in this directory are created automatically by the SOM compiler (SC.EXE), but not used in the build process (see below). When invoked by the makefile, the SOM compiler is told to update the C sources in SRC\CLASSES and write the class header files (*.h, *.ih) to INCLUDE\CLASSES. In general, all SOM code should be in the CLASSES subdirectories. Since several people might be working on the same classes, the code in CLASSES should only call implementation code in other directories of SRC, which should be prototyped in a corresponding subdirectory of INCLUDE.  The INCLUDE\ directory tree has all the headers for XWorkplace. In that tree, INCLUDE\CLASSES\ has the headers which are generated by the SOM compiler from the IDL files in the IDL directory (above). There is no hand-written code in that directory. INCLUDE\HELPERS\ has the headers for the files in SRC\HELPERS\ (see below). Note: This is now in a separate CVS archive on Netlabs, which is called "xwphelpers". You need to check out that archive to be able to compile. INCLUDE\FILESYS\ has the headers for the files in SRC\FILESYS\ (see below). INCLUDE\SHARED\ has headers for code which might be used by several parts of XWorkplace, developed by several people, and corresponds to SRC\SHARED. I guess you get the idea now.  The SRC\ directory tree contains the actual C/CPP source files for XWorkplace. In that tree, SRC\CLASSES\ has the SOM code for the WPS classes which will be compiled into XFLDR.DLL. It is this DLL which loads the NLS DLL (XFLDRxxx.DLL, with xxx being a language code) at Desktop startup or when the language is changed using the "XWorkplace Internals" settings page. See this page for more. The SRC\HELPERS\ subdirectory contains a lot of C files with helper functions which are independent of XWorkplace. That is, these can be used with any OS/2 VIO and/or PM program. The functions are grouped into categories. See this page for more. Note: This is now in a separate CVS archive on Netlabs, which is called "xwphelpers". You need to check out that archive to be able to compile. The SRC\HELPERS\ dir also contains files from Dennis Bareis' PMPRINTF package, which is available in full from his homepage. Refer to the "Debugging XWorkplace" section for details. The other subdirectories in SRC\ contain implementation code for XFLDR.DLL (called by the SOM code in SRC\CLASSES) or external programs which are part of XWorkplace, such as NetscapeDDE and Treesize.  TOOLS\ contains some tools used by the makefiles. See Making XWorkplace. ═══ 5.2. Required Tools ═══ 1. Compiler. With V0.80, I have switched to IBM VisualAge C++ 3.0 to develop XWorkplace. For VAC++ 3.0, fixpak 8 is strongly recommended, because the original crashes frequently with the complex SOM header files. Other compilers are presently not supported. Most notably, some people have tried to compile using VAC 3.6.5 and ran into problems. 2. You need some OS/2 Developer's Toolkit for all the SOM header files and the SOM compiler. I am still using the Warp 3 toolkit, mostly because if XWP is built using a newer toolkit, it will not start on Warp 3. Besides, it appears that the SOM compiler and nmake from the Warp 4.5 toolkit have some problems, according to some reports I got. 3. IBM NMAKE is our make utily. This comes with VAC, so no problem there. As said above, newer nmake's appear to have problems with XWorkplace's makefiles, mostly with invoking the SOM compiler. I am using nmake 3, which comes with either the Warp 3 toolkit or VAC 3. 4. You need to check out the "xwphelpers" CVS archive from Netlabs also. XWorkplace uses a lot of code which is shared between WarpIN and XWorkplace, which has been separated into a new CVS archive. (Starting with V0.9.6, the WarpIN sources are no longer needed.) There's no way to compile without that code. 5. Only if you enable debugging code through XWP_DEBUG in config.in, you will need the full PMPRINTF package by Dennis Bareis. Check the "Debugging" section for details. ═══ 5.3. Build Setup ═══ With V0.9.12, the build process has been reworked to make building a bit easier. External environment variables are no longer needed, and the makefiles now reference the tools by their full path, so there's no need to put the files from tools\ onto your PATH any longer. Still, the following setup is recommended for XWorkplace to build successfully: 1. You will need to set your build environment in the config.in file before building will work. This replaces the external environment variables which were required before V0.9.12. 2. Because XFLDR.DLL is locked while the WPS is running, you should SET RUNWORKPLACE=[bootdrv]:\OS2\CMD.EXE in CONFIG.SYS. This will cause an OS/2 window to come up instead of the WPS after bootup (which is really helpful if something is wrong in the XWorkplace code). Type pmshell then to start the WPS. 3. Make sure you have at least 60 MB of free space in your TEMP directory. This is needed for precompiled headers. I strongly recommend using RAMFS.IFS for your TEMP directory. This dramatically speeds up things if you have 128 MB RAM or more. See Troubleshooting for hints about RAMFS. ═══ 5.4. Making (Building) XWorkplace ═══ Starting the build process. Starting with V0.9.12, this is a two-step process. After opening a command line in the main XWorkplace directory (the parent of src and include), do this: 1. Run nmake dep. This examines all source files for dependencies (#include statements) and writes a .depend file into each source directory which is then used by the makefiles to rebuild things properly. Note: You only need to run nmake dep before building for the first time, or if source dependencies (#include statements) have changed. To be on the safe side, re-run nmake dep once after you have checked out new sources from the CVS server. nmake dep uses the fantastic fastdep utility by Knut Stange Osmundsen, which I have stolen from the Odin sources. This should be in the root directory of your xwphelpers repository. Note: If you have never built XWorkplace before, nmake dep will give you lots of warnings that headers could not be found. This is normal because the SOM headers for the XWorkplace classes will only be produced during the first full build. 2. After that, run either nmake all or nmake really_all to build. This will produce lots of object files in bin\ and the executables in bin\modules. At the same time, those executables are copied to the proper locations in your XWorkplace installation directory as specified in config.in. After that, restart the WPS to have XFLDR.DLL reloaded. nmake all (or just nmake) will only rebuild XFLDR.DLL, XWPDAEMN.EXE, and XWPHOOK.DLL , while nmake really_all will produce the full set of XWorkplace executables plus the NLS files. Rebuilding the whole thing. To rebuild everything, you have three options:  Delete the entire bin\ tree. This is not part of the CVS repository at Netlabs and recreated dynamically for all files that have been created. Deleting it will cause all target object files to become outdated and thus be recompiled, which will then also invoke the linker.  Use nmake -a on the main makefile. This will even re-invoke the SOM compiler on all .IDL files.  Open and save (or touch) include/setup.h to make it newer than the target files. This is #include'd in all XWorkplace C files, and the makefiles' inference rules will then enforce recompilation of all C files. ═══ 5.5. The "config.in" File ═══ This section describes the build environment which is used by the makefiles. Before V0.9.12, these variables had to be set externally before building. This has been changed; the environment is now set exclusively through the config.in file in the main XWorkplace directory. If these values not set correctly, you'll get errors from the makefiles (certain checks are built into those), or compilation or linking will fail. 1. CVS_WORK_ROOT must point to the root of your CVS working directories. The XWorkplace makefiles now (V0.9.6) assume that all your projects (most importantly, XWorkplace and XWPHelpers) are located in subdirectories of a single directory. (This is useful for working with CVS in the first place, so this shouldn't be much of a requirement.) This variable must point to the parent directory of xworkplace and xwphelpers. The makefiles will set a number of other environment variables based on that variable to locate the XWorkplace and XWPhelpers source tree bases. Example: SET CVS_WORK_ROOT=K:\cvs 2. TKBASE and VACBASE are new with V0.9.12 and must point to the base directories of your Toolkit and VisualAge C++ installations, respectively. Specify the parent directories of h and include. This is because the build process no longer relies on your INCLUDE environment variable, but specifies everything on the compiler and linker command lines. 3. XWPRUNNING must point to an existing XFolder/XWorkplace installation. Since the makefiles first create all the files in bin\, this is not suitable for testing your code, because XWorkplace expects a lot of files at fixed locations relative to the directory where XFLDR.DLL resides. Most makefiles will copy the target files to the directory pointed to by this environment variable (after unlocking executables), so after rebuilding, you can simply restart the WPS, and your new code will be running in the WPS. Do one of the following:  Install the latest XWorkplace binary WarpIN archive. Set XWPRUNNING to the same directory which you specified as the target path to WarpIN.  Alternatively, copy the entire "release" subtree to some new location and specify that location with XWPRUNNING to start. This must point to the parent directory of bin\, help\ etc. in the XFolder/XWorkplace installation tree. Example: SET XWPRUNNING=J:\Tools\XWorkplace 4. If XWP_DEBUG is defined (to anything), XWorkplace will be compiled and linked with debug code enabled. This will have quite a number of consequences since this will pass the __DEBUG__ define to the compiler, to which many code parts react. For example, only if that flag is defined, PmPrintf calls will be compiled. See "Debugging XWorkplace" for details. IMPORTANT NOTE: If you compile the debug version, you must have the PMPRINTF DLLs somewhere on your LIBPATH, or otherwise you'll spend days figuring out why XWorkplace is simply not working any more. (I had this once after a reinstall of OS/2, after which the DLLs where gone.) That is, XWorkplace classes will not load at Desktop startup, because the PMPRINTF DLL imports cannot be resolved. Neither will registering XWorkplace classes succeed. And don't expect to get an error message other than FALSE. By contrast, if XWP_DEBUG is not defined, release code is produced. 5. Replacing in-use files. You can rebuild XWorkplace even while the WPS is up and XWorkplace is installed. The makefiles will automatically unlock DLLs which are currently in use (DosReplaceModule). After the rebuild, restart the WPS to make it use the newly built DLLs. To do this, simply set XWP_UNLOCK_MODULES=YES. Note: This environment variable has was with V0.9.3 because with Warp 4 FP13, unlocking WPS modules hung the WPS after the next Desktop restart. This problem has been fixed with FP15, so there is really no reason to not have this variable set to YES any more. The exception to this are XWPDAEMN.EXE and XWPHOOK.DLL, which are never unlocked. This is intentional. Unlocking an executable which has registered a system hook (which becomes part of every PM process on the system) will never release the hook DLL again, so you'd have to reboot to use the new DLL. Warning: If you don't restart the daemon before restarting the WPS, the XWorkplace startup folder will get processed again after the Desktop restart. To start XWPDAEMON.EXE explicitly, give it the "-D" option, or it will tell only you to get lost. So if you get an error during the build process which says "XWPDAEMN.EXE is in use", kill XWPDAEMON.EXE using some process killer, which will properly unload the hooks, and try building again. ═══ 5.6. XWorkplace Makefiles ═══ The XWorkplace makefiles are quite smart, but therefore quite complex. This section is supposed to explain why I chose this approach and what the makefiles do exactly. I redesigned the entire XWP sources structure with V0.9.0 and made some more changes with V0.9.12. The sources were supposed to be structured so that the following was possible:  Several developers must be able to cooperatively work on the sources. As a result, it must be possible for each developer to work on his own directory to avoid conflicts.  Still, it should be possible to build the entire thing in one snap.  Building the entire thing should even take place if a makefile from one of the subdirectories was called.  The makefiles should be easily adjustable in case someone wants to use a different compiler.  Clean separation of files which reside on the Netlabs CVS server from those which are created in the build process.  Since the helpers from the "xwphelpers" CVS archive are used, it must be possible to call the makefile in that directory, but still write the .OBJ files into the XWorkplace bin\ directory.  It should be possible to rebuild the executables (XFLDR.DLL and the .EXE files) and just restart the WPS for the changes to take effect.  With V0.9.12, I was finally getting tired of always manually updating the makefile dependencies if #include statements were changed in the sources. So nmake dep support had to be introduced. As a result, I came up with the following:  A single configuration file which sets up all compiler and linker options. This is setup.in in the main directory, which is included from all makefiles via the nmake !include directive.  System-dependent configuration (directories etc.) is done via another file, config.in.  The makefiles create all output files (.OBJ, .DLL, .EXE) in the bin\ directory, which is created if it doesn't exist. To rebuild the entire thing, one can simply delete the entire bin\ tree.  If the main makefile is started, it changes to the subdirectories and calls nmake again with MAINMAKERUNNING=YES defined. This way the sub-makefile knows that it's started from the main makefile. Otherwise the sub-makefile calls the main makefile (which in turn calls the sub-makefile) so that the entire thing always gets rebuilt.  The sub-makefiles compile only. Linking is done by the main makefile. This way every executable can use shared code, such as the helpers.  nmake dep is supported through a new makefile in src\ which calls all makefiles in the subdirectories of src\ with either the "all" or the "dep" target. A "dep" target was then added to all the makefiles in src\ which invokes fastdep on all C files to produce the .depend file... which is in turn included in the makefiles. This invokes fastdep.exe from the XWP Helpers directory, which I stole from the Odin sources. fastdep was written by Knut Stange Osmundsen and creates a .depend file in each source directory with all the includes which are retrieved directly from the C sources. ═══ 5.7. Troubleshooting ═══ 1. If you get errors from the makefiles, make sure you have set up config.in correctly. 2. Same thing if the compiler cannot find an include file, or the linker cannot find libraries. 3. Make sure your TEMP, TMP, and TMPDIR directories are set correctly. Most importantly, when using RAMFS.IFS, do not use a root directory because apparently RC chokes on that. I have the following in CONFIG.SYS: IFS=J:\common\ifs\RAMFS64.IFS CALL=J:\common\ifs\RAMDISK.EXE Z: RUN=I:\OS2\CMD.EXE /C md Z:\temp SET TMP=Z:\temp SET TEMP=Z:\temp SET TMPDIR=Z:\temp 4. Also, you need to check out the "xwphelpers" CVS archive from Netlabs for the helpers code. Make sure that this code is not outdated (I usually update it together with the main XWorkplace code). Same thing if you get unknown definitions or unresolved externals during compiling/linking. This is most probably due to outdated helpers. 5. If the SOM compiler chokes on the .IDL files, you may have a problem with the nmake version that is running. Several people have reported that the nmake that comes with the Warp 4.5 Toolkit does not set SMINCLUDE correctly from the makefiles. I am still running nmake 3, which comes with the Warp 3 Toolkit and also with the IBM VAC 3 compiler. Alternatively, look at idl\makefile and search for SMINCLUDE. Uncomment the line that sets the environment variable and set the variable to the proper values externally. Some people have gotten the thing to work like that too. The SOM compiler that comes with some versions of the Warp 4.5 Toolkit appears to produce very strange errors also, according to some user reports. I recommend using the Warp 3 Toolkit; see Required Tools. 6. If you get unresolved externals while linking the XCenter widgets, take a look at src\shared\xwp.def, which exports the functions from XFLDR.DLL. 7. If you get something like "File XWPDAEMN.EXE/XWPHOOK.DLL is in use", use WatchCat or some other process killer to kill XWPDAEMN.EXE, which will automatically unload the hook DLL. Then, after the build, restart the daemon by executing XWPDAEMN -D (to circumvent the error message box which comes up otherwise). See config.in for details. 8. If you get really strange compilation errors, VAC might have gotten confused with precompiled headers. Delete all .PCH files in your TEMP directory, or comment out the PRECH line in setup.in to disable precompiled headers altogether. I have also found that killing DDE4LOAD.EXE (the VAC background process) helps sometimes, for whatever reason. 9. If you cannot install XWorkplace after the build, you have probably built the debug version, but the PMPRINTF DLLs are not on your LIBPATH. Do not build the debug version if you haven't downloaded PMPRINTF yet. See "Debugging" for details. ═══ 5.8. Creating the Code Documentation ═══ Starting with V0.9.0, you can have HTML documentation created for the whole XWorkplace code. This is done using my own xdoc utility, which can be found in the root directory of the XWorkplace Helpers. The sources for xdoc are still in the WarpIN source tree. xdoc is capable of parsing C/CPP source code and header files to some extent. When certain codes are found in a comment in the sources (e.g. @@), that comment is assumed to document some function and will be considered for HTML output. Call createdoc.cmd in the main directory, which will create the HTML documentation in a new subdirectory of the code tree HTML\. Open index.html then to get an index of all XWorkplace source files. ═══ 5.9. The SRC\CLASSES\ directory ═══ SRC\CLASSES contains only C code which was originally generated by the SOM compiler from the IDL files in the IDL\ directory. Of course, that code has been extended so that the new classes do anything meaningful. As opposed to versions before 0.9.0, this directory contains SOM code only. The idea is that (except for fairly small method code) code in one of these class files should call some implementation function in a directory other than SRC\CLASSES. Only in doing so it is possible that several developers can implement features within the same WPS class. For example, src\classes\xfldr.c branches over to src\filesys\folder.c, which is the "folder implementation" code and has most of the folder features. The XWorkplace package contains many classes, but puts them all into a single DLL. This is possible by specifying the same target DLL in the IDL files. (XWorkplace's IDL files are now in the separate IDL\ directory.) One can put several classes into one .IDL file (which happens in xtrash.idl for example) or spread them across several IDL files too. Note that the .DEF files in IDL\ are not used. These files are created automatically by the SOM compiler, which cannot however create a single DEF file if you use several IDL files for the same DLL. For finally linking the whole main DLL, the makefiles instead use \src\shared\xwp.def, which I manually created from all the DEF files which were created by the SOM compiler. This file exports lots of mysterious SOM structures which allow the SOM kernel see the classes in the DLL. ═══ 5.10. The SRC\HELPERS\ directory ═══ SRC\HELPERS contains lots of useful code which I have developed over time. This code is independent of XWorkplace and could be used with any OS/2 program. Some of the code is for PM programs, some can be used with text-mode programs also. Consider this a general OS/2 programming library. Note: XWorkplace's source code does not contain the helpers source code. Instead, during the build process, the XWorkplace makefiles branch over to the "xwphelpers" source tree, which is also on the Netlabs CVS server. You will need to set the HELPERS_BASE environment variable to point to the "xwphelpers" source tree for this to work. In detail, we have:  animate.* contains some animation code.  cnrh.* Container helper functions (new with V0.9.0, partly moved from winh.*).  comctl.* Various window procedures.  debug.* parses executables and SYM files for debugging information; used by except.c (below). Introduced with V0.84, moved to SRC\HELPERS\ with V0.9.0.  dosh.* Control Program helper functions.  eas.* contains helper functions to handle extended attributes.  except.* contains generic code for implementing powerful exception handlers. This was introduced with V0.84 and has been moved to SRC\HELPERS\ with V0.9.0. The code has been straightened out to be independent of XWorkplace with the use of exception "plug-ins". See "XWorkplace exception handling" for details.  gpih.* GPI (graphics) helper functions.  linklist.* contains helper functions to handle linked lists.  prfh.* is new with V0.82 and contains Profile (INI) helper functions.  procstat.* modified Kai Uwe Rommel's DosQProc() functions.  shapewin.* contains the ShapeWindows library. See the top of shapewin.c for details (new with V0.85).  stringh.* contains helper functions to handle strings/texts  syssound.* has code for managing system sound data and sound schemes.  threads.* contains helper functions to synchronize threads.  tmsgfile.* has Christian Langanke's new .TMF (text message file) handling to get rid of those ugly .MSG files.  winh.* Win (PM) helper functions.  wphandle.* Henk Kelder's WPS handles functions. ═══ 5.11. XWorkplace Function Prefixes ═══ XWorkplace uses function prefixes to indicate in which source file a certain function resides. XWorkplace function prefixes are in lower case to distinguish them from the OS/2 API function prefixes. So if you encounter some lower-case function prefix, you can be pretty sure it's some function in MAIN or HELPERS and has been prototyped in the corresponding header file. If a function has no prefix, it's probably in the same source file and not prototyped in the headers. However, there are two general function prefixes which are used to indicate certain function types:  fnwp* is my general prefix for window procedures (the MRESULT EXPENTRY... type), which may or may not be exported.  fncb is my general prototype for callback functions. Most of these are callbacks for the notebook helpers in shared\notebook.c, but there are some for functions in HELPERS also. See the respective function header then. Here are the other function headers and the files where their code resides:  anm* helpers\animate.*  apm* startshut\apm.*  arc* startshut\archives.*  cfg* config\cfgsys.*  cls* config\classlst.*  cmn* shared\common.*  cnrh* helpers\cnrh.*  ctl* helpers\comctl.*  dtp* filesys\desktop.*  dosh* helpers\dosh.*  dsk* filesys\disk.*  ea* helpers\eas.*  exc* helpers\except.*  fdr* filesys\folder.* or filesys\fdrhoty.*  fops* filesys\fileops.*  fsys* filesys\filesys.*  ftyp* filesys\filetype.*  gpih* helpers\gpih.*  hif* config\hookintf.*  krn* shared\kernel.*  lst* helpers\linklist.*  mnu* filesys\menus.*  ntb* shared\notebook.*  obj* filesys\object.*  prfh* helpers\prfh.*  prc* helpers\procstat.*  shp* helpers\shapewin.*  snd* helpers\syssound.* or filesys\sound.*  stb* filesys\statbars.*  strh* helpers\stringh.*  thr* helpers\threads.*  tmf* helpers\tmsgfile.*  wph* helpers\wphandle.*  wpsh* shared\wpsh.*  xsd* startshut\shutdown.*  xthr* filesys\xthreads.*  xwp* newly introduced SOM instance methods in SRC\CLASSES  xwpcls* newly introduced SOM class methods in SRC\CLASSES ═══ 5.12. Debugging XWorkplace ═══ Debugging WPS applications can be really tiresome, because you have to restart the WPS for every tiny change you made to the source codes to take effect. And, as with any PM program, you can't just printf() stuff to the screen. Even worse, it's hard to use the PM debugger, because you have to start the whole WPS (PMSHELL.EXE) with it, since XFLDR.DLL is no standalone application. So I had to look for something else. To enable the debug build, set XWP_DEBUG in config.in. Again, mind the warnings given there. Of course, you will then have to cause a complete rebuild by deleting the bin\ directory. Enabling the debug code has a large number of consequences: 1. Since in debug mode, the __DEBUG__ define is passed on the compiler command line, many other parts of the code can react to that. For example, you will find a few interesting new items in the Desktop's context menu, such as "Crash WPS" to test the exception handlers. 2. Depending on whether __XWPMEMDEBUG__ is uncommented at the bottom of include\setup.h, all memory management functions will be replaced by debug versions which give you very detailed logs of all memory that was ever allocated. See memdebug.c in the helpers for details. This will also produce an additional Desktop context menu item which will open a PM window with lots of information. 3. There are a large number of _Pmpf(("xxx")) calls in the code. These are for the magnificent PMPRINTF package by Dennis Bareis. Some files from the PMPRINTF package are included so that you can compile. The PM interface which actually displays the messages plus the required DLLs which must be on the LIBPATH are not however. Last time I checked (March 13, 2001), this package was available at http://www.labyrinth.net.au/~dbareis/zips_fw/pmf96179.zip. These calls only display anything if the proper DEBUG_xxx #define's are set in include\setup.h (changed with V0.9.0). You can conditionally enable groups of debugging flags in there, but some of them haven't been tested in a long while and might cause compilation errors. For the release version of XWorkplace, all these flags have been disabled, so no additional code is produced at all. You thus don't have to remove the commands to speed up XWorkplace, because this wouldn't make any difference. _Pmpf(("xxx")) uses regular printf syntax, except for those strange double brackets, which are needed because macros don't accept variable parameter lists otherwise. ═══ 6. Source Code Details ═══  XWorkplace settings  Folder window subclassing  Menu manipulation  Extended sort functions  XWorkplace threads  Playing sounds  XShutdown  XWorkplace exception handling ═══ 6.1. XWorkplace Settings ═══ XWorkplace uses two kinds of settings: global and instance settings. The global settings are set both in the "Workplace Shell" object and on the "XDesktop" notebook page and are stored in the "XWorkplace" app in OS2.INI. All the global notebook logic is in src\shared\notebook.c. Note that the large structure called GLOBALSETTINGS that was used from XFolder 0.1 until XWorkplace 0.9.16 is gone now. Yes, it almost broke my heart, but I got sick of it. Instead, global settings are implemented via cmnQuerySetting and cmnSetSetting in src\shared\common.c now. By contrast, the individual object settings are stored in instance data, which is declared in the .IDL files. Most of these are stored and retrieved using the normal WPS mechanism (wpSaveDeferred/wpRestoreData) and can have a certain "transparent" value, which means that the global setting is to be used instead. ═══ 6.2. Subclassing Folder Windows ═══ XWorkplace subclasses all WPFolder frame windows to intercept a large number of messages. This is needed for the majority of XWorkplace's features, which are not directly SOM/WPS-related, but are rather straight PM programming. For an introduction to how this works, see the top of src\folder\fdrsubclass.c. ═══ 6.3. Menu Manipulation ═══ This is perhaps the most complex part of XWorkplace. After all, this is what I've started with, so some code parts may well be unchanged since December '97, when I neither knew much about C nor about WPS programming. With V0.81, all the logic for manipulating and evaluating context menus has been moved to a new file, menus.c. All these functions have been given the mnu* prefix to make this a bit clearer. The XWorkplace classes override wpModifyPopupMenu and wpMenuItem[Help]Selected for almost every replacement class. These overrides either handle the new context menu items in that method code or, for XFolder and XFldDisk, call the functions in menus.c, because these two classes share many common menu items, so we can share most of the code also. All newly inserted menu items use variable menu IDs. You can tell this from their #define names, which have an _OFS_ in their name (check dlgids.h). To all these values, XWorkplace adds the offset that is specified on the "Paranoia" notebook page. For an introduction to how the config folder menu items work, see the "XWorkplace Internals" section in the XWorkplace User Guide. XWorkplace uses a fairly obscure system of global variables to be able to relate menu items to their intended functions. All the variable menu items are stored in a global linked list when the context menu is opened for a folder. In each list item, XWorkplace also stores what kind of object (template, program object, folder content item etc.) the menu item represents. This list is then examined by wpMenuItemSelected to perform the corresponding action (create a new object, start program etc.). After that, the list is destroyed, so it only eats up memory while the context menu is open. All this code is now (V0.81) in menus.c. As opposed to the "regular" new menu items, the folder content menus are initially only inserted as empty submenu stubs. They are only filled with items when they're opened: XWorkplace intercepts WM_INITMENU in the subclassed folder frame proc (fnwpSubclassedFolderFrame, src\filesys\folder.c) and then populates the menu. These menu items are stored in the global list too and marked as folder content menu items so that wpMenuItemSelected will then simply open the corresponding Desktop object. The folder content code has now been moved to menus.c also. Painting the icons is then done using owner-draw menu items; the messages which are necessary for this are also intercepted in fnwpSubclassedFolderFrame and then call corresponding functions in menus.c. The folder content menu windows are also subclassed to be able to intercept right-mouse button clicks. The new window proc for this is called (suprise!) fnwpFolderContentMenu. The xfSelectingMenuItem method introduced in V0.80 has been removed again with V0.81 due to the problems with SOM multiple inheritance. ═══ 6.4. Extended Sort Functions ═══ Folder sorting has been mostly rewritten with V0.9.12, and the documentation for this can be found in src\filesys\fdrsort.c. ═══ 6.5. XWorkplace Threads ═══ XWorkplace is quite heavily multi-threaded and offloads most tasks which are probable to take some time to several threads which are always running in the background. Those threads create object windows and are thus normally blocked, unless there's real work to do. XWorkplace uses mutex semaphores all over the place to serialize access to global data structures. With V0.9.0, all the thread code has been put into a new file, called src\filesys\xthreads.c. All threads are created in krnInitializeXWorkplace (src\shared\kernel.c), which gets called from M_XFldObject::wpclsInitData, which is probably the first SOM method called when the WPS is initializing at startup. All threads have some fntXXXThread function, which is the main thread function passed to the thr* functions in /helpers/threads.c when the thread is created. These in turn create an object window using some fnwpXXXObject function as their window procedure. For each thread, we then have some krnPostXXXMsg function which posts a message to the corresponding object window. All messages are defined and explained in include\filesys\xthreads.h. The following additional threads are available:  The Worker thread is running with idle priority and does mainly maintenance which is not time-critical. For example, the Worker thread maintains the global linked list of currently awake Desktop objects. XFldObject::wpObjectReady post WOM_ADDAWAKEOBJECT to the Worker thread, which then adds the object to that list. (This list is needed by XShutdown to store all the awake Desktop objects; see the XWorkplace User Guide for details). The Worker thread runs at "Idle" priority, unless more than 300 messages have piled up in its message queue. In this case, the priority is temporarily raised to "Regular" and set back if the message count goes below 10 again. This can happen when opening folders with a very large number of objects.  The "Speedy" thread is running at a high "Regular" priority for things which won't take long but should not block the main WPS (Workplace) thread. This thread creates an object window also. For example, the new system sounds are played here, and the Desktop start logo is shown.  The "File" thread is new with V0.9.0 and runs with regular priority, that is, concurrently with the WPS user interface. This now does file operations, such as moving objects to and emptying the trash can and such. This thread will probably get more jobs in the future. ═══ 6.6. Playing Sounds ═══ The new system sounds are played in the "Speedy" thread with high regular priority. Check fntSpeedyThread and fnwpSpeedyObject for details about how this is implemented using MMOS/2. Unfortunately, nowhere is documented how to access and modify the system sounds that appear in the "Sound" object. I have accidently discovered that this is actually fairly easy: these are stored in MMPM.INI in the MMOS/2 directory. Each sound has an index in that file, which I have declared in common.h (those MMSOUND_* #define's). These should be the same on every system. The INI data then contains a "<soundfile>#<description>#<volume>" string. Sound data manipulation has been moved into a separate file with V0.9.0: check /helpers/syssound.c for details. This now also supports sound scheme manipulation. To make sure that the actual sound playing (which requires MMPM/2) also works on systems where MMPM/2 is not installed, XWorkplace dynamically imports the functions from the MMPM/2 DLLs. See src\media\* for details. ═══ 6.7. XShutdown ═══ XShutdown (i.e. the "eXtended Shutdown" and "Restart Desktop" features) resides entirely in the src\startshut\shutdown.c file. XShutdown starts two additional threads to close all the windows. This is described in detail in the "XWorkplace Internals" section of the XWorkplace User Guide. I have also tried to put plenty of comments into the sources. ═══ 6.8. XWorkplace Exception Handling ═══ XWorkplace registers additional exception handlers in certain parts where I considered worth it. With "worth it" I mean the following situations: 1. Certain code parts crashed on my system and these parts seemed error-prone enough to me to outweigh the performance loss of registering and deregistering exception handlers. 2. Exception handlers must be registered for all additional threads. If no exception handling was registered there, crashes would take the whole WPS down, because the default WPS exception handler only deals with the default WPS threads. 3. Exception handlers must also be registered every time mutex semaphores are used. If a code part crashes while a mutex semaphore has been requested by a function, all other threads waiting for that semaphore to be released will be blocked forever. And I mean forever, because even DosKillProcess won't be able to terminate that thread. You'd have to reboot to get out of this. So the exception handler must always check for whether a mutex semaphore is currently owned by the thread and, if so, release it. XWorkplace does not use the VAC++ library funcs for all this, but installs its own set of quite complex exception handlers (using DosSetExceptionHandler()). The code for exception handlers has been made independent of XWorkplace and moved to /helpers/except.c with V0.9.0. Also, I have created a few handy macros which automatically register and deregister the XWorkplace exception handlers. This avoids the frequent problem that one forgets to deregister an exception handler, which leads to really awkward problems which are almost impossible to debug. Those macros are called TRY_xxx and CATCH to mimic at least some C++ syntax. See the top of src\helpers\except.c for detailed instructions how to use these. The XWorkplace exception handlers are the following:  excHandlerLoud is the one that makes the loud sounds and writes the XFLDTRAP.LOG file which is well-known to many XWorkplace users (grin). It uses a longjmp() to get back to the function, which might then react to that exception by trying to restore some safe state of the thread. See the func header for details about how this works. This handler is used by all the additional XWorkplace threads and also by the subclassed folder frame and folder content menu window procs. This slows down the system a bit because the handler must be registered and deregistered for each message that comes in, but there's no other way to do this. (I think.) With V0.84, I have added lots of debugging code which I found in the EXCEPTQ.ZIP package at Hobbes. The exception handlers are now capable of finding symbols either from debug code (if present) or from a SYM file in order to write more meaningful trap logs. See the top of except.c for details.  excHandlerQuiet is similar to excHandlerPlus in that it uses a longjmp() also, but neither is this reported to the user nor is the logfile written to (that's why it's "quiet"). This is used in places where exceptions have ben known to occur and there's no way to get around them. I created this handler for V0.80 because I found out that somIsObj() is not a fail-safe routine to find out if a SOM pointer is valid, even though IBM claims it is. (These were the strange errors in the Worker thread which appeared in V0.71 when folders with subfolders were deleted, because the Worker thread then tried to access these objects when the folder was populated right before deletion.) So I created the wpshCheckObject func which can return FALSE, using this handler, if access to the object fails. ═══ 7. Adding Features to XWorkplace ═══  Overview  Adding a new class  Extending existing XWorkplace classes  The "XWorkplace Setup" object  XWorkplace code you might find useful  Writing an XCenter widget plugin DLL ═══ 7.1. Overview ═══ This section describes how you can integrate your own code into XWorkplace. Since XWorkplace is now a Netlabs project on the Netlabs CVS server, certain rules must be followed so that the whole thing still works even though several developers might be adding stuff. With XWorkplace V0.9.0, I have restructured the whole directory and makefile system to allow for extending XWorkplace. In any case, the idea is that all XWorkplace classes will reside in one single DLL, XFLDR.DLL, but should be capable of being registered independently. Basically, there are two situations which you need to tell apart: 1. You can add a new class to XWorkplace. That is, you wish to hook your code into the WPS, but XWorkplace doesn't have a class for this yet. This situation applies both if you wish to replace a standard WPS class (example: the spooler class, WPSpool) which XWorkplace doesn't replace yet, or if you wish to create an all new class. (This only makes a difference at install time, whether the install program will replace a class.) 2. You may choose to extend an existing XWorkplace class (either an XWorkplace replacement for a default WPS class, e.g. XFolder, or a newly introduced XWorkplace class, e.g. XFldWPS -- the "Workplace Shell" object). This is much simpler, because you can build on existing code. ═══ 7.2. Adding a New Class ═══ This section describes what to do if you want to write some all new SOM WPS class, which should be integrated into the main XWorkplace DLL, and describe which files need to be changed to have your stuff compiled into the whole thing. 1. Most importantly, I suggest that you put all your code into a separate source directory in the XWorkplace SRC\ and INCLUDE\ directory trees. Please do not put your stuff into SRC\FILESYS, because this will make it pretty difficult to maintain order. For the purpose of explanations here, I will assume that your directory is called SRC\YOURDIR\ and your class is called XWPYourClass. So first of all, create your two subdirectories (YOURDIR) in INCLUDE and SRC. 2. Create a new IDL file for your class. As a template, you can use idl\__sample_dataf.idl, which is a sample for a WPDataFile subclass. Put your new IDL file into the idl\ directory and modify the makefile in there to recognize your IDL file. That is, add your header file to the all: statement and add a corresponding line to the bottom. That's all. And please, add a comment that you did so. For the purpose of clarity, I suggest that with your IDL file, you take one of the existing IDL files as a template. My IDL coding style has evolved during the last two years, and I now consider the comments etc. in there pretty lucid in order not to forget anything. Of course, if you have something better, go ahead. 3. Run the main makefile once. The makefile in idl\ will realize that a new .IDL file has been added and create headers in include\classes\, a .DEF file in idl\, and stub C code in src\classes\. 4. Now that you have the stub C file in src\classes, modify the makefile in src\classes to compile your stub file as well. Add your file to the OBJS macro, and add dependency rules in the $(OUTPUTDIR)\xxx.obj: style to the bottom. Again, add a comment that you did so. 5. In SRC\YOURDIR\, write your own makefile which compiles your sources. You can take the makefile in SRC\FILESYS\ as a template. This makefile is pretty smart because it automatically recognizes whether it is called from the main makefile, and if not, it invokes the main makefile, which in turn will call the sub-makefiles later. Also, that makefile uses the general makefile include setup.in in the main directory for compiler setup etc. Make sure that your makefile writes all .OBJ files into the BIN directory, which your makefile must create if it doesn't exist yet. Again, see how the makefile in SRC\FILESYS does this. Other than that, in SRC\YOURDIR, do whatever you want. 6. Coding. For your C code, make sure that you get the #include's right. Take a look at any C file in SRC\FILESYS for examples (folder.c is a good candidate, because it's fairly complex). If you use any headers from include\shared\, there are certain rules that you must follow, because these headers require other headers to be included already. Also, I strongly recommend to always include include\setup.h, because this will automatically make your code PMPRINTF-enabled. I don't care about your coding style, but if you want your code to be documented automatically, you should follow mine, because otherwise xdoc.exe won't work. See Code documentation for details. 7. Note that the SOM compiler is unable to recognize that several classes should be put into the same DLL and create a common .DEF file for this purpose, so you have to do this manually. So have the SOM compiler create a .DEF file for your class from your .IDL source file. (The makefile in idl\ will do this automatically if you have added your header to the all: target.) Then take the block below the EXPORTS line from the .DEF file and add it to the bottom of src\shared\xwp.def (which is the module definition file used for linking the whole XWorkplace DLL). These structures make the SOM kernel see your class in the DLL. If you don't do this, your class cannot be registered. 8. Finally, take a look at \makefile. This is the "master makefile" which links all .OBJ modules into the main XWorkplace DLL (XFLDR.DLL). In that makefile, there is an OBJS macro which lists all the .OBJ files which are to be linked together. Add your .OBJ file(s) to the end of that variable (and please, add a comment that you did so). This should work. If you have any questions, feel free to contact me. ═══ 7.3. Extending Existing XWorkplace Classes ═══ This section describes what to do if the class that you need already exists in XWorkplace. Of course, this requires less setup work, because much of the work has already been done. 1. If you need a new WPS method which isn't overridden yet, modify the IDL file in IDL\ to suit your needs. If the method you need is already overridden by XWorkplace, go to 3. 2. If you then re-make XWorkplace, the SOM compiler will automatically get invoked and modify the sources in SRC\CLASSES and the headers in INCLUDE\CLASSES accordingly. 3. Again, as said on the previous page, add your own directory to INCLUDE and SRC. Modify the SOM code of the class you need in SRC\CLASSES to call your implementation in your SRC\YOURDIR directory. 4. You will need to add your header from INCLUDE\YOURDIR to the SOM class code file that you modified. Please think of some useful function prefix for your exported functions so that other programmers (including me) can find your code more easily. Don't forget to update src\classes\makefile so that the class code file will be made dependent on your new header (which you have added to the SRC\CLASSES code). 5. In SRC\YOURDIR\, write your own makefile which compiles your sources. You can take the makefile in SRC\FILESYS\ as a template. This makefile is pretty smart because it automatically recognizes whether it is called from the main makefile, and if not, it invokes the main makefile, which in turn will call the sub-makefiles later. Also, that makefile uses the general makefile include setup.in in the main directory for compiler setup etc. Make sure that your makefile writes all .OBJ files into the BIN directory, which your makefile must create if it doesn't exist yet. Again, see how the makefile in SRC\FILESYS does this. Other than that, in SRC\YOURDIR, do whatever you want. 6. Finally, take a look at \makefile. This is the "master makefile" which links all .OBJ modules into the main XWorkplace DLL (XFLDR.DLL). In that makefile, there is an OBJS macro which lists all the .OBJ files which are to be linked together. Add your .OBJ file(s) to the end of that variable (and please, add a comment that you did so). ═══ 7.4. The "XWorkplace Setup" Object ═══ If you have added new code to XWorkplace, you might wonder how to integrate your code into the "XWorkplace Setup" object, most notably, how to add stuff to the "Features" container. This is pretty easy. The "Features" page uses a subclassed container (the code for those checkboxes is in helpers\comctl.c, but this you need not worry about). All you have to do is take a look at src\shared\xsetup.c. The setFeaturesInitPage function is responsible for initializing the container with the features, while the setFeaturesItemChanged function reacts to selection changes in the container. To add something, perform the following steps: 1. Add a dialog item ID to include\dlgids.h, where all the other items of this kind are (search for ID_XCSI_GENERALFEATURES to find them). 2. Add a string for your setting to the NLS strings (e.g. for English, to 001\xfldr001.rc), using that ID. 3. Add your ID to the FeatureItemsList array in src\shared\xsetup.c. Your item will then automatically get inserted into the container by setFeaturesInitPage. 4. Still, in setFeaturesInitPage, you need to add a line which checks the container checkbox according to the setting (those ctlSetRecordChecked(hwndFeaturesCnr ... lines). 5. In setFeaturesItemChanged, add a case/switch which reacts to user changes. ═══ 7.5. XWorkplace Code You Might Find Useful ═══ This section should help you identify XWorkplace code sections which might also be useful to you. 1. This applies mostly to the code in the HELPERS directory. All of that code is not dependent on XWorkplace and could be used in any program. There are lots of Control Program, Presentation Manager and GPI helper functions which might solve problems that you are having. To use the helper funcs, simply add #include "helpers\xxx.h" at the top of your code. See the top of the respective header for additional #include's which are required by the helper. 2. There are some functions in src\shared\common.c which might be useful to you, most notably to query some XWorkplace settings and NLS stuff. cmnQuerySetting will return a global setting, for example, or cmnMessageBox will display one of the pretty XWorkplace message boxes. 3. To find out how to create a WPAbstract class from scratch, including a completely new view defined by your class (like the "Class list view"), you can take a look at xclslist.idl and xclslist.c, which do exactly this. See the comments in that file for instructions. 4. To create a new settings object (which is not derived from WPSystem), take a look at XWPSetup, a direct subclass of WPAbstract, which is implemented this way. 5. For easier maintenance of notebook dialog procs, I have created src\shared\notebook.c, which does just this using callbacks, so you don't have to rewrite the same stupid window procedures for each notebook page. This is used throughout XWorkplace's source code whenever settings pages are inserted, and has proven to be extremely useful. 6. Finally, src\shared\kernel.c contains code which you can extend for certain tricky situations. For one, you can extend krnInitializeXWorkplace to have code executed upon Desktop startup; secondly, you can add messages to krn_fnwpThread1Object if you need code to absolutely always execute on thread 1 of PMSHELL.EXE, which cannot be guaranteed with open views of any kind (especially folder views). ═══ 7.6. Writing an XCenter Widget Plugin DLL ═══ You can write independent plug-in DLLs for the XCenter to implement XCenter widget classes. This saves you from dealing with all the WPS programming details and even from having to compile the rest of XWorkplace. Detailed documentation for writing plug-in DLLs has been added to the XWorkplace User Guide with V0.9.9. Please see that documentation for details. Still, since you have obviously downloaded the XWorkplace source code, if you are interested, I strongly suggest that you create the XWorkplace code documentation. Then, in the HTML files which are generated, look for the documentation for src\shared\center.c documentation which gives you plenty of introductory information. Several standard XCenter widgets have been created as plugin DLLs to show you how this can be done. Their sources are in the src\widgets directory. In that directory, you will also find ____sample.c, which you can use as a template for your own widgets. ═══ 8. Miscellaneous WPS Programming Hints ═══  Some SOM tricks ═══ 8.1. Some SOM Tricks ═══ In general, XWorkplace is pretty straightforward SOM/WPS code. It overrides a lot of wp* and wpcls* methods. In order to avoid too much confusion, the methods which XWorkplace adds do not carry the usual wp* and wpcls* prefixes, but xwp* and xwpcls* instead. Only "real" XWorkplace SOM methods have this prefix; "normal" functions have other prefixes (see the previous page for a list). As XWorkplace became more complex over time, I have delved quite deeply into SOM. While I still don't fully understand what is going on in the SOM kernel (from what I hear, I guess nobody does) and some bugs in there keep puzzling me, I've found out some interesting stuff. Note: The following is not required to write WPS classes. This is additional information for those who have written WPS classes already and might be interested in some SOM internals. Most of this is related to the "WPS Classes" object (xclslist.c). The SOM logic for this is in classlst.c in clsWPSClasses2Cnr, which can analyze the current SOM runtime environment (which is that of the WPS), i.e. all the classes that have been loaded and query their inheritance hierarchy at runtime. This inserts the WPS class tree as recordcores into a given cnr control. This works with any containers in tree views, so that some XWorkplace dialogs can use this too. Check the sources, there's some interesting stuff. Note that there are quite a number of functions in XWorkplace which take SOM (WPS) objects as a parameter, even though they are not SOM methods. See src\shared\wpsh.c for examples. The reason for this is that resolving SOM methods takes quite a bit of time, and calling regular functions will work just as well, but faster. If you look into the .IH header files created by the SOM compiler, you see that the C bindings for method calls are really all macros #define'd in the header files which translate into more complex C code. Here's an example: if you call _wpQueryStyle(somSelf) in xfldr.c, where this method has not been overridden, the WPObject version of this method should get called. Here's the #define in xfldr.ih for this: #define XFolder_wpQueryStyle WPObject_wpQueryStyle And WPObject_wpQueryStyle is #define'd in wpobject.h from the toolkit headers as follows: #define WPObject_wpQueryStyle(somSelf) \ (SOM_Resolve(somSelf, WPObject, wpQueryStyle) \ (somSelf)) Actually, there are more macros related to this, but this is the important one. SOM_Resolve in turn is a macro #define'd in somcdev.h, which calls the SOM kernel function somResolve. That function finally goes through the class method tables to find the actual function address and call it. As as result, not only does compiling of SOM code take ages (because of all the nested macros), but also calling SOM methods does, because as opposed to "static" OO languages such as C++, method resolution is occuring at run-time. IBM says in the SOM docs that calling a SOM method takes at least three times as long as calling a normal C function. Since there is no real need to write functions as SOM methods, except when you want to design a method which can be overriden in subclasses, I have only done so in rare occasions to speed up processing. Here are some other SOM tricks and functions which are not mentioned directly in the WPS reference (but only in the chaotic SOM docs), but these are very useful. Some of this is pretty basic, some might be new to WPS programmers, so I'll list this here:  Since all the SOM stuff is declared in those huge header files, you need to #include a header file if you need access to certain class-specific features. For example, if you write a subclass of WPDataFile and need some program-object method call (e.g. _wpQueryProgDetails), you need to put #include <wppgm.h> on top of your code, or the method binding will not be found. This is not neccessary if the method is defined for a superclass of your class, because the SOM headers automatically #include all the parent classes. That is, for example, you don't need to #include wpfsys.h (for WPFileSystem) for your WPDataFile subclass.  The most important thing to keep in mind is that all SOM classes are objects too. They are instances of their respective metaclasses. This takes some getting used to, but it is this concept only which allows WPS classes to be created at runtime and for class replacements in the first place. That is, for example, any Desktop object is an instance of WPObject (really one of its subclasses). The WPObject class in turn is an instance of its metaclass, M_WPObject. In SOM, the default metaclass for a class is SOMClass. However, the WPS overrides this behavior to create a unique metaclass for each WPS class, which is prefixed with M_. Since the metaclass is a class as well, it has methods too (the so-called "class methods", which operate on class objects, as opposed to the "instance methods" of the class itself, which operate on instances of the class -- the Desktop objects).  To access a class object, for any existing class you always have a corresponding macro which is an underscore plus the class name. That is, the class object of WPObject can always be obtained with _WPObject. This is useful for calling class methods, which always need to be invoked on a class object. So, to call a folder class method, you do something like _wpclsQueryOpenFolders(_WPFolder). Note: If a class has been replaced, the macro will not return the original, but the replacement class. That is, if XWorkplace is installed, the above example would actually return the XFldObject class object (see notes below), and methods invoked on the WPObject class object would actually be resolved for XFldObject. That's how class replacements work in the first place. See the notes below for more.  BOOL _somIsA(pObject, pClassObject) checks for whether pObject is an instance of the class specified by pClassObject or one of its subclasses. This is extensively used in statbars.c to adjust status bar display depending on the class of a single object which is currently selected. Example: _somIsA(somSelf, _WPObject) should always be true within the WPS context, since all WPS classes are subclasses of WPObject. By contrast, _somIsA(somSelf, _WPFolder) would only return TRUE if somSelf is a WPS folder. (This would work with _XFolder too, but this would require that you have access to the XFolder header files and that XFolder replaces WPFolder. _WPFolder always works, even if WPFolder is replaced, because _WPFolder would be resolved to point to the replacement class object then -- e.g. _XFolder.)  PSZ _somGetName(pClassObject) returns the class name specified by pClassObject, as specified in the IDL file. (That is different from wpclsQueryTitle.) Example: _somGetName(_XFldObject) will return "XFldObject".  SOMClass* _somGetParent(pClassObject) returns a parent class. Examples: _somGetParent(_WPProgram) returns the WPAbstract class object (_WPAbstract). _somGetParent(_XFolder) should return _WPFolder, unless there's some other WPFolder replacement class above XWorkplace in the replacement list.  BOOL _somDescendedFrom(pClass, pClassParent) returns TRUE if pClass is descended from pClassParent. Examples: _somDescendedFrom(_WPFolder, _WPObject) should return TRUE. _somDescendedFrom(_WPFolder, _WPProgram) would return FALSE.  somResolveByName(pClassObject, "method") gives you a function pointer as implemented by the specified class. You should assign this to a function pointer variable which matches the function prototype, or otherwise you'll get crashes. This can be useful if you invoke a SOM method frequently, for example in a loop on many objects, because then SOM method resolution has to be done only once. If you used the macros, resolution would take place for each iteration in the loop.  Class replacements. Nowhere is really documented how class replacements actually work, except that replacement classes must be direct descendants of the class which is to be replaced. I assume that class replacements are a feature of the SOM class manager, of which the WPS class manager is a descendant (WPClassManager is half documented in the WPS Reference.) At least there is a documented SOMClassMgr method, somSubstituteClass, which appears to be doing exactly this job. From what I've seen, class replacements seem to work this way: Any time a class object is queried, the class manager does not return the class object of the specified class, but the object of the replacement class instead. As a result, all the method calls are not resolved for the original class, but for the replacement class instead. Examples: If XFolder replaces WPFolder, wpModifyPopupMenu is not called for WPFolder, but for XFolder instead, even though the WPS had originally called it for a folder object. By contrast, for wpQueryIcon, which is not overridden by XFolder, method resolution leads to the method as implemented by the parent class, which should be WPFolder, unless some other class replacements is present. The class replacement mechanism is most obvious with the class object macros described above: if you have a _WPFolder in your code, this returns the class object of XFolder, i.e. _WPFolder == _XWorkplace. So if you absolutely need the WPFolder class object (which is normally not necessary, since this would circumvent XFolder's functionality), you use _WPFolder (which returns the XFolder class object) and then climb up the class object's inheritance tree using _somGetParent until _somGetName returns "WPFolder".  To get the class object of any class without having access to its header files, do the following: somId somidThis = somIdFromString("WPObject"); SOMClass *pClassObject = _somFindClass(SOMClassMgrObject, somidThis, 0, 0); Note again that _somFindClass will return replacement classes instead of the originals. (That's how the class object macros work, BTW.)  __get_somRegisteredClasses(SOMClassMgrObject) returns a SOMClassSequence of all currently registered class objects. This is used by the "WPS classes" page in the "Workplace Shell" object. See classlst.c for details. ═══ 9. Resources on the Internet ═══ This chapter contains all external links referenced in this book. Each link contained herein is an Unified Resource Locator (URL) to a certain location on the Internet. Simply double-click on one of them to launch Netscape with the respective URL. ═══ 9.1. http://service.boulder.ibm.com/dl/ddk/priv/ddk-d ═══ Click below to launch Netscape with this URL: http://service.boulder.ibm.com/dl/ddk/priv/ddk-d ═══ 9.2. http://www.edm2.com ═══ Click below to launch Netscape with this URL: http://www.edm2.com ═══ 9.3. http://www.labyrinth.net.au/~dbareis/zips_fw/pmf96179.zip ═══ Click below to launch Netscape with this URL: http://www.labyrinth.net.au/~dbareis/zips_fw/pmf96179.zip