Programming the OS/2 Switch List

From EDM2
Jump to: navigation, search

By Roger Orr

Introduction

In an earlier article (issue #22, Sep/Oct 92) I wrote about using the undocumented - but very useful - DosQProcStatus API. This is the method the pstat program uses to obtain its information, and is one of the few ways you can obtain a complete list of running processes on the system.

However, in many cases a complete list of running processes is not required and there is another, documented (and hence supported), API which can be used to perform similar functions. It appears that few OS/2 programmers make use of this API, so I thought I would give an example of its use in this article.

The API is WinQuerySwitchList - which returns information about the programs and main windows known to the PM shell. The function returns several pieces of information for each item; the primary ones being the process ID, the handle to the main frame window (if any) and the program name or title.

This API, despite the Win prefix, is available to non-PM programs such as those running in an OS/2 full screen session, or even running detached. I have therefore also written a paragraph about the use of PM functions from non-PM programs.

The switch list

So what is the switch list referred to by this function? When you press Ctrl+Esc on the OS/2 desktop the "task list" or "window list" appears. This window contains a subset of the information in the switch list - WinQuerySwitchList will obtain more information than just what is displayed in the window.

A simple PM program which creates a main window usually adds itself to the window list by specifying FCF_TASKLIST when calling WinCreateStdWindow. (This flag is also implied when FCF_STANDARD is used). This flag registers the window with the window list, and updates it when the title bar changes.

For greater flexibility a similar effect can be achieved by use of the WinAddSwitchEntry API to initially register with the window list.

The WinAddSwitchEntry function takes a SWCNTRL structure as input; which contains items such as the process id, frame window handle and title. It also contains a 'visibility' setting which allows the item to be specified as invisible - it will be in the internal switch list but not displayed in the window list.

Once written to the switch list the SWCNTRL data can be retrieved by using WinQuerySwitchEntry using the switch handle returned by WinAddSwitchEntry, and then the WinChangeSwitchEntry API can be used to alter the characteristics of the program's entry in the switch list.

The WinQuerySwitchList API allows you to get the complete list of switch entries, complete with their switch handles, into a single data structure - called a SWBLOCK.

This list will also include some programs which have NOT explicitly added themselves to the switch list. The 'visibility' setting for such programs will include the invisible one; but the data returned from the switch list can be manipulated under program control nonetheless.

Note that the list of programs returned will not be complete - for example detached programs are not shown and only one program in any one screen group will be listed. Further one program may be listed more than once if it created multiple frame windows.

Exploiting the switch list

There are a number of things you can do with the switch list.

Firstly you can use it to list the active programs and to map process IDs to a meaningful name. This could provide a simple mechanism for checking whether a related process is already running before attempting to start it.

Secondly since the switch entry contains a window handle it provides a way to send messages to other programs - and you can use WinPostMsg from non-PM processes.

Thirdly the switch list handle can be used for other switch entry API calls, such as WinSwitchToProgram which enables other programs to be made active.

For the purposes of this article I have written a simple program, called TaskList, which can be run from an OS/2 full screen or windowed session to demonstrate one or two of the possible uses of the switch list.

I hope this program shows that despite the limitations of the switch list in that it doesn't list all classes of process it can be a useful API in some cases, and without the drawbacks of using undocumented features of OS/2.

Notes on calling PM functions from non-PM programs

Since the example program calls one or two 'PM' functions despite not being a PM program, I thought some explanation might be in order since this seems to be an area which can cause some confusion.

In general, any thread using a PM function must register itself with PM and then create a PM message queue. As PM is a message based system this makes sense - the thread needs storage to save messages and so requires a queue. Note that a message queue is associated with a THREAD and not a PROCESS - a thread is initially created without its own message queue and you must create one using WinInitialize() and WinCreateMsgQueue().

Standard PM programs revolve around this message queue - the main function usually has a WinGetMsg/WinDispatchMsg loop at its centre. Many threads created in PM programs to do auxiliary work also reflect this message based structure; possibly involving use of an 'object' window rather than a 'desktop' window.

However, some of the PM functions do NOT require a message queue. This particularly applies to the graphics functions and most of the GPI functions are callable from threads without a message queue. This means you can create a thread to do 'background' priority drawing in a presentation space without this thread necessarily needing a message queue of its own.

Other functions which do not require a message queue include WinPostMsg() and the switch list APIs. In general it is easy to find out whether a function needs a message queue - try it without one and see if it works(!)

Some OS/2 reference manuals are helpful and document which functions do and do not require a message queue; but treat these as guidance only since they are sometimes wrong. If in doubt remember to check the return code.

For a PM program it is a design choice to have a message queue or not for a given thread. There is nothing to stop any thread in such a program from creating a message queue; and the decision is usually based on other reasons such as some previously written, non-PM, code.

However if a program is not a PM program (OS/2 full screen or windowed) then the choice is absent; a call to WinCreateMsgQueue will fail. For these programs only the calls not requiring a message queue are available, and of these the commonest is WinPostMsg which allows a non-PM program to pass a message to a PM program. Note that PM cannot be used to reply to the message since the sender has no message queue!

This question becomes a little more problematic if you are writing library code - how can you do the right thing? Two functions which you can use are DosGetInfoBlocks (since the PIB contains the current program type) and WinQueryQueueInfo (for message queue HMQ_CURRENT) which returns TRUE if the current thread does have a message queue.

Overview of the program

The code starts by calling the LoadSwitch function to read the current switch list into memory. Note that customary two calls to the API; the first to get the size and the second to read the data.

Note that the size can change between these two calls. You may think this is very unlikely to happen, but during system startup many processes may be vying for the CPU. Make sure you allow for the switch list changing.

Then the 'process' function is called to work through the task list. Note that since titles can contain newline characters these are searched for and replaced by a space for consistency with the window list.

If no arguments are given the entire switch list is displayed. If you compare this with the window list (Ctrl+Esc or mouse button 2) you will see some of the differences between these two views of the machine, and also you will be able to see some of the previously hidden fields. You may wish to experiment with your own programs or 'start /fs'.

If an argument is entered the program looks for a match, and then posts the window handle (if any) associated with the item a WM_SYSCOMMAND command with argument SC_MAXIMIZE. This simulates the 'maximize' button being pressed.

After this WinSwitchToProgram is called to make the current item the active program. You will notice that from a full screen program this has the effect of switching to the PM screen; this can be a useful documented way to achieve this result if the 'Switch to' item is selected.

You may notice when experimenting that the switch can occur before the window has been maximized. This is because WinPostMsg adds the message to the destination thread's message queue but then carries on, and the message will only be processed next time that thread looks for a message. Watch out for this asynchronous nature of WinPostMsg whenever you use it.

Source Code

TASKLIST.C

/*
  tasklist      - program to display OS/2 tasklist
  Compiled using IBM C Set++ as 'icc /wall+ppt- tasklist.c'
*/

#define INCL_WINSWITCHLIST
#define INCL_WINFRAMEMGR
#define INCL_WINERRORS
#include <os2.h> 

#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

/*****************************************************************************/
/* LoadSwitch: load the switch list into memory and return pointer to it     */
/*****************************************************************************/

static BOOL LoadSwitch( HAB hAB, PSWBLOCK *ppSwBlk )
  {
  ULONG cbItems = 0;             /* Count of items                          */
  PSWBLOCK pSwBlk = NULL;        /* pointer to allocated switch block       */
  size_t len = 0;                /* overall length to be allocated          */

  cbItems = WinQuerySwitchList( hAB, NULL, 0 );
  if ( cbItems == 0 )
     {
     printf( "WinQuerySwitchList error: %lu\n", WinGetLastError( hAB ) );
     return FALSE;
     }

  /* Note: SWBLOCK structure already has room for 1 SWENTRY */
  len = sizeof( *pSwBlk ) + ( cbItems - 1 ) * sizeof( SWENTRY );
  pSwBlk = malloc( len );

  cbItems = WinQuerySwitchList( hAB, pSwBlk, len );
  if ( cbItems == 0 )
     {
     printf( "WinQuerySwitchList error: %lu\n", WinGetLastError( hAB ) );
     free( pSwBlk );
     return FALSE;
     }

  *ppSwBlk = pSwBlk;
  return TRUE;
  }

/*****************************************************************************/
/* process: do the work                                                      */
/*****************************************************************************/

static BOOL process( PSWBLOCK pSwBlk, PSZ pszSwitchTo )
  {
  ULONG i = 0;                   /* loop counter                            */
  BOOL bFound = FALSE;           /* TRUE when found an item to work on      */
  PSWENTRY pSwEntry = pSwBlk->aswentry; /* pointer to current entry         */

  for ( i = 0; i < pSwBlk->cswentry; i++, pSwEntry++ )
     {
     /* Replace newlines with spaces in title text */
     PSZ pNl = pSwEntry->swctl.szSwtitle;
     while ( ( pNl = strchr( pNl, '\n' ) ) != NULL )
        *pNl = ' ';

     if ( pszSwitchTo )
        {
        if ( stricmp( pszSwitchTo, pSwEntry->swctl.szSwtitle ) == 0 )
           {
           /* Found the item the user selected */

           if ( ! WinPostMsg( pSwEntry->swctl.hwnd, WM_SYSCOMMAND,
                MPFROMSHORT( SC_MAXIMIZE ), MPFROM2SHORT( CMDSRC_OTHER, FALSE ) ) )
              printf( "Unable to post maximize message\n" );

           if ( WinSwitchToProgram( pSwEntry->hswitch ) != 0 )
              printf( "Unable to switch to program\n" );

           printf( "\n" );
           bFound = TRUE;
           }
        }
     else
        {
        if ( !bFound )
           {
           printf( "Handle     Sess   Hwnd      Icon      PID    Title\n" );
           bFound = TRUE;
           }

        printf( "%-8x   %-4u   %-8x  %-8x  %-4u   %s\n",
                pSwEntry->hswitch,
                pSwEntry->swctl.idSession,
                pSwEntry->swctl.hwnd,
                pSwEntry->swctl.hwndIcon,
                pSwEntry->swctl.idProcess,
                pSwEntry->swctl.szSwtitle );
        }
     }

  return bFound;
  }

/*****************************************************************************/
/* M A I N   P R O G R A M                                                   */
/*****************************************************************************/

int main( int argc, char **argv )
  {
  PSZ pszProg = argv[ 1 ];
  PSWBLOCK pSwBlk = NULL;
  HAB hAB = WinInitialize( 0 );

  if ( argc > 2 )
     printf( "%s expects at most one argument - program to activate\n", argv[0] );
  else if ( !LoadSwitch( hAB, &pSwBlk ) )
     printf( "Could not obtain switch list\n" );
  else
     {
     if ( !process( pSwBlk, pszProg ) && ( pszProg != NULL ) )
        printf( "Could not process %s\n", pszProg );
     free( pSwBlk );
     }

  return(0);
  }