GHOST/Keymap: support for detecting repeat events

- Keymap items now have 'repeat' boolean which can be set
  to make keymap items respond to key repeat events or not.
- Support for X11 & WIN32 (not macOS currently).

This allows for the possibility to perform actions while a key is held
and finish the action upon release.

Thanks to @Severin for review and WIN32 support.
This commit is contained in:
Campbell Barton 2020-03-06 17:24:12 +11:00
parent 73ef27f156
commit 5be0e3430d
Notes: blender-bot 2023-02-13 23:13:45 +01:00
Referenced by commit c72317943b, Attempt to fix build errors on macOS
Referenced by issue #74622, Face Sets visibility glitches
16 changed files with 165 additions and 17 deletions

View File

@ -542,6 +542,9 @@ typedef struct {
char ascii;
/** The unicode character. if the length is 6, not NULL terminated if all 6 are set */
char utf8_buf[6];
/** Generated by auto-repeat. */
char is_repeat;
} GHOST_TEventKeyData;
typedef struct {

View File

@ -38,12 +38,17 @@ class GHOST_EventKey : public GHOST_Event {
* \param type The type of key event.
* \param key The key code of the key.
*/
GHOST_EventKey(GHOST_TUns64 msec, GHOST_TEventType type, GHOST_IWindow *window, GHOST_TKey key)
GHOST_EventKey(GHOST_TUns64 msec,
GHOST_TEventType type,
GHOST_IWindow *window,
GHOST_TKey key,
bool is_repeat)
: GHOST_Event(msec, type, window)
{
m_keyEventData.key = key;
m_keyEventData.ascii = '\0';
m_keyEventData.utf8_buf[0] = '\0';
m_keyEventData.is_repeat = is_repeat;
m_data = &m_keyEventData;
}
@ -59,7 +64,8 @@ class GHOST_EventKey : public GHOST_Event {
GHOST_IWindow *window,
GHOST_TKey key,
char ascii,
const char utf8_buf[6])
const char utf8_buf[6],
bool is_repeat)
: GHOST_Event(msec, type, window)
{
m_keyEventData.key = key;
@ -68,6 +74,7 @@ class GHOST_EventKey : public GHOST_Event {
memcpy(m_keyEventData.utf8_buf, utf8_buf, sizeof(m_keyEventData.utf8_buf));
else
m_keyEventData.utf8_buf[0] = '\0';
m_keyEventData.is_repeat = is_repeat;
m_data = &m_keyEventData;
}

View File

@ -318,7 +318,7 @@ void GHOST_NDOFManager::sendKeyEvent(GHOST_TKey key,
GHOST_IWindow *window)
{
GHOST_TEventType type = press ? GHOST_kEventKeyDown : GHOST_kEventKeyUp;
GHOST_EventKey *event = new GHOST_EventKey(time, type, window, key);
GHOST_EventKey *event = new GHOST_EventKey(time, type, window, key, false);
#ifdef DEBUG_NDOF_BUTTONS
printf("keyboard %s\n", press ? "down" : "up");

View File

@ -1806,7 +1806,7 @@ GHOST_TSuccess GHOST_SystemCocoa::handleKeyEvent(void *eventPtr)
if ([event type] == NSEventTypeKeyDown) {
pushEvent(new GHOST_EventKey(
[event timestamp] * 1000, GHOST_kEventKeyDown, window, keyCode, ascii, utf8_buf));
[event timestamp] * 1000, GHOST_kEventKeyDown, window, keyCode, ascii, utf8_buf, false));
#if 0
printf("Key down rawCode=0x%x charsIgnoringModifiers=%c keyCode=%u ascii=%i %c utf8=%s\n",
[event keyCode],
@ -1820,7 +1820,7 @@ GHOST_TSuccess GHOST_SystemCocoa::handleKeyEvent(void *eventPtr)
}
else {
pushEvent(new GHOST_EventKey(
[event timestamp] * 1000, GHOST_kEventKeyUp, window, keyCode, 0, NULL));
[event timestamp] * 1000, GHOST_kEventKeyUp, window, keyCode, 0, NULL, false));
#if 0
printf("Key up rawCode=0x%x charsIgnoringModifiers=%c keyCode=%u ascii=%i %c utf8=%s\n",
[event keyCode],

View File

@ -591,7 +591,7 @@ void GHOST_SystemSDL::processEvent(SDL_Event *sdl_event)
}
}
g_event = new GHOST_EventKey(getMilliSeconds(), type, window, gkey, sym, NULL);
g_event = new GHOST_EventKey(getMilliSeconds(), type, window, gkey, sym, NULL, false);
break;
}
}

View File

@ -1047,6 +1047,20 @@ GHOST_EventKey *GHOST_SystemWin32::processKeyEvent(GHOST_WindowWin32 *window, RA
if (key != GHOST_kKeyUnknown) {
char utf8_char[6] = {0};
char ascii = 0;
bool is_repeat = false;
/* Unlike on Linux, not all keys can send repeat events. E.g. modifier keys don't. */
if (keyDown) {
if (system->m_keycode_last_repeat_key == vk) {
is_repeat = true;
}
system->m_keycode_last_repeat_key = vk;
}
else {
if (system->m_keycode_last_repeat_key == vk) {
system->m_keycode_last_repeat_key = 0;
}
}
wchar_t utf16[3] = {0};
BYTE state[256] = {0};
@ -1083,7 +1097,8 @@ GHOST_EventKey *GHOST_SystemWin32::processKeyEvent(GHOST_WindowWin32 *window, RA
window,
key,
ascii,
utf8_char);
utf8_char,
is_repeat);
// GHOST_PRINTF("%c\n", ascii); // we already get this info via EventPrinter
}
@ -1520,6 +1535,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
modifiers.clear();
system->storeModifierKeys(modifiers);
system->m_wheelDeltaAccum = 0;
system->m_keycode_last_repeat_key = 0;
event = processWindowEvent(LOWORD(wParam) ? GHOST_kEventWindowActivate :
GHOST_kEventWindowDeactivate,
window);

View File

@ -414,6 +414,8 @@ class GHOST_SystemWin32 : public GHOST_System {
/** The current state of the modifier keys. */
GHOST_ModifierKeys m_modifierKeys;
/** The virtual-key code (VKey) of the last press event. Used to detect repeat events. */
unsigned short m_keycode_last_repeat_key;
/** State variable set at initialization. */
bool m_hasPerformanceCounter;
/** High frequency timer variable. */

View File

@ -93,6 +93,11 @@
* instead of active one. See T47228 and D1746 */
#define USE_NON_LATIN_KB_WORKAROUND
static uchar bit_is_on(const uchar *ptr, int bit)
{
return ptr[bit >> 3] & (1 << (bit & 7));
}
static GHOST_TKey ghost_key_from_keysym(const KeySym key);
static GHOST_TKey ghost_key_from_keycode(const XkbDescPtr xkb_descr, const KeyCode keycode);
static GHOST_TKey ghost_key_from_keysym_or_keycode(const KeySym key,
@ -196,6 +201,7 @@ GHOST_SystemX11::GHOST_SystemX11() : GHOST_System(), m_xkb_descr(NULL), m_start_
m_xkb_descr = XkbGetMap(m_display, 0, XkbUseCoreKbd);
if (m_xkb_descr) {
XkbGetNames(m_display, XkbKeyNamesMask, m_xkb_descr);
XkbGetControls(m_display, XkbPerKeyRepeatMask | XkbRepeatKeysMask, m_xkb_descr);
}
}
@ -747,7 +753,8 @@ bool GHOST_SystemX11::processEvents(bool waitForEvent)
window,
ghost_key_from_keysym(modifiers[i]),
'\0',
NULL));
NULL,
false));
}
}
}
@ -822,6 +829,64 @@ void GHOST_SystemX11::processEvent(XEvent *xe)
GHOST_WindowX11 *window = findGhostWindow(xe->xany.window);
GHOST_Event *g_event = NULL;
/* Detect auto-repeat. */
bool is_repeat = false;
if (xe->type == KeyPress || xe->type == KeyRelease) {
XKeyEvent *xke = &(xe->xkey);
/* Set to true if this key will repeat. */
bool is_repeat_keycode = false;
if (m_xkb_descr != NULL) {
/* Use XKB support. */
is_repeat_keycode = (
/* Should always be true, check just in case. */
(xke->keycode < (XkbPerKeyBitArraySize << 3)) &&
bit_is_on(m_xkb_descr->ctrls->per_key_repeat, xke->keycode));
}
else {
/* No XKB support (filter by modifier). */
switch (XLookupKeysym(xke, 0)) {
case XK_Shift_L:
case XK_Shift_R:
case XK_Control_L:
case XK_Control_R:
case XK_Alt_L:
case XK_Alt_R:
case XK_Super_L:
case XK_Super_R:
case XK_Hyper_L:
case XK_Hyper_R:
case XK_Caps_Lock:
case XK_Scroll_Lock:
case XK_Num_Lock: {
break;
}
default: {
is_repeat_keycode = true;
}
}
}
if (is_repeat_keycode) {
if (xe->type == KeyPress) {
if (m_keycode_last_repeat_key == xke->keycode) {
is_repeat = true;
}
m_keycode_last_repeat_key = xke->keycode;
}
else {
if (m_keycode_last_repeat_key == xke->keycode) {
m_keycode_last_repeat_key = (uint)-1;
}
}
}
}
else if (xe->type == EnterNotify) {
/* We can't tell how the key state changed, clear it to avoid stuck keys. */
m_keycode_last_repeat_key = (uint)-1;
}
#ifdef USE_XINPUT_HOTPLUG
/* Hot-Plug support */
if (m_xinput_version.present) {
@ -1129,7 +1194,8 @@ void GHOST_SystemX11::processEvent(XEvent *xe)
}
#endif
g_event = new GHOST_EventKey(getMilliSeconds(), type, window, gkey, ascii, utf8_buf);
g_event = new GHOST_EventKey(
getMilliSeconds(), type, window, gkey, ascii, utf8_buf, is_repeat);
#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
/* when using IM for some languages such as Japanese,
@ -1153,7 +1219,8 @@ void GHOST_SystemX11::processEvent(XEvent *xe)
/* enqueue previous character */
pushEvent(g_event);
g_event = new GHOST_EventKey(getMilliSeconds(), type, window, gkey, '\0', &utf8_buf[i]);
g_event = new GHOST_EventKey(
getMilliSeconds(), type, window, gkey, '\0', &utf8_buf[i], is_repeat);
}
}

View File

