2 * Copyright (C) 2015 Canonical Ltd.
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.
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.
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/>.
19import QtQuick.Window 2.2 as QtQuickWindow
20import Lomiri.InputInfo 0.1
21import Lomiri.Session 0.1
22import WindowManager 1.0
27// Workaround https://bugs.launchpad.net/lomiri/+source/lomiri/+bug/1473471
28import Lomiri.Components 1.3
33 implicitWidth: units.gu(40)
34 implicitHeight: units.gu(71)
36 property alias deviceConfiguration: _deviceConfiguration
37 property alias orientations: d.orientations
38 property bool lightIndicators: false
40 property var screen: null
43 function onFormFactorChanged() { calculateUsageMode(); }
46 onWidthChanged: calculateUsageMode();
47 property var overrideDeviceName: Screens.count > 1 ? "desktop" : false
50 id: _deviceConfiguration
52 // Override for convergence to set scale etc for second monitor
53 overrideName: root.overrideDeviceName
59 property Orientations orientations: Orientations {
61 // NB: native and primary orientations here don't map exactly to their QScreen counterparts
62 native_: root.width > root.height ? Qt.LandscapeOrientation : Qt.PortraitOrientation
64 primary: deviceConfiguration.primaryOrientation == deviceConfiguration.useNativeOrientation
65 ? native_ : deviceConfiguration.primaryOrientation
67 landscape: deviceConfiguration.landscapeOrientation
68 invertedLandscape: deviceConfiguration.invertedLandscapeOrientation
69 portrait: deviceConfiguration.portraitOrientation
70 invertedPortrait: deviceConfiguration.invertedPortraitOrientation
76 schema.id: "com.lomiri.Shell"
81 objectName: "oskSettings"
82 schema.id: "com.lomiri.keyboard.maliit"
85 property int physicalOrientation: QtQuickWindow.Screen.orientation
86 property bool orientationLocked: OrientationLock.enabled
87 property var orientationLock: OrientationLock
91 deviceFilter: InputInfo.Mouse
92 property int oldCount: 0
97 deviceFilter: InputInfo.TouchPad
98 property int oldCount: 0
103 deviceFilter: InputInfo.Keyboard
104 onDeviceAdded: forceOSKEnabled = autopilotDevicePresent();
105 onDeviceRemoved: forceOSKEnabled = autopilotDevicePresent();
109 id: touchScreensModel
110 deviceFilter: InputInfo.TouchScreen
115 property: "keyboardAttached"
116 value: keyboardsModel.count > 0
117 restoreMode: Binding.RestoreBinding
120 readonly property int pointerInputDevices: miceModel.count + touchPadModel.count
121 onPointerInputDevicesChanged: calculateUsageMode()
123 function calculateUsageMode() {
124 if (lomiriSettings.usageMode === undefined)
125 return; // gsettings isn't loaded yet, we'll try again in Component.onCompleted
127 console.log("Calculating new usage mode. Pointer devices:", pointerInputDevices, "current mode:", lomiriSettings.usageMode, "old device count", miceModel.oldCount + touchPadModel.oldCount, "root width:", root.width, "height:", root.height)
128 if (lomiriSettings.usageMode === "Windowed") {
129 if (Math.min(root.width, root.height) > units.gu(60)) {
130 if (pointerInputDevices === 0) {
131 // All pointer devices have been unplugged. Move to staged.
132 lomiriSettings.usageMode = "Staged";
135 // The display is not large enough, use staged.
136 lomiriSettings.usageMode = "Staged";
139 if (Math.min(root.width, root.height) > units.gu(60)) {
140 if (pointerInputDevices > 0 && pointerInputDevices > miceModel.oldCount + touchPadModel.oldCount) {
141 lomiriSettings.usageMode = "Windowed";
144 // Make sure we initialize to something sane
145 lomiriSettings.usageMode = "Staged";
148 miceModel.oldCount = miceModel.count;
149 touchPadModel.oldCount = touchPadModel.count;
152 /* FIXME: This exposes the NameRole as a work arround for lp:1542224.
153 * When QInputInfo exposes NameRole to QML, this should be removed.
155 property bool forceOSKEnabled: false
156 property var autopilotEmulatedDeviceNames: ["py-evdev-uinput"]
157 LomiriSortFilterProxyModel {
159 model: keyboardsModel
162 function autopilotDevicePresent() {
163 for(var i = 0; i < autopilotDevices.count; i++) {
164 var device = autopilotDevices.get(i);
165 if (autopilotEmulatedDeviceNames.indexOf(device.name) != -1) {
166 console.warn("Forcing the OSK to be enabled as there is an autopilot eumlated device present.")
173 property int orientation
174 onPhysicalOrientationChanged: {
175 if (!orientationLocked) {
176 orientation = physicalOrientation;
178 if (orientation !== physicalOrientation && !shell.showingGreeter) {
185 onOrientationLockedChanged: {
186 if (orientationLocked) {
187 orientationLock.savedOrientation = physicalOrientation;
189 orientation = physicalOrientation;
192 Component.onCompleted: {
193 if (orientationLocked) {
194 orientation = orientationLock.savedOrientation;
197 calculateUsageMode();
199 // We need to manually update this on startup as the binding
200 // below doesn't seem to have any effect at that stage
201 oskSettings.disableHeight = !shell.oskEnabled || shell.usageScenario == "desktop"
204 Component.onDestruction: {
205 const from_workspaces = root.screen.workspaces;
206 const from_workspaces_size = from_workspaces.count;
207 for (var i = 0; i < from_workspaces_size; i++) {
208 const from = from_workspaces.get(i);
209 WorkspaceManager.destroyWorkspace(from);
213 // we must rotate to a supported orientation regardless of shell's preference
214 property bool orientationChangesEnabled:
215 (shell.orientation & supportedOrientations) === 0 ? true
216 : shell.orientationChangesEnabled
220 restoreMode: Binding.RestoreBinding
221 property: "disableHeight"
222 value: !shell.oskEnabled || shell.usageScenario == "desktop"
226 target: lomiriSettings
227 restoreMode: Binding.RestoreBinding
228 property: "oskSwitchVisible"
229 value: shell.hasKeyboard
232 readonly property int supportedOrientations: shell.supportedOrientations
233 & (deviceConfiguration.supportedOrientations == deviceConfiguration.useNativeOrientation
234 ? orientations.native_
235 : deviceConfiguration.supportedOrientations)
237 // During desktop mode switches back to phone mode Qt seems to swallow
238 // supported orientations by itself, not emitting them. Cause them to be emitted
239 // using the attached property here.
240 QtQuickWindow.Screen.orientationUpdateMask: supportedOrientations
242 property int acceptedOrientationAngle: {
243 if (orientation & supportedOrientations) {
244 return QtQuickWindow.Screen.angleBetween(orientations.native_, orientation);
245 } else if (shell.orientation & supportedOrientations) {
247 return shell.orientationAngle;
248 } else if (angleToOrientation(shell.mainAppWindowOrientationAngle) & supportedOrientations) {
249 return shell.mainAppWindowOrientationAngle;
251 // rotate to some supported orientation as we can't stay where we currently are
252 // TODO: Choose the closest to the current one
253 if (supportedOrientations & Qt.PortraitOrientation) {
254 return QtQuickWindow.Screen.angleBetween(orientations.native_, Qt.PortraitOrientation);
255 } else if (supportedOrientations & Qt.LandscapeOrientation) {
256 return QtQuickWindow.Screen.angleBetween(orientations.native_, Qt.LandscapeOrientation);
257 } else if (supportedOrientations & Qt.InvertedPortraitOrientation) {
258 return QtQuickWindow.Screen.angleBetween(orientations.native_, Qt.InvertedPortraitOrientation);
259 } else if (supportedOrientations & Qt.InvertedLandscapeOrientation) {
260 return QtQuickWindow.Screen.angleBetween(orientations.native_, Qt.InvertedLandscapeOrientation);
262 // if all fails, fallback to primary orientation
263 return QtQuickWindow.Screen.angleBetween(orientations.native_, orientations.primary);
268 function angleToOrientation(angle) {
271 return orientations.native_;
273 return orientations.native_ === Qt.PortraitOrientation ? Qt.InvertedLandscapeOrientation
274 : Qt.PortraitOrientation;
276 return orientations.native_ === Qt.PortraitOrientation ? Qt.InvertedPortraitOrientation
277 : Qt.InvertedLandscapeOrientation;
279 return orientations.native_ === Qt.PortraitOrientation ? Qt.LandscapeOrientation
280 : Qt.InvertedPortraitOrientation;
282 console.warn("angleToOrientation: Invalid orientation angle: " + angle);
283 return orientations.primary;
289 objectName: "rotationStates"
292 shellCover: shellCover
293 shellSnapshot: shellSnapshot
301 orientation: root.angleToOrientation(orientationAngle)
302 orientations: root.orientations
303 nativeWidth: root.width
304 nativeHeight: root.height
305 mode: applicationArguments.mode
306 hasMouse: pointerInputDevices > 0
307 hasKeyboard: keyboardsModel.count > 0
308 hasTouchscreen: touchScreensModel.count > 0
309 supportsMultiColorLed: deviceConfiguration.supportsMultiColorLed
310 lightIndicators: root.lightIndicators
311 oskEnabled: (!hasKeyboard && Screens.count === 1) ||
312 lomiriSettings.alwaysShowOsk || forceOSKEnabled
314 // Multiscreen support: in addition to judging by the device type, go by the screen type.
315 // This allows very flexible usecases beyond the typical "connect a phone to a monitor".
316 // Status quo setups:
317 // - phone + external monitor: virtual touchpad on the device
318 // - tablet + external monitor: dual-screen desktop
319 // - desktop: Has all the bells and whistles of a fully fledged PC/laptop shell
321 if (lomiriSettings.usageMode === "Windowed") {
323 } else if (deviceConfiguration.category === "phone") {
325 } else if (deviceConfiguration.category === "tablet") {
328 if (screen.formFactor === Screen.Tablet) {
330 } else if (shell.hasTouchscreen) {
332 } else if (screen.formFactor === Screen.Phone) {
340 property real transformRotationAngle
341 property real transformOriginX
342 property real transformOriginY
344 transform: Rotation {
345 origin.x: shell.transformOriginX; origin.y: shell.transformOriginY; axis { x: 0; y: 0; z: 1 }
346 angle: shell.transformRotationAngle
353 readonly property real visibleOpacity: 0.8
354 readonly property bool rotateAvailable: root.orientationLocked && root.physicalOrientation !== root.orientation
356 anchors.margins: units.gu(3)
359 when: !rotateButton.rotateAvailable
362 anchors.right: parent.left
363 anchors.top: parent.bottom
367 when: rotateButton.rotateAvailable && root.physicalOrientation == Qt.InvertedLandscapeOrientation
370 anchors.left: parent.left
371 anchors.bottom: parent.bottom
375 when: rotateButton.rotateAvailable && root.physicalOrientation == Qt.LandscapeOrientation
378 anchors.right: parent.right
379 anchors.top: parent.top
383 anchors.topMargin: shell.shellMargin
387 when: rotateButton.rotateAvailable && root.physicalOrientation == Qt.PortraitOrientation
390 anchors.right: parent.right
391 anchors.bottom: parent.bottom
395 when: rotateButton.rotateAvailable && root.physicalOrientation == Qt.InvertedPortraitOrientation
398 anchors.left: parent.left
399 anchors.top: parent.top
403 anchors.topMargin: shell.shellMargin
412 color: theme.palette.normal.background
415 color: theme.palette.normal.backgroundText
425 hideAnimation.restart()
432 implicitWidth: units.gu(3)
433 implicitHeight: implicitWidth
434 anchors.centerIn: parent
436 color: theme.palette.normal.backgroundText
443 orientationLock.savedOrientation = root.orientation
444 root.orientation = root.physicalOrientation
448 LomiriNumberAnimation {
455 to: rotateButton.visibleOpacity
456 duration: LomiriAnimation.SlowDuration
459 LomiriNumberAnimation {
467 duration: LomiriAnimation.FastDuration
470 SequentialAnimation {
471 running: rotateButton.visible
475 duration: LomiriAnimation.SnapDuration
477 direction: RotationAnimation.Shortest
479 NumberAnimation { target: icon; duration: LomiriAnimation.SnapDuration; property: "opacity"; to: 1 }
480 PauseAnimation { duration: LomiriAnimation.SlowDuration }
483 duration: LomiriAnimation.SlowDuration
484 to: root.orientationLocked ? QtQuickWindow.Screen.angleBetween(root.orientation, root.physicalOrientation) : 0
485 direction: RotationAnimation.Shortest
487 PauseAnimation { duration: LomiriAnimation.SlowDuration }
488 NumberAnimation { target: icon; duration: LomiriAnimation.SnapDuration; property: "opacity"; to: 0 }
490 onFinished: rotateButton.hide()
499 showAnimation.restart()
508 onTriggered: rotateButton.hide()
526 property real transformRotationAngle
527 property real transformOriginX
528 property real transformOriginY
530 transform: Rotation {
531 origin.x: shellSnapshot.transformOriginX; origin.y: shellSnapshot.transformOriginY;
532 axis { x: 0; y: 0; z: 1 }
533 angle: shellSnapshot.transformRotationAngle