X-Windows Lab Manual (1)

Embed Size (px)

Citation preview

ARYA INSTITUTE TECHNOLOGY

OF

ENGG.

&

X-Windows Programming LaboratoryLab Manual

Neelima Sharma

This Laboratory manual contains Excellent overview o f X-Window System and X-Window programming. This is useful for VIII semester Computer Science branch.

Introductio nOverview of X Windows X11 Release 5 came out in August 1991. The heart of the X Windows system consists of a program called X which runs on a machine with a display, keyboard, and a mouse. It waits for other programs to tell it what to do or for something to happen to the pointer or keyboard. The programs can be running on the same machine as X is or elsewhere on the network, maybe on a machine that hasn't even got a display of its own. This `network transparency' is one of the strengths of X. Graphics programs only have to know about X, not about the special low level graphics commands for each type of machine. The client programs communicate to X, the server, via a Protocol language that is common across machine types. All a client program needs to do to use the X display is to open up a connection with the server and then send Protocol requests to it. To simplify sending these, an extensive library of about 200 display subroutines is provided and it is this library, Xlib, which this document mainly describes. Many client programs can simultaneously use the same X server. To save each client having its own copy of fonts, color information, etc, (thereby wasting space and causing more data to be passed via the network), the server stores data on behalf of the clients, allowing sharing wherever possible. In order to enable the client to reference these resources the server provides resource codes and these can be used in many of the routines to specif y that certain data is to be used. X is `event driven'. For each window you create you can select what sort of events (key presses, re-exposure, etc) you want it to respond to. Typically, an X program consists of a set-up sequence followed by an `event-loop' which waits for events to be reported by the server, determines what sort of event has happened and in which window, then processes the event.

Page 2

Figure 1: X system overview

There are 4 types of messages passing between the client and server; Requests- the client can ask the server to draw something, or ask for information. Replies - the server can reply. Events - the server can surprize the client with something Errors- the server can report an error Both display requests and events are buffered and the server executes asynchronously much of the time to maximise efficient use of the network. When the client wishes to establishPage 3

it often has to ask for it, though working in synchronized mode can be up to 30 times slower, so only use it for debugging.

X clients programs should be written mindful of the fact that other clients are likely to be running at the same time. Windows should be prepared to be covered over by others then exposed, and they have to redraw themselves.Neither should clients indescriminately use scarce resources like off-screen memory or monopolise the keyboard or pointer.If can they accept cutand-paste operations, so much the better. One application that is always likely to be running is a `window manager'; a program which allows you to resize, move and re-stack windows, pop menus up, etc. Any X application you write will need to work under a window manager. Some window managers are more bossy than others (some won't let you raise windows, for instance), but there are X commands so that a client can at least make suggestions to the window manager.

X can support one or more screens containing overlapping (sub) windows and works on many types of hardware but it doesn't provide high level support. If you want to prefabricate dialog boxes from pre-defined scrollbars, buttons, etc then you should use a `toolkit' that sits on top of X. See the Motif manuals and on-line help for details. X is extensible. Release 4 of X included the SHAPE extension which provides non-rectangular, disjoint windows. Neither the use nor the writing of such extensions will be covered in this document. In the bad old days Linux, like its parent UNIX, lacked a decent graphical user interface.This led to the howls of protest from users around the world of Linux being as much cryptic as other UNIX derivatives. It was rightly tagged as the OS of the geek. The X Window System lends a face to Linux.

Page 4

Using X from a Terminal

There are a number of window managers, terminal emulators and graphics programs supplied with X which enable the user to create text windows and move them about on the screen. All these facilities are described in section (1) of the Unix manual.

To investigate the X system on your machine you might first like to try

Page 5

How to use X in a C program

From the programmer 's point of view X consists of a library of subroutines which he may call in order to perform specific functions on the display. These functions are contained in a library, /usr/lib/libX11.a, (or /usr/lib/X11R5/libX11.sl on HPs) which must be linked into the program by the loader when the executable version of the program is generated. This can usually be done by adding -lX11 to the end of the compilation command and, on HPs, -L/usr/lib/X11R5 to the start. The Xlib header file X11/Xlib.h (or /usr/include/X11R5/Xlib.h on HPs)must also be included at the top of every program file which uses the X library. On the HPs this requires the addition of I/usr/include/X11R5

The coordinate system for X windows has its origin at the top left hand corner of any given window. The x coordinate increases from left to right across the page and the y coordinate increases from top to bottom. The global coordinate system is effectively that of the RootWindow which covers the whole of the screen. The scaling of coordinates on an X display is never changed, however operations in each window are performed relative to that window's origin.

X functions which are described as returning Status produce a non zero value if they have executed successfully and return zero if they have failed. Other routines which return a pointer to something on success usually return the NULL pointer on failure. To save space, we have only declared arguments whose type or use is not self evident. Most particularly, the following variables are not declared :Display *display; Window w;Page 6

Drawable d; /* either a window you can draw into or off-screen memory (a pixmap) */ GC gc; /* a Graphics Context, where current font,

foreground color, etc is contained*/ Colormap cmap; Region r; unsigned long mask,valuemask; There is a standard order for arguments :display,resources,gc, x,y,w,h, array,array_length, mask,struct

Page 7

The X Window System is a network oriented & device independent system . Network Oriented means that there is a client end and a server end. There could be more than one client for a given server. Basically client programs make calls to the X server. Xlib maps requests to the X server or provides utility functions. Actually X converts calls to C language functions to the X server requests which actually implements that request. Just as an aside, all the conversation between client and server is carried out using the X Network Protocol,which might be carried over any reasonable implementation i.e. TCP/IP, DecNet etc.

Graphical Programming in X basically follows the asynchronous model i.e. "I won't do anything until you ask me to". This might sound like a typical response from your 7 year old. It means that an X program sits in a loop waiting for interesting things to happen. The following snippet illustrates it: while(connected to server){ Receive next event Handle the event }

To run an X program you need to have the necessar y header files and libraries in /usr/X11R6/include and /usr/X11R6/lib respectively. Chances are that if you're running any GUI version of Linux and haven't really fiddled with the system you would be having all the headers and libraries at their default places. The following program is the most basic of all Xlib programs. It simply makes a connection to the X server, prints some info and quits.

Page 8

/*connect.c -Program to connect to the X Server On Linux compile with gcc connect.c -L/usr/X11R6/lib -lX11 */

#include #include #include

int main(){ Display *display_name; int depth,screen,connection;

/*Opening display and setting defaults*/ display_name = XOpenDisplay(NULL); screen = DefaultScreen(display_name); depth = DefaultDepth(display_name,screen); connection = ConnectionNumber(display_name);

/*Displaying the info gathered*/ printf("The display is::%s\n",XDisplayName((char*)display_name)); printf("Width::%d\tHeight::%d\n",Page 9

DisplayWidth(display_name,screen), DisplayHeight(display_name,screen)); printf("Connection number is %d\n",connection);

if(depth == 1) printf("You live in prehistoric times\n"); else printf("You've got a coloured monitor with depth of %d\n", depth);

/*Closing the display*/ XCloseDisplay(display_name); }

Page 10

The first and foremost step, in case of all X programs is a request for establishing a connection to the X server. This is accomplished by the XOpenDisplay function call.We pass a NULL as the parameter to indicate that our client and server are on the same machine. After establishing a connection we seek a little info in form of the screen we are connected to(remember each display may have multiple screens),the depth of the screen(colors/bit - for example 16bit screen screen will return 16). Finally we close the connection by using XCloseDisplay(). After this introduction we move on to more serious stuff i.e. opening a window on the display. The following program attempts to open a window on the screen. This window automatically terminates after 10 seconds.

Page 11

/* window.c --This program opens a window on the display * Use "gcc -o window window.c -L/usr/X11R6/lib -lX11 * to compile this code. */#include #include int main(void) { Display *display; Window window, rootwindow; int screen;

display = XOpenDisplay(NULL); screen = DefaultScreen(display); rootwindow = RootWindow(display,screen); window = XCreateSimpleWindow(display, rootwindow, 0, 0, 100, 100, 1, 0, 0); XMapWindow(display, window); XFlush(display);

sleep(10); XCloseDisplay(display); return (0);Page 12

}

Various objects are made for us by the X server. These objects like windows, cursors etc. return a handle.We can manipulate the objects later by utilizing X routines that accept these handles as parameters. Moreover there are a lot of functions in X which are also implemented as macros.This is probably done to aid efficiency as a macro does not involve function call. However as C compilers get smart they might already be inlining appropriate function so that a function call is almost as fast as a macro.

The RootWindow macro returns the root window. Root Window denotes the screen that forms the background of our window.There is only one Root Window per display. In our case RootWindow returns essentially the background of the screen which is the parent window for all windows on the display. The next important point of action is the XCreateSimpleWindow() function. This function effectively creates a window on the display. Officially the parameters of this function are:XCreateSimpleWindow( Display *display, Window rootwindow, /*Our display*/ /*parent Window*/

Page 13

int x, int y, unsigned int width, unsigned int height,

/*Starting x coordinate*/ /*Starting y coordinate*/ /*Width of the window*/ /*Height of the Window*/ /*Width of the border*/ /*Specify border pixel value*/ /*Color of the background*/

unsigned int border_width, unsigned long border, unsigned long bkground )

