Compare commits
116 Commits
Author | SHA1 | Date | |
---|---|---|---|
90a691ed25 | |||
eadb41b3bb | |||
6829590c8f | |||
29da720e6a | |||
c5932c0f07 | |||
6195386a06 | |||
0080440118 | |||
b5be62cd1b | |||
1bac40fbe3 | |||
11637bae43 | |||
301bacec9f | |||
8943b3b0e9 | |||
f59018f2d7 | |||
235cb9c505 | |||
3f328463a8 | |||
e58c82fc04 | |||
91ca86ffe4 | |||
33d4518e50 | |||
b087191d2b | |||
64baea1693 | |||
b88b743428 | |||
c606cf076d | |||
d205d7e7c2 | |||
efdd3375d0 | |||
abcca3d3bc | |||
f4d13b9801 | |||
7da39031e1 | |||
6cdb1bd60c | |||
7c108e267c | |||
5cf06fe9a7 | |||
41f6956197 | |||
417cbea79c | |||
225c807550 | |||
ff78d2655b | |||
bde15f7c61 | |||
30300f1aeb | |||
d42c3a15d6 | |||
14a65559af | |||
ba1e7bd095 | |||
951705a4b2 | |||
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 |
82
NEWS
82
NEWS
@ -1,3 +1,85 @@
|
||||
3.3.4
|
||||
=====
|
||||
* https://live.gnome.org/EveryDetailMatters
|
||||
- Add "browse" for labels for dash items - once a tooltip is
|
||||
showing, switch to other items without a delay [Seif; #666170]
|
||||
- Always scale down windows in the overview at least to 70% [Vit; #646704]
|
||||
- Fix the new-workspace drop indicator sometimes getting stuck
|
||||
[Stefano; #664201]
|
||||
- Delay rearranging windows in the overview as long as the pointer
|
||||
is over a window [Vit; #645325]
|
||||
* Add a GConf => DConf migration file for overriden Mutter settings
|
||||
[Florian; #667636]
|
||||
* When a VPN connection is active, show that as the network icon
|
||||
[Giovanni; #665115]
|
||||
* Handle the "ExtendedAway" IM status as away, not offline [Guillaume; #667813]
|
||||
* Improve the appearance of the labels in "Applications" [Alex; #642392]
|
||||
* Adjust for GTK+ and Mutter API changes for application menu [Ryan; #668118]
|
||||
* Add section label support to the application menu [Giovanni; #666681]
|
||||
* Fix screenshot methods to work again [Cosimo; #667662]
|
||||
* Fix several crashers related to updating workspace thumbnails [Owen; #667652]
|
||||
* Fix memory management error causing gnome-shell-hotplug-sniffer to crash
|
||||
[Owen; #667378]
|
||||
* Build fixes [Emmanuele, Rico; #667864]
|
||||
* Code cleanups [Adel; #668087]
|
||||
* Misc bug fixes [Colin, Florian, Giovanni, Owen, Xavier; #633028, #658817,
|
||||
#664138, #667881, #668048, #668050]
|
||||
|
||||
Contributors:
|
||||
Emmanuele Bassi, Giovanni Campagna, Cosimo Cecchi, Xavier Claessens,
|
||||
Guillaume Desmottes, Stefano Facchini, Adel Gadllah, Alex Hultman,
|
||||
Ryan Lortie, Seif Lotfy, Florian Müllner, Vit Stanislav, Owen Taylor,
|
||||
Rico Tzschichholz, Colin Walters
|
||||
|
||||
Translations:
|
||||
Ihar Hrachyshka [be], Alexander Shopov [bg], Arash Mousavi [fa],
|
||||
Jiri Grönroos, Timo Jyrinki [fi], Fran Diéguez [gl], Kjartan Maraas [nb],
|
||||
Yuri Myasoedov [ru], Matej Urbančič [sl], Nguyễn Thái Ngọc Duy [vi]
|
||||
|
||||
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,
|
||||
#664138, #665261, #666020, #666243]
|
||||
* Build fixes [Owen]
|
||||
|
||||
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, Owen Taylor, Colin Walters, Ron Yorsten,
|
||||
Marina Zhurakhinskaya
|
||||
|
||||
Translations:
|
||||
Petr Kovar [cz], Kris Thomsen [dk], Daniel Mustieles [es],
|
||||
Ville-Pekka Vainio [fi], Yaron Shahrabani [he], Luca Ferretti [it],
|
||||
Hideki Yamane [ja], Ž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;
|
||||
}
|
||||
|
||||
|
11
configure.ac
11
configure.ac
@ -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.4],[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,14 +63,14 @@ 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
|
||||
GTK_MIN_VERSION=3.3.9
|
||||
GIO_MIN_VERSION=2.31.6
|
||||
LIBECAL_MIN_VERSION=2.32.0
|
||||
LIBEDATASERVER_MIN_VERSION=1.2.0
|
||||
LIBEDATASERVERUI_MIN_VERSION=2.91.6
|
||||
TELEPATHY_GLIB_MIN_VERSION=0.15.5
|
||||
TELEPATHY_GLIB_MIN_VERSION=0.15.6
|
||||
TELEPATHY_LOGGER_MIN_VERSION=0.2.4
|
||||
POLKIT_MIN_VERSION=0.100
|
||||
STARTUP_NOTIFICATION_MIN_VERSION=0.11
|
||||
@ -84,6 +84,7 @@ PKG_CHECK_MODULES(GNOME_SHELL, gio-unix-2.0 >= $GIO_MIN_VERSION
|
||||
gjs-internals-1.0 >= $GJS_MIN_VERSION
|
||||
libgnome-menu-3.0 $recorder_modules
|
||||
gdk-x11-3.0 libsoup-2.4
|
||||
gl
|
||||
clutter-x11-1.0 >= $CLUTTER_MIN_VERSION
|
||||
clutter-glx-1.0 >= $CLUTTER_MIN_VERSION
|
||||
libstartup-notification-1.0 >= $STARTUP_NOTIFICATION_MIN_VERSION
|
||||
|
@ -59,6 +59,8 @@ gschemas.compiled: $(gsettings_SCHEMAS:.xml=.valid)
|
||||
|
||||
all-local: gschemas.compiled
|
||||
|
||||
convertdir = $(datadir)/GConf/gsettings
|
||||
convert_DATA = gnome-shell-overrides.convert
|
||||
|
||||
shadersdir = $(pkgdatadir)/shaders
|
||||
shaders_DATA = \
|
||||
@ -69,6 +71,7 @@ EXTRA_DIST = \
|
||||
gnome-shell.desktop.in.in \
|
||||
$(menu_DATA) \
|
||||
$(shaders_DATA) \
|
||||
$(convert_DATA) \
|
||||
org.gnome.shell.gschema.xml.in
|
||||
|
||||
CLEANFILES = \
|
||||
|
5
data/gnome-shell-overrides.convert
Normal file
5
data/gnome-shell-overrides.convert
Normal file
@ -0,0 +1,5 @@
|
||||
[org.gnome.shell.overrides]
|
||||
attach-modal-dialogs = /desktop/gnome/shell/windows/attach_modal_dialogs
|
||||
button-layout = /desktop/gnome/shell/windows/button_layout
|
||||
edge-tiling = /desktop/gnome/shell/windows/edge_tiling
|
||||
workspaces-only-on-primary = /desktop/gnome/shell/windows/workspaces_only_on_primary
|
@ -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;
|
||||
}
|
||||
|
||||
@ -717,7 +740,8 @@ StTooltip StLabel {
|
||||
border-radius: 4px;
|
||||
padding: 3px;
|
||||
border: 1px rgba(0,0,0,0);
|
||||
font-size: 7.5pt;
|
||||
font-size: 8pt;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
transition-duration: 100;
|
||||
text-align: center;
|
||||
@ -771,6 +795,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 +1457,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 +1468,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 +1582,7 @@ StTooltip StLabel {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
background-image: url("corner-ripple-ltr.png");
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.ripple-box:rtl {
|
||||
@ -1596,6 +1624,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 +1633,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 \
|
||||
|
@ -205,7 +205,8 @@ const UserListItem = new Lang.Class({
|
||||
// We use background-image instead of, say, St.TextureCache
|
||||
// so the theme writers can add a rounded frame around the image
|
||||
// and so theme writers can pick the icon size.
|
||||
this._iconBin.set_style('background-image: url("' + iconFile + '");');
|
||||
this._iconBin.set_style('background-image: url("' + iconFile + '");' +
|
||||
'background-size: contain;');
|
||||
} else {
|
||||
this._iconBin.hide();
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
101
js/ui/dash.js
101
js/ui/dash.js
@ -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,9 @@ const Dash = new Lang.Class({
|
||||
this._dragPlaceholderPos = -1;
|
||||
this._animatingPlaceholdersCount = 0;
|
||||
this._favRemoveTarget = null;
|
||||
this._showLabelTimeoutId = 0;
|
||||
this._resetHoverTimeoutId = 0;
|
||||
this._labelShowing = false;
|
||||
|
||||
this._box = new St.BoxLayout({ name: 'dash',
|
||||
vertical: true,
|
||||
@ -371,16 +434,50 @@ 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._showLabelTimeoutId == 0) {
|
||||
let timeout = this._labelShowing ? 0 : DASH_ITEM_HOVER_TIMEOUT;
|
||||
this._showLabelTimeoutId = Mainloop.timeout_add(timeout,
|
||||
Lang.bind(this, function() {
|
||||
this._labelShowing = true;
|
||||
item.showLabel();
|
||||
return false;
|
||||
}));
|
||||
if (this._resetHoverTimeoutId > 0) {
|
||||
Mainloop.source_remove(this._resetHoverTimeoutId);
|
||||
this._resetHoverTimeoutId = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this._showLabelTimeoutId > 0)
|
||||
Mainloop.source_remove(this._showLabelTimeoutId);
|
||||
this._showLabelTimeoutId = 0;
|
||||
item.hideLabel();
|
||||
if (this._labelShowing) {
|
||||
this._resetHoverTimeoutId = Mainloop.timeout_add(DASH_ITEM_HOVER_TIMEOUT,
|
||||
Lang.bind(this, function() {
|
||||
this._labelShowing = false;
|
||||
return false;
|
||||
}));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_adjustIconSize: function() {
|
||||
// For the icon size, we only consider children which are "proper"
|
||||
// icons (i.e. ignoring drag placeholders) and which are not
|
||||
|
@ -333,7 +333,8 @@ const EndSessionDialog = new Lang.Class({
|
||||
this._iconBin.child = null;
|
||||
if (iconFile) {
|
||||
this._iconBin.show();
|
||||
this._iconBin.set_style('background-image: url("' + iconFile + '");');
|
||||
this._iconBin.set_style('background-image: url("' + iconFile + '");' +
|
||||
'background-size: contain;');
|
||||
} else {
|
||||
this._iconBin.hide();
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -332,6 +332,7 @@ function _windowRemoved(workspace, window) {
|
||||
workspace._lastRemovedWindow = null;
|
||||
_queueCheckWorkspaces();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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(null, icon.pid, 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,56 @@ 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));
|
||||
} else {
|
||||
this._appMenuNotifyId = 0;
|
||||
this._actionGroupNotifyId = 0;
|
||||
}
|
||||
|
||||
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 && this._targetApp.menu) {
|
||||
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 +957,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();
|
||||
|
@ -155,9 +155,12 @@ const PlacesManager = new Lang.Class({
|
||||
|
||||
this._connect = new PlaceInfo('special:connect', _("Connect to..."),
|
||||
function (size) {
|
||||
return new St.Icon({ icon_name: 'applications-internet',
|
||||
icon_type: St.IconType.FULLCOLOR,
|
||||
icon_size: size });
|
||||
// do NOT use St.Icon here, it crashes the shell
|
||||
// see wanda.js for details
|
||||
return St.TextureCache.get_default().load_icon_name('applications-internet',
|
||||
null,
|
||||
St.IconType.FULLCOLOR,
|
||||
size);
|
||||
},
|
||||
function (params) {
|
||||
// BUG: nautilus-connect-server doesn't have a desktop file, so we can't
|
||||
|
@ -393,6 +393,7 @@ const AuthenticationAgent = new Lang.Class({
|
||||
Lang.bind(this,
|
||||
function() {
|
||||
this._reallyCompleteRequest(wasDismissed);
|
||||
return false;
|
||||
}));
|
||||
} else {
|
||||
this._reallyCompleteRequest(wasDismissed);
|
||||
|
@ -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);
|
||||
@ -1391,12 +1398,23 @@ const PopupMenuSection = new Lang.Class({
|
||||
this.actor = this.box;
|
||||
this.actor._delegate = this;
|
||||
this.isOpen = true;
|
||||
|
||||
// an array of externally managed separators
|
||||
this.separators = [];
|
||||
},
|
||||
|
||||
// deliberately ignore any attempt to open() or close(), but emit the
|
||||
// corresponding signal so children can still pick it up
|
||||
open: function(animate) { this.emit('open-state-changed', true); },
|
||||
close: function() { this.emit('open-state-changed', false); },
|
||||
|
||||
destroy: function() {
|
||||
for (let i = 0; i < this.separators.length; i++)
|
||||
this.separators[i].destroy();
|
||||
this.separators = [];
|
||||
|
||||
this.parent();
|
||||
}
|
||||
});
|
||||
|
||||
const PopupSubMenuMenuItem = new Lang.Class({
|
||||
@ -1692,6 +1710,268 @@ 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 labelValue = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_LABEL, null);
|
||||
let label = labelValue ? labelValue.deep_unpack() : '';
|
||||
// remove all underscores that are not followed by another underscore
|
||||
label = label.replace(/_([^_])/, '$1');
|
||||
|
||||
let section_link = model.get_item_link(index, Gio.MENU_LINK_SECTION);
|
||||
if (section_link) {
|
||||
let item = new PopupMenuSection();
|
||||
if (label) {
|
||||
let title = new PopupMenuItem(label, { reactive: false,
|
||||
style_class: 'popup-subtitle-menu-item' });
|
||||
item._titleMenuItem = title;
|
||||
title._ignored = true;
|
||||
item.addMenuItem(title);
|
||||
}
|
||||
this._modelChanged(section_link, 0, 0, section_link.get_n_items(), item);
|
||||
return [item, true, ''];
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
k0 = 0;
|
||||
// skip ignored items at the beginning
|
||||
while (k0 < currentItems.length && currentItems[k0]._ignored)
|
||||
k0++;
|
||||
// find the right menu item matching the model item
|
||||
for (j0 = 0; j0 < position; j0++, k0++) {
|
||||
if (currentItems[k0]._ignored)
|
||||
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]._ignored)
|
||||
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) {
|
||||
let separator = new PopupSeparatorMenuItem();
|
||||
item.separators.push(separator);
|
||||
separator._ignored = true;
|
||||
target.addMenuItem(separator, k+1);
|
||||
k++;
|
||||
}
|
||||
|
||||
target.addMenuItem(item, k);
|
||||
|
||||
if (addSeparator) {
|
||||
let separator = new PopupSeparatorMenuItem();
|
||||
item.separators.push(separator);
|
||||
separator._ignored = true;
|
||||
target.addMenuItem(separator, 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) {
|
||||
if (target._titleMenuItem)
|
||||
target.actor.visible = target.numMenuItems != 1;
|
||||
else
|
||||
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
|
||||
*/
|
||||
|
@ -122,8 +122,13 @@ const GnomeShell = new Lang.Class({
|
||||
* indicating whether the operation was successful or not.
|
||||
*
|
||||
*/
|
||||
ScreenshotAreaAsync : function (x, y, width, height, filename, callback) {
|
||||
global.screenshot_area (x, y, width, height, filename, function (obj, result) { callback(result); });
|
||||
ScreenshotAreaAsync : function (params, invocation) {
|
||||
let [x, y, width, height, filename, callback] = params;
|
||||
global.screenshot_area (x, y, width, height, filename,
|
||||
function (obj, result) {
|
||||
let retval = GLib.Variant.new('(b)', [result]);
|
||||
invocation.return_value(retval);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -149,8 +154,13 @@ const GnomeShell = new Lang.Class({
|
||||
* indicating whether the operation was successful or not.
|
||||
*
|
||||
*/
|
||||
ScreenshotAsync : function (filename, callback) {
|
||||
global.screenshot(filename, function (obj, result) { callback(result); });
|
||||
ScreenshotAsync : function (params, invocation) {
|
||||
let [filename] = params;
|
||||
global.screenshot(filename,
|
||||
function (obj, result) {
|
||||
let retval = GLib.Variant.new('(b)', [result]);
|
||||
invocation.return_value(retval);
|
||||
});
|
||||
},
|
||||
|
||||
ListExtensions: function() {
|
||||
|
@ -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);
|
||||
}
|
||||
@ -1803,6 +1803,7 @@ const NMApplet = new Lang.Class({
|
||||
let activating = null;
|
||||
let default_ip4 = null;
|
||||
let default_ip6 = null;
|
||||
let active_vpn = null;
|
||||
for (let i = 0; i < this._activeConnections.length; i++) {
|
||||
let a = this._activeConnections[i];
|
||||
|
||||
@ -1832,6 +1833,8 @@ const NMApplet = new Lang.Class({
|
||||
default_ip4 = a;
|
||||
if (a.default6)
|
||||
default_ip6 = a;
|
||||
if (a._type == 'vpn')
|
||||
active_vpn = a;
|
||||
|
||||
if (a.state == NetworkManager.ActiveConnectionState.ACTIVATING)
|
||||
activating = a;
|
||||
@ -1862,7 +1865,7 @@ const NMApplet = new Lang.Class({
|
||||
}
|
||||
}
|
||||
|
||||
this._mainConnection = activating || default_ip4 || default_ip6 || this._activeConnections[0] || null;
|
||||
this._mainConnection = activating || active_vpn || default_ip4 || default_ip6 || this._activeConnections[0] || null;
|
||||
},
|
||||
|
||||
_notifyActivated: function(activeConnection) {
|
||||
|
@ -33,10 +33,6 @@ const NotificationDirection = {
|
||||
RECEIVED: 'chat-received'
|
||||
};
|
||||
|
||||
let contactFeatures = [Tp.ContactFeature.ALIAS,
|
||||
Tp.ContactFeature.AVATAR_DATA,
|
||||
Tp.ContactFeature.PRESENCE];
|
||||
|
||||
// This is GNOME Shell's implementation of the Telepathy 'Client'
|
||||
// interface. Specifically, the shell is a Telepathy 'Observer', which
|
||||
// lets us see messages even if they belong to another app (eg,
|
||||
@ -83,11 +79,21 @@ const Client = new Lang.Class({
|
||||
// account path -> AccountNotification
|
||||
this._accountNotifications = {};
|
||||
|
||||
// Define features we want
|
||||
this._accountManager = Tp.AccountManager.dup();
|
||||
let factory = this._accountManager.get_factory();
|
||||
factory.add_account_features([Tp.Account.get_feature_quark_connection()]);
|
||||
factory.add_connection_features([Tp.Connection.get_feature_quark_contact_list()]);
|
||||
factory.add_channel_features([Tp.Channel.get_feature_quark_contacts()]);
|
||||
factory.add_contact_features([Tp.ContactFeature.ALIAS,
|
||||
Tp.ContactFeature.AVATAR_DATA,
|
||||
Tp.ContactFeature.PRESENCE,
|
||||
Tp.ContactFeature.SUBSCRIPTION_STATES]);
|
||||
|
||||
// Set up a SimpleObserver, which will call _observeChannels whenever a
|
||||
// channel matching its filters is detected.
|
||||
// The second argument, recover, means _observeChannels will be run
|
||||
// for any existing channel as well.
|
||||
this._accountManager = Tp.AccountManager.dup();
|
||||
this._tpClient = new Shell.TpClient({ 'account-manager': this._accountManager,
|
||||
'name': 'GnomeShell',
|
||||
'uniquify-name': true })
|
||||
@ -114,16 +120,9 @@ const Client = new Lang.Class({
|
||||
throw new Error('Couldn\'t register Telepathy client. Error: \n' + e);
|
||||
}
|
||||
|
||||
|
||||
// Watch subscription requests and connection errors
|
||||
this._subscriptionSource = null;
|
||||
this._accountSource = null;
|
||||
let factory = this._accountManager.get_factory();
|
||||
factory.add_account_features([Tp.Account.get_feature_quark_connection()]);
|
||||
factory.add_connection_features([Tp.Connection.get_feature_quark_contact_list()]);
|
||||
factory.add_contact_features([Tp.ContactFeature.SUBSCRIPTION_STATES,
|
||||
Tp.ContactFeature.ALIAS,
|
||||
Tp.ContactFeature.AVATAR_DATA]);
|
||||
|
||||
this._accountManager.connect('account-validity-changed',
|
||||
Lang.bind(this, this._accountValidityChanged));
|
||||
@ -133,22 +132,6 @@ const Client = new Lang.Class({
|
||||
|
||||
_observeChannels: function(observer, account, conn, channels,
|
||||
dispatchOp, requests, context) {
|
||||
// If the self_contact doesn't have the ALIAS, make sure
|
||||
// to fetch it before trying to grab the channels.
|
||||
let self_contact = conn.get_self_contact();
|
||||
if (self_contact.has_feature(Tp.ContactFeature.ALIAS)) {
|
||||
this._finishObserveChannels(account, conn, channels, context);
|
||||
} else {
|
||||
Shell.get_self_contact_features(conn,
|
||||
contactFeatures,
|
||||
Lang.bind(this, function() {
|
||||
this._finishObserveChannels(account, conn, channels, context);
|
||||
}));
|
||||
context.delay();
|
||||
}
|
||||
},
|
||||
|
||||
_finishObserveChannels: function(account, conn, channels, context) {
|
||||
let len = channels.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
let channel = channels[i];
|
||||
@ -159,16 +142,7 @@ const Client = new Lang.Class({
|
||||
targetHandleType != Tp.HandleType.CONTACT)
|
||||
continue;
|
||||
|
||||
/* Request a TpContact */
|
||||
Shell.get_tp_contacts(conn, [targetHandle],
|
||||
contactFeatures,
|
||||
Lang.bind(this, function (connection, contacts, failed) {
|
||||
if (contacts.length < 1)
|
||||
return;
|
||||
|
||||
/* We got the TpContact */
|
||||
this._createChatSource(account, conn, channel, contacts[0]);
|
||||
}), null);
|
||||
this._createChatSource(account, conn, channel, channel.get_target_contact());
|
||||
}
|
||||
|
||||
context.accept();
|
||||
@ -197,11 +171,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 +186,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)
|
||||
@ -223,41 +208,25 @@ const Client = new Lang.Class({
|
||||
|
||||
_displayRoomInvitation: function(conn, channel, dispatchOp, context) {
|
||||
// We can only approve the rooms if we have been invited to it
|
||||
let selfHandle = channel.group_get_self_handle();
|
||||
if (selfHandle == 0) {
|
||||
let selfContact = channel.group_get_self_contact();
|
||||
if (selfContact == null) {
|
||||
Shell.decline_dispatch_op(context, 'Not invited to the room');
|
||||
return;
|
||||
}
|
||||
|
||||
let [invited, inviter, reason, msg] = channel.group_get_local_pending_info(selfHandle);
|
||||
let [invited, inviter, reason, msg] = channel.group_get_local_pending_contact_info(selfContact);
|
||||
if (!invited) {
|
||||
Shell.decline_dispatch_op(context, 'Not invited to the room');
|
||||
return;
|
||||
}
|
||||
|
||||
// Request a TpContact for the inviter
|
||||
Shell.get_tp_contacts(conn, [inviter],
|
||||
contactFeatures,
|
||||
Lang.bind(this, this._createRoomInviteSource, channel, context, dispatchOp));
|
||||
|
||||
context.delay();
|
||||
},
|
||||
|
||||
_createRoomInviteSource: function(connection, contacts, failed, channel, context, dispatchOp) {
|
||||
if (contacts.length < 1) {
|
||||
Shell.decline_dispatch_op(context, 'Failed to get inviter');
|
||||
return;
|
||||
}
|
||||
|
||||
// We got the TpContact
|
||||
|
||||
// FIXME: We don't have a 'chat room' icon (bgo #653737) use
|
||||
// system-users for now as Empathy does.
|
||||
let source = new ApproverSource(dispatchOp, _("Invitation"),
|
||||
Gio.icon_new_for_string('system-users'));
|
||||
Main.messageTray.add(source);
|
||||
|
||||
let notif = new RoomInviteNotification(source, dispatchOp, channel, contacts[0]);
|
||||
let notif = new RoomInviteNotification(source, dispatchOp, channel, inviter);
|
||||
source.notify(notif);
|
||||
context.accept();
|
||||
},
|
||||
@ -285,7 +254,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);
|
||||
}}));
|
||||
@ -297,21 +266,6 @@ const Client = new Lang.Class({
|
||||
},
|
||||
|
||||
_approveCall: function(account, conn, channel, dispatchOp, context) {
|
||||
let [targetHandle, targetHandleType] = channel.get_handle();
|
||||
|
||||
Shell.get_tp_contacts(conn, [targetHandle],
|
||||
contactFeatures,
|
||||
Lang.bind(this, this._createAudioVideoSource, channel, context, dispatchOp));
|
||||
|
||||
context.delay();
|
||||
},
|
||||
|
||||
_createAudioVideoSource: function(connection, contacts, failed, channel, context, dispatchOp) {
|
||||
if (contacts.length < 1) {
|
||||
Shell.decline_dispatch_op(context, 'Failed to get inviter');
|
||||
return;
|
||||
}
|
||||
|
||||
let isVideo = false;
|
||||
|
||||
let props = channel.borrow_immutable_properties();
|
||||
@ -326,27 +280,13 @@ const Client = new Lang.Class({
|
||||
Gio.icon_new_for_string('audio-input-microphone'));
|
||||
Main.messageTray.add(source);
|
||||
|
||||
let notif = new AudioVideoNotification(source, dispatchOp, channel, contacts[0], isVideo);
|
||||
let notif = new AudioVideoNotification(source, dispatchOp, channel,
|
||||
channel.get_target_contact(), isVideo);
|
||||
source.notify(notif);
|
||||
context.accept();
|
||||
},
|
||||
|
||||
_approveFileTransfer: function(account, conn, channel, dispatchOp, context) {
|
||||
let [targetHandle, targetHandleType] = channel.get_handle();
|
||||
|
||||
Shell.get_tp_contacts(conn, [targetHandle],
|
||||
contactFeatures,
|
||||
Lang.bind(this, this._createFileTransferSource, channel, context, dispatchOp));
|
||||
|
||||
context.delay();
|
||||
},
|
||||
|
||||
_createFileTransferSource: function(connection, contacts, failed, channel, context, dispatchOp) {
|
||||
if (contacts.length < 1) {
|
||||
Shell.decline_dispatch_op(context, 'Failed to get file sender');
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the icon of the file being transferred
|
||||
let gicon = Gio.content_type_get_icon(channel.get_mime_type());
|
||||
|
||||
@ -354,7 +294,8 @@ const Client = new Lang.Class({
|
||||
let source = new ApproverSource(dispatchOp, _("File Transfer"), gicon);
|
||||
Main.messageTray.add(source);
|
||||
|
||||
let notif = new FileTransferNotification(source, dispatchOp, channel, contacts[0]);
|
||||
let notif = new FileTransferNotification(source, dispatchOp, channel,
|
||||
channel.get_target_contact());
|
||||
source.notify(notif);
|
||||
context.accept();
|
||||
},
|
||||
@ -502,15 +443,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();
|
||||
|
||||
@ -734,12 +669,12 @@ const ChatSource = new Lang.Class({
|
||||
if (presence == Tp.ConnectionPresenceType.AVAILABLE) {
|
||||
msg = _("%s is online.").format(title);
|
||||
shouldNotify = (this._presence == Tp.ConnectionPresenceType.OFFLINE);
|
||||
} else if (presence == Tp.ConnectionPresenceType.OFFLINE ||
|
||||
presence == Tp.ConnectionPresenceType.EXTENDED_AWAY) {
|
||||
} else if (presence == Tp.ConnectionPresenceType.OFFLINE) {
|
||||
presence = Tp.ConnectionPresenceType.OFFLINE;
|
||||
msg = _("%s is offline.").format(title);
|
||||
shouldNotify = (this._presence != Tp.ConnectionPresenceType.OFFLINE);
|
||||
} else if (presence == Tp.ConnectionPresenceType.AWAY) {
|
||||
} else if (presence == Tp.ConnectionPresenceType.AWAY ||
|
||||
presence == Tp.ConnectionPresenceType.EXTENDED_AWAY) {
|
||||
msg = _("%s is away.").format(title);
|
||||
shouldNotify = false;
|
||||
} else if (presence == Tp.ConnectionPresenceType.BUSY) {
|
||||
@ -774,24 +709,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;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -239,7 +239,8 @@ const IMStatusChooserItem = new Lang.Class({
|
||||
},
|
||||
|
||||
_setIconFromFile: function(iconFile) {
|
||||
this._iconBin.set_style('background-image: url("' + iconFile + '");');
|
||||
this._iconBin.set_style('background-image: url("' + iconFile + '");' +
|
||||
'background-size: contain;');
|
||||
this._iconBin.child = null;
|
||||
},
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
});
|
@ -23,6 +23,8 @@ const WINDOW_DND_SIZE = 256;
|
||||
|
||||
const SCROLL_SCALE_AMOUNT = 100 / 5;
|
||||
|
||||
const WINDOW_CLONE_MAXIMUM_SCALE = 0.7;
|
||||
|
||||
const LIGHTBOX_FADE_TIME = 0.1;
|
||||
const CLOSE_BUTTON_FADE_TIME = 0.1;
|
||||
|
||||
@ -94,10 +96,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 +386,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 +490,9 @@ const WindowOverlay = new Lang.Class({
|
||||
},
|
||||
|
||||
fadeIn: function() {
|
||||
if (!this._hidden)
|
||||
return;
|
||||
|
||||
this.show();
|
||||
this.title.opacity = 0;
|
||||
this._parentActor.raise_top();
|
||||
@ -522,7 +521,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 +543,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) {
|
||||
@ -948,7 +966,7 @@ const Workspace = new Lang.Class({
|
||||
|
||||
let scale = Math.min((width - buttonOuterWidth) / rect.width,
|
||||
(height - buttonOuterHeight - captionHeight) / rect.height,
|
||||
1.0);
|
||||
WINDOW_CLONE_MAXIMUM_SCALE);
|
||||
|
||||
x = Math.floor(x + (width - scale * rect.width) / 2);
|
||||
|
||||
@ -1017,7 +1035,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 +1058,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 +1084,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 +1130,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;
|
||||
@ -1126,22 +1157,16 @@ const Workspace = new Lang.Class({
|
||||
return true;
|
||||
}
|
||||
|
||||
let actorUnderPointer = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);
|
||||
for (let i = 0; i < this._windows.length; i++) {
|
||||
if (this._windows[i].actor == actorUnderPointer)
|
||||
return true;
|
||||
}
|
||||
|
||||
this.positionWindows(WindowPositionFlags.ANIMATE);
|
||||
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 +1320,7 @@ const Workspace = new Lang.Class({
|
||||
|
||||
this.leavingOverview = true;
|
||||
|
||||
this.hideWindowsOverlays();
|
||||
this._hideAllOverlays();
|
||||
|
||||
if (this._repositionWindowsId > 0) {
|
||||
Mainloop.source_remove(this._repositionWindowsId);
|
||||
@ -1386,7 +1411,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;
|
||||
|
||||
@ -149,6 +154,8 @@ const WorkspaceThumbnail = new Lang.Class({
|
||||
this.metaWorkspace = metaWorkspace;
|
||||
this.monitorIndex = Main.layoutManager.primaryIndex;
|
||||
|
||||
this._removed = false;
|
||||
|
||||
this.actor = new St.Group({ reactive: true,
|
||||
clip_to_allocation: true,
|
||||
style_class: 'workspace-thumbnail' });
|
||||
@ -174,17 +181,21 @@ const WorkspaceThumbnail = new Lang.Class({
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
this.setPorthole(monitor.x, monitor.y, monitor.width, monitor.height);
|
||||
|
||||
let windows = global.get_window_actors().filter(this._isMyWindow, this);
|
||||
let windows = global.get_window_actors().filter(this._isWorkspaceWindow, this);
|
||||
|
||||
// Create clones for windows that should be visible in the Overview
|
||||
this._windows = [];
|
||||
this._allWindows = [];
|
||||
this._minimizedChangedIds = [];
|
||||
for (let i = 0; i < windows.length; i++) {
|
||||
windows[i].meta_window._minimizedChangedId =
|
||||
let minimizedChangedId =
|
||||
windows[i].meta_window.connect('notify::minimized',
|
||||
Lang.bind(this,
|
||||
this._updateMinimized));
|
||||
this._allWindows.push(windows[i].meta_window);
|
||||
this._minimizedChangedIds.push(minimizedChangedId);
|
||||
|
||||
if (this._isOverviewWindow(windows[i])) {
|
||||
if (this._isMyWindow(windows[i]) && this._isOverviewWindow(windows[i])) {
|
||||
this._addWindowClone(windows[i]);
|
||||
}
|
||||
}
|
||||
@ -269,17 +280,11 @@ const WorkspaceThumbnail = new Lang.Class({
|
||||
let clone = this._windows[index];
|
||||
this._windows.splice(index, 1);
|
||||
|
||||
if (win && this._isOverviewWindow(win)) {
|
||||
if (metaWin._minimizedChangedId) {
|
||||
metaWin.disconnect(metaWin._minimizedChangedId);
|
||||
delete metaWin._minimizedChangedId;
|
||||
}
|
||||
}
|
||||
clone.destroy();
|
||||
},
|
||||
|
||||
_doAddWindow : function(metaWin) {
|
||||
if (this.leavingOverview)
|
||||
if (this._removed)
|
||||
return;
|
||||
|
||||
let win = metaWin.get_compositor_private();
|
||||
@ -289,7 +294,7 @@ const WorkspaceThumbnail = new Lang.Class({
|
||||
// the compositor finds out about them...
|
||||
Mainloop.idle_add(Lang.bind(this,
|
||||
function () {
|
||||
if (this.actor &&
|
||||
if (!this._removed &&
|
||||
metaWin.get_compositor_private() &&
|
||||
metaWin.get_workspace() == this.metaWorkspace)
|
||||
this._doAddWindow(metaWin);
|
||||
@ -298,16 +303,19 @@ const WorkspaceThumbnail = new Lang.Class({
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._allWindows.indexOf(metaWin) == -1) {
|
||||
let minimizedChangedId = metaWin.connect('notify::minimized',
|
||||
Lang.bind(this,
|
||||
this._updateMinimized));
|
||||
this._allWindows.push(metaWin);
|
||||
this._minimizedChangedIds.push(minimizedChangedId);
|
||||
}
|
||||
|
||||
// We might have the window in our list already if it was on all workspaces and
|
||||
// now was moved to this workspace
|
||||
if (this._lookupIndex (metaWin) != -1)
|
||||
return;
|
||||
|
||||
if (!metaWin._minimizedChangedId)
|
||||
metaWin._minimizedChangedId = metaWin.connect('notify::minimized',
|
||||
Lang.bind(this,
|
||||
this._updateMinimized));
|
||||
|
||||
if (!this._isMyWindow(win) || !this._isOverviewWindow(win))
|
||||
return;
|
||||
|
||||
@ -319,6 +327,13 @@ const WorkspaceThumbnail = new Lang.Class({
|
||||
},
|
||||
|
||||
_windowRemoved : function(metaWorkspace, metaWin) {
|
||||
let index = this._allWindows.indexOf(metaWin);
|
||||
if (index != -1) {
|
||||
metaWin.disconnect(this._minimizedChangedIds[index]);
|
||||
this._allWindows.splice(index, 1);
|
||||
this._minimizedChangedIds.splice(index, 1);
|
||||
}
|
||||
|
||||
this._doRemoveWindow(metaWin);
|
||||
},
|
||||
|
||||
@ -345,27 +360,36 @@ const WorkspaceThumbnail = new Lang.Class({
|
||||
this.actor.destroy();
|
||||
},
|
||||
|
||||
_onDestroy: function(actor) {
|
||||
workspaceRemoved : function() {
|
||||
if (this._removed)
|
||||
return;
|
||||
|
||||
this._removed = true;
|
||||
|
||||
this.metaWorkspace.disconnect(this._windowAddedId);
|
||||
this.metaWorkspace.disconnect(this._windowRemovedId);
|
||||
global.screen.disconnect(this._windowEnteredMonitorId);
|
||||
global.screen.disconnect(this._windowLeftMonitorId);
|
||||
|
||||
for (let i = 0; i < this._windows.length; i++) {
|
||||
let metaWin = this._windows[i].metaWindow;
|
||||
if (metaWin._minimizedChangedId) {
|
||||
metaWin.disconnect(metaWin._minimizedChangedId);
|
||||
delete metaWin._minimizedChangedId;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < this._allWindows.length; i++)
|
||||
this._allWindows[i].disconnect(this._minimizedChangedIds[i]);
|
||||
},
|
||||
|
||||
_onDestroy: function(actor) {
|
||||
this.workspaceRemoved();
|
||||
|
||||
this._windows = [];
|
||||
this.actor = null;
|
||||
},
|
||||
|
||||
// Tests if @win belongs to this workspace
|
||||
_isWorkspaceWindow : function (win) {
|
||||
return Main.isWindowActorDisplayedOnWorkspace(win, this.metaWorkspace.index());
|
||||
},
|
||||
|
||||
// Tests if @win belongs to this workspace and monitor
|
||||
_isMyWindow : function (win) {
|
||||
return Main.isWindowActorDisplayedOnWorkspace(win, this.metaWorkspace.index()) &&
|
||||
return this._isWorkspaceWindow(win) &&
|
||||
(!win.get_meta_window() || win.get_meta_window().get_monitor() == this.monitorIndex);
|
||||
},
|
||||
|
||||
@ -386,6 +410,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();
|
||||
@ -519,6 +547,59 @@ const ThumbnailsBox = new Lang.Class({
|
||||
this._stateCounts[ThumbnailState[key]] = 0;
|
||||
|
||||
this._thumbnails = [];
|
||||
|
||||
Main.overview.connect('item-drag-begin',
|
||||
Lang.bind(this, this._onDragBegin));
|
||||
Main.overview.connect('item-drag-end',
|
||||
Lang.bind(this, this._onDragEnd));
|
||||
Main.overview.connect('item-drag-cancelled',
|
||||
Lang.bind(this, this._onDragCancelled));
|
||||
Main.overview.connect('window-drag-begin',
|
||||
Lang.bind(this, this._onDragBegin));
|
||||
Main.overview.connect('window-drag-end',
|
||||
Lang.bind(this, this._onDragEnd));
|
||||
Main.overview.connect('window-drag-cancelled',
|
||||
Lang.bind(this, this._onDragCancelled));
|
||||
},
|
||||
|
||||
_onDragBegin: function() {
|
||||
this._dragCancelled = false;
|
||||
this._dragMonitor = {
|
||||
dragMotion: Lang.bind(this, this._onDragMotion)
|
||||
};
|
||||
DND.addDragMonitor(this._dragMonitor);
|
||||
},
|
||||
|
||||
_onDragEnd: function() {
|
||||
if (this._dragCancelled)
|
||||
return;
|
||||
|
||||
this._endDrag();
|
||||
},
|
||||
|
||||
_onDragCancelled: function() {
|
||||
this._dragCancelled = true;
|
||||
this._endDrag();
|
||||
},
|
||||
|
||||
_endDrag: function() {
|
||||
this._clearDragPlaceholder();
|
||||
DND.removeDragMonitor(this._dragMonitor);
|
||||
},
|
||||
|
||||
_onDragMotion: function(dragEvent) {
|
||||
if (!this.actor.contains(dragEvent.targetActor))
|
||||
this._onLeave();
|
||||
return DND.DragMotionResult.CONTINUE;
|
||||
},
|
||||
|
||||
_onLeave: function() {
|
||||
this._clearDragPlaceholder();
|
||||
},
|
||||
|
||||
_clearDragPlaceholder: function() {
|
||||
this._dropPlaceholderPos = -1;
|
||||
this.actor.queue_relayout();
|
||||
},
|
||||
|
||||
// Draggable target interface
|
||||
@ -670,8 +751,10 @@ const ThumbnailsBox = new Lang.Class({
|
||||
if (thumbnail.state > ThumbnailState.NORMAL)
|
||||
continue;
|
||||
|
||||
if (currentPos >= start && currentPos < start + count)
|
||||
if (currentPos >= start && currentPos < start + count) {
|
||||
thumbnail.workspaceRemoved();
|
||||
this._setThumbnailState(thumbnail, ThumbnailState.REMOVING);
|
||||
}
|
||||
|
||||
currentPos++;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -114,10 +114,7 @@ sniff_async_ready_cb (GObject *source,
|
||||
{
|
||||
InvocationData *data = user_data;
|
||||
gchar **types;
|
||||
gint idx;
|
||||
GError *error = NULL;
|
||||
GVariantBuilder *builder;
|
||||
GVariant *result;
|
||||
|
||||
types = shell_mime_sniffer_sniff_finish (SHELL_MIME_SNIFFER (source),
|
||||
res, &error);
|
||||
@ -129,16 +126,8 @@ sniff_async_ready_cb (GObject *source,
|
||||
goto out;
|
||||
}
|
||||
|
||||
builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
|
||||
|
||||
for (idx = 0; types[idx] != NULL; idx++)
|
||||
g_variant_builder_add (builder, "s", types[idx]);
|
||||
|
||||
result = g_variant_new ("(as)", builder);
|
||||
g_dbus_method_invocation_return_value (data->invocation, result);
|
||||
|
||||
g_variant_unref (result);
|
||||
g_variant_builder_unref (builder);
|
||||
g_dbus_method_invocation_return_value (data->invocation,
|
||||
g_variant_new ("(^as)", types));
|
||||
g_strfreev (types);
|
||||
|
||||
out:
|
||||
|
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
|
||||
|
150
src/shell-app.c
150
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,10 @@ typedef struct {
|
||||
|
||||
/* Whether or not we need to resort the windows; this is done on demand */
|
||||
gboolean window_sort_stale : 1;
|
||||
|
||||
/* See GApplication documentation */
|
||||
GDBusMenuModel *remote_menu;
|
||||
GActionMuxer *muxer;
|
||||
} ShellAppRunningState;
|
||||
|
||||
/**
|
||||
@ -72,11 +77,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 {
|
||||
@ -87,8 +94,11 @@ enum {
|
||||
static guint shell_app_signals[LAST_SIGNAL] = { 0 };
|
||||
|
||||
static void create_running_state (ShellApp *app);
|
||||
static void setup_running_state (ShellApp *app, MetaWindow *window);
|
||||
static void unref_running_state (ShellAppRunningState *state);
|
||||
|
||||
G_DEFINE_TYPE (ShellApp, shell_app, G_TYPE_OBJECT)
|
||||
|
||||
static void
|
||||
shell_app_get_property (GObject *gobject,
|
||||
guint prop_id,
|
||||
@ -102,6 +112,17 @@ 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_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;
|
||||
@ -513,6 +534,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_gtk_window_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_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL),
|
||||
meta_window_get_gtk_unique_bus_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
|
||||
@ -920,6 +969,8 @@ _shell_app_add_window (ShellApp *app,
|
||||
g_signal_connect (window, "unmanaged", G_CALLBACK(shell_app_on_unmanaged), app);
|
||||
g_signal_connect (window, "notify::user-time", G_CALLBACK(shell_app_on_user_time_changed), app);
|
||||
|
||||
setup_running_state (app, window);
|
||||
|
||||
if (app->state != SHELL_APP_STATE_STARTING)
|
||||
shell_app_state_transition (app, SHELL_APP_STATE_RUNNING);
|
||||
|
||||
@ -1160,6 +1211,47 @@ create_running_state (ShellApp *app)
|
||||
app->running_state->refcount = 1;
|
||||
app->running_state->workspace_switch_id =
|
||||
g_signal_connect (screen, "workspace-switched", G_CALLBACK(shell_app_on_ws_switch), app);
|
||||
|
||||
app->running_state->muxer = g_action_muxer_new ();
|
||||
}
|
||||
|
||||
static void
|
||||
setup_running_state (ShellApp *app,
|
||||
MetaWindow *window)
|
||||
{
|
||||
/* We assume that 'gtk-unique-bus-name', gtk-application-object-path'
|
||||
* and 'gtk-app-menu-object-path' are the same for all windows which
|
||||
* have it set.
|
||||
*
|
||||
* It could be possible, however, that the first window we see
|
||||
* belonging to the app didn't have them set. For this reason, we
|
||||
* take the values from the first window that has them set and ignore
|
||||
* all the rest (until the app is stopped and restarted).
|
||||
*/
|
||||
|
||||
if (app->running_state->remote_menu == NULL)
|
||||
{
|
||||
const gchar *application_object_path;
|
||||
const gchar *app_menu_object_path;
|
||||
const gchar *unique_bus_name;
|
||||
GDBusConnection *session;
|
||||
GDBusActionGroup *actions;
|
||||
|
||||
application_object_path = meta_window_get_gtk_application_object_path (window);
|
||||
app_menu_object_path = meta_window_get_gtk_app_menu_object_path (window);
|
||||
unique_bus_name = meta_window_get_gtk_unique_bus_name (window);
|
||||
|
||||
if (application_object_path == NULL || app_menu_object_path == NULL || unique_bus_name == NULL)
|
||||
return;
|
||||
|
||||
session = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
|
||||
g_assert (session != NULL);
|
||||
app->running_state->remote_menu = g_dbus_menu_model_get (session, unique_bus_name, app_menu_object_path);
|
||||
actions = g_dbus_action_group_get (session, unique_bus_name, application_object_path);
|
||||
g_action_muxer_insert (app->running_state->muxer, "app", G_ACTION_GROUP (actions));
|
||||
g_object_unref (actions);
|
||||
g_object_unref (session);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
@ -1167,13 +1259,18 @@ 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);
|
||||
|
||||
g_clear_object (&state->remote_menu);
|
||||
g_clear_object (&state->muxer);
|
||||
|
||||
g_slice_free (ShellAppRunningState, state);
|
||||
}
|
||||
|
||||
@ -1349,6 +1446,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 +1499,46 @@ 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: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,6 +37,7 @@ 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);
|
||||
|
||||
@ -79,6 +81,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__ */
|
||||
|
@ -84,7 +84,8 @@ shell_embedded_window_hide (GtkWidget *widget)
|
||||
{
|
||||
ShellEmbeddedWindow *window = SHELL_EMBEDDED_WINDOW (widget);
|
||||
|
||||
clutter_actor_queue_relayout (CLUTTER_ACTOR (window->priv->actor));
|
||||
if (window->priv->actor)
|
||||
clutter_actor_queue_relayout (CLUTTER_ACTOR (window->priv->actor));
|
||||
|
||||
GTK_WIDGET_CLASS (shell_embedded_window_parent_class)->hide (widget);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -340,104 +340,6 @@ shell_tp_client_grab_contact_list_changed (ShellTpClient *self,
|
||||
|
||||
/* Telepathy utility functions */
|
||||
|
||||
/**
|
||||
* ShellGetTpContactCb:
|
||||
* @connection: The connection
|
||||
* @contacts: (element-type TelepathyGLib.Contact): List of contacts
|
||||
* @failed: Array of failed contacts
|
||||
*/
|
||||
|
||||
static void
|
||||
shell_global_get_tp_contacts_cb (TpConnection *self,
|
||||
guint n_contacts,
|
||||
TpContact * const *contacts,
|
||||
guint n_failed,
|
||||
const TpHandle *failed,
|
||||
const GError *error,
|
||||
gpointer user_data,
|
||||
GObject *weak_object)
|
||||
{
|
||||
int i;
|
||||
GList *contact_list = NULL;
|
||||
for (i = 0; i < n_contacts; i++) {
|
||||
contact_list = g_list_append(contact_list, contacts[i]);
|
||||
}
|
||||
|
||||
TpHandle *failed_list = g_new0 (TpHandle, n_failed + 1);
|
||||
memcpy(failed_list, failed, n_failed);
|
||||
|
||||
((ShellGetTpContactCb)user_data)(self, contact_list, failed_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_get_tp_contacts:
|
||||
* @self: A connection, which must be ready
|
||||
* @n_handles: Number of handles in handles
|
||||
* @handles: (array length=n_handles) (element-type uint): Array of handles
|
||||
* @n_features: Number of features in features
|
||||
* @features: (array length=n_features) (allow-none) (element-type uint):
|
||||
* Array of features
|
||||
* @callback: (scope async): User callback to run when the contacts are ready
|
||||
*
|
||||
* Wrap tp_connection_get_contacts_by_handle so we can transform the array
|
||||
* into a null-terminated one, which gjs can handle.
|
||||
* We send the original callback to tp_connection_get_contacts_by_handle as
|
||||
* user_data, and we have our own function as callback, which does the
|
||||
* transforming.
|
||||
*/
|
||||
void
|
||||
shell_get_tp_contacts (TpConnection *self,
|
||||
guint n_handles,
|
||||
const TpHandle *handles,
|
||||
guint n_features,
|
||||
const TpContactFeature *features,
|
||||
ShellGetTpContactCb callback)
|
||||
{
|
||||
tp_connection_get_contacts_by_handle(self, n_handles, handles,
|
||||
n_features, features,
|
||||
shell_global_get_tp_contacts_cb,
|
||||
callback, NULL, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
shell_global_get_self_contact_features_cb (TpConnection *connection,
|
||||
guint n_contacts,
|
||||
TpContact * const *contacts,
|
||||
const GError *error,
|
||||
gpointer user_data,
|
||||
GObject *weak_object)
|
||||
{
|
||||
if (error != NULL) {
|
||||
g_print ("Failed to upgrade self contact: %s", error->message);
|
||||
return;
|
||||
}
|
||||
((ShellGetSelfContactFeaturesCb)user_data)(connection, *contacts);
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_get_self_contact_features:
|
||||
* @self: A connection, which must be ready
|
||||
* @n_features: Number of features in features
|
||||
* @features: (array length=n_features) (allow-none) (element-type uint):
|
||||
* Array of features
|
||||
* @callback: (scope async): User callback to run when the contact is ready
|
||||
*
|
||||
* Wrap tp_connection_upgrade_contacts due to the lack of support for
|
||||
* proper arrays arguments in GJS.
|
||||
*/
|
||||
void
|
||||
shell_get_self_contact_features (TpConnection *self,
|
||||
guint n_features,
|
||||
const TpContactFeature *features,
|
||||
ShellGetSelfContactFeaturesCb callback)
|
||||
{
|
||||
TpContact *self_contact = tp_connection_get_self_contact (self);
|
||||
|
||||
tp_connection_upgrade_contacts (self, 1, &self_contact,
|
||||
n_features, features,
|
||||
shell_global_get_self_contact_features_cb,
|
||||
callback, NULL, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_get_contact_events:
|
||||
|
@ -102,24 +102,6 @@ void shell_tp_client_grab_contact_list_changed (ShellTpClient *self,
|
||||
TpConnection *conn);
|
||||
|
||||
/* Telepathy utility functions */
|
||||
typedef void (*ShellGetTpContactCb) (TpConnection *connection,
|
||||
GList *contacts,
|
||||
TpHandle *failed);
|
||||
|
||||
void shell_get_tp_contacts (TpConnection *self,
|
||||
guint n_handles,
|
||||
const TpHandle *handles,
|
||||
guint n_features,
|
||||
const TpContactFeature *features,
|
||||
ShellGetTpContactCb callback);
|
||||
|
||||
typedef void (*ShellGetSelfContactFeaturesCb) (TpConnection *connection,
|
||||
TpContact *contact);
|
||||
|
||||
void shell_get_self_contact_features (TpConnection *self,
|
||||
guint n_features,
|
||||
const TpContactFeature *features,
|
||||
ShellGetSelfContactFeaturesCb callback);
|
||||
|
||||
void shell_get_contact_events (TplLogManager *log_manager,
|
||||
TpAccount *account,
|
||||
|
@ -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;
|
||||
}
|
||||
@ -1314,6 +1306,8 @@ st_theme_node_render_resources (StThemeNode *node,
|
||||
StShadow *background_image_shadow_spec;
|
||||
const char *background_image;
|
||||
|
||||
g_return_if_fail (width > 0 && height > 0);
|
||||
|
||||
texture_cache = st_texture_cache_get_default ();
|
||||
|
||||
/* FIXME - need to separate this into things that need to be recomputed on
|
||||
@ -1417,9 +1411,11 @@ st_theme_node_render_resources (StThemeNode *node,
|
||||
else if (node->background_color.alpha > 0 || has_border)
|
||||
{
|
||||
CoglHandle buffer, offscreen;
|
||||
int texture_width = ceil (width);
|
||||
int texture_height = ceil (height);
|
||||
|
||||
buffer = cogl_texture_new_with_size (width,
|
||||
height,
|
||||
buffer = cogl_texture_new_with_size (texture_width,
|
||||
texture_height,
|
||||
COGL_TEXTURE_NO_SLICING,
|
||||
COGL_PIXEL_FORMAT_ANY);
|
||||
offscreen = cogl_offscreen_new_to_texture (buffer);
|
||||
@ -1427,9 +1423,14 @@ st_theme_node_render_resources (StThemeNode *node,
|
||||
if (offscreen != COGL_INVALID_HANDLE)
|
||||
{
|
||||
ClutterActorBox box = { 0, 0, width, height };
|
||||
CoglColor clear_color;
|
||||
|
||||
cogl_push_framebuffer (offscreen);
|
||||
cogl_ortho (0, width, height, 0, 0, 1.0);
|
||||
|
||||
cogl_color_set_from_4ub (&clear_color, 0, 0, 0, 0);
|
||||
cogl_clear (&clear_color, COGL_BUFFER_BIT_COLOR);
|
||||
|
||||
st_theme_node_paint_borders (node, &box, 0xFF);
|
||||
cogl_pop_framebuffer ();
|
||||
cogl_handle_unref (offscreen);
|
||||
@ -1928,6 +1929,9 @@ st_theme_node_paint (StThemeNode *node,
|
||||
allocation.x2 = width;
|
||||
allocation.y2 = height;
|
||||
|
||||
if (width <= 0 || height <= 0)
|
||||
return;
|
||||
|
||||
if (node->alloc_width != width || node->alloc_height != height)
|
||||
st_theme_node_render_resources (node, width, height);
|
||||
|
||||
|
@ -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('Subsection', 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