1 /*
2  * Copyright (C) 2000-2019 the xine project
3  *
4  * This file is part of xine, a unix video player.
5  *
6  * xine is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * xine is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
19  *
20  */
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24 
25 #include <stdio.h>
26 #include <string.h>
27 #include <pthread.h>
28 #include <inttypes.h>
29 #include <unistd.h>
30 #include <sys/time.h>
31 #include <errno.h>
32 #include <X11/Xlib.h>
33 
34 #include "_xitk.h"
35 
36 static struct {
37   Display             *display;
38   pthread_t            thread;
39 
40   xitk_widget_t       *widget, *new_widget;
41   int                  visible;
42   int                  running;
43 
44   pthread_mutex_t      mutex;
45 
46   pthread_cond_t       new_cond;
47 
48   pthread_cond_t       timer_cond;
49 
50   int                  prewait;
51   pthread_cond_t       prewait_cond;
52 } tips;
53 
_compute_interval(unsigned int millisecs)54 static struct timespec _compute_interval(unsigned int millisecs) {
55   struct timespec ts;
56 #if _POSIX_TIMERS > 0
57   clock_gettime(CLOCK_REALTIME, &ts);
58   uint64_t ttimer = (uint64_t)ts.tv_sec*1000 + ts.tv_nsec/1000000 + millisecs;
59   ts.tv_sec = ttimer/1000;
60   ts.tv_nsec = (ttimer%1000)*1000000;
61 #else
62   struct timeval tv;
63   gettimeofday(&tv, NULL);
64   uint64_t ttimer = (uint64_t)tv.tv_sec*1000 + tv.tv_usec/1000 + millisecs;
65   ts.tv_sec = ttimer/1000;
66   ts.tv_nsec = (ttimer%1000)*1000000;
67 #endif
68   return ts;
69 }
70 
71 
_tips_loop_thread(void * data)72 static __attribute__((noreturn)) void *_tips_loop_thread(void *data) {
73 
74   tips.running = 1;
75   pthread_mutex_lock(&tips.mutex);
76 
77   while(tips.running) {
78     struct timespec      ts;
79 
80     /* Wait for a new tip to show */
81     if(!tips.new_widget) {
82       while (tips.running) {
83 	ts = _compute_interval(1000);
84 	if (pthread_cond_timedwait(&tips.new_cond, &tips.mutex, &ts) != ETIMEDOUT)
85 	  break;
86       }
87     }
88 
89     /* Exit if we're quitting */
90     if (!tips.running)
91       break;
92 
93     /* Start over if nothing */
94     if(!tips.new_widget)
95       continue;
96 
97     tips.prewait = 1;
98 
99     ts = _compute_interval(500);
100 
101     pthread_cond_timedwait(&tips.prewait_cond, &tips.mutex, &ts);
102 
103     /* Start over if interrupted */
104     if(!tips.prewait)
105       continue;
106 
107     tips.prewait = 0;
108 
109     tips.widget = tips.new_widget;
110     tips.new_widget = NULL;
111 
112     if(tips.widget && (tips.widget->tips_timeout > 0) && tips.widget->tips_string && strlen(tips.widget->tips_string)) {
113       int                  x, y, w, h;
114       xitk_window_t       *xwin;
115       xitk_image_t        *image;
116       xitk_font_t         *fs;
117       unsigned int         cfore, cback;
118       struct timespec      ts;
119 
120       int                  disp_w = xitk_get_display_width();
121       int                  disp_h = xitk_get_display_height();
122 
123       int                  x_margin = 12, y_margin = 6;
124       int                  bottom_gap = 16; /* To avoid mouse cursor overlaying tips on bottom of widget */
125 
126       tips.visible = 1;
127 
128       /* Get parent window position */
129       xitk_get_window_position(tips.display, tips.widget->wl->win, &x, &y, NULL, NULL);
130 
131       x += tips.widget->x;
132       y += tips.widget->y;
133 
134       fs = xitk_font_load_font(tips.display, DEFAULT_FONT_10);
135       xitk_font_set_font(fs, tips.widget->wl->gc);
136 
137       xitk_font_unload_font(fs);
138 
139       cfore = xitk_get_pixel_color_black(tips.widget->imlibdata);
140       cback = xitk_get_pixel_color_lightgray(tips.widget->imlibdata);
141 
142       /* Note: disp_w/3 is max. width, returned image with ALIGN_LEFT will be as small as possible */
143       image = xitk_image_create_image_with_colors_from_string(tips.widget->imlibdata, DEFAULT_FONT_10,
144 							      (disp_w/3), ALIGN_LEFT,
145 							      tips.widget->tips_string, cfore, cback);
146 
147       /* Create the tips window, horizontally centered from parent widget */
148       /* If necessary, adjust position to display it fully on screen      */
149       w = image->width + x_margin;
150       h = image->height + y_margin;
151       x -= ((w - tips.widget->width) >> 1);
152       y += (tips.widget->height + bottom_gap);
153       if(x > disp_w - w)
154 	x = disp_w - w;
155       else if(x < 0)
156 	x = 0;
157       if(y > disp_h - h)
158 	/* 1 px dist to widget prevents odd behavior of mouse pointer when  */
159 	/* pointer is moved slowly from widget to tips, at least under FVWM */
160 	/*                                           v                      */
161 	y -= (tips.widget->height + h + bottom_gap + 1);
162       /* No further alternative to y-position the tips (just either below or above widget) */
163       xwin = xitk_window_create_simple_window(tips.widget->imlibdata, x, y, w, h);
164 
165       /* WM should ignore tips windows */
166       {
167 	XSetWindowAttributes tp_attr;
168 
169 	tp_attr.override_redirect = True;
170 
171         XLOCK (xitk_x_lock_display, tips.display);
172 	XChangeWindowAttributes(tips.display, (xitk_window_get_window(xwin)), CWOverrideRedirect, &tp_attr);
173         XUNLOCK (xitk_x_unlock_display, tips.display);
174 
175       }
176 
177       {
178 	xitk_pixmap_t *bg;
179 	int            width, height;
180 	GC             gc;
181 
182 	xitk_window_get_window_size(xwin, &width, &height);
183 	bg = xitk_image_create_xitk_pixmap(tips.widget->imlibdata, width, height);
184 
185         XLOCK (xitk_x_lock_display, tips.display);
186 	gc = XCreateGC(tips.display, tips.widget->imlibdata->x.base_window, None, None);
187 	XCopyArea(tips.display, (xitk_window_get_background(xwin)), bg->pixmap, gc, 0, 0, width, height, 0, 0);
188         XUNLOCK (xitk_x_unlock_display, tips.display);
189 
190         XLOCK (xitk_x_lock_display, tips.display);
191 	XSetForeground(tips.display, gc, cfore);
192 	XDrawRectangle(tips.display, bg->pixmap, gc, 0, 0, width - 1, height - 1);
193         XUNLOCK (xitk_x_unlock_display, tips.display);
194 
195         XLOCK (xitk_x_lock_display, tips.display);
196 	XSetForeground(tips.display, gc, cback);
197 	XFillRectangle(tips.display, bg->pixmap, gc, 1, 1, width - 2, height - 2);
198 	XCopyArea(tips.display, image->image->pixmap, bg->pixmap,
199 		  gc, 0, 0, image->width, image->height, (width - image->width)>>1, (height - image->height)>>1);
200         XUNLOCK (xitk_x_unlock_display, tips.display);
201 
202 	xitk_window_change_background(tips.widget->imlibdata, xwin, bg->pixmap, width, height);
203 
204 	xitk_image_destroy_xitk_pixmap(bg);
205 
206         XLOCK (xitk_x_lock_display, tips.display);
207 	XFreeGC(tips.display, gc);
208         XUNLOCK (xitk_x_unlock_display, tips.display);
209 
210 	xitk_image_free_image(tips.widget->imlibdata, &image);
211       }
212 
213       XLOCK (xitk_x_lock_display, tips.display);
214       XMapRaised(tips.display, (xitk_window_get_window(xwin)));
215       XUNLOCK (xitk_x_unlock_display, tips.display);
216 
217       ts = _compute_interval(tips.widget->tips_timeout);
218 
219       pthread_cond_timedwait(&tips.timer_cond, &tips.mutex, &ts);
220 
221       xitk_window_destroy_window(tips.widget->imlibdata, xwin);
222 
223       /* We are flushing here, otherwise tips window will stay displayed */
224       XLOCK (xitk_x_lock_display, tips.display);
225       XSync(tips.display, False);
226       XUNLOCK (xitk_x_unlock_display, tips.display);
227 
228       tips.visible = 0;
229     }
230 
231   }
232 
233   pthread_mutex_unlock(&tips.mutex);
234 
235   pthread_exit(NULL);
236 }
237 
238 /*
239  *
240  */
xitk_tips_init(Display * disp)241 void xitk_tips_init(Display *disp) {
242 
243   if(!tips.running) {
244     pthread_attr_t       pth_attrs;
245 #if defined(_POSIX_THREAD_PRIORITY_SCHEDULING) && (_POSIX_THREAD_PRIORITY_SCHEDULING > 0)
246     struct sched_param   pth_params;
247 #endif
248 
249     tips.visible    = 0;
250     tips.display    = disp;
251     tips.widget     = NULL;
252     tips.new_widget = NULL;
253     tips.prewait    = 0;
254 
255     pthread_mutex_init(&tips.mutex, NULL);
256 
257     pthread_cond_init(&tips.new_cond, NULL);
258     pthread_cond_init(&tips.timer_cond, NULL);
259     pthread_cond_init(&tips.prewait_cond, NULL);
260 
261     pthread_attr_init(&pth_attrs);
262 
263 #if defined(_POSIX_THREAD_PRIORITY_SCHEDULING) && (_POSIX_THREAD_PRIORITY_SCHEDULING > 0)
264     pthread_attr_getschedparam(&pth_attrs, &pth_params);
265     pth_params.sched_priority = sched_get_priority_min(SCHED_OTHER);
266     pthread_attr_setschedparam(&pth_attrs, &pth_params);
267 #endif
268 
269     pthread_create(&tips.thread, &pth_attrs, _tips_loop_thread, NULL);
270   }
271 }
272 
273 /*
274  *
275  */
xitk_tips_deinit(void)276 void xitk_tips_deinit(void) {
277   tips.running = 0;
278 
279   pthread_mutex_lock(&tips.mutex);
280   tips.new_widget = NULL;
281   if(tips.prewait) {
282     tips.prewait = 0;
283     pthread_cond_signal(&tips.prewait_cond);
284   }
285   else if(tips.visible)
286     pthread_cond_signal(&tips.timer_cond);
287   else
288     pthread_cond_signal(&tips.new_cond);
289   pthread_mutex_unlock(&tips.mutex);
290 
291   /* Wait until tips are hidden to avoid a race condition causing a segfault when */
292   /* the parent widget is already destroyed before it's referenced during hiding. */
293   while(tips.visible)
294     xitk_usec_sleep(5000);
295 
296   pthread_join(tips.thread, NULL);
297 
298   pthread_mutex_destroy(&tips.mutex);
299 
300   pthread_cond_destroy(&tips.new_cond);
301   pthread_cond_destroy(&tips.timer_cond);
302   pthread_cond_destroy(&tips.prewait_cond);
303 }
304 
305 /*
306  *
307  */
xitk_tips_hide_tips(void)308 void xitk_tips_hide_tips(void) {
309 
310   pthread_mutex_lock(&tips.mutex);
311   if(tips.running) {
312     tips.new_widget = NULL;
313     if(tips.prewait) {
314       tips.prewait = 0;
315       pthread_cond_signal(&tips.prewait_cond);
316     }
317     else if(tips.visible)
318       pthread_cond_signal(&tips.timer_cond);
319   }
320   pthread_mutex_unlock(&tips.mutex);
321 
322   /* Wait until tips are hidden to avoid a race condition causing a segfault when */
323   /* the parent widget is already destroyed before it's referenced during hiding. */
324   while(tips.visible)
325     xitk_usec_sleep(5000);
326 }
327 
328 /*
329  *
330  */
xitk_tips_show_widget_tips(xitk_widget_t * w)331 int xitk_tips_show_widget_tips(xitk_widget_t *w) {
332 
333   /* Don't show when window invisible. This call may occur directly after iconifying window. */
334   if(!xitk_is_window_visible(w->imlibdata->x.disp, w->wl->win))
335     return 0;
336 
337   pthread_mutex_lock(&tips.mutex);
338   if(tips.running) {
339     tips.new_widget = w;
340     if(tips.prewait) {
341       tips.prewait = 0;
342       pthread_cond_signal(&tips.prewait_cond);
343     }
344     else if(tips.visible)
345       pthread_cond_signal(&tips.timer_cond);
346     else
347       pthread_cond_signal(&tips.new_cond);
348   }
349   pthread_mutex_unlock(&tips.mutex);
350   return 1;
351 }
352 
353 /*
354  *
355  */
xitk_tips_set_timeout(xitk_widget_t * w,unsigned long timeout)356 void xitk_tips_set_timeout(xitk_widget_t *w, unsigned long timeout) {
357 
358   if(w == NULL)
359     return;
360 
361   w->tips_timeout = timeout;
362   if(w->type & (WIDGET_GROUP | WIDGET_GROUP_WIDGET)) {
363     widget_event_t  event;
364 
365     event.type         = WIDGET_EVENT_TIPS_TIMEOUT;
366     event.tips_timeout = timeout;
367     (void) w->event(w, &event, NULL);
368   }
369 }
370 
371 /*
372  *
373  */
xitk_tips_set_tips(xitk_widget_t * w,const char * str)374 void xitk_tips_set_tips(xitk_widget_t *w, const char *str) {
375 
376   if((w == NULL) || (str == NULL))
377     return;
378 
379   XITK_FREE(w->tips_string);
380   w->tips_string = strdup(str);
381 
382   /* Special GROUP widget case */
383   if(w->type & WIDGET_GROUP) {
384     if((w->type & WIDGET_GROUP_MASK) & WIDGET_GROUP_INTBOX) {
385       xitk_widget_t *widget = xitk_intbox_get_input_widget(w);
386 
387       xitk_tips_set_tips(widget, str);
388     }
389     else if((w->type & WIDGET_GROUP_MASK) & WIDGET_GROUP_DOUBLEBOX) {
390       xitk_widget_t *widget = xitk_doublebox_get_input_widget(w);
391 
392       xitk_tips_set_tips(widget, str);
393     }
394     else if((w->type & WIDGET_GROUP_MASK) & WIDGET_GROUP_COMBO) {
395       xitk_widget_t *widget = xitk_combo_get_label_widget(w);
396 
397       xitk_tips_set_tips(widget, str);
398     }
399   }
400 
401   /* No timeout, set it to default */
402   if(!w->tips_timeout)
403     xitk_tips_set_timeout(w, xitk_get_tips_timeout());
404 
405 }
406 
407