Changeset View
Changeset View
Standalone View
Standalone View
plugin/appmenumodel.cpp
Context not available. | |||||
31 | #endif | 31 | #endif | ||
---|---|---|---|---|---|
32 | 32 | | |||
33 | #include <QAction> | 33 | #include <QAction> | ||
34 | #include <QGuiApplication> | | |||
35 | #include <QMenu> | 34 | #include <QMenu> | ||
36 | #include <QDebug> | | |||
37 | #include <QDBusConnection> | 35 | #include <QDBusConnection> | ||
38 | #include <QDBusConnectionInterface> | 36 | #include <QDBusConnectionInterface> | ||
39 | #include <QDBusServiceWatcher> | 37 | #include <QDBusServiceWatcher> | ||
38 | #include <QGuiApplication> | ||||
40 | 39 | | |||
41 | #include <dbusmenuimporter.h> | 40 | #include <dbusmenuimporter.h> | ||
42 | 41 | | |||
Context not available. | |||||
70 | m_serviceWatcher(new QDBusServiceWatcher(this)) | 69 | m_serviceWatcher(new QDBusServiceWatcher(this)) | ||
71 | { | 70 | { | ||
72 | connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this, &AppMenuModel::onActiveWindowChanged); | 71 | connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this, &AppMenuModel::onActiveWindowChanged); | ||
73 | connect(this, &AppMenuModel::modelNeedsUpdate, this, &AppMenuModel::update, Qt::UniqueConnection); | 72 | connect(KWindowSystem::self() | ||
73 | , static_cast<void (KWindowSystem::*)(WId)>(&KWindowSystem::windowChanged) | ||||
74 | , this | ||||
75 | , &AppMenuModel::onWindowChanged); | ||||
76 | | ||||
77 | connect(this, &AppMenuModel::modelNeedsUpdate, this, [this] { | ||||
78 | if (!m_updatePending) { | ||||
79 | m_updatePending = true; | ||||
80 | QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); | ||||
81 | } | ||||
82 | }); | ||||
83 | | ||||
84 | connect(this, &AppMenuModel::screenGeometryChanged, this, [this] { | ||||
85 | onWindowChanged(m_currentWindowId); | ||||
86 | }); | ||||
87 | | ||||
74 | onActiveWindowChanged(KWindowSystem::activeWindow()); | 88 | onActiveWindowChanged(KWindowSystem::activeWindow()); | ||
75 | 89 | | |||
76 | m_serviceWatcher->setConnection(QDBusConnection::sessionBus()); | 90 | m_serviceWatcher->setConnection(QDBusConnection::sessionBus()); | ||
Context not available. | |||||
96 | { | 110 | { | ||
97 | if (m_menuAvailable != set) { | 111 | if (m_menuAvailable != set) { | ||
98 | m_menuAvailable = set; | 112 | m_menuAvailable = set; | ||
113 | setVisible(true); | ||||
99 | emit menuAvailableChanged(); | 114 | emit menuAvailableChanged(); | ||
100 | } | 115 | } | ||
101 | } | 116 | } | ||
102 | 117 | | |||
103 | int AppMenuModel::rowCount(const QModelIndex &parent) const | 118 | bool AppMenuModel::visible() const | ||
104 | { | 119 | { | ||
105 | Q_UNUSED(parent); | 120 | return m_visible; | ||
106 | return m_activeMenu.count(); | | |||
107 | } | 121 | } | ||
108 | 122 | | |||
109 | void AppMenuModel::update() | 123 | void AppMenuModel::setVisible(bool visible) | ||
110 | { | 124 | { | ||
111 | beginResetModel(); | 125 | if (m_visible != visible) { | ||
112 | if (!m_activeMenu.isEmpty() && !m_activeActions.isEmpty()) { | 126 | m_visible = visible; | ||
113 | m_activeMenu.clear(); | 127 | emit visibleChanged(); | ||
114 | m_activeActions.clear(); | | |||
115 | } | 128 | } | ||
129 | } | ||||
116 | 130 | | |||
117 | if (m_menu && m_menuAvailable) { | 131 | QRect AppMenuModel::screenGeometry() const | ||
118 | const auto &actions = m_menu->actions(); | 132 | { | ||
119 | for (QAction *action : actions) { | 133 | return m_screenGeometry; | ||
120 | m_activeActions.append(action); | 134 | } | ||
121 | m_activeMenu.append(action->text()); | 135 | | ||
122 | } | 136 | void AppMenuModel::setScreenGeometry(QRect geometry) | ||
137 | { | ||||
138 | if (m_screenGeometry == geometry) { | ||||
139 | return; | ||||
140 | } | ||||
141 | | ||||
142 | m_screenGeometry = geometry; | ||||
143 | emit screenGeometryChanged(); | ||||
144 | } | ||||
145 | | ||||
146 | int AppMenuModel::rowCount(const QModelIndex &parent) const | ||||
147 | { | ||||
148 | Q_UNUSED(parent); | ||||
149 | if (!m_menuAvailable || !m_menu) { | ||||
150 | return 0; | ||||
123 | } | 151 | } | ||
124 | 152 | | |||
153 | return m_menu->actions().count(); | ||||
154 | } | ||||
155 | | ||||
156 | void AppMenuModel::update() | ||||
157 | { | ||||
158 | beginResetModel(); | ||||
125 | endResetModel(); | 159 | endResetModel(); | ||
160 | m_updatePending = false; | ||||
126 | } | 161 | } | ||
127 | 162 | | |||
128 | 163 | | |||
Context not available. | |||||
144 | QByteArray value; | 179 | QByteArray value; | ||
145 | if (!s_atoms.contains(name)) { | 180 | if (!s_atoms.contains(name)) { | ||
146 | const xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom(c, false, name.length(), name.constData()); | 181 | const xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom(c, false, name.length(), name.constData()); | ||
147 | QScopedPointer<xcb_intern_atom_reply_t, QScopedPointerPodDeleter> atomReply(xcb_intern_atom_reply(c, atomCookie, Q_NULLPTR)); | 182 | QScopedPointer<xcb_intern_atom_reply_t, QScopedPointerPodDeleter> atomReply(xcb_intern_atom_reply(c, atomCookie, nullptr)); | ||
148 | if (atomReply.isNull()) { | 183 | if (atomReply.isNull()) { | ||
149 | return value; | 184 | return value; | ||
150 | } | 185 | } | ||
Context not available. | |||||
157 | 192 | | |||
158 | static const long MAX_PROP_SIZE = 10000; | 193 | static const long MAX_PROP_SIZE = 10000; | ||
159 | auto propertyCookie = xcb_get_property(c, false, id, s_atoms[name], XCB_ATOM_STRING, 0, MAX_PROP_SIZE); | 194 | auto propertyCookie = xcb_get_property(c, false, id, s_atoms[name], XCB_ATOM_STRING, 0, MAX_PROP_SIZE); | ||
160 | QScopedPointer<xcb_get_property_reply_t, QScopedPointerPodDeleter> propertyReply(xcb_get_property_reply(c, propertyCookie, NULL)); | 195 | QScopedPointer<xcb_get_property_reply_t, QScopedPointerPodDeleter> propertyReply(xcb_get_property_reply(c, propertyCookie, nullptr)); | ||
161 | if (propertyReply.isNull()) { | 196 | if (propertyReply.isNull()) { | ||
162 | return value; | 197 | return value; | ||
163 | } | 198 | } | ||
Context not available. | |||||
191 | return; | 226 | return; | ||
192 | } | 227 | } | ||
193 | 228 | | |||
229 | m_currentWindowId = id; | ||||
230 | | ||||
194 | WId transientId = info.transientFor(); | 231 | WId transientId = info.transientFor(); | ||
195 | // lok at transient windows first | 232 | // lok at transient windows first | ||
196 | while (transientId) { | 233 | while (transientId) { | ||
197 | if (updateMenuFromWindowIfHasMenu(transientId)) { | 234 | if (updateMenuFromWindowIfHasMenu(transientId)) { | ||
235 | setVisible(true); | ||||
198 | return; | 236 | return; | ||
199 | } | 237 | } | ||
200 | transientId = KWindowInfo(transientId, 0, NET::WM2TransientFor).transientFor(); | 238 | transientId = KWindowInfo(transientId, nullptr, NET::WM2TransientFor).transientFor(); | ||
201 | } | 239 | } | ||
202 | 240 | | |||
203 | if (updateMenuFromWindowIfHasMenu(id)) { | 241 | if (updateMenuFromWindowIfHasMenu(id)) { | ||
242 | setVisible(true); | ||||
204 | return; | 243 | return; | ||
205 | } | 244 | } | ||
206 | 245 | | |||
207 | // monitor whether an app menu becomes available later | 246 | // monitor whether an app menu becomes available later | ||
208 | // this can happen when an app starts, shows its window, and only later announces global menu (e.g. Firefox) | 247 | // this can happen when an app starts, shows its window, and only later announces global menu (e.g. Firefox) | ||
209 | qApp->installNativeEventFilter(this); | 248 | qApp->installNativeEventFilter(this); | ||
210 | m_currentWindowId = id; | 249 | m_delayedMenuWindowId = id; | ||
211 | 250 | | |||
212 | //no menu found, set it to unavailable | 251 | //no menu found, set it to unavailable | ||
213 | setMenuAvailable(false); | 252 | setMenuAvailable(false); | ||
Context not available. | |||||
217 | 256 | | |||
218 | } | 257 | } | ||
219 | 258 | | |||
259 | void AppMenuModel::onWindowChanged(WId id) | ||||
260 | { | ||||
261 | if (m_currentWindowId == id) { | ||||
262 | KWindowInfo info(id, NET::WMState | NET::WMGeometry); | ||||
263 | const bool contained = m_screenGeometry.isNull() || m_screenGeometry.contains(info.geometry().center()); | ||||
264 | | ||||
265 | setVisible(contained && !info.isMinimized()); | ||||
266 | } | ||||
267 | } | ||||
220 | 268 | | |||
221 | QHash<int, QByteArray> AppMenuModel::roleNames() const | 269 | QHash<int, QByteArray> AppMenuModel::roleNames() const | ||
222 | { | 270 | { | ||
223 | QHash<int, QByteArray> roleNames; | 271 | QHash<int, QByteArray> roleNames; | ||
224 | roleNames[MenuRole] = "activeMenu"; | 272 | roleNames[MenuRole] = QByteArrayLiteral("activeMenu"); | ||
225 | roleNames[ActionRole] = "activeActions"; | 273 | roleNames[ActionRole] = QByteArrayLiteral("activeActions"); | ||
226 | return roleNames; | 274 | return roleNames; | ||
227 | } | 275 | } | ||
228 | 276 | | |||
229 | QVariant AppMenuModel::data(const QModelIndex &index, int role) const | 277 | QVariant AppMenuModel::data(const QModelIndex &index, int role) const | ||
230 | { | 278 | { | ||
231 | int row = index.row(); | 279 | const int row = index.row(); | ||
232 | if (row < 0 ) { | 280 | if (row < 0 || !m_menuAvailable || !m_menu) { | ||
281 | return QVariant(); | ||||
282 | } | ||||
283 | | ||||
284 | const auto actions = m_menu->actions(); | ||||
285 | if (row >= actions.count()) { | ||||
233 | return QVariant(); | 286 | return QVariant(); | ||
234 | } | 287 | } | ||
235 | 288 | | |||
236 | if (role == MenuRole) { | 289 | if (role == MenuRole) { // TODO this should be Qt::DisplayRole | ||
237 | return m_activeMenu.at(row); | 290 | return actions.at(row)->text(); | ||
238 | } else if(role == ActionRole) { | 291 | } else if (role == ActionRole) { | ||
239 | const QVariant data = qVariantFromValue((void *) m_activeActions.at(row)); | 292 | return qVariantFromValue((void *) actions.at(row)); | ||
240 | return data; | | |||
241 | } | 293 | } | ||
242 | 294 | | |||
243 | return QVariant(); | 295 | return QVariant(); | ||
Context not available. | |||||
272 | 324 | | |||
273 | //cache first layer of sub menus, which we'll be popping up | 325 | //cache first layer of sub menus, which we'll be popping up | ||
274 | for(QAction *a: m_menu->actions()) { | 326 | for(QAction *a: m_menu->actions()) { | ||
327 | // signal dataChanged when the action changes | ||||
328 | connect(a, &QAction::changed, this, [this, a] { | ||||
329 | if (m_menuAvailable && m_menu) { | ||||
330 | const int actionIdx = m_menu->actions().indexOf(a); | ||||
331 | if (actionIdx > -1) { | ||||
332 | const QModelIndex modelIdx = index(actionIdx, 0); | ||||
333 | emit dataChanged(modelIdx, modelIdx); | ||||
334 | } | ||||
335 | } | ||||
336 | }); | ||||
337 | | ||||
338 | connect(a, &QAction::destroyed, this, &AppMenuModel::modelNeedsUpdate); | ||||
339 | | ||||
275 | if (a->menu()) { | 340 | if (a->menu()) { | ||
276 | m_importer->updateMenu(a->menu()); | 341 | m_importer->updateMenu(a->menu()); | ||
277 | } | 342 | } | ||
Context not available. | |||||
283 | 348 | | |||
284 | connect(m_importer.data(), &DBusMenuImporter::actionActivationRequested, this, [this](QAction *action) { | 349 | connect(m_importer.data(), &DBusMenuImporter::actionActivationRequested, this, [this](QAction *action) { | ||
285 | // TODO submenus | 350 | // TODO submenus | ||
286 | auto it = std::find(m_activeActions.constBegin(), m_activeActions.constEnd(), action); | 351 | if (!m_menuAvailable || !m_menu) { | ||
287 | if (it != m_activeActions.constEnd()) { | 352 | return; | ||
288 | requestActivateIndex(it - m_activeActions.constBegin()); | 353 | } | ||
354 | | ||||
355 | const auto actions = m_menu->actions(); | ||||
356 | auto it = std::find(actions.begin(), actions.end(), action); | ||||
357 | if (it != actions.end()) { | ||||
358 | requestActivateIndex(it - actions.begin()); | ||||
289 | } | 359 | } | ||
290 | }); | 360 | }); | ||
291 | } | 361 | } | ||
Context not available. | |||||
303 | const uint8_t type = e->response_type & ~0x80; | 373 | const uint8_t type = e->response_type & ~0x80; | ||
304 | if (type == XCB_PROPERTY_NOTIFY) { | 374 | if (type == XCB_PROPERTY_NOTIFY) { | ||
305 | auto *event = reinterpret_cast<xcb_property_notify_event_t *>(e); | 375 | auto *event = reinterpret_cast<xcb_property_notify_event_t *>(e); | ||
306 | if (event->window == m_currentWindowId) { | 376 | if (event->window == m_delayedMenuWindowId) { | ||
307 | 377 | | |||
308 | auto serviceNameAtom = s_atoms.value(s_x11AppMenuServiceNamePropertyName); | 378 | auto serviceNameAtom = s_atoms.value(s_x11AppMenuServiceNamePropertyName); | ||
309 | auto objectPathAtom = s_atoms.value(s_x11AppMenuObjectPathPropertyName); | 379 | auto objectPathAtom = s_atoms.value(s_x11AppMenuObjectPathPropertyName); | ||
Context not available. |