Compare commits

..

2 Commits

Author SHA1 Message Date
Giovanni Campagna
6d4ca1fcc8 WorkspaceThumbnails: show attached modal dialogs togheter with their parents
The window clones in the central part of the overview are showing modal
dialogs now, and this creates an inconsistency if the thumbnail doesn't too.
Code is intentionally similar in the two places.

https://bugzilla.gnome.org/show_bug.cgi?id=650843
2013-03-04 17:50:18 +01:00
Giovanni Campagna
93b1af401f Workspace: show attached modal dialog
Windows in the overview should be like they appear in the workspace,
including modal dialogs that are attached above them.
In addition, hiding the dialogs in the overview causes a flash as
dialog appears at the end of the transition.

Based on a patch by Maxim Ermilov <zaspire@rambler.ru>

https://bugzilla.gnome.org/show_bug.cgi?id=650843
2013-03-04 17:50:18 +01:00
21 changed files with 1089 additions and 1000 deletions

43
NEWS
View File

@@ -1,46 +1,3 @@
3.7.91
======
* overview: Fade out controls during DND that are not targets [Cosimo; #686984]
* overview: Keep open when a Control key is held [Florian; #686984]
* Improve login screen => session transition [Ray; #694321]
* Center application grid horizontally [Florian; #694261]
* Fix hiding panel when fullscreen windows span multiple monitors [Adel; 646861]
* Tweak thresholds of pressure barrier [Jasper; #694467]
* Tweak window picker layout [Jasper; #694902]
* Expose key grab DBus API to gnome-settings-daemon [Florian; #643111]
* Don't always show message tray in overview, add message indicator
[Cosimo; #687787]
* Tweak startup animation [Ray; #694326]
* Add OSD popups and expose them to gnome-settings-daemon [Florian; #613543]
* Move loupe icon to the start of the search entry [Jasper; #695069]
* Don't show the input switcher with less than 2 items [Rui; 695000]
* Fix auto-completion of system modals immediately upon display [Stef; #692937]
* Ignore workspaces in alt-tab [Florian; #661156]
* Disable copying text from password entries [Florian; #695104]
* Use standard styling for ibus candidate popups [Allan; #694796]
* Fix calendar changing height on month changes [Giovanni; #641383]
* Port the hot corner to use pressure barriers [Jasper; #663661]
* Misc bug fixes and cleanups: [Hashem, Giovanni, Alban, Jasper, Cosimo,
Florian, Adel, Daniel, Matthias, Ray, Rui, Guillaume, Stef; #685849, #690643,
#694292, #693814, #694234, #694365, #694287, #694336, #694256, #694261,
#663601, #694441, #694284, #694463, #694475, #687248, #694394, #694320,
#694701, #694784, #694858, #694906, #694327, #694876, #694905, #694969,
#694970, #694988, #695006, #695001, #694998, #695023, #695002, #695073,
#695126, #687748, #694837, #693907, #679851, #694988]
Contributors:
Giovanni Campagna, Cosimo Cecchi, Matthias Clasen, Alban Crequy, Allan Day,
Guillaume Desmottes, Adel Gadllah, Rui Matos, Daniel Mustieles,
Hashem Nasarat, Jasper St. Pierre, Ray Strode, Stef Walter
Translations:
Yuri Myasoedov [ru], Adam Matoušek [cs], Piotr Drąg [pl], Matej Urbančič [sl],
Sweta Kothari [gu], Kjartan Maraas [nb], Nguyễn Thái Ngọc Duy [vi],
Chao-Hsiung Liao [zh_HK, zh_TW], Dimitris Spingos [el],
Inaki Larranaga Murgoitio [eu], Luca Ferretti [it], A S Alam [pa],
Gheyret Kenji [ug], Stas Solovey [ru], Enrico Nicoletto [pt_BR],
Fran Diéguez [gl], Daniel Mustieles [es], Aurimas Černius [lt]
3.7.90
======
* Let GNOME Shell work on EGL and GLES2 [Neil; #693225, #693438, #693339]

View File

@@ -1,5 +1,5 @@
AC_PREREQ(2.63)
AC_INIT([gnome-shell],[3.7.91],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell])
AC_INIT([gnome-shell],[3.7.90],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_SRCDIR([src/shell-global.c])
@@ -63,7 +63,7 @@ AM_CONDITIONAL(BUILD_RECORDER, $build_recorder)
CLUTTER_MIN_VERSION=1.13.4
GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1
GJS_MIN_VERSION=1.35.4
MUTTER_MIN_VERSION=3.7.91
MUTTER_MIN_VERSION=3.7.90
GTK_MIN_VERSION=3.7.9
GIO_MIN_VERSION=2.35.0
LIBECAL_MIN_VERSION=3.5.3
@@ -96,7 +96,7 @@ PKG_CHECK_MODULES(GNOME_SHELL, gio-unix-2.0 >= $GIO_MIN_VERSION
polkit-agent-1 >= $POLKIT_MIN_VERSION xfixes
libnm-glib libnm-util >= $NETWORKMANAGER_MIN_VERSION
libnm-gtk >= $NETWORKMANAGER_MIN_VERSION
libsecret-unstable gcr-3 >= $GCR_MIN_VERSION)
gnome-keyring-1 gcr-3 >= $GCR_MIN_VERSION)
PKG_CHECK_MODULES(GNOME_SHELL_JS, gio-2.0 gjs-internals-1.0 >= $GJS_MIN_VERSION)
PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-3.0 libcroco-0.6 >= 0.6.8 x11)

View File

@@ -125,8 +125,7 @@ StScrollBar StButton#vhandle:active {
/* PopupMenu */
.popup-menu-boxpointer,
.candidate-popup-boxpointer {
.popup-menu-boxpointer {
-arrow-border-radius: 8px;
-arrow-background-color: rgba(0,0,0,0.9);
-arrow-border-width: 2px;
@@ -1258,15 +1257,15 @@ StScrollBar StButton#vhandle:active {
font-weight: bold;
}
.calendar-other-month-day {
color: #333333;
}
.calendar-day-with-events {
font-weight: bold;
color: white;
}
.calendar-other-month-day {
color: #333333;
}
.events-header-vbox {
spacing: 6pt;
padding-right: .5em;
@@ -2130,12 +2129,23 @@ StScrollBar StButton#vhandle:active {
}
/* IBus Candidate Popup */
.candidate-popup-boxpointer {
-arrow-border-radius: 8px;
-arrow-background-color: #707070;
-arrow-border-width: 0px;
-arrow-base: 24px;
-arrow-rise: 11px;
}
.candidate-popup-content {
padding: 0.5em;
spacing: 0.3em;
}
.candidate-popup-text {
font-size: 9pt;
}
.candidate-index {
padding: 0 0.5em 0 0;
color: #cccccc;

View File

@@ -168,12 +168,6 @@ const EmptyEventSource = new Lang.Class({
Name: 'EmptyEventSource',
_init: function() {
this.isLoading = false;
this.isDummy = true;
this.hasCalendars = false;
},
destroy: function() {
},
requestRange: function(begin, end) {
@@ -197,7 +191,6 @@ const CalendarServerIface = <interface name="org.gnome.Shell.CalendarServer">
<arg type="b" direction="in" />
<arg type="a(sssbxxa{sv})" direction="out" />
</method>
<property name="HasCalendars" type="b" access="read" />
<signal name="Changed" />
</interface>;
@@ -208,7 +201,8 @@ function CalendarServer() {
g_interface_name: CalendarServerInfo.name,
g_interface_info: CalendarServerInfo,
g_name: 'org.gnome.Shell.CalendarServer',
g_object_path: '/org/gnome/Shell/CalendarServer' });
g_object_path: '/org/gnome/Shell/CalendarServer',
g_flags: Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES });
}
function _datesEqual(a, b) {
@@ -235,8 +229,6 @@ const DBusEventSource = new Lang.Class({
_init: function() {
this._resetCache();
this.isLoading = false;
this.isDummy = false;
this._initialized = false;
this._dbusProxy = new CalendarServer();
@@ -257,27 +249,11 @@ const DBusEventSource = new Lang.Class({
this._onNameVanished();
}));
this._dbusProxy.connect('g-properties-changed', Lang.bind(this, function() {
this.emit('notify::has-calendars');
}));
this._initialized = true;
this.emit('notify::has-calendars');
this._onNameAppeared();
}));
},
destroy: function() {
this._dbusProxy.run_dispose();
},
get hasCalendars() {
if (this._initialized)
return this._dbusProxy.HasCalendars;
else
return false;
},
_resetCache: function() {
this._events = [];
this._lastRequestBegin = null;
@@ -317,7 +293,6 @@ const DBusEventSource = new Lang.Class({
}
this._events = newEvents;
this.isLoading = false;
this.emit('changed');
},
@@ -340,7 +315,6 @@ const DBusEventSource = new Lang.Class({
requestRange: function(begin, end, forceReload) {
if (forceReload || !(_datesEqual(begin, this._lastRequestBegin) && _datesEqual(end, this._lastRequestEnd))) {
this.isLoading = true;
this._lastRequestBegin = begin;
this._lastRequestEnd = end;
this._curRequestBegin = begin;
@@ -415,11 +389,19 @@ const Calendar = new Lang.Class({
// @eventSource: is an object implementing the EventSource API, e.g. the
// requestRange(), getEvents(), hasEvents() methods and the ::changed signal.
setEventSource: function(eventSource) {
if (this._eventSource) {
this._eventSource.disconnect(this._eventSourceChangedId);
this._eventSource = null;
}
this._eventSource = eventSource;
this._eventSource.connect('changed', Lang.bind(this, function() {
this._update(false);
}));
this._update(true);
if (this._eventSource) {
this._eventSourceChangedId = this._eventSource.connect('changed', Lang.bind(this, function() {
this._update(false);
}));
this._update(true);
}
},
// Sets the calendar to show a specific date
@@ -556,44 +538,20 @@ const Calendar = new Lang.Class({
children[i].destroy();
// Start at the beginning of the week before the start of the month
//
// We want to show always 6 weeks (to keep the calendar menu at the same
// height if there are no events), so we pad it according to the following
// policy:
//
// 1 - If a month has 6 weeks, we place no padding (example: Dec 2012)
// 2 - If a month has 5 weeks and it starts on week start, we pad one week
// before it (example: Apr 2012)
// 3 - If a month has 5 weeks and it starts on any other day, we pad one week
// after it (example: Nov 2012)
// 4 - If a month has 4 weeks, we pad one week before and one after it
// (example: Feb 2010)
//
// Actually computing the number of weeks is complex, but we know that the
// problematic categories (2 and 4) always start on week start, and that
// all months at the end have 6 weeks.
let beginDate = new Date(this._selectedDate);
beginDate.setDate(1);
beginDate.setSeconds(0);
beginDate.setHours(12);
let year = beginDate.getYear();
let daysToWeekStart = (7 + beginDate.getDay() - this._weekStart) % 7;
let startsOnWeekStart = daysToWeekStart == 0;
let weekPadding = startsOnWeekStart ? 7 : 0;
beginDate.setTime(beginDate.getTime() - (weekPadding + daysToWeekStart) * MSECS_IN_DAY);
beginDate.setTime(beginDate.getTime() - daysToWeekStart * MSECS_IN_DAY);
let iter = new Date(beginDate);
let row = 2;
// nRows here means 6 weeks + one header + one navbar
let nRows = 8;
while (row < 8) {
while (true) {
let button = new St.Button({ label: iter.getDate().toString() });
let rtl = button.get_text_direction() == Clutter.TextDirection.RTL;
if (this._eventSource.isDummy)
if (!this._eventSource)
button.reactive = false;
let iterStr = iter.toUTCString();
@@ -602,9 +560,8 @@ const Calendar = new Lang.Class({
this.setDate(newlySelectedDate, false);
}));
let hasEvents = this._eventSource.hasEvents(iter);
let hasEvents = this._eventSource && this._eventSource.hasEvents(iter);
let styleClass = 'calendar-day-base calendar-day';
if (_isWorkDay(iter))
styleClass += ' calendar-work-day'
else
@@ -644,13 +601,17 @@ const Calendar = new Lang.Class({
}
iter.setTime(iter.getTime() + MSECS_IN_DAY);
if (iter.getDay() == this._weekStart)
if (iter.getDay() == this._weekStart) {
// We stop on the first "first day of the week" after the month we are displaying
if (iter.getMonth() > this._selectedDate.getMonth() || iter.getYear() > this._selectedDate.getYear())
break;
row++;
}
}
// Signal to the event source that we are interested in events
// only from this date range
this._eventSource.requestRange(beginDate, iter, forceReload);
if (this._eventSource)
this._eventSource.requestRange(beginDate, iter, forceReload);
}
});
@@ -668,8 +629,16 @@ const EventsList = new Lang.Class({
},
setEventSource: function(eventSource) {
if (this._eventSource) {
this._eventSource.disconnect(this._eventSourceChangedId);
this._eventSource = null;
}
this._eventSource = eventSource;
this._eventSource.connect('changed', Lang.bind(this, this._update));
if (this._eventSource) {
this._eventSourceChangedId = this._eventSource.connect('changed', Lang.bind(this, this._update));
}
},
_addEvent: function(dayNameBox, timeBox, eventTitleBox, includeDayName, day, time, desc) {
@@ -686,6 +655,9 @@ const EventsList = new Lang.Class({
},
_addPeriod: function(header, begin, end, includeDayName, showNothingScheduled) {
if (!this._eventSource)
return;
let events = this._eventSource.getEvents(begin, end);
let clockFormat = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);;
@@ -782,9 +754,6 @@ const EventsList = new Lang.Class({
},
_update: function() {
if (this._eventSource.isLoading)
return;
let today = new Date();
if (_sameDay (this._date, today)) {
this._showToday();

View File

@@ -161,10 +161,8 @@ const DateMenuButton = new Lang.Class({
this._openClocksItem.actor.visible = app !== null;
},
_updateEventsVisibility: function() {
let visible = this._eventSource.hasCalendars;
_setEventsVisibility: function(visible) {
this._openCalendarItem.actor.visible = visible;
this._openClocksItem.actor.visible = visible;
this._separator.visible = visible;
if (visible) {
let alignment = 0.25;
@@ -179,16 +177,8 @@ const DateMenuButton = new Lang.Class({
},
_setEventSource: function(eventSource) {
if (this._eventSource)
this._eventSource.destroy();
this._calendar.setEventSource(eventSource);
this._eventList.setEventSource(eventSource);
this._eventSource = eventSource;
this._eventSource.connect('notify::has-calendars', Lang.bind(this, function() {
this._updateEventsVisibility();
}));
},
_sessionUpdated: function() {
@@ -197,10 +187,10 @@ const DateMenuButton = new Lang.Class({
if (showEvents) {
eventSource = new Calendar.DBusEventSource();
} else {
eventSource = new Calendar.EmptyEventSource();
eventSource = null;
}
this._setEventSource(eventSource);
this._updateEventsVisibility();
this._setEventsVisibility(showEvents);
// This needs to be handled manually, as the code to
// autohide separators doesn't work across the vbox

View File

@@ -18,6 +18,7 @@ const Main = imports.ui.main;
const Params = imports.misc.params;
const Tweener = imports.ui.tweener;
const HOT_CORNER_ACTIVATION_TIMEOUT = 0.5;
const STARTUP_ANIMATION_TIME = 0.5;
const KEYBOARD_ANIMATION_TIME = 0.15;
const BACKGROUND_FADE_ANIMATION_TIME = 1.0;
@@ -28,9 +29,6 @@ const DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff);
const MESSAGE_TRAY_PRESSURE_THRESHOLD = 250; // pixels
const MESSAGE_TRAY_PRESSURE_TIMEOUT = 1000; // ms
const HOT_CORNER_PRESSURE_THRESHOLD = 100; // pixels
const HOT_CORNER_PRESSURE_TIMEOUT = 1000; // ms
function isPopupMetaWindow(actor) {
switch(actor.meta_window.get_window_type()) {
case Meta.WindowType.DROPDOWN_MENU:
@@ -132,9 +130,9 @@ const LayoutManager = new Lang.Class({
this.monitors = [];
this.primaryMonitor = null;
this.primaryIndex = -1;
this.hotCorners = [];
this._keyboardIndex = -1;
this._hotCorners = [];
this._leftPanelBarrier = null;
this._rightPanelBarrier = null;
this._trayBarrier = null;
@@ -193,7 +191,6 @@ const LayoutManager = new Lang.Class({
this.trayBox = new St.Widget({ name: 'trayBox',
layout_manager: new Clutter.BinLayout() });
this.addChrome(this.trayBox);
this._setupTrayPressure();
this.keyboardBox = new St.BoxLayout({ name: 'keyboardBox',
reactive: true,
@@ -278,58 +275,56 @@ const LayoutManager = new Lang.Class({
_updateHotCorners: function() {
// destroy old hot corners
for (let i = 0; i < this.hotCorners.length; i++)
this.hotCorners[i].destroy();
this.hotCorners = [];
let size = this.panelBox.height;
for (let i = 0; i < this._hotCorners.length; i++)
this._hotCorners[i].destroy();
this._hotCorners = [];
// build new hot corners
for (let i = 0; i < this.monitors.length; i++) {
if (i == this.primaryIndex)
continue;
let monitor = this.monitors[i];
let cornerX = this._rtl ? monitor.x + monitor.width : monitor.x;
let cornerY = monitor.y;
if (i != this.primaryIndex) {
let haveTopLeftCorner = true;
let haveTopLeftCorner = true;
// Check if we have a top left (right for RTL) corner.
// I.e. if there is no monitor directly above or to the left(right)
let besideX = this._rtl ? monitor.x + 1 : cornerX - 1;
let besideY = cornerY;
let aboveX = cornerX;
let aboveY = cornerY - 1;
// Check if we have a top left (right for RTL) corner.
// I.e. if there is no monitor directly above or to the left(right)
let besideX = this._rtl ? monitor.x + 1 : cornerX - 1;
let besideY = cornerY;
let aboveX = cornerX;
let aboveY = cornerY - 1;
for (let j = 0; j < this.monitors.length; j++) {
if (i == j)
continue;
let otherMonitor = this.monitors[j];
if (besideX >= otherMonitor.x &&
besideX < otherMonitor.x + otherMonitor.width &&
besideY >= otherMonitor.y &&
besideY < otherMonitor.y + otherMonitor.height) {
haveTopLeftCorner = false;
break;
}
if (aboveX >= otherMonitor.x &&
aboveX < otherMonitor.x + otherMonitor.width &&
aboveY >= otherMonitor.y &&
aboveY < otherMonitor.y + otherMonitor.height) {
haveTopLeftCorner = false;
break;
}
}
if (!haveTopLeftCorner)
for (let j = 0; j < this.monitors.length; j++) {
if (i == j)
continue;
let otherMonitor = this.monitors[j];
if (besideX >= otherMonitor.x &&
besideX < otherMonitor.x + otherMonitor.width &&
besideY >= otherMonitor.y &&
besideY < otherMonitor.y + otherMonitor.height) {
haveTopLeftCorner = false;
break;
}
if (aboveX >= otherMonitor.x &&
aboveX < otherMonitor.x + otherMonitor.width &&
aboveY >= otherMonitor.y &&
aboveY < otherMonitor.y + otherMonitor.height) {
haveTopLeftCorner = false;
break;
}
}
let corner = new HotCorner(this, cornerX, cornerY);
corner.setBarrierSize(size);
this.hotCorners.push(corner);
}
if (!haveTopLeftCorner)
continue;
this.emit('hot-corners-changed');
let corner = new HotCorner(this);
this._hotCorners.push(corner);
corner.actor.set_position(cornerX, cornerY);
this.addChrome(corner.actor);
}
},
_createBackground: function(monitorIndex) {
@@ -399,15 +394,16 @@ const LayoutManager = new Lang.Class({
},
_panelBoxChanged: function() {
this._updatePanelBarrier();
let size = this.panelBox.height;
this.hotCorners.forEach(function(corner) {
corner.setBarrierSize(size);
});
this.emit('panel-box-changed');
this._updatePanelBarriers();
},
_updatePanelBarrier: function() {
_updatePanelBarriers: function() {
if (this._leftPanelBarrier) {
this._leftPanelBarrier.destroy();
this._leftPanelBarrier = null;
}
if (this._rightPanelBarrier) {
this._rightPanelBarrier.destroy();
this._rightPanelBarrier = null;
@@ -416,6 +412,10 @@ const LayoutManager = new Lang.Class({
if (this.panelBox.height) {
let primary = this.primaryMonitor;
this._leftPanelBarrier = new Meta.Barrier({ display: global.display,
x1: primary.x, y1: primary.y,
x2: primary.x, y2: primary.y + this.panelBox.height,
directions: Meta.BarrierDirection.POSITIVE_X });
this._rightPanelBarrier = new Meta.Barrier({ display: global.display,
x1: primary.x + primary.width, y1: primary.y,
x2: primary.x + primary.width, y2: primary.y + this.panelBox.height,
@@ -423,41 +423,28 @@ const LayoutManager = new Lang.Class({
}
},
_setupTrayPressure: function() {
this._trayPressure = new PressureBarrier(MESSAGE_TRAY_PRESSURE_THRESHOLD,
MESSAGE_TRAY_PRESSURE_TIMEOUT,
Shell.KeyBindingMode.NORMAL |
Shell.KeyBindingMode.OVERVIEW);
this._trayPressure.setEventFilter(this._trayBarrierEventFilter);
this._trayPressure.connect('trigger', function(barrier) {
Main.messageTray.openTray();
});
},
_updateTrayBarrier: function() {
let monitor = this.bottomMonitor;
if (this._trayBarrier) {
this._trayPressure.removeBarrier(this._trayBarrier);
this._trayBarrier.destroy();
this._trayBarrier = null;
}
if (this._trayPressure) {
this._trayPressure.destroy();
this._trayPressure = null;
}
this._trayBarrier = new Meta.Barrier({ display: global.display,
x1: monitor.x, x2: monitor.x + monitor.width,
y1: monitor.y + monitor.height, y2: monitor.y + monitor.height,
directions: Meta.BarrierDirection.NEGATIVE_Y });
this._trayPressure.addBarrier(this._trayBarrier);
},
_trayBarrierEventFilter: function(event) {
// Throw out all events where the pointer was grabbed by another
// client, as the client that grabbed the pointer expects to have
// complete control over it
if (event.grabbed && Main.modalCount == 0)
return true;
return false;
this._trayPressure = new PressureBarrier(this._trayBarrier, MESSAGE_TRAY_PRESSURE_THRESHOLD, MESSAGE_TRAY_PRESSURE_TIMEOUT);
this._trayPressure.connect('trigger', function(barrier) {
Main.messageTray.openTray();
});
},
_monitorsChanged: function() {
@@ -649,9 +636,9 @@ const LayoutManager = new Lang.Class({
if (!Main.sessionMode.isGreeter)
this._createSecondaryBackgrounds();
this._queueUpdateRegions();
this.emit('panel-box-changed');
this.emit('startup-complete');
this._queueUpdateRegions();
},
showKeyboard: function () {
@@ -1102,23 +1089,54 @@ Signals.addSignalMethods(LayoutManager.prototype);
const HotCorner = new Lang.Class({
Name: 'HotCorner',
_init : function(layoutManager, x, y) {
_init : function(layoutManager) {
// We use this flag to mark the case where the user has entered the
// hot corner and has not left both the hot corner and a surrounding
// guard area (the "environs"). This avoids triggering the hot corner
// multiple times due to an accidental jitter.
this._entered = false;
this._x = x;
this._y = y;
this.actor = new Clutter.Actor({ name: 'hot-corner-environs',
width: 3,
height: 3,
reactive: true });
this._setupFallbackCornerIfNeeded();
this._corner = new Clutter.Rectangle({ name: 'hot-corner',
width: 1,
height: 1,
opacity: 0,
reactive: true });
this._corner._delegate = this;
this._pressureBarrier = new PressureBarrier(HOT_CORNER_PRESSURE_THRESHOLD,
HOT_CORNER_PRESSURE_TIMEOUT,
Shell.KeyBindingMode.NORMAL |
Shell.KeyBindingMode.OVERVIEW);
this._pressureBarrier.connect('trigger', Lang.bind(this, this._toggleOverview));
this.actor.add_child(this._corner);
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
this._corner.set_position(this.actor.width - this._corner.width, 0);
this.actor.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
} else {
this._corner.set_position(0, 0);
}
this._activationTime = 0;
this.actor.connect('leave-event',
Lang.bind(this, this._onEnvironsLeft));
// Clicking on the hot corner environs should result in the
// same behavior as clicking on the hot corner.
this.actor.connect('button-release-event',
Lang.bind(this, this._onCornerClicked));
// In addition to being triggered by the mouse enter event,
// the hot corner can be triggered by clicking on it. This is
// useful if the user wants to undo the effect of triggering
// the hot corner once in the hot corner.
this._corner.connect('enter-event',
Lang.bind(this, this._onCornerEntered));
this._corner.connect('button-release-event',
Lang.bind(this, this._onCornerClicked));
this._corner.connect('leave-event',
Lang.bind(this, this._onCornerLeft));
// Cache the three ripples instead of dynamically creating and destroying them.
this._ripple1 = new St.BoxLayout({ style_class: 'ripple-box', opacity: 0, visible: false });
@@ -1130,74 +1148,8 @@ const HotCorner = new Lang.Class({
layoutManager.uiGroup.add_actor(this._ripple3);
},
setBarrierSize: function(size) {
if (this._verticalBarrier) {
this._pressureBarrier.removeBarrier(this._verticalBarrier);
this._verticalBarrier.destroy();
this._verticalBarrier = null;
}
if (this._horizontalBarrier) {
this._pressureBarrier.removeBarrier(this._horizontalBarrier);
this._horizontalBarrier.destroy();
this._horizontalBarrier = null;
}
if (size > 0) {
this._verticalBarrier = new Meta.Barrier({ display: global.display,
x1: this._x, x2: this._x, y1: this._y, y2: this._y + size,
directions: Meta.BarrierDirection.POSITIVE_X });
this._horizontalBarrier = new Meta.Barrier({ display: global.display,
x1: this._x, x2: this._x + size, y1: this._y, y2: this._y,
directions: Meta.BarrierDirection.POSITIVE_Y });
this._pressureBarrier.addBarrier(this._verticalBarrier);
this._pressureBarrier.addBarrier(this._horizontalBarrier);
}
},
_setupFallbackCornerIfNeeded: function() {
if (!global.display.supports_extended_barriers()) {
this.actor = new Clutter.Actor({ name: 'hot-corner-environs',
x: x, y: y,
width: 3,
height: 3,
reactive: true });
this._corner = new Clutter.Rectangle({ name: 'hot-corner',
width: 1,
height: 1,
opacity: 0,
reactive: true });
this._corner._delegate = this;
this.actor.add_child(this._corner);
layoutManager.addChrome(this.actor);
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
this._corner.set_position(this.actor.width - this._corner.width, 0);
this.actor.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
} else {
this._corner.set_position(0, 0);
}
this.actor.connect('leave-event',
Lang.bind(this, this._onEnvironsLeft));
this._corner.connect('enter-event',
Lang.bind(this, this._onCornerEntered));
this._corner.connect('leave-event',
Lang.bind(this, this._onCornerLeft));
}
},
destroy: function() {
this.setBarrierSize(0);
this._pressureBarrier.destroy();
this._pressureBarrier = null;
if (this.actor)
this.actor.destroy();
this.actor.destroy();
},
_animRipple : function(ripple, delay, time, startScale, startOpacity, finalScale) {
@@ -1217,8 +1169,9 @@ const HotCorner = new Lang.Class({
ripple.opacity = 255 * Math.sqrt(startOpacity);
ripple.scale_x = ripple.scale_y = startScale;
ripple.x = this._x;
ripple.y = this._y;
let [x, y] = this._corner.get_transformed_position();
ripple.x = x;
ripple.y = y;
Tweener.addTween(ripple, { _opacity: 0,
scale_x: finalScale,
@@ -1230,7 +1183,7 @@ const HotCorner = new Lang.Class({
onComplete: function() { ripple.visible = false; } });
},
_rippleAnimation: function() {
rippleAnimation: function() {
// Show three concentric ripples expanding outwards; the exact
// parameters were found by trial and error, so don't look
// for them to make perfect sense mathematically
@@ -1241,28 +1194,37 @@ const HotCorner = new Lang.Class({
this._animRipple(this._ripple3, 0.35, 1.0, 0.0, 0.3, 1);
},
_toggleOverview: function() {
if (Main.overview.shouldToggleByCornerOrButton()) {
this._rippleAnimation();
Main.overview.toggle();
}
},
handleDragOver: function(source, actor, x, y, time) {
if (source != Main.xdndHandler)
return DND.DragMotionResult.CONTINUE;
if (!Main.overview.visible && !Main.overview.animationInProgress) {
this.rippleAnimation();
Main.overview.showTemporarily();
}
return DND.DragMotionResult.CONTINUE;
},
_onCornerEntered : function() {
if (!this._entered) {
this._entered = true;
this._toggleOverview();
if (!Main.overview.animationInProgress) {
this._activationTime = Date.now() / 1000;
this.rippleAnimation();
Main.overview.toggle();
}
}
return false;
},
_onCornerClicked : function() {
if (this.shouldToggleOverviewOnClick())
Main.overview.toggle();
return true;
},
_onCornerLeft : function(actor, event) {
if (event.get_related() != this.actor)
this._entered = false;
@@ -1274,47 +1236,42 @@ const HotCorner = new Lang.Class({
if (event.get_related() != this._corner)
this._entered = false;
return false;
},
// Checks if the Activities button is currently sensitive to
// clicks. The first call to this function within the
// HOT_CORNER_ACTIVATION_TIMEOUT time of the hot corner being
// triggered will return false. This avoids opening and closing
// the overview if the user both triggered the hot corner and
// clicked the Activities button.
shouldToggleOverviewOnClick: function() {
if (Main.overview.animationInProgress)
return false;
if (this._activationTime == 0 || Date.now() / 1000 - this._activationTime > HOT_CORNER_ACTIVATION_TIMEOUT)
return true;
return false;
}
});
const PressureBarrier = new Lang.Class({
Name: 'PressureBarrier',
_init: function(threshold, timeout, keybindingMode) {
_init: function(barrier, threshold, timeout) {
this._barrier = barrier;
this._threshold = threshold;
this._timeout = timeout;
this._keybindingMode = keybindingMode;
this._barriers = [];
this._eventFilter = null;
this._orientation = (barrier.y1 == barrier.y2) ? Clutter.Orientation.HORIZONTAL : Clutter.Orientation.VERTICAL;
this._isTriggered = false;
this._reset();
},
addBarrier: function(barrier) {
barrier._pressureHitId = barrier.connect('hit', Lang.bind(this, this._onBarrierHit));
barrier._pressureLeftId = barrier.connect('left', Lang.bind(this, this._onBarrierLeft));
this._barriers.push(barrier);
},
_disconnectBarrier: function(barrier) {
barrier.disconnect(barrier._pressureHitId);
barrier.disconnect(barrier._pressureLeftId);
},
removeBarrier: function(barrier) {
this._disconnectBarrier(barrier);
this._barriers.splice(this._barriers.indexOf(barrier), 1);
this._barrierHitId = this._barrier.connect('hit', Lang.bind(this, this._onBarrierHit));
this._barrierLeftId = this._barrier.connect('left', Lang.bind(this, this._onBarrierLeft));
},
destroy: function() {
this._barriers.forEach(Lang.bind(this, this._disconnectBarrier));
this._barriers = [];
},
setEventFilter: function(filter) {
this._eventFilter = filter;
this._barrier.disconnect(this._barrierHitId);
this._barrier.disconnect(this._barrierLeftId);
this._barrier = null;
},
_reset: function() {
@@ -1323,34 +1280,33 @@ const PressureBarrier = new Lang.Class({
this._lastTime = 0;
},
_isHorizontal: function(barrier) {
return barrier.y1 == barrier.y2;
},
_getDistanceAcrossBarrier: function(barrier, event) {
if (this._isHorizontal(barrier))
_getDistanceAcrossBarrier: function(event) {
if (this._orientation == Clutter.Orientation.HORIZONTAL)
return Math.abs(event.dy);
else
return Math.abs(event.dx);
},
_getDistanceAlongBarrier: function(barrier, event) {
if (this._isHorizontal(barrier))
_getDistanceAlongBarrier: function(event) {
if (this._orientation == Clutter.Orientation.HORIZONTAL)
return Math.abs(event.dx);
else
return Math.abs(event.dy);
},
_isBarrierEventTooOld: function(event) {
// Ignore all events older than this time
let threshold = this._lastTime - this._timeout;
return event.time < threshold;
},
_trimBarrierEvents: function() {
// Events are guaranteed to be sorted in time order from
// oldest to newest, so just look for the first old event,
// and then chop events after that off.
let i = 0;
let threshold = this._lastTime - this._timeout;
while (i < this._barrierEvents.length) {
let [time, distance] = this._barrierEvents[i];
if (time >= threshold)
if (!this._isBarrierEventTooOld(this._barrierEvents[i]))
break;
i++;
}
@@ -1358,8 +1314,7 @@ const PressureBarrier = new Lang.Class({
let firstNewEvent = i;
for (i = 0; i < firstNewEvent; i++) {
let [time, distance] = this._barrierEvents[i];
this._currentPressure -= distance;
this._currentPressure -= this._getDistanceAcrossBarrier(this._barrierEvents[i]);
}
this._barrierEvents = this._barrierEvents.slice(firstNewEvent);
@@ -1367,30 +1322,29 @@ const PressureBarrier = new Lang.Class({
_onBarrierLeft: function(barrier, event) {
this._reset();
this._isTriggered = false;
},
_trigger: function() {
this._isTriggered = true;
this.emit('trigger');
this._reset();
},
_onBarrierHit: function(barrier, event) {
// If we've triggered the barrier, wait until the pointer has the
// left the barrier hitbox until we trigger it again.
if (this._isTriggered)
// Throw out all events where the pointer was grabbed by another
// client, as the client that grabbed the pointer expects to have
// complete control over it
if (event.grabbed && Main.modalCount == 0)
return;
if (this._eventFilter && this._eventFilter(event))
let isOverview = ((Main.keybindingMode & (Shell.KeyBindingMode.OVERVIEW)) != 0);
// Throw out events where the grab is taken by something that's
// not the overview (modal dialogs, etc.)
if (event.grabbed && !isOverview)
return;
// Throw out all events not in the proper keybinding mode
if (!(this._keybindingMode & Main.keybindingMode))
return;
let slide = this._getDistanceAlongBarrier(barrier, event);
let distance = this._getDistanceAcrossBarrier(barrier, event);
let slide = this._getDistanceAlongBarrier(event);
let distance = this._getDistanceAcrossBarrier(event);
if (distance >= this._threshold) {
this._trigger();
@@ -1406,10 +1360,8 @@ const PressureBarrier = new Lang.Class({
this._lastTime = event.time;
this._trimBarrierEvents();
distance = Math.min(15, distance);
this._barrierEvents.push([event.time, distance]);
this._currentPressure += distance;
this._barrierEvents.push(event);
this._currentPressure += Math.min(15, distance);
if (this._currentPressure >= this._threshold)
this._trigger();

View File

@@ -58,7 +58,7 @@ let shellDBusService = null;
let shellMountOpDBusService = null;
let screenSaverDBus = null;
let modalCount = 0;
let keybindingMode = Shell.KeyBindingMode.NONE;
let keybindingMode = Shell.KeyBindingMode.NORMAL;
let modalActorFocusStack = [];
let uiGroup = null;
let magnifier = null;
@@ -91,6 +91,9 @@ function start() {
global.logError = window.log;
global.log = window.log;
// Hide the stage until we're ready for it
global.stage.hide();
// Chain up async errors reported from C
global.connect('notify-error', function (global, msg, detail) { notifyError(msg, detail); });
@@ -145,6 +148,8 @@ function startSession() {
else
screenShield = new ScreenShield.ScreenShieldFallback();
// The message tray relies on being constructed
// after the panel.
panel = new Panel.Panel();
messageTray = new MessageTray.MessageTray();
keyboard = new Keyboard.Keyboard();
@@ -159,6 +164,8 @@ function startSession() {
global.screen.override_workspace_layout(Meta.ScreenCorner.TOPLEFT,
false, -1, 1);
global.display.connect('overlay-key', Lang.bind(overview, overview.toggle));
sessionMode.connect('updated', _sessionUpdated);
_sessionUpdated();
// Provide the bus object for gnome-session to
// initiate logouts.
@@ -195,14 +202,6 @@ function startSession() {
layoutManager.connect('startup-prepared', function() {
layoutManager.startupAnimation();
});
layoutManager.connect('startup-complete', function() {
if (keybindingMode == Shell.KeyBindingMode.NONE) {
keybindingMode = Shell.KeyBindingMode.NORMAL;
}
sessionMode.connect('updated', _sessionUpdated);
_sessionUpdated();
});
}
let _workspaces = [];

View File

@@ -1672,13 +1672,14 @@ const MessageTray = new Lang.Class({
Main.layoutManager.trackChrome(this._closeButton);
Main.layoutManager.connect('primary-fullscreen-changed', Lang.bind(this, this._onFullscreenChanged));
Main.layoutManager.connect('hot-corners-changed', Lang.bind(this, this._hotCornersChanged));
// If the overview shows or hides while we're in
// the message tray, revert back to normal mode.
Main.overview.connect('showing', Lang.bind(this, this._escapeTray));
Main.overview.connect('hiding', Lang.bind(this, this._escapeTray));
// Track if we've added the activities button
this._activitiesButtonAdded = false;
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
Main.wm.addKeybinding('toggle-message-tray',
@@ -1702,7 +1703,6 @@ const MessageTray = new Lang.Class({
this._trayDwellTimeoutId = 0;
this._setupTrayDwellIfNeeded();
this._sessionUpdated();
this._hotCornersChanged();
this._noMessages = new St.Label({ text: _("No Messages"),
style_class: 'no-messages-label',
@@ -1775,6 +1775,11 @@ const MessageTray = new Lang.Class({
},
_sessionUpdated: function() {
if (!this._activitiesButtonAdded && Main.panel.statusArea.activities) {
this._activitiesButtonAdded = true;
this._grabHelper.addActor(Main.panel.statusArea.activities.hotCorner.actor);
}
if ((Main.sessionMode.isLocked || Main.sessionMode.isGreeter) && this._inCtrlAltTab) {
Main.ctrlAltTabManager.removeGroup(this._summary);
this._inCtrlAltTab = false;
@@ -2096,13 +2101,6 @@ const MessageTray = new Lang.Class({
this._updateState();
},
_hotCornersChanged: function() {
let primary = Main.layoutManager.primaryIndex;
let corner = Main.layoutManager.hotCorners[primary];
if (corner && corner.actor)
this._grabHelper.addActor(corner.actor);
},
_onTrayHoverChanged: function() {
if (this.actor.hover) {
// No dwell inside notifications at the bottom of the screen

View File

@@ -33,8 +33,6 @@ const SHADE_ANIMATION_TIME = .20;
const DND_WINDOW_SWITCH_TIMEOUT = 1250;
const OVERVIEW_ACTIVATION_TIMEOUT = 0.5;
const ShellInfo = new Lang.Class({
Name: 'ShellInfo',
@@ -148,10 +146,9 @@ const Overview = new Lang.Class({
this._backgroundGroup.hide();
this._bgManagers = [];
this._activationTime = 0;
this.visible = false; // animating to overview, in overview, animating out
this._shown = false; // show() and not hide()
this._shownTemporarily = false; // showTemporarily() and not hideTemporarily()
this._modal = false; // have a modal grab
this.animationInProgress = false;
this.visibleTarget = false;
@@ -349,22 +346,18 @@ const Overview = new Lang.Class({
},
_onDragBegin: function() {
this._inXdndDrag = true;
DND.addDragMonitor(this._dragMonitor);
// Remember the workspace we started from
this._lastActiveWorkspaceIndex = global.screen.get_active_workspace_index();
},
_onDragEnd: function(time) {
this._inXdndDrag = false;
// In case the drag was canceled while in the overview
// we have to go back to where we started and hide
// the overview
if (this._shown) {
if (this._shownTemporarily) {
global.screen.get_workspace_by_index(this._lastActiveWorkspaceIndex).activate(time);
this.hide();
this.hideTemporarily();
}
this._resetWindowSwitchTimeout();
this._lastHoveredWindow = null;
@@ -412,7 +405,7 @@ const Overview = new Lang.Class({
this._needsFakePointerEvent = true;
Main.activateWindow(dragEvent.targetActor._delegate.metaWindow,
this._windowSwitchTimestamp);
this.hide();
this.hideTemporarily();
this._lastHoveredWindow = null;
}));
}
@@ -508,10 +501,9 @@ const Overview = new Lang.Class({
if (this._shown)
return;
this._shown = true;
if (!this._syncInputMode())
this._syncInputMode();
if (!this._modal)
return;
this._animateVisible();
},
@@ -544,7 +536,6 @@ const Overview = new Lang.Class({
this.visible = true;
this.animationInProgress = true;
this.visibleTarget = true;
this._activationTime = Date.now() / 1000;
// All the the actors in the window group are completely obscured,
// hiding the group holding them while the Overview is displayed greatly
@@ -577,6 +568,24 @@ const Overview = new Lang.Class({
this.emit('showing');
},
// showTemporarily:
//
// Animates the overview visible without grabbing mouse and keyboard input;
// if show() has already been called, this has no immediate effect, but
// will result in the overview not being hidden until hideTemporarily() is
// called.
showTemporarily: function() {
if (this.isDummy)
return;
if (this._shownTemporarily)
return;
this._syncInputMode();
this._animateVisible();
this._shownTemporarily = true;
},
// hide:
//
// Reverses the effect of show()
@@ -590,12 +599,30 @@ const Overview = new Lang.Class({
if (this._controlPressed)
return;
this._animateNotVisible();
if (!this._shownTemporarily)
this._animateNotVisible();
this._shown = false;
this._syncInputMode();
},
// hideTemporarily:
//
// Reverses the effect of showTemporarily()
hideTemporarily: function() {
if (this.isDummy)
return;
if (!this._shownTemporarily)
return;
if (!this._shown)
this._animateNotVisible();
this._shownTemporarily = false;
this._syncInputMode();
},
toggle: function() {
if (this.isDummy)
return;
@@ -606,20 +633,6 @@ const Overview = new Lang.Class({
this.show();
},
// Checks if the Activities button is currently sensitive to
// clicks. The first call to this function within the
// OVERVIEW_ACTIVATION_TIMEOUT time of the hot corner being
// triggered will return false. This avoids opening and closing
// the overview if the user both triggered the hot corner and
// clicked the Activities button.
shouldToggleByCornerOrButton: function() {
if (this.animationInProgress)
return false;
if (this._activationTime == 0 || Date.now() / 1000 - this._activationTime > OVERVIEW_ACTIVATION_TIMEOUT)
return true;
return false;
},
//// Private methods ////
_syncInputMode: function() {
@@ -627,23 +640,22 @@ const Overview = new Lang.Class({
// overview we don't have a problem with the release of a press/release
// going to an application.
if (this.animationInProgress)
return true;
return;
if (this._shown) {
let shouldBeModal = !this._inXdndDrag;
if (shouldBeModal) {
if (!this._modal) {
if (Main.pushModal(this._overview,
{ keybindingMode: Shell.KeyBindingMode.OVERVIEW })) {
this._modal = true;
} else {
this.hide();
return false;
}
}
} else {
global.stage_input_mode = Shell.StageInputMode.FULLSCREEN;
if (!this._modal) {
if (Main.pushModal(this._overview,
{ keybindingMode: Shell.KeyBindingMode.OVERVIEW }))
this._modal = true;
else
this.hide();
}
} else if (this._shownTemporarily) {
if (this._modal) {
Main.popModal(this._overview);
this._modal = false;
}
global.stage_input_mode = Shell.StageInputMode.FULLSCREEN;
} else {
if (this._modal) {
Main.popModal(this._overview);
@@ -652,7 +664,6 @@ const Overview = new Lang.Class({
else if (global.stage_input_mode == Shell.StageInputMode.FULLSCREEN)
global.stage_input_mode = Shell.StageInputMode.NORMAL;
}
return true;
},
_animateNotVisible: function() {
@@ -686,7 +697,7 @@ const Overview = new Lang.Class({
this.emit('shown');
// Handle any calls to hide* while we were showing
if (!this._shown)
if (!this._shown && !this._shownTemporarily)
this._animateNotVisible();
this._syncInputMode();
@@ -712,7 +723,7 @@ const Overview = new Lang.Class({
this.emit('hidden');
// Handle any calls to show* while we were hiding
if (this._shown)
if (this._shown || this._shownTemporarily)
this._animateVisible();
this._syncInputMode();

View File

@@ -452,11 +452,9 @@ const MessagesIndicator = new Lang.Class({
_updateCount: function() {
let count = 0;
let hasChats = false;
this._sources.forEach(Lang.bind(this,
function(source) {
count += source.indicatorCount;
hasChats |= source.isChat;
}));
this._count = count;
@@ -464,7 +462,6 @@ const MessagesIndicator = new Lang.Class({
"%d new messages",
count).format(count);
this._icon.visible = hasChats;
this._updateVisibility();
},

View File

@@ -18,6 +18,7 @@ const Atk = imports.gi.Atk;
const Config = imports.misc.config;
const CtrlAltTab = imports.ui.ctrlAltTab;
const DND = imports.ui.dnd;
const Layout = imports.ui.layout;
const Overview = imports.ui.overview;
const PopupMenu = imports.ui.popupMenu;
const PanelMenu = imports.ui.panelMenu;
@@ -629,15 +630,23 @@ const ActivitiesButton = new Lang.Class({
this.parent(0.0, null, true);
this.actor.accessible_role = Atk.Role.TOGGLE_BUTTON;
let container = new Shell.GenericContainer();
container.connect('get-preferred-width', Lang.bind(this, this._containerGetPreferredWidth));
container.connect('get-preferred-height', Lang.bind(this, this._containerGetPreferredHeight));
container.connect('allocate', Lang.bind(this, this._containerAllocate));
this.actor.add_actor(container);
this.actor.name = 'panelActivities';
/* Translators: If there is no suitable word for "Activities"
in your language, you can use the word for "Overview". */
this._label = new St.Label({ text: _("Activities") });
this.actor.add_actor(this._label);
container.add_actor(this._label);
this.actor.label_actor = this._label;
this.hotCorner = new Layout.HotCorner(Main.layoutManager);
container.add_actor(this.hotCorner.actor);
this.actor.connect('captured-event', Lang.bind(this, this._onCapturedEvent));
this.actor.connect_after('button-release-event', Lang.bind(this, this._onButtonRelease));
this.actor.connect_after('key-release-event', Lang.bind(this, this._onKeyRelease));
@@ -652,6 +661,44 @@ const ActivitiesButton = new Lang.Class({
}));
this._xdndTimeOut = 0;
// Since the hot corner uses stage coordinates, Clutter won't
// queue relayouts for us when the panel moves. Queue a relayout
// when that happens.
Main.layoutManager.connect('panel-box-changed', Lang.bind(this, function() {
container.queue_relayout();
}));
},
_containerGetPreferredWidth: function(actor, forHeight, alloc) {
[alloc.min_size, alloc.natural_size] = this._label.get_preferred_width(forHeight);
},
_containerGetPreferredHeight: function(actor, forWidth, alloc) {
[alloc.min_size, alloc.natural_size] = this._label.get_preferred_height(forWidth);
},
_containerAllocate: function(actor, box, flags) {
this._label.allocate(box, flags);
// The hot corner needs to be outside any padding/alignment
// that has been imposed on us
let primary = Main.layoutManager.primaryMonitor;
let hotBox = new Clutter.ActorBox();
let ok, x, y;
if (actor.get_text_direction() == Clutter.TextDirection.LTR) {
[ok, x, y] = actor.transform_stage_point(primary.x, primary.y)
} else {
[ok, x, y] = actor.transform_stage_point(primary.x + primary.width, primary.y);
// hotCorner.actor has northeast gravity, so we don't need
// to adjust x for its width
}
hotBox.x1 = Math.round(x);
hotBox.x2 = hotBox.x1 + this.hotCorner.actor.width;
hotBox.y1 = Math.round(y);
hotBox.y2 = hotBox.y1 + this.hotCorner.actor.height;
this.hotCorner.actor.allocate(hotBox, flags);
},
handleDragOver: function(source, actor, x, y, time) {
@@ -661,14 +708,14 @@ const ActivitiesButton = new Lang.Class({
if (this._xdndTimeOut != 0)
Mainloop.source_remove(this._xdndTimeOut);
this._xdndTimeOut = Mainloop.timeout_add(BUTTON_DND_ACTIVATION_TIMEOUT,
Lang.bind(this, this._xdndToggleOverview, actor));
Lang.bind(this, this._xdndShowOverview, actor));
return DND.DragMotionResult.CONTINUE;
},
_onCapturedEvent: function(actor, event) {
if (event.type() == Clutter.EventType.BUTTON_PRESS) {
if (!Main.overview.shouldToggleByCornerOrButton())
if (!this.hotCorner.shouldToggleOverviewOnClick())
return true;
}
return false;
@@ -685,12 +732,15 @@ const ActivitiesButton = new Lang.Class({
}
},
_xdndToggleOverview: function(actor) {
_xdndShowOverview: function(actor) {
let [x, y, mask] = global.get_pointer();
let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);
if (pickedActor == this.actor && Main.overview.shouldToggleByCornerOrButton())
Main.overview.toggle();
if (pickedActor == this.actor) {
if (!Main.overview.visible && !Main.overview.animationInProgress) {
Main.overview.showTemporarily();
}
}
Mainloop.source_remove(this._xdndTimeOut);
this._xdndTimeOut = 0;

View File

@@ -337,14 +337,14 @@ const InputSourceIndicator = new Lang.Class({
Main.wm.addKeybinding('switch-input-source',
new Gio.Settings({ schema: "org.gnome.desktop.wm.keybindings" }),
Meta.KeyBindingFlags.REVERSES,
Shell.KeyBindingMode.ALL & ~Shell.KeyBindingMode.MESSAGE_TRAY,
Shell.KeyBindingMode.ALL,
Lang.bind(this, this._switchInputSource));
this._keybindingActionBackward =
Main.wm.addKeybinding('switch-input-source-backward',
new Gio.Settings({ schema: "org.gnome.desktop.wm.keybindings" }),
Meta.KeyBindingFlags.REVERSES |
Meta.KeyBindingFlags.REVERSED,
Shell.KeyBindingMode.ALL & ~Shell.KeyBindingMode.MESSAGE_TRAY,
Shell.KeyBindingMode.ALL,
Lang.bind(this, this._switchInputSource));
this._settings = new Gio.Settings({ schema: DESKTOP_INPUT_SOURCES_SCHEMA });
this._settings.connect('changed::' + KEY_CURRENT_INPUT_SOURCE, Lang.bind(this, this._currentInputSourceChanged));

View File

@@ -174,15 +174,6 @@ const WindowManager = new Lang.Class({
Meta.KeyBindingFlags.NONE,
Shell.KeyBindingMode.NORMAL,
Lang.bind(this, this._openAppMenu));
Main.overview.connect('showing', Lang.bind(this, function() {
for (let i = 0; i < this._dimmedWindows.length; i++)
this._undimWindow(this._dimmedWindows[i]);
}));
Main.overview.connect('hiding', Lang.bind(this, function() {
for (let i = 0; i < this._dimmedWindows.length; i++)
this._dimWindow(this._dimmedWindows[i]);
}));
},
setCustomKeybindingHandler: function(name, modes, handler) {
@@ -342,15 +333,13 @@ const WindowManager = new Lang.Class({
if (shouldDim && !window._dimmed) {
window._dimmed = true;
this._dimmedWindows.push(window);
if (!Main.overview.visible)
this._dimWindow(window);
this._dimWindow(window);
} else if (!shouldDim && window._dimmed) {
window._dimmed = false;
this._dimmedWindows = this._dimmedWindows.filter(function(win) {
return win != window;
});
if (!Main.overview.visible)
this._undimWindow(window);
this._undimWindow(window);
}
},

View File

@@ -15,6 +15,7 @@ const Main = imports.ui.main;
const Overview = imports.ui.overview;
const Panel = imports.ui.panel;
const Tweener = imports.ui.tweener;
const WindowManager = imports.ui.windowManager;
const FOCUS_ANIMATION_TIME = 0.15;
@@ -41,6 +42,68 @@ function _interpolate(start, end, step) {
return start + (end - start) * step;
}
const WindowCloneLayout = new Lang.Class({
Name: 'WindowCloneLayout',
Extends: Clutter.LayoutManager,
_init: function(boundingBox) {
this.parent();
this._boundingBox = boundingBox;
},
get boundingBox() {
return this._boundingBox;
},
set boundingBox(b) {
this._boundingBox = b;
this.layout_changed();
},
_makeBoxForWindow: function(window) {
// We need to adjust the position of the actor because of the
// consequences of invisible borders -- in reality, the texture
// has an extra set of "padding" around it that we need to trim
// down.
// The outer rect (from which we compute the bounding box)
// paradoxically is the smaller rectangle, containing the positions
// of the visible frame. The input rect contains everything,
// including the invisible border padding.
let inputRect = window.get_input_rect();
let box = new Clutter.ActorBox();
box.set_origin(inputRect.x - this._boundingBox.x,
inputRect.y - this._boundingBox.y);
box.set_size(inputRect.width, inputRect.height);
return box;
},
vfunc_get_preferred_height: function(container, forWidth) {
return [this._boundingBox.height, this._boundingBox.height];
},
vfunc_get_preferred_width: function(container, forHeight) {
return [this._boundingBox.width, this._boundingBox.width];
},
vfunc_allocate: function(container, box, flags) {
let clone = container.get_children().forEach(function (child) {
let realWindow;
if (child == container._delegate._windowClone)
realWindow = container._delegate.realWindow;
else
realWindow = child.source;
child.allocate(this._makeBoxForWindow(realWindow.meta_window),
flags);
}, this);
},
});
const WindowClone = new Lang.Class({
Name: 'WindowClone',
@@ -50,10 +113,7 @@ const WindowClone = new Lang.Class({
this.metaWindow._delegate = this;
this._workspace = workspace;
let [borderX, borderY] = this._getInvisibleBorderPadding();
this._windowClone = new Clutter.Clone({ source: realWindow.get_texture(),
x: -borderX,
y: -borderY });
this._windowClone = new Clutter.Clone({ source: realWindow.get_texture() });
// We expect this.actor to be used for all interaction rather than
// this._windowClone; as the former is reactive and the latter
// is not, this just works for most cases. However, for DND all
@@ -61,20 +121,15 @@ const WindowClone = new Lang.Class({
// To avoid this, we hide it from pick.
Shell.util_set_hidden_from_pick(this._windowClone, true);
this.origX = realWindow.x + borderX;
this.origY = realWindow.y + borderY;
let outerRect = realWindow.meta_window.get_outer_rect();
// The MetaShapedTexture that we clone has a size that includes
// the invisible border; this is inconvenient; rather than trying
// to compensate all over the place we insert a ClutterActor into
// to compensate all over the place we insert a custom container into
// the hierarchy that is sized to only the visible portion.
// As usual, we cannot use a ShellGenericContainer or StWidget here,
// because Workspace plays dirty tricks with reparenting to do DNDs
// and scroll-to-zoom.
this.actor = new Clutter.Actor({ reactive: true,
x: this.origX,
y: this.origY,
width: outerRect.width,
height: outerRect.height });
layout_manager: new WindowCloneLayout() });
this.actor.add_child(this._windowClone);
@@ -84,10 +139,19 @@ const WindowClone = new Lang.Class({
this._dragSlot = [0, 0, 0, 0];
this._stackAbove = null;
this._sizeChangedId = this.realWindow.connect('size-changed',
this._windowClone._updateId = this.realWindow.connect('size-changed',
Lang.bind(this, this._onRealWindowSizeChanged));
this._realWindowDestroyId = this.realWindow.connect('destroy',
Lang.bind(this, this._disconnectRealWindowSignals));
this._windowClone._destroyId = this.realWindow.connect('destroy', Lang.bind(this, function() {
// First destroy the clone and then destroy everything
// This will ensure that we never see it in the _disconnectSignals loop
this._windowClone.destroy();
this.destroy();
}));
this._updateAttachedDialogs();
this._computeBoundingBox();
this.actor.x = this._boundingBox.x;
this.actor.y = this._boundingBox.y;
let clickAction = new Clutter.ClickAction();
clickAction.connect('clicked', Lang.bind(this, this._onClicked));
@@ -121,6 +185,97 @@ const WindowClone = new Lang.Class({
return this._slot;
},
deleteAll: function() {
// Delete all windows, starting from the bottom-most (most-modal) one
let windows = this.actor.get_children();
for (let i = windows.length - 1; i >= 1; i--) {
let realWindow = windows[i].source;
let metaWindow = realWindow.meta_window;
metaWindow.delete(global.get_current_time());
}
this.metaWindow.delete(global.get_current_time());
},
addAttachedDialog: function(win) {
this._doAddAttachedDialog(win, win.get_compositor_private());
this._computeBoundingBox();
this._updateDimmer();
this.emit('size-changed');
},
_doAddAttachedDialog: function(metaWin, realWin) {
let clone = new Clutter.Clone({ source: realWin });
clone._updateId = realWin.connect('size-changed', Lang.bind(this, function() {
this._computeBoundingBox();
this.emit('size-changed');
}));
clone._destroyId = realWin.connect('destroy', Lang.bind(this, function() {
clone.destroy();
this._computeBoundingBox();
this._updateDimmer();
this.emit('size-changed');
}));
this.actor.add_child(clone);
},
_updateAttachedDialogs: function() {
let iter = Lang.bind(this, function(win) {
let actor = win.get_compositor_private();
if (!actor)
return false;
if (!win.is_attached_dialog())
return false;
this._doAddAttachedDialog(win, actor);
win.foreach_transient(iter);
return true;
});
this.metaWindow.foreach_transient(iter);
this._dimmer = new WindowManager.WindowDimmer(this._windowClone);
this._updateDimmer();
},
_updateDimmer: function() {
if (this.actor.get_n_children() > 1) {
this._dimmer.setEnabled(true);
this._dimmer.dimFactor = 1.0;
} else {
this._dimmer.setEnabled(false);
}
},
get boundingBox() {
return this._boundingBox;
},
getOriginalPosition: function() {
return [this._boundingBox.x, this._boundingBox.y];
},
_computeBoundingBox: function() {
let rect = this.metaWindow.get_outer_rect();
this.actor.get_children().forEach(function (child) {
let realWindow;
if (child == this._windowClone)
realWindow = this.realWindow;
else
realWindow = child.source;
let metaWindow = realWindow.meta_window;
rect = rect.union(metaWindow.get_outer_rect());
}, this);
this._boundingBox = rect;
this.actor.layout_manager.boundingBox = rect;
},
setStackAbove: function (actor) {
this._stackAbove = actor;
if (this.inDrag)
@@ -136,44 +291,26 @@ const WindowClone = new Lang.Class({
this.actor.destroy();
},
_disconnectRealWindowSignals: function() {
if (this._sizeChangedId > 0)
this.realWindow.disconnect(this._sizeChangedId);
this._sizeChangedId = 0;
_disconnectSignals: function() {
this.actor.get_children().forEach(Lang.bind(this, function (child) {
let realWindow;
if (child == this._windowClone)
realWindow = this.realWindow;
else
realWindow = child.source;
if (this._realWindowDestroyId > 0)
this.realWindow.disconnect(this._realWindowDestroyId);
this._realWindowDestroyId = 0;
},
_getInvisibleBorderPadding: function() {
// We need to adjust the position of the actor because of the
// consequences of invisible borders -- in reality, the texture
// has an extra set of "padding" around it that we need to trim
// down.
// The outer rect paradoxically is the smaller rectangle,
// containing the positions of the visible frame. The input
// rect contains everything, including the invisible border
// padding.
let outerRect = this.metaWindow.get_outer_rect();
let inputRect = this.metaWindow.get_input_rect();
let [borderX, borderY] = [outerRect.x - inputRect.x,
outerRect.y - inputRect.y];
return [borderX, borderY];
realWindow.disconnect(child._updateId);
realWindow.disconnect(child._destroyId);
}));
},
_onRealWindowSizeChanged: function() {
let [borderX, borderY] = this._getInvisibleBorderPadding();
let outerRect = this.metaWindow.get_outer_rect();
this.actor.set_size(outerRect.width, outerRect.height);
this._windowClone.set_position(-borderX, -borderY);
this._computeBoundingBox();
this.emit('size-changed');
},
_onDestroy: function() {
this._disconnectRealWindowSignals();
this._disconnectSignals();
this.metaWindow._delegate = null;
this.actor._delegate = null;
@@ -432,7 +569,7 @@ const WindowOverlay = new Lang.Class({
Lang.bind(this,
this._onWindowAdded));
metaWindow.delete(global.get_current_time());
this._windowClone.deleteAll();
},
_onWindowAdded: function(workspace, win) {
@@ -1220,9 +1357,29 @@ const Workspace = new Lang.Class({
if (this._lookupIndex (metaWin) != -1)
return;
if (!this._isMyWindow(win) || !this._isOverviewWindow(win))
if (!this._isMyWindow(win))
return;
if (!this._isOverviewWindow(win)) {
if (metaWin.is_attached_dialog()) {
let parent = metaWin.get_transient_for();
while (parent.is_attached_dialog())
parent = metaWin.get_transient_for();
let idx = this._lookupIndex (parent);
if (idx < 0) {
// parent was not created yet, it will take care
// of the dialog when created
return;
}
let clone = this._windows[idx];
clone.addAttachedDialog(metaWin);
}
return;
}
let [clone, overlay] = this._addWindowClone(win);
if (win._overviewHint) {
@@ -1321,9 +1478,11 @@ const Workspace = new Lang.Class({
overlay.hide();
if (clone.metaWindow.showing_on_its_workspace()) {
let [origX, origY] = clone.getOriginalPosition();
Tweener.addTween(clone.actor,
{ x: clone.origX,
y: clone.origY,
{ x: origX,
y: origY,
scale_x: 1.0,
scale_y: 1.0,
time: Overview.ANIMATION_TIME,

View File

@@ -13,6 +13,7 @@ const Background = imports.ui.background;
const DND = imports.ui.dnd;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;
const WindowManager = imports.ui.windowManager;
const Workspace = imports.ui.workspace;
const WorkspacesView = imports.ui.workspacesView;
@@ -31,20 +32,49 @@ const WORKSPACE_KEEP_ALIVE_TIME = 100;
const OVERRIDE_SCHEMA = 'org.gnome.shell.overrides';
/* A layout manager that requests size only for primary_actor, but then allocates
all using a fixed layout */
const PrimaryActorLayout = new Lang.Class({
Name: 'PrimaryActorLayout',
Extends: Clutter.FixedLayout,
_init: function(primaryActor) {
this.parent();
this.primaryActor = primaryActor;
},
vfunc_get_preferred_width: function(forHeight) {
return this.primaryActor.get_preferred_width(forHeight);
},
vfunc_get_preferred_height: function(forWidth) {
return this.primaryActor.get_preferred_height(forWidth);
},
});
const WindowClone = new Lang.Class({
Name: 'WindowClone',
_init : function(realWindow) {
this.actor = new Clutter.Clone({ source: realWindow.get_texture(),
this.clone = new Clutter.Clone({ source: realWindow });
/* Can't use a Shell.GenericContainer because of DND and reparenting... */
this.actor = new Clutter.Actor({ layout_manager: new PrimaryActorLayout(this.clone),
reactive: true });
this.actor._delegate = this;
this.actor.add_child(this.clone);
this.realWindow = realWindow;
this.metaWindow = realWindow.meta_window;
this._positionChangedId = this.realWindow.connect('position-changed',
Lang.bind(this, this._onPositionChanged));
this._realWindowDestroyedId = this.realWindow.connect('destroy',
Lang.bind(this, this._disconnectRealWindowSignals));
this.clone._updateId = this.realWindow.connect('position-changed',
Lang.bind(this, this._onPositionChanged));
this.clone._destroyId = this.realWindow.connect('destroy', Lang.bind(this, function() {
// First destroy the clone and then destroy everything
// This will ensure that we never see it in the _disconnectSignals loop
this.clone.destroy();
this.destroy();
}));
this._onPositionChanged();
this.actor.connect('button-release-event',
@@ -60,6 +90,24 @@ const WindowClone = new Lang.Class({
this._draggable.connect('drag-cancelled', Lang.bind(this, this._onDragCancelled));
this._draggable.connect('drag-end', Lang.bind(this, this._onDragEnd));
this.inDrag = false;
let iter = Lang.bind(this, function(win) {
let actor = win.get_compositor_private();
if (!actor)
return false;
if (!win.is_attached_dialog())
return false;
this._doAddAttachedDialog(win, actor);
win.foreach_transient(iter);
return true;
});
this.metaWindow.foreach_transient(iter);
this._dimmer = new WindowManager.WindowDimmer(this.clone);
this._updateDimmer();
},
setStackAbove: function (actor) {
@@ -74,25 +122,57 @@ const WindowClone = new Lang.Class({
this.actor.destroy();
},
addAttachedDialog: function(win) {
this._doAddAttachedDialog(win, win.get_compositor_private());
this._updateDimmer();
},
_doAddAttachedDialog: function(metaDialog, realDialog) {
let clone = new Clutter.Clone({ source: realDialog });
this._updateDialogPosition(realDialog, clone);
clone._updateId = realDialog.connect('position-changed',
Lang.bind(this, this._updateDialogPosition, clone));
clone._destroyId = realDialog.connect('destroy', Lang.bind(this, function() {
clone.destroy();
this._updateDimmer();
}));
this.actor.add_child(clone);
},
_updateDimmer: function() {
if (this.actor.get_n_children() > 1) {
this._dimmer.setEnabled(true);
this._dimmer.dimFactor = 1.0;
} else {
this._dimmer.setEnabled(false);
}
},
_updateDialogPosition: function(realDialog, cloneDialog) {
let metaDialog = realDialog.meta_window;
let dialogRect = metaDialog.get_outer_rect();
let rect = this.metaWindow.get_outer_rect();
cloneDialog.set_position(dialogRect.x - rect.x, dialogRect.y - rect.y);
},
_onPositionChanged: function() {
let rect = this.metaWindow.get_outer_rect();
this.actor.set_position(this.realWindow.x, this.realWindow.y);
},
_disconnectRealWindowSignals: function() {
if (this._positionChangedId != 0) {
this.realWindow.disconnect(this._positionChangedId);
this._positionChangedId = 0;
}
_disconnectSignals: function() {
this.actor.get_children().forEach(function(child) {
let realWindow = child.source;
if (this._realWindowDestroyedId != 0) {
this.realWindow.disconnect(this._realWindowDestroyedId);
this._realWindowDestroyedId = 0;
}
realWindow.disconnect(child._updateId);
realWindow.disconnect(child._destroyId);
});
},
_onDestroy: function() {
this._disconnectRealWindowSignals();
this._disconnectSignals();
this.actor._delegate = null;
@@ -320,10 +400,26 @@ const WorkspaceThumbnail = new Lang.Class({
if (this._lookupIndex (metaWin) != -1)
return;
if (!this._isMyWindow(win) || !this._isOverviewWindow(win))
if (!this._isMyWindow(win))
return;
let clone = this._addWindowClone(win);
if (this._isOverviewWindow(win)) {
this._addWindowClone(win);
} else if (metaWin.is_attached_dialog()) {
let parent = metaWin.get_transient_for();
while (parent.is_attached_dialog())
parent = metaWin.get_transient_for();
let idx = this._lookupIndex (parent);
if (idx < 0) {
// parent was not created yet, it will take care
// of the dialog when created
return;
}
let clone = this._windows[idx];
clone.addAttachedDialog(metaWin);
}
},
_windowAdded : function(metaWorkspace, metaWin) {

584
po/lt.po

File diff suppressed because it is too large Load Diff

View File

@@ -490,17 +490,6 @@ calendar_sources_registry_source_removed_cb (ESourceRegistry *registry,
}
}
static void
ensure_appointment_sources (CalendarSources *sources)
{
if (!sources->priv->appointment_sources.loaded)
{
calendar_sources_load_esource_list (sources->priv->registry,
&sources->priv->appointment_sources);
sources->priv->appointment_sources.loaded = TRUE;
}
}
GList *
calendar_sources_get_appointment_clients (CalendarSources *sources)
{
@@ -508,7 +497,12 @@ calendar_sources_get_appointment_clients (CalendarSources *sources)
g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL);
ensure_appointment_sources (sources);
if (!sources->priv->appointment_sources.loaded)
{
calendar_sources_load_esource_list (sources->priv->registry,
&sources->priv->appointment_sources);
sources->priv->appointment_sources.loaded = TRUE;
}
list = g_hash_table_get_values (sources->priv->appointment_sources.clients);
@@ -518,17 +512,6 @@ calendar_sources_get_appointment_clients (CalendarSources *sources)
return list;
}
static void
ensure_task_sources (CalendarSources *sources)
{
if (!sources->priv->task_sources.loaded)
{
calendar_sources_load_esource_list (sources->priv->registry,
&sources->priv->task_sources);
sources->priv->task_sources.loaded = TRUE;
}
}
GList *
calendar_sources_get_task_clients (CalendarSources *sources)
{
@@ -536,7 +519,12 @@ calendar_sources_get_task_clients (CalendarSources *sources)
g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL);
ensure_task_sources (sources);
if (!sources->priv->task_sources.loaded)
{
calendar_sources_load_esource_list (sources->priv->registry,
&sources->priv->task_sources);
sources->priv->task_sources.loaded = TRUE;
}
list = g_hash_table_get_values (sources->priv->task_sources.clients);
@@ -545,15 +533,3 @@ calendar_sources_get_task_clients (CalendarSources *sources)
return list;
}
gboolean
calendar_sources_has_sources (CalendarSources *sources)
{
g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), FALSE);
ensure_appointment_sources (sources);
ensure_task_sources (sources);
return g_hash_table_size (sources->priv->appointment_sources.clients) > 0 ||
g_hash_table_size (sources->priv->task_sources.clients) > 0;
}

View File

@@ -61,8 +61,6 @@ CalendarSources *calendar_sources_get (void);
GList *calendar_sources_get_appointment_clients (CalendarSources *sources);
GList *calendar_sources_get_task_clients (CalendarSources *sources);
gboolean calendar_sources_has_sources (CalendarSources *sources);
G_END_DECLS
#endif /* __CALENDAR_SOURCES_H__ */

View File

@@ -57,7 +57,6 @@ static const gchar introspection_xml[] =
" <signal name='Changed'/>"
" <property name='Since' type='x' access='read'/>"
" <property name='Until' type='x' access='read'/>"
" <property name='HasCalendars' type='b' access='read'/>"
" </interface>"
"</node>";
static GDBusNodeInfo *introspection_data = NULL;
@@ -727,12 +726,6 @@ app_load_events (App *app)
app->cache_invalid = FALSE;
}
static gboolean
app_has_calendars (App *app)
{
return calendar_sources_has_sources (app->sources);
}
static void
on_appointment_sources_changed (CalendarSources *sources,
gpointer user_data)
@@ -741,26 +734,6 @@ on_appointment_sources_changed (CalendarSources *sources,
print_debug ("Sources changed\n");
app_load_events (app);
/* Notify the HasCalendars property */
{
GVariantBuilder dict_builder;
g_variant_builder_init (&dict_builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&dict_builder, "{sv}", "HasCalendars",
g_variant_new_boolean (app_has_calendars (app)));
g_dbus_connection_emit_signal (g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL),
NULL,
"/org/gnome/Shell/CalendarServer",
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
g_variant_new ("(sa{sv}as)",
"org.gnome.Shell.CalendarServer",
&dict_builder,
NULL),
NULL);
}
}
static App *
@@ -960,10 +933,6 @@ handle_get_property (GDBusConnection *connection,
{
ret = g_variant_new_int64 (app->until);
}
else if (g_strcmp0 (property_name, "HasCalendars") == 0)
{
ret = g_variant_new_boolean (app_has_calendars (app));
}
else
{
g_assert_not_reached ();

View File

@@ -21,12 +21,9 @@
#include "config.h"
#include <string.h>
#include <gnome-keyring.h>
#include <dbus/dbus-glib.h>
/* For use of unstable features in libsecret, until they stabilize */
#define SECRET_API_SUBJECT_TO_CHANGE
#include <libsecret/secret.h>
#include "shell-network-agent.h"
enum {
@@ -38,7 +35,7 @@ enum {
static gint signals[SIGNAL_LAST];
typedef struct {
GCancellable * cancellable;
gpointer keyring_op;
ShellNetworkAgent *self;
gchar *request_id;
@@ -62,24 +59,14 @@ struct _ShellNetworkAgentPrivate {
G_DEFINE_TYPE (ShellNetworkAgent, shell_network_agent, NM_TYPE_SECRET_AGENT)
static const SecretSchema network_agent_schema = {
"org.freedesktop.NetworkManager.Connection",
SECRET_SCHEMA_DONT_MATCH_NAME,
{
{ SHELL_KEYRING_UUID_TAG, SECRET_SCHEMA_ATTRIBUTE_STRING },
{ SHELL_KEYRING_SN_TAG, SECRET_SCHEMA_ATTRIBUTE_STRING },
{ SHELL_KEYRING_SK_TAG, SECRET_SCHEMA_ATTRIBUTE_STRING },
{ NULL, 0 },
}
};
static void
shell_agent_request_free (gpointer data)
{
ShellAgentRequest *request = data;
g_cancellable_cancel (request->cancellable);
g_object_unref (request->cancellable);
if (request->keyring_op)
gnome_keyring_cancel_request (request->keyring_op);
g_object_unref (request->self);
g_object_unref (request->connection);
g_free (request->setting_name);
@@ -258,92 +245,87 @@ strv_has (gchar **haystack,
}
static void
get_secrets_keyring_cb (GObject *source,
GAsyncResult *result,
gpointer user_data)
get_secrets_keyring_cb (GnomeKeyringResult result,
GList *list,
gpointer user_data)
{
ShellAgentRequest *closure;
ShellNetworkAgent *self;
ShellNetworkAgentPrivate *priv;
GError *secret_error = NULL;
GError *error = NULL;
gint n_found = 0;
GList *items;
GList *l;
GList *iter;
GHashTable *outer;
items = secret_service_search_finish (NULL, result, &secret_error);
if (g_error_matches (secret_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
g_error_free (secret_error);
return;
}
if (result == GNOME_KEYRING_RESULT_CANCELLED)
return;
closure = user_data;
self = closure->self;
priv = self->priv;
if (secret_error != NULL)
closure->keyring_op = NULL;
if (result == GNOME_KEYRING_RESULT_DENIED)
{
g_set_error (&error,
NM_SECRET_AGENT_ERROR,
NM_SECRET_AGENT_ERROR_INTERNAL_ERROR,
"Internal error while retrieving secrets from the keyring (%s)", secret_error->message);
g_error_free (secret_error);
NM_SECRET_AGENT_ERROR,
NM_SECRET_AGENT_ERROR_USER_CANCELED,
"Access to the secret storage was denied by the user");
closure->callback (NM_SECRET_AGENT (closure->self), closure->connection, NULL, error, closure->callback_data);
goto out;
}
for (l = items; l; l = g_list_next (l))
if (result != GNOME_KEYRING_RESULT_OK &&
result != GNOME_KEYRING_RESULT_NO_MATCH)
{
SecretItem *item = l->data;
GHashTable *attributes;
GHashTableIter iter;
const gchar *name, *attribute;
SecretValue *secret = secret_item_get_secret (item);
g_set_error (&error,
NM_SECRET_AGENT_ERROR,
NM_SECRET_AGENT_ERROR_INTERNAL_ERROR,
"Internal error while retrieving secrets from the keyring (result %d)", result);
/* This can happen if the user denied a request to unlock */
if (secret == NULL)
continue;
closure->callback (NM_SECRET_AGENT (closure->self), closure->connection, NULL, error, closure->callback_data);
attributes = secret_item_get_attributes (item);
g_hash_table_iter_init (&iter, attributes);
while (g_hash_table_iter_next (&iter, (gpointer *)&name, (gpointer *)&attribute))
goto out;
}
for (iter = list; iter; iter = g_list_next (iter))
{
GnomeKeyringFound *item = iter->data;
int i;
for (i = 0; i < item->attributes->len; i++)
{
if (g_strcmp0 (name, SHELL_KEYRING_SK_TAG) == 0)
GnomeKeyringAttribute *attr = &gnome_keyring_attribute_list_index (item->attributes, i);
if (g_strcmp0 (attr->name, SHELL_KEYRING_SK_TAG) == 0
&& (attr->type == GNOME_KEYRING_ATTRIBUTE_TYPE_STRING))
{
gchar *secret_name = g_strdup (attribute);
gchar *secret_name = g_strdup (attr->value.string);
if (!closure->is_vpn)
{
GValue *secret_value = g_slice_new0 (GValue);
g_value_init (secret_value, G_TYPE_STRING);
g_value_set_string (secret_value, secret_value_get (secret, NULL));
g_value_set_string (secret_value, item->secret);
g_hash_table_insert (closure->entries, secret_name, secret_value);
}
else
g_hash_table_insert (closure->vpn_entries, secret_name, g_strdup (secret_value_get (secret, NULL)));
g_hash_table_insert (closure->vpn_entries, secret_name, g_strdup (item->secret));
if (closure->hints)
n_found += strv_has (closure->hints, secret_name);
else
n_found += 1;
g_hash_table_unref (attributes);
secret_value_unref (secret);
break;
}
}
g_hash_table_unref (attributes);
secret_value_unref (secret);
}
g_list_free_full (items, g_object_unref);
if (n_found == 0 &&
(closure->flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION))
{
@@ -379,7 +361,6 @@ shell_network_agent_get_secrets (NMSecretAgent *agent,
ShellAgentRequest *request;
NMSettingConnection *setting_connection;
const char *connection_type;
GHashTable *attributes;
char *request_id;
request_id = g_strdup_printf ("%s/%s", connection_path, setting_name);
@@ -397,7 +378,6 @@ shell_network_agent_get_secrets (NMSecretAgent *agent,
request = g_slice_new (ShellAgentRequest);
request->self = g_object_ref (self);
request->cancellable = g_cancellable_new ();
request->connection = g_object_ref (connection);
request->setting_name = g_strdup (setting_name);
request->hints = g_strdupv ((gchar **)hints);
@@ -405,6 +385,7 @@ shell_network_agent_get_secrets (NMSecretAgent *agent,
request->callback = callback;
request->callback_data = callback_data;
request->is_vpn = !strcmp(connection_type, NM_SETTING_VPN_SETTING_NAME);
request->keyring_op = NULL;
request->entries = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, gvalue_destroy_notify);
if (request->is_vpn)
@@ -432,16 +413,17 @@ shell_network_agent_get_secrets (NMSecretAgent *agent,
return;
}
attributes = secret_attributes_build (&network_agent_schema,
SHELL_KEYRING_UUID_TAG, nm_connection_get_uuid (connection),
SHELL_KEYRING_SN_TAG, setting_name,
NULL);
secret_service_search (NULL, &network_agent_schema, attributes,
SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK | SECRET_SEARCH_LOAD_SECRETS,
request->cancellable, get_secrets_keyring_cb, request);
g_hash_table_unref (attributes);
request->keyring_op = gnome_keyring_find_itemsv (GNOME_KEYRING_ITEM_GENERIC_SECRET,
get_secrets_keyring_cb,
request,
NULL, /* GDestroyNotify */
SHELL_KEYRING_UUID_TAG,
GNOME_KEYRING_ATTRIBUTE_TYPE_STRING,
nm_connection_get_uuid (connection),
SHELL_KEYRING_SN_TAG,
GNOME_KEYRING_ATTRIBUTE_TYPE_STRING,
setting_name,
NULL);
}
void
@@ -559,7 +541,7 @@ shell_network_agent_cancel_get_secrets (NMSecretAgent *agent,
/************************* saving of secrets ****************************************/
static GHashTable *
static GnomeKeyringAttributeList *
create_keyring_add_attr_list (NMConnection *connection,
const gchar *connection_uuid,
const gchar *connection_id,
@@ -567,6 +549,7 @@ create_keyring_add_attr_list (NMConnection *connection,
const gchar *setting_key,
gchar **out_display_name)
{
GnomeKeyringAttributeList *attrs = NULL;
NMSettingConnection *s_con;
if (connection)
@@ -590,11 +573,17 @@ create_keyring_add_attr_list (NMConnection *connection,
setting_key);
}
return secret_attributes_build (&network_agent_schema,
SHELL_KEYRING_UUID_TAG, connection_uuid,
SHELL_KEYRING_SN_TAG, setting_name,
SHELL_KEYRING_SK_TAG, setting_key,
NULL);
attrs = gnome_keyring_attribute_list_new ();
gnome_keyring_attribute_list_append_string (attrs,
SHELL_KEYRING_UUID_TAG,
connection_uuid);
gnome_keyring_attribute_list_append_string (attrs,
SHELL_KEYRING_SN_TAG,
setting_name);
gnome_keyring_attribute_list_append_string (attrs,
SHELL_KEYRING_SK_TAG,
setting_key);
return attrs;
}
typedef struct
@@ -618,8 +607,8 @@ keyring_request_free (KeyringRequest *r)
}
static void
save_secret_cb (GObject *source,
GAsyncResult *result,
save_secret_cb (GnomeKeyringResult result,
guint val,
gpointer user_data)
{
KeyringRequest *call = user_data;
@@ -642,7 +631,7 @@ save_one_secret (KeyringRequest *r,
const gchar *secret,
const gchar *display_name)
{
GHashTable *attrs;
GnomeKeyringAttributeList *attrs;
gchar *alt_display_name = NULL;
const gchar *setting_name;
NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
@@ -661,11 +650,17 @@ save_one_secret (KeyringRequest *r,
display_name ? NULL : &alt_display_name);
g_assert (attrs);
r->n_secrets++;
secret_password_storev (&network_agent_schema, attrs, SECRET_COLLECTION_DEFAULT,
display_name ? display_name : alt_display_name,
secret, NULL, save_secret_cb, r);
gnome_keyring_item_create (NULL,
GNOME_KEYRING_ITEM_GENERIC_SECRET,
display_name ? display_name : alt_display_name,
attrs,
secret,
TRUE,
save_secret_cb,
r,
NULL);
g_hash_table_unref (attrs);
gnome_keyring_attribute_list_free (attrs);
g_free (alt_display_name);
}
@@ -771,28 +766,38 @@ shell_network_agent_save_secrets (NMSecretAgent *agent,
}
static void
delete_items_cb (GObject *source,
GAsyncResult *result,
gpointer user_data)
keyring_delete_cb (GnomeKeyringResult result, gpointer user_data)
{
/* Ignored */
}
static void
delete_find_items_cb (GnomeKeyringResult result, GList *list, gpointer user_data)
{
KeyringRequest *r = user_data;
GError *secret_error = NULL;
GList *iter;
GError *error = NULL;
NMSecretAgentDeleteSecretsFunc callback = r->callback;
secret_password_clear_finish (result, &secret_error);
if (secret_error != NULL)
if ((result == GNOME_KEYRING_RESULT_OK) || (result == GNOME_KEYRING_RESULT_NO_MATCH))
{
for (iter = list; iter != NULL; iter = g_list_next (iter))
{
GnomeKeyringFound *found = (GnomeKeyringFound *) iter->data;
gnome_keyring_item_delete (found->keyring, found->item_id, keyring_delete_cb, NULL, NULL);
}
}
else
{
error = g_error_new (NM_SECRET_AGENT_ERROR,
NM_SECRET_AGENT_ERROR_INTERNAL_ERROR,
"The request could not be completed. Keyring result: %s",
secret_error->message);
g_error_free (secret_error);
"The request could not be completed. Keyring result: %d",
result);
}
callback (r->self, r->connection, error, r->callback_data);
g_clear_error (&error);
keyring_request_free (r);
}
static void
@@ -818,9 +823,14 @@ shell_network_agent_delete_secrets (NMSecretAgent *agent,
uuid = nm_setting_connection_get_uuid (s_con);
g_assert (uuid);
secret_password_clear (&network_agent_schema, NULL, delete_items_cb, r,
SHELL_KEYRING_UUID_TAG, uuid,
NULL);
gnome_keyring_find_itemsv (GNOME_KEYRING_ITEM_GENERIC_SECRET,
delete_find_items_cb,
r,
(GDestroyNotify)keyring_request_free,
SHELL_KEYRING_UUID_TAG,
GNOME_KEYRING_ATTRIBUTE_TYPE_STRING,
uuid,
NULL);
}
void

View File

@@ -534,32 +534,23 @@ void
st_icon_set_icon_name (StIcon *icon,
const gchar *icon_name)
{
StIconPrivate *priv = icon->priv;
GIcon *gicon = NULL;
StIconPrivate *priv;
g_return_if_fail (ST_IS_ICON (icon));
if (icon_name)
gicon = g_themed_icon_new_with_default_fallbacks (icon_name);
if (g_icon_equal (priv->gicon, gicon)) /* do nothing */
{
g_object_unref (gicon);
return;
}
priv = icon->priv;
if (priv->gicon)
g_object_unref (priv->gicon);
g_object_freeze_notify (G_OBJECT (icon));
priv->gicon = gicon;
if (icon_name)
priv->gicon = g_themed_icon_new_with_default_fallbacks (icon_name);
else
priv->gicon = NULL;
g_object_notify (G_OBJECT (icon), "gicon");
g_object_notify (G_OBJECT (icon), "icon-name");
g_object_thaw_notify (G_OBJECT (icon));
st_icon_update (icon);
}
@@ -588,7 +579,7 @@ st_icon_set_gicon (StIcon *icon, GIcon *gicon)
g_return_if_fail (ST_IS_ICON (icon));
g_return_if_fail (gicon == NULL || G_IS_ICON (gicon));
if (g_icon_equal (icon->priv->gicon, gicon)) /* do nothing */
if (icon->priv->gicon == gicon) /* do nothing */
return;
if (icon->priv->gicon)