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 "XKeyboard.h"
13 #include "X11Exception.h"
14 #include <algorithm>
15 #include <cstdlib>
16 #include <cctype>
17 #include <cstring>
18 
19 
20 #define CHECK_MSG(expression,msg)                       \
21                                                         \
22     if(!(expression)){                                  \
23                                                         \
24         std::ostringstream stream;                      \
25                                                         \
26         stream << __FILE__    << " : "                  \
27                << __LINE__    << " : "                  \
28                << "XKeyboard" << " : "                  \
29                << msg << "[ " << #expression << "]";    \
30                                                         \
31         throw std::runtime_error(stream.str());         \
32     }
33 
34 
35 namespace kb {
36 
XKeyboard()37 XKeyboard::XKeyboard()
38   : _display(0), _deviceId(XkbUseCoreKbd)
39 {
40 
41   XkbIgnoreExtension(False);
42 
43   char* displayName = strdup("");
44   int eventCode;
45   int errorReturn;
46   int major = XkbMajorVersion;
47   int minor = XkbMinorVersion;
48   int reasonReturn;
49   _display = XkbOpenDisplay(displayName, &eventCode, &errorReturn, &major,
50       &minor, &reasonReturn);
51   switch (reasonReturn) {
52     case XkbOD_BadLibraryVersion:
53       throw X11Exception("Bad XKB library version.");
54       break;
55     case XkbOD_ConnectionRefused:
56       throw X11Exception("Connection to X server refused.");
57       break;
58     case XkbOD_BadServerVersion:
59       throw X11Exception("Bad X11 server version.");
60       break;
61     case XkbOD_NonXkbServer:
62       throw X11Exception("XKB not present.");
63       break;
64     case XkbOD_Success:
65       break;
66   }
67 
68   _kbdDescPtr = XkbAllocKeyboard();
69   if (_kbdDescPtr == NULL) {
70     XCloseDisplay(_display);
71     throw X11Exception("Failed to get keyboard description.");
72   }
73 
74   _kbdDescPtr->dpy = _display;
75   if (_deviceId != XkbUseCoreKbd) {
76     _kbdDescPtr->device_spec = _deviceId;
77   }
78 }
79 
~XKeyboard()80 XKeyboard::~XKeyboard()
81 {
82   if(_kbdDescPtr!=NULL)
83     XkbFreeKeyboard(_kbdDescPtr, 0, True);
84 
85   XCloseDisplay(_display);
86 }
87 
get_kb_string()88 std::string XKeyboard::get_kb_string()
89 {
90   XkbGetControls(_display, XkbAllControlsMask, _kbdDescPtr);
91   XkbGetNames(_display, XkbSymbolsNameMask, _kbdDescPtr);
92 
93   Atom symNameAtom = _kbdDescPtr->names->symbols;
94 
95   CHECK_MSG(symNameAtom != None,"Symbol Name is not present");
96 
97   char* kbsC = XGetAtomName(_display, symNameAtom);
98 
99   CHECK_MSG(kbsC,"Symbol Name pointer is invalid");
100 
101   std::string kbs(kbsC);
102   XFree(kbsC);
103 
104   CHECK_MSG(!kbs.empty(),"Symbol Name is an empty string");
105 
106   return kbs;
107 
108   /*     StringVector symNames; */
109   /*     XkbSymbolParser symParser; */
110   /*     symParser.parse(symName, symNames); */
111   /*     return symNames; */
112 }
113 
wait_event()114 void XKeyboard::wait_event()
115 {
116   CHECK_MSG(_display != 0,"Display is not present");
117 
118   Bool bret = XkbSelectEventDetails(_display, XkbUseCoreKbd,
119       XkbStateNotify, XkbAllStateComponentsMask, XkbGroupStateMask);
120 
121   CHECK_MSG(bret==True, "Failed to select event details");
122 
123   XEvent event;
124   int iret = XNextEvent(_display, &event);
125 
126   CHECK_MSG(iret==0,"Failed to retrieve next event with " << iret);
127 }
128 
set_group(int groupNum)129 void XKeyboard::set_group(int groupNum)
130 {
131   Bool result = XkbLockGroup(_display, _deviceId, groupNum);
132   CHECK_MSG(result == True,"Failed to set group to: " << groupNum);
133 }
134 
get_group() const135 int XKeyboard::get_group() const
136 {
137   XkbStateRec xkbState;
138   XkbGetState(_display, _deviceId, &xkbState);
139   return static_cast<int>(xkbState.group);
140 }
141 
142 // returns true if symbol is ok
filter(const string_vector & nonsyms,const std::string & symbol)143 bool filter(const string_vector& nonsyms, const std::string& symbol)
144 {
145   if(symbol.empty())
146     return false;
147 
148   // Filter out all prohibited words
149   string_vector::const_iterator r = find(nonsyms.begin(), nonsyms.end(), symbol);
150   if(r != nonsyms.end())
151     return false;
152 
153   // Filter out all numbers groups started with number
154   if(isdigit(symbol[0]))
155     return false;
156 
157   return true;
158 }
159 
parse1(const std::string & symbols,const string_vector & nonsyms)160 string_vector parse1(const std::string& symbols, const string_vector& nonsyms)
161 {
162   bool inSymbol = false;
163   std::string sym;
164   string_vector symlist;
165 
166   for (size_t i = 0; i < symbols.size(); i++) {
167     char ch = symbols[i];
168     if (ch == '+') {
169       if (inSymbol && !sym.empty() && filter(nonsyms, sym)) {
170         symlist.push_back(sym);
171       }
172       inSymbol = true;
173       sym.clear();
174     }
175     else if (inSymbol && ch == '(') {
176       inSymbol = false;
177     }
178     else if (inSymbol && (isalpha(static_cast<int>(ch)) || ch == '_')) {
179       sym.append(1, ch);
180     }
181     else {
182       if (inSymbol && !sym.empty() && filter(nonsyms, sym)) {
183         symlist.push_back(sym);
184       }
185       inSymbol = false;
186     }
187   }
188 
189   if (inSymbol && !sym.empty() && filter(nonsyms, sym)) {
190     symlist.push_back(sym);
191   }
192 
193   return symlist;
194 }
195 
safe_push_back(string_vector & v,std::string s,std::string)196 void safe_push_back(string_vector& v, std::string s, std::string /*note*/)
197 {
198   if(s.empty()) return;
199 #ifdef NOT_TEXSTUDIO
200   if(!note.empty()) {
201     s += "(" + note + ")";
202   }
203 #endif
204   v.push_back(s);
205 }
206 
goodchar(char ch)207 bool goodchar(char ch)
208 {
209   return (isdigit(ch) || isalpha(static_cast<int>(ch)) || ch == '_' || ch == '-');
210 }
211 
parse2(const std::string & symbols,const string_vector & nonsyms)212 string_vector parse2(const std::string& symbols, const string_vector& nonsyms)
213 {
214   enum{ok,skip,broken} state = ok;
215   int paren = 0;
216   std::string sym;
217   // Words between optional '(' ')'
218   std::string note;
219   string_vector symlist;
220 
221   for (size_t i = 0; i < symbols.size(); i++) {
222 	char ch = symbols[i];
223 
224     if (ch == '+') {
225       if (state != broken && paren == 0 && filter(nonsyms, sym)) {
226         safe_push_back(symlist, sym, note);
227       }
228       state = ok;
229       paren = 0;
230       sym.clear();
231       note.clear();
232     }
233     else if (state == ok && ch == '(') {
234       paren++;
235     }
236     else if (state == ok && ch == ')') {
237       paren--;
238     }
239     else if (state == ok && ch == ':') {
240       state = skip;
241     }
242     else if (state == ok && goodchar(ch)) {
243       if (paren == 0)
244         sym.append(1, ch);
245       else
246         note.append(1, ch);
247     }
248     else if(state == ok) {
249       state = broken;
250     }
251   }
252 
253   if (state != broken && paren == 0 && filter(nonsyms, sym)) {
254     safe_push_back(symlist, sym, note);
255   }
256 
257   return symlist;
258 }
259 
parse3(const std::string & symbols,const string_vector & nonsyms)260 string_vector parse3(const std::string& symbols, const string_vector& nonsyms)
261 {
262   enum{ok,skip,broken} state = ok;
263   int paren = 0;
264   std::string sym;
265   // Words between optional '(' ')'
266   std::string note;
267   string_vector symlist;
268 
269   for (size_t i = 0; i < symbols.size(); i++) {
270     char ch = symbols[i];
271 
272     if (ch == '+' || ch == '_') {
273       if(paren == 0) {
274         if (state != broken && paren == 0 && filter(nonsyms, sym)) {
275           safe_push_back(symlist, sym, note);
276         }
277         state = ok;
278         sym.clear();
279         note.clear();
280       }
281     }
282     else if (state == ok && ch == '(') {
283       paren++;
284     }
285     else if (state == ok && ch == ')') {
286       paren--;
287     }
288     else if (state == ok && ch == ':') {
289       state = skip;
290     }
291     else if (state == ok && goodchar(ch)) {
292       if (paren == 0)
293         sym.append(1, ch);
294       else
295         note.append(1, ch);
296     }
297     else if(state == ok) {
298       state = broken;
299     }
300   }
301 
302   if (state != broken && paren == 0 && filter(nonsyms, sym)) {
303     safe_push_back(symlist, sym, note);
304   }
305 
306   return symlist;
307 }
308 
309 }
310 
311 #undef CHECK_MSG
312 
313