From 13bb5b9522e453d45916f48507192ab03dbe0e74 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Mar 05 2015 13:13:12 +0000 Subject: import gnome-shell-3.8.4-45.el7 --- diff --git a/SOURCES/0001-Add-support-for-meta_restart-and-MetaDisplay-restart.patch b/SOURCES/0001-Add-support-for-meta_restart-and-MetaDisplay-restart.patch new file mode 100644 index 0000000..0b46ba4 --- /dev/null +++ b/SOURCES/0001-Add-support-for-meta_restart-and-MetaDisplay-restart.patch @@ -0,0 +1,338 @@ +From 3e707f08e1b8687abb093d020b78dc571c68df06 Mon Sep 17 00:00:00 2001 +From: "Owen W. Taylor" +Date: Thu, 8 May 2014 18:56:23 -0400 +Subject: [PATCH] Add support for meta_restart() and MetaDisplay::restart + +Support was added to Mutter to allow it to trigger a restart +to allow for restarts when switching in or out of stereo mode. + +Hook up to the new signals on MetaDisplay to show the restart +message and reexec. Meta.is_restart() is used to suppress +the startup animation. + +This also allows us to do 'Alt-F2 r' restarts more cleanly +without a visual flash and animation. +--- + data/theme/gnome-shell.css | 5 ++ + js/ui/layout.js | 115 +++++++++++++++++++++++++++------------------ + js/ui/main.js | 36 ++++++++++++++ + js/ui/modalDialog.js | 35 ++++++++------ + js/ui/runDialog.js | 14 +++--- + 5 files changed, 138 insertions(+), 67 deletions(-) + +diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css +index f4ea781..66346a1 100644 +--- a/data/theme/gnome-shell.css ++++ b/data/theme/gnome-shell.css +@@ -1920,6 +1920,11 @@ StScrollBar StButton#vhandle:active { + color: #444444; + } + ++/* Restart message */ ++.restart-message { ++ font-size: 14pt; ++} ++ + /* ShellMountOperation Dialogs */ + .shell-mount-operation-icon { + icon-size: 48px; +diff --git a/js/ui/layout.js b/js/ui/layout.js +index 141eecc..223a0e2 100644 +--- a/js/ui/layout.js ++++ b/js/ui/layout.js +@@ -560,56 +560,75 @@ const LayoutManager = new Lang.Class({ + // so events don't get delivered to X11 windows (which are distorted by the animation) + global.stage_input_mode = Shell.StageInputMode.FULLSCREEN; + +- if (Main.sessionMode.isGreeter) { +- this.panelBox.translation_y = -this.panelBox.height; ++ let background; ++ if (Meta.is_restart()) { ++ // On restart, we don't do an animation, so start loading the primary ++ // background now. ++ this._createPrimaryBackground(); ++ background = this._bgManagers[this.primaryIndex].background; + } else { +- // We need to force an update of the regions now before we scale +- // the UI group to get the coorect allocation for the struts. +- this._updateRegions(); +- +- this.trayBox.hide(); +- this.keyboardBox.hide(); +- +- let monitor = this.primaryMonitor; +- let x = monitor.x + monitor.width / 2.0; +- let y = monitor.y + monitor.height / 2.0; +- +- this.uiGroup.set_pivot_point(x / global.screen_width, +- y / global.screen_height); +- this.uiGroup.scale_x = this.uiGroup.scale_y = 0.5; +- this.uiGroup.opacity = 0; +- global.window_group.set_clip(monitor.x, monitor.y, monitor.width, monitor.height); +- } +- +- this._systemBackground = new Background.SystemBackground(); +- this._systemBackground.actor.hide(); +- +- global.stage.insert_child_below(this._systemBackground.actor, null); ++ if (Main.sessionMode.isGreeter) { ++ this.panelBox.translation_y = -this.panelBox.height; ++ } else { ++ // We need to force an update of the regions now before we scale ++ // the UI group to get the coorect allocation for the struts. ++ this._updateRegions(); ++ ++ this.trayBox.hide(); ++ this.keyboardBox.hide(); ++ ++ let monitor = this.primaryMonitor; ++ let x = monitor.x + monitor.width / 2.0; ++ let y = monitor.y + monitor.height / 2.0; ++ ++ this.uiGroup.set_pivot_point(x / global.screen_width, ++ y / global.screen_height); ++ this.uiGroup.scale_x = this.uiGroup.scale_y = 0.5; ++ this.uiGroup.opacity = 0; ++ global.window_group.set_clip(monitor.x, monitor.y, monitor.width, monitor.height); ++ } + +- let constraint = new Clutter.BindConstraint({ source: global.stage, +- coordinate: Clutter.BindCoordinate.ALL }); +- this._systemBackground.actor.add_constraint(constraint); ++ this._systemBackground = new Background.SystemBackground(); ++ this._systemBackground.actor.hide(); + +- let signalId = this._systemBackground.connect('loaded', +- Lang.bind(this, function() { +- this._systemBackground.disconnect(signalId); +- this._systemBackground.actor.show(); +- global.stage.show(); ++ global.stage.insert_child_below(this._systemBackground.actor, null); + +- this.emit('startup-prepared'); ++ let constraint = new Clutter.BindConstraint({ source: global.stage, ++ coordinate: Clutter.BindCoordinate.ALL }); ++ this._systemBackground.actor.add_constraint(constraint); ++ background = this._systemBackground; ++ } + +- // We're mostly prepared for the startup animation +- // now, but since a lot is going on asynchronously +- // during startup, let's defer the startup animation +- // until the event loop is uncontended and idle. +- // This helps to prevent us from running the animation +- // when the system is bogged down +- GLib.idle_add(GLib.PRIORITY_LOW, +- Lang.bind(this, function() { +- this._startupAnimation(); +- return false; +- })); +- })); ++ let signalId = background.connect('loaded', ++ Lang.bind(this, function() { ++ background.disconnect(signalId); ++ if (this._systemBackground) ++ this._systemBackground.actor.show(); ++ global.stage.show(); ++ ++ this.emit('startup-prepared'); ++ ++ // We're mostly prepared for the startup animation ++ // now, but since a lot is going on asynchronously ++ // during startup, let's defer the startup animation ++ // until the event loop is uncontended and idle. ++ // This helps to prevent us from running the animation ++ // when the system is bogged down. Don't wait on ++ // restart, since it might take a long time for us ++ // to go idle, and we don't show an animation. Just ++ // finish immediately. ++ if (Meta.is_restart()) { ++ // Nothing else will do this reliably ++ this._queueUpdateRegions(); ++ this._startupAnimationComplete(); ++ } else { ++ GLib.idle_add(GLib.PRIORITY_LOW, ++ Lang.bind(this, function() { ++ this._startupAnimation(); ++ return false; ++ })); ++ } ++ })); + }, + + _startupAnimation: function() { +@@ -647,8 +666,10 @@ const LayoutManager = new Lang.Class({ + + global.stage_input_mode = Shell.StageInputMode.NORMAL; + +- this._systemBackground.actor.destroy(); +- this._systemBackground = null; ++ if (this._systemBackground) { ++ this._systemBackground.actor.destroy(); ++ this._systemBackground = null; ++ } + + this._startingUp = false; + +diff --git a/js/ui/main.js b/js/ui/main.js +index bd5dc47..a3b73f0 100644 +--- a/js/ui/main.js ++++ b/js/ui/main.js +@@ -18,6 +18,7 @@ const ExtensionSystem = imports.ui.extensionSystem; + const ExtensionDownloader = imports.ui.extensionDownloader; + const Keyboard = imports.ui.keyboard; + const MessageTray = imports.ui.messageTray; ++const ModalDialog = imports.ui.modalDialog; + const OsdWindow = imports.ui.osdWindow; + const Overview = imports.ui.overview; + const Panel = imports.ui.panel; +@@ -176,6 +177,16 @@ function _initializeUI() { + false, -1, 1); + global.display.connect('overlay-key', Lang.bind(overview, overview.toggle)); + ++ global.display.connect('show-restart-message', function(display, message) { ++ showRestartMessage(message); ++ return true; ++ }); ++ ++ global.display.connect('restart', function() { ++ global.reexec_self(); ++ return true; ++ }); ++ + // Provide the bus object for gnome-session to + // initiate logouts. + EndSessionDialog.init(); +@@ -800,3 +811,28 @@ function queueDeferredWork(workId) { + }); + } + } ++ ++const RestartMessage = new Lang.Class({ ++ Name: 'RestartMessage', ++ Extends: ModalDialog.ModalDialog, ++ ++ _init : function(message) { ++ this.parent({ shellReactive: true, ++ styleClass: 'restart-message', ++ shouldFadeIn: false, ++ destroyOnClose: true }); ++ ++ let label = new St.Label({ text: message }); ++ ++ this.contentLayout.add(label, { x_fill: false, ++ y_fill: false, ++ x_align: St.Align.MIDDLE, ++ y_align: St.Align.MIDDLE }); ++ this.buttonLayout.hide(); ++ } ++}); ++ ++function showRestartMessage(message) { ++ let restartMessage = new RestartMessage(message); ++ restartMessage.open(); ++} +diff --git a/js/ui/modalDialog.js b/js/ui/modalDialog.js +index f770faf..aba7758 100644 +--- a/js/ui/modalDialog.js ++++ b/js/ui/modalDialog.js +@@ -43,6 +43,7 @@ const ModalDialog = new Lang.Class({ + parentActor: Main.uiGroup, + keybindingMode: Shell.KeyBindingMode.SYSTEM_MODAL, + shouldFadeIn: true, ++ shouldFadeOut: true, + destroyOnClose: true }); + + this.state = State.CLOSED; +@@ -50,6 +51,7 @@ const ModalDialog = new Lang.Class({ + this._keybindingMode = params.keybindingMode; + this._shellReactive = params.shellReactive; + this._shouldFadeIn = params.shouldFadeIn; ++ this._shouldFadeOut = params.shouldFadeOut; + this._destroyOnClose = params.destroyOnClose; + + this._group = new St.Widget({ visible: false, +@@ -303,6 +305,15 @@ const ModalDialog = new Lang.Class({ + return true; + }, + ++ _closeComplete: function() { ++ this.state = State.CLOSED; ++ this._group.hide(); ++ this.emit('closed'); ++ ++ if (this._destroyOnClose) ++ this.destroy(); ++ }, ++ + close: function(timestamp) { + if (this.state == State.CLOSED || this.state == State.CLOSING) + return; +@@ -311,20 +322,16 @@ const ModalDialog = new Lang.Class({ + this.popModal(timestamp); + this._savedKeyFocus = null; + +- Tweener.addTween(this._group, +- { opacity: 0, +- time: OPEN_AND_CLOSE_TIME, +- transition: 'easeOutQuad', +- onComplete: Lang.bind(this, +- function() { +- this.state = State.CLOSED; +- this._group.hide(); +- this.emit('closed'); +- +- if (this._destroyOnClose) +- this.destroy(); +- }) +- }); ++ if (this._shouldFadeOut) ++ Tweener.addTween(this._group, ++ { opacity: 0, ++ time: OPEN_AND_CLOSE_TIME, ++ transition: 'easeOutQuad', ++ onComplete: Lang.bind(this, ++ this._closeComplete) ++ }) ++ else ++ this._closeComplete(); + }, + + // Drop modal status without closing the dialog; this makes the +diff --git a/js/ui/runDialog.js b/js/ui/runDialog.js +index 7b753a7..1901296 100644 +--- a/js/ui/runDialog.js ++++ b/js/ui/runDialog.js +@@ -50,14 +50,10 @@ const RunDialog = new Lang.Class({ + Main.createLookingGlass().open(); + }), + +- 'r': Lang.bind(this, function() { +- global.reexec_self(); +- }), ++ 'r': Lang.bind(this, this._restart), + + // Developer brain backwards compatibility +- 'restart': Lang.bind(this, function() { +- global.reexec_self(); +- }), ++ 'restart': Lang.bind(this, this._restart), + + 'debugexit': Lang.bind(this, function() { + Meta.quit(Meta.ExitCode.ERROR); +@@ -267,6 +263,12 @@ const RunDialog = new Lang.Class({ + } + }, + ++ _restart: function() { ++ this._shouldFadeOut = false; ++ this.close(); ++ Meta.restart('Restarting...'); ++ }, ++ + open: function() { + this._history.lastItem(); + this._errorBox.hide(); +-- +1.8.3.1 + diff --git a/SOURCES/0001-appSwitcher-Add-option-to-limit-to-the-current-works.patch b/SOURCES/0001-appSwitcher-Add-option-to-limit-to-the-current-works.patch new file mode 100644 index 0000000..3511495 --- /dev/null +++ b/SOURCES/0001-appSwitcher-Add-option-to-limit-to-the-current-works.patch @@ -0,0 +1,72 @@ +From 4baaf4cccb723968637580e70ce1b6f4fcee1e39 Mon Sep 17 00:00:00 2001 +From: Adel Gadllah +Date: Thu, 4 Jul 2013 19:24:30 +0200 +Subject: [PATCH] appSwitcher: Add option to limit to the current workspace + +Add an option to limit the appSwitcher to the current workspace. For users +that use workspaces for task separation this more convient then current +behviour. While having to add an option is unfortunate there is no way to make +both groups happy as workspaces usage differes between different users / types +of users. + +https://bugzilla.gnome.org/show_bug.cgi?id=703538 +--- + data/org.gnome.shell.gschema.xml.in.in | 13 +++++++++++++ + js/ui/altTab.js | 9 +++++++-- + 2 files changed, 20 insertions(+), 2 deletions(-) + +diff --git a/data/org.gnome.shell.gschema.xml.in.in b/data/org.gnome.shell.gschema.xml.in.in +index 04d150c..81809d8 100644 +--- a/data/org.gnome.shell.gschema.xml.in.in ++++ b/data/org.gnome.shell.gschema.xml.in.in +@@ -189,6 +189,19 @@ value here is from the GsmPresenceStatus enumeration. + + + ++ ++ ++ false ++ Limit switcher to current workspace. ++ ++ If true, only applications that have windows on the current workspace are shown in the switcher. ++ Otherwise, all applications are included. ++ ++ ++ ++ + + + +diff --git a/js/ui/altTab.js b/js/ui/altTab.js +index 34d83e4..9d47d1e 100644 +--- a/js/ui/altTab.js ++++ b/js/ui/altTab.js +@@ -436,8 +436,11 @@ const AppSwitcher = new Lang.Class({ + this._arrows = []; + + let windowTracker = Shell.WindowTracker.get_default(); ++ let settings = new Gio.Settings({ schema: 'org.gnome.shell.app-switcher' }); ++ let workspace = settings.get_boolean('current-workspace-only') ? global.screen.get_active_workspace() ++ : null; + let allWindows = global.display.get_tab_list(Meta.TabList.NORMAL, +- global.screen, null); ++ global.screen, workspace); + + // Construct the AppIcons, add to the popup + for (let i = 0; i < apps.length; i++) { +@@ -447,7 +450,9 @@ const AppSwitcher = new Lang.Class({ + appIcon.cachedWindows = allWindows.filter(function(w) { + return windowTracker.get_window_app (w) == appIcon.app; + }); +- this._addIcon(appIcon); ++ if (workspace == null || appIcon.cachedWindows.length > 0) { ++ this._addIcon(appIcon); ++ } + } + + this._curApp = -1; +-- +2.1.0 + diff --git a/SOURCES/0001-dateMenu-Try-to-use-the-default-calendar-application.patch b/SOURCES/0001-dateMenu-Try-to-use-the-default-calendar-application.patch new file mode 100644 index 0000000..17255c5 --- /dev/null +++ b/SOURCES/0001-dateMenu-Try-to-use-the-default-calendar-application.patch @@ -0,0 +1,43 @@ +From 3d27dda8915fee31fa25c992649b01ab9dd02e7b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 16 Jan 2014 07:41:46 -0500 +Subject: [PATCH] dateMenu: Try to use the default calendar application + +Commit 14ceb10555699 changed the "Open Calendar" item to open the +"recommended" calendar application rather than the default one to +avoid problems with MIME subclassing (namely falling back to the +default text editor when no calendar app is installed). +With this change however, the application launched does no longer +necessarily match the one configured in Settings, which is unexpected. +To avoid both problems, use the default calendar application again, +but only if it is in the list of recommended applications. + +https://bugzilla.gnome.org/show_bug.cgi?id=722333 +--- + js/ui/dateMenu.js | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +diff --git a/js/ui/dateMenu.js b/js/ui/dateMenu.js +index 32fdd59..232af27 100644 +--- a/js/ui/dateMenu.js ++++ b/js/ui/dateMenu.js +@@ -223,10 +223,13 @@ const DateMenuButton = new Lang.Class({ + return this._calendarApp; + + let apps = Gio.AppInfo.get_recommended_for_type('text/calendar'); +- if (apps && (apps.length > 0)) +- this._calendarApp = apps[0]; +- else ++ if (apps && (apps.length > 0)) { ++ let app = Gio.AppInfo.get_default_for_type('text/calendar', false); ++ let defaultInRecommended = apps.some(function(a) { return a.equal(app); }); ++ this._calendarApp = defaultInRecommended ? app : apps[0]; ++ } else { + this._calendarApp = null; ++ } + return this._calendarApp; + }, + +-- +2.1.0 + diff --git a/SOURCES/0001-layout-Don-t-always-extend-struts-to-the-screen-edge.patch b/SOURCES/0001-layout-Don-t-always-extend-struts-to-the-screen-edge.patch deleted file mode 100644 index 0968310..0000000 --- a/SOURCES/0001-layout-Don-t-always-extend-struts-to-the-screen-edge.patch +++ /dev/null @@ -1,66 +0,0 @@ -From 58027e7dcc43290a5d0da167aec23ed46148e938 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Florian=20M=C3=BCllner?= -Date: Tue, 15 Apr 2014 17:24:07 +0200 -Subject: [PATCH] layout: Don't always extend struts to the screen edge - -NetWM struts are defined in terms of screen edges (rather than monitor -edges), which works poorly with vertical monitor layouts (as it renders -entire monitors unusable). Don't extend struts in those cases. - -https://bugzilla.gnome.org/show_bug.cgi?id=663690 ---- - js/ui/layout.js | 30 +++++++++++++++++++++++++----- - 1 file changed, 25 insertions(+), 5 deletions(-) - -diff --git a/js/ui/layout.js b/js/ui/layout.js -index 141eecc..3dcc858 100644 ---- a/js/ui/layout.js -+++ b/js/ui/layout.js -@@ -1006,19 +1006,39 @@ const LayoutManager = new Lang.Class({ - continue; - - // Ensure that the strut rects goes all the way to the screen edge, -- // as this really what mutter expects. -+ // as this really what mutter expects. However skip this step -+ // in cases where this would render an entire monitor unusable. - switch (side) { - case Meta.Side.TOP: -- y1 = 0; -+ let hasMonitorsAbove = this.monitors.some(Lang.bind(this, -+ function(mon) { -+ return this._isAboveOrBelowPrimary(mon) && -+ mon.y < primary.y; -+ })); -+ if (!hasMonitorsAbove) -+ y1 = 0; - break; - case Meta.Side.BOTTOM: -- y2 = global.screen_height; -+ if (this.primaryIndex == this.bottomIndex) -+ y2 = global.screen_height; - break; - case Meta.Side.LEFT: -- x1 = 0; -+ let hasMonitorsLeft = this.monitors.some(Lang.bind(this, -+ function(mon) { -+ return !this._isAboveOrBelowPrimary(mon) && -+ mon.x < primary.x; -+ })); -+ if (!hasMonitorsLeft) -+ x1 = 0; - break; - case Meta.Side.RIGHT: -- x2 = global.screen_width; -+ let hasMonitorsRight = this.monitors.some(Lang.bind(this, -+ function(mon) { -+ return !this._isAboveOrBelowPrimary(mon) && -+ mon.x > primary.x; -+ })); -+ if (!hasMonitorsRight) -+ x2 = global.screen_width; - break; - } - --- -1.9.0 - diff --git a/SOURCES/0001-screenshot-Also-validate-parameters-to-FlashArea.patch b/SOURCES/0001-screenshot-Also-validate-parameters-to-FlashArea.patch new file mode 100644 index 0000000..22ecd7f --- /dev/null +++ b/SOURCES/0001-screenshot-Also-validate-parameters-to-FlashArea.patch @@ -0,0 +1,69 @@ +From daf661fbffb3e4c6afd082785721f199f992eab0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 4 Jun 2014 16:26:06 +0200 +Subject: [PATCH] screenshot: Also validate parameters to FlashArea() + +Apply the same parameter validation to FlashArea() we already use +for ScreenshotArea(). + +https://bugzilla.gnome.org/show_bug.cgi?id=731220 +--- + js/ui/screenshot.js | 26 ++++++++++++++++++++------ + 1 file changed, 20 insertions(+), 6 deletions(-) + +diff --git a/js/ui/screenshot.js b/js/ui/screenshot.js +index 3c5c831..f85c62e 100644 +--- a/js/ui/screenshot.js ++++ b/js/ui/screenshot.js +@@ -64,6 +64,13 @@ const ScreenshotService = new Lang.Class({ + Gio.DBus.session.own_name('org.gnome.Shell.Screenshot', Gio.BusNameOwnerFlags.REPLACE, null, null); + }, + ++ _checkArea: function(x, y, width, height) { ++ return x >= 0 && y >= 0 && ++ width > 0 && height > 0 && ++ x + width <= global.screen_width && ++ y + height <= global.screen_height; ++ }, ++ + _onScreenshotComplete: function(obj, result, area, filenameUsed, flash, invocation) { + if (flash && result) { + let flashspot = new Flashspot(area); +@@ -76,11 +83,10 @@ const ScreenshotService = new Lang.Class({ + + ScreenshotAreaAsync : function (params, invocation) { + let [x, y, width, height, flash, filename, callback] = params; +- if (x < 0 || y < 0 || +- width <= 0 || height <= 0 || +- x + width > global.screen_width || y + height > global.screen_height) { +- invocation.return_error_literal(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED, +- "Invalid params"); ++ if (!this._checkArea(x, y, width, height)) { ++ invocation.return_error_literal(Gio.IOErrorEnum, ++ Gio.IOErrorEnum.CANCELLED, ++ "Invalid params"); + return; + } + let screenshot = new Shell.Screenshot(); +@@ -122,9 +128,17 @@ const ScreenshotService = new Lang.Class({ + })); + }, + +- FlashArea: function(x, y, width, height) { ++ FlashAreaAsync: function(params, invocation) { ++ let [x, y, width, height] = params; ++ if (!this._checkArea(x, y, width, height)) { ++ invocation.return_error_literal(Gio.IOErrorEnum, ++ Gio.IOErrorEnum.CANCELLED, ++ "Invalid params"); ++ return; ++ } + let flashspot = new Flashspot({ x : x, y : y, width: width, height: height}); + flashspot.fire(); ++ invocation.return_value(null); + } + }); + +-- +2.1.0 + diff --git a/SOURCES/0001-shell-screenshot-Only-allow-one-screenshot-request-a.patch b/SOURCES/0001-shell-screenshot-Only-allow-one-screenshot-request-a.patch new file mode 100644 index 0000000..67ce2a5 --- /dev/null +++ b/SOURCES/0001-shell-screenshot-Only-allow-one-screenshot-request-a.patch @@ -0,0 +1,552 @@ +From a13c4a9a6e26b4446d9233637dc88ced61858f35 Mon Sep 17 00:00:00 2001 +From: Adel Gadllah +Date: Sat, 27 Sep 2014 13:35:22 +0200 +Subject: [PATCH] shell-screenshot: Only allow one screenshot request at a time + per sender + +We currently allow infinite number of screenshot requests to be active at +the same time, which can "dos" the system and cause OOM. + +So fail subsequent requests for the same sender when a screenshot operation +is already running. + +https://bugzilla.gnome.org/show_bug.cgi?id=737456 +--- + js/ui/screenshot.js | 61 +++++++++++++-- + src/shell-screenshot.c | 203 ++++++++++++++++++++++++++----------------------- + src/shell-screenshot.h | 5 +- + 3 files changed, 165 insertions(+), 104 deletions(-) + +diff --git a/js/ui/screenshot.js b/js/ui/screenshot.js +index f85c62e..dd6448e 100644 +--- a/js/ui/screenshot.js ++++ b/js/ui/screenshot.js +@@ -10,6 +10,7 @@ const Shell = imports.gi.Shell; + const Signals = imports.signals; + const St = imports.gi.St; + ++const Hash = imports.misc.hash; + const Lightbox = imports.ui.lightbox; + const Main = imports.ui.main; + const Tweener = imports.ui.tweener; +@@ -61,9 +62,41 @@ const ScreenshotService = new Lang.Class({ + this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(ScreenshotIface, this); + this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell/Screenshot'); + ++ this._screenShooter = new Hash.Map(); ++ + Gio.DBus.session.own_name('org.gnome.Shell.Screenshot', Gio.BusNameOwnerFlags.REPLACE, null, null); + }, + ++ _createScreenshot: function(invocation) { ++ let sender = invocation.get_sender(); ++ if (this._screenShooter.has(sender)) { ++ invocation.return_value(GLib.Variant.new('(bs)', [false, ''])); ++ return null; ++ } ++ ++ let shooter = new Shell.Screenshot(); ++ shooter._watchNameId = ++ Gio.bus_watch_name(Gio.BusType.SESSION, sender, 0, null, ++ Lang.bind(this, this._onNameVanished)); ++ ++ this._screenShooter.set(sender, shooter); ++ ++ return shooter; ++ }, ++ ++ _onNameVanished: function(connection, name) { ++ this._removeShooterForSender(name); ++ }, ++ ++ _removeShooterForSender: function(sender) { ++ let shooter = this._screenShooter.get(sender); ++ if (!shooter) ++ return; ++ ++ Gio.bus_unwatch_name(shooter._watchNameId); ++ this._screenShooter.delete(sender); ++ }, ++ + _checkArea: function(x, y, width, height) { + return x >= 0 && y >= 0 && + width > 0 && height > 0 && +@@ -72,9 +105,15 @@ const ScreenshotService = new Lang.Class({ + }, + + _onScreenshotComplete: function(obj, result, area, filenameUsed, flash, invocation) { +- if (flash && result) { +- let flashspot = new Flashspot(area); +- flashspot.fire(); ++ if (result) { ++ if (flash) { ++ let flashspot = new Flashspot(area); ++ flashspot.fire(Lang.bind(this, function() { ++ this._removeShooterForSender(invocation.get_sender()); ++ })); ++ } ++ else ++ this._removeShooterForSender(invocation.get_sender()); + } + + let retval = GLib.Variant.new('(bs)', [result, filenameUsed]); +@@ -89,7 +128,9 @@ const ScreenshotService = new Lang.Class({ + "Invalid params"); + return; + } +- let screenshot = new Shell.Screenshot(); ++ let screenshot = this._createScreenshot(invocation); ++ if (!screenshot) ++ return; + screenshot.screenshot_area (x, y, width, height, filename, + Lang.bind(this, this._onScreenshotComplete, + flash, invocation)); +@@ -97,7 +138,9 @@ const ScreenshotService = new Lang.Class({ + + ScreenshotWindowAsync : function (params, invocation) { + let [include_frame, include_cursor, flash, filename] = params; +- let screenshot = new Shell.Screenshot(); ++ let screenshot = this._createScreenshot(invocation); ++ if (!screenshot) ++ return; + screenshot.screenshot_window (include_frame, include_cursor, filename, + Lang.bind(this, this._onScreenshotComplete, + flash, invocation)); +@@ -105,7 +148,9 @@ const ScreenshotService = new Lang.Class({ + + ScreenshotAsync : function (params, invocation) { + let [include_cursor, flash, filename] = params; +- let screenshot = new Shell.Screenshot(); ++ let screenshot = this._createScreenshot(invocation); ++ if (!screenshot) ++ return; + screenshot.screenshot(include_cursor, filename, + Lang.bind(this, this._onScreenshotComplete, + flash, invocation)); +@@ -278,7 +323,7 @@ const Flashspot = new Lang.Class({ + this.actor.set_position(area.x, area.y); + }, + +- fire: function() { ++ fire: function(doneCallback) { + this.actor.show(); + this.actor.opacity = 255; + Tweener.addTween(this.actor, +@@ -286,6 +331,8 @@ const Flashspot = new Lang.Class({ + time: FLASHSPOT_ANIMATION_OUT_TIME, + transition: 'easeOutQuad', + onComplete: Lang.bind(this, function() { ++ if (doneCallback) ++ doneCallback(); + this.destroy(); + }) + }); +diff --git a/src/shell-screenshot.c b/src/shell-screenshot.c +index b68c3b0..5630726 100644 +--- a/src/shell-screenshot.c ++++ b/src/shell-screenshot.c +@@ -27,12 +27,12 @@ struct _ShellScreenshot + { + GObject parent_instance; + +- ShellGlobal *global; ++ ShellScreenshotPrivate *priv; + }; + +-/* Used for async screenshot grabbing */ +-typedef struct _screenshot_data { +- ShellScreenshot *screenshot; ++struct _ShellScreenshotPrivate ++{ ++ ShellGlobal *global; + + char *filename; + char *filename_used; +@@ -43,20 +43,23 @@ typedef struct _screenshot_data { + gboolean include_cursor; + + ShellScreenshotCallback callback; +-} _screenshot_data; ++}; + +-G_DEFINE_TYPE(ShellScreenshot, shell_screenshot, G_TYPE_OBJECT); ++G_DEFINE_TYPE (ShellScreenshot, shell_screenshot, G_TYPE_OBJECT); ++ ++#define SHELL_SCREENSHOT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SHELL_TYPE_SCREENSHOT, ShellScreenshotPrivate)) + + static void + shell_screenshot_class_init (ShellScreenshotClass *screenshot_class) + { +- (void) screenshot_class; ++ g_type_class_add_private (screenshot_class, sizeof (ShellScreenshotPrivate)); + } + + static void + shell_screenshot_init (ShellScreenshot *screenshot) + { +- screenshot->global = shell_global_get (); ++ screenshot->priv = SHELL_SCREENSHOT_GET_PRIVATE (screenshot); ++ screenshot->priv->global = shell_global_get (); + } + + static void +@@ -64,18 +67,18 @@ on_screenshot_written (GObject *source, + GAsyncResult *result, + gpointer user_data) + { +- _screenshot_data *screenshot_data = (_screenshot_data*) user_data; +- if (screenshot_data->callback) +- screenshot_data->callback (screenshot_data->screenshot, +- g_simple_async_result_get_op_res_gboolean (G_SIMPLE_ASYNC_RESULT (result)), +- &screenshot_data->screenshot_area, +- screenshot_data->filename_used); +- +- cairo_surface_destroy (screenshot_data->image); +- g_object_unref (screenshot_data->screenshot); +- g_free (screenshot_data->filename); +- g_free (screenshot_data->filename_used); +- g_free (screenshot_data); ++ ShellScreenshot *screenshot = SHELL_SCREENSHOT (source); ++ ShellScreenshotPrivate *priv = screenshot->priv; ++ ++ if (priv->callback) ++ priv->callback (screenshot, ++ g_simple_async_result_get_op_res_gboolean (G_SIMPLE_ASYNC_RESULT (result)), ++ &priv->screenshot_area, ++ priv->filename_used); ++ ++ g_clear_pointer (&priv->image, cairo_surface_destroy); ++ g_clear_pointer (&priv->filename, g_free); ++ g_clear_pointer (&priv->filename_used, g_free); + } + + /* called in an I/O thread */ +@@ -174,12 +177,15 @@ write_screenshot_thread (GSimpleAsyncResult *result, + { + cairo_status_t status; + GOutputStream *stream; +- _screenshot_data *screenshot_data = g_async_result_get_user_data (G_ASYNC_RESULT (result)); ++ ShellScreenshot *screenshot = SHELL_SCREENSHOT (object); ++ ShellScreenshotPrivate *priv; ++ ++ g_assert (screenshot != NULL); + +- g_assert (screenshot_data != NULL); ++ priv = screenshot->priv; + +- stream = prepare_write_stream (screenshot_data->filename, +- &screenshot_data->filename_used); ++ stream = prepare_write_stream (priv->filename, ++ &priv->filename_used); + + if (stream == NULL) + status = CAIRO_STATUS_FILE_NOT_FOUND; +@@ -187,10 +193,10 @@ write_screenshot_thread (GSimpleAsyncResult *result, + { + GdkPixbuf *pixbuf; + +- pixbuf = gdk_pixbuf_get_from_surface (screenshot_data->image, ++ pixbuf = gdk_pixbuf_get_from_surface (priv->image, + 0, 0, +- cairo_image_surface_get_width (screenshot_data->image), +- cairo_image_surface_get_height (screenshot_data->image)); ++ cairo_image_surface_get_width (priv->image), ++ cairo_image_surface_get_height (priv->image)); + + if (gdk_pixbuf_save_to_stream (pixbuf, stream, "png", NULL, NULL, + "tEXt::Software", "gnome-screenshot", NULL)) +@@ -208,7 +214,7 @@ write_screenshot_thread (GSimpleAsyncResult *result, + } + + static void +-do_grab_screenshot (_screenshot_data *screenshot_data, ++do_grab_screenshot (ShellScreenshot *screenshot, + int x, + int y, + int width, +@@ -219,16 +225,17 @@ do_grab_screenshot (_screenshot_data *screenshot_data, + CoglContext *context; + int stride; + guchar *data; ++ ShellScreenshotPrivate *priv = screenshot->priv; + + backend = clutter_get_default_backend (); + context = clutter_backend_get_cogl_context (backend); + +- screenshot_data->image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, +- width, height); ++ priv->image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, ++ width, height); + + +- data = cairo_image_surface_get_data (screenshot_data->image); +- stride = cairo_image_surface_get_stride (screenshot_data->image); ++ data = cairo_image_surface_get_data (priv->image); ++ stride = cairo_image_surface_get_stride (priv->image); + + bitmap = cogl_bitmap_new_for_data (context, + width, +@@ -241,7 +248,7 @@ do_grab_screenshot (_screenshot_data *screenshot_data, + COGL_READ_PIXELS_COLOR_BUFFER, + bitmap); + +- cairo_surface_mark_dirty (screenshot_data->image); ++ cairo_surface_mark_dirty (priv->image); + cogl_object_unref (bitmap); + } + +@@ -302,16 +309,18 @@ _draw_cursor_image (cairo_surface_t *surface, + + static void + grab_screenshot (ClutterActor *stage, +- _screenshot_data *screenshot_data) ++ ShellScreenshot *screenshot) + { +- MetaScreen *screen = shell_global_get_screen (screenshot_data->screenshot->global); ++ MetaScreen *screen; + int width, height; + GSimpleAsyncResult *result; + GSettings *settings; ++ ShellScreenshotPrivate *priv = screenshot->priv; + ++ screen = shell_global_get_screen (priv->global); + meta_screen_get_size (screen, &width, &height); + +- do_grab_screenshot (screenshot_data, 0, 0, width, height); ++ do_grab_screenshot (screenshot, 0, 0, width, height); + + if (meta_screen_get_n_monitors (screen) > 1) + { +@@ -337,7 +346,7 @@ grab_screenshot (ClutterActor *stage, + cairo_region_xor (stage_region, screen_region); + cairo_region_destroy (screen_region); + +- cr = cairo_create (screenshot_data->image); ++ cr = cairo_create (priv->image); + + for (i = 0; i < cairo_region_num_rectangles (stage_region); i++) + { +@@ -351,38 +360,39 @@ grab_screenshot (ClutterActor *stage, + cairo_region_destroy (stage_region); + } + +- screenshot_data->screenshot_area.x = 0; +- screenshot_data->screenshot_area.y = 0; +- screenshot_data->screenshot_area.width = width; +- screenshot_data->screenshot_area.height = height; ++ priv->screenshot_area.x = 0; ++ priv->screenshot_area.y = 0; ++ priv->screenshot_area.width = width; ++ priv->screenshot_area.height = height; + + settings = g_settings_new (A11Y_APPS_SCHEMA); +- if (screenshot_data->include_cursor && ++ if (priv->include_cursor && + !g_settings_get_boolean (settings, MAGNIFIER_ACTIVE_KEY)) +- _draw_cursor_image (screenshot_data->image, screenshot_data->screenshot_area); ++ _draw_cursor_image (priv->image, priv->screenshot_area); + g_object_unref (settings); + +- g_signal_handlers_disconnect_by_func (stage, (void *)grab_screenshot, (gpointer)screenshot_data); ++ g_signal_handlers_disconnect_by_func (stage, (void *)grab_screenshot, (gpointer)screenshot); + +- result = g_simple_async_result_new (NULL, on_screenshot_written, (gpointer)screenshot_data, grab_screenshot); ++ result = g_simple_async_result_new (G_OBJECT (screenshot), on_screenshot_written, NULL, grab_screenshot); + g_simple_async_result_run_in_thread (result, write_screenshot_thread, G_PRIORITY_DEFAULT, NULL); + g_object_unref (result); + } + + static void + grab_area_screenshot (ClutterActor *stage, +- _screenshot_data *screenshot_data) ++ ShellScreenshot *screenshot) + { + GSimpleAsyncResult *result; ++ ShellScreenshotPrivate *priv = screenshot->priv; + +- do_grab_screenshot (screenshot_data, +- screenshot_data->screenshot_area.x, +- screenshot_data->screenshot_area.y, +- screenshot_data->screenshot_area.width, +- screenshot_data->screenshot_area.height); ++ do_grab_screenshot (screenshot, ++ priv->screenshot_area.x, ++ priv->screenshot_area.y, ++ priv->screenshot_area.width, ++ priv->screenshot_area.height); + +- g_signal_handlers_disconnect_by_func (stage, (void *)grab_area_screenshot, (gpointer)screenshot_data); +- result = g_simple_async_result_new (NULL, on_screenshot_written, (gpointer)screenshot_data, grab_area_screenshot); ++ g_signal_handlers_disconnect_by_func (stage, (void *)grab_area_screenshot, (gpointer)screenshot); ++ result = g_simple_async_result_new (G_OBJECT (screenshot), on_screenshot_written, NULL, grab_area_screenshot); + g_simple_async_result_run_in_thread (result, write_screenshot_thread, G_PRIORITY_DEFAULT, NULL); + g_object_unref (result); + } +@@ -406,16 +416,21 @@ shell_screenshot_screenshot (ShellScreenshot *screenshot, + ShellScreenshotCallback callback) + { + ClutterActor *stage; +- _screenshot_data *data = g_new0 (_screenshot_data, 1); ++ ShellScreenshotPrivate *priv = screenshot->priv; ++ ++ if (priv->filename != NULL) { ++ if (callback) ++ callback (screenshot, FALSE, NULL, ""); ++ return; ++ } + +- data->screenshot = g_object_ref (screenshot); +- data->filename = g_strdup (filename); +- data->callback = callback; +- data->include_cursor = include_cursor; ++ priv->filename = g_strdup (filename); ++ priv->callback = callback; ++ priv->include_cursor = include_cursor; + +- stage = CLUTTER_ACTOR (shell_global_get_stage (screenshot->global)); ++ stage = CLUTTER_ACTOR (shell_global_get_stage (priv->global)); + +- g_signal_connect_after (stage, "paint", G_CALLBACK (grab_screenshot), (gpointer)data); ++ g_signal_connect_after (stage, "paint", G_CALLBACK (grab_screenshot), (gpointer)screenshot); + + clutter_actor_queue_redraw (stage); + } +@@ -445,19 +460,24 @@ shell_screenshot_screenshot_area (ShellScreenshot *screenshot, + ShellScreenshotCallback callback) + { + ClutterActor *stage; +- _screenshot_data *data = g_new0 (_screenshot_data, 1); ++ ShellScreenshotPrivate *priv = screenshot->priv; + +- data->screenshot = g_object_ref (screenshot); +- data->filename = g_strdup (filename); +- data->screenshot_area.x = x; +- data->screenshot_area.y = y; +- data->screenshot_area.width = width; +- data->screenshot_area.height = height; +- data->callback = callback; ++ if (priv->filename != NULL) { ++ if (callback) ++ callback (screenshot, FALSE, NULL, ""); ++ return; ++ } ++ ++ priv->filename = g_strdup (filename); ++ priv->screenshot_area.x = x; ++ priv->screenshot_area.y = y; ++ priv->screenshot_area.width = width; ++ priv->screenshot_area.height = height; ++ priv->callback = callback; + +- stage = CLUTTER_ACTOR (shell_global_get_stage (screenshot->global)); ++ stage = CLUTTER_ACTOR (shell_global_get_stage (priv->global)); + +- g_signal_connect_after (stage, "paint", G_CALLBACK (grab_area_screenshot), (gpointer)data); ++ g_signal_connect_after (stage, "paint", G_CALLBACK (grab_area_screenshot), (gpointer)screenshot); + + clutter_actor_queue_redraw (stage); + } +@@ -484,10 +504,9 @@ shell_screenshot_screenshot_window (ShellScreenshot *screenshot, + { + GSimpleAsyncResult *result; + GSettings *settings; ++ ShellScreenshotPrivate *priv = screenshot->priv; + +- _screenshot_data *screenshot_data = g_new0 (_screenshot_data, 1); +- +- MetaScreen *screen = shell_global_get_screen (screenshot->global); ++ MetaScreen *screen = shell_global_get_screen (priv->global); + MetaDisplay *display = meta_screen_get_display (screen); + MetaWindow *window = meta_display_get_focus_window (display); + ClutterActor *window_actor; +@@ -496,20 +515,14 @@ shell_screenshot_screenshot_window (ShellScreenshot *screenshot, + MetaRectangle rect; + cairo_rectangle_int_t clip; + +- screenshot_data->screenshot = g_object_ref (screenshot); +- screenshot_data->filename = g_strdup (filename); +- screenshot_data->callback = callback; +- +- if (!window) +- { +- screenshot_data->filename_used = g_strdup (""); +- result = g_simple_async_result_new (NULL, on_screenshot_written, (gpointer)screenshot_data, shell_screenshot_screenshot_window); +- g_simple_async_result_set_op_res_gboolean (result, FALSE); +- g_simple_async_result_complete (result); +- g_object_unref (result); ++ if (priv->filename != NULL || !window) { ++ if (callback) ++ callback (screenshot, FALSE, NULL, ""); ++ return; ++ } + +- return; +- } ++ priv->filename = g_strdup (filename); ++ priv->callback = callback; + + window_actor = CLUTTER_ACTOR (meta_window_get_compositor_private (window)); + clutter_actor_get_position (window_actor, &actor_x, &actor_y); +@@ -518,8 +531,8 @@ shell_screenshot_screenshot_window (ShellScreenshot *screenshot, + { + meta_window_get_outer_rect (window, &rect); + +- screenshot_data->screenshot_area.x = rect.x; +- screenshot_data->screenshot_area.y = rect.y; ++ priv->screenshot_area.x = rect.x; ++ priv->screenshot_area.y = rect.y; + + clip.x = rect.x - (gint) actor_x; + clip.y = rect.y - (gint) actor_y; +@@ -528,25 +541,25 @@ shell_screenshot_screenshot_window (ShellScreenshot *screenshot, + { + rect = *meta_window_get_rect (window); + +- screenshot_data->screenshot_area.x = (gint) actor_x + rect.x; +- screenshot_data->screenshot_area.y = (gint) actor_y + rect.y; ++ priv->screenshot_area.x = (gint) actor_x + rect.x; ++ priv->screenshot_area.y = (gint) actor_y + rect.y; + + clip.x = rect.x; + clip.y = rect.y; + } + +- clip.width = screenshot_data->screenshot_area.width = rect.width; +- clip.height = screenshot_data->screenshot_area.height = rect.height; ++ clip.width = priv->screenshot_area.width = rect.width; ++ clip.height = priv->screenshot_area.height = rect.height; + + stex = META_SHAPED_TEXTURE (meta_window_actor_get_texture (META_WINDOW_ACTOR (window_actor))); +- screenshot_data->image = meta_shaped_texture_get_image (stex, &clip); ++ priv->image = meta_shaped_texture_get_image (stex, &clip); + + settings = g_settings_new (A11Y_APPS_SCHEMA); + if (include_cursor && !g_settings_get_boolean (settings, MAGNIFIER_ACTIVE_KEY)) +- _draw_cursor_image (screenshot_data->image, screenshot_data->screenshot_area); ++ _draw_cursor_image (priv->image, priv->screenshot_area); + g_object_unref (settings); + +- result = g_simple_async_result_new (NULL, on_screenshot_written, (gpointer)screenshot_data, shell_screenshot_screenshot_window); ++ result = g_simple_async_result_new (G_OBJECT (screenshot), on_screenshot_written, NULL, shell_screenshot_screenshot_window); + g_simple_async_result_run_in_thread (result, write_screenshot_thread, G_PRIORITY_DEFAULT, NULL); + g_object_unref (result); + } +diff --git a/src/shell-screenshot.h b/src/shell-screenshot.h +index 76925f3..0b8ab1c 100644 +--- a/src/shell-screenshot.h ++++ b/src/shell-screenshot.h +@@ -11,8 +11,9 @@ + * + */ + +-typedef struct _ShellScreenshot ShellScreenshot; +-typedef struct _ShellScreenshotClass ShellScreenshotClass; ++typedef struct _ShellScreenshot ShellScreenshot; ++typedef struct _ShellScreenshotPrivate ShellScreenshotPrivate; ++typedef struct _ShellScreenshotClass ShellScreenshotClass; + + #define SHELL_TYPE_SCREENSHOT (shell_screenshot_get_type ()) + #define SHELL_SCREENSHOT(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_SCREENSHOT, ShellScreenshot)) +-- +2.1.0 + diff --git a/SOURCES/disallow-cancel-after-success.patch b/SOURCES/disallow-cancel-after-success.patch new file mode 100644 index 0000000..e02d39a --- /dev/null +++ b/SOURCES/disallow-cancel-after-success.patch @@ -0,0 +1,185 @@ +From 6dd24af0a881aa7440f127036f42d8f521628c24 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Sun, 5 Oct 2014 16:27:00 -0400 +Subject: [PATCH] gdm: disallow cancel after verification succeeds + +Once verification has succeeded, the train's already +left the building and we shouldn't allow canceling. + +This commit renders the cancel button non-reactive +and makes the cancel function be a noop after +verification succeeds. +--- + js/gdm/authPrompt.js | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index fd02c4d..d82b2b2 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -232,60 +232,61 @@ const AuthPrompt = new Lang.Class({ + // a smartcard. Smartcard insertion "preempts" what the user was + // doing, and smartcard removal aborts the preemption. + // The exceptions are: 1) Don't reset on smartcard insertion if we're already verifying + // with a smartcard + // 2) Don't reset if we've already succeeded at verification and + // the user is getting logged in. + if (this._userVerifier.serviceIsDefault(GdmUtil.SMARTCARD_SERVICE_NAME) && + this.verificationStatus == AuthPromptStatus.VERIFYING && + this.smartcardDetected) + return; + + if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) + this.reset(); + }, + + _onShowMessage: function(userVerifier, message, type) { + this.setMessage(message, type); + this.emit('prompted'); + }, + + _onVerificationFailed: function() { + this.clear(); + + this.updateSensitivity(true); + this.setActorInDefaultButtonWell(null); + this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED; + }, + + _onVerificationComplete: function() { + this.verificationStatus = AuthPromptStatus.VERIFICATION_SUCCEEDED; ++ this.cancelButton.reactive = false; + }, + + _onReset: function() { + if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) { + this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; + this.reset(); + } + }, + + addActorToDefaultButtonWell: function(actor) { + this._defaultButtonWell.add_child(actor); + }, + + setActorInDefaultButtonWell: function(actor, animate) { + if (!this._defaultButtonWellActor && + !actor) + return; + + let oldActor = this._defaultButtonWellActor; + + if (oldActor) + Tweener.removeTweens(oldActor); + + let isSpinner; + if (actor == this._spinner.actor) + isSpinner = true; + else + isSpinner = false; + + if (this._defaultButtonWellActor != actor && oldActor) { +@@ -403,60 +404,61 @@ const AuthPrompt = new Lang.Class({ + + updateSensitivity: function(sensitive) { + this._updateNextButtonSensitivity(sensitive); + this._entry.reactive = sensitive; + this._entry.clutter_text.editable = sensitive; + }, + + hide: function() { + this.setActorInDefaultButtonWell(null, true); + this.actor.hide(); + this._message.opacity = 0; + + this.setUser(null); + + this.updateSensitivity(true); + this._entry.set_text(''); + }, + + setUser: function(user) { + if (user) { + let userWidget = new UserWidget.UserWidget(user); + this._userWell.set_child(userWidget.actor); + } else { + this._userWell.set_child(null); + } + }, + + reset: function() { + let oldStatus = this.verificationStatus; + this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; ++ this.cancelButton.reactive = true; + + if (oldStatus == AuthPromptStatus.VERIFYING) + this._userVerifier.cancel(); + + this._queryingService = null; + this.clear(); + this._message.opacity = 0; + this.setUser(null); + this.stopSpinning(); + + if (oldStatus == AuthPromptStatus.VERIFICATION_FAILED) + this.emit('failed'); + + let beginRequestType; + + if (this._mode == AuthPromptMode.UNLOCK_ONLY) { + // The user is constant at the unlock screen, so it will immediately + // respond to the request with the username + beginRequestType = BeginRequestType.PROVIDE_USERNAME; + } else if (this._userVerifier.serviceIsForeground(GdmUtil.OVIRT_SERVICE_NAME) || + this._userVerifier.serviceIsForeground(GdmUtil.SMARTCARD_SERVICE_NAME)) { + // We don't need to know the username if the user preempted the login screen + // with a smartcard or with preauthenticated oVirt credentials + beginRequestType = BeginRequestType.DONT_PROVIDE_USERNAME; + } else { + // In all other cases, we should get the username up front. + beginRequestType = BeginRequestType.PROVIDE_USERNAME; + } + + this.emit('reset', beginRequestType); +@@ -472,35 +474,38 @@ const AuthPrompt = new Lang.Class({ + }, + + begin: function(params) { + params = Params.parse(params, { userName: null, + hold: null }); + + this.updateSensitivity(false); + + let hold = params.hold; + if (!hold) + hold = new Batch.Hold(); + + this._userVerifier.begin(params.userName, hold); + this.verificationStatus = AuthPromptStatus.VERIFYING; + }, + + finish: function(onComplete) { + if (!this._userVerifier.hasPendingMessages) { + onComplete(); + return; + } + + let signalId = this._userVerifier.connect('no-more-messages', + Lang.bind(this, function() { + this._userVerifier.disconnect(signalId); + onComplete(); + })); + }, + + cancel: function() { ++ if (this.verificationStatus == AuthPromptStatus.NOT_VERIFYING || this.verificationStatus == AuthPromptStatus.VERIFICATION_SUCCEEDED) { ++ return; ++ } + this.reset(); + this.emit('cancelled'); + } + }); + Signals.addSignalMethods(AuthPrompt.prototype); +-- +2.1.0 + diff --git a/SOURCES/fix-cancel-sensitivity.patch b/SOURCES/fix-cancel-sensitivity.patch new file mode 100644 index 0000000..6747bdc --- /dev/null +++ b/SOURCES/fix-cancel-sensitivity.patch @@ -0,0 +1,89 @@ +From e8d452420d80f34c767bffc98303c0769da54ed5 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 7 Oct 2014 14:30:34 -0400 +Subject: [PATCH] gdm: fix sensitivity of auth prompt when cancelling early and + user list is disabled + +If the user list is disabled and the user clicks cancel quickly enough +after typing their username, they can get in a state where the +auth prompt gets stuck in the insensitive state. + +This is because the login dialog code makes the prompt insensitive +while while pam is processing the provided username, but the prompt +only makes itself sensitive again when it is hidden. + +This commit makes it sensitive right before asking for a username again. +--- + js/gdm/loginDialog.js | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index b4487e6..893ab31 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -685,60 +685,62 @@ const LoginDialog = new Lang.Class({ + + // Translators: this message is shown below the username entry field + // to clue the user in on how to login to the local network realm + this._authPrompt.setMessage(_("(e.g., user or %s)").format(hint), GdmUtil.MessageType.HINT); + }, + + _askForUsernameAndBeginVerification: function() { + this._authPrompt.setPasswordChar(''); + this._authPrompt.setQuestion(_("Username: ")); + + let realmManager = new Realmd.Manager(); + let realmSignalId = realmManager.connect('login-format-changed', + Lang.bind(this, this._showRealmLoginHint)); + this._showRealmLoginHint(realmManager.loginFormat); + + let nextSignalId = this._authPrompt.connect('next', + Lang.bind(this, function() { + this._authPrompt.disconnect(nextSignalId); + this._authPrompt.updateSensitivity(false); + let answer = this._authPrompt.getAnswer(); + this._user = this._userManager.get_user(answer); + this._authPrompt.clear(); + this._authPrompt.startSpinning(); + this._authPrompt.begin({ userName: answer }); + this._updateCancelButton(); + + realmManager.disconnect(realmSignalId) + realmManager.release(); + })); + this._updateCancelButton(); ++ ++ this._authPrompt.updateSensitivity(true); + this._showPrompt(); + }, + + _startSession: function(serviceName) { + Tweener.addTween(this.actor, + { opacity: 0, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad', + onUpdate: function() { + let children = Main.layoutManager.uiGroup.get_children(); + + for (let i = 0; i < children.length; i++) { + if (children[i] != Main.layoutManager.screenShieldGroup) + children[i].opacity = this.actor.opacity; + } + }, + onUpdateScope: this, + onComplete: function() { + Mainloop.idle_add(Lang.bind(this, function() { + this._greeter.call_start_session_when_ready_sync(serviceName, true, null); + return false; + })); + }, + onCompleteScope: this }); + }, + + _onSessionOpened: function(client, serviceName) { + this._authPrompt.finish(Lang.bind(this, function() { + this._startSession(serviceName); + })); +-- +1.8.3.1 + diff --git a/SOURCES/fix-login-screen-focus.patch b/SOURCES/fix-login-screen-focus.patch new file mode 100644 index 0000000..7ac50ec --- /dev/null +++ b/SOURCES/fix-login-screen-focus.patch @@ -0,0 +1,218 @@ +From f936c6e5e2566e6a665b61b1610c249e7ec5479b Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 6 Oct 2014 16:38:03 -0400 +Subject: [PATCH 1/2] screenShield: focus login screen after lifting shield + +This commit ensures the login screen gets focused after +the screen shield is raised. + +The code affects the unlock screen as well, but it's +less important since the unlock screen gets destroyed +and recreated each time the curtain moves, so it +has an opportunity to take focus on its own. +--- + js/ui/screenShield.js | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js +index 089333b..4473b72 100644 +--- a/js/ui/screenShield.js ++++ b/js/ui/screenShield.js +@@ -1,37 +1,38 @@ + // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + + const Cairo = imports.cairo; + const Clutter = imports.gi.Clutter; + const Gio = imports.gi.Gio; + const GLib = imports.gi.GLib; + const GnomeDesktop = imports.gi.GnomeDesktop; ++const Gtk = imports.gi.Gtk; + const Lang = imports.lang; + const Mainloop = imports.mainloop; + const Meta = imports.gi.Meta; + const Shell = imports.gi.Shell; + const Signals = imports.signals; + const St = imports.gi.St; + const TweenerEquations = imports.tweener.equations; + + const Background = imports.ui.background; + const GnomeSession = imports.misc.gnomeSession; + const Hash = imports.misc.hash; + const Layout = imports.ui.layout; + const OVirt = imports.gdm.oVirt; + const LoginManager = imports.misc.loginManager; + const Lightbox = imports.ui.lightbox; + const Main = imports.ui.main; + const Overview = imports.ui.overview; + const MessageTray = imports.ui.messageTray; + const ShellDBus = imports.ui.shellDBus; + const SmartcardManager = imports.misc.smartcardManager; + const Tweener = imports.ui.tweener; + const Util = imports.misc.util; + + const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver'; + const LOCK_ENABLED_KEY = 'lock-enabled'; + const LOCK_DELAY_KEY = 'lock-delay'; + + // fraction of screen height the arrow must reach before completing + // the slide up automatically + const ARROW_DRAG_THRESHOLD = 0.1; +@@ -851,60 +852,63 @@ const ScreenShield = new Lang.Class({ + _onLightboxShown: function() { + this.activate(false); + }, + + showDialog: function() { + // Ensure that the stage window is mapped, before taking a grab + // otherwise X errors out + Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { + if (!this._becomeModal()) { + // In the login screen, this is a hard error. Fail-whale + log('Could not acquire modal grab for the login screen. Aborting login process.'); + Meta.quit(Meta.ExitCode.ERROR); + } + + return false; + })); + + this.actor.show(); + this._isGreeter = Main.sessionMode.isGreeter; + this._isLocked = true; + this._ensureUnlockDialog(true, true); + this._hideLockScreen(false, 0); + }, + + _hideLockScreenComplete: function() { + if (Main.sessionMode.currentMode == 'lock-screen') + Main.sessionMode.popMode('lock-screen'); + + this._lockScreenState = MessageTray.State.HIDDEN; + this._lockScreenGroup.hide(); ++ ++ this._dialog.actor.grab_key_focus(); ++ this._dialog.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); + }, + + _hideLockScreen: function(animate, velocity) { + if (this._lockScreenState == MessageTray.State.HIDDEN) + return; + + this._lockScreenState = MessageTray.State.HIDING; + + Tweener.removeTweens(this._lockScreenGroup); + + if (animate) { + // Tween the lock screen out of screen + // if velocity is not specified (i.e. we come here from pressing ESC), + // use the same speed regardless of original position + // if velocity is specified, it's in pixels per milliseconds + let h = global.stage.height; + let delta = (h + this._lockScreenGroup.y); + let min_velocity = global.stage.height / (CURTAIN_SLIDE_TIME * 1000); + + velocity = Math.max(min_velocity, velocity); + let time = (delta / velocity) / 1000; + + Tweener.addTween(this._lockScreenGroup, + { y: -h, + time: time, + transition: 'easeInQuad', + onComplete: Lang.bind(this, this._hideLockScreenComplete), + }); + } else { + this._hideLockScreenComplete(); +-- +2.1.0 + + +From e5c2ad5e29ae1dd691f1762ff1a5e94f0dff8eb5 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 8 Oct 2014 09:39:44 -0400 +Subject: [PATCH 2/2] screenShield: fix trace back when unlocking session from + another vt + +commit 1d374ac8bd496f6fa6f4c55ffd207bd30bd50075 introduced a bug that +prevents unlock from working when initiated from another VT +(user switching). + +This commit fixes the exception raised. + +https://bugzilla.gnome.org/show_bug.cgi?id=708105 +--- + js/ui/screenShield.js | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js +index 4473b72..033ed2c 100644 +--- a/js/ui/screenShield.js ++++ b/js/ui/screenShield.js +@@ -853,62 +853,64 @@ const ScreenShield = new Lang.Class({ + this.activate(false); + }, + + showDialog: function() { + // Ensure that the stage window is mapped, before taking a grab + // otherwise X errors out + Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { + if (!this._becomeModal()) { + // In the login screen, this is a hard error. Fail-whale + log('Could not acquire modal grab for the login screen. Aborting login process.'); + Meta.quit(Meta.ExitCode.ERROR); + } + + return false; + })); + + this.actor.show(); + this._isGreeter = Main.sessionMode.isGreeter; + this._isLocked = true; + this._ensureUnlockDialog(true, true); + this._hideLockScreen(false, 0); + }, + + _hideLockScreenComplete: function() { + if (Main.sessionMode.currentMode == 'lock-screen') + Main.sessionMode.popMode('lock-screen'); + + this._lockScreenState = MessageTray.State.HIDDEN; + this._lockScreenGroup.hide(); + +- this._dialog.actor.grab_key_focus(); +- this._dialog.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); ++ if (this._dialog) { ++ this._dialog.actor.grab_key_focus(); ++ this._dialog.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); ++ } + }, + + _hideLockScreen: function(animate, velocity) { + if (this._lockScreenState == MessageTray.State.HIDDEN) + return; + + this._lockScreenState = MessageTray.State.HIDING; + + Tweener.removeTweens(this._lockScreenGroup); + + if (animate) { + // Tween the lock screen out of screen + // if velocity is not specified (i.e. we come here from pressing ESC), + // use the same speed regardless of original position + // if velocity is specified, it's in pixels per milliseconds + let h = global.stage.height; + let delta = (h + this._lockScreenGroup.y); + let min_velocity = global.stage.height / (CURTAIN_SLIDE_TIME * 1000); + + velocity = Math.max(min_velocity, velocity); + let time = (delta / velocity) / 1000; + + Tweener.addTween(this._lockScreenGroup, + { y: -h, + time: time, + transition: 'easeInQuad', + onComplete: Lang.bind(this, this._hideLockScreenComplete), + }); + } else { + this._hideLockScreenComplete(); +-- +2.1.0 + diff --git a/SOURCES/fix-session-activation.patch b/SOURCES/fix-session-activation.patch new file mode 100644 index 0000000..4b8f364 --- /dev/null +++ b/SOURCES/fix-session-activation.patch @@ -0,0 +1,120 @@ +From 7f19d43041c8402904012c89c53060208efe5016 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 13 Nov 2014 09:26:52 -0500 +Subject: [PATCH] loginDialog: only emit session-activated on user action + +Right now we emit session-activated any time the bullet +moves in the session menu. That includes at start up when +picking an item arbitrarily, and any time GDM reports the +session was read from the user's account settings. + +session-activated informs GDM about the newly selected session, +so emitting it in response to GDM reporting a session is a +bad idea (it's not only pointless, but it can least to +oscillations) + +This commit changes the code to only emit session-activated when +the user explicitly activates a session item from the gear menu. +--- + js/gdm/loginDialog.js | 6 +----- + 1 file changed, 1 insertion(+), 5 deletions(-) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 9ee259f..ec88c73 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -327,90 +327,86 @@ const SessionMenuButton = new Lang.Class({ + this._menu.toggle(); + })); + + this._items = {}; + this._activeSessionId = null; + this._populate(); + }, + + updateSensitivity: function(sensitive) { + this._button.reactive = sensitive; + this._button.can_focus = sensitive; + this._menu.close(BoxPointer.PopupAnimation.NONE); + }, + + _updateOrnament: function() { + let itemIds = Object.keys(this._items); + for (let i = 0; i < itemIds.length; i++) { + if (itemIds[i] == this._activeSessionId) + this._items[itemIds[i]].setShowDot(true); + else + this._items[itemIds[i]].setShowDot(false); + } + }, + + setActiveSession: function(sessionId) { + if (sessionId == this._activeSessionId) + return; + + this._activeSessionId = sessionId; + this._updateOrnament(); +- +- this.emit('session-activated', this._activeSessionId); + }, + + close: function() { + this._menu.close(); + }, + + _populate: function() { + let ids = Gdm.get_session_ids(); + ids.sort(); + + if (ids.length <= 1) { + this._button.hide(); + return; + } + + for (let i = 0; i < ids.length; i++) { + let [sessionName, sessionDescription] = Gdm.get_session_name_and_description(ids[i]); + + let id = ids[i]; + let item = new PopupMenu.PopupMenuItem(sessionName); + this._menu.addMenuItem(item); + this._items[id] = item; + +- if (!this._activeSessionId) +- this.setActiveSession(id); +- + item.connect('activate', Lang.bind(this, function() { + this.setActiveSession(id); ++ this.emit('session-activated', this._activeSessionId); + })); + } + } + }); + Signals.addSignalMethods(SessionMenuButton.prototype); + + const LoginDialog = new Lang.Class({ + Name: 'LoginDialog', + + _init: function(parentActor) { + this.actor = new Shell.GenericContainer({ style_class: 'login-dialog', + visible: false }); + this.actor.get_accessible().set_role(Atk.Role.WINDOW); + + this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true })); + this.actor.connect('allocate', Lang.bind(this, this._onAllocate)); + this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); + parentActor.add_child(this.actor); + + this._userManager = AccountsService.UserManager.get_default() + let gdmClient = new Gdm.Client(); + + if (GLib.getenv('GDM_GREETER_TEST') != '1') { + this._greeter = gdmClient.get_greeter_sync(null); + + this._greeter.connect('default-session-name-changed', + Lang.bind(this, this._onDefaultSessionChanged)); + + this._greeter.connect('session-opened', + Lang.bind(this, this._onSessionOpened)); +-- +2.1.0 + diff --git a/SOURCES/fix-thumbail-box-shrinking.patch b/SOURCES/fix-thumbail-box-shrinking.patch new file mode 100644 index 0000000..bd70203 --- /dev/null +++ b/SOURCES/fix-thumbail-box-shrinking.patch @@ -0,0 +1,676 @@ +From 2446c4bdb9bcea36234108303c7bf33b3d238fa3 Mon Sep 17 00:00:00 2001 +From: "Jasper St. Pierre" +Date: Mon, 25 Feb 2013 18:05:45 -0500 +Subject: [PATCH 1/8] overview: Move the group construction to the controls + manager + +Instead of creating a bunch of random actors and then passing +them off to the controls manager, let the controls manager +construct them. This leaves the controls manager in charge +of the ordeal. + +https://bugzilla.gnome.org/show_bug.cgi?id=694469 +--- + js/ui/overview.js | 37 ++++-------------------------- + js/ui/overviewControls.js | 58 ++++++++++++++++++++++++++++++++--------------- + 2 files changed, 45 insertions(+), 50 deletions(-) + +diff --git a/js/ui/overview.js b/js/ui/overview.js +index 02b23c7..ab4845c 100644 +--- a/js/ui/overview.js ++++ b/js/ui/overview.js +@@ -11,7 +11,6 @@ const Shell = imports.gi.Shell; + const Gdk = imports.gi.Gdk; + + const Background = imports.ui.background; +-const Dash = imports.ui.dash; + const DND = imports.ui.dnd; + const LayoutManager = imports.ui.layout; + const Main = imports.ui.main; +@@ -20,8 +19,6 @@ const OverviewControls = imports.ui.overviewControls; + const Panel = imports.ui.panel; + const Params = imports.misc.params; + const Tweener = imports.ui.tweener; +-const ViewSelector = imports.ui.viewSelector; +-const WorkspaceThumbnail = imports.ui.workspaceThumbnail; + + // Time for initial animation going into Overview mode + const ANIMATION_TIME = 0.25; +@@ -133,14 +130,6 @@ const Overview = new Lang.Class({ + y_expand: true }); + this._overview._delegate = this; + +- this._groupStack = new St.Widget({ layout_manager: new Clutter.BinLayout(), +- x_expand: true, y_expand: true, +- clip_to_allocation: true }); +- this._group = new St.BoxLayout({ name: 'overview-group', +- reactive: true, +- x_expand: true, y_expand: true }); +- this._groupStack.add_actor(this._group); +- + this._backgroundGroup = new Meta.BackgroundGroup(); + global.overlay_group.add_child(this._backgroundGroup); + this._backgroundGroup.hide(); +@@ -177,7 +166,6 @@ const Overview = new Lang.Class({ + Main.xdndHandler.connect('drag-end', Lang.bind(this, this._onDragEnd)); + + global.screen.connect('restacked', Lang.bind(this, this._onRestacked)); +- this._group.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); + + this._windowSwitchTimeoutId = 0; + this._windowSwitchTimestamp = 0; +@@ -276,28 +264,13 @@ const Overview = new Lang.Class({ + this._overview.add_actor(this._searchEntryBin); + + // Create controls +- this._dash = new Dash.Dash(); +- this._viewSelector = new ViewSelector.ViewSelector(this._searchEntry, +- this._dash.showAppsButton); +- this._thumbnailsBox = new WorkspaceThumbnail.ThumbnailsBox(); +- this._controls = new OverviewControls.ControlsManager(this._dash, +- this._thumbnailsBox, +- this._viewSelector); +- +- this._controls.dashActor.x_align = Clutter.ActorAlign.START; +- this._controls.dashActor.y_expand = true; +- +- // Put the dash in a separate layer to allow content to be centered +- this._groupStack.add_actor(this._controls.dashActor); +- +- // Pack all the actors into the group +- this._group.add_actor(this._controls.dashSpacer); +- this._group.add(this._viewSelector.actor, { x_fill: true, +- expand: true }); +- this._group.add_actor(this._controls.thumbnailsActor); ++ this._controls = new OverviewControls.ControlsManager(this._searchEntry); ++ this._dash = this._controls.dash; ++ this._viewSelector = this._controls.viewSelector; + + // Add our same-line elements after the search entry +- this._overview.add(this._groupStack, { y_fill: true, expand: true }); ++ this._overview.add(this._controls.actor, { y_fill: true, expand: true }); ++ this._controls.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); + + this._stack.add_actor(this._controls.indicatorActor); + +diff --git a/js/ui/overviewControls.js b/js/ui/overviewControls.js +index ba5c7a8..b3eb5d3 100644 +--- a/js/ui/overviewControls.js ++++ b/js/ui/overviewControls.js +@@ -6,10 +6,12 @@ const Meta = imports.gi.Meta; + const St = imports.gi.St; + const Shell = imports.gi.Shell; + ++const Dash = imports.ui.dash; + const Main = imports.ui.main; + const Params = imports.misc.params; + const Tweener = imports.ui.tweener; + const ViewSelector = imports.ui.viewSelector; ++const WorkspaceThumbnail = imports.ui.workspaceThumbnail; + + const SIDE_CONTROLS_ANIMATION_TIME = 0.16; + +@@ -309,6 +311,10 @@ const DashSlider = new Lang.Class({ + // available allocation + this._dash.actor.x_expand = true; + this._dash.actor.y_expand = true; ++ ++ this.actor.x_align = Clutter.ActorAlign.START; ++ this.actor.y_expand = true; ++ + this.actor.add_actor(this._dash.actor); + + this._dash.connect('icon-size-changed', Lang.bind(this, this.updateSlide)); +@@ -479,36 +485,52 @@ const MessagesIndicator = new Lang.Class({ + const ControlsManager = new Lang.Class({ + Name: 'ControlsManager', + +- _init: function(dash, thumbnails, viewSelector) { +- this._dashSlider = new DashSlider(dash); +- this.dashActor = this._dashSlider.actor; +- this.dashSpacer = new DashSpacer(); +- this.dashSpacer.setDashActor(this.dashActor); ++ _init: function(searchEntry) { ++ this.dash = new Dash.Dash(); ++ this._dashSlider = new DashSlider(this.dash); ++ this._dashSpacer = new DashSpacer(); ++ this._dashSpacer.setDashActor(this._dashSlider.actor); ++ ++ this._thumbnailsBox = new WorkspaceThumbnail.ThumbnailsBox(); ++ this._thumbnailsSlider = new ThumbnailsSlider(this._thumbnailsBox); + +- this._thumbnailsSlider = new ThumbnailsSlider(thumbnails); +- this.thumbnailsActor = this._thumbnailsSlider.actor; ++ this.viewSelector = new ViewSelector.ViewSelector(searchEntry, ++ this.dash.showAppsButton); ++ this.viewSelector.connect('page-changed', Lang.bind(this, this._setVisibility)); ++ this.viewSelector.connect('page-empty', Lang.bind(this, this._onPageEmpty)); + +- this._indicator = new MessagesIndicator(viewSelector); ++ this._indicator = new MessagesIndicator(this.viewSelector); + this.indicatorActor = this._indicator.actor; + +- this._viewSelector = viewSelector; +- this._viewSelector.connect('page-changed', Lang.bind(this, this._setVisibility)); +- this._viewSelector.connect('page-empty', Lang.bind(this, this._onPageEmpty)); ++ this.actor = new St.Widget({ layout_manager: new Clutter.BinLayout(), ++ x_expand: true, y_expand: true, ++ clip_to_allocation: true }); ++ this._group = new St.BoxLayout({ name: 'overview-group', ++ reactive: true, ++ x_expand: true, y_expand: true }); ++ this.actor.add_actor(this._group); ++ ++ this.actor.add_actor(this._dashSlider.actor); ++ ++ this._group.add_actor(this._dashSpacer); ++ this._group.add(this.viewSelector.actor, { x_fill: true, ++ expand: true }); ++ this._group.add_actor(this._thumbnailsSlider.actor); + + Main.overview.connect('showing', Lang.bind(this, this._updateSpacerVisibility)); + Main.overview.connect('item-drag-begin', Lang.bind(this, + function() { +- let activePage = this._viewSelector.getActivePage(); ++ let activePage = this.viewSelector.getActivePage(); + if (activePage != ViewSelector.ViewPage.WINDOWS) +- this._viewSelector.fadeHalf(); ++ this.viewSelector.fadeHalf(); + })); + Main.overview.connect('item-drag-end', Lang.bind(this, + function() { +- this._viewSelector.fadeIn(); ++ this.viewSelector.fadeIn(); + })); + Main.overview.connect('item-drag-cancelled', Lang.bind(this, + function() { +- this._viewSelector.fadeIn(); ++ this.viewSelector.fadeIn(); + })); + }, + +@@ -521,7 +543,7 @@ const ControlsManager = new Lang.Class({ + (Main.overview.animationInProgress && !Main.overview.visibleTarget)) + return; + +- let activePage = this._viewSelector.getActivePage(); ++ let activePage = this.viewSelector.getActivePage(); + let dashVisible = (activePage == ViewSelector.ViewPage.WINDOWS || + activePage == ViewSelector.ViewPage.APPS); + let thumbnailsVisible = (activePage == ViewSelector.ViewPage.WINDOWS); +@@ -541,8 +563,8 @@ const ControlsManager = new Lang.Class({ + if (Main.overview.animationInProgress && !Main.overview.visibleTarget) + return; + +- let activePage = this._viewSelector.getActivePage(); +- this.dashSpacer.visible = (activePage == ViewSelector.ViewPage.WINDOWS); ++ let activePage = this.viewSelector.getActivePage(); ++ this._dashSpacer.visible = (activePage == ViewSelector.ViewPage.WINDOWS); + }, + + _onPageEmpty: function() { +-- +2.1.0 + + +From 83330f59c82da874ef381ad81e79c7796bb228d1 Mon Sep 17 00:00:00 2001 +From: "Jasper St. Pierre" +Date: Wed, 18 Sep 2013 19:42:41 -0400 +Subject: [PATCH 2/8] overviewControls: Correct the use of x2 in SlidingControl + +The x2 here needs to be more than just the width; it needs to +be added onto the x1. + +https://bugzilla.gnome.org/show_bug.cgi?id=694881 +--- + js/ui/overviewControls.js | 9 +++++---- + 1 file changed, 5 insertions(+), 4 deletions(-) + +diff --git a/js/ui/overviewControls.js b/js/ui/overviewControls.js +index b3eb5d3..2b8977d 100644 +--- a/js/ui/overviewControls.js ++++ b/js/ui/overviewControls.js +@@ -62,10 +62,11 @@ const SlideLayout = new Lang.Class({ + let translationX = (realDirection == SlideDirection.LEFT) ? + (availWidth - natWidth) : (natWidth - availWidth); + +- let actorBox = new Clutter.ActorBox({ x1: translationX, +- y1: 0, +- x2: child.x_expand ? availWidth : natWidth, +- y2: child.y_expand ? availHeight : natHeight }); ++ let actorBox = new Clutter.ActorBox(); ++ actorBox.x1 = translationX; ++ actorBox.x2 = actorBox.x1 + child.x_expand ? availWidth : natWidth; ++ actorBox.y1 = 0; ++ actorBox.y2 = actorBox.y1 + child.y_expand ? availHeight : natHeight; + + child.allocate(actorBox, flags); + }, +-- +2.1.0 + + +From d603fda86689db852ff204048a57805bcded5f1b Mon Sep 17 00:00:00 2001 +From: "Jasper St. Pierre" +Date: Wed, 18 Sep 2013 19:48:22 -0400 +Subject: [PATCH 3/8] overviewControls: Clarify some code with a comment + +translationX is sort of a bad name, since it confuses with the +actor's translation, which is used for sliding without allocation. + +https://bugzilla.gnome.org/show_bug.cgi?id=694881 +--- + js/ui/overviewControls.js | 8 +++++--- + 1 file changed, 5 insertions(+), 3 deletions(-) + +diff --git a/js/ui/overviewControls.js b/js/ui/overviewControls.js +index 2b8977d..50a7f8b 100644 +--- a/js/ui/overviewControls.js ++++ b/js/ui/overviewControls.js +@@ -58,12 +58,14 @@ const SlideLayout = new Lang.Class({ + let availWidth = Math.round(box.x2 - box.x1); + let availHeight = Math.round(box.y2 - box.y1); + ++ // Align the actor inside the clipped box, as the actor's alignment ++ // flags only determine what to do if the allocated box is bigger ++ // than the actor's box. + let realDirection = getRtlSlideDirection(this._direction, child); +- let translationX = (realDirection == SlideDirection.LEFT) ? +- (availWidth - natWidth) : (natWidth - availWidth); ++ let alignX = (realDirection == SlideDirection.LEFT) ? (availWidth - natWidth) : (natWidth - availWidth); + + let actorBox = new Clutter.ActorBox(); +- actorBox.x1 = translationX; ++ actorBox.x1 = alignX; + actorBox.x2 = actorBox.x1 + child.x_expand ? availWidth : natWidth; + actorBox.y1 = 0; + actorBox.y2 = actorBox.y1 + child.y_expand ? availHeight : natHeight; +-- +2.1.0 + + +From b311522ecc172e1ca7f46cf46492b6fd7f847e23 Mon Sep 17 00:00:00 2001 +From: "Jasper St. Pierre" +Date: Wed, 18 Sep 2013 19:48:51 -0400 +Subject: [PATCH 4/8] overviewControls: Don't try to align something sliding to + the right + +0 is already the correct value. + +https://bugzilla.gnome.org/show_bug.cgi?id=694881 +--- + js/ui/overviewControls.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/js/ui/overviewControls.js b/js/ui/overviewControls.js +index 50a7f8b..0841715 100644 +--- a/js/ui/overviewControls.js ++++ b/js/ui/overviewControls.js +@@ -62,7 +62,7 @@ const SlideLayout = new Lang.Class({ + // flags only determine what to do if the allocated box is bigger + // than the actor's box. + let realDirection = getRtlSlideDirection(this._direction, child); +- let alignX = (realDirection == SlideDirection.LEFT) ? (availWidth - natWidth) : (natWidth - availWidth); ++ let alignX = (realDirection == SlideDirection.LEFT) ? (availWidth - natWidth) : 0; + + let actorBox = new Clutter.ActorBox(); + actorBox.x1 = alignX; +-- +2.1.0 + + +From 38a92b4a0dc60f475c30e00ae9987e1e320950d0 Mon Sep 17 00:00:00 2001 +From: "Jasper St. Pierre" +Date: Fri, 11 Oct 2013 22:36:49 -0400 +Subject: [PATCH 5/8] overviewControls: Don't use the child's preferred size to + slide from + +In order for the workspace thumbnails box to have the correct size, +we need to constrain the width of the thumbnails box to the height we're +given, instead of assuming an unlimited height. + +https://bugzilla.gnome.org/show_bug.cgi?id=694881 +--- + js/ui/overviewControls.js | 10 ++-------- + 1 file changed, 2 insertions(+), 8 deletions(-) + +diff --git a/js/ui/overviewControls.js b/js/ui/overviewControls.js +index 0841715..aafb905 100644 +--- a/js/ui/overviewControls.js ++++ b/js/ui/overviewControls.js +@@ -54,9 +54,9 @@ const SlideLayout = new Lang.Class({ + vfunc_allocate: function(container, box, flags) { + let child = container.get_first_child(); + +- let [, , natWidth, natHeight] = child.get_preferred_size(); + let availWidth = Math.round(box.x2 - box.x1); + let availHeight = Math.round(box.y2 - box.y1); ++ let [, natWidth] = child.get_preferred_width(availHeight); + + // Align the actor inside the clipped box, as the actor's alignment + // flags only determine what to do if the allocated box is bigger +@@ -68,7 +68,7 @@ const SlideLayout = new Lang.Class({ + actorBox.x1 = alignX; + actorBox.x2 = actorBox.x1 + child.x_expand ? availWidth : natWidth; + actorBox.y1 = 0; +- actorBox.y2 = actorBox.y1 + child.y_expand ? availHeight : natHeight; ++ actorBox.y2 = actorBox.y1 + availHeight; + + child.allocate(actorBox, flags); + }, +@@ -237,11 +237,6 @@ const ThumbnailsSlider = new Lang.Class({ + + this._thumbnailsBox = thumbnailsBox; + +- // SlideLayout reads the actor's expand flags to decide +- // whether to allocate the natural size to its child, or the whole +- // available allocation +- this._thumbnailsBox.actor.y_expand = true; +- + this.actor.request_mode = Clutter.RequestMode.WIDTH_FOR_HEIGHT; + this.actor.reactive = true; + this.actor.track_hover = true; +@@ -313,7 +308,6 @@ const DashSlider = new Lang.Class({ + // whether to allocate the natural size to its child, or the whole + // available allocation + this._dash.actor.x_expand = true; +- this._dash.actor.y_expand = true; + + this.actor.x_align = Clutter.ActorAlign.START; + this.actor.y_expand = true; +-- +2.1.0 + + +From 9bc20a14840beaa4c308a11000e2a6b737f5c208 Mon Sep 17 00:00:00 2001 +From: "Jasper St. Pierre" +Date: Fri, 11 Oct 2013 21:27:59 -0400 +Subject: [PATCH 6/8] workspaceThumbnail: Drop the _background hack + +The _background hack was added because the old way the zooming animation +worked, it set the allocation of the workspaces view and thumbnails box +to the final position and used animations to smoothly animate. + +During the 3.6 cycle when we added the new search view, Cosimo changed the +way the zoom animation works so that rather than set the final allocation +and animate, we actually do adjust the allocation of the workspaces view +and thumbnails box. + +So, as the hack is no longer necessary, we can drop it. + +https://bugzilla.gnome.org/show_bug.cgi?id=694881 +--- + data/theme/gnome-shell.css | 11 +++---- + js/ui/workspaceThumbnail.js | 77 +++++++++++++-------------------------------- + 2 files changed, 25 insertions(+), 63 deletions(-) + +diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css +index 532c8a5..80c929e 100644 +--- a/data/theme/gnome-shell.css ++++ b/data/theme/gnome-shell.css +@@ -673,7 +673,9 @@ StScrollBar StButton#vhandle:active { + padding-bottom: 32px; + } + +-.workspace-thumbnails-background { ++.workspace-thumbnails { ++ spacing: 11px; ++ visible-width: 32px; /* Amount visible before hovering */ + border: 1px solid rgba(128, 128, 128, 0.4); + border-right: 0px; + border-radius: 9px 0px 0px 9px; +@@ -681,18 +683,13 @@ StScrollBar StButton#vhandle:active { + padding: 11px 7px 11px 11px; + } + +-.workspace-thumbnails-background:rtl { ++.workspace-thumbnails:rtl { + border-right: 1px; + border-left: 0px; + border-radius: 0px 9px 9px 0px; + padding: 11px 11px 11px 7px; + } + +-.workspace-thumbnails { +- spacing: 11px; +- visible-width: 32px; /* Amount visible before hovering */ +-} +- + .workspace-thumbnail-indicator { + border: 4px solid rgba(255,255,255,0.7); + border-radius: 4px; +diff --git a/js/ui/workspaceThumbnail.js b/js/ui/workspaceThumbnail.js +index 6b631d1..d4374f2 100644 +--- a/js/ui/workspaceThumbnail.js ++++ b/js/ui/workspaceThumbnail.js +@@ -536,20 +536,6 @@ const ThumbnailsBox = new Lang.Class({ + this.actor.connect('allocate', Lang.bind(this, this._allocate)); + this.actor._delegate = this; + +- // When we animate the scale, we don't animate the requested size of the thumbnails, rather +- // we ask for our final size and then animate within that size. This slightly simplifies the +- // interaction with the main workspace windows (instead of constantly reallocating them +- // to a new size, they get a new size once, then use the standard window animation code +- // allocate the windows to their new positions), however it causes problems for drawing +- // the background and border wrapped around the thumbnail as we animate - we can't just pack +- // the container into a box and set style properties on the box since that box would wrap +- // around the final size not the animating size. So instead we fake the background with +- // an actor underneath the content and adjust the allocation of our children to leave space +- // for the border and padding of the background actor. +- this._background = new St.Bin({ style_class: 'workspace-thumbnails-background' }); +- +- this.actor.add_actor(this._background); +- + let indicator = new St.Bin({ style_class: 'workspace-thumbnail-indicator' }); + + // We don't want the indicator to affect drag-and-drop +@@ -1038,9 +1024,6 @@ const ThumbnailsBox = new Lang.Class({ + }, + + _getPreferredHeight: function(actor, forWidth, alloc) { +- // See comment about this._background in _init() +- let themeNode = this._background.get_theme_node(); +- + // Note that for getPreferredWidth/Height we cheat a bit and skip propagating + // the size request to our children because we know how big they are and know + // that the actors aren't depending on the virtual functions being called. +@@ -1048,24 +1031,21 @@ const ThumbnailsBox = new Lang.Class({ + if (this._thumbnails.length == 0) + return; + +- let spacing = this.actor.get_theme_node().get_length('spacing'); ++ let themeNode = this.actor.get_theme_node(); ++ ++ let spacing = themeNode.get_length('spacing'); + let nWorkspaces = global.screen.n_workspaces; + let totalSpacing = (nWorkspaces - 1) * spacing; + +- [alloc.min_size, alloc.natural_size] = +- themeNode.adjust_preferred_height(totalSpacing, +- totalSpacing + nWorkspaces * this._porthole.height * MAX_THUMBNAIL_SCALE); ++ alloc.min_size = totalSpacing; ++ alloc.natural_size = totalSpacing + nWorkspaces * this._porthole.height * MAX_THUMBNAIL_SCALE; + }, + + _getPreferredWidth: function(actor, forHeight, alloc) { +- // See comment about this._background in _init() +- let themeNode = this._background.get_theme_node(); +- + if (this._thumbnails.length == 0) + return; + +- // We don't animate our preferred width, which is always reported according +- // to the actual number of current workspaces, we just animate within that ++ let themeNode = this.actor.get_theme_node(); + + let spacing = this.actor.get_theme_node().get_length('spacing'); + let nWorkspaces = global.screen.n_workspaces; +@@ -1077,28 +1057,26 @@ const ThumbnailsBox = new Lang.Class({ + scale = Math.min(scale, MAX_THUMBNAIL_SCALE); + + let width = Math.round(this._porthole.width * scale); +- [alloc.min_size, alloc.natural_size] = +- themeNode.adjust_preferred_width(width, width); ++ alloc.min_size = width; ++ alloc.natural_size = width; + }, + + _allocate: function(actor, box, flags) { + let rtl = (Clutter.get_default_text_direction () == Clutter.TextDirection.RTL); + +- // See comment about this._background in _init() +- let themeNode = this._background.get_theme_node(); +- let contentBox = themeNode.get_content_box(box); +- + if (this._thumbnails.length == 0) // not visible + return; + ++ let themeNode = this.actor.get_theme_node(); ++ + let portholeWidth = this._porthole.width; + let portholeHeight = this._porthole.height; +- let spacing = this.actor.get_theme_node().get_length('spacing'); ++ let spacing = themeNode.get_length('spacing'); + + // Compute the scale we'll need once everything is updated + let nWorkspaces = global.screen.n_workspaces; + let totalSpacing = (nWorkspaces - 1) * spacing; +- let avail = (contentBox.y2 - contentBox.y1) - totalSpacing; ++ let avail = (box.y2 - box.y1) - totalSpacing; + + let newScale = (avail / nWorkspaces) / portholeHeight; + newScale = Math.min(newScale, MAX_THUMBNAIL_SCALE); +@@ -1127,21 +1105,6 @@ const ThumbnailsBox = new Lang.Class({ + else + slideOffset = thumbnailWidth + themeNode.get_padding(St.Side.RIGHT); + +- let childBox = new Clutter.ActorBox(); +- +- // The background is horizontally restricted to correspond to the current thumbnail size +- // but otherwise covers the entire allocation +- if (rtl) { +- childBox.x1 = box.x1; +- childBox.x2 = box.x2 - ((contentBox.x2 - contentBox.x1) - thumbnailWidth); +- } else { +- childBox.x1 = box.x1 + ((contentBox.x2 - contentBox.x1) - thumbnailWidth); +- childBox.x2 = box.x2; +- } +- childBox.y1 = box.y1; +- childBox.y2 = box.y2; +- this._background.allocate(childBox, flags); +- + let indicatorY1 = this._indicatorY; + let indicatorY2; + // when not animating, the workspace position overrides this._indicatorY +@@ -1153,7 +1116,7 @@ const ThumbnailsBox = new Lang.Class({ + let indicatorLeftFullBorder = indicatorThemeNode.get_padding(St.Side.LEFT) + indicatorThemeNode.get_border_width(St.Side.LEFT); + let indicatorRightFullBorder = indicatorThemeNode.get_padding(St.Side.RIGHT) + indicatorThemeNode.get_border_width(St.Side.RIGHT); + +- let y = contentBox.y1; ++ let y = box.y1; + + if (this._dropPlaceholderPos == -1) { + Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { +@@ -1161,6 +1124,8 @@ const ThumbnailsBox = new Lang.Class({ + })); + } + ++ let childBox = new Clutter.ActorBox(); ++ + for (let i = 0; i < this._thumbnails.length; i++) { + let thumbnail = this._thumbnails[i]; + +@@ -1169,10 +1134,10 @@ const ThumbnailsBox = new Lang.Class({ + + let x1, x2; + if (rtl) { +- x1 = contentBox.x1 + slideOffset * thumbnail.slidePosition; ++ x1 = box.x1 + slideOffset * thumbnail.slidePosition; + x2 = x1 + thumbnailWidth; + } else { +- x1 = contentBox.x2 - thumbnailWidth + slideOffset * thumbnail.slidePosition; ++ x1 = box.x2 - thumbnailWidth + slideOffset * thumbnail.slidePosition; + x2 = x1 + thumbnailWidth; + } + +@@ -1219,11 +1184,11 @@ const ThumbnailsBox = new Lang.Class({ + } + + if (rtl) { +- childBox.x1 = contentBox.x1; +- childBox.x2 = contentBox.x1 + thumbnailWidth; ++ childBox.x1 = box.x1; ++ childBox.x2 = box.x1 + thumbnailWidth; + } else { +- childBox.x1 = contentBox.x2 - thumbnailWidth; +- childBox.x2 = contentBox.x2; ++ childBox.x1 = box.x2 - thumbnailWidth; ++ childBox.x2 = box.x2; + } + childBox.x1 -= indicatorLeftFullBorder; + childBox.x2 += indicatorRightFullBorder; +-- +2.1.0 + + +From a391e6dd3ed64517fefeaf5876fa5c838e1eb58f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 9 Oct 2014 14:07:22 +0200 +Subject: [PATCH 7/8] overviewControls: Fix thinko in SlideLayout + +Controls that slide left are located on the left, so the offset to +align them with the corresponding edge is always 0. It's controls +on the right that need a different offset when the available width +exceeds the child's width. + +https://bugzilla.gnome.org/show_bug.cgi?id=728899 +--- + js/ui/overviewControls.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/js/ui/overviewControls.js b/js/ui/overviewControls.js +index aafb905..1d1c550 100644 +--- a/js/ui/overviewControls.js ++++ b/js/ui/overviewControls.js +@@ -62,7 +62,7 @@ const SlideLayout = new Lang.Class({ + // flags only determine what to do if the allocated box is bigger + // than the actor's box. + let realDirection = getRtlSlideDirection(this._direction, child); +- let alignX = (realDirection == SlideDirection.LEFT) ? (availWidth - natWidth) : 0; ++ let alignX = (realDirection == SlideDirection.RIGHT) ? (availWidth - natWidth) : 0; + + let actorBox = new Clutter.ActorBox(); + actorBox.x1 = alignX; +-- +2.1.0 + + +From ae265eb3f7d1b8c4f480e61bbc8d3a01dbed34db Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 10 Oct 2014 10:58:42 +0200 +Subject: [PATCH 8/8] overviewControls: Really fix x align + +Commit a4475465f1f2 fixed the wrong alignment for the fully visible +control, but regressed the partially slid-out one; take the slideX +factor into account to get the right offset for both cases. +--- + js/ui/overviewControls.js | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/js/ui/overviewControls.js b/js/ui/overviewControls.js +index 1d1c550..4f5bb7f 100644 +--- a/js/ui/overviewControls.js ++++ b/js/ui/overviewControls.js +@@ -62,7 +62,8 @@ const SlideLayout = new Lang.Class({ + // flags only determine what to do if the allocated box is bigger + // than the actor's box. + let realDirection = getRtlSlideDirection(this._direction, child); +- let alignX = (realDirection == SlideDirection.RIGHT) ? (availWidth - natWidth) : 0; ++ let alignX = (realDirection == SlideDirection.LEFT) ? (availWidth - natWidth) ++ : (availWidth - natWidth * this._slideX); + + let actorBox = new Clutter.ActorBox(); + actorBox.x1 = alignX; +-- +2.1.0 + diff --git a/SOURCES/login-banner-fixes.patch b/SOURCES/login-banner-fixes.patch new file mode 100644 index 0000000..55bb72b --- /dev/null +++ b/SOURCES/login-banner-fixes.patch @@ -0,0 +1,1989 @@ +From b0f59711785d43d0d3dd76e09064e36bea516e1b Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 12 Nov 2014 11:27:50 -0500 +Subject: [PATCH 1/5] Revert "loginDialog: make banner message more prominent." + +This reverts commit 1f00c48feb75a8749b80a7cace94f622df825f7c. +--- + data/theme/gnome-shell.css | 13 ++++++++----- + js/gdm/loginDialog.js | 44 ++++++++++++++++++++++---------------------- + 2 files changed, 30 insertions(+), 27 deletions(-) + +diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css +index e12c78d..0db9d1e 100644 +--- a/data/theme/gnome-shell.css ++++ b/data/theme/gnome-shell.css +@@ -2188,65 +2188,68 @@ StScrollBar StButton#vhandle:active { + border-radius: 4px; + background-color: rgba(255,255,255,0.1); + } + .candidate-page-button-box { + height: 2em; + width: 80px; + } + + .vertical .candidate-page-button-box { + padding-top: 0.5em; + } + + .horizontal .candidate-page-button-box { + padding-left: 0.5em; + } + + .candidate-page-button-previous { + border-radius: 4px 0px 0px 4px; + } + + .candidate-page-button-next { + border-radius: 0px 4px 4px 0px; + } + + .candidate-page-button-icon { + icon-size: 1em; + } + + /* Login Dialog */ + .login-dialog-banner-view { +- padding-top: 96px; +- padding-left: 1em; +- height: 14em; +- max-width: 42em; +- min-width: 25em; ++ padding-top: 10em; ++ height: 10em; ++} ++ ++.login-dialog-banner { ++ font-size: 9pt; ++ color: #666666; ++ width: 30em; + } + + .login-dialog-title { + font-size: 14pt; + font-weight: bold; + color: #666666; + padding-bottom: 2em; + } + + .login-dialog { + /* Reset border and background */ + border: none; + background-color: transparent; + } + + .login-dialog-button-box { + spacing: 5px; + } + + .login-dialog-user-list-view { + -st-vfade-offset: 1em; + } + + .login-dialog-user-list { + spacing: 12px; + padding: .2em; + width: 23em; + } + + .login-dialog-user-list-item { +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 8f5044f..aadc5a9 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -407,136 +407,136 @@ const LoginDialog = new Lang.Class({ + this._userManager = AccountsService.UserManager.get_default() + let gdmClient = new Gdm.Client(); + + if (GLib.getenv('GDM_GREETER_TEST') != '1') { + this._greeter = gdmClient.get_greeter_sync(null); + + this._greeter.connect('default-session-name-changed', + Lang.bind(this, this._onDefaultSessionChanged)); + + this._greeter.connect('session-opened', + Lang.bind(this, this._onSessionOpened)); + this._greeter.connect('timed-login-requested', + Lang.bind(this, this._onTimedLoginRequested)); + } + + this._settings = new Gio.Settings({ schema: GdmUtil.LOGIN_SCREEN_SCHEMA }); + + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_TEXT_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.DISABLE_USER_LIST_KEY, + Lang.bind(this, this._updateDisableUserList)); + this._settings.connect('changed::' + GdmUtil.LOGO_KEY, + Lang.bind(this, this._updateLogo)); + + this._textureCache = St.TextureCache.get_default(); + this._textureCache.connect('texture-file-changed', + Lang.bind(this, this._updateLogoTexture)); + +- let outerBox = new St.BoxLayout({ x_align: Clutter.ActorAlign.CENTER, +- y_align: Clutter.ActorAlign.START, +- y_expand: true, +- vertical: true }); +- table.pack(outerBox, 0, 0); +- this._bannerView = new St.ScrollView({ style_class: 'login-dialog-banner-view', +- opacity: 0, +- vscrollbar_policy: Gtk.PolicyType.AUTOMATIC, +- hscrollbar_policy: Gtk.PolicyType.NEVER }); +- outerBox.add_actor(this._bannerView); +- +- let innerBox = new St.BoxLayout({ vertical: true }); +- +- this._bannerView.add_actor(innerBox); +- this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner', +- text: '' }); +- this._bannerLabel.clutter_text.line_wrap = true; +- this._bannerLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; +- innerBox.add_child(this._bannerLabel); +- this._updateBanner(); +- + this._userSelectionBox = new St.BoxLayout({ style_class: 'login-dialog-user-selection-box', + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER, + x_expand: true, + y_expand: true, + vertical: true, + visible: false }); + this._userList = new UserList(); + this._userSelectionBox.add(this._userList.actor, + { expand: true, + x_fill: true, + y_fill: true }); + + this._authPrompt = new AuthPrompt.AuthPrompt(gdmClient, AuthPrompt.AuthPromptMode.UNLOCK_OR_LOG_IN); + this._authPrompt.connect('prompted', Lang.bind(this, this._onPrompted)); + this._authPrompt.connect('reset', Lang.bind(this, this._onReset)); + this._authPrompt.hide(); + this._authPrompt.actor.x_align = Clutter.ActorAlign.CENTER; + this._authPrompt.actor.y_align = Clutter.ActorAlign.CENTER; + this._authPrompt.actor.x_expand = false; + this._authPrompt.actor.y_expand = false; + table.pack(this._authPrompt.actor, 0, 0); + table.set_span(this._authPrompt.actor, 1, 3); + + // translators: this message is shown below the user list on the + // login screen. It can be activated to reveal an entry for + // manually entering the username. + let notListedLabel = new St.Label({ text: _("Not listed?"), + style_class: 'login-dialog-not-listed-label' }); + this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: notListedLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + + this._notListedButton.connect('clicked', Lang.bind(this, this._hideUserListAskForUsernameAndBeginVerification)); + + this._notListedButton.hide(); + + this._userSelectionBox.add(this._notListedButton, + { expand: false, + x_align: St.Align.START, + x_fill: true }); + + this._logoBin = new St.Widget({ style_class: 'login-dialog-logo-bin', + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.END, + x_expand: true, + y_expand: true }); + + table.pack(this._userSelectionBox, 0, 0); + table.set_span(this._userSelectionBox, 1, 3); + ++ let outerBox = new St.BoxLayout({ x_align: Clutter.ActorAlign.CENTER, ++ y_align: Clutter.ActorAlign.CENTER, ++ vertical: true }); ++ table.pack(outerBox, 0, 2); ++ table.set_span(outerBox, 1, 1); ++ this._bannerView = new St.ScrollView({ style_class: 'login-dialog-banner-view', ++ opacity: 0, ++ vscrollbar_policy: Gtk.PolicyType.AUTOMATIC, ++ hscrollbar_policy: Gtk.PolicyType.NEVER }); ++ outerBox.add_actor(this._bannerView); ++ ++ let innerBox = new St.BoxLayout({ vertical: true }); ++ ++ this._bannerView.add_actor(innerBox); ++ this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner', ++ text: '' }); ++ this._bannerLabel.clutter_text.line_wrap = true; ++ this._bannerLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; ++ innerBox.add_child(this._bannerLabel); ++ this._updateBanner(); ++ + table.pack(this._logoBin, 0, 2); + table.set_span(this._logoBin, 1, 1); + this._updateLogo(); + + // We have overlapping widgets to obtain the kind of centering we want. + // We need to make sure the widgets that take input are on top of the + // stack. + this.actor.set_child_above_sibling(this._userSelectionBox, null); + this.actor.set_child_above_sibling(this._authPrompt.actor, null); + + this._userList.connect('activate', + Lang.bind(this, function(userList, item) { + this._onUserListActivated(item); + })); + + + this._sessionMenuButton = new SessionMenuButton(); + this._sessionMenuButton.connect('session-activated', + Lang.bind(this, function(list, sessionId) { + this._greeter.call_select_session_sync (sessionId, null); + })); + this._sessionMenuButton.actor.opacity = 0; + this._sessionMenuButton.actor.show(); + this._authPrompt.addActorToDefaultButtonWell(this._sessionMenuButton.actor); + + this._disableUserList = undefined; + this._userListLoaded = false; + + // If the user list is enabled, it should take key focus; make sure the + // screen shield is initialized first to prevent it from stealing the +@@ -599,107 +599,107 @@ const LoginDialog = new Lang.Class({ + Tweener.addTween(this._bannerView, + { opacity: 255, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad' }); + }, + + _hideBannerView: function() { + Tweener.removeTweens(this._bannerView); + this._bannerView.opacity = 0; + }, + + _updateLogoTexture: function(cache, uri) { + if (this._logoFileUri != uri) + return; + + this._logoBin.destroy_all_children(); + if (this._logoFileUri) + this._logoBin.add_child(this._textureCache.load_uri_async(this._logoFileUri, + -1, _LOGO_ICON_HEIGHT)); + }, + + _updateLogo: function() { + let path = this._settings.get_string(GdmUtil.LOGO_KEY); + + this._logoFileUri = path ? Gio.file_new_for_path(path).get_uri() : null; + this._updateLogoTexture(this._textureCache, this._logoFileUri); + }, + + _onPrompted: function() { + this._sessionMenuButton.updateSensitivity(true); ++ this._fadeInBannerView(); + + if (this._shouldShowSessionMenuButton()) + this._authPrompt.setActorInDefaultButtonWell(this._sessionMenuButton.actor); + this._showPrompt(); + }, + + _onReset: function(authPrompt, beginRequest) { + this._sessionMenuButton.updateSensitivity(true); + this._hideBannerView(); + + this._user = null; + + if (beginRequest == AuthPrompt.BeginRequestType.PROVIDE_USERNAME) { + if (!this._disableUserList) + this._showUserList(); + else + this._hideUserListAskForUsernameAndBeginVerification(); + } else { + this._hideUserListAndBeginVerification(); + } + }, + + _onDefaultSessionChanged: function(client, sessionId) { + this._sessionMenuButton.setActiveSession(sessionId); + }, + + _shouldShowSessionMenuButton: function() { + if (this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.VERIFYING && + this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.VERIFICATION_FAILED) + return false; + + if (this._user && this._user.is_loaded && this._user.is_logged_in()) + return false; + + return true; + }, + + _showPrompt: function() { + if (this._authPrompt.actor.visible) + return; + this._authPrompt.actor.opacity = 0; + this._authPrompt.actor.show(); + Tweener.addTween(this._authPrompt.actor, + { opacity: 255, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad' }); +- this._fadeInBannerView(); + }, + + _showRealmLoginHint: function(realmManager, hint) { + if (!hint) + return; + + hint = hint.replace(/%U/g, 'user'); + hint = hint.replace(/%D/g, 'DOMAIN'); + hint = hint.replace(/%[^UD]/g, ''); + + // Translators: this message is shown below the username entry field + // to clue the user in on how to login to the local network realm + this._authPrompt.setMessage(_("(e.g., user or %s)").format(hint), GdmUtil.MessageType.HINT); + }, + + _askForUsernameAndBeginVerification: function() { + this._authPrompt.setPasswordChar(''); + this._authPrompt.setQuestion(_("Username: ")); + + let realmManager = new Realmd.Manager(); + let realmSignalId = realmManager.connect('login-format-changed', + Lang.bind(this, this._showRealmLoginHint)); + this._showRealmLoginHint(realmManager.loginFormat); + + let nextSignalId = this._authPrompt.connect('next', + Lang.bind(this, function() { + this._authPrompt.disconnect(nextSignalId); + this._authPrompt.updateSensitivity(false); + let answer = this._authPrompt.getAnswer(); + this._user = this._userManager.get_user(answer); +-- +2.1.0 + + +From c5ef28f7ccc96bf6ed57c80fe98cd5fe31f4004d Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 12 Nov 2014 11:29:22 -0500 +Subject: [PATCH 2/5] Revert "loginDialog: display banner message when + disable-user-list=true" + +This reverts commit b94289671424068d973a553fb610398830d25e21. + +Conflicts: + js/gdm/loginDialog.js +--- + data/theme/gnome-shell.css | 10 +++--- + js/gdm/loginDialog.js | 76 +++++++++++++--------------------------------- + 2 files changed, 25 insertions(+), 61 deletions(-) + +diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css +index 0db9d1e..aa82632 100644 +--- a/data/theme/gnome-shell.css ++++ b/data/theme/gnome-shell.css +@@ -2187,69 +2187,67 @@ StScrollBar StButton#vhandle:active { + .candidate-box:hover { + border-radius: 4px; + background-color: rgba(255,255,255,0.1); + } + .candidate-page-button-box { + height: 2em; + width: 80px; + } + + .vertical .candidate-page-button-box { + padding-top: 0.5em; + } + + .horizontal .candidate-page-button-box { + padding-left: 0.5em; + } + + .candidate-page-button-previous { + border-radius: 4px 0px 0px 4px; + } + + .candidate-page-button-next { + border-radius: 0px 4px 4px 0px; + } + + .candidate-page-button-icon { + icon-size: 1em; + } + + /* Login Dialog */ +-.login-dialog-banner-view { +- padding-top: 10em; +- height: 10em; +-} + + .login-dialog-banner { +- font-size: 9pt; ++ font-size: 10pt; ++ font-weight: bold; ++ text-align: center; + color: #666666; +- width: 30em; ++ padding-bottom: 1em; + } + + .login-dialog-title { + font-size: 14pt; + font-weight: bold; + color: #666666; + padding-bottom: 2em; + } + + .login-dialog { + /* Reset border and background */ + border: none; + background-color: transparent; + } + + .login-dialog-button-box { + spacing: 5px; + } + + .login-dialog-user-list-view { + -st-vfade-offset: 1em; + } + + .login-dialog-user-list { + spacing: 12px; + padding: .2em; + width: 23em; + } + + .login-dialog-user-list-item { +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index aadc5a9..f35921c 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -1,61 +1,60 @@ + // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + /* + * Copyright 2011 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + + const AccountsService = imports.gi.AccountsService; + const Atk = imports.gi.Atk; + const Clutter = imports.gi.Clutter; + const Gdm = imports.gi.Gdm; + const Gio = imports.gi.Gio; + const GLib = imports.gi.GLib; + const Gtk = imports.gi.Gtk; + const Lang = imports.lang; + const Mainloop = imports.mainloop; + const Meta = imports.gi.Meta; +-const Pango = imports.gi.Pango; + const Shell = imports.gi.Shell; + const Signals = imports.signals; + const St = imports.gi.St; + + const AuthPrompt = imports.gdm.authPrompt; + const Batch = imports.gdm.batch; + const BoxPointer = imports.ui.boxpointer; + const CtrlAltTab = imports.ui.ctrlAltTab; + const GdmUtil = imports.gdm.util; + const Layout = imports.ui.layout; + const Main = imports.ui.main; + const PopupMenu = imports.ui.popupMenu; + const Realmd = imports.gdm.realmd; + const Tweener = imports.ui.tweener; + const UserMenu = imports.ui.userMenu; + const UserWidget = imports.ui.userWidget; + + const _FADE_ANIMATION_TIME = 0.25; + const _SCROLL_ANIMATION_TIME = 0.5; + const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0; + const _LOGO_ICON_HEIGHT = 48; + + let _loginDialog = null; + + const UserListItem = new Lang.Class({ + Name: 'UserListItem', + + _init: function(user) { + this.user = user; + this._userChangedId = this.user.connect('changed', +@@ -367,185 +366,166 @@ const SessionMenuButton = new Lang.Class({ + ids.sort(); + + if (ids.length <= 1) { + this._button.hide(); + return; + } + + for (let i = 0; i < ids.length; i++) { + let [sessionName, sessionDescription] = Gdm.get_session_name_and_description(ids[i]); + + let id = ids[i]; + let item = new PopupMenu.PopupMenuItem(sessionName); + this._menu.addMenuItem(item); + this._items[id] = item; + + if (!this._activeSessionId) + this.setActiveSession(id); + + item.connect('activate', Lang.bind(this, function() { + this.setActiveSession(id); + })); + } + } + }); + Signals.addSignalMethods(SessionMenuButton.prototype); + + const LoginDialog = new Lang.Class({ + Name: 'LoginDialog', + + _init: function(parentActor) { +- let table = new Clutter.TableLayout(); + this.actor = new St.Widget({ accessible_role: Atk.Role.WINDOW, +- layout_manager: table, ++ layout_manager: new Clutter.BinLayout(), + style_class: 'login-dialog', + visible: false }); + + this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true })); + this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); + parentActor.add_child(this.actor); + + this._userManager = AccountsService.UserManager.get_default() + let gdmClient = new Gdm.Client(); + + if (GLib.getenv('GDM_GREETER_TEST') != '1') { + this._greeter = gdmClient.get_greeter_sync(null); + + this._greeter.connect('default-session-name-changed', + Lang.bind(this, this._onDefaultSessionChanged)); + + this._greeter.connect('session-opened', + Lang.bind(this, this._onSessionOpened)); + this._greeter.connect('timed-login-requested', + Lang.bind(this, this._onTimedLoginRequested)); + } + + this._settings = new Gio.Settings({ schema: GdmUtil.LOGIN_SCREEN_SCHEMA }); + + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_TEXT_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.DISABLE_USER_LIST_KEY, + Lang.bind(this, this._updateDisableUserList)); + this._settings.connect('changed::' + GdmUtil.LOGO_KEY, + Lang.bind(this, this._updateLogo)); + + this._textureCache = St.TextureCache.get_default(); + this._textureCache.connect('texture-file-changed', + Lang.bind(this, this._updateLogoTexture)); + + this._userSelectionBox = new St.BoxLayout({ style_class: 'login-dialog-user-selection-box', + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER, + x_expand: true, + y_expand: true, + vertical: true, + visible: false }); ++ this.actor.add_child(this._userSelectionBox); ++ ++ this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner', ++ text: '' }); ++ this._userSelectionBox.add(this._bannerLabel); ++ this._updateBanner(); ++ + this._userList = new UserList(); + this._userSelectionBox.add(this._userList.actor, + { expand: true, + x_fill: true, + y_fill: true }); + + this._authPrompt = new AuthPrompt.AuthPrompt(gdmClient, AuthPrompt.AuthPromptMode.UNLOCK_OR_LOG_IN); + this._authPrompt.connect('prompted', Lang.bind(this, this._onPrompted)); + this._authPrompt.connect('reset', Lang.bind(this, this._onReset)); + this._authPrompt.hide(); +- this._authPrompt.actor.x_align = Clutter.ActorAlign.CENTER; +- this._authPrompt.actor.y_align = Clutter.ActorAlign.CENTER; +- this._authPrompt.actor.x_expand = false; +- this._authPrompt.actor.y_expand = false; +- table.pack(this._authPrompt.actor, 0, 0); +- table.set_span(this._authPrompt.actor, 1, 3); ++ this.actor.add_child(this._authPrompt.actor); + + // translators: this message is shown below the user list on the + // login screen. It can be activated to reveal an entry for + // manually entering the username. + let notListedLabel = new St.Label({ text: _("Not listed?"), + style_class: 'login-dialog-not-listed-label' }); + this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: notListedLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + + this._notListedButton.connect('clicked', Lang.bind(this, this._hideUserListAskForUsernameAndBeginVerification)); + + this._notListedButton.hide(); + + this._userSelectionBox.add(this._notListedButton, + { expand: false, + x_align: St.Align.START, + x_fill: true }); + + this._logoBin = new St.Widget({ style_class: 'login-dialog-logo-bin', + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.END, + x_expand: true, + y_expand: true }); +- +- table.pack(this._userSelectionBox, 0, 0); +- table.set_span(this._userSelectionBox, 1, 3); +- +- let outerBox = new St.BoxLayout({ x_align: Clutter.ActorAlign.CENTER, +- y_align: Clutter.ActorAlign.CENTER, +- vertical: true }); +- table.pack(outerBox, 0, 2); +- table.set_span(outerBox, 1, 1); +- this._bannerView = new St.ScrollView({ style_class: 'login-dialog-banner-view', +- opacity: 0, +- vscrollbar_policy: Gtk.PolicyType.AUTOMATIC, +- hscrollbar_policy: Gtk.PolicyType.NEVER }); +- outerBox.add_actor(this._bannerView); +- +- let innerBox = new St.BoxLayout({ vertical: true }); +- +- this._bannerView.add_actor(innerBox); +- this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner', +- text: '' }); +- this._bannerLabel.clutter_text.line_wrap = true; +- this._bannerLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; +- innerBox.add_child(this._bannerLabel); +- this._updateBanner(); +- +- table.pack(this._logoBin, 0, 2); +- table.set_span(this._logoBin, 1, 1); ++ this.actor.add_child(this._logoBin); + this._updateLogo(); + +- // We have overlapping widgets to obtain the kind of centering we want. +- // We need to make sure the widgets that take input are on top of the +- // stack. +- this.actor.set_child_above_sibling(this._userSelectionBox, null); +- this.actor.set_child_above_sibling(this._authPrompt.actor, null); ++ if (!this._userManager.is_loaded) ++ this._userManagerLoadedId = this._userManager.connect('notify::is-loaded', ++ Lang.bind(this, function() { ++ if (this._userManager.is_loaded) { ++ this._loadUserList(); ++ this._userManager.disconnect(this._userManagerLoadedId); ++ this._userManagerLoadedId = 0; ++ } ++ })); ++ else ++ this._loadUserList(); + + this._userList.connect('activate', + Lang.bind(this, function(userList, item) { + this._onUserListActivated(item); + })); + + + this._sessionMenuButton = new SessionMenuButton(); + this._sessionMenuButton.connect('session-activated', + Lang.bind(this, function(list, sessionId) { + this._greeter.call_select_session_sync (sessionId, null); + })); + this._sessionMenuButton.actor.opacity = 0; + this._sessionMenuButton.actor.show(); + this._authPrompt.addActorToDefaultButtonWell(this._sessionMenuButton.actor); + + this._disableUserList = undefined; + this._userListLoaded = false; + + // If the user list is enabled, it should take key focus; make sure the + // screen shield is initialized first to prevent it from stealing the + // focus later + Main.layoutManager.connect('startup-complete', + Lang.bind(this, this._updateDisableUserList)); + }, + + _ensureUserListLoaded: function() { + if (!this._userManager.is_loaded) + this._userManagerLoadedId = this._userManager.connect('notify::is-loaded', + Lang.bind(this, function() { +@@ -568,101 +548,87 @@ const LoginDialog = new Lang.Class({ + if (this._authPrompt.verificationStatus == AuthPrompt.AuthPromptStatus.NOT_VERIFYING) + this._authPrompt.reset(); + } + }, + + _updateCancelButton: function() { + let cancelVisible; + + // Hide the cancel button if the user list is disabled and we're asking for + // a username + if (this._authPrompt.verificationStatus == AuthPrompt.AuthPromptStatus.NOT_VERIFYING && this._disableUserList) + cancelVisible = false; + else + cancelVisible = true; + + this._authPrompt.cancelButton.visible = cancelVisible; + }, + + _updateBanner: function() { + let enabled = this._settings.get_boolean(GdmUtil.BANNER_MESSAGE_KEY); + let text = this._settings.get_string(GdmUtil.BANNER_MESSAGE_TEXT_KEY); + + if (enabled && text) { + this._bannerLabel.set_text(text); + this._bannerLabel.show(); + } else { + this._bannerLabel.hide(); + } + }, + +- _fadeInBannerView: function() { +- Tweener.addTween(this._bannerView, +- { opacity: 255, +- time: _FADE_ANIMATION_TIME, +- transition: 'easeOutQuad' }); +- }, +- +- _hideBannerView: function() { +- Tweener.removeTweens(this._bannerView); +- this._bannerView.opacity = 0; +- }, +- + _updateLogoTexture: function(cache, uri) { + if (this._logoFileUri != uri) + return; + + this._logoBin.destroy_all_children(); + if (this._logoFileUri) + this._logoBin.add_child(this._textureCache.load_uri_async(this._logoFileUri, + -1, _LOGO_ICON_HEIGHT)); + }, + + _updateLogo: function() { + let path = this._settings.get_string(GdmUtil.LOGO_KEY); + + this._logoFileUri = path ? Gio.file_new_for_path(path).get_uri() : null; + this._updateLogoTexture(this._textureCache, this._logoFileUri); + }, + + _onPrompted: function() { + this._sessionMenuButton.updateSensitivity(true); +- this._fadeInBannerView(); + + if (this._shouldShowSessionMenuButton()) + this._authPrompt.setActorInDefaultButtonWell(this._sessionMenuButton.actor); + this._showPrompt(); + }, + + _onReset: function(authPrompt, beginRequest) { + this._sessionMenuButton.updateSensitivity(true); +- this._hideBannerView(); + + this._user = null; + + if (beginRequest == AuthPrompt.BeginRequestType.PROVIDE_USERNAME) { + if (!this._disableUserList) + this._showUserList(); + else + this._hideUserListAskForUsernameAndBeginVerification(); + } else { + this._hideUserListAndBeginVerification(); + } + }, + + _onDefaultSessionChanged: function(client, sessionId) { + this._sessionMenuButton.setActiveSession(sessionId); + }, + + _shouldShowSessionMenuButton: function() { + if (this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.VERIFYING && + this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.VERIFICATION_FAILED) + return false; + + if (this._user && this._user.is_loaded && this._user.is_logged_in()) + return false; + + return true; + }, + + _showPrompt: function() { + if (this._authPrompt.actor.visible) +-- +2.1.0 + + +From 4535550f8780b244f3e79a048c61eef3b2ed8cc8 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 10 Nov 2014 14:36:07 -0500 +Subject: [PATCH 3/5] loginDialog: allocate children manually + +The login screen is pretty custom full screen container and the standard +layout managers aren't really a good fit for the kind of layout that's +happening. This will be even more problematic with upcoming changes +to login banners, so we need to switch techniques. + +This commit moves login dialog over to using a custom allocate handler +that has specific domain knowledge of the parts of the login screen +and where they go. +--- + js/gdm/loginDialog.js | 94 ++++++++++++++++++++++++++++++++++++++++++++++----- + 1 file changed, 85 insertions(+), 9 deletions(-) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index f35921c..4835199 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -366,192 +366,268 @@ const SessionMenuButton = new Lang.Class({ + ids.sort(); + + if (ids.length <= 1) { + this._button.hide(); + return; + } + + for (let i = 0; i < ids.length; i++) { + let [sessionName, sessionDescription] = Gdm.get_session_name_and_description(ids[i]); + + let id = ids[i]; + let item = new PopupMenu.PopupMenuItem(sessionName); + this._menu.addMenuItem(item); + this._items[id] = item; + + if (!this._activeSessionId) + this.setActiveSession(id); + + item.connect('activate', Lang.bind(this, function() { + this.setActiveSession(id); + })); + } + } + }); + Signals.addSignalMethods(SessionMenuButton.prototype); + + const LoginDialog = new Lang.Class({ + Name: 'LoginDialog', + + _init: function(parentActor) { +- this.actor = new St.Widget({ accessible_role: Atk.Role.WINDOW, +- layout_manager: new Clutter.BinLayout(), +- style_class: 'login-dialog', +- visible: false }); ++ this.actor = new Shell.GenericContainer({ style_class: 'login-dialog', ++ visible: false }); ++ this.actor.get_accessible().set_role(Atk.Role.WINDOW); + + this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true })); ++ this.actor.connect('allocate', Lang.bind(this, this._onAllocate)); + this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); + parentActor.add_child(this.actor); + + this._userManager = AccountsService.UserManager.get_default() + let gdmClient = new Gdm.Client(); + + if (GLib.getenv('GDM_GREETER_TEST') != '1') { + this._greeter = gdmClient.get_greeter_sync(null); + + this._greeter.connect('default-session-name-changed', + Lang.bind(this, this._onDefaultSessionChanged)); + + this._greeter.connect('session-opened', + Lang.bind(this, this._onSessionOpened)); + this._greeter.connect('timed-login-requested', + Lang.bind(this, this._onTimedLoginRequested)); + } + + this._settings = new Gio.Settings({ schema: GdmUtil.LOGIN_SCREEN_SCHEMA }); + + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_TEXT_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.DISABLE_USER_LIST_KEY, + Lang.bind(this, this._updateDisableUserList)); + this._settings.connect('changed::' + GdmUtil.LOGO_KEY, + Lang.bind(this, this._updateLogo)); + + this._textureCache = St.TextureCache.get_default(); + this._textureCache.connect('texture-file-changed', + Lang.bind(this, this._updateLogoTexture)); + + this._userSelectionBox = new St.BoxLayout({ style_class: 'login-dialog-user-selection-box', + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER, +- x_expand: true, +- y_expand: true, + vertical: true, + visible: false }); + this.actor.add_child(this._userSelectionBox); + + this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner', + text: '' }); + this._userSelectionBox.add(this._bannerLabel); + this._updateBanner(); + + this._userList = new UserList(); + this._userSelectionBox.add(this._userList.actor, + { expand: true, + x_fill: true, + y_fill: true }); + + this._authPrompt = new AuthPrompt.AuthPrompt(gdmClient, AuthPrompt.AuthPromptMode.UNLOCK_OR_LOG_IN); + this._authPrompt.connect('prompted', Lang.bind(this, this._onPrompted)); + this._authPrompt.connect('reset', Lang.bind(this, this._onReset)); + this._authPrompt.hide(); + this.actor.add_child(this._authPrompt.actor); + + // translators: this message is shown below the user list on the + // login screen. It can be activated to reveal an entry for + // manually entering the username. + let notListedLabel = new St.Label({ text: _("Not listed?"), + style_class: 'login-dialog-not-listed-label' }); + this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: notListedLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + + this._notListedButton.connect('clicked', Lang.bind(this, this._hideUserListAskForUsernameAndBeginVerification)); + + this._notListedButton.hide(); + + this._userSelectionBox.add(this._notListedButton, + { expand: false, + x_align: St.Align.START, + x_fill: true }); + + this._logoBin = new St.Widget({ style_class: 'login-dialog-logo-bin', + x_align: Clutter.ActorAlign.CENTER, +- y_align: Clutter.ActorAlign.END, +- x_expand: true, +- y_expand: true }); ++ y_align: Clutter.ActorAlign.END }); + this.actor.add_child(this._logoBin); + this._updateLogo(); + + if (!this._userManager.is_loaded) + this._userManagerLoadedId = this._userManager.connect('notify::is-loaded', + Lang.bind(this, function() { + if (this._userManager.is_loaded) { + this._loadUserList(); + this._userManager.disconnect(this._userManagerLoadedId); + this._userManagerLoadedId = 0; + } + })); + else + this._loadUserList(); + + this._userList.connect('activate', + Lang.bind(this, function(userList, item) { + this._onUserListActivated(item); + })); + + + this._sessionMenuButton = new SessionMenuButton(); + this._sessionMenuButton.connect('session-activated', + Lang.bind(this, function(list, sessionId) { + this._greeter.call_select_session_sync (sessionId, null); + })); + this._sessionMenuButton.actor.opacity = 0; + this._sessionMenuButton.actor.show(); + this._authPrompt.addActorToDefaultButtonWell(this._sessionMenuButton.actor); + + this._disableUserList = undefined; + this._userListLoaded = false; + + // If the user list is enabled, it should take key focus; make sure the + // screen shield is initialized first to prevent it from stealing the + // focus later + Main.layoutManager.connect('startup-complete', + Lang.bind(this, this._updateDisableUserList)); + }, + ++ _getLogoBinAllocation: function (dialogBox) { ++ let actorBox = new Clutter.ActorBox(); ++ ++ let [minWidth, minHeight, natWidth, natHeight] = this._logoBin.get_preferred_size(); ++ let centerX = dialogBox.x1 + (dialogBox.x2 - dialogBox.x1) / 2; ++ ++ actorBox.x1 = centerX - natWidth / 2; ++ actorBox.y1 = dialogBox.y2 - natHeight; ++ actorBox.x2 = actorBox.x1 + natWidth; ++ actorBox.y2 = actorBox.y1 + natHeight; ++ ++ return actorBox; ++ }, ++ ++ _getCenterActorAllocation: function (dialogBox, actor) { ++ let actorBox = new Clutter.ActorBox(); ++ ++ let [minWidth, minHeight, natWidth, natHeight] = actor.get_preferred_size(); ++ let centerX = dialogBox.x1 + (dialogBox.x2 - dialogBox.x1) / 2; ++ let centerY = dialogBox.y1 + (dialogBox.y2 - dialogBox.y1) / 2; ++ ++ actorBox.x1 = centerX - natWidth / 2; ++ actorBox.y1 = centerY - natHeight / 2; ++ actorBox.x2 = actorBox.x1 + natWidth; ++ actorBox.y2 = actorBox.y1 + natHeight; ++ ++ return actorBox; ++ }, ++ ++ _onAllocate: function (actor, dialogBox, flags) { ++ let dialogHeight = dialogBox.y2 - dialogBox.y1; ++ ++ // First find out what space the children require ++ let authPromptAllocation = null; ++ if (this._authPrompt.actor.visible) ++ authPromptAllocation = this._getCenterActorAllocation(dialogBox, this._authPrompt.actor); ++ ++ let userSelectionAllocation = null; ++ let userSelectionHeight = 0; ++ if (this._userSelectionBox.visible) { ++ userSelectionAllocation = this._getCenterActorAllocation(dialogBox, this._userSelectionBox); ++ userSelectionHeight = userSelectionAllocation.y2 - userSelectionAllocation.y1; ++ } ++ ++ let logoAllocation = null; ++ let logoHeight = 0; ++ if (this._logoBin.visible) { ++ logoAllocation = this._getLogoBinAllocation(dialogBox); ++ logoHeight = logoAllocation.y2 - logoAllocation.y1; ++ } ++ ++ // Then figure out what extra space we can hand out ++ let leftOverYSpace = dialogHeight - userSelectionHeight - logoHeight; ++ if (leftOverYSpace > 0) { ++ if (userSelectionAllocation) { ++ let topExpansion = leftOverYSpace / 2; ++ ++ // Don't ever expand more than we have space for ++ if (userSelectionAllocation.y1 - topExpansion < 0) ++ topExpansion = userSelectionAllocation.y1; ++ ++ // Always expand the bottom the same as the top since it's centered ++ let bottomExpansion = topExpansion; ++ ++ userSelectionAllocation.y1 -= topExpansion; ++ userSelectionAllocation.y2 += bottomExpansion; ++ } ++ } ++ ++ // Finally hand out the allocations ++ if (authPromptAllocation) ++ this._authPrompt.actor.allocate(authPromptAllocation, flags); ++ ++ if (userSelectionAllocation) ++ this._userSelectionBox.allocate(userSelectionAllocation, flags); ++ ++ if (logoAllocation) ++ this._logoBin.allocate(logoAllocation, flags); ++ }, ++ + _ensureUserListLoaded: function() { + if (!this._userManager.is_loaded) + this._userManagerLoadedId = this._userManager.connect('notify::is-loaded', + Lang.bind(this, function() { + if (this._userManager.is_loaded) { + this._loadUserList(); + this._userManager.disconnect(this._userManagerLoadedId); + this._userManagerLoadedId = 0; + } + })); + else + GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, this._loadUserList)); + }, + + _updateDisableUserList: function() { + let disableUserList = this._settings.get_boolean(GdmUtil.DISABLE_USER_LIST_KEY); + + if (disableUserList != this._disableUserList) { + this._disableUserList = disableUserList; + + if (this._authPrompt.verificationStatus == AuthPrompt.AuthPromptStatus.NOT_VERIFYING) + this._authPrompt.reset(); + } + }, + + _updateCancelButton: function() { + let cancelVisible; + + // Hide the cancel button if the user list is disabled and we're asking for + // a username +-- +2.1.0 + + +From f3b8445905e1b34140b5e903a81a27650d68230b Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 11 Nov 2014 09:11:01 -0500 +Subject: [PATCH 4/5] loginDialog: display banner message when + disable-user-list=true + +The login screen supports showing a banner message which admins +can use to mention login rules or disclaimers. + +This message only shows up currently if the user list is enabled. +Most people who want to show a banner message also want to disable +the user list. + +This commit moves the banner message to display when the user is +prompted for login credentials instead of when showing the user +list. It also adds a scrollbar if the message is too long. +--- + data/theme/gnome-shell.css | 8 ++-- + js/gdm/loginDialog.js | 105 +++++++++++++++++++++++++++++++++++++++------ + 2 files changed, 95 insertions(+), 18 deletions(-) + +diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css +index aa82632..90eb64f 100644 +--- a/data/theme/gnome-shell.css ++++ b/data/theme/gnome-shell.css +@@ -2187,67 +2187,67 @@ StScrollBar StButton#vhandle:active { + .candidate-box:hover { + border-radius: 4px; + background-color: rgba(255,255,255,0.1); + } + .candidate-page-button-box { + height: 2em; + width: 80px; + } + + .vertical .candidate-page-button-box { + padding-top: 0.5em; + } + + .horizontal .candidate-page-button-box { + padding-left: 0.5em; + } + + .candidate-page-button-previous { + border-radius: 4px 0px 0px 4px; + } + + .candidate-page-button-next { + border-radius: 0px 4px 4px 0px; + } + + .candidate-page-button-icon { + icon-size: 1em; + } + + /* Login Dialog */ ++.login-dialog-banner-view { ++ padding-top: 24px; ++ max-width: 23em; ++} + + .login-dialog-banner { +- font-size: 10pt; +- font-weight: bold; +- text-align: center; + color: #666666; +- padding-bottom: 1em; + } + + .login-dialog-title { + font-size: 14pt; + font-weight: bold; + color: #666666; + padding-bottom: 2em; + } + + .login-dialog { + /* Reset border and background */ + border: none; + background-color: transparent; + } + + .login-dialog-button-box { + spacing: 5px; + } + + .login-dialog-user-list-view { + -st-vfade-offset: 1em; + } + + .login-dialog-user-list { + spacing: 12px; + padding: .2em; + width: 23em; + } + + .login-dialog-user-list-item { +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 4835199..348798c 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -1,60 +1,61 @@ + // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + /* + * Copyright 2011 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + + const AccountsService = imports.gi.AccountsService; + const Atk = imports.gi.Atk; + const Clutter = imports.gi.Clutter; + const Gdm = imports.gi.Gdm; + const Gio = imports.gi.Gio; + const GLib = imports.gi.GLib; + const Gtk = imports.gi.Gtk; + const Lang = imports.lang; + const Mainloop = imports.mainloop; + const Meta = imports.gi.Meta; ++const Pango = imports.gi.Pango; + const Shell = imports.gi.Shell; + const Signals = imports.signals; + const St = imports.gi.St; + + const AuthPrompt = imports.gdm.authPrompt; + const Batch = imports.gdm.batch; + const BoxPointer = imports.ui.boxpointer; + const CtrlAltTab = imports.ui.ctrlAltTab; + const GdmUtil = imports.gdm.util; + const Layout = imports.ui.layout; + const Main = imports.ui.main; + const PopupMenu = imports.ui.popupMenu; + const Realmd = imports.gdm.realmd; + const Tweener = imports.ui.tweener; + const UserMenu = imports.ui.userMenu; + const UserWidget = imports.ui.userWidget; + + const _FADE_ANIMATION_TIME = 0.25; + const _SCROLL_ANIMATION_TIME = 0.5; + const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0; + const _LOGO_ICON_HEIGHT = 48; + + let _loginDialog = null; + + const UserListItem = new Lang.Class({ + Name: 'UserListItem', + + _init: function(user) { + this.user = user; + this._userChangedId = this.user.connect('changed', +@@ -412,272 +413,346 @@ const LoginDialog = new Lang.Class({ + Lang.bind(this, this._onDefaultSessionChanged)); + + this._greeter.connect('session-opened', + Lang.bind(this, this._onSessionOpened)); + this._greeter.connect('timed-login-requested', + Lang.bind(this, this._onTimedLoginRequested)); + } + + this._settings = new Gio.Settings({ schema: GdmUtil.LOGIN_SCREEN_SCHEMA }); + + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_TEXT_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.DISABLE_USER_LIST_KEY, + Lang.bind(this, this._updateDisableUserList)); + this._settings.connect('changed::' + GdmUtil.LOGO_KEY, + Lang.bind(this, this._updateLogo)); + + this._textureCache = St.TextureCache.get_default(); + this._textureCache.connect('texture-file-changed', + Lang.bind(this, this._updateLogoTexture)); + + this._userSelectionBox = new St.BoxLayout({ style_class: 'login-dialog-user-selection-box', + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER, + vertical: true, + visible: false }); + this.actor.add_child(this._userSelectionBox); + +- this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner', +- text: '' }); +- this._userSelectionBox.add(this._bannerLabel); +- this._updateBanner(); +- + this._userList = new UserList(); + this._userSelectionBox.add(this._userList.actor, + { expand: true, + x_fill: true, + y_fill: true }); + + this._authPrompt = new AuthPrompt.AuthPrompt(gdmClient, AuthPrompt.AuthPromptMode.UNLOCK_OR_LOG_IN); + this._authPrompt.connect('prompted', Lang.bind(this, this._onPrompted)); + this._authPrompt.connect('reset', Lang.bind(this, this._onReset)); + this._authPrompt.hide(); + this.actor.add_child(this._authPrompt.actor); + + // translators: this message is shown below the user list on the + // login screen. It can be activated to reveal an entry for + // manually entering the username. + let notListedLabel = new St.Label({ text: _("Not listed?"), + style_class: 'login-dialog-not-listed-label' }); + this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: notListedLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + + this._notListedButton.connect('clicked', Lang.bind(this, this._hideUserListAskForUsernameAndBeginVerification)); + + this._notListedButton.hide(); + + this._userSelectionBox.add(this._notListedButton, + { expand: false, + x_align: St.Align.START, + x_fill: true }); + ++ this._bannerView = new St.ScrollView({ style_class: 'login-dialog-banner-view', ++ opacity: 0, ++ vscrollbar_policy: Gtk.PolicyType.AUTOMATIC, ++ hscrollbar_policy: Gtk.PolicyType.NEVER }); ++ this.actor.add_child(this._bannerView); ++ ++ let bannerBox = new St.BoxLayout({ vertical: true }); ++ ++ this._bannerView.add_actor(bannerBox); ++ this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner', ++ text: '' }); ++ this._bannerLabel.clutter_text.line_wrap = true; ++ this._bannerLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; ++ bannerBox.add_child(this._bannerLabel); ++ this._updateBanner(); ++ + this._logoBin = new St.Widget({ style_class: 'login-dialog-logo-bin', + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.END }); + this.actor.add_child(this._logoBin); + this._updateLogo(); + + if (!this._userManager.is_loaded) + this._userManagerLoadedId = this._userManager.connect('notify::is-loaded', + Lang.bind(this, function() { + if (this._userManager.is_loaded) { + this._loadUserList(); + this._userManager.disconnect(this._userManagerLoadedId); + this._userManagerLoadedId = 0; + } + })); + else + this._loadUserList(); + + this._userList.connect('activate', + Lang.bind(this, function(userList, item) { + this._onUserListActivated(item); + })); + + + this._sessionMenuButton = new SessionMenuButton(); + this._sessionMenuButton.connect('session-activated', + Lang.bind(this, function(list, sessionId) { + this._greeter.call_select_session_sync (sessionId, null); + })); + this._sessionMenuButton.actor.opacity = 0; + this._sessionMenuButton.actor.show(); + this._authPrompt.addActorToDefaultButtonWell(this._sessionMenuButton.actor); + + this._disableUserList = undefined; + this._userListLoaded = false; + + // If the user list is enabled, it should take key focus; make sure the + // screen shield is initialized first to prevent it from stealing the + // focus later + Main.layoutManager.connect('startup-complete', + Lang.bind(this, this._updateDisableUserList)); + }, + ++ _getBannerAllocation: function (dialogBox) { ++ let actorBox = new Clutter.ActorBox(); ++ ++ let [minWidth, minHeight, natWidth, natHeight] = this._bannerView.get_preferred_size(); ++ let centerX = dialogBox.x1 + (dialogBox.x2 - dialogBox.x1) / 2; ++ ++ actorBox.x1 = centerX - natWidth / 2; ++ actorBox.y1 = dialogBox.y1 + Main.layoutManager.panelBox.height; ++ actorBox.x2 = actorBox.x1 + natWidth; ++ actorBox.y2 = actorBox.y1 + natHeight; ++ ++ return actorBox; ++ }, ++ + _getLogoBinAllocation: function (dialogBox) { + let actorBox = new Clutter.ActorBox(); + + let [minWidth, minHeight, natWidth, natHeight] = this._logoBin.get_preferred_size(); + let centerX = dialogBox.x1 + (dialogBox.x2 - dialogBox.x1) / 2; + + actorBox.x1 = centerX - natWidth / 2; + actorBox.y1 = dialogBox.y2 - natHeight; + actorBox.x2 = actorBox.x1 + natWidth; + actorBox.y2 = actorBox.y1 + natHeight; + + return actorBox; + }, + + _getCenterActorAllocation: function (dialogBox, actor) { + let actorBox = new Clutter.ActorBox(); + + let [minWidth, minHeight, natWidth, natHeight] = actor.get_preferred_size(); + let centerX = dialogBox.x1 + (dialogBox.x2 - dialogBox.x1) / 2; + let centerY = dialogBox.y1 + (dialogBox.y2 - dialogBox.y1) / 2; + + actorBox.x1 = centerX - natWidth / 2; + actorBox.y1 = centerY - natHeight / 2; + actorBox.x2 = actorBox.x1 + natWidth; + actorBox.y2 = actorBox.y1 + natHeight; + + return actorBox; + }, + + _onAllocate: function (actor, dialogBox, flags) { + let dialogHeight = dialogBox.y2 - dialogBox.y1; + + // First find out what space the children require ++ let bannerAllocation = null; ++ let bannerHeight = 0; ++ let bannerWidth = 0; ++ if (this._bannerView.visible) { ++ bannerAllocation = this._getBannerAllocation(dialogBox, this._bannerView); ++ bannerHeight = bannerAllocation.y2 - bannerAllocation.y1; ++ bannerWidth = bannerAllocation.x2 - bannerAllocation.x1; ++ } ++ + let authPromptAllocation = null; +- if (this._authPrompt.actor.visible) ++ let authPromptHeight = 0; ++ if (this._authPrompt.actor.visible) { + authPromptAllocation = this._getCenterActorAllocation(dialogBox, this._authPrompt.actor); ++ authPromptHeight = authPromptAllocation.y2 - authPromptAllocation.y1; ++ } + + let userSelectionAllocation = null; + let userSelectionHeight = 0; + if (this._userSelectionBox.visible) { + userSelectionAllocation = this._getCenterActorAllocation(dialogBox, this._userSelectionBox); + userSelectionHeight = userSelectionAllocation.y2 - userSelectionAllocation.y1; + } + + let logoAllocation = null; + let logoHeight = 0; + if (this._logoBin.visible) { + logoAllocation = this._getLogoBinAllocation(dialogBox); + logoHeight = logoAllocation.y2 - logoAllocation.y1; + } + + // Then figure out what extra space we can hand out +- let leftOverYSpace = dialogHeight - userSelectionHeight - logoHeight; +- if (leftOverYSpace > 0) { +- if (userSelectionAllocation) { +- let topExpansion = leftOverYSpace / 2; ++ if (bannerAllocation) { ++ let leftOverYSpace = dialogHeight - bannerHeight - authPromptHeight - logoHeight; ++ ++ if (leftOverYSpace > 0) { ++ // First figure out how much left over space is up top ++ let leftOverTopSpace = leftOverYSpace / 2; + +- // Don't ever expand more than we have space for +- if (userSelectionAllocation.y1 - topExpansion < 0) +- topExpansion = userSelectionAllocation.y1; ++ // Then, shift the banner into the middle of that extra space ++ let yShift = leftOverTopSpace / 2; ++ ++ bannerAllocation.y1 += yShift; ++ bannerAllocation.y2 += yShift; ++ } else { ++ // recompute banner height to be constrained if there's no room for its ++ // requested height ++ ++ // First figure out how much space there is without the banner ++ leftOverYSpace += bannerHeight; ++ ++ // Then figure out how much of that space is up top ++ let availableTopSpace = leftOverYSpace / 2; ++ ++ // Then give all of that space to the banner ++ bannerAllocation.y2 = bannerAllocation.y1 + availableTopSpace; ++ } ++ } else if (userSelectionAllocation) { ++ // Grow the user list to fill the space ++ let leftOverYSpace = dialogHeight - userSelectionHeight - logoHeight; + +- // Always expand the bottom the same as the top since it's centered ++ if (leftOverYSpace > 0) { ++ let topExpansion = leftOverYSpace / 2; + let bottomExpansion = topExpansion; + + userSelectionAllocation.y1 -= topExpansion; + userSelectionAllocation.y2 += bottomExpansion; + } + } + + // Finally hand out the allocations ++ if (bannerAllocation) { ++ this._bannerView.allocate(bannerAllocation, flags); ++ } ++ + if (authPromptAllocation) + this._authPrompt.actor.allocate(authPromptAllocation, flags); + + if (userSelectionAllocation) + this._userSelectionBox.allocate(userSelectionAllocation, flags); + + if (logoAllocation) + this._logoBin.allocate(logoAllocation, flags); + }, + + _ensureUserListLoaded: function() { + if (!this._userManager.is_loaded) + this._userManagerLoadedId = this._userManager.connect('notify::is-loaded', + Lang.bind(this, function() { + if (this._userManager.is_loaded) { + this._loadUserList(); + this._userManager.disconnect(this._userManagerLoadedId); + this._userManagerLoadedId = 0; + } + })); + else + GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, this._loadUserList)); + }, + + _updateDisableUserList: function() { + let disableUserList = this._settings.get_boolean(GdmUtil.DISABLE_USER_LIST_KEY); + + if (disableUserList != this._disableUserList) { + this._disableUserList = disableUserList; + + if (this._authPrompt.verificationStatus == AuthPrompt.AuthPromptStatus.NOT_VERIFYING) + this._authPrompt.reset(); + } + }, + + _updateCancelButton: function() { + let cancelVisible; + + // Hide the cancel button if the user list is disabled and we're asking for + // a username + if (this._authPrompt.verificationStatus == AuthPrompt.AuthPromptStatus.NOT_VERIFYING && this._disableUserList) + cancelVisible = false; + else + cancelVisible = true; + + this._authPrompt.cancelButton.visible = cancelVisible; + }, + + _updateBanner: function() { + let enabled = this._settings.get_boolean(GdmUtil.BANNER_MESSAGE_KEY); + let text = this._settings.get_string(GdmUtil.BANNER_MESSAGE_TEXT_KEY); + + if (enabled && text) { + this._bannerLabel.set_text(text); + this._bannerLabel.show(); + } else { + this._bannerLabel.hide(); + } + }, + ++ _fadeInBannerView: function() { ++ Tweener.addTween(this._bannerView, ++ { opacity: 255, ++ time: _FADE_ANIMATION_TIME, ++ transition: 'easeOutQuad' }); ++ }, ++ ++ _hideBannerView: function() { ++ Tweener.removeTweens(this._bannerView); ++ this._bannerView.opacity = 0; ++ }, ++ + _updateLogoTexture: function(cache, uri) { + if (this._logoFileUri != uri) + return; + + this._logoBin.destroy_all_children(); + if (this._logoFileUri) + this._logoBin.add_child(this._textureCache.load_uri_async(this._logoFileUri, + -1, _LOGO_ICON_HEIGHT)); + }, + + _updateLogo: function() { + let path = this._settings.get_string(GdmUtil.LOGO_KEY); + + this._logoFileUri = path ? Gio.file_new_for_path(path).get_uri() : null; + this._updateLogoTexture(this._textureCache, this._logoFileUri); + }, + + _onPrompted: function() { + this._sessionMenuButton.updateSensitivity(true); + + if (this._shouldShowSessionMenuButton()) + this._authPrompt.setActorInDefaultButtonWell(this._sessionMenuButton.actor); + this._showPrompt(); + }, + + _onReset: function(authPrompt, beginRequest) { + this._sessionMenuButton.updateSensitivity(true); + + this._user = null; + +@@ -688,60 +763,61 @@ const LoginDialog = new Lang.Class({ + this._hideUserListAskForUsernameAndBeginVerification(); + } else { + this._hideUserListAndBeginVerification(); + } + }, + + _onDefaultSessionChanged: function(client, sessionId) { + this._sessionMenuButton.setActiveSession(sessionId); + }, + + _shouldShowSessionMenuButton: function() { + if (this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.VERIFYING && + this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.VERIFICATION_FAILED) + return false; + + if (this._user && this._user.is_loaded && this._user.is_logged_in()) + return false; + + return true; + }, + + _showPrompt: function() { + if (this._authPrompt.actor.visible) + return; + this._authPrompt.actor.opacity = 0; + this._authPrompt.actor.show(); + Tweener.addTween(this._authPrompt.actor, + { opacity: 255, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad' }); ++ this._fadeInBannerView(); + }, + + _showRealmLoginHint: function(realmManager, hint) { + if (!hint) + return; + + hint = hint.replace(/%U/g, 'user'); + hint = hint.replace(/%D/g, 'DOMAIN'); + hint = hint.replace(/%[^UD]/g, ''); + + // Translators: this message is shown below the username entry field + // to clue the user in on how to login to the local network realm + this._authPrompt.setMessage(_("(e.g., user or %s)").format(hint), GdmUtil.MessageType.HINT); + }, + + _askForUsernameAndBeginVerification: function() { + this._authPrompt.setPasswordChar(''); + this._authPrompt.setQuestion(_("Username: ")); + + let realmManager = new Realmd.Manager(); + let realmSignalId = realmManager.connect('login-format-changed', + Lang.bind(this, this._showRealmLoginHint)); + this._showRealmLoginHint(realmManager.loginFormat); + + let nextSignalId = this._authPrompt.connect('next', + Lang.bind(this, function() { + this._authPrompt.disconnect(nextSignalId); + this._authPrompt.updateSensitivity(false); + let answer = this._authPrompt.getAnswer(); + this._user = this._userManager.get_user(answer); +@@ -909,60 +985,61 @@ const LoginDialog = new Lang.Class({ + } + + return false; + })); + }, + + _setUserListExpanded: function(expanded) { + this._userList.updateStyle(expanded); + this._userSelectionBox.visible = expanded; + }, + + _hideUserList: function() { + this._setUserListExpanded(false); + if (this._userSelectionBox.visible) + GdmUtil.cloneAndFadeOutActor(this._userSelectionBox); + }, + + _hideUserListAskForUsernameAndBeginVerification: function() { + this._hideUserList(); + this._askForUsernameAndBeginVerification(); + }, + + _hideUserListAndBeginVerification: function() { + this._hideUserList(); + this._authPrompt.begin(); + }, + + _showUserList: function() { + this._ensureUserListLoaded(); + this._authPrompt.hide(); ++ this._hideBannerView(); + this._sessionMenuButton.close(); + this._setUserListExpanded(true); + this._notListedButton.show(); + this._userList.actor.grab_key_focus(); + }, + + _beginVerificationForItem: function(item) { + this._authPrompt.setUser(item.user); + + let userName = item.user.get_user_name(); + let hold = new Batch.Hold(); + + this._authPrompt.begin({ userName: userName, + hold: hold }); + return hold; + }, + + _onUserListActivated: function(activatedItem) { + let tasks = [function() { + return GdmUtil.cloneAndFadeOutActor(this._userSelectionBox); + }, + function() { + this._setUserListExpanded(false); + }]; + + this._user = activatedItem.user; + + this._updateCancelButton(); + + let batch = new Batch.ConcurrentBatch(this, [new Batch.ConsecutiveBatch(this, tasks), +-- +2.1.0 + + +From 9bdce53329817e3453baa85cbc4d899511630154 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 11 Nov 2014 09:11:01 -0500 +Subject: [PATCH 5/5] loginDialog: use two column view if banner message long + +Frequently banner messages are longer than can reasonable +fit in a one column view, which leads to a smooshed layout. + +This commit changes the layout to a two column view, with the +banner on the left and the prompt on the right, if the banner +message is long enough that it can't fit well above the prompt. +If there isn't enough space for two columns then we keep the +one column layout but add scrollbars. +--- + js/gdm/loginDialog.js | 67 ++++++++++++++++++++++++++++++++++++++++++--------- + 1 file changed, 55 insertions(+), 12 deletions(-) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 348798c..9ee259f 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -550,118 +550,161 @@ const LoginDialog = new Lang.Class({ + _getLogoBinAllocation: function (dialogBox) { + let actorBox = new Clutter.ActorBox(); + + let [minWidth, minHeight, natWidth, natHeight] = this._logoBin.get_preferred_size(); + let centerX = dialogBox.x1 + (dialogBox.x2 - dialogBox.x1) / 2; + + actorBox.x1 = centerX - natWidth / 2; + actorBox.y1 = dialogBox.y2 - natHeight; + actorBox.x2 = actorBox.x1 + natWidth; + actorBox.y2 = actorBox.y1 + natHeight; + + return actorBox; + }, + + _getCenterActorAllocation: function (dialogBox, actor) { + let actorBox = new Clutter.ActorBox(); + + let [minWidth, minHeight, natWidth, natHeight] = actor.get_preferred_size(); + let centerX = dialogBox.x1 + (dialogBox.x2 - dialogBox.x1) / 2; + let centerY = dialogBox.y1 + (dialogBox.y2 - dialogBox.y1) / 2; + + actorBox.x1 = centerX - natWidth / 2; + actorBox.y1 = centerY - natHeight / 2; + actorBox.x2 = actorBox.x1 + natWidth; + actorBox.y2 = actorBox.y1 + natHeight; + + return actorBox; + }, + + _onAllocate: function (actor, dialogBox, flags) { ++ let dialogWidth = dialogBox.x2 - dialogBox.x1; + let dialogHeight = dialogBox.y2 - dialogBox.y1; + + // First find out what space the children require + let bannerAllocation = null; + let bannerHeight = 0; + let bannerWidth = 0; + if (this._bannerView.visible) { + bannerAllocation = this._getBannerAllocation(dialogBox, this._bannerView); + bannerHeight = bannerAllocation.y2 - bannerAllocation.y1; + bannerWidth = bannerAllocation.x2 - bannerAllocation.x1; + } + + let authPromptAllocation = null; + let authPromptHeight = 0; ++ let authPromptWidth = 0; + if (this._authPrompt.actor.visible) { + authPromptAllocation = this._getCenterActorAllocation(dialogBox, this._authPrompt.actor); + authPromptHeight = authPromptAllocation.y2 - authPromptAllocation.y1; ++ authPromptWidth = authPromptAllocation.x2 - authPromptAllocation.x1; + } + + let userSelectionAllocation = null; + let userSelectionHeight = 0; + if (this._userSelectionBox.visible) { + userSelectionAllocation = this._getCenterActorAllocation(dialogBox, this._userSelectionBox); + userSelectionHeight = userSelectionAllocation.y2 - userSelectionAllocation.y1; + } + + let logoAllocation = null; + let logoHeight = 0; + if (this._logoBin.visible) { + logoAllocation = this._getLogoBinAllocation(dialogBox); + logoHeight = logoAllocation.y2 - logoAllocation.y1; + } + +- // Then figure out what extra space we can hand out ++ // Then figure out if we're overly constrained and need to ++ // try a different layout, or if we have what extra space we ++ // can hand out + if (bannerAllocation) { + let leftOverYSpace = dialogHeight - bannerHeight - authPromptHeight - logoHeight; + + if (leftOverYSpace > 0) { + // First figure out how much left over space is up top + let leftOverTopSpace = leftOverYSpace / 2; + + // Then, shift the banner into the middle of that extra space + let yShift = leftOverTopSpace / 2; + + bannerAllocation.y1 += yShift; + bannerAllocation.y2 += yShift; + } else { +- // recompute banner height to be constrained if there's no room for its +- // requested height +- +- // First figure out how much space there is without the banner +- leftOverYSpace += bannerHeight; +- +- // Then figure out how much of that space is up top +- let availableTopSpace = leftOverYSpace / 2; +- +- // Then give all of that space to the banner +- bannerAllocation.y2 = bannerAllocation.y1 + availableTopSpace; ++ // Then figure out how much space there would be if we switched to a ++ // wide layout with banner on one side and authprompt on the other. ++ let leftOverXSpace = dialogWidth - authPromptWidth; ++ ++ // In a wide view, half of the available space goes to the banner, ++ // and the other half goes to the margins. ++ let wideBannerWidth = leftOverXSpace / 2; ++ let wideSpacing = leftOverXSpace - wideBannerWidth; ++ ++ // If we do go with a wide layout, we need there to be at least enough ++ // space for the banner and the auth prompt to be the same width, ++ // so it doesn't look unbalanced. ++ if (authPromptWidth > 0 && wideBannerWidth > authPromptWidth) { ++ let centerX = dialogBox.x1 + dialogWidth / 2; ++ let centerY = dialogBox.y1 + dialogHeight / 2; ++ ++ // A small portion of the spacing goes down the center of the ++ // screen to help delimit the two columns of the wide view ++ let centerGap = wideSpacing / 8; ++ ++ // place the banner along the left edge of the center margin ++ bannerAllocation.x2 = centerX - centerGap / 2; ++ bannerAllocation.x1 = bannerAllocation.x2 - wideBannerWidth; ++ ++ // figure out how tall it would like to be and try to accomodate ++ // but don't let it get too close to the logo ++ let [wideMinHeight, wideBannerHeight] = this._bannerView.get_preferred_height(wideBannerWidth); ++ ++ let maxWideHeight = dialogHeight - 3 * logoHeight; ++ wideBannerHeight = Math.min(maxWideHeight, wideBannerHeight); ++ bannerAllocation.y1 = centerY - wideBannerHeight / 2; ++ bannerAllocation.y2 = bannerAllocation.y1 + wideBannerHeight; ++ ++ // place the auth prompt along the right edge of the center margin ++ authPromptAllocation.x1 = centerX + centerGap / 2; ++ authPromptAllocation.x2 = authPromptAllocation.x1 + authPromptWidth; ++ } else { ++ // If we aren't going to do a wide view, then we need to limit ++ // the height of the banner so it will present scrollbars ++ ++ // First figure out how much space there is without the banner ++ leftOverYSpace += bannerHeight; ++ ++ // Then figure out how much of that space is up top ++ let availableTopSpace = leftOverYSpace / 2; ++ ++ // Then give all of that space to the banner ++ bannerAllocation.y2 = bannerAllocation.y1 + availableTopSpace; ++ } + } + } else if (userSelectionAllocation) { + // Grow the user list to fill the space + let leftOverYSpace = dialogHeight - userSelectionHeight - logoHeight; + + if (leftOverYSpace > 0) { + let topExpansion = leftOverYSpace / 2; + let bottomExpansion = topExpansion; + + userSelectionAllocation.y1 -= topExpansion; + userSelectionAllocation.y2 += bottomExpansion; + } + } + + // Finally hand out the allocations + if (bannerAllocation) { + this._bannerView.allocate(bannerAllocation, flags); + } + + if (authPromptAllocation) + this._authPrompt.actor.allocate(authPromptAllocation, flags); + + if (userSelectionAllocation) + this._userSelectionBox.allocate(userSelectionAllocation, flags); + + if (logoAllocation) + this._logoBin.allocate(logoAllocation, flags); + }, + + _ensureUserListLoaded: function() { +-- +2.1.0 + diff --git a/SOURCES/login-screen-backport.patch b/SOURCES/login-screen-backport.patch index eb79b8e..08c265c 100644 --- a/SOURCES/login-screen-backport.patch +++ b/SOURCES/login-screen-backport.patch @@ -12907,7 +12907,7 @@ index a7b8b62..7ed5097 100644 _updateDefaultService: function() { if (this._settings.get_boolean(PASSWORD_AUTHENTICATION_KEY)) this._defaultService = PASSWORD_SERVICE_NAME; -+ else if (this.smartcardDetected) ++ else if (this._settings.get_boolean(SMARTCARD_AUTHENTICATION_KEY)) + this._defaultService = SMARTCARD_SERVICE_NAME; else if (this._haveFingerprintReader) this._defaultService = FINGERPRINT_SERVICE_NAME; @@ -15070,7 +15070,7 @@ index 887bedb..26028c8 100644 // Translators: this message is shown below the username entry field // to clue the user in on how to login to the local network realm - this._authPrompt.setHint(_("(e.g., user or %s)").format(hint)); -+ this._authPrompt.setMessage(_("(e.g., user or %s)").format(hint), AuthPrompt.MessageType.HINT); ++ this._authPrompt.setMessage(_("(e.g., user or %s)").format(hint), GdmUtil.MessageType.HINT); }, _askForUsernameAndBeginVerification: function() { @@ -15683,7 +15683,7 @@ index bfbd1b3..f421902 100644 _updateDefaultService: function() { if (this._settings.get_boolean(PASSWORD_AUTHENTICATION_KEY)) this._defaultService = PASSWORD_SERVICE_NAME; - else if (this.smartcardDetected) + else if (this._settings.get_boolean(SMARTCARD_AUTHENTICATION_KEY)) this._defaultService = SMARTCARD_SERVICE_NAME; else if (this._haveFingerprintReader) this._defaultService = FINGERPRINT_SERVICE_NAME; @@ -15875,7 +15875,7 @@ index 26028c8..315a863 100644 // Translators: this message is shown below the username entry field // to clue the user in on how to login to the local network realm - this._authPrompt.setMessage(_("(e.g., user or %s)").format(hint), AuthPrompt.MessageType.HINT); + this._authPrompt.setMessage(_("(e.g., user or %s)").format(hint), GdmUtil.MessageType.HINT); }, _askForUsernameAndBeginVerification: function() { @@ -17181,7 +17181,7 @@ index 1649e4e..9d5e243 100644 // Translators: this message is shown below the username entry field // to clue the user in on how to login to the local network realm - this._authPrompt.setMessage(_("(e.g., user or %s)").format(hint), AuthPrompt.MessageType.HINT); + this._authPrompt.setMessage(_("(e.g., user or %s)").format(hint), GdmUtil.MessageType.HINT); -- 1.8.3.1 @@ -18158,7 +18158,7 @@ index ca2b060..ee0199c 100644 // Translators: this message is shown below the username entry field // to clue the user in on how to login to the local network realm - this._authPrompt.setMessage(_("(e.g., user or %s)").format(hint), AuthPrompt.MessageType.HINT); + this._authPrompt.setMessage(_("(e.g., user or %s)").format(hint), GdmUtil.MessageType.HINT); }, _askForUsernameAndBeginVerification: function() { diff --git a/SOURCES/respect-disk-writes-lockdown-setting.patch b/SOURCES/respect-disk-writes-lockdown-setting.patch new file mode 100644 index 0000000..46f94c4 --- /dev/null +++ b/SOURCES/respect-disk-writes-lockdown-setting.patch @@ -0,0 +1,126 @@ +From 6625c06d5da50979ea4a350a168abfa5cc1e8498 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 3 Oct 2014 16:49:21 +0200 +Subject: [PATCH 1/3] screencast: Don't leak recorders on failure + +ShellRecorder.record() may fail, remove the recorder from the map +in that case. + +https://bugzilla.gnome.org/show_bug.cgi?id=737846 +--- + js/ui/screencast.js | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/js/ui/screencast.js b/js/ui/screencast.js +index 168f589..99f0366 100644 +--- a/js/ui/screencast.js ++++ b/js/ui/screencast.js +@@ -109,6 +109,8 @@ const ScreencastService = new Lang.Class({ + this._applyOptionalParameters(recorder, options); + let [success, fileName] = recorder.record(); + returnValue = [success, fileName ? fileName : '']; ++ if (!success) ++ this._stopRecordingForSender(sender); + } + + invocation.return_value(GLib.Variant.new('(bs)', returnValue)); +@@ -142,6 +144,8 @@ const ScreencastService = new Lang.Class({ + this._applyOptionalParameters(recorder, options); + let [success, fileName] = recorder.record(); + returnValue = [success, fileName ? fileName : '']; ++ if (!success) ++ this._stopRecordingForSender(sender); + } + + invocation.return_value(GLib.Variant.new('(bs)', returnValue)); +-- +2.1.0 + + +From 6c6e8f04f466909ed16670bc1c1ae77e30deb95d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 3 Oct 2014 16:40:49 +0200 +Subject: [PATCH 2/3] screencast: Re-add lockdown support + +Commit 81bb7009ea120d3 removed support for the disable-disk-writes +lockdown feature for screencasts, add it back. + +https://bugzilla.gnome.org/show_bug.cgi?id=737846 +--- + js/ui/screencast.js | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/js/ui/screencast.js b/js/ui/screencast.js +index 99f0366..63caecb 100644 +--- a/js/ui/screencast.js ++++ b/js/ui/screencast.js +@@ -41,6 +41,8 @@ const ScreencastService = new Lang.Class({ + + this._recorders = new Hash.Map(); + ++ this._lockdownSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.lockdown' }); ++ + Main.sessionMode.connect('updated', + Lang.bind(this, this._sessionModeChanged)); + }, +@@ -95,7 +97,8 @@ const ScreencastService = new Lang.Class({ + + ScreencastAsync: function(params, invocation) { + let returnValue = [false, '']; +- if (!Main.sessionMode.allowScreencast) { ++ if (!Main.sessionMode.allowScreencast || ++ this._lockdownSettings.get_boolean('disable-save-to-disk')) { + invocation.return_value(GLib.Variant.new('(bs)', returnValue)); + return; + } +@@ -118,7 +121,8 @@ const ScreencastService = new Lang.Class({ + + ScreencastAreaAsync: function(params, invocation) { + let returnValue = [false, '']; +- if (!Main.sessionMode.allowScreencast) { ++ if (!Main.sessionMode.allowScreencast || ++ this._lockdownSettings.get_boolean('disable-save-to-disk')) { + invocation.return_value(GLib.Variant.new('(bs)', returnValue)); + return; + } +-- +2.1.0 + + +From 2cfc69f5ddfd126e2c2be5180e782e67c431631a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 3 Oct 2014 16:33:37 +0200 +Subject: [PATCH 3/3] screenshot: Respect lockdown settings + +We allow users/admins to lock down disk writes, respect that when +taking screenshots. + +https://bugzilla.gnome.org/show_bug.cgi?id=737846 +--- + js/ui/screenshot.js | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/js/ui/screenshot.js b/js/ui/screenshot.js +index dd6448e..6e08fb5 100644 +--- a/js/ui/screenshot.js ++++ b/js/ui/screenshot.js +@@ -64,12 +64,15 @@ const ScreenshotService = new Lang.Class({ + + this._screenShooter = new Hash.Map(); + ++ this._lockdownSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.lockdown' }); ++ + Gio.DBus.session.own_name('org.gnome.Shell.Screenshot', Gio.BusNameOwnerFlags.REPLACE, null, null); + }, + + _createScreenshot: function(invocation) { + let sender = invocation.get_sender(); +- if (this._screenShooter.has(sender)) { ++ if (this._screenShooter.has(sender) || ++ this._lockdownSettings.get_boolean('disable-save-to-disk')) { + invocation.return_value(GLib.Variant.new('(bs)', [false, ''])); + return null; + } +-- +2.1.0 + diff --git a/SOURCES/vertical-monitor-layouts.patch b/SOURCES/vertical-monitor-layouts.patch new file mode 100644 index 0000000..4caa571 --- /dev/null +++ b/SOURCES/vertical-monitor-layouts.patch @@ -0,0 +1,131 @@ +From b2f4c99a00e6d69db88b24e2d723f71b272443de Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 15 Apr 2014 17:24:07 +0200 +Subject: [PATCH 1/2] layout: Don't always extend struts to the screen edge + +NetWM struts are defined in terms of screen edges (rather than monitor +edges), which works poorly with vertical monitor layouts (as it renders +entire monitors unusable). Don't extend struts in those cases. + +https://bugzilla.gnome.org/show_bug.cgi?id=663690 +--- + js/ui/layout.js | 30 +++++++++++++++++++++++++----- + 1 file changed, 25 insertions(+), 5 deletions(-) + +diff --git a/js/ui/layout.js b/js/ui/layout.js +index 141eecc..3dcc858 100644 +--- a/js/ui/layout.js ++++ b/js/ui/layout.js +@@ -1006,19 +1006,39 @@ const LayoutManager = new Lang.Class({ + continue; + + // Ensure that the strut rects goes all the way to the screen edge, +- // as this really what mutter expects. ++ // as this really what mutter expects. However skip this step ++ // in cases where this would render an entire monitor unusable. + switch (side) { + case Meta.Side.TOP: +- y1 = 0; ++ let hasMonitorsAbove = this.monitors.some(Lang.bind(this, ++ function(mon) { ++ return this._isAboveOrBelowPrimary(mon) && ++ mon.y < primary.y; ++ })); ++ if (!hasMonitorsAbove) ++ y1 = 0; + break; + case Meta.Side.BOTTOM: +- y2 = global.screen_height; ++ if (this.primaryIndex == this.bottomIndex) ++ y2 = global.screen_height; + break; + case Meta.Side.LEFT: +- x1 = 0; ++ let hasMonitorsLeft = this.monitors.some(Lang.bind(this, ++ function(mon) { ++ return !this._isAboveOrBelowPrimary(mon) && ++ mon.x < primary.x; ++ })); ++ if (!hasMonitorsLeft) ++ x1 = 0; + break; + case Meta.Side.RIGHT: +- x2 = global.screen_width; ++ let hasMonitorsRight = this.monitors.some(Lang.bind(this, ++ function(mon) { ++ return !this._isAboveOrBelowPrimary(mon) && ++ mon.x > primary.x; ++ })); ++ if (!hasMonitorsRight) ++ x2 = global.screen_width; + break; + } + +-- +2.1.0 + + +From ecdd65097513ff9b36afde817db8627b933da7e3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 11 Jun 2014 02:16:43 +0200 +Subject: [PATCH 2/2] layout: Do not expand struts to screen edges + +set_builtin_struts() in mutter now handles this for us, so we can kill +off the extra code here. + +https://bugzilla.gnome.org/show_bug.cgi?id=730527 +--- + js/ui/layout.js | 37 ------------------------------------- + 1 file changed, 37 deletions(-) + +diff --git a/js/ui/layout.js b/js/ui/layout.js +index 3dcc858..2f0a5a5 100644 +--- a/js/ui/layout.js ++++ b/js/ui/layout.js +@@ -1005,43 +1005,6 @@ const LayoutManager = new Lang.Class({ + else + continue; + +- // Ensure that the strut rects goes all the way to the screen edge, +- // as this really what mutter expects. However skip this step +- // in cases where this would render an entire monitor unusable. +- switch (side) { +- case Meta.Side.TOP: +- let hasMonitorsAbove = this.monitors.some(Lang.bind(this, +- function(mon) { +- return this._isAboveOrBelowPrimary(mon) && +- mon.y < primary.y; +- })); +- if (!hasMonitorsAbove) +- y1 = 0; +- break; +- case Meta.Side.BOTTOM: +- if (this.primaryIndex == this.bottomIndex) +- y2 = global.screen_height; +- break; +- case Meta.Side.LEFT: +- let hasMonitorsLeft = this.monitors.some(Lang.bind(this, +- function(mon) { +- return !this._isAboveOrBelowPrimary(mon) && +- mon.x < primary.x; +- })); +- if (!hasMonitorsLeft) +- x1 = 0; +- break; +- case Meta.Side.RIGHT: +- let hasMonitorsRight = this.monitors.some(Lang.bind(this, +- function(mon) { +- return !this._isAboveOrBelowPrimary(mon) && +- mon.x > primary.x; +- })); +- if (!hasMonitorsRight) +- x2 = global.screen_width; +- break; +- } +- + let strutRect = new Meta.Rectangle({ x: x1, y: y1, width: x2 - x1, height: y2 - y1}); + let strut = new Meta.Strut({ rect: strutRect, side: side }); + struts.push(strut); +-- +2.1.0 + diff --git a/SPECS/gnome-shell.spec b/SPECS/gnome-shell.spec index 1f0d8a6..4612499 100644 --- a/SPECS/gnome-shell.spec +++ b/SPECS/gnome-shell.spec @@ -1,6 +1,6 @@ Name: gnome-shell Version: 3.8.4 -Release: 32%{?dist} +Release: 45%{?dist} Summary: Window management and application launching for GNOME Group: User Interface/Desktops @@ -19,7 +19,10 @@ Patch4: gnome-shell-favourite-apps-empathy.patch Patch10: 0001-popupMenu-Fix-removing-the-active-menu-from-PopupMen.patch Patch11: 0001-catch-more-errors-on-extensions-enable-and-disable.patch -Patch12: 0001-layout-Don-t-always-extend-struts-to-the-screen-edge.patch +Patch12: vertical-monitor-layouts.patch +Patch13: fix-thumbail-box-shrinking.patch +Patch14: 0001-dateMenu-Try-to-use-the-default-calendar-application.patch +Patch15: 0001-appSwitcher-Add-option-to-limit-to-the-current-works.patch Patch20: 0001-main-allow-session-mode-to-be-specified-in-the-envir.patch @@ -38,6 +41,9 @@ Patch63: 0002-keyring-Cancel-active-prompts-on-disable.patch Patch70: 0001-screenshot-Extend-ScreenshotArea-parameter-validatio.patch Patch71: 0002-screencast-Fix-disabling-screencasts-via-session-mod.patch Patch72: 0003-screencast-Validate-parameters-of-ScreencastArea.patch +Patch73: 0001-screenshot-Also-validate-parameters-to-FlashArea.patch +Patch74: 0001-shell-screenshot-Only-allow-one-screenshot-request-a.patch +Patch75: respect-disk-writes-lockdown-setting.patch Patch80: 0001-shellDBus-Add-a-DBus-method-to-load-a-single-extensi.patch Patch81: 0002-extensions-Add-a-SESSION_MODE-extension-type.patch @@ -47,12 +53,19 @@ Patch90: 0001-panel-add-an-icon-to-the-ActivitiesButton.patch Patch99: login-screen-backport.patch Patch100: gdm-support-pre-authenticated-logins-from-oVirt.patch Patch101: dont-load-user-list-when-disabled.patch +Patch102: disallow-cancel-after-success.patch +Patch103: fix-login-screen-focus.patch +Patch104: fix-cancel-sensitivity.patch +Patch105: login-banner-fixes.patch +Patch106: fix-session-activation.patch + +Patch110: 0001-Add-support-for-meta_restart-and-MetaDisplay-restart.patch %define clutter_version 1.13.4 %define gnome_bluetooth_version 3.5.5 %define gobject_introspection_version 0.10.1 %define gjs_version 1.35.4 -%define mutter_version 3.8.3 +%define mutter_version 3.8.4-12 %define eds_version 3.5.3 %define gnome_desktop_version 3.7.90 %define gnome_menus_version 3.5.3 @@ -164,6 +177,9 @@ be used only by extensions.gnome.org. %patch10 -p1 -b .fix-popup-menu-manager-exception %patch11 -p1 -b .catch-extension-errors %patch12 -p1 -b .improve-vertical-monitor-layouts +%patch13 -p1 -b .fix-shrinking-workspace-switcher +%patch14 -p1 -b .use-default-calendar-app +%patch15 -p1 -b .current-workspace-app-switcher-option %patch20 -p1 -b .main-allow-session-mode-to-be-specified-in-the-envir @@ -182,6 +198,9 @@ be used only by extensions.gnome.org. %patch70 -p1 -b .validate-screenshot-params %patch71 -p1 -b .fix-disable-screencasts %patch72 -p1 -b .validate-screencast-params +%patch73 -p1 -b .validate-screenshot-params +%patch74 -p1 -b .disallow-consecutive-screenshots +%patch75 -p1 -b .respect-disk-writes-lockdown %patch80 -p1 %patch81 -p1 @@ -191,6 +210,13 @@ be used only by extensions.gnome.org. %patch99 -p1 -b .login-screen-backport %patch100 -p1 -b .gdm-support-pre-authenticated-logins-from-oVirt %patch101 -p1 -b .dont-load-user-list-when-disabled +%patch102 -p1 -b .disallow-cancel-after-success +%patch103 -p1 -b .fix-login-screen-focus +%patch104 -p1 -b .fix-cancel-sensitivity +%patch105 -p1 -b .login-banner-fixes +%patch106 -p1 -b .fix-session-activation + +%patch110 -p1 -b .meta-restart %build autoreconf -f @@ -262,9 +288,82 @@ glib-compile-schemas --allow-any-name %{_datadir}/glib-2.0/schemas &> /dev/null %{_libdir}/mozilla/plugins/*.so %changelog -* Wed Apr 16 2014 Florian Müllner - 3.8.4-32 +* Thu Nov 13 2014 Ray Strode 3.8.4-45 +- Don't inform GDM about session changes that came from GDM + Resolves: #1163474 + +* Wed Nov 12 2014 Ray Strode 3.8.4-44 +- If password authentication is disabled and smartcard authentication is + enabled and smartcard isn't plugged in at start up, prompt user for + smartcard + Resolves: #1159385 + +* Wed Nov 12 2014 Ray Strode - 3.8.4-43 +- Support long login banner messages more effectively + Resolves: #1110036 + +* Fri Oct 17 2014 Florian Müllner - 3.8.4-42 +- Respect disk-writes lockdown setting + Resolves: rhbz#1154122 + +* Sat Oct 11 2014 Florian Müllner - 3.8.4-41 +- Disallow consecutive screenshot requests to avoid an OOM situation + Resolves: rhbz#1154107 + +* Fri Oct 10 2014 Florian Müllner - 3.8.4-41 +- Add option to limit app switcher to current workspace + Resolves: rhbz#1101568 + +* Fri Oct 10 2014 Florian Müllner - 3.8.4-40 +- Try harder to use the default calendar application + Resolves: rhbz#1052201 + +* Fri Oct 10 2014 Florian Müllner - 3.8.4-40 +- Update workspace switcher fix + Resolves: rhbz#1092102 + +* Thu Oct 09 2014 Florian Müllner - 3.8.4-39 +- Validate screenshot parameters + Resolves: rhbz#1104694 + +* Thu Oct 09 2014 Florian Müllner - 3.8.4-38 +- Fix shrinking workspace switcher + Resolves: rhbz#1092102 + +* Thu Oct 09 2014 Florian Müllner - 3.8.4-38 +- Update fix for vertical monitor layouts to upstream fix + Resolves: rhbz#1075240 + +* Wed Oct 08 2014 Ray Strode 3.8.4-38 +- Fix traceback introduced in 3.8.4-36 when unlocking via + user switcher + Related: #1101333 + +* Tue Oct 07 2014 Ray Strode 3.8.4-37 +- Fix problems with LDAP and disable-user-list=TRUE + Resolves: rhbz#1137041 + +* Mon Oct 06 2014 Ray Strode 3.8.4-36 +- Fix login screen focus issue following idle + Resolves: rhbz#1101333 + +* Sun Oct 05 2014 Ray Strode 3.8.4-35 +- Disallow cancel from login screen before login attempt + has been initiated. + Resolves: rhbz#1109530 + +* Sun Oct 05 2014 Ray Strode 3.8.4-34 +- Disallow cancel from login screen after login is already + commencing. + Resolves: rhbz#1079294 + +* Thu Jul 17 2014 Owen Taylor 3.8.4-33 +- Add a patch for quadbuffer stereo suppport + Resolves: rhbz#1108893 + +* Wed Apr 16 2014 Florian Müllner - 3.13.4-32 - Improve vertical monitor layouts - Resolves: rhbz#1096186 + Resolves: rhbz#1075240 * Wed Mar 19 2014 Florian Müllner - 3.8.4-31 - Fix some more background memory leaks