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, 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, 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, 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, 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: