Compare commits
2 Commits
3.7.91
...
wip/gcampa
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6d4ca1fcc8 | ||
![]() |
93b1af401f |
43
NEWS
43
NEWS
@@ -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]
|
||||
|
@@ -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)
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
|
@@ -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
|
||||
|
392
js/ui/layout.js
392
js/ui/layout.js
@@ -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();
|
||||
|
@@ -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 = [];
|
||||
|
@@ -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
|
||||
|
@@ -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();
|
||||
|
@@ -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();
|
||||
},
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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));
|
||||
|
@@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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) {
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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__ */
|
||||
|
@@ -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 ();
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user