diff --git a/app/meson.build b/app/meson.build index 13978e91..78060cff 100644 --- a/app/meson.build +++ b/app/meson.build @@ -14,6 +14,7 @@ src = [ 'src/fps_counter.c', 'src/frame_buffer.c', 'src/input_manager.c', + 'src/keyboard_inject.c', 'src/opengl.c', 'src/receiver.c', 'src/recorder.c', diff --git a/app/src/event_converter.c b/app/src/event_converter.c index a3c2da89..71f9de16 100644 --- a/app/src/event_converter.c +++ b/app/src/event_converter.c @@ -3,157 +3,6 @@ #define MAP(FROM, TO) case FROM: *to = TO; return true #define FAIL default: return false -bool -convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) { - switch (from) { - MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN); - MAP(SDL_KEYUP, AKEY_EVENT_ACTION_UP); - FAIL; - } -} - -static enum android_metastate -autocomplete_metastate(enum android_metastate metastate) { - // fill dependent flags - if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) { - metastate |= AMETA_SHIFT_ON; - } - if (metastate & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) { - metastate |= AMETA_CTRL_ON; - } - if (metastate & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) { - metastate |= AMETA_ALT_ON; - } - if (metastate & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) { - metastate |= AMETA_META_ON; - } - - return metastate; -} - -enum android_metastate -convert_meta_state(SDL_Keymod mod) { - enum android_metastate metastate = 0; - if (mod & KMOD_LSHIFT) { - metastate |= AMETA_SHIFT_LEFT_ON; - } - if (mod & KMOD_RSHIFT) { - metastate |= AMETA_SHIFT_RIGHT_ON; - } - if (mod & KMOD_LCTRL) { - metastate |= AMETA_CTRL_LEFT_ON; - } - if (mod & KMOD_RCTRL) { - metastate |= AMETA_CTRL_RIGHT_ON; - } - if (mod & KMOD_LALT) { - metastate |= AMETA_ALT_LEFT_ON; - } - if (mod & KMOD_RALT) { - metastate |= AMETA_ALT_RIGHT_ON; - } - if (mod & KMOD_LGUI) { // Windows key - metastate |= AMETA_META_LEFT_ON; - } - if (mod & KMOD_RGUI) { // Windows key - metastate |= AMETA_META_RIGHT_ON; - } - if (mod & KMOD_NUM) { - metastate |= AMETA_NUM_LOCK_ON; - } - if (mod & KMOD_CAPS) { - metastate |= AMETA_CAPS_LOCK_ON; - } - if (mod & KMOD_MODE) { // Alt Gr - // no mapping? - } - - // fill the dependent fields - return autocomplete_metastate(metastate); -} - -bool -convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, - bool prefer_text) { - switch (from) { - MAP(SDLK_RETURN, AKEYCODE_ENTER); - MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER); - MAP(SDLK_ESCAPE, AKEYCODE_ESCAPE); - MAP(SDLK_BACKSPACE, AKEYCODE_DEL); - MAP(SDLK_TAB, AKEYCODE_TAB); - MAP(SDLK_PAGEUP, AKEYCODE_PAGE_UP); - MAP(SDLK_DELETE, AKEYCODE_FORWARD_DEL); - MAP(SDLK_HOME, AKEYCODE_MOVE_HOME); - MAP(SDLK_END, AKEYCODE_MOVE_END); - MAP(SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN); - MAP(SDLK_RIGHT, AKEYCODE_DPAD_RIGHT); - MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT); - MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN); - MAP(SDLK_UP, AKEYCODE_DPAD_UP); - MAP(SDLK_LCTRL, AKEYCODE_CTRL_LEFT); - MAP(SDLK_RCTRL, AKEYCODE_CTRL_RIGHT); - MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT); - MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT); - } - - if (!(mod & (KMOD_NUM | KMOD_SHIFT))) { - // Handle Numpad events when Num Lock is disabled - // If SHIFT is pressed, a text event will be sent instead - switch(from) { - MAP(SDLK_KP_0, AKEYCODE_INSERT); - MAP(SDLK_KP_1, AKEYCODE_MOVE_END); - MAP(SDLK_KP_2, AKEYCODE_DPAD_DOWN); - MAP(SDLK_KP_3, AKEYCODE_PAGE_DOWN); - MAP(SDLK_KP_4, AKEYCODE_DPAD_LEFT); - MAP(SDLK_KP_6, AKEYCODE_DPAD_RIGHT); - MAP(SDLK_KP_7, AKEYCODE_MOVE_HOME); - MAP(SDLK_KP_8, AKEYCODE_DPAD_UP); - MAP(SDLK_KP_9, AKEYCODE_PAGE_UP); - MAP(SDLK_KP_PERIOD, AKEYCODE_FORWARD_DEL); - } - } - - if (prefer_text && !(mod & KMOD_CTRL)) { - // do not forward alpha and space key events (unless Ctrl is pressed) - return false; - } - - if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) { - return false; - } - // if ALT and META are not pressed, also handle letters and space - switch (from) { - MAP(SDLK_a, AKEYCODE_A); - MAP(SDLK_b, AKEYCODE_B); - MAP(SDLK_c, AKEYCODE_C); - MAP(SDLK_d, AKEYCODE_D); - MAP(SDLK_e, AKEYCODE_E); - MAP(SDLK_f, AKEYCODE_F); - MAP(SDLK_g, AKEYCODE_G); - MAP(SDLK_h, AKEYCODE_H); - MAP(SDLK_i, AKEYCODE_I); - MAP(SDLK_j, AKEYCODE_J); - MAP(SDLK_k, AKEYCODE_K); - MAP(SDLK_l, AKEYCODE_L); - MAP(SDLK_m, AKEYCODE_M); - MAP(SDLK_n, AKEYCODE_N); - MAP(SDLK_o, AKEYCODE_O); - MAP(SDLK_p, AKEYCODE_P); - MAP(SDLK_q, AKEYCODE_Q); - MAP(SDLK_r, AKEYCODE_R); - MAP(SDLK_s, AKEYCODE_S); - MAP(SDLK_t, AKEYCODE_T); - MAP(SDLK_u, AKEYCODE_U); - MAP(SDLK_v, AKEYCODE_V); - MAP(SDLK_w, AKEYCODE_W); - MAP(SDLK_x, AKEYCODE_X); - MAP(SDLK_y, AKEYCODE_Y); - MAP(SDLK_z, AKEYCODE_Z); - MAP(SDLK_SPACE, AKEYCODE_SPACE); - FAIL; - } -} - enum android_motionevent_buttons convert_mouse_buttons(uint32_t state) { enum android_motionevent_buttons buttons = 0; diff --git a/app/src/event_converter.h b/app/src/event_converter.h index d28e9fdc..71e8edec 100644 --- a/app/src/event_converter.h +++ b/app/src/event_converter.h @@ -8,16 +8,6 @@ #include "control_msg.h" -bool -convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to); - -enum android_metastate -convert_meta_state(SDL_Keymod mod); - -bool -convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, - bool prefer_text); - enum android_motionevent_buttons convert_mouse_buttons(uint32_t state); diff --git a/app/src/input_manager.c b/app/src/input_manager.c index a5d0ad07..764760cf 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -53,15 +53,15 @@ is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) { void input_manager_init(struct input_manager *im, struct controller *controller, - struct screen *screen, + struct screen *screen, struct sc_key_processor *kp, const struct scrcpy_options *options) { + assert(!options->control || (kp && kp->ops)); + im->controller = controller; im->screen = screen; - im->repeat = 0; + im->kp = kp; im->control = options->control; - im->forward_key_repeat = options->forward_key_repeat; - im->prefer_text = options->prefer_text; im->forward_all_clicks = options->forward_all_clicks; im->legacy_paste = options->legacy_paste; @@ -323,26 +323,8 @@ input_manager_process_text_input(struct input_manager *im, // A shortcut must never generate text events return; } - if (!im->prefer_text) { - char c = event->text[0]; - if (isalpha(c) || c == ' ') { - assert(event->text[1] == '\0'); - // letters and space are handled as raw key event - return; - } - } - struct control_msg msg; - msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; - msg.inject_text.text = strdup(event->text); - if (!msg.inject_text.text) { - LOGW("Could not strdup input text"); - return; - } - if (!controller_push_msg(im->controller, &msg)) { - free(msg.inject_text.text); - LOGW("Could not request 'inject text'"); - } + im->kp->ops->process_text(im->kp, event); } static bool @@ -375,27 +357,6 @@ inverse_point(struct point point, struct size size) { return point; } -static bool -convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to, - bool prefer_text, uint32_t repeat) { - to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE; - - if (!convert_keycode_action(from->type, &to->inject_keycode.action)) { - return false; - } - - uint16_t mod = from->keysym.mod; - if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod, - prefer_text)) { - return false; - } - - to->inject_keycode.repeat = repeat; - to->inject_keycode.metastate = convert_meta_state(mod); - - return true; -} - static void input_manager_process_key(struct input_manager *im, const SDL_KeyboardEvent *event) { @@ -549,15 +510,6 @@ input_manager_process_key(struct input_manager *im, return; } - if (event->repeat) { - if (!im->forward_key_repeat) { - return; - } - ++im->repeat; - } else { - im->repeat = 0; - } - if (ctrl && !shift && keycode == SDLK_v && down && !repeat) { if (im->legacy_paste) { // inject the text as input events @@ -569,12 +521,7 @@ input_manager_process_key(struct input_manager *im, set_device_clipboard(controller, false); } - struct control_msg msg; - if (convert_input_key(event, &msg, im->prefer_text, im->repeat)) { - if (!controller_push_msg(controller, &msg)) { - LOGW("Could not request 'inject keycode'"); - } - } + im->kp->ops->process_key(im->kp, event); } static bool diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 1dd7825f..cdd32295 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -11,18 +11,15 @@ #include "fps_counter.h" #include "scrcpy.h" #include "screen.h" +#include "trait/key_processor.h" struct input_manager { struct controller *controller; struct screen *screen; - // SDL reports repeated events as a boolean, but Android expects the actual - // number of repetitions. This variable keeps track of the count. - unsigned repeat; + struct sc_key_processor *kp; bool control; - bool forward_key_repeat; - bool prefer_text; bool forward_all_clicks; bool legacy_paste; @@ -43,7 +40,8 @@ struct input_manager { void input_manager_init(struct input_manager *im, struct controller *controller, - struct screen *screen, const struct scrcpy_options *options); + struct screen *screen, struct sc_key_processor *kp, + const struct scrcpy_options *options); bool input_manager_handle_event(struct input_manager *im, SDL_Event *event); diff --git a/app/src/keyboard_inject.c b/app/src/keyboard_inject.c new file mode 100644 index 00000000..bcc85da8 --- /dev/null +++ b/app/src/keyboard_inject.c @@ -0,0 +1,254 @@ +#include "keyboard_inject.h" + +#include +#include + +#include "android/input.h" +#include "control_msg.h" +#include "controller.h" +#include "util/log.h" + +/** Downcast key processor to sc_keyboard_inject */ +#define DOWNCAST(KP) \ + container_of(KP, struct sc_keyboard_inject, key_processor) + +#define MAP(FROM, TO) case FROM: *to = TO; return true +#define FAIL default: return false +static bool +convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) { + switch (from) { + MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN); + MAP(SDL_KEYUP, AKEY_EVENT_ACTION_UP); + FAIL; + } +} + +static bool +convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, + bool prefer_text) { + switch (from) { + MAP(SDLK_RETURN, AKEYCODE_ENTER); + MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER); + MAP(SDLK_ESCAPE, AKEYCODE_ESCAPE); + MAP(SDLK_BACKSPACE, AKEYCODE_DEL); + MAP(SDLK_TAB, AKEYCODE_TAB); + MAP(SDLK_PAGEUP, AKEYCODE_PAGE_UP); + MAP(SDLK_DELETE, AKEYCODE_FORWARD_DEL); + MAP(SDLK_HOME, AKEYCODE_MOVE_HOME); + MAP(SDLK_END, AKEYCODE_MOVE_END); + MAP(SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN); + MAP(SDLK_RIGHT, AKEYCODE_DPAD_RIGHT); + MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT); + MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN); + MAP(SDLK_UP, AKEYCODE_DPAD_UP); + MAP(SDLK_LCTRL, AKEYCODE_CTRL_LEFT); + MAP(SDLK_RCTRL, AKEYCODE_CTRL_RIGHT); + MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT); + MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT); + } + + if (!(mod & (KMOD_NUM | KMOD_SHIFT))) { + // Handle Numpad events when Num Lock is disabled + // If SHIFT is pressed, a text event will be sent instead + switch(from) { + MAP(SDLK_KP_0, AKEYCODE_INSERT); + MAP(SDLK_KP_1, AKEYCODE_MOVE_END); + MAP(SDLK_KP_2, AKEYCODE_DPAD_DOWN); + MAP(SDLK_KP_3, AKEYCODE_PAGE_DOWN); + MAP(SDLK_KP_4, AKEYCODE_DPAD_LEFT); + MAP(SDLK_KP_6, AKEYCODE_DPAD_RIGHT); + MAP(SDLK_KP_7, AKEYCODE_MOVE_HOME); + MAP(SDLK_KP_8, AKEYCODE_DPAD_UP); + MAP(SDLK_KP_9, AKEYCODE_PAGE_UP); + MAP(SDLK_KP_PERIOD, AKEYCODE_FORWARD_DEL); + } + } + + if (prefer_text && !(mod & KMOD_CTRL)) { + // do not forward alpha and space key events (unless Ctrl is pressed) + return false; + } + + if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) { + return false; + } + // if ALT and META are not pressed, also handle letters and space + switch (from) { + MAP(SDLK_a, AKEYCODE_A); + MAP(SDLK_b, AKEYCODE_B); + MAP(SDLK_c, AKEYCODE_C); + MAP(SDLK_d, AKEYCODE_D); + MAP(SDLK_e, AKEYCODE_E); + MAP(SDLK_f, AKEYCODE_F); + MAP(SDLK_g, AKEYCODE_G); + MAP(SDLK_h, AKEYCODE_H); + MAP(SDLK_i, AKEYCODE_I); + MAP(SDLK_j, AKEYCODE_J); + MAP(SDLK_k, AKEYCODE_K); + MAP(SDLK_l, AKEYCODE_L); + MAP(SDLK_m, AKEYCODE_M); + MAP(SDLK_n, AKEYCODE_N); + MAP(SDLK_o, AKEYCODE_O); + MAP(SDLK_p, AKEYCODE_P); + MAP(SDLK_q, AKEYCODE_Q); + MAP(SDLK_r, AKEYCODE_R); + MAP(SDLK_s, AKEYCODE_S); + MAP(SDLK_t, AKEYCODE_T); + MAP(SDLK_u, AKEYCODE_U); + MAP(SDLK_v, AKEYCODE_V); + MAP(SDLK_w, AKEYCODE_W); + MAP(SDLK_x, AKEYCODE_X); + MAP(SDLK_y, AKEYCODE_Y); + MAP(SDLK_z, AKEYCODE_Z); + MAP(SDLK_SPACE, AKEYCODE_SPACE); + FAIL; + } +} + +static enum android_metastate +autocomplete_metastate(enum android_metastate metastate) { + // fill dependent flags + if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) { + metastate |= AMETA_SHIFT_ON; + } + if (metastate & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) { + metastate |= AMETA_CTRL_ON; + } + if (metastate & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) { + metastate |= AMETA_ALT_ON; + } + if (metastate & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) { + metastate |= AMETA_META_ON; + } + + return metastate; +} + +static enum android_metastate +convert_meta_state(SDL_Keymod mod) { + enum android_metastate metastate = 0; + if (mod & KMOD_LSHIFT) { + metastate |= AMETA_SHIFT_LEFT_ON; + } + if (mod & KMOD_RSHIFT) { + metastate |= AMETA_SHIFT_RIGHT_ON; + } + if (mod & KMOD_LCTRL) { + metastate |= AMETA_CTRL_LEFT_ON; + } + if (mod & KMOD_RCTRL) { + metastate |= AMETA_CTRL_RIGHT_ON; + } + if (mod & KMOD_LALT) { + metastate |= AMETA_ALT_LEFT_ON; + } + if (mod & KMOD_RALT) { + metastate |= AMETA_ALT_RIGHT_ON; + } + if (mod & KMOD_LGUI) { // Windows key + metastate |= AMETA_META_LEFT_ON; + } + if (mod & KMOD_RGUI) { // Windows key + metastate |= AMETA_META_RIGHT_ON; + } + if (mod & KMOD_NUM) { + metastate |= AMETA_NUM_LOCK_ON; + } + if (mod & KMOD_CAPS) { + metastate |= AMETA_CAPS_LOCK_ON; + } + if (mod & KMOD_MODE) { // Alt Gr + // no mapping? + } + + // fill the dependent fields + return autocomplete_metastate(metastate); +} + +static bool +convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to, + bool prefer_text, uint32_t repeat) { + to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE; + + if (!convert_keycode_action(from->type, &to->inject_keycode.action)) { + return false; + } + + uint16_t mod = from->keysym.mod; + if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod, + prefer_text)) { + return false; + } + + to->inject_keycode.repeat = repeat; + to->inject_keycode.metastate = convert_meta_state(mod); + + return true; +} + +static void +sc_key_processor_process_key(struct sc_key_processor *kp, + const SDL_KeyboardEvent *event) { + struct sc_keyboard_inject *ki = DOWNCAST(kp); + + if (event->repeat) { + if (!ki->forward_key_repeat) { + return; + } + ++ki->repeat; + } else { + ki->repeat = 0; + } + + struct control_msg msg; + if (convert_input_key(event, &msg, ki->prefer_text, ki->repeat)) { + if (!controller_push_msg(ki->controller, &msg)) { + LOGW("Could not request 'inject keycode'"); + } + } +} + +static void +sc_key_processor_process_text(struct sc_key_processor *kp, + const SDL_TextInputEvent *event) { + struct sc_keyboard_inject *ki = DOWNCAST(kp); + + if (!ki->prefer_text) { + char c = event->text[0]; + if (isalpha(c) || c == ' ') { + assert(event->text[1] == '\0'); + // letters and space are handled as raw key event + return; + } + } + + struct control_msg msg; + msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; + msg.inject_text.text = strdup(event->text); + if (!msg.inject_text.text) { + LOGW("Could not strdup input text"); + return; + } + if (!controller_push_msg(ki->controller, &msg)) { + free(msg.inject_text.text); + LOGW("Could not request 'inject text'"); + } +} + +void +sc_keyboard_inject_init(struct sc_keyboard_inject *ki, + struct controller *controller, + const struct scrcpy_options *options) { + ki->controller = controller; + ki->prefer_text = options->prefer_text; + ki->forward_key_repeat = options->forward_key_repeat; + + ki->repeat = 0; + + static const struct sc_key_processor_ops ops = { + .process_key = sc_key_processor_process_key, + .process_text = sc_key_processor_process_text, + }; + + ki->key_processor.ops = &ops; +} diff --git a/app/src/keyboard_inject.h b/app/src/keyboard_inject.h new file mode 100644 index 00000000..e59de46d --- /dev/null +++ b/app/src/keyboard_inject.h @@ -0,0 +1,30 @@ +#ifndef SC_KEYBOARD_INJECT_H +#define SC_KEYBOARD_INJECT_H + +#include "common.h" + +#include + +#include "controller.h" +#include "scrcpy.h" +#include "trait/key_processor.h" + +struct sc_keyboard_inject { + struct sc_key_processor key_processor; // key processor trait + + struct controller *controller; + + // SDL reports repeated events as a boolean, but Android expects the actual + // number of repetitions. This variable keeps track of the count. + unsigned repeat; + + bool prefer_text; + bool forward_key_repeat; +}; + +void +sc_keyboard_inject_init(struct sc_keyboard_inject *ki, + struct controller *controller, + const struct scrcpy_options *options); + +#endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index ce402ecc..def96bb7 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -18,6 +18,7 @@ #include "events.h" #include "file_handler.h" #include "input_manager.h" +#include "keyboard_inject.h" #include "recorder.h" #include "screen.h" #include "server.h" @@ -39,6 +40,7 @@ struct scrcpy { #endif struct controller controller; struct file_handler file_handler; + struct sc_keyboard_inject keyboard_inject; struct input_manager input_manager; }; @@ -411,7 +413,15 @@ scrcpy(const struct scrcpy_options *options) { } stream_started = true; - input_manager_init(&s->input_manager, &s->controller, &s->screen, options); + struct sc_key_processor *kp = NULL; + + if (options->control) { + sc_keyboard_inject_init(&s->keyboard_inject, &s->controller, options); + kp = &s->keyboard_inject.key_processor; + } + + input_manager_init(&s->input_manager, &s->controller, &s->screen, kp, + options); ret = event_loop(s, options); LOGD("quit..."); diff --git a/app/src/trait/key_processor.h b/app/src/trait/key_processor.h new file mode 100644 index 00000000..5790310b --- /dev/null +++ b/app/src/trait/key_processor.h @@ -0,0 +1,29 @@ +#ifndef SC_KEY_PROCESSOR_H +#define SC_KEY_PROCESSOR_H + +#include "common.h" + +#include +#include + +#include + +/** + * Key processor trait. + * + * Component able to process and inject keys should implement this trait. + */ +struct sc_key_processor { + const struct sc_key_processor_ops *ops; +}; + +struct sc_key_processor_ops { + void + (*process_key)(struct sc_key_processor *kp, const SDL_KeyboardEvent *event); + + void + (*process_text)(struct sc_key_processor *kp, + const SDL_TextInputEvent *event); +}; + +#endif