Skip to content

Commit

Permalink
Merge pull request #1236 from xiota/pr-lua-keygrab
Browse files Browse the repository at this point in the history
GeanyLua: Fix geany.keygrab and remove X11 dependency
  • Loading branch information
techee authored Aug 18, 2023
2 parents a0244a2 + 3c2e16a commit f2e4200
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 167 deletions.
57 changes: 28 additions & 29 deletions geanylua/docs/geanylua-ref.html
Original file line number Diff line number Diff line change
Expand Up @@ -638,39 +638,38 @@



<a name="keygrab"></a><hr><h3><tt>geany.keygrab ( [prompt] )</tt></h3><p>
<a name="keygrab"></a><hr><h3><tt>geany.keygrab ( [prompt] )</tt></h3>

Intercepts the next keyboard key pressed, preventing the editor from receiving
that keyboard event.</p>
<p>
The function returns the value of the pressed key as a string,
alphanumeric keys will return their keyboard value, such as
&nbsp;<tt>"a"</tt>&nbsp; or &nbsp;<tt>"1"</tt>&nbsp; while other keys may
return a string describing the key, such
as <tt>"Return"</tt> or <tt>"Escape"</tt>. &nbsp;
( Consult the file &nbsp;<tt>gdk/gdkkeysyms.h</tt>&nbsp; in the GTK sources for some idea of the key names. )
</p><p>
It should be possible to use this function on keyboards other than US,
although this hasn't been tested, and scripts written for one keyboard
might not be portable to another system. </p>
<p>Key combinations with the modifier keys &nbsp;<b>[Ctrl]</b> &nbsp;or&nbsp; <b>[Alt]</b>&nbsp;
are not supported. &nbsp; Detection of the &nbsp;<b>[Shift]</b>&nbsp; key state is somewhat supported,
although currently it may not be completely reliable.
&nbsp; Repeatedly calling this function in rapid succession (as in a loop) may also result in some "dropped" keys,
so for best results its use should be confined to detecting a single "lowercase" key press.</p><p>

If the optional <tt>prompt</tt> string argument is present, its text will be shown
as a "calltip" near the upper left-hand area of the current document.
(but only if there is a document open.)</p>
<p>
This function was primarily intended for assigning "chained" sets of hot key
options, in conjunction with user-defined <a href="geanylua-keys.html">keybindings</a>.
<p>Intercepts the next keyboard key pressed, preventing the editor from
receiving that keyboard event.</p>

<p>This function returns the value of the pressed key as a string.
Alphanumeric keys will return their keyboard value,
such as <tt>"a"</tt> or <tt>"1"</tt>,
while other keys may return a string describing the key,
such as <tt>"Return"</tt> or <tt>"Escape"</tt>.</p>

</p>
<p>
<p>Only one instance of this function may be active at a time.
This function will return <tt><b>nil</b></tt> if another instance is already waiting for a keypress.</p>

</p><br><br>
<p>If the optional <tt>prompt</tt> string argument is present, its text will be shown
in a "calltip" near the upper left-hand area of the current document.
The prompt will not be shown if no document is open.</p>

<p>Notes:
<ul>
<li>This function should work on non-US keyboards, although this has not been tested.
Scripts written for one keyboard may not be portable to other systems.</li>
<li>Key combinations with the modifier keys <b>[Ctrl]</b> or <b>[Alt]</b>
are not supported.</li>
<li>Detection of the <b>[Shift]</b> key state is unreliable.</li>
<li>Keys may be dropped when this function is called in rapid succession (as in a loop). For best results, use of this function should be confined to detecting single "lowercase" key presses.</li>
<li>Consult <tt>gdk/gdkkeysyms.h</tt> in the GTK sources for an idea of key names.</li>
<li>This function was primarily intended for assigning "chained" sets of hot key
options, in conjunction with user-defined <a href="geanylua-keys.html">keybindings</a>.</li>
</ul>
</p>
<br><br>