XMapWindow() call actually maps the window onto the screen. This is analogous to ShowWindow() function in the Windoze API.A similar function is XMapRaised() which accepts same parameters but raises the window over all existing windows on the display. Xlib maintains a queue of output requests to the X server. XFlush() call basically flushes out this request by sending all requests to the X Server as part of one communication. Ideally this call should be given after all output calls. Finally sleep(10) waits for 10 seconds before closing the display.

This was probably the easiest way to draw a window. Now we discuss a more involved way to make a window appear on the screen.

Page 14

/*window2.c --Program to display a window on the screen. * compile as gcc window2.c -L/usr/X11R6/lib -lX11 */

#include #include #include #define BORDER_WIDTH 2 /* Program wide globals */ Display *theDisplay; int theScreen; int theDepth;

Window OpenWindow(int x, int y, int width, int height, int flag){ XSetWindowAttributes theWindowAttributes; unsigned long theWindowMask; XSizeHints theSizeHints; Window theNewWindow;

/*Setting the attributes*/ theWindowAttributes.border_pixel

Page 15

=BlackPixel(theDisplay,theScreen); theWindowAttributes.background_pixel = WhitePixel(theDisplay,theScreen); theWindowAttributes.override_redir ect = True;

theWindowMask = CWBackPixel|CWBorderPixel|CWOverrideRedirect;

theNewWindow = XCreateWindow( theDisplay, RootWindow(theDisplay,theScreen), x,y,width,height, BORDER_WIDTH,theDepth, InputOutput, CopyFromParent, theWindowMask, &theWindowAttributes);

theSizeHints.flags = PPosition | PSize; theSizeHints.x = x; theSizeHints.y = y; theSizeHints.width = width; theSizeHints.height = height;

Page 16

XSetNormalHints(theDisplay,theNewWindow,&theSizeHints); XMapWindow(theDisplay,theNewWindow); XFlush(theDisplay); return theNewWindow; }

void main(void){ Window theWindow; theDisplay = XOpenDisplay(NULL); if(theDisplay == NULL){ fprintf(stderr,"Error::Given display cannot be opened"); return; } theScreen = DefaultScreen(theDisplay); theDepth = DefaultDepth(theDisplay,theScreen); theWindow = OpenWindow(100,100,200,200,0);

sleep(10); XCloseDisplay(theDisplay); }

Page 17

This program is more robust as all the code of drawing the window is moved to another procedure. Moreover window attributes as well as hints to the window manager ar e also involved. Let us begin stepping through the program.

After establishing a connection to the X server,there is an error detection routine which checks if the connection was successful.A successful connection results in a call to OpenWindow() which opens a window on the display. The two new chaps in OpenWindow() ar e XSetWindowAttributes and XSizeHints. Both of these are structur es. While XSetWindowAttributes is needed to set various attributes of the window, XSizeHints is needed to set information to be used by window manager.

Two other functions that need to be explained are XCreateWindow() and XSetNormalHints(). XCreateWindow() has all the ingredients of our XCreateSimpleWindow() function and more. A bare bone display is as follows:XCreateWindow( Display *display, Window parent, int x,int y, /*Our display screen*/ /*The parent window*/ /*Starting coordinates*/

unsigned int width,unsigned int height, /*Proportions of the window*/ unsigned int border_width, int depth, unsigned int Class, Visual *visual, /*Width of the border*/

/*Depth of the screen*/ /*Type of window*/ /*visual of the window*/ /*Mask of the attributes*/

unsigned long attributemask,

XSetWindowAttibutes windowsattrib); /*Structure of window attribs*/ )

Page 18

XSetNormalHints() simply sends hints to the window manager informing it about position,size and dimensions.The flags field has been set to PPosition|PSize which effectively translates that the program chooses the position and size.

After understanding the process of opening a window we are all set to learn about drawing in a window. X provides calls for drawing points,lines and arcs. Circles and ellipses are thought of as an extension of arc function. In accordance with the GUI packages all these functions have descriptive names like XDrawPoint(), XDrawLine(), XDrawRectangle(), XDrawArc() etc. We present a program which draws standard shapes into the present window.

Page 19

/*Program to draw lines and rectangles--draw.c *On Linux compile as gcc draw.c -L /usr/X11R6/lib -lX11 */

#include #include #include #define BORDER_WIDTH 2

/* Program wide globals */ Display *theDisplay; int theScreen; int theDepth; unsigned long theBlackPixel; unsigned long theWhitePixel;

void initX(void){ }

void drawLine(Window theWindow,GC theGC,int x1,int y1,int x2,int y2){ XDrawLine(theDisplay,theWindow,theGC,x1,y1,x2,y2);

Page 20

}

void drawRectangle(Window theWindow,GC theGC,int x,int y,int width,int height){ XDrawRectangle(theDisplay,theWindow,theGC,x,y,width,height); }

int createGC(Window theNewWindow,GC *theNewGC){ XGCValues theGCValues;

*theNewGC = XCreateGC(theDisplay,theNewWindow,(unsigned long) 0,&theGCValues); if(*theNewGC == 0) return 0; else{ XSetForeground(theDisplay,*theNewGC,theWhitePixel); XSetBackground(theDisplay,*theNewGC,theBlackPixel); return 1; } }

Page 21

Window OpenWindow(int x, int y, int width, int height, int flag,GC *theNewGC){ XSetWindowAttributes theWindowAttributes; unsigned long theWindowMask; XSizeHints theSizeHints; XWMHints theWMHints; Window theNewWindow;

/*Setting the attributes*/ theWindowAttributes.border_pixel =BlackPixel(theDisplay,theScreen); theWindowAttributes.background_pixel = WhitePixel(theDisplay,theScreen); theWindowAttributes.override_redir ect = False;

theWindowMask = CWBackPixel|CWBorderPixel|CWOverrideRedirect;

theNewWindow = XCreateWindow( theDisplay, RootWindow(theDisplay,theScreen), x,y,width,height, BORDER_WIDTH,theDepth, InputOutput,

Page 22

CopyFromParent, theWindowMask, &theWindowAttributes);

theWMHints.initial_state = NormalState; theWMHints.flags = StateHint;

XSetWMHints(theDisplay,theNewWindow,&theWMHints);

theSizeHints.flags = PPosition | PSize; theSizeHints.x = x; theSizeHints.y = y; theSizeHints.width = width; theSizeHints.height = height;

XSetNormalHints(theDisplay,theNewWindow,&theSizeHints);

if( createGC(theNewWindow,theNewGC) == 0){ XDestroyWindow(theDisplay,theNewWindow); return( (Window) 0); }

Page 23

XMapWindow(theDisplay,theNewWindow); XFlush(theDisplay);

return theNewWindow; }

void main(void){ Window theWindow; GC theGC;

theDisplay = XOpenDisplay(NULL);

theScreen = DefaultScreen(theDisplay); theDepth = DefaultDepth(theDisplay,theScreen); theBlackPixel = WhitePixel(theDisplay,theScreen); theWhitePixel = BlackPixel(theDisplay,theScreen);

theWindow = OpenWindow(100,100,300,300,0,&theGC); drawLine(theWindow,theGC,10,10,100,100); drawRectangle(theWindow,theGC,100,100,100,100); XFlush(theDisplay);

Page 24

sleep(10); XDestroyWindow(theDisplay,theWindow); }

This progr am displays a rectangle and a line connected to one of the edges. To draw anything on the screen we need assistance from a GC. A GC is a reservoir of default values for background, foreground, brush, pen etc. Think of it as a box of crayons.A GC is needed because without its presence we would have to specify each and every trivial aspect to the X server. In our example we just set a few values and let the remaining be at their default values.

A GC is obtained by a call to XCreateGC() function.This GC can then be passed to all drawing functions. Wrapper functions are defined for both line as well as rectangle drawing. Local variables are defined for BlackPixel() and WhitePixel() macros. This basically eases the burden on the X server. As an aside there are functions which draw filled counterparts of the functions described above. These are aptly called XFillRectangle() and XFillArc( ). Moreover there are also functions which draw multiple points, lines and arcs like XDrawPoints(), XDrawLines() and XDrawRectangles(). Be sure to check the man pages for details.

That will be all for now.In the next part we will draw circles,ovals and process various type of events.In the meantime play with the programs you just executed. Meandering is the spirit of Linux

Page 25

Xlib tutorial part 1 -- Beginnings

Welcome to X11 xlib tutorial.

Most tutorials seem to start off with a bunch of discussion of client/server and how X seems to mess up your head. Instead, I'm going to start with one of the simplest possible X programs. Put a blank window on the screen.

/* first include the standard headers that we're likely to need */ #include #include #include #include #include #include

int main(int argc, char ** argv){ int screen_num, width, height; unsigned long background, border; Window win; XEvent ev; Display *dpy;

/* First connect to the display server, as specified in the DISPLAY environment variable. */

Page 26

dpy = XOpenDisplay(NULL); if (!dpy) {fprintf(stderr, "unable to connect to display ");return 7;}

/* these are macros that pull useful data out of the display object */ /* we use these bits of info enough to want them in their own variables */ screen_num = DefaultScreen(dpy); background = BlackPixel(dpy, screen_num); border = WhitePixel(dpy, screen_num);

width = 40; /* start with a small window */ height = 40;

win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), /* display, parent */ 0,0, /* x, y: the window manager will place the window elsewhere */ width, height, /* width, height */ 2, border, /* border width & colour, unless you have a window manager */ background); /* background colour */

