OS/2 improvements
[p5sagit/p5-mst-13.2.git] / os2 / OS2 / Process / Process.pm
index b862885..6ce93c0 100644 (file)
@@ -1,12 +1,20 @@
+package OS2::localMorphPM;
+
+sub new { my ($c,$f) = @_; OS2::MorphPM($f); bless [shift], $c }
+sub DESTROY { OS2::UnMorphPM(shift->[0]) }
+
 package OS2::Process;
 
-$VERSION = 0.2;
+BEGIN {
+  require Exporter;
+  require DynaLoader;
+  #require AutoLoader;
 
-require Exporter;
-require DynaLoader;
-#require AutoLoader;
+  @ISA = qw(Exporter DynaLoader);
+  $VERSION = "1.0";
+  bootstrap OS2::Process;
+}
 
-@ISA = qw(Exporter DynaLoader);
 # Items to export into callers namespace by default. Note: do not export
 # names by default without a very good reason. Use EXPORT_OK instead.
 # Do not simply export all your public functions/methods/constants.
@@ -43,10 +51,51 @@ require DynaLoader;
        T_VIRTDRV
        T_PROTDLL
        T_32BIT
+       ppid
+       ppidOf
+       sidOf
+       scrsize
+       scrsize_set
        process_entry
-       set_title
-       get_title
+       process_entries
+       process_hentry
+       process_hentries
+       change_entry
+       change_entryh
+       Title_set
+       Title
+       WindowText
+       WindowText_set
+       WindowPos
+       WindowPos_set
+       WindowProcess
+       SwitchToProgram
+       ActiveWindow
+       ClassName
+       FocusWindow
+       FocusWindow_set
+       ShowWindow
+       PostMsg
+       BeginEnumWindows
+       EndEnumWindows
+       GetNextWindow
+       IsWindow
+       ChildWindows
+       out_codepage
+       out_codepage_set
+       in_codepage
+       in_codepage_set
+       cursor
+       cursor_set
+       screen
+       screen_set
+       process_codepages
+       QueryWindow
+       WindowFromId
+       WindowFromPoint
+       EnumDlgItem
 );
+
 sub AUTOLOAD {
     # This AUTOLOAD is used to 'autoload' constants from the constant()
     # XS function.  If a constant is not found then control is passed
@@ -70,11 +119,111 @@ sub AUTOLOAD {
     goto &$AUTOLOAD;
 }
 
-bootstrap OS2::Process;
-
 # Preloaded methods go here.
 
-sub get_title () { (process_entry())[0] }
+sub Title () { (process_entry())[0] }
+
+# *Title_set = \&sesmgr_title_set;
+
+sub swTitle_set_sw {
+  my ($title, @sw) = @_;
+  $sw[0] = $title;
+  change_entry(@sw);
+}
+
+sub swTitle_set {
+  my (@sw) = process_entry();
+  swTitle_set_sw(shift, @sw);
+}
+
+sub winTitle_set_sw {
+  my ($title, @sw) = @_;
+  my $h = OS2::localMorphPM->new(0);
+  WindowText_set $sw[1], $title;
+}
+
+sub winTitle_set {
+  my (@sw) = process_entry();
+  winTitle_set_sw(shift, @sw);
+}
+
+sub bothTitle_set {
+  my (@sw) = process_entry();
+  my $t = shift;
+  winTitle_set_sw($t, @sw);
+  swTitle_set_sw($t, @sw);
+}
+
+sub Title_set {
+  my $t = shift;
+  return 1 if sesmgr_title_set($t);
+  return 0 unless $^E == 372;
+  my (@sw) = process_entry();
+  winTitle_set_sw($t, @sw);
+  swTitle_set_sw($t, @sw);
+}
+
+sub process_entry { swentry_expand(process_swentry(@_)) }
+
+our @hentry_fields = qw( title owner_hwnd icon_hwnd 
+                        owner_phandle owner_pid owner_sid
+                        visible nonswitchable jumpable ptype sw_entry );
+
+sub swentry_hexpand ($) {
+  my %h;
+  @h{@hentry_fields} = swentry_expand(shift);
+  \%h;
+}
+
+sub process_hentry { swentry_hexpand(process_swentry(@_)) }
+
+my $swentry_size = swentry_size();
+
+sub sw_entries () {
+  my $s = swentries_list();
+  my ($c, $s1) = unpack 'La*', $s;
+  die "Unconsistent size in swentries_list()" unless 4+$c*$swentry_size == length $s;
+  my (@l, $e);
+  push @l, $e while $e = substr $s1, 0, $swentry_size, '';
+  @l;
+}
+
+sub process_entries () {
+  map [swentry_expand($_)], sw_entries;
+}
+
+sub process_hentries () {
+  map swentry_hexpand($_), sw_entries;
+}
+
+sub change_entry {
+  change_swentry(create_swentry(@_));
+}
+
+sub create_swentryh ($) {
+  my $h = shift;
+  create_swentry(@$h{@hentry_fields});
+}
+
+sub change_entryh ($) {
+  change_swentry(create_swentryh(shift));
+}
+
+# Massage entries into the same order as WindowPos_set:
+sub WindowPos ($) {
+  my ($fl, $w, $h, $x, $y, $behind, $hwnd, @rest)
+       = unpack 'L l4 L4', WindowSWP(shift);
+  ($x, $y, $fl, $w, $h, $behind, @rest);
+}
+
+sub ChildWindows ($) {
+  my @kids;
+  my $h = BeginEnumWindows shift;
+  my $w;
+  push @kids, $w while $w = GetNextWindow $h;
+  EndEnumWindows $h;
+  @kids;
+}
 
 # Autoload methods go after __END__, and are processed by the autosplit program.
 
@@ -83,15 +232,17 @@ __END__
 
 =head1 NAME
 
-OS2::Process - exports constants for system() call on OS2.
+OS2::Process - exports constants for system() call, and process control on OS2.
 
 =head1 SYNOPSIS
 
     use OS2::Process;
-    $pid = system(P_PM+P_BACKGROUND, "epm.exe");
+    $pid = system(P_PM | P_BACKGROUND, "epm.exe");
 
 =head1 DESCRIPTION
 
+=head2 Optional argument to system()
+
 the builtin function system() under OS/2 allows an optional first
 argument which denotes the mode of the process. Note that this argument is
 recognized only if it is strictly numerical.
@@ -123,14 +274,21 @@ and optionally add PM and session option bits:
 
 =head2 Access to process properties
 
-Additionaly, subroutines my_type(), process_entry() and
-C<file_type(file)>, get_title() and C<set_title(newtitle)> are implemented.  
-my_type() returns the type of the current process (one of 
-"FS", "DOS", "VIO", "PM", "DETACH" and "UNKNOWN"), or C<undef> on error.
+On OS/2 processes have the usual I<parent/child> semantic;
+additionally, there is a hierarchy of sessions with their own
+I<parent/child> tree.  A session is either a FS session, or a windowed
+pseudo-session created by PM.  A session is a "unit of user
+interaction", a change to in/out settings in one of them does not
+affect other sessions.
 
 =over
 
-=item C<file_type(file)> 
+=item my_type()
+
+returns the type of the current process (one of
+"FS", "DOS", "VIO", "PM", "DETACH" and "UNKNOWN"), or C<undef> on error.
+
+=item C<file_type(file)>
 
 returns the type of the executable file C<file>, or
 dies on error.  The bits 0-2 of the result contain one of the values
@@ -139,15 +297,15 @@ dies on error.  The bits 0-2 of the result contain one of the values
 
 =item C<T_NOTSPEC> (0)
 
-Application type is not specified in the executable header. 
+Application type is not specified in the executable header.
 
 =item C<T_NOTWINDOWCOMPAT> (1)
 
-Application type is not-window-compatible. 
+Application type is not-window-compatible.
 
 =item C<T_WINDOWCOMPAT> (2)
 
-Application type is window-compatible. 
+Application type is window-compatible.
 
 =item C<T_WINDOWAPI> (3)
 
@@ -177,11 +335,11 @@ and 4 will be set to 0.
 
 =item C<T_PHYSDRV> (0x40)
 
-Set to 1 if the executable file is a physical device driver. 
+Set to 1 if the executable file is a physical device driver.
 
 =item C<T_VIRTDRV> (0x80)
 
-Set to 1 if the executable file is a virtual device driver. 
+Set to 1 if the executable file is a virtual device driver.
 
 =item C<T_PROTDLL> (0x100)
 
@@ -190,7 +348,7 @@ library module.
 
 =item C<T_32BIT> (0x4000)
 
-Set to 1 for 32-bit executable files. 
+Set to 1 for 32-bit executable files.
 
 =back
 
@@ -200,37 +358,127 @@ conditions.  If given non-absolute path, will look on C<PATH>, will
 add extention F<.exe> if no extension is present (add extension F<.>
 to suppress).
 
+=item C<@list = process_codepages()>
+
+the first element is the currently active codepage, up to 2 additional
+entries specify the system's "prepared codepages": the codepages the
+user can switch to.  The active codepage of a process is one of the
+prepared codepages of the system (if present).
+
+=item C<process_codepage_set($cp)>
+
+sets the currently active codepage.  [Affects printer output, in/out
+codepages of sessions started by this process, and the default
+codepage for drawing in PM; is inherited by kids.  Does not affect the
+out- and in-codepages of the session.]
+
+=item ppid()
+
+returns the PID of the parent process.
+
+=item C<ppidOf($pid = $$)>
+
+returns the PID of the parent process of $pid.  -1 on error.
+
+=item C<sidOf($pid = $$)>
+
+returns the session id of the process id $pid.  -1 on error.
+
+=back
+
+=head2 Control of VIO sessions
+
+VIO applications are applications running in a text-mode session.
+
+=over
+
+=item out_codepage()
+
+gets code page used for screen output (glyphs).  -1 means that a user font
+was loaded.
+
+=item C<out_codepage_set($cp)>
+
+sets code page used for screen output (glyphs).  -1 switches to a preloaded
+user font.  -2 switches off the preloaded user font.
+
+=item in_codepage()
+
+gets code page used for keyboard input.  0 means that a hardware codepage
+is used.
+
+=item C<in_codepage_set($cp)>
+
+sets code page used for keyboard input.
+
+=item C<($w, $h) = scrsize()>
+
+width and height of the given console window in character cells.
+
+=item C<scrsize_set([$w, ] $h)>
+
+set height (and optionally width) of the given console window in
+character cells.  Use 0 size to keep the old size.
+
+=item C<($s, $e, $w, $a) = cursor()>
+
+gets start/end lines of the blinking cursor in the charcell, its width
+(1 on text modes) and attribute (-1 for hidden, in text modes other
+values mean visible, in graphic modes color).
+
+=item C<cursor_set($s, $e, [$w [, $a]])>
+
+sets start/end lines of the blinking cursor in the charcell.  Negative
+values mean percents of the character cell height.
+
+=item screen()
+
+gets a buffer with characters and attributes of the screen.
+
+=item C<screen_set($buffer)>
+
+restores the screen given the result of screen().
+
+=back
+
+=head2 Control of the process list
+
+With the exception of Title_set(), all these calls require that PM is
+running, they would not work under alternative Session Managers.
+
+=over
+
 =item process_entry()
 
 returns a list of the following data:
 
 =over
 
-=item 
+=item
 
 Title of the process (in the C<Ctrl-Esc> list);
 
-=item 
+=item
 
 window handle of switch entry of the process (in the C<Ctrl-Esc> list);
 
-=item 
+=item
 
 window handle of the icon of the process;
 
-=item 
+=item
 
 process handle of the owner of the entry in C<Ctrl-Esc> list;
 
-=item 
+=item
 
 process id of the owner of the entry in C<Ctrl-Esc> list;
 
-=item 
+=item
 
 session id of the owner of the entry in C<Ctrl-Esc> list;
 
-=item 
+=item
 
 whether visible in C<Ctrl-Esc> list;
 
@@ -239,20 +487,20 @@ whether visible in C<Ctrl-Esc> list;
 whether item cannot be switched to (note that it is not actually
 grayed in the C<Ctrl-Esc> list));
 