Expand Down
189 changes: 51 additions & 138 deletions geanylua/glspi_app.c
Original file line number Diff line number Diff line change
Expand Up @@ -439,170 +439,83 @@ static gint glspi_launch(lua_State* L)
}


static guint My_Shift_L=0;
static guint My_Shift_R=0;
static guint My_Control_L=0;
static guint My_Control_R=0;
static guint My_Alt_L=0;
static guint My_Alt_R=0;


#ifndef G_OS_WIN32

#include <X11/Xlib.h>
#include <X11/keysym.h>


#define IsShift ( (My_Shift_L == ev->xkey.keycode) || (My_Shift_R == ev->xkey.keycode) )

#define IsCtrl ( (My_Control_L == ev->xkey.keycode) || (My_Control_R == ev->xkey.keycode) )
#define IsAlt ( (My_Alt_L == ev->xkey.keycode) || (My_Alt_R == ev->xkey.keycode) )

#define IsCtrlAlt ( IsCtrl || IsAlt )

typedef struct _KeyGrabData {
gchar *prompt;
GdkKeymapKey km;
gboolean keypress;
guint keyval;
} _KeyGrabData;


static GdkFilterReturn keygrab_cb(GdkXEvent *xevent, GdkEvent *event, gpointer data)
static gboolean keygrab_cb(GtkWidget *widget, GdkEventKey *ev, gpointer data)
{
XEvent*ev = (XEvent*) xevent;
GdkKeymapKey *km = (GdkKeymapKey*) data;
switch (ev->type) {
case KeyPress:{
if (IsShift) {
km->level=1;
} else {
if (!IsCtrlAlt) km->group=1; /* Flag to know we have keydown before keyup */
}
return GDK_FILTER_REMOVE;
}
case KeyRelease:{
if (IsShift) {
km->level=0;
} else {
if ((km->group==1)&&(!IsCtrlAlt)) { /* OK, we already got our keydown */
km->group=2;
km->level=(ev->xkey.state & ShiftMask)?1:0;
km->keycode=ev->xkey.keycode;
}
}
return GDK_FILTER_REMOVE;
}
default:{}
}
return GDK_FILTER_CONTINUE;
}
_KeyGrabData *km = (_KeyGrabData*) data;

#define dosleep() g_usleep(1)
if (ev->keyval == 0) {
return FALSE;
}

#else
#include <windows.h>
#define dosleep() Sleep(1)
km->keyval = ev->keyval;
km->keypress = TRUE;
return TRUE;
}

#define IsShift ( (My_Shift_L == msg->wParam) || (My_Shift_R == msg->wParam) )

