LAST: Step 3 Adding WidgetsContentsNEXT: Programming Details 2

Programming details and WINGs functions

Count the windows
In the code up till now, we had just one window. When it receives notificaton that the window is requested to close, it shuts down the whole application. In an application with more windows open, we might not like it when closing an arbitrary window shuts everything down. An obvious solution is to exit the programma when the last open window is requested to close, and keep a count of the number of windows open. The closeAll function becomes:
int windowcounter=0;
void closeAll(WMWidget *self,void *data){
  WMDestroyWidget(self);
  fprintf(stderr,"I've been used!");
  if(--windowcounter<1) exit(0);
}
A second window should be opened with the existing screen as an argument. After success in opening, you increase windowcounter by one.
Icons and images

Defining an icon which will be used for your application, and drawing an image in a widget, are quite straightforward. Suppose, there is an XPM-image available, and it is the file /usr/include/pixmaps/picture.xpm. The following code sets an application icon and draws an icon in a label.

 RContext *ctxt;
 RImage *img;
 WMPixmap *wimg;

 /* code to open screen, window*/
 ctxt = WMScreenRContext(screen);
 img = RLoadXPM(ctxt, "/usr/include/pixmaps/picture.xpm", 0);
 WMSetApplicationIconImage(screen, img);
 wimg = WMCreatePixmapFromRImage(screen, img,0);

 /* code to create a label */
 WMSetLabelImagePosition(label, WIPImageOnly);
 WMSetLabelImage(label, wimg);
RContext refers to the X-server's so-called graphics context. This specifies which line width, fill patterns, etc. will be used. That information is not contained in the XPM-file. With WMScreenRContext, we use the existing context. RLoadXPM loads the xpm from a file, and stores it as an RImage.

The image is set as an icon for the application with this RImage. We transform the RImage into a WMPixmap. The WMPixmap can be shown in a widget. Here, we show it in a label with WMSetLabelImage . You must specify its position with the right option first.

An X pixmap is a text file. You can insert its code into your application source code directly, and handle it with RGetImageFromXPMData

Virtual screen and resolution

WINGs provide the function unsigned int WMScreenWidth (WMScreen *wmscr) to get the screen's width in pixels. There is a similar function to get its height. This is information about the virtual screen, and is not always what you are looking for. Many (or all?) Gtk+ interfaces have bigger font sizes when the virtual screen is bigger, even when the monitor is the same. If your monitor runs at 1024x768, and your virtual screen measures 1800x1440 pixels, you would often want to adjust your application to the monitor's resolution, and the view it has on the virtual screen, rather than to the screen's size itself. To get the used video mode (ie. the 1024x768 in our example), and the position on the virtual screen, the X-library libXxf86vmode provides two functions.

The returned modeline is a structure which has members hdisplay and vdisplay. The monitor's current resolution is hdisplay x vdisplay. The monitor's left uppper corner is at the position returned by XF86VidModeGetViewPort in *x_return x *y-return. The screen parameter in these function calls is not a WMScreen variable. A WMScreen variable wmscris a structure, defined in WINGsP.h, which contains the screen number in a member wmscr.screen. The follwing example defines a function *WMGetModeViewSSize() For simplicity, it is assumed the application is using the default screen. The argument to the WMScreenWidth function should of course be a WMScreen type.
    /*   extra headers    */
#include <X11/Xlib.h>
#include <X11/extensions/xf86vmode.h>
 
Display *display;
WMScreen *screen;

int *WMGetModeViewSSize(){
 int *result;
 XF86VidModeModeLine modeline;
 int dotclock_return;

 result = (int *)calloc(8,sizeof(int));

 XF86VidModeGetModeLine(display, DefaultScreen(display), &dotclock_return, &modeline);
 *result = modeline.hdisplay;
 result[1] = modeline.vdisplay;
 XF86VidModeGetViewPort(display, DefaultScreen(display), result+2, result+3);
 result[4] = WMScreenWidth(screen);
 result[5] = WMScreenHeight(screen);

 return result;
}
To compile this function, you need the libXxf86vm library. For the GNU compiler, your command would now be gcc -x c -lXft FileName.c -L/usr/X11/lib -L/usr/lib -lWINGs -lwraster -lXxf86vm -o FileName. When you run the function (after opening the screen), and print its results, you will find something like:
result 0 and 1: 1024 768 
result 2 and 3: 126 171 
result 4 and 5: 1800 1440
meaning that the monitor is running at 1024x768, its upper left corner is at (126,171) in the virtual screen, and the whole screen has a resolution of 1800x1440. The user is seeing the screen part from (126,171) to (1150,939). In the illustration to the right, (X,Y) represent the Viewport coordinates which are obtained from XF86VidModeGetViewPort. The bright part is the part of the virtual screen which is visible on the monitor at that moment.
Message log window