/* tell the display server what kind of events we would like to see */ XSelectInput(dpy, win, ButtonPressMask|StructureNotifyMask );

/* okay, put the window on the screen, please */ XMapWindow(dpy, win);

Page 27

/* as each event that we asked about occurs, we r espond. In this * case we note if the window's shape changed, and exit if a button * is pressed inside the window */ while(1){ XNextEvent(dpy, &ev); switch(ev.type){ case ConfigureNotify: if (width != ev.xconfigure.width || height != ev.xconfigure.height) { width = ev.xconfigure.width; height = ev.xconfigure.height; printf("Size changed to: %d by %d ", width, height); } break; case ButtonPress: XCloseDisplay(dpy); return 0; } } }

with comments and blank lines, that's 60 lines. It's essentially the same as the following HTML

Page 28

in html which creates an empty page without anything on it. There ar e a few things to note: An X program must connect to the display. This can be over unix sockets, with shared memory, or even over TCP, so that in theory your program can run on one machine and display on a machine on the other side of the world! The initial creation of the window does not place it on the screen. It has to be mapped. The request for the window to be mapped will not happen right away. Only when all X requests have been sent to the server. XNextEvent does this for us if it can't find any events to give us, so in the case of this program, we don't have to worry about it. Only Black and White are available without some extra work. We'll get to color in a later section.

The code can be found in xtut1.c

To compile and run it on a unix type system:

gcc -lX11 xtut1.c -o xtut1 ./xtut1

Click on it to end the program

Page 29

Make a copy of it and try a few things with it. Set the background white with a black border, for instance. Change the initial size of the window. Does you window manager acknowledge the changes that you make?

Page 30

Xlib tutorial part 2 -- Lines

A blank black window is not very interesting. We want to place some thing in the window. Well, just like in real life you need a pen or pencil to draw on a blank piece of paper, our program needs a pen to draw on our window. First we'll add a new GC pen, and a XGCValues structure for choosing what style of pen we want. ... GC pen; XGCValues values; ...

Then, after we've created the window we can create the pen,

... /* create the pen to draw lines with */ values.foreground = WhitePixel(dpy, screen_num); values.line_width = 1; values.line_style = LineSolid; pen = XCreateGC(dpy, win, GCForeground|GCLineWidth|GCLineStyle,&values); ...

This particular pen that we've chosen, draws with a white ink. Makes one pixel wide lines and draws the line solidly. There are a lot of options on that you can choose. Look at the man page for XGCValues to see all the different pieces of style information you can choose. For our purposes the three we've got is enough.

Page 31

... /* as each event that we asked about occurs, we r espond. In this * case, if a piece of window was exposed we draw two diagonal lines */ while(1){ XNextEvent(dpy, &ev); switch(ev.type){ case Expose: XDrawLine(dpy, win, pen, 0, 0, width, height); XDrawLine(dpy, win, pen, width, 0, 0, height); break; case ConfigureNotify: if (width != ev.xconfigure.width || height != ev.xconfigure.height) { width = ev.xconfigure.width; height = ev.xconfigure.height; XClearWindow(dpy, ev.xany.window); printf("Size changed to: %d by %d ", width, height); } break; case ButtonPress: XCloseDisplay(dpy); return 0;

Page 32

} } ...

The complete code can be found in xtut2.c

This is an extra 13 lines. This program is not nearly as easy to do in HTML.

Things to try: Using the window manager, try resizing the window, do the diagonal lines change right away? Try resizing and then exposing part of the window (by covering it with another window and then moving it away). Does anything look weird? Make a copy and try drawing different lines in the window. Try drawing outside the window. Does it work? Try drawing white on white or black on black. Create a second pen. One black and one white. Try drawing with one then the other and back again. Investigate the XGCValues structure. Try setting diff erent values for function, plane_mask, line_width, and line_style. If you're ambitious you can try using the dash_offset and dashes members. If you set any values other than foreground, line_width and line_style, make sure to add to the GCForeground|GCLineWidth|GCLineStyle bitmask. ( GCFunction for function and GCPlaneMask for plane_mask. ) See the XGCValues man page for other details.

Page 33

Xlib tutorial part 3 -- Colour

The world is not very interesting if we are stuck in black and white. X has a sophisticated colour management system to be able to handle colour on displays that can only handle 16 colours at a time up to those that can handle millions or billions.

In our case, we just want three new colours. We're going to assume that the colours we name already exist on the server. For instance, on a true colour system they automatically will. X requires us to have a colormap so that we can map colours to integer values. So we'll put create some new variables. One to store the colormap and two to store the colours. Don't worry about the XColor structure right now. We'll get back to it later.

... Colormap cmap; XColor xc, xc2; ...

Then we'll stick in some code to do colour lookup after we get the screen number, but before we create the window. We'll also remove the three lines where we call BlackPixel or WhitePixel

... cmap = DefaultColormap(dpy, screen_num); XAllocNamedColor(dpy, cmap, "DarkGreen", &xc, &xc2); background = xc.pixel; XAllocNamedColor(dpy, cmap, "LightGreen", &xc, &xc2); border = xc.pixel;Page 34

XAllocNamedColor(dpy, cmap, "Red", &xc, &xc2); values.foreground = xc.pixel; ...

So, nine new lines and three lines from the last section removed. We have to remove the lines to get the black or white pixel values for the default screen or they may overwrite our allocated colours.

Things to try: Try choosing various other colours from rgb.txt . Do a web search if you can't find it on your system. It should be in /usr/lib/X11/rgb.txt or /usr/share/X11/rgb.txt . Comments on Xlib tutorial part 3 -- Colour by Mike at Tue 5th May 2009 5:16PM Because XSetForeground takes an int, just passing an RGB value seems to work just fine. Other than converting text to an RGB value and supporting old systems (or ones that don't use 24bit color?) what purpose does XAllocNamedColor serve? by Alan at Wed 6th May 2009 1:49PM Hi Mike, Supporting non 24 bit colour systems is one of the purposes of XAllocNamedColor. It's quite possible that, in the future, we could have systems supporting 48 bit colour. To specify those, you could use a string to describe the actual colour and it would give you a long int as a handle. Another purpose is supporting writable colours. Using XStoreColor(s), you can change many pixels all at once from one (set of) colour to another. You don't see it used a whole lot, but it is possible. by Megane at Wed 23rd Sep 2009 3:06AM Nice tutorial. I've tried to change colour,but when I copied & replaced new code,it doesn't seems to work. It reported an error. So when I deleted "XAllocNamedColor(dpy, cmap, "Red", &xc, &xc2); values.foreground = xc.pixel;" everything seems to be normal. Program work fine,and I see green background,but still,i can't get green border(it's still default). Why? I'm running Ubuntu 9.04 Thanks!

by Alan at Wed 23rd Sep 2009 3:32PM Hi Megane, It would be very useful to know what the error report was. Likely the error told you that the colour name you tried to use was not a colour it was familiar with. You can look in rgb.txt (like in /usr/lib/X11/rgb.txt) to get the list of colours it is familiar with. The "best" way to change colour is to copy colour names from that file and use the to replace the strings Red, LightGreen, DarkGr een in the above code snippet. What other code were you trying to place there? by Megane at Mon 5th Oct 2009 5:19PM Hey Alan. The error was next: in function main :33 error: 'values' undeclared (first use in this function) :33 error: Each undeclared indentifier is reported only once for each function it appears in. But,when i delete,foreground,everything seems to work fine. Maybe that causes the error,because there is no foreground in this example? by Alan at Fri 9th Oct 2009 2:31PM Hi Megane, Oh, I see. I guess when I was creating the code fragments, I was building on the code I had used in the previous two installments and had made sure there was a values variable to put stuff into as I went. I notice that the link to the finished code is missing from this page. It should be http://xopendisplay.hilltopia.ca/xtut3.c Hopefully, reading that will make sense to you. Let me know if you are having other troubles. Comments are closed.

Xlib tutorial part 4 -- Text

Colour is great, but now it might be interesting to display some text. Again X has a large text management system to be able to handle many fonts and sizes. So, we'll start with the new variables since the last section.

... char *fontname; XFontStruct *font; char * text = "Hi!"; int text_width; int textx, texty; ...

That's two variables to remember the font and the information about it. Then one to hold the text we want to show. The other three are to hold calculated numbers to specify where the text will display in the window.

... width = 400; /* This time a bigger window */ height = 400; ... fontname = "-*-helvetica-*-10-*"; font = XLoadQuer yFont(dpy, fontname); if (!font) {

fprintf(stderr, "unable to load preferred font: %s using fixed ", fontname); font = XLoadQuer yFont(dpy, "fixed"); } ... values.font = font->fid; pen = XCreateGC(dpy, win, GCForeground|GCLineWidth|GCLineStyle|GCFont,&values); text_width = XTextWidth(font, text, strlen(text)); ...

I've increased the size of the window so we can see what's going on. XLoadQueryFont() does a round trip to ask the display server to ask if it has the given font. Remember the concept of round trip as that will come up later when we start thinking about how quick our application is. So far, we don't have much to worry about, the application should come up almost instantaneously, even over slow links because we're not doing a lot of them before entering our event (main) loop.

