1 /*
2  * widgets/common.c - common widget functions or callbacks
3  *
4  * Copyright © 2010 Mason Larobina <mason.larobina@gmail.com>
5  *
6  * This program 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 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program 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, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 #include <gtk/gtk.h>
22 
23 #include "luah.h"
24 #include "globalconf.h"
25 #include "common/luaobject.h"
26 #include "common/lualib.h"
27 #include "widgets/common.h"
28 
29 gboolean
key_press_cb(GtkWidget * UNUSED (win),GdkEventKey * ev,widget_t * w)30 key_press_cb(GtkWidget* UNUSED(win), GdkEventKey *ev, widget_t *w)
31 {
32     lua_State *L = common.L;
33     luaH_object_push(L, w->ref);
34     luaH_modifier_table_push(L, ev->state);
35     luaH_keystr_push(L, ev->keyval);
36     lua_pushboolean(L, ev->send_event);
37     gint ret = luaH_object_emit_signal(L, -4, "key-press", 3, 1);
38     gboolean catch = ret && lua_toboolean(L, -1) ? TRUE : FALSE;
39     lua_pop(L, ret + 1);
40     return catch;
41 }
42 
43 gboolean
button_cb(GtkWidget * UNUSED (win),GdkEventButton * ev,widget_t * w)44 button_cb(GtkWidget* UNUSED(win), GdkEventButton *ev, widget_t *w)
45 {
46     gint ret;
47     lua_State *L = common.L;
48     luaH_object_push(L, w->ref);
49     luaH_modifier_table_push(L, ev->state);
50     lua_pushinteger(L, ev->button);
51 
52     switch (ev->type) {
53       case GDK_2BUTTON_PRESS:
54         ret = luaH_object_emit_signal(L, -3, "button-double-click", 2, 1);
55         break;
56       case GDK_BUTTON_RELEASE:
57         ret = luaH_object_emit_signal(L, -3, "button-release", 2, 1);
58         break;
59       default:
60         ret = luaH_object_emit_signal(L, -3, "button-press", 2, 1);
61         break;
62     }
63 
64     gboolean catch = ret && lua_toboolean(L, -1) ? TRUE : FALSE;
65     lua_pop(L, ret + 1);
66     return catch;
67 }
68 
69 gboolean
scroll_cb(GtkWidget * UNUSED (wid),GdkEventScroll * ev,widget_t * w)70 scroll_cb(GtkWidget *UNUSED(wid), GdkEventScroll *ev, widget_t *w)
71 {
72     double dx, dy;
73     switch (ev->direction) {
74         case GDK_SCROLL_UP:     dx =  0; dy = -1; break;
75         case GDK_SCROLL_DOWN:   dx =  0; dy =  1; break;
76         case GDK_SCROLL_LEFT:   dx = -1; dy =  0; break;
77         case GDK_SCROLL_RIGHT:  dx =  1; dy =  0; break;
78         case GDK_SCROLL_SMOOTH: gdk_event_get_scroll_deltas((GdkEvent*)ev, &dx, &dy); break;
79         default: g_assert_not_reached();
80     }
81 
82     lua_State *L = common.L;
83     luaH_object_push(L, w->ref);
84     luaH_modifier_table_push(L, ev->state);
85     lua_pushnumber(L, dx);
86     lua_pushnumber(L, dy);
87 
88     gboolean ret = luaH_object_emit_signal(L, -4, "scroll", 3, 1);
89     lua_pop(L, ret + 1);
90     return ret;
91 }
92 
93 gboolean
mouse_cb(GtkWidget * UNUSED (win),GdkEventCrossing * ev,widget_t * w)94 mouse_cb(GtkWidget* UNUSED(win), GdkEventCrossing *ev, widget_t *w)
95 {
96     lua_State *L = common.L;
97     luaH_object_push(L, w->ref);
98     luaH_modifier_table_push(L, ev->state);
99 
100     GdkEventType type = ev->type;
101     g_assert(type == GDK_ENTER_NOTIFY || type == GDK_LEAVE_NOTIFY);
102     gint ret = luaH_object_emit_signal(L, -2, type == GDK_ENTER_NOTIFY ? "mouse-enter" : "mouse-leave", 1, 1);
103 
104     gboolean catch = ret && lua_toboolean(L, -1) ? TRUE : FALSE;
105     lua_pop(L, ret + 1);
106     return catch;
107 }
108 
109 gboolean
focus_cb(GtkWidget * UNUSED (win),GdkEventFocus * ev,widget_t * w)110 focus_cb(GtkWidget* UNUSED(win), GdkEventFocus *ev, widget_t *w)
111 {
112     lua_State *L = common.L;
113     luaH_object_push(L, w->ref);
114     gint ret;
115     if (ev->in)
116         ret = luaH_object_emit_signal(L, -1, "focus", 0, 1);
117     else
118         ret = luaH_object_emit_signal(L, -1, "unfocus", 0, 1);
119 
120     /* catch focus event */
121     if (ret && lua_toboolean(L, -1)) {
122         lua_pop(L, ret + 1);
123         return TRUE;
124     }
125 
126     lua_pop(L, ret + 1);
127     /* propagate event further */
128     return FALSE;
129 }
130 
131 /* gtk container add callback */
132 void
add_cb(GtkContainer * UNUSED (c),GtkWidget * widget,widget_t * w)133 add_cb(GtkContainer* UNUSED(c), GtkWidget *widget, widget_t *w)
134 {
135     widget_t *child = GOBJECT_TO_LUAKIT_WIDGET(widget);
136     lua_State *L = common.L;
137     luaH_object_push(L, w->ref);
138     luaH_object_push(L, child->ref);
139     luaH_object_emit_signal(L, -2, "add", 1, 0);
140     lua_pop(L, 1);
141 }
142 
143 void
resize_cb(GtkWidget * UNUSED (win),GdkRectangle * rect,widget_t * w)144 resize_cb(GtkWidget* UNUSED(win), GdkRectangle *rect, widget_t *w)
145 {
146     int width = rect->width, height = rect->height;
147     if (width == w->prev_width && height == w->prev_height)
148         return;
149     w->prev_width = width;
150     w->prev_height = height;
151 
152     lua_State *L = common.L;
153     luaH_object_push(L, w->ref);
154     lua_pushinteger(L, width);
155     lua_pushinteger(L, height);
156     luaH_object_emit_signal(L, -3, "resize", 2, 0);
157     lua_pop(L, 1);
158 }
159 
160 /* gtk container remove callback */
161 void
remove_cb(GtkContainer * UNUSED (c),GtkWidget * widget,widget_t * w)162 remove_cb(GtkContainer* UNUSED(c), GtkWidget *widget, widget_t *w)
163 {
164     widget_t *child = GOBJECT_TO_LUAKIT_WIDGET(widget);
165     lua_State *L = common.L;
166     luaH_object_push(L, w->ref);
167     luaH_object_push(L, child->ref);
168     luaH_object_emit_signal(L, -2, "remove", 1, 0);
169     lua_pop(L, 1);
170 }
171 
172 void
parent_set_cb(GtkWidget * widget,GtkWidget * UNUSED (p),widget_t * w)173 parent_set_cb(GtkWidget *widget, GtkWidget *UNUSED(p), widget_t *w)
174 {
175     lua_State *L = common.L;
176     widget_t *parent = NULL;
177     GtkContainer *new;
178     g_object_get(G_OBJECT(widget), "parent", &new, NULL);
179     luaH_object_push(L, w->ref);
180     if (new && (parent = GOBJECT_TO_LUAKIT_WIDGET(new)))
181         luaH_object_push(L, parent->ref);
182     else
183         lua_pushnil(L);
184     luaH_object_emit_signal(L, -2, "parent-set", 1, 0);
185     lua_pop(L, 1);
186 }
187 
188 void
destroy_cb(GtkWidget * UNUSED (win),widget_t * w)189 destroy_cb(GtkWidget* UNUSED(win), widget_t *w)
190 {
191     /* 1. emit destroy signal */
192     lua_State *L = common.L;
193     luaH_object_push(L, w->ref);
194     luaH_object_emit_signal(L, -1, "destroy", 0, 0);
195     lua_pop(L, 1);
196 
197     /* 2. Call widget destructor */
198     debug("destroy %p (%s)", w, w->info->name);
199     if (w->destructor)
200         w->destructor(w);
201     w->destructor = NULL;
202     w->widget = NULL;
203 
204     /* 3. Allow this Lua instance to be freed */
205     luaH_object_unref(L, w->ref);
206 }
207 
208 gboolean
true_cb()209 true_cb()
210 {
211     return TRUE;
212 }
213 
214 /* set child method for gtk container widgets */
215 gint
luaH_widget_set_child(lua_State * L,widget_t * w)216 luaH_widget_set_child(lua_State *L, widget_t *w)
217 {
218     widget_t *child = luaH_checkwidgetornil(L, 3);
219 
220     /* remove old child */
221     GtkWidget *widget = gtk_bin_get_child(GTK_BIN(w->widget));
222     if (widget) {
223         g_object_ref(G_OBJECT(widget));
224         gtk_container_remove(GTK_CONTAINER(w->widget), GTK_WIDGET(widget));
225     }
226 
227     /* add new child to container */
228     if (child)
229         gtk_container_add(GTK_CONTAINER(w->widget), GTK_WIDGET(child->widget));
230     return 0;
231 }
232 
233 /* get child method for gtk container widgets */
234 gint
luaH_widget_get_child(lua_State * L,widget_t * w)235 luaH_widget_get_child(lua_State *L, widget_t *w)
236 {
237     GtkWidget *widget = gtk_bin_get_child(GTK_BIN(w->widget));
238 
239     if (!widget)
240         return 0;
241 
242     widget_t *child = GOBJECT_TO_LUAKIT_WIDGET(widget);
243     luaH_object_push(L, child->ref);
244     return 1;
245 }
246 
247 gint
luaH_widget_remove(lua_State * L)248 luaH_widget_remove(lua_State *L)
249 {
250     widget_t *w = luaH_checkwidget(L, 1);
251     widget_t *child = luaH_checkwidget(L, 2);
252     g_object_ref(G_OBJECT(child->widget));
253     gtk_container_remove(GTK_CONTAINER(w->widget), GTK_WIDGET(child->widget));
254     return 0;
255 }
256 
257 gint
luaH_widget_get_children(lua_State * L,widget_t * w)258 luaH_widget_get_children(lua_State *L, widget_t *w)
259 {
260     if (!GTK_IS_CONTAINER(w->widget))
261         return 0;
262 
263     GList *children = gtk_container_get_children(GTK_CONTAINER(w->widget));
264     GList *iter = children;
265 
266     /* push table of the containers children onto the stack */
267     lua_newtable(L);
268     for (gint i = 1; iter; iter = iter->next) {
269         luaH_object_push(L, GOBJECT_TO_LUAKIT_WIDGET(iter->data)->ref);
270         lua_rawseti(L, -2, i++);
271     }
272     g_list_free(children);
273     return 1;
274 }
275 
276 gint
luaH_widget_replace(lua_State * L)277 luaH_widget_replace(lua_State *L)
278 {
279     widget_t *och = luaH_checkwidget(L, 1);
280     widget_t *nch = luaH_checkwidget(L, 2);
281 
282     GtkWidget *parent = gtk_widget_get_parent(GTK_WIDGET(och->widget));
283     if (!parent)
284         return 0;
285 
286     guint num_props;
287     GParamSpec **props = gtk_container_class_list_child_properties(
288             G_OBJECT_GET_CLASS(parent), &num_props);
289 
290     GValue *values = g_new0(GValue, num_props);
291     for (guint i = 0; i < num_props; i++)
292     {
293         g_value_init(&values[i], G_PARAM_SPEC_VALUE_TYPE(props[i]));
294         gtk_container_child_get_property(GTK_CONTAINER(parent),
295                 GTK_WIDGET(och->widget), props[i]->name, &values[i]);
296     }
297 
298     g_object_ref(G_OBJECT(och->widget));
299     gtk_container_remove(GTK_CONTAINER(parent), GTK_WIDGET(och->widget));
300 
301     gtk_container_add(GTK_CONTAINER(parent), GTK_WIDGET(nch->widget));
302     for (guint i = 0; i < num_props; i++)
303     {
304         gtk_container_child_set_property(GTK_CONTAINER(parent),
305                 GTK_WIDGET(nch->widget), props[i]->name, &values[i]);
306         g_value_unset(&values[i]);
307     }
308 
309     g_free(props);
310     g_free(values);
311     return 0;
312 }
313 
314 gint
luaH_widget_show(lua_State * L)315 luaH_widget_show(lua_State *L)
316 {
317     widget_t *w = luaH_checkwidget(L, 1);
318     gtk_widget_show(w->widget);
319     return 0;
320 }
321 
322 gint
luaH_widget_hide(lua_State * L)323 luaH_widget_hide(lua_State *L)
324 {
325     widget_t *w = luaH_checkwidget(L, 1);
326     gtk_widget_hide(w->widget);
327     return 0;
328 }
329 
330 gint
luaH_widget_send_key(lua_State * L)331 luaH_widget_send_key(lua_State *L)
332 {
333     widget_t *w = luaH_checkwidget(L, 1);
334     const gchar *key_name = luaL_checkstring(L, 2);
335     if (!lua_istable(L, 3))
336     {
337         lua_newtable(L);
338         lua_insert(L, 3);
339     }
340     const gboolean is_release = lua_toboolean(L, 4);
341 
342     if (!g_utf8_validate(key_name, -1, NULL))
343         return luaL_error(L, "key name isn't a utf-8 string");
344 
345     guint keyval;
346     if (g_utf8_strlen(key_name, -1) == 1)
347         keyval = gdk_unicode_to_keyval(g_utf8_get_char(key_name));
348     else
349         keyval = gdk_keyval_from_name(key_name);
350 
351     if (!keyval || keyval == GDK_KEY_VoidSymbol)
352         return luaL_error(L, "failed to get a valid key value");
353 
354     guint state = 0;
355     GString *state_string = g_string_sized_new(32);
356     lua_pushnil(L);
357     while (lua_next(L, 3)) {
358         const gchar *mod = luaL_checkstring(L, -1);
359         g_string_append_printf(state_string, "%s-", mod);
360 
361 #define MODKEY(modstr, modconst) \
362         if (strcmp(modstr, mod) == 0) { \
363             state = state | GDK_##modconst##_MASK; \
364         }
365 
366         MODKEY("shift", SHIFT);
367         MODKEY("control", CONTROL);
368         MODKEY("lock", LOCK);
369         MODKEY("mod1", MOD1);
370         MODKEY("mod2", MOD2);
371         MODKEY("mod3", MOD3);
372         MODKEY("mod4", MOD4);
373         MODKEY("mod5", MOD5);
374 
375 #undef MODKEY
376 
377         lua_pop(L, 1);
378     }
379 
380     GdkKeymapKey *keys = NULL;
381     gint n_keys;
382     if (!gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(),
383                                            keyval, &keys, &n_keys)) {
384         g_string_free(state_string, TRUE);
385         return luaL_error(L, "cannot type '%s' on current keyboard layout",
386                           key_name);
387     }
388 
389     GdkEvent *event = gdk_event_new(is_release ? GDK_KEY_RELEASE : GDK_KEY_PRESS);
390     GdkEventKey *event_key = (GdkEventKey *) event;
391     event_key->window = gtk_widget_get_window(w->widget);
392     event_key->send_event = TRUE;
393     event_key->time = GDK_CURRENT_TIME;
394     event_key->state = state;
395     event_key->keyval = keyval;
396     event_key->hardware_keycode = keys[0].keycode;
397     event_key->group = keys[0].group;
398 
399     GdkDevice *kbd = NULL;
400 #if GTK_CHECK_VERSION(3,20,0)
401     GdkSeat *seat = gdk_display_get_default_seat(gdk_display_get_default());
402     kbd = gdk_seat_get_keyboard(seat);
403 #else
404     GdkDeviceManager *dev_mgr = gdk_display_get_device_manager(gdk_display_get_default());
405     GList *devices = gdk_device_manager_list_devices(dev_mgr, GDK_DEVICE_TYPE_MASTER);
406     for (GList *dev = devices; dev && !kbd; dev = dev->next)
407         if (gdk_device_get_source(dev->data) == GDK_SOURCE_KEYBOARD)
408             kbd = dev->data;
409     g_list_free(devices);
410 #endif
411     if (!kbd)
412         return luaL_error(L, "failed to find a keyboard device");
413     gdk_event_set_device(event, kbd);
414 
415     gboolean ret;
416     debug("sending key '%s%s' to widget %p", state_string->str, key_name, w->widget);
417     g_signal_emit_by_name(w->widget, is_release ? "key-release-event" : "key-press-event", event, &ret);
418 
419     g_string_free(state_string, TRUE);
420     g_free(keys);
421     return 0;
422 }
423 
424 gint
luaH_widget_set_visible(lua_State * L,widget_t * w)425 luaH_widget_set_visible(lua_State *L, widget_t *w)
426 {
427     gboolean visible = luaH_checkboolean(L, 3);
428     gtk_widget_set_visible(w->widget, visible);
429     if (visible && w->info->tok == L_TK_WINDOW)
430         gdk_window_set_events(gtk_widget_get_window(w->widget),
431                 GDK_ALL_EVENTS_MASK);
432     return 0;
433 }
434 
435 gint
luaH_widget_get_min_size(lua_State * L,widget_t * w)436 luaH_widget_get_min_size(lua_State *L, widget_t *w)
437 {
438     gint width, height;
439     gtk_widget_get_size_request(w->widget, &width, &height);
440 
441     lua_newtable(L);
442 
443     lua_pushliteral(L, "width");
444     lua_pushinteger(L, width);
445     lua_rawset(L, -3);
446 
447     lua_pushliteral(L, "height");
448     lua_pushinteger(L, height);
449     lua_rawset(L, -3);
450 
451     return 1;
452 }
453 
454 gint
luaH_widget_set_min_size(lua_State * L,widget_t * w)455 luaH_widget_set_min_size(lua_State *L, widget_t *w)
456 {
457     luaH_checktable(L, 3);
458 
459     gint width, height;
460     gtk_widget_get_size_request(w->widget, &width, &height);
461 
462     gint top = lua_gettop(L);
463     if (luaH_rawfield(L, 3, "w"))
464         width = lua_tonumber(L, -1);
465     if (luaH_rawfield(L, 3, "h"))
466         height = lua_tonumber(L, -1);
467     lua_settop(L, top);
468 
469     gtk_widget_set_size_request(w->widget, width, height);
470     return 1;
471 }
472 
473 gint
luaH_widget_get_align(lua_State * L,widget_t * w)474 luaH_widget_get_align(lua_State *L, widget_t *w)
475 {
476     GtkAlign halign = gtk_widget_get_halign(GTK_WIDGET(w->widget)),
477              valign = gtk_widget_get_valign_with_baseline(GTK_WIDGET(w->widget));
478     lua_createtable(L, 0, 2);
479     /* set align.h */
480     lua_pushliteral(L, "h");
481     lua_pushnumber(L, halign);
482     lua_rawset(L, -3);
483     /* set align.v */
484     lua_pushliteral(L, "v");
485     lua_pushnumber(L, valign);
486     lua_rawset(L, -3);
487     return 1;
488 }
489 
490 gint
luaH_widget_set_align(lua_State * L,widget_t * w)491 luaH_widget_set_align(lua_State *L, widget_t *w)
492 {
493     luaH_checktable(L, 3);
494     GtkAlign halign = gtk_widget_get_halign(GTK_WIDGET(w->widget)),
495              valign = gtk_widget_get_valign_with_baseline(GTK_WIDGET(w->widget));
496     if (luaH_rawfield(L, 3, "h"))
497         switch (l_tokenize(lua_tostring(L, -1))) {
498             case L_TK_FILL:     halign = GTK_ALIGN_FILL;     break;
499             case L_TK_START:    halign = GTK_ALIGN_START;    break;
500             case L_TK_END:      halign = GTK_ALIGN_END;      break;
501             case L_TK_CENTER:   halign = GTK_ALIGN_CENTER;   break;
502             case L_TK_BASELINE: halign = GTK_ALIGN_BASELINE; break;
503             default:
504                 return luaL_error(L, "Bad alignment value (expected fill, start, end, center, or baseline)");
505         }
506     if (luaH_rawfield(L, 3, "v"))
507         switch (l_tokenize(lua_tostring(L, -1))) {
508             case L_TK_FILL:     valign = GTK_ALIGN_FILL;     break;
509             case L_TK_START:    valign = GTK_ALIGN_START;    break;
510             case L_TK_END:      valign = GTK_ALIGN_END;      break;
511             case L_TK_CENTER:   valign = GTK_ALIGN_CENTER;   break;
512             case L_TK_BASELINE: valign = GTK_ALIGN_BASELINE; break;
513             default:
514                 return luaL_error(L, "Bad alignment value (expected fill, start, end, center, or baseline)");
515         }
516     gtk_widget_set_halign(GTK_WIDGET(w->widget), halign);
517     gtk_widget_set_valign(GTK_WIDGET(w->widget), valign);
518     return 0;
519 }
520 
521 gint
luaH_widget_set_tooltip(lua_State * L,widget_t * w)522 luaH_widget_set_tooltip(lua_State *L, widget_t *w)
523 {
524     gtk_widget_set_tooltip_markup(w->widget, lua_tostring(L, 3) ?: "");
525     return 0;
526 }
527 
528 gint
luaH_widget_get_tooltip(lua_State * L,widget_t * w)529 luaH_widget_get_tooltip(lua_State *L, widget_t *w)
530 {
531     lua_pushstring(L, gtk_widget_get_tooltip_markup(w->widget));
532     return 1;
533 }
534 
535 gint
luaH_widget_get_parent(lua_State * L,widget_t * w)536 luaH_widget_get_parent(lua_State *L, widget_t *w)
537 {
538     GtkWidget *widget = gtk_widget_get_parent(GTK_WIDGET(w->widget));
539 
540     if (!widget)
541         return 0;
542 
543     widget_t *parent = GOBJECT_TO_LUAKIT_WIDGET(widget);
544     luaH_object_push(L, parent->ref);
545     return 1;
546 }
547 
548 gint
luaH_widget_get_focused(lua_State * L,widget_t * w)549 luaH_widget_get_focused(lua_State *L, widget_t *w)
550 {
551     gboolean focused = w->info->tok == L_TK_WINDOW ?
552         gtk_window_has_toplevel_focus(GTK_WINDOW(w->widget)) :
553         gtk_widget_is_focus(w->widget);
554     lua_pushboolean(L, focused);
555     return 1;
556 }
557 
558 gint
luaH_widget_get_visible(lua_State * L,widget_t * w)559 luaH_widget_get_visible(lua_State *L, widget_t *w)
560 {
561     lua_pushboolean(L, gtk_widget_get_visible(w->widget));
562     return 1;
563 }
564 
565 gint
luaH_widget_get_width(lua_State * L,widget_t * w)566 luaH_widget_get_width(lua_State *L, widget_t *w)
567 {
568     lua_pushnumber(L, gtk_widget_get_allocated_width(w->widget));
569     return 1;
570 }
571 
572 gint
luaH_widget_get_height(lua_State * L,widget_t * w)573 luaH_widget_get_height(lua_State *L, widget_t *w)
574 {
575     lua_pushnumber(L, gtk_widget_get_allocated_height(w->widget));
576     return 1;
577 }
578 
579 gint
luaH_widget_focus(lua_State * L)580 luaH_widget_focus(lua_State *L)
581 {
582     widget_t *w = luaH_checkwidget(L, 1);
583 
584     switch (w->info->tok) {
585         case L_TK_WINDOW:
586             /* win:focus() unfocuses anything within that window */
587             gtk_window_set_focus(GTK_WINDOW(w->widget), NULL);
588             break;
589         case L_TK_ENTRY:
590             gtk_entry_grab_focus_without_selecting(GTK_ENTRY(w->widget));
591             break;
592         default:
593             gtk_widget_grab_focus(w->widget);
594             break;
595     }
596 
597     return 0;
598 }
599 
600 gint
luaH_widget_destroy(lua_State * L)601 luaH_widget_destroy(lua_State *L)
602 {
603     widget_t *w = luaH_checkwidget(L, 1);
604     gtk_widget_destroy(GTK_WIDGET(w->widget));
605     return 0;
606 }
607 
608 // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80
609