@ -366,6 +366,8 @@ class GHOST_SystemX11 : public GHOST_System {
unsigned int m_last_release_keycode;
Time m_last_release_time;
uint m_keycode_last_repeat_key;
/**
* Return the ghost window associated with the
* X11 window xwind

View File

@ -184,6 +184,9 @@ def draw_kmi(display_keymaps, kc, km, kmi, layout, level):
if map_type == 'KEYBOARD':
subrow.prop(kmi, "type", text="", event=True)
subrow.prop(kmi, "value", text="")
subrow_repeat = subrow.row(align=True)
subrow_repeat.active = kmi.value in {'ANY', 'PRESS'}
subrow_repeat.prop(kmi, "repeat", text="Repeat")
elif map_type in {'MOUSE', 'NDOF'}:
subrow.prop(kmi, "type", text="")
subrow.prop(kmi, "value", text="")

View File

@ -370,6 +370,7 @@ enum {
KMI_EXPANDED = (1 << 1),
KMI_USER_MODIFIED = (1 << 2),
KMI_UPDATE = (1 << 3),
KMI_REPEAT_IGNORE = (1 << 4),
};
/** #wmKeyMapItem.maptype */

View File

@ -652,6 +652,12 @@ static int rna_Event_unicode_length(PointerRNA *ptr)
}
}
static bool rna_Event_is_repeat_get(PointerRNA *ptr)
{
const wmEvent *event = ptr->data;
return event->is_repeat;
}
static float rna_Event_pressure_get(PointerRNA *ptr)
{
const wmEvent *event = ptr->data;
@ -2131,6 +2137,12 @@ static void rna_def_event(BlenderRNA *brna)
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop, "Type", "");
/* keyboard */
prop = RNA_def_property(srna, "is_repeat", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_boolean_funcs(prop, "rna_Event_is_repeat_get", NULL);
RNA_def_property_ui_text(prop, "Is Repeat", "The event is generated by holding a key down");
/* mouse */
prop = RNA_def_property(srna, "mouse_x", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "x");
@ -2728,6 +2740,12 @@ static void rna_def_keyconfig(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Key Modifier", "Regular key pressed as a modifier");
RNA_def_property_update(prop, 0, "rna_KeyMapItem_update");
prop = RNA_def_property(srna, "repeat", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", KMI_REPEAT_IGNORE);
RNA_def_property_ui_text(prop, "Repeat", "Active on key-repeat events (when a key is held)");
RNA_def_property_update(prop, 0, "rna_KeyMapItem_update");
prop = RNA_def_property(srna, "show_expanded", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", KMI_EXPANDED);

View File

@ -216,6 +216,7 @@ static wmKeyMapItem *rna_KeyMap_item_new(wmKeyMap *km,
bool alt,
bool oskey,
int keymodifier,
bool repeat,
bool head)
{
/* wmWindowManager *wm = CTX_wm_manager(C); */
@ -251,6 +252,10 @@ static wmKeyMapItem *rna_KeyMap_item_new(wmKeyMap *km,
/* create keymap item */
kmi = WM_keymap_add_item(km, idname_bl, type, value, modifier, keymodifier);
if (!repeat) {
kmi->flag |= KMI_REPEAT_IGNORE;
}
/* [#32437] allow scripts to define hotkeys that get added to start of keymap
* so that they stand a chance against catch-all defines later on
*/
@ -293,8 +298,10 @@ static wmKeyMapItem *rna_KeyMap_item_new_modal(wmKeyMap *km,
bool ctrl,
bool alt,
bool oskey,
int keymodifier)
int keymodifier,
bool repeat)
{
wmKeyMapItem *kmi = NULL;
int modifier = 0;
int propvalue = 0;
@ -323,14 +330,20 @@ static wmKeyMapItem *rna_KeyMap_item_new_modal(wmKeyMap *km,
/* not initialized yet, do delayed lookup */
if (!km->modal_items) {
return WM_modalkeymap_add_item_str(km, type, value, modifier, keymodifier, propvalue_str);
kmi = WM_modalkeymap_add_item_str(km, type, value, modifier, keymodifier, propvalue_str);
}
else {
if (RNA_enum_value_from_id(km->modal_items, propvalue_str, &propvalue) == 0) {
BKE_report(reports, RPT_WARNING, "Property value not in enumeration");
}
kmi = WM_modalkeymap_add_item(km, type, value, modifier, keymodifier, propvalue);
}
if (RNA_enum_value_from_id(km->modal_items, propvalue_str, &propvalue) == 0) {
BKE_report(reports, RPT_WARNING, "Property value not in enumeration");
if (!repeat) {
kmi->flag |= KMI_REPEAT_IGNORE;
}
return WM_modalkeymap_add_item(km, type, value, modifier, keymodifier, propvalue);
return kmi;
}
static void rna_KeyMap_item_remove(wmKeyMap *km, ReportList *reports, PointerRNA *kmi_ptr)
@ -1083,6 +1096,7 @@ void RNA_api_keymapitems(StructRNA *srna)
RNA_def_boolean(func, "alt", 0, "Alt", "");
RNA_def_boolean(func, "oskey", 0, "OS Key", "");
RNA_def_enum(func, "key_modifier", rna_enum_event_type_items, 0, "Key Modifier", "");
RNA_def_boolean(func, "repeat", true, "Repeat", "When set, accept key-repeat events");
RNA_def_boolean(func,
"head",
0,
@ -1106,6 +1120,7 @@ void RNA_api_keymapitems(StructRNA *srna)
RNA_def_boolean(func, "alt", 0, "Alt", "");
RNA_def_boolean(func, "oskey", 0, "OS Key", "");
RNA_def_enum(func, "key_modifier", rna_enum_event_type_items, 0, "Key Modifier", "");
RNA_def_boolean(func, "repeat", true, "Repeat", "When set, accept key-repeat events");
parm = RNA_def_pointer(func, "item", "KeyMapItem", "Item", "Added key map item");
RNA_def_function_return(func, parm);

View File

@ -540,7 +540,9 @@ typedef struct wmEvent {
char utf8_buf[6];
/** From ghost, fallback if utf8 isn't set. */
char ascii;
char pad;
/** Generated by auto-repeat. */
char is_repeat;
/** Previous state, used for double click and the 'click'. */
short prevtype;

View File

@ -1772,6 +1772,12 @@ static bool wm_eventmatch(const wmEvent *winevent, const wmKeyMapItem *kmi)
return false;
}
if (winevent->is_repeat) {
if (kmi->flag & KMI_REPEAT_IGNORE) {
return false;
}
}
const int kmitype = WM_userdef_event_map(kmi->type);
/* the matching rules */
@ -4254,6 +4260,7 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void
/* initialize and copy state (only mouse x y and modifiers) */
event = *evt;
event.is_repeat = false;
switch (type) {
/* mouse move, also to inactive window (X11 does this) */
@ -4407,6 +4414,7 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void
event.ascii = kd->ascii;
memcpy(
event.utf8_buf, kd->utf8_buf, sizeof(event.utf8_buf)); /* might be not null terminated*/
event.is_repeat = kd->is_repeat;
event.val = (type == GHOST_kEventKeyDown) ? KM_PRESS : KM_RELEASE;
wm_eventemulation(&event, false);
@ -4418,6 +4426,7 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void
/* copy to event state */
evt->val = event.val;
evt->type = event.type;
evt->is_repeat = event.is_repeat;
/* exclude arrow keys, esc, etc from text input */
if (type == GHOST_kEventKeyUp) {

View File

@ -180,7 +180,9 @@ static bool wm_keymap_item_equals(wmKeyMapItem *a, wmKeyMapItem *b)
{
return (wm_keymap_item_equals_result(a, b) && a->type == b->type && a->val == b->val &&
a->shift == b->shift && a->ctrl == b->ctrl && a->alt == b->alt && a->oskey == b->oskey &&
a->keymodifier == b->keymodifier && a->maptype == b->maptype);
a->keymodifier == b->keymodifier && a->maptype == b->maptype &&
((ISKEYBOARD(a->type) == 0) ||
(a->flag & KMI_REPEAT_IGNORE) == (b->flag & KMI_REPEAT_IGNORE)));
}
/* properties can be NULL, otherwise the arg passed is used and ownership is given to the kmi */
@ -2012,6 +2014,7 @@ void WM_keymap_item_restore_to_default(bContext *C, wmKeyMap *keymap, wmKeyMapIt
kmi->oskey = orig->oskey;
kmi->keymodifier = orig->keymodifier;
kmi->maptype = orig->maptype;
kmi->flag = (kmi->flag & ~KMI_REPEAT_IGNORE) | (orig->flag & KMI_REPEAT_IGNORE);
WM_keyconfig_update_tag(keymap, kmi);
}