diff --git a/include/ui/console.h b/include/ui/console.h index 8a86617..d131260 100644 --- a/include/ui/console.h +++ b/include/ui/console.h @@ -338,6 +338,7 @@ static inline int vnc_display_pw_expire(DisplayState *ds, time_t expires) #endif /* curses.c */ +void curses_parse_options(const char *options); void curses_display_init(DisplayState *ds, int full_screen); /* input.c */ diff --git a/qemu-options.hx b/qemu-options.hx index c2c0823..1e407bd 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -820,9 +820,11 @@ ETEXI DEF("display", HAS_ARG, QEMU_OPTION_display, "-display sdl[,frame=on|off][,alt_grab=on|off][,ctrl_grab=on|off]\n" - " [,window_close=on|off]|curses|none|\n" - " gtk[,grab_on_hover=on|off]|\n" - " vnc=[,]\n" + " [,window_close=on|off]\n" + " none\n" + " curses[,delay[=]]\n" + " gtk[,grab_on_hover=on|off]\n" + " vnc=[,]\n" " select display type\n", QEMU_ARCH_ALL) STEXI @item -display @var{type} diff --git a/ui/curses.c b/ui/curses.c index b044790..45719a4 100644 --- a/ui/curses.c +++ b/ui/curses.c @@ -176,6 +176,82 @@ static void curses_cursor_position(DisplayChangeListener *dcl, static kbd_layout_t *kbd_layout = NULL; +struct KeyEvent { + struct KeyEvent *next; + int code; + bool down; +}; + +static struct KeyEvent *firstKeyEvent = NULL; +static struct KeyEvent *lastKeyEvent = NULL; + +static unsigned long long keyEventDelay = 0; +static QEMUTimer *keyEventTimer = NULL; + +static void curses_begin_key_event(void); + +static void curses_send_key_event(int code, bool down) +{ + qemu_input_event_send_key_number(NULL, code, down); +} + +static void curses_end_key_event(void *opaque) +{ + struct KeyEvent *event = firstKeyEvent; + + if ((firstKeyEvent = event->next)) + { + curses_begin_key_event(); + } + else + { + lastKeyEvent = NULL; + } + + g_free(event); +} + +static void curses_begin_key_event(void) +{ + struct KeyEvent *event = firstKeyEvent; + + if (!keyEventTimer) + { + keyEventTimer = timer_new_ms(QEMU_CLOCK_VIRTUAL, curses_end_key_event, NULL); + } + + curses_send_key_event(event->code, event->down); + timer_mod(keyEventTimer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + keyEventDelay); +} + +static void curses_enqueue_key_event(int code, bool down) +{ + if (keyEventDelay) + { + struct KeyEvent *event; + + event = g_malloc(sizeof(*event)); + event->next = NULL; + event->code = code; + event->down = down; + + if (lastKeyEvent) + { + lastKeyEvent->next = event; + lastKeyEvent = event; + } + else + { + firstKeyEvent = lastKeyEvent = event; + curses_begin_key_event(); + } + } + else + { + curses_send_key_event(code, down); + } +} + static void curses_refresh(DisplayChangeListener *dcl) { int chr, nextchr, keysym, keycode, keycode_alt; @@ -276,32 +352,32 @@ static void curses_refresh(DisplayChangeListener *dcl) /* since terminals don't know about key press and release * events, we need to emit both for each key received */ if (keycode & SHIFT) { - qemu_input_event_send_key_number(NULL, SHIFT_CODE, true); + curses_enqueue_key_event(SHIFT_CODE, true); } if (keycode & CNTRL) { - qemu_input_event_send_key_number(NULL, CNTRL_CODE, true); + curses_enqueue_key_event(CNTRL_CODE, true); } if (keycode & ALT) { - qemu_input_event_send_key_number(NULL, ALT_CODE, true); + curses_enqueue_key_event(ALT_CODE, true); } if (keycode & ALTGR) { - qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, true); + curses_enqueue_key_event(GREY | ALT_CODE, true); } - qemu_input_event_send_key_number(NULL, keycode, true); - qemu_input_event_send_key_number(NULL, keycode, false); + curses_enqueue_key_event(keycode, true); + curses_enqueue_key_event(keycode, false); if (keycode & ALTGR) { - qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, false); + curses_enqueue_key_event(GREY | ALT_CODE, false); } if (keycode & ALT) { - qemu_input_event_send_key_number(NULL, ALT_CODE, false); + curses_enqueue_key_event(ALT_CODE, false); } if (keycode & CNTRL) { - qemu_input_event_send_key_number(NULL, CNTRL_CODE, false); + curses_enqueue_key_event(CNTRL_CODE, false); } if (keycode & SHIFT) { - qemu_input_event_send_key_number(NULL, SHIFT_CODE, false); + curses_enqueue_key_event(SHIFT_CODE, false); } } else { keysym = curses2qemu[chr]; @@ -349,6 +425,51 @@ static void curses_keyboard_setup(void) } } +void curses_parse_options(const char *options) +{ + keyEventDelay = 0; + + while (*options) + { + const char *next; + char *end; + + if (!strstart(options, ",", &next)) + { + break; + } + options = next; + + if (strstart(options, "delay", &next)) + { + options = next; + keyEventDelay = 20; + + if (strstart(options, "=", &next)) + { + options = next; + + if (parse_uint(options, &keyEventDelay, &end, 10)) + { + break; + } + options = end; + } + } + + else + { + break; + } + } + + if (*options) + { + fprintf(stderr, "qemu: invalid Curses display option: %s\n", options); + exit(1); + } +} + static const DisplayChangeListenerOps dcl_ops = { .dpy_name = "curses", .dpy_text_update = curses_update, diff --git a/vl.c b/vl.c index 709d8cd..bd6c583 100644 --- a/vl.c +++ b/vl.c @@ -2300,6 +2300,7 @@ static DisplayType select_display(const char *p) } else if (strstart(p, "curses", &opts)) { #ifdef CONFIG_CURSES display = DT_CURSES; + curses_parse_options(opts); #else fprintf(stderr, "Curses support is disabled\n"); exit(1);