# ...putting programs into the System Tray.

The program illustrated here is a modified version of sysinfo.exw, from Lesson 16, with two major differences.

First, the information is displayed through a pixmap. This is being used as a buffer to hold the updated information, which is then all sent to the program's window at once. Doing this stops the screen from 'flickering' as much as it did in the original program.

When this program is first started up, it will be minimized, and it's program icon will be placed in the System Tray, as a little 'question mark'. ( usually near the speaker and time display. )

Left-clicking on the icon will simply show the program's normal window. The three buttons will allow:
1) - hiding the program window again,
2) - shutting it down altogether, or,
3) - opening a normal ( About ) message box.

This example also illustrates the Win32Lib wrappers you can use to declare and manipulate Win32 structures. Instead of using allocate(), peek(), poke(), etc., Win32Lib has some 'wrapped' functions to make this a little easier, since these 'wrappers' will automatically allocate the correct amount of memory, based on their type.
( Byte, Word, DWord, zero-terminated string, etc. )

-- sysinfo2.exw

    -- dynamically displays information, with it's icon in the System Tray.

    include win32lib.ew
    without warning

    object ok

    -- what follows is the 'TrayIcon' code    --
    -- originally 'wrapped' by Hendrik Mundt. --
    constant
      Shell_NotifyIconA__ = registerw32Function(shell32,"Shell_NotifyIconA", {C_LONG, C_POINTER}, C_INT ),
      zNIM_ADD = 0,
      zNIM_MODIFY = 1,
      zNIM_DELETE = 2,

      zNIF_MESSAGE = 1,
      zNIF_ICON = 2,
      zNIF_TIP = 4,
      zWM_USER = #400,
      zUWM_SYSTRAY = zWM_USER + 1

    -- declare memory required for NOTIFYICONDATA structure
    constant
      NIDcbSize     = allot( DWord ),
      NIDhWnd       = allot( Long ),
      NIDuID        = allot( Long ),
      NIDuFlags     = allot( Long ),
      NIDuCallbackMessage = allot( Long ),
      NIDhIcon      = allot( DWord ),
      NIDszTip      = allot( 64 ),
      SIZE_OF_NOTIFYICONDATA = allotted_size()

    function AddTrayIcon(atom hwnd,integer uid,atom hicon,sequence tooltip)
      atom nid

    -- allocate memory for required structure
      nid = acquire_mem( 0,SIZE_OF_NOTIFYICONDATA )
    -- and fill it with the required information
      store( nid, NIDcbSize, SIZE_OF_NOTIFYICONDATA )
      store( nid, NIDhWnd, hwnd )
      store( nid, NIDuID, uid )
      store( nid, NIDuFlags, or_all({ zNIF_MESSAGE,zNIF_ICON,zNIF_TIP} ))
      store( nid, NIDuCallbackMessage, zUWM_SYSTRAY )
      store( nid, NIDhIcon, hicon )
      store( nid, NIDszTip, tooltip )

      ok = w32Func(Shell_NotifyIconA__, {zNIM_ADD, nid} )
      release_mem( nid )

      return ok
    end function

    function DelTrayIcon(atom hwnd, integer uid)
      atom nid

      nid = acquire_mem( 0,SIZE_OF_NOTIFYICONDATA )
      store( nid, NIDcbSize, SIZE_OF_NOTIFYICONDATA )
      store( nid, NIDhWnd, hwnd )
      store( nid, NIDuID, uid )

      ok = w32Func( Shell_NotifyIconA__, {zNIM_DELETE, nid} )
      release_mem( nid )

      return ok
    end function
    -- end of TrayIcon 'wrapper' --

    sequence size

    -- our main window and it's buttons.
    constant
        MemWin  = create( Window, "- System Info -", 0, Default, Default, 480, 175, {WS_DLGFRAME} ),
        Button1 = create( PushButton, "Close", MemWin, 65, 123, 80, 25, 0 ),
        Button2 = create( PushButton, "Kill", MemWin, 180, 123, 80, 25, 0 ),
        Button3 = create( PushButton, "About", MemWin, 295, 123, 80, 25, 0 ),

    -- links to procedures in kernel32.dll
        zGlobalMemoryStatus = registerw32Procedure( kernel32, "GlobalMemoryStatus", {C_POINTER} ),
        zGetWindowsDirectory = registerw32Procedure( kernel32, "GetWindowsDirectoryA", {C_POINTER, C_INT} ),
        zGetCurrentDirectory = registerw32Function( kernel32, "GetCurrentDirectoryA", {C_INT, C_POINTER}, C_INT ),
        zGetCurrentTime = registerw32Procedure( kernel32, "GetLocalTime", {C_POINTER} )

    -- let's calculate our pixmap size, based on the client
    -- area left in our main window, and subtract 30 from the 'y' dimension
    -- to leave us room for the buttons.
        size = getClientRect(MemWin)

    constant
      Buffer = create( Pixmap, "", 0, 0, 0, size[3]-size[1], size[4]-size[2]-30, 0 ),
      MyTimer = 1

    -- easy on the eyes...
    setFont(Buffer,"System",10,1)

    -- some 'hints' for the buttons, in my 'pathetic' German.
    setHint(Button1,"Fluoreszenzunterdrükkung ;-)")
    setHint(Button2,"Stoppanweisung")
    setHint(Button3,"...kann nicht in einer Stellung stehenbleiben!")