The other thing that we've done here is to add the font to the GC(pen) so that it knows how to draw text when we tell it to do so. We've also stopped to calculate the width the string is going to take.

... case Expose: XDrawLine(dpy, win, pen, 0, 0, width/2-text_width/2, height/2); XDrawLine(dpy, win, pen, width, 0, width/2+text_width/2, height/2); XDrawLine(dpy, win, pen, 0, height, width/2-text_width/2, height/2); XDrawLine(dpy, win, pen, width, height, width/2+text_width/2, height/2);

textx = (width - text_width)/2; texty = (height + font->ascent)/2; XDrawString(dpy, ev.xany.window, pen, textx, texty, text, strlen(text)); break; ...

Here we've moved the lines so that they don't run right through the middle of the window, and instead placed the text there. Text is normally placed such that where you were starting if you are writing cursively is the text's starting point. This generally means the bottom left of the first letter of the text. Though it changes for some scripts. textx and texty are calculated such that the text should be centered in window.

Things to try: Change the text to a different colour than the lines. Move the lines to other places. Place the text in various other locations Use different texts. Even use argv[1] so you can have different text depending on the command arguments

Xlib tutorial part 5 -- Rearranging

In the next few posts, we're going to concentrate on being a good citizen in the X world. This post is about reorganizing code, next will be about obeying the user's preferences, and the following two lessons will be code for supporting non western Europe languages, as well as showing the name of the program in the title bar.

First, grab the code. The two parts of the program have been broken up into two functions. One to do setup (allocating colours, the f ont and a pen (GC) and creating a window) , and one to run the main loop. This is more normal for X programs. Usually, your program will spend some time doing setup in a variety of functions, and then run a function that is the main loop collection events and responding to them.

Other than that, we haven't done very much, but we have made way for our later change

* first include the standard headers that we're likely to need */ #include #include #include #include #include #include

GC setup(Display * dpy, int argc, char ** argv, int *width_r, int *height_r, XFontStruct **font_r){ int width, height;

int screen_num; unsigned long background, border; Window win; GC pen; XGCValues values;

char * fontname; XFontStruct *font; Colormap cmap; XColor xc, xc2;

screen_num = DefaultScreen(dpy);

cmap = DefaultColormap(dpy, screen_num);

XAllocNamedColor(dpy, cmap, "DarkGreen", &xc, &xc2); background = xc.pixel; XAllocNamedColor(dpy, cmap, "LightGreen", &xc, &xc2); border = xc.pixel; XAllocNamedColor(dpy, cmap, "Red", &xc, &xc2); values.foreground = xc.pixel;

fontname = "-*-helvetica-*-10-*"; font = XLoadQuer yFont(dpy, fontname); if (!font) {

fprintf(stderr, "unable to load preferred font: %s using fixed\n", fontname); font = XLoadQuer yFont(dpy, "fixed"); }

width = 400; height = 400;

win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), /* display, parent */ 0,0, /* x, y: the window manager will place the window elsewhere */ width, height, /* width, height */ 2, border, /* border width & colour, unless you have a window manager */ background); /* background colour */

/* create the pen to draw lines with */ values.line_width = 1; values.line_style = LineSolid; values.font = font->fid; pen = XCreateGC(dpy, win, GCForeground|GCLineWidth|GCLineStyle|GCFont,&values);

/* tell the display server what kind of events we would like to see */ XSelectInput(dpy, win, ButtonPressMask|StructureNotifyMask|ExposureMask);

/* okay, put the window on the screen, please */ XMapWindow(dpy, win);

*width_r = width; *height_r = height; *font_r = font;

return pen; }

int main_loop(Display *dpy, XFontStruct *font, GC pen, int width, int height, char *text){ int text_width; int textx, texty; XEvent ev;

text_width = XTextWidth(font, text, strlen(text));

/* as each event that we asked about occurs, we r espond. */ while(1){ XNextEvent(dpy, &ev); switch(ev.type){ case Expose: XDrawLine(dpy, ev.xany.window, pen, 0, 0, width/2-text_width/2, height/2); XDrawLine(dpy, ev.xany.window, pen, width, 0, width/2+text_width/2, height/2); XDrawLine(dpy, ev.xany.window, pen, 0, height, width/2-text_width/2, height/2); XDrawLine(dpy, ev.xany.window, pen, width, height, width/2+text_width/2, height/2); textx = (width - text_width)/2;

texty = (height + font->ascent)/2; XDrawString(dpy, ev.xany.window, pen, textx, texty, text, strlen(text)); break; case ConfigureNotify: if (width != ev.xconfigure.width || height != ev.xconfigure.height) { width = ev.xconfigure.width; height = ev.xconfigure.height; XClearWindow(dpy, ev.xany.window); printf("Size changed to: %d by %d\n", width, height); } break; case ButtonPress: XCloseDisplay(dpy); return 0; } } }

int main(int argc, char ** argv){ int width, height; Display *dpy; GC pen; XFontStruct *font;

/* First connect to the display server */ dpy = XOpenDisplay(NULL); if (!dpy) {fprintf(stderr, "unable to connect to display\n");return 7;} pen = setup(dpy, argc, argv, &width, &height, &font); return main_loop(dpy, font, pen, width, height, "Hi!"); }

Xlib tutorial part 6 -- user preferences This section is about obeying the user's preferences. It's not going to be complete. This is more a sample of what we can do. First is a couple of helper functions. They read a value from the database of user preferences (of type XrmDatabase) and allocate the correct object from that. getColour allocates a colour based on what's in that database, and getFont allocates a font, both falling back to a default value (def) if for some reason there's nothing in the database, or we can't allocate it, such as if the colour name was not a colour that rgb.txt recognized, or the font name was not valid.

... unsigned long getColour(Display *dpy, XrmDatabase db, char *name, char *cl, char *def){ XrmValue v; XColor col1, col2; Colormap cmap = DefaultColormap(dpy, DefaultScreen(dpy)); char * type;

if (XrmGetResource(db, name, cl, &type, &v) && XAllocNamedColor(dpy, cmap, v.addr, &col1, &col2)) { } else { XAllocNamedColor(dpy, cmap, def, &col1, &col2); } return col2.pixel; }

XFontStruct *getFont(Display *dpy, XrmDatabase db, char *name, char *cl, char *def){

XrmValue v; char * type; XFontStruct *font = NULL;

if (XrmGetResource(db, name, cl, &type, &v)){ if (v.addr) font = XLoadQuer yFont(dpy, v.addr); } if (!font) { if (v.addr) fprintf(stderr, "unable to load preferred font: %s using fixed ", v.addr); else fprintf(stderr, "unable to find preferred font "); font = XLoadQuer yFont(dpy, def); } return font; } ...

The colours can actually be in the format #rrggbb where rrggbb ar e hexadecimals. You can find fonts by running xlsfonts. For portions of the name, you can use *. So -*-helvetica-*-normal-*14-* should pick a 14 point normal (not italic) helvetica font.

These helper functions are called like this:

... background = getColour(dpy, "DarkGr een"); db, "xtut6.background", "xtut6.BackGround",

border = getColour(dpy, db, "xtut6.border", "xtut6.Border", "LightGreen"); values.foreground = getColour(dpy, "Red"); db, "xtut6.foreground", "xtut6.ForeGround",

font = getFont(dpy, db, "xtut6.font", "xtut6.Font", "fixed"); ...

The 3rd argument is the full description of the preference's name. The 4th argument is what's called the resource's class. This is a fallback so that we can many prefer ences of the same class that can all be set to the same thing. We'll likely see more about this in a later lesson.

To initialize the database, we need to do two things. ... static XrmOptionDescRec xrmTable[] = { {"-bg", "*background", XrmoptionSepArg, NULL}, {"-fg", "*foreground", XrmoptionSepArg, NULL}, {"-bc", "*bordercolour", XrmoptionSepArg, NULL}, {"-font", "*font", XrmoptionSepArg, NULL}, }; ... XrmDatabase db;

XrmInitialize(); db = XrmGetDatabase(dpy); XrmParseCommand(&db, xrmTable, sizeof(xrmTable)/sizeof(xrmTable[0]), "xtut6", &argc, argv); ...

The xrmTable is a list that explains what to do with command line options. In this case, if it find -bg on the command line, the following ar gument should be placed in the database to say what the background colour should be. We could have used a line like:

... {"-bg", "xtut6.background", XrmoptionSepArg, NULL}, ...

but we could expect to have problems then.

One final change, we've added another bit to the XSelectInput line and changed the switch statement to respond to ButtonRelease instead of ButtonPress. This is so that the window doesn't disappear while the button is still depressed and the ButtonRelease event go to whatever window was underneath.

Things to try: Try passing names of various colours on the command line. Try changing the resource (preference) names and still get correct functioning.

Think about some other things that could be specified on the command line. Try using the man pages or other documentation to figure out how to set it.

Xlib tutorial part 7 -- FontSets

This section is for dealing with languages that require multiple fonts. Depending on how your computer is set up it may not work properly. First thing that we must do is make sure that XLib knows what locale we are using.

... #include ... setlocale(LC_ALL, getenv("LANG")); ...