-=item 
+=item
 
 whether participates in jump sequence;
 
-=item 
+=item
 
-program type.  Possible values are: 
+program type.  Possible values are:
 
-     PROG_DEFAULT                       0 
-     PROG_FULLSCREEN                    1 
-     PROG_WINDOWABLEVIO                 2 
-     PROG_PM                            3 
-     PROG_VDM                           4 
-     PROG_WINDOWEDVDM                   7 
+     PROG_DEFAULT                       0
+     PROG_FULLSCREEN                    1
+     PROG_WINDOWABLEVIO                 2
+     PROG_PM                            3
+     PROG_VDM                           4
+     PROG_WINDOWEDVDM                   7
 
 Although there are several other program types for WIN-OS/2 programs,
 these do not show up in this field. Instead, the PROG_VDM or
@@ -263,31 +511,351 @@ is a windowed WIN-OS/2 program, it runs in a PROG_WINDOWEDVDM
 session. Likewise, if it's a full-screen WIN-OS/2 program, it runs in
 a PROG_VDM session.
 
+=item
+
+switch-entry handle.
 
 =back
 
-=item C<set_title(newtitle)> 
+Optional arguments: the pid and the window-handle of the application running
+in the OS/2 session to query.
+
+=item process_hentry()
+
+similar to process_entry(), but returns a hash reference, the keys being
+
+  title owner_hwnd icon_hwnd owner_phandle owner_pid owner_sid
+  visible nonswitchable jumpable ptype sw_entry
+
+(a copy of the list of keys is in @hentry_fields).
+
+=item process_entries()
 
