Merge lp:~lukas-kde/qtmir/wheelEvent into lp:qtmir

Proposed by Lukáš Tinkl on 2015-10-01
Status: Superseded
Proposed branch: lp:~lukas-kde/qtmir/wheelEvent
Merge into: lp:qtmir
Diff against target: 5957 lines (+3735/-602)
94 files modified
CMakeLists.txt (+4/-2)
benchmarks/CMakeLists.txt (+8/-0)
benchmarks/README (+12/-0)
benchmarks/common.py (+33/-0)
benchmarks/report_types.py (+121/-0)
benchmarks/touch_event_latency.R (+6/-0)
benchmarks/touch_event_latency.py (+271/-0)
debian/control (+22/-2)
debian/qtmir-tests.install (+6/-0)
debian/rules (+6/-8)
demos/CMakeLists.txt (+4/-0)
demos/paths.h.in (+40/-0)
demos/qml-demo-client/CMakeLists.txt (+41/-0)
demos/qml-demo-client/main.cpp (+73/-0)
demos/qml-demo-client/qtmir-demo-client.desktop.in (+9/-0)
demos/qml-demo-shell/CMakeLists.txt (+35/-0)
demos/qml-demo-shell/ResizeArea.qml (+128/-0)
demos/qml-demo-shell/Shell.qml (+19/-1)
demos/qml-demo-shell/TitleBar.qml (+4/-1)
demos/qml-demo-shell/Window.qml (+4/-81)
demos/qml-demo-shell/main.cpp (+57/-0)
demos/qml-demo-shell/qml-demo-shell.qml (+19/-0)
src/common/debughelpers.cpp (+30/-1)
src/common/debughelpers.h (+1/-0)
src/common/timestamp.cpp (+18/-0)
src/common/timestamp.h (+39/-0)
src/common/timestamp_impl.h (+34/-0)
src/modules/Unity/Application/CMakeLists.txt (+0/-1)
src/modules/Unity/Application/mirbuffersgtexture.cpp (+12/-2)
src/modules/Unity/Application/mirbuffersgtexture.h (+1/-0)
src/modules/Unity/Application/mirsurface.cpp (+54/-19)
src/modules/Unity/Application/mirsurface.h (+2/-1)
src/modules/Unity/Application/mirsurfaceinterface.h (+2/-1)
src/modules/Unity/Application/mirsurfaceitem.cpp (+15/-7)
src/modules/Unity/Application/plugin.cpp (+8/-1)
src/modules/Unity/Application/tracepoints.tp (+5/-0)
src/modules/Unity/CMakeLists.txt (+1/-0)
src/modules/Unity/Screens/CMakeLists.txt (+24/-0)
src/modules/Unity/Screens/plugin.cpp (+41/-0)
src/modules/Unity/Screens/qmldir (+2/-0)
src/modules/Unity/Screens/screens.cpp (+107/-0)
src/modules/Unity/Screens/screens.h (+82/-0)
src/platforms/mirserver/CMakeLists.txt (+11/-3)
src/platforms/mirserver/cursor.cpp (+152/-0)
src/platforms/mirserver/cursor.h (+66/-0)
src/platforms/mirserver/display.cpp (+0/-44)
src/platforms/mirserver/display.h (+0/-37)
src/platforms/mirserver/logging.h (+1/-0)
src/platforms/mirserver/miropenglcontext.cpp (+25/-10)
src/platforms/mirserver/miropenglcontext.h (+1/-0)
src/platforms/mirserver/mirserver.cpp (+44/-4)
src/platforms/mirserver/mirserver.h (+8/-2)
src/platforms/mirserver/mirserverintegration.cpp (+42/-39)
src/platforms/mirserver/mirserverintegration.h (+4/-7)
src/platforms/mirserver/mirsingleton.cpp (+33/-0)
src/platforms/mirserver/mirsingleton.h (+46/-0)
src/platforms/mirserver/offscreensurface.cpp (+61/-0)
src/platforms/mirserver/offscreensurface.h (+43/-0)
src/platforms/mirserver/qmirserver.cpp (+12/-2)
src/platforms/mirserver/qmirserver.h (+3/-0)
src/platforms/mirserver/qmirserver_p.h (+2/-0)
src/platforms/mirserver/qtcompositor.cpp (+19/-31)
src/platforms/mirserver/qtcompositor.h (+25/-5)
src/platforms/mirserver/qteventfeeder.cpp (+189/-101)
src/platforms/mirserver/qteventfeeder.h (+18/-9)
src/platforms/mirserver/screen.cpp (+103/-7)
src/platforms/mirserver/screen.h (+36/-4)
src/platforms/mirserver/screencontroller.cpp (+211/-0)
src/platforms/mirserver/screencontroller.h (+95/-0)
src/platforms/mirserver/screenwindow.cpp (+73/-86)
src/platforms/mirserver/screenwindow.h (+13/-23)
src/platforms/mirserver/tileddisplayconfigurationpolicy.cpp (+44/-0)
src/platforms/mirserver/tileddisplayconfigurationpolicy.h (+35/-0)
src/platforms/mirserver/tracepoints.tp (+5/-0)
tests/common/fake_displayconfigurationoutput.h (+73/-0)
tests/common/gmock_fixes.h (+124/-0)
tests/common/mock_display.h (+53/-0)
tests/common/mock_display_buffer.h (+43/-0)
tests/common/mock_display_configuration.h (+35/-0)
tests/common/mock_main_loop.h (+53/-0)
tests/mirserver/CMakeLists.txt (+1/-0)
tests/mirserver/QtEventFeeder/mock_qtwindowsystem.h (+21/-10)
tests/mirserver/QtEventFeeder/qteventfeeder_test.cpp (+27/-16)
tests/mirserver/Screen/CMakeLists.txt (+1/-0)
tests/mirserver/Screen/screen_test.cpp (+38/-24)
tests/mirserver/ScreenController/CMakeLists.txt (+28/-0)
tests/mirserver/ScreenController/screencontroller_test.cpp (+177/-0)
tests/mirserver/ScreenController/stub_display.h (+96/-0)
tests/mirserver/ScreenController/stub_screen.h (+31/-0)
tests/mirserver/ScreenController/testable_screencontroller.h (+37/-0)
tests/modules/General/CMakeLists.txt (+2/-8)
tests/modules/General/timestamp_test.cpp (+72/-0)
tests/modules/common/fake_mirsurface.h (+2/-1)
tests/modules/common/qtmir_test.h (+1/-1)
To merge this branch: bzr merge lp:~lukas-kde/qtmir/wheelEvent
Reviewer Review Type Date Requested Status
Daniel d'Andrada (community) 2015-10-01 Approve on 2015-10-08
PS Jenkins bot continuous-integration Approve on 2015-10-08
Review via email: mp+273150@code.launchpad.net

This proposal has been superseded by a proposal from 2015-10-13.

Commit Message

Implement support for mouse wheel events; correctly pass around buttons

Description of the Change

Implement support for mouse wheel events; also correctly pass around buttons

Cf. https://bugs.launchpad.net/ubuntu/+source/qtmir/+bug/1497091

* Are there any related MPs required for this MP to build/function as expected? Please list.

https://code.launchpad.net/~lukas-kde/qtubuntu/wheelEvent/+merge/273149

* Did you perform an exploratory manual test run of your code change and any related functionality?

Yes

* Did you make sure that your branch does not contain spurious tags?

Yes

* If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?

Yes

* If you changed the UI, has there been a design review?

N/A

To post a comment you must log in.
Daniel d'Andrada (dandrader) wrote :

"""
-pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=8)
+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application>=8)
"""

The unity-api versioning scheme is made so that qtmir and unity8 depend on a *specific* version of the API. That's meant solely ensure that qtmir and unity8 are kept in sync. There's absolutely no guarantee (or effort to towards it) that a given version is backward or forward compatible.

review: Needs Fixing
lp:~lukas-kde/qtmir/wheelEvent updated on 2015-10-07
384. By Lukáš Tinkl on 2015-10-07

require strict unity-api dep

remove noisy debug

Lukáš Tinkl (lukas-kde) wrote :

> """
> -pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=8)
> +pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application>=8)
> """
>
> The unity-api versioning scheme is made so that qtmir and unity8 depend on a
> *specific* version of the API. That's meant solely ensure that qtmir and
> unity8 are kept in sync. There's absolutely no guarantee (or effort to towards
> it) that a given version is backward or forward compatible.

Got it, fixed

Daniel d'Andrada (dandrader) wrote :

Could you please move "getMirButtonsFromQt(qtEvent->buttons())" into a separate variable like it's being done for timestamp and modifiers?

The mir::events::make_event() call is already messy from having such a large number of parameters. Using those helper variables improve readability and I think won't impact performance as I bet they will be optimized away by the compiler.

lp:~lukas-kde/qtmir/wheelEvent updated on 2015-10-07
385. By Lukáš Tinkl on 2015-10-07

extract the buttons into a variable

Lukáš Tinkl (lukas-kde) wrote :

> Could you please move "getMirButtonsFromQt(qtEvent->buttons())" into a
> separate variable like it's being done for timestamp and modifiers?
>
> The mir::events::make_event() call is already messy from having such a large
> number of parameters. Using those helper variables improve readability and I
> think won't impact performance as I bet they will be optimized away by the
> compiler.

Done

Daniel d'Andrada (dandrader) wrote :

the handleWheelEvent() line in qteventfeeder.h is way too long. Please avoid going beyond 120 chars. It's our coding style (which, by the way, I couldn't find it right now).

That's the last nitpick I have with this MP. :)

lp:~lukas-kde/qtmir/wheelEvent updated on 2015-10-08
386. By Lukáš Tinkl on 2015-10-08

wrap too long lines

Lukáš Tinkl (lukas-kde) wrote :

> the handleWheelEvent() line in qteventfeeder.h is way too long. Please avoid
> going beyond 120 chars. It's our coding style (which, by the way, I couldn't
> find it right now).
>
> That's the last nitpick I have with this MP. :)

Fixed

Daniel d'Andrada (dandrader) wrote :

> > the handleWheelEvent() line in qteventfeeder.h is way too long. Please avoid
> > going beyond 120 chars. It's our coding style (which, by the way, I couldn't
> > find it right now).
> >
> > That's the last nitpick I have with this MP. :)
>
> Fixed

Thanks!

review: Approve
lp:~lukas-kde/qtmir/wheelEvent updated on 2015-10-14
387. By Lukáš Tinkl on 2015-10-13

