1 /*
2  *                           0BSD
3  *
4  *                    BSD Zero Clause License
5  *
6  *  Copyright (c) 2019 Hermann Meyer
7  *
8  * Permission to use, copy, modify, and/or distribute this software for any
9  * purpose with or without fee is hereby granted.
10 
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
12  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
13  * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
14  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
16  * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17  * PERFORMANCE OF THIS SOFTWARE.
18  *
19  */
20 
21 
22 #include "xwidget.h"
23 #include "xwidget_private.h"
24 
25 
key_mapping(Display * dpy,XKeyEvent * xkey)26 int key_mapping(Display *dpy, XKeyEvent *xkey) {
27     if (xkey->keycode == XKeysymToKeycode(dpy,XK_Tab))
28         return (xkey->state & ShiftMask) ? 1 : 2;
29     else if (xkey->keycode == XKeysymToKeycode(dpy,XK_Up))
30         return 3;
31     else if (xkey->keycode == XKeysymToKeycode(dpy,XK_Right))
32         return 4;
33     else if (xkey->keycode == XKeysymToKeycode(dpy,XK_Down))
34         return 5;
35     else if (xkey->keycode == XKeysymToKeycode(dpy,XK_Left))
36         return 6;
37     else if (xkey->keycode == XKeysymToKeycode(dpy,XK_Home))
38         return 7;
39     else if (xkey->keycode == XKeysymToKeycode(dpy,XK_Insert))
40         return 8;
41     else if (xkey->keycode == XKeysymToKeycode(dpy,XK_End))
42         return 9;
43     else if (xkey->keycode == XKeysymToKeycode(dpy,XK_Return))
44         return 10;
45     else if (xkey->keycode == XKeysymToKeycode(dpy,XK_BackSpace))
46         return 11;
47     // keypad
48     else if (xkey->keycode == XKeysymToKeycode(dpy,XK_KP_Subtract))
49         return 1;
50     else if (xkey->keycode == XKeysymToKeycode(dpy,XK_KP_Add))
51         return 2;
52     else if (xkey->keycode == XKeysymToKeycode(dpy,XK_KP_Up))
53         return 3;
54     else if (xkey->keycode == XKeysymToKeycode(dpy,XK_KP_Right))
55         return 4;
56     else if (xkey->keycode == XKeysymToKeycode(dpy,XK_KP_Down))
57         return 5;
58     else if (xkey->keycode == XKeysymToKeycode(dpy,XK_KP_Left))
59         return 6;
60     else if (xkey->keycode == XKeysymToKeycode(dpy,XK_KP_Home))
61         return 7;
62     else if (xkey->keycode == XKeysymToKeycode(dpy,XK_KP_Insert))
63         return 8;
64     else if (xkey->keycode == XKeysymToKeycode(dpy,XK_KP_End))
65         return 9;
66     else if (xkey->keycode == XKeysymToKeycode(dpy,XK_KP_Enter))
67         return 10;
68     else return 0;
69 }
70 
destroy_widget(Widget_t * w,Xputty * main)71 void destroy_widget(Widget_t * w, Xputty *main) {
72     int count = childlist_find_child(main->childlist, w);
73     if (count == 0 && main->run == true) {
74         quit(w);
75     } else if(childlist_find_child(main->childlist, w)>=0) {
76         if(w->flags & REUSE_IMAGE) {
77             w->image = NULL;
78         }
79         if(w->flags & HAS_MEM) {
80             w->func.mem_free_callback(w, NULL);
81         }
82         childlist_remove_child(main->childlist, w);
83         int ch = childlist_has_child(w->childlist);
84         if (ch) {
85             int i = ch;
86             for(;i>0;i--) {
87                 destroy_widget(w->childlist->childs[i-1],main);
88             }
89             destroy_widget(w,main);
90         }
91         if(w->flags & IS_WIDGET) {
92             Widget_t *p = (Widget_t *) w->parent;
93             childlist_remove_child(p->childlist, w);
94         }
95         delete_adjustment(w->adj_x);
96         delete_adjustment(w->adj_y);
97         childlist_destroy(w->childlist);
98         cairo_surface_destroy(w->image);
99         cairo_destroy(w->crb);
100         cairo_surface_destroy(w->buffer);
101         cairo_destroy(w->cr);
102         cairo_surface_destroy(w->surface);
103 
104         XDestroyIC(w->xic);
105         XCloseIM(w->xim);
106         XUnmapWindow(w->app->dpy, w->widget);
107         XDestroyWindow(w->app->dpy, w->widget);
108         free(w->childlist);
109         free(w);
110         w = NULL;
111     }
112 }
113 
configure_event(void * w_,void * user_data)114 void configure_event(void *w_, void* user_data) {
115     Widget_t *wid = (Widget_t*)w_;
116     XWindowAttributes attrs;
117     XGetWindowAttributes(wid->app->dpy, (Window)wid->widget, &attrs);
118     if (wid->width != attrs.width || wid->height != attrs.height) {
119         wid->scale.scale_x    = (float)wid->scale.init_width - attrs.width;
120         wid->scale.scale_y    = (float)wid->scale.init_height - attrs.height;
121         wid->scale.cscale_x   = (float)((float)wid->scale.init_width/(float)attrs.width);
122         wid->scale.cscale_y   = (float)((float)wid->scale.init_height/(float)attrs.height);
123         wid->scale.rcscale_x   = (float)((float)attrs.width/(float)wid->scale.init_width);
124         wid->scale.rcscale_y   = (float)((float)attrs.height/(float)wid->scale.init_height);
125         wid->scale.ascale     = wid->scale.cscale_x < wid->scale.cscale_y ?
126                                 wid->scale.cscale_y : wid->scale.cscale_x;
127 
128         _resize_surface(wid, attrs.width, attrs.height);
129 
130         debug_print("Widget_t configure callback width %i height %i\n", attrs.width, attrs.height);
131 
132         _resize_childs(wid);
133     }
134     wid->func.configure_notify_callback(wid,NULL);
135 }
136 
resize_childs(Widget_t * w)137 void resize_childs(Widget_t *w) {
138     _resize_childs(w);
139 }
140 
widget_reset_scale(Widget_t * w)141 void widget_reset_scale(Widget_t *w) {
142     cairo_scale(w->crb, w->scale.cscale_x,w->scale.cscale_y);
143 }
144 
widget_set_scale(Widget_t * w)145 void widget_set_scale(Widget_t *w) {
146     cairo_scale(w->crb, w->scale.rcscale_x,w->scale.rcscale_y);
147 }
148 
create_window(Xputty * app,Window win,int x,int y,int width,int height)149 Widget_t *create_window(Xputty *app, Window win,
150                           int x, int y, int width, int height) {
151 
152     Widget_t *w = (Widget_t*)malloc(sizeof(Widget_t));
153     assert(w != NULL);
154     debug_print("assert(w)\n");
155     XSetWindowAttributes attributes;
156     attributes.save_under = True;
157     attributes.override_redirect = 0;
158 
159     long event_mask = StructureNotifyMask|ExposureMask|KeyPressMask
160                     |EnterWindowMask|LeaveWindowMask|ButtonReleaseMask
161                     |ButtonPressMask|Button1MotionMask;
162 
163 
164 
165     w->widget = XCreateWindow(app->dpy, win , x, y, width, height, 0,
166                             CopyFromParent, InputOutput, CopyFromParent,
167                             CopyFromParent, &attributes);
168     debug_print("XCreateWindow\n");
169 
170     XSetLocaleModifiers("");
171     w->xim = XOpenIM(app->dpy, 0, 0, 0);
172     if(!w->xim){
173         XSetLocaleModifiers("@im=none");
174         w->xim = XOpenIM(app->dpy, 0, 0, 0);
175     }
176 
177     w->xic = XCreateIC(w->xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
178                     XNClientWindow, w->widget, XNFocusWindow,  w->widget, NULL);
179 
180     XSetICFocus(w->xic);
181 
182     XSelectInput(app->dpy, w->widget, event_mask);
183 
184     XSizeHints* win_size_hints;
185     win_size_hints = XAllocSizeHints();
186     win_size_hints->flags =  PMinSize|PBaseSize|PWinGravity;
187     win_size_hints->min_width = width/2;
188     win_size_hints->min_height = height/2;
189     win_size_hints->base_width = width;
190     win_size_hints->base_height = height;
191     win_size_hints->win_gravity = CenterGravity;
192     XSetWMNormalHints(app->dpy, w->widget, win_size_hints);
193     XFree(win_size_hints);
194 
195     w->surface =  cairo_xlib_surface_create (app->dpy, w->widget,
196                   DefaultVisual(app->dpy, DefaultScreen(app->dpy)), width, height);
197 
198     assert(cairo_surface_status(w->surface) == CAIRO_STATUS_SUCCESS);
199     w->cr = cairo_create(w->surface);
200     cairo_select_font_face (w->cr, "Roboto", CAIRO_FONT_SLANT_NORMAL,
201                                CAIRO_FONT_WEIGHT_NORMAL);
202 
203     w->buffer = cairo_surface_create_similar (w->surface,
204                         CAIRO_CONTENT_COLOR_ALPHA, width, height);
205     assert(cairo_surface_status(w->buffer) == CAIRO_STATUS_SUCCESS);
206     w->crb = cairo_create (w->buffer);
207     cairo_select_font_face (w->crb, "Roboto", CAIRO_FONT_SLANT_NORMAL,
208                                CAIRO_FONT_WEIGHT_NORMAL);
209 
210     w->image = NULL;
211 
212     w->flags = IS_WINDOW;
213     w->flags &= ~NO_AUTOREPEAT;
214     w->flags &= ~FAST_REDRAW;
215     w->flags &= ~HIDE_ON_DELETE;
216     w->flags &= ~REUSE_IMAGE;
217     w->flags &= ~NO_PROPAGATE;
218     w->flags &= ~IS_SUBMENU;
219     w->app = app;
220     w->parent = &win;
221     w->parent_struct = NULL;
222     w->label = NULL;
223     memset(w->input_label, 0, 32 * (sizeof w->input_label[0]));
224     w->state = 0;
225     w->double_click = 0;
226     w->data = 0;
227     w->x = x;
228     w->y = y;
229     w->width = width;
230     w->height = height;
231     w->scale.init_x = x;
232     w->scale.init_y = y;
233     w->scale.init_width = width;
234     w->scale.init_height = height;
235     w->scale.scale_x  = 0.0;
236     w->scale.scale_y  = 0.0;
237     w->scale.cscale_x = 1.0;
238     w->scale.cscale_y = 1.0;
239     w->scale.rcscale_x = 1.0;
240     w->scale.rcscale_y = 1.0;
241     w->scale.ascale   = 1.0;
242     w->scale.gravity  = CENTER;
243     w->adj_x = NULL;
244     w->adj_y = NULL;
245     w->adj   = NULL;
246     w->childlist = (Childlist_t*)malloc(sizeof(Childlist_t));
247     assert(w->childlist != NULL);
248     childlist_init(w->childlist);
249     w->event_callback = widget_event_loop;
250     w->func.expose_callback = _dummy_callback;
251     w->func.configure_callback = configure_event;
252     w->func.button_press_callback = _dummy1_callback;
253     w->func.button_release_callback = _dummy1_callback;
254     w->func.double_click_callback = _dummy1_callback;
255     w->func.motion_callback = _dummy1_callback;
256     w->func.adj_callback = transparent_draw;
257     w->func.value_changed_callback = _dummy_callback;
258     w->func.key_press_callback = _dummy1_callback;
259     w->func.key_release_callback = _dummy1_callback;
260     w->func.enter_callback = _dummy_callback;
261     w->func.leave_callback = _dummy_callback;
262     w->func.user_callback = _dummy_callback;
263     w->func.mem_free_callback = _dummy_callback;
264     w->func.configure_notify_callback = _dummy_callback;
265     w->func.map_notify_callback = _dummy_callback;
266     w->func.unmap_notify_callback = _dummy_callback;
267     w->func.dialog_callback = _dummy_callback;
268     w->func.dnd_notify_callback = _dummy_callback;
269     w->xpaste_callback = _dummy_callback;
270 
271     childlist_add_child(app->childlist,w);
272     //XMapWindow(app->dpy, w->widget);
273     debug_print("size of Func_t = %lu\n", sizeof(w->func)/sizeof(void*));
274     return w;
275 }
276 
create_widget(Xputty * app,Widget_t * parent,int x,int y,int width,int height)277 Widget_t *create_widget(Xputty *app, Widget_t *parent,
278                           int x, int y, int width, int height) {
279 
280     Widget_t *w = (Widget_t*)malloc(sizeof(Widget_t));
281     assert(w != NULL);
282     debug_print("assert(w)\n");
283     XSetWindowAttributes attributes;
284     attributes.save_under = True;
285     attributes.override_redirect = True;
286 
287     long event_mask = StructureNotifyMask|ExposureMask|KeyPressMask
288                     |EnterWindowMask|LeaveWindowMask|ButtonReleaseMask
289                     |ButtonPressMask|Button1MotionMask;
290 
291 
292 
293     w->widget = XCreateWindow(app->dpy, parent->widget , x, y, width, height, 0,
294                             CopyFromParent, InputOutput, CopyFromParent,
295                             CopyFromParent|CWOverrideRedirect, &attributes);
296     debug_print("XCreateWindow\n");
297 
298     XSetLocaleModifiers("");
299     w->xim = XOpenIM(app->dpy, 0, 0, 0);
300     if(!w->xim){
301         XSetLocaleModifiers("@im=none");
302         w->xim = XOpenIM(app->dpy, 0, 0, 0);
303     }
304 
305     w->xic = XCreateIC(w->xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
306                     XNClientWindow, w->widget, XNFocusWindow,  w->widget, NULL);
307 
308     XSetICFocus(w->xic);
309 
310     XSelectInput(app->dpy, w->widget, event_mask);
311 
312     w->surface =  cairo_xlib_surface_create (app->dpy, w->widget,
313                   DefaultVisual(app->dpy, DefaultScreen(app->dpy)), width, height);
314     assert(cairo_surface_status(w->surface) == CAIRO_STATUS_SUCCESS);
315     w->cr = cairo_create(w->surface);
316     cairo_select_font_face (w->cr, "Roboto", CAIRO_FONT_SLANT_NORMAL,
317                                CAIRO_FONT_WEIGHT_NORMAL);
318 
319     w->buffer = cairo_surface_create_similar (w->surface,
320                         CAIRO_CONTENT_COLOR_ALPHA, width, height);
321     assert(cairo_surface_status(w->buffer) == CAIRO_STATUS_SUCCESS);
322     w->crb = cairo_create (w->buffer);
323     cairo_select_font_face (w->crb, "Roboto", CAIRO_FONT_SLANT_NORMAL,
324                                CAIRO_FONT_WEIGHT_NORMAL);
325 
326     w->image = NULL;
327 
328     w->flags = IS_WIDGET | USE_TRANSPARENCY;
329     w->flags &= ~NO_AUTOREPEAT;
330     w->flags &= ~FAST_REDRAW;
331     w->flags &= ~HIDE_ON_DELETE;
332     w->flags &= ~REUSE_IMAGE;
333     w->flags &= ~NO_PROPAGATE;
334     w->flags &= ~IS_SUBMENU;
335     w->app = app;
336     w->parent = parent;
337     w->parent_struct = NULL;
338     w->private_struct = NULL;
339     w->label = NULL;
340     memset(w->input_label, 0, 32 * (sizeof w->input_label[0]));
341     w->state = 0;
342     w->double_click = 0;
343     w->data = 0;
344     w->x = x;
345     w->y = y;
346     w->width = width;
347     w->height = height;
348     w->scale.gravity = CENTER;
349     w->scale.init_width = width;
350     w->scale.init_height = height;
351     w->scale.init_x   = x;
352     w->scale.init_y   = y;
353     w->scale.scale_x  = 0.0;
354     w->scale.scale_y  = 0.0;
355     w->scale.cscale_x = 1.0;
356     w->scale.cscale_y = 1.0;
357     w->scale.rcscale_x = 1.0;
358     w->scale.rcscale_y = 1.0;
359     w->scale.ascale   = 1.0;
360     w->adj_x = NULL;
361     w->adj_y = NULL;
362     w->adj   = NULL;
363     w->childlist = (Childlist_t*)malloc(sizeof(Childlist_t));
364     assert(w->childlist != NULL);
365     childlist_init(w->childlist);
366     childlist_add_child(parent->childlist, w);
367     w->event_callback = widget_event_loop;
368     w->func.expose_callback = _dummy_callback;
369     w->func.configure_callback = configure_event;
370     w->func.button_press_callback = _dummy1_callback;
371     w->func.button_release_callback = _dummy1_callback;
372     w->func.double_click_callback = _dummy1_callback;
373     w->func.motion_callback = _dummy1_callback;
374     w->func.adj_callback = transparent_draw;
375     w->func.value_changed_callback = _dummy_callback;
376     w->func.key_press_callback = _dummy1_callback;
377     w->func.key_release_callback = _dummy1_callback;
378     w->func.enter_callback = _dummy_callback;
379     w->func.leave_callback = _dummy_callback;
380     w->func.user_callback = _dummy_callback;
381     w->func.mem_free_callback = _dummy_callback;
382     w->func.configure_notify_callback = _dummy_callback;
383     w->func.map_notify_callback = _dummy_callback;
384     w->func.unmap_notify_callback = _dummy_callback;
385     w->func.dialog_callback = _dummy_callback;
386     w->func.dnd_notify_callback = _dummy_callback;
387     w->xpaste_callback = _dummy_callback;
388 
389     childlist_add_child(app->childlist,w);
390     //XMapWindow(app->dpy, w->widget);
391     debug_print("size of Widget_t = %ld\n", sizeof(struct Widget_t));
392     return w;
393 }
394 
connect_func(void (** event)(),void (* handler)())395 void connect_func(void (**event)(), void (*handler)()) {
396     debug_print("address of a is: %p\n", (void*)event);
397     debug_print("address of b is: %p\n", (void*)handler);
398     *event = handler;
399     debug_print("address of a is: %p\n", (void*)(*event));
400 }
401 
widget_set_title(Widget_t * w,const char * title)402 void widget_set_title(Widget_t *w, const char *title) {
403     XStoreName(w->app->dpy, w->widget, title);
404     XChangeProperty(w->app->dpy,  w->widget,
405     XInternAtom(w->app->dpy, "_NET_WM_NAME", False),
406         XInternAtom(w->app->dpy, "UTF8_STRING", False),
407         8, PropModeReplace, (unsigned char *) title,
408         strlen(title));
409 }
410 
widget_show(Widget_t * w)411 void widget_show(Widget_t *w) {
412     w->func.map_notify_callback(w, NULL);
413     XMapWindow(w->app->dpy, w->widget);
414 }
415 
widget_hide(Widget_t * w)416 void widget_hide(Widget_t *w) {
417     int i=0;
418     for(;i<w->childlist->elem;i++) {
419         widget_hide(w->childlist->childs[i]);
420     }
421     w->func.unmap_notify_callback(w, NULL);
422     XUnmapWindow(w->app->dpy, w->widget);
423 }
424 
widget_show_all(Widget_t * w)425 void widget_show_all(Widget_t *w) {
426     if (w->flags & IS_POPUP || w->flags & IS_TOOLTIP ||
427         w->flags & IS_SUBMENU) {
428         return;
429     } else {
430         w->func.map_notify_callback(w, NULL);
431         XMapWindow(w->app->dpy, w->widget);
432         int i=0;
433         for(;i<w->childlist->elem;i++) {
434             widget_show_all(w->childlist->childs[i]);
435         }
436     }
437 }
438 
pop_widget_show_all(Widget_t * w)439 void pop_widget_show_all(Widget_t *w) {
440     if (w->flags & IS_SUBMENU) return;
441     w->func.map_notify_callback(w, NULL);
442     XMapWindow(w->app->dpy, w->widget);
443     int i=0;
444     for(;i<w->childlist->elem;i++) {
445             pop_widget_show_all(w->childlist->childs[i]);
446     }
447 }
448 
submenu_widget_show_all(Widget_t * w)449 void submenu_widget_show_all(Widget_t *w) {
450     w->func.map_notify_callback(w, NULL);
451     XMapWindow(w->app->dpy, w->widget);
452     int i=0;
453     for(;i<w->childlist->elem;i++) {
454             submenu_widget_show_all(w->childlist->childs[i]);
455     }
456 }
457 
show_tooltip(Widget_t * wid)458 void show_tooltip(Widget_t *wid) {
459     int i = 0;
460     for(;i<wid->childlist->elem;i++) {
461         Widget_t *w = wid->childlist->childs[i];
462         if (w->flags & IS_TOOLTIP) {
463             XWindowAttributes attrs;
464             XGetWindowAttributes(w->app->dpy, (Window)w->widget, &attrs);
465             int width_t = attrs.width;
466             unsigned int mask;
467             int x, y, rx, ry;
468             Window child, root;
469             XQueryPointer(wid->app->dpy, wid->widget, &root, &child, &rx, &ry, &x, &y, &mask);
470             int x1, y1;
471             XTranslateCoordinates( wid->app->dpy, wid->widget, DefaultRootWindow(wid->app->dpy),
472                                                                        x, y, &x1, &y1, &child );
473             int snum = DefaultScreen(wid->app->dpy);
474             int screen_width = DisplayWidth(wid->app->dpy, snum);
475             if (x1+10+width_t > screen_width) x1 = x1-width_t-10;
476 
477             XMoveWindow(w->app->dpy,w->widget,x1+10, y1-10);
478             widget_show(w);
479             break;
480         }
481     }
482 }
483 
hide_tooltip(Widget_t * wid)484 void hide_tooltip(Widget_t *wid) {
485     int i = 0;
486     for(;i<wid->childlist->elem;i++) {
487         Widget_t *w = wid->childlist->childs[i];
488         if (w->flags & IS_TOOLTIP) {
489             widget_hide(w);
490             break;
491         }
492     }
493 }
494 
get_toplevel_widget(Xputty * main)495 Widget_t *get_toplevel_widget(Xputty *main) {
496     return  main->childlist->childs[0];
497 }
498 
expose_widget(Widget_t * w)499 void expose_widget(Widget_t *w) {
500     XEvent exp;
501     memset(&exp, 0, sizeof(exp));
502     exp.type = Expose;
503     exp.xexpose.window = w->widget;
504     XSendEvent(w->app->dpy, w->widget, False, ExposureMask, (XEvent *)&exp);
505 }
506 
transparent_draw(void * w_,void * user_data)507 void transparent_draw(void * w_, void* user_data) {
508     Widget_t *wid = (Widget_t*)w_;
509 
510     cairo_push_group (wid->cr);
511 
512     if (wid->flags & USE_TRANSPARENCY) {
513         Widget_t *parent = (Widget_t*)wid->parent;
514         XWindowAttributes attrs;
515         XGetWindowAttributes(wid->app->dpy, wid->widget, &attrs);
516 
517         debug_print("Widget_t _transparency \n");
518         cairo_set_source_surface (wid->crb, parent->buffer, -attrs.x, -attrs.y);
519         cairo_paint (wid->crb);
520     }
521 
522     cairo_push_group (wid->crb);
523     wid->func.expose_callback(wid, user_data);
524     cairo_pop_group_to_source (wid->crb);
525     cairo_paint (wid->crb);
526 
527     cairo_set_source_surface (wid->cr, wid->buffer,0,0);
528     cairo_paint (wid->cr);
529 
530     cairo_pop_group_to_source (wid->cr);
531     cairo_paint (wid->cr);
532     _propagate_child_expose(wid);
533 }
534 
widget_event_loop(void * w_,void * event,Xputty * main,void * user_data)535 void widget_event_loop(void *w_, void* event, Xputty *main, void* user_data) {
536     Widget_t *wid = (Widget_t*)w_;
537     XEvent *xev = (XEvent*)event;
538 
539     switch(xev->type) {
540         case ConfigureNotify:
541             wid->func.configure_callback(w_, user_data);
542             //transparent_draw(w_, user_data);
543             debug_print("Widget_t ConfigureNotify \n");
544         break;
545 
546         case Expose:
547             if (xev->xexpose.count == 0) {
548                 transparent_draw(w_, user_data);
549                 debug_print("Widget_t Expose \n");
550             }
551         break;
552 
553         case ButtonPress:
554             if (wid->state == 4) break;
555             if (wid->flags & HAS_TOOLTIP) hide_tooltip(wid);
556             _button_press(wid, &xev->xbutton, user_data);
557             debug_print("Widget_t  ButtonPress %i\n", xev->xbutton.button);
558         break;
559 
560         case ButtonRelease:
561         {
562             XButtonEvent *xbutton = &xev->xbutton;
563             _check_grab(wid, xbutton, main);
564             _check_submenu(wid, xbutton, main);
565             if (wid->state == 4) break;
566             if (xbutton->button == Button1) {
567                 if (xbutton->time < wid->double_click+300) {
568                     wid->func.double_click_callback(wid, xbutton, user_data);
569                     break;
570                 }
571                 wid->double_click = xbutton->time;
572             }
573             _has_pointer(wid, &xev->xbutton);
574             if(wid->flags & HAS_POINTER) wid->state = 1;
575             else wid->state = 0;
576             _check_enum(wid, xbutton);
577             wid->func.button_release_callback(w_, xbutton, user_data);
578             debug_print("Widget_t  ButtonRelease %i\n", xev->xbutton.button);
579         }
580         break;
581 
582         case KeyPress:
583             if (wid->state == 4) break;
584             _check_keymap(wid, xev->xkey);
585             wid->func.key_press_callback(w_, &xev->xkey, user_data);
586             debug_print("Widget_t KeyPress %u\n", xev->xkey.keycode);
587         break;
588 
589         case KeyRelease:
590             if (wid->state == 4) break;
591             {
592             unsigned short is_retriggered = 0;
593             if(wid->flags & NO_AUTOREPEAT) {
594                 if (XEventsQueued(main->dpy, QueuedAfterReading)) {
595                     XEvent nev;
596                     XPeekEvent(main->dpy, &nev);
597                     if (nev.type == KeyPress && nev.xkey.time == xev->xkey.time &&
598                         nev.xkey.keycode == xev->xkey.keycode &&
599                         (nev.xkey.keycode > 119 || nev.xkey.keycode < 110)) {
600                         XNextEvent (main->dpy, xev);
601                         is_retriggered = 1;
602                     }
603                 }
604             }
605             if (!is_retriggered) {
606                 wid->func.key_release_callback(w_, &xev->xkey, user_data);
607                 debug_print("Widget_t KeyRelease %u\n", xev->xkey.keycode);
608             }
609         }
610         break;
611 
612         case LeaveNotify:
613             wid->flags &= ~HAS_FOCUS;
614             if (wid->state == 4) break;
615             if(!(xev->xcrossing.state & Button1Mask) &&
616                !(xev->xcrossing.state & Button2Mask) &&
617                !(xev->xcrossing.state & Button3Mask)) {
618                 wid->state = 0;
619                 wid->func.leave_callback(w_, user_data);
620             }
621             if (wid->flags & HAS_TOOLTIP) hide_tooltip(wid);
622             debug_print("Widget_t LeaveNotify \n");
623         break;
624 
625         case EnterNotify:
626             wid->flags |= HAS_FOCUS;
627             if (wid->state == 4) break;
628             if(!(xev->xcrossing.state & Button1Mask) &&
629                !(xev->xcrossing.state & Button2Mask) &&
630                !(xev->xcrossing.state & Button3Mask)) {
631                 wid->state = 1;
632                 wid->func.enter_callback(w_, user_data);
633                 if (wid->flags & HAS_TOOLTIP) show_tooltip(wid);
634                 else _hide_all_tooltips(wid);
635             }
636             debug_print("Widget_t EnterNotify \n");
637         break;
638 
639         case MotionNotify:
640             if (wid->state == 4) break;
641             if (xev->xmotion.state) {
642                 adj_set_motion_state(wid, xev->xmotion.x, xev->xmotion.y);
643             }
644             wid->func.motion_callback(w_,&xev->xmotion, user_data);
645             debug_print("Widget_t MotionNotify x = %i Y = %i \n",xev->xmotion.x,xev->xmotion.y );
646         break;
647 
648         case SelectionRequest:
649             if (xev->xselectionrequest.selection != main->selection) break;
650             send_to_clipboard(wid, xev);
651             break;
652         case SelectionClear:
653             break;
654         case SelectionNotify:
655             if (xev->xselection.property == None) {
656                 wid->xpaste_callback(wid, NULL);
657                 break;
658             }
659             if (xev->xselection.selection == main->selection) {
660                 receive_paste_from_clipboard(wid, xev);
661                 break;
662             }
663             debug_print("Widget_t SelectionNotify\n");
664             handle_drag_data(wid, xev);
665             break;
666 
667         case ClientMessage:
668             if (xev->xclient.message_type == main->XdndPosition) {
669                 debug_print( "XdndPosition\n");
670                 send_dnd_status_event(wid, xev);
671             } else if (xev->xclient.message_type == main->XdndEnter) {
672                 debug_print( "XdndEnter\n");
673                 handle_dnd_enter(main, xev);
674             } else if (xev->xclient.message_type == main->XdndLeave) {
675                 debug_print( "XdndLeave\n");
676                 main->dnd_type   = None;
677                 main->dnd_source_window = 0;
678                 main->dnd_version = 0;
679             } else if (xev->xclient.message_type == main->XdndDrop) {
680                 if ((DND_SOURCE_WIN(xev) != main->dnd_source_window) ||
681                     main->dnd_type == None || main->dnd_source_window == 0) {
682                     break;
683                 }
684                 XConvertSelection(main->dpy, main->XdndSelection,
685                                    main->dnd_type, main->XdndSelection, wid->widget, CurrentTime);
686 
687                 send_dnd_finished_event(wid, xev);
688             } else if (xev->xclient.message_type == XInternAtom(wid->app->dpy, "WIDGET_DESTROY", 1)) {
689                 int ch = childlist_has_child(wid->childlist);
690                 if (ch) {
691                     int i = ch;
692                     for(;i>0;i--) {
693                         quit_widget(wid->childlist->childs[i-1]);
694                     }
695                     quit_widget(wid);
696                 } else {
697                     destroy_widget(wid,main);
698                 }
699             }
700             break;
701         default:
702         break;
703     }
704 }
705 
widget_set_dnd_aware(Widget_t * w)706 void widget_set_dnd_aware(Widget_t *w) {
707     Atom dnd_version = 5;
708     XChangeProperty (w->app->dpy, w->widget, w->app->XdndAware, XA_ATOM,
709                     32, PropModeReplace, (unsigned char*)&dnd_version, 1);
710 }
711 
widget_set_dnd_unaware(Widget_t * w)712 void widget_set_dnd_unaware(Widget_t *w) {
713     XDeleteProperty(w->app->dpy, w->widget, w->app->XdndAware);
714 }
715 
strremove(char * str,const char * sub)716 void strremove(char *str, const char *sub) {
717     char *p, *q, *r;
718     if ((q = r = strstr(str, sub)) != NULL) {
719         size_t len = strlen(sub);
720         while ((r = strstr(p = r + len, sub)) != NULL) {
721             while (p < r)
722                 *q++ = *p++;
723         }
724         while ((*q++ = *p++) != '\0')
725             continue;
726     }
727 }
728 
strdecode(char * target,const char * needle,const char * replacement)729 void strdecode(char *target, const char *needle, const char *replacement) {
730     char buffer[1024] = { 0 };
731     char *insert_point = &buffer[0];
732     const char *tmp = target;
733     size_t needle_len = strlen(needle);
734     size_t repl_len = strlen(replacement);
735 
736     while (1) {
737         const char *p = strstr(tmp, needle);
738         if (p == NULL) {
739             strcpy(insert_point, tmp);
740             break;
741         }
742         memcpy(insert_point, tmp, p - tmp);
743         insert_point += p - tmp;
744         memcpy(insert_point, replacement, repl_len);
745         insert_point += repl_len;
746         tmp = p + needle_len;
747     }
748     strcpy(target, buffer);
749 }
750 
751 
handle_drag_data(Widget_t * w,XEvent * event)752 void handle_drag_data(Widget_t *w, XEvent* event) {
753     if (event->xselection.property != w->app->XdndSelection) return;
754 
755     Atom type;
756     int format;
757     unsigned long  count = 0, remaining;
758     unsigned char* data = 0;
759 
760     XGetWindowProperty(w->app->dpy,w->widget, event->xselection.property,
761                         0, 65536, True, w->app->dnd_type, &type, &format,
762                         &count, &remaining, &data);
763 
764     send_dnd_finished_event (w, event);
765 
766     if (!data || count == 0) {
767         return;
768     }
769     char* dndfile = (char*)data;
770     strdecode(dndfile, "%20", " ");
771     strremove(dndfile, "file://");
772     w->func.dnd_notify_callback(w, (void*)&dndfile);
773     w->app->dnd_type = None;
774     w->app->dnd_source_window = 0;
775     free(data);
776 }
777 
handle_dnd_enter(Xputty * main,XEvent * event)778 void handle_dnd_enter(Xputty *main, XEvent* event) {
779     main->dnd_source_window = DND_SOURCE_WIN(event);
780     main->dnd_version = 0;
781     if (DND_STATUS_ACCEPT(event)) {
782         main->dnd_version = DND_VERSION(event);
783         if (main->dnd_version > 5) return;
784         Atom type = 0;
785         int format;
786         unsigned long count, remaining;
787         unsigned char *data = 0;
788 
789         XGetWindowProperty (main->dpy, main->dnd_source_window, main->XdndTypeList,
790             0, 0x8000000L, False, XA_ATOM, &type, &format, &count, &remaining, &data);
791         if (!data || type != XA_ATOM || format != 32) {
792             if (data) {
793                 XFree (data);
794             }
795             return;
796         }
797         Atom* types = (Atom*)data;
798         for (unsigned long l = 1; l < count; l++) {
799             if ((types[l] == main->dnd_type_uri) ||
800                 (types[l] == main->dnd_type_text) ||
801                 (types[l] == main->dnd_type_utf8)) {
802                 main->dnd_type = types[l];
803                 break;
804             }
805         }
806         if (data) {
807             XFree (data);
808         }
809     } else {
810         for (int i = 2; i < 5; ++i) {
811             if ((event->xclient.data.l[i] == main->dnd_type_uri) ||
812                 (event->xclient.data.l[i] == main->dnd_type_text) ||
813                 (event->xclient.data.l[i] == main->dnd_type_utf8)) {
814                 main->dnd_type = event->xclient.data.l[i];
815                 break;
816             }
817         }
818     }
819 }
820 
send_dnd_status_event(Widget_t * w,XEvent * event)821 void send_dnd_status_event(Widget_t *w, XEvent* event) {
822     XEvent xev;
823     memset (&xev, 0, sizeof (XEvent));
824     xev.xany.type            = ClientMessage;
825     xev.xany.display         = w->app->dpy;
826     xev.xclient.window       = w->app->dnd_source_window;
827     xev.xclient.message_type = w->app->XdndStatus;
828     xev.xclient.format       = 32;
829     xev.xclient.data.l[0]    = event->xany.window;
830     xev.xclient.data.l[1]    = (w->app->dnd_type != None) ? 1 : 0;
831     xev.xclient.data.l[2]    = DND_DROP_TIME(event);
832     xev.xclient.data.l[3]    = 0;
833     xev.xclient.data.l[4]    = w->app->XdndActionCopy;
834     XSendEvent (w->app->dpy, w->app->dnd_source_window, False, NoEventMask, &xev);
835 }
836 
send_dnd_finished_event(Widget_t * w,XEvent * event)837 void send_dnd_finished_event(Widget_t *w, XEvent* event) {
838     if (w->app->dnd_version < 2) {
839         return;
840     }
841     XEvent xev;
842     memset (&xev, 0, sizeof (XEvent));
843     xev.xany.type            = ClientMessage;
844     xev.xany.display         = w->app->dpy;
845     xev.xclient.window       = w->app->dnd_source_window;
846     xev.xclient.message_type = w->app->XdndFinished;
847     xev.xclient.format       = 32;
848     xev.xclient.data.l[0]    = event->xany.window;
849     xev.xclient.data.l[1]    = 1;
850     xev.xclient.data.l[2]    = w->app->XdndActionCopy;
851     XSendEvent (w->app->dpy, w->app->dnd_source_window, False, NoEventMask, &xev);
852 }
853 
have_paste(Widget_t * w)854 int have_paste(Widget_t *w) {
855     return XGetSelectionOwner(w->app->dpy,w->app->selection);
856 }
857 
request_paste_from_clipboard(Widget_t * w)858 void request_paste_from_clipboard(Widget_t *w) {
859     Atom XSEL_DATA = XInternAtom(w->app->dpy, "XSEL_DATA", 0);
860     XConvertSelection(w->app->dpy, w->app->selection, w->app->UTF8, XSEL_DATA, w->widget, CurrentTime);
861 }
862 
receive_paste_from_clipboard(Widget_t * w,XEvent * event)863 void receive_paste_from_clipboard(Widget_t *w, XEvent* event) {
864     if(event->xselection.property) {
865         Atom target;
866         char * data = NULL;
867         int format;
868         unsigned long N, size;
869         XGetWindowProperty(event->xselection.display, event->xselection.requestor,
870             event->xselection.property, 0L,(~0L), 0, AnyPropertyType, &target,
871             &format, &size, &N,(unsigned char**)&data);
872         if(target == w->app->UTF8 || target == XA_STRING) {
873             free(w->app->ctext);
874             w->app->ctext = NULL;
875             w->app->ctext = (unsigned char*)strndup(data, size);
876             XFree(data);
877         }
878         XDeleteProperty(event->xselection.display, event->xselection.requestor, event->xselection.property);
879         w->xpaste_callback(w, (void*)&w->app->ctext);
880     }
881 }
882 
copy_to_clipboard(Widget_t * w,char * text,int size)883 void copy_to_clipboard(Widget_t *w, char* text, int size) {
884     XSetSelectionOwner (w->app->dpy, w->app->selection, w->widget, 0);
885     if (XGetSelectionOwner (w->app->dpy, w->app->selection) != w->widget) return;
886     free(w->app->ctext);
887     w->app->ctext = NULL;
888     w->app->ctext = (unsigned char*)strndup(text, size);
889     w->app->csize = size;
890 }
891 
send_to_clipboard(Widget_t * w,XEvent * event)892 void send_to_clipboard(Widget_t *w, XEvent* event) {
893     XSelectionRequestEvent * xsr = &event->xselectionrequest;
894     XSelectionEvent xev;
895     memset (&xev, 0, sizeof (XSelectionEvent));
896     int R = 0;
897     xev.type = SelectionNotify;
898     xev.display = xsr->display;
899     xev.requestor = xsr->requestor;
900     xev.selection = xsr->selection;
901     xev.time = xsr->time;
902     xev.target = xsr->target;
903     xev.property = xsr->property;
904     if (xev.target == w->app->targets_atom) {
905         R = XChangeProperty (xev.display, xev.requestor, xev.property, XA_ATOM, 32,
906             PropModeReplace, (unsigned char*)&w->app->UTF8, 1);
907     } else if (xev.target == XA_STRING || xev.target == w->app->text_atom) {
908         R = XChangeProperty(xev.display, xev.requestor, xev.property, XA_STRING, 8,
909             PropModeReplace, w->app->ctext, w->app->csize);
910     } else if (xev.target == w->app->UTF8) {
911         R = XChangeProperty(xev.display, xev.requestor, xev.property, w->app->UTF8, 8,
912             PropModeReplace, w->app->ctext, w->app->csize);
913     } else {
914         xev.property = None;
915     }
916     if ((R & 2) == 0) XSendEvent (w->app->dpy, xev.requestor, 0, 0, (XEvent *)&xev);
917     debug_print("send to clipboard %s\n", w->app->ctext);
918 }
919 
send_configure_event(Widget_t * w,int x,int y,int width,int height)920 void send_configure_event(Widget_t *w,int x, int y, int width, int height) {
921     XConfigureEvent notify;
922     memset(&notify, 0, sizeof(notify));
923     notify.type = ConfigureNotify;
924     notify.display = w->app->dpy;
925     notify.send_event = True;
926     notify.event = w->widget;
927     notify.window = w->widget;
928     notify.x = x;
929     notify.y = y;
930     notify.width = width;
931     notify.height = height;
932     notify.border_width = 0;
933     notify.above = None;
934     notify.override_redirect = 1;
935     XSendEvent( w->app->dpy, w->widget, true, StructureNotifyMask, (XEvent*)&notify );
936 }
937 
send_button_press_event(Widget_t * w)938 void send_button_press_event(Widget_t *w) {
939     XEvent event;
940     memset(&event, 0, sizeof(XEvent));
941     XWindowAttributes attr;
942     XGetWindowAttributes(w->app->dpy, w->widget, &attr);
943     event.type = ButtonPress;
944     event.xbutton.same_screen = true;
945     event.xbutton.root = None;
946     event.xbutton.window = w->widget;
947     event.xbutton.subwindow = None;
948     event.xbutton.x = 1;
949     event.xbutton.y = 1;
950     event.xbutton.x_root = attr.x;
951     event.xbutton.y_root = attr.y;
952     event.xbutton.state = 0;
953     event.xbutton.button = Button1;
954     XSendEvent(w->app->dpy, PointerWindow, True, ButtonPressMask, &event);
955 }
956 
send_button_release_event(Widget_t * w)957 void send_button_release_event(Widget_t *w) {
958     XEvent event;
959     memset(&event, 0, sizeof(XEvent));
960     XWindowAttributes attr;
961     XGetWindowAttributes(w->app->dpy, w->widget, &attr);
962     event.type = ButtonRelease;
963     event.xbutton.same_screen = true;
964     event.xbutton.root = None;
965     event.xbutton.window = w->widget;
966     event.xbutton.subwindow = None;
967     event.xbutton.x = 1;
968     event.xbutton.y = 1;
969     event.xbutton.x_root = attr.x;
970     event.xbutton.y_root = attr.y;
971     event.xbutton.state = 0;
972     event.xbutton.button = Button1;
973     XSendEvent(w->app->dpy, PointerWindow, True, ButtonReleaseMask, &event);
974 }
975 
send_systray_message(Widget_t * w)976 void send_systray_message(Widget_t *w) {
977     XEvent event;
978     Screen *xscreen;
979     char buf[256];
980     buf[0]=0;
981 
982     xscreen=DefaultScreenOfDisplay(w->app->dpy);
983     sprintf(buf,"_NET_SYSTEM_TRAY_S%d",XScreenNumberOfScreen (xscreen));
984     Atom selection_atom = XInternAtom (w->app->dpy,buf,0);
985 
986     Window tray = XGetSelectionOwner (w->app->dpy,selection_atom);
987     Atom visualatom = XInternAtom(w->app->dpy, "_NET_SYSTEM_TRAY_VISUAL", False);
988     VisualID value = XVisualIDFromVisual(DefaultVisual(w->app->dpy, DefaultScreen(w->app->dpy)));
989     XChangeProperty(w->app->dpy, w->widget, visualatom, XA_VISUALID, 32,
990             PropModeReplace, (unsigned char*)&value, 1);
991 
992     if ( tray != None)
993         XSelectInput (w->app->dpy,tray,StructureNotifyMask);
994 
995     memset(&event, 0, sizeof(event));
996     event.xclient.type = ClientMessage;
997     event.xclient.window = tray;
998     event.xclient.message_type = XInternAtom (w->app->dpy, "_NET_SYSTEM_TRAY_OPCODE", False );
999     event.xclient.format = 32;
1000     event.xclient.data.l[0] = CurrentTime;
1001     event.xclient.data.l[1] = SYSTEM_TRAY_REQUEST_DOCK;
1002     event.xclient.data.l[2] = w->widget;
1003     event.xclient.data.l[3] = 0;
1004     event.xclient.data.l[4] = 0;
1005 
1006     XSendEvent(w->app->dpy, tray, False, NoEventMask, &event);
1007 }
1008 
quit(Widget_t * w)1009 void quit(Widget_t *w) {
1010     Atom WM_DELETE_WINDOW = XInternAtom(w->app->dpy, "WM_DELETE_WINDOW", True);
1011     XClientMessageEvent xevent;
1012     xevent.type = ClientMessage;
1013     xevent.message_type = WM_DELETE_WINDOW;
1014     xevent.display = w->app->dpy;
1015     xevent.window = get_toplevel_widget(w->app)->widget;
1016     xevent.format = 16;
1017     xevent.data.l[0] = WM_DELETE_WINDOW;
1018     XSendEvent(w->app->dpy, w->widget, 0, 0, (XEvent *)&xevent);
1019 }
1020 
quit_widget(Widget_t * w)1021 void quit_widget(Widget_t *w) {
1022     Atom QUIT_WIDGET = XInternAtom(w->app->dpy, "WIDGET_DESTROY", False);
1023     XClientMessageEvent xevent;
1024     xevent.type = ClientMessage;
1025     xevent.message_type = QUIT_WIDGET;
1026     xevent.display = w->app->dpy;
1027     xevent.window = w->widget;
1028     xevent.format = 16;
1029     xevent.data.l[0] = 1;
1030     XSendEvent(w->app->dpy, w->widget, 0, 0, (XEvent *)&xevent);
1031 }
1032 
1033