That should set up various locale specific pieces. Make sure that the LANG environment variable is set to something suitable for your system. If you are using a system that supports it you can set it to en_CA.utf8. A RHEL 4 box will support that without complaint. On the other hand my debian 4.0 seems to cough; although, I may have it set up wrong.

Next, let's start talking about FontSets. Rather than, as we did starting in section 4, calling XLoadQueryFont and getting back an XFontStruct pointer, we'll now call XCreateFontSet and get back a XFontSet, so one of the first changes is everywhere we had XFontStruct * we replace that with XFontSet.

Of course, if that's all we did, our compiler would complain. We need to change a few calls. Let's start with the getFont helper function.

... XFontSet getFont(Display *dpy, XrmDatabase db, char *name,

char *cl, char *def){ XrmValue v; char * type; XFontSet font = NULL; int nmissing; char **missing; char *def_string;

if (XrmGetResource(db, name, cl, &type, &v)){ if (v.addr) font = XCreateFontSet(dpy, v.addr, &missing, &nmissing, &def_string); } if (!font) { if (v.addr) fprintf(stderr, "unable to load preferred font: %s using fixed ", v.addr); else fprintf(stderr, "couldn't figure out preferr ed font "); font = XCreateFontSet(dpy, def, &missing, &nmissing, &def_string); } XFreeStringList(missing); return font; } ...

It also changes how we dealing with creating a pen. We can no longer get a font id from the returned value.

... /*values.font = font->fid; */ pen = XCreateGC(dpy, win, GCForeground|GCLineWidth|GCLineStyle,&values); ...

Instead, we will call Xutf8DrawString() to respond to Expose events.

... Xutf8DrawString(dpy, ev.xany.window, font, pen, textx, texty, text, strlen(text)); ...

And we'll have to update XTextWdith() with Xutf8TextEscapement() and calculate the font ascent for the FontSet.

... text_width = Xutf8TextEscapement(font, text, strlen(text)); font_ascent = 0; nfonts = XFontsOfFontSet(font, &fonts, &font_names);

for(j = 0; j < nfonts; j += 1){ if (font_ascent < fonts[j]->ascent) font_ascent = fonts[j]->ascent; printf("Font: %s ", font_names[j]); } ...

The last two changes are passing setting the window name at the top of the window and the text to display in the center of the window as an argument from the command line.

... Xutf8SetWMProperties(dpy, win, "XTut7", "xtut7", argv, argc, NULL, NULL, NULL); ... if (argv[1] && argv[ 1][0]) text = argv[1]; return main_loop(dpy, font, pen, width, height, text); ...

Download the complete code

Things to try: Try various utf8 locales and see what happens.

Try passing in various texts in various encodings, changing the code to read from the command line might make it easier to pass in some encodings. You can find texts in every imaginable encoding on the internet. Sample Code ==================================================================== #include #include #include #include #include #include #include

static XrmOptionDescRec xrmTable[] = { {"-bg", "*background", XrmoptionSepArg, NULL}, {"-fg", "*foreground", XrmoptionSepArg, NULL}, {"-bc", "*bordercolour", XrmoptionSepArg, NULL}, {"-font", "*font", XrmoptionSepArg, NULL}, };

unsigned long getColour(Display *dpy, XrmDatabase db, char *name, char *cl, char *def){ XrmValue v; XColor col1, col2; Colormap cmap = DefaultColormap(dpy, DefaultScreen(dpy)); char * type;

if (XrmGetResource(db, name, cl, &type, &v) && XAllocNamedColor(dpy, cmap, v.addr, &col1, &col2)) { } else { XAllocNamedColor(dpy, cmap, def, &col1, &col2); } return col2.pixel; }

XFontSet getFont(Display *dpy, XrmDatabase db, char *name, char *cl, char *def){ XrmValue v; char * type; XFontSet font = NULL; int nmissing; char **missing; char *def_string;

if (XrmGetResource(db, name, cl, &type, &v)){ if (v.addr) font = XCreateFontSet(dpy, v.addr, &missing, &nmissing, &def_string); } if (!font) { if (v.addr) fprintf(stderr, "unable to load preferred font: %s using fixed\n", v.addr);

else fprintf(stderr, "couldn't figure out preferr ed font\n"); font = XCreateFontSet(dpy, def, &missing, &nmissing, &def_string); } XFreeStringList(missing); return font; }

GC setup(Display * dpy, int argc, char ** argv, int *width_r, int *height_r, XFontSet *font_r){ int width, height; unsigned long background, border; Window win; GC pen; XGCValues values;

XFontSet font; XrmDatabase db;

XrmInitialize(); db = XrmGetDatabase(dpy); XrmParseCommand(&db, xrmTable, sizeof(xrmTable)/sizeof(xrmTable[0]), "xtut7", &argc, argv);

font = getFont(dpy, db, "xtut7.font", "xtut7.Font", "fixed"); background = getColour(dpy, "DarkGr een"); db, "xtut7.background", "xtut7.BackGround",

border = getColour(dpy, db, "xtut7.border", "xtut7.Border", "LightGreen"); values.foreground = getColour(dpy, "Red"); db, "xtut7.foreground", "xtut7.ForeGround",

width = 400; height = 400;

win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), /* display, parent */ 0,0, /* x, y: the window manager will place the window elsewhere */ width, height, /* width, height */ 2, border, /* border width & colour, unless you have a window manager */ background); /* background colour */

Xutf8SetWMProperties(dpy, win, "XTut7", "xtut7", argv, argc, NULL, NULL, NULL);

/* create the pen to draw lines with */ values.line_width = 1; values.line_style = LineSolid; /*values.font = font->fid; */ pen = XCreateGC(dpy, win, GCForeground|GCLineWidth|GCLineStyle,&values);

/* tell the display server what kind of events we would like to see */ XSelectInput(dpy, ButtonPressMask|ButtonReleaseMask|StructureNotifyMask|ExposureMask); win,

/* okay, put the window on the screen, please */ XMapWindow(dpy, win);

*width_r = width; *height_r = height; *font_r = font;

return pen; }

int main_loop(Display *dpy, XFontSet font, GC pen, int width, int height, char *text){ int text_width; int textx, texty; XEvent ev; int font_ascent; XFontStruct **fonts; char **font_names; int nfonts; int j;

printf("%s:%d\n", text, strlen(text));

text_width = Xutf8TextEscapement(font, text, strlen(text)); font_ascent = 0; nfonts = XFontsOfFontSet(font, &fonts, &font_names); for(j = 0; j < nfonts; j += 1){ if (font_ascent < fonts[j]->ascent) font_ascent = fonts[j]->ascent; printf("Font: %s\n", font_names[j]); }

/* as each event that we asked about occurs, we r espond. */ while(1){ XNextEvent(dpy, &ev); switch(ev.type){ case Expose: if (ev.xexpose.count > 0) break; XDrawLine(dpy, ev.xany.window, pen, 0, 0, width/2-text_width/2, height/2); XDrawLine(dpy, ev.xany.window, pen, width, 0, width/2+text_width/2, height/2); XDrawLine(dpy, ev.xany.window, pen, 0, height, width/2-text_width/2, height/2); XDrawLine(dpy, ev.xany.window, pen, width, height, width/2+text_width/2, height/2); textx = (width - text_width)/2; texty = (height + font_ascent)/2; Xutf8DrawString(dpy, ev.xany.window, font, pen, textx, texty, text, strlen(text));

break; case ConfigureNotify: if (width != ev.xconfigure.width || height != ev.xconfigure.height) { width = ev.xconfigure.width; height = ev.xconfigure.height; XClearWindow(dpy, ev.xany.window); } break; case ButtonRelease: XCloseDisplay(dpy); return 0; } } }

int main(int argc, char ** argv){ int width, height; Display *dpy; GC pen; XFontSet font; char *text = "Hello World ! "; setlocale(LC_ALL, getenv("LANG"));

/* First connect to the display server */

dpy = XOpenDisplay(NULL); if (!dpy) {fprintf(stderr, "unable to connect to display\n");return 7;} pen = setup(dpy, argc, argv, &width, &height, &font); if (argv[1] && argv[ 1][0]) text = argv[1]; return main_loop(dpy, font, pen, width, height, text); }

Xlib tutorial part 9 -- Buttons Hello, welcome to section 9 of this Xlib tutorial. In this lesson we're going to start creating buttons for your users to press. We're going to build on where we left off last lesson with XDrawString16. Also we're going to start encapsulating the code that surrounds objects in our window.

The place to start is our main loop. It's quite different. The code is below. The first thing to notice is there is now no drawing code inside the main loop anymor e. It's been completely abstracted away. Instead, all the parts of the switch statement just dispatch to something called a Button. The second thing to notice is that the font info, GC, width and height and the text to draw are no longer passed. Instead we have something called an XContext.

... int main_loop(Display *dpy, XContext context){ XEvent ev;

/* as each event that we asked about occurs, we r espond. */ while(1){ Button *button = NULL; XNextEvent(dpy, &ev); XFindContext(ev.xany.display, ev.xany.window, context, (XPointer*)&button); switch(ev.type){ /* configure notify will only be sent to the main window */ case ConfigureNotify: if (button) buttonConfigure(button, &ev); break; /* expose will be sent to both the button and the main window */

case Expose: if (ev.xexpose.count > 0) break; if (button) buttonExpose(button, &ev); break;

/* these three events will only be sent to the button */ case EnterNotify: if (button) buttonEnter(button, &ev); break; case LeaveNotify: if (button) buttonLeave(button, &ev); break; case ButtonRelease: if (button && button->buttonRelease) button->buttonRelease(button->cbdata); break; } } } ...

