1 /* 2 I, David Oberhollenzer, author of this file hereby place the contents of 3 this file into the public domain. Please feel free to use this file in any 4 way you wish. 5 */ 6 7 #include <X11/X.h> 8 #include <X11/Xlib.h> 9 #include <X11/Xutil.h> 10 #include <X11/keysym.h> 11 12 #include <string.h> 13 #include <stdlib.h> 14 15 16 #define BUTTON_HOVER 0x01 17 #define BUTTON_CLICKED 0x02 18 19 20 struct button { 21 int x, y; 22 unsigned int width, height; 23 int textx, texty; 24 int flags; 25 const char *text; 26 }; 27 28 struct messagebox { 29 Display *dpy; 30 Window wnd; 31 struct button ok; 32 int black, white; 33 Atom wmDelete; 34 GC gc; 35 }; 36 37 static const char *wmDeleteWindow = "WM_DELETE_WINDOW"; 38 39 static int is_point_inside(struct button *b, int px, int py) 40 { 41 return px >= b->x && px <= (b->x + (int)b->width - 1) && 42 py >= b->y && py <= (b->y + (int)b->height - 1); 43 } 44 45 static void messagebox_draw_button(struct messagebox *mb, struct button *b) 46 { 47 int offset = (b->flags & BUTTON_CLICKED) ? 1 : 0; 48 49 if (b->flags & BUTTON_HOVER) { 50 XFillRectangle(mb->dpy, mb->wnd, mb->gc, 51 b->x + offset, b->y + offset, 52 b->width, b->height); 53 XSetForeground(mb->dpy, mb->gc, mb->black); 54 XSetBackground(mb->dpy, mb->gc, mb->white); 55 } else { 56 XSetForeground(mb->dpy, mb->gc, mb->white); 57 XSetBackground(mb->dpy, mb->gc, mb->black); 58 XDrawRectangle(mb->dpy, mb->wnd, mb->gc, b->x, b->y, 59 b->width, b->height); 60 } 61 62 XDrawString(mb->dpy, mb->wnd, mb->gc, 63 b->textx + offset, b->texty + offset, 64 b->text, strlen(b->text)); 65 XSetForeground(mb->dpy, mb->gc, mb->white); 66 XSetBackground(mb->dpy, mb->gc, mb->black); 67 } 68 69 static int messagebox_init(struct messagebox *mb, const char *title) 70 { 71 if (!(mb->dpy = XOpenDisplay(0))) 72 return 0; 73 74 mb->black = BlackPixel(mb->dpy, DefaultScreen(mb->dpy)); 75 mb->white = WhitePixel(mb->dpy, DefaultScreen(mb->dpy)); 76 mb->wmDelete = XInternAtom(mb->dpy, wmDeleteWindow, True); 77 78 mb->wnd = XCreateSimpleWindow(mb->dpy, DefaultRootWindow(mb->dpy), 79 0, 0, 100, 100, 0, mb->black, mb->black); 80 81 if (!mb->wnd) 82 goto failwnd; 83 84 XStoreName(mb->dpy, mb->wnd, title); 85 XSetWMProtocols(mb->dpy, mb->wnd, &mb->wmDelete, 1); 86 XSelectInput(mb->dpy, mb->wnd, 87 ExposureMask | StructureNotifyMask | 88 KeyReleaseMask | PointerMotionMask | 89 ButtonPressMask | ButtonReleaseMask); 90 91 mb->gc = XCreateGC(mb->dpy, mb->wnd, 0, 0); 92 93 if (!mb->gc) 94 goto fail; 95 96 XSetForeground(mb->dpy, mb->gc, mb->white); 97 XSetBackground(mb->dpy, mb->gc, mb->black); 98 return 1; 99 fail: 100 XDestroyWindow(mb->dpy, mb->wnd); 101 failwnd: 102 XCloseDisplay(mb->dpy); 103 return 0; 104 } 105 106 static void messagebox_cleanup(struct messagebox *mb) 107 { 108 XFreeGC(mb->dpy, mb->gc); 109 XDestroyWindow(mb->dpy, mb->wnd); 110 XCloseDisplay(mb->dpy); 111 } 112 113 static void messagebox_draw(struct messagebox *mb, const char *text, 114 size_t height) 115 { 116 const char *temp = text, *end; 117 size_t i = 0; 118 119 XClearWindow(mb->dpy, mb->wnd); 120 121 while (temp != NULL) { 122 end = strchr(temp, '\n'); 123 124 XDrawString(mb->dpy, mb->wnd, mb->gc, 10, height + i + 10, 125 temp, end ? (size_t)(end - temp) : strlen(temp)); 126 127 temp = end ? (end + 1) : NULL; 128 i += height; 129 } 130 131 messagebox_draw_button(mb, &mb->ok); 132 XFlush(mb->dpy); 133 } 134 135 static void messagebox_show(struct messagebox *mb, int X, int Y, int W, int H) 136 { 137 XSizeHints hints; 138 139 XMoveResizeWindow(mb->dpy, mb->wnd, X, Y, W, H); 140 141 hints.flags = PSize | PMinSize | PMaxSize; 142 hints.min_width = hints.max_width = hints.base_width = W; 143 hints.min_height = hints.max_height = hints.base_height = H; 144 145 XSetWMNormalHints(mb->dpy, mb->wnd, &hints); 146 XMapRaised(mb->dpy, mb->wnd); 147 XFlush(mb->dpy); 148 } 149 150 /************************************************************************** 151 * A small and simple function that creates a message box with an OK * 152 * button, using ONLY Xlib. It does not return until the user closes the * 153 * message box, using the OK button, the escape key, or the close button. * 154 * * 155 * title: The title of the message box. * 156 * text: The contents of the message box. Use '\n' as a line terminator. * 157 **************************************************************************/ 158 void MessageBoxX11(const char *title, const char *text) 159 { 160 int height = 0, direction, ascent, descent, X, Y, W = 0, H; 161 const char *end, *temp; 162 struct messagebox mb; 163 XCharStruct overall; 164 size_t lines = 0; 165 XFontStruct *font; 166 XEvent e; 167 168 if (!messagebox_init(&mb, title)) 169 return; 170 171 /* Compute the printed width and height of the text */ 172 if (!(font = XQueryFont(mb.dpy, XGContextFromGC(mb.gc)))) 173 goto out; 174 175 temp = text; 176 177 while (temp != NULL) { 178 end = strchr(temp, '\n'); 179 180 XTextExtents(font, temp, 181 end ? (size_t)(end - temp) : strlen(temp), 182 &direction, &ascent, &descent, &overall); 183 184 W = overall.width > W ? overall.width : W; 185 height = (ascent + descent) > height ? 186 (ascent + descent) : height; 187 temp = end ? (end + 1) : NULL; 188 ++lines; 189 } 190 191 /* Compute the shape of the window and adjust the window accordingly */ 192 W += 20; 193 H = lines * height + height + 40; 194 X = DisplayWidth(mb.dpy, DefaultScreen(mb.dpy)) / 2 - W / 2; 195 Y = DisplayHeight(mb.dpy, DefaultScreen(mb.dpy)) / 2 - H / 2; 196 197 messagebox_show(&mb, X, Y, W, H); 198 199 /* Compute the shape of the OK button */ 200 XTextExtents(font, "OK", 2, &direction, &ascent, &descent, &overall); 201 202 mb.ok.width = overall.width + 30; 203 mb.ok.height = ascent + descent + 5; 204 mb.ok.x = W / 2 - mb.ok.width / 2; 205 mb.ok.y = H - height - 15; 206 mb.ok.textx = mb.ok.x + 15; 207 mb.ok.texty = mb.ok.y + mb.ok.height - 3; 208 mb.ok.flags = 0; 209 mb.ok.text = "OK"; 210 211 XFreeFontInfo(NULL, font, 1); 212 213 /* Event loop */ 214 for (;;) { 215 XNextEvent(mb.dpy, &e); 216 mb.ok.flags &= ~BUTTON_CLICKED; 217 218 switch (e.type) { 219 case MotionNotify: 220 if (is_point_inside(&mb.ok, e.xmotion.x, e.xmotion.y)) { 221 if (mb.ok.flags & BUTTON_HOVER) 222 break; 223 } else if (!(mb.ok.flags & BUTTON_HOVER)) { 224 break; 225 } 226 mb.ok.flags ^= BUTTON_HOVER; 227 messagebox_draw(&mb, text, height); 228 break; 229 case ButtonPress: 230 case ButtonRelease: 231 if (e.xbutton.button != Button1) 232 break; 233 if (!(mb.ok.flags & BUTTON_HOVER)) 234 break; 235 236 mb.ok.flags = e.type == ButtonPress ? 237 (mb.ok.flags | BUTTON_CLICKED) : 238 (mb.ok.flags & ~BUTTON_CLICKED); 239 240 if (!(mb.ok.flags & BUTTON_CLICKED)) 241 goto out; 242 /* fall-through */ 243 case Expose: 244 case MapNotify: 245 messagebox_draw(&mb, text, height); 246 break; 247 case KeyRelease: 248 if (XLookupKeysym(&e.xkey, 0) == XK_Escape) 249 goto out; 250 break; 251 case ClientMessage: 252 if (e.xclient.data.l[0] == (long)mb.wmDelete) 253 goto out; 254 break; 255 } 256 } 257 out: 258 messagebox_cleanup(&mb); 259 } 260 261 262 int main(void) 263 { 264 MessageBoxX11("Test", "Foo bar test text bla bla\nMulti line\nText"); 265 return EXIT_SUCCESS; 266 }