[ CI Train Bot ]
* New rebuild forced.
[ Daniel d'Andrada ]
* MirSurfaceItem: texture must be manipulated only from the scene
  graph thread (LP: #1499388, #1495871)
[ Gerry Boland ]
* Add "Closing" state to Application, use it to distinguish user-
  induced close from app-induced close. Don't clear QML cache if user-
  induced. (LP: #1500372)

388. By Lukáš Tinkl on 2015-10-13

make the cursor work

389. By Lukáš Tinkl on 2015-10-14

merge lp:~unity-team/qtmir/touch_tracing

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2015-09-17 20:17:17 +0000
3+++ CMakeLists.txt 2015-10-13 19:42:45 +0000
4@@ -75,7 +75,7 @@
5 pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
6 pkg_check_modules(QTDBUSTEST libqtdbustest-1 REQUIRED)
7 pkg_check_modules(QTDBUSMOCK libqtdbusmock-1 REQUIRED)
8-pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=8)
9+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=9)
10
11 include_directories(${APPLICATION_API_INCLUDE_DIRS})
12
13@@ -120,6 +120,7 @@
14
15 # Set QML module install path
16 set(QML_MODULE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/qt5/qml")
17+set(QTMIR_DATA_DIR ${CMAKE_INSTALL_DATADIR}/qtmir)
18
19
20 # Customisations for the build type
21@@ -156,6 +157,7 @@
22 message(STATUS "Tests disabled")
23 endif()
24
25-
26 # add subdirectories to build
27 add_subdirectory(src)
28+add_subdirectory(demos)
29+add_subdirectory(benchmarks)
30
31=== added directory 'benchmarks'
32=== added file 'benchmarks/CMakeLists.txt'
33--- benchmarks/CMakeLists.txt 1970-01-01 00:00:00 +0000
34+++ benchmarks/CMakeLists.txt 2015-10-13 19:42:45 +0000
35@@ -0,0 +1,8 @@
36+file(GLOB BENCHMARK_FILES
37+ *.py
38+ *.R
39+)
40+
41+install(FILES ${BENCHMARK_FILES}
42+ DESTINATION ${QTMIR_DATA_DIR}/benchmarks
43+)
44
45=== added file 'benchmarks/README'
46--- benchmarks/README 1970-01-01 00:00:00 +0000
47+++ benchmarks/README 2015-10-13 19:42:45 +0000
48@@ -0,0 +1,12 @@
49+To run performance tests on device:
50+
51+Firstly, you will need to install the qtmir-tests package. So either install the package using apt or build
52+and install from source
53+$ sudo apt-get install qtmir-tests
54+
55+Then you need to stop unity8 & unity-system compositor so that we can start out test in a controlled environment.
56+$ sudo stop lightdm
57+
58+Next, start the test!
59+$ cd benchmarks
60+$ sudo python3 touch_event_latency.py
61\ No newline at end of file
62
63=== added file 'benchmarks/common.py'
64--- benchmarks/common.py 1970-01-01 00:00:00 +0000
65+++ benchmarks/common.py 2015-10-13 19:42:45 +0000
66@@ -0,0 +1,33 @@
67+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
68+#
69+# Copyright (C) 2015 Canonical Ltd.
70+#
71+# This program is free software; you can redistribute it and/or modify
72+# it under the terms of the GNU Lesser General Public License as published by
73+# the Free Software Foundation; version 3.
74+#
75+# This program is distributed in the hope that it will be useful,
76+# but WITHOUT ANY WARRANTY; without even the implied warranty of
77+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
78+# GNU Lesser General Public License for more details.
79+#
80+# You should have received a copy of the GNU Lesser General Public License
81+# along with this program. If not, see <http://www.gnu.org/licenses/>.
82+
83+from autopilot import (
84+ input,
85+ platform
86+)
87+
88+def get_pointing_device():
89+ """Return the pointing device depending on the platform.
90+
91+ If the platform is `Desktop`, the pointing device will be a `Mouse`.
92+ If not, the pointing device will be `Touch`.
93+
94+ """
95+ if platform.model() == 'Desktop':
96+ input_device_class = input.Mouse
97+ else:
98+ input_device_class = input.Touch
99+ return input.Pointer(device=input_device_class.create())
100\ No newline at end of file
101
102=== added file 'benchmarks/report_types.py'
103--- benchmarks/report_types.py 1970-01-01 00:00:00 +0000
104+++ benchmarks/report_types.py 2015-10-13 19:42:45 +0000
105@@ -0,0 +1,121 @@
106+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
107+#
108+# Copyright (C) 2015 Canonical Ltd.
109+#
110+# This program is free software; you can redistribute it and/or modify
111+# it under the terms of the GNU Lesser General Public License as published by
112+# the Free Software Foundation; version 3.
113+#
114+# This program is distributed in the hope that it will be useful,
115+# but WITHOUT ANY WARRANTY; without even the implied warranty of
116+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
117+# GNU Lesser General Public License for more details.
118+#
119+# You should have received a copy of the GNU Lesser General Public License
120+# along with this program. If not, see <http://www.gnu.org/licenses/>.
121+
122+import subprocess
123+import os
124+import shutil
125+
126+class Node:
127+ def __init__(self, name):
128+ self.name = name
129+ self.children = []
130+
131+ def add_child(self, child):
132+ self.children.append(child)
133+
134+ def to_string(self):
135+ output = "<%s>" % self.name
136+ for child in self.children:
137+ output += child.to_string()
138+ output += "</%s>" % self.name
139+ return output
140+
141+class Results(Node):
142+ def __init__(self):
143+ super().__init__("results")
144+
145+class Events(Node):
146+ def __init__(self):
147+ super().__init__("events")
148+
149+class Event:
150+ def __init__(self, event):
151+ self.pid = event["vpid"]
152+ self.name = event.name
153+ self.timestamp = event.timestamp
154+
155+ def to_string(self):
156+ output = "<event "
157+ output += "pid='{}' ".format(self.pid)
158+ output += "name='{}' ".format(self.name)
159+ output += "timestamp='{}' ".format(self.timestamp)
160+ output += "/>"
161+ return output
162+
163+class Processes(Node):
164+ def __init__(self):
165+ super().__init__("processes")
166+
167+class Process:
168+ def __init__(self, name, pid):
169+ self.name = name
170+ self.pid = pid
171+
172+ def to_string(self):
173+ output = "<process "
174+ output += "name='{}' ".format(self.name)
175+ output += "pid='{}' ".format(self.pid)
176+ output += "/>"
177+ return output
178+
179+class ResultsData:
180+ def __init__(self, name, mean=0.0, deviation=0.0, comment=""):
181+ self.data = []
182+ self.name = name
183+ self.mean = mean
184+ self.deviation = deviation
185+ self.comment = comment
186+
187+ def add_data(self, value):
188+ self.data.append(value)
189+
190+ def to_string(self):
191+ output = "<data name='{}' mean='{}' deviation='{}' comment='{}' count='{}'>".format(
192+ self.name,
193+ self.mean,
194+ self.deviation,
195+ self.comment,
196+ len(self.data))
197+ output += "<values>"
198+ output += ",".join(map( lambda x: str(x), self.data))
199+ output += "</values>"
200+ output += "</data>"
201+ return output;
202+
203+ def generate_histogram(self, filename):
204+ cmdline = [
205+ shutil.which("Rscript"),
206+ os.path.split(os.path.abspath(__file__))[0] + "/touch_event_latency.R",
207+ "data.csv",
208+ "%s.png" % filename]
209+ # Use R to generate a histogram plot
210+ f = open("data.csv", "w")
211+ f.write(",".join(map( lambda x: str(x), self.data)));
212+ f.close()
213+ process = subprocess.Popen(cmdline)
214+ process.wait()
215+ if process.returncode != 0:
216+ print("Failed to generate histogram");
217+ os.remove("data.csv")
218+
219+
220+class Error:
221+ def __init__(self, comment):
222+ self.comment = comment
223+
224+ def to_string(self):
225+ return "<error comment='{}' />".format(self.comment)
226+ return output
227\ No newline at end of file
228
229=== added file 'benchmarks/touch_event_latency.R'
230--- benchmarks/touch_event_latency.R 1970-01-01 00:00:00 +0000
231+++ benchmarks/touch_event_latency.R 2015-10-13 19:42:45 +0000
232@@ -0,0 +1,6 @@
233+args <- commandArgs(trailingOnly = TRUE)
234+data <- scan(args[1], numeric(), sep=",")
235+colors = c("red", "yellow", "green", "violet", "orange", "pink", "blue")
236+png(filename=args[2])
237+hist(data, breaks=max(data), col=colors)
238+dev.off()
239
240=== added file 'benchmarks/touch_event_latency.py'
241--- benchmarks/touch_event_latency.py 1970-01-01 00:00:00 +0000
242+++ benchmarks/touch_event_latency.py 2015-10-13 19:42:45 +0000
243@@ -0,0 +1,271 @@
244+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
245+#
246+# Copyright (C) 2015 Canonical Ltd.
247+#
248+# This program is free software; you can redistribute it and/or modify
249+# it under the terms of the GNU Lesser General Public License as published by
250+# the Free Software Foundation; version 3.
251+#
252+# This program is distributed in the hope that it will be useful,
253+# but WITHOUT ANY WARRANTY; without even the implied warranty of
254+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
255+# GNU Lesser General Public License for more details.
256+#
257+# You should have received a copy of the GNU Lesser General Public License
258+# along with this program. If not, see <http://www.gnu.org/licenses/>.
259+
260+from mir_perf_framework import PerformanceTest, Server, Client
261+import time
262+import statistics
263+import shutil
264+import sys
265+from common import get_pointing_device
266+import report_types
267+
268+####### TEST #######
269+
270+
271+def perform_test():
272+ host = Server(reports=["input"])
273+ nested = Server(executable=shutil.which("qtmir-demo-shell"),
274+ host=host,
275+ reports=["input","client-input-receiver"],
276+ env={"QT_QPA_PLATFORM": "mirserver", "QML_NO_TOUCH_COMPRESSION": "1"})
277+ client = Client(executable=shutil.which("qtmir-demo-client"),
278+ server=nested,
279+ reports=["input","client-input-receiver"],
280+ env={"QT_QPA_PLATFORM": "ubuntumirclient", "QML_NO_TOUCH_COMPRESSION": "1"},
281+ options=["--", "--desktop_file_hint=/usr/share/applications/qtmir-demo-client.desktop"])
282+
283+ test = PerformanceTest([host, nested, client])
284+ test.start()
285+
286+ results = report_types.Results()
287+ processes = report_types.Processes()
288+ processes.add_child(report_types.Process("Host", host.process.pid))
289+ processes.add_child(report_types.Process("Nested Server", nested.process.pid))
290+ processes.add_child(report_types.Process("Client", client.process.pid))
291+ results.add_child(processes)
292+
293+ time.sleep(3) # wait for settle
294+
295+ host_pid = host.process.pid
296+ nested_pid = nested.process.pid
297+ client_pid = client.process.pid
298+
299+ touch = get_pointing_device()
300+ time.sleep(1) # let mir pick up the new input device
301+ touch.drag(100, 100, 1000, 100, 5, 0.006)
302+ touch.drag(1000, 100, 1000, 1000, 5, 0.006)
303+ touch.drag(1000, 1000, 100, 1000, 5, 0.006)
304+ touch.drag(100, 1000, 100, 100, 5, 0.006)
305+
306+ # time.sleep(5) # wait for settle
307+ time.sleep(2) # wait for settle
308+ test.stop()
309+
310+ ####### TRACE PARSING #######
311+
312+ trace = test.babeltrace()
313+
314+ server_touch_data_timestamps = {}
315+ client_touch_data_timestamps = {}
316+ client_touch_data_latency = {}
317+
318+ qtmir_touch_dispatch_start = {}
319+ qtmir_touch_dispatch_end = {}
320+ qtmir_touch_consume_start = {}
321+ qtmir_touch_consume_end = {}
322+
323+ events = report_types.Events()
324+
325+ for event in trace.events:
326+ events.add_child(report_types.Event(event))
327+ pid = event["vpid"]
328+ if event.name == "mir_client_input_receiver:touch_event":
329+ if pid not in client_touch_data_timestamps: client_touch_data_timestamps[pid] = []
330+ if pid not in client_touch_data_latency: client_touch_data_latency[pid] = []
331+ diff = (event.timestamp - event["event_time"]) / 1000000.0
332+ if diff > 0:
333+ client_touch_data_timestamps[pid].append(event.timestamp)
334+ client_touch_data_latency[pid].append(diff)
335+
336+ elif event.name == "mir_server_input:published_motion_event":
337+ if pid not in server_touch_data_timestamps: server_touch_data_timestamps[pid] = []
338+ server_touch_data_timestamps[pid].append(event["event_time"])
339+
340+ elif event.name == "qtmirserver:touchEventDispatch_start":
341+ if pid not in qtmir_touch_dispatch_start: qtmir_touch_dispatch_start[pid] = []
342+ qtmir_touch_dispatch_start[pid].append(event.timestamp)
343+
344+ elif event.name == "qtmirserver:touchEventDispatch_end":
345+ if pid not in qtmir_touch_dispatch_end: qtmir_touch_dispatch_end[pid] = []
346+ qtmir_touch_dispatch_end[pid].append(event.timestamp)
347+
348+ elif event.name == "qtmir:touchEventConsume_start":
349+ if pid not in qtmir_touch_consume_start: qtmir_touch_consume_start[pid] = []
350+ qtmir_touch_consume_start[pid].append(event.timestamp)
351+
352+ elif event.name == "qtmir:touchEventConsume_end":
353+ if pid not in qtmir_touch_consume_end: qtmir_touch_consume_end[pid] = []
354+ qtmir_touch_consume_end[pid].append(event.timestamp)
355+
356+ # LATENCY MEANS
357+
358+ if nested_pid in client_touch_data_latency:
359+ nested_data = client_touch_data_latency[nested_pid]
360+ nested_latency_xml = report_types.ResultsData(
361+ "nested_latency",
362+ statistics.mean(nested_data),
363+ statistics.stdev(nested_data),
364+ "Kernel to nested server latency")
365+ for value in nested_data:
366+ nested_latency_xml.add_data(value)
367+ results.add_child(nested_latency_xml)
368+ nested_latency_xml.generate_histogram("nested_latency")
369+ else:
370+ results.add_child(report_types.Error("No nested server touch latency data"))
371+
372+ if client_pid in client_touch_data_latency:
373+ client_data = client_touch_data_latency[client_pid]
374+
375+ client_latency_xml = report_types.ResultsData(
376+ "client_latency",
377+ statistics.mean(client_data),
378+ statistics.stdev(client_data),
379+ "Kernel to client latency")
380+ for value in client_data:
381+ client_latency_xml.add_data(value)
382+ results.add_child(client_latency_xml)
383+ client_latency_xml.generate_histogram("client_latency")
384+ else:
385+ results.add_child(report_types.Error("No client touch latency data"))
386+
387+ # EVENT RATES
388+ if host_pid in server_touch_data_timestamps:
389+ last_timestamp = -1
390+ input_rate = []
391+
392+ for next_timestamp in server_touch_data_timestamps[host_pid]:
393+ if last_timestamp != -1:
394+ diff = (next_timestamp - last_timestamp) / 1000000.0
395+ input_rate.append(diff)
396+ last_timestamp = next_timestamp
397+
398+ input_rate_xml = report_types.ResultsData(
399+ "host_input_ate",
400+ statistics.mean(input_rate),
401+ statistics.stdev(input_rate),
402+ "Host input event rate")
403+ for value in input_rate:
404+ input_rate_xml.add_data(value)
405+ results.add_child(input_rate_xml)
406+ input_rate_xml.generate_histogram("host_input_rate")
407+ else:
408+ results.add_child(report_types.Error("No host server input event timestamp data"))
409+
410+ if nested_pid in client_touch_data_timestamps:
411+ last_timestamp = -1
412+ input_rate = []
413+ for next_timestamp in client_touch_data_timestamps[nested_pid]:
414+ if last_timestamp != -1:
415+ diff = (next_timestamp - last_timestamp) / 1000000.0
416+ input_rate.append(diff)
417+ last_timestamp = next_timestamp
418+
419+ input_rate_xml = report_types.ResultsData(
420+ "nested_input_rate",
421+ statistics.mean(input_rate),
422+ statistics.stdev(input_rate),
423+ "Nested server event rate")
424+ for value in input_rate:
425+ input_rate_xml.add_data(value)
426+ results.add_child(input_rate_xml)
427+ input_rate_xml.generate_histogram("nested_input_rate")
428+ else:
429+ results.add_child(report_types.Error("No nested server input event timestamp data"))
430+
431+ if client_pid in client_touch_data_timestamps:
432+ last_timestamp = -1
433+ input_rate = []
434+ for next_timestamp in client_touch_data_timestamps[client_pid]:
435+ if last_timestamp != -1:
436+ diff = (next_timestamp - last_timestamp) / 1000000.0
437+ input_rate.append(diff)
438+ last_timestamp = next_timestamp
439+
440+ input_rate_xml = report_types.ResultsData(
441+ "client_input_rate",
442+ statistics.mean(input_rate),
443+ statistics.stdev(input_rate),
444+ "Client event rate")
445+ for value in input_rate:
446+ input_rate_xml.add_data(value)
447+ results.add_child(input_rate_xml)
448+ input_rate_xml.generate_histogram("client_input_rate")
449+ else:
450+ results.add_child(report_types.Error("No client event timestamp data"))
451+
452+ qtmir_loop_data = []
453+ dispatch_data = []
454+ consume_data = []
455+
456+ # TIME BETWEEN TRACEPOINTS
457+ dispatch_starts = qtmir_touch_dispatch_start[nested_pid] if nested_pid in qtmir_touch_dispatch_start else []
458+ dispatch_ends = qtmir_touch_dispatch_end[nested_pid] if nested_pid in qtmir_touch_dispatch_end else []
459+ consume_starts = qtmir_touch_consume_start[nested_pid] if nested_pid in qtmir_touch_consume_start else []
460+ consume_ends = qtmir_touch_consume_end[nested_pid] if nested_pid in qtmir_touch_consume_end else []
461+
462+ # since there's no uniqueness to events, we need to assume all events are 1:1 through system
463+ if len(dispatch_starts) > 0 and len(dispatch_starts) == len(dispatch_ends) and len(dispatch_starts) == len(consume_starts) and len(consume_starts) == len(consume_ends):
464+ i = 0
465+
466+ for start in dispatch_starts:
467+ dispatch_diff = (dispatch_ends[i] - start) / 1000000.0
468+ consume_diff = (consume_ends[i] - consume_starts[i]) / 1000000.0
469+ loop_dif = (consume_starts[i] - dispatch_ends[i]) / 1000000.0
470+ dispatch_data.append(dispatch_diff);
471+ consume_data.append(consume_diff);
472+ qtmir_loop_data.append(loop_dif);
473+ i=i+1
474+
475+ qtmir_loop_xml = report_types.ResultsData(
476+ "qtmir_eventloop",
477+ statistics.mean(qtmir_loop_data),
478+ statistics.stdev(qtmir_loop_data),
479+ "Time spent in qtmir event loop")
480+ for value in qtmir_loop_data:
481+ qtmir_loop_xml.add_data(value)
482+ results.add_child(qtmir_loop_xml)
483+ qtmir_loop_xml.generate_histogram("qtmir_eventloop")
484+
485+ qtmir_dispatch_xml = report_types.ResultsData(
486+ "qtmir_dispatch",
487+ statistics.mean(dispatch_data),
488+ statistics.stdev(dispatch_data),
489+ "Time QteventFeeder spent dispatching event to qt")
490+ for value in dispatch_data:
491+ qtmir_dispatch_xml.add_data(value)
492+ results.add_child(qtmir_dispatch_xml)
493+ qtmir_dispatch_xml.generate_histogram("qtmir_dispatch")
494+
495+ qtmir_consume_xml = report_types.ResultsData(
496+ "qtmir_consume",
497+ statistics.mean(consume_data),
498+ statistics.stdev(consume_data),
499+ "Time MirSurfaceItem spent consiming event")
500+ for value in consume_data:
501+ qtmir_consume_xml.add_data(value)
502+ results.add_child(qtmir_consume_xml)
503+ qtmir_consume_xml.generate_histogram("qtmir_consume")
504+
505+ else:
506+ results.add_child(report_types.Error("Cannot calculate QtMir loop data - Dispatch event count did not match surface consume event count"))
507+
508+ results.add_child(events)
509+ return results
510+
511+if __name__ == "__main__":
512+ results = perform_test();
513+ f = open("touch_event_latency.xml", "w")
514+ f.write(results.to_string())
515
516=== modified file 'debian/control'
517--- debian/control 2015-09-25 12:11:08 +0000
518+++ debian/control 2015-10-13 19:42:45 +0000
519@@ -23,7 +23,7 @@
520 libubuntu-app-launch2-dev,
521 libubuntu-application-api-dev (>= 2.1.0),
522 libudev-dev,
523- libunity-api-dev (>= 7.100),
524+ libunity-api-dev (>= 7.101),
525 liburl-dispatcher1-dev,
526 libxkbcommon-dev,
527 libxrender-dev,
528@@ -93,8 +93,28 @@
529 Conflicts: libqtmir,
530 libunity-mir1,
531 Provides: unity-application-impl,
532- unity-application-impl-8,
533+ unity-application-impl-9,
534 Description: Qt plugin for Unity specific Mir APIs
535 QtMir provides Qt/QML bindings for Mir features that are exposed through the
536 qtmir-desktop or qtmir-android QPA plugin such as Application management
537 (start/stop/suspend/resume) and surface management.
538+
539+Package: qtmir-tests
540+Architecture: any
541+Multi-Arch: same
542+Pre-Depends: ${misc:Pre-Depends},
543+Depends: autopilot-qt5,
544+ littler,
545+ lttng-tools,
546+ mir-test-tools,
547+ python3-autopilot,
548+ python3-babeltrace,
549+ python3-evdev,
550+ python3-mir-perf-framework,
551+ qtmir-desktop (= ${source:Version}) | qtmir-android (= ${source:Version}),
552+ qtdeclarative5-qtmir-plugin,
553+ ${misc:Depends},
554+ ${shlibs:Depends},
555+Description: QtMir tests and demos
556+ This package provides benchmark tests and a simple shell and client using the
557+ QtMir QPA.
558
559=== added file 'debian/qtmir-tests.install'
560--- debian/qtmir-tests.install 1970-01-01 00:00:00 +0000
561+++ debian/qtmir-tests.install 2015-10-13 19:42:45 +0000
562@@ -0,0 +1,6 @@
563+usr/bin/qtmir-demo-shell
564+usr/bin/qtmir-demo-client
565+usr/share/applications/qtmir-demo-client.desktop
566+usr/share/qtmir/qtmir-demo-shell/
567+usr/share/qtmir/qtmir-demo-client/*
568+usr/share/qtmir/benchmarks/*
569
570=== modified file 'debian/rules'
571--- debian/rules 2015-07-08 16:54:24 +0000
572+++ debian/rules 2015-10-13 19:42:45 +0000
573@@ -28,11 +28,11 @@
574
575 override_dh_auto_configure:
576 ifeq ($(DEB_HOST_ARCH),$(USES_GLES2))
577- mkdir -p $(ANDROID_DIR) && dh_auto_configure -B$(ANDROID_DIR) -- $(FLAGS) $(CURDIR) -DCMAKE_INSTALL_PREFIX=$(TMP1_DIR)/usr/ -DUSE_OPENGLES=1
578+ mkdir -p $(ANDROID_DIR) && dh_auto_configure -B$(ANDROID_DIR) -- $(FLAGS) $(CURDIR) -DUSE_OPENGLES=1
579 # See comment in CMakeLists.txt
580- mkdir -p $(DESKTOP_DIR) && dh_auto_configure -B$(DESKTOP_DIR) -- $(FLAGS) $(CURDIR) -DCMAKE_INSTALL_PREFIX=$(TMP2_DIR)/usr/ -DUSE_OPENGL_BUT_LINK_AGAINST_OPENGLES=1
581+ mkdir -p $(DESKTOP_DIR) && dh_auto_configure -B$(DESKTOP_DIR) -- $(FLAGS) $(CURDIR) -DUSE_OPENGL_BUT_LINK_AGAINST_OPENGLES=1
582 else
583- mkdir -p $(DESKTOP_DIR) && dh_auto_configure -B$(DESKTOP_DIR) -- $(FLAGS) $(CURDIR) -DCMAKE_INSTALL_PREFIX=$(TMP2_DIR)/usr/ -DUSE_OPENGLES=1
584+ mkdir -p $(DESKTOP_DIR) && dh_auto_configure -B$(DESKTOP_DIR) -- $(FLAGS) $(CURDIR) -DUSE_OPENGLES=1
585 endif
586
587 override_dh_auto_build:
588@@ -49,11 +49,9 @@
589
590 override_dh_auto_install:
591 ifeq ($(DEB_HOST_ARCH),$(USES_GLES2))
592- mkdir -p $(TMP1_DIR) && cd $(ANDROID_DIR) && INSTALL_ROOT=$(TMP1_DIR) make install
593- cd $(CURDIR)
594+ dh_auto_install -B$(ANDROID_DIR) --destdir=$(TMP1_DIR)
595 endif
596- mkdir -p $(TMP2_DIR) && cd $(DESKTOP_DIR) && INSTALL_ROOT=$(TMP2_DIR) make install
597- cd $(CURDIR)
598+ dh_auto_install -B$(DESKTOP_DIR) --destdir=$(TMP2_DIR)
599
600 override_dh_install:
601 ifeq ($(DEB_HOST_ARCH),$(USES_GLES2))
602@@ -61,4 +59,4 @@
603 endif
604 dh_install --sourcedir=$(TMP2_DIR) -pqtmir-desktop
605 dh_install --sourcedir=$(TMP2_DIR) -pqtdeclarative5-qtmir-plugin
606-
607+ dh_install --sourcedir=$(TMP2_DIR) -pqtmir-tests
608
609=== added file 'demos/CMakeLists.txt'
610--- demos/CMakeLists.txt 1970-01-01 00:00:00 +0000
611+++ demos/CMakeLists.txt 2015-10-13 19:42:45 +0000
612@@ -0,0 +1,4 @@
613+configure_file(paths.h.in ${CMAKE_CURRENT_BINARY_DIR}/paths.h @ONLY)
614+
615+add_subdirectory(qml-demo-client)
616+add_subdirectory(qml-demo-shell)
617\ No newline at end of file
618
619=== added file 'demos/paths.h.in'
620--- demos/paths.h.in 1970-01-01 00:00:00 +0000
621+++ demos/paths.h.in 2015-10-13 19:42:45 +0000
622@@ -0,0 +1,40 @@
623+/*
624+ * Copyright (C) 2015 Canonical, Ltd.
625+ *
626+ * This program is free software; you can redistribute it and/or modify
627+ * it under the terms of the GNU General Public License as published by
628+ * the Free Software Foundation; version 3.
629+ *
630+ * This program is distributed in the hope that it will be useful,
631+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
632+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
633+ * GNU General Public License for more details.
634+ *
635+ * You should have received a copy of the GNU General Public License
636+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
637+ */
638+
639+#ifndef PATHS_H
640+#define PATHS_H
641+
642+// Qt
643+#include <QtCore/QCoreApplication>
644+#include <QtCore/QDir>
645+#include <QtGui/QIcon>
646+#include <QtQml/QQmlEngine>
647+#include <QStandardPaths>
648+
649+inline bool isRunningInstalled() {
650+ static bool installed = (QCoreApplication::applicationDirPath() ==
651+ QDir(("@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_BINDIR@")).canonicalPath());
652+ return installed;
653+}
654+
655+inline QString qmlDirectory() {
656+ if (isRunningInstalled()) {
657+ return QString("@CMAKE_INSTALL_PREFIX@/@QTMIR_DATA_DIR@/");
658+ } else {
659+ return QString("@CMAKE_SOURCE_DIR@/demos/");
660+ }
661+}
662+#endif
663\ No newline at end of file
664
665=== added file 'demos/qml-demo-client/CMakeLists.txt'
666--- demos/qml-demo-client/CMakeLists.txt 1970-01-01 00:00:00 +0000
667+++ demos/qml-demo-client/CMakeLists.txt 2015-10-13 19:42:45 +0000
668@@ -0,0 +1,41 @@
669+set(DEMO_CLIENT qtmir-demo-client)
670+configure_file(${DEMO_CLIENT}.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/${DEMO_CLIENT}.desktop @ONLY)
671+
672+include_directories(
673+ ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
674+)
675+
676+add_executable(${DEMO_CLIENT}
677+ main.cpp
678+)
679+
680+include_directories(
681+ ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
682+ ${Qt5Qml_PRIVATE_INCLUDE_DIRS}
683+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
684+)
685+
686+target_link_libraries(
687+ ${DEMO_CLIENT}
688+ Qt5::Core
689+ Qt5::DBus
690+ Qt5::Qml
691+ Qt5::Quick
692+)
693+
694+file(GLOB QML_JS_FILES *.qml *.js *.png)
695+
696+# install binaries
697+install(TARGETS ${DEMO_CLIENT}
698+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
699+ )
700+
701+install(FILES
702+ ${QML_JS_FILES}
703+ DESTINATION ${QTMIR_DATA_DIR}/${DEMO_CLIENT}
704+)
705+
706+install(FILES
707+ ${CMAKE_CURRENT_BINARY_DIR}/${DEMO_CLIENT}.desktop
708+ DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications
709+)
710\ No newline at end of file
711
712=== added file 'demos/qml-demo-client/main.cpp'
713--- demos/qml-demo-client/main.cpp 1970-01-01 00:00:00 +0000
714+++ demos/qml-demo-client/main.cpp 2015-10-13 19:42:45 +0000
715@@ -0,0 +1,73 @@
716+/*
717+ * Copyright (C) 2012-2015 Canonical, Ltd.
718+ *
719+ * This program is free software; you can redistribute it and/or modify
720+ * it under the terms of the GNU General Public License as published by
721+ * the Free Software Foundation; version 3.
722+ *
723+ * This program is distributed in the hope that it will be useful,
724+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
725+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
726+ * GNU General Public License for more details.
727+ *
728+ * You should have received a copy of the GNU General Public License
729+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
730+ */
731+
732+// Qt
733+#include <QtQuick/QQuickView>
734+#include <QtGui/QGuiApplication>
735+#include <QDebug>
736+#include <csignal>
737+#include <libintl.h>
738+#include <getopt.h>
739+#include "../paths.h"
740+
741+// REMOVEME - Should be able to use qmlscene, but in order to use the mir benchmarking we need
742+// to parse command line switches. Wait until MIR_SOCKET supported by the benchmark framework.
743+
744+int main(int argc, char **argv)
745+{
746+ int arg;
747+ opterr = 0;
748+ while ((arg = getopt (argc, argv, "hm:")) != -1)
749+ {
750+ switch (arg)
751+ {
752+ case 'm':
753+ setenv("MIR_SOCKET", optarg, 1);
754+ break;
755+
756+ case '?':
757+ case 'h':
758+ default:
759+ puts(argv[0]);
760+ puts("Usage:");
761+ puts(" -m <Mir server socket>");
762+ puts(" -h: this help text");
763+ return -1;
764+ }
765+ }
766+
767+ QGuiApplication::setApplicationName("qml-demo-client");
768+ QGuiApplication *application;
769+
770+ application = new QGuiApplication(argc, (char**)argv);
771+ QQuickView* view = new QQuickView();
772+ view->setResizeMode(QQuickView::SizeRootObjectToView);
773+ view->setColor("black");
774+ view->setTitle("Demo Client");
775+
776+ QUrl source(::qmlDirectory() + "qtmir-demo-client/qml-demo-client.qml");
777+
778+ view->setSource(source);
779+ QObject::connect(view->engine(), SIGNAL(quit()), application, SLOT(quit()));
780+
781+ view->showFullScreen();
782+ int result = application->exec();
783+
784+ delete view;
785+ delete application;
786+
787+ return result;
788+}
789
790=== added file 'demos/qml-demo-client/qtmir-demo-client.desktop.in'
791--- demos/qml-demo-client/qtmir-demo-client.desktop.in 1970-01-01 00:00:00 +0000
792+++ demos/qml-demo-client/qtmir-demo-client.desktop.in 2015-10-13 19:42:45 +0000
793@@ -0,0 +1,9 @@
794+[Desktop Entry]
795+Type=Application
796+Name=QtMir Demo Client
797+Comment=QtMir demo client
798+Exec=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_BINDIR@/qtmir-demo-client
799+Terminal=false
800+Icon=
801+NoDisplay=false
802+X-Ubuntu-Touch=true
803
804=== added file 'demos/qml-demo-shell/CMakeLists.txt'
805--- demos/qml-demo-shell/CMakeLists.txt 1970-01-01 00:00:00 +0000
806+++ demos/qml-demo-shell/CMakeLists.txt 2015-10-13 19:42:45 +0000
807@@ -0,0 +1,35 @@
808+set(DEMO_SHELL qtmir-demo-shell)
809+
810+include_directories(
811+ ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
812+)
813+
814+add_executable(${DEMO_SHELL}
815+ main.cpp
816+)
817+
818+include_directories(
819+ ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
820+ ${Qt5Qml_PRIVATE_INCLUDE_DIRS}
821+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
822+)
823+
824+target_link_libraries(
825+ ${DEMO_SHELL}
826+ Qt5::Core
827+ Qt5::DBus
828+ Qt5::Qml
829+ Qt5::Quick
830+)
831+
832+file(GLOB QML_JS_FILES *.qml *.js *.png)
833+
834+# install binaries
835+install(TARGETS ${DEMO_SHELL}
836+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
837+ )
838+
839+install(FILES
840+ ${QML_JS_FILES}
841+ DESTINATION ${QTMIR_DATA_DIR}/${DEMO_SHELL}
842+)
843
844=== added file 'demos/qml-demo-shell/ResizeArea.qml'
845--- demos/qml-demo-shell/ResizeArea.qml 1970-01-01 00:00:00 +0000
846+++ demos/qml-demo-shell/ResizeArea.qml 2015-10-13 19:42:45 +0000
847@@ -0,0 +1,128 @@
848+import QtQuick 2.4
849+import Unity.Application 0.1
850+
851+MouseArea {
852+ id: root
853+
854+ // to be set from outside
855+ property Item target
856+ property real borderThickness
857+
858+ property bool leftBorder: false
859+ property bool rightBorder: false
860+ property bool topBorder: false
861+ property bool bottomBorder: false
862+
863+ property bool dragging: false
864+ property real startX
865+ property real startY
866+ property real startWidth
867+ property real startHeight
868+
869+ hoverEnabled: true
870+
871+ property string cursorName: {
872+ if (containsMouse || pressed) {
873+ if (leftBorder && !topBorder && !bottomBorder) {
874+ return "left_side";
875+ } else if (rightBorder && !topBorder && !bottomBorder) {
876+ return "right_side";
877+ } else if (topBorder && !leftBorder && !rightBorder) {
878+ return "top_side";
879+ } else if (bottomBorder && !leftBorder && !rightBorder) {
880+ return "bottom_side";
881+ } else if (leftBorder && topBorder) {
882+ return "top_left_corner";
883+ } else if (leftBorder && bottomBorder) {
884+ return "bottom_left_corner";
885+ } else if (rightBorder && topBorder) {
886+ return "top_right_corner";
887+ } else if (rightBorder && bottomBorder) {
888+ return "bottom_right_corner";
889+ } else {
890+ return "";
891+ }
892+ } else {
893+ return "";
894+ }
895+ }
896+ onCursorNameChanged: {
897+ Mir.cursorName = cursorName;
898+ }
899+
900+ function updateBorders() {
901+ leftBorder = mouseX <= borderThickness;
902+ rightBorder = mouseX >= width - borderThickness;
903+ topBorder = mouseY <= borderThickness;
904+ bottomBorder = mouseY >= height - borderThickness;
905+ }
906+
907+ onPressedChanged: {
908+ if (pressed) {
909+ var pos = mapToItem(target.parent, mouseX, mouseY);
910+ startX = pos.x;
911+ startY = pos.y;
912+ startWidth = target.width;
913+ startHeight = target.height;
914+ dragging = true;
915+ } else {
916+ dragging = false;
917+ if (containsMouse) {
918+ updateBorders();
919+ }
920+ }
921+ }
922+
923+ onEntered: {
924+ if (!pressed) {
925+ updateBorders();
926+ }
927+ }
928+
929+ onPositionChanged: {
930+ if (!pressed) {
931+ updateBorders();
932+ }
933+
934+ if (!dragging) {
935+ return;
936+ }
937+
938+ var pos = mapToItem(target.parent, mouse.x, mouse.y);
939+
940+ if (leftBorder) {
941+ if (startX + startWidth - pos.x > target.minWidth) {
942+ target.x = pos.x;
943+ target.width = startX + startWidth - target.x;
944+ startX = target.x;
945+ startWidth = target.width;
946+ }
947+
948+ } else if (rightBorder) {
949+ var deltaX = pos.x - startX;
950+ if (startWidth + deltaX >= target.minWidth) {
951+ target.width = startWidth + deltaX;
952+ } else {
953+ target.width = target.minWidth;
954+ }
955+ }
956+
957+ if (topBorder) {
958+ if (startY + startHeight - pos.y > target.minHeight) {
959+ target.y = pos.y;
960+ target.height = startY + startHeight - target.y;
961+ startY = target.y;
962+ startHeight = target.height;
963+ }
964+
965+ } else if (bottomBorder) {
966+ var deltaY = pos.y - startY;
967+ if (startHeight + deltaY >= target.minHeight) {
968+ target.height = startHeight + deltaY;
969+ } else {
970+ target.height = target.minHeight;
971+ }
972+ }
973+ }
974+}
975+
976
977=== renamed file 'demos/qml-demo-shell/qml-demo-shell.qml' => 'demos/qml-demo-shell/Shell.qml'
978--- demos/qml-demo-shell/qml-demo-shell.qml 2015-08-31 09:51:28 +0000
979+++ demos/qml-demo-shell/Shell.qml 2015-10-13 19:42:45 +0000
980@@ -1,4 +1,4 @@
981-import QtQuick 2.0
982+import QtQuick 2.4
983 import Unity.Application 0.1
984
985 Rectangle {
986@@ -88,6 +88,7 @@
987 }
988
989 Rectangle {
990+ id: resizeButton
991 width: 90
992 height: 40
993 color: "blue"
994@@ -103,6 +104,23 @@
995 }
996 }
997
998+ Rectangle {
999+ width: 40
1000+ height: 40
1001+ color: "green"
1002+ anchors { right: resizeButton.left; bottom: parent.bottom }
1003+ Text {
1004+ anchors.centerIn: parent
1005+ text: "⟳"
1006+ color: "white"
1007+ font.pixelSize: 35
1008+ }
1009+ MouseArea {
1010+ anchors.fill: parent
1011+ onClicked: { root.rotation += 180; }
1012+ }
1013+ }
1014+
1015 Component {
1016 id: windowStretchComponent
1017 Window {
1018
1019=== modified file 'demos/qml-demo-shell/TitleBar.qml'
1020--- demos/qml-demo-shell/TitleBar.qml 2015-08-24 12:43:01 +0000
1021+++ demos/qml-demo-shell/TitleBar.qml 2015-10-13 19:42:45 +0000
1022@@ -1,4 +1,5 @@
1023-import QtQuick 2.0
1024+import QtQuick 2.4
1025+import Unity.Application 0.1
1026
1027 Rectangle {
1028 id: root
1029@@ -21,8 +22,10 @@
1030 distanceX = pos.x;
1031 distanceY = pos.y;
1032 dragging = true;
1033+ Mir.cursorName = "grabbing";
1034 } else {
1035 dragging = false;
1036+ Mir.cursorName = "";
1037 }
1038 }
1039 onMouseXChanged: {
1040
1041=== modified file 'demos/qml-demo-shell/Window.qml'
1042--- demos/qml-demo-shell/Window.qml 2015-08-31 09:51:28 +0000
1043+++ demos/qml-demo-shell/Window.qml 2015-10-13 19:42:45 +0000
1044@@ -58,87 +58,10 @@
1045 }
1046 ]
1047
1048-
1049- MouseArea {
1050- anchors.fill: parent
1051-
1052- property real startX
1053- property real startY
1054- property real startWidth
1055- property real startHeight
1056- property bool leftBorder
1057- property bool rightBorder
1058- property bool topBorder
1059- property bool bottomBorder
1060- property bool dragging
1061- onPressedChanged: {
1062- if (pressed) {
1063- var pos = mapToItem(root.parent, mouseX, mouseY);
1064- startX = pos.x;
1065- startY = pos.y;
1066- startWidth = width;
1067- startHeight = height;
1068- leftBorder = mouseX > 0 && mouseX < root.borderThickness;
1069- rightBorder = mouseX > (root.width - root.borderThickness) && mouseX < root.width;
1070- topBorder = mouseY > 0 && mouseY < root.borderThickness;
1071- bottomBorder = mouseY > (root.height - root.borderThickness) && mouseY < root.height;
1072- dragging = true;
1073- } else {
1074- dragging = false;
1075- }
1076- }
1077-
1078- onMouseXChanged: {
1079- if (!pressed || !dragging) {
1080- return;
1081- }
1082-
1083- var pos = mapToItem(root.parent, mouseX, mouseY);
1084-
1085- if (leftBorder) {
1086-
1087- if (startX + startWidth - pos.x > root.minWidth) {
1088- root.x = pos.x;
1089- root.width = startX + startWidth - root.x;
1090- startX = root.x;
1091- startWidth = root.width;
1092- }
1093-
1094- } else if (rightBorder) {
1095- var deltaX = pos.x - startX;
1096- if (startWidth + deltaX >= root.minWidth) {
1097- root.width = startWidth + deltaX;
1098- } else {
1099- root.width = root.minWidth;
1100- }
1101- }
1102- }
1103-
1104- onMouseYChanged: {
1105- if (!pressed || !dragging) {
1106- return;
1107- }
1108-
1109- var pos = mapToItem(root.parent, mouseX, mouseY);
1110-
1111- if (topBorder) {
1112-
1113- if (startY + startHeight - pos.y > root.minHeight) {
1114- root.y = pos.y;
1115- root.height = startY + startHeight - root.y;
1116- startY = root.y;
1117- startHeight = root.height;
1118- }
1119-
1120- } else if (bottomBorder) {
1121- var deltaY = pos.y - startY;
1122- if (startHeight + deltaY >= root.minHeight) {
1123- root.height = startHeight + deltaY;
1124- } else {
1125- root.height = root.minHeight;
1126- }
1127- }
1128- }
1129+ ResizeArea {
1130+ anchors.fill: root
1131+ borderThickness: root.borderThickness
1132+ target: root
1133 }
1134
1135 TitleBar {
1136
1137=== added file 'demos/qml-demo-shell/main.cpp'
1138--- demos/qml-demo-shell/main.cpp 1970-01-01 00:00:00 +0000
1139+++ demos/qml-demo-shell/main.cpp 2015-10-13 19:42:45 +0000
1140@@ -0,0 +1,57 @@
1141+/*
1142+ * Copyright (C) 2012-2015 Canonical, Ltd.
1143+ *
1144+ * This program is free software; you can redistribute it and/or modify
1145+ * it under the terms of the GNU General Public License as published by
1146+ * the Free Software Foundation; version 3.
1147+ *
1148+ * This program is distributed in the hope that it will be useful,
1149+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1150+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1151+ * GNU General Public License for more details.
1152+ *
1153+ * You should have received a copy of the GNU General Public License
1154+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1155+ */
1156+
1157+// Qt
1158+#include <QCommandLineParser>
1159+#include <QtQuick/QQuickView>
1160+#include <QtGui/QGuiApplication>
1161+#include <QtQml/QQmlEngine>
1162+#include <QtQml/QQmlContext>
1163+#include <QLibrary>
1164+#include <QDebug>
1165+#include <csignal>
1166+#include <libintl.h>
1167+#include "../paths.h"
1168+
1169+#include <private/qobject_p.h>
1170+
1171+// REMOVEME - Should be able to use qmlscene, but in order to use the mir benchmarking we need
1172+// to parse command line switches. Wait until MIR_SOCKET supported by the benchmark framework.
1173+
1174+int main(int argc, const char *argv[])
1175+{
1176+ QGuiApplication::setApplicationName("qml-demo-shell");
1177+ QGuiApplication *application;
1178+
1179+ application = new QGuiApplication(argc, (char**)argv);
1180+ QQuickView* view = new QQuickView();
1181+ view->setResizeMode(QQuickView::SizeRootObjectToView);
1182+ view->setColor("black");
1183+ view->setTitle("Demo Shell");
1184+
1185+ QUrl source(::qmlDirectory() + "qtmir-demo-shell/qml-demo-shell.qml");
1186+
1187+ view->setSource(source);
1188+ QObject::connect(view->engine(), SIGNAL(quit()), application, SLOT(quit()));
1189+
1190+ view->showFullScreen();
1191+ int result = application->exec();
1192+
1193+ delete view;
1194+ delete application;
1195+
1196+ return result;
1197+}
1198
1199=== added file 'demos/qml-demo-shell/qml-demo-shell.qml'
1200--- demos/qml-demo-shell/qml-demo-shell.qml 1970-01-01 00:00:00 +0000
1201+++ demos/qml-demo-shell/qml-demo-shell.qml 2015-10-13 19:42:45 +0000
1202@@ -0,0 +1,19 @@
1203+import QtQuick 2.3
1204+import QtQuick.Window 2.2 as QQW
1205+import Unity.Screens 0.1
1206+
1207+Instantiator {
1208+ id: root
1209+
1210+ property var screens: Screens{}
1211+
1212+ model: screens
1213+ QQW.Window {
1214+ id: window
1215+ visible: true
1216+ Shell{ anchors.fill: parent }
1217+ Component.onCompleted: {
1218+ print("HEY", screen, screen.geometry, outputType, Screens.HDMIA)
1219+ }
1220+ }
1221+}
1222
1223=== modified file 'src/common/debughelpers.cpp'
1224--- src/common/debughelpers.cpp 2015-08-27 16:10:20 +0000
1225+++ src/common/debughelpers.cpp 2015-10-13 19:42:45 +0000
1226@@ -223,7 +223,7 @@
1227 {
1228 const int pointerCount = mir_touch_event_point_count(event);
1229
1230- QString string("MirTouchInputEvent(");
1231+ QString string("MirTouchEvent(");
1232
1233 for (int i = 0; i < pointerCount; ++i) {
1234
1235@@ -265,3 +265,32 @@
1236 break;
1237 }
1238 }
1239+
1240+namespace {
1241+const char *mirKeyboardActionToString(MirKeyboardAction keyboardAction)
1242+{
1243+ switch (keyboardAction)
1244+ {
1245+ case mir_keyboard_action_up:
1246+ return "up";
1247+ case mir_keyboard_action_down:
1248+ return "down";
1249+ case mir_keyboard_action_repeat:
1250+ return "repeat";
1251+ default:
1252+ return "???";
1253+ break;
1254+ }
1255+}
1256+}
1257+
1258+QString mirKeyboardEventToString(MirKeyboardEvent const* event)
1259+{
1260+ MirKeyboardAction keyboardAction = mir_keyboard_event_action(event);
1261+
1262+ xkb_keysym_t keyCode = mir_keyboard_event_key_code(event);
1263+
1264+ return QString("MirKeyboardEvent(action=%1,key_code=0x%2)")
1265+ .arg(mirKeyboardActionToString(keyboardAction))
1266+ .arg(keyCode, 4, 16, QLatin1Char('0'));
1267+}
1268
1269=== modified file 'src/common/debughelpers.h'
1270--- src/common/debughelpers.h 2015-08-27 16:10:20 +0000
1271+++ src/common/debughelpers.h 2015-10-13 19:42:45 +0000
1272@@ -38,6 +38,7 @@
1273
1274 QString mirPointerEventToString(MirPointerEvent const* event);
1275 QString mirTouchEventToString(MirTouchEvent const* event);
1276+QString mirKeyboardEventToString(MirKeyboardEvent const* event);
1277 const char *mirTouchActionToString(MirTouchAction touchAction);
1278
1279 #endif // UBUNTUGESTURES_DEBUG_HELPER_H
1280
1281=== added file 'src/common/timestamp.cpp'
1282--- src/common/timestamp.cpp 1970-01-01 00:00:00 +0000
1283+++ src/common/timestamp.cpp 2015-10-13 19:42:45 +0000
1284@@ -0,0 +1,18 @@
1285+
1286+#include <chrono>
1287+#include "timestamp_impl.h"
1288+
1289+std::chrono::nanoseconds appStartTime(0);
1290+
1291+void resetStartTime(std::chrono::nanoseconds timestamp)
1292+{
1293+ appStartTime = timestamp;
1294+}
1295+
1296+std::chrono::nanoseconds getStartTime(std::chrono::nanoseconds timestamp, bool allowReset)
1297+{
1298+ if (allowReset && appStartTime.count() == 0) {
1299+ resetStartTime(timestamp);
1300+ }
1301+ return appStartTime;
1302+}
1303
1304=== added file 'src/common/timestamp.h'
1305--- src/common/timestamp.h 1970-01-01 00:00:00 +0000
1306+++ src/common/timestamp.h 2015-10-13 19:42:45 +0000
1307@@ -0,0 +1,39 @@
1308+/*
1309+ * Copyright (C) 2015 Canonical, Ltd.
1310+ *
1311+ * This program is free software: you can redistribute it and/or modify it under
1312+ * the terms of the GNU Lesser General Public License version 3, as published by
1313+ * the Free Software Foundation.
1314+ *
1315+ * This program is distributed in the hope that it will be useful, but WITHOUT
1316+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1317+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1318+ * Lesser General Public License for more details.
1319+ *
1320+ * You should have received a copy of the GNU Lesser General Public License
1321+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1322+ */
1323+
1324+#ifndef QTMIR_TIMESTAMP_H
1325+#define QTMIR_TIMESTAMP_H
1326+
1327+#include <QtCore/qglobal.h>
1328+#include <chrono>
1329+
1330+namespace qtmir {
1331+
1332+// Converts a mir timestamp (in nanoseconds) to and from a timestamp in milliseconds.
1333+// Qt system events only work with ulong timestamps. On 32bit archs a ulong is 4 bytes long, so the 64 bit nanoseconds
1334+// will be truncated and skewed. In order to fix this, we truncate the result by using time since "first call"
1335+template<typename T>
1336+T compressTimestamp(std::chrono::nanoseconds timestamp);
1337+
1338+ // "Re-inflate" a truncated timestamp.
1339+template<typename T>
1340+std::chrono::nanoseconds uncompressTimestamp(T timestamp);
1341+
1342+}
1343+
1344+#include "timestamp_impl.h"
1345+
1346+#endif // QTMIR_TIMESTAMP_H
1347
1348=== added file 'src/common/timestamp_impl.h'
1349--- src/common/timestamp_impl.h 1970-01-01 00:00:00 +0000
1350+++ src/common/timestamp_impl.h 2015-10-13 19:42:45 +0000
1351@@ -0,0 +1,34 @@
1352+#include <QCoreApplication>
1353+#include <QVariant>
1354+
1355+extern "C" {
1356+ void resetStartTime(std::chrono::nanoseconds timestamp);
1357+ std::chrono::nanoseconds getStartTime(std::chrono::nanoseconds timestamp, bool allowReset = true);
1358+}
1359+
1360+namespace qtmir {
1361+
1362+template<typename T>
1363+T compressTimestamp(std::chrono::nanoseconds timestamp)
1364+{
1365+ std::chrono::nanoseconds startTime = getStartTime(timestamp);
1366+ std::chrono::nanoseconds result = timestamp - startTime;
1367+
1368+ if (std::numeric_limits<std::chrono::nanoseconds::rep>::max() > std::numeric_limits<T>::max() &&
1369+ result > std::chrono::nanoseconds(std::numeric_limits<T>::max())) {
1370+ // we've overflowed the boundaries of the millisecond type.
1371+ resetStartTime(timestamp);
1372+ return 0;
1373+ }
1374+
1375+ return result.count();
1376+}
1377+
1378+template<typename T>
1379+std::chrono::nanoseconds uncompressTimestamp(T timestamp)
1380+{
1381+ auto tsNS = std::chrono::nanoseconds(timestamp);
1382+ return getStartTime(tsNS, false) + std::chrono::nanoseconds(tsNS);
1383+}
1384+
1385+}
1386\ No newline at end of file
1387
1388=== modified file 'src/modules/Unity/Application/CMakeLists.txt'
1389--- src/modules/Unity/Application/CMakeLists.txt 2015-09-17 16:17:17 +0000
1390+++ src/modules/Unity/Application/CMakeLists.txt 2015-10-13 19:42:45 +0000
1391@@ -92,4 +92,3 @@
1392 # install
1393 add_qml_plugin(Unity.Application 0.1 Unity/Application TARGETS unityapplicationplugin)
1394 install(FILES com.canonical.qtmir.gschema.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/glib-2.0/schemas)
1395-
1396
1397=== modified file 'src/modules/Unity/Application/mirbuffersgtexture.cpp'
1398--- src/modules/Unity/Application/mirbuffersgtexture.cpp 2015-09-17 16:46:37 +0000
1399+++ src/modules/Unity/Application/mirbuffersgtexture.cpp 2015-10-13 19:42:45 +0000
1400@@ -59,6 +59,11 @@
1401 m_width = size.width.as_int();
1402 }
1403
1404+bool MirBufferSGTexture::hasBuffer() const
1405+{
1406+ return !!m_mirBuffer;
1407+}
1408+
1409 int MirBufferSGTexture::textureId() const
1410 {
1411 return m_textureId;
1412@@ -71,12 +76,17 @@
1413
1414 bool MirBufferSGTexture::hasAlphaChannel() const
1415 {
1416- return m_mirBuffer->pixel_format() == mir_pixel_format_abgr_8888
1417- || m_mirBuffer->pixel_format() == mir_pixel_format_argb_8888;
1418+ if (hasBuffer()) {
1419+ return m_mirBuffer->pixel_format() == mir_pixel_format_abgr_8888
1420+ || m_mirBuffer->pixel_format() == mir_pixel_format_argb_8888;
1421+ } else {
1422+ return false;
1423+ }
1424 }
1425
1426 void MirBufferSGTexture::bind()
1427 {
1428+ Q_ASSERT(hasBuffer());
1429 glBindTexture(GL_TEXTURE_2D, m_textureId);
1430 updateBindOptions(true/* force */);
1431
1432
1433=== modified file 'src/modules/Unity/Application/mirbuffersgtexture.h'
1434--- src/modules/Unity/Application/mirbuffersgtexture.h 2015-08-31 09:51:28 +0000
1435+++ src/modules/Unity/Application/mirbuffersgtexture.h 2015-10-13 19:42:45 +0000
1436@@ -34,6 +34,7 @@
1437
1438 void setBuffer(std::shared_ptr<mir::graphics::Buffer> buffer);
1439 void freeBuffer();
1440+ bool hasBuffer() const;
1441
1442 int textureId() const override;
1443 QSize textureSize() const override;
1444
1445=== modified file 'src/modules/Unity/Application/mirsurface.cpp'
1446--- src/modules/Unity/Application/mirsurface.cpp 2015-09-18 16:33:06 +0000
1447+++ src/modules/Unity/Application/mirsurface.cpp 2015-10-13 19:42:45 +0000
1448@@ -15,6 +15,7 @@
1449 */
1450
1451 #include "mirsurface.h"
1452+#include "timestamp.h"
1453
1454 // mirserver
1455 #include <surfaceobserver.h>
1456@@ -49,18 +50,29 @@
1457 return m_mods;
1458 }
1459
1460+MirPointerButtons
1461+getMirButtonsFromQt(Qt::MouseButtons buttons)
1462+{
1463+ MirPointerButtons result = 0;
1464+ if (buttons & Qt::LeftButton)
1465+ result |= mir_pointer_button_primary;
1466+ if (buttons & Qt::RightButton)
1467+ result |= mir_pointer_button_secondary;
1468+ if (buttons & Qt::MiddleButton)
1469+ result |= mir_pointer_button_tertiary;
1470+ if (buttons & Qt::BackButton)
1471+ result |= mir_pointer_button_back;
1472+ if (buttons & Qt::ForwardButton)
1473+ result |= mir_pointer_button_forward;
1474+
1475+ return result;
1476+}
1477+
1478 mir::EventUPtr makeMirEvent(QMouseEvent *qtEvent, MirPointerAction action)
1479 {
1480- auto timestamp = std::chrono::milliseconds(qtEvent->timestamp());
1481+ auto timestamp = uncompressTimestamp<ulong>(qtEvent->timestamp());
1482 auto modifiers = getMirModifiersFromQt(qtEvent->modifiers());
1483-
1484- MirPointerButtons buttons = 0;
1485- if (qtEvent->buttons() & Qt::LeftButton)
1486- buttons |= mir_pointer_button_primary;
1487- if (qtEvent->buttons() & Qt::RightButton)
1488- buttons |= mir_pointer_button_secondary;
1489- if (qtEvent->buttons() & Qt::MidButton)
1490- buttons |= mir_pointer_button_tertiary;
1491+ auto buttons = getMirButtonsFromQt(qtEvent->buttons());
1492
1493 return mir::events::make_event(0 /*DeviceID */, timestamp, 0 /* mac */, modifiers, action,
1494 buttons, qtEvent->x(), qtEvent->y(), 0, 0, 0, 0);
1495@@ -68,7 +80,7 @@
1496
1497 mir::EventUPtr makeMirEvent(QHoverEvent *qtEvent, MirPointerAction action)
1498 {
1499- auto timestamp = std::chrono::milliseconds(qtEvent->timestamp());
1500+ auto timestamp = uncompressTimestamp<ulong>(qtEvent->timestamp());
1501
1502 MirPointerButtons buttons = 0;
1503
1504@@ -76,6 +88,18 @@
1505 buttons, qtEvent->posF().x(), qtEvent->posF().y(), 0, 0, 0, 0);
1506 }
1507
1508+mir::EventUPtr makeMirEvent(QWheelEvent *qtEvent)
1509+{
1510+ auto timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(qtEvent->timestamp()));
1511+ auto modifiers = getMirModifiersFromQt(qtEvent->modifiers());
1512+ auto buttons = getMirButtonsFromQt(qtEvent->buttons());
1513+
1514+ return mir::events::make_event(0 /*DeviceID */, timestamp, 0 /* mac */, modifiers, mir_pointer_action_motion,
1515+ buttons, qtEvent->x(), qtEvent->y(),
1516+ qtEvent->angleDelta().x(), qtEvent->angleDelta().y(),
1517+ 0, 0);
1518+}
1519+
1520 mir::EventUPtr makeMirEvent(QKeyEvent *qtEvent)
1521 {
1522 MirKeyboardAction action = mir_keyboard_action_down;
1523@@ -93,7 +117,7 @@
1524 if (qtEvent->isAutoRepeat())
1525 action = mir_keyboard_action_repeat;
1526
1527- return mir::events::make_event(0 /* DeviceID */, std::chrono::milliseconds(qtEvent->timestamp()),
1528+ return mir::events::make_event(0 /* DeviceID */, uncompressTimestamp<ulong>(qtEvent->timestamp()),
1529 0 /* mac */, action, qtEvent->nativeVirtualKey(),
1530 qtEvent->nativeScanCode(),
1531 qtEvent->nativeModifiers());
1532@@ -105,7 +129,7 @@
1533 ulong qtTimestamp)
1534 {
1535 auto modifiers = getMirModifiersFromQt(qmods);
1536- auto ev = mir::events::make_event(0, std::chrono::milliseconds(qtTimestamp),
1537+ auto ev = mir::events::make_event(0, uncompressTimestamp<ulong>(qtTimestamp),
1538 0 /* mac */, modifiers);
1539
1540 for (int i = 0; i < qtTouchPoints.count(); ++i) {
1541@@ -309,21 +333,23 @@
1542 }
1543 }
1544
1545-void MirSurface::updateTexture()
1546+bool MirSurface::updateTexture()
1547 {
1548 QMutexLocker locker(&m_mutex);
1549-
1550- if (m_textureUpdated) {
1551- return;
1552- }
1553-
1554 Q_ASSERT(!m_texture.isNull());
1555+
1556 MirBufferSGTexture *texture = static_cast<MirBufferSGTexture*>(m_texture.data());
1557
1558+ if (m_textureUpdated) {
1559+ return texture->hasBuffer();
1560+ }
1561+
1562 const void* const userId = (void*)123;
1563 auto renderables = m_surface->generate_renderables(userId);
1564
1565- if (m_surface->buffers_ready_for_compositor(userId) > 0 && renderables.size() > 0) {
1566+ if (renderables.size() > 0 &&
1567+ (m_surface->buffers_ready_for_compositor(userId) > 0 || !texture->hasBuffer())
1568+ ) {
1569 // Avoid holding two buffers for the compositor at the same time. Thus free the current
1570 // before acquiring the next
1571 texture->freeBuffer();
1572@@ -343,6 +369,8 @@
1573 }
1574
1575 m_textureUpdated = true;
1576+
1577+ return texture->hasBuffer();
1578 }
1579
1580 void MirSurface::onCompositorSwappedBuffers()
1581@@ -569,6 +597,13 @@
1582 event->accept();
1583 }
1584
1585+void MirSurface::wheelEvent(QWheelEvent *event)
1586+{
1587+ auto ev = makeMirEvent(event);
1588+ m_surface->consume(*ev);
1589+ event->accept();
1590+}
1591+
1592 void MirSurface::keyPressEvent(QKeyEvent *qtEvent)
1593 {
1594 auto ev = makeMirEvent(qtEvent);
1595
1596=== modified file 'src/modules/Unity/Application/mirsurface.h'
1597--- src/modules/Unity/Application/mirsurface.h 2015-09-25 17:30:26 +0000
1598+++ src/modules/Unity/Application/mirsurface.h 2015-10-13 19:42:45 +0000
1599@@ -87,7 +87,7 @@
1600 // methods called from the rendering (scene graph) thread:
1601 QSharedPointer<QSGTexture> texture() override;
1602 QSGTexture *weakTexture() const override { return m_texture.data(); }
1603- void updateTexture() override;
1604+ bool updateTexture() override;
1605 unsigned int currentFrameNumber() const override;
1606 bool numBuffersReadyForCompositor() override;
1607 // end of methods called from the rendering (scene graph) thread
1608@@ -100,6 +100,7 @@
1609 void hoverEnterEvent(QHoverEvent *event) override;
1610 void hoverLeaveEvent(QHoverEvent *event) override;
1611 void hoverMoveEvent(QHoverEvent *event) override;
1612+ void wheelEvent(QWheelEvent *event) override;
1613
1614 void keyPressEvent(QKeyEvent *event) override;
1615 void keyReleaseEvent(QKeyEvent *event) override;
1616
1617=== modified file 'src/modules/Unity/Application/mirsurfaceinterface.h'
1618--- src/modules/Unity/Application/mirsurfaceinterface.h 2015-09-25 17:30:26 +0000
1619+++ src/modules/Unity/Application/mirsurfaceinterface.h 2015-10-13 19:42:45 +0000
1620@@ -54,7 +54,7 @@
1621 // methods called from the rendering (scene graph) thread:
1622 virtual QSharedPointer<QSGTexture> texture() = 0;
1623 virtual QSGTexture *weakTexture() const = 0;
1624- virtual void updateTexture() = 0;
1625+ virtual bool updateTexture() = 0;
1626 virtual unsigned int currentFrameNumber() const = 0;
1627 virtual bool numBuffersReadyForCompositor() = 0;
1628 // end of methods called from the rendering (scene graph) thread
1629@@ -67,6 +67,7 @@
1630 virtual void hoverEnterEvent(QHoverEvent *event) = 0;
1631 virtual void hoverLeaveEvent(QHoverEvent *event) = 0;
1632 virtual void hoverMoveEvent(QHoverEvent *event) = 0;
1633+ virtual void wheelEvent(QWheelEvent *event) = 0;
1634
1635 virtual void keyPressEvent(QKeyEvent *event) = 0;
1636 virtual void keyReleaseEvent(QKeyEvent *event) = 0;
1637
1638=== modified file 'src/modules/Unity/Application/mirsurfaceitem.cpp'
1639--- src/modules/Unity/Application/mirsurfaceitem.cpp 2015-09-28 14:24:48 +0000
1640+++ src/modules/Unity/Application/mirsurfaceitem.cpp 2015-10-13 19:42:45 +0000
1641@@ -20,6 +20,8 @@
1642 #include "mirsurfaceitem.h"
1643 #include "logging.h"
1644 #include "ubuntukeyboardinfo.h"
1645+#include "tracepoints.h" // generated from tracepoints.tp
1646+#include "timestamp.h"
1647
1648 // common
1649 #include <debughelpers.h>
1650@@ -216,17 +218,15 @@
1651
1652 ensureTextureProvider();
1653
1654- m_surface->updateTexture();
1655+ if (!m_textureProvider->texture() || !m_surface->updateTexture()) {
1656+ delete oldNode;
1657+ return 0;
1658+ }
1659
1660 if (m_surface->numBuffersReadyForCompositor() > 0) {
1661 QTimer::singleShot(0, this, SLOT(update()));
1662 }
1663
1664- if (!m_textureProvider->texture()) {
1665- delete oldNode;
1666- return 0;
1667- }
1668-
1669 m_textureProvider->smooth = smooth();
1670
1671 QSGDefaultImageNode *node = static_cast<QSGDefaultImageNode*>(oldNode);
1672@@ -298,7 +298,11 @@
1673
1674 void MirSurfaceItem::wheelEvent(QWheelEvent *event)
1675 {
1676- Q_UNUSED(event);
1677+ if (m_consumesInput && m_surface && m_surface->live()) {
1678+ m_surface->wheelEvent(event);
1679+ } else {
1680+ event->ignore();
1681+ }
1682 }
1683
1684 void MirSurfaceItem::hoverEnterEvent(QHoverEvent *event)
1685@@ -411,10 +415,14 @@
1686 m_lastTouchEvent->timestamp = timestamp;
1687 m_lastTouchEvent->touchPoints = touchPoints;
1688 m_lastTouchEvent->touchPointStates = touchPointStates;
1689+
1690+ tracepoint(qtmir, touchEventConsume_end, uncompressTimestamp<ulong>(timestamp).count());
1691 }
1692
1693 void MirSurfaceItem::touchEvent(QTouchEvent *event)
1694 {
1695+ tracepoint(qtmir, touchEventConsume_start, uncompressTimestamp<ulong>(event->timestamp()).count());
1696+
1697 bool accepted = processTouchEvent(event->type(),
1698 event->timestamp(),
1699 event->modifiers(),
1700
1701=== modified file 'src/modules/Unity/Application/plugin.cpp'
1702--- src/modules/Unity/Application/plugin.cpp 2015-08-31 09:51:28 +0000
1703+++ src/modules/Unity/Application/plugin.cpp 2015-10-13 19:42:45 +0000
1704@@ -27,6 +27,9 @@
1705 #include "sessionmanager.h"
1706 #include "ubuntukeyboardinfo.h"
1707
1708+// platforms/mirserver
1709+#include <mirsingleton.h>
1710+
1711 // qtmir
1712 #include "logging.h"
1713
1714@@ -64,6 +67,10 @@
1715 }
1716 return UbuntuKeyboardInfo::instance();
1717 }
1718+
1719+QObject* mirSingleton(QQmlEngine* /*engine*/, QJSEngine* /*scriptEngine*/) {
1720+ return qtmir::Mir::instance();
1721+}
1722 } // anonymous namespace
1723
1724 class UnityApplicationPlugin : public QQmlExtensionPlugin {
1725@@ -102,7 +109,7 @@
1726 uri, 0, 1, "Session", "Session can't be instantiated from QML");
1727 qmlRegisterSingletonType<qtmir::UbuntuKeyboardInfo>(
1728 uri, 0, 1, "UbuntuKeyboardInfo", ubuntuKeyboardInfoSingleton);
1729- qmlRegisterUncreatableType<Mir>(uri, 0, 1, "Mir", "Mir provides enum values, it can't be instantiated");
1730+ qmlRegisterSingletonType<qtmir::Mir>(uri, 0, 1, "Mir", mirSingleton);
1731 }
1732
1733 virtual void initializeEngine(QQmlEngine *engine, const char *uri)
1734
1735=== modified file 'src/modules/Unity/Application/tracepoints.tp'
1736--- src/modules/Unity/Application/tracepoints.tp 2014-09-04 11:11:26 +0000
1737+++ src/modules/Unity/Application/tracepoints.tp 2015-10-13 19:42:45 +0000
1738@@ -1,3 +1,5 @@
1739+#include <stdint.h>
1740+
1741 TRACEPOINT_EVENT(qtmir, startApplication, TP_ARGS(0), TP_FIELDS())
1742 TRACEPOINT_EVENT(qtmir, onProcessStarting, TP_ARGS(0), TP_FIELDS())
1743 TRACEPOINT_EVENT(qtmir, authorizeSession, TP_ARGS(0), TP_FIELDS())
1744@@ -7,3 +9,6 @@
1745 TRACEPOINT_EVENT(qtmir, firstFrameDrawn, TP_ARGS(0), TP_FIELDS())
1746 TRACEPOINT_EVENT(qtmir, appIdHasProcessId_start, TP_ARGS(0), TP_FIELDS())
1747 TRACEPOINT_EVENT(qtmir, appIdHasProcessId_end, TP_ARGS(int, found), TP_FIELDS(ctf_integer(int, found, found)))
1748+
1749+TRACEPOINT_EVENT(qtmir, touchEventConsume_start, TP_ARGS(int64_t, event_time), TP_FIELDS(ctf_integer(int64_t, event_time, event_time)))
1750+TRACEPOINT_EVENT(qtmir, touchEventConsume_end, TP_ARGS(int64_t, event_time), TP_FIELDS(ctf_integer(int64_t, event_time, event_time)))
1751
1752=== modified file 'src/modules/Unity/CMakeLists.txt'
1753--- src/modules/Unity/CMakeLists.txt 2014-09-22 18:06:58 +0000
1754+++ src/modules/Unity/CMakeLists.txt 2015-10-13 19:42:45 +0000
1755@@ -1,1 +1,2 @@
1756 add_subdirectory(Application)
1757+add_subdirectory(Screens)
1758
1759=== added directory 'src/modules/Unity/Screens'
1760=== added file 'src/modules/Unity/Screens/CMakeLists.txt'
1761--- src/modules/Unity/Screens/CMakeLists.txt 1970-01-01 00:00:00 +0000
1762+++ src/modules/Unity/Screens/CMakeLists.txt 2015-10-13 19:42:45 +0000
1763@@ -0,0 +1,24 @@
1764+include_directories(
1765+ ${CMAKE_SOURCE_DIR}/src/platforms/mirserver
1766+ ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
1767+ ${MIRSERVER_INCLUDE_DIRS}
1768+ )
1769+
1770+set(SCREENSPLUGIN_SRC
1771+ plugin.cpp
1772+ screens.cpp
1773+ )
1774+
1775+add_library(unityscreensplugin SHARED
1776+ ${SCREENSPLUGIN_SRC}
1777+)
1778+
1779+target_link_libraries(
1780+ unityscreensplugin
1781+
1782+ Qt5::Gui
1783+ Qt5::Qml
1784+)
1785+
1786+# install
1787+add_qml_plugin(Unity.Screens 0.1 Unity/Screens TARGETS unityscreensplugin)
1788
1789=== added file 'src/modules/Unity/Screens/plugin.cpp'
1790--- src/modules/Unity/Screens/plugin.cpp 1970-01-01 00:00:00 +0000
1791+++ src/modules/Unity/Screens/plugin.cpp 2015-10-13 19:42:45 +0000
1792@@ -0,0 +1,41 @@
1793+/*
1794+ * Copyright (C) 2015 Canonical, Ltd.
1795+ *
1796+ * This program is free software: you can redistribute it and/or modify it under
1797+ * the terms of the GNU Lesser General Public License version 3, as published by
1798+ * the Free Software Foundation.
1799+ *
1800+ * This program is distributed in the hope that it will be useful, but WITHOUT
1801+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1802+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1803+ * Lesser General Public License for more details.
1804+ *
1805+ * You should have received a copy of the GNU Lesser General Public License
1806+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1807+ */
1808+
1809+// Qt
1810+#include <QQmlExtensionPlugin>
1811+#include <QtQml/qqml.h>
1812+#include <QScreen>
1813+
1814+// local
1815+#include "screens.h"
1816+
1817+using namespace qtmir;
1818+
1819+class UnityScreensPlugin : public QQmlExtensionPlugin {
1820+ Q_OBJECT
1821+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface/1.0")
1822+
1823+ virtual void registerTypes(const char* uri)
1824+ {
1825+ Q_ASSERT(QLatin1String(uri) == QLatin1String("Unity.Screens"));
1826+
1827+ qRegisterMetaType<QScreen*>("QScreen*");
1828+
1829+ qmlRegisterType<qtmir::Screens>(uri, 0, 1, "Screens");
1830+ }
1831+};
1832+
1833+#include "plugin.moc"
1834
1835=== added file 'src/modules/Unity/Screens/qmldir'
1836--- src/modules/Unity/Screens/qmldir 1970-01-01 00:00:00 +0000
1837+++ src/modules/Unity/Screens/qmldir 2015-10-13 19:42:45 +0000
1838@@ -0,0 +1,2 @@
1839+module Unity.Screens
1840+plugin unityscreensplugin
1841
1842=== added file 'src/modules/Unity/Screens/screens.cpp'
1843--- src/modules/Unity/Screens/screens.cpp 1970-01-01 00:00:00 +0000
1844+++ src/modules/Unity/Screens/screens.cpp 2015-10-13 19:42:45 +0000
1845@@ -0,0 +1,107 @@
1846+/*
1847+ * Copyright (C) 2015 Canonical, Ltd.
1848+ *
1849+ * This program is free software: you can redistribute it and/or modify it under
1850+ * the terms of the GNU Lesser General Public License version 3, as published by
1851+ * the Free Software Foundation.
1852+ *
1853+ * This program is distributed in the hope that it will be useful, but WITHOUT
1854+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1855+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1856+ * Lesser General Public License for more details.
1857+ *
1858+ * You should have received a copy of the GNU Lesser General Public License
1859+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1860+ */
1861+
1862+#include "screens.h"
1863+
1864+// mirserver
1865+#include "screen.h"
1866+
1867+// Qt
1868+#include <QGuiApplication>
1869+#include <QScreen>
1870+
1871+Q_DECLARE_METATYPE(QScreen*)
1872+
1873+namespace qtmir {
1874+
1875+Screens::Screens(QObject *parent) :
1876+ QAbstractListModel(parent)
1877+{
1878+ auto app = static_cast<QGuiApplication *>(QGuiApplication::instance());
1879+ if (!app) {
1880+ return;
1881+ }
1882+ connect(app, &QGuiApplication::screenAdded, this, &Screens::onScreenAdded);
1883+ connect(app, &QGuiApplication::screenRemoved, this, &Screens::onScreenRemoved);
1884+
1885+ m_screenList = QGuiApplication::screens();
1886+}
1887+
1888+QHash<int, QByteArray> Screens::roleNames() const
1889+{
1890+ QHash<int, QByteArray> roles;
1891+ roles[ScreenRole] = "screen";
1892+ roles[OutputTypeRole] = "outputType";
1893+ return roles;
1894+}
1895+
1896+QVariant Screens::data(const QModelIndex &index, int role) const
1897+{
1898+ if (!index.isValid() || index.row() >= m_screenList.size()) {
1899+ return QVariant();
1900+ }
1901+
1902+ switch(role) {
1903+ case ScreenRole:
1904+ return QVariant::fromValue(m_screenList.at(index.row()));
1905+ case OutputTypeRole:
1906+ auto screen = static_cast<Screen*>(m_screenList.at(index.row())->handle());
1907+ if (screen) {
1908+ return QVariant(static_cast<OutputTypes>(screen->outputType())); //FIXME: cheeky
1909+ } else
1910+ return OutputTypes::Unknown;
1911+ }
1912+
1913+ return QVariant();
1914+}
1915+
1916+int Screens::rowCount(const QModelIndex &) const
1917+{
1918+ return count();
1919+}
1920+
1921+int Screens::count() const
1922+{
1923+ return m_screenList.size();
1924+}
1925+
1926+void Screens::onScreenAdded(QScreen *screen)
1927+{
1928+ if (m_screenList.contains(screen))
1929+ return;
1930+
1931+ beginInsertRows(QModelIndex(), count(), count());
1932+ m_screenList.push_back(screen);
1933+ endInsertRows();
1934+ Q_EMIT screenAdded(screen);
1935+ Q_EMIT countChanged();
1936+}
1937+
1938+void Screens::onScreenRemoved(QScreen *screen)
1939+{
1940+ int index = m_screenList.indexOf(screen);
1941+ if (index < 0)
1942+ return;
1943+
1944+ beginRemoveRows(QModelIndex(), index, index);
1945+ m_screenList.removeAt(index);
1946+ endRemoveRows();
1947+ Q_EMIT screenRemoved(screen);
1948+ Q_EMIT countChanged();
1949+}
1950+
1951+
1952+} // namespace qtmir
1953
1954=== added file 'src/modules/Unity/Screens/screens.h'
1955--- src/modules/Unity/Screens/screens.h 1970-01-01 00:00:00 +0000
1956+++ src/modules/Unity/Screens/screens.h 2015-10-13 19:42:45 +0000
1957@@ -0,0 +1,82 @@
1958+/*
1959+ * Copyright (C) 2015 Canonical, Ltd.
1960+ *
1961+ * This program is free software: you can redistribute it and/or modify it under
1962+ * the terms of the GNU Lesser General Public License version 3, as published by
1963+ * the Free Software Foundation.
1964+ *
1965+ * This program is distributed in the hope that it will be useful, but WITHOUT
1966+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1967+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1968+ * Lesser General Public License for more details.
1969+ *
1970+ * You should have received a copy of the GNU Lesser General Public License
1971+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1972+ */
1973+
1974+#ifndef SCREENS_H
1975+#define SCREENS_H
1976+
1977+#include <QAbstractListModel>
1978+
1979+class QScreen;
1980+
1981+namespace qtmir {
1982+
1983+class Screens : public QAbstractListModel
1984+{
1985+ Q_OBJECT
1986+ Q_ENUMS(OutputTypes)
1987+
1988+ Q_PROPERTY(int count READ count NOTIFY countChanged)
1989+
1990+public:
1991+ enum ItemRoles {
1992+ ScreenRole = Qt::UserRole + 1,
1993+ OutputTypeRole
1994+ };
1995+
1996+ enum OutputTypes {
1997+ Unknown,
1998+ VGA,
1999+ DVII,
2000+ DVID,
2001+ DVIA,
2002+ Composite,
2003+ SVideo,
2004+ LVDS,
2005+ Component,
2006+ NinePinDIN,
2007+ DisplayPort,
2008+ HDMIA,
2009+ HDMIB,
2010+ TV,
2011+ EDP
2012+ };
2013+
2014+ explicit Screens(QObject *parent = 0);
2015+ virtual ~Screens() noexcept = default;
2016+
2017+ /* QAbstractItemModel */
2018+ QHash<int, QByteArray> roleNames() const override;
2019+ QVariant data(const QModelIndex &index, int role = ScreenRole) const override;
2020+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
2021+
2022+ int count() const;
2023+
2024+Q_SIGNALS:
2025+ void countChanged();
2026+ void screenAdded(QScreen *screen);
2027+ void screenRemoved(QScreen *screen);
2028+
2029+private Q_SLOTS:
2030+ void onScreenAdded(QScreen *screen);
2031+ void onScreenRemoved(QScreen *screen);
2032+
2033+private:
2034+ QList<QScreen *> m_screenList;
2035+};
2036+
2037+} // namespace qtmir
2038+
2039+#endif // SCREENS_H
2040
2041=== modified file 'src/platforms/mirserver/CMakeLists.txt'
2042--- src/platforms/mirserver/CMakeLists.txt 2015-08-11 19:25:04 +0000
2043+++ src/platforms/mirserver/CMakeLists.txt 2015-10-13 19:42:45 +0000
2044@@ -30,6 +30,7 @@
2045 ${QT5PLATFORM_SUPPORT_INCLUDE_DIRS}
2046 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
2047 ${QT5_PLATFORMSUPPORT_INCLUDE_DIRS}
2048+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
2049
2050 ${APPLICATION_API_INCLUDE_DIRS}
2051 )
2052@@ -41,7 +42,10 @@
2053
2054 set(MIRSERVER_QPA_PLUGIN_SRC
2055 ${CMAKE_SOURCE_DIR}/src/common/debughelpers.cpp
2056+ ${CMAKE_SOURCE_DIR}/src/common/timestamp.cpp
2057+ cursor.cpp
2058 mirwindowmanager.cpp
2059+ mirsingleton.cpp
2060 qteventfeeder.cpp
2061 plugin.cpp
2062 qmirserver.cpp
2063@@ -52,17 +56,21 @@
2064 promptsessionlistener.cpp
2065 mirserver.cpp
2066 mirserverstatuslistener.cpp
2067- display.cpp
2068 screen.cpp
2069- displaywindow.cpp
2070+ screencontroller.cpp
2071+ screenwindow.cpp
2072 mirserverintegration.cpp
2073 miropenglcontext.cpp
2074 nativeinterface.cpp
2075+ offscreensurface.cpp
2076 qtcompositor.cpp
2077 services.cpp
2078 ubuntutheme.cpp
2079 clipboard.cpp
2080+ tileddisplayconfigurationpolicy.cpp
2081 tracepoints.c
2082+# We need to run moc on these headers
2083+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
2084 )
2085
2086 add_library(qpa-mirserver SHARED
2087@@ -82,7 +90,7 @@
2088 ${QT5PLATFORM_SUPPORT_LDFLAGS}
2089 # TODO Qt5Platform support LDFLAGS dont provide actual required ldflags...
2090 # I found these were needed...perhaps there is some way to query qmake/qconfig?
2091- -lfreetype
2092+ -lfreetype
2093 ${GIO_LDFLAGS}
2094 ${FONTCONFIG_LDFLAGS}
2095
2096
2097=== added file 'src/platforms/mirserver/cursor.cpp'
2098--- src/platforms/mirserver/cursor.cpp 1970-01-01 00:00:00 +0000
2099+++ src/platforms/mirserver/cursor.cpp 2015-10-13 19:42:45 +0000
2100@@ -0,0 +1,152 @@
2101+/*
2102+ * Copyright (C) 2015 Canonical, Ltd.
2103+ *
2104+ * This program is free software: you can redistribute it and/or modify it under
2105+ * the terms of the GNU Lesser General Public License version 3, as published by
2106+ * the Free Software Foundation.
2107+ *
2108+ * This program is distributed in the hope that it will be useful, but WITHOUT
2109+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
2110+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
2111+ * Lesser General Public License for more details.
2112+ *
2113+ * You should have received a copy of the GNU Lesser General Public License
2114+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2115+ *
2116+ */
2117+
2118+#include "cursor.h"
2119+#include "logging.h"
2120+
2121+#include "mirsingleton.h"
2122+
2123+// Unity API
2124+#include <unity/shell/application/MirMousePointerInterface.h>
2125+
2126+using namespace qtmir;
2127+
2128+Cursor::Cursor()
2129+{
2130+ m_shapeToCursorName[Qt::ArrowCursor] = "left_ptr";
2131+ m_shapeToCursorName[Qt::UpArrowCursor] = "up_arrow";
2132+ m_shapeToCursorName[Qt::CrossCursor] = "cross";
2133+ m_shapeToCursorName[Qt::WaitCursor] = "watch";
2134+ m_shapeToCursorName[Qt::IBeamCursor] = "xterm";
2135+ m_shapeToCursorName[Qt::SizeVerCursor] = "size_ver";
2136+ m_shapeToCursorName[Qt::SizeHorCursor] = "size_hor";
2137+ m_shapeToCursorName[Qt::SizeBDiagCursor] = "size_bdiag";
2138+ m_shapeToCursorName[Qt::SizeFDiagCursor] = "size_fdiag";
2139+ m_shapeToCursorName[Qt::SizeAllCursor] = "size_all";
2140+ m_shapeToCursorName[Qt::BlankCursor] = "blank";
2141+ m_shapeToCursorName[Qt::SplitVCursor] = "split_v";
2142+ m_shapeToCursorName[Qt::SplitHCursor] = "split_h";
2143+ m_shapeToCursorName[Qt::PointingHandCursor] = "pointing_hand";
2144+ m_shapeToCursorName[Qt::ForbiddenCursor] = "forbidden";
2145+ m_shapeToCursorName[Qt::WhatsThisCursor] = "whats_this";
2146+ m_shapeToCursorName[Qt::BusyCursor] = "left_ptr_watch";
2147+ m_shapeToCursorName[Qt::OpenHandCursor] = "openhand";
2148+ m_shapeToCursorName[Qt::ClosedHandCursor] = "closedhand";
2149+ m_shapeToCursorName[Qt::DragCopyCursor] = "copy";
2150+ m_shapeToCursorName[Qt::DragMoveCursor] = "move";
2151+ m_shapeToCursorName[Qt::DragLinkCursor] = "link";
2152+
2153+ connect(Mir::instance(), &Mir::cursorNameChanged, this, &Cursor::setMirCursorName);
2154+}
2155+
2156+void Cursor::changeCursor(QCursor *windowCursor, QWindow * /*window*/)
2157+{
2158+ if (m_mousePointer.isNull()) {
2159+ return;
2160+ }
2161+
2162+ if (windowCursor) {
2163+ m_qtCursorName = m_shapeToCursorName.value(windowCursor->shape(), QString("left_ptr"));
2164+ } else {
2165+ m_qtCursorName.clear();
2166+ }
2167+
2168+ updateMousePointerCursorName();
2169+}
2170+
2171+void Cursor::setMirCursorName(const QString &mirCursorName)
2172+{
2173+ m_mirCursorName = mirCursorName;
2174+ updateMousePointerCursorName();
2175+}
2176+
2177+void Cursor::setMousePointer(MirMousePointerInterface *mousePointer)
2178+{
2179+ QMutexLocker locker(&m_mutex);
2180+
2181+ if (mousePointer && !m_mousePointer.isNull()) {
2182+ qFatal("QPA mirserver: Only one MousePointer per screen is allowed!");
2183+ }
2184+
2185+ m_mousePointer = mousePointer;
2186+ updateMousePointerCursorName();
2187+}
2188+
2189+bool Cursor::handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
2190+ Qt::KeyboardModifiers modifiers)
2191+{
2192+ QMutexLocker locker(&m_mutex);
2193+
2194+ if (!m_mousePointer || !m_mousePointer->isVisible()) {
2195+ return false;
2196+ }
2197+
2198+ // Must not be called directly as we're most likely not in Qt's GUI (main) thread.
2199+ bool ok = QMetaObject::invokeMethod(m_mousePointer, "handleMouseEvent", Qt::AutoConnection,
2200+ Q_ARG(ulong, timestamp),
2201+ Q_ARG(QPointF, movement),
2202+ Q_ARG(Qt::MouseButton, static_cast<Qt::MouseButton>((int)buttons)), // FIXME in MirMousePointerInterface!!!
2203+ Q_ARG(Qt::KeyboardModifiers, modifiers));
2204+
2205+ if (!ok) {
2206+ qCWarning(QTMIR_MIR_INPUT) << "Failed to invoke MousePointer::handleMouseEvent";
2207+ }
2208+
2209+ return ok;
2210+}
2211+
2212+void Cursor::setPos(const QPoint &pos)
2213+{
2214+ if (!m_mousePointer) {
2215+ QPlatformCursor::setPos(pos);
2216+ return;
2217+ }
2218+
2219+ QPointF movement;
2220+ QPointF mouseScenePos = m_mousePointer->mapToItem(nullptr, QPointF(0, 0));
2221+
2222+ movement.setX(pos.x() - mouseScenePos.x());
2223+ movement.setY(pos.y() - mouseScenePos.y());
2224+
2225+ m_mousePointer->handleMouseEvent(0 /*timestamp*/, movement, Qt::NoButton, Qt::NoModifier);
2226+}
2227+
2228+QPoint Cursor::pos() const
2229+{
2230+ if (m_mousePointer) {
2231+ return m_mousePointer->mapToItem(nullptr, QPointF(0, 0)).toPoint();
2232+ } else {
2233+ return QPlatformCursor::pos();
2234+ }
2235+}
2236+
2237+void Cursor::updateMousePointerCursorName()
2238+{
2239+ if (!m_mousePointer) {
2240+ return;
2241+ }
2242+
2243+ if (m_mirCursorName.isEmpty()) {
2244+ if (m_qtCursorName.isEmpty()) {
2245+ m_mousePointer->setCursorName("left_ptr");
2246+ } else {
2247+ m_mousePointer->setCursorName(m_qtCursorName);
2248+ }
2249+ } else {
2250+ m_mousePointer->setCursorName(m_mirCursorName);
2251+ }
2252+}
2253
2254=== added file 'src/platforms/mirserver/cursor.h'
2255--- src/platforms/mirserver/cursor.h 1970-01-01 00:00:00 +0000
2256+++ src/platforms/mirserver/cursor.h 2015-10-13 19:42:45 +0000
2257@@ -0,0 +1,66 @@
2258+/*
2259+ * Copyright (C) 2015 Canonical, Ltd.
2260+ *
2261+ * This program is free software: you can redistribute it and/or modify it under
2262+ * the terms of the GNU Lesser General Public License version 3, as published by
2263+ * the Free Software Foundation.
2264+ *
2265+ * This program is distributed in the hope that it will be useful, but WITHOUT
2266+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
2267+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
2268+ * Lesser General Public License for more details.
2269+ *
2270+ * You should have received a copy of the GNU Lesser General Public License
2271+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2272+ *
2273+ */
2274+
2275+#ifndef QTMIR_CURSOR_H
2276+#define QTMIR_CURSOR_H
2277+
2278+#include <QMutex>
2279+#include <QPointer>
2280+
2281+// Unity API
2282+#include <unity/shell/application/MirPlatformCursor.h>
2283+
2284+namespace qtmir {
2285+
2286+class Cursor : public MirPlatformCursor
2287+{
2288+public:
2289+ Cursor();
2290+
2291+ // Called form Mir input thread
2292+ bool handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
2293+ Qt::KeyboardModifiers modifiers);
2294+
2295+ ////
2296+ // MirPlatformCursor
2297+
2298+ // Called from Qt's GUI thread
2299+ void setMousePointer(MirMousePointerInterface *mousePointer) override;
2300+
2301+ ////
2302+ // QPlatformCursor
2303+
2304+ void changeCursor(QCursor *windowCursor, QWindow *window) override;
2305+
2306+ void setPos(const QPoint &pos) override;
2307+ QPoint pos() const override;
2308+
2309+private Q_SLOTS:
2310+ void setMirCursorName(const QString &mirCursorName);
2311+
2312+private:
2313+ void updateMousePointerCursorName();
2314+ QMutex m_mutex;
2315+ QPointer<MirMousePointerInterface> m_mousePointer;
2316+ QMap<int,QString> m_shapeToCursorName;
2317+ QString m_qtCursorName;
2318+ QString m_mirCursorName;
2319+};
2320+
2321+} // namespace qtmir
2322+
2323+#endif // QTMIR_CURSOR_H
2324
2325=== removed file 'src/platforms/mirserver/display.cpp'
2326--- src/platforms/mirserver/display.cpp 2015-08-11 12:08:32 +0000
2327+++ src/platforms/mirserver/display.cpp 1970-01-01 00:00:00 +0000
2328@@ -1,44 +0,0 @@
2329-/*
2330- * Copyright (C) 2013-2015 Canonical, Ltd.
2331- *
2332- * This program is free software: you can redistribute it and/or modify it under
2333- * the terms of the GNU Lesser General Public License version 3, as published by
2334- * the Free Software Foundation.
2335- *
2336- * This program is distributed in the hope that it will be useful, but WITHOUT
2337- * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
2338- * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
2339- * Lesser General Public License for more details.
2340- *
2341- * You should have received a copy of the GNU Lesser General Public License
2342- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2343- */
2344-
2345-#include "display.h"
2346-
2347-#include "screen.h"
2348-#include "mirserver.h"
2349-
2350-#include <mir/graphics/display.h>
2351-#include <mir/graphics/display_configuration.h>
2352-
2353-namespace mg = mir::graphics;
2354-
2355-// TODO: Listen for display changes and update the list accordingly
2356-
2357-Display::Display(const std::shared_ptr<mir::graphics::DisplayConfiguration> &displayConfig)
2358-{
2359- displayConfig->for_each_output([this](mg::DisplayConfigurationOutput const& output) {
2360- if (output.used) {
2361- auto screen = new Screen(output);
2362- m_screens.push_back(screen);
2363- }
2364- });
2365-}
2366-
2367-Display::~Display()
2368-{
2369- for (auto screen : m_screens)
2370- delete screen;
2371- m_screens.clear();
2372-}
2373
2374=== removed file 'src/platforms/mirserver/display.h'
2375--- src/platforms/mirserver/display.h 2015-08-11 12:08:32 +0000
2376+++ src/platforms/mirserver/display.h 1970-01-01 00:00:00 +0000
2377@@ -1,37 +0,0 @@
2378-/*
2379- * Copyright (C) 2013-2015 Canonical, Ltd.
2380- *
2381- * This program is free software: you can redistribute it and/or modify it under
2382- * the terms of the GNU Lesser General Public License version 3, as published by
2383- * the Free Software Foundation.
2384- *
2385- * This program is distributed in the hope that it will be useful, but WITHOUT
2386- * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
2387- * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
2388- * Lesser General Public License for more details.
2389- *
2390- * You should have received a copy of the GNU Lesser General Public License
2391- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2392- */
2393-
2394-#ifndef DISPLAY_H
2395-#define DISPLAY_H
2396-
2397-#include <qpa/qplatformscreen.h>
2398-#include <memory>
2399-
2400-namespace mir { namespace graphics { class DisplayConfiguration; }}
2401-
2402-class Display
2403-{
2404-public:
2405- Display(const std::shared_ptr<mir::graphics::DisplayConfiguration> &displayConfig);
2406- virtual ~Display();
2407-
2408- QList<QPlatformScreen *> screens() const { return m_screens; }
2409-
2410-private:
2411- QList<QPlatformScreen *> m_screens;
2412-};
2413-
2414-#endif // DISPLAY_H
2415
2416=== modified file 'src/platforms/mirserver/logging.h'
2417--- src/platforms/mirserver/logging.h 2014-10-01 18:42:26 +0000
2418+++ src/platforms/mirserver/logging.h 2015-10-13 19:42:45 +0000
2419@@ -25,5 +25,6 @@
2420 Q_DECLARE_LOGGING_CATEGORY(QTMIR_SENSOR_MESSAGES)
2421 Q_DECLARE_LOGGING_CATEGORY(QTMIR_MIR_INPUT)
2422 Q_DECLARE_LOGGING_CATEGORY(QTMIR_CLIPBOARD)
2423+Q_DECLARE_LOGGING_CATEGORY(QTMIR_SCREENS)
2424
2425 #endif // UBUNTU_APPLICATION_PLUGIN_LOGGING_H
2426
2427=== modified file 'src/platforms/mirserver/miropenglcontext.cpp'
2428--- src/platforms/mirserver/miropenglcontext.cpp 2015-08-11 12:08:32 +0000
2429+++ src/platforms/mirserver/miropenglcontext.cpp 2015-10-13 19:42:45 +0000
2430@@ -16,12 +16,14 @@
2431
2432 #include "miropenglcontext.h"
2433
2434-#include "displaywindow.h"
2435+#include "offscreensurface.h"
2436+#include "mirglconfig.h"
2437 #include "mirserver.h"
2438-#include "mirglconfig.h"
2439+#include "screenwindow.h"
2440
2441 #include <QDebug>
2442
2443+#include <QOpenGLFramebufferObject>
2444 #include <QSurfaceFormat>
2445 #include <QtPlatformSupport/private/qeglconvenience_p.h>
2446
2447@@ -38,7 +40,7 @@
2448 : m_logger(new QOpenGLDebugLogger(this))
2449 #endif
2450 {
2451- std::shared_ptr<mir::graphics::Display> display = server->the_display();
2452+ auto display = server->the_display();
2453
2454 // create a temporary GL context to fetch the EGL display and config, so Qt can determine the surface format
2455 std::unique_ptr<mir::graphics::GLContext> mirContext = display->create_gl_context();
2456@@ -106,17 +108,30 @@
2457
2458 void MirOpenGLContext::swapBuffers(QPlatformSurface *surface)
2459 {
2460- // ultimately calls Mir's DisplayBuffer::post_update()
2461- DisplayWindow *displayBuffer = static_cast<DisplayWindow*>(surface);
2462- displayBuffer->swapBuffers(); //blocks for vsync
2463+ if (surface->surface()->surfaceClass() == QSurface::Offscreen) {
2464+ // NOOP
2465+ } else {
2466+ // ultimately calls Mir's DisplayBuffer::post_update()
2467+ ScreenWindow *screenWindow = static_cast<ScreenWindow*>(surface);
2468+ screenWindow->swapBuffers(); //blocks for vsync
2469+ }
2470 }
2471
2472 bool MirOpenGLContext::makeCurrent(QPlatformSurface *surface)
2473 {
2474+ if (surface->surface()->surfaceClass() == QSurface::Offscreen) {
2475+ auto offscreen = static_cast<OffscreenSurface *>(surface);
2476+ if (!offscreen->buffer()) {
2477+ auto buffer = new QOpenGLFramebufferObject(surface->surface()->size());
2478+ offscreen->setBuffer(buffer);
2479+ }
2480+ return offscreen->buffer()->bind();
2481+ }
2482+
2483 // ultimately calls Mir's DisplayBuffer::make_current()
2484- DisplayWindow *displayBuffer = static_cast<DisplayWindow*>(surface);
2485- if (displayBuffer) {
2486- displayBuffer->makeCurrent();
2487+ ScreenWindow *screenWindow = static_cast<ScreenWindow*>(surface);
2488+ if (screenWindow) {
2489+ screenWindow->makeCurrent();
2490
2491 #ifndef QT_NO_DEBUG
2492 if (!m_logger->isLogging() && m_logger->initialize()) {
2493@@ -133,7 +148,7 @@
2494
2495 void MirOpenGLContext::doneCurrent()
2496 {
2497- // could call Mir's DisplayBuffer::release_current(), but for what DisplayBuffer?
2498+ // FIXME: create a temporary GL context just to release? Would be better to get existing one.
2499 }
2500
2501 QFunctionPointer MirOpenGLContext::getProcAddress(const QByteArray &procName)
2502
2503=== modified file 'src/platforms/mirserver/miropenglcontext.h'
2504--- src/platforms/mirserver/miropenglcontext.h 2015-08-11 12:08:32 +0000
2505+++ src/platforms/mirserver/miropenglcontext.h 2015-10-13 19:42:45 +0000
2506@@ -23,6 +23,7 @@
2507 #include <QOpenGLDebugLogger>
2508 #endif
2509
2510+
2511 class MirServer;
2512
2513 class MirOpenGLContext : public QObject, public QPlatformOpenGLContext
2514
2515=== modified file 'src/platforms/mirserver/mirserver.cpp'
2516--- src/platforms/mirserver/mirserver.cpp 2015-08-11 12:08:32 +0000
2517+++ src/platforms/mirserver/mirserver.cpp 2015-10-13 19:42:45 +0000
2518@@ -23,15 +23,25 @@
2519 #include "mirglconfig.h"
2520 #include "mirserverstatuslistener.h"
2521 #include "promptsessionlistener.h"
2522+#include "screencontroller.h"
2523 #include "sessionlistener.h"
2524 #include "sessionauthorizer.h"
2525 #include "qtcompositor.h"
2526 #include "qteventfeeder.h"
2527+#include "tileddisplayconfigurationpolicy.h"
2528 #include "logging.h"
2529
2530+// std
2531+#include <memory>
2532+
2533 // egl
2534+#define MESA_EGL_NO_X11_HEADERS
2535 #include <EGL/egl.h>
2536
2537+// mir
2538+#include <mir/graphics/cursor.h>
2539+
2540+namespace mg = mir::graphics;
2541 namespace mo = mir::options;
2542 namespace msh = mir::shell;
2543 namespace ms = mir::scene;
2544@@ -45,8 +55,10 @@
2545
2546 Q_LOGGING_CATEGORY(QTMIR_MIR_MESSAGES, "qtmir.mir")
2547
2548-MirServer::MirServer(int argc, char const* argv[], QObject* parent)
2549+MirServer::MirServer(int argc, char const* argv[],
2550+ const QSharedPointer<ScreenController> &screenController, QObject* parent)
2551 : QObject(parent)
2552+ , m_screenController(screenController)
2553 {
2554 set_command_line_handler(&ignore_unparsed_arguments);
2555 set_command_line(argc, argv);
2556@@ -71,9 +83,9 @@
2557 return std::make_shared<QtCompositor>();
2558 });
2559
2560- override_the_input_dispatcher([]
2561+ override_the_input_dispatcher([&screenController]
2562 {
2563- return std::make_shared<QtEventFeeder>();
2564+ return std::make_shared<QtEventFeeder>(screenController);
2565 });
2566
2567 override_the_gl_config([]
2568@@ -92,17 +104,45 @@
2569 return std::make_shared<MirWindowManager>(the_shell_display_layout());
2570 });
2571
2572- set_terminator([&](int)
2573+ wrap_display_configuration_policy(
2574+ [](const std::shared_ptr<mg::DisplayConfigurationPolicy> &wrapped)
2575+ -> std::shared_ptr<mg::DisplayConfigurationPolicy>
2576+ {
2577+ return std::make_shared<TiledDisplayConfigurationPolicy>(wrapped);
2578+ });
2579+
2580+ set_terminator([](int)
2581 {
2582 qDebug() << "Signal caught by Mir, stopping Mir server..";
2583 QCoreApplication::quit();
2584 });
2585
2586+ add_init_callback([this, &screenController] {
2587+ screenController->init(the_display(), the_compositor());
2588+ });
2589+
2590 apply_settings();
2591
2592+ // We will draw our own cursor.
2593+ // FIXME: Call override_the_cusor() instead once this method becomes available in a
2594+ // future version of Mir.
2595+ add_init_callback([this]() {
2596+ the_cursor()->hide();
2597+ // Hack to work around https://bugs.launchpad.net/mir/+bug/1502200
2598+ static_cast<QtCompositor*>(the_compositor().get())->setCursor(the_cursor());
2599+ });
2600+
2601 qCDebug(QTMIR_MIR_MESSAGES) << "MirServer created";
2602 }
2603
2604+// Override default implementation to ensure we terminate the ScreenController first.
2605+// Code path followed when Qt tries to shutdown the server.
2606+void MirServer::stop()
2607+{
2608+ m_screenController->terminate();
2609+ mir::Server::stop();
2610+}
2611+
2612
2613 /************************************ Shell side ************************************/
2614
2615
2616=== modified file 'src/platforms/mirserver/mirserver.h'
2617--- src/platforms/mirserver/mirserver.h 2015-08-11 12:08:32 +0000
2618+++ src/platforms/mirserver/mirserver.h 2015-10-13 19:42:45 +0000
2619@@ -18,6 +18,7 @@
2620 #define MIRSERVER_H
2621
2622 #include <QObject>
2623+#include <QSharedPointer>
2624 #include <mir/server.h>
2625
2626 class QtEventFeeder;
2627@@ -25,6 +26,7 @@
2628 class SessionAuthorizer;
2629 using MirShell = mir::shell::Shell;
2630 class PromptSessionListener;
2631+class ScreenController;
2632
2633 // We use virtual inheritance of mir::Server to facilitate derived classes (e.g. testing)
2634 // calling initialization functions before MirServer is constructed.
2635@@ -38,12 +40,12 @@
2636 Q_PROPERTY(PromptSessionListener* promptSessionListener READ promptSessionListener CONSTANT)
2637
2638 public:
2639- MirServer(int argc, char const* argv[], QObject* parent = 0);
2640+ MirServer(int argc, char const* argv[], const QSharedPointer<ScreenController> &, QObject* parent = 0);
2641 ~MirServer() = default;
2642
2643 /* mir specific */
2644 using mir::Server::run;
2645- using mir::Server::stop;
2646+ using mir::Server::the_compositor;
2647 using mir::Server::the_display;
2648 using mir::Server::the_gl_config;
2649 using mir::Server::the_main_loop;
2650@@ -52,6 +54,8 @@
2651 using mir::Server::the_session_authorizer;
2652 using mir::Server::the_session_listener;
2653
2654+ void stop();
2655+
2656 /* qt specific */
2657 // getters
2658 SessionAuthorizer *sessionAuthorizer();
2659@@ -60,7 +64,9 @@
2660 MirShell *shell();
2661
2662 private:
2663+ std::weak_ptr<MirShell> m_shell;
2664 std::shared_ptr<QtEventFeeder> m_qtEventFeeder;
2665+ const QSharedPointer<ScreenController> m_screenController;
2666 };
2667
2668 #endif // MIRSERVER_H
2669
2670=== modified file 'src/platforms/mirserver/mirserverintegration.cpp'
2671--- src/platforms/mirserver/mirserverintegration.cpp 2015-08-11 12:08:32 +0000
2672+++ src/platforms/mirserver/mirserverintegration.cpp 2015-10-13 19:42:45 +0000
2673@@ -26,7 +26,8 @@
2674 #include <qpa/qplatforminputcontextfactory_p.h>
2675 #include <qpa/qwindowsysteminterface.h>
2676
2677-#include <QCoreApplication>
2678+#include <QGuiApplication>
2679+#include <QStringList>
2680 #include <QOpenGLContext>
2681 #include <QDebug>
2682
2683@@ -36,13 +37,16 @@
2684
2685 // local
2686 #include "clipboard.h"
2687-#include "display.h"
2688-#include "displaywindow.h"
2689 #include "miropenglcontext.h"
2690 #include "nativeinterface.h"
2691+#include "offscreensurface.h"
2692 #include "qmirserver.h"
2693+#include "screen.h"
2694+#include "screencontroller.h"
2695+#include "screenwindow.h"
2696 #include "services.h"
2697 #include "ubuntutheme.h"
2698+#include "logging.h"
2699
2700 namespace mg = mir::graphics;
2701 using qtmir::Clipboard;
2702@@ -52,7 +56,6 @@
2703 , m_fontDb(new QGenericUnixFontDatabase())
2704 , m_services(new Services)
2705 , m_mirServer(new QMirServer(QCoreApplication::arguments()))
2706- , m_display(nullptr)
2707 , m_nativeInterface(nullptr)
2708 , m_clipboard(new Clipboard)
2709 {
2710@@ -72,12 +75,14 @@
2711 QCoreApplication::instance(), &QCoreApplication::quit);
2712
2713 m_inputContext = QPlatformInputContextFactory::create();
2714+
2715+ // Default Qt behaviour doesn't match a shell's intentions, so customize:
2716+ qGuiApp->setQuitOnLastWindowClosed(false);
2717 }
2718
2719 MirServerIntegration::~MirServerIntegration()
2720 {
2721 delete m_nativeInterface;
2722- delete m_display;
2723 }
2724
2725 bool MirServerIntegration::hasCapability(QPlatformIntegration::Capability cap) const
2726@@ -87,7 +92,7 @@
2727 case OpenGL: return true;
2728 case ThreadedOpenGL: return true;
2729 case BufferQueueingOpenGL: return true;
2730- case MultipleWindows: return false; // multi-monitor support
2731+ case MultipleWindows: return true; // multi-monitor support
2732 case WindowManagement: return false; // platform has no WM, as this implements the WM!
2733 case NonFullScreenWindows: return false;
2734 default: return QPlatformIntegration::hasCapability(cap);
2735@@ -98,44 +103,30 @@
2736 {
2737 QWindowSystemInterface::flushWindowSystemEvents();
2738
2739- DisplayWindow* displayWindow = nullptr;
2740-
2741- auto const mirServer = m_mirServer->mirServer().lock();
2742- mg::DisplayBuffer* first_buffer{nullptr};
2743- mg::DisplaySyncGroup* first_group{nullptr};
2744- if (mirServer) {
2745- mirServer->the_display()->for_each_display_sync_group([&](mg::DisplaySyncGroup &group) {
2746- if (!first_group) {
2747- first_group = &group;
2748- }
2749- group.for_each_display_buffer([&](mg::DisplayBuffer &buffer) {
2750- if (!first_buffer) {
2751- first_buffer = &buffer;
2752- }
2753- });
2754- });
2755- }
2756-
2757- // FIXME(gerry) this will go very bad for >1 display buffer
2758- if (first_group && first_buffer)
2759- displayWindow = new DisplayWindow(window, first_group, first_buffer);
2760-
2761- if (!displayWindow)
2762+ auto screens = m_mirServer->screenController().lock();
2763+ if (!screens) {
2764+ qCritical("Screens are not initialized, unable to create a new QWindow/ScreenWindow");
2765 return nullptr;
2766-
2767- //displayWindow->requestActivateWindow();
2768- return displayWindow;
2769+ }
2770+
2771+ auto platformWindow = new ScreenWindow(window);
2772+ if (screens->compositing()) {
2773+ platformWindow->setExposed(true);
2774+ }
2775+
2776+ qCDebug(QTMIR_SCREENS) << "QWindow" << window << "with geom" << window->geometry()
2777+ << "is backed by a" << static_cast<Screen *>(window->screen()->handle())
2778+ << "with geometry" << window->screen()->geometry();
2779+ return platformWindow;
2780 }
2781
2782-QPlatformBackingStore *MirServerIntegration::createPlatformBackingStore(QWindow *window) const
2783+QPlatformBackingStore *MirServerIntegration::createPlatformBackingStore(QWindow */*window*/) const
2784 {
2785- qDebug() << "createPlatformBackingStore" << window;
2786 return nullptr;
2787 }
2788
2789 QPlatformOpenGLContext *MirServerIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
2790 {
2791- qDebug() << "createPlatformOpenGLContext" << context;
2792 return new MirOpenGLContext(m_mirServer->mirServer(), context->format());
2793 }
2794
2795@@ -151,12 +142,18 @@
2796 exit(2);
2797 }
2798
2799- m_display = new Display(m_mirServer->mirServer().data()->the_display()->configuration());
2800+ auto screens = m_mirServer->screenController().lock();
2801+ if (!screens) {
2802+ qFatal("ScreenController not initialized");
2803+ }
2804+ QObject::connect(screens.data(), &ScreenController::screenAdded,
2805+ [this](Screen *screen) { this->screenAdded(screen); });
2806+ Q_FOREACH(auto screen, screens->screens()) {
2807+ screenAdded(screen);
2808+ }
2809+
2810 m_nativeInterface = new NativeInterface(m_mirServer->mirServer());
2811
2812- for (QPlatformScreen *screen : m_display->screens())
2813- screenAdded(screen);
2814-
2815 m_clipboard->setupDBusService();
2816 }
2817
2818@@ -195,3 +192,9 @@
2819 {
2820 return m_clipboard.data();
2821 }
2822+
2823+QPlatformOffscreenSurface *MirServerIntegration::createPlatformOffscreenSurface(
2824+ QOffscreenSurface *surface) const
2825+{
2826+ return new OffscreenSurface(surface);
2827+}
2828
2829=== modified file 'src/platforms/mirserver/mirserverintegration.h'
2830--- src/platforms/mirserver/mirserverintegration.h 2015-08-11 12:08:32 +0000
2831+++ src/platforms/mirserver/mirserverintegration.h 2015-10-13 19:42:45 +0000
2832@@ -19,13 +19,9 @@
2833
2834 // qt
2835 #include <qpa/qplatformintegration.h>
2836-
2837-// local
2838-#include "mirserver.h"
2839-
2840-class Display;
2841+#include <QScopedPointer>
2842+
2843 class NativeInterface;
2844-class MirServer;
2845 class QMirServer;
2846
2847 namespace qtmir {
2848@@ -60,6 +56,8 @@
2849
2850 QPlatformNativeInterface *nativeInterface() const override;
2851
2852+ QPlatformOffscreenSurface *createPlatformOffscreenSurface(QOffscreenSurface *surface) const override;
2853+
2854 private:
2855 QScopedPointer<QPlatformAccessibility> m_accessibility;
2856 QScopedPointer<QPlatformFontDatabase> m_fontDb;
2857@@ -67,7 +65,6 @@
2858
2859 QScopedPointer<QMirServer> m_mirServer;
2860
2861- Display *m_display;
2862 NativeInterface *m_nativeInterface;
2863 QPlatformInputContext* m_inputContext;
2864 QScopedPointer<qtmir::Clipboard> m_clipboard;
2865
2866=== added file 'src/platforms/mirserver/mirsingleton.cpp'
2867--- src/platforms/mirserver/mirsingleton.cpp 1970-01-01 00:00:00 +0000
2868+++ src/platforms/mirserver/mirsingleton.cpp 2015-10-13 19:42:45 +0000
2869@@ -0,0 +1,33 @@
2870+#include "mirsingleton.h"
2871+
2872+qtmir::Mir *qtmir::Mir::m_instance = nullptr;
2873+
2874+qtmir::Mir::Mir()
2875+{
2876+}
2877+
2878+qtmir::Mir::~Mir()
2879+{
2880+ m_instance = nullptr;
2881+}
2882+
2883+qtmir::Mir *qtmir::Mir::instance()
2884+{
2885+ if (!m_instance) {
2886+ m_instance = new qtmir::Mir;
2887+ }
2888+ return m_instance;
2889+}
2890+
2891+void qtmir::Mir::setCursorName(const QString &cursorName)
2892+{
2893+ if (m_cursorName != cursorName) {
2894+ m_cursorName = cursorName;
2895+ Q_EMIT cursorNameChanged(m_cursorName);
2896+ }
2897+}
2898+
2899+QString qtmir::Mir::cursorName() const
2900+{
2901+ return m_cursorName;
2902+}
2903
2904=== added file 'src/platforms/mirserver/mirsingleton.h'
2905--- src/platforms/mirserver/mirsingleton.h 1970-01-01 00:00:00 +0000
2906+++ src/platforms/mirserver/mirsingleton.h 2015-10-13 19:42:45 +0000
2907@@ -0,0 +1,46 @@
2908+/*
2909+ * Copyright (C) 2015 Canonical, Ltd.
2910+ *
2911+ * This program is free software: you can redistribute it and/or modify it under
2912+ * the terms of the GNU Lesser General Public License version 3, as published by
2913+ * the Free Software Foundation.
2914+ *
2915+ * This program is distributed in the hope that it will be useful, but WITHOUT
2916+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
2917+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
2918+ * Lesser General Public License for more details.
2919+ *
2920+ * You should have received a copy of the GNU Lesser General Public License
2921+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2922+ */
2923+
2924+#ifndef QTMIR_MIRSINGLETON_H
2925+#define QTMIR_MIRSINGLETON_H
2926+
2927+// unity-api
2928+#include <unity/shell/application/Mir.h>
2929+
2930+namespace qtmir {
2931+
2932+class Mir : public ::Mir
2933+{
2934+ Q_OBJECT
2935+public:
2936+ virtual ~Mir();
2937+
2938+ static Mir *instance();
2939+
2940+ void setCursorName(const QString &cursorName) override;
2941+ QString cursorName() const override;
2942+
2943+private:
2944+ Mir();
2945+ Q_DISABLE_COPY(Mir)
2946+
2947+ QString m_cursorName;
2948+ static qtmir::Mir *m_instance;
2949+};
2950+
2951+} // namespace qtmir
2952+
2953+#endif // QTMIR_MIRSINGLETON_H
2954
2955=== added file 'src/platforms/mirserver/offscreensurface.cpp'
2956--- src/platforms/mirserver/offscreensurface.cpp 1970-01-01 00:00:00 +0000
2957+++ src/platforms/mirserver/offscreensurface.cpp 2015-10-13 19:42:45 +0000
2958@@ -0,0 +1,61 @@
2959+/*
2960+ * Copyright (C) 2015 Canonical, Ltd.
2961+ *
2962+ * This program is free software: you can redistribute it and/or modify it under
2963+ * the terms of the GNU Lesser General Public License version 3, as published by
2964+ * the Free Software Foundation.
2965+ *
2966+ * This program is distributed in the hope that it will be useful, but WITHOUT
2967+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
2968+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
2969+ * Lesser General Public License for more details.
2970+ *
2971+ * You should have received a copy of the GNU Lesser General Public License
2972+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2973+ */
2974+
2975+#include "offscreensurface.h"
2976+
2977+#include "mirserver.h"
2978+
2979+// Mir
2980+#include <mir/graphics/display.h>
2981+#include <mir/graphics/gl_context.h>
2982+
2983+//Qt
2984+#include <QOffscreenSurface>
2985+#include <QOpenGLFramebufferObject>
2986+#include <QSurfaceFormat>
2987+#include <QtPlatformSupport/private/qeglconvenience_p.h>
2988+
2989+namespace mg = mir::graphics;
2990+
2991+OffscreenSurface::OffscreenSurface(QOffscreenSurface *offscreenSurface)
2992+ : QPlatformOffscreenSurface(offscreenSurface)
2993+ , m_buffer(nullptr)
2994+ , m_format(offscreenSurface->requestedFormat())
2995+{
2996+}
2997+
2998+QSurfaceFormat OffscreenSurface::format() const
2999+{
3000+ return m_format;
3001+}
3002+
3003+bool OffscreenSurface::isValid() const
3004+{
3005+ if (m_buffer) {
3006+ return m_buffer->isValid();
3007+ }
3008+ return false;
3009+}
3010+
3011+QOpenGLFramebufferObject* OffscreenSurface::buffer() const
3012+{
3013+ return m_buffer;
3014+}
3015+
3016+void OffscreenSurface::setBuffer(QOpenGLFramebufferObject *buffer)
3017+{
3018+ m_buffer = buffer;
3019+}
3020
3021=== added file 'src/platforms/mirserver/offscreensurface.h'
3022--- src/platforms/mirserver/offscreensurface.h 1970-01-01 00:00:00 +0000
3023+++ src/platforms/mirserver/offscreensurface.h 2015-10-13 19:42:45 +0000
3024@@ -0,0 +1,43 @@
3025+/*
3026+ * Copyright (C) 2015 Canonical, Ltd.
3027+ *
3028+ * This program is free software: you can redistribute it and/or modify it under
3029+ * the terms of the GNU Lesser General Public License version 3, as published by
3030+ * the Free Software Foundation.
3031+ *
3032+ * This program is distributed in the hope that it will be useful, but WITHOUT
3033+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
3034+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3035+ * Lesser General Public License for more details.
3036+ *
3037+ * You should have received a copy of the GNU Lesser General Public License
3038+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3039+ */
3040+
3041+#ifndef OFFSCREENSURFACE_H
3042+#define OFFSCREENSURFACE_H
3043+
3044+#include <qpa/qplatformoffscreensurface.h>
3045+#include <QSurfaceFormat>
3046+#include <QSharedPointer>
3047+
3048+class MirServer;
3049+class QOpenGLFramebufferObject;
3050+
3051+class OffscreenSurface : public QPlatformOffscreenSurface
3052+{
3053+public:
3054+ OffscreenSurface(QOffscreenSurface *offscreenSurface);
3055+
3056+ QSurfaceFormat format() const override;
3057+ bool isValid() const override;
3058+
3059+ QOpenGLFramebufferObject* buffer() const;
3060+ void setBuffer(QOpenGLFramebufferObject *buffer);
3061+
3062+private:
3063+ QOpenGLFramebufferObject *m_buffer;
3064+ QSurfaceFormat m_format;
3065+};
3066+
3067+#endif // OFFSCREENSURFACE_H
3068
3069=== modified file 'src/platforms/mirserver/qmirserver.cpp'
3070--- src/platforms/mirserver/qmirserver.cpp 2015-05-19 15:36:17 +0000
3071+++ src/platforms/mirserver/qmirserver.cpp 2015-10-13 19:42:45 +0000
3072@@ -23,7 +23,8 @@
3073 #include "mirserver.h"
3074 #include "qmirserver.h"
3075 #include "qmirserver_p.h"
3076-
3077+#include "screencontroller.h"
3078+#include "screen.h"
3079
3080 QMirServer::QMirServer(const QStringList &arguments, QObject *parent)
3081 : QObject(parent)
3082@@ -40,7 +41,9 @@
3083 }
3084 argv[argc] = '\0';
3085
3086- d->server = QSharedPointer<MirServer>(new MirServer(argc, const_cast<const char**>(argv)));
3087+ d->screenController = QSharedPointer<ScreenController>(new ScreenController());
3088+
3089+ d->server = QSharedPointer<MirServer>(new MirServer(argc, const_cast<const char**>(argv), d->screenController));
3090
3091 d->serverThread = new MirServerThread(d->server);
3092
3093@@ -63,6 +66,7 @@
3094 qCritical() << "ERROR: QMirServer - Mir failed to start";
3095 return false;
3096 }
3097+ d->screenController->update();
3098
3099 Q_EMIT started();
3100 return true;
3101@@ -93,3 +97,9 @@
3102 Q_D(const QMirServer);
3103 return d->server.toWeakRef();
3104 }
3105+
3106+QWeakPointer<ScreenController> QMirServer::screenController() const
3107+{
3108+ Q_D(const QMirServer);
3109+ return d->screenController;
3110+}
3111
3112=== modified file 'src/platforms/mirserver/qmirserver.h'
3113--- src/platforms/mirserver/qmirserver.h 2015-05-18 20:39:09 +0000
3114+++ src/platforms/mirserver/qmirserver.h 2015-10-13 19:42:45 +0000
3115@@ -23,6 +23,7 @@
3116
3117 class QMirServerPrivate;
3118 class MirServer;
3119+class ScreenController;
3120
3121 class QMirServer: public QObject
3122 {
3123@@ -38,6 +39,8 @@
3124
3125 QWeakPointer<MirServer> mirServer() const;
3126
3127+ QWeakPointer<ScreenController> screenController() const;
3128+
3129 Q_SIGNALS:
3130 void started();
3131 void stopped();
3132
3133=== modified file 'src/platforms/mirserver/qmirserver_p.h'
3134--- src/platforms/mirserver/qmirserver_p.h 2015-05-18 18:30:33 +0000
3135+++ src/platforms/mirserver/qmirserver_p.h 2015-10-13 19:42:45 +0000
3136@@ -27,6 +27,7 @@
3137
3138 // local
3139 #include "mirserver.h"
3140+#include "screencontroller.h"
3141
3142 class QMirServer;
3143 class MirServerThread;
3144@@ -34,6 +35,7 @@
3145 struct QMirServerPrivate
3146 {
3147 QSharedPointer<MirServer> server;
3148+ QSharedPointer<ScreenController> screenController;
3149 MirServerThread *serverThread;
3150 };
3151
3152
3153=== modified file 'src/platforms/mirserver/qtcompositor.cpp'
3154--- src/platforms/mirserver/qtcompositor.cpp 2015-08-11 12:08:32 +0000
3155+++ src/platforms/mirserver/qtcompositor.cpp 2015-10-13 19:42:45 +0000
3156@@ -15,44 +15,32 @@
3157 */
3158
3159 #include "qtcompositor.h"
3160-#include "displaywindow.h"
3161-
3162-#include <QGuiApplication>
3163-#include <QWindow>
3164-
3165-#include <QDebug>
3166-
3167-QtCompositor::QtCompositor()
3168-{
3169-
3170-}
3171-
3172+#include "logging.h"
3173+
3174+#include <mir/graphics/cursor.h>
3175+
3176+// Lives in a Mir thread
3177 void QtCompositor::start()
3178 {
3179- // (Re)Start Qt's render thread by setting all its windows to exposed
3180- setAllWindowsExposed(true);
3181+ qCDebug(QTMIR_SCREENS) << "QtCompositor::start";
3182+
3183+ // FIXME: Hack to work around https://bugs.launchpad.net/mir/+bug/1502200
3184+ // See the FIXME in mirserver.cpp
3185+ if (m_cursor) {
3186+ m_cursor->hide();
3187+ }
3188+
3189+ Q_EMIT starting(); // blocks
3190 }
3191
3192 void QtCompositor::stop()
3193 {
3194- // Stop Qt's render threads by setting all its windows it obscured
3195- setAllWindowsExposed(false);
3196+ qCDebug(QTMIR_SCREENS) << "QtCompositor::stop";
3197+
3198+ Q_EMIT stopping(); // blocks
3199 }
3200
3201-void QtCompositor::setAllWindowsExposed(const bool exposed)
3202+void QtCompositor::setCursor(std::shared_ptr<mir::graphics::Cursor> cursor)
3203 {
3204- qDebug() << "QtCompositor::setAllWindowsExposed" << exposed;
3205- QList<QWindow *> windowList = QGuiApplication::allWindows();
3206-
3207- // manipulate Qt object's indirectly via posted events as we're not in Qt's GUI thread
3208- auto iterator = windowList.constBegin();
3209- while (iterator != windowList.constEnd()) {
3210- QWindow *window = *iterator;
3211- DisplayWindow *displayWindow = static_cast<DisplayWindow*>(window->handle());
3212- if (displayWindow) {
3213- QCoreApplication::postEvent(displayWindow,
3214- new QEvent( (exposed) ? QEvent::Show : QEvent::Hide));
3215- }
3216- iterator++;
3217- }
3218+ m_cursor = cursor;
3219 }
3220
3221=== modified file 'src/platforms/mirserver/qtcompositor.h'
3222--- src/platforms/mirserver/qtcompositor.h 2015-08-11 12:08:32 +0000
3223+++ src/platforms/mirserver/qtcompositor.h 2015-10-13 19:42:45 +0000
3224@@ -17,18 +17,38 @@
3225 #ifndef QTCOMPOSITOR_H
3226 #define QTCOMPOSITOR_H
3227
3228-#include "mir/compositor/compositor.h"
3229-
3230-class QtCompositor : public mir::compositor::Compositor
3231+#include <mir/compositor/compositor.h>
3232+
3233+// std lib
3234+#include <memory>
3235+
3236+// Qt
3237+#include <QObject>
3238+
3239+namespace mir {
3240+ namespace graphics {
3241+ class Cursor;
3242+ }
3243+}
3244+
3245+class QtCompositor : public QObject, public mir::compositor::Compositor
3246 {
3247+ Q_OBJECT
3248 public:
3249- QtCompositor();
3250+ QtCompositor() = default;
3251+ virtual ~QtCompositor() noexcept = default;
3252
3253 void start();
3254 void stop();
3255
3256+ void setCursor(std::shared_ptr<mir::graphics::Cursor>);
3257+
3258+Q_SIGNALS:
3259+ void starting();
3260+ void stopping();
3261+
3262 private:
3263- void setAllWindowsExposed(const bool exposed);
3264+ std::shared_ptr<mir::graphics::Cursor> m_cursor;
3265 };
3266
3267 #endif // QTCOMPOSITOR_H
3268
3269=== modified file 'src/platforms/mirserver/qteventfeeder.cpp'
3270--- src/platforms/mirserver/qteventfeeder.cpp 2015-08-27 16:10:20 +0000
3271+++ src/platforms/mirserver/qteventfeeder.cpp 2015-10-13 19:42:45 +0000
3272@@ -15,7 +15,12 @@
3273 */
3274
3275 #include "qteventfeeder.h"
3276+#include "cursor.h"
3277 #include "logging.h"
3278+#include "timestamp.h"
3279+#include "tracepoints.h" // generated from tracepoints.tp
3280+#include "screen.h" // NEEDED?
3281+#include "screencontroller.h"
3282
3283 #include <qpa/qplatforminputcontext.h>
3284 #include <qpa/qplatformintegration.h>
3285@@ -365,20 +370,30 @@
3286
3287 namespace {
3288
3289-class QtWindowSystem : public QtEventFeeder::QtWindowSystemInterface {
3290-
3291- bool hasTargetWindow() override
3292- {
3293- if (mTopLevelWindow.isNull() && !QGuiApplication::topLevelWindows().isEmpty()) {
3294- mTopLevelWindow = QGuiApplication::topLevelWindows().first();
3295- }
3296- return !mTopLevelWindow.isNull();
3297- }
3298-
3299- QRect targetWindowGeometry() override
3300- {
3301- Q_ASSERT(!mTopLevelWindow.isNull());
3302- return mTopLevelWindow->geometry();
3303+class QtWindowSystem : public QtEventFeeder::QtWindowSystemInterface
3304+{
3305+public:
3306+ QtWindowSystem()
3307+ {
3308+ // because we're using QMetaObject::invoke with arguments of those types
3309+ qRegisterMetaType<Qt::KeyboardModifiers>("Qt::KeyboardModifiers");
3310+ qRegisterMetaType<Qt::MouseButton>("Qt::MouseButton");
3311+ qRegisterMetaType<Qt::MouseButtons>("Qt::MouseButtons");
3312+ }
3313+
3314+ void setScreenController(const QSharedPointer<ScreenController> &sc) override
3315+ {
3316+ m_screenController = sc;
3317+ }
3318+
3319+ virtual QWindow* focusedWindow() override
3320+ {
3321+ return QGuiApplication::focusWindow();
3322+ }
3323+
3324+ QWindow* getWindowForTouchPoint(const QPoint &point) override //FIXME: not efficient, not updating focused window
3325+ {
3326+ return m_screenController->getWindowForPoint(point);
3327 }
3328
3329 void registerTouchDevice(QTouchDevice *device) override
3330@@ -386,47 +401,75 @@
3331 QWindowSystemInterface::registerTouchDevice(device);
3332 }
3333
3334- void handleExtendedKeyEvent(ulong timestamp, QEvent::Type type, int key,
3335+ void handleExtendedKeyEvent(QWindow *window, ulong timestamp, QEvent::Type type, int key,
3336 Qt::KeyboardModifiers modifiers,
3337 quint32 nativeScanCode, quint32 nativeVirtualKey,
3338 quint32 nativeModifiers,
3339 const QString& text, bool autorep, ushort count) override
3340 {
3341- Q_ASSERT(!mTopLevelWindow.isNull());
3342- QWindowSystemInterface::handleExtendedKeyEvent(mTopLevelWindow.data(), timestamp, type, key, modifiers,
3343+ QWindowSystemInterface::handleExtendedKeyEvent(window, timestamp, type, key, modifiers,
3344 nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorep, count);
3345 }
3346
3347- void handleTouchEvent(ulong timestamp, QTouchDevice *device,
3348+ void handleTouchEvent(QWindow *window, ulong timestamp, QTouchDevice *device,
3349 const QList<struct QWindowSystemInterface::TouchPoint> &points, Qt::KeyboardModifiers mods) override
3350 {
3351- Q_ASSERT(!mTopLevelWindow.isNull());
3352- QWindowSystemInterface::handleTouchEvent(mTopLevelWindow.data(), timestamp, device, points, mods);
3353- }
3354-
3355- void handleMouseEvent(ulong timestamp, QPointF point, Qt::MouseButton buttons, Qt::KeyboardModifiers modifiers) override
3356- {
3357- Q_ASSERT(!mTopLevelWindow.isNull());
3358- QWindowSystemInterface::handleMouseEvent(mTopLevelWindow.data(), timestamp, point, point, // local and global point are the same
3359- buttons, modifiers);
3360- }
3361-
3362+ QWindowSystemInterface::handleTouchEvent(window, timestamp, device, points, mods);
3363+ }
3364+
3365+ void handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
3366+ Qt::KeyboardModifiers modifiers) override
3367+ {
3368+ // Send to the first screen that handles the mouse event
3369+ // TODO: Have a mechanism to tell which screen currently has the logical mouse pointer
3370+ // (because they all might have their own separate graphical mouse pointer item)
3371+ // This will probably come once we implement the feature of having the mouse pointer
3372+ // crossing adjacent screens.
3373+
3374+ QList<Screen*> screens = m_screenController->screens();
3375+ bool eventHandled = false;
3376+ int i = 0;
3377+ while (i < screens.count() && !eventHandled) {
3378+ auto platformCursor = static_cast<qtmir::Cursor*>(screens[i]->cursor());
3379+ eventHandled = platformCursor->handleMouseEvent(timestamp, movement, buttons, modifiers);
3380+ ++i;
3381+ }
3382+ }
3383+
3384+ void handleWheelEvent(ulong timestamp, const QPointF &localPoint, const QPointF &globalPoint,
3385+ QPoint pixelDelta, QPoint angleDelta,
3386+ Qt::KeyboardModifiers mods, Qt::ScrollPhase phase) override
3387+ {
3388+ QWindowSystemInterface::handleWheelEvent(m_screenController->getWindowForPoint(localPoint.toPoint()),
3389+ timestamp, localPoint, globalPoint,
3390+ pixelDelta, angleDelta, mods, phase);
3391+ }
3392+
3393+ void handleEnterEvent(const QPointF &localPoint, const QPointF &globalPoint) override
3394+ {
3395+ QWindowSystemInterface::handleEnterEvent(m_screenController->getWindowForPoint(localPoint.toPoint()), localPoint, globalPoint);
3396+ }
3397+
3398+ void handleLeaveEvent(const QPointF &localPoint) override
3399+ {
3400+ QWindowSystemInterface::handleLeaveEvent(m_screenController->getWindowForPoint(localPoint.toPoint()));
3401+ }
3402
3403 private:
3404- QPointer<QWindow> mTopLevelWindow;
3405+ QSharedPointer<ScreenController> m_screenController;
3406 };
3407
3408 } // anonymous namespace
3409
3410-
3411-QtEventFeeder::QtEventFeeder(QtEventFeeder::QtWindowSystemInterface *windowSystem)
3412-{
3413- if (windowSystem) {
3414- mQtWindowSystem = windowSystem;
3415- } else {
3416- mQtWindowSystem = new QtWindowSystem;
3417- }
3418-
3419+QtEventFeeder::QtEventFeeder(const QSharedPointer<ScreenController> &screenController)
3420+ : QtEventFeeder(screenController, new QtWindowSystem)
3421+{
3422+}
3423+
3424+QtEventFeeder::QtEventFeeder(const QSharedPointer<ScreenController> &screenController,
3425+ QtEventFeeder::QtWindowSystemInterface *windowSystem)
3426+ : mQtWindowSystem(windowSystem)
3427+{
3428 // Initialize touch device. Hardcoded just like in qtubuntu
3429 // TODO: Create them from info gathered from Mir and store things like device id and source
3430 // in a QTouchDevice-derived class created by us. So that we can properly assemble back
3431@@ -436,6 +479,7 @@
3432 mTouchDevice->setCapabilities(
3433 QTouchDevice::Position | QTouchDevice::Area | QTouchDevice::Pressure |
3434 QTouchDevice::NormalizedPosition);
3435+ mQtWindowSystem->setScreenController(screenController);
3436 mQtWindowSystem->registerTouchDevice(mTouchDevice);
3437 }
3438
3439@@ -449,6 +493,7 @@
3440 auto type = mir_event_get_type(&event);
3441 if (type != mir_event_type_input)
3442 return false;
3443+
3444 auto iev = mir_event_get_input_event(&event);
3445
3446 switch (mir_input_event_get_type(iev)) {
3447@@ -472,7 +517,7 @@
3448
3449 Qt::KeyboardModifiers getQtModifiersFromMir(MirInputEventModifiers modifiers)
3450 {
3451- int qtModifiers = Qt::NoModifier;
3452+ Qt::KeyboardModifiers qtModifiers = Qt::NoModifier;
3453 if (modifiers & mir_input_event_modifier_shift) {
3454 qtModifiers |= Qt::ShiftModifier;
3455 }
3456@@ -485,12 +530,12 @@
3457 if (modifiers & mir_input_event_modifier_meta) {
3458 qtModifiers |= Qt::MetaModifier;
3459 }
3460- return static_cast<Qt::KeyboardModifiers>(qtModifiers);
3461+ return qtModifiers;
3462 }
3463
3464-Qt::MouseButton getQtMouseButtonsfromMirPointerEvent(MirPointerEvent const* pev)
3465+Qt::MouseButtons getQtMouseButtonsfromMirPointerEvent(MirPointerEvent const* pev)
3466 {
3467- int buttons = Qt::NoButton;
3468+ Qt::MouseButtons buttons = Qt::NoButton;
3469 if (mir_pointer_event_button_state(pev, mir_pointer_button_primary))
3470 buttons |= Qt::LeftButton;
3471 if (mir_pointer_event_button_state(pev, mir_pointer_button_secondary))
3472@@ -502,36 +547,56 @@
3473 if (mir_pointer_event_button_state(pev, mir_pointer_button_forward))
3474 buttons |= Qt::ForwardButton;
3475
3476- return static_cast<Qt::MouseButton>(buttons);
3477+ return buttons;
3478 }
3479 }
3480
3481 void QtEventFeeder::dispatchPointer(MirInputEvent const* ev)
3482 {
3483- if (!mQtWindowSystem->hasTargetWindow())
3484- return;
3485-
3486- auto timestamp = mir_input_event_get_event_time(ev) / 1000000;
3487-
3488+ auto timestamp = qtmir::compressTimestamp<ulong>(std::chrono::nanoseconds(mir_input_event_get_event_time(ev)));
3489 auto pev = mir_input_event_get_pointer_event(ev);
3490+ auto action = mir_pointer_event_action(pev);
3491 qCDebug(QTMIR_MIR_INPUT) << "Received" << qPrintable(mirPointerEventToString(pev));
3492
3493 auto modifiers = getQtModifiersFromMir(mir_pointer_event_modifiers(pev));
3494- auto buttons = getQtMouseButtonsfromMirPointerEvent(pev);
3495
3496+ auto movement = QPointF(mir_pointer_event_axis_value(pev, mir_pointer_axis_relative_x),
3497+ mir_pointer_event_axis_value(pev, mir_pointer_axis_relative_y));
3498 auto local_point = QPointF(mir_pointer_event_axis_value(pev, mir_pointer_axis_x),
3499 mir_pointer_event_axis_value(pev, mir_pointer_axis_y));
3500
3501- mQtWindowSystem->handleMouseEvent(timestamp, local_point,
3502- buttons, modifiers);
3503+ switch (action) {
3504+ case mir_pointer_action_button_up:
3505+ case mir_pointer_action_button_down:
3506+ case mir_pointer_action_motion:
3507+ {
3508+ const float hDelta = mir_pointer_event_axis_value(pev, mir_pointer_axis_hscroll);
3509+ const float vDelta = mir_pointer_event_axis_value(pev, mir_pointer_axis_vscroll);
3510+
3511+ if (hDelta != 0 || vDelta != 0) {
3512+ const QPoint angleDelta = QPoint(hDelta * 15, vDelta * 15);
3513+ mQtWindowSystem->handleWheelEvent(timestamp, local_point, local_point,
3514+ QPoint(), angleDelta, modifiers, Qt::ScrollUpdate);
3515+ } else {
3516+ auto buttons = getQtMouseButtonsfromMirPointerEvent(pev);
3517+ mQtWindowSystem->handleMouseEvent(timestamp, movement, buttons, modifiers);
3518+ }
3519+ break;
3520+ }
3521+ case mir_pointer_action_enter:
3522+ mQtWindowSystem->handleEnterEvent(local_point, local_point);
3523+ break;
3524+ case mir_pointer_action_leave:
3525+ mQtWindowSystem->handleLeaveEvent(local_point);
3526+ break;
3527+ default:
3528+ qCDebug(QTMIR_MIR_INPUT) << "Unrecognized pointer event";
3529+ }
3530 }
3531
3532 void QtEventFeeder::dispatchKey(MirInputEvent const* event)
3533 {
3534- if (!mQtWindowSystem->hasTargetWindow())
3535- return;
3536-
3537- ulong timestamp = mir_input_event_get_event_time(event) / 1000000;
3538+ auto timestamp = qtmir::compressTimestamp<ulong>(std::chrono::nanoseconds(mir_input_event_get_event_time(event)));
3539
3540 auto kev = mir_input_event_get_keyboard_event(event);
3541 xkb_keysym_t xk_sym = mir_keyboard_event_key_code(kev);
3542@@ -572,12 +637,17 @@
3543 text, is_auto_rep);
3544 qKeyEvent.setTimestamp(timestamp);
3545 if (context->filterEvent(&qKeyEvent)) {
3546- // key event filtered out by input context
3547+ qCDebug(QTMIR_MIR_INPUT) << "Received" << qPrintable(mirKeyboardEventToString(kev))
3548+ << "but not dispatching as it was filtered out by input context";
3549 return;
3550 }
3551 }
3552
3553- mQtWindowSystem->handleExtendedKeyEvent(timestamp, keyType, keyCode, modifiers,
3554+ qCDebug(QTMIR_MIR_INPUT).nospace() << "Received" << qPrintable(mirKeyboardEventToString(kev))
3555+ << ". Dispatching to " << mQtWindowSystem->focusedWindow();
3556+
3557+ mQtWindowSystem->handleExtendedKeyEvent(mQtWindowSystem->focusedWindow(),
3558+ timestamp, keyType, keyCode, modifiers,
3559 mir_keyboard_event_scan_code(kev),
3560 mir_keyboard_event_key_code(kev),
3561 mir_keyboard_event_modifiers(kev), text, is_auto_rep);
3562@@ -585,8 +655,9 @@
3563
3564 void QtEventFeeder::dispatchTouch(MirInputEvent const* event)
3565 {
3566- if (!mQtWindowSystem->hasTargetWindow())
3567- return;
3568+ auto timestamp = std::chrono::nanoseconds(mir_input_event_get_event_time(event));
3569+
3570+ tracepoint(qtmirserver, touchEventDispatch_start, timestamp.count());
3571
3572 auto tev = mir_input_event_get_touch_event(event);
3573 qCDebug(QTMIR_MIR_INPUT) << "Received" << qPrintable(mirTouchEventToString(tev));
3574@@ -594,54 +665,71 @@
3575 // FIXME(loicm) Max pressure is device specific. That one is for the Samsung Galaxy Nexus. That
3576 // needs to be fixed as soon as the compat input lib adds query support.
3577 const float kMaxPressure = 1.28;
3578- const QRect kWindowGeometry = mQtWindowSystem->targetWindowGeometry();
3579+ const int kPointerCount = mir_touch_event_point_count(tev);
3580 QList<QWindowSystemInterface::TouchPoint> touchPoints;
3581-
3582- // TODO: Is it worth setting the Qt::TouchPointStationary ones? Currently they are left
3583- // as Qt::TouchPointMoved
3584- const int kPointerCount = mir_touch_event_point_count(tev);
3585- for (int i = 0; i < kPointerCount; ++i) {
3586- QWindowSystemInterface::TouchPoint touchPoint;
3587-
3588- const float kX = mir_touch_event_axis_value(tev, i, mir_touch_axis_x);
3589- const float kY = mir_touch_event_axis_value(tev, i, mir_touch_axis_y);
3590- const float kW = mir_touch_event_axis_value(tev, i, mir_touch_axis_touch_major);
3591- const float kH = mir_touch_event_axis_value(tev, i, mir_touch_axis_touch_minor);
3592- const float kP = mir_touch_event_axis_value(tev, i, mir_touch_axis_pressure);
3593- touchPoint.id = mir_touch_event_id(tev, i);
3594-
3595- touchPoint.normalPosition = QPointF(kX / kWindowGeometry.width(), kY / kWindowGeometry.height());
3596- touchPoint.area = QRectF(kX - (kW / 2.0), kY - (kH / 2.0), kW, kH);
3597- touchPoint.pressure = kP / kMaxPressure;
3598- switch (mir_touch_event_action(tev, i))
3599- {
3600- case mir_touch_action_up:
3601- touchPoint.state = Qt::TouchPointReleased;
3602- break;
3603- case mir_touch_action_down:
3604- touchPoint.state = Qt::TouchPointPressed;
3605- break;
3606- case mir_touch_action_change:
3607- touchPoint.state = Qt::TouchPointMoved;
3608- break;
3609- default:
3610- break;
3611- }
3612-
3613- touchPoints.append(touchPoint);
3614+ QWindow *window = nullptr;
3615+
3616+ if (kPointerCount > 0) {
3617+ window = mQtWindowSystem->getWindowForTouchPoint(
3618+ QPoint(mir_touch_event_axis_value(tev, 0, mir_touch_axis_x),
3619+ mir_touch_event_axis_value(tev, 0, mir_touch_axis_y)));
3620+
3621+ if (!window) {
3622+ qCDebug(QTMIR_MIR_INPUT) << "REJECTING INPUT EVENT, no matching window";
3623+ return;
3624+ }
3625+
3626+ const QRect kWindowGeometry = window->geometry();
3627+
3628+ // TODO: Is it worth setting the Qt::TouchPointStationary ones? Currently they are left
3629+ // as Qt::TouchPointMoved
3630+ for (int i = 0; i < kPointerCount; ++i) {
3631+ QWindowSystemInterface::TouchPoint touchPoint;
3632+
3633+ const float kX = mir_touch_event_axis_value(tev, i, mir_touch_axis_x);
3634+ const float kY = mir_touch_event_axis_value(tev, i, mir_touch_axis_y);
3635+ const float kW = mir_touch_event_axis_value(tev, i, mir_touch_axis_touch_major);
3636+ const float kH = mir_touch_event_axis_value(tev, i, mir_touch_axis_touch_minor);
3637+ const float kP = mir_touch_event_axis_value(tev, i, mir_touch_axis_pressure);
3638+ touchPoint.id = mir_touch_event_id(tev, i);
3639+
3640+ touchPoint.normalPosition = QPointF(kX / kWindowGeometry.width(), kY / kWindowGeometry.height());
3641+ touchPoint.area = QRectF(kX - (kW / 2.0), kY - (kH / 2.0), kW, kH);
3642+ touchPoint.pressure = kP / kMaxPressure;
3643+ switch (mir_touch_event_action(tev, i))
3644+ {
3645+ case mir_touch_action_up:
3646+ touchPoint.state = Qt::TouchPointReleased;
3647+ break;
3648+ case mir_touch_action_down:
3649+ touchPoint.state = Qt::TouchPointPressed;
3650+ break;
3651+ case mir_touch_action_change:
3652+ touchPoint.state = Qt::TouchPointMoved;
3653+ break;
3654+ default:
3655+ break;
3656+ }
3657+
3658+ touchPoints.append(touchPoint);
3659+ }
3660 }
3661
3662+ auto compressedTimestamp = qtmir::compressTimestamp<ulong>(timestamp);
3663+
3664 // Qt needs a happy, sane stream of touch events. So let's make sure we're not forwarding
3665 // any insanity.
3666- validateTouches(mir_input_event_get_event_time(event) / 1000000, touchPoints);
3667+ validateTouches(window, compressedTimestamp, touchPoints);
3668
3669 // Touch event propagation.
3670 qCDebug(QTMIR_MIR_INPUT) << "Sending to Qt" << qPrintable(touchesToString(touchPoints));
3671- mQtWindowSystem->handleTouchEvent(
3672+ mQtWindowSystem->handleTouchEvent(window,
3673 //scales down the nsec_t (int64) to fit a ulong, precision lost but time difference suitable
3674- mir_input_event_get_event_time(event) / 1000000,
3675+ compressedTimestamp,
3676 mTouchDevice,
3677 touchPoints);
3678+
3679+ tracepoint(qtmirserver, touchEventDispatch_end, timestamp.count());
3680 }
3681
3682 void QtEventFeeder::start()
3683@@ -654,7 +742,7 @@
3684 // not used
3685 }
3686
3687-void QtEventFeeder::validateTouches(ulong timestamp,
3688+void QtEventFeeder::validateTouches(QWindow *window, ulong timestamp,
3689 QList<QWindowSystemInterface::TouchPoint> &touchPoints)
3690 {
3691 QSet<int> updatedTouches;
3692@@ -678,7 +766,7 @@
3693 if (!updatedTouches.contains(it.key())) {
3694 qCWarning(QTMIR_MIR_INPUT)
3695 << "There's a touch (id =" << it.key() << ") missing. Releasing it.";
3696- sendActiveTouchRelease(timestamp, it.key());
3697+ sendActiveTouchRelease(window, timestamp, it.key());
3698 it = mActiveTouches.erase(it);
3699 } else {
3700 ++it;
3701@@ -696,7 +784,7 @@
3702 }
3703 }
3704
3705-void QtEventFeeder::sendActiveTouchRelease(ulong timestamp, int id)
3706+void QtEventFeeder::sendActiveTouchRelease(QWindow *window, ulong timestamp, int id)
3707 {
3708 QList<QWindowSystemInterface::TouchPoint> touchPoints = mActiveTouches.values();
3709
3710@@ -710,7 +798,7 @@
3711 }
3712
3713 qCDebug(QTMIR_MIR_INPUT) << "Sending to Qt" << qPrintable(touchesToString(touchPoints));
3714- mQtWindowSystem->handleTouchEvent(timestamp, mTouchDevice, touchPoints);
3715+ mQtWindowSystem->handleTouchEvent(window, timestamp, mTouchDevice, touchPoints);
3716 }
3717
3718 bool QtEventFeeder::validateTouch(QWindowSystemInterface::TouchPoint &touchPoint)
3719
3720=== modified file 'src/platforms/mirserver/qteventfeeder.h'
3721--- src/platforms/mirserver/qteventfeeder.h 2015-08-11 12:08:32 +0000
3722+++ src/platforms/mirserver/qteventfeeder.h 2015-10-13 19:42:45 +0000
3723@@ -23,6 +23,7 @@
3724 #include <qpa/qwindowsysteminterface.h>
3725
3726 class QTouchDevice;
3727+class ScreenController;
3728
3729 /*
3730 Fills Qt's event loop with input events from Mir
3731@@ -33,26 +34,34 @@
3732 // Interface between QtEventFeeder and the actual QWindowSystemInterface functions
3733 // and other related Qt methods and objects to enable replacing them with mocks in
3734 // pure unit tests.
3735- // TODO - Make it work with multimonitor scenarios
3736 class QtWindowSystemInterface {
3737 public:
3738 virtual ~QtWindowSystemInterface() {}
3739- virtual bool hasTargetWindow() = 0;
3740- virtual QRect targetWindowGeometry() = 0;
3741+ virtual void setScreenController(const QSharedPointer<ScreenController> &sc) = 0;
3742+ virtual QWindow* getWindowForTouchPoint(const QPoint &point) = 0;
3743+ virtual QWindow* focusedWindow() = 0;
3744 virtual void registerTouchDevice(QTouchDevice *device) = 0;
3745- virtual void handleExtendedKeyEvent(ulong timestamp, QEvent::Type type, int key,
3746+ virtual void handleExtendedKeyEvent(QWindow *window, ulong timestamp, QEvent::Type type, int key,
3747 Qt::KeyboardModifiers modifiers,
3748 quint32 nativeScanCode, quint32 nativeVirtualKey,
3749 quint32 nativeModifiers,
3750 const QString& text = QString(), bool autorep = false,
3751 ushort count = 1) = 0;
3752- virtual void handleTouchEvent(ulong timestamp, QTouchDevice *device,
3753+ virtual void handleTouchEvent(QWindow *window, ulong timestamp, QTouchDevice *device,
3754 const QList<struct QWindowSystemInterface::TouchPoint> &points,
3755 Qt::KeyboardModifiers mods = Qt::NoModifier) = 0;
3756- virtual void handleMouseEvent(ulong timestamp, QPointF point, Qt::MouseButton buttons, Qt::KeyboardModifiers modifiers) = 0;
3757+ virtual void handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers) = 0;
3758+ virtual void handleWheelEvent(ulong timestamp, const QPointF &localPoint, const QPointF &globalPoint,
3759+ QPoint pixelDelta, QPoint angleDelta,
3760+ Qt::KeyboardModifiers mods = Qt::NoModifier,
3761+ Qt::ScrollPhase phase = Qt::ScrollUpdate) = 0;
3762+ virtual void handleEnterEvent(const QPointF &localPoint = QPointF(), const QPointF &globalPoint = QPointF()) = 0;
3763+ virtual void handleLeaveEvent(const QPointF &localPoint = QPointF()) = 0;
3764 };
3765
3766- QtEventFeeder(QtWindowSystemInterface *windowSystem = nullptr);
3767+ QtEventFeeder(const QSharedPointer<ScreenController> &screenController);
3768+ QtEventFeeder(const QSharedPointer<ScreenController> &screenController,
3769+ QtWindowSystemInterface *windowSystem);
3770 virtual ~QtEventFeeder();
3771
3772 static const int MirEventActionMask;
3773@@ -67,9 +76,9 @@
3774 void dispatchKey(MirInputEvent const* event);
3775 void dispatchTouch(MirInputEvent const* event);
3776 void dispatchPointer(MirInputEvent const* event);
3777- void validateTouches(ulong timestamp, QList<QWindowSystemInterface::TouchPoint> &touchPoints);
3778+ void validateTouches(QWindow *window, ulong timestamp, QList<QWindowSystemInterface::TouchPoint> &touchPoints);
3779 bool validateTouch(QWindowSystemInterface::TouchPoint &touchPoint);
3780- void sendActiveTouchRelease(ulong timestamp, int id);
3781+ void sendActiveTouchRelease(QWindow *window, ulong timestamp, int id);
3782
3783 QString touchesToString(const QList<struct QWindowSystemInterface::TouchPoint> &points);
3784
3785
3786=== modified file 'src/platforms/mirserver/screen.cpp'
3787--- src/platforms/mirserver/screen.cpp 2015-08-11 12:08:32 +0000
3788+++ src/platforms/mirserver/screen.cpp 2015-10-13 19:42:45 +0000
3789@@ -20,12 +20,13 @@
3790
3791 // Mir
3792 #include "mir/geometry/size.h"
3793+#include "mir/graphics/buffer.h"
3794+#include "mir/graphics/display_buffer.h"
3795+#include "mir/graphics/display.h"
3796
3797 // Qt
3798 #include <QCoreApplication>
3799 #include <qpa/qwindowsysteminterface.h>
3800-#include <QtSensors/QOrientationSensor>
3801-#include <QtSensors/QOrientationReading>
3802 #include <QThread>
3803
3804 // Qt sensors
3805@@ -102,12 +103,15 @@
3806
3807 bool Screen::skipDBusRegistration = false;
3808
3809-Screen::Screen(mir::graphics::DisplayConfigurationOutput const &screen)
3810+Screen::Screen(const mir::graphics::DisplayConfigurationOutput &screen)
3811 : QObject(nullptr)
3812+ , m_displayBuffer(nullptr)
3813+ , m_displayGroup(nullptr)
3814 , m_orientationSensor(new QOrientationSensor(this))
3815+ , m_screenWindow(nullptr)
3816 , m_unityScreen(nullptr)
3817 {
3818- readMirDisplayConfiguration(screen);
3819+ setMirDisplayConfiguration(screen);
3820
3821 // Set the default orientation based on the initial screen dimmensions.
3822 m_nativeOrientation = (m_geometry.width() >= m_geometry.height())
3823@@ -139,6 +143,14 @@
3824 }
3825 }
3826
3827+Screen::~Screen()
3828+{
3829+ //if a ScreenWindow associated with this screen, kill it
3830+ if (m_screenWindow) {
3831+ m_screenWindow->window()->destroy(); // ends up destroying m_ScreenWindow
3832+ }
3833+}
3834+
3835 bool Screen::orientationSensorEnabled()
3836 {
3837 return m_orientationSensor->isActive();
3838@@ -150,8 +162,15 @@
3839 toggleSensors(status);
3840 }
3841
3842-void Screen::readMirDisplayConfiguration(mir::graphics::DisplayConfigurationOutput const &screen)
3843+void Screen::setMirDisplayConfiguration(const mir::graphics::DisplayConfigurationOutput &screen)
3844 {
3845+ // Note: DisplayConfigurationOutput will be destroyed after this function returns
3846+
3847+ // Output data - each output has a unique id and corresponding type. Can be multiple cards.
3848+ m_outputId = screen.id;
3849+ m_cardId = screen.card_id;
3850+ m_type = screen.type;
3851+
3852 // Physical screen size
3853 m_physicalSize.setWidth(screen.physical_size_mm.width.as_float());
3854 m_physicalSize.setHeight(screen.physical_size_mm.height.as_float());
3855@@ -162,12 +181,34 @@
3856 // Pixel depth
3857 m_depth = 8 * MIR_BYTES_PER_PIXEL(screen.current_format);
3858
3859- // Mode = Resolution & refresh rate
3860+ // Power mode
3861+ m_powerMode = screen.power_mode;
3862+
3863+ QRect oldGeometry = m_geometry;
3864+ // Position of screen in virtual desktop coordinate space
3865+ m_geometry.setTop(screen.top_left.y.as_int());
3866+ m_geometry.setLeft(screen.top_left.x.as_int());
3867+
3868+ // Mode = current resolution & refresh rate
3869 mir::graphics::DisplayConfigurationMode mode = screen.modes.at(screen.current_mode_index);
3870 m_geometry.setWidth(mode.size.width.as_int());
3871 m_geometry.setHeight(mode.size.height.as_int());
3872
3873- m_refreshRate = mode.vrefresh_hz;
3874+ // DPI - unnecessary to calculate, default implementation in QPlatformScreen is sufficient
3875+
3876+ // Check for Screen geometry change
3877+ if (m_geometry != oldGeometry) {
3878+ QWindowSystemInterface::handleScreenGeometryChange(this->screen(), m_geometry, m_geometry);
3879+ if (m_screenWindow) { // resize corresponding window immediately
3880+ m_screenWindow->setGeometry(m_geometry);
3881+ }
3882+ }
3883+
3884+ // Refresh rate
3885+ if (m_refreshRate != mode.vrefresh_hz) {
3886+ m_refreshRate = mode.vrefresh_hz;
3887+ QWindowSystemInterface::handleScreenRefreshRateChange(this->screen(), mode.vrefresh_hz);
3888+ }
3889 }
3890
3891 void Screen::toggleSensors(const bool enable) const
3892@@ -226,3 +267,58 @@
3893 OrientationReadingEvent::m_type,
3894 m_orientationSensor->reading()->orientation()));
3895 }
3896+
3897+QPlatformCursor *Screen::cursor() const
3898+{
3899+ const QPlatformCursor *platformCursor = &m_cursor;
3900+ return const_cast<QPlatformCursor *>(platformCursor);
3901+}
3902+
3903+ScreenWindow *Screen::window() const
3904+{
3905+ return m_screenWindow;
3906+}
3907+
3908+void Screen::setWindow(ScreenWindow *window)
3909+{
3910+ if (window && m_screenWindow) {
3911+ qCDebug(QTMIR_SENSOR_MESSAGES) << "Screen::setWindow - overwriting existing ScreenWindow";
3912+ }
3913+ m_screenWindow = window;
3914+}
3915+
3916+void Screen::setMirDisplayBuffer(mir::graphics::DisplayBuffer *buffer, mir::graphics::DisplaySyncGroup *group)
3917+{
3918+ qCDebug(QTMIR_SCREENS) << "Screen::setMirDisplayBuffer" << buffer << group;
3919+ // This operation should only be performed while rendering is stopped
3920+ m_displayBuffer = buffer;
3921+ m_displayGroup = group;
3922+}
3923+
3924+void Screen::swapBuffers()
3925+{
3926+ m_displayBuffer->gl_swap_buffers();
3927+
3928+ /* FIXME this exposes a QtMir architecture problem, as Screen is supposed to wrap a mg::DisplayBuffer.
3929+ * We use Qt's multithreaded renderer, where each Screen is rendered to relatively independently, and
3930+ * post() called also individually.
3931+ *
3932+ * But if this is a native server on Android, in the multimonitor case a DisplaySyncGroup can contain
3933+ * 2+ DisplayBuffers, one post() call will submit all mg::DisplayBuffers in the group for flipping.
3934+ * This will cause just one Screen to be updated, blocking the swap call for the other Screens, which
3935+ * will slow rendering dramatically.
3936+ *
3937+ * Integrating the Qt Scenegraph renderer as a Mir renderer should solve this issue.
3938+ */
3939+ m_displayGroup->post();
3940+}
3941+
3942+void Screen::makeCurrent()
3943+{
3944+ m_displayBuffer->make_current();
3945+}
3946+
3947+void Screen::doneCurrent()
3948+{
3949+ m_displayBuffer->release_current();
3950+}
3951
3952=== modified file 'src/platforms/mirserver/screen.h'
3953--- src/platforms/mirserver/screen.h 2015-08-11 12:08:32 +0000
3954+++ src/platforms/mirserver/screen.h 2015-10-13 19:42:45 +0000
3955@@ -17,20 +17,28 @@
3956 #ifndef SCREEN_H
3957 #define SCREEN_H
3958
3959+// Qt
3960 #include <QObject>
3961 #include <QTimer>
3962 #include <QtDBus/QDBusInterface>
3963 #include <qpa/qplatformscreen.h>
3964
3965-#include "mir/graphics/display_configuration.h"
3966+// Mir
3967+#include <mir/graphics/display_configuration.h>
3968+
3969+// local
3970+#include "cursor.h"
3971+#include "screenwindow.h"
3972
3973 class QOrientationSensor;
3974+namespace mir { namespace graphics { class DisplayBuffer; class DisplaySyncGroup; }}
3975
3976 class Screen : public QObject, public QPlatformScreen
3977 {
3978 Q_OBJECT
3979 public:
3980- Screen(mir::graphics::DisplayConfigurationOutput const&);
3981+ Screen(const mir::graphics::DisplayConfigurationOutput &);
3982+ ~Screen();
3983
3984 // QPlatformScreen methods.
3985 QRect geometry() const override { return m_geometry; }
3986@@ -40,8 +48,12 @@
3987 qreal refreshRate() const override { return m_refreshRate; }
3988 Qt::ScreenOrientation nativeOrientation() const override { return m_nativeOrientation; }
3989 Qt::ScreenOrientation orientation() const override { return m_currentOrientation; }
3990+ QPlatformCursor *cursor() const override;
3991
3992 void toggleSensors(const bool enable) const;
3993+ mir::graphics::DisplayConfigurationOutputType outputType() const { return m_type; }
3994+
3995+ ScreenWindow* window() const;
3996
3997 // QObject methods.
3998 void customEvent(QEvent* event) override;
3999@@ -54,20 +66,40 @@
4000 void onDisplayPowerStateChanged(int, int);
4001 void onOrientationReadingChanged();
4002
4003+protected:
4004+ void setWindow(ScreenWindow *window);
4005+
4006+ void setMirDisplayConfiguration(const mir::graphics::DisplayConfigurationOutput &);
4007+ void setMirDisplayBuffer(mir::graphics::DisplayBuffer *, mir::graphics::DisplaySyncGroup *);
4008+ void swapBuffers();
4009+ void makeCurrent();
4010+ void doneCurrent();
4011+
4012 private:
4013- void readMirDisplayConfiguration(mir::graphics::DisplayConfigurationOutput const&);
4014-
4015 QRect m_geometry;
4016 int m_depth;
4017 QImage::Format m_format;
4018 QSizeF m_physicalSize;
4019 qreal m_refreshRate;
4020
4021+ mir::graphics::DisplayBuffer *m_displayBuffer;
4022+ mir::graphics::DisplaySyncGroup *m_displayGroup;
4023+ mir::graphics::DisplayConfigurationOutputId m_outputId;
4024+ mir::graphics::DisplayConfigurationCardId m_cardId;
4025+ mir::graphics::DisplayConfigurationOutputType m_type;
4026+ MirPowerMode m_powerMode;
4027+
4028 Qt::ScreenOrientation m_nativeOrientation;
4029 Qt::ScreenOrientation m_currentOrientation;
4030 QOrientationSensor *m_orientationSensor;
4031
4032+ ScreenWindow *m_screenWindow;
4033 QDBusInterface *m_unityScreen;
4034+
4035+ qtmir::Cursor m_cursor;
4036+
4037+ friend class ScreenController;
4038+ friend class ScreenWindow;
4039 };
4040
4041 #endif // SCREEN_H
4042
4043=== added file 'src/platforms/mirserver/screencontroller.cpp'
4044--- src/platforms/mirserver/screencontroller.cpp 1970-01-01 00:00:00 +0000
4045+++ src/platforms/mirserver/screencontroller.cpp 2015-10-13 19:42:45 +0000
4046@@ -0,0 +1,211 @@
4047+/*
4048+ * Copyright (C) 2015 Canonical, Ltd.
4049+ *
4050+ * This program is free software: you can redistribute it and/or modify it under
4051+ * the terms of the GNU Lesser General Public License version 3, as published by
4052+ * the Free Software Foundation.
4053+ *
4054+ * This program is distributed in the hope that it will be useful, but WITHOUT
4055+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
4056+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
4057+ * Lesser General Public License for more details.
4058+ *
4059+ * You should have received a copy of the GNU Lesser General Public License
4060+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4061+ */
4062+
4063+#include "screencontroller.h"
4064+
4065+#include "screenwindow.h"
4066+#include "qtcompositor.h"
4067+#include "logging.h"
4068+#include "mirserverintegration.h"
4069+#include "screen.h"
4070+
4071+// Mir
4072+#include <mir/graphics/display.h>
4073+#include <mir/graphics/display_buffer.h>
4074+
4075+// Qt
4076+#include <QScreen>
4077+#include <QQuickWindow>
4078+#include <qpa/qwindowsysteminterface.h>
4079+#include <QGuiApplication> // for qApp
4080+
4081+// std
4082+#include <memory>
4083+
4084+Q_LOGGING_CATEGORY(QTMIR_SCREENS, "qtmir.screens")
4085+
4086+namespace mg = mir::graphics;
4087+
4088+
4089+ScreenController::ScreenController(QObject *parent)
4090+ : QObject(parent)
4091+ , m_compositing(false)
4092+{
4093+ qCDebug(QTMIR_SCREENS) << "ScreenController::ScreenController";
4094+}
4095+
4096+// init only after MirServer has initialized - runs on MirServerThread!!!
4097+void ScreenController::init(const std::shared_ptr<mir::graphics::Display> &display,
4098+ const std::shared_ptr<mir::compositor::Compositor> &compositor)
4099+{
4100+ m_display = display;
4101+ m_compositor = compositor;
4102+
4103+ // Use a Blocking Queued Connection to enforce synchronization of Qt GUI thread with Mir thread(s)
4104+ // on compositor shutdown. Compositor startup can be lazy.
4105+ // Queued connections work because the thread affinity of this class is with the Qt GUI thread.
4106+ auto qtCompositor = static_cast<QtCompositor *>(compositor.get());
4107+ connect(qtCompositor, &QtCompositor::starting,
4108+ this, &ScreenController::onCompositorStarting);
4109+ connect(qtCompositor, &QtCompositor::stopping,
4110+ this, &ScreenController::onCompositorStopping, Qt::BlockingQueuedConnection);
4111+}
4112+
4113+// terminate before shutting down the Mir server, or else liable to deadlock with the blocking connection above
4114+// Runs on MirServerThread!!!
4115+void ScreenController::terminate()
4116+{
4117+ auto qtCompositor = static_cast<QtCompositor *>(m_compositor.get());
4118+ qtCompositor->disconnect();
4119+}
4120+
4121+void ScreenController::onCompositorStarting()
4122+{
4123+ qCDebug(QTMIR_SCREENS) << "ScreenController::onCompositorStarting";
4124+ m_compositing = true;
4125+
4126+ update();
4127+
4128+ // (Re)Start Qt's render thread by setting all windows with a corresponding screen to exposed.
4129+ for (auto screen : m_screenList) {
4130+ auto window = static_cast<ScreenWindow *>(screen->window());
4131+ if (window && window->window()) {
4132+ window->setExposed(true);
4133+ }
4134+ }
4135+}
4136+
4137+void ScreenController::onCompositorStopping()
4138+{
4139+ qCDebug(QTMIR_SCREENS) << "ScreenController::onCompositorStopping";
4140+ m_compositing = false;
4141+
4142+ // Stop Qt's render threads by setting all its windows it obscured. Must
4143+ // block until all windows have their GL contexts released.
4144+ for (auto screen : m_screenList) {
4145+ auto window = static_cast<ScreenWindow *>(screen->window());
4146+ if (window && window->window()) {
4147+ window->setExposed(false);
4148+ }
4149+ }
4150+
4151+ update();
4152+}
4153+
4154+void ScreenController::update()
4155+{
4156+ qCDebug(QTMIR_SCREENS) << "ScreenController::update";
4157+ auto display = m_display.lock();
4158+ if (!display)
4159+ return;
4160+ auto displayConfig = display->configuration();
4161+
4162+ // Mir only tells us something changed, it is up to us to figure out what.
4163+ QList<Screen*> newScreenList;
4164+ QList<Screen*> oldScreenList = m_screenList;
4165+ m_screenList.clear();
4166+
4167+ displayConfig->for_each_output(
4168+ [this, &oldScreenList, &newScreenList](const mg::DisplayConfigurationOutput &output) {
4169+ if (output.used && output.connected) {
4170+ Screen *screen = findScreenWithId(oldScreenList, output.id);
4171+ if (screen) { // we've already set up this display before, refresh its internals
4172+ screen->setMirDisplayConfiguration(output);
4173+ oldScreenList.removeAll(screen);
4174+ } else {
4175+ // new display, so create Screen for it
4176+ screen = this->createScreen(output);
4177+ newScreenList.append(screen);
4178+ qCDebug(QTMIR_SCREENS) << "Added Screen with id" << output.id.as_value()
4179+ << "and geometry" << screen->geometry();
4180+ }
4181+ m_screenList.append(screen);
4182+ }
4183+ }
4184+ );
4185+
4186+ // Delete any old & unused Screens
4187+ for (auto screen: oldScreenList) {
4188+ qCDebug(QTMIR_SCREENS) << "Removed Screen with id" << screen->m_outputId.as_value()
4189+ << "and geometry" << screen->geometry();
4190+ // The screen is automatically removed from Qt's internal list by the QPlatformScreen destructor.
4191+ auto window = static_cast<ScreenWindow *>(screen->window());
4192+ if (window && window->window() && window->isExposed()) {
4193+ window->window()->hide();
4194+ }
4195+ bool ok = QMetaObject::invokeMethod(qApp, "onScreenAboutToBeRemoved", Qt::DirectConnection, Q_ARG(QScreen*, screen->screen()));
4196+ if (!ok) {
4197+ qCWarning(QTMIR_SCREENS) << "Failed to invoke QGuiApplication::onScreenAboutToBeRemoved(QScreen*) slot.";
4198+ }
4199+ delete screen;
4200+ }
4201+
4202+ // Match up the new Mir DisplayBuffers with each Screen
4203+ display->for_each_display_sync_group([&](mg::DisplaySyncGroup &group) {
4204+ group.for_each_display_buffer([&](mg::DisplayBuffer &buffer) {
4205+ // only way to match Screen to a DisplayBuffer is by matching the geometry
4206+ QRect dbGeom(buffer.view_area().top_left.x.as_int(),
4207+ buffer.view_area().top_left.y.as_int(),
4208+ buffer.view_area().size.width.as_int(),
4209+ buffer.view_area().size.height.as_int());
4210+
4211+ for (auto screen : m_screenList) {
4212+ if (dbGeom == screen->geometry()) {
4213+ screen->setMirDisplayBuffer(&buffer, &group);
4214+ break;
4215+ }
4216+ }
4217+ });
4218+ });
4219+
4220+ qCDebug(QTMIR_SCREENS) << "=======================================";
4221+ for (auto screen: m_screenList) {
4222+ qCDebug(QTMIR_SCREENS) << screen << "- id:" << screen->m_outputId.as_value()
4223+ << "geometry:" << screen->geometry()
4224+ << "window:" << screen->window()
4225+ << "type" << static_cast<int>(screen->outputType());
4226+ }
4227+ qCDebug(QTMIR_SCREENS) << "=======================================";
4228+
4229+ for (auto screen : newScreenList) {
4230+ Q_EMIT screenAdded(screen);
4231+ }
4232+}
4233+
4234+Screen* ScreenController::createScreen(const mir::graphics::DisplayConfigurationOutput &output) const
4235+{
4236+ return new Screen(output);
4237+}
4238+
4239+Screen* ScreenController::findScreenWithId(const QList<Screen *> &list, const mg::DisplayConfigurationOutputId id)
4240+{
4241+ for (Screen *screen : list) {
4242+ if (screen->m_outputId == id) {
4243+ return screen;
4244+ }
4245+ }
4246+ return nullptr;
4247+}
4248+
4249+QWindow* ScreenController::getWindowForPoint(const QPoint &point) //FIXME - not thread safe & not efficient
4250+{
4251+ for (Screen *screen : m_screenList) {
4252+ if (screen->window() && screen->geometry().contains(point)) {
4253+ return screen->window()->window();
4254+ }
4255+ }
4256+ return nullptr;
4257+}
4258
4259=== added file 'src/platforms/mirserver/screencontroller.h'
4260--- src/platforms/mirserver/screencontroller.h 1970-01-01 00:00:00 +0000
4261+++ src/platforms/mirserver/screencontroller.h 2015-10-13 19:42:45 +0000
4262@@ -0,0 +1,95 @@
4263+/*
4264+ * Copyright (C) 2015 Canonical, Ltd.
4265+ *
4266+ * This program is free software: you can redistribute it and/or modify it under
4267+ * the terms of the GNU Lesser General Public License version 3, as published by
4268+ * the Free Software Foundation.
4269+ *
4270+ * This program is distributed in the hope that it will be useful, but WITHOUT
4271+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
4272+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
4273+ * Lesser General Public License for more details.
4274+ *
4275+ * You should have received a copy of the GNU Lesser General Public License
4276+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4277+ */
4278+
4279+#ifndef SCREENCONTROLLER_H
4280+#define SCREENCONTROLLER_H
4281+
4282+#include <QObject>
4283+#include <QPoint>
4284+
4285+// Mir
4286+#include <mir/graphics/display_configuration.h>
4287+
4288+// std
4289+#include <memory>
4290+
4291+namespace mir {
4292+ namespace graphics { class Display; }
4293+ namespace compositor { class Compositor; }
4294+}
4295+class Screen;
4296+class QWindow;
4297+
4298+/*
4299+ * ScreenController monitors the Mir display configuration and compositor status, and updates
4300+ * the relevant QScreen and QWindow states accordingly.
4301+ *
4302+ * Primary purposes are:
4303+ * 1. to update QScreen state on Mir display configuration changes
4304+ * 2. to stop the Qt renderer by hiding its QWindow when Mir wants to stop all compositing,
4305+ * and resume Qt's renderer by showing its QWindow when Mir wants to resume compositing.
4306+ *
4307+ *
4308+ * Threading Note:
4309+ * This object must have affinity to the main Qt GUI thread, as it creates & destroys Platform
4310+ * objects which Qt uses internally. However beware as the init() & terminate() methods need to
4311+ * be called on the MirServerThread thread, as we need to monitor the screen state *after*
4312+ * Mir has initialized but before Qt's event loop has started, and tear down before Mir terminates.
4313+ * Also note the MirServerThread does not have an QEventLoop.
4314+ *
4315+ * All other methods must be called on the Qt GUI thread.
4316+ */
4317+
4318+class ScreenController : public QObject
4319+{
4320+ Q_OBJECT
4321+public:
4322+ explicit ScreenController(QObject *parent = 0);
4323+
4324+ QList<Screen*> screens() const { return m_screenList; }
4325+ bool compositing() const { return m_compositing; }
4326+
4327+ QWindow* getWindowForPoint(const QPoint &point);
4328+
4329+Q_SIGNALS:
4330+ void screenAdded(Screen *screen);
4331+
4332+public Q_SLOTS:
4333+ void update();
4334+
4335+public:
4336+ // called by MirServer
4337+ void init(const std::shared_ptr<mir::graphics::Display> &display,
4338+ const std::shared_ptr<mir::compositor::Compositor> &compositor);
4339+ void terminate();
4340+
4341+ // override for testing purposes
4342+ virtual Screen *createScreen(const mir::graphics::DisplayConfigurationOutput &output) const;
4343+
4344+protected Q_SLOTS:
4345+ void onCompositorStarting();
4346+ void onCompositorStopping();
4347+
4348+private:
4349+ Screen* findScreenWithId(const QList<Screen*> &list, const mir::graphics::DisplayConfigurationOutputId id);
4350+
4351+ std::weak_ptr<mir::graphics::Display> m_display;
4352+ std::shared_ptr<mir::compositor::Compositor> m_compositor;
4353+ QList<Screen*> m_screenList;
4354+ bool m_compositing;
4355+};
4356+
4357+#endif // SCREENCONTROLLER_H
4358
4359=== renamed file 'src/platforms/mirserver/displaywindow.cpp' => 'src/platforms/mirserver/screenwindow.cpp'
4360--- src/platforms/mirserver/displaywindow.cpp 2015-08-11 12:08:32 +0000
4361+++ src/platforms/mirserver/screenwindow.cpp 2015-10-13 19:42:45 +0000
4362@@ -14,15 +14,22 @@
4363 * along with this program. If not, see <http://www.gnu.org/licenses/>.
4364 */
4365
4366-#include "displaywindow.h"
4367-
4368-#include "mir/geometry/size.h"
4369-
4370+#include "screenwindow.h"
4371+#include "screen.h"
4372+
4373+// Mir
4374+#include <mir/geometry/size.h>
4375+#include <mir/graphics/display_buffer.h>
4376+
4377+// Qt
4378 #include <qpa/qwindowsysteminterface.h>
4379 #include <qpa/qplatformscreen.h>
4380-
4381+#include <QQuickWindow>
4382+#include <QtQuick/private/qsgrenderloop_p.h>
4383 #include <QDebug>
4384
4385+#include "logging.h"
4386+
4387 static WId newWId()
4388 {
4389 static WId id = 0;
4390@@ -33,94 +40,74 @@
4391 return ++id;
4392 }
4393
4394-DisplayWindow::DisplayWindow(
4395- QWindow *window,
4396- mir::graphics::DisplaySyncGroup *displayGroup,
4397- mir::graphics::DisplayBuffer *displayBuffer)
4398- : QObject(nullptr), QPlatformWindow(window)
4399- , m_isExposed(true)
4400+ScreenWindow::ScreenWindow(QWindow *window)
4401+ : QPlatformWindow(window)
4402+ , m_exposed(false)
4403 , m_winId(newWId())
4404- , m_displayGroup(displayGroup)
4405- , m_displayBuffer(displayBuffer)
4406 {
4407- qDebug() << "DisplayWindow::DisplayWindow";
4408- qWarning("Window %p: %p 0x%x\n", this, window, uint(m_winId));
4409+ // Register with the Screen it is associated with
4410+ auto myScreen = static_cast<Screen *>(screen());
4411+ Q_ASSERT(myScreen);
4412+ myScreen->setWindow(this);
4413+
4414+ qCDebug(QTMIR_SCREENS) << "ScreenWindow" << this << "with window ID" << uint(m_winId) << "backed by" << myScreen;
4415
4416 QRect screenGeometry(screen()->availableGeometry());
4417 if (window->geometry() != screenGeometry) {
4418 setGeometry(screenGeometry);
4419+ window->setGeometry(screenGeometry);
4420 }
4421 window->setSurfaceType(QSurface::OpenGLSurface);
4422-
4423- // The compositor window is always active. I.e., it's always focused so that
4424- // it always processes key events, etc
4425- requestActivateWindow();
4426-}
4427-
4428-QRect DisplayWindow::geometry() const
4429-{
4430- // For yet-to-become-fullscreen windows report the geometry covering the entire
4431- // screen. This is particularly important for Quick where the root object may get
4432- // sized to some geometry queried before calling create().
4433- return screen()->availableGeometry();
4434-}
4435-
4436-void DisplayWindow::setGeometry(const QRect &)
4437-{
4438- // We only support full-screen windows
4439- QRect rect(screen()->availableGeometry());
4440- QWindowSystemInterface::handleGeometryChange(window(), rect);
4441- QPlatformWindow::setGeometry(rect);
4442-}
4443-
4444-bool DisplayWindow::isExposed() const
4445-{
4446- return m_isExposed;
4447-}
4448-
4449-bool DisplayWindow::event(QEvent *event)
4450-{
4451- // Intercept Hide event and convert to Expose event, as Hide causes Qt to release GL
4452- // resources, which we don't want. Must intercept Show to un-do hide.
4453- if (event->type() == QEvent::Hide) {
4454- qDebug() << "DisplayWindow::event got QEvent::Hide";
4455- m_isExposed = false;
4456- QWindowSystemInterface::handleExposeEvent(window(), QRect());
4457- QWindowSystemInterface::flushWindowSystemEvents();
4458- return true;
4459- } else if (event->type() == QEvent::Show) {
4460- qDebug() << "DisplayWindow::event got QEvent::Show";
4461- m_isExposed = true;
4462- QRect rect(QPoint(), geometry().size());
4463- QWindowSystemInterface::handleExposeEvent(window(), rect);
4464- QWindowSystemInterface::flushWindowSystemEvents();
4465- return true;
4466+}
4467+
4468+ScreenWindow::~ScreenWindow()
4469+{
4470+ qCDebug(QTMIR_SCREENS) << "Destroying ScreenWindow" << this;
4471+ static_cast<Screen *>(screen())->setWindow(nullptr);
4472+}
4473+
4474+bool ScreenWindow::isExposed() const
4475+{
4476+ return m_exposed;
4477+}
4478+
4479+void ScreenWindow::setExposed(const bool exposed)
4480+{
4481+ qCDebug(QTMIR_SCREENS) << "ScreenWindow::setExposed" << this << exposed;
4482+ if (m_exposed == exposed)
4483+ return;
4484+
4485+ m_exposed = exposed;
4486+ if (!window())
4487+ return;
4488+
4489+ // If backing a QQuickWindow, need to stop/start its renderer immediately
4490+ auto quickWindow = static_cast<QQuickWindow *>(window());
4491+ if (!quickWindow)
4492+ return;
4493+
4494+ auto renderer = QSGRenderLoop::instance();
4495+ if (exposed) {
4496+ renderer->show(quickWindow);
4497+ QWindowSystemInterface::handleExposeEvent(window(), QRegion()); // else it won't redraw
4498+ } else {
4499+ quickWindow->setPersistentOpenGLContext(false);
4500+ quickWindow->setPersistentSceneGraph(false);
4501+ renderer->hide(quickWindow); // ExposeEvent will arrive too late, need to stop compositor immediately
4502 }
4503- return QObject::event(event);
4504-}
4505-
4506-void DisplayWindow::swapBuffers()
4507-{
4508- m_displayBuffer->gl_swap_buffers();
4509-
4510- // FIXME this exposes a QtMir architecture problem now, as DisplayWindow
4511- // is supposed to wrap a mg::DisplayBuffer. We use Qt's multithreaded
4512- // renderer, where each DisplayWindow is rendered to relatively
4513- // independently, and post() called also individually.
4514- //
4515- // But in multimonitor case where a DisplaySyncGroup contains 2
4516- // DisplayBuffers, one post() call will submit both
4517- // mg::DisplayBuffers for flipping, which can happen before the other
4518- // DisplayWindow has been rendered to, causing visual artifacts
4519- m_displayGroup->post();
4520-}
4521-
4522-void DisplayWindow::makeCurrent()
4523-{
4524- m_displayBuffer->make_current();
4525-}
4526-
4527-void DisplayWindow::doneCurrent()
4528-{
4529- m_displayBuffer->release_current();
4530+}
4531+
4532+void ScreenWindow::swapBuffers()
4533+{
4534+ static_cast<Screen *>(screen())->swapBuffers();
4535+}
4536+
4537+void ScreenWindow::makeCurrent()
4538+{
4539+ static_cast<Screen *>(screen())->makeCurrent();
4540+}
4541+
4542+void ScreenWindow::doneCurrent()
4543+{
4544+ static_cast<Screen *>(screen())->doneCurrent();
4545 }
4546
4547=== renamed file 'src/platforms/mirserver/displaywindow.h' => 'src/platforms/mirserver/screenwindow.h'
4548--- src/platforms/mirserver/displaywindow.h 2015-08-11 12:08:32 +0000
4549+++ src/platforms/mirserver/screenwindow.h 2015-10-13 19:42:45 +0000
4550@@ -14,43 +14,33 @@
4551 * along with this program. If not, see <http://www.gnu.org/licenses/>.
4552 */
4553
4554-#ifndef DISPLAYWINDOW_H
4555-#define DISPLAYWINDOW_H
4556+#ifndef SCREENWINDOW_H
4557+#define SCREENWINDOW_H
4558
4559 #include <qpa/qplatformwindow.h>
4560
4561-#include <mir/graphics/display.h>
4562-#include <mir/graphics/display_buffer.h>
4563-
4564-#include <QObject>
4565-
4566-// DisplayWindow wraps the whatever implementation Mir creates of a DisplayBuffer,
4567-// which is the buffer output for an individual display.
4568-
4569-class DisplayWindow : public QObject, public QPlatformWindow
4570+// ScreenWindow implements the basics of a QPlatformWindow.
4571+// QtMir enforces one Window per Screen, so Window and Screen are tightly coupled.
4572+// All Mir specifics live in the associated Screen object.
4573+
4574+class ScreenWindow : public QPlatformWindow
4575 {
4576- Q_OBJECT
4577 public:
4578- explicit DisplayWindow(QWindow *window, mir::graphics::DisplaySyncGroup*, mir::graphics::DisplayBuffer*);
4579+ explicit ScreenWindow(QWindow *window);
4580+ virtual ~ScreenWindow();
4581
4582- QRect geometry() const override;
4583- void setGeometry(const QRect &rect) override;
4584+ bool isExposed() const override;
4585+ void setExposed(const bool exposed);
4586
4587 WId winId() const override { return m_winId; }
4588
4589- bool isExposed() const override;
4590-
4591- bool event(QEvent *event) override;
4592-
4593 void swapBuffers();
4594 void makeCurrent();
4595 void doneCurrent();
4596
4597 private:
4598- bool m_isExposed;
4599+ bool m_exposed;
4600 WId m_winId;
4601- mir::graphics::DisplaySyncGroup *m_displayGroup;
4602- mir::graphics::DisplayBuffer *m_displayBuffer;
4603 };
4604
4605-#endif // DISPLAYWINDOW_H
4606+#endif // SCREENWINDOW_H
4607
4608=== added file 'src/platforms/mirserver/tileddisplayconfigurationpolicy.cpp'
4609--- src/platforms/mirserver/tileddisplayconfigurationpolicy.cpp 1970-01-01 00:00:00 +0000
4610+++ src/platforms/mirserver/tileddisplayconfigurationpolicy.cpp 2015-10-13 19:42:45 +0000
4611@@ -0,0 +1,44 @@
4612+/*
4613+ * Copyright (C) 2015 Canonical, Ltd.
4614+ *
4615+ * This program is free software: you can redistribute it and/or modify it under
4616+ * the terms of the GNU Lesser General Public License version 3, as published by
4617+ * the Free Software Foundation.
4618+ *
4619+ * This program is distributed in the hope that it will be useful, but WITHOUT
4620+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
4621+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
4622+ * Lesser General Public License for more details.
4623+ *
4624+ * You should have received a copy of the GNU Lesser General Public License
4625+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4626+ */
4627+
4628+#include "tileddisplayconfigurationpolicy.h"
4629+
4630+#include <mir/graphics/display_configuration.h>
4631+#include <mir/geometry/point.h>
4632+
4633+namespace mg = mir::graphics;
4634+
4635+TiledDisplayConfigurationPolicy::TiledDisplayConfigurationPolicy(
4636+ const std::shared_ptr<mir::graphics::DisplayConfigurationPolicy> &wrapped)
4637+ : m_wrapped(wrapped)
4638+{
4639+}
4640+
4641+void TiledDisplayConfigurationPolicy::apply_to(mg::DisplayConfiguration& conf)
4642+{
4643+ int nextTopLeftPosition = 0;
4644+
4645+ m_wrapped->apply_to(conf);
4646+
4647+ conf.for_each_output(
4648+ [&](mg::UserDisplayConfigurationOutput& output)
4649+ {
4650+ if (output.connected && output.used) {
4651+ output.top_left = mir::geometry::Point{nextTopLeftPosition, 0};
4652+ nextTopLeftPosition += output.modes[output.preferred_mode_index].size.width.as_int();
4653+ }
4654+ });
4655+}
4656
4657=== added file 'src/platforms/mirserver/tileddisplayconfigurationpolicy.h'
4658--- src/platforms/mirserver/tileddisplayconfigurationpolicy.h 1970-01-01 00:00:00 +0000
4659+++ src/platforms/mirserver/tileddisplayconfigurationpolicy.h 2015-10-13 19:42:45 +0000
4660@@ -0,0 +1,35 @@
4661+/*
4662+ * Copyright (C) 2015 Canonical, Ltd.
4663+ *
4664+ * This program is free software: you can redistribute it and/or modify it under
4665+ * the terms of the GNU Lesser General Public License version 3, as published by
4666+ * the Free Software Foundation.
4667+ *
4668+ * This program is distributed in the hope that it will be useful, but WITHOUT
4669+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
4670+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
4671+ * Lesser General Public License for more details.
4672+ *
4673+ * You should have received a copy of the GNU Lesser General Public License
4674+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4675+ */
4676+
4677+#ifndef TILEDDISPLAYCONFIGURATIONPOLICY_H
4678+#define TILEDDISPLAYCONFIGURATIONPOLICY_H
4679+
4680+#include <mir/graphics/display_configuration_policy.h>
4681+
4682+#include <memory>
4683+
4684+class TiledDisplayConfigurationPolicy : public mir::graphics::DisplayConfigurationPolicy
4685+{
4686+public:
4687+ TiledDisplayConfigurationPolicy(const std::shared_ptr<mir::graphics::DisplayConfigurationPolicy> &wrapped);
4688+
4689+ void apply_to(mir::graphics::DisplayConfiguration& conf) override;
4690+
4691+private:
4692+ const std::shared_ptr<mir::graphics::DisplayConfigurationPolicy> m_wrapped;
4693+};
4694+
4695+#endif // TILEDDISPLAYCONFIGURATIONPOLICY_H
4696
4697=== modified file 'src/platforms/mirserver/tracepoints.tp'
4698--- src/platforms/mirserver/tracepoints.tp 2014-09-22 18:06:58 +0000
4699+++ src/platforms/mirserver/tracepoints.tp 2015-10-13 19:42:45 +0000
4700@@ -1,3 +1,5 @@
4701+#include <stdint.h>
4702+
4703 TRACEPOINT_EVENT(qtmirserver, starting, TP_ARGS(0), TP_FIELDS())
4704 TRACEPOINT_EVENT(qtmirserver, stopping, TP_ARGS(0), TP_FIELDS())
4705 TRACEPOINT_EVENT(qtmirserver, surfaceCreated, TP_ARGS(0), TP_FIELDS())
4706@@ -8,3 +10,6 @@
4707
4708 TRACEPOINT_EVENT(qtmirserver, surfacePlacementStart, TP_ARGS(0), TP_FIELDS())
4709 TRACEPOINT_EVENT(qtmirserver, surfacePlacementEnd, TP_ARGS(0), TP_FIELDS())
4710+
4711+TRACEPOINT_EVENT(qtmirserver, touchEventDispatch_start, TP_ARGS(int64_t, event_time), TP_FIELDS(ctf_integer(int64_t, event_time, event_time)))
4712+TRACEPOINT_EVENT(qtmirserver, touchEventDispatch_end, TP_ARGS(int64_t, event_time), TP_FIELDS(ctf_integer(int64_t, event_time, event_time)))
4713
4714=== added directory 'tests/common'
4715=== added file 'tests/common/fake_displayconfigurationoutput.h'
4716--- tests/common/fake_displayconfigurationoutput.h 1970-01-01 00:00:00 +0000
4717+++ tests/common/fake_displayconfigurationoutput.h 2015-10-13 19:42:45 +0000
4718@@ -0,0 +1,73 @@
4719+/*
4720+ * Copyright (C) 2015 Canonical, Ltd.
4721+ *
4722+ * This program is free software: you can redistribute it and/or modify it under
4723+ * the terms of the GNU Lesser General Public License version 3, as published by
4724+ * the Free Software Foundation.
4725+ *
4726+ * This program is distributed in the hope that it will be useful, but WITHOUT
4727+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
4728+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
4729+ * Lesser General Public License for more details.
4730+ *
4731+ * You should have received a copy of the GNU Lesser General Public License
4732+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4733+ */
4734+
4735+#ifndef FAKE_DISPLAYCONFIGURATIONOUTPUT_H
4736+#define FAKE_DISPLAYCONFIGURATIONOUTPUT_H
4737+
4738+#include <mir/graphics/display_configuration.h>
4739+
4740+namespace mg = mir::graphics;
4741+namespace geom = mir::geometry;
4742+
4743+const mg::DisplayConfigurationOutput fakeOutput1
4744+{
4745+ mg::DisplayConfigurationOutputId{3},
4746+ mg::DisplayConfigurationCardId{2},
4747+ mg::DisplayConfigurationOutputType::dvid,
4748+ {
4749+ mir_pixel_format_abgr_8888
4750+ },
4751+ {
4752+ {geom::Size{100, 200}, 60.0},
4753+ {geom::Size{100, 200}, 59.0},
4754+ {geom::Size{150, 200}, 59.0}
4755+ },
4756+ 0,
4757+ geom::Size{1111, 2222},
4758+ true,
4759+ true,
4760+ geom::Point(),
4761+ 2,
4762+ mir_pixel_format_abgr_8888,
4763+ mir_power_mode_on,
4764+ mir_orientation_normal
4765+};
4766+
4767+const mg::DisplayConfigurationOutput fakeOutput2
4768+{
4769+ mg::DisplayConfigurationOutputId{2},
4770+ mg::DisplayConfigurationCardId{4},
4771+ mg::DisplayConfigurationOutputType::lvds,
4772+ {
4773+ mir_pixel_format_xbgr_8888
4774+ },
4775+ {
4776+ {geom::Size{800, 1200}, 90.0},
4777+ {geom::Size{1600, 2400}, 60.0},
4778+ {geom::Size{1500, 2000}, 75.0}
4779+ },
4780+ 0,
4781+ geom::Size{1000, 2000},
4782+ true,
4783+ true,
4784+ geom::Point(500, 600),
4785+ 2,
4786+ mir_pixel_format_xbgr_8888,
4787+ mir_power_mode_on,
4788+ mir_orientation_left
4789+};
4790+
4791+#endif // FAKE_DISPLAYCONFIGURATIONOUTPUT_H
4792
4793=== added file 'tests/common/gmock_fixes.h'
4794--- tests/common/gmock_fixes.h 1970-01-01 00:00:00 +0000
4795+++ tests/common/gmock_fixes.h 2015-10-13 19:42:45 +0000
4796@@ -0,0 +1,124 @@
4797+//
4798+// Copyright © 2012 Canonical Ltd. Copyright 2007, Google Inc.
4799+//
4800+// All rights reserved.
4801+//
4802+// Redistribution and use in source and binary forms, with or without
4803+// modification, are permitted provided that the following conditions are
4804+// met:
4805+//
4806+// * Redistributions of source code must retain the above copyright
4807+// notice, this list of conditions and the following disclaimer.
4808+// * Redistributions in binary form must reproduce the above
4809+// copyright notice, this list of conditions and the following disclaimer
4810+// in the documentation and/or other materials provided with the
4811+// distribution.
4812+// * Neither the name of Google Inc. nor the names of its
4813+// contributors may be used to endorse or promote products derived from
4814+// this software without specific prior written permission.
4815+//
4816+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4817+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
4818+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
4819+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
4820+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
4821+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
4822+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
4823+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
4824+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
4825+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
4826+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
4827+//
4828+// Author: wan@google.com (Zhanyong Wan)
4829+// Authored by: Alan Griffiths <alan@octopull.co.uk>
4830+
4831+#ifndef MIR_TEST_GMOCK_FIXES_H_
4832+#define MIR_TEST_GMOCK_FIXES_H_
4833+
4834+#include <memory>
4835+#include <gmock/gmock.h>
4836+
4837+namespace testing
4838+{
4839+namespace internal
4840+{
4841+
4842+template<typename T>
4843+class ActionResultHolder<std::unique_ptr<T>>
4844+: public UntypedActionResultHolderBase {
4845+ public:
4846+ explicit ActionResultHolder(std::unique_ptr<T>&& a_value) :
4847+ value_(std::move(a_value)) {}
4848+
4849+ // The compiler-generated copy constructor and assignment operator
4850+ // are exactly what we need, so we don't need to define them.
4851+
4852+ // Returns the held value and deletes this object.
4853+ std::unique_ptr<T> GetValueAndDelete() const {
4854+ std::unique_ptr<T> retval(std::move(value_));
4855+ delete this;
4856+ return retval;
4857+ }
4858+
4859+ // Prints the held value as an action's result to os.
4860+ virtual void PrintAsActionResult(::std::ostream* os) const {
4861+ *os << "\n Returns: ";
4862+ // T may be a reference type, so we don't use UniversalPrint().
4863+ UniversalPrinter<std::unique_ptr<T>>::Print(value_, os);
4864+ }
4865+
4866+ // Performs the given mock function's default action and returns the
4867+ // result in a new-ed ActionResultHolder.
4868+ template <typename F>
4869+ static ActionResultHolder* PerformDefaultAction(
4870+ const FunctionMockerBase<F>* func_mocker,
4871+ const typename Function<F>::ArgumentTuple& args,
4872+ const string& call_description) {
4873+ return new ActionResultHolder(
4874+ func_mocker->PerformDefaultAction(args, call_description));
4875+ }
4876+
4877+ // Performs the given action and returns the result in a new-ed
4878+ // ActionResultHolder.
4879+ template <typename F>
4880+ static ActionResultHolder*
4881+ PerformAction(const Action<F>& action,
4882+ const typename Function<F>::ArgumentTuple& args) {
4883+ return new ActionResultHolder(action.Perform(args));
4884+ }
4885+
4886+ private:
4887+ std::unique_ptr<T> mutable value_;
4888+
4889+ // T could be a reference type, so = isn't supported.
4890+ GTEST_DISALLOW_ASSIGN_(ActionResultHolder);
4891+};
4892+
4893+}
4894+
4895+template<typename T>
4896+class DefaultValue<std::unique_ptr<T>> {
4897+ public:
4898+ // Unsets the default value for type T.
4899+ static void Clear() {}
4900+
4901+ // Returns true iff the user has set the default value for type T.
4902+ static bool IsSet() { return false; }
4903+
4904+ // Returns true if T has a default return value set by the user or there
4905+ // exists a built-in default value.
4906+ static bool Exists() {
4907+ return true;
4908+ }
4909+
4910+ // Returns the default value for type T if the user has set one;
4911+ // otherwise returns the built-in default value if there is one;
4912+ // otherwise aborts the process.
4913+ static std::unique_ptr<T> Get() {
4914+ return std::unique_ptr<T>();
4915+ }
4916+};
4917+
4918+}
4919+
4920+#endif /* MIR_TEST_GMOCK_FIXES_H_ */
4921
4922=== added file 'tests/common/mock_display.h'
4923--- tests/common/mock_display.h 1970-01-01 00:00:00 +0000
4924+++ tests/common/mock_display.h 2015-10-13 19:42:45 +0000
4925@@ -0,0 +1,53 @@
4926+/*
4927+ * Copyright (C) 2015 Canonical, Ltd.
4928+ *
4929+ * This program is free software: you can redistribute it and/or modify it under
4930+ * the terms of the GNU Lesser General Public License version 3, as published by
4931+ * the Free Software Foundation.
4932+ *
4933+ * This program is distributed in the hope that it will be useful, but WITHOUT
4934+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
4935+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
4936+ * Lesser General Public License for more details.
4937+ *
4938+ * You should have received a copy of the GNU Lesser General Public License
4939+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4940+ */
4941+
4942+#ifndef MOCKDISPLAY_H
4943+#define MOCKDISPLAY_H
4944+
4945+#include <mir/graphics/display.h>
4946+#include <mir/graphics/gl_context.h>
4947+
4948+#include <gmock/gmock.h>
4949+#include "gmock_fixes.h"
4950+
4951+class MockDisplaySyncGroup : public mir::graphics::DisplaySyncGroup
4952+{
4953+public:
4954+ MOCK_METHOD1(for_each_display_buffer, void(std::function<void(mir::graphics::DisplayBuffer&)> const& f));
4955+ MOCK_METHOD0(post, void());
4956+};
4957+
4958+struct MockDisplay : public mir::graphics::Display
4959+{
4960+public:
4961+ MOCK_METHOD1(for_each_display_sync_group, void(std::function<void(mir::graphics::DisplaySyncGroup&)> const&));
4962+ MOCK_CONST_METHOD0(configuration, std::unique_ptr<mir::graphics::DisplayConfiguration>());
4963+ MOCK_METHOD1(configure, void(mir::graphics::DisplayConfiguration const&));
4964+ MOCK_METHOD2(register_configuration_change_handler,
4965+ void(mir::graphics::EventHandlerRegister&, mir::graphics::DisplayConfigurationChangeHandler const&));
4966+
4967+ MOCK_METHOD3(register_pause_resume_handlers, void(mir::graphics::EventHandlerRegister&,
4968+ mir::graphics::DisplayPauseHandler const&,
4969+ mir::graphics::DisplayResumeHandler const&));
4970+ MOCK_METHOD0(pause, void());
4971+ MOCK_METHOD0(resume, void());
4972+ MOCK_METHOD1(create_hardware_cursor, std::shared_ptr<mir::graphics::Cursor>(std::shared_ptr<mir::graphics::CursorImage> const&));
4973+ MOCK_METHOD0(create_gl_context, std::unique_ptr<mir::graphics::GLContext>());
4974+};
4975+
4976+
4977+
4978+#endif // MOCKDISPLAY_H
4979
4980=== added file 'tests/common/mock_display_buffer.h'
4981--- tests/common/mock_display_buffer.h 1970-01-01 00:00:00 +0000
4982+++ tests/common/mock_display_buffer.h 2015-10-13 19:42:45 +0000
4983@@ -0,0 +1,43 @@
4984+/*
4985+ * Copyright (C) 2015 Canonical, Ltd.
4986+ *
4987+ * This program is free software: you can redistribute it and/or modify it under
4988+ * the terms of the GNU Lesser General Public License version 3, as published by
4989+ * the Free Software Foundation.
4990+ *
4991+ * This program is distributed in the hope that it will be useful, but WITHOUT
4992+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
4993+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
4994+ * Lesser General Public License for more details.
4995+ *
4996+ * You should have received a copy of the GNU Lesser General Public License
4997+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4998+ */
4999+
5000+#ifndef MOCK_DISPLAY_BUFFER_H
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches