Compare commits

...

76 Commits

Author SHA1 Message Date
36bf63a5de Bump version to 3.3.3
Update NEWS and require Mutter 3.3.3
2012-01-04 12:39:37 -05:00
66c4881fd2 Add missing files to distribution 2012-01-04 12:39:37 -05:00
c6b169cb33 docs: fix srcdir != builddir builds 2012-01-04 12:39:37 -05:00
0e753ed5a6 docs: Fix build to exclude private sources
Exclude private header files from the scanned set, and set the rpath
correctly to point to the bluetooth private library.
2012-01-04 11:57:51 -05:00
785969feb5 l10n: close bug #667204 2012-01-04 10:20:17 +01:00
56036476a4 Updated Japanese translation 2012-01-04 17:15:50 +09:00
936b1b5638 notificationDaemon: group sources based on a combination of pid and title
That way different system notifications, such as the ones about battery power
and the ones about software updates, are shown with separate message tray
sources.

https://bugzilla.gnome.org/show_bug.cgi?id=664138
2012-01-03 15:15:33 -05:00
bf3b94d654 Updated Danish translation 2012-01-03 12:14:47 +01:00
90b4a0856f [l10n]Updated Turkish translation 2012-01-02 09:34:21 +02:00
686190ce80 Updated Spanish translation 2011-12-31 19:04:09 +01:00
45495c2474 Updated Hebrew translation. 2011-12-30 10:50:54 +02:00
9caa88fecb Updated Norwegian Nynorsk translation 2011-12-26 14:31:34 +01:00
25948f214e St: Implement background-size CSS property
Implement the background-size CSS property, specified by the CSS
Backgrounds and Borders Module Level 3, including the keywords
"contain", "cover", and fixed-size backgrounds.

https://bugzilla.gnome.org/show_bug.cgi?id=633462
2011-12-23 14:23:23 -05:00
bea4faacd4 Updated Spanish translation 2011-12-23 14:26:02 +01:00
a7bd9f811b PopupMenu: disconnect from 'destroy' signals of destroyed items
After an item is destroyed, all its signals were disconnected,
except for 'destroy' itself. This could lead to exceptions, if
destroy was called more than once on the item.

https://bugzilla.gnome.org/show_bug.cgi?id=665680
2011-12-22 18:00:52 +01:00
2b9561fcbb dash: Move labels beside the app icon upon hovering
Instead of using an St.Tooltip to show the app's name under the icon,
manually position a new St.Label ourselves. Make sure to keep the label
hidden when right-clicking so it doesn't get in the way of the popup menu.
Only one tooltip/label will be displayed at a time.

https://bugzilla.gnome.org/show_bug.cgi?id=666166
2011-12-22 09:07:48 +00:00
7b9c9b2f7d shell-app: ensure there's always a muxer when setting actions on it
It's not guaranteed that the application DBus proxy appears before
we receive the first focus event from the toplevel window.
Ensure that the first method to access the action muxer creates it if
hasn't been created yet.
2011-12-21 16:36:56 +01:00
b47b82ed42 build: Split out "private" sources variable that can be easily appended
Move the action muxing copy stuff in there.
2011-12-20 17:36:59 -05:00
c4c2c11dca action muxer: drop direct GDBusActionGroup use
We now have GRemoteActionGroup interface with the needed API,
implemented by GDBusActionGroup.

With the new API we could theoretically turn GActionMuxer itself into a
GRemoteActionGroup and expose the _full API to the shell so that the
timestamps could be passed from there.
2011-12-20 17:36:59 -05:00
ce38293a0f GActionMuxer: pass platform data
Pass the current timestamp as platform data when activating
an action. This is implemented slightly hacky, since we use
clutter_get_current_event_time() to get at the timestamp, but
the alternative is to expose g_action_muxer_activate_action_full
to js, which would be quite a bit more involved.
2011-12-20 17:36:59 -05:00
6c4e9d23f2 Add per-window actions
GTK+ also exports window-specific actions, by putting the object path
for the exported action group in the _DBUS_OBJECT_PATH X property.
We add this action group to the app's muxer with a 'win' prefix,
since that is what the exported menu expects. Whenever the focus
window changes, we update the window-specific actions of its
application, and emit notify::action-group to cause the app
menu to be updated.
2011-12-20 17:36:59 -05:00
4aa1fe9ca2 Another update for GLib API changes
GDBusActionGroup api has changed again, adapt to that.
Also, use a GActionMuxer to add the 'app.' prefix to actions,
instead of manually stripping it out of the action names.
In the future, the muxer will also contain per-window actions
with a 'win.' prefix.
2011-12-20 17:36:59 -05:00
951fff5aa0 Application Menu: watch for menu property changes
By the time the window is first mapped and the app menu button is
synced, we may not have finished reading the menu. In that case,
connect to notify::menu and update accordingly.
2011-12-20 17:36:59 -05:00
5ad8080cb9 Application Menu: update for latest GMenu changes
GMenuProxy has been replaced by GDBusMenuModel, and the object path
has been moved (now needs to be retrieved from the AppMenu GApplication
property).
Update the test to prefix each action with "app." as documented,
and use a GtkApplicationWindow instead of a plain GtkWindow.
2011-12-20 17:36:59 -05:00
e53e3cbb09 test-gapplication: update for latest gapplication changes
g_application_set_action_group is deprecated, we should use
GActionMap. Also, GSimpleActions can now be constructed as normal
GObjects.
2011-12-20 17:36:59 -05:00
8a029f333f popupMenu: Remove app. from app actions 2011-12-20 17:36:59 -05:00
4debedb275 Application Menu: add support for showing GApplication actions
Use the new GApplication support in ShellApp to create the application
menu. Supports plain (no state), boolean and double actions.
Includes a test application (as no other application uses GApplication
for actions)

https://bugzilla.gnome.org/show_bug.cgi?id=621203
2011-12-20 17:36:59 -05:00
8764253861 ShellApp: port to new GDBusActionGroup and GMenuProxy API
GDBusActionGroup and GMenuProxy are new objects in GIO 2.32 that
help with accessing menus and actions of remote applications.
This patch makes it possible for the shell to associate an
application with a dbus name and from that a GMenu, that will
be shown as the application menu.

https://bugzilla.gnome.org/show_bug.cgi?id=621203
2011-12-20 17:36:59 -05:00
bbdce159fa Util: fix binary search exit condition
The loop can exit with an interval of length one or one of
length zero. In the first case it is correct to check which side
of the interval to return, in the second case no comparison should
be made (since there is only one possible value).
In practice, this usually results in one comparison more than needed,
but in some cases (when the position was past the end of the array),
would call the comparator with undefined.

https://bugzilla.gnome.org/show_bug.cgi?id=666614
2011-12-20 22:44:03 +01:00
087d8e602e Updated POTFILES.in 2011-12-20 20:39:12 +01:00
50aa15dec9 Reintroduce Wanda The Fish
When transitioning from gnome-panel to gnome-shell in 3.0 we
lost the ability to summon the wisdom of the mythical fish.
This patch restores this, for the few adepts that are aware of
the magical incantation.
(Not as configurable as the original one, but it's an easter egg
after all...)

https://bugzilla.gnome.org/show_bug.cgi?id=666606
2011-12-20 20:06:14 +01:00
26580f8f2c IconGrid: don't force the size of the icon
Forcing the icon size will distort it unnecessarily, and will
in any case not work if showing an animation (which is a ClutterGroup).
Instead, set the size on the bin, and make it align its child
if needed.

https://bugzilla.gnome.org/show_bug.cgi?id=666606
2011-12-20 19:25:52 +01:00
875b6d131b StTextureCache: fix load_sliced_image with different sizes
grid_width and grid_height were inverted, which caused a crash
in GdkPixbuf code. This was never noticed because the animation
in the panel is a square.

https://bugzilla.gnome.org/show_bug.cgi?id=666606
2011-12-20 19:25:43 +01:00
77afd6782f NetworkMenu: actually add new access point to the network list
Previously the code in _accessPointAdded was iterating over the
the network list to find a good place, and at that time, added both
the network to the list and the item to the menu. When I refactored
to call queueCreateSection, I forgot to add code to insert the
network in the list.
Add it now, using the new Util.insertSorted function.

https://bugzilla.gnome.org/show_bug.cgi?id=666429
2011-12-19 16:54:46 +01:00
09ab13cf04 Array: add capability to insert while keeping a specific order
Adds two new functions, Util.lowerBound and Util.insertSorted,
that take an array, a value and a comparator, and find the
first position at which the value can be inserted without
violating the order, in optimal time.

https://bugzilla.gnome.org/show_bug.cgi?id=666429
2011-12-19 16:54:46 +01:00
56f312dc03 st: Fix typo
Commit c7846e1 introduced a small typo, fix.
2011-12-19 16:08:37 +01:00
f2cbddc196 st-theme-node: Fix memory leak
We ref the icon_colors in st_theme_node_get_icon_colors, but never
unref it.
2011-12-19 09:47:26 -05:00
c7846e172f st-icon: Fix memory leak
We were never freeing the dup'd icon_name string for an StIcon.
2011-12-19 09:47:26 -05:00
54afb7b25e browser-plugin: Fix installation on WebKit-like browsers
I missed this use of raw UTF8Characters last time. Hopefully this
should be the last fix required for WebKit support.

https://bugzilla.gnome.org/show_bug.cgi?id=666444
2011-12-18 18:29:55 -05:00
f3cb9d0443 Network Menu: update the UI only when really needed
By using Main.queueDeferredWork, we can ensure that most of the
menu contents (in particular, the heaviest parts like the list of
wifi networks) are not updated immediately as we receive signals
from NetworkManager. Instead, the menu is rebuilt some time later,
or as soon as the user opens the menu.
This means that it is no longer needed to optimize for the
access-point-added case, replacing a lot of buggy code with a safer
call to _queueCreateSection, which in turn should ensure that the
more menu, if existing, is always at the end and that at most 5 networks
are visible outside it.