The next four blocks of code, are all the routines which get the information to be displayed in the main window.

    sequence meminfo, windirstring, currentdirstring
    atom buffer

    procedure GetMemory()
      buffer = allocate(32)
      poke4(buffer,32)
      w32Proc(zGlobalMemoryStatus,{buffer})
      meminfo = peek4u({buffer,8})
      free(buffer)
    end procedure

    function GetWindowsDir()
      sequence WinDir
      integer i
      buffer = allocate(100)
      w32Proc(zGetWindowsDirectory, {buffer, 100})
      WinDir = ""
      i = 0
      while peek(buffer+i) != 0 do
        WinDir = WinDir & peek(buffer+i)
        i = i+1
      end while
    free(buffer)
    return WinDir
    end function

    function GetCurrentDir()
      sequence CurrentDir
      integer i
      buffer = allocate(4)   -- first get the length using this dummy length.
      i=w32Func(zGetCurrentDirectory, {4, buffer})
      if i=0 then
        ok = message_box( "Couldn't link to C function GetCurrentDirectoryA",
        "Win32 Error", MB_ICONHAND+MB_TASKMODAL )
        free(buffer)
        abort(1)
      end if
      free(buffer)
      buffer = allocate(i)   -- do it again, with the correct length this time!
      i=w32Func(zGetCurrentDirectory, {i, buffer})
      CurrentDir = ""
      i = 0
      while peek(buffer+i) != 0 do
        CurrentDir = CurrentDir & peek(buffer+i)
        i = i+1
      end while
      free(buffer)
      return CurrentDir
    end function

    function Getcurrenttime()
    sequence currenttime
      buffer = allocate(16)
      w32Proc(zGetCurrentTime, {buffer})
      currenttime = ""
      for i = 0 to 15 do
        currenttime = currenttime & peek(buffer+i)
      end for
      free(buffer)
      return currenttime
    end function