#define IsCtrl ( (My_Control_L == msg->wParam) || (My_Control_R == msg->wParam) )
#define IsAlt ( (My_Alt_L == msg->wParam) || (My_Alt_R == msg->wParam) )
static gint glspi_keygrab(lua_State* L)
{
GeanyDocument *doc = NULL;
const gchar *prompt = NULL;
static gulong keygrab_cb_handle = 0;

#define IsCtrlAlt ( IsCtrl || IsAlt )

_KeyGrabData km;
km.keypress = FALSE;
km.keyval = 0;

static GdkFilterReturn keygrab_cb(GdkXEvent *xevent, GdkEvent *event, gpointer data)
{
MSG*msg = (MSG*) xevent;
GdkKeymapKey *km = (GdkKeymapKey*) data;
switch (msg->message) {
case WM_KEYDOWN:{
if (IsShift) {
km->level=1;
} else {
if (!IsCtrlAlt) km->group=1; /* Flag to know we have keydown before keyup */
}
return GDK_FILTER_REMOVE;
}
case WM_KEYUP:{
if (IsShift) {
km->level=0;
} else {
if ((km->group==1)&&(!IsCtrlAlt)) { /* OK, we already got our keydown */
km->group=2;
km->level=HIBYTE(GetKeyState(VK_SHIFT))?1:0;
km->keycode=msg->wParam;
}
}
return GDK_FILTER_REMOVE;
/* get prompt, if exists */
if (lua_gettop(L) > 0) {
if (!lua_isstring(L, 1)) {
return FAIL_STRING_ARG(1);
}
default:{}
prompt = lua_tostring(L,1);
doc = document_get_current();
}
return GDK_FILTER_CONTINUE;
}


#endif


#include <gdk/gdkkeysyms.h>
static gint init_key(guint keyval){
GdkKeymapKey *kmk=NULL;
GdkKeymap *gdk_key_map=gdk_keymap_get_default();
gint n_keys=0;
gint rv=0;
if (gdk_keymap_get_entries_for_keyval(gdk_key_map,keyval,&kmk,&n_keys)) {
rv=kmk[0].keycode;
g_free(kmk);
/* show prompt in tooltip */
if (prompt && doc && doc->is_valid ) {
gint fvl = scintilla_send_message(doc->editor->sci, SCI_GETFIRSTVISIBLELINE, 0, 0);
gint pos = sci_get_position_from_line(doc->editor->sci, fvl+1);
scintilla_send_message(doc->editor->sci, SCI_CALLTIPSHOW, pos+3, (sptr_t) prompt);
}
return rv;
}

#define InitKey(code,value) if (!code) { code=init_key(value); }

static gint glspi_keygrab(lua_State* L)
{
GeanyDocument*doc=NULL;
const gchar*prompt=NULL;
GdkKeymapKey km={0,0,0};
GdkKeymap *gdk_key_map;
km.keycode=0;
km.group=0; /* Note: we hijack this field to use as a flag for first keydown. */
km.level=0;
InitKey(My_Shift_L, GDK_Shift_L);
InitKey(My_Shift_R, GDK_Shift_R);
InitKey(My_Control_L, GDK_Control_L);
InitKey(My_Control_R, GDK_Control_R);
InitKey(My_Alt_L, GDK_Alt_L);
InitKey(My_Alt_R, GDK_Alt_R);
if (lua_gettop(L)>0) {
if (!lua_isstring(L,1)) {return FAIL_STRING_ARG(1); }
prompt=lua_tostring(L,1);
doc=document_get_current();
/* callback to handle keypress
only one keygrab callback can be running at a time, otherwise geanylua will hang
*/
if (!keygrab_cb_handle) {
keygrab_cb_handle = g_signal_connect(main_widgets->window, "key-press-event", G_CALLBACK(keygrab_cb), &km);
} else {
lua_pushnil(L);
return 1;
}

if (prompt && doc && doc->is_valid ) {
gint fvl=scintilla_send_message(doc->editor->sci,SCI_GETFIRSTVISIBLELINE, 0,0);
gint pos=sci_get_position_from_line(doc->editor->sci, fvl+1);
scintilla_send_message(doc->editor->sci,SCI_CALLTIPSHOW,pos+3, (sptr_t)prompt);
}
gdk_window_add_filter(gtk_widget_get_window(main_widgets->window), keygrab_cb, &km);
do {
/* wait for keypress */
while (!km.keypress) {
while (gtk_events_pending()) {
if (km.group==2) { break; }
if (km.keypress) {
break;
}
gtk_main_iteration();
}
if (km.group==2) { break; }
dosleep();
} while (km.group!=2);
}

/* remove callback and clear handle */
g_signal_handler_disconnect(main_widgets->window, keygrab_cb_handle);
keygrab_cb_handle = 0;

gdk_window_remove_filter(gtk_widget_get_window(main_widgets->window), keygrab_cb, &km);
/* clear tooltip */
if (prompt && doc && doc->is_valid) {
sci_send_command(doc->editor->sci, SCI_CALLTIPCANCEL);
sci_send_command(doc->editor->sci, SCI_CALLTIPCANCEL);
}
km.group=0; /* reset the hijacked flag before passing to GDK */
gdk_key_map = gdk_keymap_get_default();
lua_pushstring(L, gdk_keyval_name(gdk_keymap_lookup_key(gdk_key_map, &km)));

lua_pushstring(L, gdk_keyval_name(km.keyval));
return 1;
}

Expand Down

0 comments on commit f2e4200

Please sign in to comment.