Compare commits

...

116 Commits

Author SHA1 Message Date
90a691ed25 Bump version to 3.3.4
Update NEWS
2012-01-20 21:12:39 -05:00
eadb41b3bb Bump dependencies on gtk+ 3.3.9 and glib 2.31.6 2012-01-20 20:46:01 +01:00
6829590c8f dash: improve timing of labels
DashItem labels have initial delay before showing up, but once the
first label in the dash is visible (meaning the user is very likely
exploring things) and the pointer is moved along the dash, the label
will follow immediately.

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

Signed-off-by: Seif Lotfy <seif.lotfy@collabora.co.uk>
2012-01-20 20:19:18 +01:00
29da720e6a notificationDaemon: fix order of arguments to _lookupSource()
The order of arguments passed to _lookupSource() was wrong, causing
problems when tray icons were removed.

https://bugzilla.gnome.org/show_bug.cgi?id=664138
2012-01-20 00:03:07 -05:00
c5932c0f07 Adjust to gtk/mutter changes for Application API
https://bugzilla.gnome.org/show_bug.cgi?id=668118
2012-01-18 17:25:35 -05:00
6195386a06 Update iconGrid label style to current mockup
Reference: http://git.gnome.org/browse/gnome-shell-design/plain/mockups/static/overview-application-picker.png

https://bugzilla.gnome.org/show_bug.cgi?id=642392
2012-01-18 19:17:46 +00:00
0080440118 workspaceThumbnail: fix window tracking bugs
- We should only call workspaceRemoved() for workspaces that are
  are actually being removed.
- When we have multiple monitors, a window on a secondary monitor is
  on all workspaces, so it ends up in all workspaces _allWindows
  lists, so we can't use previous presence in that list to determine
  whether we need to go ahead and add the actor; allWindows is simply
  the list of windows where we are listening  to notify::minimized.

https://bugzilla.gnome.org/show_bug.cgi?id=667652
2012-01-18 10:20:53 -05:00
b5be62cd1b ShellApp: Use unique name for menus
Otherwise since the auto-activate flag is set for dbus messages, we
may re-vivify applications that have DBus service files.

https://bugzilla.gnome.org/show_bug.cgi?id=667881
2012-01-17 14:00:51 -05:00
1bac40fbe3 App menu: only create the popup menu with a GMenu
Since the application proxy is created asynchronously, at the time
the GActionGroup (GActionMuxer) is created, there is no GDBusMenu yet.
Defer creating the menu in that case.
Also, clear out signal handlers if have no target application.

https://bugzilla.gnome.org/show_bug.cgi?id=633028
2012-01-17 18:40:04 +01:00
11637bae43 Network Menu: prefer VPN connections when showing the icon
When VPN is active, and it's not the default routing, we should
show the vpn icon, rather than the physical connection.

https://bugzilla.gnome.org/show_bug.cgi?id=665115
2012-01-17 18:24:44 +01:00
301bacec9f workspaceThumbnail: improve handling of notify::minimized signal
There were various cases where we could lose track of a window and
leave the notify::minimized signal connect after the actor was destroyed
or the workspace removed. This could result in operations on a removed
workspace and assertion failures inside Mutter.

https://bugzilla.redhat.com/show_bug.cgi?id=773059
https://bugzilla.gnome.org/show_bug.cgi?id=667652
2012-01-17 10:53:07 -05:00
8943b3b0e9 workspaceThumbnail: disconnect handlers when workspace is removed
We need to remove the handlers when the workspace is removed, not
when the animation of it being removed finishes, or we can access
a destroyed workspace and triggger an assertion failure in Mutter.

https://bugzilla.redhat.com/show_bug.cgi?id=705664
https://bugzilla.gnome.org/show_bug.cgi?id=667652
2012-01-17 10:53:07 -05:00
f59018f2d7 workspaceThumbnail: clear the drop placeholder on pointer leave
We add a drag monitor to check whether the pointer is inside
the workspace selector, and update the visibility of the drop
placeholder consequently.

https://bugzilla.gnome.org/show_bug.cgi?id=664201
2012-01-17 15:45:23 +01:00
235cb9c505 build: Add the 'gl' pkg-config
Cogl does not explicitly link against GL or GLES any more, and Clutter
master dropped the 'gl' pkg-config requirement because it introduced
unneeded and conflicting dependencies.

GNOME Shell still uses glXQuery* API, so it needs to explicitly link
against libGL.

https://bugzilla.gnome.org/show_bug.cgi?id=667864
2012-01-17 13:48:59 +00:00
3f328463a8 Fix timeout callback leaks
Make sure that we don't leak oneshot timeout handlers in main.js and
polkitAuthenticationAgent.js by making the callback return false.

https://bugzilla.gnome.org/show_bug.cgi?id=668087
2012-01-17 12:39:03 +01:00
e58c82fc04 theme-node-drawing: don't crash if st_theme_node_paint() is called on an empty area
When st_theme_node_paint() was called with zero width or height and a theme
node with a shadow, we'd crash because we'd fail to allocate a texture with
an empty size, then unreference the NULL pointer.

https://bugzilla.redhat.com/show_bug.cgi?id=748293
https://bugzilla.gnome.org/show_bug.cgi?id=668050
2012-01-16 17:38:38 -05:00
91ca86ffe4 st-theme-node-drawing: clear border buffer before drawing on it
A new texture has undefined contents - when we're creating a shadow,
we need to clear the contents of the texture before drawing the border
and background into it.

https://bugzilla.gnome.org/show_bug.cgi?id=668048
2012-01-16 17:38:38 -05:00
33d4518e50 hotplug-sniffer: fix double free when setting D-Bus return value
g_dbus_method_invocation_return_value() adopts a floating reference,
so we don't also need to unreference it; fix by replacing the code
using a more compact form using the ^ convenience character in
GVariant type specifications. (Thanks to Ryan Lortie for the
suggestion.)

https://bugzilla.gnome.org/show_bug.cgi?id=667378
2012-01-16 17:38:38 -05:00
b087191d2b RemoteMenu: add support for section labels
According to the GIO docs, sections can have labels too. We support
them by inserting a non reactive menu item at the beginning of the
section. This item is specially flagged to be ignored while processing
changed signals from the model (since it does not correspond to any
model item)

https://bugzilla.gnome.org/show_bug.cgi?id=666681
2012-01-16 19:29:50 +01:00
64baea1693 Workspace: Set a maximum scale for window clones
If there's a single small window (e.g. empathy chat) in the overview, it
looks usable, because it's as big as outside of the overview, but when
you start to type, overview search is launched, which is confusing.

Fix that by setting maximum scale for window clones to 0.7

https://bugzilla.gnome.org/show_bug.cgi?id=646704
2012-01-15 16:25:18 +01:00
b88b743428 Delay rearrangement when cursor hovers a window
In overview when closing a window and afterwards dragging a window it can
happen that you pick a wrong window or no window if windows' positions is
updated while initiating the drag.
Fix that by delaying window rearrangement when cursor is over a window.

https://bugzilla.gnome.org/show_bug.cgi?id=645325
2012-01-15 16:22:53 +01:00
c606cf076d Updated Slovenian translation 2012-01-15 13:46:05 +01:00
d205d7e7c2 Updated Galician translations 2012-01-15 02:34:44 +01:00
efdd3375d0 updated persian translation 2012-01-15 00:42:32 +03:30
abcca3d3bc ShellEmbeddedWindow: don't update the size of a destroyed actor.
shell_embedded_window_hide() can be called during widget destruction,
after the associated ClutterActor has been already cleared out.
Fix a crash in that case.

https://bugzilla.gnome.org/show_bug.cgi?id=633028
2012-01-14 13:31:04 +01:00
f4d13b9801 ShellApp: don't use the app proxy before it's created
The application proxy is created asynchrously after the dbus name
is registed. This means that when tracking the first window (and
therefore creating the first window GActionGroup) there is no
app proxy yet.

https://bugzilla.gnome.org/show_bug.cgi?id=633028
2012-01-14 13:31:04 +01:00
7da39031e1 Place display: don't use a StIcon for DND
DND code assumes it can query the size of the actor before parenting,
while StWidget asserts that get_preferred_size() is only called
after the actor is on stage. This fixes a crash while dragging
"Connect to..."

https://bugzilla.gnome.org/show_bug.cgi?id=633028
2012-01-14 13:31:04 +01:00
6cdb1bd60c telepathyClient: handle ExtendedAway as Away and not Offline
https://bugzilla.gnome.org/show_bug.cgi?id=667813
2012-01-13 11:18:35 +01:00
7c108e267c dbus: fix Screenshot async methods fallout from GDBus migration
With GJS' GDBus implementation, we get the invocation paramters as an
array if we declare a method as async.
This is bad and not consistent with what GJS does for synchronous
methods, but it's the way it is, and other classes in gnome-shell
implement this correctly by exploding the array into its components in
the method implementation, but not the screenshot methods.
Also, we're supposed to return a value using the provided invocation
object, not with a callback now, so do that.

https://bugzilla.gnome.org/show_bug.cgi?id=667662
2012-01-12 15:44:38 -05:00
5cf06fe9a7 telepathyClient: No need to prepare channel contacts
tp-glib does it for us since version 0.15.6

