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