1 /*
2  * XOSD
3  *
4  * Copyright (c) 2000 Andre Renaud (andre@ignavus.net)
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation; either version 2 of the License, or (at your
9  * option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 675 Mass Ave, Cambridge, MA 02139, USA.
19  */
20 #include "intern.h"
21 
22 #define SLIDER_SCALE 0.8
23 #define SLIDER_SCALE_ON 0.7
24 #define XOFFSET 10
25 
26 const char *osd_default_font =
27   "-misc-fixed-medium-r-semicondensed--*-*-*-*-c-*-*-*";
28 #if 0
29 "-adobe-helvetica-bold-r-*-*-10-*";
30 #endif
31 const char *osd_default_colour = "green";
32 
33 /** Global error string. */
34 char *xosd_error;
35 
36 /* Wait until display is in next state. {{{ */
37 static void
_wait_until_update(xosd * osd,int generation)38 _wait_until_update(xosd * osd, int generation)
39 {
40   pthread_mutex_lock(&osd->mutex_sync);
41   while (osd->generation == generation) {
42     DEBUG(Dtrace, "waiting %d %d", generation, osd->generation);
43     pthread_cond_wait(&osd->cond_sync, &osd->mutex_sync);
44   }
45   pthread_mutex_unlock(&osd->mutex_sync);
46 }
47 
48 /* }}} */
49 
50 /* Serialize access to the X11 connection. {{{
51  *
52  * Background: xosd needs a thread which handles X11 exposures. XNextEvent()
53  * blocks and would deny any other thread - especially the thread which calls
54  * the xosd API - the usage of the same X11 connection. XInitThreads() can't be
55  * used, because xosd is a library which can be loaded dynamically way after
56  * the loading application has done its first X11 call, after which calling
57  * XInitThreads() is no longer possible. (Debian-Bug #252170)
58  *
59  * The exposure-thread gets the MUTEX and sleeps on a select([X11,pipe]). When
60  * an X11 event occurs, the tread can directly use X11 calls.
61  * When another thread needs to do an X11 call, it uses _xosd_lock(osd) to
62  * notify the exposure-thread via the pipe, which uses cond_wait to voluntarily
63  * pass the right to access X11 to the signalling thread. The calling thread
64  * acquire the MUTEX and can than use the X11 calls.
65  * After using X11, the thread calls _xosd_unlock(osd) to remove its token from
66  * the pipe and to wake up the exposure-thread via cond_signal, before
67  * releasing the MUTEX.
68  * The number of characters in the pipe is an indication for the number of
69  * threads waiting for the X11-MUTEX.
70  */
71 static /*inline */ void
_xosd_lock(xosd * osd)72 _xosd_lock(xosd * osd)
73 {
74   char c = 0;
75   FUNCTION_START(Dlocking);
76   write(osd->pipefd[1], &c, sizeof(c));
77   pthread_mutex_lock(&osd->mutex);
78   FUNCTION_END(Dlocking);
79 }
80 static /*inline */ void
_xosd_unlock(xosd * osd)81 _xosd_unlock(xosd * osd)
82 {
83   char c;
84   int generation = osd->generation, update = osd->update;
85   FUNCTION_START(Dlocking);
86   read(osd->pipefd[0], &c, sizeof(c));
87   pthread_cond_signal(&osd->cond_wait);
88   pthread_mutex_unlock(&osd->mutex);
89   if (update & UPD_show)
90     _wait_until_update(osd, generation & ~1); /* no wait when already shown. */
91   FUNCTION_END(Dlocking);
92 }
93 
94 /* }}} */
95 
96 /* Draw percentage/slider bar. {{{ */
97 static void                     /*inline */
_draw_bar(xosd * osd,int nbars,int on,XRectangle * p,XRectangle * mod,int is_slider)98 _draw_bar(xosd * osd, int nbars, int on, XRectangle * p, XRectangle * mod,
99           int is_slider)
100 {
101   int i;
102   XRectangle rs[2];
103   FUNCTION_START(Dfunction);
104 
105   rs[0].x = rs[1].x = mod->x + p->x;
106   rs[0].y = (rs[1].y = mod->y + p->y) + p->height / 3;
107   rs[0].width = mod->width + p->width * SLIDER_SCALE;
108   rs[0].height = mod->height + p->height / 3;
109   rs[1].width = mod->width + p->width * SLIDER_SCALE_ON;
110   rs[1].height = mod->height + p->height;
111   for (i = 0; i < nbars; i++, rs[0].x = rs[1].x += p->width) {
112     XRectangle *r = &(rs[is_slider ? (i == on) : (i < on)]);
113     XFillRectangles(osd->display, osd->mask_bitmap, osd->mask_gc, r, 1);
114     XFillRectangles(osd->display, osd->line_bitmap, osd->gc, r, 1);
115   }
116   FUNCTION_END(Dfunction);
117 }
118 static void
draw_bar(xosd * osd,int line)119 draw_bar(xosd * osd, int line)
120 {
121   struct xosd_bar *l = &osd->lines[line].bar;
122   int is_slider = l->type == LINE_slider, nbars, on;
123   XRectangle p, m;
124   p.x = XOFFSET;
125   p.y = osd->line_height * line;
126   p.width = -osd->extent->y / 2;
127   p.height = -osd->extent->y;
128 
129   assert(osd);
130   FUNCTION_START(Dfunction);
131 
132   /* Calculate number of bars in automatic mode */
133   if (osd->bar_length == -1) {
134     nbars = (osd->screen_width * SLIDER_SCALE) / p.width;
135     switch (osd->align) {
136     case XOSD_center:
137       p.x = osd->screen_width * ((1 - SLIDER_SCALE) / 2);
138       break;
139     case XOSD_right:
140       p.x = osd->screen_width * (1 - SLIDER_SCALE);
141     case XOSD_left:
142       break;
143     }
144   } else {
145     nbars = osd->bar_length;
146     switch (osd->align) {
147     case XOSD_center:
148       p.x = (osd->screen_width - (nbars * p.width)) / 2;
149       break;
150     case XOSD_right:
151       p.x = osd->screen_width - (nbars * p.width) - p.x;
152     case XOSD_left:
153       break;
154     }
155   }
156   on = ((nbars - is_slider) * l->value) / 100;
157 
158   DEBUG(Dvalue, "percent=%d, nbars=%d, on=%d", l->value, nbars, on);
159 
160   /* Outline */
161   if (osd->outline_offset) {
162     m.x = m.y = -osd->outline_offset;
163     m.width = m.height = 2 * osd->outline_offset;
164     XSetForeground(osd->display, osd->gc, osd->outline_pixel);
165     _draw_bar(osd, nbars, on, &p, &m, is_slider);
166   }
167   /* Shadow */
168   if (osd->shadow_offset) {
169     m.x = m.y = osd->shadow_offset;
170     m.width = m.height = 0;
171     XSetForeground(osd->display, osd->gc, osd->shadow_pixel);
172     _draw_bar(osd, nbars, on, &p, &m, is_slider);
173   }
174   /* Bar/Slider */
175   if (1) {
176     m.x = m.y = m.width = m.height = 0;
177     XSetForeground(osd->display, osd->gc, osd->pixel);
178     _draw_bar(osd, nbars, on, &p, &m, is_slider);
179   }
180 }
181 
182 /* }}} */
183 
184 /* Draw text. {{{ */
185 static void                     /*inline */
_draw_text(xosd * osd,char * string,int x,int y)186 _draw_text(xosd * osd, char *string, int x, int y)
187 {
188   int len = strlen(string);
189   FUNCTION_START(Dfunction);
190   XmbDrawString(osd->display, osd->mask_bitmap, osd->fontset, osd->mask_gc, x,
191                 y, string, len);
192   XmbDrawString(osd->display, osd->line_bitmap, osd->fontset, osd->gc, x, y,
193                 string, len);
194   FUNCTION_END(Dfunction);
195 }
196 static void
draw_text(xosd * osd,int line)197 draw_text(xosd * osd, int line)
198 {
199   int x = XOFFSET, y = osd->line_height * line - osd->extent->y;
200   struct xosd_text *l = &osd->lines[line].text;
201 
202   assert(osd);
203   FUNCTION_START(Dfunction);
204 
205   if (l->string == NULL)
206     return;
207 
208   if (l->width < 0) {
209     XRectangle rect;
210     XmbTextExtents(osd->fontset, l->string, strlen(l->string), NULL, &rect);
211     l->width = rect.width;
212   }
213 
214   switch (osd->align) {
215   case XOSD_center:
216     x = (osd->screen_width - l->width) / 2;
217     break;
218   case XOSD_right:
219     x = osd->screen_width - l->width - x;
220   case XOSD_left:
221     break;
222   }
223 
224   if (osd->shadow_offset) {
225     XSetForeground(osd->display, osd->gc, osd->shadow_pixel);
226     _draw_text(osd, l->string, x + osd->shadow_offset,
227                y + osd->shadow_offset);
228   }
229   if (osd->outline_offset) {
230     int i, j;
231     XSetForeground(osd->display, osd->gc, osd->outline_pixel);
232     /* FIXME: echo . | osd_cat -O 50 -p middle -A center */
233     for (i = 1; i <= osd->outline_offset; i++)
234       for (j = 0; j < 9; j++)
235         if (j != 4)
236           _draw_text(osd, l->string, x + (j / 3 - 1) * i,
237                      y + (j % 3 - 1) * i);
238   }
239   if (1) {
240     XSetForeground(osd->display, osd->gc, osd->pixel);
241     _draw_text(osd, l->string, x, y);
242   }
243 }
244 
245 /* }}} */
246 
247 /* Handles X11 events, timeouts and does the drawing. {{{
248  * This is running in it's own thread for Expose-events.
249  * The order of update handling is important:
250  * 1. The size must be correct -> UPD_size first
251  * 2. Change the position, which might expose part of window -> UPD_pos
252  * 3. The XShape must be set before something is drawn -> UPD_mask, UPD_lines
253  * 4. The window should be mapped before something is drawn -> UPD_show
254  * 5. Start the timer last to not account for processing time -> UPD_timer
255  * If you change this order, you'll get a broken display. You've been warned!
256  */
257 static void *
event_loop(void * osdv)258 event_loop(void *osdv)
259 {
260   xosd *osd = osdv;
261   int xfd, max;
262 
263   FUNCTION_START(Dfunction);
264   DEBUG(Dtrace, "event thread started");
265   assert(osd);
266 
267   xfd = ConnectionNumber(osd->display);
268   max = (osd->pipefd[0] > xfd) ? osd->pipefd[0] : xfd;
269 
270   pthread_mutex_lock(&osd->mutex);
271   DEBUG(Dtrace, "Request exposure events");
272   XSelectInput(osd->display, osd->window, ExposureMask);
273   osd->update |= UPD_size | UPD_pos | UPD_mask;
274   while (!osd->done) {
275     int retval, line;
276     fd_set readfds;
277     struct timeval tv, *tvp = NULL;
278 
279     FD_ZERO(&readfds);
280     FD_SET(xfd, &readfds);
281     FD_SET(osd->pipefd[0], &readfds);
282 
283     /* Hide display requested. */
284     if (osd->update & UPD_hide) {
285       DEBUG(Dupdate, "UPD_hide");
286       if (osd->generation & 1) {
287         XUnmapWindow(osd->display, osd->window);
288         osd->generation++;
289       }
290     }
291     /* The font, outline or shadow was changed. Recalculate line height,
292      * resize window and bitmaps. */
293     if (osd->update & UPD_size) {
294       XFontSetExtents *extents = XExtentsOfFontSet(osd->fontset);
295       DEBUG(Dupdate, "UPD_size");
296       osd->extent = &extents->max_logical_extent;
297       osd->line_height = osd->extent->height + osd->shadow_offset + 2 *
298         osd->outline_offset;
299       osd->height = osd->line_height * osd->number_lines;
300       for (line = 0; line < osd->number_lines; line++)
301         if (osd->lines[line].type == LINE_text)
302           osd->lines[line].text.width = -1;
303 
304       XResizeWindow(osd->display, osd->window, osd->screen_width,
305                     osd->height);
306       XFreePixmap(osd->display, osd->mask_bitmap);
307       osd->mask_bitmap = XCreatePixmap(osd->display, osd->window,
308                                        osd->screen_width, osd->height, 1);
309       XFreePixmap(osd->display, osd->line_bitmap);
310       osd->line_bitmap = XCreatePixmap(osd->display, osd->window,
311                                        osd->screen_width, osd->height,
312                                        osd->depth);
313     }
314     /* H/V offset or vertical positon was changed. Horizontal alignment is
315      * handles internally as line realignment with UPD_content. */
316     if (osd->update & UPD_pos) {
317       int x = 0, y = 0;
318       DEBUG(Dupdate, "UPD_pos");
319       switch (osd->align) {
320       case XOSD_left:
321       case XOSD_center:
322         x = osd->screen_xpos + osd->hoffset;
323         break;
324       case XOSD_right:
325         x = osd->screen_xpos - osd->hoffset;
326       }
327       switch (osd->pos) {
328       case XOSD_bottom:
329         y = osd->screen_height - osd->height - osd->voffset;
330         break;
331       case XOSD_middle:
332         y = (osd->screen_height - osd->height) / 2 - osd->voffset;
333         break;
334       case XOSD_top:
335         y = osd->voffset;
336       }
337       XMoveWindow(osd->display, osd->window, x, y);
338     }
339     /* If the content changed, redraw lines in background buffer.
340      * Also update XShape unless only colours were changed. */
341     if (osd->update & (UPD_mask | UPD_lines)) {
342       DEBUG(Dupdate, "UPD_lines");
343       for (line = 0; line < osd->number_lines; line++) {
344         int y = osd->line_height * line;
345 #ifdef DEBUG_XSHAPE
346         XSetForeground(osd->display, osd->gc, osd->outline_pixel);
347         XFillRectangle(osd->display, osd->line_bitmap, osd->gc, 0,
348                        y, osd->screen_width, osd->line_height);
349 #endif
350         if (osd->update & UPD_mask) {
351           XFillRectangle(osd->display, osd->mask_bitmap, osd->mask_gc_back, 0,
352                          y, osd->screen_width, osd->line_height);
353         }
354         switch (osd->lines[line].type) {
355         case LINE_text:
356           draw_text(osd, line);
357           break;
358         case LINE_percentage:
359         case LINE_slider:
360           draw_bar(osd, line);
361         case LINE_blank:
362           break;
363         }
364       }
365     }
366 #ifndef DEBUG_XSHAPE
367     /* More than colours was changed, also update XShape. */
368     if (osd->update & UPD_mask) {
369       DEBUG(Dupdate, "UPD_mask");
370       XShapeCombineMask(osd->display, osd->window, ShapeBounding, 0, 0,
371                         osd->mask_bitmap, ShapeSet);
372     }
373 #endif
374     /* Show display requested. */
375     if (osd->update & UPD_show) {
376       DEBUG(Dupdate, "UPD_show");
377       if (~osd->generation & 1) {
378         osd->generation++;
379         XMapRaised(osd->display, osd->window);
380       }
381     }
382     /* Copy content, if window was changed or exposed. */
383     if ((osd->generation & 1)
384         && osd->update & (UPD_size | UPD_pos | UPD_lines | UPD_show)) {
385       DEBUG(Dupdate, "UPD_copy");
386       XCopyArea(osd->display, osd->line_bitmap, osd->window, osd->gc, 0, 0,
387                 osd->screen_width, osd->height, 0, 0);
388     }
389     /* Flush all pennding X11 requests, if any. */
390     if (osd->update & ~UPD_timer) {
391       XFlush(osd->display);
392       osd->update &= UPD_timer;
393     }
394     /* Restart the timer when requested. */
395     if (osd->update & UPD_timer) {
396       DEBUG(Dupdate, "UPD_timer");
397       osd->update = UPD_none;
398       if ((osd->generation & 1) && (osd->timeout > 0))
399         gettimeofday(&osd->timeout_start, NULL);
400       else
401         timerclear(&osd->timeout_start);
402     }
403     /* Calculate timeout delta or hide display. */
404     if (timerisset(&osd->timeout_start)) {
405       gettimeofday(&tv, NULL);
406       tv.tv_sec -= osd->timeout;
407       if (timercmp(&tv, &osd->timeout_start, <)) {
408         tv.tv_sec = osd->timeout_start.tv_sec - tv.tv_sec;
409         tv.tv_usec = osd->timeout_start.tv_usec - tv.tv_usec;
410         if (tv.tv_usec < 0) {
411           tv.tv_usec += 1000000;
412           tv.tv_sec -= 1;
413         }
414         tvp = &tv;
415       } else {
416         timerclear(&osd->timeout_start);
417         if (osd->generation & 1)
418           osd->update |= UPD_hide;
419         continue;               /* Hide the window first and than restart the loop */
420       }
421     }
422 
423     /* Signal update */
424     pthread_mutex_lock(&osd->mutex_sync);
425     pthread_cond_broadcast(&osd->cond_sync);
426     pthread_mutex_unlock(&osd->mutex_sync);
427 
428     /* Wait for the next X11 event or an API request via the pipe. */
429     retval = select(max + 1, &readfds, NULL, NULL, tvp);
430     DEBUG(Dvalue, "SELECT=%d PIPE=%ld X11=%ld", retval,
431           FD_ISSET(osd->pipefd[0], &readfds), FD_ISSET(xfd, &readfds));
432 
433     if (retval == -1 && errno == EINTR) {
434       DEBUG(Dselect, "select() EINTR");
435       continue;
436     } else if (retval == -1) {
437       DEBUG(Dselect, "select() error %d", errno);
438       osd->done = 1;
439       break;
440     } else if (retval == 0) {
441       DEBUG(Dselect, "select() timeout");
442       continue;                 /* timeout */
443     } else if (FD_ISSET(osd->pipefd[0], &readfds)) {
444       /* Another thread wants to use the X11 connection */
445       pthread_cond_wait(&osd->cond_wait, &osd->mutex);
446       DEBUG(Dselect, "Resume exposure thread after X11 call");
447       continue;
448     } else if (FD_ISSET(xfd, &readfds)) {
449       XEvent report;
450       /* There is a event, but it might not be an Exposure-event, so don't use
451        * XWindowEvent(), since that might block. */
452       XNextEvent(osd->display, &report);
453       /* ignore sent by server/manual send flag */
454       switch (report.type & 0x7f) {
455       case Expose:
456         {
457           XExposeEvent *XE = &report.xexpose;
458           /* http://x.holovko.ru/Xlib/chap10.html#10.9.1 */
459           DEBUG(Dvalue, "expose %d: x=%d y=%d w=%d h=%d", XE->count,
460                 XE->x, XE->y, XE->width, XE->height);
461 #if 0
462           if (report.xexpose.count == 0) {
463             int ytop, ybot;
464             ytop = report.xexpose.y / osd->line_height;
465             ybot =
466               (report.xexpose.y + report.xexpose.height) / osd->line_height;
467             do {
468               osd->lines[ytop].width = -1;
469             } while (ytop++ < ybot);
470           }
471 #endif
472           XCopyArea(osd->display, osd->line_bitmap, osd->window, osd->gc,
473                     report.xexpose.x, report.xexpose.y, report.xexpose.width,
474                     report.xexpose.height, report.xexpose.x, report.xexpose.y);
475           break;
476         }
477       case GraphicsExpose:
478         {
479           XGraphicsExposeEvent *XE = &report.xgraphicsexpose;
480           DEBUG(Dvalue, "gfxexpose %d: x=%d y=%d w=%d h=%d code=%d",
481                 XE->count, XE->x, XE->y, XE->width, XE->height, XE->major_code);
482           break;
483         }
484       case NoExpose:
485         {
486           XNoExposeEvent *XE = &report.xnoexpose;
487           DEBUG(Dvalue, "noexpose: code=%d", XE->major_code);
488           break;
489         }
490       default:
491         DEBUG(Dvalue, "XEvent=%d", report.type);
492         break;
493       }
494       continue;
495     } else {
496       DEBUG(Dselect, "select() FATAL %d", retval);
497       exit(-1);                 /* Impossible */
498     }
499   }
500   pthread_mutex_unlock(&osd->mutex);
501 
502   return NULL;
503 }
504 
505 /* }}} */
506 
507 /* Parse textual colour value. {{{ */
508 static int
parse_colour(xosd * osd,XColor * col,unsigned long * pixel,const char * colour)509 parse_colour(xosd * osd, XColor * col, unsigned long *pixel,
510              const char *colour)
511 {
512   Colormap colourmap;
513   int retval = 0;
514 
515   FUNCTION_START(Dfunction);
516   DEBUG(Dtrace, "getting colourmap");
517   colourmap = DefaultColormap(osd->display, osd->screen);
518 
519   DEBUG(Dtrace, "parsing colour");
520   if (XParseColor(osd->display, colourmap, colour, col)) {
521     DEBUG(Dtrace, "attempting to allocate colour");
522     if (XAllocColor(osd->display, colourmap, col)) {
523       DEBUG(Dtrace, "allocation sucessful");
524       *pixel = col->pixel;
525     } else {
526       DEBUG(Dtrace, "defaulting to white. could not allocate colour");
527       *pixel = WhitePixel(osd->display, osd->screen);
528       retval = -1;
529     }
530   } else {
531     DEBUG(Dtrace, "could not poarse colour. defaulting to white");
532     *pixel = WhitePixel(osd->display, osd->screen);
533     retval = -1;
534   }
535 
536   return retval;
537 }
538 
539 /* }}} */
540 
541 /* Tell window manager to put window topmost. {{{ */
542 void
stay_on_top(Display * dpy,Window win)543 stay_on_top(Display * dpy, Window win)
544 {
545   Atom gnome, net_wm, type;
546   int format;
547   unsigned long nitems, bytesafter;
548   unsigned char *args = NULL;
549   Window root = DefaultRootWindow(dpy);
550 
551   FUNCTION_START(Dfunction);
552   /*
553    * build atoms
554    */
555   gnome = XInternAtom(dpy, "_WIN_SUPPORTING_WM_CHECK", False);
556   net_wm = XInternAtom(dpy, "_NET_SUPPORTED", False);
557 
558   /*
559    * gnome-compilant
560    * tested with icewm + WindowMaker
561    */
562   if (Success == XGetWindowProperty
563       (dpy, root, gnome, 0, (65536 / sizeof(long)), False,
564        AnyPropertyType, &type, &format, &nitems, &bytesafter, &args) &&
565       nitems > 0) {
566     /*
567      * FIXME: check capabilities
568      */
569     XClientMessageEvent xev;
570     Atom gnome_layer = XInternAtom(dpy, "_WIN_LAYER", False);
571 
572     memset(&xev, 0, sizeof(xev));
573     xev.type = ClientMessage;
574     xev.window = win;
575     xev.message_type = gnome_layer;
576     xev.format = 32;
577     xev.data.l[0] = 6 /* WIN_LAYER_ONTOP */ ;
578 
579     XSendEvent(dpy, DefaultRootWindow(dpy), False, SubstructureNotifyMask,
580                (XEvent *) & xev);
581     XFree(args);
582   }
583   /*
584    * netwm compliant.
585    * tested with kde
586    */
587   else if (Success == XGetWindowProperty
588            (dpy, root, net_wm, 0, (65536 / sizeof(long)), False,
589             AnyPropertyType, &type, &format, &nitems, &bytesafter, &args)
590            && nitems > 0) {
591     XEvent e;
592     Atom net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
593     Atom net_wm_top = XInternAtom(dpy, "_NET_WM_STATE_STAYS_ON_TOP", False);
594 
595     memset(&e, 0, sizeof(e));
596     e.xclient.type = ClientMessage;
597     e.xclient.message_type = net_wm_state;
598     e.xclient.display = dpy;
599     e.xclient.window = win;
600     e.xclient.format = 32;
601     e.xclient.data.l[0] = 1 /* _NET_WM_STATE_ADD */ ;
602     e.xclient.data.l[1] = net_wm_top;
603     e.xclient.data.l[2] = 0l;
604     e.xclient.data.l[3] = 0l;
605     e.xclient.data.l[4] = 0l;
606 
607     XSendEvent(dpy, DefaultRootWindow(dpy), False,
608                SubstructureRedirectMask, &e);
609     XFree(args);
610   }
611   XRaiseWindow(dpy, win);
612 }
613 
614 /* }}} */
615 
616 /* xosd_init -- Create a new xosd "object" {{{
617  * Deprecated: Use xosd_create. */
618 xosd *
xosd_init(const char * font,const char * colour,int timeout,xosd_pos pos,int voffset,int shadow_offset,int number_lines)619 xosd_init(const char *font, const char *colour, int timeout, xosd_pos pos,
620           int voffset, int shadow_offset, int number_lines)
621 {
622   xosd *osd = xosd_create(number_lines);
623 
624   FUNCTION_START(Dfunction);
625   if (osd == NULL)
626     return NULL;
627 
628   if (xosd_set_font(osd, font) == -1) {
629     xosd_destroy(osd);
630     /*
631      * we do not set xosd_error, as set_font has already set it to
632      * a sensible error message.
633      */
634     return NULL;
635   }
636   xosd_set_colour(osd, colour);
637   xosd_set_timeout(osd, timeout);
638   xosd_set_pos(osd, pos);
639   xosd_set_vertical_offset(osd, voffset);
640   xosd_set_shadow_offset(osd, shadow_offset);
641 
642   return osd;
643 }
644 
645 /* }}} */
646 
647 /* xosd_create -- Create a new xosd "object" {{{ */
648 xosd *
xosd_create(int number_lines)649 xosd_create(int number_lines)
650 {
651   xosd *osd;
652   int event_basep, error_basep, i;
653   char *display;
654   XSetWindowAttributes setwinattr;
655   XGCValues xgcv = { .graphics_exposures = False };
656 #ifdef HAVE_XINERAMA
657   int screens;
658   int dummy_a, dummy_b;
659   XineramaScreenInfo *screeninfo = NULL;
660 #endif
661 
662   FUNCTION_START(Dfunction);
663   DEBUG(Dtrace, "getting display");
664   display = getenv("DISPLAY");
665   if (!display) {
666     xosd_error = "No display";
667     return NULL;
668   }
669 
670   DEBUG(Dtrace, "Mallocing osd");
671   osd = malloc(sizeof(xosd));
672   memset(osd, 0, sizeof(xosd));
673   if (osd == NULL) {
674     xosd_error = "Out of memory";
675     goto error0;
676   }
677 
678   DEBUG(Dtrace, "Creating pipe");
679   if (pipe(osd->pipefd) == -1) {
680     xosd_error = "Error creating pipe";
681     goto error0b;
682   }
683 
684   DEBUG(Dtrace, "initializing mutex");
685   pthread_mutex_init(&osd->mutex, NULL);
686   pthread_mutex_init(&osd->mutex_sync, NULL);
687   DEBUG(Dtrace, "initializing condition");
688   pthread_cond_init(&osd->cond_wait, NULL);
689   pthread_cond_init(&osd->cond_sync, NULL);
690 
691   DEBUG(Dtrace, "initializing number lines");
692   osd->number_lines = number_lines;
693   osd->lines = malloc(sizeof(union xosd_line) * osd->number_lines);
694   if (osd->lines == NULL) {
695     xosd_error = "Out of memory";
696     goto error1;
697   }
698 
699   for (i = 0; i < osd->number_lines; i++)
700     memset(&osd->lines[i], 0, sizeof(union xosd_line));
701 
702   DEBUG(Dtrace, "misc osd variable initialization");
703   osd->generation = 0;
704   osd->done = 0;
705   osd->pos = XOSD_top;
706   osd->hoffset = 0;
707   osd->align = XOSD_left;
708   osd->voffset = 0;
709   osd->timeout = -1;
710   timerclear(&osd->timeout_start);
711   osd->fontset = NULL;
712   osd->bar_length = -1;         /* old automatic width calculation */
713 
714   DEBUG(Dtrace, "Display query");
715   osd->display = XOpenDisplay(display);
716   if (!osd->display) {
717     xosd_error = "Cannot open display";
718     goto error2;
719   }
720   osd->screen = XDefaultScreen(osd->display);
721 
722   DEBUG(Dtrace, "x shape extension query");
723   if (!XShapeQueryExtension(osd->display, &event_basep, &error_basep)) {
724     xosd_error = "X-Server does not support shape extension";
725     goto error3;
726   }
727 
728   osd->visual = DefaultVisual(osd->display, osd->screen);
729   osd->depth = DefaultDepth(osd->display, osd->screen);
730 
731   DEBUG(Dtrace, "font selection info");
732   xosd_set_font(osd, osd_default_font);
733   if (osd->fontset == NULL) {
734     /*
735      * if we still don't have a fontset, then abort
736      */
737     xosd_error = "Default font not found";
738     goto error3;
739   }
740 
741   DEBUG(Dtrace, "width and height initialization");
742 #ifdef HAVE_XINERAMA
743   if (XineramaQueryExtension(osd->display, &dummy_a, &dummy_b) &&
744       (screeninfo = XineramaQueryScreens(osd->display, &screens)) &&
745       XineramaIsActive(osd->display)) {
746     osd->screen_width = screeninfo[0].width;
747     osd->screen_height = screeninfo[0].height;
748     osd->screen_xpos = screeninfo[0].x_org;
749   } else
750 #endif
751   {
752     osd->screen_width = XDisplayWidth(osd->display, osd->screen);
753     osd->screen_height = XDisplayHeight(osd->display, osd->screen);
754     osd->screen_xpos = 0;
755   }
756 #ifdef HAVE_XINERAMA
757   if (screeninfo)
758     XFree(screeninfo);
759 #endif
760   osd->line_height = 10 /*Dummy value */ ;
761   osd->height = osd->line_height * osd->number_lines;
762 
763   DEBUG(Dtrace, "creating X Window");
764   setwinattr.override_redirect = 1;
765 
766   osd->window = XCreateWindow(osd->display,
767                               XRootWindow(osd->display, osd->screen),
768                               0, 0,
769                               osd->screen_width, osd->height,
770                               0,
771                               osd->depth,
772                               CopyFromParent,
773                               osd->visual, CWOverrideRedirect, &setwinattr);
774   XStoreName(osd->display, osd->window, "XOSD");
775 
776   osd->mask_bitmap =
777     XCreatePixmap(osd->display, osd->window, osd->screen_width,
778                   osd->height, 1);
779   osd->line_bitmap =
780     XCreatePixmap(osd->display, osd->window, osd->screen_width,
781                   osd->line_height, osd->depth);
782 
783   osd->gc = XCreateGC(osd->display, osd->window, GCGraphicsExposures, &xgcv);
784   osd->mask_gc = XCreateGC(osd->display, osd->mask_bitmap, GCGraphicsExposures, &xgcv);
785   osd->mask_gc_back = XCreateGC(osd->display, osd->mask_bitmap, GCGraphicsExposures, &xgcv);
786 
787   XSetBackground(osd->display, osd->gc,
788                  WhitePixel(osd->display, osd->screen));
789 
790   XSetForeground(osd->display, osd->mask_gc_back,
791                  BlackPixel(osd->display, osd->screen));
792   XSetBackground(osd->display, osd->mask_gc_back,
793                  WhitePixel(osd->display, osd->screen));
794 
795   XSetForeground(osd->display, osd->mask_gc,
796                  WhitePixel(osd->display, osd->screen));
797   XSetBackground(osd->display, osd->mask_gc,
798                  BlackPixel(osd->display, osd->screen));
799 
800 
801   DEBUG(Dtrace, "setting colour");
802   xosd_set_colour(osd, osd_default_colour);
803 
804   DEBUG(Dtrace, "stay on top");
805   stay_on_top(osd->display, osd->window);
806 
807   DEBUG(Dtrace, "initializing event thread");
808   pthread_create(&osd->event_thread, NULL, event_loop, osd);
809 
810   return osd;
811 
812 error3:
813   XCloseDisplay(osd->display);
814 error2:
815   free(osd->lines);
816 error1:
817   pthread_cond_destroy(&osd->cond_sync);
818   pthread_cond_destroy(&osd->cond_wait);
819   pthread_mutex_destroy(&osd->mutex_sync);
820   pthread_mutex_destroy(&osd->mutex);
821   close(osd->pipefd[0]);
822   close(osd->pipefd[1]);
823 error0b:
824   free(osd);
825 error0:
826   return NULL;
827 }
828 
829 /* }}} */
830 
831 /* xosd_uninit -- Destroy a xosd "object" {{{
832  * Deprecated: Use xosd_destroy. */
833 int
xosd_uninit(xosd * osd)834 xosd_uninit(xosd * osd)
835 {
836   FUNCTION_START(Dfunction);
837   return xosd_destroy(osd);
838 }
839 
840 /* }}} */
841 
842 /* xosd_destroy -- Destroy a xosd "object" {{{ */
843 int
xosd_destroy(xosd * osd)844 xosd_destroy(xosd * osd)
845 {
846   int i;
847 
848   FUNCTION_START(Dfunction);
849   if (osd == NULL)
850     return -1;
851 
852   DEBUG(Dtrace, "waiting for threads to exit");
853   _xosd_lock(osd);
854   osd->done = 1;
855   _xosd_unlock(osd);
856 
857   DEBUG(Dtrace, "join threads");
858   pthread_join(osd->event_thread, NULL);
859 
860   DEBUG(Dtrace, "freeing X resources");
861   XFreeGC(osd->display, osd->gc);
862   XFreeGC(osd->display, osd->mask_gc);
863   XFreeGC(osd->display, osd->mask_gc_back);
864   XFreePixmap(osd->display, osd->line_bitmap);
865   XFreeFontSet(osd->display, osd->fontset);
866   XFreePixmap(osd->display, osd->mask_bitmap);
867   XDestroyWindow(osd->display, osd->window);
868 
869   XCloseDisplay(osd->display);
870 
871   DEBUG(Dtrace, "freeing lines");
872   for (i = 0; i < osd->number_lines; i++)
873     if (osd->lines[i].type == LINE_text && osd->lines[i].text.string)
874       free(osd->lines[i].text.string);
875   free(osd->lines);
876 
877   DEBUG(Dtrace, "destroying condition and mutex");
878   pthread_cond_destroy(&osd->cond_sync);
879   pthread_cond_destroy(&osd->cond_wait);
880   pthread_mutex_destroy(&osd->mutex_sync);
881   pthread_mutex_destroy(&osd->mutex);
882   close(osd->pipefd[0]);
883   close(osd->pipefd[1]);
884 
885   DEBUG(Dtrace, "freeing osd structure");
886   free(osd);
887 
888   FUNCTION_END(Dfunction);
889   return 0;
890 }
891 
892 /* }}} */
893 
894 /* xosd_set_bar_length  -- Set length of percentage and slider bar {{{ */
895 int
xosd_set_bar_length(xosd * osd,int length)896 xosd_set_bar_length(xosd * osd, int length)
897 {
898   FUNCTION_START(Dfunction);
899   if (osd == NULL)
900     return -1;
901 
902   if (length == 0)
903     return -1;
904   if (length < -1)
905     return -1;
906 
907   osd->bar_length = length;
908 
909   return 0;
910 }
911 
912 /* }}} */
913 
914 /* xosd_display -- Display information {{{ */
915 int
xosd_display(xosd * osd,int line,xosd_command command,...)916 xosd_display(xosd * osd, int line, xosd_command command, ...)
917 {
918   int ret = -1;
919 union xosd_line newline = { type:LINE_blank };
920   va_list a;
921 
922   FUNCTION_START(Dfunction);
923   if (osd == NULL)
924     return -1;
925 
926   if (line < 0 || line >= osd->number_lines) {
927     xosd_error = "xosd_display: Invalid Line Number";
928     return -1;
929   }
930 
931   va_start(a, command);
932   switch (command) {
933   case XOSD_string:
934   case XOSD_printf:
935     {
936       char buf[XOSD_MAX_PRINTF_BUF_SIZE];
937       struct xosd_text *l = &newline.text;
938       char *string = va_arg(a, char *);
939       if (command == XOSD_printf) {
940         if (vsnprintf(buf, sizeof(buf), string, a) >= sizeof(buf)) {
941           xosd_error = "xosd_display: Buffer too small";
942           goto error;
943         }
944         string = buf;
945       }
946       if (string && *string) {
947         ret = strlen(string);
948         l->type = LINE_text;
949         l->string = malloc(ret + 1);
950         memcpy(l->string, string, ret + 1);
951       } else {
952         ret = 0;
953         l->type = LINE_blank;
954       }
955       l->width = -1;
956       break;
957     }
958 
959   case XOSD_percentage:
960   case XOSD_slider:
961     {
962       struct xosd_bar *l = &newline.bar;
963       ret = va_arg(a, int);
964       ret = (ret < 0) ? 0 : (ret > 100) ? 100 : ret;
965       l->type = (command == XOSD_percentage) ? LINE_percentage : LINE_slider;
966       l->value = ret;
967       break;
968     }
969 
970   default:
971     {
972       xosd_error = "xosd_display: Unknown command";
973       goto error;
974     }
975   }
976 
977   _xosd_lock(osd);
978   /* Free old entry */
979   switch (osd->lines[line].type) {
980   case LINE_text:
981     free(osd->lines[line].text.string);
982   case LINE_blank:
983   case LINE_percentage:
984   case LINE_slider:
985     break;
986   }
987   osd->lines[line] = newline;
988   osd->update |= UPD_content | UPD_timer | UPD_show;
989   _xosd_unlock(osd);
990 
991 error:
992   va_end(a);
993   return ret;
994 }
995 
996 /* }}} */
997 
998 /* xosd_is_onscreen -- Returns weather the display is show {{{ */
999 int
xosd_is_onscreen(xosd * osd)1000 xosd_is_onscreen(xosd * osd)
1001 {
1002   FUNCTION_START(Dfunction);
1003   if (osd == NULL)
1004     return -1;
1005   return osd->generation & 1;
1006 }
1007 
1008 /* }}} */
1009 
1010 /* xosd_wait_until_no_display -- Wait until nothing is displayed {{{ */
1011 int
xosd_wait_until_no_display(xosd * osd)1012 xosd_wait_until_no_display(xosd * osd)
1013 {
1014   int generation;
1015   FUNCTION_START(Dfunction);
1016   if (osd == NULL)
1017     return -1;
1018 
1019   if ((generation = osd->generation) & 1)
1020     _wait_until_update(osd, generation);
1021 
1022   FUNCTION_END(Dfunction);
1023   return 0;
1024 }
1025 
1026 /* }}} */
1027 
1028 /* xosd_set_colour -- Change the colour of the display {{{ */
1029 int
xosd_set_colour(xosd * osd,const char * colour)1030 xosd_set_colour(xosd * osd, const char *colour)
1031 {
1032   int retval = 0;
1033 
1034   FUNCTION_START(Dfunction);
1035   if (osd == NULL)
1036     return -1;
1037 
1038   _xosd_lock(osd);
1039   retval = parse_colour(osd, &osd->colour, &osd->pixel, colour);
1040   osd->update |= UPD_lines;
1041   _xosd_unlock(osd);
1042 
1043   return retval;
1044 }
1045 
1046 /* }}} */
1047 
1048 /* xosd_set_shadow_colour -- Change the colour of the shadow {{{ */
1049 int
xosd_set_shadow_colour(xosd * osd,const char * colour)1050 xosd_set_shadow_colour(xosd * osd, const char *colour)
1051 {
1052   int retval = 0;
1053 
1054   FUNCTION_START(Dfunction);
1055   if (osd == NULL)
1056     return -1;
1057 
1058   _xosd_lock(osd);
1059   retval = parse_colour(osd, &osd->shadow_colour, &osd->shadow_pixel, colour);
1060   osd->update |= UPD_lines;
1061   _xosd_unlock(osd);
1062 
1063   return retval;
1064 }
1065 
1066 /* }}} */
1067 
1068 /* xosd_set_outline_colour -- Change the colour of the outline {{{ */
1069 int
xosd_set_outline_colour(xosd * osd,const char * colour)1070 xosd_set_outline_colour(xosd * osd, const char *colour)
1071 {
1072   int retval = 0;
1073 
1074   FUNCTION_START(Dfunction);
1075   if (osd == NULL)
1076     return -1;
1077 
1078   _xosd_lock(osd);
1079   retval =
1080     parse_colour(osd, &osd->outline_colour, &osd->outline_pixel, colour);
1081   osd->update |= UPD_lines;
1082   _xosd_unlock(osd);
1083 
1084   return retval;
1085 }
1086 
1087 /* }}} */
1088 
1089 /* xosd_set_font -- Change the text-display font {{{
1090  * Might return error if fontset can't be created. **/
1091 int
xosd_set_font(xosd * osd,const char * font)1092 xosd_set_font(xosd * osd, const char *font)
1093 {
1094   XFontSet fontset2;
1095   char **missing;
1096   int nmissing;
1097   char *defstr;
1098   int ret = 0;
1099 
1100   FUNCTION_START(Dfunction);
1101   if (osd == NULL)
1102     return -1;
1103   if (font == NULL)
1104     return -1;
1105 
1106   /*
1107    * Try to create the new font. If it doesn't succeed, keep old font.
1108    */
1109   _xosd_lock(osd);
1110   fontset2 = XCreateFontSet(osd->display, font, &missing, &nmissing, &defstr);
1111   XFreeStringList(missing);
1112   if (fontset2 == NULL) {
1113     xosd_error = "Requested font not found";
1114     ret = -1;
1115   } else {
1116     if (osd->fontset != NULL)
1117       XFreeFontSet(osd->display, osd->fontset);
1118     osd->fontset = fontset2;
1119     osd->update |= UPD_font;
1120   }
1121   _xosd_unlock(osd);
1122 
1123   return ret;
1124 }
1125 
1126 /* }}} */
1127 
1128 /* xosd_set_shadow_offset -- Change the offset of the text shadow {{{ */
1129 int
xosd_set_shadow_offset(xosd * osd,int shadow_offset)1130 xosd_set_shadow_offset(xosd * osd, int shadow_offset)
1131 {
1132   FUNCTION_START(Dfunction);
1133   if (osd == NULL)
1134     return -1;
1135   if (shadow_offset < 0)
1136     return -1;
1137 
1138   _xosd_lock(osd);
1139   osd->shadow_offset = shadow_offset;
1140   osd->update |= UPD_font;
1141   _xosd_unlock(osd);
1142 
1143   return 0;
1144 }
1145 
1146 /* }}} */
1147 
1148 /* xosd_set_outline_offset -- Change the offset of the text outline {{{ */
1149 int
xosd_set_outline_offset(xosd * osd,int outline_offset)1150 xosd_set_outline_offset(xosd * osd, int outline_offset)
1151 {
1152   FUNCTION_START(Dfunction);
1153   if (osd == NULL)
1154     return -1;
1155   if (outline_offset < 0)
1156     return -1;
1157 
1158   _xosd_lock(osd);
1159   osd->outline_offset = outline_offset;
1160   osd->update |= UPD_font;
1161   _xosd_unlock(osd);
1162 
1163   return 0;
1164 }
1165 
1166 /* }}} */
1167 
1168 /* xosd_set_vertical_offset -- Change the number of pixels the display is offset from the position {{{ */
1169 int
xosd_set_vertical_offset(xosd * osd,int voffset)1170 xosd_set_vertical_offset(xosd * osd, int voffset)
1171 {
1172   FUNCTION_START(Dfunction);
1173   if (osd == NULL)
1174     return -1;
1175 
1176   _xosd_lock(osd);
1177   osd->voffset = voffset;
1178   osd->update |= UPD_pos;
1179   _xosd_unlock(osd);
1180 
1181   return 0;
1182 }
1183 
1184 /* }}} */
1185 
1186 /* xosd_set_horizontal_offset -- Change the number of pixels the display is offset from the position {{{ */
1187 int
xosd_set_horizontal_offset(xosd * osd,int hoffset)1188 xosd_set_horizontal_offset(xosd * osd, int hoffset)
1189 {
1190   FUNCTION_START(Dfunction);
1191   if (osd == NULL)
1192     return -1;
1193 
1194   _xosd_lock(osd);
1195   osd->hoffset = hoffset;
1196   osd->update |= UPD_pos;
1197   _xosd_unlock(osd);
1198 
1199   return 0;
1200 }
1201 
1202 /* }}} */
1203 
1204 /* xosd_set_pos -- Change the vertical position of the display {{{ */
1205 int
xosd_set_pos(xosd * osd,xosd_pos pos)1206 xosd_set_pos(xosd * osd, xosd_pos pos)
1207 {
1208   FUNCTION_START(Dfunction);
1209   if (osd == NULL)
1210     return -1;
1211 
1212   _xosd_lock(osd);
1213   osd->pos = pos;
1214   osd->update |= UPD_pos;
1215   _xosd_unlock(osd);
1216 
1217   return 0;
1218 }
1219 
1220 /* }}} */
1221 
1222 /* xosd_set_align -- Change the horizontal alignment of the display {{{ */
1223 int
xosd_set_align(xosd * osd,xosd_align align)1224 xosd_set_align(xosd * osd, xosd_align align)
1225 {
1226   FUNCTION_START(Dfunction);
1227   if (osd == NULL)
1228     return -1;
1229 
1230   _xosd_lock(osd);
1231   osd->align = align;
1232   osd->update |= UPD_content;   /* XOSD_right depends on text width */
1233   _xosd_unlock(osd);
1234 
1235   return 0;
1236 }
1237 
1238 /* }}} */
1239 
1240 /* xosd_get_colour -- Gets the RGB value of the display's colour {{{ */
1241 int
xosd_get_colour(xosd * osd,int * red,int * green,int * blue)1242 xosd_get_colour(xosd * osd, int *red, int *green, int *blue)
1243 {
1244   FUNCTION_START(Dfunction);
1245   if (osd == NULL)
1246     return -1;
1247 
1248   if (red)
1249     *red = osd->colour.red;
1250   if (blue)
1251     *blue = osd->colour.blue;
1252   if (green)
1253     *green = osd->colour.green;
1254 
1255   return 0;
1256 }
1257 
1258 /* }}} */
1259 
1260 /* xosd_set_timeout -- Change the time before display is hidden. {{{ */
1261 int
xosd_set_timeout(xosd * osd,int timeout)1262 xosd_set_timeout(xosd * osd, int timeout)
1263 {
1264   FUNCTION_START(Dfunction);
1265   if (osd == NULL)
1266     return -1;
1267   _xosd_lock(osd);
1268   osd->timeout = timeout;
1269   osd->update |= UPD_timer;
1270   _xosd_unlock(osd);
1271   return 0;
1272 }
1273 
1274 /* }}} */
1275 
1276 /* xosd_hide -- hide the display {{{ */
1277 int
xosd_hide(xosd * osd)1278 xosd_hide(xosd * osd)
1279 {
1280   FUNCTION_START(Dfunction);
1281   if (osd == NULL)
1282     return -1;
1283 
1284   if (osd->generation & 1) {
1285     _xosd_lock(osd);
1286     osd->update &= ~UPD_show;
1287     osd->update |= UPD_hide;
1288     _xosd_unlock(osd);
1289     return 0;
1290   }
1291   return -1;
1292 }
1293 
1294 /* }}} */
1295 
1296 /* xosd_show -- Show the display after being hidden {{{ */
1297 int
xosd_show(xosd * osd)1298 xosd_show(xosd * osd)
1299 {
1300   FUNCTION_START(Dfunction);
1301   if (osd == NULL)
1302     return -1;
1303 
1304   if (~osd->generation & 1) {
1305     _xosd_lock(osd);
1306     osd->update &= ~UPD_hide;
1307     osd->update |= UPD_show | UPD_timer;
1308     _xosd_unlock(osd);
1309     return 0;
1310   }
1311   return -1;
1312 }
1313 
1314 /* }}} */
1315 
1316 /* xosd_scroll -- Scroll the display up "lines" number of lines {{{ */
1317 int
xosd_scroll(xosd * osd,int lines)1318 xosd_scroll(xosd * osd, int lines)
1319 {
1320   int i;
1321   union xosd_line *src, *dst;
1322 
1323   FUNCTION_START(Dfunction);
1324   if (osd == NULL)
1325     return -1;
1326   if (lines <= 0 || lines > osd->number_lines)
1327     return -1;
1328 
1329   _xosd_lock(osd);
1330   /* Clear old text */
1331   for (i = 0, src = osd->lines; i < lines; i++, src++)
1332     if (src->type == LINE_text && src->text.string) {
1333       free(src->text.string);
1334       src->text.string = NULL;
1335     }
1336   /* Move following lines forward */
1337   for (dst = osd->lines; i < osd->number_lines; i++)
1338     *dst++ = *src++;
1339   /* Blank new lines */
1340   for (; dst < src; dst++) {
1341     dst->type = LINE_blank;
1342     dst->text.string = NULL;
1343   }
1344   osd->update |= UPD_content;
1345   _xosd_unlock(osd);
1346   return 0;
1347 }
1348 
1349 /* }}} */
1350 
1351 /* xosd_get_number_lines -- Get the maximum number of lines allowed {{{ */
1352 int
xosd_get_number_lines(xosd * osd)1353 xosd_get_number_lines(xosd * osd)
1354 {
1355   FUNCTION_START(Dfunction);
1356   if (osd == NULL)
1357     return -1;
1358 
1359   return osd->number_lines;
1360 }
1361 
1362 /* }}} */
1363 
1364 /* vim: foldmethod=marker tabstop=2 shiftwidth=2 expandtab
1365  */
1366