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
41 property alias screenIndex: shell.screenIndex
44 function onFormFactorChanged() { calculateUsageMode(); }
47 onWidthChanged: calculateUsageMode();
48 property var overrideDeviceName: Screens.count > 1 ? "desktop" : false
51 id: _deviceConfiguration
53 // Override for convergence to set scale etc for second monitor
54 overrideName: root.overrideDeviceName
60 property Orientations orientations: Orientations {
62 // NB: native and primary orientations here don't map exactly to their QScreen counterparts
63 native_: root.width > root.height ? Qt.LandscapeOrientation : Qt.PortraitOrientation
65 primary: deviceConfiguration.primaryOrientation == deviceConfiguration.useNativeOrientation
66 ? native_ : deviceConfiguration.primaryOrientation
68 landscape: deviceConfiguration.landscapeOrientation
69 invertedLandscape: deviceConfiguration.invertedLandscapeOrientation
70 portrait: deviceConfiguration.portraitOrientation
71 invertedPortrait: deviceConfiguration.invertedPortraitOrientation
77 schema.id: "com.lomiri.Shell"
82 objectName: "oskSettings"
83 schema.id: "com.lomiri.keyboard.maliit"
86 property int physicalOrientation: QtQuickWindow.Screen.orientation
87 property bool orientationLocked: OrientationLock.enabled
88 property var orientationLock: OrientationLock
92 deviceFilter: InputInfo.Mouse
93 property int oldCount: 0
98 deviceFilter: InputInfo.TouchPad
99 property int oldCount: 0
104 deviceFilter: InputInfo.Keyboard
105 onDeviceAdded: forceOSKEnabled = autopilotDevicePresent();
106 onDeviceRemoved: forceOSKEnabled = autopilotDevicePresent();
110 id: touchScreensModel
111 deviceFilter: InputInfo.TouchScreen
116 property: "keyboardAttached"
117 value: keyboardsModel.count > 0
118 restoreMode: Binding.RestoreBinding
121 readonly property int pointerInputDevices: miceModel.count + touchPadModel.count
122 onPointerInputDevicesChanged: calculateUsageMode()
124 function calculateUsageMode() {
125 if (lomiriSettings.usageMode === undefined)
126 return; // gsettings isn't loaded yet, we'll try again in Component.onCompleted
128 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)
129 if (lomiriSettings.usageMode === "Windowed") {
130 if (Math.min(root.width, root.height) > units.gu(60)) {
131 if (pointerInputDevices === 0) {
132 // All pointer devices have been unplugged. Move to staged.
133 lomiriSettings.usageMode = "Staged";
136 // The display is not large enough, use staged.
137 lomiriSettings.usageMode = "Staged";
140 if (Math.min(root.width, root.height) > units.gu(60)) {
141 if (pointerInputDevices > 0 && pointerInputDevices > miceModel.oldCount + touchPadModel.oldCount) {
142 lomiriSettings.usageMode = "Windowed";
145 // Make sure we initialize to something sane
146 lomiriSettings.usageMode = "Staged";
149 miceModel.oldCount = miceModel.count;
150 touchPadModel.oldCount = touchPadModel.count;
153 /* FIXME: This exposes the NameRole as a work arround for lp:1542224.
154 * When QInputInfo exposes NameRole to QML, this should be removed.
156 property bool forceOSKEnabled: false
157 property var autopilotEmulatedDeviceNames: ["py-evdev-uinput"]
158 LomiriSortFilterProxyModel {
160 model: keyboardsModel
163 function autopilotDevicePresent() {
164 for(var i = 0; i < autopilotDevices.count; i++) {
165 var device = autopilotDevices.get(i);
166 if (autopilotEmulatedDeviceNames.indexOf(device.name) != -1) {
167 console.warn("Forcing the OSK to be enabled as there is an autopilot eumlated device present.")
174 property int orientation
175 onPhysicalOrientationChanged: {
176 if (!orientationLocked) {
177 orientation = physicalOrientation;
179 if (orientation !== physicalOrientation && !shell.showingGreeter) {
186 onOrientationLockedChanged: {
187 if (orientationLocked) {
188 orientationLock.savedOrientation = physicalOrientation;
190 orientation = physicalOrientation;
193 Component.onCompleted: {
194 if (orientationLocked) {
195 orientation = orientationLock.savedOrientation;
198 calculateUsageMode();
200 // We need to manually update this on startup as the binding
201 // below doesn't seem to have any effect at that stage
202 oskSettings.disableHeight = !shell.oskEnabled || shell.usageScenario == "desktop"
205 Component.onDestruction: {
206 const from_workspaces = root.screen.workspaces;
207 const from_workspaces_size = from_workspaces.count;
208 for (var i = 0; i < from_workspaces_size; i++) {
209 const from = from_workspaces.get(i);
210 WorkspaceManager.destroyWorkspace(from);
214 // we must rotate to a supported orientation regardless of shell's preference
215 property bool orientationChangesEnabled:
216 (shell.orientation & supportedOrientations) === 0 ? true
217 : shell.orientationChangesEnabled
221 restoreMode: Binding.RestoreBinding
222 property: "disableHeight"
223 value: !shell.oskEnabled || shell.usageScenario == "desktop"
227 target: lomiriSettings
228 restoreMode: Binding.RestoreBinding
229 property: "oskSwitchVisible"
230 value: shell.hasKeyboard
233 readonly property int supportedOrientations: shell.supportedOrientations
234 & (deviceConfiguration.supportedOrientations == deviceConfiguration.useNativeOrientation
235 ? orientations.native_
236 : deviceConfiguration.supportedOrientations)
238 // During desktop mode switches back to phone mode Qt seems to swallow
239 // supported orientations by itself, not emitting them. Cause them to be emitted
240 // using the attached property here.
241 QtQuickWindow.Screen.orientationUpdateMask: supportedOrientations
243 property int acceptedOrientationAngle: {
244 if (orientation & supportedOrientations) {
245 return QtQuickWindow.Screen.angleBetween(orientations.native_, orientation);
246 } else if (shell.orientation & supportedOrientations) {
248 return shell.orientationAngle;
249 } else if (angleToOrientation(shell.mainAppWindowOrientationAngle) & supportedOrientations) {
250 return shell.mainAppWindowOrientationAngle;
252 // rotate to some supported orientation as we can't stay where we currently are
253 // TODO: Choose the closest to the current one
254 if (supportedOrientations & Qt.PortraitOrientation) {
255 return QtQuickWindow.Screen.angleBetween(orientations.native_, Qt.PortraitOrientation);
256 } else if (supportedOrientations & Qt.LandscapeOrientation) {
257 return QtQuickWindow.Screen.angleBetween(orientations.native_, Qt.LandscapeOrientation);
258 } else if (supportedOrientations & Qt.InvertedPortraitOrientation) {
259 return QtQuickWindow.Screen.angleBetween(orientations.native_, Qt.InvertedPortraitOrientation);
260 } else if (supportedOrientations & Qt.InvertedLandscapeOrientation) {
261 return QtQuickWindow.Screen.angleBetween(orientations.native_, Qt.InvertedLandscapeOrientation);
263 // if all fails, fallback to primary orientation
264 return QtQuickWindow.Screen.angleBetween(orientations.native_, orientations.primary);
269 function angleToOrientation(angle) {
272 return orientations.native_;
274 return orientations.native_ === Qt.PortraitOrientation ? Qt.InvertedLandscapeOrientation
275 : Qt.PortraitOrientation;
277 return orientations.native_ === Qt.PortraitOrientation ? Qt.InvertedPortraitOrientation
278 : Qt.InvertedLandscapeOrientation;
280 return orientations.native_ === Qt.PortraitOrientation ? Qt.LandscapeOrientation
281 : Qt.InvertedPortraitOrientation;
283 console.warn("angleToOrientation: Invalid orientation angle: " + angle);
284 return orientations.primary;
290 objectName: "rotationStates"
293 shellCover: shellCover
294 shellSnapshot: shellSnapshot
302 orientation: root.angleToOrientation(orientationAngle)
303 orientations: root.orientations
304 nativeWidth: root.width
305 nativeHeight: root.height
306 mode: applicationArguments.mode
307 hasMouse: pointerInputDevices > 0
308 hasKeyboard: keyboardsModel.count > 0
309 hasTouchscreen: touchScreensModel.count > 0
310 supportsMultiColorLed: deviceConfiguration.supportsMultiColorLed
311 lightIndicators: root.lightIndicators
312 oskEnabled: (!hasKeyboard && Screens.count === 1) ||
313 lomiriSettings.alwaysShowOsk || forceOSKEnabled
315 // Multiscreen support: in addition to judging by the device type, go by the screen type.
316 // This allows very flexible usecases beyond the typical "connect a phone to a monitor".
317 // Status quo setups:
318 // - phone + external monitor: virtual touchpad on the device
319 // - tablet + external monitor: dual-screen desktop
320 // - desktop: Has all the bells and whistles of a fully fledged PC/laptop shell
322 if (lomiriSettings.usageMode === "Windowed") {
324 } else if (deviceConfiguration.category === "phone") {
326 } else if (deviceConfiguration.category === "tablet") {
329 if (screen.formFactor === Screen.Tablet) {
331 } else if (shell.hasTouchscreen) {
333 } else if (screen.formFactor === Screen.Phone) {
341 property real transformRotationAngle
342 property real transformOriginX
343 property real transformOriginY
345 transform: Rotation {
346 origin.x: shell.transformOriginX; origin.y: shell.transformOriginY; axis { x: 0; y: 0; z: 1 }
347 angle: shell.transformRotationAngle
354 screenOrientation: root.orientation
355 screenOrientationLocked: root.orientationLocked
358 orientationLock.savedOrientation = root.orientation
359 root.orientation = root.physicalOrientation
377 property real transformRotationAngle
378 property real transformOriginX
379 property real transformOriginY
381 transform: Rotation {
382 origin.x: shellSnapshot.transformOriginX; origin.y: shellSnapshot.transformOriginY;
383 axis { x: 0; y: 0; z: 1 }
384 angle: shellSnapshot.transformRotationAngle