-- does not work with some windows (if the title is set from the start).  
+similar to process_entry(), but returns a list of array reference for all
+the elements in the switch list (one controlling C<Ctrl-Esc> window).
+
+=item process_hentries()
+
+similar to process_hentry(), but returns a list of hash reference for all
+the elements in the switch list (one controlling C<Ctrl-Esc> window).
+
+=item change_entry()
+
+changes a process entry, arguments are the same as process_entry() returns.
+
+=item change_entryh()
+
+Similar to change_entry(), but takes a hash reference as an argument.
+
+=item Title()
+
+returns a title of the current session.  (There is no way to get this
+info in non-standard Session Managers, this implementation is a
+shortcut via process_entry().)
+
+=item C<Title_set(newtitle)>
+
+tries two different interfaces.  The Session Manager one does not work
+with some windows (if the title is set from the start).
 This is a limitation of OS/2, in such a case $^E is set to 372 (type
 
   help 372
 
-for a funny - and wrong  - explanation ;-).
+for a funny - and wrong  - explanation ;-).  In such cases a
+direct-manipulation of low-level entries is used.  Keep in mind that
+some versions of OS/2 leak memory with such a manipulation.
+
+=item C<SwitchToProgram($sw_entry)>
+
+switch to session given by a switch list handle.
+
+Use of this function causes another window (and its related windows)
+of a PM session to appear on the front of the screen, or a switch to
+another session in the case of a non-PM program. In either case,
+the keyboard (and mouse for the non-PM case) input is directed to
+the new program.
+
+=back
+
+=head2 Control of the PM windows
+
+Some of these API's require sending a message to the specified window.
+In such a case the process needs to be a PM process, or to be morphed
+to a PM process via OS2::MorphPM().
+
+For a temporary morphing to PM use L<OS2::localMorphPM class>.
+
+Keep in mind that PM windows are engaged in 2 "orthogonal" window
+trees, as well as in the z-order list.
+
+One tree is given by the I<parent/child> relationship.  This
+relationship affects drawing (child is drawn relative to its parent
+(lower-left corner), and the drawing is clipped by the parent's
+boundary; parent may request that I<it's> drawing is clipped to be
+confined to the outsize of the childs and/or siblings' windows);
+hiding; minimizing/restoring; and destroying windows.
+
+Another tree (not necessarily connected?) is given by I<ownership>
+relationship.  Ownership relationship assumes cooperation of the
+engaged windows via passing messages on "important events"; e.g.,
+scrollbars send information messages when the "bar" is moved, menus
+send messages when an item is selected; frames
+move/hide/unhide/minimize/restore/change-z-order-of owned frames when
+the owner is moved/etc., and destroy the owned frames (even when these
+frames are not descendants) when the owner is destroyed; etc.  [An
+important restriction on ownership is that owner should be created by
+the same thread as the owned thread, so they engage in the same
+message queue.]
+
+Windows may be in many different state: Focused, Activated (=Windows
+in the I<parent/child> tree between the root and the window with
+focus; usually indicate such "active state" by titlebar highlights),
+Enabled/Disabled (this influences *an ability* to receive user input
+(be focused?), and may change appearance, as for enabled/disabled
+buttons), Visible/Hidden, Minimized/Maximized/Restored, Modal, etc.
+
+=over
+
+=item C<WindowText($hwnd)>
+
+gets "a text content" of a window.
+
+=item C<WindowText_set($hwnd, $text)>
+
+sets "a text content" of a window.
+
+=item C<WindowPos($hwnd)>
+
+gets window position info as 8 integers (of C<SWP>), in the order suitable
+for WindowPos_set(): $x, $y, $fl, $w, $h, $behind, @rest.
+
+=item C<WindowPos_set($hwnd, $x, $y, $flags = SWP_MOVE, $wid = 0, $h = 0, $behind = HWND_TOP)>
+
+Set state of the window: position, size, zorder, show/hide, activation,
+minimize/maximize/restore etc.  Which of these operations to perform
+is governed by $flags.
+
+=item C<WindowProcess($hwnd)>
+
+gets I<PID> and I<TID> of the process associated to the window.
+
+=item ActiveWindow([$parentHwnd])
+
+gets the active subwindow's handle for $parentHwnd or desktop.
+Returns FALSE if none.
+
+=item C<ClassName($hwnd)>
+
+returns the class name of the window.
+
+If this window is of any of the preregistered WC_* classes the class
+name returned is in the form "#nnnnn", where "nnnnn" is a group
+of up to five digits that corresponds to the value of the WC_* class name
+constant.
+
+=item FocusWindow()
+
+returns the handle of the focus window.  Optional argument for specifying the desktop
+to use.
+
+=item C<FocusWindow_set($hwnd)>
+
+set the focus window by handle.  Optional argument for specifying the desktop
+to use.  E.g, the first entry in program_entries() is the C<Ctrl-Esc> list.
+To show it
+
+       WinShowWindow( wlhwnd, TRUE );
+       WinSetFocus( HWND_DESKTOP, wlhwnd );
+       WinSwitchToProgram(wlhswitch);
+
+
+=item C<ShowWindow($hwnd [, $show])>
+
+Set visible/hidden flag of the window.  Default: $show is TRUE.
+
+=item C<PostMsg($hwnd, $msg, $mp1, $mp2)>
+
+post message to a window.  The meaning of $mp1, $mp2 is specific for each
+message id $msg, they default to 0.  E.g., in C it is done similar to
+
+    /* Emulate `Restore' */
+    WinPostMsg(SwitchBlock.tswe[i].swctl.hwnd, WM_SYSCOMMAND,
+               MPFROMSHORT(SC_RESTORE),        0);
+
+    /* Emulate `Show-Contextmenu' (Double-Click-2) */
+    hwndParent = WinQueryFocus(HWND_DESKTOP);
+    hwndActive = WinQueryActiveWindow(hwndParent);
+    WinPostMsg(hwndActive, WM_CONTEXTMENU, MPFROM2SHORT(0,0), MPFROMLONG(0));
+
+    /* Emulate `Close' */
+    WinPostMsg(pSWB->aswentry[i].swctl.hwnd, WM_CLOSE, 0, 0);
+
+    /* Same but softer: */
+    WinPostMsg(hwndactive, WM_SAVEAPPLICATION, 0L, 0L);
+    WinPostMsg(hwndactive, WM_CLOSE, 0L, 0L));
+    WinPostMsg(hwndactive, WM_QUIT, 0L, 0L));
+
+=item C<$eh = BeginEnumWindows($hwnd)>
+
+starts enumerating immediate child windows of $hwnd in z-order.  The
+enumeration reflects the state at the moment of BeginEnumWindows() calls;
+use IsWindow() to be sure.
+
+=item C<$kid_hwnd = GetNextWindow($eh)>
+
+gets the next kid in the list.  Gets 0 on error or when the list ends.
 