https://bugzilla.gnome.org/show_bug.cgi?id=658817
2012-01-12 20:55:40 +01:00
41f6956197 Fix some fallout from background-size addition
Commit 25948f214e replaced the old hardcoded scaling behavior of
background-images with the CSS-compliant option to control that
behavior with the background-size property. Fix some fallout from
the changed default scaling behavior.
2012-01-10 21:50:21 +01:00
417cbea79c gsettings: Add migration file for overrides
Mutter/Metacity settings overridden by the shell have not been
migrated when moving to GSettings, but there's no good reason not
to migrate those preferences as well.

https://bugzilla.gnome.org/show_bug.cgi?id=667636
2012-01-10 19:40:01 +01:00
225c807550 Updated Finnish translation by Jiri Grönroos. 2012-01-10 09:28:22 +01:00
ff78d2655b Updated Belarusian translation. 2012-01-10 01:19:50 +03:00
bde15f7c61 Updated Bulgarian translation 2012-01-08 10:49:22 +02:00
30300f1aeb Updated Norwegian bokmål translation 2012-01-06 21:18:12 +01:00
d42c3a15d6 Updated Vietnamese translation 2012-01-06 16:36:00 +07:00
14a65559af po/vi: import from Damned Lies 2012-01-06 16:30:38 +07:00
ba1e7bd095 Updated Russian translation 2012-01-05 15:59:24 +04:00
951705a4b2 NEWS: small fixes for 3.3.3 release
Add a couple of missing translators and a missing bug ID from late changes.
2012-01-04 12:46:46 -05:00
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
100 changed files with 15398 additions and 8968 deletions

82
NEWS
View File