https://bugzilla.gnome.org/show_bug.cgi?id=664124
2011-12-16 08:58:11 +01:00
fcee7f2f3a run-js-test: Do not use the default stage
This was left out in commit faff0738eb.
2011-12-16 00:18:17 +01:00
faff0738eb Do not use the default stage
https://bugzilla.gnome.org/show_bug.cgi?id=664052
2011-12-15 16:13:29 -05:00
69e26c6dee telepathyClient: only notify on new channels when asked to do so
We need to notify when the channel dispatcher calls HandleChannels on
us with a channel we already handle. However, we don't want to notify
if we claim a new incoming channel which doesn't actually have
anything interesting in it yet.

For example, a new channel pops up just to give a delivery
notification. We want (or, need) to handle it but don't want to notify
for it.

Fixes: https://bugzilla.gnome.org/show_bug.cgi?id=666243

Signed-off-by: Jonny Lamb <jonnylamb@gnome.org>
2011-12-15 16:04:45 +00:00
1acec65c5e theme - update window captions to match mockups
Change window caption text weight, padding and radius.

https://bugzilla.gnome.org/show_bug.cgi?id=664487
2011-12-14 11:46:24 +00:00
8a6a3968c3 workspace: Remove tweens when hiding overlays
The window overlays may be shown erroneously if hideOverlays is
called while the corresponding clone has an uncompleted tween which
calls showOverlays in its onComplete handler, for instance when
quickly leaving the overview before the initial overview animation
has finished. To fix, remove all existing tweens when hiding the
overlays.

https://bugzilla.gnome.org/show_bug.cgi?id=666020
2011-12-12 20:02:51 +01:00
58e4870cb8 Uploaded Ukranian 2011-12-11 16:17:41 +02:00
102099cadd Updated Hebrew translation. 2011-12-10 10:40:25 +02:00
efe6d06ddd telepathyClient: reset badge as soon as the summary notification is shown
That way the user will not see an outdated pending messages count while
they are viewing them or chatting with the sender.

https://bugzilla.gnome.org/show_bug.cgi?id=659272
2011-12-09 11:48:38 -05:00
f0d0e025dd Added Macedonian translation. 2011-12-08 22:49:25 +01:00
fc2d0215f5 Added Macedonian translation. 2011-12-08 22:40:52 +01:00
02af8eb824 browser-plugin: Fix memory leak when passing an invalid UUID
https://bugzilla.gnome.org/show_bug.cgi?id=665261
2011-12-08 11:50:35 -05:00
102f2a91f9 Make the window labels visible at all times
When in overview, window labels flicker or are temporarily hidden on a
number of occasions - when simply clicking around the area the windows
are displayed in, dragging a window, sliding in the workspace list,
adding new workspaces etc. This patch makes the label for any window
visible at any given moment when in overview and the said window is
not being dragged around.

