Compare commits
76 Commits
wip/menus-
...
3.3.3
Author | SHA1 | Date | |
---|---|---|---|
36bf63a5de | |||
66c4881fd2 | |||
c6b169cb33 | |||
0e753ed5a6 | |||
785969feb5 | |||
56036476a4 | |||
936b1b5638 | |||
bf3b94d654 | |||
90b4a0856f | |||
686190ce80 | |||
45495c2474 | |||
9caa88fecb | |||
25948f214e | |||
bea4faacd4 | |||
a7bd9f811b | |||
2b9561fcbb | |||
7b9c9b2f7d | |||
b47b82ed42 | |||
c4c2c11dca | |||
ce38293a0f | |||
6c4e9d23f2 | |||
4aa1fe9ca2 | |||
951fff5aa0 | |||
5ad8080cb9 | |||
e53e3cbb09 | |||
8a029f333f | |||
4debedb275 | |||
8764253861 | |||
bbdce159fa | |||
087d8e602e | |||
50aa15dec9 | |||
26580f8f2c | |||
875b6d131b | |||
77afd6782f | |||
09ab13cf04 | |||
56f312dc03 | |||
f2cbddc196 | |||
c7846e172f | |||
54afb7b25e | |||
f3cb9d0443 | |||
fcee7f2f3a | |||
faff0738eb | |||
69e26c6dee | |||
1acec65c5e | |||
8a6a3968c3 | |||
58e4870cb8 | |||
102099cadd | |||
efe6d06ddd | |||
f0d0e025dd | |||
fc2d0215f5 | |||
02af8eb824 | |||
102f2a91f9 | |||
2c649d5da5 | |||
7fa7d04ed0 | |||
aad9179373 | |||
d5b4e30eb7 | |||
34ba6f2f71 | |||
d845f40b98 | |||
80c16aa8f7 | |||
0cbaeaefed | |||
ab6a7773ce | |||
85e243982b | |||
6eb168bc68 | |||
47b55e29d4 | |||
2c243b678f | |||
a634f25d30 | |||
26df6cf35c | |||
84dde8e9bb | |||
f2c79be11a | |||
10df80b96a | |||
bbb83656bf | |||
ef49670ae4 | |||
e4df00c77d | |||
3d70662716 | |||
8d0638a187 | |||
06143396b7 |
41
NEWS
41
NEWS
@ -1,3 +1,44 @@
|
||||
3.3.3
|
||||
=====
|
||||
* https://live.gnome.org/EveryDetailMatters
|
||||
- Stop flashing the window labels on actions in overview [Zan; #644861]
|
||||
- Improve the look of window captions in the overview [Marc; #664487]
|
||||
- Move dash tooltips beside the icon [Seif, Stefano; #666166]
|
||||
* Support application menus exported from applications via new GLib API
|
||||
and D-Bus protocol. [Giovanni, Colin, Matthias, Cosimo]
|
||||
* For removable device prompts, show "Open with Rhythmbox], rather
|
||||
than "Open with Rhythmbox Music Player' [Florian; #664561]
|
||||
* Switch to activating the message tray only with a hot corner rather
|
||||
than a full row of pixels, allowing mouse events to apps [Rui; #663366]
|
||||
* Fully handle the case where the workspaces-only-on-primary
|
||||
GSetting is set to false [Florian; #652580]
|
||||
* Add support for background-size CSS property to St [Quentin; #633462]
|
||||
* Port to new GJS Lang.Class framework [Giovanni; #664436]
|
||||
* Finish port to GDBus [Giovanni; #664436]
|
||||
* Stop using the deprecated Clutter default stage [Florian, Jasper; #664052]
|
||||
* Fix bugs that kept browser plugin from working in WebKit-based browser
|
||||
[Jasper; #666444]
|
||||
* Fix typo that made uninstalling extensions not work [Jasper]
|
||||
* Fix crash in browser plugin if shell is not run [Jürg]
|
||||
* Reintroduce piscine paschal ovum [Giovanni; #666606]
|
||||
* Network menu bug fixes
|
||||
Giovanni; #664124, #665194, #665680, #666429, #666614]
|
||||
* Misc bug fixes [Florian, Jasper, Jonny, Marina, Ron; #647587, #659272,
|
||||
#665261, #666020, #666243]
|
||||
|
||||
Contributors:
|
||||
Jürg Billeter, Giovanni Campagna, Stefano Candori, Cosimo Cecchi,
|
||||
Matthias Clasen, Zan Dobersek, Quentin Glidic, Jonny Lamb, Ryan Lortie,
|
||||
Seif Lotfy, Rui Matos, Florian Müllner, Bastien Nocera, Jasper St. Pierre,
|
||||
Marc Plano-Lesay, Colin Walters, Ron Yorsten, Marina Zhurakhinskaya
|
||||
|
||||
Translations:
|
||||
Petr Kovar [cz], Kris Thomsen [dk], Daniel Mustieles [es],
|
||||
Ville-Pekka Vainio [fi], Yaron Shahrabani [he], Žygimantas Beručka [lt],
|
||||
Jovan Naumovski [mk], Kjartan Maraas [nb], "Andreas N" [nn],
|
||||
Lucian Adrian Grijincu [ro], Matej Urbančič [sl], Praveen Illa [te],
|
||||
Muhammet Kara [tr], Daniel Korostil [uk], Aron Xu [zh_CN]
|
||||
|
||||
3.3.2
|
||||
=====
|
||||
* Port D-Bus usage in the shell to GDBus [Giovanni, Marc-Antoine, Florian,
|
||||
|
@ -455,7 +455,7 @@ plugin_enable_extension (PluginObject *obj,
|
||||
NPString uuid,
|
||||
gboolean enabled)
|
||||
{
|
||||
const gchar *uuid_str = uuid.UTF8Characters;
|
||||
gchar *uuid_str = g_strndup (uuid.UTF8Characters, uuid.UTF8Length);
|
||||
if (!uuid_is_valid (uuid_str))
|
||||
return FALSE;
|
||||
|
||||
@ -468,6 +468,8 @@ plugin_enable_extension (PluginObject *obj,
|
||||
NULL, /* callback */
|
||||
NULL /* user_data */);
|
||||
|
||||
g_free (uuid_str);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -476,21 +478,32 @@ plugin_install_extension (PluginObject *obj,
|
||||
NPString uuid,
|
||||
NPString version_tag)
|
||||
{
|
||||
const gchar *uuid_str = uuid.UTF8Characters;
|
||||
gchar *uuid_str = g_strndup (uuid.UTF8Characters, uuid.UTF8Length);
|
||||
gchar *version_tag_str;
|
||||
|
||||
if (!uuid_is_valid (uuid_str))
|
||||
return FALSE;
|
||||
{
|
||||
g_free (uuid_str);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
version_tag_str = g_strndup (version_tag.UTF8Characters,
|
||||
version_tag.UTF8Length);
|
||||
|
||||
g_dbus_proxy_call (obj->proxy,
|
||||
"InstallRemoteExtension",
|
||||
g_variant_new ("(ss)",
|
||||
uuid_str,
|
||||
version_tag.UTF8Characters),
|
||||
version_tag_str),
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1, /* timeout */
|
||||
NULL, /* cancellable */
|
||||
NULL, /* callback */
|
||||
NULL /* user_data */);
|
||||
|
||||
g_free (uuid_str);
|
||||
g_free (version_tag_str);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -501,11 +514,14 @@ plugin_uninstall_extension (PluginObject *obj,
|
||||
{
|
||||
GError *error = NULL;
|
||||
GVariant *res;
|
||||
const gchar *uuid_str;
|
||||
gchar *uuid_str;
|
||||
|
||||
uuid_str = uuid.UTF8Characters;
|
||||
uuid_str = g_strndup (uuid.UTF8Characters, uuid.UTF8Length);
|
||||
if (!uuid_is_valid (uuid_str))
|
||||
return FALSE;
|
||||
{
|
||||
g_free (uuid_str);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
res = g_dbus_proxy_call_sync (obj->proxy,
|
||||
"UninstallExtension",
|
||||
@ -516,6 +532,8 @@ plugin_uninstall_extension (PluginObject *obj,
|
||||
NULL, /* cancellable */
|
||||
&error);
|
||||
|
||||
g_free (uuid_str);
|
||||
|
||||
if (!res)
|
||||
{
|
||||
g_warning ("Failed to uninstall extension: %s", error->message);
|
||||
@ -533,11 +551,14 @@ plugin_get_info (PluginObject *obj,
|
||||
{
|
||||
GError *error = NULL;
|
||||
GVariant *res;
|
||||
const gchar *uuid_str;
|
||||
gchar *uuid_str;
|
||||
|
||||
uuid_str = uuid.UTF8Characters;
|
||||
uuid_str = g_strndup (uuid.UTF8Characters, uuid.UTF8Length);
|
||||
if (!uuid_is_valid (uuid_str))
|
||||
return FALSE;
|
||||
{
|
||||
g_free (uuid_str);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
res = g_dbus_proxy_call_sync (obj->proxy,
|
||||
"GetExtensionInfo",
|
||||
@ -547,6 +568,8 @@ plugin_get_info (PluginObject *obj,
|
||||
NULL, /* cancellable */
|
||||
&error);
|
||||
|
||||
g_free (uuid_str);
|
||||
|
||||
if (!res)
|
||||
{
|
||||
g_warning ("Failed to retrieve extension metadata: %s", error->message);
|
||||
@ -564,11 +587,14 @@ plugin_get_errors (PluginObject *obj,
|
||||
{
|
||||
GError *error = NULL;
|
||||
GVariant *res;
|
||||
const gchar *uuid_str;
|
||||
gchar *uuid_str;
|
||||
|
||||
uuid_str = uuid.UTF8Characters;
|
||||
uuid_str = g_strndup (uuid.UTF8Characters, uuid.UTF8Length);
|
||||
if (!uuid_is_valid (uuid_str))
|
||||
return FALSE;
|
||||
{
|
||||
g_free (uuid_str);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
res = g_dbus_proxy_call_sync (obj->proxy,
|
||||
"GetExtensionErrors",
|
||||
@ -578,6 +604,8 @@ plugin_get_errors (PluginObject *obj,
|
||||
NULL, /* cancellable */
|
||||
&error);
|
||||
|
||||
g_free (uuid_str);
|
||||
|
||||
if (!res)
|
||||
{
|
||||
g_warning ("Failed to retrieve errors: %s", error->message);
|
||||
@ -633,7 +661,8 @@ plugin_get_shell_version (PluginObject *obj,
|
||||
STRINGN_TO_NPVARIANT (buffer, length, *result);
|
||||
|
||||
out:
|
||||
g_variant_unref (res);
|
||||
if (res)
|
||||
g_variant_unref (res);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
AC_PREREQ(2.63)
|
||||
AC_INIT([gnome-shell],[3.3.2],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell])
|
||||
AC_INIT([gnome-shell],[3.3.3],[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.7.5
|
||||
GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1
|
||||
GJS_MIN_VERSION=1.29.18
|
||||
MUTTER_MIN_VERSION=3.3.2
|
||||
MUTTER_MIN_VERSION=3.3.3
|
||||
FOLKS_MIN_VERSION=0.5.2
|
||||
GTK_MIN_VERSION=3.0.0
|
||||
GIO_MIN_VERSION=2.31.0
|
||||
|
@ -60,6 +60,7 @@ StScrollBar StBin#trough {
|
||||
StScrollBar StButton#vhandle
|
||||
{
|
||||
background-image: url("scroll-vhandle.svg");
|
||||
background-size: contain;
|
||||
background-color: #252525;
|
||||
border: 1px solid #080808;
|
||||
border-radius: 8px;
|
||||
@ -68,6 +69,7 @@ StScrollBar StButton#vhandle
|
||||
StScrollBar StButton#hhandle
|
||||
{
|
||||
background-image: url("scroll-hhandle.svg");
|
||||
background-size: contain;
|
||||
background-color: #252525;
|
||||
border: 1px solid #080808;
|
||||
border-radius: 8px;
|
||||
@ -224,16 +226,20 @@ StTooltip StLabel {
|
||||
|
||||
.toggle-switch-us {
|
||||
background-image: url("toggle-off-us.svg");
|
||||
background-size: contain;
|
||||
}
|
||||
.toggle-switch-us:checked {
|
||||
background-image: url("toggle-on-us.svg");
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.toggle-switch-intl {
|
||||
background-image: url("toggle-off-intl.svg");
|
||||
background-size: contain;
|
||||
}
|
||||
.toggle-switch-intl:checked {
|
||||
background-image: url("toggle-on-intl.svg");
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.nm-menu-item-icons {
|
||||
@ -356,6 +362,7 @@ StTooltip StLabel {
|
||||
.panel-button:focus {
|
||||
border-image: url("panel-button-border.svg") 10 10 0 2;
|
||||
background-image: url("panel-button-highlight-wide.svg");
|
||||
background-size: contain;
|
||||
color: white;
|
||||
text-shadow: black 0px 2px 2px;
|
||||
}
|
||||
@ -364,6 +371,7 @@ StTooltip StLabel {
|
||||
.panel-status-button:checked,
|
||||
.panel-status-button:focus {
|
||||
background-image: url("panel-button-highlight-narrow.svg");
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.panel-button:active > .system-status-icon,
|
||||
@ -467,16 +475,17 @@ StTooltip StLabel {
|
||||
}
|
||||
|
||||
.window-caption {
|
||||
background: rgba(0,0,0,0.8);
|
||||
border: 1px solid rgba(128,128,128,0.40);
|
||||
border-radius: 10px;
|
||||
background: rgba(0,0,0,0.5);
|
||||
border-radius: 8px;
|
||||
font-size: 9pt;
|
||||
padding: 2px 8px;
|
||||
-shell-caption-spacing: 4px;
|
||||
font-weight: bold;
|
||||
padding: 6px 12px;
|
||||
-shell-caption-spacing: 12px;
|
||||
}
|
||||
|
||||
.window-close {
|
||||
background-image: url("close-window.svg");
|
||||
background-size: 34px;
|
||||
height: 34px;
|
||||
width: 34px;
|
||||
-shell-close-overlap: 20px;
|
||||
@ -511,6 +520,7 @@ StTooltip StLabel {
|
||||
|
||||
.placeholder {
|
||||
background-image: url("dash-placeholder.svg");
|
||||
background-size: contain;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
@ -645,6 +655,17 @@ StTooltip StLabel {
|
||||
font-size: 11pt;
|
||||
}
|
||||
|
||||
.dash-label {
|
||||
border-radius: 7px;
|
||||
padding: 4px 12px;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
color: #ffffff;
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;;
|
||||
text-align: center;
|
||||
-x-offset: 8px;
|
||||
}
|
||||
|
||||
/* Apps */
|
||||
|
||||
.icon-grid {
|
||||
@ -686,11 +707,13 @@ StTooltip StLabel {
|
||||
.app-filter:selected {
|
||||
color: #ffffff;
|
||||
background-image: url("filter-selected-ltr.svg");
|
||||
background-size: contain;
|
||||
background-position: 190px 10px;
|
||||
}
|
||||
|
||||
.app-filter:selected:rtl {
|
||||
background-image: url("filter-selected-rtl.svg");
|
||||
background-size: contain;
|
||||
background-position: 10px 10px;
|
||||
}
|
||||
|
||||
@ -771,6 +794,7 @@ StTooltip StLabel {
|
||||
.app-well-app.running > .overview-icon {
|
||||
text-shadow: black 0px 2px 2px;
|
||||
background-image: url("running-indicator.svg");
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.contact:selected,
|
||||
@ -1432,6 +1456,7 @@ StTooltip StLabel {
|
||||
|
||||
.summary-source-button:selected .summary-source {
|
||||
background-image: url("panel-button-highlight-narrow.svg");
|
||||
background-size: contain;
|
||||
border-image: url("source-button-border.svg") 10 10 0 1;
|
||||
}
|
||||
|
||||
@ -1442,6 +1467,7 @@ StTooltip StLabel {
|
||||
|
||||
.summary-source-button:expanded:selected {
|
||||
background-image: url("panel-button-highlight-wide.svg");
|
||||
background-size: contain;
|
||||
border-image: url("source-button-border.svg") 10 10 0 1;
|
||||
}
|
||||
|
||||
@ -1555,6 +1581,7 @@ StTooltip StLabel {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
background-image: url("corner-ripple-ltr.png");
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.ripple-box:rtl {
|
||||
@ -1596,6 +1623,7 @@ StTooltip StLabel {
|
||||
border: 0px;
|
||||
background: rgba(255,255,255,0.5);
|
||||
background-image: url("ws-switch-arrow-up.svg");
|
||||
background-size: contain;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@ -1604,6 +1632,7 @@ StTooltip StLabel {
|
||||
border: 0px;
|
||||
background: rgba(255,255,255,0.5);
|
||||
background-image: url("ws-switch-arrow-down.svg");
|
||||
background-size: contain;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
|
@ -18,11 +18,10 @@ DOC_MODULE=shell
|
||||
# The top-level SGML file. You can change this if you want to.
|
||||
DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.sgml
|
||||
|
||||
# Directories containing the source code, relative to $(srcdir).
|
||||
# Directories containing the source code
|
||||
# gtk-doc will search all .c and .h files beneath these paths
|
||||
# for inline comments documenting functions and macros.
|
||||
# e.g. DOC_SOURCE_DIR=../../../gtk ../../../gdk
|
||||
DOC_SOURCE_DIR=../../../src
|
||||
DOC_SOURCE_DIR=$(top_srcdir)/src
|
||||
|
||||
# Extra options to pass to gtkdoc-scangobj. Not normally needed.
|
||||
SCANGOBJ_OPTIONS=
|
||||
@ -58,7 +57,16 @@ EXTRA_HFILES=
|
||||
|
||||
# Header files or dirs to ignore when scanning. Use base file/dir names
|
||||
# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h private_code
|
||||
IGNORE_HFILES=calendar-server gvc hotplug-sniffer st tray
|
||||
IGNORE_HFILES= \
|
||||
calendar-server \
|
||||
gvc \
|
||||
hotplug-sniffer \
|
||||
st \
|
||||
tray \
|
||||
gactionmuxer.h \
|
||||
gactionobservable.h \
|
||||
gactionobserver.h \
|
||||
shell-recorder-src.h
|
||||
|
||||
# Images to copy into HTML directory.
|
||||
# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
|
||||
@ -79,7 +87,7 @@ expand_content_files=
|
||||
# e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS)
|
||||
# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib)
|
||||
GTKDOC_CFLAGS=$(GNOME_SHELL_CFLAGS)
|
||||
GTKDOC_LIBS=$(GNOME_SHELL_LIBS) $(top_builddir)/src/libgnome-shell.la
|
||||
GTKDOC_LIBS=$(GNOME_SHELL_LIBS) $(BLUETOOTH_LIBS) $(top_builddir)/src/libgnome-shell.la
|
||||
|
||||
# This includes the standard gtk-doc make rules, copied by gtkdocize.
|
||||
include $(top_srcdir)/gtk-doc.make
|
||||
|
@ -18,11 +18,10 @@ DOC_MODULE=st
|
||||
# The top-level SGML file. You can change this if you want to.
|
||||
DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.sgml
|
||||
|
||||
# Directories containing the source code, relative to $(srcdir).
|
||||
# Directories containing the source code
|
||||
# gtk-doc will search all .c and .h files beneath these paths
|
||||
# for inline comments documenting functions and macros.
|
||||
# e.g. DOC_SOURCE_DIR=../../../gtk ../../../gdk
|
||||
DOC_SOURCE_DIR=../../../src/st
|
||||
DOC_SOURCE_DIR=$(top_srcdir)/src/st
|
||||
|
||||
# Extra options to pass to gtkdoc-scangobj. Not normally needed.
|
||||
SCANGOBJ_OPTIONS=
|
||||
|
@ -72,6 +72,7 @@ nobase_dist_js_DATA = \
|
||||
ui/tweener.js \
|
||||
ui/userMenu.js \
|
||||
ui/viewSelector.js \
|
||||
ui/wanda.js \
|
||||
ui/windowAttentionHandler.js \
|
||||
ui/windowManager.js \
|
||||
ui/workspace.js \
|
||||
|
@ -38,7 +38,7 @@ function recursivelyDeleteDir(dir) {
|
||||
let child = dir.get_child(info.get_name());
|
||||
if (type == Gio.FileType.REGULAR)
|
||||
deleteGFile(child);
|
||||
else if (type == Gio.TypeType.DIRECTORY)
|
||||
else if (type == Gio.FileType.DIRECTORY)
|
||||
recursivelyDeleteDir(child);
|
||||
}
|
||||
|
||||
|
@ -232,3 +232,53 @@ function fixupPCIDescription(desc) {
|
||||
|
||||
return out.join(' ');
|
||||
}
|
||||
|
||||
// lowerBound:
|
||||
// @array: an array or array-like object, already sorted
|
||||
// according to @cmp
|
||||
// @val: the value to add
|
||||
// @cmp: a comparator (or undefined to compare as numbers)
|
||||
//
|
||||
// Returns the position of the first element that is not
|
||||
// lower than @val, according to @cmp.
|
||||
// That is, returns the first position at which it
|
||||
// is possible to insert @val without violating the
|
||||
// order.
|
||||
// This is quite like an ordinary binary search, except
|
||||
// that it doesn't stop at first element comparing equal.
|
||||
|
||||
function lowerBound(array, val, cmp) {
|
||||
let min, max, mid, v;
|
||||
cmp = cmp || function(a, b) { return a - b; };
|
||||
|
||||
if (array.length == 0)
|
||||
return 0;
|
||||
|
||||
min = 0; max = array.length;
|
||||
while (min < (max - 1)) {
|
||||
mid = Math.floor((min + max) / 2);
|
||||
v = cmp(array[mid], val);
|
||||
|
||||
if (v < 0)
|
||||
min = mid + 1;
|
||||
else
|
||||
max = mid;
|
||||
}
|
||||
|
||||
return (min == max || cmp(array[min], val) < 0) ? max : min;
|
||||
}
|
||||
|
||||
// insertSorted:
|
||||
// @array: an array sorted according to @cmp
|
||||
// @val: a value to insert
|
||||
// @cmp: the sorting function
|
||||
//
|
||||
// Inserts @val into @array, preserving the
|
||||
// sorting invariants.
|
||||
// Returns the position at which it was inserted
|
||||
function insertSorted(array, val, cmp) {
|
||||
let pos = lowerBound(array, val, cmp);
|
||||
array.splice(pos, 0, val);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
@ -470,6 +470,7 @@ const AppWellIcon = new Lang.Class({
|
||||
Lang.bind(this,
|
||||
this._onStateChanged));
|
||||
this._onStateChanged();
|
||||
this.isMenuUp = false;
|
||||
},
|
||||
|
||||
_onDestroy: function() {
|
||||
@ -551,8 +552,8 @@ const AppWellIcon = new Lang.Class({
|
||||
this._menuManager.addMenu(this._menu);
|
||||
}
|
||||
|
||||
this.isMenuUp = true;
|
||||
this.actor.set_hover(true);
|
||||
this.actor.show_tooltip();
|
||||
this._menu.popup();
|
||||
|
||||
return false;
|
||||
@ -568,6 +569,7 @@ const AppWellIcon = new Lang.Class({
|
||||
|
||||
_onMenuPoppedDown: function() {
|
||||
this.actor.sync_hover();
|
||||
this.isMenuUp = false;
|
||||
},
|
||||
|
||||
_onActivate: function (event) {
|
||||
|
@ -557,7 +557,7 @@ const AutorunTransientNotification = new Lang.Class({
|
||||
|
||||
let label = new St.Bin({ y_align: St.Align.MIDDLE,
|
||||
child: new St.Label
|
||||
({ text: _("Open with %s").format(app.get_display_name()) })
|
||||
({ text: _("Open with %s").format(app.get_name()) })
|
||||
});
|
||||
box.add(label);
|
||||
|
||||
|
@ -6,6 +6,7 @@ const Lang = imports.lang;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
const Mainloop = imports.mainloop;
|
||||
|
||||
const AppDisplay = imports.ui.appDisplay;
|
||||
const AppFavorites = imports.ui.appFavorites;
|
||||
@ -16,6 +17,9 @@ const Tweener = imports.ui.tweener;
|
||||
const Workspace = imports.ui.workspace;
|
||||
|
||||
const DASH_ANIMATION_TIME = 0.2;
|
||||
const DASH_ITEM_LABEL_SHOW_TIME = 0.15;
|
||||
const DASH_ITEM_LABEL_HIDE_TIME = 0.1;
|
||||
const DASH_ITEM_HOVER_TIMEOUT = 300;
|
||||
|
||||
// A container like StBin, but taking the child's scale into account
|
||||
// when requesting a size
|
||||
@ -32,6 +36,8 @@ const DashItemContainer = new Lang.Class({
|
||||
Lang.bind(this, this._allocate));
|
||||
this.actor._delegate = this;
|
||||
|
||||
this._label = null;
|
||||
|
||||
this.child = null;
|
||||
this._childScale = 1;
|
||||
this._childOpacity = 255;
|
||||
@ -84,6 +90,60 @@ const DashItemContainer = new Lang.Class({
|
||||
alloc.natural_size = natWidth * this.child.scale_y;
|
||||
},
|
||||
|
||||
showLabel: function() {
|
||||
if (this._label == null)
|
||||
return;
|
||||
|
||||
this._label.opacity = 0;
|
||||
this._label.show();
|
||||
|
||||
let [stageX, stageY] = this.actor.get_transformed_position();
|
||||
|
||||
let itemHeight = this.actor.allocation.y2 - this.actor.allocation.y1;
|
||||
|
||||
let labelHeight = this._label.get_height();
|
||||
let yOffset = Math.floor((itemHeight - labelHeight) / 2)
|
||||
|
||||
let y = stageY + yOffset;
|
||||
|
||||
let node = this._label.get_theme_node();
|
||||
let xOffset = node.get_length('-x-offset');
|
||||
|
||||
let x;
|
||||
if (St.Widget.get_default_direction () == St.TextDirection.RTL)
|
||||
x = stageX - this._label.get_width() - xOffset;
|
||||
else
|
||||
x = stageX + this.actor.get_width() + xOffset;
|
||||
|
||||
this._label.set_position(x, y);
|
||||
Tweener.addTween(this._label,
|
||||
{ opacity: 255,
|
||||
time: DASH_ITEM_LABEL_SHOW_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
});
|
||||
},
|
||||
|
||||
setLabelText: function(text) {
|
||||
if (this._label == null)
|
||||
this._label = new St.Label({ style_class: 'dash-label'});
|
||||
|
||||
this._label.set_text(text);
|
||||
Main.layoutManager.addChrome(this._label);
|
||||
this._label.hide();
|
||||
},
|
||||
|
||||
hideLabel: function () {
|
||||
this._label.opacity = 255;
|
||||
Tweener.addTween(this._label,
|
||||
{ opacity: 0,
|
||||
time: DASH_ITEM_LABEL_HIDE_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this, function() {
|
||||
this._label.hide();
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
setChild: function(actor) {
|
||||
if (this.child == actor)
|
||||
return;
|
||||
@ -238,6 +298,7 @@ const Dash = new Lang.Class({
|
||||
this._dragPlaceholderPos = -1;
|
||||
this._animatingPlaceholdersCount = 0;
|
||||
this._favRemoveTarget = null;
|
||||
this._labelTimeoutId = 0;
|
||||
|
||||
this._box = new St.BoxLayout({ name: 'dash',
|
||||
vertical: true,
|
||||
@ -371,16 +432,37 @@ const Dash = new Lang.Class({
|
||||
Lang.bind(this, function() {
|
||||
display.actor.opacity = 255;
|
||||
}));
|
||||
display.actor.set_tooltip_text(app.get_name());
|
||||
|
||||
let item = new DashItemContainer();
|
||||
item.setChild(display.actor);
|
||||
|
||||
display.icon.setIconSize(this.iconSize);
|
||||
item.setLabelText(app.get_name());
|
||||
|
||||
display.icon.setIconSize(this.iconSize);
|
||||
display.actor.connect('notify::hover',
|
||||
Lang.bind(this, function() {
|
||||
this._onHover(item, display)
|
||||
}));
|
||||
return item;
|
||||
},
|
||||
|
||||
_onHover: function (item, display) {
|
||||
if (display.actor.get_hover() && !display.isMenuUp) {
|
||||
if (this._labelTimeoutId == 0) {
|
||||
this._labelTimeoutId = Mainloop.timeout_add(DASH_ITEM_HOVER_TIMEOUT,
|
||||
Lang.bind(this, function() {
|
||||
item.showLabel();
|
||||
return false;
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
if (this._labelTimeoutId > 0)
|
||||
Mainloop.source_remove(this._labelTimeoutId);
|
||||
this._labelTimeoutId = 0;
|
||||
item.hideLabel();
|
||||
}
|
||||
},
|
||||
|
||||
_adjustIconSize: function() {
|
||||
// For the icon size, we only consider children which are "proper"
|
||||
// icons (i.e. ignoring drag placeholders) and which are not
|
||||
|
@ -202,7 +202,7 @@ function gotExtensionZipFile(session, message, uuid) {
|
||||
global.settings.set_strv(ENABLED_EXTENSIONS_KEY, enabledExtensions);
|
||||
}
|
||||
|
||||
loadExtension(dir, true, ExtensionType.PER_USER);
|
||||
loadExtension(dir, ExtensionType.PER_USER, true);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,8 @@ const BaseIcon = new Lang.Class({
|
||||
this.actor.set_child(box);
|
||||
|
||||
this.iconSize = ICON_SIZE;
|
||||
this._iconBin = new St.Bin();
|
||||
this._iconBin = new St.Bin({ x_align: St.Align.MIDDLE,
|
||||
y_align: St.Align.MIDDLE });
|
||||
|
||||
box.add_actor(this._iconBin);
|
||||
|
||||
@ -125,12 +126,12 @@ const BaseIcon = new Lang.Class({
|
||||
this.iconSize = size;
|
||||
this.icon = this.createIcon(this.iconSize);
|
||||
|
||||
this._iconBin.child = this.icon;
|
||||
|
||||
// The icon returned by createIcon() might actually be smaller than
|
||||
// the requested icon size (for instance StTextureCache does this
|
||||
// for fallback icons), so set the size explicitly.
|
||||
this.icon.set_size(this.iconSize, this.iconSize);
|
||||
|
||||
this._iconBin.child = this.icon;
|
||||
this._iconBin.set_size(this.iconSize, this.iconSize);
|
||||
},
|
||||
|
||||
_onStyleChanged: function() {
|
||||
@ -145,6 +146,11 @@ const BaseIcon = new Lang.Class({
|
||||
size = found ? len : ICON_SIZE;
|
||||
}
|
||||
|
||||
// don't create icons unnecessarily
|
||||
if (size == this.iconSize &&
|
||||
this._iconBin.child)
|
||||
return;
|
||||
|
||||
this._createIconTexture(size);
|
||||
}
|
||||
});
|
||||
|
@ -433,9 +433,9 @@ const HotCorner = new Lang.Class({
|
||||
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 });
|
||||
this._ripple2 = new St.BoxLayout({ style_class: 'ripple-box', opacity: 0 });
|
||||
this._ripple3 = new St.BoxLayout({ style_class: 'ripple-box', opacity: 0 });
|
||||
this._ripple1 = new St.BoxLayout({ style_class: 'ripple-box', opacity: 0, visible: false });
|
||||
this._ripple2 = new St.BoxLayout({ style_class: 'ripple-box', opacity: 0, visible: false });
|
||||
this._ripple3 = new St.BoxLayout({ style_class: 'ripple-box', opacity: 0, visible: false });
|
||||
|
||||
Main.uiGroup.add_actor(this._ripple1);
|
||||
Main.uiGroup.add_actor(this._ripple2);
|
||||
|
@ -44,7 +44,7 @@ const Magnifier = new Lang.Class({
|
||||
this._zoomRegions = [];
|
||||
|
||||
// Create small clutter tree for the magnified mouse.
|
||||
let xfixesCursor = Shell.XFixesCursor.get_default();
|
||||
let xfixesCursor = Shell.XFixesCursor.get_for_stage(global.stage);
|
||||
this._mouseSprite = new Clutter.Texture();
|
||||
xfixesCursor.update_texture_image(this._mouseSprite);
|
||||
this._cursorRoot = new Clutter.Group();
|
||||
|
@ -1430,8 +1430,16 @@ const MessageTray = new Lang.Class({
|
||||
this._notificationRemoved = false;
|
||||
this._reNotifyAfterHideNotification = null;
|
||||
|
||||
this._corner = new Clutter.Rectangle({ width: 1,
|
||||
height: 1,
|
||||
opacity: 0,
|
||||
reactive: true });
|
||||
this._corner.connect('enter-event', Lang.bind(this, this._onCornerEnter));
|
||||
Main.layoutManager.trayBox.add_actor(this._corner);
|
||||
Main.layoutManager.trackChrome(this._corner);
|
||||
|
||||
Main.layoutManager.trayBox.add_actor(this.actor);
|
||||
this.actor.y = -1;
|
||||
this.actor.y = this.actor.height;
|
||||
Main.layoutManager.trackChrome(this.actor);
|
||||
Main.layoutManager.trackChrome(this._notificationBin);
|
||||
|
||||
@ -1470,12 +1478,24 @@ const MessageTray = new Lang.Class({
|
||||
this._chatSummaryItemsCount = 0;
|
||||
},
|
||||
|
||||
_onCornerEnter: function(actor, event) {
|
||||
this._pointerInSummary = true;
|
||||
this._updateState();
|
||||
},
|
||||
|
||||
_setSizePosition: function() {
|
||||
let monitor = Main.layoutManager.bottomMonitor;
|
||||
this._notificationBin.x = 0;
|
||||
this._notificationBin.width = monitor.width;
|
||||
this._summaryBin.x = 0;
|
||||
this._summaryBin.width = monitor.width;
|
||||
|
||||
if (St.Widget.get_default_direction() == St.TextDirection.RTL)
|
||||
this._corner.x = 0;
|
||||
else
|
||||
this._corner.x = Main.layoutManager.trayBox.width - 1;
|
||||
|
||||
this._corner.y = Main.layoutManager.trayBox.height - 1;
|
||||
},
|
||||
|
||||
contains: function(source) {
|
||||
@ -2076,7 +2096,7 @@ const MessageTray = new Lang.Class({
|
||||
|
||||
_hideTray: function() {
|
||||
this._tween(this.actor, '_trayState', State.HIDDEN,
|
||||
{ y: -1,
|
||||
{ y: this.actor.height,
|
||||
time: ANIMATION_TIME,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
|
@ -94,7 +94,7 @@ const NotificationDaemon = new Lang.Class({
|
||||
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(NotificationDaemonIface, this);
|
||||
this._dbusImpl.export(Gio.DBus.session, '/org/freedesktop/Notifications');
|
||||
|
||||
this._sources = {};
|
||||
this._sources = [];
|
||||
this._senderToPid = {};
|
||||
this._notifications = {};
|
||||
this._busProxy = new Bus();
|
||||
@ -152,14 +152,30 @@ const NotificationDaemon = new Lang.Class({
|
||||
}
|
||||
},
|
||||
|
||||
_lookupSource: function(title, pid, trayIcon) {
|
||||
for (let i = 0; i < this._sources.length; i++) {
|
||||
let source = this._sources[i];
|
||||
if (source.pid == pid &&
|
||||
(source.initialTitle == title || source.trayIcon || trayIcon))
|
||||
return source;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
// Returns the source associated with ndata.notification if it is set.
|
||||
// Otherwise, returns the source associated with the pid if one is
|
||||
// stored in this._sources and the notification is not transient.
|
||||
// Otherwise, creates a new source as long as pid is provided.
|
||||
// Otherwise, returns the source associated with the title and pid if
|
||||
// such source is stored in this._sources and the notification is not
|
||||
// transient. If the existing or requested source is associated with
|
||||
// a tray icon and passed in pid matches a pid of an existing source,
|
||||
// the title match is ignored to enable representing a tray icon and
|
||||
// notifications from the same application with a single source.
|
||||
//
|
||||
// If no existing source is found, a new source is created as long as
|
||||
// pid is provided.
|
||||
//
|
||||
// Either a pid or ndata.notification is needed to retrieve or
|
||||
// create a source.
|
||||
_getSource: function(title, pid, ndata, sender) {
|
||||
_getSource: function(title, pid, ndata, sender, trayIcon) {
|
||||
if (!pid && !(ndata && ndata.notification))
|
||||
return null;
|
||||
|
||||
@ -176,20 +192,24 @@ const NotificationDaemon = new Lang.Class({
|
||||
// with a transient one from the same sender, so we
|
||||
// always create a new source object for new transient notifications
|
||||
// and never add it to this._sources .
|
||||
if (!isForTransientNotification && this._sources[pid]) {
|
||||
let source = this._sources[pid];
|
||||
source.setTitle(title);
|
||||
return source;
|
||||
if (!isForTransientNotification) {
|
||||
let source = this._lookupSource(title, pid, trayIcon);
|
||||
if (source) {
|
||||
source.setTitle(title);
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
let source = new Source(title, pid, sender);
|
||||
let source = new Source(title, pid, sender, trayIcon);
|
||||
source.setTransient(isForTransientNotification);
|
||||
|
||||
if (!isForTransientNotification) {
|
||||
this._sources[pid] = source;
|
||||
this._sources.push(source);
|
||||
source.connect('destroy', Lang.bind(this,
|
||||
function() {
|
||||
delete this._sources[pid];
|
||||
let index = this._sources.indexOf(source);
|
||||
if (index >= 0)
|
||||
this._sources.splice(index, 1);
|
||||
}));
|
||||
}
|
||||
|
||||
@ -268,7 +288,7 @@ const NotificationDaemon = new Lang.Class({
|
||||
let sender = invocation.get_sender();
|
||||
let pid = this._senderToPid[sender];
|
||||
|
||||
let source = this._getSource(appName, pid, ndata, sender);
|
||||
let source = this._getSource(appName, pid, ndata, sender, null);
|
||||
|
||||
if (source) {
|
||||
this._notifyForSource(source, ndata);
|
||||
@ -294,7 +314,7 @@ const NotificationDaemon = new Lang.Class({
|
||||
}
|
||||
|
||||
let [pid] = result;
|
||||
source = this._getSource(appName, pid, ndata, sender);
|
||||
source = this._getSource(appName, pid, ndata, sender, null);
|
||||
|
||||
// We only store sender-pid entries for persistent sources.
|
||||
// Removing the entries once the source is destroyed
|
||||
@ -443,8 +463,8 @@ const NotificationDaemon = new Lang.Class({
|
||||
if (!tracker.focus_app)
|
||||
return;
|
||||
|
||||
for (let id in this._sources) {
|
||||
let source = this._sources[id];
|
||||
for (let i = 0; i < this._sources.length; i++) {
|
||||
let source = this._sources[i];
|
||||
if (source.app == tracker.focus_app) {
|
||||
source.destroyNonResidentNotifications();
|
||||
return;
|
||||
@ -463,12 +483,11 @@ const NotificationDaemon = new Lang.Class({
|
||||
},
|
||||
|
||||
_onTrayIconAdded: function(o, icon) {
|
||||
let source = this._getSource(icon.title || icon.wm_class || _("Unknown"), icon.pid, null, null);
|
||||
source.setTrayIcon(icon);
|
||||
let source = this._getSource(icon.title || icon.wm_class || _("Unknown"), icon.pid, null, null, icon);
|
||||
},
|
||||
|
||||
_onTrayIconRemoved: function(o, icon) {
|
||||
let source = this._sources[icon.pid];
|
||||
let source = this._lookupSource(icon.pid, null, true);
|
||||
if (source)
|
||||
source.destroy();
|
||||
}
|
||||
@ -478,10 +497,12 @@ const Source = new Lang.Class({
|
||||
Name: 'NotificationDaemonSource',
|
||||
Extends: MessageTray.Source,
|
||||
|
||||
_init: function(title, pid, sender) {
|
||||
_init: function(title, pid, sender, trayIcon) {
|
||||
this.parent(title);
|
||||
|
||||
this._pid = pid;
|
||||
this.initialTitle = title;
|
||||
|
||||
this.pid = pid;
|
||||
if (sender)
|
||||
this._nameWatcherId = Gio.DBus.session.watch_name(sender,
|
||||
Gio.BusNameWatcherFlags.NONE,
|
||||
@ -495,7 +516,12 @@ const Source = new Lang.Class({
|
||||
this.title = this.app.get_name();
|
||||
else
|
||||
this.useNotificationIcon = true;
|
||||
this._trayIcon = null;
|
||||
|
||||
this.trayIcon = trayIcon;
|
||||
if (this.trayIcon) {
|
||||
this._setSummaryIcon(this.trayIcon);
|
||||
this.useNotificationIcon = false;
|
||||
}
|
||||
},
|
||||
|
||||
_onNameVanished: function() {
|
||||
@ -522,7 +548,7 @@ const Source = new Lang.Class({
|
||||
},
|
||||
|
||||
handleSummaryClick: function() {
|
||||
if (!this._trayIcon)
|
||||
if (!this.trayIcon)
|
||||
return false;
|
||||
|
||||
let event = Clutter.get_current_event();
|
||||
@ -543,11 +569,11 @@ const Source = new Lang.Class({
|
||||
let id = global.connect('notify::stage-input-mode', Lang.bind(this,
|
||||
function () {
|
||||
global.disconnect(id);
|
||||
this._trayIcon.click(event);
|
||||
this.trayIcon.click(event);
|
||||
}));
|
||||
Main.overview.hide();
|
||||
} else {
|
||||
this._trayIcon.click(event);
|
||||
this.trayIcon.click(event);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
@ -556,31 +582,25 @@ const Source = new Lang.Class({
|
||||
if (this.app)
|
||||
return;
|
||||
|
||||
this.app = Shell.WindowTracker.get_default().get_app_from_pid(this._pid);
|
||||
this.app = Shell.WindowTracker.get_default().get_app_from_pid(this.pid);
|
||||
if (!this.app)
|
||||
return;
|
||||
|
||||
// Only override the icon if we were previously using
|
||||
// notification-based icons (ie, not a trayicon) or if it was unset before
|
||||
if (!this._trayIcon) {
|
||||
if (!this.trayIcon) {
|
||||
this.useNotificationIcon = false;
|
||||
this._setSummaryIcon(this.app.create_icon_texture (this.ICON_SIZE));
|
||||
}
|
||||
},
|
||||
|
||||
setTrayIcon: function(icon) {
|
||||
this._setSummaryIcon(icon);
|
||||
this.useNotificationIcon = false;
|
||||
this._trayIcon = icon;
|
||||
},
|
||||
|
||||
open: function(notification) {
|
||||
this.destroyNonResidentNotifications();
|
||||
this.openApp();
|
||||
},
|
||||
|
||||
_lastNotificationRemoved: function() {
|
||||
if (!this._trayIcon)
|
||||
if (!this.trayIcon)
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
|
@ -23,6 +23,7 @@ const Params = imports.misc.params;
|
||||
const PlaceDisplay = imports.ui.placeDisplay;
|
||||
const Tweener = imports.ui.tweener;
|
||||
const ViewSelector = imports.ui.viewSelector;
|
||||
const Wanda = imports.ui.wanda;
|
||||
const WorkspacesView = imports.ui.workspacesView;
|
||||
const WorkspaceThumbnail = imports.ui.workspaceThumbnail;
|
||||
|
||||
@ -108,7 +109,6 @@ const Overview = new Lang.Class({
|
||||
if (this.isDummy) {
|
||||
this.animationInProgress = false;
|
||||
this.visible = false;
|
||||
this.workspaces = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -180,8 +180,6 @@ const Overview = new Lang.Class({
|
||||
this._lastActiveWorkspaceIndex = -1;
|
||||
this._lastHoveredWindow = null;
|
||||
this._needsFakePointerEvent = false;
|
||||
|
||||
this.workspaces = null;
|
||||
},
|
||||
|
||||
// The members we construct that are implemented in JS might
|
||||
@ -204,6 +202,8 @@ const Overview = new Lang.Class({
|
||||
this._viewSelector.addViewTab('applications', _("Applications"), appView.actor, 'system-run');
|
||||
|
||||
// Default search providers
|
||||
// Wanda comes obviously first
|
||||
this.addSearchProvider(new Wanda.WandaSearchProvider());
|
||||
this.addSearchProvider(new AppDisplay.AppSearchProvider());
|
||||
this.addSearchProvider(new AppDisplay.SettingsSearchProvider());
|
||||
this.addSearchProvider(new PlaceDisplay.PlaceSearchProvider());
|
||||
@ -587,13 +587,10 @@ const Overview = new Lang.Class({
|
||||
|
||||
this._workspacesDisplay.show();
|
||||
|
||||
this.workspaces = this._workspacesDisplay.workspacesView;
|
||||
global.overlay_group.add_actor(this.workspaces.actor);
|
||||
|
||||
if (!this._desktopFade.child)
|
||||
this._desktopFade.child = this._getDesktopClone();
|
||||
|
||||
if (!this.workspaces.getActiveWorkspace().hasMaximizedWindows()) {
|
||||
if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows()) {
|
||||
this._desktopFade.opacity = 255;
|
||||
this._desktopFade.show();
|
||||
Tweener.addTween(this._desktopFade,
|
||||
@ -728,7 +725,7 @@ const Overview = new Lang.Class({
|
||||
this.animationInProgress = true;
|
||||
this._hideInProgress = true;
|
||||
|
||||
if (!this.workspaces.getActiveWorkspace().hasMaximizedWindows()) {
|
||||
if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows()) {
|
||||
this._desktopFade.opacity = 0;
|
||||
this._desktopFade.show();
|
||||
Tweener.addTween(this._desktopFade,
|
||||
@ -737,7 +734,7 @@ const Overview = new Lang.Class({
|
||||
transition: 'easeOutQuad' });
|
||||
}
|
||||
|
||||
this.workspaces.hide();
|
||||
this._workspacesDisplay.zoomFromOverview();
|
||||
|
||||
// Make other elements fade out.
|
||||
Tweener.addTween(this._group,
|
||||
@ -779,9 +776,6 @@ const Overview = new Lang.Class({
|
||||
|
||||
global.window_group.show();
|
||||
|
||||
this.workspaces.destroy();
|
||||
this.workspaces = null;
|
||||
|
||||
this._workspacesDisplay.hide();
|
||||
|
||||
this._desktopFade.hide();
|
||||
|
@ -3,6 +3,7 @@
|
||||
const Cairo = imports.cairo;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Pango = imports.gi.Pango;
|
||||
@ -235,11 +236,15 @@ const AppMenuButton = new Lang.Class({
|
||||
Name: 'AppMenuButton',
|
||||
Extends: PanelMenu.Button,
|
||||
|
||||
_init: function() {
|
||||
this.parent(0.0);
|
||||
_init: function(menuManager) {
|
||||
this.parent(0.0, true);
|
||||
|
||||
this._startingApps = [];
|
||||
|
||||
this._menuManager = menuManager;
|
||||
this._targetApp = null;
|
||||
this._appMenuNotifyId = 0;
|
||||
this._actionGroupNotifyId = 0;
|
||||
|
||||
let bin = new St.Bin({ name: 'appMenu' });
|
||||
this.actor.add_actor(bin);
|
||||
@ -264,10 +269,6 @@ const AppMenuButton = new Lang.Class({
|
||||
|
||||
this._iconBottomClip = 0;
|
||||
|
||||
this._quitMenu = new PopupMenu.PopupMenuItem('');
|
||||
this.menu.addMenuItem(this._quitMenu);
|
||||
this._quitMenu.connect('activate', Lang.bind(this, this._onQuit));
|
||||
|
||||
this._visible = !Main.overview.visible;
|
||||
if (!this._visible)
|
||||
this.actor.hide();
|
||||
@ -446,12 +447,6 @@ const AppMenuButton = new Lang.Class({
|
||||
}
|
||||
},
|
||||
|
||||
_onQuit: function() {
|
||||
if (this._targetApp == null)
|
||||
return;
|
||||
this._targetApp.request_quit();
|
||||
},
|
||||
|
||||
_onAppStateChanged: function(appSys, app) {
|
||||
let state = app.state;
|
||||
if (state != Shell.AppState.STARTING) {
|
||||
@ -513,8 +508,10 @@ const AppMenuButton = new Lang.Class({
|
||||
}
|
||||
|
||||
if (targetApp == this._targetApp) {
|
||||
if (targetApp && targetApp.get_state() != Shell.AppState.STARTING)
|
||||
if (targetApp && targetApp.get_state() != Shell.AppState.STARTING) {
|
||||
this.stopAnimation();
|
||||
this._maybeSetMenu();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -524,20 +521,53 @@ const AppMenuButton = new Lang.Class({
|
||||
this._iconBox.hide();
|
||||
this._label.setText('');
|
||||
|
||||
if (this._appMenuNotifyId)
|
||||
this._targetApp.disconnect(this._appMenuNotifyId);
|
||||
if (this._actionGroupNotifyId)
|
||||
this._targetApp.disconnect(this._actionGroupNotifyId);
|
||||
if (targetApp) {
|
||||
this._appMenuNotifyId = targetApp.connect('notify::menu', Lang.bind(this, this._sync));
|
||||
this._actionGroupNotifyId = targetApp.connect('notify::action-group', Lang.bind(this, this._sync));
|
||||
}
|
||||
|
||||
this._targetApp = targetApp;
|
||||
let icon = targetApp.get_faded_icon(2 * PANEL_ICON_SIZE);
|
||||
|
||||
this._label.setText(targetApp.get_name());
|
||||
// TODO - _quit() doesn't really work on apps in state STARTING yet
|
||||
this._quitMenu.label.set_text(_("Quit %s").format(targetApp.get_name()));
|
||||
|
||||
this._iconBox.set_child(icon);
|
||||
this._iconBox.show();
|
||||
|
||||
if (targetApp.get_state() == Shell.AppState.STARTING)
|
||||
this.startAnimation();
|
||||
else
|
||||
this._maybeSetMenu();
|
||||
|
||||
this.emit('changed');
|
||||
},
|
||||
|
||||
_maybeSetMenu: function() {
|
||||
let menu;
|
||||
|
||||
if (this._targetApp.action_group) {
|
||||
if (this.menu instanceof PopupMenu.RemoteMenu &&
|
||||
this.menu.actionGroup == this._targetApp.action_group)
|
||||
return;
|
||||
|
||||
menu = new PopupMenu.RemoteMenu(this.actor, this._targetApp.menu, this._targetApp.action_group);
|
||||
} else {
|
||||
if (this.menu && !(this.menu instanceof PopupMenu.RemoteMenu))
|
||||
return;
|
||||
|
||||
// fallback to older menu
|
||||
menu = new PopupMenu.PopupMenu(this.actor, 0.0, St.Side.TOP, 0);
|
||||
menu.addAction(_("Quit"), Lang.bind(this, function() {
|
||||
this._targetApp.request_quit();
|
||||
}));
|
||||
}
|
||||
|
||||
this.setMenu(menu);
|
||||
this._menuManager.addMenu(menu);
|
||||
}
|
||||
});
|
||||
|
||||
@ -924,9 +954,8 @@ const Panel = new Lang.Class({
|
||||
// more cleanly with the rest of the panel
|
||||
this._menus.addMenu(this._activitiesButton.menu);
|
||||
|
||||
this._appMenu = new AppMenuButton();
|
||||
this._appMenu = new AppMenuButton(this._menus);
|
||||
this._leftBox.add(this._appMenu.actor);
|
||||
this._menus.addMenu(this._appMenu.menu);
|
||||
}
|
||||
|
||||
/* center */
|
||||
|
@ -96,22 +96,39 @@ const Button = new Lang.Class({
|
||||
Name: 'PanelMenuButton',
|
||||
Extends: ButtonBox,
|
||||
|
||||
_init: function(menuAlignment) {
|
||||
_init: function(menuAlignment, dontCreateMenu) {
|
||||
this.parent({ reactive: true,
|
||||
can_focus: true,
|
||||
track_hover: true });
|
||||
|
||||
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
|
||||
this.actor.connect('key-press-event', Lang.bind(this, this._onSourceKeyPress));
|
||||
this.menu = new PopupMenu.PopupMenu(this.actor, menuAlignment, St.Side.TOP);
|
||||
this.menu.actor.add_style_class_name('panel-menu');
|
||||
this.menu.connect('open-state-changed', Lang.bind(this, this._onOpenStateChanged));
|
||||
this.menu.actor.connect('key-press-event', Lang.bind(this, this._onMenuKeyPress));
|
||||
Main.uiGroup.add_actor(this.menu.actor);
|
||||
this.menu.actor.hide();
|
||||
|
||||
if (dontCreateMenu)
|
||||
this.menu = null;
|
||||
else
|
||||
this.setMenu(new PopupMenu.PopupMenu(this.actor, menuAlignment, St.Side.TOP, 0));
|
||||
},
|
||||
|
||||
setMenu: function(menu) {
|
||||
if (this.menu)
|
||||
this.menu.destroy();
|
||||
|
||||
this.menu = menu;
|
||||
if (this.menu) {
|
||||
this.menu.actor.add_style_class_name('panel-menu');
|
||||
this.menu.connect('open-state-changed', Lang.bind(this, this._onOpenStateChanged));
|
||||
this.menu.actor.connect('key-press-event', Lang.bind(this, this._onMenuKeyPress));
|
||||
|
||||
Main.uiGroup.add_actor(this.menu.actor);
|
||||
this.menu.actor.hide();
|
||||
}
|
||||
},
|
||||
|
||||
_onButtonPress: function(actor, event) {
|
||||
if (!this.menu)
|
||||
return;
|
||||
|
||||
if (!this.menu.isOpen) {
|
||||
// Setting the max-height won't do any good if the minimum height of the
|
||||
// menu is higher then the screen; it's useful if part of the menu is
|
||||
@ -125,6 +142,9 @@ const Button = new Lang.Class({
|
||||
},
|
||||
|
||||
_onSourceKeyPress: function(actor, event) {
|
||||
if (!this.menu)
|
||||
return false;
|
||||
|
||||
let symbol = event.get_key_symbol();
|
||||
if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return) {
|
||||
this.menu.toggle();
|
||||
|
@ -2,7 +2,9 @@
|
||||
|
||||
const Cairo = imports.cairo;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
@ -937,7 +939,12 @@ const PopupMenuBase = new Lang.Class({
|
||||
this.emit('activate', menuItem);
|
||||
this.close(true);
|
||||
}));
|
||||
menuItem.connect('destroy', Lang.bind(this, function(emitter) {
|
||||
// the weird name is to avoid a conflict with some random property
|
||||
// the menuItem may have, called destroyId
|
||||
// (FIXME: in the future it may make sense to have container objects
|
||||
// like PopupMenuManager does)
|
||||
menuItem._popupMenuDestroyId = menuItem.connect('destroy', Lang.bind(this, function(menuItem) {
|
||||
menuItem.disconnect(menuItem._popupMenuDestroyId);
|
||||
menuItem.disconnect(menuItem._activateId);
|
||||
menuItem.disconnect(menuItem._activeChangeId);
|
||||
menuItem.disconnect(menuItem._sensitiveChangeId);
|
||||
@ -1692,6 +1699,246 @@ const PopupComboBoxMenuItem = new Lang.Class({
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* RemoteMenu:
|
||||
*
|
||||
* A PopupMenu that tracks a GMenuModel and shows its actions
|
||||
* (exposed by GApplication/GActionGroup)
|
||||
*/
|
||||
const RemoteMenu = new Lang.Class({
|
||||
Name: 'RemoteMenu',
|
||||
Extends: PopupMenu,
|
||||
|
||||
_init: function(sourceActor, model, actionGroup) {
|
||||
this.parent(sourceActor, 0.0, St.Side.TOP);
|
||||
|
||||
this.model = model;
|
||||
this.actionGroup = actionGroup;
|
||||
|
||||
this._actions = { };
|
||||
this._modelChanged(this.model, 0, 0, this.model.get_n_items(), this);
|
||||
|
||||
this._actionStateChangeId = this.actionGroup.connect('action-state-changed', Lang.bind(this, this._actionStateChanged));
|
||||
this._actionEnableChangeId = this.actionGroup.connect('action-enabled-changed', Lang.bind(this, this._actionEnabledChanged));
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this._actionStateChangeId) {
|
||||
this.actionGroup.disconnect(this._actionStateChangeId);
|
||||
this._actionStateChangeId = 0;
|
||||
}
|
||||
|
||||
if (this._actionEnableChangeId) {
|
||||
this.actionGroup.disconnect(this._actionEnableChangeId);
|
||||
this._actionEnableChangeId = 0;
|
||||
}
|
||||
|
||||
this.parent();
|
||||
},
|
||||
|
||||
_createMenuItem: function(model, index) {
|
||||
let section_link = model.get_item_link(index, Gio.MENU_LINK_SECTION);
|
||||
if (section_link) {
|
||||
let item = new PopupMenuSection();
|
||||
this._modelChanged(section_link, 0, 0, section_link.get_n_items(), item);
|
||||
return [item, true, ''];
|
||||
}
|
||||
|
||||
// labels are not checked for existance, as they're required for all items
|
||||
let label = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_LABEL, null).deep_unpack();
|
||||
// remove all underscores that are not followed by another underscore
|
||||
label = label.replace(/_([^_])/, '$1');
|
||||
let submenu_link = model.get_item_link(index, Gio.MENU_LINK_SUBMENU);
|
||||
|
||||
if (submenu_link) {
|
||||
let item = new PopupSubMenuMenuItem(label);
|
||||
this._modelChanged(submenu_link, 0, 0, submenu_link.get_n_items(), item.menu);
|
||||
return [item, false, ''];
|
||||
}
|
||||
|
||||
let action_id = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_ACTION, null).deep_unpack();
|
||||
if (!this.actionGroup.has_action(action_id)) {
|
||||
// the action may not be there yet, wait for action-added
|
||||
return [null, false, 'action-added'];
|
||||
}
|
||||
|
||||
if (!this._actions[action_id])
|
||||
this._actions[action_id] = { enabled: this.actionGroup.get_action_enabled(action_id),
|
||||
state: this.actionGroup.get_action_state(action_id),
|
||||
items: [ ],
|
||||
};
|
||||
let action = this._actions[action_id];
|
||||
let item, target, destroyId, specificSignalId;
|
||||
|
||||
if (action.state) {
|
||||
// Docs have get_state_hint(), except that the DBus protocol
|
||||
// has no provision for it (so ShellApp does not implement it,
|
||||
// and neither GApplication), and g_action_get_state_hint()
|
||||
// always returns null
|
||||
// Funny :)
|
||||
|
||||
switch (String.fromCharCode(action.state.classify())) {
|
||||
case 'b':
|
||||
item = new PopupSwitchMenuItem(label, action.state.get_boolean());
|
||||
action.items.push(item);
|
||||
specificSignalId = item.connect('toggled', Lang.bind(this, function(item) {
|
||||
this.actionGroup.activate_action(action_id, null);
|
||||
}));
|
||||
break;
|
||||
case 's':
|
||||
item = new PopupMenuItem(label);
|
||||
item._remoteTarget = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null).deep_unpack();
|
||||
action.items.push(item);
|
||||
item.setShowDot(action.state.deep_unpack() == item._remoteTarget);
|
||||
specificSignalId = item.connect('activate', Lang.bind(this, function(item) {
|
||||
this.actionGroup.activate_action(action_id, GLib.Variant.new_string(item._remoteTarget));
|
||||
}));
|
||||
break;
|
||||
default:
|
||||
log('Action "%s" has state of type %s, which is not supported'.format(action_id, action.state.get_type_string()));
|
||||
return [null, false, 'action-state-changed'];
|
||||
}
|
||||
} else {
|
||||
target = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null);
|
||||
item = new PopupMenuItem(label);
|
||||
action.items.push(item);
|
||||
specificSignalId = item.connect('activate', Lang.bind(this, function() {
|
||||
this.actionGroup.activate_action(action_id, target);
|
||||
}));
|
||||
}
|
||||
|
||||
item.actor.reactive = item.actor.can_focus = action.enabled;
|
||||
if (action.enabled)
|
||||
item.actor.remove_style_pseudo_class('insensitive');
|
||||
else
|
||||
item.actor.add_style_pseudo_class('insensitive');
|
||||
|
||||
destroyId = item.connect('destroy', Lang.bind(this, function() {
|
||||
item.disconnect(destroyId);
|
||||
item.disconnect(specificSignalId);
|
||||
|
||||
let pos = action.items.indexOf(item);
|
||||
if (pos != -1)
|
||||
action.items.splice(pos, 1);
|
||||
}));
|
||||
|
||||
return [item, false, ''];
|
||||
},
|
||||
|
||||
_modelChanged: function(model, position, removed, added, target) {
|
||||
let j, k;
|
||||
let j0, k0;
|
||||
|
||||
let currentItems = target._getMenuItems();
|
||||
|
||||
for (j0 = 0, k0 = 0; j0 < position; j0++, k0++) {
|
||||
if (currentItems[k0] instanceof PopupSeparatorMenuItem)
|
||||
k0++;
|
||||
}
|
||||
|
||||
if (removed == -1) {
|
||||
// special flag to indicate we should destroy everything
|
||||
for (k = k0; k < currentItems.length; k++)
|
||||
currentItems[k].destroy();
|
||||
} else {
|
||||
for (j = j0, k = k0; j < j0 + removed; j++, k++) {
|
||||
currentItems[k].destroy();
|
||||
|
||||
if (currentItems[k] instanceof PopupSeparatorMenuItem)
|
||||
j--;
|
||||
}
|
||||
}
|
||||
|
||||
for (j = j0, k = k0; j < j0 + added; j++, k++) {
|
||||
let [item, addSeparator, changeSignal] = this._createMenuItem(model, j);
|
||||
|
||||
if (item) {
|
||||
// separators must be added in the parent to make autohiding work
|
||||
if (addSeparator) {
|
||||
target.addMenuItem(new PopupSeparatorMenuItem(), k+1);
|
||||
k++;
|
||||
}
|
||||
|
||||
target.addMenuItem(item, k);
|
||||
|
||||
if (addSeparator) {
|
||||
target.addMenuItem(new PopupSeparatorMenuItem(), k+1);
|
||||
k++;
|
||||
}
|
||||
} else if (changeSignal) {
|
||||
let signalId = this.actionGroup.connect(changeSignal, Lang.bind(this, function() {
|
||||
this.actionGroup.disconnect(signalId);
|
||||
|
||||
// force a full update
|
||||
this._modelChanged(model, 0, -1, model.get_n_items(), target);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if (!model._changedId) {
|
||||
model._changedId = model.connect('items-changed', Lang.bind(this, this._modelChanged, target));
|
||||
model._destroyId = target.connect('destroy', function() {
|
||||
if (model._changedId)
|
||||
model.disconnect(model._changedId);
|
||||
if (model._destroyId)
|
||||
target.disconnect(model._destroyId);
|
||||
model._changedId = 0;
|
||||
model._destroyId = 0;
|
||||
});
|
||||
}
|
||||
|
||||
if (target instanceof PopupMenuSection) {
|
||||
target.actor.visible = target.numMenuItems != 0;
|
||||
} else {
|
||||
let sourceItem = target.sourceActor._delegate;
|
||||
if (sourceItem instanceof PopupSubMenuMenuItem)
|
||||
sourceItem.actor.visible = target.numMenuItems != 0;
|
||||
}
|
||||
},
|
||||
|
||||
_actionStateChanged: function(actionGroup, action_id) {
|
||||
let action = this._actions[action_id];
|
||||
if (!action)
|
||||
return;
|
||||
|
||||
action.state = actionGroup.get_action_state(action_id);
|
||||
if (action.items.length) {
|
||||
switch (String.fromCharCode(action.state.classify())) {
|
||||
case 'b':
|
||||
for (let i = 0; i < action.items.length; i++)
|
||||
action.items[i].setToggleState(action.state.get_boolean());
|
||||
break;
|
||||
case 'd':
|
||||
for (let i = 0; i < action.items.length; i++)
|
||||
action.items[i].setValue(action.state.get_double());
|
||||
break;
|
||||
case 's':
|
||||
for (let i = 0; i < action.items.length; i++)
|
||||
action.items[i].setShowDot(action.items[i]._remoteTarget == action.state.deep_unpack());
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_actionEnabledChanged: function(actionGroup, action_id) {
|
||||
let action = this._actions[action_id];
|
||||
if (!action)
|
||||
return;
|
||||
|
||||
action.enabled = actionGroup.get_action_enabled(action_id);
|
||||
if (action.items.length) {
|
||||
for (let i = 0; i < action.items.length; i++) {
|
||||
let item = action.items[i];
|
||||
item.actor.reactive = item.actor.can_focus = action.enabled;
|
||||
|
||||
if (action.enabled)
|
||||
item.actor.remove_style_pseudo_class('insensitive');
|
||||
else
|
||||
item.actor.add_style_pseudo_class('insensitive');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Basic implementation of a menu manager.
|
||||
* Call addMenu to add menus
|
||||
*/
|
||||
|
@ -30,7 +30,6 @@ const Indicator = new Lang.Class({
|
||||
_init: function() {
|
||||
this.parent('bluetooth-disabled', null);
|
||||
|
||||
GLib.spawn_command_line_sync ('pkill -f "^bluetooth-applet$"');
|
||||
this._applet = new GnomeBluetoothApplet.Applet();
|
||||
|
||||
this._killswitch = new PopupMenu.PopupSwitchMenuItem(_("Bluetooth"), false);
|
||||
|
@ -249,9 +249,11 @@ const NMWirelessSectionTitleMenuItem = new Lang.Class({
|
||||
updateForDevice: function(device) {
|
||||
// we show the switch
|
||||
// - if there not just one device
|
||||
// - if the switch is off
|
||||
// - if the switch is off (but it can be turned on)
|
||||
// - if the device is activated or disconnected
|
||||
if (device && this._softwareEnabled && this._hardwareEnabled) {
|
||||
if (!this._hardwareEnabled) {
|
||||
this.setStatus(_("hardware disabled"));
|
||||
} else if (device && this._softwareEnabled) {
|
||||
let text = device.getStatusLabel();
|
||||
this.setStatus(text);
|
||||
} else
|
||||
@ -327,7 +329,7 @@ const NMDevice = new Lang.Class({
|
||||
}
|
||||
this.section = new PopupMenu.PopupMenuSection();
|
||||
|
||||
this._createSection();
|
||||
this._deferredWorkId = Main.initializeDeferredWork(this.section.actor, Lang.bind(this, this._createSection));
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
@ -394,16 +396,29 @@ const NMDevice = new Lang.Class({
|
||||
this._activeConnection = activeConnection;
|
||||
|
||||
this._clearSection();
|
||||
this._createSection();
|
||||
this._queueCreateSection();
|
||||
},
|
||||
|
||||
checkConnection: function(connection) {
|
||||
let exists = this._findConnection(connection._uuid) != -1;
|
||||
let pos = this._findConnection(connection._uuid);
|
||||
let exists = pos != -1;
|
||||
let valid = this.connectionValid(connection);
|
||||
|
||||
if (exists && !valid)
|
||||
this.removeConnection(connection);
|
||||
else if (!exists && valid)
|
||||
this.addConnection(connection);
|
||||
else if (exists && valid) {
|
||||
// propagate changes and update the UI
|
||||
|
||||
if (this._connections[pos].timestamp != connection._timestamp) {
|
||||
this._connections[pos].timestamp = connection._timestamp;
|
||||
this._connections.sort(this._connectionSortFunction);
|
||||
|
||||
this._clearSection();
|
||||
this._queueCreateSection();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addConnection: function(connection) {
|
||||
@ -418,7 +433,7 @@ const NMDevice = new Lang.Class({
|
||||
this._connections.sort(this._connectionSortFunction);
|
||||
|
||||
this._clearSection();
|
||||
this._createSection();
|
||||
this._queueCreateSection();
|
||||
},
|
||||
|
||||
removeConnection: function(connection) {
|
||||
@ -442,7 +457,7 @@ const NMDevice = new Lang.Class({
|
||||
// (or in the case of NMDeviceWired, we want to hide
|
||||
// the only explicit connection)
|
||||
this._clearSection();
|
||||
this._createSection();
|
||||
this._queueCreateSection();
|
||||
}
|
||||
},
|
||||
|
||||
@ -526,6 +541,11 @@ const NMDevice = new Lang.Class({
|
||||
return -1;
|
||||
},
|
||||
|
||||
_queueCreateSection: function() {
|
||||
this._clearSection();
|
||||
Main.queueDeferredWork(this._deferredWorkId);
|
||||
},
|
||||
|
||||
_clearSection: function() {
|
||||
// Clear everything
|
||||
this.section.removeAll();
|
||||
@ -620,7 +640,7 @@ const NMDevice = new Lang.Class({
|
||||
this._updateStatusItem();
|
||||
|
||||
this._clearSection();
|
||||
this._createSection();
|
||||
this._queueCreateSection();
|
||||
this.emit('state-changed');
|
||||
},
|
||||
|
||||
@ -864,7 +884,12 @@ const NMDeviceBluetooth = new Lang.Class({
|
||||
this._autoConnectionName = this._makeConnectionName(this.device);
|
||||
|
||||
this._clearSection();
|
||||
this._createSection();
|
||||
this._queueCreateSection();
|
||||
this._updateStatusItem();
|
||||
},
|
||||
|
||||
_getDescription: function() {
|
||||
return this.device.name || _("Bluetooth");
|
||||
}
|
||||
});
|
||||
|
||||
@ -1209,7 +1234,6 @@ const NMDeviceWireless = new Lang.Class({
|
||||
accessPoints: [ accessPoint ]
|
||||
};
|
||||
apObj.ssidText = ssidToLabel(apObj.ssid);
|
||||
needsupdate = true;
|
||||
}
|
||||
|
||||
// check if this enables new connections for this group
|
||||
@ -1224,37 +1248,13 @@ const NMDeviceWireless = new Lang.Class({
|
||||
}
|
||||
}
|
||||
|
||||
if (needsupdate) {
|
||||
if (apObj.item)
|
||||
apObj.item.destroy();
|
||||
|
||||
if (pos == -1 || needsupdate) {
|
||||
if (pos != -1)
|
||||
this._networks.splice(pos, 1);
|
||||
pos = Util.insertSorted(this._networks, apObj, this._networkSortFunction);
|
||||
|
||||
if (this._networks.length == 0) {
|
||||
// only network in the list
|
||||
this._networks.push(apObj);
|
||||
this._clearSection();
|
||||
this._createSection();
|
||||
return;
|
||||
}
|
||||
|
||||
// skip networks that should appear earlier
|
||||
let menuPos = 0;
|
||||
for (pos = 0;
|
||||
pos < this._networks.length &&
|
||||
this._networkSortFunction(this._networks[pos], apObj) < 0; ++pos) {
|
||||
if (this._networks[pos] != this._activeNetwork)
|
||||
menuPos++;
|
||||
}
|
||||
|
||||
// (re-)add the network
|
||||
this._networks.splice(pos, 0, apObj);
|
||||
|
||||
if (this._shouldShowConnectionList()) {
|
||||
menuPos += (this._activeConnectionItem ? 1 : 0);
|
||||
this._createNetworkItem(apObj, menuPos);
|
||||
}
|
||||
this._clearSection();
|
||||
this._queueCreateSection();
|
||||
}
|
||||
},
|
||||
|
||||
@ -1341,7 +1341,7 @@ const NMDeviceWireless = new Lang.Class({
|
||||
let obj = this._connections[pos];
|
||||
this._connections.splice(pos, 1);
|
||||
|
||||
let anyauto = false, forceupdate = false;
|
||||
let forceupdate = false;
|
||||
for (let i = 0; i < this._networks.length; i++) {
|
||||
let apObj = this._networks[i];
|
||||
let connections = apObj.connections;
|
||||
@ -1349,16 +1349,14 @@ const NMDeviceWireless = new Lang.Class({
|
||||
if (connections[k]._uuid == connection._uuid) {
|
||||
// remove the connection from the access point group
|
||||
connections.splice(k);
|
||||
anyauto = connections.length == 0;
|
||||
forceupdate = forceupdate || connections.length == 0;
|
||||
|
||||
if (anyauto) {
|
||||
// this potentially changes the sorting order
|
||||
forceupdate = true;
|
||||
if (forceupdate)
|
||||
break;
|
||||
}
|
||||
|
||||
if (apObj.item) {
|
||||
if (apObj.item instanceof PopupMenu.PopupSubMenuMenuItem) {
|
||||
let items = apObj.item.menu.getMenuItems();
|
||||
let items = apObj.item.menu._getMenuItems();
|
||||
if (items.length == 2) {
|
||||
// we need to update the connection list to convert this to a normal item
|
||||
forceupdate = true;
|
||||
@ -1380,10 +1378,10 @@ const NMDeviceWireless = new Lang.Class({
|
||||
}
|
||||
}
|
||||
|
||||
if (forceupdate || anyauto) {
|
||||
if (forceupdate) {
|
||||
this._networks.sort(this._networkSortFunction);
|
||||
this._clearSection();
|
||||
this._createSection();
|
||||
this._queueCreateSection();
|
||||
}
|
||||
},
|
||||
|
||||
@ -1416,13 +1414,13 @@ const NMDeviceWireless = new Lang.Class({
|
||||
if (forceupdate) {
|
||||
this._networks.sort(this._networkSortFunction);
|
||||
this._clearSection();
|
||||
this._createSection();
|
||||
this._queueCreateSection();
|
||||
}
|
||||
},
|
||||
|
||||
_createActiveConnectionItem: function() {
|
||||
let icon, title;
|
||||
if (this._activeConnection._connection) {
|
||||
if (this._activeConnection && this._activeConnection._connection) {
|
||||
let connection = this._activeConnection._connection;
|
||||
if (this._activeNetwork)
|
||||
this._activeConnectionItem = new NMNetworkMenuItem(this._activeNetwork.accessPoints, undefined,
|
||||
@ -1515,7 +1513,7 @@ const NMDeviceWireless = new Lang.Class({
|
||||
if (!this._shouldShowConnectionList())
|
||||
return;
|
||||
|
||||
if(this._activeConnection) {
|
||||
if (this._activeNetwork) {
|
||||
this._createActiveConnectionItem();
|
||||
this.section.addMenuItem(this._activeConnectionItem);
|
||||
}
|
||||
@ -1524,8 +1522,10 @@ const NMDeviceWireless = new Lang.Class({
|
||||
|
||||
for(let j = 0; j < this._networks.length; j++) {
|
||||
let apObj = this._networks[j];
|
||||
if (apObj == this._activeNetwork)
|
||||
if (apObj == this._activeNetwork) {
|
||||
activeOffset--;
|
||||
continue;
|
||||
}
|
||||
|
||||
this._createNetworkItem(apObj, j + activeOffset);
|
||||
}
|
||||
|
@ -197,11 +197,11 @@ const Client = new Lang.Class({
|
||||
|
||||
_handleChannels: function(handler, account, conn, channels,
|
||||
requests, user_action_time, context) {
|
||||
this._handlingChannels(account, conn, channels);
|
||||
this._handlingChannels(account, conn, channels, true);
|
||||
context.accept();
|
||||
},
|
||||
|
||||
_handlingChannels: function(account, conn, channels) {
|
||||
_handlingChannels: function(account, conn, channels, notify) {
|
||||
let len = channels.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
let channel = channels[i];
|
||||
@ -212,7 +212,18 @@ const Client = new Lang.Class({
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this._tpClient.is_handling_channel(channel)) {
|
||||
// 'notify' will be true when coming from an actual HandleChannels
|
||||
// call, and not when from a successful Claim call. The point is
|
||||
// we don't want to notify for a channel we just claimed which
|
||||
// has no new messages (for example, a new channel which only has
|
||||
// a delivery notification). We rely on _displayPendingMessages()
|
||||
// and _messageReceived() to notify for new messages.
|
||||
|
||||
// But we should still notify from HandleChannels because the
|
||||
// Telepathy spec states that handlers must foreground channels
|
||||
// in HandleChannels calls which are already being handled.
|
||||
|
||||
if (notify && this._tpClient.is_handling_channel(channel)) {
|
||||
// We are already handling the channel, display the source
|
||||
let source = this._chatSources[channel.get_object_path()];
|
||||
if (source)
|
||||
@ -285,7 +296,7 @@ const Client = new Lang.Class({
|
||||
Lang.bind(this, function(dispatchOp, result) {
|
||||
try {
|
||||
dispatchOp.claim_with_finish(result);
|
||||
this._handlingChannels(account, conn, [channel]);
|
||||
this._handlingChannels(account, conn, [channel], false);
|
||||
} catch (err) {
|
||||
throw new Error('Failed to Claim channel: ' + err);
|
||||
}}));
|
||||
@ -502,15 +513,9 @@ const ChatSource = new Lang.Class({
|
||||
this._notification.setUrgency(MessageTray.Urgency.HIGH);
|
||||
this._notifyTimeoutId = 0;
|
||||
|
||||
// We ack messages when the message box is collapsed if user has
|
||||
// interacted with it before and so read the messages:
|
||||
// - user clicked on it the tray
|
||||
// - user expanded the notification by hovering over the toaster notification
|
||||
this._shouldAck = false;
|
||||
|
||||
this.connect('summary-item-clicked', Lang.bind(this, this._summaryItemClicked));
|
||||
this._notification.connect('expanded', Lang.bind(this, this._notificationExpanded));
|
||||
this._notification.connect('collapsed', Lang.bind(this, this._notificationCollapsed));
|
||||
// We ack messages when the user expands the new notification or views the summary
|
||||
// notification, in which case the notification is also expanded.
|
||||
this._notification.connect('expanded', Lang.bind(this, this._ackMessages));
|
||||
|
||||
this._presence = contact.get_presence_type();
|
||||
|
||||
@ -774,24 +779,6 @@ const ChatSource = new Lang.Class({
|
||||
// 'pending-message-removed' for each one.
|
||||
this._channel.ack_all_pending_messages_async(Lang.bind(this, function(src, result) {
|
||||
this._channel.ack_all_pending_messages_finish(result);}));
|
||||
},
|
||||
|
||||
_summaryItemClicked: function(source, button) {
|
||||
if (button != 1)
|
||||
return;
|
||||
|
||||
this._shouldAck = true;
|
||||
},
|
||||
|
||||
_notificationExpanded: function() {
|
||||
this._shouldAck = true;
|
||||
},
|
||||
|
||||
_notificationCollapsed: function() {
|
||||
if (this._shouldAck)
|
||||
this._ackMessages();
|
||||
|
||||
this._shouldAck = false;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -419,29 +419,6 @@ const ViewSelector = new Lang.Class({
|
||||
// not when setting the initially selected one.
|
||||
if (!tab.visible)
|
||||
tab.show(!firstSwitch);
|
||||
|
||||
// Pull a Meg Ryan:
|
||||
if (!firstSwitch && Main.overview.workspaces) {
|
||||
if (tab != this._tabs[0]) {
|
||||
Tweener.addTween(Main.overview.workspaces.actor,
|
||||
{ opacity: 0,
|
||||
time: 0.1,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this,
|
||||
function() {
|
||||
Main.overview.workspaces.actor.hide();
|
||||
Main.overview.workspaces.actor.opacity = 255;
|
||||
})
|
||||
});
|
||||
} else {
|
||||
Main.overview.workspaces.actor.opacity = 0;
|
||||
Main.overview.workspaces.actor.show();
|
||||
Tweener.addTween(Main.overview.workspaces.actor,
|
||||
{ opacity: 255,
|
||||
time: 0.1,
|
||||
transition: 'easeOutQuad' });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
switchTab: function(id) {
|
||||
|
210
js/ui/wanda.js
Normal file
210
js/ui/wanda.js
Normal file
@ -0,0 +1,210 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GdkPixbuf = imports.gi.GdkPixbuf;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const IconGrid = imports.ui.iconGrid;
|
||||
const Main = imports.ui.main;
|
||||
const Search = imports.ui.search;
|
||||
|
||||
// we could make these gsettings
|
||||
const FISH_NAME = 'wanda';
|
||||
const FISH_SPEED = 300;
|
||||
const FISH_COMMAND = 'fortune';
|
||||
|
||||
const GNOME_PANEL_PIXMAPDIR = '../gnome-panel/pixmaps';
|
||||
const FISH_GROUP = 'Fish Animation';
|
||||
|
||||
const MAGIC_FISH_KEY = 'free the fish';
|
||||
|
||||
const WandaIcon = new Lang.Class({
|
||||
Name: 'WandaIcon',
|
||||
Extends: IconGrid.BaseIcon,
|
||||
|
||||
_init : function(fish, label, params) {
|
||||
this._fish = fish;
|
||||
let file = GLib.build_filenamev([global.datadir, GNOME_PANEL_PIXMAPDIR, fish + '.fish']);
|
||||
|
||||
if (GLib.file_test(file, GLib.FileTest.EXISTS)) {
|
||||
this._keyfile = new GLib.KeyFile();
|
||||
this._keyfile.load_from_file(file, GLib.KeyFileFlags.NONE);
|
||||
|
||||
this._imageFile = GLib.build_filenamev([global.datadir, GNOME_PANEL_PIXMAPDIR,
|
||||
this._keyfile.get_string(FISH_GROUP, 'image')]);
|
||||
|
||||
let tmpPixbuf = GdkPixbuf.Pixbuf.new_from_file(this._imageFile);
|
||||
|
||||
this._imgHeight = tmpPixbuf.height;
|
||||
this._imgWidth = tmpPixbuf.width / this._keyfile.get_integer(FISH_GROUP, 'frames');
|
||||
} else {
|
||||
this._imageFile = null;
|
||||
}
|
||||
|
||||
this.parent(label, params);
|
||||
},
|
||||
|
||||
createIcon: function(iconSize) {
|
||||
if (this._animations)
|
||||
this._animations.destroy();
|
||||
|
||||
if (!this._imageFile) {
|
||||
return new St.Icon({ icon_name: 'face-smile',
|
||||
icon_type: St.IconType.FULLCOLOR,
|
||||
icon_size: iconSize
|
||||
});
|
||||
}
|
||||
|
||||
this._animations = St.TextureCache.get_default().load_sliced_image(this._imageFile, this._imgWidth, this._imgHeight);
|
||||
this._animations.connect('destroy', Lang.bind(this, function() {
|
||||
if (this._timeoutId)
|
||||
GLib.source_remove(this._timeoutId);
|
||||
this._timeoutId = 0;
|
||||
this._animations = null;
|
||||
}));
|
||||
this._animations.connect('notify::mapped', Lang.bind(this, function() {
|
||||
if (this._animations.mapped && !this._timeoutId) {
|
||||
this._timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, FISH_SPEED, Lang.bind(this, this._update));
|
||||
|
||||
this._i = 0;
|
||||
this._update();
|
||||
} else if (!this._animations.mapped && this._timeoutId) {
|
||||
GLib.source_remove(this._timeoutId);
|
||||
this._timeoutId = 0;
|
||||
}
|
||||
}));
|
||||
|
||||
this._i = 0;
|
||||
|
||||
return this._animations;
|
||||
},
|
||||
|
||||
_update: function() {
|
||||
let n = this._animations.get_n_children();
|
||||
if (n == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this._animations.get_nth_child(this._i).hide();
|
||||
this._i = (this._i + 1) % n;
|
||||
this._animations.get_nth_child(this._i).show();
|
||||
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
const WandaIconBin = new Lang.Class({
|
||||
Name: 'WandaIconBin',
|
||||
|
||||
_init: function(fish, label, params) {
|
||||
this.actor = new St.Bin({ style_class: 'search-result-content',
|
||||
reactive: true,
|
||||
track_hover: true });
|
||||
this.icon = new WandaIcon(fish, label, params);
|
||||
|
||||
this.actor.child = this.icon.actor;
|
||||
this.actor.label_actor = this.icon.label;
|
||||
},
|
||||
});
|
||||
|
||||
const FortuneDialog = new Lang.Class({
|
||||
Name: 'FortuneDialog',
|
||||
|
||||
_init: function(name, command) {
|
||||
let text;
|
||||
|
||||
try {
|
||||
let [res, stdout, stderr, status] = GLib.spawn_command_line_sync(command);
|
||||
text = String.fromCharCode.apply(null, stdout);
|
||||
} catch(e) {
|
||||
text = _("Sorry, no wisdom for you today:\n%s").format(e.message);
|
||||
}
|
||||
|
||||
this._title = new St.Label({ style_class: 'polkit-dialog-headline',
|
||||
text: _("%s the Oracle says").format(name) });
|
||||
this._label = new St.Label({ style_class: 'polkit-dialog-description',
|
||||
text: text });
|
||||
this._label.clutter_text.line_wrap = true;
|
||||
|
||||
this._box = new St.BoxLayout({ vertical: true,
|
||||
style_class: 'polkit-dialog' // this is just to force a reasonable width
|
||||
});
|
||||
this._box.add(this._title, { align: St.Align.MIDDLE });
|
||||
this._box.add(this._label, { expand: true });
|
||||
|
||||
this._button = new St.Button({ button_mask: St.ButtonMask.ONE,
|
||||
style_class: 'modal-dialog',
|
||||
reactive: true });
|
||||
this._button.connect('clicked', Lang.bind(this, this.destroy));
|
||||
this._button.child = this._box;
|
||||
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
|
||||
Main.layoutManager.addChrome(this._button);
|
||||
this._button.set_position(Math.floor(monitor.width / 2 - this._button.width / 2),
|
||||
Math.floor(monitor.height / 2 - this._button.height / 2));
|
||||
|
||||
GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 10, Lang.bind(this, this.destroy));
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this._button.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
function capitalize(str) {
|
||||
return str[0].toUpperCase() + str.substring(1, str.length);
|
||||
}
|
||||
|
||||
const WandaSearchProvider = new Lang.Class({
|
||||
Name: 'WandaSearchProvider',
|
||||
Extends: Search.SearchProvider,
|
||||
|
||||
_init: function() {
|
||||
this.parent(_("Your favorite Easter Egg"));
|
||||
},
|
||||
|
||||
getResultMeta: function(fish) {
|
||||
return { 'id': fish,
|
||||
'name': capitalize(fish),
|
||||
'createIcon': function(iconSize) {
|
||||
// for DND only (maybe could be improved)
|
||||
// DON'T use St.Icon here, it crashes the shell
|
||||
// (dnd.js code assumes it can query the actor size
|
||||
// without parenting it, while StWidget accesses
|
||||
// StThemeNode in get_preferred_width/height, which
|
||||
// triggers an assertion failure)
|
||||
return St.TextureCache.get_default().load_icon_name(null,
|
||||
'face-smile',
|
||||
St.IconType.FULLCOLOR,
|
||||
iconSize);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
getInitialResultSet: function(terms) {
|
||||
if (terms.join(' ') == MAGIC_FISH_KEY) {
|
||||
return [ FISH_NAME ];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
getSubsearchResultSet: function(previousResults, terms) {
|
||||
return this.getInitialResultSet(terms);
|
||||
},
|
||||
|
||||
activateResult: function(fish, params) {
|
||||
if (this._dialog)
|
||||
this._dialog.destroy();
|
||||
this._dialog = new FortuneDialog(capitalize(fish), FISH_COMMAND);
|
||||
},
|
||||
|
||||
createResultActor: function (resultMeta, terms) {
|
||||
let icon = new WandaIconBin(resultMeta.id, resultMeta.name);
|
||||
return icon.actor;
|
||||
}
|
||||
});
|
@ -94,10 +94,11 @@ const ScaledPoint = new Lang.Class({
|
||||
const WindowClone = new Lang.Class({
|
||||
Name: 'WindowClone',
|
||||
|
||||
_init : function(realWindow) {
|
||||
_init : function(realWindow, workspace) {
|
||||
this.realWindow = realWindow;
|
||||
this.metaWindow = realWindow.meta_window;
|
||||
this.metaWindow._delegate = this;
|
||||
this._workspace = workspace;
|
||||
|
||||
let [borderX, borderY] = this._getInvisibleBorderPadding();
|
||||
this._windowClone = new Clutter.Clone({ source: realWindow.get_texture(),
|
||||
@ -383,19 +384,12 @@ const WindowClone = new Lang.Class({
|
||||
this.emit('drag-begin');
|
||||
},
|
||||
|
||||
_getWorkspaceActor : function() {
|
||||
let index = this.metaWindow.get_workspace().index();
|
||||
return Main.overview.workspaces.getWorkspaceByIndex(index);
|
||||
},
|
||||
|
||||
handleDragOver : function(source, actor, x, y, time) {
|
||||
let workspace = this._getWorkspaceActor();
|
||||
return workspace.handleDragOver(source, actor, x, y, time);
|
||||
return this._workspace.handleDragOver(source, actor, x, y, time);
|
||||
},
|
||||
|
||||
acceptDrop : function(source, actor, x, y, time) {
|
||||
let workspace = this._getWorkspaceActor();
|
||||
workspace.acceptDrop(source, actor, x, y, time);
|
||||
this._workspace.acceptDrop(source, actor, x, y, time);
|
||||
},
|
||||
|
||||
_onDragCancelled : function (draggable, time) {
|
||||
@ -494,6 +488,9 @@ const WindowOverlay = new Lang.Class({
|
||||
},
|
||||
|
||||
fadeIn: function() {
|
||||
if (!this._hidden)
|
||||
return;
|
||||
|
||||
this.show();
|
||||
this.title.opacity = 0;
|
||||
this._parentActor.raise_top();
|
||||
@ -522,7 +519,7 @@ const WindowOverlay = new Lang.Class({
|
||||
// get_transformed_position() and get_transformed_size(),
|
||||
// as windowClone might be moving.
|
||||
// See Workspace._showWindowOverlay
|
||||
updatePositions: function(cloneX, cloneY, cloneWidth, cloneHeight) {
|
||||
updatePositions: function(cloneX, cloneY, cloneWidth, cloneHeight, animate) {
|
||||
let button = this.closeButton;
|
||||
let title = this.title;
|
||||
|
||||
@ -544,15 +541,34 @@ const WindowOverlay = new Lang.Class({
|
||||
else
|
||||
buttonX = cloneX + (cloneWidth - button._overlap);
|
||||
|
||||
button.set_position(Math.floor(buttonX), Math.floor(buttonY));
|
||||
if (animate)
|
||||
this._animateOverlayActor(button, Math.floor(buttonX), Math.floor(buttonY), button.width);
|
||||
else
|
||||
button.set_position(Math.floor(buttonX), Math.floor(buttonY));
|
||||
|
||||
if (!title.fullWidth)
|
||||
title.fullWidth = title.width;
|
||||
title.width = Math.min(title.fullWidth, cloneWidth);
|
||||
let titleWidth = Math.min(title.fullWidth, cloneWidth);
|
||||
|
||||
let titleX = cloneX + (cloneWidth - title.width) / 2;
|
||||
let titleX = cloneX + (cloneWidth - titleWidth) / 2;
|
||||
let titleY = cloneY + cloneHeight + title._spacing;
|
||||
title.set_position(Math.floor(titleX), Math.floor(titleY));
|
||||
|
||||
if (animate)
|
||||
this._animateOverlayActor(title, Math.floor(titleX), Math.floor(titleY), titleWidth);
|
||||
else {
|
||||
title.width = titleWidth;
|
||||
title.set_position(Math.floor(titleX), Math.floor(titleY));
|
||||
}
|
||||
},
|
||||
|
||||
_animateOverlayActor: function(actor, x, y, width) {
|
||||
Tweener.addTween(actor,
|
||||
{ x: x,
|
||||
y: y,
|
||||
width: width,
|
||||
time: Overview.ANIMATION_TIME,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
},
|
||||
|
||||
_closeWindow: function(actor) {
|
||||
@ -1017,7 +1033,7 @@ const Workspace = new Lang.Class({
|
||||
|
||||
let [x, y, scale] = this._computeWindowLayout(metaWindow, slot);
|
||||
|
||||
if (overlay)
|
||||
if (overlay && initialPositioning)
|
||||
overlay.hide();
|
||||
if (animate && isOnCurrentWorkspace) {
|
||||
if (!metaWindow.showing_on_its_workspace()) {
|
||||
@ -1040,20 +1056,11 @@ const Workspace = new Lang.Class({
|
||||
});
|
||||
}
|
||||
|
||||
Tweener.addTween(clone.actor,
|
||||
{ x: x,
|
||||
y: y,
|
||||
scale_x: scale,
|
||||
scale_y: scale,
|
||||
time: Overview.ANIMATION_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this, function() {
|
||||
this._showWindowOverlay(clone, overlay, true);
|
||||
})
|
||||
});
|
||||
this._animateClone(clone, overlay, x, y, scale, initialPositioning);
|
||||
} else {
|
||||
clone.actor.set_position(x, y);
|
||||
clone.actor.set_scale(scale, scale);
|
||||
this._updateWindowOverlayPositions(clone, overlay, x, y, scale, false);
|
||||
this._showWindowOverlay(clone, overlay, isOnCurrentWorkspace);
|
||||
}
|
||||
}
|
||||
@ -1075,23 +1082,35 @@ const Workspace = new Lang.Class({
|
||||
}
|
||||
},
|
||||
|
||||
_animateClone: function(clone, overlay, x, y, scale, initialPositioning) {
|
||||
Tweener.addTween(clone.actor,
|
||||
{ x: x,
|
||||
y: y,
|
||||
scale_x: scale,
|
||||
scale_y: scale,
|
||||
time: Overview.ANIMATION_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this, function() {
|
||||
this._showWindowOverlay(clone, overlay, true);
|
||||
})
|
||||
});
|
||||
|
||||
this._updateWindowOverlayPositions(clone, overlay, x, y, scale, true);
|
||||
},
|
||||
|
||||
_updateWindowOverlayPositions: function(clone, overlay, x, y, scale, animate) {
|
||||
let [cloneWidth, cloneHeight] = clone.actor.get_size();
|
||||
cloneWidth = scale * cloneWidth;
|
||||
cloneHeight = scale * cloneHeight;
|
||||
if (overlay)
|
||||
overlay.updatePositions(x, y, cloneWidth, cloneHeight, animate);
|
||||
},
|
||||
|
||||
_showWindowOverlay: function(clone, overlay, fade) {
|
||||
if (clone.inDrag)
|
||||
return;
|
||||
|
||||
// This is a little messy and complicated because when we
|
||||
// start the fade-in we may not have done the final positioning
|
||||
// of the workspaces. (Tweener doesn't necessarily finish
|
||||
// all animations before calling onComplete callbacks.)
|
||||
// So we need to manually compute where the window will
|
||||
// be after the workspace animation finishes.
|
||||
let [cloneX, cloneY] = clone.actor.get_position();
|
||||
let [cloneWidth, cloneHeight] = clone.actor.get_size();
|
||||
cloneWidth = clone.actor.scale_x * cloneWidth;
|
||||
cloneHeight = clone.actor.scale_y * cloneHeight;
|
||||
|
||||
if (overlay) {
|
||||
overlay.updatePositions(cloneX, cloneY, cloneWidth, cloneHeight);
|
||||
if (fade)
|
||||
overlay.fadeIn();
|
||||
else
|
||||
@ -1109,6 +1128,16 @@ const Workspace = new Lang.Class({
|
||||
}
|
||||
},
|
||||
|
||||
_hideAllOverlays: function() {
|
||||
for (let i = 0; i < this._windows.length; i++) {
|
||||
let clone = this._windows[i];
|
||||
Tweener.removeTweens(clone.actor);
|
||||
let overlay = this._windowOverlays[i];
|
||||
if (overlay)
|
||||
overlay.hide();
|
||||
}
|
||||
},
|
||||
|
||||
_delayedWindowRepositioning: function() {
|
||||
if (this._windowIsZooming)
|
||||
return true;
|
||||
@ -1130,18 +1159,6 @@ const Workspace = new Lang.Class({
|
||||
return false;
|
||||
},
|
||||
|
||||
showWindowsOverlays: function() {
|
||||
if (this.leavingOverview)
|
||||
return;
|
||||
|
||||
this._windowOverlaysGroup.show();
|
||||
this._showAllOverlays();
|
||||
},
|
||||
|
||||
hideWindowsOverlays: function() {
|
||||
this._windowOverlaysGroup.hide();
|
||||
},
|
||||
|
||||
_doRemoveWindow : function(metaWin) {
|
||||
let win = metaWin.get_compositor_private();
|
||||
|
||||
@ -1295,7 +1312,7 @@ const Workspace = new Lang.Class({
|
||||
|
||||
this.leavingOverview = true;
|
||||
|
||||
this.hideWindowsOverlays();
|
||||
this._hideAllOverlays();
|
||||
|
||||
if (this._repositionWindowsId > 0) {
|
||||
Mainloop.source_remove(this._repositionWindowsId);
|
||||
@ -1386,7 +1403,7 @@ const Workspace = new Lang.Class({
|
||||
|
||||
// Create a clone of a (non-desktop) window and add it to the window list
|
||||
_addWindowClone : function(win) {
|
||||
let clone = new WindowClone(win);
|
||||
let clone = new WindowClone(win, this);
|
||||
let overlay = new WindowOverlay(clone, this._windowOverlaysGroup);
|
||||
|
||||
clone.connect('selected',
|
||||
|
@ -51,6 +51,7 @@ const WindowClone = new Lang.Class({
|
||||
dragActorMaxSize: Workspace.WINDOW_DND_SIZE,
|
||||
dragActorOpacity: Workspace.DRAGGING_WINDOW_OPACITY });
|
||||
this._draggable.connect('drag-begin', Lang.bind(this, this._onDragBegin));
|
||||
this._draggable.connect('drag-cancelled', Lang.bind(this, this._onDragCancelled));
|
||||
this._draggable.connect('drag-end', Lang.bind(this, this._onDragEnd));
|
||||
this.inDrag = false;
|
||||
},
|
||||
@ -108,6 +109,10 @@ const WindowClone = new Lang.Class({
|
||||
this.emit('drag-begin');
|
||||
},
|
||||
|
||||
_onDragCancelled : function (draggable, time) {
|
||||
this.emit('drag-cancelled');
|
||||
},
|
||||
|
||||
_onDragEnd : function (draggable, time, snapback) {
|
||||
this.inDrag = false;
|
||||
|
||||
@ -386,6 +391,10 @@ const WorkspaceThumbnail = new Lang.Class({
|
||||
Lang.bind(this, function(clone) {
|
||||
Main.overview.beginWindowDrag();
|
||||
}));
|
||||
clone.connect('drag-cancelled',
|
||||
Lang.bind(this, function(clone) {
|
||||
Main.overview.cancelledWindowDrag();
|
||||
}));
|
||||
clone.connect('drag-end',
|
||||
Lang.bind(this, function(clone) {
|
||||
Main.overview.endWindowDrag();
|
||||
|
@ -1,6 +1,7 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
@ -19,6 +20,7 @@ const WORKSPACE_SWITCH_TIME = 0.25;
|
||||
// Note that mutter has a compile-time limit of 36
|
||||
const MAX_WORKSPACES = 16;
|
||||
|
||||
const OVERRIDE_SCHEMA = 'org.gnome.shell.overrides';
|
||||
|
||||
const CONTROLS_POP_IN_TIME = 0.1;
|
||||
|
||||
@ -40,8 +42,6 @@ const WorkspacesView = new Lang.Class({
|
||||
this._spacing = node.get_length('spacing');
|
||||
this._updateWorkspaceActors(false);
|
||||
}));
|
||||
this.actor.connect('notify::mapped',
|
||||
Lang.bind(this, this._onMappedChanged));
|
||||
|
||||
this._width = 0;
|
||||
this._height = 0;
|
||||
@ -59,6 +59,11 @@ const WorkspacesView = new Lang.Class({
|
||||
this._zoomOut = false; // zoom to a larger area
|
||||
this._inDrag = false; // dragging a window
|
||||
|
||||
this._settings = new Gio.Settings({ schema: OVERRIDE_SCHEMA });
|
||||
this._updateExtraWorkspacesId =
|
||||
this._settings.connect('changed::workspaces-only-on-primary',
|
||||
Lang.bind(this, this._updateExtraWorkspaces));
|
||||
|
||||
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
|
||||
this._workspaces = workspaces;
|
||||
|
||||
@ -67,17 +72,7 @@ const WorkspacesView = new Lang.Class({
|
||||
this._workspaces[w].actor.reparent(this.actor);
|
||||
this._workspaces[activeWorkspaceIndex].actor.raise_top();
|
||||
|
||||
this._extraWorkspaces = [];
|
||||
let monitors = Main.layoutManager.monitors;
|
||||
let m = 0;
|
||||
for (let i = 0; i < monitors.length; i++) {
|
||||
if (i == Main.layoutManager.primaryIndex)
|
||||
continue;
|
||||
let ws = new Workspace.Workspace(null, i);
|
||||
this._extraWorkspaces[m++] = ws;
|
||||
ws.setGeometry(monitors[i].x, monitors[i].y, monitors[i].width, monitors[i].height);
|
||||
global.overlay_group.add_actor(ws.actor);
|
||||
}
|
||||
this._updateExtraWorkspaces();
|
||||
|
||||
// Position/scale the desktop windows and their children after the
|
||||
// workspaces have been created. This cannot be done first because
|
||||
@ -88,6 +83,8 @@ const WorkspacesView = new Lang.Class({
|
||||
Lang.bind(this, function() {
|
||||
for (let w = 0; w < this._workspaces.length; w++)
|
||||
this._workspaces[w].zoomToOverview();
|
||||
if (!this._extraWorkspaces)
|
||||
return;
|
||||
for (let w = 0; w < this._extraWorkspaces.length; w++)
|
||||
this._extraWorkspaces[w].zoomToOverview();
|
||||
}));
|
||||
@ -98,14 +95,14 @@ const WorkspacesView = new Lang.Class({
|
||||
this._clipWidth, this._clipHeight);
|
||||
}));
|
||||
|
||||
this._scrollAdjustment = new St.Adjustment({ value: activeWorkspaceIndex,
|
||||
lower: 0,
|
||||
page_increment: 1,
|
||||
page_size: 1,
|
||||
step_increment: 0,
|
||||
upper: this._workspaces.length });
|
||||
this._scrollAdjustment.connect('notify::value',
|
||||
Lang.bind(this, this._onScroll));
|
||||
this.scrollAdjustment = new St.Adjustment({ value: activeWorkspaceIndex,
|
||||
lower: 0,
|
||||
page_increment: 1,
|
||||
page_size: 1,
|
||||
step_increment: 0,
|
||||
upper: this._workspaces.length });
|
||||
this.scrollAdjustment.connect('notify::value',
|
||||
Lang.bind(this, this._onScroll));
|
||||
|
||||
this._switchWorkspaceNotifyId =
|
||||
global.window_manager.connect('switch-workspace',
|
||||
@ -119,8 +116,35 @@ const WorkspacesView = new Lang.Class({
|
||||
Lang.bind(this, this._dragBegin));
|
||||
this._windowDragEndId = Main.overview.connect('window-drag-end',
|
||||
Lang.bind(this, this._dragEnd));
|
||||
this._swipeScrollBeginId = 0;
|
||||
this._swipeScrollEndId = 0;
|
||||
},
|
||||
|
||||
_updateExtraWorkspaces: function() {
|
||||
this._destroyExtraWorkspaces();
|
||||
|
||||
if (!this._settings.get_boolean('workspaces-only-on-primary'))
|
||||
return;
|
||||
|
||||
this._extraWorkspaces = [];
|
||||
let monitors = Main.layoutManager.monitors;
|
||||
for (let i = 0; i < monitors.length; i++) {
|
||||
if (i == Main.layoutManager.primaryIndex)
|
||||
continue;
|
||||
|
||||
let ws = new Workspace.Workspace(null, i);
|
||||
ws.setGeometry(monitors[i].x, monitors[i].y,
|
||||
monitors[i].width, monitors[i].height);
|
||||
global.overlay_group.add_actor(ws.actor);
|
||||
this._extraWorkspaces.push(ws);
|
||||
}
|
||||
},
|
||||
|
||||
_destroyExtraWorkspaces: function() {
|
||||
if (!this._extraWorkspaces)
|
||||
return;
|
||||
|
||||
for (let m = 0; m < this._extraWorkspaces.length; m++)
|
||||
this._extraWorkspaces[m].destroy();
|
||||
this._extraWorkspaces = null;
|
||||
},
|
||||
|
||||
setGeometry: function(x, y, width, height, spacing) {
|
||||
@ -157,10 +181,6 @@ const WorkspacesView = new Lang.Class({
|
||||
return this._workspaces[active];
|
||||
},
|
||||
|
||||
getWorkspaceByIndex: function(index) {
|
||||
return this._workspaces[index];
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
|
||||
let activeWorkspace = this._workspaces[activeWorkspaceIndex];
|
||||
@ -171,6 +191,8 @@ const WorkspacesView = new Lang.Class({
|
||||
|
||||
for (let w = 0; w < this._workspaces.length; w++)
|
||||
this._workspaces[w].zoomFromOverview();
|
||||
if (!this._extraWorkspaces)
|
||||
return;
|
||||
for (let w = 0; w < this._extraWorkspaces.length; w++)
|
||||
this._extraWorkspaces[w].zoomFromOverview();
|
||||
},
|
||||
@ -182,6 +204,8 @@ const WorkspacesView = new Lang.Class({
|
||||
syncStacking: function(stackIndices) {
|
||||
for (let i = 0; i < this._workspaces.length; i++)
|
||||
this._workspaces[i].syncStacking(stackIndices);
|
||||
if (!this._extraWorkspaces)
|
||||
return;
|
||||
for (let i = 0; i < this._extraWorkspaces.length; i++)
|
||||
this._extraWorkspaces[i].syncStacking(stackIndices);
|
||||
},
|
||||
@ -243,10 +267,8 @@ const WorkspacesView = new Lang.Class({
|
||||
for (let w = 0; w < this._workspaces.length; w++) {
|
||||
let workspace = this._workspaces[w];
|
||||
if (this._animating || this._scrolling) {
|
||||
workspace.hideWindowsOverlays();
|
||||
workspace.actor.show();
|
||||
} else {
|
||||
workspace.showWindowsOverlays();
|
||||
if (this._inDrag)
|
||||
workspace.actor.visible = (Math.abs(w - active) <= 1);
|
||||
else
|
||||
@ -262,7 +284,7 @@ const WorkspacesView = new Lang.Class({
|
||||
this._animatingScroll = true;
|
||||
|
||||
if (showAnimation) {
|
||||
Tweener.addTween(this._scrollAdjustment, {
|
||||
Tweener.addTween(this.scrollAdjustment, {
|
||||
value: index,
|
||||
time: WORKSPACE_SWITCH_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
@ -272,7 +294,7 @@ const WorkspacesView = new Lang.Class({
|
||||
})
|
||||
});
|
||||
} else {
|
||||
this._scrollAdjustment.value = index;
|
||||
this.scrollAdjustment.value = index;
|
||||
this._animatingScroll = false;
|
||||
}
|
||||
},
|
||||
@ -280,7 +302,7 @@ const WorkspacesView = new Lang.Class({
|
||||
updateWorkspaces: function(oldNumWorkspaces, newNumWorkspaces) {
|
||||
let active = global.screen.get_active_workspace_index();
|
||||
|
||||
Tweener.addTween(this._scrollAdjustment,
|
||||
Tweener.addTween(this.scrollAdjustment,
|
||||
{ upper: newNumWorkspaces,
|
||||
time: WORKSPACE_SWITCH_TIME,
|
||||
transition: 'easeOutQuad'
|
||||
@ -307,12 +329,12 @@ const WorkspacesView = new Lang.Class({
|
||||
},
|
||||
|
||||
_onDestroy: function() {
|
||||
for (let i = 0; i < this._extraWorkspaces.length; i++)
|
||||
this._extraWorkspaces[i].destroy();
|
||||
this._scrollAdjustment.run_dispose();
|
||||
this._destroyExtraWorkspaces();
|
||||
this.scrollAdjustment.run_dispose();
|
||||
Main.overview.disconnect(this._overviewShowingId);
|
||||
Main.overview.disconnect(this._overviewShownId);
|
||||
global.window_manager.disconnect(this._switchWorkspaceNotifyId);
|
||||
this._settings.disconnect(this._updateExtraWorkspacesId);
|
||||
|
||||
if (this._inDrag)
|
||||
this._dragEnd();
|
||||
@ -335,21 +357,6 @@ const WorkspacesView = new Lang.Class({
|
||||
}
|
||||
},
|
||||
|
||||
_onMappedChanged: function() {
|
||||
if (this.actor.mapped) {
|
||||
let direction = Overview.SwipeScrollDirection.VERTICAL;
|
||||
Main.overview.setScrollAdjustment(this._scrollAdjustment,
|
||||
direction);
|
||||
this._swipeScrollBeginId = Main.overview.connect('swipe-scroll-begin',
|
||||
Lang.bind(this, this._swipeScrollBegin));
|
||||
this._swipeScrollEndId = Main.overview.connect('swipe-scroll-end',
|
||||
Lang.bind(this, this._swipeScrollEnd));
|
||||
} else {
|
||||
Main.overview.disconnect(this._swipeScrollBeginId);
|
||||
Main.overview.disconnect(this._swipeScrollEndId);
|
||||
}
|
||||
},
|
||||
|
||||
_dragBegin: function() {
|
||||
if (this._scrolling)
|
||||
return;
|
||||
@ -371,6 +378,9 @@ const WorkspacesView = new Lang.Class({
|
||||
this._firstDragMotion = false;
|
||||
for (let i = 0; i < this._workspaces.length; i++)
|
||||
this._workspaces[i].setReservedSlot(dragEvent.dragActor._delegate);
|
||||
if (!this._extraWorkspaces)
|
||||
return DND.DragMotionResult.CONTINUE;
|
||||
|
||||
for (let i = 0; i < this._extraWorkspaces.length; i++)
|
||||
this._extraWorkspaces[i].setReservedSlot(dragEvent.dragActor._delegate);
|
||||
}
|
||||
@ -384,15 +394,18 @@ const WorkspacesView = new Lang.Class({
|
||||
|
||||
for (let i = 0; i < this._workspaces.length; i++)
|
||||
this._workspaces[i].setReservedSlot(null);
|
||||
|
||||
if (!this._extraWorkspaces)
|
||||
return;
|
||||
for (let i = 0; i < this._extraWorkspaces.length; i++)
|
||||
this._extraWorkspaces[i].setReservedSlot(null);
|
||||
},
|
||||
|
||||
_swipeScrollBegin: function() {
|
||||
startSwipeScroll: function() {
|
||||
this._scrolling = true;
|
||||
},
|
||||
|
||||
_swipeScrollEnd: function(overview, result) {
|
||||
endSwipeScroll: function(result) {
|
||||
this._scrolling = false;
|
||||
|
||||
if (result == Overview.SwipeScrollResult.CLICK) {
|
||||
@ -441,7 +454,6 @@ const WorkspacesView = new Lang.Class({
|
||||
let dy = newY - currentY;
|
||||
|
||||
for (let i = 0; i < this._workspaces.length; i++) {
|
||||
this._workspaces[i].hideWindowsOverlays();
|
||||
this._workspaces[i].actor.visible = Math.abs(i - adj.value) <= 1;
|
||||
this._workspaces[i].actor.y += dy;
|
||||
}
|
||||
@ -462,6 +474,8 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
||||
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
||||
this.actor.connect('notify::mapped', Lang.bind(this, this._setupSwipeScrolling));
|
||||
this.actor.connect('parent-set', Lang.bind(this, this._parentSet));
|
||||
this.actor.set_clip_to_allocation(true);
|
||||
|
||||
let controls = new St.Bin({ style_class: 'workspace-controls',
|
||||
@ -478,12 +492,19 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
controls.connect('scroll-event',
|
||||
Lang.bind(this, this._onScrollEvent));
|
||||
|
||||
this._monitorIndex = Main.layoutManager.primaryIndex;
|
||||
this._primaryIndex = Main.layoutManager.primaryIndex;
|
||||
|
||||
this._thumbnailsBox = new WorkspaceThumbnail.ThumbnailsBox();
|
||||
controls.add_actor(this._thumbnailsBox.actor);
|
||||
|
||||
this.workspacesView = null;
|
||||
this._workspacesViews = null;
|
||||
this._primaryScrollAdjustment = null;
|
||||
|
||||
this._settings = new Gio.Settings({ schema: OVERRIDE_SCHEMA });
|
||||
this._settings.connect('changed::workspaces-only-on-primary',
|
||||
Lang.bind(this,
|
||||
this._workspacesOnlyOnPrimaryChanged));
|
||||
this._workspacesOnlyOnPrimaryChanged();
|
||||
|
||||
this._inDrag = false;
|
||||
this._cancelledDrag = false;
|
||||
@ -494,6 +515,8 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
|
||||
this._updateAlwaysZoom();
|
||||
|
||||
// If we stop hiding the overview on layout changes, we will need to
|
||||
// update the _workspacesViews here
|
||||
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._updateAlwaysZoom));
|
||||
|
||||
Main.xdndHandler.connect('drag-begin', Lang.bind(this, function(){
|
||||
@ -514,6 +537,9 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
this._windowDragBeginId = 0;
|
||||
this._windowDragCancelledId = 0;
|
||||
this._windowDragEndId = 0;
|
||||
this._notifyOpacityId = 0;
|
||||
this._swipeScrollBeginId = 0;
|
||||
this._swipeScrollEndId = 0;
|
||||
},
|
||||
|
||||
show: function() {
|
||||
@ -524,16 +550,7 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
this._controls.show();
|
||||
this._thumbnailsBox.show();
|
||||
|
||||
this._workspaces = [];
|
||||
for (let i = 0; i < global.screen.n_workspaces; i++) {
|
||||
let metaWorkspace = global.screen.get_workspace_by_index(i);
|
||||
this._workspaces[i] = new Workspace.Workspace(metaWorkspace, this._monitorIndex);
|
||||
}
|
||||
|
||||
if (this.workspacesView)
|
||||
this.workspacesView.destroy();
|
||||
this.workspacesView = new WorkspacesView(this._workspaces);
|
||||
this._updateWorkspacesGeometry();
|
||||
this._updateWorkspacesViews();
|
||||
|
||||
this._restackedNotifyId =
|
||||
global.screen.connect('restacked',
|
||||
@ -564,6 +581,12 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
this._onRestacked();
|
||||
},
|
||||
|
||||
zoomFromOverview: function() {
|
||||
for (let i = 0; i < this._workspacesViews.length; i++) {
|
||||
this._workspacesViews[i].hide();
|
||||
}
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
this._controls.hide();
|
||||
this._thumbnailsBox.hide();
|
||||
@ -597,12 +620,120 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
this._windowDragEndId = 0;
|
||||
}
|
||||
|
||||
this.workspacesView.destroy();
|
||||
this.workspacesView = null;
|
||||
for (let w = 0; w < this._workspaces.length; w++) {
|
||||
this._workspaces[w].disconnectAll();
|
||||
this._workspaces[w].destroy();
|
||||
for (let i = 0; i < this._workspacesViews.length; i++)
|
||||
this._workspacesViews[i].destroy();
|
||||
this._workspacesViews = null;
|
||||
|
||||
for (let i = 0; i < this._workspaces.length; i++)
|
||||
for (let w = 0; w < this._workspaces[i].length; w++) {
|
||||
this._workspaces[i][w].disconnectAll();
|
||||
this._workspaces[i][w].destroy();
|
||||
}
|
||||
},
|
||||
|
||||
_setupSwipeScrolling: function() {
|
||||
if (this._swipeScrollBeginId)
|
||||
Main.overview.disconnect(this._swipeScrollBeginId);
|
||||
this._swipeScrollBeginId = 0;
|
||||
|
||||
if (this._swipeScrollEndId)
|
||||
Main.overview.disconnect(this._swipeScrollEndId);
|
||||
this._swipeScrollEndId = 0;
|
||||
|
||||
if (!this.actor.mapped)
|
||||
return;
|
||||
|
||||
let direction = Overview.SwipeScrollDirection.VERTICAL;
|
||||
Main.overview.setScrollAdjustment(this._scrollAdjustment,
|
||||
direction);
|
||||
this._swipeScrollBeginId = Main.overview.connect('swipe-scroll-begin',
|
||||
Lang.bind(this, function() {
|
||||
for (let i = 0; i < this._workspacesViews.length; i++)
|
||||
this._workspacesViews[i].startSwipeScroll();
|
||||
}));
|
||||
this._swipeScrollEndId = Main.overview.connect('swipe-scroll-end',
|
||||
Lang.bind(this, function(overview, result) {
|
||||
for (let i = 0; i < this._workspacesViews.length; i++)
|
||||
this._workspacesViews[i].endSwipeScroll(result);
|
||||
}));
|
||||
},
|
||||
|
||||
_workspacesOnlyOnPrimaryChanged: function() {
|
||||
this._workspacesOnlyOnPrimary = this._settings.get_boolean('workspaces-only-on-primary');
|
||||
|
||||
if (!Main.overview.visible)
|
||||
return;
|
||||
|
||||
this._updateWorkspacesViews();
|
||||
},
|
||||
|
||||
_updateWorkspacesViews: function() {
|
||||
if (this._workspacesViews)
|
||||
for (let i = 0; i < this._workspacesViews.length; i++)
|
||||
this._workspacesViews[i].destroy();
|
||||
|
||||
if (this._workspaces)
|
||||
for (let i = 0; i < this._workspaces.length; i++)
|
||||
for (let w = 0; w < this._workspaces[i].length; w++)
|
||||
this._workspaces[i][w].destroy();
|
||||
|
||||
this._workspacesViews = [];
|
||||
this._workspaces = [];
|
||||
let monitors = Main.layoutManager.monitors;
|
||||
for (let i = 0; i < monitors.length; i++) {
|
||||
if (this._workspacesOnlyOnPrimary && i != this._primaryIndex)
|
||||
continue; // we are only interested in the primary monitor
|
||||
|
||||
let monitorWorkspaces = [];
|
||||
for (let w = 0; w < global.screen.n_workspaces; w++) {
|
||||
let metaWorkspace = global.screen.get_workspace_by_index(w);
|
||||
monitorWorkspaces.push(new Workspace.Workspace(metaWorkspace, i));
|
||||
}
|
||||
|
||||
this._workspaces.push(monitorWorkspaces);
|
||||
|
||||
let view = new WorkspacesView(monitorWorkspaces);
|
||||
if (this._workspacesOnlyOnPrimary || i == this._primaryIndex) {
|
||||
this._scrollAdjustment = view.scrollAdjustment;
|
||||
this._scrollAdjustment.connect('notify::value',
|
||||
Lang.bind(this, this._scrollValueChanged));
|
||||
this._setupSwipeScrolling();
|
||||
}
|
||||
this._workspacesViews.push(view);
|
||||
}
|
||||
|
||||
this._updateWorkspacesGeometry();
|
||||
|
||||
for (let i = 0; i < this._workspacesViews.length; i++)
|
||||
global.overlay_group.add_actor(this._workspacesViews[i].actor);
|
||||
},
|
||||
|
||||
_scrollValueChanged: function() {
|
||||
if (this._workspacesOnlyOnPrimary)
|
||||
return;
|
||||
|
||||
for (let i = 0; i < this._workspacesViews.length; i++) {
|
||||
if (i == this._primaryIndex)
|
||||
continue;
|
||||
|
||||
let adjustment = this._workspacesViews[i].scrollAdjustment;
|
||||
// the adjustments work in terms of workspaces, so the
|
||||
// values map directly
|
||||
adjustment.value = this._scrollAdjustment.value;
|
||||
}
|
||||
},
|
||||
|
||||
_getPrimaryView: function() {
|
||||
if (!this._workspacesViews)
|
||||
return null;
|
||||
if (this._workspacesOnlyOnPrimary)
|
||||
return this._workspacesViews[0];
|
||||
else
|
||||
return this._workspacesViews[this._primaryIndex];
|
||||
},
|
||||
|
||||
activeWorkspaceHasMaximizedWindows: function() {
|
||||
return this._getPrimaryView().getActiveWorkspace().hasMaximizedWindows();
|
||||
},
|
||||
|
||||
// zoomFraction property allows us to tween the controls sliding in and out
|
||||
@ -675,8 +806,37 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
this._updateWorkspacesGeometry();
|
||||
},
|
||||
|
||||
_parentSet: function(actor, oldParent) {
|
||||
if (oldParent && this._notifyOpacityId)
|
||||
oldParent.disconnect(this._notifyOpacityId);
|
||||
this._notifyOpacityId = 0;
|
||||
|
||||
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this,
|
||||
function() {
|
||||
let newParent = this.actor.get_parent();
|
||||
if (!newParent)
|
||||
return;
|
||||
|
||||
// This is kinda hackish - we want the primary view to
|
||||
// appear as parent of this.actor, though in reality it
|
||||
// is added directly to overlay_group
|
||||
this._notifyOpacityId = newParent.connect('notify::opacity',
|
||||
Lang.bind(this, function() {
|
||||
let opacity = this.actor.get_parent().opacity;
|
||||
let primaryView = this._getPrimaryView();
|
||||
if (!primaryView)
|
||||
return;
|
||||
primaryView.actor.opacity = opacity;
|
||||
if (opacity == 0)
|
||||
primaryView.actor.hide();
|
||||
else
|
||||
primaryView.actor.show();
|
||||
}));
|
||||
}));
|
||||
},
|
||||
|
||||
_updateWorkspacesGeometry: function() {
|
||||
if (!this.workspacesView)
|
||||
if (!this._workspacesViews)
|
||||
return;
|
||||
|
||||
let fullWidth = this.actor.allocation.x2 - this.actor.allocation.x1;
|
||||
@ -697,8 +857,6 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
let clipX = rtl ? x + controlsVisible : x;
|
||||
let clipY = y + (fullHeight - clipHeight) / 2;
|
||||
|
||||
this.workspacesView.setClipRect(clipX, clipY, clipWidth, clipHeight);
|
||||
|
||||
if (this._zoomOut) {
|
||||
width -= controlsNatural;
|
||||
if (rtl)
|
||||
@ -713,7 +871,28 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
let difference = fullHeight - height;
|
||||
y += difference / 2;
|
||||
|
||||
this.workspacesView.setGeometry(x, y, width, height, difference);
|
||||
|
||||
let monitors = Main.layoutManager.monitors;
|
||||
let m = 0;
|
||||
for (let i = 0; i < monitors.length; i++) {
|
||||
if (i == this._primaryIndex) {
|
||||
this._workspacesViews[m].setClipRect(clipX, clipY,
|
||||
clipWidth, clipHeight);
|
||||
this._workspacesViews[m].setGeometry(x, y, width, height,
|
||||
difference);
|
||||
m++;
|
||||
} else if (!this._workspacesOnlyOnPrimary) {
|
||||
this._workspacesViews[m].setClipRect(monitors[i].x,
|
||||
monitors[i].y,
|
||||
monitors[i].width,
|
||||
monitors[i].height);
|
||||
this._workspacesViews[m].setGeometry(monitors[i].x,
|
||||
monitors[i].y,
|
||||
monitors[i].width,
|
||||
monitors[i].height, 0);
|
||||
m++;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_onRestacked: function() {
|
||||
@ -725,12 +904,14 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
stackIndices[stack[i].get_meta_window().get_stable_sequence()] = i;
|
||||
}
|
||||
|
||||
this.workspacesView.syncStacking(stackIndices);
|
||||
for (let i = 0; i < this._workspacesViews.length; i++)
|
||||
this._workspacesViews[i].syncStacking(stackIndices);
|
||||
|
||||
this._thumbnailsBox.syncStacking(stackIndices);
|
||||
},
|
||||
|
||||
_workspacesChanged: function() {
|
||||
let oldNumWorkspaces = this._workspaces.length;
|
||||
let oldNumWorkspaces = this._workspaces[0].length;
|
||||
let newNumWorkspaces = global.screen.n_workspaces;
|
||||
let active = global.screen.get_active_workspace_index();
|
||||
|
||||
@ -740,15 +921,24 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
this._updateAlwaysZoom();
|
||||
this._updateZoom();
|
||||
|
||||
if (this.workspacesView == null)
|
||||
if (this._workspacesViews == null)
|
||||
return;
|
||||
|
||||
let lostWorkspaces = [];
|
||||
if (newNumWorkspaces > oldNumWorkspaces) {
|
||||
// Assume workspaces are only added at the end
|
||||
for (let w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
|
||||
let metaWorkspace = global.screen.get_workspace_by_index(w);
|
||||
this._workspaces[w] = new Workspace.Workspace(metaWorkspace, this._monitorIndex);
|
||||
let monitors = Main.layoutManager.monitors;
|
||||
let m = 0;
|
||||
for (let i = 0; i < monitors.length; i++) {
|
||||
if (this._workspacesOnlyOnPrimaryChanged &&
|
||||
i != this._primaryIndex)
|
||||
continue;
|
||||
|
||||
// Assume workspaces are only added at the end
|
||||
for (let w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
|
||||
let metaWorkspace = global.screen.get_workspace_by_index(w);
|
||||
this._workspaces[m++][w] =
|
||||
new Workspace.Workspace(metaWorkspace, i);
|
||||
}
|
||||
}
|
||||
|
||||
this._thumbnailsBox.addThumbnails(oldNumWorkspaces, newNumWorkspaces - oldNumWorkspaces);
|
||||
@ -759,25 +949,28 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
let removedNum = oldNumWorkspaces - newNumWorkspaces;
|
||||
for (let w = 0; w < oldNumWorkspaces; w++) {
|
||||
let metaWorkspace = global.screen.get_workspace_by_index(w);
|
||||
if (this._workspaces[w].metaWorkspace != metaWorkspace) {
|
||||
if (this._workspaces[0][w].metaWorkspace != metaWorkspace) {
|
||||
removedIndex = w;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lostWorkspaces = this._workspaces.splice(removedIndex,
|
||||
removedNum);
|
||||
for (let i = 0; i < this._workspaces.length; i++) {
|
||||
lostWorkspaces = this._workspaces[i].splice(removedIndex,
|
||||
removedNum);
|
||||
|
||||
for (let l = 0; l < lostWorkspaces.length; l++) {
|
||||
lostWorkspaces[l].disconnectAll();
|
||||
lostWorkspaces[l].destroy();
|
||||
for (let l = 0; l < lostWorkspaces.length; l++) {
|
||||
lostWorkspaces[l].disconnectAll();
|
||||
lostWorkspaces[l].destroy();
|
||||
}
|
||||
}
|
||||
|
||||
this._thumbnailsBox.removeThumbmails(removedIndex, removedNum);
|
||||
}
|
||||
|
||||
this.workspacesView.updateWorkspaces(oldNumWorkspaces,
|
||||
newNumWorkspaces);
|
||||
for (let i = 0; i < this._workspacesViews.length; i++)
|
||||
this._workspacesViews[i].updateWorkspaces(oldNumWorkspaces,
|
||||
newNumWorkspaces);
|
||||
},
|
||||
|
||||
_updateZoom : function() {
|
||||
@ -789,7 +982,7 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
this._zoomOut = shouldZoom;
|
||||
this._updateWorkspacesGeometry();
|
||||
|
||||
if (!this.workspacesView)
|
||||
if (!this._workspacesViews)
|
||||
return;
|
||||
|
||||
Tweener.addTween(this,
|
||||
@ -797,7 +990,8 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
time: WORKSPACE_SWITCH_TIME,
|
||||
transition: 'easeOutQuad' });
|
||||
|
||||
this.workspacesView.updateWindowPositions();
|
||||
for (let i = 0; i < this._workspacesViews.length; i++)
|
||||
this._workspacesViews[i].updateWindowPositions();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -35,6 +35,7 @@ kn
|
||||
ku
|
||||
lt
|
||||
lv
|
||||
mk
|
||||
mr
|
||||
ms
|
||||
nb
|
||||
|
@ -36,6 +36,7 @@ js/ui/status/volume.js
|
||||
js/ui/telepathyClient.js
|
||||
js/ui/userMenu.js
|
||||
js/ui/viewSelector.js
|
||||
js/ui/wanda.js
|
||||
js/ui/windowAttentionHandler.js
|
||||
src/gvc/gvc-mixer-control.c
|
||||
src/main.c
|
||||
|
4
po/it.po
4
po/it.po
@ -11,7 +11,7 @@ msgstr ""
|
||||
"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-"
|
||||
"shell&keywords=I18N+L10N&component=general\n"
|
||||
"POT-Creation-Date: 2011-10-04 20:49+0000\n"
|
||||
"PO-Revision-Date: 2011-10-17 10:30+0200\n"
|
||||
"PO-Revision-Date: 2012-01-04 10:19+0100\n"
|
||||
"Last-Translator: Luca Ferretti <lferrett@gnome.org>\n"
|
||||
"Language-Team: Italian <tp@lists.linux.it>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@ -583,7 +583,7 @@ msgstr[1] "Il sistema verrà spento automaticamente tra %d secondi."
|
||||
|
||||
#: ../js/ui/endSessionDialog.js:87
|
||||
msgid "Powering off the system."
|
||||
msgstr "pegnimento del sistema."
|
||||
msgstr "Spegnimento del sistema."
|
||||
|
||||
#: ../js/ui/endSessionDialog.js:98
|
||||
msgid "Click Restart to quit these applications and restart the system."
|
||||
|
723
po/zh_CN.po
723
po/zh_CN.po
File diff suppressed because it is too large
Load Diff
@ -121,9 +121,18 @@ shell_public_headers_h = \
|
||||
shell-wm.h \
|
||||
shell-xfixes-cursor.h
|
||||
|
||||
shell_private_sources = \
|
||||
gactionmuxer.h \
|
||||
gactionmuxer.c \
|
||||
gactionobservable.h \
|
||||
gactionobservable.c \
|
||||
gactionobserver.h \
|
||||
gactionobserver.c
|
||||
|
||||
libgnome_shell_la_SOURCES = \
|
||||
$(shell_built_sources) \
|
||||
$(shell_public_headers_h) \
|
||||
$(shell_private_sources) \
|
||||
shell-app-private.h \
|
||||
shell-app-system-private.h \
|
||||
shell-embedded-window-private.h \
|
||||
@ -157,10 +166,12 @@ libgnome_shell_la_SOURCES = \
|
||||
shell-util.c \
|
||||
shell-window-tracker.c \
|
||||
shell-wm.c \
|
||||
shell-xfixes-cursor.c
|
||||
shell-xfixes-cursor.c \
|
||||
$(NULL)
|
||||
|
||||
|
||||
libgnome_shell_la_gir_sources = \
|
||||
$(filter-out %-private.h $(shell_recorder_non_gir_sources), $(shell_public_headers_h) $(libgnome_shell_la_SOURCES))
|
||||
$(filter-out %-private.h $(shell_private_sources), $(shell_public_headers_h) $(libgnome_shell_la_SOURCES))
|
||||
|
||||
gnome_shell_real_SOURCES = \
|
||||
main.c
|
||||
@ -168,6 +179,8 @@ gnome_shell_real_CPPFLAGS = $(gnome_shell_cflags)
|
||||
gnome_shell_real_LDADD = libgnome-shell.la $(libgnome_shell_la_LIBADD)
|
||||
gnome_shell_real_DEPENDENCIES = libgnome-shell.la
|
||||
|
||||
EXTRA_DIST += test-gapplication.js
|
||||
|
||||
########################################
|
||||
|
||||
shell_recorder_sources = \
|
||||
@ -175,12 +188,16 @@ shell_recorder_sources = \
|
||||
shell-recorder.h
|
||||
|
||||
# Custom element is an internal detail
|
||||
shell_recorder_non_gir_sources = \
|
||||
shell-recorder-src.c \
|
||||
shell-recorder-src.h
|
||||
|
||||
if BUILD_RECORDER
|
||||
libgnome_shell_la_SOURCES += $(shell_recorder_sources) $(shell_recorder_non_gir_sources)
|
||||
libgnome_shell_la_SOURCES += $(shell_recorder_sources)
|
||||
|
||||
shell_recorder_private_sources = \
|
||||
shell-recorder-src.c \
|
||||
shell-recorder-src.h \
|
||||
$(NULL)
|
||||
|
||||
shell_private_sources += $(shell_recorder_private_sources)
|
||||
|
||||
noinst_PROGRAMS += test-recorder
|
||||
|
||||
@ -188,7 +205,7 @@ test_recorder_CPPFLAGS = $(TEST_SHELL_RECORDER_CFLAGS)
|
||||
test_recorder_LDADD = $(TEST_SHELL_RECORDER_LIBS)
|
||||
|
||||
test_recorder_SOURCES = \
|
||||
$(shell_recorder_sources) $(shell_recorder_non_gir_sources) \
|
||||
$(shell_recorder_sources) $(shell_recorder_private_sources) \
|
||||
test-recorder.c
|
||||
endif BUILD_RECORDER
|
||||
|
||||
|
533
src/gactionmuxer.c
Normal file
533
src/gactionmuxer.c
Normal file
@ -0,0 +1,533 @@
|
||||
/*
|
||||
* Copyright © 2011 Canonical Limited
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the licence, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Author: Ryan Lortie <desrt@desrt.ca>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gactionmuxer.h"
|
||||
|
||||
#include "gactionobservable.h"
|
||||
#include "gactionobserver.h"
|
||||
|
||||
#include <clutter/clutter.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* SECTION:gactionmuxer
|
||||
* @short_description: Aggregate and monitor several action groups
|
||||
*
|
||||
* #GActionMuxer is a #GActionGroup and #GActionObservable that is
|
||||
* capable of containing other #GActionGroup instances.
|
||||
*
|
||||
* The typical use is aggregating all of the actions applicable to a
|
||||
* particular context into a single action group, with namespacing.
|
||||
*
|
||||
* Consider the case of two action groups -- one containing actions
|
||||
* applicable to an entire application (such as 'quit') and one
|
||||
* containing actions applicable to a particular window in the
|
||||
* application (such as 'fullscreen').
|
||||
*
|
||||
* In this case, each of these action groups could be added to a
|
||||
* #GActionMuxer with the prefixes "app" and "win", respectively. This
|
||||
* would expose the actions as "app.quit" and "win.fullscreen" on the
|
||||
* #GActionGroup interface presented by the #GActionMuxer.
|
||||
*
|
||||
* Activations and state change requests on the #GActionMuxer are wired
|
||||
* through to the underlying action group in the expected way.
|
||||
*
|
||||
* This class is typically only used at the site of "consumption" of
|
||||
* actions (eg: when displaying a menu that contains many actions on
|
||||
* different objects).
|
||||
*/
|
||||
|
||||
static void g_action_muxer_group_iface_init (GActionGroupInterface *iface);
|
||||
static void g_action_muxer_observable_iface_init (GActionObservableInterface *iface);
|
||||
|
||||
typedef GObjectClass GActionMuxerClass;
|
||||
|
||||
struct _GActionMuxer
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
GHashTable *actions;
|
||||
GHashTable *groups;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (GActionMuxer, g_action_muxer, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, g_action_muxer_group_iface_init)
|
||||
G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_OBSERVABLE, g_action_muxer_observable_iface_init))
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GActionMuxer *muxer;
|
||||
GSList *watchers;
|
||||
gchar *fullname;
|
||||
} Action;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GActionMuxer *muxer;
|
||||
GActionGroup *group;
|
||||
gchar *prefix;
|
||||
gulong handler_ids[4];
|
||||
} Group;
|
||||
|
||||
static gchar **
|
||||
g_action_muxer_list_actions (GActionGroup *action_group)
|
||||
{
|
||||
GActionMuxer *muxer = G_ACTION_MUXER (action_group);
|
||||
|
||||
return (gchar **) muxer->groups;
|
||||
}
|
||||
|
||||
static Group *
|
||||
g_action_muxer_find_group (GActionMuxer *muxer,
|
||||
const gchar **name)
|
||||
{
|
||||
const gchar *dot;
|
||||
gchar *prefix;
|
||||
Group *group;
|
||||
|
||||
dot = strchr (*name, '.');
|
||||
|
||||
if (!dot)
|
||||
return NULL;
|
||||
|
||||
prefix = g_strndup (*name, dot - *name);
|
||||
group = g_hash_table_lookup (muxer->groups, prefix);
|
||||
g_free (prefix);
|
||||
|
||||
*name = dot + 1;
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
static Action *
|
||||
g_action_muxer_lookup_action (GActionMuxer *muxer,
|
||||
const gchar *prefix,
|
||||
const gchar *action_name,
|
||||
gchar **fullname)
|
||||
{
|
||||
Action *action;
|
||||
|
||||
*fullname = g_strconcat (prefix, ".", action_name, NULL);
|
||||
action = g_hash_table_lookup (muxer->actions, *fullname);
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
static void
|
||||
g_action_muxer_action_enabled_changed (GActionGroup *action_group,
|
||||
const gchar *action_name,
|
||||
gboolean enabled,
|
||||
gpointer user_data)
|
||||
{
|
||||
Group *group = user_data;
|
||||
gchar *fullname;
|
||||
Action *action;
|
||||
GSList *node;
|
||||
|
||||
action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
|
||||
for (node = action ? action->watchers : NULL; node; node = node->next)
|
||||
g_action_observer_action_enabled_changed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname, enabled);
|
||||
g_action_group_action_enabled_changed (G_ACTION_GROUP (group->muxer), fullname, enabled);
|
||||
g_free (fullname);
|
||||
}
|
||||
|
||||
static void
|
||||
g_action_muxer_action_state_changed (GActionGroup *action_group,
|
||||
const gchar *action_name,
|
||||
GVariant *state,
|
||||
gpointer user_data)
|
||||
{
|
||||
Group *group = user_data;
|
||||
gchar *fullname;
|
||||
Action *action;
|
||||
GSList *node;
|
||||
|
||||
action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
|
||||
for (node = action ? action->watchers : NULL; node; node = node->next)
|
||||
g_action_observer_action_state_changed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname, state);
|
||||
g_action_group_action_state_changed (G_ACTION_GROUP (group->muxer), fullname, state);
|
||||
g_free (fullname);
|
||||
}
|
||||
|
||||
static void
|
||||
g_action_muxer_action_added (GActionGroup *action_group,
|
||||
const gchar *action_name,
|
||||
gpointer user_data)
|
||||
{
|
||||
const GVariantType *parameter_type;
|
||||
Group *group = user_data;
|
||||
gboolean enabled;
|
||||
GVariant *state;
|
||||
|
||||
if (g_action_group_query_action (group->group, action_name, &enabled, ¶meter_type, NULL, NULL, &state))
|
||||
{
|
||||
gchar *fullname;
|
||||
Action *action;
|
||||
GSList *node;
|
||||
|
||||
action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
|
||||
|
||||
for (node = action ? action->watchers : NULL; node; node = node->next)
|
||||
g_action_observer_action_added (node->data,
|
||||
G_ACTION_OBSERVABLE (group->muxer),
|
||||
fullname, parameter_type, enabled, state);
|
||||
|
||||
g_action_group_action_added (G_ACTION_GROUP (group->muxer), fullname);
|
||||
|
||||
if (state)
|
||||
g_variant_unref (state);
|
||||
|
||||
g_free (fullname);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
g_action_muxer_action_removed (GActionGroup *action_group,
|
||||
const gchar *action_name,
|
||||
gpointer user_data)
|
||||
{
|
||||
Group *group = user_data;
|
||||
gchar *fullname;
|
||||
Action *action;
|
||||
GSList *node;
|
||||
|
||||
action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
|
||||
for (node = action ? action->watchers : NULL; node; node = node->next)
|
||||
g_action_observer_action_removed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname);
|
||||
g_action_group_action_removed (G_ACTION_GROUP (group->muxer), fullname);
|
||||
g_free (fullname);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
g_action_muxer_query_action (GActionGroup *action_group,
|
||||
const gchar *action_name,
|
||||
gboolean *enabled,
|
||||
const GVariantType **parameter_type,
|
||||
const GVariantType **state_type,
|
||||
GVariant **state_hint,
|
||||
GVariant **state)
|
||||
{
|
||||
GActionMuxer *muxer = G_ACTION_MUXER (action_group);
|
||||
Group *group;
|
||||
|
||||
group = g_action_muxer_find_group (muxer, &action_name);
|
||||
|
||||
if (!group)
|
||||
return FALSE;
|
||||
|
||||
return g_action_group_query_action (group->group, action_name, enabled,
|
||||
parameter_type, state_type, state_hint, state);
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
get_platform_data (void)
|
||||
{
|
||||
gchar time[32];
|
||||
GVariantBuilder *builder;
|
||||
GVariant *result;
|
||||
|
||||
g_snprintf (time, 32, "_TIME%d", clutter_get_current_event_time ());
|
||||
|
||||
builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
|
||||
|
||||
g_variant_builder_add (builder, "{sv}", "desktop-startup-id",
|
||||
g_variant_new_string (time));
|
||||
|
||||
result = g_variant_builder_end (builder);
|
||||
g_variant_builder_unref (builder);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
g_action_muxer_activate_action (GActionGroup *action_group,
|
||||
const gchar *action_name,
|
||||
GVariant *parameter)
|
||||
{
|
||||
GActionMuxer *muxer = G_ACTION_MUXER (action_group);
|
||||
Group *group;
|
||||
|
||||
group = g_action_muxer_find_group (muxer, &action_name);
|
||||
|
||||
if (group)
|
||||
{
|
||||
if (G_IS_REMOTE_ACTION_GROUP (group->group))
|
||||
g_remote_action_group_activate_action_full (G_REMOTE_ACTION_GROUP (group->group),
|
||||
action_name,
|
||||
parameter,
|
||||
get_platform_data ());
|
||||
else
|
||||
g_action_group_activate_action (group->group, action_name, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
g_action_muxer_change_action_state (GActionGroup *action_group,
|
||||
const gchar *action_name,
|
||||
GVariant *state)
|
||||
{
|
||||
GActionMuxer *muxer = G_ACTION_MUXER (action_group);
|
||||
Group *group;
|
||||
|
||||
group = g_action_muxer_find_group (muxer, &action_name);
|
||||
|
||||
if (group)
|
||||
{
|
||||
if (G_IS_REMOTE_ACTION_GROUP (group->group))
|
||||
g_remote_action_group_change_action_state_full (G_REMOTE_ACTION_GROUP (group->group),
|
||||
action_name,
|
||||
state,
|
||||
get_platform_data ());
|
||||
else
|
||||
g_action_group_change_action_state (group->group, action_name, state);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
g_action_muxer_unregister_internal (Action *action,
|
||||
gpointer observer)
|
||||
{
|
||||
GActionMuxer *muxer = action->muxer;
|
||||
GSList **ptr;
|
||||
|
||||
for (ptr = &action->watchers; *ptr; ptr = &(*ptr)->next)
|
||||
if ((*ptr)->data == observer)
|
||||
{
|
||||
*ptr = g_slist_remove (*ptr, observer);
|
||||
|
||||
if (action->watchers == NULL)
|
||||
{
|
||||
g_hash_table_remove (muxer->actions, action->fullname);
|
||||
g_free (action->fullname);
|
||||
|
||||
g_slice_free (Action, action);
|
||||
|
||||
g_object_unref (muxer);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
g_action_muxer_weak_notify (gpointer data,
|
||||
GObject *where_the_object_was)
|
||||
{
|
||||
Action *action = data;
|
||||
|
||||
g_action_muxer_unregister_internal (action, where_the_object_was);
|
||||
}
|
||||
|
||||
static void
|
||||
g_action_muxer_register_observer (GActionObservable *observable,
|
||||
const gchar *name,
|
||||
GActionObserver *observer)
|
||||
{
|
||||
GActionMuxer *muxer = G_ACTION_MUXER (observable);
|
||||
Action *action;
|
||||
|
||||
action = g_hash_table_lookup (muxer->actions, name);
|
||||
|
||||
if (action == NULL)
|
||||
{
|
||||
action = g_slice_new (Action);
|
||||
action->muxer = g_object_ref (muxer);
|
||||
action->fullname = g_strdup (name);
|
||||
action->watchers = NULL;
|
||||
|
||||
g_hash_table_insert (muxer->actions, action->fullname, action);
|
||||
}
|
||||
|
||||
action->watchers = g_slist_prepend (action->watchers, observer);
|
||||
g_object_weak_ref (G_OBJECT (observer), g_action_muxer_weak_notify, action);
|
||||
}
|
||||
|
||||
static void
|
||||
g_action_muxer_unregister_observer (GActionObservable *observable,
|
||||
const gchar *name,
|
||||
GActionObserver *observer)
|
||||
{
|
||||
GActionMuxer *muxer = G_ACTION_MUXER (observable);
|
||||
Action *action;
|
||||
|
||||
action = g_hash_table_lookup (muxer->actions, name);
|
||||
g_object_weak_unref (G_OBJECT (observer), g_action_muxer_weak_notify, action);
|
||||
g_action_muxer_unregister_internal (action, observer);
|
||||
}
|
||||
|
||||
static void
|
||||
g_action_muxer_free_group (gpointer data)
|
||||
{
|
||||
Group *group = data;
|
||||
|
||||
g_object_unref (group->group);
|
||||
g_free (group->prefix);
|
||||
|
||||
g_slice_free (Group, group);
|
||||
}
|
||||
|
||||
static void
|
||||
g_action_muxer_finalize (GObject *object)
|
||||
{
|
||||
GActionMuxer *muxer = G_ACTION_MUXER (object);
|
||||
|
||||
g_assert_cmpint (g_hash_table_size (muxer->actions), ==, 0);
|
||||
g_hash_table_unref (muxer->actions);
|
||||
g_hash_table_unref (muxer->groups);
|
||||
|
||||
G_OBJECT_CLASS (g_action_muxer_parent_class)
|
||||
->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
g_action_muxer_init (GActionMuxer *muxer)
|
||||
{
|
||||
muxer->actions = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
muxer->groups = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_action_muxer_free_group);
|
||||
}
|
||||
|
||||
static void
|
||||
g_action_muxer_observable_iface_init (GActionObservableInterface *iface)
|
||||
{
|
||||
iface->register_observer = g_action_muxer_register_observer;
|
||||
iface->unregister_observer = g_action_muxer_unregister_observer;
|
||||
}
|
||||
|
||||
static void
|
||||
g_action_muxer_group_iface_init (GActionGroupInterface *iface)
|
||||
{
|
||||
iface->list_actions = g_action_muxer_list_actions;
|
||||
iface->query_action = g_action_muxer_query_action;
|
||||
iface->activate_action = g_action_muxer_activate_action;
|
||||
iface->change_action_state = g_action_muxer_change_action_state;
|
||||
}
|
||||
|
||||
static void
|
||||
g_action_muxer_class_init (GObjectClass *class)
|
||||
{
|
||||
class->finalize = g_action_muxer_finalize;
|
||||
}
|
||||
|
||||
/*
|
||||
* g_action_muxer_insert:
|
||||
* @muxer: a #GActionMuxer
|
||||
* @prefix: the prefix string for the action group
|
||||
* @action_group: a #GActionGroup
|
||||
*
|
||||
* Adds the actions in @action_group to the list of actions provided by
|
||||
* @muxer. @prefix is prefixed to each action name, such that for each
|
||||
* action <varname>x</varname> in @action_group, there is an equivalent
|
||||
* action @prefix<literal>.</literal><varname>x</varname> in @muxer.
|
||||
*
|
||||
* For example, if @prefix is "<literal>app</literal>" and @action_group
|
||||
* contains an action called "<literal>quit</literal>", then @muxer will
|
||||
* now contain an action called "<literal>app.quit</literal>".
|
||||
*
|
||||
* If any #GActionObservers are registered for actions in the group,
|
||||
* "action_added" notifications will be emitted, as appropriate.
|
||||
*
|
||||
* @prefix must not contain a dot ('.').
|
||||
*/
|
||||
void
|
||||
g_action_muxer_insert (GActionMuxer *muxer,
|
||||
const gchar *prefix,
|
||||
GActionGroup *action_group)
|
||||
{
|
||||
gchar **actions;
|
||||
Group *group;
|
||||
gint i;
|
||||
|
||||
/* TODO: diff instead of ripout and replace */
|
||||
g_action_muxer_remove (muxer, prefix);
|
||||
|
||||
group = g_slice_new (Group);
|
||||
group->muxer = muxer;
|
||||
group->group = g_object_ref (action_group);
|
||||
group->prefix = g_strdup (prefix);
|
||||
|
||||
g_hash_table_insert (muxer->groups, group->prefix, group);
|
||||
|
||||
actions = g_action_group_list_actions (group->group);
|
||||
for (i = 0; actions[i]; i++)
|
||||
g_action_muxer_action_added (group->group, actions[i], group);
|
||||
g_strfreev (actions);
|
||||
|
||||
group->handler_ids[0] = g_signal_connect (group->group, "action-added",
|
||||
G_CALLBACK (g_action_muxer_action_added), group);
|
||||
group->handler_ids[1] = g_signal_connect (group->group, "action-removed",
|
||||
G_CALLBACK (g_action_muxer_action_removed), group);
|
||||
group->handler_ids[2] = g_signal_connect (group->group, "action-enabled-changed",
|
||||
G_CALLBACK (g_action_muxer_action_enabled_changed), group);
|
||||
group->handler_ids[3] = g_signal_connect (group->group, "action-state-changed",
|
||||
G_CALLBACK (g_action_muxer_action_state_changed), group);
|
||||
}
|
||||
|
||||
/*
|
||||
* g_action_muxer_remove:
|
||||
* @muxer: a #GActionMuxer
|
||||
* @prefix: the prefix of the action group to remove
|
||||
*
|
||||
* Removes a #GActionGroup from the #GActionMuxer.
|
||||
*
|
||||
* If any #GActionObservers are registered for actions in the group,
|
||||
* "action_removed" notifications will be emitted, as appropriate.
|
||||
*/
|
||||
void
|
||||
g_action_muxer_remove (GActionMuxer *muxer,
|
||||
const gchar *prefix)
|
||||
{
|
||||
Group *group;
|
||||
|
||||
group = g_hash_table_lookup (muxer->groups, prefix);
|
||||
|
||||
if (group != NULL)
|
||||
{
|
||||
gchar **actions;
|
||||
gint i;
|
||||
|
||||
g_hash_table_steal (muxer->groups, prefix);
|
||||
|
||||
actions = g_action_group_list_actions (group->group);
|
||||
for (i = 0; actions[i]; i++)
|
||||
g_action_muxer_action_removed (group->group, actions[i], group);
|
||||
g_strfreev (actions);
|
||||
|
||||
/* 'for loop' or 'four loop'? */
|
||||
for (i = 0; i < 4; i++)
|
||||
g_signal_handler_disconnect (group->group, group->handler_ids[i]);
|
||||
|
||||
g_action_muxer_free_group (group);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* g_action_muxer_new:
|
||||
*
|
||||
* Creates a new #GActionMuxer.
|
||||
*/
|
||||
GActionMuxer *
|
||||
g_action_muxer_new (void)
|
||||
{
|
||||
return g_object_new (G_TYPE_ACTION_MUXER, NULL);
|
||||
}
|
53
src/gactionmuxer.h
Normal file
53
src/gactionmuxer.h
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright © 2011 Canonical Limited
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the licence, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Author: Ryan Lortie <desrt@desrt.ca>
|
||||
*/
|
||||
|
||||
#ifndef __G_ACTION_MUXER_H__
|
||||
#define __G_ACTION_MUXER_H__
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define G_TYPE_ACTION_MUXER (g_action_muxer_get_type ())
|
||||
#define G_ACTION_MUXER(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
|
||||
G_TYPE_ACTION_MUXER, GActionMuxer))
|
||||
#define G_IS_ACTION_MUXER(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \
|
||||
G_TYPE_ACTION_MUXER))
|
||||
|
||||
typedef struct _GActionMuxer GActionMuxer;
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
GType g_action_muxer_get_type (void);
|
||||
G_GNUC_INTERNAL
|
||||
GActionMuxer * g_action_muxer_new (void);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void g_action_muxer_insert (GActionMuxer *muxer,
|
||||
const gchar *prefix,
|
||||
GActionGroup *group);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void g_action_muxer_remove (GActionMuxer *muxer,
|
||||
const gchar *prefix);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __G_ACTION_MUXER_H__ */
|
80
src/gactionobservable.c
Normal file
80
src/gactionobservable.c
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright © 2011 Canonical Limited
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* licence or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
|
||||
* USA.
|
||||
*
|
||||
* Authors: Ryan Lortie <desrt@desrt.ca>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gactionobservable.h"
|
||||
|
||||
G_DEFINE_INTERFACE (GActionObservable, g_action_observable, G_TYPE_OBJECT)
|
||||
|
||||
/*
|
||||
* SECTION:gactionobserable
|
||||
* @short_description: an interface implemented by objects that report
|
||||
* changes to actions
|
||||
*/
|
||||
|
||||
void
|
||||
g_action_observable_default_init (GActionObservableInterface *iface)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* g_action_observable_register_observer:
|
||||
* @observable: a #GActionObservable
|
||||
* @action_name: the name of the action
|
||||
* @observer: the #GActionObserver to which the events will be reported
|
||||
*
|
||||
* Registers @observer as being interested in changes to @action_name on
|
||||
* @observable.
|
||||
*/
|
||||
void
|
||||
g_action_observable_register_observer (GActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
GActionObserver *observer)
|
||||
{
|
||||
g_return_if_fail (G_IS_ACTION_OBSERVABLE (observable));
|
||||
|
||||
G_ACTION_OBSERVABLE_GET_IFACE (observable)
|
||||
->register_observer (observable, action_name, observer);
|
||||
}
|
||||
|
||||
/*
|
||||
* g_action_observable_unregister_observer:
|
||||
* @observable: a #GActionObservable
|
||||
* @action_name: the name of the action
|
||||
* @observer: the #GActionObserver to which the events will be reported
|
||||
*
|
||||
* Removes the registration of @observer as being interested in changes
|
||||
* to @action_name on @observable.
|
||||
*
|
||||
* If the observer was registered multiple times, it must be
|
||||
* unregistered an equal number of times.
|
||||
*/
|
||||
void
|
||||
g_action_observable_unregister_observer (GActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
GActionObserver *observer)
|
||||
{
|
||||
g_return_if_fail (G_IS_ACTION_OBSERVABLE (observable));
|
||||
|
||||
G_ACTION_OBSERVABLE_GET_IFACE (observable)
|
||||
->unregister_observer (observable, action_name, observer);
|
||||
}
|
64
src/gactionobservable.h
Normal file
64
src/gactionobservable.h
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright © 2011 Canonical Limited
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* licence or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
|
||||
* USA.
|
||||
*
|
||||
* Authors: Ryan Lortie <desrt@desrt.ca>
|
||||
*/
|
||||
|
||||
#ifndef __G_ACTION_OBSERVABLE_H__
|
||||
#define __G_ACTION_OBSERVABLE_H__
|
||||
|
||||
#include "gactionobserver.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define G_TYPE_ACTION_OBSERVABLE (g_action_observable_get_type ())
|
||||
#define G_ACTION_OBSERVABLE(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
|
||||
G_TYPE_ACTION_OBSERVABLE, GActionObservable))
|
||||
#define G_IS_ACTION_OBSERVABLE(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \
|
||||
G_TYPE_ACTION_OBSERVABLE))
|
||||
#define G_ACTION_OBSERVABLE_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), \
|
||||
G_TYPE_ACTION_OBSERVABLE, GActionObservableInterface))
|
||||
|
||||
typedef struct _GActionObservableInterface GActionObservableInterface;
|
||||
|
||||
struct _GActionObservableInterface
|
||||
{
|
||||
GTypeInterface g_iface;
|
||||
|
||||
void (* register_observer) (GActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
GActionObserver *observer);
|
||||
void (* unregister_observer) (GActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
GActionObserver *observer);
|
||||
};
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
GType g_action_observable_get_type (void);
|
||||
G_GNUC_INTERNAL
|
||||
void g_action_observable_register_observer (GActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
GActionObserver *observer);
|
||||
G_GNUC_INTERNAL
|
||||
void g_action_observable_unregister_observer (GActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
GActionObserver *observer);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __G_ACTION_OBSERVABLE_H__ */
|
161
src/gactionobserver.c
Normal file
161
src/gactionobserver.c
Normal file
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright © 2011 Canonical Limited
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* licence or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
|
||||
* USA.
|
||||
*
|
||||
* Authors: Ryan Lortie <desrt@desrt.ca>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gactionobserver.h"
|
||||
|
||||
G_DEFINE_INTERFACE (GActionObserver, g_action_observer, G_TYPE_OBJECT)
|
||||
|
||||
/**
|
||||
* SECTION:gactionobserver
|
||||
* @short_description: an interface implemented by objects that are
|
||||
* interested in monitoring actions for changes
|
||||
*
|
||||
* GActionObserver is a simple interface allowing objects that wish to
|
||||
* be notified of changes to actions to be notified of those changes.
|
||||
*
|
||||
* It is also possible to monitor changes to action groups using
|
||||
* #GObject signals, but there are a number of reasons that this
|
||||
* approach could become problematic:
|
||||
*
|
||||
* - there are four separate signals that must be manually connected
|
||||
* and disconnected
|
||||
*
|
||||
* - when a large number of different observers wish to monitor a
|
||||
* (usually disjoint) set of actions within the same action group,
|
||||
* there is only one way to avoid having all notifications delivered
|
||||
* to all observers: signal detail. In order to use signal detail,
|
||||
* each action name must be quarked, which is not always practical.
|
||||
*
|
||||
* - even if quarking is acceptable, #GObject signal details are
|
||||
* implemented by scanning a linked list, so there is no real
|
||||
* decrease in complexity
|
||||
*/
|
||||
|
||||
void
|
||||
g_action_observer_default_init (GActionObserverInterface *class)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* g_action_observer_action_added:
|
||||
* @observer: a #GActionObserver
|
||||
* @observable: the source of the event
|
||||
* @action_name: the name of the action
|
||||
* @enabled: %TRUE if the action is now enabled
|
||||
* @parameter_type: the parameter type for action invocations, or %NULL
|
||||
* if no parameter is required
|
||||
* @state: the current state of the action, or %NULL if the action is
|
||||
* stateless
|
||||
*
|
||||
* This function is called when an action that the observer is
|
||||
* registered to receive events for is added.
|
||||
*
|
||||
* This function should only be called by objects with which the
|
||||
* observer has explicitly registered itself to receive events.
|
||||
*/
|
||||
void
|
||||
g_action_observer_action_added (GActionObserver *observer,
|
||||
GActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
const GVariantType *parameter_type,
|
||||
gboolean enabled,
|
||||
GVariant *state)
|
||||
{
|
||||
g_return_if_fail (G_IS_ACTION_OBSERVER (observer));
|
||||
|
||||
G_ACTION_OBSERVER_GET_IFACE (observer)
|
||||
->action_added (observer, observable, action_name, parameter_type, enabled, state);
|
||||
}
|
||||
|
||||
/*
|
||||
* g_action_observer_action_enabled_changed:
|
||||
* @observer: a #GActionObserver
|
||||
* @observable: the source of the event
|
||||
* @action_name: the name of the action
|
||||
* @enabled: %TRUE if the action is now enabled
|
||||
*
|
||||
* This function is called when an action that the observer is
|
||||
* registered to receive events for becomes enabled or disabled.
|
||||
*
|
||||
* This function should only be called by objects with which the
|
||||
* observer has explicitly registered itself to receive events.
|
||||
*/
|
||||
void
|
||||
g_action_observer_action_enabled_changed (GActionObserver *observer,
|
||||
GActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
gboolean enabled)
|
||||
{
|
||||
g_return_if_fail (G_IS_ACTION_OBSERVER (observer));
|
||||
|
||||
G_ACTION_OBSERVER_GET_IFACE (observer)
|
||||
->action_enabled_changed (observer, observable, action_name, enabled);
|
||||
}
|
||||
|
||||
/*
|
||||
* g_action_observer_action_state_changed:
|
||||
* @observer: a #GActionObserver
|
||||
* @observable: the source of the event
|
||||
* @action_name: the name of the action
|
||||
* @state: the new state of the action
|
||||
*
|
||||
* This function is called when an action that the observer is
|
||||
* registered to receive events for changes its state.
|
||||
*
|
||||
* This function should only be called by objects with which the
|
||||
* observer has explicitly registered itself to receive events.
|
||||
*/
|
||||
void
|
||||
g_action_observer_action_state_changed (GActionObserver *observer,
|
||||
GActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
GVariant *state)
|
||||
{
|
||||
g_return_if_fail (G_IS_ACTION_OBSERVER (observer));
|
||||
|
||||
G_ACTION_OBSERVER_GET_IFACE (observer)
|
||||
->action_state_changed (observer, observable, action_name, state);
|
||||
}
|
||||
|
||||
/*
|
||||
* g_action_observer_action_removed:
|
||||
* @observer: a #GActionObserver
|
||||
* @observable: the source of the event
|
||||
* @action_name: the name of the action
|
||||
*
|
||||
* This function is called when an action that the observer is
|
||||
* registered to receive events for is removed.
|
||||
*
|
||||
* This function should only be called by objects with which the
|
||||
* observer has explicitly registered itself to receive events.
|
||||
*/
|
||||
void
|
||||
g_action_observer_action_removed (GActionObserver *observer,
|
||||
GActionObservable *observable,
|
||||
const gchar *action_name)
|
||||
{
|
||||
g_return_if_fail (G_IS_ACTION_OBSERVER (observer));
|
||||
|
||||
G_ACTION_OBSERVER_GET_IFACE (observer)
|
||||
->action_removed (observer, observable, action_name);
|
||||
}
|
90
src/gactionobserver.h
Normal file
90
src/gactionobserver.h
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright © 2011 Canonical Limited
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* licence or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
|
||||
* USA.
|
||||
*
|
||||
* Authors: Ryan Lortie <desrt@desrt.ca>
|
||||
*/
|
||||
|
||||
#ifndef __G_ACTION_OBSERVER_H__
|
||||
#define __G_ACTION_OBSERVER_H__
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define G_TYPE_ACTION_OBSERVER (g_action_observer_get_type ())
|
||||
#define G_ACTION_OBSERVER(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
|
||||
G_TYPE_ACTION_OBSERVER, GActionObserver))
|
||||
#define G_IS_ACTION_OBSERVER(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \
|
||||
G_TYPE_ACTION_OBSERVER))
|
||||
#define G_ACTION_OBSERVER_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), \
|
||||
G_TYPE_ACTION_OBSERVER, GActionObserverInterface))
|
||||
|
||||
typedef struct _GActionObserverInterface GActionObserverInterface;
|
||||
typedef struct _GActionObservable GActionObservable;
|
||||
typedef struct _GActionObserver GActionObserver;
|
||||
|
||||
struct _GActionObserverInterface
|
||||
{
|
||||
GTypeInterface g_iface;
|
||||
|
||||
void (* action_added) (GActionObserver *observer,
|
||||
GActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
const GVariantType *parameter_type,
|
||||
gboolean enabled,
|
||||
GVariant *state);
|
||||
void (* action_enabled_changed) (GActionObserver *observer,
|
||||
GActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
gboolean enabled);
|
||||
void (* action_state_changed) (GActionObserver *observer,
|
||||
GActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
GVariant *state);
|
||||
void (* action_removed) (GActionObserver *observer,
|
||||
GActionObservable *observable,
|
||||
const gchar *action_name);
|
||||
};
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
GType g_action_observer_get_type (void);
|
||||
G_GNUC_INTERNAL
|
||||
void g_action_observer_action_added (GActionObserver *observer,
|
||||
GActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
const GVariantType *parameter_type,
|
||||
gboolean enabled,
|
||||
GVariant *state);
|
||||
G_GNUC_INTERNAL
|
||||
void g_action_observer_action_enabled_changed (GActionObserver *observer,
|
||||
GActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
gboolean enabled);
|
||||
G_GNUC_INTERNAL
|
||||
void g_action_observer_action_state_changed (GActionObserver *observer,
|
||||
GActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
GVariant *state);
|
||||
G_GNUC_INTERNAL
|
||||
void g_action_observer_action_removed (GActionObserver *observer,
|
||||
GActionObservable *observable,
|
||||
const gchar *action_name);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __G_ACTION_OBSERVER_H__ */
|
@ -323,7 +323,7 @@ gnome_shell_plugin_xevent_filter (MetaPlugin *plugin,
|
||||
#endif
|
||||
|
||||
if ((xev->xany.type == EnterNotify || xev->xany.type == LeaveNotify)
|
||||
&& xev->xcrossing.window == clutter_x11_get_stage_window (CLUTTER_STAGE (clutter_stage_get_default ())))
|
||||
&& xev->xcrossing.window == clutter_x11_get_stage_window (CLUTTER_STAGE (meta_plugin_get_stage (plugin))))
|
||||
{
|
||||
/* If the pointer enters a child of the stage window (eg, a
|
||||
* trayicon), we want to consider it to still be in the stage,
|
||||
|
249
src/main.c
249
src/main.c
@ -8,7 +8,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <cogl-pango/cogl-pango.h>
|
||||
#include <clutter/clutter.h>
|
||||
#include <clutter/x11/clutter-x11.h>
|
||||
#include <gdk/gdk.h>
|
||||
@ -159,157 +158,6 @@ shell_dbus_init (gboolean replace)
|
||||
g_object_unref (session);
|
||||
}
|
||||
|
||||
static void
|
||||
constrain_tooltip (StTooltip *tooltip,
|
||||
const ClutterGeometry *geometry,
|
||||
ClutterGeometry *adjusted_geometry,
|
||||
gpointer data)
|
||||
{
|
||||
const ClutterGeometry *tip_area = st_tooltip_get_tip_area (tooltip);
|
||||
ShellGlobal *global = shell_global_get ();
|
||||
MetaScreen *screen = shell_global_get_screen (global);
|
||||
int n_monitors = meta_screen_get_n_monitors (screen);
|
||||
int i;
|
||||
|
||||
*adjusted_geometry = *geometry;
|
||||
|
||||
/* A point that determines what screen we'll constrain to */
|
||||
int x = tip_area->x + tip_area->width / 2;
|
||||
int y = tip_area->y + tip_area->height / 2;
|
||||
|
||||
for (i = 0; i < n_monitors; i++)
|
||||
{
|
||||
MetaRectangle rect;
|
||||
meta_screen_get_monitor_geometry (screen, i, &rect);
|
||||
if (x >= rect.x && x < rect.x + rect.width &&
|
||||
y >= rect.y && y < rect.y + rect.height)
|
||||
{
|
||||
if (adjusted_geometry->x + adjusted_geometry->width > rect.x + rect.width)
|
||||
adjusted_geometry->x = rect.x + rect.width - adjusted_geometry->width;
|
||||
if (adjusted_geometry->x < rect.x)
|
||||
adjusted_geometry->x = rect.x;
|
||||
|
||||
if (adjusted_geometry->y + adjusted_geometry->height > rect.y + rect.height)
|
||||
adjusted_geometry->y = rect.y + rect.height - adjusted_geometry->height;
|
||||
if (adjusted_geometry->y < rect.y)
|
||||
adjusted_geometry->y = rect.y;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
update_font_options (GtkSettings *settings)
|
||||
{
|
||||
StThemeContext *context;
|
||||
ClutterStage *stage;
|
||||
ClutterBackend *backend;
|
||||
gint dpi;
|
||||
gint hinting;
|
||||
gchar *hint_style_str;
|
||||
cairo_hint_style_t hint_style = CAIRO_HINT_STYLE_NONE;
|
||||
gint antialias;
|
||||
cairo_antialias_t antialias_mode = CAIRO_ANTIALIAS_NONE;
|
||||
cairo_font_options_t *options;
|
||||
|
||||
g_object_get (settings,
|
||||
"gtk-xft-dpi", &dpi,
|
||||
"gtk-xft-antialias", &antialias,
|
||||
"gtk-xft-hinting", &hinting,
|
||||
"gtk-xft-hintstyle", &hint_style_str,
|
||||
NULL);
|
||||
|
||||
stage = CLUTTER_STAGE (clutter_stage_get_default ());
|
||||
context = st_theme_context_get_for_stage (stage);
|
||||
|
||||
if (dpi != -1)
|
||||
/* GTK stores resolution as 1024 * dots/inch */
|
||||
st_theme_context_set_resolution (context, dpi / 1024);
|
||||
else
|
||||
st_theme_context_set_default_resolution (context);
|
||||
|
||||
st_tooltip_set_constrain_func (stage, constrain_tooltip, NULL, NULL);
|
||||
|
||||
/* Clutter (as of 0.9) passes comprehensively wrong font options
|
||||
* override whatever set_font_flags() did above.
|
||||
*
|
||||
* http://bugzilla.openedhand.com/show_bug.cgi?id=1456
|
||||
*/
|
||||
backend = clutter_get_default_backend ();
|
||||
options = cairo_font_options_create ();
|
||||
|
||||
cairo_font_options_set_hint_metrics (options, CAIRO_HINT_METRICS_ON);
|
||||
|
||||
if (hinting >= 0 && !hinting)
|
||||
{
|
||||
hint_style = CAIRO_HINT_STYLE_NONE;
|
||||
}
|
||||
else if (hint_style_str)
|
||||
{
|
||||
if (strcmp (hint_style_str, "hintnone") == 0)
|
||||
hint_style = CAIRO_HINT_STYLE_NONE;
|
||||
else if (strcmp (hint_style_str, "hintslight") == 0)
|
||||
hint_style = CAIRO_HINT_STYLE_SLIGHT;
|
||||
else if (strcmp (hint_style_str, "hintmedium") == 0)
|
||||
hint_style = CAIRO_HINT_STYLE_MEDIUM;
|
||||
else if (strcmp (hint_style_str, "hintfull") == 0)
|
||||
hint_style = CAIRO_HINT_STYLE_FULL;
|
||||
}
|
||||
|
||||
g_free (hint_style_str);
|
||||
|
||||
cairo_font_options_set_hint_style (options, hint_style);
|
||||
|
||||
/* We don't want to turn on subpixel anti-aliasing; since Clutter
|
||||
* doesn't currently have the code to support ARGB masks,
|
||||
* generating them then squashing them back to A8 is pointless.
|
||||
*/
|
||||
antialias_mode = (antialias < 0 || antialias) ? CAIRO_ANTIALIAS_GRAY
|
||||
: CAIRO_ANTIALIAS_NONE;
|
||||
|
||||
cairo_font_options_set_antialias (options, antialias_mode);
|
||||
|
||||
clutter_backend_set_font_options (backend, options);
|
||||
cairo_font_options_destroy (options);
|
||||
}
|
||||
|
||||
static void
|
||||
settings_notify_cb (GtkSettings *settings,
|
||||
GParamSpec *pspec,
|
||||
gpointer data)
|
||||
{
|
||||
update_font_options (settings);
|
||||
}
|
||||
|
||||
static void
|
||||
shell_fonts_init (void)
|
||||
{
|
||||
GtkSettings *settings;
|
||||
CoglPangoFontMap *fontmap;
|
||||
|
||||
/* Disable text mipmapping; it causes problems on pre-GEM Intel
|
||||
* drivers and we should just be rendering text at the right
|
||||
* size rather than scaling it. If we do effects where we dynamically
|
||||
* zoom labels, then we might want to reconsider.
|
||||
*/
|
||||
fontmap = COGL_PANGO_FONT_MAP (clutter_get_font_map ());
|
||||
cogl_pango_font_map_set_use_mipmapping (fontmap, FALSE);
|
||||
|
||||
settings = gtk_settings_get_default ();
|
||||
g_object_connect (settings,
|
||||
"signal::notify::gtk-xft-dpi",
|
||||
G_CALLBACK (settings_notify_cb), NULL,
|
||||
"signal::notify::gtk-xft-antialias",
|
||||
G_CALLBACK (settings_notify_cb), NULL,
|
||||
"signal::notify::gtk-xft-hinting",
|
||||
G_CALLBACK (settings_notify_cb), NULL,
|
||||
"signal::notify::gtk-xft-hintstyle",
|
||||
G_CALLBACK (settings_notify_cb), NULL,
|
||||
NULL);
|
||||
update_font_options (settings);
|
||||
}
|
||||
|
||||
static void
|
||||
shell_prefs_init (void)
|
||||
{
|
||||
@ -323,100 +171,6 @@ shell_prefs_init (void)
|
||||
OVERRIDES_SCHEMA);
|
||||
}
|
||||
|
||||
/* This is an IBus workaround. The flow of events with IBus is that every time
|
||||
* it gets gets a key event, it:
|
||||
*
|
||||
* Sends it to the daemon via D-Bus asynchronously
|
||||
* When it gets an reply, synthesizes a new GdkEvent and puts it into the
|
||||
* GDK event queue with gdk_event_put(), including
|
||||
* IBUS_FORWARD_MASK = 1 << 25 in the state to prevent a loop.
|
||||
*
|
||||
* (Normally, IBus uses the GTK+ key snooper mechanism to get the key
|
||||
* events early, but since our key events aren't visible to GTK+ key snoopers,
|
||||
* IBus will instead get the events via the standard
|
||||
* GtkIMContext.filter_keypress() mechanism.)
|
||||
*
|
||||
* There are a number of potential problems here; probably the worst
|
||||
* problem is that IBus doesn't forward the timestamp with the event
|
||||
* so that every key event that gets delivered ends up with
|
||||
* GDK_CURRENT_TIME. This creates some very subtle bugs; for example
|
||||
* if you have IBus running and a keystroke is used to trigger
|
||||
* launching an application, focus stealing prevention won't work
|
||||
* right. http://code.google.com/p/ibus/issues/detail?id=1184
|
||||
*
|
||||
* In any case, our normal flow of key events is:
|
||||
*
|
||||
* GDK filter function => clutter_x11_handle_event => clutter actor
|
||||
*
|
||||
* So, if we see a key event that gets delivered via the GDK event handler
|
||||
* function - then we know it must be one of these synthesized events, and
|
||||
* we should push it back to clutter.
|
||||
*
|
||||
* To summarize, the full key event flow with IBus is:
|
||||
*
|
||||
* GDK filter function
|
||||
* => Mutter
|
||||
* => gnome_shell_plugin_xevent_filter()
|
||||
* => clutter_x11_handle_event()
|
||||
* => clutter event delivery to actor
|
||||
* => gtk_im_context_filter_event()
|
||||
* => sent to IBus daemon
|
||||
* => response received from IBus daemon
|
||||
* => gdk_event_put()
|
||||
* => GDK event handler
|
||||
* => <this function>
|
||||
* => clutter_event_put()
|
||||
* => clutter event delivery to actor
|
||||
*
|
||||
* Anything else we see here we just pass on to the normal GDK event handler
|
||||
* gtk_main_do_event().
|
||||
*/
|
||||
static void
|
||||
gnome_shell_gdk_event_handler (GdkEvent *event_gdk,
|
||||
gpointer data)
|
||||
{
|
||||
if (event_gdk->type == GDK_KEY_PRESS || event_gdk->type == GDK_KEY_RELEASE)
|
||||
{
|
||||
ClutterActor *stage;
|
||||
Window stage_xwindow;
|
||||
|
||||
stage = clutter_stage_get_default ();
|
||||
stage_xwindow = clutter_x11_get_stage_window (CLUTTER_STAGE (stage));
|
||||
|
||||
if (GDK_WINDOW_XID (event_gdk->key.window) == stage_xwindow)
|
||||
{
|
||||
ClutterDeviceManager *device_manager = clutter_device_manager_get_default ();
|
||||
ClutterInputDevice *keyboard = clutter_device_manager_get_core_device (device_manager,
|
||||
CLUTTER_KEYBOARD_DEVICE);
|
||||
|
||||
ClutterEvent *event_clutter = clutter_event_new ((event_gdk->type == GDK_KEY_PRESS) ?
|
||||
CLUTTER_KEY_PRESS : CLUTTER_KEY_RELEASE);
|
||||
event_clutter->key.time = event_gdk->key.time;
|
||||
event_clutter->key.flags = CLUTTER_EVENT_NONE;
|
||||
event_clutter->key.stage = CLUTTER_STAGE (stage);
|
||||
event_clutter->key.source = NULL;
|
||||
|
||||
/* This depends on ClutterModifierType and GdkModifierType being
|
||||
* identical, which they are currently. (They both match the X
|
||||
* modifier state in the low 16-bits and have the same extensions.) */
|
||||
event_clutter->key.modifier_state = event_gdk->key.state;
|
||||
|
||||
event_clutter->key.keyval = event_gdk->key.keyval;
|
||||
event_clutter->key.hardware_keycode = event_gdk->key.hardware_keycode;
|
||||
event_clutter->key.unicode_value = gdk_keyval_to_unicode (event_clutter->key.keyval);
|
||||
event_clutter->key.device = keyboard;
|
||||
|
||||
clutter_event_put (event_clutter);
|
||||
clutter_event_free (event_clutter);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
gtk_main_do_event (event_gdk);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
malloc_statistics_callback (ShellPerfLog *perf_log,
|
||||
gpointer data)
|
||||
@ -559,12 +313,9 @@ main (int argc, char **argv)
|
||||
|
||||
shell_dbus_init (meta_get_replace_current_wm ());
|
||||
shell_a11y_init ();
|
||||
shell_fonts_init ();
|
||||
shell_perf_log_init ();
|
||||
shell_prefs_init ();
|
||||
|
||||
gdk_event_handler_set (gnome_shell_gdk_event_handler, NULL, NULL);
|
||||
|
||||
g_irepository_prepend_search_path (GNOME_SHELL_PKGLIBDIR);
|
||||
#if HAVE_BLUETOOTH
|
||||
g_irepository_prepend_search_path (BLUETOOTH_DIR);
|
||||
|
@ -63,7 +63,6 @@ int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
GOptionContext *context;
|
||||
ClutterActor *stage;
|
||||
GError *error = NULL;
|
||||
ShellGlobal *global;
|
||||
GjsContext *js_context;
|
||||
@ -124,9 +123,8 @@ main(int argc, char **argv)
|
||||
filename = argv[1];
|
||||
}
|
||||
|
||||
stage = clutter_stage_get_default ();
|
||||
title = g_filename_display_basename (filename);
|
||||
clutter_stage_set_title (CLUTTER_STAGE (stage), title);
|
||||
g_set_prgname (title);
|
||||
g_free (title);
|
||||
|
||||
#if HAVE_BLUETOOTH
|
||||
|
319
src/shell-app.c
319
src/shell-app.c
@ -15,6 +15,7 @@
|
||||
#include "shell-app-system-private.h"
|
||||
#include "shell-window-tracker-private.h"
|
||||
#include "st.h"
|
||||
#include "gactionmuxer.h"
|
||||
|
||||
typedef enum {
|
||||
MATCH_NONE,
|
||||
@ -37,6 +38,15 @@ typedef struct {
|
||||
|
||||
/* Whether or not we need to resort the windows; this is done on demand */
|
||||
gboolean window_sort_stale : 1;
|
||||
|
||||
/* See GApplication documentation */
|
||||
gint name_watcher_id;
|
||||
gchar *dbus_name;
|
||||
GDBusProxy *app_proxy;
|
||||
GActionGroup *remote_actions;
|
||||
GMenuModel *remote_menu;
|
||||
GActionMuxer *muxer;
|
||||
GCancellable *dbus_cancellable;
|
||||
} ShellAppRunningState;
|
||||
|
||||
/**
|
||||
@ -72,11 +82,13 @@ struct _ShellApp
|
||||
char *casefolded_exec;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (ShellApp, shell_app, G_TYPE_OBJECT);
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_STATE
|
||||
PROP_STATE,
|
||||
PROP_ID,
|
||||
PROP_DBUS_ID,
|
||||
PROP_ACTION_GROUP,
|
||||
PROP_MENU
|
||||
};
|
||||
|
||||
enum {
|
||||
@ -88,6 +100,15 @@ static guint shell_app_signals[LAST_SIGNAL] = { 0 };
|
||||
|
||||
static void create_running_state (ShellApp *app);
|
||||
static void unref_running_state (ShellAppRunningState *state);
|
||||
static void on_dbus_name_appeared (GDBusConnection *bus,
|
||||
const gchar *name,
|
||||
const gchar *name_owner,
|
||||
gpointer user_data);
|
||||
static void on_dbus_name_disappeared (GDBusConnection *bus,
|
||||
const gchar *name,
|
||||
gpointer user_data);
|
||||
|
||||
G_DEFINE_TYPE (ShellApp, shell_app, G_TYPE_OBJECT)
|
||||
|
||||
static void
|
||||
shell_app_get_property (GObject *gobject,
|
||||
@ -102,6 +123,20 @@ shell_app_get_property (GObject *gobject,
|
||||
case PROP_STATE:
|
||||
g_value_set_enum (value, app->state);
|
||||
break;
|
||||
case PROP_ID:
|
||||
g_value_set_string (value, shell_app_get_id (app));
|
||||
break;
|
||||
case PROP_DBUS_ID:
|
||||
g_value_set_string (value, shell_app_get_dbus_id (app));
|
||||
break;
|
||||
case PROP_ACTION_GROUP:
|
||||
if (app->running_state)
|
||||
g_value_set_object (value, app->running_state->muxer);
|
||||
break;
|
||||
case PROP_MENU:
|
||||
if (app->running_state)
|
||||
g_value_set_object (value, app->running_state->remote_menu);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
||||
break;
|
||||
@ -151,6 +186,15 @@ window_backed_app_get_icon (ShellApp *app,
|
||||
return actor;
|
||||
}
|
||||
|
||||
const char *
|
||||
shell_app_get_dbus_id (ShellApp *app)
|
||||
{
|
||||
if (app->running_state)
|
||||
return app->running_state->dbus_name;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_app_create_icon_texture:
|
||||
*
|
||||
@ -513,6 +557,34 @@ shell_app_activate_window (ShellApp *app,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
shell_app_update_window_actions (ShellApp *app, MetaWindow *window)
|
||||
{
|
||||
const char *object_path;
|
||||
|
||||
object_path = meta_window_get_dbus_object_path (window);
|
||||
if (object_path != NULL)
|
||||
{
|
||||
GActionGroup *actions;
|
||||
|
||||
actions = g_object_get_data (G_OBJECT (window), "actions");
|
||||
if (actions == NULL)
|
||||
{
|
||||
actions = G_ACTION_GROUP (g_dbus_action_group_get (g_dbus_proxy_get_connection (app->running_state->app_proxy),
|
||||
meta_window_get_dbus_unique_name (window),
|
||||
object_path));
|
||||
g_object_set_data_full (G_OBJECT (window), "actions", actions, g_object_unref);
|
||||
}
|
||||
|
||||
if (!app->running_state->muxer)
|
||||
app->running_state->muxer = g_action_muxer_new ();
|
||||
|
||||
g_action_muxer_insert (app->running_state->muxer, "win", actions);
|
||||
g_object_notify (G_OBJECT (app), "action-group");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_app_activate:
|
||||
* @app: a #ShellApp
|
||||
@ -903,6 +975,34 @@ shell_app_on_ws_switch (MetaScreen *screen,
|
||||
g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
|
||||
}
|
||||
|
||||
static void
|
||||
on_dbus_application_id_changed (MetaWindow *window,
|
||||
GParamSpec *pspec,
|
||||
gpointer user_data)
|
||||
{
|
||||
const char *appid;
|
||||
ShellApp *app = SHELL_APP (user_data);
|
||||
|
||||
/* Ignore changes in the appid after it's set, shouldn't happen */
|
||||
if (app->running_state->dbus_name != NULL)
|
||||
return;
|
||||
|
||||
appid = meta_window_get_dbus_application_id (window);
|
||||
|
||||
if (!appid)
|
||||
return;
|
||||
|
||||
g_assert (app->running_state != NULL);
|
||||
app->running_state->dbus_name = g_strdup (appid);
|
||||
app->running_state->name_watcher_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
|
||||
appid,
|
||||
G_BUS_NAME_WATCHER_FLAGS_NONE,
|
||||
on_dbus_name_appeared,
|
||||
on_dbus_name_disappeared,
|
||||
g_object_ref (app),
|
||||
g_object_unref);
|
||||
}
|
||||
|
||||
void
|
||||
_shell_app_add_window (ShellApp *app,
|
||||
MetaWindow *window)
|
||||
@ -923,6 +1023,9 @@ _shell_app_add_window (ShellApp *app,
|
||||
if (app->state != SHELL_APP_STATE_STARTING)
|
||||
shell_app_state_transition (app, SHELL_APP_STATE_RUNNING);
|
||||
|
||||
g_signal_connect (window, "notify::dbus-application-id", G_CALLBACK(on_dbus_application_id_changed), app);
|
||||
on_dbus_application_id_changed (window, NULL, app);
|
||||
|
||||
g_object_thaw_notify (G_OBJECT (app));
|
||||
|
||||
g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
|
||||
@ -939,6 +1042,7 @@ _shell_app_remove_window (ShellApp *app,
|
||||
|
||||
g_signal_handlers_disconnect_by_func (window, G_CALLBACK(shell_app_on_unmanaged), app);
|
||||
g_signal_handlers_disconnect_by_func (window, G_CALLBACK(shell_app_on_user_time_changed), app);
|
||||
g_signal_handlers_disconnect_by_func (window, G_CALLBACK(on_dbus_application_id_changed), app);
|
||||
g_object_unref (window);
|
||||
app->running_state->windows = g_slist_remove (app->running_state->windows, window);
|
||||
|
||||
@ -948,6 +1052,137 @@ _shell_app_remove_window (ShellApp *app,
|
||||
g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
|
||||
}
|
||||
|
||||
static void
|
||||
on_dbus_proxy_gotten (GObject *initable,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
ShellApp *self = SHELL_APP (user_data);
|
||||
ShellAppRunningState *state = self->running_state;
|
||||
GError *error = NULL;
|
||||
GVariant *menu_property;
|
||||
|
||||
state->app_proxy = g_dbus_proxy_new_finish (result,
|
||||
&error);
|
||||
|
||||
if (error)
|
||||
{
|
||||
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
|
||||
!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
|
||||
{
|
||||
g_warning ("Unexpected error while creating application proxy: %s", error->message);
|
||||
}
|
||||
|
||||
g_clear_error (&error);
|
||||
g_clear_object (&state->dbus_cancellable);
|
||||
|
||||
if (state->name_watcher_id)
|
||||
{
|
||||
g_bus_unwatch_name (state->name_watcher_id);
|
||||
state->name_watcher_id = 0;
|
||||
}
|
||||
|
||||
g_free (state->dbus_name);
|
||||
state->dbus_name = NULL;
|
||||
|
||||
g_object_unref (self);
|
||||
return;
|
||||
}
|
||||
|
||||
/* on to the second step, the primary action group */
|
||||
|
||||
state->remote_actions = (GActionGroup*)g_dbus_action_group_get (
|
||||
g_dbus_proxy_get_connection (state->app_proxy),
|
||||
g_dbus_proxy_get_name (state->app_proxy),
|
||||
g_dbus_proxy_get_object_path (state->app_proxy));
|
||||
|
||||
if (!state->muxer)
|
||||
state->muxer = g_action_muxer_new ();
|
||||
|
||||
g_action_muxer_insert (state->muxer, "app", state->remote_actions);
|
||||
g_strfreev (g_action_group_list_actions (state->remote_actions));
|
||||
|
||||
g_object_notify (G_OBJECT (self), "action-group");
|
||||
|
||||
menu_property = g_dbus_proxy_get_cached_property (state->app_proxy, "AppMenu");
|
||||
|
||||
if (menu_property && g_variant_n_children (menu_property) > 0)
|
||||
{
|
||||
const gchar *object_path;
|
||||
|
||||
g_variant_get_child (menu_property, 0, "&o", &object_path);
|
||||
|
||||
state->remote_menu = G_MENU_MODEL (g_dbus_menu_model_get (g_dbus_proxy_get_connection (state->app_proxy),
|
||||
state->dbus_name,
|
||||
object_path));
|
||||
|
||||
g_object_notify (G_OBJECT (self), "menu");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_dbus_name_appeared (GDBusConnection *bus,
|
||||
const gchar *name,
|
||||
const gchar *name_owner,
|
||||
gpointer user_data)
|
||||
{
|
||||
ShellApp *self = SHELL_APP (user_data);
|
||||
ShellAppRunningState *state = self->running_state;
|
||||
char *object_path;
|
||||
|
||||
g_assert (state != NULL);
|
||||
|
||||
object_path = g_strconcat ("/", name, NULL);
|
||||
g_strdelimit (object_path, ".", '/');
|
||||
|
||||
if (!state->dbus_cancellable)
|
||||
state->dbus_cancellable = g_cancellable_new ();
|
||||
|
||||
/* first step: the application proxy */
|
||||
|
||||
g_dbus_proxy_new (bus,
|
||||
G_DBUS_PROXY_FLAGS_NONE,
|
||||
NULL, /* interface info */
|
||||
name_owner,
|
||||
object_path,
|
||||
"org.gtk.Application",
|
||||
state->dbus_cancellable,
|
||||
on_dbus_proxy_gotten,
|
||||
g_object_ref (self));
|
||||
|
||||
g_object_notify (G_OBJECT (self), "dbus-id");
|
||||
|
||||
g_free (object_path);
|
||||
}
|
||||
|
||||
static void
|
||||
on_dbus_name_disappeared (GDBusConnection *bus,
|
||||
const gchar *name,
|
||||
gpointer user_data)
|
||||
{
|
||||
ShellApp *self = SHELL_APP (user_data);
|
||||
ShellAppRunningState *state = self->running_state;
|
||||
|
||||
g_assert (state != NULL);
|
||||
|
||||
if (state->dbus_cancellable)
|
||||
{
|
||||
g_cancellable_cancel (state->dbus_cancellable);
|
||||
g_clear_object (&state->dbus_cancellable);
|
||||
}
|
||||
|
||||
g_clear_object (&state->app_proxy);
|
||||
g_clear_object (&state->remote_actions);
|
||||
g_clear_object (&state->remote_menu);
|
||||
g_clear_object (&state->muxer);
|
||||
|
||||
g_free (state->dbus_name);
|
||||
state->dbus_name = NULL;
|
||||
|
||||
g_bus_unwatch_name (state->name_watcher_id);
|
||||
state->name_watcher_id = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_app_get_pids:
|
||||
* @app: a #ShellApp
|
||||
@ -1167,13 +1402,30 @@ unref_running_state (ShellAppRunningState *state)
|
||||
{
|
||||
MetaScreen *screen;
|
||||
|
||||
g_assert (state->refcount > 0);
|
||||
|
||||
state->refcount--;
|
||||
if (state->refcount > 0)
|
||||
return;
|
||||
|
||||
screen = shell_global_get_screen (shell_global_get ());
|
||||
|
||||
g_signal_handler_disconnect (screen, state->workspace_switch_id);
|
||||
|
||||
if (state->dbus_cancellable)
|
||||
{
|
||||
g_cancellable_cancel (state->dbus_cancellable);
|
||||
g_object_unref (state->dbus_cancellable);
|
||||
}
|
||||
|
||||
g_clear_object (&state->app_proxy);
|
||||
g_clear_object (&state->remote_actions);
|
||||
g_clear_object (&state->remote_menu);
|
||||
g_clear_object (&state->muxer);
|
||||
g_free (state->dbus_name);
|
||||
|
||||
if (state->name_watcher_id)
|
||||
g_bus_unwatch_name (state->name_watcher_id);
|
||||
|
||||
g_slice_free (ShellAppRunningState, state);
|
||||
}
|
||||
|
||||
@ -1349,6 +1601,9 @@ shell_app_dispose (GObject *object)
|
||||
while (app->running_state->windows)
|
||||
_shell_app_remove_window (app, app->running_state->windows->data);
|
||||
}
|
||||
/* We should have been transitioned when we removed all of our windows */
|
||||
g_assert (app->state == SHELL_APP_STATE_STOPPED);
|
||||
g_assert (app->running_state == NULL);
|
||||
|
||||
G_OBJECT_CLASS(shell_app_parent_class)->dispose (object);
|
||||
}
|
||||
@ -1399,4 +1654,60 @@ shell_app_class_init(ShellAppClass *klass)
|
||||
SHELL_TYPE_APP_STATE,
|
||||
SHELL_APP_STATE_STOPPED,
|
||||
G_PARAM_READABLE));
|
||||
|
||||
/**
|
||||
* ShellApp:id:
|
||||
*
|
||||
* The id of this application (a desktop filename, or a special string
|
||||
* like window:0xabcd1234)
|
||||
*/
|
||||
g_object_class_install_property (gobject_class,
|
||||
PROP_ID,
|
||||
g_param_spec_string ("id",
|
||||
"Application id",
|
||||
"The desktop file id of this ShellApp",
|
||||
NULL,
|
||||
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
/**
|
||||
* ShellApp:dbus-id:
|
||||
*
|
||||
* The DBus well-known name of the application, if one can be associated
|
||||
* to this ShellApp (it means that the application is using GApplication)
|
||||
*/
|
||||
g_object_class_install_property (gobject_class,
|
||||
PROP_DBUS_ID,
|
||||
g_param_spec_string ("dbus-id",
|
||||
"Application DBus Id",
|
||||
"The DBus well-known name of the application",
|
||||
NULL,
|
||||
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
/**
|
||||
* ShellApp:action-group:
|
||||
*
|
||||
* The #GDBusActionGroup associated with this ShellApp, if any. See the
|
||||
* documentation of #GApplication and #GActionGroup for details.
|
||||
*/
|
||||
g_object_class_install_property (gobject_class,
|
||||
PROP_ACTION_GROUP,
|
||||
g_param_spec_object ("action-group",
|
||||
"Application Action Group",
|
||||
"The action group exported by the remote application",
|
||||
G_TYPE_ACTION_GROUP,
|
||||
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
/**
|
||||
* ShellApp:menu:
|
||||
*
|
||||
* The #GMenuProxy associated with this ShellApp, if any. See the
|
||||
* documentation of #GMenuModel for details.
|
||||
*/
|
||||
g_object_class_install_property (gobject_class,
|
||||
PROP_MENU,
|
||||
g_param_spec_object ("menu",
|
||||
"Application Menu",
|
||||
"The primary menu exported by the remote application",
|
||||
G_TYPE_MENU_MODEL,
|
||||
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ G_BEGIN_DECLS
|
||||
typedef struct _ShellApp ShellApp;
|
||||
typedef struct _ShellAppClass ShellAppClass;
|
||||
typedef struct _ShellAppPrivate ShellAppPrivate;
|
||||
typedef struct _ShellAppAction ShellAppAction;
|
||||
|
||||
#define SHELL_TYPE_APP (shell_app_get_type ())
|
||||
#define SHELL_APP(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_APP, ShellApp))
|
||||
@ -36,9 +37,12 @@ typedef enum {
|
||||
GType shell_app_get_type (void) G_GNUC_CONST;
|
||||
|
||||
const char *shell_app_get_id (ShellApp *app);
|
||||
|
||||
GMenuTreeEntry *shell_app_get_tree_entry (ShellApp *app);
|
||||
GDesktopAppInfo *shell_app_get_app_info (ShellApp *app);
|
||||
|
||||
const char *shell_app_get_dbus_id (ShellApp *app);
|
||||
|
||||
ClutterActor *shell_app_create_icon_texture (ShellApp *app, int size);
|
||||
ClutterActor *shell_app_get_faded_icon (ShellApp *app, int size);
|
||||
const char *shell_app_get_name (ShellApp *app);
|
||||
@ -79,6 +83,8 @@ int shell_app_compare_by_name (ShellApp *app, ShellApp *other);
|
||||
|
||||
int shell_app_compare (ShellApp *app, ShellApp *other);
|
||||
|
||||
void shell_app_update_window_actions (ShellApp *app, MetaWindow *window);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __SHELL_APP_H__ */
|
||||
|
@ -15,6 +15,7 @@
|
||||
#endif
|
||||
|
||||
#include <X11/extensions/Xfixes.h>
|
||||
#include <cogl-pango/cogl-pango.h>
|
||||
#include <canberra.h>
|
||||
#include <clutter/glx/clutter-glx.h>
|
||||
#include <clutter/x11/clutter-x11.h>
|
||||
@ -793,6 +794,249 @@ global_stage_after_paint (ClutterStage *stage,
|
||||
"clutter.stagePaintDone");
|
||||
}
|
||||
|
||||
static void
|
||||
constrain_tooltip (StTooltip *tooltip,
|
||||
const ClutterGeometry *geometry,
|
||||
ClutterGeometry *adjusted_geometry,
|
||||
gpointer data)
|
||||
{
|
||||
const ClutterGeometry *tip_area = st_tooltip_get_tip_area (tooltip);
|
||||
ShellGlobal *global = shell_global_get ();
|
||||
MetaScreen *screen = shell_global_get_screen (global);
|
||||
int n_monitors = meta_screen_get_n_monitors (screen);
|
||||
int i;
|
||||
|
||||
*adjusted_geometry = *geometry;
|
||||
|
||||
/* A point that determines what screen we'll constrain to */
|
||||
int x = tip_area->x + tip_area->width / 2;
|
||||
int y = tip_area->y + tip_area->height / 2;
|
||||
|
||||
for (i = 0; i < n_monitors; i++)
|
||||
{
|
||||
MetaRectangle rect;
|
||||
meta_screen_get_monitor_geometry (screen, i, &rect);
|
||||
if (x >= rect.x && x < rect.x + rect.width &&
|
||||
y >= rect.y && y < rect.y + rect.height)
|
||||
{
|
||||
if (adjusted_geometry->x + adjusted_geometry->width > rect.x + rect.width)
|
||||
adjusted_geometry->x = rect.x + rect.width - adjusted_geometry->width;
|
||||
if (adjusted_geometry->x < rect.x)
|
||||
adjusted_geometry->x = rect.x;
|
||||
|
||||
if (adjusted_geometry->y + adjusted_geometry->height > rect.y + rect.height)
|
||||
adjusted_geometry->y = rect.y + rect.height - adjusted_geometry->height;
|
||||
if (adjusted_geometry->y < rect.y)
|
||||
adjusted_geometry->y = rect.y;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
update_font_options (GtkSettings *settings,
|
||||
ClutterStage *stage)
|
||||
{
|
||||
StThemeContext *context;
|
||||
ClutterBackend *backend;
|
||||
gint dpi;
|
||||
gint hinting;
|
||||
gchar *hint_style_str;
|
||||
cairo_hint_style_t hint_style = CAIRO_HINT_STYLE_NONE;
|
||||
gint antialias;
|
||||
cairo_antialias_t antialias_mode = CAIRO_ANTIALIAS_NONE;
|
||||
cairo_font_options_t *options;
|
||||
|
||||
g_object_get (settings,
|
||||
"gtk-xft-dpi", &dpi,
|
||||
"gtk-xft-antialias", &antialias,
|
||||
"gtk-xft-hinting", &hinting,
|
||||
"gtk-xft-hintstyle", &hint_style_str,
|
||||
NULL);
|
||||
|
||||
context = st_theme_context_get_for_stage (stage);
|
||||
|
||||
if (dpi != -1)
|
||||
/* GTK stores resolution as 1024 * dots/inch */
|
||||
st_theme_context_set_resolution (context, dpi / 1024);
|
||||
else
|
||||
st_theme_context_set_default_resolution (context);
|
||||
|
||||
st_tooltip_set_constrain_func (stage, constrain_tooltip, NULL, NULL);
|
||||
|
||||
/* Clutter (as of 0.9) passes comprehensively wrong font options
|
||||
* override whatever set_font_flags() did above.
|
||||
*
|
||||
* http://bugzilla.openedhand.com/show_bug.cgi?id=1456
|
||||
*/
|
||||
backend = clutter_get_default_backend ();
|
||||
options = cairo_font_options_create ();
|
||||
|
||||
cairo_font_options_set_hint_metrics (options, CAIRO_HINT_METRICS_ON);
|
||||
|
||||
if (hinting >= 0 && !hinting)
|
||||
{
|
||||
hint_style = CAIRO_HINT_STYLE_NONE;
|
||||
}
|
||||
else if (hint_style_str)
|
||||
{
|
||||
if (strcmp (hint_style_str, "hintnone") == 0)
|
||||
hint_style = CAIRO_HINT_STYLE_NONE;
|
||||
else if (strcmp (hint_style_str, "hintslight") == 0)
|
||||
hint_style = CAIRO_HINT_STYLE_SLIGHT;
|
||||
else if (strcmp (hint_style_str, "hintmedium") == 0)
|
||||
hint_style = CAIRO_HINT_STYLE_MEDIUM;
|
||||
else if (strcmp (hint_style_str, "hintfull") == 0)
|
||||
hint_style = CAIRO_HINT_STYLE_FULL;
|
||||
}
|
||||
|
||||
g_free (hint_style_str);
|
||||
|
||||
cairo_font_options_set_hint_style (options, hint_style);
|
||||
|
||||
/* We don't want to turn on subpixel anti-aliasing; since Clutter
|
||||
* doesn't currently have the code to support ARGB masks,
|
||||
* generating them then squashing them back to A8 is pointless.
|
||||
*/
|
||||
antialias_mode = (antialias < 0 || antialias) ? CAIRO_ANTIALIAS_GRAY
|
||||
: CAIRO_ANTIALIAS_NONE;
|
||||
|
||||
cairo_font_options_set_antialias (options, antialias_mode);
|
||||
|
||||
clutter_backend_set_font_options (backend, options);
|
||||
cairo_font_options_destroy (options);
|
||||
}
|
||||
|
||||
static void
|
||||
settings_notify_cb (GtkSettings *settings,
|
||||
GParamSpec *pspec,
|
||||
gpointer data)
|
||||
{
|
||||
update_font_options (settings, CLUTTER_STAGE (data));
|
||||
}
|
||||
|
||||
static void
|
||||
shell_fonts_init (ClutterStage *stage)
|
||||
{
|
||||
GtkSettings *settings;
|
||||
CoglPangoFontMap *fontmap;
|
||||
|
||||
/* Disable text mipmapping; it causes problems on pre-GEM Intel
|
||||
* drivers and we should just be rendering text at the right
|
||||
* size rather than scaling it. If we do effects where we dynamically
|
||||
* zoom labels, then we might want to reconsider.
|
||||
*/
|
||||
fontmap = COGL_PANGO_FONT_MAP (clutter_get_font_map ());
|
||||
cogl_pango_font_map_set_use_mipmapping (fontmap, FALSE);
|
||||
|
||||
settings = gtk_settings_get_default ();
|
||||
g_object_connect (settings,
|
||||
"signal::notify::gtk-xft-dpi",
|
||||
G_CALLBACK (settings_notify_cb), stage,
|
||||
"signal::notify::gtk-xft-antialias",
|
||||
G_CALLBACK (settings_notify_cb), stage,
|
||||
"signal::notify::gtk-xft-hinting",
|
||||
G_CALLBACK (settings_notify_cb), stage,
|
||||
"signal::notify::gtk-xft-hintstyle",
|
||||
G_CALLBACK (settings_notify_cb), stage,
|
||||
NULL);
|
||||
update_font_options (settings, stage);
|
||||
}
|
||||
|
||||
/* This is an IBus workaround. The flow of events with IBus is that every time
|
||||
* it gets gets a key event, it:
|
||||
*
|
||||
* Sends it to the daemon via D-Bus asynchronously
|
||||
* When it gets an reply, synthesizes a new GdkEvent and puts it into the
|
||||
* GDK event queue with gdk_event_put(), including
|
||||
* IBUS_FORWARD_MASK = 1 << 25 in the state to prevent a loop.
|
||||
*
|
||||
* (Normally, IBus uses the GTK+ key snooper mechanism to get the key
|
||||
* events early, but since our key events aren't visible to GTK+ key snoopers,
|
||||
* IBus will instead get the events via the standard
|
||||
* GtkIMContext.filter_keypress() mechanism.)
|
||||
*
|
||||
* There are a number of potential problems here; probably the worst
|
||||
* problem is that IBus doesn't forward the timestamp with the event
|
||||
* so that every key event that gets delivered ends up with
|
||||
* GDK_CURRENT_TIME. This creates some very subtle bugs; for example
|
||||
* if you have IBus running and a keystroke is used to trigger
|
||||
* launching an application, focus stealing prevention won't work
|
||||
* right. http://code.google.com/p/ibus/issues/detail?id=1184
|
||||
*
|
||||
* In any case, our normal flow of key events is:
|
||||
*
|
||||
* GDK filter function => clutter_x11_handle_event => clutter actor
|
||||
*
|
||||
* So, if we see a key event that gets delivered via the GDK event handler
|
||||
* function - then we know it must be one of these synthesized events, and
|
||||
* we should push it back to clutter.
|
||||
*
|
||||
* To summarize, the full key event flow with IBus is:
|
||||
*
|
||||
* GDK filter function
|
||||
* => Mutter
|
||||
* => gnome_shell_plugin_xevent_filter()
|
||||
* => clutter_x11_handle_event()
|
||||
* => clutter event delivery to actor
|
||||
* => gtk_im_context_filter_event()
|
||||
* => sent to IBus daemon
|
||||
* => response received from IBus daemon
|
||||
* => gdk_event_put()
|
||||
* => GDK event handler
|
||||
* => <this function>
|
||||
* => clutter_event_put()
|
||||
* => clutter event delivery to actor
|
||||
*
|
||||
* Anything else we see here we just pass on to the normal GDK event handler
|
||||
* gtk_main_do_event().
|
||||
*/
|
||||
static void
|
||||
gnome_shell_gdk_event_handler (GdkEvent *event_gdk,
|
||||
gpointer data)
|
||||
{
|
||||
if (event_gdk->type == GDK_KEY_PRESS || event_gdk->type == GDK_KEY_RELEASE)
|
||||
{
|
||||
ClutterActor *stage;
|
||||
Window stage_xwindow;
|
||||
|
||||
stage = CLUTTER_ACTOR (data);
|
||||
stage_xwindow = clutter_x11_get_stage_window (CLUTTER_STAGE (stage));
|
||||
|
||||
if (GDK_WINDOW_XID (event_gdk->key.window) == stage_xwindow)
|
||||
{
|
||||
ClutterDeviceManager *device_manager = clutter_device_manager_get_default ();
|
||||
ClutterInputDevice *keyboard = clutter_device_manager_get_core_device (device_manager,
|
||||
CLUTTER_KEYBOARD_DEVICE);
|
||||
|
||||
ClutterEvent *event_clutter = clutter_event_new ((event_gdk->type == GDK_KEY_PRESS) ?
|
||||
CLUTTER_KEY_PRESS : CLUTTER_KEY_RELEASE);
|
||||
event_clutter->key.time = event_gdk->key.time;
|
||||
event_clutter->key.flags = CLUTTER_EVENT_NONE;
|
||||
event_clutter->key.stage = CLUTTER_STAGE (stage);
|
||||
event_clutter->key.source = NULL;
|
||||
|
||||
/* This depends on ClutterModifierType and GdkModifierType being
|
||||
* identical, which they are currently. (They both match the X
|
||||
* modifier state in the low 16-bits and have the same extensions.) */
|
||||
event_clutter->key.modifier_state = event_gdk->key.state;
|
||||
|
||||
event_clutter->key.keyval = event_gdk->key.keyval;
|
||||
event_clutter->key.hardware_keycode = event_gdk->key.hardware_keycode;
|
||||
event_clutter->key.unicode_value = gdk_keyval_to_unicode (event_clutter->key.keyval);
|
||||
event_clutter->key.device = keyboard;
|
||||
|
||||
clutter_event_put (event_clutter);
|
||||
clutter_event_free (event_clutter);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
gtk_main_do_event (event_gdk);
|
||||
}
|
||||
|
||||
void
|
||||
_shell_global_set_plugin (ShellGlobal *global,
|
||||
MetaPlugin *plugin)
|
||||
@ -838,6 +1082,10 @@ _shell_global_set_plugin (ShellGlobal *global,
|
||||
g_signal_connect (global->meta_display, "notify::focus-window",
|
||||
G_CALLBACK (focus_window_changed), global);
|
||||
|
||||
shell_fonts_init (global->stage);
|
||||
|
||||
gdk_event_handler_set (gnome_shell_gdk_event_handler, global->stage, NULL);
|
||||
|
||||
global->focus_manager = st_focus_manager_get_for_stage (global->stage);
|
||||
}
|
||||
|
||||
|
@ -413,6 +413,9 @@ update_focus_app (ShellWindowTracker *self)
|
||||
new_focus_win = meta_display_get_focus_window (shell_global_get_display (shell_global_get ()));
|
||||
new_focus_app = new_focus_win ? shell_window_tracker_get_window_app (self, new_focus_win) : NULL;
|
||||
|
||||
if (new_focus_app)
|
||||
shell_app_update_window_actions (new_focus_app, new_focus_win);
|
||||
|
||||
set_focus_app (self, new_focus_app);
|
||||
}
|
||||
|
||||
|
@ -321,19 +321,29 @@ shell_xfixes_cursor_class_init (ShellXFixesCursorClass *klass)
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_xfixes_cursor_get_default:
|
||||
* shell_xfixes_cursor_get_for_stage:
|
||||
* @stage: (transfer none): The #ClutterStage to get the cursor for
|
||||
*
|
||||
* Return value: (transfer none): The global #ShellXFixesCursor singleton
|
||||
* Return value: (transfer none): A #ShellXFixesCursor instance
|
||||
*/
|
||||
ShellXFixesCursor *
|
||||
shell_xfixes_cursor_get_default ()
|
||||
shell_xfixes_cursor_get_for_stage (ClutterStage *stage)
|
||||
{
|
||||
static ShellXFixesCursor *instance = NULL;
|
||||
ShellXFixesCursor *instance;
|
||||
static GQuark xfixes_cursor_quark;
|
||||
|
||||
if (G_UNLIKELY (xfixes_cursor_quark == 0))
|
||||
xfixes_cursor_quark = g_quark_from_static_string ("gnome-shell-xfixes-cursor");
|
||||
|
||||
instance = g_object_get_qdata (G_OBJECT (stage), xfixes_cursor_quark);
|
||||
|
||||
if (instance == NULL)
|
||||
instance = g_object_new (SHELL_TYPE_XFIXES_CURSOR,
|
||||
"stage", clutter_stage_get_default (),
|
||||
NULL);
|
||||
{
|
||||
instance = g_object_new (SHELL_TYPE_XFIXES_CURSOR,
|
||||
"stage", stage,
|
||||
NULL);
|
||||
g_object_set_qdata (G_OBJECT (stage), xfixes_cursor_quark, instance);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ typedef struct _ShellXFixesCursorClass ShellXFixesCursorClass;
|
||||
|
||||
GType shell_xfixes_cursor_get_type (void) G_GNUC_CONST;
|
||||
|
||||
ShellXFixesCursor *shell_xfixes_cursor_get_default (void);
|
||||
ShellXFixesCursor *shell_xfixes_cursor_get_for_stage (ClutterStage *stage);
|
||||
|
||||
void shell_xfixes_cursor_show (ShellXFixesCursor *xfixes_cursor);
|
||||
void shell_xfixes_cursor_hide (ShellXFixesCursor *xfixes_cursor);
|
||||
|
@ -174,6 +174,20 @@ st_icon_dispose (GObject *gobject)
|
||||
G_OBJECT_CLASS (st_icon_parent_class)->dispose (gobject);
|
||||
}
|
||||
|
||||
static void
|
||||
st_icon_finalize (GObject *gobject)
|
||||
{
|
||||
StIconPrivate *priv = ST_ICON (gobject)->priv;
|
||||
|
||||
if (priv->icon_name)
|
||||
{
|
||||
g_free (priv->icon_name);
|
||||
priv->icon_name = NULL;
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (st_icon_parent_class)->finalize (gobject);
|
||||
}
|
||||
|
||||
static void
|
||||
st_icon_get_preferred_height (ClutterActor *actor,
|
||||
gfloat for_width,
|
||||
@ -317,6 +331,7 @@ st_icon_class_init (StIconClass *klass)
|
||||
object_class->get_property = st_icon_get_property;
|
||||
object_class->set_property = st_icon_set_property;
|
||||
object_class->dispose = st_icon_dispose;
|
||||
object_class->finalize = st_icon_finalize;
|
||||
|
||||
actor_class->get_preferred_height = st_icon_get_preferred_height;
|
||||
actor_class->get_preferred_width = st_icon_get_preferred_width;
|
||||
|
@ -1226,9 +1226,9 @@ load_sliced_image (GSimpleAsyncResult *result,
|
||||
|
||||
width = gdk_pixbuf_get_width (pix);
|
||||
height = gdk_pixbuf_get_height (pix);
|
||||
for (y = 0; y < height; y += data->grid_width)
|
||||
for (y = 0; y < height; y += data->grid_height)
|
||||
{
|
||||
for (x = 0; x < width; x += data->grid_height)
|
||||
for (x = 0; x < width; x += data->grid_width)
|
||||
{
|
||||
GdkPixbuf *pixbuf = gdk_pixbuf_new_subpixbuf (pix, x, y, data->grid_width, data->grid_height);
|
||||
g_assert (pixbuf != NULL);
|
||||
|
@ -5,6 +5,7 @@
|
||||
* Copyright 2009, 2010 Red Hat, Inc.
|
||||
* Copyright 2009, 2010 Florian Müllner
|
||||
* Copyright 2010 Intel Corporation.
|
||||
* Copyright 2011 Quentin "Sardem FF7" Glidic
|
||||
*
|
||||
* Contains code derived from:
|
||||
* rectangle.c: Rounded rectangle.
|
||||
@ -390,69 +391,110 @@ st_theme_node_lookup_corner (StThemeNode *node,
|
||||
return material;
|
||||
}
|
||||
|
||||
static void
|
||||
get_background_scale (StThemeNode *node,
|
||||
gdouble painting_area_width,
|
||||
gdouble painting_area_height,
|
||||
gdouble background_image_width,
|
||||
gdouble background_image_height,
|
||||
gdouble *scale_w,
|
||||
gdouble *scale_h)
|
||||
{
|
||||
*scale_w = -1.0;
|
||||
*scale_h = -1.0;
|
||||
|
||||
switch (node->background_size)
|
||||
{
|
||||
case ST_BACKGROUND_SIZE_AUTO:
|
||||
*scale_w = 1.0;
|
||||
break;
|
||||
case ST_BACKGROUND_SIZE_CONTAIN:
|
||||
if (background_image_width > background_image_height)
|
||||
*scale_w = painting_area_width / background_image_width;
|
||||
else
|
||||
*scale_w = painting_area_height / background_image_height;
|
||||
break;
|
||||
case ST_BACKGROUND_SIZE_COVER:
|
||||
if (background_image_width < background_image_height)
|
||||
*scale_w = painting_area_width / background_image_width;
|
||||
else
|
||||
*scale_w = painting_area_height / background_image_height;
|
||||
break;
|
||||
case ST_BACKGROUND_SIZE_FIXED:
|
||||
if (node->background_size_w > -1)
|
||||
{
|
||||
*scale_w = node->background_size_w / background_image_width;
|
||||
if (node->background_size_h > -1)
|
||||
*scale_h = node->background_size_h / background_image_height;
|
||||
}
|
||||
else if (node->background_size_h > -1)
|
||||
*scale_w = node->background_size_h / background_image_height;
|
||||
break;
|
||||
}
|
||||
if (*scale_h < 0.0)
|
||||
*scale_h = *scale_w;
|
||||
}
|
||||
|
||||
static void
|
||||
get_background_coordinates (StThemeNode *node,
|
||||
gdouble painting_area_width,
|
||||
gdouble painting_area_height,
|
||||
gdouble background_image_width,
|
||||
gdouble background_image_height,
|
||||
gdouble *x,
|
||||
gdouble *y)
|
||||
{
|
||||
/* honor the specified position if any */
|
||||
if (node->background_position_set)
|
||||
{
|
||||
*x = node->background_position_x;
|
||||
*y = node->background_position_y;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* center the background on the widget */
|
||||
*x = (painting_area_width / 2.0) - (background_image_width / 2.0);
|
||||
*y = (painting_area_height / 2.0) - (background_image_height / 2.0);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
get_background_position (StThemeNode *self,
|
||||
const ClutterActorBox *allocation,
|
||||
ClutterActorBox *result)
|
||||
{
|
||||
gfloat w, h;
|
||||
gdouble painting_area_width, painting_area_height;
|
||||
gdouble background_image_width, background_image_height;
|
||||
gdouble x, y;
|
||||
gdouble scale_w, scale_h;
|
||||
|
||||
result->x1 = result->y1 = 0;
|
||||
result->x2 = allocation->x2 - allocation->x1;
|
||||
result->y2 = allocation->y2 - allocation->y1;
|
||||
/* get the background image size */
|
||||
background_image_width = allocation->x2 - allocation->x1;
|
||||
background_image_height = allocation->y2 - allocation->y1;
|
||||
|
||||
w = cogl_texture_get_width (self->background_texture);
|
||||
h = cogl_texture_get_height (self->background_texture);
|
||||
/* get the painting area size */
|
||||
painting_area_width = cogl_texture_get_width (self->background_texture);
|
||||
painting_area_height = cogl_texture_get_height (self->background_texture);
|
||||
|
||||
/* scale the background into the allocated bounds, when not being absolutely positioned */
|
||||
if ((w > result->x2 || h > result->y2) && !self->background_position_set)
|
||||
{
|
||||
gint new_h, new_w, offset;
|
||||
gint box_w, box_h;
|
||||
/* scale if requested */
|
||||
get_background_scale (self,
|
||||
painting_area_width, painting_area_height,
|
||||
background_image_width, background_image_height,
|
||||
&scale_w, &scale_h);
|
||||
background_image_width *= scale_w;
|
||||
background_image_height *= scale_h;
|
||||
|
||||
box_w = (int) result->x2;
|
||||
box_h = (int) result->y2;
|
||||
/* get coordinates */
|
||||
get_background_coordinates (self,
|
||||
painting_area_width, painting_area_height,
|
||||
background_image_width, background_image_height,
|
||||
&x, &y);
|
||||
|
||||
/* scale to fit */
|
||||
new_h = (int)((h / w) * ((gfloat) box_w));
|
||||
new_w = (int)((w / h) * ((gfloat) box_h));
|
||||
|
||||
if (new_h > box_h)
|
||||
{
|
||||
/* center for new width */
|
||||
offset = ((box_w) - new_w) * 0.5;
|
||||
result->x1 = offset;
|
||||
result->x2 = offset + new_w;
|
||||
|
||||
result->y2 = box_h;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* center for new height */
|
||||
offset = ((box_h) - new_h) * 0.5;
|
||||
result->y1 = offset;
|
||||
result->y2 = offset + new_h;
|
||||
|
||||
result->x2 = box_w;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* honor the specified position if any */
|
||||
if (self->background_position_set)
|
||||
{
|
||||
result->x1 = self->background_position_x;
|
||||
result->y1 = self->background_position_y;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* center the background on the widget */
|
||||
result->x1 = (int)(((allocation->x2 - allocation->x1) / 2) - (w / 2));
|
||||
result->y1 = (int)(((allocation->y2 - allocation->y1) / 2) - (h / 2));
|
||||
}
|
||||
result->x2 = result->x1 + w;
|
||||
result->y2 = result->y1 + h;
|
||||
}
|
||||
/* place the background image */
|
||||
result->x1 = x;
|
||||
result->y1 = y;
|
||||
result->x2 = result->x1 + background_image_width;
|
||||
result->y2 = result->y1 + background_image_height;
|
||||
}
|
||||
|
||||
/* Use of this function marks code which doesn't support
|
||||
@ -533,12 +575,13 @@ create_cairo_pattern_of_background_image (StThemeNode *node,
|
||||
cairo_content_t content;
|
||||
cairo_matrix_t matrix;
|
||||
const char *file;
|
||||
double height_ratio, width_ratio;
|
||||
int file_width;
|
||||
int file_height;
|
||||
|
||||
StTextureCache *texture_cache;
|
||||
|
||||
gdouble background_image_width, background_image_height;
|
||||
gdouble x, y;
|
||||
gdouble scale_w, scale_h;
|
||||
|
||||
file = st_theme_node_get_background_image (node);
|
||||
|
||||
texture_cache = st_texture_cache_get_default ();
|
||||
@ -553,90 +596,39 @@ create_cairo_pattern_of_background_image (StThemeNode *node,
|
||||
content = cairo_surface_get_content (surface);
|
||||
pattern = cairo_pattern_create_for_surface (surface);
|
||||
|
||||
file_width = cairo_image_surface_get_width (surface);
|
||||
file_height = cairo_image_surface_get_height (surface);
|
||||
|
||||
height_ratio = file_height / node->alloc_height;
|
||||
width_ratio = file_width / node->alloc_width;
|
||||
background_image_width = cairo_image_surface_get_width (surface);
|
||||
background_image_height = cairo_image_surface_get_height (surface);
|
||||
|
||||
*needs_background_fill = TRUE;
|
||||
if ((file_width > node->alloc_width || file_height > node->alloc_height)
|
||||
&& !node->background_position_set)
|
||||
{
|
||||
double scale_factor;
|
||||
double x_offset, y_offset;
|
||||
|
||||
if (width_ratio > height_ratio)
|
||||
{
|
||||
double scaled_height;
|
||||
cairo_matrix_init_identity (&matrix);
|
||||
|
||||
/* center vertically */
|
||||
get_background_scale (node,
|
||||
node->alloc_width, node->alloc_height,
|
||||
background_image_width, background_image_height,
|
||||
&scale_w, &scale_h);
|
||||
if ((scale_w != 1) || (scale_h != 1))
|
||||
cairo_matrix_scale (&matrix, 1.0/scale_w, 1.0/scale_h);
|
||||
background_image_width *= scale_w;
|
||||
background_image_height *= scale_h;
|
||||
|
||||
scale_factor = width_ratio;
|
||||
scaled_height = file_height / scale_factor;
|
||||
get_background_coordinates (node,
|
||||
node->alloc_width, node->alloc_height,
|
||||
background_image_width, background_image_height,
|
||||
&x, &y);
|
||||
cairo_matrix_translate (&matrix, -x, -y);
|
||||
|
||||
x_offset = 0.;
|
||||
y_offset = - (node->alloc_height / 2. - scaled_height / 2.);
|
||||
}
|
||||
else
|
||||
{
|
||||
double scaled_width;
|
||||
/* If it's opaque, fills up the entire allocated
|
||||
* area, then don't bother doing a background fill first
|
||||
*/
|
||||
if (content != CAIRO_CONTENT_COLOR_ALPHA
|
||||
&& x >= 0
|
||||
&& -x + background_image_width >= node->alloc_width
|
||||
&& y >= 0
|
||||
&& -y + background_image_height >= node->alloc_height)
|
||||
*needs_background_fill = FALSE;
|
||||
|
||||
/* center horizontally */
|
||||
|
||||
scale_factor = height_ratio;
|
||||
scaled_width = file_width / scale_factor;
|
||||
|
||||
y_offset = 0.;
|
||||
x_offset = - (node->alloc_width / 2. - scaled_width / 2.);
|
||||
}
|
||||
|
||||
cairo_matrix_init_scale (&matrix, scale_factor, scale_factor);
|
||||
cairo_matrix_translate (&matrix, x_offset, y_offset);
|
||||
|
||||
cairo_pattern_set_matrix (pattern, &matrix);
|
||||
|
||||
/* If it's opaque, and when scaled, fills up the entire allocated
|
||||
* area, then don't bother doing a background fill first
|
||||
*/
|
||||
if (content != CAIRO_CONTENT_COLOR_ALPHA && width_ratio == height_ratio)
|
||||
*needs_background_fill = FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
double x_offset, y_offset;
|
||||
|
||||
if (node->background_position_set)
|
||||
{
|
||||
x_offset = -node->background_position_x;
|
||||
y_offset = -node->background_position_y;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (node->alloc_width > file_width)
|
||||
x_offset = - (node->alloc_width / 2.0 - file_width / 2.0);
|
||||
else
|
||||
x_offset = - (file_width / 2.0 - node->alloc_width / 2.0);
|
||||
|
||||
if (node->alloc_height > file_height)
|
||||
y_offset = - (node->alloc_height / 2.0 - file_height / 2.0);
|
||||
else
|
||||
y_offset = - (file_height / 2.0 - node->alloc_height / 2.0);
|
||||
}
|
||||
|
||||
/* If it's opaque, and when translated, fills up the entire allocated
|
||||
* area, then don't bother doing a background fill first
|
||||
*/
|
||||
if (content != CAIRO_CONTENT_COLOR_ALPHA
|
||||
&& -x_offset <= 0
|
||||
&& -x_offset + file_width >= node->alloc_width
|
||||
&& -y_offset <= 0
|
||||
&& -y_offset + file_height >= node->alloc_height)
|
||||
*needs_background_fill = FALSE;
|
||||
|
||||
cairo_matrix_init_translate (&matrix, x_offset, y_offset);
|
||||
cairo_pattern_set_matrix (pattern, &matrix);
|
||||
}
|
||||
cairo_pattern_set_matrix (pattern, &matrix);
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
* st-theme-node-private.h: private structures and functions for StThemeNode
|
||||
*
|
||||
* Copyright 2009, 2010 Red Hat, Inc.
|
||||
* Copyright 2011 Quentin "Sardem FF7" Glidic
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
@ -24,6 +25,7 @@
|
||||
#include <gdk/gdk.h>
|
||||
|
||||
#include "st-theme-node.h"
|
||||
#include "st-types.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
@ -44,6 +46,9 @@ struct _StThemeNode {
|
||||
int background_position_x;
|
||||
int background_position_y;
|
||||
gboolean background_position_set : 1;
|
||||
StBackgroundSize background_size;
|
||||
gint background_size_w;
|
||||
gint background_size_h;
|
||||
|
||||
ClutterColor foreground_color;
|
||||
ClutterColor border_color[4];
|
||||
|
@ -7,6 +7,7 @@
|
||||
* Copyright 2009, 2010 Florian Müllner
|
||||
* Copyright 2010 Adel Gadllah
|
||||
* Copyright 2010 Giovanni Campagna
|
||||
* Copyright 2011 Quentin "Sardem FF7" Glidic
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
@ -90,6 +91,12 @@ st_theme_node_dispose (GObject *gobject)
|
||||
node->border_image = NULL;
|
||||
}
|
||||
|
||||
if (node->icon_colors)
|
||||
{
|
||||
st_icon_colors_unref (node->icon_colors);
|
||||
node->icon_colors = NULL;
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (st_theme_node_parent_class)->dispose (gobject);
|
||||
}
|
||||
|
||||
@ -1573,6 +1580,7 @@ _st_theme_node_ensure_background (StThemeNode *node)
|
||||
node->background_color = TRANSPARENT_COLOR;
|
||||
node->background_gradient_type = ST_GRADIENT_NONE;
|
||||
node->background_position_set = FALSE;
|
||||
node->background_size = ST_BACKGROUND_SIZE_AUTO;
|
||||
|
||||
ensure_properties (node);
|
||||
|
||||
@ -1600,6 +1608,7 @@ _st_theme_node_ensure_background (StThemeNode *node)
|
||||
g_free (node->background_image);
|
||||
node->background_image = NULL;
|
||||
node->background_position_set = FALSE;
|
||||
node->background_size = ST_BACKGROUND_SIZE_AUTO;
|
||||
|
||||
for (term = decl->value; term; term = term->next)
|
||||
{
|
||||
@ -1656,6 +1665,44 @@ _st_theme_node_ensure_background (StThemeNode *node)
|
||||
else
|
||||
node->background_position_set = TRUE;
|
||||
}
|
||||
else if (strcmp (property_name, "-size") == 0)
|
||||
{
|
||||
if (decl->value->type == TERM_IDENT)
|
||||
{
|
||||
if (strcmp (decl->value->content.str->stryng->str, "contain") == 0)
|
||||
node->background_size = ST_BACKGROUND_SIZE_CONTAIN;
|
||||
else if (strcmp (decl->value->content.str->stryng->str, "cover") == 0)
|
||||
node->background_size = ST_BACKGROUND_SIZE_COVER;
|
||||
else if ((strcmp (decl->value->content.str->stryng->str, "auto") == 0) && (decl->value->next) && (decl->value->next->type == TERM_NUMBER))
|
||||
{
|
||||
GetFromTermResult result = get_length_from_term_int (node, decl->value->next, FALSE, &node->background_size_h);
|
||||
|
||||
node->background_size_w = -1;
|
||||
node->background_size = (result == VALUE_FOUND) ? ST_BACKGROUND_SIZE_FIXED : ST_BACKGROUND_SIZE_AUTO;
|
||||
}
|
||||
else
|
||||
node->background_size = ST_BACKGROUND_SIZE_AUTO;
|
||||
}
|
||||
else if (decl->value->type == TERM_NUMBER)
|
||||
{
|
||||
GetFromTermResult result = get_length_from_term_int (node, decl->value, FALSE, &node->background_size_w);
|
||||
if (result == VALUE_NOT_FOUND)
|
||||
continue;
|
||||
|
||||
node->background_size = ST_BACKGROUND_SIZE_FIXED;
|
||||
|
||||
if ((decl->value->next) && (decl->value->next->type == TERM_NUMBER))
|
||||
{
|
||||
result = get_length_from_term_int (node, decl->value->next, FALSE, &node->background_size_h);
|
||||
|
||||
if (result == VALUE_FOUND)
|
||||
continue;
|
||||
}
|
||||
node->background_size_h = -1;
|
||||
}
|
||||
else
|
||||
node->background_size = ST_BACKGROUND_SIZE_AUTO;
|
||||
}
|
||||
else if (strcmp (property_name, "-color") == 0)
|
||||
{
|
||||
GetFromTermResult result;
|
||||
|
@ -49,6 +49,13 @@ typedef enum {
|
||||
ST_ICON_DOCUMENT
|
||||
} StIconType;
|
||||
|
||||
typedef enum {
|
||||
ST_BACKGROUND_SIZE_AUTO,
|
||||
ST_BACKGROUND_SIZE_CONTAIN,
|
||||
ST_BACKGROUND_SIZE_COVER,
|
||||
ST_BACKGROUND_SIZE_FIXED
|
||||
} StBackgroundSize;
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __ST_TYPES_H__ */
|
||||
|
@ -432,7 +432,7 @@ main (int argc, char **argv)
|
||||
theme = st_theme_new ("st/test-theme.css",
|
||||
NULL, NULL);
|
||||
|
||||
stage = clutter_stage_get_default ();
|
||||
stage = clutter_stage_new ();
|
||||
context = st_theme_context_get_for_stage (CLUTTER_STAGE (stage));
|
||||
st_theme_context_set_theme (context, theme);
|
||||
st_theme_context_set_resolution (context, 96.);
|
||||
|
104
src/test-gapplication.js
Executable file
104
src/test-gapplication.js
Executable file
@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env gjs
|
||||
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
|
||||
function do_action(action, parameter) {
|
||||
print ("Action '" + action.name + "' invoked");
|
||||
}
|
||||
|
||||
function do_action_param(action, parameter) {
|
||||
print ("Action '" + action.name + "' invoked with parameter " + parameter.print(true));
|
||||
}
|
||||
|
||||
function do_action_toggle(action) {
|
||||
action.set_state(GLib.Variant.new('b', !action.state.deep_unpack()));
|
||||
print ("Toggled");
|
||||
}
|
||||
|
||||
function do_action_state_change(action) {
|
||||
print ("Action '" + action.name + "' has now state " + action.state.print(true));
|
||||
}
|
||||
|
||||
function main() {
|
||||
Gtk.init(null, null);
|
||||
|
||||
let app = new Gtk.Application({ application_id: 'org.gnome.Shell.GtkApplicationTest' });
|
||||
app.connect('activate', function() {
|
||||
print ("Activated");
|
||||
});
|
||||
|
||||
let action = new Gio.SimpleAction({ name: 'one' });
|
||||
action.connect('activate', do_action);
|
||||
app.add_action(action);
|
||||
|
||||
let action = new Gio.SimpleAction({ name: 'two' });
|
||||
action.connect('activate', do_action);
|
||||
app.add_action(action);
|
||||
|
||||
let action = new Gio.SimpleAction({ name: 'toggle', state: GLib.Variant.new('b', false) });
|
||||
action.connect('activate', do_action_toggle);
|
||||
action.connect('notify::state', do_action_state_change);
|
||||
app.add_action(action);
|
||||
|
||||
let action = new Gio.SimpleAction({ name: 'disable', enabled: false });
|
||||
action.set_enabled(false);
|
||||
action.connect('activate', do_action);
|
||||
app.add_action(action);
|
||||
|
||||
let action = new Gio.SimpleAction({ name: 'parameter-int', parameter_type: GLib.VariantType.new('u') });
|
||||
action.connect('activate', do_action_param);
|
||||
app.add_action(action);
|
||||
|
||||
let action = new Gio.SimpleAction({ name: 'parameter-string', parameter_type: GLib.VariantType.new('s') });
|
||||
action.connect('activate', do_action_param);
|
||||
app.add_action(action);
|
||||
|
||||
let menu = new Gio.Menu();
|
||||
menu.append('An action', 'app.one');
|
||||
|
||||
let section = new Gio.Menu();
|
||||
section.append('Another action', 'app.two');
|
||||
section.append('Same as above', 'app.two');
|
||||
menu.append_section(null, section);
|
||||
|
||||
// another section, to check separators
|
||||
section = new Gio.Menu();
|
||||
section.append('Checkbox', 'app.toggle');
|
||||
section.append('Disabled', 'app.disable');
|
||||
menu.append_section(null, section);
|
||||
|
||||
// empty sections or submenus should be invisible
|
||||
menu.append_section('Empty section', new Gio.Menu());
|
||||
menu.append_submenu('Empty submenu', new Gio.Menu());
|
||||
|
||||
let submenu = new Gio.Menu();
|
||||
submenu.append('Open c:\\', 'app.parameter-string::c:\\');
|
||||
submenu.append('Open /home', 'app.parameter-string::/home');
|
||||
menu.append_submenu('Recent files', submenu);
|
||||
|
||||
let item = Gio.MenuItem.new('Say 42', null);
|
||||
item.set_action_and_target_value('app.parameter-int', GLib.Variant.new('u', 42));
|
||||
menu.append_item(item);
|
||||
|
||||
let item = Gio.MenuItem.new('Say 43', null);
|
||||
item.set_action_and_target_value('app.parameter-int', GLib.Variant.new('u', 43));
|
||||
menu.append_item(item);
|
||||
|
||||
app.set_app_menu(menu);
|
||||
|
||||
let window = null;
|
||||
|
||||
app.connect_after('startup', function(app) {
|
||||
window = new Gtk.ApplicationWindow({ title: "Test Application", application: app });
|
||||
});
|
||||
app.connect('activate', function(app) {
|
||||
window.present();
|
||||
});
|
||||
|
||||
app.run(null);
|
||||
}
|
||||
|
||||
main();
|
@ -36,7 +36,8 @@ int main (int argc, char **argv)
|
||||
clutter_color_from_string (&red, "red");
|
||||
clutter_color_from_string (&green, "green");
|
||||
clutter_color_from_string (&blue, "blue");
|
||||
stage = clutter_stage_get_default ();
|
||||
stage = clutter_stage_new ();
|
||||
g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
|
||||
|
||||
text = g_object_new (CLUTTER_TYPE_TEXT,
|
||||
"text", "Red",
|
||||
|
@ -2,6 +2,7 @@ noinst_SCRIPTS = run-test.sh
|
||||
EXTRA_DIST = run-test.sh.in
|
||||
|
||||
TEST_JS = \
|
||||
interactive/background-size.js \
|
||||
interactive/borders.js \
|
||||
interactive/border-radius.js \
|
||||
interactive/border-width.js \
|
||||
@ -16,10 +17,14 @@ TEST_JS = \
|
||||
interactive/scroll-view-sizing.js \
|
||||
interactive/table.js \
|
||||
interactive/transitions.js \
|
||||
testcommon/100-200.svg \
|
||||
testcommon/200-100.svg \
|
||||
testcommon/200-200.svg \
|
||||
testcommon/border-image.png \
|
||||
testcommon/face-plain.png \
|
||||
testcommon/ui.js \
|
||||
unit/format.js \
|
||||
unit/insertSorted.js \
|
||||
unit/markup.js \
|
||||
unit/jsParse.js \
|
||||
unit/url.js
|
||||
|
89
tests/interactive/background-size.js
Normal file
89
tests/interactive/background-size.js
Normal file
@ -0,0 +1,89 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const UI = imports.testcommon.ui;
|
||||
|
||||
UI.init();
|
||||
let stage = Clutter.Stage.get_default();
|
||||
stage.width = 1024;
|
||||
stage.height = 768;
|
||||
|
||||
let vbox = new St.BoxLayout({ width: stage.width,
|
||||
height: stage.height,
|
||||
style: 'background: #ffee88;' });
|
||||
stage.add_actor(vbox);
|
||||
|
||||
let scroll = new St.ScrollView();
|
||||
vbox.add(scroll, { expand: true });
|
||||
|
||||
let vbox = new St.BoxLayout({ vertical: true,
|
||||
style: 'padding: 10px;'
|
||||
+ 'spacing: 20px;' });
|
||||
scroll.add_actor(vbox);
|
||||
|
||||
let tbox = null;
|
||||
|
||||
function addTestCase(image, size, backgroundSize) {
|
||||
let obin = new St.Bin({ style: 'border: 3px solid green;' });
|
||||
tbox.add(obin);
|
||||
|
||||
let bin = new St.Bin({ style_class: 'background-image-' + image,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
style: 'border: 1px solid transparent;'
|
||||
+ 'background-size: ' + backgroundSize + ';',
|
||||
x_fill: true,
|
||||
y_fill: true
|
||||
});
|
||||
obin.set_child(bin);
|
||||
|
||||
bin.set_child(new St.Label({ text: backgroundSize,
|
||||
style: 'font-size: 15px;'
|
||||
+ 'text-align: center;'
|
||||
}));
|
||||
}
|
||||
|
||||
function addTestLine(image, size, backgroundSizes) {
|
||||
vbox.add(new St.Label({ text: image + '.svg / ' + size.width + '×' + size.height,
|
||||
style: 'font-size: 15px;'
|
||||
+ 'text-align: center;'
|
||||
}));
|
||||
|
||||
tbox = new St.BoxLayout({ style: 'spacing: 20px;' });
|
||||
vbox.add(tbox);
|
||||
|
||||
if (backgroundSizes.length == 2)
|
||||
addTestCase(image, size, "auto");
|
||||
for each (let s in backgroundSizes)
|
||||
addTestCase(image, size, s);
|
||||
}
|
||||
|
||||
let size1 = { width: 200, height: 200 }
|
||||
let size2 = { width: 250, height: 250 }
|
||||
let size3 = { width: 100, height: 100 }
|
||||
|
||||
// fixed size
|
||||
addTestLine('200-200', size1, ["200px 200px", "100px 100px", "100px 200px"]);
|
||||
|
||||
// same size
|
||||
addTestLine('200-200', size1, ["contain", "cover"]);
|
||||
// smaller
|
||||
addTestLine('200-200', size2, ["contain", "cover"]);
|
||||
// larger
|
||||
addTestLine('200-200', size3, ["contain", "cover"]);
|
||||
|
||||
|
||||
addTestLine('200-100', size1, ["contain", "cover"]);
|
||||
addTestLine('200-100', size2, ["contain", "cover"]);
|
||||
addTestLine('200-100', size3, ["contain", "cover"]);
|
||||
|
||||
|
||||
addTestLine('100-200', size1, ["contain", "cover"]);
|
||||
addTestLine('100-200', size2, ["contain", "cover"]);
|
||||
addTestLine('100-200', size3, ["contain", "cover"]);
|
||||
|
||||
stage.show();
|
||||
Clutter.main();
|
||||
stage.destroy();
|
21
tests/testcommon/100-200.svg
Normal file
21
tests/testcommon/100-200.svg
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 100 200" width="100" height="200">
|
||||
<path
|
||||
d="
|
||||
M 2,2 h 96 v 196 h -96 v -196
|
||||
M 8,8 h 84 v 184 h -84 v -184
|
||||
"
|
||||
fill="white"
|
||||
stroke="blue"
|
||||
stroke-width="2"
|
||||
stroke-linecap="square"
|
||||
/>
|
||||
<path
|
||||
d="
|
||||
M 10,10 h 20 v 20 h -20 v -20
|
||||
"
|
||||
fill="green"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 518 B |
21
tests/testcommon/200-100.svg
Normal file
21
tests/testcommon/200-100.svg
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 200 100" width="200" height="100">
|
||||
<path
|
||||
d="
|
||||
M 2,2 h 196 v 96 h -196 v -96
|
||||
M 8,8 h 184 v 84 h -184 v -84
|
||||
"
|
||||
fill="white"
|
||||
stroke="blue"
|
||||
stroke-width="2"
|
||||
stroke-linecap="square"
|
||||
/>
|
||||
<path
|
||||
d="
|
||||
M 10,10 h 20 v 20 h -20 v -20
|
||||
"
|
||||
fill="green"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 518 B |
21
tests/testcommon/200-200.svg
Normal file
21
tests/testcommon/200-200.svg
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 200 200" width="200" height="200">
|
||||
<path
|
||||
d="
|
||||
M 2,2 h 196 v 196 h -196 v -196
|
||||
M 8,8 h 184 v 184 h -184 v -184
|
||||
"
|
||||
fill="white"
|
||||
stroke="blue"
|
||||
stroke-width="2"
|
||||
stroke-linecap="square"
|
||||
/>
|
||||
<path
|
||||
d="
|
||||
M 10,10 h 20 v 20 h -20 v -20
|
||||
"
|
||||
fill="green"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 522 B |
@ -37,6 +37,18 @@ stage {
|
||||
border-image: url('border-image.png') 16;
|
||||
}
|
||||
|
||||
.background-image-200-200 {
|
||||
background-image: url('200-200.svg');
|
||||
}
|
||||
|
||||
.background-image-100-200 {
|
||||
background-image: url('100-200.svg');
|
||||
}
|
||||
|
||||
.background-image-200-100 {
|
||||
background-image: url('200-100.svg');
|
||||
}
|
||||
|
||||
.background-gradient {
|
||||
background-gradient-start: rgba(127, 255, 127, .6);
|
||||
background-gradient-end: rgba(127, 127, 255, .6);
|
||||
|
76
tests/unit/insertSorted.js
Normal file
76
tests/unit/insertSorted.js
Normal file
@ -0,0 +1,76 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
// Test cases for Util.insertSorted
|
||||
|
||||
const JsUnit = imports.jsUnit;
|
||||
|
||||
// Needed so that Util can bring some UI stuff
|
||||
// we don't actually use
|
||||
const Environment = imports.ui.environment;
|
||||
Environment.init();
|
||||
const Util = imports.misc.util;
|
||||
|
||||
function assertArrayEquals(errorMessage, array1, array2) {
|
||||
JsUnit.assertEquals(errorMessage + ' length',
|
||||
array1.length, array2.length);
|
||||
for (let j = 0; j < array1.length; j++) {
|
||||
JsUnit.assertEquals(errorMessage + ' item ' + j,
|
||||
array1[j], array2[j]);
|
||||
}
|
||||
}
|
||||
|
||||
function cmp(one, two) {
|
||||
return one-two;
|
||||
}
|
||||
|
||||
let arrayInt = [1, 2, 3, 5, 6];
|
||||
Util.insertSorted(arrayInt, 4, cmp);
|
||||
|
||||
assertArrayEquals('first test', [1,2,3,4,5,6], arrayInt);
|
||||
|
||||
// no comparator, integer sorting is implied
|
||||
Util.insertSorted(arrayInt, 3);
|
||||
|
||||
assertArrayEquals('second test', [1,2,3,3,4,5,6], arrayInt);
|
||||
|
||||
let obj1 = { a: 1 };
|
||||
let obj2 = { a: 2, b: 0 };
|
||||
let obj3 = { a: 2, b: 1 };
|
||||
let obj4 = { a: 3 };
|
||||
|
||||
function objCmp(one, two) {
|
||||
return one.a - two.a;
|
||||
}
|
||||
|
||||
let arrayObj = [obj1, obj3, obj4];
|
||||
|
||||
// obj2 compares equivalent to obj3, should be
|
||||
// inserted before
|
||||
Util.insertSorted(arrayObj, obj2, objCmp);
|
||||
|
||||
assertArrayEquals('object test', [obj1, obj2, obj3, obj4], arrayObj);
|
||||
|
||||
function checkedCmp(one, two) {
|
||||
if (typeof one != 'number' ||
|
||||
typeof two != 'number')
|
||||
throw new TypeError('Invalid type passed to checkedCmp');
|
||||
|
||||
return one-two;
|
||||
}
|
||||
|
||||
let arrayEmpty = [];
|
||||
|
||||
// check that no comparisons are made when
|
||||
// inserting in a empty array
|
||||
Util.insertSorted(arrayEmpty, 3, checkedCmp);
|
||||
|
||||
// Insert at the end and check that we don't
|
||||
// access past it
|
||||
Util.insertSorted(arrayEmpty, 4, checkedCmp);
|
||||
Util.insertSorted(arrayEmpty, 5, checkedCmp);
|
||||
|
||||
// Some more insertions
|
||||
Util.insertSorted(arrayEmpty, 2, checkedCmp);
|
||||
Util.insertSorted(arrayEmpty, 1, checkedCmp);
|
||||
|
||||
assertArrayEquals('checkedCmp test', [1, 2, 3, 4, 5], arrayEmpty);
|
Reference in New Issue
Block a user