Compare commits
237 Commits
goa-client
...
3.1.4
Author | SHA1 | Date | |
---|---|---|---|
60612cace9 | |||
f057502834 | |||
21a1149532 | |||
6724ca4f63 | |||
7a6c25b3fb | |||
0366e320af | |||
f03793b825 | |||
0907f030b4 | |||
7542e68b6f | |||
9003a34285 | |||
3cc27eaabe | |||
601b081bf3 | |||
4405d993c9 | |||
361308eb1b | |||
d894dedaf6 | |||
d12dd1491f | |||
1625591598 | |||
a50c30a4fd | |||
02bfc74c1e | |||
a012dd838d | |||
e2b634e59a | |||
80b98d8787 | |||
bf2ba83cd5 | |||
727c43dbab | |||
203dedfb3a | |||
8d5d4159a3 | |||
cecba0269f | |||
3c878793b5 | |||
0549f42030 | |||
50f248ec5b | |||
544bdb4fb1 | |||
88b295ff9f | |||
602326bb57 | |||
31857cf05f | |||
896d8e830c | |||
5f86e29830 | |||
579ae59eca | |||
90db743cc9 | |||
fe82897064 | |||
0f49f36519 | |||
a92b7342ba | |||
86818e9c52 | |||
7a8a00c705 | |||
c8d5e0a51c | |||
524a7ff6fb | |||
5f6ac33d59 | |||
b4f5e4206d | |||
5133c3e010 | |||
0b77ea422a | |||
5c1dd4ea18 | |||
e1c687184e | |||
297d1356a2 | |||
98327b0c13 | |||
6786aee5ed | |||
0b9c726b4e | |||
436dd6ee8c | |||
acc053302d | |||
534b371d42 | |||
6004e3d2e1 | |||
c632074ba7 | |||
664245d2a6 | |||
fda4fc674d | |||
bf28c24c82 | |||
32df0e80ca | |||
297eab738f | |||
5819dd3a5a | |||
a007b1bb2d | |||
5cb43b6bae | |||
f524138a64 | |||
0aa626b2fb | |||
3ae1050f67 | |||
67736642f3 | |||
09f3c87d20 | |||
9deca75de8 | |||
5bc1dede81 | |||
6d53d43766 | |||
325462d9bf | |||
ee4ae62946 | |||
153768ef7f | |||
7249a218ba | |||
de348a4c49 | |||
fda8ffd9ef | |||
6cb707cc4f | |||
998c5f17fc | |||
c34b357051 | |||
40f4e92461 | |||
c727da823b | |||
8d1b7962d8 | |||
a6dfe20348 | |||
ed46390bbc | |||
80eb5bf535 | |||
7596fdb460 | |||
8db1ff8aef | |||
1dfffdbc4e | |||
64b2b4a7d4 | |||
ae35d0e43c | |||
b22c5eb167 | |||
6d5e414863 | |||
3e74dfb66d | |||
1245628521 | |||
c567690004 | |||
2feff4207b | |||
8c40b6f9a7 | |||
fc759bff77 | |||
3581c1d5b4 | |||
64e7459d1c | |||
f71afa9248 | |||
1ebca2e6d5 | |||
6222796b6a | |||
dcecf41d18 | |||
9d1fbffe75 | |||
6a6ba94bb9 | |||
ef5de3a5c0 | |||
f042e43990 | |||
620330db8f | |||
3765acc0a5 | |||
2e8654b96c | |||
cc5c5e7e8a | |||
9c849b0290 | |||
ad314b362e | |||
737a4f2816 | |||
96e4128e3e | |||
6f515f2327 | |||
cbb9a7ab96 | |||
e2e3b29ed1 | |||
82a8ac1976 | |||
bfec396ec2 | |||
d0e7f880d7 | |||
ff81659b9e | |||
6b2b3475c8 | |||
ed8acefc00 | |||
737f395d6c | |||
ab0a5d4a53 | |||
9412ac2027 | |||
c8670819dc | |||
4132ccae33 | |||
c450120409 | |||
6ce07abf50 | |||
e39e539ee9 | |||
9563a8f8a0 | |||
d5f37fa280 | |||
5c6e5ef6d0 | |||
42adc38609 | |||
e5fadb3b42 | |||
4c5f3aa971 | |||
6fc49b79a4 | |||
9e804b082f | |||
1fb220beb7 | |||
1fc1282fad | |||
8834a7df10 | |||
a34071e1c3 | |||
52a342300a | |||
b9456caeb0 | |||
26aa4333a5 | |||
04d2b0d282 | |||
7def1a4aa5 | |||
001b9868fb | |||
59a3e393f9 | |||
b846354787 | |||
2674d96e54 | |||
0c98fe8546 | |||
f18ed3c6ae | |||
644acf7018 | |||
96dca48b3b | |||
1309b64c33 | |||
1301dee744 | |||
efc194e36c | |||
60f41a109f | |||
890efa787a | |||
60c88612f7 | |||
82648bc86c | |||
ea1e5a5210 | |||
aa03734d39 | |||
bb0f76f562 | |||
8c2a290d09 | |||
8e661c3780 | |||
a03b6c419a | |||
fb55dd677f | |||
408790a630 | |||
08371f073d | |||
e7289378b7 | |||
31bde574de | |||
5aacfe4e6e | |||
5cf7bdabfd | |||
2021edd1fb | |||
45c1a9eafb | |||
0ae1556b94 | |||
c0739bd1e3 | |||
af51e8df7b | |||
22ef63cc44 | |||
03a5133e8c | |||
7f2456c00d | |||
aa4dbee362 | |||
7b65735cc9 | |||
7ef35fbec7 | |||
8b10d85fee | |||
d99b1e6c09 | |||
57ab7aec5b | |||
29a221bf62 | |||
671c569a9e | |||
492c033760 | |||
33a3b8046d | |||
88df183450 | |||
6a9080c3d6 | |||
019670b5ab | |||
4cab0c95d3 | |||
d51e79d483 | |||
69a27911a7 | |||
dd48514b24 | |||
eb54662098 | |||
545f0432c8 | |||
fdefb317cb | |||
4e0c8bfe67 | |||
dbd629d5d2 | |||
619a44a499 | |||
c5ca4e3ff0 | |||
55771b437b | |||
8727680983 | |||
ab9f21351f | |||
0055cabc62 | |||
fc70c2246b | |||
f7b6acaff8 | |||
91caa8e59f | |||
4c44bb7f52 | |||
df8a735aa9 | |||
82aea3e8d6 | |||
a8baf4a2a2 | |||
bfd344cdec | |||
a0fd4e195d | |||
4b3fbc4c9b | |||
63b1699a35 | |||
48acc41698 | |||
17672accfe | |||
986d72d9de | |||
ffd461cdb4 | |||
71dfab9711 | |||
c4f5274d74 |
8
.gitignore
vendored
8
.gitignore
vendored
@ -3,6 +3,7 @@
|
||||
*.o
|
||||
.deps
|
||||
.libs
|
||||
ABOUT-NLS
|
||||
ChangeLog
|
||||
INSTALL
|
||||
Makefile
|
||||
@ -29,8 +30,13 @@ m4/
|
||||
omf.make
|
||||
po/*.gmo
|
||||
po/gnome-shell.pot
|
||||
po/*.header
|
||||
po/*.sed
|
||||
po/*.sin
|
||||
po/Makefile.in.in
|
||||
po/Makevars.template
|
||||
po/POTFILES
|
||||
po/Rules-quot
|
||||
po/stamp-it
|
||||
scripts/launcher.pyc
|
||||
src/*.gir
|
||||
@ -43,9 +49,11 @@ src/calendar-server/org.gnome.Shell.CalendarServer.service
|
||||
src/gnome-shell
|
||||
src/gnome-shell-calendar-server
|
||||
src/gnome-shell-extension-tool
|
||||
src/gnome-shell-hotplug-sniffer
|
||||
src/gnome-shell-jhbuild
|
||||
src/gnome-shell-perf-helper
|
||||
src/gnome-shell-real
|
||||
src/hotplug-sniffer/org.gnome.Shell.HotplugSniffer.service
|
||||
src/run-js-test
|
||||
src/test-recorder
|
||||
src/test-recorder.ogg
|
||||
|
@ -3,5 +3,5 @@ E-mail: otaylor@redhat.com
|
||||
Userid: otaylor
|
||||
|
||||
Colin Walters
|
||||
E-mail: walters@redhat.com
|
||||
E-mail: walters@verbum.org
|
||||
Userid: walters
|
||||
|
163
NEWS
163
NEWS
@ -1,3 +1,166 @@
|
||||
3.1.4
|
||||
=====
|
||||
* Take over inserted media handling and autorun from gnome-session [Cosimo]
|
||||
* Message Tray
|
||||
- Display a count of unread notifications on icons
|
||||
[Jasper, Guillaume; #649356, #654139]
|
||||
- Only remove icons when the sender quits from D-Bus, not when it
|
||||
closes its last window [Neha, Marina; #645764]
|
||||
- Solve problems switching chats between shell and Empathy
|
||||
[Guillaume; #654237]
|
||||
- Fix handling of bad GMarkup in messages [Dan; #650298]
|
||||
- Never show notifications when the screensaver is active [Dan; #654550]
|
||||
* Telepathy integrationpp
|
||||
- Implement Telepathy Debug interface to enable empathy-debugger
|
||||
[Guillaume; #652816]
|
||||
- Allow approving room invitations, and audio/video calls
|
||||
[Guillaume; #653740 #653939]
|
||||
- Send typing notifications [Jonny; #650196]
|
||||
* Fix selection highlighting for light-on-dark entries [Jasper; #643768]
|
||||
* Make control-Return in the overview open a new window [Maxim]
|
||||
* Delay showing the alt-Tab switcher to reduce visual noise when
|
||||
flipping betweeen windows [Dan; #652346]
|
||||
* When we have vertically stacked monitors, put the message tray
|
||||
on the bottom one [Dan; #636963]
|
||||
* Fix various problems with keynav and the Activities button
|
||||
[Dan; #641253 #645759]
|
||||
* Ensure screensaver is locked when switching users [Colin; #654565]
|
||||
* Improve extension creation tool [Jasper; #653206]
|
||||
* Fix compatibility with latest GJS [Giovanni; #654349]
|
||||
* Code cleanups [Adel, Dan, Jasper; #645759 #654577 #654791 #654987]
|
||||
* Misc bug fixes [Richard, Dan, Florian, Giovanni, Jasper, Marc-Antoine, Rui;
|
||||
#647175 #649513 #650452 #651082 #653700 #653989 #654105 #654791 #654267
|
||||
#654269 #654527 #655446]
|
||||
* Build fixes [Florian, Siegfried; #654300]
|
||||
|
||||
Contributors:
|
||||
Giovanni Campagna, Cosimo Cecchi, Guillaume Desmottes, Neha Doijode,
|
||||
Maxim Ermilov, Adel Gadllah, Siegfried-Angel Gevatter Pujals, Richard Hughes,
|
||||
Jonny Lamb, Rui Matos, Florian Müllner, Marc-Antoine Perennou, Colin Walters,
|
||||
Dan Winship, Marina Zhurakhinskaya
|
||||
|
||||
Translations:
|
||||
Mario Blättermann, Paul Seyfert [de], Jorge González, Daniel Mustieles [es],
|
||||
Fran Dieguez [gl], Yaron Shahrabani [he], Luca Ferretti [it],
|
||||
Rudolfs Mazurs [lv], Kjartan Maraas [nb], A S Alam [pa], Yuri Kozlov [ru],
|
||||
Michal Štrba, Matej Urbančič [sl]
|
||||
|
||||
3.1.3
|
||||
=====
|
||||
* Fix problem with "user theme extension" breaking the CSS for other
|
||||
extensions [Giovanni; #650971]
|
||||
* Telepathy IM framework integration
|
||||
- Switch to using telepathy-glib rather than talking to
|
||||
Telepathy via D-Bus [Guillaume, Jasper; #645585, #649633, #651138, #651227]
|
||||
- Acknowledge messages when the user clicks on them [Guillaume, #647893]
|
||||
- Fix problem with telepathy icon blinking for incoming messages
|
||||
even though the user has been notified of them [Guillaume; #643594]
|
||||
* Networking
|
||||
- keep wirelesss networks in predictable order [Giovanni; #646580, #652313]
|
||||
- Show unmanaged devices in the menu [Giovanni; #646946]
|
||||
- Fix overflow when too many VPN connections [Giovanni; #651602]
|
||||
* Bluetooth
|
||||
- Show "hardware disabled" when disabled by rfkill [Giovanni; #648048]
|
||||
- Fix bug updating status of devices [Giovanni; #647565]
|
||||
* LookingGlass console:
|
||||
- Add a "Memory" tab [Colin; #650692]
|
||||
- Make escape work from any page [Dan Winship; #647303]
|
||||
- Provide a way to refer to panel items as, e.g.,
|
||||
Main.panel._activities [Dan Winship; #646915]
|
||||
* User menu
|
||||
- Fix problem with suspend menu option locking the screen even when the user
|
||||
disabled that. [Florian; #652327]
|
||||
- Hide "power off..." option if shutdown is disabled via PolicyKit
|
||||
[Florian; #652038]
|
||||
* Track changes to WM_CLASS (fixes problems with LibreOffice tracking)
|
||||
[Colin; #649315]
|
||||
* Remove app tracking workarounds for Firefox and LibreOffice [Colin; #651015]
|
||||
* Use upstream gettext autoconfigury rather than glib version [Javier; #631576]
|
||||
* Show messages in the message tray when an application is fullscreen
|
||||
[Dan Winship; #608667]
|
||||
* Don't autohide the workspace pager if there is more than one workspace
|
||||
[Florian; #652714, #653078, #653142]
|
||||
* Don't always slide out the workspace pager at drag begin [Florian; #652730]
|
||||
* Only offer to remove a favorite app when dragging it's icon [Owen; #642895]
|
||||
* Allow dropping an icon anywhere on a workspace [Adel; #652079]
|
||||
* st-scroll-view: Make the fade effect and offset themable [Jasper; #651813]
|
||||
* Obey the user's preference when running an application in a terminal
|
||||
from the run dialog [Florian; #648422]
|
||||
* Consistently exit overview when launching external applications
|
||||
[Colin; #653095]
|
||||
* Adapt to changes in GJS for how GObject APIs are bound
|
||||
[Alex, Colin, Florian, Jasper, Marc-Antoine; #649981, #652597]
|
||||
* Fix problems with scrolling in overflow for alt-Tab switcher
|
||||
[Dan Winship, Adel; #647807]
|
||||
* Mark relationships between labels and actors for accessibility [Alejandro]
|
||||
* Add org.gnome.shell.enabled-extensions complementing disabled-extensions
|
||||
GSetting [Tassilo; #651088]
|
||||
* Visual tweaks [Jakub, Jasper; #646261, #652715]
|
||||
* Switch to building against clutter-1.7 with independent Cogl [Adel; #653397]
|
||||
* Code cleanups [Colin, Dan Winship, Florian; #633620, #645031, #648755, #648758,
|
||||
#648760, #649203, #649517, #650317, #652730]
|
||||
* Memory leak fixes [Colin, Maxim; #649508, #650934]
|
||||
* Build Fixes [Colin, Dan Winship, Florian, Ionut, Morten, Owen, Sean; #647395,
|
||||
#648006, #650869, #653199, #653275
|
||||
* Miscellaneous bug fixes [Adam, Adel, Dan Williams, Dan Winship, Florian,
|
||||
Ionut, Jasper, Maxim, Ray; #620105, #639459, #641570, #642793, #643513,
|
||||
#645848, #646919, #647186, #648305, #648410, #648562, #648894, #649001,
|
||||
#645990, #647893, #647907, #651012, #651086, #651606, #651569, #651866,
|
||||
#652388, #653511]
|
||||
|
||||
Contributors:
|
||||
Ionut Biru, Giovanni Campagna, Guillaume Desmottes, Adam Dingle,
|
||||
Maxim Ermilov, Adel Gadllah, Tassilo Horn, Javier Jardón, Jonny Lamb,
|
||||
Alexander Larsson, Rui Matos, Morten Mjelva, Florian Müllner,
|
||||
Marc-Antoine Perennou, Alejandro Piñeiro, Jasper St. Pierre, Jakub Steiner,
|
||||
Ray Strode, Owen Taylor, Colin Walters, Dan Williams, Sean Wilson, Dan Winship
|
||||
|
||||
Translations:
|
||||
Daniel Martinez Cucalon [ar], Ihar Hrachyshka [be], Carles Ferrando,
|
||||
Gil Forcada, Sílvia Miranda [ca], Kristjan Schmidt [eo], Jorge González,
|
||||
Daniel Mustieles [es], Seán de Búrca [ga], Fran Diéguez [gl],
|
||||
Yaron Shahrabani [he], Kjartan Maraas [nb], Misha Shnurapet,
|
||||
Yuri Myasoedov [ru], Daniel Nylander [se], Peter Mráz [sk],
|
||||
Matej Urbančič [sl], Krishnababu Krothapalli [te], Daniel Korostil [uk],
|
||||
Aron Xu [zh_CN]
|
||||
|
||||
3.0.2
|
||||
=====
|
||||
* Network Menu [Dan Williams]
|
||||
- Fix connecting to WPA2 Enterprise access points
|
||||
Fixes https://bugzilla.gnome.org/show_bug.cgi?id=648171
|
||||
- Show the mobile broadband wizard when selecting 3G network
|
||||
Fixes https://bugzilla.gnome.org/show_bug.cgi?id=649318
|
||||
- Miscellaneous bug fixes
|
||||
648648, 650124
|
||||
* Fix duplicate icons in the application browser [Owen]
|
||||
https://bugzilla.gnome.org/show_bug.cgi?id=648739
|
||||
* Make clicking anywhere on the volume icon slider work [Giovanni]
|
||||
https://bugzilla.gnome.org/show_bug.cgi?id=646660
|
||||
* Fix a case where activating and clicking the hot corner
|
||||
at the same time could result in immediately leaving the
|
||||
overview [Rui]
|
||||
https://bugzilla.gnome.org/show_bug.cgi?id=649427
|
||||
* Fix a case where applications became misordered in Alt-Tab [Jasper]
|
||||
https://bugzilla.gnome.org/show_bug.cgi?id=643302
|
||||
* Fix a bug where messages you send could show up in
|
||||
notifications as if someone else sent them [Jonny]
|
||||
https://bugzilla.gnome.org/show_bug.cgi?id=650219
|
||||
* Memory leak fixes [Colin, Maxim]
|
||||
642652, 649508, 649497
|
||||
* Miscellaneous minor bug fixes [Adel, Christopher, Jasper]
|
||||
649596, 648765, 648983, 649632
|
||||
|
||||
Contributors:
|
||||
Christopher Aillon, Giovanni Campagna, Maxim Ermilov,
|
||||
Adel Gadllah, Jonny Lamb, Rui Matos, Jasper St. Pierre,
|
||||
Owen Taylor, Colin Walters, Dan Williams
|
||||
|
||||
Translations:
|
||||
Arash Mousavi [fa], Seán de Búrca [ga], Timo Jyrinki [fi],
|
||||
Sigurd Gartmann [nb], Daniel Nylander [se], Peter Mráz [sl],
|
||||
Abduxukur Abdurixit [ug], Nguyễn Thái Ngọc Duy [vi]
|
||||
|
||||
3.0.1
|
||||
=====
|
||||
|
||||
|
43
configure.ac
43
configure.ac
@ -1,5 +1,5 @@
|
||||
AC_PREREQ(2.63)
|
||||
AC_INIT([gnome-shell],[3.0.1],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell])
|
||||
AC_INIT([gnome-shell],[3.1.4],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell])
|
||||
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
AC_CONFIG_SRCDIR([src/shell-global.c])
|
||||
@ -23,12 +23,16 @@ AM_PROG_CC_C_O
|
||||
LT_PREREQ([2.2.6])
|
||||
LT_INIT([disable-static])
|
||||
|
||||
# i18n
|
||||
IT_PROG_INTLTOOL([0.40])
|
||||
|
||||
AM_GNU_GETTEXT([external])
|
||||
AM_GNU_GETTEXT_VERSION([0.17])
|
||||
|
||||
GETTEXT_PACKAGE=gnome-shell
|
||||
AC_SUBST(GETTEXT_PACKAGE)
|
||||
AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE",
|
||||
[The prefix for our gettext translation domains.])
|
||||
IT_PROG_INTLTOOL(0.26)
|
||||
AM_GLIB_GNU_GETTEXT
|
||||
|
||||
PKG_PROG_PKG_CONFIG([0.22])
|
||||
|
||||
@ -60,19 +64,19 @@ fi
|
||||
|
||||
AM_CONDITIONAL(BUILD_RECORDER, $build_recorder)
|
||||
|
||||
CLUTTER_MIN_VERSION=1.5.15
|
||||
CLUTTER_MIN_VERSION=1.7.5
|
||||
GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1
|
||||
GJS_MIN_VERSION=0.7.11
|
||||
GJS_MIN_VERSION=1.29.15
|
||||
MUTTER_MIN_VERSION=3.0.0
|
||||
GTK_MIN_VERSION=3.0.0
|
||||
GIO_MIN_VERSION=2.25.9
|
||||
LIBECAL_MIN_VERSION=2.32.0
|
||||
LIBEDATASERVER_MIN_VERSION=1.2.0
|
||||
LIBEDATASERVERUI2_MIN_VERSION=1.2.0
|
||||
LIBEDATASERVERUI3_MIN_VERSION=2.91.6
|
||||
TELEPATHY_GLIB_MIN_VERSION=0.13.12
|
||||
LIBEDATASERVERUI_MIN_VERSION=2.91.6
|
||||
TELEPATHY_GLIB_MIN_VERSION=0.15.3
|
||||
TELEPATHY_LOGGER_MIN_VERSION=0.2.4
|
||||
POLKIT_MIN_VERSION=0.100
|
||||
STARTUP_NOTIFICATION_MIN_VERSION=0.11
|
||||
|
||||
# Collect more than 20 libraries for a prize!
|
||||
PKG_CHECK_MODULES(GNOME_SHELL, gio-2.0 >= $GIO_MIN_VERSION
|
||||
@ -81,10 +85,10 @@ PKG_CHECK_MODULES(GNOME_SHELL, gio-2.0 >= $GIO_MIN_VERSION
|
||||
libmutter >= $MUTTER_MIN_VERSION
|
||||
gjs-internals-1.0 >= $GJS_MIN_VERSION
|
||||
libgnome-menu $recorder_modules gconf-2.0
|
||||
gdk-x11-3.0
|
||||
gdk-x11-3.0 libsoup-2.4
|
||||
clutter-x11-1.0 >= $CLUTTER_MIN_VERSION
|
||||
clutter-glx-1.0 >= $CLUTTER_MIN_VERSION
|
||||
libstartup-notification-1.0
|
||||
libstartup-notification-1.0 >= $STARTUP_NOTIFICATION_MIN_VERSION
|
||||
gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION
|
||||
libcanberra
|
||||
telepathy-glib >= $TELEPATHY_GLIB_MIN_VERSION
|
||||
@ -93,6 +97,8 @@ PKG_CHECK_MODULES(GNOME_SHELL, gio-2.0 >= $GIO_MIN_VERSION
|
||||
|
||||
PKG_CHECK_MODULES(SHELL_PERF_HELPER, gtk+-3.0 gio-2.0)
|
||||
|
||||
PKG_CHECK_MODULES(SHELL_HOTPLUG_SNIFFER, gio-2.0 gdk-pixbuf-2.0)
|
||||
|
||||
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"])
|
||||
@ -111,8 +117,7 @@ saved_CFLAGS=$CFLAGS
|
||||
saved_LIBS=$LIBS
|
||||
CFLAGS=$GNOME_SHELL_CFLAGS
|
||||
LIBS=$GNOME_SHELL_LIBS
|
||||
# sn_startup_sequence_get_application_id, we can replace with a version check later
|
||||
AC_CHECK_FUNCS(JS_NewGlobalObject sn_startup_sequence_get_application_id XFixesCreatePointerBarrier)
|
||||
AC_CHECK_FUNCS(JS_NewGlobalObject XFixesCreatePointerBarrier)
|
||||
CFLAGS=$saved_CFLAGS
|
||||
LIBS=$saved_LIBS
|
||||
|
||||
@ -123,9 +128,9 @@ PKG_CHECK_MODULES(GVC, libpulse libpulse-mainloop-glib gobject-2.0)
|
||||
PKG_CHECK_MODULES(DESKTOP_SCHEMAS, gsettings-desktop-schemas >= 0.1.7)
|
||||
|
||||
AC_MSG_CHECKING([for bluetooth support])
|
||||
PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 2.90.0],
|
||||
[BLUETOOTH_DIR=`$PKG_CONFIG --variable=libdir gnome-bluetooth-1.0`/gnome-bluetooth
|
||||
BLUETOOTH_LIBS="-L'$BLUETOOTH_DIR' -lgnome-bluetooth-applet"
|
||||
PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 3.1.0],
|
||||
[BLUETOOTH_DIR=`$PKG_CONFIG --variable=applet_libdir gnome-bluetooth-1.0`
|
||||
BLUETOOTH_LIBS=`$PKG_CONFIG --variable=applet_libs gnome-bluetooth-1.0`
|
||||
AC_SUBST([BLUETOOTH_LIBS],["$BLUETOOTH_LIBS"])
|
||||
AC_DEFINE_UNQUOTED([BLUETOOTH_DIR],["$BLUETOOTH_DIR"],[Path to installed GnomeBluetooth typelib and library])
|
||||
AC_DEFINE([HAVE_BLUETOOTH],[1],[Define if you have libgnome-bluetooth-applet])
|
||||
@ -135,13 +140,7 @@ PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 2.90.0],
|
||||
AC_SUBST([HAVE_BLUETOOTH],[0])
|
||||
AC_MSG_RESULT([no])])
|
||||
|
||||
# Default to libedataserverui-3.0, but allow falling back to 1.2
|
||||
PKG_CHECK_EXISTS(libedataserverui-3.0,
|
||||
[EDS_API=3.0
|
||||
LIBEDATASERVERUI_MIN_VERSION=$LIBEDATASERVERUI3_MIN_VERSION],
|
||||
[EDS_API=1.2
|
||||
LIBEDATASERVERUI_MIN_VERSION=$LIBEDATASERVERUI2_MIN_VERSION])
|
||||
PKG_CHECK_MODULES(CALENDAR_SERVER, libecal-1.2 >= $LIBECAL_MIN_VERSION libedataserver-1.2 >= $LIBEDATASERVER_MIN_VERSION libedataserverui-$EDS_API >= $LIBEDATASERVERUI_MIN_VERSION gio-2.0)
|
||||
PKG_CHECK_MODULES(CALENDAR_SERVER, libecal-1.2 >= $LIBECAL_MIN_VERSION libedataserver-1.2 >= $LIBEDATASERVER_MIN_VERSION libedataserverui-3.0 >= $LIBEDATASERVERUI_MIN_VERSION gio-2.0)
|
||||
AC_SUBST(CALENDAR_SERVER_CFLAGS)
|
||||
AC_SUBST(CALENDAR_SERVER_LIBS)
|
||||
|
||||
|
@ -15,8 +15,18 @@
|
||||
<default>[]</default>
|
||||
<_summary>Uuids of extensions to disable</_summary>
|
||||
<_description>
|
||||
GNOME Shell extensions have a uuid property;
|
||||
this key lists extensions which should not be loaded.
|
||||
GNOME Shell extensions have a uuid property; this key lists extensions
|
||||
which should not be loaded. This setting overrides enabled-extensions
|
||||
for extensions that appear in both lists.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="enabled-extensions" type="as">
|
||||
<default>[]</default>
|
||||
<_summary>Uuids of extensions to enable</_summary>
|
||||
<_description>
|
||||
GNOME Shell extensions have a uuid property; this key lists extensions
|
||||
which should be loaded. disabled-extensions overrides this setting for
|
||||
extensions that appear in both lists.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="enable-app-monitoring" type="b">
|
||||
|
@ -39,6 +39,11 @@ StScrollBar
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
StScrollView.vfade
|
||||
{
|
||||
-st-vfade-offset: 68px;
|
||||
}
|
||||
|
||||
StScrollView StScrollBar
|
||||
{
|
||||
min-width: 16px;
|
||||
@ -182,6 +187,7 @@ StTooltip StLabel {
|
||||
}
|
||||
|
||||
.popup-inactive-menu-item {
|
||||
font-weight: normal;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
@ -258,7 +264,7 @@ StTooltip StLabel {
|
||||
}
|
||||
|
||||
.panel-corner:active,
|
||||
.panel-corner:checked,
|
||||
.panel-corner:overview,
|
||||
.panel-corner:focus {
|
||||
-panel-corner-inner-border-color: rgba(255,255,255,0.8);
|
||||
}
|
||||
@ -295,7 +301,7 @@ StTooltip StLabel {
|
||||
}
|
||||
|
||||
.panel-button:active,
|
||||
.panel-button:checked,
|
||||
.panel-button:overview,
|
||||
.panel-button:focus {
|
||||
border-image: url("panel-button-border.svg") 10 10 0 2;
|
||||
background-image: url("panel-button-highlight-wide.svg");
|
||||
@ -458,6 +464,7 @@ StTooltip StLabel {
|
||||
background-gradient-start: rgba(5,5,6,0.1);
|
||||
background-gradient-end: rgba(254,254,254,0.1);
|
||||
background-gradient-direction: vertical;
|
||||
selected-color: black;
|
||||
caret-color: rgb(128, 128, 128);
|
||||
caret-size: 1px;
|
||||
width: 250px;
|
||||
@ -718,6 +725,8 @@ StTooltip StLabel {
|
||||
.lg-dialog StEntry
|
||||
{
|
||||
color: #88ff66;
|
||||
selection-background-color: #88ff66;
|
||||
selected-color: black;
|
||||
}
|
||||
|
||||
.lg-obj-inspector-title
|
||||
@ -1130,6 +1139,83 @@ StTooltip StLabel {
|
||||
icon-size: 36px;
|
||||
}
|
||||
|
||||
.hotplug-transient-box {
|
||||
spacing: 6px;
|
||||
padding: 2px 72px 2px 12px;
|
||||
}
|
||||
|
||||
.hotplug-notification-item {
|
||||
background-color: #3c3c3c;
|
||||
padding: 0px 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #181818;
|
||||
}
|
||||
|
||||
.hotplug-notification-item:hover {
|
||||
border: 1px solid #a1a1a1;
|
||||
}
|
||||
|
||||
.hotplug-notification-item:focus {
|
||||
background-color: #666666;
|
||||
}
|
||||
|
||||
.hotplug-notification-item:active {
|
||||
border: 1px solid #a1a1a1;
|
||||
background-color: #2b2b2b;
|
||||
}
|
||||
|
||||
.hotplug-notification-item-icon {
|
||||
icon-size: 24px;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
.hotplug-resident-box {
|
||||
spacing: 8px;
|
||||
}
|
||||
|
||||
.hotplug-resident-mount {
|
||||
spacing: 8px;
|
||||
border-radius: 4px;
|
||||
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.hotplug-resident-mount:hover {
|
||||
background-gradient-direction: horizontal;
|
||||
background-gradient-start: rgba(255, 255, 255, 0.1);
|
||||
background-gradient-end: rgba(255, 255, 255, 0);
|
||||
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.hotplug-resident-mount-label {
|
||||
color: inherit;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
.hotplug-resident-mount-icon {
|
||||
icon-size: 24px;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
.hotplug-resident-eject-icon {
|
||||
icon-size: 16px;
|
||||
}
|
||||
|
||||
.hotplug-resident-eject-button {
|
||||
padding: 2px;
|
||||
border: 1px solid #2b2b2b;
|
||||
border-radius: 5px;
|
||||
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.hotplug-resident-eject-button:hover {
|
||||
color: #fff;
|
||||
background-color: #2b2b2b;
|
||||
border: 1px solid #a1a1a1;
|
||||
}
|
||||
|
||||
.chat-log-message {
|
||||
color: #888888;
|
||||
}
|
||||
@ -1189,6 +1275,8 @@ StTooltip StLabel {
|
||||
color: #545454;
|
||||
background-color: #e8e8e8;
|
||||
caret-color: #545454;
|
||||
selection-background-color: #bcbcbc;
|
||||
selected-color: #323232;
|
||||
box-shadow: 0px 0px 6px 2px rgba(255,255,255,0.9);
|
||||
}
|
||||
|
||||
@ -1255,6 +1343,16 @@ StTooltip StLabel {
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.summary-source-counter {
|
||||
color: white;
|
||||
background-color: #3465A4;
|
||||
text-shadow: black 1px 1px 0;
|
||||
font-size: 9pt;
|
||||
border-radius: 1em;
|
||||
min-height: 1em;
|
||||
min-width: 1em;
|
||||
}
|
||||
|
||||
.source-title {
|
||||
font-size: 9pt;
|
||||
font-weight: bold;
|
||||
@ -1470,6 +1568,8 @@ StTooltip StLabel {
|
||||
font-weight: bold;
|
||||
width: 23em;
|
||||
color: white;
|
||||
selection-background-color: white;
|
||||
selected-color: black;
|
||||
}
|
||||
|
||||
.run-dialog {
|
||||
@ -1573,6 +1673,90 @@ StTooltip StLabel {
|
||||
color: #444444;
|
||||
}
|
||||
|
||||
/* ShellMountOperation Dialogs */
|
||||
.shell-mount-operation-icon {
|
||||
icon-size: 48px;
|
||||
}
|
||||
|
||||
.mount-password-reask {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.show-processes-dialog,
|
||||
.mount-question-dialog {
|
||||
spacing: 24px;
|
||||
}
|
||||
|
||||
.show-processes-dialog-subject,
|
||||
.mount-question-dialog-subject {
|
||||
font-size: 12pt;
|
||||
font-weight: bold;
|
||||
color: #666666;
|
||||
padding-top: 10px;
|
||||
padding-left: 17px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.show-processes-dialog-subject:rtl,
|
||||
.mount-question-dialog-subject:rtl {
|
||||
padding-left: 0px;
|
||||
padding-right: 17px;
|
||||
}
|
||||
|
||||
.show-processes-dialog-description,
|
||||
.mount-question-dialog-description {
|
||||
font-size: 10pt;
|
||||
color: white;
|
||||
padding-left: 17px;
|
||||
width: 28em;
|
||||
}
|
||||
|
||||
.show-processes-dialog-description:rtl,
|
||||
.mount-question-dialog-description:rtl {
|
||||
padding-right: 17px;
|
||||
}
|
||||
|
||||
.show-processes-dialog-app-list {
|
||||
font-size: 10pt;
|
||||
max-height: 200px;
|
||||
padding-top: 24px;
|
||||
padding-left: 49px;
|
||||
padding-right: 32px;
|
||||
}
|
||||
|
||||
.show-processes-dialog-app-list:rtl {
|
||||
padding-right: 49px;
|
||||
padding-left: 32px;
|
||||
}
|
||||
|
||||
.show-processes-dialog-app-list-item {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.show-processes-dialog-app-list-item:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.show-processes-dialog-app-list-item:ltr {
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.show-processes-dialog-app-list-item:rtl {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.show-processes-dialog-app-list-item-icon:ltr {
|
||||
padding-right: 17px;
|
||||
}
|
||||
|
||||
.show-processes-dialog-app-list-item-icon:rtl {
|
||||
padding-left: 17px;
|
||||
}
|
||||
|
||||
.show-processes-dialog-app-list-item-name {
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
/* PolicyKit Authentication Dialog */
|
||||
.polkit-dialog {
|
||||
/* this is the width of the entire modal popup */
|
||||
|
@ -2,24 +2,62 @@
|
||||
<!-- 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="119.97824"
|
||||
height="119.97824"
|
||||
id="svg7355"
|
||||
version="1.1">
|
||||
version="1.1"
|
||||
inkscape:version="0.48.1 r9760"
|
||||
sodipodi:docname="running-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="1920"
|
||||
inkscape:window-height="1141"
|
||||
id="namedview4173"
|
||||
showgrid="false"
|
||||
inkscape:zoom="8.1348081"
|
||||
inkscape:cx="81.120662"
|
||||
inkscape:cy="58.117986"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="g30864" />
|
||||
<defs
|
||||
id="defs7357">
|
||||
<radialGradient
|
||||
xlink:href="#linearGradient36429"
|
||||
id="radialGradient7461"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.0525552,0,0,1.0525552,-2.5162753,-9.0000838)"
|
||||
cx="47.878681"
|
||||
cy="171.25"
|
||||
fx="47.878681"
|
||||
fy="171.25"
|
||||
gradientTransform="matrix(1.011539,0,0,0.57582113,-0.39262194,71.83807)"
|
||||
cx="47.428951"
|
||||
cy="167.16817"
|
||||
fx="47.428951"
|
||||
fy="167.16817"
|
||||
r="37" />
|
||||
<linearGradient
|
||||
id="linearGradient36429">
|
||||
@ -59,7 +97,7 @@
|
||||
fx="49.067139"
|
||||
cy="242.50381"
|
||||
cx="49.067139"
|
||||
gradientTransform="matrix(1.1891549,0,0,0.55513246,-9.281289,36.12653)"
|
||||
gradientTransform="matrix(1.1891549,0,0,0.15252127,-9.281289,132.52772)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient7488"
|
||||
xlink:href="#linearGradient36471" />
|
||||
@ -72,19 +110,21 @@
|
||||
id="g30864"
|
||||
transform="translate(255.223,70.118091)">
|
||||
<rect
|
||||
ry="3.5996203"
|
||||
rx="3.5996203"
|
||||
y="98"
|
||||
x="11"
|
||||
height="74"
|
||||
width="74"
|
||||
ry="3.4593496"
|
||||
rx="3.4593496"
|
||||
y="99.596962"
|
||||
x="12.596948"
|
||||
height="71.116341"
|
||||
width="71.116341"
|
||||
id="rect14000"
|
||||
style="opacity:0.371875;fill:url(#radialGradient7461);fill-opacity:1;stroke:none" />
|
||||
style="opacity:0.37187500000000001;fill:url(#radialGradient7461);fill-opacity:1;stroke:none" />
|
||||
<path
|
||||
id="rect34520"
|
||||
d="m 84.506708,167.95508 c 6e-6,1.96759 -1.584022,3.55162 -3.551629,3.55163 l -65.910146,0 c -1.967608,-1e-5 -3.551648,-1.58402 -3.551643,-3.55164"
|
||||
style="opacity:0.2;fill:none;stroke:url(#radialGradient7488);stroke-width:1;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0" />
|
||||
d="m 83.273151,166.72152 c 0,1.96759 -1.584022,3.55163 -3.551629,3.55163 l -63.443032,0 c -1.967608,0 -3.551648,-1.58402 -3.551643,-3.55164 0,-5.85318 0,-5.85318 0,0"
|
||||
style="opacity:0.35;fill:none;stroke:url(#radialGradient7488);stroke-width:1;stroke-opacity:1"
|
||||
connector-curvature="0"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccscc" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3.9 KiB |
@ -13,8 +13,8 @@
|
||||
height="22"
|
||||
id="svg3199"
|
||||
version="1.1"
|
||||
inkscape:version="0.47 r22583"
|
||||
sodipodi:docname="New document 11">
|
||||
inkscape:version="0.48.1 r9760"
|
||||
sodipodi:docname="toggle-on-intl.svg">
|
||||
<defs
|
||||
id="defs3201">
|
||||
<inkscape:perspective
|
||||
@ -39,14 +39,14 @@
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.35"
|
||||
inkscape:cx="32.500004"
|
||||
inkscape:cy="10.999997"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="49.147112"
|
||||
inkscape:cy="17.532036"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="609"
|
||||
inkscape:window-height="501"
|
||||
inkscape:window-width="1412"
|
||||
inkscape:window-height="1067"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="0" />
|
||||
@ -58,7 +58,7 @@
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
@ -72,7 +72,7 @@
|
||||
transform="translate(-453.5,448.36218)"
|
||||
id="g16453">
|
||||
<rect
|
||||
style="color:#000000;fill:#204a87;fill-opacity:1;fill-rule:nonzero;stroke:#3465a4;stroke-width:0.99999994;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;enable-background:accumulate"
|
||||
style="color:#000000;fill:#4a90d9;fill-opacity:1;fill-rule:nonzero;stroke:#3465a4;stroke-width:0.99999994000000003;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;enable-background:accumulate"
|
||||
id="rect16256-9-4"
|
||||
width="63.000004"
|
||||
height="19"
|
||||
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
@ -13,8 +13,8 @@
|
||||
height="22"
|
||||
id="svg2857"
|
||||
version="1.1"
|
||||
inkscape:version="0.47 r22583"
|
||||
sodipodi:docname="New document 2">
|
||||
inkscape:version="0.48.1 r9760"
|
||||
sodipodi:docname="toggle-on-us.svg">
|
||||
<defs
|
||||
id="defs2859">
|
||||
<inkscape:perspective
|
||||
@ -40,16 +40,18 @@
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="-69.642856"
|
||||
inkscape:cy="42.428569"
|
||||
inkscape:cx="19.689855"
|
||||
inkscape:cy="2.0517979"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="609"
|
||||
inkscape:window-height="501"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="0" />
|
||||
inkscape:window-width="941"
|
||||
inkscape:window-height="751"
|
||||
inkscape:window-x="2577"
|
||||
inkscape:window-y="206"
|
||||
inkscape:window-maximized="0"
|
||||
borderlayer="true"
|
||||
inkscape:showpageshadow="false" />
|
||||
<metadata
|
||||
id="metadata2862">
|
||||
<rdf:RDF>
|
||||
@ -58,7 +60,7 @@
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
@ -72,7 +74,7 @@
|
||||
transform="translate(-351.35714,708.36218)"
|
||||
id="g16453">
|
||||
<rect
|
||||
style="color:#000000;fill:#204a87;fill-opacity:1;fill-rule:nonzero;stroke:#3465a4;stroke-width:0.99999994;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;enable-background:accumulate"
|
||||
style="color:#000000;fill:#4a90d9;fill-opacity:1;fill-rule:nonzero;stroke:#3465a4;stroke-width:0.99999994000000003;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;enable-background:accumulate"
|
||||
id="rect16256-9-4"
|
||||
width="63.000004"
|
||||
height="19"
|
||||
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.8 KiB |
@ -10,11 +10,14 @@ nobase_dist_js_DATA = \
|
||||
misc/history.js \
|
||||
misc/modemManager.js \
|
||||
misc/params.js \
|
||||
misc/screenSaver.js \
|
||||
misc/util.js \
|
||||
perf/core.js \
|
||||
ui/altTab.js \
|
||||
ui/appDisplay.js \
|
||||
ui/appFavorites.js \
|
||||
ui/automountManager.js \
|
||||
ui/autorunManager.js \
|
||||
ui/boxpointer.js \
|
||||
ui/calendar.js \
|
||||
ui/chrome.js \
|
||||
@ -27,6 +30,7 @@ nobase_dist_js_DATA = \
|
||||
ui/environment.js \
|
||||
ui/extensionSystem.js \
|
||||
ui/iconGrid.js \
|
||||
ui/layout.js \
|
||||
ui/lightbox.js \
|
||||
ui/link.js \
|
||||
ui/lookingGlass.js \
|
||||
@ -35,6 +39,7 @@ nobase_dist_js_DATA = \
|
||||
ui/main.js \
|
||||
ui/messageTray.js \
|
||||
ui/modalDialog.js \
|
||||
ui/shellMountOperation.js \
|
||||
ui/notificationDaemon.js \
|
||||
ui/overview.js \
|
||||
ui/panel.js \
|
||||
|
@ -109,7 +109,8 @@ const SessionManagerIface = {
|
||||
name: 'org.gnome.SessionManager',
|
||||
methods: [
|
||||
{ name: 'Logout', inSignature: 'u', outSignature: '' },
|
||||
{ name: 'Shutdown', inSignature: '', outSignature: '' }
|
||||
{ name: 'Shutdown', inSignature: '', outSignature: '' },
|
||||
{ name: 'CanShutdown', inSignature: '', outSignature: 'b' }
|
||||
]
|
||||
};
|
||||
|
||||
|
53
js/misc/screenSaver.js
Normal file
53
js/misc/screenSaver.js
Normal file
@ -0,0 +1,53 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const DBus = imports.dbus;
|
||||
const Lang = imports.lang;
|
||||
|
||||
const ScreenSaverIface = {
|
||||
name: 'org.gnome.ScreenSaver',
|
||||
methods: [{ name: 'GetActive',
|
||||
inSignature: '',
|
||||
outSignature: 'b' },
|
||||
{ name: 'Lock',
|
||||
inSignature: '' },
|
||||
{ name: 'SetActive',
|
||||
inSignature: 'b' }],
|
||||
signals: [{ name: 'ActiveChanged',
|
||||
inSignature: 'b' }]
|
||||
};
|
||||
|
||||
function ScreenSaverProxy() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
ScreenSaverProxy.prototype = {
|
||||
_init: function() {
|
||||
DBus.session.proxifyObject(this,
|
||||
'org.gnome.ScreenSaver',
|
||||
'/org/gnome/ScreenSaver');
|
||||
|
||||
DBus.session.watch_name('org.gnome.ScreenSaver',
|
||||
false, // do not launch a name-owner if none exists
|
||||
Lang.bind(this, this._onSSAppeared),
|
||||
Lang.bind(this, this._onSSVanished));
|
||||
|
||||
this.screenSaverActive = false;
|
||||
this.connect('ActiveChanged',
|
||||
Lang.bind(this, this._onActiveChanged));
|
||||
},
|
||||
|
||||
_onSSAppeared: function(owner) {
|
||||
this.GetActiveRemote(Lang.bind(this, function(isActive) {
|
||||
this.screenSaverActive = isActive;
|
||||
}))
|
||||
},
|
||||
|
||||
_onSSVanished: function(oldOwner) {
|
||||
this.screenSaverActive = false;
|
||||
},
|
||||
|
||||
_onActiveChanged: function(object, isActive) {
|
||||
this.screenSaverActive = isActive;
|
||||
}
|
||||
};
|
||||
DBus.proxifyPrototype(ScreenSaverProxy.prototype, ScreenSaverIface);
|
@ -45,7 +45,7 @@ function spawn(argv) {
|
||||
// occur when trying to parse or start the program.
|
||||
function spawnCommandLine(command_line) {
|
||||
try {
|
||||
let [success, argc, argv] = GLib.shell_parse_argv(command_line);
|
||||
let [success, argv] = GLib.shell_parse_argv(command_line);
|
||||
trySpawn(argv);
|
||||
} catch (err) {
|
||||
_handleSpawnError(command_line, err);
|
||||
@ -85,10 +85,10 @@ function trySpawn(argv)
|
||||
// Runs @command_line in the background. If launching @command_line
|
||||
// fails, this will throw an error.
|
||||
function trySpawnCommandLine(command_line) {
|
||||
let success, argc, argv;
|
||||
let success, argv;
|
||||
|
||||
try {
|
||||
[success, argc, argv] = GLib.shell_parse_argv(command_line);
|
||||
[success, argv] = GLib.shell_parse_argv(command_line);
|
||||
} catch (err) {
|
||||
// Replace "Error invoking GLib.shell_parse_argv: " with
|
||||
// something nicer
|
||||
|
@ -14,7 +14,8 @@ const Tweener = imports.ui.tweener;
|
||||
|
||||
const POPUP_APPICON_SIZE = 96;
|
||||
const POPUP_SCROLL_TIME = 0.10; // seconds
|
||||
const POPUP_FADE_TIME = 0.1; // seconds
|
||||
const POPUP_FADE_IN_TIME = 0.4; // seconds
|
||||
const POPUP_FADE_OUT_TIME = 0.1; // seconds
|
||||
|
||||
const APP_ICON_HOVER_TIMEOUT = 200; // milliseconds
|
||||
|
||||
@ -74,7 +75,7 @@ AltTabPopup.prototype = {
|
||||
|
||||
_allocate: function (actor, box, flags) {
|
||||
let childBox = new Clutter.ActorBox();
|
||||
let primary = global.get_primary_monitor();
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
|
||||
let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
|
||||
let rightPadding = this.actor.get_theme_node().get_padding(St.Side.RIGHT);
|
||||
@ -97,8 +98,6 @@ AltTabPopup.prototype = {
|
||||
// those calculations
|
||||
if (this._thumbnails) {
|
||||
let icon = this._appIcons[this._currentApp].actor;
|
||||
// Force a stage relayout to make sure we get the correct position
|
||||
global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, 0, 0);
|
||||
let [posX, posY] = icon.get_transformed_position();
|
||||
let thumbnailCenter = posX + icon.width / 2;
|
||||
let [childMinWidth, childNaturalWidth] = this._thumbnails.actor.get_preferred_width(-1);
|
||||
@ -121,7 +120,7 @@ AltTabPopup.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
show : function(backward, switch_group) {
|
||||
show : function(backward, binding) {
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
let apps = tracker.get_running_apps ('');
|
||||
|
||||
@ -152,7 +151,7 @@ AltTabPopup.prototype = {
|
||||
this.actor.get_allocation_box();
|
||||
|
||||
// Make the initial selection
|
||||
if (switch_group) {
|
||||
if (binding == 'switch_group') {
|
||||
if (backward) {
|
||||
this._select(0, this._appIcons[0].cachedWindows.length - 1);
|
||||
} else {
|
||||
@ -161,6 +160,10 @@ AltTabPopup.prototype = {
|
||||
else
|
||||
this._select(0, 0);
|
||||
}
|
||||
} else if (binding == 'switch_group_backward') {
|
||||
this._select(0, this._appIcons[0].cachedWindows.length - 1);
|
||||
} else if (binding == 'switch_windows_backward') {
|
||||
this._select(this._appIcons.length - 1);
|
||||
} else if (this._appIcons.length == 1) {
|
||||
this._select(0);
|
||||
} else if (backward) {
|
||||
@ -180,10 +183,15 @@ AltTabPopup.prototype = {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Using easeInOutExpo over 400ms gives us 150ms of "nearly
|
||||
// invisible" (less than 10% opacity), followed by a 100ms
|
||||
// tween in (to 90% opacity, with the last 10% coming over the
|
||||
// next 150ms). So if the user releases Alt quickly after we
|
||||
// start tweening, they'll never see the switcher.
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 255,
|
||||
time: POPUP_FADE_TIME,
|
||||
transition: 'easeOutQuad'
|
||||
time: POPUP_FADE_IN_TIME,
|
||||
transition: 'easeInOutExpo'
|
||||
});
|
||||
|
||||
return true;
|
||||
@ -219,33 +227,25 @@ AltTabPopup.prototype = {
|
||||
|
||||
this._disableHover();
|
||||
|
||||
if (action == Meta.KeyBindingAction.SWITCH_GROUP)
|
||||
this._select(this._currentApp, backwards ? this._previousWindow() : this._nextWindow());
|
||||
else if (keysym == Clutter.Escape)
|
||||
if (keysym == Clutter.Escape) {
|
||||
this.destroy();
|
||||
else if (this._thumbnailsFocused) {
|
||||
if (action == Meta.KeyBindingAction.SWITCH_WINDOWS)
|
||||
if (backwards) {
|
||||
if (this._currentWindow == 0 || this._currentWindow == -1)
|
||||
this._select(this._previousApp());
|
||||
else
|
||||
this._select(this._currentApp, this._previousWindow());
|
||||
} else {
|
||||
if (this._currentWindow == this._appIcons[this._currentApp].cachedWindows.length - 1)
|
||||
this._select(this._nextApp());
|
||||
else
|
||||
this._select(this._currentApp, this._nextWindow());
|
||||
}
|
||||
else if (keysym == Clutter.Left)
|
||||
} else if (action == Meta.KeyBindingAction.SWITCH_GROUP) {
|
||||
this._select(this._currentApp, backwards ? this._previousWindow() : this._nextWindow());
|
||||
} else if (action == Meta.KeyBindingAction.SWITCH_GROUP_BACKWARD) {
|
||||
this._select(this._currentApp, this._previousWindow());
|
||||
} else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS) {
|
||||
this._select(backwards ? this._previousApp() : this._nextApp());
|
||||
} else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS_BACKWARD) {
|
||||
this._select(this._previousApp());
|
||||
} else if (this._thumbnailsFocused) {
|
||||
if (keysym == Clutter.Left)
|
||||
this._select(this._currentApp, this._previousWindow());
|
||||
else if (keysym == Clutter.Right)
|
||||
this._select(this._currentApp, this._nextWindow());
|
||||
else if (keysym == Clutter.Up)
|
||||
this._select(this._currentApp, null, true);
|
||||
} else {
|
||||
if (action == Meta.KeyBindingAction.SWITCH_WINDOWS)
|
||||
this._select(backwards ? this._previousApp() : this._nextApp());
|
||||
else if (keysym == Clutter.Left)
|
||||
if (keysym == Clutter.Left)
|
||||
this._select(this._previousApp());
|
||||
else if (keysym == Clutter.Right)
|
||||
this._select(this._nextApp());
|
||||
@ -374,7 +374,7 @@ AltTabPopup.prototype = {
|
||||
if (this.actor.visible) {
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 0,
|
||||
time: POPUP_FADE_TIME,
|
||||
time: POPUP_FADE_OUT_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this,
|
||||
function() {
|
||||
@ -632,7 +632,7 @@ SwitcherList.prototype = {
|
||||
this._items[this._highlighted].add_style_pseudo_class('selected');
|
||||
}
|
||||
|
||||
let monitor = global.get_primary_monitor();
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
let itemSize = this._items[index].allocation.x2 - this._items[index].allocation.x1;
|
||||
let [posX, posY] = this._items[index].get_transformed_position();
|
||||
posX += this.actor.x;
|
||||
@ -660,7 +660,7 @@ SwitcherList.prototype = {
|
||||
|
||||
_scrollToRight : function() {
|
||||
this._scrollableLeft = true;
|
||||
let monitor = global.get_primary_monitor();
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
let padding = this.actor.get_theme_node().get_horizontal_padding();
|
||||
let parentPadding = this.actor.get_parent().get_theme_node().get_horizontal_padding();
|
||||
let x = this._items[this._highlighted].allocation.x2 - monitor.width + padding + parentPadding;
|
||||
@ -757,7 +757,7 @@ SwitcherList.prototype = {
|
||||
let children = this._list.get_children();
|
||||
let childBox = new Clutter.ActorBox();
|
||||
|
||||
let primary = global.get_primary_monitor();
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
let parentRightPadding = this.actor.get_parent().get_theme_node().get_padding(St.Side.RIGHT);
|
||||
if (this.actor.allocation.x2 == primary.x + primary.width - parentRightPadding) {
|
||||
if (this._squareItems)
|
||||
@ -887,7 +887,7 @@ AppSwitcher.prototype = {
|
||||
totalSpacing += this._separator.width + this._list.spacing;
|
||||
|
||||
// We just assume the whole screen here due to weirdness happing with the passed width
|
||||
let primary = global.get_primary_monitor();
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
let parentPadding = this.actor.get_parent().get_theme_node().get_horizontal_padding();
|
||||
let availWidth = primary.width - parentPadding - this.actor.get_theme_node().get_horizontal_padding();
|
||||
let height = 0;
|
||||
|
@ -44,7 +44,7 @@ AlphabeticalView.prototype = {
|
||||
this.actor = new St.ScrollView({ x_fill: true,
|
||||
y_fill: false,
|
||||
y_align: St.Align.START,
|
||||
vfade: true });
|
||||
style_class: 'vfade' });
|
||||
this.actor.add_actor(box);
|
||||
this.actor.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
this.actor.connect('notify::mapped', Lang.bind(this,
|
||||
@ -172,10 +172,12 @@ ViewByCategories.prototype = {
|
||||
// (used only before the actor is mapped the first time)
|
||||
this._currentCategory = -2;
|
||||
this._filters = new St.BoxLayout({ vertical: true, reactive: true });
|
||||
this._filters.connect('scroll-event', Lang.bind(this, this._scrollFilter));
|
||||
|
||||
this._filtersBox = new St.ScrollView({ x_fill: false,
|
||||
y_fill: false,
|
||||
style_class: 'vfade' });
|
||||
this._filtersBox.add_actor(this._filters);
|
||||
this.actor.add(this._view.actor, { expand: true, x_fill: true, y_fill: true });
|
||||
this.actor.add(this._filters, { expand: false, y_fill: false, y_align: St.Align.START });
|
||||
this.actor.add(this._filtersBox, { expand: false, y_fill: false, y_align: St.Align.START });
|
||||
|
||||
// Always select the "All" filter when switching to the app view
|
||||
this.actor.connect('notify::mapped', Lang.bind(this,
|
||||
@ -193,14 +195,6 @@ ViewByCategories.prototype = {
|
||||
this.actor.add(this._focusDummy);
|
||||
},
|
||||
|
||||
_scrollFilter: function(actor, event) {
|
||||
let direction = event.get_scroll_direction();
|
||||
if (direction == Clutter.ScrollDirection.UP)
|
||||
this._selectCategory(Math.max(this._currentCategory - 1, -1))
|
||||
else if (direction == Clutter.ScrollDirection.DOWN)
|
||||
this._selectCategory(Math.min(this._currentCategory + 1, this._sections.length - 1));
|
||||
},
|
||||
|
||||
_selectCategory: function(num) {
|
||||
if (this._currentCategory == num) // nothing to do
|
||||
return;
|
||||
@ -333,8 +327,16 @@ BaseAppSearchProvider.prototype = {
|
||||
params = Params.parse(params, { workspace: null,
|
||||
timestamp: null });
|
||||
|
||||
let workspace = params.workspace ? params.workspace.index() : -1;
|
||||
let event = Clutter.get_current_event();
|
||||
let modifiers = event ? Shell.get_event_state(event) : 0;
|
||||
let openNewWindow = modifiers & Clutter.ModifierType.CONTROL_MASK;
|
||||
|
||||
let app = this._appSys.get_app(id);
|
||||
app.activate(params.workspace ? params.workspace.index() : -1);
|
||||
if (openNewWindow)
|
||||
app.open_new_window(workspace);
|
||||
else
|
||||
app.activate(workspace);
|
||||
},
|
||||
|
||||
dragActivateResult: function(id, params) {
|
||||
|
278
js/ui/automountManager.js
Normal file
278
js/ui/automountManager.js
Normal file
@ -0,0 +1,278 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Lang = imports.lang;
|
||||
const DBus = imports.dbus;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Params = imports.misc.params;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const ShellMountOperation = imports.ui.shellMountOperation;
|
||||
const ScreenSaver = imports.misc.screenSaver;
|
||||
|
||||
// GSettings keys
|
||||
const SETTINGS_SCHEMA = 'org.gnome.desktop.media-handling';
|
||||
const SETTING_ENABLE_AUTOMOUNT = 'automount';
|
||||
|
||||
const AUTORUN_EXPIRE_TIMEOUT_SECS = 10;
|
||||
|
||||
const ConsoleKitSessionIface = {
|
||||
name: 'org.freedesktop.ConsoleKit.Session',
|
||||
methods: [{ name: 'IsActive',
|
||||
inSignature: '',
|
||||
outSignature: 'b' }],
|
||||
signals: [{ name: 'ActiveChanged',
|
||||
inSignature: 'b' }]
|
||||
};
|
||||
|
||||
const ConsoleKitSessionProxy = DBus.makeProxyClass(ConsoleKitSessionIface);
|
||||
|
||||
const ConsoleKitManagerIface = {
|
||||
name: 'org.freedesktop.ConsoleKit.Manager',
|
||||
methods: [{ name: 'GetCurrentSession',
|
||||
inSignature: '',
|
||||
outSignature: 'o' }]
|
||||
};
|
||||
|
||||
function ConsoleKitManager() {
|
||||
this._init();
|
||||
};
|
||||
|
||||
ConsoleKitManager.prototype = {
|
||||
_init: function() {
|
||||
this.sessionActive = true;
|
||||
|
||||
DBus.system.proxifyObject(this,
|
||||
'org.freedesktop.ConsoleKit',
|
||||
'/org/freedesktop/ConsoleKit/Manager');
|
||||
|
||||
DBus.system.watch_name('org.freedesktop.ConsoleKit',
|
||||
false, // do not launch a name-owner if none exists
|
||||
Lang.bind(this, this._onManagerAppeared),
|
||||
Lang.bind(this, this._onManagerVanished));
|
||||
},
|
||||
|
||||
_onManagerAppeared: function(owner) {
|
||||
this.GetCurrentSessionRemote(Lang.bind(this, this._onCurrentSession));
|
||||
},
|
||||
|
||||
_onManagerVanished: function(oldOwner) {
|
||||
this.sessionActive = true;
|
||||
},
|
||||
|
||||
_onCurrentSession: function(session) {
|
||||
this._ckSession = new ConsoleKitSessionProxy(DBus.system, 'org.freedesktop.ConsoleKit', session);
|
||||
|
||||
this._ckSession.connect
|
||||
('ActiveChanged', Lang.bind(this, function(object, isActive) {
|
||||
this.sessionActive = isActive;
|
||||
}));
|
||||
this._ckSession.IsActiveRemote(Lang.bind(this, function(isActive) {
|
||||
this.sessionActive = isActive;
|
||||
}));
|
||||
}
|
||||
};
|
||||
DBus.proxifyPrototype(ConsoleKitManager.prototype, ConsoleKitManagerIface);
|
||||
|
||||
function AutomountManager() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
AutomountManager.prototype = {
|
||||
_init: function() {
|
||||
this._settings = new Gio.Settings({ schema: SETTINGS_SCHEMA });
|
||||
this._volumeQueue = [];
|
||||
|
||||
this.ckListener = new ConsoleKitManager();
|
||||
|
||||
this._ssProxy = new ScreenSaver.ScreenSaverProxy();
|
||||
this._ssProxy.connect('ActiveChanged',
|
||||
Lang.bind(this,
|
||||
this._screenSaverActiveChanged));
|
||||
|
||||
this._volumeMonitor = Gio.VolumeMonitor.get();
|
||||
|
||||
this._volumeMonitor.connect('volume-added',
|
||||
Lang.bind(this,
|
||||
this._onVolumeAdded));
|
||||
this._volumeMonitor.connect('volume-removed',
|
||||
Lang.bind(this,
|
||||
this._onVolumeRemoved));
|
||||
this._volumeMonitor.connect('drive-connected',
|
||||
Lang.bind(this,
|
||||
this._onDriveConnected));
|
||||
this._volumeMonitor.connect('drive-disconnected',
|
||||
Lang.bind(this,
|
||||
this._onDriveDisconnected));
|
||||
this._volumeMonitor.connect('drive-eject-button',
|
||||
Lang.bind(this,
|
||||
this._onDriveEjectButton));
|
||||
|
||||
Mainloop.idle_add(Lang.bind(this, this._startupMountAll));
|
||||
},
|
||||
|
||||
_screenSaverActiveChanged: function(object, isActive) {
|
||||
if (!isActive) {
|
||||
this._volumeQueue.forEach(Lang.bind(this, function(volume) {
|
||||
this._checkAndMountVolume(volume);
|
||||
}));
|
||||
}
|
||||
|
||||
// clear the queue anyway
|
||||
this._volumeQueue = [];
|
||||
},
|
||||
|
||||
_startupMountAll: function() {
|
||||
let volumes = this._volumeMonitor.get_volumes();
|
||||
volumes.forEach(Lang.bind(this, function(volume) {
|
||||
this._checkAndMountVolume(volume, { checkSession: false,
|
||||
useMountOp: false });
|
||||
}));
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
_onDriveConnected: function() {
|
||||
// if we're not in the current ConsoleKit session,
|
||||
// or screensaver is active, don't play sounds
|
||||
if (!this.ckListener.sessionActive)
|
||||
return;
|
||||
|
||||
if (this._ssProxy.screenSaverActive)
|
||||
return;
|
||||
|
||||
global.play_theme_sound(0, 'device-added-media');
|
||||
},
|
||||
|
||||
_onDriveDisconnected: function() {
|
||||
// if we're not in the current ConsoleKit session,
|
||||
// or screensaver is active, don't play sounds
|
||||
if (!this.ckListener.sessionActive)
|
||||
return;
|
||||
|
||||
if (this._ssProxy.screenSaverActive)
|
||||
return;
|
||||
|
||||
global.play_theme_sound(0, 'device-removed-media');
|
||||
},
|
||||
|
||||
_onDriveEjectButton: function(monitor, drive) {
|
||||
// TODO: this code path is not tested, as the GVfs volume monitor
|
||||
// doesn't emit this signal just yet.
|
||||
if (!this.ckListener.sessionActive)
|
||||
return;
|
||||
|
||||
// we force stop/eject in this case, so we don't have to pass a
|
||||
// mount operation object
|
||||
if (drive.can_stop()) {
|
||||
drive.stop
|
||||
(Gio.MountUnmountFlags.FORCE, null, null,
|
||||
Lang.bind(this, function(drive, res) {
|
||||
try {
|
||||
drive.stop_finish(res);
|
||||
} catch (e) {
|
||||
log("Unable to stop the drive after drive-eject-button " + e.toString());
|
||||
}
|
||||
}));
|
||||
} else if (drive.can_eject()) {
|
||||
drive.eject_with_operation
|
||||
(Gio.MountUnmountFlags.FORCE, null, null,
|
||||
Lang.bind(this, function(drive, res) {
|
||||
try {
|
||||
drive.eject_with_operation_finish(res);
|
||||
} catch (e) {
|
||||
log("Unable to eject the drive after drive-eject-button " + e.toString());
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
_onVolumeAdded: function(monitor, volume) {
|
||||
this._checkAndMountVolume(volume);
|
||||
},
|
||||
|
||||
_checkAndMountVolume: function(volume, params) {
|
||||
params = Params.parse(params, { checkSession: true,
|
||||
useMountOp: true });
|
||||
|
||||
if (params.checkSession) {
|
||||
// if we're not in the current ConsoleKit session,
|
||||
// don't attempt automount
|
||||
if (!this.ckListener.sessionActive)
|
||||
return;
|
||||
|
||||
if (this._ssProxy.screenSaverActive) {
|
||||
if (this._volumeQueue.indexOf(volume) == -1)
|
||||
this._volumeQueue.push(volume);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._settings.get_boolean(SETTING_ENABLE_AUTOMOUNT) ||
|
||||
!volume.should_automount() ||
|
||||
!volume.can_mount()) {
|
||||
// allow the autorun to run anyway; this can happen if the
|
||||
// mount gets added programmatically later, even if
|
||||
// should_automount() or can_mount() are false, like for
|
||||
// blank optical media.
|
||||
this._allowAutorun(volume);
|
||||
this._allowAutorunExpire(volume);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.useMountOp) {
|
||||
let operation = new ShellMountOperation.ShellMountOperation(volume);
|
||||
this._mountVolume(volume, operation.mountOp);
|
||||
} else {
|
||||
this._mountVolume(volume, null);
|
||||
}
|
||||
},
|
||||
|
||||
_mountVolume: function(volume, operation) {
|
||||
this._allowAutorun(volume);
|
||||
volume.mount(0, operation, null,
|
||||
Lang.bind(this, this._onVolumeMounted));
|
||||
},
|
||||
|
||||
_onVolumeMounted: function(volume, res) {
|
||||
this._allowAutorunExpire(volume);
|
||||
|
||||
try {
|
||||
volume.mount_finish(res);
|
||||
} catch (e) {
|
||||
let string = e.toString();
|
||||
|
||||
// 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);
|
||||
}
|
||||
},
|
||||
|
||||
_onVolumeRemoved: function(monitor, volume) {
|
||||
this._volumeQueue =
|
||||
this._volumeQueue.filter(function(element) {
|
||||
return (element != volume);
|
||||
});
|
||||
},
|
||||
|
||||
_reaskPassword: function(volume) {
|
||||
let operation = new ShellMountOperation.ShellMountOperation(volume, { reaskPassword: true });
|
||||
this._mountVolume(volume, operation.mountOp);
|
||||
},
|
||||
|
||||
_allowAutorun: function(volume) {
|
||||
volume.allowAutorun = true;
|
||||
},
|
||||
|
||||
_allowAutorunExpire: function(volume) {
|
||||
Mainloop.timeout_add_seconds(AUTORUN_EXPIRE_TIMEOUT_SECS, function() {
|
||||
volume.allowAutorun = false;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
634
js/ui/autorunManager.js
Normal file
634
js/ui/autorunManager.js
Normal file
@ -0,0 +1,634 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Lang = imports.lang;
|
||||
const DBus = imports.dbus;
|
||||
const Gio = imports.gi.Gio;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const MessageTray = imports.ui.messageTray;
|
||||
const ShellMountOperation = imports.ui.shellMountOperation;
|
||||
|
||||
// GSettings keys
|
||||
const SETTINGS_SCHEMA = 'org.gnome.desktop.media-handling';
|
||||
const SETTING_DISABLE_AUTORUN = 'autorun-never';
|
||||
const SETTING_START_APP = 'autorun-x-content-start-app';
|
||||
const SETTING_IGNORE = 'autorun-x-content-ignore';
|
||||
const SETTING_OPEN_FOLDER = 'autorun-x-content-open-folder';
|
||||
|
||||
const AutorunSetting = {
|
||||
RUN: 0,
|
||||
IGNORE: 1,
|
||||
FILES: 2,
|
||||
ASK: 3
|
||||
};
|
||||
|
||||
const HOTPLUG_ICON_SIZE = 16;
|
||||
|
||||
// misc utils
|
||||
function ignoreAutorunForMount(mount) {
|
||||
let root = mount.get_root();
|
||||
let volume = mount.get_volume();
|
||||
|
||||
if ((root.is_native() && !isMountRootHidden(root)) ||
|
||||
(volume && volume.allowAutorun && volume.should_automount()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function isMountRootHidden(root) {
|
||||
let path = root.get_path();
|
||||
|
||||
// skip any mounts in hidden directory hierarchies
|
||||
return (path.indexOf('/.') != -1);
|
||||
}
|
||||
|
||||
function startAppForMount(app, mount) {
|
||||
let files = [];
|
||||
let root = mount.get_root();
|
||||
let retval = false;
|
||||
|
||||
files.push(root);
|
||||
|
||||
try {
|
||||
retval = app.launch(files,
|
||||
global.create_app_launch_context())
|
||||
} catch (e) {
|
||||
log('Unable to launch the application ' + app.get_name()
|
||||
+ ': ' + e.toString());
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/******************************************/
|
||||
|
||||
const HotplugSnifferIface = {
|
||||
name: 'org.gnome.Shell.HotplugSniffer',
|
||||
methods: [{ name: 'SniffURI',
|
||||
inSignature: 's',
|
||||
outSignature: 'as' }]
|
||||
};
|
||||
|
||||
const HotplugSniffer = function() {
|
||||
this._init();
|
||||
};
|
||||
|
||||
HotplugSniffer.prototype = {
|
||||
_init: function() {
|
||||
DBus.session.proxifyObject(this,
|
||||
'org.gnome.Shell.HotplugSniffer',
|
||||
'/org/gnome/Shell/HotplugSniffer');
|
||||
},
|
||||
};
|
||||
DBus.proxifyPrototype(HotplugSniffer.prototype, HotplugSnifferIface);
|
||||
|
||||
function ContentTypeDiscoverer(callback) {
|
||||
this._init(callback);
|
||||
}
|
||||
|
||||
ContentTypeDiscoverer.prototype = {
|
||||
_init: function(callback) {
|
||||
this._callback = callback;
|
||||
},
|
||||
|
||||
guessContentTypes: function(mount) {
|
||||
// guess mount's content types using GIO
|
||||
mount.guess_content_type(false, null,
|
||||
Lang.bind(this,
|
||||
this._onContentTypeGuessed));
|
||||
},
|
||||
|
||||
_onContentTypeGuessed: function(mount, res) {
|
||||
let contentTypes = [];
|
||||
|
||||
try {
|
||||
contentTypes = mount.guess_content_type_finish(res);
|
||||
} catch (e) {
|
||||
log('Unable to guess content types on added mount ' + mount.get_name()
|
||||
+ ': ' + e.toString());
|
||||
}
|
||||
|
||||
if (contentTypes.length) {
|
||||
this._emitCallback(mount, contentTypes);
|
||||
} else {
|
||||
let root = mount.get_root();
|
||||
|
||||
let hotplugSniffer = new HotplugSniffer();
|
||||
hotplugSniffer.SniffURIRemote
|
||||
(root.get_uri(), DBus.CALL_FLAG_START,
|
||||
Lang.bind(this, function(contentTypes) {
|
||||
this._emitCallback(mount, contentTypes);
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
_emitCallback: function(mount, contentTypes) {
|
||||
if (!contentTypes)
|
||||
contentTypes = [];
|
||||
|
||||
// we're not interested in win32 software content types here
|
||||
contentTypes = contentTypes.filter(function(type) {
|
||||
return (type != 'x-content/win32-software');
|
||||
});
|
||||
|
||||
let apps = [];
|
||||
contentTypes.forEach(function(type) {
|
||||
let app = Gio.app_info_get_default_for_type(type, false);
|
||||
|
||||
if (app)
|
||||
apps.push(app);
|
||||
});
|
||||
|
||||
if (apps.length == 0)
|
||||
apps.push(Gio.app_info_get_default_for_type('inode/directory', false));
|
||||
|
||||
this._callback(mount, apps, contentTypes);
|
||||
}
|
||||
}
|
||||
|
||||
function AutorunManager() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
AutorunManager.prototype = {
|
||||
_init: function() {
|
||||
this._volumeMonitor = Gio.VolumeMonitor.get();
|
||||
|
||||
this._volumeMonitor.connect('mount-added',
|
||||
Lang.bind(this,
|
||||
this._onMountAdded));
|
||||
this._volumeMonitor.connect('mount-removed',
|
||||
Lang.bind(this,
|
||||
this._onMountRemoved));
|
||||
|
||||
this._transDispatcher = new AutorunTransientDispatcher();
|
||||
this._createResidentSource();
|
||||
|
||||
let mounts = this._volumeMonitor.get_mounts();
|
||||
|
||||
mounts.forEach(Lang.bind(this, function (mount) {
|
||||
let discoverer = new ContentTypeDiscoverer(Lang.bind (this,
|
||||
function (mount, apps) {
|
||||
this._residentSource.addMount(mount, apps);
|
||||
}));
|
||||
|
||||
discoverer.guessContentTypes(mount);
|
||||
}));
|
||||
},
|
||||
|
||||
_createResidentSource: function() {
|
||||
this._residentSource = new AutorunResidentSource();
|
||||
this._residentSource.connect('destroy',
|
||||
Lang.bind(this,
|
||||
this._createResidentSource));
|
||||
},
|
||||
|
||||
_onMountAdded: function(monitor, mount) {
|
||||
// don't do anything if our session is not the currently
|
||||
// active one
|
||||
if (!Main.automountManager.ckListener.sessionActive)
|
||||
return;
|
||||
|
||||
let discoverer = new ContentTypeDiscoverer(Lang.bind (this,
|
||||
function (mount, apps, contentTypes) {
|
||||
this._transDispatcher.addMount(mount, apps, contentTypes);
|
||||
this._residentSource.addMount(mount, apps);
|
||||
}));
|
||||
|
||||
discoverer.guessContentTypes(mount);
|
||||
},
|
||||
|
||||
_onMountRemoved: function(monitor, mount) {
|
||||
this._transDispatcher.removeMount(mount);
|
||||
this._residentSource.removeMount(mount);
|
||||
},
|
||||
|
||||
ejectMount: function(mount) {
|
||||
let mountOp = new ShellMountOperation.ShellMountOperation(mount);
|
||||
|
||||
// first, see if we have a drive
|
||||
let drive = mount.get_drive();
|
||||
let volume = mount.get_volume();
|
||||
|
||||
if (drive &&
|
||||
drive.get_start_stop_type() == Gio.DriveStartStopType.SHUTDOWN &&
|
||||
drive.can_stop()) {
|
||||
drive.stop(0, mountOp.mountOp, null,
|
||||
Lang.bind(this, this._onStop));
|
||||
} else {
|
||||
if (mount.can_eject()) {
|
||||
mount.eject_with_operation(0, mountOp.mountOp, null,
|
||||
Lang.bind(this, this._onEject));
|
||||
} else if (volume && volume.can_eject()) {
|
||||
volume.eject_with_operation(0, mountOp.mountOp, null,
|
||||
Lang.bind(this, this._onEject));
|
||||
} else if (drive && drive.can_eject()) {
|
||||
drive.eject_with_operation(0, mountOp.mountOp, null,
|
||||
Lang.bind(this, this._onEject));
|
||||
} else if (mount.can_unmount()) {
|
||||
mount.unmount_with_operation(0, mountOp.mountOp, null,
|
||||
Lang.bind(this, this._onUnmount));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_onUnmount: function(mount, res) {
|
||||
try {
|
||||
mount.unmount_with_operation_finish(res);
|
||||
} catch (e) {
|
||||
// FIXME: we need to ignore G_IO_ERROR_FAILED_HANDLED errors here
|
||||
// but we can't access the error code from JS.
|
||||
// See https://bugzilla.gnome.org/show_bug.cgi?id=591480
|
||||
log('Unable to eject the mount ' + mount.get_name()
|
||||
+ ': ' + e.toString());
|
||||
}
|
||||
},
|
||||
|
||||
_onEject: function(source, res) {
|
||||
try {
|
||||
source.eject_with_operation_finish(res);
|
||||
} catch (e) {
|
||||
// FIXME: we need to ignore G_IO_ERROR_FAILED_HANDLED errors here
|
||||
// but we can't access the error code from JS.
|
||||
// See https://bugzilla.gnome.org/show_bug.cgi?id=591480
|
||||
log('Unable to eject the drive ' + source.get_name()
|
||||
+ ': ' + e.toString());
|
||||
}
|
||||
},
|
||||
|
||||
_onStop: function(drive, res) {
|
||||
try {
|
||||
drive.stop_finish(res);
|
||||
} catch (e) {
|
||||
// FIXME: we need to ignore G_IO_ERROR_FAILED_HANDLED errors here
|
||||
// but we can't access the error code from JS.
|
||||
// See https://bugzilla.gnome.org/show_bug.cgi?id=591480
|
||||
log('Unable to stop the drive ' + drive.get_name()
|
||||
+ ': ' + e.toString());
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
function AutorunResidentSource() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
AutorunResidentSource.prototype = {
|
||||
__proto__: MessageTray.Source.prototype,
|
||||
|
||||
_init: function() {
|
||||
MessageTray.Source.prototype._init.call(this, _('Removable Devices'));
|
||||
|
||||
this._mounts = [];
|
||||
|
||||
this._notification = new AutorunResidentNotification(this);
|
||||
this._setSummaryIcon(this.createNotificationIcon(HOTPLUG_ICON_SIZE));
|
||||
},
|
||||
|
||||
addMount: function(mount, apps) {
|
||||
if (ignoreAutorunForMount(mount))
|
||||
return;
|
||||
|
||||
let filtered = this._mounts.filter(function (element) {
|
||||
return (element.mount == mount);
|
||||
});
|
||||
|
||||
if (filtered.length != 0)
|
||||
return;
|
||||
|
||||
let element = { mount: mount, apps: apps };
|
||||
this._mounts.push(element);
|
||||
this._redisplay();
|
||||
},
|
||||
|
||||
removeMount: function(mount) {
|
||||
this._mounts =
|
||||
this._mounts.filter(function (element) {
|
||||
return (element.mount != mount);
|
||||
});
|
||||
|
||||
this._redisplay();
|
||||
},
|
||||
|
||||
_redisplay: function() {
|
||||
if (this._mounts.length == 0) {
|
||||
this._notification.destroy();
|
||||
this.destroy();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._notification.updateForMounts(this._mounts);
|
||||
|
||||
// add ourselves as a source, and push the notification
|
||||
if (!Main.messageTray.contains(this)) {
|
||||
Main.messageTray.add(this);
|
||||
this.pushNotification(this._notification);
|
||||
}
|
||||
},
|
||||
|
||||
createNotificationIcon: function(iconSize) {
|
||||
return new St.Icon ({ icon_name: 'drive-harddisk',
|
||||
icon_size: iconSize ? iconSize : this.ICON_SIZE });
|
||||
}
|
||||
}
|
||||
|
||||
function AutorunResidentNotification(source) {
|
||||
this._init(source);
|
||||
}
|
||||
|
||||
AutorunResidentNotification.prototype = {
|
||||
__proto__: MessageTray.Notification.prototype,
|
||||
|
||||
_init: function(source) {
|
||||
MessageTray.Notification.prototype._init.call(this, source,
|
||||
source.title, null,
|
||||
{ customContent: true });
|
||||
|
||||
// set the notification as resident
|
||||
this.setResident(true);
|
||||
|
||||
this._layout = new St.BoxLayout ({ style_class: 'hotplug-resident-box',
|
||||
vertical: true });
|
||||
|
||||
this.addActor(this._layout,
|
||||
{ x_expand: true,
|
||||
x_fill: true });
|
||||
},
|
||||
|
||||
updateForMounts: function(mounts) {
|
||||
// remove all the layout content
|
||||
this._layout.destroy_children();
|
||||
|
||||
for (let idx = 0; idx < mounts.length; idx++) {
|
||||
let element = mounts[idx];
|
||||
|
||||
let actor = this._itemForMount(element.mount, element.apps);
|
||||
this._layout.add(actor, { x_fill: true,
|
||||
expand: true });
|
||||
}
|
||||
},
|
||||
|
||||
_itemForMount: function(mount, apps) {
|
||||
let item = new St.BoxLayout();
|
||||
|
||||
// prepare the mount button content
|
||||
let mountLayout = new St.BoxLayout();
|
||||
|
||||
let mountIcon = new St.Icon({ gicon: mount.get_icon(),
|
||||
style_class: 'hotplug-resident-mount-icon' });
|
||||
mountLayout.add_actor(mountIcon);
|
||||
|
||||
let labelBin = new St.Bin({ y_align: St.Align.MIDDLE });
|
||||
let mountLabel =
|
||||
new St.Label({ text: mount.get_name(),
|
||||
style_class: 'hotplug-resident-mount-label',
|
||||
track_hover: true,
|
||||
reactive: true });
|
||||
labelBin.add_actor(mountLabel);
|
||||
mountLayout.add_actor(labelBin);
|
||||
|
||||
let mountButton = new St.Button({ child: mountLayout,
|
||||
x_align: St.Align.START,
|
||||
x_fill: true,
|
||||
style_class: 'hotplug-resident-mount',
|
||||
button_mask: St.ButtonMask.ONE });
|
||||
item.add(mountButton, { x_align: St.Align.START,
|
||||
expand: true });
|
||||
|
||||
let ejectIcon =
|
||||
new St.Icon({ icon_name: 'media-eject',
|
||||
style_class: 'hotplug-resident-eject-icon' });
|
||||
|
||||
let ejectButton =
|
||||
new St.Button({ style_class: 'hotplug-resident-eject-button',
|
||||
button_mask: St.ButtonMask.ONE,
|
||||
child: ejectIcon });
|
||||
item.add(ejectButton, { x_align: St.Align.END });
|
||||
|
||||
// now connect signals
|
||||
mountButton.connect('clicked', Lang.bind(this, function(actor, event) {
|
||||
startAppForMount(apps[0], mount);
|
||||
}));
|
||||
|
||||
ejectButton.connect('clicked', Lang.bind(this, function() {
|
||||
Main.autorunManager.ejectMount(mount);
|
||||
}));
|
||||
|
||||
return item;
|
||||
},
|
||||
}
|
||||
|
||||
function AutorunTransientDispatcher() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
AutorunTransientDispatcher.prototype = {
|
||||
_init: function() {
|
||||
this._sources = [];
|
||||
this._settings = new Gio.Settings({ schema: SETTINGS_SCHEMA });
|
||||
},
|
||||
|
||||
_getAutorunSettingForType: function(contentType) {
|
||||
let runApp = this._settings.get_strv(SETTING_START_APP);
|
||||
if (runApp.indexOf(contentType) != -1)
|
||||
return AutorunSetting.RUN;
|
||||
|
||||
let ignore = this._settings.get_strv(SETTING_IGNORE);
|
||||
if (ignore.indexOf(contentType) != -1)
|
||||
return AutorunSetting.IGNORE;
|
||||
|
||||
let openFiles = this._settings.get_strv(SETTING_OPEN_FOLDER);
|
||||
if (openFiles.indexOf(contentType) != -1)
|
||||
return AutorunSetting.FILES;
|
||||
|
||||
return AutorunSetting.ASK;
|
||||
},
|
||||
|
||||
_getSourceForMount: function(mount) {
|
||||
let filtered =
|
||||
this._sources.filter(function (source) {
|
||||
return (source.mount == mount);
|
||||
});
|
||||
|
||||
// we always make sure not to add two sources for the same
|
||||
// mount in addMount(), so it's safe to assume filtered.length
|
||||
// is always either 1 or 0.
|
||||
if (filtered.length == 1)
|
||||
return filtered[0];
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
_addSource: function(mount, apps) {
|
||||
// if we already have a source showing for this
|
||||
// mount, return
|
||||
if (this._getSourceForMount(mount))
|
||||
return;
|
||||
|
||||
// add a new source
|
||||
this._sources.push(new AutorunTransientSource(mount, apps));
|
||||
},
|
||||
|
||||
addMount: function(mount, apps, contentTypes) {
|
||||
// if autorun is disabled globally, return
|
||||
if (this._settings.get_boolean(SETTING_DISABLE_AUTORUN))
|
||||
return;
|
||||
|
||||
// if the mount doesn't want to be autorun, return
|
||||
if (ignoreAutorunForMount(mount))
|
||||
return;
|
||||
|
||||
let setting = this._getAutorunSettingForType(contentTypes[0]);
|
||||
|
||||
// check at the settings for the first content type
|
||||
// to see whether we should ask
|
||||
if (setting == AutorunSetting.IGNORE)
|
||||
return; // return right away
|
||||
|
||||
let success = false;
|
||||
let app = null;
|
||||
|
||||
if (setting == AutorunSetting.RUN) {
|
||||
app = Gio.app_info_get_default_for_type(type, false);
|
||||
} else if (setting == AutorunSetting.FILES) {
|
||||
app = Gio.app_info_get_default_for_type('inode/directory', false);
|
||||
}
|
||||
|
||||
if (app)
|
||||
success = startAppForMount(app, mount);
|
||||
|
||||
// we fallback here also in case the settings did not specify 'ask',
|
||||
// but we failed launching the default app or the default file manager
|
||||
if (!success)
|
||||
this._addSource(mount, apps);
|
||||
},
|
||||
|
||||
removeMount: function(mount) {
|
||||
let source = this._getSourceForMount(mount);
|
||||
|
||||
// if we aren't tracking this mount, don't do anything
|
||||
if (!source)
|
||||
return;
|
||||
|
||||
// destroy the notification source
|
||||
source.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
function AutorunTransientSource(mount, apps) {
|
||||
this._init(mount, apps);
|
||||
}
|
||||
|
||||
AutorunTransientSource.prototype = {
|
||||
__proto__: MessageTray.Source.prototype,
|
||||
|
||||
_init: function(mount, apps) {
|
||||
MessageTray.Source.prototype._init.call(this, mount.get_name());
|
||||
|
||||
this.mount = mount;
|
||||
this.apps = apps;
|
||||
|
||||
this._notification = new AutorunTransientNotification(this);
|
||||
this._setSummaryIcon(this.createNotificationIcon(this.ICON_SIZE));
|
||||
|
||||
// add ourselves as a source, and popup the notification
|
||||
Main.messageTray.add(this);
|
||||
this.notify(this._notification);
|
||||
},
|
||||
|
||||
createNotificationIcon: function(iconSize) {
|
||||
return new St.Icon({ gicon: this.mount.get_icon(),
|
||||
icon_size: iconSize ? iconSize : this.ICON_SIZE });
|
||||
}
|
||||
}
|
||||
|
||||
function AutorunTransientNotification(source) {
|
||||
this._init(source);
|
||||
}
|
||||
|
||||
AutorunTransientNotification.prototype = {
|
||||
__proto__: MessageTray.Notification.prototype,
|
||||
|
||||
_init: function(source) {
|
||||
MessageTray.Notification.prototype._init.call(this, source,
|
||||
source.title, null,
|
||||
{ customContent: true });
|
||||
|
||||
this._box = new St.BoxLayout({ style_class: 'hotplug-transient-box',
|
||||
vertical: true });
|
||||
this.addActor(this._box);
|
||||
|
||||
this._mount = source.mount;
|
||||
|
||||
source.apps.forEach(Lang.bind(this, function (app) {
|
||||
let actor = this._buttonForApp(app);
|
||||
|
||||
if (actor)
|
||||
this._box.add(actor, { x_fill: true,
|
||||
x_align: St.Align.START });
|
||||
}));
|
||||
|
||||
this._box.add(this._buttonForEject(), { x_fill: true,
|
||||
x_align: St.Align.START });
|
||||
|
||||
// set the notification to transient and urgent, so that it
|
||||
// expands out
|
||||
this.setTransient(true);
|
||||
this.setUrgency(MessageTray.Urgency.CRITICAL);
|
||||
},
|
||||
|
||||
_buttonForApp: function(app) {
|
||||
let box = new St.BoxLayout();
|
||||
let icon = new St.Icon({ gicon: app.get_icon(),
|
||||
style_class: 'hotplug-notification-item-icon' });
|
||||
box.add(icon);
|
||||
|
||||
let label = new St.Bin({ y_align: St.Align.MIDDLE,
|
||||
child: new St.Label
|
||||
({ text: _("Open with %s").format(app.get_display_name()) })
|
||||
});
|
||||
box.add(label);
|
||||
|
||||
let button = new St.Button({ child: box,
|
||||
x_fill: true,
|
||||
x_align: St.Align.START,
|
||||
button_mask: St.ButtonMask.ONE,
|
||||
style_class: 'hotplug-notification-item' });
|
||||
|
||||
button.connect('clicked', Lang.bind(this, function() {
|
||||
startAppForMount(app, this._mount);
|
||||
this.destroy();
|
||||
}));
|
||||
|
||||
return button;
|
||||
},
|
||||
|
||||
_buttonForEject: function() {
|
||||
let box = new St.BoxLayout();
|
||||
let icon = new St.Icon({ icon_name: 'media-eject',
|
||||
style_class: 'hotplug-notification-item-icon' });
|
||||
box.add(icon);
|
||||
|
||||
let label = new St.Bin({ y_align: St.Align.MIDDLE,
|
||||
child: new St.Label
|
||||
({ text: _("Eject") })
|
||||
});
|
||||
box.add(label);
|
||||
|
||||
let button = new St.Button({ child: box,
|
||||
x_fill: true,
|
||||
x_align: St.Align.START,
|
||||
button_mask: St.ButtonMask.ONE,
|
||||
style_class: 'hotplug-notification-item' });
|
||||
|
||||
button.connect('clicked', Lang.bind(this, function() {
|
||||
Main.autorunManager.ejectMount(this._mount);
|
||||
}));
|
||||
|
||||
return button;
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ const Meta = imports.gi.Meta;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
const POPUP_ANIMATION_TIME = 0.15;
|
||||
@ -329,7 +330,7 @@ BoxPointer.prototype = {
|
||||
// We also want to keep it onscreen, and separated from the
|
||||
// edge by the same distance as the main part of the box is
|
||||
// separated from its sourceActor
|
||||
let primary = global.get_primary_monitor();
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
let themeNode = this.actor.get_theme_node();
|
||||
let borderWidth = themeNode.get_length('-arrow-border-width');
|
||||
let arrowBase = themeNode.get_length('-arrow-base');
|
||||
|
@ -8,13 +8,13 @@ const Signals = imports.signals;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const Params = imports.misc.params;
|
||||
const ScreenSaver = imports.misc.screenSaver;
|
||||
|
||||
// This manages the shell "chrome"; the UI that's visible in the
|
||||
// normal mode (ie, outside the Overview), that surrounds the main
|
||||
// workspace content.
|
||||
|
||||
const defaultParams = {
|
||||
visibleInOverview: false,
|
||||
visibleInFullscreen: false,
|
||||
affectsStruts: true,
|
||||
affectsInputRegion: true
|
||||
@ -36,8 +36,8 @@ Chrome.prototype = {
|
||||
|
||||
this._trackedActors = [];
|
||||
|
||||
global.screen.connect('monitors-changed',
|
||||
Lang.bind(this, this._monitorsChanged));
|
||||
Main.layoutManager.connect('monitors-changed',
|
||||
Lang.bind(this, this._relayout));
|
||||
global.screen.connect('restacked',
|
||||
Lang.bind(this, this._windowsRestacked));
|
||||
|
||||
@ -50,9 +50,15 @@ Chrome.prototype = {
|
||||
Main.overview.connect('hidden',
|
||||
Lang.bind(this, this._overviewHidden));
|
||||
|
||||
this._updateMonitors();
|
||||
this._updateFullscreen();
|
||||
this._queueUpdateRegions();
|
||||
this._screenSaverProxy = new ScreenSaver.ScreenSaverProxy();
|
||||
this._screenSaverProxy.connect('ActiveChanged', Lang.bind(this, this._onScreenSaverActiveChanged));
|
||||
this._screenSaverProxy.GetActiveRemote(Lang.bind(this,
|
||||
function(result, err) {
|
||||
if (!err)
|
||||
this._onScreenSaverActiveChanged(this._screenSaverProxy, result);
|
||||
}));
|
||||
|
||||
this._relayout();
|
||||
},
|
||||
|
||||
_allocated: function(actor, box, flags) {
|
||||
@ -73,11 +79,8 @@ Chrome.prototype = {
|
||||
// in its visibility will affect the input region, but NOT the
|
||||
// struts.
|
||||
//
|
||||
// If %visibleInOverview is %true in @params, @actor will remain
|
||||
// visible when the overview is brought up. Otherwise it will
|
||||
// automatically be hidden. Likewise, if %visibleInFullscreen is
|
||||
// %true, the actor will be visible even when a fullscreen window
|
||||
// should be covering it.
|
||||
// If %visibleInFullscreen is %true, the actor will be visible
|
||||
// even when a fullscreen window should be covering it.
|
||||
//
|
||||
// If %affectsStruts or %affectsInputRegion is %false, the actor
|
||||
// will not have the indicated effect.
|
||||
@ -96,7 +99,7 @@ Chrome.prototype = {
|
||||
//
|
||||
// @params can have any of the same values as in addActor(), though
|
||||
// some possibilities don't make sense (eg, trying to have a
|
||||
// %visibleInOverview child of a non-%visibleInOverview parent).
|
||||
// %visibleInFullscreen child of a non-%visibleInFullscreen parent).
|
||||
// By default, @actor has the same params as its chrome ancestor.
|
||||
trackActor: function(actor, params) {
|
||||
let ancestor = actor.get_parent();
|
||||
@ -189,10 +192,8 @@ Chrome.prototype = {
|
||||
_updateVisibility: function() {
|
||||
for (let i = 0; i < this._trackedActors.length; i++) {
|
||||
let actorData = this._trackedActors[i];
|
||||
if (this._inOverview && !actorData.visibleInOverview)
|
||||
this.actor.set_skip_paint(actorData.actor, true);
|
||||
else if (!this._inOverview && !actorData.visibleInFullscreen &&
|
||||
this._findMonitorForActor(actorData.actor).inFullscreen)
|
||||
if (!this._inOverview && !actorData.visibleInFullscreen &&
|
||||
this._findMonitorForActor(actorData.actor).inFullscreen)
|
||||
this.actor.set_skip_paint(actorData.actor, true);
|
||||
else
|
||||
this.actor.set_skip_paint(actorData.actor, false);
|
||||
@ -211,18 +212,18 @@ Chrome.prototype = {
|
||||
this._queueUpdateRegions();
|
||||
},
|
||||
|
||||
_updateMonitors: function() {
|
||||
let monitors = global.get_monitors();
|
||||
let primary = global.get_primary_monitor();
|
||||
this._monitors = monitors;
|
||||
for (let i = 0; i < monitors.length; i++) {
|
||||
let monitor = monitors[i];
|
||||
if (monitor.x == primary.x &&
|
||||
monitor.y == primary.y &&
|
||||
monitor.width == primary.width &&
|
||||
monitor.height == primary.height)
|
||||
this._primaryMonitor = monitor;
|
||||
}
|
||||
_relayout: function() {
|
||||
this._monitors = Main.layoutManager.monitors;
|
||||
this._primaryMonitor = Main.layoutManager.primaryMonitor;
|
||||
|
||||
this._updateFullscreen();
|
||||
this._updateVisibility();
|
||||
this._queueUpdateRegions();
|
||||
},
|
||||
|
||||
_onScreenSaverActiveChanged: function(proxy, screenSaverActive) {
|
||||
this.actor.visible = !screenSaverActive;
|
||||
this._queueUpdateRegions();
|
||||
},
|
||||
|
||||
_findMonitorForRect: function(x, y, w, h) {
|
||||
@ -261,15 +262,6 @@ Chrome.prototype = {
|
||||
return this._primaryMonitor; // Not on any monitor, pretend its on the primary
|
||||
},
|
||||
|
||||
_monitorsChanged: function() {
|
||||
this._updateMonitors();
|
||||
|
||||
// Update everything that depends on monitor positions
|
||||
this._updateFullscreen();
|
||||
this._updateVisibility();
|
||||
this._queueUpdateRegions();
|
||||
},
|
||||
|
||||
_queueUpdateRegions: function() {
|
||||
if (!this._updateRegionIdle)
|
||||
this._updateRegionIdle = Mainloop.idle_add(Lang.bind(this, this._updateRegions),
|
||||
@ -297,6 +289,11 @@ Chrome.prototype = {
|
||||
|
||||
for (let i = windows.length - 1; i > -1; i--) {
|
||||
let window = windows[i];
|
||||
|
||||
// Skip minimized windows
|
||||
if (!window.showing_on_its_workspace())
|
||||
continue;
|
||||
|
||||
let layer = window.get_meta_window().get_layer();
|
||||
|
||||
if (layer == Meta.StackLayer.FULLSCREEN) {
|
||||
@ -335,11 +332,6 @@ Chrome.prototype = {
|
||||
this._updateVisibility();
|
||||
this._queueUpdateRegions();
|
||||
}
|
||||
|
||||
// Figure out where the pointer is in case we lost track of
|
||||
// it during a grab. (In particular, if a trayicon popup menu
|
||||
// is dismissed, see if we need to close the message tray.)
|
||||
global.sync_pointer();
|
||||
},
|
||||
|
||||
_updateRegions: function() {
|
||||
|
@ -153,14 +153,14 @@ CtrlAltTabPopup.prototype = {
|
||||
},
|
||||
|
||||
_getPreferredWidth: function (actor, forHeight, alloc) {
|
||||
let primary = global.get_primary_monitor();
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
|
||||
alloc.min_size = primary.width;
|
||||
alloc.natural_size = primary.width;
|
||||
},
|
||||
|
||||
_getPreferredHeight: function (actor, forWidth, alloc) {
|
||||
let primary = global.get_primary_monitor();
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
|
||||
alloc.min_size = primary.height;
|
||||
alloc.natural_size = primary.height;
|
||||
@ -168,7 +168,7 @@ CtrlAltTabPopup.prototype = {
|
||||
|
||||
_allocate: function (actor, box, flags) {
|
||||
let childBox = new Clutter.ActorBox();
|
||||
let primary = global.get_primary_monitor();
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
|
||||
let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
|
||||
let vPadding = this.actor.get_theme_node().get_vertical_padding();
|
||||
|
@ -324,7 +324,7 @@ Dash.prototype = {
|
||||
this._favRemoveTarget = null;
|
||||
}));
|
||||
}
|
||||
DND.removeMonitor(this._dragMonitor);
|
||||
DND.removeDragMonitor(this._dragMonitor);
|
||||
},
|
||||
|
||||
_onDragMotion: function(dragEvent) {
|
||||
@ -342,7 +342,10 @@ Dash.prototype = {
|
||||
|
||||
let srcIsFavorite = (id in favorites);
|
||||
|
||||
if (srcIsFavorite && this._favRemoveTarget == null) {
|
||||
if (srcIsFavorite &&
|
||||
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);
|
||||
|
@ -198,6 +198,7 @@ DateMenuButton.prototype = {
|
||||
|
||||
_onPreferencesActivate: function() {
|
||||
this.menu.close();
|
||||
Main.overview.hide();
|
||||
let app = Shell.AppSystem.get_default().get_app('gnome-datetime-panel.desktop');
|
||||
app.activate(-1);
|
||||
},
|
||||
|
@ -61,7 +61,7 @@ function addDragMonitor(monitor) {
|
||||
dragMonitors.push(monitor);
|
||||
}
|
||||
|
||||
function removeMonitor(monitor) {
|
||||
function removeDragMonitor(monitor) {
|
||||
for (let i = 0; i < dragMonitors.length; i++)
|
||||
if (dragMonitors[i] == monitor) {
|
||||
dragMonitors.splice(i, 1);
|
||||
@ -284,13 +284,13 @@ _Draggable.prototype = {
|
||||
this._dragOffsetY = actorStageY - this._dragStartY;
|
||||
|
||||
// Set the actor's scale such that it will keep the same
|
||||
// transformed size when it's reparented to the stage
|
||||
// transformed size when it's reparented to the uiGroup
|
||||
let [scaledWidth, scaledHeight] = this.actor.get_transformed_size();
|
||||
this.actor.set_scale(scaledWidth / this.actor.width,
|
||||
scaledHeight / this.actor.height);
|
||||
}
|
||||
|
||||
this._dragActor.reparent(this.actor.get_stage());
|
||||
this._dragActor.reparent(Main.uiGroup);
|
||||
this._dragActor.raise_top();
|
||||
Shell.util_set_hidden_from_pick(this._dragActor, true);
|
||||
|
||||
@ -442,7 +442,7 @@ _Draggable.prototype = {
|
||||
return true;
|
||||
// If it accepted the drop without taking the actor,
|
||||
// handle it ourselves.
|
||||
if (this._dragActor.get_parent() == this._dragActor.get_stage()) {
|
||||
if (this._dragActor.get_parent() == Main.uiGroup) {
|
||||
if (this._restoreOnSuccess) {
|
||||
this._restoreDragActor(event.get_time());
|
||||
return true;
|
||||
|
@ -39,35 +39,6 @@ function _patchContainerClass(containerClass) {
|
||||
};
|
||||
}
|
||||
|
||||
// Replace @method with something that throws an error instead
|
||||
function _blockMethod(method, replacement, reason) {
|
||||
let match = method.match(/^(.+)\.([^.]+)$/);
|
||||
if (!match)
|
||||
throw new Error('Bad method name "' + method + '"');
|
||||
let proto = 'imports.gi.' + match[1] + '.prototype';
|
||||
let property = match[2];
|
||||
|
||||
if (!global.set_property_mutable(proto, property, true))
|
||||
throw new Error('Bad method name "' + method + '"');
|
||||
|
||||
// eval() is evil in general, but we know it's safe here since
|
||||
// set_property_mutable() would have failed if proto was
|
||||
// malformed.
|
||||
let node = eval(proto);
|
||||
|
||||
let msg = 'Do not use "' + method + '".';
|
||||
if (replacement)
|
||||
msg += ' Use "' + replacement + '" instead.';
|
||||
if (reason)
|
||||
msg += ' (' + reason + ')';
|
||||
|
||||
node[property] = function() {
|
||||
throw new Error(msg);
|
||||
};
|
||||
|
||||
global.set_property_mutable(proto, property, false);
|
||||
}
|
||||
|
||||
function init() {
|
||||
// Add some bindings to the global JS namespace; (gjs keeps the web
|
||||
// browser convention of having that namespace be called 'window'.)
|
||||
@ -99,17 +70,6 @@ function init() {
|
||||
return base;
|
||||
};
|
||||
|
||||
_blockMethod('Clutter.Event.get_state', 'Shell.get_event_state',
|
||||
'gjs\'s handling of Clutter.ModifierType is broken. See bug 597292.');
|
||||
_blockMethod('Gdk.Window.get_device_position', 'global.get_pointer',
|
||||
'gjs\'s handling of Gdk.ModifierType is broken. See bug 597292.');
|
||||
|
||||
// Now close the back door to prevent extensions from trying to
|
||||
// abuse it. We can't actually delete it since
|
||||
// Shell.Global.prototype itself is read-only.
|
||||
global.set_property_mutable('imports.gi.Shell.Global.prototype', 'set_property_mutable', true);
|
||||
Shell.Global.prototype.set_property_mutable = undefined;
|
||||
|
||||
// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=508783
|
||||
Date.prototype.toLocaleFormat = function(format) {
|
||||
return Shell.util_format_date(format, this.getTime());
|
||||
|
@ -23,8 +23,9 @@ const ExtensionType = {
|
||||
const extensionMeta = {};
|
||||
// Maps uuid -> importer object (extension directory tree)
|
||||
const extensions = {};
|
||||
// Array of uuids
|
||||
// Arrays of uuids
|
||||
var disabledExtensions;
|
||||
var enabledExtensions;
|
||||
// GFile for user extensions
|
||||
var userExtensionsDir = null;
|
||||
|
||||
@ -178,6 +179,7 @@ function init() {
|
||||
}
|
||||
|
||||
disabledExtensions = global.settings.get_strv('disabled-extensions', -1);
|
||||
enabledExtensions = global.settings.get_strv('enabled-extensions', -1);
|
||||
}
|
||||
|
||||
function _loadExtensionsIn(dir, type) {
|
||||
@ -195,7 +197,10 @@ function _loadExtensionsIn(dir, type) {
|
||||
if (fileType != Gio.FileType.DIRECTORY)
|
||||
continue;
|
||||
let name = info.get_name();
|
||||
let enabled = disabledExtensions.indexOf(name) < 0;
|
||||
// Enable all but disabled extensions if enabledExtensions is not set.
|
||||
// If it is set, enable one those, except they are disabled as well.
|
||||
let enabled = (enabledExtensions.length == 0 || enabledExtensions.indexOf(name) >= 0)
|
||||
&& disabledExtensions.indexOf(name) < 0;
|
||||
let child = dir.get_child(name);
|
||||
loadExtension(child, enabled, type);
|
||||
}
|
||||
|
323
js/ui/layout.js
Normal file
323
js/ui/layout.js
Normal file
@ -0,0 +1,323 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Lang = imports.lang;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
const HOT_CORNER_ACTIVATION_TIMEOUT = 0.5;
|
||||
|
||||
function LayoutManager() {
|
||||
this._init.apply(this, arguments);
|
||||
}
|
||||
|
||||
LayoutManager.prototype = {
|
||||
_init: function () {
|
||||
this._rtl = (St.Widget.get_default_direction() == St.TextDirection.RTL);
|
||||
this.monitors = [];
|
||||
this.primaryMonitor = null;
|
||||
this.primaryIndex = -1;
|
||||
this._hotCorners = [];
|
||||
|
||||
this._updateMonitors();
|
||||
},
|
||||
|
||||
// This is called by Main after everything else is constructed;
|
||||
// _updateHotCorners needs access to Main.panel, which didn't exist
|
||||
// yet when the LayoutManager was constructed.
|
||||
init: function() {
|
||||
global.screen.connect('monitors-changed', Lang.bind(this, this._monitorsChanged));
|
||||
this._updateHotCorners();
|
||||
},
|
||||
|
||||
_updateMonitors: function() {
|
||||
let screen = global.screen;
|
||||
|
||||
this.monitors = [];
|
||||
let nMonitors = screen.get_n_monitors();
|
||||
for (let i = 0; i < nMonitors; i++)
|
||||
this.monitors.push(screen.get_monitor_geometry(i));
|
||||
|
||||
if (nMonitors == 1) {
|
||||
this.primaryIndex = this.bottomIndex = 0;
|
||||
} else {
|
||||
// If there are monitors below the primary, then we need
|
||||
// to split primary from bottom.
|
||||
this.primaryIndex = this.bottomIndex = screen.get_primary_monitor();
|
||||
for (let i = 0; i < this.monitors.length; i++) {
|
||||
let monitor = this.monitors[i];
|
||||
if (this._isAboveOrBelowPrimary(monitor)) {
|
||||
if (monitor.y > this.monitors[this.bottomIndex].y)
|
||||
this.bottomIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.primaryMonitor = this.monitors[this.primaryIndex];
|
||||
this.bottomMonitor = this.monitors[this.bottomIndex];
|
||||
},
|
||||
|
||||
_updateHotCorners: function() {
|
||||
// destroy old hot corners
|
||||
for (let i = 0; i < this._hotCorners.length; i++)
|
||||
this._hotCorners[i].destroy();
|
||||
this._hotCorners = [];
|
||||
|
||||
// build new hot corners
|
||||
for (let i = 0; i < this.monitors.length; i++) {
|
||||
if (i == this.primaryIndex)
|
||||
continue;
|
||||
|
||||
let monitor = this.monitors[i];
|
||||
let cornerX = this._rtl ? monitor.x + monitor.width : monitor.x;
|
||||
let cornerY = monitor.y;
|
||||
|
||||
let haveTopLeftCorner = true;
|
||||
|
||||
// Check if we have a top left (right for RTL) corner.
|
||||
// I.e. if there is no monitor directly above or to the left(right)
|
||||
let besideX = this._rtl ? monitor.x + 1 : cornerX - 1;
|
||||
let besideY = cornerY;
|
||||
let aboveX = cornerX;
|
||||
let aboveY = cornerY - 1;
|
||||
|
||||
for (let j = 0; j < this.monitors.length; j++) {
|
||||
if (i == j)
|
||||
continue;
|
||||
let otherMonitor = this.monitors[j];
|
||||
if (besideX >= otherMonitor.x &&
|
||||
besideX < otherMonitor.x + otherMonitor.width &&
|
||||
besideY >= otherMonitor.y &&
|
||||
besideY < otherMonitor.y + otherMonitor.height) {
|
||||
haveTopLeftCorner = false;
|
||||
break;
|
||||
}
|
||||
if (aboveX >= otherMonitor.x &&
|
||||
aboveX < otherMonitor.x + otherMonitor.width &&
|
||||
aboveY >= otherMonitor.y &&
|
||||
aboveY < otherMonitor.y + otherMonitor.height) {
|
||||
haveTopLeftCorner = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!haveTopLeftCorner)
|
||||
continue;
|
||||
|
||||
let corner = new HotCorner();
|
||||
this._hotCorners.push(corner);
|
||||
corner.actor.set_position(cornerX, cornerY);
|
||||
Main.chrome.addActor(corner.actor, { affectsStruts: false });
|
||||
}
|
||||
},
|
||||
|
||||
_monitorsChanged: function() {
|
||||
this._updateMonitors();
|
||||
this._updateHotCorners();
|
||||
|
||||
this.emit('monitors-changed');
|
||||
},
|
||||
|
||||
_isAboveOrBelowPrimary: function(monitor) {
|
||||
let primary = this.monitors[this.primaryIndex];
|
||||
let monitorLeft = monitor.x, monitorRight = monitor.x + monitor.width;
|
||||
let primaryLeft = primary.x, primaryRight = primary.x + primary.width;
|
||||
|
||||
if ((monitorLeft >= primaryLeft && monitorLeft <= primaryRight) ||
|
||||
(monitorRight >= primaryLeft && monitorRight <= primaryRight) ||
|
||||
(primaryLeft >= monitorLeft && primaryLeft <= monitorRight) ||
|
||||
(primaryRight >= monitorLeft && primaryRight <= monitorRight))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
get focusIndex() {
|
||||
let screen = global.screen;
|
||||
let display = screen.get_display();
|
||||
let focusWindow = display.focus_window;
|
||||
|
||||
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];
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(LayoutManager.prototype);
|
||||
|
||||
|
||||
// HotCorner:
|
||||
//
|
||||
// This class manages a "hot corner" that can toggle switching to
|
||||
// overview.
|
||||
function HotCorner() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
HotCorner.prototype = {
|
||||
_init : function() {
|
||||
// We use this flag to mark the case where the user has entered the
|
||||
// hot corner and has not left both the hot corner and a surrounding
|
||||
// guard area (the "environs"). This avoids triggering the hot corner
|
||||
// multiple times due to an accidental jitter.
|
||||
this._entered = false;
|
||||
|
||||
this.actor = new Clutter.Group({ name: 'hot-corner-environs',
|
||||
width: 3,
|
||||
height: 3,
|
||||
reactive: true });
|
||||
|
||||
this._corner = new Clutter.Rectangle({ name: 'hot-corner',
|
||||
width: 1,
|
||||
height: 1,
|
||||
opacity: 0,
|
||||
reactive: true });
|
||||
this._corner._delegate = this;
|
||||
|
||||
this.actor.add_actor(this._corner);
|
||||
|
||||
if (St.Widget.get_default_direction() == St.TextDirection.RTL) {
|
||||
this._corner.set_position(this.actor.width - this._corner.width, 0);
|
||||
this.actor.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
|
||||
} else {
|
||||
this._corner.set_position(0, 0);
|
||||
}
|
||||
|
||||
this._activationTime = 0;
|
||||
|
||||
this.actor.connect('leave-event',
|
||||
Lang.bind(this, this._onEnvironsLeft));
|
||||
|
||||
// Clicking on the hot corner environs should result in the
|
||||
// same behavior as clicking on the hot corner.
|
||||
this.actor.connect('button-release-event',
|
||||
Lang.bind(this, this._onCornerClicked));
|
||||
|
||||
// In addition to being triggered by the mouse enter event,
|
||||
// the hot corner can be triggered by clicking on it. This is
|
||||
// useful if the user wants to undo the effect of triggering
|
||||
// the hot corner once in the hot corner.
|
||||
this._corner.connect('enter-event',
|
||||
Lang.bind(this, this._onCornerEntered));
|
||||
this._corner.connect('button-release-event',
|
||||
Lang.bind(this, this._onCornerClicked));
|
||||
this._corner.connect('leave-event',
|
||||
Lang.bind(this, this._onCornerLeft));
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this.actor.destroy();
|
||||
},
|
||||
|
||||
_addRipple : function(delay, time, startScale, startOpacity, finalScale, finalOpacity) {
|
||||
// We draw a ripple by using a source image and animating it scaling
|
||||
// outwards and fading away. We want the ripples to move linearly
|
||||
// or it looks unrealistic, but if the opacity of the ripple goes
|
||||
// linearly to zero it fades away too quickly, so we use Tweener's
|
||||
// 'onUpdate' to give a non-linear curve to the fade-away and make
|
||||
// it more visible in the middle section.
|
||||
|
||||
let [x, y] = this._corner.get_transformed_position();
|
||||
let ripple = new St.BoxLayout({ style_class: 'ripple-box',
|
||||
opacity: 255 * Math.sqrt(startOpacity),
|
||||
scale_x: startScale,
|
||||
scale_y: startScale,
|
||||
x: x,
|
||||
y: y });
|
||||
ripple._opacity = startOpacity;
|
||||
if (ripple.get_direction() == St.TextDirection.RTL)
|
||||
ripple.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
|
||||
Tweener.addTween(ripple, { _opacity: finalOpacity,
|
||||
scale_x: finalScale,
|
||||
scale_y: finalScale,
|
||||
delay: delay,
|
||||
time: time,
|
||||
transition: 'linear',
|
||||
onUpdate: function() { ripple.opacity = 255 * Math.sqrt(ripple._opacity); },
|
||||
onComplete: function() { ripple.destroy(); } });
|
||||
Main.uiGroup.add_actor(ripple);
|
||||
},
|
||||
|
||||
rippleAnimation: function() {
|
||||
// Show three concentric ripples expanding outwards; the exact
|
||||
// parameters were found by trial and error, so don't look
|
||||
// for them to make perfect sense mathematically
|
||||
|
||||
// delay time scale opacity => scale opacity
|
||||
this._addRipple(0.0, 0.83, 0.25, 1.0, 1.5, 0.0);
|
||||
this._addRipple(0.05, 1.0, 0.0, 0.7, 1.25, 0.0);
|
||||
this._addRipple(0.35, 1.0, 0.0, 0.3, 1, 0.0);
|
||||
},
|
||||
|
||||
handleDragOver: function(source, actor, x, y, time) {
|
||||
if (source != Main.xdndHandler)
|
||||
return;
|
||||
|
||||
if (!Main.overview.visible && !Main.overview.animationInProgress) {
|
||||
this.rippleAnimation();
|
||||
Main.overview.showTemporarily();
|
||||
Main.overview.beginItemDrag(actor);
|
||||
}
|
||||
},
|
||||
|
||||
_onCornerEntered : function() {
|
||||
if (!this._entered) {
|
||||
this._entered = true;
|
||||
if (!Main.overview.animationInProgress) {
|
||||
this._activationTime = Date.now() / 1000;
|
||||
|
||||
this.rippleAnimation();
|
||||
Main.overview.toggle();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_onCornerClicked : function() {
|
||||
if (this.shouldToggleOverviewOnClick())
|
||||
Main.overview.toggle();
|
||||
return true;
|
||||
},
|
||||
|
||||
_onCornerLeft : function(actor, event) {
|
||||
if (event.get_related() != this.actor)
|
||||
this._entered = false;
|
||||
// Consume event, otherwise this will confuse onEnvironsLeft
|
||||
return true;
|
||||
},
|
||||
|
||||
_onEnvironsLeft : function(actor, event) {
|
||||
if (event.get_related() != this._corner)
|
||||
this._entered = false;
|
||||
return false;
|
||||
},
|
||||
|
||||
// Checks if the Activities button is currently sensitive to
|
||||
// clicks. The first call to this function within the
|
||||
// HOT_CORNER_ACTIVATION_TIMEOUT time of the hot corner being
|
||||
// triggered will return false. This avoids opening and closing
|
||||
// the overview if the user both triggered the hot corner and
|
||||
// clicked the Activities button.
|
||||
shouldToggleOverviewOnClick: function() {
|
||||
if (Main.overview.animationInProgress)
|
||||
return false;
|
||||
if (this._activationTime == 0 || Date.now() / 1000 - this._activationTime > HOT_CORNER_ACTIVATION_TIMEOUT)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
};
|
@ -436,7 +436,7 @@ Inspector.prototype = {
|
||||
if (!this._eventHandler)
|
||||
return;
|
||||
|
||||
let primary = global.get_primary_monitor();
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
|
||||
let [minWidth, minHeight, natWidth, natHeight] =
|
||||
this._eventHandler.get_preferred_size();
|
||||
@ -582,6 +582,53 @@ ErrorLog.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
function Memory() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
Memory.prototype = {
|
||||
_init: function() {
|
||||
this.actor = new St.BoxLayout({ vertical: true });
|
||||
this._glibc_uordblks = new St.Label();
|
||||
this.actor.add(this._glibc_uordblks);
|
||||
|
||||
this._js_bytes = new St.Label();
|
||||
this.actor.add(this._js_bytes);
|
||||
|
||||
this._gjs_boxed = new St.Label();
|
||||
this.actor.add(this._gjs_boxed);
|
||||
|
||||
this._gjs_gobject = new St.Label();
|
||||
this.actor.add(this._gjs_gobject);
|
||||
|
||||
this._gjs_function = new St.Label();
|
||||
this.actor.add(this._gjs_function);
|
||||
|
||||
this._gjs_closure = new St.Label();
|
||||
this.actor.add(this._gjs_closure);
|
||||
|
||||
this._gcbutton = new St.Button({ label: 'Full GC',
|
||||
style_class: 'lg-obj-inspector-button' });
|
||||
this._gcbutton.connect('clicked', Lang.bind(this, function () { global.gc(); this._renderText(); }));
|
||||
this.actor.add(this._gcbutton, { x_align: St.Align.START,
|
||||
x_fill: false });
|
||||
|
||||
this.actor.connect('notify::mapped', Lang.bind(this, this._renderText));
|
||||
},
|
||||
|
||||
_renderText: function() {
|
||||
if (!this.actor.mapped)
|
||||
return;
|
||||
let memInfo = global.get_memory_info();
|
||||
this._glibc_uordblks.text = 'glibc_uordblks: ' + memInfo.glibc_uordblks;
|
||||
this._js_bytes.text = 'js bytes: ' + memInfo.js_bytes;
|
||||
this._gjs_boxed.text = 'gjs_boxed: ' + memInfo.gjs_boxed;
|
||||
this._gjs_gobject.text = 'gjs_gobject: ' + memInfo.gjs_gobject;
|
||||
this._gjs_function.text = 'gjs_function: ' + memInfo.gjs_function;
|
||||
this._gjs_closure.text = 'gjs_closure: ' + memInfo.gjs_closure;
|
||||
}
|
||||
};
|
||||
|
||||
function Extensions() {
|
||||
this._init();
|
||||
}
|
||||
@ -764,6 +811,9 @@ LookingGlass.prototype = {
|
||||
this._errorLog = new ErrorLog();
|
||||
notebook.appendPage('Errors', this._errorLog.actor);
|
||||
|
||||
this._memory = new Memory();
|
||||
notebook.appendPage('Memory', this._memory.actor);
|
||||
|
||||
this._extensions = new Extensions();
|
||||
notebook.appendPage('Extensions', this._extensions.actor);
|
||||
|
||||
@ -857,7 +907,7 @@ LookingGlass.prototype = {
|
||||
},
|
||||
|
||||
_resizeTo: function(actor) {
|
||||
let primary = global.get_primary_monitor();
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
let myWidth = primary.width * 0.7;
|
||||
let myHeight = primary.height * 0.7;
|
||||
let [srcX, srcY] = actor.get_transformed_position();
|
||||
|
118
js/ui/main.js
118
js/ui/main.js
@ -12,6 +12,8 @@ const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const AutomountManager = imports.ui.automountManager;
|
||||
const AutorunManager = imports.ui.autorunManager;
|
||||
const Chrome = imports.ui.chrome;
|
||||
const CtrlAltTab = imports.ui.ctrlAltTab;
|
||||
const EndSessionDialog = imports.ui.endSessionDialog;
|
||||
@ -23,6 +25,7 @@ const Overview = imports.ui.overview;
|
||||
const Panel = imports.ui.panel;
|
||||
const PlaceDisplay = imports.ui.placeDisplay;
|
||||
const RunDialog = imports.ui.runDialog;
|
||||
const Layout = imports.ui.layout;
|
||||
const LookingGlass = imports.ui.lookingGlass;
|
||||
const NotificationDaemon = imports.ui.notificationDaemon;
|
||||
const WindowAttentionHandler = imports.ui.windowAttentionHandler;
|
||||
@ -38,6 +41,8 @@ const Util = imports.misc.util;
|
||||
const DEFAULT_BACKGROUND_COLOR = new Clutter.Color();
|
||||
DEFAULT_BACKGROUND_COLOR.from_pixel(0x2266bbff);
|
||||
|
||||
let automountManager = null;
|
||||
let autorunManager = null;
|
||||
let chrome = null;
|
||||
let panel = null;
|
||||
let hotCorners = [];
|
||||
@ -59,6 +64,7 @@ let uiGroup = null;
|
||||
let magnifier = null;
|
||||
let xdndHandler = null;
|
||||
let statusIconDispatcher = null;
|
||||
let layoutManager = null;
|
||||
let _errorLogStack = [];
|
||||
let _startDate;
|
||||
let _defaultCssStylesheet = null;
|
||||
@ -126,6 +132,7 @@ function start() {
|
||||
global.overlay_group.reparent(uiGroup);
|
||||
global.stage.add_actor(uiGroup);
|
||||
|
||||
layoutManager = new Layout.LayoutManager();
|
||||
placesManager = new PlaceDisplay.PlacesManager();
|
||||
xdndHandler = new XdndHandler.XdndHandler();
|
||||
ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager();
|
||||
@ -139,7 +146,10 @@ function start() {
|
||||
notificationDaemon = new NotificationDaemon.NotificationDaemon();
|
||||
windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler();
|
||||
telepathyClient = new TelepathyClient.Client();
|
||||
automountManager = new AutomountManager.AutomountManager();
|
||||
autorunManager = new AutorunManager.AutorunManager();
|
||||
|
||||
layoutManager.init();
|
||||
overview.init();
|
||||
statusIconDispatcher.start(messageTray.actor);
|
||||
|
||||
@ -178,14 +188,9 @@ function start() {
|
||||
// Attempt to become a PolicyKit authentication agent
|
||||
PolkitAuthenticationAgent.init()
|
||||
|
||||
global.screen.connect('monitors-changed', _relayout);
|
||||
|
||||
ExtensionSystem.init();
|
||||
ExtensionSystem.loadExtensions();
|
||||
|
||||
// Perform initial relayout here
|
||||
_relayout();
|
||||
|
||||
panel.startStatusArea();
|
||||
panel.startupAnimation();
|
||||
|
||||
@ -208,6 +213,7 @@ function start() {
|
||||
|
||||
global.screen.connect('window-entered-monitor', _windowEnteredMonitor);
|
||||
global.screen.connect('window-left-monitor', _windowLeftMonitor);
|
||||
global.screen.connect('restacked', _windowsRestacked);
|
||||
|
||||
_nWorkspacesChanged();
|
||||
}
|
||||
@ -299,17 +305,24 @@ function _windowRemoved(workspace, window) {
|
||||
function _windowLeftMonitor(metaScreen, monitorIndex, metaWin) {
|
||||
// If the window left the primary monitor, that
|
||||
// might make that workspace empty
|
||||
if (monitorIndex == global.get_primary_monitor_index())
|
||||
if (monitorIndex == layoutManager.primaryIndex)
|
||||
_queueCheckWorkspaces();
|
||||
}
|
||||
|
||||
function _windowEnteredMonitor(metaScreen, monitorIndex, metaWin) {
|
||||
// If the window entered the primary monitor, that
|
||||
// might make that workspace non-empty
|
||||
if (monitorIndex == global.get_primary_monitor_index())
|
||||
if (monitorIndex == layoutManager.primaryIndex)
|
||||
_queueCheckWorkspaces();
|
||||
}
|
||||
|
||||
function _windowsRestacked() {
|
||||
// Figure out where the pointer is in case we lost track of
|
||||
// it during a grab. (In particular, if a trayicon popup menu
|
||||
// is dismissed, see if we need to close the message tray.)
|
||||
global.sync_pointer();
|
||||
}
|
||||
|
||||
function _queueCheckWorkspaces() {
|
||||
if (_checkWorkspacesId == 0)
|
||||
_checkWorkspacesId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, _checkWorkspaces);
|
||||
@ -393,12 +406,21 @@ function setThemeStylesheet(cssStylesheet)
|
||||
*/
|
||||
function loadTheme() {
|
||||
let themeContext = St.ThemeContext.get_for_stage (global.stage);
|
||||
let previousTheme = themeContext.get_theme();
|
||||
|
||||
let cssStylesheet = _defaultCssStylesheet;
|
||||
if (_cssStylesheet != null)
|
||||
cssStylesheet = _cssStylesheet;
|
||||
|
||||
let theme = new St.Theme ({ application_stylesheet: cssStylesheet });
|
||||
|
||||
if (previousTheme) {
|
||||
let customStylesheets = previousTheme.get_custom_stylesheets();
|
||||
|
||||
for (let i = 0; i < customStylesheets.length; i++)
|
||||
theme.load_stylesheet(customStylesheets[i]);
|
||||
}
|
||||
|
||||
themeContext.set_theme (theme);
|
||||
}
|
||||
|
||||
@ -464,80 +486,16 @@ function _getAndClearErrorStack() {
|
||||
return errors;
|
||||
}
|
||||
|
||||
function _relayout() {
|
||||
let monitors = global.get_monitors();
|
||||
// destroy old corners
|
||||
for (let i = 0; i < hotCorners.length; i++)
|
||||
hotCorners[i].destroy();
|
||||
hotCorners = [];
|
||||
|
||||
|
||||
let primary = global.get_primary_monitor();
|
||||
for (let i = 0; i < monitors.length; i++) {
|
||||
let monitor = monitors[i];
|
||||
let isPrimary = (monitor.x == primary.x &&
|
||||
monitor.y == primary.y &&
|
||||
monitor.width == primary.width &&
|
||||
monitor.height == primary.height);
|
||||
|
||||
let cornerX = monitor.x;
|
||||
let cornerY = monitor.y;
|
||||
if (St.Widget.get_default_direction() == St.TextDirection.RTL)
|
||||
cornerX += monitor.width;
|
||||
|
||||
|
||||
let haveTopLeftCorner = true;
|
||||
|
||||
/* Check if we have a top left (right for RTL) corner.
|
||||
* I.e. if there is no monitor directly above or to the left(right) */
|
||||
let besideX;
|
||||
if (St.Widget.get_default_direction() == St.TextDirection.RTL)
|
||||
besideX = monitor.x + 1;
|
||||
else
|
||||
besideX = cornerX - 1;
|
||||
let besideY = cornerY;
|
||||
let aboveX = cornerX;
|
||||
let aboveY = cornerY - 1;
|
||||
|
||||
for (let j = 0; j < monitors.length; j++) {
|
||||
if (i == j)
|
||||
continue;
|
||||
let otherMonitor = monitors[j];
|
||||
if (besideX >= otherMonitor.x &&
|
||||
besideX < otherMonitor.x + otherMonitor.width &&
|
||||
besideY >= otherMonitor.y &&
|
||||
besideY < otherMonitor.y + otherMonitor.height) {
|
||||
haveTopLeftCorner = false;
|
||||
break;
|
||||
}
|
||||
if (aboveX >= otherMonitor.x &&
|
||||
aboveX < otherMonitor.x + otherMonitor.width &&
|
||||
aboveY >= otherMonitor.y &&
|
||||
aboveY < otherMonitor.y + otherMonitor.height) {
|
||||
haveTopLeftCorner = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* We only want hot corners where there is a natural top-left
|
||||
* corner, and on the primary monitor */
|
||||
if (!isPrimary && !haveTopLeftCorner)
|
||||
continue;
|
||||
|
||||
let corner = new Panel.HotCorner(isPrimary ? panel.button : null);
|
||||
hotCorners.push(corner);
|
||||
corner.actor.set_position(cornerX, cornerY);
|
||||
if (isPrimary)
|
||||
panel.setHotCorner(corner);
|
||||
function logStackTrace(msg) {
|
||||
try {
|
||||
throw new Error();
|
||||
} catch (e) {
|
||||
// e.stack must have at least two lines, with the first being
|
||||
// logStackTrace() (which we strip off), and the second being
|
||||
// our caller.
|
||||
let trace = e.stack.substr(e.stack.indexOf('\n') + 1);
|
||||
log(msg ? (msg + '\n' + trace) : trace);
|
||||
}
|
||||
|
||||
panel.relayout();
|
||||
overview.relayout();
|
||||
|
||||
// To avoid updating the position and size of the workspaces
|
||||
// in the overview, we just hide the overview. The positions
|
||||
// will be updated when it is next shown.
|
||||
overview.hide();
|
||||
}
|
||||
|
||||
function isWindowActorDisplayedOnWorkspace(win, workspaceIndex) {
|
||||
|
@ -68,14 +68,19 @@ function _fixMarkup(text, allowMarkup) {
|
||||
// Support &, ", ', < and >, escape all other
|
||||
// occurrences of '&'.
|
||||
let _text = text.replace(/&(?!amp;|quot;|apos;|lt;|gt;)/g, '&');
|
||||
|
||||
// Support <b>, <i>, and <u>, escape anything else
|
||||
// so it displays as raw markup.
|
||||
return _text.replace(/<(\/?[^biu]>|[^>\/][^>])/g, '<$1');
|
||||
} else {
|
||||
// Escape everything
|
||||
let _text = text.replace(/&/g, '&');
|
||||
return _text.replace(/</g, '<');
|
||||
_text = _text.replace(/<(?!\/?[biu]>)/g, '<');
|
||||
|
||||
try {
|
||||
Pango.parse_markup(_text, -1, '');
|
||||
return _text;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// !allowMarkup, or invalid markup
|
||||
return GLib.markup_escape_text(text, -1);
|
||||
}
|
||||
|
||||
function URLHighlighter(text, lineWrap, allowMarkup) {
|
||||
@ -557,7 +562,7 @@ Notification.prototype = {
|
||||
this._scrollArea = new St.ScrollView({ name: 'notification-scrollview',
|
||||
vscrollbar_policy: this._scrollPolicy,
|
||||
hscrollbar_policy: Gtk.PolicyType.NEVER,
|
||||
vfade: true });
|
||||
style_class: 'vfade' });
|
||||
this._table.add(this._scrollArea, { row: 1, col: 1 });
|
||||
this._contentArea = new St.BoxLayout({ name: 'notification-body',
|
||||
vertical: true });
|
||||
@ -829,6 +834,7 @@ Notification.prototype = {
|
||||
// Restore banner opacity in case the notification is shown in the
|
||||
// banner mode again on update.
|
||||
this._bannerLabel.opacity = 255;
|
||||
this.emit('collapsed');
|
||||
},
|
||||
|
||||
_onActionInvoked: function(actor, mouseButtonClicked, id) {
|
||||
@ -879,20 +885,96 @@ Source.prototype = {
|
||||
|
||||
_init: function(title) {
|
||||
this.title = title;
|
||||
|
||||
this.actor = new Shell.GenericContainer();
|
||||
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
||||
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
||||
this.actor.connect('destroy', Lang.bind(this,
|
||||
function() {
|
||||
this._actorDestroyed = true;
|
||||
}));
|
||||
this._actorDestroyed = false;
|
||||
|
||||
this._counterLabel = new St.Label();
|
||||
this._counterBin = new St.Bin({ style_class: 'summary-source-counter',
|
||||
child: this._counterLabel });
|
||||
this._counterBin.hide();
|
||||
|
||||
this._iconBin = new St.Bin({ width: this.ICON_SIZE,
|
||||
height: this.ICON_SIZE,
|
||||
x_fill: true,
|
||||
y_fill: true });
|
||||
|
||||
this.actor.add_actor(this._iconBin);
|
||||
this.actor.add_actor(this._counterBin);
|
||||
|
||||
this.isTransient = false;
|
||||
this.isChat = false;
|
||||
|
||||
this.notifications = [];
|
||||
},
|
||||
|
||||
_getPreferredWidth: function (actor, forHeight, alloc) {
|
||||
let [min, nat] = this._iconBin.get_preferred_width(forHeight);
|
||||
alloc.min_size = min; alloc.nat_size = nat;
|
||||
},
|
||||
|
||||
_getPreferredHeight: function (actor, forWidth, alloc) {
|
||||
let [min, nat] = this._iconBin.get_preferred_height(forWidth);
|
||||
alloc.min_size = min; alloc.nat_size = nat;
|
||||
},
|
||||
|
||||
_allocate: function(actor, box, flags) {
|
||||
// the iconBin should fill our entire box
|
||||
this._iconBin.allocate(box, flags);
|
||||
|
||||
let childBox = new Clutter.ActorBox();
|
||||
|
||||
let [minWidth, minHeight, naturalWidth, naturalHeight] = this._counterBin.get_preferred_size();
|
||||
let direction = this.actor.get_direction();
|
||||
|
||||
if (direction == St.TextDirection.LTR) {
|
||||
// allocate on the right in LTR
|
||||
childBox.x1 = box.x2 - naturalWidth;
|
||||
childBox.x2 = box.x2;
|
||||
} else {
|
||||
// allocate on the left in RTL
|
||||
childBox.x1 = 0;
|
||||
childBox.x2 = naturalWidth;
|
||||
}
|
||||
|
||||
childBox.y1 = box.y2 - naturalHeight;
|
||||
childBox.y2 = box.y2;
|
||||
|
||||
this._counterBin.allocate(childBox, flags);
|
||||
},
|
||||
|
||||
_setCount: function(count, visible) {
|
||||
if (isNaN(parseInt(count)))
|
||||
throw new Error("Invalid notification count: " + count);
|
||||
|
||||
if (this._actorDestroyed)
|
||||
return;
|
||||
|
||||
this._counterBin.visible = visible;
|
||||
this._counterLabel.set_text(count.toString());
|
||||
},
|
||||
|
||||
_updateCount: function() {
|
||||
let count = this.notifications.length;
|
||||
this._setCount(count, count > 1);
|
||||
},
|
||||
|
||||
setTransient: function(isTransient) {
|
||||
this.isTransient = isTransient;
|
||||
},
|
||||
|
||||
setTitle: function(newTitle) {
|
||||
this.title = newTitle;
|
||||
this.emit('title-changed');
|
||||
},
|
||||
|
||||
// Called to create a new icon actor (of size this.ICON_SIZE).
|
||||
// Must be overridden by the subclass if you do not pass icons
|
||||
// explicitly to the Notification() constructor.
|
||||
@ -903,7 +985,7 @@ Source.prototype = {
|
||||
// Unlike createNotificationIcon, this always returns the same actor;
|
||||
// there is only one summary icon actor for a Source.
|
||||
getSummaryIcon: function() {
|
||||
return this._iconBin;
|
||||
return this.actor;
|
||||
},
|
||||
|
||||
pushNotification: function(notification) {
|
||||
@ -922,7 +1004,11 @@ Source.prototype = {
|
||||
this.notifications.splice(index, 1);
|
||||
if (this.notifications.length == 0)
|
||||
this._lastNotificationRemoved();
|
||||
|
||||
this._updateCount();
|
||||
}));
|
||||
|
||||
this._updateCount();
|
||||
},
|
||||
|
||||
notify: function(notification) {
|
||||
@ -959,6 +1045,8 @@ Source.prototype = {
|
||||
for (let i = this.notifications.length - 1; i >= 0; i--)
|
||||
if (!this.notifications[i].resident)
|
||||
this.notifications[i].destroy();
|
||||
|
||||
this._updateCount();
|
||||
},
|
||||
|
||||
// Default implementation is to destroy this source, but subclasses can override
|
||||
@ -995,6 +1083,11 @@ SummaryItem.prototype = {
|
||||
this._sourceTitleBin.child = this._sourceTitle;
|
||||
this._sourceTitleBin.width = 0;
|
||||
|
||||
this.source.connect('title-changed',
|
||||
Lang.bind(this, function() {
|
||||
this._sourceTitle.text = source.title;
|
||||
}));
|
||||
|
||||
this._sourceBox.add(this._sourceIcon, { y_fill: false });
|
||||
this._sourceBox.add(this._sourceTitleBin, { expand: true, y_fill: false });
|
||||
this.actor.child = this._sourceBox;
|
||||
@ -1002,7 +1095,7 @@ SummaryItem.prototype = {
|
||||
this.notificationStackView = new St.ScrollView({ name: source.isChat ? '' : 'summary-notification-stack-scrollview',
|
||||
vscrollbar_policy: source.isChat ? Gtk.PolicyType.NEVER : Gtk.PolicyType.AUTOMATIC,
|
||||
hscrollbar_policy: Gtk.PolicyType.NEVER,
|
||||
vfade: true });
|
||||
style_class: 'vfade' });
|
||||
this.notificationStack = new St.BoxLayout({ name: 'summary-notification-stack',
|
||||
vertical: true });
|
||||
this.notificationStackView.add_actor(this.notificationStack);
|
||||
@ -1242,11 +1335,11 @@ MessageTray.prototype = {
|
||||
this._reNotifyAfterHideNotification = null;
|
||||
|
||||
Main.chrome.addActor(this.actor, { affectsStruts: false,
|
||||
visibleInOverview: true });
|
||||
visibleInFullscreen: true });
|
||||
Main.chrome.trackActor(this._notificationBin);
|
||||
Main.chrome.trackActor(this._summaryBoxPointer.actor);
|
||||
|
||||
global.screen.connect('monitors-changed', Lang.bind(this, this._setSizePosition));
|
||||
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._setSizePosition));
|
||||
|
||||
this._setSizePosition();
|
||||
|
||||
@ -1282,20 +1375,20 @@ MessageTray.prototype = {
|
||||
},
|
||||
|
||||
_setSizePosition: function() {
|
||||
let primary = global.get_primary_monitor();
|
||||
this.actor.x = primary.x;
|
||||
this.actor.y = primary.y + primary.height - 1;
|
||||
this.actor.width = primary.width;
|
||||
let monitor = Main.layoutManager.bottomMonitor;
|
||||
this.actor.x = monitor.x;
|
||||
this.actor.y = monitor.y + monitor.height - 1;
|
||||
this.actor.width = monitor.width;
|
||||
this._notificationBin.x = 0;
|
||||
this._notificationBin.width = primary.width;
|
||||
this._notificationBin.width = monitor.width;
|
||||
this._summaryBin.x = 0;
|
||||
this._summaryBin.width = primary.width;
|
||||
this._summaryBin.width = monitor.width;
|
||||
|
||||
if (this._pointerBarrier)
|
||||
global.destroy_pointer_barrier(this._pointerBarrier);
|
||||
this._pointerBarrier =
|
||||
global.create_pointer_barrier(primary.x + primary.width, primary.y + primary.height - this.actor.height,
|
||||
primary.x + primary.width, primary.y + primary.height,
|
||||
global.create_pointer_barrier(monitor.x + monitor.width, monitor.y + monitor.height - this.actor.height,
|
||||
monitor.x + monitor.width, monitor.y + monitor.height,
|
||||
4 /* BarrierNegativeX */);
|
||||
|
||||
|
||||
@ -1605,6 +1698,8 @@ MessageTray.prototype = {
|
||||
this._clickedSummaryItemMouseButton != button) {
|
||||
this._clickedSummaryItem = summaryItem;
|
||||
this._clickedSummaryItemMouseButton = button;
|
||||
|
||||
summaryItem.source.emit('summary-item-clicked', button);
|
||||
} else {
|
||||
this._unsetClickedSummaryItem();
|
||||
}
|
||||
@ -1854,18 +1949,18 @@ MessageTray.prototype = {
|
||||
},
|
||||
|
||||
_showTray: function() {
|
||||
let primary = global.get_primary_monitor();
|
||||
let monitor = Main.layoutManager.bottomMonitor;
|
||||
this._tween(this.actor, '_trayState', State.SHOWN,
|
||||
{ y: primary.y + primary.height - this.actor.height,
|
||||
{ y: monitor.y + monitor.height - this.actor.height,
|
||||
time: ANIMATION_TIME,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
},
|
||||
|
||||
_hideTray: function() {
|
||||
let primary = global.get_primary_monitor();
|
||||
let monitor = Main.layoutManager.bottomMonitor;
|
||||
this._tween(this.actor, '_trayState', State.HIDDEN,
|
||||
{ y: primary.y + primary.height - 1,
|
||||
{ y: monitor.y + monitor.height - 1,
|
||||
time: ANIMATION_TIME,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
@ -2011,9 +2106,15 @@ MessageTray.prototype = {
|
||||
this._notification.expand(!autoExpanding);
|
||||
},
|
||||
|
||||
_onNotificationExpanded: function() {
|
||||
_onNotificationExpanded: function() {
|
||||
let expandedY = this.actor.height - this._notificationBin.height;
|
||||
if (this._notificationBin.y != expandedY)
|
||||
|
||||
// Don't animate the notification to its new position if it has shrunk:
|
||||
// there will be a very visible "gap" that breaks the illusion.
|
||||
|
||||
if (this._notificationBin.y < expandedY)
|
||||
this._notificationBin.y = expandedY;
|
||||
else if (this._notification.y != expandedY)
|
||||
this._tween(this._notificationBin, '_notificationState', State.SHOWN,
|
||||
{ y: expandedY,
|
||||
time: ANIMATION_TIME,
|
||||
@ -2028,7 +2129,6 @@ MessageTray.prototype = {
|
||||
},
|
||||
|
||||
_showSummary: function(timeout) {
|
||||
let primary = global.get_primary_monitor();
|
||||
this._summaryBin.opacity = 0;
|
||||
this._summaryBin.y = this.actor.height;
|
||||
this._tween(this._summaryBin, '_summaryState', State.SHOWN,
|
||||
|
@ -149,7 +149,7 @@ ModalDialog.prototype = {
|
||||
},
|
||||
|
||||
_fadeOpen: function() {
|
||||
let monitor = global.get_focus_monitor();
|
||||
let monitor = Main.layoutManager.focusMonitor;
|
||||
|
||||
this._backgroundBin.set_position(monitor.x, monitor.y);
|
||||
this._backgroundBin.set_size(monitor.width, monitor.height);
|
||||
|
@ -122,7 +122,7 @@ NotificationDaemon.prototype = {
|
||||
} else if (hints['image-data']) {
|
||||
let [width, height, rowStride, hasAlpha,
|
||||
bitsPerSample, nChannels, data] = hints['image-data'];
|
||||
return textureCache.load_from_raw(data, data.length, hasAlpha,
|
||||
return textureCache.load_from_raw(data, hasAlpha,
|
||||
width, height, rowStride, size);
|
||||
} else {
|
||||
let stockIcon;
|
||||
@ -148,7 +148,7 @@ NotificationDaemon.prototype = {
|
||||
//
|
||||
// Either a pid or ndata.notification is needed to retrieve or
|
||||
// create a source.
|
||||
_getSource: function(title, pid, ndata) {
|
||||
_getSource: function(title, pid, ndata, sender) {
|
||||
if (!pid && !(ndata && ndata.notification))
|
||||
return null;
|
||||
|
||||
@ -165,10 +165,13 @@ NotificationDaemon.prototype = {
|
||||
// with a transient one from the same sender, so we
|
||||
// always create a new source object for new transient notifications
|
||||
// and never add it to this._sources .
|
||||
if (!isForTransientNotification && this._sources[pid])
|
||||
return this._sources[pid];
|
||||
if (!isForTransientNotification && this._sources[pid]) {
|
||||
let source = this._sources[pid];
|
||||
source.setTitle(title);
|
||||
return source;
|
||||
}
|
||||
|
||||
let source = new Source(title, pid);
|
||||
let source = new Source(title, pid, sender);
|
||||
source.setTransient(isForTransientNotification);
|
||||
|
||||
if (!isForTransientNotification) {
|
||||
@ -187,9 +190,11 @@ NotificationDaemon.prototype = {
|
||||
actions, hints, timeout) {
|
||||
let id;
|
||||
|
||||
// Filter out chat and presence notifications from Empathy, since we
|
||||
// handle that information from telepathyClient.js
|
||||
// Filter out chat, presence, calls and invitation notifications from
|
||||
// Empathy, since we handle that information from telepathyClient.js
|
||||
if (appName == 'Empathy' && (hints['category'] == 'im.received' ||
|
||||
hints['category'] == 'x-empathy.im.room-invitation' ||
|
||||
hints['category'] == 'x-empathy.call.incoming' ||
|
||||
hints['category'] == 'presence.online' ||
|
||||
hints['category'] == 'presence.offline')) {
|
||||
// Ignore replacesId since we already sent back a
|
||||
@ -241,7 +246,7 @@ NotificationDaemon.prototype = {
|
||||
let sender = DBus.getCurrentMessageContext().sender;
|
||||
let pid = this._senderToPid[sender];
|
||||
|
||||
let source = this._getSource(appName, pid, ndata);
|
||||
let source = this._getSource(appName, pid, ndata, sender);
|
||||
|
||||
if (source) {
|
||||
this._notifyForSource(source, ndata);
|
||||
@ -262,7 +267,7 @@ NotificationDaemon.prototype = {
|
||||
if (!ndata)
|
||||
return;
|
||||
|
||||
source = this._getSource(appName, pid, ndata);
|
||||
source = this._getSource(appName, pid, ndata, sender);
|
||||
|
||||
// We only store sender-pid entries for persistent sources.
|
||||
// Removing the entries once the source is destroyed
|
||||
@ -411,7 +416,7 @@ NotificationDaemon.prototype = {
|
||||
},
|
||||
|
||||
_onTrayIconAdded: function(o, icon) {
|
||||
let source = this._getSource(icon.title || icon.wm_class || _("Unknown"), icon.pid, null);
|
||||
let source = this._getSource(icon.title || icon.wm_class || _("Unknown"), icon.pid, null, null);
|
||||
source.setTrayIcon(icon);
|
||||
},
|
||||
|
||||
@ -424,18 +429,28 @@ NotificationDaemon.prototype = {
|
||||
|
||||
DBus.conformExport(NotificationDaemon.prototype, NotificationDaemonIface);
|
||||
|
||||
function Source(title, pid) {
|
||||
this._init(title, pid);
|
||||
function Source(title, pid, sender) {
|
||||
this._init(title, pid, sender);
|
||||
}
|
||||
|
||||
Source.prototype = {
|
||||
__proto__: MessageTray.Source.prototype,
|
||||
|
||||
_init: function(title, pid) {
|
||||
_init: function(title, pid, sender) {
|
||||
MessageTray.Source.prototype._init.call(this, title);
|
||||
|
||||
this._pid = pid;
|
||||
this._appStateChangedId = 0;
|
||||
if (sender)
|
||||
// TODO: dbus-glib implementation of watch_name() doesn’t return an id to be used for
|
||||
// unwatch_name() or implement unwatch_name(), however when we move to using GDBus implementation,
|
||||
// we should save the id here and call unwatch_name() with it in destroy().
|
||||
// Moving to GDBus is the work in progress: https://bugzilla.gnome.org/show_bug.cgi?id=648651
|
||||
// and https://bugzilla.gnome.org/show_bug.cgi?id=622921 .
|
||||
DBus.session.watch_name(sender,
|
||||
false,
|
||||
null,
|
||||
Lang.bind(this, this._onNameVanished));
|
||||
|
||||
this._setApp();
|
||||
if (this.app)
|
||||
this.title = this.app.get_name();
|
||||
@ -444,6 +459,16 @@ Source.prototype = {
|
||||
this._trayIcon = null;
|
||||
},
|
||||
|
||||
_onNameVanished: function() {
|
||||
// Destroy the notification source when its sender is removed from DBus.
|
||||
// Only do so if this.app is set to avoid removing "notify-send" sources, senders
|
||||
// of which аre removed from DBus immediately.
|
||||
// Sender being removed from DBus would normally result in a tray icon being removed,
|
||||
// so allow the code path that handles the tray icon being removed to handle that case.
|
||||
if (!this.trayIcon && this.app)
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
processNotification: function(notification, icon) {
|
||||
if (!this.app)
|
||||
this._setApp();
|
||||
@ -496,10 +521,6 @@ Source.prototype = {
|
||||
if (!this.app)
|
||||
return;
|
||||
|
||||
// We only update the app if this.app is null, so we can't disconnect the old this._appStateChangedId
|
||||
// even if it were non-zero for some reason.
|
||||
this._appStateChangedId = this.app.connect('notify::state', Lang.bind(this, this._appStateChanged));
|
||||
|
||||
// Only override the icon if we were previously using
|
||||
// notification-based icons (ie, not a trayicon) or if it was unset before
|
||||
if (!this._trayIcon) {
|
||||
@ -524,19 +545,6 @@ Source.prototype = {
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
_appStateChanged: function() {
|
||||
// Destroy notification sources when their apps exit.
|
||||
// The app exiting would normally result in a tray icon being removed,
|
||||
// so the associated source would be destroyed through the code path
|
||||
// that handles the tray icon being removed. We should not destroy
|
||||
// the source associated with a tray icon when the application state
|
||||
// is Shell.AppState.STOPPED because running applications that have
|
||||
// no open windows would also have that state. This is often the case
|
||||
// for applications that use tray icons.
|
||||
if (!this._trayIcon && this.app.get_state() == Shell.AppState.STOPPED)
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
openApp: function() {
|
||||
if (this.app == null)
|
||||
return;
|
||||
@ -549,10 +557,6 @@ Source.prototype = {
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this.app && this._appStateChangedId) {
|
||||
this.app.disconnect(this._appStateChangedId);
|
||||
this._appStateChangedId = 0;
|
||||
}
|
||||
MessageTray.Source.prototype.destroy.call(this);
|
||||
}
|
||||
};
|
||||
|
@ -121,7 +121,7 @@ Overview.prototype = {
|
||||
let spacing = node.get_length('spacing');
|
||||
if (spacing != this._spacing) {
|
||||
this._spacing = spacing;
|
||||
this.relayout();
|
||||
this._relayout();
|
||||
}
|
||||
}));
|
||||
|
||||
@ -202,6 +202,8 @@ Overview.prototype = {
|
||||
// the left of the overview
|
||||
Main.ctrlAltTabManager.addGroup(this.dash.actor, _("Dash"), 'user-bookmarks');
|
||||
|
||||
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._relayout));
|
||||
this._relayout();
|
||||
},
|
||||
|
||||
_onDragBegin: function() {
|
||||
@ -220,7 +222,7 @@ Overview.prototype = {
|
||||
}
|
||||
this._resetWindowSwitchTimeout();
|
||||
this._lastHoveredWindow = null;
|
||||
DND.removeMonitor(this._dragMonitor);
|
||||
DND.removeDragMonitor(this._dragMonitor);
|
||||
this.endItemDrag();
|
||||
},
|
||||
|
||||
@ -281,7 +283,8 @@ Overview.prototype = {
|
||||
},
|
||||
|
||||
_onButtonPress: function(actor, event) {
|
||||
if (this._scrollDirection == SwipeScrollDirection.NONE)
|
||||
if (this._scrollDirection == SwipeScrollDirection.NONE
|
||||
|| event.get_button() != 1)
|
||||
return;
|
||||
|
||||
let [stageX, stageY] = event.get_coords();
|
||||
@ -390,7 +393,7 @@ Overview.prototype = {
|
||||
[stageX, stageY] = event.get_coords();
|
||||
let dx = this._dragX - stageX;
|
||||
let dy = this._dragY - stageY;
|
||||
let primary = global.get_primary_monitor();
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
|
||||
this._dragX = stageX;
|
||||
this._dragY = stageY;
|
||||
@ -437,8 +440,13 @@ Overview.prototype = {
|
||||
return clone;
|
||||
},
|
||||
|
||||
relayout: function () {
|
||||
let primary = global.get_primary_monitor();
|
||||
_relayout: function () {
|
||||
// To avoid updating the position and size of the workspaces
|
||||
// we just hide the overview. The positions will be updated
|
||||
// when it is next shown.
|
||||
this.hide();
|
||||
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
let rtl = (St.Widget.get_default_direction () == St.TextDirection.RTL);
|
||||
|
||||
let contentY = Main.panel.actor.height;
|
||||
|
446
js/ui/panel.js
446
js/ui/panel.js
@ -12,6 +12,7 @@ const Signals = imports.signals;
|
||||
|
||||
const Config = imports.misc.config;
|
||||
const CtrlAltTab = imports.ui.ctrlAltTab;
|
||||
const Layout = imports.ui.layout;
|
||||
const Overview = imports.ui.overview;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
@ -24,8 +25,6 @@ const PANEL_ICON_SIZE = 24;
|
||||
|
||||
const STARTUP_ANIMATION_TIME = 0.2;
|
||||
|
||||
const HOT_CORNER_ACTIVATION_TIMEOUT = 0.5;
|
||||
|
||||
const BUTTON_DND_ACTIVATION_TIMEOUT = 250;
|
||||
|
||||
const ANIMATED_ICON_UPDATE_TIMEOUT = 100;
|
||||
@ -302,6 +301,7 @@ AppMenuButton.prototype = {
|
||||
|
||||
this._visible = true;
|
||||
this.actor.show();
|
||||
this.actor.reactive = true;
|
||||
|
||||
if (!this._targetIsCurrent)
|
||||
return;
|
||||
@ -318,6 +318,7 @@ AppMenuButton.prototype = {
|
||||
return;
|
||||
|
||||
this._visible = false;
|
||||
this.actor.reactive = false;
|
||||
if (!this._targetIsCurrent) {
|
||||
this.actor.hide();
|
||||
return;
|
||||
@ -542,13 +543,163 @@ AppMenuButton.prototype = {
|
||||
|
||||
Signals.addSignalMethods(AppMenuButton.prototype);
|
||||
|
||||
// Activities button. Because everything else in the top bar is a
|
||||
// PanelMenu.Button, it simplifies some things to make this be one too.
|
||||
// We just hack it up to not actually have a menu attached to it.
|
||||
function ActivitiesButton() {
|
||||
this._init.apply(this, arguments);
|
||||
}
|
||||
|
||||
function PanelCorner(side) {
|
||||
this._init(side);
|
||||
ActivitiesButton.prototype = {
|
||||
__proto__: PanelMenu.Button.prototype,
|
||||
|
||||
_init: function() {
|
||||
PanelMenu.Button.prototype._init.call(this, 0.0);
|
||||
|
||||
let container = new Shell.GenericContainer();
|
||||
container.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||
container.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
||||
container.connect('allocate', Lang.bind(this, this._allocate));
|
||||
this.actor.child = container;
|
||||
this.actor.name = 'panelActivities';
|
||||
|
||||
/* Translators: If there is no suitable word for "Activities"
|
||||
in your language, you can use the word for "Overview". */
|
||||
this._label = new St.Label({ text: _("Activities") });
|
||||
container.add_actor(this._label);
|
||||
|
||||
this._hotCorner = new Layout.HotCorner();
|
||||
container.add_actor(this._hotCorner.actor);
|
||||
|
||||
// Hack up our menu...
|
||||
this.menu.open = Lang.bind(this, this._onMenuOpenRequest);
|
||||
this.menu.close = Lang.bind(this, this._onMenuCloseRequest);
|
||||
this.menu.toggle = Lang.bind(this, this._onMenuToggleRequest);
|
||||
|
||||
this.actor.connect('captured-event', Lang.bind(this, this._onCapturedEvent));
|
||||
this.actor.connect_after('button-release-event', Lang.bind(this, this._onButtonRelease));
|
||||
this.actor.connect_after('key-release-event', Lang.bind(this, this._onKeyRelease));
|
||||
|
||||
Main.overview.connect('showing', Lang.bind(this, function() {
|
||||
this.actor.add_style_pseudo_class('overview');
|
||||
this._escapeMenuGrab();
|
||||
}));
|
||||
Main.overview.connect('hiding', Lang.bind(this, function() {
|
||||
this.actor.remove_style_pseudo_class('overview');
|
||||
this._escapeMenuGrab();
|
||||
}));
|
||||
|
||||
this._xdndTimeOut = 0;
|
||||
},
|
||||
|
||||
_getPreferredWidth: function(actor, forHeight, alloc) {
|
||||
[alloc.min_size, alloc.natural_size] = this._label.get_preferred_width(forHeight);
|
||||
},
|
||||
|
||||
_getPreferredHeight: function(actor, forWidth, alloc) {
|
||||
[alloc.min_size, alloc.natural_size] = this._label.get_preferred_height(forWidth);
|
||||
},
|
||||
|
||||
_allocate: function(actor, box, flags) {
|
||||
this._label.allocate(box, flags);
|
||||
|
||||
// The hot corner needs to be outside any padding/alignment
|
||||
// that has been imposed on us
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
let hotBox = new Clutter.ActorBox();
|
||||
let ok, x, y;
|
||||
if (actor.get_direction() == St.TextDirection.LTR) {
|
||||
[ok, x, y] = actor.transform_stage_point(primary.x, primary.y)
|
||||
} else {
|
||||
[ok, x, y] = actor.transform_stage_point(primary.x + primary.width, primary.y);
|
||||
// hotCorner.actor has northeast gravity, so we don't need
|
||||
// to adjust x for its width
|
||||
}
|
||||
|
||||
hotBox.x1 = Math.round(x);
|
||||
hotBox.x2 = hotBox.x1 + this._hotCorner.actor.width;
|
||||
hotBox.y1 = Math.round(y);
|
||||
hotBox.y2 = hotBox.y1 + this._hotCorner.actor.height;
|
||||
this._hotCorner.actor.allocate(hotBox, flags);
|
||||
},
|
||||
|
||||
handleDragOver: function(source, actor, x, y, time) {
|
||||
if (source != Main.xdndHandler)
|
||||
return;
|
||||
|
||||
if (this._xdndTimeOut != 0)
|
||||
Mainloop.source_remove(this._xdndTimeOut);
|
||||
this._xdndTimeOut = Mainloop.timeout_add(BUTTON_DND_ACTIVATION_TIMEOUT,
|
||||
Lang.bind(this, this._xdndShowOverview, actor));
|
||||
},
|
||||
|
||||
_escapeMenuGrab: function() {
|
||||
if (this.menu.isOpen)
|
||||
this.menu.close();
|
||||
},
|
||||
|
||||
_onCapturedEvent: function(actor, event) {
|
||||
if (event.type() == Clutter.EventType.BUTTON_PRESS) {
|
||||
if (!this._hotCorner.shouldToggleOverviewOnClick())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_onMenuOpenRequest: function() {
|
||||
this.menu.isOpen = true;
|
||||
this.menu.emit('open-state-changed', true);
|
||||
},
|
||||
|
||||
_onMenuCloseRequest: function() {
|
||||
this.menu.isOpen = false;
|
||||
this.menu.emit('open-state-changed', false);
|
||||
},
|
||||
|
||||
_onMenuToggleRequest: function() {
|
||||
this.menu.isOpen = !this.menu.isOpen;
|
||||
this.menu.emit('open-state-changed', this.menu.isOpen);
|
||||
},
|
||||
|
||||
_onButtonRelease: function() {
|
||||
if (this.menu.isOpen) {
|
||||
this.menu.close();
|
||||
Main.overview.toggle();
|
||||
}
|
||||
},
|
||||
|
||||
_onKeyRelease: function(actor, event) {
|
||||
let symbol = event.get_key_symbol();
|
||||
if (symbol == Clutter.KEY_Return || symbol == Clutter.KEY_space) {
|
||||
if (this.menu.isOpen)
|
||||
this.menu.close();
|
||||
Main.overview.toggle();
|
||||
}
|
||||
},
|
||||
|
||||
_xdndShowOverview: function(actor) {
|
||||
let [x, y, mask] = global.get_pointer();
|
||||
let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);
|
||||
|
||||
if (pickedActor == this.actor) {
|
||||
if (!Main.overview.visible && !Main.overview.animationInProgress) {
|
||||
Main.overview.showTemporarily();
|
||||
Main.overview.beginItemDrag(actor);
|
||||
}
|
||||
}
|
||||
|
||||
Mainloop.source_remove(this._xdndTimeOut);
|
||||
this._xdndTimeOut = 0;
|
||||
}
|
||||
};
|
||||
|
||||
function PanelCorner(panel, side) {
|
||||
this._init(panel, side);
|
||||
}
|
||||
|
||||
PanelCorner.prototype = {
|
||||
_init: function(side) {
|
||||
_init: function(panel, side) {
|
||||
this._panel = panel;
|
||||
this._side = side;
|
||||
this.actor = new St.DrawingArea({ style_class: 'panel-corner' });
|
||||
this.actor.connect('repaint', Lang.bind(this, this._repaint));
|
||||
@ -624,185 +775,14 @@ PanelCorner.prototype = {
|
||||
this.actor.set_size(cornerRadius,
|
||||
innerBorderWidth + cornerRadius);
|
||||
if (this._side == St.Side.LEFT)
|
||||
this.actor.set_position(Main.panel.actor.x,
|
||||
Main.panel.actor.y + Main.panel.actor.height - innerBorderWidth);
|
||||
this.actor.set_position(this._panel.actor.x,
|
||||
this._panel.actor.y + this._panel.actor.height - innerBorderWidth);
|
||||
else
|
||||
this.actor.set_position(Main.panel.actor.x + Main.panel.actor.width - cornerRadius,
|
||||
Main.panel.actor.y + Main.panel.actor.height - innerBorderWidth);
|
||||
this.actor.set_position(this._panel.actor.x + this._panel.actor.width - cornerRadius,
|
||||
this._panel.actor.y + this._panel.actor.height - innerBorderWidth);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* HotCorner:
|
||||
*
|
||||
* This class manages the "hot corner" that can toggle switching to
|
||||
* overview.
|
||||
*/
|
||||
function HotCorner(button) {
|
||||
this._init(button);
|
||||
}
|
||||
|
||||
HotCorner.prototype = {
|
||||
_init : function(button) {
|
||||
// This is the activities button associated with this hot corner,
|
||||
// if this is on the primary monitor (or null with the corner is
|
||||
// on a different monitor)
|
||||
this._button = button;
|
||||
|
||||
// We use this flag to mark the case where the user has entered the
|
||||
// hot corner and has not left both the hot corner and a surrounding
|
||||
// guard area (the "environs"). This avoids triggering the hot corner
|
||||
// multiple times due to an accidental jitter.
|
||||
this._entered = false;
|
||||
|
||||
this.actor = new Clutter.Group({ width: 3,
|
||||
height: 3,
|
||||
reactive: true });
|
||||
|
||||
this._corner = new Clutter.Rectangle({ width: 1,
|
||||
height: 1,
|
||||
opacity: 0,
|
||||
reactive: true });
|
||||
|
||||
this.actor.add_actor(this._corner);
|
||||
|
||||
if (St.Widget.get_default_direction() == St.TextDirection.RTL) {
|
||||
this._corner.set_position(this.actor.width - this._corner.width, 0);
|
||||
this.actor.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
|
||||
} else {
|
||||
this._corner.set_position(0, 0);
|
||||
}
|
||||
|
||||
this._activationTime = 0;
|
||||
|
||||
this.actor.connect('enter-event',
|
||||
Lang.bind(this, this._onEnvironsEntered));
|
||||
this.actor.connect('leave-event',
|
||||
Lang.bind(this, this._onEnvironsLeft));
|
||||
// Clicking on the hot corner environs should result in the same bahavior
|
||||
// as clicking on the hot corner.
|
||||
this.actor.connect('button-release-event',
|
||||
Lang.bind(this, this._onCornerClicked));
|
||||
|
||||
// In addition to being triggered by the mouse enter event, the hot corner
|
||||
// can be triggered by clicking on it. This is useful if the user wants to
|
||||
// undo the effect of triggering the hot corner once in the hot corner.
|
||||
this._corner.connect('enter-event',
|
||||
Lang.bind(this, this._onCornerEntered));
|
||||
this._corner.connect('button-release-event',
|
||||
Lang.bind(this, this._onCornerClicked));
|
||||
this._corner.connect('leave-event',
|
||||
Lang.bind(this, this._onCornerLeft));
|
||||
|
||||
this._corner._delegate = this._corner;
|
||||
this._corner.handleDragOver = Lang.bind(this,
|
||||
function(source, actor, x, y, time) {
|
||||
if (source == Main.xdndHandler) {
|
||||
if(!Main.overview.visible && !Main.overview.animationInProgress) {
|
||||
this.rippleAnimation();
|
||||
Main.overview.showTemporarily();
|
||||
Main.overview.beginItemDrag(actor);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Main.chrome.addActor(this.actor, { visibleInOverview: true, affectsStruts: false });
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this.actor.destroy();
|
||||
},
|
||||
|
||||
_addRipple : function(delay, time, startScale, startOpacity, finalScale, finalOpacity) {
|
||||
// We draw a ripple by using a source image and animating it scaling
|
||||
// outwards and fading away. We want the ripples to move linearly
|
||||
// or it looks unrealistic, but if the opacity of the ripple goes
|
||||
// linearly to zero it fades away too quickly, so we use Tweener's
|
||||
// 'onUpdate' to give a non-linear curve to the fade-away and make
|
||||
// it more visible in the middle section.
|
||||
|
||||
let [x, y] = this._corner.get_transformed_position();
|
||||
let ripple = new St.BoxLayout({ style_class: 'ripple-box',
|
||||
opacity: 255 * Math.sqrt(startOpacity),
|
||||
scale_x: startScale,
|
||||
scale_y: startScale,
|
||||
x: x,
|
||||
y: y });
|
||||
ripple._opacity = startOpacity;
|
||||
if (ripple.get_direction() == St.TextDirection.RTL)
|
||||
ripple.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
|
||||
Tweener.addTween(ripple, { _opacity: finalOpacity,
|
||||
scale_x: finalScale,
|
||||
scale_y: finalScale,
|
||||
delay: delay,
|
||||
time: time,
|
||||
transition: 'linear',
|
||||
onUpdate: function() { ripple.opacity = 255 * Math.sqrt(ripple._opacity); },
|
||||
onComplete: function() { ripple.destroy(); } });
|
||||
Main.uiGroup.add_actor(ripple);
|
||||
},
|
||||
|
||||
rippleAnimation: function() {
|
||||
// Show three concentric ripples expanding outwards; the exact
|
||||
// parameters were found by trial and error, so don't look
|
||||
// for them to make perfect sense mathematically
|
||||
|
||||
// delay time scale opacity => scale opacity
|
||||
this._addRipple(0.0, 0.83, 0.25, 1.0, 1.5, 0.0);
|
||||
this._addRipple(0.05, 1.0, 0.0, 0.7, 1.25, 0.0);
|
||||
this._addRipple(0.35, 1.0, 0.0, 0.3, 1, 0.0);
|
||||
},
|
||||
|
||||
_onEnvironsEntered : function() {
|
||||
if (this._button)
|
||||
this._button.hover = true;
|
||||
},
|
||||
|
||||
_onCornerEntered : function() {
|
||||
if (!this._entered) {
|
||||
this._entered = true;
|
||||
if (!Main.overview.animationInProgress) {
|
||||
this._activationTime = Date.now() / 1000;
|
||||
|
||||
this.rippleAnimation();
|
||||
Main.overview.toggle();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_onCornerClicked : function() {
|
||||
if (!Main.overview.animationInProgress)
|
||||
this.maybeToggleOverviewOnClick();
|
||||
return false;
|
||||
},
|
||||
|
||||
_onCornerLeft : function(actor, event) {
|
||||
if (event.get_related() != this.actor)
|
||||
this._entered = false;
|
||||
// Consume event, otherwise this will confuse onEnvironsLeft
|
||||
return true;
|
||||
},
|
||||
|
||||
_onEnvironsLeft : function(actor, event) {
|
||||
if (this._button)
|
||||
this._button.hover = false;
|
||||
|
||||
if (event.get_related() != this._corner)
|
||||
this._entered = false;
|
||||
return false;
|
||||
},
|
||||
|
||||
// Toggles the overview unless this is the first click on the Activities button within the HOT_CORNER_ACTIVATION_TIMEOUT time
|
||||
// of the hot corner being triggered. This check avoids opening and closing the overview if the user both triggered the hot corner
|
||||
// and clicked the Activities button.
|
||||
maybeToggleOverviewOnClick: function() {
|
||||
if (this._activationTime == 0 || Date.now() / 1000 - this._activationTime > HOT_CORNER_ACTIVATION_TIMEOUT)
|
||||
Main.overview.toggle();
|
||||
this._activationTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Panel() {
|
||||
this._init();
|
||||
@ -832,8 +812,8 @@ Panel.prototype = {
|
||||
this._centerBox = new St.BoxLayout({ name: 'panelCenter' });
|
||||
this._rightBox = new St.BoxLayout({ name: 'panelRight' });
|
||||
|
||||
this._leftCorner = new PanelCorner(St.Side.LEFT);
|
||||
this._rightCorner = new PanelCorner(St.Side.RIGHT);
|
||||
this._leftCorner = new PanelCorner(this, St.Side.LEFT);
|
||||
this._rightCorner = new PanelCorner(this, St.Side.RIGHT);
|
||||
|
||||
/* This box container ensures that the centerBox is positioned in the *absolute*
|
||||
* center, but can be pushed aside if necessary. */
|
||||
@ -907,32 +887,16 @@ Panel.prototype = {
|
||||
}));
|
||||
|
||||
/* Button on the left side of the panel. */
|
||||
/* Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". */
|
||||
let label = new St.Label({ text: _("Activities") });
|
||||
this.button = new St.Button({ name: 'panelActivities',
|
||||
style_class: 'panel-button',
|
||||
reactive: true,
|
||||
can_focus: true });
|
||||
this._activities = this.button;
|
||||
this.button.set_child(label);
|
||||
this.button._delegate = this.button;
|
||||
this.button._xdndTimeOut = 0;
|
||||
this.button.handleDragOver = Lang.bind(this,
|
||||
function(source, actor, x, y, time) {
|
||||
if (source == Main.xdndHandler) {
|
||||
if (this.button._xdndTimeOut != 0)
|
||||
Mainloop.source_remove(this.button._xdndTimeOut);
|
||||
this.button._xdndTimeOut = Mainloop.timeout_add(BUTTON_DND_ACTIVATION_TIMEOUT,
|
||||
Lang.bind(this,
|
||||
function() {
|
||||
this._xdndShowOverview(actor);
|
||||
}));
|
||||
}
|
||||
});
|
||||
this._leftBox.add(this.button);
|
||||
this._activitiesButton = new ActivitiesButton();
|
||||
this._activities = this._activitiesButton.actor;
|
||||
this._leftBox.add(this._activities);
|
||||
|
||||
// Synchronize the buttons pseudo classes with its corner
|
||||
this.button.connect('style-changed', Lang.bind(this,
|
||||
// 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);
|
||||
|
||||
// Synchronize the button's pseudo classes with its corner
|
||||
this._activities.connect('style-changed', Lang.bind(this,
|
||||
function(actor) {
|
||||
let rtl = actor.get_direction() == St.TextDirection.RTL;
|
||||
let corner = rtl ? this._rightCorner : this._leftCorner;
|
||||
@ -940,8 +904,6 @@ Panel.prototype = {
|
||||
corner.actor.set_style_pseudo_class(pseudoClass);
|
||||
}));
|
||||
|
||||
this._hotCorner = null;
|
||||
|
||||
this._appMenu = new AppMenuButton();
|
||||
this._leftBox.add(this._appMenu.actor);
|
||||
|
||||
@ -980,65 +942,17 @@ Panel.prototype = {
|
||||
Main.statusIconDispatcher.connect('status-icon-added', Lang.bind(this, this._onTrayIconAdded));
|
||||
Main.statusIconDispatcher.connect('status-icon-removed', Lang.bind(this, this._onTrayIconRemoved));
|
||||
|
||||
// TODO: decide what to do with the rest of the panel in the Overview mode (make it fade-out, become non-reactive, etc.)
|
||||
// We get into the Overview mode on button-press-event as opposed to button-release-event because eventually we'll probably
|
||||
// have the Overview act like a menu that allows the user to release the mouse on the activity the user wants
|
||||
// to switch to.
|
||||
this.button.connect('clicked', Lang.bind(this, function(b) {
|
||||
if (!Main.overview.animationInProgress) {
|
||||
this._hotCorner.maybeToggleOverviewOnClick();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
// In addition to pressing the button, the Overview can be entered and exited by other means, such as
|
||||
// pressing the System key, Alt+F1 or Esc. We want the button to be pressed in when the Overview is entered
|
||||
// and to be released when it is exited regardless of how it was triggered.
|
||||
Main.overview.connect('showing', Lang.bind(this, function() {
|
||||
this.button.checked = true;
|
||||
}));
|
||||
Main.overview.connect('hiding', Lang.bind(this, function() {
|
||||
this.button.checked = false;
|
||||
}));
|
||||
|
||||
Main.chrome.addActor(this.actor, { visibleInOverview: true });
|
||||
Main.chrome.addActor(this._leftCorner.actor, { visibleInOverview: true,
|
||||
affectsStruts: false,
|
||||
affectsInputRegion: false });
|
||||
Main.chrome.addActor(this._rightCorner.actor, { visibleInOverview: true,
|
||||
affectsStruts: false,
|
||||
affectsInputRegion: false });
|
||||
Main.chrome.addActor(this.actor);
|
||||
Main.chrome.addActor(this._leftCorner.actor, { affectsStruts: false,
|
||||
affectsInputRegion: false });
|
||||
Main.chrome.addActor(this._rightCorner.actor, { affectsStruts: false,
|
||||
affectsInputRegion: false });
|
||||
|
||||
Main.ctrlAltTabManager.addGroup(this.actor, _("Top Bar"), 'start-here',
|
||||
{ sortGroup: CtrlAltTab.SortGroup.TOP });
|
||||
},
|
||||
|
||||
_xdndShowOverview: function (actor) {
|
||||
let [x, y, mask] = global.get_pointer();
|
||||
let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);
|
||||
|
||||
if (pickedActor != this.button) {
|
||||
Mainloop.source_remove(this.button._xdndTimeOut);
|
||||
this.button._xdndTimeOut = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if(!Main.overview.visible && !Main.overview.animationInProgress) {
|
||||
Main.overview.showTemporarily();
|
||||
Main.overview.beginItemDrag(actor);
|
||||
}
|
||||
|
||||
Mainloop.source_remove(this.button._xdndTimeOut);
|
||||
this.button._xdndTimeOut = 0;
|
||||
},
|
||||
|
||||
|
||||
// While there can be multiple hotcorners (one per monitor), the hot corner
|
||||
// that is on top of the Activities button is special since it needs special
|
||||
// coordination with clicking on that button
|
||||
setHotCorner: function(corner) {
|
||||
this._hotCorner = corner;
|
||||
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._relayout));
|
||||
this._relayout();
|
||||
},
|
||||
|
||||
startStatusArea: function() {
|
||||
@ -1085,8 +999,8 @@ Panel.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
relayout: function() {
|
||||
let primary = global.get_primary_monitor();
|
||||
_relayout: function() {
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
|
||||
this.actor.set_position(primary.x, primary.y);
|
||||
this.actor.set_size(primary.width, -1);
|
||||
|
@ -26,8 +26,7 @@ Button.prototype = {
|
||||
this.menu = new PopupMenu.PopupMenu(this.actor, menuAlignment, St.Side.TOP, 0);
|
||||
this.menu.connect('open-state-changed', Lang.bind(this, this._onOpenStateChanged));
|
||||
this.menu.actor.connect('key-press-event', Lang.bind(this, this._onMenuKeyPress));
|
||||
Main.chrome.addActor(this.menu.actor, { visibleInOverview: true,
|
||||
affectsStruts: false });
|
||||
Main.chrome.addActor(this.menu.actor, { affectsStruts: false });
|
||||
this.menu.actor.hide();
|
||||
},
|
||||
|
||||
@ -36,7 +35,7 @@ Button.prototype = {
|
||||
// Setting the max-height won't do any good if the minimum height of the
|
||||
// menu is higher then the screen; it's useful if part of the menu is
|
||||
// scrollable so the minimum height is smaller than the natural height
|
||||
let monitor = global.get_primary_monitor();
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
this.menu.actor.style = ('max-height: ' +
|
||||
Math.round(monitor.height - Main.panel.actor.height) +
|
||||
'px;');
|
||||
|
@ -268,10 +268,7 @@ PlacesManager.prototype = {
|
||||
if (!GLib.file_test(this._bookmarksPath, GLib.FileTest.EXISTS))
|
||||
return;
|
||||
|
||||
let [success, bookmarksContent, len] = GLib.file_get_contents(this._bookmarksPath);
|
||||
|
||||
if (!success)
|
||||
return;
|
||||
let bookmarksContent = Shell.get_file_contents_utf8_sync(this._bookmarksPath);
|
||||
|
||||
let bookmarks = bookmarksContent.split('\n');
|
||||
|
||||
|
@ -704,11 +704,35 @@ PopupSwitchMenuItem.prototype = {
|
||||
this._switch = new Switch(active);
|
||||
|
||||
this.addActor(this.label);
|
||||
this.addActor(this._switch.actor, { align: St.Align.END });
|
||||
|
||||
this.connect('activate', Lang.bind(this,function(from) {
|
||||
this._statusBin = new St.Bin({ x_align: St.Align.END });
|
||||
this.addActor(this._statusBin, { align: St.Align.END });
|
||||
|
||||
this._statusLabel = new St.Label({ text: '',
|
||||
style_class: 'popup-inactive-menu-item'
|
||||
});
|
||||
this._statusBin.child = this._switch.actor;
|
||||
},
|
||||
|
||||
setStatus: function(text) {
|
||||
if (text != null) {
|
||||
this._statusLabel.text = text;
|
||||
this._statusBin.child = this._statusLabel;
|
||||
this.actor.reactive = false;
|
||||
this.actor.can_focus = false;
|
||||
} else {
|
||||
this._statusBin.child = this._switch.actor;
|
||||
this.actor.reactive = true;
|
||||
this.actor.can_focus = true;
|
||||
}
|
||||
},
|
||||
|
||||
activate: function(event) {
|
||||
if (this._switch.actor.mapped) {
|
||||
this.toggle();
|
||||
}));
|
||||
}
|
||||
|
||||
PopupBaseMenuItem.prototype.activate.call(this, event);
|
||||
},
|
||||
|
||||
toggle: function() {
|
||||
@ -922,6 +946,14 @@ PopupMenuBase.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
get firstMenuItem() {
|
||||
let items = this._getMenuItems();
|
||||
if (items.length)
|
||||
return items[0];
|
||||
else
|
||||
return null;
|
||||
},
|
||||
|
||||
removeAll: function() {
|
||||
let children = this._getMenuItems();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
|
@ -23,6 +23,10 @@ const HISTORY_KEY = 'command-history';
|
||||
const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown';
|
||||
const DISABLE_COMMAND_LINE_KEY = 'disable-command-line';
|
||||
|
||||
const TERMINAL_SCHEMA = 'org.gnome.desktop.default-applications.terminal';
|
||||
const EXEC_KEY = 'exec';
|
||||
const EXEC_ARG_KEY = 'exec-arg';
|
||||
|
||||
const DIALOG_GROW_TIME = 0.1;
|
||||
|
||||
function CommandCompleter() {
|
||||
@ -169,6 +173,7 @@ __proto__: ModalDialog.ModalDialog.prototype,
|
||||
ModalDialog.ModalDialog.prototype._init.call(this, { styleClass: 'run-dialog' });
|
||||
|
||||
this._lockdownSettings = new Gio.Settings({ schema: LOCKDOWN_SCHEMA });
|
||||
this._terminalSettings = new Gio.Settings({ schema: TERMINAL_SCHEMA });
|
||||
global.settings.connect('changed::development-tools', Lang.bind(this, function () {
|
||||
this._enableInternalCommands = global.settings.get_boolean('development-tools');
|
||||
}));
|
||||
@ -189,7 +194,7 @@ __proto__: ModalDialog.ModalDialog.prototype,
|
||||
}),
|
||||
|
||||
'debugexit': Lang.bind(this, function() {
|
||||
Meta.exit(Meta.ExitCode.ERROR);
|
||||
Meta.quit(Meta.ExitCode.ERROR);
|
||||
}),
|
||||
|
||||
// rt is short for "reload theme"
|
||||
@ -309,8 +314,11 @@ __proto__: ModalDialog.ModalDialog.prototype,
|
||||
f();
|
||||
} else if (input) {
|
||||
try {
|
||||
if (inTerminal)
|
||||
command = 'gnome-terminal -x ' + input;
|
||||
if (inTerminal) {
|
||||
let exec = this._terminalSettings.get_string(EXEC_KEY);
|
||||
let exec_arg = this._terminalSettings.get_string(EXEC_ARG_KEY);
|
||||
command = exec + ' ' + exec_arg + ' ' + input;
|
||||
}
|
||||
Util.trySpawnCommandLine(command);
|
||||
} catch (e) {
|
||||
// Mmmh, that failed - see if @input matches an existing file
|
||||
@ -325,33 +333,46 @@ __proto__: ModalDialog.ModalDialog.prototype,
|
||||
|
||||
if (GLib.file_test(path, GLib.FileTest.EXISTS)) {
|
||||
let file = Gio.file_new_for_path(path);
|
||||
Gio.app_info_launch_default_for_uri(file.get_uri(),
|
||||
global.create_app_launch_context());
|
||||
} else {
|
||||
this._commandError = true;
|
||||
|
||||
this._errorMessage.set_text(e.message);
|
||||
|
||||
if (!this._errorBox.visible) {
|
||||
let [errorBoxMinHeight, errorBoxNaturalHeight] = this._errorBox.get_preferred_height(-1);
|
||||
|
||||
let parentActor = this._errorBox.get_parent();
|
||||
Tweener.addTween(parentActor,
|
||||
{ height: parentActor.height + errorBoxNaturalHeight,
|
||||
time: DIALOG_GROW_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this,
|
||||
function() {
|
||||
parentActor.set_height(-1);
|
||||
this._errorBox.show();
|
||||
})
|
||||
});
|
||||
try {
|
||||
Gio.app_info_launch_default_for_uri(file.get_uri(),
|
||||
global.create_app_launch_context());
|
||||
} catch (e) {
|
||||
// The exception from gjs contains an error string like:
|
||||
// Error invoking Gio.app_info_launch_default_for_uri: No application
|
||||
// is registered as handling this file
|
||||
// We are only interested in the part after the first colon.
|
||||
let message = e.message.replace(/[^:]*: *(.+)/, '$1');
|
||||
this._showError(message);
|
||||
}
|
||||
} else {
|
||||
this._showError(e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_showError : function(message) {
|
||||
this._commandError = true;
|
||||
|
||||
this._errorMessage.set_text(message);
|
||||
|
||||
if (!this._errorBox.visible) {
|
||||
let [errorBoxMinHeight, errorBoxNaturalHeight] = this._errorBox.get_preferred_height(-1);
|
||||
|
||||
let parentActor = this._errorBox.get_parent();
|
||||
Tweener.addTween(parentActor,
|
||||
{ height: parentActor.height + errorBoxNaturalHeight,
|
||||
time: DIALOG_GROW_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this,
|
||||
function() {
|
||||
parentActor.set_height(-1);
|
||||
this._errorBox.show();
|
||||
})
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
open: function() {
|
||||
this._history.lastItem();
|
||||
this._errorBox.hide();
|
||||
|
@ -3,10 +3,11 @@
|
||||
const DBus = imports.dbus;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Mainloop = imports.mainloop;
|
||||
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
|
||||
// This module provides functionality for driving the shell user interface
|
||||
// in an automated fashion. The primary current use case for this is
|
||||
// automated performance testing (see runPerfScript()), but it could
|
||||
@ -246,18 +247,14 @@ function _collect(scriptModule, outputFile) {
|
||||
Shell.write_string_to_stream(out, '"events":\n');
|
||||
Shell.PerfLog.get_default().dump_events(out);
|
||||
|
||||
let monitors = global.get_monitors();
|
||||
let primary = global.get_primary_monitor();
|
||||
let monitors = Main.layoutManager.monitors;
|
||||
let primary = Main.layoutManager.primaryIndex;
|
||||
Shell.write_string_to_stream(out, ',\n"monitors":\n[');
|
||||
for (let i = 0; i < monitors.length; i++) {
|
||||
let monitor = monitors[i];
|
||||
let is_primary = (monitor.x == primary.x &&
|
||||
monitor.y == primary.y &&
|
||||
monitor.width == primary.width &&
|
||||
monitor.height == primary.height);
|
||||
if (i != 0)
|
||||
Shell.write_string_to_stream(out, ', ');
|
||||
Shell.write_string_to_stream(out, '"%s%dx%d+%d+%d"'.format(is_primary ? "*" : "",
|
||||
Shell.write_string_to_stream(out, '"%s%dx%d+%d+%d"'.format(i == primary ? "*" : "",
|
||||
monitor.width, monitor.height,
|
||||
monitor.x, monitor.y));
|
||||
}
|
||||
|
@ -157,7 +157,7 @@ SearchProvider.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* getResultInfo:
|
||||
* getResultMeta:
|
||||
* @id: Result identifier string
|
||||
*
|
||||
* Return an object with 'id', 'name', (both strings) and 'createIcon'
|
||||
|
@ -28,6 +28,7 @@ SearchResult.prototype = {
|
||||
x_align: St.Align.START,
|
||||
y_fill: true });
|
||||
this.actor._delegate = this;
|
||||
this._dragActorSource = null;
|
||||
|
||||
let content = provider.createResultActor(metaInfo, terms);
|
||||
if (content == null) {
|
||||
@ -37,7 +38,11 @@ SearchResult.prototype = {
|
||||
let icon = new IconGrid.BaseIcon(this.metaInfo['name'],
|
||||
{ createIcon: this.metaInfo['createIcon'] });
|
||||
content.set_child(icon.actor);
|
||||
this._dragActorSource = icon.icon;
|
||||
this.actor.label_actor = icon.label;
|
||||
} else {
|
||||
if (content._delegate && content._delegate.getDragActorSource)
|
||||
this._dragActorSource = content._delegate.getDragActorSource();
|
||||
}
|
||||
this._content = content;
|
||||
this.actor.set_child(content);
|
||||
@ -76,6 +81,8 @@ SearchResult.prototype = {
|
||||
},
|
||||
|
||||
getDragActorSource: function() {
|
||||
if (this._dragActorSource)
|
||||
return this._dragActorSource;
|
||||
// not exactly right, but alignment problems are hard to notice
|
||||
return this._content;
|
||||
},
|
||||
@ -192,7 +199,7 @@ SearchResults.prototype = {
|
||||
|
||||
let scrollView = new St.ScrollView({ x_fill: true,
|
||||
y_fill: false,
|
||||
vfade: true });
|
||||
style_class: 'vfade' });
|
||||
scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
scrollView.add_actor(this._content);
|
||||
|
||||
|
405
js/ui/shellMountOperation.js
Normal file
405
js/ui/shellMountOperation.js
Normal file
@ -0,0 +1,405 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Lang = imports.lang;
|
||||
const Signals = imports.signals;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Pango = imports.gi.Pango;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const MessageTray = imports.ui.messageTray;
|
||||
const ModalDialog = imports.ui.modalDialog;
|
||||
const Params = imports.misc.params;
|
||||
|
||||
const LIST_ITEM_ICON_SIZE = 48;
|
||||
|
||||
/* ------ Common Utils ------- */
|
||||
function _setLabelText(label, text) {
|
||||
if (text) {
|
||||
label.set_text(text);
|
||||
label.show();
|
||||
} else {
|
||||
label.set_text('');
|
||||
label.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function _setButtonsForChoices(dialog, choices) {
|
||||
let buttons = [];
|
||||
|
||||
for (let idx = 0; idx < choices.length; idx++) {
|
||||
let button = idx;
|
||||
buttons.unshift({ label: choices[idx],
|
||||
action: Lang.bind(dialog, function() {
|
||||
dialog.emit('response', button);
|
||||
})});
|
||||
}
|
||||
|
||||
dialog.setButtons(buttons);
|
||||
}
|
||||
|
||||
function _setLabelsForMessage(dialog, message) {
|
||||
let labels = message.split('\n');
|
||||
|
||||
_setLabelText(dialog.subjectLabel, labels[0]);
|
||||
if (labels.length > 1)
|
||||
_setLabelText(dialog.descriptionLabel, labels[1]);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------- */
|
||||
|
||||
function ListItem(app) {
|
||||
this._init(app);
|
||||
}
|
||||
|
||||
ListItem.prototype = {
|
||||
_init: function(app) {
|
||||
this._app = app;
|
||||
|
||||
let layout = new St.BoxLayout({ vertical: false});
|
||||
|
||||
this.actor = new St.Button({ style_class: 'show-processes-dialog-app-list-item',
|
||||
can_focus: true,
|
||||
child: layout,
|
||||
reactive: true,
|
||||
x_align: St.Align.START,
|
||||
x_fill: true });
|
||||
|
||||
this._icon = this._app.create_icon_texture(LIST_ITEM_ICON_SIZE);
|
||||
|
||||
let iconBin = new St.Bin({ style_class: 'show-processes-dialog-app-list-item-icon',
|
||||
child: this._icon });
|
||||
layout.add(iconBin);
|
||||
|
||||
this._nameLabel = new St.Label({ text: this._app.get_name(),
|
||||
style_class: 'show-processes-dialog-app-list-item-name' });
|
||||
let labelBin = new St.Bin({ y_align: St.Align.MIDDLE,
|
||||
child: this._nameLabel });
|
||||
layout.add(labelBin);
|
||||
|
||||
this.actor.connect('clicked', Lang.bind(this, this._onClicked));
|
||||
},
|
||||
|
||||
_onClicked: function() {
|
||||
this.emit('activate');
|
||||
this._app.activate(-1);
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(ListItem.prototype);
|
||||
|
||||
function ShellMountOperation(source, params) {
|
||||
this._init(source, params);
|
||||
}
|
||||
|
||||
ShellMountOperation.prototype = {
|
||||
_init: function(source, params) {
|
||||
params = Params.parse(params, { reaskPassword: false });
|
||||
|
||||
this._reaskPassword = params.reaskPassword;
|
||||
|
||||
this._dialog = null;
|
||||
this._processesDialog = null;
|
||||
|
||||
this.mountOp = new Shell.MountOperation();
|
||||
|
||||
this.mountOp.connect('ask-question',
|
||||
Lang.bind(this, this._onAskQuestion));
|
||||
this.mountOp.connect('ask-password',
|
||||
Lang.bind(this, this._onAskPassword));
|
||||
this.mountOp.connect('show-processes-2',
|
||||
Lang.bind(this, this._onShowProcesses2));
|
||||
this.mountOp.connect('aborted',
|
||||
Lang.bind(this, this._onAborted));
|
||||
|
||||
this._icon = new St.Icon({ gicon: source.get_icon(),
|
||||
style_class: 'shell-mount-operation-icon' });
|
||||
},
|
||||
|
||||
_onAskQuestion: function(op, message, choices) {
|
||||
this._dialog = new ShellMountQuestionDialog(this._icon);
|
||||
|
||||
this._dialog.connect('response',
|
||||
Lang.bind(this, function(object, choice) {
|
||||
this.mountOp.set_choice(choice);
|
||||
this.mountOp.reply(Gio.MountOperationResult.HANDLED);
|
||||
|
||||
this._dialog.close(global.get_current_time());
|
||||
this._dialog = null;
|
||||
}));
|
||||
|
||||
this._dialog.update(message, choices);
|
||||
this._dialog.open(global.get_current_time());
|
||||
},
|
||||
|
||||
_onAskPassword: function(op, message) {
|
||||
this._notificationShowing = true;
|
||||
this._source = new ShellMountPasswordSource(message, this._icon, this._reaskPassword);
|
||||
|
||||
this._source.connect('password-ready',
|
||||
Lang.bind(this, function(source, password) {
|
||||
this.mountOp.set_password(password);
|
||||
this.mountOp.reply(Gio.MountOperationResult.HANDLED);
|
||||
|
||||
this._notificationShowing = false;
|
||||
this._source.destroy();
|
||||
}));
|
||||
|
||||
this._source.connect('destroy',
|
||||
Lang.bind(this, function() {
|
||||
if (!this._notificationShowing)
|
||||
return;
|
||||
|
||||
this._notificationShowing = false;
|
||||
this.mountOp.reply(Gio.MountOperationResult.ABORTED);
|
||||
}));
|
||||
},
|
||||
|
||||
_onAborted: function(op) {
|
||||
if (!this._dialog)
|
||||
return;
|
||||
|
||||
this._dialog.close(global.get_current_time());
|
||||
this._dialog = null;
|
||||
},
|
||||
|
||||
_onShowProcesses2: function(op) {
|
||||
let processes = op.get_show_processes_pids();
|
||||
let choices = op.get_show_processes_choices();
|
||||
let message = op.get_show_processes_message();
|
||||
|
||||
if (!this._processesDialog) {
|
||||
this._processesDialog = new ShellProcessesDialog(this._icon);
|
||||
this._dialog = this._processesDialog;
|
||||
|
||||
this._processesDialog.connect('response',
|
||||
Lang.bind(this, function(object, choice) {
|
||||
if (choice == -1) {
|
||||
this.mountOp.reply(Gio.MountOperationResult.ABORTED);
|
||||
} else {
|
||||
this.mountOp.set_choice(choice);
|
||||
this.mountOp.reply(Gio.MountOperationResult.HANDLED);
|
||||
}
|
||||
|
||||
this._processesDialog.close(global.get_current_time());
|
||||
this._dialog = null;
|
||||
}));
|
||||
this._processesDialog.open(global.get_current_time());
|
||||
}
|
||||
|
||||
this._processesDialog.update(message, processes, choices);
|
||||
},
|
||||
}
|
||||
|
||||
function ShellMountQuestionDialog(icon) {
|
||||
this._init(icon);
|
||||
}
|
||||
|
||||
ShellMountQuestionDialog.prototype = {
|
||||
__proto__: ModalDialog.ModalDialog.prototype,
|
||||
|
||||
_init: function(icon) {
|
||||
ModalDialog.ModalDialog.prototype._init.call(this, { styleClass: 'mount-question-dialog' });
|
||||
|
||||
let mainContentLayout = new St.BoxLayout();
|
||||
this.contentLayout.add(mainContentLayout, { x_fill: true,
|
||||
y_fill: false });
|
||||
|
||||
this._iconBin = new St.Bin({ child: icon });
|
||||
mainContentLayout.add(this._iconBin,
|
||||
{ x_fill: true,
|
||||
y_fill: false,
|
||||
x_align: St.Align.END,
|
||||
y_align: St.Align.MIDDLE });
|
||||
|
||||
let messageLayout = new St.BoxLayout({ vertical: true });
|
||||
mainContentLayout.add(messageLayout,
|
||||
{ y_align: St.Align.START });
|
||||
|
||||
this.subjectLabel = new St.Label({ style_class: 'mount-question-dialog-subject' });
|
||||
|
||||
messageLayout.add(this.subjectLabel,
|
||||
{ y_fill: false,
|
||||
y_align: St.Align.START });
|
||||
|
||||
this.descriptionLabel = new St.Label({ style_class: 'mount-question-dialog-description' });
|
||||
this.descriptionLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
||||
this.descriptionLabel.clutter_text.line_wrap = true;
|
||||
|
||||
messageLayout.add(this.descriptionLabel,
|
||||
{ y_fill: true,
|
||||
y_align: St.Align.START });
|
||||
},
|
||||
|
||||
update: function(message, choices) {
|
||||
_setLabelsForMessage(this, message);
|
||||
_setButtonsForChoices(this, choices);
|
||||
}
|
||||
}
|
||||
Signals.addSignalMethods(ShellMountQuestionDialog.prototype);
|
||||
|
||||
function ShellMountPasswordSource(message, icon, reaskPassword) {
|
||||
this._init(message, icon, reaskPassword);
|
||||
}
|
||||
|
||||
ShellMountPasswordSource.prototype = {
|
||||
__proto__: MessageTray.Source.prototype,
|
||||
|
||||
_init: function(message, icon, reaskPassword) {
|
||||
let strings = message.split('\n');
|
||||
MessageTray.Source.prototype._init.call(this, strings[0]);
|
||||
|
||||
this._notification = new ShellMountPasswordNotification(this, strings, icon, reaskPassword);
|
||||
|
||||
// add ourselves as a source, and popup the notification
|
||||
Main.messageTray.add(this);
|
||||
this.notify(this._notification);
|
||||
},
|
||||
}
|
||||
Signals.addSignalMethods(ShellMountPasswordSource.prototype);
|
||||
|
||||
function ShellMountPasswordNotification(source, strings, icon, reaskPassword) {
|
||||
this._init(source, strings, icon, reaskPassword);
|
||||
}
|
||||
|
||||
ShellMountPasswordNotification.prototype = {
|
||||
__proto__: MessageTray.Notification.prototype,
|
||||
|
||||
_init: function(source, strings, icon, reaskPassword) {
|
||||
MessageTray.Notification.prototype._init.call(this, source,
|
||||
strings[0], null,
|
||||
{ customContent: true,
|
||||
icon: icon });
|
||||
|
||||
// set the notification to transient and urgent, so that it
|
||||
// expands out
|
||||
this.setTransient(true);
|
||||
this.setUrgency(MessageTray.Urgency.CRITICAL);
|
||||
|
||||
if (strings[1])
|
||||
this.addBody(strings[1]);
|
||||
|
||||
if (reaskPassword) {
|
||||
let label = new St.Label({ style_class: 'mount-password-reask',
|
||||
text: _("Wrong password, please try again") });
|
||||
|
||||
this.addActor(label);
|
||||
}
|
||||
|
||||
this._responseEntry = new St.Entry({ style_class: 'mount-password-entry',
|
||||
can_focus: true });
|
||||
this.setActionArea(this._responseEntry);
|
||||
|
||||
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();
|
||||
},
|
||||
|
||||
_onEntryActivated: function() {
|
||||
let text = this._responseEntry.get_text();
|
||||
if (text == '')
|
||||
return;
|
||||
|
||||
this.source.emit('password-ready', text);
|
||||
}
|
||||
}
|
||||
|
||||
function ShellProcessesDialog(icon) {
|
||||
this._init(icon);
|
||||
}
|
||||
|
||||
ShellProcessesDialog.prototype = {
|
||||
__proto__: ModalDialog.ModalDialog.prototype,
|
||||
|
||||
_init: function(icon) {
|
||||
ModalDialog.ModalDialog.prototype._init.call(this, { styleClass: 'show-processes-dialog' });
|
||||
|
||||
let mainContentLayout = new St.BoxLayout();
|
||||
this.contentLayout.add(mainContentLayout, { x_fill: true,
|
||||
y_fill: false });
|
||||
|
||||
this._iconBin = new St.Bin({ child: icon });
|
||||
mainContentLayout.add(this._iconBin,
|
||||
{ x_fill: true,
|
||||
y_fill: false,
|
||||
x_align: St.Align.END,
|
||||
y_align: St.Align.MIDDLE });
|
||||
|
||||
let messageLayout = new St.BoxLayout({ vertical: true });
|
||||
mainContentLayout.add(messageLayout,
|
||||
{ y_align: St.Align.START });
|
||||
|
||||
this.subjectLabel = new St.Label({ style_class: 'show-processes-dialog-subject' });
|
||||
|
||||
messageLayout.add(this.subjectLabel,
|
||||
{ y_fill: false,
|
||||
y_align: St.Align.START });
|
||||
|
||||
this.descriptionLabel = new St.Label({ style_class: 'show-processes-dialog-description' });
|
||||
this.descriptionLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
||||
this.descriptionLabel.clutter_text.line_wrap = true;
|
||||
|
||||
messageLayout.add(this.descriptionLabel,
|
||||
{ y_fill: true,
|
||||
y_align: St.Align.START });
|
||||
|
||||
let scrollView = new St.ScrollView({ style_class: 'show-processes-dialog-app-list'});
|
||||
scrollView.set_policy(Gtk.PolicyType.NEVER,
|
||||
Gtk.PolicyType.AUTOMATIC);
|
||||
this.contentLayout.add(scrollView,
|
||||
{ x_fill: true,
|
||||
y_fill: true });
|
||||
scrollView.hide();
|
||||
|
||||
this._applicationList = new St.BoxLayout({ vertical: true });
|
||||
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',
|
||||
Lang.bind(this, function() {
|
||||
if (this._applicationList.get_children().length == 1)
|
||||
scrollView.show();
|
||||
}));
|
||||
|
||||
this._applicationList.connect('actor-removed',
|
||||
Lang.bind(this, function() {
|
||||
if (this._applicationList.get_children().length == 0)
|
||||
scrollView.hide();
|
||||
}));
|
||||
},
|
||||
|
||||
_setAppsForPids: function(pids) {
|
||||
// remove all the items
|
||||
this._applicationList.destroy_children();
|
||||
|
||||
pids.forEach(Lang.bind(this, function(pid) {
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
let app = tracker.get_app_from_pid(pid);
|
||||
|
||||
if (!app)
|
||||
return;
|
||||
|
||||
let item = new ListItem(app);
|
||||
this._applicationList.add(item.actor, { x_fill: true });
|
||||
|
||||
item.connect('activate',
|
||||
Lang.bind(this, function() {
|
||||
// use -1 to indicate Cancel
|
||||
this.emit('response', -1);
|
||||
}));
|
||||
}));
|
||||
},
|
||||
|
||||
update: function(message, processes, choices) {
|
||||
this._setAppsForPids(processes);
|
||||
_setLabelsForMessage(this, message);
|
||||
_setButtonsForChoices(this, choices);
|
||||
}
|
||||
}
|
||||
Signals.addSignalMethods(ShellProcessesDialog.prototype);
|
@ -10,6 +10,7 @@ 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 PopupMenu = imports.ui.popupMenu;
|
||||
const Util = imports.misc.util;
|
||||
@ -88,6 +89,7 @@ ATIndicator.prototype = {
|
||||
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this.menu.addAction(_("Universal Access Settings"), function() {
|
||||
Main.overview.hide();
|
||||
let app = Shell.AppSystem.get_default().get_app('gnome-universal-access-panel.desktop');
|
||||
app.activate(-1);
|
||||
});
|
||||
|
@ -90,6 +90,7 @@ Indicator.prototype = {
|
||||
this._updateFullMenu();
|
||||
|
||||
this.menu.addAction(_("Bluetooth Settings"), function() {
|
||||
Main.overview.hide()
|
||||
let app = Shell.AppSystem.get_default().get_app('bluetooth-properties.desktop');
|
||||
app.activate(-1);
|
||||
});
|
||||
@ -108,7 +109,11 @@ Indicator.prototype = {
|
||||
current_state != GnomeBluetoothApplet.KillswitchState.HARD_BLOCKED;
|
||||
|
||||
this._killswitch.setToggleState(on);
|
||||
this._killswitch.actor.reactive = can_toggle;
|
||||
if (can_toggle)
|
||||
this._killswitch.setStatus(null);
|
||||
else
|
||||
/* TRANSLATORS: this means that bluetooth was disabled by hardware rfkill */
|
||||
this._killswitch.setStatus(_("hardware disabled"));
|
||||
|
||||
if (has_adapter)
|
||||
this.actor.show();
|
||||
@ -210,14 +215,15 @@ Indicator.prototype = {
|
||||
if (device.can_connect) {
|
||||
item._connected = device.connected;
|
||||
item._connectedMenuitem = new PopupMenu.PopupSwitchMenuItem(_("Connection"), device.connected);
|
||||
|
||||
item._connectedMenuitem.connect('toggled', Lang.bind(this, function() {
|
||||
if (item._connected > ConnectionState.CONNECTED) {
|
||||
// operation already in progress, revert
|
||||
// (should not happen anyway)
|
||||
menuitem.setToggleState(menuitem.state);
|
||||
}
|
||||
if (item._connected) {
|
||||
item._connected = ConnectionState.DISCONNECTING;
|
||||
menuitem.setStatus(_("disconnecting..."));
|
||||
this._applet.disconnect_device(item._device.device_path, function(applet, success) {
|
||||
if (success) { // apply
|
||||
item._connected = ConnectionState.DISCONNECTED;
|
||||
@ -226,9 +232,11 @@ Indicator.prototype = {
|
||||
item._connected = ConnectionState.CONNECTED;
|
||||
menuitem.setToggleState(true);
|
||||
}
|
||||
menuitem.setStatus(null);
|
||||
});
|
||||
} else {
|
||||
item._connected = ConnectionState.CONNECTING;
|
||||
menuitem.setStatus(_("connecting..."));
|
||||
this._applet.connect_device(item._device.device_path, function(applet, success) {
|
||||
if (success) { // apply
|
||||
item._connected = ConnectionState.CONNECTED;
|
||||
@ -237,6 +245,7 @@ Indicator.prototype = {
|
||||
item._connected = ConnectionState.DISCONNECTED;
|
||||
menuitem.setToggleState(false);
|
||||
}
|
||||
menuitem.setStatus(null);
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
@ -9,6 +9,7 @@ const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const Util = imports.misc.util;
|
||||
@ -68,9 +69,11 @@ XKBIndicator.prototype = {
|
||||
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this.menu.addAction(_("Show Keyboard Layout..."), Lang.bind(this, function() {
|
||||
Main.overview.hide();
|
||||
Util.spawn(['gkbd-keyboard-display', '-g', String(this._config.get_current_group() + 1)]);
|
||||
}));
|
||||
this.menu.addAction(_("Localization Settings"), function() {
|
||||
Main.overview.hide();
|
||||
let app = Shell.AppSystem.get_default().get_app('gnome-region-panel.desktop');
|
||||
app.activate(-1);
|
||||
});
|
||||
|
@ -192,63 +192,7 @@ NMNetworkMenuItem.prototype = {
|
||||
apObj.updateId = 0;
|
||||
}
|
||||
|
||||
PopupMenu.PopupImageMenuItem.prototype.destroy.call(this);
|
||||
}
|
||||
};
|
||||
|
||||
function NMDeviceTitleMenuItem() {
|
||||
this._init.apply(this, arguments);
|
||||
}
|
||||
|
||||
NMDeviceTitleMenuItem.prototype = {
|
||||
__proto__: PopupMenu.PopupBaseMenuItem.prototype,
|
||||
|
||||
_init: function(description, params) {
|
||||
PopupMenu.PopupBaseMenuItem.prototype._init.call(this, params);
|
||||
|
||||
this._descriptionLabel = new St.Label({ text: description,
|
||||
style_class: 'popup-subtitle-menu-item'
|
||||
});
|
||||
this.addActor(this._descriptionLabel);
|
||||
|
||||
this._statusBin = new St.Bin({ x_align: St.Align.END });
|
||||
this.addActor(this._statusBin, { align: St.Align.END });
|
||||
|
||||
this._statusLabel = new St.Label({ text: '',
|
||||
style_class: 'popup-inactive-menu-item'
|
||||
});
|
||||
this._switch = new PopupMenu.Switch(false);
|
||||
this._statusBin.child = this._switch.actor;
|
||||
},
|
||||
|
||||
setStatus: function(text) {
|
||||
if (text != null) {
|
||||
this._statusLabel.text = text;
|
||||
this._statusBin.child = this._statusLabel;
|
||||
this.actor.reactive = false;
|
||||
this.actor.can_focus = false;
|
||||
} else {
|
||||
this._statusBin.child = this._switch.actor;
|
||||
this.actor.reactive = true;
|
||||
this.actor.can_focus = true;
|
||||
}
|
||||
},
|
||||
|
||||
activate: function(event) {
|
||||
if (this._switch.actor.mapped) {
|
||||
this._switch.toggle();
|
||||
this.emit('toggled', this._switch.state);
|
||||
}
|
||||
|
||||
PopupMenu.PopupBaseMenuItem.prototype.activate.call(this, event);
|
||||
},
|
||||
|
||||
get state() {
|
||||
return this._switch.state;
|
||||
},
|
||||
|
||||
setToggleState: function(newval) {
|
||||
this._switch.setToggleState(newval);
|
||||
PopupMenu.PopupBaseMenuItem.prototype.destroy.call(this);
|
||||
}
|
||||
};
|
||||
|
||||
@ -257,7 +201,13 @@ function NMWiredSectionTitleMenuItem() {
|
||||
}
|
||||
|
||||
NMWiredSectionTitleMenuItem.prototype = {
|
||||
__proto__: NMDeviceTitleMenuItem.prototype,
|
||||
__proto__: PopupMenu.PopupSwitchMenuItem.prototype,
|
||||
|
||||
_init: function(label, params) {
|
||||
params = params || { };
|
||||
params.style_class = 'popup-subtitle-menu-item';
|
||||
PopupMenu.PopupSwitchMenuItem.prototype._init.call(this, label, false, params);
|
||||
},
|
||||
|
||||
updateForDevice: function(device) {
|
||||
if (device) {
|
||||
@ -269,7 +219,7 @@ NMWiredSectionTitleMenuItem.prototype = {
|
||||
},
|
||||
|
||||
activate: function(event) {
|
||||
NMDeviceTitleMenuItem.prototype.activate.call(this, event);
|
||||
PopupMenu.PopupSwitchMenuItem.prototype.activate.call(this, event);
|
||||
|
||||
if (!this._device) {
|
||||
log('Section title activated when there is more than one device, should be non reactive');
|
||||
@ -295,10 +245,12 @@ function NMWirelessSectionTitleMenuItem() {
|
||||
}
|
||||
|
||||
NMWirelessSectionTitleMenuItem.prototype = {
|
||||
__proto__: NMDeviceTitleMenuItem.prototype,
|
||||
__proto__: PopupMenu.PopupSwitchMenuItem.prototype,
|
||||
|
||||
_init: function(client, property, title, params) {
|
||||
NMDeviceTitleMenuItem.prototype._init.call(this, title, params);
|
||||
params = params || { };
|
||||
params.style_class = 'popup-subtitle-menu-item';
|
||||
PopupMenu.PopupSwitchMenuItem.prototype._init.call(this, title, false, params);
|
||||
|
||||
this._client = client;
|
||||
this._property = property + '_enabled';
|
||||
@ -324,7 +276,7 @@ NMWirelessSectionTitleMenuItem.prototype = {
|
||||
},
|
||||
|
||||
activate: function(event) {
|
||||
NMDeviceTitleMenuItem.prototype.activate.call(this, event);
|
||||
PopupMenu.PopupSwitchMenuItem.prototype.activate.call(this, event);
|
||||
|
||||
this._client[this._setEnabledFunc](this._switch.state);
|
||||
},
|
||||
@ -373,15 +325,14 @@ NMDevice.prototype = {
|
||||
};
|
||||
this._connections.push(obj);
|
||||
}
|
||||
this._connections.sort(function(one, two) {
|
||||
return two.timestamp - one.timestamp;
|
||||
});
|
||||
this._connections.sort(this._connectionSortFunction);
|
||||
this._activeConnection = null;
|
||||
this._activeConnectionItem = null;
|
||||
this._autoConnectionItem = null;
|
||||
this._overflowItem = null;
|
||||
|
||||
if (this.device) {
|
||||
this.statusItem = new NMDeviceTitleMenuItem(this._getDescription());
|
||||
this.statusItem = new PopupMenu.PopupSwitchMenuItem(this._getDescription(), this.connected, { style_class: 'popup-subtitle-menu-item' });
|
||||
this._statusChanged = this.statusItem.connect('toggled', Lang.bind(this, function(item, state) {
|
||||
if (state)
|
||||
this.activate();
|
||||
@ -482,9 +433,7 @@ NMDevice.prototype = {
|
||||
timestamp: connection._timestamp,
|
||||
};
|
||||
this._connections.push(obj);
|
||||
this._connections.sort(function(one, two) {
|
||||
return two.timestamp - one.timestamp;
|
||||
});
|
||||
this._connections.sort(this._connectionSortFunction);
|
||||
|
||||
this._clearSection();
|
||||
this._createSection();
|
||||
@ -519,6 +468,13 @@ NMDevice.prototype = {
|
||||
return this.device.connection_valid(connection);
|
||||
},
|
||||
|
||||
_connectionSortFunction: function(one, two) {
|
||||
if (one.timestamp == two.timestamp)
|
||||
return GLib.utf8_collate(one.name, two.name);
|
||||
|
||||
return two.timestamp - one.timestamp;
|
||||
},
|
||||
|
||||
setEnabled: function(enabled) {
|
||||
// do nothing by default, we want to keep the conneciton list visible
|
||||
// in the majority of cases (wired, wwan, vpn)
|
||||
@ -526,11 +482,15 @@ NMDevice.prototype = {
|
||||
|
||||
getStatusLabel: function() {
|
||||
switch(this.device.state) {
|
||||
case NetworkManager.DeviceState.UNMANAGED:
|
||||
case NetworkManager.DeviceState.DISCONNECTED:
|
||||
case NetworkManager.DeviceState.DEACTIVATING:
|
||||
case NetworkManager.DeviceState.ACTIVATED:
|
||||
return null;
|
||||
case NetworkManager.DeviceState.UNMANAGED:
|
||||
/* Translators: this is for network devices that are physically present but are not
|
||||
under NetworkManager's control (and thus cannot be used in the menu) */
|
||||
return _("unmanaged");
|
||||
case NetworkManager.DeviceState.DEACTIVATING:
|
||||
return _("disconnecting...");
|
||||
case NetworkManager.DeviceState.PREPARE:
|
||||
case NetworkManager.DeviceState.CONFIG:
|
||||
case NetworkManager.DeviceState.IP_CONFIG:
|
||||
@ -589,6 +549,7 @@ NMDevice.prototype = {
|
||||
this.section.removeAll();
|
||||
this._autoConnectionItem = null;
|
||||
this._activeConnectionItem = null;
|
||||
this._overflowItem = null;
|
||||
for (let i = 0; i < this._connections.length; i++) {
|
||||
this._connections[i].item = null;
|
||||
}
|
||||
@ -607,13 +568,23 @@ NMDevice.prototype = {
|
||||
this.section.addMenuItem(this._activeConnectionItem);
|
||||
}
|
||||
if (this._connections.length > 0) {
|
||||
let activeOffset = this._activeConnectionItem ? 1 : 0;
|
||||
|
||||
for(let j = 0; j < this._connections.length; ++j) {
|
||||
let obj = this._connections[j];
|
||||
if (this._activeConnection &&
|
||||
obj.connection == this._activeConnection._connection)
|
||||
continue;
|
||||
obj.item = this._createConnectionItem(obj);
|
||||
this.section.addMenuItem(obj.item);
|
||||
|
||||
if (j + activeOffset >= NUM_VISIBLE_NETWORKS) {
|
||||
if (!this._overflowItem) {
|
||||
this._overflowItem = new PopupMenu.PopupSubMenuMenuItem(_("More..."));
|
||||
this.section.addMenuItem(this._overflowItem);
|
||||
}
|
||||
this._overflowItem.menu.addMenuItem(obj.item);
|
||||
} else
|
||||
this.section.addMenuItem(obj.item);
|
||||
}
|
||||
} else if (this._autoConnectionName) {
|
||||
this._autoConnectionItem = new PopupMenu.PopupMenuItem(this._autoConnectionName);
|
||||
@ -821,7 +792,7 @@ NMDeviceModem.prototype = {
|
||||
}));
|
||||
}
|
||||
|
||||
NMDevice.prototype._init.call(this, client, device, connections, 1);
|
||||
NMDevice.prototype._init.call(this, client, device, connections);
|
||||
},
|
||||
|
||||
setEnabled: function(enabled) {
|
||||
@ -838,7 +809,7 @@ NMDeviceModem.prototype = {
|
||||
},
|
||||
|
||||
get connected() {
|
||||
return this._enabled && this.device.state == NetworkManager.DeviceState.CONNECTED;
|
||||
return this._enabled && this.device.state == NetworkManager.DeviceState.ACTIVATED;
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
@ -1310,12 +1281,29 @@ NMDeviceWireless.prototype = {
|
||||
if (apObj.accessPoints.length == 0) {
|
||||
if (apObj.item)
|
||||
apObj.item.destroy();
|
||||
this._networks.splice(pos, 1);
|
||||
if (this._overflowItem &&
|
||||
this._overflowItem.menu.length == 0) {
|
||||
this._overflowItem.destroy();
|
||||
this._overflowItem = null;
|
||||
|
||||
if (this._overflowItem) {
|
||||
if (!apObj.isMore) {
|
||||
// we removed an item in the main menu, and we have a more submenu
|
||||
// we need to extract the first item in more and move it to the submenu
|
||||
|
||||
let apObj = this._overflowItem.menu.firstMenuItem;
|
||||
if (apObj.item) {
|
||||
apObj.item.destroy();
|
||||
|
||||
this._createNetworkItem(apObj, NUM_VISIBLE_NETWORKS-1);
|
||||
}
|
||||
}
|
||||
|
||||
// This can happen if the removed connection is from the overflow
|
||||
// menu, or if we just moved the last connection out from the menu
|
||||
if (this._overflowItem.menu.length == 0) {
|
||||
this._overflowItem.destroy();
|
||||
this._overflowItem = null;
|
||||
}
|
||||
}
|
||||
this._networks.splice(pos, 1);
|
||||
|
||||
} else if (apObj.item)
|
||||
apObj.item.updateAccessPoints(apObj.accessPoints);
|
||||
},
|
||||
@ -1511,14 +1499,16 @@ NMDeviceWireless.prototype = {
|
||||
}
|
||||
}));
|
||||
}
|
||||
if (position < NUM_VISIBLE_NETWORKS)
|
||||
this.section.addMenuItem(apObj.item);
|
||||
else {
|
||||
if (position < NUM_VISIBLE_NETWORKS) {
|
||||
apObj.isMore = false;
|
||||
this.section.addMenuItem(apObj.item, position);
|
||||
} else {
|
||||
if (!this._overflowItem) {
|
||||
this._overflowItem = new PopupMenu.PopupSubMenuMenuItem(_("More..."));
|
||||
this.section.addMenuItem(this._overflowItem);
|
||||
}
|
||||
this._overflowItem.menu.addMenuItem(apObj.item, position - NUM_VISIBLE_NETWORKS);
|
||||
apObj.isMore = true;
|
||||
}
|
||||
},
|
||||
|
||||
@ -1613,6 +1603,7 @@ NMApplet.prototype = {
|
||||
this.menu.addMenuItem(this._devices.vpn.section);
|
||||
|
||||
this.menu.addAction(_("Network Settings"), function() {
|
||||
Main.overview.hide();
|
||||
let app = Shell.AppSystem.get_default().get_app('gnome-network-panel.desktop');
|
||||
app.activate(-1);
|
||||
});
|
||||
@ -1691,21 +1682,18 @@ NMApplet.prototype = {
|
||||
|
||||
_syncSectionTitle: function(category) {
|
||||
let devices = this._devices[category].devices;
|
||||
let managedDevices = devices.filter(function(dev) {
|
||||
return dev.device.state != NetworkManager.DeviceState.UNMANAGED;
|
||||
});
|
||||
let item = this._devices[category].item;
|
||||
let section = this._devices[category].section;
|
||||
if (managedDevices.length == 0)
|
||||
if (devices.length == 0)
|
||||
section.actor.hide();
|
||||
else {
|
||||
section.actor.show();
|
||||
if (managedDevices.length == 1) {
|
||||
let dev = managedDevices[0];
|
||||
if (devices.length == 1) {
|
||||
let dev = devices[0];
|
||||
dev.statusItem.actor.hide();
|
||||
item.updateForDevice(dev);
|
||||
} else {
|
||||
managedDevices.forEach(function(dev) {
|
||||
devices.forEach(function(dev) {
|
||||
dev.statusItem.actor.show();
|
||||
});
|
||||
// remove status text from the section title item
|
||||
|
@ -7,12 +7,13 @@ const Mainloop = imports.mainloop;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
const BUS_NAME = 'org.gnome.PowerManager';
|
||||
const OBJECT_PATH = '/org/gnome/PowerManager';
|
||||
const BUS_NAME = 'org.gnome.SettingsDaemon';
|
||||
const OBJECT_PATH = '/org/gnome/SettingsDaemon/Power';
|
||||
|
||||
const UPDeviceType = {
|
||||
UNKNOWN: 0,
|
||||
@ -40,7 +41,7 @@ const UPDeviceState = {
|
||||
};
|
||||
|
||||
const PowerManagerInterface = {
|
||||
name: 'org.gnome.PowerManager',
|
||||
name: 'org.gnome.SettingsDaemon.Power',
|
||||
methods: [
|
||||
{ name: 'GetDevices', inSignature: '', outSignature: 'a(susbut)' },
|
||||
{ name: 'GetPrimaryDevice', inSignature: '', outSignature: '(susbut)' },
|
||||
@ -80,6 +81,7 @@ Indicator.prototype = {
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
|
||||
this.menu.addAction(_("Power Settings"),function() {
|
||||
Main.overview.hide();
|
||||
let app = Shell.AppSystem.get_default().get_app('gnome-power-panel.desktop');
|
||||
app.activate(-1);
|
||||
});
|
||||
@ -91,7 +93,6 @@ Indicator.prototype = {
|
||||
_readPrimaryDevice: function() {
|
||||
this._proxy.GetPrimaryDeviceRemote(Lang.bind(this, function(device, error) {
|
||||
if (error) {
|
||||
this._checkError(error);
|
||||
this._hasPrimary = false;
|
||||
this._primaryDeviceId = null;
|
||||
this._batteryItem.actor.hide();
|
||||
@ -143,7 +144,6 @@ Indicator.prototype = {
|
||||
this._deviceItems = [];
|
||||
|
||||
if (error) {
|
||||
this._checkError(error);
|
||||
this._deviceSep.actor.hide();
|
||||
return;
|
||||
}
|
||||
@ -174,21 +174,12 @@ Indicator.prototype = {
|
||||
this.setGIcon(gicon);
|
||||
this.actor.show();
|
||||
} else {
|
||||
this._checkError(error);
|
||||
this.menu.close();
|
||||
this.actor.hide();
|
||||
}
|
||||
}));
|
||||
this._readPrimaryDevice();
|
||||
this._readOtherDevices();
|
||||
},
|
||||
|
||||
_checkError: function(error) {
|
||||
if (!this._restarted && error && error.message.match(/org\.freedesktop\.DBus\.Error\.(UnknownMethod|InvalidArgs)/)) {
|
||||
Util.killall('gnome-power-manager');
|
||||
Util.spawn(['gnome-power-manager']);
|
||||
this._restarted = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -9,6 +9,7 @@ const Gvc = imports.gi.Gvc;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Util = imports.misc.util;
|
||||
@ -61,6 +62,7 @@ Indicator.prototype = {
|
||||
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this.menu.addAction(_("Sound Settings"), function() {
|
||||
Main.overview.hide();
|
||||
let app = Shell.AppSystem.get_default().get_app('gnome-sound-panel.desktop');
|
||||
app.activate(-1);
|
||||
});
|
||||
|
@ -14,23 +14,14 @@ const GnomeSession = imports.misc.gnomeSession;
|
||||
const Main = imports.ui.main;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const ScreenSaver = imports.misc.screenSaver;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
const BUS_NAME = 'org.gnome.ScreenSaver';
|
||||
const OBJECT_PATH = '/org/gnome/ScreenSaver';
|
||||
|
||||
const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown';
|
||||
const DISABLE_USER_SWITCH_KEY = 'disable-user-switching';
|
||||
const DISABLE_LOCK_SCREEN_KEY = 'disable-lock-screen';
|
||||
const DISABLE_LOG_OUT_KEY = 'disable-log-out';
|
||||
|
||||
const ScreenSaverInterface = {
|
||||
name: BUS_NAME,
|
||||
methods: [ { name: 'Lock', inSignature: '' } ]
|
||||
};
|
||||
|
||||
let ScreenSaverProxy = DBus.makeProxyClass(ScreenSaverInterface);
|
||||
|
||||
// Adapted from gdm/gui/user-switch-applet/applet.c
|
||||
//
|
||||
// Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
|
||||
@ -57,11 +48,12 @@ StatusMenuButton.prototype = {
|
||||
this._presence = new GnomeSession.Presence();
|
||||
this._presenceItems = {};
|
||||
this._session = new GnomeSession.SessionManager();
|
||||
this._haveShutdown = true;
|
||||
|
||||
this._account_mgr = Tp.AccountManager.dup()
|
||||
|
||||
this._upClient = new UPowerGlib.Client();
|
||||
this._screenSaverProxy = new ScreenSaverProxy(DBus.session, BUS_NAME, OBJECT_PATH);
|
||||
this._screenSaverProxy = new ScreenSaver.ScreenSaverProxy();
|
||||
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
||||
|
||||
this._iconBox = new St.Bin();
|
||||
@ -89,12 +81,25 @@ StatusMenuButton.prototype = {
|
||||
Lang.bind(this, this._updateSwitchUser));
|
||||
this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY,
|
||||
Lang.bind(this, this._updateLogout));
|
||||
|
||||
this._lockdownSettings.connect('changed::' + DISABLE_LOCK_SCREEN_KEY,
|
||||
Lang.bind(this, this._updateLockScreen));
|
||||
this._updateSwitchUser();
|
||||
this._updateLogout();
|
||||
this._updateLockScreen();
|
||||
|
||||
// Whether shutdown is available or not depends on both lockdown
|
||||
// settings (disable-log-out) and Polkit policy - the latter doesn't
|
||||
// notify, so we update the menu item each time the menu opens or
|
||||
// the lockdown setting changes, which should be close enough.
|
||||
this.menu.connect('open-state-changed', Lang.bind(this,
|
||||
function(menu, open) {
|
||||
if (open)
|
||||
this._updateHaveShutdown();
|
||||
}));
|
||||
this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY,
|
||||
Lang.bind(this, this._updateHaveShutdown));
|
||||
|
||||
this._upClient.connect('notify::can-suspend', Lang.bind(this, this._updateSuspendOrPowerOff));
|
||||
},
|
||||
|
||||
@ -111,13 +116,25 @@ StatusMenuButton.prototype = {
|
||||
},
|
||||
|
||||
_updateSessionSeparator: function() {
|
||||
let showSeparator = this._loginScreenItem.actor.visible ||
|
||||
this._logoutItem.actor.visible ||
|
||||
this._lockScreenItem.actor.visible;
|
||||
if (showSeparator)
|
||||
let sessionItemsVisible = this._loginScreenItem.actor.visible ||
|
||||
this._logoutItem.actor.visible ||
|
||||
this._lockScreenItem.actor.visible;
|
||||
|
||||
let showSessionSeparator = sessionItemsVisible &&
|
||||
this._suspendOrPowerOffItem.actor.visible;
|
||||
|
||||
let showSettingsSeparator = sessionItemsVisible ||
|
||||
this._suspendOrPowerOffItem.actor.visible;
|
||||
|
||||
if (showSessionSeparator)
|
||||
this._sessionSeparator.actor.show();
|
||||
else
|
||||
this._sessionSeparator.actor.hide();
|
||||
|
||||
if (showSettingsSeparator)
|
||||
this._settingsSeparator.actor.show();
|
||||
else
|
||||
this._settingsSeparator.actor.hide();
|
||||
},
|
||||
|
||||
_updateSwitchUser: function() {
|
||||
@ -147,16 +164,34 @@ StatusMenuButton.prototype = {
|
||||
this._updateSessionSeparator();
|
||||
},
|
||||
|
||||
_updateHaveShutdown: function() {
|
||||
this._session.CanShutdownRemote(Lang.bind(this,
|
||||
function(result, error) {
|
||||
if (!error) {
|
||||
this._haveShutdown = result;
|
||||
this._updateSuspendOrPowerOff();
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
_updateSuspendOrPowerOff: function() {
|
||||
this._haveSuspend = this._upClient.get_can_suspend();
|
||||
|
||||
if (!this._suspendOrPowerOffItem)
|
||||
return;
|
||||
|
||||
if (!this._haveShutdown && !this._haveSuspend)
|
||||
this._suspendOrPowerOffItem.actor.hide();
|
||||
else
|
||||
this._suspendOrPowerOffItem.actor.show();
|
||||
this._updateSessionSeparator();
|
||||
|
||||
// If we can't suspend show Power Off... instead
|
||||
// and disable the alt key
|
||||
if (!this._haveSuspend) {
|
||||
this._suspendOrPowerOffItem.updateText(_("Power Off..."), null);
|
||||
} else if (!this._haveShutdown) {
|
||||
this._suspendOrPowerOffItem.updateText(_("Suspend"), null);
|
||||
} else {
|
||||
this._suspendOrPowerOffItem.updateText(_("Suspend"), _("Power Off..."));
|
||||
}
|
||||
@ -202,6 +237,7 @@ StatusMenuButton.prototype = {
|
||||
|
||||
item = new PopupMenu.PopupSeparatorMenuItem();
|
||||
this.menu.addMenuItem(item);
|
||||
this._settingsSeparator = item;
|
||||
|
||||
item = new PopupMenu.PopupMenuItem(_("Lock Screen"));
|
||||
item.connect('activate', Lang.bind(this, this._onLockScreenActivate));
|
||||
@ -255,8 +291,11 @@ StatusMenuButton.prototype = {
|
||||
|
||||
_onLoginScreenActivate: function() {
|
||||
Main.overview.hide();
|
||||
this._gdm.goto_login_session();
|
||||
this._onLockScreenActivate();
|
||||
// Ensure we only move to GDM after the screensaver has activated; in some
|
||||
// OS configurations, the X server may block event processing on VT switch
|
||||
this._screenSaverProxy.SetActiveRemote(true, Lang.bind(this, function() {
|
||||
this._gdm.goto_login_session();
|
||||
}));
|
||||
},
|
||||
|
||||
_onQuitSessionActivate: function() {
|
||||
@ -269,7 +308,8 @@ StatusMenuButton.prototype = {
|
||||
|
||||
if (this._haveSuspend &&
|
||||
this._suspendOrPowerOffItem.state == PopupMenu.PopupAlternatingMenuItemState.DEFAULT) {
|
||||
this._screenSaverProxy.LockRemote(Lang.bind(this, function() {
|
||||
// Ensure we only suspend after the screensaver has activated
|
||||
this._screenSaverProxy.SetActiveRemote(true, Lang.bind(this, function() {
|
||||
this._upClient.suspend_sync(null);
|
||||
}));
|
||||
} else {
|
||||
|
@ -24,6 +24,9 @@ const SCROLLBACK_IDLE_LENGTH = 5;
|
||||
// See Source._displayPendingMessages
|
||||
const SCROLLBACK_HISTORY_LINES = 10;
|
||||
|
||||
// See Notification._onEntryChanged
|
||||
const COMPOSING_STOP_TIMEOUT = 5;
|
||||
|
||||
const NotificationDirection = {
|
||||
SENT: 'chat-sent',
|
||||
RECEIVED: 'chat-received'
|
||||
@ -69,22 +72,34 @@ function Client() {
|
||||
|
||||
Client.prototype = {
|
||||
_init : function() {
|
||||
// channel path -> Source
|
||||
this._sources = {};
|
||||
// channel path -> ChatSource
|
||||
this._chatSources = {};
|
||||
this._chatState = Tp.ChannelChatState.ACTIVE;
|
||||
|
||||
// Set up a SimpleObserver, which will call _observeChannels whenever a
|
||||
// channel matching its filters is detected.
|
||||
// The second argument, recover, means _observeChannels will be run
|
||||
// for any existing channel as well.
|
||||
let dbus = Tp.DBusDaemon.dup();
|
||||
this._observer = Shell.TpClient.new(dbus);
|
||||
this._observer.set_observe_channels_func(
|
||||
this._tpClient = new Shell.TpClient({ 'dbus_daemon': dbus,
|
||||
'name': 'GnomeShell',
|
||||
'uniquify-name': true })
|
||||
this._tpClient.set_observe_channels_func(
|
||||
Lang.bind(this, this._observeChannels));
|
||||
this._tpClient.set_approve_channels_func(
|
||||
Lang.bind(this, this._approveChannels));
|
||||
this._tpClient.set_handle_channels_func(
|
||||
Lang.bind(this, this._handleChannels));
|
||||
|
||||
// Allow other clients (such as Empathy) to pre-empt our channels if
|
||||
// needed
|
||||
this._tpClient.set_delegated_channels_callback(
|
||||
Lang.bind(this, this._delegatedChannelsCb));
|
||||
|
||||
try {
|
||||
this._observer.register();
|
||||
this._tpClient.register();
|
||||
} catch (e) {
|
||||
throw new Error('Couldn\'t register SimpleObserver. Error: \n' + e);
|
||||
throw new Error('Couldn\'t register Telepathy client. Error: \n' + e);
|
||||
}
|
||||
},
|
||||
|
||||
@ -97,7 +112,7 @@ Client.prototype = {
|
||||
this._finishObserveChannels(account, conn, channels, context);
|
||||
} else {
|
||||
Shell.get_self_contact_features(conn,
|
||||
contactFeatures.length, contactFeatures,
|
||||
contactFeatures,
|
||||
Lang.bind(this, function() {
|
||||
this._finishObserveChannels(account, conn, channels, context);
|
||||
}));
|
||||
@ -117,60 +132,216 @@ Client.prototype = {
|
||||
continue;
|
||||
|
||||
/* Request a TpContact */
|
||||
Shell.get_tp_contacts(conn, 1, [targetHandle],
|
||||
contactFeatures.length, contactFeatures,
|
||||
Shell.get_tp_contacts(conn, [targetHandle],
|
||||
contactFeatures,
|
||||
Lang.bind(this, function (connection, contacts, failed) {
|
||||
if (contacts.length < 1)
|
||||
return;
|
||||
|
||||
/* We got the TpContact */
|
||||
this._createSource(account, conn, channel, contacts[0]);
|
||||
this._createChatSource(account, conn, channel, contacts[0]);
|
||||
}), null);
|
||||
}
|
||||
|
||||
context.accept();
|
||||
},
|
||||
|
||||
_createSource: function(account, conn, channel, contact) {
|
||||
if (this._sources[channel.get_object_path()])
|
||||
_createChatSource: function(account, conn, channel, contact) {
|
||||
if (this._chatSources[channel.get_object_path()])
|
||||
return;
|
||||
|
||||
let source = new Source(account, conn, channel, contact);
|
||||
let source = new ChatSource(account, conn, channel, contact, this._tpClient);
|
||||
|
||||
this._sources[channel.get_object_path()] = source;
|
||||
this._chatSources[channel.get_object_path()] = source;
|
||||
source.connect('destroy', Lang.bind(this,
|
||||
function() {
|
||||
delete this._sources[channel.get_object_path()];
|
||||
if (this._tpClient.is_handling_channel(channel)) {
|
||||
// The chat box has been destroyed so it can't
|
||||
// handle the channel any more.
|
||||
channel.close_async(function(src, result) {
|
||||
channel.close_finish(result);
|
||||
});
|
||||
}
|
||||
|
||||
delete this._chatSources[channel.get_object_path()];
|
||||
}));
|
||||
},
|
||||
|
||||
_handleChannels: function(handler, account, conn, channels,
|
||||
requests, user_action_time, context) {
|
||||
this._handlingChannels(account, conn, channels);
|
||||
context.accept();
|
||||
},
|
||||
|
||||
_handlingChannels: function(account, conn, channels) {
|
||||
let len = channels.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
let channel = channels[i];
|
||||
|
||||
// We can only handle text channel, so close any other channel
|
||||
if (!(channel instanceof Tp.TextChannel)) {
|
||||
channel.close_async(null);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this._tpClient.is_handling_channel(channel)) {
|
||||
// We are already handling the channel, display the source
|
||||
let source = this._chatSources[channel.get_object_path()];
|
||||
if (source)
|
||||
source.notify();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_displayRoomInvitation: function(conn, channel, dispatchOp, context) {
|
||||
// We can only approve the rooms if we have been invited to it
|
||||
let selfHandle = channel.group_get_self_handle();
|
||||
if (selfHandle == 0) {
|
||||
Shell.decline_dispatch_op(context, 'Not invited to the room');
|
||||
return;
|
||||
}
|
||||
|
||||
let [invited, inviter, reason, msg] = channel.group_get_local_pending_info(selfHandle);
|
||||
if (!invited) {
|
||||
Shell.decline_dispatch_op(context, 'Not invited to the room');
|
||||
return;
|
||||
}
|
||||
|
||||
// Request a TpContact for the inviter
|
||||
Shell.get_tp_contacts(conn, [inviter],
|
||||
contactFeatures,
|
||||
Lang.bind(this, this._createRoomInviteSource, channel, context, dispatchOp));
|
||||
|
||||
context.delay();
|
||||
},
|
||||
|
||||
_createRoomInviteSource: function(connection, contacts, failed, channel, context, dispatchOp) {
|
||||
if (contacts.length < 1) {
|
||||
Shell.decline_dispatch_op(context, 'Failed to get inviter');
|
||||
return;
|
||||
}
|
||||
|
||||
// We got the TpContact
|
||||
|
||||
// FIXME: We don't have a 'chat room' icon (bgo #653737) use
|
||||
// system-users for now as Empathy does.
|
||||
let source = new ApproverSource(dispatchOp, _("Invitation"), 'system-users');
|
||||
Main.messageTray.add(source);
|
||||
|
||||
let notif = new RoomInviteNotification(source, dispatchOp, channel, contacts[0]);
|
||||
source.notify(notif);
|
||||
context.accept();
|
||||
},
|
||||
|
||||
_approveChannels: function(approver, account, conn, channels,
|
||||
dispatchOp, context) {
|
||||
let channel = channels[0];
|
||||
let chanType = channel.get_channel_type();
|
||||
|
||||
if (chanType == Tp.IFACE_CHANNEL_TYPE_TEXT)
|
||||
this._approveTextChannel(account, conn, channel, dispatchOp, context);
|
||||
else if (chanType == Tp.IFACE_CHANNEL_TYPE_STREAMED_MEDIA ||
|
||||
chanType == 'org.freedesktop.Telepathy.Channel.Type.Call.DRAFT')
|
||||
this._approveCall(account, conn, channel, dispatchOp, context);
|
||||
},
|
||||
|
||||
_approveTextChannel: function(account, conn, channel, dispatchOp, context) {
|
||||
let [targetHandle, targetHandleType] = channel.get_handle();
|
||||
|
||||
if (targetHandleType == Tp.HandleType.CONTACT) {
|
||||
// Approve private text channels right away as we are going to handle it
|
||||
dispatchOp.claim_with_async(this._tpClient,
|
||||
Lang.bind(this, function(dispatchOp, result) {
|
||||
try {
|
||||
dispatchOp.claim_with_finish(result);
|
||||
this._handlingChannels(account, conn, [channel]);
|
||||
} catch (err) {
|
||||
throw new Error('Failed to Claim channel: ' + err);
|
||||
}}));
|
||||
|
||||
context.accept();
|
||||
} else {
|
||||
this._displayRoomInvitation(conn, channel, dispatchOp, context);
|
||||
}
|
||||
},
|
||||
|
||||
_approveCall: function(account, conn, channel, dispatchOp, context) {
|
||||
let [targetHandle, targetHandleType] = channel.get_handle();
|
||||
|
||||
Shell.get_tp_contacts(conn, [targetHandle],
|
||||
contactFeatures,
|
||||
Lang.bind(this, this._createAudioVideoSource, channel, context, dispatchOp));
|
||||
},
|
||||
|
||||
_createAudioVideoSource: function(connection, contacts, failed, channel, context, dispatchOp) {
|
||||
if (contacts.length < 1) {
|
||||
Shell.decline_dispatch_op(context, 'Failed to get inviter');
|
||||
return;
|
||||
}
|
||||
|
||||
let isVideo = false;
|
||||
|
||||
let props = channel.borrow_immutable_properties();
|
||||
|
||||
if (props['org.freedesktop.Telepathy.Channel.Type.Call.DRAFT.InitialVideo'] ||
|
||||
props[Tp.PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_VIDEO])
|
||||
isVideo = true;
|
||||
|
||||
// We got the TpContact
|
||||
let source = new ApproverSource(dispatchOp, _("Call"), isVideo ? 'camera-web' : 'audio-input-microphone');
|
||||
Main.messageTray.add(source);
|
||||
|
||||
let notif = new AudioVideoNotification(source, dispatchOp, channel, contacts[0], isVideo);
|
||||
source.notify(notif);
|
||||
context.accept();
|
||||
},
|
||||
|
||||
_delegatedChannelsCb: function(client, channels) {
|
||||
// Nothing to do as we don't make a distinction between observed and
|
||||
// handled channels.
|
||||
}
|
||||
};
|
||||
|
||||
function Source(account, conn, channel, contact) {
|
||||
this._init(account, conn, channel, contact);
|
||||
function ChatSource(account, conn, channel, contact, client) {
|
||||
this._init(account, conn, channel, contact, client);
|
||||
}
|
||||
|
||||
Source.prototype = {
|
||||
ChatSource.prototype = {
|
||||
__proto__: MessageTray.Source.prototype,
|
||||
|
||||
_init: function(account, conn, channel, contact) {
|
||||
_init: function(account, conn, channel, contact, client) {
|
||||
MessageTray.Source.prototype._init.call(this, contact.get_alias());
|
||||
|
||||
this.isChat = true;
|
||||
|
||||
this._account = account;
|
||||
this._contact = contact;
|
||||
this._client = client;
|
||||
|
||||
this._pendingMessages = [];
|
||||
|
||||
this._conn = conn;
|
||||
this._channel = channel;
|
||||
this._closedId = this._channel.connect('invalidated', Lang.bind(this, this._channelClosed));
|
||||
|
||||
this._notification = new Notification(this);
|
||||
this._notification = new ChatNotification(this);
|
||||
this._notification.setUrgency(MessageTray.Urgency.HIGH);
|
||||
|
||||
// We ack messages when the message box is collapsed if user has
|
||||
// interacted with it before and so read the messages:
|
||||
// - user clicked on it the tray
|
||||
// - user expanded the notification by hovering over the toaster notification
|
||||
this._shouldAck = false;
|
||||
|
||||
this.connect('summary-item-clicked', Lang.bind(this, this._summaryItemClicked));
|
||||
this._notification.connect('expanded', Lang.bind(this, this._notificationExpanded));
|
||||
this._notification.connect('collapsed', Lang.bind(this, this._notificationCollapsed));
|
||||
|
||||
this._presence = contact.get_presence_type();
|
||||
|
||||
this._sentId = this._channel.connect('message-sent', Lang.bind(this, this._messageSent));
|
||||
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._setSummaryIcon(this.createNotificationIcon());
|
||||
|
||||
@ -187,7 +358,7 @@ Source.prototype = {
|
||||
|
||||
_updateAlias: function() {
|
||||
let oldAlias = this.title;
|
||||
this.title = this._contact.get_alias();
|
||||
this.setTitle(this._contact.get_alias());
|
||||
this._notification.appendAliasChange(oldAlias, this.title);
|
||||
this.pushNotification(this._notification);
|
||||
},
|
||||
@ -216,13 +387,17 @@ Source.prototype = {
|
||||
},
|
||||
|
||||
open: function(notification) {
|
||||
let props = {};
|
||||
props[Tp.PROP_CHANNEL_CHANNEL_TYPE] = Tp.IFACE_CHANNEL_TYPE_TEXT;
|
||||
[props[Tp.PROP_CHANNEL_TARGET_HANDLE], props[Tp.PROP_CHANNEL_TARGET_HANDLE_TYPE]] = this._channel.get_handle();
|
||||
if (this._client.is_handling_channel(this._channel)) {
|
||||
// We are handling the channel, try to pass it to Empathy
|
||||
this._client.delegate_channels_async([this._channel], global.get_current_time(), "", null);
|
||||
}
|
||||
else {
|
||||
// We are not the handler, just ask to present the channel
|
||||
let dbus = Tp.DBusDaemon.dup();
|
||||
let cd = Tp.ChannelDispatcher.new(dbus);
|
||||
|
||||
let req = Tp.AccountChannelRequest.new(this._account, props, global.get_current_time());
|
||||
|
||||
req.ensure_channel_async('', null, null);
|
||||
cd.present_channel_async(this._channel, global.get_current_time(), null);
|
||||
}
|
||||
},
|
||||
|
||||
_getLogMessages: function() {
|
||||
@ -240,7 +415,20 @@ Source.prototype = {
|
||||
let logMessages = events.map(makeMessageFromTplEvent);
|
||||
|
||||
let pendingTpMessages = this._channel.get_pending_messages();
|
||||
let pendingMessages = pendingTpMessages.map(function (tpMessage) { return makeMessageFromTpMessage(tpMessage, NotificationDirection.RECEIVED); });
|
||||
let pendingMessages = [];
|
||||
|
||||
for (let i = 0; i < pendingTpMessages.length; i++) {
|
||||
let message = pendingTpMessages[i];
|
||||
|
||||
if (message.get_message_type() == Tp.ChannelTextMessageType.DELIVERY_REPORT)
|
||||
continue;
|
||||
|
||||
pendingMessages.push(makeMessageFromTpMessage(message, NotificationDirection.RECEIVED));
|
||||
|
||||
this._pendingMessages.push(message);
|
||||
}
|
||||
|
||||
this._updateCount();
|
||||
|
||||
let showTimestamp = false;
|
||||
|
||||
@ -276,6 +464,7 @@ Source.prototype = {
|
||||
_channelClosed: function() {
|
||||
this._channel.disconnect(this._closedId);
|
||||
this._channel.disconnect(this._receivedId);
|
||||
this._channel.disconnect(this._pendingId);
|
||||
this._channel.disconnect(this._sentId);
|
||||
|
||||
this._contact.disconnect(this._notifyAliasId);
|
||||
@ -285,10 +474,17 @@ Source.prototype = {
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
_updateCount: function() {
|
||||
this._setCount(this._pendingMessages.length, this._pendingMessages.length > 0);
|
||||
},
|
||||
|
||||
_messageReceived: function(channel, message) {
|
||||
if (message.get_message_type() == Tp.ChannelTextMessageType.DELIVERY_REPORT)
|
||||
return;
|
||||
|
||||
this._pendingMessages.push(message);
|
||||
this._updateCount();
|
||||
|
||||
message = makeMessageFromTpMessage(message, NotificationDirection.RECEIVED);
|
||||
this._notification.appendMessage(message);
|
||||
this.notify();
|
||||
@ -315,10 +511,25 @@ Source.prototype = {
|
||||
}
|
||||
|
||||
let msg = Tp.ClientMessage.new_text(type, text);
|
||||
this._channel.send_message_async(msg, 0, null);
|
||||
this._channel.send_message_async(msg, 0, Lang.bind(this, function (src, result) {
|
||||
this._channel.send_message_finish(result);
|
||||
}));
|
||||
},
|
||||
|
||||
_presenceChanged: function (contact, presence, type, status, message) {
|
||||
setChatState: function(state) {
|
||||
// We don't want to send COMPOSING every time a letter is typed into
|
||||
// the entry. We send the state only when it changes. Telepathy/Empathy
|
||||
// might change it behind our back if the user is using both
|
||||
// gnome-shell's entry and the Empathy conversation window. We could
|
||||
// keep track of it with the ChatStateChanged signal but it is good
|
||||
// enough right now.
|
||||
if (state != this._chatState) {
|
||||
this._chatState = state;
|
||||
this._channel.set_chat_state_async(state, null);
|
||||
}
|
||||
},
|
||||
|
||||
_presenceChanged: function (contact, presence, status, message) {
|
||||
let msg, shouldNotify, title;
|
||||
|
||||
if (this._presence == presence)
|
||||
@ -351,14 +562,53 @@ Source.prototype = {
|
||||
this._notification.appendPresence(msg, shouldNotify);
|
||||
if (shouldNotify)
|
||||
this.notify();
|
||||
},
|
||||
|
||||
_pendingRemoved: function(channel, message) {
|
||||
let idx = this._pendingMessages.indexOf(message);
|
||||
|
||||
if (idx >= 0) {
|
||||
this._pendingMessages.splice(idx, 1);
|
||||
this._updateCount();
|
||||
}
|
||||
else
|
||||
throw new Error('Message not in our pending list: ' + message);
|
||||
},
|
||||
|
||||
_ackMessages: function() {
|
||||
if (this._pendingMessages.length == 0)
|
||||
return;
|
||||
|
||||
// Don't clear our messages here, tp-glib will send a
|
||||
// 'pending-message-removed' for each one.
|
||||
this._channel.ack_messages_async(this._pendingMessages, Lang.bind(this, function(src, result) {
|
||||
this._channel.ack_messages_finish(result);}));
|
||||
},
|
||||
|
||||
_summaryItemClicked: function(source, button) {
|
||||
if (button != 1)
|
||||
return;
|
||||
|
||||
this._shouldAck = true;
|
||||
},
|
||||
|
||||
_notificationExpanded: function() {
|
||||
this._shouldAck = true;
|
||||
},
|
||||
|
||||
_notificationCollapsed: function() {
|
||||
if (this._shouldAck)
|
||||
this._ackMessages();
|
||||
|
||||
this._shouldAck = false;
|
||||
}
|
||||
};
|
||||
|
||||
function Notification(source) {
|
||||
function ChatNotification(source) {
|
||||
this._init(source);
|
||||
}
|
||||
|
||||
Notification.prototype = {
|
||||
ChatNotification.prototype = {
|
||||
__proto__: MessageTray.Notification.prototype,
|
||||
|
||||
_init: function(source) {
|
||||
@ -368,6 +618,7 @@ Notification.prototype = {
|
||||
this._responseEntry = new St.Entry({ style_class: 'chat-response',
|
||||
can_focus: true });
|
||||
this._responseEntry.clutter_text.connect('activate', Lang.bind(this, this._onEntryActivated));
|
||||
this._responseEntry.clutter_text.connect('text-changed', Lang.bind(this, this._onEntryChanged));
|
||||
this.setActionArea(this._responseEntry);
|
||||
|
||||
this._oldMaxScrollAdjustment = 0;
|
||||
@ -384,6 +635,7 @@ Notification.prototype = {
|
||||
|
||||
this._history = [];
|
||||
this._timestampTimeoutId = 0;
|
||||
this._composingTimeoutId = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -411,17 +663,43 @@ Notification.prototype = {
|
||||
styles.push('chat-action');
|
||||
}
|
||||
|
||||
this.update(this.source.title, messageBody, { customContent: true, bannerMarkup: true });
|
||||
if (message.direction == NotificationDirection.RECEIVED) {
|
||||
this.update(this.source.title, messageBody, { customContent: true,
|
||||
bannerMarkup: true });
|
||||
}
|
||||
|
||||
this._append(messageBody, styles, message.timestamp, noTimestamp);
|
||||
},
|
||||
|
||||
_filterMessages: function() {
|
||||
if (this._history.length < 1)
|
||||
return;
|
||||
|
||||
let lastMessageTime = this._history[0].time;
|
||||
let currentTime = (Date.now() / 1000);
|
||||
|
||||
// Keep the scrollback from growing too long. If the most
|
||||
// recent message (before the one we just added) is within
|
||||
// SCROLLBACK_RECENT_TIME, we will keep
|
||||
// SCROLLBACK_RECENT_LENGTH previous messages. Otherwise
|
||||
// we'll keep SCROLLBACK_IDLE_LENGTH messages.
|
||||
|
||||
let maxLength = (lastMessageTime < currentTime - SCROLLBACK_RECENT_TIME) ?
|
||||
SCROLLBACK_IDLE_LENGTH : SCROLLBACK_RECENT_LENGTH;
|
||||
|
||||
let filteredHistory = this._history.filter(function(item) { return item.realMessage });
|
||||
if (filteredHistory.length > maxLength) {
|
||||
let lastMessageToKeep = filteredHistory[maxLength];
|
||||
let expired = this._history.splice(this._history.indexOf(lastMessageToKeep));
|
||||
for (let i = 0; i < expired.length; i++)
|
||||
expired[i].actor.destroy();
|
||||
}
|
||||
},
|
||||
|
||||
_append: function(text, styles, timestamp, noTimestamp) {
|
||||
let currentTime = (Date.now() / 1000);
|
||||
if (!timestamp)
|
||||
timestamp = currentTime;
|
||||
let lastMessageTime = -1;
|
||||
if (this._history.length > 0)
|
||||
lastMessageTime = this._history[0].time;
|
||||
|
||||
// Reset the old message timeout
|
||||
if (this._timestampTimeoutId)
|
||||
@ -444,23 +722,7 @@ Notification.prototype = {
|
||||
Lang.bind(this, this.appendTimestamp));
|
||||
}
|
||||
|
||||
if (this._history.length > 1) {
|
||||
// Keep the scrollback from growing too long. If the most
|
||||
// recent message (before the one we just added) is within
|
||||
// SCROLLBACK_RECENT_TIME, we will keep
|
||||
// SCROLLBACK_RECENT_LENGTH previous messages. Otherwise
|
||||
// we'll keep SCROLLBACK_IDLE_LENGTH messages.
|
||||
|
||||
let maxLength = (lastMessageTime < currentTime - SCROLLBACK_RECENT_TIME) ?
|
||||
SCROLLBACK_IDLE_LENGTH : SCROLLBACK_RECENT_LENGTH;
|
||||
let filteredHistory = this._history.filter(function(item) { return item.realMessage });
|
||||
if (filteredHistory.length > maxLength) {
|
||||
let lastMessageToKeep = filteredHistory[maxLength];
|
||||
let expired = this._history.splice(this._history.indexOf(lastMessageToKeep));
|
||||
for (let i = 0; i < expired.length; i++)
|
||||
expired[i].actor.destroy();
|
||||
}
|
||||
}
|
||||
this._filterMessages();
|
||||
},
|
||||
|
||||
_formatTimestamp: function(date) {
|
||||
@ -502,6 +764,9 @@ Notification.prototype = {
|
||||
this._history.unshift({ actor: timeLabel, time: lastMessageTime, realMessage: false });
|
||||
|
||||
this._timestampTimeoutId = 0;
|
||||
|
||||
this._filterMessages();
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
@ -513,6 +778,8 @@ Notification.prototype = {
|
||||
let label = this.addBody(text, true);
|
||||
label.add_style_class_name('chat-meta-message');
|
||||
this._history.unshift({ actor: label, time: (Date.now() / 1000), realMessage: false});
|
||||
|
||||
this._filterMessages();
|
||||
},
|
||||
|
||||
appendAliasChange: function(oldAlias, newAlias) {
|
||||
@ -526,6 +793,8 @@ Notification.prototype = {
|
||||
label.add_style_class_name('chat-meta-message');
|
||||
this._history.unshift({ actor: label, time: (Date.now() / 1000), realMessage: false });
|
||||
this.update(newAlias, null, { customContent: true });
|
||||
|
||||
this._filterMessages();
|
||||
},
|
||||
|
||||
_onEntryActivated: function() {
|
||||
@ -539,5 +808,166 @@ Notification.prototype = {
|
||||
// see Source._messageSent
|
||||
this._responseEntry.set_text('');
|
||||
this.source.respond(text);
|
||||
},
|
||||
|
||||
_composingStopTimeout: function() {
|
||||
this._composingTimeoutId = 0;
|
||||
|
||||
this.source.setChatState(Tp.ChannelChatState.PAUSED);
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
_onEntryChanged: function() {
|
||||
let text = this._responseEntry.get_text();
|
||||
|
||||
// If we're typing, we want to send COMPOSING.
|
||||
// If we empty the entry, we want to send ACTIVE.
|
||||
// If we've stopped typing for COMPOSING_STOP_TIMEOUT
|
||||
// seconds, we want to send PAUSED.
|
||||
|
||||
// Remove composing timeout.
|
||||
if (this._composingTimeoutId > 0) {
|
||||
Mainloop.source_remove(this._composingTimeoutId);
|
||||
this._composingTimeoutId = 0;
|
||||
}
|
||||
|
||||
if (text != '') {
|
||||
this.source.setChatState(Tp.ChannelChatState.COMPOSING);
|
||||
|
||||
this._composingTimeoutId = Mainloop.timeout_add_seconds(
|
||||
COMPOSING_STOP_TIMEOUT,
|
||||
Lang.bind(this, this._composingStopTimeout));
|
||||
} else {
|
||||
this.source.setChatState(Tp.ChannelChatState.ACTIVE);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function ApproverSource(dispatchOp, text, icon) {
|
||||
this._init(dispatchOp, text, icon);
|
||||
}
|
||||
|
||||
ApproverSource.prototype = {
|
||||
__proto__: MessageTray.Source.prototype,
|
||||
|
||||
_init: function(dispatchOp, text, icon) {
|
||||
MessageTray.Source.prototype._init.call(this, text);
|
||||
|
||||
this._icon = icon;
|
||||
this._setSummaryIcon(this.createNotificationIcon());
|
||||
|
||||
this._dispatchOp = dispatchOp;
|
||||
|
||||
// Destroy the source if the channel dispatch operation is invalidated
|
||||
// as we can't approve any more.
|
||||
this._invalidId = dispatchOp.connect('invalidated',
|
||||
Lang.bind(this, function(domain, code, msg) {
|
||||
this.destroy();
|
||||
}));
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this._invalidId != 0) {
|
||||
this._dispatchOp.disconnect(this._invalidId);
|
||||
this._invalidId = 0;
|
||||
}
|
||||
|
||||
MessageTray.Source.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
createNotificationIcon: function() {
|
||||
return new St.Icon({ icon_name: this._icon,
|
||||
icon_type: St.IconType.FULLCOLOR,
|
||||
icon_size: this.ICON_SIZE });
|
||||
}
|
||||
}
|
||||
|
||||
function RoomInviteNotification(source, dispatchOp, channel, inviter) {
|
||||
this._init(source, dispatchOp, channel, inviter);
|
||||
}
|
||||
|
||||
RoomInviteNotification.prototype = {
|
||||
__proto__: MessageTray.Notification.prototype,
|
||||
|
||||
_init: function(source, dispatchOp, channel, inviter) {
|
||||
MessageTray.Notification.prototype._init.call(this,
|
||||
source,
|
||||
/* translators: argument is a room name like
|
||||
* room@jabber.org for example. */
|
||||
_("Invitation to %s").format(channel.get_identifier()),
|
||||
null,
|
||||
{ customContent: true });
|
||||
this.setResident(true);
|
||||
|
||||
/* translators: first argument is the name of a contact and the second
|
||||
* one the name of a room. "Alice is inviting you to join room@jabber.org
|
||||
* for example. */
|
||||
this.addBody(_("%s is inviting you to join %s").format(inviter.get_alias(), channel.get_identifier()));
|
||||
|
||||
this.addButton('decline', _("Decline"));
|
||||
this.addButton('accept', _("Accept"));
|
||||
|
||||
this.connect('action-invoked', Lang.bind(this, function(self, action) {
|
||||
switch (action) {
|
||||
case 'decline':
|
||||
dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE,
|
||||
'', function(src, result) {
|
||||
src.leave_channels_finish(result)});
|
||||
break;
|
||||
case 'accept':
|
||||
dispatchOp.handle_with_time_async('', global.get_current_time(),
|
||||
function(src, result) {
|
||||
src.handle_with_time_finish(result)});
|
||||
break;
|
||||
}
|
||||
this.destroy();
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
// Audio Video
|
||||
function AudioVideoNotification(source, dispatchOp, channel, contact, isVideo) {
|
||||
this._init(source, dispatchOp, channel, contact, isVideo);
|
||||
}
|
||||
|
||||
AudioVideoNotification.prototype = {
|
||||
__proto__: MessageTray.Notification.prototype,
|
||||
|
||||
_init: function(source, dispatchOp, channel, contact, isVideo) {
|
||||
let title = '';
|
||||
|
||||
if (isVideo)
|
||||
/* translators: argument is a contact name like Alice for example. */
|
||||
title = _("Video call from %s").format(contact.get_alias());
|
||||
else
|
||||
/* translators: argument is a contact name like Alice for example. */
|
||||
title = _("Call from %s").format(contact.get_alias());
|
||||
|
||||
MessageTray.Notification.prototype._init.call(this,
|
||||
source,
|
||||
title,
|
||||
null,
|
||||
{ customContent: true });
|
||||
this.setResident(true);
|
||||
|
||||
this.addButton('reject', _("Reject"));
|
||||
this.addButton('answer', _("Answer"));
|
||||
|
||||
this.connect('action-invoked', Lang.bind(this, function(self, action) {
|
||||
switch (action) {
|
||||
case 'reject':
|
||||
dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE,
|
||||
'', function(src, result) {
|
||||
src.leave_channels_finish(result)});
|
||||
break;
|
||||
case 'answer':
|
||||
dispatchOp.handle_with_time_async('', global.get_current_time(),
|
||||
function(src, result) {
|
||||
src.handle_with_time_finish(result)});
|
||||
break;
|
||||
}
|
||||
this.destroy();
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
@ -141,13 +141,19 @@ SearchTab.prototype = {
|
||||
'edit-find');
|
||||
|
||||
this._text.connect('text-changed', Lang.bind(this, this._onTextChanged));
|
||||
this._text.connect('activate', Lang.bind(this, function (se) {
|
||||
if (this._searchTimeoutId > 0) {
|
||||
Mainloop.source_remove(this._searchTimeoutId);
|
||||
this._doSearch();
|
||||
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 activateSelected.
|
||||
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.activateSelected();
|
||||
return true;
|
||||
}
|
||||
this._searchResults.activateSelected();
|
||||
return true;
|
||||
return false;
|
||||
}));
|
||||
|
||||
this._entry.connect('notify::mapped', Lang.bind(this, this._onMapped));
|
||||
|
@ -6,6 +6,7 @@ const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Meta = imports.gi.Meta;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const AltTab = imports.ui.altTab;
|
||||
const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup;
|
||||
@ -22,8 +23,7 @@ function getDimShader() {
|
||||
if (dimShader === null)
|
||||
return null;
|
||||
if (!dimShader) {
|
||||
let [success, source, length] = GLib.file_get_contents(global.datadir +
|
||||
'/shaders/dim-window.glsl');
|
||||
let source = Shell.get_file_contents_utf8_sync(global.datadir + '/shaders/dim-window.glsl');
|
||||
try {
|
||||
let shader = new Clutter.Shader();
|
||||
shader.set_fragment_source(source, -1);
|
||||
@ -119,6 +119,8 @@ WindowManager.prototype = {
|
||||
this.setKeybindingHandler('switch_to_workspace_down', Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setKeybindingHandler('switch_windows', Lang.bind(this, this._startAppSwitcher));
|
||||
this.setKeybindingHandler('switch_group', Lang.bind(this, this._startAppSwitcher));
|
||||
this.setKeybindingHandler('switch_windows_backward', Lang.bind(this, this._startAppSwitcher));
|
||||
this.setKeybindingHandler('switch_group_backward', Lang.bind(this, this._startAppSwitcher));
|
||||
this.setKeybindingHandler('switch_panels', Lang.bind(this, this._startA11ySwitcher));
|
||||
|
||||
Main.overview.connect('showing', Lang.bind(this, function() {
|
||||
@ -180,7 +182,7 @@ WindowManager.prototype = {
|
||||
*/
|
||||
this._minimizing.push(actor);
|
||||
|
||||
let primary = global.get_primary_monitor();
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
let xDest = primary.x;
|
||||
if (St.Widget.get_default_direction() == St.TextDirection.RTL)
|
||||
xDest += primary.width;
|
||||
@ -534,7 +536,7 @@ WindowManager.prototype = {
|
||||
|
||||
let tabPopup = new AltTab.AltTabPopup();
|
||||
|
||||
if (!tabPopup.show(backwards, binding == 'switch_group'))
|
||||
if (!tabPopup.show(backwards, binding))
|
||||
tabPopup.destroy();
|
||||
},
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GConf = imports.gi.GConf;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
@ -27,6 +28,8 @@ const CLOSE_BUTTON_FADE_TIME = 0.1;
|
||||
|
||||
const DRAGGING_WINDOW_OPACITY = 100;
|
||||
|
||||
const BUTTON_LAYOUT_KEY = '/desktop/gnome/shell/windows/button_layout';
|
||||
|
||||
// Define a layout scheme for small window counts. For larger
|
||||
// counts we fall back to an algorithm. We need more schemes here
|
||||
// unless we have a really good algorithm.
|
||||
@ -222,8 +225,12 @@ WindowClone.prototype = {
|
||||
let [width, height] = this.actor.get_transformed_size();
|
||||
|
||||
let monitorIndex = this.metaWindow.get_monitor();
|
||||
let availArea = global.get_monitors()[monitorIndex];
|
||||
if (monitorIndex == global.get_primary_monitor_index()) {
|
||||
let monitor = Main.layoutManager.monitors[monitorIndex];
|
||||
let availArea = new Meta.Rectangle({ x: monitor.x,
|
||||
y: monitor.y,
|
||||
width: monitor.width,
|
||||
height: monitor.height });
|
||||
if (monitorIndex == Main.layoutManager.primaryIndex) {
|
||||
availArea.y += Main.panel.actor.height;
|
||||
availArea.height -= Main.panel.actor.height;
|
||||
}
|
||||
@ -305,6 +312,21 @@ WindowClone.prototype = {
|
||||
this.emit('drag-begin');
|
||||
},
|
||||
|
||||
_getWorkspaceActor : function() {
|
||||
let index = this.metaWindow.get_workspace().index();
|
||||
return Main.overview.workspaces.getWorkspaceByIndex(index);
|
||||
},
|
||||
|
||||
handleDragOver : function(source, actor, x, y, time) {
|
||||
let workspace = this._getWorkspaceActor();
|
||||
return workspace.handleDragOver(source, actor, x, y, time);
|
||||
},
|
||||
|
||||
acceptDrop : function(source, actor, x, y, time) {
|
||||
let workspace = this._getWorkspaceActor();
|
||||
workspace.acceptDrop(source, actor, x, y, time);
|
||||
},
|
||||
|
||||
_onDragCancelled : function (draggable, time) {
|
||||
this.emit('drag-cancelled');
|
||||
},
|
||||
@ -435,9 +457,20 @@ WindowOverlay.prototype = {
|
||||
let button = this.closeButton;
|
||||
let title = this.title;
|
||||
|
||||
let gconf = GConf.Client.get_default();
|
||||
let layout = gconf.get_string(BUTTON_LAYOUT_KEY);
|
||||
let rtl = St.Widget.get_default_direction() == St.TextDirection.RTL;
|
||||
|
||||
let split = layout.split(":");
|
||||
let side;
|
||||
if (split[0].indexOf("close") > -1)
|
||||
side = rtl ? St.Side.RIGHT : St.Side.LEFT;
|
||||
else
|
||||
side = rtl ? St.Side.LEFT : St.Side.RIGHT;
|
||||
|
||||
let buttonX;
|
||||
let buttonY = cloneY - (button.height - button._overlap);
|
||||
if (St.Widget.get_default_direction() == St.TextDirection.RTL)
|
||||
if (side == St.Side.LEFT)
|
||||
buttonX = cloneX - (button.width - button._overlap);
|
||||
else
|
||||
buttonX = cloneX + (cloneWidth - button._overlap);
|
||||
@ -564,7 +597,7 @@ Workspace.prototype = {
|
||||
this._height = 0;
|
||||
|
||||
this.monitorIndex = monitorIndex;
|
||||
this._monitor = global.get_monitors()[this.monitorIndex];
|
||||
this._monitor = Main.layoutManager.monitors[this.monitorIndex];
|
||||
this._windowOverlaysGroup = new Clutter.Group();
|
||||
// Without this the drop area will be overlapped.
|
||||
this._windowOverlaysGroup.set_size(0, 0);
|
||||
@ -619,6 +652,7 @@ Workspace.prototype = {
|
||||
function () {
|
||||
this._dropRect.set_position(x, y);
|
||||
this._dropRect.set_size(width, height);
|
||||
this.positionWindows(WindowPositionFlags.ANIMATE);
|
||||
return false;
|
||||
}));
|
||||
|
||||
@ -641,16 +675,6 @@ Workspace.prototype = {
|
||||
return this._windows.length == 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* setReactive:
|
||||
* @reactive: %true iff the workspace should be reactive
|
||||
*
|
||||
* Set the workspace (desktop) reactive
|
||||
**/
|
||||
setReactive: function(reactive) {
|
||||
this.actor.reactive = reactive;
|
||||
},
|
||||
|
||||
// Only use this for n <= 20 say
|
||||
_factorial: function(n) {
|
||||
let result = 1;
|
||||
|
@ -56,7 +56,7 @@ WorkspaceSwitcherPopup.prototype = {
|
||||
|
||||
_getPreferredHeight : function (actor, forWidth, alloc) {
|
||||
let children = this._list.get_children();
|
||||
let primary = global.get_primary_monitor();
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
|
||||
let availHeight = primary.height;
|
||||
availHeight -= Main.panel.actor.height;
|
||||
@ -82,7 +82,7 @@ WorkspaceSwitcherPopup.prototype = {
|
||||
},
|
||||
|
||||
_getPreferredWidth : function (actor, forHeight, alloc) {
|
||||
let primary = global.get_primary_monitor();
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
this._childWidth = Math.round(this._childHeight * primary.width / primary.height);
|
||||
|
||||
alloc.min_size = this._childWidth;
|
||||
@ -125,7 +125,7 @@ WorkspaceSwitcherPopup.prototype = {
|
||||
},
|
||||
|
||||
_position: function() {
|
||||
let primary = global.get_primary_monitor();
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
this._container.x = primary.x + Math.floor((primary.width - this._container.width) / 2);
|
||||
this._container.y = primary.y + Main.panel.actor.height +
|
||||
Math.floor(((primary.height - Main.panel.actor.height) - this._container.height) / 2);
|
||||
|
@ -146,7 +146,7 @@ function WorkspaceThumbnail(metaWorkspace) {
|
||||
WorkspaceThumbnail.prototype = {
|
||||
_init : function(metaWorkspace) {
|
||||
this.metaWorkspace = metaWorkspace;
|
||||
this.monitorIndex = global.get_primary_monitor_index();
|
||||
this.monitorIndex = Main.layoutManager.primaryIndex;
|
||||
|
||||
this.actor = new St.Group({ reactive: true,
|
||||
clip_to_allocation: true,
|
||||
@ -170,7 +170,7 @@ WorkspaceThumbnail.prototype = {
|
||||
this._background = new Clutter.Clone({ source: global.background_actor });
|
||||
this._contents.add_actor(this._background);
|
||||
|
||||
let monitor = global.get_primary_monitor();
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
this.setPorthole(monitor.x, monitor.y, monitor.width, monitor.height);
|
||||
|
||||
let windows = global.get_window_actors().filter(this._isMyWindow, this);
|
||||
@ -178,6 +178,11 @@ WorkspaceThumbnail.prototype = {
|
||||
// Create clones for windows that should be visible in the Overview
|
||||
this._windows = [];
|
||||
for (let i = 0; i < windows.length; i++) {
|
||||
windows[i].meta_window._minimizedChangedId =
|
||||
windows[i].meta_window.connect('notify::minimized',
|
||||
Lang.bind(this,
|
||||
this._updateMinimized));
|
||||
|
||||
if (this._isOverviewWindow(windows[i])) {
|
||||
this._addWindowClone(windows[i]);
|
||||
}
|
||||
@ -257,11 +262,18 @@ WorkspaceThumbnail.prototype = {
|
||||
return;
|
||||
|
||||
// Check if window still should be here
|
||||
if (win && this._isMyWindow(win))
|
||||
if (win && this._isMyWindow(win) && this._isOverviewWindow(win))
|
||||
return;
|
||||
|
||||
let clone = this._windows[index];
|
||||
this._windows.splice(index, 1);
|
||||
|
||||
if (win && this._isOverviewWindow(win)) {
|
||||
if (metaWin._minimizedChangedId) {
|
||||
metaWin.disconnect(metaWin._minimizedChangedId);
|
||||
delete metaWin._minimizedChangedId;
|
||||
}
|
||||
}
|
||||
clone.destroy();
|
||||
},
|
||||
|
||||
@ -290,6 +302,11 @@ WorkspaceThumbnail.prototype = {
|
||||
if (this._lookupIndex (metaWin) != -1)
|
||||
return;
|
||||
|
||||
if (!metaWin._minimizedChangedId)
|
||||
metaWin._minimizedChangedId = metaWin.connect('notify::minimized',
|
||||
Lang.bind(this,
|
||||
this._updateMinimized));
|
||||
|
||||
if (!this._isMyWindow(win) || !this._isOverviewWindow(win))
|
||||
return;
|
||||
|
||||
@ -316,6 +333,13 @@ WorkspaceThumbnail.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
_updateMinimized: function(metaWin) {
|
||||
if (metaWin.minimized)
|
||||
this._doRemoveWindow(metaWin);
|
||||
else
|
||||
this._doAddWindow(metaWin);
|
||||
},
|
||||
|
||||
destroy : function() {
|
||||
this.actor.destroy();
|
||||
},
|
||||
@ -326,6 +350,14 @@ WorkspaceThumbnail.prototype = {
|
||||
global.screen.disconnect(this._windowEnteredMonitorId);
|
||||
global.screen.disconnect(this._windowLeftMonitorId);
|
||||
|
||||
for (let i = 0; i < this._windows.length; i++) {
|
||||
let metaWin = this._windows[i].metaWindow;
|
||||
if (metaWin._minimizedChangedId) {
|
||||
metaWin.disconnect(metaWin._minimizedChangedId);
|
||||
delete metaWin._minimizedChangedId;
|
||||
}
|
||||
}
|
||||
|
||||
this._windows = [];
|
||||
this.actor = null;
|
||||
},
|
||||
@ -339,7 +371,8 @@ WorkspaceThumbnail.prototype = {
|
||||
// Tests if @win should be shown in the Overview
|
||||
_isOverviewWindow : function (win) {
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
return tracker.is_window_interesting(win.get_meta_window());
|
||||
return tracker.is_window_interesting(win.get_meta_window()) &&
|
||||
win.get_meta_window().showing_on_its_workspace();
|
||||
},
|
||||
|
||||
// Create a clone of a (non-desktop) window and add it to the window list
|
||||
@ -495,7 +528,7 @@ ThumbnailsBox.prototype = {
|
||||
|
||||
// The "porthole" is the portion of the screen that we show in the workspaces
|
||||
let panelHeight = Main.panel.actor.height;
|
||||
let monitor = global.get_primary_monitor();
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
this._porthole = {
|
||||
x: monitor.x,
|
||||
y: monitor.y + panelHeight,
|
||||
|
@ -49,9 +49,12 @@ WorkspacesView.prototype = {
|
||||
this._height = 0;
|
||||
this._x = 0;
|
||||
this._y = 0;
|
||||
this._clipX = 0;
|
||||
this._clipY = 0;
|
||||
this._clipWidth = 0;
|
||||
this._clipHeight = 0;
|
||||
this._workspaceRatioSpacing = 0;
|
||||
this._spacing = 0;
|
||||
this._lostWorkspaces = [];
|
||||
this._animating = false; // tweening
|
||||
this._scrolling = false; // swipe-scrolling
|
||||
this._animatingScroll = false; // programatically updating the adjustment
|
||||
@ -67,10 +70,10 @@ WorkspacesView.prototype = {
|
||||
this._workspaces[activeWorkspaceIndex].actor.raise_top();
|
||||
|
||||
this._extraWorkspaces = [];
|
||||
let monitors = global.get_monitors();
|
||||
let monitors = Main.layoutManager.monitors;
|
||||
let m = 0;
|
||||
for (let i = 0; i < monitors.length; i++) {
|
||||
if (i == global.get_primary_monitor_index())
|
||||
if (i == Main.layoutManager.primaryIndex)
|
||||
continue;
|
||||
let ws = new Workspace.Workspace(null, i);
|
||||
this._extraWorkspaces[m++] = ws;
|
||||
@ -93,7 +96,8 @@ WorkspacesView.prototype = {
|
||||
this._overviewShownId =
|
||||
Main.overview.connect('shown',
|
||||
Lang.bind(this, function() {
|
||||
this.actor.set_clip(this._x, this._y, this._width, this._height);
|
||||
this.actor.set_clip(this._clipX, this._clipY,
|
||||
this._clipWidth, this._clipHeight);
|
||||
}));
|
||||
|
||||
this._scrollAdjustment = new St.Adjustment({ value: activeWorkspaceIndex,
|
||||
@ -137,6 +141,13 @@ WorkspacesView.prototype = {
|
||||
this._workspaces[i].setGeometry(x, y, width, height);
|
||||
},
|
||||
|
||||
setClipRect: function(x, y, width, height) {
|
||||
this._clipX = x;
|
||||
this._clipY = y;
|
||||
this._clipWidth = width;
|
||||
this._clipHeight = height;
|
||||
},
|
||||
|
||||
_lookupWorkspaceForMetaWindow: function (metaWindow) {
|
||||
for (let i = 0; i < this._workspaces.length; i++) {
|
||||
if (this._workspaces[i].containsMetaWindow(metaWindow))
|
||||
@ -150,6 +161,10 @@ WorkspacesView.prototype = {
|
||||
return this._workspaces[active];
|
||||
},
|
||||
|
||||
getWorkspaceByIndex: function(index) {
|
||||
return this._workspaces[index];
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
|
||||
let activeWorkspace = this._workspaces[activeWorkspaceIndex];
|
||||
@ -224,27 +239,6 @@ WorkspacesView.prototype = {
|
||||
this._updateVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
for (let l = 0; l < this._lostWorkspaces.length; l++) {
|
||||
let workspace = this._lostWorkspaces[l];
|
||||
|
||||
Tweener.removeTweens(workspace.actor);
|
||||
|
||||
workspace.actor.show();
|
||||
workspace.hideWindowsOverlays();
|
||||
|
||||
if (showAnimation) {
|
||||
Tweener.addTween(workspace.actor,
|
||||
{ y: workspace.x,
|
||||
time: WORKSPACE_SWITCH_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this,
|
||||
this._cleanWorkspaces)
|
||||
});
|
||||
} else {
|
||||
this._cleanWorkspaces();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_updateVisibility: function() {
|
||||
@ -265,17 +259,6 @@ WorkspacesView.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
_cleanWorkspaces: function() {
|
||||
if (this._lostWorkspaces.length == 0)
|
||||
return;
|
||||
|
||||
for (let l = 0; l < this._lostWorkspaces.length; l++)
|
||||
this._lostWorkspaces[l].destroy();
|
||||
this._lostWorkspaces = [];
|
||||
|
||||
this._updateWorkspaceActors(false);
|
||||
},
|
||||
|
||||
_updateScrollAdjustment: function(index, showAnimation) {
|
||||
if (this._scrolling)
|
||||
return;
|
||||
@ -298,12 +281,9 @@ WorkspacesView.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
updateWorkspaces: function(oldNumWorkspaces, newNumWorkspaces, lostWorkspaces) {
|
||||
updateWorkspaces: function(oldNumWorkspaces, newNumWorkspaces) {
|
||||
let active = global.screen.get_active_workspace_index();
|
||||
|
||||
for (let l = 0; l < lostWorkspaces.length; l++)
|
||||
lostWorkspaces[l].disconnectAll();
|
||||
|
||||
Tweener.addTween(this._scrollAdjustment,
|
||||
{ upper: newNumWorkspaces,
|
||||
time: WORKSPACE_SWITCH_TIME,
|
||||
@ -311,12 +291,13 @@ WorkspacesView.prototype = {
|
||||
});
|
||||
|
||||
if (newNumWorkspaces > oldNumWorkspaces) {
|
||||
for (let w = oldNumWorkspaces; w < newNumWorkspaces; w++)
|
||||
for (let w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
|
||||
this._workspaces[w].setGeometry(this._x, this._y,
|
||||
this._width, this._height);
|
||||
this.actor.add_actor(this._workspaces[w].actor);
|
||||
}
|
||||
|
||||
this._updateWorkspaceActors(false);
|
||||
} else {
|
||||
this._lostWorkspaces = lostWorkspaces;
|
||||
}
|
||||
|
||||
this._scrollToActive(true);
|
||||
@ -402,7 +383,7 @@ WorkspacesView.prototype = {
|
||||
this._extraWorkspaces[i].setReservedSlot(dragEvent.dragActor._delegate);
|
||||
}
|
||||
|
||||
let primary = global.get_primary_monitor();
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
|
||||
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
|
||||
let topWorkspace, bottomWorkspace;
|
||||
@ -468,7 +449,7 @@ WorkspacesView.prototype = {
|
||||
Mainloop.source_remove(this._timeoutId);
|
||||
this._timeoutId = 0;
|
||||
}
|
||||
DND.removeMonitor(this._dragMonitor);
|
||||
DND.removeDragMonitor(this._dragMonitor);
|
||||
this._inDrag = false;
|
||||
|
||||
for (let i = 0; i < this._workspaces.length; i++)
|
||||
@ -569,8 +550,8 @@ WorkspacesDisplay.prototype = {
|
||||
controls.connect('scroll-event',
|
||||
Lang.bind(this, this._onScrollEvent));
|
||||
|
||||
this._monitorIndex = global.get_primary_monitor_index();
|
||||
this._monitor = global.get_monitors()[this._monitorIndex];
|
||||
this._monitorIndex = Main.layoutManager.primaryIndex;
|
||||
this._monitor = Main.layoutManager.monitors[this._monitorIndex];
|
||||
|
||||
this._thumbnailsBox = new WorkspaceThumbnail.ThumbnailsBox();
|
||||
controls.add_actor(this._thumbnailsBox.actor);
|
||||
@ -586,7 +567,10 @@ WorkspacesDisplay.prototype = {
|
||||
|
||||
this._updateAlwaysZoom();
|
||||
|
||||
global.screen.connect('monitors-changed', Lang.bind(this, this._updateAlwaysZoom));
|
||||
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._updateAlwaysZoom));
|
||||
global.screen.connect('notify::n-workspaces',
|
||||
Lang.bind(this, this._workspacesChanged));
|
||||
|
||||
Main.xdndHandler.connect('drag-begin', Lang.bind(this, function(){
|
||||
this._alwaysZoomOut = true;
|
||||
}));
|
||||
@ -596,7 +580,6 @@ WorkspacesDisplay.prototype = {
|
||||
this._updateAlwaysZoom();
|
||||
}));
|
||||
|
||||
this._nWorkspacesNotifyId = 0;
|
||||
this._switchWorkspaceNotifyId = 0;
|
||||
|
||||
this._itemDragBeginId = 0;
|
||||
@ -626,10 +609,6 @@ WorkspacesDisplay.prototype = {
|
||||
this.workspacesView = new WorkspacesView(this._workspaces);
|
||||
this._updateWorkspacesGeometry();
|
||||
|
||||
this._nWorkspacesNotifyId =
|
||||
global.screen.connect('notify::n-workspaces',
|
||||
Lang.bind(this, this._workspacesChanged));
|
||||
|
||||
this._restackedNotifyId =
|
||||
global.screen.connect('restacked',
|
||||
Lang.bind(this, this._onRestacked));
|
||||
@ -660,10 +639,6 @@ WorkspacesDisplay.prototype = {
|
||||
this._controls.hide();
|
||||
this._thumbnailsBox.hide();
|
||||
|
||||
if (this._nWorkspacesNotifyId > 0) {
|
||||
global.screen.disconnect(this._nWorkspacesNotifyId);
|
||||
this._nWorkspacesNotifyId = 0;
|
||||
}
|
||||
if (this._restackedNotifyId > 0){
|
||||
global.screen.disconnect(this._restackedNotifyId);
|
||||
this._restackedNotifyId = 0;
|
||||
@ -712,10 +687,15 @@ WorkspacesDisplay.prototype = {
|
||||
},
|
||||
|
||||
_updateAlwaysZoom: function() {
|
||||
this._alwaysZoomOut = false;
|
||||
// Always show the pager if workspaces are actually used,
|
||||
// e.g. there are windows on more than one
|
||||
this._alwaysZoomOut = global.screen.n_workspaces > 2;
|
||||
|
||||
let monitors = global.get_monitors();
|
||||
let primary = global.get_primary_monitor();
|
||||
if (this._alwaysZoomOut)
|
||||
return;
|
||||
|
||||
let monitors = Main.layoutManager.monitors;
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
|
||||
/* Look for any monitor to the right of the primary, if there is
|
||||
* one, we always keep zoom out, otherwise its hard to reach
|
||||
@ -783,6 +763,13 @@ WorkspacesDisplay.prototype = {
|
||||
|
||||
let rtl = (St.Widget.get_default_direction () == St.TextDirection.RTL);
|
||||
|
||||
let clipWidth = width - controlsVisible;
|
||||
let clipHeight = (fullHeight / fullWidth) * clipWidth;
|
||||
let clipX = rtl ? x + controlsVisible : x;
|
||||
let clipY = y + (fullHeight - clipHeight) / 2;
|
||||
|
||||
this.workspacesView.setClipRect(clipX, clipY, clipWidth, clipHeight);
|
||||
|
||||
if (this._zoomOut) {
|
||||
width -= controlsNatural;
|
||||
if (rtl)
|
||||
@ -821,6 +808,12 @@ WorkspacesDisplay.prototype = {
|
||||
if (oldNumWorkspaces == newNumWorkspaces)
|
||||
return;
|
||||
|
||||
this._updateAlwaysZoom();
|
||||
this._updateZoom();
|
||||
|
||||
if (this.workspacesView == null)
|
||||
return;
|
||||
|
||||
let lostWorkspaces = [];
|
||||
if (newNumWorkspaces > oldNumWorkspaces) {
|
||||
// Assume workspaces are only added at the end
|
||||
@ -846,24 +839,23 @@ WorkspacesDisplay.prototype = {
|
||||
lostWorkspaces = this._workspaces.splice(removedIndex,
|
||||
removedNum);
|
||||
|
||||
// Don't let the user try to select this workspace as it's
|
||||
// making its exit.
|
||||
for (let l = 0; l < lostWorkspaces.length; l++)
|
||||
lostWorkspaces[l].setReactive(false);
|
||||
for (let l = 0; l < lostWorkspaces.length; l++) {
|
||||
lostWorkspaces[l].disconnectAll();
|
||||
lostWorkspaces[l].destroy();
|
||||
}
|
||||
|
||||
this._thumbnailsBox.removeThumbmails(removedIndex, removedNum);
|
||||
}
|
||||
|
||||
this.workspacesView.updateWorkspaces(oldNumWorkspaces,
|
||||
newNumWorkspaces,
|
||||
lostWorkspaces);
|
||||
newNumWorkspaces);
|
||||
},
|
||||
|
||||
_updateZoom : function() {
|
||||
if (Main.overview.animationInProgress)
|
||||
return;
|
||||
|
||||
let shouldZoom = this._alwaysZoomOut || this._controls.hover || (this._inDrag && !this._cancelledDrag);
|
||||
let shouldZoom = this._alwaysZoomOut || this._controls.hover;
|
||||
if (shouldZoom != this._zoomOut) {
|
||||
this._zoomOut = shouldZoom;
|
||||
this._updateWorkspacesGeometry();
|
||||
@ -887,12 +879,22 @@ WorkspacesDisplay.prototype = {
|
||||
_dragBegin: function() {
|
||||
this._inDrag = true;
|
||||
this._cancelledDrag = false;
|
||||
this._updateZoom();
|
||||
this._dragMonitor = {
|
||||
dragMotion: Lang.bind(this, this._onDragMotion)
|
||||
};
|
||||
DND.addDragMonitor(this._dragMonitor);
|
||||
},
|
||||
|
||||
_dragCancelled: function() {
|
||||
this._cancelledDrag = true;
|
||||
this._updateZoom();
|
||||
DND.removeDragMonitor(this._dragMonitor);
|
||||
},
|
||||
|
||||
_onDragMotion: function(dragEvent) {
|
||||
let controlsHovered = this._controls.contains(dragEvent.targetActor);
|
||||
this._controls.set_hover(controlsHovered);
|
||||
|
||||
return DND.DragMotionResult.CONTINUE;
|
||||
},
|
||||
|
||||
_dragEnd: function() {
|
||||
|
@ -36,53 +36,48 @@ visually attractive and easy to use experience.
|
||||
.SH OPTIONS
|
||||
|
||||
.TP
|
||||
.B \-r, \-\-replace
|
||||
Replace the running metacity/gnome-panel
|
||||
.B \-\-replace
|
||||
Replace the running window manager
|
||||
.br
|
||||
|
||||
.TP
|
||||
.B \-v, \-\-verbose
|
||||
Shows details about the results of running `gnome-shell'.
|
||||
.B \-\-sm-disable
|
||||
Disable connection to the session manager
|
||||
.br
|
||||
|
||||
.TP
|
||||
.B \-g, \-\-debug
|
||||
Run under a debugger
|
||||
.B \-\-sm-client-id=ID
|
||||
Specify session management ID
|
||||
.br
|
||||
|
||||
.TP
|
||||
.B \-\-debug\-command
|
||||
Command to use for debugging (defaults to 'gdb \-\-args')
|
||||
.B \-\-sm-save-file=FILE
|
||||
Initialize session from savefile
|
||||
.br
|
||||
|
||||
.TP
|
||||
.B \-\-screen=SCREEN
|
||||
X screen to use
|
||||
.br
|
||||
|
||||
.TP
|
||||
.B \-d, \-\-display=DISPLAY
|
||||
X display to use
|
||||
.br
|
||||
|
||||
.TP
|
||||
.B \-\-sync
|
||||
.br
|
||||
Make X calls synchronously, useful when debugging down X errors
|
||||
Make X calls synchronous
|
||||
.br
|
||||
|
||||
.TP
|
||||
.B \-\-xephyr
|
||||
Run a debugging instance inside Xephyr
|
||||
.B \-\-version
|
||||
Print version and exit
|
||||
.br
|
||||
|
||||
.TP
|
||||
.B \-\-geometry
|
||||
Specify Xephyr screen geometry
|
||||
.br
|
||||
|
||||
.TP
|
||||
.B \-w, \-\-wide
|
||||
Use widescreen (1280x800) with Xephyr
|
||||
.br
|
||||
|
||||
.TP
|
||||
.B \-\-create\-extension
|
||||
Create a new GNOME Shell extension
|
||||
|
||||
.TP
|
||||
.B \-\-eval\-file
|
||||
Evaluate the contents of the given JavaScript file
|
||||
.B \-\-help
|
||||
Display help and exit
|
||||
.br
|
||||
|
||||
.SH BUGS
|
||||
|
@ -1,10 +1,12 @@
|
||||
af
|
||||
an
|
||||
ar
|
||||
be
|
||||
bg
|
||||
bn
|
||||
bn_IN
|
||||
ca
|
||||
ca@valencia
|
||||
cs
|
||||
da
|
||||
de
|
||||
@ -46,6 +48,7 @@ sr
|
||||
sr@latin
|
||||
sv
|
||||
ta
|
||||
te
|
||||
th
|
||||
tr
|
||||
ug
|
||||
|
1426
po/ca@valencia.po
Normal file
1426
po/ca@valencia.po
Normal file
File diff suppressed because it is too large
Load Diff
4
po/vi.po
4
po/vi.po
@ -9,7 +9,7 @@ msgstr ""
|
||||
"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-"
|
||||
"shell&keywords=I18N+L10N&component=general\n"
|
||||
"POT-Creation-Date: 2011-03-25 20:20+0000\n"
|
||||
"PO-Revision-Date: 2011-04-12 18:34+0700\n"
|
||||
"PO-Revision-Date: 2011-05-22 21:50+0700\n"
|
||||
"Last-Translator: Duy <pclouds@gmail.com>\n"
|
||||
"Language-Team: Vietnamese <vi-VN@googlegroups.com>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@ -551,7 +551,7 @@ msgstr "Ứng dụng"
|
||||
#. the left of the overview
|
||||
#: ../js/ui/overview.js:205
|
||||
msgid "Dash"
|
||||
msgstr "Khung ứng dụng"
|
||||
msgstr "Neo ứng dụng"
|
||||
|
||||
#. TODO - _quit() doesn't really work on apps in state STARTING yet
|
||||
#: ../js/ui/panel.js:515
|
||||
|
542
po/zh_CN.po
542
po/zh_CN.po
File diff suppressed because it is too large
Load Diff
@ -1,13 +1,4 @@
|
||||
|
||||
servicedir = $(datadir)/dbus-1/services
|
||||
service_in_files = calendar-server/org.gnome.Shell.CalendarServer.service.in
|
||||
service_DATA = $(service_in_files:.service.in=.service)
|
||||
|
||||
$(service_DATA): $(service_in_files) Makefile
|
||||
$(AM_V_GEN) \
|
||||
[ -d $(@D) ] || $(mkdir_p) $(@D) ; \
|
||||
sed -e "s|\@libexecdir\@|$(libexecdir)|" $< > $@.tmp && mv $@.tmp $@
|
||||
CLEANFILES += $(service_DATA)
|
||||
service_in_files += calendar-server/org.gnome.Shell.CalendarServer.service.in
|
||||
|
||||
libexec_PROGRAMS += gnome-shell-calendar-server
|
||||
|
||||
|
24
src/Makefile-hotplug-sniffer.am
Normal file
24
src/Makefile-hotplug-sniffer.am
Normal file
@ -0,0 +1,24 @@
|
||||
service_in_files += hotplug-sniffer/org.gnome.Shell.HotplugSniffer.service.in
|
||||
|
||||
libexec_PROGRAMS += gnome-shell-hotplug-sniffer
|
||||
|
||||
gnome_shell_hotplug_sniffer_SOURCES = \
|
||||
hotplug-sniffer/hotplug-mimetypes.h \
|
||||
hotplug-sniffer/shell-mime-sniffer.h \
|
||||
hotplug-sniffer/shell-mime-sniffer.c \
|
||||
hotplug-sniffer/hotplug-sniffer.c \
|
||||
$(NULL)
|
||||
|
||||
gnome_shell_hotplug_sniffer_CFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-DG_DISABLE_DEPRECATED \
|
||||
$(SHELL_HOTPLUG_SNIFFER_CFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
gnome_shell_hotplug_sniffer_LDFLAGS = \
|
||||
$(SHELL_HOTPLUG_SNIFFER_LIBS) \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_DIST += \
|
||||
hotplug-sniffer/org.gnome.Shell.HotplugSniffer.service.in \
|
||||
$(NULL)
|
@ -47,20 +47,20 @@ st-marshal.c: Makefile st/st-marshal.list
|
||||
|
||||
st-enum-types.h: stamp-st-enum-types.h Makefile
|
||||
@true
|
||||
stamp-st-enum-types.h: $(source_h) st/st-enum-types.h.in $(st_source_h)
|
||||
$(AM_V_GEN) ( cd $(srcdir) && \
|
||||
stamp-st-enum-types.h: $(source_h) $(srcdir)/st/st-enum-types.h.in $(st_source_h)
|
||||
$(AM_V_GEN) ( \
|
||||
$(GLIB_MKENUMS) \
|
||||
--template st/st-enum-types.h.in \
|
||||
$(st_source_h) ) >> $@.tmp && \
|
||||
--template $(srcdir)/st/st-enum-types.h.in \
|
||||
$(addprefix $(srcdir)/, $(st_source_h)) ) >> $@.tmp && \
|
||||
(cmp -s $@.tmp st-enum-types.h || cp $@.tmp st-enum-types.h) && \
|
||||
rm -f $@.tmp && \
|
||||
echo timestamp > $(@F)
|
||||
|
||||
st-enum-types.c: stamp-st-enum-types.h st/st-enum-types.c.in
|
||||
$(AM_V_GEN) ( cd $(srcdir) && \
|
||||
st-enum-types.c: stamp-st-enum-types.h $(srcdir)/st/st-enum-types.c.in
|
||||
$(AM_V_GEN) ( \
|
||||
$(GLIB_MKENUMS) \
|
||||
--template st/st-enum-types.c.in \
|
||||
$(st_source_h) ) >> $@.tmp && \
|
||||
--template $(srcdir)/st/st-enum-types.c.in \
|
||||
$(addprefix $(srcdir)/,$(st_source_h)) ) >> $@.tmp && \
|
||||
cp $@.tmp $@ && \
|
||||
rm -f $@.tmp
|
||||
|
||||
|
@ -6,6 +6,7 @@ bin_SCRIPTS =
|
||||
libexec_PROGRAMS =
|
||||
noinst_LTLIBRARIES =
|
||||
noinst_PROGRAMS =
|
||||
service_in_files =
|
||||
|
||||
-include $(INTROSPECTION_MAKEFILE)
|
||||
INTROSPECTION_GIRS =
|
||||
@ -15,9 +16,19 @@ INTROSPECTION_COMPILER_ARGS = --includedir=$(srcdir) --includedir=$(MUTTER_TYPEL
|
||||
typelibdir = $(pkglibdir)
|
||||
typelib_DATA = $(INTROSPECTION_GIRS:.gir=.typelib)
|
||||
|
||||
servicedir = $(datadir)/dbus-1/services
|
||||
service_DATA = $(service_in_files:.service.in=.service)
|
||||
|
||||
$(service_DATA): $(service_in_files) Makefile
|
||||
$(AM_V_GEN) \
|
||||
[ -d $(@D) ] || $(mkdir_p) $(@D) ; \
|
||||
sed -e "s|\@libexecdir\@|$(libexecdir)|" $< > $@.tmp && mv $@.tmp $@
|
||||
CLEANFILES += $(service_DATA)
|
||||
|
||||
CLEANFILES += $(gir_DATA) $(typelib_DATA)
|
||||
|
||||
bin_SCRIPTS += gnome-shell-extension-tool
|
||||
EXTRA_DIST += gnome-shell-extension-tool.in
|
||||
bin_PROGRAMS = gnome-shell-real
|
||||
|
||||
if USE_JHBUILD_WRAPPER_SCRIPT
|
||||
@ -26,6 +37,7 @@ bin_SCRIPTS += gnome-shell-jhbuild
|
||||
else
|
||||
gnome_shell = gnome-shell-real
|
||||
endif
|
||||
EXTRA_DIST += gnome-shell-jhbuild.in
|
||||
|
||||
noinst_DATA = gnome-shell
|
||||
gnome-shell: $(gnome_shell) Makefile
|
||||
@ -55,13 +67,13 @@ gnome-shell-extension-tool: gnome-shell-extension-tool.in Makefile
|
||||
$(AM_V_GEN) sed $(generated_script_substitutions) $< > $@.tmp && mv $@.tmp $@ && chmod a+x $@
|
||||
|
||||
CLEANFILES += gnome-shell $(bin_SCRIPTS)
|
||||
EXTRA_DIST += $(bin_SCRIPTS:=.in)
|
||||
|
||||
include Makefile-gdmuser.am
|
||||
include Makefile-st.am
|
||||
include Makefile-tray.am
|
||||
include Makefile-gvc.am
|
||||
include Makefile-calendar-server.am
|
||||
include Makefile-hotplug-sniffer.am
|
||||
|
||||
gnome_shell_cflags = \
|
||||
$(GNOME_SHELL_CFLAGS) \
|
||||
@ -97,6 +109,7 @@ shell_public_headers_h = \
|
||||
shell-gtk-embed.h \
|
||||
shell-global.h \
|
||||
shell-mobile-providers.h \
|
||||
shell-mount-operation.h \
|
||||
shell-perf-log.h \
|
||||
shell-slicer.h \
|
||||
shell-stack.h \
|
||||
@ -130,6 +143,7 @@ libgnome_shell_la_SOURCES = \
|
||||
shell-gtk-embed.c \
|
||||
shell-global.c \
|
||||
shell-mobile-providers.c \
|
||||
shell-mount-operation.c \
|
||||
shell-perf-log.c \
|
||||
shell-polkit-authentication-agent.h \
|
||||
shell-polkit-authentication-agent.c \
|
||||
@ -222,21 +236,21 @@ shell-marshal.c: Makefile shell-marshal.list
|
||||
shell-enum-types.h: stamp-shell-enum-types.h Makefile
|
||||
@true
|
||||
stamp-shell-enum-types.h: $(srcdir)/shell-enum-types.h.in $(shell_public_headers_h)
|
||||
$(AM_V_GEN) ( cd $(srcdir) && \
|
||||
$(AM_V_GEN) ( \
|
||||
$(GLIB_MKENUMS) \
|
||||
--template $< \
|
||||
$(shell_public_headers_h) ) > $(@F).tmp && \
|
||||
$(addprefix $(srcdir)/,$(shell_public_headers_h)) ) > $(@F).tmp && \
|
||||
(cmp -s $(@F).tmp shell-enum-types.h || mv $(@F).tmp shell-enum-types.h) && \
|
||||
rm -f $(@F).tmp && \
|
||||
echo timestamp > $(@F)
|
||||
EXTRA_DIST += shell-enum-types.h.in
|
||||
CLEANFILES += stamp-shell-enum-types.h
|
||||
|
||||
shell-enum-types.c: shell-enum-types.c.in stamp-shell-enum-types.h
|
||||
$(AM_V_GEN) ( cd $(srcdir) && \
|
||||
shell-enum-types.c: $(srcdir)/shell-enum-types.c.in stamp-shell-enum-types.h
|
||||
$(AM_V_GEN) ( \
|
||||
$(GLIB_MKENUMS) \
|
||||
--template $< \
|
||||
$(shell_public_headers_h) ) > $(@F).tmp && \
|
||||
$(addprefix $(srcdir)/,$(shell_public_headers_h)) ) > $(@F).tmp && \
|
||||
mv $(@F).tmp $(@F) && \
|
||||
rm -f $(@F).tmp
|
||||
EXTRA_DIST += shell-enum-types.c.in
|
||||
@ -255,10 +269,10 @@ libgnome_shell_la_LIBADD = \
|
||||
libgnome_shell_la_CPPFLAGS = $(gnome_shell_cflags)
|
||||
|
||||
Shell-0.1.gir: libgnome-shell.la St-1.0.gir
|
||||
Shell_0_1_gir_INCLUDES = Clutter-1.0 ClutterX11-1.0 Meta-3.0 TelepathyGLib-0.12 TelepathyLogger-0.2
|
||||
Shell_0_1_gir_INCLUDES = Clutter-1.0 ClutterX11-1.0 Meta-3.0 TelepathyGLib-0.12 TelepathyLogger-0.2 Soup-2.4
|
||||
Shell_0_1_gir_CFLAGS = $(libgnome_shell_la_CPPFLAGS) -I $(srcdir)
|
||||
Shell_0_1_gir_LIBS = libgnome-shell.la
|
||||
Shell_0_1_gir_FILES = $(addprefix $(srcdir)/,$(libgnome_shell_la_gir_sources))
|
||||
Shell_0_1_gir_FILES = $(libgnome_shell_la_gir_sources)
|
||||
Shell_0_1_gir_SCANNERFLAGS = --include-uninstalled=$(builddir)/St-1.0.gir \
|
||||
--add-include-path=$(MUTTER_GIR_DIR)
|
||||
INTROSPECTION_GIRS += Shell-0.1.gir
|
||||
|
@ -777,7 +777,10 @@ remove_user (GdmUserManager *manager,
|
||||
if (gdm_user_get_object_path (user) != NULL) {
|
||||
g_hash_table_remove (manager->priv->users_by_object_path, gdm_user_get_object_path (user));
|
||||
}
|
||||
g_hash_table_remove (manager->priv->users_by_name, gdm_user_get_user_name (user));
|
||||
|
||||
if (gdm_user_get_user_name (user) != NULL) {
|
||||
g_hash_table_remove (manager->priv->users_by_name, gdm_user_get_user_name (user));
|
||||
}
|
||||
|
||||
if (manager->priv->is_loaded) {
|
||||
g_signal_emit (manager, signals[USER_REMOVED], 0, user);
|
||||
@ -884,6 +887,13 @@ on_user_removed_in_accounts_service (DBusGProxy *proxy,
|
||||
|
||||
user = g_hash_table_lookup (manager->priv->users_by_object_path, object_path);
|
||||
|
||||
if (user == NULL) {
|
||||
g_debug ("GdmUserManager: ignoring untracked user %s", object_path);
|
||||
return;
|
||||
} else {
|
||||
g_debug ("GdmUserManager: tracked user %s removed from accounts service", object_path);
|
||||
}
|
||||
|
||||
manager->priv->new_users = g_slist_remove (manager->priv->new_users, user);
|
||||
|
||||
remove_user (manager, user);
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import optparse
|
||||
@ -26,6 +27,69 @@ if args:
|
||||
parser.print_usage()
|
||||
sys.exit(1)
|
||||
|
||||
SAMPLE_EXTENSION_FILES = {
|
||||
"extension.js": """
|
||||
const St = imports.gi.St;
|
||||
const Main = imports.ui.main;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
let text;
|
||||
|
||||
function _hideHello() {
|
||||
Main.uiGroup.remove_actor(text);
|
||||
text = null;
|
||||
}
|
||||
|
||||
function _showHello() {
|
||||
if (!text) {
|
||||
text = new St.Label({ style_class: 'helloworld-label', text: "Hello, world!" });
|
||||
Main.uiGroup.add_actor(text);
|
||||
}
|
||||
|
||||
text.opacity = 255;
|
||||
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
|
||||
text.set_position(Math.floor(monitor.width / 2 - text.width / 2),
|
||||
Math.floor(monitor.height / 2 - text.height / 2));
|
||||
|
||||
Tweener.addTween(text,
|
||||
{ opacity: 0,
|
||||
time: 2,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: _hideHello });
|
||||
}
|
||||
|
||||
function main() {
|
||||
let button = new St.Bin({ style_class: 'panel-button',
|
||||
reactive: true,
|
||||
can_focus: true,
|
||||
x_fill: true,
|
||||
y_fill: false,
|
||||
track_hover: true });
|
||||
let icon = new St.Icon({ icon_name: 'system-run',
|
||||
icon_type: St.IconType.SYMBOLIC,
|
||||
style_class: 'system-status-icon' });
|
||||
|
||||
button.set_child(icon);
|
||||
button.connect('button-press-event', _showHello);
|
||||
|
||||
Main.panel._rightBox.insert_actor(button, 0);
|
||||
}
|
||||
""",
|
||||
|
||||
"stylesheet.css": """
|
||||
.helloworld-label {
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
background-color: rgba(10,10,10,0.7);
|
||||
border-radius: 5px;
|
||||
padding: .5em;
|
||||
}
|
||||
""",
|
||||
}
|
||||
|
||||
if options.create_extension:
|
||||
print
|
||||
print '''Name should be a very short (ideally descriptive) string.
|
||||
@ -41,7 +105,7 @@ Examples are: "Make windows visible on click", "Block advertisement popups"
|
||||
underifier = re.compile('[^A-Za-z]')
|
||||
sample_uuid = underifier.sub('_', name)
|
||||
# TODO use evolution data server
|
||||
hostname = subprocess.Popen(['hostname'], stdout=subprocess.PIPE).communicate()[0].strip()
|
||||
hostname = socket.gethostname()
|
||||
sample_uuid = sample_uuid + '@' + hostname
|
||||
|
||||
print
|
||||
@ -71,41 +135,11 @@ use an extension title clicktofocus@janedoe.example.com.'''
|
||||
f.write(json.write(meta) + '\n')
|
||||
f.close()
|
||||
|
||||
extensionjs_path = os.path.join(extension_path, 'extension.js')
|
||||
f = open(extensionjs_path, 'w')
|
||||
f.write('''// Sample extension code, makes clicking on the panel show a message
|
||||
const St = imports.gi.St;
|
||||
const Mainloop = imports.mainloop;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
|
||||
function _showHello() {
|
||||
let text = new St.Label({ style_class: 'helloworld-label', text: "Hello, world!" });
|
||||
let monitor = global.get_primary_monitor();
|
||||
global.stage.add_actor(text);
|
||||
text.set_position(Math.floor (monitor.width / 2 - text.width / 2), Math.floor(monitor.height / 2 - text.height / 2));
|
||||
Mainloop.timeout_add(3000, function () { text.destroy(); });
|
||||
}
|
||||
|
||||
// Put your extension initialization code here
|
||||
function main() {
|
||||
Main.panel.actor.reactive = true;
|
||||
Main.panel.actor.connect('button-release-event', _showHello);
|
||||
}
|
||||
''')
|
||||
f.close()
|
||||
|
||||
f = open(os.path.join(extension_path, 'stylesheet.css'), 'w')
|
||||
f.write('''/* Example stylesheet */
|
||||
.helloworld-label {
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
background-color: rgba(10,10,10,0.7);
|
||||
border-radius: 5px;
|
||||
}
|
||||
''')
|
||||
f.close()
|
||||
for filename, contents in SAMPLE_EXTENSION_FILES.iteritems():
|
||||
path = os.path.join(extension_path, filename)
|
||||
f = open(path, 'w')
|
||||
f.write(contents)
|
||||
f.close()
|
||||
|
||||
print "Created extension in %r" % (extension_path, )
|
||||
subprocess.Popen(['gnome-open', extensionjs_path])
|
||||
|
@ -532,7 +532,7 @@ def restore_gnome():
|
||||
component = client.get_string(gconf_path)
|
||||
|
||||
if component == None or component == "":
|
||||
return
|
||||
return False
|
||||
|
||||
# See gnome-session/gsm-util.c:gsm_util_find_desktop_file_for_app_name()
|
||||
# The one difference is that we don't search the autostart directories,
|
||||
@ -549,9 +549,15 @@ def restore_gnome():
|
||||
|
||||
if appinfo:
|
||||
appinfo.launch()
|
||||
return True
|
||||
return False
|
||||
|
||||
launch_component("/desktop/gnome/session/required_components/windowmanager")
|
||||
launch_component("/desktop/gnome/session/required_components/panel")
|
||||
# GNOME2 fallback
|
||||
wm = launch_component("/desktop/gnome/session/required_components/windowmanager")
|
||||
panel = launch_component("/desktop/gnome/session/required_components/panel")
|
||||
|
||||
if not wm and not panel: # Probably GNOME3
|
||||
subprocess.Popen(['gnome-shell'])
|
||||
|
||||
# Main program
|
||||
|
||||
|
139
src/hotplug-sniffer/hotplug-mimetypes.h
Normal file
139
src/hotplug-sniffer/hotplug-mimetypes.h
Normal file
@ -0,0 +1,139 @@
|
||||
#ifndef __HOTPLUG_MIMETYPES_H__
|
||||
#define __HOTPLUG_MIMETYPES_H__
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
G_GNUC_UNUSED static const gchar *docs_mimetypes[] = {
|
||||
"application/vnd.oasis.opendocument.text",
|
||||
"application/vnd.oasis.opendocument.presentation",
|
||||
"application/vnd.oasis.opendocument.spreadsheet",
|
||||
"application/msword",
|
||||
"application/vnd.ms-excel",
|
||||
"application/vnd.ms-powerpoint",
|
||||
"application/rtf",
|
||||
"application/pdf",
|
||||
"application/x-bzpdf",
|
||||
"application/x-gzpdf",
|
||||
"application/x-xzpdf",
|
||||
"application/postscript",
|
||||
"application/x-bzpostscript",
|
||||
"application/x-gzpostscript",
|
||||
"image/x-eps",
|
||||
"image/x-bzeps",
|
||||
"image/x-gzeps",
|
||||
"application/x-dvi",
|
||||
"application/x-bzdvi",
|
||||
"application/x-gzdvi",
|
||||
"image/vnd.djvu",
|
||||
"application/x-cbr",
|
||||
"application/x-cbz",
|
||||
"application/x-cb7",
|
||||
"application/x-cbt",
|
||||
NULL
|
||||
};
|
||||
|
||||
G_GNUC_UNUSED static const gchar *video_mimetypes[] = {
|
||||
"application/mxf",
|
||||
"application/ogg",
|
||||
"application/ram",
|
||||
"application/sdp",
|
||||
"application/vnd.ms-wpl",
|
||||
"application/vnd.rn-realmedia",
|
||||
"application/x-extension-m4a",
|
||||
"application/x-extension-mp4",
|
||||
"application/x-flash-video",
|
||||
"application/x-matroska",
|
||||
"application/x-netshow-channel",
|
||||
"application/x-ogg",
|
||||
"application/x-quicktimeplayer",
|
||||
"application/x-shorten",
|
||||
"image/vnd.rn-realpix",
|
||||
"image/x-pict",
|
||||
"misc/ultravox",
|
||||
"text/x-google-video-pointer",
|
||||
"video/3gpp",
|
||||
"video/dv",
|
||||
"video/fli",
|
||||
"video/flv",
|
||||
"video/mp2t",
|
||||
"video/mp4",
|
||||
"video/mp4v-es",
|
||||
"video/mpeg",
|
||||
"video/msvideo",
|
||||
"video/ogg",
|
||||
"video/quicktime",
|
||||
"video/vivo",
|
||||
"video/vnd.divx",
|
||||
"video/vnd.rn-realvideo",
|
||||
"video/vnd.vivo",
|
||||
"video/webm",
|
||||
"video/x-anim",
|
||||
"video/x-avi",
|
||||
"video/x-flc",
|
||||
"video/x-fli",
|
||||
"video/x-flic",
|
||||
"video/x-flv",
|
||||
"video/x-m4v",
|
||||
"video/x-matroska",
|
||||
"video/x-mpeg",
|
||||
"video/x-ms-asf",
|
||||
"video/x-ms-asx",
|
||||
"video/x-msvideo",
|
||||
"video/x-ms-wm",
|
||||
"video/x-ms-wmv",
|
||||
"video/x-ms-wmx",
|
||||
"video/x-ms-wvx",
|
||||
"video/x-nsv",
|
||||
"video/x-ogm+ogg",
|
||||
"video/x-theora+ogg",
|
||||
"video/x-totem-stream",
|
||||
NULL
|
||||
};
|
||||
|
||||
G_GNUC_UNUSED static const gchar *audio_mimetypes[] = {
|
||||
"audio/3gpp",
|
||||
"audio/ac3",
|
||||
"audio/AMR",
|
||||
"audio/AMR-WB",
|
||||
"audio/basic",
|
||||
"audio/flac",
|
||||
"audio/midi",
|
||||
"audio/mp2",
|
||||
"audio/mp4",
|
||||
"audio/mpeg",
|
||||
"audio/ogg",
|
||||
"audio/prs.sid",
|
||||
"audio/vnd.rn-realaudio",
|
||||
"audio/x-aiff",
|
||||
"audio/x-ape",
|
||||
"audio/x-flac",
|
||||
"audio/x-gsm",
|
||||
"audio/x-it",
|
||||
"audio/x-m4a",
|
||||
"audio/x-matroska",
|
||||
"audio/x-mod",
|
||||
"audio/x-mp3",
|
||||
"audio/x-mpeg",
|
||||
"audio/x-ms-asf",
|
||||
"audio/x-ms-asx",
|
||||
"audio/x-ms-wax",
|
||||
"audio/x-ms-wma",
|
||||
"audio/x-musepack",
|
||||
"audio/x-pn-aiff",
|
||||
"audio/x-pn-au",
|
||||
"audio/x-pn-wav",
|
||||
"audio/x-pn-windows-acm",
|
||||
"audio/x-realaudio",
|
||||
"audio/x-real-audio",
|
||||
"audio/x-sbc",
|
||||
"audio/x-speex",
|
||||
"audio/x-tta",
|
||||
"audio/x-wav",
|
||||
"audio/x-wavpack",
|
||||
"audio/x-vorbis",
|
||||
"audio/x-vorbis+ogg",
|
||||
"audio/x-xm",
|
||||
NULL
|
||||
};
|
||||
|
||||
#endif /* __HOTPLUG_MIMETYPES_H__ */
|
319
src/hotplug-sniffer/hotplug-sniffer.c
Normal file
319
src/hotplug-sniffer/hotplug-sniffer.c
Normal file
@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Red Hat, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*
|
||||
* Authors: David Zeuthen <davidz@redhat.com>
|
||||
* Cosimo Cecchi <cosimoc@redhat.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#include "shell-mime-sniffer.h"
|
||||
#include "hotplug-mimetypes.h"
|
||||
|
||||
/* Set the environment variable HOTPLUG_SNIFFER_DEBUG to show debug */
|
||||
static void print_debug (const gchar *str, ...);
|
||||
|
||||
#define BUS_NAME "org.gnome.Shell.HotplugSniffer"
|
||||
#define AUTOQUIT_TIMEOUT 5
|
||||
|
||||
static const gchar introspection_xml[] =
|
||||
"<node>"
|
||||
" <interface name='org.gnome.Shell.HotplugSniffer'>"
|
||||
" <method name='SniffURI'>"
|
||||
" <arg type='s' name='uri' direction='in'/>"
|
||||
" <arg type='as' name='content_types' direction='out'/>"
|
||||
" </method>"
|
||||
" </interface>"
|
||||
"</node>";
|
||||
|
||||
static GDBusNodeInfo *introspection_data = NULL;
|
||||
static GMainLoop *loop = NULL;
|
||||
static guint autoquit_id = 0;
|
||||
|
||||
static gboolean
|
||||
autoquit_timeout_cb (gpointer _unused)
|
||||
{
|
||||
print_debug ("Timeout reached, quitting...");
|
||||
|
||||
autoquit_id = 0;
|
||||
g_main_loop_quit (loop);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
ensure_autoquit_off (void)
|
||||
{
|
||||
if (g_getenv ("HOTPLUG_SNIFFER_PERSIST") != NULL)
|
||||
return;
|
||||
|
||||
if (autoquit_id != 0)
|
||||
{
|
||||
g_source_remove (autoquit_id);
|
||||
autoquit_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
ensure_autoquit_on (void)
|
||||
{
|
||||
if (g_getenv ("HOTPLUG_SNIFFER_PERSIST") != NULL)
|
||||
return;
|
||||
|
||||
autoquit_id =
|
||||
g_timeout_add_seconds (AUTOQUIT_TIMEOUT,
|
||||
autoquit_timeout_cb, NULL);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
GVariant *parameters;
|
||||
GDBusMethodInvocation *invocation;
|
||||
} InvocationData;
|
||||
|
||||
static InvocationData *
|
||||
invocation_data_new (GVariant *params,
|
||||
GDBusMethodInvocation *invocation)
|
||||
{
|
||||
InvocationData *ret;
|
||||
|
||||
ret = g_slice_new0 (InvocationData);
|
||||
ret->parameters = g_variant_ref (params);
|
||||
ret->invocation = g_object_ref (invocation);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
invocation_data_free (InvocationData *data)
|
||||
{
|
||||
g_variant_unref (data->parameters);
|
||||
g_clear_object (&data->invocation);
|
||||
|
||||
g_slice_free (InvocationData, data);
|
||||
}
|
||||
|
||||
static void
|
||||
sniff_async_ready_cb (GObject *source,
|
||||
GAsyncResult *res,
|
||||
gpointer user_data)
|
||||
{
|
||||
InvocationData *data = user_data;
|
||||
gchar **types;
|
||||
gint idx;
|
||||
GError *error = NULL;
|
||||
GVariantBuilder *builder;
|
||||
GVariant *result;
|
||||
|
||||
types = shell_mime_sniffer_sniff_finish (SHELL_MIME_SNIFFER (source),
|
||||
res, &error);
|
||||
|
||||
if (error != NULL)
|
||||
{
|
||||
g_dbus_method_invocation_return_gerror (data->invocation, error);
|
||||
g_error_free (error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
|
||||
|
||||
for (idx = 0; types[idx] != NULL; idx++)
|
||||
g_variant_builder_add (builder, "s", types[idx]);
|
||||
|
||||
result = g_variant_new ("(as)", builder);
|
||||
g_dbus_method_invocation_return_value (data->invocation, result);
|
||||
|
||||
g_variant_unref (result);
|
||||
g_variant_builder_unref (builder);
|
||||
g_strfreev (types);
|
||||
|
||||
out:
|
||||
invocation_data_free (data);
|
||||
ensure_autoquit_on ();
|
||||
}
|
||||
|
||||
static void
|
||||
handle_sniff_uri (InvocationData *data)
|
||||
{
|
||||
ShellMimeSniffer *sniffer;
|
||||
const gchar *uri;
|
||||
GFile *file;
|
||||
|
||||
ensure_autoquit_off ();
|
||||
|
||||
g_variant_get (data->parameters,
|
||||
"(&s)", &uri,
|
||||
NULL);
|
||||
file = g_file_new_for_uri (uri);
|
||||
|
||||
print_debug ("Initiating sniff for uri %s", uri);
|
||||
|
||||
sniffer = shell_mime_sniffer_new (file);
|
||||
shell_mime_sniffer_sniff_async (sniffer,
|
||||
sniff_async_ready_cb,
|
||||
data);
|
||||
|
||||
g_object_unref (sniffer);
|
||||
g_object_unref (file);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_method_call (GDBusConnection *connection,
|
||||
const gchar *sender,
|
||||
const gchar *object_path,
|
||||
const gchar *interface_name,
|
||||
const gchar *method_name,
|
||||
GVariant *parameters,
|
||||
GDBusMethodInvocation *invocation,
|
||||
gpointer user_data)
|
||||
{
|
||||
InvocationData *data;
|
||||
|
||||
data = invocation_data_new (parameters, invocation);
|
||||
|
||||
if (g_strcmp0 (method_name, "SniffURI") == 0)
|
||||
handle_sniff_uri (data);
|
||||
else
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
static const GDBusInterfaceVTable interface_vtable =
|
||||
{
|
||||
handle_method_call,
|
||||
NULL, /* get_property */
|
||||
NULL, /* set_property */
|
||||
};
|
||||
|
||||
static void
|
||||
on_bus_acquired (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
gpointer user_data)
|
||||
{
|
||||
GError *error = NULL;
|
||||
|
||||
print_debug ("Connected to the session bus: %s", name);
|
||||
|
||||
g_dbus_connection_register_object (connection,
|
||||
"/org/gnome/Shell/HotplugSniffer",
|
||||
introspection_data->interfaces[0],
|
||||
&interface_vtable,
|
||||
NULL,
|
||||
NULL,
|
||||
&error);
|
||||
|
||||
if (error != NULL)
|
||||
{
|
||||
g_printerr ("Error exporting object on the session bus: %s",
|
||||
error->message);
|
||||
g_error_free (error);
|
||||
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
print_debug ("Object exported on the session bus");
|
||||
}
|
||||
|
||||
static void
|
||||
on_name_lost (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
gpointer user_data)
|
||||
{
|
||||
print_debug ("Lost bus name: %s, exiting", name);
|
||||
|
||||
g_main_loop_quit (loop);
|
||||
}
|
||||
|
||||
static void
|
||||
on_name_acquired (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
gpointer user_data)
|
||||
{
|
||||
print_debug ("Acquired bus name: %s", name);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char **argv)
|
||||
{
|
||||
guint name_owner_id;
|
||||
|
||||
g_type_init ();
|
||||
|
||||
introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
|
||||
g_assert (introspection_data != NULL);
|
||||
|
||||
ensure_autoquit_on ();
|
||||
loop = g_main_loop_new (NULL, FALSE);
|
||||
|
||||
name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
|
||||
BUS_NAME, 0,
|
||||
on_bus_acquired,
|
||||
on_name_acquired,
|
||||
on_name_lost,
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
g_main_loop_run (loop);
|
||||
|
||||
if (name_owner_id != 0)
|
||||
g_bus_unown_name (name_owner_id);
|
||||
|
||||
if (loop != NULL)
|
||||
g_main_loop_unref (loop);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------------- */
|
||||
|
||||
static void
|
||||
print_debug (const gchar *format, ...)
|
||||
{
|
||||
gchar *s;
|
||||
va_list ap;
|
||||
gchar timebuf[64];
|
||||
GTimeVal now;
|
||||
time_t now_t;
|
||||
struct tm broken_down;
|
||||
static volatile gsize once_init_value = 0;
|
||||
static gboolean show_debug = FALSE;
|
||||
static guint pid = 0;
|
||||
|
||||
if (g_once_init_enter (&once_init_value))
|
||||
{
|
||||
show_debug = (g_getenv ("HOTPLUG_SNIFFER_DEBUG") != NULL);
|
||||
pid = getpid ();
|
||||
g_once_init_leave (&once_init_value, 1);
|
||||
}
|
||||
|
||||
if (!show_debug)
|
||||
goto out;
|
||||
|
||||
g_get_current_time (&now);
|
||||
now_t = now.tv_sec;
|
||||
localtime_r (&now_t, &broken_down);
|
||||
strftime (timebuf, sizeof timebuf, "%H:%M:%S", &broken_down);
|
||||
|
||||
va_start (ap, format);
|
||||
s = g_strdup_vprintf (format, ap);
|
||||
va_end (ap);
|
||||
|
||||
g_print ("gnome-shell-hotplug-sniffer[%d]: %s.%03d: %s\n", pid, timebuf, (gint) (now.tv_usec / 1000), s);
|
||||
g_free (s);
|
||||
out:
|
||||
;
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
[D-BUS Service]
|
||||
Name=org.gnome.Shell.HotplugSniffer
|
||||
Exec=@libexecdir@/gnome-shell-hotplug-sniffer
|
607
src/hotplug-sniffer/shell-mime-sniffer.c
Normal file
607
src/hotplug-sniffer/shell-mime-sniffer.c
Normal file
@ -0,0 +1,607 @@
|
||||
/*
|
||||
* Copyright (C) 1999, 2000, 2001 Eazel, Inc.
|
||||
* Copyright (C) 2011 Red Hat, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*
|
||||
* Author: Cosimo Cecchi <cosimoc@redhat.com>
|
||||
*
|
||||
* The code for crawling the directory hierarchy is based on
|
||||
* nautilus/libnautilus-private/nautilus-directory-async.c, with
|
||||
* the following copyright and author:
|
||||
*
|
||||
* Copyright (C) 1999, 2000, 2001 Eazel, Inc.
|
||||
* Author: Darin Adler <darin@bentspoon.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#include "shell-mime-sniffer.h"
|
||||
#include "hotplug-mimetypes.h"
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||||
|
||||
#define LOADER_ATTRS \
|
||||
G_FILE_ATTRIBUTE_STANDARD_TYPE "," \
|
||||
G_FILE_ATTRIBUTE_STANDARD_NAME "," \
|
||||
G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
|
||||
|
||||
#define WATCHDOG_TIMEOUT 1500
|
||||
#define DIRECTORY_LOAD_ITEMS_PER_CALLBACK 100
|
||||
#define HIGH_SCORE_RATIO 0.10
|
||||
|
||||
G_DEFINE_TYPE (ShellMimeSniffer, shell_mime_sniffer, G_TYPE_OBJECT);
|
||||
|
||||
enum {
|
||||
PROP_FILE = 1,
|
||||
NUM_PROPERTIES
|
||||
};
|
||||
|
||||
static GHashTable *image_type_table = NULL;
|
||||
static GHashTable *audio_type_table = NULL;
|
||||
static GHashTable *video_type_table = NULL;
|
||||
static GHashTable *docs_type_table = NULL;
|
||||
|
||||
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
|
||||
|
||||
typedef struct {
|
||||
ShellMimeSniffer *self;
|
||||
|
||||
GFile *file;
|
||||
GFileEnumerator *enumerator;
|
||||
GList *deep_count_subdirectories;
|
||||
|
||||
gint audio_count;
|
||||
gint image_count;
|
||||
gint document_count;
|
||||
gint video_count;
|
||||
|
||||
gint total_items;
|
||||
} DeepCountState;
|
||||
|
||||
struct _ShellMimeSnifferPrivate {
|
||||
GFile *file;
|
||||
|
||||
GCancellable *cancellable;
|
||||
guint watchdog_id;
|
||||
|
||||
GSimpleAsyncResult *async_result;
|
||||
gchar **sniffed_mime;
|
||||
};
|
||||
|
||||
static void deep_count_load (DeepCountState *state,
|
||||
GFile *file);
|
||||
|
||||
static void
|
||||
init_mimetypes (void)
|
||||
{
|
||||
static gsize once_init = 0;
|
||||
|
||||
if (g_once_init_enter (&once_init))
|
||||
{
|
||||
GSList *formats, *l;
|
||||
GdkPixbufFormat *format;
|
||||
gchar **types;
|
||||
gint idx;
|
||||
|
||||
image_type_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||||
video_type_table = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
audio_type_table = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
docs_type_table = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
|
||||
formats = gdk_pixbuf_get_formats ();
|
||||
|
||||
for (l = formats; l != NULL; l = l->next)
|
||||
{
|
||||
format = l->data;
|
||||
types = gdk_pixbuf_format_get_mime_types (format);
|
||||
|
||||
for (idx = 0; types[idx] != NULL; idx++)
|
||||
g_hash_table_insert (image_type_table, g_strdup (types[idx]), GINT_TO_POINTER (1));
|
||||
|
||||
g_strfreev (types);
|
||||
}
|
||||
|
||||
g_slist_free (formats);
|
||||
|
||||
for (idx = 0; audio_mimetypes[idx] != NULL; idx++)
|
||||
g_hash_table_insert (audio_type_table, (gpointer) audio_mimetypes[idx], GINT_TO_POINTER (1));
|
||||
|
||||
for (idx = 0; video_mimetypes[idx] != NULL; idx++)
|
||||
g_hash_table_insert (video_type_table, (gpointer) video_mimetypes[idx], GINT_TO_POINTER (1));
|
||||
|
||||
for (idx = 0; docs_mimetypes[idx] != NULL; idx++)
|
||||
g_hash_table_insert (docs_type_table, (gpointer) docs_mimetypes[idx], GINT_TO_POINTER (1));
|
||||
|
||||
g_once_init_leave (&once_init, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
add_content_type_to_cache (DeepCountState *state,
|
||||
const gchar *content_type)
|
||||
{
|
||||
gboolean matched = TRUE;
|
||||
|
||||
if (g_hash_table_lookup (image_type_table, content_type))
|
||||
state->image_count++;
|
||||
else if (g_hash_table_lookup (video_type_table, content_type))
|
||||
state->video_count++;
|
||||
else if (g_hash_table_lookup (docs_type_table, content_type))
|
||||
state->document_count++;
|
||||
else if (g_hash_table_lookup (audio_type_table, content_type))
|
||||
state->audio_count++;
|
||||
else
|
||||
matched = FALSE;
|
||||
|
||||
if (matched)
|
||||
state->total_items++;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const gchar *type;
|
||||
gdouble ratio;
|
||||
} SniffedResult;
|
||||
|
||||
static gint
|
||||
results_cmp_func (gconstpointer a,
|
||||
gconstpointer b)
|
||||
{
|
||||
const SniffedResult *sniffed_a = a;
|
||||
const SniffedResult *sniffed_b = b;
|
||||
|
||||
if (sniffed_a->ratio < sniffed_b->ratio)
|
||||
return 1;
|
||||
|
||||
if (sniffed_a->ratio > sniffed_b->ratio)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
prepare_async_result (DeepCountState *state)
|
||||
{
|
||||
ShellMimeSniffer *self = state->self;
|
||||
GArray *results;
|
||||
GPtrArray *sniffed_mime;
|
||||
SniffedResult result;
|
||||
|
||||
sniffed_mime = g_ptr_array_new ();
|
||||
results = g_array_new (TRUE, TRUE, sizeof (SniffedResult));
|
||||
|
||||
if (state->total_items == 0)
|
||||
goto out;
|
||||
|
||||
result.type = "x-content/video";
|
||||
result.ratio = (gdouble) state->video_count / (gdouble) state->total_items;
|
||||
g_array_append_val (results, result);
|
||||
|
||||
result.type = "x-content/audio";
|
||||
result.ratio = (gdouble) state->audio_count / (gdouble) state->total_items;
|
||||
g_array_append_val (results, result);
|
||||
|
||||
result.type = "x-content/pictures";
|
||||
result.ratio = (gdouble) state->image_count / (gdouble) state->total_items;
|
||||
g_array_append_val (results, result);
|
||||
|
||||
result.type = "x-content/documents";
|
||||
result.ratio = (gdouble) state->document_count / (gdouble) state->total_items;
|
||||
g_array_append_val (results, result);
|
||||
|
||||
g_array_sort (results, results_cmp_func);
|
||||
|
||||
result = g_array_index (results, SniffedResult, 0);
|
||||
g_ptr_array_add (sniffed_mime, g_strdup (result.type));
|
||||
|
||||
/* if other types score high in ratio, add them, up to three */
|
||||
result = g_array_index (results, SniffedResult, 1);
|
||||
if (result.ratio < HIGH_SCORE_RATIO)
|
||||
goto out;
|
||||
g_ptr_array_add (sniffed_mime, g_strdup (result.type));
|
||||
|
||||
result = g_array_index (results, SniffedResult, 2);
|
||||
if (result.ratio < HIGH_SCORE_RATIO)
|
||||
goto out;
|
||||
g_ptr_array_add (sniffed_mime, g_strdup (result.type));
|
||||
|
||||
out:
|
||||
g_ptr_array_add (sniffed_mime, NULL);
|
||||
self->priv->sniffed_mime = (gchar **) g_ptr_array_free (sniffed_mime, FALSE);
|
||||
|
||||
g_array_free (results, TRUE);
|
||||
g_simple_async_result_complete_in_idle (self->priv->async_result);
|
||||
}
|
||||
|
||||
/* adapted from nautilus/libnautilus-private/nautilus-directory-async.c */
|
||||
static void
|
||||
deep_count_one (DeepCountState *state,
|
||||
GFileInfo *info)
|
||||
{
|
||||
GFile *subdir;
|
||||
const char *content_type;
|
||||
|
||||
if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
|
||||
{
|
||||
/* record the fact that we have to descend into this directory */
|
||||
subdir = g_file_get_child (state->file, g_file_info_get_name (info));
|
||||
state->deep_count_subdirectories =
|
||||
g_list_append (state->deep_count_subdirectories, subdir);
|
||||
}
|
||||
else
|
||||
{
|
||||
content_type = g_file_info_get_content_type (info);
|
||||
add_content_type_to_cache (state, content_type);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
deep_count_finish (DeepCountState *state)
|
||||
{
|
||||
prepare_async_result (state);
|
||||
|
||||
if (state->enumerator)
|
||||
{
|
||||
if (!g_file_enumerator_is_closed (state->enumerator))
|
||||
g_file_enumerator_close_async (state->enumerator,
|
||||
0, NULL, NULL, NULL);
|
||||
|
||||
g_object_unref (state->enumerator);
|
||||
}
|
||||
|
||||
g_cancellable_reset (state->self->priv->cancellable);
|
||||
g_clear_object (&state->file);
|
||||
|
||||
g_list_free_full (state->deep_count_subdirectories, g_object_unref);
|
||||
|
||||
g_free (state);
|
||||
}
|
||||
|
||||
static void
|
||||
deep_count_next_dir (DeepCountState *state)
|
||||
{
|
||||
GFile *new_file;
|
||||
|
||||
g_clear_object (&state->file);
|
||||
|
||||
if (state->deep_count_subdirectories != NULL)
|
||||
{
|
||||
/* Work on a new directory. */
|
||||
new_file = state->deep_count_subdirectories->data;
|
||||
state->deep_count_subdirectories =
|
||||
g_list_remove (state->deep_count_subdirectories, new_file);
|
||||
|
||||
deep_count_load (state, new_file);
|
||||
g_object_unref (new_file);
|
||||
}
|
||||
else
|
||||
{
|
||||
deep_count_finish (state);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
deep_count_more_files_callback (GObject *source_object,
|
||||
GAsyncResult *res,
|
||||
gpointer user_data)
|
||||
{
|
||||
DeepCountState *state;
|
||||
GList *files, *l;
|
||||
GFileInfo *info;
|
||||
|
||||
state = user_data;
|
||||
|
||||
if (g_cancellable_is_cancelled (state->self->priv->cancellable))
|
||||
{
|
||||
deep_count_finish (state);
|
||||
return;
|
||||
}
|
||||
|
||||
files = g_file_enumerator_next_files_finish (state->enumerator,
|
||||
res, NULL);
|
||||
|
||||
for (l = files; l != NULL; l = l->next)
|
||||
{
|
||||
info = l->data;
|
||||
deep_count_one (state, info);
|
||||
g_object_unref (info);
|
||||
}
|
||||
|
||||
if (files == NULL)
|
||||
{
|
||||
g_file_enumerator_close_async (state->enumerator, 0, NULL, NULL, NULL);
|
||||
g_object_unref (state->enumerator);
|
||||
state->enumerator = NULL;
|
||||
|
||||
deep_count_next_dir (state);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_file_enumerator_next_files_async (state->enumerator,
|
||||
DIRECTORY_LOAD_ITEMS_PER_CALLBACK,
|
||||
G_PRIORITY_LOW,
|
||||
state->self->priv->cancellable,
|
||||
deep_count_more_files_callback,
|
||||
state);
|
||||
}
|
||||
|
||||
g_list_free (files);
|
||||
}
|
||||
|
||||
static void
|
||||
deep_count_callback (GObject *source_object,
|
||||
GAsyncResult *res,
|
||||
gpointer user_data)
|
||||
{
|
||||
DeepCountState *state;
|
||||
GFileEnumerator *enumerator;
|
||||
|
||||
state = user_data;
|
||||
|
||||
if (g_cancellable_is_cancelled (state->self->priv->cancellable))
|
||||
{
|
||||
deep_count_finish (state);
|
||||
return;
|
||||
}
|
||||
|
||||
enumerator = g_file_enumerate_children_finish (G_FILE (source_object),
|
||||
res, NULL);
|
||||
|
||||
if (enumerator == NULL)
|
||||
{
|
||||
deep_count_next_dir (state);
|
||||
}
|
||||
else
|
||||
{
|
||||
state->enumerator = enumerator;
|
||||
g_file_enumerator_next_files_async (state->enumerator,
|
||||
DIRECTORY_LOAD_ITEMS_PER_CALLBACK,
|
||||
G_PRIORITY_LOW,
|
||||
state->self->priv->cancellable,
|
||||
deep_count_more_files_callback,
|
||||
state);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
deep_count_load (DeepCountState *state,
|
||||
GFile *file)
|
||||
{
|
||||
state->file = g_object_ref (file);
|
||||
|
||||
g_file_enumerate_children_async (state->file,
|
||||
LOADER_ATTRS,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, /* flags */
|
||||
G_PRIORITY_LOW, /* prio */
|
||||
state->self->priv->cancellable,
|
||||
deep_count_callback,
|
||||
state);
|
||||
}
|
||||
|
||||
static void
|
||||
deep_count_start (ShellMimeSniffer *self)
|
||||
{
|
||||
DeepCountState *state;
|
||||
|
||||
state = g_new0 (DeepCountState, 1);
|
||||
state->self = self;
|
||||
|
||||
deep_count_load (state, self->priv->file);
|
||||
}
|
||||
|
||||
static void
|
||||
query_info_async_ready_cb (GObject *source,
|
||||
GAsyncResult *res,
|
||||
gpointer user_data)
|
||||
{
|
||||
GFileInfo *info;
|
||||
GError *error = NULL;
|
||||
ShellMimeSniffer *self = user_data;
|
||||
|
||||
info = g_file_query_info_finish (G_FILE (source),
|
||||
res, &error);
|
||||
|
||||
if (error != NULL)
|
||||
{
|
||||
g_simple_async_result_take_error (self->priv->async_result,
|
||||
error);
|
||||
g_simple_async_result_complete_in_idle (self->priv->async_result);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
|
||||
{
|
||||
g_simple_async_result_set_error (self->priv->async_result,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_NOT_DIRECTORY,
|
||||
"Not a directory");
|
||||
g_simple_async_result_complete_in_idle (self->priv->async_result);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
deep_count_start (self);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
watchdog_timeout_reached_cb (gpointer user_data)
|
||||
{
|
||||
ShellMimeSniffer *self = user_data;
|
||||
|
||||
self->priv->watchdog_id = 0;
|
||||
g_cancellable_cancel (self->priv->cancellable);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
start_loading_file (ShellMimeSniffer *self)
|
||||
{
|
||||
g_file_query_info_async (self->priv->file,
|
||||
LOADER_ATTRS,
|
||||
G_FILE_QUERY_INFO_NONE,
|
||||
G_PRIORITY_DEFAULT,
|
||||
self->priv->cancellable,
|
||||
query_info_async_ready_cb,
|
||||
self);
|
||||
}
|
||||
|
||||
static void
|
||||
shell_mime_sniffer_set_file (ShellMimeSniffer *self,
|
||||
GFile *file)
|
||||
{
|
||||
g_clear_object (&self->priv->file);
|
||||
self->priv->file = g_object_ref (file);
|
||||
}
|
||||
|
||||
static void
|
||||
shell_mime_sniffer_dispose (GObject *object)
|
||||
{
|
||||
ShellMimeSniffer *self = SHELL_MIME_SNIFFER (object);
|
||||
|
||||
g_clear_object (&self->priv->file);
|
||||
g_clear_object (&self->priv->cancellable);
|
||||
g_clear_object (&self->priv->async_result);
|
||||
|
||||
if (self->priv->watchdog_id != 0)
|
||||
{
|
||||
g_source_remove (self->priv->watchdog_id);
|
||||
self->priv->watchdog_id = 0;
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (shell_mime_sniffer_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
shell_mime_sniffer_finalize (GObject *object)
|
||||
{
|
||||
ShellMimeSniffer *self = SHELL_MIME_SNIFFER (object);
|
||||
|
||||
g_strfreev (self->priv->sniffed_mime);
|
||||
|
||||
G_OBJECT_CLASS (shell_mime_sniffer_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
shell_mime_sniffer_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
ShellMimeSniffer *self = SHELL_MIME_SNIFFER (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_FILE:
|
||||
g_value_set_object (value, self->priv->file);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
shell_mime_sniffer_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
ShellMimeSniffer *self = SHELL_MIME_SNIFFER (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_FILE:
|
||||
shell_mime_sniffer_set_file (self, g_value_get_object (value));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
shell_mime_sniffer_class_init (ShellMimeSnifferClass *klass)
|
||||
{
|
||||
GObjectClass *oclass;
|
||||
|
||||
oclass = G_OBJECT_CLASS (klass);
|
||||
oclass->dispose = shell_mime_sniffer_dispose;
|
||||
oclass->finalize = shell_mime_sniffer_finalize;
|
||||
oclass->get_property = shell_mime_sniffer_get_property;
|
||||
oclass->set_property = shell_mime_sniffer_set_property;
|
||||
|
||||
properties[PROP_FILE] =
|
||||
g_param_spec_object ("file",
|
||||
"File",
|
||||
"The loaded file",
|
||||
G_TYPE_FILE,
|
||||
G_PARAM_READWRITE);
|
||||
|
||||
g_type_class_add_private (klass, sizeof (ShellMimeSnifferPrivate));
|
||||
g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
|
||||
}
|
||||
|
||||
static void
|
||||
shell_mime_sniffer_init (ShellMimeSniffer *self)
|
||||
{
|
||||
self->priv =
|
||||
G_TYPE_INSTANCE_GET_PRIVATE (self,
|
||||
SHELL_TYPE_MIME_SNIFFER,
|
||||
ShellMimeSnifferPrivate);
|
||||
init_mimetypes ();
|
||||
}
|
||||
|
||||
ShellMimeSniffer *
|
||||
shell_mime_sniffer_new (GFile *file)
|
||||
{
|
||||
return g_object_new (SHELL_TYPE_MIME_SNIFFER,
|
||||
"file", file,
|
||||
NULL);
|
||||
}
|
||||
|
||||
void
|
||||
shell_mime_sniffer_sniff_async (ShellMimeSniffer *self,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_assert (self->priv->watchdog_id == 0);
|
||||
g_assert (self->priv->async_result == NULL);
|
||||
|
||||
self->priv->async_result =
|
||||
g_simple_async_result_new (G_OBJECT (self),
|
||||
callback, user_data,
|
||||
shell_mime_sniffer_sniff_finish);
|
||||
|
||||
self->priv->cancellable = g_cancellable_new ();
|
||||
|
||||
self->priv->watchdog_id =
|
||||
g_timeout_add (WATCHDOG_TIMEOUT,
|
||||
watchdog_timeout_reached_cb, self);
|
||||
|
||||
start_loading_file (self);
|
||||
}
|
||||
|
||||
gchar **
|
||||
shell_mime_sniffer_sniff_finish (ShellMimeSniffer *self,
|
||||
GAsyncResult *res,
|
||||
GError **error)
|
||||
{
|
||||
if (g_simple_async_result_propagate_error (self->priv->async_result, error))
|
||||
return NULL;
|
||||
|
||||
return g_strdupv (self->priv->sniffed_mime);
|
||||
}
|
68
src/hotplug-sniffer/shell-mime-sniffer.h
Normal file
68
src/hotplug-sniffer/shell-mime-sniffer.h
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Red Hat, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*
|
||||
* Author: Cosimo Cecchi <cosimoc@redhat.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __SHELL_MIME_SNIFFER_H__
|
||||
#define __SHELL_MIME_SNIFFER_H__
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SHELL_TYPE_MIME_SNIFFER (shell_mime_sniffer_get_type ())
|
||||
#define SHELL_MIME_SNIFFER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHELL_TYPE_MIME_SNIFFER, ShellMimeSniffer))
|
||||
#define SHELL_IS_MIME_SNIFFER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SHELL_TYPE_MIME_SNIFFER))
|
||||
#define SHELL_MIME_SNIFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_MIME_SNIFFER, ShellMimeSnifferClass))
|
||||
#define SHELL_IS_MIME_SNIFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_MIME_SNIFFER))
|
||||
#define SHELL_MIME_SNIFFER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_MIME_SNIFFER, ShellMimeSnifferClass))
|
||||
|
||||
typedef struct _ShellMimeSniffer ShellMimeSniffer;
|
||||
typedef struct _ShellMimeSnifferPrivate ShellMimeSnifferPrivate;
|
||||
typedef struct _ShellMimeSnifferClass ShellMimeSnifferClass;
|
||||
|
||||
struct _ShellMimeSniffer
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
ShellMimeSnifferPrivate *priv;
|
||||
};
|
||||
|
||||
struct _ShellMimeSnifferClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
GType shell_mime_sniffer_get_type (void) G_GNUC_CONST;
|
||||
|
||||
ShellMimeSniffer *shell_mime_sniffer_new (GFile *file);
|
||||
|
||||
void shell_mime_sniffer_sniff_async (ShellMimeSniffer *self,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
|
||||
gchar ** shell_mime_sniffer_sniff_finish (ShellMimeSniffer *self,
|
||||
GAsyncResult *res,
|
||||
GError **error);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __SHELL_MIME_SNIFFER_H__ */
|
47
src/main.c
47
src/main.c
@ -19,6 +19,8 @@
|
||||
#include <meta/main.h>
|
||||
#include <meta/meta-plugin.h>
|
||||
#include <meta/prefs.h>
|
||||
#include <telepathy-glib/debug.h>
|
||||
#include <telepathy-glib/debug-sender.h>
|
||||
|
||||
#include "shell-a11y.h"
|
||||
#include "shell-global.h"
|
||||
@ -428,6 +430,25 @@ muted_log_handler (const char *log_domain,
|
||||
/* Intentionally empty to discard message */
|
||||
}
|
||||
|
||||
static void
|
||||
default_log_handler (const char *log_domain,
|
||||
GLogLevelFlags log_level,
|
||||
const char *message,
|
||||
gpointer data)
|
||||
{
|
||||
TpDebugSender *sender = data;
|
||||
GTimeVal now;
|
||||
|
||||
g_get_current_time (&now);
|
||||
|
||||
tp_debug_sender_add_message (sender, &now, log_domain, log_level, message);
|
||||
|
||||
/* Filter out telepathy-glib logs, we don't want to flood Shell's output
|
||||
* with those. */
|
||||
if (!g_str_has_prefix (log_domain, "tp-glib"))
|
||||
g_log_default_handler (log_domain, log_level, message, data);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
print_version (const gchar *option_name,
|
||||
const gchar *value,
|
||||
@ -453,6 +474,8 @@ main (int argc, char **argv)
|
||||
{
|
||||
GOptionContext *ctx;
|
||||
GError *error = NULL;
|
||||
int ecode;
|
||||
TpDebugSender *sender;
|
||||
|
||||
g_type_init ();
|
||||
|
||||
@ -468,6 +491,8 @@ main (int argc, char **argv)
|
||||
exit (1);
|
||||
}
|
||||
|
||||
g_option_context_free (ctx);
|
||||
|
||||
meta_plugin_type_register (gnome_shell_plugin_get_type ());
|
||||
|
||||
/* Prevent meta_init() from causing gtk to load gail and at-bridge */
|
||||
@ -503,9 +528,29 @@ main (int argc, char **argv)
|
||||
muted_log_handler, NULL);
|
||||
g_log_set_handler ("Bluetooth", G_LOG_LEVEL_DEBUG | G_LOG_LEVEL_MESSAGE,
|
||||
muted_log_handler, NULL);
|
||||
g_log_set_handler ("tp-glib/proxy", G_LOG_LEVEL_DEBUG,
|
||||
muted_log_handler, NULL);
|
||||
|
||||
/* Turn on telepathy-glib debugging but filter it out in
|
||||
* default_log_handler. This handler also exposes all the logs over D-Bus
|
||||
* using TpDebugSender. */
|
||||
tp_debug_set_flags ("all");
|
||||
|
||||
sender = tp_debug_sender_dup ();
|
||||
g_log_set_default_handler (default_log_handler, sender);
|
||||
|
||||
/* Initialize the global object */
|
||||
shell_global_get ();
|
||||
|
||||
return meta_run ();
|
||||
ecode = meta_run ();
|
||||
|
||||
if (g_getenv ("GNOME_SHELL_ENABLE_CLEANUP"))
|
||||
{
|
||||
g_printerr ("Doing final cleanup...\n");
|
||||
g_object_unref (shell_global_get ());
|
||||
}
|
||||
|
||||
g_object_unref (sender);
|
||||
|
||||
return ecode;
|
||||
}
|
||||
|
@ -32,18 +32,17 @@
|
||||
|
||||
#include <clutter/x11/clutter-x11.h>
|
||||
#include <gdk/gdkx.h>
|
||||
#include <girepository.h>
|
||||
#include <gjs/gjs.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "shell-global.h"
|
||||
#include "shell-global-private.h"
|
||||
|
||||
static char **include_path = NULL;
|
||||
static char *command = NULL;
|
||||
|
||||
static GOptionEntry entries[] = {
|
||||
{ "command", 'c', 0, G_OPTION_ARG_STRING, &command, "Program passed in as a string", "COMMAND" },
|
||||
{ "include-path", 'I', 0, G_OPTION_ARG_STRING_ARRAY, &include_path, "Add the directory DIR to the list of directories to search for js files.", "DIR" },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
@ -131,6 +130,14 @@ main(int argc, char **argv)
|
||||
clutter_stage_set_title (CLUTTER_STAGE (stage), title);
|
||||
g_free (title);
|
||||
|
||||
#if HAVE_BLUETOOTH
|
||||
/* The module imports are all so intertwined that if the test
|
||||
* imports anything in js/ui, it will probably eventually end up
|
||||
* pulling in ui/status/bluetooth.js. So we need this.
|
||||
*/
|
||||
g_irepository_prepend_search_path (BLUETOOTH_DIR);
|
||||
#endif
|
||||
|
||||
/* evaluate the script */
|
||||
error = NULL;
|
||||
if (!gjs_context_eval (js_context, script, len,
|
||||
|
@ -25,6 +25,11 @@
|
||||
#include <meta/display.h>
|
||||
#include <meta/util.h>
|
||||
|
||||
/* Memory report bits */
|
||||
#ifdef HAVE_MALLINFO
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
#include "shell-enum-types.h"
|
||||
#include "shell-global-private.h"
|
||||
#include "shell-jsapi-compat-private.h"
|
||||
@ -34,6 +39,8 @@
|
||||
#include "shell-wm.h"
|
||||
#include "st.h"
|
||||
|
||||
static ShellGlobal *the_object = NULL;
|
||||
|
||||
static void grab_notify (GtkWidget *widget, gboolean is_grab, gpointer user_data);
|
||||
|
||||
struct _ShellGlobal {
|
||||
@ -62,8 +69,6 @@ struct _ShellGlobal {
|
||||
|
||||
GdkWindow *stage_window;
|
||||
|
||||
gint last_change_screen_width, last_change_screen_height;
|
||||
|
||||
guint work_count;
|
||||
GSList *leisure_closures;
|
||||
guint leisure_function_id;
|
||||
@ -237,9 +242,6 @@ shell_global_init (ShellGlobal *global)
|
||||
|
||||
global->input_mode = SHELL_STAGE_INPUT_MODE_NORMAL;
|
||||
|
||||
global->last_change_screen_width = 0;
|
||||
global->last_change_screen_height = 0;
|
||||
|
||||
ca_context_create (&global->sound_context);
|
||||
ca_context_change_props (global->sound_context, CA_PROP_APPLICATION_NAME, PACKAGE_NAME, CA_PROP_APPLICATION_ID, "org.gnome.Shell", NULL);
|
||||
ca_context_open (global->sound_context);
|
||||
@ -254,6 +256,20 @@ shell_global_init (ShellGlobal *global)
|
||||
g_strfreev (search_path);
|
||||
}
|
||||
|
||||
static void
|
||||
shell_global_finalize (GObject *object)
|
||||
{
|
||||
ShellGlobal *global = SHELL_GLOBAL (object);
|
||||
|
||||
g_object_unref (global->js_context);
|
||||
gtk_widget_destroy (GTK_WIDGET (global->grab_notifier));
|
||||
g_object_unref (global->settings);
|
||||
|
||||
the_object = NULL;
|
||||
|
||||
G_OBJECT_CLASS(shell_global_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
shell_global_class_init (ShellGlobalClass *klass)
|
||||
{
|
||||
@ -261,6 +277,7 @@ shell_global_class_init (ShellGlobalClass *klass)
|
||||
|
||||
gobject_class->get_property = shell_global_get_property;
|
||||
gobject_class->set_property = shell_global_set_property;
|
||||
gobject_class->finalize = shell_global_finalize;
|
||||
|
||||
/* Emitted from gnome-shell-plugin.c during event handling */
|
||||
shell_global_signals[XDND_POSITION_CHANGED] =
|
||||
@ -424,8 +441,6 @@ shell_global_class_init (ShellGlobalClass *klass)
|
||||
ShellGlobal *
|
||||
shell_global_get (void)
|
||||
{
|
||||
static ShellGlobal *the_object = NULL;
|
||||
|
||||
if (!the_object)
|
||||
the_object = g_object_new (SHELL_TYPE_GLOBAL, 0);
|
||||
|
||||
@ -680,23 +695,6 @@ shell_global_get_window_actors (ShellGlobal *global)
|
||||
return meta_plugin_get_window_actors (global->plugin);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
update_screen_size (gpointer data)
|
||||
{
|
||||
int width, height;
|
||||
ShellGlobal *global = SHELL_GLOBAL (data);
|
||||
|
||||
meta_plugin_query_screen_size (global->plugin, &width, &height);
|
||||
|
||||
if (global->last_change_screen_width == width && global->last_change_screen_height == height)
|
||||
return FALSE;
|
||||
|
||||
global->last_change_screen_width = width;
|
||||
global->last_change_screen_height = height;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
global_stage_notify_width (GObject *gobject,
|
||||
GParamSpec *pspec,
|
||||
@ -705,11 +703,6 @@ global_stage_notify_width (GObject *gobject,
|
||||
ShellGlobal *global = SHELL_GLOBAL (data);
|
||||
|
||||
g_object_notify (G_OBJECT (global), "screen-width");
|
||||
|
||||
meta_later_add (META_LATER_BEFORE_REDRAW,
|
||||
update_screen_size,
|
||||
global,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -720,11 +713,6 @@ global_stage_notify_height (GObject *gobject,
|
||||
ShellGlobal *global = SHELL_GLOBAL (data);
|
||||
|
||||
g_object_notify (G_OBJECT (global), "screen-height");
|
||||
|
||||
meta_later_add (META_LATER_BEFORE_REDRAW,
|
||||
update_screen_size,
|
||||
global,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -763,7 +751,6 @@ _shell_global_set_plugin (ShellGlobal *global,
|
||||
G_CALLBACK (global_stage_notify_width), global);
|
||||
g_signal_connect (stage, "notify::height",
|
||||
G_CALLBACK (global_stage_notify_height), global);
|
||||
update_screen_size (global);
|
||||
|
||||
g_signal_connect (stage, "paint",
|
||||
G_CALLBACK (global_stage_before_paint), global);
|
||||
@ -1111,6 +1098,38 @@ shell_global_maybe_gc (ShellGlobal *global)
|
||||
gjs_context_maybe_gc (global->js_context);
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_global_get_memory_info:
|
||||
* @global:
|
||||
* @meminfo: (out caller-allocates): Output location for memory information
|
||||
*
|
||||
* Load process-global data about memory usage.
|
||||
*/
|
||||
void
|
||||
shell_global_get_memory_info (ShellGlobal *global,
|
||||
ShellMemoryInfo *meminfo)
|
||||
{
|
||||
JSContext *context;
|
||||
|
||||
memset (meminfo, 0, sizeof (meminfo));
|
||||
#ifdef HAVE_MALLINFO
|
||||
{
|
||||
struct mallinfo info = mallinfo ();
|
||||
meminfo->glibc_uordblks = info.uordblks;
|
||||
}
|
||||
#endif
|
||||
|
||||
context = gjs_context_get_native_context (global->js_context);
|
||||
|
||||
meminfo->js_bytes = JS_GetGCParameter (JS_GetRuntime (context), JSGC_BYTES);
|
||||
|
||||
meminfo->gjs_boxed = (unsigned int) gjs_counter_boxed.value;
|
||||
meminfo->gjs_gobject = (unsigned int) gjs_counter_object.value;
|
||||
meminfo->gjs_function = (unsigned int) gjs_counter_function.value;
|
||||
meminfo->gjs_closure = (unsigned int) gjs_counter_closure.value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* shell_global_notify_error:
|
||||
* @global: a #ShellGlobal
|
||||
@ -1174,109 +1193,6 @@ void shell_global_init_xdnd (ShellGlobal *global)
|
||||
32, PropModeReplace, (const unsigned char *)&stage_win, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_global_get_monitors:
|
||||
* @global: the #ShellGlobal
|
||||
*
|
||||
* Gets a list of the bounding boxes of the active screen's monitors.
|
||||
*
|
||||
* Return value: (transfer full) (element-type Meta.Rectangle): a list
|
||||
* of monitor bounding boxes.
|
||||
*/
|
||||
GSList *
|
||||
shell_global_get_monitors (ShellGlobal *global)
|
||||
{
|
||||
MetaScreen *screen = shell_global_get_screen (global);
|
||||
GSList *monitors = NULL;
|
||||
MetaRectangle rect;
|
||||
int i;
|
||||
|
||||
for (i = meta_screen_get_n_monitors (screen) - 1; i >= 0; i--)
|
||||
{
|
||||
meta_screen_get_monitor_geometry (screen, i, &rect);
|
||||
monitors = g_slist_prepend (monitors,
|
||||
meta_rectangle_copy (&rect));
|
||||
}
|
||||
return monitors;
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_global_get_primary_monitor:
|
||||
* @global: the #ShellGlobal
|
||||
*
|
||||
* Gets the bounding box of the primary monitor (the one that the
|
||||
* panel is on).
|
||||
*
|
||||
* Return value: the bounding box of the primary monitor
|
||||
*/
|
||||
MetaRectangle *
|
||||
shell_global_get_primary_monitor (ShellGlobal *global)
|
||||
{
|
||||
MetaScreen *screen = shell_global_get_screen (global);
|
||||
MetaRectangle rect;
|
||||
gint primary = 0;
|
||||
|
||||
primary = meta_screen_get_primary_monitor (screen);
|
||||
meta_screen_get_monitor_geometry (screen, primary, &rect);
|
||||
|
||||
return meta_rectangle_copy (&rect);
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_global_get_primary_monitor_index:
|
||||
* @global: the #ShellGlobal
|
||||
*
|
||||
* Gets the index of the primary monitor (the one that the
|
||||
* panel is on).
|
||||
*
|
||||
* Return value: the index of the primary monitor
|
||||
*/
|
||||
int
|
||||
shell_global_get_primary_monitor_index (ShellGlobal *global)
|
||||
{
|
||||
MetaScreen *screen = shell_global_get_screen (global);
|
||||
|
||||
return meta_screen_get_primary_monitor (screen);
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_global_get_focus_monitor:
|
||||
* @global: the #ShellGlobal
|
||||
*
|
||||
* Gets the bounding box of the monitor containing the window that
|
||||
* currently contains the keyboard focus.
|
||||
*
|
||||
* Return value: the bounding box of the focus monitor
|
||||
*/
|
||||
MetaRectangle *
|
||||
shell_global_get_focus_monitor (ShellGlobal *global)
|
||||
{
|
||||
MetaScreen *screen = shell_global_get_screen (global);
|
||||
MetaDisplay *display = meta_screen_get_display (screen);
|
||||
MetaWindow *focus = meta_display_get_focus_window (display);
|
||||
MetaRectangle rect, wrect;
|
||||
int nmonitors, i;
|
||||
|
||||
if (focus)
|
||||
{
|
||||
meta_window_get_outer_rect (focus, &wrect);
|
||||
nmonitors = meta_screen_get_n_monitors (screen);
|
||||
|
||||
/* Find the monitor that the top-left corner of @focus is on. */
|
||||
for (i = 0; i < nmonitors; i++)
|
||||
{
|
||||
meta_screen_get_monitor_geometry (screen, i, &rect);
|
||||
|
||||
if (rect.x <= wrect.x && rect.y <= wrect.y &&
|
||||
rect.x + rect.width > wrect.x &&
|
||||
rect.y + rect.height > wrect.y)
|
||||
return meta_rectangle_copy (&rect);
|
||||
}
|
||||
}
|
||||
|
||||
return shell_global_get_primary_monitor (global);
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_global_get_pointer:
|
||||
* @global: the #ShellGlobal
|
||||
@ -1430,73 +1346,6 @@ shell_global_create_app_launch_context (ShellGlobal *global)
|
||||
return (GAppLaunchContext *)context;
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_global_set_property_mutable:
|
||||
* @global: the #ShellGlobal
|
||||
* @object: the "path" to a JS object, starting from the root object.
|
||||
* (Eg, "global.stage" or "imports.gi.Gtk.Window.prototype")
|
||||
* @property: a property on @object
|
||||
* @mutable: %TRUE or %FALSE
|
||||
*
|
||||
* If @mutable is %TRUE, this clears the "permanent" and "readonly" flags
|
||||
* on @property of @object. If @mutable is %FALSE, it sets them.
|
||||
*
|
||||
* You can use this to make it possible to modify properties that
|
||||
* would otherwise be read-only from JavaScript.
|
||||
*
|
||||
* Return value: success or failure.
|
||||
*/
|
||||
gboolean
|
||||
shell_global_set_property_mutable (ShellGlobal *global,
|
||||
const char *object,
|
||||
const char *property,
|
||||
gboolean mutable)
|
||||
{
|
||||
JSContext *context = gjs_context_get_native_context (global->js_context);
|
||||
char **parts;
|
||||
JSObject *obj;
|
||||
jsval val = JSVAL_VOID;
|
||||
int i;
|
||||
jsuint attrs;
|
||||
JSBool found;
|
||||
|
||||
JS_BeginRequest (context);
|
||||
JS_AddValueRoot (context, &val);
|
||||
|
||||
parts = g_strsplit (object, ".", -1);
|
||||
obj = JS_GetGlobalObject (context);
|
||||
for (i = 0; parts[i]; i++)
|
||||
{
|
||||
if (!JS_GetProperty (context, obj, parts[i], &val))
|
||||
{
|
||||
g_strfreev (parts);
|
||||
goto out_error;
|
||||
}
|
||||
obj = JSVAL_TO_OBJECT (val);
|
||||
}
|
||||
g_strfreev (parts);
|
||||
|
||||
if (!JS_GetPropertyAttributes (context, obj, property, &attrs, &found) || !found)
|
||||
goto out_error;
|
||||
|
||||
if (mutable)
|
||||
attrs &= ~(JSPROP_PERMANENT | JSPROP_READONLY);
|
||||
else
|
||||
attrs |= (JSPROP_PERMANENT | JSPROP_READONLY);
|
||||
|
||||
if (!JS_SetPropertyAttributes (context, obj, property, attrs, &found))
|
||||
goto out_error;
|
||||
|
||||
JS_RemoveValueRoot (context, &val);
|
||||
JS_EndRequest (context);
|
||||
return TRUE;
|
||||
out_error:
|
||||
gjs_log_exception (context, NULL);
|
||||
JS_RemoveValueRoot (context, &val);
|
||||
JS_EndRequest (context);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ShellLeisureFunction func;
|
||||
|
@ -32,10 +32,6 @@ ShellGlobal *shell_global_get (void);
|
||||
MetaScreen *shell_global_get_screen (ShellGlobal *global);
|
||||
GdkScreen *shell_global_get_gdk_screen (ShellGlobal *global);
|
||||
GList *shell_global_get_window_actors (ShellGlobal *global);
|
||||
GSList *shell_global_get_monitors (ShellGlobal *global);
|
||||
MetaRectangle *shell_global_get_primary_monitor (ShellGlobal *global);
|
||||
int shell_global_get_primary_monitor_index (ShellGlobal *global);
|
||||
MetaRectangle *shell_global_get_focus_monitor (ShellGlobal *global);
|
||||
GSettings *shell_global_get_settings (ShellGlobal *global);
|
||||
guint32 shell_global_get_current_time (ShellGlobal *global);
|
||||
|
||||
@ -90,10 +86,19 @@ void shell_global_get_pointer (ShellGlobal *global,
|
||||
void shell_global_gc (ShellGlobal *global);
|
||||
void shell_global_maybe_gc (ShellGlobal *global);
|
||||
|
||||
gboolean shell_global_set_property_mutable (ShellGlobal *global,
|
||||
const char *object,
|
||||
const char *property,
|
||||
gboolean mutable);
|
||||
typedef struct {
|
||||
guint glibc_uordblks;
|
||||
|
||||
guint js_bytes;
|
||||
|
||||
guint gjs_boxed;
|
||||
guint gjs_gobject;
|
||||
guint gjs_function;
|
||||
guint gjs_closure;
|
||||
} ShellMemoryInfo;
|
||||
|
||||
void shell_global_get_memory_info (ShellGlobal *global,
|
||||
ShellMemoryInfo *meminfo);
|
||||
|
||||
|
||||
/* Run-at-leisure API */
|
||||
|
184
src/shell-mount-operation.c
Normal file
184
src/shell-mount-operation.c
Normal file
@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Red Hat, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*
|
||||
* Author: Cosimo Cecchi <cosimoc@redhat.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#include "shell-mount-operation.h"
|
||||
|
||||
/* This is a dummy class; we would like to be able to subclass the
|
||||
* object from JS but we can't yet; the default GMountOperation impl
|
||||
* automatically calls g_mount_operation_reply(UNHANDLED) after an idle,
|
||||
* in interactive methods. We want to handle the reply outselves
|
||||
* instead, so we just override the default methods with empty ones,
|
||||
* except for ask-password, as we don't want to handle that.
|
||||
*
|
||||
* Also, we need to workaround the fact that gjs doesn't support type
|
||||
* annotations for signals yet (so we can't effectively forward e.g.
|
||||
* the GPid array to JS).
|
||||
* See https://bugzilla.gnome.org/show_bug.cgi?id=645978
|
||||
*/
|
||||
G_DEFINE_TYPE (ShellMountOperation, shell_mount_operation, G_TYPE_MOUNT_OPERATION);
|
||||
|
||||
enum {
|
||||
SHOW_PROCESSES_2,
|
||||
NUM_SIGNALS
|
||||
};
|
||||
|
||||
static guint signals[NUM_SIGNALS] = { 0, };
|
||||
|
||||
struct _ShellMountOperationPrivate {
|
||||
GArray *pids;
|
||||
gchar **choices;
|
||||
gchar *message;
|
||||
};
|
||||
|
||||
static void
|
||||
shell_mount_operation_init (ShellMountOperation *self)
|
||||
{
|
||||
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, SHELL_TYPE_MOUNT_OPERATION,
|
||||
ShellMountOperationPrivate);
|
||||
}
|
||||
|
||||
static void
|
||||
shell_mount_operation_ask_password (GMountOperation *op,
|
||||
const char *message,
|
||||
const char *default_user,
|
||||
const char *default_domain,
|
||||
GAskPasswordFlags flags)
|
||||
{
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
static void
|
||||
shell_mount_operation_ask_question (GMountOperation *op,
|
||||
const char *message,
|
||||
const char *choices[])
|
||||
{
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
static void
|
||||
shell_mount_operation_show_processes (GMountOperation *operation,
|
||||
const gchar *message,
|
||||
GArray *processes,
|
||||
const gchar *choices[])
|
||||
{
|
||||
ShellMountOperation *self = SHELL_MOUNT_OPERATION (operation);
|
||||
|
||||
if (self->priv->pids != NULL)
|
||||
{
|
||||
g_array_unref (self->priv->pids);
|
||||
self->priv->pids = NULL;
|
||||
}
|
||||
|
||||
g_free (self->priv->message);
|
||||
g_strfreev (self->priv->choices);
|
||||
|
||||
/* save the parameters */
|
||||
self->priv->pids = g_array_ref (processes);
|
||||
self->priv->choices = g_strdupv ((gchar **) choices);
|
||||
self->priv->message = g_strdup (message);
|
||||
|
||||
g_signal_emit (self, signals[SHOW_PROCESSES_2], 0);
|
||||
}
|
||||
|
||||
static void
|
||||
shell_mount_operation_finalize (GObject *obj)
|
||||
{
|
||||
ShellMountOperation *self = SHELL_MOUNT_OPERATION (obj);
|
||||
|
||||
g_strfreev (self->priv->choices);
|
||||
g_free (self->priv->message);
|
||||
|
||||
if (self->priv->pids != NULL)
|
||||
{
|
||||
g_array_unref (self->priv->pids);
|
||||
self->priv->pids = NULL;
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (shell_mount_operation_parent_class)->finalize (obj);
|
||||
}
|
||||
|
||||
static void
|
||||
shell_mount_operation_class_init (ShellMountOperationClass *klass)
|
||||
{
|
||||
GMountOperationClass *mclass;
|
||||
GObjectClass *oclass;
|
||||
|
||||
mclass = G_MOUNT_OPERATION_CLASS (klass);
|
||||
mclass->show_processes = shell_mount_operation_show_processes;
|
||||
mclass->ask_question = shell_mount_operation_ask_question;
|
||||
mclass->ask_password = shell_mount_operation_ask_password;
|
||||
|
||||
oclass = G_OBJECT_CLASS (klass);
|
||||
oclass->finalize = shell_mount_operation_finalize;
|
||||
|
||||
signals[SHOW_PROCESSES_2] =
|
||||
g_signal_new ("show-processes-2",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
0, NULL, NULL,
|
||||
g_cclosure_marshal_VOID__VOID,
|
||||
G_TYPE_NONE, 0);
|
||||
|
||||
g_type_class_add_private (klass, sizeof (ShellMountOperationPrivate));
|
||||
}
|
||||
|
||||
GMountOperation *
|
||||
shell_mount_operation_new (void)
|
||||
{
|
||||
return g_object_new (SHELL_TYPE_MOUNT_OPERATION, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_mount_operation_get_show_processes_pids:
|
||||
* @self: a #ShellMountOperation
|
||||
*
|
||||
* Returns: (transfer full) (element-type GPid): a #GArray
|
||||
*/
|
||||
GArray *
|
||||
shell_mount_operation_get_show_processes_pids (ShellMountOperation *self)
|
||||
{
|
||||
return g_array_ref (self->priv->pids);
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_mount_operation_get_show_processes_choices:
|
||||
* @self: a #ShellMountOperation
|
||||
*
|
||||
* Returns: (transfer full):
|
||||
*/
|
||||
gchar **
|
||||
shell_mount_operation_get_show_processes_choices (ShellMountOperation *self)
|
||||
{
|
||||
return g_strdupv (self->priv->choices);
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_mount_operation_get_show_processes_message:
|
||||
* @self: a #ShellMountOperation
|
||||
*
|
||||
* Returns: (transfer full):
|
||||
*/
|
||||
gchar *
|
||||
shell_mount_operation_get_show_processes_message (ShellMountOperation *self)
|
||||
{
|
||||
return g_strdup (self->priv->message);
|
||||
}
|
63
src/shell-mount-operation.h
Normal file
63
src/shell-mount-operation.h
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Red Hat, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*
|
||||
* Author: Cosimo Cecchi <cosimoc@redhat.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __SHELL_MOUNT_OPERATION_H__
|
||||
#define __SHELL_MOUNT_OPERATION_H__
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SHELL_TYPE_MOUNT_OPERATION (shell_mount_operation_get_type ())
|
||||
#define SHELL_MOUNT_OPERATION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), SHELL_TYPE_MOUNT_OPERATION, ShellMountOperation))
|
||||
#define SHELL_MOUNT_OPERATION_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), SHELL_TYPE_MOUNT_OPERATION, ShellMountOperationClass))
|
||||
#define SHELL_IS_MOUNT_OPERATION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), SHELL_TYPE_MOUNT_OPERATION))
|
||||
#define SHELL_IS_MOUNT_OPERATION_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), SHELL_TYPE_MOUNT_OPERATION))
|
||||
#define SHELL_MOUNT_OPERATION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), SHELL_TYPE_MOUNT_OPERATION, ShellMountOperationClass))
|
||||
|
||||
typedef struct _ShellMountOperation ShellMountOperation;
|
||||
typedef struct _ShellMountOperationClass ShellMountOperationClass;
|
||||
typedef struct _ShellMountOperationPrivate ShellMountOperationPrivate;
|
||||
|
||||
struct _ShellMountOperation
|
||||
{
|
||||
GMountOperation parent_instance;
|
||||
|
||||
ShellMountOperationPrivate *priv;
|
||||
};
|
||||
|
||||
struct _ShellMountOperationClass
|
||||
{
|
||||
GMountOperationClass parent_class;
|
||||
};
|
||||
|
||||
|
||||
GType shell_mount_operation_get_type (void);
|
||||
GMountOperation *shell_mount_operation_new (void);
|
||||
|
||||
GArray * shell_mount_operation_get_show_processes_pids (ShellMountOperation *self);
|
||||
gchar ** shell_mount_operation_get_show_processes_choices (ShellMountOperation *self);
|
||||
gchar * shell_mount_operation_get_show_processes_message (ShellMountOperation *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __SHELL_MOUNT_OPERATION_H__ */
|
@ -62,8 +62,6 @@ struct _ShellRecorder {
|
||||
|
||||
gboolean only_paint; /* Used to temporarily suppress recording */
|
||||
|
||||
gboolean have_pack_invert; /* True when GL_MESA_pack_invert is available */
|
||||
|
||||
int framerate;
|
||||
char *pipeline_description;
|
||||
char *filename;
|
||||
@ -200,7 +198,7 @@ create_recording_icon (void)
|
||||
|
||||
texture = cogl_texture_new_from_data (32, 32,
|
||||
COGL_TEXTURE_NONE,
|
||||
COGL_PIXEL_FORMAT_BGRA_8888,
|
||||
CLUTTER_CAIRO_FORMAT_ARGB32,
|
||||
COGL_PIXEL_FORMAT_ANY,
|
||||
cairo_image_surface_get_stride (surface),
|
||||
cairo_image_surface_get_data (surface));
|
||||
@ -414,6 +412,8 @@ recorder_fetch_cursor_image (ShellRecorder *recorder)
|
||||
*(guint32 *)(data + i * stride + 4 * j) = cursor_image->pixels[i * cursor_image->width + j];
|
||||
|
||||
cairo_surface_mark_dirty (recorder->cursor_image);
|
||||
|
||||
XFree (cursor_image);
|
||||
}
|
||||
|
||||
/* Overlay the cursor image on the frame. We draw the cursor image
|
||||
@ -450,15 +450,7 @@ recorder_draw_cursor (ShellRecorder *recorder,
|
||||
recorder->stage_height,
|
||||
recorder->stage_width * 4);
|
||||
|
||||
/* When not using GL_MESA_pack_invert the data we get from glReadPixels is "upside down",
|
||||
* so transform our cairo drawing to match */
|
||||
cr = cairo_create (surface);
|
||||
if (!recorder->have_pack_invert)
|
||||
{
|
||||
cairo_translate(cr, 0, recorder->stage_height);
|
||||
cairo_scale(cr, 1, -1);
|
||||
}
|
||||
|
||||
cairo_set_source_surface (cr,
|
||||
recorder->cursor_image,
|
||||
recorder->pointer_x - recorder->cursor_hot_x,
|
||||
@ -534,36 +526,11 @@ recorder_record_frame (ShellRecorder *recorder)
|
||||
|
||||
GST_BUFFER_TIMESTAMP(buffer) = get_wall_time() - recorder->start_time;
|
||||
|
||||
/* We could use cogl_read_pixels, but it only currently supports
|
||||
* COGL_PIXEL_FORMAT_RGBA_8888, while we prefer the native framebuffer
|
||||
* format. (COGL_PIXEL_FORMAT_ARGB_8888_PRE,
|
||||
* COGL_PIXEL_FORMAT_BGRA_PRE, depending on endianess)
|
||||
*
|
||||
* http://bugzilla.openedhand.com/show_bug.cgi?id=1959
|
||||
*/
|
||||
|
||||
/* Flush any primitives that Cogl has batched up */
|
||||
cogl_flush ();
|
||||
|
||||
/* Set the parameters for how data is stored; these are the initial
|
||||
* values but Cogl will have modified them for its own purposes */
|
||||
glPixelStorei (GL_PACK_ALIGNMENT, 4);
|
||||
glPixelStorei (GL_PACK_ROW_LENGTH, 0);
|
||||
glPixelStorei (GL_PACK_SKIP_PIXELS, 0);
|
||||
glPixelStorei (GL_PACK_SKIP_ROWS, 0);
|
||||
|
||||
if (recorder->have_pack_invert)
|
||||
glPixelStorei (GL_PACK_INVERT_MESA, TRUE);
|
||||
|
||||
glReadBuffer (GL_BACK_LEFT);
|
||||
glReadPixels (0, 0,
|
||||
recorder->stage_width, recorder->stage_height,
|
||||
GL_BGRA,
|
||||
GL_UNSIGNED_INT_8_8_8_8_REV,
|
||||
data);
|
||||
|
||||
if (recorder->have_pack_invert)
|
||||
glPixelStorei (GL_PACK_INVERT_MESA, FALSE);
|
||||
cogl_read_pixels (0, 0,
|
||||
recorder->stage_width, recorder->stage_height,
|
||||
COGL_READ_PIXELS_COLOR_BUFFER,
|
||||
CLUTTER_CAIRO_FORMAT_ARGB32,
|
||||
data);
|
||||
|
||||
recorder_draw_cursor (recorder, buffer);
|
||||
|
||||
@ -866,7 +833,6 @@ recorder_set_stage (ShellRecorder *recorder,
|
||||
if (recorder->stage)
|
||||
{
|
||||
int error_base;
|
||||
const char *gl_extensions;
|
||||
|
||||
recorder->stage = stage;
|
||||
g_signal_connect (recorder->stage, "destroy",
|
||||
@ -891,8 +857,6 @@ recorder_set_stage (ShellRecorder *recorder,
|
||||
XFixesDisplayCursorNotifyMask);
|
||||
|
||||
clutter_stage_ensure_current (stage);
|
||||
gl_extensions = (const char *)glGetString (GL_EXTENSIONS);
|
||||
recorder->have_pack_invert = strstr (gl_extensions, "GL_MESA_pack_invert") != NULL;
|
||||
|
||||
recorder_get_initial_cursor_position (recorder);
|
||||
}
|
||||
@ -1093,8 +1057,6 @@ recorder_pipeline_add_source (RecorderPipeline *pipeline)
|
||||
GstPad *sink_pad = NULL, *src_pad = NULL;
|
||||
gboolean result = FALSE;
|
||||
GstElement *ffmpegcolorspace;
|
||||
GstElement *videoflip;
|
||||
GError *error = NULL;
|
||||
|
||||
sink_pad = gst_bin_find_unlinked_pad (GST_BIN (pipeline->pipeline), GST_PAD_SINK);
|
||||
if (sink_pad == NULL)
|
||||
@ -1124,38 +1086,8 @@ recorder_pipeline_add_source (RecorderPipeline *pipeline)
|
||||
}
|
||||
gst_bin_add (GST_BIN (pipeline->pipeline), ffmpegcolorspace);
|
||||
|
||||
/* glReadPixels gives us an upside-down buffer, so we have to flip it back
|
||||
* right-side up.
|
||||
*
|
||||
* When available MESA_pack_invert extension is used to avoid the
|
||||
* flip entirely, since the data is actually stored in the frame buffer
|
||||
* in the order that we expect.
|
||||
*
|
||||
* We use gst_parse_launch to avoid having to know the enum value for flip-vertical
|
||||
*/
|
||||
|
||||
if (!pipeline->recorder->have_pack_invert)
|
||||
{
|
||||
videoflip = gst_parse_launch_full ("videoflip method=vertical-flip", NULL,
|
||||
GST_PARSE_FLAG_FATAL_ERRORS,
|
||||
&error);
|
||||
if (videoflip == NULL)
|
||||
{
|
||||
g_warning("Can't create videoflip element: %s", error->message);
|
||||
g_error_free (error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
gst_bin_add (GST_BIN (pipeline->pipeline), videoflip);
|
||||
gst_element_link_many (pipeline->src, ffmpegcolorspace, videoflip, NULL);
|
||||
|
||||
src_pad = gst_element_get_static_pad (videoflip, "src");
|
||||
}
|
||||
else
|
||||
{
|
||||
gst_element_link_many (pipeline->src, ffmpegcolorspace, NULL);
|
||||
src_pad = gst_element_get_static_pad (ffmpegcolorspace, "src");
|
||||
}
|
||||
gst_element_link_many (pipeline->src, ffmpegcolorspace, NULL);
|
||||
src_pad = gst_element_get_static_pad (ffmpegcolorspace, "src");
|
||||
|
||||
if (!src_pad)
|
||||
{
|
||||
|
@ -11,6 +11,14 @@ struct _ShellTpClientPrivate
|
||||
ShellTpClientObserveChannelsImpl observe_impl;
|
||||
gpointer user_data_obs;
|
||||
GDestroyNotify destroy_obs;
|
||||
|
||||
ShellTpClientApproveChannelsImpl approve_channels_impl;
|
||||
gpointer user_data_approve_channels;
|
||||
GDestroyNotify destroy_approve_channels;
|
||||
|
||||
ShellTpClientHandleChannelsImpl handle_channels_impl;
|
||||
gpointer user_data_handle_channels;
|
||||
GDestroyNotify destroy_handle_channels;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -32,23 +40,97 @@ struct _ShellTpClientPrivate
|
||||
* Signature of the implementation of the ObserveChannels method.
|
||||
*/
|
||||
|
||||
/**
|
||||
* ShellTpClientApproveChannelsImpl:
|
||||
* @client: a #ShellTpClient instance
|
||||
* @account: a #TpAccount having %TP_ACCOUNT_FEATURE_CORE prepared if possible
|
||||
* @connection: a #TpConnection having %TP_CONNECTION_FEATURE_CORE prepared
|
||||
* if possible
|
||||
* @channels: (element-type TelepathyGLib.Channel): a #GList of #TpChannel,
|
||||
* all having %TP_CHANNEL_FEATURE_CORE prepared if possible
|
||||
* @dispatch_operation: (allow-none): a #TpChannelDispatchOperation or %NULL;
|
||||
* the dispatch_operation is not guaranteed to be prepared
|
||||
* @context: a #TpAddDispatchOperationContext representing the context of this
|
||||
* D-Bus call
|
||||
*
|
||||
* Signature of the implementation of the AddDispatchOperation method.
|
||||
*/
|
||||
|
||||
/**
|
||||
* ShellTpClientHandleChannelsImpl:
|
||||
* @client: a #ShellTpClient instance
|
||||
* @account: a #TpAccount having %TP_ACCOUNT_FEATURE_CORE prepared if possible
|
||||
* @connection: a #TpConnection having %TP_CONNECTION_FEATURE_CORE prepared
|
||||
* if possible
|
||||
* @channels: (element-type TelepathyGLib.Channel): a #GList of #TpChannel,
|
||||
* all having %TP_CHANNEL_FEATURE_CORE prepared if possible
|
||||
* @requests_satisfied: (element-type TelepathyGLib.ChannelRequest): a #GList of
|
||||
* #TpChannelRequest having their object-path defined but are not guaranteed
|
||||
* to be prepared.
|
||||
* @user_action_time: the time at which user action occurred, or one of the
|
||||
* special values %TP_USER_ACTION_TIME_NOT_USER_ACTION or
|
||||
* %TP_USER_ACTION_TIME_CURRENT_TIME
|
||||
* (see #TpAccountChannelRequest:user-action-time for details)
|
||||
* @context: a #TpHandleChannelsContext representing the context of this
|
||||
* D-Bus call
|
||||
*
|
||||
* Signature of the implementation of the HandleChannels method.
|
||||
*/
|
||||
|
||||
static void
|
||||
shell_tp_client_init (ShellTpClient *self)
|
||||
{
|
||||
GHashTable *filter;
|
||||
|
||||
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, SHELL_TYPE_TP_CLIENT,
|
||||
ShellTpClientPrivate);
|
||||
|
||||
/* We only care about single-user text-based chats */
|
||||
filter = tp_asv_new (
|
||||
TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
|
||||
TP_IFACE_CHANNEL_TYPE_TEXT,
|
||||
TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
|
||||
TP_HANDLE_TYPE_CONTACT,
|
||||
NULL);
|
||||
|
||||
/* Observer */
|
||||
tp_base_client_set_observer_recover (TP_BASE_CLIENT (self), TRUE);
|
||||
|
||||
/* We only care about single-user text-based chats */
|
||||
tp_base_client_take_observer_filter (TP_BASE_CLIENT (self),
|
||||
tp_base_client_add_observer_filter (TP_BASE_CLIENT (self), filter);
|
||||
|
||||
/* Approver */
|
||||
tp_base_client_add_approver_filter (TP_BASE_CLIENT (self), filter);
|
||||
|
||||
/* Approve room invitations. We don't handle or observe room channels so
|
||||
* just register this filter for the approver. */
|
||||
tp_base_client_take_approver_filter (TP_BASE_CLIENT (self), tp_asv_new (
|
||||
TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
|
||||
TP_IFACE_CHANNEL_TYPE_TEXT,
|
||||
TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
|
||||
TP_HANDLE_TYPE_ROOM,
|
||||
NULL));
|
||||
|
||||
/* Approve calls (StreameMedia and Call.DRAFT). We let Empathy handle the
|
||||
* call itself. */
|
||||
tp_base_client_take_approver_filter (TP_BASE_CLIENT (self),
|
||||
tp_asv_new (
|
||||
TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
|
||||
TP_IFACE_CHANNEL_TYPE_TEXT,
|
||||
TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
|
||||
TP_HANDLE_TYPE_CONTACT,
|
||||
TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
|
||||
TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
|
||||
NULL));
|
||||
|
||||
/* FIXME: use TP_IFACE_CHANNEL_TYPE_CALL once API is undrafted (fdo #24936) */
|
||||
tp_base_client_take_approver_filter (TP_BASE_CLIENT (self),
|
||||
tp_asv_new (
|
||||
TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
|
||||
"org.freedesktop.Telepathy.Channel.Type.Call.DRAFT",
|
||||
TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
|
||||
NULL));
|
||||
|
||||
/* Handler */
|
||||
tp_base_client_add_handler_filter (TP_BASE_CLIENT (self), filter);
|
||||
|
||||
g_hash_table_unref (filter);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -68,6 +150,40 @@ observe_channels (TpBaseClient *client,
|
||||
dispatch_operation, requests, context, self->priv->user_data_obs);
|
||||
}
|
||||
|
||||
static void
|
||||
add_dispatch_operation (TpBaseClient *client,
|
||||
TpAccount *account,
|
||||
TpConnection *connection,
|
||||
GList *channels,
|
||||
TpChannelDispatchOperation *dispatch_operation,
|
||||
TpAddDispatchOperationContext *context)
|
||||
{
|
||||
ShellTpClient *self = (ShellTpClient *) client;
|
||||
|
||||
g_assert (self->priv->approve_channels_impl != NULL);
|
||||
|
||||
self->priv->approve_channels_impl (self, account, connection, channels,
|
||||
dispatch_operation, context, self->priv->user_data_approve_channels);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_channels (TpBaseClient *client,
|
||||
TpAccount *account,
|
||||
TpConnection *connection,
|
||||
GList *channels,
|
||||
GList *requests_satisfied,
|
||||
gint64 user_action_time,
|
||||
TpHandleChannelsContext *context)
|
||||
{
|
||||
ShellTpClient *self = (ShellTpClient *) client;
|
||||
|
||||
g_assert (self->priv->handle_channels_impl != NULL);
|
||||
|
||||
self->priv->handle_channels_impl (self, account, connection, channels,
|
||||
requests_satisfied, user_action_time, context,
|
||||
self->priv->user_data_handle_channels);
|
||||
}
|
||||
|
||||
static void
|
||||
shell_tp_client_dispose (GObject *object)
|
||||
{
|
||||
@ -79,6 +195,21 @@ shell_tp_client_dispose (GObject *object)
|
||||
{
|
||||
self->priv->destroy_obs (self->priv->user_data_obs);
|
||||
self->priv->destroy_obs = NULL;
|
||||
self->priv->user_data_obs = NULL;
|
||||
}
|
||||
|
||||
if (self->priv->destroy_approve_channels != NULL)
|
||||
{
|
||||
self->priv->destroy_approve_channels (self->priv->user_data_approve_channels);
|
||||
self->priv->destroy_approve_channels = NULL;
|
||||
self->priv->user_data_approve_channels = NULL;
|
||||
}
|
||||
|
||||
if (self->priv->destroy_handle_channels != NULL)
|
||||
{
|
||||
self->priv->destroy_handle_channels (self->priv->user_data_handle_channels);
|
||||
self->priv->destroy_handle_channels = NULL;
|
||||
self->priv->user_data_handle_channels = NULL;
|
||||
}
|
||||
|
||||
if (dispose != NULL)
|
||||
@ -96,22 +227,8 @@ shell_tp_client_class_init (ShellTpClientClass *cls)
|
||||
object_class->dispose = shell_tp_client_dispose;
|
||||
|
||||
base_clt_cls->observe_channels = observe_channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_tp_client_new:
|
||||
* @dbus: a #TpDBusDaemon object, may not be %NULL
|
||||
*
|
||||
* Returns: a new #ShellTpClient
|
||||
*/
|
||||
ShellTpClient *
|
||||
shell_tp_client_new (TpDBusDaemon *dbus)
|
||||
{
|
||||
return g_object_new (SHELL_TYPE_TP_CLIENT,
|
||||
"dbus-daemon", dbus,
|
||||
"name", "GnomeShell",
|
||||
"uniquify-name", TRUE,
|
||||
NULL);
|
||||
base_clt_cls->add_dispatch_operation = add_dispatch_operation;
|
||||
base_clt_cls->handle_channels = handle_channels;
|
||||
}
|
||||
|
||||
void
|
||||
@ -127,6 +244,32 @@ shell_tp_client_set_observe_channels_func (ShellTpClient *self,
|
||||
self->priv->destroy_obs = destroy;
|
||||
}
|
||||
|
||||
void
|
||||
shell_tp_client_set_approve_channels_func (ShellTpClient *self,
|
||||
ShellTpClientApproveChannelsImpl approve_channels_impl,
|
||||
gpointer user_data,
|
||||
GDestroyNotify destroy)
|
||||
{
|
||||
g_assert (self->priv->approve_channels_impl == NULL);
|
||||
|
||||
self->priv->approve_channels_impl = approve_channels_impl;
|
||||
self->priv->user_data_approve_channels = user_data;
|
||||
self->priv->destroy_approve_channels = destroy;
|
||||
}
|
||||
|
||||
void
|
||||
shell_tp_client_set_handle_channels_func (ShellTpClient *self,
|
||||
ShellTpClientHandleChannelsImpl handle_channels_impl,
|
||||
gpointer user_data,
|
||||
GDestroyNotify destroy)
|
||||
{
|
||||
g_assert (self->priv->handle_channels_impl == NULL);
|
||||
|
||||
self->priv->handle_channels_impl = handle_channels_impl;
|
||||
self->priv->user_data_handle_channels = user_data;
|
||||
self->priv->destroy_handle_channels = destroy;
|
||||
}
|
||||
|
||||
/* Telepathy utility functions */
|
||||
|
||||
/**
|
||||
@ -254,3 +397,15 @@ shell_get_contact_events (TplLogManager *log_manager,
|
||||
NULL, NULL,
|
||||
callback, NULL);
|
||||
}
|
||||
|
||||
/* gjs doesn't allow us to craft a GError so we need a C wrapper */
|
||||
void
|
||||
shell_decline_dispatch_op (TpAddDispatchOperationContext *context,
|
||||
const gchar *message)
|
||||
{
|
||||
GError *error = g_error_new_literal (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
|
||||
message);
|
||||
|
||||
tp_add_dispatch_operation_context_fail (context, error);
|
||||
g_error_free (error);
|
||||
}
|
||||
|
@ -51,13 +51,40 @@ typedef void (*ShellTpClientObserveChannelsImpl) (ShellTpClient *client,
|
||||
TpObserveChannelsContext *context,
|
||||
gpointer user_data);
|
||||
|
||||
ShellTpClient * shell_tp_client_new (TpDBusDaemon *dbus);
|
||||
|
||||
void shell_tp_client_set_observe_channels_func (ShellTpClient *self,
|
||||
ShellTpClientObserveChannelsImpl observe_impl,
|
||||
gpointer user_data,
|
||||
GDestroyNotify destroy);
|
||||
|
||||
typedef void (*ShellTpClientApproveChannelsImpl) (
|
||||
ShellTpClient *client,
|
||||
TpAccount *account,
|
||||
TpConnection *connection,
|
||||
GList *channels,
|
||||
TpChannelDispatchOperation *dispatch_operation,
|
||||
TpAddDispatchOperationContext *context,
|
||||
gpointer user_data);
|
||||
|
||||
void shell_tp_client_set_approve_channels_func (ShellTpClient *self,
|
||||
ShellTpClientApproveChannelsImpl approve_impl,
|
||||
gpointer user_data,
|
||||
GDestroyNotify destroy);
|
||||
|
||||
typedef void (*ShellTpClientHandleChannelsImpl) (
|
||||
ShellTpClient *client,
|
||||
TpAccount *account,
|
||||
TpConnection *connection,
|
||||
GList *channels,
|
||||
GList *requests_satisfied,
|
||||
gint64 user_action_time,
|
||||
TpHandleChannelsContext *context,
|
||||
gpointer user_data);
|
||||
|
||||
void shell_tp_client_set_handle_channels_func (ShellTpClient *self,
|
||||
ShellTpClientHandleChannelsImpl handle_channels_impl,
|
||||
gpointer user_data,
|
||||
GDestroyNotify destroy);
|
||||
|
||||
/* Telepathy utility functions */
|
||||
typedef void (*ShellGetTpContactCb) (TpConnection *connection,
|
||||
GList *contacts,
|
||||
@ -84,5 +111,8 @@ void shell_get_contact_events (TplLogManager *log_manager,
|
||||
guint num_events,
|
||||
GAsyncReadyCallback callback);
|
||||
|
||||
void shell_decline_dispatch_op (TpAddDispatchOperationContext *context,
|
||||
const gchar *message);
|
||||
|
||||
G_END_DECLS
|
||||
#endif /* __SHELL_TP_CLIENT_H__ */
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
#include "shell-util.h"
|
||||
#include <glib/gi18n-lib.h>
|
||||
#include <gconf/gconf-client.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include <libxml/parser.h>
|
||||
@ -72,7 +71,8 @@ shell_util_get_file_display_name_if_mount (GFile *file)
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define HOME_NAME_KEY "/apps/nautilus/desktop/home_icon_name"
|
||||
#define HOME_NAME_SCHEMA "org.gnome.nautilus.desktop"
|
||||
#define HOME_NAME_KEY "home-icon-name"
|
||||
static char *
|
||||
shell_util_get_file_display_for_common_files (GFile *file)
|
||||
{
|
||||
@ -81,20 +81,23 @@ shell_util_get_file_display_for_common_files (GFile *file)
|
||||
compare = g_file_new_for_path (g_get_home_dir ());
|
||||
if (g_file_equal (file, compare))
|
||||
{
|
||||
char *gconf_name;
|
||||
GSettings *settings;
|
||||
char *name;
|
||||
|
||||
g_object_unref (compare);
|
||||
|
||||
gconf_name = gconf_client_get_string (gconf_client_get_default (),
|
||||
HOME_NAME_KEY, NULL);
|
||||
if (!(gconf_name && gconf_name[0]))
|
||||
settings = g_settings_new (HOME_NAME_SCHEMA);
|
||||
name = g_settings_get_string (settings, HOME_NAME_KEY);
|
||||
g_object_unref (settings);
|
||||
|
||||
if (!(name && name[0]))
|
||||
{
|
||||
g_free (gconf_name);
|
||||
g_free (name);
|
||||
return g_strdup (_("Home Folder"));
|
||||
}
|
||||
else
|
||||
{
|
||||
return gconf_name;
|
||||
return name;
|
||||
}
|
||||
}
|
||||
g_object_unref (compare);
|
||||
@ -557,6 +560,29 @@ shell_get_event_state (ClutterEvent *event)
|
||||
return state & CLUTTER_MODIFIER_MASK;
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_write_soup_message_to_stream:
|
||||
* @stream: a #GOutputStream
|
||||
* @message: a #SoupMessage
|
||||
* @error: location to store GError
|
||||
*
|
||||
* Write a string to a GOutputStream as binary data. This is a
|
||||
* workaround for the lack of proper binary strings in GJS.
|
||||
*/
|
||||
void
|
||||
shell_write_soup_message_to_stream (GOutputStream *stream,
|
||||
SoupMessage *message,
|
||||
GError **error)
|
||||
{
|
||||
SoupMessageBody *body;
|
||||
|
||||
body = message->response_body;
|
||||
|
||||
g_output_stream_write_all (stream,
|
||||
body->data, body->length,
|
||||
NULL, NULL, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_write_string_to_stream:
|
||||
* @stream: a #GOutputStream
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <clutter/clutter.h>
|
||||
#include <libsoup/soup.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
@ -25,6 +26,10 @@ char *shell_util_format_date (const char *format,
|
||||
ClutterModifierType
|
||||
shell_get_event_state (ClutterEvent *event);
|
||||
|
||||
void shell_write_soup_message_to_stream (GOutputStream *stream,
|
||||
SoupMessage *message,
|
||||
GError **error);
|
||||
|
||||
gboolean shell_write_string_to_stream (GOutputStream *stream,
|
||||
const char *str,
|
||||
GError **error);
|
||||
|
@ -41,24 +41,6 @@
|
||||
* have it also track through startup-notification.
|
||||
*/
|
||||
|
||||
/* Title patterns to detect apps that don't set WM class as needed.
|
||||
* Format: application ID, title regex pattern, NULL (for GRegex) */
|
||||
static struct
|
||||
{
|
||||
const char *app_id;
|
||||
const char *pattern;
|
||||
GRegex *regex;
|
||||
} title_patterns[] = {
|
||||
{"mozilla-firefox.desktop", ".* - Mozilla Firefox", NULL}, \
|
||||
{"openoffice.org-writer.desktop", ".* - OpenOffice.org Writer$", NULL}, \
|
||||
{"openoffice.org-calc.desktop", ".* - OpenOffice.org Calc$", NULL}, \
|
||||
{"openoffice.org-impress.desktop", ".* - OpenOffice.org Impress$", NULL}, \
|
||||
{"openoffice.org-draw.desktop", ".* - OpenOffice.org Draw$", NULL}, \
|
||||
{"openoffice.org-base.desktop", ".* - OpenOffice.org Base$", NULL}, \
|
||||
{"openoffice.org-math.desktop", ".* - OpenOffice.org Math$", NULL}, \
|
||||
{NULL, NULL, NULL}
|
||||
};
|
||||
|
||||
struct _ShellWindowTracker
|
||||
{
|
||||
GObject parent;
|
||||
@ -160,47 +142,6 @@ shell_window_tracker_class_init (ShellWindowTrackerClass *klass)
|
||||
G_TYPE_NONE, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* get_app_id_from_title:
|
||||
*
|
||||
* Use a window's "title" property to determine an application ID.
|
||||
* This is a temporary crutch for a few applications until we get
|
||||
* them correctly setting their WM_CLASS.
|
||||
*/
|
||||
static const char *
|
||||
get_app_id_from_title (MetaWindow *window)
|
||||
{
|
||||
static gboolean patterns_initialized = FALSE;
|
||||
const char *title;
|
||||
int i;
|
||||
|
||||
title = meta_window_get_title (window);
|
||||
|
||||
if (!patterns_initialized) /* Generate match patterns once for all */
|
||||
{
|
||||
patterns_initialized = TRUE;
|
||||
for (i = 0; title_patterns[i].app_id; i++)
|
||||
{
|
||||
title_patterns[i].regex = g_regex_new (title_patterns[i].pattern,
|
||||
0, 0, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Match window title patterns to identifiers for non-standard apps */
|
||||
if (title)
|
||||
{
|
||||
for (i = 0; title_patterns[i].app_id; i++)
|
||||
{
|
||||
if (g_regex_match (title_patterns[i].regex, title, 0, NULL))
|
||||
{
|
||||
/* Matched, return the app id we want */
|
||||
return title_patterns[i].app_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_appid_from_window:
|
||||
*
|
||||
@ -305,14 +246,6 @@ get_app_from_window_wmclass (MetaWindow *window)
|
||||
app = shell_app_system_lookup_heuristic_basename (appsys, with_desktop);
|
||||
g_free (with_desktop);
|
||||
|
||||
if (app == NULL)
|
||||
{
|
||||
const char *id = get_app_id_from_title (window);
|
||||
|
||||
if (id != NULL)
|
||||
app = shell_app_system_get_app (appsys, id);
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
@ -480,32 +413,30 @@ _shell_window_tracker_get_app_context (ShellWindowTracker *tracker, ShellApp *ap
|
||||
}
|
||||
|
||||
static void
|
||||
on_transient_window_title_changed (MetaWindow *window,
|
||||
GParamSpec *spec,
|
||||
ShellWindowTracker *self)
|
||||
update_focus_app (ShellWindowTracker *self)
|
||||
{
|
||||
ShellAppSystem *appsys;
|
||||
ShellApp *app;
|
||||
const char *id;
|
||||
MetaWindow *new_focus_win;
|
||||
ShellApp *new_focus_app;
|
||||
|
||||
/* Check if we now have a mapping using the window title */
|
||||
id = get_app_id_from_title (window);
|
||||
if (id == NULL)
|
||||
return;
|
||||
new_focus_win = meta_display_get_focus_window (meta_screen_get_display (shell_global_get_screen (shell_global_get ())));
|
||||
new_focus_app = new_focus_win ? shell_window_tracker_get_window_app (self, new_focus_win) : NULL;
|
||||
|
||||
appsys = shell_app_system_get_default ();
|
||||
app = shell_app_system_get_app (appsys, id);
|
||||
if (app == NULL)
|
||||
return;
|
||||
g_object_unref (app);
|
||||
set_focus_app (self, new_focus_app);
|
||||
}
|
||||
|
||||
/* We found an app, don't listen for further title changes */
|
||||
g_signal_handlers_disconnect_by_func (window, G_CALLBACK (on_transient_window_title_changed),
|
||||
self);
|
||||
static void
|
||||
on_wm_class_changed (MetaWindow *window,
|
||||
GParamSpec *pspec,
|
||||
gpointer user_data)
|
||||
{
|
||||
ShellWindowTracker *self = SHELL_WINDOW_TRACKER (user_data);
|
||||
|
||||
/* It's simplest to just treat this as a remove + add. */
|
||||
disassociate_window (self, window);
|
||||
track_window (self, window);
|
||||
/* also just recaulcuate the focused app, in case it was the focused
|
||||
window that changed */
|
||||
update_focus_app (self);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -524,15 +455,7 @@ track_window (ShellWindowTracker *self,
|
||||
/* At this point we've stored the association from window -> application */
|
||||
g_hash_table_insert (self->window_to_app, window, app);
|
||||
|
||||
if (shell_app_is_transient (app))
|
||||
{
|
||||
/* For a transient application, it's possible one of our title regexps
|
||||
* will match at a later time, i.e. the application may not have set
|
||||
* its title fully at the time it initially maps a window. Watch
|
||||
* for title changes and recompute the app.
|
||||
*/
|
||||
g_signal_connect (window, "notify::title", G_CALLBACK (on_transient_window_title_changed), self);
|
||||
}
|
||||
g_signal_connect (window, "notify::wm-class", G_CALLBACK (on_wm_class_changed), self);
|
||||
|
||||
_shell_app_add_window (app, window);
|
||||
|
||||
@ -564,7 +487,10 @@ disassociate_window (ShellWindowTracker *self,
|
||||
g_hash_table_remove (self->window_to_app, window);
|
||||
|
||||
if (shell_window_tracker_is_window_interesting (window))
|
||||
{
|
||||
_shell_app_remove_window (app, window);
|
||||
g_signal_handlers_disconnect_by_func (window, G_CALLBACK(on_wm_class_changed), self);
|
||||
}
|
||||
|
||||
g_signal_emit (self, signals[TRACKED_WINDOWS_CHANGED], 0);
|
||||
|
||||
@ -709,15 +635,11 @@ static void
|
||||
shell_window_tracker_finalize (GObject *object)
|
||||
{
|
||||
ShellWindowTracker *self = SHELL_WINDOW_TRACKER (object);
|
||||
int i;
|
||||
|
||||
g_hash_table_destroy (self->running_apps);
|
||||
g_hash_table_destroy (self->window_to_app);
|
||||
g_hash_table_destroy (self->launched_pid_to_app);
|
||||
|
||||
for (i = 0; title_patterns[i].app_id; i++)
|
||||
g_regex_unref (title_patterns[i].regex);
|
||||
|
||||
G_OBJECT_CLASS (shell_window_tracker_parent_class)->finalize(object);
|
||||
}
|
||||
|
||||
@ -884,13 +806,7 @@ on_focus_window_changed (MetaDisplay *display,
|
||||
GParamSpec *spec,
|
||||
ShellWindowTracker *tracker)
|
||||
{
|
||||
MetaWindow *new_focus_win;
|
||||
ShellApp *new_focus_app;
|
||||
|
||||
new_focus_win = meta_display_get_focus_window (display);
|
||||
new_focus_app = new_focus_win ? shell_window_tracker_get_window_app (tracker, new_focus_win) : NULL;
|
||||
|
||||
set_focus_app (tracker, new_focus_app);
|
||||
update_focus_app (tracker);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -944,7 +860,6 @@ shell_startup_sequence_get_id (ShellStartupSequence *sequence)
|
||||
ShellApp *
|
||||
shell_startup_sequence_get_app (ShellStartupSequence *sequence)
|
||||
{
|
||||
#ifdef HAVE_SN_STARTUP_SEQUENCE_GET_APPLICATION_ID
|
||||
const char *appid;
|
||||
ShellAppSystem *appsys;
|
||||
ShellApp *app;
|
||||
@ -956,9 +871,6 @@ shell_startup_sequence_get_app (ShellStartupSequence *sequence)
|
||||
appsys = shell_app_system_get_default ();
|
||||
app = shell_app_system_get_app_for_path (appsys, appid);
|
||||
return app;
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
const char *
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user