Changeset View
Changeset View
Standalone View
Standalone View
applets/appmenu/plugin/appmenumodel.cpp
Show All 17 Lines | |||||
18 | * | 18 | * | ||
19 | * You should have received a copy of the GNU General Public License | 19 | * You should have received a copy of the GNU General Public License | ||
20 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | 20 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
21 | * | 21 | * | ||
22 | ******************************************************************/ | 22 | ******************************************************************/ | ||
23 | 23 | | |||
24 | #include "appmenumodel.h" | 24 | #include "appmenumodel.h" | ||
25 | 25 | | |||
26 | #include <config-X11.h> | | |||
27 | | ||||
28 | #if HAVE_X11 | | |||
29 | #include <QX11Info> | | |||
30 | #include <xcb/xcb.h> | | |||
31 | #endif | | |||
32 | | ||||
33 | #include <QAction> | 26 | #include <QAction> | ||
34 | #include <QMenu> | 27 | #include <QMenu> | ||
35 | #include <QDBusConnection> | 28 | #include <QDBusConnection> | ||
36 | #include <QDBusConnectionInterface> | 29 | #include <QDBusConnectionInterface> | ||
37 | #include <QDBusServiceWatcher> | 30 | #include <QDBusServiceWatcher> | ||
38 | #include <QGuiApplication> | 31 | #include <QGuiApplication> | ||
39 | 32 | | |||
40 | #include <dbusmenuimporter.h> | 33 | #include <dbusmenuimporter.h> | ||
41 | 34 | #include <abstracttasksmodel.h> | |||
42 | static const QByteArray s_x11AppMenuServiceNamePropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME"); | | |||
43 | static const QByteArray s_x11AppMenuObjectPathPropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH"); | | |||
44 | | ||||
45 | #if HAVE_X11 | | |||
46 | static QHash<QByteArray, xcb_atom_t> s_atoms; | | |||
47 | #endif | | |||
48 | 35 | | |||
49 | class KDBusMenuImporter : public DBusMenuImporter | 36 | class KDBusMenuImporter : public DBusMenuImporter | ||
50 | { | 37 | { | ||
51 | 38 | | |||
52 | public: | 39 | public: | ||
53 | KDBusMenuImporter(const QString &service, const QString &path, QObject *parent) | 40 | KDBusMenuImporter(const QString &service, const QString &path, QObject *parent) | ||
54 | : DBusMenuImporter(service, path, parent) | 41 | : DBusMenuImporter(service, path, parent) | ||
55 | { | 42 | { | ||
56 | 43 | | |||
57 | } | 44 | } | ||
58 | 45 | | |||
59 | protected: | 46 | protected: | ||
60 | QIcon iconForName(const QString &name) override | 47 | QIcon iconForName(const QString &name) override | ||
61 | { | 48 | { | ||
62 | return QIcon::fromTheme(name); | 49 | return QIcon::fromTheme(name); | ||
63 | } | 50 | } | ||
64 | 51 | | |||
65 | }; | 52 | }; | ||
66 | 53 | | |||
67 | AppMenuModel::AppMenuModel(QObject *parent) | 54 | AppMenuModel::AppMenuModel(QObject *parent) | ||
68 | : QAbstractListModel(parent), | 55 | : QAbstractListModel(parent), | ||
69 | m_serviceWatcher(new QDBusServiceWatcher(this)) | 56 | m_serviceWatcher(new QDBusServiceWatcher(this)), | ||
57 | m_tasksModel(new TaskManager::TasksModel(this)) | ||||
70 | { | 58 | { | ||
71 | connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this, &AppMenuModel::onActiveWindowChanged); | 59 | m_tasksModel->setFilterByScreen(true); | ||
72 | connect(KWindowSystem::self() | 60 | connect(m_tasksModel, &TaskManager::TasksModel::activeTaskChanged, this, &AppMenuModel::onActiveWindowChanged); | ||
73 | , static_cast<void (KWindowSystem::*)(WId)>(&KWindowSystem::windowChanged) | | |||
74 | , this | | |||
75 | , &AppMenuModel::onWindowChanged); | | |||
76 | 61 | | |||
77 | connect(this, &AppMenuModel::modelNeedsUpdate, this, [this] { | 62 | connect(this, &AppMenuModel::modelNeedsUpdate, this, [this] { | ||
78 | if (!m_updatePending) { | 63 | if (!m_updatePending) { | ||
79 | m_updatePending = true; | 64 | m_updatePending = true; | ||
80 | QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); | 65 | QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); | ||
81 | } | 66 | } | ||
82 | }); | 67 | }); | ||
83 | 68 | | |||
84 | connect(this, &AppMenuModel::screenGeometryChanged, this, [this] { | 69 | connect(this, &AppMenuModel::screenGeometryChanged, this, &AppMenuModel::onActiveWindowChanged); | ||
broulik: Is this needed? I would think if screen geometry changes, task manager updates and filters and… | |||||
85 | onWindowChanged(m_currentWindowId); | | |||
86 | }); | | |||
87 | 70 | | |||
88 | onActiveWindowChanged(KWindowSystem::activeWindow()); | 71 | onActiveWindowChanged(); | ||
89 | 72 | | |||
90 | m_serviceWatcher->setConnection(QDBusConnection::sessionBus()); | 73 | m_serviceWatcher->setConnection(QDBusConnection::sessionBus()); | ||
91 | //if our current DBus connection gets lost, close the menu | 74 | //if our current DBus connection gets lost, close the menu | ||
92 | //we'll select the new menu when the focus changes | 75 | //we'll select the new menu when the focus changes | ||
93 | connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this](const QString &serviceName) | 76 | connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this](const QString &serviceName) | ||
94 | { | 77 | { | ||
95 | if (serviceName == m_serviceName) { | 78 | if (serviceName == m_serviceName) { | ||
96 | setMenuAvailable(false); | 79 | setMenuAvailable(false); | ||
Show All 37 Lines | |||||
134 | } | 117 | } | ||
135 | 118 | | |||
136 | void AppMenuModel::setScreenGeometry(QRect geometry) | 119 | void AppMenuModel::setScreenGeometry(QRect geometry) | ||
137 | { | 120 | { | ||
138 | if (m_screenGeometry == geometry) { | 121 | if (m_screenGeometry == geometry) { | ||
139 | return; | 122 | return; | ||
140 | } | 123 | } | ||
141 | 124 | | |||
125 | m_tasksModel->setScreenGeometry(geometry); | ||||
142 | m_screenGeometry = geometry; | 126 | m_screenGeometry = geometry; | ||
143 | emit screenGeometryChanged(); | 127 | emit screenGeometryChanged(); | ||
I think you can just connect this signal to our signal in the constructor and then remove the if check here broulik: I think you can just connect this signal to our signal in the constructor and then remove the… | |||||
144 | } | 128 | } | ||
145 | 129 | | |||
146 | int AppMenuModel::rowCount(const QModelIndex &parent) const | 130 | int AppMenuModel::rowCount(const QModelIndex &parent) const | ||
147 | { | 131 | { | ||
148 | Q_UNUSED(parent); | 132 | Q_UNUSED(parent); | ||
149 | if (!m_menuAvailable || !m_menu) { | 133 | if (!m_menuAvailable || !m_menu) { | ||
150 | return 0; | 134 | return 0; | ||
151 | } | 135 | } | ||
152 | 136 | | |||
153 | return m_menu->actions().count(); | 137 | return m_menu->actions().count(); | ||
154 | } | 138 | } | ||
155 | 139 | | |||
156 | void AppMenuModel::update() | 140 | void AppMenuModel::update() | ||
157 | { | 141 | { | ||
158 | beginResetModel(); | 142 | beginResetModel(); | ||
159 | endResetModel(); | 143 | endResetModel(); | ||
160 | m_updatePending = false; | 144 | m_updatePending = false; | ||
161 | } | 145 | } | ||
162 | 146 | | |||
147 | #include <QDebug> | ||||
broulik: Remove | |||||
163 | 148 | | |||
164 | void AppMenuModel::onActiveWindowChanged(WId id) | 149 | void AppMenuModel::onActiveWindowChanged() | ||
165 | { | 150 | { | ||
166 | qApp->removeNativeEventFilter(this); | 151 | auto objectPath = m_tasksModel->data(m_tasksModel->activeTask(), TaskManager::AbstractTasksModel::ApplicationMenuObjectPath).value<QString>(); | ||
Please write as const QModelIndex activeTaskIdx = m_tasksMode->activeTask(); const QString objectPath = m_tasksModel->data(activeTaskIdx, TaskManager::AbstractTasmsModel::ApplicationMenuObjectPath).toString(); const QString serviceName = ....; (perhaps you could drop a using namespace TaskManager at the top of the cpp file) broulik: Please write as
```
const QModelIndex activeTaskIdx = m_tasksMode->activeTask();
const QString… | |||||
152 | auto serviceName = m_tasksModel->data(m_tasksModel->activeTask(), TaskManager::AbstractTasksModel::ApplicationMenuServiceName).value<QString>(); | ||||
167 | 153 | | |||
168 | if (!id) { | 154 | if (objectPath != QString() && serviceName != QString()) { | ||
broulik: `!objectPath.isEmpty()` | |||||
169 | setMenuAvailable(false); | 155 | setMenuAvailable(true); | ||
170 | emit modelNeedsUpdate(); | 156 | updateApplicationMenu(serviceName, objectPath); | ||
171 | return; | | |||
172 | } | | |||
173 | | ||||
174 | #if HAVE_X11 | | |||
175 | if (KWindowSystem::isPlatformX11()) { | | |||
176 | auto *c = QX11Info::connection(); | | |||
177 | | ||||
178 | auto getWindowPropertyString = [c](WId id, const QByteArray &name) -> QByteArray { | | |||
179 | QByteArray value; | | |||
180 | if (!s_atoms.contains(name)) { | | |||
181 | const xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom(c, false, name.length(), name.constData()); | | |||
182 | QScopedPointer<xcb_intern_atom_reply_t, QScopedPointerPodDeleter> atomReply(xcb_intern_atom_reply(c, atomCookie, nullptr)); | | |||
183 | if (atomReply.isNull()) { | | |||
184 | return value; | | |||
185 | } | | |||
186 | | ||||
187 | s_atoms[name] = atomReply->atom; | | |||
188 | if (s_atoms[name] == XCB_ATOM_NONE) { | | |||
189 | return value; | | |||
190 | } | | |||
191 | } | | |||
192 | | ||||
193 | static const long MAX_PROP_SIZE = 10000; | | |||
194 | auto propertyCookie = xcb_get_property(c, false, id, s_atoms[name], XCB_ATOM_STRING, 0, MAX_PROP_SIZE); | | |||
195 | QScopedPointer<xcb_get_property_reply_t, QScopedPointerPodDeleter> propertyReply(xcb_get_property_reply(c, propertyCookie, nullptr)); | | |||
196 | if (propertyReply.isNull()) { | | |||
197 | return value; | | |||
198 | } | | |||
199 | | ||||
200 | if (propertyReply->type == XCB_ATOM_STRING && propertyReply->format == 8 && propertyReply->value_len > 0) { | | |||
201 | const char *data = (const char *) xcb_get_property_value(propertyReply.data()); | | |||
202 | int len = propertyReply->value_len; | | |||
203 | if (data) { | | |||
204 | value = QByteArray(data, data[len - 1] ? len : len - 1); | | |||
205 | } | | |||
206 | } | | |||
207 | | ||||
208 | return value; | | |||
209 | }; | | |||
210 | | ||||
211 | auto updateMenuFromWindowIfHasMenu = [this, &getWindowPropertyString](WId id) { | | |||
212 | const QString serviceName = QString::fromUtf8(getWindowPropertyString(id, s_x11AppMenuServiceNamePropertyName)); | | |||
213 | const QString menuObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_x11AppMenuObjectPathPropertyName)); | | |||
214 | | ||||
215 | if (!serviceName.isEmpty() && !menuObjectPath.isEmpty()) { | | |||
216 | updateApplicationMenu(serviceName, menuObjectPath); | | |||
217 | return true; | | |||
218 | } | | |||
219 | return false; | | |||
220 | }; | | |||
221 | | ||||
222 | KWindowInfo info(id, NET::WMState | NET::WMWindowType, NET::WM2TransientFor); | | |||
223 | if (info.hasState(NET::SkipTaskbar) || | | |||
224 | info.windowType(NET::UtilityMask) == NET::Utility || | | |||
225 | info.windowType(NET::DesktopMask) == NET::Desktop) { | | |||
226 | return; | | |||
227 | } | | |||
228 | | ||||
229 | m_currentWindowId = id; | | |||
230 | | ||||
231 | WId transientId = info.transientFor(); | | |||
232 | // lok at transient windows first | | |||
233 | while (transientId) { | | |||
234 | if (updateMenuFromWindowIfHasMenu(transientId)) { | | |||
235 | setVisible(true); | | |||
236 | return; | | |||
237 | } | | |||
238 | transientId = KWindowInfo(transientId, nullptr, NET::WM2TransientFor).transientFor(); | | |||
239 | } | | |||
240 | | ||||
241 | if (updateMenuFromWindowIfHasMenu(id)) { | | |||
242 | setVisible(true); | 157 | setVisible(true); | ||
broulik: No need for parentheses | |||||
243 | return; | | |||
244 | } | | |||
245 | | ||||
246 | // monitor whether an app menu becomes available later | | |||
247 | // this can happen when an app starts, shows its window, and only later announces global menu (e.g. Firefox) | | |||
248 | qApp->installNativeEventFilter(this); | | |||
249 | m_delayedMenuWindowId = id; | | |||
250 | | ||||
251 | //no menu found, set it to unavailable | | |||
252 | setMenuAvailable(false); | | |||
253 | emit modelNeedsUpdate(); | 158 | emit modelNeedsUpdate(); | ||
254 | } | 159 | } else { | ||
255 | #endif | 160 | setMenuAvailable(false); | ||
256 | 161 | setVisible(false); | |||
257 | } | | |||
258 | | ||||
259 | void AppMenuModel::onWindowChanged(WId id) | | |||
260 | { | | |||
261 | if (m_currentWindowId == id) { | | |||
262 | KWindowInfo info(id, NET::WMState | NET::WMGeometry); | | |||
263 | | ||||
264 | //! HACK: if the user has enabled screen scaling under X11 environment | | |||
265 | //! then the window and screen geometries can not be trusted for comparison | | |||
266 | //! before windows coordinates be adjusted properly. | | |||
267 | //! BUG: 404500 | | |||
268 | QPoint windowCenter = info.geometry().center(); | | |||
269 | if (KWindowSystem::isPlatformX11()) { | | |||
270 | windowCenter /= qApp->devicePixelRatio(); | | |||
271 | } | | |||
272 | | ||||
273 | const bool contained = m_screenGeometry.isNull() || m_screenGeometry.contains(windowCenter); | | |||
274 | | ||||
275 | setVisible(contained && !info.isMinimized()); | | |||
276 | } | 162 | } | ||
277 | } | 163 | } | ||
278 | 164 | | |||
279 | QHash<int, QByteArray> AppMenuModel::roleNames() const | 165 | QHash<int, QByteArray> AppMenuModel::roleNames() const | ||
280 | { | 166 | { | ||
281 | QHash<int, QByteArray> roleNames; | 167 | QHash<int, QByteArray> roleNames; | ||
282 | roleNames[MenuRole] = QByteArrayLiteral("activeMenu"); | 168 | roleNames[MenuRole] = QByteArrayLiteral("activeMenu"); | ||
283 | roleNames[ActionRole] = QByteArrayLiteral("activeActions"); | 169 | roleNames[ActionRole] = QByteArrayLiteral("activeActions"); | ||
▲ Show 20 Lines • Show All 80 Lines • ▼ Show 20 Line(s) | 245 | connect(m_importer.data(), &DBusMenuImporter::actionActivationRequested, this, [this](QAction *action) { | |||
364 | 250 | | |||
365 | const auto actions = m_menu->actions(); | 251 | const auto actions = m_menu->actions(); | ||
366 | auto it = std::find(actions.begin(), actions.end(), action); | 252 | auto it = std::find(actions.begin(), actions.end(), action); | ||
367 | if (it != actions.end()) { | 253 | if (it != actions.end()) { | ||
368 | requestActivateIndex(it - actions.begin()); | 254 | requestActivateIndex(it - actions.begin()); | ||
369 | } | 255 | } | ||
370 | }); | 256 | }); | ||
371 | } | 257 | } | ||
372 | | ||||
373 | bool AppMenuModel::nativeEventFilter(const QByteArray &eventType, void *message, long *result) | | |||
374 | { | | |||
375 | Q_UNUSED(result); | | |||
376 | | ||||
377 | if (!KWindowSystem::isPlatformX11() || eventType != "xcb_generic_event_t") { | | |||
378 | return false; | | |||
379 | } | | |||
380 | | ||||
381 | #if HAVE_X11 | | |||
382 | auto e = static_cast<xcb_generic_event_t *>(message); | | |||
383 | const uint8_t type = e->response_type & ~0x80; | | |||
384 | if (type == XCB_PROPERTY_NOTIFY) { | | |||
385 | auto *event = reinterpret_cast<xcb_property_notify_event_t *>(e); | | |||
386 | if (event->window == m_delayedMenuWindowId) { | | |||
387 | | ||||
388 | auto serviceNameAtom = s_atoms.value(s_x11AppMenuServiceNamePropertyName); | | |||
389 | auto objectPathAtom = s_atoms.value(s_x11AppMenuObjectPathPropertyName); | | |||
390 | | ||||
391 | if (serviceNameAtom != XCB_ATOM_NONE && objectPathAtom != XCB_ATOM_NONE) { // shouldn't happen | | |||
392 | if (event->atom == serviceNameAtom || event->atom == objectPathAtom) { | | |||
393 | // see if we now have a menu | | |||
394 | onActiveWindowChanged(KWindowSystem::activeWindow()); | | |||
395 | } | | |||
396 | } | | |||
397 | } | | |||
398 | } | | |||
399 | #else | | |||
400 | Q_UNUSED(message); | | |||
401 | #endif | | |||
402 | | ||||
403 | return false; | | |||
404 | } | | |||
405 | |
Is this needed? I would think if screen geometry changes, task manager updates and filters and signals the active task having changed?