@ -1,3 +1,85 @@
3.3.4
=====
* https://live.gnome.org/EveryDetailMatters
- Add "browse" for labels for dash items - once a tooltip is
showing, switch to other items without a delay [Seif; #666170]
- Always scale down windows in the overview at least to 70% [Vit; #646704]
- Fix the new-workspace drop indicator sometimes getting stuck
[Stefano; #664201]
- Delay rearranging windows in the overview as long as the pointer
is over a window [Vit; #645325]
* Add a GConf => DConf migration file for overriden Mutter settings
[Florian; #667636]
* When a VPN connection is active, show that as the network icon
[Giovanni; #665115]
* Handle the "ExtendedAway" IM status as away, not offline [Guillaume; #667813]
* Improve the appearance of the labels in "Applications" [Alex; #642392]
* Adjust for GTK+ and Mutter API changes for application menu [Ryan; #668118]
* Add section label support to the application menu [Giovanni; #666681]
* Fix screenshot methods to work again [Cosimo; #667662]
* Fix several crashers related to updating workspace thumbnails [Owen; #667652]
* Fix memory management error causing gnome-shell-hotplug-sniffer to crash
[Owen; #667378]
* Build fixes [Emmanuele, Rico; #667864]
* Code cleanups [Adel; #668087]
* Misc bug fixes [Colin, Florian, Giovanni, Owen, Xavier; #633028, #658817,
#664138, #667881, #668048, #668050]
Contributors:
Emmanuele Bassi, Giovanni Campagna, Cosimo Cecchi, Xavier Claessens,
Guillaume Desmottes, Stefano Facchini, Adel Gadllah, Alex Hultman,
Ryan Lortie, Seif Lotfy, Florian Müllner, Vit Stanislav, Owen Taylor,
Rico Tzschichholz, Colin Walters
Translations:
Ihar Hrachyshka [be], Alexander Shopov [bg], Arash Mousavi [fa],
Jiri Grönroos, Timo Jyrinki [fi], Fran Diéguez [gl], Kjartan Maraas [nb],
Yuri Myasoedov [ru], Matej Urbančič [sl], Nguyễn Thái Ngọc Duy [vi]
3.3.3
=====
* https://live.gnome.org/EveryDetailMatters
- Stop flashing the window labels on actions in overview [Zan; #644861]
- Improve the look of window captions in the overview [Marc; #664487]
- Move dash tooltips beside the icon [Seif, Stefano; #666166]
* Support application menus exported from applications via new GLib API
and D-Bus protocol. [Giovanni, Colin, Matthias, Cosimo]
* For removable device prompts, show "Open with Rhythmbox], rather
than "Open with Rhythmbox Music Player' [Florian; #664561]
* Switch to activating the message tray only with a hot corner rather
than a full row of pixels, allowing mouse events to apps [Rui; #663366]
* Fully handle the case where the workspaces-only-on-primary
GSetting is set to false [Florian; #652580]
* Add support for background-size CSS property to St [Quentin; #633462]
* Port to new GJS Lang.Class framework [Giovanni; #664436]
* Finish port to GDBus [Giovanni; #664436]
* Stop using the deprecated Clutter default stage [Florian, Jasper; #664052]
* Fix bugs that kept browser plugin from working in WebKit-based browser
[Jasper; #666444]
* Fix typo that made uninstalling extensions not work [Jasper]
* Fix crash in browser plugin if shell is not run [Jürg]
* Reintroduce piscine paschal ovum [Giovanni; #666606]
* Network menu bug fixes
Giovanni; #664124, #665194, #665680, #666429, #666614]
* Misc bug fixes [Florian, Jasper, Jonny, Marina, Ron; #647587, #659272,
#664138, #665261, #666020, #666243]
* Build fixes [Owen]
Contributors:
Jürg Billeter, Giovanni Campagna, Stefano Candori, Cosimo Cecchi,
Matthias Clasen, Zan Dobersek, Quentin Glidic, Jonny Lamb, Ryan Lortie,
Seif Lotfy, Rui Matos, Florian Müllner, Bastien Nocera, Jasper St. Pierre,
Marc Plano-Lesay, Owen Taylor, Colin Walters, Ron Yorsten,
Marina Zhurakhinskaya
Translations:
Petr Kovar [cz], Kris Thomsen [dk], Daniel Mustieles [es],
Ville-Pekka Vainio [fi], Yaron Shahrabani [he], Luca Ferretti [it],
Hideki Yamane [ja], Žygimantas Beručka [lt], Jovan Naumovski [mk],
Kjartan Maraas [nb], "Andreas N" [nn], Lucian Adrian Grijincu [ro],
Matej Urbančič [sl], Praveen Illa [te], Muhammet Kara [tr],
Daniel Korostil [uk], Aron Xu [zh_CN]
3.3.2
=====
* Port D-Bus usage in the shell to GDBus [Giovanni, Marc-Antoine, Florian,

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.4],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_SRCDIR([src/shell-global.c])
@ -63,14 +63,14 @@ AM_CONDITIONAL(BUILD_RECORDER, $build_recorder)
CLUTTER_MIN_VERSION=1.7.5
GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1
GJS_MIN_VERSION=1.29.18
MUTTER_MIN_VERSION=3.3.2
MUTTER_MIN_VERSION=3.3.3
FOLKS_MIN_VERSION=0.5.2
GTK_MIN_VERSION=3.0.0
GIO_MIN_VERSION=2.31.0
GTK_MIN_VERSION=3.3.9
GIO_MIN_VERSION=2.31.6
LIBECAL_MIN_VERSION=2.32.0
LIBEDATASERVER_MIN_VERSION=1.2.0
LIBEDATASERVERUI_MIN_VERSION=2.91.6
TELEPATHY_GLIB_MIN_VERSION=0.15.5
TELEPATHY_GLIB_MIN_VERSION=0.15.6
TELEPATHY_LOGGER_MIN_VERSION=0.2.4
POLKIT_MIN_VERSION=0.100
STARTUP_NOTIFICATION_MIN_VERSION=0.11
@ -84,6 +84,7 @@ PKG_CHECK_MODULES(GNOME_SHELL, gio-unix-2.0 >= $GIO_MIN_VERSION
gjs-internals-1.0 >= $GJS_MIN_VERSION
libgnome-menu-3.0 $recorder_modules
gdk-x11-3.0 libsoup-2.4
gl
clutter-x11-1.0 >= $CLUTTER_MIN_VERSION
clutter-glx-1.0 >= $CLUTTER_MIN_VERSION
libstartup-notification-1.0 >= $STARTUP_NOTIFICATION_MIN_VERSION

View File

@ -59,6 +59,8 @@ gschemas.compiled: $(gsettings_SCHEMAS:.xml=.valid)
all-local: gschemas.compiled
convertdir = $(datadir)/GConf/gsettings
convert_DATA = gnome-shell-overrides.convert
shadersdir = $(pkgdatadir)/shaders
shaders_DATA = \
@ -69,6 +71,7 @@ EXTRA_DIST = \
gnome-shell.desktop.in.in \
$(menu_DATA) \
$(shaders_DATA) \
$(convert_DATA) \
org.gnome.shell.gschema.xml.in
CLEANFILES = \

View File

@ -0,0 +1,5 @@
[org.gnome.shell.overrides]
attach-modal-dialogs = /desktop/gnome/shell/windows/attach_modal_dialogs
button-layout = /desktop/gnome/shell/windows/button_layout
edge-tiling = /desktop/gnome/shell/windows/edge_tiling
workspaces-only-on-primary = /desktop/gnome/shell/windows/workspaces_only_on_primary

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;
}
@ -717,7 +740,8 @@ StTooltip StLabel {
border-radius: 4px;
padding: 3px;
border: 1px rgba(0,0,0,0);
font-size: 7.5pt;
font-size: 8pt;
font-weight: bold;
color: white;
transition-duration: 100;
text-align: center;
@ -771,6 +795,7 @@ StTooltip StLabel {
.app-well-app.running > .overview-icon {
text-shadow: black 0px 2px 2px;
background-image: url("running-indicator.svg");
background-size: contain;
}
.contact:selected,
@ -1432,6 +1457,7 @@ StTooltip StLabel {
.summary-source-button:selected .summary-source {
background-image: url("panel-button-highlight-narrow.svg");
background-size: contain;
border-image: url("source-button-border.svg") 10 10 0 1;
}
@ -1442,6 +1468,7 @@ StTooltip StLabel {
.summary-source-button:expanded:selected {
background-image: url("panel-button-highlight-wide.svg");
background-size: contain;
border-image: url("source-button-border.svg") 10 10 0 1;
}
@ -1555,6 +1582,7 @@ StTooltip StLabel {
width: 52px;
height: 52px;
background-image: url("corner-ripple-ltr.png");
background-size: contain;
}
.ripple-box:rtl {
@ -1596,6 +1624,7 @@ StTooltip StLabel {
border: 0px;
background: rgba(255,255,255,0.5);
background-image: url("ws-switch-arrow-up.svg");
background-size: contain;
border-radius: 8px;
}
@ -1604,6 +1633,7 @@ StTooltip StLabel {
border: 0px;
background: rgba(255,255,255,0.5);
background-image: url("ws-switch-arrow-down.svg");
background-size: contain;
border-radius: 8px;
}

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

@ -205,7 +205,8 @@ const UserListItem = new Lang.Class({
// We use background-image instead of, say, St.TextureCache
// so the theme writers can add a rounded frame around the image
// and so theme writers can pick the icon size.
this._iconBin.set_style('background-image: url("' + iconFile + '");');
this._iconBin.set_style('background-image: url("' + iconFile + '");' +
'background-size: contain;');
} else {
this._iconBin.hide();
}

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,9 @@ const Dash = new Lang.Class({
this._dragPlaceholderPos = -1;
this._animatingPlaceholdersCount = 0;
this._favRemoveTarget = null;
this._showLabelTimeoutId = 0;
this._resetHoverTimeoutId = 0;
this._labelShowing = false;
this._box = new St.BoxLayout({ name: 'dash',
vertical: true,
@ -371,16 +434,50 @@ const Dash = new Lang.Class({
Lang.bind(this, function() {
display.actor.opacity = 255;
}));
display.actor.set_tooltip_text(app.get_name());
let item = new DashItemContainer();
item.setChild(display.actor);
display.icon.setIconSize(this.iconSize);
item.setLabelText(app.get_name());
display.icon.setIconSize(this.iconSize);
display.actor.connect('notify::hover',
Lang.bind(this, function() {
this._onHover(item, display)
}));
return item;
},
_onHover: function (item, display) {
if (display.actor.get_hover() && !display.isMenuUp) {
if (this._showLabelTimeoutId == 0) {
let timeout = this._labelShowing ? 0 : DASH_ITEM_HOVER_TIMEOUT;
this._showLabelTimeoutId = Mainloop.timeout_add(timeout,
Lang.bind(this, function() {
this._labelShowing = true;
item.showLabel();
return false;
}));
if (this._resetHoverTimeoutId > 0) {
Mainloop.source_remove(this._resetHoverTimeoutId);
this._resetHoverTimeoutId = 0;
}
}
} else {
if (this._showLabelTimeoutId > 0)
Mainloop.source_remove(this._showLabelTimeoutId);
this._showLabelTimeoutId = 0;
item.hideLabel();
if (this._labelShowing) {
this._resetHoverTimeoutId = Mainloop.timeout_add(DASH_ITEM_HOVER_TIMEOUT,
Lang.bind(this, function() {
this._labelShowing = false;
return false;
}));
}
}
},
_adjustIconSize: function() {
// For the icon size, we only consider children which are "proper"
// icons (i.e. ignoring drag placeholders) and which are not

View File

@ -333,7 +333,8 @@ const EndSessionDialog = new Lang.Class({
this._iconBin.child = null;
if (iconFile) {
this._iconBin.show();
this._iconBin.set_style('background-image: url("' + iconFile + '");');
this._iconBin.set_style('background-image: url("' + iconFile + '");' +
'background-size: contain;');
} else {
this._iconBin.hide();
}

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

@ -332,6 +332,7 @@ function _windowRemoved(workspace, window) {
workspace._lastRemovedWindow = null;
_queueCheckWorkspaces();
}
return false;
});
}

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(null, icon.pid, true);
if (source)
source.destroy();
}
@ -478,10 +497,12 @@ const Source = new Lang.Class({
Name: 'NotificationDaemonSource',
Extends: MessageTray.Source,
_init: function(title, pid, sender) {
_init: function(title, pid, sender, trayIcon) {
this.parent(title);
this._pid = pid;
this.initialTitle = title;
this.pid = pid;
if (sender)
this._nameWatcherId = Gio.DBus.session.watch_name(sender,
Gio.BusNameWatcherFlags.NONE,
@ -495,7 +516,12 @@ const Source = new Lang.Class({
this.title = this.app.get_name();
else
this.useNotificationIcon = true;
this._trayIcon = null;
this.trayIcon = trayIcon;
if (this.trayIcon) {
this._setSummaryIcon(this.trayIcon);
this.useNotificationIcon = false;
}
},
_onNameVanished: function() {
@ -522,7 +548,7 @@ const Source = new Lang.Class({
},
handleSummaryClick: function() {
if (!this._trayIcon)
if (!this.trayIcon)
return false;
let event = Clutter.get_current_event();
@ -543,11 +569,11 @@ const Source = new Lang.Class({
let id = global.connect('notify::stage-input-mode', Lang.bind(this,
function () {
global.disconnect(id);
this._trayIcon.click(event);
this.trayIcon.click(event);
}));
Main.overview.hide();
} else {
this._trayIcon.click(event);
this.trayIcon.click(event);
}
return true;
},
@ -556,31 +582,25 @@ const Source = new Lang.Class({
if (this.app)
return;
this.app = Shell.WindowTracker.get_default().get_app_from_pid(this._pid);
this.app = Shell.WindowTracker.get_default().get_app_from_pid(this.pid);
if (!this.app)
return;
// Only override the icon if we were previously using
// notification-based icons (ie, not a trayicon) or if it was unset before
if (!this._trayIcon) {
if (!this.trayIcon) {
this.useNotificationIcon = false;
this._setSummaryIcon(this.app.create_icon_texture (this.ICON_SIZE));
}
},
setTrayIcon: function(icon) {
this._setSummaryIcon(icon);
this.useNotificationIcon = false;
this._trayIcon = icon;
},
open: function(notification) {
this.destroyNonResidentNotifications();
this.openApp();
},
_lastNotificationRemoved: function() {
if (!this._trayIcon)
if (!this.trayIcon)
this.destroy();
},

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,56 @@ const AppMenuButton = new Lang.Class({
this._iconBox.hide();
this._label.setText('');
if (this._appMenuNotifyId)
this._targetApp.disconnect(this._appMenuNotifyId);
if (this._actionGroupNotifyId)
this._targetApp.disconnect(this._actionGroupNotifyId);
if (targetApp) {
this._appMenuNotifyId = targetApp.connect('notify::menu', Lang.bind(this, this._sync));
this._actionGroupNotifyId = targetApp.connect('notify::action-group', Lang.bind(this, this._sync));
} else {
this._appMenuNotifyId = 0;
this._actionGroupNotifyId = 0;
}
this._targetApp = targetApp;
let icon = targetApp.get_faded_icon(2 * PANEL_ICON_SIZE);
this._label.setText(targetApp.get_name());
// TODO - _quit() doesn't really work on apps in state STARTING yet
this._quitMenu.label.set_text(_("Quit %s").format(targetApp.get_name()));
this._iconBox.set_child(icon);
this._iconBox.show();
if (targetApp.get_state() == Shell.AppState.STARTING)
this.startAnimation();
else
this._maybeSetMenu();
this.emit('changed');
},
_maybeSetMenu: function() {
let menu;
if (this._targetApp.action_group && this._targetApp.menu) {
if (this.menu instanceof PopupMenu.RemoteMenu &&
this.menu.actionGroup == this._targetApp.action_group)
return;
menu = new PopupMenu.RemoteMenu(this.actor, this._targetApp.menu, this._targetApp.action_group);
} else {
if (this.menu && !(this.menu instanceof PopupMenu.RemoteMenu))
return;
// fallback to older menu
menu = new PopupMenu.PopupMenu(this.actor, 0.0, St.Side.TOP, 0);
menu.addAction(_("Quit"), Lang.bind(this, function() {
this._targetApp.request_quit();
}));
}
this.setMenu(menu);
this._menuManager.addMenu(menu);
}
});
@ -924,9 +957,8 @@ const Panel = new Lang.Class({
// more cleanly with the rest of the panel
this._menus.addMenu(this._activitiesButton.menu);
this._appMenu = new AppMenuButton();
this._appMenu = new AppMenuButton(this._menus);
this._leftBox.add(this._appMenu.actor);
this._menus.addMenu(this._appMenu.menu);
}
/* center */

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

@ -155,9 +155,12 @@ const PlacesManager = new Lang.Class({
this._connect = new PlaceInfo('special:connect', _("Connect to..."),
function (size) {
return new St.Icon({ icon_name: 'applications-internet',
icon_type: St.IconType.FULLCOLOR,
icon_size: size });
// do NOT use St.Icon here, it crashes the shell
// see wanda.js for details
return St.TextureCache.get_default().load_icon_name('applications-internet',
null,
St.IconType.FULLCOLOR,
size);
},
function (params) {
// BUG: nautilus-connect-server doesn't have a desktop file, so we can't

View File

@ -393,6 +393,7 @@ const AuthenticationAgent = new Lang.Class({
Lang.bind(this,
function() {
this._reallyCompleteRequest(wasDismissed);
return false;
}));
} else {
this._reallyCompleteRequest(wasDismissed);

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);
@ -1391,12 +1398,23 @@ const PopupMenuSection = new Lang.Class({
this.actor = this.box;
this.actor._delegate = this;
this.isOpen = true;
// an array of externally managed separators
this.separators = [];
},
// deliberately ignore any attempt to open() or close(), but emit the
// corresponding signal so children can still pick it up
open: function(animate) { this.emit('open-state-changed', true); },
close: function() { this.emit('open-state-changed', false); },
destroy: function() {
for (let i = 0; i < this.separators.length; i++)
this.separators[i].destroy();
this.separators = [];
this.parent();
}
});
const PopupSubMenuMenuItem = new Lang.Class({
@ -1692,6 +1710,268 @@ const PopupComboBoxMenuItem = new Lang.Class({
}
});
/**
* RemoteMenu:
*
* A PopupMenu that tracks a GMenuModel and shows its actions
* (exposed by GApplication/GActionGroup)
*/
const RemoteMenu = new Lang.Class({
Name: 'RemoteMenu',
Extends: PopupMenu,
_init: function(sourceActor, model, actionGroup) {
this.parent(sourceActor, 0.0, St.Side.TOP);
this.model = model;
this.actionGroup = actionGroup;
this._actions = { };
this._modelChanged(this.model, 0, 0, this.model.get_n_items(), this);
this._actionStateChangeId = this.actionGroup.connect('action-state-changed', Lang.bind(this, this._actionStateChanged));
this._actionEnableChangeId = this.actionGroup.connect('action-enabled-changed', Lang.bind(this, this._actionEnabledChanged));
},
destroy: function() {
if (this._actionStateChangeId) {
this.actionGroup.disconnect(this._actionStateChangeId);
this._actionStateChangeId = 0;
}
if (this._actionEnableChangeId) {
this.actionGroup.disconnect(this._actionEnableChangeId);
this._actionEnableChangeId = 0;
}
this.parent();
},
_createMenuItem: function(model, index) {
let labelValue = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_LABEL, null);
let label = labelValue ? labelValue.deep_unpack() : '';
// remove all underscores that are not followed by another underscore
label = label.replace(/_([^_])/, '$1');
let section_link = model.get_item_link(index, Gio.MENU_LINK_SECTION);
if (section_link) {
let item = new PopupMenuSection();
if (label) {
let title = new PopupMenuItem(label, { reactive: false,
style_class: 'popup-subtitle-menu-item' });
item._titleMenuItem = title;
title._ignored = true;
item.addMenuItem(title);
}
this._modelChanged(section_link, 0, 0, section_link.get_n_items(), item);
return [item, true, ''];
}
let submenu_link = model.get_item_link(index, Gio.MENU_LINK_SUBMENU);
if (submenu_link) {
let item = new PopupSubMenuMenuItem(label);
this._modelChanged(submenu_link, 0, 0, submenu_link.get_n_items(), item.menu);
return [item, false, ''];
}
let action_id = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_ACTION, null).deep_unpack();
if (!this.actionGroup.has_action(action_id)) {
// the action may not be there yet, wait for action-added
return [null, false, 'action-added'];
}
if (!this._actions[action_id])
this._actions[action_id] = { enabled: this.actionGroup.get_action_enabled(action_id),
state: this.actionGroup.get_action_state(action_id),
items: [ ],
};
let action = this._actions[action_id];
let item, target, destroyId, specificSignalId;
if (action.state) {
// Docs have get_state_hint(), except that the DBus protocol
// has no provision for it (so ShellApp does not implement it,
// and neither GApplication), and g_action_get_state_hint()
// always returns null
// Funny :)
switch (String.fromCharCode(action.state.classify())) {
case 'b':
item = new PopupSwitchMenuItem(label, action.state.get_boolean());
action.items.push(item);
specificSignalId = item.connect('toggled', Lang.bind(this, function(item) {
this.actionGroup.activate_action(action_id, null);
}));
break;
case 's':
item = new PopupMenuItem(label);
item._remoteTarget = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null).deep_unpack();
action.items.push(item);
item.setShowDot(action.state.deep_unpack() == item._remoteTarget);
specificSignalId = item.connect('activate', Lang.bind(this, function(item) {
this.actionGroup.activate_action(action_id, GLib.Variant.new_string(item._remoteTarget));
}));
break;
default:
log('Action "%s" has state of type %s, which is not supported'.format(action_id, action.state.get_type_string()));
return [null, false, 'action-state-changed'];
}
} else {
target = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null);
item = new PopupMenuItem(label);
action.items.push(item);
specificSignalId = item.connect('activate', Lang.bind(this, function() {
this.actionGroup.activate_action(action_id, target);
}));
}
item.actor.reactive = item.actor.can_focus = action.enabled;
if (action.enabled)
item.actor.remove_style_pseudo_class('insensitive');
else
item.actor.add_style_pseudo_class('insensitive');
destroyId = item.connect('destroy', Lang.bind(this, function() {
item.disconnect(destroyId);
item.disconnect(specificSignalId);
let pos = action.items.indexOf(item);
if (pos != -1)
action.items.splice(pos, 1);
}));
return [item, false, ''];
},
_modelChanged: function(model, position, removed, added, target) {
let j, k;
let j0, k0;
let currentItems = target._getMenuItems();
k0 = 0;
// skip ignored items at the beginning
while (k0 < currentItems.length && currentItems[k0]._ignored)
k0++;
// find the right menu item matching the model item
for (j0 = 0; j0 < position; j0++, k0++) {
if (currentItems[k0]._ignored)
k0++;
}
if (removed == -1) {
// special flag to indicate we should destroy everything
for (k = k0; k < currentItems.length; k++)
currentItems[k].destroy();
} else {
for (j = j0, k = k0; j < j0 + removed; j++, k++) {
currentItems[k].destroy();
if (currentItems[k]._ignored)
j--;
}
}
for (j = j0, k = k0; j < j0 + added; j++, k++) {
let [item, addSeparator, changeSignal] = this._createMenuItem(model, j);
if (item) {
// separators must be added in the parent to make autohiding work
if (addSeparator) {
let separator = new PopupSeparatorMenuItem();
item.separators.push(separator);
separator._ignored = true;
target.addMenuItem(separator, k+1);
k++;
}
target.addMenuItem(item, k);
if (addSeparator) {
let separator = new PopupSeparatorMenuItem();
item.separators.push(separator);
separator._ignored = true;
target.addMenuItem(separator, k+1);
k++;
}
} else if (changeSignal) {
let signalId = this.actionGroup.connect(changeSignal, Lang.bind(this, function() {
this.actionGroup.disconnect(signalId);
// force a full update
this._modelChanged(model, 0, -1, model.get_n_items(), target);
}));
}
}
if (!model._changedId) {
model._changedId = model.connect('items-changed', Lang.bind(this, this._modelChanged, target));
model._destroyId = target.connect('destroy', function() {
if (model._changedId)
model.disconnect(model._changedId);
if (model._destroyId)
target.disconnect(model._destroyId);
model._changedId = 0;
model._destroyId = 0;
});
}
if (target instanceof PopupMenuSection) {
if (target._titleMenuItem)
target.actor.visible = target.numMenuItems != 1;
else
target.actor.visible = target.numMenuItems != 0;
} else {
let sourceItem = target.sourceActor._delegate;
if (sourceItem instanceof PopupSubMenuMenuItem)
sourceItem.actor.visible = target.numMenuItems != 0;
}
},
_actionStateChanged: function(actionGroup, action_id) {
let action = this._actions[action_id];
if (!action)
return;
action.state = actionGroup.get_action_state(action_id);
if (action.items.length) {
switch (String.fromCharCode(action.state.classify())) {
case 'b':
for (let i = 0; i < action.items.length; i++)
action.items[i].setToggleState(action.state.get_boolean());
break;
case 'd':
for (let i = 0; i < action.items.length; i++)
action.items[i].setValue(action.state.get_double());
break;
case 's':
for (let i = 0; i < action.items.length; i++)
action.items[i].setShowDot(action.items[i]._remoteTarget == action.state.deep_unpack());
}
}
},
_actionEnabledChanged: function(actionGroup, action_id) {
let action = this._actions[action_id];
if (!action)
return;
action.enabled = actionGroup.get_action_enabled(action_id);
if (action.items.length) {
for (let i = 0; i < action.items.length; i++) {
let item = action.items[i];
item.actor.reactive = item.actor.can_focus = action.enabled;
if (action.enabled)
item.actor.remove_style_pseudo_class('insensitive');
else
item.actor.add_style_pseudo_class('insensitive');
}
}
}
});
/* Basic implementation of a menu manager.
* Call addMenu to add menus
*/

View File

@ -122,8 +122,13 @@ const GnomeShell = new Lang.Class({
* indicating whether the operation was successful or not.
*
*/
ScreenshotAreaAsync : function (x, y, width, height, filename, callback) {
global.screenshot_area (x, y, width, height, filename, function (obj, result) { callback(result); });
ScreenshotAreaAsync : function (params, invocation) {
let [x, y, width, height, filename, callback] = params;
global.screenshot_area (x, y, width, height, filename,
function (obj, result) {
let retval = GLib.Variant.new('(b)', [result]);
invocation.return_value(retval);
});
},
/**
@ -149,8 +154,13 @@ const GnomeShell = new Lang.Class({
* indicating whether the operation was successful or not.
*
*/
ScreenshotAsync : function (filename, callback) {
global.screenshot(filename, function (obj, result) { callback(result); });
ScreenshotAsync : function (params, invocation) {
let [filename] = params;
global.screenshot(filename,
function (obj, result) {
let retval = GLib.Variant.new('(b)', [result]);
invocation.return_value(retval);
});
},
ListExtensions: function() {

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);
}
@ -1803,6 +1803,7 @@ const NMApplet = new Lang.Class({
let activating = null;
let default_ip4 = null;
let default_ip6 = null;
let active_vpn = null;
for (let i = 0; i < this._activeConnections.length; i++) {
let a = this._activeConnections[i];
@ -1832,6 +1833,8 @@ const NMApplet = new Lang.Class({
default_ip4 = a;
if (a.default6)
default_ip6 = a;
if (a._type == 'vpn')
active_vpn = a;
if (a.state == NetworkManager.ActiveConnectionState.ACTIVATING)
activating = a;
@ -1862,7 +1865,7 @@ const NMApplet = new Lang.Class({
}
}
this._mainConnection = activating || default_ip4 || default_ip6 || this._activeConnections[0] || null;
this._mainConnection = activating || active_vpn || default_ip4 || default_ip6 || this._activeConnections[0] || null;
},
_notifyActivated: function(activeConnection) {

View File

@ -33,10 +33,6 @@ const NotificationDirection = {
RECEIVED: 'chat-received'
};
let contactFeatures = [Tp.ContactFeature.ALIAS,
Tp.ContactFeature.AVATAR_DATA,
Tp.ContactFeature.PRESENCE];
// This is GNOME Shell's implementation of the Telepathy 'Client'
// interface. Specifically, the shell is a Telepathy 'Observer', which
// lets us see messages even if they belong to another app (eg,
@ -83,11 +79,21 @@ const Client = new Lang.Class({
// account path -> AccountNotification
this._accountNotifications = {};
// Define features we want
this._accountManager = Tp.AccountManager.dup();
let factory = this._accountManager.get_factory();
factory.add_account_features([Tp.Account.get_feature_quark_connection()]);
factory.add_connection_features([Tp.Connection.get_feature_quark_contact_list()]);
factory.add_channel_features([Tp.Channel.get_feature_quark_contacts()]);
factory.add_contact_features([Tp.ContactFeature.ALIAS,
Tp.ContactFeature.AVATAR_DATA,
Tp.ContactFeature.PRESENCE,
Tp.ContactFeature.SUBSCRIPTION_STATES]);
// Set up a SimpleObserver, which will call _observeChannels whenever a
// channel matching its filters is detected.
// The second argument, recover, means _observeChannels will be run
// for any existing channel as well.
this._accountManager = Tp.AccountManager.dup();
this._tpClient = new Shell.TpClient({ 'account-manager': this._accountManager,
'name': 'GnomeShell',
'uniquify-name': true })
@ -114,16 +120,9 @@ const Client = new Lang.Class({
throw new Error('Couldn\'t register Telepathy client. Error: \n' + e);
}
// Watch subscription requests and connection errors
this._subscriptionSource = null;
this._accountSource = null;
let factory = this._accountManager.get_factory();
factory.add_account_features([Tp.Account.get_feature_quark_connection()]);
factory.add_connection_features([Tp.Connection.get_feature_quark_contact_list()]);
factory.add_contact_features([Tp.ContactFeature.SUBSCRIPTION_STATES,
Tp.ContactFeature.ALIAS,
Tp.ContactFeature.AVATAR_DATA]);
this._accountManager.connect('account-validity-changed',
Lang.bind(this, this._accountValidityChanged));
@ -133,22 +132,6 @@ const Client = new Lang.Class({
_observeChannels: function(observer, account, conn, channels,
dispatchOp, requests, context) {
// If the self_contact doesn't have the ALIAS, make sure
// to fetch it before trying to grab the channels.
let self_contact = conn.get_self_contact();
if (self_contact.has_feature(Tp.ContactFeature.ALIAS)) {
this._finishObserveChannels(account, conn, channels, context);
} else {
Shell.get_self_contact_features(conn,
contactFeatures,
Lang.bind(this, function() {
this._finishObserveChannels(account, conn, channels, context);
}));
context.delay();
}
},
_finishObserveChannels: function(account, conn, channels, context) {
let len = channels.length;
for (let i = 0; i < len; i++) {
let channel = channels[i];
@ -159,16 +142,7 @@ const Client = new Lang.Class({
targetHandleType != Tp.HandleType.CONTACT)
continue;
/* Request a TpContact */
Shell.get_tp_contacts(conn, [targetHandle],
contactFeatures,
Lang.bind(this, function (connection, contacts, failed) {
if (contacts.length < 1)
return;
/* We got the TpContact */
this._createChatSource(account, conn, channel, contacts[0]);
}), null);
this._createChatSource(account, conn, channel, channel.get_target_contact());
}
context.accept();
@ -197,11 +171,11 @@ const Client = new Lang.Class({
_handleChannels: function(handler, account, conn, channels,
requests, user_action_time, context) {
this._handlingChannels(account, conn, channels);
this._handlingChannels(account, conn, channels, true);
context.accept();
},
_handlingChannels: function(account, conn, channels) {
_handlingChannels: function(account, conn, channels, notify) {
let len = channels.length;
for (let i = 0; i < len; i++) {
let channel = channels[i];
@ -212,7 +186,18 @@ const Client = new Lang.Class({
continue;
}
if (this._tpClient.is_handling_channel(channel)) {
// 'notify' will be true when coming from an actual HandleChannels
// call, and not when from a successful Claim call. The point is
// we don't want to notify for a channel we just claimed which
// has no new messages (for example, a new channel which only has
// a delivery notification). We rely on _displayPendingMessages()
// and _messageReceived() to notify for new messages.
// But we should still notify from HandleChannels because the
// Telepathy spec states that handlers must foreground channels
// in HandleChannels calls which are already being handled.
if (notify && this._tpClient.is_handling_channel(channel)) {
// We are already handling the channel, display the source
let source = this._chatSources[channel.get_object_path()];
if (source)
@ -223,41 +208,25 @@ const Client = new Lang.Class({
_displayRoomInvitation: function(conn, channel, dispatchOp, context) {
// We can only approve the rooms if we have been invited to it
let selfHandle = channel.group_get_self_handle();
if (selfHandle == 0) {
let selfContact = channel.group_get_self_contact();
if (selfContact == null) {
Shell.decline_dispatch_op(context, 'Not invited to the room');
return;
}
let [invited, inviter, reason, msg] = channel.group_get_local_pending_info(selfHandle);
let [invited, inviter, reason, msg] = channel.group_get_local_pending_contact_info(selfContact);
if (!invited) {
Shell.decline_dispatch_op(context, 'Not invited to the room');
return;
}
// Request a TpContact for the inviter
Shell.get_tp_contacts(conn, [inviter],
contactFeatures,
Lang.bind(this, this._createRoomInviteSource, channel, context, dispatchOp));
context.delay();
},
_createRoomInviteSource: function(connection, contacts, failed, channel, context, dispatchOp) {
if (contacts.length < 1) {
Shell.decline_dispatch_op(context, 'Failed to get inviter');
return;
}
// We got the TpContact
// FIXME: We don't have a 'chat room' icon (bgo #653737) use
// system-users for now as Empathy does.
let source = new ApproverSource(dispatchOp, _("Invitation"),
Gio.icon_new_for_string('system-users'));
Main.messageTray.add(source);
let notif = new RoomInviteNotification(source, dispatchOp, channel, contacts[0]);
let notif = new RoomInviteNotification(source, dispatchOp, channel, inviter);
source.notify(notif);
context.accept();
},
@ -285,7 +254,7 @@ const Client = new Lang.Class({
Lang.bind(this, function(dispatchOp, result) {
try {
dispatchOp.claim_with_finish(result);
this._handlingChannels(account, conn, [channel]);
this._handlingChannels(account, conn, [channel], false);
} catch (err) {
throw new Error('Failed to Claim channel: ' + err);
}}));
@ -297,21 +266,6 @@ const Client = new Lang.Class({
},
_approveCall: function(account, conn, channel, dispatchOp, context) {
let [targetHandle, targetHandleType] = channel.get_handle();
Shell.get_tp_contacts(conn, [targetHandle],
contactFeatures,
Lang.bind(this, this._createAudioVideoSource, channel, context, dispatchOp));
context.delay();
},
_createAudioVideoSource: function(connection, contacts, failed, channel, context, dispatchOp) {
if (contacts.length < 1) {
Shell.decline_dispatch_op(context, 'Failed to get inviter');
return;
}
let isVideo = false;
let props = channel.borrow_immutable_properties();
@ -326,27 +280,13 @@ const Client = new Lang.Class({
Gio.icon_new_for_string('audio-input-microphone'));
Main.messageTray.add(source);
let notif = new AudioVideoNotification(source, dispatchOp, channel, contacts[0], isVideo);
let notif = new AudioVideoNotification(source, dispatchOp, channel,
channel.get_target_contact(), isVideo);
source.notify(notif);
context.accept();
},
_approveFileTransfer: function(account, conn, channel, dispatchOp, context) {
let [targetHandle, targetHandleType] = channel.get_handle();
Shell.get_tp_contacts(conn, [targetHandle],
contactFeatures,
Lang.bind(this, this._createFileTransferSource, channel, context, dispatchOp));
context.delay();
},
_createFileTransferSource: function(connection, contacts, failed, channel, context, dispatchOp) {
if (contacts.length < 1) {
Shell.decline_dispatch_op(context, 'Failed to get file sender');
return;
}
// Use the icon of the file being transferred
let gicon = Gio.content_type_get_icon(channel.get_mime_type());
@ -354,7 +294,8 @@ const Client = new Lang.Class({
let source = new ApproverSource(dispatchOp, _("File Transfer"), gicon);
Main.messageTray.add(source);
let notif = new FileTransferNotification(source, dispatchOp, channel, contacts[0]);
let notif = new FileTransferNotification(source, dispatchOp, channel,
channel.get_target_contact());
source.notify(notif);
context.accept();
},
@ -502,15 +443,9 @@ const ChatSource = new Lang.Class({
this._notification.setUrgency(MessageTray.Urgency.HIGH);
this._notifyTimeoutId = 0;
// We ack messages when the message box is collapsed if user has
// interacted with it before and so read the messages:
// - user clicked on it the tray
// - user expanded the notification by hovering over the toaster notification
this._shouldAck = false;
this.connect('summary-item-clicked', Lang.bind(this, this._summaryItemClicked));
this._notification.connect('expanded', Lang.bind(this, this._notificationExpanded));
this._notification.connect('collapsed', Lang.bind(this, this._notificationCollapsed));
// We ack messages when the user expands the new notification or views the summary
// notification, in which case the notification is also expanded.
this._notification.connect('expanded', Lang.bind(this, this._ackMessages));
this._presence = contact.get_presence_type();
@ -734,12 +669,12 @@ const ChatSource = new Lang.Class({
if (presence == Tp.ConnectionPresenceType.AVAILABLE) {
msg = _("%s is online.").format(title);
shouldNotify = (this._presence == Tp.ConnectionPresenceType.OFFLINE);
} else if (presence == Tp.ConnectionPresenceType.OFFLINE ||
presence == Tp.ConnectionPresenceType.EXTENDED_AWAY) {
} else if (presence == Tp.ConnectionPresenceType.OFFLINE) {
presence = Tp.ConnectionPresenceType.OFFLINE;
msg = _("%s is offline.").format(title);
shouldNotify = (this._presence != Tp.ConnectionPresenceType.OFFLINE);
} else if (presence == Tp.ConnectionPresenceType.AWAY) {
} else if (presence == Tp.ConnectionPresenceType.AWAY ||
presence == Tp.ConnectionPresenceType.EXTENDED_AWAY) {
msg = _("%s is away.").format(title);
shouldNotify = false;
} else if (presence == Tp.ConnectionPresenceType.BUSY) {
@ -774,24 +709,6 @@ const ChatSource = new Lang.Class({
// 'pending-message-removed' for each one.
this._channel.ack_all_pending_messages_async(Lang.bind(this, function(src, result) {
this._channel.ack_all_pending_messages_finish(result);}));
},
_summaryItemClicked: function(source, button) {
if (button != 1)
return;
this._shouldAck = true;
},
_notificationExpanded: function() {
this._shouldAck = true;
},
_notificationCollapsed: function() {
if (this._shouldAck)
this._ackMessages();
this._shouldAck = false;
}
});

View File

@ -239,7 +239,8 @@ const IMStatusChooserItem = new Lang.Class({
},
_setIconFromFile: function(iconFile) {
this._iconBin.set_style('background-image: url("' + iconFile + '");');
this._iconBin.set_style('background-image: url("' + iconFile + '");' +
'background-size: contain;');
this._iconBin.child = null;
},

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

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

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;
@ -149,6 +154,8 @@ const WorkspaceThumbnail = new Lang.Class({
this.metaWorkspace = metaWorkspace;
this.monitorIndex = Main.layoutManager.primaryIndex;
this._removed = false;
this.actor = new St.Group({ reactive: true,
clip_to_allocation: true,
style_class: 'workspace-thumbnail' });
@ -174,17 +181,21 @@ const WorkspaceThumbnail = new Lang.Class({
let monitor = Main.layoutManager.primaryMonitor;
this.setPorthole(monitor.x, monitor.y, monitor.width, monitor.height);
let windows = global.get_window_actors().filter(this._isMyWindow, this);
let windows = global.get_window_actors().filter(this._isWorkspaceWindow, this);
// Create clones for windows that should be visible in the Overview
this._windows = [];
this._allWindows = [];
this._minimizedChangedIds = [];
for (let i = 0; i < windows.length; i++) {
windows[i].meta_window._minimizedChangedId =
let minimizedChangedId =
windows[i].meta_window.connect('notify::minimized',
Lang.bind(this,
this._updateMinimized));
this._allWindows.push(windows[i].meta_window);
this._minimizedChangedIds.push(minimizedChangedId);
if (this._isOverviewWindow(windows[i])) {
if (this._isMyWindow(windows[i]) && this._isOverviewWindow(windows[i])) {
this._addWindowClone(windows[i]);
}
}
@ -269,17 +280,11 @@ const WorkspaceThumbnail = new Lang.Class({
let clone = this._windows[index];
this._windows.splice(index, 1);
if (win && this._isOverviewWindow(win)) {
if (metaWin._minimizedChangedId) {
metaWin.disconnect(metaWin._minimizedChangedId);
delete metaWin._minimizedChangedId;
}
}
clone.destroy();
},
_doAddWindow : function(metaWin) {
if (this.leavingOverview)
if (this._removed)
return;
let win = metaWin.get_compositor_private();
@ -289,7 +294,7 @@ const WorkspaceThumbnail = new Lang.Class({
// the compositor finds out about them...
Mainloop.idle_add(Lang.bind(this,
function () {
if (this.actor &&
if (!this._removed &&
metaWin.get_compositor_private() &&
metaWin.get_workspace() == this.metaWorkspace)
this._doAddWindow(metaWin);
@ -298,16 +303,19 @@ const WorkspaceThumbnail = new Lang.Class({
return;
}
if (this._allWindows.indexOf(metaWin) == -1) {
let minimizedChangedId = metaWin.connect('notify::minimized',
Lang.bind(this,
this._updateMinimized));
this._allWindows.push(metaWin);
this._minimizedChangedIds.push(minimizedChangedId);
}
// We might have the window in our list already if it was on all workspaces and
// now was moved to this workspace
if (this._lookupIndex (metaWin) != -1)
return;
if (!metaWin._minimizedChangedId)
metaWin._minimizedChangedId = metaWin.connect('notify::minimized',
Lang.bind(this,
this._updateMinimized));
if (!this._isMyWindow(win) || !this._isOverviewWindow(win))
return;
@ -319,6 +327,13 @@ const WorkspaceThumbnail = new Lang.Class({
},
_windowRemoved : function(metaWorkspace, metaWin) {
let index = this._allWindows.indexOf(metaWin);
if (index != -1) {
metaWin.disconnect(this._minimizedChangedIds[index]);
this._allWindows.splice(index, 1);
this._minimizedChangedIds.splice(index, 1);
}
this._doRemoveWindow(metaWin);
},
@ -345,27 +360,36 @@ const WorkspaceThumbnail = new Lang.Class({
this.actor.destroy();
},
_onDestroy: function(actor) {
workspaceRemoved : function() {
if (this._removed)
return;
this._removed = true;
this.metaWorkspace.disconnect(this._windowAddedId);
this.metaWorkspace.disconnect(this._windowRemovedId);
global.screen.disconnect(this._windowEnteredMonitorId);
global.screen.disconnect(this._windowLeftMonitorId);
for (let i = 0; i < this._windows.length; i++) {
let metaWin = this._windows[i].metaWindow;
if (metaWin._minimizedChangedId) {
metaWin.disconnect(metaWin._minimizedChangedId);
delete metaWin._minimizedChangedId;
}
}
for (let i = 0; i < this._allWindows.length; i++)
this._allWindows[i].disconnect(this._minimizedChangedIds[i]);
},
_onDestroy: function(actor) {
this.workspaceRemoved();
this._windows = [];
this.actor = null;
},
// Tests if @win belongs to this workspace
_isWorkspaceWindow : function (win) {
return Main.isWindowActorDisplayedOnWorkspace(win, this.metaWorkspace.index());
},
// Tests if @win belongs to this workspace and monitor
_isMyWindow : function (win) {
return Main.isWindowActorDisplayedOnWorkspace(win, this.metaWorkspace.index()) &&
return this._isWorkspaceWindow(win) &&
(!win.get_meta_window() || win.get_meta_window().get_monitor() == this.monitorIndex);
},
@ -386,6 +410,10 @@ const WorkspaceThumbnail = new Lang.Class({
Lang.bind(this, function(clone) {
Main.overview.beginWindowDrag();
}));
clone.connect('drag-cancelled',
Lang.bind(this, function(clone) {
Main.overview.cancelledWindowDrag();
}));
clone.connect('drag-end',
Lang.bind(this, function(clone) {
Main.overview.endWindowDrag();
@ -519,6 +547,59 @@ const ThumbnailsBox = new Lang.Class({
this._stateCounts[ThumbnailState[key]] = 0;
this._thumbnails = [];
Main.overview.connect('item-drag-begin',
Lang.bind(this, this._onDragBegin));
Main.overview.connect('item-drag-end',
Lang.bind(this, this._onDragEnd));
Main.overview.connect('item-drag-cancelled',
Lang.bind(this, this._onDragCancelled));
Main.overview.connect('window-drag-begin',
Lang.bind(this, this._onDragBegin));
Main.overview.connect('window-drag-end',
Lang.bind(this, this._onDragEnd));
Main.overview.connect('window-drag-cancelled',
Lang.bind(this, this._onDragCancelled));
},
_onDragBegin: function() {
this._dragCancelled = false;
this._dragMonitor = {
dragMotion: Lang.bind(this, this._onDragMotion)
};
DND.addDragMonitor(this._dragMonitor);
},
_onDragEnd: function() {
if (this._dragCancelled)
return;
this._endDrag();
},
_onDragCancelled: function() {
this._dragCancelled = true;
this._endDrag();
},
_endDrag: function() {
this._clearDragPlaceholder();
DND.removeDragMonitor(this._dragMonitor);
},
_onDragMotion: function(dragEvent) {
if (!this.actor.contains(dragEvent.targetActor))
this._onLeave();
return DND.DragMotionResult.CONTINUE;
},
_onLeave: function() {
this._clearDragPlaceholder();
},
_clearDragPlaceholder: function() {
this._dropPlaceholderPos = -1;
this.actor.queue_relayout();
},
// Draggable target interface
@ -670,8 +751,10 @@ const ThumbnailsBox = new Lang.Class({
if (thumbnail.state > ThumbnailState.NORMAL)
continue;
if (currentPos >= start && currentPos < start + count)
if (currentPos >= start && currentPos < start + count) {
thumbnail.workspaceRemoved();
this._setThumbnailState(thumbnail, ThumbnailState.REMOVING);
}
currentPos++;
}

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

706
po/be.po

File diff suppressed because it is too large Load Diff

664
po/bg.po

File diff suppressed because it is too large Load Diff

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

778
po/fa.po

File diff suppressed because it is too large Load Diff

905
po/fi.po

File diff suppressed because it is too large Load Diff

792
po/gl.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

757
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

815
po/ru.po

File diff suppressed because it is too large Load Diff

720
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

740
po/vi.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

@ -114,10 +114,7 @@ sniff_async_ready_cb (GObject *source,
{
InvocationData *data = user_data;
gchar **types;
gint idx;
GError *error = NULL;
GVariantBuilder *builder;
GVariant *result;
types = shell_mime_sniffer_sniff_finish (SHELL_MIME_SNIFFER (source),
res, &error);
@ -129,16 +126,8 @@ sniff_async_ready_cb (GObject *source,
goto out;
}
builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
for (idx = 0; types[idx] != NULL; idx++)
g_variant_builder_add (builder, "s", types[idx]);
result = g_variant_new ("(as)", builder);
g_dbus_method_invocation_return_value (data->invocation, result);
g_variant_unref (result);
g_variant_builder_unref (builder);
g_dbus_method_invocation_return_value (data->invocation,
g_variant_new ("(^as)", types));
g_strfreev (types);
out:

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,10 @@ typedef struct {
/* Whether or not we need to resort the windows; this is done on demand */
gboolean window_sort_stale : 1;
/* See GApplication documentation */
GDBusMenuModel *remote_menu;
GActionMuxer *muxer;
} ShellAppRunningState;
/**
@ -72,11 +77,13 @@ struct _ShellApp
char *casefolded_exec;
};
G_DEFINE_TYPE (ShellApp, shell_app, G_TYPE_OBJECT);
enum {
PROP_0,
PROP_STATE
PROP_STATE,
PROP_ID,
PROP_DBUS_ID,
PROP_ACTION_GROUP,
PROP_MENU
};
enum {
@ -87,8 +94,11 @@ enum {
static guint shell_app_signals[LAST_SIGNAL] = { 0 };
static void create_running_state (ShellApp *app);
static void setup_running_state (ShellApp *app, MetaWindow *window);
static void unref_running_state (ShellAppRunningState *state);
G_DEFINE_TYPE (ShellApp, shell_app, G_TYPE_OBJECT)
static void
shell_app_get_property (GObject *gobject,
guint prop_id,
@ -102,6 +112,17 @@ shell_app_get_property (GObject *gobject,
case PROP_STATE:
g_value_set_enum (value, app->state);
break;
case PROP_ID:
g_value_set_string (value, shell_app_get_id (app));
break;
case PROP_ACTION_GROUP:
if (app->running_state)
g_value_set_object (value, app->running_state->muxer);
break;
case PROP_MENU:
if (app->running_state)
g_value_set_object (value, app->running_state->remote_menu);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
@ -513,6 +534,34 @@ shell_app_activate_window (ShellApp *app,
}
}
void
shell_app_update_window_actions (ShellApp *app, MetaWindow *window)
{
const char *object_path;
object_path = meta_window_get_gtk_window_object_path (window);
if (object_path != NULL)
{
GActionGroup *actions;
actions = g_object_get_data (G_OBJECT (window), "actions");
if (actions == NULL)
{
actions = G_ACTION_GROUP (g_dbus_action_group_get (g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL),
meta_window_get_gtk_unique_bus_name (window),
object_path));
g_object_set_data_full (G_OBJECT (window), "actions", actions, g_object_unref);
}
if (!app->running_state->muxer)
app->running_state->muxer = g_action_muxer_new ();
g_action_muxer_insert (app->running_state->muxer, "win", actions);
g_object_notify (G_OBJECT (app), "action-group");
}
}
/**
* shell_app_activate:
* @app: a #ShellApp
@ -920,6 +969,8 @@ _shell_app_add_window (ShellApp *app,
g_signal_connect (window, "unmanaged", G_CALLBACK(shell_app_on_unmanaged), app);
g_signal_connect (window, "notify::user-time", G_CALLBACK(shell_app_on_user_time_changed), app);
setup_running_state (app, window);
if (app->state != SHELL_APP_STATE_STARTING)
shell_app_state_transition (app, SHELL_APP_STATE_RUNNING);
@ -1160,6 +1211,47 @@ create_running_state (ShellApp *app)
app->running_state->refcount = 1;
app->running_state->workspace_switch_id =
g_signal_connect (screen, "workspace-switched", G_CALLBACK(shell_app_on_ws_switch), app);
app->running_state->muxer = g_action_muxer_new ();
}
static void
setup_running_state (ShellApp *app,
MetaWindow *window)
{
/* We assume that 'gtk-unique-bus-name', gtk-application-object-path'
* and 'gtk-app-menu-object-path' are the same for all windows which
* have it set.
*
* It could be possible, however, that the first window we see
* belonging to the app didn't have them set. For this reason, we
* take the values from the first window that has them set and ignore
* all the rest (until the app is stopped and restarted).
*/
if (app->running_state->remote_menu == NULL)
{
const gchar *application_object_path;
const gchar *app_menu_object_path;
const gchar *unique_bus_name;
GDBusConnection *session;
GDBusActionGroup *actions;
application_object_path = meta_window_get_gtk_application_object_path (window);
app_menu_object_path = meta_window_get_gtk_app_menu_object_path (window);
unique_bus_name = meta_window_get_gtk_unique_bus_name (window);
if (application_object_path == NULL || app_menu_object_path == NULL || unique_bus_name == NULL)
return;
session = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
g_assert (session != NULL);
app->running_state->remote_menu = g_dbus_menu_model_get (session, unique_bus_name, app_menu_object_path);
actions = g_dbus_action_group_get (session, unique_bus_name, application_object_path);
g_action_muxer_insert (app->running_state->muxer, "app", G_ACTION_GROUP (actions));
g_object_unref (actions);
g_object_unref (session);
}
}
static void
@ -1167,13 +1259,18 @@ unref_running_state (ShellAppRunningState *state)
{
MetaScreen *screen;
g_assert (state->refcount > 0);
state->refcount--;
if (state->refcount > 0)
return;
screen = shell_global_get_screen (shell_global_get ());
g_signal_handler_disconnect (screen, state->workspace_switch_id);
g_clear_object (&state->remote_menu);
g_clear_object (&state->muxer);
g_slice_free (ShellAppRunningState, state);
}
@ -1349,6 +1446,9 @@ shell_app_dispose (GObject *object)
while (app->running_state->windows)
_shell_app_remove_window (app, app->running_state->windows->data);
}
/* We should have been transitioned when we removed all of our windows */
g_assert (app->state == SHELL_APP_STATE_STOPPED);
g_assert (app->running_state == NULL);
G_OBJECT_CLASS(shell_app_parent_class)->dispose (object);
}
@ -1399,4 +1499,46 @@ shell_app_class_init(ShellAppClass *klass)
SHELL_TYPE_APP_STATE,
SHELL_APP_STATE_STOPPED,
G_PARAM_READABLE));
/**
* ShellApp:id:
*
* The id of this application (a desktop filename, or a special string
* like window:0xabcd1234)
*/
g_object_class_install_property (gobject_class,
PROP_ID,
g_param_spec_string ("id",
"Application id",
"The desktop file id of this ShellApp",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
/**
* ShellApp:action-group:
*
* The #GDBusActionGroup associated with this ShellApp, if any. See the
* documentation of #GApplication and #GActionGroup for details.
*/
g_object_class_install_property (gobject_class,
PROP_ACTION_GROUP,
g_param_spec_object ("action-group",
"Application Action Group",
"The action group exported by the remote application",
G_TYPE_ACTION_GROUP,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
/**
* ShellApp:menu:
*
* The #GMenuProxy associated with this ShellApp, if any. See the
* documentation of #GMenuModel for details.
*/
g_object_class_install_property (gobject_class,
PROP_MENU,
g_param_spec_object ("menu",
"Application Menu",
"The primary menu exported by the remote application",
G_TYPE_MENU_MODEL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}

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,6 +37,7 @@ typedef enum {
GType shell_app_get_type (void) G_GNUC_CONST;
const char *shell_app_get_id (ShellApp *app);
GMenuTreeEntry *shell_app_get_tree_entry (ShellApp *app);
GDesktopAppInfo *shell_app_get_app_info (ShellApp *app);
@ -79,6 +81,8 @@ int shell_app_compare_by_name (ShellApp *app, ShellApp *other);
int shell_app_compare (ShellApp *app, ShellApp *other);
void shell_app_update_window_actions (ShellApp *app, MetaWindow *window);
G_END_DECLS
#endif /* __SHELL_APP_H__ */

View File

@ -84,7 +84,8 @@ shell_embedded_window_hide (GtkWidget *widget)
{
ShellEmbeddedWindow *window = SHELL_EMBEDDED_WINDOW (widget);
clutter_actor_queue_relayout (CLUTTER_ACTOR (window->priv->actor));
if (window->priv->actor)
clutter_actor_queue_relayout (CLUTTER_ACTOR (window->priv->actor));
GTK_WIDGET_CLASS (shell_embedded_window_parent_class)->hide (widget);
}

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

@ -340,104 +340,6 @@ shell_tp_client_grab_contact_list_changed (ShellTpClient *self,
/* Telepathy utility functions */
/**
* ShellGetTpContactCb:
* @connection: The connection
* @contacts: (element-type TelepathyGLib.Contact): List of contacts
* @failed: Array of failed contacts
*/
static void
shell_global_get_tp_contacts_cb (TpConnection *self,
guint n_contacts,
TpContact * const *contacts,
guint n_failed,
const TpHandle *failed,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
int i;
GList *contact_list = NULL;
for (i = 0; i < n_contacts; i++) {
contact_list = g_list_append(contact_list, contacts[i]);
}
TpHandle *failed_list = g_new0 (TpHandle, n_failed + 1);
memcpy(failed_list, failed, n_failed);
((ShellGetTpContactCb)user_data)(self, contact_list, failed_list);
}
/**
* shell_get_tp_contacts:
* @self: A connection, which must be ready
* @n_handles: Number of handles in handles
* @handles: (array length=n_handles) (element-type uint): Array of handles
* @n_features: Number of features in features
* @features: (array length=n_features) (allow-none) (element-type uint):
* Array of features
* @callback: (scope async): User callback to run when the contacts are ready
*
* Wrap tp_connection_get_contacts_by_handle so we can transform the array
* into a null-terminated one, which gjs can handle.
* We send the original callback to tp_connection_get_contacts_by_handle as
* user_data, and we have our own function as callback, which does the
* transforming.
*/
void
shell_get_tp_contacts (TpConnection *self,
guint n_handles,
const TpHandle *handles,
guint n_features,
const TpContactFeature *features,
ShellGetTpContactCb callback)
{
tp_connection_get_contacts_by_handle(self, n_handles, handles,
n_features, features,
shell_global_get_tp_contacts_cb,
callback, NULL, NULL);
}
static void
shell_global_get_self_contact_features_cb (TpConnection *connection,
guint n_contacts,
TpContact * const *contacts,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
if (error != NULL) {
g_print ("Failed to upgrade self contact: %s", error->message);
return;
}
((ShellGetSelfContactFeaturesCb)user_data)(connection, *contacts);
}
/**
* shell_get_self_contact_features:
* @self: A connection, which must be ready
* @n_features: Number of features in features
* @features: (array length=n_features) (allow-none) (element-type uint):
* Array of features
* @callback: (scope async): User callback to run when the contact is ready
*
* Wrap tp_connection_upgrade_contacts due to the lack of support for
* proper arrays arguments in GJS.
*/
void
shell_get_self_contact_features (TpConnection *self,
guint n_features,
const TpContactFeature *features,
ShellGetSelfContactFeaturesCb callback)
{
TpContact *self_contact = tp_connection_get_self_contact (self);
tp_connection_upgrade_contacts (self, 1, &self_contact,
n_features, features,
shell_global_get_self_contact_features_cb,
callback, NULL, NULL);
}
/**
* shell_get_contact_events:

View File

@ -102,24 +102,6 @@ void shell_tp_client_grab_contact_list_changed (ShellTpClient *self,
TpConnection *conn);
/* Telepathy utility functions */
typedef void (*ShellGetTpContactCb) (TpConnection *connection,
GList *contacts,
TpHandle *failed);
void shell_get_tp_contacts (TpConnection *self,
guint n_handles,
const TpHandle *handles,
guint n_features,
const TpContactFeature *features,
ShellGetTpContactCb callback);
typedef void (*ShellGetSelfContactFeaturesCb) (TpConnection *connection,
TpContact *contact);
void shell_get_self_contact_features (TpConnection *self,
guint n_features,
const TpContactFeature *features,
ShellGetSelfContactFeaturesCb callback);
void shell_get_contact_events (TplLogManager *log_manager,
TpAccount *account,

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;
}
@ -1314,6 +1306,8 @@ st_theme_node_render_resources (StThemeNode *node,
StShadow *background_image_shadow_spec;
const char *background_image;
g_return_if_fail (width > 0 && height > 0);
texture_cache = st_texture_cache_get_default ();
/* FIXME - need to separate this into things that need to be recomputed on
@ -1417,9 +1411,11 @@ st_theme_node_render_resources (StThemeNode *node,
else if (node->background_color.alpha > 0 || has_border)
{
CoglHandle buffer, offscreen;
int texture_width = ceil (width);
int texture_height = ceil (height);
buffer = cogl_texture_new_with_size (width,
height,
buffer = cogl_texture_new_with_size (texture_width,
texture_height,
COGL_TEXTURE_NO_SLICING,
COGL_PIXEL_FORMAT_ANY);
offscreen = cogl_offscreen_new_to_texture (buffer);
@ -1427,9 +1423,14 @@ st_theme_node_render_resources (StThemeNode *node,
if (offscreen != COGL_INVALID_HANDLE)
{
ClutterActorBox box = { 0, 0, width, height };
CoglColor clear_color;
cogl_push_framebuffer (offscreen);
cogl_ortho (0, width, height, 0, 0, 1.0);
cogl_color_set_from_4ub (&clear_color, 0, 0, 0, 0);
cogl_clear (&clear_color, COGL_BUFFER_BIT_COLOR);
st_theme_node_paint_borders (node, &box, 0xFF);
cogl_pop_framebuffer ();
cogl_handle_unref (offscreen);
@ -1928,6 +1929,9 @@ st_theme_node_paint (StThemeNode *node,
allocation.x2 = width;
allocation.y2 = height;
if (width <= 0 || height <= 0)
return;
if (node->alloc_width != width || node->alloc_height != height)
st_theme_node_render_resources (node, width, height);

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('Subsection', section);
// empty sections or submenus should be invisible
menu.append_section('Empty section', new Gio.Menu());
menu.append_submenu('Empty submenu', new Gio.Menu());
let submenu = new Gio.Menu();
submenu.append('Open c:\\', 'app.parameter-string::c:\\');
submenu.append('Open /home', 'app.parameter-string::/home');
menu.append_submenu('Recent files', submenu);
let item = Gio.MenuItem.new('Say 42', null);
item.set_action_and_target_value('app.parameter-int', GLib.Variant.new('u', 42));
menu.append_item(item);
let item = Gio.MenuItem.new('Say 43', null);
item.set_action_and_target_value('app.parameter-int', GLib.Variant.new('u', 43));
menu.append_item(item);
app.set_app_menu(menu);
let window = null;
app.connect_after('startup', function(app) {
window = new Gtk.ApplicationWindow({ title: "Test Application", application: app });
});
app.connect('activate', function(app) {
window.present();
});
app.run(null);
}
main();

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