...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