An XContext can be treated just like a hash table, the only proviso is that you can only use XID's (windows, pixmaps, GContexts, ...) as the key. The XContext is designed specifically for dispatching to the appropriate object when an event comes in. Call the XFindContext function with the context and the window id, and you get back what you saved. We'll see the call to XSaveContext a little later.

The rest of the changes to the code might make more sense if you had seen how Button is defined.

... typedef void (*Callback)(void *cbdata);

typedef struct Button Button; struct Button { XChar2b * text; int text_width; int font_ascent; int width, height; unsigned long border, background, for eground; void *cbdata; Callback buttonRelease; }; ...

Notice that the text to display is here, its width, how tall it should be, and its colours. The buttonRelease callback and cbdata is so that our setup code can pass a function to be called when a click has happened.

So, next let's look at what happens when some of the button functions are called.

...

void buttonExpose(Button *button, XEvent *ev) { int textx, texty, len; if (!button) return; if (button->text){ len = XChar2bLen(button->text); textx = (button->width - button->text_width)/2; texty = (button->height + button->font_ascent)/2; XDrawString16(ev->xany.display, ev->xany.window, DefGC(ev->xany.display), textx, texty, button->text, len); } else { /* if there's no text draw the big X */ XDrawLine(ev->xany.display, ev->xany.window, DefGC(ev->xany.display), 0, 0, button->width, button->height); XDrawLine(ev->xany.display, ev->xany.window, DefGC(ev->xany.display), button->width, 0, 0, button->height); } } ...

This should be straightforward. DefGC is a macro, I've defined, that gets the default GC that Xlib creates for us during XOpenDisplay(). We really should have been using it from the beginning. The other thing that might be different from befor e is that we're now using the event object to get the dispaly and window, and from there the GC. The reason is that this way we know we're drawing in the window on the display that was exposed.

... #define DefGC(dpy) Def aultGC(dpy, DefaultScreen(dpy)) ...

That's a macro that expands to two macros (that are part of Xlib).

... void buttonConfigure(Button *button, XEvent *ev){ if (!button) return; if (button->width != ev->xconfigure.width || button->height != ev->xconfigure.height) { button->width = ev->xconfigure.width; button->height = ev- >xconfigure.height; XClearWindow(ev->xany.display, ev->xany.window); } } ...

buttonConfigure just records the new size of the button if it has changed. Notice that the X calls the button a window. And it is. It's a subwindow of the main appliation window. Each rectangular piece of your screen can be a window. XClearWindow() verifies that the old version of the window was cleared and we won't be drawing over text later.

...

void buttonEnter(Button *button, XEvent *ev) { XSetWindowAttributes attrs; if(!button) return; attrs.background_pixel = button->border; attrs.border_pixel = button->background; XChangeWindowAttributes(ev->xany.display, ev->xany.window, CWBackPixel|CWBorderPixel, &attrs); XClearArea(ev->xany.display, ev->xany.window, 0, 0, button->width, button->height, True); } ...

This is called when the mouse enters the button. In this case, we have it switch its border and background colours.

... void buttonLeave(Button *button, XEvent *ev) { XSetWindowAttributes attrs; if(!button) return;

attrs.background_pixel = button->background; attrs.border_pixel = button->border; XChangeWindowAttributes(ev->xany.display, ev->xany.window, CWBackPixel|CWBorderPixel, &attrs); XClearArea(ev->xany.display, ev->xany.window, 0, 0, button->width, button->height, True); } ...

and we switch them back when the mouse leaves. XChangeWindowAttributes lets us change the border and background colours of a window, among other things. XClearArea like XClearWindow clears the window to make sure the background colour is updated. It won't be until the X display server has to clear the window in the case of an expose event. In this case, we cause an expose event by passing True as the last argument of XClearArea.

So now that we've seen what happens when events come in to the button, let's consider how this button got created in the first place.

... void createButton(Display *dpy, Window par ent, char *text, XFontStruct *font, int x, int y, int width, int height, unsigned long foreground, unsigned long background, unsigned long border, XContext ctxt, Callback callback, void *cbdata){ ...

That's a large number of arguments. From what we've talked about in previous lessons and from the mention of the XContext and Callback from before, you should be able to understand what each of them is for.

... Button *button; Window win; int strlength = strlen(text);

win = XCreateSimpleWindow(dpy, parent, x, y, width, height, 2, border, background); /* borderwidth, border and background colour */ if (!win) { fprintf(stderr, "unable to create a subwindow "); exit(31); }

button = calloc(sizeof(*button), 1); if (!button){ fprintf(stderr, "unable to allocate any space, dieing "); exit(32); } ...

Here's where the button object is created. If we were were in C++ or some other language with direct support for objects we would like say button = new Button( ...args...); but we're using C right now. So the code below is setting all the fields.

...

button->font_ascent = font->ascent;

button->text = malloc(sizeof(*button->text) * (strlength+1)); if (!button->text){ fprintf(stderr, "unable to allocate any string space, dieing "); exit(32); } strlength = utf8toXChar2b(button->text, strlength, text, strlength); button->text_width = XTextWidth16(font, button->text, strlength); button->buttonRelease = callback; button->cbdata = cbdata; button->width = width; button->height = height; button->background = background; button->foreground = for eground; button->border = border;

XSelectInput(dpy, win, ButtonPressMask|ButtonReleaseMask|StructureNotifyMask|ExposureMask

|LeaveWindowMask|EnterWindowMask);

XSaveContext(dpy, win, ctxt, (XPointer)button); XMapWindow(dpy, win); } ...

Notice the new Masks being sent to XSelectInput(). We want to know about a entr y and exit from the button so that we can highlight it when the user mouses over it.

The other thing to see here is the call to XSaveContext(). Contexts are provided by Xlib. As mentioned above they can be used just like hash tables for Window ids which is exactly what we're using them here for.

What's left that's changed? Our setup() function.

... XContext setup(Display * dpy, int argc, char ** argv){ static XrmOptionDescRec xrmTable[] = { {"-bg", "*background", XrmoptionSepArg, NULL}, {"-fg", "*foreground", XrmoptionSepArg, NULL}, {"-bc", "*bordercolour", XrmoptionSepArg, NULL}, {"-font", "*font", XrmoptionSepArg, NULL}, }; Button *mainwindow;

Window win; XGCValues values;

XFontStruct * font; XrmDatabase db;

XContext ctxt;

ctxt = XUniqueContext();

mainwindow = calloc(sizeof(*mainwindow), 1); ...

I've moved the xrmTable into the setup function. It's only ever used in this function and it doesn't really matter where it is, but might as well not pollute the file level namespace.

The above code also treats the main window as a button, and creates a context for all our various windows. (In this case 2, but more in subsequent sections). Notice we create a new object to store the mainwindow.

...

XrmInitialize(); db = XrmGetDatabase(dpy); XrmParseCommand(&db, xrmTable, sizeof(xrmTable)/sizeof(xrmTable[0]),

"xtut9", &argc, argv);

font = getFont(dpy, db, "xtut9.font", "xtut9.Font", "fixed"); mainwindow->background = getColour(dpy, "xtut9.BackGround", "DarkGreen"); mainwindow->border = getColour(dpy, "LightGreen"); db, "xtut9.background",

db, "xtut9.border", "xtut9.Border",

mainwindow->foreground = values.foreground = getColour(dpy, db, "xtut9.foreground", "xtut9.ForeGround", "Red");

mainwindow->width = 400; mainwindow->height = 400; ...

The above code should all be straight forward. The only major differnce from earlier is that we're storing the colours in the mainwindow object.

...

win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), /* display, parent */ 0,0, /* x, y: the window manager will place the window elsewhere */ mainwindow->width, mainwindow->height, /* width, height */ 2, mainwindow->border, /* border width & colour, unless you have a window manager */ mainwindow->background); /* background colour */

Xutf8SetWMProperties(dpy, win, "XTut9", "xtut9", argv, argc, NULL, NULL, NULL);

/* make the default pen what we want */ values.line_width = 1; values.line_style = LineSolid; values.font = font->fid;

XChangeGC(dpy, DefGC(dpy), GCForeground|GCLineWidth|GCLineStyle|GCFont,&values); ...

We're using the default GC again and setting it to our prefered configuration. Whenever possible, reuse things like GCs since it uses less resources on the server.

To make sense of the next section, we need to introduce a new structure definition and callback function (for when the button is pressed).

... (At top level)

typedef struct exitInfo ExitInfo; struct exitInfo { Display *dpy; XFontStruct *font;

};

void exitButton(void *cbdata){ ExitInfo *ei = (ExitInfo*)cbdata; XFreeFont(ei->dpy, ei->font); XCloseDisplay(ei->dpy); exit(0); }

...

In the exit function we free the font. The X display server will do that for us anyway when we close the connection, but it's good practice to think about making sure we free things when we're done with them.

So, now that we have those definitions the following should make sense.

...

