Unity 8
Shell.qml
1 /*
2  * Copyright (C) 2013-2016 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.4
18 import QtQuick.Window 2.2
19 import AccountsService 0.1
20 import Unity.Application 0.1
21 import Ubuntu.Components 1.3
22 import Ubuntu.Components.Popups 1.3
23 import Ubuntu.Gestures 0.1
24 import Ubuntu.Telephony 0.1 as Telephony
25 import Unity.Connectivity 0.1
26 import Unity.Launcher 0.1
27 import GlobalShortcut 1.0 // has to be before Utils, because of WindowInputFilter
28 import GSettings 1.0
29 import ImageCache 0.1
30 import Utils 0.1
31 import Powerd 0.1
32 import SessionBroadcast 0.1
33 import "Greeter"
34 import "Launcher"
35 import "Panel"
36 import "Components"
37 import "Notifications"
38 import "Stage"
39 import "Tutorial"
40 import "Wizard"
41 import Unity.Notifications 1.0 as NotificationBackend
42 import Unity.Session 0.1
43 import Unity.DashCommunicator 0.1
44 import Unity.Indicators 0.1 as Indicators
45 import Cursor 1.1
46 import WindowManager 1.0
47 
48 
49 StyledItem {
50  id: shell
51 
52  theme.name: "Ubuntu.Components.Themes.SuruDark"
53 
54  // to be set from outside
55  property int orientationAngle: 0
56  property int orientation
57  property Orientations orientations
58  property real nativeWidth
59  property real nativeHeight
60  property alias indicatorAreaShowProgress: panel.indicatorAreaShowProgress
61  property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
62  property string mode: "full-greeter"
63  property alias oskEnabled: inputMethod.enabled
64  function updateFocusedAppOrientation() {
65  stage.updateFocusedAppOrientation();
66  }
67  function updateFocusedAppOrientationAnimated() {
68  stage.updateFocusedAppOrientationAnimated();
69  }
70  property bool hasMouse: false
71 
72  // to be read from outside
73  readonly property int mainAppWindowOrientationAngle: stage.mainAppWindowOrientationAngle
74 
75  readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
76  && stage.orientationChangesEnabled
77  && (!greeter || !greeter.animating)
78 
79  readonly property bool showingGreeter: greeter && greeter.shown
80 
81  property bool startingUp: true
82  Timer { id: finishStartUpTimer; interval: 500; onTriggered: startingUp = false }
83 
84  property int supportedOrientations: {
85  if (startingUp) {
86  // Ensure we don't rotate during start up
87  return Qt.PrimaryOrientation;
88  } else if (showingGreeter || notifications.topmostIsFullscreen) {
89  return Qt.PrimaryOrientation;
90  } else {
91  return shell.orientations.map(stage.supportedOrientations);
92  }
93  }
94 
95  readonly property var mainApp: stage.mainApp
96 
97  onMainAppChanged: {
98  if (mainApp) {
99  _onMainAppChanged(mainApp.appId);
100  }
101  }
102  Connections {
103  target: ApplicationManager
104  onFocusRequested: {
105  if (shell.mainApp && shell.mainApp.appId === appId) {
106  _onMainAppChanged(appId);
107  }
108  }
109  }
110  function _onMainAppChanged(appId) {
111  if (wizard.active && appId != "" && appId != "unity8-dash") {
112  // If this happens on first boot, we may be in the
113  // wizard while receiving a call. But a call is more
114  // important than the wizard so just bail out of it.
115  wizard.hide();
116  }
117 
118  if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
119  // If we are in the middle of a call, make dialer lockedApp and show it.
120  // This can happen if user backs out of dialer back to greeter, then
121  // launches dialer again.
122  greeter.lockedApp = appId;
123  }
124  greeter.notifyAppFocusRequested(appId);
125 
126  panel.indicators.hide();
127  launcher.hide(launcher.ignoreHideIfMouseOverLauncher);
128  }
129 
130  // For autopilot consumption
131  readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
132 
133  // Note when greeter is waiting on PAM, so that we can disable edges until
134  // we know which user data to show and whether the session is locked.
135  readonly property bool waitingOnGreeter: greeter && greeter.waiting
136 
137  property real edgeSize: units.gu(settings.edgeDragWidth)
138 
139  WallpaperResolver {
140  id: wallpaperResolver
141  objectName: "wallpaperResolver"
142 
143  readonly property url defaultBackground: "file://" + Constants.defaultWallpaper
144  readonly property bool hasCustomBackground: background != defaultBackground
145 
146  // Use a cached version of the scaled-down wallpaper (as sometimes the
147  // image can be quite big compared to the device size, including for
148  // our default wallpaper). We use a name=wallpaper argument here to
149  // make sure we don't litter our cache with lots of scaled images. We
150  // only need to bother caching one at a time.
151  readonly property url cachedBackground: background.toString().indexOf("file:///") === 0 ? "image://unity8imagecache/" + background + "?name=wallpaper" : background
152 
153  GSettings {
154  id: backgroundSettings
155  schema.id: "org.gnome.desktop.background"
156  }
157 
158  candidates: [
159  AccountsService.backgroundFile,
160  backgroundSettings.pictureUri,
161  defaultBackground
162  ]
163  }
164 
165  readonly property alias greeter: greeterLoader.item
166 
167  function activateApplication(appId) {
168  // Either open the app in our own session, or -- if we're acting as a
169  // greeter -- ask the user's session to open it for us.
170  if (shell.mode === "greeter") {
171  activateURL("application:///" + appId + ".desktop");
172  } else {
173  startApp(appId);
174  }
175  }
176 
177  function activateURL(url) {
178  SessionBroadcast.requestUrlStart(AccountsService.user, url);
179  greeter.notifyUserRequestedApp();
180  panel.indicators.hide();
181  }
182 
183  function startApp(appId) {
184  if (ApplicationManager.findApplication(appId)) {
185  ApplicationManager.requestFocusApplication(appId);
186  } else {
187  ApplicationManager.startApplication(appId);
188  }
189  }
190 
191  function startLockedApp(app) {
192  if (greeter.locked) {
193  greeter.lockedApp = app;
194  }
195  startApp(app); // locked apps are always in our same session
196  }
197 
198  Binding {
199  target: LauncherModel
200  property: "applicationManager"
201  value: ApplicationManager
202  }
203 
204  Component.onCompleted: {
205  finishStartUpTimer.start();
206  }
207 
208  VolumeControl {
209  id: volumeControl
210  }
211 
212  DashCommunicator {
213  id: dash
214  objectName: "dashCommunicator"
215  }
216 
217  PhysicalKeysMapper {
218  id: physicalKeysMapper
219  objectName: "physicalKeysMapper"
220 
221  onPowerKeyLongPressed: dialogs.showPowerDialog();
222  onVolumeDownTriggered: volumeControl.volumeDown();
223  onVolumeUpTriggered: volumeControl.volumeUp();
224  onScreenshotTriggered: itemGrabber.capture(shell);
225  }
226 
227  GlobalShortcut {
228  // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
229  }
230 
231  WindowInputFilter {
232  id: inputFilter
233  Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
234  Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
235  }
236 
237  WindowInputMonitor {
238  objectName: "windowInputMonitor"
239  onHomeKeyActivated: {
240  // Ignore when greeter is active, to avoid pocket presses
241  if (!greeter.active) {
242  launcher.fadeOut();
243  shell.showHome();
244  }
245  }
246  onTouchBegun: { cursor.opacity = 0; }
247  onTouchEnded: {
248  // move the (hidden) cursor to the last known touch position
249  var mappedCoords = mapFromItem(null, pos.x, pos.y);
250  cursor.x = mappedCoords.x;
251  cursor.y = mappedCoords.y;
252  cursor.mouseNeverMoved = false;
253  }
254  }
255 
256  GSettings {
257  id: settings
258  schema.id: "com.canonical.Unity8"
259  }
260 
261  Item {
262  id: stages
263  objectName: "stages"
264  width: parent.width
265  height: parent.height
266 
267  SurfaceManager {
268  id: surfaceMan
269  objectName: "surfaceManager"
270  }
271  TopLevelWindowModel {
272  id: topLevelSurfaceList
273  objectName: "topLevelSurfaceList"
274  applicationManager: ApplicationManager // it's a singleton
275  surfaceManager: surfaceMan
276  }
277 
278  Stage {
279  id: stage
280  objectName: "stage"
281  anchors.fill: parent
282  focus: true
283 
284  dragAreaWidth: shell.edgeSize
285  background: wallpaperResolver.background
286 
287  applicationManager: ApplicationManager
288  topLevelSurfaceList: topLevelSurfaceList
289  inputMethodRect: inputMethod.visibleRect
290 
291  property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
292  ? "phone"
293  : shell.usageScenario
294 
295  mode: usageScenario == "phone" ? "staged"
296  : usageScenario == "tablet" ? "stagedWithSideStage"
297  : "windowed"
298 
299  shellOrientation: shell.orientation
300  shellOrientationAngle: shell.orientationAngle
301  orientations: shell.orientations
302  nativeWidth: shell.nativeWidth
303  nativeHeight: shell.nativeHeight
304 
305  interactive: (!greeter || !greeter.shown)
306  && panel.indicators.fullyClosed
307  && !notifications.useModal
308 
309  onInteractiveChanged: { if (interactive) { focus = true; } }
310 
311  leftMargin: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
312  suspended: greeter.shown
313  altTabPressed: physicalKeysMapper.altTabPressed
314  oskEnabled: shell.oskEnabled
315  spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
316  }
317  }
318 
319  InputMethod {
320  id: inputMethod
321  objectName: "inputMethod"
322  surface: topLevelSurfaceList.inputMethodSurface
323  anchors {
324  fill: parent
325  topMargin: panel.panelHeight
326  leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
327  }
328  z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running || launcher.drawerShown ? overlay.z + 1 : overlay.z - 1
329  }
330 
331  Loader {
332  id: greeterLoader
333  objectName: "greeterLoader"
334  anchors.fill: parent
335  anchors.topMargin: panel.panelHeight
336  sourceComponent: shell.mode != "shell" ? integratedGreeter :
337  Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
338  onLoaded: {
339  item.objectName = "greeter"
340  }
341  }
342 
343  Component {
344  id: integratedGreeter
345  Greeter {
346 
347  enabled: panel.indicators.fullyClosed // hides OSK when panel is open
348  hides: [launcher, panel.indicators]
349  tabletMode: shell.usageScenario != "phone"
350  forcedUnlock: wizard.active || shell.mode === "full-shell"
351  background: wallpaperResolver.cachedBackground
352  hasCustomBackground: wallpaperResolver.hasCustomBackground
353  allowFingerprint: !dialogs.hasActiveDialog &&
354  !notifications.topmostIsFullscreen &&
355  !panel.indicators.shown
356 
357  // avoid overlapping with Launcher's edge drag area
358  // FIXME: Fix TouchRegistry & friends and remove this workaround
359  // Issue involves launcher's DDA getting disabled on a long
360  // left-edge drag
361  dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
362 
363  onSessionStarted: {
364  launcher.hide();
365  }
366 
367  onTease: {
368  if (!tutorial.running) {
369  launcher.tease();
370  }
371  }
372 
373  onEmergencyCall: startLockedApp("dialer-app")
374  }
375  }
376 
377  Timer {
378  // See powerConnection for why this is useful
379  id: showGreeterDelayed
380  interval: 1
381  onTriggered: {
382  greeter.forceShow();
383  }
384  }
385 
386  Connections {
387  id: callConnection
388  target: callManager
389 
390  onHasCallsChanged: {
391  if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
392  // We just received an incoming call while locked. The
393  // indicator will have already launched dialer-app for us, but
394  // there is a race between "hasCalls" changing and the dialer
395  // starting up. So in case we lose that race, we'll start/
396  // focus the dialer ourselves here too. Even if the indicator
397  // didn't launch the dialer for some reason (or maybe a call
398  // started via some other means), if an active call is
399  // happening, we want to be in the dialer.
400  startLockedApp("dialer-app")
401  }
402  }
403  }
404 
405  Connections {
406  id: powerConnection
407  target: Powerd
408 
409  onStatusChanged: {
410  if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
411  !callManager.hasCalls && !wizard.active) {
412  // We don't want to simply call greeter.showNow() here, because
413  // that will take too long. Qt will delay button event
414  // handling until the greeter is done loading and may think the
415  // user held down the power button the whole time, leading to a
416  // power dialog being shown. Instead, delay showing the
417  // greeter until we've finished handling the event. We could
418  // make the greeter load asynchronously instead, but that
419  // introduces a whole host of timing issues, especially with
420  // its animations. So this is simpler.
421  showGreeterDelayed.start();
422  }
423  }
424  }
425 
426  function showHome() {
427  greeter.notifyUserRequestedApp();
428 
429  if (shell.mode === "greeter") {
430  SessionBroadcast.requestHomeShown(AccountsService.user);
431  } else {
432  var animate = !LightDMService.greeter.active && !stages.shown;
433  dash.setCurrentScope(0, animate, false);
434  ApplicationManager.requestFocusApplication("unity8-dash");
435  }
436  }
437 
438  Item {
439  id: overlay
440  z: 10
441 
442  anchors.fill: parent
443 
444  Panel {
445  id: panel
446  objectName: "panel"
447  anchors.fill: parent //because this draws indicator menus
448  indicators {
449  hides: [launcher]
450  available: tutorial.panelEnabled
451  && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
452  && (!greeter || !greeter.hasLockedApp)
453  && !shell.waitingOnGreeter
454  && settings.enableIndicatorMenu
455  width: parent.width > units.gu(60) ? units.gu(40) : parent.width
456 
457  minimizedPanelHeight: units.gu(3)
458  expandedPanelHeight: units.gu(7)
459 
460  indicatorsModel: Indicators.IndicatorsModel {
461  // tablet and phone both use the same profile
462  // FIXME: use just "phone" for greeter too, but first fix
463  // greeter app launching to either load the app inside the
464  // greeter or tell the session to load the app. This will
465  // involve taking the url-dispatcher dbus name and using
466  // SessionBroadcast to tell the session.
467  profile: shell.mode === "greeter" ? "desktop_greeter" : "phone"
468  Component.onCompleted: load();
469  }
470  }
471 
472  callHint {
473  greeterShown: greeter.shown
474  }
475 
476  readonly property bool focusedSurfaceIsFullscreen: topLevelSurfaceList.focusedWindow
477  ? topLevelSurfaceList.focusedWindow.state === Mir.FullscreenState
478  : false
479  fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
480  || greeter.hasLockedApp
481  locked: greeter && greeter.active
482  }
483 
484  Launcher {
485  id: launcher
486  objectName: "launcher"
487 
488  anchors.top: parent.top
489  anchors.topMargin: inverted ? 0 : panel.panelHeight
490  anchors.bottom: parent.bottom
491  width: parent.width
492  dragAreaWidth: shell.edgeSize
493  available: tutorial.launcherEnabled
494  && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
495  && !greeter.hasLockedApp
496  && !shell.waitingOnGreeter
497  && settings.enableLauncher
498  inverted: shell.usageScenario !== "desktop"
499  superPressed: physicalKeysMapper.superPressed
500  superTabPressed: physicalKeysMapper.superTabPressed
501  panelWidth: units.gu(settings.launcherWidth)
502  lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
503  blurSource: greeter.shown ? greeter : stages
504  topPanelHeight: panel.panelHeight
505  drawerEnabled: !greeter.shown
506 
507  onShowDashHome: showHome()
508  onLauncherApplicationSelected: {
509  greeter.notifyUserRequestedApp();
510  shell.activateApplication(appId);
511  }
512  onShownChanged: {
513  if (shown) {
514  panel.indicators.hide()
515  }
516  }
517  onFocusChanged: {
518  if (!focus) {
519  stage.focus = true;
520  }
521  }
522 
523  GlobalShortcut {
524  shortcut: Qt.MetaModifier | Qt.Key_A
525  onTriggered: {
526  launcher.openDrawer(true);
527  }
528  }
529  GlobalShortcut {
530  shortcut: Qt.AltModifier | Qt.Key_F1
531  onTriggered: {
532  launcher.openForKeyboardNavigation();
533  }
534  }
535  GlobalShortcut {
536  shortcut: Qt.MetaModifier | Qt.Key_0
537  onTriggered: {
538  if (LauncherModel.get(9)) {
539  activateApplication(LauncherModel.get(9).appId);
540  }
541  }
542  }
543  Repeater {
544  model: 9
545  GlobalShortcut {
546  shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
547  onTriggered: {
548  if (LauncherModel.get(index)) {
549  activateApplication(LauncherModel.get(index).appId);
550  }
551  }
552  }
553  }
554  }
555 
556  KeyboardShortcutsOverlay {
557  objectName: "shortcutsOverlay"
558  enabled: launcher.shortcutHintsShown && width < parent.width - (launcher.lockedVisible ? launcher.panelWidth : 0) - padding
559  && height < parent.height - padding - panel.panelHeight
560  anchors.centerIn: parent
561  anchors.horizontalCenterOffset: launcher.lockedVisible ? launcher.panelWidth/2 : 0
562  anchors.verticalCenterOffset: panel.panelHeight/2
563  visible: opacity > 0
564  opacity: enabled ? 0.95 : 0
565 
566  Behavior on opacity {
567  UbuntuNumberAnimation {}
568  }
569  }
570 
571  Tutorial {
572  id: tutorial
573  objectName: "tutorial"
574  anchors.fill: parent
575 
576  paused: callManager.hasCalls || !greeter || greeter.active ||
577  wizard.active
578  delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
579  inputMethod.visible ||
580  (launcher.shown && !launcher.lockedVisible) ||
581  panel.indicators.shown || stage.rightEdgeDragProgress > 0
582  usageScenario: shell.usageScenario
583  lastInputTimestamp: inputFilter.lastInputTimestamp
584  launcher: launcher
585  panel: panel
586  stage: stage
587  }
588 
589  Wizard {
590  id: wizard
591  objectName: "wizard"
592  anchors.fill: parent
593  deferred: shell.mode === "greeter"
594 
595  function unlockWhenDoneWithWizard() {
596  if (!active) {
597  Connectivity.unlockAllModems();
598  }
599  }
600 
601  Component.onCompleted: unlockWhenDoneWithWizard()
602  onActiveChanged: unlockWhenDoneWithWizard()
603  }
604 
605  MouseArea { // modal notifications prevent interacting with other contents
606  anchors.fill: parent
607  visible: notifications.useModal
608  enabled: visible
609  }
610 
611  Notifications {
612  id: notifications
613 
614  model: NotificationBackend.Model
615  margin: units.gu(1)
616  hasMouse: shell.hasMouse
617  background: wallpaperResolver.cachedBackground
618 
619  y: topmostIsFullscreen ? 0 : panel.panelHeight
620  height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
621 
622  states: [
623  State {
624  name: "narrow"
625  when: overlay.width <= units.gu(60)
626  AnchorChanges {
627  target: notifications
628  anchors.left: parent.left
629  anchors.right: parent.right
630  }
631  },
632  State {
633  name: "wide"
634  when: overlay.width > units.gu(60)
635  AnchorChanges {
636  target: notifications
637  anchors.left: undefined
638  anchors.right: parent.right
639  }
640  PropertyChanges { target: notifications; width: units.gu(38) }
641  }
642  ]
643  }
644  }
645 
646  Dialogs {
647  id: dialogs
648  objectName: "dialogs"
649  anchors.fill: parent
650  z: overlay.z + 10
651  usageScenario: shell.usageScenario
652  onPowerOffClicked: {
653  shutdownFadeOutRectangle.enabled = true;
654  shutdownFadeOutRectangle.visible = true;
655  shutdownFadeOut.start();
656  }
657  }
658 
659  Connections {
660  target: SessionBroadcast
661  onShowHome: if (shell.mode !== "greeter") showHome()
662  }
663 
664  URLDispatcher {
665  id: urlDispatcher
666  objectName: "urlDispatcher"
667  active: shell.mode === "greeter"
668  onUrlRequested: shell.activateURL(url)
669  }
670 
671  ItemGrabber {
672  id: itemGrabber
673  anchors.fill: parent
674  z: dialogs.z + 10
675  GlobalShortcut { shortcut: Qt.Key_Print; onTriggered: itemGrabber.capture(shell) }
676  Connections {
677  target: stage
678  ignoreUnknownSignals: true
679  onItemSnapshotRequested: itemGrabber.capture(item)
680  }
681  }
682 
683  Timer {
684  id: cursorHidingTimer
685  interval: 3000
686  running: panel.focusedSurfaceIsFullscreen && cursor.opacity > 0
687  onTriggered: cursor.opacity = 0;
688  }
689 
690  Cursor {
691  id: cursor
692  objectName: "cursor"
693  visible: shell.hasMouse
694  z: itemGrabber.z + 1
695  topBoundaryOffset: panel.panelHeight
696 
697  confiningItem: stage.itemConfiningMouseCursor
698 
699  property bool mouseNeverMoved: true
700  Binding {
701  target: cursor; property: "x"; value: shell.width / 2
702  when: cursor.mouseNeverMoved && cursor.visible
703  }
704  Binding {
705  target: cursor; property: "y"; value: shell.height / 2
706  when: cursor.mouseNeverMoved && cursor.visible
707  }
708 
709  height: units.gu(3)
710 
711  readonly property var previewRectangle: stage.previewRectangle.target &&
712  stage.previewRectangle.target.dragging ?
713  stage.previewRectangle : null
714 
715  onPushedLeftBoundary: {
716  if (buttons === Qt.NoButton) {
717  launcher.pushEdge(amount);
718  } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
719  previewRectangle.maximizeLeft(amount);
720  }
721  }
722 
723  onPushedRightBoundary: {
724  if (buttons === Qt.NoButton) {
725  stage.pushRightEdge(amount);
726  } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
727  previewRectangle.maximizeRight(amount);
728  }
729  }
730 
731  onPushedTopBoundary: {
732  if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
733  previewRectangle.maximize(amount);
734  }
735  }
736  onPushedTopLeftCorner: {
737  if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
738  previewRectangle.maximizeTopLeft(amount);
739  }
740  }
741  onPushedTopRightCorner: {
742  if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
743  previewRectangle.maximizeTopRight(amount);
744  }
745  }
746  onPushedBottomLeftCorner: {
747  if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
748  previewRectangle.maximizeBottomLeft(amount);
749  }
750  }
751  onPushedBottomRightCorner: {
752  if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
753  previewRectangle.maximizeBottomRight(amount);
754  }
755  }
756  onPushStopped: {
757  if (previewRectangle) {
758  previewRectangle.stop();
759  }
760  }
761 
762  onMouseMoved: {
763  mouseNeverMoved = false;
764  cursor.opacity = 1;
765  }
766 
767  Behavior on opacity { UbuntuNumberAnimation {} }
768  }
769 
770  // non-visual objects
771  KeymapSwitcher {
772  focusedSurface: topLevelSurfaceList.focusedWindow ? topLevelSurfaceList.focusedWindow.surface : null
773  }
774  BrightnessControl {}
775 
776  Rectangle {
777  id: shutdownFadeOutRectangle
778  z: cursor.z + 1
779  enabled: false
780  visible: false
781  color: "black"
782  anchors.fill: parent
783  opacity: 0.0
784  NumberAnimation on opacity {
785  id: shutdownFadeOut
786  from: 0.0
787  to: 1.0
788  onStopped: {
789  if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
790  DBusUnitySessionService.shutdown();
791  }
792  }
793  }
794  }
795 }