Merge lp:~3v1n0/unity/xi2-input-monitor into lp:unity

Proposed by Marco Trevisan (Treviño)
Status: Merged
Approved by: Andrea Azzarone
Approved revision: no longer in the source branch.
Merged at revision: 4182
Proposed branch: lp:~3v1n0/unity/xi2-input-monitor
Merge into: lp:unity
Diff against target: 610 lines (+494/-2)
11 files modified
launcher/EdgeBarrierController.cpp (+1/-1)
launcher/EdgeBarrierControllerPrivate.h (+1/-1)
plugins/unityshell/src/unityshell.h (+2/-0)
unity-shared/CMakeLists.txt (+1/-0)
unity-shared/InputMonitor.cpp (+386/-0)
unity-shared/InputMonitor.h (+91/-0)
unity-shared/StandaloneWindowManager.cpp (+3/-0)
unity-shared/StandaloneWindowManager.h (+1/-0)
unity-shared/WindowManager.h (+1/-0)
unity-shared/XWindowManager.cpp (+6/-0)
unity-shared/XWindowManager.h (+1/-0)
To merge this branch: bzr merge lp:~3v1n0/unity/xi2-input-monitor
Reviewer Review Type Date Requested Status
Andrea Azzarone (community) Approve
Review via email: mp+303122@code.launchpad.net

Commit message

InputMonitor: add an unity class that monitors XInput2 events and converts them to XEvent

Clients of this class can register event handlers, and when an interested event will hit
our event filter function (that is set only if we have handlers), then we notify them
with a standard XEvent struct, converted from the XIDeviceEvent cookie.

The nice thing of this monitor is that it always reports events, despite the X grabs.

To post a comment you must log in.
Revision history for this message
Andrea Azzarone (azzar1) wrote :

Nice! I think the class Monitor has no need to have a virtual dtor. So please make it non-virtual.

review: Needs Fixing
Revision history for this message
Marco Trevisan (Treviño) (3v1n0) wrote :

Done thanks...

I think I added that thinking of future abstractions, but this won't be the case.

Revision history for this message
Andrea Azzarone (azzar1) wrote :

Thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'launcher/EdgeBarrierController.cpp'
2--- launcher/EdgeBarrierController.cpp 2015-12-23 09:29:24 +0000
3+++ launcher/EdgeBarrierController.cpp 2016-08-22 10:28:22 +0000
4@@ -295,7 +295,7 @@
5 nux::GetGraphicsDisplay()->AddEventFilter(event_filter);
6 }
7
8-bool EdgeBarrierController::Impl::HandleEvent(XEvent xevent)
9+bool EdgeBarrierController::Impl::HandleEvent(XEvent& xevent)
10 {
11 Display *dpy = nux::GetGraphicsDisplay()->GetX11Display();
12 XGenericEventCookie *cookie = &xevent.xcookie;
13
14=== modified file 'launcher/EdgeBarrierControllerPrivate.h'
15--- launcher/EdgeBarrierControllerPrivate.h 2015-12-23 09:29:24 +0000
16+++ launcher/EdgeBarrierControllerPrivate.h 2016-08-22 10:28:22 +0000
17@@ -58,7 +58,7 @@
18 PointerBarrierWrapper::Ptr FindBarrierEventOwner(XIBarrierEvent* barrier_event);
19
20 static bool HandleEventCB(XEvent event, void* data);
21- bool HandleEvent(XEvent event);
22+ bool HandleEvent(XEvent& event);
23
24 std::vector<PointerBarrierWrapper::Ptr> vertical_barriers_;
25 std::vector<PointerBarrierWrapper::Ptr> horizontal_barriers_;
26
27=== modified file 'plugins/unityshell/src/unityshell.h'
28--- plugins/unityshell/src/unityshell.h 2016-08-12 11:21:48 +0000
29+++ plugins/unityshell/src/unityshell.h 2016-08-22 10:28:22 +0000
30@@ -56,6 +56,7 @@
31 #include "DashStyle.h"
32 #include "EdgeBarrierController.h"
33 #include "FavoriteStoreGSettings.h"
34+#include "InputMonitor.h"
35 #include "ShortcutController.h"
36 #include "LauncherController.h"
37 #include "LockScreenController.h"
38@@ -317,6 +318,7 @@
39 internal::FavoriteStoreGSettings favorite_store_;
40 ThumbnailGenerator thumbnail_generator_;
41 lockscreen::Settings lockscreen_settings_;
42+ input::Monitor input_monitor_;
43
44 /* The window thread should be the last thing removed, as c++ does it in reverse order */
45 std::unique_ptr<nux::WindowThread> wt;
46
47=== modified file 'unity-shared/CMakeLists.txt'
48--- unity-shared/CMakeLists.txt 2016-08-12 11:21:48 +0000
49+++ unity-shared/CMakeLists.txt 2016-08-22 10:28:22 +0000
50@@ -82,6 +82,7 @@
51 set (UNITY_SHARED_SOURCES
52 XKeyboardUtil.cpp
53 XWindowManager.cpp
54+ InputMonitor.cpp
55 ${UNITY_SHARED_SOURCES}
56 )
57 else()
58
59=== added file 'unity-shared/InputMonitor.cpp'
60--- unity-shared/InputMonitor.cpp 1970-01-01 00:00:00 +0000
61+++ unity-shared/InputMonitor.cpp 2016-08-22 10:28:22 +0000
62@@ -0,0 +1,386 @@
63+// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*-
64+/*
65+ * Copyright (C) 2014 Canonical Ltd
66+ *
67+ * This program is free software: you can redistribute it and/or modify
68+ * it under the terms of the GNU General Public License version 3 as
69+ * published by the Free Software Foundation.
70+ *
71+ * This program is distributed in the hope that it will be useful,
72+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
73+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
74+ * GNU General Public License for more details.
75+ *
76+ * You should have received a copy of the GNU General Public License
77+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
78+ *
79+ * Authored by: Marco Trevisan <marco.trevisan@canonical.com>
80+ */
81+
82+#include "InputMonitor.h"
83+
84+#include <Nux/Nux.h>
85+#include <NuxCore/Logger.h>
86+#include <X11/extensions/XInput2.h>
87+#include <UnityCore/GLibSource.h>
88+#include <unordered_set>
89+#include <gdk/gdkx.h>
90+#include <glib.h>
91+
92+namespace unity
93+{
94+namespace input
95+{
96+namespace
97+{
98+DECLARE_LOGGER(logger, "unity.input.monitor");
99+
100+Monitor* instance_ = nullptr;
101+
102+const unsigned XINPUT_MAJOR_VERSION = 2;
103+const unsigned XINPUT_MINOR_VERSION = 3;
104+
105+bool operator&(Events l, Events r)
106+{
107+ typedef std::underlying_type<Events>::type ut;
108+ return static_cast<ut>(static_cast<ut>(l) & static_cast<ut>(r));
109+}
110+
111+template <typename EVENT>
112+void initialize_event_common(EVENT* ev, XIDeviceEvent* xiev)
113+{
114+ ev->serial = xiev->serial;
115+ ev->send_event = xiev->send_event;
116+ ev->display = xiev->display;
117+ ev->window = xiev->event;
118+ ev->root = xiev->root;
119+ ev->subwindow = xiev->child;
120+ ev->time = xiev->time;
121+ ev->x = std::round(xiev->event_x);
122+ ev->y = std::round(xiev->event_y);
123+ ev->x_root = std::round(xiev->root_x);
124+ ev->y_root = std::round(xiev->root_y);
125+ ev->state = xiev->mods.effective;
126+ ev->same_screen = True;
127+}
128+
129+template <typename EVENT_TYPE, typename NATIVE_TYPE>
130+void initialize_event(XEvent* ev, NATIVE_TYPE* xiev);
131+
132+template <>
133+void initialize_event<XButtonEvent>(XEvent* ev, XIDeviceEvent* xiev)
134+{
135+ XButtonEvent* bev = &ev->xbutton;
136+ ev->type = (xiev->evtype == XI_ButtonPress) ? ButtonPress : ButtonRelease;
137+ initialize_event_common(bev, xiev);
138+ bev->button = xiev->detail;
139+}
140+
141+template <>
142+void initialize_event<XKeyEvent>(XEvent* ev, XIDeviceEvent* xiev)
143+{
144+ XKeyEvent* kev = &ev->xkey;
145+ ev->type = (xiev->evtype == XI_KeyPress) ? KeyPress : KeyRelease;
146+ initialize_event_common(kev, xiev);
147+ kev->keycode = xiev->detail;
148+}
149+
150+template <>
151+void initialize_event<XMotionEvent>(XEvent* ev, XIDeviceEvent* xiev)
152+{
153+ XMotionEvent* mev = &ev->xmotion;
154+ ev->type = MotionNotify;
155+ initialize_event_common(mev, xiev);
156+ mev->is_hint = NotifyNormal;
157+
158+ for (int i = 0; i < xiev->buttons.mask_len * 8; ++i)
159+ {
160+ if (XIMaskIsSet(xiev->buttons.mask, i))
161+ {
162+ mev->is_hint = NotifyHint;
163+ break;
164+ }
165+ }
166+}
167+
168+template <>
169+void initialize_event<XGenericEventCookie>(XEvent* ev, XIBarrierEvent* xiev)
170+{
171+ XGenericEventCookie* cev = &ev->xcookie;
172+ cev->type = GenericEvent;
173+ cev->serial = xiev->serial;
174+ cev->send_event = xiev->send_event;
175+ cev->display = xiev->display;
176+ cev->evtype = xiev->evtype;
177+ cev->data = xiev;
178+}
179+}
180+
181+struct Monitor::Impl
182+{
183+ Impl()
184+ : xi_opcode_(0)
185+ , event_filter_set_(false)
186+ , invoking_callbacks_(false)
187+ {
188+ Display *dpy = gdk_x11_get_default_xdisplay();
189+ int event_base, error_base;
190+
191+ if (XQueryExtension(dpy, "XInputExtension", &xi_opcode_, &event_base, &error_base))
192+ {
193+ int maj = XINPUT_MAJOR_VERSION;
194+ int min = XINPUT_MINOR_VERSION;
195+
196+ if (XIQueryVersion(dpy, &maj, &min) == BadRequest)
197+ {
198+ LOG_ERROR(logger) << "Need XInput version "<< maj << "." << min << ", "
199+ << "impossible, to setup an InputMonitor";
200+ }
201+ }
202+ else
203+ {
204+ LOG_ERROR(logger) << "Missing XInput, impossible to setup an InputMonitor";
205+ }
206+ }
207+
208+ ~Impl()
209+ {
210+ if (event_filter_set_)
211+ {
212+ pointer_callbacks_.clear();
213+ key_callbacks_.clear();
214+ barrier_callbacks_.clear();
215+ UpdateEventMonitor();
216+ }
217+ }
218+
219+ bool RegisterClient(Events type, EventCallback const& cb)
220+ {
221+ bool added = false;
222+
223+ if (type & Events::POINTER)
224+ added = pointer_callbacks_.insert(cb).second || added;
225+
226+ if (type & Events::KEYS)
227+ added = key_callbacks_.insert(cb).second || added;
228+
229+ if (type & Events::BARRIER)
230+ added = barrier_callbacks_.insert(cb).second || added;
231+
232+ if (added)
233+ UpdateEventMonitor();
234+
235+ return added;
236+ }
237+
238+ bool UnregisterClient(EventCallback const& cb)
239+ {
240+ if (invoking_callbacks_)
241+ {
242+ // Delay the event removal if we're currently invoking a callback
243+ // not to break the callbacks loop
244+ removal_queue_.insert(cb);
245+ return false;
246+ }
247+
248+ bool removed = false;
249+ removed = pointer_callbacks_.erase(cb) > 0 || removed;
250+ removed = key_callbacks_.erase(cb) > 0 || removed;
251+ removed = barrier_callbacks_.erase(cb) > 0 || removed;
252+
253+ if (removed)
254+ UpdateEventMonitor();
255+
256+ return removed;
257+ }
258+
259+ void UpdateEventMonitor()
260+ {
261+ auto* dpy = nux::GetGraphicsDisplay()->GetX11Display();
262+ Window root = DefaultRootWindow(dpy);
263+
264+ unsigned char master_dev_bits[XIMaskLen(XI_LASTEVENT)] = { 0 };
265+ XIEventMask master_dev = { XIAllMasterDevices, sizeof(master_dev_bits), master_dev_bits };
266+
267+ if (!barrier_callbacks_.empty())
268+ {
269+ XISetMask(master_dev.mask, XI_BarrierHit);
270+ XISetMask(master_dev.mask, XI_BarrierLeave);
271+ }
272+
273+ unsigned char all_devs_bits[XIMaskLen(XI_LASTEVENT)] = { 0 };
274+ XIEventMask all_devs = { XIAllDevices, sizeof(all_devs_bits), all_devs_bits };
275+
276+ if (!pointer_callbacks_.empty())
277+ {
278+ XISetMask(all_devs.mask, XI_Motion);
279+ XISetMask(all_devs.mask, XI_ButtonPress);
280+ XISetMask(all_devs.mask, XI_ButtonRelease);
281+ }
282+
283+ if (!key_callbacks_.empty())
284+ {
285+ XISetMask(all_devs.mask, XI_KeyPress);
286+ XISetMask(all_devs.mask, XI_KeyRelease);
287+ }
288+
289+ XIEventMask selected[] = {master_dev, all_devs};
290+ XISelectEvents(dpy, root, selected, G_N_ELEMENTS(selected));
291+ XSync(dpy, False);
292+
293+ LOG_DEBUG(logger) << "Pointer clients: " << pointer_callbacks_.size() << ", "
294+ << "Key clients: " << key_callbacks_.size() << ", "
295+ << "Barrier clients: " << barrier_callbacks_.size();
296+
297+ if (!pointer_callbacks_.empty() || !key_callbacks_.empty() || !barrier_callbacks_.empty())
298+ {
299+ if (!event_filter_set_)
300+ {
301+ nux::GetGraphicsDisplay()->AddEventFilter({[] (XEvent event, void* data) {
302+ return static_cast<Impl*>(data)->HandleEvent(event);
303+ }, this});
304+
305+ event_filter_set_ = true;
306+ LOG_DEBUG(logger) << "Event filter enabled";
307+ }
308+ }
309+ else if (event_filter_set_)
310+ {
311+ nux::GetGraphicsDisplay()->RemoveEventFilter(this);
312+ event_filter_set_ = false;
313+ LOG_DEBUG(logger) << "Event filter disabled";
314+ }
315+ }
316+
317+ bool HandleEvent(XEvent& event)
318+ {
319+ bool handled = false;
320+
321+ if (event.type != GenericEvent || event.xcookie.extension != xi_opcode_)
322+ return handled;
323+
324+ switch (event.xcookie.evtype)
325+ {
326+ case XI_ButtonPress:
327+ case XI_ButtonRelease:
328+ handled = InvokeCallbacks<XButtonEvent>(pointer_callbacks_, event);
329+ break;
330+ case XI_Motion:
331+ handled = InvokeCallbacks<XMotionEvent>(pointer_callbacks_, event);
332+ break;
333+ case XI_KeyPress:
334+ case XI_KeyRelease:
335+ handled = InvokeCallbacks<XKeyEvent>(key_callbacks_, event);
336+ break;
337+ case XI_BarrierHit:
338+ case XI_BarrierLeave:
339+ handled = InvokeCallbacks<XGenericEventCookie, XIBarrierEvent>(barrier_callbacks_, event);
340+ break;
341+ }
342+
343+ return handled;
344+ }
345+
346+ template <typename EVENT_TYPE, typename NATIVE_TYPE = XIDeviceEvent>
347+ bool InvokeCallbacks(std::unordered_set<EventCallback>& callbacks, XEvent& xiev)
348+ {
349+ XGenericEventCookie *cookie = &xiev.xcookie;
350+
351+ if (!XGetEventData(xiev.xany.display, cookie))
352+ return false;
353+
354+ XEvent event;
355+ initialize_event<EVENT_TYPE>(&event, reinterpret_cast<NATIVE_TYPE*>(cookie->data));
356+ invoking_callbacks_ = true;
357+
358+ for (auto it = callbacks.begin(); it != callbacks.end();)
359+ {
360+ if (it->empty())
361+ {
362+ it = callbacks.erase(it);
363+ continue;
364+ }
365+
366+ (*it)(event);
367+ ++it;
368+ }
369+
370+ XFreeEventData(xiev.xany.display, cookie);
371+ invoking_callbacks_ = false;
372+
373+ // A callback might unregister itself on the event callback, causing the
374+ // above callbacks loop to crash, so in this case we save the event in the
375+ // removal queue and eventually we unregistered these callbacks.
376+ bool update_event_monitor = false;
377+ for (auto it = removal_queue_.begin(); it != removal_queue_.end(); it = removal_queue_.erase(it))
378+ {
379+ auto const& cb = *it;
380+ pointer_callbacks_.erase(cb);
381+ key_callbacks_.erase(cb);
382+ barrier_callbacks_.erase(cb);
383+ update_event_monitor = true;
384+ }
385+
386+ if (callbacks.empty() || update_event_monitor)
387+ {
388+ idle_removal_.reset(new glib::Idle([this] {
389+ UpdateEventMonitor();
390+ return false;
391+ }));
392+
393+ return false;
394+ }
395+
396+ return true;
397+ }
398+
399+ int xi_opcode_;
400+ bool event_filter_set_;
401+ bool invoking_callbacks_;
402+ glib::Source::UniquePtr idle_removal_;
403+ std::unordered_set<EventCallback> pointer_callbacks_;
404+ std::unordered_set<EventCallback> key_callbacks_;
405+ std::unordered_set<EventCallback> barrier_callbacks_;
406+ std::unordered_set<EventCallback> removal_queue_;
407+};
408+
409+Monitor::Monitor()
410+{
411+ if (instance_)
412+ {
413+ LOG_WARN(logger) << "More than one input::Monitor created.";
414+ return;
415+ }
416+
417+ instance_ = this;
418+ impl_.reset(new Impl());
419+}
420+
421+Monitor::~Monitor()
422+{
423+ if (this == instance_)
424+ instance_ = nullptr;
425+}
426+
427+Monitor& Monitor::Get()
428+{
429+ if (!instance_)
430+ {
431+ LOG_ERROR(logger) << "No input::Monitor created yet.";
432+ }
433+
434+ return *instance_;
435+}
436+
437+bool Monitor::RegisterClient(Events events, EventCallback const& cb)
438+{
439+ return impl_->RegisterClient(events, cb);
440+}
441+
442+bool Monitor::UnregisterClient(EventCallback const& cb)
443+{
444+ return impl_->UnregisterClient(cb);
445+}
446+
447+} // input namespace
448+} // unity namespace
449
450=== added file 'unity-shared/InputMonitor.h'
451--- unity-shared/InputMonitor.h 1970-01-01 00:00:00 +0000
452+++ unity-shared/InputMonitor.h 2016-08-22 10:28:22 +0000
453@@ -0,0 +1,91 @@
454+// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*-
455+/*
456+ * Copyright (C) 2014 Canonical Ltd
457+ *
458+ * This program is free software: you can redistribute it and/or modify
459+ * it under the terms of the GNU General Public License version 3 as
460+ * published by the Free Software Foundation.
461+ *
462+ * This program is distributed in the hope that it will be useful,
463+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
464+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
465+ * GNU General Public License for more details.
466+ *
467+ * You should have received a copy of the GNU General Public License
468+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
469+ *
470+ * Authored by: Marco Trevisan <marco.trevisan@canonical.com>
471+ */
472+
473+#ifndef __UNITY_INPUT_MONITOR__
474+#define __UNITY_INPUT_MONITOR__
475+
476+#include <X11/Xlib.h>
477+#include <sigc++/slot.h>
478+#include <memory>
479+
480+namespace unity
481+{
482+namespace input
483+{
484+enum class Events : unsigned
485+{
486+ POINTER = (1 << 0),
487+ KEYS = (1 << 1),
488+ BARRIER = (1 << 2),
489+ INPUT = POINTER | KEYS,
490+ ALL = POINTER | KEYS | BARRIER
491+};
492+
493+class Monitor : public sigc::trackable
494+{
495+public:
496+ typedef sigc::slot<void, XEvent const&> EventCallback;
497+
498+ static Monitor& Get();
499+
500+ Monitor();
501+ ~Monitor();
502+
503+ bool RegisterClient(Events, EventCallback const&);
504+ bool UnregisterClient(EventCallback const&);
505+
506+private:
507+ Monitor(Monitor const&) = delete;
508+ Monitor& operator=(Monitor const&) = delete;
509+
510+ struct Impl;
511+ std::unique_ptr<Impl> impl_;
512+};
513+
514+} // input namespace
515+} // unity namespace
516+
517+namespace std
518+{
519+template<>
520+struct hash<unity::input::Monitor::EventCallback>
521+{
522+ size_t operator()(unity::input::Monitor::EventCallback const& cb) const
523+ {
524+ if (cb.rep_)
525+ return std::hash<size_t>()(reinterpret_cast<size_t>(cb.rep_->call_));
526+
527+ return std::hash<size_t>()(reinterpret_cast<size_t>(cb.rep_));
528+ }
529+};
530+
531+template<>
532+struct equal_to<unity::input::Monitor::EventCallback>
533+{
534+ bool operator()(unity::input::Monitor::EventCallback const& lhs, unity::input::Monitor::EventCallback const& rhs) const
535+ {
536+ if (!lhs.rep_ || !rhs.rep_)
537+ return (lhs.rep_ == rhs.rep_);
538+
539+ return (lhs.rep_->call_ == rhs.rep_->call_);
540+ }
541+};
542+} // std namespace
543+
544+#endif // __UNITY_INPUT_MONITOR__
545
546=== modified file 'unity-shared/StandaloneWindowManager.cpp'
547--- unity-shared/StandaloneWindowManager.cpp 2015-11-20 11:33:38 +0000
548+++ unity-shared/StandaloneWindowManager.cpp 2016-08-22 10:28:22 +0000
549@@ -624,6 +624,9 @@
550 return std::string();
551 }
552
553+void StandaloneWindowManager::SetCardinalProperty(Window, Atom, std::vector<long> const&)
554+{}
555+
556 std::vector<long> StandaloneWindowManager::GetCardinalProperty(Window, Atom) const
557 {
558 return std::vector<long>();
559
560=== modified file 'unity-shared/StandaloneWindowManager.h'
561--- unity-shared/StandaloneWindowManager.h 2016-03-18 18:58:26 +0000
562+++ unity-shared/StandaloneWindowManager.h 2016-08-22 10:28:22 +0000
563@@ -165,6 +165,7 @@
564 virtual std::string GetWindowName(Window window_id) const;
565 virtual bool IsOnscreenKeyboard(Window window_id) const;
566 virtual std::string GetStringProperty(Window window_id, Atom) const;
567+ virtual void SetCardinalProperty(Window window_id, Atom, std::vector<long> const&);
568 virtual std::vector<long> GetCardinalProperty(Window window_id, Atom) const;
569
570 // Mock functions
571
572=== modified file 'unity-shared/WindowManager.h'
573--- unity-shared/WindowManager.h 2016-03-18 18:58:26 +0000
574+++ unity-shared/WindowManager.h 2016-08-22 10:28:22 +0000
575@@ -170,6 +170,7 @@
576 virtual bool IsOnscreenKeyboard(Window window_id) const = 0;
577
578 virtual std::string GetStringProperty(Window, Atom) const = 0;
579+ virtual void SetCardinalProperty(Window, Atom, std::vector<long> const&) = 0;
580 virtual std::vector<long> GetCardinalProperty(Window, Atom) const = 0;
581
582 virtual Cursor GetCachedCursor(unsigned int cursor_name) const = 0;
583
584=== modified file 'unity-shared/XWindowManager.cpp'
585--- unity-shared/XWindowManager.cpp 2015-01-21 15:28:59 +0000
586+++ unity-shared/XWindowManager.cpp 2016-08-22 10:28:22 +0000
587@@ -123,6 +123,12 @@
588 return std::string(val, n_items);
589 }
590
591+void XWindowManager::SetCardinalProperty(Window window_id, Atom atom, std::vector<long> const& values)
592+{
593+ XChangeProperty(screen->dpy(), window_id, atom, XA_CARDINAL, 32, PropModeReplace,
594+ (unsigned char *) values.data(), values.size());
595+}
596+
597 std::vector<long> XWindowManager::GetCardinalProperty(Window window_id, Atom atom) const
598 {
599 Atom type;
600
601=== modified file 'unity-shared/XWindowManager.h'
602--- unity-shared/XWindowManager.h 2015-01-21 15:28:59 +0000
603+++ unity-shared/XWindowManager.h 2016-08-22 10:28:22 +0000
604@@ -36,6 +36,7 @@
605 std::string GetWindowName(Window window_id) const;
606 bool IsOnscreenKeyboard(Window window_id) const;
607 std::string GetStringProperty(Window window_id, Atom atom) const;
608+ void SetCardinalProperty(Window, Atom, std::vector<long> const&);
609 std::vector<long> GetCardinalProperty(Window, Atom) const;
610 };
611