Hello, and welcome to part 15 of this Xlib tutorial.
This week we're going to look at making the keyboard input. You can get the codeI've reduced the code to just what's necessary to demonstrate some new concepts. We'll hook up the buttons and menus in a later lesson.
/* first include the standard headers that we're likely to need */
#include <X11/Xlib.h<
#include <X11/Xutil.h<
#include <X11/Xresource.h<
#include <stdio.h<
#include <stdlib.h<
#include <string.h<
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 */
dpy = XOpenDisplay(NULL);
if (!dpy) {fprintf(stderr, "unable to connect to displayn");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 */
This should all look familiar so far. The next line won't look very different either. Note that we want to know when keyboard keys are changed.
/* tell the display server what kind of events we would like to see */
XSelectInput(dpy, win, ButtonPressMask|StructureNotifyMask|KeyPressMask|KeyReleaseMask|KeymapStateMask);
/* okay, put the window on the screen, please */
XMapWindow(dpy, win);
Below is the main loop. The two things that are interesting are the XRefreshKeyboardMapping() call (which is good practice to do since another process may change the mapping and the KeymapNotify notifies us that it's happened) and the XLookupString() call. The XLookupString() call turns the ev.xkey which can be anything, into a string and a key symbol. The string will be filled with whatever the key is mapped to (and can be remapped). The keysym is a slightly different thing. In this case we don't use it, but if we wanted to support the function keys, the escape key, backspace, the page up keys etc, we would have to look at the keysym and compare them to the values defined in <X11/keysymdef.h>.
/* as each event that we asked about occurs, we respond. 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 KeymapNotify:
XRefreshKeyboardMapping(&ev.xmapping);
break;
case KeyPress: break; /* ignore these */
case KeyRelease:
{
char string[25];
int len;
KeySym keysym;
len = XLookupString(&ev.xkey, string, 25, &keysym, NULL);
if (len < 0){
if (string[0] == 'r'){
string[0] = 'n';
}
fputs(string, stdout);
}
}
break;
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 %dn", width, height);
}
break;
case ButtonPress:
XCloseDisplay(dpy);
return 0;
}
}
}
That's all I'm going to do this week. Try playing around with it to see what you can get it to do. Did you notice that (assuming you're on a unix like machine) that when you type, nothing shows up until you press enter? Do you know why? I can assure you that it has nothing to do with X. But you could put a fflush(NULL) call in after the fputs() call and it will change.
Hello, and welcome to part 14 of this Xlib tutorial.
This week we're going to look at making the buttons prettier by using gradients, similar to what you see in MicroSoft OutLook. You can get the codeAgain we have to do some mathematics. The idea is to make the button look like it bulges out from the surrounding objects. If we imagine a cylinder, lying horizontal, half of it visible, sticking out with light shining from above we immediately realize the top half of the cylinder will be a lighter colour than the bottom half. On top of that, the colour will change from brightest according to a straight forward mathematical progression to darker and darker until it is at its darkest at the bottom.
This sounds somewhat like a quadratic equation, but I played with using a quadratic equation to create the colours and discovered that it did not look quite right. In the end, I decided for my work, I prefered something sinusoidal: cosine actually.
Here's the code to fill in a one pixel wide pixmap with a gradient colour:
for(y = 0; y < height; y += 1){
cosine = cos(l2rads * y)/2.0; /* mute it */
col2.red = bcolour.red + diffcol.red * cosine;
col2.green = bcolour.green + diffcol.green * cosine;
col2.blue = bcolour.blue + diffcol.blue * cosine;
XAllocColor(dpy, cmap, &col2);
XSetForeground(dpy, gc, col2.pixel);
for (x = 0; x < width; x += 1){
XDrawPoint(dpy, pmap, gc, x, y);
}
}
This code doesn't work in isolation, but I thought we'd start from the meat
and work out. The cosine =
line calculates a simple gradient
brightness. Then, for each of red, green and blue we add a brightness
difference and then allocate the colour and set each pixel in the row that
colour.
Here's the start of the function that it's part of.
static Pixmap createGradPixmap(Display *dpy, int width, int height,
char *basecolour){
int x = 0, y;
double cosine, l2rads;
XColor bcolour, col2, diffcol;
Colormap cmap = DefaultColormap(dpy, DefaultScreen(dpy));
Pixmap pmap;
GC gc = DefGC(dpy);
Window win = DefaultRootWindow(dpy);
/* assume width == 1 for this iteration */
width = 1;
l2rads = M_PIl/(height);
XParseColor(dpy, cmap, basecolour, &bcolour);
diffcol.red = min(bcolour.red, 0xffff-bcolour.red);
diffcol.green = min(bcolour.green, 0xffff-bcolour.green);
diffcol.blue = min(bcolour.blue, 0xffff-bcolour.blue);
fprintf(stderr, "height %d, width %d, %s
", height, width, basecolour);
pmap = XCreatePixmap(dpy, win, width, height,
DefaultDepth(dpy, DefaultScreen(dpy)));
We are working in our default colourmap, any reasonably modern system should be able to handle enough colours. We set l2rads (length to radians) to a value such that we go through 180 degrees (1 pi radians) over the height of the button.
We then figure out which colour we're working with, and figure out the maximum difference in each of red, green and blue that we can go without going off the range. (X colours are all in the 0-0xffff(65535) range.)
and finally we create the pixmap we're going to use. The part of the
function after the loop is a simple return pmap;
In the same file (gradient.c), I have a couple of functions for managing the pixmaps (functions that will actually be called by other parts of the program). It will cache them and free the pixmaps on the display server when the last user frees it.
To use these functions, in the button object, I've added a field bgpix which initialises to 0 and changed the background field to be a string so that we can pass the string in to the getGradPixmap() function in gradient.c
In our buttonExpose method, we now, rather than just redrawing, we set the check if the bgpix is set to something other than 0, and call getGradPixmap() and set bgpix and the background pixmap of the window to it. Then, to be sure the window gets drawn we call XClearArea with an exposure of True so that it will call the expose again. That second time, we just redraw the text.
buttonConfigureNotify() is very similar, except we have to free the pixmap if the button changed size. Other changes to the button you find in the new source, I hope, will be easily understood.
The changes in mainloop.c are merely to add more insteresting things to the menus, and the changes in menu.c include a bug fix and letting the menubar also have a gradient.
Things to try
Hello, and welcome to part 14 of this Xlib tutorial.
Another long hiatus, I'm not going to claim we're back this time. We'll see. Summertime might keep me too busy.This time, I'm going to start on something a litle more fun and interesting: flower menus similar to what you saw in the first version of the Sims. They're a type of pie menu and can be quite pretty as well as functional. Download the code and give it a try so you can see visually what's happening.
First off, we need to set up a way to get to a context menu in the app. So, in app.c change the XSelectInput line to:
XSelectInput(dpy, win, StructureNotifyMask|ExposureMask);
and add a few helper functions:
Window setAppContextMenu(Display *dpy, XContext ctxt, Window w,
Window contextmenu){
App *app;
Window old;
XFindContext(dpy, w, ctxt, (XPointer *)&app);
/*printf("storing context menu %p in %p
", contextmenu, app); */
old = app->contextmenu;
app->contextmenu = contextmenu;
return old;
}
static void buttonPress(Block *block, XEvent *ev){
if (ev->xbutton.button == Button3){
App *app = &block->app;
if (!app || !app->contextmenu) return; /* oops */
/* printf("context menu is %p in %p
",app->contextmenu, app); */
pieMenuMap(app->contextmenu, ev, app->ctxt);
XUngrabPointer(ev->xbutton.display, ev->xbutton.time);
}
}
static void buttonRelease(Block *block, XEvent *ev){
if (ev->xbutton.button == Button3){
App *app = &block->app;
if (!app || !app->contextmenu) return; /* oops */
XUnmapWindow(ev->xany.display, app->contextmenu);
/* printf("context menu is %p in %p
",app->contextmenu, app); */
}
}
A point of interest in this code, is the printf's that are currently commented out. I put them there as I was testing. You can put them in so that you can see what's happening as you click on the application. Of course, just sticking in this code won't let you compile.
First we have to register the callbacks:
static struct Funcs _AppFuncs = {
appConfigureNotify,
NULL, /* leave */
NULL, /* enter */
NULL, /* expose */
buttonPress, /* button press */
buttonRelease /* button release */
};
And add the contextmenu item to the App struct.
struct App {
struct Funcs *funcs;
Window menubar; /* so we can keep it the width of the app window */
int menubarHeight;
Window contextmenu;
XContext ctxt;
int width, height;
unsigned long border, background, foreground;
};
While there, also add a reference to the setAppContextMenu()
Now, there will be some changes to menus as well, but first let's get the changes in button out of the way. We need to support buttons with centred text. Not left justitfied as we had in the earlier menus. To do that I've added a attribute set when the button is created with the unimaginative name of "center". Variables and fields with obvious names help with later debugging.
In button.c where we had been just setting textx = 0 for the menus (and
earlier had (button->width - button->text_width)/2
) we want
to be able to run it off what center is set to and rather than using an
if statement if we set center to 0 when we want the text left justified, and
1 when we want it centered, we can simply multiply the longer value by
the field.
textx = button->center*((button->width - button->text_width)/2);
Okay, now we can look at the changes in menu.c to see what is happening. First, we need to include the shape extension:
#include <X11/extensions/shape.h>
We'll also have add -lXext when we compile. The shape extension allows us to create non-rectangular windows in our application. It was an early extension in X, but unfortunately, you don't see its use very much, except in gimmicky things. I've never quite figured out why.
One of the first things to notice, is that there will be a lot of common code in creating a pie menu as a normal menu. So, I renamed the newMenu() initialization to newXMenu() and added the pointer to the data as one of the arguments. That way, we can reuse pretty much all of the function and have a new newMenu() initialization that allocates the a Menu and calls newXMenu and a new newPieMenu() initialization that does the same for PieMenus, setting the funcs to point to the pie menu methods instead of to the normal menu functions.
I've added a few changes to the menuSetSubWins, including a call to piemenuSetSubWins when calling it with a piemenu. piemenuSetSubWins just sets the menu's width to 0 so that we know it's not set up yet.
Now for the interesting math bits. It turns out that most flower menus seem to work best with ony 6 pedals in the flower. If you get too many items, they bunch too close and it's hard to pick the pedal you actually want. So, what've I done, is calculate the precise best placing for six pedals.
enum { MaxPIES = 6 };
/* these numbers are 6 points evenly spaced around a circle */
static float xs[MaxPIES] = { 0, .86, .86, 0, -.86, -.86};
static float ys[MaxPIES] = { -1, -.5, .5, 1, .5, -.5};
I worked these numbers out with sines and cosines. The xs are the sines of 0, 60, 120, 180, 240, 300, and 260. The ys are the cosines of those same angles.
Next, let's look at what happens when the application asks the menu to reveal itself. (Ie when someone right clicks on the application and app.c's buttonPress is called.)
void pieMenuMap(Window menuwin, XEvent *ev, XContext ctxt){
int j, menuwidth, menuheight;
int maxwidth = 0; /* of the subwindows */
Menu *menu = NULL;
Button**buttons;
Pixmap pmap;
GC gc;
/*printf("
RETREIVING %p %p %p %p
", ev->xany.display, menuwin, ctxt, menu); */
XFindContext(ev->xany.display, menuwin, ctxt, (XPointer *)&menu);
if (!menu) return; /* oops */
if (menu->width){
menuwidth = 2.72*menu->width;
menuheight = 3*menu->width;
XMoveWindow(ev->xany.display, menuwin,
ev->xbutton.x_root - menuwidth/2,
ev->xbutton.y_root - menuheight/2);
XMapWindow(ev->xany.display, menuwin);
return;
}
This much should be straightforward if you've read the previous parts. First we find the Menu object, and return if we don't find it. Then we readjust the menu to show up where the person clicked and map it if the menu has a valid width.
buttons = malloc(sizeof(*buttons)*menu->nsubws);
for(j =0; j < menu->nsubws; j ++ ){
Button *button = NULL;
if (XFindContext(ev->xany.display, menu->subws[j], menu->ctxt, (XPointer*)&button))
continue;
buttons[j] = button;
/*printf("button %d is %d wide and %d high
",
j, button->width, button->font_ascent); */
if (maxwidth < button->text_width + 2 * button->font_ascent);
maxwidth = button->text_width + 2 * button->font_ascent;
}
Here we collect the buttons as they were set in piemenuSetSubWins(), and as we go along calculate the widest of them.
/* width of the complete pie is 2.72 times the width/height of a piece */
menuwidth = 2.72*maxwidth;
menuheight = 3*maxwidth;
XMoveResizeWindow(ev->xany.display, menuwin,
ev->xbutton.x_root - menuwidth/2, ev->xbutton.y_root - menuheight/2,
menuwidth+1, menuheight);
for(j =0; j < MaxPIES && j < menu->nsubws; j ++ ){
XMoveResizeWindow(ev->xany.display, menu->subws[j],
(1.36+xs[j])*maxwidth-maxwidth/2, (1.5+ys[j])*maxwidth-maxwidth/2,
maxwidth, maxwidth);
}
free(buttons);
The numbers 2.72, 3, 1.36, and 1.5 look good on my system, they make the pedals look good. You can try playing with them to get them just how you want. They should both be a least 2 so that the text of the menu item will fit. You'll notice of course that the first pair of numbers is double the second. What we did here was to move all the subwindows into place inside the menu.
/* now shape it nicely */
pmap = XCreatePixmap(ev->xany.display, menuwin, menuwidth, menuheight,1);
if (!pmap) fprintf(stderr, " can't create a pixmap
");
A pixmap is an area on the display server, like a window, except you can't see it, nor can it have sub windows. It's good for storing pictures of things you want the display system to have quick access to. What we're doing here is creating a set of nice roundish circles in a pixmap to use as the template for the main flower menu.
gc = XCreateGC(ev->xany.display, pmap, 0, NULL);
XSetForeground(ev->xany.display, gc, 0);
XFillRectangle(ev->xany.display, pmap, gc, 0, 0, menuwidth, menuheight);
XSetForeground(ev->xany.display, gc, 1);
for(j =0; j < MaxPIES && j < menu->nsubws; j ++ ){
XFillArc(ev->xany.display, pmap, gc,
(1.36+xs[j])*maxwidth-maxwidth/2, (1.5+ys[j])*maxwidth-maxwidth/2,
maxwidth, maxwidth, 0, 360*64);
}
XShapeCombineMask(ev->xany.display, menuwin, ShapeBounding, 0, 0, pmap, ShapeSet);
XFreePixmap(ev->xany.display, pmap);
XFillArc can create circles and elipses (among other things), XShapeCombineMask sets the shape of the menu to the set of circles and XFreePixmap is called because we're done with that pixmap. That's important since we don't want to use up the all display system's memory. Now we repeat the setup, but this time, we create a pixmap with only one circle in it and set the shape of the various windows in the menu to it.
pmap = XCreatePixmap(ev->xany.display, menuwin, maxwidth, maxwidth,1);
XSetForeground(ev->xany.display, gc, 0);
XFillRectangle(ev->xany.display, pmap, gc, 0, 0, maxwidth, maxwidth);
XSetForeground(ev->xany.display, gc, 1);
XFillArc(ev->xany.display, pmap, gc, 0, 0, maxwidth, maxwidth, 0, 360*64);
for(j =0; j < MaxPIES && j < menu->nsubws; j ++ ){
XShapeCombineMask(ev->xany.display, menu->subws[j],
ShapeBounding, 0, 0, pmap, ShapeSet);
}
XFreePixmap(ev->xany.display, pmap);
XFreeGC(ev->xany.display, gc);
And then we can map the menu and we'll see the up to six pedals of the flower.
menu->width = maxwidth;
XMapWindow(ev->xany.display, menuwin);
}
This has been a rather long section. Next time we'll make them look a bit more interesting.
Hello, and welcome to part 13 of this Xlib tutorial.
After a nice month long hiatus, we're back.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 <sys/param.h>
. 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 <-- X11 Server protocol-major-version: 000b protocol-minor-version: 0000 release-number: 042da808 resource-id-base: 01c00000 resource-id-mask: 001fffff motion-buffer-size: 00000100 image-byte-order: LSB first bitmap-format-bit-order: LSB first bitmap-format-scanline-unit: 20 bitmap-format-scanline-pad: 20 min-keycode: 8 (^H) max-keycode: 255 (377) vendor: "The X.Org Foundation" pixmap-formats: (7) roots: (1)
This is standard start of protocol stuff. The client (xtut12) is announcing itself and the server is describing what's available
When you're reading through a xscope dump, it really helps to remember the the client is the process that's asking for windows to be drawn, and the server is the machine with the monitor, mouse and keyboard. If you get it backwards you can end up quite confused.
Looking further, you can see things like this:
0.01: Client --> 224 bytes ............REQUEST: CreateWindow depth: 00 wid: WIN 01c00001 parent: WIN 0000003e x: 0 y: 0 width: 0190 height: 0190 border-width: 0000 class: CopyFromParent visual: CopyFromParent value-mask: background-pixel | border-pixel value-list: background-pixel: 0000aedc border-pixel: 00006ad9
and
..............EVENT: MapNotify event: WIN 01c00001 window: WIN 01c00001 override-redirect: False
In this case, we're seeing xtut12 ask for a window to be created, and we can see an event occurring, (much later) when the window is mapped (shown on the screen, assuming all parents are mapped, and it's not obscured).
One unfortunate part of xscope, is that it doesn't know about X extensions, and won't translate them for you.
I think that's all I'm going to do this week. If you can't get xscope running on your system, you can download a sample output to look through
On the other hand if you can get it working, try playing around with it, see if you can figure out how to fix any of the bugs that are in xtut12.
Hello, and welcome to part 12 of this Xlib tutorial.
After lasat 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.First off, get the code here.
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_redirect = False;
block->funcs->configureNotify(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 resized 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 from 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:
Hello, and welcome to part 11 of this Xlib tutorial.
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 trying 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.
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_redirect=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 redirect 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:
Hello, welcome to part 10 of the Xlib tutorial.
Before we start, I would like to mention something. At the end of each section, I make sure that my code works. If it doesn't work on your unix system let me know and we'll see if we can work it out. Each week I'm want to give you something that works so that you can play with it. Play is an important part of learning. Run the examples, but also try changing them and running them again to see what else you can do with your knowledge. If you break it and can't get back to a working piece of code, you can always start again from my code. In a few weeks, I'll be describing some possible ways of debugging X lib code.
This part of the tutorial, we're going continue encapsulating code. First, we've moved all the button related functions to their own file. That way they're out of the way. Also, the two XChar2b functions, and the resource functions each get their own file. We've also moved all the Xrm database initialization code into its own function in the resources file.
...
XrmDatabase setupDB(Display *dpy,
XrmOptionDescRec *xrmTable, int nCommandLineResources,
const char *progname, int *argc, char **argv){
XrmDatabase db;
char filename[256];
XrmInitialize();
db = XrmGetDatabase(dpy);
XrmParseCommand(&db, xrmTable, nCommandLineResources, progname, argc, argv);
sprintf(filename, "%.240s.resources", progname);
if(XrmCombineFileDatabase(filename, &db, False)){
printf("read %s
", filename);
} else {
printf("didn't read %s
", filename);
}
return db;
}
...
And while we were at it we added a new function to get string resources.
...
char *getResource(Display *dpy, XrmDatabase db, char *name, char *cl, char *def){
XrmValue v;
char * type;
if(XrmGetResource(db, name, cl, &type, &v))
return strdup(v.addr);
return strdup(def);
}
...
getFont() and getColour() , of course, remain the same.
In C, with encapsulation, comes the need for .h header files. such as common.h
#include
#include
#include
#include
#include
#include
#define DefGC(dpy) DefaultGC(dpy, DefaultScreen(dpy))
XrmDatabase setupDB(Display *dpy,
XrmOptionDescRec *xrmTable, int nCommandLineResources,
const char *progname, int *argc, char **argv);
char *getResource(Display *dpy, XrmDatabase db, char *name, char *cl, char *def);
unsigned long getColour(Display *dpy, XrmDatabase db, char *name,
char *cl, char *def);
XFontStruct *getFont(Display *dpy, XrmDatabase db, char *name,
char *cl, char *def);
int XChar2bLen(XChar2b *string);
int utf8toXChar2b(XChar2b *output_r, int outsize, const char *input, int inlen);
and button.h
#ifndef XTUT_CALLBACK
#define XTUT_CALLBACK
typedef void (*Callback)(void *cbdata);
#endif
#ifndef XTUT_BUTTON
#define XTUT_BUTTON
typedef struct Button Button;
struct Button {
XChar2b * text;
int text_width;
int font_ascent;
int width, height;
unsigned long border, background, foreground;
void *cbdata;
Callback buttonRelease;
};
#endif
void createButton(Display *dpy, Window parent, 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);
void buttonExpose(Button *button, XEvent *ev);
void buttonConfigure(Button *button, XEvent *ev);
void buttonEnter(Button *button, XEvent *ev);
void buttonLeave(Button *button, XEvent *ev);
void buttonExpose(Button *button, XEvent *ev);
I'm going to stop there this week. Last week's section was long and next week we'll see how we can turn this into menus and menubars, which is going to be even longer.
You can get the new source code from /xtut10.tgz Type 'make' in the directory and it should compile on any unix/linux/bsd system. You may have to tweak the Makefile, if you can't figure it out, ask.
Things to try:
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 anymore. 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 respond. */
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, foreground;
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 before 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) DefaultGC(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 parent, 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 = foreground;
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 entry 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, db, "xtut9.background", "xtut9.BackGround", "DarkGreen");
mainwindow->border = getColour(dpy, db, "xtut9.border", "xtut9.Border", "LightGreen");
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.
Here's the full code.
A few things to try:
Hello,
It's unfortunate, but even today, almost twenty years after XFontSet was created, sometimes it doesn't work properly. You might not have the proper fonts installed, the locale isn't installed properly, whatever. So we're going to try another tack, going back to XFontStruct and friends, but using XChar2b to reach beyond the ASCII characters.
First we'll need a way of translating UTF8 to XChar2b. This works with the knowledge of how UTF8 is structured. First, ASCII characters map to themselvs. For everything else up to 21 bits, the first byte has the high x bits all set to 1 with a 0 in the next highest bit where x is the number of bytes to display the item using UTF8. 8-11 bits requires two bytes, 12-16 bits requires 3 bytes and 17-21 bits requires 4 bytes. Unfortunately, XChar2b only supports up to 16 bits, so 17-21 will be ignored (which means all Chinese, Japanese and Korean characters). The remaining bits in the first byte are the high bits of the character code. Subsequent bytes have 1 in the high bit and 0 in the next highest bit, followed by 6 bits.
...
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) << 6) + (input[j]&0x3F);
k++;
break;
case 0xE0: /* three bytes 4+6+6 = 16 bits */
if (inlen < j+2){ return k; }
j++;
output_r[k].byte1 = ((c&0xF) << 4) + ((input[j]&0x3C) >> 2);
c = input[j];
j++;
output_r[k].byte2 = ((c&0x3) << 6) + (input[j]&0x3F);
k++;
break;
case 0xFF:
/* the character uses more than 16 bits */
continue;
}
}
return k;
}
...
Most of the rest of the differences since the last section are to move back from not using XFontSet to XFontStruct *. What is different is in the main_loop() function.
...
int strlength = strlen(text);
/* may be too big, but definitely big enough */
string = malloc(sizeof(*string) * strlen(text));
strlength = utf8toXChar2b(string, strlength, text, strlength);
printf("%d
", strlength);
text_width = XTextWidth16(font, string, strlength);
printf("%d
", text_width);
font_ascent = font->ascent;
/* as each event that we asked about occurs, we respond. */
...
What we're doing here is converting our string to an array of XChar2b, calculating its width and storing the font ascent. Notice the use of XTextWidth16() instead of XTextWidth() or Xutf8TextEscapement()
Then in the response to an Expose event,
...
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;
XDrawString16(dpy, ev.xany.window, pen, textx, texty, string, strlength);
break;
...
We are again drawing lines as we did before, and use XDrawString16() instead of Xutf8DrawString() or XDrawString(). Download the full code.
And that's it. Next lesson will be a bit more interesting since we're going to start interacting with the user.
Things to try:
Hello,
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 preferred 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);
...
Things to try:
Hello,
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 = XLoadQueryFont(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 = XLoadQueryFont(dpy, def);
}
return font;
}
...
The colours can actually be in the format #rrggbb where rrggbb are
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, db, "xtut6.background", "xtut6.BackGround", "DarkGreen");
border = getColour(dpy, db, "xtut6.border", "xtut6.Border", "LightGreen");
values.foreground = getColour(dpy, db, "xtut6.foreground", "xtut6.ForeGround", "Red");
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 preferences 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 argument 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:
Hello,
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 font 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 changes.
Things to try:
Hello,
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 = XLoadQueryFont(dpy, fontname);
if (!font) {
fprintf(stderr, "unable to load preferred font: %s using fixed
", fontname);
font = XLoadQueryFont(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:
Hello,
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;
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:
Hello,
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.
...
/* as each event that we asked about occurs, we respond. 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;
}
}
...
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:
Hello,
And welcome to my 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 <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xresource.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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. */
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);
/* as each event that we asked about occurs, we respond. 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
<html>
<head>
<title></title>
</head>
<body>
</body>
</html>
in html which creates an empty page without anything on it. There are a few things to note:
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
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?