Compare commits

..

7 Commits

Author SHA1 Message Date
d4802861ed status/keyboard: Adapt to changes in the settings schema 2012-05-15 02:24:23 +02:00
a8fa0b8146 candidatePanel: Use the Lang.Class framework 2012-05-03 13:20:01 +02:00
cf0bb62f40 candidatePanel: Remove the PopupSeparatorMenuItem copy
No reason to not use the real deal.
2012-05-03 13:20:01 +02:00
de8106967d candidatePanel: Remove hardcoded styling
The styling should be done in the CSS file.
2012-05-03 13:19:58 +02:00
1a24f061cf candidatePanel: A candidate popup for IBus input methods
This is an implementation of the org.freedesktop.IBus.Panel API which
shows a shell style popup (BoxPointer) when using an IBus input
method.

The original code is from the ibus-gjs project[1] with minor
modifications.

[1] https://github.com/fujiwarat/ibus-gjs
2012-05-03 13:19:53 +02:00
e038845458 status/keyboard: Add the input source switcher keybindings 2012-05-03 13:19:53 +02:00
b03273c765 status/keyboard: Initial port to the new input sources settings 2012-05-03 13:19:53 +02:00
221 changed files with 25954 additions and 27507 deletions

3
.gitignore vendored
View File

@ -50,7 +50,6 @@ po/gnome-shell.pot
po/*.header po/*.header
po/*.sed po/*.sed
po/*.sin po/*.sin
po/.intltool-merge-cache
po/Makefile.in.in po/Makefile.in.in
po/Makevars.template po/Makevars.template
po/POTFILES po/POTFILES
@ -63,8 +62,6 @@ src/*-enum-types.[ch]
src/*-marshal.[ch] src/*-marshal.[ch]
src/Makefile src/Makefile
src/Makefile.in src/Makefile.in
src/calendar-server/evolution-calendar.desktop
src/calendar-server/evolution-calendar.desktop.in
src/calendar-server/org.gnome.Shell.CalendarServer.service src/calendar-server/org.gnome.Shell.CalendarServer.service
src/gnome-shell src/gnome-shell
src/gnome-shell-calendar-server src/gnome-shell-calendar-server

311
NEWS
View File

@ -1,314 +1,3 @@
3.5.91
======
* Improve modal dialog styling of network secret prompts [Jasper; #682412]
* Fix visibility of non-active workspaces during overview transition
[Florian; #682002]
* Improve scrollbar theming [Cosimo; #682476]
* Make sure the app menu remains hidden in locked state [Florian; #682475]
* Add tooltip to show-applications icon [Jasper; #682445]
* Do not add duplicate remote search providers [Florian; #682470]
* Handle 'popup-menu' signal on summary items [Florian; #682486]
* Fix dwelling during mouse-down [Owen; #682385]
* Set label actor for endSessionDialog.ListItem [Alejandro; #677503]
* Don't match on comments when searching applications [Florian; #682529]
* Make workspace selector more similar to the mockup [Stefano; #662087]
* Fix extension installation and reloading [Jasper; #682578]
* Hide removable devices in the lock screen [Giovanni; #681143]
* Reset cancellable after hitting Escape on login screen [Alban; #681537]
* Fix suspend from the user menu [Giovanni; #682746]
* Set label actor for summary items in message tray [Alejandro; #677229]
* Set label for the "Show applications" dash button [Alejandro; #682366]
* Load extensions as late as possible [Jasper; #682822]
* Improve mount operation dialogs [Jon; #682645]
* Remove "Connect to ..." item from places search [Florian; #682817]
* Don't auto-expand notifications with actions [Giovanni; #682738]
* Add a new lock screen menu to combine volume network and power
[Giovanni; #682540]
* Add support for pre-edit to StIMText [Daiki; #664041]
* Remove StIconType [Jasper, Florian, Rui, Giovanni, Debarshi; #682540]
* Use monitor geometry for dwelling [Florian; #683044]
* Add support for surrounding-text to StIMText [Daiki; #683015]
* Improve the placement and style of the "No results" text [Jasper; #683135]
* Remove broken network device activation policy [Giovanni; #683136]
* Hide power status icon when no battery is present [Tim; #683080]
* Ensure summary items are square and have spacing [Debarshi; #682248]
* Fix close buttons overlapping screen edge [Debarshi; #682343]
* Escape the tray when a legacy icon is clicked [Giovanni; #682244]
* Update arrow in the screen shield to match latest mockups [Giovanni; #682285]
* Allow lifting the screen shield with the mouse wheel [Giovanni; #683164]
* Make sure to show the app menu after unlocking the screen [Jasper; #683154]
* Misc bug fixes and cleanups [Debarshi, Florian, Giovanni, Jasper, Rui;
#582650, #667439, #682238, #682268, #682429, #682455, #682544, #682546,
#682683, #682710, #682998, #683073, #683137, #683156]
Contributors:
Alban Browaeys, Giovanni Campagna, Cosimo Cecchi, Stefano Facchini,
Adel Gadllah, Tim Lunn, Rui Matos, William Jon McCann, Florian Müllner,
Alejandro Piñeiro, Debarshi Ray, Jasper St. Pierre, Owen Taylor, Daiki Ueno
Translations:
Piotr Drąg [pl], Takayuki KUSANO [ja], Kjartan Maraas [nb],
Aurimas Černius [lt], Daniel Mustieles [es], Yuri Myasoedov [ru],
Khaled Hosny [ar], Yaron Shahrabani [he], Tom Tryfonidis [el],
Nilamdyuti Goswami [as], Fran Diéguez [gl], Nguyễn Thái Ngọc Duy [vi],
A S Alam [pa], Dr.T.Vasudevan [ta], Luca Ferretti [it]
3.5.90
======
* Use symbolic icons for workspace switch OSD [Jon; #680738]
* Lock screen improvements:
- Hide user menu and a11y menu in the screen lock [Giovanni; #681143]
- Bump the lock screen slightly when pressing a key [Giovanni; #681143]
- Constrain vertical movement of the screen shield [Giovanni; #681143]
- Return to lock screen on idle [Giovanni; #682041]
- Unlock screen automatically after fast-user switching [Giovanni; #682096]
- Fix "other user" label [Ray; #681750]
* Constrain content of system modals to primary monitor [#681743]
* Respect automatic lock setting on suspend/user-switch [Giovanni; #680231]
* Improve styling of keyring prompt [Jasper; #681821]
* Do not hard-code <super> as overlay-key [Florian; #665547]
* Update style of attached modal dialogs [Florian; #681601]
* a11y: allow navigation on non reactive items [Alejandro; #667439, #667439]
* Implement mode-less overview design [Joost, Florian; #682109]
* Implement message-tray redesign:
- Restyle the message tray [Ana, Allan, Florian; #677213, #682342]
- Move the desktop upwards when showing the tray [Debarshi; #681392]
- Add a close button to notifications [Ana, Jasper; #682253]
- Add a keybinding to toggle the tray [Debarshi; #681392]
- Make the tray keyboard navigable [Debarshi; #681519]
- Add dwelling at the bottom of the screen to open the tray [Owen; #682310]
- Don't time out banners when the user is inactive [Marina, Jasper]
- Misc fixes and cleanups [Jasper, Marina]
* Fix showing "Next Week" on Sundays [Sebastian; #682198]
* Delay restoring IM presence until the network comes up [Florian; #677982]
* Display enterprise login hint [Ray; #681975]
* Ignore unrecognized/irrelevant network devices/connections [Dan; #682364]
* Misc bug fixes and cleanups: [Dan, Florian, Jasper, Jiro, Piotr, Rico;
#643687, #682045, #682189]
Contributors:
Giovanni Campagna, Allan Day, Piotr Drąg, William Jon McCann,
Sebastian Keller, Jiro Matsuzawa, Florian Müllner, Alejandro Piñeiro,
Debarshi Ray, Ana Risteska, Jasper St. Pierre, Ray Strode, Owen Taylor,
Rico Tzschichholz, Joost Verdoorn, Dan Winship, Marina Zhurakhinskaya
Translations:
Nilamdyuti Goswami [as], Daniel Mustieles [es], Yaron Shahrabani [he],
Chao-Hsiung Liao [zh_HK, zh_TW], Tobias Endrigkeit [de], A S Alam [pa],
Sandeep Sheshrao Shedmake [mr], Fran Diéguez [gl],
Мирослав Николић [sr, sr@latin]
3.5.5
=====
* Update style to match mockups [Allan]
- improve calendar layout and legibility
- update notifications and menus
- use a common style for entries
- update scrollbars to match GTK+
- improve clock/unlock button in lock screen
- update polkit dialogs [Jasper]
* Fix login dialog growing when selecting different users [Florian; #675076]
* Implement screen lock in the shell [Giovanni]
- restructure login code to be shared with session unlock [#619955]
- add initial screen shield / unlock dialog implementation [#619955]
- implement (optional) notification list on lock shield [#619955]
- update login dialog style to match lock screen [#619955]
- filter notifications to only show new ones on the screen lock [#681143]
- make notifications scrollable if necessary [#681143]
- use correct application names in notifications [#681143]
- allow to return to the shield by pressing Escape [#681143]
* Minor login dialog improvements [Florian]
- update style to match the overall visuals [#660913]
- indicate whether users are logged in [#658185]
* Add support for background-repeat CSS property [Jasper; #680801]
* Add :active pseudo class on scroll handles [Florian]
* Remove markup from translated strings [Matthias; #681270]
* Misc bug fixes and cleanups: [Alban, Florian, Giovanni, Jasper, Jeremy,
Matthias, Piotr; #677893, #679944, #680064, #680170, #680216, #680426,
#681101, #681382]
Contributors:
Jeremy Bicha, Alban Browaeys, Giovanni Campagna, Matthias Clasen, Allan Day,
Piotr Drąg , Florian Müllner, Jasper St. Pierre
Translations:
Matej Urbančič [sl], Tom Tryfonidis [el], Yaron Shahrabani [he],
Kjartan Maraas [nb], Baurzhan Muftakhidinov [kk], Praveen Illa [te],
Khaled Hosny [ar], Daniel Mustieles [es], Gabor Kelemen [hu],
Fran Diéguez [gl], Sweta Kothari [gu], Aleksej Kabanov [ru],
Nilamdyuti Goswami [as], Arash Mousavi [fa], Мирослав Николић [sr, sr@latin]
3.5.4
=====
* Fix wrong result handling of remote calls [Florian; #678852]
* dateMenu: Fix regression that caused no date to be displayed [Colin]
* WindowTracker: Fix refcounting bug in get_app_for_window() [Giovanni; #678992]
* Show the workspace switcher for move-to-workspace keybinding
[Giovanni, Jasper; #674104, #660839, #679005]
* userMenu: Move "Power off" item to the bottom [Florian; #678887]
* Remove contacts search provider [Florian, Rui; #677442]
* network: don't ask for always-ask secrets when interaction isn't allowed
[Dan; #679091]
* PolkitAgent: Look for the right password prompt [Matthias; #675300]
* Implement extension updates [Jasper; #679099]
* userMenu: Don't disconnect account signals when disabled [Guillaume; #669112]
* Fix startup notification when opening calendar [Florian; #677907]
* networkAgent: use absolute path if configured [Clemens; #679212]
* recorder: Port to GStreamer-1.0 API [Florian; #679445]
* telepathyClient: don't add log messages on presence changes [Ana; #669508]
* lookingGlass: Don't use a signal callback on 'paint' to draw the border
[Jasper; #679464]
* Add support for inhibiting automount [Hans; #678597]
* Implemented banner support for the login screen [Matthias, Marius; #665346]
* boxpointer: Flip side if we would end outside the monitor [Rui; #678164]
* boxpointer: Change 'animate' parameter on show/hide to a bitmask
[Rui; #678337]
* Add a grayscale effect [Matthias, Jasper, Florian: #676782, #674499]
* UserMenu: show "Install Updates & Restart" when appropriate
[Giovanni; #677394, #680080]
* messageTray: don't show the message tray when a new notification is shown
[Ana; #677210]
* panel: don't break when indicator has no menu [Jean-Philippe; #678694]
* appMenu: Disable app menu during startup animations [Florian; #672322]
* autorun: Add a notification when unmounting drives [Cosimo; #676125]
* st-icon: Fix potential crash involving shadows [Jasper; #679776]
* Remove manual garbage collection on tweeners end [Cosimo; #679832]
* dash: hide tooltips when overview begins hiding [Stefano; #674241]
* Update modal dialog animation for new centered position [Florian; #674499]
* calendar: Fix grid lines in RTL locales [Florian; #679879]
* Integrate IBus with keyboard indicator [Rui; #641531]
* Move ibus status icon under keyboard [Matthias]
* gdm: port from libgdmgreeter to libgdm [Ray; #676401]
* Misc bug fixes and cleanups [Antoine, Cosimo, Giovanni, Jasper, Rico;
#678978, #672790, #679847, #679944]
Contributors:
Jean-Philippe Braun, Clemens Buchacher, Giovanni Campagna, Cosimo Cecchi,
Matthias Clasen, Hans de Goede, Guillaume Desmottes, Stefano Facchini,
Antoine Jacoutot, Rui Matos, Florian Müllner, Marius Rieder, Ana Risteska,
Jasper St. Pierre, Rico Tzschichholz, Colin Walters, Dan Williams
Translations:
Matej Urbančič [sl], Khaled Hosny [ar], Nguyễn Thái Ngọc Duy [vi],
Nilamdyuti Goswami [as], Alexander Shopov [bg], Ivaylo Valkov [bg],
Daniel Mustieles [es], Kjartan Maraas [nb,nn], Yaron Shahrabani [he],
Nilamdyuti Goswami [as], Chao-Hsiung Liao [zh_HK, zh_TW], Ihar Hrachyshka [be],
Praveen Illa [te]
3.5.3
=====
* calendar: Adapt to Evolution-Data-Server API changes [Matthew; #677402]
* messageTray: Don't show non urgent notifications while in fullscreen
[Adel; #677590]
* modalDialog: show dialogs on monitor with the mouse pointer [Tim; #642591]
* extensionSystem: Prepare for extension updating system [Jasper; #677586]
* appDisplay: Don't show apps in NoDisplay categories in the All view
[Jasper; #658176]
* st: Trigger theme updates on resolution changes [Florian; #677975]
* Always enable a11y [Bastien; #678095]
* telepathyClient: ignore invalidated channels [Guillaume; #677457]
* shell-app: Update app menu if necessary [Florian; #676238]
* Enable the Screen Reader menu item [Matthias; #663256]
* Disable unredirection when a modal operation is active [Giovanni]
* Make folks optional [Colin]
* Improve mount-operation support [Cosimo]
- Fix exception when showing password entry [#678428]
- Close the password entry on operation abort [#673787]
- autorun: Don't allow autorun for things we mount on startup [#660595]
- Turn passphrase prompt into a dialog [#674962]
- Implement org.Gtk.MountOperationHandler [#678516]
* Network menu improvements
- Sort Wifi networks by strength [Giovanni; #658946]
- Prefer wifi/3g over VPN in the panel [Cosimo; #672591]
* clock: Switch to using GnomeWallClock [Colin; #657074]
* remoteSearch: Allow to reference .desktop file for Title/Icon
[Florian; #678816]
* Fix memory leaks [Jasper, Pavel; #678079, #678406, #678737]
* Misc fixes [Florian, Giovanni, Guillaume, Jasper, Kjartan, Piotr, Rui;
#658955, #677497, #678396, #678502]
* Misc cleanups [Bastien, Florian, Jasper; #677426, #677515, #678096, #678416]
Contributors:
Matthew Barnes, Giovanni Campagna, Cosimo Cecchi, Matthias Clasen,
Guillaume Desmottes, Piotr Drąg, Adel Gadllah, Tim L, Kjartan Maraas,
Rui Matos, Florian Müllner, Bastien Nocera, Jasper St. Pierre, Colin Walters
Translations:
Matej Urbančič [sl], Yuri Kozlov [ru], Tom Tryfonidis [el],
Kjartan Maraas [nb], Žygimantas Beručka [lt], Luca Ferretti [it],
Khaled Hosny [ar], Daniel Mustieles [es], Fran Diéguez [gl], A S Alam [pa]
3.5.2
=====
* main: Move 'toggle-recording' binding into the shell [Florian; #674377]
* popupMenu: make sure to break the grab when the slider is not visible
[Stefano; #672713]
* st-theme-node-drawing: Don't use GL types [Neil; #672711]
* Mirror Evolution calendar settings into our own schema [Owen; #674424]
* shell-network-agent: don't crash if a request isn't found [Dan; #674961]
* notificationDaemon: Match app based on WM_CLASS [Jasper; #673761]
* NetworkMenu: use network-offline while loading [Giovanni; #674426]
* lookingGlass: Remove the Errors tab [Jasper; #675104]
* searchDisplay: Reset keyboard focus after displaying async results
[Rui; #675078]
* gdm: don't fail if fprintd is unavailable [Ray; #675006]
* messageTray: Fix scrolling up [Jasper; #661615]
* main: Close the recorder instead of pausing it [Rui; #675128]
* Accessibility [Alejandro]
- Use the proper label_actor for date menu on top panel [#675307]
- Set the proper role/label_actor for SearchResult.content [#672242]
- do not expose a label text if 'hidden' style class is used [#675341]
* Magnifier: Add brightness and contrast functionality [Joseph; #639851]
* theme: use a smaller border-radius for top bar [Jakub; #672430]
* placeDisplay: use new bookmark file location [Matthias; #675443]
* port all synchronous search providers to the async API [Jasper, Rui; #675328]
* NetworkAgent: disallow multiple requests for the same connection/setting
[Giovanni; #674961]
* userMenu: Update to latest mockups [Florian; #675802]
* util: Don't double-fork when spawning from Alt-F2 [Colin; #675789]
* messageTray: Make Source usable without subclassing [Jasper; #661236]
* panel: Check for appMenu button's reactivity before opening [Florian; #676316]
* Fix formatting of bluetooth passkey [Florian; #651251]
* notificationDaemon: Filter out file-transfer notifications [Jasper; #676175]
* Don't use global.log() [Jasper; #675790]
* Fix broken extension loading in some distributions [Owen, Alexandre; #670477]
* shell-app: Raise windows in reverse order to preserve the stacking
[Rui; #676371]
* Generalize gdm-mode [Florian; #676156]
* Switch string formatting to the one inside gjs [Jasper; #675479]
* extensionUtils: Support subdirectories in getCurrentExtension
[Jasper; #677001]
* panel: Refuse to add (legacy) status icons not part of the session mode
[Florian; #677058]
* Add an initial-setup mode [Matthias; #676697]
* status/keyboard: Port to the new input sources settings [Rui; #641531]
* NetworkMenu: show notifications for failed VPN connections [Giovanni; #676330]
* userMenu: Indicate progress on status changes [Florian; #659067]
* recorder: Honor "disable-save-to-disk" lockdown key [Rūdolfs; #673630]
* searchDisplay: Use the rowLimit we pass to the IconGrid [Christian; #675527]
* endSessionDialog: Factor out _updateDescription from _updateContent
[Alejandro; #674210]
* Fix empathy's appMenu freezing the shell [Alban; #676447]
* Code cleanups [Florian, Giovanni, Jasper; #672807, #672413, #676837, #676850,
#672272]
* Misc bug fixes [Alban, Florian, Giovanni, Guillaume, Jasper, Piotr, Rico,
Ron, Rui, Stefano; #659968, #672192, #673177, #673198, #674323, #675301,
#675370, #676347, #676806, #677097]
Contributors:
Alban Browaeys, Giovanni Campagna, Matthias Clasen, Guillaume Desmottes,
Piotr Drąg, Stefano Facchini, Rui Matos, Rūdolfs Mazurs, Florian Müllner,
Alejandro Piñeiro, Neil Roberts, Alexandre Rostovtsev, Joseph Scheuhammer,
Jakub Steiner, Jasper St. Pierre, Ray Strode, Owen Taylor, Rico Tzschichholz,
Colin Walters, Dan Winship, Ron Yorston
Translations:
OKANO Takayoshi [ja], Daniel Mustieles [es], Changwoo Ryu [ko],
Yaron Shahrabani [he], Fran Diéguez [gl], Jonh Wendell [pt_BR],
Kjartan Maraas [nb], Luca Ferretti [it], Tom Tryfonidis [el],
Sandeep Sheshrao Shedmake [mr], Takanori MATSUURA [ja], Dirgita [id],
Mantas Kriaučiūnas [lt], Matej Urbančič [sl], Jiro Matsuzawa [ja]
3.4.1 3.4.1
===== =====
* Fix crash that occurred when an icon theme change caused unexpected * Fix crash that occurred when an icon theme change caused unexpected

View File

@ -41,7 +41,7 @@
"It can be used only by extensions.gnome.org" "It can be used only by extensions.gnome.org"
#define PLUGIN_MIME_STRING "application/x-gnome-shell-integration::Gnome Shell Integration Dummy Content-Type"; #define PLUGIN_MIME_STRING "application/x-gnome-shell-integration::Gnome Shell Integration Dummy Content-Type";
#define PLUGIN_API_VERSION 5 #define PLUGIN_API_VERSION 3
typedef struct { typedef struct {
GDBusProxy *proxy; GDBusProxy *proxy;
@ -163,7 +163,6 @@ NP_Initialize(NPNetscapeFuncs *pfuncs, NPPluginFuncs *plugin)
plugin->newp = NPP_New; plugin->newp = NPP_New;
plugin->destroy = NPP_Destroy; plugin->destroy = NPP_Destroy;
plugin->getvalue = NPP_GetValue; plugin->getvalue = NPP_GetValue;
plugin->setwindow = NPP_SetWindow;
return NPERR_NO_ERROR; return NPERR_NO_ERROR;
} }
@ -225,7 +224,7 @@ NPP_New(NPMIMEType mimetype,
NULL, /* interface info */ NULL, /* interface info */
"org.gnome.Shell", "org.gnome.Shell",
"/org/gnome/Shell", "/org/gnome/Shell",
"org.gnome.Shell.Extensions", "org.gnome.Shell",
NULL, /* GCancellable */ NULL, /* GCancellable */
&error); &error);
if (!data->proxy) if (!data->proxy)
@ -268,7 +267,6 @@ typedef struct {
NPObject parent; NPObject parent;
NPP instance; NPP instance;
GDBusProxy *proxy; GDBusProxy *proxy;
GSettings *settings;
NPObject *listener; NPObject *listener;
NPObject *restart_listener; NPObject *restart_listener;
gint signal_id; gint signal_id;
@ -325,9 +323,6 @@ on_shell_appeared (GDBusConnection *connection,
} }
} }
#define SHELL_SCHEMA "org.gnome.shell"
#define ENABLED_EXTENSIONS_KEY "enabled-extensions"
static NPObject * static NPObject *
plugin_object_allocate (NPP instance, plugin_object_allocate (NPP instance,
NPClass *klass) NPClass *klass)
@ -337,7 +332,6 @@ plugin_object_allocate (NPP instance,
obj->instance = instance; obj->instance = instance;
obj->proxy = g_object_ref (data->proxy); obj->proxy = g_object_ref (data->proxy);
obj->settings = g_settings_new (SHELL_SCHEMA);
obj->signal_id = g_signal_connect (obj->proxy, "g-signal", obj->signal_id = g_signal_connect (obj->proxy, "g-signal",
G_CALLBACK (on_shell_signal), obj); G_CALLBACK (on_shell_signal), obj);
@ -373,14 +367,39 @@ plugin_object_deallocate (NPObject *npobj)
g_slice_free (PluginObject, obj); g_slice_free (PluginObject, obj);
} }
static NPIdentifier api_version_id;
static NPIdentifier shell_version_id;
static NPIdentifier get_info_id;
static NPIdentifier list_extensions_id;
static NPIdentifier enable_extension_id;
static NPIdentifier install_extension_id;
static NPIdentifier uninstall_extension_id;
static NPIdentifier onextension_changed_id;
static NPIdentifier onrestart_id;
static NPIdentifier get_errors_id;
static NPIdentifier launch_extension_prefs_id;
static bool
plugin_object_has_method (NPObject *npobj,
NPIdentifier name)
{
return (name == get_info_id ||
name == list_extensions_id ||
name == enable_extension_id ||
name == install_extension_id ||
name == uninstall_extension_id ||
name == get_errors_id ||
name == launch_extension_prefs_id);
}
static inline gboolean static inline gboolean
uuid_is_valid (NPString string) uuid_is_valid (const gchar *uuid)
{ {
gsize i; gsize i;
for (i = 0; i < string.UTF8Length; i++) for (i = 0; uuid[i]; i ++)
{ {
gchar c = string.UTF8Characters[i]; gchar c = uuid[i];
if (c < 32 || c >= 127) if (c < 32 || c >= 127)
return FALSE; return FALSE;
@ -444,73 +463,8 @@ jsonify_variant (GVariant *variant,
} }
static gboolean static gboolean
parse_args (const gchar *format_str, plugin_list_extensions (PluginObject *obj,
uint32_t argc, NPVariant *result)
const NPVariant *argv,
...)
{
va_list args;
gsize i;
gboolean ret = FALSE;
if (strlen (format_str) != argc)
return FALSE;
va_start (args, argv);
for (i = 0; format_str[i]; i++)
{
gpointer arg_location;
const NPVariant arg = argv[i];
arg_location = va_arg (args, gpointer);
switch (format_str[i])
{
case 'u':
{
NPString string;
if (!NPVARIANT_IS_STRING (arg))
goto out;
string = NPVARIANT_TO_STRING (arg);
if (!uuid_is_valid (string))
goto out;
*(gchar **) arg_location = g_strndup (string.UTF8Characters, string.UTF8Length);
}
break;
case 'b':
if (!NPVARIANT_IS_BOOLEAN (arg))
goto out;
*(gboolean *) arg_location = NPVARIANT_TO_BOOLEAN (arg);
break;
case 'o':
if (!NPVARIANT_IS_OBJECT (arg))
goto out;
*(NPObject **) arg_location = NPVARIANT_TO_OBJECT (arg);
}
}
ret = TRUE;
out:
va_end (args);
return ret;
}
static gboolean
plugin_list_extensions (PluginObject *obj,
uint32_t argc,
const NPVariant *args,
NPVariant *result)
{ {
GError *error = NULL; GError *error = NULL;
GVariant *res; GVariant *res;
@ -534,159 +488,91 @@ plugin_list_extensions (PluginObject *obj,
} }
static gboolean static gboolean
plugin_enable_extension (PluginObject *obj, plugin_enable_extension (PluginObject *obj,
uint32_t argc, NPString uuid,
const NPVariant *argv, gboolean enabled)
NPVariant *result)
{ {
gboolean ret; gchar *uuid_str = g_strndup (uuid.UTF8Characters, uuid.UTF8Length);
gchar *uuid; if (!uuid_is_valid (uuid_str))
gboolean enabled;
gsize length;
gchar **uuids;
const gchar **new_uuids;
if (!parse_args ("ub", argc, argv, &uuid, &enabled))
return FALSE;
uuids = g_settings_get_strv (obj->settings, ENABLED_EXTENSIONS_KEY);
length = g_strv_length (uuids);
if (enabled)
{ {
new_uuids = g_new (const gchar *, length + 2); /* New key, NULL */ g_free (uuid_str);
memcpy (new_uuids, uuids, length * sizeof (*new_uuids)); return FALSE;
new_uuids[length] = uuid;
new_uuids[length + 1] = NULL;
} }
else
{
gsize i = 0, j = 0;
new_uuids = g_new (const gchar *, length);
for (i = 0; i < length; i ++)
{
if (g_str_equal (uuids[i], uuid))
continue;
new_uuids[j] = uuids[i];
j++;
}
new_uuids[j] = NULL;
}
ret = g_settings_set_strv (obj->settings,
ENABLED_EXTENSIONS_KEY,
new_uuids);
g_strfreev (uuids);
g_free (new_uuids);
g_free (uuid);
return ret;
}
typedef struct _AsyncClosure AsyncClosure;
struct _AsyncClosure {
PluginObject *obj;
NPObject *callback;
NPObject *errback;
};
static void
install_extension_cb (GObject *proxy,
GAsyncResult *async_res,
gpointer user_data)
{
AsyncClosure *async_closure = (AsyncClosure *) user_data;
GError *error = NULL;
GVariant *res;
NPVariant args[1];
NPVariant result = { NPVariantType_Void };
NPObject *callback;
res = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), async_res, &error);
if (res == NULL)
{
if (g_dbus_error_is_remote_error (error))
g_dbus_error_strip_remote_error (error);
STRINGZ_TO_NPVARIANT (error->message, args[0]);
callback = async_closure->errback;
}
else
{
char *string_result;
g_variant_get (res, "(&s)", &string_result);
STRINGZ_TO_NPVARIANT (string_result, args[0]);
callback = async_closure->callback;
}
funcs.invokeDefault (async_closure->obj->instance,
callback, args, 1, &result);
funcs.releasevariantvalue (&result);
funcs.releaseobject (async_closure->callback);
funcs.releaseobject (async_closure->errback);
g_slice_free (AsyncClosure, async_closure);
}
static gboolean
plugin_install_extension (PluginObject *obj,
uint32_t argc,
const NPVariant *argv,
NPVariant *result)
{
gchar *uuid;
NPObject *callback, *errback;
AsyncClosure *async_closure;
if (!parse_args ("uoo", argc, argv, &uuid, &callback, &errback))
return FALSE;
async_closure = g_slice_new (AsyncClosure);
async_closure->obj = obj;
async_closure->callback = funcs.retainobject (callback);
async_closure->errback = funcs.retainobject (errback);
g_dbus_proxy_call (obj->proxy, g_dbus_proxy_call (obj->proxy,
"InstallRemoteExtension", (enabled ? "EnableExtension" : "DisableExtension"),
g_variant_new ("(s)", uuid), g_variant_new ("(s)", uuid_str),
G_DBUS_CALL_FLAGS_NONE, G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout */ -1, /* timeout */
NULL, /* cancellable */ NULL, /* cancellable */
install_extension_cb, NULL, /* callback */
async_closure); NULL /* user_data */);
g_free (uuid); g_free (uuid_str);
return TRUE; return TRUE;
} }
static gboolean static gboolean
plugin_uninstall_extension (PluginObject *obj, plugin_install_extension (PluginObject *obj,
uint32_t argc, NPString uuid,
const NPVariant *argv, NPString version_tag)
NPVariant *result) {
gchar *uuid_str = g_strndup (uuid.UTF8Characters, uuid.UTF8Length);
gchar *version_tag_str;
if (!uuid_is_valid (uuid_str))
{
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_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;
}
static gboolean
plugin_uninstall_extension (PluginObject *obj,
NPString uuid,
NPVariant *result)
{ {
GError *error = NULL; GError *error = NULL;
GVariant *res; GVariant *res;
gchar *uuid; gchar *uuid_str;
if (!parse_args ("u", argc, argv, &uuid)) uuid_str = g_strndup (uuid.UTF8Characters, uuid.UTF8Length);
return FALSE; if (!uuid_is_valid (uuid_str))
{
g_free (uuid_str);
return FALSE;
}
res = g_dbus_proxy_call_sync (obj->proxy, res = g_dbus_proxy_call_sync (obj->proxy,
"UninstallExtension", "UninstallExtension",
g_variant_new ("(s)", uuid), g_variant_new ("(s)",
uuid_str),
G_DBUS_CALL_FLAGS_NONE, G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout */ -1, /* timeout */
NULL, /* cancellable */ NULL, /* cancellable */
&error); &error);
g_free (uuid); g_free (uuid_str);
if (!res) if (!res)
{ {
@ -699,27 +585,30 @@ plugin_uninstall_extension (PluginObject *obj,
} }
static gboolean static gboolean
plugin_get_info (PluginObject *obj, plugin_get_info (PluginObject *obj,
uint32_t argc, NPString uuid,
const NPVariant *argv, NPVariant *result)
NPVariant *result)
{ {
GError *error = NULL; GError *error = NULL;
GVariant *res; GVariant *res;
gchar *uuid; gchar *uuid_str;
if (!parse_args ("u", argc, argv, &uuid)) uuid_str = g_strndup (uuid.UTF8Characters, uuid.UTF8Length);
return FALSE; if (!uuid_is_valid (uuid_str))
{
g_free (uuid_str);
return FALSE;
}
res = g_dbus_proxy_call_sync (obj->proxy, res = g_dbus_proxy_call_sync (obj->proxy,
"GetExtensionInfo", "GetExtensionInfo",
g_variant_new ("(s)", uuid), g_variant_new ("(s)", uuid_str),
G_DBUS_CALL_FLAGS_NONE, G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout */ -1, /* timeout */
NULL, /* cancellable */ NULL, /* cancellable */
&error); &error);
g_free (uuid); g_free (uuid_str);
if (!res) if (!res)
{ {
@ -732,26 +621,31 @@ plugin_get_info (PluginObject *obj,
} }
static gboolean static gboolean
plugin_get_errors (PluginObject *obj, plugin_get_errors (PluginObject *obj,
uint32_t argc, NPString uuid,
const NPVariant *argv, NPVariant *result)
NPVariant *result)
{ {
GError *error = NULL; GError *error = NULL;
GVariant *res; GVariant *res;
gchar *uuid; gchar *uuid_str;
if (!parse_args ("u", argc, argv, &uuid)) uuid_str = g_strndup (uuid.UTF8Characters, uuid.UTF8Length);
return FALSE; if (!uuid_is_valid (uuid_str))
{
g_free (uuid_str);
return FALSE;
}
res = g_dbus_proxy_call_sync (obj->proxy, res = g_dbus_proxy_call_sync (obj->proxy,
"GetExtensionErrors", "GetExtensionErrors",
g_variant_new ("(s)", uuid), g_variant_new ("(s)", uuid_str),
G_DBUS_CALL_FLAGS_NONE, G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout */ -1, /* timeout */
NULL, /* cancellable */ NULL, /* cancellable */
&error); &error);
g_free (uuid_str);
if (!res) if (!res)
{ {
g_warning ("Failed to retrieve errors: %s", error->message); g_warning ("Failed to retrieve errors: %s", error->message);
@ -763,25 +657,29 @@ plugin_get_errors (PluginObject *obj,
} }
static gboolean static gboolean
plugin_launch_extension_prefs (PluginObject *obj, plugin_launch_extension_prefs (PluginObject *obj,
uint32_t argc, NPString uuid,
const NPVariant *argv, NPVariant *result)
NPVariant *result)
{ {
gchar *uuid; gchar *uuid_str;
if (!parse_args ("u", argc, argv, &uuid)) uuid_str = g_strndup (uuid.UTF8Characters, uuid.UTF8Length);
return FALSE; if (!uuid_is_valid (uuid_str))
{
g_free (uuid_str);
return FALSE;
}
g_dbus_proxy_call (obj->proxy, g_dbus_proxy_call (obj->proxy,
"LaunchExtensionPrefs", "LaunchExtensionPrefs",
g_variant_new ("(s)", uuid), g_variant_new ("(s)", uuid_str),
G_DBUS_CALL_FLAGS_NONE, G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout */ -1, /* timeout */
NULL, /* cancellable */ NULL, /* cancellable */
NULL, /* callback */ NULL, /* callback */
NULL /* user_data */); NULL /* user_data */);
g_free (uuid_str);
return TRUE; return TRUE;
} }
@ -835,40 +733,10 @@ plugin_get_shell_version (PluginObject *obj,
return ret; return ret;
} }
#define METHODS \
METHOD (list_extensions) \
METHOD (get_info) \
METHOD (enable_extension) \
METHOD (install_extension) \
METHOD (uninstall_extension) \
METHOD (get_errors) \
METHOD (launch_extension_prefs) \
/* */
#define METHOD(x) \
static NPIdentifier x##_id;
METHODS
#undef METHOD
static NPIdentifier api_version_id;
static NPIdentifier shell_version_id;
static NPIdentifier onextension_changed_id;
static NPIdentifier onrestart_id;
static bool
plugin_object_has_method (NPObject *npobj,
NPIdentifier name)
{
#define METHOD(x) (name == (x##_id)) ||
/* expands to (name == list_extensions_id) || FALSE; */
return METHODS FALSE;
#undef METHOD
}
static bool static bool
plugin_object_invoke (NPObject *npobj, plugin_object_invoke (NPObject *npobj,
NPIdentifier name, NPIdentifier name,
const NPVariant *argv, const NPVariant *args,
uint32_t argc, uint32_t argc,
NPVariant *result) NPVariant *result)
{ {
@ -880,13 +748,61 @@ plugin_object_invoke (NPObject *npobj,
VOID_TO_NPVARIANT (*result); VOID_TO_NPVARIANT (*result);
#define METHOD(x) \ if (!plugin_object_has_method (npobj, name))
if (name == x##_id) \ return FALSE;
return plugin_##x (obj, argc, argv, result);
METHODS
#undef METHOD
return FALSE; if (name == list_extensions_id)
return plugin_list_extensions (obj, result);
else if (name == get_info_id)
{
if (!NPVARIANT_IS_STRING(args[0])) return FALSE;
return plugin_get_info (obj, NPVARIANT_TO_STRING(args[0]), result);
}
else if (name == enable_extension_id)
{
if (!NPVARIANT_IS_STRING(args[0])) return FALSE;
if (!NPVARIANT_IS_BOOLEAN(args[1])) return FALSE;
return plugin_enable_extension (obj,
NPVARIANT_TO_STRING(args[0]),
NPVARIANT_TO_BOOLEAN(args[1]));
}
else if (name == install_extension_id)
{
if (!NPVARIANT_IS_STRING(args[0])) return FALSE;
if (!NPVARIANT_IS_STRING(args[1])) return FALSE;
return plugin_install_extension (obj,
NPVARIANT_TO_STRING(args[0]),
NPVARIANT_TO_STRING(args[1]));
}
else if (name == uninstall_extension_id)
{
if (!NPVARIANT_IS_STRING(args[0])) return FALSE;
return plugin_uninstall_extension (obj,
NPVARIANT_TO_STRING(args[0]),
result);
}
else if (name == get_errors_id)
{
if (!NPVARIANT_IS_STRING(args[0])) return FALSE;
return plugin_get_errors (obj,
NPVARIANT_TO_STRING(args[0]),
result);
}
else if (name == launch_extension_prefs_id)
{
if (!NPVARIANT_IS_STRING(args[0])) return FALSE;
return plugin_launch_extension_prefs (obj,
NPVARIANT_TO_STRING(args[0]),
result);
}
return TRUE;
} }
static bool static bool
@ -1030,12 +946,3 @@ NPP_GetValue(NPP instance,
return NPERR_NO_ERROR; return NPERR_NO_ERROR;
} }
/* Opera tries to call NPP_SetWindow without checking the
* NULL pointer beforehand. */
NPError
NPP_SetWindow(NPP instance,
NPWindow *window)
{
return NPERR_NO_ERROR;
}

View File

@ -1,5 +1,5 @@
AC_PREREQ(2.63) AC_PREREQ(2.63)
AC_INIT([gnome-shell],[3.5.91],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell]) AC_INIT([gnome-shell],[3.4.1],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell])
AC_CONFIG_HEADERS([config.h]) AC_CONFIG_HEADERS([config.h])
AC_CONFIG_SRCDIR([src/shell-global.c]) AC_CONFIG_SRCDIR([src/shell-global.c])
@ -44,15 +44,15 @@ AC_SUBST(PYTHON)
# We need at least this, since gst_plugin_register_static() was added # We need at least this, since gst_plugin_register_static() was added
# in 0.10.16, but nothing older than 0.10.21 has been tested. # in 0.10.16, but nothing older than 0.10.21 has been tested.
GSTREAMER_MIN_VERSION=0.11.92 GSTREAMER_MIN_VERSION=0.10.16
recorder_modules= recorder_modules=
build_recorder=false build_recorder=false
AC_MSG_CHECKING([for GStreamer (needed for recording functionality)]) AC_MSG_CHECKING([for GStreamer (needed for recording functionality)])
if $PKG_CONFIG --exists gstreamer-1.0 '>=' $GSTREAMER_MIN_VERSION ; then if $PKG_CONFIG --exists gstreamer-0.10 '>=' $GSTREAMER_MIN_VERSION ; then
AC_MSG_RESULT(yes) AC_MSG_RESULT(yes)
build_recorder=true build_recorder=true
recorder_modules="gstreamer-1.0 gstreamer-base-1.0 x11" recorder_modules="gstreamer-0.10 gstreamer-base-0.10 x11"
PKG_CHECK_MODULES(TEST_SHELL_RECORDER, $recorder_modules clutter-1.0 xfixes gl) PKG_CHECK_MODULES(TEST_SHELL_RECORDER, $recorder_modules clutter-1.0 xfixes gl)
else else
AC_MSG_RESULT(no) AC_MSG_RESULT(no)
@ -60,32 +60,30 @@ fi
AM_CONDITIONAL(BUILD_RECORDER, $build_recorder) AM_CONDITIONAL(BUILD_RECORDER, $build_recorder)
CLUTTER_MIN_VERSION=1.11.11 CLUTTER_MIN_VERSION=1.9.16
GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1 GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1
GJS_MIN_VERSION=1.33.2 GJS_MIN_VERSION=1.29.18
MUTTER_MIN_VERSION=3.5.91 MUTTER_MIN_VERSION=3.4.1
FOLKS_MIN_VERSION=0.5.2
GTK_MIN_VERSION=3.3.9 GTK_MIN_VERSION=3.3.9
GIO_MIN_VERSION=2.31.6 GIO_MIN_VERSION=2.31.6
LIBECAL_MIN_VERSION=3.5.3 LIBECAL_MIN_VERSION=2.32.0
LIBEDATASERVER_MIN_VERSION=3.5.3 LIBEDATASERVER_MIN_VERSION=1.2.0
LIBEDATASERVERUI_MIN_VERSION=3.5.3 LIBEDATASERVERUI_MIN_VERSION=2.91.6
TELEPATHY_GLIB_MIN_VERSION=0.17.5 TELEPATHY_GLIB_MIN_VERSION=0.17.5
TELEPATHY_LOGGER_MIN_VERSION=0.2.4 TELEPATHY_LOGGER_MIN_VERSION=0.2.4
POLKIT_MIN_VERSION=0.100 POLKIT_MIN_VERSION=0.100
STARTUP_NOTIFICATION_MIN_VERSION=0.11 STARTUP_NOTIFICATION_MIN_VERSION=0.11
GCR_MIN_VERSION=3.3.90 GCR_MIN_VERSION=3.3.90
GNOME_DESKTOP_REQUIRED_VERSION=3.5.1
GNOME_MENUS_REQUIRED_VERSION=3.5.3
# Collect more than 20 libraries for a prize! # Collect more than 20 libraries for a prize!
PKG_CHECK_MODULES(GNOME_SHELL, gio-unix-2.0 >= $GIO_MIN_VERSION PKG_CHECK_MODULES(GNOME_SHELL, gio-unix-2.0 >= $GIO_MIN_VERSION
libxml-2.0 libxml-2.0
gtk+-3.0 >= $GTK_MIN_VERSION gtk+-3.0 >= $GTK_MIN_VERSION
atk-bridge-2.0 folks >= $FOLKS_MIN_VERSION
libmutter >= $MUTTER_MIN_VERSION libmutter >= $MUTTER_MIN_VERSION
gjs-internals-1.0 >= $GJS_MIN_VERSION gjs-internals-1.0 >= $GJS_MIN_VERSION
libgnome-menu-3.0 >= $GNOME_MENUS_REQUIRED_VERSION libgnome-menu-3.0 $recorder_modules
$recorder_modules
gdk-x11-3.0 libsoup-2.4 gdk-x11-3.0 libsoup-2.4
gl gl
clutter-x11-1.0 >= $CLUTTER_MIN_VERSION clutter-x11-1.0 >= $CLUTTER_MIN_VERSION
@ -97,8 +95,7 @@ PKG_CHECK_MODULES(GNOME_SHELL, gio-unix-2.0 >= $GIO_MIN_VERSION
telepathy-logger-0.2 >= $TELEPATHY_LOGGER_MIN_VERSION telepathy-logger-0.2 >= $TELEPATHY_LOGGER_MIN_VERSION
polkit-agent-1 >= $POLKIT_MIN_VERSION xfixes polkit-agent-1 >= $POLKIT_MIN_VERSION xfixes
libnm-glib libnm-util gnome-keyring-1 libnm-glib libnm-util gnome-keyring-1
gcr-3 >= $GCR_MIN_VERSION gcr-3 >= $GCR_MIN_VERSION)
gnome-desktop-3.0 >= $GNOME_DESKTOP_REQUIRED_VERSION)
PKG_CHECK_MODULES(SHELL_PERF_HELPER, gtk+-3.0 gio-2.0) PKG_CHECK_MODULES(SHELL_PERF_HELPER, gtk+-3.0 gio-2.0)
@ -106,6 +103,10 @@ PKG_CHECK_MODULES(SHELL_HOTPLUG_SNIFFER, gio-2.0 gdk-pixbuf-2.0)
PKG_CHECK_MODULES(BROWSER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION json-glib-1.0 >= 0.13.2) PKG_CHECK_MODULES(BROWSER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION json-glib-1.0 >= 0.13.2)
GJS_VERSION=`$PKG_CONFIG --modversion gjs-internals-1.0`
AC_DEFINE_UNQUOTED([GJS_VERSION], ["$GJS_VERSION"], [The version of GJS we're linking to])
AC_SUBST([GJS_VERSION], ["$GJS_VERSION"])
GOBJECT_INTROSPECTION_CHECK([$GOBJECT_INTROSPECTION_MIN_VERSION]) GOBJECT_INTROSPECTION_CHECK([$GOBJECT_INTROSPECTION_MIN_VERSION])
saved_CFLAGS=$CFLAGS saved_CFLAGS=$CFLAGS
@ -120,7 +121,7 @@ PKG_CHECK_MODULES(GNOME_SHELL_JS, gio-2.0 gjs-internals-1.0 >= $GJS_MIN_VERSION)
PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-3.0 libcroco-0.6 >= 0.6.2 x11) PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-3.0 libcroco-0.6 >= 0.6.2 x11)
PKG_CHECK_MODULES(TRAY, gtk+-3.0) PKG_CHECK_MODULES(TRAY, gtk+-3.0)
PKG_CHECK_MODULES(GVC, libpulse libpulse-mainloop-glib gobject-2.0) PKG_CHECK_MODULES(GVC, libpulse libpulse-mainloop-glib gobject-2.0)
PKG_CHECK_MODULES(DESKTOP_SCHEMAS, gsettings-desktop-schemas >= 3.5.4) PKG_CHECK_MODULES(DESKTOP_SCHEMAS, gsettings-desktop-schemas >= 0.1.7)
AC_MSG_CHECKING([for bluetooth support]) AC_MSG_CHECKING([for bluetooth support])
PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 3.1.0], PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 3.1.0],
@ -236,6 +237,31 @@ AC_ARG_ENABLE(jhbuild-wrapper-script,
AS_HELP_STRING([--enable-jhbuild-wrapper-script],[Make "gnome-shell" script work for jhbuild]),,enable_jhbuild_wrapper_script=no) AS_HELP_STRING([--enable-jhbuild-wrapper-script],[Make "gnome-shell" script work for jhbuild]),,enable_jhbuild_wrapper_script=no)
AM_CONDITIONAL(USE_JHBUILD_WRAPPER_SCRIPT, test "x$enable_jhbuild_wrapper_script" = xyes) AM_CONDITIONAL(USE_JHBUILD_WRAPPER_SCRIPT, test "x$enable_jhbuild_wrapper_script" = xyes)
AC_MSG_CHECKING([location of system Certificate Authority list])
AC_ARG_WITH(ca-certificates,
[AC_HELP_STRING([--with-ca-certificates=@<:@path@:>@],
[path to system Certificate Authority list])])
if test "$with_ca_certificates" = "no"; then
AC_MSG_RESULT([disabled])
else
if test -z "$with_ca_certificates"; then
for f in /etc/pki/tls/certs/ca-bundle.crt \
/etc/ssl/certs/ca-certificates.crt; do
if test -f "$f"; then
with_ca_certificates="$f"
fi
done
if test -z "$with_ca_certificates"; then
AC_MSG_ERROR([could not find. Use --with-ca-certificates=path to set, or --without-ca-certificates to disable])
fi
fi
AC_MSG_RESULT($with_ca_certificates)
AC_DEFINE_UNQUOTED(SHELL_SYSTEM_CA_FILE, ["$with_ca_certificates"], [The system TLS CA list])
fi
AC_SUBST(SHELL_SYSTEM_CA_FILE,["$with_ca_certificates"])
BROWSER_PLUGIN_DIR="${BROWSER_PLUGIN_DIR:-"\${libdir}/mozilla/plugins"}" BROWSER_PLUGIN_DIR="${BROWSER_PLUGIN_DIR:-"\${libdir}/mozilla/plugins"}"
AC_ARG_VAR([BROWSER_PLUGIN_DIR],[Where to install the plugin to]) AC_ARG_VAR([BROWSER_PLUGIN_DIR],[Where to install the plugin to])
@ -249,7 +275,6 @@ AC_CONFIG_FILES([
docs/reference/st/Makefile docs/reference/st/Makefile
docs/reference/st/st-docs.sgml docs/reference/st/st-docs.sgml
js/Makefile js/Makefile
src/calendar-server/evolution-calendar.desktop.in
src/Makefile src/Makefile
browser-plugin/Makefile browser-plugin/Makefile
tests/Makefile tests/Makefile

View File

@ -8,7 +8,14 @@ desktop_DATA = gnome-shell.desktop gnome-shell-extension-prefs.desktop
-e "s|@VERSION[@]|$(VERSION)|" \ -e "s|@VERSION[@]|$(VERSION)|" \
$< > $@ || rm $@ $< > $@ || rm $@
@INTLTOOL_DESKTOP_RULE@ # Placeholder until we add intltool
%.desktop:%.desktop.in
$(AM_V_GEN) sed s/^_// < $< > $@ || rm $@
searchprovidersdir = $(pkgdatadir)/open-search-providers
dist_searchproviders_DATA = \
open-search-providers/google.xml \
open-search-providers/wikipedia.xml
introspectiondir = $(datadir)/dbus-1/interfaces introspectiondir = $(datadir)/dbus-1/interfaces
introspection_DATA = org.gnome.ShellSearchProvider.xml introspection_DATA = org.gnome.ShellSearchProvider.xml
@ -29,31 +36,26 @@ dist_theme_DATA = \
theme/dash-placeholder.svg \ theme/dash-placeholder.svg \
theme/filter-selected-ltr.svg \ theme/filter-selected-ltr.svg \
theme/filter-selected-rtl.svg \ theme/filter-selected-rtl.svg \
theme/gdm.css \
theme/gnome-shell.css \ theme/gnome-shell.css \
theme/logged-in-indicator.svg \
theme/message-tray-background.png \
theme/noise-texture.png \
theme/panel-button-border.svg \ theme/panel-button-border.svg \
theme/panel-button-highlight-narrow.svg \ theme/panel-button-highlight-narrow.svg \
theme/panel-button-highlight-wide.svg \ theme/panel-button-highlight-wide.svg \
theme/process-working.svg \ theme/process-working.svg \
theme/running-indicator.svg \ theme/running-indicator.svg \
theme/scroll-hhandle.svg \
theme/scroll-vhandle.svg \
theme/source-button-border.svg \ theme/source-button-border.svg \
theme/toggle-off-us.svg \ theme/toggle-off-us.svg \
theme/toggle-off-intl.svg \ theme/toggle-off-intl.svg \
theme/toggle-on-us.svg \ theme/toggle-on-us.svg \
theme/toggle-on-intl.svg \ theme/toggle-on-intl.svg \
theme/ws-switch-arrow-up.png \ theme/ws-switch-arrow-up.svg \
theme/ws-switch-arrow-down.png theme/ws-switch-arrow-down.svg
gsettings_SCHEMAS = org.gnome.shell.gschema.xml gsettings_SCHEMAS = org.gnome.shell.gschema.xml org.gnome.shell.evolution.calendar.gschema.xml
@INTLTOOL_XML_NOMERGE_RULE@ @INTLTOOL_XML_NOMERGE_RULE@
%.gschema.xml.in: %.gschema.xml.in.in Makefile
$(AM_V_GEN) sed -e 's|@GETTEXT_PACKAGE[@]|$(GETTEXT_PACKAGE)|g' \
$< > $@ || rm $@
@GSETTINGS_RULES@ @GSETTINGS_RULES@
# We need to compile schemas at make time # We need to compile schemas at make time
@ -66,19 +68,25 @@ all-local: gschemas.compiled
convertdir = $(datadir)/GConf/gsettings convertdir = $(datadir)/GConf/gsettings
convert_DATA = gnome-shell-overrides.convert convert_DATA = gnome-shell-overrides.convert
shadersdir = $(pkgdatadir)/shaders
shaders_DATA = \
shaders/dim-window.glsl
EXTRA_DIST = \ EXTRA_DIST = \
gnome-shell.desktop.in.in \ gnome-shell.desktop.in.in \
gnome-shell-extension-prefs.desktop.in.in \ gnome-shell-extension-prefs.desktop.in.in \
$(introspection_DATA) \ $(introspection_DATA) \
$(menu_DATA) \ $(menu_DATA) \
$(shaders_DATA) \
$(convert_DATA) \ $(convert_DATA) \
org.gnome.shell.gschema.xml.in.in org.gnome.shell.evolution.calendar.gschema.xml.in \
org.gnome.shell.gschema.xml.in
CLEANFILES = \ CLEANFILES = \
gnome-shell.desktop.in \ gnome-shell.desktop.in \
gnome-shell-extension-prefs.in \ gnome-shell-extension-prefs.in \
$(desktop_DATA) \ $(desktop_DATA) \
$(gsettings_SCHEMAS) \ $(gsettings_SCHEMAS) \
gschemas.compiled \ gschemas.compiled
org.gnome.shell.gschema.valid \
org.gnome.shell.gschema.xml.in

View File

@ -0,0 +1,7 @@
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>Google</ShortName>
<Description>Google Search</Description>
<InputEncoding>UTF-8</InputEncoding>
<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEAGABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADs9Pt8xetPtu9FsfFNtu%2BTzvb2%2B%2Fne4dFJeBw0egA%2FfAJAfAA8ewBBegAAAAD%2B%2FPtft98Mp%2BwWsfAVsvEbs%2FQeqvF8xO7%2F%2F%2F63yqkxdgM7gwE%2FggM%2BfQA%2BegBDeQDe7PIbotgQufcMufEPtfIPsvAbs%2FQvq%2Bfz%2Bf%2F%2B%2B%2FZKhR05hgBBhQI8hgBAgAI9ewD0%2B%2Fg3pswAtO8Cxf4Kw%2FsJvvYAqupKsNv%2B%2Fv7%2F%2FP5VkSU0iQA7jQA9hgBDgQU%2BfQH%2F%2Ff%2FQ6fM4sM4KsN8AteMCruIqqdbZ7PH8%2Fv%2Fg6Nc%2Fhg05kAA8jAM9iQI%2BhQA%2BgQDQu6b97uv%2F%2F%2F7V8Pqw3eiWz97q8%2Ff%2F%2F%2F%2F7%2FPptpkkqjQE4kwA7kAA5iwI8iAA8hQCOSSKdXjiyflbAkG7u2s%2F%2B%2F%2F39%2F%2F7r8utrqEYtjQE8lgA7kwA7kwA9jwA9igA9hACiWSekVRyeSgiYSBHx6N%2F%2B%2Fv7k7OFRmiYtlAA5lwI7lwI4lAA7kgI9jwE9iwI4iQCoVhWcTxCmb0K%2BooT8%2Fv%2F7%2F%2F%2FJ2r8fdwI1mwA3mQA3mgA8lAE8lAE4jwA9iwE%2BhwGfXifWvqz%2B%2Ff%2F58u%2Fev6Dt4tr%2B%2F%2F2ZuIUsggA7mgM6mAM3lgA5lgA6kQE%2FkwBChwHt4dv%2F%2F%2F728ei1bCi7VAC5XQ7kz7n%2F%2F%2F6bsZkgcB03lQA9lgM7kwA2iQktZToPK4r9%2F%2F%2F9%2F%2F%2FSqYK5UwDKZAS9WALIkFn%2B%2F%2F3%2F%2BP8oKccGGcIRJrERILYFEMwAAuEAAdX%2F%2Ff7%2F%2FP%2B%2BfDvGXQLIZgLEWgLOjlf7%2F%2F%2F%2F%2F%2F9QU90EAPQAAf8DAP0AAfMAAOUDAtr%2F%2F%2F%2F7%2B%2Fu2bCTIYwDPZgDBWQDSr4P%2F%2Fv%2F%2F%2FP5GRuABAPkAA%2FwBAfkDAPAAAesAAN%2F%2F%2B%2Fz%2F%2F%2F64g1C5VwDMYwK8Yg7y5tz8%2Fv%2FV1PYKDOcAAP0DAf4AAf0AAfYEAOwAAuAAAAD%2F%2FPvi28ymXyChTATRrIb8%2F%2F3v8fk6P8MAAdUCAvoAAP0CAP0AAfYAAO4AAACAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAA</Image>
<Url type="text/html" method="GET" template="http://www.google.com/search?q={searchTerms}"/>
</OpenSearchDescription>

View File

@ -0,0 +1,44 @@
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>Wikipedia</ShortName>
<Description>Wikipedia, the free encyclopedia</Description>
<InputEncoding>UTF-8</InputEncoding>
<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAEAgQAhIOEAMjHyABIR0gA6ejpAGlqaQCpqKkAKCgoAPz9%2FAAZGBkAmJiYANjZ2ABXWFcAent6ALm6uQA8OjwAiIiIiIiIiIiIiI4oiL6IiIiIgzuIV4iIiIhndo53KIiIiB%2FWvXoYiIiIfEZfWBSIiIEGi%2FfoqoiIgzuL84i9iIjpGIoMiEHoiMkos3FojmiLlUipYliEWIF%2BiDe0GoRa7D6GPbjcu1yIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Image>
<Url type="text/html" method="GET" template="http://{language}.wikipedia.org/wiki/Special:Search?search={searchTerms}"/>
<!-- The criterion for being below is being listed with more than 100,000
articles on http://meta.wikimedia.org/wiki/List_of_Wikipedias -->
<Language>ar</Language>
<Language>bg</Language>
<Language>ca</Language>
<Language>cs</Language>
<Language>da</Language>
<Language>de</Language>
<Language>en</Language>
<Language>eo</Language>
<Language>es</Language>
<Language>fa</Language>
<Language>fi</Language>
<Language>fr</Language>
<Language>he</Language>
<Language>hu</Language>
<Language>id</Language>
<Language>it</Language>
<Language>ja</Language>
<Language>ko</Language>
<Language>lt</Language>
<Language>nl</Language>
<Language>no</Language>
<Language>pl</Language>
<Language>pt</Language>
<Language>ro</Language>
<Language>ru</Language>
<Language>sk</Language>
<Language>sl</Language>
<Language>sr</Language>
<Language>sv</Language>
<Language>tr</Language>
<Language>uk</Language>
<Language>vi</Language>
<Language>vo</Language>
<Language>war</Language>
<Language>zh</Language>
</OpenSearchDescription>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- NOTE: This schema is a GNOME 3.4 workaround - it uses the same path
as org.gnome.evolution.calendar, but avoids us requiring Evolution
be installed. In GNOME 3.6 the selected state will become a flag
on the calendar. Because the translations are in Evolution,
this is untranslated and in POTFILES.skip.
-->
<schemalist>
<schema path="/org/gnome/evolution/calendar/" id="org.gnome.shell.evolution.calendar" gettext-domain="evolution">
<key type="as" name="selected-calendars">
<default>[]</default>
<summary>List of selected calendars</summary>
<description>List of calendars to load</description>
</key>
<key type="as" name="selected-tasks">
<default>[]</default>
<summary>List of selected task lists</summary>
<description>List of task lists to load</description>
</key>
</schema>
</schemalist>

View File

@ -61,6 +61,7 @@ value here is from the TpConnectionPresenceType enumeration.</_summary>
<_summary>Internally used to store the last session presence status for the user. The <_summary>Internally used to store the last session presence status for the user. The
value here is from the GsmPresenceStatus enumeration.</_summary> value here is from the GsmPresenceStatus enumeration.</_summary>
</key> </key>
<child name="clock" schema="org.gnome.shell.clock"/>
<child name="calendar" schema="org.gnome.shell.calendar"/> <child name="calendar" schema="org.gnome.shell.calendar"/>
<child name="recorder" schema="org.gnome.shell.recorder"/> <child name="recorder" schema="org.gnome.shell.recorder"/>
<child name="keybindings" schema="org.gnome.shell.keybindings"/> <child name="keybindings" schema="org.gnome.shell.keybindings"/>
@ -87,13 +88,6 @@ value here is from the GsmPresenceStatus enumeration.</_summary>
Keybinding to open the application menu. Keybinding to open the application menu.
</_description> </_description>
</key> </key>
<key name="toggle-message-tray" type="as">
<default>["&lt;Super&gt;m"]</default>
<_summary>Keybinding to toggle the visibility of the message tray</_summary>
<_description>
Keybinding to toggle the visibility of the message tray.
</_description>
</key>
<key name="toggle-recording" type="as"> <key name="toggle-recording" type="as">
<default><![CDATA[['<Control><Shift><Alt>r']]]></default> <default><![CDATA[['<Control><Shift><Alt>r']]]></default>
<_summary>Keybinding to toggle the screen recorder</_summary> <_summary>Keybinding to toggle the screen recorder</_summary>
@ -114,6 +108,24 @@ value here is from the GsmPresenceStatus enumeration.</_summary>
</key> </key>
</schema> </schema>
<schema id="org.gnome.shell.clock" path="/org/gnome/shell/clock/"
gettext-domain="@GETTEXT_PACKAGE@">
<key name="show-seconds" type="b">
<default>false</default>
<_summary>Show time with seconds</_summary>
<_description>
If true, display seconds in time.
</_description>
</key>
<key name="show-date" type="b">
<default>false</default>
<_summary>Show date in clock</_summary>
<_description>
If true, display date in the clock, in addition to time.
</_description>
</key>
</schema>
<schema id="org.gnome.shell.recorder" path="/org/gnome/shell/recorder/" <schema id="org.gnome.shell.recorder" path="/org/gnome/shell/recorder/"
gettext-domain="@GETTEXT_PACKAGE@"> gettext-domain="@GETTEXT_PACKAGE@">
<key name="framerate" type="i"> <key name="framerate" type="i">

View File

@ -0,0 +1,27 @@
#version 110
uniform sampler2D tex;
uniform float fraction;
uniform float height;
const float c = -0.2;
const float border_max_height = 60.0;
mat4 contrast = mat4 (1.0 + c, 0.0, 0.0, 0.0,
0.0, 1.0 + c, 0.0, 0.0,
0.0, 0.0, 1.0 + c, 0.0,
0.0, 0.0, 0.0, 1.0);
vec4 off = vec4(0.633, 0.633, 0.633, 0);
void main()
{
vec4 color = texture2D(tex, cogl_tex_coord_in[0].xy);
float y = height * cogl_tex_coord_in[0].y;
// To reduce contrast, blend with a mid gray
cogl_color_out = color * contrast - off * c * color.a;
// We only fully dim at a distance of BORDER_MAX_HEIGHT from the top and
// when the fraction is 1.0. For other locations and fractions we linearly
// interpolate back to the original undimmed color, so the top of the window
// is at full color.
cogl_color_out = color + (cogl_color_out - color) * max(min(y / border_max_height, 1.0), 0.0);
cogl_color_out = color + (cogl_color_out - color) * fraction;
}

View File

@ -10,11 +10,11 @@
xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="29" width="28"
height="29" height="25"
id="svg10621" id="svg10621"
version="1.1" version="1.1"
inkscape:version="0.48.2 r9819" inkscape:version="0.48.1 r9760"
sodipodi:docname="calendar-today.svg"> sodipodi:docname="calendar-today.svg">
<defs <defs
id="defs10623"> id="defs10623">
@ -118,17 +118,6 @@
fx="51" fx="51"
fy="30" fy="30"
r="42" /> r="42" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient34508-1-3"
id="radialGradient3113"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.72146227,0,0,0.27484277,14.205424,21.754717)"
cx="51"
cy="30"
fx="51"
fy="30"
r="42" />
</defs> </defs>
<sodipodi:namedview <sodipodi:namedview
id="base" id="base"
@ -138,29 +127,20 @@
inkscape:pageopacity="0" inkscape:pageopacity="0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:zoom="15.839192" inkscape:zoom="15.839192"
inkscape:cx="20.652108" inkscape:cx="8.3750933"
inkscape:cy="11.839084" inkscape:cy="8.0837211"
inkscape:document-units="px" inkscape:document-units="px"
inkscape:current-layer="layer1" inkscape:current-layer="layer1"
showgrid="true" showgrid="false"
fit-margin-top="0" fit-margin-top="0"
fit-margin-left="0" fit-margin-left="0"
fit-margin-right="0" fit-margin-right="0"
fit-margin-bottom="0" fit-margin-bottom="0"
inkscape:window-width="1280" inkscape:window-width="1440"
inkscape:window-height="741" inkscape:window-height="843"
inkscape:window-x="0" inkscape:window-x="0"
inkscape:window-y="27" inkscape:window-y="26"
inkscape:window-maximized="1" inkscape:window-maximized="1" />
borderlayer="true">
<inkscape:grid
type="xygrid"
id="grid3109"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
</sodipodi:namedview>
<metadata <metadata
id="metadata10626"> id="metadata10626">
<rdf:RDF> <rdf:RDF>
@ -177,28 +157,31 @@
inkscape:label="Layer 1" inkscape:label="Layer 1"
inkscape:groupmode="layer" inkscape:groupmode="layer"
id="layer1" id="layer1"
transform="translate(-469.08263,-532.99307)"> transform="translate(-469.08263,-536.99307)">
<path <g
sodipodi:type="arc" id="g3003">
style="opacity:0.4625;color:#000000;fill:url(#radialGradient3113);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" <path
id="path34506-3" inkscape:export-ydpi="90"
sodipodi:cx="51" inkscape:export-xdpi="90"
sodipodi:cy="30" inkscape:export-filename="/home/jimmac/src/cvs/gnome/gnome-shell-design/mockups/motion/textures/panel.png"
sodipodi:rx="42" transform="matrix(0.43692393,0,0,1.3783114,460.60467,517.48289)"
sodipodi:ry="16" sodipodi:end="6.2831853"
d="M 9,29.999999 A 42,16 0 0 1 93,30 l -42,0 z" sodipodi:start="3.1415927"
sodipodi:start="3.1415927" d="M 9,29.999999 C 9.0000011,21.163443 27.804042,14 51.000002,14 74.195961,14 93,21.163444 93,30 l -42,0 z"
sodipodi:end="6.2831853" sodipodi:ry="16"
transform="matrix(0.43692393,0,0,1.3783114,461.29951,517.6437)" sodipodi:rx="42"
inkscape:export-filename="/home/jimmac/src/cvs/gnome/gnome-shell-design/mockups/motion/textures/panel.png" sodipodi:cy="30"
inkscape:export-xdpi="90" sodipodi:cx="51"
inkscape:export-ydpi="90" /> id="path34506-3"
<rect style="opacity:0.4625;color:#000000;fill:url(#radialGradient2997);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
style="fill:#ffffff;fill-opacity:0.50196078;stroke-width:0.43599999;stroke-miterlimit:4;stroke-dasharray:none" sodipodi:type="arc" />
id="rect2996" <rect
width="31" y="558.85046"
height="3" x="468.96878"
x="468.08264" height="3.1425927"
y="558.99304" /> width="28.149134"
id="rect2996"
style="fill:#ffffff;fill-opacity:0.50196078;stroke-width:0.43599999;stroke-miterlimit:4;stroke-dasharray:none" />
</g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

180
data/theme/gdm.css Normal file
View File

@ -0,0 +1,180 @@
/* Copyright 2011, Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU Lesser General Public License,
* version 2.1, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* Login Dialog */
.login-dialog-title {
font-size: 14pt;
font-weight: bold;
color: #666666;
padding-bottom: 2em;
}
.login-dialog {
border-radius: 16px;
min-height: 150px;
max-height: 700px;
min-width: 350px;
}
.login-dialog-prompt-fingerprint-message {
font-size: 10.5pt;
}
.login-dialog-user-list-view {
-st-vfade-offset: 1em;
}
.login-dialog-user-list {
spacing: 12px;
}
.login-dialog-user-list-item {
color: #666666;
}
.login-dialog-user-list-item:ltr {
padding-right: 1em;
}
.login-dialog-user-list-item:rtl {
padding-left: 1em;
}
.login-dialog-user-list-item .login-dialog-user-list-item-name {
font-size: 20pt;
padding-left: 1em;
color: #666666;
}
.login-dialog-user-list-item:hover .login-dialog-user-list-item-name {
color: white;
}
.login-dialog-user-list-item:focus .login-dialog-user-list-item-name {
color: white;
text-shadow: black 0px 2px 2px;
}
.login-dialog-user-list-item-vertical-layout {
spacing: 2px;
}
.login-dialog-user-list-item .login-dialog-user-list-item-focus-bin {
background-color: rgba(0,0,0,0.0);
height: 2px;
}
.login-dialog-user-list-item:focus .login-dialog-user-list-item-focus-bin {
background-color: #666666;
}
.login-dialog-user-list-item-icon {
border: 2px solid #8b8b8b;
border-radius: 8px;
width: 64px;
height: 64px;
}
.login-dialog-not-listed-button {
padding-top: 2em;
}
.login-dialog-not-listed-label {
font-size: 14pt;
font-weight: bold;
color: #666666;
}
.login-dialog-not-listed-button:hover .login-dialog-not-listed-label {
color: white;
}
.login-dialog-prompt-layout {
padding-bottom: 32px;
}
.login-dialog-prompt-label {
color: white;
font-size: 20pt;
}
.login-dialog-prompt-entry {
padding: 4px;
border-radius: 4px;
border: 2px solid #5656cc;
color: black;
background-color: white;
caret-color: black;
caret-size: 1px;
width: 15em;
}
.login-dialog-prompt-entry .capslock-warning {
icon-size: 16px;
warning-color: #999;
}
.login-dialog-prompt-entry:insensitive {
color: rgba(0,0,0,0.7);
border: 2px solid #565656;
}
.login-dialog-session-list {
color: #ffffff;
font-size: 10.5pt;
}
.login-dialog-session-list-button {
padding: 4px;
}
.login-dialog-session-list-button:focus {
background-color: #4c4c4c;
}
.login-dialog-session-list-button:active {
background-color: #4c4c4c;
}
.login-dialog-session-list-button:hover {
font-weight: bold;
}
.login-dialog-session-list-scroll-view {
background-gradient-start: rgba(80,80,80,0.3);
background-gradient-end: rgba(80,80,80,0.7);
background-gradient-direction: vertical;
box-shadow: inset 0px 2px 4px rgba(0,0,0,0.9);
border-radius: 8px;
border: 1px solid rgba(80,80,80,1.0);
padding: .5em;
}
.login-dialog-session-list-item:focus {
background-color: #666666;
}
.login-dialog-session-list-triangle {
padding-right: .5em;
}
.login-dialog-session-list-item-box {
spacing: .25em;
}
.login-dialog-session-list-item-dot {
width: .75em;
height: .75em;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,130 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="300"
height="80"
id="svg7355"
version="1.1"
inkscape:version="0.48.2 r9819"
sodipodi:docname="logged-in-indicator.svg">
<metadata
id="metadata4175">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#2c1cff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:window-width="1440"
inkscape:window-height="843"
id="namedview4173"
showgrid="false"
inkscape:zoom="2.8760889"
inkscape:cx="106.00403"
inkscape:cy="80.68078"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="g30864" />
<defs
id="defs7357">
<radialGradient
xlink:href="#linearGradient36429"
id="radialGradient7461"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.5919312,0,0,0.57582113,-20.687059,48.400487)"
cx="47.428951"
cy="167.16817"
fx="47.428951"
fy="167.16817"
r="37" />
<linearGradient
id="linearGradient36429">
<stop
id="stop36431"
offset="0"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop36433"
offset="1"
style="stop-color:#ffffff;stop-opacity:0;" />
</linearGradient>
<radialGradient
xlink:href="#linearGradient36471"
id="radialGradient7463"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.1891549,0,0,0.55513246,-9.281289,36.12653)"
cx="49.067139"
cy="242.50381"
fx="49.067139"
fy="242.50381"
r="37.00671" />
<linearGradient
id="linearGradient36471">
<stop
id="stop36473"
offset="0"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop36475"
offset="1"
style="stop-color:#ffffff;stop-opacity:0;" />
</linearGradient>
<radialGradient
r="37.00671"
fy="242.50381"
fx="49.067139"
cy="242.50381"
cx="49.067139"
gradientTransform="matrix(3.4218418,0,0,0.03365337,-61.309005,138.5071)"
gradientUnits="userSpaceOnUse"
id="radialGradient7488"
xlink:href="#linearGradient36471" />
</defs>
<g
id="layer1"
transform="matrix(1.6213276,0,0,1.6213276,-431.6347,-272.5745)">
<g
style="display:inline"
id="g30864"
transform="translate(255.223,70.118091)">
<rect
ry="3.4593496"
rx="8.8641119"
y="76.159348"
x="12.596948"
height="71.116341"
width="182.22595"
id="rect14000"
style="opacity:0.371875;fill:url(#radialGradient7461);fill-opacity:1;stroke:none" />
<path
id="rect34520"
d="m 194.80022,146.83551 -182.559919,0"
style="opacity:0.35;fill:none;stroke:url(#radialGradient7488);stroke-width:0.61184424;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
connector-curvature="0"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

View File

@ -9,7 +9,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="17" width="21"
height="10" height="10"
id="svg2" id="svg2"
version="1.1" version="1.1"
@ -66,9 +66,9 @@
<rect <rect
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke-width:0.43599999;stroke-miterlimit:4;stroke-dasharray:none" style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke-width:0.43599999;stroke-miterlimit:4;stroke-dasharray:none"
id="rect3796" id="rect3796"
width="7" width="3"
height="2" height="2"
x="5" x="9"
y="8" /> y="8" />
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="10"
height="4"
id="svg2"
version="1.1"
inkscape:version="0.47 r22583"
sodipodi:docname="scroll-hhandle.svg">
<defs
id="defs4">
</defs>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#323232;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="rect3592"
width="2"
height="4"
x="0"
y="0"
rx="0"
ry="0" />
<use
x="0"
y="0"
xlink:href="#rect3592"
id="use2825"
transform="translate(8,0)"
width="10"
height="4" />
<use
x="0"
y="0"
xlink:href="#use2825"
id="use2827"
transform="translate(-4,0)"
width="10"
height="4" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="4"
height="10"
id="svg2"
version="1.1"
inkscape:version="0.47 r22583"
sodipodi:docname="scroll-hhandle.svg">
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#323232;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="rect3592"
width="2"
height="4"
x="0"
y="-4"
rx="0"
ry="0"
transform="matrix(0,1,-1,0,0,0)" />
<use
x="0"
y="0"
xlink:href="#rect3592"
id="use3705"
transform="translate(0,4)"
width="4"
height="10" />
<use
x="0"
y="0"
xlink:href="#use3705"
id="use3707"
transform="translate(0,4)"
width="4"
height="10" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 850 B

View File

@ -0,0 +1,376 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="96"
height="96"
id="svg25070"
inkscape:version="0.48.0 r9654"
sodipodi:docname="ws-switch-arrow-down.svg">
<metadata
id="metadata3353">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="718"
inkscape:window-height="480"
id="namedview3351"
showgrid="false"
inkscape:zoom="2.6979167"
inkscape:cx="48"
inkscape:cy="48"
inkscape:window-x="0"
inkscape:window-y="26"
inkscape:window-maximized="0"
inkscape:current-layer="svg25070" />
<defs
id="defs25072">
<linearGradient
x1="-86.552246"
y1="185.439"
x2="-83.37072"
y2="197.31261"
id="linearGradient24957"
xlink:href="#linearGradient4034-0-4"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(6,0)" />
<linearGradient
id="linearGradient4034-0-4">
<stop
id="stop4036-5-7"
style="stop-color:#eeeeec;stop-opacity:1"
offset="0" />
<stop
id="stop4038-9-6"
style="stop-color:#babdb6;stop-opacity:1"
offset="1" />
</linearGradient>
<filter
x="0"
y="0"
width="1"
height="1"
color-interpolation-filters="sRGB"
id="filter24765">
<feColorMatrix
result="fbSourceGraphic"
values="1"
type="saturate"
id="feColorMatrix24767" />
<feColorMatrix
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
in="fbSourceGraphic"
id="feColorMatrix24769" />
</filter>
<linearGradient
x1="-74.520325"
y1="169.06032"
x2="-74.520325"
y2="205.94189"
id="linearGradient24955"
xlink:href="#linearGradient4632-1-3-9-3-2"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-5,0)" />
<linearGradient
id="linearGradient4632-1-3-9-3-2">
<stop
id="stop4634-1-8-3-9-0"
style="stop-color:#eeeeec;stop-opacity:1"
offset="0" />
<stop
id="stop4636-1-9-9-8-8"
style="stop-color:#ffffff;stop-opacity:1"
offset="0.0274937" />
<stop
id="stop4638-8-3-9-6-6"
style="stop-color:#f2f2f2;stop-opacity:1"
offset="0.274937" />
<stop
id="stop4640-8-5-7-8-9"
style="stop-color:#eeeeec;stop-opacity:1"
offset="0.38707438" />
<stop
id="stop4642-5-41-9-6-9"
style="stop-color:#d9dad8;stop-opacity:1"
offset="0.66528589" />
<stop
id="stop4644-5-2-7-9-2"
style="stop-color:#dfe0dd;stop-opacity:1"
offset="0.76745707" />
<stop
id="stop4646-3-2-3-7-3"
style="stop-color:#f0f0f0;stop-opacity:1"
offset="1" />
</linearGradient>
<radialGradient
cx="-33.412369"
cy="185.74171"
r="2.3554697"
fx="-33.412369"
fy="185.74171"
id="radialGradient24959"
xlink:href="#linearGradient4869-4-1"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0075,0,0,1.0075,-5.4544,-1.25141)" />
<linearGradient
id="linearGradient4869-4-1">
<stop
id="stop4871-6-2"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop4879-7-4"
style="stop-color:#eeeeec;stop-opacity:1"
offset="0.31807542" />
<stop
id="stop4877-6-1"
style="stop-color:#c8c9c6;stop-opacity:1"
offset="0.74691135" />
<stop
id="stop4873-1-0"
style="stop-color:#d3d7cf;stop-opacity:1"
offset="1" />
</linearGradient>
<filter
x="0"
y="0"
width="1"
height="1"
color-interpolation-filters="sRGB"
id="filter25011">
<feColorMatrix
result="fbSourceGraphic"
values="1"
type="saturate"
id="feColorMatrix25013" />
<feColorMatrix
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
in="fbSourceGraphic"
id="feColorMatrix25015" />
</filter>
<radialGradient
cx="-33.412369"
cy="185.74171"
r="2.3554697"
fx="-33.412369"
fy="185.74171"
id="radialGradient24961"
xlink:href="#linearGradient4869-4-0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0075,0,0,1.0075,-5.4544,-1.25141)" />
<linearGradient
id="linearGradient4869-4-0">
<stop
id="stop4871-6-8"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop4879-7-5"
style="stop-color:#eeeeec;stop-opacity:1"
offset="0.31807542" />
<stop
id="stop4877-6-5"
style="stop-color:#c8c9c6;stop-opacity:1"
offset="0.74691135" />
<stop
id="stop4873-1-4"
style="stop-color:#d3d7cf;stop-opacity:1"
offset="1" />
</linearGradient>
<filter
x="0"
y="0"
width="1"
height="1"
color-interpolation-filters="sRGB"
id="filter25023">
<feColorMatrix
result="fbSourceGraphic"
values="1"
type="saturate"
id="feColorMatrix25025" />
<feColorMatrix
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
in="fbSourceGraphic"
id="feColorMatrix25027" />
</filter>
<linearGradient
x1="-39.858727"
y1="184.61784"
x2="-38.244785"
y2="188.84898"
id="linearGradient24963"
xlink:href="#linearGradient4941"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient4941">
<stop
id="stop4943"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop4945"
style="stop-color:#ffffff;stop-opacity:0"
offset="1" />
</linearGradient>
<filter
x="0"
y="0"
width="1"
height="1"
color-interpolation-filters="sRGB"
id="filter25033">
<feColorMatrix
result="fbSourceGraphic"
values="1"
type="saturate"
id="feColorMatrix25035" />
<feColorMatrix
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
in="fbSourceGraphic"
id="feColorMatrix25037" />
</filter>
<linearGradient
x1="-39.858727"
y1="184.61784"
x2="-38.244785"
y2="188.84898"
id="linearGradient24965"
xlink:href="#linearGradient4941-7"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient4941-7">
<stop
id="stop4943-2"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop4945-5"
style="stop-color:#ffffff;stop-opacity:0"
offset="1" />
</linearGradient>
<filter
x="0"
y="0"
width="1"
height="1"
color-interpolation-filters="sRGB"
id="filter25043">
<feColorMatrix
result="fbSourceGraphic"
values="1"
type="saturate"
id="feColorMatrix25045" />
<feColorMatrix
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
in="fbSourceGraphic"
id="feColorMatrix25047" />
</filter>
<filter
x="0"
y="0"
width="1"
height="1"
color-interpolation-filters="sRGB"
id="filter25049">
<feColorMatrix
result="fbSourceGraphic"
values="1"
type="saturate"
id="feColorMatrix25051" />
<feColorMatrix
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
in="fbSourceGraphic"
id="feColorMatrix25053" />
</filter>
<filter
x="0"
y="0"
width="1"
height="1"
color-interpolation-filters="sRGB"
id="filter25055">
<feColorMatrix
result="fbSourceGraphic"
values="1"
type="saturate"
id="feColorMatrix25057" />
<feColorMatrix
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
in="fbSourceGraphic"
id="feColorMatrix25059" />
</filter>
</defs>
<g
transform="matrix(0,1,-1,0,48.0003,4.1307112e-7)"
id="layer1">
<g
transform="matrix(-2,0,0,2,-97.2497,-374.967)"
id="g4030-1-8"
style="stroke:#000000;stroke-opacity:1;display:inline">
<path
d="m -72.5,173.5 -14,14 14,14"
id="path3165-7-3"
style="color:#000000;fill:none;stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
inkscape:connector-curvature="0" />
</g>
<path
d="m -36.5,186.40625 a 2.09375,2.09375 0 1 1 -4.1875,0 2.09375,2.09375 0 1 1 4.1875,0 z"
transform="matrix(-3.34328,0,0,3.34328,-89.2797,-623.176)"
id="path4050-2-7-9-4"
style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52343899;marker:none;visibility:visible;display:inline;overflow:visible"
inkscape:connector-curvature="0" />
<path
d="m -36.5,186.40625 a 2.09375,2.09375 0 1 1 -4.1875,0 2.09375,2.09375 0 1 1 4.1875,0 z"
transform="matrix(-3.34328,0,0,3.34328,-111.2797,-623.176)"
id="path4050-2-7-9-4-8"
style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52343899;marker:none;visibility:visible;display:inline;overflow:visible"
inkscape:connector-curvature="0" />
<path
d="m -36.5,186.40625 a 2.09375,2.09375 0 1 1 -4.1875,0 2.09375,2.09375 0 1 1 4.1875,0 z"
transform="matrix(-2.86565,0,0,2.86565,-70.8457,-534.143)"
id="path4050-2-7-9-4-0"
style="color:#000000;fill:none;stroke:#000000;stroke-width:0.69792098;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
inkscape:connector-curvature="0" />
<path
d="m -36.5,186.40625 a 2.09375,2.09375 0 1 1 -4.1875,0 2.09375,2.09375 0 1 1 4.1875,0 z"
transform="matrix(-2.86565,0,0,2.86565,-92.8457,-534.143)"
id="path4050-2-7-9-4-0-9"
style="color:#000000;fill:none;stroke:#000000;stroke-width:0.69792098;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
inkscape:connector-curvature="0" />
<path
d="m 47.87528,-34.0295 c 1.53896,0.0448 3.0511,0.70928 4.125,1.8125 l 32.25,32.25 -32.25,32.25 c -2.2253,2.2253 -6.2747,2.2253 -8.5,0 -2.2253,-2.22528 -2.2253,-6.2747 0,-8.5 l 23.75,-23.75 -23.75,-23.75 c -1.73168,-1.6731 -2.295,-4.44228 -1.3546,-6.65894 0.94042,-2.21668 3.32312,-3.73604 5.7296,-3.65356 z"
id="path3165-7-3-1"
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0pt;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;text-anchor:start;opacity:0.35;color:#000000;fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;font-family:Bitstream Vera Sans"
inkscape:connector-curvature="0" />
<path
d="m 41.8316,28.09418 c -0.014,-1.58898 0.54158,-3.18406 1.66868,-4.31118 l 23.75,-23.75 m -25.1046,-30.40894 c 0.94042,-2.21668 3.32312,-3.73604 5.7296,-3.65356 1.53896,0.0448 3.0511,0.70928 4.125,1.8125 l 32.25,32.25"
id="path3165-7-3-1-9"
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0pt;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;text-anchor:start;color:#000000;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;font-family:Bitstream Vera Sans"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 841 B

View File

@ -0,0 +1,447 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="96"
height="96"
id="svg25070"
version="1.1"
inkscape:version="0.48.0 r9654"
sodipodi:docname="ws-switch-arrow-up.svg">
<defs
id="defs25072">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 24 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="48 : 24 : 1"
inkscape:persp3d-origin="24 : 16 : 1"
id="perspective25078" />
<inkscape:perspective
id="perspective24985"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4034-0-4"
id="linearGradient24957"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(6)"
x1="-86.552246"
y1="185.439"
x2="-83.37072"
y2="197.31261" />
<linearGradient
inkscape:collect="always"
id="linearGradient4034-0-4">
<stop
style="stop-color: rgb(238, 238, 236); stop-opacity: 1;"
offset="0"
id="stop4036-5-7" />
<stop
style="stop-color: rgb(186, 189, 182); stop-opacity: 1;"
offset="1"
id="stop4038-9-6" />
</linearGradient>
<filter
id="filter24765"
inkscape:label="Invert"
x="0"
y="0"
width="1"
height="1"
inkscape:menu="Color"
inkscape:menu-tooltip="Invert colors"
color-interpolation-filters="sRGB">
<feColorMatrix
id="feColorMatrix24767"
type="saturate"
values="1"
result="fbSourceGraphic" />
<feColorMatrix
id="feColorMatrix24769"
in="fbSourceGraphic"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" />
</filter>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4632-1-3-9-3-2"
id="linearGradient24955"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-5)"
x1="-74.520325"
y1="169.06032"
x2="-74.520325"
y2="205.94189" />
<linearGradient
id="linearGradient4632-1-3-9-3-2">
<stop
style="stop-color: rgb(238, 238, 236); stop-opacity: 1;"
offset="0"
id="stop4634-1-8-3-9-0" />
<stop
id="stop4636-1-9-9-8-8"
offset="0.0274937"
style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" />
<stop
id="stop4638-8-3-9-6-6"
offset="0.274937"
style="stop-color: rgb(242, 242, 242); stop-opacity: 1;" />
<stop
id="stop4640-8-5-7-8-9"
offset="0.38707438"
style="stop-color: rgb(238, 238, 236); stop-opacity: 1;" />
<stop
id="stop4642-5-41-9-6-9"
offset="0.66528589"
style="stop-color: rgb(217, 218, 216); stop-opacity: 1;" />
<stop
id="stop4644-5-2-7-9-2"
offset="0.76745707"
style="stop-color: rgb(223, 224, 221); stop-opacity: 1;" />
<stop
style="stop-color: rgb(240, 240, 240); stop-opacity: 1;"
offset="1"
id="stop4646-3-2-3-7-3" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4869-4-1"
id="radialGradient24959"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0075, 0, 0, 1.0075, -5.4544, -1.25141)"
cx="-33.412369"
cy="185.74171"
fx="-33.412369"
fy="185.74171"
r="2.3554697" />
<linearGradient
id="linearGradient4869-4-1">
<stop
style="stop-color: rgb(255, 255, 255); stop-opacity: 1;"
offset="0"
id="stop4871-6-2" />
<stop
id="stop4879-7-4"
offset="0.31807542"
style="stop-color: rgb(238, 238, 236); stop-opacity: 1;" />
<stop
id="stop4877-6-1"
offset="0.74691135"
style="stop-color: rgb(200, 201, 198); stop-opacity: 1;" />
<stop
style="stop-color: rgb(211, 215, 207); stop-opacity: 1;"
offset="1"
id="stop4873-1-0" />
</linearGradient>
<filter
id="filter25011"
inkscape:label="Invert"
x="0"
y="0"
width="1"
height="1"
inkscape:menu="Color"
inkscape:menu-tooltip="Invert colors"
color-interpolation-filters="sRGB">
<feColorMatrix
id="feColorMatrix25013"
type="saturate"
values="1"
result="fbSourceGraphic" />
<feColorMatrix
id="feColorMatrix25015"
in="fbSourceGraphic"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" />
</filter>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4869-4-0"
id="radialGradient24961"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0075, 0, 0, 1.0075, -5.4544, -1.25141)"
cx="-33.412369"
cy="185.74171"
fx="-33.412369"
fy="185.74171"
r="2.3554697" />
<linearGradient
id="linearGradient4869-4-0">
<stop
style="stop-color: rgb(255, 255, 255); stop-opacity: 1;"
offset="0"
id="stop4871-6-8" />
<stop
id="stop4879-7-5"
offset="0.31807542"
style="stop-color: rgb(238, 238, 236); stop-opacity: 1;" />
<stop
id="stop4877-6-5"
offset="0.74691135"
style="stop-color: rgb(200, 201, 198); stop-opacity: 1;" />
<stop
style="stop-color: rgb(211, 215, 207); stop-opacity: 1;"
offset="1"
id="stop4873-1-4" />
</linearGradient>
<filter
id="filter25023"
inkscape:label="Invert"
x="0"
y="0"
width="1"
height="1"
inkscape:menu="Color"
inkscape:menu-tooltip="Invert colors"
color-interpolation-filters="sRGB">
<feColorMatrix
id="feColorMatrix25025"
type="saturate"
values="1"
result="fbSourceGraphic" />
<feColorMatrix
id="feColorMatrix25027"
in="fbSourceGraphic"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" />
</filter>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4941"
id="linearGradient24963"
gradientUnits="userSpaceOnUse"
x1="-39.858727"
y1="184.61784"
x2="-38.244785"
y2="188.84898" />
<linearGradient
inkscape:collect="always"
id="linearGradient4941">
<stop
style="stop-color: rgb(255, 255, 255); stop-opacity: 1;"
offset="0"
id="stop4943" />
<stop
style="stop-color: rgb(255, 255, 255); stop-opacity: 0;"
offset="1"
id="stop4945" />
</linearGradient>
<filter
id="filter25033"
inkscape:label="Invert"
x="0"
y="0"
width="1"
height="1"
inkscape:menu="Color"
inkscape:menu-tooltip="Invert colors"
color-interpolation-filters="sRGB">
<feColorMatrix
id="feColorMatrix25035"
type="saturate"
values="1"
result="fbSourceGraphic" />
<feColorMatrix
id="feColorMatrix25037"
in="fbSourceGraphic"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" />
</filter>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4941-7"
id="linearGradient24965"
gradientUnits="userSpaceOnUse"
x1="-39.858727"
y1="184.61784"
x2="-38.244785"
y2="188.84898" />
<linearGradient
inkscape:collect="always"
id="linearGradient4941-7">
<stop
style="stop-color: rgb(255, 255, 255); stop-opacity: 1;"
offset="0"
id="stop4943-2" />
<stop
style="stop-color: rgb(255, 255, 255); stop-opacity: 0;"
offset="1"
id="stop4945-5" />
</linearGradient>
<filter
id="filter25043"
inkscape:label="Invert"
x="0"
y="0"
width="1"
height="1"
inkscape:menu="Color"
inkscape:menu-tooltip="Invert colors"
color-interpolation-filters="sRGB">
<feColorMatrix
id="feColorMatrix25045"
type="saturate"
values="1"
result="fbSourceGraphic" />
<feColorMatrix
id="feColorMatrix25047"
in="fbSourceGraphic"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" />
</filter>
<filter
id="filter25049"
inkscape:label="Invert"
x="0"
y="0"
width="1"
height="1"
inkscape:menu="Color"
inkscape:menu-tooltip="Invert colors"
color-interpolation-filters="sRGB">
<feColorMatrix
id="feColorMatrix25051"
type="saturate"
values="1"
result="fbSourceGraphic" />
<feColorMatrix
id="feColorMatrix25053"
in="fbSourceGraphic"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" />
</filter>
<filter
id="filter25055"
inkscape:label="Invert"
x="0"
y="0"
width="1"
height="1"
inkscape:menu="Color"
inkscape:menu-tooltip="Invert colors"
color-interpolation-filters="sRGB">
<feColorMatrix
id="feColorMatrix25057"
type="saturate"
values="1"
result="fbSourceGraphic" />
<feColorMatrix
id="feColorMatrix25059"
in="fbSourceGraphic"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.8284271"
inkscape:cx="-12.356322"
inkscape:cy="57.536221"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1200"
inkscape:window-height="840"
inkscape:window-x="0"
inkscape:window-y="26"
inkscape:window-maximized="0" />
<metadata
id="metadata25075">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer"
transform="translate(0, 48)">
<g
id="g3181"
transform="matrix(0,1,-1,0,48.0003,-48)">
<g
style="stroke:#000000;stroke-opacity:1;display:inline"
transform="matrix(2,0,0,2,193.25,-374.967)"
id="g4030-1-8">
<path
style="color:#000000;fill:none;stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
d="m -72.5,173.5 -14,14 14,14"
id="path3165-7-3"
sodipodi:nodetypes="ccc"
inkscape:connector-curvature="0" />
</g>
<path
transform="matrix(3.34328,0,0,3.34328,185.28,-623.176)"
d="m -36.5,186.40625 c 0,1.15635 -0.937404,2.09375 -2.09375,2.09375 -1.156346,0 -2.09375,-0.9374 -2.09375,-2.09375 0,-1.15635 0.937404,-2.09375 2.09375,-2.09375 1.156346,0 2.09375,0.9374 2.09375,2.09375 z"
sodipodi:ry="2.09375"
sodipodi:rx="2.09375"
sodipodi:cy="186.40625"
sodipodi:cx="-38.59375"
id="path4050-2-7-9-4"
style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52343899;marker:none;visibility:visible;display:inline;overflow:visible"
sodipodi:type="arc" />
<path
transform="matrix(3.34328,0,0,3.34328,207.28,-623.176)"
d="m -36.5,186.40625 c 0,1.15635 -0.937404,2.09375 -2.09375,2.09375 -1.156346,0 -2.09375,-0.9374 -2.09375,-2.09375 0,-1.15635 0.937404,-2.09375 2.09375,-2.09375 1.156346,0 2.09375,0.9374 2.09375,2.09375 z"
sodipodi:ry="2.09375"
sodipodi:rx="2.09375"
sodipodi:cy="186.40625"
sodipodi:cx="-38.59375"
id="path4050-2-7-9-4-8"
style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52343899;marker:none;visibility:visible;display:inline;overflow:visible"
sodipodi:type="arc" />
<path
transform="matrix(2.86565,0,0,2.86565,166.846,-534.143)"
d="m -36.5,186.40625 c 0,1.15635 -0.937404,2.09375 -2.09375,2.09375 -1.156346,0 -2.09375,-0.9374 -2.09375,-2.09375 0,-1.15635 0.937404,-2.09375 2.09375,-2.09375 1.156346,0 2.09375,0.9374 2.09375,2.09375 z"
sodipodi:ry="2.09375"
sodipodi:rx="2.09375"
sodipodi:cy="186.40625"
sodipodi:cx="-38.59375"
id="path4050-2-7-9-4-0"
style="color:#000000;fill:none;stroke:#000000;stroke-width:0.69792098;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
sodipodi:type="arc" />
<path
transform="matrix(2.86565,0,0,2.86565,188.846,-534.143)"
d="m -36.5,186.40625 c 0,1.15635 -0.937404,2.09375 -2.09375,2.09375 -1.156346,0 -2.09375,-0.9374 -2.09375,-2.09375 0,-1.15635 0.937404,-2.09375 2.09375,-2.09375 1.156346,0 2.09375,0.9374 2.09375,2.09375 z"
sodipodi:ry="2.09375"
sodipodi:rx="2.09375"
sodipodi:cy="186.40625"
sodipodi:cx="-38.59375"
id="path4050-2-7-9-4-0-9"
style="color:#000000;fill:none;stroke:#000000;stroke-width:0.69792098;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
sodipodi:type="arc" />
<path
transform="matrix(2,0,0,2,-586,-765.967)"
sodipodi:nodetypes="ccccscccsc"
id="path3165-7-3-1"
d="m 317.06251,365.96875 c -0.76948,0.0224 -1.52555,0.35464 -2.0625,0.90625 l -16.125,16.125 16.125,16.125 c 1.11265,1.11265 3.13735,1.11265 4.25,0 1.11265,-1.11264 1.11265,-3.13735 0,-4.25 l -11.875,-11.875 11.875,-11.875 c 0.86584,-0.83655 1.1475,-2.22114 0.6773,-3.32947 -0.47021,-1.10834 -1.66156,-1.86802 -2.8648,-1.82678 z"
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0pt;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;text-anchor:start;opacity:0.35;color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;font-family:Bitstream Vera Sans"
inkscape:connector-curvature="0" />
<path
transform="matrix(2,0,0,2,-586,-765.967)"
sodipodi:nodetypes="ccccccc"
id="path3165-7-3-1-9"
d="m 320.08435,397.03059 c 0.007,-0.79449 -0.27079,-1.59203 -0.83434,-2.15559 L 307.37501,383 m 12.5523,-15.20447 c -0.47021,-1.10834 -1.66156,-1.86802 -2.8648,-1.82678 -0.76948,0.0224 -1.52555,0.35464 -2.0625,0.90625 L 298.87501,383"
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0pt;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;text-anchor:start;color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;font-family:Bitstream Vera Sans"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -68,10 +68,6 @@ IGNORE_HFILES= \
gactionobserver.h \ gactionobserver.h \
shell-recorder-src.h shell-recorder-src.h
if !BUILD_RECORDER
IGNORE_HFILES += shell-recorder.h
endif
# Images to copy into HTML directory. # Images to copy into HTML directory.
# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png # e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
HTML_IMAGES= HTML_IMAGES=

View File

@ -6,7 +6,9 @@ misc/config.js: misc/config.js.in Makefile
[ -d $(@D) ] || $(mkdir_p) $(@D) ; \ [ -d $(@D) ] || $(mkdir_p) $(@D) ; \
sed -e "s|[@]PACKAGE_NAME@|$(PACKAGE_NAME)|g" \ sed -e "s|[@]PACKAGE_NAME@|$(PACKAGE_NAME)|g" \
-e "s|[@]PACKAGE_VERSION@|$(PACKAGE_VERSION)|g" \ -e "s|[@]PACKAGE_VERSION@|$(PACKAGE_VERSION)|g" \
-e "s|[@]GJS_VERSION@|$(GJS_VERSION)|g" \
-e "s|[@]HAVE_BLUETOOTH@|$(HAVE_BLUETOOTH)|g" \ -e "s|[@]HAVE_BLUETOOTH@|$(HAVE_BLUETOOTH)|g" \
-e "s|[@]SHELL_SYSTEM_CA_FILE@|$(SHELL_SYSTEM_CA_FILE)|g" \
-e "s|[@]GETTEXT_PACKAGE@|$(GETTEXT_PACKAGE)|g" \ -e "s|[@]GETTEXT_PACKAGE@|$(GETTEXT_PACKAGE)|g" \
-e "s|[@]datadir@|$(datadir)|g" \ -e "s|[@]datadir@|$(datadir)|g" \
-e "s|[@]libexecdir@|$(libexecdir)|g" \ -e "s|[@]libexecdir@|$(libexecdir)|g" \
@ -17,21 +19,22 @@ jsdir = $(pkgdatadir)/js
nobase_dist_js_DATA = \ nobase_dist_js_DATA = \
gdm/batch.js \ gdm/batch.js \
gdm/consoleKit.js \
gdm/fingerprint.js \ gdm/fingerprint.js \
gdm/loginDialog.js \ gdm/loginDialog.js \
gdm/powerMenu.js \ gdm/powerMenu.js \
gdm/realmd.js \ gdm/systemd.js \
gdm/util.js \
extensionPrefs/main.js \ extensionPrefs/main.js \
misc/config.js \ misc/config.js \
misc/extensionUtils.js \ misc/extensionUtils.js \
misc/fileUtils.js \ misc/fileUtils.js \
misc/format.js \
misc/gnomeSession.js \ misc/gnomeSession.js \
misc/history.js \ misc/history.js \
misc/jsParse.js \ misc/jsParse.js \
misc/loginManager.js \
misc/modemManager.js \ misc/modemManager.js \
misc/params.js \ misc/params.js \
misc/screenSaver.js \
misc/util.js \ misc/util.js \
perf/core.js \ perf/core.js \
ui/altTab.js \ ui/altTab.js \
@ -42,6 +45,7 @@ nobase_dist_js_DATA = \
ui/boxpointer.js \ ui/boxpointer.js \
ui/calendar.js \ ui/calendar.js \
ui/checkBox.js \ ui/checkBox.js \
ui/contactDisplay.js \
ui/ctrlAltTab.js \ ui/ctrlAltTab.js \
ui/dash.js \ ui/dash.js \
ui/dateMenu.js \ ui/dateMenu.js \
@ -49,15 +53,13 @@ nobase_dist_js_DATA = \
ui/endSessionDialog.js \ ui/endSessionDialog.js \
ui/environment.js \ ui/environment.js \
ui/extensionSystem.js \ ui/extensionSystem.js \
ui/extensionDownloader.js \
ui/flashspot.js \ ui/flashspot.js \
ui/ibusCandidatePopup.js\
ui/grabHelper.js \
ui/iconGrid.js \ ui/iconGrid.js \
ui/keyboard.js \ ui/keyboard.js \
ui/keyringPrompt.js \ ui/keyringPrompt.js \
ui/layout.js \ ui/layout.js \
ui/lightbox.js \ ui/lightbox.js \
ui/link.js \
ui/lookingGlass.js \ ui/lookingGlass.js \
ui/magnifier.js \ ui/magnifier.js \
ui/magnifierDBus.js \ ui/magnifierDBus.js \
@ -65,7 +67,6 @@ nobase_dist_js_DATA = \
ui/messageTray.js \ ui/messageTray.js \
ui/modalDialog.js \ ui/modalDialog.js \
ui/networkAgent.js \ ui/networkAgent.js \
ui/sessionMode.js \
ui/shellEntry.js \ ui/shellEntry.js \
ui/shellMountOperation.js \ ui/shellMountOperation.js \
ui/notificationDaemon.js \ ui/notificationDaemon.js \
@ -73,26 +74,24 @@ nobase_dist_js_DATA = \
ui/panel.js \ ui/panel.js \
ui/panelMenu.js \ ui/panelMenu.js \
ui/placeDisplay.js \ ui/placeDisplay.js \
ui/pointerWatcher.js \
ui/polkitAuthenticationAgent.js \ ui/polkitAuthenticationAgent.js \
ui/popupMenu.js \ ui/popupMenu.js \
ui/remoteSearch.js \ ui/remoteSearch.js \
ui/runDialog.js \ ui/runDialog.js \
ui/screenShield.js \
ui/scripting.js \ ui/scripting.js \
ui/search.js \ ui/search.js \
ui/searchDisplay.js \ ui/searchDisplay.js \
ui/shellDBus.js \ ui/shellDBus.js \
ui/statusIconDispatcher.js \
ui/status/accessibility.js \ ui/status/accessibility.js \
ui/status/candidatePanel.js \
ui/status/keyboard.js \ ui/status/keyboard.js \
ui/status/lockScreenMenu.js \
ui/status/network.js \ ui/status/network.js \
ui/status/power.js \ ui/status/power.js \
ui/status/volume.js \ ui/status/volume.js \
ui/status/bluetooth.js \ ui/status/bluetooth.js \
ui/telepathyClient.js \ ui/telepathyClient.js \
ui/tweener.js \ ui/tweener.js \
ui/unlockDialog.js \
ui/userMenu.js \ ui/userMenu.js \
ui/viewSelector.js \ ui/viewSelector.js \
ui/wanda.js \ ui/wanda.js \

View File

@ -6,14 +6,15 @@ const GObject = imports.gi.GObject;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const Gtk = imports.gi.Gtk; const Gtk = imports.gi.Gtk;
const Pango = imports.gi.Pango; const Pango = imports.gi.Pango;
const Format = imports.format;
const _ = Gettext.gettext; const _ = Gettext.gettext;
const Config = imports.misc.config; const Config = imports.misc.config;
const Format = imports.misc.format;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const GnomeShellIface = <interface name="org.gnome.Shell.Extensions">
const GnomeShellIface = <interface name="org.gnome.Shell">
<signal name="ExtensionStatusChanged"> <signal name="ExtensionStatusChanged">
<arg type="s" name="uuid"/> <arg type="s" name="uuid"/>
<arg type="i" name="state"/> <arg type="i" name="state"/>
@ -161,7 +162,7 @@ const Application = new Lang.Class({
vbox.add(toolbar); vbox.add(toolbar);
let toolitem; let toolitem;
let label = new Gtk.Label({ label: '<b>' + _("Extension") + '</b>', let label = new Gtk.Label({ label: _("<b>Extension</b>"),
use_markup: true }); use_markup: true });
toolitem = new Gtk.ToolItem({ child: label }); toolitem = new Gtk.ToolItem({ child: label });
toolbar.add(toolitem); toolbar.add(toolitem);
@ -201,17 +202,23 @@ const Application = new Lang.Class({
}, },
_scanExtensions: function() { _scanExtensions: function() {
let finder = new ExtensionUtils.ExtensionFinder(); ExtensionUtils.scanExtensions(Lang.bind(this, function(uuid, dir, type) {
finder.connect('extension-found', Lang.bind(this, this._extensionFound)); if (ExtensionUtils.extensions[uuid] !== undefined)
finder.scanExtensions(); return;
},
_extensionFound: function(signals, extension) { let extension;
let iter = this._model.append(); try {
this._model.set(iter, [0, 1], [extension.uuid, extension.metadata.name]); extension = ExtensionUtils.createExtensionObject(uuid, dir, type);
this._extensionIters[extension.uuid] = iter; } catch(e) {
}, global.logError('' + e);
return;
}
let iter = this._model.append();
this._model.set(iter, [0, 1], [uuid, extension.metadata.name]);
this._extensionIters[uuid] = iter;
}));
},
_onActivate: function() { _onActivate: function() {
this._window.present(); this._window.present();
@ -250,7 +257,7 @@ function initEnvironment() {
}, },
logError: function(s) { logError: function(s) {
log('ERROR: ' + s); global.log('ERROR: ' + s);
}, },
userdatadir: GLib.build_filenamev([GLib.get_user_data_dir(), 'gnome-shell']) userdatadir: GLib.build_filenamev([GLib.get_user_data_dir(), 'gnome-shell'])
@ -261,6 +268,7 @@ function initEnvironment() {
function main(argv) { function main(argv) {
initEnvironment(); initEnvironment();
ExtensionUtils.init();
Gettext.bindtextdomain(Config.GETTEXT_PACKAGE, Config.LOCALEDIR); Gettext.bindtextdomain(Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
Gettext.textdomain(Config.GETTEXT_PACKAGE); Gettext.textdomain(Config.GETTEXT_PACKAGE);

22
js/gdm/consoleKit.js Normal file
View File

@ -0,0 +1,22 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Gio = imports.gi.Gio;
const ConsoleKitManagerIface = <interface name='org.freedesktop.ConsoleKit.Manager'>
<method name='CanRestart'>
<arg type='b' direction='out'/>
</method>
<method name='CanStop'>
<arg type='b' direction='out'/>
</method>
<method name='Restart' />
<method name='Stop' />
</interface>;
const ConsoleKitProxy = Gio.DBusProxy.makeProxyWrapper(ConsoleKitManagerIface);
function ConsoleKitManager() {
return new ConsoleKitProxy(Gio.DBus.system,
'org.freedesktop.ConsoleKit',
'/org/freedesktop/ConsoleKit/Manager');
};

View File

@ -30,24 +30,83 @@ const Pango = imports.gi.Pango;
const Signals = imports.signals; const Signals = imports.signals;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const St = imports.gi.St; const St = imports.gi.St;
const Gdm = imports.gi.Gdm; const GdmGreeter = imports.gi.GdmGreeter;
const Batch = imports.gdm.batch; const Batch = imports.gdm.batch;
const Fprint = imports.gdm.fingerprint; const Fprint = imports.gdm.fingerprint;
const GdmUtil = imports.gdm.util;
const Lightbox = imports.ui.lightbox; const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main; const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog; const ModalDialog = imports.ui.modalDialog;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const UserMenu = imports.ui.userMenu;
const _PASSWORD_SERVICE_NAME = 'gdm-password';
const _FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint';
const _FADE_ANIMATION_TIME = 0.16;
const _RESIZE_ANIMATION_TIME = 0.25; const _RESIZE_ANIMATION_TIME = 0.25;
const _SCROLL_ANIMATION_TIME = 0.5; const _SCROLL_ANIMATION_TIME = 2.0;
const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0; const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0;
const _LOGO_ICON_NAME_SIZE = 48; const _LOGO_ICON_NAME_SIZE = 48;
const _LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen';
const _FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication';
const _LOGO_KEY = 'logo';
let _loginDialog = null; let _loginDialog = null;
function _fadeInActor(actor) {
let hold = new Batch.Hold();
if (actor.opacity == 255 && actor.visible)
return null;
actor.show();
let [minHeight, naturalHeight] = actor.get_preferred_height(-1);
actor.opacity = 0;
actor.set_height(0);
Tweener.addTween(actor,
{ opacity: 255,
height: naturalHeight,
time: _FADE_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: function() {
actor.set_height(-1);
hold.release();
},
onCompleteScope: this
});
return hold;
}
function _fadeOutActor(actor) {
let hold = new Batch.Hold();
if (!actor.visible) {
actor.opacity = 0;
return null;
}
if (actor.opacity == 0) {
actor.hide();
return null;
}
Tweener.addTween(actor,
{ opacity: 0,
height: 0,
time: _FADE_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: function() {
actor.hide();
actor.set_height(-1);
hold.release();
},
onCompleteScope: this
});
return hold;
}
function _smoothlyResizeActor(actor, width, height) { function _smoothlyResizeActor(actor, width, height) {
let finalWidth; let finalWidth;
let finalHeight; let finalHeight;
@ -89,59 +148,95 @@ const UserListItem = new Lang.Class({
this._userChangedId = this.user.connect('changed', this._userChangedId = this.user.connect('changed',
Lang.bind(this, this._onUserChanged)); Lang.bind(this, this._onUserChanged));
let layout = new St.BoxLayout({ vertical: false }); this._verticalBox = new St.BoxLayout({ style_class: 'login-dialog-user-list-item-vertical-layout',
vertical: true });
this.actor = new St.Button({ style_class: 'login-dialog-user-list-item', this.actor = new St.Button({ style_class: 'login-dialog-user-list-item',
can_focus: true, can_focus: true,
child: layout, child: this._verticalBox,
reactive: true, reactive: true,
x_align: St.Align.START, x_align: St.Align.START,
x_fill: true }); x_fill: true });
let layout = new St.BoxLayout({ vertical: false });
this._userAvatar = new UserMenu.UserAvatarWidget(this.user, this._verticalBox.add(layout,
{ styleClass: 'login-dialog-user-list-item-icon' }); { y_fill: true,
layout.add(this._userAvatar.actor); x_fill: true,
expand: true });
this._focusBin = new St.Bin({ style_class: 'login-dialog-user-list-item-focus-bin' });
this._verticalBox.add(this._focusBin,
{ x_fill: false,
x_align: St.Align.MIDDLE,
y_fill: false,
expand: true });
this._iconBin = new St.Bin();
layout.add(this._iconBin);
let textLayout = new St.BoxLayout({ style_class: 'login-dialog-user-list-item-text-box', let textLayout = new St.BoxLayout({ style_class: 'login-dialog-user-list-item-text-box',
vertical: true }); vertical: true });
layout.add(textLayout, { expand: true }); layout.add(textLayout,
{ y_fill: false,
y_align: St.Align.MIDDLE,
expand: true });
this._nameLabel = new St.Label({ style_class: 'login-dialog-user-list-item-name' }); this._nameLabel = new St.Label({ text: this.user.get_real_name(),
textLayout.add(this._nameLabel, style_class: 'login-dialog-user-list-item-name' });
{ y_fill: false, textLayout.add(this._nameLabel);
y_align: St.Align.MIDDLE,
expand: true });
this._timedLoginIndicator = new St.Bin({ style_class: 'login-dialog-timed-login-indicator', this._updateIcon();
scale_x: 0 });
textLayout.add(this._timedLoginIndicator,
{ x_fill: true,
x_align: St.Align.MIDDLE,
y_fill: false,
y_align: St.Align.END });
this.actor.connect('clicked', Lang.bind(this, this._onClicked)); this.actor.connect('clicked', Lang.bind(this, this._onClicked));
this._onUserChanged();
}, },
_onUserChanged: function() { _onUserChanged: function() {
this._nameLabel.set_text(this.user.get_real_name()); this._nameLabel.set_text(this.user.get_real_name());
this._userAvatar.update(); this._updateIcon();
this._updateLoggedIn();
}, },
syncStyleClasses: function() { _setIconFromFile: function(iconFile, styleClass) {
this._updateLoggedIn(); if (styleClass)
this._iconBin.set_style_class_name(styleClass);
this._iconBin.set_style(null);
if (global.stage.get_key_focus() == this.actor) this._iconBin.child = null;
this.actor.add_style_pseudo_class('focus'); if (iconFile) {
else this._iconBin.show();
this.actor.remove_style_pseudo_class('focus'); // 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 + '");' +
'background-size: contain;');
} else {
this._iconBin.hide();
}
}, },
_updateLoggedIn: function() { _setIconFromName: function(iconName, styleClass) {
if (this.user.is_logged_in()) if (styleClass)
this.actor.add_style_pseudo_class('logged-in'); this._iconBin.set_style_class_name(styleClass);
this._iconBin.set_style(null);
if (iconName != null) {
let icon = new St.Icon();
icon.set_icon_name(iconName)
this._iconBin.child = icon;
this._iconBin.show();
} else {
this._iconBin.child = null;
this._iconBin.hide();
}
},
_updateIcon: function() {
let iconFileName = this.user.get_icon_file();
let gicon = null;
if (GLib.file_test(iconFileName, GLib.FileTest.EXISTS))
this._setIconFromFile(iconFileName, 'login-dialog-user-list-item-icon');
else else
this.actor.remove_style_pseudo_class('logged-in'); this._setIconFromName('avatar-default', 'login-dialog-user-list-item-icon');
}, },
_onClicked: function() { _onClicked: function() {
@ -149,19 +244,25 @@ const UserListItem = new Lang.Class({
}, },
fadeOutName: function() { fadeOutName: function() {
return GdmUtil.fadeOutActor(this._nameLabel); return _fadeOutActor(this._nameLabel);
}, },
fadeInName: function() { fadeInName: function() {
return GdmUtil.fadeInActor(this._nameLabel); return _fadeInActor(this._nameLabel);
}, },
showTimedLoginIndicator: function(time) { showFocusAnimation: function(time) {
let hold = new Batch.Hold(); let hold = new Batch.Hold();
this.hideTimedLoginIndicator(); let node = this.actor.get_theme_node();
Tweener.addTween(this._timedLoginIndicator, let padding = node.get_horizontal_padding();
{ scale_x: 1.,
let box = this._verticalBox.get_allocation_box();
Tweener.removeTweens(this._focusBin);
this._focusBin.width = 0;
Tweener.addTween(this._focusBin,
{ width: (box.x2 - box.x1 - padding),
time: time, time: time,
transition: 'linear', transition: 'linear',
onComplete: function() { onComplete: function() {
@ -170,11 +271,6 @@ const UserListItem = new Lang.Class({
onCompleteScope: this onCompleteScope: this
}); });
return hold; return hold;
},
hideTimedLoginIndicator: function() {
Tweener.removeTweens(this._timedLoginIndicator);
this._timedLoginIndicator.scale_x = 0.;
} }
}); });
Signals.addSignalMethods(UserListItem.prototype); Signals.addSignalMethods(UserListItem.prototype);
@ -188,10 +284,13 @@ const UserList = new Lang.Class({
Gtk.PolicyType.AUTOMATIC); Gtk.PolicyType.AUTOMATIC);
this._box = new St.BoxLayout({ vertical: true, this._box = new St.BoxLayout({ vertical: true,
style_class: 'login-dialog-user-list', style_class: 'login-dialog-user-list' });
pseudo_class: 'expanded' });
this.actor.add_actor(this._box); this.actor.add_actor(this._box,
{ x_fill: true,
y_fill: true,
x_align: St.Align.START,
y_align: St.Align.MIDDLE });
this._items = {}; this._items = {};
this.actor.connect('key-focus-in', Lang.bind(this, this._moveFocusToItems)); this.actor.connect('key-focus-in', Lang.bind(this, this._moveFocusToItems));
@ -211,7 +310,7 @@ const UserList = new Lang.Class({
_showItem: function(item) { _showItem: function(item) {
let tasks = [function() { let tasks = [function() {
return GdmUtil.fadeInActor(item.actor); return _fadeInActor(item.actor);
}, },
function() { function() {
@ -268,21 +367,17 @@ const UserList = new Lang.Class({
for (let userName in this._items) { for (let userName in this._items) {
let item = this._items[userName]; let item = this._items[userName];
item.actor.set_hover(false);
item.actor.reactive = false;
item.actor.can_focus = false; item.actor.can_focus = false;
item.syncStyleClasses(); item._focusBin.width = 0;
item._timedLoginIndicator.scale_x = 0.;
if (item != exception) if (item != exception)
tasks.push(function() { tasks.push(function() {
return GdmUtil.fadeOutActor(item.actor); return _fadeOutActor(item.actor);
}); });
} }
this._box.remove_style_pseudo_class('expanded');
let batch = new Batch.ConsecutiveBatch(this, let batch = new Batch.ConsecutiveBatch(this,
[function() { [function() {
return GdmUtil.fadeOutActor(this.actor.vscroll); return _fadeOutActor(this.actor.vscroll);
}, },
new Batch.ConcurrentBatch(this, tasks) new Batch.ConcurrentBatch(this, tasks)
@ -326,16 +421,12 @@ const UserList = new Lang.Class({
for (let userName in this._items) { for (let userName in this._items) {
let item = this._items[userName]; let item = this._items[userName];
item.actor.sync_hover();
item.actor.reactive = true;
item.actor.can_focus = true; item.actor.can_focus = true;
item.syncStyleClasses();
tasks.push(function() { tasks.push(function() {
return this._showItem(item); return this._showItem(item);
}); });
} }
this._box.add_style_pseudo_class('expanded');
let batch = new Batch.ConsecutiveBatch(this, let batch = new Batch.ConsecutiveBatch(this,
[function() { [function() {
this.takeOverWhitespace(); this.takeOverWhitespace();
@ -353,7 +444,7 @@ const UserList = new Lang.Class({
}, },
function() { function() {
return GdmUtil.fadeInActor(this.actor.vscroll); return _fadeInActor(this.actor.vscroll);
}]); }]);
return batch.run(); return batch.run();
}, },
@ -368,7 +459,7 @@ const UserList = new Lang.Class({
Tweener.addTween (adjustment, Tweener.addTween (adjustment,
{ value: value, { value: value,
time: _SCROLL_ANIMATION_TIME, time: _SCROLL_ANIMATION_TIME,
transition: 'easeOutQuad' }); transition: 'linear' });
}, },
jumpToItem: function(item) { jumpToItem: function(item) {
@ -420,6 +511,7 @@ const UserList = new Lang.Class({
Lang.bind(this, Lang.bind(this,
function() { function() {
this.scrollToItem(item); this.scrollToItem(item);
item.showFocusAnimation(0);
})); }));
this._moveFocusToItems(); this._moveFocusToItems();
@ -461,7 +553,10 @@ const SessionListItem = new Lang.Class({
this._box = new St.BoxLayout({ style_class: 'login-dialog-session-list-item-box' }); this._box = new St.BoxLayout({ style_class: 'login-dialog-session-list-item-box' });
this.actor.add_actor(this._box); this.actor.add_actor(this._box,
{ expand: true,
x_fill: true,
y_fill: true });
this.actor.connect('clicked', Lang.bind(this, this._onClicked)); this.actor.connect('clicked', Lang.bind(this, this._onClicked));
this._dot = new St.DrawingArea({ style_class: 'login-dialog-session-list-item-dot' }); this._dot = new St.DrawingArea({ style_class: 'login-dialog-session-list-item-dot' });
@ -472,7 +567,10 @@ const SessionListItem = new Lang.Class({
let label = new St.Label({ style_class: 'login-dialog-session-list-item-label', let label = new St.Label({ style_class: 'login-dialog-session-list-item-label',
text: name }); text: name });
this._box.add_actor(label); this._box.add_actor(label,
{ expand: true,
x_fill: true,
y_fill: true });
}, },
setShowDot: function(show) { setShowDot: function(show) {
@ -516,7 +614,10 @@ const SessionList = new Lang.Class({
x_fill: true, x_fill: true,
y_fill: true }); y_fill: true });
let box = new St.BoxLayout(); let box = new St.BoxLayout();
this._button.add_actor(box); this._button.add_actor(box,
{ x_fill: true,
y_fill: true,
expand: true });
this._triangle = new St.Label({ style_class: 'login-dialog-session-list-triangle', this._triangle = new St.Label({ style_class: 'login-dialog-session-list-triangle',
text: '\u25B8' }); text: '\u25B8' });
@ -524,18 +625,30 @@ const SessionList = new Lang.Class({
let label = new St.Label({ style_class: 'login-dialog-session-list-label', let label = new St.Label({ style_class: 'login-dialog-session-list-label',
text: _("Session...") }); text: _("Session...") });
box.add_actor(label); box.add_actor(label,
{ x_fill: true,
y_fill: true,
expand: true });
this._button.connect('clicked', this._button.connect('clicked',
Lang.bind(this, this._onClicked)); Lang.bind(this, this._onClicked));
this._box.add_actor(this._button); this._box.add_actor(this._button,
{ x_fill: true,
y_fill: true,
expand: true });
this._scrollView = new St.ScrollView({ style_class: 'login-dialog-session-list-scroll-view'}); this._scrollView = new St.ScrollView({ style_class: 'login-dialog-session-list-scroll-view'});
this._scrollView.set_policy(Gtk.PolicyType.NEVER, this._scrollView.set_policy(Gtk.PolicyType.NEVER,
Gtk.PolicyType.AUTOMATIC); Gtk.PolicyType.AUTOMATIC);
this._box.add_actor(this._scrollView); this._box.add_actor(this._scrollView,
{ x_fill: true,
y_fill: true,
expand: true });
this._itemList = new St.BoxLayout({ style_class: 'login-dialog-session-item-list', this._itemList = new St.BoxLayout({ style_class: 'login-dialog-session-item-list',
vertical: true }); vertical: true });
this._scrollView.add_actor(this._itemList); this._scrollView.add_actor(this._itemList,
{ x_fill: true,
y_fill: true,
expand: true });
this._scrollView.hide(); this._scrollView.hide();
this.isOpen = false; this.isOpen = false;
this._populate(); this._populate();
@ -588,7 +701,7 @@ const SessionList = new Lang.Class({
this._activeSessionId = null; this._activeSessionId = null;
this._items = {}; this._items = {};
let ids = Gdm.get_session_ids(); let ids = GdmGreeter.get_session_ids();
ids.sort(); ids.sort();
if (ids.length <= 1) { if (ids.length <= 1) {
@ -600,10 +713,14 @@ const SessionList = new Lang.Class({
} }
for (let i = 0; i < ids.length; i++) { for (let i = 0; i < ids.length; i++) {
let [sessionName, sessionDescription] = Gdm.get_session_name_and_description(ids[i]); let [sessionName, sessionDescription] = GdmGreeter.get_session_name_and_description(ids[i]);
let item = new SessionListItem(ids[i], sessionName); let item = new SessionListItem(ids[i], sessionName);
this._itemList.add_actor(item.actor); this._itemList.add_actor(item.actor,
{ x_align: St.Align.START,
y_align: St.Align.START,
x_fill: true,
y_fill: true });
this._items[ids[i]] = item; this._items[ids[i]] = item;
if (!this._activeSessionId) if (!this._activeSessionId)
@ -622,55 +739,52 @@ const LoginDialog = new Lang.Class({
Name: 'LoginDialog', Name: 'LoginDialog',
Extends: ModalDialog.ModalDialog, Extends: ModalDialog.ModalDialog,
_init: function(parentActor) { _init: function() {
this.parent({ shellReactive: true, this.parent({ shellReactive: true, styleClass: 'login-dialog' });
styleClass: 'login-dialog',
parentActor: parentActor
});
this.connect('destroy', this.connect('destroy',
Lang.bind(this, this._onDestroy)); Lang.bind(this, this._onDestroy));
this.connect('opened', this.connect('opened',
Lang.bind(this, this._onOpened)); Lang.bind(this, this._onOpened));
this._userManager = AccountsService.UserManager.get_default() this._userManager = AccountsService.UserManager.get_default()
this._greeterClient = new Gdm.Client(); this._greeterClient = new GdmGreeter.Client();
this._greeter = this._greeterClient.get_greeter_sync(null); this._greeterClient.open_connection();
this._greeter.connect('default-session-name-changed', this._greeterClient.call_start_conversation(_PASSWORD_SERVICE_NAME);
Lang.bind(this, this._onDefaultSessionChanged));
this._greeter.connect('session-opened', this._greeterClient.connect('reset',
Lang.bind(this, this._onSessionOpened)); Lang.bind(this, this._onReset));
this._greeter.connect('timed-login-requested', this._greeterClient.connect('default-session-changed',
Lang.bind(this, this._onTimedLoginRequested)); Lang.bind(this, this._onDefaultSessionChanged));
this._greeterClient.connect('info',
Lang.bind(this, this._onInfo));
this._greeterClient.connect('problem',
Lang.bind(this, this._onProblem));
this._greeterClient.connect('info-query',
Lang.bind(this, this._onInfoQuery));
this._greeterClient.connect('secret-info-query',
Lang.bind(this, this._onSecretInfoQuery));
this._greeterClient.connect('session-opened',
Lang.bind(this, this._onSessionOpened));
this._greeterClient.connect('timed-login-requested',
Lang.bind(this, this._onTimedLoginRequested));
this._greeterClient.connect('authentication-failed',
Lang.bind(this, this._onAuthenticationFailed));
this._greeterClient.connect('conversation-stopped',
Lang.bind(this, this._onConversationStopped));
this._userVerifier = new GdmUtil.ShellUserVerifier(this._greeterClient); this._settings = new Gio.Settings({ schema: _LOGIN_SCREEN_SCHEMA });
this._userVerifier.connect('ask-question', Lang.bind(this, this._askQuestion));
this._userVerifier.connect('verification-failed', Lang.bind(this, this._onVerificationFailed));
this._userVerifier.connect('reset', Lang.bind(this, this._onReset));
this._userVerifier.connect('show-login-hint', Lang.bind(this, this._showLoginHint)); this._fprintManager = new Fprint.FprintManager();
this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._hideLoginHint)); this._startFingerprintConversationIfNeeded();
this._settings.connect('changed::' + _LOGO_KEY,
this._settings = new Gio.Settings({ schema: GdmUtil.LOGIN_SCREEN_SCHEMA });
this._settings.connect('changed::' + GdmUtil.LOGO_KEY,
Lang.bind(this, this._updateLogo)); Lang.bind(this, this._updateLogo));
this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_KEY,
Lang.bind(this, this._updateBanner));
this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_TEXT_KEY,
Lang.bind(this, this._updateBanner));
this._logoBox = new St.Bin({ style_class: 'login-dialog-logo-box' }); this._logoBox = new St.Bin({ style_class: 'login-dialog-logo-box' });
this.contentLayout.add(this._logoBox); this.contentLayout.add(this._logoBox);
this._updateLogo(); this._updateLogo();
this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner',
text: '' });
this.contentLayout.add(this._bannerLabel);
this._updateBanner();
this._titleLabel = new St.Label({ style_class: 'login-dialog-title', this._titleLabel = new St.Label({ style_class: 'login-dialog-title',
text: C_("title", "Sign In") }); text: C_("title", "Sign In") });
@ -715,14 +829,17 @@ const LoginDialog = new Lang.Class({
x_fill: true, x_fill: true,
y_fill: false, y_fill: false,
x_align: St.Align.START }); x_align: St.Align.START });
this._promptLoginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint-message' }); // Translators: this message is shown below the password entry field
this._promptLoginHint.hide(); // to indicate the user can swipe their finger instead
this._promptBox.add(this._promptLoginHint); this._promptFingerprintMessage = new St.Label({ text: _("(or swipe finger)"),
style_class: 'login-dialog-prompt-fingerprint-message' });
this._promptFingerprintMessage.hide();
this._promptBox.add(this._promptFingerprintMessage);
this._sessionList = new SessionList(); this._sessionList = new SessionList();
this._sessionList.connect('session-activated', this._sessionList.connect('session-activated',
Lang.bind(this, function(list, sessionId) { Lang.bind(this, function(list, sessionId) {
this._greeter.call_select_session_sync (sessionId, null); this._greeterClient.call_select_session (sessionId);
})); }));
this._promptBox.add(this._sessionList.actor, this._promptBox.add(this._sessionList.actor,
@ -770,9 +887,25 @@ const LoginDialog = new Lang.Class({
}, },
_startFingerprintConversationIfNeeded: function() {
this._haveFingerprintReader = false;
if (!this._settings.get_boolean(_FINGERPRINT_AUTHENTICATION_KEY))
return;
this._fprintManager.GetDefaultDeviceRemote(Gio.DBusCallFlags.NONE, Lang.bind(this,
function(device, error) {
if (!error && device)
this._haveFingerprintReader = true;
if (this._haveFingerprintReader)
this._greeterClient.call_start_conversation(_FINGERPRINT_SERVICE_NAME);
}));
},
_updateLogo: function() { _updateLogo: function() {
this._logoBox.child = null; this._logoBox.child = null;
let path = this._settings.get_string(GdmUtil.LOGO_KEY); let path = this._settings.get_string(_LOGO_KEY);
if (path) { if (path) {
let file = Gio.file_new_for_path(path); let file = Gio.file_new_for_path(path);
@ -784,19 +917,10 @@ const LoginDialog = new Lang.Class({
}, },
_updateBanner: function() {
let enabled = this._settings.get_boolean(GdmUtil.BANNER_MESSAGE_KEY);
let text = this._settings.get_string(GdmUtil.BANNER_MESSAGE_TEXT_KEY);
if (enabled && text) {
this._bannerLabel.set_text(text);
this._fadeInBanner();
} else {
this._fadeOutBanner();
}
},
_onReset: function(client, serviceName) { _onReset: function(client, serviceName) {
this._greeterClient.call_start_conversation(_PASSWORD_SERVICE_NAME);
this._startFingerprintConversationIfNeeded();
let tasks = [this._hidePrompt, let tasks = [this._hidePrompt,
new Batch.ConcurrentBatch(this, [this._fadeInTitleLabel, new Batch.ConcurrentBatch(this, [this._fadeInTitleLabel,
@ -805,7 +929,7 @@ const LoginDialog = new Lang.Class({
function() { function() {
this._sessionList.close(); this._sessionList.close();
this._promptLoginHint.hide(); this._promptFingerprintMessage.hide();
this._userList.actor.show(); this._userList.actor.show();
this._userList.actor.opacity = 255; this._userList.actor.opacity = 255;
return this._userList.showItems(); return this._userList.showItems();
@ -826,45 +950,61 @@ const LoginDialog = new Lang.Class({
this._sessionList.setActiveSession(sessionId); this._sessionList.setActiveSession(sessionId);
}, },
_showLoginHint: function(verifier, message) { _onInfo: function(client, serviceName, info) {
this._promptLoginHint.set_text(message) // We don't display fingerprint messages, because they
GdmUtil.fadeInActor(this._promptLoginHint); // have words like UPEK in them. Instead we use the messages
// as a cue to display our own message.
if (serviceName == _FINGERPRINT_SERVICE_NAME &&
this._haveFingerprintReader &&
(!this._promptFingerprintMessage.visible ||
this._promptFingerprintMessage.opacity != 255)) {
_fadeInActor(this._promptFingerprintMessage);
return;
}
if (serviceName != _PASSWORD_SERVICE_NAME)
return;
Main.notifyError(info);
}, },
_hideLoginHint: function() { _onProblem: function(client, serviceName, problem) {
GdmUtil.fadeOutActor(this._promptLoginHint); // we don't want to show auth failed messages to
this._promptLoginHint.set_text(''); // users who haven't enrolled their fingerprint.
if (serviceName != _PASSWORD_SERVICE_NAME)
return;
Main.notifyError(problem);
}, },
cancel: function() { _onCancel: function(client) {
this._userVerifier.cancel(); this._greeterClient.call_cancel();
}, },
_fadeInPrompt: function() { _fadeInPrompt: function() {
let tasks = [function() { let tasks = [function() {
return GdmUtil.fadeInActor(this._promptLabel); return _fadeInActor(this._promptLabel);
}, },
function() { function() {
return GdmUtil.fadeInActor(this._promptEntry); return _fadeInActor(this._promptEntry);
}, },
function() { function() {
// Show it with 0 opacity so we preallocate space for it // Show it with 0 opacity so we preallocate space for it
// in the event we need to fade in the message // in the event we need to fade in the message
this._promptLoginHint.opacity = 0; this._promptFingerprintMessage.opacity = 0;
this._promptLoginHint.show(); this._promptFingerprintMessage.show();
}, },
function() { function() {
return GdmUtil.fadeInActor(this._promptBox); return _fadeInActor(this._promptBox);
}, },
function() { function() {
if (this._user && this._user.is_logged_in()) if (this._user && this._user.is_logged_in())
return null; return null;
return GdmUtil.fadeInActor(this._sessionList.actor); return _fadeInActor(this._sessionList.actor);
}, },
function() { function() {
@ -879,14 +1019,13 @@ const LoginDialog = new Lang.Class({
_showPrompt: function() { _showPrompt: function() {
let hold = new Batch.Hold(); let hold = new Batch.Hold();
let buttons = [{ action: Lang.bind(this, this.cancel), let buttons = [{ action: Lang.bind(this, this._onCancel),
label: _("Cancel"), label: _("Cancel"),
key: Clutter.Escape }, key: Clutter.Escape },
{ action: Lang.bind(this, function() { { action: Lang.bind(this, function() {
hold.release(); hold.release();
}), }),
label: C_("button", "Sign In"), label: C_("button", "Sign In") }];
default: true }];
this._promptEntryActivateCallbackId = this._promptEntry.clutter_text.connect('activate', this._promptEntryActivateCallbackId = this._promptEntry.clutter_text.connect('activate',
Lang.bind(this, function() { Lang.bind(this, function() {
@ -921,12 +1060,13 @@ const LoginDialog = new Lang.Class({
this.setButtons([]); this.setButtons([]);
let tasks = [function() { let tasks = [function() {
return GdmUtil.fadeOutActor(this._promptBox); return _fadeOutActor(this._promptBox);
}, },
function() { function() {
this._promptLoginHint.hide(); this._promptFingerprintMessage.hide();
this._promptEntry.reactive = true; this._promptEntry.reactive = true;
this._promptEntry.remove_style_pseudo_class('insensitive');
this._promptEntry.set_text(''); this._promptEntry.set_text('');
}]; }];
@ -935,26 +1075,43 @@ const LoginDialog = new Lang.Class({
return batch.run(); return batch.run();
}, },
_askQuestion: function(verifier, serviceName, question, passwordChar) { _askQuestion: function(serviceName, question) {
this._promptLabel.set_text(question); this._promptLabel.set_text(question);
this._promptEntry.set_text('');
this._promptEntry.clutter_text.set_password_char(passwordChar);
let tasks = [this._showPrompt, let tasks = [this._showPrompt,
function() { function() {
let _text = this._promptEntry.get_text(); let _text = this._promptEntry.get_text();
this._promptEntry.reactive = false; this._promptEntry.reactive = false;
this._userVerifier.answerQuery(serviceName, _text); this._promptEntry.add_style_pseudo_class('insensitive');
this._greeterClient.call_answer_query(serviceName, _text);
}]; }];
let batch = new Batch.ConsecutiveBatch(this, tasks); let batch = new Batch.ConsecutiveBatch(this, tasks);
return batch.run(); return batch.run();
}, },
_onInfoQuery: function(client, serviceName, question) {
// We only expect questions to come from the main auth service
if (serviceName != _PASSWORD_SERVICE_NAME)
return;
this._promptEntry.set_text('');
this._promptEntry.clutter_text.set_password_char('');
this._askQuestion(serviceName, question);
},
_onSecretInfoQuery: function(client, serviceName, secretQuestion) {
// We only expect secret requests to come from the main auth service
if (serviceName != _PASSWORD_SERVICE_NAME)
return;
this._promptEntry.set_text('');
this._promptEntry.clutter_text.set_password_char('\u25cf');
this._askQuestion(serviceName, secretQuestion);
},
_onSessionOpened: function(client, serviceName) { _onSessionOpened: function(client, serviceName) {
this._greeter.call_start_session_when_ready_sync(serviceName, true, null); this._greeterClient.call_start_session_when_ready(serviceName, true);
}, },
_waitForItemForUser: function(userName) { _waitForItemForUser: function(userName) {
@ -981,7 +1138,7 @@ const LoginDialog = new Lang.Class({
_showTimedLoginAnimation: function() { _showTimedLoginAnimation: function() {
this._timedLoginItem.actor.grab_key_focus(); this._timedLoginItem.actor.grab_key_focus();
return this._timedLoginItem.showTimedLoginIndicator(this._timedLoginAnimationTime); return this._timedLoginItem.showFocusAnimation(this._timedLoginAnimationTime);
}, },
_blockTimedLoginUntilIdle: function() { _blockTimedLoginUntilIdle: function() {
@ -1022,6 +1179,7 @@ const LoginDialog = new Lang.Class({
// item. // item.
if (!this.is_loaded) { if (!this.is_loaded) {
this._userList.jumpToItem(this._timedLoginItem); this._userList.jumpToItem(this._timedLoginItem);
this._timedLoginItem.showFocusAnimation(0);
} }
}, },
@ -1035,7 +1193,7 @@ const LoginDialog = new Lang.Class({
function() { function() {
this._timedLoginBatch = null; this._timedLoginBatch = null;
this._greeter.call_begin_auto_login_sync(userName, null); this._greeterClient.call_begin_auto_login(userName);
}]; }];
this._timedLoginBatch = new Batch.ConsecutiveBatch(this, tasks); this._timedLoginBatch = new Batch.ConsecutiveBatch(this, tasks);
@ -1049,9 +1207,6 @@ const LoginDialog = new Lang.Class({
this._timedLoginBatch = null; this._timedLoginBatch = null;
} }
if (this._timedLoginItem)
this._timedLoginItem.hideTimedLoginIndicator();
let userName = this._timedLoginItem.user.get_user_name(); let userName = this._timedLoginItem.user.get_user_name();
if (userName) if (userName)
@ -1081,8 +1236,19 @@ const LoginDialog = new Lang.Class({
})); }));
}, },
_onVerificationFailed: function() { _onAuthenticationFailed: function(client) {
this._userVerifier.cancel(); this._greeterClient.call_cancel();
},
_onConversationStopped: function(client, serviceName) {
// if the password service fails, then cancel everything.
// But if, e.g., fingerprint fails, still give
// password authentication a chance to succeed
if (serviceName == _PASSWORD_SERVICE_NAME) {
this._greeterClient.call_cancel();
} else if (serviceName == _FINGERPRINT_SERVICE_NAME) {
_fadeOutActor(this._promptFingerprintMessage);
}
}, },
_onNotListedClicked: function(user) { _onNotListedClicked: function(user) {
@ -1103,10 +1269,7 @@ const LoginDialog = new Lang.Class({
this._fadeOutLogo]), this._fadeOutLogo]),
function() { function() {
let hold = new Batch.Hold(); this._greeterClient.call_begin_verification(_PASSWORD_SERVICE_NAME);
this._userVerifier.begin(null, hold);
return hold;
}]; }];
let batch = new Batch.ConsecutiveBatch(this, tasks); let batch = new Batch.ConsecutiveBatch(this, tasks);
@ -1114,47 +1277,30 @@ const LoginDialog = new Lang.Class({
}, },
_fadeInLogo: function() { _fadeInLogo: function() {
return GdmUtil.fadeInActor(this._logoBox); return _fadeInActor(this._logoBox);
}, },
_fadeOutLogo: function() { _fadeOutLogo: function() {
return GdmUtil.fadeOutActor(this._logoBox); return _fadeOutActor(this._logoBox);
},
_fadeInBanner: function() {
return GdmUtil.fadeInActor(this._bannerLabel);
},
_fadeOutBanner: function() {
return GdmUtil.fadeOutActor(this._bannerLabel);
}, },
_fadeInTitleLabel: function() { _fadeInTitleLabel: function() {
return GdmUtil.fadeInActor(this._titleLabel); return _fadeInActor(this._titleLabel);
}, },
_fadeOutTitleLabel: function() { _fadeOutTitleLabel: function() {
return GdmUtil.fadeOutActor(this._titleLabel); return _fadeOutActor(this._titleLabel);
}, },
_fadeInNotListedButton: function() { _fadeInNotListedButton: function() {
return GdmUtil.fadeInActor(this._notListedButton); return _fadeInActor(this._notListedButton);
}, },
_fadeOutNotListedButton: function() { _fadeOutNotListedButton: function() {
return GdmUtil.fadeOutActor(this._notListedButton); return _fadeOutActor(this._notListedButton);
},
_beginVerificationForUser: function(userName) {
let hold = new Batch.Hold();
this._userVerifier.begin(userName, hold);
return hold;
}, },
_onUserListActivated: function(activatedItem) { _onUserListActivated: function(activatedItem) {
let userName;
let tasks = [function() { let tasks = [function() {
this._userList.actor.reactive = false; this._userList.actor.reactive = false;
return this._userList.pinInPlace(); return this._userList.pinInPlace();
@ -1181,9 +1327,12 @@ const LoginDialog = new Lang.Class({
}, },
function() { function() {
userName = activatedItem.user.get_user_name(); let userName = activatedItem.user.get_user_name();
this._greeterClient.call_begin_verification_for_user(_PASSWORD_SERVICE_NAME,
userName);
return this._beginVerificationForUser(userName); if (this._haveFingerprintReader)
this._greeterClient.call_begin_verification_for_user(_FINGERPRINT_SERVICE_NAME, userName);
}]; }];
this._user = activatedItem.user; this._user = activatedItem.user;

View File

@ -21,7 +21,8 @@
const Lang = imports.lang; const Lang = imports.lang;
const UPowerGlib = imports.gi.UPowerGlib; const UPowerGlib = imports.gi.UPowerGlib;
const LoginManager = imports.misc.loginManager; const ConsoleKit = imports.gdm.consoleKit;
const Systemd = imports.gdm.systemd;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
@ -31,10 +32,13 @@ const PowerMenuButton = new Lang.Class({
Extends: PanelMenu.SystemStatusButton, Extends: PanelMenu.SystemStatusButton,
_init: function() { _init: function() {
this.parent('system-shutdown-symbolic', null); this.parent('system-shutdown', null);
this._upClient = new UPowerGlib.Client(); this._upClient = new UPowerGlib.Client();
this._loginManager = LoginManager.getLoginManager(); if (Systemd.haveSystemd())
this._systemdLoginManager = new Systemd.SystemdLoginManager();
else
this._consoleKitManager = new ConsoleKit.ConsoleKitManager();
this._createSubMenu(); this._createSubMenu();
@ -61,19 +65,57 @@ const PowerMenuButton = new Lang.Class({
}, },
_updateHaveShutdown: function() { _updateHaveShutdown: function() {
this._loginManager.canPowerOff(Lang.bind(this, function(result) {
this._haveShutdown = result; if (Systemd.haveSystemd()) {
this._powerOffItem.actor.visible = this._haveShutdown; this._systemdLoginManager.CanPowerOffRemote(Lang.bind(this,
this._updateVisibility(); function(result, error) {
})); if (!error)
this._haveShutdown = result != 'no';
else
this._haveShutdown = false;
this._powerOffItem.actor.visible = this._haveShutdown;
this._updateVisibility();
}));
} else {
this._consoleKitManager.CanStopRemote(Lang.bind(this,
function(result, error) {
if (!error)
this._haveShutdown = result;
else
this._haveShutdown = false;
this._powerOffItem.actor.visible = this._haveShutdown;
this._updateVisibility();
}));
}
}, },
_updateHaveRestart: function() { _updateHaveRestart: function() {
this._loginManager.canReboot(Lang.bind(this, function(result) {
this._haveRestart = result; if (Systemd.haveSystemd()) {
this._restartItem.actor.visible = this._haveRestart; this._systemdLoginManager.CanRebootRemote(Lang.bind(this,
this._updateVisibility(); function(result, error) {
})); if (!error)
this._haveRestart = result != 'no';
else
this._haveRestart = false;
this._restartItem.actor.visible = this._haveRestart;
this._updateVisibility();
}));
} else {
this._consoleKitManager.CanRestartRemote(Lang.bind(this,
function(result, error) {
if (!error)
this._haveRestart = result;
else
this._haveRestart = false;
this._restartItem.actor.visible = this._haveRestart;
this._updateVisibility();
}));
}
}, },
_updateHaveSuspend: function() { _updateHaveSuspend: function() {
@ -110,13 +152,19 @@ const PowerMenuButton = new Lang.Class({
if (!this._haveRestart) if (!this._haveRestart)
return; return;
this._loginManager.reboot(); if (Systemd.haveSystemd())
this._systemdLoginManager.RebootRemote(true);
else
this._consoleKitManager.RestartRemote();
}, },
_onActivatePowerOff: function() { _onActivatePowerOff: function() {
if (!this._haveShutdown) if (!this._haveShutdown)
return; return;
this._loginManager.powerOff(); if (Systemd.haveSystemd())
this._systemdLoginManager.PowerOffRemote(true);
else
this._consoleKitManager.StopRemote();
} }
}); });

View File

@ -1,139 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const Shell = imports.gi.Shell;
const Signals = imports.signals;
const ProviderIface = <interface name='org.freedesktop.realmd.Provider'>
<property name="Name" type="s" access="read"/>
<property name="Version" type="s" access="read"/>
<property name="Realms" type="ao" access="read"/>
<method name="Discover">
<arg name="string" type="s" direction="in"/>
<arg name="options" type="a{sv}" direction="in"/>
<arg name="relevance" type="i" direction="out"/>
<arg name="realm" type="ao" direction="out"/>
</method>
</interface>;
const Provider = Gio.DBusProxy.makeProxyWrapper(ProviderIface);
const ServiceIface = <interface name="org.freedesktop.realmd.Service">
<method name="Cancel">
<arg name="operation" type="s" direction="in"/>
</method>
<method name="Release" />
<method name="SetLocale">
<arg name="locale" type="s" direction="in"/>
</method>
<signal name="Diagnostics">
<arg name="data" type="s"/>
<arg name="operation" type="s"/>
</signal>
</interface>;
const Service = Gio.DBusProxy.makeProxyWrapper(ServiceIface);
const RealmIface = <interface name="org.freedesktop.realmd.Realm">
<property name="Name" type="s" access="read"/>
<property name="Configured" type="s" access="read"/>
<property name="Details" type="a(ss)" access="read"/>
<property name="LoginFormats" type="as" access="read"/>
<property name="LoginPolicy" type="s" access="read"/>
<property name="PermittedLogins" type="as" access="read"/>
<property name="SupportedInterfaces" type="as" access="read"/>
<method name="ChangeLoginPolicy">
<arg name="login_policy" type="s" direction="in"/>
<arg name="permitted_add" type="as" direction="in"/>
<arg name="permitted_remove" type="as" direction="in"/>
<arg name="options" type="a{sv}" direction="in"/>
</method>
<method name="Deconfigure">
<arg name="options" type="a{sv}" direction="in"/>
</method>
</interface>;
const Realm = Gio.DBusProxy.makeProxyWrapper(RealmIface);
const Manager = new Lang.Class({
Name: 'Manager',
_init: function(parentActor) {
this._aggregateProvider = Provider(Gio.DBus.system,
'org.freedesktop.realmd',
'/org/freedesktop/realmd',
Lang.bind(this, this._reloadRealms))
this._realms = {};
this._aggregateProvider.connect('g-properties-changed',
Lang.bind(this, function(proxy, properties) {
if ('Realms' in properties.deep_unpack())
this._reloadRealms();
}));
},
_reloadRealms: function() {
let realmPaths = this._aggregateProvider.Realms;
if (!realmPaths)
return;
for (let i = 0; i < realmPaths.length; i++) {
let realm = Realm(Gio.DBus.system,
'org.freedesktop.realmd',
realmPaths[i],
Lang.bind(this, this._onRealmLoaded));
}
},
_reloadRealm: function(realm) {
if (!realm.Configured) {
if (this._realms[realm.get_object_path()])
delete this._realms[realm.get_object_path()];
return;
}
this._realms[realm.get_object_path()] = realm;
this._updateLoginFormat();
},
_onRealmLoaded: function(realm, error) {
if (error)
return;
this._reloadRealm(realm);
realm.connect('g-properties-changed',
Lang.bind(this, function(proxy, properties) {
if ('Configured' in properties.deep_unpack())
this._reloadRealm();
}));
},
_updateLoginFormat: function() {
let newLoginFormat;
for (let realmPath in this._realms) {
let realm = this._realms[realmPath];
if (realm.LoginFormats && realm.LoginFormats.length > 0) {
newLoginFormat = realm.LoginFormats[0];
break;
}
}
if (this._loginFormat != newLoginFormat) {
this._loginFormat = newLoginFormat;
this.emit('login-format-changed', newLoginFormat);
}
},
get loginFormat() {
if (this._loginFormat !== undefined)
return this._loginFormat;
this._updateLoginFormat();
return this._loginFormat;
}
});
Signals.addSignalMethods(Manager.prototype)

31
js/gdm/systemd.js Normal file
View File

@ -0,0 +1,31 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const SystemdLoginManagerIface = <interface name='org.freedesktop.login1.Manager'>
<method name='PowerOff'>
<arg type='b' direction='in'/>
</method>
<method name='Reboot'>
<arg type='b' direction='in'/>
</method>
<method name='CanPowerOff'>
<arg type='s' direction='out'/>
</method>
<method name='CanReboot'>
<arg type='s' direction='out'/>
</method>
</interface>;
const SystemdLoginManagerProxy = Gio.DBusProxy.makeProxyWrapper(SystemdLoginManagerIface);
function SystemdLoginManager() {
return new SystemdLoginManagerProxy(Gio.DBus.system,
'org.freedesktop.login1',
'/org/freedesktop/login1');
};
function haveSystemd() {
return GLib.access("/sys/fs/cgroup/systemd", 0) >= 0;
}

View File

@ -1,319 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const Signals = imports.signals;
const Batch = imports.gdm.batch;
const Fprint = imports.gdm.fingerprint;
const Realmd = imports.gdm.realmd;
const Main = imports.ui.main;
const Params = imports.misc.params;
const Tweener = imports.ui.tweener;
const PASSWORD_SERVICE_NAME = 'gdm-password';
const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint';
const FADE_ANIMATION_TIME = 0.16;
const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen';
const FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication';
const BANNER_MESSAGE_KEY = 'banner-message-enable';
const BANNER_MESSAGE_TEXT_KEY = 'banner-message-text';
const LOGO_KEY = 'logo';
function fadeInActor(actor) {
if (actor.opacity == 255 && actor.visible)
return null;
let hold = new Batch.Hold();
actor.show();
let [minHeight, naturalHeight] = actor.get_preferred_height(-1);
actor.opacity = 0;
actor.set_height(0);
Tweener.addTween(actor,
{ opacity: 255,
height: naturalHeight,
time: FADE_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: function() {
this.set_height(-1);
hold.release();
},
});
return hold;
}
function fadeOutActor(actor) {
if (!actor.visible || actor.opacity == 0) {
actor.opacity = 0;
actor.hide();
return null;
}
let hold = new Batch.Hold();
Tweener.addTween(actor,
{ opacity: 0,
height: 0,
time: FADE_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: function() {
this.hide();
this.set_height(-1);
hold.release();
},
});
return hold;
}
const ShellUserVerifier = new Lang.Class({
Name: 'ShellUserVerifier',
_init: function(client, params) {
params = Params.parse(params, { reauthenticationOnly: false });
this._reauthOnly = params.reauthenticationOnly;
this._client = client;
this._settings = new Gio.Settings({ schema: LOGIN_SCREEN_SCHEMA });
this._fprintManager = new Fprint.FprintManager();
this._realmManager = new Realmd.Manager();
},
begin: function(userName, hold) {
this._cancellable = new Gio.Cancellable();
this._hold = hold;
this._userName = userName;
this._checkForFingerprintReader();
if (userName) {
// If possible, reauthenticate an already running session,
// so any session specific credentials get updated appropriately
this._client.open_reauthentication_channel(userName, this._cancellable,
Lang.bind(this, this._reauthenticationChannelOpened));
} else {
this._client.get_user_verifier(this._cancellable, Lang.bind(this, this._userVerifierGot));
}
},
cancel: function() {
if (this._cancellable)
this._cancellable.cancel();
if (this._userVerifier)
this._userVerifier.call_cancel_sync(null);
},
clear: function() {
if (this._cancellable) {
this._cancellable.cancel();
this._cancellable = null;
}
if (this._userVerifier) {
this._userVerifier.run_dispose();
this._userVerifier = null;
}
},
answerQuery: function(serviceName, answer) {
this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null);
},
_checkForFingerprintReader: function() {
this._haveFingerprintReader = false;
if (!this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY))
return;
this._fprintManager.GetDefaultDeviceRemote(Gio.DBusCallFlags.NONE, this._cancellable, Lang.bind(this,
function(device, error) {
if (!error && device)
this._haveFingerprintReader = true;
}));
},
_reauthenticationChannelOpened: function(client, result) {
try {
this._userVerifier = client.open_reauthentication_channel_finish(result);
this._connectSignals();
this._beginVerification();
this._hold.release();
} catch (e) {
if (this._reauthOnly) {
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
return;
logError(e, 'Failed to open reauthentication channel');
this.emit('verification-failed');
this._hold.release();
return;
}
// If there's no session running, or it otherwise fails, then fall back
// to performing verification from this login session
client.get_user_verifier(this._cancellable, Lang.bind(this, this._userVerifierGot));
}
},
_userVerifierGot: function(client, result) {
try {
this._userVerifier = client.get_user_verifier_finish(result);
} catch(e if e.matches(Gio.IOErrorEnum, Gio.ErrorEnum.CANCELLED)) {
return;
}
this._connectSignals();
this._beginVerification();
this._hold.release();
},
_connectSignals: function() {
this._userVerifier.connect('info', Lang.bind(this, this._onInfo));
this._userVerifier.connect('problem', Lang.bind(this, this._onProblem));
this._userVerifier.connect('info-query', Lang.bind(this, this._onInfoQuery));
this._userVerifier.connect('secret-info-query', Lang.bind(this, this._onSecretInfoQuery));
this._userVerifier.connect('conversation-stopped', Lang.bind(this, this._onConversationStopped));
this._userVerifier.connect('reset', Lang.bind(this, this._onReset));
this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete));
},
_beginVerification: function() {
this._hold.acquire();
if (this._userName) {
this._userVerifier.call_begin_verification_for_user(PASSWORD_SERVICE_NAME,
this._userName,
this._cancellable,
Lang.bind(this, function(obj, result) {
try {
obj.call_begin_verification_for_user_finish(result);
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
return;
}
this._hold.release();
}));
if (this._haveFingerprintReader) {
this._hold.acquire();
this._userVerifier.call_begin_verification_for_user(FINGERPRINT_SERVICE_NAME,
this._userName,
this._cancellable,
Lang.bind(this, function(obj, result) {
try {
obj.call_begin_verification_for_user_finish(result);
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
return;
}
this._hold.release();
}));
}
} else {
this._userVerifier.call_begin_verification(PASSWORD_SERVICE_NAME,
this._cancellable,
Lang.bind(this, function(obj, result) {
try {
obj.call_begin_verification_finish(result);
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
return;
}
this._hold.release();
}));
}
},
_onInfo: function(client, serviceName, info) {
// We don't display fingerprint messages, because they
// have words like UPEK in them. Instead we use the messages
// as a cue to display our own message.
if (serviceName == FINGERPRINT_SERVICE_NAME &&
this._haveFingerprintReader) {
// Translators: this message is shown below the password entry field
// to indicate the user can swipe their finger instead
this.emit('show-login-hint', _("(or swipe finger)"));
} else if (serviceName == PASSWORD_SERVICE_NAME) {
Main.notifyError(info);
}
},
_onProblem: function(client, serviceName, problem) {
// we don't want to show auth failed messages to
// users who haven't enrolled their fingerprint.
if (serviceName != PASSWORD_SERVICE_NAME)
return;
Main.notifyError(problem);
},
_showRealmLoginHint: function() {
if (this._realmManager.loginFormat) {
let hint = this._realmManager.loginFormat;
hint = hint.replace(/%U/g, 'user');
hint = hint.replace(/%D/g, 'DOMAIN');
hint = hint.replace(/%[^UD]/g, '');
// Translators: this message is shown below the username entry field
// to clue the user in on how to login to the local network realm
this.emit('show-login-hint',
_("(e.g., user or %s)").format(hint));
}
},
_onInfoQuery: function(client, serviceName, question) {
// We only expect questions to come from the main auth service
if (serviceName != PASSWORD_SERVICE_NAME)
return;
this._showRealmLoginHint();
this._realmLoginHintSignalId = this._realmManager.connect('login-format-changed',
Lang.bind(this, this._showRealmLoginHint));
this.emit('ask-question', serviceName, question, '');
},
_onSecretInfoQuery: function(client, serviceName, secretQuestion) {
// We only expect secret requests to come from the main auth service
if (serviceName != PASSWORD_SERVICE_NAME)
return;
this.emit('ask-question', serviceName, secretQuestion, '\u25cf');
},
_onReset: function() {
this._userVerifier.run_dispose();
this._userVerifier = null;
this.emit('reset');
},
_onVerificationComplete: function() {
this.emit('verification-complete');
},
_onConversationStopped: function(client, serviceName) {
// if the password service fails, then cancel everything.
// But if, e.g., fingerprint fails, still give
// password authentication a chance to succeed
if (serviceName == PASSWORD_SERVICE_NAME) {
this.emit('verification-failed');
}
this.emit('hide-login-hint');
if (this._realmLoginHintSignalId) {
this._realmManager.disconnect(this._realmLoginHintSignalId);
this._realmLoginHintSignalId = 0;
}
},
});
Signals.addSignalMethods(ShellUserVerifier.prototype);

View File

@ -4,8 +4,12 @@
const PACKAGE_NAME = '@PACKAGE_NAME@'; const PACKAGE_NAME = '@PACKAGE_NAME@';
/* The version of this package */ /* The version of this package */
const PACKAGE_VERSION = '@PACKAGE_VERSION@'; const PACKAGE_VERSION = '@PACKAGE_VERSION@';
/* The version of GJS we're linking to */
const GJS_VERSION = '@GJS_VERSION@';
/* 1 if gnome-bluetooth is available, 0 otherwise */ /* 1 if gnome-bluetooth is available, 0 otherwise */
const HAVE_BLUETOOTH = @HAVE_BLUETOOTH@; const HAVE_BLUETOOTH = @HAVE_BLUETOOTH@;
/* The system TLS CA list */
const SHELL_SYSTEM_CA_FILE = '@SHELL_SYSTEM_CA_FILE@';
/* gettext package */ /* gettext package */
const GETTEXT_PACKAGE = '@GETTEXT_PACKAGE@'; const GETTEXT_PACKAGE = '@GETTEXT_PACKAGE@';
/* locale dir */ /* locale dir */

View File

@ -3,9 +3,6 @@
// Common utils for the extension system and the extension // Common utils for the extension system and the extension
// preferences tool // preferences tool
const Lang = imports.lang;
const Signals = imports.signals;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const ShellJS = imports.gi.ShellJS; const ShellJS = imports.gi.ShellJS;
@ -17,6 +14,9 @@ const ExtensionType = {
PER_USER: 2 PER_USER: 2
}; };
// GFile for user extensions
var userExtensionsDir = null;
// Maps uuid -> metadata object // Maps uuid -> metadata object
const extensions = {}; const extensions = {};
@ -40,18 +40,13 @@ function getCurrentExtension() {
throw new Error('Could not find current extension'); throw new Error('Could not find current extension');
let path = match[1]; let path = match[1];
let file = Gio.File.new_for_path(path); let uuid = GLib.path_get_basename(GLib.path_get_dirname(path));
// Walk up the directory tree, looking for an extesion with let extension = extensions[uuid];
// the same UUID as a directory name. if (extension === undefined)
while (file != null) { throw new Error('Could not find current extension');
let extension = extensions[file.get_basename()];
if (extension !== undefined)
return extension;
file = file.get_parent();
}
throw new Error('Could not find current extension'); return extension;
} }
/** /**
@ -88,6 +83,9 @@ function isOutOfDate(extension) {
if (!versionCheck(extension.metadata['shell-version'], Config.PACKAGE_VERSION)) if (!versionCheck(extension.metadata['shell-version'], Config.PACKAGE_VERSION))
return true; return true;
if (extension.metadata['js-version'] && !versionCheck(extension.metadata['js-version'], Config.GJS_VERSION))
return true;
return false; return false;
} }
@ -122,7 +120,7 @@ function createExtensionObject(uuid, dir, type) {
// Encourage people to add this // Encourage people to add this
if (!meta.url) { if (!meta.url) {
log('Warning: Missing "url" property in %s/metadata.json'.format(uuid)); global.log('Warning: Missing "url" property in metadata.json');
} }
if (uuid != meta.uuid) { if (uuid != meta.uuid) {
@ -152,55 +150,45 @@ function installImporter(extension) {
_extension = null; _extension = null;
} }
const ExtensionFinder = new Lang.Class({ function init() {
Name: 'ExtensionFinder', let userExtensionsPath = GLib.build_filenamev([global.userdatadir, 'extensions']);
userExtensionsDir = Gio.file_new_for_path(userExtensionsPath);
_scanExtensionsInDirectory: function(dir, type) { try {
let fileEnum; if (!userExtensionsDir.query_exists(null))
let file, info; userExtensionsDir.make_directory_with_parents(null);
try { } catch (e) {
fileEnum = dir.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null); global.logError('' + e);
} catch(e) {
logError(e, 'Could not enumerate extensions directory');
return;
}
while ((info = fileEnum.next_file(null)) != null) {
let fileType = info.get_file_type();
if (fileType != Gio.FileType.DIRECTORY)
continue;
let uuid = info.get_name();
let extensionDir = dir.get_child(uuid);
let existing = extensions[uuid];
if (existing) {
log('Extension %s already installed in %s. %s will not be loaded'.format(uuid, existing.path, extensionDir.get_path()));
continue;
}
let extension;
try {
extension = createExtensionObject(uuid, extensionDir, type);
} catch(e) {
logError(e, 'Could not load extension %s'.format(uuid));
continue;
}
this.emit('extension-found', extension);
}
fileEnum.close(null);
},
scanExtensions: function() {
let userExtensionsDir = Gio.File.new_for_path(GLib.build_filenamev([global.userdatadir, 'extensions']));
this._scanExtensionsInDirectory(userExtensionsDir, ExtensionType.PER_USER);
let systemDataDirs = GLib.get_system_data_dirs();
for (let i = 0; i < systemDataDirs.length; i++) {
let dirPath = GLib.build_filenamev([systemDataDirs[i], 'gnome-shell', 'extensions']);
let dir = Gio.file_new_for_path(dirPath);
if (dir.query_exists(null))
this._scanExtensionsInDirectory(dir, ExtensionType.SYSTEM);
}
} }
}); }
Signals.addSignalMethods(ExtensionFinder.prototype);
function scanExtensionsInDirectory(callback, dir, type) {
let fileEnum;
let file, info;
try {
fileEnum = dir.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null);
} catch(e) {
global.logError('' + e);
return;
}
while ((info = fileEnum.next_file(null)) != null) {
let fileType = info.get_file_type();
if (fileType != Gio.FileType.DIRECTORY)
continue;
let uuid = info.get_name();
let extensionDir = dir.get_child(uuid);
callback(uuid, extensionDir, type);
}
fileEnum.close(null);
}
function scanExtensions(callback) {
let systemDataDirs = GLib.get_system_data_dirs();
scanExtensionsInDirectory(callback, userExtensionsDir, ExtensionType.PER_USER);
for (let i = 0; i < systemDataDirs.length; i++) {
let dirPath = GLib.build_filenamev([systemDataDirs[i], 'gnome-shell', 'extensions']);
let dir = Gio.file_new_for_path(dirPath);
if (dir.query_exists(null))
scanExtensionsInDirectory(callback, dir, ExtensionType.SYSTEM);
}
}

View File

@ -28,7 +28,7 @@ function deleteGFile(file) {
return file['delete'](null); return file['delete'](null);
} }
function recursivelyDeleteDir(dir, deleteParent) { function recursivelyDeleteDir(dir) {
let children = dir.enumerate_children('standard::name,standard::type', let children = dir.enumerate_children('standard::name,standard::type',
Gio.FileQueryInfoFlags.NONE, null); Gio.FileQueryInfoFlags.NONE, null);
@ -39,29 +39,8 @@ function recursivelyDeleteDir(dir, deleteParent) {
if (type == Gio.FileType.REGULAR) if (type == Gio.FileType.REGULAR)
deleteGFile(child); deleteGFile(child);
else if (type == Gio.FileType.DIRECTORY) else if (type == Gio.FileType.DIRECTORY)
recursivelyDeleteDir(child, true); recursivelyDeleteDir(child);
} }
if (deleteParent) deleteGFile(dir);
deleteGFile(dir);
}
function recursivelyMoveDir(srcDir, destDir) {
let children = srcDir.enumerate_children('standard::name,standard::type',
Gio.FileQueryInfoFlags.NONE, null);
if (!destDir.query_exists(null))
destDir.make_directory_with_parents(null);
let info, child;
while ((info = children.next_file(null)) != null) {
let type = info.get_file_type();
let srcChild = srcDir.get_child(info.get_name());
let destChild = destDir.get_child(info.get_name());
log([srcChild.get_path(), destChild.get_path()]);
if (type == Gio.FileType.REGULAR)
srcChild.move(destChild, Gio.FileCopyFlags.NONE, null, null);
else if (type == Gio.FileType.DIRECTORY)
recursivelyMoveDir(srcChild, destChild);
}
} }

71
js/misc/format.js Normal file
View File

@ -0,0 +1,71 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const ShellJS = imports.gi.ShellJS;
/*
* This function is intended to extend the String object and provide
* an String.format API for string formatting.
* It has to be set up using String.prototype.format = Format.format;
* Usage:
* "somestring %s %d".format('hello', 5);
* It supports %s, %d, %x and %f, for %f it also support precisions like
* "%.2f".format(1.526). All specifiers can be prefixed with a minimum
* field width, e.g. "%5s".format("foo"). Unless the width is prefixed
* with '0', the formatted string will be padded with spaces.
*/
function format() {
let str = this;
let i = 0;
let args = arguments;
return str.replace(/%(I+)?([0-9]+)?(?:\.([0-9]+))?(.)/g, function (str, flagsGroup, widthGroup, precisionGroup, genericGroup) {
if (precisionGroup != '' && genericGroup != 'f')
throw new Error("Precision can only be specified for 'f'");
let hasAlternativeIntFlag = (flagsGroup.indexOf('I') != -1);
if (hasAlternativeIntFlag && genericGroup != 'd')
throw new Error("Alternative output digits can only be specfied for 'd'");
let fillChar = (widthGroup[0] == '0') ? '0' : ' ';
let width = parseInt(widthGroup, 10) || 0;
function fillWidth(s, c, w) {
let fill = '';
for (let i = 0; i < w; i++)
fill += c;
return fill.substr(s.length) + s;
}
let s = '';
switch (genericGroup) {
case '%':
return '%';
break;
case 's':
s = args[i++].toString();
break;
case 'd':
let intV = parseInt(args[i++]);
if (hasAlternativeIntFlag)
s = ShellJS.format_int_alternative_output(intV);
else
s = intV.toString();
break;
case 'x':
s = parseInt(args[i++]).toString(16);
break;
case 'f':
if (precisionGroup == '')
s = parseFloat(args[i++]).toString();
else
s = parseFloat(args[i++]).toFixed(parseInt(precisionGroup));
break;
default:
throw new Error('Unsupported conversion character %' + genericGroup);
}
return fillWidth(s, fillChar, width);
});
}

View File

@ -50,20 +50,9 @@ const SessionManagerIface = <interface name="org.gnome.SessionManager">
<arg type="u" direction="in" /> <arg type="u" direction="in" />
</method> </method>
<method name="Shutdown" /> <method name="Shutdown" />
<method name="Reboot" />
<method name="CanShutdown"> <method name="CanShutdown">
<arg type="b" direction="out" /> <arg type="b" direction="out" />
</method> </method>
<method name="IsInhibited">
<arg type="u" direction="in" />
<arg type="b" direction="out" />
</method>
<signal name="InhibitorAdded">
<arg type="o" direction="out"/>
</signal>
<signal name="InhibitorRemoved">
<arg type="o" direction="out"/>
</signal>
</interface>; </interface>;
var SessionManagerProxy = Gio.DBusProxy.makeProxyWrapper(SessionManagerIface); var SessionManagerProxy = Gio.DBusProxy.makeProxyWrapper(SessionManagerIface);

View File

@ -1,197 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const Shell = imports.gi.Shell;
const SystemdLoginManagerIface = <interface name='org.freedesktop.login1.Manager'>
<method name='PowerOff'>
<arg type='b' direction='in'/>
</method>
<method name='Reboot'>
<arg type='b' direction='in'/>
</method>
<method name='CanPowerOff'>
<arg type='s' direction='out'/>
</method>
<method name='CanReboot'>
<arg type='s' direction='out'/>
</method>
</interface>;
const SystemdLoginSessionIface = <interface name='org.freedesktop.login1.Session'>
<signal name='Lock' />
<signal name='Unlock' />
</interface>;
const SystemdLoginManager = Gio.DBusProxy.makeProxyWrapper(SystemdLoginManagerIface);
const SystemdLoginSession = Gio.DBusProxy.makeProxyWrapper(SystemdLoginSessionIface);
const ConsoleKitManagerIface = <interface name='org.freedesktop.ConsoleKit.Manager'>
<method name='CanRestart'>
<arg type='b' direction='out'/>
</method>
<method name='CanStop'>
<arg type='b' direction='out'/>
</method>
<method name='Restart' />
<method name='Stop' />
<method name='GetCurrentSession'>
<arg type='o' direction='out' />
</method>
</interface>;
const ConsoleKitSessionIface = <interface name='org.freedesktop.ConsoleKit.Session'>
<method name='IsActive'>
<arg type='b' direction='out' />
</method>
<signal name='ActiveChanged'>
<arg type='b' direction='out' />
</signal>
<signal name='Lock' />
<signal name='Unlock' />
</interface>;
const ConsoleKitSession = Gio.DBusProxy.makeProxyWrapper(ConsoleKitSessionIface);
const ConsoleKitManager = Gio.DBusProxy.makeProxyWrapper(ConsoleKitManagerIface);
function haveSystemd() {
return GLib.access("/sys/fs/cgroup/systemd", 0) >= 0;
}
let _loginManager = null;
/**
* LoginManager:
* An abstraction over systemd/logind and ConsoleKit.
*
*/
function getLoginManager() {
if (_loginManager == null) {
if (haveSystemd())
_loginManager = new LoginManagerSystemd();
else
_loginManager = new LoginManagerConsoleKit();
}
return _loginManager;
}
const LoginManagerSystemd = new Lang.Class({
Name: 'LoginManagerSystemd',
_init: function() {
this._proxy = new SystemdLoginManager(Gio.DBus.system,
'org.freedesktop.login1',
'/org/freedesktop/login1');
},
// Having this function is a bit of a hack since the Systemd and ConsoleKit
// session objects have different interfaces - but in both cases there are
// Lock/Unlock signals, and that's all we count upon at the moment.
getCurrentSessionProxy: function() {
if (!this._currentSession) {
this._currentSession = new SystemdLoginSession(Gio.DBus.system,
'org.freedesktop.login1',
'/org/freedesktop/login1/session/' +
GLib.getenv('XDG_SESSION_ID'));
}
return this._currentSession;
},
get sessionActive() {
return Shell.session_is_active_for_systemd();
},
canPowerOff: function(asyncCallback) {
this._proxy.CanPowerOffRemote(function(result, error) {
if (error)
asyncCallback(false);
else
asyncCallback(result[0] != 'no');
});
},
canReboot: function(asyncCallback) {
this._proxy.CanRebootRemote(function(result, error) {
if (error)
asyncCallback(false);
else
asyncCallback(result[0] != 'no');
});
},
powerOff: function() {
this._proxy.PowerOffRemote(true);
},
reboot: function() {
this._proxy.RebootRemote(true);
}
});
const LoginManagerConsoleKit = new Lang.Class({
Name: 'LoginManagerConsoleKit',
_init: function() {
this._proxy = new ConsoleKitManager(Gio.DBus.system,
'org.freedesktop.ConsoleKit',
'/org/freedesktop/ConsoleKit/Manager');
},
// Having this function is a bit of a hack since the Systemd and ConsoleKit
// session objects have different interfaces - but in both cases there are
// Lock/Unlock signals, and that's all we count upon at the moment.
getCurrentSessionProxy: function() {
if (!this._currentSession) {
let [currentSessionId] = this._proxy.GetCurrentSessionSync();
this._currentSession = new ConsoleKitSession(Gio.DBus.system,
'org.freedesktop.ConsoleKit',
currentSessionId);
}
return this._currentSession;
},
get sessionActive() {
if (this._sessionActive !== undefined)
return this._sessionActive;
let session = this.getCurrentSessionProxy();
session.connectSignal('ActiveChanged', Lang.bind(this, function(object, senderName, [isActive]) {
this._sessionActive = isActive;
}));
[this._sessionActive] = session.IsActiveSync();
return this._sessionActive;
},
canPowerOff: function(asyncCallback) {
this._proxy.CanStopRemote(function(result, error) {
if (error)
asyncCallback(false);
else
asyncCallback(result[0]);
});
},
canReboot: function(asyncCallback) {
this._proxy.CanRestartRemote(function(result, error) {
if (error)
asyncCallback(false);
else
asyncCallback(result[0]);
});
},
powerOff: function() {
this._proxy.StopRemote();
},
reboot: function() {
this._proxy.RestartRemote();
}
});

48
js/misc/screenSaver.js Normal file
View File

@ -0,0 +1,48 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const ScreenSaverIface = <interface name="org.gnome.ScreenSaver">
<method name="GetActive">
<arg type="b" direction="out" />
</method>
<method name="Lock" />
<method name="SetActive">
<arg type="b" direction="in" />
</method>
<signal name="ActiveChanged">
<arg type="b" direction="out" />
</signal>
</interface>;
const ScreenSaverInfo = Gio.DBusInterfaceInfo.new_for_xml(ScreenSaverIface);
function ScreenSaverProxy() {
var self = new Gio.DBusProxy({ g_connection: Gio.DBus.session,
g_interface_name: ScreenSaverInfo.name,
g_interface_info: ScreenSaverInfo,
g_name: 'org.gnome.ScreenSaver',
g_object_path: '/org/gnome/ScreenSaver',
g_flags: (Gio.DBusProxyFlags.DO_NOT_AUTO_START |
Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES) });
self.init(null);
self.screenSaverActive = false;
self.connectSignal('ActiveChanged', function(proxy, senderName, [isActive]) {
self.screenSaverActive = isActive;
});
self.connect('notify::g-name-owner', function() {
if (self.g_name_owner) {
self.GetActiveRemote(function(result, excp) {
if (result) {
let [isActive] = result;
self.screenSaverActive = isActive;
}
});
} else
self.screenSaverActive = false;
});
return self;
}

View File

@ -1,6 +1,9 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Gdk = imports.gi.Gdk;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Shell = imports.gi.Shell;
const Main = imports.ui.main; const Main = imports.ui.main;
@ -80,33 +83,24 @@ function spawnCommandLine(command_line) {
// this will throw an error. // this will throw an error.
function trySpawn(argv) function trySpawn(argv)
{ {
var success, pid;
try { try {
[success, pid] = GLib.spawn_async(null, argv, null, GLib.spawn_async(null, argv, null,
GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD, GLib.SpawnFlags.SEARCH_PATH,
null); null, null);
} catch (err) { } catch (err) {
/* Rewrite the error in case of ENOENT */ if (err.code == GLib.SpawnError.G_SPAWN_ERROR_NOENT) {
if (err.matches(GLib.SpawnError, GLib.SpawnError.NOENT)) { err.message = _("Command not found");
throw new GLib.SpawnError({ code: GLib.SpawnError.NOENT, } else {
message: _("Command not found") });
} else if (err instanceof GLib.Error) {
// The exception from gjs contains an error string like: // The exception from gjs contains an error string like:
// Error invoking GLib.spawn_command_line_async: Failed to // Error invoking GLib.spawn_command_line_async: Failed to
// execute child process "foo" (No such file or directory) // execute child process "foo" (No such file or directory)
// We are only interested in the part in the parentheses. (And // We are only interested in the part in the parentheses. (And
// we can't pattern match the text, since it gets localized.) // we can't pattern match the text, since it gets localized.)
let message = err.message.replace(/.*\((.+)\)/, '$1'); err.message = err.message.replace(/.*\((.+)\)/, '$1');
throw new (err.constructor)({ code: err.code,
message: message });
} else {
throw err;
} }
throw err;
} }
// Dummy child watch; we don't want to double-fork internally
// because then we lose the parent-child relationship, which
// can break polkit. See https://bugzilla.redhat.com//show_bug.cgi?id=819275
GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, function () {}, null);
} }
// trySpawnCommandLine: // trySpawnCommandLine:
@ -150,7 +144,7 @@ function killall(processName) {
// whatever... // whatever...
let argv = ['pkill', '-f', '^([^ ]*/)?' + processName + '($| )']; let argv = ['pkill', '-f', '^([^ ]*/)?' + processName + '($| )'];
GLib.spawn_sync(null, argv, null, GLib.SpawnFlags.SEARCH_PATH, null); GLib.spawn_sync(null, argv, null, GLib.SpawnFlags.SEARCH_PATH, null, null);
// It might be useful to return success/failure, but we'd need // It might be useful to return success/failure, but we'd need
// a wrapper around WIFEXITED and WEXITSTATUS. Since none of // a wrapper around WIFEXITED and WEXITSTATUS. Since none of
// the current callers care, we don't bother. // the current callers care, we don't bother.

View File

@ -1,7 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const System = imports.system;
const Main = imports.ui.main; const Main = imports.ui.main;
const Scripting = imports.ui.scripting; const Scripting = imports.ui.scripting;
@ -101,7 +99,7 @@ function run() {
Main.overview.hide(); Main.overview.hide();
yield Scripting.waitLeisure(); yield Scripting.waitLeisure();
System.gc(); global.gc();
yield Scripting.sleep(1000); yield Scripting.sleep(1000);
Scripting.collectStatistics(); Scripting.collectStatistics();
Scripting.scriptEvent('afterShowHide'); Scripting.scriptEvent('afterShowHide');
@ -115,10 +113,10 @@ function run() {
for (let i = 0; i < 2; i++) { for (let i = 0; i < 2; i++) {
Scripting.scriptEvent('applicationsShowStart'); Scripting.scriptEvent('applicationsShowStart');
Main.overview._dash.showAppsButton.checked = true; Main.overview._viewSelector.switchTab('applications');
yield Scripting.waitLeisure(); yield Scripting.waitLeisure();
Scripting.scriptEvent('applicationsShowDone'); Scripting.scriptEvent('applicationsShowDone');
Main.overview._dash.showAppsButton.checked = false; Main.overview._viewSelector.switchTab('windows');
yield Scripting.waitLeisure(); yield Scripting.waitLeisure();
} }
} }

View File

@ -22,7 +22,6 @@ const Search = imports.ui.search;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const Workspace = imports.ui.workspace; const Workspace = imports.ui.workspace;
const Params = imports.misc.params; const Params = imports.misc.params;
const Util = imports.misc.util;
const MAX_APPLICATION_WORK_MILLIS = 75; const MAX_APPLICATION_WORK_MILLIS = 75;
const MENU_POPUP_TIMEOUT = 600; const MENU_POPUP_TIMEOUT = 600;
@ -37,7 +36,6 @@ const AlphabeticalView = new Lang.Class({
this._pendingAppLaterId = 0; this._pendingAppLaterId = 0;
this._appIcons = {}; // desktop file id this._appIcons = {}; // desktop file id
this._allApps = [];
let box = new St.BoxLayout({ vertical: true }); let box = new St.BoxLayout({ vertical: true });
box.add(this._grid.actor, { y_align: St.Align.START, expand: true }); box.add(this._grid.actor, { y_align: St.Align.START, expand: true });
@ -62,22 +60,16 @@ const AlphabeticalView = new Lang.Class({
})); }));
}, },
removeAll: function() { _removeAll: function() {
this._grid.removeAll(); this._grid.removeAll();
this._appIcons = {}; this._appIcons = {};
this._allApps = [];
}, },
addApp: function(app) { _addApp: function(app) {
var id = app.get_id(); var id = app.get_id();
if (this._appIcons[id] !== undefined)
return;
let appIcon = new AppWellIcon(app); let appIcon = new AppWellIcon(app);
let pos = Util.insertSorted(this._allApps, app, function(a, b) {
return a.compare_by_name(b); this._grid.addItem(appIcon.actor);
});
this._grid.addItem(appIcon.actor, pos);
appIcon.actor.connect('key-focus-in', Lang.bind(this, this._ensureIconVisible)); appIcon.actor.connect('key-focus-in', Lang.bind(this, this._ensureIconVisible));
this._appIcons[id] = appIcon; this._appIcons[id] = appIcon;
@ -128,6 +120,14 @@ const AlphabeticalView = new Lang.Class({
icon.actor.visible = true; icon.actor.visible = true;
} }
} }
},
setAppList: function(apps) {
this._removeAll();
for (var i = 0; i < apps.length; i++) {
var app = apps[i];
this._addApp(app);
}
} }
}); });
@ -147,6 +147,7 @@ const ViewByCategories = new Lang.Class({
// (used only before the actor is mapped the first time) // (used only before the actor is mapped the first time)
this._currentCategory = -2; this._currentCategory = -2;
this._categories = []; this._categories = [];
this._apps = null;
this._categoryBox = new St.BoxLayout({ vertical: true, this._categoryBox = new St.BoxLayout({ vertical: true,
reactive: true, reactive: true,
@ -203,19 +204,16 @@ const ViewByCategories = new Lang.Class({
if (nextType == GMenu.TreeItemType.ENTRY) { if (nextType == GMenu.TreeItemType.ENTRY) {
var entry = iter.get_entry(); var entry = iter.get_entry();
var app = this._appSystem.lookup_app_by_tree_entry(entry); var app = this._appSystem.lookup_app_by_tree_entry(entry);
if (!entry.get_app_info().get_nodisplay()) { if (!entry.get_app_info().get_nodisplay())
this._view.addApp(app);
appList.push(app); appList.push(app);
}
} else if (nextType == GMenu.TreeItemType.DIRECTORY) { } else if (nextType == GMenu.TreeItemType.DIRECTORY) {
var itemDir = iter.get_directory(); if (!dir.get_is_nodisplay())
if (!itemDir.get_is_nodisplay()) this._loadCategory(iter.get_directory(), appList);
this._loadCategory(itemDir, appList);
} }
} }
}, },
_addCategory: function(name, index, dir) { _addCategory: function(name, index, dir, allApps) {
let button = new St.Button({ label: GLib.markup_escape_text (name, -1), let button = new St.Button({ label: GLib.markup_escape_text (name, -1),
style_class: 'app-filter', style_class: 'app-filter',
x_align: St.Align.START, x_align: St.Align.START,
@ -227,6 +225,7 @@ const ViewByCategories = new Lang.Class({
var apps; var apps;
if (dir == null) { if (dir == null) {
apps = allApps;
this._allCategoryButton = button; this._allCategoryButton = button;
} else { } else {
apps = []; apps = [];
@ -240,7 +239,6 @@ const ViewByCategories = new Lang.Class({
}, },
_removeAll: function() { _removeAll: function() {
this._view.removeAll();
this._categories = []; this._categories = [];
this._categoryBox.destroy_all_children(); this._categoryBox.destroy_all_children();
}, },
@ -248,8 +246,13 @@ const ViewByCategories = new Lang.Class({
refresh: function() { refresh: function() {
this._removeAll(); this._removeAll();
var allApps = Shell.AppSystem.get_default().get_all();
allApps.sort(function(a, b) {
return a.compare_by_name(b);
});
/* Translators: Filter to display all applications */ /* Translators: Filter to display all applications */
this._addCategory(_("All"), -1, null); this._addCategory(_("All"), -1, null, allApps);
var tree = this._appSystem.get_tree(); var tree = this._appSystem.get_tree();
var root = tree.get_root_directory(); var root = tree.get_root_directory();
@ -267,6 +270,7 @@ const ViewByCategories = new Lang.Class({
} }
} }
this._view.setAppList(allApps);
this._selectCategory(-1); this._selectCategory(-1);
if (this._focusDummy) { if (this._focusDummy) {
@ -308,10 +312,11 @@ const AppSearchProvider = new Lang.Class({
_init: function() { _init: function() {
this.parent(_("APPLICATIONS")); this.parent(_("APPLICATIONS"));
this._appSys = Shell.AppSystem.get_default(); this._appSys = Shell.AppSystem.get_default();
}, },
getResultMetas: function(apps, callback) { getResultMetas: function(apps) {
let metas = []; let metas = [];
for (let i = 0; i < apps.length; i++) { for (let i = 0; i < apps.length; i++) {
let app = apps[i]; let app = apps[i];
@ -322,15 +327,15 @@ const AppSearchProvider = new Lang.Class({
} }
}); });
} }
callback(metas); return metas;
}, },
getInitialResultSet: function(terms) { getInitialResultSet: function(terms) {
this.searchSystem.pushResults(this, this._appSys.initial_search(terms)); return this._appSys.initial_search(terms);
}, },
getSubsearchResultSet: function(previousResults, terms) { getSubsearchResultSet: function(previousResults, terms) {
this.searchSystem.pushResults(this, this._appSys.subsearch(previousResults, terms)); return this._appSys.subsearch(previousResults, terms);
}, },
activateResult: function(app, params) { activateResult: function(app, params) {
@ -373,7 +378,7 @@ const SettingsSearchProvider = new Lang.Class({
this._gnomecc = this._appSys.lookup_app('gnome-control-center.desktop'); this._gnomecc = this._appSys.lookup_app('gnome-control-center.desktop');
}, },
getResultMetas: function(prefs, callback) { getResultMetas: function(prefs) {
let metas = []; let metas = [];
for (let i = 0; i < prefs.length; i++) { for (let i = 0; i < prefs.length; i++) {
let pref = prefs[i]; let pref = prefs[i];
@ -384,15 +389,15 @@ const SettingsSearchProvider = new Lang.Class({
} }
}); });
} }
callback(metas); return metas;
}, },
getInitialResultSet: function(terms) { getInitialResultSet: function(terms) {
this.searchSystem.pushResults(this, this._appSys.search_settings(terms)); return this._appSys.search_settings(terms);
}, },
getSubsearchResultSet: function(previousResults, terms) { getSubsearchResultSet: function(previousResults, terms) {
this.searchSystem.pushResults(this, this._appSys.search_settings(terms)); return this._appSys.search_settings(terms);
}, },
activateResult: function(pref, params) { activateResult: function(pref, params) {

View File

@ -5,14 +5,11 @@ const Mainloop = imports.mainloop;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const Params = imports.misc.params; const Params = imports.misc.params;
const Shell = imports.gi.Shell;
const GnomeSession = imports.misc.gnomeSession; const Shell = imports.gi.Shell;
const LoginManager = imports.misc.loginManager;
const Main = imports.ui.main; const Main = imports.ui.main;
const ShellMountOperation = imports.ui.shellMountOperation; const ShellMountOperation = imports.ui.shellMountOperation;
const ScreenSaver = imports.misc.screenSaver;
const GNOME_SESSION_AUTOMOUNT_INHIBIT = 16;
// GSettings keys // GSettings keys
const SETTINGS_SCHEMA = 'org.gnome.desktop.media-handling'; const SETTINGS_SCHEMA = 'org.gnome.desktop.media-handling';
@ -20,22 +17,75 @@ const SETTING_ENABLE_AUTOMOUNT = 'automount';
const AUTORUN_EXPIRE_TIMEOUT_SECS = 10; const AUTORUN_EXPIRE_TIMEOUT_SECS = 10;
const ConsoleKitSessionIface = <interface name="org.freedesktop.ConsoleKit.Session">
<method name="IsActive">
<arg type="b" direction="out" />
</method>
<signal name="ActiveChanged">
<arg type="b" direction="out" />
</signal>
</interface>;
const ConsoleKitSessionProxy = Gio.DBusProxy.makeProxyWrapper(ConsoleKitSessionIface);
const ConsoleKitManagerIface = <interface name="org.freedesktop.ConsoleKit.Manager">
<method name="GetCurrentSession">
<arg type="o" direction="out" />
</method>
</interface>;
const ConsoleKitManagerInfo = Gio.DBusInterfaceInfo.new_for_xml(ConsoleKitManagerIface);
function ConsoleKitManager() {
var self = new Gio.DBusProxy({ g_connection: Gio.DBus.system,
g_interface_name: ConsoleKitManagerInfo.name,
g_interface_info: ConsoleKitManagerInfo,
g_name: 'org.freedesktop.ConsoleKit',
g_object_path: '/org/freedesktop/ConsoleKit/Manager',
g_flags: (Gio.DBusProxyFlags.DO_NOT_AUTO_START |
Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES) });
self._updateSessionActive = function() {
if (self.g_name_owner) {
self.GetCurrentSessionRemote(function([session]) {
self._ckSession = new ConsoleKitSessionProxy(Gio.DBus.system, 'org.freedesktop.ConsoleKit', session);
self._ckSession.connectSignal('ActiveChanged', function(object, senderName, [isActive]) {
self.sessionActive = isActive;
});
self._ckSession.IsActiveRemote(function([isActive]) {
self.sessionActive = isActive;
});
});
} else {
self.sessionActive = true;
}
};
self.connect('notify::g-name-owner',
Lang.bind(self, self._updateSessionActive));
self._updateSessionActive();
self.init(null);
return self;
}
function haveSystemd() {
return GLib.access("/sys/fs/cgroup/systemd", 0) >= 0;
}
const AutomountManager = new Lang.Class({ const AutomountManager = new Lang.Class({
Name: 'AutomountManager', Name: 'AutomountManager',
_init: function() { _init: function() {
this._settings = new Gio.Settings({ schema: SETTINGS_SCHEMA }); this._settings = new Gio.Settings({ schema: SETTINGS_SCHEMA });
this._volumeQueue = []; this._volumeQueue = [];
this._session = new GnomeSession.SessionManager();
this._session.connectSignal('InhibitorAdded',
Lang.bind(this, this._InhibitorsChanged));
this._session.connectSignal('InhibitorRemoved',
Lang.bind(this, this._InhibitorsChanged));
this._inhibited = false;
this._loginManager = LoginManager.getLoginManager(); if (!haveSystemd())
this.ckListener = new ConsoleKitManager();
Main.screenShield.connect('lock-status-changed', Lang.bind(this, this._lockStatusChanged)); this._ssProxy = new ScreenSaver.ScreenSaverProxy();
this._ssProxy.connectSignal('ActiveChanged',
Lang.bind(this, this._screenSaverActiveChanged));
this._volumeMonitor = Gio.VolumeMonitor.get(); this._volumeMonitor = Gio.VolumeMonitor.get();
@ -58,18 +108,8 @@ const AutomountManager = new Lang.Class({
Mainloop.idle_add(Lang.bind(this, this._startupMountAll)); Mainloop.idle_add(Lang.bind(this, this._startupMountAll));
}, },
_InhibitorsChanged: function(object, senderName, [inhibtor]) { _screenSaverActiveChanged: function(object, senderName, [isActive]) {
this._session.IsInhibitedRemote(GNOME_SESSION_AUTOMOUNT_INHIBIT, if (!isActive) {
Lang.bind(this,
function(result, error) {
if (!error) {
this._inhibited = result[0];
}
}));
},
_lockStatusChanged: function(shield, locked) {
if (!locked) {
this._volumeQueue.forEach(Lang.bind(this, function(volume) { this._volumeQueue.forEach(Lang.bind(this, function(volume) {
this._checkAndMountVolume(volume); this._checkAndMountVolume(volume);
})); }));
@ -83,20 +123,30 @@ const AutomountManager = new Lang.Class({
let volumes = this._volumeMonitor.get_volumes(); let volumes = this._volumeMonitor.get_volumes();
volumes.forEach(Lang.bind(this, function(volume) { volumes.forEach(Lang.bind(this, function(volume) {
this._checkAndMountVolume(volume, { checkSession: false, this._checkAndMountVolume(volume, { checkSession: false,
useMountOp: false, useMountOp: false });
allowAutorun: false });
})); }));
return false; return false;
}, },
isSessionActive: function() {
// Return whether the current session is active, using the
// right mechanism: either systemd if available or ConsoleKit
// as fallback.
if (haveSystemd())
return Shell.session_is_active_for_systemd();
return this.ckListener.sessionActive;
},
_onDriveConnected: function() { _onDriveConnected: function() {
// if we're not in the current ConsoleKit session, // if we're not in the current ConsoleKit session,
// or screensaver is active, don't play sounds // or screensaver is active, don't play sounds
if (!this._loginManager.sessionActive) if (!this.isSessionActive())
return; return;
if (Main.screenShield.locked) if (this._ssProxy.screenSaverActive)
return; return;
global.play_theme_sound(0, 'device-added-media'); global.play_theme_sound(0, 'device-added-media');
@ -105,10 +155,10 @@ const AutomountManager = new Lang.Class({
_onDriveDisconnected: function() { _onDriveDisconnected: function() {
// if we're not in the current ConsoleKit session, // if we're not in the current ConsoleKit session,
// or screensaver is active, don't play sounds // or screensaver is active, don't play sounds
if (!this._loginManager.sessionActive) if (!this.isSessionActive())
return; return;
if (Main.screenShield.locked) if (this._ssProxy.screenSaverActive)
return; return;
global.play_theme_sound(0, 'device-removed-media'); global.play_theme_sound(0, 'device-removed-media');
@ -117,7 +167,7 @@ const AutomountManager = new Lang.Class({
_onDriveEjectButton: function(monitor, drive) { _onDriveEjectButton: function(monitor, drive) {
// TODO: this code path is not tested, as the GVfs volume monitor // TODO: this code path is not tested, as the GVfs volume monitor
// doesn't emit this signal just yet. // doesn't emit this signal just yet.
if (!this._loginManager.sessionActive) if (!this.isSessionActive())
return; return;
// we force stop/eject in this case, so we don't have to pass a // we force stop/eject in this case, so we don't have to pass a
@ -151,16 +201,15 @@ const AutomountManager = new Lang.Class({
_checkAndMountVolume: function(volume, params) { _checkAndMountVolume: function(volume, params) {
params = Params.parse(params, { checkSession: true, params = Params.parse(params, { checkSession: true,
useMountOp: true, useMountOp: true });
allowAutorun: true });
if (params.checkSession) { if (params.checkSession) {
// if we're not in the current ConsoleKit session, // if we're not in the current ConsoleKit session,
// don't attempt automount // don't attempt automount
if (!this._loginManager.sessionActive) if (!this.isSessionActive())
return; return;
if (Main.screenShield.locked) { if (this._ssProxy.screenSaverActive) {
if (this._volumeQueue.indexOf(volume) == -1) if (this._volumeQueue.indexOf(volume) == -1)
this._volumeQueue.push(volume); this._volumeQueue.push(volume);
@ -168,9 +217,6 @@ const AutomountManager = new Lang.Class({
} }
} }
if (this._inhibited)
return;
// Volume is already mounted, don't bother. // Volume is already mounted, don't bother.
if (volume.get_mount()) if (volume.get_mount())
return; return;
@ -190,20 +236,15 @@ const AutomountManager = new Lang.Class({
if (params.useMountOp) { if (params.useMountOp) {
let operation = new ShellMountOperation.ShellMountOperation(volume); let operation = new ShellMountOperation.ShellMountOperation(volume);
this._mountVolume(volume, operation, params.allowAutorun); this._mountVolume(volume, operation.mountOp);
} else { } else {
this._mountVolume(volume, null, params.allowAutorun); this._mountVolume(volume, null);
} }
}, },
_mountVolume: function(volume, operation, allowAutorun) { _mountVolume: function(volume, operation) {
if (allowAutorun) this._allowAutorun(volume);
this._allowAutorun(volume); volume.mount(0, operation, null,
let mountOp = operation ? operation.mountOp : null;
volume._operation = operation;
volume.mount(0, mountOp, null,
Lang.bind(this, this._onVolumeMounted)); Lang.bind(this, this._onVolumeMounted));
}, },
@ -212,19 +253,15 @@ const AutomountManager = new Lang.Class({
try { try {
volume.mount_finish(res); volume.mount_finish(res);
this._closeOperation(volume);
} catch (e) { } catch (e) {
// FIXME: we will always get G_IO_ERROR_FAILED from the gvfs udisks let string = e.toString();
// backend in this case, see
// https://bugs.freedesktop.org/show_bug.cgi?id=51271
if (e.message.indexOf('No key available with this passphrase') != -1) {
this._reaskPassword(volume);
} else {
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED_HANDLED))
log('Unable to mount volume ' + volume.get_name() + ': ' + e.toString());
this._closeOperation(volume); // FIXME: needs proper error code handling instead of this
} // See https://bugzilla.gnome.org/show_bug.cgi?id=591480
if (string.indexOf('No key available with this passphrase') != -1)
this._reaskPassword(volume);
else
log('Unable to mount volume ' + volume.get_name() + ': ' + string);
} }
}, },
@ -236,16 +273,8 @@ const AutomountManager = new Lang.Class({
}, },
_reaskPassword: function(volume) { _reaskPassword: function(volume) {
let existingDialog = volume._operation ? volume._operation.borrowDialog() : null; let operation = new ShellMountOperation.ShellMountOperation(volume, { reaskPassword: true });
let operation = this._mountVolume(volume, operation.mountOp);
new ShellMountOperation.ShellMountOperation(volume,
{ existingDialog: existingDialog });
this._mountVolume(volume, operation);
},
_closeOperation: function(volume) {
if (volume._operation)
volume._operation.close();
}, },
_allowAutorun: function(volume) { _allowAutorun: function(volume) {

View File

@ -4,7 +4,6 @@ const Lang = imports.lang;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const St = imports.gi.St; const St = imports.gi.St;
const LoginManager = imports.misc.loginManager;
const Main = imports.ui.main; const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray; const MessageTray = imports.ui.messageTray;
const ShellMountOperation = imports.ui.shellMountOperation; const ShellMountOperation = imports.ui.shellMountOperation;
@ -24,14 +23,12 @@ const AutorunSetting = {
}; };
// misc utils // misc utils
function shouldAutorunMount(mount, forTransient) { function ignoreAutorunForMount(mount) {
let root = mount.get_root(); let root = mount.get_root();
let volume = mount.get_volume(); let volume = mount.get_volume();
if (!volume || (!volume.allowAutorun && forTransient)) if ((root.is_native() && !isMountRootHidden(root)) ||
return false; (volume && volume.allowAutorun && volume.should_automount()))
if (!root.is_native() || isMountRootHidden(root))
return false; return false;
return true; return true;
@ -143,8 +140,6 @@ const AutorunManager = new Lang.Class({
Name: 'AutorunManager', Name: 'AutorunManager',
_init: function() { _init: function() {
this._loginManager = LoginManager.getLoginManager();
this._volumeMonitor = Gio.VolumeMonitor.get(); this._volumeMonitor = Gio.VolumeMonitor.get();
this._volumeMonitor.connect('mount-added', this._volumeMonitor.connect('mount-added',
@ -179,7 +174,7 @@ const AutorunManager = new Lang.Class({
_onMountAdded: function(monitor, mount) { _onMountAdded: function(monitor, mount) {
// don't do anything if our session is not the currently // don't do anything if our session is not the currently
// active one // active one
if (!this._loginManager.sessionActive) if (!Main.automountManager.isSessionActive())
return; return;
let discoverer = new ContentTypeDiscoverer(Lang.bind (this, let discoverer = new ContentTypeDiscoverer(Lang.bind (this,
@ -229,9 +224,11 @@ const AutorunManager = new Lang.Class({
try { try {
mount.unmount_with_operation_finish(res); mount.unmount_with_operation_finish(res);
} catch (e) { } catch (e) {
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED_HANDLED)) // FIXME: we need to ignore G_IO_ERROR_FAILED_HANDLED errors here
log('Unable to eject the mount ' + mount.get_name() // but we can't access the error code from JS.
+ ': ' + e.toString()); // See https://bugzilla.gnome.org/show_bug.cgi?id=591480
log('Unable to eject the mount ' + mount.get_name()
+ ': ' + e.toString());
} }
}, },
@ -239,9 +236,11 @@ const AutorunManager = new Lang.Class({
try { try {
source.eject_with_operation_finish(res); source.eject_with_operation_finish(res);
} catch (e) { } catch (e) {
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED_HANDLED)) // FIXME: we need to ignore G_IO_ERROR_FAILED_HANDLED errors here
log('Unable to eject the drive ' + source.get_name() // but we can't access the error code from JS.
+ ': ' + e.toString()); // See https://bugzilla.gnome.org/show_bug.cgi?id=591480
log('Unable to eject the drive ' + source.get_name()
+ ': ' + e.toString());
} }
}, },
@ -249,9 +248,11 @@ const AutorunManager = new Lang.Class({
try { try {
drive.stop_finish(res); drive.stop_finish(res);
} catch (e) { } catch (e) {
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED_HANDLED)) // FIXME: we need to ignore G_IO_ERROR_FAILED_HANDLED errors here
log('Unable to stop the drive ' + drive.get_name() // but we can't access the error code from JS.
+ ': ' + e.toString()); // See https://bugzilla.gnome.org/show_bug.cgi?id=591480
log('Unable to stop the drive ' + drive.get_name()
+ ': ' + e.toString());
} }
}, },
}); });
@ -261,16 +262,16 @@ const AutorunResidentSource = new Lang.Class({
Extends: MessageTray.Source, Extends: MessageTray.Source,
_init: function() { _init: function() {
this.parent(_("Removable Devices"), 'media-removable'); this.parent(_("Removable Devices"));
this.showInLockScreen = false;
this._mounts = []; this._mounts = [];
this._notification = new AutorunResidentNotification(this); this._notification = new AutorunResidentNotification(this);
this._setSummaryIcon(this.createNotificationIcon());
}, },
addMount: function(mount, apps) { addMount: function(mount, apps) {
if (!shouldAutorunMount(mount, false)) if (ignoreAutorunForMount(mount))
return; return;
let filtered = this._mounts.filter(function (element) { let filtered = this._mounts.filter(function (element) {
@ -309,6 +310,12 @@ const AutorunResidentSource = new Lang.Class({
Main.messageTray.add(this); Main.messageTray.add(this);
this.pushNotification(this._notification); this.pushNotification(this._notification);
} }
},
createNotificationIcon: function() {
return new St.Icon ({ icon_name: 'media-removable',
icon_type: St.IconType.FULLCOLOR,
icon_size: this.ICON_SIZE });
} }
}); });
@ -448,7 +455,7 @@ const AutorunTransientDispatcher = new Lang.Class({
return; return;
// if the mount doesn't want to be autorun, return // if the mount doesn't want to be autorun, return
if (!shouldAutorunMount(mount, true)) if (ignoreAutorunForMount(mount))
return; return;
let setting = this._getAutorunSettingForType(contentTypes[0]); let setting = this._getAutorunSettingForType(contentTypes[0]);
@ -493,21 +500,22 @@ const AutorunTransientSource = new Lang.Class({
Extends: MessageTray.Source, Extends: MessageTray.Source,
_init: function(mount, apps) { _init: function(mount, apps) {
this.parent(mount.get_name());
this.mount = mount; this.mount = mount;
this.apps = apps; this.apps = apps;
this.parent(mount.get_name());
this._notification = new AutorunTransientNotification(this); this._notification = new AutorunTransientNotification(this);
this._setSummaryIcon(this.createNotificationIcon());
// add ourselves as a source, and popup the notification // add ourselves as a source, and popup the notification
Main.messageTray.add(this); Main.messageTray.add(this);
this.notify(this._notification); this.notify(this._notification);
}, },
createIcon: function(size) { createNotificationIcon: function() {
return new St.Icon({ gicon: this.mount.get_icon(), return new St.Icon({ gicon: this.mount.get_icon(),
icon_size: size }); icon_size: this.ICON_SIZE });
} }
}); });

View File

@ -9,13 +9,6 @@ const Shell = imports.gi.Shell;
const Main = imports.ui.main; const Main = imports.ui.main;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const PopupAnimation = {
NONE: 0,
SLIDE: 1 << 0,
FADE: 1 << 1,
FULL: ~0,
};
const POPUP_ANIMATION_TIME = 0.15; const POPUP_ANIMATION_TIME = 0.15;
/** /**
@ -25,10 +18,7 @@ const POPUP_ANIMATION_TIME = 0.15;
* *
* An actor which displays a triangle "arrow" pointing to a given * An actor which displays a triangle "arrow" pointing to a given
* side. The .bin property is a container in which content can be * side. The .bin property is a container in which content can be
* placed. The arrow position may be controlled via * placed. The arrow position may be controlled via setArrowOrigin().
* setArrowOrigin(). The arrow side might be temporarily flipped
* depending on the box size and source position to keep the box
* totally inside the monitor if possible.
* *
*/ */
const BoxPointer = new Lang.Class({ const BoxPointer = new Lang.Class({
@ -36,7 +26,6 @@ const BoxPointer = new Lang.Class({
_init: function(arrowSide, binProperties) { _init: function(arrowSide, binProperties) {
this._arrowSide = arrowSide; this._arrowSide = arrowSide;
this._userArrowSide = arrowSide;
this._arrowOrigin = 0; this._arrowOrigin = 0;
this.actor = new St.Bin({ x_fill: true, this.actor = new St.Bin({ x_fill: true,
y_fill: true }); y_fill: true });
@ -76,16 +65,11 @@ const BoxPointer = new Lang.Class({
show: function(animate, onComplete) { show: function(animate, onComplete) {
let themeNode = this.actor.get_theme_node(); let themeNode = this.actor.get_theme_node();
let rise = themeNode.get_length('-arrow-rise'); let rise = themeNode.get_length('-arrow-rise');
let animationTime = (animate & PopupAnimation.FULL) ? POPUP_ANIMATION_TIME : 0;
if (animate & PopupAnimation.FADE)
this.opacity = 0;
else
this.opacity = 255;
this.opacity = 0;
this.actor.show(); this.actor.show();
if (animate & PopupAnimation.SLIDE) { if (animate) {
switch (this._arrowSide) { switch (this._arrowSide) {
case St.Side.TOP: case St.Side.TOP:
this.yOffset = -rise; this.yOffset = -rise;
@ -111,7 +95,7 @@ const BoxPointer = new Lang.Class({
if (onComplete) if (onComplete)
onComplete(); onComplete();
}), }),
time: animationTime }); time: POPUP_ANIMATION_TIME });
}, },
hide: function(animate, onComplete) { hide: function(animate, onComplete) {
@ -119,10 +103,8 @@ const BoxPointer = new Lang.Class({
let yOffset = 0; let yOffset = 0;
let themeNode = this.actor.get_theme_node(); let themeNode = this.actor.get_theme_node();
let rise = themeNode.get_length('-arrow-rise'); let rise = themeNode.get_length('-arrow-rise');
let fade = (animate & PopupAnimation.FADE);
let animationTime = (animate & PopupAnimation.FULL) ? POPUP_ANIMATION_TIME : 0;
if (animate & PopupAnimation.SLIDE) { if (animate) {
switch (this._arrowSide) { switch (this._arrowSide) {
case St.Side.TOP: case St.Side.TOP:
yOffset = rise; yOffset = rise;
@ -141,14 +123,13 @@ const BoxPointer = new Lang.Class({
this._muteInput(); this._muteInput();
Tweener.addTween(this, { opacity: fade ? 0 : 255, Tweener.addTween(this, { opacity: 0,
xOffset: xOffset, xOffset: xOffset,
yOffset: yOffset, yOffset: yOffset,
transition: 'linear', transition: 'linear',
time: animationTime, time: POPUP_ANIMATION_TIME,
onComplete: Lang.bind(this, function () { onComplete: Lang.bind(this, function () {
this.actor.hide(); this.actor.hide();
this.opacity = 0;
this.xOffset = 0; this.xOffset = 0;
this.yOffset = 0; this.yOffset = 0;
if (onComplete) if (onComplete)
@ -218,27 +199,8 @@ const BoxPointer = new Lang.Class({
} }
this.bin.allocate(childBox, flags); this.bin.allocate(childBox, flags);
if (this._sourceActor && this._sourceActor.mapped) { if (this._sourceActor && this._sourceActor.mapped)
this._reposition(this._sourceActor, this._arrowAlignment); this._reposition(this._sourceActor, this._arrowAlignment);
if (this._shouldFlip()) {
switch (this._arrowSide) {
case St.Side.TOP:
this._arrowSide = St.Side.BOTTOM;
break;
case St.Side.BOTTOM:
this._arrowSide = St.Side.TOP;
break;
case St.Side.LEFT:
this._arrowSide = St.Side.RIGHT;
break;
case St.Side.RIGHT:
this._arrowSide = St.Side.LEFT;
break;
}
this._reposition(this._sourceActor, this._arrowAlignment);
}
}
}, },
_drawBorder: function(area) { _drawBorder: function(area) {
@ -252,6 +214,7 @@ const BoxPointer = new Lang.Class({
let halfBorder = borderWidth / 2; let halfBorder = borderWidth / 2;
let halfBase = Math.floor(base/2); let halfBase = Math.floor(base/2);
let borderColor = themeNode.get_color('-arrow-border-color');
let backgroundColor = themeNode.get_color('-arrow-background-color'); let backgroundColor = themeNode.get_color('-arrow-background-color');
let [width, height] = area.get_surface_size(); let [width, height] = area.get_surface_size();
@ -262,6 +225,7 @@ const BoxPointer = new Lang.Class({
boxWidth -= rise; boxWidth -= rise;
} }
let cr = area.get_context(); let cr = area.get_context();
Clutter.cairo_set_source_color(cr, borderColor);
// Translate so that box goes from 0,0 to boxWidth,boxHeight, // Translate so that box goes from 0,0 to boxWidth,boxHeight,
// with the arrow poking out of that // with the arrow poking out of that
@ -357,18 +321,12 @@ const BoxPointer = new Lang.Class({
Clutter.cairo_set_source_color(cr, backgroundColor); Clutter.cairo_set_source_color(cr, backgroundColor);
cr.fillPreserve(); cr.fillPreserve();
Clutter.cairo_set_source_color(cr, borderColor);
if (borderWidth > 0) { cr.setLineWidth(borderWidth);
let borderColor = themeNode.get_color('-arrow-border-color'); cr.stroke();
Clutter.cairo_set_source_color(cr, borderColor);
cr.setLineWidth(borderWidth);
cr.stroke();
}
}, },
setPosition: function(sourceActor, alignment) { setPosition: function(sourceActor, alignment) {
this._arrowSide = this._userArrowSide;
// We need to show it now to force an allocation, // We need to show it now to force an allocation,
// so that we can query the correct size. // so that we can query the correct size.
this.actor.show(); this.actor.show();
@ -385,7 +343,11 @@ const BoxPointer = new Lang.Class({
if (!this._sourceActor) if (!this._sourceActor)
return; return;
this.setPosition(this._sourceActor, this._arrowAlignment); // We need to show it now to force an allocation,
// so that we can query the correct size.
this.actor.show();
this._reposition(this._sourceActor, this._arrowAlignment);
}, },
_reposition: function(sourceActor, alignment) { _reposition: function(sourceActor, alignment) {
@ -410,7 +372,6 @@ const BoxPointer = new Lang.Class({
let themeNode = this.actor.get_theme_node(); let themeNode = this.actor.get_theme_node();
let gap = themeNode.get_length('-boxpointer-gap'); let gap = themeNode.get_length('-boxpointer-gap');
let padding = themeNode.get_length('-arrow-rise');
let resX, resY; let resX, resY;
@ -436,8 +397,8 @@ const BoxPointer = new Lang.Class({
case St.Side.BOTTOM: case St.Side.BOTTOM:
resX = sourceCenterX - (halfMargin + (natWidth - margin) * alignment); resX = sourceCenterX - (halfMargin + (natWidth - margin) * alignment);
resX = Math.max(resX, monitor.x + padding); resX = Math.max(resX, monitor.x + 10);
resX = Math.min(resX, monitor.x + monitor.width - (padding + natWidth)); resX = Math.min(resX, monitor.x + monitor.width - (10 + natWidth));
this.setArrowOrigin(sourceCenterX - resX); this.setArrowOrigin(sourceCenterX - resX);
break; break;
@ -445,8 +406,8 @@ const BoxPointer = new Lang.Class({
case St.Side.RIGHT: case St.Side.RIGHT:
resY = sourceCenterY - (halfMargin + (natHeight - margin) * alignment); resY = sourceCenterY - (halfMargin + (natHeight - margin) * alignment);
resY = Math.max(resY, monitor.y + padding); resY = Math.max(resY, monitor.y + 10);
resY = Math.min(resY, monitor.y + monitor.height - (padding + natHeight)); resY = Math.min(resY, monitor.y + monitor.height - (10 + natHeight));
this.setArrowOrigin(sourceCenterY - resY); this.setArrowOrigin(sourceCenterY - resY);
break; break;
@ -485,39 +446,6 @@ const BoxPointer = new Lang.Class({
-(this._yPosition + this._yOffset)); -(this._yPosition + this._yOffset));
}, },
_shouldFlip: function() {
let sourceAllocation = Shell.util_get_transformed_allocation(this._sourceActor);
let boxAllocation = Shell.util_get_transformed_allocation(this.actor);
let boxWidth = boxAllocation.x2 - boxAllocation.x1;
let boxHeight = boxAllocation.y2 - boxAllocation.y1;
let monitor = Main.layoutManager.findMonitorForActor(this.actor);
switch (this._arrowSide) {
case St.Side.TOP:
if (boxAllocation.y2 > monitor.y + monitor.height &&
boxHeight < sourceAllocation.y1 - monitor.y)
return true;
break;
case St.Side.BOTTOM:
if (boxAllocation.y1 < monitor.y &&
boxHeight < monitor.y + monitor.height - sourceAllocation.y2)
return true;
break;
case St.Side.LEFT:
if (boxAllocation.x2 > monitor.x + monitor.width &&
boxWidth < sourceAllocation.x1 - monitor.x)
return true;
break;
case St.Side.RIGHT:
if (boxAllocation.x1 < monitor.x &&
boxWidth < monitor.x + monitor.width - sourceAllocation.x2)
return true;
break;
}
return false;
},
set xOffset(offset) { set xOffset(offset) {
this._xOffset = offset; this._xOffset = offset;
this._shiftActor(); this._shiftActor();

View File

@ -204,11 +204,12 @@ const CalendarServerInfo = Gio.DBusInterfaceInfo.new_for_xml(CalendarServerIfac
function CalendarServer() { function CalendarServer() {
var self = new Gio.DBusProxy({ g_connection: Gio.DBus.session, var self = new Gio.DBusProxy({ g_connection: Gio.DBus.session,
g_interface_name: CalendarServerInfo.name, g_interface_name: CalendarServerInfo.name,
g_interface_info: CalendarServerInfo, g_interface_info: CalendarServerInfo,
g_name: 'org.gnome.Shell.CalendarServer', g_name: 'org.gnome.Shell.CalendarServer',
g_object_path: '/org/gnome/Shell/CalendarServer', g_object_path: '/org/gnome/Shell/CalendarServer',
g_flags: Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES }); g_flags: (Gio.DBusProxyFlags.DO_NOT_AUTO_START |
Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES) });
self.init(null); self.init(null);
return self; return self;
@ -447,7 +448,7 @@ const Calendar = new Lang.Class({
} }
// All the children after this are days, and get removed when we update the calendar // All the children after this are days, and get removed when we update the calendar
this._firstDayIndex = this.actor.get_n_children(); this._firstDayIndex = this.actor.get_children().length;
}, },
_onStyleChange: function(actor, event) { _onStyleChange: function(actor, event) {
@ -550,7 +551,6 @@ const Calendar = new Lang.Class({
let row = 2; let row = 2;
while (true) { while (true) {
let button = new St.Button({ label: iter.getDate().toString() }); let button = new St.Button({ label: iter.getDate().toString() });
let rtl = button.get_text_direction() == Clutter.TextDirection.RTL;
if (!this._eventSource) if (!this._eventSource)
button.reactive = false; button.reactive = false;
@ -571,10 +571,7 @@ const Calendar = new Lang.Class({
// Hack used in lieu of border-collapse - see gnome-shell.css // Hack used in lieu of border-collapse - see gnome-shell.css
if (row == 2) if (row == 2)
styleClass = 'calendar-day-top ' + styleClass; styleClass = 'calendar-day-top ' + styleClass;
if (iter.getDay() == this._weekStart)
let leftMost = rtl ? iter.getDay() == (this._weekStart + 6) % 7
: iter.getDay() == this._weekStart;
if (leftMost)
styleClass = 'calendar-day-left ' + styleClass; styleClass = 'calendar-day-left ' + styleClass;
if (_sameDay(now, iter)) if (_sameDay(now, iter))
@ -716,15 +713,13 @@ const EventsList = new Lang.Class({
let tomorrowEnd = new Date(dayEnd.getTime() + 86400 * 1000); let tomorrowEnd = new Date(dayEnd.getTime() + 86400 * 1000);
this._addPeriod(_("Tomorrow"), tomorrowBegin, tomorrowEnd, false, true); this._addPeriod(_("Tomorrow"), tomorrowBegin, tomorrowEnd, false, true);
let dayInWeek = (dayEnd.getDay() - this._weekStart + 7) % 7; if (dayEnd.getDay() <= 4 + this._weekStart) {
if (dayInWeek < 5) {
/* If now is within the first 5 days we show "This week" and /* If now is within the first 5 days we show "This week" and
* include events up until and including Saturday/Sunday * include events up until and including Saturday/Sunday
* (depending on whether a week starts on Sunday/Monday). * (depending on whether a week starts on Sunday/Monday).
*/ */
let thisWeekBegin = new Date(dayBegin.getTime() + 2 * 86400 * 1000); let thisWeekBegin = new Date(dayBegin.getTime() + 2 * 86400 * 1000);
let thisWeekEnd = new Date(dayEnd.getTime() + (6 - dayInWeek) * 86400 * 1000); let thisWeekEnd = new Date(dayEnd.getTime() + (6 + this._weekStart - dayEnd.getDay()) * 86400 * 1000);
this._addPeriod(_("This week"), thisWeekBegin, thisWeekEnd, true, false); this._addPeriod(_("This week"), thisWeekBegin, thisWeekEnd, true, false);
} else { } else {
/* otherwise it's one of the two last days of the week ... show /* otherwise it's one of the two last days of the week ... show
@ -732,7 +727,7 @@ const EventsList = new Lang.Class({
* Saturday/Sunday * Saturday/Sunday
*/ */
let nextWeekBegin = new Date(dayBegin.getTime() + 2 * 86400 * 1000); let nextWeekBegin = new Date(dayBegin.getTime() + 2 * 86400 * 1000);
let nextWeekEnd = new Date(dayEnd.getTime() + (13 - dayInWeek) * 86400 * 1000); let nextWeekEnd = new Date(dayEnd.getTime() + (13 + this._weekStart - dayEnd.getDay()) * 86400 * 1000);
this._addPeriod(_("Next week"), nextWeekBegin, nextWeekEnd, true, false); this._addPeriod(_("Next week"), nextWeekBegin, nextWeekEnd, true, false);
} }
}, },

196
js/ui/contactDisplay.js Normal file
View File

@ -0,0 +1,196 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Folks = imports.gi.Folks
const Lang = imports.lang;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Atk = imports.gi.Atk;
const Util = imports.misc.util;
const IconGrid = imports.ui.iconGrid;
const Search = imports.ui.search;
const SearchDisplay = imports.ui.searchDisplay;
const MAX_SEARCH_RESULTS_ROWS = 1;
const ICON_SIZE = 81;
function launchContact(id) {
Util.spawn(['gnome-contacts', '-i', id]);
}
/* This class represents a shown contact search result in the overview */
const Contact = new Lang.Class({
Name: 'Contact',
_init: function(id) {
this._contactSys = Shell.ContactSystem.get_default();
this.individual = this._contactSys.get_individual(id);
this.actor = new St.Bin({ style_class: 'contact',
reactive: true,
can_focus: true,
track_hover: true,
accessible_role: Atk.Role.PUSH_BUTTON });
let content = new St.BoxLayout( { style_class: 'contact-content',
vertical: false });
this.actor.set_child(content);
let icon = new St.Icon({ icon_type: St.IconType.FULLCOLOR,
icon_size: ICON_SIZE,
style_class: 'contact-icon' });
if (this.individual.avatar != null)
icon.gicon = this.individual.avatar;
else
icon.icon_name = 'avatar-default';
content.add(icon, { x_fill: true,
y_fill: false,
x_align: St.Align.START,
y_align: St.Align.MIDDLE });
let details = new St.BoxLayout({ style_class: 'contact-details',
vertical: true });
content.add(details, { x_fill: true,
y_fill: false,
x_align: St.Align.START,
y_align: St.Align.MIDDLE });
let email = this._contactSys.get_email_for_display(this.individual);
let aliasText = this.individual.alias ||
this.individual.full_name ||
this.individual.nickname ||
email ||
_("Unknown");
let aliasLabel = new St.Label({ text: aliasText,
style_class: 'contact-details-alias' });
details.add(aliasLabel, { x_fill: true,
y_fill: false,
x_align: St.Align.START,
y_align: St.Align.START });
this.actor.label_actor = aliasLabel;
let presence = this._createPresence(this.individual.presence_type);
details.add(presence, { x_fill: false,
y_fill: true,
x_align: St.Align.START,
y_align: St.Align.END });
},
_createPresence: function(presence) {
let text;
let iconName;
switch(presence) {
case Folks.PresenceType.AVAILABLE:
text = _("Available");
iconName = 'user-available';
break;
case Folks.PresenceType.AWAY:
case Folks.PresenceType.EXTENDED_AWAY:
text = _("Away");
iconName = 'user-away';
break;
case Folks.PresenceType.BUSY:
text = _("Busy");
iconName = 'user-busy';
break;
case Folks.PresenceType.OFFLINE:
text = _("Offline");
iconName = 'user-offline';
break;
default:
text = '';
iconName = null;
}
let box = new St.BoxLayout({ vertical: false,
style_class: 'contact-details-status' });
if (iconName) {
let icon = new St.Icon({ icon_name: iconName,
icon_type: St.IconType.FULLCOLOR,
icon_size: 16,
style_class: 'contact-details-status-icon' });
box.add(icon, { x_fill: true,
y_fill: false,
x_align: St.Align.START,
y_align: St.Align.START });
}
let label = new St.Label({ text: text });
box.add(label, { x_fill: true,
y_fill: false,
x_align: St.Align.END,
y_align: St.Align.START });
return box;
},
createIcon: function(size) {
let tc = St.TextureCache.get_default();
let icon = this.individual.avatar;
if (icon != null) {
return tc.load_gicon(null, icon, size);
} else {
return tc.load_icon_name(null, 'avatar-default', St.IconType.FULLCOLOR, size);
}
},
});
/* Searches for and returns contacts */
const ContactSearchProvider = new Lang.Class({
Name: 'ContactSearchProvider',
Extends: Search.SearchProvider,
_init: function() {
this.parent(_("CONTACTS"));
this._contactSys = Shell.ContactSystem.get_default();
},
getResultMetas: function(ids) {
let metas = [];
for (let i = 0; i < ids.length; i++) {
let contact = new Contact(ids[i]);
metas.push({ 'id': ids[i],
'name': contact.alias,
'createIcon': function(size) {
return contact.createIcon(size);
}
});
}
return metas;
},
getInitialResultSet: function(terms) {
return this._contactSys.initial_search(terms);
},
getSubsearchResultSet: function(previousResults, terms) {
return this._contactSys.subsearch(previousResults, terms);
},
createResultActor: function(resultMeta, terms) {
let contact = new Contact(resultMeta.id);
return contact.actor;
},
createResultContainerActor: function() {
let grid = new IconGrid.IconGrid({ rowLimit: MAX_SEARCH_RESULTS_ROWS,
xAlign: St.Align.START });
grid.actor.style_class = 'contact-grid';
let actor = new SearchDisplay.GridSearchResults(this, grid);
return actor;
},
activateResult: function(id, params) {
launchContact(id);
}
});

View File

@ -27,6 +27,7 @@ const CtrlAltTabManager = new Lang.Class({
_init: function() { _init: function() {
this._items = []; this._items = [];
this._focusManager = St.FocusManager.get_for_stage(global.stage);
}, },
addGroup: function(root, name, icon, params) { addGroup: function(root, name, icon, params) {
@ -40,11 +41,11 @@ const CtrlAltTabManager = new Lang.Class({
this._items.push(item); this._items.push(item);
root.connect('destroy', Lang.bind(this, function() { this.removeGroup(root); })); root.connect('destroy', Lang.bind(this, function() { this.removeGroup(root); }));
global.focus_manager.add_group(root); this._focusManager.add_group(root);
}, },
removeGroup: function(root) { removeGroup: function(root) {
global.focus_manager.remove_group(root); this._focusManager.remove_group(root);
for (let i = 0; i < this._items.length; i++) { for (let i = 0; i < this._items.length; i++) {
if (this._items[i].root == root) { if (this._items[i].root == root) {
this._items.splice(i, 1); this._items.splice(i, 1);
@ -318,6 +319,7 @@ const CtrlAltTabSwitcher = new Lang.Class({
let icon = item.iconActor; let icon = item.iconActor;
if (!icon) { if (!icon) {
icon = new St.Icon({ icon_name: item.iconName, icon = new St.Icon({ icon_name: item.iconName,
icon_type: St.IconType.SYMBOLIC,
icon_size: POPUP_APPICON_SIZE }); icon_size: POPUP_APPICON_SIZE });
} }
box.add(icon, { x_fill: false, y_fill: false } ); box.add(icon, { x_fill: false, y_fill: false } );

View File

@ -124,19 +124,16 @@ const DashItemContainer = new Lang.Class({
}, },
setLabelText: function(text) { setLabelText: function(text) {
if (this.label == null) { if (this.label == null)
this.label = new St.Label({ style_class: 'dash-label'}); this.label = new St.Label({ style_class: 'dash-label'});
Main.layoutManager.addChrome(this.label);
this.label.hide();
}
this.label.set_text(text); this.label.set_text(text);
Main.layoutManager.addChrome(this.label);
this.label.hide();
}, },
hideLabel: function () { hideLabel: function () {
if (this.label == null) this.label.opacity = 255;
return;
Tweener.addTween(this.label, Tweener.addTween(this.label,
{ opacity: 0, { opacity: 0,
time: DASH_ITEM_LABEL_HIDE_TIME, time: DASH_ITEM_LABEL_HIDE_TIME,
@ -230,47 +227,36 @@ const DashItemContainer = new Lang.Class({
} }
}); });
const ShowAppsIcon = new Lang.Class({ const RemoveFavoriteIcon = new Lang.Class({
Name: 'ShowAppsIcon', Name: 'RemoveFavoriteIcon',
Extends: DashItemContainer, Extends: DashItemContainer,
_init: function() { _init: function() {
this.parent(); this.parent();
this.toggleButton = new St.Button({ style_class: 'show-apps', this._iconBin = new St.Bin({ style_class: 'remove-favorite' });
track_hover: true,
can_focus: true,
toggle_mode: true });
this._iconActor = null; this._iconActor = null;
this.icon = new IconGrid.BaseIcon(_("Show Applications"), this.icon = new IconGrid.BaseIcon(_("Remove"),
{ setSizeManually: true, { setSizeManually: true,
showLabel: false, showLabel: false,
createIcon: Lang.bind(this, this._createIcon) }); createIcon: Lang.bind(this, this._createIcon) });
this.toggleButton.add_actor(this.icon.actor); this._iconBin.set_child(this.icon.actor);
this.toggleButton._delegate = this; this._iconBin._delegate = this;
this.setChild(this.toggleButton); this.setChild(this._iconBin);
this.setHover(false);
this.toggleButton.label_actor = this.label;
}, },
_createIcon: function(size) { _createIcon: function(size) {
this._iconActor = new St.Icon({ icon_name: 'view-grid-symbolic', this._iconActor = new St.Icon({ icon_name: 'user-trash',
icon_size: size, style_class: 'remove-favorite-icon',
style_class: 'show-apps-icon', icon_size: size });
track_hover: true });
return this._iconActor; return this._iconActor;
}, },
setHover: function(hovered) { setHover: function(hovered) {
this.toggleButton.set_hover(hovered); this._iconBin.set_hover(hovered);
if (this._iconActor) if (this._iconActor)
this._iconActor.set_hover(hovered); this._iconActor.set_hover(hovered);
if (hovered)
this.setLabelText(_("Remove from Favorites"));
else
this.setLabelText(_("Show Applications"));
}, },
// Rely on the dragged item being a favorite // Rely on the dragged item being a favorite
@ -321,28 +307,17 @@ const Dash = new Lang.Class({
this._dragPlaceholder = null; this._dragPlaceholder = null;
this._dragPlaceholderPos = -1; this._dragPlaceholderPos = -1;
this._animatingPlaceholdersCount = 0; this._animatingPlaceholdersCount = 0;
this._favRemoveTarget = null;
this._showLabelTimeoutId = 0; this._showLabelTimeoutId = 0;
this._resetHoverTimeoutId = 0; this._resetHoverTimeoutId = 0;
this._labelShowing = false; this._labelShowing = false;
this._container = new St.BoxLayout({ name: 'dash', this._box = new St.BoxLayout({ name: 'dash',
vertical: true, vertical: true,
clip_to_allocation: true });
this._box = new St.BoxLayout({ vertical: true,
clip_to_allocation: true }); clip_to_allocation: true });
this._box._delegate = this; this._box._delegate = this;
this._container.add(this._box);
this._showAppsIcon = new ShowAppsIcon(); this.actor = new St.Bin({ y_align: St.Align.START, child: this._box });
this._showAppsIcon.icon.setIconSize(this.iconSize);
this._hookUpLabel(this._showAppsIcon);
this.showAppsButton = this._showAppsIcon.toggleButton;
this._container.add(this._showAppsIcon.actor);
this.actor = new St.Bin({ child: this._container });
this.actor.connect('notify::height', Lang.bind(this, this.actor.connect('notify::height', Lang.bind(this,
function() { function() {
if (this._maxHeight != this.actor.height) if (this._maxHeight != this.actor.height)
@ -395,6 +370,14 @@ const Dash = new Lang.Class({
_endDrag: function() { _endDrag: function() {
this._clearDragPlaceholder(); this._clearDragPlaceholder();
if (this._favRemoveTarget) {
this._favRemoveTarget.animateOutAndDestroy();
this._favRemoveTarget.actor.connect('destroy', Lang.bind(this,
function() {
this._favRemoveTarget = null;
}));
this._adjustIconSize();
}
DND.removeDragMonitor(this._dragMonitor); DND.removeDragMonitor(this._dragMonitor);
}, },
@ -413,13 +396,28 @@ const Dash = new Lang.Class({
let srcIsFavorite = (id in favorites); let srcIsFavorite = (id in favorites);
let showAppsHovered = if (srcIsFavorite &&
this._showAppsIcon.actor.contains(dragEvent.targetActor); app.get_state() != Shell.AppState.RUNNING &&
dragEvent.source.actor &&
this.actor.contains (dragEvent.source.actor) &&
this._favRemoveTarget == null) {
this._favRemoveTarget = new RemoveFavoriteIcon();
this._favRemoveTarget.icon.setIconSize(this.iconSize);
this._box.add(this._favRemoveTarget.actor);
this._adjustIconSize();
this._favRemoveTarget.animateIn();
}
if (!this._box.contains(dragEvent.targetActor) || showAppsHovered) let favRemoveHovered = false;
if (this._favRemoveTarget)
favRemoveHovered =
this._favRemoveTarget.actor.contains(dragEvent.targetActor);
if (!this._box.contains(dragEvent.targetActor) || favRemoveHovered)
this._clearDragPlaceholder(); this._clearDragPlaceholder();
this._showAppsIcon.setHover(showAppsHovered); if (this._favRemoveTarget)
this._favRemoveTarget.setHover(favRemoveHovered);
return DND.DragMotionResult.CONTINUE; return DND.DragMotionResult.CONTINUE;
}, },
@ -435,17 +433,6 @@ const Dash = new Lang.Class({
Main.queueDeferredWork(this._workId); Main.queueDeferredWork(this._workId);
}, },
_hookUpLabel: function(item) {
item.child.connect('notify::hover', Lang.bind(this, function() {
this._onHover(item);
}));
Main.overview.connect('hiding', Lang.bind(this, function() {
this._labelShowing = false;
item.hideLabel();
}));
},
_createAppItem: function(app) { _createAppItem: function(app) {
let display = new AppDisplay.AppWellIcon(app, let display = new AppDisplay.AppWellIcon(app,
{ setSizeManually: true, { setSizeManually: true,
@ -465,14 +452,18 @@ const Dash = new Lang.Class({
item.setLabelText(app.get_name()); item.setLabelText(app.get_name());
// Override default AppWellIcon label_actor // Override default AppWellIcon label_actor
display.actor.label_actor = item.label; display.actor.label_actor = item.label;
display.icon.setIconSize(this.iconSize);
this._hookUpLabel(item);
display.icon.setIconSize(this.iconSize);
display.actor.connect('notify::hover',
Lang.bind(this, function() {
this._onHover(item, display)
}));
return item; return item;
}, },
_onHover: function (item) { _onHover: function (item, display) {
if (item.child.get_hover() && !item.child._delegate.isMenuUp) { if (display.actor.get_hover() && !display.isMenuUp) {
if (this._showLabelTimeoutId == 0) { if (this._showLabelTimeoutId == 0) {
let timeout = this._labelShowing ? 0 : DASH_ITEM_HOVER_TIMEOUT; let timeout = this._labelShowing ? 0 : DASH_ITEM_HOVER_TIMEOUT;
this._showLabelTimeoutId = Mainloop.timeout_add(timeout, this._showLabelTimeoutId = Mainloop.timeout_add(timeout,
@ -483,7 +474,7 @@ const Dash = new Lang.Class({
})); }));
if (this._resetHoverTimeoutId > 0) { if (this._resetHoverTimeoutId > 0) {
Mainloop.source_remove(this._resetHoverTimeoutId); Mainloop.source_remove(this._resetHoverTimeoutId);
this._resetHoverTimeoutId = 0; this._resetHoverTimeoutId = 0;
} }
} }
} else { } else {
@ -513,12 +504,18 @@ const Dash = new Lang.Class({
!actor._delegate.animatingOut; !actor._delegate.animatingOut;
}); });
iconChildren.push(this._showAppsIcon.actor); if (iconChildren.length == 0) {
this._box.add_style_pseudo_class('empty');
return;
}
this._box.remove_style_pseudo_class('empty');
if (this._maxHeight == -1) if (this._maxHeight == -1)
return; return;
let themeNode = this._container.get_theme_node();
let themeNode = this._box.get_theme_node();
let maxAllocation = new Clutter.ActorBox({ x1: 0, y1: 0, let maxAllocation = new Clutter.ActorBox({ x1: 0, y1: 0,
x2: 42 /* whatever */, x2: 42 /* whatever */,
y2: this._maxHeight }); y2: this._maxHeight });
@ -544,6 +541,7 @@ const Dash = new Lang.Class({
[minHeight, natHeight] = iconChildren[0].get_preferred_height(-1); [minHeight, natHeight] = iconChildren[0].get_preferred_height(-1);
} }
// Subtract icon padding and box spacing from the available height // Subtract icon padding and box spacing from the available height
availHeight -= iconChildren.length * (natHeight - this.iconSize) + availHeight -= iconChildren.length * (natHeight - this.iconSize) +
(iconChildren.length - 1) * spacing; (iconChildren.length - 1) * spacing;

View File

@ -2,7 +2,6 @@
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const GnomeDesktop = imports.gi.GnomeDesktop;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
const Cairo = imports.cairo; const Cairo = imports.cairo;
@ -17,6 +16,14 @@ const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const Calendar = imports.ui.calendar; const Calendar = imports.ui.calendar;
const UPowerGlib = imports.gi.UPowerGlib;
// in org.gnome.desktop.interface
const CLOCK_FORMAT_KEY = 'clock-format';
// in org.gnome.shell.clock
const CLOCK_SHOW_DATE_KEY = 'show-date';
const CLOCK_SHOW_SECONDS_KEY = 'show-seconds';
function _onVertSepRepaint (area) function _onVertSepRepaint (area)
{ {
@ -38,7 +45,9 @@ const DateMenuButton = new Lang.Class({
Name: 'DateMenuButton', Name: 'DateMenuButton',
Extends: PanelMenu.Button, Extends: PanelMenu.Button,
_init: function() { _init: function(params) {
params = Params.parse(params, { showEvents: true });
let item; let item;
let hbox; let hbox;
let vbox; let vbox;
@ -53,8 +62,8 @@ const DateMenuButton = new Lang.Class({
// role ATK_ROLE_MENU like other elements of the panel. // role ATK_ROLE_MENU like other elements of the panel.
this.actor.accessible_role = Atk.Role.LABEL; this.actor.accessible_role = Atk.Role.LABEL;
this._clockDisplay = new St.Label(); this._clock = new St.Label();
this.actor.add_actor(this._clockDisplay); this.actor.add_actor(this._clock);
hbox = new St.BoxLayout({name: 'calendarArea' }); hbox = new St.BoxLayout({name: 'calendarArea' });
this.menu.addActor(hbox); this.menu.addActor(hbox);
@ -66,11 +75,11 @@ const DateMenuButton = new Lang.Class({
// Date // Date
this._date = new St.Label(); this._date = new St.Label();
this.actor.label_actor = this._clockDisplay; this.actor.label_actor = this._date;
this._date.style_class = 'datemenu-date-label'; this._date.style_class = 'datemenu-date-label';
vbox.add(this._date); vbox.add(this._date);
if (Main.sessionMode.showCalendarEvents) { if (params.showEvents) {
this._eventSource = new Calendar.DBusEventSource(); this._eventSource = new Calendar.DBusEventSource();
this._eventList = new Calendar.EventsList(this._eventSource); this._eventList = new Calendar.EventsList(this._eventSource);
} else { } else {
@ -101,7 +110,7 @@ const DateMenuButton = new Lang.Class({
item.actor.reparent(vbox); item.actor.reparent(vbox);
} }
if (Main.sessionMode.showCalendarEvents) { if (params.showEvents) {
// Add vertical separator // Add vertical separator
item = new St.DrawingArea({ style_class: 'calendar-vertical-separator', item = new St.DrawingArea({ style_class: 'calendar-vertical-separator',
@ -148,29 +157,77 @@ const DateMenuButton = new Lang.Class({
// Done with hbox for calendar and event list // Done with hbox for calendar and event list
this._clock = new GnomeDesktop.WallClock(); // Track changes to clock settings
this._clock.connect('notify::clock', Lang.bind(this, this._updateClockAndDate)); this._desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' });
this._clockSettings = new Gio.Settings({ schema: 'org.gnome.shell.clock' });
this._desktopSettings.connect('changed', Lang.bind(this, this._updateClockAndDate));
this._clockSettings.connect('changed', Lang.bind(this, this._updateClockAndDate));
// https://bugzilla.gnome.org/show_bug.cgi?id=655129
this._upClient = new UPowerGlib.Client();
this._upClient.connect('notify-resume', Lang.bind(this, this._updateClockAndDate));
// Start the clock
this._updateClockAndDate(); this._updateClockAndDate();
}, },
_updateClockAndDate: function() { _updateClockAndDate: function() {
this._clockDisplay.set_text(this._clock.clock); let format = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);
let showDate = this._clockSettings.get_boolean(CLOCK_SHOW_DATE_KEY);
let showSeconds = this._clockSettings.get_boolean(CLOCK_SHOW_SECONDS_KEY);
let clockFormat;
let dateFormat;
switch (format) {
case '24h':
if (showDate)
/* Translators: This is the time format with date used
in 24-hour mode. */
clockFormat = showSeconds ? _("%a %b %e, %R:%S")
: _("%a %b %e, %R");
else
/* Translators: This is the time format without date used
in 24-hour mode. */
clockFormat = showSeconds ? _("%a %R:%S")
: _("%a %R");
break;
case '12h':
default:
if (showDate)
/* Translators: This is a time format with date used
for AM/PM. */
clockFormat = showSeconds ? _("%a %b %e, %l:%M:%S %p")
: _("%a %b %e, %l:%M %p");
else
/* Translators: This is a time format without date used
for AM/PM. */
clockFormat = showSeconds ? _("%a %l:%M:%S %p")
: _("%a %l:%M %p");
break;
}
let displayDate = new Date();
this._clock.set_text(displayDate.toLocaleFormat(clockFormat));
/* Translators: This is the date format to use when the calendar popup is /* Translators: This is the date format to use when the calendar popup is
* shown - it is shown just below the time in the shell (e.g. "Tue 9:29 AM"). * shown - it is shown just below the time in the shell (e.g. "Tue 9:29 AM").
*/ */
let dateFormat = _("%A %B %e, %Y"); dateFormat = _("%A %B %e, %Y");
let displayDate = new Date();
this._date.set_text(displayDate.toLocaleFormat(dateFormat)); this._date.set_text(displayDate.toLocaleFormat(dateFormat));
Mainloop.timeout_add_seconds(1, Lang.bind(this, this._updateClockAndDate));
return false;
}, },
_onOpenCalendarActivate: function() { _onOpenCalendarActivate: function() {
this.menu.close(); this.menu.close();
let calendarSettings = new Gio.Settings({ schema: 'org.gnome.desktop.default-applications.office.calendar' }); let calendarSettings = new Gio.Settings({ schema: 'org.gnome.desktop.default-applications.office.calendar' });
let tool = calendarSettings.get_string('exec'); let tool = calendarSettings.get_string('exec');
if (tool.length == 0 || tool.substr(0, 9) == 'evolution') { if (tool.length == 0 || tool == 'evolution') {
// TODO: pass the selected day // TODO: pass the selected day
let app = Shell.AppSystem.get_default().lookup_app('evolution-calendar.desktop'); Util.spawn(['evolution', '-c', 'calendar']);
app.activate();
} else { } else {
let needTerm = calendarSettings.get_boolean('needs-term'); let needTerm = calendarSettings.get_boolean('needs-term');
if (needTerm) { if (needTerm) {

View File

@ -31,10 +31,10 @@ const St = imports.gi.St;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const GnomeSession = imports.misc.gnomeSession; const GnomeSession = imports.misc.gnomeSession;
const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main; const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog; const ModalDialog = imports.ui.modalDialog;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const UserMenu = imports.ui.userMenu;
let _endSessionDialog = null; let _endSessionDialog = null;
@ -90,7 +90,7 @@ const shutdownDialogContent = {
label: C_("button", "Restart") }, label: C_("button", "Restart") },
{ signal: 'ConfirmedShutdown', { signal: 'ConfirmedShutdown',
label: C_("button", "Power Off") }], label: C_("button", "Power Off") }],
iconName: 'system-shutdown-symbolic', iconName: 'system-shutdown',
iconStyleClass: 'end-session-dialog-shutdown-icon' iconStyleClass: 'end-session-dialog-shutdown-icon'
}; };
@ -105,7 +105,7 @@ const restartDialogContent = {
endDescription: _("Restarting the system."), endDescription: _("Restarting the system."),
confirmButtons: [{ signal: 'ConfirmedReboot', confirmButtons: [{ signal: 'ConfirmedReboot',
label: C_("button", "Restart") }], label: C_("button", "Restart") }],
iconName: 'system-shutdown-symbolic', iconName: 'system-shutdown',
iconStyleClass: 'end-session-dialog-shutdown-icon' iconStyleClass: 'end-session-dialog-shutdown-icon'
}; };
@ -161,7 +161,6 @@ const ListItem = new Lang.Class({
this._descriptionLabel = new St.Label({ text: this._reason, this._descriptionLabel = new St.Label({ text: this._reason,
style_class: 'end-session-dialog-app-list-item-description' }); style_class: 'end-session-dialog-app-list-item-description' });
this.actor.label_actor = this._nameLabel;
textLayout.add(this._descriptionLabel, textLayout.add(this._descriptionLabel,
{ expand: true, { expand: true,
x_fill: true }); x_fill: true });
@ -281,17 +280,21 @@ const EndSessionDialog = new Lang.Class({
scrollView.hide(); scrollView.hide();
this._applicationList = new St.BoxLayout({ vertical: true }); this._applicationList = new St.BoxLayout({ vertical: true });
scrollView.add_actor(this._applicationList); scrollView.add_actor(this._applicationList,
{ x_fill: true,
y_fill: true,
x_align: St.Align.START,
y_align: St.Align.MIDDLE });
this._applicationList.connect('actor-added', this._applicationList.connect('actor-added',
Lang.bind(this, function() { Lang.bind(this, function() {
if (this._applicationList.get_n_children() == 1) if (this._applicationList.get_children().length == 1)
scrollView.show(); scrollView.show();
})); }));
this._applicationList.connect('actor-removed', this._applicationList.connect('actor-removed',
Lang.bind(this, function() { Lang.bind(this, function() {
if (this._applicationList.get_n_children() == 0) if (this._applicationList.get_children().length == 0)
scrollView.hide(); scrollView.hide();
})); }));
@ -304,7 +307,42 @@ const EndSessionDialog = new Lang.Class({
this._user.disconnect(this._userChangedId); this._user.disconnect(this._userChangedId);
}, },
_updateDescription: function() { _setIconFromFile: function(iconFile, styleClass) {
if (styleClass)
this._iconBin.set_style_class_name(styleClass);
this._iconBin.set_style(null);
this._iconBin.child = null;
if (iconFile) {
this._iconBin.show();
this._iconBin.set_style('background-image: url("' + iconFile + '");' +
'background-size: contain;');
} else {
this._iconBin.hide();
}
},
_setIconFromName: function(iconName, styleClass) {
if (styleClass)
this._iconBin.set_style_class_name(styleClass);
this._iconBin.set_style(null);
if (iconName != null) {
let textureCache = St.TextureCache.get_default();
let icon = textureCache.load_icon_name(this._iconBin.get_theme_node(),
iconName,
St.IconType.SYMBOLIC,
_DIALOG_ICON_SIZE);
this._iconBin.child = icon;
this._iconBin.show();
} else {
this._iconBin.child = null;
this._iconBin.hide();
}
},
_updateContent: function() {
if (this.state != ModalDialog.State.OPENING && if (this.state != ModalDialog.State.OPENING &&
this.state != ModalDialog.State.OPENED) this.state != ModalDialog.State.OPENED)
return; return;
@ -314,6 +352,17 @@ const EndSessionDialog = new Lang.Class({
let subject = dialogContent.subject; let subject = dialogContent.subject;
let description; let description;
if (this._user.is_loaded && !dialogContent.iconName) {
let iconFile = this._user.get_icon_file();
if (GLib.file_test(iconFile, GLib.FileTest.EXISTS))
this._setIconFromFile(iconFile, dialogContent.iconStyleClass);
else
this._setIconFromName('avatar-default', dialogContent.iconStyleClass);
} else if (dialogContent.iconName) {
this._setIconFromName(dialogContent.iconName,
dialogContent.iconStyleClass);
}
if (this._inhibitors.length > 0) { if (this._inhibitors.length > 0) {
this._stopTimer(); this._stopTimer();
description = dialogContent.inhibitedDescription; description = dialogContent.inhibitedDescription;
@ -346,27 +395,6 @@ const EndSessionDialog = new Lang.Class({
_setLabelText(this._descriptionLabel, description); _setLabelText(this._descriptionLabel, description);
}, },
_updateContent: function() {
if (this.state != ModalDialog.State.OPENING &&
this.state != ModalDialog.State.OPENED)
return;
let dialogContent = DialogContent[this._type];
if (dialogContent.iconName) {
this._iconBin.child = new St.Icon({ icon_name: dialogContent.iconName,
icon_size: _DIALOG_ICON_SIZE,
style_class: dialogContent.iconStyleClass });
} else {
let avatarWidget = new UserMenu.UserAvatarWidget(this._user,
{ iconSize: _DIALOG_ICON_SIZE,
styleClass: dialogContent.iconStyleClass });
this._iconBin.child = avatarWidget.actor;
avatarWidget.update();
}
this._updateDescription();
},
_updateButtons: function() { _updateButtons: function() {
let dialogContent = DialogContent[this._type]; let dialogContent = DialogContent[this._type];
let buttons = [{ action: Lang.bind(this, this.cancel), let buttons = [{ action: Lang.bind(this, this.cancel),
@ -413,7 +441,7 @@ const EndSessionDialog = new Lang.Class({
{ _secondsLeft: 0, { _secondsLeft: 0,
time: this._secondsLeft, time: this._secondsLeft,
transition: 'linear', transition: 'linear',
onUpdate: Lang.bind(this, this._updateDescription), onUpdate: Lang.bind(this, this._updateContent),
onComplete: Lang.bind(this, function() { onComplete: Lang.bind(this, function() {
let dialogContent = DialogContent[this._type]; let dialogContent = DialogContent[this._type];
let button = dialogContent.confirmButtons[dialogContent.confirmButtons.length - 1]; let button = dialogContent.confirmButtons[dialogContent.confirmButtons.length - 1];

View File

@ -90,7 +90,7 @@ function init() {
} }
// OK, now things are initialized enough that we can import shell JS // OK, now things are initialized enough that we can import shell JS
const Format = imports.format; const Format = imports.misc.format;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
Tweener.init(); Tweener.init();

View File

@ -1,270 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Lang = imports.lang;
const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Soup = imports.gi.Soup;
const St = imports.gi.St;
const Shell = imports.gi.Shell;
const Config = imports.misc.config;
const ExtensionUtils = imports.misc.extensionUtils;
const ExtensionSystem = imports.ui.extensionSystem;
const FileUtils = imports.misc.fileUtils;
const ModalDialog = imports.ui.modalDialog;
const _signals = ExtensionSystem._signals;
const REPOSITORY_URL_BASE = 'https://extensions.gnome.org';
const REPOSITORY_URL_DOWNLOAD = REPOSITORY_URL_BASE + '/download-extension/%s.shell-extension.zip';
const REPOSITORY_URL_INFO = REPOSITORY_URL_BASE + '/extension-info/';
const REPOSITORY_URL_UPDATE = REPOSITORY_URL_BASE + '/update-info/';
let _httpSession;
function installExtension(uuid, invocation) {
let params = { uuid: uuid,
shell_version: Config.PACKAGE_VERSION };
let message = Soup.form_request_new_from_hash('GET', REPOSITORY_URL_INFO, params);
_httpSession.queue_message(message, function(session, message) {
if (message.status_code != Soup.KnownStatusCode.OK) {
ExtensionSystem.logExtensionError(uuid, 'downloading info: ' + message.status_code);
invocation.return_dbus_error('org.gnome.Shell.DownloadInfoError', message.status_code.toString());
return;
}
let info;
try {
info = JSON.parse(message.response_body.data);
} catch (e) {
ExtensionSystem.logExtensionError(uuid, 'parsing info: ' + e);
invocation.return_dbus_error('org.gnome.Shell.ParseInfoError', e.toString());
return;
}
let dialog = new InstallExtensionDialog(uuid, info, invocation);
dialog.open(global.get_current_time());
});
}
function uninstallExtension(uuid) {
let extension = ExtensionUtils.extensions[uuid];
if (!extension)
return false;
// Don't try to uninstall system extensions
if (extension.type != ExtensionUtils.ExtensionType.PER_USER)
return false;
if (!ExtensionSystem.unloadExtension(extension))
return false;
FileUtils.recursivelyDeleteDir(extension.dir, true);
return true;
}
function gotExtensionZipFile(session, message, uuid, dir, callback, errback) {
if (message.status_code != Soup.KnownStatusCode.OK) {
errback('DownloadExtensionError', message.status_code);
return;
}
try {
if (!dir.query_exists(null))
dir.make_directory_with_parents(null);
} catch (e) {
errback('CreateExtensionDirectoryError', e);
return;
}
let [file, stream] = Gio.File.new_tmp('XXXXXX.shell-extension.zip');
let contents = message.response_body.flatten().get_as_bytes();
stream.output_stream.write_bytes(contents, null);
stream.close(null);
let [success, pid] = GLib.spawn_async(null,
['unzip', '-uod', dir.get_path(), '--', file.get_path()],
null,
GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD,
null);
if (!success) {
errback('ExtractExtensionError');
return;
}
GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, function(pid, status) {
GLib.spawn_close_pid(pid);
if (status != 0)
errback('ExtractExtensionError');
else
callback();
});
}
function updateExtension(uuid) {
// This gets a bit tricky. We want the update to be seamless -
// if we have any error during downloading or extracting, we
// want to not unload the current version.
let oldExtensionTmpDir = GLib.Dir.make_tmp('XXXXXX-shell-extension');
let newExtensionTmpDir = GLib.Dir.make_tmp('XXXXXX-shell-extension');
let params = { shell_version: Config.PACKAGE_VERSION };
let url = REPOSITORY_URL_DOWNLOAD.format(uuid);
let message = Soup.form_request_new_from_hash('GET', url, params);
_httpSession.queue_message(message, Lang.bind(this, function(session, message) {
gotExtensionZipFile(session, message, uuid, newExtensionTmpDir, function() {
let oldExtension = ExtensionUtils.extensions[uuid];
let extensionDir = oldExtension.dir;
if (!ExtensionSystem.unloadExtension(oldExtension))
return;
FileUtils.recursivelyMoveDir(extensionDir, oldExtensionTmpDir);
FileUtils.recursivelyMoveDir(newExtensionTmpDir, extensionDir);
let extension = ExtensionUtils.createExtensionObject(uuid, extensionDir, ExtensionUtils.ExtensionType.PER_USER);
try {
ExtensionSystem.loadExtension(extension);
} catch(e) {
ExtensionSystem.unloadExtension(extension);
logError(e, 'Error loading extension %s'.format(uuid));
FileUtils.recursivelyDeleteDir(extensionDir, false);
FileUtils.recursivelyMoveDir(oldExtensionTmpDir, extensionDir);
// Restore what was there before. We can't do much if we
// fail here.
ExtensionSystem.loadExtension(oldExtension);
return;
}
FileUtils.recursivelyDeleteDir(oldExtensionTmpDir, true);
}, function(code, message) {
log('Error while updating extension %s: %s (%s)'.format(uuid, code, message ? message : ''));
});
}));
}
function checkForUpdates() {
let metadatas = {};
for (let uuid in ExtensionUtils.extensions) {
metadatas[uuid] = ExtensionUtils.extensions[uuid].metadata;
}
let params = { shell_version: Config.PACKAGE_VERSION,
installed: JSON.stringify(metadatas) };
let url = REPOSITORY_URL_UPDATE;
let message = Soup.form_request_new_from_hash('GET', url, params);
_httpSession.queue_message(message, function(session, message) {
if (message.status_code != Soup.KnownStatusCode.OK)
return;
let operations = JSON.parse(message.response_body.data);
for (let uuid in operations) {
let operation = operations[uuid];
if (operation == 'blacklist')
uninstallExtension(uuid);
else if (operation == 'upgrade' || operation == 'downgrade')
updateExtension(uuid);
}
});
}
const InstallExtensionDialog = new Lang.Class({
Name: 'InstallExtensionDialog',
Extends: ModalDialog.ModalDialog,
_init: function(uuid, info, invocation) {
this.parent({ styleClass: 'extension-dialog' });
this._uuid = uuid;
this._info = info;
this._invocation = invocation;
this.setButtons([{ label: _("Cancel"),
action: Lang.bind(this, this._onCancelButtonPressed),
key: Clutter.Escape
},
{ label: _("Install"),
action: Lang.bind(this, this._onInstallButtonPressed),
default: true
}]);
let message = _("Download and install '%s' from extensions.gnome.org?").format(info.name);
let box = new St.BoxLayout();
this.contentLayout.add(box);
let gicon = new Gio.FileIcon({ file: Gio.File.new_for_uri(REPOSITORY_URL_BASE + info.icon) })
let icon = new St.Icon({ gicon: gicon });
box.add(icon);
let label = new St.Label({ text: message });
box.add(label);
},
_onCancelButtonPressed: function(button, event) {
this.close(global.get_current_time());
this._invocation.return_value(GLib.Variant.new('(s)', ['cancelled']));
},
_onInstallButtonPressed: function(button, event) {
let params = { shell_version: Config.PACKAGE_VERSION };
let url = REPOSITORY_URL_DOWNLOAD.format(this._uuid);
let message = Soup.form_request_new_from_hash('GET', url, params);
let uuid = this._uuid;
let dir = Gio.File.new_for_path(GLib.build_filenamev([global.userdatadir, 'extensions', uuid]));
let invocation = this._invocation;
function errback(code, message) {
invocation.return_dbus_error('org.gnome.Shell.' + code, message ? message.toString() : '');
}
function callback() {
// Add extension to 'enabled-extensions' for the user, always...
let enabledExtensions = global.settings.get_strv(ExtensionSystem.ENABLED_EXTENSIONS_KEY);
if (enabledExtensions.indexOf(uuid) == -1) {
enabledExtensions.push(uuid);
global.settings.set_strv(ExtensionSystem.ENABLED_EXTENSIONS_KEY, enabledExtensions);
}
let extension = ExtensionUtils.createExtensionObject(uuid, dir, ExtensionUtils.ExtensionType.PER_USER);
try {
ExtensionSystem.loadExtension(extension);
} catch(e) {
uninstallExtension(uuid);
errback('LoadExtensionError', e);
return;
}
invocation.return_value(GLib.Variant.new('(s)', 'successful'));
}
_httpSession.queue_message(message, Lang.bind(this, function(session, message) {
gotExtensionZipFile(session, message, uuid, dir, callback, errback);
}));
this.close(global.get_current_time());
}
});
function init() {
_httpSession = new Soup.SessionAsync({ ssl_use_system_ca_file: true });
// See: https://bugzilla.gnome.org/show_bug.cgi?id=655189 for context.
// _httpSession.add_feature(new Soup.ProxyResolverDefault());
Soup.Session.prototype.add_feature.call(_httpSession, new Soup.ProxyResolverDefault());
}

View File

@ -3,11 +3,19 @@
const Lang = imports.lang; const Lang = imports.lang;
const Signals = imports.signals; const Signals = imports.signals;
const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const St = imports.gi.St; const St = imports.gi.St;
const Shell = imports.gi.Shell;
const Soup = imports.gi.Soup;
const Config = imports.misc.config;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const FileUtils = imports.misc.fileUtils;
const ModalDialog = imports.ui.modalDialog;
const API_VERSION = 1;
const ExtensionState = { const ExtensionState = {
ENABLED: 1, ENABLED: 1,
@ -22,6 +30,29 @@ const ExtensionState = {
UNINSTALLED: 99 UNINSTALLED: 99
}; };
const REPOSITORY_URL_BASE = 'https://extensions.gnome.org';
const REPOSITORY_URL_DOWNLOAD = REPOSITORY_URL_BASE + '/download-extension/%s.shell-extension.zip';
const REPOSITORY_URL_INFO = REPOSITORY_URL_BASE + '/extension-info/';
const _httpSession = new Soup.SessionAsync();
// The unfortunate state of gjs, gobject-introspection and libsoup
// means that I have to do a hack to add a feature.
// See: https://bugzilla.gnome.org/show_bug.cgi?id=655189 for context.
if (Soup.Session.prototype.add_feature != null)
Soup.Session.prototype.add_feature.call(_httpSession, new Soup.ProxyResolverDefault());
function _getCertFile() {
let localCert = GLib.build_filenamev([global.userdatadir, 'extensions.gnome.org.crt']);
if (GLib.file_test(localCert, GLib.FileTest.EXISTS))
return localCert;
else
return Config.SHELL_SYSTEM_CA_FILE;
}
_httpSession.ssl_ca_file = _getCertFile();
// Arrays of uuids // Arrays of uuids
var enabledExtensions; var enabledExtensions;
// Contains the order that extensions were enabled in. // Contains the order that extensions were enabled in.
@ -38,6 +69,90 @@ const disconnect = Lang.bind(_signals, _signals.disconnect);
const ENABLED_EXTENSIONS_KEY = 'enabled-extensions'; const ENABLED_EXTENSIONS_KEY = 'enabled-extensions';
function installExtensionFromUUID(uuid, version_tag) {
let params = { uuid: uuid,
version_tag: version_tag,
shell_version: Config.PACKAGE_VERSION,
api_version: API_VERSION.toString() };
let message = Soup.form_request_new_from_hash('GET', REPOSITORY_URL_INFO, params);
_httpSession.queue_message(message,
function(session, message) {
let info = JSON.parse(message.response_body.data);
let dialog = new InstallExtensionDialog(uuid, version_tag, info.name);
dialog.open(global.get_current_time());
});
}
function uninstallExtensionFromUUID(uuid) {
let extension = ExtensionUtils.extensions[uuid];
if (!extension)
return false;
// Try to disable it -- if it's ERROR'd, we can't guarantee that,
// but it will be removed on next reboot, and hopefully nothing
// broke too much.
disableExtension(uuid);
// Don't try to uninstall system extensions
if (extension.type != ExtensionUtils.ExtensionType.PER_USER)
return false;
extension.state = ExtensionState.UNINSTALLED;
_signals.emit('extension-state-changed', extension);
delete ExtensionUtils.extensions[uuid];
FileUtils.recursivelyDeleteDir(Gio.file_new_for_path(extension.path));
return true;
}
function gotExtensionZipFile(session, message, uuid) {
if (message.status_code != Soup.KnownStatusCode.OK) {
logExtensionError(uuid, 'downloading extension: ' + message.status_code);
return;
}
// FIXME: use a GFile mkstemp-type method once one exists
let fd, tmpzip;
try {
[fd, tmpzip] = GLib.file_open_tmp('XXXXXX.shell-extension.zip');
} catch (e) {
logExtensionError(uuid, 'tempfile: ' + e.toString());
return;
}
let stream = new Gio.UnixOutputStream({ fd: fd });
let dir = ExtensionUtils.userExtensionsDir.get_child(uuid);
Shell.write_soup_message_to_stream(stream, message);
stream.close(null);
let [success, pid] = GLib.spawn_async(null,
['unzip', '-uod', dir.get_path(), '--', tmpzip],
null,
GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD,
null);
if (!success) {
logExtensionError(uuid, 'extract: could not extract');
return;
}
GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, function(pid, status) {
GLib.spawn_close_pid(pid);
// Add extension to 'enabled-extensions' for the user, always...
let enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
if (enabledExtensions.indexOf(uuid) == -1) {
enabledExtensions.push(uuid);
global.settings.set_strv(ENABLED_EXTENSIONS_KEY, enabledExtensions);
}
loadExtension(dir, ExtensionUtils.ExtensionType.PER_USER, true);
});
}
function disableExtension(uuid) { function disableExtension(uuid) {
let extension = ExtensionUtils.extensions[uuid]; let extension = ExtensionUtils.extensions[uuid];
if (!extension) if (!extension)
@ -63,23 +178,23 @@ function disableExtension(uuid) {
try { try {
ExtensionUtils.extensions[uuid].stateObj.disable(); ExtensionUtils.extensions[uuid].stateObj.disable();
} catch(e) { } catch(e) {
logExtensionError(uuid, e); logExtensionError(uuid, e.toString());
} }
} }
if (extension.stylesheet) { try {
let theme = St.ThemeContext.get_for_stage(global.stage).get_theme(); extension.stateObj.disable();
theme.unload_stylesheet(extension.stylesheet.get_path()); } catch(e) {
logExtensionError(uuid, e.toString());
return;
} }
extension.stateObj.disable();
for (let i = 0; i < order.length; i++) { for (let i = 0; i < order.length; i++) {
let uuid = order[i]; let uuid = order[i];
try { try {
ExtensionUtils.extensions[uuid].stateObj.enable(); ExtensionUtils.extensions[uuid].stateObj.enable();
} catch(e) { } catch(e) {
logExtensionError(uuid, e); logExtensionError(uuid, e.toString());
} }
} }
@ -102,86 +217,68 @@ function enableExtension(uuid) {
extensionOrder.push(uuid); extensionOrder.push(uuid);
extension.stateObj.enable(); try {
extension.stateObj.enable();
let stylesheetFile = extension.dir.get_child('stylesheet.css'); } catch(e) {
if (stylesheetFile.query_exists(null)) { logExtensionError(uuid, e.toString());
let theme = St.ThemeContext.get_for_stage(global.stage).get_theme(); return;
theme.load_stylesheet(stylesheetFile.get_path());
extension.stylesheet = stylesheetFile;
} }
extension.state = ExtensionState.ENABLED; extension.state = ExtensionState.ENABLED;
_signals.emit('extension-state-changed', extension); _signals.emit('extension-state-changed', extension);
} }
function logExtensionError(uuid, error) { function logExtensionError(uuid, message, state) {
let extension = ExtensionUtils.extensions[uuid]; let extension = ExtensionUtils.extensions[uuid];
if (!extension) if (!extension)
return; return;
let message = '' + error;
if (error.state)
extension.state = error.state;
else
extension.state = ExtensionState.ERROR;
if (!extension.errors) if (!extension.errors)
extension.errors = []; extension.errors = [];
log('Extension "%s" had error: %s'.format(uuid, message)); extension.errors.push(message);
global.logError('Extension "%s" had error: %s'.format(uuid, message));
state = state || ExtensionState.ERROR;
_signals.emit('extension-state-changed', { uuid: uuid, _signals.emit('extension-state-changed', { uuid: uuid,
error: message, error: message,
state: extension.state }); state: state });
} }
function loadExtension(extension) { function loadExtension(dir, type, enabled) {
let uuid = dir.get_basename();
let extension;
if (ExtensionUtils.extensions[uuid] != undefined) {
global.logError('Extension "%s" is already loaded'.format(uuid));
return;
}
try {
extension = ExtensionUtils.createExtensionObject(uuid, dir, type);
} catch(e) {
logExtensionError(uuid, e.message);
return;
}
// Default to error, we set success as the last step // Default to error, we set success as the last step
extension.state = ExtensionState.ERROR; extension.state = ExtensionState.ERROR;
if (ExtensionUtils.isOutOfDate(extension)) { if (ExtensionUtils.isOutOfDate(extension)) {
let error = new Error('extension is not compatible with current GNOME Shell and/or GJS version'); logExtensionError(uuid, 'extension is not compatible with current GNOME Shell and/or GJS version', ExtensionState.OUT_OF_DATE);
error.state = ExtensionState.OUT_OF_DATE; extension.state = ExtensionState.OUT_OF_DATE;
throw error; return;
} }
let enabled = enabledExtensions.indexOf(extension.uuid) != -1;
if (enabled) { if (enabled) {
initExtension(extension.uuid); initExtension(uuid);
if (extension.state == ExtensionState.DISABLED) if (extension.state == ExtensionState.DISABLED)
enableExtension(extension.uuid); enableExtension(uuid);
} else { } else {
extension.state = ExtensionState.INITIALIZED; extension.state = ExtensionState.INITIALIZED;
} }
_signals.emit('extension-state-changed', extension); _signals.emit('extension-state-changed', extension);
} global.log('Loaded extension ' + uuid);
function unloadExtension(extension) {
// Try to disable it -- if it's ERROR'd, we can't guarantee that,
// but it will be removed on next reboot, and hopefully nothing
// broke too much.
disableExtension(extension.uuid);
extension.state = ExtensionState.UNINSTALLED;
_signals.emit('extension-state-changed', extension);
delete ExtensionUtils.extensions[extension.uuid];
return true;
}
function reloadExtension(oldExtension) {
// Grab the things we'll need to pass to createExtensionObject
// to reload it.
let { uuid: uuid, dir: dir, type: type } = oldExtension;
// Then unload the old extension.
unloadExtension(oldExtension);
// Now, recreate the extension and load it.
let newExtension = ExtensionUtils.createExtensionObject(uuid, dir, type);
loadExtension(newExtension);
} }
function initExtension(uuid) { function initExtension(uuid) {
@ -192,24 +289,64 @@ function initExtension(uuid) {
throw new Error("Extension was not properly created. Call loadExtension first"); throw new Error("Extension was not properly created. Call loadExtension first");
let extensionJs = dir.get_child('extension.js'); let extensionJs = dir.get_child('extension.js');
if (!extensionJs.query_exists(null)) if (!extensionJs.query_exists(null)) {
throw new Error('Missing extension.js'); logExtensionError(uuid, 'Missing extension.js');
return;
}
let stylesheetPath = null;
let themeContext = St.ThemeContext.get_for_stage(global.stage);
let theme = themeContext.get_theme();
let stylesheetFile = dir.get_child('stylesheet.css');
if (stylesheetFile.query_exists(null)) {
try {
theme.load_stylesheet(stylesheetFile.get_path());
} catch (e) {
logExtensionError(uuid, 'Stylesheet parse error: ' + e);
return;
}
}
let extensionModule; let extensionModule;
let extensionState = null; let extensionState = null;
try {
ExtensionUtils.installImporter(extension);
extensionModule = extension.imports.extension;
} catch (e) {
if (stylesheetPath != null)
theme.unload_stylesheet(stylesheetPath);
logExtensionError(uuid, '' + e);
return;
}
ExtensionUtils.installImporter(extension); if (!extensionModule.init) {
extensionModule = extension.imports.extension; logExtensionError(uuid, 'missing \'init\' function');
return;
}
if (extensionModule.init) { try {
extensionState = extensionModule.init(extension); extensionState = extensionModule.init(extension);
} catch (e) {
if (stylesheetPath != null)
theme.unload_stylesheet(stylesheetPath);
logExtensionError(uuid, 'Failed to evaluate init function:' + e);
return;
} }
if (!extensionState) if (!extensionState)
extensionState = extensionModule; extensionState = extensionModule;
extension.stateObj = extensionState; extension.stateObj = extensionState;
if (!extensionState.enable) {
logExtensionError(uuid, 'missing \'enable\' function');
return;
}
if (!extensionState.disable) {
logExtensionError(uuid, 'missing \'disable\' function');
return;
}
extension.state = ExtensionState.DISABLED; extension.state = ExtensionState.DISABLED;
_signals.emit('extension-loaded', uuid); _signals.emit('extension-loaded', uuid);
} }
@ -221,11 +358,7 @@ function onEnabledExtensionsChanged() {
newEnabledExtensions.filter(function(uuid) { newEnabledExtensions.filter(function(uuid) {
return enabledExtensions.indexOf(uuid) == -1; return enabledExtensions.indexOf(uuid) == -1;
}).forEach(function(uuid) { }).forEach(function(uuid) {
try { enableExtension(uuid);
enableExtension(uuid);
} catch(e) {
logExtensionError(uuid, e);
}
}); });
// Find and disable all the newly disabled extensions: UUIDs found in the // Find and disable all the newly disabled extensions: UUIDs found in the
@ -233,27 +366,87 @@ function onEnabledExtensionsChanged() {
enabledExtensions.filter(function(item) { enabledExtensions.filter(function(item) {
return newEnabledExtensions.indexOf(item) == -1; return newEnabledExtensions.indexOf(item) == -1;
}).forEach(function(uuid) { }).forEach(function(uuid) {
try { disableExtension(uuid);
disableExtension(uuid);
} catch(e) {
logExtensionError(uuid, e);
}
}); });
enabledExtensions = newEnabledExtensions; enabledExtensions = newEnabledExtensions;
} }
function loadExtensions() { function init() {
ExtensionUtils.init();
global.settings.connect('changed::' + ENABLED_EXTENSIONS_KEY, onEnabledExtensionsChanged); global.settings.connect('changed::' + ENABLED_EXTENSIONS_KEY, onEnabledExtensionsChanged);
enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY); enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
let finder = new ExtensionUtils.ExtensionFinder();
finder.connect('extension-found', function(signals, extension) {
try {
loadExtension(extension);
} catch(e) {
logExtensionError(extension.uuid, e);
}
});
finder.scanExtensions();
} }
function loadExtensions() {
ExtensionUtils.scanExtensions(function(uuid, dir, type) {
let enabled = enabledExtensions.indexOf(uuid) != -1;
loadExtension(dir, type, enabled);
});
}
const InstallExtensionDialog = new Lang.Class({
Name: 'InstallExtensionDialog',
Extends: ModalDialog.ModalDialog,
_init: function(uuid, version_tag, name) {
this.parent({ styleClass: 'extension-dialog' });
this._uuid = uuid;
this._version_tag = version_tag;
this._name = name;
this.setButtons([{ label: _("Cancel"),
action: Lang.bind(this, this._onCancelButtonPressed),
key: Clutter.Escape
},
{ label: _("Install"),
action: Lang.bind(this, this._onInstallButtonPressed)
}]);
let message = _("Download and install '%s' from extensions.gnome.org?").format(name);
this._descriptionLabel = new St.Label({ text: message });
this.contentLayout.add(this._descriptionLabel,
{ y_fill: true,
y_align: St.Align.START });
},
_onCancelButtonPressed: function(button, event) {
this.close(global.get_current_time());
// Even though the extension is already "uninstalled", send through
// a state-changed signal for any users who want to know if the install
// went through correctly -- using proper async DBus would block more
// traditional clients like the plugin
let meta = { uuid: this._uuid,
state: ExtensionState.UNINSTALLED,
error: '' };
_signals.emit('extension-state-changed', meta);
},
_onInstallButtonPressed: function(button, event) {
let state = { uuid: this._uuid,
state: ExtensionState.DOWNLOADING,
error: '' };
_signals.emit('extension-state-changed', state);
let params = { version_tag: this._version_tag,
shell_version: Config.PACKAGE_VERSION,
api_version: API_VERSION.toString() };
let url = REPOSITORY_URL_DOWNLOAD.format(this._uuid);
let message = Soup.form_request_new_from_hash('GET', url, params);
_httpSession.queue_message(message,
Lang.bind(this, function(session, message) {
gotExtensionZipFile(session, message, this._uuid);
}));
this.close(global.get_current_time());
}
});

View File

@ -1,355 +0,0 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
const Clutter = imports.gi.Clutter;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Main = imports.ui.main;
const Params = imports.misc.params;
function _navigateActor(actor, needsGrab) {
if (!actor)
return;
if (needsGrab && actor instanceof St.Widget)
needsGrab = !actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
if (needsGrab)
actor.grab_key_focus();
}
// GrabHelper:
// @owner: the actor that owns the GrabHelper
//
// Creates a new GrabHelper object, for dealing with keyboard and pointer grabs
// associated with a set of actors.
//
// Note that the grab can be automatically dropped at any time by the user, and
// your code just needs to deal with it; you shouldn't adjust behavior directly
// after you call ungrab(), but instead pass an 'onUngrab' callback when you
// call grab().
const GrabHelper = new Lang.Class({
Name: 'GrabHelper',
_init: function(owner) {
this._owner = owner;
this._grabStack = [];
this._actors = [];
this._capturedEventId = 0;
this._eventId = 0;
this._keyFocusNotifyId = 0;
this._focusWindowChangedId = 0;
this._ignoreRelease = false;
this._modalCount = 0;
this._grabFocusCount = 0;
},
// addActor:
// @actor: an actor
//
// Adds @actor to the set of actors that are allowed to process events
// during a grab.
addActor: function(actor) {
actor.__grabHelperDestroyId = actor.connect('destroy', Lang.bind(this, function() { this.removeActor(actor); }));
this._actors.push(actor);
},
// removeActor:
// @actor: an actor
//
// Removes @actor from the set of actors that are allowed to
// process events during a grab.
removeActor: function(actor) {
let index = this._actors.indexOf(actor);
if (index != -1)
this._actors.splice(index, 1);
if (actor.__grabHelperDestroyId) {
actor.disconnect(actor.__grabHelperDestroyId);
delete actor.__grabHelperDestroyId;
}
},
_isWithinGrabbedActor: function(actor) {
while (actor) {
if (this._actors.indexOf(actor) != -1)
return true;
actor = actor.get_parent();
}
return false;
},
get currentGrab() {
let idx = this._grabStack.length - 1;
while (idx >= 0 && this._grabStack[idx].untracked)
idx--;
return this._grabStack[idx] || {};
},
_findStackIndex: function(actor) {
if (!actor)
return -1;
for (let i = 0; i < this._grabStack.length; i++) {
if (this._grabStack[i].actor === actor)
return i;
}
return -1;
},
isActorGrabbed: function(actor) {
return this._findStackIndex(actor) >= 0;
},
// grab:
// @params: A bunch of parameters, see below
//
// Grabs the mouse and keyboard, according to the GrabHelper's
// parameters. If @newFocus is not %null, then the keyboard focus
// is moved to the first #StWidget:can-focus widget inside it.
//
// The grab will automatically be dropped if:
// - The user clicks outside the grabbed actors
// - The user types Escape
// - The keyboard focus is moved outside the grabbed actors
// - A window is focused
//
// If @params.actor is not null, then it will be focused as the
// new actor. If you attempt to grab an already focused actor, the
// request to be focused will be ignored. The actor will not be
// added to the grab stack, so do not call a paired ungrab().
//
// If @params contains { modal: true }, then grab() will push a modal
// on the owner of the GrabHelper. As long as there is at least one
// { modal: true } actor on the grab stack, the grab will be kept.
// When the last { modal: true } actor is ungrabbed, then the modal
// will be dropped. A modal grab can fail if there is already a grab
// in effect from aother application; in this case the function returns
// false and nothing happens. Non-modal grabs can never fail.
//
// If @params contains { grabFocus: true }, then if you call grab()
// while the shell is outside the overview, it will set the stage
// input mode to %Shell.StageInputMode.FOCUSED, and ungrab() will
// revert it back, and re-focus the previously-focused window (if
// another window hasn't been explicitly focused before then).
//
// If @params contains { untracked: true }, then it will be skipped
// when the grab helper ungrabs for you, or when calculating
// currentGrab.
grab: function(params) {
params = Params.parse(params, { actor: null,
modal: false,
untracked: false,
grabFocus: false,
onUngrab: null });
let focus = global.stage.key_focus;
let hadFocus = focus && this._isWithinGrabbedActor(focus);
let newFocus = params.actor;
if (this.isActorGrabbed(params.actor))
return true;
if (this._grabFocusCount == 0 && this._modalCount == 0) {
if (!this._fullGrab(hadFocus, params.modal, params.grabFocus))
return false;
}
params.savedFocus = focus;
this._grabStack.push(params);
if (params.modal)
this._modalCount++;
if (params.grabFocus)
this._grabFocusCount++;
_navigateActor(newFocus, hadFocus);
return true;
},
_fullGrab: function(hadFocus, modal, grabFocus) {
let metaDisplay = global.screen.get_display();
if (modal) {
if (!Main.pushModal(this._owner))
return false;
}
this._grabbedFromKeynav = hadFocus;
this._preGrabInputMode = global.stage_input_mode;
this._prevFocusedWindow = null;
if (grabFocus) {
this._prevFocusedWindow = metaDisplay.focus_window;
if (this._preGrabInputMode == Shell.StageInputMode.NONREACTIVE ||
this._preGrabInputMode == Shell.StageInputMode.NORMAL) {
global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
}
}
if (modal || grabFocus) {
this._capturedEventId = global.stage.connect('captured-event', Lang.bind(this, this._onCapturedEvent));
this._eventId = global.stage.connect('event', Lang.bind(this, this._onEvent));
this._keyFocusNotifyId = global.stage.connect('notify::key-focus', Lang.bind(this, this._onKeyFocusChanged));
this._focusWindowChangedId = metaDisplay.connect('notify::focus-window', Lang.bind(this, this._focusWindowChanged));
}
return true;
},
// ignoreRelease:
//
// Make sure that the next button release event evaluated by the
// capture event handler returns false. This is designed for things
// like the ComboBoxMenu that go away on press, but need to eat
// the next release event.
ignoreRelease: function() {
this._ignoreRelease = true;
},
// ungrab:
// @params: The parameters for the grab; see below.
//
// Pops an actor from the grab stack, potentially dropping the grab.
//
// If the actor that was popped from the grab stack was not the actor
// That was passed in, this call is ignored.
ungrab: function(params) {
params = Params.parse(params, { actor: this.currentGrab.actor });
let grabStackIndex = this._findStackIndex(params.actor);
if (grabStackIndex < 0)
return;
let poppedGrabs = this._grabStack.slice(grabStackIndex);
// "Pop" all newly ungrabbed actors off the grab stack
// by truncating the array.
this._grabStack.length = grabStackIndex;
let wasModal = this._modalCount > 0;
for (let i = poppedGrabs.length - 1; i >= 0; i--) {
let poppedGrab = poppedGrabs[i];
if (poppedGrab.onUngrab)
poppedGrab.onUngrab();
if (poppedGrab.modal)
this._modalCount--;
if (poppedGrab.grabFocus)
this._grabFocusCount--;
}
let focus = global.stage.key_focus;
let hadFocus = focus && this._isWithinGrabbedActor(focus);
if (this._grabFocusCount == 0 && this._modalCount == 0)
this._fullUngrab(wasModal);
let poppedGrab = poppedGrabs[0];
_navigateActor(poppedGrab.savedFocus, hadFocus);
},
_fullUngrab: function(wasModal) {
if (this._capturedEventId > 0) {
global.stage.disconnect(this._capturedEventId);
this._capturedEventId = 0;
}
if (this._eventId > 0) {
global.stage.disconnect(this._eventId);
this._eventId = 0;
}
if (this._keyFocusNotifyId > 0) {
global.stage.disconnect(this._keyFocusNotifyId);
this._keyFocusNotifyId = 0;
}
if (!this._focusWindowChanged > 0) {
let metaDisplay = global.screen.get_display();
metaDisplay.disconnect(this._focusWindowChangedId);
this._focusWindowChangedId = 0;
}
let prePopInputMode = global.stage_input_mode;
if (wasModal) {
Main.popModal(this._owner);
global.sync_pointer();
}
if (this._grabbedFromKeynav) {
if (this._preGrabInputMode == Shell.StageInputMode.FOCUSED &&
prePopInputMode != Shell.StageInputMode.FULLSCREEN)
global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
}
if (this._prevFocusedWindow) {
let metaDisplay = global.screen.get_display();
if (!metaDisplay.focus_window) {
metaDisplay.set_input_focus_window(this._prevFocusedWindow,
false, global.get_current_time());
}
}
},
_onCapturedEvent: function(actor, event) {
let type = event.type();
let press = type == Clutter.EventType.BUTTON_PRESS;
let release = type == Clutter.EventType.BUTTON_RELEASE;
let button = press || release;
if (release && this._ignoreRelease) {
this._ignoreRelease = false;
return false;
}
if (!button && this._modalCount == 0)
return false;
if (this._isWithinGrabbedActor(event.get_source()))
return false;
if (button) {
// If we have a press event, ignore the next event,
// which should be a release event.
if (press)
this._ignoreRelease = true;
this.ungrab();
}
return this._modalCount > 0;
},
// We catch 'event' rather than 'key-press-event' so that we get
// a chance to run before the overview's own Escape check
_onEvent: function(actor, event) {
if (event.type() == Clutter.EventType.KEY_PRESS &&
event.get_key_symbol() == Clutter.KEY_Escape) {
this.ungrab();
return true;
}
return false;
},
_onKeyFocusChanged: function() {
let focus = global.stage.key_focus;
if (!focus || !this._isWithinGrabbedActor(focus))
this.ungrab();
},
_focusWindowChanged: function() {
let metaDisplay = global.screen.get_display();
if (metaDisplay.focus_window != null)
this.ungrab();
}
});

View File

@ -1,228 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter;
const IBus = imports.gi.IBus;
const Lang = imports.lang;
const St = imports.gi.St;
const BoxPointer = imports.ui.boxpointer;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
const MAX_CANDIDATES_PER_PAGE = 16;
const CandidateArea = new Lang.Class({
Name: 'CandidateArea',
Extends: PopupMenu.PopupBaseMenuItem,
_init: function() {
this.parent({ reactive: false });
// St.Table exhibits some sizing problems so let's go with a
// clutter layout manager for now.
this._table = new Clutter.Actor();
this.addActor(this._table);
this._tableLayout = new Clutter.TableLayout();
this._table.set_layout_manager(this._tableLayout);
this._indexLabels = [];
this._candidateLabels = [];
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
this._indexLabels.push(new St.Label({ style_class: 'candidate-index' }));
this._candidateLabels.push(new St.Label({ style_class: 'candidate-label' }));
}
this._orientation = -1;
this._cursorPosition = 0;
},
_setOrientation: function(orientation) {
if (this._orientation == orientation)
return;
this._orientation = orientation;
this._table.remove_all_children();
if (this._orientation == IBus.Orientation.HORIZONTAL)
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
this._tableLayout.pack(this._indexLabels[i], i*2, 0);
this._tableLayout.pack(this._candidateLabels[i], i*2 + 1, 0);
}
else // VERTICAL || SYSTEM
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
this._tableLayout.pack(this._indexLabels[i], 0, i);
this._tableLayout.pack(this._candidateLabels[i], 1, i);
}
},
setCandidates: function(indexes, candidates, orientation, cursorPosition, cursorVisible) {
this._setOrientation(orientation);
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
let visible = i < candidates.length;
this._indexLabels[i].visible = visible;
this._candidateLabels[i].visible = visible;
if (!visible)
continue;
this._indexLabels[i].text = ((indexes && indexes[i]) ? indexes[i] : '%x.'.format(i + 1));
this._candidateLabels[i].text = candidates[i];
}
this._candidateLabels[this._cursorPosition].remove_style_pseudo_class('selected');
this._cursorPosition = cursorPosition;
if (cursorVisible)
this._candidateLabels[cursorPosition].add_style_pseudo_class('selected');
},
});
const CandidatePopup = new Lang.Class({
Name: 'CandidatePopup',
Extends: PopupMenu.PopupMenu,
_init: function() {
this._cursor = new St.Bin({ opacity: 0 });
Main.uiGroup.add_actor(this._cursor);
this.parent(this._cursor, 0, St.Side.TOP);
this.actor.hide();
Main.uiGroup.add_actor(this.actor);
this._preeditTextItem = new PopupMenu.PopupMenuItem('', { reactive: false });
this._preeditTextItem.actor.hide();
this.addMenuItem(this._preeditTextItem);
this._auxTextItem = new PopupMenu.PopupMenuItem('', { reactive: false });
this._auxTextItem.actor.hide();
this.addMenuItem(this._auxTextItem);
this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this._lookupTableItem = new CandidateArea();
this._lookupTableItem.actor.hide();
this.addMenuItem(this._lookupTableItem);
this._panelService = null;
},
setPanelService: function(panelService) {
this._panelService = panelService;
if (!panelService)
return;
panelService.connect('set-cursor-location',
Lang.bind(this, function(ps, x, y, w, h) {
this._cursor.set_position(x, y);
this._cursor.set_size(w, h);
}));
panelService.connect('update-preedit-text',
Lang.bind(this, function(ps, text, cursorPosition, visible) {
if (visible)
this._preeditTextItem.actor.show();
else
this._preeditTextItem.actor.hide();
this._updateVisibility();
this._preeditTextItem.actor.label_actor.text = text.get_text();
let attrs = text.get_attributes();
if (attrs)
this._setTextAttributes(this._preeditTextItem.actor.label_actor.clutter_text,
attrs);
}));
panelService.connect('show-preedit-text',
Lang.bind(this, function(ps) {
this._preeditTextItem.actor.show();
this._updateVisibility();
}));
panelService.connect('hide-preedit-text',
Lang.bind(this, function(ps) {
this._preeditTextItem.actor.hide();
this._updateVisibility();
}));
panelService.connect('update-auxiliary-text',
Lang.bind(this, function(ps, text, visible) {
if (visible)
this._auxTextItem.actor.show();
else
this._auxTextItem.actor.hide();
this._updateVisibility();
this._auxTextItem.actor.label_actor.text = text.get_text();
}));
panelService.connect('show-auxiliary-text',
Lang.bind(this, function(ps) {
this._auxTextItem.actor.show();
this._updateVisibility();
}));
panelService.connect('hide-auxiliary-text',
Lang.bind(this, function(ps) {
this._auxTextItem.actor.hide();
this._updateVisibility();
}));
panelService.connect('update-lookup-table',
Lang.bind(this, function(ps, lookupTable, visible) {
if (visible)
this._lookupTableItem.actor.show();
else
this._lookupTableItem.actor.hide();
this._updateVisibility();
let cursorPos = lookupTable.get_cursor_pos();
let pageSize = lookupTable.get_page_size();
let page = ((cursorPos == 0) ? 0 : Math.floor(cursorPos / pageSize));
let startIndex = page * pageSize;
let endIndex = Math.min((page + 1) * pageSize,
lookupTable.get_number_of_candidates());
let indexes = [];
let indexLabel;
for (let i = 0; indexLabel = lookupTable.get_label(i); ++i)
indexes.push(indexLabel.get_text());
let candidates = [];
for (let i = startIndex; i < endIndex; ++i)
candidates.push(lookupTable.get_candidate(i).get_text());
this._lookupTableItem.setCandidates(indexes,
candidates,
lookupTable.get_orientation(),
cursorPos % pageSize,
lookupTable.is_cursor_visible());
}));
panelService.connect('show-lookup-table',
Lang.bind(this, function(ps) {
this._lookupTableItem.actor.show();
this._updateVisibility();
}));
panelService.connect('hide-lookup-table',
Lang.bind(this, function(ps) {
this._lookupTableItem.actor.hide();
this._updateVisibility();
}));
panelService.connect('focus-out',
Lang.bind(this, function(ps) {
this.close(BoxPointer.PopupAnimation.NONE);
}));
},
_updateVisibility: function() {
let isVisible = (this._preeditTextItem.actor.visible ||
this._auxTextItem.actor.visible ||
this._lookupTableItem.actor.visible);
if (isVisible)
this.open(BoxPointer.PopupAnimation.NONE);
else
this.close(BoxPointer.PopupAnimation.NONE);
},
_setTextAttributes: function(clutterText, ibusAttrList) {
let attr;
for (let i = 0; attr = ibusAttrList.get(i); ++i)
if (attr.get_attr_type() == IBus.AttrType.BACKGROUND)
clutterText.set_selection(attr.get_start_index(), attr.get_end_index());
}
});

View File

@ -282,10 +282,6 @@ const IconGrid = new Lang.Class({
return this._computeLayout(rowWidth)[0]; return this._computeLayout(rowWidth)[0];
}, },
getRowLimit: function() {
return this._rowLimit;
},
_computeLayout: function (forWidth) { _computeLayout: function (forWidth) {
let nColumns = 0; let nColumns = 0;
let usedWidth = 0; let usedWidth = 0;
@ -309,22 +305,21 @@ const IconGrid = new Lang.Class({
this._grid.queue_relayout(); this._grid.queue_relayout();
}, },
removeAll: function() { removeAll: function () {
this._grid.destroy_all_children(); this._grid.get_children().forEach(Lang.bind(this, function (child) {
child.destroy();
}));
}, },
addItem: function(actor, index) { addItem: function(actor) {
if (index !== undefined) this._grid.add_actor(actor);
this._grid.insert_child_at_index(actor, index);
else
this._grid.add_actor(actor);
}, },
getItemAtIndex: function(index) { getItemAtIndex: function(index) {
return this._grid.get_child_at_index(index); return this._grid.get_children()[index];
}, },
visibleItemsCount: function() { visibleItemsCount: function() {
return this._grid.get_n_children() - this._grid.get_n_skip_paint(); return this._grid.get_children().length - this._grid.get_n_skip_paint();
} }
}); });

View File

@ -93,7 +93,7 @@ const Key = new Lang.Class({
this._getExtendedKeys(); this._getExtendedKeys();
this.actor._extended_keys = this._extended_keyboard; this.actor._extended_keys = this._extended_keyboard;
this._boxPointer.actor.hide(); this._boxPointer.actor.hide();
Main.layoutManager.addChrome(this._boxPointer.actor); Main.layoutManager.addChrome(this._boxPointer.actor, { visibleInFullscreen: true });
} }
}, },
@ -175,7 +175,7 @@ const Key = new Lang.Class({
this.actor.fake_release(); this.actor.fake_release();
this._boxPointer.actor.raise_top(); this._boxPointer.actor.raise_top();
this._boxPointer.setPosition(this.actor, 0.5); this._boxPointer.setPosition(this.actor, 0.5);
this._boxPointer.show(BoxPointer.PopupAnimation.FULL); this._boxPointer.show(true);
this.actor.set_hover(false); this.actor.set_hover(false);
if (!this._grabbed) { if (!this._grabbed) {
Main.pushModal(this.actor); Main.pushModal(this.actor);
@ -186,7 +186,7 @@ const Key = new Lang.Class({
} else { } else {
if (this._grabbed) if (this._grabbed)
this._ungrab(); this._ungrab();
this._boxPointer.hide(BoxPointer.PopupAnimation.FULL); this._boxPointer.hide(true);
} }
} }
}); });
@ -541,8 +541,16 @@ const KeyboardSource = new Lang.Class({
Extends: MessageTray.Source, Extends: MessageTray.Source,
_init: function(keyboard) { _init: function(keyboard) {
this.parent(_("Keyboard"));
this._keyboard = keyboard; this._keyboard = keyboard;
this.parent(_("Keyboard"), 'input-keyboard-symbolic');
this._setSummaryIcon(this.createNotificationIcon());
},
createNotificationIcon: function() {
return new St.Icon({ icon_name: 'input-keyboard',
icon_type: St.IconType.SYMBOLIC,
icon_size: this.ICON_SIZE });
}, },
handleSummaryClick: function() { handleSummaryClick: function() {

View File

@ -65,8 +65,7 @@ const KeyringDialog = new Lang.Class({
key: Clutter.Escape key: Clutter.Escape
}, },
{ label: '', { label: '',
action: Lang.bind(this, this._onContinueButton), action: Lang.bind(this, this._onContinueButton)
default: true
}] }]
this.setButtons(buttons); this.setButtons(buttons);
@ -84,10 +83,7 @@ const KeyringDialog = new Lang.Class({
if (this.prompt.password_visible) { if (this.prompt.password_visible) {
let label = new St.Label(({ style_class: 'prompt-dialog-password-label' })); let label = new St.Label(({ style_class: 'prompt-dialog-password-label' }));
label.set_text(_("Password:")); label.set_text(_("Password:"));
table.add(label, { row: row, col: 0, table.add(label, { row: row, col: 0, x_expand: false, x_fill: true, x_align: St.Align.START });
x_expand: false, x_fill: true,
x_align: St.Align.START,
y_fill: false, y_align: St.Align.MIDDLE });
this._passwordEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry', this._passwordEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry',
text: '', text: '',
can_focus: true}); can_focus: true});
@ -103,10 +99,7 @@ const KeyringDialog = new Lang.Class({
if (this.prompt.confirm_visible) { if (this.prompt.confirm_visible) {
var label = new St.Label(({ style_class: 'prompt-dialog-password-label' })); var label = new St.Label(({ style_class: 'prompt-dialog-password-label' }));
label.set_text(_("Type again:")); label.set_text(_("Type again:"));
table.add(label, { row: row, col: 0, table.add(label, { row: row, col: 0, x_expand: false, x_fill: true, x_align: St.Align.START });
x_expand: false, x_fill: true,
x_align: St.Align.START,
y_fill: false, y_align: St.Align.MIDDLE });
this._confirmEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry', this._confirmEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry',
text: '', text: '',
can_focus: true}); can_focus: true});

View File

@ -1,7 +1,6 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const GObject = imports.gi.GObject;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta; const Meta = imports.gi.Meta;
@ -12,85 +11,13 @@ const St = imports.gi.St;
const DND = imports.ui.dnd; const DND = imports.ui.dnd;
const Main = imports.ui.main; const Main = imports.ui.main;
const Params = imports.misc.params; const Params = imports.misc.params;
const ScreenSaver = imports.misc.screenSaver;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const HOT_CORNER_ACTIVATION_TIMEOUT = 0.5; const HOT_CORNER_ACTIVATION_TIMEOUT = 0.5;
const STARTUP_ANIMATION_TIME = 0.2; const STARTUP_ANIMATION_TIME = 0.2;
const KEYBOARD_ANIMATION_TIME = 0.5; const KEYBOARD_ANIMATION_TIME = 0.5;
const MonitorConstraint = new Lang.Class({
Name: 'MonitorConstraint',
Extends: Clutter.Constraint,
Properties: {'primary': GObject.ParamSpec.boolean('primary',
'Primary', 'Track primary monitor',
GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE,
false),
'index': GObject.ParamSpec.int('index',
'Monitor index', 'Track specific monitor',
GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE,
-1, 64, -1)},
_init: function(props) {
this._primary = false;
this._index = -1;
this.parent(props);
},
get primary() {
return this._primary;
},
set primary(v) {
this._primary = v;
if (this.actor)
this.actor.queue_relayout();
this.notify('primary');
},
get index() {
return this._index;
},
set index(v) {
this._index = v;
if (this.actor)
this.actor.queue_relayout();
this.notify('index');
},
vfunc_set_actor: function(actor) {
if (actor) {
if (!this._monitorsChangedId) {
this._monitorsChangedId = Main.layoutManager.connect('monitors-changed', Lang.bind(this, function() {
this.actor.queue_relayout();
}));
}
} else {
if (this._monitorsChangedId)
Main.layoutManager.disconnect(this._monitorsChangedId);
this._monitorsChangedId = 0;
}
this.parent(actor);
},
vfunc_update_allocation: function(actor, actorBox) {
if (!this._primary && this._index < 0)
return;
let monitor;
if (this._primary) {
monitor = Main.layoutManager.primaryMonitor;
} else {
let index = Math.min(this._index, Main.layoutManager.monitors.length - 1);
monitor = Main.layoutManager.monitors[index];
}
actorBox.init_rect(monitor.x, monitor.y, monitor.width, monitor.height);
}
});
const LayoutManager = new Lang.Class({ const LayoutManager = new Lang.Class({
Name: 'LayoutManager', Name: 'LayoutManager',
@ -106,38 +33,26 @@ const LayoutManager = new Lang.Class({
this._chrome = new Chrome(this); this._chrome = new Chrome(this);
this.screenShieldGroup = new St.Widget({ name: 'screenShieldGroup',
visible: false,
clip_to_allocation: true,
layout_manager: new Clutter.BinLayout(),
});
this.addChrome(this.screenShieldGroup);
this.panelBox = new St.BoxLayout({ name: 'panelBox', this.panelBox = new St.BoxLayout({ name: 'panelBox',
vertical: true }); vertical: true });
this.addChrome(this.panelBox, { affectsStruts: true, this.addChrome(this.panelBox, { affectsStruts: true });
trackFullscreen: true });
this.panelBox.connect('allocation-changed', this.panelBox.connect('allocation-changed',
Lang.bind(this, this._updatePanelBarriers)); Lang.bind(this, this._updatePanelBarriers));
this.trayBox = new St.BoxLayout({ name: 'trayBox' }); this.trayBox = new St.BoxLayout({ name: 'trayBox' });
this.addChrome(this.trayBox); this.addChrome(this.trayBox, { visibleInFullscreen: true });
this.trayBox.connect('allocation-changed', this.trayBox.connect('allocation-changed',
Lang.bind(this, this._updateTrayBarrier)); Lang.bind(this, this._updateTrayBarrier));
this.keyboardBox = new St.BoxLayout({ name: 'keyboardBox', this.keyboardBox = new St.BoxLayout({ name: 'keyboardBox',
reactive: true, reactive: true,
track_hover: true }); track_hover: true });
this.addChrome(this.keyboardBox); this.addChrome(this.keyboardBox, { visibleInFullscreen: true });
this._keyboardHeightNotifyId = 0; this._keyboardHeightNotifyId = 0;
global.screen.connect('monitors-changed', global.screen.connect('monitors-changed',
Lang.bind(this, this._monitorsChanged)); Lang.bind(this, this._monitorsChanged));
this._monitorsChanged(); this._monitorsChanged();
this._chrome.connect('primary-fullscreen-changed', Lang.bind(this, function(chrome, state) {
this.emit('primary-fullscreen-changed', state);
}));
}, },
// This is called by Main after everything else is constructed; // This is called by Main after everything else is constructed;
@ -230,9 +145,6 @@ const LayoutManager = new Lang.Class({
}, },
_updateBoxes: function() { _updateBoxes: function() {
this.screenShieldGroup.set_position(0, 0);
this.screenShieldGroup.set_size(global.screen_width, global.screen_height);
this.panelBox.set_position(this.primaryMonitor.x, this.primaryMonitor.y); this.panelBox.set_position(this.primaryMonitor.x, this.primaryMonitor.y);
this.panelBox.set_size(this.primaryMonitor.width, -1); this.panelBox.set_size(this.primaryMonitor.width, -1);
@ -312,9 +224,26 @@ const LayoutManager = new Lang.Class({
return false; return false;
}, },
get currentMonitor() { get focusIndex() {
let index = global.screen.get_current_monitor(); let focusWindow = global.display.focus_window;
return this.monitors[index];
if (focusWindow) {
let wrect = focusWindow.get_outer_rect();
for (let i = 0; i < this.monitors.length; i++) {
let monitor = this.monitors[i];
if (monitor.x <= wrect.x && monitor.y <= wrect.y &&
monitor.x + monitor.width > wrect.x &&
monitor.y + monitor.height > wrect.y)
return i;
}
}
return this.primaryIndex;
},
get focusMonitor() {
return this.monitors[this.focusIndex];
}, },
_startupAnimation: function() { _startupAnimation: function() {
@ -402,10 +331,8 @@ const LayoutManager = new Lang.Class({
// the window manager struts. Changes to @actor's visibility will // the window manager struts. Changes to @actor's visibility will
// NOT affect whether or not the strut is present, however. // NOT affect whether or not the strut is present, however.
// //
// If %trackFullscreen in @params is %true, the actor's visibility // If %visibleInFullscreen in @params is %true, the actor will be
// will be bound to the presence of fullscreen windows on the same // visible even when a fullscreen window should be covering it.
// monitor (it will be hidden whenever a fullscreen window is visible,
// and shown otherwise)
addChrome: function(actor, params) { addChrome: function(actor, params) {
this._chrome.addActor(actor, params); this._chrome.addActor(actor, params);
}, },
@ -419,8 +346,10 @@ const LayoutManager = new Lang.Class({
// struts or input region to cover specific children. // struts or input region to cover specific children.
// //
// @params can have any of the same values as in addChrome(), // @params can have any of the same values as in addChrome(),
// though some possibilities don't make sense. By default, @actor has // though some possibilities don't make sense (eg, trying to have
// the same params as its chrome ancestor. // a %visibleInFullscreen child of a non-%visibleInFullscreen
// parent). By default, @actor has the same params as its chrome
// ancestor.
trackChrome: function(actor, params) { trackChrome: function(actor, params) {
this._chrome.trackActor(actor, params); this._chrome.trackActor(actor, params);
}, },
@ -626,7 +555,7 @@ const HotCorner = new Lang.Class({
// workspace content. // workspace content.
const defaultParams = { const defaultParams = {
trackFullscreen: false, visibleInFullscreen: false,
affectsStruts: false, affectsStruts: false,
affectsInputRegion: true affectsInputRegion: true
}; };
@ -639,7 +568,6 @@ const Chrome = new Lang.Class({
this._monitors = []; this._monitors = [];
this._inOverview = false; this._inOverview = false;
this._isLocked = false;
this._updateRegionIdle = 0; this._updateRegionIdle = 0;
this._freezeUpdateCount = 0; this._freezeUpdateCount = 0;
@ -654,6 +582,16 @@ const Chrome = new Lang.Class({
global.screen.connect('notify::n-workspaces', global.screen.connect('notify::n-workspaces',
Lang.bind(this, this._queueUpdateRegions)); Lang.bind(this, this._queueUpdateRegions));
this._screenSaverActive = false;
this._screenSaverProxy = new ScreenSaver.ScreenSaverProxy();
this._screenSaverProxy.connectSignal('ActiveChanged', Lang.bind(this, function(proxy, senderName, [isActive]) {
this._onScreenSaverActiveChanged(isActive);
}));
this._screenSaverProxy.GetActiveRemote(Lang.bind(this, function(result, err) {
if (!err)
this._onScreenSaverActiveChanged(result[0]);
}));
this._relayout(); this._relayout();
}, },
@ -662,8 +600,6 @@ const Chrome = new Lang.Class({
Lang.bind(this, this._overviewShowing)); Lang.bind(this, this._overviewShowing));
Main.overview.connect('hidden', Main.overview.connect('hidden',
Lang.bind(this, this._overviewHidden)); Lang.bind(this, this._overviewHidden));
Main.screenShield.connect('lock-status-changed',
Lang.bind(this, this._lockStatusChanged));
}, },
addActor: function(actor, params) { addActor: function(actor, params) {
@ -758,18 +694,19 @@ const Chrome = new Lang.Class({
_updateVisibility: function() { _updateVisibility: function() {
for (let i = 0; i < this._trackedActors.length; i++) { for (let i = 0; i < this._trackedActors.length; i++) {
let actorData = this._trackedActors[i], visible; let actorData = this._trackedActors[i], visible;
if (!actorData.trackFullscreen)
continue;
if (!actorData.isToplevel) if (!actorData.isToplevel)
continue; continue;
if (this._inOverview || this._isLocked) if (this._screenSaverActive)
visible = false;
else if (this._inOverview)
visible = true; visible = true;
else if (this.findMonitorForActor(actorData.actor).inFullscreen) else if (!actorData.visibleInFullscreen &&
this.findMonitorForActor(actorData.actor).inFullscreen)
visible = false; visible = false;
else else
visible = true; visible = true;
actorData.actor.visible = visible; Main.uiGroup.set_skip_paint(actorData.actor, !visible);
} }
}, },
@ -785,12 +722,6 @@ const Chrome = new Lang.Class({
this._queueUpdateRegions(); this._queueUpdateRegions();
}, },
_lockStatusChanged: function(shield, locked) {
this._isLocked = locked;
this._updateVisibility();
this._queueUpdateRegions();
},
_relayout: function() { _relayout: function() {
this._monitors = this._layoutManager.monitors; this._monitors = this._layoutManager.monitors;
this._primaryMonitor = this._layoutManager.primaryMonitor; this._primaryMonitor = this._layoutManager.primaryMonitor;
@ -800,6 +731,12 @@ const Chrome = new Lang.Class({
this._queueUpdateRegions(); this._queueUpdateRegions();
}, },
_onScreenSaverActiveChanged: function(screenSaverActive) {
this._screenSaverActive = screenSaverActive;
this._updateVisibility();
this._queueUpdateRegions();
},
_findMonitorForRect: function(x, y, w, h) { _findMonitorForRect: function(x, y, w, h) {
// First look at what monitor the center of the rectangle is at // First look at what monitor the center of the rectangle is at
let cx = x + w/2; let cx = x + w/2;
@ -915,8 +852,6 @@ const Chrome = new Lang.Class({
for (let i = 0; i < this._monitors.length; i++) for (let i = 0; i < this._monitors.length; i++)
wasInFullscreen[i] = this._monitors[i].inFullscreen; wasInFullscreen[i] = this._monitors[i].inFullscreen;
let primaryWasInFullscreen = this._primaryMonitor.inFullscreen;
this._updateFullscreen(); this._updateFullscreen();
let changed = false; let changed = false;
@ -926,15 +861,10 @@ const Chrome = new Lang.Class({
break; break;
} }
} }
if (changed) { if (changed) {
this._updateVisibility(); this._updateVisibility();
this._queueUpdateRegions(); this._queueUpdateRegions();
} }
if (primaryWasInFullscreen != this._primaryMonitor.inFullscreen) {
this.emit('primary-fullscreen-changed', this._primaryMonitor.inFullscreen);
}
}, },
updateRegions: function() { updateRegions: function() {
@ -1049,5 +979,3 @@ const Chrome = new Lang.Class({
return false; return false;
} }
}); });
Signals.addSignalMethods(Chrome.prototype);

View File

@ -8,8 +8,6 @@ const St = imports.gi.St;
const Params = imports.misc.params; const Params = imports.misc.params;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const DEFAULT_FADE_FACTOR = 0.4;
/** /**
* Lightbox: * Lightbox:
* @container: parent Clutter.Container * @container: parent Clutter.Container
@ -17,8 +15,7 @@ const DEFAULT_FADE_FACTOR = 0.4;
* - inhibitEvents: whether to inhibit events for @container * - inhibitEvents: whether to inhibit events for @container
* - width: shade actor width * - width: shade actor width
* - height: shade actor height * - height: shade actor height
* - fadeInTime: seconds used to fade in * - fadeTime: seconds used to fade in/out
* - fadeOutTime: seconds used to fade out
* *
* Lightbox creates a dark translucent "shade" actor to hide the * Lightbox creates a dark translucent "shade" actor to hide the
* contents of @container, and allows you to specify particular actors * contents of @container, and allows you to specify particular actors
@ -41,16 +38,12 @@ const Lightbox = new Lang.Class({
params = Params.parse(params, { inhibitEvents: false, params = Params.parse(params, { inhibitEvents: false,
width: null, width: null,
height: null, height: null,
fadeInTime: null, fadeTime: null
fadeOutTime: null,
fadeFactor: DEFAULT_FADE_FACTOR
}); });
this._container = container; this._container = container;
this._children = container.get_children(); this._children = container.get_children();
this._fadeInTime = params.fadeInTime; this._fadeTime = params.fadeTime;
this._fadeOutTime = params.fadeOutTime;
this._fadeFactor = params.fadeFactor;
this.actor = new St.Bin({ x: 0, this.actor = new St.Bin({ x: 0,
y: 0, y: 0,
style_class: 'lightbox', style_class: 'lightbox',
@ -59,7 +52,6 @@ const Lightbox = new Lang.Class({
container.add_actor(this.actor); container.add_actor(this.actor);
this.actor.raise_top(); this.actor.raise_top();
this.actor.hide(); this.actor.hide();
this.shown = false;
this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
@ -101,30 +93,24 @@ const Lightbox = new Lang.Class({
}, },
show: function() { show: function() {
if (this._fadeInTime) { if (this._fadeTime) {
this.shown = false;
this.actor.opacity = 0; this.actor.opacity = 0;
Tweener.addTween(this.actor, Tweener.addTween(this.actor,
{ opacity: 255 * this._fadeFactor, { opacity: 255,
time: this._fadeInTime, time: this._fadeTime,
transition: 'easeOutQuad', transition: 'easeOutQuad'
onComplete: Lang.bind(this, function() {
this.shown = true;
})
}); });
} else { } else {
this.actor.opacity = 255 * this._fadeFactor; this.actor.opacity = 255;
this.shown = true;
} }
this.actor.show(); this.actor.show();
}, },
hide: function() { hide: function() {
this.shown = false; if (this._fadeTime) {
if (this._fadeOutTime) {
Tweener.addTween(this.actor, Tweener.addTween(this.actor,
{ opacity: 0, { opacity: 0,
time: this._fadeOutTime, time: this._fadeTime,
transition: 'easeOutQuad', transition: 'easeOutQuad',
onComplete: Lang.bind(this, function() { onComplete: Lang.bind(this, function() {
this.actor.hide(); this.actor.hide();

21
js/ui/link.js Normal file
View File

@ -0,0 +1,21 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Lang = imports.lang;
const Signals = imports.signals;
const St = imports.gi.St;
const Link = new Lang.Class({
Name: 'Link',
_init : function(props) {
let realProps = { reactive: true,
track_hover: true,
style_class: 'shell-link' };
// The user can pass in reactive: false to override the above and get
// a non-reactive link (a link to the current page, perhaps)
Lang.copyProperties(props, realProps);
this.actor = new St.Button(realProps);
}
});
Signals.addSignalMethods(Link.prototype);

View File

@ -12,18 +12,16 @@ const Shell = imports.gi.Shell;
const Signals = imports.signals; const Signals = imports.signals;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
const System = imports.system;
const History = imports.misc.history; const History = imports.misc.history;
const ExtensionSystem = imports.ui.extensionSystem; const ExtensionSystem = imports.ui.extensionSystem;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Link = imports.ui.link;
const ShellEntry = imports.ui.shellEntry; const ShellEntry = imports.ui.shellEntry;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const Main = imports.ui.main; const Main = imports.ui.main;
const JsParse = imports.misc.jsParse; const JsParse = imports.misc.jsParse;
const CHEVRON = '>>> ';
/* Imports...feel free to add here as needed */ /* Imports...feel free to add here as needed */
var commandHeader = 'const Clutter = imports.gi.Clutter; ' + var commandHeader = 'const Clutter = imports.gi.Clutter; ' +
'const GLib = imports.gi.GLib; ' + 'const GLib = imports.gi.GLib; ' +
@ -38,8 +36,9 @@ var commandHeader = 'const Clutter = imports.gi.Clutter; ' +
/* Utility functions...we should probably be able to use these /* Utility functions...we should probably be able to use these
* in the shell core code too. */ * in the shell core code too. */
'const stage = global.stage; ' + 'const stage = global.stage; ' +
'const color = function(pixel) { let c= new Clutter.Color(); c.from_pixel(pixel); return c; }; ' +
/* Special lookingGlass functions */ /* Special lookingGlass functions */
'const it = Main.lookingGlass.getIt(); ' + 'const it = Main.lookingGlass.getIt(); ' +
'const r = Lang.bind(Main.lookingGlass, Main.lookingGlass.getResult); '; 'const r = Lang.bind(Main.lookingGlass, Main.lookingGlass.getResult); ';
const HISTORY_KEY = 'looking-glass-history'; const HISTORY_KEY = 'looking-glass-history';
@ -262,8 +261,9 @@ function objectToString(o) {
const ObjLink = new Lang.Class({ const ObjLink = new Lang.Class({
Name: 'ObjLink', Name: 'ObjLink',
Extends: Link.Link,
_init: function(lookingGlass, o, title) { _init: function(o, title) {
let text; let text;
if (title) if (title)
text = title; text = title;
@ -272,30 +272,24 @@ const ObjLink = new Lang.Class({
text = GLib.markup_escape_text(text, -1); text = GLib.markup_escape_text(text, -1);
this._obj = o; this._obj = o;
this.actor = new St.Button({ reactive: true, this.parent({ label: text });
track_hover: true,
style_class: 'shell-link',
label: text });
this.actor.get_child().single_line_mode = true; this.actor.get_child().single_line_mode = true;
this.actor.connect('clicked', Lang.bind(this, this._onClicked)); this.actor.connect('clicked', Lang.bind(this, this._onClicked));
this._lookingGlass = lookingGlass;
}, },
_onClicked: function (link) { _onClicked: function (link) {
this._lookingGlass.inspectObject(this._obj, this.actor); Main.lookingGlass.inspectObject(this._obj, this.actor);
} }
}); });
const Result = new Lang.Class({ const Result = new Lang.Class({
Name: 'Result', Name: 'Result',
_init: function(lookingGlass, command, o, index) { _init : function(command, o, index) {
this.index = index; this.index = index;
this.o = o; this.o = o;
this.actor = new St.BoxLayout({ vertical: true }); this.actor = new St.BoxLayout({ vertical: true });
this._lookingGlass = lookingGlass;
let cmdTxt = new St.Label({ text: command }); let cmdTxt = new St.Label({ text: command });
cmdTxt.clutter_text.ellipsize = Pango.EllipsizeMode.END; cmdTxt.clutter_text.ellipsize = Pango.EllipsizeMode.END;
@ -305,7 +299,7 @@ const Result = new Lang.Class({
let resultTxt = new St.Label({ text: 'r(' + index + ') = ' }); let resultTxt = new St.Label({ text: 'r(' + index + ') = ' });
resultTxt.clutter_text.ellipsize = Pango.EllipsizeMode.END; resultTxt.clutter_text.ellipsize = Pango.EllipsizeMode.END;
box.add(resultTxt); box.add(resultTxt);
let objLink = new ObjLink(this._lookingGlass, o); let objLink = new ObjLink(o);
box.add(objLink.actor); box.add(objLink.actor);
let line = new Clutter.Rectangle({ name: 'Separator' }); let line = new Clutter.Rectangle({ name: 'Separator' });
let padBin = new St.Bin({ name: 'Separator', x_fill: true, y_fill: true }); let padBin = new St.Bin({ name: 'Separator', x_fill: true, y_fill: true });
@ -317,18 +311,16 @@ const Result = new Lang.Class({
const WindowList = new Lang.Class({ const WindowList = new Lang.Class({
Name: 'WindowList', Name: 'WindowList',
_init: function(lookingGlass) { _init : function () {
this.actor = new St.BoxLayout({ name: 'Windows', vertical: true, style: 'spacing: 8px' }); this.actor = new St.BoxLayout({ name: 'Windows', vertical: true, style: 'spacing: 8px' });
let tracker = Shell.WindowTracker.get_default(); let tracker = Shell.WindowTracker.get_default();
this._updateId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._updateWindowList)); this._updateId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._updateWindowList));
global.display.connect('window-created', Lang.bind(this, this._updateWindowList)); global.display.connect('window-created', Lang.bind(this, this._updateWindowList));
tracker.connect('tracked-windows-changed', Lang.bind(this, this._updateWindowList)); tracker.connect('tracked-windows-changed', Lang.bind(this, this._updateWindowList));
this._lookingGlass = lookingGlass;
}, },
_updateWindowList: function() { _updateWindowList: function() {
this.actor.destroy_all_children(); this.actor.get_children().forEach(function (actor) { actor.destroy(); });
let windows = global.get_window_actors(); let windows = global.get_window_actors();
let tracker = Shell.WindowTracker.get_default(); let tracker = Shell.WindowTracker.get_default();
for (let i = 0; i < windows.length; i++) { for (let i = 0; i < windows.length; i++) {
@ -340,7 +332,7 @@ const WindowList = new Lang.Class({
} }
let box = new St.BoxLayout({ vertical: true }); let box = new St.BoxLayout({ vertical: true });
this.actor.add(box); this.actor.add(box);
let windowLink = new ObjLink(this._lookingGlass, metaWindow, metaWindow.title); let windowLink = new ObjLink(metaWindow, metaWindow.title);
box.add(windowLink.actor, { x_align: St.Align.START, x_fill: false }); box.add(windowLink.actor, { x_align: St.Align.START, x_fill: false });
let propsBox = new St.BoxLayout({ vertical: true, style: 'padding-left: 6px;' }); let propsBox = new St.BoxLayout({ vertical: true, style: 'padding-left: 6px;' });
box.add(propsBox); box.add(propsBox);
@ -351,7 +343,7 @@ const WindowList = new Lang.Class({
let propBox = new St.BoxLayout({ style: 'spacing: 6px; ' }); let propBox = new St.BoxLayout({ style: 'spacing: 6px; ' });
propsBox.add(propBox); propsBox.add(propBox);
propBox.add(new St.Label({ text: 'app: ' }), { y_fill: false }); propBox.add(new St.Label({ text: 'app: ' }), { y_fill: false });
let appLink = new ObjLink(this._lookingGlass, app, app.get_id()); let appLink = new ObjLink(app, app.get_id());
propBox.add(appLink.actor, { y_fill: false }); propBox.add(appLink.actor, { y_fill: false });
propBox.add(icon, { y_fill: false }); propBox.add(icon, { y_fill: false });
} else { } else {
@ -365,7 +357,7 @@ Signals.addSignalMethods(WindowList.prototype);
const ObjInspector = new Lang.Class({ const ObjInspector = new Lang.Class({
Name: 'ObjInspector', Name: 'ObjInspector',
_init: function(lookingGlass) { _init : function () {
this._obj = null; this._obj = null;
this._previousObj = null; this._previousObj = null;
@ -377,8 +369,6 @@ const ObjInspector = new Lang.Class({
style_class: 'lg-dialog', style_class: 'lg-dialog',
vertical: true }); vertical: true });
this.actor.add_actor(this._container); this.actor.add_actor(this._container);
this._lookingGlass = lookingGlass;
}, },
selectObject: function(obj, skipPrevious) { selectObject: function(obj, skipPrevious) {
@ -388,7 +378,7 @@ const ObjInspector = new Lang.Class({
this._previousObj = null; this._previousObj = null;
this._obj = obj; this._obj = obj;
this._container.destroy_all_children(); this._container.get_children().forEach(function (child) { child.destroy(); });
let hbox = new St.BoxLayout({ style_class: 'lg-obj-inspector-title' }); let hbox = new St.BoxLayout({ style_class: 'lg-obj-inspector-title' });
this._container.add_actor(hbox); this._container.add_actor(hbox);
@ -422,7 +412,7 @@ const ObjInspector = new Lang.Class({
let link; let link;
try { try {
let prop = obj[propName]; let prop = obj[propName];
link = new ObjLink(this._lookingGlass, prop).actor; link = new ObjLink(prop).actor;
} catch (e) { } catch (e) {
link = new St.Label({ text: '<error>' }); link = new St.Label({ text: '<error>' });
} }
@ -467,7 +457,7 @@ const ObjInspector = new Lang.Class({
_onInsert: function() { _onInsert: function() {
let obj = this._obj; let obj = this._obj;
this.close(); this.close();
this._lookingGlass.insertObject(obj); Main.lookingGlass.insertObject(obj);
}, },
_onBack: function() { _onBack: function() {
@ -475,36 +465,34 @@ const ObjInspector = new Lang.Class({
} }
}); });
const RedBorderEffect = new Lang.Class({ function addBorderPaintHook(actor) {
Name: 'RedBorderEffect', let signalId = actor.connect_after('paint',
Extends: Clutter.Effect, function () {
let color = new Cogl.Color();
color.init_from_4ub(0xff, 0, 0, 0xc4);
Cogl.set_source_color(color);
vfunc_paint: function() { let geom = actor.get_allocation_geometry();
let actor = this.get_actor(); let width = 2;
actor.continue_paint();
let color = new Cogl.Color(); // clockwise order
color.init_from_4ub(0xff, 0, 0, 0xc4); Cogl.rectangle(0, 0, geom.width, width);
Cogl.set_source_color(color); Cogl.rectangle(geom.width - width, width,
geom.width, geom.height);
Cogl.rectangle(0, geom.height,
geom.width - width, geom.height - width);
Cogl.rectangle(0, geom.height - width,
width, width);
});
let geom = actor.get_allocation_geometry(); actor.queue_redraw();
let width = 2; return signalId;
}
// clockwise order
Cogl.rectangle(0, 0, geom.width, width);
Cogl.rectangle(geom.width - width, width,
geom.width, geom.height);
Cogl.rectangle(0, geom.height,
geom.width - width, geom.height - width);
Cogl.rectangle(0, geom.height - width,
width, width);
},
});
const Inspector = new Lang.Class({ const Inspector = new Lang.Class({
Name: 'Inspector', Name: 'Inspector',
_init: function(lookingGlass) { _init: function() {
let container = new Shell.GenericContainer({ width: 0, let container = new Shell.GenericContainer({ width: 0,
height: 0 }); height: 0 });
container.connect('allocate', Lang.bind(this, this._allocate)); container.connect('allocate', Lang.bind(this, this._allocate));
@ -518,6 +506,9 @@ const Inspector = new Lang.Class({
this._displayText = new St.Label(); this._displayText = new St.Label();
eventHandler.add(this._displayText, { expand: true }); eventHandler.add(this._displayText, { expand: true });
this._borderPaintTarget = null;
this._borderPaintId = null;
eventHandler.connect('destroy', Lang.bind(this, this._onDestroy));
eventHandler.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent)); eventHandler.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent));
eventHandler.connect('button-press-event', Lang.bind(this, this._onButtonPressEvent)); eventHandler.connect('button-press-event', Lang.bind(this, this._onButtonPressEvent));
eventHandler.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); eventHandler.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
@ -532,8 +523,6 @@ const Inspector = new Lang.Class({
// out, or move the pointer outside of _pointerTarget. // out, or move the pointer outside of _pointerTarget.
this._target = null; this._target = null;
this._pointerTarget = null; this._pointerTarget = null;
this._lookingGlass = lookingGlass;
}, },
_allocate: function(actor, box, flags) { _allocate: function(actor, box, flags) {
@ -554,13 +543,18 @@ const Inspector = new Lang.Class({
}, },
_close: function() { _close: function() {
Clutter.ungrab_pointer(); Clutter.ungrab_pointer(this._eventHandler);
Clutter.ungrab_keyboard(); Clutter.ungrab_keyboard(this._eventHandler);
this._eventHandler.destroy(); this._eventHandler.destroy();
this._eventHandler = null; this._eventHandler = null;
this.emit('closed'); this.emit('closed');
}, },
_onDestroy: function() {
if (this._borderPaintTarget != null)
this._borderPaintTarget.disconnect(this._borderPaintId);
},
_onKeyPressEvent: function (actor, event) { _onKeyPressEvent: function (actor, event) {
if (event.get_key_symbol() == Clutter.Escape) if (event.get_key_symbol() == Clutter.Escape)
this._close(); this._close();
@ -629,7 +623,12 @@ const Inspector = new Lang.Class({
this._displayText.text = ''; this._displayText.text = '';
this._displayText.text = position + ' ' + this._target; this._displayText.text = position + ' ' + this._target;
this._lookingGlass.setBorderPaintTarget(this._target); if (this._borderPaintTarget != this._target) {
if (this._borderPaintTarget != null)
this._borderPaintTarget.disconnect(this._borderPaintId);
this._borderPaintTarget = this._target;
this._borderPaintId = addBorderPaintHook(this._target);
}
} }
}); });
@ -663,7 +662,7 @@ const Memory = new Lang.Class({
this._gcbutton = new St.Button({ label: 'Full GC', this._gcbutton = new St.Button({ label: 'Full GC',
style_class: 'lg-obj-inspector-button' }); style_class: 'lg-obj-inspector-button' });
this._gcbutton.connect('clicked', Lang.bind(this, function () { System.gc(); this._renderText(); })); this._gcbutton.connect('clicked', Lang.bind(this, function () { global.gc(); this._renderText(); }));
this.actor.add(this._gcbutton, { x_align: St.Align.START, this.actor.add(this._gcbutton, { x_align: St.Align.START,
x_fill: false }); x_fill: false });
@ -724,13 +723,13 @@ const Extensions = new Lang.Class({
let extension = actor._extension; let extension = actor._extension;
let uri = extension.dir.get_uri(); let uri = extension.dir.get_uri();
Gio.app_info_launch_default_for_uri(uri, global.create_app_launch_context()); Gio.app_info_launch_default_for_uri(uri, global.create_app_launch_context());
this._lookingGlass.close(); Main.lookingGlass.close();
}, },
_onWebPage: function (actor) { _onWebPage: function (actor) {
let extension = actor._extension; let extension = actor._extension;
Gio.app_info_launch_default_for_uri(extension.metadata.url, global.create_app_launch_context()); Gio.app_info_launch_default_for_uri(extension.metadata.url, global.create_app_launch_context());
this._lookingGlass.close(); Main.lookingGlass.close();
}, },
_onViewErrors: function (actor) { _onViewErrors: function (actor) {
@ -794,33 +793,24 @@ const Extensions = new Lang.Class({
text: this._stateToString(extension.state) }); text: this._stateToString(extension.state) });
metaBox.add(state); metaBox.add(state);
let viewsource = new St.Button({ reactive: true, let viewsource = new Link.Link({ label: _("View Source") });
track_hover: true, viewsource.actor._extension = extension;
style_class: 'shell-link', viewsource.actor.connect('clicked', Lang.bind(this, this._onViewSource));
label: _("View Source") }); metaBox.add(viewsource.actor);
viewsource._extension = extension;
viewsource.connect('clicked', Lang.bind(this, this._onViewSource));
metaBox.add(viewsource);
if (extension.metadata.url) { if (extension.metadata.url) {
let webpage = new St.Button({ reactive: true, let webpage = new Link.Link({ label: _("Web Page") });
track_hover: true, webpage.actor._extension = extension;
style_class: 'shell-link', webpage.actor.connect('clicked', Lang.bind(this, this._onWebPage));
label: _("Web Page") }); metaBox.add(webpage.actor);
webpage._extension = extension;
webpage.connect('clicked', Lang.bind(this, this._onWebPage));
metaBox.add(webpage);
} }
let viewerrors = new St.Button({ reactive: true, let viewerrors = new Link.Link({ label: _("Show Errors") });
track_hover: true, viewerrors.actor._extension = extension;
style_class: 'shell-link', viewerrors.actor._parentBox = box;
label: _("Show Errors") }); viewerrors.actor._isShowing = false;
viewerrors._extension = extension; viewerrors.actor.connect('clicked', Lang.bind(this, this._onViewErrors));
viewerrors._parentBox = box; metaBox.add(viewerrors.actor);
viewerrors._isShowing = false;
viewerrors.connect('clicked', Lang.bind(this, this._onViewErrors));
metaBox.add(viewerrors);
return box; return box;
} }
@ -831,7 +821,8 @@ const LookingGlass = new Lang.Class({
_init : function() { _init : function() {
this._borderPaintTarget = null; this._borderPaintTarget = null;
this._redBorderEffect = new RedBorderEffect(); this._borderPaintId = 0;
this._borderDestroyId = 0;
this._open = false; this._open = false;
@ -861,18 +852,19 @@ const LookingGlass = new Lang.Class({
Main.layoutManager.keyboardBox.connect('allocation-changed', Main.layoutManager.keyboardBox.connect('allocation-changed',
Lang.bind(this, this._queueResize)); Lang.bind(this, this._queueResize));
this._objInspector = new ObjInspector(this); this._objInspector = new ObjInspector();
Main.uiGroup.add_actor(this._objInspector.actor); Main.uiGroup.add_actor(this._objInspector.actor);
this._objInspector.actor.hide(); this._objInspector.actor.hide();
let toolbar = new St.BoxLayout({ name: 'Toolbar' }); let toolbar = new St.BoxLayout({ name: 'Toolbar' });
this.actor.add_actor(toolbar); this.actor.add_actor(toolbar);
let inspectIcon = new St.Icon({ icon_name: 'gtk-color-picker', let inspectIcon = new St.Icon({ icon_name: 'gtk-color-picker',
icon_type: St.IconType.FULLCOLOR,
icon_size: 24 }); icon_size: 24 });
toolbar.add_actor(inspectIcon); toolbar.add_actor(inspectIcon);
inspectIcon.reactive = true; inspectIcon.reactive = true;
inspectIcon.connect('button-press-event', Lang.bind(this, function () { inspectIcon.connect('button-press-event', Lang.bind(this, function () {
let inspector = new Inspector(this); let inspector = new Inspector();
inspector.connect('target', Lang.bind(this, function(i, target, stageX, stageY) { inspector.connect('target', Lang.bind(this, function(i, target, stageX, stageY) {
this._pushResult('<inspect x:' + stageX + ' y:' + stageY + '>', this._pushResult('<inspect x:' + stageX + ' y:' + stageY + '>',
target); target);
@ -902,14 +894,18 @@ const LookingGlass = new Lang.Class({
this._entryArea = new St.BoxLayout({ name: 'EntryArea' }); this._entryArea = new St.BoxLayout({ name: 'EntryArea' });
this._evalBox.add_actor(this._entryArea); this._evalBox.add_actor(this._entryArea);
let label = new St.Label({ text: CHEVRON }); let label = new St.Label({ text: 'js>>> ' });
this._entryArea.add(label); this._entryArea.add(label);
this._entry = new St.Entry({ can_focus: true }); this._entry = new St.Entry({ can_focus: true });
ShellEntry.addContextMenu(this._entry); ShellEntry.addContextMenu(this._entry);
this._entryArea.add(this._entry, { expand: true }); this._entryArea.add(this._entry, { expand: true });
this._windowList = new WindowList(this); this._windowList = new WindowList();
this._windowList.connect('selected', Lang.bind(this, function(list, window) {
notebook.selectIndex(0);
this._pushResult('<window selection>', window);
}));
notebook.appendPage('Windows', this._windowList.actor); notebook.appendPage('Windows', this._windowList.actor);
this._memory = new Memory(); this._memory = new Memory();
@ -963,22 +959,23 @@ const LookingGlass = new Lang.Class({
+ 'font-family: "' + fontDesc.get_family() + '";'; + 'font-family: "' + fontDesc.get_family() + '";';
}, },
setBorderPaintTarget: function(obj) {
if (this._borderPaintTarget != null)
this._borderPaintTarget.remove_effect(this._redBorderEffect);
this._borderPaintTarget = obj;
if (this._borderPaintTarget != null)
this._borderPaintTarget.add_effect(this._redBorderEffect);
},
_pushResult: function(command, obj) { _pushResult: function(command, obj) {
let index = this._results.length + this._offset; let index = this._results.length + this._offset;
let result = new Result(this, CHEVRON + command, obj, index); let result = new Result('>>> ' + command, obj, index);
this._results.push(result); this._results.push(result);
this._resultsArea.add(result.actor); this._resultsArea.add(result.actor);
if (obj instanceof Clutter.Actor) if (this._borderPaintTarget != null) {
this.setBorderPaintTarget(obj); this._borderPaintTarget.disconnect(this._borderPaintId);
this._borderPaintTarget = null;
}
if (obj instanceof Clutter.Actor) {
this._borderPaintTarget = obj;
this._borderPaintId = addBorderPaintHook(obj);
this._borderDestroyId = obj.connect('destroy', Lang.bind(this, function () {
this._borderDestroyId = 0;
this._borderPaintTarget = null;
}));
}
let children = this._resultsArea.get_children(); let children = this._resultsArea.get_children();
if (children.length > this._maxItems) { if (children.length > this._maxItems) {
this._results.shift(); this._results.shift();
@ -1159,7 +1156,11 @@ const LookingGlass = new Lang.Class({
this._open = false; this._open = false;
Tweener.removeTweens(this.actor); Tweener.removeTweens(this.actor);
this.setBorderPaintTarget(null); if (this._borderPaintTarget != null) {
this._borderPaintTarget.disconnect(this._borderPaintId);
this._borderPaintTarget.disconnect(this._borderDestroyId);
this._borderPaintTarget = null;
}
Main.popModal(this._entry); Main.popModal(this._entry);

View File

@ -12,11 +12,10 @@ const Signals = imports.signals;
const Main = imports.ui.main; const Main = imports.ui.main;
const MagnifierDBus = imports.ui.magnifierDBus; const MagnifierDBus = imports.ui.magnifierDBus;
const Params = imports.misc.params; const Params = imports.misc.params;
const PointerWatcher = imports.ui.pointerWatcher;
const MOUSE_POLL_FREQUENCY = 50; const MOUSE_POLL_FREQUENCY = 50;
const CROSSHAIRS_CLIP_SIZE = [100, 100]; const CROSSHAIRS_CLIP_SIZE = [100, 100];
const NO_CHANGE = 0.0;
// Settings // Settings
const APPLICATIONS_SCHEMA = 'org.gnome.desktop.a11y.applications'; const APPLICATIONS_SCHEMA = 'org.gnome.desktop.a11y.applications';
@ -25,14 +24,6 @@ const SHOW_KEY = 'screen-magnifier-enabled';
const MAGNIFIER_SCHEMA = 'org.gnome.desktop.a11y.magnifier'; const MAGNIFIER_SCHEMA = 'org.gnome.desktop.a11y.magnifier';
const SCREEN_POSITION_KEY = 'screen-position'; const SCREEN_POSITION_KEY = 'screen-position';
const MAG_FACTOR_KEY = 'mag-factor'; const MAG_FACTOR_KEY = 'mag-factor';
const INVERT_LIGHTNESS_KEY = 'invert-lightness';
const COLOR_SATURATION_KEY = 'color-saturation';
const BRIGHT_RED_KEY = 'brightness-red';
const BRIGHT_GREEN_KEY = 'brightness-green';
const BRIGHT_BLUE_KEY = 'brightness-blue';
const CONTRAST_RED_KEY = 'contrast-red';
const CONTRAST_GREEN_KEY = 'contrast-green';
const CONTRAST_BLUE_KEY = 'contrast-blue';
const LENS_MODE_KEY = 'lens-mode'; const LENS_MODE_KEY = 'lens-mode';
const CLAMP_MODE_KEY = 'scroll-at-edges'; const CLAMP_MODE_KEY = 'scroll-at-edges';
const MOUSE_TRACKING_KEY = 'mouse-tracking'; const MOUSE_TRACKING_KEY = 'mouse-tracking';
@ -56,7 +47,7 @@ const Magnifier = new Lang.Class({
let xfixesCursor = Shell.XFixesCursor.get_for_stage(global.stage); let xfixesCursor = Shell.XFixesCursor.get_for_stage(global.stage);
this._mouseSprite = new Clutter.Texture(); this._mouseSprite = new Clutter.Texture();
xfixesCursor.update_texture_image(this._mouseSprite); xfixesCursor.update_texture_image(this._mouseSprite);
this._cursorRoot = new Clutter.Actor(); this._cursorRoot = new Clutter.Group();
this._cursorRoot.add_actor(this._mouseSprite); this._cursorRoot.add_actor(this._mouseSprite);
// Create the first ZoomRegion and initialize it according to the // Create the first ZoomRegion and initialize it according to the
@ -136,8 +127,11 @@ const Magnifier = new Lang.Class({
* Turn on mouse tracking, if not already doing so. * Turn on mouse tracking, if not already doing so.
*/ */
startTrackingMouse: function() { startTrackingMouse: function() {
if (!this._pointerWatch) if (!this._mouseTrackingId)
this._pointerWatch = PointerWatcher.getPointerWatcher().addWatch(MOUSE_POLL_FREQUENCY, Lang.bind(this, this.scrollToMousePos)); this._mouseTrackingId = Mainloop.timeout_add(
MOUSE_POLL_FREQUENCY,
Lang.bind(this, this.scrollToMousePos)
);
}, },
/** /**
@ -145,10 +139,10 @@ const Magnifier = new Lang.Class({
* Turn off mouse tracking, if not already doing so. * Turn off mouse tracking, if not already doing so.
*/ */
stopTrackingMouse: function() { stopTrackingMouse: function() {
if (this._pointerWatch) if (this._mouseTrackingId)
this._pointerWatch.remove(); Mainloop.source_remove(this._mouseTrackingId);
this._pointerWatch = null; this._mouseTrackingId = null;
}, },
/** /**
@ -300,7 +294,8 @@ const Magnifier = new Lang.Class({
*/ */
setCrosshairsColor: function(color) { setCrosshairsColor: function(color) {
if (this._crossHairs) { if (this._crossHairs) {
let [res, clutterColor] = Clutter.Color.from_string(color); let clutterColor = new Clutter.Color();
clutterColor.from_string(color);
this._crossHairs.setColor(clutterColor); this._crossHairs.setColor(clutterColor);
} }
}, },
@ -448,25 +443,6 @@ const Magnifier = new Lang.Class({
aPref = this._settings.get_enum(MOUSE_TRACKING_KEY); aPref = this._settings.get_enum(MOUSE_TRACKING_KEY);
if (aPref) if (aPref)
zoomRegion.setMouseTrackingMode(aPref); zoomRegion.setMouseTrackingMode(aPref);
aPref = this._settings.get_boolean(INVERT_LIGHTNESS_KEY);
if (aPref)
zoomRegion.setInvertLightness(aPref);
aPref = this._settings.get_double(COLOR_SATURATION_KEY);
if (aPref)
zoomRegion.setColorSaturation(aPref);
let bc = {};
bc.r = this._settings.get_double(BRIGHT_RED_KEY);
bc.g = this._settings.get_double(BRIGHT_GREEN_KEY);
bc.b = this._settings.get_double(BRIGHT_BLUE_KEY);
zoomRegion.setBrightness(bc);
bc.r = this._settings.get_double(CONTRAST_RED_KEY);
bc.g = this._settings.get_double(CONTRAST_GREEN_KEY);
bc.b = this._settings.get_double(CONTRAST_BLUE_KEY);
zoomRegion.setContrast(bc);
} }
let showCrosshairs = this._settings.get_boolean(SHOW_CROSS_HAIRS_KEY); let showCrosshairs = this._settings.get_boolean(SHOW_CROSS_HAIRS_KEY);
@ -489,25 +465,6 @@ const Magnifier = new Lang.Class({
this._settings.connect('changed::' + MOUSE_TRACKING_KEY, this._settings.connect('changed::' + MOUSE_TRACKING_KEY,
Lang.bind(this, this._updateMouseTrackingMode)); Lang.bind(this, this._updateMouseTrackingMode));
this._settings.connect('changed::' + INVERT_LIGHTNESS_KEY,
Lang.bind(this, this._updateInvertLightness));
this._settings.connect('changed::' + COLOR_SATURATION_KEY,
Lang.bind(this, this._updateColorSaturation));
this._settings.connect('changed::' + BRIGHT_RED_KEY,
Lang.bind(this, this._updateBrightness));
this._settings.connect('changed::' + BRIGHT_GREEN_KEY,
Lang.bind(this, this._updateBrightness));
this._settings.connect('changed::' + BRIGHT_BLUE_KEY,
Lang.bind(this, this._updateBrightness));
this._settings.connect('changed::' + CONTRAST_RED_KEY,
Lang.bind(this, this._updateContrast));
this._settings.connect('changed::' + CONTRAST_GREEN_KEY,
Lang.bind(this, this._updateContrast));
this._settings.connect('changed::' + CONTRAST_BLUE_KEY,
Lang.bind(this, this._updateContrast));
this._settings.connect('changed::' + SHOW_CROSS_HAIRS_KEY, this._settings.connect('changed::' + SHOW_CROSS_HAIRS_KEY,
Lang.bind(this, function() { Lang.bind(this, function() {
this.setCrosshairsVisible(this._settings.get_boolean(SHOW_CROSS_HAIRS_KEY)); this.setCrosshairsVisible(this._settings.get_boolean(SHOW_CROSS_HAIRS_KEY));
@ -583,47 +540,7 @@ const Magnifier = new Lang.Class({
this._settings.get_enum(MOUSE_TRACKING_KEY) this._settings.get_enum(MOUSE_TRACKING_KEY)
); );
} }
}, }
_updateInvertLightness: function() {
// Applies only to the first zoom region.
if (this._zoomRegions.length) {
this._zoomRegions[0].setInvertLightness(
this._settings.get_boolean(INVERT_LIGHTNESS_KEY)
);
}
},
_updateColorSaturation: function() {
// Applies only to the first zoom region.
if (this._zoomRegions.length) {
this._zoomRegions[0].setColorSaturation(
this._settings.get_double(COLOR_SATURATION_KEY)
);
}
},
_updateBrightness: function() {
// Applies only to the first zoom region.
if (this._zoomRegions.length) {
let brightness = {};
brightness.r = this._settings.get_double(BRIGHT_RED_KEY);
brightness.g = this._settings.get_double(BRIGHT_GREEN_KEY);
brightness.b = this._settings.get_double(BRIGHT_BLUE_KEY);
this._zoomRegions[0].setBrightness(brightness);
}
},
_updateContrast: function() {
// Applies only to the first zoom region.
if (this._zoomRegions.length) {
let contrast = {};
contrast.r = this._settings.get_double(CONTRAST_RED_KEY);
contrast.g = this._settings.get_double(CONTRAST_GREEN_KEY);
contrast.b = this._settings.get_double(CONTRAST_BLUE_KEY);
this._zoomRegions[0].setContrast(contrast);
}
},
}); });
Signals.addSignalMethods(Magnifier.prototype); Signals.addSignalMethods(Magnifier.prototype);
@ -637,10 +554,6 @@ const ZoomRegion = new Lang.Class({
this._clampScrollingAtEdges = false; this._clampScrollingAtEdges = false;
this._lensMode = false; this._lensMode = false;
this._screenPosition = GDesktopEnums.MagnifierScreenPosition.FULL_SCREEN; this._screenPosition = GDesktopEnums.MagnifierScreenPosition.FULL_SCREEN;
this._invertLightness = false;
this._colorSaturation = 1.0;
this._brightness = { r: NO_CHANGE, g: NO_CHANGE, b: NO_CHANGE };
this._contrast = { r: NO_CHANGE, g: NO_CHANGE, b: NO_CHANGE };
this._magView = null; this._magView = null;
this._background = null; this._background = null;
@ -966,107 +879,6 @@ const ZoomRegion = new Lang.Class({
} }
}, },
/**
* setInvertLightness:
* Set whether to invert the lightness of the magnified view.
* @flag Boolean to either invert brightness (true), or not (false).
*/
setInvertLightness: function(flag) {
this._invertLightness = flag;
if (this._magShaderEffects)
this._magShaderEffects.setInvertLightness(this._invertLightness);
},
/**
* getInvertLightness:
* Retrieve whether the lightness is inverted.
* @return Boolean indicating inversion (true), or not (false).
*/
getInvertLightness: function() {
return this._invertLightness;
},
/**
* setColorSaturation:
* Set the color saturation of the magnified view.
* @sauration A value from 0.0 to 1.0 that defines the color
* saturation, with 0.0 defining no color (grayscale),
* and 1.0 defining full color.
*/
setColorSaturation: function(saturation) {
this._colorSaturation = saturation;
if (this._magShaderEffects)
this._magShaderEffects.setColorSaturation(this._colorSaturation);
},
/**
* getColorSaturation:
* Retrieve the color saturation of the magnified view.
*/
getColorSaturation: function() {
return this._colorSaturation;
},
/**
* setBrightness:
* Alter the brightness of the magnified view.
* @brightness Object containing the contrast for the red, green,
* and blue channels. Values of 0.0 represent "standard"
* brightness (no change), whereas values less or greater than
* 0.0 indicate decreased or incresaed brightness, respectively.
*/
setBrightness: function(brightness) {
this._brightness.r = brightness.r;
this._brightness.g = brightness.g;
this._brightness.b = brightness.b;
if (this._magShaderEffects)
this._magShaderEffects.setBrightness(this._brightness);
},
/**
* getBrightness:
* Retrive the current brightness of the Zoom Region.
* @return Object containing the brightness change for the red, green,
* and blue channels.
*/
getBrightness: function() {
let brightness = {};
brightness.r = this._brightness.r;
brightness.g = this._brightness.g;
brightness.b = this._brightness.b;
return brightness;
},
/**
* setContrast:
* Alter the contrast of the magnified view.
* @contrast Object containing the contrast for the red, green,
* and blue channels. Values of 0.0 represent "standard"
* contrast (no change), whereas values less or greater than
* 0.0 indicate decreased or incresaed contrast, respectively.
*/
setContrast: function(contrast) {
this._contrast.r = contrast.r;
this._contrast.g = contrast.g;
this._contrast.b = contrast.b;
if (this._magShaderEffects)
this._magShaderEffects.setContrast(this._contrast);
},
/**
* getContrast:
* Retreive the contrast of the magnified view.
* @return Object containing the contrast for the red, green,
* and blue channels.
*/
getContrast: function() {
let contrast = {};
contrast.r = this._contrast.r;
contrast.g = this._contrast.g;
contrast.b = this._contrast.b;
return contrast;
},
//// Private methods //// //// Private methods ////
_createActors: function() { _createActors: function() {
@ -1077,21 +889,21 @@ const ZoomRegion = new Lang.Class({
// hide the magnified region from CLUTTER_PICK_ALL // hide the magnified region from CLUTTER_PICK_ALL
Shell.util_set_hidden_from_pick (this._magView, true); Shell.util_set_hidden_from_pick (this._magView, true);
// Add a group to clip the contents of the magnified view. // Append a Clutter.Group to clip the contents of the magnified view.
let mainGroup = new Clutter.Actor({ clip_to_allocation: true }); let mainGroup = new Clutter.Group({ clip_to_allocation: true });
this._magView.set_child(mainGroup); this._magView.set_child(mainGroup);
// Add a background for when the magnified uiGroup is scrolled // Add a background for when the magnified uiGroup is scrolled
// out of view (don't want to see desktop showing through). // out of view (don't want to see desktop showing through).
this._background = new Clutter.Actor({ background_color: Main.DEFAULT_BACKGROUND_COLOR, this._background = new Clutter.Rectangle({ color: Main.DEFAULT_BACKGROUND_COLOR });
width: global.screen_width,
height: global.screen_height });
mainGroup.add_actor(this._background); mainGroup.add_actor(this._background);
// Clone the group that contains all of UI on the screen. This is the // Clone the group that contains all of UI on the screen. This is the
// chrome, the windows, etc. // chrome, the windows, etc.
this._uiGroupClone = new Clutter.Clone({ source: Main.uiGroup }); this._uiGroupClone = new Clutter.Clone({ source: Main.uiGroup });
mainGroup.add_actor(this._uiGroupClone); mainGroup.add_actor(this._uiGroupClone);
Main.uiGroup.set_size(global.screen_width, global.screen_height);
this._background.set_size(global.screen_width, global.screen_height);
// Add either the given mouseSourceActor to the ZoomRegion, or a clone of // Add either the given mouseSourceActor to the ZoomRegion, or a clone of
// it. // it.
@ -1105,13 +917,6 @@ const ZoomRegion = new Lang.Class({
this._crossHairsActor = this._crossHairs.addToZoomRegion(this, this._mouseActor); this._crossHairsActor = this._crossHairs.addToZoomRegion(this, this._mouseActor);
else else
this._crossHairsActor = null; this._crossHairsActor = null;
// Contrast and brightness effects.
this._magShaderEffects = new MagShaderEffects(this._uiGroupClone);
this._magShaderEffects.setColorSaturation(this._colorSaturation);
this._magShaderEffects.setInvertLightness(this._invertLightness);
this._magShaderEffects.setBrightness(this._brightness);
this._magShaderEffects.setContrast(this._contrast);
}, },
_destroyActors: function() { _destroyActors: function() {
@ -1120,8 +925,6 @@ const ZoomRegion = new Lang.Class({
if (this._crossHairs) if (this._crossHairs)
this._crossHairs.removeFromParent(this._crossHairsActor); this._crossHairs.removeFromParent(this._crossHairsActor);
this._magShaderEffects.destroyEffects();
this._magShaderEffects = null;
this._magView.destroy(); this._magView.destroy();
this._magView = null; this._magView = null;
this._background = null; this._background = null;
@ -1325,7 +1128,7 @@ const ZoomRegion = new Lang.Class({
this._mouseActor.set_scale(this._xMagFactor, this._yMagFactor); this._mouseActor.set_scale(this._xMagFactor, this._yMagFactor);
let [x, y] = this._screenToViewPort(0, 0); let [x, y] = this._screenToViewPort(0, 0);
this._uiGroupClone.set_position(Math.round(x), Math.round(y)); this._uiGroupClone.set_position(x, y);
this._updateMousePosition(); this._updateMousePosition();
}, },
@ -1353,6 +1156,7 @@ const ZoomRegion = new Lang.Class({
if (!this.isActive()) if (!this.isActive())
return; return;
Main.uiGroup.set_size(global.screen_width, global.screen_height);
this._background.set_size(global.screen_width, global.screen_height); this._background.set_size(global.screen_width, global.screen_height);
if (this._screenPosition == GDesktopEnums.MagnifierScreenPosition.NONE) if (this._screenPosition == GDesktopEnums.MagnifierScreenPosition.NONE)
@ -1376,15 +1180,15 @@ const Crosshairs = new Lang.Class({
let groupWidth = global.screen_width * 3; let groupWidth = global.screen_width * 3;
let groupHeight = global.screen_height * 3; let groupHeight = global.screen_height * 3;
this._actor = new Clutter.Actor({ this._actor = new Clutter.Group({
clip_to_allocation: false, clip_to_allocation: false,
width: groupWidth, width: groupWidth,
height: groupHeight height: groupHeight
}); });
this._horizLeftHair = new Clutter.Actor(); this._horizLeftHair = new Clutter.Rectangle();
this._horizRightHair = new Clutter.Actor(); this._horizRightHair = new Clutter.Rectangle();
this._vertTopHair = new Clutter.Actor(); this._vertTopHair = new Clutter.Rectangle();
this._vertBottomHair = new Clutter.Actor(); this._vertBottomHair = new Clutter.Rectangle();
this._actor.add_actor(this._horizLeftHair); this._actor.add_actor(this._horizLeftHair);
this._actor.add_actor(this._horizRightHair); this._actor.add_actor(this._horizRightHair);
this._actor.add_actor(this._vertTopHair); this._actor.add_actor(this._vertTopHair);
@ -1455,10 +1259,10 @@ const Crosshairs = new Lang.Class({
* @clutterColor: The color as a Clutter.Color. * @clutterColor: The color as a Clutter.Color.
*/ */
setColor: function(clutterColor) { setColor: function(clutterColor) {
this._horizLeftHair.background_color = clutterColor; this._horizLeftHair.set_color(clutterColor);
this._horizRightHair.background_color = clutterColor; this._horizRightHair.set_color(clutterColor);
this._vertTopHair.background_color = clutterColor; this._vertTopHair.set_color(clutterColor);
this._vertBottomHair.background_color = clutterColor; this._vertBottomHair.set_color(clutterColor);
}, },
/** /**
@ -1467,7 +1271,9 @@ const Crosshairs = new Lang.Class({
* @color: The color as a Clutter.Color. * @color: The color as a Clutter.Color.
*/ */
getColor: function() { getColor: function() {
return this._horizLeftHair.get_color(); let clutterColor = new Clutter.Color();
this._horizLeftHair.get_color(clutterColor);
return clutterColor;
}, },
/** /**
@ -1627,144 +1433,3 @@ const Crosshairs = new Lang.Class({
this._vertBottomHair.set_position((groupWidth - thickness) / 2, bottom); this._vertBottomHair.set_position((groupWidth - thickness) / 2, bottom);
} }
}); });
const MagShaderEffects = new Lang.Class({
Name: 'MagShaderEffects',
_init: function(uiGroupClone) {
this._inverse = new Shell.InvertLightnessEffect();
this._brightnessContrast = new Clutter.BrightnessContrastEffect();
this._colorDesaturation = new Clutter.DesaturateEffect();
this._inverse.set_enabled(false);
this._brightnessContrast.set_enabled(false);
this._magView = uiGroupClone;
this._magView.add_effect(this._inverse);
this._magView.add_effect(this._brightnessContrast);
this._magView.add_effect(this._colorDesaturation);
},
/**
* destroyEffects:
* Remove contrast and brightness effects from the magnified view, and
* lose the reference to the actor they were applied to. Don't use this
* object after calling this.
*/
destroyEffects: function() {
this._magView.clear_effects();
this._colorDesaturation = null;
this._brightnessContrast = null;
this._inverse = null;
this._magView = null;
},
/**
* setInvertLightness:
* Enable/disable invert lightness effect.
* @invertFlag: Enabled flag.
*/
setInvertLightness: function(invertFlag) {
this._inverse.set_enabled(invertFlag);
},
/**
* getInvertLightness:
* Report whether the inversion effect is enabled.
* @return: Boolean.
*/
getInvertLightness: function() {
return this._inverse.get_enabled();
},
setColorSaturation: function(factor) {
this._colorDesaturation.set_factor(1.0 - factor);
},
getColorSaturation: function() {
return 1.0 - this._colorDesaturation.get_factor();
},
/**
* setBrightness:
* Set the brightness of the magnified view.
* @brightness: Object containing the brightness for the red, green,
* and blue channels. Values of 0.0 represent "standard"
* brightness (no change), whereas values less or greater than
* 0.0 indicate decreased or incresaed brightness,
* respectively.
*/
setBrightness: function(brightness) {
let bRed = brightness.r;
let bGreen = brightness.g;
let bBlue = brightness.b;
this._brightnessContrast.set_brightness_full(bRed, bGreen, bBlue);
// Enable the effect if the brightness OR contrast change are such that
// it modifies the brightness and/or contrast.
let [cRed, cGreen, cBlue] = this._brightnessContrast.get_contrast();
this._brightnessContrast.set_enabled(
(bRed != NO_CHANGE || bGreen != NO_CHANGE || bBlue != NO_CHANGE ||
cRed != NO_CHANGE || cGreen != NO_CHANGE || cBlue != NO_CHANGE)
);
},
/**
* getBrightness:
* Retrieve current brightness of the magnified view.
* @return: Object containing the brightness for the red, green,
* and blue channels. Values of 0.0 represent "standard"
* brightness (no change), whereas values less or greater than
* 0.0 indicate decreased or incresaed brightness, respectively.
*/
getBrightness: function() {
let result = {};
let [bRed, bGreen, bBlue] = this._brightnessContrast.get_brightness();
result.r = bRed;
result.g = bGreen;
result.b = bBlue;
return result;
},
/**
* Set the contrast of the magnified view.
* @contrast: Object containing the contrast for the red, green,
* and blue channels. Values of 0.0 represent "standard"
* contrast (no change), whereas values less or greater than
* 0.0 indicate decreased or incresaed contrast, respectively.
*/
setContrast: function(contrast) {
let cRed = contrast.r;
let cGreen = contrast.g;
let cBlue = contrast.b;
this._brightnessContrast.set_contrast_full(cRed, cGreen, cBlue);
// Enable the effect if the contrast OR brightness change are such that
// it modifies the brightness and/or contrast.
// should be able to use Clutter.color_equal(), but that complains of
// a null first argument.
let [bRed, bGreen, bBlue] = this._brightnessContrast.get_brightness();
this._brightnessContrast.set_enabled(
cRed != NO_CHANGE || cGreen != NO_CHANGE || cBlue != NO_CHANGE ||
bRed != NO_CHANGE || bGreen != NO_CHANGE || bBlue != NO_CHANGE
);
},
/**
* Retrieve current contrast of the magnified view.
* @return: Object containing the contrast for the red, green,
* and blue channels. Values of 0.0 represent "standard"
* contrast (no change), whereas values less or greater than
* 0.0 indicate decreased or incresaed contrast, respectively.
*/
getContrast: function() {
let resutl = {};
let [cRed, cGreen, cBlue] = this._brightnessContrast.get_contrast();
result.r = cRed;
result.g = cGreen;
result.b = cBlue;
return result;
}
});

View File

@ -18,7 +18,6 @@ const PolkitAuthenticationAgent = imports.ui.polkitAuthenticationAgent;
const KeyringPrompt = imports.ui.keyringPrompt; const KeyringPrompt = imports.ui.keyringPrompt;
const Environment = imports.ui.environment; const Environment = imports.ui.environment;
const ExtensionSystem = imports.ui.extensionSystem; const ExtensionSystem = imports.ui.extensionSystem;
const ExtensionDownloader = imports.ui.extensionDownloader;
const Keyboard = imports.ui.keyboard; const Keyboard = imports.ui.keyboard;
const MessageTray = imports.ui.messageTray; const MessageTray = imports.ui.messageTray;
const Overview = imports.ui.overview; const Overview = imports.ui.overview;
@ -30,88 +29,77 @@ const LookingGlass = imports.ui.lookingGlass;
const NetworkAgent = imports.ui.networkAgent; const NetworkAgent = imports.ui.networkAgent;
const NotificationDaemon = imports.ui.notificationDaemon; const NotificationDaemon = imports.ui.notificationDaemon;
const WindowAttentionHandler = imports.ui.windowAttentionHandler; const WindowAttentionHandler = imports.ui.windowAttentionHandler;
const ScreenShield = imports.ui.screenShield;
const Scripting = imports.ui.scripting; const Scripting = imports.ui.scripting;
const SessionMode = imports.ui.sessionMode;
const ShellDBus = imports.ui.shellDBus; const ShellDBus = imports.ui.shellDBus;
const ShellMountOperation = imports.ui.shellMountOperation;
const TelepathyClient = imports.ui.telepathyClient; const TelepathyClient = imports.ui.telepathyClient;
const UnlockDialog = imports.ui.unlockDialog;
const WindowManager = imports.ui.windowManager; const WindowManager = imports.ui.windowManager;
const Magnifier = imports.ui.magnifier; const Magnifier = imports.ui.magnifier;
const XdndHandler = imports.ui.xdndHandler; const XdndHandler = imports.ui.xdndHandler;
const StatusIconDispatcher = imports.ui.statusIconDispatcher;
const Util = imports.misc.util; const Util = imports.misc.util;
const OVERRIDES_SCHEMA = 'org.gnome.shell.overrides'; const OVERRIDES_SCHEMA = 'org.gnome.shell.overrides';
const DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2266bbff); const DEFAULT_BACKGROUND_COLOR = new Clutter.Color();
DEFAULT_BACKGROUND_COLOR.from_pixel(0x2266bbff);
let automountManager = null; let automountManager = null;
let autorunManager = null; let autorunManager = null;
let panel = null; let panel = null;
let hotCorners = [];
let placesManager = null;
let overview = null; let overview = null;
let runDialog = null; let runDialog = null;
let lookingGlass = null; let lookingGlass = null;
let wm = null; let wm = null;
let messageTray = null; let messageTray = null;
let screenShield = null;
let notificationDaemon = null; let notificationDaemon = null;
let windowAttentionHandler = null; let windowAttentionHandler = null;
let telepathyClient = null; let telepathyClient = null;
let ctrlAltTabManager = null; let ctrlAltTabManager = null;
let recorder = null; let recorder = null;
let sessionMode = null;
let shellDBusService = null; let shellDBusService = null;
let shellMountOpDBusService = null;
let screenSaverDBus = null;
let modalCount = 0; let modalCount = 0;
let modalActorFocusStack = []; let modalActorFocusStack = [];
let uiGroup = null; let uiGroup = null;
let magnifier = null; let magnifier = null;
let xdndHandler = null; let xdndHandler = null;
let statusIconDispatcher = null;
let keyboard = null; let keyboard = null;
let layoutManager = null; let layoutManager = null;
let networkAgent = null; let networkAgent = null;
let _startDate; let _startDate;
let _defaultCssStylesheet = null; let _defaultCssStylesheet = null;
let _cssStylesheet = null; let _cssStylesheet = null;
let _gdmCssStylesheet = null;
let _overridesSettings = null; let _overridesSettings = null;
let background = null; let background = null;
function createUserSession() { function _createUserSession() {
// Load the calendar server. Note that we are careful about
// not loading any events until the user presses the clock
global.launch_calendar_server();
placesManager = new PlaceDisplay.PlacesManager();
telepathyClient = new TelepathyClient.Client(); telepathyClient = new TelepathyClient.Client();
automountManager = new AutomountManager.AutomountManager(); automountManager = new AutomountManager.AutomountManager();
autorunManager = new AutorunManager.AutorunManager(); autorunManager = new AutorunManager.AutorunManager();
networkAgent = new NetworkAgent.NetworkAgent(); networkAgent = new NetworkAgent.NetworkAgent();
_initRecorder();
} }
function createGDMSession() { function _createGDMSession() {
screenShield.showDialog();
}
function createGDMLoginDialog(parentActor) {
// We do this this here instead of at the top to prevent GDM // We do this this here instead of at the top to prevent GDM
// related code from getting loaded in normal user sessions // related code from getting loaded in normal user sessions
const LoginDialog = imports.gdm.loginDialog; const LoginDialog = imports.gdm.loginDialog;
let loginDialog = new LoginDialog.LoginDialog(parentActor); let loginDialog = new LoginDialog.LoginDialog();
return [loginDialog, true]; loginDialog.connect('loaded', function() {
} loginDialog.open();
});
function createSessionUnlockDialog(parentActor) {
let dialog = new UnlockDialog.UnlockDialog(parentActor);
return [dialog, false];
}
function createInitialSetupSession() {
networkAgent = new NetworkAgent.NetworkAgent();
} }
function _initRecorder() { function _initRecorder() {
let recorderSettings = new Gio.Settings({ schema: 'org.gnome.shell.recorder' }); let recorderSettings = new Gio.Settings({ schema: 'org.gnome.shell.recorder' });
let desktopLockdownSettings = new Gio.Settings({ schema: 'org.gnome.desktop.lockdown' });
let bindingSettings = new Gio.Settings({ schema: 'org.gnome.shell.keybindings' }); let bindingSettings = new Gio.Settings({ schema: 'org.gnome.shell.keybindings' });
global.display.add_keybinding('toggle-recording', global.display.add_keybinding('toggle-recording',
@ -124,7 +112,7 @@ function _initRecorder() {
if (recorder.is_recording()) { if (recorder.is_recording()) {
recorder.close(); recorder.close();
Meta.enable_unredirect_for_screen(global.screen); Meta.enable_unredirect_for_screen(global.screen);
} else if (!desktopLockdownSettings.get_boolean('disable-save-to-disk')) { } else {
// read the parameters from GSettings always in case they have changed // read the parameters from GSettings always in case they have changed
recorder.set_framerate(recorderSettings.get_int('framerate')); recorder.set_framerate(recorderSettings.get_int('framerate'));
/* Translators: this is a filename used for screencast recording */ /* Translators: this is a filename used for screencast recording */
@ -143,6 +131,26 @@ function _initRecorder() {
}); });
} }
function _initUserSession() {
_initRecorder();
global.screen.override_workspace_layout(Meta.ScreenCorner.TOPLEFT, false, -1, 1);
ExtensionSystem.init();
ExtensionSystem.loadExtensions();
Meta.keybindings_set_custom_handler('panel-run-dialog', function() {
getRunDialog().open();
});
Meta.keybindings_set_custom_handler('panel-main-menu', function () {
overview.toggle();
});
global.display.connect('overlay-key', Lang.bind(overview, overview.toggle));
}
function start() { function start() {
// These are here so we don't break compatibility. // These are here so we don't break compatibility.
global.logError = window.log; global.logError = window.log;
@ -153,9 +161,7 @@ function start() {
Gio.DesktopAppInfo.set_desktop_env('GNOME'); Gio.DesktopAppInfo.set_desktop_env('GNOME');
sessionMode = new SessionMode.SessionMode();
shellDBusService = new ShellDBus.GnomeShell(); shellDBusService = new ShellDBus.GnomeShell();
shellMountOpDBusService = new ShellMountOperation.GnomeShellMountOpHandler();
// Ensure ShellWindowTracker and ShellAppUsage are initialized; this will // Ensure ShellWindowTracker and ShellAppUsage are initialized; this will
// also initialize ShellAppSystem first. ShellAppSystem // also initialize ShellAppSystem first. ShellAppSystem
@ -177,6 +183,7 @@ function start() {
global.stage.no_clear_hint = true; global.stage.no_clear_hint = true;
_defaultCssStylesheet = global.datadir + '/theme/gnome-shell.css'; _defaultCssStylesheet = global.datadir + '/theme/gnome-shell.css';
_gdmCssStylesheet = global.datadir + '/theme/gdm.css';
loadTheme(); loadTheme();
// Set up stage hierarchy to group all UI actors under one container. // Set up stage hierarchy to group all UI actors under one container.
@ -204,10 +211,10 @@ function start() {
layoutManager = new Layout.LayoutManager(); layoutManager = new Layout.LayoutManager();
xdndHandler = new XdndHandler.XdndHandler(); xdndHandler = new XdndHandler.XdndHandler();
ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager(); ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager();
overview = new Overview.Overview(); // This overview object is just a stub for non-user sessions
overview = new Overview.Overview({ isDummy: global.session_type != Shell.SessionType.USER });
magnifier = new Magnifier.Magnifier(); magnifier = new Magnifier.Magnifier();
screenShield = new ScreenShield.ScreenShield(); statusIconDispatcher = new StatusIconDispatcher.StatusIconDispatcher();
screenSaverDBus = new ShellDBus.ScreenSaverDBus();
panel = new Panel.Panel(); panel = new Panel.Panel();
wm = new WindowManager.WindowManager(); wm = new WindowManager.WindowManager();
messageTray = new MessageTray.MessageTray(); messageTray = new MessageTray.MessageTray();
@ -215,31 +222,20 @@ function start() {
notificationDaemon = new NotificationDaemon.NotificationDaemon(); notificationDaemon = new NotificationDaemon.NotificationDaemon();
windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler(); windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler();
sessionMode.createSession(); if (global.session_type == Shell.SessionType.USER)
_createUserSession();
else if (global.session_type == Shell.SessionType.GDM)
_createGDMSession();
panel.startStatusArea();
panel.init();
layoutManager.init(); layoutManager.init();
keyboard.init(); keyboard.init();
overview.init(); overview.init();
if (sessionMode.hasWorkspaces) if (global.session_type == Shell.SessionType.USER)
global.screen.override_workspace_layout(Meta.ScreenCorner.TOPLEFT, _initUserSession();
false, -1, 1); statusIconDispatcher.start(messageTray.actor);
if (sessionMode.hasRunDialog) {
Meta.keybindings_set_custom_handler('panel-run-dialog', function() {
getRunDialog().open();
});
}
if (sessionMode.hasOverview) {
Meta.keybindings_set_custom_handler('panel-main-menu', function () {
overview.toggle();
});
global.display.connect('overlay-key',
Lang.bind(overview, overview.toggle));
}
// Provide the bus object for gnome-session to // Provide the bus object for gnome-session to
// initiate logouts. // initiate logouts.
@ -274,11 +270,6 @@ function start() {
global.screen.connect('restacked', _windowsRestacked); global.screen.connect('restacked', _windowsRestacked);
_nWorkspacesChanged(); _nWorkspacesChanged();
if (sessionMode.allowExtensions) {
ExtensionDownloader.init();
ExtensionSystem.loadExtensions();
}
} }
let _workspaces = []; let _workspaces = [];
@ -502,6 +493,9 @@ function loadTheme() {
let theme = new St.Theme ({ application_stylesheet: cssStylesheet }); let theme = new St.Theme ({ application_stylesheet: cssStylesheet });
if (global.session_type == Shell.SessionType.GDM)
theme.load_stylesheet(_gdmCssStylesheet);
if (previousTheme) { if (previousTheme) {
let customStylesheets = previousTheme.get_custom_stylesheets(); let customStylesheets = previousTheme.get_custom_stylesheets();
@ -522,7 +516,6 @@ function notify(msg, details) {
messageTray.add(source); messageTray.add(source);
let notification = new MessageTray.Notification(source, msg, details); let notification = new MessageTray.Notification(source, msg, details);
notification.setTransient(true); notification.setTransient(true);
notification.setShowWhenLocked(true);
source.notify(notification); source.notify(notification);
} }
@ -565,11 +558,6 @@ function _globalKeyPressHandler(actor, event) {
if (event.type() != Clutter.EventType.KEY_PRESS) if (event.type() != Clutter.EventType.KEY_PRESS)
return false; return false;
if (!sessionMode.allowKeybindingsWhenModal) {
if (modalCount > (overview.visible ? 1 : 0))
return false;
}
let symbol = event.get_key_symbol(); let symbol = event.get_key_symbol();
let keyCode = event.get_key_code(); let keyCode = event.get_key_code();
let ignoredModifiers = global.display.get_ignored_modifier_mask(); let ignoredModifiers = global.display.get_ignored_modifier_mask();
@ -578,49 +566,48 @@ function _globalKeyPressHandler(actor, event) {
// This relies on the fact that Clutter.ModifierType is the same as Gdk.ModifierType // This relies on the fact that Clutter.ModifierType is the same as Gdk.ModifierType
let action = global.display.get_keybinding_action(keyCode, modifierState); let action = global.display.get_keybinding_action(keyCode, modifierState);
// Other bindings are only available to the user session when the overview is up and
// no modal dialog is present.
if (global.session_type == Shell.SessionType.USER && (!overview.visible || modalCount > 1))
return false;
// This isn't a Meta.KeyBindingAction yet
if (symbol == Clutter.Super_L || symbol == Clutter.Super_R) {
overview.hide();
return true;
}
if (action == Meta.KeyBindingAction.SWITCH_PANELS) { if (action == Meta.KeyBindingAction.SWITCH_PANELS) {
ctrlAltTabManager.popup(modifierState & Clutter.ModifierType.SHIFT_MASK, ctrlAltTabManager.popup(modifierState & Clutter.ModifierType.SHIFT_MASK,
modifierState); modifierState);
return true; return true;
} }
// None of the other bindings are relevant outside of the user's session
if (global.session_type != Shell.SessionType.USER)
return false;
switch (action) { switch (action) {
// left/right would effectively act as synonyms for up/down if we enabled them; // left/right would effectively act as synonyms for up/down if we enabled them;
// but that could be considered confusing; we also disable them in the main view. // but that could be considered confusing; we also disable them in the main view.
// //
// case Meta.KeyBindingAction.WORKSPACE_LEFT: // case Meta.KeyBindingAction.WORKSPACE_LEFT:
// if (!sessionMode.hasWorkspaces)
// return false;
//
// wm.actionMoveWorkspaceLeft(); // wm.actionMoveWorkspaceLeft();
// return true; // return true;
// case Meta.KeyBindingAction.WORKSPACE_RIGHT: // case Meta.KeyBindingAction.WORKSPACE_RIGHT:
// if (!sessionMode.hasWorkspaces)
// return false;
//
// wm.actionMoveWorkspaceRight(); // wm.actionMoveWorkspaceRight();
// return true; // return true;
case Meta.KeyBindingAction.WORKSPACE_UP: case Meta.KeyBindingAction.WORKSPACE_UP:
if (!sessionMode.hasWorkspaces) wm.actionMoveWorkspaceUp();
return false;
wm.actionMoveWorkspace(Meta.MotionDirection.UP);
return true; return true;
case Meta.KeyBindingAction.WORKSPACE_DOWN: case Meta.KeyBindingAction.WORKSPACE_DOWN:
if (!sessionMode.hasWorkspaces) wm.actionMoveWorkspaceDown();
return false;
wm.actionMoveWorkspace(Meta.MotionDirection.DOWN);
return true; return true;
case Meta.KeyBindingAction.PANEL_RUN_DIALOG: case Meta.KeyBindingAction.PANEL_RUN_DIALOG:
case Meta.KeyBindingAction.COMMAND_2: case Meta.KeyBindingAction.COMMAND_2:
if (!sessionMode.hasRunDialog)
return false;
getRunDialog().open(); getRunDialog().open();
return true; return true;
case Meta.KeyBindingAction.PANEL_MAIN_MENU: case Meta.KeyBindingAction.PANEL_MAIN_MENU:
case Meta.KeyBindingAction.OVERLAY_KEY:
overview.hide(); overview.hide();
return true; return true;
} }
@ -636,10 +623,6 @@ function _findModal(actor) {
return -1; return -1;
} }
function isInModalStack(actor) {
return _findModal(actor) != -1;
}
/** /**
* pushModal: * pushModal:
* @actor: #ClutterActor which will be given keyboard focus * @actor: #ClutterActor which will be given keyboard focus
@ -672,7 +655,6 @@ function pushModal(actor, timestamp, options) {
log('pushModal: invocation of begin_modal failed'); log('pushModal: invocation of begin_modal failed');
return false; return false;
} }
Meta.disable_unredirect_for_screen(global.screen);
} }
global.set_stage_input_mode(Shell.StageInputMode.FULLSCREEN); global.set_stage_input_mode(Shell.StageInputMode.FULLSCREEN);
@ -681,7 +663,7 @@ function pushModal(actor, timestamp, options) {
let actorDestroyId = actor.connect('destroy', function() { let actorDestroyId = actor.connect('destroy', function() {
let index = _findModal(actor); let index = _findModal(actor);
if (index >= 0) if (index >= 0)
popModal(actor); modalActorFocusStack.splice(index, 1);
}); });
let curFocus = global.stage.get_key_focus(); let curFocus = global.stage.get_key_focus();
let curFocusDestroyId; let curFocusDestroyId;
@ -753,7 +735,6 @@ function popModal(actor, timestamp) {
global.end_modal(timestamp); global.end_modal(timestamp);
global.set_stage_input_mode(Shell.StageInputMode.NORMAL); global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
Meta.enable_unredirect_for_screen(global.screen);
} }
function createLookingGlass() { function createLookingGlass() {
@ -896,8 +877,7 @@ function initializeDeferredWork(actor, callback, props) {
function queueDeferredWork(workId) { function queueDeferredWork(workId) {
let data = _deferredWorkData[workId]; let data = _deferredWorkData[workId];
if (!data) { if (!data) {
let message = 'Invalid work id %d'.format(workId); global.logError('invalid work id ', workId);
logError(new Error(message), message);
return; return;
} }
if (_deferredWorkQueue.indexOf(workId) < 0) if (_deferredWorkQueue.indexOf(workId) < 0)

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,6 @@ const Atk = imports.gi.Atk;
const Params = imports.misc.params; const Params = imports.misc.params;
const Layout = imports.ui.layout;
const Lightbox = imports.ui.lightbox; const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main; const Main = imports.ui.main;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
@ -36,9 +35,7 @@ const ModalDialog = new Lang.Class({
_init: function(params) { _init: function(params) {
params = Params.parse(params, { shellReactive: false, params = Params.parse(params, { shellReactive: false,
styleClass: null, styleClass: null });
parentActor: Main.uiGroup
});
this.state = State.CLOSED; this.state = State.CLOSED;
this._hasModal = false; this._hasModal = false;
@ -48,7 +45,7 @@ const ModalDialog = new Lang.Class({
x: 0, x: 0,
y: 0, y: 0,
accessible_role: Atk.Role.DIALOG }); accessible_role: Atk.Role.DIALOG });
params.parentActor.add_actor(this._group); Main.uiGroup.add_actor(this._group);
let constraint = new Clutter.BindConstraint({ source: global.stage, let constraint = new Clutter.BindConstraint({ source: global.stage,
coordinate: Clutter.BindCoordinate.ALL }); coordinate: Clutter.BindCoordinate.ALL });
@ -57,17 +54,15 @@ const ModalDialog = new Lang.Class({
this._group.connect('destroy', Lang.bind(this, this._onGroupDestroy)); this._group.connect('destroy', Lang.bind(this, this._onGroupDestroy));
this._actionKeys = {}; this._actionKeys = {};
this._group.connect('key-release-event', Lang.bind(this, this._onKeyReleaseEvent)); this._group.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent));
this._backgroundBin = new St.Bin(); this._backgroundBin = new St.Bin();
this._monitorConstraint = new Layout.MonitorConstraint();
this._backgroundBin.add_constraint(this._monitorConstraint);
this._group.add_actor(this._backgroundBin); this._group.add_actor(this._backgroundBin);
this.dialogLayout = new St.BoxLayout({ style_class: 'modal-dialog', this._dialogLayout = new St.BoxLayout({ style_class: 'modal-dialog',
vertical: true }); vertical: true });
if (params.styleClass != null) { if (params.styleClass != null) {
this.dialogLayout.add_style_class_name(params.styleClass); this._dialogLayout.add_style_class_name(params.styleClass);
} }
if (!this._shellReactive) { if (!this._shellReactive) {
@ -80,29 +75,29 @@ const ModalDialog = new Lang.Class({
this._eventBlocker = new Clutter.Group({ reactive: true }); this._eventBlocker = new Clutter.Group({ reactive: true });
stack.add_actor(this._eventBlocker); stack.add_actor(this._eventBlocker);
stack.add_actor(this.dialogLayout); stack.add_actor(this._dialogLayout);
} else { } else {
this._backgroundBin.child = this.dialogLayout; this._backgroundBin.child = this._dialogLayout;
} }
this.contentLayout = new St.BoxLayout({ vertical: true }); this.contentLayout = new St.BoxLayout({ vertical: true });
this.dialogLayout.add(this.contentLayout, this._dialogLayout.add(this.contentLayout,
{ x_fill: true, { x_fill: true,
y_fill: true, y_fill: true,
x_align: St.Align.MIDDLE, x_align: St.Align.MIDDLE,
y_align: St.Align.START }); y_align: St.Align.START });
this._buttonLayout = new St.BoxLayout({ style_class: 'modal-dialog-button-box', this._buttonLayout = new St.BoxLayout({ style_class: 'modal-dialog-button-box',
visible: false, visible: false,
vertical: false }); vertical: false });
this.dialogLayout.add(this._buttonLayout, this._dialogLayout.add(this._buttonLayout,
{ expand: true, { expand: true,
x_align: St.Align.MIDDLE, x_align: St.Align.MIDDLE,
y_align: St.Align.END }); y_align: St.Align.END });
global.focus_manager.add_group(this.dialogLayout); global.focus_manager.add_group(this._dialogLayout);
this._initialKeyFocus = this.dialogLayout; this._initialKeyFocus = this._dialogLayout;
this._initialKeyFocusDestroyId = 0; this._initialKeyFocusDestroyId = 0;
this._savedKeyFocus = null; this._savedKeyFocus = null;
}, },
@ -111,10 +106,6 @@ const ModalDialog = new Lang.Class({
this._group.destroy(); this._group.destroy();
}, },
setActionKey: function(key, action) {
this._actionKeys[key] = action;
},
setButtons: function(buttons) { setButtons: function(buttons) {
let hadChildren = this._buttonLayout.get_children() > 0; let hadChildren = this._buttonLayout.get_children() > 0;
@ -128,17 +119,11 @@ const ModalDialog = new Lang.Class({
let label = buttonInfo['label']; let label = buttonInfo['label'];
let action = buttonInfo['action']; let action = buttonInfo['action'];
let key = buttonInfo['key']; let key = buttonInfo['key'];
let isDefault = buttonInfo['default'];
if (isDefault && !key)
key = Clutter.KEY_Return;
buttonInfo.button = new St.Button({ style_class: 'modal-dialog-button', buttonInfo.button = new St.Button({ style_class: 'modal-dialog-button',
reactive: true, reactive: true,
can_focus: true, can_focus: true,
label: label }); label: label });
if (isDefault)
buttonInfo.button.add_style_pseudo_class('default');
let x_alignment; let x_alignment;
if (buttons.length == 1) if (buttons.length == 1)
@ -182,16 +167,12 @@ const ModalDialog = new Lang.Class({
}, },
_onKeyReleaseEvent: function(object, event) { _onKeyPressEvent: function(object, keyPressEvent) {
let symbol = event.get_key_symbol(); let symbol = keyPressEvent.get_key_symbol();
let action = this._actionKeys[symbol]; let action = this._actionKeys[symbol];
if (action) { if (action)
action(); action();
return true;
}
return false;
}, },
_onGroupDestroy: function() { _onGroupDestroy: function() {
@ -199,11 +180,14 @@ const ModalDialog = new Lang.Class({
}, },
_fadeOpen: function() { _fadeOpen: function() {
this._monitorConstraint.index = global.screen.get_current_monitor(); let monitor = Main.layoutManager.focusMonitor;
this._backgroundBin.set_position(monitor.x, monitor.y);
this._backgroundBin.set_size(monitor.width, monitor.height);
this.state = State.OPENING; this.state = State.OPENING;
this.dialogLayout.opacity = 255; this._dialogLayout.opacity = 255;
if (this._lightbox) if (this._lightbox)
this._lightbox.show(); this._lightbox.show();
this._group.opacity = 0; this._group.opacity = 0;
@ -227,7 +211,7 @@ const ModalDialog = new Lang.Class({
this._initialKeyFocus = actor; this._initialKeyFocus = actor;
this._initialKeyFocusDestroyId = actor.connect('destroy', Lang.bind(this, function() { this._initialKeyFocusDestroyId = actor.connect('destroy', Lang.bind(this, function() {
this._initialKeyFocus = this.dialogLayout; this._initialKeyFocus = this._dialogLayout;
this._initialKeyFocusDestroyId = 0; this._initialKeyFocusDestroyId = 0;
})); }));
}, },
@ -320,7 +304,7 @@ const ModalDialog = new Lang.Class({
return; return;
this.popModal(timestamp); this.popModal(timestamp);
Tweener.addTween(this.dialogLayout, Tweener.addTween(this._dialogLayout,
{ opacity: 0, { opacity: 0,
time: FADE_OUT_DIALOG_TIME, time: FADE_OUT_DIALOG_TIME,
transition: 'easeOutQuad', transition: 'easeOutQuad',

View File

@ -135,10 +135,7 @@ const NetworkSecretDialog = new Lang.Class({
} else } else
secret.valid = true; secret.valid = true;
secretTable.add(label, { row: pos, col: 0, secretTable.add(label, { row: pos, col: 0, x_expand: false, x_fill: true, x_align: St.Align.START, y_align: St.Align.START });
x_expand: false, x_fill: true,
x_align: St.Align.START,
y_fill: false, y_align: St.Align.MIDDLE });
secretTable.add(secret.entry, { row: pos, col: 1, x_expand: true, x_fill: true, y_align: St.Align.END }); secretTable.add(secret.entry, { row: pos, col: 1, x_expand: true, x_fill: true, y_align: St.Align.END });
pos++; pos++;
@ -150,7 +147,7 @@ const NetworkSecretDialog = new Lang.Class({
this._okButton = { label: _("Connect"), this._okButton = { label: _("Connect"),
action: Lang.bind(this, this._onOk), action: Lang.bind(this, this._onOk),
default: true key: Clutter.KEY_Return,
}; };
this.setButtons([{ label: _("Cancel"), this.setButtons([{ label: _("Cancel"),
@ -169,6 +166,10 @@ const NetworkSecretDialog = new Lang.Class({
this._okButton.button.reactive = valid; this._okButton.button.reactive = valid;
this._okButton.button.can_focus = valid; this._okButton.button.can_focus = valid;
if (valid)
this._okButton.button.remove_style_pseudo_class('disabled');
else
this._okButton.button.add_style_pseudo_class('disabled');
}, },
_onOk: function() { _onOk: function() {
@ -682,10 +683,7 @@ const NetworkAgent = new Lang.Class({
try { try {
externalUIMode = keyfile.get_boolean('GNOME', 'supports-external-ui-mode'); externalUIMode = keyfile.get_boolean('GNOME', 'supports-external-ui-mode');
} catch(e) { } // ignore errors if key does not exist } catch(e) { } // ignore errors if key does not exist
let path = binary; let path = GLib.build_filenamev([Config.LIBEXECDIR, binary]);
if (!GLib.path_is_absolute(path)) {
path = GLib.build_filenamev([Config.LIBEXECDIR, path]);
}
if (GLib.file_test(path, GLib.FileTest.IS_EXECUTABLE)) if (GLib.file_test(path, GLib.FileTest.IS_EXECUTABLE))
this._vpnBinaries[service] = { fileName: path, externalUIMode: externalUIMode }; this._vpnBinaries[service] = { fileName: path, externalUIMode: externalUIMode };

View File

@ -1,7 +1,6 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const GdkPixbuf = imports.gi.GdkPixbuf;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Lang = imports.lang; const Lang = imports.lang;
@ -88,21 +87,6 @@ const rewriteRules = {
] ]
}; };
const STANDARD_TRAY_ICON_IMPLEMENTATIONS = {
'bluetooth-applet': 'bluetooth',
'gnome-volume-control-applet': 'volume', // renamed to gnome-sound-applet
// when moved to control center
'gnome-sound-applet': 'volume',
'nm-applet': 'network',
'gnome-power-manager': 'battery',
'keyboard': 'keyboard',
'a11y-keyboard': 'a11y',
'kbd-scrolllock': 'keyboard',
'kbd-numlock': 'keyboard',
'kbd-capslock': 'keyboard',
'ibus-ui-gtk': 'keyboard'
};
const NotificationDaemon = new Lang.Class({ const NotificationDaemon = new Lang.Class({
Name: 'NotificationDaemon', Name: 'NotificationDaemon',
@ -115,19 +99,18 @@ const NotificationDaemon = new Lang.Class({
this._notifications = {}; this._notifications = {};
this._busProxy = new Bus(); this._busProxy = new Bus();
this._trayManager = new Shell.TrayManager(); Main.statusIconDispatcher.connect('message-icon-added', Lang.bind(this, this._onTrayIconAdded));
this._trayManager.connect('tray-icon-added', Lang.bind(this, this._onTrayIconAdded)); Main.statusIconDispatcher.connect('message-icon-removed', Lang.bind(this, this._onTrayIconRemoved));
this._trayManager.connect('tray-icon-removed', Lang.bind(this, this._onTrayIconRemoved));
Shell.WindowTracker.get_default().connect('notify::focus-app', Shell.WindowTracker.get_default().connect('notify::focus-app',
Lang.bind(this, this._onFocusAppChanged)); Lang.bind(this, this._onFocusAppChanged));
Main.overview.connect('hidden', Main.overview.connect('hidden',
Lang.bind(this, this._onFocusAppChanged)); Lang.bind(this, this._onFocusAppChanged));
this._trayManager.manage_stage(global.stage, Main.messageTray.actor);
}, },
_iconForNotificationData: function(icon, hints) { _iconForNotificationData: function(icon, hints, size) {
let textureCache = St.TextureCache.get_default();
// If an icon is not specified, we use 'image-data' or 'image-path' hint for an icon // If an icon is not specified, we use 'image-data' or 'image-path' hint for an icon
// and don't show a large image. There are currently many applications that use // and don't show a large image. There are currently many applications that use
// notify_notification_set_icon_from_pixbuf() from libnotify, which in turn sets // notify_notification_set_icon_from_pixbuf() from libnotify, which in turn sets
@ -138,18 +121,20 @@ const NotificationDaemon = new Lang.Class({
// a large image. // a large image.
if (icon) { if (icon) {
if (icon.substr(0, 7) == 'file://') if (icon.substr(0, 7) == 'file://')
return new Gio.FileIcon({ file: Gio.File.new_for_uri(icon) }); return textureCache.load_uri_async(icon, size, size);
else if (icon[0] == '/') { else if (icon[0] == '/') {
return new Gio.FileIcon({ file: Gio.File.new_for_path(icon) }); let uri = GLib.filename_to_uri(icon, null);
return textureCache.load_uri_async(uri, size, size);
} else } else
return new Gio.ThemedIcon({ name: icon }); return new St.Icon({ icon_name: icon,
icon_type: St.IconType.FULLCOLOR,
icon_size: size });
} else if (hints['image-data']) { } else if (hints['image-data']) {
let [width, height, rowStride, hasAlpha, let [width, height, rowStride, hasAlpha,
bitsPerSample, nChannels, data] = hints['image-data']; bitsPerSample, nChannels, data] = hints['image-data'];
return Shell.util_create_pixbuf_from_data(data, GdkPixbuf.Colorspace.RGB, hasAlpha, return textureCache.load_from_raw(data, hasAlpha, width, height, rowStride, size);
bitsPerSample, width, height, rowStride);
} else if (hints['image-path']) { } else if (hints['image-path']) {
return new Gio.FileIcon({ file: Gio.File.new_for_path(hints['image-path']) }); return textureCache.load_uri_async(GLib.filename_to_uri(hints['image-path'], null), size, size);
} else { } else {
let stockIcon; let stockIcon;
switch (hints.urgency) { switch (hints.urgency) {
@ -161,7 +146,9 @@ const NotificationDaemon = new Lang.Class({
stockIcon = 'gtk-dialog-error'; stockIcon = 'gtk-dialog-error';
break; break;
} }
return new Gio.ThemedIcon({ name: stockIcon }); return new St.Icon({ icon_name: stockIcon,
icon_type: St.IconType.FULLCOLOR,
icon_size: size });
} }
}, },
@ -234,19 +221,12 @@ const NotificationDaemon = new Lang.Class({
let [appName, replacesId, icon, summary, body, actions, hints, timeout] = params; let [appName, replacesId, icon, summary, body, actions, hints, timeout] = params;
let id; let id;
for (let hint in hints) {
// unpack the variants
hints[hint] = hints[hint].deep_unpack();
}
hints = Params.parse(hints, { urgency: Urgency.NORMAL }, true);
// Filter out chat, presence, calls and invitation notifications from // Filter out chat, presence, calls and invitation notifications from
// Empathy, since we handle that information from telepathyClient.js // Empathy, since we handle that information from telepathyClient.js
if (appName == 'Empathy' && (hints['category'] == 'im.received' || if (appName == 'Empathy' && (hints['category'] == 'im.received' ||
hints['category'] == 'x-empathy.im.room-invitation' || hints['category'] == 'x-empathy.im.room-invitation' ||
hints['category'] == 'x-empathy.call.incoming' || hints['category'] == 'x-empathy.call.incoming' ||
hints['category'] == 'x-empathy.transfer.incoming' || hints['category'] == 'x-empathy.call.incoming"' ||
hints['category'] == 'x-empathy.im.subscription-request' || hints['category'] == 'x-empathy.im.subscription-request' ||
hints['category'] == 'presence.online' || hints['category'] == 'presence.online' ||
hints['category'] == 'presence.offline')) { hints['category'] == 'presence.offline')) {
@ -256,7 +236,6 @@ const NotificationDaemon = new Lang.Class({
Mainloop.idle_add(Lang.bind(this, Mainloop.idle_add(Lang.bind(this,
function () { function () {
this._emitNotificationClosed(id, NotificationClosedReason.DISMISSED); this._emitNotificationClosed(id, NotificationClosedReason.DISMISSED);
return false;
})); }));
return invocation.return_value(GLib.Variant.new('(u)', [id])); return invocation.return_value(GLib.Variant.new('(u)', [id]));
} }
@ -270,6 +249,13 @@ const NotificationDaemon = new Lang.Class({
} }
} }
for (let hint in hints) {
// unpack the variants
hints[hint] = hints[hint].deep_unpack();
}
hints = Params.parse(hints, { urgency: Urgency.NORMAL }, true);
// Be compatible with the various hints for image data and image path // Be compatible with the various hints for image data and image path
// 'image-data' and 'image-path' are the latest name of these hints, introduced in 1.2 // 'image-data' and 'image-path' are the latest name of these hints, introduced in 1.2
@ -355,9 +341,7 @@ const NotificationDaemon = new Lang.Class({
[ndata.id, ndata.icon, ndata.summary, ndata.body, [ndata.id, ndata.icon, ndata.summary, ndata.body,
ndata.actions, ndata.hints, ndata.notification]; ndata.actions, ndata.hints, ndata.notification];
let gicon = this._iconForNotificationData(icon, hints); let iconActor = this._iconForNotificationData(icon, hints, source.ICON_SIZE);
let iconActor = new St.Icon({ gicon: gicon,
icon_size: MessageTray.NOTIFICATION_ICON_SIZE });
if (notification == null) { if (notification == null) {
notification = new MessageTray.Notification(source, summary, body, notification = new MessageTray.Notification(source, summary, body,
@ -437,8 +421,8 @@ const NotificationDaemon = new Lang.Class({
// of the 'transient' hint with hints['transient'] rather than hints.transient // of the 'transient' hint with hints['transient'] rather than hints.transient
notification.setTransient(hints['transient'] == true); notification.setTransient(hints['transient'] == true);
let sourceGIcon = source.useNotificationIcon ? this._iconForNotificationData(icon, hints) : null; let sourceIconActor = source.useNotificationIcon ? this._iconForNotificationData(icon, hints, source.ICON_SIZE) : null;
source.processNotification(notification, sourceGIcon); source.processNotification(notification, sourceIconActor);
}, },
CloseNotification: function(id) { CloseNotification: function(id) {
@ -499,11 +483,7 @@ const NotificationDaemon = new Lang.Class({
}, },
_onTrayIconAdded: function(o, icon) { _onTrayIconAdded: function(o, icon) {
let wmClass = icon.wm_class.toLowerCase(); let source = this._getSource(icon.title || icon.wm_class || _("Unknown"), icon.pid, null, null, icon);
if (STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass] !== undefined)
return;
let source = this._getSource(icon.title || icon.wm_class || C_("program", "Unknown"), icon.pid, null, null, icon);
}, },
_onTrayIconRemoved: function(o, icon) { _onTrayIconRemoved: function(o, icon) {
@ -554,11 +534,11 @@ const Source = new Lang.Class({
this.destroy(); this.destroy();
}, },
processNotification: function(notification, gicon) { processNotification: function(notification, icon) {
if (gicon) if (!this.app)
this._gicon = gicon; this._setApp();
if (!this.trayIcon) if (!this.app && icon)
this.iconUpdated(); this._setSummaryIcon(icon);
let tracker = Shell.WindowTracker.get_default(); let tracker = Shell.WindowTracker.get_default();
if (notification.resident && this.app && tracker.focus_app == this.app) if (notification.resident && this.app && tracker.focus_app == this.app)
@ -583,12 +563,18 @@ const Source = new Lang.Class({
this.notifications.length > 0) this.notifications.length > 0)
return false; return false;
let id = global.connect('notify::stage-input-mode', Lang.bind(this, function () { if (Main.overview.visible) {
global.disconnect(id); // We can't just connect to Main.overview's 'hidden' signal,
// because it's emitted *before* it calls popModal()...
let id = global.connect('notify::stage-input-mode', Lang.bind(this,
function () {
global.disconnect(id);
this.trayIcon.click(event);
}));
Main.overview.hide();
} else {
this.trayIcon.click(event); this.trayIcon.click(event);
})); }
Main.overview.hide();
return true; return true;
}, },
@ -620,20 +606,10 @@ const Source = new Lang.Class({
// notification-based icons (ie, not a trayicon) or if it was unset before // notification-based icons (ie, not a trayicon) or if it was unset before
if (!this.trayIcon) { if (!this.trayIcon) {
this.useNotificationIcon = false; this.useNotificationIcon = false;
this.iconUpdated(); this._setSummaryIcon(this.app.create_icon_texture (this.ICON_SIZE));
} }
}, },
setTitle: function(title) {
// Do nothing if .app is set, we don't want to override the
// app name with whatever is provided through libnotify (usually
// garbage)
if (this.app)
return;
this.parent(title);
},
open: function(notification) { open: function(notification) {
this.destroyNonResidentNotifications(); this.destroyNonResidentNotifications();
this.openApp(); this.openApp();
@ -662,20 +638,5 @@ const Source = new Lang.Class({
} }
this.parent(); this.parent();
},
createIcon: function(size) {
if (this.trayIcon) {
return new Clutter.Clone({ width: size,
height: size,
source: this.trayIcon });
} else if (this.app) {
return this.app.create_icon_texture(size);
} else if (this._gicon) {
return new St.Icon({ gicon: this._gicon,
icon_size: size });
} else {
return null;
}
} }
}); });

View File

@ -10,14 +10,21 @@ const St = imports.gi.St;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const Gdk = imports.gi.Gdk; const Gdk = imports.gi.Gdk;
const AppDisplay = imports.ui.appDisplay;
const ContactDisplay = imports.ui.contactDisplay;
const Dash = imports.ui.dash; const Dash = imports.ui.dash;
const DND = imports.ui.dnd; const DND = imports.ui.dnd;
const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main; const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray; const MessageTray = imports.ui.messageTray;
const Panel = imports.ui.panel; const Panel = imports.ui.panel;
const Params = imports.misc.params; const Params = imports.misc.params;
const PlaceDisplay = imports.ui.placeDisplay;
const RemoteSearch = imports.ui.remoteSearch;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const ViewSelector = imports.ui.viewSelector; const ViewSelector = imports.ui.viewSelector;
const Wanda = imports.ui.wanda;
const WorkspacesView = imports.ui.workspacesView;
const WorkspaceThumbnail = imports.ui.workspaceThumbnail; const WorkspaceThumbnail = imports.ui.workspaceThumbnail;
// Time for initial animation going into Overview mode // Time for initial animation going into Overview mode
@ -70,13 +77,13 @@ const ShellInfo = new Lang.Class({
let notification = null; let notification = null;
if (this._source.notifications.length == 0) { if (this._source.notifications.length == 0) {
notification = new MessageTray.Notification(this._source, text, null); notification = new MessageTray.Notification(this._source, text, null);
notification.setTransient(true);
notification.setShowWhenLocked(true);
} else { } else {
notification = this._source.notifications[0]; notification = this._source.notifications[0];
notification.update(text, null, { clear: true }); notification.update(text, null, { clear: true });
} }
notification.setTransient(true);
this._undoCallback = undoCallback; this._undoCallback = undoCallback;
if (undoCallback) { if (undoCallback) {
notification.addButton('system-undo', notification.addButton('system-undo',
@ -92,8 +99,10 @@ const ShellInfo = new Lang.Class({
const Overview = new Lang.Class({ const Overview = new Lang.Class({
Name: 'Overview', Name: 'Overview',
_init : function() { _init : function(params) {
this.isDummy = !Main.sessionMode.hasOverview; params = Params.parse(params, { isDummy: false });
this.isDummy = params.isDummy;
// We only have an overview in user sessions, so // We only have an overview in user sessions, so
// create a dummy overview in other cases // create a dummy overview in other cases
@ -138,6 +147,8 @@ const Overview = new Lang.Class({
this._capturedEventId = 0; this._capturedEventId = 0;
this._buttonPressId = 0; this._buttonPressId = 0;
this._workspacesDisplay = null;
this.visible = false; // animating to overview, in overview, animating out this.visible = false; // animating to overview, in overview, animating out
this._shown = false; // show() and not hide() this._shown = false; // show() and not hide()
this._shownTemporarily = false; // showTemporarily() and not hideTemporarily() this._shownTemporarily = false; // showTemporarily() and not hideTemporarily()
@ -184,23 +195,30 @@ const Overview = new Lang.Class({
this._shellInfo = new ShellInfo(); this._shellInfo = new ShellInfo();
this._searchEntry = new St.Entry({ name: 'searchEntry', this._viewSelector = new ViewSelector.ViewSelector();
/* Translators: this is the text displayed
in the search entry when no search is
active; it should not exceed ~30
characters. */
hint_text: _("Type to search..."),
track_hover: true,
can_focus: true });
this._group.add_actor(this._searchEntry);
this._dash = new Dash.Dash();
this._viewSelector = new ViewSelector.ViewSelector(this._searchEntry,
this._dash.showAppsButton);
this._group.add_actor(this._viewSelector.actor); this._group.add_actor(this._viewSelector.actor);
this._group.add_actor(this._dash.actor);
this._workspacesDisplay = new WorkspacesView.WorkspacesDisplay();
this._viewSelector.addViewTab('windows', _("Windows"), this._workspacesDisplay.actor, 'text-x-generic');
let appView = new AppDisplay.AllAppDisplay();
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());
this.addSearchProvider(new ContactDisplay.ContactSearchProvider());
// Load remote search providers provided by applications
RemoteSearch.loadRemoteSearchProviders(Lang.bind(this, this.addSearchProvider));
// TODO - recalculate everything when desktop size changes // TODO - recalculate everything when desktop size changes
this._dash = new Dash.Dash();
this._group.add_actor(this._dash.actor);
this._dash.actor.add_constraint(this._viewSelector.constrainY);
this._dash.actor.add_constraint(this._viewSelector.constrainHeight); this._dash.actor.add_constraint(this._viewSelector.constrainHeight);
this.dashIconSize = this._dash.iconSize; this.dashIconSize = this._dash.iconSize;
this._dash.connect('icon-size-changed', this._dash.connect('icon-size-changed',
@ -210,7 +228,7 @@ const Overview = new Lang.Class({
// Translators: this is the name of the dock/favorites area on // Translators: this is the name of the dock/favorites area on
// the left of the overview // the left of the overview
Main.ctrlAltTabManager.addGroup(this._dash.actor, _("Dash"), 'user-bookmarks-symbolic'); Main.ctrlAltTabManager.addGroup(this._dash.actor, _("Dash"), 'user-bookmarks');
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._relayout)); Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._relayout));
this._relayout(); this._relayout();
@ -486,13 +504,13 @@ const Overview = new Lang.Class({
this._coverPane.set_position(0, contentY); this._coverPane.set_position(0, contentY);
this._coverPane.set_size(primary.width, contentHeight); this._coverPane.set_size(primary.width, contentHeight);
let searchWidth = this._searchEntry.get_width();
let searchHeight = this._searchEntry.get_height();
let searchX = (primary.width - searchWidth) / 2;
let searchY = contentY + this._spacing;
let dashWidth = Math.round(DASH_SPLIT_FRACTION * primary.width); let dashWidth = Math.round(DASH_SPLIT_FRACTION * primary.width);
let dashY = searchY + searchHeight + this._spacing; let viewWidth = primary.width - dashWidth - this._spacing;
let viewHeight = contentHeight - 2 * this._spacing;
let viewY = contentY + this._spacing;
let viewX = rtl ? 0 : dashWidth + this._spacing;
// Set the dash's x position - y is handled by a constraint
let dashX; let dashX;
if (rtl) { if (rtl) {
this._dash.actor.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST); this._dash.actor.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
@ -500,14 +518,8 @@ const Overview = new Lang.Class({
} else { } else {
dashX = 0; dashX = 0;
} }
this._dash.actor.set_x(dashX);
let viewX = rtl ? 0 : dashWidth + this._spacing;
let viewY = searchY + searchHeight + this._spacing;
let viewWidth = primary.width - dashWidth - this._spacing;
let viewHeight = contentHeight - this._spacing - viewY;
this._searchEntry.set_position(searchX, searchY);
this._dash.actor.set_position(dashX, dashY);
this._viewSelector.actor.set_position(viewX, viewY); this._viewSelector.actor.set_position(viewX, viewY);
this._viewSelector.actor.set_size(viewWidth, viewHeight); this._viewSelector.actor.set_size(viewWidth, viewHeight);
}, },
@ -557,28 +569,6 @@ const Overview = new Lang.Class({
Lang.bind(this, this._onButtonPress)); Lang.bind(this, this._onButtonPress));
}, },
fadeInDesktop: function() {
this._desktopFade.opacity = 0;
this._desktopFade.show();
Tweener.addTween(this._desktopFade,
{ opacity: 255,
time: ANIMATION_TIME,
transition: 'easeOutQuad' });
},
fadeOutDesktop: function() {
if (!this._desktopFade.child)
this._desktopFade.child = this._getDesktopClone();
this._desktopFade.opacity = 255;
this._desktopFade.show();
Tweener.addTween(this._desktopFade,
{ opacity: 0,
time: ANIMATION_TIME,
transition: 'easeOutQuad'
});
},
_animateVisible: function() { _animateVisible: function() {
if (this.visible || this.animationInProgress) if (this.visible || this.animationInProgress)
return; return;
@ -599,7 +589,21 @@ const Overview = new Lang.Class({
global.window_group.hide(); global.window_group.hide();
this._group.show(); this._group.show();
this._background.show(); this._background.show();
this._viewSelector.show();
this._workspacesDisplay.show();
if (!this._desktopFade.child)
this._desktopFade.child = this._getDesktopClone();
if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows()) {
this._desktopFade.opacity = 255;
this._desktopFade.show();
Tweener.addTween(this._desktopFade,
{ opacity: 0,
time: ANIMATION_TIME,
transition: 'easeOutQuad'
});
}
this._group.opacity = 0; this._group.opacity = 0;
Tweener.addTween(this._group, Tweener.addTween(this._group,
@ -726,7 +730,16 @@ const Overview = new Lang.Class({
this.animationInProgress = true; this.animationInProgress = true;
this._hideInProgress = true; this._hideInProgress = true;
this._viewSelector.zoomFromOverview(); if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows()) {
this._desktopFade.opacity = 0;
this._desktopFade.show();
Tweener.addTween(this._desktopFade,
{ opacity: 255,
time: ANIMATION_TIME,
transition: 'easeOutQuad' });
}
this._workspacesDisplay.zoomFromOverview();
// Make other elements fade out. // Make other elements fade out.
Tweener.addTween(this._group, Tweener.addTween(this._group,
@ -768,7 +781,8 @@ const Overview = new Lang.Class({
global.window_group.show(); global.window_group.show();
this._viewSelector.hide(); this._workspacesDisplay.hide();
this._desktopFade.hide(); this._desktopFade.hide();
this._background.hide(); this._background.hide();
this._group.hide(); this._group.hide();

View File

@ -14,7 +14,6 @@ const St = imports.gi.St;
const Signals = imports.signals; const Signals = imports.signals;
const Atk = imports.gi.Atk; const Atk = imports.gi.Atk;
const Config = imports.misc.config; const Config = imports.misc.config;
const CtrlAltTab = imports.ui.ctrlAltTab; const CtrlAltTab = imports.ui.ctrlAltTab;
const DND = imports.ui.dnd; const DND = imports.ui.dnd;
@ -22,6 +21,7 @@ const Layout = imports.ui.layout;
const Overview = imports.ui.overview; const Overview = imports.ui.overview;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const DateMenu = imports.ui.dateMenu;
const Main = imports.ui.main; const Main = imports.ui.main;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
@ -32,6 +32,33 @@ const BUTTON_DND_ACTIVATION_TIMEOUT = 250;
const ANIMATED_ICON_UPDATE_TIMEOUT = 100; const ANIMATED_ICON_UPDATE_TIMEOUT = 100;
const SPINNER_ANIMATION_TIME = 0.2; const SPINNER_ANIMATION_TIME = 0.2;
const STANDARD_STATUS_AREA_ORDER = ['a11y', 'keyboard', 'volume', 'bluetooth', 'network', 'battery', 'userMenu'];
const STANDARD_STATUS_AREA_SHELL_IMPLEMENTATION = {
'a11y': imports.ui.status.accessibility.ATIndicator,
'volume': imports.ui.status.volume.Indicator,
'battery': imports.ui.status.power.Indicator,
'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
'userMenu': imports.ui.userMenu.UserMenuButton
};
if (Config.HAVE_BLUETOOTH)
STANDARD_STATUS_AREA_SHELL_IMPLEMENTATION['bluetooth'] = imports.ui.status.bluetooth.Indicator;
try {
STANDARD_STATUS_AREA_SHELL_IMPLEMENTATION['network'] = imports.ui.status.network.NMApplet;
} catch(e) {
log('NMApplet is not supported. It is possible that your NetworkManager version is too old');
}
const GDM_STATUS_AREA_ORDER = ['a11y', 'display', 'keyboard', 'volume', 'battery', 'powerMenu'];
const GDM_STATUS_AREA_SHELL_IMPLEMENTATION = {
'a11y': imports.ui.status.accessibility.ATIndicator,
'volume': imports.ui.status.volume.Indicator,
'battery': imports.ui.status.power.Indicator,
'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
'powerMenu': imports.gdm.powerMenu.PowerMenuButton
};
// To make sure the panel corners blend nicely with the panel, // To make sure the panel corners blend nicely with the panel,
// we draw background and borders the same way, e.g. drawing // we draw background and borders the same way, e.g. drawing
// them as filled shapes from the outside inwards instead of // them as filled shapes from the outside inwards instead of
@ -222,14 +249,14 @@ const AppMenuButton = new Lang.Class({
Name: 'AppMenuButton', Name: 'AppMenuButton',
Extends: PanelMenu.Button, Extends: PanelMenu.Button,
_init: function(panel) { _init: function(menuManager) {
this.parent(0.0, null, true); this.parent(0.0, null, true);
this.actor.accessible_role = Atk.Role.MENU; this.actor.accessible_role = Atk.Role.MENU;
this._startingApps = []; this._startingApps = [];
this._menuManager = panel.menuManager; this._menuManager = menuManager;
this._targetApp = null; this._targetApp = null;
this._appMenuNotifyId = 0; this._appMenuNotifyId = 0;
this._actionGroupNotifyId = 0; this._actionGroupNotifyId = 0;
@ -286,7 +313,7 @@ const AppMenuButton = new Lang.Class({
}, },
show: function() { show: function() {
if (this._visible || Main.screenShield.locked) if (this._visible)
return; return;
this._visible = true; this._visible = true;
@ -347,7 +374,6 @@ const AppMenuButton = new Lang.Class({
return; return;
this._stop = true; this._stop = true;
this.actor.reactive = true;
Tweener.addTween(this._spinner.actor, Tweener.addTween(this._spinner.actor,
{ opacity: 0, { opacity: 0,
time: SPINNER_ANIMATION_TIME, time: SPINNER_ANIMATION_TIME,
@ -362,7 +388,6 @@ const AppMenuButton = new Lang.Class({
startAnimation: function() { startAnimation: function() {
this._stop = false; this._stop = false;
this.actor.reactive = false;
this._spinner.actor.show(); this._spinner.actor.show();
}, },
@ -468,15 +493,6 @@ const AppMenuButton = new Lang.Class({
this._sync(); this._sync();
}, },
setLockedState: function(locked) {
if (locked) {
this.hide();
} else {
this.show();
this._sync();
}
},
_sync: function() { _sync: function() {
let tracker = Shell.WindowTracker.get_default(); let tracker = Shell.WindowTracker.get_default();
let focusedApp = tracker.focus_app; let focusedApp = tracker.focus_app;
@ -763,11 +779,10 @@ const PanelCorner = new Lang.Class({
return null; return null;
// Start at the back and work backward // Start at the back and work backward
let index; let index = children.length - 1;
for (index = children.length - 1; index >= 0; index--) { while (!children[index].visible && index >= 0)
if (children[index].visible) index--;
break;
}
if (index < 0) if (index < 0)
return null; return null;
@ -788,11 +803,10 @@ const PanelCorner = new Lang.Class({
return null; return null;
// Start at the front and work forward // Start at the front and work forward
let index; let index = 0;
for (index = 0; index < children.length; index++) { while (!children[index].visible && index < children.length)
if (children[index].visible) index++;
break;
}
if (index == children.length) if (index == children.length)
return null; return null;
@ -902,29 +916,6 @@ const PanelCorner = new Lang.Class({
} }
}); });
const PANEL_ITEM_IMPLEMENTATIONS = {
'activities': ActivitiesButton,
'appMenu': AppMenuButton,
'dateMenu': imports.ui.dateMenu.DateMenuButton,
'a11y': imports.ui.status.accessibility.ATIndicator,
'volume': imports.ui.status.volume.Indicator,
'battery': imports.ui.status.power.Indicator,
'lockScreen': imports.ui.status.lockScreenMenu.Indicator,
'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
'powerMenu': imports.gdm.powerMenu.PowerMenuButton,
'userMenu': imports.ui.userMenu.UserMenuButton
};
if (Config.HAVE_BLUETOOTH)
PANEL_ITEM_IMPLEMENTATIONS['bluetooth'] =
imports.ui.status.bluetooth.Indicator;
try {
PANEL_ITEM_IMPLEMENTATIONS['network'] =
imports.ui.status.network.NMApplet;
} catch(e) {
log('NMApplet is not supported. It is possible that your NetworkManager version is too old');
}
const Panel = new Lang.Class({ const Panel = new Lang.Class({
Name: 'Panel', Name: 'Panel',
@ -934,7 +925,7 @@ const Panel = new Lang.Class({
reactive: true }); reactive: true });
this.actor._delegate = this; this.actor._delegate = this;
this.statusArea = {}; this._statusArea = {};
Main.overview.connect('shown', Lang.bind(this, function () { Main.overview.connect('shown', Lang.bind(this, function () {
this.actor.add_style_class_name('in-overview'); this.actor.add_style_class_name('in-overview');
@ -943,9 +934,7 @@ const Panel = new Lang.Class({
this.actor.remove_style_class_name('in-overview'); this.actor.remove_style_class_name('in-overview');
})); }));
Main.screenShield.connect('lock-status-changed', Lang.bind(this, this._onLockStateChanged)); this._menus = new PopupMenu.PopupMenuManager(this);
this.menuManager = new PopupMenu.PopupMenuManager(this);
this._leftBox = new St.BoxLayout({ name: 'panelLeft' }); this._leftBox = new St.BoxLayout({ name: 'panelLeft' });
this.actor.add_actor(this._leftBox); this.actor.add_actor(this._leftBox);
@ -972,8 +961,42 @@ const Panel = new Lang.Class({
this.actor.connect('allocate', Lang.bind(this, this._allocate)); this.actor.connect('allocate', Lang.bind(this, this._allocate));
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress)); this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
/* Button on the left side of the panel. */
if (global.session_type == Shell.SessionType.USER) {
this._activitiesButton = new ActivitiesButton();
this._activities = this._activitiesButton.actor;
this._leftBox.add(this._activities);
// The activities button has a pretend menu, so as to integrate
// more cleanly with the rest of the panel
this._menus.addMenu(this._activitiesButton.menu);
this._appMenu = new AppMenuButton(this._menus);
this._leftBox.add(this._appMenu.actor);
}
/* center */
if (global.session_type == Shell.SessionType.USER)
this._dateMenu = new DateMenu.DateMenuButton({ showEvents: true });
else
this._dateMenu = new DateMenu.DateMenuButton({ showEvents: false });
this._centerBox.add(this._dateMenu.actor, { y_fill: true });
this._menus.addMenu(this._dateMenu.menu);
/* right */
if (global.session_type == Shell.SessionType.GDM) {
this._status_area_order = GDM_STATUS_AREA_ORDER;
this._status_area_shell_implementation = GDM_STATUS_AREA_SHELL_IMPLEMENTATION;
} else {
this._status_area_order = STANDARD_STATUS_AREA_ORDER;
this._status_area_shell_implementation = STANDARD_STATUS_AREA_SHELL_IMPLEMENTATION;
}
Main.statusIconDispatcher.connect('status-icon-added', Lang.bind(this, this._onTrayIconAdded));
Main.statusIconDispatcher.connect('status-icon-removed', Lang.bind(this, this._onTrayIconRemoved));
Main.layoutManager.panelBox.add(this.actor); Main.layoutManager.panelBox.add(this.actor);
Main.ctrlAltTabManager.addGroup(this.actor, _("Top Bar"), 'start-here-symbolic', Main.ctrlAltTabManager.addGroup(this.actor, _("Top Bar"), 'start-here',
{ sortGroup: CtrlAltTab.SortGroup.TOP }); { sortGroup: CtrlAltTab.SortGroup.TOP });
}, },
@ -1091,71 +1114,84 @@ const Panel = new Lang.Class({
}, },
openAppMenu: function() { openAppMenu: function() {
let indicator = this.statusArea.appMenu; let menu = this._appMenu.menu;
if (!indicator) // appMenu not supported by current session mode if (Main.overview.visible || menu.isOpen)
return; return;
let menu = indicator.menu;
if (!indicator.actor.reactive || menu.isOpen)
return;
menu.open(); menu.open();
menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
}, },
init: function() { startStatusArea: function() {
let panel = Main.sessionMode.panel; for (let i = 0; i < this._status_area_order.length; i++) {
this._initBox(panel.left, this._leftBox); let role = this._status_area_order[i];
this._initBox(panel.center, this._centerBox); let constructor = this._status_area_shell_implementation[role];
this._initBox(panel.right, this._rightBox);
},
_initBox: function(elements, box) {
for (let i = 0; i < elements.length; i++) {
let role = elements[i];
let constructor = PANEL_ITEM_IMPLEMENTATIONS[role];
if (!constructor) { if (!constructor) {
// panel icon is not supported (can happen for // This icon is not implemented (this is a bug)
// bluetooth or network)
continue; continue;
} }
let indicator = new constructor(this); let indicator = new constructor();
this._addToPanelBox(role, indicator, i, box); this.addToStatusArea(role, indicator, i);
} }
}, },
_addToPanelBox: function(role, indicator, position, box) { _insertStatusItem: function(actor, position) {
box.insert_child_at_index(indicator.actor, position); let children = this._rightBox.get_children();
if (indicator.menu) let i;
this.menuManager.addMenu(indicator.menu); for (i = children.length - 1; i >= 0; i--) {
this.statusArea[role] = indicator; let rolePosition = children[i]._rolePosition;
let destroyId = indicator.connect('destroy', Lang.bind(this, function(emitter) { if (position > rolePosition) {
delete this.statusArea[role]; this._rightBox.insert_child_at_index(actor, i + 1);
emitter.disconnect(destroyId); break;
})); }
}
if (i == -1) {
// If we didn't find a position, we must be first
this._rightBox.insert_child_at_index(actor, 0);
}
actor._rolePosition = position;
}, },
addToStatusArea: function(role, indicator, position, box) { addToStatusArea: function(role, indicator, position) {
if (this.statusArea[role]) if (this._statusArea[role])
throw new Error('Extension point conflict: there is already a status indicator for role ' + role); throw new Error('Extension point conflict: there is already a status indicator for role ' + role);
if (!(indicator instanceof PanelMenu.Button)) if (!(indicator instanceof PanelMenu.Button))
throw new TypeError('Status indicator must be an instance of PanelMenu.Button'); throw new TypeError('Status indicator must be an instance of PanelMenu.Button');
position = position || 0; if (!position)
let boxes = { position = 0;
left: this._leftBox, this._insertStatusItem(indicator.actor, position);
center: this._centerBox, this._menus.addMenu(indicator.menu);
right: this._rightBox
}; this._statusArea[role] = indicator;
let boxContainer = boxes[box] || this._rightBox; let destroyId = indicator.connect('destroy', Lang.bind(this, function(emitter) {
this._addToPanelBox(role, indicator, position, boxContainer); this._statusArea[role] = null;
emitter.disconnect(destroyId);
}));
return indicator; return indicator;
}, },
_onLockStateChanged: function(shield, locked) { _onTrayIconAdded: function(o, icon, role) {
for (let id in this.statusArea) if (this._status_area_shell_implementation[role]) {
this.statusArea[id].setLockedState(locked); // This icon is legacy, and replaced by a Shell version
// Hide it
return;
}
icon.height = PANEL_ICON_SIZE;
let buttonBox = new PanelMenu.ButtonBox();
let box = buttonBox.actor;
box.add_actor(icon);
this._insertStatusItem(box, this._status_area_order.indexOf(role));
},
_onTrayIconRemoved: function(o, icon) {
let box = icon.get_parent();
if (box && box._delegate instanceof PanelMenu.ButtonBox)
box.destroy();
}, },
}); });

View File

@ -1,7 +1,6 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
const Gtk = imports.gi.Gtk; const Gtk = imports.gi.Gtk;
const Lang = imports.lang; const Lang = imports.lang;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
@ -146,13 +145,6 @@ const Button = new Lang.Class({
} }
}, },
setLockedState: function(locked) {
// default behaviour is to hide completely
if (locked)
this.menu.close();
this.actor.visible = !locked;
},
_onButtonPress: function(actor, event) { _onButtonPress: function(actor, event) {
if (!this.menu) if (!this.menu)
return; return;
@ -183,7 +175,8 @@ const Button = new Lang.Class({
_onMenuKeyPress: function(actor, event) { _onMenuKeyPress: function(actor, event) {
let symbol = event.get_key_symbol(); let symbol = event.get_key_symbol();
if (symbol == Clutter.KEY_Left || symbol == Clutter.KEY_Right) { if (symbol == Clutter.KEY_Left || symbol == Clutter.KEY_Right) {
let group = global.focus_manager.get_group(this.actor); let focusManager = St.FocusManager.get_for_stage(global.stage);
let group = focusManager.get_group(this.actor);
if (group) { if (group) {
let direction = (symbol == Clutter.KEY_Left) ? Gtk.DirectionType.LEFT : Gtk.DirectionType.RIGHT; let direction = (symbol == Clutter.KEY_Left) ? Gtk.DirectionType.LEFT : Gtk.DirectionType.RIGHT;
group.navigate_focus(this.actor, direction, false); group.navigate_focus(this.actor, direction, false);
@ -231,36 +224,19 @@ const SystemStatusButton = new Lang.Class({
_init: function(iconName, nameText) { _init: function(iconName, nameText) {
this.parent(0.0, nameText); this.parent(0.0, nameText);
this._iconActor = new St.Icon({ icon_name: iconName,
icon_type: St.IconType.SYMBOLIC,
style_class: 'system-status-icon' });
this.actor.add_actor(this._iconActor);
this.actor.add_style_class_name('panel-status-button'); this.actor.add_style_class_name('panel-status-button');
this._box = new St.BoxLayout({ style_class: 'panel-status-button-box' });
this.actor.add_actor(this._box);
if (iconName)
this.setIcon(iconName);
},
addIcon: function(gicon) {
let icon = new St.Icon({ gicon: gicon,
style_class: 'system-status-icon' });
this._box.add_actor(icon);
return icon;
}, },
setIcon: function(iconName) { setIcon: function(iconName) {
// Need to first add a NULL GIcon and then set icon_name, to ensure this._iconActor.icon_name = iconName;
// compatibility with -symbolic fallbacks
if (!this.mainIcon)
this.mainIcon = this.addIcon(null);
this.mainIcon.icon_name = iconName;
}, },
setGIcon: function(gicon) { setGIcon: function(gicon) {
if (this.mainIcon) this._iconActor.gicon = gicon;
this.mainIcon.gicon = gicon;
else
this.mainIcon = this.addIcon(gicon);
} }
}); });

View File

@ -153,8 +153,26 @@ const PlacesManager = new Lang.Class({
Gio.app_info_launch_default_for_uri(desktopUri, _makeLaunchContext(params)); Gio.app_info_launch_default_for_uri(desktopUri, _makeLaunchContext(params));
}); });
this._connect = new PlaceInfo('special:connect', _("Connect to..."),
function (size) {
// do NOT use St.Icon here, it crashes the shell
// see wanda.js for details
return St.TextureCache.get_default().load_icon_name(null,
'applications-internet',
St.IconType.FULLCOLOR,
size);
},
function (params) {
// BUG: nautilus-connect-server doesn't have a desktop file, so we can't
// launch it with the workspace from params. It's probably pretty rare
// and odd to drag this place onto a workspace in any case
Util.spawn(['nautilus-connect-server']);
});
this._defaultPlaces.push(this._home); this._defaultPlaces.push(this._home);
this._defaultPlaces.push(this._desktopMenu); this._defaultPlaces.push(this._desktopMenu);
this._defaultPlaces.push(this._connect);
/* /*
* Show devices, code more or less ported from nautilus-places-sidebar.c * Show devices, code more or less ported from nautilus-places-sidebar.c
@ -171,7 +189,7 @@ const PlacesManager = new Lang.Class({
this._volumeMonitor.connect('drive-changed', Lang.bind(this, this._updateDevices)); this._volumeMonitor.connect('drive-changed', Lang.bind(this, this._updateDevices));
this._updateDevices(); this._updateDevices();
this._bookmarksPath = GLib.build_filenamev([GLib.get_user_config_dir(), 'gtk-3.0', 'bookmarks']); this._bookmarksPath = GLib.build_filenamev([GLib.get_home_dir(), '.gtk-bookmarks']);
this._bookmarksFile = Gio.file_new_for_path(this._bookmarksPath); this._bookmarksFile = Gio.file_new_for_path(this._bookmarksPath);
this._monitor = this._bookmarksFile.monitor_file(Gio.FileMonitorFlags.NONE, null); this._monitor = this._bookmarksFile.monitor_file(Gio.FileMonitorFlags.NONE, null);
this._bookmarkTimeoutId = 0; this._bookmarkTimeoutId = 0;
@ -347,13 +365,12 @@ const PlaceSearchProvider = new Lang.Class({
_init: function() { _init: function() {
this.parent(_("PLACES & DEVICES")); this.parent(_("PLACES & DEVICES"));
this.placesManager = new PlacesManager();
}, },
getResultMetas: function(resultIds, callback) { getResultMetas: function(resultIds) {
let metas = []; let metas = [];
for (let i = 0; i < resultIds.length; i++) { for (let i = 0; i < resultIds.length; i++) {
let placeInfo = this.placesManager.lookupPlaceById(resultIds[i]); let placeInfo = Main.placesManager.lookupPlaceById(resultIds[i]);
if (!placeInfo) if (!placeInfo)
metas.push(null); metas.push(null);
else else
@ -364,22 +381,24 @@ const PlaceSearchProvider = new Lang.Class({
} }
}); });
} }
callback(metas); return metas;
}, },
activateResult: function(id, params) { activateResult: function(id, params) {
let placeInfo = this.placesManager.lookupPlaceById(id); let placeInfo = Main.placesManager.lookupPlaceById(id);
placeInfo.launch(params); placeInfo.launch(params);
}, },
_compareResultMeta: function (idA, idB) { _compareResultMeta: function (idA, idB) {
let infoA = this.placesManager.lookupPlaceById(idA); let infoA = Main.placesManager.lookupPlaceById(idA);
let infoB = this.placesManager.lookupPlaceById(idB); let infoB = Main.placesManager.lookupPlaceById(idB);
return infoA.name.localeCompare(infoB.name); return infoA.name.localeCompare(infoB.name);
}, },
_searchPlaces: function(places, terms) { _searchPlaces: function(places, terms) {
let multiplePrefixResults = [];
let prefixResults = []; let prefixResults = [];
let multipleSubstringResults = [];
let substringResults = []; let substringResults = [];
terms = terms.map(String.toLowerCase); terms = terms.map(String.toLowerCase);
@ -387,26 +406,29 @@ const PlaceSearchProvider = new Lang.Class({
for (let i = 0; i < places.length; i++) { for (let i = 0; i < places.length; i++) {
let place = places[i]; let place = places[i];
let mtype = place.matchTerms(terms); let mtype = place.matchTerms(terms);
if (mtype == Search.MatchType.PREFIX) if (mtype == Search.MatchType.MULTIPLE_PREFIX)
multiplePrefixResults.push(place.id);
else if (mtype == Search.MatchType.PREFIX)
prefixResults.push(place.id); prefixResults.push(place.id);
else if (mtype == Search.MatchType.MULTIPLE_SUBSTRING)
multipleSubstringResults.push(place.id);
else if (mtype == Search.MatchType.SUBSTRING) else if (mtype == Search.MatchType.SUBSTRING)
substringResults.push(place.id); substringResults.push(place.id);
} }
prefixResults.sort(Lang.bind(this, this._compareResultMeta)); multiplePrefixResults.sort(this._compareResultMeta);
substringResults.sort(Lang.bind(this, this._compareResultMeta)); prefixResults.sort(this._compareResultMeta);
multipleSubstringResults.sort(this._compareResultMeta);
this.searchSystem.pushResults(this, prefixResults.concat(substringResults)); substringResults.sort(this._compareResultMeta);
return multiplePrefixResults.concat(prefixResults.concat(multipleSubstringResults.concat(substringResults)));
}, },
getInitialResultSet: function(terms) { getInitialResultSet: function(terms) {
let places = this.placesManager.getAllPlaces(); let places = Main.placesManager.getAllPlaces();
this._searchPlaces(places, terms); return this._searchPlaces(places, terms);
}, },
getSubsearchResultSet: function(previousResults, terms) { getSubsearchResultSet: function(previousResults, terms) {
let places = previousResults.map(Lang.bind(this, function(id) { let places = previousResults.map(function (id) { return Main.placesManager.lookupPlaceById(id); });
return this.placesManager.lookupPlaceById(id); return this._searchPlaces(places, terms);
}));
this._searchPlaces(places, terms);
} }
}); });

View File

@ -1,126 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Shell = imports.gi.Shell;
// We stop polling if the user is idle for more than this amount of time
const IDLE_TIME = 1000;
// This file implements a reasonably efficient system for tracking the position
// of the mouse pointer. We simply query the pointer from the X server in a loop,
// but we turn off the polling when the user is idle.
let _pointerWatcher = null;
function getPointerWatcher() {
if (_pointerWatcher == null)
_pointerWatcher = new PointerWatcher();
return _pointerWatcher;
}
const PointerWatch = new Lang.Class({
Name: 'PointerWatch',
_init: function(watcher, interval, callback) {
this.watcher = watcher;
this.interval = interval;
this.callback = callback;
},
// remove:
// remove this watch. This function may safely be called
// while the callback is executing.
remove: function() {
this.watcher._removeWatch(this);
}
});
const PointerWatcher = new Lang.Class({
Name: 'PointerWatcher',
_init: function() {
let idleMonitor = Shell.IdleMonitor.get();
idleMonitor.add_watch(IDLE_TIME,
Lang.bind(this, this._onIdleMonitorWatch));
this._idle = idleMonitor.get_idletime() > IDLE_TIME;
this._watches = [];
this.pointerX = null;
this.pointerY = null;
},
// addWatch:
// @interval: hint as to the time resolution needed. When the user is
// not idle, the position of the pointer will be queried at least
// once every this many milliseconds.
// @callback: function to call when the pointer position changes - takes
// two arguments, X and Y.
//
// Set up a watch on the position of the mouse pointer. Returns a
// PointerWatch object which has a remove() method to remove the watch.
addWatch: function(interval, callback) {
// Avoid unreliably calling the watch for the current position
this._updatePointer();
let watch = new PointerWatch(this, interval, callback);
this._watches.push(watch);
this._updateTimeout();
return watch;
},
_removeWatch: function(watch) {
for (let i = 0; i < this._watches.length; i++) {
if (this._watches[i] == watch) {
this._watches.splice(i, 1);
this._updateTimeout();
return;
}
}
},
_onIdleMonitorWatch: function(monitor, id, userBecameIdle) {
this._idle = userBecameIdle;
if (!userBecameIdle)
this._updatePointer();
this._updateTimeout();
},
_updateTimeout: function() {
if (this._timeoutId) {
Mainloop.source_remove(this._timeoutId);
this._timeoutId = 0;
}
if (this._idle || this._watches.length == 0)
return;
let minInterval = this._watches[0].interval;
for (let i = 1; i < this._watches.length; i++)
minInterval = Math.min(this._watches[i].interval, minInterval);
this._timeoutId = Mainloop.timeout_add(minInterval,
Lang.bind(this, this._onTimeout));
},
_onTimeout: function() {
this._updatePointer();
return true;
},
_updatePointer: function() {
let [x, y, mods] = global.get_pointer();
if (this.pointerX == x && this.pointerY == y)
return;
this.pointerX = x;
this.pointerY = y;
for (let i = 0; i < this._watches.length;) {
let watch = this._watches[i];
watch.callback(x, y);
if (watch == this._watches[i]) // guard against self-removal
i++;
}
}
});

View File

@ -35,9 +35,6 @@ const PolkitAgent = imports.gi.PolkitAgent;
const ModalDialog = imports.ui.modalDialog; const ModalDialog = imports.ui.modalDialog;
const ShellEntry = imports.ui.shellEntry; const ShellEntry = imports.ui.shellEntry;
const UserMenu = imports.ui.userMenu;
const DIALOG_ICON_SIZE = 48;
const AuthenticationDialog = new Lang.Class({ const AuthenticationDialog = new Lang.Class({
Name: 'AuthenticationDialog', Name: 'AuthenticationDialog',
@ -120,11 +117,9 @@ const AuthenticationDialog = new Lang.Class({
let userBox = new St.BoxLayout({ style_class: 'polkit-dialog-user-layout', let userBox = new St.BoxLayout({ style_class: 'polkit-dialog-user-layout',
vertical: false }); vertical: false });
messageBox.add(userBox); messageBox.add(userBox);
this._userAvatar = new UserMenu.UserAvatarWidget(this._user, this._userIcon = new St.Icon();
{ iconSize: DIALOG_ICON_SIZE, this._userIcon.hide();
styleClass: 'polkit-dialog-user-icon' }); userBox.add(this._userIcon,
this._userAvatar.actor.hide();
userBox.add(this._userAvatar.actor,
{ x_fill: true, { x_fill: true,
y_fill: false, y_fill: false,
x_align: St.Align.END, x_align: St.Align.END,
@ -140,17 +135,17 @@ const AuthenticationDialog = new Lang.Class({
this._onUserChanged(); this._onUserChanged();
this._passwordBox = new St.BoxLayout({ vertical: false, style_class: 'prompt-dialog-password-box' }); this._passwordBox = new St.BoxLayout({ vertical: false });
messageBox.add(this._passwordBox); messageBox.add(this._passwordBox);
this._passwordLabel = new St.Label(({ style_class: 'prompt-dialog-password-label' })); this._passwordLabel = new St.Label(({ style_class: 'prompt-dialog-password-label' }));
this._passwordBox.add(this._passwordLabel, { y_fill: false, y_align: St.Align.MIDDLE }); this._passwordBox.add(this._passwordLabel);
this._passwordEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry', this._passwordEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry',
text: "", text: "",
can_focus: true}); can_focus: true});
ShellEntry.addContextMenu(this._passwordEntry, { isPassword: true }); ShellEntry.addContextMenu(this._passwordEntry, { isPassword: true });
this._passwordEntry.clutter_text.connect('activate', Lang.bind(this, this._onEntryActivate)); this._passwordEntry.clutter_text.connect('activate', Lang.bind(this, this._onEntryActivate));
this._passwordBox.add(this._passwordEntry, this._passwordBox.add(this._passwordEntry,
{ expand: true }); {expand: true });
this.setInitialKeyFocus(this._passwordEntry); this.setInitialKeyFocus(this._passwordEntry);
this._passwordBox.hide(); this._passwordBox.hide();
@ -172,7 +167,6 @@ const AuthenticationDialog = new Lang.Class({
*/ */
this._nullMessageLabel = new St.Label({ style_class: 'prompt-dialog-null-label', this._nullMessageLabel = new St.Label({ style_class: 'prompt-dialog-null-label',
text: 'abc'}); text: 'abc'});
this._nullMessageLabel.add_style_class_name('hidden');
this._nullMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; this._nullMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
this._nullMessageLabel.clutter_text.line_wrap = true; this._nullMessageLabel.clutter_text.line_wrap = true;
messageBox.add(this._nullMessageLabel); messageBox.add(this._nullMessageLabel);
@ -183,8 +177,7 @@ const AuthenticationDialog = new Lang.Class({
key: Clutter.Escape key: Clutter.Escape
}, },
{ label: _("Authenticate"), { label: _("Authenticate"),
action: Lang.bind(this, this._onAuthenticateButtonPressed), action: Lang.bind(this, this._onAuthenticateButtonPressed)
default: true
}]); }]);
this._doneEmitted = false; this._doneEmitted = false;
@ -275,7 +268,7 @@ const AuthenticationDialog = new Lang.Class({
_onSessionRequest: function(session, request, echo_on) { _onSessionRequest: function(session, request, echo_on) {
// Cheap localization trick // Cheap localization trick
if (request == 'Password:' || request == 'Password: ') if (request == 'Password:')
this._passwordLabel.set_text(_("Password:")); this._passwordLabel.set_text(_("Password:"));
else else
this._passwordLabel.set_text(request); this._passwordLabel.set_text(request);
@ -319,8 +312,18 @@ const AuthenticationDialog = new Lang.Class({
_onUserChanged: function() { _onUserChanged: function() {
if (this._user.is_loaded) { if (this._user.is_loaded) {
this._userAvatar.update(); if (this._userIcon) {
this._userAvatar.actor.show(); let iconFileName = this._user.get_icon_file();
let iconFile = Gio.file_new_for_path(iconFileName);
let icon;
if (iconFile.query_exists(null)) {
icon = new Gio.FileIcon({file: iconFile});
} else {
icon = new Gio.ThemedIcon({name: 'avatar-default'});
}
this._userIcon.set_gicon (icon);
this._userIcon.show();
}
} }
}, },

View File

@ -37,13 +37,12 @@ const PopupBaseMenuItem = new Lang.Class({
activate: true, activate: true,
hover: true, hover: true,
sensitive: true, sensitive: true,
style_class: null, style_class: null
can_focus: true
}); });
this.actor = new Shell.GenericContainer({ style_class: 'popup-menu-item', this.actor = new Shell.GenericContainer({ style_class: 'popup-menu-item',
reactive: params.reactive, reactive: params.reactive,
track_hover: params.reactive, track_hover: params.reactive,
can_focus: params.can_focus, can_focus: params.reactive,
accessible_role: Atk.Role.MENU_ITEM}); accessible_role: Atk.Role.MENU_ITEM});
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth)); 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('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
@ -70,9 +69,10 @@ const PopupBaseMenuItem = new Lang.Class({
} }
if (params.reactive && params.hover) if (params.reactive && params.hover)
this.actor.connect('notify::hover', Lang.bind(this, this._onHoverChanged)); this.actor.connect('notify::hover', Lang.bind(this, this._onHoverChanged));
if (params.reactive) {
this.actor.connect('key-focus-in', Lang.bind(this, this._onKeyFocusIn)); this.actor.connect('key-focus-in', Lang.bind(this, this._onKeyFocusIn));
this.actor.connect('key-focus-out', Lang.bind(this, this._onKeyFocusOut)); this.actor.connect('key-focus-out', Lang.bind(this, this._onKeyFocusOut));
}
}, },
_onStyleChanged: function (actor) { _onStyleChanged: function (actor) {
@ -136,6 +136,10 @@ const PopupBaseMenuItem = new Lang.Class({
this.actor.reactive = sensitive; this.actor.reactive = sensitive;
this.actor.can_focus = sensitive; this.actor.can_focus = sensitive;
if (sensitive)
this.actor.remove_style_pseudo_class('insensitive');
else
this.actor.add_style_pseudo_class('insensitive');
this.emit('sensitive-changed', sensitive); this.emit('sensitive-changed', sensitive);
}, },
@ -180,14 +184,12 @@ const PopupBaseMenuItem = new Lang.Class({
this._dot = new St.DrawingArea({ style_class: 'popup-menu-item-dot' }); this._dot = new St.DrawingArea({ style_class: 'popup-menu-item-dot' });
this._dot.connect('repaint', Lang.bind(this, this._onRepaintDot)); this._dot.connect('repaint', Lang.bind(this, this._onRepaintDot));
this.actor.add_actor(this._dot); this.actor.add_actor(this._dot);
this.actor.add_accessible_state (Atk.StateType.CHECKED);
} else { } else {
if (!this._dot) if (!this._dot)
return; return;
this._dot.destroy(); this._dot.destroy();
this._dot = null; this._dot = null;
this.actor.remove_accessible_state (Atk.StateType.CHECKED);
} }
}, },
@ -398,8 +400,7 @@ const PopupSeparatorMenuItem = new Lang.Class({
Extends: PopupBaseMenuItem, Extends: PopupBaseMenuItem,
_init: function () { _init: function () {
this.parent({ reactive: false, this.parent({ reactive: false });
can_focus: false});
this._drawingArea = new St.DrawingArea({ style_class: 'popup-separator-menu-item' }); this._drawingArea = new St.DrawingArea({ style_class: 'popup-separator-menu-item' });
this.addActor(this._drawingArea, { span: -1, expand: true }); this.addActor(this._drawingArea, { span: -1, expand: true });
@ -718,8 +719,7 @@ const Switch = new Lang.Class({
_init: function(state) { _init: function(state) {
this.actor = new St.Bin({ style_class: 'toggle-switch', this.actor = new St.Bin({ style_class: 'toggle-switch',
accessible_role: Atk.Role.CHECK_BOX, accessible_role: Atk.Role.CHECK_BOX});
can_focus: true });
// Translators: this MUST be either "toggle-switch-us" // Translators: this MUST be either "toggle-switch-us"
// (for toggle switches containing the English words // (for toggle switches containing the English words
// "ON" and "OFF") or "toggle-switch-intl" (for toggle // "ON" and "OFF") or "toggle-switch-intl" (for toggle
@ -773,10 +773,12 @@ const PopupSwitchMenuItem = new Lang.Class({
this._statusLabel.text = text; this._statusLabel.text = text;
this._statusBin.child = this._statusLabel; this._statusBin.child = this._statusLabel;
this.actor.reactive = false; this.actor.reactive = false;
this.actor.can_focus = false;
this.actor.accessible_role = Atk.Role.MENU_ITEM; this.actor.accessible_role = Atk.Role.MENU_ITEM;
} else { } else {
this._statusBin.child = this._switch.actor; this._statusBin.child = this._switch.actor;
this.actor.reactive = true; this.actor.reactive = true;
this.actor.can_focus = true;
this.actor.accessible_role = Atk.Role.CHECK_MENU_ITEM; this.actor.accessible_role = Atk.Role.CHECK_MENU_ITEM;
} }
this.checkAccessibleState(); this.checkAccessibleState();
@ -867,9 +869,12 @@ const PopupMenuBase = new Lang.Class({
// for the menu which causes its prelight state to freeze // for the menu which causes its prelight state to freeze
this.blockSourceEvents = false; this.blockSourceEvents = false;
// Can be set while a menu is up to let all events through without special
// menu handling useful for scrollbars in menus, and probably not otherwise.
this.passEvents = false;
this._activeMenuItem = null; this._activeMenuItem = null;
this._childMenus = []; this._childMenus = [];
this._settingsActions = { };
}, },
addAction: function(title, callback) { addAction: function(title, callback) {
@ -883,7 +888,8 @@ const PopupMenuBase = new Lang.Class({
}, },
addSettingsAction: function(title, desktopFile) { addSettingsAction: function(title, desktopFile) {
if (!Main.sessionMode.allowSettings) // Don't allow user settings to get edited unless we're in a user session
if (global.session_type != Shell.SessionType.USER)
return null; return null;
let menuItem = this.addAction(title, function() { let menuItem = this.addAction(title, function() {
@ -897,21 +903,11 @@ const PopupMenuBase = new Lang.Class({
Main.overview.hide(); Main.overview.hide();
app.activate(); app.activate();
}); });
this._settingsActions[desktopFile] = menuItem;
return menuItem; return menuItem;
}, },
setSettingsVisibility: function(visible) {
for (let id in this._settingsActions) {
let item = this._settingsActions[id];
item.actor.visible = visible;
}
},
isEmpty: function() { isEmpty: function() {
return this.box.get_n_children() == 0; return this.box.get_children().length == 0;
}, },
isChildMenu: function(menu) { isChildMenu: function(menu) {
@ -947,7 +943,7 @@ const PopupMenuBase = new Lang.Class({
_connectSubMenuSignals: function(object, menu) { _connectSubMenuSignals: function(object, menu) {
object._subMenuActivateId = menu.connect('activate', Lang.bind(this, function() { object._subMenuActivateId = menu.connect('activate', Lang.bind(this, function() {
this.emit('activate'); this.emit('activate');
this.close(BoxPointer.PopupAnimation.FULL); this.close(true);
})); }));
object._subMenuActiveChangeId = menu.connect('active-changed', Lang.bind(this, function(submenu, submenuItem) { object._subMenuActiveChangeId = menu.connect('active-changed', Lang.bind(this, function(submenu, submenuItem) {
if (this._activeMenuItem && this._activeMenuItem != submenuItem) if (this._activeMenuItem && this._activeMenuItem != submenuItem)
@ -982,7 +978,7 @@ const PopupMenuBase = new Lang.Class({
})); }));
menuItem._activateId = menuItem.connect('activate', Lang.bind(this, function (menuItem, event) { menuItem._activateId = menuItem.connect('activate', Lang.bind(this, function (menuItem, event) {
this.emit('activate', menuItem); this.emit('activate', menuItem);
this.close(BoxPointer.PopupAnimation.FULL); this.close(true);
})); }));
// the weird name is to avoid a conflict with some random property // the weird name is to avoid a conflict with some random property
// the menuItem may have, called destroyId // the menuItem may have, called destroyId
@ -1052,17 +1048,14 @@ const PopupMenuBase = new Lang.Class({
if (menuItem instanceof PopupMenuSection) { if (menuItem instanceof PopupMenuSection) {
this._connectSubMenuSignals(menuItem, menuItem); this._connectSubMenuSignals(menuItem, menuItem);
menuItem._parentOpenStateChangedId = this.connect('open-state-changed', menuItem._closingId = this.connect('open-state-changed',
function(self, open) { function(self, open) {
if (open) if (!open)
menuItem.open(); menuItem.close(false);
else
menuItem.close();
}); });
menuItem.connect('destroy', Lang.bind(this, function() { menuItem.connect('destroy', Lang.bind(this, function() {
menuItem.disconnect(menuItem._subMenuActivateId); menuItem.disconnect(menuItem._subMenuActivateId);
menuItem.disconnect(menuItem._subMenuActiveChangeId); menuItem.disconnect(menuItem._subMenuActiveChangeId);
this.disconnect(menuItem._parentOpenStateChangedId);
this.length--; this.length--;
})); }));
@ -1075,7 +1068,7 @@ const PopupMenuBase = new Lang.Class({
this._connectItemSignals(menuItem); this._connectItemSignals(menuItem);
menuItem._closingId = this.connect('open-state-changed', function(self, open) { menuItem._closingId = this.connect('open-state-changed', function(self, open) {
if (!open) if (!open)
menuItem.menu.close(BoxPointer.PopupAnimation.FADE); menuItem.menu.close(false);
}); });
} else if (menuItem instanceof PopupSeparatorMenuItem) { } else if (menuItem instanceof PopupSeparatorMenuItem) {
this._connectItemSignals(menuItem); this._connectItemSignals(menuItem);
@ -1162,9 +1155,9 @@ const PopupMenuBase = new Lang.Class({
toggle: function() { toggle: function() {
if (this.isOpen) if (this.isOpen)
this.close(BoxPointer.PopupAnimation.FULL); this.close(true);
else else
this.open(BoxPointer.PopupAnimation.FULL); this.open(true);
}, },
destroy: function() { destroy: function() {
@ -1225,7 +1218,7 @@ const PopupMenu = new Lang.Class({
_onKeyPressEvent: function(actor, event) { _onKeyPressEvent: function(actor, event) {
if (event.get_key_symbol() == Clutter.Escape) { if (event.get_key_symbol() == Clutter.Escape) {
this.close(BoxPointer.PopupAnimation.FULL); this.close(true);
return true; return true;
} }
@ -1288,6 +1281,24 @@ const PopupSubMenu = new Lang.Class({
hscrollbar_policy: Gtk.PolicyType.NEVER, hscrollbar_policy: Gtk.PolicyType.NEVER,
vscrollbar_policy: Gtk.PolicyType.NEVER }); vscrollbar_policy: Gtk.PolicyType.NEVER });
// StScrollbar plays dirty tricks with events, calling
// clutter_set_motion_events_enabled (FALSE) during the scroll; this
// confuses our event tracking, so we just turn it off during the
// scroll.
let vscroll = this.actor.get_vscroll_bar();
vscroll.connect('scroll-start',
Lang.bind(this, function() {
let topMenu = this._getTopMenu();
if (topMenu)
topMenu.passEvents = true;
}));
vscroll.connect('scroll-stop',
Lang.bind(this, function() {
let topMenu = this._getTopMenu();
if (topMenu)
topMenu.passEvents = false;
}));
this.actor.add_actor(this.box); this.actor.add_actor(this.box);
this.actor._delegate = this; this.actor._delegate = this;
this.actor.clip_to_allocation = true; this.actor.clip_to_allocation = true;
@ -1409,7 +1420,7 @@ const PopupSubMenu = new Lang.Class({
// Move focus back to parent menu if the user types Left. // Move focus back to parent menu if the user types Left.
if (this.isOpen && event.get_key_symbol() == Clutter.KEY_Left) { if (this.isOpen && event.get_key_symbol() == Clutter.KEY_Left) {
this.close(BoxPointer.PopupAnimation.FULL); this.close(true);
this.sourceActor._delegate.setActive(true); this.sourceActor._delegate.setActive(true);
return true; return true;
} }
@ -1443,7 +1454,7 @@ const PopupMenuSection = new Lang.Class({
// deliberately ignore any attempt to open() or close(), but emit the // deliberately ignore any attempt to open() or close(), but emit the
// corresponding signal so children can still pick it up // corresponding signal so children can still pick it up
open: function() { this.emit('open-state-changed', true); }, open: function(animate) { this.emit('open-state-changed', true); },
close: function() { this.emit('open-state-changed', false); }, close: function() { this.emit('open-state-changed', false); },
destroy: function() { destroy: function() {
@ -1491,7 +1502,7 @@ const PopupSubMenuMenuItem = new Lang.Class({
let symbol = event.get_key_symbol(); let symbol = event.get_key_symbol();
if (symbol == Clutter.KEY_Right) { if (symbol == Clutter.KEY_Right) {
this.menu.open(BoxPointer.PopupAnimation.FULL); this.menu.open(true);
this.menu.actor.navigate_focus(null, Gtk.DirectionType.DOWN, false); this.menu.actor.navigate_focus(null, Gtk.DirectionType.DOWN, false);
return true; return true;
} else if (symbol == Clutter.KEY_Left && this.menu.isOpen) { } else if (symbol == Clutter.KEY_Left && this.menu.isOpen) {
@ -1503,7 +1514,7 @@ const PopupSubMenuMenuItem = new Lang.Class({
}, },
activate: function(event) { activate: function(event) {
this.menu.open(BoxPointer.PopupAnimation.FULL); this.menu.open(true);
}, },
_onButtonReleaseEvent: function(actor) { _onButtonReleaseEvent: function(actor) {
@ -1530,7 +1541,7 @@ const PopupComboMenu = new Lang.Class({
_onKeyPressEvent: function(actor, event) { _onKeyPressEvent: function(actor, event) {
if (event.get_key_symbol() == Clutter.Escape) { if (event.get_key_symbol() == Clutter.Escape) {
this.close(BoxPointer.PopupAnimation.FULL); this.close(true);
return true; return true;
} }
@ -1882,6 +1893,10 @@ const RemoteMenu = new Lang.Class({
} }
item.actor.reactive = item.actor.can_focus = action.enabled; 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() { destroyId = item.connect('destroy', Lang.bind(this, function() {
item.disconnect(destroyId); item.disconnect(destroyId);
@ -1906,7 +1921,7 @@ const RemoteMenu = new Lang.Class({
while (k0 < currentItems.length && currentItems[k0]._ignored) while (k0 < currentItems.length && currentItems[k0]._ignored)
k0++; k0++;
// find the right menu item matching the model item // find the right menu item matching the model item
for (j0 = 0; k0 < currentItems.length && j0 < position; j0++, k0++) { for (j0 = 0; j0 < position; j0++, k0++) {
if (currentItems[k0]._ignored) if (currentItems[k0]._ignored)
k0++; k0++;
} }
@ -1916,7 +1931,7 @@ const RemoteMenu = new Lang.Class({
for (k = k0; k < currentItems.length; k++) for (k = k0; k < currentItems.length; k++)
currentItems[k].destroy(); currentItems[k].destroy();
} else { } else {
for (j = j0, k = k0; k < currentItems.length && j < j0 + removed; j++, k++) { for (j = j0, k = k0; j < j0 + removed; j++, k++) {
currentItems[k].destroy(); currentItems[k].destroy();
if (currentItems[k]._ignored) if (currentItems[k]._ignored)
@ -1947,9 +1962,8 @@ const RemoteMenu = new Lang.Class({
k++; k++;
} }
} else if (changeSignal) { } else if (changeSignal) {
let signalId = this.actionGroup.connect(changeSignal, Lang.bind(this, function(actionGroup, actionName) { let signalId = this.actionGroup.connect(changeSignal, Lang.bind(this, function() {
actionGroup.disconnect(signalId); this.actionGroup.disconnect(signalId);
if (this._actions[actionName]) return;
// force a full update // force a full update
this._modelChanged(model, 0, -1, model.get_n_items(), target); this._modelChanged(model, 0, -1, model.get_n_items(), target);
@ -2014,6 +2028,11 @@ const RemoteMenu = new Lang.Class({
for (let i = 0; i < action.items.length; i++) { for (let i = 0; i < action.items.length; i++) {
let item = action.items[i]; let item = action.items[i];
item.actor.reactive = item.actor.can_focus = action.enabled; 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');
} }
} }
} }
@ -2175,11 +2194,11 @@ const PopupMenuManager = new Lang.Class({
let oldMenu = this._activeMenu; let oldMenu = this._activeMenu;
this._activeMenu = null; this._activeMenu = null;
for (let i = this._menuStack.length - 1; i >= 0; i--) for (let i = this._menuStack.length - 1; i >= 0; i--)
this._menuStack[i].close(BoxPointer.PopupAnimation.FADE); this._menuStack[i].close(false);
oldMenu.close(BoxPointer.PopupAnimation.FADE); oldMenu.close(false);
newMenu.open(BoxPointer.PopupAnimation.FADE); newMenu.open(false);
} else } else
newMenu.open(BoxPointer.PopupAnimation.FULL); newMenu.open(true);
}, },
_onMenuSourceEnter: function(menu) { _onMenuSourceEnter: function(menu) {
@ -2264,6 +2283,9 @@ const PopupMenuManager = new Lang.Class({
this._owner.menuEventFilter(event)) this._owner.menuEventFilter(event))
return true; return true;
if (this._activeMenu != null && this._activeMenu.passEvents)
return false;
if (this._didPop) { if (this._didPop) {
this._didPop = false; this._didPop = false;
return true; return true;
@ -2291,6 +2313,6 @@ const PopupMenuManager = new Lang.Class({
_closeMenu: function() { _closeMenu: function() {
if (this._activeMenu != null) if (this._activeMenu != null)
this._activeMenu.close(BoxPointer.PopupAnimation.FULL); this._activeMenu.close(true);
} }
}); });

View File

@ -34,17 +34,16 @@ var SearchProviderProxy = Gio.DBusProxy.makeProxyWrapper(SearchProviderIface);
function loadRemoteSearchProviders(addProviderCallback) { function loadRemoteSearchProviders(addProviderCallback) {
let dataDirs = GLib.get_system_data_dirs(); let dataDirs = GLib.get_system_data_dirs();
let loadedProviders = {};
for (let i = 0; i < dataDirs.length; i++) { for (let i = 0; i < dataDirs.length; i++) {
let path = GLib.build_filenamev([dataDirs[i], 'gnome-shell', 'search-providers']); let path = GLib.build_filenamev([dataDirs[i], 'gnome-shell', 'search-providers']);
let dir = Gio.file_new_for_path(path); let dir = Gio.file_new_for_path(path);
if (!dir.query_exists(null)) if (!dir.query_exists(null))
continue; continue;
loadRemoteSearchProvidersFromDir(dir, loadedProviders, addProviderCallback); loadRemoteSearchProvidersFromDir(dir, addProviderCallback);
} }
}; };
function loadRemoteSearchProvidersFromDir(dir, loadedProviders, addProviderCallback) { function loadRemoteSearchProvidersFromDir(dir, addProviderCallback) {
let dirPath = dir.get_path(); let dirPath = dir.get_path();
FileUtils.listDirAsync(dir, Lang.bind(this, function(files) { FileUtils.listDirAsync(dir, Lang.bind(this, function(files) {
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
@ -63,34 +62,15 @@ function loadRemoteSearchProvidersFromDir(dir, loadedProviders, addProviderCallb
let remoteProvider, title; let remoteProvider, title;
try { try {
let group = KEY_FILE_GROUP; let group = KEY_FILE_GROUP;
let icon = keyfile.get_string(group, 'Icon');
let busName = keyfile.get_string(group, 'BusName'); let busName = keyfile.get_string(group, 'BusName');
let objectPath = keyfile.get_string(group, 'ObjectPath'); let objectPath = keyfile.get_string(group, 'ObjectPath');
title = keyfile.get_locale_string(group, 'Title', null);
if (loadedProviders[objectPath])
continue;
let appInfo = null;
try {
let desktopId = keyfile.get_string(group, 'DesktopId');
appInfo = Gio.DesktopAppInfo.new(desktopId);
} catch (e) {
}
let icon;
if (appInfo) {
icon = appInfo.get_icon();
title = appInfo.get_name();
} else {
let iconName = keyfile.get_string(group, 'Icon');
icon = new Gio.ThemedIcon({ name: iconName });
title = keyfile.get_locale_string(group, 'Title', null);
}
remoteProvider = new RemoteSearchProvider(title, remoteProvider = new RemoteSearchProvider(title,
icon, icon,
busName, busName,
objectPath); objectPath);
loadedProviders[objectPath] = remoteProvider;
} catch(e) { } catch(e) {
log('Failed to add search provider "%s": %s'.format(title, e.toString())); log('Failed to add search provider "%s": %s'.format(title, e.toString()));
continue; continue;
@ -111,13 +91,15 @@ const RemoteSearchProvider = new Lang.Class({
dbusName, dbusPath); dbusName, dbusPath);
this.parent(title.toUpperCase()); this.parent(title.toUpperCase());
this.async = true;
this._cancellable = new Gio.Cancellable(); this._cancellable = new Gio.Cancellable();
}, },
createIcon: function(size, meta) { createIcon: function(size, meta) {
if (meta['gicon']) { if (meta['gicon']) {
return new St.Icon({ gicon: Gio.icon_new_for_string(meta['gicon']), return new St.Icon({ gicon: Gio.icon_new_for_string(meta['gicon']),
icon_size: size }); icon_size: size,
icon_type: St.IconType.FULLCOLOR });
} else if (meta['icon-data']) { } else if (meta['icon-data']) {
let [width, height, rowStride, hasAlpha, let [width, height, rowStride, hasAlpha,
bitsPerSample, nChannels, data] = meta['icon-data']; bitsPerSample, nChannels, data] = meta['icon-data'];
@ -128,7 +110,8 @@ const RemoteSearchProvider = new Lang.Class({
// Ugh, but we want to fall back to something ... // Ugh, but we want to fall back to something ...
return new St.Icon({ icon_name: 'text-x-generic', return new St.Icon({ icon_name: 'text-x-generic',
icon_size: size }); icon_size: size,
icon_type: St.IconType.FULLCOLOR });
}, },
_getResultsFinished: function(results, error) { _getResultsFinished: function(results, error) {
@ -137,7 +120,7 @@ const RemoteSearchProvider = new Lang.Class({
this.searchSystem.pushResults(this, results[0]); this.searchSystem.pushResults(this, results[0]);
}, },
getInitialResultSet: function(terms) { getInitialResultSetAsync: function(terms) {
this._cancellable.cancel(); this._cancellable.cancel();
this._cancellable.reset(); this._cancellable.reset();
try { try {
@ -150,7 +133,7 @@ const RemoteSearchProvider = new Lang.Class({
} }
}, },
getSubsearchResultSet: function(previousResults, newTerms) { getSubsearchResultSetAsync: function(previousResults, newTerms) {
this._cancellable.cancel(); this._cancellable.cancel();
this._cancellable.reset(); this._cancellable.reset();
try { try {
@ -181,7 +164,7 @@ const RemoteSearchProvider = new Lang.Class({
callback(resultMetas); callback(resultMetas);
}, },
getResultMetas: function(ids, callback) { getResultMetasAsync: function(ids, callback) {
this._cancellable.cancel(); this._cancellable.cancel();
this._cancellable.reset(); this._cancellable.reset();
try { try {

View File

@ -1,783 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Cairo = imports.cairo;
const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GnomeDesktop = imports.gi.GnomeDesktop;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta;
const Signals = imports.signals;
const St = imports.gi.St;
const TweenerEquations = imports.tweener.equations;
const GnomeSession = imports.misc.gnomeSession;
const Layout = imports.ui.layout;
const LoginManager = imports.misc.loginManager;
const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;
const Tweener = imports.ui.tweener;
const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver';
const LOCK_ENABLED_KEY = 'lock-enabled';
const CURTAIN_SLIDE_TIME = 0.5;
// fraction of screen height the arrow must reach before completing
// the slide up automatically
const ARROW_DRAG_TRESHOLD = 0.1;
// Parameters for the arrow animation
const N_ARROWS = 3;
const ARROW_ANIMATION_TIME = 0.6;
const ARROW_ANIMATION_PEAK_OPACITY = 0.4;
// The distance in px that the lock screen will move to when pressing
// a key that has no effect in the lock screen (bumping it)
const BUMP_SIZE = 25;
const BUMP_TIME = 0.3;
const SUMMARY_ICON_SIZE = 48;
// Lightbox fading times
// STANDARD_FADE_TIME is used when the session goes idle, while
// SHORT_FADE_TIME is used when requesting lock explicitly from the user menu
const STANDARD_FADE_TIME = 10;
const SHORT_FADE_TIME = 0.8;
const Clock = new Lang.Class({
Name: 'ScreenShieldClock',
CLOCK_FORMAT_KEY: 'clock-format',
CLOCK_SHOW_SECONDS_KEY: 'clock-show-seconds',
_init: function() {
this.actor = new St.BoxLayout({ style_class: 'screen-shield-clock',
vertical: true });
this._time = new St.Label({ style_class: 'screen-shield-clock-time' });
this._date = new St.Label({ style_class: 'screen-shield-clock-date' });
this.actor.add(this._time, { x_align: St.Align.MIDDLE });
this.actor.add(this._date, { x_align: St.Align.MIDDLE });
this._wallClock = new GnomeDesktop.WallClock({ time_only: true });
this._wallClock.connect('notify::clock', Lang.bind(this, this._updateClock));
this._updateClock();
},
_updateClock: function() {
this._time.text = this._wallClock.clock;
let date = new Date();
/* Translators: This is a time format for a date in
long format */
this._date.text = date.toLocaleFormat(_("%A, %B %d"));
},
destroy: function() {
this.actor.destroy();
this._wallClock.run_dispose();
}
});
const NotificationsBox = new Lang.Class({
Name: 'NotificationsBox',
_init: function() {
this.actor = new St.BoxLayout({ vertical: true,
name: 'screenShieldNotifications',
style_class: 'screen-shield-notifications-box' });
this._residentNotificationBox = new St.BoxLayout({ vertical: true,
style_class: 'screen-shield-notifications-box' });
let scrollView = new St.ScrollView({ x_fill: false, x_align: St.Align.MIDDLE });
this._persistentNotificationBox = new St.BoxLayout({ vertical: true,
style_class: 'screen-shield-notifications-box' });
scrollView.add_actor(this._persistentNotificationBox);
this.actor.add(this._residentNotificationBox, { x_fill: true });
this.actor.add(scrollView, { x_fill: true, x_align: St.Align.MIDDLE });
this._items = [];
Main.messageTray.getSummaryItems().forEach(Lang.bind(this, function(item) {
this._summaryItemAdded(Main.messageTray, item, true);
}));
this._updateVisibility();
this._summaryAddedId = Main.messageTray.connect('summary-item-added', Lang.bind(this, this._summaryItemAdded));
},
destroy: function() {
if (this._summaryAddedId) {
Main.messageTray.disconnect(this._summaryAddedId);
this._summaryAddedId = 0;
}
for (let i = 0; i < this._items.length; i++)
this._removeItem(this._items[i]);
this._items = [];
this.actor.destroy();
},
_updateVisibility: function() {
if (this._residentNotificationBox.get_n_children() > 0) {
this.actor.show();
return;
}
let children = this._persistentNotificationBox.get_children();
this.actor.visible = children.some(function(a) { return a.visible; });
},
_sourceIsResident: function(source) {
return source.hasResidentNotification() && !source.isChat;
},
_makeNotificationCountText: function(count, isChat) {
if (isChat)
return ngettext("%d new message", "%d new messages", count).format(count);
else
return ngettext("%d new notification", "%d new notifications", count).format(count);
},
_makeNotificationSource: function(source) {
let box = new St.BoxLayout({ style_class: 'screen-shield-notification-source' });
let sourceActor = new MessageTray.SourceActor(source, SUMMARY_ICON_SIZE);
box.add(sourceActor.actor, { y_fill: true });
let textBox = new St.BoxLayout({ vertical: true });
box.add(textBox);
let label = new St.Label({ text: source.title,
style_class: 'screen-shield-notification-label' });
textBox.add(label);
let count = source.unseenCount;
let countLabel = new St.Label({ text: this._makeNotificationCountText(count, source.isChat),
style_class: 'screen-shield-notification-count-text' });
textBox.add(countLabel);
box.visible = count != 0;
return [box, countLabel];
},
_summaryItemAdded: function(tray, item, dontUpdateVisibility) {
// Ignore transient sources, or sources explicitly marked not to show
// in the lock screen
if (item.source.isTransient || !item.source.showInLockScreen)
return;
let obj = {
item: item,
source: item.source,
resident: this._sourceIsResident(item.source),
contentUpdatedId: 0,
sourceDestroyId: 0,
sourceBox: null,
countLabel: null,
};
if (obj.resident) {
this._residentNotificationBox.add(item.notificationStackWidget);
item.closeButtonVisible = false;
item.prepareNotificationStackForShowing();
} else {
[obj.sourceBox, obj.countLabel] = this._makeNotificationSource(item.source);
this._persistentNotificationBox.add(obj.sourceBox, { x_fill: false, x_align: St.Align.MIDDLE });
}
obj.contentUpdatedId = item.connect('content-updated', Lang.bind(this, this._onItemContentUpdated));
obj.sourceCountChangedId = item.source.connect('count-updated', Lang.bind(this, this._onSourceChanged));
obj.sourceTitleChangedId = item.source.connect('title-changed', Lang.bind(this, this._onSourceChanged));
obj.sourceDestroyId = item.source.connect('destroy', Lang.bind(this, this._onSourceDestroy));
this._items.push(obj);
if (!dontUpdateVisibility)
this._updateVisibility();
},
_findSource: function(source) {
for (let i = 0; i < this._items.length; i++) {
if (this._items[i].source == source)
return i;
}
return -1;
},
_onItemContentUpdated: function(item) {
let obj = this._items[this._findSource(item.source)];
this._updateItem(obj);
},
_onSourceChanged: function(source) {
let obj = this._items[this._findSource(source)];
this._updateItem(obj);
},
_updateItem: function(obj) {
let itemShouldBeResident = this._sourceIsResident(obj.source);
if (itemShouldBeResident && obj.resident) {
// Nothing to do here, the actor is already updated
return;
}
if (obj.resident && !itemShouldBeResident) {
// make into a regular item
obj.item.doneShowingNotificationStack();
this._residentNotificationBox.remove_actor(obj.item.notificationStackWidget);
[obj.sourceBox, obj.countLabel] = this._makeNotificationSource(obj.source);
this._persistentNotificationBox.add(obj.sourceBox);
} else if (itemShouldBeResident && !obj.resident) {
// make into a resident item
obj.sourceBox.destroy();
obj.sourceBox = obj.countLabel = null;
obj.resident = true;
this._residentNotificationBox.add(obj.item.notificationStackWidget);
obj.item.closeButtonVisible = false;
obj.item.prepareNotificationStackForShowing();
} else {
// just update the counter
let count = obj.source.unseenCount;
obj.countLabel.text = this._makeNotificationCountText(count, obj.source.isChat);
obj.sourceBox.visible = count != 0;
}
this._updateVisibility();
},
_onSourceDestroy: function(source) {
let idx = this._findSource(source);
this._removeItem(this._items[idx]);
this._items.splice(idx, 1);
this._updateVisibility();
},
_removeItem: function(obj) {
if (obj.resident) {
obj.item.doneShowingNotificationStack();
this._residentNotificationBox.remove_actor(obj.item.notificationStackWidget);
} else {
obj.sourceBox.destroy();
}
obj.item.disconnect(obj.contentUpdatedId);
obj.source.disconnect(obj.sourceDestroyId);
obj.source.disconnect(obj.sourceCountChangedId);
},
});
const Arrow = new Lang.Class({
Name: 'Arrow',
Extends: St.Bin,
_init: function(params) {
this.parent(params);
this.x_fill = this.y_fill = true;
this.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
this._drawingArea = new St.DrawingArea();
this._drawingArea.connect('repaint', Lang.bind(this, this._drawArrow));
this.child = this._drawingArea;
this._shadowHelper = null;
this._shadowWidth = this._shadowHeight = 0;
},
_drawArrow: function(arrow) {
let cr = arrow.get_context();
let [w, h] = arrow.get_surface_size();
let node = this.get_theme_node();
let thickness = node.get_length('-arrow-thickness');
Clutter.cairo_set_source_color(cr, node.get_foreground_color());
cr.setLineCap(Cairo.LineCap.ROUND);
cr.setLineWidth(thickness);
cr.moveTo(thickness / 2, h - thickness / 2);
cr.lineTo(w/2, thickness);
cr.lineTo(w - thickness / 2, h - thickness / 2);
cr.stroke();
},
vfunc_style_changed: function() {
let node = this.get_theme_node();
this._shadow = node.get_shadow('-arrow-shadow');
if (this._shadow)
this._shadowHelper = St.ShadowHelper.new(this._shadow);
else
this._shadowHelper = null;
},
vfunc_paint: function() {
if (this._shadowHelper) {
this._shadowHelper.update(this._drawingArea);
let allocation = this._drawingArea.get_allocation_box();
let paintOpacity = this._drawingArea.get_paint_opacity();
this._shadowHelper.paint(allocation, paintOpacity);
}
this._drawingArea.paint();
}
});
/**
* To test screen shield, make sure to kill gnome-screensaver.
*
* If you are setting org.gnome.desktop.session.idle-delay directly in dconf,
* rather than through System Settings, you also need to set
* org.gnome.settings-daemon.plugins.power.sleep-display-ac and
* org.gnome.settings-daemon.plugins.power.sleep-display-battery to the same value.
* This will ensure that the screen blanks at the right time when it fades out.
* https://bugzilla.gnome.org/show_bug.cgi?id=668703 explains the dependance.
*/
const ScreenShield = new Lang.Class({
Name: 'ScreenShield',
_init: function() {
this.actor = Main.layoutManager.screenShieldGroup;
this._lockScreenState = MessageTray.State.HIDDEN;
this._lockScreenGroup = new St.Widget({ x_expand: true,
y_expand: true,
reactive: true,
can_focus: true,
name: 'lockScreenGroup',
});
this._lockScreenGroup.connect('key-release-event',
Lang.bind(this, this._onLockScreenKeyRelease));
this._lockScreenGroup.connect('scroll-event',
Lang.bind(this, this._onLockScreenScroll));
this._lockScreenContents = new St.Widget({ layout_manager: new Clutter.BinLayout(),
name: 'lockScreenContents' });
this._lockScreenContents.add_constraint(new Layout.MonitorConstraint({ primary: true }));
this._background = Meta.BackgroundActor.new_for_screen(global.screen);
this._lockScreenGroup.add_actor(this._background);
this._lockScreenGroup.add_actor(this._lockScreenContents);
this._arrowContainer = new St.BoxLayout({ style_class: 'screen-shield-arrows',
vertical: true,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.END,
// HACK: without these, ClutterBinLayout
// ignores alignment properties on the actor
x_expand: true,
y_expand: true });
for (let i = 0; i < N_ARROWS; i++) {
let arrow = new Arrow({ opacity: 0 });
this._arrowContainer.add_actor(arrow);
}
this._lockScreenContents.add_actor(this._arrowContainer);
let dragArea = new Clutter.Rect({ origin: new Clutter.Point({ x: 0, y: -global.screen_height, }),
size: new Clutter.Size({ width: global.screen_width,
height: global.screen_height }) });
let action = new Clutter.DragAction({ drag_axis: Clutter.DragAxis.Y_AXIS,
drag_area: dragArea });
action.connect('drag-begin', Lang.bind(this, this._onDragBegin));
action.connect('drag-end', Lang.bind(this, this._onDragEnd));
this._lockScreenGroup.add_action(action);
this._lockDialogGroup = new St.Widget({ x_expand: true,
y_expand: true,
name: 'lockDialogGroup' });
this.actor.add_actor(this._lockDialogGroup);
this.actor.add_actor(this._lockScreenGroup);
this._presence = new GnomeSession.Presence(Lang.bind(this, function(proxy, error) {
if (error) {
logError(error, 'Error while reading gnome-session presence');
return;
}
this._onStatusChanged(proxy.status);
}));
this._presence.connectSignal('StatusChanged', Lang.bind(this, function(proxy, senderName, [status]) {
this._onStatusChanged(status);
}));
this._loginManager = LoginManager.getLoginManager();
this._loginSession = this._loginManager.getCurrentSessionProxy();
this._loginSession.connectSignal('Lock', Lang.bind(this, function() { this.lock(false); }));
this._loginSession.connectSignal('Unlock', Lang.bind(this, function() { this.unlock(); }));
this._settings = new Gio.Settings({ schema: SCREENSAVER_SCHEMA });
this._isModal = false;
this._isLocked = false;
this._hasLockScreen = false;
this._lightbox = new Lightbox.Lightbox(Main.uiGroup,
{ inhibitEvents: true,
fadeInTime: STANDARD_FADE_TIME,
fadeFactor: 1 });
},
_onLockScreenKeyRelease: function(actor, event) {
if (event.get_key_symbol() == Clutter.KEY_Escape) {
this._ensureUnlockDialog();
this._hideLockScreen(true);
return true;
}
// Don't bump if the lock screen is not showing or is
// animating, as the bump overrides the animation and would
// remove any onComplete handler
if (this._lockScreenState == MessageTray.State.SHOWN)
this._bumpLockScreen();
return true;
},
_onLockScreenScroll: function(actor, event) {
if (this._lockScreenState != MessageTray.State.SHOWN)
return false;
let delta = 0;
if (event.get_scroll_direction() == Clutter.ScrollDirection.UP)
delta = 5;
else if (event.get_scroll_direction() == Clutter.ScrollDirection.SMOOTH)
delta = Math.max(0, event.get_scroll_delta()[0]);
this._lockScreenScrollCounter += delta;
// 7 standard scrolls to lift up
if (this._lockScreenScrollCounter > 35) {
this._ensureUnlockDialog();
this._hideLockScreen(0);
}
return true;
},
_animateArrows: function() {
let arrows = this._arrowContainer.get_children();
let unitaryDelay = ARROW_ANIMATION_TIME / (arrows.length + 1);
let maxOpacity = 255 * ARROW_ANIMATION_PEAK_OPACITY;
for (let i = 0; i < arrows.length; i++) {
arrows.opacity = 0;
Tweener.addTween(arrows[i],
{ opacity: 0,
delay: unitaryDelay * (N_ARROWS - (i + 1)),
time: ARROW_ANIMATION_TIME,
transition: function(t, b, c, d) {
if (t < d/2)
return TweenerEquations.easeOutQuad(t, 0, maxOpacity, d/2);
else
return TweenerEquations.easeInQuad(t - d/2, maxOpacity, -maxOpacity, d/2);
}
});
}
return true;
},
_onDragBegin: function() {
Tweener.removeTweens(this._lockScreenGroup);
this._lockScreenState = MessageTray.State.HIDING;
this._ensureUnlockDialog();
},
_onDragEnd: function(action, actor, eventX, eventY, modifiers) {
if (this._lockScreenGroup.y < -(ARROW_DRAG_TRESHOLD * global.stage.height)) {
// Complete motion automatically
this._hideLockScreen(true);
} else {
// restore the lock screen to its original place
// try to use the same speed as the normal animation
let h = global.stage.height;
let time = CURTAIN_SLIDE_TIME * (-this._lockScreenGroup.y) / h;
Tweener.removeTweens(this._lockScreenGroup);
Tweener.addTween(this._lockScreenGroup,
{ y: 0,
time: time,
transition: 'linear',
onComplete: function() {
this._lockScreenGroup.fixed_position_set = false;
this._lockScreenState = MessageTray.State.SHOWN;
},
onCompleteScope: this,
});
// If we have a unlock dialog, cancel it
if (this._dialog) {
this._dialog.cancel();
if (!this._keepDialog) {
this._dialog = null;
}
}
}
},
_onStatusChanged: function(status) {
if (status == GnomeSession.PresenceStatus.IDLE) {
if (this._dialog) {
this._dialog.cancel();
if (!this._keepDialog) {
this._dialog = null;
}
}
if (!this._isModal) {
Main.pushModal(this.actor);
this._isModal = true;
}
if (!this._isLocked)
this._lightbox.show();
} else {
let lightboxWasShown = this._lightbox.shown;
this._lightbox.hide();
let shouldLock = lightboxWasShown && this._settings.get_boolean(LOCK_ENABLED_KEY);
if (shouldLock || this._isLocked) {
this.lock(false);
} else if (this._isModal) {
this.unlock();
}
}
},
showDialog: function() {
this.lock(true);
this._ensureUnlockDialog();
this._hideLockScreen(false);
},
_bumpLockScreen: function() {
Tweener.removeTweens(this._lockScreenGroup);
Tweener.addTween(this._lockScreenGroup,
{ y: -BUMP_SIZE,
time: BUMP_TIME / 2,
transition: 'easeOutQuad',
onComplete: function() {
Tweener.addTween(this,
{ y: 0,
time: BUMP_TIME / 2,
transition: 'easeInQuad' });
}
});
},
_hideLockScreen: function(animate) {
this._lockScreenState = MessageTray.State.HIDING;
if (animate) {
// Tween the lock screen out of screen
// try to use the same speed regardless of original position
let h = global.stage.height;
let time = CURTAIN_SLIDE_TIME * (h + this._lockScreenGroup.y) / h;
Tweener.removeTweens(this._lockScreenGroup);
Tweener.addTween(this._lockScreenGroup,
{ y: -h,
time: time,
transition: 'linear',
onComplete: function() {
this._lockScreenHidden();
},
onCompleteScope: this,
});
} else {
this._lockScreenHidden();
}
},
_lockScreenHidden: function() {
this._lockScreenState = MessageTray.State.HIDDEN;
this._lockScreenGroup.hide();
},
_ensureUnlockDialog: function() {
if (!this._dialog) {
[this._dialog, this._keepDialog] = Main.sessionMode.createUnlockDialog(this._lockDialogGroup);
if (!this._dialog) {
// This session mode has no locking capabilities
this.unlock();
return;
}
this._dialog.connect('loaded', Lang.bind(this, function() {
if (!this._dialog.open()) {
log('Could not open login dialog: failed to acquire grab');
this.unlock();
}
}));
this._dialog.connect('failed', Lang.bind(this, this._onUnlockFailed));
this._dialog.connect('unlocked', Lang.bind(this, this._onUnlockSucceded));
}
if (this._keepDialog) {
// Notify the other components that even though we are showing the
// screenshield, we're not in a locked state
// (this happens for the gdm greeter)
this._isLocked = false;
this.emit('lock-status-changed', false);
}
},
_onUnlockFailed: function() {
this._dialog.destroy();
this._dialog = null;
this._resetLockScreen(false);
},
_onUnlockSucceded: function() {
this.unlock();
},
_resetLockScreen: function(animate) {
this._lockScreenGroup.show();
this._lockScreenState = MessageTray.State.SHOWING;
if (animate) {
this._lockScreenGroup.y = -global.screen_height;
Tweener.removeTweens(this._lockScreenGroup);
Tweener.addTween(this._lockScreenGroup,
{ y: 0,
time: SHORT_FADE_TIME,
transition: 'linear',
onComplete: function() {
this._lockScreenShown();
},
onCompleteScope: this
});
this._lockDialogGroup.opacity = 0;
Tweener.removeTweens(this._lockDialogGroup);
Tweener.addTween(this._lockDialogGroup,
{ opacity: 255,
time: SHORT_FADE_TIME,
transition: 'easeOutQuad' });
} else {
this._lockDialogGroup.opacity = 255;
this._lockScreenShown();
}
this._lockScreenGroup.grab_key_focus();
},
_lockScreenShown: function() {
if (this._arrowAnimationId)
Mainloop.source_remove(this._arrowAnimationId);
this._arrowAnimationId = Mainloop.timeout_add(6000, Lang.bind(this, this._animateArrows));
this._animateArrows();
this._lockScreenState = MessageTray.State.SHOWN;
this._lockScreenGroup.fixed_position_set = false;
this._lockScreenScrollCounter = 0;
this.emit('lock-screen-shown');
},
// Some of the actors in the lock screen are heavy in
// resources, so we only create them when needed
_prepareLockScreen: function() {
this._lockScreenContentsBox = new St.BoxLayout({ x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
x_expand: true,
y_expand: true,
vertical: true });
this._clock = new Clock();
this._lockScreenContentsBox.add(this._clock.actor, { x_fill: true,
y_fill: true });
this._lockScreenContents.add_actor(this._lockScreenContentsBox);
if (this._settings.get_boolean('show-notifications')) {
this._notificationsBox = new NotificationsBox();
this._lockScreenContentsBox.add(this._notificationsBox.actor, { x_fill: true,
y_fill: true,
expand: true });
}
this._hasLockScreen = true;
},
_clearLockScreen: function() {
this._clock.destroy();
this._clock = null;
if (this._notificationsBox) {
this._notificationsBox.destroy();
this._notificationsBox = null;
}
this._lockScreenContentsBox.destroy();
if (this._arrowAnimationId) {
Mainloop.source_remove(this._arrowAnimationId);
this._arrowAnimationId = 0;
}
this._hasLockScreen = false;
},
get locked() {
return this._isLocked;
},
unlock: function() {
if (this._hasLockScreen)
this._clearLockScreen();
if (this._keepDialog) {
// The dialog must be kept alive,
// so immediately go back to it
// This will also reset _isLocked
this._ensureUnlockDialog();
return;
}
if (this._dialog) {
this._dialog.destroy();
this._dialog = null;
}
this._lightbox.hide();
if (this._isModal) {
Main.popModal(this.actor);
this._isModal = false;
}
this._isLocked = false;
this.actor.hide();
this.emit('lock-status-changed', false);
},
lock: function(animate) {
if (!this._hasLockScreen)
this._prepareLockScreen();
if (!this._isModal) {
Main.pushModal(this.actor);
this._isModal = true;
}
this._isLocked = true;
this.actor.show();
this._resetLockScreen(animate);
this.emit('lock-status-changed', true);
},
});
Signals.addSignalMethods(ScreenShield.prototype);

View File

@ -10,13 +10,17 @@ const Util = imports.misc.util;
const FileUtils = imports.misc.fileUtils; const FileUtils = imports.misc.fileUtils;
const Main = imports.ui.main; const Main = imports.ui.main;
const DISABLED_OPEN_SEARCH_PROVIDERS_KEY = 'disabled-open-search-providers';
// Not currently referenced by the search API, but // Not currently referenced by the search API, but
// this enumeration can be useful for provider // this enumeration can be useful for provider
// implementations. // implementations.
const MatchType = { const MatchType = {
NONE: 0, NONE: 0,
SUBSTRING: 1, SUBSTRING: 1,
PREFIX: 2 MULTIPLE_SUBSTRING: 2,
PREFIX: 3,
MULTIPLE_PREFIX: 4
}; };
const SearchResultDisplay = new Lang.Class({ const SearchResultDisplay = new Lang.Class({
@ -49,7 +53,7 @@ const SearchResultDisplay = new Lang.Class({
* Remove all results from this display. * Remove all results from this display.
*/ */
clear: function() { clear: function() {
this.actor.destroy_all_children(); this.actor.get_children().forEach(function (actor) { actor.destroy(); });
}, },
/** /**
@ -68,8 +72,11 @@ const SearchResultDisplay = new Lang.Class({
* Subclass this object to add a new result type * Subclass this object to add a new result type
* to the search system, then call registerProvider() * to the search system, then call registerProvider()
* in SearchSystem with an instance. * in SearchSystem with an instance.
* Search is asynchronous and uses the * By default, search is synchronous and uses the
* getInitialResultSet()/getSubsearchResultSet() methods. * getInitialResultSet()/getSubsearchResultSet() methods.
* For asynchronous search, set the async property to true
* and implement getInitialResultSetAsync()/getSubsearchResultSetAsync()
* instead.
*/ */
const SearchProvider = new Lang.Class({ const SearchProvider = new Lang.Class({
Name: 'SearchProvider', Name: 'SearchProvider',
@ -77,6 +84,7 @@ const SearchProvider = new Lang.Class({
_init: function(title) { _init: function(title) {
this.title = title; this.title = title;
this.searchSystem = null; this.searchSystem = null;
this.async = false;
}, },
/** /**
@ -87,7 +95,7 @@ const SearchProvider = new Lang.Class({
* therefore a single term of length one or two), or when * therefore a single term of length one or two), or when
* a new term is added. * a new term is added.
* *
* Should "return" an array of result identifier strings representing * Should return an array of result identifier strings representing
* items which match the given search terms. This * items which match the given search terms. This
* is expected to be a substring match on the metadata for a given * is expected to be a substring match on the metadata for a given
* item. Ordering of returned results is up to the discretion of the provider, * item. Ordering of returned results is up to the discretion of the provider,
@ -97,9 +105,6 @@ const SearchProvider = new Lang.Class({
* description) before single matches * description) before single matches
* * Put items which match on a prefix before non-prefix substring matches * * Put items which match on a prefix before non-prefix substring matches
* *
* We say "return" above, but in order to make the query asynchronous, use
* this.searchSystem.pushResults();. The return value should be ignored.
*
* This function should be fast; do not perform unindexed full-text searches * This function should be fast; do not perform unindexed full-text searches
* or network queries. * or network queries.
*/ */
@ -107,6 +112,18 @@ const SearchProvider = new Lang.Class({
throw new Error('Not implemented'); throw new Error('Not implemented');
}, },
/**
* getInitialResultSetAsync:
* @terms: Array of search terms, treated as logical AND
*
* Like getInitialResultSet(), but the method should return immediately
* without a return value - use SearchSystem.pushResults() when the
* corresponding results are ready.
*/
getInitialResultSetAsync: function(terms) {
throw new Error('Not implemented');
},
/** /**
* getSubsearchResultSet: * getSubsearchResultSet:
* @previousResults: Array of item identifiers * @previousResults: Array of item identifiers
@ -119,26 +136,62 @@ const SearchProvider = new Lang.Class({
* *
* This allows search providers to only search through the previous * This allows search providers to only search through the previous
* result set, rather than possibly performing a full re-query. * result set, rather than possibly performing a full re-query.
*
* Similar to getInitialResultSet, the return value for this will
* be ignored; use this.searchSystem.pushResults();.
*/ */
getSubsearchResultSet: function(previousResults, newTerms) { getSubsearchResultSet: function(previousResults, newTerms) {
throw new Error('Not implemented'); throw new Error('Not implemented');
}, },
/**
* getSubsearchResultSetAsync:
* @previousResults: Array of item identifiers
* @newTerms: Updated search terms
*
* Like getSubsearchResultSet(), but the method should return immediately
* without a return value - use SearchSystem.pushResults() when the
* corresponding results are ready.
*/
getSubsearchResultSetAsync: function(previousResults, newTerms) {
throw new Error('Not implemented');
},
/** /**
* getResultMetas: * getResultMetas:
* @ids: Result identifier strings * @ids: Result identifier strings
* *
* Call callback with array of objects with 'id', 'name', (both strings) and * Return an array of objects with 'id', 'name', (both strings) and
* 'createIcon' (function(size) returning a Clutter.Texture) properties * 'createIcon' (function(size) returning a Clutter.Texture) properties
* with the same number of members as @ids * with the same number of members as @ids
*/ */
getResultMetas: function(ids, callback) { getResultMetas: function(ids) {
throw new Error('Not implemented'); throw new Error('Not implemented');
}, },
/**
* getResultMetasAsync:
* @ids: Result identifier strings
* @callback: callback to pass the results to when ready
*
* Like getResultMetas(), but the method should return immediately
* without a return value - pass the results to the provided @callback
* when ready.
*/
getResultMetasAsync: function(ids, callback) {
throw new Error('Not implemented');
},
/**
* createResultContainer:
*
* Search providers may optionally override this to render their
* results in a custom fashion. The default implementation
* will create a vertical list.
*
* Returns: An instance of SearchResultDisplay.
*/
createResultContainerActor: function() {
return null;
},
/** /**
* createResultActor: * createResultActor:
* @resultMeta: Object with result metadata * @resultMeta: Object with result metadata
@ -167,6 +220,99 @@ const SearchProvider = new Lang.Class({
}); });
Signals.addSignalMethods(SearchProvider.prototype); Signals.addSignalMethods(SearchProvider.prototype);
const OpenSearchSystem = new Lang.Class({
Name: 'OpenSearchSystem',
_init: function() {
this._providers = [];
global.settings.connect('changed::' + DISABLED_OPEN_SEARCH_PROVIDERS_KEY, Lang.bind(this, this._refresh));
this._refresh();
},
getProviders: function() {
let res = [];
for (let i = 0; i < this._providers.length; i++)
res.push({ id: i, name: this._providers[i].name });
return res;
},
setSearchTerms: function(terms) {
this._terms = terms;
},
_checkSupportedProviderLanguage: function(provider) {
if (provider.url.search(/{language}/) == -1)
return true;
let langs = GLib.get_language_names();
langs.push('en');
let lang = null;
for (let i = 0; i < langs.length; i++) {
for (let k = 0; k < provider.langs.length; k++) {
if (langs[i] == provider.langs[k])
lang = langs[i];
}
if (lang)
break;
}
provider.lang = lang;
return lang != null;
},
activateResult: function(id, params) {
let searchTerms = this._terms.join(' ');
let url = this._providers[id].url.replace('{searchTerms}', encodeURIComponent(searchTerms));
if (url.match('{language}'))
url = url.replace('{language}', this._providers[id].lang);
try {
Gio.app_info_launch_default_for_uri(url, global.create_app_launch_context());
} catch (e) {
// TODO: remove this after glib will be removed from moduleset
// In the default jhbuild, gio is in our prefix but gvfs is not
Util.spawn(['gvfs-open', url])
}
Main.overview.hide();
},
_addProvider: function(fileName) {
let path = global.datadir + '/open-search-providers/' + fileName;
let source = Shell.get_file_contents_utf8_sync(path);
let [success, name, url, langs, icon_uri] = Shell.parse_search_provider(source);
let provider ={ name: name,
url: url,
id: this._providers.length,
icon_uri: icon_uri,
langs: langs };
if (this._checkSupportedProviderLanguage(provider)) {
this._providers.push(provider);
this.emit('changed');
}
},
_refresh: function() {
this._providers = [];
let names = global.settings.get_strv(DISABLED_OPEN_SEARCH_PROVIDERS_KEY);
let file = Gio.file_new_for_path(global.datadir + '/open-search-providers');
FileUtils.listDirAsync(file, Lang.bind(this, function(files) {
for (let i = 0; i < files.length; i++) {
let enabled = true;
let name = files[i].get_name();
for (let k = 0; k < names.length; k++)
if (names[k] == name)
enabled = false;
if (enabled)
this._addProvider(name);
}
}));
}
});
Signals.addSignalMethods(OpenSearchSystem.prototype);
const SearchSystem = new Lang.Class({ const SearchSystem = new Lang.Class({
Name: 'SearchSystem', Name: 'SearchSystem',
@ -233,33 +379,42 @@ const SearchSystem = new Lang.Class({
} }
} }
let previousResultsArr = this._previousResults;
let results = []; let results = [];
this._previousTerms = terms;
this._previousResults = results;
if (isSubSearch) { if (isSubSearch) {
for (let i = 0; i < this._providers.length; i++) { for (let i = 0; i < this._providers.length; i++) {
let [provider, previousResults] = previousResultsArr[i]; let [provider, previousResults] = this._previousResults[i];
try { try {
results.push([provider, []]); if (provider.async) {
provider.getSubsearchResultSet(previousResults, terms); provider.getSubsearchResultSetAsync(previousResults, terms);
results.push([provider, []]);
} else {
let providerResults = provider.getSubsearchResultSet(previousResults, terms);
results.push([provider, providerResults]);
}
} catch (error) { } catch (error) {
log('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message); global.log ('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message);
} }
} }
} else { } else {
for (let i = 0; i < this._providers.length; i++) { for (let i = 0; i < this._providers.length; i++) {
let provider = this._providers[i]; let provider = this._providers[i];
try { try {
results.push([provider, []]); if (provider.async) {
provider.getInitialResultSet(terms); provider.getInitialResultSetAsync(terms);
results.push([provider, []]);
} else {
let providerResults = provider.getInitialResultSet(terms);
results.push([provider, providerResults]);
}
} catch (error) { } catch (error) {
log('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message); global.log ('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message);
} }
} }
} }
this._previousTerms = terms;
this._previousResults = results;
this.emit('search-completed', results);
}, },
}); });
Signals.addSignalMethods(SearchSystem.prototype); Signals.addSignalMethods(SearchSystem.prototype);

View File

@ -5,7 +5,6 @@ const Lang = imports.lang;
const Gtk = imports.gi.Gtk; const Gtk = imports.gi.Gtk;
const Meta = imports.gi.Meta; const Meta = imports.gi.Meta;
const St = imports.gi.St; const St = imports.gi.St;
const Atk = imports.gi.Atk;
const DND = imports.ui.dnd; const DND = imports.ui.dnd;
const IconGrid = imports.ui.iconGrid; const IconGrid = imports.ui.iconGrid;
@ -34,13 +33,12 @@ const SearchResult = new Lang.Class({
content = new St.Bin({ style_class: 'search-result-content', content = new St.Bin({ style_class: 'search-result-content',
reactive: true, reactive: true,
can_focus: true, can_focus: true,
track_hover: true, track_hover: true });
accessible_role: Atk.Role.PUSH_BUTTON });
let icon = new IconGrid.BaseIcon(this.metaInfo['name'], let icon = new IconGrid.BaseIcon(this.metaInfo['name'],
{ createIcon: this.metaInfo['createIcon'] }); { createIcon: this.metaInfo['createIcon'] });
content.set_child(icon.actor); content.set_child(icon.actor);
this._dragActorSource = icon.icon; this._dragActorSource = icon.icon;
content.label_actor = icon.label; this.actor.label_actor = icon.label;
} else { } else {
if (content._delegate && content._delegate.getDragActorSource) if (content._delegate && content._delegate.getDragActorSource)
this._dragActorSource = content._delegate.getDragActorSource(); this._dragActorSource = content._delegate.getDragActorSource();
@ -121,7 +119,13 @@ const GridSearchResults = new Lang.Class({
if (results.length == 0) if (results.length == 0)
return; return;
provider.getResultMetas(results, Lang.bind(this, this.renderResults)); if (provider.async) {
provider.getResultMetasAsync(results,
Lang.bind(this, this.renderResults));
} else {
let metas = provider.getResultMetas(results);
this.renderResults(metas);
}
})); }));
})); }));
this._notDisplayedResult = []; this._notDisplayedResult = [];
@ -131,7 +135,7 @@ const GridSearchResults = new Lang.Class({
getResultsForDisplay: function() { getResultsForDisplay: function() {
let alreadyVisible = this._pendingClear ? 0 : this._grid.visibleItemsCount(); let alreadyVisible = this._pendingClear ? 0 : this._grid.visibleItemsCount();
let canDisplay = this._grid.childrenInRow(this._width) * this._grid.getRowLimit() let canDisplay = this._grid.childrenInRow(this._width) * MAX_SEARCH_RESULTS_ROWS
- alreadyVisible; - alreadyVisible;
let numResults = Math.min(this._notDisplayedResult.length, canDisplay); let numResults = Math.min(this._notDisplayedResult.length, canDisplay);
@ -173,9 +177,11 @@ const GridSearchResults = new Lang.Class({
const SearchResults = new Lang.Class({ const SearchResults = new Lang.Class({
Name: 'SearchResults', Name: 'SearchResults',
_init: function(searchSystem) { _init: function(searchSystem, openSearchSystem) {
this._searchSystem = searchSystem; this._searchSystem = searchSystem;
this._searchSystem.connect('search-updated', Lang.bind(this, this._updateResults)); this._searchSystem.connect('search-updated', Lang.bind(this, this._updateCurrentResults));
this._searchSystem.connect('search-completed', Lang.bind(this, this._updateResults));
this._openSearchSystem = openSearchSystem;
this.actor = new St.BoxLayout({ name: 'searchResults', this.actor = new St.BoxLayout({ name: 'searchResults',
vertical: true }); vertical: true });
@ -190,7 +196,7 @@ const SearchResults = new Lang.Class({
scrollView.add_actor(this._content); scrollView.add_actor(this._content);
this.actor.add(scrollView, { x_fill: true, this.actor.add(scrollView, { x_fill: true,
y_fill: true, y_fill: false,
expand: true, expand: true,
x_align: St.Align.START, x_align: St.Align.START,
y_align: St.Align.START }); y_align: St.Align.START });
@ -205,22 +211,66 @@ const SearchResults = new Lang.Class({
})); }));
this._statusText = new St.Label({ style_class: 'search-statustext' }); this._statusText = new St.Label({ style_class: 'search-statustext' });
this._statusBin = new St.Bin({ x_align: St.Align.MIDDLE, this._content.add(this._statusText);
y_align: St.Align.MIDDLE });
this._content.add(this._statusBin, { expand: true });
this._statusBin.add_actor(this._statusText);
this._providers = this._searchSystem.getProviders(); this._providers = this._searchSystem.getProviders();
this._providerMeta = []; this._providerMeta = [];
this._providerMetaResults = {};
for (let i = 0; i < this._providers.length; i++) { for (let i = 0; i < this._providers.length; i++) {
this.createProviderMeta(this._providers[i]); this.createProviderMeta(this._providers[i]);
this._providerMetaResults[this.providers[i].title] = [];
} }
this._searchProvidersBox = new St.BoxLayout({ style_class: 'search-providers-box' }); this._searchProvidersBox = new St.BoxLayout({ style_class: 'search-providers-box' });
this.actor.add(this._searchProvidersBox); this.actor.add(this._searchProvidersBox);
this._openSearchProviders = [];
this._openSearchSystem.connect('changed', Lang.bind(this, this._updateOpenSearchProviderButtons));
this._updateOpenSearchProviderButtons();
this._highlightDefault = false; this._highlightDefault = false;
this._defaultResult = null; this._defaultResult = null;
}, },
_updateOpenSearchProviderButtons: function() {
for (let i = 0; i < this._openSearchProviders.length; i++)
this._openSearchProviders[i].actor.destroy();
this._openSearchProviders = this._openSearchSystem.getProviders();
for (let i = 0; i < this._openSearchProviders.length; i++)
this._createOpenSearchProviderButton(this._openSearchProviders[i]);
},
_createOpenSearchProviderButton: function(provider) {
let button = new St.Button({ style_class: 'dash-search-button',
reactive: true,
can_focus: true,
x_fill: true,
y_align: St.Align.MIDDLE });
let bin = new St.Bin({ x_fill: false,
x_align:St.Align.MIDDLE });
button.connect('clicked', Lang.bind(this, function() {
this._openSearchSystem.activateResult(provider.id);
}));
let title = new St.Label({ text: provider.name,
style_class: 'dash-search-button-label' });
button.label_actor = title;
bin.set_child(title);
button.set_child(bin);
provider.actor = button;
button.setSelected = function(selected) {
if (selected)
button.add_style_pseudo_class('selected');
else
button.remove_style_pseudo_class('selected');
};
button.activate = Lang.bind(this, function() {
this._openSearchSystem.activateResult(provider.id);
});
button.actor = button;
this._searchProvidersBox.add(button);
},
createProviderMeta: function(provider) { createProviderMeta: function(provider) {
let providerBox = new St.BoxLayout({ style_class: 'search-section', let providerBox = new St.BoxLayout({ style_class: 'search-section',
vertical: true }); vertical: true });
@ -232,12 +282,16 @@ const SearchResults = new Lang.Class({
x_fill: true, x_fill: true,
y_fill: true }); y_fill: true });
providerBox.add(resultDisplayBin, { expand: true }); providerBox.add(resultDisplayBin, { expand: true });
let resultDisplay = new GridSearchResults(provider); let resultDisplay = provider.createResultContainerActor();
if (resultDisplay == null) {
resultDisplay = new GridSearchResults(provider);
}
resultDisplayBin.set_child(resultDisplay.actor); resultDisplayBin.set_child(resultDisplay.actor);
this._providerMeta.push({ provider: provider, this._providerMeta.push({ provider: provider,
actor: providerBox, actor: providerBox,
resultDisplay: resultDisplay }); resultDisplay: resultDisplay,
hasPendingResults: false });
this._content.add(providerBox); this._content.add(providerBox);
}, },
@ -253,6 +307,7 @@ const SearchResults = new Lang.Class({
}, },
_clearDisplay: function() { _clearDisplay: function() {
this._visibleResultsCount = 0;
for (let i = 0; i < this._providerMeta.length; i++) { for (let i = 0; i < this._providerMeta.length; i++) {
let meta = this._providerMeta[i]; let meta = this._providerMeta[i];
meta.resultDisplay.clear(); meta.resultDisplay.clear();
@ -268,14 +323,14 @@ const SearchResults = new Lang.Class({
reset: function() { reset: function() {
this._searchSystem.reset(); this._searchSystem.reset();
this._statusBin.hide(); this._statusText.hide();
this._clearDisplay(); this._clearDisplay();
}, },
startingSearch: function() { startingSearch: function() {
this.reset(); this.reset();
this._statusText.set_text(_("Searching...")); this._statusText.set_text(_("Searching..."));
this._statusBin.show(); this._statusText.show();
}, },
doSearch: function (searchString) { doSearch: function (searchString) {
@ -291,6 +346,8 @@ const SearchResults = new Lang.Class({
for (let i = 0; i < this._providerMeta.length; i++) { for (let i = 0; i < this._providerMeta.length; i++) {
let meta = this._providerMeta[i]; let meta = this._providerMeta[i];
if (meta.hasPendingResults)
return;
if (!meta.actor.visible) if (!meta.actor.visible)
continue; continue;
@ -315,57 +372,78 @@ const SearchResults = new Lang.Class({
} }
}, },
_updateStatusText: function () { _updateCurrentResults: function(searchSystem, results) {
let haveResults = false;
for (let i = 0; i < this._providerMeta.length; ++i)
if (this._providerMeta[i].resultDisplay.getFirstResult()) {
haveResults = true;
break;
}
if (!haveResults) {
this._statusText.set_text(_("No results."));
this._statusBin.show();
} else {
this._statusBin.hide();
}
},
_updateResults: function(searchSystem, results) {
let terms = searchSystem.getTerms(); let terms = searchSystem.getTerms();
let [provider, providerResults] = results; let [provider, providerResults] = results;
let meta = this._metaForProvider(provider); let meta = this._metaForProvider(provider);
meta.hasPendingResults = false;
this._updateProviderResults(provider, providerResults, terms);
},
_updateProviderResults: function(provider, providerResults, terms) {
let meta = this._metaForProvider(provider);
if (providerResults.length == 0) { if (providerResults.length == 0) {
this._clearDisplayForProvider(provider); this._clearDisplayForProvider(provider);
meta.resultDisplay.setResults([], []); meta.resultDisplay.setResults([], []);
this._maybeSetInitialSelection();
this._updateStatusText();
} else { } else {
this._providerMetaResults[provider.title] = providerResults;
meta.resultDisplay.setResults(providerResults, terms); meta.resultDisplay.setResults(providerResults, terms);
let results = meta.resultDisplay.getResultsForDisplay(); let results = meta.resultDisplay.getResultsForDisplay();
provider.getResultMetas(results, Lang.bind(this, function(metas) { if (provider.async) {
provider.getResultMetasAsync(results, Lang.bind(this,
function(metas) {
this._clearDisplayForProvider(provider);
meta.actor.show();
// Hinding drops the key focus if we have it
let focus = global.stage.get_key_focus();
this._content.hide();
meta.resultDisplay.renderResults(metas);
this._maybeSetInitialSelection();
this._content.show();
if (this._content.contains(focus))
global.stage.set_key_focus(focus);
}));
} else {
let metas = provider.getResultMetas(results);
this._clearDisplayForProvider(provider); this._clearDisplayForProvider(provider);
meta.actor.show(); meta.actor.show();
// Hiding drops the key focus if we have it
let focus = global.stage.get_key_focus();
// To avoid CSS transitions causing flickering when
// the first search result stays the same, we hide the
// content while filling in the results.
this._content.hide();
meta.resultDisplay.renderResults(metas); meta.resultDisplay.renderResults(metas);
this._maybeSetInitialSelection(); }
this._updateStatusText();
this._content.show();
if (this._content.contains(focus))
global.stage.set_key_focus(focus);
}));
} }
this._maybeSetInitialSelection();
},
_updateResults: function(searchSystem, results) {
if (results.length == 0) {
this._statusText.set_text(_("No matching results."));
this._statusText.show();
} else {
this._statusText.hide();
}
let terms = searchSystem.getTerms();
this._openSearchSystem.setSearchTerms(terms);
// To avoid CSS transitions causing flickering when the first search
// result stays the same, we hide the content while filling in the
// results.
this._content.hide();
for (let i = 0; i < results.length; i++) {
let [provider, providerResults] = results[i];
let meta = this._metaForProvider(provider);
meta.hasPendingResults = provider.async;
if (!meta.hasPendingResults)
this._updateProviderResults(provider, providerResults, terms);
}
this._content.show();
return true;
}, },
activateDefault: function() { activateDefault: function() {

View File

@ -1,94 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Lang = imports.lang;
const Main = imports.ui.main;
const Params = imports.misc.params;
const DEFAULT_MODE = 'user';
const _modes = {
'gdm': { hasOverview: false,
showCalendarEvents: false,
allowSettings: false,
allowExtensions: false,
allowKeybindingsWhenModal: true,
hasRunDialog: false,
hasWorkspaces: false,
createSession: Main.createGDMSession,
createUnlockDialog: Main.createGDMLoginDialog,
panel: {
left: [],
center: ['dateMenu'],
right: ['a11y', 'display', 'keyboard',
'volume', 'battery', 'lockScreen', 'powerMenu']
}
},
'initial-setup': { hasOverview: false,
showCalendarEvents: false,
allowSettings: false,
allowExtensions: false,
allowKeybindingsWhenModal: false,
hasRunDialog: false,
hasWorkspaces: false,
createSession: Main.createInitialSetupSession,
panel: {
left: [],
center: ['dateMenu'],
right: ['a11y', 'keyboard', 'volume', 'lockScreen']
}
},
'user': { hasOverview: true,
showCalendarEvents: true,
allowSettings: true,
allowExtensions: true,
allowKeybindingsWhenModal: false,
hasRunDialog: true,
hasWorkspaces: true,
createSession: Main.createUserSession,
createUnlockDialog: Main.createSessionUnlockDialog,
panel: {
left: ['activities', 'appMenu'],
center: ['dateMenu'],
right: ['a11y', 'keyboard', 'volume', 'bluetooth',
'network', 'battery', 'lockScreen', 'userMenu']
}
}
};
function listModes() {
let modes = Object.getOwnPropertyNames(_modes);
for (let i = 0; i < modes.length; i++)
print(modes[i]);
}
const SessionMode = new Lang.Class({
Name: 'SessionMode',
_init: function() {
let params = _modes[global.session_mode];
params = Params.parse(params, _modes[DEFAULT_MODE]);
this._createSession = params.createSession;
delete params.createSession;
this._createUnlockDialog = params.createUnlockDialog;
delete params.createUnlockDialog;
Lang.copyProperties(params, this);
},
createSession: function() {
if (this._createSession)
this._createSession();
},
createUnlockDialog: function() {
if (this._createUnlockDialog)
return this._createUnlockDialog.apply(this, arguments);
else
return null;
},
});

View File

@ -7,7 +7,6 @@ const Shell = imports.gi.Shell;
const Config = imports.misc.config; const Config = imports.misc.config;
const ExtensionSystem = imports.ui.extensionSystem; const ExtensionSystem = imports.ui.extensionSystem;
const ExtensionDownloader = imports.ui.extensionDownloader;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Flashspot = imports.ui.flashspot; const Flashspot = imports.ui.flashspot;
const Main = imports.ui.main; const Main = imports.ui.main;
@ -18,6 +17,17 @@ const GnomeShellIface = <interface name="org.gnome.Shell">
<arg type="b" direction="out" name="success" /> <arg type="b" direction="out" name="success" />
<arg type="s" direction="out" name="result" /> <arg type="s" direction="out" name="result" />
</method> </method>
<method name="ListExtensions">
<arg type="a{sa{sv}}" direction="out" name="extensions" />
</method>
<method name="GetExtensionInfo">
<arg type="s" direction="in" name="extension" />
<arg type="a{sv}" direction="out" name="info" />
</method>
<method name="GetExtensionErrors">
<arg type="s" direction="in" name="extension" />
<arg type="as" direction="out" name="errors" />
</method>
<method name="ScreenshotArea"> <method name="ScreenshotArea">
<arg type="i" direction="in" name="x"/> <arg type="i" direction="in" name="x"/>
<arg type="i" direction="in" name="y"/> <arg type="i" direction="in" name="y"/>
@ -46,21 +56,30 @@ const GnomeShellIface = <interface name="org.gnome.Shell">
<arg type="i" direction="in" name="width"/> <arg type="i" direction="in" name="width"/>
<arg type="i" direction="in" name="height"/> <arg type="i" direction="in" name="height"/>
</method> </method>
<method name="EnableExtension">
<arg type="s" direction="in" name="uuid"/>
</method>
<method name="DisableExtension">
<arg type="s" direction="in" name="uuid"/>
</method>
<method name="InstallRemoteExtension">
<arg type="s" direction="in" name="uuid"/>
<arg type="s" direction="in" name="version"/>
</method>
<method name="UninstallExtension">
<arg type="s" direction="in" name="uuid"/>
<arg type="b" direction="out" name="success"/>
</method>
<method name="LaunchExtensionPrefs">
<arg type="s" direction="in" name="uuid"/>
</method>
<property name="OverviewActive" type="b" access="readwrite" /> <property name="OverviewActive" type="b" access="readwrite" />
<property name="ApiVersion" type="i" access="read" />
<property name="ShellVersion" type="s" access="read" /> <property name="ShellVersion" type="s" access="read" />
</interface>; <signal name="ExtensionStatusChanged">
<arg type="s" name="uuid"/>
const ScreenSaverIface = <interface name="org.gnome.ScreenSaver"> <arg type="i" name="state"/>
<method name="Lock"> <arg type="s" name="error"/>
</method>
<method name="GetActive">
<arg name="active" direction="out" type="b" />
</method>
<method name="SetActive">
<arg name="value" direction="in" type="u" />
</method>
<signal name="ActiveChanged">
<arg name="new_value" type="b" />
</signal> </signal>
</interface>; </interface>;
@ -70,8 +89,8 @@ const GnomeShell = new Lang.Class({
_init: function() { _init: function() {
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellIface, this); this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellIface, this);
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell'); this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell');
ExtensionSystem.connect('extension-state-changed',
this._extensionsSerivce = new GnomeShellExtensions(); Lang.bind(this, this._extensionStateChanged));
}, },
/** /**
@ -183,67 +202,6 @@ const GnomeShell = new Lang.Class({
flashspot.fire(); flashspot.fire();
}, },
get OverviewActive() {
return Main.overview.visible;
},
set OverviewActive(visible) {
if (visible)
Main.overview.show();
else
Main.overview.hide();
},
ShellVersion: Config.PACKAGE_VERSION
});
const GnomeShellExtensionsIface = <interface name="org.gnome.Shell.Extensions">
<method name="ListExtensions">
<arg type="a{sa{sv}}" direction="out" name="extensions" />
</method>
<method name="GetExtensionInfo">
<arg type="s" direction="in" name="extension" />
<arg type="a{sv}" direction="out" name="info" />
</method>
<method name="GetExtensionErrors">
<arg type="s" direction="in" name="extension" />
<arg type="as" direction="out" name="errors" />
</method>
<signal name="ExtensionStatusChanged">
<arg type="s" name="uuid"/>
<arg type="i" name="state"/>
<arg type="s" name="error"/>
</signal>
<method name="InstallRemoteExtension">
<arg type="s" direction="in" name="uuid"/>
<arg type="s" direction="out" name="result"/>
</method>
<method name="UninstallExtension">
<arg type="s" direction="in" name="uuid"/>
<arg type="b" direction="out" name="success"/>
</method>
<method name="LaunchExtensionPrefs">
<arg type="s" direction="in" name="uuid"/>
</method>
<method name="ReloadExtension">
<arg type="s" direction="in" name="uuid"/>
</method>
<method name="CheckForUpdates">
</method>
<property name="ShellVersion" type="s" access="read" />
</interface>;
const GnomeShellExtensions = new Lang.Class({
Name: 'GnomeShellExtensionsDBus',
_init: function() {
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellExtensionsIface, this);
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell');
ExtensionSystem.connect('extension-state-changed',
Lang.bind(this, this._extensionStateChanged));
},
ListExtensions: function() { ListExtensions: function() {
let out = {}; let out = {};
for (let uuid in ExtensionUtils.extensions) { for (let uuid in ExtensionUtils.extensions) {
@ -302,12 +260,26 @@ const GnomeShellExtensions = new Lang.Class({
return extension.errors; return extension.errors;
}, },
InstallRemoteExtensionAsync: function([uuid], invocation) { EnableExtension: function(uuid) {
return ExtensionDownloader.installExtension(uuid, invocation); let enabledExtensions = global.settings.get_strv(ExtensionSystem.ENABLED_EXTENSIONS_KEY);
if (enabledExtensions.indexOf(uuid) == -1)
enabledExtensions.push(uuid);
global.settings.set_strv(ExtensionSystem.ENABLED_EXTENSIONS_KEY, enabledExtensions);
},
DisableExtension: function(uuid) {
let enabledExtensions = global.settings.get_strv(ExtensionSystem.ENABLED_EXTENSIONS_KEY);
while (enabledExtensions.indexOf(uuid) != -1)
enabledExtensions.splice(enabledExtensions.indexOf(uuid), 1);
global.settings.set_strv(ExtensionSystem.ENABLED_EXTENSIONS_KEY, enabledExtensions);
},
InstallRemoteExtension: function(uuid, version_tag) {
ExtensionSystem.installExtensionFromUUID(uuid, version_tag);
}, },
UninstallExtension: function(uuid) { UninstallExtension: function(uuid) {
return ExtensionDownloader.uninstallExtension(uuid); return ExtensionSystem.uninstallExtensionFromUUID(uuid);
}, },
LaunchExtensionPrefs: function(uuid) { LaunchExtensionPrefs: function(uuid) {
@ -317,18 +289,19 @@ const GnomeShellExtensions = new Lang.Class({
['extension:///' + uuid], -1, null); ['extension:///' + uuid], -1, null);
}, },
ReloadExtension: function(uuid) { get OverviewActive() {
let extension = ExtensionUtils.extensions[uuid]; return Main.overview.visible;
if (!extension)
return;
ExtensionSystem.reloadExtension(extension);
}, },
CheckForUpdates: function() { set OverviewActive(visible) {
ExtensionDownloader.checkForUpdates(); if (visible)
Main.overview.show();
else
Main.overview.hide();
}, },
ApiVersion: ExtensionSystem.API_VERSION,
ShellVersion: Config.PACKAGE_VERSION, ShellVersion: Config.PACKAGE_VERSION,
_extensionStateChanged: function(_, newState) { _extensionStateChanged: function(_, newState) {
@ -336,33 +309,3 @@ const GnomeShellExtensions = new Lang.Class({
GLib.Variant.new('(sis)', [newState.uuid, newState.state, newState.error])); GLib.Variant.new('(sis)', [newState.uuid, newState.state, newState.error]));
} }
}); });
const ScreenSaverDBus = new Lang.Class({
Name: 'ScreenSaverDBus',
_init: function() {
this.parent();
Main.screenShield.connect('lock-status-changed', Lang.bind(this, function(shield, locked) {
this._dbusImpl.emit_signal('ActiveChanged', GLib.Variant.new('(b)', [locked]));
}));
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(ScreenSaverIface, this);
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/ScreenSaver');
},
Lock: function() {
Main.screenShield.lock(true);
},
SetActive: function(active) {
if (active)
Main.screenShield.lock(true);
else
Main.screenShield.unlock();
},
GetActive: function() {
return Main.screenShield.locked;
}
});

View File

@ -7,7 +7,7 @@ const Main = imports.ui.main;
const Params = imports.misc.params; const Params = imports.misc.params;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const EntryMenu = new Lang.Class({ const _EntryMenu = new Lang.Class({
Name: 'ShellEntryMenu', Name: 'ShellEntryMenu',
Extends: PopupMenu.PopupMenu, Extends: PopupMenu.PopupMenu,
@ -34,37 +34,18 @@ const EntryMenu = new Lang.Class({
this._pasteItem = item; this._pasteItem = item;
this._passwordItem = null; this._passwordItem = null;
if (params.isPassword) if (params.isPassword) {
this._makePasswordItem(); item = new PopupMenu.PopupMenuItem('');
item.connect('activate', Lang.bind(this,
this._onPasswordActivated));
this.addMenuItem(item);
this._passwordItem = item;
}
Main.uiGroup.add_actor(this.actor); Main.uiGroup.add_actor(this.actor);
this.actor.hide(); this.actor.hide();
}, },
_makePasswordItem: function() {
let item = new PopupMenu.PopupMenuItem('');
item.connect('activate', Lang.bind(this,
this._onPasswordActivated));
this.addMenuItem(item);
this._passwordItem = item;
},
get isPassword() {
return this._passwordItem != null;
},
set isPassword(v) {
if (v == this.isPassword)
return;
if (v)
this._makePasswordItem();
else {
this._passwordItem.destroy();
this._passwordItem = null;
}
},
open: function() { open: function() {
this._updatePasteItem(); this._updatePasteItem();
this._updateCopyItem(); this._updateCopyItem();
@ -123,50 +104,50 @@ const EntryMenu = new Lang.Class({
function _setMenuAlignment(entry, stageX) { function _setMenuAlignment(entry, stageX) {
let [success, entryX, entryY] = entry.transform_stage_point(stageX, 0); let [success, entryX, entryY] = entry.transform_stage_point(stageX, 0);
if (success) if (success)
entry.menu.setSourceAlignment(entryX / entry.width); entry._menu.setSourceAlignment(entryX / entry.width);
}; };
function _onClicked(action, actor) { function _onClicked(action, actor) {
let entry = actor.menu ? actor : actor.get_parent(); let entry = actor._menu ? actor : actor.get_parent();
if (entry.menu.isOpen) { if (entry._menu.isOpen) {
entry.menu.close(); entry._menu.close();
} else if (action.get_button() == 3) { } else if (action.get_button() == 3) {
let [stageX, stageY] = action.get_coords(); let [stageX, stageY] = action.get_coords();
_setMenuAlignment(entry, stageX); _setMenuAlignment(entry, stageX);
entry.menu.open(); entry._menu.open();
} }
}; };
function _onLongPress(action, actor, state) { function _onLongPress(action, actor, state) {
let entry = actor.menu ? actor : actor.get_parent(); let entry = actor._menu ? actor : actor.get_parent();
if (state == Clutter.LongPressState.QUERY) if (state == Clutter.LongPressState.QUERY)
return action.get_button() == 1 && !entry.menu.isOpen; return action.get_button() == 1 && !entry._menu.isOpen;
if (state == Clutter.LongPressState.ACTIVATE) { if (state == Clutter.LongPressState.ACTIVATE) {
let [stageX, stageY] = action.get_coords(); let [stageX, stageY] = action.get_coords();
_setMenuAlignment(entry, stageX); _setMenuAlignment(entry, stageX);
entry.menu.open(); entry._menu.open();
} }
return false; return false;
}; };
function _onPopup(actor) { function _onPopup(actor) {
let entry = actor.menu ? actor : actor.get_parent(); let entry = actor._menu ? actor : actor.get_parent();
let [success, textX, textY, lineHeight] = entry.clutter_text.position_to_coords(-1); let [success, textX, textY, lineHeight] = entry.clutter_text.position_to_coords(-1);
if (success) if (success)
entry.menu.setSourceAlignment(textX / entry.width); entry._menu.setSourceAlignment(textX / entry.width);
entry.menu.open(); entry._menu.open();
}; };
function addContextMenu(entry, params) { function addContextMenu(entry, params) {
if (entry.menu) if (entry._menu)
return; return;
entry.menu = new EntryMenu(entry, params); entry._menu = new _EntryMenu(entry, params);
entry._menuManager = new PopupMenu.PopupMenuManager({ actor: entry }); entry._menuManager = new PopupMenu.PopupMenuManager({ actor: entry });
entry._menuManager.addMenu(entry.menu); entry._menuManager.addMenu(entry._menu);
let clickAction; let clickAction;

View File

@ -1,21 +1,17 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter;
const Lang = imports.lang; const Lang = imports.lang;
const Signals = imports.signals; const Signals = imports.signals;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk; const Gtk = imports.gi.Gtk;
const Pango = imports.gi.Pango; const Pango = imports.gi.Pango;
const St = imports.gi.St; const St = imports.gi.St;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const CheckBox = imports.ui.checkBox;
const Main = imports.ui.main; const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray; const MessageTray = imports.ui.messageTray;
const ModalDialog = imports.ui.modalDialog; const ModalDialog = imports.ui.modalDialog;
const Params = imports.misc.params; const Params = imports.misc.params;
const ShellEntry = imports.ui.shellEntry;
const LIST_ITEM_ICON_SIZE = 48; const LIST_ITEM_ICON_SIZE = 48;
@ -52,11 +48,6 @@ function _setLabelsForMessage(dialog, message) {
_setLabelText(dialog.descriptionLabel, labels[1]); _setLabelText(dialog.descriptionLabel, labels[1]);
} }
function _createIcon(gicon) {
return new St.Icon({ gicon: gicon,
style_class: 'shell-mount-operation-icon' })
}
/* -------------------------------------------------------- */ /* -------------------------------------------------------- */
const ListItem = new Lang.Class({ const ListItem = new Lang.Class({
@ -100,11 +91,11 @@ const ShellMountOperation = new Lang.Class({
Name: 'ShellMountOperation', Name: 'ShellMountOperation',
_init: function(source, params) { _init: function(source, params) {
params = Params.parse(params, { existingDialog: null }); params = Params.parse(params, { reaskPassword: false });
this._reaskPassword = params.reaskPassword;
this._dialog = null; this._dialog = null;
this._dialogId = 0;
this._existingDialog = params.existingDialog;
this._processesDialog = null; this._processesDialog = null;
this.mountOp = new Shell.MountOperation(); this.mountOp = new Shell.MountOperation();
@ -116,177 +107,99 @@ const ShellMountOperation = new Lang.Class({
this.mountOp.connect('show-processes-2', this.mountOp.connect('show-processes-2',
Lang.bind(this, this._onShowProcesses2)); Lang.bind(this, this._onShowProcesses2));
this.mountOp.connect('aborted', this.mountOp.connect('aborted',
Lang.bind(this, this.close)); Lang.bind(this, this._onAborted));
this.mountOp.connect('show-unmount-progress',
Lang.bind(this, this._onShowUnmountProgress));
this._gicon = source.get_icon(); this._icon = new St.Icon({ gicon: source.get_icon(),
}, style_class: 'shell-mount-operation-icon' });
_closeExistingDialog: function() {
if (!this._existingDialog)
return;
this._existingDialog.close();
this._existingDialog = null;
}, },
_onAskQuestion: function(op, message, choices) { _onAskQuestion: function(op, message, choices) {
this._closeExistingDialog(); this._dialog = new ShellMountQuestionDialog(this._icon);
this._dialog = new ShellMountQuestionDialog(this._gicon);
this._dialogId = this._dialog.connect('response', Lang.bind(this, this._dialog.connect('response',
function(object, choice) { Lang.bind(this, function(object, choice) {
this.mountOp.set_choice(choice); this.mountOp.set_choice(choice);
this.mountOp.reply(Gio.MountOperationResult.HANDLED); this.mountOp.reply(Gio.MountOperationResult.HANDLED);
this.close(); this._dialog.close(global.get_current_time());
})); this._dialog = null;
}));
this._dialog.update(message, choices); this._dialog.update(message, choices);
this._dialog.open(); this._dialog.open(global.get_current_time());
}, },
_onAskPassword: function(op, message, defaultUser, defaultDomain, flags) { _onAskPassword: function(op, message) {
if (this._existingDialog) { this._notificationShowing = true;
this._dialog = this._existingDialog; this._source = new ShellMountPasswordSource(message, this._icon, this._reaskPassword);
this._dialog.reaskPassword();
} else {
this._dialog = new ShellMountPasswordDialog(message, this._gicon, flags);
}
this._dialogId = this._dialog.connect('response', Lang.bind(this, this._source.connect('password-ready',
function(object, choice, password, remember) { Lang.bind(this, function(source, password) {
if (choice == -1) { this.mountOp.set_password(password);
this.mountOp.reply(Gio.MountOperationResult.ABORTED); this.mountOp.reply(Gio.MountOperationResult.HANDLED);
} else {
if (remember)
this.mountOp.set_password_save(Gio.PasswordSave.PERMANENTLY);
else
this.mountOp.set_password_save(Gio.PasswordSave.NEVER);
this.mountOp.set_password(password); this._notificationShowing = false;
this.mountOp.reply(Gio.MountOperationResult.HANDLED); this._source.destroy();
} }));
}));
this._dialog.open(); this._source.connect('destroy',
Lang.bind(this, function() {
if (!this._notificationShowing)
return;
this._notificationShowing = false;
this.mountOp.reply(Gio.MountOperationResult.ABORTED);
}));
}, },
close: function(op) { _onAborted: function(op) {
this._closeExistingDialog(); if (!this._dialog)
this._processesDialog = null; return;
if (this._dialog) { this._dialog.close(global.get_current_time());
this._dialog.close(); this._dialog = null;
this._dialog = null;
}
if (this._notifier) {
this._notifier.done();
this._notifier = null;
}
}, },
_onShowProcesses2: function(op) { _onShowProcesses2: function(op) {
this._closeExistingDialog();
let processes = op.get_show_processes_pids(); let processes = op.get_show_processes_pids();
let choices = op.get_show_processes_choices(); let choices = op.get_show_processes_choices();
let message = op.get_show_processes_message(); let message = op.get_show_processes_message();
if (!this._processesDialog) { if (!this._processesDialog) {
this._processesDialog = new ShellProcessesDialog(this._gicon); this._processesDialog = new ShellProcessesDialog(this._icon);
this._dialog = this._processesDialog; this._dialog = this._processesDialog;
this._dialogId = this._processesDialog.connect('response', Lang.bind(this, this._processesDialog.connect('response',
function(object, choice) { Lang.bind(this, function(object, choice) {
if (choice == -1) { if (choice == -1) {
this.mountOp.reply(Gio.MountOperationResult.ABORTED); this.mountOp.reply(Gio.MountOperationResult.ABORTED);
} else { } else {
this.mountOp.set_choice(choice); this.mountOp.set_choice(choice);
this.mountOp.reply(Gio.MountOperationResult.HANDLED); this.mountOp.reply(Gio.MountOperationResult.HANDLED);
} }
this.close(); this._processesDialog.close(global.get_current_time());
})); this._dialog = null;
this._processesDialog.open(); }));
this._processesDialog.open(global.get_current_time());
} }
this._processesDialog.update(message, processes, choices); this._processesDialog.update(message, processes, choices);
}, },
_onShowUnmountProgress: function(op, message, timeLeft, bytesLeft) {
if (!this._notifier)
this._notifier = new ShellUnmountNotifier();
if (bytesLeft == 0)
this._notifier.done(message);
else
this._notifier.show(message);
},
borrowDialog: function() {
if (this._dialogId != 0) {
this._dialog.disconnect(this._dialogId);
this._dialogId = 0;
}
return this._dialog;
}
});
const ShellUnmountNotifier = new Lang.Class({
Name: 'ShellUnmountNotifier',
Extends: MessageTray.Source,
_init: function() {
this.parent('', 'media-removable');
this._notification = null;
Main.messageTray.add(this);
},
show: function(message) {
let [header, text] = message.split('\n', 2);
if (!this._notification) {
this._notification = new MessageTray.Notification(this, header, text);
this._notification.setTransient(true);
this._notification.setUrgency(MessageTray.Urgency.CRITICAL);
} else {
this._notification.update(header, text);
}
this.notify(this._notification);
},
done: function(message) {
if (this._notification) {
this._notification.destroy();
this._notification = null;
}
if (message) {
let notification = new MessageTray.Notification(this, message, null);
notification.setTransient(true);
this.notify(notification);
}
}
}); });
const ShellMountQuestionDialog = new Lang.Class({ const ShellMountQuestionDialog = new Lang.Class({
Name: 'ShellMountQuestionDialog', Name: 'ShellMountQuestionDialog',
Extends: ModalDialog.ModalDialog, Extends: ModalDialog.ModalDialog,
_init: function(gicon) { _init: function(icon) {
this.parent({ styleClass: 'mount-question-dialog' }); this.parent({ styleClass: 'mount-question-dialog' });
let mainContentLayout = new St.BoxLayout(); let mainContentLayout = new St.BoxLayout();
this.contentLayout.add(mainContentLayout, { x_fill: true, this.contentLayout.add(mainContentLayout, { x_fill: true,
y_fill: false }); y_fill: false });
this._iconBin = new St.Bin({ child: _createIcon(gicon) }); this._iconBin = new St.Bin({ child: icon });
mainContentLayout.add(this._iconBin, mainContentLayout.add(this._iconBin,
{ x_fill: true, { x_fill: true,
y_fill: false, y_fill: false,
@ -321,108 +234,62 @@ const ShellMountQuestionDialog = new Lang.Class({
}); });
Signals.addSignalMethods(ShellMountQuestionDialog.prototype); Signals.addSignalMethods(ShellMountQuestionDialog.prototype);
const ShellMountPasswordDialog = new Lang.Class({ const ShellMountPasswordSource = new Lang.Class({
Name: 'ShellMountPasswordDialog', Name: 'ShellMountPasswordSource',
Extends: ModalDialog.ModalDialog, Extends: MessageTray.Source,
_init: function(message, gicon, flags) { _init: function(message, icon, reaskPassword) {
let strings = message.split('\n'); let strings = message.split('\n');
this.parent({ styleClass: 'prompt-dialog' }); this.parent(strings[0]);
let mainContentBox = new St.BoxLayout({ style_class: 'prompt-dialog-main-layout', this._notification = new ShellMountPasswordNotification(this, strings, icon, reaskPassword);
vertical: false });
this.contentLayout.add(mainContentBox);
let icon = _createIcon(gicon); // add ourselves as a source, and popup the notification
mainContentBox.add(icon, Main.messageTray.add(this);
{ x_fill: true, this.notify(this._notification);
y_fill: false, },
x_align: St.Align.END, });
y_align: St.Align.START }); Signals.addSignalMethods(ShellMountPasswordSource.prototype);
this._messageBox = new St.BoxLayout({ style_class: 'prompt-dialog-message-layout', const ShellMountPasswordNotification = new Lang.Class({
vertical: true }); Name: 'ShellMountPasswordNotification',
mainContentBox.add(this._messageBox, Extends: MessageTray.Notification,
{ y_align: St.Align.START, expand: true, x_fill: true, y_fill: true });
let subject = new St.Label({ style_class: 'prompt-dialog-headline' }); _init: function(source, strings, icon, reaskPassword) {
this._messageBox.add(subject, this.parent(source, strings[0], null, { customContent: true, icon: icon });
{ y_fill: false,
y_align: St.Align.START }); // set the notification to transient and urgent, so that it
if (strings[0]) // expands out
subject.set_text(strings[0]); this.setTransient(true);
this.setUrgency(MessageTray.Urgency.CRITICAL);
let description = new St.Label({ style_class: 'prompt-dialog-description' });
description.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
description.clutter_text.line_wrap = true;
this._messageBox.add(description,
{ y_fill: true,
y_align: St.Align.START });
if (strings[1]) if (strings[1])
description.set_text(strings[1]); this.addBody(strings[1]);
this._passwordBox = new St.BoxLayout({ vertical: false, style_class: 'prompt-dialog-password-box' }); if (reaskPassword) {
this._messageBox.add(this._passwordBox); let label = new St.Label({ style_class: 'mount-password-reask',
text: _("Wrong password, please try again") });
this._passwordLabel = new St.Label(({ style_class: 'prompt-dialog-password-label', this.addActor(label);
text: _("Password") }));
this._passwordBox.add(this._passwordLabel, { y_fill: false, y_align: St.Align.MIDDLE });
this._passwordEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry',
text: "",
can_focus: true});
ShellEntry.addContextMenu(this._passwordEntry, { isPassword: true });
this._passwordEntry.clutter_text.connect('activate', Lang.bind(this, this._onEntryActivate));
this._passwordEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
this._passwordBox.add(this._passwordEntry, {expand: true });
this.setInitialKeyFocus(this._passwordEntry);
this._errorMessageLabel = new St.Label({ style_class: 'prompt-dialog-error-label',
text: _("Sorry, that didn\'t work. Please try again.") });
this._errorMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
this._errorMessageLabel.clutter_text.line_wrap = true;
this._errorMessageLabel.hide();
this._messageBox.add(this._errorMessageLabel);
if (flags & Gio.AskPasswordFlags.SAVING_SUPPORTED) {
this._rememberChoice = new CheckBox.CheckBox();
this._rememberChoice.getLabelActor().text = _("Remember Password");
this._rememberChoice.actor.checked = true;
this._messageBox.add(this._rememberChoice.actor);
} else {
this._rememberChoice = null;
} }
let buttons = [{ label: _("Cancel"), this._responseEntry = new St.Entry({ style_class: 'mount-password-entry',
action: Lang.bind(this, this._onCancelButton), can_focus: true });
key: Clutter.Escape this.setActionArea(this._responseEntry);
},
{ label: _("Unlock"),
action: Lang.bind(this, this._onUnlockButton),
default: true
}];
this.setButtons(buttons); this._responseEntry.clutter_text.connect('activate',
Lang.bind(this, this._onEntryActivated));
this._responseEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
this._responseEntry.grab_key_focus();
}, },
reaskPassword: function() { _onEntryActivated: function() {
this._passwordEntry.set_text(''); let text = this._responseEntry.get_text();
this._errorMessageLabel.show(); if (text == '')
}, return;
_onCancelButton: function() { this.source.emit('password-ready', text);
this.emit('response', -1, '', false);
},
_onUnlockButton: function() {
this._onEntryActivate();
},
_onEntryActivate: function() {
this.emit('response', 1,
this._passwordEntry.get_text(),
this._rememberChoice &&
this._rememberChoice.actor.checked);
} }
}); });
@ -430,14 +297,14 @@ const ShellProcessesDialog = new Lang.Class({
Name: 'ShellProcessesDialog', Name: 'ShellProcessesDialog',
Extends: ModalDialog.ModalDialog, Extends: ModalDialog.ModalDialog,
_init: function(gicon) { _init: function(icon) {
this.parent({ styleClass: 'show-processes-dialog' }); this.parent({ styleClass: 'show-processes-dialog' });
let mainContentLayout = new St.BoxLayout(); let mainContentLayout = new St.BoxLayout();
this.contentLayout.add(mainContentLayout, { x_fill: true, this.contentLayout.add(mainContentLayout, { x_fill: true,
y_fill: false }); y_fill: false });
this._iconBin = new St.Bin({ child: _createIcon(gicon) }); this._iconBin = new St.Bin({ child: icon });
mainContentLayout.add(this._iconBin, mainContentLayout.add(this._iconBin,
{ x_fill: true, { x_fill: true,
y_fill: false, y_fill: false,
@ -471,17 +338,21 @@ const ShellProcessesDialog = new Lang.Class({
scrollView.hide(); scrollView.hide();
this._applicationList = new St.BoxLayout({ vertical: true }); this._applicationList = new St.BoxLayout({ vertical: true });
scrollView.add_actor(this._applicationList); scrollView.add_actor(this._applicationList,
{ x_fill: true,
y_fill: true,
x_align: St.Align.START,
y_align: St.Align.MIDDLE });
this._applicationList.connect('actor-added', this._applicationList.connect('actor-added',
Lang.bind(this, function() { Lang.bind(this, function() {
if (this._applicationList.get_n_children() == 1) if (this._applicationList.get_children().length == 1)
scrollView.show(); scrollView.show();
})); }));
this._applicationList.connect('actor-removed', this._applicationList.connect('actor-removed',
Lang.bind(this, function() { Lang.bind(this, function() {
if (this._applicationList.get_n_children() == 0) if (this._applicationList.get_children().length == 0)
scrollView.hide(); scrollView.hide();
})); }));
}, },
@ -515,253 +386,3 @@ const ShellProcessesDialog = new Lang.Class({
} }
}); });
Signals.addSignalMethods(ShellProcessesDialog.prototype); Signals.addSignalMethods(ShellProcessesDialog.prototype);
const GnomeShellMountOpIface = <interface name="org.Gtk.MountOperationHandler">
<method name="AskPassword">
<arg type="s" direction="in" name="object_id"/>
<arg type="s" direction="in" name="message"/>
<arg type="s" direction="in" name="icon_name"/>
<arg type="s" direction="in" name="default_user"/>
<arg type="s" direction="in" name="default_domain"/>
<arg type="u" direction="in" name="flags"/>
<arg type="u" direction="out" name="response"/>
<arg type="a{sv}" direction="out" name="response_details"/>
</method>
<method name="AskQuestion">
<arg type="s" direction="in" name="object_id"/>
<arg type="s" direction="in" name="message"/>
<arg type="s" direction="in" name="icon_name"/>
<arg type="as" direction="in" name="choices"/>
<arg type="u" direction="out" name="response"/>
<arg type="a{sv}" direction="out" name="response_details"/>
</method>
<method name="ShowProcesses">
<arg type="s" direction="in" name="object_id"/>
<arg type="s" direction="in" name="message"/>
<arg type="s" direction="in" name="icon_name"/>
<arg type="ai" direction="in" name="application_pids"/>
<arg type="as" direction="in" name="choices"/>
<arg type="u" direction="out" name="response"/>
<arg type="a{sv}" direction="out" name="response_details"/>
</method>
<method name="Close"/>
</interface>;
const ShellMountOperationType = {
NONE: 0,
ASK_PASSWORD: 1,
ASK_QUESTION: 2,
SHOW_PROCESSES: 3
};
const GnomeShellMountOpHandler = new Lang.Class({
Name: 'GnomeShellMountOpHandler',
_init: function() {
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellMountOpIface, this);
this._dbusImpl.export(Gio.DBus.session, '/org/gtk/MountOperationHandler');
Gio.bus_own_name_on_connection(Gio.DBus.session, 'org.gtk.MountOperationHandler',
Gio.BusNameOwnerFlags.REPLACE, null, null);
this._dialog = null;
this._volumeMonitor = Gio.VolumeMonitor.get();
this._ensureEmptyRequest();
},
_ensureEmptyRequest: function() {
this._currentId = null;
this._currentInvocation = null;
this._currentType = ShellMountOperationType.NONE;
},
_clearCurrentRequest: function(response, details) {
if (this._currentInvocation) {
this._currentInvocation.return_value(
GLib.Variant.new('(ua{sv})', [response, details]));
}
this._ensureEmptyRequest();
},
_setCurrentRequest: function(invocation, id, type) {
let oldId = this._currentId;
let oldType = this._currentType;
let requestId = id + '@' + invocation.get_sender();
this._clearCurrentRequest(Gio.MountOperationResult.UNHANDLED, {});
this._currentInvocation = invocation;
this._currentId = requestId;
this._currentType = type;
if (this._dialog && (oldId == requestId) && (oldType == type))
return true;
return false;
},
_closeDialog: function() {
if (this._dialog) {
this._dialog.close();
this._dialog = null;
}
},
_createGIcon: function(iconName) {
let realIconName = iconName ? iconName : 'drive-harddisk';
return new Gio.ThemedIcon({ name: realIconName,
use_default_fallbacks: true });
},
/**
* AskPassword:
* @id: an opaque ID identifying the object for which the operation is requested
* The ID must be unique in the context of the calling process.
* @message: the message to display
* @icon_name: the name of an icon to display
* @default_user: the default username for display
* @default_domain: the default domain for display
* @flags: a set of GAskPasswordFlags
* @response: a GMountOperationResult
* @response_details: a dictionary containing the response details as
* entered by the user. The dictionary MAY contain the following properties:
* - "password" -> (s): a password to be used to complete the mount operation
* - "password_save" -> (u): a GPasswordSave
*
* The dialog will stay visible until clients call the Close() method, or
* another dialog becomes visible.
* Calling AskPassword again for the same id will have the effect to clear
* the existing dialog and update it with a message indicating the previous
* attempt went wrong.
*/
AskPasswordAsync: function(params, invocation) {
let [id, message, iconName, defaultUser, defaultDomain, flags] = params;
if (this._setCurrentRequest(invocation, id, ShellMountOperationType.ASK_PASSWORD)) {
this._dialog.reaskPassword();
return;
}
this._closeDialog();
this._dialog = new ShellMountPasswordDialog(message, this._createGIcon(iconName), flags);
this._dialog.connect('response', Lang.bind(this,
function(object, choice, password, remember) {
let details = {};
let response;
if (choice == -1) {
response = Gio.MountOperationResult.ABORTED;
} else {
response = Gio.MountOperationResult.HANDLED;
let passSave = remember ? Gio.PasswordSave.PERMANENTLY : Gio.PasswordSave.NEVER;
details['password_save'] = GLib.Variant.new('u', passSave);
details['password'] = GLib.Variant.new('s', password);
}
this._clearCurrentRequest(response, details);
}));
this._dialog.open();
},
/**
* AskQuestion:
* @id: an opaque ID identifying the object for which the operation is requested
* The ID must be unique in the context of the calling process.
* @message: the message to display
* @icon_name: the name of an icon to display
* @choices: an array of choice strings
* GetResponse:
* @response: a GMountOperationResult
* @response_details: a dictionary containing the response details as
* entered by the user. The dictionary MAY contain the following properties:
* - "choice" -> (i): the chosen answer among the array of strings passed in
*
* The dialog will stay visible until clients call the Close() method, or
* another dialog becomes visible.
* Calling AskQuestion again for the same id will have the effect to clear
* update the dialog with the new question.
*/
AskQuestionAsync: function(params, invocation) {
let [id, message, iconName, choices] = params;
if (this._setCurrentRequest(invocation, id, ShellMountOperationType.ASK_QUESTION)) {
this._dialog.update(message, choices);
return;
}
this._closeDialog();
this._dialog = new ShellMountQuestionDialog(this._createGIcon(iconName), message);
this._dialog.connect('response', Lang.bind(this,
function(object, choice) {
this._clearCurrentRequest(Gio.MountOperationResult.HANDLED,
{ choice: GLib.Variant.new('i', choice) });
}));
this._dialog.update(message, choices);
this._dialog.open();
},
/**
* ShowProcesses:
* @id: an opaque ID identifying the object for which the operation is requested
* The ID must be unique in the context of the calling process.
* @message: the message to display
* @icon_name: the name of an icon to display
* @application_pids: the PIDs of the applications to display
* @choices: an array of choice strings
* @response: a GMountOperationResult
* @response_details: a dictionary containing the response details as
* entered by the user. The dictionary MAY contain the following properties:
* - "choice" -> (i): the chosen answer among the array of strings passed in
*
* The dialog will stay visible until clients call the Close() method, or
* another dialog becomes visible.
* Calling ShowProcesses again for the same id will have the effect to clear
* the existing dialog and update it with the new message and the new list
* of processes.
*/
ShowProcessesAsync: function(params, invocation) {
let [id, message, iconName, applicationPids, choices] = params;
if (this._setCurrentRequest(invocation, id, ShellMountOperationType.SHOW_PROCESSES)) {
this._dialog.update(message, applicationPids, choices);
return;
}
this._closeDialog();
this._dialog = new ShellProcessesDialog(this._createGIcon(iconName));
this._dialog.connect('response', Lang.bind(this,
function(object, choice) {
let response;
let details = {};
if (choice == -1) {
response = Gio.MountOperationResult.ABORTED;
} else {
response = Gio.MountOperationResult.HANDLED;
details['choice'] = GLib.Variant.new('i', choice);
}
this._clearCurrentRequest(response, details);
}));
this._dialog.update(message, applicationPids, choices);
this._dialog.open();
},
/**
* Close:
*
* Closes a dialog previously opened by AskPassword, AskQuestion or ShowProcesses.
* If no dialog is open, does nothing.
*/
Close: function(params, invocation) {
this._clearCurrentRequest(Gio.MountOperationResult.UNHANDLED, {});
this._closeDialog();
}
});

View File

@ -1,10 +1,18 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const GDesktopEnums = imports.gi.GDesktopEnums;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Shell = imports.gi.Shell;
const Signals = imports.signals;
const St = imports.gi.St;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const Util = imports.misc.util;
const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard'; const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard';
const KEY_STICKY_KEYS_ENABLED = 'stickykeys-enable'; const KEY_STICKY_KEYS_ENABLED = 'stickykeys-enable';
@ -36,7 +44,7 @@ const ATIndicator = new Lang.Class({
Extends: PanelMenu.SystemStatusButton, Extends: PanelMenu.SystemStatusButton,
_init: function() { _init: function() {
this.parent('preferences-desktop-accessibility-symbolic', _("Accessibility")); this.parent('preferences-desktop-accessibility', _("Accessibility"));
let highContrast = this._buildHCItem(); let highContrast = this._buildHCItem();
this.menu.addMenuItem(highContrast); this.menu.addMenuItem(highContrast);
@ -48,9 +56,9 @@ const ATIndicator = new Lang.Class({
let textZoom = this._buildFontItem(); let textZoom = this._buildFontItem();
this.menu.addMenuItem(textZoom); this.menu.addMenuItem(textZoom);
let screenReader = this._buildItem(_("Screen Reader"), APPLICATIONS_SCHEMA, // let screenReader = this._buildItem(_("Screen Reader"), APPLICATIONS_SCHEMA,
'screen-reader-enabled'); // 'screen-reader-enabled');
this.menu.addMenuItem(screenReader); // this.menu.addMenuItem(screenReader);
let screenKeyboard = this._buildItem(_("Screen Keyboard"), APPLICATIONS_SCHEMA, let screenKeyboard = this._buildItem(_("Screen Keyboard"), APPLICATIONS_SCHEMA,
'screen-keyboard-enabled'); 'screen-keyboard-enabled');
@ -75,10 +83,6 @@ const ATIndicator = new Lang.Class({
this.menu.addSettingsAction(_("Universal Access Settings"), 'gnome-universal-access-panel.desktop'); this.menu.addSettingsAction(_("Universal Access Settings"), 'gnome-universal-access-panel.desktop');
}, },
setLockedState: function(locked) {
this.actor.visible = !locked;
},
_buildItemExtended: function(string, initial_value, writable, on_set) { _buildItemExtended: function(string, initial_value, writable, on_set) {
let widget = new PopupMenu.PopupSwitchMenuItem(string, initial_value); let widget = new PopupMenu.PopupSwitchMenuItem(string, initial_value);
if (!writable) if (!writable)

View File

@ -1,11 +1,15 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const Gdk = imports.gi.Gdk;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const GnomeBluetoothApplet = imports.gi.GnomeBluetoothApplet; const GnomeBluetoothApplet = imports.gi.GnomeBluetoothApplet;
const GnomeBluetooth = imports.gi.GnomeBluetooth; const Gtk = imports.gi.Gtk;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop;
const St = imports.gi.St; const St = imports.gi.St;
const Shell = imports.gi.Shell;
const Main = imports.ui.main; const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray; const MessageTray = imports.ui.messageTray;
@ -24,7 +28,7 @@ const Indicator = new Lang.Class({
Extends: PanelMenu.SystemStatusButton, Extends: PanelMenu.SystemStatusButton,
_init: function() { _init: function() {
this.parent('bluetooth-disabled-symbolic', _("Bluetooth")); this.parent('bluetooth-disabled', _("Bluetooth"));
this._applet = new GnomeBluetoothApplet.Applet(); this._applet = new GnomeBluetoothApplet.Applet();
@ -32,11 +36,11 @@ const Indicator = new Lang.Class({
this._applet.connect('notify::killswitch-state', Lang.bind(this, this._updateKillswitch)); this._applet.connect('notify::killswitch-state', Lang.bind(this, this._updateKillswitch));
this._killswitch.connect('toggled', Lang.bind(this, function() { this._killswitch.connect('toggled', Lang.bind(this, function() {
let current_state = this._applet.killswitch_state; let current_state = this._applet.killswitch_state;
if (current_state != GnomeBluetooth.KillswitchState.HARD_BLOCKED && if (current_state != GnomeBluetoothApplet.KillswitchState.HARD_BLOCKED &&
current_state != GnomeBluetooth.KillswitchState.NO_ADAPTER) { current_state != GnomeBluetoothApplet.KillswitchState.NO_ADAPTER) {
this._applet.killswitch_state = this._killswitch.state ? this._applet.killswitch_state = this._killswitch.state ?
GnomeBluetooth.KillswitchState.UNBLOCKED: GnomeBluetoothApplet.KillswitchState.UNBLOCKED:
GnomeBluetooth.KillswitchState.SOFT_BLOCKED; GnomeBluetoothApplet.KillswitchState.SOFT_BLOCKED;
} else } else
this._killswitch.setToggleState(false); this._killswitch.setToggleState(false);
})); }));
@ -88,17 +92,12 @@ const Indicator = new Lang.Class({
this._applet.connect('cancel-request', Lang.bind(this, this._cancelRequest)); this._applet.connect('cancel-request', Lang.bind(this, this._cancelRequest));
}, },
setLockedState: function(locked) {
this._isLocked = locked;
this._updateKillswitch();
},
_updateKillswitch: function() { _updateKillswitch: function() {
let current_state = this._applet.killswitch_state; let current_state = this._applet.killswitch_state;
let on = current_state == GnomeBluetooth.KillswitchState.UNBLOCKED; let on = current_state == GnomeBluetoothApplet.KillswitchState.UNBLOCKED;
let has_adapter = current_state != GnomeBluetooth.KillswitchState.NO_ADAPTER; let has_adapter = current_state != GnomeBluetoothApplet.KillswitchState.NO_ADAPTER;
let can_toggle = current_state != GnomeBluetooth.KillswitchState.NO_ADAPTER && let can_toggle = current_state != GnomeBluetoothApplet.KillswitchState.NO_ADAPTER &&
current_state != GnomeBluetooth.KillswitchState.HARD_BLOCKED; current_state != GnomeBluetoothApplet.KillswitchState.HARD_BLOCKED;
this._killswitch.setToggleState(on); this._killswitch.setToggleState(on);
if (can_toggle) if (can_toggle)
@ -107,14 +106,14 @@ const Indicator = new Lang.Class({
/* TRANSLATORS: this means that bluetooth was disabled by hardware rfkill */ /* TRANSLATORS: this means that bluetooth was disabled by hardware rfkill */
this._killswitch.setStatus(_("hardware disabled")); this._killswitch.setStatus(_("hardware disabled"));
this.actor.visible = !this._isLocked && has_adapter; this.actor.visible = has_adapter;
if (on) { if (on) {
this._discoverable.actor.show(); this._discoverable.actor.show();
this.setIcon('bluetooth-active-symbolic'); this.setIcon('bluetooth-active');
} else { } else {
this._discoverable.actor.hide(); this._discoverable.actor.hide();
this.setIcon('bluetooth-disabled-symbolic'); this.setIcon('bluetooth-disabled');
} }
}, },
@ -306,7 +305,7 @@ const Indicator = new Lang.Class({
_ensureSource: function() { _ensureSource: function() {
if (!this._source) { if (!this._source) {
this._source = new MessageTray.Source(_("Bluetooth"), 'bluetooth-active'); this._source = new Source();
Main.messageTray.add(this._source); Main.messageTray.add(this._source);
} }
}, },
@ -331,6 +330,35 @@ const Indicator = new Lang.Class({
} }
}); });
const Source = new Lang.Class({
Name: 'BluetoothSource',
Extends: MessageTray.Source,
_init: function() {
this.parent(_("Bluetooth"));
this._setSummaryIcon(this.createNotificationIcon());
},
notify: function(notification) {
this._private_destroyId = notification.connect('destroy', Lang.bind(this, function(notification) {
if (this.notification == notification) {
// the destroyed notification is the last for this source
this.notification.disconnect(this._private_destroyId);
this.destroy();
}
}));
this.parent(notification);
},
createNotificationIcon: function() {
return new St.Icon({ icon_name: 'bluetooth-active',
icon_type: St.IconType.SYMBOLIC,
icon_size: this.ICON_SIZE });
}
});
const AuthNotification = new Lang.Class({ const AuthNotification = new Lang.Class({
Name: 'AuthNotification', Name: 'AuthNotification',
Extends: MessageTray.Notification, Extends: MessageTray.Notification,
@ -381,7 +409,7 @@ const ConfirmNotification = new Lang.Class({
this._applet = applet; this._applet = applet;
this._devicePath = device_path; this._devicePath = device_path;
this.addBody(_("Device %s wants to pair with this computer").format(long_name)); this.addBody(_("Device %s wants to pair with this computer").format(long_name));
this.addBody(_("Please confirm whether the PIN '%06d' matches the one on the device.").format(pin)); this.addBody(_("Please confirm whether the PIN '%s' matches the one on the device.").format(pin));
this.addButton('matches', _("Matches")); this.addButton('matches', _("Matches"));
this.addButton('does-not-match', _("Does not match")); this.addButton('does-not-match', _("Does not match"));
@ -417,8 +445,7 @@ const PinNotification = new Lang.Class({
this._entry.connect('key-release-event', Lang.bind(this, function(entry, event) { this._entry.connect('key-release-event', Lang.bind(this, function(entry, event) {
let key = event.get_key_symbol(); let key = event.get_key_symbol();
if (key == Clutter.KEY_Return) { if (key == Clutter.KEY_Return) {
if (this._canActivateOkButton()) this.emit('action-invoked', 'ok');
this.emit('action-invoked', 'ok');
return true; return true;
} else if (key == Clutter.KEY_Escape) { } else if (key == Clutter.KEY_Escape) {
this.emit('action-invoked', 'cancel'); this.emit('action-invoked', 'cancel');
@ -431,12 +458,6 @@ const PinNotification = new Lang.Class({
this.addButton('ok', _("OK")); this.addButton('ok', _("OK"));
this.addButton('cancel', _("Cancel")); this.addButton('cancel', _("Cancel"));
this.setButtonSensitive('ok', this._canActivateOkButton());
this._entry.clutter_text.connect('text-changed', Lang.bind(this,
function() {
this.setButtonSensitive('ok', this._canActivateOkButton());
}));
this.connect('action-invoked', Lang.bind(this, function(self, action) { this.connect('action-invoked', Lang.bind(this, function(self, action) {
if (action == 'ok') { if (action == 'ok') {
if (this._numeric) { if (this._numeric) {
@ -459,11 +480,6 @@ const PinNotification = new Lang.Class({
})); }));
}, },
_canActivateOkButton: function() {
// PINs have a fixed length of 6
return this._entry.clutter_text.text.length == 6;
},
grabFocus: function(lockTray) { grabFocus: function(lockTray) {
this.parent(lockTray); this.parent(lockTray);
global.stage.set_key_focus(this._entry); global.stage.set_key_focus(this._entry);

View File

@ -0,0 +1,584 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/*
* Copyright 2012 Red Hat, Inc.
* Copyright 2012 Peng Huang <shawn.p.huang@gmail.com>
* Copyright 2012 Takao Fujiwara <tfujiwar@redhat.com>
* Copyright 2012 Tiger Soldier <tigersoldi@gmail.com>
*
* 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.1 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope 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 program. If not, see <http://www.gnu.org/licenses/>.
*/
const St = imports.gi.St;
const GLib = imports.gi.GLib;
const IBus = imports.gi.IBus;
const Lang = imports.lang;
const Signals = imports.signals;
const Shell = imports.gi.Shell;
const BoxPointer = imports.ui.boxpointer;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
const ORIENTATION_HORIZONTAL = 0;
const ORIENTATION_VERTICAL = 1;
const ORIENTATION_SYSTEM = 2;
const StCandidateArea = new Lang.Class({
Name: 'StCandidateArea',
_init: function(orientation) {
this.actor = new St.BoxLayout({ style_class: 'candidate-area' });
this._orientation = orientation;
this._labels = [];
this._labelBoxes = [];
this._createUI();
},
_removeOldWidgets: function() {
this.actor.destroy_all_children();
this._labels = [];
this._labelBoxes = [];
},
_createUI: function() {
let vbox = null;
let hbox = null;
if (this._orientation == ORIENTATION_VERTICAL) {
vbox = new St.BoxLayout({ vertical: true,
style_class: 'candidate-vertical' });
this.actor.add_child(vbox,
{ expand: true,
x_fill: true,
y_fill: true
});
} else {
hbox = new St.BoxLayout({ vertical: false,
style_class: 'candidate-horizontal' });
this.actor.add_child(hbox,
{ expand: true,
x_fill: true,
y_fill: true
});
}
for (let i = 0; i < 16; i++) {
let label1 = new St.Label({ text: '1234567890abcdef'.charAt(i) + '.',
style_class: 'popup-menu-item',
reactive: true });
let label2 = new St.Label({ text: '' ,
style_class: 'popup-menu-item',
reactive: true });
if (this._orientation == ORIENTATION_VERTICAL) {
let candidateHBox = new St.BoxLayout({vertical: false});
let labelBox = new St.Bin({ style_class: 'candidate-hlabel-content' });
labelBox.set_child(label1);
labelBox.set_fill(true, true);
let textBox = new St.Bin({ style_class: 'candidate-htext-content' });
textBox.set_child(label2);
textBox.set_fill(true, true);
candidateHBox.add_child(labelBox,
{ expand: false,
x_fill: false,
y_fill: true
});
candidateHBox.add_child(textBox,
{ expand: true,
x_fill: true,
y_fill: true
});
vbox.add_child(candidateHBox);
this._labelBoxes.push(candidateHBox);
} else {
let candidateHBox = new St.BoxLayout({ style_class: 'candidate-vcontent',
vertical: false });
candidateHBox.add_child(label1);
candidateHBox.add_child(label2);
hbox.add_child(candidateHBox);
this._labelBoxes.push(candidateHBox);
}
this._labels.push([label1, label2]);
}
for (let i = 0; i < this._labels.length; i++) {
for(let j = 0; j < this._labels[i].length; j++) {
let widget = this._labels[i][j];
widget.candidateIndex = i;
widget.connect('button-press-event',
Lang.bind(this, function (widget, event) {
this._candidateClickedCB(widget, event);
}));
widget.connect('enter-event',
function(widget, event) {
widget.add_style_pseudo_class('hover');
});
widget.connect('leave-event',
function(widget, event) {
widget.remove_style_pseudo_class('hover');
});
}
}
},
_recreateUI: function() {
this._removeOldWidgets();
this._createUI();
},
_candidateClickedCB: function(widget, event) {
this.emit('candidate-clicked',
widget.candidateIndex,
event.get_button(),
event.get_state());
},
setLabels: function(labels) {
if (!labels || labels.length == 0) {
for (let i = 0; i < 16; i++) {
this._labels[i][0].set_text('1234567890abcdef'.charAt(i) + '.');
}
return;
}
for (let i = 0; i < labels.length && i < this._labels.length; i++) {
/* Use a ClutterActor attribute of Shell's theme instead of
* Pango.AttrList for the lookup window GUI and
* can ignore 'attrs' simply from IBus engines?
*/
let [text, attrs] = labels[i];
this._labels[i][0].set_text(text);
}
},
setCandidates: function(candidates, focusCandidate, showCursor) {
if (focusCandidate == undefined) {
focusCandidate = 0;
}
if (showCursor == undefined) {
showCursor = true;
}
if (candidates.length > this._labels.length) {
assert();
}
for (let i = 0; i < candidates.length; i++) {
/* Use a ClutterActor attribute of Shell's theme instead of
* Pango.AttrList for the lookup window GUI and
* can ignore 'attrs' simply from IBus engines?
*/
let [text, attrs] = candidates[i];
if (i == focusCandidate && showCursor) {
this._labels[i][1].add_style_pseudo_class('active');
} else {
this._labels[i][1].remove_style_pseudo_class('active');
}
this._labels[i][1].set_text(text);
this._labelBoxes[i].show();
}
for (let i = this._labelBoxes.length - 1; i >= candidates.length; i--) {
this._labelBoxes[i].hide();
}
},
setOrientation: function(orientation) {
if (orientation == this._orientation)
return;
this._orientation = orientation;
this._recreateUI();
},
showAll: function() {
this.actor.show();
},
hideAll: function() {
this.actor.hide();
},
});
Signals.addSignalMethods(StCandidateArea.prototype);
const CandidatePanel = new Lang.Class({
Name: 'CandidatePanel',
_init: function() {
this._orientation = ORIENTATION_VERTICAL;
this._currentOrientation = this._orientation;
this._preeditVisible = false;
this._auxStringVisible = false;
this._lookupTableVisible = false;
this._lookupTable = null;
this._cursorLocation = [0, 0, 0, 0];
this._movedCursorLocation = null;
this._initSt();
},
_initSt: function() {
this._arrowSide = St.Side.TOP;
this._arrowAlignment = 0.0;
this._boxPointer = new BoxPointer.BoxPointer(this._arrowSide,
{ x_fill: true,
y_fill: true,
x_align: St.Align.START });
this.actor = this._boxPointer.actor;
this.actor._delegate = this;
this.actor.style_class = 'popup-menu-boxpointer';
this.actor.add_style_class_name('popup-menu');
this.actor.add_style_class_name('candidate-panel');
this._cursorActor = new Shell.GenericContainer();
Main.uiGroup.add_actor(this.actor);
Main.uiGroup.add_actor(this._cursorActor);
this._stCandidatePanel = new St.BoxLayout({ style_class: 'candidate-panel',
vertical: true });
this._boxPointer.bin.set_child(this._stCandidatePanel);
this._stPreeditLabel = new St.Label({ style_class: 'popup-menu-item',
text: '' });
if (!this._preeditVisible) {
this._stPreeditLabel.hide();
}
this._stAuxLabel = new St.Label({ style_class: 'popup-menu-item',
text: '' });
if (!this._auxVisible) {
this._stAuxLabel.hide();
}
this._separator = new PopupMenu.PopupSeparatorMenuItem();
if (!this._preeditVisible && !this._auxVisible) {
this._separator.actor.hide();
}
// create candidates area
this._stCandidateArea = new StCandidateArea(this._currentOrientation);
this._stCandidateArea.connect('candidate-clicked',
Lang.bind(this, function(x, i, b, s) {
this.emit('candidate-clicked', i, b, s);}));
this.updateLookupTable(this._lookupTable, this._lookupTableVisible);
// TODO: page up/down GUI
this._packAllStWidgets();
this._isVisible = true;
this.hideAll();
this._checkShowStates();
},
_packAllStWidgets: function() {
this._stCandidatePanel.add_child(this._stPreeditLabel,
{ x_fill: true,
y_fill: false,
x_align: St.Align.MIDDLE,
y_align: St.Align.START });
this._stCandidatePanel.add_child(this._stAuxLabel,
{ x_fill: true,
y_fill: false,
x_align: St.Align.MIDDLE,
y_align: St.Align.MIDDLE });
this._stCandidatePanel.add_child(this._separator.actor,
{ x_fill: true,
y_fill: false,
x_align: St.Align.MIDDLE,
y_align: St.Align.MIDDLE });
this._stCandidatePanel.add_child(this._stCandidateArea.actor,
{ x_fill: true,
y_fill: false,
x_align: St.Align.MIDDLE,
y_align: St.Align.END });
},
showPreeditText: function() {
this._preeditVisible = true;
this._stPreeditLabel.show();
this._checkShowStates();
},
hidePreeditText: function() {
this._preeditVisible = false;
this._checkShowStates();
this._stPreeditLabel.hide();
},
updatePreeditText: function(text, cursorPos, visible) {
if (visible) {
this.showPreeditText();
} else {
this.hidePreeditText();
}
let str = text.get_text();
this._stPreeditLabel.set_text(str);
let attrs = text.get_attributes();
for (let i = 0; attrs != null && attrs.get(i) != null; i++) {
let attr = attrs.get(i);
if (attr.get_attr_type() == IBus.AttrType.BACKGROUND) {
let startIndex = attr.get_start_index();
let endIndex = attr.get_end_index();
let len = GLib.utf8_strlen(str, -1);
let markup = '';
if (startIndex == 0 &&
endIndex == GLib.utf8_strlen(str, -1)) {
markup = markup.concat(str);
} else {
if (startIndex > 0) {
markup = markup.concat(GLib.utf8_substring(str,
0,
startIndex));
}
if (startIndex != endIndex) {
markup = markup.concat('<span background=\"#555555\">');
markup = markup.concat(GLib.utf8_substring(str,
startIndex,
endIndex));
markup = markup.concat('</span>');
}
if (endIndex < len) {
markup = markup.concat(GLib.utf8_substring(str,
endIndex,
len));
}
}
let clutter_text = this._stPreeditLabel.get_clutter_text();
clutter_text.set_markup(markup);
clutter_text.queue_redraw();
}
}
},
showAuxiliaryText: function() {
this._auxStringVisible = true;
this._stAuxLabel.show();
this._checkShowStates();
},
hideAuxiliaryText: function() {
this._auxStringVisible = false;
this._checkShowStates();
this._stAuxLabel.hide();
},
updateAuxiliaryText: function(text, show) {
if (show) {
this.showAuxiliaryText();
} else {
this.hideAuxiliaryText();
}
this._stAuxLabel.set_text(text.get_text());
},
_refreshLabels: function() {
let newLabels = [];
for (let i = 0; this._lookupTable.get_label(i) != null; i++) {
let label = this._lookupTable.get_label(i);
newLabels.push([label.get_text(), label.get_attributes()]);
}
this._stCandidateArea.setLabels(newLabels);
},
_getCandidatesInCurrentPage: function() {
let cursorPos = this._lookupTable.get_cursor_pos();
let pageSize = this._lookupTable.get_page_size();
let page = ((cursorPos == 0) ? 0 : Math.floor(cursorPos / pageSize));
let startIndex = page * pageSize;
let endIndex = Math.min((page + 1) * pageSize,
this._lookupTable.get_number_of_candidates());
let candidates = [];
for (let i = startIndex; i < endIndex; i++) {
candidates.push(this._lookupTable.get_candidate(i));
}
return candidates;
},
_getCursorPosInCurrentPage: function() {
let cursorPos = this._lookupTable.get_cursor_pos();
let pageSize = this._lookupTable.get_page_size();
let posInPage = cursorPos % pageSize;
return posInPage;
},
_refreshCandidates: function() {
let candidates = this._getCandidatesInCurrentPage();
let newCandidates = [];
for (let i = 0; i < candidates.length; i++) {
let candidate = candidates[i];
newCandidates.push([candidate.get_text(),
candidate.get_attributes()]);
}
this._stCandidateArea.setCandidates(newCandidates,
this._getCursorPosInCurrentPage(),
this._lookupTable.is_cursor_visible());
},
updateLookupTable: function(lookupTable, visible) {
// hide lookup table
if (!visible) {
this.hideLookupTable();
}
this._lookupTable = lookupTable || new IBus.LookupTable();
let orientation = this._lookupTable.get_orientation();
if (orientation != ORIENTATION_HORIZONTAL &&
orientation != ORIENTATION_VERTICAL) {
orientation = this._orientation;
}
this.setCurrentOrientation(orientation);
this._refreshCandidates();
this._refreshLabels();
// show lookup table
if (visible) {
this.showLookupTable();
}
},
showLookupTable: function() {
this._lookupTableVisible = true;
this._stCandidateArea.showAll();
this._checkShowStates();
},
hideLookupTable: function() {
this._lookupTableVisible = false;
this._checkShowStates();
this._stCandidateArea.hideAll();
},
pageUpLookupTable: function() {
this._lookupTable.page_up();
this._refreshCandidates();
},
pageDownLookup_table: function() {
this._lookupTable.page_down();
this._refreshCandidates();
},
cursorUpLookupTable: function() {
this._lookupTable.cursor_up();
this._refreshCandidates();
},
cursorDownLookupTable: function() {
this._lookupTable.cursor_down();
this._refreshCandidates();
},
setCursorLocation: function(x, y, w, h) {
// if cursor location is changed, we reset the moved cursor location
if (this._cursorLocation.join() != [x, y, w, h].join()) {
this._cursorLocation = [x, y, w, h];
this._movedCursorLocation = null;
this._checkPosition();
}
},
_checkShowStates: function() {
this._checkSeparatorShowStates();
if (this._preeditVisible ||
this._auxStringVisible ||
this._lookupTableVisible) {
this._checkPosition();
this.showAll();
this.emit('show');
} else {
this.hideAll();
this.emit('hide');
}
},
_checkSeparatorShowStates: function() {
if (this._preeditVisible || this._auxStringVisible) {
this._separator.actor.show();
}
else
this._separator.actor.hide();
},
reset: function() {
let text = IBus.Text.new_from_string('');
this.updatePreeditText(text, 0, false);
text = IBus.Text.new_from_string('');
this.updateAuxiliaryText(text, false);
this.updateLookupTable(null, false);
this.hideAll();
},
setCurrentOrientation: function(orientation) {
if (this._currentOrientation == orientation) {
return;
}
this._currentOrientation = orientation;
this._stCandidateArea.setOrientation(orientation);
},
setOrientation: function(orientation) {
this._orientation = orientation;
this.updateLookupTable(this._lookupTable, this._lookupTableVisible);
},
getCurrentOrientation: function() {
return this._currentOrientation;
},
_checkPosition: function() {
let cursorLocation = this._movedCursorLocation || this._cursorLocation;
let [cursorX, cursorY, cursorWidth, cursorHeight] = cursorLocation;
let windowRight = cursorX + cursorWidth + this.actor.get_width();
let windowBottom = cursorY + cursorHeight + this.actor.get_height();
this._cursorActor.set_position(cursorX, cursorY);
this._cursorActor.set_size(cursorWidth, cursorHeight);
let monitor = Main.layoutManager.findMonitorForActor(this._cursorActor);
let [sx, sy] = [monitor.x + monitor.width, monitor.y + monitor.height];
if (windowBottom > sy) {
this._arrowSide = St.Side.BOTTOM;
} else {
this._arrowSide = St.Side.TOP;
}
this._boxPointer._arrowSide = this._arrowSide;
this._boxPointer.setArrowOrigin(this._arrowSide);
this._boxPointer.setPosition(this._cursorActor, this._arrowAlignment);
},
showAll: function() {
if (!this._isVisible) {
this.actor.opacity = 255;
this.actor.show();
this._isVisible = true;
}
},
hideAll: function() {
if (this._isVisible) {
this.actor.opacity = 0;
this.actor.hide();
this._isVisible = false;
}
},
move: function(x, y) {
this.actor.set_position(x, y);
}
});
Signals.addSignalMethods(CandidatePanel.prototype);

View File

@ -1,20 +1,19 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter;
const GdkPixbuf = imports.gi.GdkPixbuf;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const GnomeDesktop = imports.gi.GnomeDesktop;
const Lang = imports.lang; const Lang = imports.lang;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const St = imports.gi.St; const St = imports.gi.St;
try { try {
var IBus = imports.gi.IBus; var IBus = imports.gi.IBus;
if (!('new_async' in IBus.Bus)) const CandidatePanel = imports.ui.status.candidatePanel;
throw "IBus version is too old";
const IBusCandidatePopup = imports.ui.ibusCandidatePopup;
} catch (e) { } catch (e) {
var IBus = null; var IBus = null;
log(e);
} }
const Main = imports.ui.main; const Main = imports.ui.main;
@ -22,119 +21,28 @@ const PopupMenu = imports.ui.popupMenu;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const Util = imports.misc.util; const Util = imports.misc.util;
const DESKTOP_INPUT_SOURCES_KEYBINDINGS_SCHEMA = 'org.gnome.desktop.input-sources.keybindings';
const DESKTOP_INPUT_SOURCES_SCHEMA = 'org.gnome.desktop.input-sources'; const DESKTOP_INPUT_SOURCES_SCHEMA = 'org.gnome.desktop.input-sources';
const KEY_CURRENT_INPUT_SOURCE = 'current'; const KEY_CURRENT_IS = 'current';
const KEY_INPUT_SOURCES = 'sources'; const KEY_INPUT_SOURCES = 'sources';
const INPUT_SOURCE_TYPE_XKB = 'xkb';
const INPUT_SOURCE_TYPE_IBUS = 'ibus';
const IBusManager = new Lang.Class({
Name: 'IBusManager',
_init: function(readyCallback) {
if (!IBus)
return;
IBus.init();
this._readyCallback = readyCallback;
this._candidatePopup = new IBusCandidatePopup.CandidatePopup();
this._ibus = null;
this._panelService = null;
this._engines = {};
this._ready = false;
this._nameWatcherId = Gio.DBus.session.watch_name(IBus.SERVICE_IBUS,
Gio.BusNameWatcherFlags.NONE,
Lang.bind(this, this._onNameAppeared),
Lang.bind(this, this._clear));
},
_clear: function() {
if (this._panelService)
this._panelService.destroy();
if (this._ibus)
this._ibus.destroy();
this._ibus = null;
this._panelService = null;
this._candidatePopup.setPanelService(null);
this._engines = {};
this._ready = false;
},
_onNameAppeared: function() {
this._ibus = IBus.Bus.new_async();
this._ibus.connect('connected', Lang.bind(this, this._onConnected));
},
_onConnected: function() {
this._ibus.list_engines_async(-1, null, Lang.bind(this, this._initEngines));
this._ibus.request_name_async(IBus.SERVICE_PANEL,
IBus.BusNameFlag.REPLACE_EXISTING,
-1, null,
Lang.bind(this, this._initPanelService));
this._ibus.connect('disconnected', Lang.bind(this, this._clear));
},
_initEngines: function(ibus, result) {
let enginesList = this._ibus.list_engines_async_finish(result);
if (enginesList) {
for (let i = 0; i < enginesList.length; ++i) {
let name = enginesList[i].get_name();
this._engines[name] = enginesList[i];
}
} else {
this._clear();
return;
}
this._updateReadiness();
},
_initPanelService: function(ibus, result) {
let success = this._ibus.request_name_async_finish(result);
if (success) {
this._panelService = new IBus.PanelService({ connection: this._ibus.get_connection(),
object_path: IBus.PATH_PANEL });
this._candidatePopup.setPanelService(this._panelService);
} else {
this._clear();
return;
}
this._updateReadiness();
},
_updateReadiness: function() {
this._ready = (Object.keys(this._engines).length > 0 &&
this._panelService != null);
if (this._ready && this._readyCallback)
this._readyCallback();
},
getEngineDesc: function(id) {
if (!IBus || !this._ready)
return null;
return this._engines[id];
}
});
const LayoutMenuItem = new Lang.Class({ const LayoutMenuItem = new Lang.Class({
Name: 'LayoutMenuItem', Name: 'LayoutMenuItem',
Extends: PopupMenu.PopupBaseMenuItem, Extends: PopupMenu.PopupBaseMenuItem,
_init: function(displayName, shortName) { _init: function(name, shortName, xkbLayout, xkbVariant, ibusEngine) {
this.parent(); this.parent();
this.label = new St.Label({ text: displayName }); this.label = new St.Label({ text: name });
this.indicator = new St.Label({ text: shortName }); this.indicator = new St.Label({ text: shortName });
this.addActor(this.label); this.addActor(this.label);
this.addActor(this.indicator); this.addActor(this.indicator);
this.sourceName = name;
this.shortName = shortName;
this.xkbLayout = xkbLayout;
this.xkbVariant = xkbVariant;
this.ibusEngine = ibusEngine;
} }
}); });
@ -152,171 +60,312 @@ const InputSourceIndicator = new Lang.Class({
this.actor.add_actor(this._container); this.actor.add_actor(this._container);
this.actor.add_style_class_name('panel-status-button'); this.actor.add_style_class_name('panel-status-button');
this._labelActors = {}; this._labelActors = [ ];
this._layoutItems = {}; this._layoutItems = [ ];
this._settings = new Gio.Settings({ schema: DESKTOP_INPUT_SOURCES_SCHEMA }); this._settings = new Gio.Settings({ schema: DESKTOP_INPUT_SOURCES_SCHEMA });
this._settings.connect('changed::' + KEY_CURRENT_INPUT_SOURCE, Lang.bind(this, this._currentInputSourceChanged)); this._settings.connect('changed::' + KEY_CURRENT_IS, Lang.bind(this, this._currentISChanged));
this._settings.connect('changed::' + KEY_INPUT_SOURCES, Lang.bind(this, this._inputSourcesChanged)); this._settings.connect('changed::' + KEY_INPUT_SOURCES, Lang.bind(this, this._inputSourcesChanged));
this._currentSourceIndex = this._settings.get_uint(KEY_CURRENT_INPUT_SOURCE); if (IBus)
this._xkbInfo = new GnomeDesktop.XkbInfo(); this._ibusInit();
this._ibusManager = new IBusManager(Lang.bind(this, this._inputSourcesChanged));
this._inputSourcesChanged(); this._inputSourcesChanged();
// re-using "allowSettings" for the keyboard layout is a bit shady, if (global.session_type == Shell.SessionType.USER) {
// but at least for now it is used as "allow popping up windows
// from shell menus"; we can always add a separate sessionMode
// option if need arises.
if (Main.sessionMode.allowSettings) {
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this._showLayoutItem = this.menu.addAction(_("Show Keyboard Layout"), Lang.bind(this, this._showLayout)); this.menu.addAction(_("Show Keyboard Layout"), Lang.bind(this, function() {
Main.overview.hide();
let description = this._selectedLayout.xkbLayout;
if (this._selectedLayout.xkbVariant.length > 0)
description = description + '\t' + this._selectedLayout.xkbVariant;
Util.spawn(['gkbd-keyboard-display', '-l', description]);
}));
} }
this.menu.addSettingsAction(_("Region and Language Settings"), 'gnome-region-panel.desktop'); this.menu.addSettingsAction(_("Region and Language Settings"), 'gnome-region-panel.desktop');
global.display.add_keybinding('switch-next',
new Gio.Settings({ schema: DESKTOP_INPUT_SOURCES_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.NONE,
Lang.bind(this, this._switchNext));
global.display.add_keybinding('switch-previous',
new Gio.Settings({ schema: DESKTOP_INPUT_SOURCES_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.NONE,
Lang.bind(this, this._switchPrevious));
}, },
setLockedState: function(locked) { _ibusInit: function() {
if (Main.sessionMode.allowSettings) { IBus.init();
this._showLayoutItem.actor.visible = !locked; this._ibus = new IBus.Bus();
if (!this._ibus.is_connected()) {
log('ibus-daemon is not running');
return;
} }
this.menu.setSettingsVisibility(!locked);
this._ibus.request_name(IBus.SERVICE_PANEL,
IBus.BusNameFlag.ALLOW_REPLACEMENT |
IBus.BusNameFlag.REPLACE_EXISTING);
this._panel = new IBus.PanelService({ connection: this._ibus.get_connection(),
object_path: IBus.PATH_PANEL });
this._ibusInitPanelService();
this._candidatePanel = new CandidatePanel.CandidatePanel();
this._ibusInitCandidatePanel();
}, },
_currentInputSourceChanged: function() { _ibusInitCandidatePanel: function() {
let nVisibleSources = Object.keys(this._layoutItems).length; this._candidatePanel.connect('cursor-up',
if (nVisibleSources < 2) Lang.bind(this, function(widget) {
return; this.cursorUp();
}));
this._candidatePanel.connect('cursor-down',
Lang.bind(this, function(widget) {
this.cursorDown();
}));
this._candidatePanel.connect('page-up',
Lang.bind(this, function(widget) {
this.pageUp();
}));
this._candidatePanel.connect('page-down',
Lang.bind(this, function(widget) {
this.pageDown();
}));
this._candidatePanel.connect('candidate-clicked',
Lang.bind(this, function(widget, index, button, state) {
this.candidateClicked(index, button, state);
}));
},
let nSources = this._settings.get_value(KEY_INPUT_SOURCES).n_children(); _ibusInitPanelService: function() {
let newCurrentSourceIndex = this._settings.get_uint(KEY_CURRENT_INPUT_SOURCE); this._panel.connect('set-cursor-location',
if (newCurrentSourceIndex >= nSources) Lang.bind(this, this.setCursorLocation));
return; this._panel.connect('update-preedit-text',
Lang.bind(this, this.updatePreeditText));
this._panel.connect('show-preedit-text',
Lang.bind(this, this.showPreeditText));
this._panel.connect('hide-preedit-text',
Lang.bind(this, this.hidePreeditText));
this._panel.connect('update-auxiliary-text',
Lang.bind(this, this.updateAuxiliaryText));
this._panel.connect('show-auxiliary-text',
Lang.bind(this, this.showAuxiliaryText));
this._panel.connect('hide-auxiliary-text',
Lang.bind(this, this.hideAuxiliaryText));
this._panel.connect('update-lookup-table',
Lang.bind(this, this.updateLookupTable));
this._panel.connect('show-lookup-table',
Lang.bind(this, this.showLookupTable));
this._panel.connect('hide-lookup-table',
Lang.bind(this, this.hideLookupTable));
this._panel.connect('page-up-lookup-table',
Lang.bind(this, this.pageUpLookupTable));
this._panel.connect('page-down-lookup-table',
Lang.bind(this, this.pageDownLookupTable));
this._panel.connect('cursor-up-lookup-table',
Lang.bind(this, this.cursorUpLookupTable));
this._panel.connect('cursor-down-lookup-table',
Lang.bind(this, this.cursorDownLookupTable));
this._panel.connect('focus-in', Lang.bind(this, this.focusIn));
this._panel.connect('focus-out', Lang.bind(this, this.focusOut));
},
if (!this._layoutItems[newCurrentSourceIndex]) { setCursorLocation: function(panel, x, y, w, h) {
// This source index is invalid as we weren't able to this._candidatePanel.setCursorLocation(x, y, w, h);
// build a menu item for it, so we hide ourselves since we },
// can't fix it here. *shrug*
this.menu.close(); updatePreeditText: function(panel, text, cursorPos, visible) {
this.actor.hide(); this._candidatePanel.updatePreeditText(text, cursorPos, visible);
return; },
} else {
this.actor.show(); showPreeditText: function(panel) {
this._candidatePanel.showPreeditText();
},
hidePreeditText: function(panel) {
this._candidatePanel.hidePreeditText();
},
updateAuxiliaryText: function(panel, text, visible) {
this._candidatePanel.updateAuxiliaryText(text, visible);
},
showAuxiliaryText: function(panel) {
this._candidatePanel.showAuxiliaryText();
},
hideAuxiliaryText: function(panel) {
this._candidatePanel.hideAuxiliaryText();
},
updateLookupTable: function(panel, lookupTable, visible) {
this._candidatePanel.updateLookupTable(lookupTable, visible);
},
showLookupTable: function(panel) {
this._candidatePanel.showLookupTable();
},
hideLookupTable: function(panel) {
this._candidatePanel.hideLookupTable();
},
pageUpLookupTable: function(panel) {
this._candidatePanel.pageUpLookupTable();
},
pageDownLookupTable: function(panel) {
this._candidatePanel.pageDownLookupTable();
},
cursorUpLookupTable: function(panel) {
this._candidatePanel.cursorUpLookupTable();
},
cursorDownLookupTable: function(panel) {
this._candidatePanel.cursorDownLookupTable();
},
focusIn: function(panel, path) {
},
focusOut: function(panel, path) {
this._candidatePanel.reset();
},
cursorUp: function() {
this._panel.cursor_up();
},
cursorDown: function() {
this._panel.cursor_down();
},
pageUp: function() {
this._panel.page_up();
},
pageDown: function() {
this._panel.page_down();
},
candidateClicked: function(index, button, state) {
this._panel.candidate_clicked(index, button, state);
},
_currentISChanged: function() {
let source = this._settings.get_value(KEY_CURRENT_IS);
let name = source.get_child_value(0).get_string()[0];
if (this._selectedLayout) {
this._selectedLayout.setShowDot(false);
this._selectedLayout = null;
} }
if (this._layoutItems[this._currentSourceIndex]) { if (this._selectedLabel) {
this._layoutItems[this._currentSourceIndex].setShowDot(false); this._container.set_skip_paint(this._selectedLabel, true);
this._container.set_skip_paint(this._labelActors[this._currentSourceIndex], true); this._selectedLabel = null;
} }
this._layoutItems[newCurrentSourceIndex].setShowDot(true); for (let i = 0; i < this._layoutItems.length; ++i) {
this._container.set_skip_paint(this._labelActors[newCurrentSourceIndex], false); let item = this._layoutItems[i];
if (item.sourceName == name) {
item.setShowDot(true);
this._selectedLayout = item;
break;
}
}
this._currentSourceIndex = newCurrentSourceIndex; for (let i = 0; i < this._labelActors.length; ++i) {
let actor = this._labelActors[i];
if (actor.sourceName == name) {
this._selectedLabel = actor;
this._container.set_skip_paint(actor, false);
break;
}
}
if (!this._selectedLayout || !this._selectedLabel)
this._layoutItems[0].activate();
}, },
_inputSourcesChanged: function() { _inputSourcesChanged: function() {
let sources = this._settings.get_value(KEY_INPUT_SOURCES); let sources = this._settings.get_value(KEY_INPUT_SOURCES);
let nSources = sources.n_children(); if (sources.n_children() > 1) {
for (let i in this._layoutItems)
this._layoutItems[i].destroy();
for (let i in this._labelActors)
this._labelActors[i].destroy();
this._layoutItems = {};
this._labelActors = {};
let infos = [];
let infosByShortName = {};
for (let i = 0; i < nSources; i++) {
let info = { exists: false };
let [type, id] = sources.get_child_value(i).deep_unpack();
if (type == INPUT_SOURCE_TYPE_XKB) {
[info.exists, info.displayName, info.shortName, , ] =
this._xkbInfo.get_layout_info(id);
} else if (type == INPUT_SOURCE_TYPE_IBUS) {
let engineDesc = this._ibusManager.getEngineDesc(id);
if (engineDesc) {
info.exists = true;
info.displayName = engineDesc.get_longname();
info.shortName = engineDesc.get_symbol();
}
}
if (!info.exists)
continue;
info.sourceIndex = i;
if (!(info.shortName in infosByShortName))
infosByShortName[info.shortName] = [];
infosByShortName[info.shortName].push(info);
infos.push(info);
}
if (infos.length > 1) {
this.actor.show(); this.actor.show();
} else { } else {
this.menu.close(); this.menu.close();
this.actor.hide(); this.actor.hide();
} }
for (let i = 0; i < infos.length; i++) { for (let i = 0; i < this._layoutItems.length; i++)
let info = infos[i]; this._layoutItems[i].destroy();
if (infosByShortName[info.shortName].length > 1) {
let sub = infosByShortName[info.shortName].indexOf(info) + 1;
info.shortName += String.fromCharCode(0x2080 + sub);
}
let item = new LayoutMenuItem(info.displayName, info.shortName); for (let i = 0; i < this._labelActors.length; i++)
this._layoutItems[info.sourceIndex] = item; this._labelActors[i].destroy();
this._selectedLayout = null;
this._layoutItems = [ ];
this._selectedLabel = null;
this._labelActors = [ ];
for (let i = 0; i < sources.n_children(); ++i) {
let name = sources.get_child_value(i).get_child_value(0).get_string()[0];
let shortName = sources.get_child_value(i).get_child_value(1).get_string()[0];
let xkbLayout = sources.get_child_value(i).get_child_value(2).get_string()[0];
let xkbVariant = sources.get_child_value(i).get_child_value(3).get_string()[0];
let ibusEngine = sources.get_child_value(i).get_child_value(4).get_string()[0];
let item = new LayoutMenuItem(name, shortName, xkbLayout, xkbVariant, ibusEngine);
this._layoutItems.push(item);
this.menu.addMenuItem(item, i); this.menu.addMenuItem(item, i);
item.connect('activate', Lang.bind(this, function() { item.connect('activate', Lang.bind(this, function() {
this._settings.set_value(KEY_CURRENT_INPUT_SOURCE, if (this._selectedLayout == null || item.sourceName != this._selectedLayout.sourceName) {
GLib.Variant.new_uint32(info.sourceIndex)); let name = GLib.Variant.new_string(item.sourceName);
let shortName = GLib.Variant.new_string(item.shortName);
let xkbLayout = GLib.Variant.new_string(item.xkbLayout);
let xkbVariant = GLib.Variant.new_string(item.xkbVariant);
let ibusEngine = GLib.Variant.new_string(item.ibusEngine);
let tuple = GLib.Variant.new_tuple([name, shortName, xkbLayout, xkbVariant, ibusEngine], 5);
this._settings.set_value(KEY_CURRENT_IS, tuple);
}
})); }));
let shortLabel = new St.Label({ text: info.shortName }); let shortLabel = new St.Label({ text: shortName });
this._labelActors[info.sourceIndex] = shortLabel; shortLabel.sourceName = name;
this._labelActors.push(shortLabel);
this._container.add_actor(shortLabel); this._container.add_actor(shortLabel);
this._container.set_skip_paint(shortLabel, true); this._container.set_skip_paint(shortLabel, true);
} }
this._currentInputSourceChanged(); this._currentISChanged();
}, },
_showLayout: function() { _switchNext: function() {
Main.overview.hide(); if (!this._selectedLayout || !this._selectedLabel) {
this._layoutItems[0].activate();
let sources = this._settings.get_value(KEY_INPUT_SOURCES); return;
let current = this._settings.get_uint(KEY_CURRENT_INPUT_SOURCE); }
let [type, id] = sources.get_child_value(current).deep_unpack(); for (let i = 0; i < this._layoutItems.length; ++i) {
let xkbLayout = ''; let item = this._layoutItems[i];
let xkbVariant = ''; if (item.sourceName == this._selectedLayout.sourceName) {
this._layoutItems[(++i == this._layoutItems.length) ? 0 : i].activate();
if (type == INPUT_SOURCE_TYPE_XKB) { break;
[, , , xkbLayout, xkbVariant] = this._xkbInfo.get_layout_info(id);
} else if (type == INPUT_SOURCE_TYPE_IBUS) {
let engineDesc = this._ibusManager.getEngineDesc(id);
if (engineDesc) {
xkbLayout = engineDesc.get_layout();
xkbVariant = '';
} }
} }
},
if (!xkbLayout || xkbLayout.length == 0) _switchPrevious: function() {
if (!this._selectedLayout || !this._selectedLabel) {
this._layoutItems[0].activate();
return; return;
}
let description = xkbLayout; for (let i = 0; i < this._layoutItems.length; ++i) {
if (xkbVariant.length > 0) let item = this._layoutItems[i];
description = description + '\t' + xkbVariant; if (item.sourceName == this._selectedLayout.sourceName) {
this._layoutItems[(--i == -1) ? (this._layoutItems.length - 1) : i].activate();
Util.spawn(['gkbd-keyboard-display', '-l', description]); break;
}
}
}, },
_containerGetPreferredWidth: function(container, for_height, alloc) { _containerGetPreferredWidth: function(container, for_height, alloc) {
@ -325,7 +374,7 @@ const InputSourceIndicator = new Lang.Class({
// for those we don't actually display. // for those we don't actually display.
let max_min_width = 0, max_natural_width = 0; let max_min_width = 0, max_natural_width = 0;
for (let i in this._labelActors) { for (let i = 0; i < this._labelActors.length; i++) {
let [min_width, natural_width] = this._labelActors[i].get_preferred_width(for_height); let [min_width, natural_width] = this._labelActors[i].get_preferred_width(for_height);
max_min_width = Math.max(max_min_width, min_width); max_min_width = Math.max(max_min_width, min_width);
max_natural_width = Math.max(max_natural_width, natural_width); max_natural_width = Math.max(max_natural_width, natural_width);
@ -338,7 +387,7 @@ const InputSourceIndicator = new Lang.Class({
_containerGetPreferredHeight: function(container, for_width, alloc) { _containerGetPreferredHeight: function(container, for_width, alloc) {
let max_min_height = 0, max_natural_height = 0; let max_min_height = 0, max_natural_height = 0;
for (let i in this._labelActors) { for (let i = 0; i < this._labelActors.length; i++) {
let [min_height, natural_height] = this._labelActors[i].get_preferred_height(for_width); let [min_height, natural_height] = this._labelActors[i].get_preferred_height(for_width);
max_min_height = Math.max(max_min_height, min_height); max_min_height = Math.max(max_min_height, min_height);
max_natural_height = Math.max(max_natural_height, natural_height); max_natural_height = Math.max(max_natural_height, natural_height);
@ -355,7 +404,7 @@ const InputSourceIndicator = new Lang.Class({
box.y2 -= box.y1; box.y2 -= box.y1;
box.y1 = 0; box.y1 = 0;
for (let i in this._labelActors) for (let i = 0; i < this._labelActors.length; i++)
this._labelActors[i].allocate_align_fill(box, 0.5, 0, false, false, flags); this._labelActors[i].allocate_align_fill(box, 0.5, 0, false, false, flags);
} }
}); });

View File

@ -1,62 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter;
const GObject = imports.gi.GObject;
const Lang = imports.lang;
const St = imports.gi.St;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const VolumeMenu = imports.ui.status.volume;
const Indicator = new Lang.Class({
Name: 'LockScreenMenuIndicator',
Extends: PanelMenu.SystemStatusButton,
_init: function() {
this.parent(null, _("Volume, network, battery"));
this.actor.hide();
this._volume = Main.panel.statusArea.volume;
if (this._volume) {
this._volumeIcon = this.addIcon(null);
this._volume.mainIcon.bind_property('gicon', this._volumeIcon, 'gicon',
GObject.BindingFlags.SYNC_CREATE);
this._volume.mainIcon.bind_property('visible', this._volumeIcon, 'visible',
GObject.BindingFlags.SYNC_CREATE);
this._volumeControl = VolumeMenu.getMixerControl();
this._volumeMenu = new VolumeMenu.VolumeMenu(this._volumeControl);
this.menu.addMenuItem(this._volumeMenu);
}
this._network = Main.panel.statusArea.network;
if (this._network) {
this._networkIcon = this.addIcon(null);
this._network.mainIcon.bind_property('gicon', this._networkIcon, 'gicon',
GObject.BindingFlags.SYNC_CREATE);
this._network.mainIcon.bind_property('visible', this._networkIcon, 'visible',
GObject.BindingFlags.SYNC_CREATE);
this._networkSecondaryIcon = this.addIcon(null);
this._network.secondaryIcon.bind_property('gicon', this._networkSecondaryIcon, 'gicon',
GObject.BindingFlags.SYNC_CREATE);
this._network.secondaryIcon.bind_property('visible', this._networkSecondaryIcon, 'visible',
GObject.BindingFlags.SYNC_CREATE);
}
this._battery = Main.panel.statusArea.battery;
if (this._battery) {
this._batteryIcon = this.addIcon(null);
this._battery.mainIcon.bind_property('gicon', this._batteryIcon, 'gicon',
GObject.BindingFlags.SYNC_CREATE);
this._battery.mainIcon.bind_property('visible', this._batteryIcon, 'visible',
GObject.BindingFlags.SYNC_CREATE);
}
},
setLockedState: function(locked) {
this.actor.visible = locked;
}
});

File diff suppressed because it is too large Load Diff

View File

@ -2,10 +2,14 @@
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Shell = imports.gi.Shell;
const St = imports.gi.St; const St = imports.gi.St;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const Util = imports.misc.util;
const BUS_NAME = 'org.gnome.SettingsDaemon'; const BUS_NAME = 'org.gnome.SettingsDaemon';
const OBJECT_PATH = '/org/gnome/SettingsDaemon/Power'; const OBJECT_PATH = '/org/gnome/SettingsDaemon/Power';
@ -52,11 +56,10 @@ const Indicator = new Lang.Class({
Extends: PanelMenu.SystemStatusButton, Extends: PanelMenu.SystemStatusButton,
_init: function() { _init: function() {
this.parent('battery-missing-symbolic', _("Battery")); this.parent('battery-missing', _("Battery"));
this._proxy = new PowerManagerProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH); this._proxy = new PowerManagerProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH);
this._isLocked = false;
this._deviceItems = [ ]; this._deviceItems = [ ];
this._hasPrimary = false; this._hasPrimary = false;
this._primaryDeviceId = null; this._primaryDeviceId = null;
@ -77,11 +80,6 @@ const Indicator = new Lang.Class({
this._devicesChanged(); this._devicesChanged();
}, },
setLockedState: function(locked) {
this._isLocked = locked;
this._syncIcon();
},
_readPrimaryDevice: function() { _readPrimaryDevice: function() {
this._proxy.GetPrimaryDeviceRemote(Lang.bind(this, function(result, error) { this._proxy.GetPrimaryDeviceRemote(Lang.bind(this, function(result, error) {
if (error) { if (error) {
@ -150,21 +148,16 @@ const Indicator = new Lang.Class({
})); }));
}, },
_syncIcon: function() { _devicesChanged: function() {
let icon = this._proxy.Icon; let icon = this._proxy.Icon;
let hasIcon = false;
if (icon) { if (icon) {
let gicon = Gio.icon_new_for_string(icon); let gicon = Gio.icon_new_for_string(icon);
this.setGIcon(gicon); this.setGIcon(gicon);
hasIcon = true; this.actor.show();
} else {
this.menu.close();
this.actor.hide();
} }
this.mainIcon.visible = hasIcon;
this.actor.visible = hasIcon && !this._isLocked;
},
_devicesChanged: function() {
this._syncIcon();
this._readPrimaryDevice(); this._readPrimaryDevice();
this._readOtherDevices(); this._readOtherDevices();
} }
@ -183,6 +176,7 @@ const DeviceItem = new Lang.Class({
this._label = new St.Label({ text: this._deviceTypeToString(device_type) }); this._label = new St.Label({ text: this._deviceTypeToString(device_type) });
this._icon = new St.Icon({ gicon: Gio.icon_new_for_string(icon), this._icon = new St.Icon({ gicon: Gio.icon_new_for_string(icon),
icon_type: St.IconType.SYMBOLIC,
style_class: 'popup-menu-icon' }); style_class: 'popup-menu-icon' });
this._box.add_actor(this._icon); this._box.add_actor(this._icon);
@ -191,8 +185,6 @@ const DeviceItem = new Lang.Class({
let percentLabel = new St.Label({ text: C_("percent of battery remaining", "%d%%").format(Math.round(percentage)) }); let percentLabel = new St.Label({ text: C_("percent of battery remaining", "%d%%").format(Math.round(percentage)) });
this.addActor(percentLabel, { align: St.Align.END }); this.addActor(percentLabel, { align: St.Align.END });
//FIXME: ideally we would like to expose this._label and percentLabel
this.actor.label_actor = percentLabel;
}, },
_deviceTypeToString: function(type) { _deviceTypeToString: function(type) {
@ -220,7 +212,7 @@ const DeviceItem = new Lang.Class({
case UPDeviceType.COMPUTER: case UPDeviceType.COMPUTER:
return _("Computer"); return _("Computer");
default: default:
return C_("device", "Unknown"); return _("Unknown");
} }
} }
}); });

View File

@ -2,37 +2,29 @@
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Shell = imports.gi.Shell;
const Gvc = imports.gi.Gvc; const Gvc = imports.gi.Gvc;
const Signals = imports.signals;
const St = imports.gi.St; const St = imports.gi.St;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const Util = imports.misc.util;
const VOLUME_ADJUSTMENT_STEP = 0.05; /* Volume adjustment step in % */ const VOLUME_ADJUSTMENT_STEP = 0.05; /* Volume adjustment step in % */
const VOLUME_NOTIFY_ID = 1; const VOLUME_NOTIFY_ID = 1;
// Each Gvc.MixerControl is a connection to PulseAudio, const Indicator = new Lang.Class({
// so it's better to make it a singleton Name: 'VolumeIndicator',
let _mixerControl; Extends: PanelMenu.SystemStatusButton,
function getMixerControl() {
if (_mixerControl)
return _mixerControl;
_mixerControl = new Gvc.MixerControl({ name: 'GNOME Shell Volume Control' }); _init: function() {
_mixerControl.open(); this.parent('audio-volume-muted', _("Volume"));
return _mixerControl; this._control = new Gvc.MixerControl({ name: 'GNOME Shell Volume Control' });
}
const VolumeMenu = new Lang.Class({
Name: 'VolumeMenu',
Extends: PopupMenu.PopupMenuSection,
_init: function(control) {
this.parent();
this._control = control;
this._control.connect('state-changed', Lang.bind(this, this._onControlStateChanged)); this._control.connect('state-changed', Lang.bind(this, this._onControlStateChanged));
this._control.connect('default-sink-changed', Lang.bind(this, this._readOutput)); this._control.connect('default-sink-changed', Lang.bind(this, this._readOutput));
this._control.connect('default-source-changed', Lang.bind(this, this._readInput)); this._control.connect('default-source-changed', Lang.bind(this, this._readInput));
@ -48,10 +40,10 @@ const VolumeMenu = new Lang.Class({
this._outputSlider = new PopupMenu.PopupSliderMenuItem(0); this._outputSlider = new PopupMenu.PopupSliderMenuItem(0);
this._outputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_output')); this._outputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_output'));
this._outputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange)); this._outputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange));
this.addMenuItem(this._outputTitle); this.menu.addMenuItem(this._outputTitle);
this.addMenuItem(this._outputSlider); this.menu.addMenuItem(this._outputSlider);
this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this._input = null; this._input = null;
this._inputVolumeId = 0; this._inputVolumeId = 0;
@ -60,11 +52,18 @@ const VolumeMenu = new Lang.Class({
this._inputSlider = new PopupMenu.PopupSliderMenuItem(0); this._inputSlider = new PopupMenu.PopupSliderMenuItem(0);
this._inputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_input')); this._inputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_input'));
this._inputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange)); this._inputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange));
this.addMenuItem(this._inputTitle); this.menu.addMenuItem(this._inputTitle);
this.addMenuItem(this._inputSlider); this.menu.addMenuItem(this._inputSlider);
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this.menu.addSettingsAction(_("Sound Settings"), 'gnome-sound-panel.desktop');
this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
this._control.open();
}, },
scroll: function(direction) { _onScrollEvent: function(actor, event) {
let direction = event.get_scroll_direction();
let currentVolume = this._output.volume; let currentVolume = this._output.volume;
if (direction == Clutter.ScrollDirection.DOWN) { if (direction == Clutter.ScrollDirection.DOWN) {
@ -90,8 +89,9 @@ const VolumeMenu = new Lang.Class({
if (this._control.get_state() == Gvc.MixerControlState.READY) { if (this._control.get_state() == Gvc.MixerControlState.READY) {
this._readOutput(); this._readOutput();
this._readInput(); this._readInput();
this.actor.show();
} else { } else {
this.emit('icon-changed', null); this.actor.hide();
} }
}, },
@ -110,7 +110,7 @@ const VolumeMenu = new Lang.Class({
this._volumeChanged (null, null, '_output'); this._volumeChanged (null, null, '_output');
} else { } else {
this._outputSlider.setValue(0); this._outputSlider.setValue(0);
this.emit('icon-changed', 'audio-volume-muted-symbolic'); this.setIcon('audio-volume-muted-symbolic');
} }
}, },
@ -156,14 +156,14 @@ const VolumeMenu = new Lang.Class({
_volumeToIcon: function(volume) { _volumeToIcon: function(volume) {
if (volume <= 0) { if (volume <= 0) {
return 'audio-volume-muted-symbolic'; return 'audio-volume-muted';
} else { } else {
let n = Math.floor(3 * volume / this._volumeMax) + 1; let n = Math.floor(3 * volume / this._volumeMax) + 1;
if (n < 2) if (n < 2)
return 'audio-volume-low-symbolic'; return 'audio-volume-low';
if (n >= 3) if (n >= 3)
return 'audio-volume-high-symbolic'; return 'audio-volume-high';
return 'audio-volume-medium-symbolic'; return 'audio-volume-medium';
} }
}, },
@ -197,55 +197,15 @@ const VolumeMenu = new Lang.Class({
slider.setValue(muted ? 0 : (this[property].volume / this._volumeMax)); slider.setValue(muted ? 0 : (this[property].volume / this._volumeMax));
if (property == '_output') { if (property == '_output') {
if (muted) if (muted)
this.emit('icon-changed', 'audio-volume-muted-symbolic'); this.setIcon('audio-volume-muted');
else else
this.emit('icon-changed', this._volumeToIcon(this._output.volume)); this.setIcon(this._volumeToIcon(this._output.volume));
} }
}, },
_volumeChanged: function(object, param_spec, property) { _volumeChanged: function(object, param_spec, property) {
this[property+'Slider'].setValue(this[property].volume / this._volumeMax); this[property+'Slider'].setValue(this[property].volume / this._volumeMax);
if (property == '_output' && !this._output.is_muted) if (property == '_output' && !this._output.is_muted)
this.emit('icon-changed', this._volumeToIcon(this._output.volume)); this.setIcon(this._volumeToIcon(this._output.volume));
}
});
const Indicator = new Lang.Class({
Name: 'VolumeIndicator',
Extends: PanelMenu.SystemStatusButton,
_init: function() {
this.parent('audio-volume-muted-symbolic', _("Volume"));
this._isLocked = false;
this._control = getMixerControl();
this._volumeMenu = new VolumeMenu(this._control);
this._volumeMenu.connect('icon-changed', Lang.bind(this, function(menu, icon) {
this._hasPulseAudio = (icon != null);
this.setIcon(icon);
this._syncVisibility();
}));
this.menu.addMenuItem(this._volumeMenu);
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this.menu.addSettingsAction(_("Sound Settings"), 'gnome-sound-panel.desktop');
this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
},
setLockedState: function(locked) {
this._isLocked = locked;
this._syncVisibility();
},
_syncVisibility: function() {
this.actor.visible = this._hasPulseAudio && !this._isLocked;
this.mainIcon.visible = this._hasPulseAudio;
},
_onScrollEvent: function(actor, event) {
this._volumeMenu.scroll(event.get_scroll_direction());
} }
}); });

View File

@ -0,0 +1,63 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Lang = imports.lang;
const Shell = imports.gi.Shell;
const Signals = imports.signals;
const MessageTray = imports.ui.messageTray;
const NotificationDaemon = imports.ui.notificationDaemon;
const Util = imports.misc.util;
const STANDARD_TRAY_ICON_IMPLEMENTATIONS = {
'bluetooth-applet': 'bluetooth',
'gnome-volume-control-applet': 'volume', // renamed to gnome-sound-applet
// when moved to control center
'gnome-sound-applet': 'volume',
'nm-applet': 'network',
'gnome-power-manager': 'battery',
'keyboard': 'keyboard',
'a11y-keyboard': 'a11y',
'kbd-scrolllock': 'keyboard',
'kbd-numlock': 'keyboard',
'kbd-capslock': 'keyboard',
'ibus-ui-gtk': 'input-method'
};
const StatusIconDispatcher = new Lang.Class({
Name: 'StatusIconDispatcher',
_init: function() {
this._traymanager = new Shell.TrayManager();
this._traymanager.connect('tray-icon-added', Lang.bind(this, this._onTrayIconAdded));
this._traymanager.connect('tray-icon-removed', Lang.bind(this, this._onTrayIconRemoved));
// Yet-another-Ubuntu-workaround - we have to kill their
// app-indicators, so that applications fall back to normal
// status icons
// http://bugzilla.gnome.org/show_bug.cgi=id=621382
Util.killall('indicator-application-service');
},
start: function(themeWidget) {
this._traymanager.manage_stage(global.stage, themeWidget);
},
_onTrayIconAdded: function(o, icon) {
let wmClass = (icon.wm_class || 'unknown').toLowerCase();
let role = STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass];
if (role)
this.emit('status-icon-added', icon, role);
else
this.emit('message-icon-added', icon);
},
_onTrayIconRemoved: function(o, icon) {
let wmClass = (icon.wm_class || 'unknown').toLowerCase();
let role = STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass];
if (role)
this.emit('status-icon-removed', icon);
else
this.emit('message-icon-removed', icon);
}
});
Signals.addSignalMethods(StatusIconDispatcher.prototype);

View File

@ -132,9 +132,6 @@ const Client = new Lang.Class({
let channel = channels[i]; let channel = channels[i];
let [targetHandle, targetHandleType] = channel.get_handle(); let [targetHandle, targetHandleType] = channel.get_handle();
if (channel.get_invalidated())
continue;
/* Only observe contact text channels */ /* Only observe contact text channels */
if ((!(channel instanceof Tp.TextChannel)) || if ((!(channel instanceof Tp.TextChannel)) ||
targetHandleType != Tp.HandleType.CONTACT) targetHandleType != Tp.HandleType.CONTACT)
@ -184,9 +181,6 @@ const Client = new Lang.Class({
continue; continue;
} }
if (channel.get_invalidated())
continue;
// 'notify' will be true when coming from an actual HandleChannels // 'notify' will be true when coming from an actual HandleChannels
// call, and not when from a successful Claim call. The point is // 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 // we don't want to notify for a channel we just claimed which
@ -211,15 +205,13 @@ const Client = new Lang.Class({
// We can only approve the rooms if we have been invited to it // We can only approve the rooms if we have been invited to it
let selfContact = channel.group_get_self_contact(); let selfContact = channel.group_get_self_contact();
if (selfContact == null) { if (selfContact == null) {
context.fail(new Tp.Error({ code: Tp.Error.INVALID_ARGUMENT, Shell.decline_dispatch_op(context, 'Not invited to the room');
message: 'Not invited to the room' }));
return; return;
} }
let [invited, inviter, reason, msg] = channel.group_get_local_pending_contact_info(selfContact); let [invited, inviter, reason, msg] = channel.group_get_local_pending_contact_info(selfContact);
if (!invited) { if (!invited) {
context.fail(new Tp.Error({ code: Tp.Error.INVALID_ARGUMENT, Shell.decline_dispatch_op(context, 'Not invited to the room');
message: 'Not invited to the room' }));
return; return;
} }
@ -239,21 +231,12 @@ const Client = new Lang.Class({
let channel = channels[0]; let channel = channels[0];
let chanType = channel.get_channel_type(); let chanType = channel.get_channel_type();
if (channel.get_invalidated()) {
context.fail(new Tp.Error({ code: Tp.Error.INVALID_ARGUMENT,
message: 'Channel is invalidated' }));
return;
}
if (chanType == Tp.IFACE_CHANNEL_TYPE_TEXT) if (chanType == Tp.IFACE_CHANNEL_TYPE_TEXT)
this._approveTextChannel(account, conn, channel, dispatchOp, context); this._approveTextChannel(account, conn, channel, dispatchOp, context);
else if (chanType == Tp.IFACE_CHANNEL_TYPE_CALL) else if (chanType == Tp.IFACE_CHANNEL_TYPE_CALL)
this._approveCall(account, conn, channel, dispatchOp, context); this._approveCall(account, conn, channel, dispatchOp, context);
else if (chanType == Tp.IFACE_CHANNEL_TYPE_FILE_TRANSFER) else if (chanType == Tp.IFACE_CHANNEL_TYPE_FILE_TRANSFER)
this._approveFileTransfer(account, conn, channel, dispatchOp, context); this._approveFileTransfer(account, conn, channel, dispatchOp, context);
else
context.fail(new Tp.Error({ code: Tp.Error.INVALID_ARGUMENT,
message: 'Unsupported channel type' }));
}, },
_approveTextChannel: function(account, conn, channel, dispatchOp, context) { _approveTextChannel: function(account, conn, channel, dispatchOp, context) {
@ -382,8 +365,8 @@ const Client = new Lang.Class({
_ensureSubscriptionSource: function() { _ensureSubscriptionSource: function() {
if (this._subscriptionSource == null) { if (this._subscriptionSource == null) {
this._subscriptionSource = new MessageTray.Source(_("Subscription request"), this._subscriptionSource = new MultiNotificationSource(
'gtk-dialog-question'); _("Subscription request"), 'gtk-dialog-question');
Main.messageTray.add(this._subscriptionSource); Main.messageTray.add(this._subscriptionSource);
this._subscriptionSource.connect('destroy', Lang.bind(this, function () { this._subscriptionSource.connect('destroy', Lang.bind(this, function () {
this._subscriptionSource = null; this._subscriptionSource = null;
@ -418,8 +401,8 @@ const Client = new Lang.Class({
_ensureAccountSource: function() { _ensureAccountSource: function() {
if (this._accountSource == null) { if (this._accountSource == null) {
this._accountSource = new MessageTray.Source(_("Connection error"), this._accountSource = new MultiNotificationSource(
'gtk-dialog-error'); _("Connection error"), 'gtk-dialog-error');
Main.messageTray.add(this._accountSource); Main.messageTray.add(this._accountSource);
this._accountSource.connect('destroy', Lang.bind(this, function () { this._accountSource.connect('destroy', Lang.bind(this, function () {
this._accountSource = null; this._accountSource = null;
@ -435,13 +418,14 @@ const ChatSource = new Lang.Class({
Extends: MessageTray.Source, Extends: MessageTray.Source,
_init: function(account, conn, channel, contact, client) { _init: function(account, conn, channel, contact, client) {
this.parent(contact.get_alias());
this.isChat = true;
this._account = account; this._account = account;
this._contact = contact; this._contact = contact;
this._client = client; this._client = client;
this.parent(contact.get_alias());
this.isChat = true;
this._pendingMessages = []; this._pendingMessages = [];
this._conn = conn; this._conn = conn;
@ -462,6 +446,8 @@ const ChatSource = new Lang.Class({
this._receivedId = this._channel.connect('message-received', Lang.bind(this, this._messageReceived)); this._receivedId = this._channel.connect('message-received', Lang.bind(this, this._messageReceived));
this._pendingId = this._channel.connect('pending-message-removed', Lang.bind(this, this._pendingRemoved)); this._pendingId = this._channel.connect('pending-message-removed', Lang.bind(this, this._pendingRemoved));
this._setSummaryIcon(this.createNotificationIcon());
this._notifyAliasId = this._contact.connect('notify::alias', Lang.bind(this, this._updateAlias)); this._notifyAliasId = this._contact.connect('notify::alias', Lang.bind(this, this._updateAlias));
this._notifyAvatarId = this._contact.connect('notify::avatar-file', Lang.bind(this, this._updateAvatarIcon)); this._notifyAvatarId = this._contact.connect('notify::avatar-file', Lang.bind(this, this._updateAvatarIcon));
this._presenceChangedId = this._contact.connect('presence-changed', Lang.bind(this, this._presenceChanged)); this._presenceChangedId = this._contact.connect('presence-changed', Lang.bind(this, this._presenceChanged));
@ -484,9 +470,9 @@ const ChatSource = new Lang.Class({
this._notification.appendAliasChange(oldAlias, newAlias); this._notification.appendAliasChange(oldAlias, newAlias);
}, },
createIcon: function(size) { createNotificationIcon: function() {
this._iconBox = new St.Bin({ style_class: 'avatar-box' }); this._iconBox = new St.Bin({ style_class: 'avatar-box' });
this._iconBox._size = size; this._iconBox._size = this.ICON_SIZE;
let textureCache = St.TextureCache.get_default(); let textureCache = St.TextureCache.get_default();
let file = this._contact.get_avatar_file(); let file = this._contact.get_avatar_file();
@ -495,45 +481,16 @@ const ChatSource = new Lang.Class({
this._iconBox.child = textureCache.load_uri_async(uri, this._iconBox._size, this._iconBox._size); this._iconBox.child = textureCache.load_uri_async(uri, this._iconBox._size, this._iconBox._size);
} else { } else {
this._iconBox.child = new St.Icon({ icon_name: 'avatar-default', this._iconBox.child = new St.Icon({ icon_name: 'avatar-default',
icon_type: St.IconType.FULLCOLOR,
icon_size: this._iconBox._size }); icon_size: this._iconBox._size });
} }
return this._iconBox; return this._iconBox;
}, },
createSecondaryIcon: function() {
let iconBox = new St.Bin();
iconBox.child = new St.Icon({ style_class: 'secondary-icon' });
let presenceType = this._contact.get_presence_type();
switch (presenceType) {
case Tp.ConnectionPresenceType.AVAILABLE:
iconBox.child.icon_name = 'user-available';
break;
case Tp.ConnectionPresenceType.BUSY:
iconBox.child.icon_name = 'user-busy';
break;
case Tp.ConnectionPresenceType.OFFLINE:
iconBox.child.icon_name = 'user-offline';
break;
case Tp.ConnectionPresenceType.HIDDEN:
iconBox.child.icon_name = 'user-invisible';
break;
case Tp.ConnectionPresenceType.AWAY:
iconBox.child.icon_name = 'user-away';
break;
case Tp.ConnectionPresenceType.EXTENDED_AWAY:
iconBox.child.icon_name = 'user-idle';
break;
default:
iconBox.child.icon_name = 'user-offline';
}
return iconBox;
},
_updateAvatarIcon: function() { _updateAvatarIcon: function() {
this.iconUpdated(); this._setSummaryIcon(this.createNotificationIcon());
this._notification.update(this._notification.title, null, { customContent: true }); this._notification.update(this._notification.title, null, { customContent: true, icon: this.createNotificationIcon() });
}, },
open: function(notification) { open: function(notification) {
@ -553,10 +510,10 @@ const ChatSource = new Lang.Class({
_getLogMessages: function() { _getLogMessages: function() {
let logManager = Tpl.LogManager.dup_singleton(); let logManager = Tpl.LogManager.dup_singleton();
let entity = Tpl.Entity.new_from_tp_contact(this._contact, Tpl.EntityType.CONTACT); let entity = Tpl.Entity.new_from_tp_contact(this._contact, Tpl.EntityType.CONTACT);
Shell.get_contact_events(logManager,
logManager.get_filtered_events_async(this._account, entity, this._account, entity,
Tpl.EventTypeMask.TEXT, SCROLLBACK_HISTORY_LINES, SCROLLBACK_HISTORY_LINES,
null, Lang.bind(this, this._displayPendingMessages)); Lang.bind(this, this._displayPendingMessages));
}, },
_displayPendingMessages: function(logManager, result) { _displayPendingMessages: function(logManager, result) {
@ -578,7 +535,7 @@ const ChatSource = new Lang.Class({
this._pendingMessages.push(message); this._pendingMessages.push(message);
} }
this.countUpdated(); this._updateCount();
let showTimestamp = false; let showTimestamp = false;
@ -624,17 +581,8 @@ const ChatSource = new Lang.Class({
this.destroy(); this.destroy();
}, },
/* All messages are new messages for Telepathy sources */ _updateCount: function() {
get count() { this._setCount(this._pendingMessages.length, this._pendingMessages.length > 0);
return this._pendingMessages.length;
},
get unseenCount() {
return this.count;
},
get countVisible() {
return this.count > 0;
}, },
_messageReceived: function(channel, message) { _messageReceived: function(channel, message) {
@ -642,7 +590,7 @@ const ChatSource = new Lang.Class({
return; return;
this._pendingMessages.push(message); this._pendingMessages.push(message);
this.countUpdated(); this._updateCount();
message = makeMessageFromTpMessage(message, NotificationDirection.RECEIVED); message = makeMessageFromTpMessage(message, NotificationDirection.RECEIVED);
this._notification.appendMessage(message); this._notification.appendMessage(message);
@ -704,14 +652,38 @@ const ChatSource = new Lang.Class({
}, },
_presenceChanged: function (contact, presence, status, message) { _presenceChanged: function (contact, presence, status, message) {
let msg, title; let msg, shouldNotify, title;
if (this._presence == presence)
return;
title = GLib.markup_escape_text(this.title, -1); title = GLib.markup_escape_text(this.title, -1);
this._notification.update(this._notification.title, null, { customContent: true, secondaryIcon: this.createSecondaryIcon() }); 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.OFFLINE;
msg = _("%s is offline.").format(title);
shouldNotify = (this._presence != Tp.ConnectionPresenceType.OFFLINE);
} 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) {
msg = _("%s is busy.").format(title);
shouldNotify = false;
} else
return;
this._presence = presence;
if (message) if (message)
msg += ' <i>(' + GLib.markup_escape_text(message, -1) + ')</i>'; msg += ' <i>(' + GLib.markup_escape_text(message, -1) + ')</i>';
this._notification.appendPresence(msg, shouldNotify);
if (shouldNotify)
this.notify();
}, },
_pendingRemoved: function(channel, message) { _pendingRemoved: function(channel, message) {
@ -719,7 +691,7 @@ const ChatSource = new Lang.Class({
if (idx >= 0) { if (idx >= 0) {
this._pendingMessages.splice(idx, 1); this._pendingMessages.splice(idx, 1);
this.countUpdated(); this._updateCount();
} }
else else
throw new Error('Message not in our pending list: ' + message); throw new Error('Message not in our pending list: ' + message);
@ -738,7 +710,7 @@ const ChatNotification = new Lang.Class({
Extends: MessageTray.Notification, Extends: MessageTray.Notification,
_init: function(source) { _init: function(source) {
this.parent(source, source.title, null, { customContent: true, secondaryIcon: source.createSecondaryIcon() }); this.parent(source, source.title, null, { customContent: true });
this.setResident(true); this.setResident(true);
this._responseEntry = new St.Entry({ style_class: 'chat-response', this._responseEntry = new St.Entry({ style_class: 'chat-response',
@ -831,7 +803,7 @@ const ChatNotification = new Lang.Class({
let groups = this._contentArea.get_children(); let groups = this._contentArea.get_children();
for (let i = 0; i < groups.length; i++) { for (let i = 0; i < groups.length; i++) {
let group = groups[i]; let group = groups[i];
if (group.get_n_children() == 0) if (group.get_children().length == 0)
group.destroy(); group.destroy();
} }
}, },
@ -948,6 +920,19 @@ const ChatNotification = new Lang.Class({
return false; return false;
}, },
appendPresence: function(text, asTitle) {
if (asTitle)
this.update(text, null, { customContent: true, titleMarkup: true });
else
this.update(this.source.title, null, { customContent: true });
let label = this._append({ body: text,
group: 'meta',
styles: ['chat-meta-message'] });
this._filterMessages();
},
appendAliasChange: function(oldAlias, newAlias) { appendAliasChange: function(oldAlias, newAlias) {
oldAlias = GLib.markup_escape_text(oldAlias, -1); oldAlias = GLib.markup_escape_text(oldAlias, -1);
newAlias = GLib.markup_escape_text(newAlias, -1); newAlias = GLib.markup_escape_text(newAlias, -1);
@ -1017,10 +1002,11 @@ const ApproverSource = new Lang.Class({
Extends: MessageTray.Source, Extends: MessageTray.Source,
_init: function(dispatchOp, text, gicon) { _init: function(dispatchOp, text, gicon) {
this._gicon = gicon;
this.parent(text); this.parent(text);
this._gicon = gicon;
this._setSummaryIcon(this.createNotificationIcon());
this._dispatchOp = dispatchOp; this._dispatchOp = dispatchOp;
// Destroy the source if the channel dispatch operation is invalidated // Destroy the source if the channel dispatch operation is invalidated
@ -1040,9 +1026,10 @@ const ApproverSource = new Lang.Class({
this.parent(); this.parent();
}, },
createIcon: function(size) { createNotificationIcon: function() {
return new St.Icon({ gicon: this._gicon, return new St.Icon({ gicon: this._gicon,
icon_size: size }); icon_type: St.IconType.FULLCOLOR,
icon_size: this.ICON_SIZE });
} }
}); });
@ -1164,6 +1151,40 @@ const FileTransferNotification = new Lang.Class({
} }
}); });
// A notification source that can embed multiple notifications
const MultiNotificationSource = new Lang.Class({
Name: 'MultiNotificationSource',
Extends: MessageTray.Source,
_init: function(title, icon) {
this.parent(title);
this._icon = icon;
this._setSummaryIcon(this.createNotificationIcon());
this._nbNotifications = 0;
},
notify: function(notification) {
this.parent(notification);
this._nbNotifications += 1;
// Display the source while there is at least one notification
notification.connect('destroy', Lang.bind(this, function () {
this._nbNotifications -= 1;
if (this._nbNotifications == 0)
this.destroy();
}));
},
createNotificationIcon: function() {
return new St.Icon({ gicon: Gio.icon_new_for_string(this._icon),
icon_type: St.IconType.FULLCOLOR,
icon_size: this.ICON_SIZE });
}
});
// Subscription request // Subscription request
const SubscriptionRequestNotification = new Lang.Class({ const SubscriptionRequestNotification = new Lang.Class({
Name: 'SubscriptionRequestNotification', Name: 'SubscriptionRequestNotification',
@ -1193,6 +1214,7 @@ const SubscriptionRequestNotification = new Lang.Class({
} }
else { else {
iconBox.child = new St.Icon({ icon_name: 'avatar-default', iconBox.child = new St.Icon({ icon_name: 'avatar-default',
icon_type: St.IconType.FULLCOLOR,
icon_size: iconBox._size }); icon_size: iconBox._size });
} }
@ -1337,14 +1359,15 @@ const AccountNotification = new Lang.Class({
case 'reconnect': case 'reconnect':
// If it fails again, a new notification should pop up with the // If it fails again, a new notification should pop up with the
// new error. // new error.
account.reconnect_async(null); account.reconnect_async(null, null);
break; break;
case 'edit': case 'edit':
let cmd = '/usr/bin/empathy-accounts' let cmd = '/usr/bin/empathy-accounts'
+ ' --select-account=%s' + ' --select-account=%s'
.format(account.get_path_suffix()); .format(account.get_path_suffix());
let app_info = Gio.app_info_create_from_commandline(cmd, null, 0); let app_info = Gio.app_info_create_from_commandline(cmd, null, 0,
app_info.launch([], global.create_app_launch_context()); null);
app_info.launch([], null, null);
break; break;
} }
this.destroy(); this.destroy();

View File

@ -1,242 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const AccountsService = imports.gi.AccountsService;
const Clutter = imports.gi.Clutter;
const Gdm = imports.gi.Gdm;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const Signals = imports.signals;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog;
const ShellEntry = imports.ui.shellEntry;
const Tweener = imports.ui.tweener;
const UserMenu = imports.ui.userMenu;
const Batch = imports.gdm.batch;
const GdmUtil = imports.gdm.util;
// The timeout before going back automatically to the lock screen (in seconds)
const IDLE_TIMEOUT = 2 * 60;
// A widget showing the user avatar and name
const UserWidget = new Lang.Class({
Name: 'UserWidget',
_init: function(user) {
this._user = user;
this.actor = new St.BoxLayout({ style_class: 'unlock-dialog-user-name-container',
vertical: false });
this._avatar = new UserMenu.UserAvatarWidget(user);
this.actor.add(this._avatar.actor,
{ x_fill: true, y_fill: true });
this._label = new St.Label({ style_class: 'login-dialog-username' });
this.actor.add(this._label,
{ expand: true,
x_fill: true,
y_align: St.Align.MIDDLE });
this._userLoadedId = this._user.connect('notify::is-loaded',
Lang.bind(this, this._updateUser));
this._userChangedId = this._user.connect('changed',
Lang.bind(this, this._updateUser));
if (this._user.is_loaded)
this._updateUser();
},
destroy: function() {
if (this._userLoadedId != 0) {
this._user.disconnect(this._userLoadedId);
this._userLoadedId = 0;
}
if (this._userChangedId != 0) {
this._user.disconnect(this._userChangedId);
this._userChangedId = 0;
}
this.actor.destroy();
},
_updateUser: function() {
if (this._user.is_loaded)
this._label.text = this._user.get_real_name();
else
this._label.text = '';
this._avatar.update();
}
});
const UnlockDialog = new Lang.Class({
Name: 'UnlockDialog',
Extends: ModalDialog.ModalDialog,
_init: function(parentActor) {
this.parent({ shellReactive: true,
styleClass: 'login-dialog',
parentActor: parentActor
});
this._userManager = AccountsService.UserManager.get_default();
this._userName = GLib.get_user_name();
this._user = this._userManager.get_user(this._userName);
this._greeterClient = new Gdm.Client();
this._userVerifier = new GdmUtil.ShellUserVerifier(this._greeterClient, { reauthenticationOnly: true });
this._userVerifier.connect('reset', Lang.bind(this, this._reset));
this._userVerifier.connect('ask-question', Lang.bind(this, this._onAskQuestion));
this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete));
this._userVerifier.connect('verification-failed', Lang.bind(this, this._onVerificationFailed));
this._userVerifier.connect('show-login-hint', Lang.bind(this, this._showLoginHint));
this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._hideLoginHint));
this._userWidget = new UserWidget(this._user);
this.contentLayout.add_actor(this._userWidget.actor);
this._promptLayout = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout',
vertical: true });
this._promptLabel = new St.Label({ style_class: 'login-dialog-prompt-label' });
this._promptLayout.add(this._promptLabel,
{ x_align: St.Align.START });
this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry',
can_focus: true });
ShellEntry.addContextMenu(this._promptEntry);
this.setInitialKeyFocus(this._promptEntry);
this._promptEntry.clutter_text.connect('activate', Lang.bind(this, this._doUnlock));
this._promptLayout.add(this._promptEntry,
{ expand: true,
x_fill: true });
this.contentLayout.add_actor(this._promptLayout);
this._promptLoginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint' });
this._promptLoginHint.hide();
this.contentLayout.add_actor(this._promptLoginHint);
let cancelButton = { label: _("Cancel"),
action: Lang.bind(this, this._escape),
key: Clutter.KEY_Escape };
this._okButton = { label: _("Unlock"),
action: Lang.bind(this, this._doUnlock),
default: true };
this.setButtons([cancelButton, this._okButton]);
this._updateOkButton(false);
this._reset();
let otherUserLabel = new St.Label({ text: _("Log in as another user"),
style_class: 'login-dialog-not-listed-label' });
this._otherUserButton = new St.Button({ style_class: 'login-dialog-not-listed-button',
can_focus: true,
child: otherUserLabel,
reactive: true,
x_align: St.Align.START,
x_fill: true });
this._otherUserButton.connect('clicked', Lang.bind(this, this._otherUserClicked));
this.dialogLayout.add(this._otherUserButton,
{ x_align: St.Align.START,
x_fill: false });
GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, function() {
this.emit('loaded');
return false;
}));
this._idleMonitor = Shell.IdleMonitor.get();
// this dialog is only created after user activity (curtain drag or
// escape key press), so the timeout will fire after IDLE_TIMEOUT seconds of inactivity
this._idleWatchId = this._idleMonitor.add_watch(IDLE_TIMEOUT * 1000, Lang.bind(this, this._escape));
},
_updateOkButton: function(sensitive) {
this._okButton.button.reactive = sensitive;
},
_reset: function() {
this._userVerifier.clear();
this._userVerifier.begin(this._userName, new Batch.Hold());
},
_onAskQuestion: function(verifier, serviceName, question, passwordChar) {
this._promptLabel.text = question;
this._promptEntry.text = '';
this._promptEntry.clutter_text.set_password_char(passwordChar);
this._promptEntry.menu.isPassword = passwordChar != '';
this._currentQuery = serviceName;
this._updateOkButton(true);
},
_showLoginHint: function(verifier, message) {
this._promptLoginHint.set_text(message)
GdmUtil.fadeInActor(this._promptLoginHint);
},
_hideLoginHint: function() {
GdmUtil.fadeOutActor(this._promptLoginHint);
},
_doUnlock: function() {
if (!this._currentQuery)
return;
let query = this._currentQuery;
this._currentQuery = null;
this._updateOkButton(false);
this._userVerifier.answerQuery(query, this._promptEntry.text);
},
_onVerificationComplete: function() {
this._userVerifier.clear();
this.emit('unlocked');
},
_onVerificationFailed: function() {
this._userVerifier.cancel();
this.emit('failed');
},
_escape: function() {
this._onVerificationFailed();
},
_otherUserClicked: function(button, event) {
Gdm.goto_login_session_sync(null);
this._userVerifier.cancel();
this.emit('failed');
},
destroy: function() {
this._userVerifier.clear();
if (this._idleWatchId) {
this._idleMonitor.remove_watch(this._idleWatchId);
this._idleWatchId = 0;
}
this.parent();
},
cancel: function() {
this._userVerifier.cancel(null);
this.destroy();
},
});

View File

@ -1,7 +1,6 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const AccountsService = imports.gi.AccountsService; const AccountsService = imports.gi.AccountsService;
const Gdm = imports.gi.Gdm;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Lang = imports.lang; const Lang = imports.lang;
@ -16,15 +15,13 @@ const GnomeSession = imports.misc.gnomeSession;
const Main = imports.ui.main; const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const Params = imports.misc.params; const ScreenSaver = imports.misc.screenSaver;
const Util = imports.misc.util; const Util = imports.misc.util;
const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown'; const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown';
const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver';
const DISABLE_USER_SWITCH_KEY = 'disable-user-switching'; const DISABLE_USER_SWITCH_KEY = 'disable-user-switching';
const DISABLE_LOCK_SCREEN_KEY = 'disable-lock-screen'; const DISABLE_LOCK_SCREEN_KEY = 'disable-lock-screen';
const DISABLE_LOG_OUT_KEY = 'disable-log-out'; const DISABLE_LOG_OUT_KEY = 'disable-log-out';
const LOCK_ENABLED_KEY = 'lock-enabled';
const DIALOG_ICON_SIZE = 64; const DIALOG_ICON_SIZE = 64;
@ -43,37 +40,6 @@ const IMStatus = {
// Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>. // Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
// Copyright (C) 2008,2009 Red Hat, Inc. // Copyright (C) 2008,2009 Red Hat, Inc.
const UserAvatarWidget = new Lang.Class({
Name: 'UserAvatarWidget',
_init: function(user, params) {
this._user = user;
params = Params.parse(params, { reactive: false,
iconSize: DIALOG_ICON_SIZE,
styleClass: 'status-chooser-user-icon' });
this._iconSize = params.iconSize;
this.actor = new St.Bin({ style_class: params.styleClass,
track_hover: params.reactive,
reactive: params.reactive });
},
update: function() {
let iconFile = this._user.get_icon_file();
if (!GLib.file_test(iconFile, GLib.FileTest.EXISTS))
iconFile = null;
if (iconFile) {
let file = Gio.File.new_for_path(iconFile);
this.actor.child = null;
this.actor.style = 'background-image: url("%s");'.format(iconFile);
} else {
this.actor.style = null;
this.actor.child = new St.Icon({ icon_name: 'avatar-default-symbolic',
icon_size: this._iconSize });
}
}
});
const IMStatusItem = new Lang.Class({ const IMStatusItem = new Lang.Class({
Name: 'IMStatusItem', Name: 'IMStatusItem',
@ -102,7 +68,6 @@ const IMUserNameItem = new Lang.Class({
_init: function() { _init: function() {
this.parent({ reactive: false, this.parent({ reactive: false,
can_focus: false,
style_class: 'status-chooser-user-name' }); style_class: 'status-chooser-user-name' });
this._wrapper = new Shell.GenericContainer(); this._wrapper = new Shell.GenericContainer();
@ -140,14 +105,9 @@ const IMStatusChooserItem = new Lang.Class({
_init: function() { _init: function() {
this.parent({ reactive: false, this.parent({ reactive: false,
can_focus: false,
style_class: 'status-chooser' }); style_class: 'status-chooser' });
this._userManager = AccountsService.UserManager.get_default(); this._iconBin = new St.Button({ style_class: 'status-chooser-user-icon' });
this._user = this._userManager.get_user(GLib.get_user_name());
this._avatar = new UserAvatarWidget(this._user, { reactive: true });
this._iconBin = new St.Button({ child: this._avatar.actor });
this.addActor(this._iconBin); this.addActor(this._iconBin);
this._iconBin.connect('clicked', Lang.bind(this, this._iconBin.connect('clicked', Lang.bind(this,
@ -166,22 +126,22 @@ const IMStatusChooserItem = new Lang.Class({
let item; let item;
item = new IMStatusItem(_("Available"), 'user-available-symbolic'); item = new IMStatusItem(_("Available"), 'user-available');
this._combo.addMenuItem(item, IMStatus.AVAILABLE); this._combo.addMenuItem(item, IMStatus.AVAILABLE);
item = new IMStatusItem(_("Busy"), 'user-busy-symbolic'); item = new IMStatusItem(_("Busy"), 'user-busy');
this._combo.addMenuItem(item, IMStatus.BUSY); this._combo.addMenuItem(item, IMStatus.BUSY);
item = new IMStatusItem(_("Invisible"), 'user-invisible-symbolic'); item = new IMStatusItem(_("Hidden"), 'user-invisible');
this._combo.addMenuItem(item, IMStatus.HIDDEN); this._combo.addMenuItem(item, IMStatus.HIDDEN);
item = new IMStatusItem(_("Away"), 'user-away-symbolic'); item = new IMStatusItem(_("Away"), 'user-away');
this._combo.addMenuItem(item, IMStatus.AWAY); this._combo.addMenuItem(item, IMStatus.AWAY);
item = new IMStatusItem(_("Idle"), 'user-idle-symbolic'); item = new IMStatusItem(_("Idle"), 'user-idle');
this._combo.addMenuItem(item, IMStatus.IDLE); this._combo.addMenuItem(item, IMStatus.IDLE);
item = new IMStatusItem(_("Unavailable"), 'user-offline-symbolic'); item = new IMStatusItem(_("Unavailable"), 'user-offline');
this._combo.addMenuItem(item, IMStatus.OFFLINE); this._combo.addMenuItem(item, IMStatus.OFFLINE);
this._combo.connect('active-item-changed', this._combo.connect('active-item-changed',
@ -209,22 +169,25 @@ const IMStatusChooserItem = new Lang.Class({
Lang.bind(this, this._IMAccountsChanged)); Lang.bind(this, this._IMAccountsChanged));
this._accountMgr.prepare_async(null, Lang.bind(this, this._accountMgr.prepare_async(null, Lang.bind(this,
function(mgr) { function(mgr) {
let [presence, status, msg] = mgr.get_most_available_presence();
let savedPresence = global.settings.get_int('saved-im-presence');
this._IMAccountsChanged(mgr); this._IMAccountsChanged(mgr);
if (this._networkMonitor.network_available) if (savedPresence == presence) {
this._restorePresence(); this._IMStatusChanged(mgr, presence, status, msg);
else } else {
this._setComboboxPresence(Tp.ConnectionPresenceType.OFFLINE); this._setComboboxPresence(savedPresence);
status = this._statusForPresence(savedPresence);
msg = msg ? msg : '';
mgr.set_all_requested_presences(savedPresence, status, msg);
}
})); }));
this._networkMonitor = Gio.NetworkMonitor.get_default(); this._userManager = AccountsService.UserManager.get_default();
this._networkMonitor.connect('network-changed',
Lang.bind(this, function(monitor, available) {
this._IMAccountsChanged(this._accountMgr);
if (available && !this._imPresenceRestored) this._user = this._userManager.get_user(GLib.get_user_name());
this._restorePresence();
}));
this._userLoadedId = this._user.connect('notify::is-loaded', this._userLoadedId = this._user.connect('notify::is-loaded',
Lang.bind(this, Lang.bind(this,
@ -238,21 +201,6 @@ const IMStatusChooserItem = new Lang.Class({
})); }));
}, },
_restorePresence: function() {
let [presence, status, msg] = this._accountMgr.get_most_available_presence();
let savedPresence = global.settings.get_int('saved-im-presence');
if (savedPresence == presence) {
this._IMStatusChanged(this._accountMgr, presence, status, msg);
} else {
this._setComboboxPresence(savedPresence);
status = this._statusForPresence(savedPresence);
msg = msg ? msg : '';
this._accountMgr.set_all_requested_presences(savedPresence, status, msg);
}
},
destroy: function() { destroy: function() {
// clean up signal handlers // clean up signal handlers
if (this._userLoadedId != 0) { if (this._userLoadedId != 0) {
@ -278,12 +226,44 @@ const IMStatusChooserItem = new Lang.Class({
}, },
_updateUser: function() { _updateUser: function() {
if (this._user.is_loaded) let iconFile = null;
if (this._user.is_loaded) {
this._name.label.set_text(this._user.get_real_name()); this._name.label.set_text(this._user.get_real_name());
else iconFile = this._user.get_icon_file();
if (!GLib.file_test(iconFile, GLib.FileTest.EXISTS))
iconFile = null;
} else {
this._name.label.set_text(""); this._name.label.set_text("");
}
this._avatar.update(); if (iconFile)
this._setIconFromFile(iconFile);
else
this._setIconFromName('avatar-default');
},
_setIconFromFile: function(iconFile) {
this._iconBin.set_style('background-image: url("' + iconFile + '");' +
'background-size: contain;');
this._iconBin.child = null;
},
_setIconFromName: function(iconName) {
this._iconBin.set_style(null);
if (iconName != null) {
let textureCache = St.TextureCache.get_default();
let icon = textureCache.load_icon_name(this._iconBin.get_theme_node(),
iconName,
St.IconType.SYMBOLIC,
DIALOG_ICON_SIZE);
this._iconBin.child = icon;
this._iconBin.show();
} else {
this._iconBin.child = null;
this._iconBin.hide();
}
}, },
_statusForPresence: function(presence) { _statusForPresence: function(presence) {
@ -309,8 +289,7 @@ const IMStatusChooserItem = new Lang.Class({
let accounts = mgr.get_valid_accounts().filter(function(account) { let accounts = mgr.get_valid_accounts().filter(function(account) {
return account.enabled; return account.enabled;
}); });
let sensitive = accounts.length > 0 && this._networkMonitor.network_available; this._combo.setSensitive(accounts.length > 0);
this._combo.setSensitive(sensitive);
}, },
_IMStatusChanged: function(accountMgr, presence, status, message) { _IMStatusChanged: function(accountMgr, presence, status, message) {
@ -463,7 +442,6 @@ const UserMenuButton = new Lang.Class({
let box = new St.BoxLayout({ name: 'panelUserMenu' }); let box = new St.BoxLayout({ name: 'panelUserMenu' });
this.actor.add_actor(box); this.actor.add_actor(box);
this._screenSaverSettings = new Gio.Settings({ schema: SCREENSAVER_SCHEMA });
this._lockdownSettings = new Gio.Settings({ schema: LOCKDOWN_SCHEMA }); this._lockdownSettings = new Gio.Settings({ schema: LOCKDOWN_SCHEMA });
this._userManager = AccountsService.UserManager.get_default(); this._userManager = AccountsService.UserManager.get_default();
@ -476,38 +454,32 @@ const UserMenuButton = new Lang.Class({
this._accountMgr = Tp.AccountManager.dup(); this._accountMgr = Tp.AccountManager.dup();
this._upClient = new UPowerGlib.Client(); this._upClient = new UPowerGlib.Client();
this._screenSaverProxy = new ScreenSaver.ScreenSaverProxy();
this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
this._iconBox = new St.Bin(); this._iconBox = new St.Bin();
box.add(this._iconBox, { y_align: St.Align.MIDDLE, y_fill: false }); box.add(this._iconBox, { y_align: St.Align.MIDDLE, y_fill: false });
let textureCache = St.TextureCache.get_default(); let textureCache = St.TextureCache.get_default();
this._offlineIcon = new St.Icon({ icon_name: 'user-offline-symbolic', this._offlineIcon = new St.Icon({ icon_name: 'user-offline',
style_class: 'popup-menu-icon' }); style_class: 'popup-menu-icon' });
this._availableIcon = new St.Icon({ icon_name: 'user-available-symbolic', this._availableIcon = new St.Icon({ icon_name: 'user-available',
style_class: 'popup-menu-icon' }); style_class: 'popup-menu-icon' });
this._busyIcon = new St.Icon({ icon_name: 'user-busy-symbolic', this._busyIcon = new St.Icon({ icon_name: 'user-busy',
style_class: 'popup-menu-icon' }); style_class: 'popup-menu-icon' });
this._invisibleIcon = new St.Icon({ icon_name: 'user-invisible-symbolic', this._invisibleIcon = new St.Icon({ icon_name: 'user-invisible',
style_class: 'popup-menu-icon' }); style_class: 'popup-menu-icon' });
this._awayIcon = new St.Icon({ icon_name: 'user-away-symbolic', this._awayIcon = new St.Icon({ icon_name: 'user-away',
style_class: 'popup-menu-icon' }); style_class: 'popup-menu-icon' });
this._idleIcon = new St.Icon({ icon_name: 'user-idle-symbolic', this._idleIcon = new St.Icon({ icon_name: 'user-idle',
style_class: 'popup-menu-icon' }); style_class: 'popup-menu-icon' });
this._pendingIcon = new St.Icon({ icon_name: 'user-status-pending-symbolic',
style_class: 'popup-menu-icon' });
this._accountMgr.connect('most-available-presence-changed', this._accountMgr.connect('most-available-presence-changed',
Lang.bind(this, this._updatePresenceIcon)); Lang.bind(this, this._updatePresenceIcon));
this._accountMgr.connect('account-enabled',
Lang.bind(this, this._onAccountEnabled));
this._accountMgr.connect('account-removed',
Lang.bind(this, this._onAccountRemoved));
this._accountMgr.prepare_async(null, Lang.bind(this, this._accountMgr.prepare_async(null, Lang.bind(this,
function(mgr) { function(mgr) {
let [presence, s, msg] = mgr.get_most_available_presence(); let [presence, s, msg] = mgr.get_most_available_presence();
this._updatePresenceIcon(mgr, presence, s, msg); this._updatePresenceIcon(mgr, presence, s, msg);
this._setupAccounts();
})); }));
this._name = new St.Label(); this._name = new St.Label();
@ -525,13 +497,13 @@ const UserMenuButton = new Lang.Class({
})); }));
this._userManager.connect('notify::is-loaded', this._userManager.connect('notify::is-loaded',
Lang.bind(this, this._updateMultiUser)); Lang.bind(this, this._updateSwitchUser));
this._userManager.connect('notify::has-multiple-users', this._userManager.connect('notify::has-multiple-users',
Lang.bind(this, this._updateMultiUser)); Lang.bind(this, this._updateSwitchUser));
this._userManager.connect('user-added', this._userManager.connect('user-added',
Lang.bind(this, this._updateMultiUser)); Lang.bind(this, this._updateSwitchUser));
this._userManager.connect('user-removed', this._userManager.connect('user-removed',
Lang.bind(this, this._updateMultiUser)); Lang.bind(this, this._updateSwitchUser));
this._lockdownSettings.connect('changed::' + DISABLE_USER_SWITCH_KEY, this._lockdownSettings.connect('changed::' + DISABLE_USER_SWITCH_KEY,
Lang.bind(this, this._updateSwitchUser)); Lang.bind(this, this._updateSwitchUser));
this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY, this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY,
@ -543,10 +515,6 @@ const UserMenuButton = new Lang.Class({
this._updateLogout(); this._updateLogout();
this._updateLockScreen(); this._updateLockScreen();
this._updatesFile = Gio.File.new_for_path('/var/lib/PackageKit/prepared-update');
this._updatesMonitor = this._updatesFile.monitor(Gio.FileMonitorFlags.NONE, null);
this._updatesMonitor.connect('changed', Lang.bind(this, this._updateInstallUpdates));
// Whether shutdown is available or not depends on both lockdown // Whether shutdown is available or not depends on both lockdown
// settings (disable-log-out) and Polkit policy - the latter doesn't // settings (disable-log-out) and Polkit policy - the latter doesn't
// notify, so we update the menu item each time the menu opens or // notify, so we update the menu item each time the menu opens or
@ -562,10 +530,6 @@ const UserMenuButton = new Lang.Class({
this._upClient.connect('notify::can-suspend', Lang.bind(this, this._updateSuspendOrPowerOff)); this._upClient.connect('notify::can-suspend', Lang.bind(this, this._updateSuspendOrPowerOff));
}, },
setLockedState: function(locked) {
this.actor.visible = !locked;
},
_onDestroy: function() { _onDestroy: function() {
this._user.disconnect(this._userLoadedId); this._user.disconnect(this._userLoadedId);
this._user.disconnect(this._userChangedId); this._user.disconnect(this._userChangedId);
@ -578,45 +542,31 @@ const UserMenuButton = new Lang.Class({
this._name.set_text(""); this._name.set_text("");
}, },
_updateMultiUser: function() {
this._updateSwitchUser();
this._updateLogout();
},
_updateSwitchUser: function() { _updateSwitchUser: function() {
let allowSwitch = !this._lockdownSettings.get_boolean(DISABLE_USER_SWITCH_KEY); let allowSwitch = !this._lockdownSettings.get_boolean(DISABLE_USER_SWITCH_KEY);
let multiUser = this._userManager.can_switch() && this._userManager.has_multiple_users; if (allowSwitch &&
let multiSession = Gdm.get_session_ids().length > 1; this._userManager.can_switch() &&
this._userManager.has_multiple_users)
this._loginScreenItem.label.set_text(multiUser ? _("Switch User") this._loginScreenItem.actor.show();
: _("Switch Session")); else
this._loginScreenItem.actor.visible = allowSwitch && (multiUser || multiSession); this._loginScreenItem.actor.hide();
}, },
_updateLogout: function() { _updateLogout: function() {
let allowLogout = !this._lockdownSettings.get_boolean(DISABLE_LOG_OUT_KEY); let allowLogout = !this._lockdownSettings.get_boolean(DISABLE_LOG_OUT_KEY);
let multiUser = this._userManager.has_multiple_users; this._logoutItem.actor.visible = allowLogout;
let multiSession = Gdm.get_session_ids().length > 1;
this._logoutItem.actor.visible = allowLogout && (multiUser || multiSession);
}, },
_updateLockScreen: function() { _updateLockScreen: function() {
let allowLockScreen = !this._lockdownSettings.get_boolean(DISABLE_LOCK_SCREEN_KEY); let allowLockScreen = !this._lockdownSettings.get_boolean(DISABLE_LOCK_SCREEN_KEY);
this._lockScreenItem.actor.visible = allowLockScreen; this._logoutItem.actor.visible = allowLockScreen;
},
_updateInstallUpdates: function() {
let haveUpdates = this._updatesFile.query_exists(null);
this._installUpdatesItem.actor.visible = haveUpdates && this._haveShutdown;
}, },
_updateHaveShutdown: function() { _updateHaveShutdown: function() {
this._session.CanShutdownRemote(Lang.bind(this, this._session.CanShutdownRemote(Lang.bind(this,
function(result, error) { function(result, error) {
if (!error) { if (!error) {
this._haveShutdown = result[0]; this._haveShutdown = result;
this._updateInstallUpdates();
this._updateSuspendOrPowerOff(); this._updateSuspendOrPowerOff();
} }
})); }));
@ -630,14 +580,14 @@ const UserMenuButton = new Lang.Class({
this._suspendOrPowerOffItem.actor.visible = this._haveShutdown || this._haveSuspend; this._suspendOrPowerOffItem.actor.visible = this._haveShutdown || this._haveSuspend;
// If we can't power off show Suspend instead // If we can't suspend show Power Off... instead
// and disable the alt key // and disable the alt key
if (!this._haveShutdown) { if (!this._haveSuspend) {
this._suspendOrPowerOffItem.updateText(_("Power Off..."), null);
} else if (!this._haveShutdown) {
this._suspendOrPowerOffItem.updateText(_("Suspend"), null); this._suspendOrPowerOffItem.updateText(_("Suspend"), null);
} else if (!this._haveSuspend) {
this._suspendOrPowerOffItem.updateText(_("Power Off"), null);
} else { } else {
this._suspendOrPowerOffItem.updateText(_("Power Off"), _("Suspend")); this._suspendOrPowerOffItem.updateText(_("Suspend"), _("Power Off..."));
} }
}, },
@ -661,54 +611,11 @@ const UserMenuButton = new Lang.Class({
this._iconBox.child = this._offlineIcon; this._iconBox.child = this._offlineIcon;
}, },
_setupAccounts: function() {
let accounts = this._accountMgr.get_valid_accounts();
for (let i = 0; i < accounts.length; i++) {
accounts[i]._changingId = accounts[i].connect('notify::connection-status',
Lang.bind(this, this._updateChangingPresence));
}
this._updateChangingPresence();
},
_onAccountEnabled: function(accountMgr, account) {
if (!account._changingId)
account._changingId = account.connect('notify::connection-status',
Lang.bind(this, this._updateChangingPresence));
this._updateChangingPresence();
},
_onAccountRemoved: function(accountMgr, account) {
if (account._changingId) {
account.disconnect(account._changingId);
account._changingId = 0;
}
this._updateChangingPresence();
},
_updateChangingPresence: function() {
let accounts = this._accountMgr.get_valid_accounts();
let changing = false;
for (let i = 0; i < accounts.length; i++) {
if (accounts[i].connection_status == Tp.ConnectionStatus.CONNECTING) {
changing = true;
break;
}
}
if (changing) {
this._iconBox.child = this._pendingIcon;
} else {
let [presence, s, msg] = this._accountMgr.get_most_available_presence();
this._updatePresenceIcon(this._accountMgr, presence, s, msg);
}
},
_createSubMenu: function() { _createSubMenu: function() {
let item; let item;
item = new IMStatusChooserItem(); item = new IMStatusChooserItem();
if (Main.sessionMode.allowSettings) item.connect('activate', Lang.bind(this, this._onMyAccountActivate));
item.connect('activate', Lang.bind(this, this._onMyAccountActivate));
this.menu.addMenuItem(item); this.menu.addMenuItem(item);
this._statusChooser = item; this._statusChooser = item;
@ -720,44 +627,41 @@ const UserMenuButton = new Lang.Class({
item = new PopupMenu.PopupSeparatorMenuItem(); item = new PopupMenu.PopupSeparatorMenuItem();
this.menu.addMenuItem(item); this.menu.addMenuItem(item);
if (Main.sessionMode.allowSettings) { item = new PopupMenu.PopupMenuItem(_("Online Accounts"));
item = new PopupMenu.PopupMenuItem(_("System Settings")); item.connect('activate', Lang.bind(this, this._onOnlineAccountsActivate));
item.connect('activate', Lang.bind(this, this._onPreferencesActivate)); this.menu.addMenuItem(item);
this.menu.addMenuItem(item);
} item = new PopupMenu.PopupMenuItem(_("System Settings"));
item.connect('activate', Lang.bind(this, this._onPreferencesActivate));
this.menu.addMenuItem(item);
item = new PopupMenu.PopupSeparatorMenuItem(); item = new PopupMenu.PopupSeparatorMenuItem();
this.menu.addMenuItem(item); this.menu.addMenuItem(item);
item = new PopupMenu.PopupMenuItem(_("Lock Screen"));
item.connect('activate', Lang.bind(this, this._onLockScreenActivate));
this.menu.addMenuItem(item);
this._lockScreenItem = item;
item = new PopupMenu.PopupMenuItem(_("Switch User")); item = new PopupMenu.PopupMenuItem(_("Switch User"));
item.connect('activate', Lang.bind(this, this._onLoginScreenActivate)); item.connect('activate', Lang.bind(this, this._onLoginScreenActivate));
this.menu.addMenuItem(item); this.menu.addMenuItem(item);
this._loginScreenItem = item; this._loginScreenItem = item;
item = new PopupMenu.PopupMenuItem(_("Log Out")); item = new PopupMenu.PopupMenuItem(_("Log Out..."));
item.connect('activate', Lang.bind(this, this._onQuitSessionActivate)); item.connect('activate', Lang.bind(this, this._onQuitSessionActivate));
this.menu.addMenuItem(item); this.menu.addMenuItem(item);
this._logoutItem = item; this._logoutItem = item;
item = new PopupMenu.PopupMenuItem(_("Lock"));
item.connect('activate', Lang.bind(this, this._onLockScreenActivate));
this.menu.addMenuItem(item);
this._lockScreenItem = item;
item = new PopupMenu.PopupSeparatorMenuItem(); item = new PopupMenu.PopupSeparatorMenuItem();
this.menu.addMenuItem(item); this.menu.addMenuItem(item);
item = new PopupMenu.PopupAlternatingMenuItem(_("Power Off"), item = new PopupMenu.PopupAlternatingMenuItem(_("Suspend"),
_("Suspend")); _("Power Off..."));
this.menu.addMenuItem(item); this.menu.addMenuItem(item);
item.connect('activate', Lang.bind(this, this._onSuspendOrPowerOffActivate));
this._suspendOrPowerOffItem = item; this._suspendOrPowerOffItem = item;
item.connect('activate', Lang.bind(this, this._onSuspendOrPowerOffActivate));
this._updateSuspendOrPowerOff(); this._updateSuspendOrPowerOff();
item = new PopupMenu.PopupMenuItem(_("Install Updates & Restart"));
item.connect('activate', Lang.bind(this, this._onInstallUpdatesActivate));
this.menu.addMenuItem(item);
this._installUpdatesItem = item;
}, },
_updatePresenceStatus: function(item, event) { _updatePresenceStatus: function(item, event) {
@ -785,6 +689,12 @@ const UserMenuButton = new Lang.Class({
app.activate(); app.activate();
}, },
_onOnlineAccountsActivate: function() {
Main.overview.hide();
let app = Shell.AppSystem.get_default().lookup_setting('gnome-online-accounts-panel.desktop');
app.activate(-1);
},
_onPreferencesActivate: function() { _onPreferencesActivate: function() {
Main.overview.hide(); Main.overview.hide();
let app = Shell.AppSystem.get_default().lookup_app('gnome-control-center.desktop'); let app = Shell.AppSystem.get_default().lookup_app('gnome-control-center.desktop');
@ -793,14 +703,16 @@ const UserMenuButton = new Lang.Class({
_onLockScreenActivate: function() { _onLockScreenActivate: function() {
Main.overview.hide(); Main.overview.hide();
Main.screenShield.lock(true); this._screenSaverProxy.LockRemote();
}, },
_onLoginScreenActivate: function() { _onLoginScreenActivate: function() {
Main.overview.hide(); Main.overview.hide();
if (this._screenSaverSettings.get_boolean(LOCK_ENABLED_KEY)) // Ensure we only move to GDM after the screensaver has activated; in some
Main.screenShield.lock(false); // OS configurations, the X server may block event processing on VT switch
Gdm.goto_login_session_sync(null); this._screenSaverProxy.SetActiveRemote(true, Lang.bind(this, function() {
this._userManager.goto_login_session();
}));
}, },
_onQuitSessionActivate: function() { _onQuitSessionActivate: function() {
@ -808,31 +720,17 @@ const UserMenuButton = new Lang.Class({
this._session.LogoutRemote(0); this._session.LogoutRemote(0);
}, },
_onInstallUpdatesActivate: function() {
Main.overview.hide();
Util.spawn(['pkexec', '/usr/libexec/pk-trigger-offline-update']);
this._session.RebootRemote();
},
_onSuspendOrPowerOffActivate: function() { _onSuspendOrPowerOffActivate: function() {
Main.overview.hide(); Main.overview.hide();
if (this._haveShutdown && if (this._haveSuspend &&
this._suspendOrPowerOffItem.state == PopupMenu.PopupAlternatingMenuItemState.DEFAULT) { this._suspendOrPowerOffItem.state == PopupMenu.PopupAlternatingMenuItemState.DEFAULT) {
this._session.ShutdownRemote(); // Ensure we only suspend after locking the screen
} else { this._screenSaverProxy.LockRemote(Lang.bind(this, function() {
if (this._screenSaverSettings.get_boolean(LOCK_ENABLED_KEY)) {
let tmpId = Main.screenShield.connect('lock-screen-shown', Lang.bind(this, function() {
Main.screenShield.disconnect(tmpId);
this._upClient.suspend_sync(null);
}));
Main.screenShield.lock(true);
} else {
this._upClient.suspend_sync(null); this._upClient.suspend_sync(null);
} }));
} else {
this._session.ShutdownRemote();
} }
} }
}); });

View File

@ -9,258 +9,176 @@ const Lang = imports.lang;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const St = imports.gi.St; const St = imports.gi.St;
const AppDisplay = imports.ui.appDisplay;
const Main = imports.ui.main; const Main = imports.ui.main;
const PlaceDisplay = imports.ui.placeDisplay;
const RemoteSearch = imports.ui.remoteSearch;
const Search = imports.ui.search; const Search = imports.ui.search;
const SearchDisplay = imports.ui.searchDisplay; const SearchDisplay = imports.ui.searchDisplay;
const ShellEntry = imports.ui.shellEntry; const ShellEntry = imports.ui.shellEntry;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const Wanda = imports.ui.wanda;
const WorkspacesView = imports.ui.workspacesView; const BaseTab = new Lang.Class({
Name: 'BaseTab',
_init: function(titleActor, pageActor, name, a11yIcon) {
this.title = titleActor;
this.page = new St.Bin({ child: pageActor,
x_align: St.Align.START,
y_align: St.Align.START,
x_fill: true,
y_fill: true,
style_class: 'view-tab-page' });
if (this.title.can_focus) {
Main.ctrlAltTabManager.addGroup(this.title, name, a11yIcon);
} else {
Main.ctrlAltTabManager.addGroup(this.page, name, a11yIcon,
{ proxy: this.title,
focusCallback: Lang.bind(this, this._a11yFocus) });
}
this.visible = false;
},
show: function(animate) {
this.visible = true;
this.page.show();
if (!animate)
return;
this.page.opacity = 0;
Tweener.addTween(this.page,
{ opacity: 255,
time: 0.1,
transition: 'easeOutQuad' });
},
hide: function() {
this.visible = false;
Tweener.addTween(this.page,
{ opacity: 0,
time: 0.1,
transition: 'easeOutQuad',
onComplete: Lang.bind(this,
function() {
this.page.hide();
})
});
},
_a11yFocus: function() {
this._activate();
this.page.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
},
_activate: function() {
this.emit('activated');
}
});
Signals.addSignalMethods(BaseTab.prototype);
const FocusTrap = new Lang.Class({ const ViewTab = new Lang.Class({
Name: 'FocusTrap', Name: 'ViewTab',
Extends: St.Widget, Extends: BaseTab,
vfunc_navigate_focus: function(from, direction) { _init: function(id, label, pageActor, a11yIcon) {
if (direction == Gtk.DirectionType.TAB_FORWARD || this.id = id;
direction == Gtk.DirectionType.TAB_BACKWARD)
return this.parent(from, direction); let titleActor = new St.Button({ label: label,
return false; style_class: 'view-tab-title' });
titleActor.connect('clicked', Lang.bind(this, this._activate));
this.parent(titleActor, pageActor, label, a11yIcon);
} }
}); });
const ViewSelector = new Lang.Class({ const SearchTab = new Lang.Class({
Name: 'ViewSelector', Name: 'SearchTab',
Extends: BaseTab,
_init : function(searchEntry, showAppsButton) {
this.actor = new St.BoxLayout({ name: 'viewSelector',
vertical: true });
this._showAppsButton = showAppsButton;
this._showAppsButton.connect('notify::checked', Lang.bind(this, this._onShowAppsButtonToggled));
this._pageArea = new Shell.Stack();
this.actor.add(this._pageArea, { x_fill: true,
y_fill: true,
expand: true });
this._activePage = null;
_init: function() {
this.active = false; this.active = false;
this._searchPending = false; this._searchPending = false;
this._searchTimeoutId = 0; this._searchTimeoutId = 0;
this._searchSystem = new Search.SearchSystem(); this._searchSystem = new Search.SearchSystem();
this._openSearchSystem = new Search.OpenSearchSystem();
this._entry = searchEntry; this._entry = new St.Entry({ name: 'searchEntry',
/* Translators: this is the text displayed
in the search entry when no search is
active; it should not exceed ~30
characters. */
hint_text: _("Type to search..."),
track_hover: true,
can_focus: true });
ShellEntry.addContextMenu(this._entry); ShellEntry.addContextMenu(this._entry);
this._text = this._entry.clutter_text; this._text = this._entry.clutter_text;
this._text.connect('text-changed', Lang.bind(this, this._onTextChanged));
this._text.connect('key-press-event', Lang.bind(this, this._onKeyPress)); this._text.connect('key-press-event', Lang.bind(this, this._onKeyPress));
this._inactiveIcon = new St.Icon({ style_class: 'search-entry-icon',
icon_name: 'edit-find',
icon_type: St.IconType.SYMBOLIC });
this._activeIcon = new St.Icon({ style_class: 'search-entry-icon',
icon_name: 'edit-clear',
icon_type: St.IconType.SYMBOLIC });
this._entry.set_secondary_icon(this._inactiveIcon);
this._iconClickedId = 0;
this._searchResults = new SearchDisplay.SearchResults(this._searchSystem, this._openSearchSystem);
this.parent(this._entry, this._searchResults.actor, _("Search"), 'edit-find');
this._text.connect('text-changed', Lang.bind(this, this._onTextChanged));
this._text.connect('key-press-event', Lang.bind(this, function (o, e) {
// We can't connect to 'activate' here because search providers
// might want to do something with the modifiers in activateDefault.
let symbol = e.get_key_symbol();
if (symbol == Clutter.Return || symbol == Clutter.KP_Enter) {
if (this._searchTimeoutId > 0) {
Mainloop.source_remove(this._searchTimeoutId);
this._doSearch();
}
this._searchResults.activateDefault();
return true;
}
return false;
}));
this._entry.connect('notify::mapped', Lang.bind(this, this._onMapped));
global.stage.connect('notify::key-focus', Lang.bind(this, this._onStageKeyFocusChanged));
this._capturedEventId = 0;
this._text.connect('key-focus-in', Lang.bind(this, function() { this._text.connect('key-focus-in', Lang.bind(this, function() {
this._searchResults.highlightDefault(true); this._searchResults.highlightDefault(true);
})); }));
this._text.connect('key-focus-out', Lang.bind(this, function() { this._text.connect('key-focus-out', Lang.bind(this, function() {
this._searchResults.highlightDefault(false); this._searchResults.highlightDefault(false);
})); }));
this._entry.connect('notify::mapped', Lang.bind(this, this._onMapped));
global.stage.connect('notify::key-focus', Lang.bind(this, this._onStageKeyFocusChanged));
this._inactiveIcon = new St.Icon({ style_class: 'search-entry-icon',
icon_name: 'edit-find-symbolic' });
this._activeIcon = new St.Icon({ style_class: 'search-entry-icon',
icon_name: 'edit-clear-symbolic' });
this._entry.set_secondary_icon(this._inactiveIcon);
this._iconClickedId = 0;
this._capturedEventId = 0;
this._workspacesDisplay = new WorkspacesView.WorkspacesDisplay();
this._workspacesPage = this._addPage(this._workspacesDisplay.actor, null,
_("Windows"), 'text-x-generic-symbolic');
this._appDisplay = new AppDisplay.AllAppDisplay();
this._appsPage = this._addPage(this._appDisplay.actor, null,
_("Applications"), 'system-run-symbolic');
this._searchResults = new SearchDisplay.SearchResults(this._searchSystem);
this._searchPage = this._addPage(this._searchResults.actor, this._entry,
_("Search"), 'edit-find-symbolic');
// 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());
// Load remote search providers provided by applications
RemoteSearch.loadRemoteSearchProviders(Lang.bind(this, this.addSearchProvider));
// Since the entry isn't inside the results container we install this // Since the entry isn't inside the results container we install this
// dummy widget as the last results container child so that we can // dummy widget as the last results container child so that we can
// include the entry in the keynav tab path // include the entry in the keynav tab path...
this._focusTrap = new FocusTrap({ can_focus: true }); this._focusTrap = new St.Bin({ can_focus: true });
this._focusTrap.connect('key-focus-in', Lang.bind(this, function() { this._focusTrap.connect('key-focus-in', Lang.bind(this, function() {
this._entry.grab_key_focus(); this._entry.grab_key_focus();
})); }));
// ... but make it unfocusable using arrow keys keynav by making its
// bounding box always contain the possible focus source's bounding
// box since StWidget's keynav logic won't ever select it as a target
// in that case.
this._focusTrap.add_constraint(new Clutter.BindConstraint({ source: this._searchResults.actor,
coordinate: Clutter.BindCoordinate.ALL }));
this._searchResults.actor.add_actor(this._focusTrap); this._searchResults.actor.add_actor(this._focusTrap);
global.focus_manager.add_group(this._searchResults.actor); global.focus_manager.add_group(this._searchResults.actor);
Main.overview.connect('item-drag-begin',
Lang.bind(this, this._resetShowAppsButton));
this._stageKeyPressId = 0;
Main.overview.connect('showing', Lang.bind(this,
function () {
this._resetShowAppsButton();
this._stageKeyPressId = global.stage.connect('key-press-event',
Lang.bind(this, this._onStageKeyPress));
}));
Main.overview.connect('hiding', Lang.bind(this,
function () {
this._resetShowAppsButton();
if (this._stageKeyPressId != 0) {
global.stage.disconnect(this._stageKeyPressId);
this._stageKeyPressId = 0;
}
}));
// Public constraints which may be used to tie actors' height or
// vertical position to the current tab's content; as the content's
// height and position depend on the view selector's style properties
// (e.g. font size, padding, spacing, ...) it would be extremely hard
// and ugly to get these from the outside. While it would be possible
// to use position and height properties directly, outside code would
// need to ensure that the content is properly allocated before
// accessing the properties.
this.constrainHeight = new Clutter.BindConstraint({ source: this._pageArea,
coordinate: Clutter.BindCoordinate.HEIGHT });
},
show: function() {
this._activePage = this._workspacesPage;
this._appsPage.hide();
this._searchPage.hide();
this._workspacesDisplay.show();
if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows())
Main.overview.fadeOutDesktop();
this._showPage(this._workspacesPage);
},
zoomFromOverview: function() {
this._workspacesDisplay.zoomFromOverview();
if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows())
Main.overview.fadeInDesktop();
}, },
hide: function() { hide: function() {
this._workspacesDisplay.hide(); this.parent();
},
_addPage: function(actor, a11yFocus, name, a11yIcon) {
let page = new St.Bin({ child: actor,
x_align: St.Align.START,
y_align: St.Align.START,
x_fill: true,
y_fill: true });
if (a11yFocus)
Main.ctrlAltTabManager.addGroup(a11yFocus, name, a11yIcon);
else
Main.ctrlAltTabManager.addGroup(actor, name, a11yIcon,
{ proxy: this.actor,
focusCallback: Lang.bind(this,
function() {
this._a11yFocusPage(page);
})
});;
this._pageArea.add_actor(page);
return page
},
_showPage: function(page) {
if(page == this._activePage)
return;
if(this._activePage) {
Tweener.addTween(this._activePage,
{ opacity: 0,
time: 0.1,
transition: 'easeOutQuad',
onComplete: Lang.bind(this,
function() {
this._activePage.hide();
this._activePage = page;
})
});
}
page.show();
Tweener.addTween(page,
{ opacity: 255,
time: 0.1,
transition: 'easeOutQuad'
});
},
_a11yFocusPage: function(page) {
this._showAppsButton.checked = page == this._appsPage;
page.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
},
_onShowAppsButtonToggled: function() {
if (this.active)
this.reset();
else
this._showPage(this._showAppsButton.checked ? this._appsPage
: this._workspacesPage);
},
_resetShowAppsButton: function() {
this._showAppsButton.checked = false;
},
_onStageKeyPress: function(actor, event) {
let modifiers = event.get_state();
let symbol = event.get_key_symbol();
if (symbol == Clutter.Escape) {
if (this.active)
this.reset();
else if (this._showAppsButton.checked)
this._resetShowAppsButton();
else
Main.overview.hide();
return true;
} else if (Clutter.keysym_to_unicode(symbol) ||
(symbol == Clutter.BackSpace && this.active)) {
this.startSearch(event);
} else if (!this.active) {
if (symbol == Clutter.Tab || symbol == Clutter.Down) {
this._activePage.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
return true;
} else if (symbol == Clutter.ISO_Left_Tab) {
this._activePage.navigate_focus(null, Gtk.DirectionType.TAB_BACKWARD, false);
return true;
}
}
return false;
},
_searchCancelled: function() {
this._showPage(this._showAppsButton.checked ? this._appsPage
: this._workspacesPage);
// Leave the entry focused when it doesn't have any text; // Leave the entry focused when it doesn't have any text;
// when replacing a selected search term, Clutter emits // when replacing a selected search term, Clutter emits
@ -309,6 +227,16 @@ const ViewSelector = new Lang.Class({
} }
}, },
addSearchProvider: function(provider) {
this._searchSystem.registerProvider(provider);
this._searchResults.createProviderMeta(provider);
},
removeSearchProvider: function(provider) {
this._searchSystem.unregisterProvider(provider);
this._searchResults.destroyProviderMeta(provider);
},
startSearch: function(event) { startSearch: function(event) {
global.stage.set_key_focus(this._text); global.stage.set_key_focus(this._text);
this._text.event(event, false); this._text.event(event, false);
@ -335,13 +263,14 @@ const ViewSelector = new Lang.Class({
this.reset(); this.reset();
})); }));
} }
this._activate();
} else { } else {
if (this._iconClickedId > 0) if (this._iconClickedId > 0)
this._entry.disconnect(this._iconClickedId); this._entry.disconnect(this._iconClickedId);
this._iconClickedId = 0; this._iconClickedId = 0;
this._entry.set_secondary_icon(this._inactiveIcon); this._entry.set_secondary_icon(this._inactiveIcon);
this._searchCancelled(); this.emit('search-cancelled');
} }
if (!this.active) { if (!this.active) {
if (this._searchTimeoutId > 0) { if (this._searchTimeoutId > 0) {
@ -362,15 +291,6 @@ const ViewSelector = new Lang.Class({
this.reset(); this.reset();
return true; return true;
} }
} else if (symbol == Clutter.Return || symbol == Clutter.KP_Enter) {
// We can't connect to 'activate' here because search providers
// might want to do something with the modifiers in activateDefault.
if (this._searchTimeoutId > 0) {
Mainloop.source_remove(this._searchTimeoutId);
this._doSearch();
}
this._searchResults.activateDefault();
return true;
} else if (this.active) { } else if (this.active) {
let arrowNext, nextDirection; let arrowNext, nextDirection;
if (entry.get_text_direction() == Clutter.TextDirection.RTL) { if (entry.get_text_direction() == Clutter.TextDirection.RTL) {
@ -420,17 +340,259 @@ const ViewSelector = new Lang.Class({
let text = this._text.get_text().replace(/^\s+/g, '').replace(/\s+$/g, ''); let text = this._text.get_text().replace(/^\s+/g, '').replace(/\s+$/g, '');
this._searchResults.doSearch(text); this._searchResults.doSearch(text);
this._showPage(this._searchPage); return false;
}
});
const ViewSelector = new Lang.Class({
Name: 'ViewSelector',
_init : function() {
this.actor = new St.BoxLayout({ name: 'viewSelector',
vertical: true });
// The tab bar is located at the top of the view selector and
// holds both "normal" tab labels and the search entry. The former
// is left aligned, the latter right aligned - unless the text
// direction is RTL, in which case the order is reversed.
this._tabBar = new Shell.GenericContainer();
this._tabBar.connect('get-preferred-width',
Lang.bind(this, this._getPreferredTabBarWidth));
this._tabBar.connect('get-preferred-height',
Lang.bind(this, this._getPreferredTabBarHeight));
this._tabBar.connect('allocate',
Lang.bind(this, this._allocateTabBar));
this.actor.add(this._tabBar);
// Box to hold "normal" tab labels
this._tabBox = new St.BoxLayout({ name: 'viewSelectorTabBar' });
this._tabBar.add_actor(this._tabBox);
// The searchArea just holds the entry
this._searchArea = new St.Bin({ name: 'searchArea' });
this._tabBar.add_actor(this._searchArea);
// The page area holds the tab pages. Every page is given the
// area's full allocation, so that the pages would appear on top
// of each other if the inactive ones weren't hidden.
this._pageArea = new Shell.Stack();
this.actor.add(this._pageArea, { x_fill: true,
y_fill: true,
expand: true });
this._tabs = [];
this._activeTab = null;
this._searchTab = new SearchTab();
this._searchArea.set_child(this._searchTab.title);
this._addTab(this._searchTab);
this._searchTab.connect('search-cancelled', Lang.bind(this,
function() {
this._switchTab(this._activeTab);
}));
Main.overview.connect('item-drag-begin',
Lang.bind(this, this._switchDefaultTab));
this._stageKeyPressId = 0;
Main.overview.connect('showing', Lang.bind(this,
function () {
this._switchDefaultTab();
this._stageKeyPressId = global.stage.connect('key-press-event',
Lang.bind(this, this._onStageKeyPress));
}));
Main.overview.connect('hiding', Lang.bind(this,
function () {
this._switchDefaultTab();
if (this._stageKeyPressId != 0) {
global.stage.disconnect(this._stageKeyPressId);
this._stageKeyPressId = 0;
}
}));
// Public constraints which may be used to tie actors' height or
// vertical position to the current tab's content; as the content's
// height and position depend on the view selector's style properties
// (e.g. font size, padding, spacing, ...) it would be extremely hard
// and ugly to get these from the outside. While it would be possible
// to use position and height properties directly, outside code would
// need to ensure that the content is properly allocated before
// accessing the properties.
this.constrainY = new Clutter.BindConstraint({ source: this._pageArea,
coordinate: Clutter.BindCoordinate.Y });
this.constrainHeight = new Clutter.BindConstraint({ source: this._pageArea,
coordinate: Clutter.BindCoordinate.HEIGHT });
},
_addTab: function(tab) {
tab.page.hide();
this._pageArea.add_actor(tab.page);
tab.connect('activated', Lang.bind(this, function(tab) {
this._switchTab(tab);
}));
},
addViewTab: function(id, title, pageActor, a11yIcon) {
let viewTab = new ViewTab(id, title, pageActor, a11yIcon);
this._tabs.push(viewTab);
this._tabBox.add(viewTab.title);
this._addTab(viewTab);
},
_switchTab: function(tab) {
let firstSwitch = this._activeTab == null;
if (this._activeTab && this._activeTab.visible) {
if (this._activeTab == tab)
return;
this._activeTab.title.remove_style_pseudo_class('selected');
this._activeTab.hide();
}
if (tab != this._searchTab) {
tab.title.add_style_pseudo_class('selected');
this._activeTab = tab;
if (this._searchTab.visible) {
this._searchTab.hide();
}
}
// Only fade when switching between tabs,
// not when setting the initially selected one.
if (!tab.visible)
tab.show(!firstSwitch);
},
switchTab: function(id) {
for (let i = 0; i < this._tabs.length; i++)
if (this._tabs[i].id == id) {
this._switchTab(this._tabs[i]);
break;
}
},
_switchDefaultTab: function() {
if (this._tabs.length > 0)
this._switchTab(this._tabs[0]);
},
_nextTab: function() {
if (this._tabs.length == 0 ||
this._tabs[this._tabs.length - 1] == this._activeTab)
return;
for (let i = 0; i < this._tabs.length; i++)
if (this._tabs[i] == this._activeTab) {
this._switchTab(this._tabs[i + 1]);
return;
}
},
_prevTab: function() {
if (this._tabs.length == 0 || this._tabs[0] == this._activeTab)
return;
for (let i = 0; i < this._tabs.length; i++)
if (this._tabs[i] == this._activeTab) {
this._switchTab(this._tabs[i - 1]);
return;
}
},
_getPreferredTabBarWidth: function(box, forHeight, alloc) {
let children = box.get_children();
for (let i = 0; i < children.length; i++) {
let [childMin, childNat] = children[i].get_preferred_width(forHeight);
alloc.min_size += childMin;
alloc.natural_size += childNat;
}
},
_getPreferredTabBarHeight: function(box, forWidth, alloc) {
let children = box.get_children();
for (let i = 0; i < children.length; i++) {
let [childMin, childNatural] = children[i].get_preferred_height(forWidth);
if (childMin > alloc.min_size)
alloc.min_size = childMin;
if (childNatural > alloc.natural_size)
alloc.natural_size = childNatural;
}
},
_allocateTabBar: function(container, box, flags) {
let allocWidth = box.x2 - box.x1;
let allocHeight = box.y2 - box.y1;
let [searchMinWidth, searchNatWidth] = this._searchArea.get_preferred_width(-1);
let [barMinWidth, barNatWidth] = this._tabBox.get_preferred_width(-1);
let childBox = new Clutter.ActorBox();
childBox.y1 = 0;
childBox.y2 = allocHeight;
if (this.actor.get_text_direction() == Clutter.TextDirection.RTL) {
childBox.x1 = allocWidth - barNatWidth;
childBox.x2 = allocWidth;
} else {
childBox.x1 = 0;
childBox.x2 = barNatWidth;
}
this._tabBox.allocate(childBox, flags);
if (this.actor.get_text_direction() == Clutter.TextDirection.RTL) {
childBox.x1 = 0;
childBox.x2 = searchNatWidth;
} else {
childBox.x1 = allocWidth - searchNatWidth;
childBox.x2 = allocWidth;
}
this._searchArea.allocate(childBox, flags);
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this,
function() {
this.constrainY.offset = this.actor.y;
}));
},
_onStageKeyPress: function(actor, event) {
let modifiers = event.get_state();
let symbol = event.get_key_symbol();
if (symbol == Clutter.Escape) {
if (this._searchTab.active)
this._searchTab.reset();
else
Main.overview.hide();
return true;
} else if (Clutter.keysym_to_unicode(symbol) ||
(symbol == Clutter.BackSpace && this._searchTab.active)) {
this._searchTab.startSearch(event);
} else if (!this._searchTab.active) {
if (modifiers & Clutter.ModifierType.CONTROL_MASK) {
if (symbol == Clutter.Page_Up) {
this._prevTab();
return true;
} else if (symbol == Clutter.Page_Down) {
this._nextTab();
return true;
}
} else if (symbol == Clutter.Tab) {
this._activeTab.page.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
return true;
} else if (symbol == Clutter.ISO_Left_Tab) {
this._activeTab.page.navigate_focus(null, Gtk.DirectionType.TAB_BACKWARD, false);
return true;
}
}
return false;
}, },
addSearchProvider: function(provider) { addSearchProvider: function(provider) {
this._searchSystem.registerProvider(provider); this._searchTab.addSearchProvider(provider);
this._searchResults.createProviderMeta(provider);
}, },
removeSearchProvider: function(provider) { removeSearchProvider: function(provider) {
this._searchSystem.unregisterProvider(provider); this._searchTab.removeSearchProvider(provider);
this._searchResults.destroyProviderMeta(provider);
} }
}); });
Signals.addSignalMethods(ViewSelector.prototype); Signals.addSignalMethods(ViewSelector.prototype);

View File

@ -9,7 +9,6 @@ const Signals = imports.signals;
const St = imports.gi.St; const St = imports.gi.St;
const IconGrid = imports.ui.iconGrid; const IconGrid = imports.ui.iconGrid;
const Layout = imports.ui.layout;
const Main = imports.ui.main; const Main = imports.ui.main;
const Search = imports.ui.search; const Search = imports.ui.search;
@ -55,7 +54,9 @@ const WandaIcon = new Lang.Class({
if (!this._imageFile) { if (!this._imageFile) {
return new St.Icon({ icon_name: 'face-smile', return new St.Icon({ icon_name: 'face-smile',
icon_size: iconSize }); 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 = St.TextureCache.get_default().load_sliced_image(this._imageFile, this._imgWidth, this._imgHeight);
@ -141,18 +142,17 @@ const FortuneDialog = new Lang.Class({
this._button.connect('clicked', Lang.bind(this, this.destroy)); this._button.connect('clicked', Lang.bind(this, this.destroy));
this._button.child = this._box; this._button.child = this._box;
this._bin = new St.Bin({ x_align: St.Align.MIDDLE, let monitor = Main.layoutManager.primaryMonitor;
y_align: St.Align.MIDDLE });
this._bin.add_constraint(new Layout.MonitorConstraint({ primary: true }));
this._bin.add_actor(this._button);
Main.layoutManager.addChrome(this._bin); 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)); GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 10, Lang.bind(this, this.destroy));
}, },
destroy: function() { destroy: function() {
this._bin.destroy(); this._button.destroy();
} }
}); });
@ -168,34 +168,34 @@ const WandaSearchProvider = new Lang.Class({
this.parent(_("Your favorite Easter Egg")); this.parent(_("Your favorite Easter Egg"));
}, },
getResultMetas: function(fish, callback) { getResultMetas: function(fish) {
callback([{ 'id': fish[0], // there may be many fish in the sea, but return [{ 'id': fish[0], // there may be many fish in the sea, but
// only one which speaks the truth! // only one which speaks the truth!
'name': capitalize(fish[0]), 'name': capitalize(fish[0]),
'createIcon': function(iconSize) { 'createIcon': function(iconSize) {
// for DND only (maybe could be improved) // for DND only (maybe could be improved)
// DON'T use St.Icon here, it crashes the shell // DON'T use St.Icon here, it crashes the shell
// (dnd.js code assumes it can query the actor size // (dnd.js code assumes it can query the actor size
// without parenting it, while StWidget accesses // without parenting it, while StWidget accesses
// StThemeNode in get_preferred_width/height, which // StThemeNode in get_preferred_width/height, which
// triggers an assertion failure) // triggers an assertion failure)
return St.TextureCache.get_default().load_icon_name(null, return St.TextureCache.get_default().load_icon_name(null,
'face-smile', 'face-smile',
iconSize); St.IconType.FULLCOLOR,
} iconSize);
}]); }
}];
}, },
getInitialResultSet: function(terms) { getInitialResultSet: function(terms) {
if (terms.join(' ') == MAGIC_FISH_KEY) { if (terms.join(' ') == MAGIC_FISH_KEY) {
this.searchSystem.pushResults(this, [ FISH_NAME ]); return [ FISH_NAME ];
} else {
this.searchSystem.pushResults(this, []);
} }
return [];
}, },
getSubsearchResultSet: function(previousResults, terms) { getSubsearchResultSet: function(previousResults, terms) {
this.getInitialResultSet(terms); return this.getInitialResultSet(terms);
}, },
activateResult: function(fish, params) { activateResult: function(fish, params) {

View File

@ -53,10 +53,10 @@ const Source = new Lang.Class({
Extends: MessageTray.Source, Extends: MessageTray.Source,
_init: function(app, window) { _init: function(app, window) {
this.parent(app.get_name());
this._window = window; this._window = window;
this._app = app; this._app = app;
this._setSummaryIcon(this.createNotificationIcon());
this.parent(app.get_name());
this.signalIDs = []; this.signalIDs = [];
this.signalIDs.push(this._window.connect('notify::demands-attention', Lang.bind(this, function() { this.destroy(); }))); this.signalIDs.push(this._window.connect('notify::demands-attention', Lang.bind(this, function() { this.destroy(); })));
@ -73,8 +73,8 @@ const Source = new Lang.Class({
this.signalIDs = []; this.signalIDs = [];
}, },
createIcon : function(size) { createNotificationIcon : function() {
return this._app.create_icon_texture(size); return this._app.create_icon_texture(this.ICON_SIZE);
}, },
open : function(notification) { open : function(notification) {

View File

@ -15,55 +15,72 @@ const Tweener = imports.ui.tweener;
const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings'; const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';
const WINDOW_ANIMATION_TIME = 0.25; const WINDOW_ANIMATION_TIME = 0.25;
const DIM_BRIGHTNESS = -0.3;
const DIM_TIME = 0.500; const DIM_TIME = 0.500;
const UNDIM_TIME = 0.250; const UNDIM_TIME = 0.250;
var dimShader = undefined;
function getDimShaderSource() {
if (!dimShader)
dimShader = Shell.get_file_contents_utf8_sync(global.datadir + '/shaders/dim-window.glsl');
return dimShader;
}
function getTopInvisibleBorder(metaWindow) {
let outerRect = metaWindow.get_outer_rect();
let inputRect = metaWindow.get_input_rect();
return outerRect.y - inputRect.y;
}
const WindowDimmer = new Lang.Class({ const WindowDimmer = new Lang.Class({
Name: 'WindowDimmer', Name: 'WindowDimmer',
_init: function(actor) { _init: function(actor) {
this._brightnessEffect = new Clutter.BrightnessContrastEffect(); if (Clutter.feature_available(Clutter.FeatureFlags.SHADERS_GLSL)) {
actor.add_effect(this._brightnessEffect); this._effect = new Clutter.ShaderEffect({ shader_type: Clutter.ShaderType.FRAGMENT_SHADER });
this._effect.set_shader_source(getDimShaderSource());
} else {
this._effect = null;
}
this.actor = actor; this.actor = actor;
this._enabled = true;
this._dimFactor = 0.0;
this._syncEnabled();
}, },
_syncEnabled: function() { set dimFraction(fraction) {
this._brightnessEffect.enabled = (this._enabled && this._dimFactor > 0); this._dimFraction = fraction;
if (this._effect == null)
return;
if (!Meta.prefs_get_attach_modal_dialogs()) {
this._effect.enabled = false;
return;
}
if (fraction > 0.01) {
Shell.shader_effect_set_double_uniform(this._effect, 'height', this.actor.get_height());
Shell.shader_effect_set_double_uniform(this._effect, 'fraction', fraction);
if (!this._effect.actor)
this.actor.add_effect(this._effect);
} else {
if (this._effect.actor)
this.actor.remove_effect(this._effect);
}
}, },
setEnabled: function(enabled) { get dimFraction() {
this._enabled = enabled; return this._dimFraction;
this._syncEnabled();
}, },
set dimFactor(factor) { _dimFraction: 0.0
this._dimFactor = factor;
this._brightnessEffect.set_brightness(factor * DIM_BRIGHTNESS);
this._syncEnabled();
},
get dimFactor() {
return this._dimFactor;
}
}); });
function getWindowDimmer(actor) { function getWindowDimmer(actor) {
let enabled = Meta.prefs_get_attach_modal_dialogs(); if (!actor._windowDimmer)
if (actor._windowDimmer) actor._windowDimmer = new WindowDimmer(actor);
actor._windowDimmer.setEnabled(enabled);
if (enabled) { return actor._windowDimmer;
if (!actor._windowDimmer)
actor._windowDimmer = new WindowDimmer(actor);
return actor._windowDimmer;
} else {
return null;
}
} }
const WindowManager = new Lang.Class({ const WindowManager = new Lang.Class({
@ -77,7 +94,6 @@ const WindowManager = new Lang.Class({
this._unmaximizing = []; this._unmaximizing = [];
this._mapping = []; this._mapping = [];
this._destroying = []; this._destroying = [];
this._movingWindow = null;
this._dimmedWindows = []; this._dimmedWindows = [];
@ -109,14 +125,6 @@ const WindowManager = new Lang.Class({
Lang.bind(this, this._showWorkspaceSwitcher)); Lang.bind(this, this._showWorkspaceSwitcher));
Meta.keybindings_set_custom_handler('switch-to-workspace-down', Meta.keybindings_set_custom_handler('switch-to-workspace-down',
Lang.bind(this, this._showWorkspaceSwitcher)); Lang.bind(this, this._showWorkspaceSwitcher));
Meta.keybindings_set_custom_handler('move-to-workspace-left',
Lang.bind(this, this._showWorkspaceSwitcher));
Meta.keybindings_set_custom_handler('move-to-workspace-right',
Lang.bind(this, this._showWorkspaceSwitcher));
Meta.keybindings_set_custom_handler('move-to-workspace-up',
Lang.bind(this, this._showWorkspaceSwitcher));
Meta.keybindings_set_custom_handler('move-to-workspace-down',
Lang.bind(this, this._showWorkspaceSwitcher));
Meta.keybindings_set_custom_handler('switch-windows', Meta.keybindings_set_custom_handler('switch-windows',
Lang.bind(this, this._startAppSwitcher)); Lang.bind(this, this._startAppSwitcher));
Meta.keybindings_set_custom_handler('switch-group', Meta.keybindings_set_custom_handler('switch-group',
@ -134,11 +142,11 @@ const WindowManager = new Lang.Class({
Main.overview.connect('showing', Lang.bind(this, function() { Main.overview.connect('showing', Lang.bind(this, function() {
for (let i = 0; i < this._dimmedWindows.length; i++) for (let i = 0; i < this._dimmedWindows.length; i++)
this._undimWindow(this._dimmedWindows[i]); this._undimWindow(this._dimmedWindows[i], true);
})); }));
Main.overview.connect('hiding', Lang.bind(this, function() { Main.overview.connect('hiding', Lang.bind(this, function() {
for (let i = 0; i < this._dimmedWindows.length; i++) for (let i = 0; i < this._dimmedWindows.length; i++)
this._dimWindow(this._dimmedWindows[i]); this._dimWindow(this._dimmedWindows[i], true);
})); }));
}, },
@ -150,14 +158,12 @@ const WindowManager = new Lang.Class({
this._animationBlockCount = Math.max(0, this._animationBlockCount - 1); this._animationBlockCount = Math.max(0, this._animationBlockCount - 1);
}, },
_shouldAnimate: function() { _shouldAnimate : function(actor) {
return !(Main.overview.visible || this._animationBlockCount > 0); if (Main.overview.visible || this._animationBlockCount > 0)
},
_shouldAnimateActor: function(actor) {
if (!this._shouldAnimate())
return false; return false;
return actor.meta_window.get_window_type() == Meta.WindowType.NORMAL; if (actor && (actor.meta_window.get_window_type() != Meta.WindowType.NORMAL))
return false;
return true;
}, },
_removeEffect : function(list, actor) { _removeEffect : function(list, actor) {
@ -170,7 +176,7 @@ const WindowManager = new Lang.Class({
}, },
_minimizeWindow : function(shellwm, actor) { _minimizeWindow : function(shellwm, actor) {
if (!this._shouldAnimateActor(actor)) { if (!this._shouldAnimate(actor)) {
shellwm.completed_minimize(actor); shellwm.completed_minimize(actor);
return; return;
} }
@ -254,42 +260,43 @@ const WindowManager = new Lang.Class({
window._dimmed = true; window._dimmed = true;
this._dimmedWindows.push(window); this._dimmedWindows.push(window);
if (!Main.overview.visible) if (!Main.overview.visible)
this._dimWindow(window); this._dimWindow(window, true);
} else if (!shouldDim && window._dimmed) { } else if (!shouldDim && window._dimmed) {
window._dimmed = false; window._dimmed = false;
this._dimmedWindows = this._dimmedWindows.filter(function(win) { this._dimmedWindows = this._dimmedWindows.filter(function(win) {
return win != window; return win != window;
}); });
if (!Main.overview.visible) if (!Main.overview.visible)
this._undimWindow(window); this._undimWindow(window, true);
} }
}, },
_dimWindow: function(window) { _dimWindow: function(window, animate) {
let actor = window.get_compositor_private(); let actor = window.get_compositor_private();
if (!actor) if (!actor)
return; return;
let dimmer = getWindowDimmer(actor); if (animate)
if (!dimmer) Tweener.addTween(getWindowDimmer(actor),
return; { dimFraction: 1.0,
Tweener.addTween(dimmer, time: DIM_TIME,
{ dimFactor: 1.0, transition: 'linear'
time: DIM_TIME, });
transition: 'linear' else
}); getWindowDimmer(actor).dimFraction = 1.0;
}, },
_undimWindow: function(window) { _undimWindow: function(window, animate) {
let actor = window.get_compositor_private(); let actor = window.get_compositor_private();
if (!actor) if (!actor)
return; return;
let dimmer = getWindowDimmer(actor); if (animate)
if (!dimmer) Tweener.addTween(getWindowDimmer(actor),
return; { dimFraction: 0.0,
Tweener.addTween(dimmer, time: UNDIM_TIME,
{ dimFactor: 0.0, transition: 'linear'
time: UNDIM_TIME, });
transition: 'linear' }); else
getWindowDimmer(actor).dimFraction = 0.0;
}, },
_mapWindow : function(shellwm, actor) { _mapWindow : function(shellwm, actor) {
@ -311,7 +318,6 @@ const WindowManager = new Lang.Class({
this._checkDimming(actor.get_meta_window().get_transient_for()); this._checkDimming(actor.get_meta_window().get_transient_for());
if (this._shouldAnimate()) { if (this._shouldAnimate()) {
actor.set_scale(1.0, 0.0); actor.set_scale(1.0, 0.0);
actor.scale_gravity = Clutter.Gravity.CENTER;
actor.show(); actor.show();
this._mapping.push(actor); this._mapping.push(actor);
@ -331,7 +337,7 @@ const WindowManager = new Lang.Class({
shellwm.completed_map(actor); shellwm.completed_map(actor);
return; return;
} }
if (!this._shouldAnimateActor(actor)) { if (!this._shouldAnimate(actor)) {
shellwm.completed_map(actor); shellwm.completed_map(actor);
return; return;
} }
@ -388,7 +394,6 @@ const WindowManager = new Lang.Class({
} }
actor.set_scale(1.0, 1.0); actor.set_scale(1.0, 1.0);
actor.scale_gravity = Clutter.Gravity.CENTER;
actor.show(); actor.show();
this._destroying.push(actor); this._destroying.push(actor);
@ -460,13 +465,11 @@ const WindowManager = new Lang.Class({
this._switchData = switchData; this._switchData = switchData;
switchData.inGroup = new Clutter.Group(); switchData.inGroup = new Clutter.Group();
switchData.outGroup = new Clutter.Group(); switchData.outGroup = new Clutter.Group();
switchData.movingWindowBin = new Clutter.Group();
switchData.windows = []; switchData.windows = [];
let wgroup = global.window_group; let wgroup = global.window_group;
wgroup.add_actor(switchData.inGroup); wgroup.add_actor(switchData.inGroup);
wgroup.add_actor(switchData.outGroup); wgroup.add_actor(switchData.outGroup);
wgroup.add_actor(switchData.movingWindowBin);
for (let i = 0; i < windows.length; i++) { for (let i = 0; i < windows.length; i++) {
let window = windows[i]; let window = windows[i];
@ -474,12 +477,7 @@ const WindowManager = new Lang.Class({
if (!window.meta_window.showing_on_its_workspace()) if (!window.meta_window.showing_on_its_workspace())
continue; continue;
if (this._movingWindow && window.meta_window == this._movingWindow) { if (window.get_workspace() == from) {
switchData.movingWindow = { window: window,
parent: window.get_parent() };
switchData.windows.push(switchData.movingWindow);
window.reparent(switchData.movingWindowBin);
} else if (window.get_workspace() == from) {
switchData.windows.push({ window: window, switchData.windows.push({ window: window,
parent: window.get_parent() }); parent: window.get_parent() });
window.reparent(switchData.outGroup); window.reparent(switchData.outGroup);
@ -494,8 +492,6 @@ const WindowManager = new Lang.Class({
switchData.inGroup.set_position(-xDest, -yDest); switchData.inGroup.set_position(-xDest, -yDest);
switchData.inGroup.raise_top(); switchData.inGroup.raise_top();
switchData.movingWindowBin.raise_top();
Tweener.addTween(switchData.outGroup, Tweener.addTween(switchData.outGroup,
{ x: xDest, { x: xDest,
y: yDest, y: yDest,
@ -533,10 +529,6 @@ const WindowManager = new Lang.Class({
Tweener.removeTweens(switchData.outGroup); Tweener.removeTweens(switchData.outGroup);
switchData.inGroup.destroy(); switchData.inGroup.destroy();
switchData.outGroup.destroy(); switchData.outGroup.destroy();
switchData.movingWindowBin.destroy();
if (this._movingWindow)
this._movingWindow = null;
shellwm.completed_switch_workspace(); shellwm.completed_switch_workspace();
}, },
@ -544,7 +536,7 @@ const WindowManager = new Lang.Class({
_startAppSwitcher : function(display, screen, window, binding) { _startAppSwitcher : function(display, screen, window, binding) {
/* prevent a corner case where both popups show up at once */ /* prevent a corner case where both popups show up at once */
if (this._workspaceSwitcherPopup != null) if (this._workspaceSwitcherPopup != null)
this._workspaceSwitcherPopup.destroy(); this._workspaceSwitcherPopup.actor.hide();
let tabPopup = new AltTab.AltTabPopup(); let tabPopup = new AltTab.AltTabPopup();
@ -568,56 +560,76 @@ const WindowManager = new Lang.Class({
if (screen.n_workspaces == 1) if (screen.n_workspaces == 1)
return; return;
let [action,,,direction] = binding.get_name().split('-'); if (this._workspaceSwitcherPopup == null)
let direction = Meta.MotionDirection[direction.toUpperCase()]; this._workspaceSwitcherPopup = new WorkspaceSwitcherPopup.WorkspaceSwitcherPopup();
let newWs;
if (binding.get_name() == 'switch-to-workspace-up')
if (direction != Meta.MotionDirection.UP && this.actionMoveWorkspaceUp();
direction != Meta.MotionDirection.DOWN) else if (binding.get_name() == 'switch-to-workspace-down')
return; this.actionMoveWorkspaceDown();
// left/right would effectively act as synonyms for up/down if we enabled them;
if (action == 'switch') // but that could be considered confusing.
newWs = this.actionMoveWorkspace(direction); // else if (binding.get_name() == 'switch-to-workspace-left')
else // this.actionMoveWorkspaceLeft();
newWs = this.actionMoveWindow(window, direction); // else if (binding.get_name() == 'switch-to-workspace-right')
// this.actionMoveWorkspaceRight();
if (!Main.overview.visible) {
if (this._workspaceSwitcherPopup == null) {
this._workspaceSwitcherPopup = new WorkspaceSwitcherPopup.WorkspaceSwitcherPopup();
this._workspaceSwitcherPopup.connect('destroy', Lang.bind(this, function() {
this._workspaceSwitcherPopup = null;
}));
}
this._workspaceSwitcherPopup.display(direction, newWs.index());
}
}, },
actionMoveWorkspace: function(direction) { actionMoveWorkspaceLeft: function() {
let activeWorkspace = global.screen.get_active_workspace(); let rtl = (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL);
let toActivate = activeWorkspace.get_neighbor(direction); let activeWorkspaceIndex = global.screen.get_active_workspace_index();
let indexToActivate = activeWorkspaceIndex;
if (rtl && activeWorkspaceIndex < global.screen.n_workspaces - 1)
indexToActivate++;
else if (!rtl && activeWorkspaceIndex > 0)
indexToActivate--;
if (activeWorkspace != toActivate) if (indexToActivate != activeWorkspaceIndex)
toActivate.activate(global.get_current_time()); global.screen.get_workspace_by_index(indexToActivate).activate(global.get_current_time());
return toActivate; if (!Main.overview.visible)
this._workspaceSwitcherPopup.display(WorkspaceSwitcherPopup.UP, indexToActivate);
}, },
actionMoveWindow: function(window, direction) { actionMoveWorkspaceRight: function() {
let activeWorkspace = global.screen.get_active_workspace(); let rtl = (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL);
let toActivate = activeWorkspace.get_neighbor(direction); let activeWorkspaceIndex = global.screen.get_active_workspace_index();
let indexToActivate = activeWorkspaceIndex;
if (rtl && activeWorkspaceIndex > 0)
indexToActivate--;
else if (!rtl && activeWorkspaceIndex < global.screen.n_workspaces - 1)
indexToActivate++;
if (activeWorkspace != toActivate) { if (indexToActivate != activeWorkspaceIndex)
// This won't have any effect for "always sticky" windows global.screen.get_workspace_by_index(indexToActivate).activate(global.get_current_time());
// (like desktop windows or docks)
this._movingWindow = window; if (!Main.overview.visible)
window.change_workspace(toActivate); this._workspaceSwitcherPopup.display(WorkspaceSwitcherPopup.DOWN, indexToActivate);
global.display.clear_mouse_mode();
toActivate.activate_with_focus (window, global.get_current_time());
}
return toActivate;
}, },
actionMoveWorkspaceUp: function() {
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
let indexToActivate = activeWorkspaceIndex;
if (activeWorkspaceIndex > 0)
indexToActivate--;
if (indexToActivate != activeWorkspaceIndex)
global.screen.get_workspace_by_index(indexToActivate).activate(global.get_current_time());
if (!Main.overview.visible)
this._workspaceSwitcherPopup.display(WorkspaceSwitcherPopup.UP, indexToActivate);
},
actionMoveWorkspaceDown: function() {
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
let indexToActivate = activeWorkspaceIndex;
if (activeWorkspaceIndex < global.screen.n_workspaces - 1)
indexToActivate++;
if (indexToActivate != activeWorkspaceIndex)
global.screen.get_workspace_by_index(indexToActivate).activate(global.get_current_time());
if (!Main.overview.visible)
this._workspaceSwitcherPopup.display(WorkspaceSwitcherPopup.DOWN, indexToActivate);
}
}); });

Some files were not shown because too many files have changed in this diff Show More