In all the applications up till now, error and other messages have been sent to stderr or stdout. when you start the programmes by (double-)clicking in your file manager, the messages may disappear, or pop up in a window. This makes starting the application from an xterm command line the most practical. To get rid of this disappointing feature, you can programme another window to send the messages to, or, more logically, use a named pipe to send them to a different application which you already have on your system. This section gives an example how to code this last method.

The method is simple: when the first message needs to be written, the code creates the pipe with mknod. If successful, it forks. The child process uses unix' execlp to start the logging application. In this example it is xconsole, with the pipe as its file argument. The parent process opens the pipe for writing. The application now can write to the pipe.

The first detail is in the function to close our applicaton, closeAll, in the examples. This function should terminate the child process, and also delete the file which was used for piping the data. The second detail is the way we keep track if the child process is still running, or whether the user has clicked it away. For this we declare a signal handler each time we start up the child process. At the SIGCHLD signal, which indicates the child process has been terminated, we call a function which deletes the pipe file as well, and sets a global variable to a value which allows us to check if the child process has terminated. When writing our second message, we check first if the child process is still running. If it is, we can write to the pipe, if it isn't, we create a new child process and pipe. If there is any problem, we fall back on the usual stderr. Here is the (extra) code for a simple implementation:

#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#define ERRMSGFIFO "/tmp/WINGsWindowfifo"
#define NOLOGWINDOW (-2)
#define FIFOERROR (-1)
#define FIFOLOWESTPOSS 0

int fifonr = NOLOGWINDOW; /* the fifo nr, or an error value */
int sibpid;               /* the child's process ID  */


/* clean up when closing: */
void closeAll(WMWidget *self,void *data) {
  WMDestroyWidget(self);
  if(--windowCounter < 1) {
    if (fifonr >= FIFOLOWESTPOSS)
      kill(sibpid,SIGTERM);
    if (!access(ERRMSGFIFO, F_OK | W_OK))
     unlink(ERRMSGFIFO);
    exit(0);
  }
}


/* Handle the case the child terminates. Set fifonr and clean up:     */
void redirectmsg(int sig) {

  fifonr = NOLOGWINDOW;
  if (!access(ERRMSGFIFO, F_OK | W_OK))
    unlink(ERRMSGFIFO);
  return;
}


/* Have the log window pop up:    */
int showMessageWindow() {

  (void) signal(SIGCHLD, redirectmsg); /* use redirectmsg whenever the child process stops  */

  if (access(ERRMSGFIFO, F_OK) == -1)
    fifonr = mknod(ERRMSGFIFO, 0640|O_EXCL|S_IFIFO, (dev_t)0);
  else
    fifonr = FIFOERROR;
    /* fifonr == FIFOERROR if mkfifo or access failed, for mknod returns -1 on failure   */

  if (fifonr != FIFOERROR) {
    sibpid = fork();

    if (sibpid == 0) {
      execlp("xconsole", "xconsole", "-file", ERRMSGFIFO, "-geometry", "250x400",
             "-title", "Application Messages", (char *)0);
      exit(1);
    } else {
      fifonr = open(ERRMSGFIFO, O_WRONLY);
    }
  }
  return fifonr;
}


/* Usage:       */
void someActionWithMessage(void *self, void *data) {

  if (fifonr < FIFOLOWESTPOSS)
    fifonr = showMessageWindow();   /* (re)start xconsole, or try again in case of FIFOERROR  */

  if (fifonr == FIFOERROR)          /* if still error, use stderr   */
    fprintf(stderr, "%s selected\n", WMgetSomeInformationFrom(self));
  else {
    char textbuffer[100];

    snprintf(textbuffer, sizeof(textbuffer), "%s is the information\n",
             WMGetSomeInformationFrom(self));
    write(fifonr, textbuffer, strlen(textbuffer));
  }
}

The someActionWithMessage function is a WMAction in this case. Of course, there must be an xconsole in the user's path and he needs the correct rights. The example catches the events that the user clicks away the xconsole before he is finished, that the fifo file already exists, and that the fifo file is replaced with something which is not accessible during run time. There is nothing to change in main.

LAST: Step 3 Adding WidgetsContentsNEXT: Programming Details 2