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