-=item get_title() 
+=item C<EndEnumWindows($eh)>
 
-is a shortcut implemented via process_entry().
+End enumeration and release the list.
+
+=item C<@list = ChildWindows($hwnd)>
+
+returns the list of child windows at the moment of the call.  Same remark
+as for enumeration interface applies.  Example of usage:
+
+  sub l {
+    my ($o,$h) = @_;
+    printf ' ' x $o . "%#x\n", $h;
+    l($o+2,$_) for ChildWindows $h;
+  }
+  l 0, $HWND_DESKTOP
+
+=item C<IsWindow($hwnd)>
+
+true if the window handle is still valid.
+
+=item C<QueryWindow($hwnd, $type)>
+
+gets the handle of a related window.  $type should be one of C<QW_*> constants.
+
+=item C<IsChild($hwnd, $parent)>
+
+return TRUE if $hwnd is a descendant of $parent.
+
+=item C<WindowFromId($hwnd, $id)>
+
+return a window handle of a child of $hwnd with the given $id.
+
+  hwndSysMenu = WinWindowFromID(hwndDlg, FID_SYSMENU);
+  WinSendMsg(hwndSysMenu, MM_SETITEMATTR,
+      MPFROM2SHORT(SC_CLOSE, TRUE),
+      MPFROM2SHORT(MIA_DISABLED, MIA_DISABLED));
+
+=item C<WindowFromPoint($x, $y [, $hwndParent [, $descedantsToo]])>
+
+gets a handle of a child of $hwndParent at C<($x,$y)>.  If $descedantsToo
+(defaulting to 0) then children of children may be returned too.  May return
+$hwndParent (defaults to desktop) if no suitable children are found,
+or 0 if the point is outside the parent.
+
+$x and $y are relative to $hwndParent.
+
+=item C<EnumDlgItem($dlgHwnd, $type [, $relativeHwnd])>
+
+gets a dialog item window handle for an item of type $type of $dlgHwnd
+relative to $relativeHwnd, which is descendant of $dlgHwnd.
+$relativeHwnd may be specified if $type is EDI_FIRSTTABITEM or
+EDI_LASTTABITEM.
+
+The return is always an immediate child of hwndDlg, even if hwnd is
+not an immediate child window.  $type may be
+
+=over
+
+=item EDI_FIRSTGROUPITEM
+
+First item in the same group.
+
+=item EDI_FIRSTTABITEM
+
+First item in dialog with style WS_TABSTOP. hwnd is ignored.
+
+=item EDI_LASTGROUPITEM
+
+Last item in the same group.
+
+=item EDI_LASTTABITEM
+
+Last item in dialog with style WS_TABSTOP. hwnd is ignored.
+
+=item EDI_NEXTGROUPITEM
+
+Next item in the same group. Wraps around to beginning of group when
+the end of the group is reached.
+
+=item EDI_NEXTTABITEM
+
+Next item with style WS_TABSTOP. Wraps around to beginning of dialog
+item list when end is reached.
+
+=item EDI_PREVGROUPITEM
+
+Previous item in the same group. Wraps around to end of group when the
+start of the group is reached. For information on the WS_GROUP style,
+see Window Styles.
+
+=item EDI_PREVTABITEM
+
+Previous item with style WS_TABSTOP. Wraps around to end of dialog
+item list when beginning is reached.
 
 =back
 
