Unity 8
OrientedShell.qml
1 /*
2  * Copyright (C) 2015 Canonical, Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 import QtQuick 2.12
18 import QtQuick.Window 2.2
19 import Unity.InputInfo 0.1
20 import Unity.Session 0.1
21 import Unity.Screens 0.1
22 import Utils 0.1
23 import GSettings 1.0
24 import "Components"
25 import "Rotation"
26 // Workaround https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1473471
27 import Ubuntu.Components 1.3
28 
29 Item {
30  id: root
31 
32  implicitWidth: units.gu(40)
33  implicitHeight: units.gu(71)
34 
35  onWidthChanged: calculateUsageMode();
36 
37  DeviceConfiguration {
38  id: deviceConfiguration
39  name: applicationArguments.deviceName
40  }
41 
42  property alias orientations: d.orientations
43  property bool lightIndicators: false
44 
45  Item {
46  id: d
47 
48  property Orientations orientations: Orientations {
49  id: orientations
50  // NB: native and primary orientations here don't map exactly to their QScreen counterparts
51  native_: root.width > root.height ? Qt.LandscapeOrientation : Qt.PortraitOrientation
52 
53  primary: deviceConfiguration.primaryOrientation == deviceConfiguration.useNativeOrientation
54  ? native_ : deviceConfiguration.primaryOrientation
55 
56  landscape: deviceConfiguration.landscapeOrientation
57  invertedLandscape: deviceConfiguration.invertedLandscapeOrientation
58  portrait: deviceConfiguration.portraitOrientation
59  invertedPortrait: deviceConfiguration.invertedPortraitOrientation
60  }
61  }
62 
63  GSettings {
64  id: unity8Settings
65  schema.id: "com.canonical.Unity8"
66  }
67 
68  GSettings {
69  id: oskSettings
70  objectName: "oskSettings"
71  schema.id: "com.canonical.keyboard.maliit"
72  }
73 
74  property int physicalOrientation: Screen.orientation
75  property bool orientationLocked: OrientationLock.enabled
76  property var orientationLock: OrientationLock
77 
78  InputDeviceModel {
79  id: miceModel
80  deviceFilter: InputInfo.Mouse
81  property int oldCount: 0
82  }
83 
84  InputDeviceModel {
85  id: touchPadModel
86  deviceFilter: InputInfo.TouchPad
87  property int oldCount: 0
88  }
89 
90  InputDeviceModel {
91  id: keyboardsModel
92  deviceFilter: InputInfo.Keyboard
93  onDeviceAdded: forceOSKEnabled = autopilotDevicePresent();
94  onDeviceRemoved: forceOSKEnabled = autopilotDevicePresent();
95  }
96 
97  InputDeviceModel {
98  id: touchScreensModel
99  deviceFilter: InputInfo.TouchScreen
100  }
101 
102  Binding {
103  target: QuickUtils
104  property: "keyboardAttached"
105  value: keyboardsModel.count > 0
106  }
107 
108  readonly property int pointerInputDevices: miceModel.count + touchPadModel.count
109  onPointerInputDevicesChanged: calculateUsageMode()
110 
111  function calculateUsageMode() {
112  if (unity8Settings.usageMode === undefined)
113  return; // gsettings isn't loaded yet, we'll try again in Component.onCompleted
114 
115  console.log("Calculating new usage mode. Pointer devices:", pointerInputDevices, "current mode:", unity8Settings.usageMode, "old device count", miceModel.oldCount + touchPadModel.oldCount, "root width:", root.width / units.gu(1), "height:", root.height / units.gu(1))
116  if (unity8Settings.usageMode === "Windowed") {
117  if (Math.min(root.width, root.height) > units.gu(60)) {
118  if (pointerInputDevices === 0) {
119  // All pointer devices have been unplugged. Move to staged.
120  unity8Settings.usageMode = "Staged";
121  }
122  } else {
123  // The display is not large enough, use staged.
124  unity8Settings.usageMode = "Staged";
125  }
126  } else {
127  if (Math.min(root.width, root.height) > units.gu(60)) {
128  if (pointerInputDevices > 0 && pointerInputDevices > miceModel.oldCount + touchPadModel.oldCount) {
129  unity8Settings.usageMode = "Windowed";
130  }
131  } else {
132  // Make sure we initialize to something sane
133  unity8Settings.usageMode = "Staged";
134  }
135  }
136  miceModel.oldCount = miceModel.count;
137  touchPadModel.oldCount = touchPadModel.count;
138  }
139 
140  /* FIXME: This exposes the NameRole as a work arround for lp:1542224.
141  * When QInputInfo exposes NameRole to QML, this should be removed.
142  */
143  property bool forceOSKEnabled: false
144  property var autopilotEmulatedDeviceNames: ["py-evdev-uinput"]
145  UnitySortFilterProxyModel {
146  id: autopilotDevices
147  model: keyboardsModel
148  }
149 
150  function autopilotDevicePresent() {
151  for(var i = 0; i < autopilotDevices.count; i++) {
152  var device = autopilotDevices.get(i);
153  if (autopilotEmulatedDeviceNames.indexOf(device.name) != -1) {
154  console.warn("Forcing the OSK to be enabled as there is an autopilot eumlated device present.")
155  return true;
156  }
157  }
158  return false;
159  }
160 
161  Screens {
162  id: screens
163  }
164 
165  property int orientation
166  onPhysicalOrientationChanged: {
167  if (!orientationLocked) {
168  orientation = physicalOrientation;
169  }
170  }
171  onOrientationLockedChanged: {
172  if (orientationLocked) {
173  orientationLock.savedOrientation = physicalOrientation;
174  } else {
175  orientation = physicalOrientation;
176  }
177  }
178  Component.onCompleted: {
179  if (orientationLocked) {
180  orientation = orientationLock.savedOrientation;
181  }
182 
183  calculateUsageMode();
184 
185  // We need to manually update this on startup as the binding
186  // below doesn't seem to have any effect at that stage
187  oskSettings.disableHeight = !shell.oskEnabled || shell.usageScenario == "desktop"
188  }
189 
190  // we must rotate to a supported orientation regardless of shell's preference
191  property bool orientationChangesEnabled:
192  (shell.orientation & supportedOrientations) === 0 ? true
193  : shell.orientationChangesEnabled
194 
195  Binding {
196  target: oskSettings
197  property: "disableHeight"
198  value: !shell.oskEnabled || shell.usageScenario == "desktop"
199  }
200 
201  Binding {
202  target: unity8Settings
203  property: "oskSwitchVisible"
204  value: shell.hasKeyboard
205  }
206 
207  readonly property int supportedOrientations: shell.supportedOrientations
208  & (deviceConfiguration.supportedOrientations == deviceConfiguration.useNativeOrientation
209  ? orientations.native_
210  : deviceConfiguration.supportedOrientations)
211 
212  property int acceptedOrientationAngle: {
213  if (orientation & supportedOrientations) {
214  return Screen.angleBetween(orientations.native_, orientation);
215  } else if (shell.orientation & supportedOrientations) {
216  // stay where we are
217  return shell.orientationAngle;
218  } else if (angleToOrientation(shell.mainAppWindowOrientationAngle) & supportedOrientations) {
219  return shell.mainAppWindowOrientationAngle;
220  } else {
221  // rotate to some supported orientation as we can't stay where we currently are
222  // TODO: Choose the closest to the current one
223  if (supportedOrientations & Qt.PortraitOrientation) {
224  return Screen.angleBetween(orientations.native_, Qt.PortraitOrientation);
225  } else if (supportedOrientations & Qt.LandscapeOrientation) {
226  return Screen.angleBetween(orientations.native_, Qt.LandscapeOrientation);
227  } else if (supportedOrientations & Qt.InvertedPortraitOrientation) {
228  return Screen.angleBetween(orientations.native_, Qt.InvertedPortraitOrientation);
229  } else if (supportedOrientations & Qt.InvertedLandscapeOrientation) {
230  return Screen.angleBetween(orientations.native_, Qt.InvertedLandscapeOrientation);
231  } else {
232  // if all fails, fallback to primary orientation
233  return Screen.angleBetween(orientations.native_, orientations.primary);
234  }
235  }
236  }
237 
238  function angleToOrientation(angle) {
239  switch (angle) {
240  case 0:
241  return orientations.native_;
242  case 90:
243  return orientations.native_ === Qt.PortraitOrientation ? Qt.InvertedLandscapeOrientation
244  : Qt.PortraitOrientation;
245  case 180:
246  return orientations.native_ === Qt.PortraitOrientation ? Qt.InvertedPortraitOrientation
247  : Qt.InvertedLandscapeOrientation;
248  case 270:
249  return orientations.native_ === Qt.PortraitOrientation ? Qt.LandscapeOrientation
250  : Qt.InvertedPortraitOrientation;
251  default:
252  console.warn("angleToOrientation: Invalid orientation angle: " + angle);
253  return orientations.primary;
254  }
255  }
256 
257  RotationStates {
258  id: rotationStates
259  objectName: "rotationStates"
260  orientedShell: root
261  shell: shell
262  shellCover: shellCover
263  shellSnapshot: shellSnapshot
264  }
265 
266  Shell {
267  id: shell
268  objectName: "shell"
269  width: root.width
270  height: root.height
271  orientation: root.angleToOrientation(orientationAngle)
272  orientations: root.orientations
273  nativeWidth: root.width
274  nativeHeight: root.height
275  mode: applicationArguments.mode
276  interactiveBlur: applicationArguments.interactiveBlur
277  hasMouse: pointerInputDevices > 0
278  hasKeyboard: keyboardsModel.count > 0
279  hasTouchscreen: touchScreensModel.count > 0
280  supportsMultiColorLed: deviceConfiguration.supportsMultiColorLed
281  lightIndicators: root.lightIndicators
282 
283  // Since we dont have proper multiscreen support yet
284  // hardcode screen count to only show osk on this screen
285  // when it's the only one connected.
286  // FIXME once multiscreen has landed
287  oskEnabled: (!hasKeyboard && screens.count === 1) ||
288  unity8Settings.alwaysShowOsk || forceOSKEnabled
289 
290  usageScenario: {
291  if (unity8Settings.usageMode === "Windowed") {
292  return "desktop";
293  } else {
294  if (deviceConfiguration.category === "phone") {
295  return "phone";
296  } else {
297  return "tablet";
298  }
299  }
300  }
301 
302  property real transformRotationAngle
303  property real transformOriginX
304  property real transformOriginY
305 
306  transform: Rotation {
307  origin.x: shell.transformOriginX; origin.y: shell.transformOriginY; axis { x: 0; y: 0; z: 1 }
308  angle: shell.transformRotationAngle
309  }
310  }
311 
312  Rectangle {
313  id: shellCover
314  color: "black"
315  anchors.fill: parent
316  visible: false
317  }
318 
319  ItemSnapshot {
320  id: shellSnapshot
321  target: shell
322  visible: false
323  width: root.width
324  height: root.height
325 
326  property real transformRotationAngle
327  property real transformOriginX
328  property real transformOriginY
329 
330  transform: Rotation {
331  origin.x: shellSnapshot.transformOriginX; origin.y: shellSnapshot.transformOriginY;
332  axis { x: 0; y: 0; z: 1 }
333  angle: shellSnapshot.transformRotationAngle
334  }
335  }
336 }