Changeset View
Standalone View
keyboard_layout.cpp
Show All 16 Lines | |||||
17 | You should have received a copy of the GNU General Public License | 17 | You should have received a copy of the GNU General Public License | ||
18 | along with this program. If not, see <http://www.gnu.org/licenses/>. | 18 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
19 | *********************************************************************/ | 19 | *********************************************************************/ | ||
20 | #include "keyboard_layout.h" | 20 | #include "keyboard_layout.h" | ||
21 | #include "keyboard_input.h" | 21 | #include "keyboard_input.h" | ||
22 | #include "input_event.h" | 22 | #include "input_event.h" | ||
23 | #include "main.h" | 23 | #include "main.h" | ||
24 | #include "platform.h" | 24 | #include "platform.h" | ||
25 | #include "utils.h" | ||||
25 | 26 | | |||
26 | #include <KGlobalAccel> | 27 | #include <KGlobalAccel> | ||
28 | #include <KLocalizedString> | ||||
29 | #include <KNotifications/KStatusNotifierItem> | ||||
27 | #include <QAction> | 30 | #include <QAction> | ||
28 | #include <QDBusConnection> | 31 | #include <QDBusConnection> | ||
29 | #include <QDBusMessage> | 32 | #include <QDBusMessage> | ||
30 | #include <QDBusPendingCall> | 33 | #include <QDBusPendingCall> | ||
34 | #include <QMenu> | ||||
31 | 35 | | |||
32 | namespace KWin | 36 | namespace KWin | ||
33 | { | 37 | { | ||
34 | 38 | | |||
35 | KeyboardLayout::KeyboardLayout(Xkb *xkb) | 39 | KeyboardLayout::KeyboardLayout(Xkb *xkb) | ||
36 | : QObject() | 40 | : QObject() | ||
37 | , m_xkb(xkb) | 41 | , m_xkb(xkb) | ||
42 | , m_notifierItem(new KStatusNotifierItem(this)) | ||||
38 | { | 43 | { | ||
39 | } | 44 | } | ||
40 | 45 | | |||
41 | KeyboardLayout::~KeyboardLayout() = default; | 46 | KeyboardLayout::~KeyboardLayout() = default; | ||
42 | 47 | | |||
43 | void KeyboardLayout::init() | 48 | void KeyboardLayout::init() | ||
44 | { | 49 | { | ||
45 | QAction *switchKeyboardAction = new QAction(this); | 50 | QAction *switchKeyboardAction = new QAction(this); | ||
46 | switchKeyboardAction->setObjectName(QStringLiteral("Switch to Next Keyboard Layout")); | 51 | switchKeyboardAction->setObjectName(QStringLiteral("Switch to Next Keyboard Layout")); | ||
47 | switchKeyboardAction->setProperty("componentName", QStringLiteral("KDE Keyboard Layout Switcher")); | 52 | switchKeyboardAction->setProperty("componentName", QStringLiteral("KDE Keyboard Layout Switcher")); | ||
48 | const QKeySequence sequence = QKeySequence(Qt::ALT+Qt::CTRL+Qt::Key_K); | 53 | const QKeySequence sequence = QKeySequence(Qt::ALT+Qt::CTRL+Qt::Key_K); | ||
49 | KGlobalAccel::self()->setDefaultShortcut(switchKeyboardAction, QList<QKeySequence>({sequence})); | 54 | KGlobalAccel::self()->setDefaultShortcut(switchKeyboardAction, QList<QKeySequence>({sequence})); | ||
50 | KGlobalAccel::self()->setShortcut(switchKeyboardAction, QList<QKeySequence>({sequence})); | 55 | KGlobalAccel::self()->setShortcut(switchKeyboardAction, QList<QKeySequence>({sequence})); | ||
51 | kwinApp()->platform()->setupActionForGlobalAccel(switchKeyboardAction); | 56 | kwinApp()->platform()->setupActionForGlobalAccel(switchKeyboardAction); | ||
52 | connect(switchKeyboardAction, &QAction::triggered, this, | 57 | connect(switchKeyboardAction, &QAction::triggered, this, &KeyboardLayout::switchToNextLayout); | ||
53 | [this] { | | |||
54 | m_xkb->switchToNextLayout(); | | |||
55 | checkLayoutChange(); | | |||
56 | } | | |||
57 | ); | | |||
58 | 58 | | |||
59 | QDBusConnection::sessionBus().connect(QString(), | 59 | QDBusConnection::sessionBus().connect(QString(), | ||
60 | QStringLiteral("/Layouts"), | 60 | QStringLiteral("/Layouts"), | ||
61 | QStringLiteral("org.kde.keyboard"), | 61 | QStringLiteral("org.kde.keyboard"), | ||
62 | QStringLiteral("reloadConfig"), | 62 | QStringLiteral("reloadConfig"), | ||
63 | this, | 63 | this, | ||
64 | SLOT(reconfigure())); | 64 | SLOT(reconfigure())); | ||
65 | initNotifierItem(); | ||||
65 | 66 | | |||
66 | reconfigure(); | 67 | reconfigure(); | ||
67 | } | 68 | } | ||
68 | 69 | | |||
70 | void KeyboardLayout::initNotifierItem() | ||||
71 | { | ||||
72 | m_notifierItem->setCategory(KStatusNotifierItem::Hardware); | ||||
73 | m_notifierItem->setStatus(KStatusNotifierItem::Active); | ||||
74 | m_notifierItem->setToolTipTitle(i18nc("tooltip title", "Keyboard Layout")); | ||||
75 | m_notifierItem->setTitle(i18nc("tooltip title", "Keyboard Layout")); | ||||
76 | m_notifierItem->setToolTipIconByName(QStringLiteral("preferences-desktop-keyboard")); | ||||
77 | m_notifierItem->setStandardActionsEnabled(false); | ||||
78 | | ||||
79 | // TODO: proper icon | ||||
80 | m_notifierItem->setIconByName(QStringLiteral("preferences-desktop-keyboard")); | ||||
81 | | ||||
82 | connect(m_notifierItem, &KStatusNotifierItem::activateRequested, this, &KeyboardLayout::switchToNextLayout); | ||||
83 | connect(m_notifierItem, &KStatusNotifierItem::scrollRequested, this, | ||||
84 | [this] (int delta, Qt::Orientation orientation) { | ||||
85 | if (orientation == Qt::Horizontal) { | ||||
86 | return; | ||||
87 | } | ||||
88 | if (delta > 0) { | ||||
89 | switchToNextLayout(); | ||||
90 | } else { | ||||
91 | switchToPreviousLayout(); | ||||
92 | } | ||||
93 | } | ||||
94 | ); | ||||
95 | | ||||
96 | m_notifierItem->setStatus(KStatusNotifierItem::Active); | ||||
97 | } | ||||
98 | | ||||
99 | void KeyboardLayout::switchToNextLayout() | ||||
100 | { | ||||
101 | m_xkb->switchToNextLayout(); | ||||
102 | checkLayoutChange(); | ||||
103 | } | ||||
104 | | ||||
105 | void KeyboardLayout::switchToPreviousLayout() | ||||
106 | { | ||||
107 | m_xkb->switchToPreviousLayout(); | ||||
108 | checkLayoutChange(); | ||||
109 | } | ||||
110 | | ||||
69 | void KeyboardLayout::reconfigure() | 111 | void KeyboardLayout::reconfigure() | ||
70 | { | 112 | { | ||
71 | m_xkb->reconfigure(); | 113 | m_xkb->reconfigure(); | ||
72 | resetLayout(); | 114 | resetLayout(); | ||
73 | } | 115 | } | ||
74 | 116 | | |||
75 | void KeyboardLayout::resetLayout() | 117 | void KeyboardLayout::resetLayout() | ||
76 | { | 118 | { | ||
77 | m_layout = m_xkb->currentLayout(); | 119 | m_layout = m_xkb->currentLayout(); | ||
120 | updateNotifier(); | ||||
davidedmundson: You only want this in ::reconfigure() before resetLayout and not here.
You're calling this… | |||||
Yes it does. processKeymapChange is called when a parent Wayland server informs this nested Wayland server about a new keymap. This means we have a new number of keyboard layouts and that can affect whether the notifier is shown or not. graesslin: > that won't affect whether you're showing or hiding it.
Yes it does. processKeymapChange is… | |||||
121 | reinitNotifierMenu(); | ||||
78 | } | 122 | } | ||
79 | 123 | | |||
80 | void KeyboardLayout::keyEvent(KeyEvent *event) | 124 | void KeyboardLayout::keyEvent(KeyEvent *event) | ||
81 | { | 125 | { | ||
82 | if (!event->isAutoRepeat()) { | 126 | if (!event->isAutoRepeat()) { | ||
83 | checkLayoutChange(); | 127 | checkLayoutChange(); | ||
84 | } | 128 | } | ||
85 | } | 129 | } | ||
86 | 130 | | |||
87 | void KeyboardLayout::checkLayoutChange() | 131 | void KeyboardLayout::checkLayoutChange() | ||
88 | { | 132 | { | ||
89 | const auto layout = m_xkb->currentLayout(); | 133 | const auto layout = m_xkb->currentLayout(); | ||
90 | if (m_layout == layout) { | 134 | if (m_layout == layout) { | ||
91 | return; | 135 | return; | ||
92 | } | 136 | } | ||
93 | m_layout = layout; | 137 | m_layout = layout; | ||
94 | notifyLayoutChange(); | 138 | notifyLayoutChange(); | ||
139 | updateNotifier(); | ||||
95 | } | 140 | } | ||
96 | 141 | | |||
97 | void KeyboardLayout::notifyLayoutChange() | 142 | void KeyboardLayout::notifyLayoutChange() | ||
98 | { | 143 | { | ||
99 | // notify OSD service about the new layout | 144 | // notify OSD service about the new layout | ||
100 | if (!kwinApp()->usesLibinput()) { | 145 | if (!kwinApp()->usesLibinput()) { | ||
101 | return; | 146 | return; | ||
102 | } | 147 | } | ||
103 | // only if kwin is in charge of keyboard input | 148 | // only if kwin is in charge of keyboard input | ||
104 | QDBusMessage msg = QDBusMessage::createMethodCall( | 149 | QDBusMessage msg = QDBusMessage::createMethodCall( | ||
105 | QStringLiteral("org.kde.plasmashell"), | 150 | QStringLiteral("org.kde.plasmashell"), | ||
106 | QStringLiteral("/org/kde/osdService"), | 151 | QStringLiteral("/org/kde/osdService"), | ||
107 | QStringLiteral("org.kde.osdService"), | 152 | QStringLiteral("org.kde.osdService"), | ||
108 | QStringLiteral("kbdLayoutChanged")); | 153 | QStringLiteral("kbdLayoutChanged")); | ||
109 | 154 | | |||
110 | msg << m_xkb->layoutName(); | 155 | msg << m_xkb->layoutName(); | ||
111 | 156 | | |||
112 | QDBusConnection::sessionBus().asyncCall(msg); | 157 | QDBusConnection::sessionBus().asyncCall(msg); | ||
113 | } | 158 | } | ||
114 | 159 | | |||
160 | void KeyboardLayout::updateNotifier() | ||||
161 | { | ||||
162 | m_notifierItem->setToolTipSubTitle(m_xkb->layoutName()); | ||||
163 | // TODO: update icon | ||||
164 | } | ||||
165 | | ||||
166 | void KeyboardLayout::reinitNotifierMenu() | ||||
167 | { | ||||
168 | const auto layouts = m_xkb->layoutNames(); | ||||
169 | | ||||
170 | QMenu *menu = new QMenu; | ||||
171 | auto switchLayout = [this] (xkb_layout_index_t index) { | ||||
172 | m_xkb->switchToLayout(index); | ||||
173 | checkLayoutChange(); | ||||
174 | }; | ||||
175 | for (auto it = layouts.begin(); it != layouts.end(); it++) { | ||||
176 | menu->addAction(it.value(), std::bind(switchLayout, it.key())); | ||||
177 | } | ||||
178 | | ||||
179 | menu->addSeparator(); | ||||
180 | menu->addAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure Layouts..."), this, | ||||
181 | [this] { | ||||
182 | // TODO: introduce helper function to start kcmshell5 | ||||
davidedmundson: so KToolInvocation::startServiceByName()?
| |||||
yes, except that we used to use KToolInvocation and ported away from it. Something - in the case of KWin - doesn't work for it. The standard case of KWin is special. You can see it with the line below. We create a Process, not a QProcess. It's a specialized class to do KWin specific adjustments. Sometimes KWin is meh. graesslin: yes, except that we used to use KToolInvocation and ported away from it. Something - in the… | |||||
183 | QProcess *p = new Process(this); | ||||
184 | p->setArguments(QStringList{QStringLiteral("--args=--tab=layouts"), QStringLiteral("kcm_keyboard")}); | ||||
185 | p->setProcessEnvironment(kwinApp()->processStartupEnvironment()); | ||||
186 | p->setProgram(QStringLiteral("kcmshell5")); | ||||
187 | connect(p, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), p, &QProcess::deleteLater); | ||||
188 | connect(p, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error), this, | ||||
189 | [p] (QProcess::ProcessError e) { | ||||
190 | if (e == QProcess::FailedToStart) { | ||||
191 | qCDebug(KWIN_CORE) << "Failed to start kcmshell5"; | ||||
192 | } | ||||
193 | } | ||||
194 | ); | ||||
195 | p->start(); | ||||
196 | } | ||||
197 | ); | ||||
198 | | ||||
199 | m_notifierItem->setContextMenu(menu); | ||||
You probably don't want to be doing this. SNI has two two code paths for menus. addAction() which works the way you'd expect creating a DBus menu and sending that setContextMenu is a more legacy version that gets a signal from Plasma to show a menu, then kwin's process does the actual showing of it. (and generally speaking doing that won't work on wayland..it might be an exception here) Given you want a flat list, use m_notifierItem->addAction(..) even for the separator. davidedmundson: You probably don't want to be doing this.
SNI has two two code paths for menus.
addAction()… | |||||
uh nice, didn't know that and followed the existing code too blindly. graesslin: uh nice, didn't know that and followed the existing code too blindly. | |||||
This doesn't work :-( I added the actions, but the menu doesn't get shown at all. graesslin: This doesn't work :-( I added the actions, but the menu doesn't get shown at all. | |||||
200 | } | ||||
201 | | ||||
115 | } | 202 | } |
You only want this in ::reconfigure() before resetLayout and not here.
You're calling this method from reconfigure and in processKeymapChange, from keyboard_input - that won't affect whether you're showing or hiding it.