+=back
+
+=head1 OS2::localMorphPM class
+
+This class morphs the process to PM for the duration of the given context.
+
+  {
+    my $h = OS2::localMorphPM->new(0);
+    # Do something
+  }
+
+The argument has the same meaning as one to OS2::MorphPM().  Calls can
+nest with internal ones being NOPs.
+
+=head1 TODO
+
+Constants (currently one needs to get them looking in a header file):
+
+  HWND_*
+  WM_*                 /* Separate module? */
+  SC_*
+  SWP_*
+  WC_*
+  PROG_*
+  QW_*
+  EDI_*
+  WS_*
+
+Show/Hide, Enable/Disable (WinShowWindow(), WinIsWindowVisible(),
+WinEnableWindow(), WinIsWindowEnabled()).
+
+Maximize/minimize/restore via WindowPos_set(), check via checking
+WS_MAXIMIZED/WS_MINIMIZED flags (how to get them?).
+
+=head1 $^E
+
+the majority of the APIs of this module set $^E on failure (no matter
+whether they die() on failure or not).  By the semantic of PM API
+which returns something other than a boolean, it is impossible to
+distinguish failure from a "normal" 0-return.  In such cases C<$^E ==
+0> indicates an absence of error.
+
+=head1 BUGS
+
+whether a given API dies or returns FALSE/empty-list on error may be
+confusing.  This may change in the future.
+
 =head1 AUTHOR
 
-Andreas Kaiser <ak@ananke.s.bawue.de>, 
+Andreas Kaiser <ak@ananke.s.bawue.de>,
 Ilya Zakharevich <ilya@math.ohio-state.edu>.
 
 =head1 SEE ALSO
 
-C<spawn*>() system calls.
+C<spawn*>() system calls, L<OS2::Proc> and L<OS2::WinObject> modules.
 
 =cut