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 | // TODO: implement scroll? | ||||
84 | | ||||
85 | m_notifierItem->setStatus(KStatusNotifierItem::Active); | ||||
86 | } | ||||
87 | | ||||
88 | void KeyboardLayout::switchToNextLayout() | ||||
89 | { | ||||
90 | m_xkb->switchToNextLayout(); | ||||
91 | checkLayoutChange(); | ||||
92 | } | ||||
93 | | ||||
69 | void KeyboardLayout::reconfigure() | 94 | void KeyboardLayout::reconfigure() | ||
70 | { | 95 | { | ||
71 | m_xkb->reconfigure(); | 96 | m_xkb->reconfigure(); | ||
72 | resetLayout(); | 97 | resetLayout(); | ||
73 | } | 98 | } | ||
74 | 99 | | |||
75 | void KeyboardLayout::resetLayout() | 100 | void KeyboardLayout::resetLayout() | ||
76 | { | 101 | { | ||
77 | m_layout = m_xkb->currentLayout(); | 102 | m_layout = m_xkb->currentLayout(); | ||
103 | 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… | |||||
104 | reinitNotifierMenu(); | ||||
78 | } | 105 | } | ||
79 | 106 | | |||
80 | void KeyboardLayout::keyEvent(KeyEvent *event) | 107 | void KeyboardLayout::keyEvent(KeyEvent *event) | ||
81 | { | 108 | { | ||
82 | if (!event->isAutoRepeat()) { | 109 | if (!event->isAutoRepeat()) { | ||
83 | checkLayoutChange(); | 110 | checkLayoutChange(); | ||
84 | } | 111 | } | ||
85 | } | 112 | } | ||
86 | 113 | | |||
87 | void KeyboardLayout::checkLayoutChange() | 114 | void KeyboardLayout::checkLayoutChange() | ||
88 | { | 115 | { | ||
89 | const auto layout = m_xkb->currentLayout(); | 116 | const auto layout = m_xkb->currentLayout(); | ||
90 | if (m_layout == layout) { | 117 | if (m_layout == layout) { | ||
91 | return; | 118 | return; | ||
92 | } | 119 | } | ||
93 | m_layout = layout; | 120 | m_layout = layout; | ||
94 | notifyLayoutChange(); | 121 | notifyLayoutChange(); | ||
122 | updateNotifier(); | ||||
95 | } | 123 | } | ||
96 | 124 | | |||
97 | void KeyboardLayout::notifyLayoutChange() | 125 | void KeyboardLayout::notifyLayoutChange() | ||
98 | { | 126 | { | ||
99 | // notify OSD service about the new layout | 127 | // notify OSD service about the new layout | ||
100 | if (!kwinApp()->usesLibinput()) { | 128 | if (!kwinApp()->usesLibinput()) { | ||
101 | return; | 129 | return; | ||
102 | } | 130 | } | ||
103 | // only if kwin is in charge of keyboard input | 131 | // only if kwin is in charge of keyboard input | ||
104 | QDBusMessage msg = QDBusMessage::createMethodCall( | 132 | QDBusMessage msg = QDBusMessage::createMethodCall( | ||
105 | QStringLiteral("org.kde.plasmashell"), | 133 | QStringLiteral("org.kde.plasmashell"), | ||
106 | QStringLiteral("/org/kde/osdService"), | 134 | QStringLiteral("/org/kde/osdService"), | ||
107 | QStringLiteral("org.kde.osdService"), | 135 | QStringLiteral("org.kde.osdService"), | ||
108 | QStringLiteral("kbdLayoutChanged")); | 136 | QStringLiteral("kbdLayoutChanged")); | ||
109 | 137 | | |||
110 | msg << m_xkb->layoutName(); | 138 | msg << m_xkb->layoutName(); | ||
111 | 139 | | |||
112 | QDBusConnection::sessionBus().asyncCall(msg); | 140 | QDBusConnection::sessionBus().asyncCall(msg); | ||
113 | } | 141 | } | ||
114 | 142 | | |||
143 | void KeyboardLayout::updateNotifier() | ||||
144 | { | ||||
145 | m_notifierItem->setToolTipSubTitle(m_xkb->layoutName()); | ||||
146 | // TODO: update icon | ||||
147 | } | ||||
148 | | ||||
149 | void KeyboardLayout::reinitNotifierMenu() | ||||
150 | { | ||||
151 | const auto layouts = m_xkb->layoutNames(); | ||||
152 | | ||||
153 | QMenu *menu = new QMenu; | ||||
154 | auto switchLayout = [this] (xkb_layout_index_t index) { | ||||
155 | m_xkb->switchToLayout(index); | ||||
156 | checkLayoutChange(); | ||||
157 | }; | ||||
158 | for (auto it = layouts.begin(); it != layouts.end(); it++) { | ||||
159 | menu->addAction(it.value(), std::bind(switchLayout, it.key())); | ||||
160 | } | ||||
161 | | ||||
162 | menu->addSeparator(); | ||||
163 | menu->addAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure Layouts..."), this, | ||||
164 | [this] { | ||||
165 | // 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… | |||||
166 | QProcess *p = new Process(this); | ||||
167 | p->setArguments(QStringList{QStringLiteral("--args=--tab=layouts"), QStringLiteral("kcm_keyboard")}); | ||||
168 | p->setProcessEnvironment(kwinApp()->processStartupEnvironment()); | ||||
169 | p->setProgram(QStringLiteral("kcmshell5")); | ||||
170 | connect(p, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), p, &QProcess::deleteLater); | ||||
171 | connect(p, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error), this, | ||||
172 | [p] (QProcess::ProcessError e) { | ||||
173 | if (e == QProcess::FailedToStart) { | ||||
174 | qCDebug(KWIN_CORE) << "Failed to start kcmshell5"; | ||||
175 | } | ||||
176 | } | ||||
177 | ); | ||||
178 | p->start(); | ||||
179 | } | ||||
180 | ); | ||||
181 | | ||||
182 | 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. | |||||
183 | } | ||||
184 | | ||||
115 | } | 185 | } |
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.