The following refresh_info() routine is responsible for writing all the information into our pixmap buffer, and then writing the whole pixmap out to the program's main window every second.

    procedure refresh_info( integer self, integer event, sequence params )
      sequence s, t, string1

    -- first let's clear the buffer pixmap by painting over it.
      setPenColor(Buffer,getSysColor( COLOR_BTNFACE ))
      drawRectangle(Buffer,1,0,0,size[3]-size[1],size[4]-size[2]-30)

    -- then write out our time string.
      s=Getcurrenttime()
      t=sprintf("%02d",s[9..10]) & ":" & sprintf("%02d",s[11.12])
                 & ":" & sprintf("%02d",s[13..14])
      setPenPos(Buffer,2,2)
      wPuts(Buffer,"current time = " & t )

    -- ...then our memory info...
      GetMemory()
      setPenPos(Buffer,2,20)
      string1= "bytes of memory = " & sprintf("%d",meminfo[3])
      wPuts(Buffer,string1)
      setPenPos(Buffer,2,38)
      string1= "percent of memory in use  = " & sprintf("%d",meminfo[2]) & "%"
      wPuts(Buffer,string1 & "  - including 'virtual' memory.")
      setPenPos(Buffer,2,56)
      string1= "free memory bytes = " & sprintf("%d",meminfo[4])
      wPuts(Buffer,string1)

    -- then, write out our directory info...
      setPenPos(Buffer,2,74)
      wPuts(Buffer,"Windows dir = " & windirstring)

      setPenPos(Buffer,2,92)
      wPuts(Buffer,"this program's dir = " & currentdirstring)

      copyBlt( MemWin, 0, 0, Buffer )
    end procedure

The next five procedures are all the event handlers for everything but the onTimer[] event, which is above.

    procedure onOpen_MemWin( integer self, integer event, sequence params )
    -- put it's icon in the System Tray
    -- note, for this simple example,
    -- we're using the 'stock' IDI_QUESTION icon.
      ok = AddTrayIcon( getHandle(MemWin),1,
          w32Func( xLoadIcon, { NULL, IDI_QUESTION } ),
          "System Information" )   -- our app. name in the tooltip.
    -- this is our re-fresh interval timer.
      setTimer(MemWin, MyTimer, 1000)
    -- since the following is 'static', we only get it once.
      windirstring = GetWindowsDir()
      currentdirstring=GetCurrentDir()
    end procedure

    procedure hide_MemWin( integer self, integer event, sequence params )
      setVisible(MemWin,False)
    end procedure

    procedure kill_MemWin( integer self, integer event, sequence params )
    -- since we're leaving, remove the program's icon.
      ok = DelTrayIcon( getHandle(MemWin), 1 )
      closeWindow(MemWin)
    end procedure

    procedure about( integer self, integer event, sequence params )
    ok = message_box( "An application running from the SystemTray,\n" &
      "with code contributed by Hendrik Mundt.\n\n" &
      "Using Win32lib.ew,  by David Cuny and Friends.",
      "... a Win32 Tutorial",#2000)
    end procedure

    procedure onEvenr_MemWin( integer self, integer event, sequence params )
      integer lParam = params[1] -- maybe

      if lParam = WM_LBUTTONUP then
          setVisible( MemWin, True )
          ok = w32Func( xSetForegroundWindow,{getHandle(MemWin)})
      end if

    end procedure

    setHandler( MemWin, w32HOpen, routine_id("onOpen_MemWin") )
    setHandler( MemWin, w32HTimer, routine_id("refresh_info") )
    setHandler( Button1, w32HClick, routine_id("hide_MemWin") )
    setHandler( Button2, w32HClick, routine_id("kill_MemWin") )
    setHandler( Button3, w32HClick, routine_id("about") )
    setHandler( MemWin, w32HEvent, routine_id("onEvent_MemWin") )

    -- WinMain(MemWin,WS_MINIMIZE) -- if I do this, program vanishes into space!
    WinMain(MemWin,Normal)
    ----

For some 'magical' reason this program's icon seems to disappear in about 10 seconds even if it's not removed on program exit, but it's better to be safe than sorry...

This program could be simplified by declaring 'nid' only once outside of the two functions, and removing the ( release_mem( nid ) ) call from AddTrayIcon().

This would simplify the DelTrayIcon() function to only this:

    function DelTrayIcon(atom hwnd, integer uid)

      ok = w32Func( Shell_NotifyIconA__, {zNIM_DELETE, nid} )
      release_mem( nid )

      return ok
    end function

...and there's more ;-)
...since hwnd, and uid, are not really required any more, when calling DelTrayIcon().

...end...

CONTENTS