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/>.
18import QtQuick.Window 2.2 as QtQuickWindow
19import Lomiri.InputInfo 0.1
20import Lomiri.Session 0.1
21import WindowManager 1.0
26// Workaround https://bugs.launchpad.net/lomiri/+source/lomiri/+bug/1473471
27import Lomiri.Components 1.3
32 implicitWidth: units.gu(40)
33 implicitHeight: units.gu(71)
35 property alias deviceConfiguration: _deviceConfiguration
36 property alias orientations: d.orientations
37 property bool lightIndicators: false
39 property var screen: null
42 onFormFactorChanged: calculateUsageMode();
45 onWidthChanged: calculateUsageMode();
46 property var overrideDeviceName: Screens.count > 1 ? "desktop" : false
49 id: _deviceConfiguration
51 // Override for convergence to set scale etc for second monitor
52 overrideName: root.overrideDeviceName
58 property Orientations orientations: Orientations {
60 // NB: native and primary orientations here don't map exactly to their QScreen counterparts
61 native_: root.width > root.height ? Qt.LandscapeOrientation : Qt.PortraitOrientation
63 primary: deviceConfiguration.primaryOrientation == deviceConfiguration.useNativeOrientation
64 ? native_ : deviceConfiguration.primaryOrientation
66 landscape: deviceConfiguration.landscapeOrientation
67 invertedLandscape: deviceConfiguration.invertedLandscapeOrientation
68 portrait: deviceConfiguration.portraitOrientation
69 invertedPortrait: deviceConfiguration.invertedPortraitOrientation
75 schema.id: "com.lomiri.Shell"
80 objectName: "oskSettings"
81 schema.id: "com.lomiri.keyboard.maliit"
84 property int physicalOrientation: QtQuickWindow.Screen.orientation
85 property bool orientationLocked: OrientationLock.enabled
86 property var orientationLock: OrientationLock
90 deviceFilter: InputInfo.Mouse
91 property int oldCount: 0
96 deviceFilter: InputInfo.TouchPad
97 property int oldCount: 0
102 deviceFilter: InputInfo.Keyboard
103 onDeviceAdded: forceOSKEnabled = autopilotDevicePresent();
104 onDeviceRemoved: forceOSKEnabled = autopilotDevicePresent();
108 id: touchScreensModel
109 deviceFilter: InputInfo.TouchScreen
114 property: "keyboardAttached"
115 value: keyboardsModel.count > 0
118 readonly property int pointerInputDevices: miceModel.count + touchPadModel.count
119 onPointerInputDevicesChanged: calculateUsageMode()
121 function calculateUsageMode() {
122 if (lomiriSettings.usageMode === undefined)
123 return; // gsettings isn't loaded yet, we'll try again in Component.onCompleted
125 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)
126 if (lomiriSettings.usageMode === "Windowed") {
127 if (Math.min(root.width, root.height) > units.gu(60)) {
128 if (pointerInputDevices === 0) {
129 // All pointer devices have been unplugged. Move to staged.
130 lomiriSettings.usageMode = "Staged";
133 // The display is not large enough, use staged.
134 lomiriSettings.usageMode = "Staged";
137 if (Math.min(root.width, root.height) > units.gu(60)) {
138 if (pointerInputDevices > 0 && pointerInputDevices > miceModel.oldCount + touchPadModel.oldCount) {
139 lomiriSettings.usageMode = "Windowed";
142 // Make sure we initialize to something sane
143 lomiriSettings.usageMode = "Staged";
146 miceModel.oldCount = miceModel.count;
147 touchPadModel.oldCount = touchPadModel.count;
150 /* FIXME: This exposes the NameRole as a work arround for lp:1542224.
151 * When QInputInfo exposes NameRole to QML, this should be removed.
153 property bool forceOSKEnabled: false
154 property var autopilotEmulatedDeviceNames: ["py-evdev-uinput"]
155 LomiriSortFilterProxyModel {
157 model: keyboardsModel
160 function autopilotDevicePresent() {
161 for(var i = 0; i < autopilotDevices.count; i++) {
162 var device = autopilotDevices.get(i);
163 if (autopilotEmulatedDeviceNames.indexOf(device.name) != -1) {
164 console.warn("Forcing the OSK to be enabled as there is an autopilot eumlated device present.")
171 property int orientation
172 onPhysicalOrientationChanged: {
173 if (!orientationLocked) {
174 orientation = physicalOrientation;
177 onOrientationLockedChanged: {
178 if (orientationLocked) {
179 orientationLock.savedOrientation = physicalOrientation;
181 orientation = physicalOrientation;
184 Component.onCompleted: {
185 if (orientationLocked) {
186 orientation = orientationLock.savedOrientation;
189 calculateUsageMode();
191 // We need to manually update this on startup as the binding
192 // below doesn't seem to have any effect at that stage
193 oskSettings.disableHeight = !shell.oskEnabled || shell.usageScenario == "desktop"
196 Component.onDestruction: {
197 const from_workspaces = root.screen.workspaces;
198 const from_workspaces_size = from_workspaces.count;
199 for (var i = 0; i < from_workspaces_size; i++) {
200 const from = from_workspaces.get(i);
201 WorkspaceManager.destroyWorkspace(from);
205 // we must rotate to a supported orientation regardless of shell's preference
206 property bool orientationChangesEnabled:
207 (shell.orientation & supportedOrientations) === 0 ? true
208 : shell.orientationChangesEnabled
212 property: "disableHeight"
213 value: !shell.oskEnabled || shell.usageScenario == "desktop"
217 target: lomiriSettings
218 property: "oskSwitchVisible"
219 value: shell.hasKeyboard
222 readonly property int supportedOrientations: shell.supportedOrientations
223 & (deviceConfiguration.supportedOrientations == deviceConfiguration.useNativeOrientation
224 ? orientations.native_
225 : deviceConfiguration.supportedOrientations)
227 // During desktop mode switches back to phone mode Qt seems to swallow
228 // supported orientations by itself, not emitting them. Cause them to be emitted
229 // using the attached property here.
230 QtQuickWindow.Screen.orientationUpdateMask: supportedOrientations
232 property int acceptedOrientationAngle: {
233 if (orientation & supportedOrientations) {
234 return QtQuickWindow.Screen.angleBetween(orientations.native_, orientation);
235 } else if (shell.orientation & supportedOrientations) {
237 return shell.orientationAngle;
238 } else if (angleToOrientation(shell.mainAppWindowOrientationAngle) & supportedOrientations) {
239 return shell.mainAppWindowOrientationAngle;
241 // rotate to some supported orientation as we can't stay where we currently are
242 // TODO: Choose the closest to the current one
243 if (supportedOrientations & Qt.PortraitOrientation) {
244 return QtQuickWindow.Screen.angleBetween(orientations.native_, Qt.PortraitOrientation);
245 } else if (supportedOrientations & Qt.LandscapeOrientation) {
246 return QtQuickWindow.Screen.angleBetween(orientations.native_, Qt.LandscapeOrientation);
247 } else if (supportedOrientations & Qt.InvertedPortraitOrientation) {
248 return QtQuickWindow.Screen.angleBetween(orientations.native_, Qt.InvertedPortraitOrientation);
249 } else if (supportedOrientations & Qt.InvertedLandscapeOrientation) {
250 return QtQuickWindow.Screen.angleBetween(orientations.native_, Qt.InvertedLandscapeOrientation);
252 // if all fails, fallback to primary orientation
253 return QtQuickWindow.Screen.angleBetween(orientations.native_, orientations.primary);
258 function angleToOrientation(angle) {
261 return orientations.native_;
263 return orientations.native_ === Qt.PortraitOrientation ? Qt.InvertedLandscapeOrientation
264 : Qt.PortraitOrientation;
266 return orientations.native_ === Qt.PortraitOrientation ? Qt.InvertedPortraitOrientation
267 : Qt.InvertedLandscapeOrientation;
269 return orientations.native_ === Qt.PortraitOrientation ? Qt.LandscapeOrientation
270 : Qt.InvertedPortraitOrientation;
272 console.warn("angleToOrientation: Invalid orientation angle: " + angle);
273 return orientations.primary;
279 objectName: "rotationStates"
282 shellCover: shellCover
283 shellSnapshot: shellSnapshot
291 orientation: root.angleToOrientation(orientationAngle)
292 orientations: root.orientations
293 nativeWidth: root.width
294 nativeHeight: root.height
295 mode: applicationArguments.mode
296 hasMouse: pointerInputDevices > 0
297 hasKeyboard: keyboardsModel.count > 0
298 hasTouchscreen: touchScreensModel.count > 0
299 supportsMultiColorLed: deviceConfiguration.supportsMultiColorLed
300 lightIndicators: root.lightIndicators
301 oskEnabled: (!hasKeyboard && Screens.count === 1) ||
302 lomiriSettings.alwaysShowOsk || forceOSKEnabled
304 // Multiscreen support: in addition to judging by the device type, go by the screen type.
305 // This allows very flexible usecases beyond the typical "connect a phone to a monitor".
306 // Status quo setups:
307 // - phone + external monitor: virtual touchpad on the device
308 // - tablet + external monitor: dual-screen desktop
309 // - desktop: Has all the bells and whistles of a fully fledged PC/laptop shell
311 if (lomiriSettings.usageMode === "Windowed") {
313 } else if (deviceConfiguration.category === "phone") {
315 } else if (deviceConfiguration.category === "tablet") {
318 if (screen.formFactor === Screen.Tablet) {
320 } else if (screen.formFactor === Screen.Phone) {
328 property real transformRotationAngle
329 property real transformOriginX
330 property real transformOriginY
332 transform: Rotation {
333 origin.x: shell.transformOriginX; origin.y: shell.transformOriginY; axis { x: 0; y: 0; z: 1 }
334 angle: shell.transformRotationAngle
352 property real transformRotationAngle
353 property real transformOriginX
354 property real transformOriginY
356 transform: Rotation {
357 origin.x: shellSnapshot.transformOriginX; origin.y: shellSnapshot.transformOriginY;
358 axis { x: 0; y: 0; z: 1 }
359 angle: shellSnapshot.transformRotationAngle