{ ExitInfo *exitInfo; exitInfo = malloc(sizeof(*exitInfo)); exitInfo->dpy = dpy; exitInfo->font = font; createButton(dpy, win, "Exit", font, /*display text font */

mainwindow->width/2-40, 17, 80, (font->ascent+font->descent)*2,/*xywh*/ /* colours */ mainwindow->foreground, mainwindow->background, mainwindow->border, ctxt, exitButton, exitInfo); } ... /* context & callback info */

Create the callback data, and then create the button and give it the callback data.

When the button in our main window is created, we can finish the setup call.

...

/* tell the display server what kind of events we would like to see */ XSelectInput(dpy, win, StructureNotifyMask|ExposureMask); /* okay, put the window on the screen, please */ XMapWindow(dpy, win);

/* save the useful information about the window */ XSaveContext(dpy, win, ctxt, (XPointer)mainwindow);

return ctxt; } ...

XSelectInput() takes a few less masks than earlier since we no longer have to wonder about button clicks. And we save the mainwindow the same way as we saved the button with XSaveContext() so it can be retrieved in our main loop.

For completeness, here's the XChar2bLen that was called in buttonExpose.

...

int XChar2bLen(XChar2b *string){ int j = 0; for(j = 0; string[j].byte1 || string[j].byte2; j ++ ) ; return j; } ...

And here's our new main().

...

int main(int argc, char ** argv){ Display *dpy; XContext ctxt;

/* First connect to the display server */ dpy = XOpenDisplay(NULL); if (!dpy) {fprintf(stderr, "unable to connect to display ");return 7;} ctxt = setup(dpy, argc, argv); return main_loop(dpy, ctxt); } ...

We're no longer passing as much information around, just a reference to the hashtable with the display connection.

Change the way the colours change when the mouse is over the button. Make the button just a bit of underlined text like a web browser does. Use XCreateFontCursor() and XDefineCursor( ) to make the cursor in the button different from the cursor in the rest of the application window.

Sample Code: ============================================================== #include #include #include #include #include #include

#define DefGC(dpy) Def aultGC(dpy, DefaultScreen(dpy))

typedef void (*Callback)(void *cbdata);

typedef struct Button Button; struct Button { XChar2b * text; int text_width; int font_ascent; int width, height; unsigned long border, background, for eground; void *cbdata; Callback buttonRelease; };

int XChar2bLen(XChar2b *string){ int j = 0; for(j = 0; string[j].byte1 || string[j].byte2; j ++ ) ; return j; }

int utf8toXChar2b(XChar2b *output_r, int outsize, const char *input, int inlen){ int j, k; for(j =0, k=0; j < inlen && k < outsize; j ++){ unsigned char c = input[j]; if (c < 128) { output_r[k].byte1 = 0; output_r[k].byte2 = c; k++; } else if (c < 0xC0) { /* we're inside a character we don't know */ continue; } else switch(c&0xF0){ case 0xC0: case 0xD0: /* two bytes 5+6 = 11 bits */ if (inlen < j+1){ return k; } output_r[k].byte1 = (c&0x1C) >> 2; j++; output_r[k].byte2 = ((c&0x3) 2); c = input[j]; j++; output_r[k].byte2 = ((c&0x3) dpy, ei->font); XCloseDisplay(ei->dpy); exit(0); }

void createButton(Display *dpy, Window par ent, char *text, XFontStruct *font, int x, int y, int width, int height, unsigned long foreground, unsigned long background, unsigned long border, XContext ctxt, Callback callback, void *cbdata){ Button *button; Window win;

int strlength = strlen(text);

win = XCreateSimpleWindow(dpy, parent, x, y, width, height, 2, border, background); /* borderwidth, border and background colour */ if (!win) { fprintf(stderr, "unable to create a subwindow\n"); exit(31); }

button = calloc(sizeof(*button), 1); if (!button){ fprintf(stderr, "unable to allocate any space, dieing\n"); exit(32); }

button->font_ascent = font->ascent;

button->text = malloc(sizeof(*button->text) * (strlength+1)); if (!button->text){ fprintf(stderr, "unable to allocate any string space, dieing\n"); exit(32); } strlength = utf8toXChar2b(button->text, strlength, text, strlength); button->text_width = XTextWidth16(font, button->text, strlength); button->buttonRelease = callback;

button->cbdata = cbdata; button->width = width; button->height = height; button->background = background; button->foreground = for eground; button->border = border;

XSelectInput(dpy, win, ButtonPressMask|ButtonReleaseMask|StructureNotifyMask|ExposureMask |LeaveWindowMask|EnterWindowMask);

XSaveContext(dpy, win, ctxt, (XPointer)button); XMapWindow(dpy, win); }

XContext setup(Display * dpy, int argc, char ** argv){ static XrmOptionDescRec xrmTable[] = { {"-bg", "*background", XrmoptionSepArg, NULL}, {"-fg", "*foreground", XrmoptionSepArg, NULL}, {"-bc", "*bordercolour", XrmoptionSepArg, NULL}, {"-font", "*font", XrmoptionSepArg, NULL}, }; Button *mainwindow; Window win; XGCValues values;

XFontStruct * font; XrmDatabase db;

XContext ctxt;

ctxt = XUniqueContext();

mainwindow = calloc(sizeof(*mainwindow), 1);

XrmInitialize(); db = XrmGetDatabase(dpy); XrmParseCommand(&db, xrmTable, sizeof(xrmTable)/sizeof(xrmTable[0]), "xtut9", &argc, argv);

font = getFont(dpy, db, "xtut9.font", "xtut9.Font", "fixed"); mainwindow->background = getColour(dpy, "xtut9.BackGround", "DarkGreen"); mainwindow->border = getColour(dpy, "LightGreen"); db, "xtut9.background",

db, "xtut9.border", "xtut9.Border",

mainwindow->foreground = values.foreground = getColour(dpy, db, "xtut9.foreground", "xtut9.ForeGround", "Red");

mainwindow->width = 400; mainwindow->height = 400;

win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), /* display, parent */ 0,0, /* x, y: the window manager will place the window elsewhere */ mainwindow->width, mainwindow->height, /* width, height */ 2, mainwindow->border, /* border width & colour, unless you have a window manager */ mainwindow->background); /* background colour */

Xutf8SetWMProperties(dpy, win, "XTut9", "xtut9", argv, argc, NULL, NULL, NULL);

/* make the default pen what we want */ values.line_width = 1; values.line_style = LineSolid; values.font = font->fid;

XChangeGC(dpy, DefGC(dpy), GCForeground|GCLineWidth|GCLineStyle|GCFont,&values);

{ ExitInfo *exitInfo; exitInfo = malloc(sizeof(*exitInfo)); exitInfo->dpy = dpy; exitInfo->font = font;

createButton(dpy, win, "Exit", font, /*display text font */ mainwindow->width/2-40, 17, 80, (font->ascent+font->descent)*2,/*xywh*/ /* colours */ mainwindow->foreground, mainwindow->background, mainwindow->border, ctxt, exitButton, exitInfo); } /* context & callback info */

/* tell the display server what kind of events we would like to see */ XSelectInput(dpy, win, StructureNotifyMask|ExposureMask); /* okay, put the window on the screen, please */ XMapWindow(dpy, win);

/* save the useful information about the window */ XSaveContext(dpy, win, ctxt, (XPointer)mainwindow);

return ctxt; }

void buttonExpose(Button *button, XEvent *ev) { int textx, texty, len; if (!button) return; if (button->text){ len = XChar2bLen(button->text); textx = (button->width - button->text_width)/2;

texty = (button->height + button->font_ascent)/2; XDrawString16(ev->xany.display, ev->xany.window, DefGC(ev->xany.display), textx, texty, button->text, len); } else { /* if there's no text draw the big X */ XDrawLine(ev->xany.display, ev->xany.window, DefGC(ev->xany.display), 0, 0, button->width, button->height); XDrawLine(ev->xany.display, ev->xany.window, DefGC(ev->xany.display), button->width, 0, 0, button->height); } } void buttonConfigure(Button *button, XEvent *ev){ if (!button) return; if (button->width != ev->xconfigure.width || button->height != ev->xconfigure.height) { button->width = ev->xconfigure.width; button->height = ev- >xconfigure.height; XClearWindow(ev->xany.display, ev->xany.window); } }

void buttonEnter(Button *button, XEvent *ev) { XSetWindowAttributes attrs; if(!button) return; attrs.background_pixel = button->border; attrs.border_pixel = button->background;

XChangeWindowAttributes(ev->xany.display, ev->xany.window, CWBackPixel|CWBorderPixel, &attrs); XClearArea(ev->xany.display, ev->xany.window, 0, 0, button->width, button->height, True); } void buttonLeave(Button *button, XEvent *ev) { XSetWindowAttributes attrs; if(!button) return; attrs.background_pixel = button->background; attrs.border_pixel = button->border; XChangeWindowAttributes(ev->xany.display, ev->xany.window, CWBackPixel|CWBorderPixel, &attrs); XClearArea(ev->xany.display, ev->xany.window, 0, 0, button->width, button->height, True); }