https://bugzilla.gnome.org/show_bug.cgi?id=644861
2011-12-08 14:15:13 +00:00
2c649d5da5 Update Czech translation 2011-12-07 17:11:32 +01:00
7fa7d04ed0 NetworkMenu: show hardware disabled when rfkill is active
When wifi or wwan are blocked by hardware killswitch, we should not
allow changing the switch (it won't work anyway), and show
"hardware disabled" instead, similar to what we already do in the
bluetooth menu.

https://bugzilla.gnome.org/show_bug.cgi?id=665194
2011-12-07 15:36:18 +01:00
aad9179373 NetworkMenu: fix number of visible networks
When placing networks in _createSection, we were taking in
consideration that _activeNetwork is always first, by adding 1,
but then kept this offset also for networks following it (normally,
all of them, since _activeNetwork is also the most recently used),
that instead should not be affected by the movement.
This resulted in the menu showing 4 networks + More... instead of
5.

https://bugzilla.gnome.org/show_bug.cgi?id=664124
2011-12-07 15:35:19 +01:00
d5b4e30eb7 browser-plugin: Fix crash if shell is not running 2011-12-06 17:30:35 +01:00
34ba6f2f71 Updated Lithuanian translation 2011-12-06 17:07:46 +02:00
d845f40b98 Updated Lithuanian translation 2011-12-06 17:00:49 +02:00
80c16aa8f7 layout: Make ripple boxes initially invisible
The three boxes for the ripple animation are visible when created. This
means that the drag and drop code that searches for an actor to handle
the drag can find the ripple boxes instead of the Activities button or
hot corner. The latter can handle drag and drop while the ripple boxes
can't.
This is only a problem if drag and drop is attempted before the ripple
animation has been played: the boxes are made invisible at the end of
the animation. The fix is to just create the boxes invisible.
2011-12-05 10:39:14 +01:00
0cbaeaefed messageTray: use a "hot corner" to summon the tray
Instead of leaving the tray covering the whole last pixel row when it's
hidden, hide it completely. This avoids mouse events not being delivered to
application windows on the last pixel row.

To summon the tray we use a single reactive pixel on the corner.

https://bugzilla.gnome.org/show_bug.cgi?id=663366
2011-12-05 04:51:16 +00:00
ab6a7773ce browser-plugin: Make sure to use the UTF8Length parameter
Some plugin hosts may have junk after the UTF8Characters that we need to strip
off. No current browsers that I know of do this, but it still helps to be
correct.
2011-12-04 10:31:04 -05:00
85e243982b autorun-manager: Use app names rather than full names
https://bugzilla.gnome.org/show_bug.cgi?id=665461
2011-12-04 15:38:03 +01:00
6eb168bc68 Updated Finnish translation 2011-12-04 00:16:01 +02:00
47b55e29d4 workspace-thumbnails: Emit 'window-drag-cancelled'
The dash handles 'window-drag-cancelled', to be able to do the
animations for drag snap-back and size changes in parallel. As
the signal is not emitted for previews in the workspace switcher,
it does not work in that case.
2011-12-02 17:08:39 +01:00
2c243b678f Updated Norwegian bokmål translation 2011-12-01 20:18:28 +01:00
a634f25d30 workspaces-view: Handle swipe scolling in workspacesDisplay
If workspaces-only-on-primary is false, swipe scrolling is now
broken with multiple monitors. To fix, let workspacesDisplay
handle swipe scrolling for all views.

https://bugzilla.gnome.org/show_bug.cgi?id=652580
2011-12-01 17:55:13 +01:00
26df6cf35c workspaces-display: Implement workspaces on non-primary monitors
If workspaces-only-on-primary is false, workspaces should be shown
on each monitor; rather than letting the existing workspaces span
the entire screen, manage one workspacesView per monitor (similar
to the extra workspaces in WorkspacesView when the setting is true).

https://bugzilla.gnome.org/show_bug.cgi?id=652580
2011-12-01 17:55:13 +01:00
84dde8e9bb workspaces-view: Don't create extra workspaces unconditionally
Extra workspaces are special, in that they collect windows from
all workspaces for a particular monitor. This matches the default
behavior, but we need more than a single workspace per monitor if
workspaces-only-on-primary is false, so don't create the extra
workspaces in that case.

https://bugzilla.gnome.org/show_bug.cgi?id=652580
2011-12-01 17:55:13 +01:00
f2c79be11a workspaces-display: Make workspacesView private
WorkspacesDisplay was introduced to manage the workspace objects
and views; however, the overview still accesses the view held
by the workspacesDisplay directly, which is a bit odd.
Add some additional methods needed by the overview, and make the
view a private property.

https://bugzilla.gnome.org/show_bug.cgi?id=652580
2011-12-01 17:55:13 +01:00
10df80b96a status: Remove unneeded pkill call
The bluetooth-applet is only started in the fallback session,
not when running under the shell.

https://bugzilla.gnome.org/show_bug.cgi?id=647587
2011-12-01 10:51:19 +01:00
bbb83656bf extensionSystem: Set the proper 'enabled' and 'type' parameters
When installing an extension at runtime, we accidentally swapped the 'type'
and 'enabled' parameters. While this doesn't directly affect anything right
now, as everything works coincidentally, future patches that look at the
'type' parameter to decide what to do would get the wrong answer.
2011-11-30 22:04:00 -05:00
ef49670ae4 fileUtils: Fix recursivelyDeleteDir 2011-11-30 22:04:00 -05:00
e4df00c77d Updated Telugu Translation 2011-11-29 23:12:12 +05:30
3d70662716 Updated Romanian translation 2011-11-27 05:23:35 +02:00
8d0638a187 Update Simplified Chinese translation. 2011-11-26 09:39:55 +00:00
06143396b7 Updated Slovenian translation 2011-11-25 21:24:36 +01:00
81 changed files with 12497 additions and 6367 deletions

41
NEWS
View File

@ -1,3 +1,44 @@
3.3.3
=====
* https://live.gnome.org/EveryDetailMatters
- Stop flashing the window labels on actions in overview [Zan; #644861]
- Improve the look of window captions in the overview [Marc; #664487]
- Move dash tooltips beside the icon [Seif, Stefano; #666166]
* Support application menus exported from applications via new GLib API
and D-Bus protocol. [Giovanni, Colin, Matthias, Cosimo]
* For removable device prompts, show "Open with Rhythmbox], rather
than "Open with Rhythmbox Music Player' [Florian; #664561]
* Switch to activating the message tray only with a hot corner rather
than a full row of pixels, allowing mouse events to apps [Rui; #663366]
* Fully handle the case where the workspaces-only-on-primary
GSetting is set to false [Florian; #652580]
* Add support for background-size CSS property to St [Quentin; #633462]
* Port to new GJS Lang.Class framework [Giovanni; #664436]
* Finish port to GDBus [Giovanni; #664436]
* Stop using the deprecated Clutter default stage [Florian, Jasper; #664052]
* Fix bugs that kept browser plugin from working in WebKit-based browser
[Jasper; #666444]
* Fix typo that made uninstalling extensions not work [Jasper]
* Fix crash in browser plugin if shell is not run [Jürg]
* Reintroduce piscine paschal ovum [Giovanni; #666606]
* Network menu bug fixes
Giovanni; #664124, #665194, #665680, #666429, #666614]
* Misc bug fixes [Florian, Jasper, Jonny, Marina, Ron; #647587, #659272,
#665261, #666020, #666243]
Contributors:
Jürg Billeter, Giovanni Campagna, Stefano Candori, Cosimo Cecchi,
Matthias Clasen, Zan Dobersek, Quentin Glidic, Jonny Lamb, Ryan Lortie,
Seif Lotfy, Rui Matos, Florian Müllner, Bastien Nocera, Jasper St. Pierre,
Marc Plano-Lesay, Colin Walters, Ron Yorsten, Marina Zhurakhinskaya
Translations:
Petr Kovar [cz], Kris Thomsen [dk], Daniel Mustieles [es],
Ville-Pekka Vainio [fi], Yaron Shahrabani [he], Žygimantas Beručka [lt],
Jovan Naumovski [mk], Kjartan Maraas [nb], "Andreas N" [nn],
Lucian Adrian Grijincu [ro], Matej Urbančič [sl], Praveen Illa [te],
Muhammet Kara [tr], Daniel Korostil [uk], Aron Xu [zh_CN]
3.3.2
=====
* Port D-Bus usage in the shell to GDBus [Giovanni, Marc-Antoine, Florian,

View File

@ -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;
}

View File

@ -1,5 +1,5 @@
AC_PREREQ(2.63)
AC_INIT([gnome-shell],[3.3.2],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell])
AC_INIT([gnome-shell],[3.3.3],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_SRCDIR([src/shell-global.c])
@ -63,7 +63,7 @@ AM_CONDITIONAL(BUILD_RECORDER, $build_recorder)
CLUTTER_MIN_VERSION=1.7.5
GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1
GJS_MIN_VERSION=1.29.18
MUTTER_MIN_VERSION=3.3.2
MUTTER_MIN_VERSION=3.3.3
FOLKS_MIN_VERSION=0.5.2
GTK_MIN_VERSION=3.0.0
GIO_MIN_VERSION=2.31.0

View File

@ -60,6 +60,7 @@ StScrollBar StBin#trough {
StScrollBar StButton#vhandle
{
background-image: url("scroll-vhandle.svg");
background-size: contain;
background-color: #252525;
border: 1px solid #080808;
border-radius: 8px;
@ -68,6 +69,7 @@ StScrollBar StButton#vhandle
StScrollBar StButton#hhandle
{
background-image: url("scroll-hhandle.svg");
background-size: contain;
background-color: #252525;
border: 1px solid #080808;
border-radius: 8px;
@ -224,16 +226,20 @@ StTooltip StLabel {
.toggle-switch-us {
background-image: url("toggle-off-us.svg");
background-size: contain;
}
.toggle-switch-us:checked {
background-image: url("toggle-on-us.svg");
background-size: contain;
}
.toggle-switch-intl {
background-image: url("toggle-off-intl.svg");
background-size: contain;
}
.toggle-switch-intl:checked {
background-image: url("toggle-on-intl.svg");
background-size: contain;
}
.nm-menu-item-icons {
@ -356,6 +362,7 @@ StTooltip StLabel {
.panel-button:focus {
border-image: url("panel-button-border.svg") 10 10 0 2;
background-image: url("panel-button-highlight-wide.svg");
background-size: contain;
color: white;
text-shadow: black 0px 2px 2px;
}
@ -364,6 +371,7 @@ StTooltip StLabel {
.panel-status-button:checked,
.panel-status-button:focus {
background-image: url("panel-button-highlight-narrow.svg");
background-size: contain;
}
.panel-button:active > .system-status-icon,
@ -467,16 +475,17 @@ StTooltip StLabel {
}
.window-caption {
background: rgba(0,0,0,0.8);
border: 1px solid rgba(128,128,128,0.40);
border-radius: 10px;
background: rgba(0,0,0,0.5);
border-radius: 8px;
font-size: 9pt;
padding: 2px 8px;
-shell-caption-spacing: 4px;
font-weight: bold;
padding: 6px 12px;
-shell-caption-spacing: 12px;
}
.window-close {
background-image: url("close-window.svg");
background-size: 34px;
height: 34px;
width: 34px;
-shell-close-overlap: 20px;
@ -511,6 +520,7 @@ StTooltip StLabel {
.placeholder {
background-image: url("dash-placeholder.svg");
background-size: contain;
height: 24px;
}
@ -645,6 +655,17 @@ StTooltip StLabel {
font-size: 11pt;
}
.dash-label {
border-radius: 7px;
padding: 4px 12px;
background-color: rgba(0,0,0,0.5);
color: #ffffff;
font-size: 0.9em;
font-weight: bold;;
text-align: center;
-x-offset: 8px;
}
/* Apps */
.icon-grid {
@ -686,11 +707,13 @@ StTooltip StLabel {
.app-filter:selected {
color: #ffffff;
background-image: url("filter-selected-ltr.svg");
background-size: contain;
background-position: 190px 10px;
}
.app-filter:selected:rtl {
background-image: url("filter-selected-rtl.svg");
background-size: contain;
background-position: 10px 10px;
}
@ -771,6 +794,7 @@ StTooltip StLabel {
.app-well-app.running > .overview-icon {
text-shadow: black 0px 2px 2px;
background-image: url("running-indicator.svg");
background-size: contain;
}
.contact:selected,
@ -1432,6 +1456,7 @@ StTooltip StLabel {
.summary-source-button:selected .summary-source {
background-image: url("panel-button-highlight-narrow.svg");
background-size: contain;
border-image: url("source-button-border.svg") 10 10 0 1;
}
@ -1442,6 +1467,7 @@ StTooltip StLabel {
.summary-source-button:expanded:selected {
background-image: url("panel-button-highlight-wide.svg");
background-size: contain;
border-image: url("source-button-border.svg") 10 10 0 1;
}
@ -1555,6 +1581,7 @@ StTooltip StLabel {
width: 52px;
height: 52px;
background-image: url("corner-ripple-ltr.png");
background-size: contain;
}
.ripple-box:rtl {
@ -1596,6 +1623,7 @@ StTooltip StLabel {
border: 0px;
background: rgba(255,255,255,0.5);
background-image: url("ws-switch-arrow-up.svg");
background-size: contain;
border-radius: 8px;
}
@ -1604,6 +1632,7 @@ StTooltip StLabel {
border: 0px;
background: rgba(255,255,255,0.5);
background-image: url("ws-switch-arrow-down.svg");
background-size: contain;
border-radius: 8px;
}

View File

@ -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

View File

@ -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=

View File

@ -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 \

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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) {

View File

@ -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);

View File

@ -6,6 +6,7 @@ const Lang = imports.lang;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Mainloop = imports.mainloop;
const AppDisplay = imports.ui.appDisplay;
const AppFavorites = imports.ui.appFavorites;
@ -16,6 +17,9 @@ const Tweener = imports.ui.tweener;
const Workspace = imports.ui.workspace;
const DASH_ANIMATION_TIME = 0.2;
const DASH_ITEM_LABEL_SHOW_TIME = 0.15;
const DASH_ITEM_LABEL_HIDE_TIME = 0.1;
const DASH_ITEM_HOVER_TIMEOUT = 300;
// A container like StBin, but taking the child's scale into account
// when requesting a size
@ -32,6 +36,8 @@ const DashItemContainer = new Lang.Class({
Lang.bind(this, this._allocate));
this.actor._delegate = this;
this._label = null;
this.child = null;
this._childScale = 1;
this._childOpacity = 255;
@ -84,6 +90,60 @@ const DashItemContainer = new Lang.Class({
alloc.natural_size = natWidth * this.child.scale_y;
},
showLabel: function() {
if (this._label == null)
return;
this._label.opacity = 0;
this._label.show();
let [stageX, stageY] = this.actor.get_transformed_position();
let itemHeight = this.actor.allocation.y2 - this.actor.allocation.y1;
let labelHeight = this._label.get_height();
let yOffset = Math.floor((itemHeight - labelHeight) / 2)
let y = stageY + yOffset;
let node = this._label.get_theme_node();
let xOffset = node.get_length('-x-offset');
let x;
if (St.Widget.get_default_direction () == St.TextDirection.RTL)
x = stageX - this._label.get_width() - xOffset;
else
x = stageX + this.actor.get_width() + xOffset;
this._label.set_position(x, y);
Tweener.addTween(this._label,
{ opacity: 255,
time: DASH_ITEM_LABEL_SHOW_TIME,
transition: 'easeOutQuad',
});
},
setLabelText: function(text) {
if (this._label == null)
this._label = new St.Label({ style_class: 'dash-label'});
this._label.set_text(text);
Main.layoutManager.addChrome(this._label);
this._label.hide();
},
hideLabel: function () {
this._label.opacity = 255;
Tweener.addTween(this._label,
{ opacity: 0,
time: DASH_ITEM_LABEL_HIDE_TIME,
transition: 'easeOutQuad',
onComplete: Lang.bind(this, function() {
this._label.hide();
})
});
},
setChild: function(actor) {
if (this.child == actor)
return;
@ -238,6 +298,7 @@ const Dash = new Lang.Class({
this._dragPlaceholderPos = -1;
this._animatingPlaceholdersCount = 0;
this._favRemoveTarget = null;
this._labelTimeoutId = 0;
this._box = new St.BoxLayout({ name: 'dash',
vertical: true,
@ -371,16 +432,37 @@ const Dash = new Lang.Class({
Lang.bind(this, function() {
display.actor.opacity = 255;
}));
display.actor.set_tooltip_text(app.get_name());
let item = new DashItemContainer();
item.setChild(display.actor);
display.icon.setIconSize(this.iconSize);
item.setLabelText(app.get_name());
display.icon.setIconSize(this.iconSize);
display.actor.connect('notify::hover',
Lang.bind(this, function() {
this._onHover(item, display)
}));
return item;
},
_onHover: function (item, display) {
if (display.actor.get_hover() && !display.isMenuUp) {
if (this._labelTimeoutId == 0) {
this._labelTimeoutId = Mainloop.timeout_add(DASH_ITEM_HOVER_TIMEOUT,
Lang.bind(this, function() {
item.showLabel();
return false;
}));
}
} else {
if (this._labelTimeoutId > 0)
Mainloop.source_remove(this._labelTimeoutId);
this._labelTimeoutId = 0;
item.hideLabel();
}
},
_adjustIconSize: function() {
// For the icon size, we only consider children which are "proper"
// icons (i.e. ignoring drag placeholders) and which are not

View File

@ -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);
});
}

View File

@ -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);
}
});

View File

@ -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);

View File

@ -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();

View File

@ -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'
});

View File

@ -94,7 +94,7 @@ const NotificationDaemon = new Lang.Class({
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(NotificationDaemonIface, this);
this._dbusImpl.export(Gio.DBus.session, '/org/freedesktop/Notifications');
this._sources = {};
this._sources = [];
this._senderToPid = {};
this._notifications = {};
this._busProxy = new Bus();
@ -152,14 +152,30 @@ const NotificationDaemon = new Lang.Class({
}
},
_lookupSource: function(title, pid, trayIcon) {
for (let i = 0; i < this._sources.length; i++) {
let source = this._sources[i];
if (source.pid == pid &&
(source.initialTitle == title || source.trayIcon || trayIcon))
return source;
}
return null;
},
// Returns the source associated with ndata.notification if it is set.
// Otherwise, returns the source associated with the pid if one is
// stored in this._sources and the notification is not transient.
// Otherwise, creates a new source as long as pid is provided.
// Otherwise, returns the source associated with the title and pid if
// such source is stored in this._sources and the notification is not
// transient. If the existing or requested source is associated with
// a tray icon and passed in pid matches a pid of an existing source,
// the title match is ignored to enable representing a tray icon and
// notifications from the same application with a single source.
//
// If no existing source is found, a new source is created as long as
// pid is provided.
//
// Either a pid or ndata.notification is needed to retrieve or
// create a source.
_getSource: function(title, pid, ndata, sender) {
_getSource: function(title, pid, ndata, sender, trayIcon) {
if (!pid && !(ndata && ndata.notification))
return null;
@ -176,20 +192,24 @@ const NotificationDaemon = new Lang.Class({
// with a transient one from the same sender, so we
// always create a new source object for new transient notifications
// and never add it to this._sources .
if (!isForTransientNotification && this._sources[pid]) {
let source = this._sources[pid];
source.setTitle(title);
return source;
if (!isForTransientNotification) {
let source = this._lookupSource(title, pid, trayIcon);
if (source) {
source.setTitle(title);
return source;
}
}
let source = new Source(title, pid, sender);
let source = new Source(title, pid, sender, trayIcon);
source.setTransient(isForTransientNotification);
if (!isForTransientNotification) {
this._sources[pid] = source;
this._sources.push(source);
source.connect('destroy', Lang.bind(this,
function() {
delete this._sources[pid];
let index = this._sources.indexOf(source);
if (index >= 0)
this._sources.splice(index, 1);
}));
}
@ -268,7 +288,7 @@ const NotificationDaemon = new Lang.Class({
let sender = invocation.get_sender();
let pid = this._senderToPid[sender];
let source = this._getSource(appName, pid, ndata, sender);
let source = this._getSource(appName, pid, ndata, sender, null);
if (source) {
this._notifyForSource(source, ndata);
@ -294,7 +314,7 @@ const NotificationDaemon = new Lang.Class({
}
let [pid] = result;
source = this._getSource(appName, pid, ndata, sender);
source = this._getSource(appName, pid, ndata, sender, null);
// We only store sender-pid entries for persistent sources.
// Removing the entries once the source is destroyed
@ -443,8 +463,8 @@ const NotificationDaemon = new Lang.Class({
if (!tracker.focus_app)
return;
for (let id in this._sources) {
let source = this._sources[id];
for (let i = 0; i < this._sources.length; i++) {
let source = this._sources[i];
if (source.app == tracker.focus_app) {
source.destroyNonResidentNotifications();
return;
@ -463,12 +483,11 @@ const NotificationDaemon = new Lang.Class({
},
_onTrayIconAdded: function(o, icon) {
let source = this._getSource(icon.title || icon.wm_class || _("Unknown"), icon.pid, null, null);
source.setTrayIcon(icon);
let source = this._getSource(icon.title || icon.wm_class || _("Unknown"), icon.pid, null, null, icon);
},
_onTrayIconRemoved: function(o, icon) {
let source = this._sources[icon.pid];
let source = this._lookupSource(icon.pid, null, true);
if (source)
source.destroy();
}
@ -478,10 +497,12 @@ const Source = new Lang.Class({
Name: 'NotificationDaemonSource',
Extends: MessageTray.Source,
_init: function(title, pid, sender) {
_init: function(title, pid, sender, trayIcon) {
this.parent(title);
this._pid = pid;
this.initialTitle = title;
this.pid = pid;
if (sender)
this._nameWatcherId = Gio.DBus.session.watch_name(sender,
Gio.BusNameWatcherFlags.NONE,
@ -495,7 +516,12 @@ const Source = new Lang.Class({
this.title = this.app.get_name();
else
this.useNotificationIcon = true;
this._trayIcon = null;
this.trayIcon = trayIcon;
if (this.trayIcon) {
this._setSummaryIcon(this.trayIcon);
this.useNotificationIcon = false;
}
},
_onNameVanished: function() {
@ -522,7 +548,7 @@ const Source = new Lang.Class({
},
handleSummaryClick: function() {
if (!this._trayIcon)
if (!this.trayIcon)
return false;
let event = Clutter.get_current_event();
@ -543,11 +569,11 @@ const Source = new Lang.Class({
let id = global.connect('notify::stage-input-mode', Lang.bind(this,
function () {
global.disconnect(id);
this._trayIcon.click(event);
this.trayIcon.click(event);
}));
Main.overview.hide();
} else {
this._trayIcon.click(event);
this.trayIcon.click(event);
}
return true;
},
@ -556,31 +582,25 @@ const Source = new Lang.Class({
if (this.app)
return;
this.app = Shell.WindowTracker.get_default().get_app_from_pid(this._pid);
this.app = Shell.WindowTracker.get_default().get_app_from_pid(this.pid);
if (!this.app)
return;
// Only override the icon if we were previously using
// notification-based icons (ie, not a trayicon) or if it was unset before
if (!this._trayIcon) {
if (!this.trayIcon) {
this.useNotificationIcon = false;
this._setSummaryIcon(this.app.create_icon_texture (this.ICON_SIZE));
}
},
setTrayIcon: function(icon) {
this._setSummaryIcon(icon);
this.useNotificationIcon = false;
this._trayIcon = icon;
},
open: function(notification) {
this.destroyNonResidentNotifications();
this.openApp();
},
_lastNotificationRemoved: function() {
if (!this._trayIcon)
if (!this.trayIcon)
this.destroy();
},

View File

@ -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();

View File

@ -3,6 +3,7 @@
const Cairo = imports.cairo;
const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Pango = imports.gi.Pango;
@ -235,11 +236,15 @@ const AppMenuButton = new Lang.Class({
Name: 'AppMenuButton',
Extends: PanelMenu.Button,
_init: function() {
this.parent(0.0);
_init: function(menuManager) {
this.parent(0.0, true);
this._startingApps = [];
this._menuManager = menuManager;
this._targetApp = null;
this._appMenuNotifyId = 0;
this._actionGroupNotifyId = 0;
let bin = new St.Bin({ name: 'appMenu' });
this.actor.add_actor(bin);
@ -264,10 +269,6 @@ const AppMenuButton = new Lang.Class({
this._iconBottomClip = 0;
this._quitMenu = new PopupMenu.PopupMenuItem('');
this.menu.addMenuItem(this._quitMenu);
this._quitMenu.connect('activate', Lang.bind(this, this._onQuit));
this._visible = !Main.overview.visible;
if (!this._visible)
this.actor.hide();
@ -446,12 +447,6 @@ const AppMenuButton = new Lang.Class({
}
},
_onQuit: function() {
if (this._targetApp == null)
return;
this._targetApp.request_quit();
},
_onAppStateChanged: function(appSys, app) {
let state = app.state;
if (state != Shell.AppState.STARTING) {
@ -513,8 +508,10 @@ const AppMenuButton = new Lang.Class({
}
if (targetApp == this._targetApp) {
if (targetApp && targetApp.get_state() != Shell.AppState.STARTING)
if (targetApp && targetApp.get_state() != Shell.AppState.STARTING) {
this.stopAnimation();
this._maybeSetMenu();
}
return;
}
@ -524,20 +521,53 @@ const AppMenuButton = new Lang.Class({
this._iconBox.hide();
this._label.setText('');
if (this._appMenuNotifyId)
this._targetApp.disconnect(this._appMenuNotifyId);
if (this._actionGroupNotifyId)
this._targetApp.disconnect(this._actionGroupNotifyId);
if (targetApp) {
this._appMenuNotifyId = targetApp.connect('notify::menu', Lang.bind(this, this._sync));
this._actionGroupNotifyId = targetApp.connect('notify::action-group', Lang.bind(this, this._sync));
}
this._targetApp = targetApp;
let icon = targetApp.get_faded_icon(2 * PANEL_ICON_SIZE);
this._label.setText(targetApp.get_name());
// TODO - _quit() doesn't really work on apps in state STARTING yet
this._quitMenu.label.set_text(_("Quit %s").format(targetApp.get_name()));
this._iconBox.set_child(icon);
this._iconBox.show();
if (targetApp.get_state() == Shell.AppState.STARTING)
this.startAnimation();
else
this._maybeSetMenu();
this.emit('changed');
},
_maybeSetMenu: function() {
let menu;
if (this._targetApp.action_group) {
if (this.menu instanceof PopupMenu.RemoteMenu &&
this.menu.actionGroup == this._targetApp.action_group)
return;
menu = new PopupMenu.RemoteMenu(this.actor, this._targetApp.menu, this._targetApp.action_group);
} else {
if (this.menu && !(this.menu instanceof PopupMenu.RemoteMenu))
return;
// fallback to older menu
menu = new PopupMenu.PopupMenu(this.actor, 0.0, St.Side.TOP, 0);
menu.addAction(_("Quit"), Lang.bind(this, function() {
this._targetApp.request_quit();
}));
}
this.setMenu(menu);
this._menuManager.addMenu(menu);
}
});
@ -924,9 +954,8 @@ const Panel = new Lang.Class({
// more cleanly with the rest of the panel
this._menus.addMenu(this._activitiesButton.menu);
this._appMenu = new AppMenuButton();
this._appMenu = new AppMenuButton(this._menus);
this._leftBox.add(this._appMenu.actor);
this._menus.addMenu(this._appMenu.menu);
}
/* center */

View File

@ -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();

View File

@ -2,7 +2,9 @@
const Cairo = imports.cairo;
const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const Shell = imports.gi.Shell;
const Signals = imports.signals;
@ -937,7 +939,12 @@ const PopupMenuBase = new Lang.Class({
this.emit('activate', menuItem);
this.close(true);
}));
menuItem.connect('destroy', Lang.bind(this, function(emitter) {
// the weird name is to avoid a conflict with some random property
// the menuItem may have, called destroyId
// (FIXME: in the future it may make sense to have container objects
// like PopupMenuManager does)
menuItem._popupMenuDestroyId = menuItem.connect('destroy', Lang.bind(this, function(menuItem) {
menuItem.disconnect(menuItem._popupMenuDestroyId);
menuItem.disconnect(menuItem._activateId);
menuItem.disconnect(menuItem._activeChangeId);
menuItem.disconnect(menuItem._sensitiveChangeId);
@ -1692,6 +1699,246 @@ const PopupComboBoxMenuItem = new Lang.Class({
}
});
/**
* RemoteMenu:
*
* A PopupMenu that tracks a GMenuModel and shows its actions
* (exposed by GApplication/GActionGroup)
*/
const RemoteMenu = new Lang.Class({
Name: 'RemoteMenu',
Extends: PopupMenu,
_init: function(sourceActor, model, actionGroup) {
this.parent(sourceActor, 0.0, St.Side.TOP);
this.model = model;
this.actionGroup = actionGroup;
this._actions = { };
this._modelChanged(this.model, 0, 0, this.model.get_n_items(), this);
this._actionStateChangeId = this.actionGroup.connect('action-state-changed', Lang.bind(this, this._actionStateChanged));
this._actionEnableChangeId = this.actionGroup.connect('action-enabled-changed', Lang.bind(this, this._actionEnabledChanged));
},
destroy: function() {
if (this._actionStateChangeId) {
this.actionGroup.disconnect(this._actionStateChangeId);
this._actionStateChangeId = 0;
}
if (this._actionEnableChangeId) {
this.actionGroup.disconnect(this._actionEnableChangeId);
this._actionEnableChangeId = 0;
}
this.parent();
},
_createMenuItem: function(model, index) {
let section_link = model.get_item_link(index, Gio.MENU_LINK_SECTION);
if (section_link) {
let item = new PopupMenuSection();
this._modelChanged(section_link, 0, 0, section_link.get_n_items(), item);
return [item, true, ''];
}
// labels are not checked for existance, as they're required for all items
let label = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_LABEL, null).deep_unpack();
// remove all underscores that are not followed by another underscore
label = label.replace(/_([^_])/, '$1');
let submenu_link = model.get_item_link(index, Gio.MENU_LINK_SUBMENU);
if (submenu_link) {
let item = new PopupSubMenuMenuItem(label);
this._modelChanged(submenu_link, 0, 0, submenu_link.get_n_items(), item.menu);
return [item, false, ''];
}
let action_id = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_ACTION, null).deep_unpack();
if (!this.actionGroup.has_action(action_id)) {
// the action may not be there yet, wait for action-added
return [null, false, 'action-added'];
}
if (!this._actions[action_id])
this._actions[action_id] = { enabled: this.actionGroup.get_action_enabled(action_id),
state: this.actionGroup.get_action_state(action_id),
items: [ ],
};
let action = this._actions[action_id];
let item, target, destroyId, specificSignalId;
if (action.state) {
// Docs have get_state_hint(), except that the DBus protocol
// has no provision for it (so ShellApp does not implement it,
// and neither GApplication), and g_action_get_state_hint()
// always returns null
// Funny :)
switch (String.fromCharCode(action.state.classify())) {
case 'b':
item = new PopupSwitchMenuItem(label, action.state.get_boolean());
action.items.push(item);
specificSignalId = item.connect('toggled', Lang.bind(this, function(item) {
this.actionGroup.activate_action(action_id, null);
}));
break;
case 's':
item = new PopupMenuItem(label);
item._remoteTarget = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null).deep_unpack();
action.items.push(item);
item.setShowDot(action.state.deep_unpack() == item._remoteTarget);
specificSignalId = item.connect('activate', Lang.bind(this, function(item) {
this.actionGroup.activate_action(action_id, GLib.Variant.new_string(item._remoteTarget));
}));
break;
default:
log('Action "%s" has state of type %s, which is not supported'.format(action_id, action.state.get_type_string()));
return [null, false, 'action-state-changed'];
}
} else {
target = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null);
item = new PopupMenuItem(label);
action.items.push(item);
specificSignalId = item.connect('activate', Lang.bind(this, function() {
this.actionGroup.activate_action(action_id, target);
}));
}
item.actor.reactive = item.actor.can_focus = action.enabled;
if (action.enabled)
item.actor.remove_style_pseudo_class('insensitive');
else
item.actor.add_style_pseudo_class('insensitive');
destroyId = item.connect('destroy', Lang.bind(this, function() {
item.disconnect(destroyId);
item.disconnect(specificSignalId);
let pos = action.items.indexOf(item);
if (pos != -1)
action.items.splice(pos, 1);
}));
return [item, false, ''];
},
_modelChanged: function(model, position, removed, added, target) {
let j, k;
let j0, k0;
let currentItems = target._getMenuItems();
for (j0 = 0, k0 = 0; j0 < position; j0++, k0++) {
if (currentItems[k0] instanceof PopupSeparatorMenuItem)
k0++;
}
if (removed == -1) {
// special flag to indicate we should destroy everything
for (k = k0; k < currentItems.length; k++)
currentItems[k].destroy();
} else {
for (j = j0, k = k0; j < j0 + removed; j++, k++) {
currentItems[k].destroy();
if (currentItems[k] instanceof PopupSeparatorMenuItem)
j--;
}
}
for (j = j0, k = k0; j < j0 + added; j++, k++) {
let [item, addSeparator, changeSignal] = this._createMenuItem(model, j);
if (item) {
// separators must be added in the parent to make autohiding work
if (addSeparator) {
target.addMenuItem(new PopupSeparatorMenuItem(), k+1);
k++;
}
target.addMenuItem(item, k);
if (addSeparator) {
target.addMenuItem(new PopupSeparatorMenuItem(), k+1);
k++;
}
} else if (changeSignal) {
let signalId = this.actionGroup.connect(changeSignal, Lang.bind(this, function() {
this.actionGroup.disconnect(signalId);
// force a full update
this._modelChanged(model, 0, -1, model.get_n_items(), target);
}));
}
}
if (!model._changedId) {
model._changedId = model.connect('items-changed', Lang.bind(this, this._modelChanged, target));
model._destroyId = target.connect('destroy', function() {
if (model._changedId)
model.disconnect(model._changedId);
if (model._destroyId)
target.disconnect(model._destroyId);
model._changedId = 0;
model._destroyId = 0;
});
}
if (target instanceof PopupMenuSection) {
target.actor.visible = target.numMenuItems != 0;
} else {
let sourceItem = target.sourceActor._delegate;
if (sourceItem instanceof PopupSubMenuMenuItem)
sourceItem.actor.visible = target.numMenuItems != 0;
}
},
_actionStateChanged: function(actionGroup, action_id) {
let action = this._actions[action_id];
if (!action)
return;
action.state = actionGroup.get_action_state(action_id);
if (action.items.length) {
switch (String.fromCharCode(action.state.classify())) {
case 'b':
for (let i = 0; i < action.items.length; i++)
action.items[i].setToggleState(action.state.get_boolean());
break;
case 'd':
for (let i = 0; i < action.items.length; i++)
action.items[i].setValue(action.state.get_double());
break;
case 's':
for (let i = 0; i < action.items.length; i++)
action.items[i].setShowDot(action.items[i]._remoteTarget == action.state.deep_unpack());
}
}
},
_actionEnabledChanged: function(actionGroup, action_id) {
let action = this._actions[action_id];
if (!action)
return;
action.enabled = actionGroup.get_action_enabled(action_id);
if (action.items.length) {
for (let i = 0; i < action.items.length; i++) {
let item = action.items[i];
item.actor.reactive = item.actor.can_focus = action.enabled;
if (action.enabled)
item.actor.remove_style_pseudo_class('insensitive');
else
item.actor.add_style_pseudo_class('insensitive');
}
}
}
});
/* Basic implementation of a menu manager.
* Call addMenu to add menus
*/

View File

@ -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);

View File

@ -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);
}

View File

@ -197,11 +197,11 @@ const Client = new Lang.Class({
_handleChannels: function(handler, account, conn, channels,
requests, user_action_time, context) {
this._handlingChannels(account, conn, channels);
this._handlingChannels(account, conn, channels, true);
context.accept();
},
_handlingChannels: function(account, conn, channels) {
_handlingChannels: function(account, conn, channels, notify) {
let len = channels.length;
for (let i = 0; i < len; i++) {
let channel = channels[i];
@ -212,7 +212,18 @@ const Client = new Lang.Class({
continue;
}
if (this._tpClient.is_handling_channel(channel)) {
// 'notify' will be true when coming from an actual HandleChannels
// call, and not when from a successful Claim call. The point is
// we don't want to notify for a channel we just claimed which
// has no new messages (for example, a new channel which only has
// a delivery notification). We rely on _displayPendingMessages()
// and _messageReceived() to notify for new messages.
// But we should still notify from HandleChannels because the
// Telepathy spec states that handlers must foreground channels
// in HandleChannels calls which are already being handled.
if (notify && this._tpClient.is_handling_channel(channel)) {
// We are already handling the channel, display the source
let source = this._chatSources[channel.get_object_path()];
if (source)
@ -285,7 +296,7 @@ const Client = new Lang.Class({
Lang.bind(this, function(dispatchOp, result) {
try {
dispatchOp.claim_with_finish(result);
this._handlingChannels(account, conn, [channel]);
this._handlingChannels(account, conn, [channel], false);
} catch (err) {
throw new Error('Failed to Claim channel: ' + err);
}}));
@ -502,15 +513,9 @@ const ChatSource = new Lang.Class({
this._notification.setUrgency(MessageTray.Urgency.HIGH);
this._notifyTimeoutId = 0;
// We ack messages when the message box is collapsed if user has
// interacted with it before and so read the messages:
// - user clicked on it the tray
// - user expanded the notification by hovering over the toaster notification
this._shouldAck = false;
this.connect('summary-item-clicked', Lang.bind(this, this._summaryItemClicked));
this._notification.connect('expanded', Lang.bind(this, this._notificationExpanded));
this._notification.connect('collapsed', Lang.bind(this, this._notificationCollapsed));
// We ack messages when the user expands the new notification or views the summary
// notification, in which case the notification is also expanded.
this._notification.connect('expanded', Lang.bind(this, this._ackMessages));
this._presence = contact.get_presence_type();
@ -774,24 +779,6 @@ const ChatSource = new Lang.Class({
// 'pending-message-removed' for each one.
this._channel.ack_all_pending_messages_async(Lang.bind(this, function(src, result) {
this._channel.ack_all_pending_messages_finish(result);}));
},
_summaryItemClicked: function(source, button) {
if (button != 1)
return;
this._shouldAck = true;
},
_notificationExpanded: function() {
this._shouldAck = true;
},
_notificationCollapsed: function() {
if (this._shouldAck)
this._ackMessages();
this._shouldAck = false;
}
});

View File

@ -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
View 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;
}
});

View File

@ -94,10 +94,11 @@ const ScaledPoint = new Lang.Class({
const WindowClone = new Lang.Class({
Name: 'WindowClone',
_init : function(realWindow) {
_init : function(realWindow, workspace) {
this.realWindow = realWindow;
this.metaWindow = realWindow.meta_window;
this.metaWindow._delegate = this;
this._workspace = workspace;
let [borderX, borderY] = this._getInvisibleBorderPadding();
this._windowClone = new Clutter.Clone({ source: realWindow.get_texture(),
@ -383,19 +384,12 @@ const WindowClone = new Lang.Class({
this.emit('drag-begin');
},
_getWorkspaceActor : function() {
let index = this.metaWindow.get_workspace().index();
return Main.overview.workspaces.getWorkspaceByIndex(index);
},
handleDragOver : function(source, actor, x, y, time) {
let workspace = this._getWorkspaceActor();
return workspace.handleDragOver(source, actor, x, y, time);
return this._workspace.handleDragOver(source, actor, x, y, time);
},
acceptDrop : function(source, actor, x, y, time) {
let workspace = this._getWorkspaceActor();
workspace.acceptDrop(source, actor, x, y, time);
this._workspace.acceptDrop(source, actor, x, y, time);
},
_onDragCancelled : function (draggable, time) {
@ -494,6 +488,9 @@ const WindowOverlay = new Lang.Class({
},
fadeIn: function() {
if (!this._hidden)
return;
this.show();
this.title.opacity = 0;
this._parentActor.raise_top();
@ -522,7 +519,7 @@ const WindowOverlay = new Lang.Class({
// get_transformed_position() and get_transformed_size(),
// as windowClone might be moving.
// See Workspace._showWindowOverlay
updatePositions: function(cloneX, cloneY, cloneWidth, cloneHeight) {
updatePositions: function(cloneX, cloneY, cloneWidth, cloneHeight, animate) {
let button = this.closeButton;
let title = this.title;
@ -544,15 +541,34 @@ const WindowOverlay = new Lang.Class({
else
buttonX = cloneX + (cloneWidth - button._overlap);
button.set_position(Math.floor(buttonX), Math.floor(buttonY));
if (animate)
this._animateOverlayActor(button, Math.floor(buttonX), Math.floor(buttonY), button.width);
else
button.set_position(Math.floor(buttonX), Math.floor(buttonY));
if (!title.fullWidth)
title.fullWidth = title.width;
title.width = Math.min(title.fullWidth, cloneWidth);
let titleWidth = Math.min(title.fullWidth, cloneWidth);
let titleX = cloneX + (cloneWidth - title.width) / 2;
let titleX = cloneX + (cloneWidth - titleWidth) / 2;
let titleY = cloneY + cloneHeight + title._spacing;
title.set_position(Math.floor(titleX), Math.floor(titleY));
if (animate)
this._animateOverlayActor(title, Math.floor(titleX), Math.floor(titleY), titleWidth);
else {
title.width = titleWidth;
title.set_position(Math.floor(titleX), Math.floor(titleY));
}
},
_animateOverlayActor: function(actor, x, y, width) {
Tweener.addTween(actor,
{ x: x,
y: y,
width: width,
time: Overview.ANIMATION_TIME,
transition: 'easeOutQuad'
});
},
_closeWindow: function(actor) {
@ -1017,7 +1033,7 @@ const Workspace = new Lang.Class({
let [x, y, scale] = this._computeWindowLayout(metaWindow, slot);
if (overlay)
if (overlay && initialPositioning)
overlay.hide();
if (animate && isOnCurrentWorkspace) {
if (!metaWindow.showing_on_its_workspace()) {
@ -1040,20 +1056,11 @@ const Workspace = new Lang.Class({
});
}
Tweener.addTween(clone.actor,
{ x: x,
y: y,
scale_x: scale,
scale_y: scale,
time: Overview.ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: Lang.bind(this, function() {
this._showWindowOverlay(clone, overlay, true);
})
});
this._animateClone(clone, overlay, x, y, scale, initialPositioning);
} else {
clone.actor.set_position(x, y);
clone.actor.set_scale(scale, scale);
this._updateWindowOverlayPositions(clone, overlay, x, y, scale, false);
this._showWindowOverlay(clone, overlay, isOnCurrentWorkspace);
}
}
@ -1075,23 +1082,35 @@ const Workspace = new Lang.Class({
}
},
_animateClone: function(clone, overlay, x, y, scale, initialPositioning) {
Tweener.addTween(clone.actor,
{ x: x,
y: y,
scale_x: scale,
scale_y: scale,
time: Overview.ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: Lang.bind(this, function() {
this._showWindowOverlay(clone, overlay, true);
})
});
this._updateWindowOverlayPositions(clone, overlay, x, y, scale, true);
},
_updateWindowOverlayPositions: function(clone, overlay, x, y, scale, animate) {
let [cloneWidth, cloneHeight] = clone.actor.get_size();
cloneWidth = scale * cloneWidth;
cloneHeight = scale * cloneHeight;
if (overlay)
overlay.updatePositions(x, y, cloneWidth, cloneHeight, animate);
},
_showWindowOverlay: function(clone, overlay, fade) {
if (clone.inDrag)
return;
// This is a little messy and complicated because when we
// start the fade-in we may not have done the final positioning
// of the workspaces. (Tweener doesn't necessarily finish
// all animations before calling onComplete callbacks.)
// So we need to manually compute where the window will
// be after the workspace animation finishes.
let [cloneX, cloneY] = clone.actor.get_position();
let [cloneWidth, cloneHeight] = clone.actor.get_size();
cloneWidth = clone.actor.scale_x * cloneWidth;
cloneHeight = clone.actor.scale_y * cloneHeight;
if (overlay) {
overlay.updatePositions(cloneX, cloneY, cloneWidth, cloneHeight);
if (fade)
overlay.fadeIn();
else
@ -1109,6 +1128,16 @@ const Workspace = new Lang.Class({
}
},
_hideAllOverlays: function() {
for (let i = 0; i < this._windows.length; i++) {
let clone = this._windows[i];
Tweener.removeTweens(clone.actor);
let overlay = this._windowOverlays[i];
if (overlay)
overlay.hide();
}
},
_delayedWindowRepositioning: function() {
if (this._windowIsZooming)
return true;
@ -1130,18 +1159,6 @@ const Workspace = new Lang.Class({
return false;
},
showWindowsOverlays: function() {
if (this.leavingOverview)
return;
this._windowOverlaysGroup.show();
this._showAllOverlays();
},
hideWindowsOverlays: function() {
this._windowOverlaysGroup.hide();
},
_doRemoveWindow : function(metaWin) {
let win = metaWin.get_compositor_private();
@ -1295,7 +1312,7 @@ const Workspace = new Lang.Class({
this.leavingOverview = true;
this.hideWindowsOverlays();
this._hideAllOverlays();
if (this._repositionWindowsId > 0) {
Mainloop.source_remove(this._repositionWindowsId);
@ -1386,7 +1403,7 @@ const Workspace = new Lang.Class({
// Create a clone of a (non-desktop) window and add it to the window list
_addWindowClone : function(win) {
let clone = new WindowClone(win);
let clone = new WindowClone(win, this);
let overlay = new WindowOverlay(clone, this._windowOverlaysGroup);
clone.connect('selected',

View File

@ -51,6 +51,7 @@ const WindowClone = new Lang.Class({
dragActorMaxSize: Workspace.WINDOW_DND_SIZE,
dragActorOpacity: Workspace.DRAGGING_WINDOW_OPACITY });
this._draggable.connect('drag-begin', Lang.bind(this, this._onDragBegin));
this._draggable.connect('drag-cancelled', Lang.bind(this, this._onDragCancelled));
this._draggable.connect('drag-end', Lang.bind(this, this._onDragEnd));
this.inDrag = false;
},
@ -108,6 +109,10 @@ const WindowClone = new Lang.Class({
this.emit('drag-begin');
},
_onDragCancelled : function (draggable, time) {
this.emit('drag-cancelled');
},
_onDragEnd : function (draggable, time, snapback) {
this.inDrag = false;
@ -386,6 +391,10 @@ const WorkspaceThumbnail = new Lang.Class({
Lang.bind(this, function(clone) {
Main.overview.beginWindowDrag();
}));
clone.connect('drag-cancelled',
Lang.bind(this, function(clone) {
Main.overview.cancelledWindowDrag();
}));
clone.connect('drag-end',
Lang.bind(this, function(clone) {
Main.overview.endWindowDrag();

View File

@ -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();
}
},

View File

@ -35,6 +35,7 @@ kn
ku
lt
lv
mk
mr
ms
nb

View File

@ -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

647
po/cs.po

File diff suppressed because it is too large Load Diff

757
po/da.po

File diff suppressed because it is too large Load Diff

618
po/es.po

File diff suppressed because it is too large Load Diff

518
po/fi.po

File diff suppressed because it is too large Load Diff

735
po/he.po

File diff suppressed because it is too large Load Diff

View File

@ -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."

864
po/ja.po

File diff suppressed because it is too large Load Diff

920
po/lt.po

File diff suppressed because it is too large Load Diff

1639
po/mk.po Normal file

File diff suppressed because it is too large Load Diff

550
po/nb.po

File diff suppressed because it is too large Load Diff

2032
po/nn.po

File diff suppressed because it is too large Load Diff

1674
po/ro.po

File diff suppressed because it is too large Load Diff

691
po/sl.po

File diff suppressed because it is too large Load Diff

747
po/te.po

File diff suppressed because it is too large Load Diff

788
po/tr.po

File diff suppressed because it is too large Load Diff

694
po/uk.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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
View 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, &parameter_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
View 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
View 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
View 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
View 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
View 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__ */

View File

@ -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,

View File

@ -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);

View File

@ -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

View File

@ -15,6 +15,7 @@
#include "shell-app-system-private.h"
#include "shell-window-tracker-private.h"
#include "st.h"
#include "gactionmuxer.h"
typedef enum {
MATCH_NONE,
@ -37,6 +38,15 @@ typedef struct {
/* Whether or not we need to resort the windows; this is done on demand */
gboolean window_sort_stale : 1;
/* See GApplication documentation */
gint name_watcher_id;
gchar *dbus_name;
GDBusProxy *app_proxy;
GActionGroup *remote_actions;
GMenuModel *remote_menu;
GActionMuxer *muxer;
GCancellable *dbus_cancellable;
} ShellAppRunningState;
/**
@ -72,11 +82,13 @@ struct _ShellApp
char *casefolded_exec;
};
G_DEFINE_TYPE (ShellApp, shell_app, G_TYPE_OBJECT);
enum {
PROP_0,
PROP_STATE
PROP_STATE,
PROP_ID,
PROP_DBUS_ID,
PROP_ACTION_GROUP,
PROP_MENU
};
enum {
@ -88,6 +100,15 @@ static guint shell_app_signals[LAST_SIGNAL] = { 0 };
static void create_running_state (ShellApp *app);
static void unref_running_state (ShellAppRunningState *state);
static void on_dbus_name_appeared (GDBusConnection *bus,
const gchar *name,
const gchar *name_owner,
gpointer user_data);
static void on_dbus_name_disappeared (GDBusConnection *bus,
const gchar *name,
gpointer user_data);
G_DEFINE_TYPE (ShellApp, shell_app, G_TYPE_OBJECT)
static void
shell_app_get_property (GObject *gobject,
@ -102,6 +123,20 @@ shell_app_get_property (GObject *gobject,
case PROP_STATE:
g_value_set_enum (value, app->state);
break;
case PROP_ID:
g_value_set_string (value, shell_app_get_id (app));
break;
case PROP_DBUS_ID:
g_value_set_string (value, shell_app_get_dbus_id (app));
break;
case PROP_ACTION_GROUP:
if (app->running_state)
g_value_set_object (value, app->running_state->muxer);
break;
case PROP_MENU:
if (app->running_state)
g_value_set_object (value, app->running_state->remote_menu);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
@ -151,6 +186,15 @@ window_backed_app_get_icon (ShellApp *app,
return actor;
}
const char *
shell_app_get_dbus_id (ShellApp *app)
{
if (app->running_state)
return app->running_state->dbus_name;
else
return NULL;
}
/**
* shell_app_create_icon_texture:
*
@ -513,6 +557,34 @@ shell_app_activate_window (ShellApp *app,
}
}
void
shell_app_update_window_actions (ShellApp *app, MetaWindow *window)
{
const char *object_path;
object_path = meta_window_get_dbus_object_path (window);
if (object_path != NULL)
{
GActionGroup *actions;
actions = g_object_get_data (G_OBJECT (window), "actions");
if (actions == NULL)
{
actions = G_ACTION_GROUP (g_dbus_action_group_get (g_dbus_proxy_get_connection (app->running_state->app_proxy),
meta_window_get_dbus_unique_name (window),
object_path));
g_object_set_data_full (G_OBJECT (window), "actions", actions, g_object_unref);
}
if (!app->running_state->muxer)
app->running_state->muxer = g_action_muxer_new ();
g_action_muxer_insert (app->running_state->muxer, "win", actions);
g_object_notify (G_OBJECT (app), "action-group");
}
}
/**
* shell_app_activate:
* @app: a #ShellApp
@ -903,6 +975,34 @@ shell_app_on_ws_switch (MetaScreen *screen,
g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
}
static void
on_dbus_application_id_changed (MetaWindow *window,
GParamSpec *pspec,
gpointer user_data)
{
const char *appid;
ShellApp *app = SHELL_APP (user_data);
/* Ignore changes in the appid after it's set, shouldn't happen */
if (app->running_state->dbus_name != NULL)
return;
appid = meta_window_get_dbus_application_id (window);
if (!appid)
return;
g_assert (app->running_state != NULL);
app->running_state->dbus_name = g_strdup (appid);
app->running_state->name_watcher_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
appid,
G_BUS_NAME_WATCHER_FLAGS_NONE,
on_dbus_name_appeared,
on_dbus_name_disappeared,
g_object_ref (app),
g_object_unref);
}
void
_shell_app_add_window (ShellApp *app,
MetaWindow *window)
@ -923,6 +1023,9 @@ _shell_app_add_window (ShellApp *app,
if (app->state != SHELL_APP_STATE_STARTING)
shell_app_state_transition (app, SHELL_APP_STATE_RUNNING);
g_signal_connect (window, "notify::dbus-application-id", G_CALLBACK(on_dbus_application_id_changed), app);
on_dbus_application_id_changed (window, NULL, app);
g_object_thaw_notify (G_OBJECT (app));
g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
@ -939,6 +1042,7 @@ _shell_app_remove_window (ShellApp *app,
g_signal_handlers_disconnect_by_func (window, G_CALLBACK(shell_app_on_unmanaged), app);
g_signal_handlers_disconnect_by_func (window, G_CALLBACK(shell_app_on_user_time_changed), app);
g_signal_handlers_disconnect_by_func (window, G_CALLBACK(on_dbus_application_id_changed), app);
g_object_unref (window);
app->running_state->windows = g_slist_remove (app->running_state->windows, window);
@ -948,6 +1052,137 @@ _shell_app_remove_window (ShellApp *app,
g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
}
static void
on_dbus_proxy_gotten (GObject *initable,
GAsyncResult *result,
gpointer user_data)
{
ShellApp *self = SHELL_APP (user_data);
ShellAppRunningState *state = self->running_state;
GError *error = NULL;
GVariant *menu_property;
state->app_proxy = g_dbus_proxy_new_finish (result,
&error);
if (error)
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
{
g_warning ("Unexpected error while creating application proxy: %s", error->message);
}
g_clear_error (&error);
g_clear_object (&state->dbus_cancellable);
if (state->name_watcher_id)
{
g_bus_unwatch_name (state->name_watcher_id);
state->name_watcher_id = 0;
}
g_free (state->dbus_name);
state->dbus_name = NULL;
g_object_unref (self);
return;
}
/* on to the second step, the primary action group */
state->remote_actions = (GActionGroup*)g_dbus_action_group_get (
g_dbus_proxy_get_connection (state->app_proxy),
g_dbus_proxy_get_name (state->app_proxy),
g_dbus_proxy_get_object_path (state->app_proxy));
if (!state->muxer)
state->muxer = g_action_muxer_new ();
g_action_muxer_insert (state->muxer, "app", state->remote_actions);
g_strfreev (g_action_group_list_actions (state->remote_actions));
g_object_notify (G_OBJECT (self), "action-group");
menu_property = g_dbus_proxy_get_cached_property (state->app_proxy, "AppMenu");
if (menu_property && g_variant_n_children (menu_property) > 0)
{
const gchar *object_path;
g_variant_get_child (menu_property, 0, "&o", &object_path);
state->remote_menu = G_MENU_MODEL (g_dbus_menu_model_get (g_dbus_proxy_get_connection (state->app_proxy),
state->dbus_name,
object_path));
g_object_notify (G_OBJECT (self), "menu");
}
}
static void
on_dbus_name_appeared (GDBusConnection *bus,
const gchar *name,
const gchar *name_owner,
gpointer user_data)
{
ShellApp *self = SHELL_APP (user_data);
ShellAppRunningState *state = self->running_state;
char *object_path;
g_assert (state != NULL);
object_path = g_strconcat ("/", name, NULL);
g_strdelimit (object_path, ".", '/');
if (!state->dbus_cancellable)
state->dbus_cancellable = g_cancellable_new ();
/* first step: the application proxy */
g_dbus_proxy_new (bus,
G_DBUS_PROXY_FLAGS_NONE,
NULL, /* interface info */
name_owner,
object_path,
"org.gtk.Application",
state->dbus_cancellable,
on_dbus_proxy_gotten,
g_object_ref (self));
g_object_notify (G_OBJECT (self), "dbus-id");
g_free (object_path);
}
static void
on_dbus_name_disappeared (GDBusConnection *bus,
const gchar *name,
gpointer user_data)
{
ShellApp *self = SHELL_APP (user_data);
ShellAppRunningState *state = self->running_state;
g_assert (state != NULL);
if (state->dbus_cancellable)
{
g_cancellable_cancel (state->dbus_cancellable);
g_clear_object (&state->dbus_cancellable);
}
g_clear_object (&state->app_proxy);
g_clear_object (&state->remote_actions);
g_clear_object (&state->remote_menu);
g_clear_object (&state->muxer);
g_free (state->dbus_name);
state->dbus_name = NULL;
g_bus_unwatch_name (state->name_watcher_id);
state->name_watcher_id = 0;
}
/**
* shell_app_get_pids:
* @app: a #ShellApp
@ -1167,13 +1402,30 @@ unref_running_state (ShellAppRunningState *state)
{
MetaScreen *screen;
g_assert (state->refcount > 0);
state->refcount--;
if (state->refcount > 0)
return;
screen = shell_global_get_screen (shell_global_get ());
g_signal_handler_disconnect (screen, state->workspace_switch_id);
if (state->dbus_cancellable)
{
g_cancellable_cancel (state->dbus_cancellable);
g_object_unref (state->dbus_cancellable);
}
g_clear_object (&state->app_proxy);
g_clear_object (&state->remote_actions);
g_clear_object (&state->remote_menu);
g_clear_object (&state->muxer);
g_free (state->dbus_name);
if (state->name_watcher_id)
g_bus_unwatch_name (state->name_watcher_id);
g_slice_free (ShellAppRunningState, state);
}
@ -1349,6 +1601,9 @@ shell_app_dispose (GObject *object)
while (app->running_state->windows)
_shell_app_remove_window (app, app->running_state->windows->data);
}
/* We should have been transitioned when we removed all of our windows */
g_assert (app->state == SHELL_APP_STATE_STOPPED);
g_assert (app->running_state == NULL);
G_OBJECT_CLASS(shell_app_parent_class)->dispose (object);
}
@ -1399,4 +1654,60 @@ shell_app_class_init(ShellAppClass *klass)
SHELL_TYPE_APP_STATE,
SHELL_APP_STATE_STOPPED,
G_PARAM_READABLE));
/**
* ShellApp:id:
*
* The id of this application (a desktop filename, or a special string
* like window:0xabcd1234)
*/
g_object_class_install_property (gobject_class,
PROP_ID,
g_param_spec_string ("id",
"Application id",
"The desktop file id of this ShellApp",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
/**
* ShellApp:dbus-id:
*
* The DBus well-known name of the application, if one can be associated
* to this ShellApp (it means that the application is using GApplication)
*/
g_object_class_install_property (gobject_class,
PROP_DBUS_ID,
g_param_spec_string ("dbus-id",
"Application DBus Id",
"The DBus well-known name of the application",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
/**
* ShellApp:action-group:
*
* The #GDBusActionGroup associated with this ShellApp, if any. See the
* documentation of #GApplication and #GActionGroup for details.
*/
g_object_class_install_property (gobject_class,
PROP_ACTION_GROUP,
g_param_spec_object ("action-group",
"Application Action Group",
"The action group exported by the remote application",
G_TYPE_ACTION_GROUP,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
/**
* ShellApp:menu:
*
* The #GMenuProxy associated with this ShellApp, if any. See the
* documentation of #GMenuModel for details.
*/
g_object_class_install_property (gobject_class,
PROP_MENU,
g_param_spec_object ("menu",
"Application Menu",
"The primary menu exported by the remote application",
G_TYPE_MENU_MODEL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}

View File

@ -13,6 +13,7 @@ G_BEGIN_DECLS
typedef struct _ShellApp ShellApp;
typedef struct _ShellAppClass ShellAppClass;
typedef struct _ShellAppPrivate ShellAppPrivate;
typedef struct _ShellAppAction ShellAppAction;
#define SHELL_TYPE_APP (shell_app_get_type ())
#define SHELL_APP(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_APP, ShellApp))
@ -36,9 +37,12 @@ typedef enum {
GType shell_app_get_type (void) G_GNUC_CONST;
const char *shell_app_get_id (ShellApp *app);
GMenuTreeEntry *shell_app_get_tree_entry (ShellApp *app);
GDesktopAppInfo *shell_app_get_app_info (ShellApp *app);
const char *shell_app_get_dbus_id (ShellApp *app);
ClutterActor *shell_app_create_icon_texture (ShellApp *app, int size);
ClutterActor *shell_app_get_faded_icon (ShellApp *app, int size);
const char *shell_app_get_name (ShellApp *app);
@ -79,6 +83,8 @@ int shell_app_compare_by_name (ShellApp *app, ShellApp *other);
int shell_app_compare (ShellApp *app, ShellApp *other);
void shell_app_update_window_actions (ShellApp *app, MetaWindow *window);
G_END_DECLS
#endif /* __SHELL_APP_H__ */

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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;
}

View File

@ -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];

View File

@ -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;

View File

@ -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__ */

View File

@ -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
View File

@ -0,0 +1,104 @@
#!/usr/bin/env gjs
const Gdk = imports.gi.Gdk;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
function do_action(action, parameter) {
print ("Action '" + action.name + "' invoked");
}
function do_action_param(action, parameter) {
print ("Action '" + action.name + "' invoked with parameter " + parameter.print(true));
}
function do_action_toggle(action) {
action.set_state(GLib.Variant.new('b', !action.state.deep_unpack()));
print ("Toggled");
}
function do_action_state_change(action) {
print ("Action '" + action.name + "' has now state " + action.state.print(true));
}
function main() {
Gtk.init(null, null);
let app = new Gtk.Application({ application_id: 'org.gnome.Shell.GtkApplicationTest' });
app.connect('activate', function() {
print ("Activated");
});
let action = new Gio.SimpleAction({ name: 'one' });
action.connect('activate', do_action);
app.add_action(action);
let action = new Gio.SimpleAction({ name: 'two' });
action.connect('activate', do_action);
app.add_action(action);
let action = new Gio.SimpleAction({ name: 'toggle', state: GLib.Variant.new('b', false) });
action.connect('activate', do_action_toggle);
action.connect('notify::state', do_action_state_change);
app.add_action(action);
let action = new Gio.SimpleAction({ name: 'disable', enabled: false });
action.set_enabled(false);
action.connect('activate', do_action);
app.add_action(action);
let action = new Gio.SimpleAction({ name: 'parameter-int', parameter_type: GLib.VariantType.new('u') });
action.connect('activate', do_action_param);
app.add_action(action);
let action = new Gio.SimpleAction({ name: 'parameter-string', parameter_type: GLib.VariantType.new('s') });
action.connect('activate', do_action_param);
app.add_action(action);
let menu = new Gio.Menu();
menu.append('An action', 'app.one');
let section = new Gio.Menu();
section.append('Another action', 'app.two');
section.append('Same as above', 'app.two');
menu.append_section(null, section);
// another section, to check separators
section = new Gio.Menu();
section.append('Checkbox', 'app.toggle');
section.append('Disabled', 'app.disable');
menu.append_section(null, section);
// empty sections or submenus should be invisible
menu.append_section('Empty section', new Gio.Menu());
menu.append_submenu('Empty submenu', new Gio.Menu());
let submenu = new Gio.Menu();
submenu.append('Open c:\\', 'app.parameter-string::c:\\');
submenu.append('Open /home', 'app.parameter-string::/home');
menu.append_submenu('Recent files', submenu);
let item = Gio.MenuItem.new('Say 42', null);
item.set_action_and_target_value('app.parameter-int', GLib.Variant.new('u', 42));
menu.append_item(item);
let item = Gio.MenuItem.new('Say 43', null);
item.set_action_and_target_value('app.parameter-int', GLib.Variant.new('u', 43));
menu.append_item(item);
app.set_app_menu(menu);
let window = null;
app.connect_after('startup', function(app) {
window = new Gtk.ApplicationWindow({ title: "Test Application", application: app });
});
app.connect('activate', function(app) {
window.present();
});
app.run(null);
}
main();

View File

@ -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",

View File

@ -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

View 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();

View 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

View 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

View 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

View File

@ -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);

View 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);