1 /*
2     SPDX-FileCopyrightText: 2010 Andriy Rysin <rysin@kde.org>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #ifndef X11_HELPER_H_
8 #define X11_HELPER_H_
9 
10 #include <QAbstractNativeEventFilter>
11 #include <QKeySequence>
12 #include <QString>
13 #include <QStringList>
14 #include <QWidget>
15 #include <QX11Info>
16 
17 #define explicit explicit_is_keyword_in_cpp
18 #include <xcb/xcb.h>
19 #include <xcb/xkb.h>
20 #undef explicit
21 
22 namespace
23 {
24 typedef union {
25     /* All XKB events share these fields. */
26     struct {
27         uint8_t response_type;
28         uint8_t xkbType;
29         uint16_t sequence;
30         xcb_timestamp_t time;
31         uint8_t deviceID;
32     } any;
33     xcb_xkb_new_keyboard_notify_event_t new_keyboard_notify;
34     xcb_xkb_map_notify_event_t map_notify;
35     xcb_xkb_state_notify_event_t state_notify;
36 } _xkb_event;
37 }
38 
39 class XEventNotifier : public QObject, public QAbstractNativeEventFilter
40 {
41     Q_OBJECT
42 
43 Q_SIGNALS:
44     void layoutChanged();
45     void layoutMapChanged();
46 
47 public:
48     XEventNotifier();
~XEventNotifier()49     ~XEventNotifier() override
50     {
51     }
52 
53     virtual void start();
54     virtual void stop();
55 
56 protected:
57     virtual bool processOtherEvents(xcb_generic_event_t *e);
58     virtual bool processXkbEvents(xcb_generic_event_t *e);
59     bool nativeEventFilter(const QByteArray &eventType, void *message, long *) override;
60 
61 private:
62     int registerForXkbEvents(Display *display);
63     bool isXkbEvent(xcb_generic_event_t *event);
64     bool isGroupSwitchEvent(_xkb_event *event);
65     bool isLayoutSwitchEvent(_xkb_event *event);
66 
67     int xkbOpcode;
68 };
69 
70 struct XkbConfig {
71     QString keyboardModel;
72     QStringList layouts;
73     QStringList variants;
74     QStringList options;
75 
isValidXkbConfig76     bool isValid()
77     {
78         return !layouts.empty();
79     }
80 };
81 
82 class LayoutUnit
83 {
84 public:
85     static const int MAX_LABEL_LENGTH;
86 
LayoutUnit()87     LayoutUnit()
88     {
89     }
90     explicit LayoutUnit(const QString &fullLayoutName);
LayoutUnit(const QString & layout,const QString & variant)91     LayoutUnit(const QString &layout, const QString &variant)
92     {
93         m_layout = layout;
94         m_variant = variant;
95     }
LayoutUnit(const LayoutUnit & other)96     /*explicit*/ LayoutUnit(const LayoutUnit &other)
97     {
98         operator=(other);
99     }
100 
101     LayoutUnit &operator=(const LayoutUnit &other)
102     {
103         if (this != &other) {
104             m_layout = other.m_layout;
105             m_variant = other.m_variant;
106             displayName = other.displayName;
107             shortcut = other.shortcut;
108         }
109         return *this;
110     }
111 
getRawDisplayName()112     QString getRawDisplayName() const
113     {
114         return displayName;
115     }
getDisplayName()116     QString getDisplayName() const
117     {
118         return !displayName.isEmpty() ? displayName : m_layout;
119     }
setDisplayName(const QString & name)120     void setDisplayName(const QString &name)
121     {
122         displayName = name;
123     }
124 
setShortcut(const QKeySequence & shortcut)125     void setShortcut(const QKeySequence &shortcut)
126     {
127         this->shortcut = shortcut;
128     }
getShortcut()129     QKeySequence getShortcut() const
130     {
131         return shortcut;
132     }
layout()133     QString layout() const
134     {
135         return m_layout;
136     }
setLayout(const QString & layout)137     void setLayout(const QString &layout)
138     {
139         m_layout = layout;
140     }
variant()141     QString variant() const
142     {
143         return m_variant;
144     }
setVariant(const QString & variant)145     void setVariant(const QString &variant)
146     {
147         m_variant = variant;
148     }
149 
isEmpty()150     bool isEmpty() const
151     {
152         return m_layout.isEmpty();
153     }
isValid()154     bool isValid() const
155     {
156         return !isEmpty();
157     }
158     bool operator==(const LayoutUnit &layoutItem) const
159     {
160         // FIXME: why not compare the other properties?
161         return m_layout == layoutItem.m_layout && m_variant == layoutItem.m_variant;
162     }
163     bool operator!=(const LayoutUnit &layoutItem) const
164     {
165         return !(*this == layoutItem);
166     }
167     QString toString() const;
168 
169 private:
170     QString displayName;
171     QKeySequence shortcut;
172     QString m_layout;
173     QString m_variant;
174 };
175 
176 struct LayoutSet {
177     QList<LayoutUnit> layouts;
178     LayoutUnit currentLayout;
179 
LayoutSetLayoutSet180     LayoutSet()
181     {
182     }
183 
LayoutSetLayoutSet184     LayoutSet(const LayoutSet &other)
185     {
186         operator=(other);
187     }
188 
isValidLayoutSet189     bool isValid() const
190     {
191         return currentLayout.isValid() && layouts.contains(currentLayout);
192     }
193 
194     bool operator==(const LayoutSet &currentLayouts) const
195     {
196         return this->layouts == currentLayouts.layouts && this->currentLayout == currentLayouts.currentLayout;
197     }
198 
199     LayoutSet &operator=(const LayoutSet &currentLayouts)
200     {
201         this->layouts = currentLayouts.layouts;
202         this->currentLayout = currentLayouts.currentLayout;
203         return *this;
204     }
205 
toStringLayoutSet206     QString toString() const
207     {
208         QString str(currentLayout.toString());
209         str += QLatin1String(": ");
210         for (const auto &layoutUnit : qAsConst(layouts)) {
211             str += layoutUnit.toString() + QLatin1Char(' ');
212         }
213         return str;
214     }
215 
toStringLayoutSet216     static QString toString(const QList<LayoutUnit> &layoutUnits)
217     {
218         QString str;
219         for (const auto &layoutUnit : layoutUnits) {
220             str += layoutUnit.toString() + QLatin1Char(',');
221         }
222         return str;
223     }
224 };
225 
226 class X11Helper
227 {
228 public:
229     static const int MAX_GROUP_COUNT;
230     static const int ARTIFICIAL_GROUP_LIMIT_COUNT;
231 
232     static const char LEFT_VARIANT_STR[];
233     static const char RIGHT_VARIANT_STR[];
234 
235     static bool xkbSupported(int *xkbOpcode);
236 
237     static void switchToNextLayout();
238     static void scrollLayouts(int delta);
239     static bool isDefaultLayout();
240     static bool setDefaultLayout();
241     static bool setLayout(const LayoutUnit &layout);
242     static LayoutUnit getCurrentLayout();
243     static LayoutSet getCurrentLayouts();
244     static QList<LayoutUnit> getLayoutsList();
245     static QStringList getLayoutsListAsString(const QList<LayoutUnit> &layoutsList);
246 
247     enum FetchType { ALL, LAYOUTS_ONLY, MODEL_ONLY };
248     static bool getGroupNames(Display *dpy, XkbConfig *xkbConfig, FetchType fetchType);
249 
250     static unsigned int getGroup();
251     static bool setGroup(unsigned int group);
252 };
253 
254 #endif /* X11_HELPER_H_ */
255