1 /*
2  * Implementation of a class to get keyboard layout information and change layouts
3  *
4  * Copyright (C) 2008 by Jay Bromley <jbromley@gmail.com>
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the Free
8  * Software Foundation; either version 3 of the License, or (at your option)
9  * any later version.
10  */
11 
12 #include <algorithm>
13 #include <cstdlib>
14 #include <cctype>
15 #include <cstring>
16 
17 #include <iostream>
18 #include <string>
19 #include <sstream>
20 
21 #include <X11/XKBlib.h>
22 #include <X11/extensions/XKBrules.h>
23 
24 #include "XKeyboard.hpp"
25 #include "Utils.hpp"
26 
27 using namespace std;
28 
29 namespace kb {
30 
XKeyboard(size_t verbose)31 XKeyboard::XKeyboard(size_t verbose)
32   : _display(0), _deviceId(XkbUseCoreKbd), _kbdDescPtr(0), _verbose(verbose)
33 {
34 }
35 
open_display()36 void XKeyboard::open_display()
37 {
38 
39   XkbIgnoreExtension(False);
40 
41   char* displayName = strdup(""); // allocates memory for string!
42   int eventCode;
43   int errorReturn;
44   int major = XkbMajorVersion;
45   int minor = XkbMinorVersion;
46   int reasonReturn;
47 
48   _display = XkbOpenDisplay(displayName, &eventCode, &errorReturn, &major,
49       &minor, &reasonReturn);
50   free(displayName);
51   switch (reasonReturn) {
52     case XkbOD_Success:           break;
53     case XkbOD_BadLibraryVersion: THROW_MSG(_verbose, "Bad XKB library version.");
54     case XkbOD_ConnectionRefused: THROW_MSG(_verbose, "Connection to X server refused.");
55     case XkbOD_BadServerVersion:  THROW_MSG(_verbose, "Bad X11 server version.");
56     case XkbOD_NonXkbServer:      THROW_MSG(_verbose, "XKB not present.");
57     default:                      THROW_MSG(_verbose, "XKB refused to open the display with reason '" << reasonReturn << "'.");
58   }
59 
60   _kbdDescPtr = XkbAllocKeyboard();
61   if (_kbdDescPtr == NULL) {
62     THROW_MSG(_verbose, "Failed to get keyboard description.");
63   }
64 
65   _kbdDescPtr->dpy = _display;
66   if (_deviceId != XkbUseCoreKbd) {
67     _kbdDescPtr->device_spec = _deviceId;
68   }
69 }
70 
~XKeyboard()71 XKeyboard::~XKeyboard()
72 {
73   if(_kbdDescPtr!=NULL)
74     XkbFreeKeyboard(_kbdDescPtr, 0, True);
75 
76   if (_display!=NULL) {
77     XCloseDisplay(_display);
78   }
79 }
80 
81 // XkbRF_VarDefsRec contains heap-allocated C strings, but doesn't provide a
82 // direct cleanup method. This wrapper privides a workaround.
83 // See also https://gitlab.freedesktop.org/xorg/lib/libxkbfile/issues/6
84 struct XkbRF_VarDefsRec_wrapper {
85 
86   XkbRF_VarDefsRec _it;
87 
XkbRF_VarDefsRec_wrapperkb::XkbRF_VarDefsRec_wrapper88   XkbRF_VarDefsRec_wrapper() {
89     std::memset(&_it,0,sizeof(_it));
90   }
91 
~XkbRF_VarDefsRec_wrapperkb::XkbRF_VarDefsRec_wrapper92   ~XkbRF_VarDefsRec_wrapper() {
93     if(_it.model) std::free(_it.model);
94     if(_it.layout) std::free(_it.layout);
95     if(_it.variant) std::free(_it.variant);
96     if(_it.options) std::free(_it.options);
97   }
98 };
99 
get_layout_variant()100 layout_variant_strings XKeyboard::get_layout_variant()
101 {
102   XkbRF_VarDefsRec_wrapper vdr;
103   char* tmp = NULL;
104   Bool bret;
105 
106   bret = XkbRF_GetNamesProp(_display, &tmp, &vdr._it);
107   free(tmp);  // return memory allocated by XkbRF_GetNamesProp
108   CHECK_MSG(_verbose, bret==True, "Failed to get keyboard properties");
109 
110   return make_pair(string(vdr._it.layout ? vdr._it.layout : "us"),
111                    string(vdr._it.variant ? vdr._it.variant : ""));
112 }
113 
build_layout_from(string_vector & out,const layout_variant_strings & lv)114 void XKeyboard::build_layout_from(string_vector& out, const layout_variant_strings& lv)
115 {
116   std::istringstream layout(lv.first);
117   std::istringstream variant(lv.second);
118 
119   while(true) {
120     string l,v;
121 
122     getline(layout, l, ',');
123     getline(variant, v, ',');
124     if(!layout && !variant)
125       break;
126 
127     if(v!="") {
128       v = "(" + v + ")";
129     }
130     if(l!="") {
131       out.push_back(l+v);
132     }
133   }
134 }
135 
136 
build_layout(string_vector & out)137 void XKeyboard::build_layout(string_vector& out)
138 {
139   layout_variant_strings lv=this->get_layout_variant();
140   build_layout_from(out, lv);
141 }
142 
wait_event()143 void XKeyboard::wait_event()
144 {
145   CHECK(_verbose, _display != 0);
146 
147   Bool bret = XkbSelectEventDetails(_display, XkbUseCoreKbd,
148       XkbStateNotify, XkbAllStateComponentsMask, XkbGroupStateMask);
149   CHECK_MSG(_verbose, bret==True, "XkbSelectEventDetails failed");
150 
151   XEvent event;
152   int iret = XNextEvent(_display, &event);
153   CHECK_MSG(_verbose, iret==0, "XNextEvent failed with " << iret);
154 }
155 
set_group(int groupNum)156 void XKeyboard::set_group(int groupNum)
157 {
158   Bool result = XkbLockGroup(_display, _deviceId, groupNum);
159   CHECK(_verbose, result == True);
160   XFlush(_display);
161 }
162 
get_group() const163 int XKeyboard::get_group() const
164 {
165   XkbStateRec xkbState;
166   XkbGetState(_display, _deviceId, &xkbState);
167   return static_cast<int>(xkbState.group);
168 }
169 
170 // returns true if symbol is ok
filter(const string_vector & nonsyms,const std::string & symbol)171 bool filter(const string_vector& nonsyms, const std::string& symbol)
172 {
173   if(symbol.empty())
174     return false;
175 
176   // Filter out all prohibited words
177   string_vector::const_iterator r = find(nonsyms.begin(), nonsyms.end(), symbol);
178   if(r != nonsyms.end())
179     return false;
180 
181   // Filter out all numbers groups started with number
182   if(isdigit(symbol[0]))
183     return false;
184 
185   return true;
186 }
187 
188 }
189 
190