int main_loop(Display *dpy, XContext context){ XEvent ev;

/* as each event that we asked about occurs, we r espond. */ while(1){ Button *button = NULL; XNextEvent(dpy, &ev); XFindContext(ev.xany.display, ev.xany.window, context, (XPointer*)&button); switch(ev.type){

/* configure notify will only be sent to the main window */ case ConfigureNotify: if (button) buttonConfigure(button, &ev); break; /* expose will be sent to both the button and the main window */ case Expose: if (ev.xexpose.count > 0) break; if (button) buttonExpose(button, &ev); break;

/* these three events will only be sent to the button */ case EnterNotify: if (button) buttonEnter(button, &ev); break; case LeaveNotify: if (button) buttonLeave(button, &ev); break; case ButtonRelease: if (button && button->buttonRelease) button->buttonRelease(button->cbdata); break;

} } }

int main(int argc, char ** argv){ Display *dpy; XContext ctxt;

/* First connect to the display server */ dpy = XOpenDisplay(NULL); if (!dpy) {fprintf(stderr, "unable to connect to display\n");return 7;} ctxt = setup(dpy, argc, argv); return main_loop(dpy, ctxt); }

Xlib tutorial part 11 -- Menus

This week, I'm introducing a lot of new code. You can get it here. I'm not going to show every little change since I'm not introducing much in the way of new concepts. If you have questions, post a comment below.

What I am going to do is describe some of what I was thinking when I was writing this code. And to describe how menus work in X.

I was tr ying to build a modular piece of code so that each type of object has its own .c file. So, app.c, button.c, menu.c, and menubar.c.

getResources.c and xc2b.c are similar to the same files in last week. I've added a caching system to getResources.c and a debugging function from xc2b.c. Look through the code to see what I mean, neither of them is specific to X, though it means that the getResources won't have as many X requests if you are reusing fonts and colours a lot. And you should be.

In funcs.h, I've created a C struct and a C union that describes the types of objects in the system. This is not an object oriented system, per say, but has some of it's advantages. If you want an object oriented system in C you can move up to Xt, or switch to C++ which has linguistic support for object oriented programming. Even better is Lisp, if you're willing to really change languages. Much of what I describe as far as understanding the protocol will still be applicable even if you are using another language.

In button.c, I've added some code to create a menubutton. Menubuttons are just like buttons except they live inside of other objects (menus or menubars).

Inside menubar.c is an object that that represents the menubar across the top of the application which is in app.c. Most of what's in app.c is an abstraction of part of what used to be in the main .c file.

Author : Mukesh Yadav

Page 95

Inside menu.c is the meat for this week. Menu windows are special types of windows. They are not at all like the button from last week, or the menubar from this week. Those windows were parented inside the main app window, which in turn, the window manager decorated. Menus are top level windows that we don't want decorated. So, when we create a menu, we need to pass some things to the window creation call that we have not yet done. We need to use the XSetWindowAttributes structure and XCreateWindow()

Window newMenu(char *progname, Display *dpy, XrmDatabase db, XContext ctxt, char *name){ Window win; Menu *menu; Window parent = DefaultRootWindow(dpy); XSetWindowAttributes xswa;

xswa.save_under = True; xswa.override_redirect = True;

menu = calloc(sizeof(*menu), 1); if (!menu){ fprintf(stderr, "unable to allocate any space for %s menu, dieing ", name); exit(32); } menu->ctxt = ctxt; menu->funcs = MenuFuncs; win = XCreateWindow(dpy, parent, 0, 0, 100, 100,

0, CopyFromParent, CopyFromParent, CopyFromParent, CWOverrideRedirect|CWSaveUnder , &xswa);

XSelectInput(dpy, win, StructureNotifyMask); XSaveContext(dpy, win, ctxt, (XPointer)menu); return win; }

Notice the xswa.save_under = True and xswa.override_redir ect=True. Then the CWOverrideRedirect|CWSaveUnder, &xswa arguments to XCreateWindow. What that did was tell the X server that we want to set the override redirect (IE don't tell the window manager that about this window, keep it a secret between us and the X server) and we suggest saving anything this windw obsures, because this is window probably won't be up very long. If the X server supports save under, it means less expose events for other X clients.

As a side note, when I was first learning to program for X, I thought this override redir ect bit was great and gave me a lot of power. I went off on a tangent and wrote a few programmes that used it for the oddest things. Eventually I discovered that they really didn't work very well with the established programmes and window managers. They just weren't right. Learn from my mistake. Only use override redirect for menus. It's what it's for. Anything else and you're misusing it. At least until you've learned much more than I have to teach you. And if you have, you can stop reading my tutorial.

Anyway, I'm not going to harp on this anymore. We've completed our quick tour of the code changes for this week.

Things to try: There's a bug in the code. Try adding more items to the help menu and you'll probably find it. See if you can fix the bug.

See if you can correct where the menus are popping up. They seem to pop up where you click, not under the menubar button. Try getting the menus to respond more like a normal menubar menu

Xlib tutorial part 12 -- Better Menus

After last week, I thought we should take it a little easier. I might have some space this time to explain a few things I didn't last week. You may notice that the menus line up nicely this time. Most of that is due to a few lines in button.c:

static void menuBarButtonPress(Block *block, XEvent *ev){ MenuBarButton *mbb = &block->menubarbutton; int x, y; x = ev->xbutton.x_root - ev->xbutton.x; y = ev->xbutton.y_root - ev->xbutton.y + 20; XMoveWindow(ev->xbutton.display, mbb->menu, x, y); XMapWindow(ev->xbutton.display, mbb->menu); } static void menuBarButtonRelease(Block *block, XEvent *ev){ MenuBarButton *mbb = &block->menubarbutton; XUnmapWindow(ev->xbutton.display, mbb->menu); }

Base on the name, you should be able to guess that I hooked it up so that it will be called when our menu bar buttons are pressed. There's a type (MenuBarButton) that is new from last week. It remembers where its button window actually is (thanks to the menubar calling resizeBlock()) and causes the menu to show up relative to it when the button is pressed rather than where the mouse pointer is at the time.

void resizeBlock(Block *block, Window win, int width, int height, XEvent *ev){

XEvent temp; XResizeWindow(ev->xany.display, win, width, height); if (!(block && block->funcs && block->funcs->configureNotify)) return; temp.xconfigure.type = ev->xany.type; temp.xconfigure.display = ev->xany.display; temp.xconfigure.window = win; temp.xconfigure.event = win; temp.xconfigure.width = width; temp.xconfigure.height = height; temp.xconfigure.border_width = 0; /* assume we never use borders */ temp.xconfigure.above = None; temp.xconfigure.override_redir ect = False; block->funcs->configureNotif y(block, &temp); }

resizeBlock() will work on any of the window blocks in our programs so far to resize them as the parent desires and to let the block know it's resized so it can do something without waiting for a round trip to the server. (The server will also tell us that the window has been resized, but if we don't wait a long chain of resizes won't take as long...)

I'm actually cheating here, creating a false X configure event and sending it. This should cause any problems, since the window is being r esized anyway. We only have to be careful we don't send events that aren't going to happen anyway, since we may get other parts of the program into odd states.

There's only one other important change, and that's to the way menubar buttons are created. Last week we just used a normal button and passed the button release event to the callback. Which

was fine except that bit of code didn't know where the menu should pop up. We've stopped callbacks f rom happening and now instead let the menubarbutton know what window it should pop up.

Since most of the code to create either button is the same, I've reused most of it and only split off parts into newButton() and newMemnuBarButton(), calling the original function with the common parts the unimaginative name of newXButton().

Things to try: There's still a bug in the code. Try adding more items to the help menu and you'll probably find it. See if you can fix the bug. The colours are still pretty lame. How might you create shading? Try getting the menus to respond even more like a normal menubar menu

CODE

Xlib tutorial part 13 -- XScope

This week, I want to start talking about debugging tools. First off get a copy of xscope. You'll have to compile it if you get it from there. For some reason my debian system did not have it installed by default and I could not find it in any of the Debian packages, you may have better luck with your system. Sometimes when working on a display program, we end up wondering what exactly our program is telling the display server. X, because it is a network protocol, makes it easy to listen in on what is being sent across the wire. xscope is a program for doing just that. To use it, you set it up as though it's a server and when a client connects to it, it connects to the actual server. It then dumps anything that the client and actual server say to each other onto its standard output. Each request that the client sends, and each event that the server sends are printed in a (not particularily) easy to read format. First let's make sure it's all set up. To compile the version I gave a link to, I had to edit the file scope.c and add a line near the beginning #include . For whatever reason, MAXHOSTNAMELEN was used, but not defined in scope.c.

I also had to ./configure to create the Makefile, then run make.

Once I had the executable, I discovered that my X server by default hasn't been listening to port 6000. (The normal port for X). I found that quite odd, so I had to restart the server. Then I had to run xhost + so that any client can access the server. (Note, if you're on the internet, this is likely a bad idea. Running xhost + turns off the X security system. Or at least one of them.

I could now start xscope.

Finally, I had to set the DISPLAY environment variable to :1.0 instead of the standard :0.0 in the terminal I want things to run through xscope.

Hopefully, you won't have quite so many problems setting up xscope for your system. But my description of what I did should help you along if you do have problems.

Okay, that's out of the way. Let's take a look at some sample output of xscope. I'm running xtut12 from the last lesson. 0.00: Client --> 12 bytes byte-order: LSB first major-version: 000b minor-version: 0000 0.00: 308 bytes