From 1a348576400b26dad3a58a81415c4c833fb4915c Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Wed, 16 Dec 2009 15:17:08 -0500 Subject: [PATCH] Make the relative desktop switching actions interactive The desktop popup can now be shown "permanently" in which case it stays until you tell it to hide. --- openbox/actions.c | 7 +- openbox/actions.h | 4 +- openbox/actions/cyclewindows.c | 12 +- openbox/actions/desktop.c | 341 +++++++++++++++++++++++++++------ openbox/screen.c | 20 +- openbox/screen.h | 8 +- 6 files changed, 311 insertions(+), 81 deletions(-) diff --git a/openbox/actions.c b/openbox/actions.c index fda119c8..78546361 100644 --- a/openbox/actions.c +++ b/openbox/actions.c @@ -304,9 +304,12 @@ void actions_run_acts(GSList *acts, if (interactive_act) actions_interactive_cancel_act(); if (act->i_pre) - act->i_pre(act->options); - ok = actions_interactive_begin_act(act, state); + if (!act->i_pre(state, act->options)) + act->i_input = NULL; /* remove the interactivity */ } + /* check again cuz it might have been cancelled */ + if (actions_act_is_interactive(act)) + ok = actions_interactive_begin_act(act, state); } /* fire the action's run function with this data */ diff --git a/openbox/actions.h b/openbox/actions.h index de86b9e1..94fc15cb 100644 --- a/openbox/actions.h +++ b/openbox/actions.h @@ -37,7 +37,9 @@ typedef gboolean (*ObActionsRunFunc)(ObActionsData *data, typedef gpointer (*ObActionsDataSetupFunc)(xmlNodePtr node); /* functions for interactive actions */ -typedef void (*ObActionsIPreFunc)(gpointer options); +/* return TRUE if the action is going to be interactive, or false to change + your mind and make it not */ +typedef gboolean (*ObActionsIPreFunc)(guint initial_state, gpointer options); typedef void (*ObActionsIPostFunc)(gpointer options); typedef gboolean (*ObActionsIInputFunc)(guint initial_state, XEvent *e, diff --git a/openbox/actions/cyclewindows.c b/openbox/actions/cyclewindows.c index 3d021bda..28618ef3 100644 --- a/openbox/actions/cyclewindows.c +++ b/openbox/actions/cyclewindows.c @@ -27,17 +27,17 @@ static gpointer setup_func(xmlNodePtr node, ObActionsIPreFunc *pre, ObActionsIInputFunc *in, ObActionsICancelFunc *c, - ObActionsIPreFunc *post); + ObActionsIPostFunc *post); static gpointer setup_forward_func(xmlNodePtr node, ObActionsIPreFunc *pre, ObActionsIInputFunc *in, ObActionsICancelFunc *c, - ObActionsIPreFunc *post); + ObActionsIPostFunc *post); static gpointer setup_backward_func(xmlNodePtr node, ObActionsIPreFunc *pre, ObActionsIInputFunc *in, ObActionsICancelFunc *c, - ObActionsIPreFunc *post); + ObActionsIPostFunc *post); static void free_func(gpointer options); static gboolean run_func(ObActionsData *data, gpointer options); static gboolean i_input_func(guint initial_state, @@ -58,7 +58,7 @@ static gpointer setup_func(xmlNodePtr node, ObActionsIPreFunc *pre, ObActionsIInputFunc *input, ObActionsICancelFunc *cancel, - ObActionsIPreFunc *post) + ObActionsIPostFunc *post) { xmlNodePtr n; Options *o; @@ -115,7 +115,7 @@ static gpointer setup_forward_func(xmlNodePtr node, ObActionsIPreFunc *pre, ObActionsIInputFunc *input, ObActionsICancelFunc *cancel, - ObActionsIPreFunc *post) + ObActionsIPostFunc *post) { Options *o = setup_func(node, pre, input, cancel, post); o->forward = TRUE; @@ -126,7 +126,7 @@ static gpointer setup_backward_func(xmlNodePtr node, ObActionsIPreFunc *pre, ObActionsIInputFunc *input, ObActionsICancelFunc *cancel, - ObActionsIPreFunc *post) + ObActionsIPostFunc *post) { Options *o = setup_func(node, pre, input, cancel, post); o->forward = FALSE; diff --git a/openbox/actions/desktop.c b/openbox/actions/desktop.c index 27b717b1..14f6ef20 100644 --- a/openbox/actions/desktop.c +++ b/openbox/actions/desktop.c @@ -1,6 +1,7 @@ #include "openbox/actions.h" #include "openbox/screen.h" #include "openbox/client.h" +#include "openbox/openbox.h" #include typedef enum { @@ -24,59 +25,130 @@ typedef struct { } u; gboolean send; gboolean follow; + gboolean interactive; } Options; -static gpointer setup_go_func(xmlNodePtr node); -static gpointer setup_send_func(xmlNodePtr node); +static gpointer setup_go_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post); +static gpointer setup_send_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post); static gboolean run_func(ObActionsData *data, gpointer options); + +static gboolean i_pre_func(guint state, gpointer options); +static gboolean i_input_func(guint initial_state, + XEvent *e, + gpointer options, + gboolean *used); +static void i_post_func(gpointer options); + /* 3.4-compatibility */ static gpointer setup_go_last_func(xmlNodePtr node); static gpointer setup_send_last_func(xmlNodePtr node); static gpointer setup_go_abs_func(xmlNodePtr node); static gpointer setup_send_abs_func(xmlNodePtr node); -static gpointer setup_go_next_func(xmlNodePtr node); -static gpointer setup_send_next_func(xmlNodePtr node); -static gpointer setup_go_prev_func(xmlNodePtr node); -static gpointer setup_send_prev_func(xmlNodePtr node); -static gpointer setup_go_left_func(xmlNodePtr node); -static gpointer setup_send_left_func(xmlNodePtr node); -static gpointer setup_go_right_func(xmlNodePtr node); -static gpointer setup_send_right_func(xmlNodePtr node); -static gpointer setup_go_up_func(xmlNodePtr node); -static gpointer setup_send_up_func(xmlNodePtr node); -static gpointer setup_go_down_func(xmlNodePtr node); -static gpointer setup_send_down_func(xmlNodePtr node); - +static gpointer setup_go_next_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post); +static gpointer setup_send_next_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post); +static gpointer setup_go_prev_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post); +static gpointer setup_send_prev_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post); +static gpointer setup_go_left_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post); +static gpointer setup_send_left_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post); +static gpointer setup_go_right_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post); +static gpointer setup_send_right_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post); +static gpointer setup_go_up_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post); +static gpointer setup_send_up_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post); +static gpointer setup_go_down_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post); +static gpointer setup_send_down_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post); + void action_desktop_startup(void) { - actions_register("GoToDesktop", setup_go_func, g_free, run_func); - actions_register("SendToDesktop", setup_send_func, g_free, run_func); + actions_register_i("GoToDesktop", setup_go_func, g_free, run_func); + actions_register_i("SendToDesktop", setup_send_func, g_free, run_func); /* 3.4-compatibility */ actions_register("DesktopLast", setup_go_last_func, g_free, run_func); actions_register("SendToDesktopLast", setup_send_last_func, g_free, run_func); actions_register("Desktop", setup_go_abs_func, g_free, run_func); actions_register("SendToDesktop", setup_send_abs_func, g_free, run_func); - actions_register("DesktopNext", setup_go_next_func, g_free, run_func); - actions_register("SendToDesktopNext", setup_send_next_func, - g_free, run_func); - actions_register("DesktopPrevious", setup_go_prev_func, g_free, run_func); - actions_register("SendToDesktopPrevious", setup_send_prev_func, - g_free, run_func); - actions_register("DesktopLeft", setup_go_left_func, g_free, run_func); - actions_register("SendToDesktopLeft", setup_send_left_func, - g_free, run_func); - actions_register("DesktopRight", setup_go_right_func, g_free, run_func); - actions_register("SendToDesktopRight", setup_send_right_func, - g_free, run_func); - actions_register("DesktopUp", setup_go_up_func, g_free, run_func); - actions_register("SendToDesktopUp", setup_send_up_func, g_free, run_func); - actions_register("DesktopDown", setup_go_down_func, g_free, run_func); - actions_register("SendToDesktopDown", setup_send_down_func, - g_free, run_func); + actions_register_i("DesktopNext", setup_go_next_func, g_free, run_func); + actions_register_i("SendToDesktopNext", setup_send_next_func, + g_free, run_func); + actions_register_i("DesktopPrevious", setup_go_prev_func, + g_free, run_func); + actions_register_i("SendToDesktopPrevious", setup_send_prev_func, + g_free, run_func); + actions_register_i("DesktopLeft", setup_go_left_func, g_free, run_func); + actions_register_i("SendToDesktopLeft", setup_send_left_func, + g_free, run_func); + actions_register_i("DesktopRight", setup_go_right_func, g_free, run_func); + actions_register_i("SendToDesktopRight", setup_send_right_func, + g_free, run_func); + actions_register_i("DesktopUp", setup_go_up_func, g_free, run_func); + actions_register_i("SendToDesktopUp", setup_send_up_func, + g_free, run_func); + actions_register_i("DesktopDown", setup_go_down_func, g_free, run_func); + actions_register_i("SendToDesktopDown", setup_send_down_func, + g_free, run_func); } -static gpointer setup_go_func(xmlNodePtr node) +static gpointer setup_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post) { xmlNodePtr n; Options *o; @@ -135,18 +207,49 @@ static gpointer setup_go_func(xmlNodePtr node) return o; } -static gpointer setup_send_func(xmlNodePtr node) + +static gpointer setup_go_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post) +{ + Options *o; + + o = setup_func(node, pre, input, cancel, post); + if (o->type == RELATIVE) { + o->interactive = TRUE; + *pre = i_pre_func; + *input = i_input_func; + *post = i_post_func; + } + + return o; +} + +static gpointer setup_send_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post) { xmlNodePtr n; Options *o; - o = setup_go_func(node); + o = setup_func(node, pre, input, cancel, post); o->send = TRUE; o->follow = TRUE; if ((n = obt_parse_find_node(node, "follow"))) o->follow = obt_parse_node_bool(n); + if (o->type == RELATIVE && o->follow) { + o->interactive = TRUE; + *pre = i_pre_func; + *input = i_input_func; + *post = i_post_func; + } + return o; } @@ -188,7 +291,54 @@ static gboolean run_func(ObActionsData *data, gpointer options) actions_client_move(data, FALSE); } - return FALSE; + + return o->interactive; +} + +static gboolean i_input_func(guint initial_state, + XEvent *e, + gpointer options, + gboolean *used) +{ + if (e->type == KeyPress) { + /* Escape cancels no matter what */ + if (ob_keycode_match(e->xkey.keycode, OB_KEY_ESCAPE)) { + return FALSE; + } + + /* There were no modifiers and they pressed enter */ + else if (ob_keycode_match(e->xkey.keycode, OB_KEY_RETURN) && + !initial_state) + { + return FALSE; + } + } + /* They released the modifiers */ + else if (e->type == KeyRelease && initial_state && + (e->xkey.state & initial_state) == 0) + { + return FALSE; + } + + return TRUE; +} + +static gboolean i_pre_func(guint initial_state, gpointer options) +{ + if (!initial_state) { + Options *o = options; + o->interactive = FALSE; + return FALSE; + } + else { + screen_show_desktop_popup(screen_desktop, TRUE); + return TRUE; + } +} + +static void i_post_func(gpointer options) +{ + screen_hide_desktop_popup(); } /* 3.4-compatilibity */ @@ -241,7 +391,11 @@ static gpointer setup_send_abs_func(xmlNodePtr node) return o; } -static void setup_rel(Options *o, xmlNodePtr node, gboolean lin, ObDirection dir) +static void setup_rel(Options *o, xmlNodePtr node, gboolean lin, + ObDirection dir, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsIPostFunc *post) { xmlNodePtr n; @@ -252,88 +406,149 @@ static void setup_rel(Options *o, xmlNodePtr node, gboolean lin, ObDirection dir if ((n = obt_parse_find_node(node, "wrap"))) o->u.rel.wrap = obt_parse_node_bool(n); + + if (input) { + o->interactive = TRUE; + *pre = i_pre_func; + *input = i_input_func; + *post = i_post_func; + } } -static gpointer setup_go_next_func(xmlNodePtr node) +static gpointer setup_go_next_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post) { Options *o = g_new0(Options, 1); - setup_rel(o, node, TRUE, OB_DIRECTION_EAST); + setup_rel(o, node, TRUE, OB_DIRECTION_EAST, pre, input, post); return o; } -static gpointer setup_send_next_func(xmlNodePtr node) +static gpointer setup_send_next_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post) { Options *o = setup_follow(node); - setup_rel(o, node, TRUE, OB_DIRECTION_EAST); + setup_rel(o, node, TRUE, OB_DIRECTION_EAST, + pre, (o->follow ? input : NULL), post); return o; } -static gpointer setup_go_prev_func(xmlNodePtr node) +static gpointer setup_go_prev_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post) { Options *o = g_new0(Options, 1); - setup_rel(o, node, TRUE, OB_DIRECTION_WEST); + setup_rel(o, node, TRUE, OB_DIRECTION_WEST, pre, input, post); return o; } -static gpointer setup_send_prev_func(xmlNodePtr node) +static gpointer setup_send_prev_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post) { Options *o = setup_follow(node); - setup_rel(o, node, TRUE, OB_DIRECTION_WEST); + setup_rel(o, node, TRUE, OB_DIRECTION_WEST, + pre, (o->follow ? input : NULL), post); return o; } -static gpointer setup_go_left_func(xmlNodePtr node) +static gpointer setup_go_left_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post) { Options *o = g_new0(Options, 1); - setup_rel(o, node, FALSE, OB_DIRECTION_WEST); + setup_rel(o, node, FALSE, OB_DIRECTION_WEST, pre, input, post); return o; } -static gpointer setup_send_left_func(xmlNodePtr node) +static gpointer setup_send_left_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post) { Options *o = setup_follow(node); - setup_rel(o, node, FALSE, OB_DIRECTION_WEST); + setup_rel(o, node, FALSE, OB_DIRECTION_WEST, + pre, (o->follow ? input : NULL), post); return o; } -static gpointer setup_go_right_func(xmlNodePtr node) +static gpointer setup_go_right_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post) { Options *o = g_new0(Options, 1); - setup_rel(o, node, FALSE, OB_DIRECTION_EAST); + setup_rel(o, node, FALSE, OB_DIRECTION_EAST, pre, input, post); return o; } -static gpointer setup_send_right_func(xmlNodePtr node) +static gpointer setup_send_right_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post) { Options *o = setup_follow(node); - setup_rel(o, node, FALSE, OB_DIRECTION_EAST); + setup_rel(o, node, FALSE, OB_DIRECTION_EAST, + pre, (o->follow ? input : NULL), post); return o; } -static gpointer setup_go_up_func(xmlNodePtr node) +static gpointer setup_go_up_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post) { Options *o = g_new0(Options, 1); - setup_rel(o, node, FALSE, OB_DIRECTION_NORTH); + setup_rel(o, node, FALSE, OB_DIRECTION_NORTH, pre, input, post); return o; } -static gpointer setup_send_up_func(xmlNodePtr node) +static gpointer setup_send_up_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post) { Options *o = setup_follow(node); - setup_rel(o, node, FALSE, OB_DIRECTION_NORTH); + setup_rel(o, node, FALSE, OB_DIRECTION_NORTH, + pre, (o->follow ? input : NULL), post); return o; } -static gpointer setup_go_down_func(xmlNodePtr node) +static gpointer setup_go_down_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post) { Options *o = g_new0(Options, 1); - setup_rel(o, node, FALSE, OB_DIRECTION_SOUTH); + setup_rel(o, node, FALSE, OB_DIRECTION_SOUTH, pre, input, post); return o; } -static gpointer setup_send_down_func(xmlNodePtr node) +static gpointer setup_send_down_func(xmlNodePtr node, + ObActionsIPreFunc *pre, + ObActionsIInputFunc *input, + ObActionsICancelFunc *cancel, + ObActionsIPostFunc *post) { Options *o = setup_follow(node); - setup_rel(o, node, FALSE, OB_DIRECTION_SOUTH); + setup_rel(o, node, FALSE, OB_DIRECTION_SOUTH, + pre, (o->follow ? input : NULL), post); return o; } diff --git a/openbox/screen.c b/openbox/screen.c index 5050a685..c9819c0a 100644 --- a/openbox/screen.c +++ b/openbox/screen.c @@ -77,6 +77,7 @@ static GSList *struts_right = NULL; static GSList *struts_bottom = NULL; static ObPagerPopup *desktop_popup; +static gboolean desktop_popup_perm; /*! The number of microseconds that you need to be on a desktop before it will replace the remembered "last desktop" */ @@ -347,6 +348,7 @@ void screen_startup(gboolean reconfig) gboolean namesexist = FALSE; desktop_popup = pager_popup_new(); + desktop_popup_perm = FALSE; pager_popup_height(desktop_popup, POPUP_HEIGHT); if (reconfig) { @@ -677,7 +679,7 @@ void screen_set_desktop(guint num, gboolean dofocus) ob_debug("Moving to desktop %d", num+1); if (ob_state() == OB_STATE_RUNNING) - screen_show_desktop_popup(screen_desktop); + screen_show_desktop_popup(screen_desktop, FALSE); /* ignore enter events caused by the move */ ignore_start = event_start_ignore_all_enters(); @@ -701,8 +703,7 @@ void screen_set_desktop(guint num, gboolean dofocus) for (it = g_list_last(stacking_list); it; it = g_list_previous(it)) { if (WINDOW_IS_CLIENT(it->data)) { ObClient *c = it->data; - client_hide(c); - if (c == focus_client) { + if (client_hide(c) && c == focus_client) { /* c was focused and we didn't do fallback clearly so make sure openbox doesnt still consider the window focused. this happens when using NextWindow with allDesktops, since @@ -922,7 +923,7 @@ static gboolean hide_desktop_popup_func(gpointer data) return FALSE; /* don't repeat */ } -void screen_show_desktop_popup(guint d) +void screen_show_desktop_popup(guint d, gboolean perm) { Rect *a; @@ -942,9 +943,13 @@ void screen_show_desktop_popup(guint d) pager_popup_show(desktop_popup, screen_desktop_names[d], d); obt_main_loop_timeout_remove(ob_main_loop, hide_desktop_popup_func); - obt_main_loop_timeout_add(ob_main_loop, config_desktop_popup_time * 1000, - hide_desktop_popup_func, desktop_popup, - g_direct_equal, NULL); + if (!perm && !desktop_popup_perm) + /* only hide if its not already being show permanently */ + obt_main_loop_timeout_add(ob_main_loop, + config_desktop_popup_time * 1000, + hide_desktop_popup_func, desktop_popup, + g_direct_equal, NULL); + g_free(a); } @@ -953,6 +958,7 @@ void screen_hide_desktop_popup(void) obt_main_loop_timeout_remove_data(ob_main_loop, hide_desktop_popup_func, desktop_popup, FALSE); pager_popup_hide(desktop_popup); + desktop_popup_perm = FALSE; } guint screen_find_desktop(guint from, ObDirection dir, diff --git a/openbox/screen.h b/openbox/screen.h index 750de946..94085398 100644 --- a/openbox/screen.h +++ b/openbox/screen.h @@ -75,8 +75,12 @@ void screen_remove_desktop(gboolean current); guint screen_find_desktop(guint from, ObDirection dir, gboolean wrap, gboolean linear); -/*! Show the desktop popup/notification */ -void screen_show_desktop_popup(guint d); +/*! Show the desktop popup/notification + @permanent If TRUE, the popup will stay on the screen until you call + screen_hide_desktop_popup(). Otherwise it will hide after a + delay. + */ +void screen_show_desktop_popup(guint d, gboolean permanent); /*! Hide it */ void screen_hide_desktop_popup(void); -- 2.44.0