From ef77be806a846f464c4588a4b5a8f30fe7623891 Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Sat, 4 Jan 2014 19:14:05 +0100 Subject: [PATCH] Include mod_xkbevents instead of using a submodule --- .gitmodules | 3 - mod_xkbevents | 1 - mod_xkbevents/.gitignore | 7 + mod_xkbevents/Makefile | 27 ++++ mod_xkbevents/mod_xkbevents.c | 294 +++++++++++++++++++++++++++++++++++ mod_xkbevents/statusbar_xkbgroup.lua | 28 ++++ mod_xkbevents/xkbbell.lua | 124 +++++++++++++++ mod_xkbevents/xkbion.lua | 169 ++++++++++++++++++++ 8 files changed, 649 insertions(+), 4 deletions(-) delete mode 160000 mod_xkbevents create mode 100644 mod_xkbevents/.gitignore create mode 100644 mod_xkbevents/Makefile create mode 100644 mod_xkbevents/mod_xkbevents.c create mode 100644 mod_xkbevents/statusbar_xkbgroup.lua create mode 100644 mod_xkbevents/xkbbell.lua create mode 100644 mod_xkbevents/xkbion.lua diff --git a/.gitmodules b/.gitmodules index c40400b0..8e6b25d1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "mod_xkbevents"] - path = mod_xkbevents - url = ../mod_xkbevents [submodule "libextl"] path = libextl url = ../libextl diff --git a/mod_xkbevents b/mod_xkbevents deleted file mode 160000 index 58c1823e..00000000 --- a/mod_xkbevents +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 58c1823ebd4db54604e4d5934b84c580c48b2321 diff --git a/mod_xkbevents/.gitignore b/mod_xkbevents/.gitignore new file mode 100644 index 00000000..48b7f767 --- /dev/null +++ b/mod_xkbevents/.gitignore @@ -0,0 +1,7 @@ +*.swp +exports.c +exports.h +exports.o +mod_xkbevents.o +mod_xkbevents.lc +mod_xkbevents.so diff --git a/mod_xkbevents/Makefile b/mod_xkbevents/Makefile new file mode 100644 index 00000000..7fb002a9 --- /dev/null +++ b/mod_xkbevents/Makefile @@ -0,0 +1,27 @@ +## +## frame_xkb module Makefile +## + +# System-specific configuration is in system.mk +TOPDIR=.. +include $(TOPDIR)/build/system-inc.mk + +###################################### + +INCLUDES += $(X11_INCLUDES) $(LIBTU_INCLUDES) $(LIBEXTL_INCLUDES) -I$(TOPDIR) +CFLAGS += $(XOPEN_SOURCE) $(C99_SOURCE) -Wall +LDFLAGS += $(X11_LIBS) + +SOURCES=mod_xkbevents.c + +MAKE_EXPORTS=mod_xkbevents + +MODULE=mod_xkbevents + +###################################### + +include $(TOPDIR)/build/rules.mk + +###################################### + +_install: module_install diff --git a/mod_xkbevents/mod_xkbevents.c b/mod_xkbevents/mod_xkbevents.c new file mode 100644 index 00000000..35429529 --- /dev/null +++ b/mod_xkbevents/mod_xkbevents.c @@ -0,0 +1,294 @@ +/* + * Ion xkb events + * + * Copyright (c) Sergey Redin 2006. + * Copyright (c) Etan Reisner 2011. + * + * Released under the MIT License. + */ + +#include +#include + +#include "ioncore/event.h" +#include "ioncore/global.h" +#include "ioncore/xwindow.h" + +#include "exports.h" + +static int xkb_event_code, xkb_error_code; +WHook *xkb_group_event=NULL, *xkb_bell_event=NULL; + +INTRSTRUCT(WAnyParams); +DECLSTRUCT(WAnyParams){ + bool send_event; /* True => synthetically generated */ + Time time; /* server time when event generated */ + unsigned int device; /* Xkb device ID, will not be XkbUseCoreKbd */ +}; + +INTRSTRUCT(WGroupParams); +DECLSTRUCT(WGroupParams){ + WAnyParams any; + + int group; + int base_group; + int latched_group; + int locked_group; +}; + +INTRSTRUCT(WBellParams); +DECLSTRUCT(WBellParams){ + WAnyParams any; + + int percent; + int pitch; + int duration; + unsigned int bell_class; + unsigned int bell_id; + char *name; + WClientWin *window; + bool event_only; +}; + +/*{{{ Module information */ + +#include "version.h" + +char mod_xkbevents_ion_api_version[]=ION_API_VERSION; + +/*}}} Module information */ + +static char *get_atom_name(Atom atom) +{ + char *xatomname, *atomname; + + xatomname = XGetAtomName(ioncore_g.dpy, atom); + atomname = scopy(xatomname); + XFree(xatomname); + + return atomname; +} + +static bool docall(ExtlFn fn, ExtlTab t) +{ + bool ret; + + extl_protect(NULL); + ret=extl_call(fn, "t", NULL, t); + extl_unprotect(NULL); + + extl_unref_table(t); + + return ret; +} + +#define MRSH_ANY(PRM,TAB) \ + extl_table_sets_b(TAB, "send_event", PRM->any.send_event); \ + extl_table_sets_i(TAB, "time", PRM->any.time); \ + extl_table_sets_i(TAB, "device", PRM->any.device) + +static bool mrsh_group_extl(ExtlFn fn, WGroupParams *param) +{ + ExtlTab t=extl_create_table(); + + MRSH_ANY(param,t); + if(param->group!=-1) + extl_table_sets_i(t, "group", param->group + 1); + if(param->base_group!=-1) + extl_table_sets_i(t, "base", param->base_group + 1); + if(param->latched_group!=-1) + extl_table_sets_i(t, "latched", param->latched_group + 1); + if(param->locked_group!=-1) + extl_table_sets_i(t, "locked", param->locked_group + 1); + + return docall(fn, t); +} + +static bool mrsh_bell_extl(ExtlFn fn, WBellParams *param) +{ + ExtlTab t=extl_create_table(); + + MRSH_ANY(param,t); + extl_table_sets_i(t, "percent", param->percent); + extl_table_sets_i(t, "pitch", param->pitch); + extl_table_sets_i(t, "duration", param->duration); + + extl_table_sets_i(t, "bell_class", param->bell_class); + extl_table_sets_i(t, "bell_id", param->bell_id); + + if(param->name){ + extl_table_sets_s(t, "name", param->name); + free(param->name); + } + + if(param->window) + extl_table_sets_o(t, "window", (Obj*)param->window); + + extl_table_sets_b(t, "event_only", param->event_only); + + return docall(fn, t); +} + +#define PARAM_ANY(PRM,NM) \ + PRM.any.send_event=kev->NM.send_event; \ + PRM.any.time=kev->NM.time; \ + PRM.any.device=kev->NM.device + +#define CHANGED(NM,FLD) (kev->state.changed&XkbGroup##NM##Mask)?kev->state.FLD:-1 + +bool handle_xkb_event(XEvent *ev) +{ + void *p=NULL; + WHook *hook=NULL; + XkbEvent *kev=NULL; + WHookMarshallExtl *fn=NULL; + + if(ev->type!=xkb_event_code) + return FALSE; + + kev=(XkbEvent*)ev; + + switch(kev->any.xkb_type){ + case XkbStateNotify: + { + WGroupParams p2; + p=&p2; + + hook=xkb_group_event; + fn=(WHookMarshallExtl*)mrsh_group_extl; + + PARAM_ANY(p2,state); + + p2.group=CHANGED(State,group); + p2.base_group=CHANGED(Base,base_group); + p2.latched_group=CHANGED(Latch,latched_group); + p2.locked_group=CHANGED(Lock,locked_group); + } + break; + case XkbBellNotify: + { + WBellParams p2; + p=&p2; + + hook=xkb_bell_event; + fn=(WHookMarshallExtl*)mrsh_bell_extl; + + PARAM_ANY(p2,bell); + + p2.percent=kev->bell.percent; + p2.pitch=kev->bell.pitch; + p2.duration=kev->bell.duration; + + p2.bell_class=kev->bell.bell_class; + p2.bell_id=kev->bell.bell_id; + + p2.name=NULL; + if(kev->bell.name!=None) + p2.name=get_atom_name(kev->bell.name); + + p2.window=NULL; + if(kev->bell.window!=None) + p2.window=XWINDOW_REGION_OF_T(kev->bell.window, WClientWin); + + p2.event_only=kev->bell.event_only; + } + break; + } + + if(hook && p && fn) + hook_call_p(hook, p, fn); + + return FALSE; +} + +#undef CHANGED + +#undef PARAM_ANY + + +/*EXTL_DOC + * Set the current XKB group. See \code{XkbLockGroup}(3) manual page + * for details. See xkbion.lua for example use. + */ +EXTL_EXPORT +int mod_xkbevents_lock_group(int state) +{ + return XkbLockGroup(ioncore_g.dpy, XkbUseCoreKbd, state); +} + +/*EXTL_DOC + * Latch modifiers. See \code{XkbLatchModifiers}(3) manual page + * for details. + */ +EXTL_EXPORT +int mod_xkbevents_lock_modifiers(int affect, int values) +{ + return XkbLockModifiers(ioncore_g.dpy, XkbUseCoreKbd, affect, values); +} + +/*{{{ Init & deinit */ + +/* ion never does this though it looks to me like that leaks (though I suppose + * that doesn't matter if modules can't ever be unloaded at runtime. +void deinit_hooks() +{ +} +*/ + +#define INIT_HOOK_(NM) \ + NM=mainloop_register_hook(#NM, create_hook()); \ + if(NM==NULL) return FALSE; + +static bool init_hooks() +{ + INIT_HOOK_(xkb_group_event); + INIT_HOOK_(xkb_bell_event); + + return TRUE; +} + +#undef INIT_HOOK_ + +void mod_xkbevents_deinit() +{ + mod_xkbevents_unregister_exports(); +} + +bool mod_xkbevents_init() +{ + int opcode; + int major=XkbMajorVersion; + int minor=XkbMinorVersion; + + if(!XkbLibraryVersion(&major,&minor)){ + warn(TR("X library built with XKB version %d.%02d but mod_xkbevents was built with XKB version %d.%02d. Going to try to work anyway."), major, minor, XkbMajorVersion, XkbMinorVersion); + } + + if(!XkbQueryExtension(ioncore_g.dpy,&opcode,&xkb_event_code,&xkb_error_code,&major,&minor)>0){ + if ((major!=0)||(minor!=0)) + warn(TR("Server supports incompatible XKB version %d.%02d. Going to try to work anyway."), major, minor); + else + warn(TR("XkbQueryExtension failed. Going to try to work anyway.")); + } + + if(!init_hooks()) + return FALSE; + + if(!mod_xkbevents_register_exports()) + return FALSE; + + if(!hook_add(ioncore_handle_event_alt, (void (*)())handle_xkb_event)) + return FALSE; + + /* Select for the specific XkbState events we care about. */ + XkbSelectEventDetails(ioncore_g.dpy, XkbUseCoreKbd, XkbStateNotify, + XkbGroupStateMask|XkbGroupBaseMask|XkbGroupLockMask, + XkbGroupStateMask|XkbGroupBaseMask|XkbGroupLockMask); + + /* Select for all XkbBell events (we can't select for less). */ + XkbSelectEvents(ioncore_g.dpy, XkbUseCoreKbd, XkbBellNotifyMask, XkbBellNotifyMask); + + return TRUE; +} + +/*}}} Init & deinit */ diff --git a/mod_xkbevents/statusbar_xkbgroup.lua b/mod_xkbevents/statusbar_xkbgroup.lua new file mode 100644 index 00000000..e3bb1eef --- /dev/null +++ b/mod_xkbevents/statusbar_xkbgroup.lua @@ -0,0 +1,28 @@ +--[[ +Author: Etan Reisner +Email: deryni@unreliablesource.net +Summary: Adds an xkbgroup statusbar meter. +License: MIT +Last Updated: 2011-05-09 + +Copyright (c) Etan Reisner 2011 +--]] + +local group_hook = ioncore.get_hook("xkb_group_event") +if not group_hook then + pcall(dopath, "mod_xkbevents") + group_hook = ioncore.get_hook("xkb_group_event") + if not group_hook then + warn("Could not load mod_xkbevents.") + return + end +end + +local function group_event(tab) + if tab.group then + mod_statusbar.inform("xkbgroup", tostring(tab.group)) + end + ioncore.defer(mod_statusbar.update) +end + +group_hook:add(group_event) diff --git a/mod_xkbevents/xkbbell.lua b/mod_xkbevents/xkbbell.lua new file mode 100644 index 00000000..c2e076f9 --- /dev/null +++ b/mod_xkbevents/xkbbell.lua @@ -0,0 +1,124 @@ +--[[ +Author: Etan Reisner +Email: deryni@unreliablesource.net +Summary: Displays an WInfoWin on XkbBell events, also sets xkbbell grattr:s on the frame containing the window that triggered the event. +License: MIT +Last Updated: 2011-05-05 + +Copyright (c) Etan Reisner 2011 +--]] + +local bell_hook = ioncore.get_hook("xkb_bell_event") +if not bell_hook then + pcall(dopath, "mod_xkbevents") + bell_hook = ioncore.get_hook("xkb_bell_event") + if not bell_hook then + warn("Could not load mod_xkbevents.") + return + end +end + +xkbbell = xkbbell or {} +if not xkbbell.timeout then + xkbbell.timeout = 10000 +end +if not xkbbell.low_threshold then + xkbbell.low_threshold = 50 +end +if not xkbbell.medium_threshold then + xkbbell.medium_threshold = 75 +end +if not xkbbell.high_threshold then + xkbbell.high_threshold = 100 +end + +local timer = ioncore.create_timer() +local screen_iws = setmetatable({}, {__mode="kv"}) +local iw_hide_funs = setmetatable({}, {__mode="kv"}) +local active_frames = setmetatable({}, {__mode="kv"}) + +local function set_hidden(iw, scr, state) + scr = scr or ioncore.find_manager(iw, "WScreen") + scr:set_hidden(iw, state or "set") +end + +local function unset_grattrs(frame) + frame:set_grattr("xkbbell", "unset") + frame:set_grattr("xkbbell-low", "unset") + frame:set_grattr("xkbbell-medium", "unset") + frame:set_grattr("xkbbell-high", "unset") +end + +local function hide_fun(iw, scr) + return function() + set_hidden(iw, scr) + end +end + +local function region_notify(reg, how) + if how ~= "activated" then + return + end + + local frame = ioncore.find_manager(reg, "WFrame") + if frame and active_frames[frame] then + active_frames[frame] = nil + unset_grattrs(frame) + end +end + +local function bell_info(tab) + local style = "xkbbell" + if tab.percent <= xkbbell.low_threshold then + style = "xkbbell-low" + elseif tab.percent <= xkbbell.medium_threshold then + style = "xkbbell-medium" + elseif tab.percent <= xkbbell.high_threshold then + style = "xkbbell-high" + end + + if tab.window then + local frame = ioncore.find_manager(tab.window, "WFrame") + if frame then + local cframe = ioncore.find_manager(ioncore.current(), "WFrame") + if frame ~= cframe then + active_frames[frame] = true + unset_grattrs(frame) + frame:set_grattr(style, "set") + end + return + end + -- Fall through to setting a screen iw for this bell. + end + + local scr = ioncore.current():screen_of() + local iw = screen_iws[scr] + ioncore.defer(function() + if not iw then + iw = scr:attach_new{ + name="xkbbell"..scr:id(), + style=style, + + type="WInfoWin", + hidden=true, + unnumbered=true, + sizepolicy="free", + geom={x=0,y=0,w=1,h=1}, + } + screen_iws[scr] = iw + iw:set_text("Bell!", -1) + + iw_hide_funs[scr] = hide_fun(iw, scr) + end + + set_hidden(iw, scr, "unset") + timer:set(xkbbell.timeout, iw_hide_funs[scr]) + end) +end + +bell_hook:add(bell_info) + +local hook = ioncore.get_hook("region_notify") +if hook then + hook:add() +end diff --git a/mod_xkbevents/xkbion.lua b/mod_xkbevents/xkbion.lua new file mode 100644 index 00000000..16aa153b --- /dev/null +++ b/mod_xkbevents/xkbion.lua @@ -0,0 +1,169 @@ +-- xkbion.lua +-- TODO: make xkbion_set understand some simple presets + +-- (c) Sergey Redin +-- Thanks: +-- smersh at users.sf.net (author of xkbind) for the original idea + +--[[ + +-- This script allows you to use independent keyboard layouts for different windows in Anion3. +-- It uses a window property to store the XKB groups, so you can restart Ion without losing +-- settings for each window. + +-- Example usage. This is what I have in my cfg_ion.lua: + +dopath("mod_xkbevents") +dopath("xkbion.lua") +xkbion_set { + {name="EN", hint="", action = function() mod_xkbevents.lock_group(0) end}, + {name="RU", hint="important", action = function() mod_xkbevents.lock_group(1) end}, + key="Caps_Lock", + statusname = "xkbion", +} +xkbion_set { + {name="NUM", command="numlockx on"}, + {name="---", command="numlockx off"}, + key="Num_Lock", + statusname="num", + atomname="XKBION_NUM", +} +xkbion_set { + {name="----", hint="", action = function() mod_xkbevents.lock_modifiers(2, 0) end}, + {name="CAPS", hint="critical", action = function() mod_xkbevents.lock_modifiers(2, 2) end}, + key="Caps_Lock", + statusname = "caps", + atomname="XKBION_CAPS", +} + +-- Edit this to suit your needs. +-- Please note, if you want to use Caps_Lock key to change the keyboard groups like I do, +-- do not forget to add "grp:caps_toggle" to your XKB settings, just to prevent X from using +-- this key also for swiching keyboard registers. + +-- At least one group definition must be present. +-- "name" is only neseccary if you want to use mod_statusbar to indicate current XKB group. +-- "hint" is only necessary if you want to highlight your XKB group in statusbar, possible +-- values are standard values provided by the mod_statusbar: important, normal, critical +-- "command" and "action" are also unneseccary but xkbion.lua is not particulary useful +-- without them. :) The same thing for "key". + +-- The last thing to say about xkbion_set() parameters is that if you call xkbion_set +-- more than once (like I do it for XKB groups and NumLock state) you must choose different +-- "atomname" values. The default for atomname is XKBION_GROUP. + +-- The second xkbion_set() call (numlock section) is here mostly for the example. Most users +-- will need only one call, for changing XKB group. Please also note that you can define more +-- than two groups in call to xkbion_set(). + +-- You can use this line in cfg_statusbar.lua to indicate the current XKB group: + +-- template="... %xkbion ...", + +-- If your Ion does not have mod_xkb, you may try the following: + +-- xkbion_set { +-- {name="EN", command="setxkbmap us -option grp:caps_toggle"}, +-- {name="RU", command="setxkbmap ru winkeys -option grp:caps_toggle"}, +-- key="Caps_Lock", +-- statusname = "xkbion", +-- } + +]] + +function xkbion_set (groups) -- the only global created by xkbion.lua + + if not groups or type(groups) ~= "table" then error("bad args") end + if not groups[1] or type(groups[1]) ~= "table" then + error("default group is undefined") + end + + -- window_group_prop(w) - get XKBION_GROUP integer property of window `w' (set it to 1 if it's not yet defined) + -- window_group_prop(w, group) - set XKBION_GROUP property of window `w' to integer `group' + -- "XKBION_GROUP" is just the default name + local window_group_prop + do + local XA_INTEGER = 19 + local atom = notioncore.x_intern_atom( tostring( groups.atomname or "XKBION_GROUP" ) ) + if not atom or type(atom) ~= "number" then + error("Cannot intern atom " .. atomname) + end + window_group_prop = function(w, gnum) + if not w or type(w) ~= "userdata" or not w.xid or type(w.xid) ~= "function" then return 1 end + local xid = tonumber( w:xid() ) + if gnum == nil then + local t = notioncore.x_get_window_property( xid, atom, XA_INTEGER, 1, true ) + if t and type(t) == "table" and t[1] ~= nil then + do return tonumber(t[1]) end + else + gnum = 1 + end + else + gnum = tonumber(gnum) + end + -- we're here if the second argument is set or if the window does not have our property yet + notioncore.defer( function() + notioncore.x_change_property( xid, atom, XA_INTEGER, 32, "replace", {gnum} ) + end ) + return gnum + end + end + + local set_group + do + local current_gnum = 1 + local first_time = true + local statusname = groups.statusname + if statusname and type(statusname) ~= "string" then statusname = nil end + set_group = function(w, do_increment) + local gnum + if w then + gnum = window_group_prop(w) + else + gnum = 1 + end + if do_increment then gnum = gnum + 1 end + local g = groups[gnum] + if not g then gnum, g = 1, groups[1] end + if not g then return end -- error in settings, groups[1] not defined + if first_time then + first_time = false + elseif gnum == current_gnum then + return + end + window_group_prop(w, gnum) -- it's OK to call it even it `w' is nil + if g.command then + notioncore.exec(g.command) + end + if g.action then notioncore.defer(g.action) end + current_gnum = gnum + local group_name = g.name + local hint_name = g.hint + if statusname and group_name and type(group_name) == "string" then + mod_statusbar.inform(statusname, group_name) + mod_statusbar.inform(statusname.."_hint", hint_name) + notioncore.defer(mod_statusbar.update) + end + end + end + + notioncore.get_hook("region_notify_hook"):add( + function(reg, action) + if (reg ~= nil) and (tostring(reg.__typename) == "WClientWin") then + if (action == "activated") or (action == "pseudoactivated") then + set_group(reg) + end + end + end + ) + + local key = groups.key + if key and type(key) == "string" then + defbindings("WClientWin", { + kpress(key, function (_, _sub) set_group(_, true) end) + }) + end + + set_group() -- initialize + +end -- xkbion_set() -- 2.11.4.GIT