Compare commits
2083 Commits
wip/classi
...
wip/deskto
Author | SHA1 | Date | |
---|---|---|---|
45797a977b | |||
884b94233e | |||
74d3e3139f | |||
77b5385cc3 | |||
d749d646be | |||
fa8224d7b5 | |||
f687197ccc | |||
28a6aefb6c | |||
96c2a90e11 | |||
63cf46e49b | |||
200a9ef1af | |||
6050ca6e0c | |||
8bd7db9227 | |||
982feb85c1 | |||
f165cc23c0 | |||
8737b06559 | |||
15ab285174 | |||
3a4782cc64 | |||
0256a6d47b | |||
8b0e846e0e | |||
41acb5d3cc | |||
a2f9b8ea9b | |||
6237a1c505 | |||
7c08db0b0f | |||
df1270ac49 | |||
46edc053d4 | |||
908046c31a | |||
8380c79875 | |||
8a4879a96a | |||
cdf1a77f08 | |||
3f9857ccbd | |||
1d65a31420 | |||
dafdf0838a | |||
f9cf135f68 | |||
a1878e54c9 | |||
95e5d899a9 | |||
ee8321df67 | |||
4918213e68 | |||
ed7f349fc6 | |||
2888f22a24 | |||
fcb217f681 | |||
9ffa9fe1a8 | |||
905020c507 | |||
02f5500641 | |||
465af55d6e | |||
80a3bb85aa | |||
ea26bd3003 | |||
508a511d2a | |||
2d80cb71db | |||
e31693bbee | |||
fb561f10a7 | |||
0c57d53e03 | |||
3b1b9f589b | |||
ac8d39acf4 | |||
664e795217 | |||
82bf323f63 | |||
547ac85113 | |||
46e0e4430d | |||
609a31ea46 | |||
3e99eb10d1 | |||
b9d935af0c | |||
31d3e82aa8 | |||
dfdc17197b | |||
62b965b4b7 | |||
ba221abea5 | |||
aa026c7134 | |||
496cfff97a | |||
ccaa7f5f3e | |||
f492d21c70 | |||
1983541f8c | |||
e4cb3672b9 | |||
a06a78a9c1 | |||
2ba91ad837 | |||
66eb3ea723 | |||
d30e992b20 | |||
c6a342563f | |||
fd584eda05 | |||
4301506590 | |||
88f7c3a970 | |||
f21a9f0cc2 | |||
2233c2e618 | |||
6264419bd4 | |||
b62effb8fa | |||
c8a07dd612 | |||
02c99e4b25 | |||
1242a16265 | |||
a89fd17b8e | |||
3d5e7bd6f1 | |||
cefcb89487 | |||
005272bde9 | |||
491e60e4f2 | |||
e5f72fd302 | |||
5f21b100b8 | |||
1a4c7629c5 | |||
d21734ee47 | |||
e140e2c367 | |||
7ced1f5b54 | |||
54b028ee3e | |||
703336e1ea | |||
9e936252ae | |||
fc71a0f081 | |||
86c72fa15d | |||
6ba5af1e9e | |||
33a4f59cfb | |||
39134f0d9b | |||
30e7440851 | |||
be1a7bac7c | |||
fb52a93a28 | |||
efdf1ff755 | |||
2c00dad211 | |||
c23786c73e | |||
7f1b07b76f | |||
2f35ad6e65 | |||
159c7d34c7 | |||
fe8e990ed7 | |||
1fb9b18cb6 | |||
5c2586127b | |||
661b266b45 | |||
98af044196 | |||
8006c336f5 | |||
bdf07d2ce8 | |||
efcf858e60 | |||
93d9c16672 | |||
7aaf261f5a | |||
5eb4450012 | |||
49c8cdd8f6 | |||
c860b96a86 | |||
69403bda80 | |||
f5456b66ff | |||
28b4c413cb | |||
5b97250bb1 | |||
5d26c29eaa | |||
613944eccd | |||
8d0e8fc021 | |||
41ee70d414 | |||
3691e8ddd7 | |||
be54e94045 | |||
366ca72342 | |||
fc4e392ac1 | |||
507be35d3a | |||
5c0d62cd0e | |||
7b7c4568b2 | |||
f38091d96b | |||
7c78e1fbf5 | |||
72f0a48fac | |||
193f872ebe | |||
c3f96cf0e8 | |||
df09109d81 | |||
662cb9e2a3 | |||
daa54a3798 | |||
f035a1a0e0 | |||
2688bf3333 | |||
4095a58eb9 | |||
c1b1ebe97e | |||
a47b97d443 | |||
df5d5583eb | |||
79e764d5ec | |||
2fcb04e5b2 | |||
da1e264687 | |||
03975287d2 | |||
50a61b38f7 | |||
1fe072471e | |||
93e840295e | |||
6ab7d640f0 | |||
255cb8edb1 | |||
367fb32493 | |||
ef6d1fd6ce | |||
3e8ab0645b | |||
135727c9f7 | |||
c58448817b | |||
8ae0f1a9dc | |||
ba9c1d98f6 | |||
4db6e70f97 | |||
9d1f789937 | |||
11c2933e23 | |||
fbd4951ea7 | |||
744749f2f3 | |||
db1c65970b | |||
2d8ed4c77f | |||
9ba970b83d | |||
954d262d67 | |||
1b6090fe13 | |||
f8234b07f8 | |||
25318f696d | |||
1ab3d12bc7 | |||
d66e0a0b45 | |||
d46ceead04 | |||
1cc9480e56 | |||
c022b541f1 | |||
96588466d4 | |||
dbde12f8bf | |||
660f0fec16 | |||
fd9401cc62 | |||
1edb9f7525 | |||
15cfb9d1d9 | |||
da6744da2d | |||
bd5aa66a5f | |||
7c30fe7738 | |||
8ce599df38 | |||
75fe13f1df | |||
8ad6ded3ec | |||
38d22c47f5 | |||
956b6b89b6 | |||
f27c2e6813 | |||
d35c9f880a | |||
d62aacf301 | |||
716ea64212 | |||
c9d6b13f6a | |||
b437e68026 | |||
1dfc38d078 | |||
387184b052 | |||
beec47d7ad | |||
6b554337ff | |||
08f95264d6 | |||
2802920e93 | |||
b04c47c15f | |||
56d96383e2 | |||
f2cbf846e7 | |||
0088e94293 | |||
a03a077e3d | |||
85d2b9e32a | |||
aa6471b3cc | |||
b462a85c43 | |||
9d8f30f955 | |||
420db828e9 | |||
fd8def705d | |||
39c4fa1bf0 | |||
32b964e9b7 | |||
2980515c85 | |||
36bee16781 | |||
4f5d3e00db | |||
6fb044f351 | |||
b403845d03 | |||
9d0e00acce | |||
d5afe8f4f2 | |||
3eb5ca3653 | |||
db07aa42ea | |||
081f51b9eb | |||
38d9c16aba | |||
392a426ddf | |||
d77b2751a6 | |||
3b28308291 | |||
574ecb5ad4 | |||
7a57a780d8 | |||
cf9842433e | |||
c6d089d701 | |||
ec37e2d2b5 | |||
e68b648a33 | |||
56179d8a54 | |||
47d232f694 | |||
fc26fb2149 | |||
92d828b04e | |||
6f9dc913d4 | |||
9ea0f7255f | |||
938628a05f | |||
a765dfc52e | |||
d58f0646cf | |||
792b963bda | |||
9a8bf3b881 | |||
1e02081cd2 | |||
3f24a87034 | |||
dd9f8021ff | |||
74978e84f8 | |||
6ef775390f | |||
1313c1b157 | |||
6d6c400b25 | |||
46bd1b9b18 | |||
dcea8bed6d | |||
e8b35f4623 | |||
ae263bb4db | |||
754ca7c8f2 | |||
804c02701a | |||
fbb4077812 | |||
961e1b89a2 | |||
cc449228f3 | |||
3984c47867 | |||
cfb80266c2 | |||
cc5198205d | |||
a27b44a3c2 | |||
937d064860 | |||
9c814d1584 | |||
415563dc6e | |||
bed653737b | |||
b53be942d4 | |||
1d26161d23 | |||
39afb58472 | |||
22cd18571b | |||
5753eb6682 | |||
e26a6ea71b | |||
6fbe765636 | |||
22b2ccd83d | |||
fc5aadd6dd | |||
5a9f0c24b4 | |||
0c12c072fa | |||
f7284caefd | |||
4e7f317679 | |||
3534d6fddc | |||
0b79e9cc9e | |||
3ce97ccaa8 | |||
407a340b2b | |||
532346ecfb | |||
cd25f5b6cb | |||
5a0ac6c2ac | |||
f0da08bbb1 | |||
fb3f6e2238 | |||
8b977252f3 | |||
9582f9b6e5 | |||
3f15a41006 | |||
e1c4cfd7eb | |||
a326f40bbf | |||
09c2fff8fc | |||
a4c1eb12b4 | |||
4e80758970 | |||
40ae408b3b | |||
84d8d4f622 | |||
5c04840312 | |||
bd28d5c48a | |||
f176d890c0 | |||
3b158a96b7 | |||
dcd0b2bf66 | |||
edd1c89ea1 | |||
32d858dce3 | |||
1e4bb53a34 | |||
f5f94097bf | |||
77a3218db3 | |||
c3ed40905a | |||
268ac0bde8 | |||
88e3f6af47 | |||
21a85832b3 | |||
254efdd122 | |||
62ac6e74d9 | |||
2c2268b39d | |||
41aa14eaf0 | |||
1f50f4658d | |||
d31481fd8b | |||
80ab28bc3a | |||
48b7ebe1c0 | |||
c59cf18337 | |||
b7b1260540 | |||
897c5634b0 | |||
78e3a05e14 | |||
1bb6367b79 | |||
f5512ef21b | |||
a0fa9937ba | |||
ef2345ea85 | |||
dd8fd09470 | |||
a779e2aeca | |||
aaaf25d578 | |||
2e65c852c3 | |||
1b206fe94c | |||
8b93c97a09 | |||
6247b55fc3 | |||
12d9d49fa4 | |||
aef3f097e4 | |||
1a415d5fa7 | |||
e4d46aee97 | |||
d3a88e59b9 | |||
44e3811520 | |||
e0574d2861 | |||
d4f66da793 | |||
c7e3289396 | |||
9cb7aeb32d | |||
4537370a54 | |||
9d2bc1142f | |||
c44caa5c96 | |||
77dc587686 | |||
ce768241da | |||
5f9e50175f | |||
34ec457a47 | |||
dd1651f2d1 | |||
c3c529b001 | |||
aa569304bc | |||
3d57fd3227 | |||
c18a6a6577 | |||
9720301d01 | |||
5ea75499fe | |||
b45bbb77ef | |||
d29b86baf0 | |||
1730aff2b9 | |||
1b03484b04 | |||
f3749753ad | |||
ae80e81b75 | |||
8581e980e9 | |||
7234aa601f | |||
a8a2b7d7bb | |||
9a901c7add | |||
1a0b809ec5 | |||
802c7254b4 | |||
a99e3be44e | |||
d7cf203547 | |||
3364ce53e6 | |||
2a2bcc8984 | |||
6b8339e9b4 | |||
32613ba544 | |||
8e9d91fedc | |||
41bb1c8535 | |||
cb5e34f58a | |||
f7c3cf5d78 | |||
fdc0832506 | |||
4413f816e6 | |||
02224bb5fe | |||
c37c4d8c6d | |||
00ccbdaae9 | |||
777189d7bd | |||
c5dfc432d9 | |||
144f7d6813 | |||
6a19b7c1b0 | |||
741b204fc7 | |||
aa394754b6 | |||
492f8f58bb | |||
b9e1a5708d | |||
576a9e4654 | |||
a7bbbad185 | |||
12ec05aed2 | |||
2bde6612d1 | |||
8460aa7d6a | |||
64efbe703a | |||
d272b0cbf3 | |||
d52c95a15f | |||
2b8414a453 | |||
bc3d019ecf | |||
bc9d44e5d7 | |||
d1a8177778 | |||
8d9aa6388d | |||
6d317d300c | |||
f647b3f0e0 | |||
059b75cdbb | |||
35fa42ca56 | |||
4b450bab11 | |||
4394a05243 | |||
fd11ad95f6 | |||
07b57de03e | |||
a2a5f5df3f | |||
148f2210ca | |||
93f072d1fc | |||
1104a385fa | |||
f5b2febf13 | |||
1b03e55cc3 | |||
4a3f020cd9 | |||
2acd23b14c | |||
339a2f4a6f | |||
0fef796757 | |||
f0e5fb04fc | |||
156be19c2d | |||
956c486345 | |||
062235f3a7 | |||
945b357ed8 | |||
c95ec8e99f | |||
61eb631e59 | |||
1ec349f7c9 | |||
c4bc9616af | |||
de050991d4 | |||
6521951e82 | |||
e818ddf152 | |||
b6499e5248 | |||
3cb809b444 | |||
12b7e56261 | |||
0007343175 | |||
8e49c433e8 | |||
16b2169965 | |||
6a7fa52879 | |||
4afc7438a6 | |||
b80ce2a32f | |||
386f88c9b2 | |||
563f09d9de | |||
423bdc6af1 | |||
0d0413e027 | |||
d4942858ba | |||
81bb7009ea | |||
310dc03e2a | |||
362e0bf1ec | |||
3bb330fcc4 | |||
18f5d0241e | |||
97f5b3f74d | |||
f794acdb5b | |||
f20909ba04 | |||
f495ce13b1 | |||
cac1d67b75 | |||
548111e2ff | |||
f5c0706c2e | |||
e6ef11575b | |||
7563e04743 | |||
33e51cc38b | |||
ef09596648 | |||
93db5a091f | |||
978ab2cdaa | |||
cb09ae5cc0 | |||
6bd275669d | |||
51485396c7 | |||
73e1f238cf | |||
37487c243e | |||
c05627a49e | |||
4ba6791043 | |||
54bec54765 | |||
5c8c4e0aad | |||
5cca26a565 | |||
a347f02545 | |||
a45cc0a048 | |||
52ec5cf8e7 | |||
3e4d0954b5 | |||
e76bcce3bb | |||
5a06b34b1d | |||
79dcb0359f | |||
5dc540b894 | |||
0bbb2786f8 | |||
c1fb1ba94c | |||
974331b825 | |||
f74567fbab | |||
c77c01e437 | |||
2b63680f55 | |||
114d32a28f | |||
cba5bca842 | |||
e99351d4db | |||
2524829023 | |||
a7bcc4c00d | |||
58ca6ec6aa | |||
bb2599eb30 | |||
2a95273b79 | |||
d393bc45a4 | |||
3a14eb9363 | |||
3586e53fd9 | |||
db75734774 | |||
137cbbd141 | |||
24f142df1d | |||
b16ee1a3a6 | |||
1b8580f12b | |||
aefdf15a45 | |||
99af697cd7 | |||
30ff15ec0b | |||
13ce39058d | |||
4ed7f5eb67 | |||
2df0c02dfd | |||
158ca9746c | |||
c58a2e8e46 | |||
41117578c5 | |||
a8ea6c2c66 | |||
7652f4272c | |||
04f1f5f94b | |||
f78e17ce02 | |||
c1518dc728 | |||
899f7da032 | |||
eec4334a78 | |||
2ad9eafeaf | |||
9dc9103d6c | |||
ed0e880913 | |||
a70e74e478 | |||
4d34abeeef | |||
621810c409 | |||
62d2c7efc3 | |||
c410dce07a | |||
d2709c681f | |||
0e6625f719 | |||
cc6a408d5d | |||
d10e3d8498 | |||
c89bab0ff1 | |||
6bc8963e8e | |||
34db64234f | |||
b43f0a4536 | |||
45ba07c214 | |||
4d72bfd495 | |||
925eaa1db0 | |||
69957dac3d | |||
b1c2f4a8d6 | |||
aba6c222cb | |||
b8da674816 | |||
f06dba5007 | |||
8dfcee9039 | |||
be355bf7b4 | |||
a1eedd496c | |||
9f7bad82e5 | |||
4e17d11da6 | |||
f38d5a9c06 | |||
696efcdf20 | |||
728c3a72ad | |||
c8c839a569 | |||
6827dacb4b | |||
246271fd9d | |||
4305ebc5b0 | |||
39bff8bfbc | |||
fc6a0c1006 | |||
d6dc9af5ff | |||
e62045eb7f | |||
bda3e53511 | |||
6295d0fc6c | |||
3271e65d56 | |||
f8225141dc | |||
8e9e583b79 | |||
bbadfce65a | |||
9e191d681f | |||
a3236997be | |||
be961cd60e | |||
4efd363134 | |||
7bdee9ce05 | |||
7dc9a9bf2f | |||
87245c7b33 | |||
db497a2ecf | |||
9d250bec05 | |||
1371f69810 | |||
82ee6aed7f | |||
d730fd0a5f | |||
cb4e4bb2db | |||
6fd3c0fbb4 | |||
13170fbb3c | |||
7e7295f259 | |||
d30cb2d4d9 | |||
f4100ac507 | |||
1a0415a100 | |||
1cc5bb5ec4 | |||
43661fff76 | |||
509bdceac2 | |||
835cb8caa5 | |||
ed32adc9bb | |||
44f8fecf84 | |||
0cee0e08e9 | |||
73cd595b73 | |||
c50b23e9ca | |||
d1a763bc21 | |||
de8ca5c1b5 | |||
14372ba7f3 | |||
ca861d8ff9 | |||
37a316e66c | |||
22c8be6645 | |||
8cce1b4f6c | |||
040bb9354a | |||
fb0f9cd1a1 | |||
3816db03f5 | |||
d802416dae | |||
2cd41773ba | |||
8ce5b087bc | |||
03fd74da4e | |||
ed178b702f | |||
b4567acc6b | |||
953f44bcc5 | |||
be4f259b71 | |||
e9531487d9 | |||
d715110961 | |||
3c31908e08 | |||
09d34a2129 | |||
e5e3f3c299 | |||
bcd1c793ea | |||
185152a74a | |||
ded99b9a09 | |||
55a04bbf2b | |||
7d5d7453c2 | |||
952f58153f | |||
d0d981435a | |||
fd83990d8a | |||
ad0c4caf1c | |||
126f0ed95d | |||
a2b499c460 | |||
5a5b3bf291 | |||
a4a6e7cf53 | |||
658db43ad3 | |||
cfecd063c9 | |||
a8fe063726 | |||
dcf637edd8 | |||
c0345f1088 | |||
0cbdfca141 | |||
cb1f696e22 | |||
cf7355e4d0 | |||
dab8c5ea56 | |||
4b889eac32 | |||
86835db8f2 | |||
bc317bf3f2 | |||
5c036eadf9 | |||
263474705b | |||
1e781ec78f | |||
ef1eabf033 | |||
2fa40555e6 | |||
2aae272d86 | |||
7db0900cc8 | |||
c1e2d66abd | |||
78b1ba56ce | |||
5385205b8e | |||
5c8bbb511e | |||
fde01f0b71 | |||
1c04ae3216 | |||
35b4907e52 | |||
2431b8e021 | |||
bd5c04b923 | |||
6e00b6e214 | |||
c9b079cbb5 | |||
9163372786 | |||
dabcd29fb6 | |||
d36e435801 | |||
a18fb27d0f | |||
2dbe511519 | |||
e031a5d28b | |||
f9b32474b0 | |||
53d268a7ef | |||
70da558802 | |||
11215374ff | |||
cb45a38838 | |||
bb4d430ebf | |||
c17f84ca23 | |||
0ae1f9ffc7 | |||
d15bcd9845 | |||
f79a11d993 | |||
9391d9d11b | |||
aee90a3116 | |||
ffac5279a7 | |||
318283fc70 | |||
3582ba0c77 | |||
985d0c786c | |||
9c8c282e08 | |||
93dc7a51c0 | |||
393577ee78 | |||
eef593a34e | |||
3790e924e9 | |||
2f165aade8 | |||
2c502aec45 | |||
6bbf246752 | |||
0b3e8e29cf | |||
d509ab7779 | |||
502a9aefdc | |||
ccba18aa8f | |||
586ebcd5be | |||
317b9a9c87 | |||
67f10ea7eb | |||
793edd3a2b | |||
5dabaf2fe9 | |||
1d9b25f771 | |||
74cd20116a | |||
867695eb4f | |||
9e3592ebf3 | |||
f8ec14d625 | |||
1f21e50663 | |||
7403545a48 | |||
1d95b317ba | |||
f7568b69d4 | |||
8d7649eaec | |||
10b1c7e8a3 | |||
b1c936164c | |||
74ad6abfc2 | |||
9786b2d096 | |||
ea02380c15 | |||
048d5dc914 | |||
aa6b63373e | |||
0b219bf8cb | |||
109b29aeb5 | |||
bc069b99ec | |||
16fa186b63 | |||
e70c0d3e2d | |||
8d47afb195 | |||
a01469fb08 | |||
929636ebd0 | |||
681ef1efec | |||
4f14f122bd | |||
67e9ed5d60 | |||
5c25497e16 | |||
626cbea9cf | |||
aa7ed319e9 | |||
580bd67278 | |||
9e44978aed | |||
64b5ec0b11 | |||
48f9ea3d9e | |||
798a0ca240 | |||
5ee6cbd4c8 | |||
3b219a6a9a | |||
536ff6f561 | |||
013b6aa44a | |||
0e7d3a7558 | |||
8bd4895538 | |||
465c77ddcf | |||
6ef2d4a4cc | |||
7ae7f046c2 | |||
f4051e810e | |||
e6c239d0f3 | |||
35a7a3c1ac | |||
a0991c8261 | |||
9520880568 | |||
719d793e22 | |||
c7fb65c78e | |||
dd74ea99a7 | |||
c6fe6eb7ab | |||
2cbee05c8a | |||
308b1d6039 | |||
4cd832c05a | |||
2af4925d95 | |||
5e52f0e2a8 | |||
a46a68d616 | |||
203d7c4b43 | |||
24703ffa57 | |||
5cd913a527 | |||
91844e48e9 | |||
6c2f3d1d17 | |||
9c222c7e5c | |||
2249da7976 | |||
a55288bda0 | |||
e645edbda7 | |||
aee7cd73c4 | |||
380a71dd21 | |||
23dd5cc160 | |||
847cb5b972 | |||
cc64091f9c | |||
b68eb44ca5 | |||
403540e8a1 | |||
c39497222f | |||
9e56e668e0 | |||
41ae93dba0 | |||
6c527c1bb4 | |||
90c7876341 | |||
10b77a8305 | |||
1902f4773b | |||
4b95be6a95 | |||
61323926e0 | |||
e30d18febe | |||
9f2f80ae4f | |||
bd6e0ceb81 | |||
673d7038d8 | |||
3a6231dcc1 | |||
9d54e46ce7 | |||
63e6d11892 | |||
0509bb9bb4 | |||
5c78908a5f | |||
5a2269c6c6 | |||
a7e9655e32 | |||
1ec82d2ddd | |||
98eaef621a | |||
74a6ca58ef | |||
747faa43ae | |||
32a53f7412 | |||
9c94e9813c | |||
19749bb37f | |||
5ab4c484a5 | |||
e602199bfb | |||
62e1c08dd6 | |||
37e2b60cd3 | |||
f299078585 | |||
bf0a0d5bad | |||
365cda386c | |||
5de5b7a66a | |||
d6de0a64ed | |||
26f8441f73 | |||
51bca08386 | |||
1579aad362 | |||
a2a580954a | |||
944c28f3b3 | |||
c13a597fe0 | |||
86e0ae0b93 | |||
f71ffed3c7 | |||
cae96d9023 | |||
676b649731 | |||
634ce34e00 | |||
6c3096f71f | |||
991ed2da72 | |||
907f02cd28 | |||
9ba78ce04a | |||
14a3d9f7fb | |||
f73a01295c | |||
ccfa3d3be1 | |||
727e4c0b37 | |||
6ce470b9b2 | |||
4b7e230531 | |||
aefe0d3ddd | |||
bbb23b515f | |||
976166a04a | |||
b20129c37e | |||
4fe1360b2c | |||
f0113c5ff5 | |||
a259016436 | |||
9a79c71e88 | |||
e62d22a50e | |||
402f2d939c | |||
374aee75d8 | |||
88ce65266e | |||
e0a6a623d2 | |||
d0310bd745 | |||
35c665156b | |||
3754a76f10 | |||
db2e6e5b95 | |||
d5f95db68d | |||
d96726c392 | |||
d1c54f55e6 | |||
176a73f4e9 | |||
54a9592e19 | |||
b2aa29e221 | |||
0eba0f8dd3 | |||
d8d0afff0b | |||
b799e8c0a6 | |||
583255e1a8 | |||
c68ccbf263 | |||
d658ec8de2 | |||
8727661c1c | |||
731e8bfe2b | |||
f88d9c06f5 | |||
248a0c1b6c | |||
434f1edb25 | |||
2a550e6466 | |||
4f4132943d | |||
62760d5b2d | |||
34aa501637 | |||
6330379f77 | |||
30c64baa7f | |||
cdbed0c615 | |||
f6e7034172 | |||
cd9c5b9c5d | |||
7833e21b01 | |||
12ba2b222f | |||
8583ca73e4 | |||
b588ae4e0e | |||
28abc15c00 | |||
4d663680d8 | |||
6505f8e94a | |||
ff3c20dda2 | |||
c698dee071 | |||
8891a41793 | |||
026f61f5aa | |||
af063dc2f2 | |||
3075f3cfe4 | |||
419f2dca15 | |||
5803f69605 | |||
bfd1cc99a0 | |||
a6a2cea414 | |||
10e857cebe | |||
ed76b54511 | |||
9659d056b7 | |||
b79b0952b4 | |||
fa44dc7d16 | |||
b0dc841e00 | |||
c72ae375c8 | |||
0fd6ae5330 | |||
c8792ccfa6 | |||
b689972e67 | |||
ae6d7bbfa3 | |||
2591bc90ac | |||
61fe000daa | |||
c864ebeabb | |||
24c530611f | |||
d44d00f0f4 | |||
dc3082e66d | |||
c61573a8b7 | |||
3b95560d32 | |||
990f68375e | |||
1290c98c9b | |||
08599afdd4 | |||
bb040918e3 | |||
6fac33ea7d | |||
e16c16b3ef | |||
1eeeead78f | |||
046a1a7af8 | |||
db89648f62 | |||
9c839cdc70 | |||
938b1ff06a | |||
ecd838bf01 | |||
855a31ff25 | |||
7464add904 | |||
7514607129 | |||
12a1d7b38d | |||
d6fe008b2c | |||
d920da6624 | |||
e2c86cef47 | |||
8e270dc246 | |||
22b6a25408 | |||
cde695d903 | |||
e98eb57e3e | |||
11d997c42b | |||
eab1ab0fac | |||
660b801775 | |||
eb80503bcc | |||
14ceb10555 | |||
d9a4688e98 | |||
092586c931 | |||
31478e9fb4 | |||
cc7630c236 | |||
c29810b2f6 | |||
c1240d3f2c | |||
654f1dd055 | |||
53c609c278 | |||
a2fcbb7e65 | |||
db14dc973a | |||
c29aaa047d | |||
49064ed56d | |||
72bc46d339 | |||
40b895d16b | |||
33cad9a824 | |||
e96d9e0ea1 | |||
ed63bda932 | |||
18107d567d | |||
608818fa9f | |||
9ae2440ec1 | |||
ef9c50e63a | |||
5d50b08351 | |||
123fc19c4e | |||
a9058e471c | |||
bd1f48d9fe | |||
66ab4d217d | |||
7a8b392607 | |||
bfdf069d2d | |||
b4b00a48d9 | |||
e5f226612e | |||
20619ad3c1 | |||
37dce7d4c3 | |||
a67b82e730 | |||
e0b8ad7911 | |||
dbb39d366e | |||
e1de36398d | |||
871c28aeeb | |||
c84dc6254d | |||
60cb1ad7c5 | |||
4a5ff5dcfb | |||
5c40307745 | |||
39426f03e6 | |||
92e5d2b8f5 | |||
09aa59f98b | |||
c9c1c89a27 | |||
96b76709e9 | |||
ca3107e21b | |||
2e4f223207 | |||
17df668186 | |||
aef70152de | |||
e0252f35be | |||
0f47534766 | |||
54feaa67e8 | |||
64ecfa49eb | |||
fdae613b14 | |||
c57c08b2c6 | |||
d2103995cb | |||
196fb0f16e | |||
c0afe7260a | |||
099c8703ae | |||
b03e480dbf | |||
8430353389 | |||
a123ec94ef | |||
4a2f54f6ff | |||
b5c85eaeca | |||
8b3b91d78d | |||
e1de3973fe | |||
c1993a6ffc | |||
ab26fc438a | |||
c37259b01d | |||
bde8cc3285 | |||
59ba5504d0 | |||
65eb5a3d05 | |||
b925322e9e | |||
f0c2ad00f8 | |||
fc53a25a4c | |||
58872d162b | |||
30f1b8f02a | |||
ee4f199a9f | |||
e3957f3bac | |||
2506673514 | |||
7cb12015fd | |||
caaac9b9ec | |||
1dac4d00c4 | |||
b41902f4df | |||
ada70dd683 | |||
7e5d8a8d54 | |||
e981cae27c | |||
d7c377c229 | |||
8772edcd33 | |||
254740cf68 | |||
c3775c0f56 | |||
5ecf40e967 | |||
45026df4bd | |||
b8830f4a09 | |||
811ee1d989 | |||
8c32102e99 | |||
1b135095c7 | |||
becf4396c9 | |||
929e066506 | |||
355ad9ac2c | |||
2cda0472bd | |||
4710753700 | |||
2b439ef209 | |||
2fc76e6d9e | |||
ff544450a5 | |||
b4590da686 | |||
6c4be0311a | |||
4a2cdc20f0 | |||
6ce79c62eb | |||
96994721ef | |||
f358bb1a96 | |||
1bce210c51 | |||
8a3c8d1b1c | |||
08b224de1f | |||
86c92c37d2 | |||
07053c3df7 | |||
4fb33c9b09 | |||
5e6a25c3c2 | |||
e14ef4a294 | |||
8b659f0f4c | |||
a6395c95d4 | |||
98e74dfd38 | |||
1d728186db | |||
2499f2ed80 | |||
963905adcd | |||
7c21ab0985 | |||
77d3712261 | |||
0376f22d41 | |||
007820b7c1 | |||
dc98711477 | |||
e98c6ff31b | |||
9eae74357a | |||
1ee88a2878 | |||
14189e6827 | |||
3dd6113a0a | |||
ae5cdea5af | |||
3c1f389e25 | |||
284d821233 | |||
78272e5592 | |||
098bc41083 | |||
c837090282 | |||
cdc1678e6f | |||
2a3d76b0cc | |||
29e89491de | |||
d63947aec4 | |||
512f0a67fb | |||
a7cd4657f5 | |||
bf02cde598 | |||
b7dbb35546 | |||
6fd5f0e3de | |||
e6469df065 | |||
d61fe357f6 | |||
965dd2ab67 | |||
78e011d558 | |||
76590d6c69 | |||
16dc6ce41a | |||
1dadcee5c9 | |||
bed3bb45f7 | |||
18d850d7d6 | |||
f920fd8b6f | |||
b9da6d9ef6 | |||
3be489c69e | |||
cab092aad7 | |||
0451cba343 | |||
12334cd9ad | |||
9d18a2dff4 | |||
869e1dc241 | |||
b878f3fc4a | |||
2aeabcc38b | |||
94778c0dac | |||
390491b37a | |||
f8ea825577 | |||
f77ad7d184 | |||
e467a53def | |||
9cd2e12915 | |||
627a6587b4 | |||
9b3b419f7a | |||
f37ed44837 | |||
dddc333407 | |||
75714fb784 | |||
5a6342a5fe | |||
e07379b406 | |||
54bfe34273 | |||
bef876e4b4 | |||
6ef9ab6a0b | |||
53ffdd8d68 | |||
ba198034cb | |||
58e66b9e1b | |||
ac7efcb43a | |||
b514b9ec7c | |||
f69942eb35 | |||
f28827f625 | |||
f20a6b3635 | |||
7f7ff3ce3e | |||
0abb0756ba | |||
070ed9396c | |||
307004a455 | |||
d097327bd8 | |||
f72f501931 | |||
e9584cfcab | |||
aec0e75d73 | |||
a6b4d49454 | |||
1cc726593e | |||
5001bd8810 | |||
46aa70aa09 | |||
41cf447c45 | |||
b328fd7aed | |||
917a24580b | |||
af36182879 | |||
160c721afc | |||
ee0040ef1c | |||
9e60a55dd7 | |||
57f27572ae | |||
ceae032112 | |||
d3ab367fcd | |||
0ceefb48c8 | |||
a9815ae1e9 | |||
79140fd132 | |||
6c0f48ce25 | |||
da7ab7057d | |||
499ae609dd | |||
e78b3644e4 | |||
ce6b39bd1c | |||
603f528b47 | |||
5fecd07045 | |||
8edd7ad32e | |||
3a27d0b849 | |||
b351536dee | |||
eeea8559b6 | |||
17591117c1 | |||
c4470fd1e6 | |||
45fc7ec01f | |||
dc54472ca5 | |||
4b3bf05aaf | |||
ec014a7ecf | |||
a8c87f3bab | |||
b39e76200a | |||
01deb9ef7d | |||
5c89568cca | |||
6119b44746 | |||
7af4a7bf88 | |||
04e4d69662 | |||
a87e0f028a | |||
31774a7711 | |||
a32f27a2aa | |||
805a409318 | |||
42d45bd14a | |||
5870709fbc | |||
0fdb7430ff | |||
4771f80d6f | |||
91405583fd | |||
f864303aac | |||
e5ebf4a2b2 | |||
725a36e37a | |||
78dacfa865 | |||
16547fb3c2 | |||
9eeabf79f9 | |||
81b71cc143 | |||
8301acd4d6 | |||
f80afb1755 | |||
71c6b60054 | |||
95a1b874d8 | |||
07de96ede9 | |||
209014b083 | |||
127f10e7a8 | |||
67615a0cbc | |||
dde20f0c76 | |||
ddced79475 | |||
e80795b7f0 | |||
080337a4e8 | |||
884af3c986 | |||
edc95b2cb7 | |||
96239e95ec | |||
331ad76675 | |||
29152d3022 | |||
73fa4b1cbd | |||
7766a91e8c | |||
b7bad765aa | |||
5af09a60e9 | |||
ef623cfbbd | |||
ce5faba185 | |||
40d9ed535b | |||
d5675765f3 | |||
65067c24cc | |||
e2463cb501 | |||
de2f2d7ef1 | |||
7cdb75e7ce | |||
1502e308b8 | |||
dd1fe68e5c | |||
85c1dc5a0c | |||
7b3a689aad | |||
2c28a12cca | |||
324021fd64 | |||
1dceae97c6 | |||
0bef925b51 | |||
0ba1f29e40 | |||
3b1e536822 | |||
9a83662a18 | |||
5bcead5b5c | |||
d8f9502ea2 | |||
020128b9ca | |||
1566a4e607 | |||
fbd59631ee | |||
6c36856499 | |||
f4cda49988 | |||
a6b49fe7d6 | |||
26966b2bf3 | |||
36a7429aa0 | |||
7be1fe09f1 | |||
62bf08d323 | |||
260c082c4e | |||
5e0ff7fd56 | |||
aac312ca34 | |||
a90401454a | |||
5679e6b3a9 | |||
194574bb0e | |||
337d2da38a | |||
df848aa084 | |||
0cc1615252 | |||
d4259fa8aa | |||
157d7e2b59 | |||
ee50904147 | |||
ec39aa3890 | |||
beb0fdf4b8 | |||
443fe813c3 | |||
69cdc5a9b9 | |||
dac54a6019 | |||
e5ca53e56c | |||
7a79236bab | |||
cd37e2908f | |||
3368e49aa7 | |||
9e31f05861 | |||
b52f4ed25b | |||
00eb764880 | |||
6d011a3700 | |||
f9d9caf417 | |||
b50702dd52 | |||
e74a82b9d6 | |||
12fa983242 | |||
1607df835a | |||
7b705dd670 | |||
323e3028b6 | |||
7e1b4692e2 | |||
c1dd971ce9 | |||
39610e6933 | |||
fdb189102d | |||
e54d095064 | |||
e6634c56d3 | |||
deb77a4dde | |||
90ea27c423 | |||
19533f87e3 | |||
e6c5c84133 | |||
404aaa5658 | |||
2c70df25f5 | |||
db959ac2eb | |||
bbf0dce390 | |||
1cbb8b9851 | |||
96387cce47 | |||
570fc68cb1 | |||
e6ce0057af | |||
a3f625fe39 | |||
bdfe459d64 | |||
95ec8ef5e1 | |||
b8198716d9 | |||
071a4e5f83 | |||
d99cd71408 | |||
030e6aa507 | |||
f146b01e3e | |||
c249ff9046 | |||
8b48560c81 | |||
bdbea2463b | |||
8fb2263471 | |||
ea55c36a3a | |||
37595ff3e7 | |||
cca008b73c | |||
b9dcbd9d33 | |||
b6bf8d5b2d | |||
43ed66cf26 | |||
57eae1be43 | |||
b394d184cc | |||
81e01b6f88 | |||
52efe32e0f | |||
e7886734c4 | |||
083c37a7b2 | |||
b24a10aa00 | |||
e1ef14d12b | |||
1a33de91e2 | |||
6e15e2d72a | |||
d58b715c52 | |||
804ff8b5a5 | |||
6e89d2f46a | |||
e99d69b7d9 | |||
f25416c3f5 | |||
a7bb6a2781 | |||
41f14e0e89 | |||
01bd10f485 | |||
476eacd5ca | |||
ca2fb74f41 | |||
8a2baf5b6e | |||
d44fc3aa1b | |||
7d78c42dfc | |||
a361180745 | |||
310dc10c4d | |||
96e02c4c2e | |||
afb3b9f029 | |||
d5e647a191 | |||
a1d37617a8 | |||
2541f85942 | |||
07fd23dc5e | |||
946311b2a3 | |||
4868032215 | |||
fb0cf64536 | |||
b37afcdba1 | |||
f644bee831 | |||
6fcc7e3e23 | |||
95602eb85d | |||
9f3afdf928 | |||
627a2412d2 | |||
acffd1e792 | |||
62ca4ba624 | |||
6ea8f35343 | |||
1bd8c67041 | |||
7425b382d6 | |||
5f61b57d63 | |||
9db73767d9 | |||
c562245c16 | |||
9525216d78 | |||
28a71a29e6 | |||
7b06d34ba4 | |||
656d24e477 | |||
df0f03d831 | |||
1db6d15677 | |||
08a0479c9e | |||
6682b7dfa5 | |||
aad5d98b43 | |||
a8a4a85dac | |||
ad71b969b2 | |||
8bcb10391e | |||
8e7d74bc3b | |||
629b6faa22 | |||
ef1e27966d | |||
ea0eb4ba09 | |||
f83ad77c08 | |||
af219ce9e7 | |||
2ac88a7fa6 | |||
c495ff8ad8 | |||
0e12215614 | |||
b4b13b0cb9 | |||
677cdfd6c8 | |||
e9f18c6d8d | |||
8640e18d1f | |||
300c4d8432 | |||
92c877493a | |||
ff4926be35 | |||
df3872f665 | |||
fcded2ea2b | |||
420f0109ee | |||
b4678a1cee | |||
5d3c90197c | |||
d5d517748c | |||
de3d3c15a5 | |||
3db253998b | |||
d3f978a19d | |||
a50ddd6d8c | |||
37700b4b1a | |||
a8f9871725 | |||
a4a5492782 | |||
9ebb80502a | |||
dcacbb37c3 | |||
34443da7ab | |||
09c14f1a10 | |||
e801647083 | |||
0122cee0ae | |||
1188b1be36 | |||
905c4932d1 | |||
6484da26f0 | |||
079041cf1f | |||
b8df7798bf | |||
e4af954a95 | |||
f3df62f27e | |||
4b3c9e826d | |||
ba4780cbd3 | |||
b1a6940188 | |||
3c8325f1f3 | |||
5fa9581db3 | |||
96001d86e1 | |||
963e808d98 | |||
d124ca377f | |||
a3d3d81447 | |||
244121d920 | |||
adf95fb90d | |||
f8446f1bfc | |||
b922035d4f | |||
b75fc2bde3 | |||
10e16cca22 | |||
6628214484 | |||
e3c9c46c1c | |||
6a1b341336 | |||
65f96494f8 | |||
6e3e2d9f29 | |||
25d8debb11 | |||
426b227f03 | |||
2fbd9b95a7 | |||
9758029c11 | |||
f21b8206af | |||
4f8f79a907 | |||
c9e2f16404 | |||
2658754295 | |||
77d8ff3620 | |||
c4621119b3 | |||
a2a292d48e | |||
8349b70ba8 | |||
dd6fe58475 | |||
386929e84b | |||
43a355e07a | |||
1a5d2ac459 | |||
21403b19e0 | |||
cb7778d433 | |||
07c0105c83 | |||
7da186d4e9 | |||
75885c3095 | |||
5d275389af | |||
75589b4450 | |||
a9b12d5d73 | |||
6a00a504d4 | |||
6b9cac928b | |||
b292384380 | |||
01ac5aeb0d | |||
1a41ee0c30 | |||
1950a67e15 | |||
65303d027a | |||
fae838b054 | |||
eab4c7bce9 | |||
6bb38b873b | |||
ba5860cdd6 | |||
ee62863759 | |||
4a3a61bea1 | |||
f3901eb668 | |||
b53d1df4df | |||
df9b0c548e | |||
3056fc4710 | |||
e411a4af21 | |||
3430012136 | |||
809295c03d | |||
c9783b38c4 | |||
44e4cbf04a | |||
049695b914 | |||
7b1aab3759 | |||
5e87fea1ee | |||
a187111a26 | |||
3044a6b517 | |||
824fbe09c2 | |||
764eed7599 | |||
ccb81919f7 | |||
4505c38b58 | |||
d9770dcffb | |||
9936f97fcb | |||
4cf09f46e7 | |||
6ffe3a424c | |||
641794d458 | |||
73d9ac6c01 | |||
3205d1e16a | |||
ec61004622 | |||
ae93d258a5 | |||
12845f0eef | |||
6b4f524620 | |||
dec0baa147 | |||
340609d149 | |||
9a30c3d722 | |||
e19a927579 | |||
53a595885a | |||
ebd7406386 | |||
66cdbc5cad | |||
060c049056 | |||
31270c36d2 | |||
0aa26e9134 | |||
b45a2d7335 | |||
07676d483d | |||
6436452bc9 | |||
60ad374514 | |||
b611ddda5e | |||
186bd156dd | |||
3628c81885 | |||
180000a531 | |||
5f995c64d4 | |||
fe2c2014de | |||
754df5ebe2 | |||
d8b7ac9044 | |||
5f3b04ecbd | |||
1f1aba4a32 | |||
1390cd4048 | |||
31cdac7bfb | |||
0705901bce | |||
87495575bc | |||
b292bba092 | |||
5c7f0a0ad8 | |||
2df86e1966 | |||
41406e3be2 | |||
7ed4bd67b8 | |||
8ce3531de8 | |||
579e53f02c | |||
1379c6b404 | |||
153d3045ed | |||
278686dc48 | |||
9b9f33bc8b | |||
24984458de | |||
e3eb4a20a5 | |||
001bbd36f5 | |||
382cc52baa | |||
2af368ab82 | |||
f514160fae | |||
30e4dcef90 | |||
4618e11406 | |||
f887b77e36 | |||
fb527139a0 | |||
261fbef516 | |||
32e8a9b377 | |||
5f95351074 | |||
1c57c0e651 | |||
ec6ee27f29 | |||
70234edd02 | |||
7e24a696bd | |||
c3ed936776 | |||
235ec7cb2e | |||
4f8586d81d | |||
6cd1e38425 | |||
f7512dc540 | |||
45415a742f | |||
2247065dc5 | |||
c550e2ccf5 | |||
bc75e5ec44 | |||
5b39496008 | |||
4016da6632 | |||
f2edcb9bdf | |||
3d8a87563d | |||
93bde0cae2 | |||
30179bb60d | |||
498bc21133 | |||
af7a34521f | |||
7fc2b33a0a | |||
a5d3f7785a | |||
f1cdce38a6 | |||
65e4652142 | |||
569797d7c5 | |||
35a7c94cd1 | |||
c8365b7444 | |||
8c5a343729 | |||
22ddec46ab | |||
a4e70ba4ca | |||
978ac65cae | |||
eb5a836822 | |||
ad1b9b71ae | |||
60257f422a | |||
6576d4852c | |||
c30661c44c | |||
2d9cf195d7 | |||
a21e76caab | |||
a534a6bf09 | |||
2b0f219bfd | |||
33dde63256 | |||
4dc9540325 | |||
2b1a661614 | |||
2138fc2349 | |||
cdc8dd88e9 | |||
2a5f8e84bb | |||
6f8540f25a | |||
151c699f5c | |||
9133f6d352 | |||
ab276e4e86 | |||
cdc4f85e47 | |||
b2fb84e361 | |||
4ab1e893f0 | |||
70bc3d178b | |||
777f145128 | |||
2f0181ac44 | |||
83b4b698a0 | |||
a10a294a2e | |||
34036a3b51 | |||
911bc03381 | |||
0240aeac8d | |||
a43be186d3 | |||
fb63f48d0a | |||
c0279df3c6 | |||
e614b993f2 | |||
8035f30305 | |||
af0b5e6176 | |||
1bf996c705 | |||
33f69481f7 | |||
354fb6b8c6 | |||
d30477b6f8 | |||
496b5d62ae | |||
3652002a68 | |||
c0e5719271 | |||
997f851031 | |||
eaf184b585 | |||
0616261a94 | |||
d2c45f428f | |||
6054ab35cb | |||
5ba0c6404b | |||
f738c2be9d | |||
12ac2e5534 | |||
60985b396a | |||
d9b33e01ee | |||
0335f2726a | |||
23717cc4d7 | |||
4422f301b4 | |||
1981da1b4e | |||
a130f50107 | |||
c40198046c | |||
e5b44a3970 | |||
5e7443706b | |||
39e75e932a | |||
b8a8edc513 | |||
b1051365fa | |||
a935cd0c5d | |||
62ce90795f | |||
6bcad45392 | |||
8747c0c58d | |||
b91d9c2867 | |||
140935bca3 | |||
67c7abaac3 | |||
11bd7dc7de | |||
406f65cb23 | |||
2344706462 | |||
fcc32fca7e | |||
7f587fd4da | |||
f47c0601ce | |||
3d638c692e | |||
e6ef3ea24f | |||
8dd880d0c8 | |||
393a3a079f | |||
ceb035e9b3 | |||
e38570437e | |||
f0203d1f19 | |||
e0c5a61be5 | |||
526a16298c | |||
443f02cd92 | |||
b682c8e052 | |||
d07c8dcd9c | |||
9bcce4f271 | |||
eab497a814 | |||
a8c8df6ee8 | |||
b3549f421d | |||
ae0accb5a4 | |||
d367566db9 | |||
35812208e7 | |||
afaff724f6 | |||
ec8dfb0cad | |||
7798da8dff | |||
89a49ce72e | |||
655dce6a4b | |||
2cf403a3e9 | |||
4351ad2414 | |||
fd2944fa8d | |||
f4626cf8ad | |||
65bf0d20e7 | |||
2f496de98a | |||
644b8304ab | |||
5c4570fcaa | |||
ac0bd3b116 | |||
7ad881d0ec | |||
9a25224890 | |||
3f6f597093 | |||
9ab22fe551 | |||
9794e71a86 | |||
8cb3884fae | |||
098bd4509b | |||
4dc5bac72f | |||
b150869b51 | |||
daf8be9f78 | |||
d14cf1e80b | |||
605d34008e | |||
d3135414cc | |||
b3f29b46cf | |||
4545719e18 | |||
cc60afa31a | |||
85743ede7e | |||
8e231cb2ec | |||
72405cd43f | |||
a9ad9d5e6d | |||
2d79c7333f | |||
7a79cfd76b | |||
6600d6b6d9 | |||
8ca25aed8b | |||
1db353a0fc | |||
2d384eb546 | |||
5d1de33026 | |||
f2ba49fa35 | |||
772ae1cae4 | |||
1d267f2642 | |||
cba299160a | |||
ea33193e73 | |||
ef69c228fd | |||
7ba0f07732 | |||
07696086a2 | |||
426581eb1e | |||
fe88811a40 | |||
30aaa6e26c | |||
4bd071bf3c | |||
309ac65447 | |||
fe246470ce | |||
660cfe707c | |||
a0d7d7bc4b | |||
758764ea75 | |||
6935c62a61 | |||
a34d4d9124 | |||
c49bb5aa03 | |||
427750d6af | |||
b9ad5f8727 | |||
c776826425 | |||
a8ede18db5 | |||
9aa84ec54a | |||
91a6520c3b | |||
45fde4888e | |||
484ef5f2f6 | |||
cdd354739a | |||
420f544bf3 | |||
15b33189ae | |||
685a9f36f6 | |||
3b57812e29 | |||
d583c48992 | |||
0e636ea67e | |||
c9ce1bd30f | |||
f2cbc3192c | |||
56d76791f1 | |||
df2cab877f | |||
27ad8305e5 | |||
c97b4dd48e | |||
59ecd610b1 | |||
42c1285ead | |||
622c1c9236 | |||
2d88508f9d | |||
86efba4ecc | |||
90fae00aa6 | |||
d497ed4040 | |||
c4a21ae5ad | |||
5e5798bee9 | |||
d793077b91 | |||
3e826f9249 | |||
2adfb60dc4 | |||
f7212cf80c | |||
69556643bb | |||
f0ebc84840 | |||
490eb59ea3 | |||
16a9391726 | |||
adf8ba67d2 | |||
c37b222cbf | |||
72f8f2beb1 | |||
16bb9c17f5 | |||
ca44977d92 | |||
bcf294475f | |||
3b31774dd3 | |||
c106347c59 | |||
2679be9d97 | |||
4d59368c7d | |||
3f29680fb6 | |||
8f1df14ae7 | |||
7159e360d9 | |||
65723bcac5 | |||
73ae451cb4 | |||
dc0ea3a248 | |||
9548cd8341 | |||
8410fc38c9 | |||
0c9c2168b3 | |||
2690f54743 | |||
22783813c5 | |||
b5ed46a5af | |||
1a3f17e296 | |||
9281129f07 | |||
0e9ddd3b99 | |||
d87db04e55 | |||
906ec3c8a9 | |||
449575ceae | |||
6255c77eba | |||
c33622f2b3 | |||
e4860acb58 | |||
e96867f757 | |||
57bd43baf3 | |||
3a4e595d32 | |||
dcad22bfa8 | |||
acba0e47d8 | |||
dd19459e18 | |||
8be3c5ed21 | |||
51726d8de7 | |||
8f41c6bad8 | |||
e294abc567 | |||
9f9518c872 | |||
5faf7cb59e | |||
4b095d532c | |||
4288761235 | |||
73388f30fd | |||
2d9ddd4bc8 | |||
55aa0cf303 | |||
52ca15b514 | |||
bd383888de | |||
1bd349485f | |||
155f9dc1b1 | |||
fe7ee1edc3 | |||
c11cbff605 | |||
fbc629266f | |||
75d44dca6b | |||
3e6b794a33 | |||
a757ce48a1 | |||
507f29a7bd | |||
e5f4f77073 | |||
f07fee538d | |||
fa1420b384 | |||
724a2bd72f | |||
d525d02348 | |||
4920cf2b98 | |||
f41d0938a4 | |||
924a405829 | |||
fc9225e24a | |||
ac202cbdd3 | |||
cedd68c942 | |||
4231c879ef | |||
42e58a4b72 | |||
ebd1bc83c9 | |||
0bc9d7455f | |||
5274166f8c | |||
5566aa4588 | |||
ab638a4f54 | |||
86a8452ab7 | |||
8c96c16eac | |||
45c2e6575d | |||
a786a7dc55 | |||
6adf5cbee5 | |||
1964b54627 | |||
c9d0e82c52 | |||
1a4948f0f2 | |||
66da3f5668 | |||
9ebeb64570 | |||
571aaece2e | |||
f60fb954a2 | |||
1d136cacfb | |||
994021d77f | |||
8899325a0e | |||
43876a9357 | |||
d6cace32f5 | |||
2388de455b | |||
b14b3ab276 | |||
32ccb779c6 | |||
52536e94e5 | |||
5d6b3abd26 | |||
79682bbcb0 | |||
770ff19313 | |||
8d4855f100 | |||
7d4e14f384 | |||
f162dd7e87 | |||
06dc12e217 | |||
36edff9e15 | |||
34a0c079de | |||
84b581ae13 | |||
587c93eadf | |||
b5aa549e0c | |||
5fb6738612 | |||
685262fc5e | |||
8802f6cf23 | |||
2698d0db29 | |||
2fa7230133 | |||
658243eae4 | |||
7282bc8859 | |||
07511cb21a | |||
f71f767a3c | |||
176daa1469 | |||
5487f8cf93 | |||
944762ac83 | |||
c66068c435 | |||
d479c936b8 | |||
c5a3f784dd | |||
529d012865 | |||
8a1b83417a | |||
6c64c0873c | |||
d2c6149923 | |||
9f98f6f02d | |||
2f7c0b1985 | |||
f61548234a | |||
6eef830ba4 | |||
88a9b7648c | |||
b7678493f9 | |||
a9ec8a354a | |||
a42d35deab | |||
2008feb7da | |||
c41424b57b | |||
3bd5563a7e | |||
eff8ec00c4 | |||
1256af7b9a | |||
b38ecaf925 | |||
bd5d945fb2 | |||
15063ef3d5 | |||
3b8a125732 | |||
9860b1c677 | |||
a0e340f06e | |||
57d3bec95c | |||
be10f3c2b5 | |||
f7af96dbb2 | |||
468a855f04 | |||
429f9e1d15 | |||
5de91197ae | |||
bc6b4b01ea | |||
e89ce7358a | |||
bdf0a6fd0b | |||
783abd4f5f | |||
28aa9201f0 | |||
378df515d0 | |||
04d68c6e36 | |||
5308d12239 | |||
15cac0157c | |||
fc696bc054 | |||
5a7b7b7389 | |||
9c9cf6f5ab | |||
0155739bef | |||
d0902fa28b | |||
3aa0d455c9 | |||
835d4e4b58 | |||
6075332f2f | |||
c5d8484e19 | |||
575ab0d609 | |||
b48a7d5cfd | |||
9af107feff | |||
70b5db16d3 | |||
5072ea7e47 | |||
13b83c95b7 | |||
e48dbe65f1 | |||
14fb51e6d7 | |||
b42af9aa99 | |||
ca2ee22827 | |||
13a2b28bf1 | |||
04bf75d137 | |||
76c589702f | |||
c11f89e72a | |||
cfacb05461 | |||
c4ba31cb23 | |||
db0eacddc5 | |||
62972f5138 | |||
6fe263f19f | |||
b6a87acb71 | |||
784b04b191 | |||
3af9f636af | |||
16c0585b95 | |||
fc9a96ac85 | |||
9dfc3af9d7 | |||
8dc63932fc | |||
066e5cddb5 | |||
27ffad2148 | |||
41db363b06 | |||
184ac11c93 | |||
673aa3c5f2 | |||
2dd1c04bf3 | |||
4df1222ce7 | |||
208121646c | |||
f47a11f792 | |||
36fc3a5c96 | |||
ed12c9d611 | |||
4153feeb15 | |||
349c642d10 | |||
4728105f43 | |||
1f46a0dc26 | |||
7ff7ced504 | |||
a43ee41b0b | |||
ec7ade4ee6 | |||
9b846cbb83 | |||
2cc7fd07f8 | |||
72d54d9915 | |||
b390b82e9d | |||
1ef9ee13a3 | |||
6822795bb8 | |||
187dec2816 | |||
31f75e4607 | |||
92083eaf76 | |||
6b40c3974d | |||
c528401b62 | |||
8a17f512f4 | |||
e725f8a0fe | |||
525d3c2619 | |||
aba46720c0 | |||
2fb1d707fe | |||
dd06aa98ad | |||
cfb66ca153 | |||
0b4660702b | |||
9760227c66 | |||
5faeaa2028 | |||
cd58f9f9e5 | |||
00338bbc4b | |||
31d14a2fb0 | |||
49fa0ddebc | |||
d50c3e6c67 | |||
b39f56e813 | |||
ead0286ca6 | |||
6eb05af306 | |||
6b1e632621 | |||
29cb10fed8 | |||
a88433dba6 | |||
7e5f1fe411 | |||
00c78d33e4 | |||
50fbf9982e | |||
71ad39b6e3 | |||
3d329cdf2d | |||
21af5c8f73 | |||
716d7bb93e | |||
98f4b99446 | |||
830e701d13 | |||
57d9e7d5aa | |||
5fc16bb05b | |||
cb08bd2e2e | |||
961dba7faa | |||
41f933b89e | |||
d4dc1c87c5 | |||
e58fbeea17 | |||
2492dc50db | |||
b4f5f1e461 | |||
69347fff09 | |||
5c439f4e9c | |||
c84236ed73 | |||
ae1a3a0cb2 | |||
b3fea016f1 | |||
7c93639e59 | |||
affcd5c7de | |||
e00eb06f66 | |||
5ecc204d57 | |||
2a5eed1eb4 |
8
.gitignore
vendored
8
.gitignore
vendored
@ -19,6 +19,8 @@ configure
|
||||
data/50-gnome-shell-*.xml
|
||||
data/gnome-shell.desktop
|
||||
data/gnome-shell.desktop.in
|
||||
data/gnome-shell-wayland.desktop
|
||||
data/gnome-shell-wayland.desktop.in
|
||||
data/gnome-shell-extension-prefs.desktop
|
||||
data/gnome-shell-extension-prefs.desktop.in
|
||||
data/gschemas.compiled
|
||||
@ -38,6 +40,7 @@ docs/reference/*/*.txt
|
||||
docs/reference/*/*.types
|
||||
docs/reference/*/html/
|
||||
docs/reference/*/xml/
|
||||
docs/reference/shell/doc-gen-*
|
||||
gtk-doc.make
|
||||
js/misc/config.js
|
||||
intltool-extract.in
|
||||
@ -70,13 +73,14 @@ src/calendar-server/evolution-calendar.desktop.in
|
||||
src/calendar-server/org.gnome.Shell.CalendarServer.service
|
||||
src/gnome-shell
|
||||
src/gnome-shell-calendar-server
|
||||
src/gnome-shell-extension-tool
|
||||
src/gnome-shell-extension-prefs
|
||||
src/gnome-shell-extension-tool
|
||||
src/gnome-shell-hotplug-sniffer
|
||||
src/gnome-shell-jhbuild
|
||||
src/gnome-shell-perf-helper
|
||||
src/gnome-shell-perf-tool
|
||||
src/gnome-shell-real
|
||||
src/gnome-shell-wayland
|
||||
src/hotplug-sniffer/org.gnome.Shell.HotplugSniffer.service
|
||||
src/run-js-test
|
||||
src/test-recorder
|
||||
@ -85,6 +89,8 @@ src/test-theme
|
||||
src/st.h
|
||||
src/stamp-st.h
|
||||
src/stamp-st.h.tmp
|
||||
src/st-scroll-view-fade-generated.c
|
||||
src/stamp-st-scroll-view-fade-generated.c
|
||||
stamp-h1
|
||||
tests/run-test.sh
|
||||
xmldocs.make
|
||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "src/gvc"]
|
||||
path = src/gvc
|
||||
url = git://git.gnome.org/libgnome-volume-control
|
4
HACKING
4
HACKING
@ -138,8 +138,8 @@ GObjects, although this feature isn't used very often in the Shell itself.
|
||||
|
||||
_init: function(icon, label) {
|
||||
this.parent({ reactive: false });
|
||||
this.addActor(icon);
|
||||
this.addActor(label);
|
||||
this.actor.add_child(icon);
|
||||
this.actor.add_child(label);
|
||||
},
|
||||
|
||||
open: function() {
|
||||
|
@ -16,6 +16,7 @@ EXTRA_DIST = \
|
||||
# These are files checked into Git that we don't want to distribute
|
||||
DIST_EXCLUDE = \
|
||||
.gitignore \
|
||||
.gitmodules \
|
||||
gnome-shell.doap \
|
||||
HACKING \
|
||||
MAINTAINERS \
|
||||
|
674
NEWS
674
NEWS
@ -1,3 +1,677 @@
|
||||
3.10.0.1
|
||||
=========
|
||||
* Fix login screen [Ray; #708691]
|
||||
|
||||
Contributors:
|
||||
Ray Strode, Giovanni Campagna, Jasper St. Pierree
|
||||
|
||||
Translations:
|
||||
Kjartan Maraas [nb], Marek Černocký [cs], A S Alam [pa], Daniel Mustieles [es],
|
||||
Ihar Hrachyshka [be], Chao-Hsiung Liao [zh_HK], Nilamdyuti Goswami [as],
|
||||
Yuri Myasoedov [ru], Baurzhan Muftakhidinov [kk]
|
||||
|
||||
3.10.0
|
||||
======
|
||||
* Fix fade effect in ScrollViews [Carlos; #708256]
|
||||
* network: Resync when activating connection changes [Jasper; #708322]
|
||||
* Close run dialog when the screen locks [Florian; #708218]
|
||||
* Fix entry growing out of password dialogs [Florian; #708324, #703833]
|
||||
* Vertically center labels in submenu items [Jasper; #708330]
|
||||
* https://bugzilla.gnome.org/show_bug.cgi?id=708387 [Mike; #708387]
|
||||
* Fix bluetooth icon not being added to status menu [Jasper; #708541]
|
||||
* Fix GNOME 2 keyring dialogs appearing on lock screen [Florian; #708187]
|
||||
* Fix passwords being cleared twice when authentication fails [Florian; #708186]
|
||||
* Fix message tray appearing in a11y popup on login screen [Florian; #708380]
|
||||
* Increase width of aggregate menu popup [Adel; #708472]
|
||||
|
||||
Contributors:
|
||||
Adel Gadllah, Mike Gorse, Ryan Lortie, Florian Müllner, Frédéric Péters,
|
||||
Carlos Soriano, Jasper St. Pierre, Rico Tzschichholz
|
||||
|
||||
Translations:
|
||||
Daniel Șerbănescu [ro], Ryan Lortie [eo], Ihar Hrachyshka [be],
|
||||
A S Alam [pa], Jiro Matsuzawa [ja], Chao-Hsiung Liao [zh_HK, zh_TW],
|
||||
Piotr Drąg [pl], Kristjan SCHMIDT [eo], Daniel Korostil [uk],
|
||||
Rūdolfs Mazurs [lv], Reinout van Schouwen [nl], Yosef Or Boczko [he],
|
||||
Fran Diéguez [gl], António Lima [pt], Andika Triwidada [id],
|
||||
Alexandre Franke [fr], Rafael Ferreira [pt_BR], Milo Casagrande [it],
|
||||
Kenneth Nielsen [da], Matej Urbančič [sl]
|
||||
|
||||
3.9.92
|
||||
======
|
||||
* Don't show page indicators if there's only one page [Florian; #707363]
|
||||
* Make :active style of app and non-app results consistent [Jakub; #704714]
|
||||
* Fade app pages when scrolled [Florian; #707409]
|
||||
* Don't block scrolling on page indicators [Carlos; #707609]
|
||||
* Tweak visual appearance of folder views [Florian; #707662]
|
||||
* Don't put minimized apps at the end of the app switcher [Florian; #707663]
|
||||
* Merge the wayland branch [Giovanni, Neil; #707467]
|
||||
* Make search entry behave better in RTL locales [Matthias, Florian; #705779]
|
||||
* Allow to change app pages with pageUp/pageDown keys [Carlos; #707979]
|
||||
* Set approriate a11y states on expandable menu items [Alejandro; #708038]
|
||||
* Improve page indicator animation [Carlos; #707565]
|
||||
* Misc bug fixes and cleanups [Florian, Olivier, Jasper, Giovanni, Magdalen,
|
||||
Adel, Carlos, Rico, Joanmarie; #707308, #707430, #707508, #707557, #707600,
|
||||
#707614, #707666, #707814, #707806, #707801, #707889, #707892, #707935,
|
||||
#707842, #707940, #707996, #708007, #708009, #708020, #707580, #708080]
|
||||
|
||||
Contributors:
|
||||
Magdalen Berns, Olivier Blin, Giovanni Campagna, Matthias Clasen,
|
||||
Joanmarie Diggs, Adel Gadllah, Florian Müllner, Alejandro Piñeiro,
|
||||
Neil Roberts, Carlos Soriano, Jasper St. Pierre, Jakub Steiner,
|
||||
Rico Tzschichholz
|
||||
|
||||
Translations:
|
||||
Rafael Ferreira [pt_BR], Fran Diéguez [gl], Daniel Mustieles [es],
|
||||
Aurimas Černius [lt], Luca Ferretti [it], Piotr Drąg [pl],
|
||||
Chao-Hsiung Liao [zh_HK, zh_TW], Timo Jyrinki [fi], Daniel Korostil [uk],
|
||||
Dušan Kazik [sk], Adam Matoušek [cs], Marek Černocký [cs],
|
||||
Jiro Matsuzawa [ja], Yuri Myasoedov [ru], Tobias Endrigkeit [de],
|
||||
Kjartan Maraas [nb], Victor Ibragimov [tg], Мирослав Николић [sr, sr@latin],
|
||||
A S Alam [pa], Khaled Hosny [ar], Andika Triwidada [id],
|
||||
Nilamdyuti Goswami [as], Ihar Hrachyshka [be], Rūdolfs Mazurs [lv],
|
||||
Mattias Põldaru [et], Gabor Kelemen [hu], Bruce Cowan [en_GB],
|
||||
Matej Urbančič [sl], Enrico Nicoletto [pt_BR], Benjamin Steinwender [de],
|
||||
Changwoo Ryu [ko], Kris Thomsen [da], Alexandre Franke [fr],
|
||||
Evgeny Bobkin [ru], Baurzhan Muftakhidinov [kk], Peter Mráz [sk],
|
||||
Inaki Larranaga Murgoitio [eu], Yosef Or Boczko [he]
|
||||
|
||||
3.9.91
|
||||
======
|
||||
* Improve submenu styling [Jakub; #706037]
|
||||
* Fix changing slider values via keyboard [Alejandro; #706386]
|
||||
* Fix accessibility of sliders [Alejandro; #706391]
|
||||
* Tweak system actions style [Jakub; #706638]
|
||||
* Add support for auth without username / fix Not Listed? [Ray; #706607]
|
||||
* Dash: Don't show tooltips for apps with open popups [Giovanni; #705611]
|
||||
* Implement new end-session/power-off dialog design [Jasper, Matthias; #706612]
|
||||
* Implement building separate binaries for x11 and wayland [Giovanni; #705497]
|
||||
* authPrompt: Fix controls moving when showing messages [Ray; #706670]
|
||||
* Tweak padding between system status icons [Allan; #706796]
|
||||
* Add a generic accessible usable by JS code [Alejandro; #648623]
|
||||
* Improve keynav and accessibility of the calendar [Alejandro; #706903]
|
||||
* Update to new NetworkManager APIs [Jasper; #706098]
|
||||
* Hide system actions section in the lock screen [Jasper; #706852]
|
||||
* Don't show other logged in users at log out [Giovanni; #707124]
|
||||
* Remove "Session" subtitle heading in login dialog [Jasper; #707072]
|
||||
* dash: Reload favorites when installed apps change [Giovanni; #706878]
|
||||
* Don't open overview after closing last window on workspace [Florian; #662581]
|
||||
* Add FocusApp DBus method [Giovanni; #654086]
|
||||
* Add ShowApplications DBus method [Giovanni; #698743]
|
||||
* Implement new app picker design [Carlos, Florian; #706081]
|
||||
* Improve frequent apps being empty by default [Carlos, Florian; #694710]
|
||||
* Extend clickable area of page indicators [Giovanni; #707314]
|
||||
|
||||
* Misc bug fixes [Ray, Giovanni, Jasper, Emmanuele; #706542, #706654, #706005,
|
||||
#706681, #706841, #706843, #707064, #706262, #707197, #707269]
|
||||
|
||||
Contributors:
|
||||
Emmanuele Bassi, Giovanni Campagna, Matthias Clasen, Allan Day, Adel Gadllah,
|
||||
Florian Müllner, Alejandro Piñeiro, Carlos Soriano, Jasper St. Pierre,
|
||||
Jakub Steiner, Ray Strode, Seán de Búrca
|
||||
|
||||
Translations:
|
||||
Piotr Drąg [pl], Kjartan Maraas [nb], Victor Ibragimov [tg],
|
||||
Enrico Nicoletto [pt_BR], Benjamin Steinwender [de],
|
||||
Baurzhan Muftakhidinov [kk], Aurimas Černius [lt], Seán de Búrca [ga],
|
||||
Fran Diéguez [gl], Daniel Mustieles [es], Dušan Kazik [sk],
|
||||
Matej Urbančič [sl], Andika Triwidada [id], Jordi Mas [ca],
|
||||
Ihar Hrachyshka [be]
|
||||
|
||||
3.9.90
|
||||
======
|
||||
* workspaceThumbnails: Exclude transient windows when shifting workspaces
|
||||
[Bradley; #705174]
|
||||
* Never show a horizontal scrollbar on lock screen [Jasper; #704327]
|
||||
* authPrompt: Fix disable-user-list / Not Listed? [Ray; #705370]
|
||||
* Animate the lock screen notification transitions [Giovanni; #687660]
|
||||
* Wake up the screen when new notifications appear [Giovanni; #703084]
|
||||
* Use StartupWMClass for application matching [Giovanni; #673657, #705801]
|
||||
* dateMenu: Add style class for the clock label [Jonh; #705634]
|
||||
* keyboard: Translate IBus IME name if possible [Daiki; #695673]
|
||||
* power: Display single digit minutes correctly [Sebastian; #705803]
|
||||
* Implement new aggregate status menu [Jasper; #705845]
|
||||
* Improve triangle animation when expanding sub-menus [Tarun; #703109]
|
||||
* Fix alignment of search provider icons [Tarun; #695760]
|
||||
* Slide dash and workspace switcher on overview transitions [Tarun; #694262]
|
||||
* Respect always-show-universal-access-status setting [Tanner; #705733]
|
||||
* Handle .desktop files with capital letters [Giovanni; #706252]
|
||||
* authPrompt: Add smartcard support [Ray; #683437]
|
||||
* Fix call notifications in busy mode [Emilio; #666221]
|
||||
* Improve triangle animation when expanding sub-menus [Tarun; #703109]
|
||||
* Move message tray menu to a tray button [Jasper; #699272]
|
||||
* Wi-fi dialog improvements [Jasper, Allan; #705916, #706136]
|
||||
* Work towards running as wayland compositor [Giovanni]
|
||||
- Switch to Mutter abstraction layer for cursor tracking [#705911]
|
||||
- Add confirmation dialog for display changes [#706208]
|
||||
* Use a different background in screen shield [Giovanni; #688210]
|
||||
* Add fade animation before blanking the screen [Giovanni; #699112]
|
||||
* Misc. bugfixes and cleanups [Jasper, Giovanni, Adel, Colin, Ray, Florian,
|
||||
Magdalen; #704448, #702536, #686855, #695581, #700901, #701761, #701495,
|
||||
#701848, #697833, #701731, #705664, #705840, #705898, #706089, #706153,
|
||||
#704646, #706262, #706324, #703810, #703811, #704015, #706232, #705917,
|
||||
#706536]
|
||||
|
||||
Contributors:
|
||||
Magdalen Berns, Giovanni Campagna, Allan Day, Tanner Doshier, Adel Gadllah,
|
||||
Sebastian Keller, Tarun Kumar Joshi, Florian Müllner, Bradley Pankow,
|
||||
Emilio Pozuelo Monfort, Jasper St. Pierre, Ray Strode, Rico Tzschichholz,
|
||||
Daiki Ueno, Colin Walters, Jonh Wendell
|
||||
|
||||
Translations:
|
||||
Kjartan Maraas [nb], Aurimas Černius [lt], Yaron Shahrabani [he],
|
||||
Fran Diéguez [gl], Gabor Kelemen [hu],
|
||||
Juan Diego Martins da Costa Cruz [pt_BR], Inaki Larranaga Murgoitio [eu],
|
||||
Yuri Myasoedov [ru], Daniel Mustieles [es], Seán de Búrca [ga],
|
||||
Khaled Hosny [ar], Victor Ibragimov [tg], Friedel Wolff [af],
|
||||
Marek Černocký [cs], Matej Urbančič [sl], A S Alam [pa],
|
||||
Rafael Ferreira [pt_BR], Andika Triwidada [id], Dušan Kazik [sk]
|
||||
|
||||
3.9.5
|
||||
=====
|
||||
* Fix width changes of the calendar popup [Florian; #704200]
|
||||
* Work towards aggregate status menu [Jasper; #702539, #704336, #704368,
|
||||
#704670]
|
||||
* Update design of lock screen notifications [Allan; #702305]
|
||||
* Don't show empty backgroundMenu [Michael; #703868]
|
||||
* Add option to limit app switcher to current workspace [Adel; #703538]
|
||||
* Consolidate design of login screen and unlock dialog [Ray; #702308, #704795]
|
||||
* Respect hasWorkspace property of session mode [Jasper; #698593]
|
||||
* Fix fade of app menu icon in RTL locales [Jasper; #704583]
|
||||
* Destroy notifications when the close button is clicked [Adel; #687016]
|
||||
* Fix clicks on legacy tray icons in the message tray [Florian; #704095]
|
||||
* authPrompt: Fade out message if users start to type [Ray; #704817]
|
||||
* Export timestamps of global shortcuts on DBus [Bastien; #704859]
|
||||
* Fix duplicate search provider results [Jasper; #700283]
|
||||
* Misc bug fixes and cleanups [Lionel, Florian, Emilio, Ray, Jasper; #703859,
|
||||
#703540, #704077, #703997, #704318, #704347, #704265, #704411, #704430,
|
||||
#704347, #704453, #704471, #704542, #704707, #703905, #705037]
|
||||
|
||||
Contributors:
|
||||
Allan Day, Adel Gadllah, Lionel Landwerlin, Florian Müllner, Bastien Nocera,
|
||||
Emilio Pozuelo Monfort, Jasper St. Pierre, Ray Strode, Colin Walters,
|
||||
Michael Wood
|
||||
|
||||
Translations:
|
||||
eternalhui [zh_CN], Victor Ibragimov [tg], Dušan Kazik [sk],
|
||||
Jiro Matsuzawa [ja], Kjartan Maraas [nb], Milo Casagrande [it],
|
||||
Marek Černocký [cs], Daniel Mustieles [es], Benjamin Steinwender [de]
|
||||
|
||||
3.9.4
|
||||
=====
|
||||
* Fix chat entries not being focused when expanded [Jasper; #698778]
|
||||
* Fix alignment of "Not Listed?" label [Mathieu; #702307]
|
||||
* Fix alignment of time stamps in chat notifications [Carlos; #687809]
|
||||
* Round the ends of slider trough [Jasper; #702825]
|
||||
* Add support for "box-shadow: none" [Cosimo; #702782]
|
||||
* Keep chrome below popup windows [Florian; #702338]
|
||||
* Move the session list to a popup menu [Ray; #702818]
|
||||
* Fix autorun notifications for "non-native" volumes [Matthias; #703418]
|
||||
* dnd: Speed up by not picking on each motion event [Jasper; #703443]
|
||||
* Fix management of asynchronous background loading [Lionel; #703001]
|
||||
* Rework focus handling [Jasper; #700735]
|
||||
* Optimize box-shadow rendering [Lionel; #689858]
|
||||
* Remove support for fixed positioning in BoxLayouts [Florian; #703808]
|
||||
* Misc bug fixes and cleanups [Adel, Jasper, Florian, Ray, Lionel, Emilio;
|
||||
#702849, #610279, #703132, #703105, #703160, #703126, #703304, #703403,
|
||||
#698593, #703442, #703565, #700901, #703874, #703807, #703893, #703909]
|
||||
|
||||
Contributors:
|
||||
Mathieu Bridon, Giovanni Campagna, Cosimo Cecchi, Matthias Clasen,
|
||||
Fran Diéguez, Adel Gadllah, Lionel Landwerlin, Florian Müllner,
|
||||
Emilio Pozuelo Monfort, Carlos Soriano, Jasper St. Pierre, Ray Strode
|
||||
|
||||
Translations:
|
||||
Baurzhan Muftakhidinov [kk], Marek Černocký [cs], Daniel Mustieles [es],
|
||||
Fran Diéguez [gl], Kjartan Maraas [nb], Andika Triwidada [id],
|
||||
Benjamin Steinwender [de], Nguyễn Thái Ngọc Duy [vi], Trần Ngọc Quân [vi]
|
||||
|
||||
3.9.3
|
||||
=====
|
||||
* Don't push window thumbs when workspace switcher is hidden [Jasper; #701167]
|
||||
* Tweak timeout for activating windows during XDND [Adel; #700150]
|
||||
* Fix ellipsization in control buttons in app picker [Carlos; #696307]
|
||||
* Fix DND to empty dash [Florian; #684618]
|
||||
* Fix OSD window appearing below system modal dialogs [Rui; #701269]
|
||||
* Clear clipboard on screen lock to prevent information leak [Florian; #698922]
|
||||
* Allow session mode specific overrides schema [Florian; #701717]
|
||||
* window-switcher: Only show windows from current workspace by default
|
||||
[Florian; #701214]
|
||||
* logout dialog: Show the correct text right away [Matthias; #702056]
|
||||
* bluetooth: Port to bluez 5 [Emilio; #700891]
|
||||
* dateMenu: Allow events to span multiple lines [Giovanni; #701231]
|
||||
* gdm: Clear message queue when no more messages are pending [Jonh; #702458]
|
||||
* Misc bug fixes and cleanups [Jasper, Florian, Adel, Giovanni; #693836,
|
||||
#700972, #701386, #700877, #701755, #698918, #701224, #702125, #701954,
|
||||
#701849, #702121]
|
||||
|
||||
Contributors:
|
||||
Giovanni Campagna, Matthias Clasen, Fran Diéguez, Adel Gadllah, Rui Matos,
|
||||
Florian Müllner, Emilio Pozuelo Monfort, Carlos Soriano, Jasper St. Pierre,
|
||||
Jonh Wendell
|
||||
|
||||
Translations:
|
||||
Marek Černocký [cs], Victor Ibragimov [tg], Fran Diéguez [gl],
|
||||
Benjamin Steinwender [de], Cheng-Chia Tseng [zh_HK, zh_TW],
|
||||
eternalhui [zh_CN], Ivaylo Valkov [bg], Kjartan Maraas [nb],
|
||||
Daniel Mustieles [es]
|
||||
|
||||
3.9.2
|
||||
=====
|
||||
* Use a symbolic icon for DESKTOP windows [Matthias; #697914]
|
||||
* Move paint state cache into StWidget [Jasper; #697274]
|
||||
* gdm: Fix regression where domain login hint not shown [Stef; #698200]
|
||||
* Make calendar keyboard navigable [Tanner; #667434]
|
||||
* Hide "Open Calendar" item when no calendar app is installed [Lionel; #697725]
|
||||
* Update how branding appears on login screen [Florian; #694912, #699877]
|
||||
* Allow OSD popups to grow if necessary [Marta; #696523]
|
||||
* Fix offset of shadow offscreen rendering [Lionel; #698301]
|
||||
* Fix insensitive button preventing empty keyring password [Stef; #696304]
|
||||
* Allow cancelling keyring dialog between prompts [Stef; #682830]
|
||||
* modalDialog: Show spinner while working [Stef; #684438]
|
||||
* overview: Only show close buttons for windows that may close [Jasper; #699269]
|
||||
* Add input purpose and hints to StEntry and StIMText [Daiki; #691392]
|
||||
* Set input-purpose property for password entries [Rui; #700043]
|
||||
* Provide a DBus API for screencasting [Florian; #696247]
|
||||
* overview: Disable hotcorner during DND [Jasper; #698484]
|
||||
* polkitAgent: Allow retrying after mistyped passwords [Stef; #684431]
|
||||
* Add a way to get backtraces from criticals and warnings [Giovanni; #700262]
|
||||
* Allow switch-to-workspace-n keybindings in overview [Florian; #649977]
|
||||
* Update man page [Matthias; #700339]
|
||||
* Add FocusSearch DBus method [Florian; #700536]
|
||||
* Hide frequent view when app monitoring is disabled [Florian; #699714]
|
||||
* Show switcher popup for switch-to-workspace-n keybindings [Elad; #659288]
|
||||
* gdm: Update the session chooser style [Allan; #695742]
|
||||
* Fix some app folders getting truncated at the top [Florian; #694371]
|
||||
* Don't block the message tray while a notification is showing [Jasper; #700639]
|
||||
* popupMenu: Allow for an optional border for slider handle [Florian; #697917]
|
||||
* Re-lock screen when restarted after a crash [Colin; #691987]
|
||||
* Synchronize input source switching with key events [Rui; #697007]
|
||||
* Switch input source on modifiers-only accelerator [Rui; #697008]
|
||||
* Allow input source switching in message tray [Rui; #697009]
|
||||
* Misc bug fixes and cleanups [Alban, Jasper, Giovanni, Florian, Rui, Tomeu,
|
||||
Stef, Gustavo; #698863, #699799, #699800, #676285, #699975, #700097, #698812,
|
||||
#698486, #700194, #695314, #700257, #699678, #700356, #700322, #700394,
|
||||
#700409, #700595, #700625, #691746, #700620, #700807, #659288, #700784,
|
||||
#700842, #700847, #700488, #700735, #696159, #700900, #700853, #700923,
|
||||
#700944, #697661, #700854, #700190, #699189, #701097]
|
||||
|
||||
Contributors:
|
||||
Elad Alfassa, Alban Browaeys, Giovanni Campagna, Matthias Clasen, Allan Day,
|
||||
Tanner Doshier, Lionel Landwerlin, Rui Matos, Simon McVittie,
|
||||
Marta Milakovic, Florian Müllner, Gustavo Padovan, Jasper St. Pierre,
|
||||
Daiki Ueno, Tomeu Vizoso, Stef Walter, Colin Walters
|
||||
|
||||
Translations:
|
||||
Matej Urbančič [sl], Kjartan Maraas [nb], Victor Ibragimov [tg],
|
||||
Dušan Kazik [sk], Gil Forcada [ca], Daniel Mustieles [es]
|
||||
|
||||
3.9.1
|
||||
=====
|
||||
* Add additional toggle-overview keybinding [Matthias; #698251]
|
||||
* Disable <super> shortcut when sticky keys are enabled [Matthias; #685974]
|
||||
* Disable tray context menu while a notification displays [Jasper; #695800]
|
||||
* Watch GApplication busy state [Cosimo; #697207]
|
||||
* Disable style transitions if animations are disabled [Jasper; #698391]
|
||||
* Filter out hidden applications from "Frequent" view [Giovanni; #696949]
|
||||
* Fix window previews swapping place randomly [Jasper; #694469, #698776]
|
||||
* Add support for serialized GIcons in remote search providers [Cosimo; #698761]
|
||||
* Fix hotcorner regression in RTL locales [Jasper; #698884]
|
||||
* Allow some keybindings to work while a top bar menu is open [Florian; #698938]
|
||||
* Make open-app-menu keybinding a toggle action [Florian; #686756]
|
||||
* Only recognize common URL schemes in notification messages [Monica; #661225]
|
||||
* Misc fixes and cleanups [Tim, Jasper, Florian, Giovanni, Owen; #698531,
|
||||
#698622, #698427, #698483, #698513, #697203, #698959, #698918, #699029,
|
||||
#699075, #696720, #649748]
|
||||
|
||||
Contributors:
|
||||
Giovanni Campagna, Cosimo Cecchi, Monica Chelliah, Matthias Clasen, Tim Lunn,
|
||||
Florian Müllner, Jasper St. Pierre, Michael Wood, Owen W. Taylor
|
||||
|
||||
Translations:
|
||||
Fran Diéguez [gl], Muhammet Kara [tr], Daniel Mustieles [es],
|
||||
Gil Forcada [ia], Anish A [ml], Dimitris Spingos [el], Marek Černocký [cs],
|
||||
Žygimantas Beručka [lt]
|
||||
|
||||
3.8.1
|
||||
=====
|
||||
* Clip window group during startup animation [Jasper; #696323]
|
||||
* Check for logind rather than systemd [Martin; #696252]
|
||||
* Don't special-case last remote search provider position [Giovanni; #694974]
|
||||
* Fix memory leaks [Ray, Jasper; ##697119, #697295, #697300, #697395]
|
||||
* AppSwitcherPopup: Activate only the selected window if any [Rui; #697480]
|
||||
* Enable screen recorder keybinding in all modes [Florian; #696200]
|
||||
* Remove box-shadow from screen shield for performance reasons [Adel; #697274]
|
||||
* Add support for -st-natural-width/height CSS properties [Giovanni; #664411]
|
||||
* Remove excessive padding from notification buttons [Allan; #664411]
|
||||
* Fix thumbnail dragging in overview [Jasper; #697504]
|
||||
* theme-node: Add get_url()/lookup_url() methods [Florian; #693688]
|
||||
* Misc bug fixes and cleanups [Jasper, Rui, Colin, David, Ray, Matthias:
|
||||
#695859, #696259, #696585, #696436, #697432, #697435, #697560, #697722,
|
||||
#697709]
|
||||
|
||||
Contributors:
|
||||
Giovanni Campagna, Matthias Clasen, Allan Day, Adel Gadllah, David Gumberg,
|
||||
Rui Matos, Florian Müllner, Martin Pitt, Jasper St. Pierre, Ray Strode,
|
||||
Colin Walters
|
||||
|
||||
Translations:
|
||||
Daniel Martinez [an], Bruce Cowan [en_GB], Khaled Hosny [ar],
|
||||
Ihar Hrachyshka [be], Aron Xu [zh_CN], Wojciech Szczęsny [pl],
|
||||
Inaki Larranaga Murgoitio [eu], Kjartan Maraas [nb],
|
||||
Милош Поповић [sr, sr@latin], Trần Ngọc Quân [vi]
|
||||
|
||||
3.8.0.1
|
||||
=======
|
||||
* Background bug fixes [Ray; #696712]
|
||||
|
||||
3.8.0
|
||||
=====
|
||||
* Remove blur and desaturation from lock screen [Jasper; #696322]
|
||||
* Remove scroll view fade near edges [Adel; #696404]
|
||||
* dateMenu: Open calendar component when using Evolution [Florian; #696432]
|
||||
* Fix unlocking on fast user switch [Cosimo; #696287]
|
||||
* Tweak screen shield animation [Rui; #696380]
|
||||
* Fix major memory leak when changing backgrounds [Ray; #696157]
|
||||
* Miscellaneous bug fixes [Jasper, Adel, Florian; #696199, #696212, #696422,
|
||||
#696447, #696235]
|
||||
|
||||
Contributors:
|
||||
Giovanni Campagna, Cosimo Cecchi, Adel Gadllah, Rui Matos, Florian Müllner,
|
||||
Jasper St. Pierre, Ray Strode
|
||||
|
||||
Translations:
|
||||
Alexandre Franke [fr], Victor Ibragimov [tg], Arash Mousavi [fa],
|
||||
Gabor Kelemen [hu], Sandeep Sheshrao Shedmake [mr], ManojKumar Giri [or],
|
||||
Shantha kumar [ta], Rajesh Ranjan [hi], Stas Solovey [ru],
|
||||
Shankar Prasad [kn], Dušan Kazik [sk], Ihar Hrachyshka [be],
|
||||
Wouter Bolsterlee [nl], Kris Thomsen [da], Jiro Matsuzawa [ja],
|
||||
Daniel Korostil [uk], Ani Peter [ml], Krishnababu Krothapalli [te],
|
||||
Mantas Kriaučiūnas [lt], Praveen Illa [te]
|
||||
|
||||
3.7.92
|
||||
======
|
||||
* Drop fallback lock implementation [Florian; #693403]
|
||||
* Don't let the user trigger message-tray when in fullscreen [Jasper; #694997]
|
||||
* Scroll search results when using keynav [Jasper; #689681]
|
||||
* Allow raising the shield by starting to type the password [Jasper; #686740]
|
||||
* Improve tracking of fullscreen windows [Owen; #649748]
|
||||
* Improve animation of new windows in overview [Giovanni; #695582]
|
||||
* workspace switcher: Animate new workspaces created by DND [Giovanni; #685285]
|
||||
* Give user time to read messages on login screen [Ray; #694688]
|
||||
* Misc bug fixes and cleanups [Jasper, Ray, Florian, Cosimo, Giovanni, Adel,
|
||||
Stef, Takao, Rui, Neil; #695154, #694993, #695272, #691578, #694321, #695338,
|
||||
#695409, #695458, #695526, #695601, #695471, #695324, #695650, #695656,
|
||||
#695659, #695485, #695395, #694951, #695824, #695841, #695801, #694321,
|
||||
#693708, #695800, #695607, #695882, #691578, #685851, #694371, #690857,
|
||||
#694092, #695747, #696007, #693438, #696064, #696102
|
||||
|
||||
Contributors:
|
||||
Giovanni Campagna, Cosimo Cecchi, Allan Day, Takao Fujiwara, Adel Gadllah,
|
||||
Tim Lunn, Rui Matos, Florian Müllner, Neil Roberts, Jasper St. Pierre,
|
||||
Ray Strode, Stef Walter, Colin Walters, Owen W. Taylor
|
||||
|
||||
Translations:
|
||||
Nilamdyuti Goswami [as], Chao-Hsiung Liao [zh_HK, zh_TW],
|
||||
Yuri Myasoedov [ru], Gheyret Kenji [ug], Baurzhan Muftakhidinov [kk],
|
||||
Ville-Pekka Vainio [fi], Matej Urbančič [sl],
|
||||
Мирослав Николић [sr, sr@latin], Rūdolfs Mazurs [lv], Christian Kirbach [de],
|
||||
Andika Triwidada [id], Gil Forcada [ca], Mattias Põldaru [et],
|
||||
Duarte Loreto [pt], Adam Matoušek [cs], Changwoo Ryu [ko],
|
||||
Ihar Hrachyshka [be], Carles Ferrando [ca@valencia], Sweta Kothari [gu]
|
||||
|
||||
3.7.91
|
||||
======
|
||||
* overview: Fade out controls during DND that are not targets [Cosimo; #686984]
|
||||
* overview: Keep open when a Control key is held [Florian; #686984]
|
||||
* Improve login screen => session transition [Ray; #694321]
|
||||
* Center application grid horizontally [Florian; #694261]
|
||||
* Fix hiding panel when fullscreen windows span multiple monitors [Adel; 646861]
|
||||
* Tweak thresholds of pressure barrier [Jasper; #694467]
|
||||
* Tweak window picker layout [Jasper; #694902]
|
||||
* Expose key grab DBus API to gnome-settings-daemon [Florian; #643111]
|
||||
* Don't always show message tray in overview, add message indicator
|
||||
[Cosimo; #687787]
|
||||
* Tweak startup animation [Ray; #694326]
|
||||
* Add OSD popups and expose them to gnome-settings-daemon [Florian; #613543]
|
||||
* Move loupe icon to the start of the search entry [Jasper; #695069]
|
||||
* Don't show the input switcher with less than 2 items [Rui; 695000]
|
||||
* Fix auto-completion of system modals immediately upon display [Stef; #692937]
|
||||
* Ignore workspaces in alt-tab [Florian; #661156]
|
||||
* Disable copying text from password entries [Florian; #695104]
|
||||
* Use standard styling for ibus candidate popups [Allan; #694796]
|
||||
* Fix calendar changing height on month changes [Giovanni; #641383]
|
||||
* Port the hot corner to use pressure barriers [Jasper; #663661]
|
||||
* Misc bug fixes and cleanups: [Hashem, Giovanni, Alban, Jasper, Cosimo,
|
||||
Florian, Adel, Daniel, Matthias, Ray, Rui, Guillaume, Stef; #685849, #690643,
|
||||
#694292, #693814, #694234, #694365, #694287, #694336, #694256, #694261,
|
||||
#663601, #694441, #694284, #694463, #694475, #687248, #694394, #694320,
|
||||
#694701, #694784, #694858, #694906, #694327, #694876, #694905, #694969,
|
||||
#694970, #694988, #695006, #695001, #694998, #695023, #695002, #695073,
|
||||
#695126, #687748, #694837, #693907, #679851, #694988]
|
||||
|
||||
Contributors:
|
||||
Giovanni Campagna, Cosimo Cecchi, Matthias Clasen, Alban Crequy, Allan Day,
|
||||
Guillaume Desmottes, Adel Gadllah, Rui Matos, Daniel Mustieles,
|
||||
Hashem Nasarat, Jasper St. Pierre, Ray Strode, Stef Walter
|
||||
|
||||
Translations:
|
||||
Yuri Myasoedov [ru], Adam Matoušek [cs], Piotr Drąg [pl], Matej Urbančič [sl],
|
||||
Sweta Kothari [gu], Kjartan Maraas [nb], Nguyễn Thái Ngọc Duy [vi],
|
||||
Chao-Hsiung Liao [zh_HK, zh_TW], Dimitris Spingos [el],
|
||||
Inaki Larranaga Murgoitio [eu], Luca Ferretti [it], A S Alam [pa],
|
||||
Gheyret Kenji [ug], Stas Solovey [ru], Enrico Nicoletto [pt_BR],
|
||||
Fran Diéguez [gl], Daniel Mustieles [es], Aurimas Černius [lt]
|
||||
|
||||
3.7.90
|
||||
======
|
||||
* Let GNOME Shell work on EGL and GLES2 [Neil; #693225, #693438, #693339]
|
||||
* Implement middle-click paste [Jasper; #645019]
|
||||
* Fix top bar transition between session modes [Rui; #692966]
|
||||
* Trigger the message tray with downward pressure [Jasper; #677215]
|
||||
* Don't ask for a password on shutdown [Adel; #693385]
|
||||
* Add a context menu to the message tray [Adel; #691035, #693887]
|
||||
* Use unicode formatting in the date menu [Matthias; #689251]
|
||||
* Use proper ellipsis instead of three dots [Jeremy; #689542]
|
||||
* Tweak screen shield animation [Giovanni; #691964]
|
||||
* Always hide the OSK when showing the message tray [Florian; #662687]
|
||||
* Support sound in notifications [Giovanni; #642831]
|
||||
* Place application popup menus above chrome [Jasper; #633620]
|
||||
* Hide overview elements while searching [Cosimo; #682050]
|
||||
* Implement updated IBus candidate popup designs [Rui; #691902]
|
||||
* Add support for enable-animations preference [Cosimo; #655746]
|
||||
* Don't always show the message tray in the overview [Cosimo; #693987]
|
||||
* Improve arrangement of window previews [Adel; #690313]
|
||||
* Remove builtin settings provider [Giovanni; #690824]
|
||||
* Minimize fullscreen windows when they end up in the background [Adel; #693991]
|
||||
* Add context menu to the background actor [Jasper; #681540]
|
||||
* Handle backgrounds in the shell, improve startup animation [Ray; #682429]
|
||||
* Hide universal access menu when not needed [Giovanni; #681528]
|
||||
* Implement updated app picker designs [Florian; #694192]
|
||||
* Improve login manager -> session transition [Ray; #694062]
|
||||
* Don't use a grid layout in window picker [Adel; #694210]
|
||||
* Use scroll wheel for workspace switching rather than zoom [Florian; #686639]
|
||||
* Misc bug fixes and cleanups: [Jasper, Florian, Debarshi, Adel, Matthias,
|
||||
Giovanni, Daiki, Rico, Bastien, Cosimo, Ray, Allan, Antonio; #693284,
|
||||
#692680, #691746, #693303, #693162, #693161, #693522, #693385, #691715,
|
||||
#688915, #689106, #682429, #693570, #693737, #693458, #692845, 693836,
|
||||
#681540, #679925, #688227, #692773, #693909, #683288, #693854, #693746,
|
||||
#693931, #693924, #693940, #693970, #693935, #693937, #693974, #693936,
|
||||
#693975, #693822, #694030, #685849, #694052, #694035, #694038, #694079,
|
||||
#694064, #681735, #694100, #694057, #694070, #693572, #693896, #686984,
|
||||
#694123, #694125, #693756, #693757, #687556, #694215, 694062, #694227,
|
||||
#694240, #694234, #694264, 694276, 694282, #694241, #689394, #694202,
|
||||
#694265, #694289, #691806, #694290, #694296]
|
||||
|
||||
Contributors:
|
||||
Jeremy Bicha, Giovanni Campagna, Cosimo Cecchi, Matthias Clasen, Allan Day,
|
||||
António Fernandes, Adel Gadllah, Rui Matos, Florian Müllner, Bastien Nocera,
|
||||
Debarshi Ray, Neil Roberts, Jasper St. Pierre, Ray Strode, Rico Tzschichholz,
|
||||
Daiki Ueno
|
||||
|
||||
Translations:
|
||||
Yasumichi Akahoshi [ja], Yoji TOYODA [ja], Dušan Kazik [sk],
|
||||
Wouter Bolsterlee [nl], Matej Urbančič [sl], Gheyret Kenji [ug],
|
||||
Ivaylo Valkov [bg], Daniel Korostil [uk], Gheyret Kenji [ug],
|
||||
Daniel Mustieles [es], Anish A [ml], Gil Forcada [ca],
|
||||
Carles Ferrando [ca@valencia], Мирослав Николић [sr, sr@latin],
|
||||
Aurimas Černius [lt], Rafael Ferreira [pt_BR], Fran Diéguez [gl],
|
||||
Piotr Drąg [pl], Luca Ferretti [it], A S Alam [pa]
|
||||
|
||||
3.7.5
|
||||
=====
|
||||
* MessageTray: pass keyboard events to tray icons [Giovanni; #687425]
|
||||
* network: add support for virtual devices (vlan, bond, bridge) [Dan; #677144]
|
||||
* gdm: Allow right-clicking buttons for left-handed users [Jasper; #688748]
|
||||
* Make list search results span all available horizontal space [Tanner; #691967]
|
||||
* Make Show-Applications button depress when held down [Hashem; #692319]
|
||||
* Set a max width on search results [Cosimo; #692453]
|
||||
* Reserve scrollbar allocation for automatic policy [Cosimo; #686881]
|
||||
* Improve scaling algorithm for window thumbnails [Jasper; #686944]
|
||||
* Fix launching settings panels after g-c-c changes [Jasper; #692483]
|
||||
* Stop launching applications from empty searches [Hashem; #692391]
|
||||
* Implement per-source notification filtering [Giovanni; #685926]
|
||||
* ScreenShield: Omit ActiveChanged() signal at end of fade [Giovanni; #691964]
|
||||
* ScreenShield: Lower the shield on idle before locking [Giovanni; #692560]
|
||||
* Make previews of minimized windows translucent in overview [Florian; #692999]
|
||||
* windowManager: Respect icon geometry when minimizing [Florian; #692997]
|
||||
* ScreenShield: Only show lock icon when actually locked [Giovanni; #693007]
|
||||
* general: Use & instead of 'and' for Settings panels [Jeremy; #689590]
|
||||
* network: Add support for new ModemManager1 interface [Aleksander; #687359]
|
||||
* network: Handle LTE-only modems as GSM ones [Aleksander; #688144]
|
||||
* mobile-providers: Port to libnm-gtk [Aleksander; #688943]
|
||||
* general: Consistently use Title Case in top bar [Jeremy; #689589]
|
||||
* panel: Add :overview pseudo class while in overview [Florian; #693218]
|
||||
* sessionMode: Add support for mode-specific styling [Florian; #693219]
|
||||
* loginManager: Make suspend a NOP in the ConsoleKit patch [Florian; #693162]
|
||||
* screenShield: Inhibit suspend until the screen is locked [Florian; #686482]
|
||||
* Misc bug fixes and cleanups [Jasper, Giovanni, Rui, Cosimo, Florian, Stefano,
|
||||
Adel, Yanko; #691745, #691731, #690171, #689091, #691976, #691963, #684279,
|
||||
#692052, #692091, #642831, #692454, #692715, #692678, #692723, #692677,
|
||||
#683986, #692693, #692749, #692948, #692995, #692996, #692994, #677215,
|
||||
#692586, #693067, #693031, #693049, #643111, #693161, #693220]
|
||||
|
||||
Contributors:
|
||||
Jeremy Bicha, Giovanni Campagna, Cosimo Cecchi, Tanner Doshier,
|
||||
Stefano Facchini, Adel Gadllah, Yanko Kaneti, Rui Matos, Aleksander Morgado,
|
||||
Florian Müllner, Hashem Nasarat, Jasper St. Pierre, Dan Winship
|
||||
|
||||
Translations:
|
||||
Duarte Loreto [pt], Daniel Mustieles [es], Kjartan Maraas [nb],
|
||||
Nilamdyuti Goswami [as], Мирослав Николић [sr,sr@latin],
|
||||
Tobias Endrigkeit [de], Fabio Tomat [fur], Matej Urbančič [sl], A S Alam [pa],
|
||||
Inaki Larranaga Murgoitio [eu], Piotr Drąg [pl], Wouter Bolsterlee [nl],
|
||||
Gheyret Kenji [ug], Yaron Shahrabani [he], Chao-Hsiung Liao [zh_HK,zh_TW],
|
||||
Milo Casagrande [it], Benjamin Steinwender [de]
|
||||
|
||||
3.7.4.1
|
||||
=======
|
||||
* userMenu: Use show-full-name-in-top-bar setting [Bastien; #689561]
|
||||
* dateMenu: Add "Open Clocks" entry [Mathieu; #644390]
|
||||
* screenshot: Immediately show the flash spot [Jasper; #691875]
|
||||
* Misc. bug fixes [Rico, Jeremy]
|
||||
|
||||
Contributors:
|
||||
Jeremy Bicha, Mathieu Bridon, Bastien Nocera, Jasper St. Pierre,
|
||||
Rico Tzschichholz
|
||||
|
||||
Translations:
|
||||
Ihar Hrachyshka [be]
|
||||
|
||||
3.7.4
|
||||
=====
|
||||
* Make menu separators crisp [Giovanni, Allan; #641745]
|
||||
* power: Update for new D-Bus name [Bastien; #690506]
|
||||
* Add smooth scrolling support [Jasper; #687573]
|
||||
* Tweak notification layout [Allan; #688506]
|
||||
* Ping the active window when using the app menu [Giovanni; #684340]
|
||||
* Make password entries insensitive after submission [Jasper; #690594, #690895]
|
||||
* Honor lock-delay GSettings key [Giovanni, Matthias; #690766, #691170]
|
||||
* Use text/calendar preferred app as the calendar app [Giovanni; #690767]
|
||||
* lookingGlass: Move to an inspect() function [Jasper; #690726]
|
||||
* Make OSK animation quicker, snappier [Rui; #688642]
|
||||
* Allow to close chat notifications with Escape [Jasper; #690897]
|
||||
* Honor org.gnome.desktop.screensaver.user-switch-enabled [Giovanni; #691042]
|
||||
* Add a SelectArea() DBus method [Cosimo; #687954]
|
||||
* Support non-absolute paths when saving screenshots [Cosimo; #688004]
|
||||
* OSK: Fix extended keys popups [Rui; #674955]
|
||||
* Don't hide or show the keyboard immediately [Rui; #688646]
|
||||
* Improve padding in power menu [Giovanni; #689297]
|
||||
* Add per-window input source switching [Rui; #691414]
|
||||
* Misc bug fixes and cleanups [Rico, Jasper, Giovanni, Rui, Florian, Dan;
|
||||
#690608, #690589, #690539, #687081, #690667, #690665, #690666, #685856,
|
||||
#690858, #690895, #680414, #690965, #691019, #690590, #681376, #690180,
|
||||
#685513, #689263, #691553, #691720, #691743, #691750]
|
||||
|
||||
Contributors:
|
||||
Giovanni Campagna, Cosimo Cecchi, Matthias Clasen, Allan Day, Rui Matos,
|
||||
Florian Müllner, Bastien Nocera, Jasper St. Pierre, Rico Tzschichholz,
|
||||
Dan Winship
|
||||
|
||||
Translations:
|
||||
Matej Urbančič [sl], Kjartan Maraas [nb], Mattias Põldaru [et],
|
||||
Yaron Shahrabani [he], Aurimas Černius [lt], Khaled Hosny [ar],
|
||||
Fran Diéguez [gl], Daniel Mustieles [es], Piotr Drąg [pl], Balázs Úr [hu],
|
||||
Baurzhan Muftakhidinov [kk], Tobias Endrigkeit [de], Dušan Kazik [sk],
|
||||
Aron Xu [zh_CN], Gheyret Kenji [ug]
|
||||
|
||||
3.7.3
|
||||
=====
|
||||
* Add 'No Messages' label when message tray is empty [Victoria; #686738]
|
||||
* Use better icons in Ctrl-Alt-Tab popup [Stéphane; #641303]
|
||||
* Show the OSK on the monitor where the focused window lives [Giovanni; #685856]
|
||||
* Highlight window clone and caption when hovered [Giovanni, Marc; #665310]
|
||||
* Improve login process indication [Stéphane; #687113]
|
||||
* Omit empty categories in apps view [Stéphane; #687970]
|
||||
* Style panel differently according to mode [Florian; #684573]
|
||||
* Make it possible to hide the user name [Matthias; #688577]
|
||||
* Consolidate and improve chat connection notifications [Giovanni; #687213]
|
||||
* Improve notification scrollbar appearance [Carlos; #688393]
|
||||
* Fade scroll view fade near scrolling edges [Jasper; #689249]
|
||||
* Add a read-only org.gnome.Shell.Mode property [Debarshi; #689300]
|
||||
* Don't close message tray after using context menus [Giovanni; #689296]
|
||||
* Port swipe-scrolling to ClutterPanAction [Jasper, Florian; #689062, #689552]
|
||||
* Remember state of 'Remember Password' checkbox [Ron; #688039]
|
||||
* Improve timestamp format in chat notifications [Carlos; #680989]
|
||||
* Improve style of missed-messages counter [Carlos; #686472]
|
||||
* Omit connection failure notifications if cancelled by user [Giovanni; #684823]
|
||||
* Add window-based Alt-Tab popup [Florian; #688913]
|
||||
* Support external session mode definitions [Florian; #689304]
|
||||
* Support session-mode-specific extensions [Florian; #689305]
|
||||
* Support 'parentMode' property in session modes [Florian; #689308]
|
||||
* Support a new org.gnome.ShellSearchProvider2 DBus interface
|
||||
[Cosimo; #689735, #690009]
|
||||
* Add "windows" to Ctrl-Alt-Tab popup [Jasper; #689653]
|
||||
* Port PopupMenu to GrabHelper [Jasper; #689109, #689954]
|
||||
* Show headphone icon when headphones are plugged in [Giovanni; #675902]
|
||||
* Display (non-app) search results as list [Tanner, Cosimo; #681797]
|
||||
* Skip diacritical marks in search terms [Aleksander; #648587]
|
||||
* Expose all engine options in input sources [Giovanni, Rui; #682318]
|
||||
* Add input source switcher popup [Rui; #682315]
|
||||
* Add minimal support for InfiniBand in network menu [Dan; #677150]
|
||||
* Misc bug fixes and cleanups [Sebastian, Aleksander, Giovanni, Tim, Cosimo,
|
||||
Florian, Matthias, Rui, Lionel, Colin, Piotr, Guillaume, Bastien, Tanner,
|
||||
Carlos, Stéphane, Jakub; #688422, #688379, #688750, #688771, #686800,
|
||||
#688133, #688895, #688966, #683986, #688004, #689108, #689029, #683449,
|
||||
#688196, #689304, #689243, #689295, #689325, #689400, #679168, #689568,
|
||||
#689537, #689528, #689749, #689789, #689353, #689820, #689868, #689778,
|
||||
#689959, #688589, #688589, #689955, #687250, #689965, #690046, #690049,
|
||||
#689884, #682286, #690173, #690174, #672941, #689876, #687881, #690171,
|
||||
#690241, #690312, #690175, #687955, #650843, #688234, #690427
|
||||
|
||||
Contributors:
|
||||
Giovanni Campagna, Cosimo Cecchi, Matthias Clasen, Stéphane Démurget,
|
||||
Guillaume Desmottes, Tanner Doshier, Piotr Drąg, Sebastian Keller,
|
||||
Lionel Landwerlin, Tim Lunn, Victoria Martínez de la Cruz, Aleksander Morgado,
|
||||
Florian Müllner, Bastien Nocera, Marc Plano-Lesay, Carlos Soriano Sánchez,
|
||||
Jakub Steiner, Jasper St. Pierre, Colin Walters, Dan Winship, Ron Yorston
|
||||
|
||||
Translations:
|
||||
Yuri Myasoedov [ru], Wouter Bolsterlee [nl], Yaron Shahrabani [he],
|
||||
Nilamdyuti Goswami [as], Ani Peter [ml], Kjartan Maraas [nb],
|
||||
Dr.T.Vasudevan [ta], A S Alam [pa], Shankar Prasad [kn], Khaled Hosny [ar],
|
||||
Daniel Mustieles [es], Dušan Kazik [sk]
|
||||
|
||||
3.7.2
|
||||
=====
|
||||
* Enforce RTL in he for messages that might end up as LTR [Florian; #686630]
|
||||
|
@ -13,6 +13,14 @@ PKG_NAME="gnome-shell"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Fetch submodules if needed
|
||||
if test ! -f src/gvc/Makefile.am;
|
||||
then
|
||||
echo "+ Setting up submodules"
|
||||
git submodule init
|
||||
fi
|
||||
git submodule update
|
||||
|
||||
which gnome-autogen.sh || {
|
||||
echo "You need to install gnome-common from GNOME Git (or from"
|
||||
echo "your OS vendor's package manager)."
|
||||
|
@ -17,5 +17,4 @@ libgnome_shell_browser_plugin_la_SOURCES = \
|
||||
|
||||
libgnome_shell_browser_plugin_la_CFLAGS = \
|
||||
$(BROWSER_PLUGIN_CFLAGS) \
|
||||
-DG_DISABLE_DEPRECATED \
|
||||
-DG_LOG_DOMAIN=\"GnomeShellBrowserPlugin\"
|
||||
|
176
configure.ac
176
configure.ac
@ -1,5 +1,5 @@
|
||||
AC_PREREQ(2.63)
|
||||
AC_INIT([gnome-shell],[3.7.2],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell])
|
||||
AC_INIT([gnome-shell],[3.10.0.1],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell])
|
||||
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
AC_CONFIG_SRCDIR([src/shell-global.c])
|
||||
@ -16,8 +16,6 @@ m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])
|
||||
|
||||
# Checks for programs.
|
||||
AC_PROG_CC
|
||||
# Needed for per-target cflags, like in gnomeshell-taskpanel
|
||||
AM_PROG_CC_C_O
|
||||
|
||||
# Initialize libtool
|
||||
LT_PREREQ([2.2.6])
|
||||
@ -26,9 +24,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",
|
||||
@ -55,81 +50,71 @@ if $PKG_CONFIG --exists gstreamer-1.0 '>=' $GSTREAMER_MIN_VERSION ; then
|
||||
AC_MSG_RESULT(yes)
|
||||
build_recorder=true
|
||||
recorder_modules="gstreamer-1.0 gstreamer-base-1.0 x11 gtk+-3.0"
|
||||
PKG_CHECK_MODULES(TEST_SHELL_RECORDER, $recorder_modules clutter-1.0 xfixes gl)
|
||||
PKG_CHECK_MODULES(TEST_SHELL_RECORDER, $recorder_modules clutter-1.0)
|
||||
else
|
||||
AC_MSG_RESULT(no)
|
||||
fi
|
||||
|
||||
AM_CONDITIONAL(BUILD_RECORDER, $build_recorder)
|
||||
|
||||
CLUTTER_MIN_VERSION=1.11.11
|
||||
CLUTTER_MIN_VERSION=1.13.4
|
||||
GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1
|
||||
GJS_MIN_VERSION=1.33.2
|
||||
MUTTER_MIN_VERSION=3.7.2
|
||||
GTK_MIN_VERSION=3.3.9
|
||||
GIO_MIN_VERSION=2.35.0
|
||||
GJS_MIN_VERSION=1.35.4
|
||||
MUTTER_MIN_VERSION=3.10.0
|
||||
GTK_MIN_VERSION=3.7.9
|
||||
GIO_MIN_VERSION=2.37.0
|
||||
LIBECAL_MIN_VERSION=3.5.3
|
||||
LIBEDATASERVER_MIN_VERSION=3.5.3
|
||||
LIBEDATASERVERUI_MIN_VERSION=3.5.3
|
||||
TELEPATHY_GLIB_MIN_VERSION=0.17.5
|
||||
TELEPATHY_LOGGER_MIN_VERSION=0.2.4
|
||||
POLKIT_MIN_VERSION=0.100
|
||||
STARTUP_NOTIFICATION_MIN_VERSION=0.11
|
||||
GCR_MIN_VERSION=3.3.90
|
||||
GNOME_DESKTOP_REQUIRED_VERSION=3.7.1
|
||||
GNOME_MENUS_REQUIRED_VERSION=3.5.3
|
||||
NETWORKMANAGER_MIN_VERSION=0.9.7
|
||||
GCR_MIN_VERSION=3.7.5
|
||||
GNOME_DESKTOP_REQUIRED_VERSION=3.7.90
|
||||
NETWORKMANAGER_MIN_VERSION=0.9.8
|
||||
PULSE_MIN_VERS=2.0
|
||||
|
||||
# Collect more than 20 libraries for a prize!
|
||||
PKG_CHECK_MODULES(GNOME_SHELL, gio-unix-2.0 >= $GIO_MIN_VERSION
|
||||
libxml-2.0
|
||||
gtk+-3.0 >= $GTK_MIN_VERSION
|
||||
atk-bridge-2.0
|
||||
libmutter >= $MUTTER_MIN_VERSION
|
||||
gjs-internals-1.0 >= $GJS_MIN_VERSION
|
||||
libgnome-menu-3.0 >= $GNOME_MENUS_REQUIRED_VERSION
|
||||
$recorder_modules
|
||||
gdk-x11-3.0 libsoup-2.4
|
||||
gl
|
||||
clutter-x11-1.0 >= $CLUTTER_MIN_VERSION
|
||||
clutter-glx-1.0 >= $CLUTTER_MIN_VERSION
|
||||
libstartup-notification-1.0 >= $STARTUP_NOTIFICATION_MIN_VERSION
|
||||
gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION
|
||||
libcanberra
|
||||
telepathy-glib >= $TELEPATHY_GLIB_MIN_VERSION
|
||||
telepathy-logger-0.2 >= $TELEPATHY_LOGGER_MIN_VERSION
|
||||
polkit-agent-1 >= $POLKIT_MIN_VERSION xfixes
|
||||
libnm-glib libnm-util >= $NETWORKMANAGER_MIN_VERSION
|
||||
libnm-gtk >= $NETWORKMANAGER_MIN_VERSION
|
||||
gnome-keyring-1 gcr-3 >= $GCR_MIN_VERSION)
|
||||
SHARED_PCS="gio-unix-2.0 >= $GIO_MIN_VERSION
|
||||
libxml-2.0
|
||||
gtk+-3.0 >= $GTK_MIN_VERSION
|
||||
atk-bridge-2.0
|
||||
gjs-internals-1.0 >= $GJS_MIN_VERSION
|
||||
$recorder_modules
|
||||
gdk-x11-3.0 libsoup-2.4
|
||||
xtst
|
||||
clutter-x11-1.0 >= $CLUTTER_MIN_VERSION
|
||||
clutter-glx-1.0 >= $CLUTTER_MIN_VERSION
|
||||
libstartup-notification-1.0 >= $STARTUP_NOTIFICATION_MIN_VERSION
|
||||
gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION
|
||||
libcanberra libcanberra-gtk3
|
||||
telepathy-glib >= $TELEPATHY_GLIB_MIN_VERSION
|
||||
polkit-agent-1 >= $POLKIT_MIN_VERSION
|
||||
libnm-glib libnm-util >= $NETWORKMANAGER_MIN_VERSION
|
||||
libnm-gtk >= $NETWORKMANAGER_MIN_VERSION
|
||||
libsecret-unstable gcr-base-3 >= $GCR_MIN_VERSION"
|
||||
|
||||
PKG_CHECK_MODULES(SHELL_PERF_HELPER, gtk+-3.0 gio-2.0)
|
||||
PKG_CHECK_MODULES(GNOME_SHELL, $SHARED_PCS)
|
||||
PKG_CHECK_MODULES(MUTTER, libmutter >= $MUTTER_MIN_VERSION)
|
||||
PKG_CHECK_MODULES(MUTTER_WAYLAND, [libmutter-wayland >= $MUTTER_MIN_VERSION],
|
||||
[MUTTER_WAYLAND_TYPELIB_DIR=`$PKG_CONFIG --variable=typelibdir libmutter-wayland`
|
||||
AC_SUBST(MUTTER_WAYLAND_TYPELIB_DIR)
|
||||
have_mutter_wayland=yes],
|
||||
[have_mutter_wayland=no])
|
||||
|
||||
PKG_CHECK_MODULES(SHELL_HOTPLUG_SNIFFER, gio-2.0 gdk-pixbuf-2.0)
|
||||
|
||||
PKG_CHECK_MODULES(BROWSER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION json-glib-1.0 >= 0.13.2)
|
||||
|
||||
GNOME_KEYBINDINGS_KEYSDIR=`$PKG_CONFIG --variable keysdir gnome-keybindings`
|
||||
AC_SUBST([GNOME_KEYBINDINGS_KEYSDIR])
|
||||
|
||||
GOBJECT_INTROSPECTION_CHECK([$GOBJECT_INTROSPECTION_MIN_VERSION])
|
||||
|
||||
saved_CFLAGS=$CFLAGS
|
||||
saved_LIBS=$LIBS
|
||||
CFLAGS=$GNOME_SHELL_CFLAGS
|
||||
LIBS=$GNOME_SHELL_LIBS
|
||||
AC_CHECK_FUNCS(JS_NewGlobalObject XFixesCreatePointerBarrier)
|
||||
CFLAGS=$saved_CFLAGS
|
||||
LIBS=$saved_LIBS
|
||||
AM_CONDITIONAL(HAVE_MUTTER_WAYLAND, test $have_mutter_wayland != no)
|
||||
|
||||
PKG_CHECK_MODULES(GNOME_SHELL_JS, gio-2.0 gjs-internals-1.0 >= $GJS_MIN_VERSION)
|
||||
PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-3.0 libcroco-0.6 >= 0.6.8 x11)
|
||||
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)
|
||||
PKG_CHECK_MODULES(BROWSER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION json-glib-1.0 >= 0.13.2)
|
||||
PKG_CHECK_MODULES(TRAY, gtk+-3.0)
|
||||
PKG_CHECK_MODULES(GVC, libpulse libpulse-mainloop-glib gobject-2.0)
|
||||
PKG_CHECK_MODULES(DESKTOP_SCHEMAS, gsettings-desktop-schemas >= 3.7.2)
|
||||
PKG_CHECK_MODULES(GVC, libpulse >= $PULSE_MIN_VERS libpulse-mainloop-glib gobject-2.0)
|
||||
PKG_CHECK_MODULES(DESKTOP_SCHEMAS, gsettings-desktop-schemas >= 3.7.4)
|
||||
PKG_CHECK_MODULES(CARIBOU, caribou-1.0 >= 0.4.8)
|
||||
|
||||
AC_MSG_CHECKING([for bluetooth support])
|
||||
PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 3.1.0],
|
||||
PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 3.9.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"])
|
||||
@ -146,36 +131,15 @@ PKG_CHECK_MODULES(CALENDAR_SERVER, libecal-1.2 >= $LIBECAL_MIN_VERSION libedatas
|
||||
AC_SUBST(CALENDAR_SERVER_CFLAGS)
|
||||
AC_SUBST(CALENDAR_SERVER_LIBS)
|
||||
|
||||
AC_ARG_WITH(systemd,
|
||||
AS_HELP_STRING([--with-systemd],
|
||||
[Add systemd support]),
|
||||
[with_systemd=$withval], [with_systemd=auto])
|
||||
GNOME_KEYBINDINGS_KEYSDIR=`$PKG_CONFIG --variable keysdir gnome-keybindings`
|
||||
AC_SUBST([GNOME_KEYBINDINGS_KEYSDIR])
|
||||
|
||||
PKG_CHECK_MODULES(SYSTEMD,
|
||||
[libsystemd-login libsystemd-daemon],
|
||||
[have_systemd=yes], [have_systemd=no])
|
||||
|
||||
if test "x$with_systemd" = "xauto" ; then
|
||||
if test x$have_systemd = xno ; then
|
||||
use_systemd=no
|
||||
else
|
||||
use_systemd=yes
|
||||
fi
|
||||
else
|
||||
use_systemd=$with_systemd
|
||||
fi
|
||||
|
||||
if test "x$use_systemd" = "xyes"; then
|
||||
if test "x$have_systemd" = "xno"; then
|
||||
AC_MSG_ERROR([Systemd support explicitly required, but systemd not found])
|
||||
fi
|
||||
|
||||
AC_DEFINE(WITH_SYSTEMD, 1, [systemd support])
|
||||
fi
|
||||
GOBJECT_INTROSPECTION_CHECK([$GOBJECT_INTROSPECTION_MIN_VERSION])
|
||||
|
||||
MUTTER_GIR_DIR=`$PKG_CONFIG --variable=girdir libmutter`
|
||||
MUTTER_TYPELIB_DIR=`$PKG_CONFIG --variable=typelibdir libmutter`
|
||||
AC_SUBST(MUTTER_GIR_DIR)
|
||||
|
||||
MUTTER_TYPELIB_DIR=`$PKG_CONFIG --variable=typelibdir libmutter`
|
||||
AC_SUBST(MUTTER_TYPELIB_DIR)
|
||||
|
||||
GJS_CONSOLE=`$PKG_CONFIG --variable=gjs_console gjs-1.0`
|
||||
@ -198,16 +162,6 @@ fi
|
||||
|
||||
# Sets GLIB_GENMARSHAL and GLIB_MKENUMS
|
||||
AM_PATH_GLIB_2_0()
|
||||
G_IR_SCANNER=`$PKG_CONFIG --variable=g_ir_scanner gobject-introspection-1.0`
|
||||
AC_SUBST(G_IR_SCANNER)
|
||||
G_IR_COMPILER=`$PKG_CONFIG --variable=g_ir_compiler gobject-introspection-1.0`
|
||||
AC_SUBST(G_IR_COMPILER)
|
||||
G_IR_GENERATE=`$PKG_CONFIG --variable=g_ir_generate gobject-introspection-1.0`
|
||||
AC_SUBST(G_IR_GENERATE)
|
||||
GIRDIR=`$PKG_CONFIG --variable=girdir gobject-introspection-1.0`
|
||||
AC_SUBST(GIRDIR)
|
||||
TYPELIBDIR="$($PKG_CONFIG --variable=typelibdir gobject-introspection-1.0)"
|
||||
AC_SUBST(TYPELIBDIR)
|
||||
|
||||
GTK_DOC_CHECK([1.15], [--flavour no-tmpl])
|
||||
|
||||
@ -223,36 +177,7 @@ if test "$enable_man" != no; then
|
||||
fi
|
||||
AM_CONDITIONAL(ENABLE_MAN, test "$enable_man" != no)
|
||||
|
||||
# Stay command-line compatible with the gnome-common configure option. Here
|
||||
# minimum/yes/maximum are the same, however.
|
||||
AC_ARG_ENABLE(compile_warnings,
|
||||
AS_HELP_STRING([--enable-compile-warnings=@<:@no/minimum/yes/maximum/error@:>@],[Turn on compiler warnings]),,
|
||||
enable_compile_warnings=error)
|
||||
|
||||
changequote(,)dnl
|
||||
if test "$enable_compile_warnings" != no ; then
|
||||
if test "x$GCC" = "xyes"; then
|
||||
case " $CFLAGS " in
|
||||
*[\ \ ]-Wall[\ \ ]*) ;;
|
||||
*) CFLAGS="$CFLAGS -Wall" ;;
|
||||
esac
|
||||
case " $CFLAGS " in
|
||||
*[\ \ ]-Wmissing-prototypes[\ \ ]*) ;;
|
||||
*) CFLAGS="$CFLAGS -Wmissing-prototypes" ;;
|
||||
esac
|
||||
if test "$enable_compile_warnings" = error ; then
|
||||
case " $CFLAGS " in
|
||||
*[\ \ ]-Werror[\ \ ]*) ;;
|
||||
*) CFLAGS="$CFLAGS -Werror -Wno-error=deprecated-declarations" ;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
changequote([,])dnl
|
||||
|
||||
AC_ARG_ENABLE(jhbuild-wrapper-script,
|
||||
AS_HELP_STRING([--enable-jhbuild-wrapper-script],[Make "gnome-shell" script work for jhbuild]),,enable_jhbuild_wrapper_script=no)
|
||||
AM_CONDITIONAL(USE_JHBUILD_WRAPPER_SCRIPT, test "x$enable_jhbuild_wrapper_script" = xyes)
|
||||
GNOME_COMPILE_WARNINGS([error])
|
||||
|
||||
BROWSER_PLUGIN_DIR="${BROWSER_PLUGIN_DIR:-"\${libdir}/mozilla/plugins"}"
|
||||
AC_ARG_VAR([BROWSER_PLUGIN_DIR],[Where to install the plugin to])
|
||||
@ -269,6 +194,7 @@ AC_CONFIG_FILES([
|
||||
js/Makefile
|
||||
src/calendar-server/evolution-calendar.desktop.in
|
||||
src/Makefile
|
||||
src/gvc/Makefile
|
||||
browser-plugin/Makefile
|
||||
tests/Makefile
|
||||
po/Makefile.in
|
||||
|
@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<KeyListEntries schema="org.gnome.shell.keybindings"
|
||||
group="system"
|
||||
_name="Screenshots"
|
||||
wm_name="GNOME Shell"
|
||||
package="gnome-shell">
|
||||
|
||||
<KeyListEntry name="toggle-recording"
|
||||
_description="Record a screencast"/>
|
||||
|
||||
</KeyListEntries>
|
||||
|
@ -11,6 +11,9 @@
|
||||
<KeyListEntry name="focus-active-notification"
|
||||
_description="Focus the active notification"/>
|
||||
|
||||
<KeyListEntry name="toggle-overview"
|
||||
_description="Show the overview"/>
|
||||
|
||||
<KeyListEntry name="toggle-application-view"
|
||||
_description="Show all applications"/>
|
||||
|
||||
|
@ -1,5 +1,12 @@
|
||||
wandadir = $(pkgdatadir)
|
||||
dist_wanda_DATA = wanda.png
|
||||
|
||||
desktopdir=$(datadir)/applications
|
||||
desktop_DATA = gnome-shell.desktop gnome-shell-extension-prefs.desktop
|
||||
if HAVE_MUTTER_WAYLAND
|
||||
desktop_DATA += gnome-shell-wayland.desktop
|
||||
endif HAVE_MUTTER_WAYLAND
|
||||
|
||||
|
||||
# We substitute in bindir so it works as an autostart
|
||||
# file when built in a non-system prefix
|
||||
@ -11,7 +18,11 @@ desktop_DATA = gnome-shell.desktop gnome-shell-extension-prefs.desktop
|
||||
@INTLTOOL_DESKTOP_RULE@
|
||||
|
||||
introspectiondir = $(datadir)/dbus-1/interfaces
|
||||
introspection_DATA = org.gnome.ShellSearchProvider.xml
|
||||
introspection_DATA = \
|
||||
org.gnome.Shell.Screencast.xml \
|
||||
org.gnome.Shell.Screenshot.xml \
|
||||
org.gnome.ShellSearchProvider.xml \
|
||||
org.gnome.ShellSearchProvider2.xml
|
||||
|
||||
themedir = $(pkgdatadir)/theme
|
||||
dist_theme_DATA = \
|
||||
@ -32,7 +43,12 @@ dist_theme_DATA = \
|
||||
theme/gnome-shell.css \
|
||||
theme/logged-in-indicator.svg \
|
||||
theme/message-tray-background.png \
|
||||
theme/more-results.svg \
|
||||
theme/noise-texture.png \
|
||||
theme/page-indicator-active.svg \
|
||||
theme/page-indicator-inactive.svg \
|
||||
theme/page-indicator-checked.svg \
|
||||
theme/page-indicator-hover.svg \
|
||||
theme/panel-button-border.svg \
|
||||
theme/panel-button-highlight-narrow.svg \
|
||||
theme/panel-button-highlight-wide.svg \
|
||||
@ -48,15 +64,10 @@ dist_theme_DATA = \
|
||||
theme/ws-switch-arrow-down.png
|
||||
|
||||
keysdir = @GNOME_KEYBINDINGS_KEYSDIR@
|
||||
keys_in_files = \
|
||||
50-gnome-shell-screenshot.xml.in \
|
||||
50-gnome-shell-system.xml.in \
|
||||
$(NULL)
|
||||
keys_in_files = 50-gnome-shell-system.xml.in
|
||||
keys_DATA = $(keys_in_files:.xml.in=.xml)
|
||||
|
||||
gsettings_SCHEMAS = \
|
||||
org.gnome.shell.gschema.xml \
|
||||
org.gnome.shell-foo.gschema.xml
|
||||
gsettings_SCHEMAS = org.gnome.shell.gschema.xml
|
||||
|
||||
@INTLTOOL_XML_NOMERGE_RULE@
|
||||
|
||||
@ -78,22 +89,21 @@ convert_DATA = gnome-shell-overrides.convert
|
||||
|
||||
EXTRA_DIST = \
|
||||
gnome-shell.desktop.in.in \
|
||||
gnome-shell-wayland.desktop.in.in \
|
||||
gnome-shell-extension-prefs.desktop.in.in \
|
||||
$(introspection_DATA) \
|
||||
$(menu_DATA) \
|
||||
$(convert_DATA) \
|
||||
$(keys_in_files) \
|
||||
org.gnome.shell.gschema.xml.in.in \
|
||||
org.gnome.shell-foo.gschema.xml.in.in
|
||||
org.gnome.shell.gschema.xml.in.in
|
||||
|
||||
CLEANFILES = \
|
||||
gnome-shell.desktop.in \
|
||||
gnome-shell-wayland.desktop.in \
|
||||
gnome-shell-extension-prefs.in \
|
||||
$(desktop_DATA) \
|
||||
$(keys_DATA) \
|
||||
$(gsettings_SCHEMAS) \
|
||||
gschemas.compiled \
|
||||
org.gnome.shell.gschema.valid \
|
||||
org.gnome.shell.gschema.xml.in \
|
||||
org.gnome.shell-foo.gschema.valid \
|
||||
org.gnome.shell-foo.gschema.xml.in
|
||||
org.gnome.shell.gschema.xml.in
|
||||
|
15
data/gnome-shell-wayland.desktop.in.in
Normal file
15
data/gnome-shell-wayland.desktop.in.in
Normal file
@ -0,0 +1,15 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
_Name=GNOME Shell (wayland compositor)
|
||||
_Comment=Window management and application launching
|
||||
Exec=@bindir@/mutter-launch -- gnome-shell-wayland --wayland
|
||||
X-GNOME-Bugzilla-Bugzilla=GNOME
|
||||
X-GNOME-Bugzilla-Product=gnome-shell
|
||||
X-GNOME-Bugzilla-Component=general
|
||||
X-GNOME-Bugzilla-Version=@VERSION@
|
||||
Categories=GNOME;GTK;Core;
|
||||
OnlyShowIn=GNOME;
|
||||
NoDisplay=true
|
||||
X-GNOME-Autostart-Phase=DisplayServer
|
||||
X-GNOME-Autostart-Notify=true
|
||||
X-GNOME-AutoRestart=false
|
96
data/org.gnome.Shell.Screencast.xml
Normal file
96
data/org.gnome.Shell.Screencast.xml
Normal file
@ -0,0 +1,96 @@
|
||||
<!DOCTYPE node PUBLIC
|
||||
'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
|
||||
'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
|
||||
<node>
|
||||
|
||||
<!--
|
||||
org.gnome.Shell.Screencast:
|
||||
@short_description: Screencast interface
|
||||
|
||||
The interface used to record screen contents.
|
||||
-->
|
||||
<interface name="org.gnome.Shell.Screencast">
|
||||
|
||||
<!--
|
||||
Screencast:
|
||||
@file_template: the template for the filename to use
|
||||
@options: a dictionary of optional parameters
|
||||
@success: whether the screencast was started successfully
|
||||
@filename_used: the file where the screencast is being saved
|
||||
|
||||
Records a screencast of the whole screen and saves it
|
||||
(by default) as webm video under a filename derived from
|
||||
@file_template. The template is either a relative or absolute
|
||||
filename which may contain some escape sequences - %d and %t
|
||||
will be replaced by the start date and time of the recording.
|
||||
If a relative name is used, the screencast will be saved in the
|
||||
$XDG_VIDEOS_DIR if it exists, or the home directory otherwise.
|
||||
The actual filename of the saved video is returned in @filename_used.
|
||||
The set of optional parameters in @options currently consists of:
|
||||
'draw-cursor'(b): whether the cursor should be included in the
|
||||
recording (true)
|
||||
'framerate'(i): the number of frames per second that should be
|
||||
recorded if possible (30)
|
||||
'pipeline'(s): the GStreamer pipeline used to encode recordings
|
||||
in gst-launch format; if not specified, the
|
||||
recorder will produce vp8 (webm) video (unset)
|
||||
-->
|
||||
<method name="Screencast">
|
||||
<arg type="s" direction="in" name="file_template"/>
|
||||
<arg type="a{sv}" direction="in" name="options"/>
|
||||
<arg type="b" direction="in" name="flash"/>
|
||||
<arg type="b" direction="out" name="success"/>
|
||||
<arg type="s" direction="out" name="filename_used"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
ScreencastArea:
|
||||
@x: the X coordinate of the area to capture
|
||||
@y: the Y coordinate of the area to capture
|
||||
@width: the width of the area to capture
|
||||
@height: the height of the area to capture
|
||||
@file_template: the template for the filename to use
|
||||
@options: a dictionary of optional parameters
|
||||
@success: whether the screencast was started successfully
|
||||
@filename_used: the file where the screencast is being saved
|
||||
|
||||
Records a screencast of the passed in area and saves it
|
||||
(by default) as webm video under a filename derived from
|
||||
@file_template. The template is either a relative or absolute
|
||||
filename which may contain some escape sequences - %d and %t
|
||||
will be replaced by the start date and time of the recording.
|
||||
If a relative name is used, the screencast will be saved in the
|
||||
$XDG_VIDEOS_DIR if it exists, or the home directory otherwise.
|
||||
The actual filename of the saved video is returned in @filename_used.
|
||||
The set of optional parameters in @options currently consists of:
|
||||
'draw-cursor'(b): whether the cursor should be included in the
|
||||
recording (true)
|
||||
'framerate'(i): the number of frames per second that should be
|
||||
recorded if possible (30)
|
||||
'pipeline'(s): the GStreamer pipeline used to encode recordings
|
||||
in gst-launch format; if not specified, the
|
||||
recorder will produce vp8 (webm) video (unset)
|
||||
-->
|
||||
<method name="ScreencastArea">
|
||||
<arg type="i" direction="in" name="x"/>
|
||||
<arg type="i" direction="in" name="y"/>
|
||||
<arg type="i" direction="in" name="width"/>
|
||||
<arg type="i" direction="in" name="height"/>
|
||||
<arg type="s" direction="in" name="file_template"/>
|
||||
<arg type="a{sv}" direction="in" name="options"/>
|
||||
<arg type="b" direction="out" name="success"/>
|
||||
<arg type="s" direction="out" name="filename_used"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
StopScreencast:
|
||||
@success: whether stopping the recording was successful
|
||||
|
||||
Stop the recording started by either Screencast or ScreencastArea.
|
||||
-->
|
||||
<method name="StopScreencast">
|
||||
<arg type="b" direction="out" name="success"/>
|
||||
</method>
|
||||
|
||||
</interface>
|
||||
</node>
|
128
data/org.gnome.Shell.Screenshot.xml
Normal file
128
data/org.gnome.Shell.Screenshot.xml
Normal file
@ -0,0 +1,128 @@
|
||||
<!DOCTYPE node PUBLIC
|
||||
'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
|
||||
'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
|
||||
<node>
|
||||
|
||||
<!--
|
||||
org.gnome.Shell.Screenshot:
|
||||
@short_description: Screenshot interface
|
||||
|
||||
The interface used to capture pictures of the screen contents.
|
||||
-->
|
||||
<interface name="org.gnome.Shell.Screenshot">
|
||||
|
||||
<!--
|
||||
Screenshot:
|
||||
@filename: The filename for the screenshot
|
||||
@include_cursor: Whether to include the cursor image or not
|
||||
@flash: Whether to flash the screen or not
|
||||
@success: whether the screenshot was captured
|
||||
@filename_used: the file where the screenshot was saved
|
||||
|
||||
Takes a screenshot of the whole screen and saves it
|
||||
in @filename as png image, it returns a boolean
|
||||
indicating whether the operation was successful or not.
|
||||
@filename can either be an absolute path or a basename, in
|
||||
which case the screenshot will be saved in the $XDG_PICTURES_DIR
|
||||
or the home directory if it doesn't exist. The filename used
|
||||
to save the screenshot will be returned in @filename_used.
|
||||
-->
|
||||
<method name="Screenshot">
|
||||
<arg type="b" direction="in" name="include_cursor"/>
|
||||
<arg type="b" direction="in" name="flash"/>
|
||||
<arg type="s" direction="in" name="filename"/>
|
||||
<arg type="b" direction="out" name="success"/>
|
||||
<arg type="s" direction="out" name="filename_used"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
ScreenshotWindow:
|
||||
@include_frame: Whether to include the frame or not
|
||||
@include_cursor: Whether to include the cursor image or not
|
||||
@flash: Whether to flash the window area or not
|
||||
@filename: The filename for the screenshot
|
||||
@success: whether the screenshot was captured
|
||||
@filename_used: the file where the screenshot was saved
|
||||
|
||||
Takes a screenshot of the focused window (optionally omitting the frame)
|
||||
and saves it in @filename as png image, it returns a boolean
|
||||
indicating whether the operation was successful or not.
|
||||
@filename can either be an absolute path or a basename, in
|
||||
which case the screenshot will be saved in the $XDG_PICTURES_DIR
|
||||
or the home directory if it doesn't exist. The filename used
|
||||
to save the screenshot will be returned in @filename_used.
|
||||
-->
|
||||
<method name="ScreenshotWindow">
|
||||
<arg type="b" direction="in" name="include_frame"/>
|
||||
<arg type="b" direction="in" name="include_cursor"/>
|
||||
<arg type="b" direction="in" name="flash"/>
|
||||
<arg type="s" direction="in" name="filename"/>
|
||||
<arg type="b" direction="out" name="success"/>
|
||||
<arg type="s" direction="out" name="filename_used"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
ScreenshotArea:
|
||||
@x: the X coordinate of the area to capture
|
||||
@y: the Y coordinate of the area to capture
|
||||
@width: the width of the area to capture
|
||||
@height: the height of the area to capture
|
||||
@flash: whether to flash the area or not
|
||||
@filename: the filename for the screenshot
|
||||
@success: whether the screenshot was captured
|
||||
@filename_used: the file where the screenshot was saved
|
||||
|
||||
Takes a screenshot of the passed in area and saves it
|
||||
in @filename as png image, it returns a boolean
|
||||
indicating whether the operation was successful or not.
|
||||
@filename can either be an absolute path or a basename, in
|
||||
which case the screenshot will be saved in the $XDG_PICTURES_DIR
|
||||
or the home directory if it doesn't exist. The filename used
|
||||
to save the screenshot will be returned in @filename_used.
|
||||
-->
|
||||
<method name="ScreenshotArea">
|
||||
<arg type="i" direction="in" name="x"/>
|
||||
<arg type="i" direction="in" name="y"/>
|
||||
<arg type="i" direction="in" name="width"/>
|
||||
<arg type="i" direction="in" name="height"/>
|
||||
<arg type="b" direction="in" name="flash"/>
|
||||
<arg type="s" direction="in" name="filename"/>
|
||||
<arg type="b" direction="out" name="success"/>
|
||||
<arg type="s" direction="out" name="filename_used"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
FlashArea:
|
||||
@x: the X coordinate of the area to flash
|
||||
@y: the Y coordinate of the area to flash
|
||||
@width: the width of the area to flash
|
||||
@height: the height of the area to flash
|
||||
|
||||
Renders a flash spot effect in the specified rectangle of the screen.
|
||||
-->
|
||||
<method name="FlashArea">
|
||||
<arg type="i" direction="in" name="x"/>
|
||||
<arg type="i" direction="in" name="y"/>
|
||||
<arg type="i" direction="in" name="width"/>
|
||||
<arg type="i" direction="in" name="height"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
SelectArea:
|
||||
@x: the X coordinate of the selected area
|
||||
@y: the Y coordinate of the selected area
|
||||
@width: the width of the selected area
|
||||
@height: the height of the selected area
|
||||
|
||||
Interactively allows the user to select a rectangular area of
|
||||
the screen, and returns its coordinates.
|
||||
-->
|
||||
<method name="SelectArea">
|
||||
<arg type="i" direction="out" name="x"/>
|
||||
<arg type="i" direction="out" name="y"/>
|
||||
<arg type="i" direction="out" name="width"/>
|
||||
<arg type="i" direction="out" name="height"/>
|
||||
</method>
|
||||
|
||||
</interface>
|
||||
</node>
|
@ -2,146 +2,72 @@
|
||||
'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
|
||||
'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
|
||||
<node>
|
||||
|
||||
<!--
|
||||
org.gnome.Shell.SearchProvider:
|
||||
@short_description: Search provider interface
|
||||
|
||||
The interface used for integrating into GNOME Shell's search
|
||||
interface. This interface is deprecated, and org.gnome.Shell.SearchProvider2 should be used instead.
|
||||
-->
|
||||
<interface name="org.gnome.Shell.SearchProvider">
|
||||
<doc:doc>
|
||||
<doc:description>
|
||||
<doc:para>
|
||||
The interface used for integrating into GNOME Shell's search
|
||||
interface.
|
||||
</doc:para>
|
||||
</doc:description>
|
||||
</doc:doc>
|
||||
|
||||
<!--
|
||||
GetInitialResultSet:
|
||||
@terms: Array of search terms, which the provider should treat as logical AND.
|
||||
@results: An array of result identifier strings representing items which match the given search terms. Identifiers must be unique within the provider's domain, but other than that may be chosen freely by the provider.
|
||||
|
||||
Called when the user first begins a search.
|
||||
-->
|
||||
<method name="GetInitialResultSet">
|
||||
<doc:doc>
|
||||
<doc:description>
|
||||
<doc:para>
|
||||
Called when the user first begins a search.
|
||||
</doc:para>
|
||||
</doc:description>
|
||||
</doc:doc>
|
||||
<arg type="as" direction="in">
|
||||
<doc:doc>
|
||||
<doc:summary>
|
||||
<doc:para>
|
||||
Array of search terms, which the provider should treat as
|
||||
logical AND.
|
||||
</doc:para>
|
||||
</doc:summary>
|
||||
</doc:doc>
|
||||
</arg>
|
||||
<arg type="as" direction="out">
|
||||
<doc:doc>
|
||||
<doc:summary>
|
||||
<doc:para>
|
||||
An array of result identifier strings representing items which
|
||||
match the given search terms. Identifiers must be unique within
|
||||
the provider's domain, but other than that may be chosen freely
|
||||
by the provider.
|
||||
</doc:para>
|
||||
</doc:summary>
|
||||
</doc:doc>
|
||||
</arg>
|
||||
<arg type="as" name="terms" direction="in" />
|
||||
<arg type="as" name="results" direction="out" />
|
||||
</method>
|
||||
|
||||
<!--
|
||||
GetSubsearchResultSet:
|
||||
@previous_results: Array of results previously returned by GetInitialResultSet().
|
||||
@terms: Array of updated search terms, which the provider should treat as logical AND.
|
||||
@results: An array of result identifier strings representing items which match the given search terms. Identifiers must be unique within the provider's domain, but other than that may be chosen freely by the provider.
|
||||
|
||||
Called when a search is performed which is a "subsearch" of
|
||||
the previous search, e.g. the method may return less results, but
|
||||
not more or different results.
|
||||
|
||||
This allows search providers to only search through the previous
|
||||
result set, rather than possibly performing a full re-query.
|
||||
-->
|
||||
<method name="GetSubsearchResultSet">
|
||||
<doc:doc>
|
||||
<doc:description>
|
||||
<doc:para>
|
||||
Called when a search is performed which is a "subsearch" of
|
||||
the previous search, e.g. the method may return less results, but
|
||||
not more or different results.
|
||||
|
||||
This allows search providers to only search through the previous
|
||||
result set, rather than possibly performing a full re-query.
|
||||
</doc:para>
|
||||
</doc:description>
|
||||
</doc:doc>
|
||||
<arg type="as" direction="in">
|
||||
<doc:doc>
|
||||
<doc:summary>
|
||||
<doc:para>
|
||||
Array of item identifiers
|
||||
</doc:para>
|
||||
</doc:summary>
|
||||
</doc:doc>
|
||||
</arg>
|
||||
<arg type="as" direction="in">
|
||||
<doc:doc>
|
||||
<doc:summary>
|
||||
<doc:para>
|
||||
Array of updated search terms, which the provider should treat as
|
||||
logical AND.
|
||||
</doc:para>
|
||||
</doc:summary>
|
||||
</doc:doc>
|
||||
</arg>
|
||||
<arg type="as" direction="out">
|
||||
<doc:doc>
|
||||
<doc:summary>
|
||||
<doc:para>
|
||||
An array of result identifier strings representing items which
|
||||
match the given search terms. Identifiers must be unique within
|
||||
the provider's domain, but other than that may be chosen freely
|
||||
by the provider.
|
||||
</doc:para>
|
||||
</doc:summary>
|
||||
</doc:doc>
|
||||
</arg>
|
||||
<arg type="as" name="previous_results" direction="in" />
|
||||
<arg type="as" name="terms" direction="in" />
|
||||
<arg type="as" name="results" direction="out" />
|
||||
</method>
|
||||
|
||||
<!--
|
||||
GetResultMetas:
|
||||
@identifiers: An array of result identifiers as returned by GetInitialResultSet() or GetSubsearchResultSet()
|
||||
@metas: A dictionary describing the given search result, containing a human-readable 'name' (string), along with the result identifier this meta is for, 'id' (string). Optionally, either 'gicon' (a serialized GIcon) or 'icon-data' (raw image data as (iiibiiay) - width, height, rowstride, has-alpha, bits per sample, channels, data) can be specified if the result can be better served with a thumbnail of the content (such as with images). A 'description' field (string) may also be specified if more context would help the user find the desired result.
|
||||
|
||||
Return an array of meta data used to display each given result
|
||||
-->
|
||||
<method name="GetResultMetas">
|
||||
<doc:doc>
|
||||
<doc:description>
|
||||
<doc:para>
|
||||
Return an array of meta data used to display each given result
|
||||
</doc:para>
|
||||
</doc:description>
|
||||
</doc:doc>
|
||||
<arg type="as" direction="in">
|
||||
<doc:doc>
|
||||
<doc:summary>
|
||||
<doc:para>
|
||||
An array of result identifiers as returned by
|
||||
GetInitialResultSet() or GetSubsearchResultSet()
|
||||
</doc:para>
|
||||
</doc:summary>
|
||||
</doc:doc>
|
||||
</arg>
|
||||
<arg type="aa{sv}" direction="out">
|
||||
<doc:doc>
|
||||
<doc:summary>
|
||||
<doc:para>
|
||||
A dictionary describing the given search result, containing
|
||||
'id', 'name' (both strings) and either 'icon' (a serialized
|
||||
GIcon) or 'icon-data' (raw image data as (iiibiiay) - width,
|
||||
height, rowstride, has-alpha, bits per sample, channels, data)
|
||||
</doc:para>
|
||||
</doc:summary>
|
||||
</doc:doc>
|
||||
</arg>
|
||||
<arg type="as" name="identifiers" direction="in" />
|
||||
<arg type="aa{sv}" name="metas" direction="out" />
|
||||
</method>
|
||||
|
||||
<!--
|
||||
ActivateResult:
|
||||
@identifier: A result identifier as returned by GetInitialResultSet() or GetSubsearchResultSet()
|
||||
|
||||
Called when the users chooses a given result. The result should
|
||||
be displayed in the application associated with the corresponding
|
||||
provider.
|
||||
|
||||
This method is deprecated, and providers should implement ActivateResult2()
|
||||
instead.
|
||||
-->
|
||||
<method name="ActivateResult">
|
||||
<doc:doc>
|
||||
<doc:description>
|
||||
<doc:para>
|
||||
Called when the users chooses a given result. The result should
|
||||
be displayed in the application associated with the corresponding
|
||||
provider.
|
||||
</doc:para>
|
||||
</doc:description>
|
||||
</doc:doc>
|
||||
<arg type="s" direction="in">
|
||||
<doc:doc>
|
||||
<doc:summary>
|
||||
<doc:para>
|
||||
A result identifier as returned by GetInitialResultSet() or
|
||||
GetSubsearchResultSet()
|
||||
</doc:para>
|
||||
</doc:summary>
|
||||
</doc:doc>
|
||||
</arg>
|
||||
<arg type="s" name="identifier" direction="in" />
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
||||
|
87
data/org.gnome.ShellSearchProvider2.xml
Normal file
87
data/org.gnome.ShellSearchProvider2.xml
Normal file
@ -0,0 +1,87 @@
|
||||
<!DOCTYPE node PUBLIC
|
||||
'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
|
||||
'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
|
||||
<node>
|
||||
|
||||
<!--
|
||||
org.gnome.Shell.SearchProvider2:
|
||||
@short_description: Search provider interface
|
||||
|
||||
The interface used for integrating into GNOME Shell's search
|
||||
interface (version 2).
|
||||
-->
|
||||
<interface name="org.gnome.Shell.SearchProvider2">
|
||||
|
||||
<!--
|
||||
GetInitialResultSet:
|
||||
@terms: Array of search terms, which the provider should treat as logical AND.
|
||||
@results: An array of result identifier strings representing items which match the given search terms. Identifiers must be unique within the provider's domain, but other than that may be chosen freely by the provider.
|
||||
|
||||
Called when the user first begins a search.
|
||||
-->
|
||||
<method name="GetInitialResultSet">
|
||||
<arg type="as" name="terms" direction="in" />
|
||||
<arg type="as" name="results" direction="out" />
|
||||
</method>
|
||||
|
||||
<!--
|
||||
GetSubsearchResultSet:
|
||||
@previous_results: Array of results previously returned by GetInitialResultSet().
|
||||
@terms: Array of updated search terms, which the provider should treat as logical AND.
|
||||
@results: An array of result identifier strings representing items which match the given search terms. Identifiers must be unique within the provider's domain, but other than that may be chosen freely by the provider.
|
||||
|
||||
Called when a search is performed which is a "subsearch" of
|
||||
the previous search, e.g. the method may return less results, but
|
||||
not more or different results.
|
||||
|
||||
This allows search providers to only search through the previous
|
||||
result set, rather than possibly performing a full re-query.
|
||||
-->
|
||||
<method name="GetSubsearchResultSet">
|
||||
<arg type="as" name="previous_results" direction="in" />
|
||||
<arg type="as" name="terms" direction="in" />
|
||||
<arg type="as" name="results" direction="out" />
|
||||
</method>
|
||||
|
||||
<!--
|
||||
GetResultMetas:
|
||||
@identifiers: An array of result identifiers as returned by GetInitialResultSet() or GetSubsearchResultSet()
|
||||
@metas: A dictionary describing the given search result, containing a human-readable 'name' (string), along with the result identifier this meta is for, 'id' (string). Optionally, 'icon' (a serialized GIcon as obtained by g_icon_serialize) can be specified if the result can be better served with a thumbnail of the content (such as with images). 'gicon' (a serialized GIcon as obtained by g_icon_to_string) or 'icon-data' (raw image data as (iiibiiay) - width, height, rowstride, has-alpha, bits per sample, channels, data) are deprecated values that can also be used for that purpose. A 'description' field (string) may also be specified if more context would help the user find the desired result.
|
||||
|
||||
Return an array of meta data used to display each given result
|
||||
-->
|
||||
<method name="GetResultMetas">
|
||||
<arg type="as" name="identifiers" direction="in" />
|
||||
<arg type="aa{sv}" name="metas" direction="out" />
|
||||
</method>
|
||||
|
||||
<!--
|
||||
ActivateResult:
|
||||
@identifier: A result identifier as returned by GetInitialResultSet() or GetSubsearchResultSet()
|
||||
@terms: Array of search terms, which the provider should treat as logical AND.
|
||||
@timestamp: A timestamp of the user interaction that triggered this call
|
||||
|
||||
Called when the users chooses a given result. The result should
|
||||
be displayed in the application associated with the corresponding
|
||||
provider. The provided search terms can be used to allow launching a full search in
|
||||
the application.
|
||||
-->
|
||||
<method name="ActivateResult">
|
||||
<arg type="s" name="identifier" direction="in" />
|
||||
<arg type="as" name="terms" direction="in" />
|
||||
<arg type="u" name="timestamp" direction="in" />
|
||||
</method>
|
||||
|
||||
<!--
|
||||
LaunchSearch:
|
||||
@terms: Array of search terms, which the provider should treat as logical AND.
|
||||
@timestamp: A timestamp of the user interaction that triggered this call
|
||||
|
||||
Asks the search provider to launch a full search in the application for the provided terms.
|
||||
-->
|
||||
<method name="LaunchSearch">
|
||||
<arg type="as" name="terms" direction="in" />
|
||||
<arg type="u" name="timestamp" direction="in" />
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
@ -1,13 +0,0 @@
|
||||
<schemalist>
|
||||
<schema id="org.gnome.shell-foo.overrides" path="/org/gnome/shell-foo/overrides/"
|
||||
gettext-domain="@GETTEXT_PACKAGE@">
|
||||
<key name="button-layout" type="s">
|
||||
<default>":minimize,close"</default>
|
||||
<_summary>Arrangement of buttons on the titlebar</_summary>
|
||||
<_description>
|
||||
This key overrides the key in org.gnome.desktop.wm.preferences when
|
||||
running GNOME Shell in Foo mode.
|
||||
</_description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
@ -21,16 +21,6 @@
|
||||
EnableExtension and DisableExtension DBus methods on org.gnome.Shell.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="enable-app-monitoring" type="b">
|
||||
<default>true</default>
|
||||
<_summary>Whether to collect stats about applications usage</_summary>
|
||||
<_description>
|
||||
The shell normally monitors active applications in order to present
|
||||
the most used ones (e.g. in launchers). While this data will be
|
||||
kept private, you may want to disable this for privacy reasons.
|
||||
Please note that doing so won't remove already saved data.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="favorite-apps" type="as">
|
||||
<default>[ 'epiphany.desktop', 'evolution.desktop', 'empathy.desktop', 'rhythmbox.desktop', 'shotwell.desktop', 'libreoffice-writer.desktop', 'nautilus.desktop', 'gnome-documents.desktop' ]</default>
|
||||
<_summary>List of desktop file IDs for favorite applications</_summary>
|
||||
@ -39,6 +29,14 @@
|
||||
will be displayed in the favorites area.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="app-folder-categories" type="as">
|
||||
<default>[ 'Utilities', 'Sundry' ]</default>
|
||||
<_summary>List of categories that should be displayed as folders</_summary>
|
||||
<_description>
|
||||
Each category name in this list will be represented as folder in the
|
||||
application view, rather than being displayed inline in the main view.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="command-history" type="as">
|
||||
<default>[]</default>
|
||||
<_summary>History for command (Alt-F2) dialog</_summary>
|
||||
@ -47,16 +45,6 @@
|
||||
<default>[]</default>
|
||||
<_summary>History for the looking glass dialog</_summary>
|
||||
</key>
|
||||
<key name="saved-im-presence" type="i">
|
||||
<default>1</default>
|
||||
<_summary>Internally used to store the last IM presence explicitly set by the user. The
|
||||
value here is from the TpConnectionPresenceType enumeration.</_summary>
|
||||
</key>
|
||||
<key name="saved-session-presence" type="i">
|
||||
<default>0</default>
|
||||
<_summary>Internally used to store the last session presence status for the user. The
|
||||
value here is from the GsmPresenceStatus enumeration.</_summary>
|
||||
</key>
|
||||
<key name="always-show-log-out" type="b">
|
||||
<default>false</default>
|
||||
<_summary>Always show the 'Log out' menuitem in the user menu.</_summary>
|
||||
@ -65,13 +53,17 @@ value here is from the GsmPresenceStatus enumeration.</_summary>
|
||||
menuitem in single-user, single-session situations.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="show-full-name" type="b">
|
||||
<default>true</default>
|
||||
<_summary>Show full name in the user menu</_summary>
|
||||
<_description>Whether the users full name is shown in the user menu or not.</_description>
|
||||
<key name="remember-mount-password" type="b">
|
||||
<default>false</default>
|
||||
<_summary>Whether to remember password for mounting encrypted or remote filesystems</_summary>
|
||||
<_description>
|
||||
The shell will request a password when an encrypted device or a
|
||||
remote filesystem is mounted. If the password can be saved for
|
||||
future use a 'Remember Password' checkbox will be present.
|
||||
This key sets the default state of the checkbox.
|
||||
</_description>
|
||||
</key>
|
||||
<child name="calendar" schema="org.gnome.shell.calendar"/>
|
||||
<child name="recorder" schema="org.gnome.shell.recorder"/>
|
||||
<child name="keybindings" schema="org.gnome.shell.keybindings"/>
|
||||
<child name="keyboard" schema="org.gnome.shell.keyboard"/>
|
||||
</schema>
|
||||
@ -104,6 +96,13 @@ value here is from the GsmPresenceStatus enumeration.</_summary>
|
||||
Overview.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="toggle-overview" type="as">
|
||||
<default>["<Super>s"]</default>
|
||||
<_summary>Keybinding to open the overview</_summary>
|
||||
<_description>
|
||||
Keybinding to open the Activities Overview.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="toggle-message-tray" type="as">
|
||||
<default>["<Super>m"]</default>
|
||||
<_summary>Keybinding to toggle the visibility of the message tray</_summary>
|
||||
@ -118,13 +117,6 @@ value here is from the GsmPresenceStatus enumeration.</_summary>
|
||||
Keybinding to focus the active notification.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="toggle-recording" type="as">
|
||||
<default><![CDATA[['<Control><Shift><Alt>r']]]></default>
|
||||
<_summary>Keybinding to toggle the screen recorder</_summary>
|
||||
<_description>
|
||||
Keybinding to start/stop the builtin screen recorder.
|
||||
</_description>
|
||||
</key>
|
||||
</schema>
|
||||
|
||||
<schema id="org.gnome.shell.keyboard" path="/org/gnome/shell/keyboard/"
|
||||
@ -138,41 +130,43 @@ value here is from the GsmPresenceStatus enumeration.</_summary>
|
||||
</key>
|
||||
</schema>
|
||||
|
||||
<schema id="org.gnome.shell.recorder" path="/org/gnome/shell/recorder/"
|
||||
<schema id="org.gnome.shell.app-switcher"
|
||||
path="/org/gnome/shell/app-switcher/"
|
||||
gettext-domain="@GETTEXT_PACKAGE@">
|
||||
<key name="framerate" type="i">
|
||||
<default>30</default>
|
||||
<_summary>Framerate used for recording screencasts.</_summary>
|
||||
<key type="b" name="current-workspace-only">
|
||||
<default>false</default>
|
||||
<summary>Limit switcher to current workspace.</summary>
|
||||
<description>
|
||||
If true, only applications that have windows on the current workspace are shown in the switcher.
|
||||
Otherwise, all applications are included.
|
||||
</description>
|
||||
</key>
|
||||
</schema>
|
||||
|
||||
<enum id="org.gnome.shell.window-switcher.AppIconMode">
|
||||
<value value="1" nick="thumbnail-only"/>
|
||||
<value value="2" nick="app-icon-only"/>
|
||||
<value value="3" nick="both"/>
|
||||
</enum>
|
||||
<schema id="org.gnome.shell.window-switcher"
|
||||
path="/org/gnome/shell/window-switcher/"
|
||||
gettext-domain="@GETTEXT_PACKAGE@">
|
||||
<key name="app-icon-mode" enum="org.gnome.shell.window-switcher.AppIconMode">
|
||||
<default>'both'</default>
|
||||
<_summary>The application icon mode.</_summary>
|
||||
<_description>
|
||||
The framerate of the resulting screencast recordered
|
||||
by GNOME Shell's screencast recorder in frames-per-second.
|
||||
Configures how the windows are shown in the switcher. Valid possibilities
|
||||
are 'thumbnail-only' (shows a thumbnail of the window), 'app-icon-only'
|
||||
(shows only the application icon) or 'both'.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="pipeline" type="s">
|
||||
<default>''</default>
|
||||
<_summary>The gstreamer pipeline used to encode the screencast</_summary>
|
||||
<_description>
|
||||
Sets the GStreamer pipeline used to encode recordings.
|
||||
It follows the syntax used for gst-launch. The pipeline should have
|
||||
an unconnected sink pad where the recorded video is recorded. It will
|
||||
normally have a unconnected source pad; output from that pad
|
||||
will be written into the output file. However the pipeline can also
|
||||
take care of its own output - this might be used to send the output
|
||||
to an icecast server via shout2send or similar. When unset or set
|
||||
to an empty value, the default pipeline will be used. This is currently
|
||||
'vp8enc min_quantizer=13 max_quantizer=13 cpu-used=5 deadline=1000000 threads=%T ! queue ! webmmux'
|
||||
and records to WEBM using the VP8 codec. %T is used as a placeholder
|
||||
for a guess at the optimal thread count on the system.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="file-extension" type="s">
|
||||
<default>'webm'</default>
|
||||
<_summary>File extension used for storing the screencast</_summary>
|
||||
<_description>
|
||||
The filename for recorded screencasts will be a unique filename
|
||||
based on the current date, and use this extension. It should be
|
||||
changed when recording to a different container format.
|
||||
</_description>
|
||||
<key type="b" name="current-workspace-only">
|
||||
<default>true</default>
|
||||
<summary>Limit switcher to current workspace.</summary>
|
||||
<description>
|
||||
If true, only windows from the current workspace are shown in the switcher.
|
||||
Otherwise, all windows are included.
|
||||
</description>
|
||||
</key>
|
||||
</schema>
|
||||
|
||||
@ -222,10 +216,10 @@ value here is from the GsmPresenceStatus enumeration.</_summary>
|
||||
|
||||
<key name="focus-change-on-pointer-rest" type="b">
|
||||
<default>true</default>
|
||||
<summary>Delay focus changes in mouse mode until the pointer stops moving</summary>
|
||||
<description>
|
||||
<_summary>Delay focus changes in mouse mode until the pointer stops moving</_summary>
|
||||
<_description>
|
||||
This key overrides the key in org.gnome.mutter when running GNOME Shell.
|
||||
</description>
|
||||
</_description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
||||
|
File diff suppressed because it is too large
Load Diff
114
data/theme/more-results.svg
Normal file
114
data/theme/more-results.svg
Normal file
@ -0,0 +1,114 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="16"
|
||||
height="16"
|
||||
id="svg12430"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.4 r9939"
|
||||
sodipodi:docname="more-results.svg">
|
||||
<defs
|
||||
id="defs12432" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#7a7a7a"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="1"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="90.509668"
|
||||
inkscape:cx="6.5009792"
|
||||
inkscape:cy="8.3589595"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g14642-3-0"
|
||||
showgrid="false"
|
||||
borderlayer="true"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:window-width="1440"
|
||||
inkscape:window-height="840"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid13002" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata12435">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1036.3622)">
|
||||
<g
|
||||
style="display:inline"
|
||||
transform="translate(-141.99984,638.37113)"
|
||||
inkscape:label="zoom-in"
|
||||
id="g14642-3-0">
|
||||
<path
|
||||
sodipodi:type="inkscape:offset"
|
||||
inkscape:radius="0"
|
||||
inkscape:original="M 145.1875 400 C 144.5248 400 144 400.54899 144 401.21875 L 144 410.78125 C 144 411.45101 144.5248 412 145.1875 412 L 154.8125 412 C 155.4752 412 156 411.45101 156 410.78125 L 156 401.21875 C 156 400.54899 155.4752 400 154.8125 400 L 145.1875 400 z M 149 403 L 151 403 L 151 405 L 153 405 L 153 407 L 151 407 L 151 409 L 149 409 L 149 407 L 147 407 L 147 405 L 149 405 L 149 403 z "
|
||||
xlink:href="#rect11749-5-0-1-8"
|
||||
style="color:#bebebe;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;opacity:0.8"
|
||||
id="path13004"
|
||||
inkscape:href="#rect11749-5-0-1-8"
|
||||
d="M 145.1875,400 C 144.5248,400 144,400.54899 144,401.21875 l 0,9.5625 c 0,0.66976 0.5248,1.21875 1.1875,1.21875 l 9.625,0 c 0.6627,0 1.1875,-0.54899 1.1875,-1.21875 l 0,-9.5625 C 156,400.54899 155.4752,400 154.8125,400 L 145.1875,400 z m 3.8125,3 2,0 0,2 2,0 0,2 -2,0 0,2 -2,0 0,-2 -2,0 0,-2 2,0 L 149,403 Z"
|
||||
transform="translate(0,1)" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path13004"
|
||||
id="use11960"
|
||||
transform="translate(1,-1)"
|
||||
width="16"
|
||||
height="16" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#use11960"
|
||||
id="use11962"
|
||||
transform="translate(-2,0)"
|
||||
width="16"
|
||||
height="16" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1"
|
||||
d="M 7 5 L 7 7 L 5 7 L 5 9 L 7 9 L 7 11 L 9 11 L 9 9 L 11 9 L 11 7 L 9 7 L 9 5 L 7 5 z "
|
||||
transform="translate(141.99984,397.99107)"
|
||||
id="rect3757" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#bebebe;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible"
|
||||
d="M 145.1875,400 C 144.5248,400 144,400.54899 144,401.21875 l 0,9.5625 c 0,0.66976 0.5248,1.21875 1.1875,1.21875 l 9.625,0 c 0.6627,0 1.1875,-0.54899 1.1875,-1.21875 l 0,-9.5625 C 156,400.54899 155.4752,400 154.8125,400 L 145.1875,400 z m 3.8125,3 2,0 0,2 2,0 0,2 -2,0 0,2 -2,0 0,-2 -2,0 0,-2 2,0 L 149,403 Z"
|
||||
id="rect11749-5-0-1-8" />
|
||||
<rect
|
||||
style="fill:none;stroke:none"
|
||||
id="rect3620-5-4"
|
||||
width="15.981825"
|
||||
height="16"
|
||||
x="142"
|
||||
y="398"
|
||||
rx="0"
|
||||
ry="0" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.4 KiB |
71
data/theme/page-indicator-active.svg
Normal file
71
data/theme/page-indicator-active.svg
Normal file
@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="18"
|
||||
height="18"
|
||||
id="svg4703"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.4 r9939"
|
||||
sodipodi:docname="page-indicator-pushed.svg">
|
||||
<defs
|
||||
id="defs4705" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="31.392433"
|
||||
inkscape:cx="1.0245308"
|
||||
inkscape:cy="13.3715"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
inkscape:grid-bbox="true"
|
||||
inkscape:document-units="px"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1374"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid6140" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata4708">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
transform="translate(0,2)">
|
||||
<path
|
||||
transform="matrix(0.54617904,0,0,0.62523128,-1131.9904,-392.39214)"
|
||||
d="m 2099.9808,638.83099 a 10.985409,9.5964489 0 1 1 -21.9708,0 10.985409,9.5964489 0 1 1 21.9708,0 z"
|
||||
sodipodi:ry="9.5964489"
|
||||
sodipodi:rx="10.985409"
|
||||
sodipodi:cy="638.83099"
|
||||
sodipodi:cx="2088.9954"
|
||||
id="path4711"
|
||||
style="fill:#fdffff;fill-opacity:1;stroke:none"
|
||||
sodipodi:type="arc" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
67
data/theme/page-indicator-checked.svg
Normal file
67
data/theme/page-indicator-checked.svg
Normal file
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="18"
|
||||
height="18"
|
||||
id="svg4703"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.4 r9939"
|
||||
sodipodi:docname="page-indicator-active.svg">
|
||||
<defs
|
||||
id="defs4705" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="22.197802"
|
||||
inkscape:cx="2.1522887"
|
||||
inkscape:cy="16.782904"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
inkscape:grid-bbox="true"
|
||||
inkscape:document-units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1021"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata4708">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
transform="translate(0,2)">
|
||||
<path
|
||||
transform="matrix(0.72823872,0,0,0.8336417,-1512.2872,-525.55618)"
|
||||
d="m 2099.9808,638.83099 c 0,5.29998 -4.9184,9.59645 -10.9854,9.59645 -6.0671,0 -10.9854,-4.29647 -10.9854,-9.59645 0,-5.29997 4.9183,-9.59645 10.9854,-9.59645 6.067,0 10.9854,4.29648 10.9854,9.59645 z"
|
||||
sodipodi:ry="9.5964489"
|
||||
sodipodi:rx="10.985409"
|
||||
sodipodi:cy="638.83099"
|
||||
sodipodi:cx="2088.9954"
|
||||
id="path4711"
|
||||
style="fill:#fdffff;fill-opacity:0.94117647;stroke:none"
|
||||
sodipodi:type="arc" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
67
data/theme/page-indicator-hover.svg
Normal file
67
data/theme/page-indicator-hover.svg
Normal file
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="18"
|
||||
height="18"
|
||||
id="svg5266"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.4 r9939"
|
||||
sodipodi:docname="page-indicator-inactive.svg">
|
||||
<defs
|
||||
id="defs5268" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="11.313709"
|
||||
inkscape:cx="-2.307566"
|
||||
inkscape:cy="17.859535"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
inkscape:grid-bbox="true"
|
||||
inkscape:document-units="px"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1374"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata5271">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
transform="translate(0,2)">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="fill:none;fill-opacity:0;stroke:#ffffff;stroke-width:2.93356276000000005;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="path5274"
|
||||
sodipodi:cx="2088.9954"
|
||||
sodipodi:cy="638.83099"
|
||||
sodipodi:rx="10.985409"
|
||||
sodipodi:ry="9.5964489"
|
||||
d="m 2099.9808,638.83099 c 0,5.29998 -4.9184,9.59645 -10.9854,9.59645 -6.0671,0 -10.9854,-4.29647 -10.9854,-9.59645 0,-5.29997 4.9183,-9.59645 10.9854,-9.59645 6.067,0 10.9854,4.29648 10.9854,9.59645 z"
|
||||
transform="matrix(0.63720887,0,0,0.72943648,-1322.1264,-458.98661)" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
67
data/theme/page-indicator-inactive.svg
Normal file
67
data/theme/page-indicator-inactive.svg
Normal file
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="18"
|
||||
height="18"
|
||||
id="svg5266"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.4 r9939"
|
||||
sodipodi:docname="page-indicator-inactive.svg">
|
||||
<defs
|
||||
id="defs5268" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="11.313709"
|
||||
inkscape:cx="-2.307566"
|
||||
inkscape:cy="17.859535"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
inkscape:grid-bbox="true"
|
||||
inkscape:document-units="px"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1374"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata5271">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
transform="translate(0,2)">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="fill:none;fill-opacity:0;stroke:#ffffff;stroke-width:2.93356276000000005;stroke-miterlimit:4;stroke-opacity:0.39215686000000000;stroke-dasharray:none"
|
||||
id="path5274"
|
||||
sodipodi:cx="2088.9954"
|
||||
sodipodi:cy="638.83099"
|
||||
sodipodi:rx="10.985409"
|
||||
sodipodi:ry="9.5964489"
|
||||
d="m 2099.9808,638.83099 c 0,5.29998 -4.9184,9.59645 -10.9854,9.59645 -6.0671,0 -10.9854,-4.29647 -10.9854,-9.59645 0,-5.29997 4.9183,-9.59645 10.9854,-9.59645 6.067,0 10.9854,4.29648 10.9854,9.59645 z"
|
||||
transform="matrix(0.63720887,0,0,0.72943648,-1322.1264,-458.98661)" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
BIN
data/wanda.png
Normal file
BIN
data/wanda.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
@ -76,9 +76,30 @@ endif
|
||||
# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
|
||||
HTML_IMAGES=
|
||||
|
||||
doc-gen-org.gnome.Shell.SearchProvider.xml: $(top_srcdir)/data/org.gnome.ShellSearchProvider.xml
|
||||
gdbus-codegen \
|
||||
--interface-prefix org.gnome.ShellSearchProvider. \
|
||||
--generate-docbook doc-gen \
|
||||
$(top_srcdir)/data/org.gnome.ShellSearchProvider.xml
|
||||
|
||||
doc-gen-org.gnome.Shell.SearchProvider2.xml: $(top_srcdir)/data/org.gnome.ShellSearchProvider2.xml
|
||||
gdbus-codegen \
|
||||
--interface-prefix org.gnome.ShellSearchProvider2. \
|
||||
--generate-docbook doc-gen \
|
||||
$(top_srcdir)/data/org.gnome.ShellSearchProvider2.xml
|
||||
|
||||
doc-gen-org.gnome.Shell.Screenshot.xml: $(top_srcdir)/data/org.gnome.Shell.Screenshot.xml
|
||||
gdbus-codegen \
|
||||
--interface-prefix org.gnome.Shell.Screenshot. \
|
||||
--generate-docbook doc-gen \
|
||||
$(top_srcdir)/data/org.gnome.Shell.Screenshot.xml
|
||||
|
||||
# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
|
||||
# e.g. content_files=running.sgml building.sgml changes-2.0.sgml
|
||||
content_files=
|
||||
content_files= \
|
||||
doc-gen-org.gnome.Shell.SearchProvider.xml \
|
||||
doc-gen-org.gnome.Shell.SearchProvider2.xml \
|
||||
doc-gen-org.gnome.Shell.Screenshot.xml
|
||||
|
||||
# SGML files where gtk-doc abbrevations (#GtkWidget) are expanded
|
||||
# These files must be listed here *and* in content_files
|
||||
@ -91,7 +112,7 @@ expand_content_files=
|
||||
# e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS)
|
||||
# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib)
|
||||
GTKDOC_CFLAGS=$(GNOME_SHELL_CFLAGS)
|
||||
GTKDOC_LIBS=$(GNOME_SHELL_LIBS) $(BLUETOOTH_LIBS) $(top_builddir)/src/libgnome-shell.la
|
||||
GTKDOC_LIBS=$(GNOME_SHELL_LIBS) $(BLUETOOTH_LIBS) $(top_builddir)/src/libgnome-shell-menu.la $(top_builddir)/src/libgnome-shell-base.la $(top_builddir)/src/libgnome-shell.la
|
||||
|
||||
# This includes the standard gtk-doc make rules, copied by gtkdocize.
|
||||
include $(top_srcdir)/gtk-doc.make
|
||||
|
@ -43,12 +43,13 @@
|
||||
</chapter>
|
||||
<chapter>
|
||||
<title>Integration helpers and utilities</title>
|
||||
<xi:include href="doc-gen-org.gnome.Shell.SearchProvider.xml"/>
|
||||
<xi:include href="doc-gen-org.gnome.Shell.SearchProvider2.xml"/>
|
||||
<xi:include href="xml/shell-global.xml"/>
|
||||
<xi:include href="xml/shell-keybinding-modes.xml"/>
|
||||
<xi:include href="xml/shell-wm.xml"/>
|
||||
<xi:include href="xml/shell-xfixes-cursor.xml"/>
|
||||
<xi:include href="xml/shell-util.xml"/>
|
||||
<xi:include href="xml/shell-mount-operation.xml"/>
|
||||
<xi:include href="xml/shell-mobile-providers.xml"/>
|
||||
<xi:include href="xml/shell-network-agent.xml"/>
|
||||
<xi:include href="xml/shell-polkit-authentication-agent.xml"/>
|
||||
<xi:include href="xml/shell-tp-client.xml"/>
|
||||
|
@ -52,13 +52,6 @@ its dependencies to build from tarballs.</description>
|
||||
<gnome:userid>walters</gnome:userid>
|
||||
</foaf:Person>
|
||||
</maintainer>
|
||||
<maintainer>
|
||||
<foaf:Person>
|
||||
<foaf:name>Dan Winship</foaf:name>
|
||||
<foaf:mbox rdf:resource="mailto:danw@gnome.org" />
|
||||
<gnome:userid>danw</gnome:userid>
|
||||
</foaf:Person>
|
||||
</maintainer>
|
||||
<maintainer>
|
||||
<foaf:Person>
|
||||
<foaf:name>Marina Zhurakhinskaya</foaf:name>
|
||||
|
@ -17,10 +17,10 @@ misc/config.js: misc/config.js.in Makefile
|
||||
jsdir = $(pkgdatadir)/js
|
||||
|
||||
nobase_dist_js_DATA = \
|
||||
gdm/authPrompt.js \
|
||||
gdm/batch.js \
|
||||
gdm/fingerprint.js \
|
||||
gdm/loginDialog.js \
|
||||
gdm/powerMenu.js \
|
||||
gdm/realmd.js \
|
||||
gdm/util.js \
|
||||
extensionPrefs/main.js \
|
||||
@ -28,16 +28,22 @@ nobase_dist_js_DATA = \
|
||||
misc/extensionUtils.js \
|
||||
misc/fileUtils.js \
|
||||
misc/gnomeSession.js \
|
||||
misc/hash.js \
|
||||
misc/history.js \
|
||||
misc/jsParse.js \
|
||||
misc/loginManager.js \
|
||||
misc/modemManager.js \
|
||||
misc/objectManager.js \
|
||||
misc/params.js \
|
||||
misc/smartcardManager.js \
|
||||
misc/util.js \
|
||||
perf/core.js \
|
||||
ui/altTab.js \
|
||||
ui/animation.js \
|
||||
ui/appDisplay.js \
|
||||
ui/appFavorites.js \
|
||||
ui/backgroundMenu.js \
|
||||
ui/background.js \
|
||||
ui/boxpointer.js \
|
||||
ui/calendar.js \
|
||||
ui/checkBox.js \
|
||||
@ -49,7 +55,7 @@ nobase_dist_js_DATA = \
|
||||
ui/extensionSystem.js \
|
||||
ui/extensionDownloader.js \
|
||||
ui/environment.js \
|
||||
ui/flashspot.js \
|
||||
ui/focusCaretTracker.js\
|
||||
ui/ibusCandidatePopup.js\
|
||||
ui/grabHelper.js \
|
||||
ui/iconGrid.js \
|
||||
@ -62,32 +68,43 @@ nobase_dist_js_DATA = \
|
||||
ui/main.js \
|
||||
ui/messageTray.js \
|
||||
ui/modalDialog.js \
|
||||
ui/separator.js \
|
||||
ui/sessionMode.js \
|
||||
ui/shellEntry.js \
|
||||
ui/shellMountOperation.js \
|
||||
ui/slider.js \
|
||||
ui/notificationDaemon.js \
|
||||
ui/osdWindow.js \
|
||||
ui/overview.js \
|
||||
ui/overviewControls.js \
|
||||
ui/panel.js \
|
||||
ui/panelMenu.js \
|
||||
ui/pointerWatcher.js \
|
||||
ui/popupMenu.js \
|
||||
ui/remoteSearch.js \
|
||||
ui/remoteMenu.js \
|
||||
ui/runDialog.js \
|
||||
ui/screencast.js \
|
||||
ui/screenshot.js \
|
||||
ui/screenShield.js \
|
||||
ui/scripting.js \
|
||||
ui/search.js \
|
||||
ui/searchDisplay.js \
|
||||
ui/shellDBus.js \
|
||||
ui/status/accessibility.js \
|
||||
ui/status/brightness.js \
|
||||
ui/status/keyboard.js \
|
||||
ui/status/lockScreenMenu.js \
|
||||
ui/status/network.js \
|
||||
ui/status/power.js \
|
||||
ui/status/rfkill.js \
|
||||
ui/status/volume.js \
|
||||
ui/status/bluetooth.js \
|
||||
ui/status/screencast.js \
|
||||
ui/status/system.js \
|
||||
ui/switcherPopup.js \
|
||||
ui/tweener.js \
|
||||
ui/unlockDialog.js \
|
||||
ui/userMenu.js \
|
||||
ui/userWidget.js \
|
||||
ui/viewSelector.js \
|
||||
ui/wanda.js \
|
||||
ui/windowAttentionHandler.js \
|
||||
@ -102,7 +119,6 @@ nobase_dist_js_DATA = \
|
||||
ui/components/automountManager.js \
|
||||
ui/components/networkAgent.js \
|
||||
ui/components/polkitAgent.js \
|
||||
ui/components/recorder.js \
|
||||
ui/components/telepathyClient.js \
|
||||
ui/components/keyring.js \
|
||||
$(NULL)
|
||||
|
@ -45,6 +45,7 @@ const Application = new Lang.Class({
|
||||
this._extensionPrefsModules = {};
|
||||
|
||||
this._extensionIters = {};
|
||||
this._startupUuid = null;
|
||||
},
|
||||
|
||||
_buildModel: function() {
|
||||
@ -203,6 +204,7 @@ const Application = new Lang.Class({
|
||||
_scanExtensions: function() {
|
||||
let finder = new ExtensionUtils.ExtensionFinder();
|
||||
finder.connect('extension-found', Lang.bind(this, this._extensionFound));
|
||||
finder.connect('extensions-loaded', Lang.bind(this, this._extensionsLoaded));
|
||||
finder.scanExtensions();
|
||||
},
|
||||
|
||||
@ -212,6 +214,11 @@ const Application = new Lang.Class({
|
||||
this._extensionIters[extension.uuid] = iter;
|
||||
},
|
||||
|
||||
_extensionsLoaded: function() {
|
||||
if (this._startupUuid && this._extensionAvailable(this._startupUuid))
|
||||
this._selectExtension(this._startupUuid);
|
||||
this._startupUuid = null;
|
||||
},
|
||||
|
||||
_onActivate: function() {
|
||||
this._window.present();
|
||||
@ -232,10 +239,10 @@ const Application = new Lang.Class({
|
||||
// Strip off "extension:///" prefix which fakes a URI, if it exists
|
||||
uuid = stripPrefix(uuid, "extension:///");
|
||||
|
||||
if (!this._extensionAvailable(uuid))
|
||||
return 1;
|
||||
|
||||
this._selectExtension(uuid);
|
||||
if (this._extensionAvailable(uuid))
|
||||
this._selectExtension(uuid);
|
||||
else
|
||||
this._startupUuid = uuid;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
500
js/gdm/authPrompt.js
Normal file
500
js/gdm/authPrompt.js
Normal file
@ -0,0 +1,500 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Lang = imports.lang;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Animation = imports.ui.animation;
|
||||
const Batch = imports.gdm.batch;
|
||||
const GdmUtil = imports.gdm.util;
|
||||
const Params = imports.misc.params;
|
||||
const ShellEntry = imports.ui.shellEntry;
|
||||
const Tweener = imports.ui.tweener;
|
||||
const UserWidget = imports.ui.userWidget;
|
||||
|
||||
const DEFAULT_BUTTON_WELL_ICON_SIZE = 24;
|
||||
const DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1.0;
|
||||
const DEFAULT_BUTTON_WELL_ANIMATION_TIME = 0.3;
|
||||
|
||||
const MESSAGE_FADE_OUT_ANIMATION_TIME = 0.5;
|
||||
|
||||
const AuthPromptMode = {
|
||||
UNLOCK_ONLY: 0,
|
||||
UNLOCK_OR_LOG_IN: 1
|
||||
};
|
||||
|
||||
const AuthPromptStatus = {
|
||||
NOT_VERIFYING: 0,
|
||||
VERIFYING: 1,
|
||||
VERIFICATION_FAILED: 2,
|
||||
VERIFICATION_SUCCEEDED: 3
|
||||
};
|
||||
|
||||
const BeginRequestType = {
|
||||
PROVIDE_USERNAME: 0,
|
||||
DONT_PROVIDE_USERNAME: 1
|
||||
};
|
||||
|
||||
const AuthPrompt = new Lang.Class({
|
||||
Name: 'AuthPrompt',
|
||||
|
||||
_init: function(gdmClient, mode) {
|
||||
this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;
|
||||
|
||||
this._gdmClient = gdmClient;
|
||||
this._mode = mode;
|
||||
|
||||
let reauthenticationOnly;
|
||||
if (this._mode == AuthPromptMode.UNLOCK_ONLY)
|
||||
reauthenticationOnly = true;
|
||||
else if (this._mode == AuthPromptMode.UNLOCK_OR_LOG_IN)
|
||||
reauthenticationOnly = false;
|
||||
|
||||
this._userVerifier = new GdmUtil.ShellUserVerifier(this._gdmClient, { reauthenticationOnly: reauthenticationOnly });
|
||||
|
||||
this._userVerifier.connect('ask-question', Lang.bind(this, this._onAskQuestion));
|
||||
this._userVerifier.connect('show-message', Lang.bind(this, this._onShowMessage));
|
||||
this._userVerifier.connect('verification-failed', Lang.bind(this, this._onVerificationFailed));
|
||||
this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete));
|
||||
this._userVerifier.connect('reset', Lang.bind(this, this._onReset));
|
||||
this._userVerifier.connect('smartcard-status-changed', Lang.bind(this, this._onSmartcardStatusChanged));
|
||||
this.smartcardDetected = this._userVerifier.smartcardDetected;
|
||||
|
||||
this.connect('next', Lang.bind(this, function() {
|
||||
this.updateSensitivity(false);
|
||||
this.startSpinning();
|
||||
if (this._queryingService) {
|
||||
this._userVerifier.answerQuery(this._queryingService, this._entry.text);
|
||||
} else {
|
||||
this._preemptiveAnswer = this._entry.text;
|
||||
}
|
||||
}));
|
||||
|
||||
this.actor = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout',
|
||||
vertical: true });
|
||||
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
||||
this.actor.connect('key-press-event',
|
||||
Lang.bind(this, function(actor, event) {
|
||||
if (event.get_key_symbol() == Clutter.KEY_Escape) {
|
||||
this.cancel();
|
||||
}
|
||||
}));
|
||||
|
||||
this._userWell = new St.Bin({ x_fill: true,
|
||||
x_align: St.Align.START });
|
||||
this.actor.add(this._userWell,
|
||||
{ x_align: St.Align.START,
|
||||
x_fill: true,
|
||||
y_fill: true,
|
||||
expand: true });
|
||||
this._label = new St.Label({ style_class: 'login-dialog-prompt-label' });
|
||||
|
||||
this.actor.add(this._label,
|
||||
{ expand: true,
|
||||
x_fill: true,
|
||||
y_fill: true,
|
||||
x_align: St.Align.START });
|
||||
this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry',
|
||||
can_focus: true });
|
||||
ShellEntry.addContextMenu(this._entry, { isPassword: true });
|
||||
|
||||
this.actor.add(this._entry,
|
||||
{ expand: true,
|
||||
x_fill: true,
|
||||
y_fill: false,
|
||||
x_align: St.Align.START });
|
||||
|
||||
this._entry.grab_key_focus();
|
||||
|
||||
this._message = new St.Label({ opacity: 0,
|
||||
styleClass: 'login-dialog-message' });
|
||||
this._message.clutter_text.line_wrap = true;
|
||||
this.actor.add(this._message, { x_fill: true, y_align: St.Align.START });
|
||||
|
||||
this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box',
|
||||
vertical: false });
|
||||
this.actor.add(this._buttonBox,
|
||||
{ expand: true,
|
||||
x_align: St.Align.MIDDLE,
|
||||
y_align: St.Align.END });
|
||||
|
||||
this._defaultButtonWell = new St.Widget({ layout_manager: new Clutter.BinLayout() });
|
||||
this._defaultButtonWellActor = null;
|
||||
|
||||
this._initButtons();
|
||||
|
||||
let spinnerIcon = global.datadir + '/theme/process-working.svg';
|
||||
this._spinner = new Animation.AnimatedIcon(spinnerIcon, DEFAULT_BUTTON_WELL_ICON_SIZE);
|
||||
this._spinner.actor.opacity = 0;
|
||||
this._spinner.actor.show();
|
||||
this._defaultButtonWell.add_child(this._spinner.actor);
|
||||
},
|
||||
|
||||
_onDestroy: function() {
|
||||
this._userVerifier.clear();
|
||||
this._userVerifier.disconnectAll();
|
||||
this._userVerifier = null;
|
||||
},
|
||||
|
||||
_initButtons: function() {
|
||||
this.cancelButton = new St.Button({ style_class: 'modal-dialog-button',
|
||||
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
|
||||
reactive: true,
|
||||
can_focus: true,
|
||||
label: _("Cancel") });
|
||||
this.cancelButton.connect('clicked',
|
||||
Lang.bind(this, function() {
|
||||
this.cancel();
|
||||
}));
|
||||
this._buttonBox.add(this.cancelButton,
|
||||
{ expand: false,
|
||||
x_fill: false,
|
||||
y_fill: false,
|
||||
x_align: St.Align.START,
|
||||
y_align: St.Align.END });
|
||||
|
||||
this._buttonBox.add(this._defaultButtonWell,
|
||||
{ expand: true,
|
||||
x_fill: false,
|
||||
y_fill: false,
|
||||
x_align: St.Align.END,
|
||||
y_align: St.Align.MIDDLE });
|
||||
this.nextButton = new St.Button({ style_class: 'modal-dialog-button',
|
||||
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
|
||||
reactive: true,
|
||||
can_focus: true,
|
||||
label: _("Next") });
|
||||
this.nextButton.connect('clicked',
|
||||
Lang.bind(this, function() {
|
||||
this.emit('next');
|
||||
}));
|
||||
this.nextButton.add_style_pseudo_class('default');
|
||||
this._buttonBox.add(this.nextButton,
|
||||
{ expand: false,
|
||||
x_fill: false,
|
||||
y_fill: false,
|
||||
x_align: St.Align.END,
|
||||
y_align: St.Align.END });
|
||||
|
||||
this._updateNextButtonSensitivity(this._entry.text.length > 0);
|
||||
|
||||
this._entry.clutter_text.connect('text-changed',
|
||||
Lang.bind(this, function() {
|
||||
if (!this._userVerifier.hasPendingMessages)
|
||||
this._fadeOutMessage();
|
||||
|
||||
this._updateNextButtonSensitivity(this._entry.text.length > 0);
|
||||
}));
|
||||
this._entry.clutter_text.connect('activate', Lang.bind(this, function() {
|
||||
this.emit('next');
|
||||
}));
|
||||
},
|
||||
|
||||
_onAskQuestion: function(verifier, serviceName, question, passwordChar) {
|
||||
if (this._preemptiveAnswer) {
|
||||
if (this._queryingService)
|
||||
this._userVerifier.answerQuery(this._queryingService, this._preemptiveAnswer);
|
||||
this._preemptiveAnswer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._queryingService)
|
||||
this.clear();
|
||||
|
||||
this._queryingService = serviceName;
|
||||
this.setPasswordChar(passwordChar);
|
||||
this.setQuestion(question);
|
||||
|
||||
if (passwordChar) {
|
||||
if (this._userVerifier.reauthenticating)
|
||||
this.nextButton.label = _("Unlock");
|
||||
else
|
||||
this.nextButton.label = C_("button", "Sign In");
|
||||
} else {
|
||||
this.nextButton.label = _("Next");
|
||||
}
|
||||
|
||||
this.updateSensitivity(true);
|
||||
this.emit('prompted');
|
||||
},
|
||||
|
||||
_onSmartcardStatusChanged: function() {
|
||||
this.smartcardDetected = this._userVerifier.smartcardDetected;
|
||||
|
||||
// Most of the time we want to reset if the user inserts or removes
|
||||
// a smartcard. Smartcard insertion "preempts" what the user was
|
||||
// doing, and smartcard removal aborts the preemption.
|
||||
// The exceptions are: 1) Don't reset on smartcard insertion if we're already verifying
|
||||
// with a smartcard
|
||||
// 2) Don't reset if we've already succeeded at verification and
|
||||
// the user is getting logged in.
|
||||
if (this._userVerifier.serviceIsDefault(GdmUtil.SMARTCARD_SERVICE_NAME) &&
|
||||
this.verificationStatus == AuthPromptStatus.VERIFYING &&
|
||||
this.smartcardDetected)
|
||||
return;
|
||||
|
||||
if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED)
|
||||
this.reset();
|
||||
},
|
||||
|
||||
_onShowMessage: function(userVerifier, message, type) {
|
||||
this.setMessage(message, type);
|
||||
this.emit('prompted');
|
||||
},
|
||||
|
||||
_onVerificationFailed: function() {
|
||||
this._queryingService = null;
|
||||
this.clear();
|
||||
|
||||
this.updateSensitivity(true);
|
||||
this.setActorInDefaultButtonWell(null);
|
||||
this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED;
|
||||
},
|
||||
|
||||
_onVerificationComplete: function() {
|
||||
this.verificationStatus = AuthPromptStatus.VERIFICATION_SUCCEEDED;
|
||||
},
|
||||
|
||||
_onReset: function() {
|
||||
if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) {
|
||||
this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;
|
||||
this.reset();
|
||||
}
|
||||
},
|
||||
|
||||
addActorToDefaultButtonWell: function(actor) {
|
||||
this._defaultButtonWell.add_child(actor);
|
||||
},
|
||||
|
||||
setActorInDefaultButtonWell: function(actor, animate) {
|
||||
if (!this._defaultButtonWellActor &&
|
||||
!actor)
|
||||
return;
|
||||
|
||||
let oldActor = this._defaultButtonWellActor;
|
||||
|
||||
if (oldActor)
|
||||
Tweener.removeTweens(oldActor);
|
||||
|
||||
let isSpinner;
|
||||
if (actor == this._spinner.actor)
|
||||
isSpinner = true;
|
||||
else
|
||||
isSpinner = false;
|
||||
|
||||
if (this._defaultButtonWellActor != actor && oldActor) {
|
||||
if (!animate) {
|
||||
oldActor.opacity = 0;
|
||||
} else {
|
||||
Tweener.addTween(oldActor,
|
||||
{ opacity: 0,
|
||||
time: DEFAULT_BUTTON_WELL_ANIMATION_TIME,
|
||||
delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY,
|
||||
transition: 'linear',
|
||||
onCompleteScope: this,
|
||||
onComplete: function() {
|
||||
if (isSpinner) {
|
||||
if (this._spinner)
|
||||
this._spinner.stop();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (actor) {
|
||||
if (isSpinner)
|
||||
this._spinner.play();
|
||||
|
||||
if (!animate)
|
||||
actor.opacity = 255;
|
||||
else
|
||||
Tweener.addTween(actor,
|
||||
{ opacity: 255,
|
||||
time: DEFAULT_BUTTON_WELL_ANIMATION_TIME,
|
||||
delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY,
|
||||
transition: 'linear' });
|
||||
}
|
||||
|
||||
this._defaultButtonWellActor = actor;
|
||||
},
|
||||
|
||||
startSpinning: function() {
|
||||
this.setActorInDefaultButtonWell(this._spinner.actor, true);
|
||||
},
|
||||
|
||||
stopSpinning: function() {
|
||||
this.setActorInDefaultButtonWell(null, false);
|
||||
},
|
||||
|
||||
clear: function() {
|
||||
this._entry.text = '';
|
||||
this.stopSpinning();
|
||||
},
|
||||
|
||||
setPasswordChar: function(passwordChar) {
|
||||
this._entry.clutter_text.set_password_char(passwordChar);
|
||||
this._entry.menu.isPassword = passwordChar != '';
|
||||
},
|
||||
|
||||
setQuestion: function(question) {
|
||||
this._label.set_text(question);
|
||||
|
||||
this._label.show();
|
||||
this._entry.show();
|
||||
|
||||
this._entry.grab_key_focus();
|
||||
},
|
||||
|
||||
getAnswer: function() {
|
||||
let text;
|
||||
|
||||
if (this._preemptiveAnswer) {
|
||||
text = this._preemptiveAnswer;
|
||||
this._preemptiveAnswer = null;
|
||||
} else {
|
||||
text = this._entry.get_text();
|
||||
}
|
||||
|
||||
return text;
|
||||
},
|
||||
|
||||
_fadeOutMessage: function() {
|
||||
if (this._message.opacity == 0)
|
||||
return;
|
||||
Tweener.removeTweens(this._message);
|
||||
Tweener.addTween(this._message,
|
||||
{ opacity: 0,
|
||||
time: MESSAGE_FADE_OUT_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
},
|
||||
|
||||
setMessage: function(message, type) {
|
||||
if (type == GdmUtil.MessageType.ERROR)
|
||||
this._message.add_style_class_name('login-dialog-message-warning');
|
||||
else
|
||||
this._message.remove_style_class_name('login-dialog-message-warning');
|
||||
|
||||
if (type == GdmUtil.MessageType.HINT)
|
||||
this._message.add_style_class_name('login-dialog-message-hint');
|
||||
else
|
||||
this._message.remove_style_class_name('login-dialog-message-hint');
|
||||
|
||||
if (message) {
|
||||
Tweener.removeTweens(this._message);
|
||||
this._message.text = message;
|
||||
this._message.opacity = 255;
|
||||
} else {
|
||||
this._message.opacity = 0;
|
||||
}
|
||||
},
|
||||
|
||||
_updateNextButtonSensitivity: function(sensitive) {
|
||||
this.nextButton.reactive = sensitive;
|
||||
this.nextButton.can_focus = sensitive;
|
||||
},
|
||||
|
||||
updateSensitivity: function(sensitive) {
|
||||
this._updateNextButtonSensitivity(sensitive);
|
||||
this._entry.reactive = sensitive;
|
||||
this._entry.clutter_text.editable = sensitive;
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
this.setActorInDefaultButtonWell(null, true);
|
||||
this.actor.hide();
|
||||
this._message.opacity = 0;
|
||||
|
||||
this.setUser(null);
|
||||
|
||||
this.updateSensitivity(true);
|
||||
this._entry.set_text('');
|
||||
},
|
||||
|
||||
setUser: function(user) {
|
||||
if (user) {
|
||||
let userWidget = new UserWidget.UserWidget(user);
|
||||
this._userWell.set_child(userWidget.actor);
|
||||
} else {
|
||||
this._userWell.set_child(null);
|
||||
}
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
let oldStatus = this.verificationStatus;
|
||||
this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;
|
||||
|
||||
if (oldStatus == AuthPromptStatus.VERIFYING)
|
||||
this._userVerifier.cancel();
|
||||
|
||||
this._queryingService = null;
|
||||
this.clear();
|
||||
this._message.opacity = 0;
|
||||
this.setUser(null);
|
||||
this.stopSpinning();
|
||||
|
||||
if (oldStatus == AuthPromptStatus.VERIFICATION_FAILED)
|
||||
this.emit('failed');
|
||||
|
||||
let beginRequestType;
|
||||
|
||||
if (this._mode == AuthPromptMode.UNLOCK_ONLY) {
|
||||
// The user is constant at the unlock screen, so it will immediately
|
||||
// respond to the request with the username
|
||||
beginRequestType = BeginRequestType.PROVIDE_USERNAME;
|
||||
} else if (this.smartcardDetected &&
|
||||
this._userVerifier.serviceIsForeground(GdmUtil.SMARTCARD_SERVICE_NAME)) {
|
||||
// We don't need to know the username if the user preempted the login screen
|
||||
// with a smartcard.
|
||||
beginRequestType = BeginRequestType.DONT_PROVIDE_USERNAME;
|
||||
} else {
|
||||
// In all other cases, we should get the username up front.
|
||||
beginRequestType = BeginRequestType.PROVIDE_USERNAME;
|
||||
}
|
||||
|
||||
this.emit('reset', beginRequestType);
|
||||
},
|
||||
|
||||
addCharacter: function(unichar) {
|
||||
if (!this._entry.visible)
|
||||
return;
|
||||
|
||||
this._entry.grab_key_focus();
|
||||
this._entry.clutter_text.insert_unichar(unichar);
|
||||
},
|
||||
|
||||
begin: function(params) {
|
||||
params = Params.parse(params, { userName: null,
|
||||
hold: null });
|
||||
|
||||
this.updateSensitivity(false);
|
||||
|
||||
let hold = params.hold;
|
||||
if (!hold)
|
||||
hold = new Batch.Hold();
|
||||
|
||||
this._userVerifier.begin(params.userName, hold);
|
||||
this.verificationStatus = AuthPromptStatus.VERIFYING;
|
||||
},
|
||||
|
||||
finish: function(onComplete) {
|
||||
if (!this._userVerifier.hasPendingMessages) {
|
||||
onComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
let signalId = this._userVerifier.connect('no-more-messages',
|
||||
Lang.bind(this, function() {
|
||||
this._userVerifier.disconnect(signalId);
|
||||
onComplete();
|
||||
}));
|
||||
},
|
||||
|
||||
cancel: function() {
|
||||
this.reset();
|
||||
this.emit('cancelled');
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(AuthPrompt.prototype);
|
File diff suppressed because it is too large
Load Diff
@ -1,129 +0,0 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
/*
|
||||
* Copyright 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, 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.
|
||||
*/
|
||||
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
|
||||
const LoginManager = imports.misc.loginManager;
|
||||
|
||||
const GdmUtil = imports.gdm.util;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
|
||||
const PowerMenuButton = new Lang.Class({
|
||||
Name: 'PowerMenuButton',
|
||||
Extends: PanelMenu.SystemStatusButton,
|
||||
|
||||
_init: function() {
|
||||
/* Translators: accessible name of the power menu in the login screen */
|
||||
this.parent('system-shutdown-symbolic', _("Power"));
|
||||
|
||||
this._loginManager = LoginManager.getLoginManager();
|
||||
|
||||
this._settings = new Gio.Settings({ schema: GdmUtil.LOGIN_SCREEN_SCHEMA });
|
||||
this._settings.connect('changed::disable-restart-buttons',
|
||||
Lang.bind(this, this._updateVisibility));
|
||||
|
||||
this._createSubMenu();
|
||||
|
||||
// ConsoleKit doesn't send notifications when shutdown/reboot
|
||||
// are disabled, so we update the menu item each time the menu opens
|
||||
this.menu.connect('open-state-changed', Lang.bind(this,
|
||||
function(menu, open) {
|
||||
if (open) {
|
||||
this._updateHaveShutdown();
|
||||
this._updateHaveRestart();
|
||||
this._updateHaveSuspend();
|
||||
}
|
||||
}));
|
||||
this._updateHaveShutdown();
|
||||
this._updateHaveRestart();
|
||||
this._updateHaveSuspend();
|
||||
},
|
||||
|
||||
_updateVisibility: function() {
|
||||
let shouldBeVisible = (this._haveSuspend || this._haveShutdown || this._haveRestart);
|
||||
this.actor.visible = shouldBeVisible && !this._settings.get_boolean('disable-restart-buttons');
|
||||
},
|
||||
|
||||
_updateHaveShutdown: function() {
|
||||
this._loginManager.canPowerOff(Lang.bind(this, function(result) {
|
||||
this._haveShutdown = result;
|
||||
this._powerOffItem.actor.visible = this._haveShutdown;
|
||||
this._updateVisibility();
|
||||
}));
|
||||
},
|
||||
|
||||
_updateHaveRestart: function() {
|
||||
this._loginManager.canReboot(Lang.bind(this, function(result) {
|
||||
this._haveRestart = result;
|
||||
this._restartItem.actor.visible = this._haveRestart;
|
||||
this._updateVisibility();
|
||||
}));
|
||||
},
|
||||
|
||||
_updateHaveSuspend: function() {
|
||||
this._loginManager.canSuspend(Lang.bind(this, function(result) {
|
||||
this._haveSuspend = result;
|
||||
this._suspendItem.actor.visible = this._haveSuspend;
|
||||
this._updateVisibility();
|
||||
}));
|
||||
},
|
||||
|
||||
_createSubMenu: function() {
|
||||
let item;
|
||||
|
||||
item = new PopupMenu.PopupMenuItem(_("Suspend"));
|
||||
item.connect('activate', Lang.bind(this, this._onActivateSuspend));
|
||||
this.menu.addMenuItem(item);
|
||||
this._suspendItem = item;
|
||||
|
||||
item = new PopupMenu.PopupMenuItem(_("Restart"));
|
||||
item.connect('activate', Lang.bind(this, this._onActivateRestart));
|
||||
this.menu.addMenuItem(item);
|
||||
this._restartItem = item;
|
||||
|
||||
item = new PopupMenu.PopupMenuItem(_("Power Off"));
|
||||
item.connect('activate', Lang.bind(this, this._onActivatePowerOff));
|
||||
this.menu.addMenuItem(item);
|
||||
this._powerOffItem = item;
|
||||
},
|
||||
|
||||
_onActivateSuspend: function() {
|
||||
if (!this._haveSuspend)
|
||||
return;
|
||||
|
||||
this._loginManager.suspend();
|
||||
},
|
||||
|
||||
_onActivateRestart: function() {
|
||||
if (!this._haveRestart)
|
||||
return;
|
||||
|
||||
this._loginManager.reboot();
|
||||
},
|
||||
|
||||
_onActivatePowerOff: function() {
|
||||
if (!this._haveShutdown)
|
||||
return;
|
||||
|
||||
this._loginManager.powerOff();
|
||||
}
|
||||
});
|
@ -63,7 +63,7 @@ const Manager = new Lang.Class({
|
||||
Lang.bind(this, this._reloadRealms))
|
||||
this._realms = {};
|
||||
|
||||
this._aggregateProvider.connect('g-properties-changed',
|
||||
this._signalId = this._aggregateProvider.connect('g-properties-changed',
|
||||
Lang.bind(this, function(proxy, properties) {
|
||||
if ('Realms' in properties.deep_unpack())
|
||||
this._reloadRealms();
|
||||
@ -106,7 +106,7 @@ const Manager = new Lang.Class({
|
||||
realm.connect('g-properties-changed',
|
||||
Lang.bind(this, function(proxy, properties) {
|
||||
if ('Configured' in properties.deep_unpack())
|
||||
this._reloadRealm();
|
||||
this._reloadRealm(realm);
|
||||
}));
|
||||
},
|
||||
|
||||
@ -134,6 +134,18 @@ const Manager = new Lang.Class({
|
||||
this._updateLoginFormat();
|
||||
|
||||
return this._loginFormat;
|
||||
},
|
||||
|
||||
release: function() {
|
||||
Service(Gio.DBus.system,
|
||||
'org.freedesktop.realmd',
|
||||
'/org/freedesktop/realmd',
|
||||
function(service) {
|
||||
service.ReleaseRemote();
|
||||
});
|
||||
this._aggregateProvider.disconnect(this._signalId);
|
||||
this._realms = { };
|
||||
this._updateLoginFormat();
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(Manager.prototype)
|
||||
|
370
js/gdm/util.js
370
js/gdm/util.js
@ -1,23 +1,31 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Batch = imports.gdm.batch;
|
||||
const Fprint = imports.gdm.fingerprint;
|
||||
const Realmd = imports.gdm.realmd;
|
||||
const Main = imports.ui.main;
|
||||
const Params = imports.misc.params;
|
||||
const ShellEntry = imports.ui.shellEntry;
|
||||
const SmartcardManager = imports.misc.smartcardManager;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
const PASSWORD_SERVICE_NAME = 'gdm-password';
|
||||
const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint';
|
||||
const SMARTCARD_SERVICE_NAME = 'gdm-smartcard';
|
||||
const FADE_ANIMATION_TIME = 0.16;
|
||||
const CLONE_FADE_ANIMATION_TIME = 0.25;
|
||||
|
||||
const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen';
|
||||
const PASSWORD_AUTHENTICATION_KEY = 'enable-password-authentication';
|
||||
const FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication';
|
||||
const SMARTCARD_AUTHENTICATION_KEY = 'enable-smartcard-authentication';
|
||||
const BANNER_MESSAGE_KEY = 'banner-message-enable';
|
||||
const BANNER_MESSAGE_TEXT_KEY = 'banner-message-text';
|
||||
const ALLOWED_FAILURES_KEY = 'allowed-failures';
|
||||
@ -25,6 +33,16 @@ const ALLOWED_FAILURES_KEY = 'allowed-failures';
|
||||
const LOGO_KEY = 'logo';
|
||||
const DISABLE_USER_LIST_KEY = 'disable-user-list';
|
||||
|
||||
// Give user 16ms to read each character of a PAM message
|
||||
const USER_READ_TIME = 16
|
||||
|
||||
const MessageType = {
|
||||
NONE: 0,
|
||||
ERROR: 1,
|
||||
INFO: 2,
|
||||
HINT: 3
|
||||
};
|
||||
|
||||
function fadeInActor(actor) {
|
||||
if (actor.opacity == 255 && actor.visible)
|
||||
return null;
|
||||
@ -71,6 +89,34 @@ function fadeOutActor(actor) {
|
||||
return hold;
|
||||
}
|
||||
|
||||
function cloneAndFadeOutActor(actor) {
|
||||
// Immediately hide actor so its sibling can have its space
|
||||
// and position, but leave a non-reactive clone on-screen,
|
||||
// so from the user's point of view it smoothly fades away
|
||||
// and reveals its sibling.
|
||||
actor.hide();
|
||||
|
||||
let clone = new Clutter.Clone({ source: actor,
|
||||
reactive: false });
|
||||
|
||||
Main.uiGroup.add_child(clone);
|
||||
|
||||
let [x, y] = actor.get_transformed_position();
|
||||
clone.set_position(x, y);
|
||||
|
||||
let hold = new Batch.Hold();
|
||||
Tweener.addTween(clone,
|
||||
{ opacity: 0,
|
||||
time: CLONE_FADE_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: function() {
|
||||
clone.destroy();
|
||||
hold.release();
|
||||
}
|
||||
});
|
||||
return hold;
|
||||
}
|
||||
|
||||
const ShellUserVerifier = new Lang.Class({
|
||||
Name: 'ShellUserVerifier',
|
||||
|
||||
@ -81,9 +127,28 @@ const ShellUserVerifier = new Lang.Class({
|
||||
this._client = client;
|
||||
|
||||
this._settings = new Gio.Settings({ schema: LOGIN_SCREEN_SCHEMA });
|
||||
this._settings.connect('changed',
|
||||
Lang.bind(this, this._updateDefaultService));
|
||||
this._updateDefaultService();
|
||||
|
||||
this._fprintManager = new Fprint.FprintManager();
|
||||
this._realmManager = new Realmd.Manager();
|
||||
this._smartcardManager = SmartcardManager.getSmartcardManager();
|
||||
|
||||
// We check for smartcards right away, since an inserted smartcard
|
||||
// at startup should result in immediately initiating authentication.
|
||||
// This is different than fingeprint readers, where we only check them
|
||||
// after a user has been picked.
|
||||
this._checkForSmartcard();
|
||||
|
||||
this._smartcardManager.connect('smartcard-inserted',
|
||||
Lang.bind(this, this._checkForSmartcard));
|
||||
this._smartcardManager.connect('smartcard-removed',
|
||||
Lang.bind(this, this._checkForSmartcard));
|
||||
|
||||
this._messageQueue = [];
|
||||
this._messageQueueTimeoutId = 0;
|
||||
this.hasPendingMessages = false;
|
||||
this.reauthenticating = false;
|
||||
|
||||
this._failCounter = 0;
|
||||
},
|
||||
@ -92,6 +157,7 @@ const ShellUserVerifier = new Lang.Class({
|
||||
this._cancellable = new Gio.Cancellable();
|
||||
this._hold = hold;
|
||||
this._userName = userName;
|
||||
this.reauthenticating = false;
|
||||
|
||||
this._checkForFingerprintReader();
|
||||
|
||||
@ -109,8 +175,10 @@ const ShellUserVerifier = new Lang.Class({
|
||||
if (this._cancellable)
|
||||
this._cancellable.cancel();
|
||||
|
||||
if (this._userVerifier)
|
||||
if (this._userVerifier) {
|
||||
this._userVerifier.call_cancel_sync(null);
|
||||
this.clear();
|
||||
}
|
||||
},
|
||||
|
||||
clear: function() {
|
||||
@ -123,33 +191,119 @@ const ShellUserVerifier = new Lang.Class({
|
||||
this._userVerifier.run_dispose();
|
||||
this._userVerifier = null;
|
||||
}
|
||||
|
||||
this._clearMessageQueue();
|
||||
},
|
||||
|
||||
answerQuery: function(serviceName, answer) {
|
||||
// Clear any previous message
|
||||
this.emit('show-message', null, null);
|
||||
if (!this.hasPendingMessages) {
|
||||
this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null);
|
||||
} else {
|
||||
let signalId = this.connect('no-more-messages',
|
||||
Lang.bind(this, function() {
|
||||
this.disconnect(signalId);
|
||||
this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null);
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null);
|
||||
_getIntervalForMessage: function(message) {
|
||||
// We probably could be smarter here
|
||||
return message.length * USER_READ_TIME;
|
||||
},
|
||||
|
||||
finishMessageQueue: function() {
|
||||
if (!this.hasPendingMessages)
|
||||
return;
|
||||
|
||||
this._messageQueue = [];
|
||||
|
||||
this.hasPendingMessages = false;
|
||||
this.emit('no-more-messages');
|
||||
},
|
||||
|
||||
_queueMessageTimeout: function() {
|
||||
if (this._messageQueue.length == 0) {
|
||||
this.finishMessageQueue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._messageQueueTimeoutId != 0)
|
||||
return;
|
||||
|
||||
let message = this._messageQueue.shift();
|
||||
|
||||
this.emit('show-message', message.text, message.type);
|
||||
|
||||
this._messageQueueTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
|
||||
message.interval,
|
||||
Lang.bind(this, function() {
|
||||
this._messageQueueTimeoutId = 0;
|
||||
this._queueMessageTimeout();
|
||||
}));
|
||||
},
|
||||
|
||||
_queueMessage: function(message, messageType) {
|
||||
let interval = this._getIntervalForMessage(message);
|
||||
|
||||
this.hasPendingMessages = true;
|
||||
this._messageQueue.push({ text: message, type: messageType, interval: interval });
|
||||
this._queueMessageTimeout();
|
||||
},
|
||||
|
||||
_clearMessageQueue: function() {
|
||||
this.finishMessageQueue();
|
||||
|
||||
if (this._messageQueueTimeoutId != 0) {
|
||||
GLib.source_remove(this._messageQueueTimeoutId);
|
||||
this._messageQueueTimeoutId = 0;
|
||||
}
|
||||
this.emit('show-message', null, MessageType.NONE);
|
||||
},
|
||||
|
||||
_checkForFingerprintReader: function() {
|
||||
this._haveFingerprintReader = false;
|
||||
|
||||
if (!this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY))
|
||||
if (!this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY)) {
|
||||
this._updateDefaultService();
|
||||
return;
|
||||
}
|
||||
|
||||
this._fprintManager.GetDefaultDeviceRemote(Gio.DBusCallFlags.NONE, this._cancellable, Lang.bind(this,
|
||||
function(device, error) {
|
||||
if (!error && device)
|
||||
this._haveFingerprintReader = true;
|
||||
this._updateDefaultService();
|
||||
}));
|
||||
},
|
||||
|
||||
_checkForSmartcard: function() {
|
||||
let smartcardDetected;
|
||||
|
||||
if (!this._settings.get_boolean(SMARTCARD_AUTHENTICATION_KEY))
|
||||
smartcardDetected = false;
|
||||
else if (this.reauthenticating)
|
||||
smartcardDetected = this._smartcardManager.hasInsertedLoginToken();
|
||||
else
|
||||
smartcardDetected = this._smartcardManager.hasInsertedTokens();
|
||||
|
||||
if (smartcardDetected != this.smartcardDetected) {
|
||||
this.smartcardDetected = smartcardDetected;
|
||||
|
||||
if (this.smartcardDetected)
|
||||
this._preemptingService = SMARTCARD_SERVICE_NAME;
|
||||
else if (this._preemptingService == SMARTCARD_SERVICE_NAME)
|
||||
this._preemptingService = null;
|
||||
|
||||
this.emit('smartcard-status-changed');
|
||||
}
|
||||
},
|
||||
|
||||
_reportInitError: function(where, error) {
|
||||
logError(error, where);
|
||||
this._hold.release();
|
||||
|
||||
this.emit('show-message', _("Authentication error"), 'login-dialog-message-warning');
|
||||
this._queueMessage(_("Authentication error"), MessageType.ERROR);
|
||||
this._verificationFailed(false);
|
||||
},
|
||||
|
||||
@ -170,6 +324,7 @@ const ShellUserVerifier = new Lang.Class({
|
||||
return;
|
||||
}
|
||||
|
||||
this.reauthenticating = true;
|
||||
this._connectSignals();
|
||||
this._beginVerification();
|
||||
this._hold.release();
|
||||
@ -200,126 +355,113 @@ const ShellUserVerifier = new Lang.Class({
|
||||
this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete));
|
||||
},
|
||||
|
||||
_beginVerification: function() {
|
||||
_getForegroundService: function() {
|
||||
if (this._preemptingService)
|
||||
return this._preemptingService;
|
||||
|
||||
return this._defaultService;
|
||||
},
|
||||
|
||||
serviceIsForeground: function(serviceName) {
|
||||
return serviceName == this._getForegroundService();
|
||||
},
|
||||
|
||||
serviceIsDefault: function(serviceName) {
|
||||
return serviceName == this._defaultService;
|
||||
},
|
||||
|
||||
_updateDefaultService: function() {
|
||||
if (this._settings.get_boolean(PASSWORD_AUTHENTICATION_KEY))
|
||||
this._defaultService = PASSWORD_SERVICE_NAME;
|
||||
else if (this.smartcardDetected)
|
||||
this._defaultService = SMARTCARD_SERVICE_NAME;
|
||||
else if (this._haveFingerprintReader)
|
||||
this._defaultService = FINGERPRINT_SERVICE_NAME;
|
||||
},
|
||||
|
||||
_startService: function(serviceName) {
|
||||
this._hold.acquire();
|
||||
|
||||
if (this._userName) {
|
||||
this._userVerifier.call_begin_verification_for_user(PASSWORD_SERVICE_NAME,
|
||||
this._userName,
|
||||
this._cancellable,
|
||||
Lang.bind(this, function(obj, result) {
|
||||
try {
|
||||
obj.call_begin_verification_for_user_finish(result);
|
||||
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
|
||||
return;
|
||||
} catch(e) {
|
||||
this._reportInitError('Failed to start verification for user', e);
|
||||
return;
|
||||
}
|
||||
this._userVerifier.call_begin_verification_for_user(serviceName,
|
||||
this._userName,
|
||||
this._cancellable,
|
||||
Lang.bind(this, function(obj, result) {
|
||||
try {
|
||||
obj.call_begin_verification_for_user_finish(result);
|
||||
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
|
||||
return;
|
||||
} catch(e) {
|
||||
this._reportInitError('Failed to start verification for user', e);
|
||||
return;
|
||||
}
|
||||
|
||||
this._hold.release();
|
||||
}));
|
||||
|
||||
if (this._haveFingerprintReader) {
|
||||
this._hold.acquire();
|
||||
|
||||
this._userVerifier.call_begin_verification_for_user(FINGERPRINT_SERVICE_NAME,
|
||||
this._userName,
|
||||
this._cancellable,
|
||||
Lang.bind(this, function(obj, result) {
|
||||
try {
|
||||
obj.call_begin_verification_for_user_finish(result);
|
||||
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
|
||||
return;
|
||||
} catch(e) {
|
||||
this._reportInitError('Failed to start fingerprint verification for user', e);
|
||||
return;
|
||||
}
|
||||
|
||||
this._hold.release();
|
||||
}));
|
||||
}
|
||||
this._hold.release();
|
||||
}));
|
||||
} else {
|
||||
this._userVerifier.call_begin_verification(PASSWORD_SERVICE_NAME,
|
||||
this._cancellable,
|
||||
Lang.bind(this, function(obj, result) {
|
||||
try {
|
||||
obj.call_begin_verification_finish(result);
|
||||
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
|
||||
return;
|
||||
} catch(e) {
|
||||
this._reportInitError('Failed to start verification', e);
|
||||
return;
|
||||
}
|
||||
this._userVerifier.call_begin_verification(serviceName,
|
||||
this._cancellable,
|
||||
Lang.bind(this, function(obj, result) {
|
||||
try {
|
||||
obj.call_begin_verification_finish(result);
|
||||
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
|
||||
return;
|
||||
} catch(e) {
|
||||
this._reportInitError('Failed to start verification', e);
|
||||
return;
|
||||
}
|
||||
|
||||
this._hold.release();
|
||||
}));
|
||||
this._hold.release();
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
_beginVerification: function() {
|
||||
this._startService(this._getForegroundService());
|
||||
|
||||
if (this._userName && this._haveFingerprintReader && !this.serviceIsForeground(FINGERPRINT_SERVICE_NAME))
|
||||
this._startService(FINGERPRINT_SERVICE_NAME);
|
||||
},
|
||||
|
||||
_onInfo: function(client, serviceName, info) {
|
||||
// We don't display fingerprint messages, because they
|
||||
// have words like UPEK in them. Instead we use the messages
|
||||
// as a cue to display our own message.
|
||||
if (serviceName == FINGERPRINT_SERVICE_NAME &&
|
||||
if (this.serviceIsForeground(serviceName)) {
|
||||
this._queueMessage(info, MessageType.INFO);
|
||||
} else if (serviceName == FINGERPRINT_SERVICE_NAME &&
|
||||
this._haveFingerprintReader) {
|
||||
// We don't show fingerprint messages directly since it's
|
||||
// not the main auth service. Instead we use the messages
|
||||
// as a cue to display our own message.
|
||||
|
||||
// Translators: this message is shown below the password entry field
|
||||
// to indicate the user can swipe their finger instead
|
||||
this.emit('show-login-hint', _("(or swipe finger)"));
|
||||
} else if (serviceName == PASSWORD_SERVICE_NAME) {
|
||||
this.emit('show-message', info, 'login-dialog-message-info');
|
||||
this._queueMessage(_("(or swipe finger)"), MessageType.HINT);
|
||||
}
|
||||
},
|
||||
|
||||
_onProblem: function(client, serviceName, problem) {
|
||||
// we don't want to show auth failed messages to
|
||||
// users who haven't enrolled their fingerprint.
|
||||
if (serviceName != PASSWORD_SERVICE_NAME)
|
||||
if (!this.serviceIsForeground(serviceName))
|
||||
return;
|
||||
this.emit('show-message', problem, 'login-dialog-message-warning');
|
||||
},
|
||||
|
||||
_showRealmLoginHint: function() {
|
||||
if (this._realmManager.loginFormat) {
|
||||
let hint = this._realmManager.loginFormat;
|
||||
|
||||
hint = hint.replace(/%U/g, 'user');
|
||||
hint = hint.replace(/%D/g, 'DOMAIN');
|
||||
hint = hint.replace(/%[^UD]/g, '');
|
||||
|
||||
// Translators: this message is shown below the username entry field
|
||||
// to clue the user in on how to login to the local network realm
|
||||
this.emit('show-login-hint',
|
||||
_("(e.g., user or %s)").format(hint));
|
||||
}
|
||||
this._queueMessage(problem, MessageType.ERROR);
|
||||
},
|
||||
|
||||
_onInfoQuery: function(client, serviceName, question) {
|
||||
// We only expect questions to come from the main auth service
|
||||
if (serviceName != PASSWORD_SERVICE_NAME)
|
||||
if (!this.serviceIsForeground(serviceName))
|
||||
return;
|
||||
|
||||
this._showRealmLoginHint();
|
||||
this._realmLoginHintSignalId = this._realmManager.connect('login-format-changed',
|
||||
Lang.bind(this, this._showRealmLoginHint));
|
||||
|
||||
this.emit('ask-question', serviceName, question, '');
|
||||
},
|
||||
|
||||
_onSecretInfoQuery: function(client, serviceName, secretQuestion) {
|
||||
// We only expect secret requests to come from the main auth service
|
||||
if (serviceName != PASSWORD_SERVICE_NAME)
|
||||
if (!this.serviceIsForeground(serviceName))
|
||||
return;
|
||||
|
||||
this.emit('ask-question', serviceName, secretQuestion, '\u25cf');
|
||||
},
|
||||
|
||||
_onReset: function() {
|
||||
this.clear();
|
||||
|
||||
// Clear previous attempts to authenticate
|
||||
this._failCounter = 0;
|
||||
this._updateDefaultService();
|
||||
|
||||
this.emit('reset');
|
||||
},
|
||||
@ -328,6 +470,15 @@ const ShellUserVerifier = new Lang.Class({
|
||||
this.emit('verification-complete');
|
||||
},
|
||||
|
||||
_cancelAndReset: function() {
|
||||
this.cancel();
|
||||
this._onReset();
|
||||
},
|
||||
|
||||
_retry: function() {
|
||||
this.begin(this._userName, new Batch.Hold());
|
||||
},
|
||||
|
||||
_verificationFailed: function(retry) {
|
||||
// For Not Listed / enterprise logins, immediately reset
|
||||
// the dialog
|
||||
@ -339,15 +490,25 @@ const ShellUserVerifier = new Lang.Class({
|
||||
this._failCounter < this._settings.get_int(ALLOWED_FAILURES_KEY);
|
||||
|
||||
if (canRetry) {
|
||||
this.clear();
|
||||
this.begin(this._userName, new Batch.Hold());
|
||||
if (!this.hasPendingMessages) {
|
||||
this._retry();
|
||||
} else {
|
||||
let signalId = this.connect('no-more-messages',
|
||||
Lang.bind(this, function() {
|
||||
this.disconnect(signalId);
|
||||
this._retry();
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
// Allow some time to see the message, then reset everything
|
||||
Mainloop.timeout_add(3000, Lang.bind(this, function() {
|
||||
this.cancel();
|
||||
|
||||
this._onReset();
|
||||
}));
|
||||
if (!this.hasPendingMessages) {
|
||||
this._cancelAndReset();
|
||||
} else {
|
||||
let signalId = this.connect('no-more-messages',
|
||||
Lang.bind(this, function() {
|
||||
this.disconnect(signalId);
|
||||
this._cancelAndReset();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
this.emit('verification-failed');
|
||||
@ -357,16 +518,9 @@ const ShellUserVerifier = new Lang.Class({
|
||||
// if the password service fails, then cancel everything.
|
||||
// But if, e.g., fingerprint fails, still give
|
||||
// password authentication a chance to succeed
|
||||
if (serviceName == PASSWORD_SERVICE_NAME) {
|
||||
if (this.serviceIsForeground(serviceName)) {
|
||||
this._verificationFailed(true);
|
||||
}
|
||||
|
||||
this.emit('hide-login-hint');
|
||||
|
||||
if (this._realmLoginHintSignalId) {
|
||||
this._realmManager.disconnect(this._realmLoginHintSignalId);
|
||||
this._realmLoginHintSignalId = 0;
|
||||
}
|
||||
},
|
||||
});
|
||||
Signals.addSignalMethods(ShellUserVerifier.prototype);
|
||||
|
@ -11,6 +11,7 @@ const Gio = imports.gi.Gio;
|
||||
const ShellJS = imports.gi.ShellJS;
|
||||
|
||||
const Config = imports.misc.config;
|
||||
const FileUtils = imports.misc.fileUtils;
|
||||
|
||||
const ExtensionType = {
|
||||
SYSTEM: 1,
|
||||
@ -150,53 +151,40 @@ function installImporter(extension) {
|
||||
const ExtensionFinder = new Lang.Class({
|
||||
Name: 'ExtensionFinder',
|
||||
|
||||
_scanExtensionsInDirectory: function(dir, type) {
|
||||
let fileEnum;
|
||||
let file, info;
|
||||
try {
|
||||
fileEnum = dir.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null);
|
||||
} catch(e) {
|
||||
if (e.domain != Gio.io_error_quark() || e.code != Gio.IOErrorEnum.NOT_FOUND)
|
||||
logError(e, 'Could not enumerate extensions directory');
|
||||
_loadExtension: function(extensionDir, info, perUserDir) {
|
||||
let fileType = info.get_file_type();
|
||||
if (fileType != Gio.FileType.DIRECTORY)
|
||||
return;
|
||||
let uuid = info.get_name();
|
||||
let existing = extensions[uuid];
|
||||
if (existing) {
|
||||
log('Extension %s already installed in %s. %s will not be loaded'.format(uuid, existing.path, extensionDir.get_path()));
|
||||
return;
|
||||
}
|
||||
|
||||
while ((info = fileEnum.next_file(null)) != null) {
|
||||
let fileType = info.get_file_type();
|
||||
if (fileType != Gio.FileType.DIRECTORY)
|
||||
continue;
|
||||
let uuid = info.get_name();
|
||||
let extensionDir = dir.get_child(uuid);
|
||||
|
||||
let existing = extensions[uuid];
|
||||
if (existing) {
|
||||
log('Extension %s already installed in %s. %s will not be loaded'.format(uuid, existing.path, extensionDir.get_path()));
|
||||
continue;
|
||||
}
|
||||
|
||||
let extension;
|
||||
try {
|
||||
extension = createExtensionObject(uuid, extensionDir, type);
|
||||
} catch(e) {
|
||||
logError(e, 'Could not load extension %s'.format(uuid));
|
||||
continue;
|
||||
}
|
||||
this.emit('extension-found', extension);
|
||||
let extension;
|
||||
let type = extensionDir.has_prefix(perUserDir) ? ExtensionType.PER_USER
|
||||
: ExtensionType.SYSTEM;
|
||||
try {
|
||||
extension = createExtensionObject(uuid, extensionDir, type);
|
||||
} catch(e) {
|
||||
logError(e, 'Could not load extension %s'.format(uuid));
|
||||
return;
|
||||
}
|
||||
fileEnum.close(null);
|
||||
this.emit('extension-found', extension);
|
||||
},
|
||||
|
||||
_extensionsLoaded: function() {
|
||||
this.emit('extensions-loaded');
|
||||
},
|
||||
|
||||
scanExtensions: function() {
|
||||
let userExtensionsDir = Gio.File.new_for_path(GLib.build_filenamev([global.userdatadir, 'extensions']));
|
||||
this._scanExtensionsInDirectory(userExtensionsDir, ExtensionType.PER_USER);
|
||||
|
||||
let systemDataDirs = GLib.get_system_data_dirs();
|
||||
for (let i = 0; i < systemDataDirs.length; i++) {
|
||||
let dirPath = GLib.build_filenamev([systemDataDirs[i], 'gnome-shell', 'extensions']);
|
||||
let dir = Gio.file_new_for_path(dirPath);
|
||||
if (dir.query_exists(null))
|
||||
this._scanExtensionsInDirectory(dir, ExtensionType.SYSTEM);
|
||||
}
|
||||
let perUserDir = Gio.File.new_for_path(global.userdatadir);
|
||||
FileUtils.collectFromDatadirsAsync('extensions',
|
||||
{ processFile: Lang.bind(this, this._loadExtension),
|
||||
loadedCallback: Lang.bind(this, this._extensionsLoaded),
|
||||
includeUserDir: true,
|
||||
data: perUserDir });
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(ExtensionFinder.prototype);
|
||||
|
@ -2,10 +2,12 @@
|
||||
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Params = imports.misc.params;
|
||||
|
||||
function listDirAsync(file, callback) {
|
||||
let allFiles = [];
|
||||
file.enumerate_children_async(Gio.FILE_ATTRIBUTE_STANDARD_NAME,
|
||||
file.enumerate_children_async('standard::name,standard::type',
|
||||
Gio.FileQueryInfoFlags.NONE,
|
||||
GLib.PRIORITY_LOW, null, function (obj, res) {
|
||||
let enumerator = obj.enumerate_children_finish(res);
|
||||
@ -23,6 +25,63 @@ function listDirAsync(file, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function _collectFromDirectoryAsync(dir, loadState) {
|
||||
function done() {
|
||||
loadState.numLoading--;
|
||||
if (loadState.loadedCallback &&
|
||||
loadState.numLoading == 0)
|
||||
loadState.loadedCallback(loadState.data);
|
||||
}
|
||||
|
||||
dir.query_info_async('standard::type', Gio.FileQueryInfoFlags.NONE,
|
||||
GLib.PRIORITY_DEFAULT, null, function(object, res) {
|
||||
try {
|
||||
object.query_info_finish(res);
|
||||
} catch (e) {
|
||||
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
|
||||
log(e.message);
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
listDirAsync(dir, Lang.bind(this, function(infos) {
|
||||
for (let i = 0; i < infos.length; i++)
|
||||
loadState.processFile(dir.get_child(infos[i].get_name()),
|
||||
infos[i], loadState.data);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
function collectFromDatadirsAsync(subdir, params) {
|
||||
params = Params.parse(params, { includeUserDir: false,
|
||||
processFile: null,
|
||||
loadedCallback: null,
|
||||
data: null });
|
||||
let loadState = { data: params.data,
|
||||
numLoading: 0,
|
||||
loadedCallback: params.loadedCallback,
|
||||
processFile: params.processFile };
|
||||
|
||||
if (params.processFile == null) {
|
||||
if (params.loadedCallback)
|
||||
params.loadedCallback(params.data);
|
||||
return;
|
||||
}
|
||||
|
||||
let dataDirs = GLib.get_system_data_dirs();
|
||||
if (params.includeUserDir)
|
||||
dataDirs.unshift(GLib.get_user_data_dir());
|
||||
loadState.numLoading = dataDirs.length;
|
||||
|
||||
for (let i = 0; i < dataDirs.length; i++) {
|
||||
let path = GLib.build_filenamev([dataDirs[i], 'gnome-shell', subdir]);
|
||||
let dir = Gio.File.new_for_path(path);
|
||||
|
||||
_collectFromDirectoryAsync(dir, loadState);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteGFile(file) {
|
||||
// Work around 'delete' being a keyword in JS.
|
||||
return file['delete'](null);
|
||||
@ -58,7 +117,6 @@ function recursivelyMoveDir(srcDir, destDir) {
|
||||
let type = info.get_file_type();
|
||||
let srcChild = srcDir.get_child(info.get_name());
|
||||
let destChild = destDir.get_child(info.get_name());
|
||||
log([srcChild.get_path(), destChild.get_path()]);
|
||||
if (type == Gio.FileType.REGULAR)
|
||||
srcChild.move(destChild, Gio.FileCopyFlags.NONE, null, null);
|
||||
else if (type == Gio.FileType.DIRECTORY)
|
||||
|
@ -58,6 +58,7 @@ const SessionManagerIface = <interface name="org.gnome.SessionManager">
|
||||
<arg type="u" direction="in" />
|
||||
<arg type="b" direction="out" />
|
||||
</method>
|
||||
<property name="SessionIsActive" type="b" access="read"/>
|
||||
<signal name="InhibitorAdded">
|
||||
<arg type="o" direction="out"/>
|
||||
</signal>
|
||||
|
144
js/misc/hash.js
Normal file
144
js/misc/hash.js
Normal file
@ -0,0 +1,144 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Lang = imports.lang;
|
||||
const System = imports.system;
|
||||
|
||||
const Params = imports.misc.params;
|
||||
|
||||
// This is an implementation of EcmaScript SameValue algorithm,
|
||||
// which returns true if two values are not observably distinguishable.
|
||||
// It was taken from http://wiki.ecmascript.org/doku.php?id=harmony:egal
|
||||
//
|
||||
// In the future, we may want to use the 'is' operator instead.
|
||||
function _sameValue(x, y) {
|
||||
if (x === y) {
|
||||
// 0 === -0, but they are not identical
|
||||
return x !== 0 || 1 / x === 1 / y;
|
||||
}
|
||||
|
||||
// NaN !== NaN, but they are identical.
|
||||
// NaNs are the only non-reflexive value, i.e., if x !== x,
|
||||
// then x is a NaN.
|
||||
// isNaN is broken: it converts its argument to number, so
|
||||
// isNaN("foo") => true
|
||||
return x !== x && y !== y;
|
||||
}
|
||||
|
||||
const _hashers = {
|
||||
object: function(o) { return o ? System.addressOf(o) : 'null'; },
|
||||
function: function(f) { return System.addressOf(f); },
|
||||
string: function(s) { return s; },
|
||||
number: function(n) { return String(n); },
|
||||
undefined: function() { return 'undefined'; },
|
||||
};
|
||||
|
||||
/* Map is meant to be similar in usage to ES6 Map, which is
|
||||
described at http://wiki.ecmascript.org/doku.php?id=harmony:simple_maps_and_sets,
|
||||
without requiring more than ES5 + Gjs extensions.
|
||||
|
||||
Known differences from other implementations:
|
||||
Polyfills around the web usually implement HashMaps for
|
||||
primitive values and reversed WeakMaps for object keys,
|
||||
but we want real maps with real O(1) semantics in all cases,
|
||||
and the easiest way is to have different hashers for different
|
||||
types.
|
||||
|
||||
Known differences from the ES6 specification:
|
||||
- Map is a Lang.Class, not a ES6 class, so inheritance,
|
||||
prototype, sealing, etc. work differently.
|
||||
- items(), keys() and values() don't return iterators,
|
||||
they return actual arrays, so they incur a full copy everytime
|
||||
they're called, and they don't see changes if you mutate
|
||||
the table while iterating
|
||||
(admittedly, the ES6 spec is a bit unclear on this, and
|
||||
the reference code would just blow up)
|
||||
*/
|
||||
const Map = new Lang.Class({
|
||||
Name: 'Map',
|
||||
|
||||
_init: function(iterable) {
|
||||
this._pool = { };
|
||||
this._size = 0;
|
||||
|
||||
if (iterable) {
|
||||
for (let i = 0; i < iterable.length; i++) {
|
||||
let [key, value] = iterable[i];
|
||||
this.set(key, value);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_hashKey: function(key) {
|
||||
let type = typeof(key);
|
||||
return type + ':' + _hashers[type](key);
|
||||
},
|
||||
|
||||
_internalGet: function(key) {
|
||||
let hash = this._hashKey(key);
|
||||
let node = this._pool[hash];
|
||||
|
||||
if (node && _sameValue(node.key, key))
|
||||
return [true, node.value];
|
||||
else
|
||||
return [false, null];
|
||||
},
|
||||
|
||||
get: function(key) {
|
||||
return this._internalGet(key)[1];
|
||||
},
|
||||
|
||||
has: function(key) {
|
||||
return this._internalGet(key)[0];
|
||||
},
|
||||
|
||||
set: function(key, value) {
|
||||
let hash = this._hashKey(key);
|
||||
let node = this._pool[hash];
|
||||
|
||||
if (node) {
|
||||
node.key = key;
|
||||
node.value = value;
|
||||
} else {
|
||||
this._pool[hash] = { key: key, value: value };
|
||||
this._size++;
|
||||
}
|
||||
},
|
||||
|
||||
delete: function(key) {
|
||||
let hash = this._hashKey(key);
|
||||
let node = this._pool[hash];
|
||||
|
||||
if (node && _sameValue(node.key, key)) {
|
||||
delete this._pool[hash];
|
||||
this._size--;
|
||||
return [node.key, node.value];
|
||||
} else {
|
||||
return [null, null];
|
||||
}
|
||||
},
|
||||
|
||||
keys: function() {
|
||||
let pool = this._pool;
|
||||
return Object.getOwnPropertyNames(pool).map(function(hash) {
|
||||
return pool[hash].key;
|
||||
});
|
||||
},
|
||||
|
||||
values: function() {
|
||||
let pool = this._pool;
|
||||
return Object.getOwnPropertyNames(pool).map(function(hash) {
|
||||
return pool[hash].value;
|
||||
});
|
||||
},
|
||||
|
||||
items: function() {
|
||||
let pool = this._pool;
|
||||
return Object.getOwnPropertyNames(pool).map(function(hash) {
|
||||
return [pool[hash].key, pool[hash].value];
|
||||
});
|
||||
},
|
||||
|
||||
size: function() {
|
||||
return this._size;
|
||||
},
|
||||
});
|
@ -5,27 +5,32 @@ const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Shell = imports.gi.Shell;
|
||||
const UPowerGlib = imports.gi.UPowerGlib;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const SystemdLoginManagerIface = <interface name='org.freedesktop.login1.Manager'>
|
||||
<method name='PowerOff'>
|
||||
<arg type='b' direction='in'/>
|
||||
</method>
|
||||
<method name='Reboot'>
|
||||
<arg type='b' direction='in'/>
|
||||
</method>
|
||||
<method name='Suspend'>
|
||||
<arg type='b' direction='in'/>
|
||||
</method>
|
||||
<method name='CanPowerOff'>
|
||||
<arg type='s' direction='out'/>
|
||||
</method>
|
||||
<method name='CanReboot'>
|
||||
<arg type='s' direction='out'/>
|
||||
</method>
|
||||
<method name='CanSuspend'>
|
||||
<arg type='s' direction='out'/>
|
||||
</method>
|
||||
<method name='Inhibit'>
|
||||
<arg type='s' direction='in'/>
|
||||
<arg type='s' direction='in'/>
|
||||
<arg type='s' direction='in'/>
|
||||
<arg type='s' direction='in'/>
|
||||
<arg type='h' direction='out'/>
|
||||
</method>
|
||||
<method name='GetSession'>
|
||||
<arg type='s' direction='in'/>
|
||||
<arg type='o' direction='out'/>
|
||||
</method>
|
||||
<method name='ListSessions'>
|
||||
<arg name='sessions' type='a(susso)' direction='out'/>
|
||||
</method>
|
||||
<signal name='PrepareForSleep'>
|
||||
<arg type='b' direction='out'/>
|
||||
</signal>
|
||||
</interface>;
|
||||
|
||||
const SystemdLoginSessionIface = <interface name='org.freedesktop.login1.Session'>
|
||||
@ -51,12 +56,6 @@ const ConsoleKitManagerIface = <interface name='org.freedesktop.ConsoleKit.Manag
|
||||
</interface>;
|
||||
|
||||
const ConsoleKitSessionIface = <interface name='org.freedesktop.ConsoleKit.Session'>
|
||||
<method name='IsActive'>
|
||||
<arg type='b' direction='out' />
|
||||
</method>
|
||||
<signal name='ActiveChanged'>
|
||||
<arg type='b' direction='out' />
|
||||
</signal>
|
||||
<signal name='Lock' />
|
||||
<signal name='Unlock' />
|
||||
</interface>;
|
||||
@ -65,7 +64,38 @@ const ConsoleKitSession = Gio.DBusProxy.makeProxyWrapper(ConsoleKitSessionIface)
|
||||
const ConsoleKitManager = Gio.DBusProxy.makeProxyWrapper(ConsoleKitManagerIface);
|
||||
|
||||
function haveSystemd() {
|
||||
return GLib.access("/sys/fs/cgroup/systemd", 0) >= 0;
|
||||
return GLib.access("/run/systemd/seats", 0) >= 0;
|
||||
}
|
||||
|
||||
function versionCompare(required, reference) {
|
||||
required = required.split('.');
|
||||
reference = reference.split('.');
|
||||
|
||||
for (let i = 0; i < required.length; i++) {
|
||||
let requiredInt = parseInt(required[i]);
|
||||
let referenceInt = parseInt(reference[i]);
|
||||
if (requiredInt != referenceInt)
|
||||
return requiredInt < referenceInt;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function canLock() {
|
||||
try {
|
||||
let params = GLib.Variant.new('(ss)', ['org.gnome.DisplayManager.Manager', 'Version']);
|
||||
let result = Gio.DBus.system.call_sync('org.gnome.DisplayManager',
|
||||
'/org/gnome/DisplayManager/Manager',
|
||||
'org.freedesktop.DBus.Properties',
|
||||
'Get', params, null,
|
||||
Gio.DBusCallFlags.NONE,
|
||||
-1, null);
|
||||
|
||||
let version = result.deep_unpack()[0].deep_unpack();
|
||||
return versionCompare('3.5.91', version);
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let _loginManager = null;
|
||||
@ -93,42 +123,30 @@ const LoginManagerSystemd = new Lang.Class({
|
||||
this._proxy = new SystemdLoginManager(Gio.DBus.system,
|
||||
'org.freedesktop.login1',
|
||||
'/org/freedesktop/login1');
|
||||
this._proxy.connectSignal('PrepareForSleep',
|
||||
Lang.bind(this, this._prepareForSleep));
|
||||
},
|
||||
|
||||
// Having this function is a bit of a hack since the Systemd and ConsoleKit
|
||||
// session objects have different interfaces - but in both cases there are
|
||||
// Lock/Unlock signals, and that's all we count upon at the moment.
|
||||
getCurrentSessionProxy: function() {
|
||||
if (!this._currentSession) {
|
||||
this._currentSession = new SystemdLoginSession(Gio.DBus.system,
|
||||
'org.freedesktop.login1',
|
||||
'/org/freedesktop/login1/session/' +
|
||||
GLib.getenv('XDG_SESSION_ID'));
|
||||
getCurrentSessionProxy: function(callback) {
|
||||
if (this._currentSession) {
|
||||
callback (this._currentSession);
|
||||
return;
|
||||
}
|
||||
|
||||
return this._currentSession;
|
||||
},
|
||||
|
||||
get sessionActive() {
|
||||
return Shell.session_is_active_for_systemd();
|
||||
},
|
||||
|
||||
canPowerOff: function(asyncCallback) {
|
||||
this._proxy.CanPowerOffRemote(function(result, error) {
|
||||
if (error)
|
||||
asyncCallback(false);
|
||||
else
|
||||
asyncCallback(result[0] != 'no');
|
||||
});
|
||||
},
|
||||
|
||||
canReboot: function(asyncCallback) {
|
||||
this._proxy.CanRebootRemote(function(result, error) {
|
||||
if (error)
|
||||
asyncCallback(false);
|
||||
else
|
||||
asyncCallback(result[0] != 'no');
|
||||
});
|
||||
this._proxy.GetSessionRemote(GLib.getenv('XDG_SESSION_ID'), Lang.bind(this,
|
||||
function(result, error) {
|
||||
if (error) {
|
||||
logError(error, 'Could not get a proxy for the current session');
|
||||
} else {
|
||||
this._currentSession = new SystemdLoginSession(Gio.DBus.system,
|
||||
'org.freedesktop.login1',
|
||||
result[0]);
|
||||
callback(this._currentSession);
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
canSuspend: function(asyncCallback) {
|
||||
@ -140,18 +158,44 @@ const LoginManagerSystemd = new Lang.Class({
|
||||
});
|
||||
},
|
||||
|
||||
powerOff: function() {
|
||||
this._proxy.PowerOffRemote(true);
|
||||
},
|
||||
|
||||
reboot: function() {
|
||||
this._proxy.RebootRemote(true);
|
||||
listSessions: function(asyncCallback) {
|
||||
this._proxy.ListSessionsRemote(function(result, error) {
|
||||
if (error)
|
||||
asyncCallback([]);
|
||||
else
|
||||
asyncCallback(result[0]);
|
||||
});
|
||||
},
|
||||
|
||||
suspend: function() {
|
||||
this._proxy.SuspendRemote(true);
|
||||
},
|
||||
|
||||
inhibit: function(reason, callback) {
|
||||
let inVariant = GLib.Variant.new('(ssss)',
|
||||
['sleep',
|
||||
'GNOME Shell',
|
||||
reason,
|
||||
'delay']);
|
||||
this._proxy.call_with_unix_fd_list('Inhibit', inVariant, 0, -1, null, null,
|
||||
Lang.bind(this, function(proxy, result) {
|
||||
let fd = -1;
|
||||
try {
|
||||
let [outVariant, fdList] = proxy.call_with_unix_fd_list_finish(result);
|
||||
fd = fdList.steal_fds(outVariant.deep_unpack())[0];
|
||||
callback(new Gio.UnixInputStream({ fd: fd }));
|
||||
} catch(e) {
|
||||
logError(e, "Error getting systemd inhibitor");
|
||||
callback(null);
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
_prepareForSleep: function(proxy, sender, [aboutToSuspend]) {
|
||||
this.emit('prepare-for-sleep', aboutToSuspend);
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(LoginManagerSystemd.prototype);
|
||||
|
||||
const LoginManagerConsoleKit = new Lang.Class({
|
||||
Name: 'LoginManagerConsoleKit',
|
||||
@ -160,70 +204,45 @@ const LoginManagerConsoleKit = new Lang.Class({
|
||||
this._proxy = new ConsoleKitManager(Gio.DBus.system,
|
||||
'org.freedesktop.ConsoleKit',
|
||||
'/org/freedesktop/ConsoleKit/Manager');
|
||||
this._upClient = new UPowerGlib.Client();
|
||||
},
|
||||
|
||||
// Having this function is a bit of a hack since the Systemd and ConsoleKit
|
||||
// session objects have different interfaces - but in both cases there are
|
||||
// Lock/Unlock signals, and that's all we count upon at the moment.
|
||||
getCurrentSessionProxy: function() {
|
||||
if (!this._currentSession) {
|
||||
let [currentSessionId] = this._proxy.GetCurrentSessionSync();
|
||||
this._currentSession = new ConsoleKitSession(Gio.DBus.system,
|
||||
'org.freedesktop.ConsoleKit',
|
||||
currentSessionId);
|
||||
getCurrentSessionProxy: function(callback) {
|
||||
if (this._currentSession) {
|
||||
callback (this._currentSession);
|
||||
return;
|
||||
}
|
||||
|
||||
return this._currentSession;
|
||||
},
|
||||
|
||||
get sessionActive() {
|
||||
if (this._sessionActive !== undefined)
|
||||
return this._sessionActive;
|
||||
|
||||
let session = this.getCurrentSessionProxy();
|
||||
session.connectSignal('ActiveChanged', Lang.bind(this, function(object, senderName, [isActive]) {
|
||||
this._sessionActive = isActive;
|
||||
}));
|
||||
[this._sessionActive] = session.IsActiveSync();
|
||||
|
||||
return this._sessionActive;
|
||||
},
|
||||
|
||||
canPowerOff: function(asyncCallback) {
|
||||
this._proxy.CanStopRemote(function(result, error) {
|
||||
if (error)
|
||||
asyncCallback(false);
|
||||
else
|
||||
asyncCallback(result[0]);
|
||||
});
|
||||
},
|
||||
|
||||
canReboot: function(asyncCallback) {
|
||||
this._proxy.CanRestartRemote(function(result, error) {
|
||||
if (error)
|
||||
asyncCallback(false);
|
||||
else
|
||||
asyncCallback(result[0]);
|
||||
});
|
||||
this._proxy.GetCurrentSessionRemote(Lang.bind(this,
|
||||
function(result, error) {
|
||||
if (error) {
|
||||
logError(error, 'Could not get a proxy for the current session');
|
||||
} else {
|
||||
this._currentSession = new ConsoleKitSession(Gio.DBus.system,
|
||||
'org.freedesktop.ConsoleKit',
|
||||
result[0]);
|
||||
callback(this._currentSession);
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
canSuspend: function(asyncCallback) {
|
||||
Mainloop.idle_add(Lang.bind(this, function() {
|
||||
asyncCallback(this._upClient.get_can_suspend());
|
||||
return false;
|
||||
}));
|
||||
asyncCallback(false);
|
||||
},
|
||||
|
||||
powerOff: function() {
|
||||
this._proxy.StopRemote();
|
||||
},
|
||||
|
||||
reboot: function() {
|
||||
this._proxy.RestartRemote();
|
||||
listSessions: function(asyncCallback) {
|
||||
asyncCallback([]);
|
||||
},
|
||||
|
||||
suspend: function() {
|
||||
this._upClient.suspend_sync(null);
|
||||
this.emit('prepare-for-sleep', true);
|
||||
this.emit('prepare-for-sleep', false);
|
||||
},
|
||||
|
||||
inhibit: function(reason, callback) {
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(LoginManagerConsoleKit.prototype);
|
||||
|
@ -2,9 +2,93 @@
|
||||
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const NMGtk = imports.gi.NMGtk;
|
||||
const Signals = imports.signals;
|
||||
|
||||
// _getMobileProvidersDatabase:
|
||||
//
|
||||
// Gets the database of mobile providers, with references between MCCMNC/SID and
|
||||
// operator name
|
||||
//
|
||||
let _mpd;
|
||||
function _getMobileProvidersDatabase() {
|
||||
if (_mpd == null) {
|
||||
try {
|
||||
_mpd = new NMGtk.MobileProvidersDatabase();
|
||||
_mpd.init(null);
|
||||
} catch (e) {
|
||||
log(e.message);
|
||||
_mpd = null;
|
||||
}
|
||||
}
|
||||
|
||||
return _mpd;
|
||||
}
|
||||
|
||||
// _findProviderForMccMnc:
|
||||
// @operator_name: operator name
|
||||
// @operator_code: operator code
|
||||
//
|
||||
// Given an operator name string (which may not be a real operator name) and an
|
||||
// operator code string, tries to find a proper operator name to display.
|
||||
//
|
||||
function _findProviderForMccMnc(operator_name, operator_code) {
|
||||
if (operator_name) {
|
||||
if (operator_name.length != 0 &&
|
||||
(operator_name.length > 6 || operator_name.length < 5)) {
|
||||
// this looks like a valid name, i.e. not an MCCMNC (that some
|
||||
// devices return when not yet connected
|
||||
return operator_name;
|
||||
}
|
||||
|
||||
if (isNaN(parseInt(operator_name))) {
|
||||
// name is definitely not a MCCMNC, so it may be a name
|
||||
// after all; return that
|
||||
return operator_name;
|
||||
}
|
||||
}
|
||||
|
||||
let needle;
|
||||
if ((!operator_name || operator_name.length == 0) && operator_code)
|
||||
needle = operator_code;
|
||||
else if (operator_name && (operator_name.length == 6 || operator_name.length == 5))
|
||||
needle = operator_name;
|
||||
else // nothing to search
|
||||
return null;
|
||||
|
||||
let mpd = _getMobileProvidersDatabase();
|
||||
if (mpd) {
|
||||
let provider = mpd.lookup_3gpp_mcc_mnc(needle);
|
||||
if (provider)
|
||||
return provider.get_name();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// _findProviderForSid:
|
||||
// @sid: System Identifier of the serving CDMA network
|
||||
//
|
||||
// Tries to find the operator name corresponding to the given SID
|
||||
//
|
||||
function _findProviderForSid(sid) {
|
||||
if (sid == 0)
|
||||
return null;
|
||||
|
||||
let mpd = _getMobileProvidersDatabase();
|
||||
if (mpd) {
|
||||
let provider = mpd.lookup_cdma_sid(sid);
|
||||
if (provider)
|
||||
return provider.get_name();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Support for the old ModemManager interface (MM < 0.7)
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
// The following are not the complete interfaces, just the methods we need
|
||||
// (or may need in the future)
|
||||
|
||||
@ -42,76 +126,6 @@ const ModemCdmaInterface = <interface name="org.freedesktop.ModemManager.Modem.C
|
||||
|
||||
const ModemCdmaProxy = Gio.DBusProxy.makeProxyWrapper(ModemCdmaInterface);
|
||||
|
||||
let _providersTable;
|
||||
function _getProvidersTable() {
|
||||
if (_providersTable)
|
||||
return _providersTable;
|
||||
return _providersTable = Shell.mobile_providers_parse(null,null);
|
||||
}
|
||||
|
||||
function findProviderForMCCMNC(table, needle) {
|
||||
let needlemcc = needle.substring(0, 3);
|
||||
let needlemnc = needle.substring(3, needle.length);
|
||||
|
||||
let name2, name3;
|
||||
for (let iter in table) {
|
||||
let country = table[iter];
|
||||
let providers = country.get_providers();
|
||||
|
||||
// Search through each country's providers
|
||||
for (let i = 0; i < providers.length; i++) {
|
||||
let provider = providers[i];
|
||||
|
||||
// Search through MCC/MNC list
|
||||
let list = provider.get_gsm_mcc_mnc();
|
||||
for (let j = 0; j < list.length; j++) {
|
||||
let mccmnc = list[j];
|
||||
|
||||
// Match both 2-digit and 3-digit MNC; prefer a
|
||||
// 3-digit match if found, otherwise a 2-digit one.
|
||||
if (mccmnc.mcc != needlemcc)
|
||||
continue; // MCC was wrong
|
||||
|
||||
if (!name3 && needle.length == 6 && needlemnc == mccmnc.mnc)
|
||||
name3 = provider.name;
|
||||
|
||||
if (!name2 && needlemnc.substring(0, 2) == mccmnc.mnc.substring(0, 2))
|
||||
name2 = provider.name;
|
||||
|
||||
if (name2 && name3)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return name3 || name2 || null;
|
||||
}
|
||||
|
||||
function findProviderForSid(table, sid) {
|
||||
if (sid == 0)
|
||||
return null;
|
||||
|
||||
// Search through each country
|
||||
for (let iter in table) {
|
||||
let country = table[iter];
|
||||
let providers = country.get_providers();
|
||||
|
||||
// Search through each country's providers
|
||||
for (let i = 0; i < providers.length; i++) {
|
||||
let provider = providers[i];
|
||||
let cdma_sid = provider.get_cdma_sid();
|
||||
|
||||
// Search through CDMA SID list
|
||||
for (let j = 0; j < cdma_sid.length; j++) {
|
||||
if (cdma_sid[j] == sid)
|
||||
return provider.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const ModemGsm = new Lang.Class({
|
||||
Name: 'ModemGsm',
|
||||
|
||||
@ -127,7 +141,7 @@ const ModemGsm = new Lang.Class({
|
||||
this.emit('notify::signal-quality');
|
||||
}));
|
||||
this._proxy.connectSignal('RegistrationInfo', Lang.bind(this, function(proxy, sender, [status, code, name]) {
|
||||
this.operator_name = this._findOperatorName(name, code);
|
||||
this.operator_name = _findProviderForMccMnc(name, code);
|
||||
this.emit('notify::operator-name');
|
||||
}));
|
||||
this._proxy.GetRegistrationInfoRemote(Lang.bind(this, function([result], err) {
|
||||
@ -137,7 +151,7 @@ const ModemGsm = new Lang.Class({
|
||||
}
|
||||
|
||||
let [status, code, name] = result;
|
||||
this.operator_name = this._findOperatorName(name, code);
|
||||
this.operator_name = _findProviderForMccMnc(name, code);
|
||||
this.emit('notify::operator-name');
|
||||
}));
|
||||
this._proxy.GetSignalQualityRemote(Lang.bind(this, function(result, err) {
|
||||
@ -150,32 +164,6 @@ const ModemGsm = new Lang.Class({
|
||||
}
|
||||
this.emit('notify::signal-quality');
|
||||
}));
|
||||
},
|
||||
|
||||
_findOperatorName: function(name, opCode) {
|
||||
if (name) {
|
||||
if (name && name.length != 0 && (name.length > 6 || name.length < 5)) {
|
||||
// this looks like a valid name, i.e. not an MCCMNC (that some
|
||||
// devices return when not yet connected
|
||||
return name;
|
||||
}
|
||||
if (isNaN(parseInt(name))) {
|
||||
// name is definitely not a MCCMNC, so it may be a name
|
||||
// after all; return that
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
let needle;
|
||||
if ((name == null || name.length == 0) && opCode)
|
||||
needle = opCode;
|
||||
else if (name.length == 6 || name.length == 5)
|
||||
needle = name;
|
||||
else // nothing to search
|
||||
return null;
|
||||
|
||||
let table = _getProvidersTable();
|
||||
return findProviderForMCCMNC(table, needle);
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(ModemGsm.prototype);
|
||||
@ -215,15 +203,99 @@ const ModemCdma = new Lang.Class({
|
||||
// it will return an error if the device is not connected
|
||||
this.operator_name = null;
|
||||
} else {
|
||||
let [bandClass, band, id] = result;
|
||||
if (name.length > 0) {
|
||||
let table = _getProvidersTable();
|
||||
this.operator_name = findProviderForSid(table, id);
|
||||
} else
|
||||
this.operator_name = null;
|
||||
let [bandClass, band, sid] = result;
|
||||
|
||||
this.operator_name = _findProviderForSid(sid)
|
||||
}
|
||||
this.emit('notify::operator-name');
|
||||
}));
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(ModemCdma.prototype);
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Support for the new ModemManager1 interface (MM >= 0.7)
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const BroadbandModemInterface = <interface name="org.freedesktop.ModemManager1.Modem">
|
||||
<property name="SignalQuality" type="(ub)" access="read" />
|
||||
</interface>;
|
||||
const BroadbandModemProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModemInterface);
|
||||
|
||||
const BroadbandModem3gppInterface = <interface name="org.freedesktop.ModemManager1.Modem.Modem3gpp">
|
||||
<property name="OperatorCode" type="s" access="read" />
|
||||
<property name="OperatorName" type="s" access="read" />
|
||||
</interface>;
|
||||
const BroadbandModem3gppProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModem3gppInterface);
|
||||
|
||||
const BroadbandModemCdmaInterface = <interface name="org.freedesktop.ModemManager1.Modem.ModemCdma">
|
||||
<property name="Sid" type="u" access="read" />
|
||||
</interface>;
|
||||
const BroadbandModemCdmaProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModemCdmaInterface);
|
||||
|
||||
const BroadbandModem = new Lang.Class({
|
||||
Name: 'BroadbandModem',
|
||||
|
||||
_init: function(path, capabilities) {
|
||||
this._proxy = new BroadbandModemProxy(Gio.DBus.system, 'org.freedesktop.ModemManager1', path);
|
||||
this._proxy_3gpp = new BroadbandModem3gppProxy(Gio.DBus.system, 'org.freedesktop.ModemManager1', path);
|
||||
this._proxy_cdma = new BroadbandModemCdmaProxy(Gio.DBus.system, 'org.freedesktop.ModemManager1', path);
|
||||
this._capabilities = capabilities;
|
||||
|
||||
this._proxy.connect('g-properties-changed', Lang.bind(this, function(proxy, properties) {
|
||||
if ('SignalQuality' in properties.deep_unpack())
|
||||
this._reloadSignalQuality();
|
||||
}));
|
||||
this._reloadSignalQuality();
|
||||
|
||||
this._proxy_3gpp.connect('g-properties-changed', Lang.bind(this, function(proxy, properties) {
|
||||
let unpacked = properties.deep_unpack();
|
||||
if ('OperatorName' in unpacked || 'OperatorCode' in unpacked)
|
||||
this._reload3gppOperatorName();
|
||||
}));
|
||||
this._reload3gppOperatorName();
|
||||
|
||||
this._proxy_cdma.connect('g-properties-changed', Lang.bind(this, function(proxy, properties) {
|
||||
let unpacked = properties.deep_unpack();
|
||||
if ('Nid' in unpacked || 'Sid' in unpacked)
|
||||
this._reloadCdmaOperatorName();
|
||||
}));
|
||||
this._reloadCdmaOperatorName();
|
||||
},
|
||||
|
||||
_reloadSignalQuality: function() {
|
||||
let [quality, recent] = this._proxy.SignalQuality;
|
||||
this.signal_quality = quality;
|
||||
this.emit('notify::signal-quality');
|
||||
},
|
||||
|
||||
_reloadOperatorName: function() {
|
||||
let new_name = "";
|
||||
if (this.operator_name_3gpp && this.operator_name_3gpp.length > 0)
|
||||
new_name += this.operator_name_3gpp;
|
||||
|
||||
if (this.operator_name_cdma && this.operator_name_cdma.length > 0) {
|
||||
if (new_name != "")
|
||||
new_name += ", ";
|
||||
new_name += this.operator_name_cdma;
|
||||
}
|
||||
|
||||
this.operator_name = new_name;
|
||||
this.emit('notify::operator-name');
|
||||
},
|
||||
|
||||
_reload3gppOperatorName: function() {
|
||||
let name = this._proxy_3gpp.OperatorName;
|
||||
let code = this._proxy_3gpp.OperatorCode;
|
||||
this.operator_name_3gpp = _findProviderForMccMnc(name, code);
|
||||
this._reloadOperatorName();
|
||||
},
|
||||
|
||||
_reloadCdmaOperatorName: function() {
|
||||
let sid = this._proxy_cdma.Sid;
|
||||
this.operator_name_cdma = _findProviderForSid(sid);
|
||||
this._reloadOperatorName();
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(BroadbandModem.prototype);
|
||||
|
257
js/misc/objectManager.js
Normal file
257
js/misc/objectManager.js
Normal file
@ -0,0 +1,257 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Params = imports.misc.params;
|
||||
const Signals = imports.signals;
|
||||
|
||||
// Specified in the D-Bus specification here:
|
||||
// http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager
|
||||
const ObjectManagerIface = <interface name="org.freedesktop.DBus.ObjectManager">
|
||||
<method name="GetManagedObjects">
|
||||
<arg name="objects" type="a{oa{sa{sv}}}" direction="out"/>
|
||||
</method>
|
||||
<signal name="InterfacesAdded">
|
||||
<arg name="objectPath" type="o"/>
|
||||
<arg name="interfaces" type="a{sa{sv}}" />
|
||||
</signal>
|
||||
<signal name="InterfacesRemoved">
|
||||
<arg name="objectPath" type="o"/>
|
||||
<arg name="interfaces" type="as" />
|
||||
</signal>
|
||||
</interface>;
|
||||
|
||||
const ObjectManagerInfo = Gio.DBusInterfaceInfo.new_for_xml(ObjectManagerIface);
|
||||
|
||||
const ObjectManager = new Lang.Class({
|
||||
Name: 'ObjectManager',
|
||||
_init: function(params) {
|
||||
params = Params.parse(params, { connection: null,
|
||||
name: null,
|
||||
objectPath: null,
|
||||
knownInterfaces: null,
|
||||
cancellable: null,
|
||||
onLoaded: null });
|
||||
|
||||
this._connection = params.connection;
|
||||
this._serviceName = params.name;
|
||||
this._managerPath = params.objectPath;
|
||||
this._cancellable = params.cancellable;
|
||||
|
||||
this._managerProxy = new Gio.DBusProxy({ g_connection: this._connection,
|
||||
g_interface_name: ObjectManagerInfo.name,
|
||||
g_interface_info: ObjectManagerInfo,
|
||||
g_name: this._serviceName,
|
||||
g_object_path: this._managerPath,
|
||||
g_flags: Gio.DBusProxyFlags.NONE });
|
||||
|
||||
this._interfaceInfos = {};
|
||||
this._objects = {};
|
||||
this._interfaces = {};
|
||||
this._onLoaded = params.onLoaded;
|
||||
|
||||
if (params.knownInterfaces)
|
||||
this._registerInterfaces(params.knownInterfaces);
|
||||
|
||||
// Start out inhibiting load until at least the proxy
|
||||
// manager is loaded and the remote objects are fetched
|
||||
this._numLoadInhibitors = 1;
|
||||
this._managerProxy.init_async(GLib.PRIORITY_DEFAULT,
|
||||
this._cancellable,
|
||||
Lang.bind(this, this._onManagerProxyLoaded));
|
||||
},
|
||||
|
||||
_tryToCompleteLoad: function() {
|
||||
this._numLoadInhibitors--;
|
||||
if (this._numLoadInhibitors == 0) {
|
||||
if (this._onLoaded)
|
||||
this._onLoaded();
|
||||
}
|
||||
},
|
||||
|
||||
_addInterface: function(objectPath, interfaceName, onFinished) {
|
||||
let info = this._interfaceInfos[interfaceName];
|
||||
|
||||
if (!info) {
|
||||
if (onFinished)
|
||||
onFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
let proxy = new Gio.DBusProxy({ g_connection: this._connection,
|
||||
g_name: this._serviceName,
|
||||
g_object_path: objectPath,
|
||||
g_interface_name: interfaceName,
|
||||
g_interface_info: info,
|
||||
g_flags: Gio.DBusProxyFlags.NONE });
|
||||
|
||||
proxy.init_async(GLib.PRIORITY_DEFAULT,
|
||||
this._cancellable,
|
||||
Lang.bind(this, function(initable, result) {
|
||||
let error = null;
|
||||
try {
|
||||
initable.init_finish(result);
|
||||
} catch(e) {
|
||||
logError(e, 'could not initialize proxy for interface ' + interfaceName);
|
||||
|
||||
if (onFinished)
|
||||
onFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
let isNewObject;
|
||||
if (!this._objects[objectPath]) {
|
||||
this._objects[objectPath] = {};
|
||||
isNewObject = true;
|
||||
} else {
|
||||
isNewObject = false;
|
||||
}
|
||||
|
||||
this._objects[objectPath][interfaceName] = proxy;
|
||||
|
||||
if (!this._interfaces[interfaceName])
|
||||
this._interfaces[interfaceName] = [];
|
||||
|
||||
this._interfaces[interfaceName].push(proxy);
|
||||
|
||||
if (isNewObject)
|
||||
this.emit('object-added', objectPath);
|
||||
|
||||
this.emit('interface-added', interfaceName, proxy);
|
||||
|
||||
if (onFinished)
|
||||
onFinished();
|
||||
}));
|
||||
},
|
||||
|
||||
_removeInterface: function(objectPath, interfaceName) {
|
||||
if (!this._objects[objectPath])
|
||||
return;
|
||||
|
||||
let proxy = this._objects[objectPath][interfaceName];
|
||||
|
||||
if (this._interfaces[interfaceName]) {
|
||||
let index = this._interfaces[interfaceName].indexOf(proxy);
|
||||
|
||||
if (index >= 0)
|
||||
this._interfaces[interfaceName].splice(index, 1);
|
||||
|
||||
if (this._interfaces[interfaceName].length == 0)
|
||||
delete this._interfaces[interfaceName];
|
||||
}
|
||||
|
||||
this.emit('interface-removed', interfaceName, proxy);
|
||||
|
||||
this._objects[objectPath][interfaceName] = null;
|
||||
|
||||
if (Object.keys(this._objects[objectPath]).length == 0) {
|
||||
delete this._objects[objectPath];
|
||||
this.emit('object-removed', objectPath);
|
||||
}
|
||||
},
|
||||
|
||||
_onManagerProxyLoaded: function(initable, result) {
|
||||
let error = null;
|
||||
try {
|
||||
initable.init_finish(result);
|
||||
} catch(e) {
|
||||
logError(e, 'could not initialize object manager for object ' + params.name);
|
||||
|
||||
this._tryToCompleteLoad();
|
||||
return;
|
||||
}
|
||||
|
||||
this._managerProxy.connectSignal('InterfacesAdded',
|
||||
Lang.bind(this, function(objectManager, sender, [objectPath, interfaces]) {
|
||||
let interfaceNames = Object.keys(interfaces);
|
||||
for (let i = 0; i < interfaceNames.length; i++)
|
||||
this._addInterface(objectPath, interfaceNames[i]);
|
||||
}));
|
||||
this._managerProxy.connectSignal('InterfacesRemoved',
|
||||
Lang.bind(this, function(objectManager, sender, [objectPath, interfaceNames]) {
|
||||
for (let i = 0; i < interfaceNames.length; i++)
|
||||
this._removeInterface(objectPath, interfaceNames[i]);
|
||||
}));
|
||||
|
||||
if (Object.keys(this._interfaceInfos).length == 0) {
|
||||
this._tryToCompleteLoad();
|
||||
return;
|
||||
}
|
||||
|
||||
this._managerProxy.GetManagedObjectsRemote(Lang.bind(this, function(result, error) {
|
||||
if (!result) {
|
||||
if (error) {
|
||||
logError(error, 'could not get remote objects for service ' + this._serviceName + ' path ' + this._managerPath);
|
||||
}
|
||||
|
||||
this._tryToCompleteLoad();
|
||||
return;
|
||||
}
|
||||
|
||||
let [objects] = result;
|
||||
|
||||
let objectPaths = Object.keys(objects);
|
||||
for (let i = 0; i < objectPaths.length; i++) {
|
||||
let objectPath = objectPaths[i];
|
||||
let object = objects[objectPath];
|
||||
|
||||
let interfaceNames = Object.getOwnPropertyNames(object);
|
||||
for (let j = 0; j < interfaceNames.length; j++) {
|
||||
let interfaceName = interfaceNames[j];
|
||||
|
||||
// Prevent load from completing until the interface is loaded
|
||||
this._numLoadInhibitors++;
|
||||
this._addInterface(objectPath,
|
||||
interfaceName,
|
||||
Lang.bind(this, this._tryToCompleteLoad));
|
||||
}
|
||||
}
|
||||
this._tryToCompleteLoad();
|
||||
}));
|
||||
},
|
||||
|
||||
_registerInterfaces: function(interfaces) {
|
||||
for (let i = 0; i < interfaces.length; i++) {
|
||||
let info = Gio.DBusInterfaceInfo.new_for_xml(interfaces[i]);
|
||||
this._interfaceInfos[info.name] = info;
|
||||
}
|
||||
},
|
||||
|
||||
getProxy: function(objectPath, interfaceName) {
|
||||
let object = this._objects[objectPath];
|
||||
|
||||
if (!object)
|
||||
return null;
|
||||
|
||||
return object[interfaceName];
|
||||
},
|
||||
|
||||
getProxiesForInterface: function(interfaceName) {
|
||||
let proxyList = this._interfaces[interfaceName];
|
||||
|
||||
if (!proxyList)
|
||||
return [];
|
||||
|
||||
return proxyList;
|
||||
},
|
||||
|
||||
getAllProxies: function() {
|
||||
let proxies = [];
|
||||
|
||||
let objectPaths = Object.keys(this._objects);
|
||||
for (let i = 0; i < objectPaths.length; i++) {
|
||||
let object = this._objects[objectPaths];
|
||||
|
||||
let interfaceNames = Object.keys(object);
|
||||
for (let j = 0; i < interfaceNames.length; i++) {
|
||||
let interfaceName = interfaceNames[i];
|
||||
if (object[interfaceName])
|
||||
proxies.push(object(interfaceName));
|
||||
}
|
||||
}
|
||||
|
||||
return proxies;
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(ObjectManager.prototype);
|
117
js/misc/smartcardManager.js
Normal file
117
js/misc/smartcardManager.js
Normal file
@ -0,0 +1,117 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const ObjectManager = imports.misc.objectManager;
|
||||
|
||||
const SmartcardTokenIface = <interface name="org.gnome.SettingsDaemon.Smartcard.Token">
|
||||
<property name="Name" type="s" access="read"/>
|
||||
<property name="Driver" type="o" access="read"/>
|
||||
<property name="IsInserted" type="b" access="read"/>
|
||||
<property name="UsedToLogin" type="b" access="read"/>
|
||||
</interface>;
|
||||
|
||||
let _smartcardManager = null;
|
||||
|
||||
function getSmartcardManager() {
|
||||
if (_smartcardManager == null)
|
||||
_smartcardManager = new SmartcardManager();
|
||||
|
||||
return _smartcardManager;
|
||||
}
|
||||
|
||||
const SmartcardManager = new Lang.Class({
|
||||
Name: 'SmartcardManager',
|
||||
_init: function() {
|
||||
this._objectManager = new ObjectManager.ObjectManager({ connection: Gio.DBus.session,
|
||||
name: "org.gnome.SettingsDaemon.Smartcard",
|
||||
objectPath: '/org/gnome/SettingsDaemon/Smartcard',
|
||||
knownInterfaces: [ SmartcardTokenIface ],
|
||||
onLoaded: Lang.bind(this, this._onLoaded) });
|
||||
this._insertedTokens = {};
|
||||
this._loginToken = null;
|
||||
},
|
||||
|
||||
_onLoaded: function() {
|
||||
let tokens = this._objectManager.getProxiesForInterface('org.gnome.SettingsDaemon.Smartcard.Token');
|
||||
|
||||
for (let i = 0; i < tokens.length; i++)
|
||||
this._addToken(tokens[i]);
|
||||
|
||||
this._objectManager.connect('interface-added', Lang.bind(this, function(objectManager, interfaceName, proxy) {
|
||||
if (interfaceName == 'org.gnome.SettingsDaemon.Smartcard.Token')
|
||||
this._addToken(proxy);
|
||||
}));
|
||||
|
||||
this._objectManager.connect('interface-removed', Lang.bind(this, function(objectManager, interfaceName, proxy) {
|
||||
if (interfaceName == 'org.gnome.SettingsDaemon.Smartcard.Token')
|
||||
this._removeToken(proxy);
|
||||
}));
|
||||
},
|
||||
|
||||
_updateToken: function(token) {
|
||||
let objectPath = token.get_object_path();
|
||||
|
||||
delete this._insertedTokens[objectPath];
|
||||
|
||||
if (token.IsInserted)
|
||||
this._insertedTokens[objectPath] = token;
|
||||
|
||||
if (token.UsedToLogin)
|
||||
this._loginToken = token;
|
||||
},
|
||||
|
||||
_addToken: function(token) {
|
||||
this._updateToken(token);
|
||||
|
||||
token.connect('g-properties-changed',
|
||||
Lang.bind(this, function(proxy, properties) {
|
||||
if ('IsInserted' in properties.deep_unpack()) {
|
||||
this._updateToken(token);
|
||||
|
||||
if (token.IsInserted) {
|
||||
this.emit('smartcard-inserted', token);
|
||||
} else {
|
||||
this.emit('smartcard-removed', token);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Emit a smartcard-inserted at startup if it's already plugged in
|
||||
if (token.IsInserted)
|
||||
this.emit('smartcard-inserted', token);
|
||||
},
|
||||
|
||||
_removeToken: function(token) {
|
||||
let objectPath = token.get_object_path();
|
||||
|
||||
if (this._insertedTokens[objectPath] == token) {
|
||||
delete this._insertedTokens[objectPath];
|
||||
this.emit('smartcard-removed', token);
|
||||
}
|
||||
|
||||
if (this._loginToken == token)
|
||||
this._loginToken = null;
|
||||
|
||||
token.disconnectAll();
|
||||
},
|
||||
|
||||
hasInsertedTokens: function() {
|
||||
return Object.keys(this._insertedTokens).length > 0;
|
||||
},
|
||||
|
||||
hasInsertedLoginToken: function() {
|
||||
if (!this._loginToken)
|
||||
return false;
|
||||
|
||||
if (!this._loginToken.IsInserted)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
});
|
||||
Signals.addSignalMethods(SmartcardManager.prototype);
|
122
js/misc/util.js
122
js/misc/util.js
@ -1,8 +1,14 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
const SCROLL_TIME = 0.1;
|
||||
|
||||
// http://daringfireball.net/2010/07/improved_regex_for_matching_urls
|
||||
const _balancedParens = '\\((?:[^\\s()<>]+|(?:\\(?:[^\\s()<>]+\\)))*\\)';
|
||||
@ -13,7 +19,7 @@ const _urlRegexp = new RegExp(
|
||||
'(^|' + _leadingJunk + ')' +
|
||||
'(' +
|
||||
'(?:' +
|
||||
'[a-z][\\w-]+://' + // scheme://
|
||||
'(?:http|https|ftp)://' + // scheme://
|
||||
'|' +
|
||||
'www\\d{0,3}[.]' + // www.
|
||||
'|' +
|
||||
@ -134,31 +140,6 @@ function _handleSpawnError(command, err) {
|
||||
Main.notifyError(title, err.message);
|
||||
}
|
||||
|
||||
// killall:
|
||||
// @processName: a process name
|
||||
//
|
||||
// Kills @processName. If no process with the given name is found,
|
||||
// this will fail silently.
|
||||
function killall(processName) {
|
||||
try {
|
||||
// pkill is more portable than killall, but on Linux at least
|
||||
// it won't match if you pass more than 15 characters of the
|
||||
// process name... However, if you use the '-f' flag to match
|
||||
// the entire command line, it will work, but we have to be
|
||||
// careful in that case that we can match
|
||||
// '/usr/bin/processName' but not 'gedit processName.c' or
|
||||
// whatever...
|
||||
|
||||
let argv = ['pkill', '-f', '^([^ ]*/)?' + processName + '($| )'];
|
||||
GLib.spawn_sync(null, argv, null, GLib.SpawnFlags.SEARCH_PATH, null);
|
||||
// It might be useful to return success/failure, but we'd need
|
||||
// a wrapper around WIFEXITED and WEXITSTATUS. Since none of
|
||||
// the current callers care, we don't bother.
|
||||
} catch (e) {
|
||||
logError(e, 'Failed to kill ' + processName);
|
||||
}
|
||||
}
|
||||
|
||||
// lowerBound:
|
||||
// @array: an array or array-like object, already sorted
|
||||
// according to @cmp
|
||||
@ -208,3 +189,92 @@ function insertSorted(array, val, cmp) {
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
const CloseButton = new Lang.Class({
|
||||
Name: 'CloseButton',
|
||||
Extends: St.Button,
|
||||
|
||||
_init: function(boxpointer) {
|
||||
this.parent({ style_class: 'notification-close'});
|
||||
|
||||
// This is a bit tricky. St.Bin has its own x-align/y-align properties
|
||||
// that compete with Clutter's properties. This should be fixed for
|
||||
// Clutter 2.0. Since St.Bin doesn't define its own setters, the
|
||||
// setters are a workaround to get Clutter's version.
|
||||
this.set_x_align(Clutter.ActorAlign.END);
|
||||
this.set_y_align(Clutter.ActorAlign.START);
|
||||
|
||||
// XXX Clutter 2.0 workaround: ClutterBinLayout needs expand
|
||||
// to respect the alignments.
|
||||
this.set_x_expand(true);
|
||||
this.set_y_expand(true);
|
||||
|
||||
this._boxPointer = boxpointer;
|
||||
if (boxpointer)
|
||||
this._boxPointer.connect('arrow-side-changed', Lang.bind(this, this._sync));
|
||||
},
|
||||
|
||||
_computeBoxPointerOffset: function() {
|
||||
if (!this._boxPointer || !this._boxPointer.actor.get_stage())
|
||||
return 0;
|
||||
|
||||
let side = this._boxPointer.arrowSide;
|
||||
if (side == St.Side.TOP)
|
||||
return this._boxPointer.getArrowHeight();
|
||||
else
|
||||
return 0;
|
||||
},
|
||||
|
||||
_sync: function() {
|
||||
let themeNode = this.get_theme_node();
|
||||
|
||||
let offY = this._computeBoxPointerOffset();
|
||||
this.translation_x = themeNode.get_length('-shell-close-overlap-x')
|
||||
this.translation_y = themeNode.get_length('-shell-close-overlap-y') + offY;
|
||||
},
|
||||
|
||||
vfunc_style_changed: function() {
|
||||
this._sync();
|
||||
this.parent();
|
||||
},
|
||||
});
|
||||
|
||||
function makeCloseButton(boxpointer) {
|
||||
return new CloseButton(boxpointer);
|
||||
}
|
||||
|
||||
function ensureActorVisibleInScrollView(scrollView, actor) {
|
||||
let adjustment = scrollView.vscroll.adjustment;
|
||||
let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
|
||||
|
||||
let offset = 0;
|
||||
let vfade = scrollView.get_effect("fade");
|
||||
if (vfade)
|
||||
offset = vfade.vfade_offset;
|
||||
|
||||
let box = actor.get_allocation_box();
|
||||
let y1 = box.y1, y2 = box.y2;
|
||||
|
||||
let parent = actor.get_parent();
|
||||
while (parent != scrollView) {
|
||||
if (!parent)
|
||||
throw new Error("actor not in scroll view");
|
||||
|
||||
let box = parent.get_allocation_box();
|
||||
y1 += box.y1;
|
||||
y2 += box.y1;
|
||||
parent = parent.get_parent();
|
||||
}
|
||||
|
||||
if (y1 < value + offset)
|
||||
value = Math.max(0, y1 - offset);
|
||||
else if (y2 > value + pageSize - offset)
|
||||
value = Math.min(upper, y2 + offset - pageSize);
|
||||
else
|
||||
return;
|
||||
|
||||
Tweener.addTween(adjustment,
|
||||
{ value: value,
|
||||
time: SCROLL_TIME,
|
||||
transition: 'easeOutQuad' });
|
||||
}
|
||||
|
951
js/ui/altTab.js
951
js/ui/altTab.js
File diff suppressed because it is too large
Load Diff
84
js/ui/animation.js
Normal file
84
js/ui/animation.js
Normal file
@ -0,0 +1,84 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const St = imports.gi.St;
|
||||
const Signals = imports.signals;
|
||||
const Atk = imports.gi.Atk;
|
||||
|
||||
const ANIMATED_ICON_UPDATE_TIMEOUT = 100;
|
||||
|
||||
const Animation = new Lang.Class({
|
||||
Name: 'Animation',
|
||||
|
||||
_init: function(filename, width, height, speed) {
|
||||
this.actor = new St.Bin();
|
||||
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
||||
this._speed = speed;
|
||||
|
||||
this._isLoaded = false;
|
||||
this._isPlaying = false;
|
||||
this._timeoutId = 0;
|
||||
this._frame = 0;
|
||||
this._animations = St.TextureCache.get_default().load_sliced_image (filename, width, height,
|
||||
Lang.bind(this, this._animationsLoaded));
|
||||
this.actor.set_child(this._animations);
|
||||
},
|
||||
|
||||
play: function() {
|
||||
if (this._isLoaded && this._timeoutId == 0) {
|
||||
if (this._frame == 0)
|
||||
this._showFrame(0);
|
||||
|
||||
this._timeoutId = Mainloop.timeout_add(this._speed, Lang.bind(this, this._update));
|
||||
}
|
||||
|
||||
this._isPlaying = true;
|
||||
},
|
||||
|
||||
stop: function() {
|
||||
if (this._timeoutId > 0) {
|
||||
Mainloop.source_remove(this._timeoutId);
|
||||
this._timeoutId = 0;
|
||||
}
|
||||
|
||||
this._isPlaying = false;
|
||||
},
|
||||
|
||||
_showFrame: function(frame) {
|
||||
let oldFrameActor = this._animations.get_child_at_index(this._frame);
|
||||
if (oldFrameActor)
|
||||
oldFrameActor.hide();
|
||||
|
||||
this._frame = (frame % this._animations.get_n_children());
|
||||
|
||||
let newFrameActor = this._animations.get_child_at_index(this._frame);
|
||||
if (newFrameActor)
|
||||
newFrameActor.show();
|
||||
},
|
||||
|
||||
_update: function() {
|
||||
this._showFrame(this._frame + 1);
|
||||
return true;
|
||||
},
|
||||
|
||||
_animationsLoaded: function() {
|
||||
this._isLoaded = true;
|
||||
|
||||
if (this._isPlaying)
|
||||
this.play();
|
||||
},
|
||||
|
||||
_onDestroy: function() {
|
||||
this.stop();
|
||||
}
|
||||
});
|
||||
|
||||
const AnimatedIcon = new Lang.Class({
|
||||
Name: 'AnimatedIcon',
|
||||
Extends: Animation,
|
||||
|
||||
_init: function(filename, size) {
|
||||
this.parent(filename, size, size, ANIMATED_ICON_UPDATE_TIMEOUT);
|
||||
}
|
||||
});
|
1418
js/ui/appDisplay.js
1418
js/ui/appDisplay.js
File diff suppressed because it is too large
Load Diff
@ -14,15 +14,15 @@ const AppFavorites = new Lang.Class({
|
||||
_init: function() {
|
||||
this._favorites = {};
|
||||
global.settings.connect('changed::' + this.FAVORITE_APPS_KEY, Lang.bind(this, this._onFavsChanged));
|
||||
this._reload();
|
||||
this.reload();
|
||||
},
|
||||
|
||||
_onFavsChanged: function() {
|
||||
this._reload();
|
||||
this.reload();
|
||||
this.emit('changed');
|
||||
},
|
||||
|
||||
_reload: function() {
|
||||
reload: function() {
|
||||
let ids = global.settings.get_strv(this.FAVORITE_APPS_KEY);
|
||||
let appSys = Shell.AppSystem.get_default();
|
||||
let apps = ids.map(function (id) {
|
||||
|
815
js/ui/background.js
Normal file
815
js/ui/background.js
Normal file
@ -0,0 +1,815 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GDesktopEnums = imports.gi.GDesktopEnums;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GnomeDesktop = imports.gi.GnomeDesktop;
|
||||
const Lang = imports.lang;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const Params = imports.misc.params;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
const BACKGROUND_SCHEMA = 'org.gnome.desktop.background';
|
||||
const PRIMARY_COLOR_KEY = 'primary-color';
|
||||
const SECONDARY_COLOR_KEY = 'secondary-color';
|
||||
const COLOR_SHADING_TYPE_KEY = 'color-shading-type';
|
||||
const BACKGROUND_STYLE_KEY = 'picture-options';
|
||||
const PICTURE_OPACITY_KEY = 'picture-opacity';
|
||||
const PICTURE_URI_KEY = 'picture-uri';
|
||||
|
||||
const FADE_ANIMATION_TIME = 1.0;
|
||||
|
||||
// These parameters affect how often we redraw.
|
||||
// The first is how different (percent crossfaded) the slide show
|
||||
// has to look before redrawing and the second is the minimum
|
||||
// frequency (in seconds) we're willing to wake up
|
||||
const ANIMATION_OPACITY_STEP_INCREMENT = 4.0;
|
||||
const ANIMATION_MIN_WAKEUP_INTERVAL = 1.0;
|
||||
|
||||
let _backgroundCache = null;
|
||||
|
||||
const BackgroundCache = new Lang.Class({
|
||||
Name: 'BackgroundCache',
|
||||
|
||||
_init: function() {
|
||||
this._patterns = [];
|
||||
this._images = [];
|
||||
this._pendingFileLoads = [];
|
||||
this._fileMonitors = {};
|
||||
},
|
||||
|
||||
getPatternContent: function(params) {
|
||||
params = Params.parse(params, { monitorIndex: 0,
|
||||
color: null,
|
||||
secondColor: null,
|
||||
shadingType: null,
|
||||
effects: Meta.BackgroundEffects.NONE });
|
||||
|
||||
let content = null;
|
||||
let candidateContent = null;
|
||||
for (let i = 0; i < this._patterns.length; i++) {
|
||||
if (!this._patterns[i])
|
||||
continue;
|
||||
|
||||
if (this._patterns[i].get_shading() != params.shadingType)
|
||||
continue;
|
||||
|
||||
if (!params.color.equal(this._patterns[i].get_color()))
|
||||
continue;
|
||||
|
||||
if (params.shadingType != GDesktopEnums.BackgroundShading.SOLID &&
|
||||
!params.secondColor.equal(this._patterns[i].get_second_color()))
|
||||
continue;
|
||||
|
||||
candidateContent = this._patterns[i];
|
||||
|
||||
if (params.effects != this._patterns[i].effects)
|
||||
continue;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (candidateContent) {
|
||||
content = candidateContent.copy(params.monitorIndex, params.effects);
|
||||
} else {
|
||||
content = new Meta.Background({ meta_screen: global.screen,
|
||||
monitor: params.monitorIndex,
|
||||
effects: params.effects });
|
||||
|
||||
if (params.shadingType == GDesktopEnums.BackgroundShading.SOLID) {
|
||||
content.load_color(params.color);
|
||||
} else {
|
||||
content.load_gradient(params.shadingType, params.color, params.secondColor);
|
||||
}
|
||||
}
|
||||
|
||||
this._patterns.push(content);
|
||||
|
||||
return content;
|
||||
},
|
||||
|
||||
_monitorFile: function(filename) {
|
||||
if (this._fileMonitors[filename])
|
||||
return;
|
||||
|
||||
let file = Gio.File.new_for_path(filename);
|
||||
let monitor = file.monitor(Gio.FileMonitorFlags.NONE, null);
|
||||
|
||||
let signalId = monitor.connect('changed',
|
||||
Lang.bind(this, function() {
|
||||
for (let i = 0; i < this._images.length; i++) {
|
||||
if (this._images[i].get_filename() == filename)
|
||||
this._images.splice(i, 1);
|
||||
}
|
||||
|
||||
monitor.disconnect(signalId);
|
||||
|
||||
this.emit('file-changed', filename);
|
||||
}));
|
||||
|
||||
this._fileMonitors[filename] = monitor;
|
||||
},
|
||||
|
||||
_removeContent: function(contentList, content) {
|
||||
let index = contentList.indexOf(content);
|
||||
|
||||
if (index >= 0)
|
||||
contentList.splice(index, 1);
|
||||
},
|
||||
|
||||
removePatternContent: function(content) {
|
||||
this._removeContent(this._patterns, content);
|
||||
},
|
||||
|
||||
removeImageContent: function(content) {
|
||||
let filename = content.get_filename();
|
||||
|
||||
if (filename && this._fileMonitors[filename])
|
||||
delete this._fileMonitors[filename];
|
||||
|
||||
this._removeContent(this._images, content);
|
||||
},
|
||||
|
||||
_loadImageContent: function(params) {
|
||||
params = Params.parse(params, { monitorIndex: 0,
|
||||
style: null,
|
||||
filename: null,
|
||||
effects: Meta.BackgroundEffects.NONE,
|
||||
cancellable: null,
|
||||
onFinished: null });
|
||||
|
||||
for (let i = 0; i < this._pendingFileLoads.length; i++) {
|
||||
if (this._pendingFileLoads[i].filename == params.filename &&
|
||||
this._pendingFileLoads[i].style == params.style) {
|
||||
this._pendingFileLoads[i].callers.push({ shouldCopy: true,
|
||||
monitorIndex: params.monitorIndex,
|
||||
effects: params.effects,
|
||||
onFinished: params.onFinished });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this._pendingFileLoads.push({ filename: params.filename,
|
||||
style: params.style,
|
||||
callers: [{ shouldCopy: false,
|
||||
monitorIndex: params.monitorIndex,
|
||||
effects: params.effects,
|
||||
onFinished: params.onFinished }] });
|
||||
|
||||
let content = new Meta.Background({ meta_screen: global.screen,
|
||||
monitor: params.monitorIndex,
|
||||
effects: params.effects });
|
||||
|
||||
content.load_file_async(params.filename,
|
||||
params.style,
|
||||
params.cancellable,
|
||||
Lang.bind(this,
|
||||
function(object, result) {
|
||||
try {
|
||||
content.load_file_finish(result);
|
||||
|
||||
this._monitorFile(params.filename);
|
||||
this._images.push(content);
|
||||
} catch(e) {
|
||||
content = null;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this._pendingFileLoads.length; i++) {
|
||||
let pendingLoad = this._pendingFileLoads[i];
|
||||
if (pendingLoad.filename != params.filename ||
|
||||
pendingLoad.style != params.style)
|
||||
continue;
|
||||
|
||||
for (let j = 0; j < pendingLoad.callers.length; j++) {
|
||||
if (pendingLoad.callers[j].onFinished) {
|
||||
if (content && pendingLoad.callers[j].shouldCopy) {
|
||||
content = object.copy(pendingLoad.callers[j].monitorIndex,
|
||||
pendingLoad.callers[j].effects);
|
||||
|
||||
}
|
||||
|
||||
pendingLoad.callers[j].onFinished(content);
|
||||
}
|
||||
}
|
||||
|
||||
this._pendingFileLoads.splice(i, 1);
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
getImageContent: function(params) {
|
||||
params = Params.parse(params, { monitorIndex: 0,
|
||||
style: null,
|
||||
filename: null,
|
||||
effects: Meta.BackgroundEffects.NONE,
|
||||
cancellable: null,
|
||||
onFinished: null });
|
||||
|
||||
let content = null;
|
||||
let candidateContent = null;
|
||||
for (let i = 0; i < this._images.length; i++) {
|
||||
if (!this._images[i])
|
||||
continue;
|
||||
|
||||
if (this._images[i].get_style() != params.style)
|
||||
continue;
|
||||
|
||||
if (this._images[i].get_filename() != params.filename)
|
||||
continue;
|
||||
|
||||
if (params.style == GDesktopEnums.BackgroundStyle.SPANNED &&
|
||||
this._images[i].monitor_index != this._monitorIndex)
|
||||
continue;
|
||||
|
||||
candidateContent = this._images[i];
|
||||
|
||||
if (params.effects != this._images[i].effects)
|
||||
continue;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (candidateContent) {
|
||||
content = candidateContent.copy(params.monitorIndex, params.effects);
|
||||
|
||||
if (params.cancellable && params.cancellable.is_cancelled())
|
||||
content = null;
|
||||
else
|
||||
this._images.push(content);
|
||||
|
||||
if (params.onFinished)
|
||||
params.onFinished(content);
|
||||
} else {
|
||||
this._loadImageContent({ filename: params.filename,
|
||||
style: params.style,
|
||||
effects: params.effects,
|
||||
monitorIndex: params.monitorIndex,
|
||||
cancellable: params.cancellable,
|
||||
onFinished: params.onFinished });
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
getAnimation: function(params) {
|
||||
params = Params.parse(params, { filename: null,
|
||||
onLoaded: null });
|
||||
|
||||
if (this._animationFilename == params.filename) {
|
||||
if (params.onLoaded) {
|
||||
GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, function() {
|
||||
params.onLoaded(this._animation);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
let animation = new Animation({ filename: params.filename });
|
||||
|
||||
animation.load(Lang.bind(this, function() {
|
||||
this._monitorFile(params.filename);
|
||||
this._animationFilename = params.filename;
|
||||
this._animation = animation;
|
||||
|
||||
if (params.onLoaded) {
|
||||
GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, function() {
|
||||
params.onLoaded(this._animation);
|
||||
}));
|
||||
}
|
||||
}));
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(BackgroundCache.prototype);
|
||||
|
||||
function getBackgroundCache() {
|
||||
if (!_backgroundCache)
|
||||
_backgroundCache = new BackgroundCache();
|
||||
return _backgroundCache;
|
||||
}
|
||||
|
||||
const Background = new Lang.Class({
|
||||
Name: 'Background',
|
||||
|
||||
_init: function(params) {
|
||||
params = Params.parse(params, { monitorIndex: 0,
|
||||
layoutManager: Main.layoutManager,
|
||||
effects: Meta.BackgroundEffects.NONE,
|
||||
settings: null });
|
||||
this.actor = new Meta.BackgroundGroup();
|
||||
this.actor._delegate = this;
|
||||
|
||||
this._destroySignalId = this.actor.connect('destroy',
|
||||
Lang.bind(this, this._destroy));
|
||||
|
||||
this._settings = params.settings;
|
||||
this._monitorIndex = params.monitorIndex;
|
||||
this._layoutManager = params.layoutManager;
|
||||
this._effects = params.effects;
|
||||
this._fileWatches = {};
|
||||
this._pattern = null;
|
||||
// contains a single image for static backgrounds and
|
||||
// two images (from and to) for slide shows
|
||||
this._images = {};
|
||||
|
||||
this._brightness = 1.0;
|
||||
this._vignetteSharpness = 0.2;
|
||||
this._saturation = 1.0;
|
||||
this._cancellable = new Gio.Cancellable();
|
||||
this.isLoaded = false;
|
||||
|
||||
this._settingsChangedSignalId = this._settings.connect('changed', Lang.bind(this, function() {
|
||||
this.emit('changed');
|
||||
}));
|
||||
|
||||
this._load();
|
||||
},
|
||||
|
||||
_destroy: function() {
|
||||
this._cancellable.cancel();
|
||||
|
||||
if (this._updateAnimationTimeoutId) {
|
||||
GLib.source_remove (this._updateAnimationTimeoutId);
|
||||
this._updateAnimationTimeoutId = 0;
|
||||
}
|
||||
|
||||
let i;
|
||||
let keys = Object.keys(this._fileWatches);
|
||||
for (i = 0; i < keys.length; i++) {
|
||||
this._cache.disconnect(this._fileWatches[keys[i]]);
|
||||
}
|
||||
this._fileWatches = null;
|
||||
|
||||
if (this._pattern) {
|
||||
if (this._pattern.content)
|
||||
this._cache.removePatternContent(this._pattern.content);
|
||||
|
||||
this._pattern.destroy();
|
||||
this._pattern = null;
|
||||
}
|
||||
|
||||
keys = Object.keys(this._images);
|
||||
for (i = 0; i < keys.length; i++) {
|
||||
let actor = this._images[keys[i]];
|
||||
|
||||
if (actor.content)
|
||||
this._cache.removeImageContent(actor.content);
|
||||
|
||||
actor.destroy();
|
||||
this._images[keys[i]] = null;
|
||||
}
|
||||
|
||||
this.actor.disconnect(this._destroySignalId);
|
||||
this._destroySignalId = 0;
|
||||
|
||||
if (this._settingsChangedSignalId != 0)
|
||||
this._settings.disconnect(this._settingsChangedSignalId);
|
||||
this._settingsChangedSignalId = 0;
|
||||
},
|
||||
|
||||
_setLoaded: function() {
|
||||
if (this.isLoaded)
|
||||
return;
|
||||
|
||||
this.isLoaded = true;
|
||||
|
||||
GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, function() {
|
||||
this.emit('loaded');
|
||||
return false;
|
||||
}));
|
||||
},
|
||||
|
||||
_loadPattern: function() {
|
||||
let colorString, res, color, secondColor;
|
||||
|
||||
colorString = this._settings.get_string(PRIMARY_COLOR_KEY);
|
||||
[res, color] = Clutter.Color.from_string(colorString);
|
||||
colorString = this._settings.get_string(SECONDARY_COLOR_KEY);
|
||||
[res, secondColor] = Clutter.Color.from_string(colorString);
|
||||
|
||||
let shadingType = this._settings.get_enum(COLOR_SHADING_TYPE_KEY);
|
||||
|
||||
let content = this._cache.getPatternContent({ monitorIndex: this._monitorIndex,
|
||||
effects: this._effects,
|
||||
color: color,
|
||||
secondColor: secondColor,
|
||||
shadingType: shadingType });
|
||||
|
||||
this._pattern = new Meta.BackgroundActor();
|
||||
this.actor.add_child(this._pattern);
|
||||
|
||||
this._pattern.content = content;
|
||||
},
|
||||
|
||||
_watchCacheFile: function(filename) {
|
||||
if (this._fileWatches[filename])
|
||||
return;
|
||||
|
||||
let signalId = this._cache.connect('file-changed',
|
||||
Lang.bind(this, function(cache, changedFile) {
|
||||
if (changedFile == filename) {
|
||||
this.emit('changed');
|
||||
}
|
||||
}));
|
||||
this._fileWatches[filename] = signalId;
|
||||
},
|
||||
|
||||
_addImage: function(content, index, filename) {
|
||||
content.saturation = this._saturation;
|
||||
content.brightness = this._brightness;
|
||||
content.vignette_sharpness = this._vignetteSharpness;
|
||||
|
||||
let actor = new Meta.BackgroundActor();
|
||||
actor.content = content;
|
||||
|
||||
// The background pattern is the first actor in
|
||||
// the group, and all images should be above that.
|
||||
this.actor.insert_child_at_index(actor, index + 1);
|
||||
|
||||
this._images[index] = actor;
|
||||
this._watchCacheFile(filename);
|
||||
},
|
||||
|
||||
_updateImage: function(content, index, filename) {
|
||||
content.saturation = this._saturation;
|
||||
content.brightness = this._brightness;
|
||||
content.vignette_sharpness = this._vignetteSharpness;
|
||||
|
||||
this._cache.removeImageContent(this._images[index].content);
|
||||
this._images[index].content = content;
|
||||
this._watchCacheFile(filename);
|
||||
},
|
||||
|
||||
_updateAnimationProgress: function() {
|
||||
if (this._images[1])
|
||||
this._images[1].opacity = this._animation.transitionProgress * 255;
|
||||
|
||||
this._queueUpdateAnimation();
|
||||
},
|
||||
|
||||
_updateAnimation: function() {
|
||||
this._updateAnimationTimeoutId = 0;
|
||||
|
||||
this._animation.update(this._layoutManager.monitors[this._monitorIndex]);
|
||||
let files = this._animation.keyFrameFiles;
|
||||
|
||||
if (files.length == 0) {
|
||||
this._setLoaded();
|
||||
this._queueUpdateAnimation();
|
||||
return;
|
||||
}
|
||||
|
||||
let numPendingImages = files.length;
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
if (this._images[i] && this._images[i].content &&
|
||||
this._images[i].content.get_filename() == files[i]) {
|
||||
|
||||
numPendingImages--;
|
||||
if (numPendingImages == 0)
|
||||
this._updateAnimationProgress();
|
||||
continue;
|
||||
}
|
||||
this._cache.getImageContent({ monitorIndex: this._monitorIndex,
|
||||
effects: this._effects,
|
||||
style: this._style,
|
||||
filename: files[i],
|
||||
cancellable: this._cancellable,
|
||||
onFinished: Lang.bind(this, function(content, i) {
|
||||
numPendingImages--;
|
||||
|
||||
if (!content) {
|
||||
this._setLoaded();
|
||||
if (numPendingImages == 0)
|
||||
this._updateAnimationProgress();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._images[i]) {
|
||||
this._addImage(content, i, files[i]);
|
||||
} else {
|
||||
this._updateImage(content, i, files[i]);
|
||||
}
|
||||
|
||||
if (numPendingImages == 0) {
|
||||
this._setLoaded();
|
||||
this._updateAnimationProgress();
|
||||
}
|
||||
}, i)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_queueUpdateAnimation: function() {
|
||||
if (this._updateAnimationTimeoutId != 0)
|
||||
return;
|
||||
|
||||
if (!this._cancellable || this._cancellable.is_cancelled())
|
||||
return;
|
||||
|
||||
if (!this._animation.transitionDuration)
|
||||
return;
|
||||
|
||||
let nSteps = 255 / ANIMATION_OPACITY_STEP_INCREMENT;
|
||||
let timePerStep = (this._animation.transitionDuration * 1000) / nSteps;
|
||||
|
||||
let interval = Math.max(ANIMATION_MIN_WAKEUP_INTERVAL * 1000,
|
||||
timePerStep);
|
||||
|
||||
if (interval > GLib.MAXUINT32)
|
||||
return;
|
||||
|
||||
this._updateAnimationTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
|
||||
interval,
|
||||
Lang.bind(this, function() {
|
||||
this._updateAnimationTimeoutId = 0;
|
||||
this._updateAnimation();
|
||||
return false;
|
||||
}));
|
||||
},
|
||||
|
||||
_loadAnimation: function(filename) {
|
||||
this._cache.getAnimation({ filename: filename,
|
||||
onLoaded: Lang.bind(this, function(animation) {
|
||||
this._animation = animation;
|
||||
|
||||
if (!this._animation || this._cancellable.is_cancelled()) {
|
||||
this._setLoaded();
|
||||
return;
|
||||
}
|
||||
|
||||
this._updateAnimation();
|
||||
this._watchCacheFile(filename);
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
_loadFile: function(filename) {
|
||||
this._cache.getImageContent({ monitorIndex: this._monitorIndex,
|
||||
effects: this._effects,
|
||||
style: this._style,
|
||||
filename: filename,
|
||||
cancellable: this._cancellable,
|
||||
onFinished: Lang.bind(this, function(content) {
|
||||
if (!content) {
|
||||
if (!this._cancellable.is_cancelled())
|
||||
this._loadAnimation(filename);
|
||||
return;
|
||||
}
|
||||
|
||||
this._addImage(content, 0, filename);
|
||||
this._setLoaded();
|
||||
})
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
_load: function () {
|
||||
this._cache = getBackgroundCache();
|
||||
|
||||
this._loadPattern(this._cache);
|
||||
|
||||
this._style = this._settings.get_enum(BACKGROUND_STYLE_KEY);
|
||||
if (this._style == GDesktopEnums.BackgroundStyle.NONE) {
|
||||
this._setLoaded();
|
||||
return;
|
||||
}
|
||||
|
||||
let uri = this._settings.get_string(PICTURE_URI_KEY);
|
||||
let filename;
|
||||
if (GLib.uri_parse_scheme(uri) != null)
|
||||
filename = Gio.File.new_for_uri(uri).get_path();
|
||||
else
|
||||
filename = uri;
|
||||
|
||||
if (!filename) {
|
||||
this._setLoaded();
|
||||
return;
|
||||
}
|
||||
|
||||
this._loadFile(filename);
|
||||
},
|
||||
|
||||
get saturation() {
|
||||
return this._saturation;
|
||||
},
|
||||
|
||||
set saturation(saturation) {
|
||||
this._saturation = saturation;
|
||||
|
||||
if (this._pattern && this._pattern.content)
|
||||
this._pattern.content.saturation = saturation;
|
||||
|
||||
let keys = Object.keys(this._images);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
let image = this._images[keys[i]];
|
||||
if (image && image.content)
|
||||
image.content.saturation = saturation;
|
||||
}
|
||||
},
|
||||
|
||||
get brightness() {
|
||||
return this._brightness;
|
||||
},
|
||||
|
||||
set brightness(factor) {
|
||||
this._brightness = factor;
|
||||
if (this._pattern && this._pattern.content)
|
||||
this._pattern.content.brightness = factor;
|
||||
|
||||
let keys = Object.keys(this._images);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
let image = this._images[keys[i]];
|
||||
if (image && image.content)
|
||||
image.content.brightness = factor;
|
||||
}
|
||||
},
|
||||
|
||||
get vignetteSharpness() {
|
||||
return this._vignetteSharpness;
|
||||
},
|
||||
|
||||
set vignetteSharpness(sharpness) {
|
||||
this._vignetteSharpness = sharpness;
|
||||
if (this._pattern && this._pattern.content)
|
||||
this._pattern.content.vignette_sharpness = sharpness;
|
||||
|
||||
let keys = Object.keys(this._images);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
let image = this._images[keys[i]];
|
||||
if (image && image.content)
|
||||
image.content.vignette_sharpness = sharpness;
|
||||
}
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(Background.prototype);
|
||||
|
||||
const SystemBackground = new Lang.Class({
|
||||
Name: 'SystemBackground',
|
||||
|
||||
_init: function() {
|
||||
this._cache = getBackgroundCache();
|
||||
this.actor = new Meta.BackgroundActor();
|
||||
|
||||
this._cache.getImageContent({ style: GDesktopEnums.BackgroundStyle.WALLPAPER,
|
||||
filename: global.datadir + '/theme/noise-texture.png',
|
||||
effects: Meta.BackgroundEffects.NONE,
|
||||
onFinished: Lang.bind(this, function(content) {
|
||||
this.actor.content = content;
|
||||
this.emit('loaded');
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(SystemBackground.prototype);
|
||||
|
||||
const Animation = new Lang.Class({
|
||||
Name: 'Animation',
|
||||
|
||||
_init: function(params) {
|
||||
params = Params.parse(params, { filename: null });
|
||||
|
||||
this.filename = params.filename;
|
||||
this.keyFrameFiles = [];
|
||||
this.transitionProgress = 0.0;
|
||||
this.transitionDuration = 0.0;
|
||||
this.loaded = false;
|
||||
},
|
||||
|
||||
load: function(callback) {
|
||||
let file = Gio.File.new_for_path(this.filename);
|
||||
|
||||
this._show = new GnomeDesktop.BGSlideShow({ filename: this.filename });
|
||||
|
||||
this._show.load_async(null,
|
||||
Lang.bind(this,
|
||||
function(object, result) {
|
||||
this.loaded = true;
|
||||
if (callback)
|
||||
callback();
|
||||
}));
|
||||
},
|
||||
|
||||
update: function(monitor) {
|
||||
this.keyFrameFiles = [];
|
||||
|
||||
if (!this._show)
|
||||
return;
|
||||
|
||||
if (this._show.get_num_slides() < 1)
|
||||
return;
|
||||
|
||||
let [progress, duration, isFixed, file1, file2] = this._show.get_current_slide(monitor.width, monitor.height);
|
||||
|
||||
this.transitionDuration = duration;
|
||||
this.transitionProgress = progress;
|
||||
|
||||
if (file1)
|
||||
this.keyFrameFiles.push(file1);
|
||||
|
||||
if (file2)
|
||||
this.keyFrameFiles.push(file2);
|
||||
},
|
||||
});
|
||||
Signals.addSignalMethods(Animation.prototype);
|
||||
|
||||
const BackgroundManager = new Lang.Class({
|
||||
Name: 'BackgroundManager',
|
||||
|
||||
_init: function(params) {
|
||||
params = Params.parse(params, { container: null,
|
||||
layoutManager: Main.layoutManager,
|
||||
monitorIndex: null,
|
||||
effects: Meta.BackgroundEffects.NONE,
|
||||
controlPosition: true,
|
||||
settingsSchema: BACKGROUND_SCHEMA });
|
||||
|
||||
this._settings = new Gio.Settings({ schema: params.settingsSchema });
|
||||
this._container = params.container;
|
||||
this._layoutManager = params.layoutManager;
|
||||
this._effects = params.effects;
|
||||
this._monitorIndex = params.monitorIndex;
|
||||
this._controlPosition = params.controlPosition;
|
||||
|
||||
this.background = this._createBackground();
|
||||
this._newBackground = null;
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this._newBackground) {
|
||||
this._newBackground.actor.destroy();
|
||||
this._newBackground = null;
|
||||
}
|
||||
|
||||
if (this.background) {
|
||||
this.background.actor.destroy();
|
||||
this.background = null;
|
||||
}
|
||||
},
|
||||
|
||||
_updateBackground: function(background, monitorIndex) {
|
||||
let newBackground = this._createBackground(monitorIndex);
|
||||
newBackground.vignetteSharpness = background.vignetteSharpness;
|
||||
newBackground.brightness = background.brightness;
|
||||
newBackground.saturation = background.saturation;
|
||||
newBackground.visible = background.visible;
|
||||
|
||||
newBackground.loadedSignalId = newBackground.connect('loaded',
|
||||
Lang.bind(this, function() {
|
||||
newBackground.disconnect(newBackground.loadedSignalId);
|
||||
newBackground.loadedSignalId = 0;
|
||||
Tweener.addTween(background.actor,
|
||||
{ opacity: 0,
|
||||
time: FADE_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this, function() {
|
||||
if (this._newBackground == newBackground) {
|
||||
this.background = newBackground;
|
||||
this._newBackground = null;
|
||||
} else {
|
||||
newBackground.actor.destroy();
|
||||
}
|
||||
|
||||
background.actor.destroy();
|
||||
|
||||
this.emit('changed');
|
||||
})
|
||||
});
|
||||
}));
|
||||
|
||||
this._newBackground = newBackground;
|
||||
},
|
||||
|
||||
_createBackground: function() {
|
||||
let background = new Background({ monitorIndex: this._monitorIndex,
|
||||
layoutManager: this._layoutManager,
|
||||
effects: this._effects,
|
||||
settings: this._settings });
|
||||
this._container.add_child(background.actor);
|
||||
|
||||
let monitor = this._layoutManager.monitors[this._monitorIndex];
|
||||
|
||||
background.actor.set_size(monitor.width, monitor.height);
|
||||
if (this._controlPosition) {
|
||||
background.actor.set_position(monitor.x, monitor.y);
|
||||
background.actor.lower_bottom();
|
||||
}
|
||||
|
||||
background.changeSignalId = background.connect('changed', Lang.bind(this, function() {
|
||||
background.disconnect(background.changeSignalId);
|
||||
background.changeSignalId = 0;
|
||||
this._updateBackground(background, this._monitorIndex);
|
||||
}));
|
||||
|
||||
background.actor.connect('destroy', Lang.bind(this, function() {
|
||||
if (background.changeSignalId)
|
||||
background.disconnect(background.changeSignalId);
|
||||
|
||||
if (background.loadedSignalId)
|
||||
background.disconnect(background.loadedSignalId);
|
||||
}));
|
||||
|
||||
return background;
|
||||
},
|
||||
});
|
||||
Signals.addSignalMethods(BackgroundManager.prototype);
|
68
js/ui/backgroundMenu.js
Normal file
68
js/ui/backgroundMenu.js
Normal file
@ -0,0 +1,68 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Lang = imports.lang;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const BoxPointer = imports.ui.boxpointer;
|
||||
const Main = imports.ui.main;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
|
||||
const BackgroundMenu = new Lang.Class({
|
||||
Name: 'BackgroundMenu',
|
||||
Extends: PopupMenu.PopupMenu,
|
||||
|
||||
_init: function(source) {
|
||||
this.parent(source, 0, St.Side.TOP);
|
||||
|
||||
this.addSettingsAction(_("Settings"), 'gnome-control-center.desktop');
|
||||
this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this.addSettingsAction(_("Change Background…"), 'gnome-background-panel.desktop');
|
||||
|
||||
this.actor.add_style_class_name('background-menu');
|
||||
|
||||
Main.uiGroup.add_actor(this.actor);
|
||||
this.actor.hide();
|
||||
}
|
||||
});
|
||||
|
||||
function addBackgroundMenu(actor) {
|
||||
let cursor = new St.Bin({ opacity: 0 });
|
||||
Main.uiGroup.add_actor(cursor);
|
||||
|
||||
actor.reactive = true;
|
||||
actor._backgroundMenu = new BackgroundMenu(cursor);
|
||||
actor._backgroundManager = new PopupMenu.PopupMenuManager({ actor: actor });
|
||||
actor._backgroundManager.addMenu(actor._backgroundMenu);
|
||||
|
||||
function openMenu() {
|
||||
let [x, y] = global.get_pointer();
|
||||
cursor.set_position(x, y);
|
||||
actor._backgroundMenu.open(BoxPointer.PopupAnimation.NONE);
|
||||
}
|
||||
|
||||
let clickAction = new Clutter.ClickAction();
|
||||
clickAction.connect('long-press', function(action, actor, state) {
|
||||
if (state == Clutter.LongPressState.QUERY)
|
||||
return action.get_button() == 1 && !actor._backgroundMenu.isOpen;
|
||||
if (state == Clutter.LongPressState.ACTIVATE) {
|
||||
openMenu();
|
||||
actor._backgroundManager.ignoreRelease();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
clickAction.connect('clicked', function(action) {
|
||||
if (action.get_button() == 3)
|
||||
openMenu();
|
||||
});
|
||||
actor.add_action(clickAction);
|
||||
|
||||
actor.connect('destroy', function() {
|
||||
actor._backgroundMenu.destroy();
|
||||
actor._backgroundMenu = null;
|
||||
actor._backgroundManager = null;
|
||||
|
||||
cursor.destroy();
|
||||
});
|
||||
}
|
@ -3,8 +3,9 @@
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Lang = imports.lang;
|
||||
const Meta = imports.gi.Meta;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const Tweener = imports.ui.tweener;
|
||||
@ -38,6 +39,7 @@ const BoxPointer = new Lang.Class({
|
||||
this._arrowSide = arrowSide;
|
||||
this._userArrowSide = arrowSide;
|
||||
this._arrowOrigin = 0;
|
||||
this._arrowActor = null;
|
||||
this.actor = new St.Bin({ x_fill: true,
|
||||
y_fill: true });
|
||||
this._container = new Shell.GenericContainer();
|
||||
@ -60,6 +62,10 @@ const BoxPointer = new Lang.Class({
|
||||
this._muteInput();
|
||||
},
|
||||
|
||||
get arrowSide() {
|
||||
return this._arrowSide;
|
||||
},
|
||||
|
||||
_muteInput: function() {
|
||||
if (this._capturedEventId == 0)
|
||||
this._capturedEventId = this.actor.connect('captured-event',
|
||||
@ -220,31 +226,27 @@ const BoxPointer = new Lang.Class({
|
||||
this.bin.allocate(childBox, flags);
|
||||
|
||||
if (this._sourceActor && this._sourceActor.mapped) {
|
||||
this._reposition(this._sourceActor, this._arrowAlignment);
|
||||
|
||||
if (this._shouldFlip()) {
|
||||
switch (this._arrowSide) {
|
||||
case St.Side.TOP:
|
||||
this._arrowSide = St.Side.BOTTOM;
|
||||
break;
|
||||
case St.Side.BOTTOM:
|
||||
this._arrowSide = St.Side.TOP;
|
||||
break;
|
||||
case St.Side.LEFT:
|
||||
this._arrowSide = St.Side.RIGHT;
|
||||
break;
|
||||
case St.Side.RIGHT:
|
||||
this._arrowSide = St.Side.LEFT;
|
||||
break;
|
||||
}
|
||||
this._reposition(this._sourceActor, this._arrowAlignment);
|
||||
}
|
||||
this._reposition();
|
||||
this._updateFlip();
|
||||
}
|
||||
},
|
||||
|
||||
_drawBorder: function(area) {
|
||||
let themeNode = this.actor.get_theme_node();
|
||||
|
||||
if (this._arrowActor) {
|
||||
let [sourceX, sourceY] = this._arrowActor.get_transformed_position();
|
||||
let [sourceWidth, sourceHeight] = this._arrowActor.get_transformed_size();
|
||||
let [absX, absY] = this.actor.get_transformed_position();
|
||||
|
||||
if (this._arrowSide == St.Side.TOP ||
|
||||
this._arrowSide == St.Side.BOTTOM) {
|
||||
this._arrowOrigin = sourceX - absX + sourceWidth / 2;
|
||||
} else {
|
||||
this._arrowOrigin = sourceY - absY + sourceHeight / 2;
|
||||
}
|
||||
}
|
||||
|
||||
let borderWidth = themeNode.get_length('-arrow-border-width');
|
||||
let base = themeNode.get_length('-arrow-base');
|
||||
let rise = themeNode.get_length('-arrow-rise');
|
||||
@ -405,11 +407,11 @@ const BoxPointer = new Lang.Class({
|
||||
cr.setLineWidth(borderWidth);
|
||||
cr.stroke();
|
||||
}
|
||||
|
||||
cr.$dispose();
|
||||
},
|
||||
|
||||
setPosition: function(sourceActor, alignment) {
|
||||
this._arrowSide = this._userArrowSide;
|
||||
|
||||
// We need to show it now to force an allocation,
|
||||
// so that we can query the correct size.
|
||||
this.actor.show();
|
||||
@ -417,7 +419,8 @@ const BoxPointer = new Lang.Class({
|
||||
this._sourceActor = sourceActor;
|
||||
this._arrowAlignment = alignment;
|
||||
|
||||
this._reposition(sourceActor, alignment);
|
||||
this._reposition();
|
||||
this._updateFlip();
|
||||
},
|
||||
|
||||
setSourceAlignment: function(alignment) {
|
||||
@ -429,7 +432,10 @@ const BoxPointer = new Lang.Class({
|
||||
this.setPosition(this._sourceActor, this._arrowAlignment);
|
||||
},
|
||||
|
||||
_reposition: function(sourceActor, alignment) {
|
||||
_reposition: function() {
|
||||
let sourceActor = this._sourceActor;
|
||||
let alignment = this._arrowAlignment;
|
||||
|
||||
// Position correctly relative to the sourceActor
|
||||
let sourceNode = sourceActor.get_theme_node();
|
||||
let sourceContentBox = sourceNode.get_content_box(sourceActor.get_allocation_box());
|
||||
@ -550,10 +556,20 @@ const BoxPointer = new Lang.Class({
|
||||
}
|
||||
},
|
||||
|
||||
// @actor: an actor relative to which the arrow is positioned.
|
||||
// Differently from setPosition, this will not move the boxpointer itself,
|
||||
// on the arrow
|
||||
setArrowActor: function(actor) {
|
||||
if (this._arrowActor != actor) {
|
||||
this._arrowActor = actor;
|
||||
this._border.queue_repaint();
|
||||
}
|
||||
},
|
||||
|
||||
_shiftActor : function() {
|
||||
// Since the position of the BoxPointer depends on the allocated size
|
||||
// of the BoxPointer and the position of the source actor, trying
|
||||
// to position the BoxPoiner via the x/y properties will result in
|
||||
// to position the BoxPointer via the x/y properties will result in
|
||||
// allocation loops and warnings. Instead we do the positioning via
|
||||
// the anchor point, which is independent of allocation, and leave
|
||||
// x == y == 0.
|
||||
@ -561,37 +577,49 @@ const BoxPointer = new Lang.Class({
|
||||
-(this._yPosition + this._yOffset));
|
||||
},
|
||||
|
||||
_shouldFlip: function() {
|
||||
_calculateArrowSide: function(arrowSide) {
|
||||
let sourceAllocation = Shell.util_get_transformed_allocation(this._sourceActor);
|
||||
let boxAllocation = Shell.util_get_transformed_allocation(this.actor);
|
||||
let boxWidth = boxAllocation.x2 - boxAllocation.x1;
|
||||
let boxHeight = boxAllocation.y2 - boxAllocation.y1;
|
||||
let [minWidth, minHeight, boxWidth, boxHeight] = this._container.get_preferred_size();
|
||||
let monitor = Main.layoutManager.findMonitorForActor(this.actor);
|
||||
|
||||
switch (this._arrowSide) {
|
||||
switch (arrowSide) {
|
||||
case St.Side.TOP:
|
||||
if (boxAllocation.y2 > monitor.y + monitor.height &&
|
||||
if (sourceAllocation.y2 + boxHeight > monitor.y + monitor.height &&
|
||||
boxHeight < sourceAllocation.y1 - monitor.y)
|
||||
return true;
|
||||
return St.Side.BOTTOM;
|
||||
break;
|
||||
case St.Side.BOTTOM:
|
||||
if (boxAllocation.y1 < monitor.y &&
|
||||
if (sourceAllocation.y1 - boxHeight < monitor.y &&
|
||||
boxHeight < monitor.y + monitor.height - sourceAllocation.y2)
|
||||
return true;
|
||||
return St.Side.TOP;
|
||||
break;
|
||||
case St.Side.LEFT:
|
||||
if (boxAllocation.x2 > monitor.x + monitor.width &&
|
||||
if (sourceAllocation.x2 + boxWidth > monitor.x + monitor.width &&
|
||||
boxWidth < sourceAllocation.x1 - monitor.x)
|
||||
return true;
|
||||
return St.Side.RIGHT;
|
||||
break;
|
||||
case St.Side.RIGHT:
|
||||
if (boxAllocation.x1 < monitor.x &&
|
||||
if (sourceAllocation.x1 - boxWidth < monitor.x &&
|
||||
boxWidth < monitor.x + monitor.width - sourceAllocation.x2)
|
||||
return true;
|
||||
return St.Side.LEFT;
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
return arrowSide;
|
||||
},
|
||||
|
||||
_updateFlip: function() {
|
||||
let arrowSide = this._calculateArrowSide(this._userArrowSide);
|
||||
if (this._arrowSide != arrowSide) {
|
||||
this._arrowSide = arrowSide;
|
||||
this._reposition();
|
||||
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() {
|
||||
this._container.queue_relayout();
|
||||
return false;
|
||||
}));
|
||||
|
||||
this.emit('arrow-side-changed');
|
||||
}
|
||||
},
|
||||
|
||||
set xOffset(offset) {
|
||||
@ -618,5 +646,21 @@ const BoxPointer = new Lang.Class({
|
||||
|
||||
get opacity() {
|
||||
return this.actor.opacity;
|
||||
},
|
||||
|
||||
updateArrowSide: function(side) {
|
||||
this._arrowSide = side;
|
||||
this._border.queue_repaint();
|
||||
|
||||
this.emit('arrow-side-changed');
|
||||
},
|
||||
|
||||
getPadding: function(side) {
|
||||
return this.bin.get_theme_node().get_padding(side);
|
||||
},
|
||||
|
||||
getArrowHeight: function() {
|
||||
return this.actor.get_theme_node().get_length('-arrow-rise');
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(BoxPointer.prototype);
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const St = imports.gi.St;
|
||||
const Signals = imports.signals;
|
||||
@ -11,7 +12,6 @@ const Mainloop = imports.mainloop;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const MSECS_IN_DAY = 24 * 60 * 60 * 1000;
|
||||
const WEEKDATE_HEADER_WIDTH_DIGITS = 3;
|
||||
const SHOW_WEEKDATE_KEY = 'show-weekdate';
|
||||
|
||||
// in org.gnome.desktop.interface
|
||||
@ -63,15 +63,18 @@ function _formatEventTime(event, clockFormat) {
|
||||
} else {
|
||||
switch (clockFormat) {
|
||||
case '24h':
|
||||
/* Translators: Shown in calendar event list, if 24h format */
|
||||
ret = event.date.toLocaleFormat(C_("event list time", "%H:%M"));
|
||||
/* Translators: Shown in calendar event list, if 24h format,
|
||||
\u2236 is a ratio character, similar to : */
|
||||
ret = event.date.toLocaleFormat(C_("event list time", "%H\u2236%M"));
|
||||
break;
|
||||
|
||||
default:
|
||||
/* explicit fall-through */
|
||||
case '12h':
|
||||
/* Transators: Shown in calendar event list, if 12h format */
|
||||
ret = event.date.toLocaleFormat(C_("event list time", "%l:%M %p"));
|
||||
/* Translators: Shown in calendar event list, if 12h format,
|
||||
\u2236 is a ratio character, similar to : and \u2009 is
|
||||
a thin space */
|
||||
ret = event.date.toLocaleFormat(C_("event list time", "%l\u2236%M\u2009%p"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -95,15 +98,6 @@ function _getCalendarWeekForDate(date) {
|
||||
return weekNumber;
|
||||
}
|
||||
|
||||
function _getDigitWidth(actor){
|
||||
let context = actor.get_pango_context();
|
||||
let themeNode = actor.get_theme_node();
|
||||
let font = themeNode.get_font();
|
||||
let metrics = context.get_metrics(font, context.get_language());
|
||||
let width = metrics.get_approximate_digit_width();
|
||||
return width;
|
||||
}
|
||||
|
||||
function _getCalendarDayAbbreviation(dayNumber) {
|
||||
let abbreviations = [
|
||||
/* Translators: Calendar grid abbreviation for Sunday.
|
||||
@ -174,6 +168,12 @@ const EmptyEventSource = new Lang.Class({
|
||||
Name: 'EmptyEventSource',
|
||||
|
||||
_init: function() {
|
||||
this.isLoading = false;
|
||||
this.isDummy = true;
|
||||
this.hasCalendars = false;
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
},
|
||||
|
||||
requestRange: function(begin, end) {
|
||||
@ -197,21 +197,18 @@ const CalendarServerIface = <interface name="org.gnome.Shell.CalendarServer">
|
||||
<arg type="b" direction="in" />
|
||||
<arg type="a(sssbxxa{sv})" direction="out" />
|
||||
</method>
|
||||
<property name="HasCalendars" type="b" access="read" />
|
||||
<signal name="Changed" />
|
||||
</interface>;
|
||||
|
||||
const CalendarServerInfo = Gio.DBusInterfaceInfo.new_for_xml(CalendarServerIface);
|
||||
|
||||
function CalendarServer() {
|
||||
var self = new Gio.DBusProxy({ g_connection: Gio.DBus.session,
|
||||
g_interface_name: CalendarServerInfo.name,
|
||||
g_interface_info: CalendarServerInfo,
|
||||
g_name: 'org.gnome.Shell.CalendarServer',
|
||||
g_object_path: '/org/gnome/Shell/CalendarServer',
|
||||
g_flags: Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES });
|
||||
|
||||
self.init(null);
|
||||
return self;
|
||||
return new Gio.DBusProxy({ g_connection: Gio.DBus.session,
|
||||
g_interface_name: CalendarServerInfo.name,
|
||||
g_interface_info: CalendarServerInfo,
|
||||
g_name: 'org.gnome.Shell.CalendarServer',
|
||||
g_object_path: '/org/gnome/Shell/CalendarServer' });
|
||||
}
|
||||
|
||||
function _datesEqual(a, b) {
|
||||
@ -238,18 +235,49 @@ const DBusEventSource = new Lang.Class({
|
||||
|
||||
_init: function() {
|
||||
this._resetCache();
|
||||
this.isLoading = false;
|
||||
this.isDummy = false;
|
||||
|
||||
this._initialized = false;
|
||||
this._dbusProxy = new CalendarServer();
|
||||
this._dbusProxy.connectSignal('Changed', Lang.bind(this, this._onChanged));
|
||||
this._dbusProxy.init_async(GLib.PRIORITY_DEFAULT, null, Lang.bind(this, function(object, result) {
|
||||
try {
|
||||
this._dbusProxy.init_finish(result);
|
||||
} catch(e) {
|
||||
log('Error loading calendars: ' + e.message);
|
||||
return;
|
||||
}
|
||||
|
||||
this._dbusProxy.connect('notify::g-name-owner', Lang.bind(this, function() {
|
||||
if (this._dbusProxy.g_name_owner)
|
||||
this._onNameAppeared();
|
||||
else
|
||||
this._onNameVanished();
|
||||
this._dbusProxy.connectSignal('Changed', Lang.bind(this, this._onChanged));
|
||||
|
||||
this._dbusProxy.connect('notify::g-name-owner', Lang.bind(this, function() {
|
||||
if (this._dbusProxy.g_name_owner)
|
||||
this._onNameAppeared();
|
||||
else
|
||||
this._onNameVanished();
|
||||
}));
|
||||
|
||||
this._dbusProxy.connect('g-properties-changed', Lang.bind(this, function() {
|
||||
this.emit('notify::has-calendars');
|
||||
}));
|
||||
|
||||
this._initialized = true;
|
||||
this.emit('notify::has-calendars');
|
||||
this._onNameAppeared();
|
||||
}));
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this._dbusProxy.run_dispose();
|
||||
},
|
||||
|
||||
get hasCalendars() {
|
||||
if (this._initialized)
|
||||
return this._dbusProxy.HasCalendars;
|
||||
else
|
||||
return false;
|
||||
},
|
||||
|
||||
_resetCache: function() {
|
||||
this._events = [];
|
||||
this._lastRequestBegin = null;
|
||||
@ -289,10 +317,15 @@ const DBusEventSource = new Lang.Class({
|
||||
}
|
||||
|
||||
this._events = newEvents;
|
||||
this.isLoading = false;
|
||||
this.emit('changed');
|
||||
},
|
||||
|
||||
_loadEvents: function(forceReload) {
|
||||
// Ignore while loading
|
||||
if (!this._initialized)
|
||||
return;
|
||||
|
||||
if (this._curRequestBegin && this._curRequestEnd){
|
||||
let callFlags = Gio.DBusCallFlags.NO_AUTO_START;
|
||||
if (forceReload)
|
||||
@ -307,6 +340,7 @@ const DBusEventSource = new Lang.Class({
|
||||
|
||||
requestRange: function(begin, end, forceReload) {
|
||||
if (forceReload || !(_datesEqual(begin, this._lastRequestBegin) && _datesEqual(end, this._lastRequestEnd))) {
|
||||
this.isLoading = true;
|
||||
this._lastRequestBegin = begin;
|
||||
this._lastRequestEnd = end;
|
||||
this._curRequestBegin = begin;
|
||||
@ -345,8 +379,6 @@ const Calendar = new Lang.Class({
|
||||
|
||||
_init: function() {
|
||||
this._weekStart = Shell.util_get_week_start();
|
||||
this._weekdate = NaN;
|
||||
this._digitWidth = NaN;
|
||||
this._settings = new Gio.Settings({ schema: 'org.gnome.shell.calendar' });
|
||||
|
||||
this._settings.connect('changed::' + SHOW_WEEKDATE_KEY, Lang.bind(this, this._onSettingsChange));
|
||||
@ -383,19 +415,11 @@ const Calendar = new Lang.Class({
|
||||
// @eventSource: is an object implementing the EventSource API, e.g. the
|
||||
// requestRange(), getEvents(), hasEvents() methods and the ::changed signal.
|
||||
setEventSource: function(eventSource) {
|
||||
if (this._eventSource) {
|
||||
this._eventSource.disconnect(this._eventSourceChangedId);
|
||||
this._eventSource = null;
|
||||
}
|
||||
|
||||
this._eventSource = eventSource;
|
||||
|
||||
if (this._eventSource) {
|
||||
this._eventSourceChangedId = this._eventSource.connect('changed', Lang.bind(this, function() {
|
||||
this._update(false);
|
||||
}));
|
||||
this._update(true);
|
||||
}
|
||||
this._eventSource.connect('changed', Lang.bind(this, function() {
|
||||
this._update(false);
|
||||
}));
|
||||
this._update(true);
|
||||
},
|
||||
|
||||
// Sets the calendar to show a specific date
|
||||
@ -419,18 +443,21 @@ const Calendar = new Lang.Class({
|
||||
this.actor.add(this._topBox,
|
||||
{ row: 0, col: 0, col_span: offsetCols + 7 });
|
||||
|
||||
this.actor.connect('style-changed', Lang.bind(this, this._onStyleChange));
|
||||
this._backButton = new St.Button({ style_class: 'calendar-change-month-back',
|
||||
accessible_name: _("Previous month"),
|
||||
can_focus: true });
|
||||
this._topBox.add(this._backButton);
|
||||
this._backButton.connect('clicked', Lang.bind(this, this._onPrevMonthButtonClicked));
|
||||
|
||||
let back = new St.Button({ style_class: 'calendar-change-month-back' });
|
||||
this._topBox.add(back);
|
||||
back.connect('clicked', Lang.bind(this, this._onPrevMonthButtonClicked));
|
||||
|
||||
this._monthLabel = new St.Label({style_class: 'calendar-month-label'});
|
||||
this._monthLabel = new St.Label({style_class: 'calendar-month-label',
|
||||
can_focus: true });
|
||||
this._topBox.add(this._monthLabel, { expand: true, x_fill: false, x_align: St.Align.MIDDLE });
|
||||
|
||||
let forward = new St.Button({ style_class: 'calendar-change-month-forward' });
|
||||
this._topBox.add(forward);
|
||||
forward.connect('clicked', Lang.bind(this, this._onNextMonthButtonClicked));
|
||||
this._forwardButton = new St.Button({ style_class: 'calendar-change-month-forward',
|
||||
accessible_name: _("Next month"),
|
||||
can_focus: true });
|
||||
this._topBox.add(this._forwardButton);
|
||||
this._forwardButton.connect('clicked', Lang.bind(this, this._onNextMonthButtonClicked));
|
||||
|
||||
// Add weekday labels...
|
||||
//
|
||||
@ -457,18 +484,6 @@ const Calendar = new Lang.Class({
|
||||
this._firstDayIndex = this.actor.get_n_children();
|
||||
},
|
||||
|
||||
_onStyleChange: function(actor, event) {
|
||||
// width of a digit in pango units
|
||||
this._digitWidth = _getDigitWidth(this.actor) / Pango.SCALE;
|
||||
this._setWeekdateHeaderWidth();
|
||||
},
|
||||
|
||||
_setWeekdateHeaderWidth: function() {
|
||||
if (this.digitWidth != NaN && this._useWeekdate && this._weekdateHeader) {
|
||||
this._weekdateHeader.set_width (this._digitWidth * WEEKDATE_HEADER_WIDTH_DIGITS);
|
||||
}
|
||||
},
|
||||
|
||||
_onScroll : function(actor, event) {
|
||||
switch (event.get_scroll_direction()) {
|
||||
case Clutter.ScrollDirection.UP:
|
||||
@ -501,10 +516,12 @@ const Calendar = new Lang.Class({
|
||||
}
|
||||
}
|
||||
|
||||
this.setDate(newDate, false);
|
||||
},
|
||||
this._backButton.grab_key_focus();
|
||||
|
||||
_onNextMonthButtonClicked: function() {
|
||||
this.setDate(newDate, false);
|
||||
},
|
||||
|
||||
_onNextMonthButtonClicked: function() {
|
||||
let newDate = new Date(this._selectedDate);
|
||||
let oldMonth = newDate.getMonth();
|
||||
if (oldMonth == 11) {
|
||||
@ -523,7 +540,9 @@ const Calendar = new Lang.Class({
|
||||
}
|
||||
}
|
||||
|
||||
this.setDate(newDate, false);
|
||||
this._forwardButton.grab_key_focus();
|
||||
|
||||
this.setDate(newDate, false);
|
||||
},
|
||||
|
||||
_onSettingsChange: function() {
|
||||
@ -546,30 +565,60 @@ const Calendar = new Lang.Class({
|
||||
children[i].destroy();
|
||||
|
||||
// Start at the beginning of the week before the start of the month
|
||||
//
|
||||
// We want to show always 6 weeks (to keep the calendar menu at the same
|
||||
// height if there are no events), so we pad it according to the following
|
||||
// policy:
|
||||
//
|
||||
// 1 - If a month has 6 weeks, we place no padding (example: Dec 2012)
|
||||
// 2 - If a month has 5 weeks and it starts on week start, we pad one week
|
||||
// before it (example: Apr 2012)
|
||||
// 3 - If a month has 5 weeks and it starts on any other day, we pad one week
|
||||
// after it (example: Nov 2012)
|
||||
// 4 - If a month has 4 weeks, we pad one week before and one after it
|
||||
// (example: Feb 2010)
|
||||
//
|
||||
// Actually computing the number of weeks is complex, but we know that the
|
||||
// problematic categories (2 and 4) always start on week start, and that
|
||||
// all months at the end have 6 weeks.
|
||||
|
||||
let beginDate = new Date(this._selectedDate);
|
||||
beginDate.setDate(1);
|
||||
beginDate.setSeconds(0);
|
||||
beginDate.setHours(12);
|
||||
let year = beginDate.getYear();
|
||||
|
||||
let daysToWeekStart = (7 + beginDate.getDay() - this._weekStart) % 7;
|
||||
beginDate.setTime(beginDate.getTime() - daysToWeekStart * MSECS_IN_DAY);
|
||||
let startsOnWeekStart = daysToWeekStart == 0;
|
||||
let weekPadding = startsOnWeekStart ? 7 : 0;
|
||||
|
||||
beginDate.setTime(beginDate.getTime() - (weekPadding + daysToWeekStart) * MSECS_IN_DAY);
|
||||
|
||||
let iter = new Date(beginDate);
|
||||
let row = 2;
|
||||
while (true) {
|
||||
let button = new St.Button({ label: iter.getDate().toString() });
|
||||
// nRows here means 6 weeks + one header + one navbar
|
||||
let nRows = 8;
|
||||
while (row < 8) {
|
||||
let button = new St.Button({ label: iter.getDate().toString(),
|
||||
can_focus: true });
|
||||
let rtl = button.get_text_direction() == Clutter.TextDirection.RTL;
|
||||
|
||||
if (!this._eventSource)
|
||||
if (this._eventSource.isDummy)
|
||||
button.reactive = false;
|
||||
|
||||
let iterStr = iter.toUTCString();
|
||||
button.connect('clicked', Lang.bind(this, function() {
|
||||
this._shouldDateGrabFocus = true;
|
||||
|
||||
let newlySelectedDate = new Date(iterStr);
|
||||
this.setDate(newlySelectedDate, false);
|
||||
|
||||
this._shouldDateGrabFocus = false;
|
||||
}));
|
||||
|
||||
let hasEvents = this._eventSource && this._eventSource.hasEvents(iter);
|
||||
let hasEvents = this._eventSource.hasEvents(iter);
|
||||
let styleClass = 'calendar-day-base calendar-day';
|
||||
|
||||
if (_isWorkDay(iter))
|
||||
styleClass += ' calendar-work-day'
|
||||
else
|
||||
@ -589,9 +638,6 @@ const Calendar = new Lang.Class({
|
||||
else if (iter.getMonth() != this._selectedDate.getMonth())
|
||||
styleClass += ' calendar-other-month-day';
|
||||
|
||||
if (_sameDay(this._selectedDate, iter))
|
||||
button.add_style_pseudo_class('active');
|
||||
|
||||
if (hasEvents)
|
||||
styleClass += ' calendar-day-with-events'
|
||||
|
||||
@ -601,6 +647,13 @@ const Calendar = new Lang.Class({
|
||||
this.actor.add(button,
|
||||
{ row: row, col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7 });
|
||||
|
||||
if (_sameDay(this._selectedDate, iter)) {
|
||||
button.add_style_pseudo_class('active');
|
||||
|
||||
if (this._shouldDateGrabFocus)
|
||||
button.grab_key_focus();
|
||||
}
|
||||
|
||||
if (this._useWeekdate && iter.getDay() == 4) {
|
||||
let label = new St.Label({ text: _getCalendarWeekForDate(iter).toString(),
|
||||
style_class: 'calendar-day-base calendar-week-number'});
|
||||
@ -609,17 +662,13 @@ const Calendar = new Lang.Class({
|
||||
}
|
||||
|
||||
iter.setTime(iter.getTime() + MSECS_IN_DAY);
|
||||
if (iter.getDay() == this._weekStart) {
|
||||
// We stop on the first "first day of the week" after the month we are displaying
|
||||
if (iter.getMonth() > this._selectedDate.getMonth() || iter.getYear() > this._selectedDate.getYear())
|
||||
break;
|
||||
|
||||
if (iter.getDay() == this._weekStart)
|
||||
row++;
|
||||
}
|
||||
}
|
||||
// Signal to the event source that we are interested in events
|
||||
// only from this date range
|
||||
if (this._eventSource)
|
||||
this._eventSource.requestRange(beginDate, iter, forceReload);
|
||||
this._eventSource.requestRange(beginDate, iter, forceReload);
|
||||
}
|
||||
});
|
||||
|
||||
@ -629,7 +678,7 @@ const EventsList = new Lang.Class({
|
||||
Name: 'EventsList',
|
||||
|
||||
_init: function() {
|
||||
this.actor = new St.BoxLayout({ vertical: true, style_class: 'events-header-vbox'});
|
||||
this.actor = new St.Table({ style_class: 'events-table' });
|
||||
this._date = new Date();
|
||||
this._desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' });
|
||||
this._desktopSettings.connect('changed', Lang.bind(this, this._update));
|
||||
@ -637,70 +686,76 @@ const EventsList = new Lang.Class({
|
||||
},
|
||||
|
||||
setEventSource: function(eventSource) {
|
||||
if (this._eventSource) {
|
||||
this._eventSource.disconnect(this._eventSourceChangedId);
|
||||
this._eventSource = null;
|
||||
}
|
||||
|
||||
this._eventSource = eventSource;
|
||||
|
||||
if (this._eventSource) {
|
||||
this._eventSourceChangedId = this._eventSource.connect('changed', Lang.bind(this, this._update));
|
||||
}
|
||||
this._eventSource.connect('changed', Lang.bind(this, this._update));
|
||||
},
|
||||
|
||||
_addEvent: function(dayNameBox, timeBox, eventTitleBox, includeDayName, day, time, desc) {
|
||||
if (includeDayName) {
|
||||
dayNameBox.add(new St.Label( { style_class: 'events-day-dayname',
|
||||
text: day } ),
|
||||
{ x_fill: true } );
|
||||
}
|
||||
timeBox.add(new St.Label( { style_class: 'events-day-time',
|
||||
text: time} ),
|
||||
{ x_fill: true } );
|
||||
eventTitleBox.add(new St.Label( { style_class: 'events-day-task',
|
||||
text: desc} ));
|
||||
_addEvent: function(event, index, includeDayName) {
|
||||
let dayString;
|
||||
if (includeDayName)
|
||||
dayString = _getEventDayAbbreviation(event.date.getDay());
|
||||
else
|
||||
dayString = '';
|
||||
|
||||
let dayLabel = new St.Label({ style_class: 'events-day-dayname',
|
||||
text: dayString });
|
||||
dayLabel.clutter_text.line_wrap = false;
|
||||
dayLabel.clutter_text.ellipsize = false;
|
||||
|
||||
this.actor.add(dayLabel, { row: index, col: 0,
|
||||
x_expand: false, x_align: St.Align.END,
|
||||
y_fill: false, y_align: St.Align.START });
|
||||
|
||||
let clockFormat = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);
|
||||
let timeString = _formatEventTime(event, clockFormat);
|
||||
let timeLabel = new St.Label({ style_class: 'events-day-time',
|
||||
text: timeString });
|
||||
timeLabel.clutter_text.line_wrap = false;
|
||||
timeLabel.clutter_text.ellipsize = false;
|
||||
|
||||
this.actor.add(timeLabel, { row: index, col: 1,
|
||||
x_expand: false, x_align: St.Align.MIDDLE,
|
||||
y_fill: false, y_align: St.Align.START });
|
||||
|
||||
let titleLabel = new St.Label({ style_class: 'events-day-task',
|
||||
text: event.summary });
|
||||
titleLabel.clutter_text.line_wrap = true;
|
||||
titleLabel.clutter_text.ellipsize = false;
|
||||
|
||||
this.actor.add(titleLabel, { row: index, col: 2,
|
||||
x_expand: true, x_align: St.Align.START,
|
||||
y_fill: false, y_align: St.Align.START });
|
||||
},
|
||||
|
||||
_addPeriod: function(header, begin, end, includeDayName, showNothingScheduled) {
|
||||
if (!this._eventSource)
|
||||
return;
|
||||
|
||||
_addPeriod: function(header, index, begin, end, includeDayName, showNothingScheduled) {
|
||||
let events = this._eventSource.getEvents(begin, end);
|
||||
|
||||
let clockFormat = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);;
|
||||
|
||||
if (events.length == 0 && !showNothingScheduled)
|
||||
return;
|
||||
return index;
|
||||
|
||||
let vbox = new St.BoxLayout( {vertical: true} );
|
||||
this.actor.add(vbox);
|
||||
|
||||
vbox.add(new St.Label({ style_class: 'events-day-header', text: header }));
|
||||
let box = new St.BoxLayout({style_class: 'events-header-hbox'});
|
||||
let dayNameBox = new St.BoxLayout({ vertical: true, style_class: 'events-day-name-box' });
|
||||
let timeBox = new St.BoxLayout({ vertical: true, style_class: 'events-time-box' });
|
||||
let eventTitleBox = new St.BoxLayout({ vertical: true, style_class: 'events-event-box' });
|
||||
box.add(dayNameBox, {x_fill: false});
|
||||
box.add(timeBox, {x_fill: false});
|
||||
box.add(eventTitleBox, {expand: true});
|
||||
vbox.add(box);
|
||||
this.actor.add(new St.Label({ style_class: 'events-day-header', text: header }),
|
||||
{ row: index, col: 0, col_span: 3,
|
||||
// In theory, x_expand should be true here, but x_expand
|
||||
// is a property of the column for StTable, ie all day cells
|
||||
// get it too
|
||||
x_expand: false, x_align: St.Align.START,
|
||||
y_fill: false, y_align: St.Align.START });
|
||||
index++;
|
||||
|
||||
for (let n = 0; n < events.length; n++) {
|
||||
let event = events[n];
|
||||
let dayString = _getEventDayAbbreviation(event.date.getDay());
|
||||
let timeString = _formatEventTime(event, clockFormat);
|
||||
let summaryString = event.summary;
|
||||
this._addEvent(dayNameBox, timeBox, eventTitleBox, includeDayName, dayString, timeString, summaryString);
|
||||
this._addEvent(events[n], index, includeDayName);
|
||||
index++;
|
||||
}
|
||||
|
||||
if (events.length == 0 && showNothingScheduled) {
|
||||
let now = new Date();
|
||||
/* Translators: Text to show if there are no events */
|
||||
let nothingEvent = new CalendarEvent(now, now, _("Nothing Scheduled"), true);
|
||||
let timeString = _formatEventTime(nothingEvent, clockFormat);
|
||||
this._addEvent(dayNameBox, timeBox, eventTitleBox, false, "", timeString, nothingEvent.summary);
|
||||
this._addEvent(nothingEvent, index, false);
|
||||
index++;
|
||||
}
|
||||
|
||||
return index;
|
||||
},
|
||||
|
||||
_showOtherDay: function(day) {
|
||||
@ -717,20 +772,21 @@ const EventsList = new Lang.Class({
|
||||
else
|
||||
/* Translators: Shown on calendar heading when selected day occurs on different year */
|
||||
dayString = day.toLocaleFormat(C_("calendar heading", "%A, %B %d, %Y"));
|
||||
this._addPeriod(dayString, dayBegin, dayEnd, false, true);
|
||||
this._addPeriod(dayString, 0, dayBegin, dayEnd, false, true);
|
||||
},
|
||||
|
||||
_showToday: function() {
|
||||
this.actor.destroy_all_children();
|
||||
let index = 0;
|
||||
|
||||
let now = new Date();
|
||||
let dayBegin = _getBeginningOfDay(now);
|
||||
let dayEnd = _getEndOfDay(now);
|
||||
this._addPeriod(_("Today"), dayBegin, dayEnd, false, true);
|
||||
index = this._addPeriod(_("Today"), index, dayBegin, dayEnd, false, true);
|
||||
|
||||
let tomorrowBegin = new Date(dayBegin.getTime() + 86400 * 1000);
|
||||
let tomorrowEnd = new Date(dayEnd.getTime() + 86400 * 1000);
|
||||
this._addPeriod(_("Tomorrow"), tomorrowBegin, tomorrowEnd, false, true);
|
||||
index = this._addPeriod(_("Tomorrow"), index, tomorrowBegin, tomorrowEnd, false, true);
|
||||
|
||||
let dayInWeek = (dayEnd.getDay() - this._weekStart + 7) % 7;
|
||||
|
||||
@ -741,7 +797,7 @@ const EventsList = new Lang.Class({
|
||||
*/
|
||||
let thisWeekBegin = new Date(dayBegin.getTime() + 2 * 86400 * 1000);
|
||||
let thisWeekEnd = new Date(dayEnd.getTime() + (6 - dayInWeek) * 86400 * 1000);
|
||||
this._addPeriod(_("This week"), thisWeekBegin, thisWeekEnd, true, false);
|
||||
index = this._addPeriod(_("This week"), index, thisWeekBegin, thisWeekEnd, true, false);
|
||||
} else {
|
||||
/* otherwise it's one of the two last days of the week ... show
|
||||
* "Next week" and include events up until and including *next*
|
||||
@ -749,7 +805,7 @@ const EventsList = new Lang.Class({
|
||||
*/
|
||||
let nextWeekBegin = new Date(dayBegin.getTime() + 2 * 86400 * 1000);
|
||||
let nextWeekEnd = new Date(dayEnd.getTime() + (13 - dayInWeek) * 86400 * 1000);
|
||||
this._addPeriod(_("Next week"), nextWeekBegin, nextWeekEnd, true, false);
|
||||
index = this._addPeriod(_("Next week"), index, nextWeekBegin, nextWeekEnd, true, false);
|
||||
}
|
||||
},
|
||||
|
||||
@ -762,6 +818,9 @@ const EventsList = new Lang.Class({
|
||||
},
|
||||
|
||||
_update: function() {
|
||||
if (this._eventSource.isLoading)
|
||||
return;
|
||||
|
||||
let today = new Date();
|
||||
if (_sameDay (this._date, today)) {
|
||||
this._showToday();
|
||||
|
@ -1,115 +1,40 @@
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Pango = imports.gi.Pango;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Lang = imports.lang;
|
||||
|
||||
const CheckBoxContainer = new Lang.Class({
|
||||
Name: 'CheckBoxContainer',
|
||||
|
||||
_init: function() {
|
||||
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('style-changed', Lang.bind(this,
|
||||
function() {
|
||||
let node = this.actor.get_theme_node();
|
||||
this._spacing = node.get_length('spacing');
|
||||
}));
|
||||
this.actor.request_mode = Clutter.RequestMode.HEIGHT_FOR_WIDTH;
|
||||
|
||||
this._box = new St.Bin();
|
||||
this.actor.add_actor(this._box);
|
||||
|
||||
this.label = new St.Label();
|
||||
this.label.clutter_text.set_line_wrap(true);
|
||||
this.label.clutter_text.set_ellipsize(Pango.EllipsizeMode.NONE);
|
||||
this.actor.add_actor(this.label);
|
||||
|
||||
this._spacing = 0;
|
||||
},
|
||||
|
||||
_getPreferredWidth: function(actor, forHeight, alloc) {
|
||||
let [minWidth, natWidth] = this._box.get_preferred_width(forHeight);
|
||||
|
||||
alloc.min_size = minWidth + this._spacing;
|
||||
alloc.natural_size = natWidth + this._spacing;
|
||||
},
|
||||
|
||||
_getPreferredHeight: function(actor, forWidth, alloc) {
|
||||
/* FIXME: StBoxlayout currently does not handle
|
||||
height-for-width children correctly, so hard-code
|
||||
two lines for the label until that problem is fixed.
|
||||
|
||||
https://bugzilla.gnome.org/show_bug.cgi?id=672543 */
|
||||
/*
|
||||
let [minBoxHeight, natBoxHeight] =
|
||||
this._box.get_preferred_height(forWidth);
|
||||
let [minLabelHeight, natLabelHeight] =
|
||||
this.label.get_preferred_height(forWidth);
|
||||
|
||||
alloc.min_size = Math.max(minBoxHeight, minLabelHeight);
|
||||
alloc.natural_size = Math.max(natBoxHeight, natLabelHeight);
|
||||
*/
|
||||
let [minBoxHeight, natBoxHeight] =
|
||||
this._box.get_preferred_height(-1);
|
||||
let [minLabelHeight, natLabelHeight] =
|
||||
this.label.get_preferred_height(-1);
|
||||
|
||||
alloc.min_size = Math.max(minBoxHeight, 2 * minLabelHeight);
|
||||
alloc.natural_size = Math.max(natBoxHeight, 2 * natLabelHeight);
|
||||
},
|
||||
|
||||
_allocate: function(actor, box, flags) {
|
||||
let availWidth = box.x2 - box.x1;
|
||||
let availHeight = box.y2 - box.y1;
|
||||
|
||||
let childBox = new Clutter.ActorBox();
|
||||
let [minBoxWidth, natBoxWidth] =
|
||||
this._box.get_preferred_width(-1);
|
||||
let [minBoxHeight, natBoxHeight] =
|
||||
this._box.get_preferred_height(-1);
|
||||
childBox.x1 = box.x1;
|
||||
childBox.x2 = box.x1 + natBoxWidth;
|
||||
childBox.y1 = box.y1;
|
||||
childBox.y2 = box.y1 + natBoxHeight;
|
||||
this._box.allocate(childBox, flags);
|
||||
|
||||
childBox.x1 = box.x1 + natBoxWidth + this._spacing;
|
||||
childBox.x2 = availWidth - childBox.x1;
|
||||
childBox.y1 = box.y1;
|
||||
childBox.y2 = box.y2;
|
||||
this.label.allocate(childBox, flags);
|
||||
}
|
||||
});
|
||||
|
||||
const CheckBox = new Lang.Class({
|
||||
Name: 'CheckBox',
|
||||
|
||||
_init: function(label) {
|
||||
let container = new St.BoxLayout();
|
||||
this.actor = new St.Button({ style_class: 'check-box',
|
||||
child: container,
|
||||
button_mask: St.ButtonMask.ONE,
|
||||
toggle_mode: true,
|
||||
can_focus: true,
|
||||
x_fill: true,
|
||||
y_fill: true });
|
||||
this._container = new CheckBoxContainer();
|
||||
this.actor.set_child(this._container.actor);
|
||||
|
||||
this._box = new St.Bin();
|
||||
this._box.set_y_align(Clutter.ActorAlign.START);
|
||||
container.add_actor(this._box);
|
||||
|
||||
this._label = new St.Label();
|
||||
this._label.clutter_text.set_line_wrap(true);
|
||||
this._label.clutter_text.set_ellipsize(Pango.EllipsizeMode.NONE);
|
||||
container.add_actor(this._label);
|
||||
|
||||
if (label)
|
||||
this.setLabel(label);
|
||||
},
|
||||
|
||||
setLabel: function(label) {
|
||||
this._container.label.set_text(label);
|
||||
this._label.set_text(label);
|
||||
},
|
||||
|
||||
getLabelActor: function() {
|
||||
return this._container.label;
|
||||
return this._label;
|
||||
}
|
||||
});
|
||||
|
@ -8,7 +8,6 @@ const Params = imports.misc.params;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const GnomeSession = imports.misc.gnomeSession;
|
||||
const LoginManager = imports.misc.loginManager;
|
||||
const Main = imports.ui.main;
|
||||
const ShellMountOperation = imports.ui.shellMountOperation;
|
||||
|
||||
@ -33,7 +32,6 @@ const AutomountManager = new Lang.Class({
|
||||
Lang.bind(this, this._InhibitorsChanged));
|
||||
this._inhibited = false;
|
||||
|
||||
this._loginManager = LoginManager.getLoginManager();
|
||||
this._volumeMonitor = Gio.VolumeMonitor.get();
|
||||
},
|
||||
|
||||
@ -85,25 +83,29 @@ const AutomountManager = new Lang.Class({
|
||||
_onDriveConnected: function() {
|
||||
// if we're not in the current ConsoleKit session,
|
||||
// or screensaver is active, don't play sounds
|
||||
if (!this._loginManager.sessionActive)
|
||||
if (!this._session.SessionIsActive)
|
||||
return;
|
||||
|
||||
global.play_theme_sound(0, 'device-added-media');
|
||||
global.play_theme_sound(0, 'device-added-media',
|
||||
_("External drive connected"),
|
||||
null);
|
||||
},
|
||||
|
||||
_onDriveDisconnected: function() {
|
||||
// if we're not in the current ConsoleKit session,
|
||||
// or screensaver is active, don't play sounds
|
||||
if (!this._loginManager.sessionActive)
|
||||
if (!this._session.SessionIsActive)
|
||||
return;
|
||||
|
||||
global.play_theme_sound(0, 'device-removed-media');
|
||||
global.play_theme_sound(0, 'device-removed-media',
|
||||
_("External drive disconnected"),
|
||||
null);
|
||||
},
|
||||
|
||||
_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._loginManager.sessionActive)
|
||||
if (!this._session.SessionIsActive)
|
||||
return;
|
||||
|
||||
// we force stop/eject in this case, so we don't have to pass a
|
||||
@ -143,7 +145,7 @@ const AutomountManager = new Lang.Class({
|
||||
if (params.checkSession) {
|
||||
// if we're not in the current ConsoleKit session,
|
||||
// don't attempt automount
|
||||
if (!this._loginManager.sessionActive)
|
||||
if (!this._session.SessionIsActive)
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ const Lang = imports.lang;
|
||||
const Gio = imports.gi.Gio;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const LoginManager = imports.misc.loginManager;
|
||||
const GnomeSession = imports.misc.gnomeSession;
|
||||
const Main = imports.ui.main;
|
||||
const MessageTray = imports.ui.messageTray;
|
||||
const ShellMountOperation = imports.ui.shellMountOperation;
|
||||
@ -31,7 +31,7 @@ function shouldAutorunMount(mount, forTransient) {
|
||||
if (!volume || (!volume.allowAutorun && forTransient))
|
||||
return false;
|
||||
|
||||
if (!root.is_native() || isMountRootHidden(root))
|
||||
if (root.is_native() && isMountRootHidden(root))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@ -162,8 +162,7 @@ const AutorunManager = new Lang.Class({
|
||||
Name: 'AutorunManager',
|
||||
|
||||
_init: function() {
|
||||
this._loginManager = LoginManager.getLoginManager();
|
||||
|
||||
this._session = new GnomeSession.SessionManager();
|
||||
this._volumeMonitor = Gio.VolumeMonitor.get();
|
||||
|
||||
this._transDispatcher = new AutorunTransientDispatcher(this);
|
||||
@ -215,7 +214,7 @@ const AutorunManager = new Lang.Class({
|
||||
_onMountAdded: function(monitor, mount) {
|
||||
// don't do anything if our session is not the currently
|
||||
// active one
|
||||
if (!this._loginManager.sessionActive)
|
||||
if (!this._session.SessionIsActive)
|
||||
return;
|
||||
|
||||
this._processMount(mount, true);
|
||||
@ -293,7 +292,7 @@ const AutorunResidentSource = new Lang.Class({
|
||||
|
||||
_init: function(manager) {
|
||||
this.parent(_("Removable Devices"), 'media-removable');
|
||||
this.showInLockScreen = false;
|
||||
this.resident = true;
|
||||
|
||||
this._mounts = [];
|
||||
|
||||
@ -301,6 +300,10 @@ const AutorunResidentSource = new Lang.Class({
|
||||
this._notification = new AutorunResidentNotification(this._manager, this);
|
||||
},
|
||||
|
||||
_createPolicy: function() {
|
||||
return new MessageTray.NotificationPolicy({ showInLockScreen: false });
|
||||
},
|
||||
|
||||
buildRightClickMenu: function() {
|
||||
return null;
|
||||
},
|
||||
|
@ -25,7 +25,7 @@ const KeyringDialog = new Lang.Class({
|
||||
this.prompt = new Shell.KeyringPrompt();
|
||||
this.prompt.connect('show-password', Lang.bind(this, this._onShowPassword));
|
||||
this.prompt.connect('show-confirm', Lang.bind(this, this._onShowConfirm));
|
||||
this.prompt.connect('hide-prompt', Lang.bind(this, this._onHidePrompt));
|
||||
this.prompt.connect('prompt-close', Lang.bind(this, this._onHidePrompt));
|
||||
|
||||
let mainContentBox = new St.BoxLayout({ style_class: 'prompt-dialog-main-layout',
|
||||
vertical: false });
|
||||
@ -60,41 +60,46 @@ const KeyringDialog = new Lang.Class({
|
||||
|
||||
this._controlTable = null;
|
||||
|
||||
let buttons = [{ label: '',
|
||||
action: Lang.bind(this, this._onCancelButton),
|
||||
key: Clutter.Escape
|
||||
},
|
||||
{ label: '',
|
||||
action: Lang.bind(this, this._onContinueButton),
|
||||
default: true
|
||||
}]
|
||||
|
||||
this.setButtons(buttons);
|
||||
this._cancelButton = buttons[0].button;
|
||||
this._continueButton = buttons[1].button;
|
||||
this._cancelButton = this.addButton({ label: '',
|
||||
action: Lang.bind(this, this._onCancelButton),
|
||||
key: Clutter.Escape },
|
||||
{ expand: true, x_fill: false, x_align: St.Align.START });
|
||||
this.placeSpinner({ expand: false,
|
||||
x_fill: false,
|
||||
y_fill: false,
|
||||
x_align: St.Align.END,
|
||||
y_align: St.Align.MIDDLE });
|
||||
this._continueButton = this.addButton({ label: '',
|
||||
action: Lang.bind(this, this._onContinueButton),
|
||||
default: true },
|
||||
{ expand: false, x_fill: false, x_align: St.Align.END });
|
||||
|
||||
this.prompt.bind_property('cancel-label', this._cancelButton, 'label', GObject.BindingFlags.SYNC_CREATE);
|
||||
this.prompt.bind_property('continue-label', this._continueButton, 'label', GObject.BindingFlags.SYNC_CREATE);
|
||||
},
|
||||
|
||||
_buildControlTable: function() {
|
||||
let table = new St.Table({ style_class: 'keyring-dialog-control-table' });
|
||||
let layout = new Clutter.TableLayout();
|
||||
let table = new St.Widget({ style_class: 'keyring-dialog-control-table',
|
||||
layout_manager: layout });
|
||||
layout.hookup_style(table);
|
||||
let row = 0;
|
||||
|
||||
if (this.prompt.password_visible) {
|
||||
let label = new St.Label(({ style_class: 'prompt-dialog-password-label' }));
|
||||
let label = new St.Label({ style_class: 'prompt-dialog-password-label' });
|
||||
label.set_text(_("Password:"));
|
||||
table.add(label, { row: row, col: 0,
|
||||
x_expand: false, x_fill: true,
|
||||
x_align: St.Align.START,
|
||||
y_fill: false, y_align: St.Align.MIDDLE });
|
||||
label.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
||||
layout.pack(label, 0, row);
|
||||
layout.child_set(label, { x_expand: false, y_fill: false,
|
||||
x_align: Clutter.TableAlignment.START });
|
||||
this._passwordEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry',
|
||||
text: '',
|
||||
can_focus: true});
|
||||
can_focus: true });
|
||||
this._passwordEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
|
||||
ShellEntry.addContextMenu(this._passwordEntry, { isPassword: true });
|
||||
this._passwordEntry.clutter_text.connect('activate', Lang.bind(this, this._onPasswordActivate));
|
||||
table.add(this._passwordEntry, { row: row, col: 1, x_expand: true, x_fill: true, x_align: St.Align.START });
|
||||
layout.pack(this._passwordEntry, 1, row);
|
||||
row++;
|
||||
} else {
|
||||
this._passwordEntry = null;
|
||||
@ -103,17 +108,16 @@ const KeyringDialog = new Lang.Class({
|
||||
if (this.prompt.confirm_visible) {
|
||||
var label = new St.Label(({ style_class: 'prompt-dialog-password-label' }));
|
||||
label.set_text(_("Type again:"));
|
||||
table.add(label, { row: row, col: 0,
|
||||
x_expand: false, x_fill: true,
|
||||
x_align: St.Align.START,
|
||||
y_fill: false, y_align: St.Align.MIDDLE });
|
||||
layout.pack(label, 0, row);
|
||||
layout.child_set(label, { x_expand: false, y_fill: false,
|
||||
x_align: Clutter.TableAlignment.START });
|
||||
this._confirmEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry',
|
||||
text: '',
|
||||
can_focus: true});
|
||||
can_focus: true });
|
||||
this._confirmEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
|
||||
ShellEntry.addContextMenu(this._confirmEntry, { isPassword: true });
|
||||
this._confirmEntry.clutter_text.connect('activate', Lang.bind(this, this._onConfirmActivate));
|
||||
table.add(this._confirmEntry, { row: row, col: 1, x_expand: true, x_fill: true, x_align: St.Align.START });
|
||||
layout.pack(this._confirmEntry, 1, row);
|
||||
row++;
|
||||
} else {
|
||||
this._confirmEntry = null;
|
||||
@ -126,14 +130,14 @@ const KeyringDialog = new Lang.Class({
|
||||
let choice = new CheckBox.CheckBox();
|
||||
this.prompt.bind_property('choice-label', choice.getLabelActor(), 'text', GObject.BindingFlags.SYNC_CREATE);
|
||||
this.prompt.bind_property('choice-chosen', choice.actor, 'checked', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL);
|
||||
table.add(choice.actor, { row: row, col: 1, x_expand: false, x_fill: true, x_align: St.Align.START });
|
||||
layout.pack(choice.actor, 1, row);
|
||||
row++;
|
||||
}
|
||||
|
||||
let warning = new St.Label({ style_class: 'prompt-dialog-error-label' });
|
||||
warning.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
||||
warning.clutter_text.line_wrap = true;
|
||||
table.add(warning, { row: row, col: 1, x_expand: false, x_fill: false, x_align: St.Align.START });
|
||||
layout.pack(warning, 1, row);
|
||||
this.prompt.bind_property('warning-visible', warning, 'visible', GObject.BindingFlags.SYNC_CREATE);
|
||||
this.prompt.bind_property('warning', warning, 'text', GObject.BindingFlags.SYNC_CREATE);
|
||||
|
||||
@ -146,6 +150,22 @@ const KeyringDialog = new Lang.Class({
|
||||
this._messageBox.add(table, { x_fill: true, y_fill: true });
|
||||
},
|
||||
|
||||
_updateSensitivity: function(sensitive) {
|
||||
if (this._passwordEntry) {
|
||||
this._passwordEntry.reactive = sensitive;
|
||||
this._passwordEntry.clutter_text.editable = sensitive;
|
||||
}
|
||||
|
||||
if (this._confirmEntry) {
|
||||
this._confirmEntry.reactive = sensitive;
|
||||
this._confirmEntry.clutter_text.editable = sensitive;
|
||||
}
|
||||
|
||||
this._continueButton.can_focus = sensitive;
|
||||
this._continueButton.reactive = sensitive;
|
||||
this.setWorking(!sensitive);
|
||||
},
|
||||
|
||||
_ensureOpen: function() {
|
||||
// NOTE: ModalDialog.open() is safe to call if the dialog is
|
||||
// already open - it just returns true without side-effects
|
||||
@ -167,12 +187,14 @@ const KeyringDialog = new Lang.Class({
|
||||
_onShowPassword: function(prompt) {
|
||||
this._buildControlTable();
|
||||
this._ensureOpen();
|
||||
this._updateSensitivity(true);
|
||||
this._passwordEntry.grab_key_focus();
|
||||
},
|
||||
|
||||
_onShowConfirm: function(prompt) {
|
||||
this._buildControlTable();
|
||||
this._ensureOpen();
|
||||
this._updateSensitivity(true);
|
||||
this._continueButton.grab_key_focus();
|
||||
},
|
||||
|
||||
@ -192,6 +214,7 @@ const KeyringDialog = new Lang.Class({
|
||||
},
|
||||
|
||||
_onContinueButton: function() {
|
||||
this._updateSensitivity(false);
|
||||
this.prompt.complete();
|
||||
},
|
||||
|
||||
@ -200,27 +223,50 @@ const KeyringDialog = new Lang.Class({
|
||||
},
|
||||
});
|
||||
|
||||
const KeyringDummyDialog = new Lang.Class({
|
||||
Name: 'KeyringDummyDialog',
|
||||
|
||||
_init: function() {
|
||||
this.prompt = new Shell.KeyringPrompt();
|
||||
this.prompt.connect('show-password',
|
||||
Lang.bind(this, this._cancelPrompt));
|
||||
this.prompt.connect('show-confirm', Lang.bind(this,
|
||||
this._cancelPrompt));
|
||||
},
|
||||
|
||||
_cancelPrompt: function() {
|
||||
this.prompt.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
const KeyringPrompter = new Lang.Class({
|
||||
Name: 'KeyringPrompter',
|
||||
|
||||
_init: function() {
|
||||
this._prompter = new Gcr.SystemPrompter();
|
||||
this._prompter.connect('new-prompt', function(prompter) {
|
||||
let dialog = new KeyringDialog();
|
||||
return dialog.prompt;
|
||||
});
|
||||
this._prompter.connect('new-prompt', Lang.bind(this,
|
||||
function() {
|
||||
let dialog = this._enabled ? new KeyringDialog()
|
||||
: new KeyringDummyDialog();
|
||||
return dialog.prompt;
|
||||
}));
|
||||
this._dbusId = null;
|
||||
this._registered = false;
|
||||
this._enabled = false;
|
||||
},
|
||||
|
||||
enable: function() {
|
||||
this._prompter.register(Gio.DBus.session);
|
||||
this._dbusId = Gio.DBus.session.own_name('org.gnome.keyring.SystemPrompter',
|
||||
Gio.BusNameOwnerFlags.REPLACE, null, null);
|
||||
if (!this._registered) {
|
||||
this._prompter.register(Gio.DBus.session);
|
||||
this._dbusId = Gio.DBus.session.own_name('org.gnome.keyring.SystemPrompter',
|
||||
Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT, null, null);
|
||||
this._registered = true;
|
||||
}
|
||||
this._enabled = true;
|
||||
},
|
||||
|
||||
disable: function() {
|
||||
this._prompter.unregister(false);
|
||||
Gio.DBus.session.unown_name(this._dbusId);
|
||||
this._enabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -62,14 +62,9 @@ const NetworkSecretDialog = new Lang.Class({
|
||||
|
||||
if (this._content.message != null) {
|
||||
let descriptionLabel = new St.Label({ style_class: 'prompt-dialog-description',
|
||||
text: this._content.message,
|
||||
// HACK: for reasons unknown to me, the label
|
||||
// is not asked the correct height for width,
|
||||
// and thus is underallocated
|
||||
// place a fixed height to avoid overflowing
|
||||
style: 'height: 3em'
|
||||
});
|
||||
text: this._content.message });
|
||||
descriptionLabel.clutter_text.line_wrap = true;
|
||||
descriptionLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
||||
|
||||
messageBox.add(descriptionLabel,
|
||||
{ y_fill: true,
|
||||
@ -77,13 +72,18 @@ const NetworkSecretDialog = new Lang.Class({
|
||||
expand: true });
|
||||
}
|
||||
|
||||
let secretTable = new St.Table({ style_class: 'network-dialog-secret-table' });
|
||||
let layout = new Clutter.TableLayout();
|
||||
let secretTable = new St.Widget({ style_class: 'network-dialog-secret-table',
|
||||
layout_manager: layout });
|
||||
layout.hookup_style(secretTable);
|
||||
|
||||
let initialFocusSet = false;
|
||||
let pos = 0;
|
||||
for (let i = 0; i < this._content.secrets.length; i++) {
|
||||
let secret = this._content.secrets[i];
|
||||
let label = new St.Label({ style_class: 'prompt-dialog-password-label',
|
||||
text: secret.label });
|
||||
label.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
||||
|
||||
let reactive = secret.key != null;
|
||||
|
||||
@ -116,11 +116,10 @@ const NetworkSecretDialog = new Lang.Class({
|
||||
} else
|
||||
secret.valid = true;
|
||||
|
||||
secretTable.add(label, { row: pos, col: 0,
|
||||
x_expand: false, x_fill: true,
|
||||
x_align: St.Align.START,
|
||||
y_fill: false, y_align: St.Align.MIDDLE });
|
||||
secretTable.add(secret.entry, { row: pos, col: 1, x_expand: true, x_fill: true, y_align: St.Align.END });
|
||||
layout.pack(label, 0, pos);
|
||||
layout.child_set(label, { x_expand: false, y_fill: false,
|
||||
x_align: Clutter.TableAlignment.START });
|
||||
layout.pack(secret.entry, 1, pos);
|
||||
pos++;
|
||||
|
||||
if (secret.password)
|
||||
@ -385,11 +384,7 @@ const VPNRequestHandler = new Lang.Class({
|
||||
this._childPid = pid;
|
||||
this._stdin = new Gio.UnixOutputStream({ fd: stdin, close_fd: true });
|
||||
this._stdout = new Gio.UnixInputStream({ fd: stdout, close_fd: true });
|
||||
// We need this one too, even if don't actually care of what the process
|
||||
// has to say on stderr, because otherwise the fd opened by g_spawn_async_with_pipes
|
||||
// is kept open indefinitely
|
||||
let stderrStream = new Gio.UnixInputStream({ fd: stderr, close_fd: true });
|
||||
stderrStream.close(null);
|
||||
GLib.close(stderr);
|
||||
this._dataStdout = new Gio.DataInputStream({ base_stream: this._stdout });
|
||||
|
||||
if (this._newStylePlugin)
|
||||
@ -587,18 +582,19 @@ const NetworkAgent = new Lang.Class({
|
||||
Name: 'NetworkAgent',
|
||||
|
||||
_init: function() {
|
||||
this._native = new Shell.NetworkAgent({ auto_register: false,
|
||||
identifier: 'org.gnome.Shell.NetworkAgent' });
|
||||
this._native = new Shell.NetworkAgent({ identifier: 'org.gnome.Shell.NetworkAgent' });
|
||||
|
||||
this._dialogs = { };
|
||||
this._vpnRequests = { };
|
||||
|
||||
this._native.connect('new-request', Lang.bind(this, this._newRequest));
|
||||
this._native.connect('cancel-request', Lang.bind(this, this._cancelRequest));
|
||||
|
||||
this._enabled = false;
|
||||
},
|
||||
|
||||
enable: function() {
|
||||
this._native.auto_register = true;
|
||||
this._enabled = true;
|
||||
},
|
||||
|
||||
disable: function() {
|
||||
@ -612,11 +608,15 @@ const NetworkAgent = new Lang.Class({
|
||||
this._vpnRequests[requestId].cancel(true);
|
||||
this._vpnRequests = { };
|
||||
|
||||
this._native.auto_register = false;
|
||||
this._native.unregister();
|
||||
this._enabled = false;
|
||||
},
|
||||
|
||||
_newRequest: function(agent, requestId, connection, settingName, hints, flags) {
|
||||
if (!this._enabled) {
|
||||
agent.respond(requestId, Shell.NetworkAgentResponse.USER_CANCELED);
|
||||
return;
|
||||
}
|
||||
|
||||
if (settingName == 'vpn') {
|
||||
this._vpnRequest(requestId, connection, hints, flags);
|
||||
return;
|
||||
|
@ -16,7 +16,7 @@ const PolkitAgent = imports.gi.PolkitAgent;
|
||||
const Components = imports.ui.components;
|
||||
const ModalDialog = imports.ui.modalDialog;
|
||||
const ShellEntry = imports.ui.shellEntry;
|
||||
const UserMenu = imports.ui.userMenu;
|
||||
const UserWidget = imports.ui.userWidget;
|
||||
|
||||
const DIALOG_ICON_SIZE = 48;
|
||||
|
||||
@ -31,7 +31,6 @@ const AuthenticationDialog = new Lang.Class({
|
||||
this.message = message;
|
||||
this.userNames = userNames;
|
||||
this._wasDismissed = false;
|
||||
this._completed = false;
|
||||
|
||||
let mainContentBox = new St.BoxLayout({ style_class: 'prompt-dialog-main-layout',
|
||||
vertical: false });
|
||||
@ -101,9 +100,9 @@ const AuthenticationDialog = new Lang.Class({
|
||||
let userBox = new St.BoxLayout({ style_class: 'polkit-dialog-user-layout',
|
||||
vertical: false });
|
||||
messageBox.add(userBox);
|
||||
this._userAvatar = new UserMenu.UserAvatarWidget(this._user,
|
||||
{ iconSize: DIALOG_ICON_SIZE,
|
||||
styleClass: 'polkit-dialog-user-icon' });
|
||||
this._userAvatar = new UserWidget.Avatar(this._user,
|
||||
{ iconSize: DIALOG_ICON_SIZE,
|
||||
styleClass: 'polkit-dialog-user-icon' });
|
||||
this._userAvatar.actor.hide();
|
||||
userBox.add(this._userAvatar.actor,
|
||||
{ x_fill: true,
|
||||
@ -159,29 +158,34 @@ const AuthenticationDialog = new Lang.Class({
|
||||
messageBox.add(this._nullMessageLabel);
|
||||
this._nullMessageLabel.show();
|
||||
|
||||
this.setButtons([{ label: _("Cancel"),
|
||||
action: Lang.bind(this, this.cancel),
|
||||
key: Clutter.Escape
|
||||
},
|
||||
{ label: _("Authenticate"),
|
||||
action: Lang.bind(this, this._onAuthenticateButtonPressed),
|
||||
default: true
|
||||
}]);
|
||||
this._cancelButton = this.addButton({ label: _("Cancel"),
|
||||
action: Lang.bind(this, this.cancel),
|
||||
key: Clutter.Escape },
|
||||
{ expand: true, x_fill: false, x_align: St.Align.START });
|
||||
this.placeSpinner({ expand: false,
|
||||
x_fill: false,
|
||||
y_fill: false,
|
||||
x_align: St.Align.END,
|
||||
y_align: St.Align.MIDDLE });
|
||||
this._okButton = this.addButton({ label: _("Authenticate"),
|
||||
action: Lang.bind(this, this._onAuthenticateButtonPressed),
|
||||
default: true },
|
||||
{ expand: false, x_fill: false, x_align: St.Align.END });
|
||||
|
||||
this._doneEmitted = false;
|
||||
|
||||
this._identityToAuth = Polkit.UnixUser.new_for_name(userName);
|
||||
this._cookie = cookie;
|
||||
},
|
||||
|
||||
performAuthentication: function() {
|
||||
this.destroySession();
|
||||
this._session = new PolkitAgent.Session({ identity: this._identityToAuth,
|
||||
cookie: this._cookie });
|
||||
this._session.connect('completed', Lang.bind(this, this._onSessionCompleted));
|
||||
this._session.connect('request', Lang.bind(this, this._onSessionRequest));
|
||||
this._session.connect('show-error', Lang.bind(this, this._onSessionShowError));
|
||||
this._session.connect('show-info', Lang.bind(this, this._onSessionShowInfo));
|
||||
},
|
||||
|
||||
startAuthentication: function() {
|
||||
this._session.initiate();
|
||||
},
|
||||
|
||||
@ -203,19 +207,29 @@ const AuthenticationDialog = new Lang.Class({
|
||||
log('polkitAuthenticationAgent: Failed to show modal dialog.' +
|
||||
' Dismissing authentication request for action-id ' + this.actionId +
|
||||
' cookie ' + this._cookie);
|
||||
this._emitDone(false, true);
|
||||
this._emitDone(true);
|
||||
}
|
||||
},
|
||||
|
||||
_emitDone: function(keepVisible, dismissed) {
|
||||
_emitDone: function(dismissed) {
|
||||
if (!this._doneEmitted) {
|
||||
this._doneEmitted = true;
|
||||
this.emit('done', keepVisible, dismissed);
|
||||
this.emit('done', dismissed);
|
||||
}
|
||||
},
|
||||
|
||||
_updateSensitivity: function(sensitive) {
|
||||
this._passwordEntry.reactive = sensitive;
|
||||
this._passwordEntry.clutter_text.editable = sensitive;
|
||||
|
||||
this._okButton.can_focus = sensitive;
|
||||
this._okButton.reactive = sensitive;
|
||||
this.setWorking(!sensitive);
|
||||
},
|
||||
|
||||
_onEntryActivate: function() {
|
||||
let response = this._passwordEntry.get_text();
|
||||
this._updateSensitivity(false);
|
||||
this._session.response(response);
|
||||
// When the user responds, dismiss already shown info and
|
||||
// error texts (if any)
|
||||
@ -229,12 +243,16 @@ const AuthenticationDialog = new Lang.Class({
|
||||
},
|
||||
|
||||
_onSessionCompleted: function(session, gainedAuthorization) {
|
||||
if (this._completed)
|
||||
if (this._completed || this._doneEmitted)
|
||||
return;
|
||||
|
||||
this._completed = true;
|
||||
|
||||
if (!gainedAuthorization) {
|
||||
/* Yay, all done */
|
||||
if (gainedAuthorization) {
|
||||
this._emitDone(false);
|
||||
|
||||
} else {
|
||||
/* Unless we are showing an existing error message from the PAM
|
||||
* module (the PAM module could be reporting the authentication
|
||||
* error providing authentication-method specific information),
|
||||
@ -250,8 +268,10 @@ const AuthenticationDialog = new Lang.Class({
|
||||
this._infoMessageLabel.hide();
|
||||
this._nullMessageLabel.hide();
|
||||
}
|
||||
|
||||
/* Try and authenticate again */
|
||||
this.performAuthentication();
|
||||
}
|
||||
this._emitDone(!gainedAuthorization, false);
|
||||
},
|
||||
|
||||
_onSessionRequest: function(session, request, echo_on) {
|
||||
@ -269,6 +289,7 @@ const AuthenticationDialog = new Lang.Class({
|
||||
this._passwordBox.show();
|
||||
this._passwordEntry.set_text('');
|
||||
this._passwordEntry.grab_key_focus();
|
||||
this._updateSensitivity(true);
|
||||
this._ensureOpen();
|
||||
},
|
||||
|
||||
@ -294,6 +315,7 @@ const AuthenticationDialog = new Lang.Class({
|
||||
if (this._session) {
|
||||
if (!this._completed)
|
||||
this._session.cancel();
|
||||
this._completed = false;
|
||||
this._session = null;
|
||||
}
|
||||
},
|
||||
@ -308,7 +330,7 @@ const AuthenticationDialog = new Lang.Class({
|
||||
cancel: function() {
|
||||
this._wasDismissed = true;
|
||||
this.close(global.get_current_time());
|
||||
this._emitDone(false, true);
|
||||
this._emitDone(true);
|
||||
},
|
||||
});
|
||||
Signals.addSignalMethods(AuthenticationDialog.prototype);
|
||||
@ -318,7 +340,6 @@ const AuthenticationAgent = new Lang.Class({
|
||||
|
||||
_init: function() {
|
||||
this._currentDialog = null;
|
||||
this._isCompleting = false;
|
||||
this._handle = null;
|
||||
this._native = new Shell.PolkitAuthenticationAgent();
|
||||
this._native.connect('initiate', Lang.bind(this, this._onInitiate));
|
||||
@ -326,11 +347,19 @@ const AuthenticationAgent = new Lang.Class({
|
||||
},
|
||||
|
||||
enable: function() {
|
||||
this._native.register();
|
||||
try {
|
||||
this._native.register();
|
||||
} catch(e) {
|
||||
log('Failed to register AuthenticationAgent');
|
||||
}
|
||||
},
|
||||
|
||||
disable: function() {
|
||||
this._native.unregister();
|
||||
try {
|
||||
this._native.unregister();
|
||||
} catch(e) {
|
||||
log('Failed to unregister AuthenticationAgent');
|
||||
}
|
||||
},
|
||||
|
||||
_onInitiate: function(nativeAgent, actionId, message, iconName, cookie, userNames) {
|
||||
@ -347,45 +376,24 @@ const AuthenticationAgent = new Lang.Class({
|
||||
// discussion.
|
||||
|
||||
this._currentDialog.connect('done', Lang.bind(this, this._onDialogDone));
|
||||
this._currentDialog.startAuthentication();
|
||||
this._currentDialog.performAuthentication();
|
||||
},
|
||||
|
||||
_onCancel: function(nativeAgent) {
|
||||
this._completeRequest(false, false);
|
||||
this._completeRequest(false);
|
||||
},
|
||||
|
||||
_onDialogDone: function(dialog, keepVisible, dismissed) {
|
||||
this._completeRequest(keepVisible, dismissed);
|
||||
_onDialogDone: function(dialog, dismissed) {
|
||||
this._completeRequest(dismissed);
|
||||
},
|
||||
|
||||
_reallyCompleteRequest: function(dismissed) {
|
||||
_completeRequest: function(dismissed) {
|
||||
this._currentDialog.close();
|
||||
this._currentDialog.destroySession();
|
||||
this._currentDialog = null;
|
||||
this._isCompleting = false;
|
||||
|
||||
this._native.complete(dismissed)
|
||||
this._native.complete(dismissed);
|
||||
},
|
||||
|
||||
_completeRequest: function(keepVisible, wasDismissed) {
|
||||
if (this._isCompleting)
|
||||
return;
|
||||
|
||||
this._isCompleting = true;
|
||||
|
||||
if (keepVisible) {
|
||||
// Give the user 2 seconds to read 'Authentication Failure' before
|
||||
// dismissing the dialog
|
||||
Mainloop.timeout_add(2000,
|
||||
Lang.bind(this,
|
||||
function() {
|
||||
this._reallyCompleteRequest(wasDismissed);
|
||||
return false;
|
||||
}));
|
||||
} else {
|
||||
this._reallyCompleteRequest(wasDismissed);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const Component = AuthenticationAgent;
|
||||
|
@ -1,62 +0,0 @@
|
||||
|
||||
const Lang = imports.lang;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const Gio = imports.gi.Gio;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const Recorder = new Lang.Class({
|
||||
Name: 'Recorder',
|
||||
|
||||
_init: function() {
|
||||
this._recorderSettings = new Gio.Settings({ schema: 'org.gnome.shell.recorder' });
|
||||
this._desktopLockdownSettings = new Gio.Settings({ schema: 'org.gnome.desktop.lockdown' });
|
||||
this._bindingSettings = new Gio.Settings({ schema: 'org.gnome.shell.keybindings' });
|
||||
this._recorder = null;
|
||||
},
|
||||
|
||||
enable: function() {
|
||||
Main.wm.addKeybinding('toggle-recording',
|
||||
this._bindingSettings,
|
||||
Meta.KeyBindingFlags.NONE,
|
||||
Main.KeybindingMode.NORMAL |
|
||||
Main.KeybindingMode.OVERVIEW,
|
||||
Lang.bind(this, this._toggleRecorder));
|
||||
},
|
||||
|
||||
disable: function() {
|
||||
Main.wm.removeKeybinding('toggle-recording');
|
||||
},
|
||||
|
||||
_ensureRecorder: function() {
|
||||
if (this._recorder == null)
|
||||
this._recorder = new Shell.Recorder({ stage: global.stage });
|
||||
return this._recorder;
|
||||
},
|
||||
|
||||
_toggleRecorder: function() {
|
||||
let recorder = this._ensureRecorder();
|
||||
if (recorder.is_recording()) {
|
||||
recorder.close();
|
||||
Meta.enable_unredirect_for_screen(global.screen);
|
||||
} else if (!this._desktopLockdownSettings.get_boolean('disable-save-to-disk')) {
|
||||
// read the parameters from GSettings always in case they have changed
|
||||
recorder.set_framerate(this._recorderSettings.get_int('framerate'));
|
||||
/* Translators: this is a filename used for screencast recording */
|
||||
// xgettext:no-c-format
|
||||
recorder.set_file_template(_("Screencast from %d %t") + '.' + this._recorderSettings.get_string('file-extension'));
|
||||
let pipeline = this._recorderSettings.get_string('pipeline');
|
||||
|
||||
if (!pipeline.match(/^\s*$/))
|
||||
recorder.set_pipeline(pipeline);
|
||||
else
|
||||
recorder.set_pipeline(null);
|
||||
|
||||
Meta.disable_unredirect_for_screen(global.screen);
|
||||
recorder.record();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const Component = Recorder;
|
@ -13,11 +13,12 @@ const Tp = imports.gi.TelepathyGLib;
|
||||
const History = imports.misc.history;
|
||||
const Main = imports.ui.main;
|
||||
const MessageTray = imports.ui.messageTray;
|
||||
const NotificationDaemon = imports.ui.notificationDaemon;
|
||||
const Params = imports.misc.params;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
|
||||
// See Notification.appendMessage
|
||||
const SCROLLBACK_IMMEDIATE_TIME = 60; // 1 minute
|
||||
const SCROLLBACK_IMMEDIATE_TIME = 3 * 60; // 3 minutes
|
||||
const SCROLLBACK_RECENT_TIME = 15 * 60; // 15 minutes
|
||||
const SCROLLBACK_RECENT_LENGTH = 20;
|
||||
const SCROLLBACK_IDLE_LENGTH = 5;
|
||||
@ -415,6 +416,8 @@ const TelepathyClient = new Lang.Class({
|
||||
_ensureAppSource: function() {
|
||||
if (this._appSource == null) {
|
||||
this._appSource = new MessageTray.Source(_("Chat"), 'empathy');
|
||||
this._appSource.policy = new NotificationDaemon.NotificationApplicationPolicy('empathy');
|
||||
|
||||
Main.messageTray.add(this._appSource);
|
||||
this._appSource.connect('destroy', Lang.bind(this, function () {
|
||||
this._appSource = null;
|
||||
@ -478,12 +481,16 @@ const ChatSource = new Lang.Class({
|
||||
}));
|
||||
item.connect('activate', Lang.bind(this, function() {
|
||||
this.setMuted(!this.isMuted);
|
||||
this.emit('done-displaying-content');
|
||||
this.emit('done-displaying-content', false);
|
||||
}));
|
||||
rightClickMenu.add(item.actor);
|
||||
return rightClickMenu;
|
||||
},
|
||||
|
||||
_createPolicy: function() {
|
||||
return new NotificationDaemon.NotificationApplicationPolicy('empathy');
|
||||
},
|
||||
|
||||
_updateAlias: function() {
|
||||
let oldAlias = this.title;
|
||||
let newAlias = this._contact.get_alias();
|
||||
@ -633,6 +640,10 @@ const ChatSource = new Lang.Class({
|
||||
return this._pendingMessages.length;
|
||||
},
|
||||
|
||||
get indicatorCount() {
|
||||
return this.count;
|
||||
},
|
||||
|
||||
get unseenCount() {
|
||||
return this.count;
|
||||
},
|
||||
@ -926,26 +937,24 @@ const ChatNotification = new Lang.Class({
|
||||
}
|
||||
// Show the word "Yesterday" and time if date is on yesterday
|
||||
else if(daysAgo <2){
|
||||
/* Translators: this is a time format string followed by the word "Yesterday". i.e. "14:30 on Yesterday"*/
|
||||
/* Translators: this is the word "Yesterday" followed by a time string. i.e. "Yesterday, 14:30"*/
|
||||
// xgettext:no-c-format
|
||||
format = _("<b>%H:%M</b> on Yesterday");
|
||||
format = _("<b>Yesterday</b>, <b>%H:%M</b>");
|
||||
}
|
||||
// Show a week day and time if date is in the last week
|
||||
else if (daysAgo < 7) {
|
||||
/* Translators: this is a time format string followed by a week day name. i.e. "14:30 on Monday*/
|
||||
/* Translators: this is the week day name followed by a time string. i.e. "Monday, 14:30*/
|
||||
// xgettext:no-c-format
|
||||
format = _("<b>%H:%M</b> on <b>%A</b>");
|
||||
format = _("<b>%A</b>, <b>%H:%M</b>");
|
||||
|
||||
} else if (date.getYear() == now.getYear()) {
|
||||
/* Translators: this is a time format in the style of "14:30 on Wednesday, May 25",
|
||||
shown when you get a chat message in the same year */
|
||||
/* Translators: this is the month name and day number followed by a time string. i.e. "May 25, 14:30"*/
|
||||
// xgettext:no-c-format
|
||||
format = _("<b>%H:%M</b> on <b>%A</b>, <b>%B</b> <b>%d</b>");
|
||||
format = _("<b>%B</b> <b>%d</b>, <b>%H:%M</b>");
|
||||
} else {
|
||||
/* Translators: this is a time format in the style of "14:30 on Wednesday, May 25, 2012",
|
||||
shown when you get a chat message in a different year */
|
||||
/* Translators: this is the month name, day number, year number followed by a time string. i.e. "May 25 2012, 14:30"*/
|
||||
// xgettext:no-c-format
|
||||
format = _("<b>%H:%M</b> on <b>%A</b>, <b>%B</b> <b>%d</b>, %Y");
|
||||
format = _("<b>%B</b> <b>%d</b> <b>%Y</b>, <b>%H:%M</b> ");
|
||||
}
|
||||
|
||||
return date.toLocaleFormat(format);
|
||||
@ -958,7 +967,8 @@ const ChatNotification = new Lang.Class({
|
||||
let timeLabel = this._append({ body: this._formatTimestamp(lastMessageDate),
|
||||
group: 'meta',
|
||||
styles: ['chat-meta-message'],
|
||||
childProps: { expand: true, x_fill: false },
|
||||
childProps: { expand: true, x_fill: false,
|
||||
x_align: St.Align.END },
|
||||
noTimestamp: true,
|
||||
timestamp: lastMessageTime });
|
||||
|
||||
@ -1050,6 +1060,10 @@ const ApproverSource = new Lang.Class({
|
||||
}));
|
||||
},
|
||||
|
||||
_createPolicy: function() {
|
||||
return new NotificationDaemon.NotificationApplicationPolicy('empathy');
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this._invalidId != 0) {
|
||||
this._dispatchOp.disconnect(this._invalidId);
|
||||
@ -1121,6 +1135,8 @@ const AudioVideoNotification = new Lang.Class({
|
||||
this.parent(source, title, null, { customContent: true });
|
||||
this.setResident(true);
|
||||
|
||||
this.setUrgency(MessageTray.Urgency.CRITICAL);
|
||||
|
||||
this.addButton('reject', _("Decline"));
|
||||
/* translators: this is a button label (verb), not a noun */
|
||||
this.addButton('answer', _("Answer"));
|
||||
@ -1348,9 +1364,8 @@ const AccountNotification = new Lang.Class({
|
||||
this.connect('action-invoked', Lang.bind(this, function(self, action) {
|
||||
switch (action) {
|
||||
case 'view':
|
||||
let cmd = '/usr/bin/empathy-accounts'
|
||||
+ ' --select-account=%s'
|
||||
.format(account.get_path_suffix());
|
||||
let cmd = 'empathy-accounts --select-account=' +
|
||||
account.get_path_suffix();
|
||||
let app_info = Gio.app_info_create_from_commandline(cmd, null, 0);
|
||||
app_info.launch([], global.create_app_launch_context());
|
||||
break;
|
||||
|
@ -1,15 +1,14 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Lang = imports.lang;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const AltTab = imports.ui.altTab;
|
||||
const Main = imports.ui.main;
|
||||
const SwitcherPopup = imports.ui.switcherPopup;
|
||||
const Params = imports.misc.params;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
@ -27,6 +26,9 @@ const CtrlAltTabManager = new Lang.Class({
|
||||
|
||||
_init: function() {
|
||||
this._items = [];
|
||||
this.addGroup(global.window_group, _("Windows"),
|
||||
'emblem-documents-symbolic', { sortGroup: SortGroup.TOP,
|
||||
focusCallback: Lang.bind(this, this._focusWindows) });
|
||||
},
|
||||
|
||||
addGroup: function(root, name, icon, params) {
|
||||
@ -40,11 +42,13 @@ const CtrlAltTabManager = new Lang.Class({
|
||||
|
||||
this._items.push(item);
|
||||
root.connect('destroy', Lang.bind(this, function() { this.removeGroup(root); }));
|
||||
global.focus_manager.add_group(root);
|
||||
if (root instanceof St.Widget)
|
||||
global.focus_manager.add_group(root);
|
||||
},
|
||||
|
||||
removeGroup: function(root) {
|
||||
global.focus_manager.remove_group(root);
|
||||
if (root instanceof St.Widget)
|
||||
global.focus_manager.remove_group(root);
|
||||
for (let i = 0; i < this._items.length; i++) {
|
||||
if (this._items[i].root == root) {
|
||||
this._items.splice(i, 1);
|
||||
@ -53,18 +57,11 @@ const CtrlAltTabManager = new Lang.Class({
|
||||
}
|
||||
},
|
||||
|
||||
focusGroup: function(item) {
|
||||
if (item.window) {
|
||||
Main.activateWindow(item.window);
|
||||
} else if (item.focusCallback) {
|
||||
item.focusCallback();
|
||||
} else {
|
||||
if (global.stage_input_mode == Shell.StageInputMode.NONREACTIVE ||
|
||||
global.stage_input_mode == Shell.StageInputMode.NORMAL)
|
||||
global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
|
||||
|
||||
focusGroup: function(item, timestamp) {
|
||||
if (item.focusCallback)
|
||||
item.focusCallback(timestamp);
|
||||
else
|
||||
item.root.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
}
|
||||
},
|
||||
|
||||
// Sort the items into a consistent order; panel first, tray last,
|
||||
@ -75,44 +72,45 @@ const CtrlAltTabManager = new Lang.Class({
|
||||
if (a.sortGroup != b.sortGroup)
|
||||
return a.sortGroup - b.sortGroup;
|
||||
|
||||
let y;
|
||||
if (a.x == undefined) {
|
||||
if (a.window)
|
||||
a.x = a.window.get_compositor_private().x;
|
||||
else
|
||||
[a.x, y] = a.proxy.get_transformed_position();
|
||||
}
|
||||
if (b.x == undefined) {
|
||||
if (b.window)
|
||||
b.x = b.window.get_compositor_private().x;
|
||||
else
|
||||
[b.x, y] = b.proxy.get_transformed_position();
|
||||
}
|
||||
let ax, bx, y;
|
||||
[ax, y] = a.proxy.get_transformed_position();
|
||||
[bx, y] = b.proxy.get_transformed_position();
|
||||
|
||||
return a.x - b.x;
|
||||
return ax - bx;
|
||||
},
|
||||
|
||||
popup: function(backwards, mask) {
|
||||
popup: function(backward, binding, mask) {
|
||||
// Start with the set of focus groups that are currently mapped
|
||||
let items = this._items.filter(function (item) { return item.proxy.mapped; });
|
||||
|
||||
// And add the windows metacity would show in its Ctrl-Alt-Tab list
|
||||
if (!Main.overview.visible) {
|
||||
if (Main.sessionMode.hasWindows && !Main.overview.visible) {
|
||||
let screen = global.screen;
|
||||
let display = screen.get_display();
|
||||
let windows = display.get_tab_list(Meta.TabList.DOCKS, screen, screen.get_active_workspace ());
|
||||
let windowTracker = Shell.WindowTracker.get_default();
|
||||
let textureCache = St.TextureCache.get_default();
|
||||
for (let i = 0; i < windows.length; i++) {
|
||||
let icon;
|
||||
let app = windowTracker.get_window_app(windows[i]);
|
||||
if (app)
|
||||
icon = app.create_icon_texture(POPUP_APPICON_SIZE);
|
||||
else
|
||||
icon = textureCache.bind_pixbuf_property(windows[i], 'icon');
|
||||
items.push({ window: windows[i],
|
||||
name: windows[i].title,
|
||||
let icon = null;
|
||||
let iconName = null;
|
||||
if (windows[i].get_window_type () == Meta.WindowType.DESKTOP) {
|
||||
iconName = 'video-display-symbolic';
|
||||
} else {
|
||||
let app = windowTracker.get_window_app(windows[i]);
|
||||
if (app)
|
||||
icon = app.create_icon_texture(POPUP_APPICON_SIZE);
|
||||
else
|
||||
icon = textureCache.bind_pixbuf_property(windows[i], 'icon');
|
||||
}
|
||||
|
||||
items.push({ name: windows[i].title,
|
||||
proxy: windows[i].get_compositor_private(),
|
||||
focusCallback: Lang.bind(windows[i],
|
||||
function(timestamp) {
|
||||
Main.activateWindow(this, timestamp);
|
||||
}),
|
||||
iconActor: icon,
|
||||
iconName: iconName,
|
||||
sortGroup: SortGroup.MIDDLE });
|
||||
}
|
||||
}
|
||||
@ -123,187 +121,61 @@ const CtrlAltTabManager = new Lang.Class({
|
||||
items.sort(Lang.bind(this, this._sortItems));
|
||||
|
||||
if (!this._popup) {
|
||||
this._popup = new CtrlAltTabPopup();
|
||||
this._popup.show(items, backwards, mask);
|
||||
this._popup = new CtrlAltTabPopup(items);
|
||||
this._popup.show(backward, binding, mask);
|
||||
|
||||
this._popup.actor.connect('destroy',
|
||||
Lang.bind(this, function() {
|
||||
this._popup = null;
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
_focusWindows: function(timestamp) {
|
||||
global.screen.focus_default_window(timestamp);
|
||||
}
|
||||
});
|
||||
|
||||
function mod(a, b) {
|
||||
return (a + b) % b;
|
||||
}
|
||||
|
||||
const CtrlAltTabPopup = new Lang.Class({
|
||||
Name: 'CtrlAltTabPopup',
|
||||
Extends: SwitcherPopup.SwitcherPopup,
|
||||
|
||||
_init : function() {
|
||||
this.actor = new Shell.GenericContainer({ name: 'ctrlAltTabPopup',
|
||||
reactive: true });
|
||||
|
||||
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, this._onDestroy));
|
||||
|
||||
this._haveModal = false;
|
||||
this._modifierMask = 0;
|
||||
this._selection = 0;
|
||||
|
||||
Main.uiGroup.add_actor(this.actor);
|
||||
_createSwitcher: function() {
|
||||
this._switcherList = new CtrlAltTabSwitcher(this._items);
|
||||
return true;
|
||||
},
|
||||
|
||||
_getPreferredWidth: function (actor, forHeight, alloc) {
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
|
||||
alloc.min_size = primary.width;
|
||||
alloc.natural_size = primary.width;
|
||||
},
|
||||
|
||||
_getPreferredHeight: function (actor, forWidth, alloc) {
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
|
||||
alloc.min_size = primary.height;
|
||||
alloc.natural_size = primary.height;
|
||||
},
|
||||
|
||||
_allocate: function (actor, box, flags) {
|
||||
let childBox = new Clutter.ActorBox();
|
||||
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();
|
||||
let hPadding = this.actor.get_theme_node().get_horizontal_padding();
|
||||
|
||||
let [childMinHeight, childNaturalHeight] = this._switcher.actor.get_preferred_height(primary.width - hPadding);
|
||||
let [childMinWidth, childNaturalWidth] = this._switcher.actor.get_preferred_width(childNaturalHeight);
|
||||
childBox.x1 = Math.max(primary.x + leftPadding, primary.x + Math.floor((primary.width - childNaturalWidth) / 2));
|
||||
childBox.x2 = Math.min(primary.x + primary.width - hPadding, childBox.x1 + childNaturalWidth);
|
||||
childBox.y1 = primary.y + Math.floor((primary.height - childNaturalHeight) / 2);
|
||||
childBox.y2 = childBox.y1 + childNaturalHeight;
|
||||
this._switcher.actor.allocate(childBox, flags);
|
||||
},
|
||||
|
||||
show : function(items, startBackwards, mask) {
|
||||
if (!Main.pushModal(this.actor))
|
||||
return false;
|
||||
this._haveModal = true;
|
||||
this._modifierMask = AltTab.primaryModifier(mask);
|
||||
|
||||
this._keyPressEventId = this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
|
||||
this._keyReleaseEventId = this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
|
||||
|
||||
this._items = items;
|
||||
this._switcher = new CtrlAltTabSwitcher(items);
|
||||
this.actor.add_actor(this._switcher.actor);
|
||||
|
||||
if (startBackwards)
|
||||
this._selection = this._items.length - 1;
|
||||
this._select(this._selection);
|
||||
|
||||
let [x, y, mods] = global.get_pointer();
|
||||
if (!(mods & this._modifierMask)) {
|
||||
this._finish();
|
||||
return false;
|
||||
_initialSelection: function(backward, binding) {
|
||||
if (binding == 'switch-panels') {
|
||||
if (backward)
|
||||
this._selectedIndex = this._items.length - 1;
|
||||
} else if (binding == 'switch-panels-backward') {
|
||||
if (!backward)
|
||||
this._selectedIndex = this._items.length - 1;
|
||||
}
|
||||
|
||||
this.actor.opacity = 0;
|
||||
this.actor.show();
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 255,
|
||||
time: POPUP_FADE_TIME,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
|
||||
return true;
|
||||
this._select(this._selectedIndex);
|
||||
},
|
||||
|
||||
_next : function() {
|
||||
return mod(this._selection + 1, this._items.length);
|
||||
},
|
||||
|
||||
_previous : function() {
|
||||
return mod(this._selection - 1, this._items.length);
|
||||
},
|
||||
|
||||
_keyPressEvent : function(actor, event) {
|
||||
let keysym = event.get_key_symbol();
|
||||
let shift = (event.get_state() & Clutter.ModifierType.SHIFT_MASK);
|
||||
if (shift && keysym == Clutter.KEY_Tab)
|
||||
keysym = Clutter.ISO_Left_Tab;
|
||||
|
||||
if (keysym == Clutter.KEY_Escape)
|
||||
this.destroy();
|
||||
else if (keysym == Clutter.KEY_Tab)
|
||||
this._select(this._next());
|
||||
else if (keysym == Clutter.KEY_ISO_Left_Tab)
|
||||
_keyPressHandler: function(keysym, backwards, action) {
|
||||
if (action == Meta.KeyBindingAction.SWITCH_PANELS)
|
||||
this._select(backwards ? this._previous() : this._next());
|
||||
else if (action == Meta.KeyBindingAction.SWITCH_PANELS_BACKWARD)
|
||||
this._select(backwards ? this._next() : this._previous());
|
||||
else if (keysym == Clutter.Left)
|
||||
this._select(this._previous());
|
||||
else if (keysym == Clutter.KEY_Left)
|
||||
this._select(this._previous());
|
||||
else if (keysym == Clutter.KEY_Right)
|
||||
else if (keysym == Clutter.Right)
|
||||
this._select(this._next());
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_keyReleaseEvent : function(actor, event) {
|
||||
let [x, y, mods] = global.get_pointer();
|
||||
let state = mods & this._modifierMask;
|
||||
|
||||
if (state == 0)
|
||||
this._finish();
|
||||
|
||||
return true;
|
||||
_finish : function(time) {
|
||||
this.parent(time);
|
||||
Main.ctrlAltTabManager.focusGroup(this._items[this._selectedIndex], time);
|
||||
},
|
||||
|
||||
_finish : function() {
|
||||
this.destroy();
|
||||
|
||||
Main.ctrlAltTabManager.focusGroup(this._items[this._selection]);
|
||||
},
|
||||
|
||||
_popModal: function() {
|
||||
if (this._haveModal) {
|
||||
Main.popModal(this.actor);
|
||||
this._haveModal = false;
|
||||
}
|
||||
},
|
||||
|
||||
destroy : function() {
|
||||
this._popModal();
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 0,
|
||||
time: POPUP_FADE_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this,
|
||||
function() {
|
||||
this.actor.destroy();
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
_onDestroy : function() {
|
||||
this._popModal();
|
||||
if (this._keyPressEventId)
|
||||
this.actor.disconnect(this._keyPressEventId);
|
||||
if (this._keyReleaseEventId)
|
||||
this.actor.disconnect(this._keyReleaseEventId);
|
||||
},
|
||||
|
||||
_select : function(num) {
|
||||
this._selection = num;
|
||||
this._switcher.highlight(num);
|
||||
}
|
||||
});
|
||||
|
||||
const CtrlAltTabSwitcher = new Lang.Class({
|
||||
Name: 'CtrlAltTabSwitcher',
|
||||
Extends: AltTab.SwitcherList,
|
||||
Extends: SwitcherPopup.SwitcherList,
|
||||
|
||||
_init : function(items) {
|
||||
this.parent(true);
|
||||
|
289
js/ui/dash.js
289
js/ui/dash.js
@ -22,11 +22,8 @@ const DASH_ITEM_LABEL_HIDE_TIME = 0.1;
|
||||
const DASH_ITEM_HOVER_TIMEOUT = 300;
|
||||
|
||||
function getAppFromSource(source) {
|
||||
if (source instanceof AppDisplay.AppWellIcon) {
|
||||
if (source instanceof AppDisplay.AppIcon) {
|
||||
return source.app;
|
||||
} else if (source.metaWindow) {
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
return tracker.get_window_app(source.metaWindow);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@ -36,30 +33,26 @@ function getAppFromSource(source) {
|
||||
// when requesting a size
|
||||
const DashItemContainer = new Lang.Class({
|
||||
Name: 'DashItemContainer',
|
||||
Extends: St.Widget,
|
||||
|
||||
_init: function() {
|
||||
this.actor = new Shell.GenericContainer({ style_class: 'dash-item-container' });
|
||||
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._delegate = this;
|
||||
this.parent({ style_class: 'dash-item-container' });
|
||||
|
||||
this._labelText = "";
|
||||
this.label = new St.Label({ style_class: 'dash-label'});
|
||||
this.label.hide();
|
||||
Main.layoutManager.addChrome(this.label);
|
||||
this.actor.label_actor = this.label;
|
||||
this.label_actor = this.label;
|
||||
|
||||
this.child = null;
|
||||
this._childScale = 1;
|
||||
this._childOpacity = 255;
|
||||
this._childScale = 0;
|
||||
this._childOpacity = 0;
|
||||
this.animatingOut = false;
|
||||
},
|
||||
|
||||
_allocate: function(actor, box, flags) {
|
||||
vfunc_allocate: function(box, flags) {
|
||||
this.set_allocation(box, flags);
|
||||
|
||||
if (this.child == null)
|
||||
return;
|
||||
|
||||
@ -81,28 +74,28 @@ const DashItemContainer = new Lang.Class({
|
||||
this.child.allocate(childBox, flags);
|
||||
},
|
||||
|
||||
_getPreferredHeight: function(actor, forWidth, alloc) {
|
||||
alloc.min_size = 0;
|
||||
alloc.natural_size = 0;
|
||||
vfunc_get_preferred_height: function(forWidth) {
|
||||
let themeNode = this.get_theme_node();
|
||||
|
||||
if (this.child == null)
|
||||
return;
|
||||
return [0, 0];
|
||||
|
||||
forWidth = themeNode.adjust_for_width(forWidth);
|
||||
let [minHeight, natHeight] = this.child.get_preferred_height(forWidth);
|
||||
alloc.min_size += minHeight * this.child.scale_y;
|
||||
alloc.natural_size += natHeight * this.child.scale_y;
|
||||
return themeNode.adjust_preferred_height(minHeight * this.child.scale_y,
|
||||
natHeight * this.child.scale_y);
|
||||
},
|
||||
|
||||
_getPreferredWidth: function(actor, forHeight, alloc) {
|
||||
alloc.min_size = 0;
|
||||
alloc.natural_size = 0;
|
||||
vfunc_get_preferred_width: function(forHeight) {
|
||||
let themeNode = this.get_theme_node();
|
||||
|
||||
if (this.child == null)
|
||||
return;
|
||||
return [0, 0];
|
||||
|
||||
forHeight = themeNode.adjust_for_height(forHeight);
|
||||
let [minWidth, natWidth] = this.child.get_preferred_width(forHeight);
|
||||
alloc.min_size = minWidth * this.child.scale_y;
|
||||
alloc.natural_size = natWidth * this.child.scale_y;
|
||||
return themeNode.adjust_preferred_width(minWidth * this.child.scale_y,
|
||||
natWidth * this.child.scale_y);
|
||||
},
|
||||
|
||||
showLabel: function() {
|
||||
@ -113,9 +106,9 @@ const DashItemContainer = new Lang.Class({
|
||||
this.label.opacity = 0;
|
||||
this.label.show();
|
||||
|
||||
let [stageX, stageY] = this.actor.get_transformed_position();
|
||||
let [stageX, stageY] = this.get_transformed_position();
|
||||
|
||||
let itemHeight = this.actor.allocation.y2 - this.actor.allocation.y1;
|
||||
let itemHeight = this.allocation.y2 - this.allocation.y1;
|
||||
|
||||
let labelHeight = this.label.get_height();
|
||||
let yOffset = Math.floor((itemHeight - labelHeight) / 2)
|
||||
@ -129,7 +122,7 @@ const DashItemContainer = new Lang.Class({
|
||||
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
|
||||
x = stageX - this.label.get_width() - xOffset;
|
||||
else
|
||||
x = stageX + this.actor.get_width() + xOffset;
|
||||
x = stageX + this.get_width() + xOffset;
|
||||
|
||||
this.label.set_position(x, y);
|
||||
Tweener.addTween(this.label,
|
||||
@ -159,22 +152,25 @@ const DashItemContainer = new Lang.Class({
|
||||
if (this.child == actor)
|
||||
return;
|
||||
|
||||
this.actor.destroy_all_children();
|
||||
this.destroy_all_children();
|
||||
|
||||
this.child = actor;
|
||||
this.actor.add_actor(this.child);
|
||||
this.add_actor(this.child);
|
||||
|
||||
this.child.set_scale_with_gravity(this._childScale, this._childScale,
|
||||
Clutter.Gravity.CENTER);
|
||||
this.child.set_opacity(this._childOpacity);
|
||||
},
|
||||
|
||||
animateIn: function() {
|
||||
show: function(animate) {
|
||||
if (this.child == null)
|
||||
return;
|
||||
|
||||
this.childScale = 0;
|
||||
this.childOpacity = 0;
|
||||
let time = animate ? DASH_ANIMATION_TIME : 0;
|
||||
Tweener.addTween(this,
|
||||
{ childScale: 1.0,
|
||||
childOpacity: 255,
|
||||
time: DASH_ANIMATION_TIME,
|
||||
time: time,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
},
|
||||
@ -183,7 +179,7 @@ const DashItemContainer = new Lang.Class({
|
||||
if (this.label)
|
||||
this.label.destroy();
|
||||
|
||||
this.actor.destroy();
|
||||
this.parent();
|
||||
},
|
||||
|
||||
animateOutAndDestroy: function() {
|
||||
@ -191,19 +187,18 @@ const DashItemContainer = new Lang.Class({
|
||||
this.label.destroy();
|
||||
|
||||
if (this.child == null) {
|
||||
this.actor.destroy();
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
this.animatingOut = true;
|
||||
this.childScale = 1.0;
|
||||
Tweener.addTween(this,
|
||||
{ childScale: 0.0,
|
||||
childOpacity: 0,
|
||||
time: DASH_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this, function() {
|
||||
this.actor.destroy();
|
||||
this.destroy();
|
||||
})
|
||||
});
|
||||
},
|
||||
@ -216,7 +211,7 @@ const DashItemContainer = new Lang.Class({
|
||||
|
||||
this.child.set_scale_with_gravity(scale, scale,
|
||||
Clutter.Gravity.CENTER);
|
||||
this.actor.queue_relayout();
|
||||
this.queue_relayout();
|
||||
},
|
||||
|
||||
get childScale() {
|
||||
@ -230,7 +225,7 @@ const DashItemContainer = new Lang.Class({
|
||||
return;
|
||||
|
||||
this.child.set_opacity(opacity);
|
||||
this.actor.queue_redraw();
|
||||
this.queue_redraw();
|
||||
},
|
||||
|
||||
get childOpacity() {
|
||||
@ -292,13 +287,7 @@ const ShowAppsIcon = new Lang.Class({
|
||||
},
|
||||
|
||||
handleDragOver: function(source, actor, x, y, time) {
|
||||
let app = getAppFromSource(source);
|
||||
if (app == null)
|
||||
return DND.DragMotionResult.NO_DROP;
|
||||
|
||||
let id = app.get_id();
|
||||
let isFavorite = AppFavorites.getAppFavorites().isFavorite(id);
|
||||
if (!isFavorite)
|
||||
if (!this._canRemoveApp(getAppFromSource(source)))
|
||||
return DND.DragMotionResult.NO_DROP;
|
||||
|
||||
return DND.DragMotionResult.MOVE_DROP;
|
||||
@ -306,7 +295,7 @@ const ShowAppsIcon = new Lang.Class({
|
||||
|
||||
acceptDrop: function(source, actor, x, y, time) {
|
||||
let app = getAppFromSource(source);
|
||||
if (app == null)
|
||||
if (!this._canRemoveApp(app))
|
||||
return false;
|
||||
|
||||
let id = app.get_id();
|
||||
@ -331,6 +320,16 @@ const DragPlaceholderItem = new Lang.Class({
|
||||
}
|
||||
});
|
||||
|
||||
const EmptyDropTargetItem = new Lang.Class({
|
||||
Name: 'EmptyDropTargetItem',
|
||||
Extends: DashItemContainer,
|
||||
|
||||
_init: function() {
|
||||
this.parent();
|
||||
this.setChild(new St.Bin({ style_class: 'empty-dash-drop-target' }));
|
||||
}
|
||||
});
|
||||
|
||||
const DashActor = new Lang.Class({
|
||||
Name: 'DashActor',
|
||||
Extends: St.Widget,
|
||||
@ -361,6 +360,23 @@ const DashActor = new Lang.Class({
|
||||
childBox.y1 = contentBox.y2 - showAppsNatHeight;
|
||||
childBox.y2 = contentBox.y2;
|
||||
showAppsButton.allocate(childBox, flags);
|
||||
},
|
||||
|
||||
vfunc_get_preferred_height: function(forWidth) {
|
||||
// We want to request the natural height of all our children
|
||||
// as our natural height, so we chain up to StWidget (which
|
||||
// then calls BoxLayout), but we only request the showApps
|
||||
// button as the minimum size
|
||||
|
||||
let [, natHeight] = this.parent(forWidth);
|
||||
|
||||
let themeNode = this.get_theme_node();
|
||||
let adjustedForWidth = themeNode.adjust_for_width(forWidth);
|
||||
let [, showAppsButton] = this.get_children();
|
||||
let [minHeight, ] = showAppsButton.get_preferred_height(adjustedForWidth);
|
||||
[minHeight, ] = themeNode.adjust_preferred_height(minHeight, natHeight);
|
||||
|
||||
return [minHeight, natHeight];
|
||||
}
|
||||
});
|
||||
|
||||
@ -386,12 +402,14 @@ const Dash = new Lang.Class({
|
||||
this._container.add_actor(this._box);
|
||||
|
||||
this._showAppsIcon = new ShowAppsIcon();
|
||||
this._showAppsIcon.childScale = 1;
|
||||
this._showAppsIcon.childOpacity = 255;
|
||||
this._showAppsIcon.icon.setIconSize(this.iconSize);
|
||||
this._hookUpLabel(this._showAppsIcon);
|
||||
|
||||
this.showAppsButton = this._showAppsIcon.toggleButton;
|
||||
|
||||
this._container.add_actor(this._showAppsIcon.actor);
|
||||
this._container.add_actor(this._showAppsIcon);
|
||||
|
||||
this.actor = new St.Bin({ child: this._container });
|
||||
this.actor.connect('notify::height', Lang.bind(this,
|
||||
@ -405,7 +423,10 @@ const Dash = new Lang.Class({
|
||||
|
||||
this._appSystem = Shell.AppSystem.get_default();
|
||||
|
||||
this._appSystem.connect('installed-changed', Lang.bind(this, this._queueRedisplay));
|
||||
this._appSystem.connect('installed-changed', Lang.bind(this, function() {
|
||||
AppFavorites.getAppFavorites().reload();
|
||||
this._queueRedisplay();
|
||||
}));
|
||||
AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._queueRedisplay));
|
||||
this._appSystem.connect('app-state-changed', Lang.bind(this, this._queueRedisplay));
|
||||
|
||||
@ -415,12 +436,10 @@ const Dash = new Lang.Class({
|
||||
Lang.bind(this, this._onDragEnd));
|
||||
Main.overview.connect('item-drag-cancelled',
|
||||
Lang.bind(this, this._onDragCancelled));
|
||||
Main.overview.connect('window-drag-begin',
|
||||
Lang.bind(this, this._onDragBegin));
|
||||
Main.overview.connect('window-drag-cancelled',
|
||||
Lang.bind(this, this._onDragCancelled));
|
||||
Main.overview.connect('window-drag-end',
|
||||
Lang.bind(this, this._onDragEnd));
|
||||
|
||||
// Translators: this is the name of the dock/favorites area on
|
||||
// the left of the overview
|
||||
Main.ctrlAltTabManager.addGroup(this.actor, _("Dash"), 'user-bookmarks-symbolic');
|
||||
},
|
||||
|
||||
_onDragBegin: function() {
|
||||
@ -429,6 +448,12 @@ const Dash = new Lang.Class({
|
||||
dragMotion: Lang.bind(this, this._onDragMotion)
|
||||
};
|
||||
DND.addDragMonitor(this._dragMonitor);
|
||||
|
||||
if (this._box.get_n_children() == 0) {
|
||||
this._emptyDropTarget = new EmptyDropTargetItem();
|
||||
this._box.insert_child_at_index(this._emptyDropTarget, 0);
|
||||
this._emptyDropTarget.show(true);
|
||||
}
|
||||
},
|
||||
|
||||
_onDragCancelled: function() {
|
||||
@ -445,6 +470,7 @@ const Dash = new Lang.Class({
|
||||
|
||||
_endDrag: function() {
|
||||
this._clearDragPlaceholder();
|
||||
this._clearEmptyDropTarget();
|
||||
this._showAppsIcon.setDragApp(null);
|
||||
DND.removeDragMonitor(this._dragMonitor);
|
||||
},
|
||||
@ -455,7 +481,7 @@ const Dash = new Lang.Class({
|
||||
return DND.DragMotionResult.CONTINUE;
|
||||
|
||||
let showAppsHovered =
|
||||
this._showAppsIcon.actor.contains(dragEvent.targetActor);
|
||||
this._showAppsIcon.contains(dragEvent.targetActor);
|
||||
|
||||
if (!this._box.contains(dragEvent.targetActor) || showAppsHovered)
|
||||
this._clearDragPlaceholder();
|
||||
@ -479,21 +505,27 @@ const Dash = new Lang.Class({
|
||||
Main.queueDeferredWork(this._workId);
|
||||
},
|
||||
|
||||
_hookUpLabel: function(item) {
|
||||
_hookUpLabel: function(item, appIcon) {
|
||||
item.child.connect('notify::hover', Lang.bind(this, function() {
|
||||
this._onHover(item);
|
||||
this._syncLabel(item, appIcon);
|
||||
}));
|
||||
|
||||
Main.overview.connect('hiding', Lang.bind(this, function() {
|
||||
this._labelShowing = false;
|
||||
item.hideLabel();
|
||||
}));
|
||||
|
||||
if (appIcon) {
|
||||
appIcon.connect('sync-tooltip', Lang.bind(this, function() {
|
||||
this._syncLabel(item, appIcon);
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
_createAppItem: function(app) {
|
||||
let appIcon = new AppDisplay.AppWellIcon(app,
|
||||
{ setSizeManually: true,
|
||||
showLabel: false });
|
||||
let appIcon = new AppDisplay.AppIcon(app,
|
||||
{ setSizeManually: true,
|
||||
showLabel: false });
|
||||
appIcon._draggable.connect('drag-begin',
|
||||
Lang.bind(this, function() {
|
||||
appIcon.actor.opacity = 50;
|
||||
@ -510,13 +542,13 @@ const Dash = new Lang.Class({
|
||||
let item = new DashItemContainer();
|
||||
item.setChild(appIcon.actor);
|
||||
|
||||
// Override default AppWellIcon label_actor, now the
|
||||
// Override default AppIcon label_actor, now the
|
||||
// accessible_name is set at DashItemContainer.setLabelText
|
||||
appIcon.actor.label_actor = null;
|
||||
item.setLabelText(app.get_name());
|
||||
|
||||
appIcon.icon.setIconSize(this.iconSize);
|
||||
this._hookUpLabel(item);
|
||||
this._hookUpLabel(item, appIcon);
|
||||
|
||||
return item;
|
||||
},
|
||||
@ -534,8 +566,10 @@ const Dash = new Lang.Class({
|
||||
}
|
||||
},
|
||||
|
||||
_onHover: function (item) {
|
||||
if (item.child.get_hover()) {
|
||||
_syncLabel: function (item, appIcon) {
|
||||
let shouldShow = appIcon ? appIcon.shouldShowTooltip() : item.child.get_hover();
|
||||
|
||||
if (shouldShow) {
|
||||
if (this._showLabelTimeoutId == 0) {
|
||||
let timeout = this._labelShowing ? 0 : DASH_ITEM_HOVER_TIMEOUT;
|
||||
this._showLabelTimeoutId = Mainloop.timeout_add(timeout,
|
||||
@ -570,13 +604,13 @@ const Dash = new Lang.Class({
|
||||
// animating out (which means they will be destroyed at the end of
|
||||
// the animation)
|
||||
let iconChildren = this._box.get_children().filter(function(actor) {
|
||||
return actor._delegate.child &&
|
||||
actor._delegate.child._delegate &&
|
||||
actor._delegate.child._delegate.icon &&
|
||||
!actor._delegate.animatingOut;
|
||||
return actor.child &&
|
||||
actor.child._delegate &&
|
||||
actor.child._delegate.icon &&
|
||||
!actor.animatingOut;
|
||||
});
|
||||
|
||||
iconChildren.push(this._showAppsIcon.actor);
|
||||
iconChildren.push(this._showAppsIcon);
|
||||
|
||||
if (this._maxHeight == -1)
|
||||
return;
|
||||
@ -589,23 +623,18 @@ const Dash = new Lang.Class({
|
||||
let availHeight = maxContent.y2 - maxContent.y1;
|
||||
let spacing = themeNode.get_length('spacing');
|
||||
|
||||
|
||||
let firstIcon = iconChildren[0]._delegate.child._delegate.icon;
|
||||
let firstButton = iconChildren[0].child;
|
||||
let firstIcon = firstButton._delegate.icon;
|
||||
|
||||
let minHeight, natHeight;
|
||||
|
||||
// Enforce the current icon size during the size request if
|
||||
// the icon is animating
|
||||
if (firstIcon._animating) {
|
||||
let [currentWidth, currentHeight] = firstIcon.icon.get_size();
|
||||
// Enforce the current icon size during the size request
|
||||
let [currentWidth, currentHeight] = firstIcon.icon.get_size();
|
||||
|
||||
firstIcon.icon.set_size(this.iconSize, this.iconSize);
|
||||
[minHeight, natHeight] = iconChildren[0].get_preferred_height(-1);
|
||||
firstIcon.icon.set_size(this.iconSize, this.iconSize);
|
||||
[minHeight, natHeight] = firstButton.get_preferred_height(-1);
|
||||
|
||||
firstIcon.icon.set_size(currentWidth, currentHeight);
|
||||
} else {
|
||||
[minHeight, natHeight] = iconChildren[0].get_preferred_height(-1);
|
||||
}
|
||||
firstIcon.icon.set_size(currentWidth, currentHeight);
|
||||
|
||||
// Subtract icon padding and box spacing from the available height
|
||||
availHeight -= iconChildren.length * (natHeight - this.iconSize) +
|
||||
@ -630,7 +659,7 @@ const Dash = new Lang.Class({
|
||||
|
||||
let scale = oldIconSize / newIconSize;
|
||||
for (let i = 0; i < iconChildren.length; i++) {
|
||||
let icon = iconChildren[i]._delegate.child._delegate.icon;
|
||||
let icon = iconChildren[i].child._delegate.icon;
|
||||
|
||||
// Set the new size immediately, to keep the icons' sizes
|
||||
// in sync with this.iconSize
|
||||
@ -650,15 +679,11 @@ const Dash = new Lang.Class({
|
||||
icon.icon.set_size(icon.icon.width * scale,
|
||||
icon.icon.height * scale);
|
||||
|
||||
icon._animating = true;
|
||||
Tweener.addTween(icon.icon,
|
||||
{ width: targetWidth,
|
||||
height: targetHeight,
|
||||
time: DASH_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: function() {
|
||||
icon._animating = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -669,13 +694,13 @@ const Dash = new Lang.Class({
|
||||
let running = this._appSystem.get_running();
|
||||
|
||||
let children = this._box.get_children().filter(function(actor) {
|
||||
return actor._delegate.child &&
|
||||
actor._delegate.child._delegate &&
|
||||
actor._delegate.child._delegate.app;
|
||||
return actor.child &&
|
||||
actor.child._delegate &&
|
||||
actor.child._delegate.app;
|
||||
});
|
||||
// Apps currently in the dash
|
||||
let oldApps = children.map(function(actor) {
|
||||
return actor._delegate.child._delegate.app;
|
||||
return actor.child._delegate.app;
|
||||
});
|
||||
// Apps supposed to be in the dash
|
||||
let newApps = [];
|
||||
@ -741,7 +766,7 @@ const Dash = new Lang.Class({
|
||||
let insertHere = newApps[newIndex + 1] &&
|
||||
newApps[newIndex + 1] == oldApps[oldIndex];
|
||||
let alreadyRemoved = removedActors.reduce(function(result, actor) {
|
||||
let removedApp = actor._delegate.child._delegate.app;
|
||||
let removedApp = actor.child._delegate.app;
|
||||
return result || removedApp == newApps[newIndex];
|
||||
}, false);
|
||||
|
||||
@ -758,11 +783,11 @@ const Dash = new Lang.Class({
|
||||
}
|
||||
|
||||
for (let i = 0; i < addedItems.length; i++)
|
||||
this._box.insert_child_at_index(addedItems[i].item.actor,
|
||||
this._box.insert_child_at_index(addedItems[i].item,
|
||||
addedItems[i].pos);
|
||||
|
||||
for (let i = 0; i < removedActors.length; i++) {
|
||||
let item = removedActors[i]._delegate;
|
||||
let item = removedActors[i];
|
||||
|
||||
// Don't animate item removal when the overview is transitioning
|
||||
// or hidden
|
||||
@ -776,25 +801,39 @@ const Dash = new Lang.Class({
|
||||
|
||||
// Skip animations on first run when adding the initial set
|
||||
// of items, to avoid all items zooming in at once
|
||||
if (!this._shownInitially) {
|
||||
|
||||
let animate = this._shownInitially && Main.overview.visible &&
|
||||
!Main.overview.animationInProgress;
|
||||
|
||||
if (!this._shownInitially)
|
||||
this._shownInitially = true;
|
||||
return;
|
||||
|
||||
for (let i = 0; i < addedItems.length; i++) {
|
||||
addedItems[i].item.show(animate);
|
||||
}
|
||||
|
||||
// Don't animate item addition when the overview is transitioning
|
||||
// or hidden
|
||||
if (!Main.overview.visible || Main.overview.animationInProgress)
|
||||
return;
|
||||
|
||||
for (let i = 0; i < addedItems.length; i++)
|
||||
addedItems[i].item.animateIn();
|
||||
// Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744
|
||||
// Without it, StBoxLayout may use a stale size cache
|
||||
this._box.queue_relayout();
|
||||
},
|
||||
|
||||
_clearDragPlaceholder: function() {
|
||||
if (this._dragPlaceholder) {
|
||||
this._animatingPlaceholdersCount++;
|
||||
this._dragPlaceholder.animateOutAndDestroy();
|
||||
this._dragPlaceholder.connect('destroy',
|
||||
Lang.bind(this, function() {
|
||||
this._animatingPlaceholdersCount--;
|
||||
}));
|
||||
this._dragPlaceholder = null;
|
||||
this._dragPlaceholderPos = -1;
|
||||
}
|
||||
this._dragPlaceholderPos = -1;
|
||||
},
|
||||
|
||||
_clearEmptyDropTarget: function() {
|
||||
if (this._emptyDropTarget) {
|
||||
this._emptyDropTarget.animateOutAndDestroy();
|
||||
this._emptyDropTarget = null;
|
||||
}
|
||||
},
|
||||
|
||||
@ -818,27 +857,22 @@ const Dash = new Lang.Class({
|
||||
// the remove target has the same size as "normal" items, we don't
|
||||
// need to do the same adjustment there.
|
||||
if (this._dragPlaceholder) {
|
||||
boxHeight -= this._dragPlaceholder.actor.height;
|
||||
boxHeight -= this._dragPlaceholder.height;
|
||||
numChildren--;
|
||||
}
|
||||
|
||||
let pos = Math.floor(y * numChildren / boxHeight);
|
||||
let pos;
|
||||
if (!this._emptyDropTarget)
|
||||
pos = Math.floor(y * numChildren / boxHeight);
|
||||
else
|
||||
pos = 0; // always insert at the top when dash is empty
|
||||
|
||||
if (pos != this._dragPlaceholderPos && pos <= numFavorites && this._animatingPlaceholdersCount == 0) {
|
||||
this._dragPlaceholderPos = pos;
|
||||
|
||||
// Don't allow positioning before or after self
|
||||
if (favPos != -1 && (pos == favPos || pos == favPos + 1)) {
|
||||
if (this._dragPlaceholder) {
|
||||
this._dragPlaceholder.animateOutAndDestroy();
|
||||
this._animatingPlaceholdersCount++;
|
||||
this._dragPlaceholder.actor.connect('destroy',
|
||||
Lang.bind(this, function() {
|
||||
this._animatingPlaceholdersCount--;
|
||||
}));
|
||||
}
|
||||
this._dragPlaceholder = null;
|
||||
|
||||
this._clearDragPlaceholder();
|
||||
return DND.DragMotionResult.CONTINUE;
|
||||
}
|
||||
|
||||
@ -847,7 +881,7 @@ const Dash = new Lang.Class({
|
||||
// an animation
|
||||
let fadeIn;
|
||||
if (this._dragPlaceholder) {
|
||||
this._dragPlaceholder.actor.destroy();
|
||||
this._dragPlaceholder.destroy();
|
||||
fadeIn = false;
|
||||
} else {
|
||||
fadeIn = true;
|
||||
@ -856,17 +890,16 @@ const Dash = new Lang.Class({
|
||||
this._dragPlaceholder = new DragPlaceholderItem();
|
||||
this._dragPlaceholder.child.set_width (this.iconSize);
|
||||
this._dragPlaceholder.child.set_height (this.iconSize / 2);
|
||||
this._box.insert_child_at_index(this._dragPlaceholder.actor,
|
||||
this._box.insert_child_at_index(this._dragPlaceholder,
|
||||
this._dragPlaceholderPos);
|
||||
if (fadeIn)
|
||||
this._dragPlaceholder.animateIn();
|
||||
this._dragPlaceholder.show(fadeIn);
|
||||
}
|
||||
|
||||
// Remove the drag placeholder if we are not in the
|
||||
// "favorites zone"
|
||||
if (pos > numFavorites && this._dragPlaceholder) {
|
||||
if (pos > numFavorites)
|
||||
this._clearDragPlaceholder();
|
||||
}
|
||||
|
||||
if (!this._dragPlaceholder)
|
||||
return DND.DragMotionResult.NO_DROP;
|
||||
|
||||
@ -897,10 +930,10 @@ const Dash = new Lang.Class({
|
||||
let children = this._box.get_children();
|
||||
for (let i = 0; i < this._dragPlaceholderPos; i++) {
|
||||
if (this._dragPlaceholder &&
|
||||
children[i] == this._dragPlaceholder.actor)
|
||||
children[i] == this._dragPlaceholder)
|
||||
continue;
|
||||
|
||||
let childId = children[i]._delegate.child._delegate.app.get_id();
|
||||
let childId = children[i].child._delegate.app.get_id();
|
||||
if (childId == id)
|
||||
continue;
|
||||
if (childId in favorites)
|
||||
|
@ -32,6 +32,7 @@ function _onVertSepRepaint (area)
|
||||
cr.setDash([1, 3], 1); // Hard-code for now
|
||||
cr.setLineWidth(stippleWidth);
|
||||
cr.stroke();
|
||||
cr.$dispose();
|
||||
};
|
||||
|
||||
const DateMenuButton = new Lang.Class({
|
||||
@ -48,16 +49,13 @@ const DateMenuButton = new Lang.Class({
|
||||
menuAlignment = 1.0 - menuAlignment;
|
||||
this.parent(menuAlignment);
|
||||
|
||||
// At this moment calendar menu is not keyboard navigable at
|
||||
// all (so not accessible), so it doesn't make sense to set as
|
||||
// role ATK_ROLE_MENU like other elements of the panel.
|
||||
this.actor.accessible_role = Atk.Role.LABEL;
|
||||
|
||||
this._clockDisplay = new St.Label();
|
||||
this._clockDisplay = new St.Label({ y_align: Clutter.ActorAlign.CENTER });
|
||||
this.actor.label_actor = this._clockDisplay;
|
||||
this.actor.add_actor(this._clockDisplay);
|
||||
this.actor.add_style_class_name ('clock-display');
|
||||
|
||||
hbox = new St.BoxLayout({name: 'calendarArea' });
|
||||
this.menu.addActor(hbox);
|
||||
hbox = new St.BoxLayout({ name: 'calendarArea' });
|
||||
this.menu.box.add_child(hbox);
|
||||
|
||||
// Fill up the first column
|
||||
|
||||
@ -65,9 +63,8 @@ const DateMenuButton = new Lang.Class({
|
||||
hbox.add(vbox);
|
||||
|
||||
// Date
|
||||
this._date = new St.Label();
|
||||
this.actor.label_actor = this._clockDisplay;
|
||||
this._date.style_class = 'datemenu-date-label';
|
||||
this._date = new St.Label({ style_class: 'datemenu-date-label',
|
||||
can_focus: true });
|
||||
vbox.add(this._date);
|
||||
|
||||
this._eventList = new Calendar.EventsList();
|
||||
@ -83,14 +80,23 @@ const DateMenuButton = new Lang.Class({
|
||||
}));
|
||||
vbox.add(this._calendar.actor);
|
||||
|
||||
item = this.menu.addSettingsAction(_("Date and Time Settings"), 'gnome-datetime-panel.desktop');
|
||||
if (item) {
|
||||
let separator = new PopupMenu.PopupSeparatorMenuItem();
|
||||
separator.setColumnWidths(1);
|
||||
vbox.add(separator.actor, {y_align: St.Align.END, expand: true, y_fill: false});
|
||||
let separator = new PopupMenu.PopupSeparatorMenuItem();
|
||||
vbox.add(separator.actor, { y_align: St.Align.END, expand: true, y_fill: false });
|
||||
|
||||
this._openCalendarItem = new PopupMenu.PopupMenuItem(_("Open Calendar"));
|
||||
this._openCalendarItem.connect('activate', Lang.bind(this, this._onOpenCalendarActivate));
|
||||
vbox.add(this._openCalendarItem.actor, {y_align: St.Align.END, expand: true, y_fill: false});
|
||||
|
||||
this._openClocksItem = new PopupMenu.PopupMenuItem(_("Open Clocks"));
|
||||
this._openClocksItem.connect('activate', Lang.bind(this, this._onOpenClocksActivate));
|
||||
vbox.add(this._openClocksItem.actor, {y_align: St.Align.END, expand: true, y_fill: false});
|
||||
|
||||
Shell.AppSystem.get_default().connect('installed-changed',
|
||||
Lang.bind(this, this._appInstalledChanged));
|
||||
|
||||
item = this.menu.addSettingsAction(_("Date & Time Settings"), 'gnome-datetime-panel.desktop');
|
||||
if (item) {
|
||||
item.actor.show_on_set_parent = false;
|
||||
item.actor.can_focus = false;
|
||||
item.actor.reparent(vbox);
|
||||
this._dateAndTimeSeparator = separator;
|
||||
}
|
||||
@ -101,22 +107,7 @@ const DateMenuButton = new Lang.Class({
|
||||
hbox.add(this._separator);
|
||||
|
||||
// Fill up the second column
|
||||
vbox = new St.BoxLayout({ name: 'calendarEventsArea',
|
||||
vertical: true });
|
||||
hbox.add(vbox, { expand: true });
|
||||
|
||||
// Event list
|
||||
vbox.add(this._eventList.actor, { expand: true });
|
||||
|
||||
this._openCalendarItem = new PopupMenu.PopupMenuItem(_("Open Calendar"));
|
||||
this._openCalendarItem.connect('activate', Lang.bind(this, this._onOpenCalendarActivate));
|
||||
this._openCalendarItem.actor.can_focus = false;
|
||||
vbox.add(this._openCalendarItem.actor, {y_align: St.Align.END, expand: true, y_fill: false});
|
||||
|
||||
this._calendarSettings = new Gio.Settings({ schema: 'org.gnome.desktop.default-applications.office.calendar' });
|
||||
this._calendarSettings.connect('changed::exec',
|
||||
Lang.bind(this, this._calendarSettingsChanged));
|
||||
this._calendarSettingsChanged();
|
||||
hbox.add(this._eventList.actor, { expand: true, y_fill: false, y_align: St.Align.START });
|
||||
|
||||
// Whenever the menu is opened, select today
|
||||
this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) {
|
||||
@ -151,30 +142,40 @@ const DateMenuButton = new Lang.Class({
|
||||
this._sessionUpdated();
|
||||
},
|
||||
|
||||
_calendarSettingsChanged: function() {
|
||||
let exec = this._calendarSettings.get_string('exec');
|
||||
let fullExec = GLib.find_program_in_path(exec);
|
||||
this._openCalendarItem.actor.visible = fullExec != null;
|
||||
_appInstalledChanged: function() {
|
||||
this._calendarApp = undefined;
|
||||
this._updateEventsVisibility();
|
||||
},
|
||||
|
||||
_setEventsVisibility: function(visible) {
|
||||
this._openCalendarItem.actor.visible = visible;
|
||||
_updateEventsVisibility: function() {
|
||||
let visible = this._eventSource.hasCalendars;
|
||||
this._openCalendarItem.actor.visible = visible &&
|
||||
(this._getCalendarApp() != null);
|
||||
this._openClocksItem.actor.visible = visible &&
|
||||
(this._getClockApp() != null);
|
||||
this._separator.visible = visible;
|
||||
this._eventList.actor.visible = visible;
|
||||
if (visible) {
|
||||
let alignment = 0.25;
|
||||
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
|
||||
alignment = 1.0 - alignment;
|
||||
this.menu._arrowAlignment = alignment;
|
||||
this._eventList.actor.get_parent().show();
|
||||
let alignment = 0.25;
|
||||
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
|
||||
alignment = 1.0 - alignment;
|
||||
this.menu._arrowAlignment = alignment;
|
||||
} else {
|
||||
this.menu._arrowAlignment = 0.5;
|
||||
this._eventList.actor.get_parent().hide();
|
||||
this.menu._arrowAlignment = 0.5;
|
||||
}
|
||||
},
|
||||
|
||||
_setEventSource: function(eventSource) {
|
||||
if (this._eventSource)
|
||||
this._eventSource.destroy();
|
||||
|
||||
this._calendar.setEventSource(eventSource);
|
||||
this._eventList.setEventSource(eventSource);
|
||||
|
||||
this._eventSource = eventSource;
|
||||
this._eventSource.connect('notify::has-calendars', Lang.bind(this, function() {
|
||||
this._updateEventsVisibility();
|
||||
}));
|
||||
},
|
||||
|
||||
_sessionUpdated: function() {
|
||||
@ -183,10 +184,10 @@ const DateMenuButton = new Lang.Class({
|
||||
if (showEvents) {
|
||||
eventSource = new Calendar.DBusEventSource();
|
||||
} else {
|
||||
eventSource = null;
|
||||
eventSource = new Calendar.EmptyEventSource();
|
||||
}
|
||||
this._setEventSource(eventSource);
|
||||
this._setEventsVisibility(showEvents);
|
||||
this._updateEventsVisibility();
|
||||
|
||||
// This needs to be handled manually, as the code to
|
||||
// autohide separators doesn't work across the vbox
|
||||
@ -203,26 +204,34 @@ const DateMenuButton = new Lang.Class({
|
||||
this._date.set_text(displayDate.toLocaleFormat(dateFormat));
|
||||
},
|
||||
|
||||
_getCalendarApp: function() {
|
||||
if (this._calendarApp !== undefined)
|
||||
return this._calendarApp;
|
||||
|
||||
let apps = Gio.AppInfo.get_recommended_for_type('text/calendar');
|
||||
if (apps && (apps.length > 0))
|
||||
this._calendarApp = apps[0];
|
||||
else
|
||||
this._calendarApp = null;
|
||||
return this._calendarApp;
|
||||
},
|
||||
|
||||
_getClockApp: function() {
|
||||
return Shell.AppSystem.get_default().lookup_app('gnome-clocks.desktop');
|
||||
},
|
||||
|
||||
_onOpenCalendarActivate: function() {
|
||||
this.menu.close();
|
||||
let tool = this._calendarSettings.get_string('exec');
|
||||
if (tool.length == 0 || tool.substr(0, 9) == 'evolution') {
|
||||
// TODO: pass the selected day
|
||||
let app = Shell.AppSystem.get_default().lookup_app('evolution-calendar.desktop');
|
||||
app.activate();
|
||||
} else {
|
||||
let needTerm = this._calendarSettings.get_boolean('needs-term');
|
||||
if (needTerm) {
|
||||
let terminalSettings = new Gio.Settings({ schema: 'org.gnome.desktop.default-applications.terminal' });
|
||||
let term = terminalSettings.get_string('exec');
|
||||
let arg = terminalSettings.get_string('exec-arg');
|
||||
if (arg != '')
|
||||
Util.spawn([term, arg, tool]);
|
||||
else
|
||||
Util.spawn([term, tool]);
|
||||
} else {
|
||||
Util.spawnCommandLine(tool)
|
||||
}
|
||||
}
|
||||
|
||||
let app = this._getCalendarApp();
|
||||
if (app.get_id() == 'evolution.desktop')
|
||||
app = Gio.DesktopAppInfo.new('evolution-calendar.desktop');
|
||||
app.launch([], global.create_app_launch_context());
|
||||
},
|
||||
|
||||
_onOpenClocksActivate: function() {
|
||||
this.menu.close();
|
||||
let app = this._getClockApp();
|
||||
app.activate();
|
||||
}
|
||||
});
|
||||
|
215
js/ui/dnd.js
215
js/ui/dnd.js
@ -1,9 +1,11 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const St = imports.gi.St;
|
||||
const Lang = imports.lang;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const Tweener = imports.ui.tweener;
|
||||
@ -26,9 +28,9 @@ const DragMotionResult = {
|
||||
};
|
||||
|
||||
const DRAG_CURSOR_MAP = {
|
||||
0: Shell.Cursor.DND_UNSUPPORTED_TARGET,
|
||||
1: Shell.Cursor.DND_COPY,
|
||||
2: Shell.Cursor.DND_MOVE
|
||||
0: Meta.Cursor.DND_UNSUPPORTED_TARGET,
|
||||
1: Meta.Cursor.DND_COPY,
|
||||
2: Meta.Cursor.DND_MOVE
|
||||
};
|
||||
|
||||
const DragDropResult = {
|
||||
@ -43,9 +45,7 @@ let dragMonitors = [];
|
||||
|
||||
function _getEventHandlerActor() {
|
||||
if (!eventHandlerActor) {
|
||||
eventHandlerActor = new Clutter.Rectangle();
|
||||
eventHandlerActor.width = 0;
|
||||
eventHandlerActor.height = 0;
|
||||
eventHandlerActor = new Clutter.Actor({ width: 0, height: 0 });
|
||||
Main.uiGroup.add_actor(eventHandlerActor);
|
||||
// We connect to 'event' rather than 'captured-event' because the capturing phase doesn't happen
|
||||
// when you've grabbed the pointer.
|
||||
@ -85,11 +85,8 @@ const _Draggable = new Lang.Class({
|
||||
|
||||
this.actor.connect('destroy', Lang.bind(this, function() {
|
||||
this._actorDestroyed = true;
|
||||
// If the drag actor is destroyed and we were going to fix
|
||||
// up its hover state, fix up the parent hover state instead
|
||||
if (this.actor == this._firstLeaveActor)
|
||||
this._firstLeaveActor = this._dragOrigParent;
|
||||
if (this._dragInProgress)
|
||||
|
||||
if (this._dragInProgress && this._dragCancellable)
|
||||
this._cancelDrag(global.get_current_time());
|
||||
this.disconnectAll();
|
||||
}));
|
||||
@ -102,12 +99,7 @@ const _Draggable = new Lang.Class({
|
||||
this._buttonDown = false; // The mouse button has been pressed and has not yet been released.
|
||||
this._dragInProgress = false; // The drag has been started, and has not been dropped or cancelled yet.
|
||||
this._animationInProgress = false; // The drag is over and the item is in the process of animating to its original position (snapping back or reverting).
|
||||
|
||||
// During the drag, we eat enter/leave events so that actors don't prelight.
|
||||
// But we remember the actors that we first left/last entered so we can
|
||||
// fix up the hover state after the drag ends.
|
||||
this._firstLeaveActor = null;
|
||||
this._lastEnterActor = null;
|
||||
this._dragCancellable = true;
|
||||
|
||||
this._eventsGrabbed = false;
|
||||
},
|
||||
@ -136,25 +128,26 @@ const _Draggable = new Lang.Class({
|
||||
},
|
||||
|
||||
_ungrabActor: function() {
|
||||
Clutter.ungrab_pointer();
|
||||
if (!this._onEventId)
|
||||
return;
|
||||
|
||||
Clutter.ungrab_pointer();
|
||||
this.actor.disconnect(this._onEventId);
|
||||
this._onEventId = null;
|
||||
},
|
||||
|
||||
_grabEvents: function() {
|
||||
if (!this._eventsGrabbed) {
|
||||
Clutter.grab_pointer(_getEventHandlerActor());
|
||||
Clutter.grab_keyboard(_getEventHandlerActor());
|
||||
this._eventsGrabbed = true;
|
||||
this._eventsGrabbed = Main.pushModal(_getEventHandlerActor());
|
||||
if (this._eventsGrabbed)
|
||||
Clutter.grab_pointer(_getEventHandlerActor());
|
||||
}
|
||||
},
|
||||
|
||||
_ungrabEvents: function() {
|
||||
if (this._eventsGrabbed) {
|
||||
Clutter.ungrab_pointer();
|
||||
Clutter.ungrab_keyboard();
|
||||
Main.popModal(_getEventHandlerActor());
|
||||
this._eventsGrabbed = false;
|
||||
}
|
||||
},
|
||||
@ -193,16 +186,24 @@ const _Draggable = new Lang.Class({
|
||||
this._cancelDrag(event.get_time());
|
||||
return true;
|
||||
}
|
||||
} else if (event.type() == Clutter.EventType.LEAVE) {
|
||||
if (this._firstLeaveActor == null)
|
||||
this._firstLeaveActor = event.get_source();
|
||||
} else if (event.type() == Clutter.EventType.ENTER) {
|
||||
this._lastEnterActor = event.get_source();
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* fakeRelease:
|
||||
*
|
||||
* Fake a release event.
|
||||
* Must be called if you want to intercept release events on draggable
|
||||
* actors for other purposes (for example if you're using
|
||||
* PopupMenu.ignoreRelease())
|
||||
*/
|
||||
fakeRelease: function() {
|
||||
this._buttonDown = false;
|
||||
this._ungrabActor();
|
||||
},
|
||||
|
||||
/**
|
||||
* startDrag:
|
||||
* @stageX: X coordinate of event
|
||||
@ -228,13 +229,13 @@ const _Draggable = new Lang.Class({
|
||||
if (this._onEventId)
|
||||
this._ungrabActor();
|
||||
this._grabEvents();
|
||||
global.set_cursor(Shell.Cursor.DND_IN_DRAG);
|
||||
global.screen.set_cursor(Meta.Cursor.DND_IN_DRAG);
|
||||
|
||||
this._dragX = this._dragStartX = stageX;
|
||||
this._dragY = this._dragStartY = stageY;
|
||||
|
||||
if (this.actor._delegate && this.actor._delegate.getDragActor) {
|
||||
this._dragActor = this.actor._delegate.getDragActor(this._dragStartX, this._dragStartY);
|
||||
this._dragActor = this.actor._delegate.getDragActor();
|
||||
this._dragActor.reparent(Main.uiGroup);
|
||||
this._dragActor.raise_top();
|
||||
Shell.util_set_hidden_from_pick(this._dragActor, true);
|
||||
@ -274,19 +275,19 @@ const _Draggable = new Lang.Class({
|
||||
this._dragOrigY = this._dragActor.y;
|
||||
this._dragOrigScale = this._dragActor.scale_x;
|
||||
|
||||
this._dragActor.reparent(Main.uiGroup);
|
||||
this._dragActor.raise_top();
|
||||
Shell.util_set_hidden_from_pick(this._dragActor, true);
|
||||
// Set the actor's scale such that it will keep the same
|
||||
// transformed size when it's reparented to the uiGroup
|
||||
let [scaledWidth, scaledHeight] = this.actor.get_transformed_size();
|
||||
this._dragActor.set_scale(scaledWidth / this.actor.width,
|
||||
scaledHeight / this.actor.height);
|
||||
|
||||
let [actorStageX, actorStageY] = this.actor.get_transformed_position();
|
||||
this._dragOffsetX = actorStageX - this._dragStartX;
|
||||
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 uiGroup
|
||||
let [scaledWidth, scaledHeight] = this.actor.get_transformed_size();
|
||||
this.actor.set_scale(scaledWidth / this.actor.width,
|
||||
scaledHeight / this.actor.height);
|
||||
this._dragActor.reparent(Main.uiGroup);
|
||||
this._dragActor.raise_top();
|
||||
Shell.util_set_hidden_from_pick(this._dragActor, true);
|
||||
}
|
||||
|
||||
this._dragOrigOpacity = this._dragActor.opacity;
|
||||
@ -343,60 +344,65 @@ const _Draggable = new Lang.Class({
|
||||
return true;
|
||||
},
|
||||
|
||||
_updateDragHover : function () {
|
||||
let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
|
||||
this._dragX, this._dragY);
|
||||
let dragEvent = {
|
||||
x: this._dragX,
|
||||
y: this._dragY,
|
||||
dragActor: this._dragActor,
|
||||
source: this.actor._delegate,
|
||||
targetActor: target
|
||||
};
|
||||
for (let i = 0; i < dragMonitors.length; i++) {
|
||||
let motionFunc = dragMonitors[i].dragMotion;
|
||||
if (motionFunc) {
|
||||
let result = motionFunc(dragEvent);
|
||||
if (result != DragMotionResult.CONTINUE) {
|
||||
global.screen.set_cursor(DRAG_CURSOR_MAP[result]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (target) {
|
||||
if (target._delegate && target._delegate.handleDragOver) {
|
||||
let [r, targX, targY] = target.transform_stage_point(this._dragX, this._dragY);
|
||||
// We currently loop through all parents on drag-over even if one of the children has handled it.
|
||||
// We can check the return value of the function and break the loop if it's true if we don't want
|
||||
// to continue checking the parents.
|
||||
let result = target._delegate.handleDragOver(this.actor._delegate,
|
||||
this._dragActor,
|
||||
targX,
|
||||
targY,
|
||||
0);
|
||||
if (result != DragMotionResult.CONTINUE) {
|
||||
global.screen.set_cursor(DRAG_CURSOR_MAP[result]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
target = target.get_parent();
|
||||
}
|
||||
global.screen.set_cursor(Meta.Cursor.DND_IN_DRAG);
|
||||
return false;
|
||||
},
|
||||
|
||||
_queueUpdateDragHover: function() {
|
||||
if (this._updateHoverId)
|
||||
GLib.source_remove(this._updateHoverId);
|
||||
|
||||
this._updateHoverId = GLib.idle_add(GLib.PRIORITY_DEFAULT,
|
||||
Lang.bind(this, this._updateDragHover));
|
||||
},
|
||||
|
||||
_updateDragPosition : function (event) {
|
||||
let [stageX, stageY] = event.get_coords();
|
||||
this._dragX = stageX;
|
||||
this._dragY = stageY;
|
||||
this._dragActor.set_position(stageX + this._dragOffsetX,
|
||||
stageY + this._dragOffsetY);
|
||||
|
||||
// If we are dragging, update the position
|
||||
if (this._dragActor) {
|
||||
this._dragActor.set_position(stageX + this._dragOffsetX,
|
||||
stageY + this._dragOffsetY);
|
||||
|
||||
let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
|
||||
stageX, stageY);
|
||||
|
||||
// We call observers only once per motion with the innermost
|
||||
// target actor. If necessary, the observer can walk the
|
||||
// parent itself.
|
||||
let dragEvent = {
|
||||
x: stageX,
|
||||
y: stageY,
|
||||
dragActor: this._dragActor,
|
||||
source: this.actor._delegate,
|
||||
targetActor: target
|
||||
};
|
||||
for (let i = 0; i < dragMonitors.length; i++) {
|
||||
let motionFunc = dragMonitors[i].dragMotion;
|
||||
if (motionFunc) {
|
||||
let result = motionFunc(dragEvent);
|
||||
if (result != DragMotionResult.CONTINUE) {
|
||||
global.set_cursor(DRAG_CURSOR_MAP[result]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (target) {
|
||||
if (target._delegate && target._delegate.handleDragOver) {
|
||||
let [r, targX, targY] = target.transform_stage_point(stageX, stageY);
|
||||
// We currently loop through all parents on drag-over even if one of the children has handled it.
|
||||
// We can check the return value of the function and break the loop if it's true if we don't want
|
||||
// to continue checking the parents.
|
||||
let result = target._delegate.handleDragOver(this.actor._delegate,
|
||||
this._dragActor,
|
||||
targX,
|
||||
targY,
|
||||
event.get_time());
|
||||
if (result != DragMotionResult.CONTINUE) {
|
||||
global.set_cursor(DRAG_CURSOR_MAP[result]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
target = target.get_parent();
|
||||
}
|
||||
global.set_cursor(Shell.Cursor.DND_IN_DRAG);
|
||||
}
|
||||
|
||||
this._queueUpdateDragHover();
|
||||
return true;
|
||||
},
|
||||
|
||||
@ -425,6 +431,11 @@ const _Draggable = new Lang.Class({
|
||||
}
|
||||
}
|
||||
|
||||
// At this point it is too late to cancel a drag by destroying
|
||||
// the actor, the fate of which is decided by acceptDrop and its
|
||||
// side-effects
|
||||
this._dragCancellable = false;
|
||||
|
||||
while (target) {
|
||||
if (target._delegate && target._delegate.acceptDrop) {
|
||||
let [r, targX, targY] = target.transform_stage_point(dropX, dropY);
|
||||
@ -433,8 +444,6 @@ const _Draggable = new Lang.Class({
|
||||
targX,
|
||||
targY,
|
||||
event.get_time())) {
|
||||
if (this._actorDestroyed)
|
||||
return true;
|
||||
// If it accepted the drop without taking the actor,
|
||||
// handle it ourselves.
|
||||
if (this._dragActor.get_parent() == Main.uiGroup) {
|
||||
@ -446,7 +455,7 @@ const _Draggable = new Lang.Class({
|
||||
}
|
||||
|
||||
this._dragInProgress = false;
|
||||
global.unset_cursor();
|
||||
global.screen.set_cursor(Meta.Cursor.DEFAULT);
|
||||
this.emit('drag-end', event.get_time(), true);
|
||||
this._dragComplete();
|
||||
return true;
|
||||
@ -498,7 +507,7 @@ const _Draggable = new Lang.Class({
|
||||
let [snapBackX, snapBackY, snapBackScale] = this._getRestoreLocation();
|
||||
|
||||
if (this._actorDestroyed) {
|
||||
global.unset_cursor();
|
||||
global.screen.set_cursor(Meta.Cursor.DEFAULT);
|
||||
if (!this._buttonDown)
|
||||
this._dragComplete();
|
||||
this.emit('drag-end', eventTime, false);
|
||||
@ -552,7 +561,7 @@ const _Draggable = new Lang.Class({
|
||||
} else {
|
||||
dragActor.destroy();
|
||||
}
|
||||
global.unset_cursor();
|
||||
global.screen.set_cursor(Meta.Cursor.DEFAULT);
|
||||
this.emit('drag-end', eventTime, false);
|
||||
|
||||
this._animationInProgress = false;
|
||||
@ -560,32 +569,16 @@ const _Draggable = new Lang.Class({
|
||||
this._dragComplete();
|
||||
},
|
||||
|
||||
// Actor is an actor we have entered or left during the drag; call
|
||||
// st_widget_sync_hover on all StWidget ancestors
|
||||
_syncHover: function(actor) {
|
||||
while (actor) {
|
||||
let parent = actor.get_parent();
|
||||
if (actor instanceof St.Widget)
|
||||
actor.sync_hover();
|
||||
|
||||
actor = parent;
|
||||
}
|
||||
},
|
||||
|
||||
_dragComplete: function() {
|
||||
if (!this._actorDestroyed)
|
||||
Shell.util_set_hidden_from_pick(this._dragActor, false);
|
||||
|
||||
this._ungrabEvents();
|
||||
global.sync_pointer();
|
||||
|
||||
if (this._firstLeaveActor) {
|
||||
this._syncHover(this._firstLeaveActor);
|
||||
this._firstLeaveActor = null;
|
||||
}
|
||||
|
||||
if (this._lastEnterActor) {
|
||||
this._syncHover(this._lastEnterActor);
|
||||
this._lastEnterActor = null;
|
||||
if (this._updateHoverId) {
|
||||
GLib.source_remove(this._updateHoverId);
|
||||
this._updateHoverId = 0;
|
||||
}
|
||||
|
||||
this._dragActor = undefined;
|
||||
|
@ -19,7 +19,7 @@
|
||||
*/
|
||||
|
||||
const Lang = imports.lang;
|
||||
const Signals = imports.signals;
|
||||
const Mainloop = imports.mainloop;
|
||||
|
||||
const AccountsService = imports.gi.AccountsService;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
@ -31,10 +31,10 @@ const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const GnomeSession = imports.misc.gnomeSession;
|
||||
const Main = imports.ui.main;
|
||||
const LoginManager = imports.misc.loginManager;
|
||||
const ModalDialog = imports.ui.modalDialog;
|
||||
const Tweener = imports.ui.tweener;
|
||||
const UserMenu = imports.ui.userMenu;
|
||||
const UserWidget = imports.ui.userWidget;
|
||||
|
||||
let _endSessionDialog = null;
|
||||
|
||||
@ -50,6 +50,7 @@ const EndSessionDialogIface = <interface name="org.gnome.SessionManager.EndSessi
|
||||
<arg type="u" direction="in" />
|
||||
<arg type="ao" direction="in" />
|
||||
</method>
|
||||
<method name="Close" />
|
||||
<signal name="ConfirmedLogout" />
|
||||
<signal name="ConfirmedReboot" />
|
||||
<signal name="ConfirmedShutdown" />
|
||||
@ -60,63 +61,96 @@ const EndSessionDialogIface = <interface name="org.gnome.SessionManager.EndSessi
|
||||
const logoutDialogContent = {
|
||||
subjectWithUser: C_("title", "Log Out %s"),
|
||||
subject: C_("title", "Log Out"),
|
||||
inhibitedDescription: _("Click Log Out to quit these applications and log out of the system."),
|
||||
uninhibitedDescriptionWithUser: function(user, seconds) {
|
||||
descriptionWithUser: function(user, seconds) {
|
||||
return ngettext("%s will be logged out automatically in %d second.",
|
||||
"%s will be logged out automatically in %d seconds.",
|
||||
seconds).format(user, seconds);
|
||||
},
|
||||
uninhibitedDescription: function(seconds) {
|
||||
description: function(seconds) {
|
||||
return ngettext("You will be logged out automatically in %d second.",
|
||||
"You will be logged out automatically in %d seconds.",
|
||||
seconds).format(seconds);
|
||||
},
|
||||
endDescription: _("Logging out of the system."),
|
||||
confirmButtons: [{ signal: 'ConfirmedLogout',
|
||||
label: C_("button", "Log Out") }],
|
||||
iconStyleClass: 'end-session-dialog-logout-icon'
|
||||
iconStyleClass: 'end-session-dialog-logout-icon',
|
||||
showOtherSessions: false,
|
||||
};
|
||||
|
||||
const shutdownDialogContent = {
|
||||
subject: C_("title", "Power Off"),
|
||||
inhibitedDescription: _("Click Power Off to quit these applications and power off the system."),
|
||||
uninhibitedDescription: function(seconds) {
|
||||
description: function(seconds) {
|
||||
return ngettext("The system will power off automatically in %d second.",
|
||||
"The system will power off automatically in %d seconds.",
|
||||
seconds).format(seconds);
|
||||
},
|
||||
endDescription: _("Powering off the system."),
|
||||
confirmButtons: [{ signal: 'ConfirmedReboot',
|
||||
label: C_("button", "Restart") },
|
||||
{ signal: 'ConfirmedShutdown',
|
||||
label: C_("button", "Power Off") }],
|
||||
iconName: 'system-shutdown-symbolic',
|
||||
iconStyleClass: 'end-session-dialog-shutdown-icon'
|
||||
iconStyleClass: 'end-session-dialog-shutdown-icon',
|
||||
showOtherSessions: true,
|
||||
};
|
||||
|
||||
const restartDialogContent = {
|
||||
subject: C_("title", "Restart"),
|
||||
inhibitedDescription: _("Click Restart to quit these applications and restart the system."),
|
||||
uninhibitedDescription: function(seconds) {
|
||||
description: function(seconds) {
|
||||
return ngettext("The system will restart automatically in %d second.",
|
||||
"The system will restart automatically in %d seconds.",
|
||||
seconds).format(seconds);
|
||||
},
|
||||
endDescription: _("Restarting the system."),
|
||||
confirmButtons: [{ signal: 'ConfirmedReboot',
|
||||
label: C_("button", "Restart") }],
|
||||
iconName: 'view-refresh-symbolic',
|
||||
iconStyleClass: 'end-session-dialog-shutdown-icon'
|
||||
iconStyleClass: 'end-session-dialog-shutdown-icon',
|
||||
showOtherSessions: true,
|
||||
};
|
||||
|
||||
const restartInstallDialogContent = {
|
||||
|
||||
subject: C_("title", "Restart & Install Updates"),
|
||||
description: function(seconds) {
|
||||
return ngettext("The system will automatically restart and install updates in %d second.",
|
||||
"The system will automatically restart and install updates in %d seconds.",
|
||||
seconds).format(seconds);
|
||||
},
|
||||
confirmButtons: [{ signal: 'ConfirmedReboot',
|
||||
label: C_("button", "Restart & Install") }],
|
||||
iconName: 'view-refresh-symbolic',
|
||||
iconStyleClass: 'end-session-dialog-shutdown-icon',
|
||||
showOtherSessions: true,
|
||||
};
|
||||
|
||||
const DialogContent = {
|
||||
0 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT */: logoutDialogContent,
|
||||
1 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_SHUTDOWN */: shutdownDialogContent,
|
||||
2 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_RESTART */: restartDialogContent
|
||||
2 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_RESTART */: restartDialogContent,
|
||||
3: restartInstallDialogContent
|
||||
};
|
||||
|
||||
const MAX_USERS_IN_SESSION_DIALOG = 5;
|
||||
|
||||
const LogindSessionIface = <interface name='org.freedesktop.login1.Session'>
|
||||
<property name="Id" type="s" access="read"/>
|
||||
<property name="Remote" type="b" access="read"/>
|
||||
<property name="Class" type="s" access="read"/>
|
||||
<property name="Type" type="s" access="read"/>
|
||||
<property name="State" type="s" access="read"/>
|
||||
</interface>;
|
||||
|
||||
const LogindSession = Gio.DBusProxy.makeProxyWrapper(LogindSessionIface);
|
||||
|
||||
function findAppFromInhibitor(inhibitor) {
|
||||
let [desktopFile] = inhibitor.GetAppIdSync();
|
||||
let desktopFile;
|
||||
try {
|
||||
[desktopFile] = inhibitor.GetAppIdSync();
|
||||
} catch(e) {
|
||||
// XXX -- sometimes JIT inhibitors generated by gnome-session
|
||||
// get removed too soon. Don't fail in this case.
|
||||
log('gnome-session gave us a dead inhibitor: %s'.format(inhibitor.get_object_path()));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!GLib.str_has_suffix(desktopFile, '.desktop'))
|
||||
desktopFile += '.desktop';
|
||||
@ -124,58 +158,6 @@ function findAppFromInhibitor(inhibitor) {
|
||||
return Shell.AppSystem.get_default().lookup_heuristic_basename(desktopFile);
|
||||
}
|
||||
|
||||
const ListItem = new Lang.Class({
|
||||
Name: 'ListItem',
|
||||
|
||||
_init: function(app, reason) {
|
||||
this._app = app;
|
||||
this._reason = reason;
|
||||
|
||||
if (this._reason == null)
|
||||
this._reason = '';
|
||||
|
||||
let layout = new St.BoxLayout({ vertical: false});
|
||||
|
||||
this.actor = new St.Button({ style_class: 'end-session-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(_ITEM_ICON_SIZE);
|
||||
|
||||
let iconBin = new St.Bin({ style_class: 'end-session-dialog-app-list-item-icon',
|
||||
child: this._icon });
|
||||
layout.add(iconBin);
|
||||
|
||||
let textLayout = new St.BoxLayout({ style_class: 'end-session-dialog-app-list-item-text-box',
|
||||
vertical: true });
|
||||
layout.add(textLayout);
|
||||
|
||||
this._nameLabel = new St.Label({ text: this._app.get_name(),
|
||||
style_class: 'end-session-dialog-app-list-item-name' });
|
||||
textLayout.add(this._nameLabel,
|
||||
{ expand: false,
|
||||
x_fill: true });
|
||||
|
||||
this._descriptionLabel = new St.Label({ text: this._reason,
|
||||
style_class: 'end-session-dialog-app-list-item-description' });
|
||||
this.actor.label_actor = this._nameLabel;
|
||||
textLayout.add(this._descriptionLabel,
|
||||
{ expand: true,
|
||||
x_fill: true });
|
||||
|
||||
this.actor.connect('clicked', Lang.bind(this, this._onClicked));
|
||||
},
|
||||
|
||||
_onClicked: function() {
|
||||
this.emit('activate');
|
||||
this._app.activate();
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(ListItem.prototype);
|
||||
|
||||
// The logout timer only shows updates every 10 seconds
|
||||
// until the last 10 seconds, then it shows updates every
|
||||
// second. This function takes a given time and returns
|
||||
@ -223,24 +205,26 @@ const EndSessionDialog = new Lang.Class({
|
||||
Extends: ModalDialog.ModalDialog,
|
||||
|
||||
_init: function() {
|
||||
this.parent({ styleClass: 'end-session-dialog' });
|
||||
this.parent({ styleClass: 'end-session-dialog',
|
||||
destroyOnClose: false });
|
||||
|
||||
this._user = AccountsService.UserManager.get_default().get_user(GLib.get_user_name());
|
||||
this._loginManager = LoginManager.getLoginManager();
|
||||
this._userManager = AccountsService.UserManager.get_default();
|
||||
this._user = this._userManager.get_user(GLib.get_user_name());
|
||||
this._updatesFile = Gio.File.new_for_path('/system-update');
|
||||
|
||||
this._secondsLeft = 0;
|
||||
this._totalSecondsToStayOpen = 0;
|
||||
this._inhibitors = [];
|
||||
this._applications = [];
|
||||
this._sessions = [];
|
||||
|
||||
this.connect('destroy',
|
||||
Lang.bind(this, this._onDestroy));
|
||||
this.connect('opened',
|
||||
Lang.bind(this, this._onOpened));
|
||||
|
||||
this._userLoadedId = this._user.connect('notify::is_loaded',
|
||||
Lang.bind(this, this._updateContent));
|
||||
|
||||
this._userChangedId = this._user.connect('changed',
|
||||
Lang.bind(this, this._updateContent));
|
||||
this._userLoadedId = this._user.connect('notify::is_loaded', Lang.bind(this, this._sync));
|
||||
this._userChangedId = this._user.connect('changed', Lang.bind(this, this._sync));
|
||||
|
||||
let mainContentLayout = new St.BoxLayout({ vertical: false });
|
||||
this.contentLayout.add(mainContentLayout,
|
||||
@ -272,28 +256,28 @@ const EndSessionDialog = new Lang.Class({
|
||||
{ y_fill: true,
|
||||
y_align: St.Align.START });
|
||||
|
||||
let scrollView = new St.ScrollView({ style_class: 'end-session-dialog-app-list'});
|
||||
scrollView.set_policy(Gtk.PolicyType.NEVER,
|
||||
Gtk.PolicyType.AUTOMATIC);
|
||||
this.contentLayout.add(scrollView,
|
||||
this._scrollView = new St.ScrollView({ style_class: 'end-session-dialog-list' });
|
||||
this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
this.contentLayout.add(this._scrollView,
|
||||
{ x_fill: true,
|
||||
y_fill: true });
|
||||
scrollView.hide();
|
||||
this._scrollView.hide();
|
||||
|
||||
this._inhibitorSection = new St.BoxLayout({ vertical: true,
|
||||
style_class: 'end-session-dialog-inhibitor-layout' });
|
||||
this._scrollView.add_actor(this._inhibitorSection);
|
||||
|
||||
this._applicationHeader = new St.Label({ style_class: 'end-session-dialog-list-header',
|
||||
text: _("Some applications are busy or have unsaved work.") });
|
||||
this._applicationList = new St.BoxLayout({ vertical: true });
|
||||
scrollView.add_actor(this._applicationList);
|
||||
this._inhibitorSection.add_actor(this._applicationHeader);
|
||||
this._inhibitorSection.add_actor(this._applicationList);
|
||||
|
||||
this._applicationList.connect('actor-added',
|
||||
Lang.bind(this, function() {
|
||||
if (this._applicationList.get_n_children() == 1)
|
||||
scrollView.show();
|
||||
}));
|
||||
|
||||
this._applicationList.connect('actor-removed',
|
||||
Lang.bind(this, function() {
|
||||
if (this._applicationList.get_n_children() == 0)
|
||||
scrollView.hide();
|
||||
}));
|
||||
this._sessionHeader = new St.Label({ style_class: 'end-session-dialog-list-header',
|
||||
text: _("Other users are logged in.") });
|
||||
this._sessionList = new St.BoxLayout({ vertical: true });
|
||||
this._inhibitorSection.add_actor(this._sessionHeader);
|
||||
this._inhibitorSection.add_actor(this._sessionList);
|
||||
|
||||
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(EndSessionDialogIface, this);
|
||||
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/SessionManager/EndSessionDialog');
|
||||
@ -304,52 +288,42 @@ const EndSessionDialog = new Lang.Class({
|
||||
this._user.disconnect(this._userChangedId);
|
||||
},
|
||||
|
||||
_updateDescription: function() {
|
||||
if (this.state != ModalDialog.State.OPENING &&
|
||||
this.state != ModalDialog.State.OPENED)
|
||||
_sync: function() {
|
||||
let open = (this.state == ModalDialog.State.OPENING || this.state == ModalDialog.State.OPENED);
|
||||
if (!open)
|
||||
return;
|
||||
|
||||
if (this._type == 2 && this._updatesFile.query_exists(null))
|
||||
this._type = 3;
|
||||
|
||||
let dialogContent = DialogContent[this._type];
|
||||
|
||||
let subject = dialogContent.subject;
|
||||
|
||||
let description;
|
||||
let displayTime = _roundSecondsToInterval(this._totalSecondsToStayOpen,
|
||||
this._secondsLeft,
|
||||
10);
|
||||
|
||||
if (this._inhibitors.length > 0) {
|
||||
this._stopTimer();
|
||||
description = dialogContent.inhibitedDescription;
|
||||
} else if (this._secondsLeft > 0 && this._inhibitors.length == 0) {
|
||||
let displayTime = _roundSecondsToInterval(this._totalSecondsToStayOpen,
|
||||
this._secondsLeft,
|
||||
10);
|
||||
if (this._user.is_loaded) {
|
||||
let realName = this._user.get_real_name();
|
||||
|
||||
if (this._user.is_loaded) {
|
||||
let realName = this._user.get_real_name();
|
||||
if (realName != null) {
|
||||
if (dialogContent.subjectWithUser)
|
||||
subject = dialogContent.subjectWithUser.format(realName);
|
||||
|
||||
if (realName != null) {
|
||||
if (dialogContent.subjectWithUser)
|
||||
subject = dialogContent.subjectWithUser.format(realName);
|
||||
|
||||
if (dialogContent.uninhibitedDescriptionWithUser)
|
||||
description = dialogContent.uninhibitedDescriptionWithUser(realName, displayTime);
|
||||
else
|
||||
description = dialogContent.uninhibitedDescription(displayTime);
|
||||
}
|
||||
if (dialogContent.descriptionWithUser)
|
||||
description = dialogContent.descriptionWithUser(realName, displayTime);
|
||||
else
|
||||
description = dialogContent.description(displayTime);
|
||||
}
|
||||
|
||||
if (!description)
|
||||
description = dialogContent.uninhibitedDescription(displayTime);
|
||||
} else {
|
||||
description = dialogContent.endDescription;
|
||||
}
|
||||
|
||||
_setLabelText(this._subjectLabel, subject);
|
||||
_setLabelText(this._descriptionLabel, description);
|
||||
},
|
||||
if (!description)
|
||||
description = dialogContent.description(displayTime);
|
||||
|
||||
_updateContent: function() {
|
||||
if (this.state != ModalDialog.State.OPENING &&
|
||||
this.state != ModalDialog.State.OPENED)
|
||||
return;
|
||||
_setLabelText(this._descriptionLabel, description);
|
||||
_setLabelText(this._subjectLabel, subject);
|
||||
|
||||
let dialogContent = DialogContent[this._type];
|
||||
if (dialogContent.iconName) {
|
||||
@ -357,14 +331,18 @@ const EndSessionDialog = new Lang.Class({
|
||||
icon_size: _DIALOG_ICON_SIZE,
|
||||
style_class: dialogContent.iconStyleClass });
|
||||
} else {
|
||||
let avatarWidget = new UserMenu.UserAvatarWidget(this._user,
|
||||
{ iconSize: _DIALOG_ICON_SIZE,
|
||||
styleClass: dialogContent.iconStyleClass });
|
||||
let avatarWidget = new UserWidget.Avatar(this._user,
|
||||
{ iconSize: _DIALOG_ICON_SIZE,
|
||||
styleClass: dialogContent.iconStyleClass });
|
||||
this._iconBin.child = avatarWidget.actor;
|
||||
avatarWidget.update();
|
||||
}
|
||||
|
||||
this._updateDescription();
|
||||
let hasApplications = this._applications.length > 0;
|
||||
let hasSessions = this._sessions.length > 0;
|
||||
this._scrollView.visible = hasApplications || hasSessions;
|
||||
this._applicationHeader.visible = hasApplications;
|
||||
this._sessionHeader.visible = hasSessions;
|
||||
},
|
||||
|
||||
_updateButtons: function() {
|
||||
@ -377,7 +355,12 @@ const EndSessionDialog = new Lang.Class({
|
||||
let signal = dialogContent.confirmButtons[i].signal;
|
||||
let label = dialogContent.confirmButtons[i].label;
|
||||
buttons.push({ action: Lang.bind(this, function() {
|
||||
this._confirm(signal);
|
||||
this.close(true);
|
||||
let signalId = this.connect('closed',
|
||||
Lang.bind(this, function() {
|
||||
this.disconnect(signalId);
|
||||
this._confirm(signal);
|
||||
}));
|
||||
}),
|
||||
label: label });
|
||||
}
|
||||
@ -385,15 +368,17 @@ const EndSessionDialog = new Lang.Class({
|
||||
this.setButtons(buttons);
|
||||
},
|
||||
|
||||
close: function() {
|
||||
close: function(skipSignal) {
|
||||
this.parent();
|
||||
this._dbusImpl.emit_signal('Closed', null);
|
||||
|
||||
if (!skipSignal)
|
||||
this._dbusImpl.emit_signal('Closed', null);
|
||||
},
|
||||
|
||||
cancel: function() {
|
||||
this._stopTimer();
|
||||
this._dbusImpl.emit_signal('Canceled', null);
|
||||
this.close(global.get_current_time());
|
||||
this.close();
|
||||
},
|
||||
|
||||
_confirm: function(signal) {
|
||||
@ -403,32 +388,68 @@ const EndSessionDialog = new Lang.Class({
|
||||
},
|
||||
|
||||
_onOpened: function() {
|
||||
if (this._inhibitors.length == 0)
|
||||
this._startTimer();
|
||||
this._sync();
|
||||
},
|
||||
|
||||
_startTimer: function() {
|
||||
let startTime = GLib.get_monotonic_time();
|
||||
this._secondsLeft = this._totalSecondsToStayOpen;
|
||||
Tweener.addTween(this,
|
||||
{ _secondsLeft: 0,
|
||||
time: this._secondsLeft,
|
||||
transition: 'linear',
|
||||
onUpdate: Lang.bind(this, this._updateDescription),
|
||||
onComplete: Lang.bind(this, function() {
|
||||
let dialogContent = DialogContent[this._type];
|
||||
let button = dialogContent.confirmButtons[dialogContent.confirmButtons.length - 1];
|
||||
this._confirm(button.signal);
|
||||
}),
|
||||
});
|
||||
|
||||
this._timerId = Mainloop.timeout_add_seconds(1, Lang.bind(this,
|
||||
function() {
|
||||
let currentTime = GLib.get_monotonic_time();
|
||||
let secondsElapsed = ((currentTime - startTime) / 1000000);
|
||||
|
||||
this._secondsLeft = this._totalSecondsToStayOpen - secondsElapsed;
|
||||
if (this._secondsLeft > 0) {
|
||||
this._sync();
|
||||
return true;
|
||||
}
|
||||
|
||||
let dialogContent = DialogContent[this._type];
|
||||
let button = dialogContent.confirmButtons[dialogContent.confirmButtons.length - 1];
|
||||
this._confirm(button.signal);
|
||||
|
||||
return false;
|
||||
}));
|
||||
},
|
||||
|
||||
_stopTimer: function() {
|
||||
Tweener.removeTweens(this);
|
||||
if (this._timerId > 0) {
|
||||
Mainloop.source_remove(this._timerId);
|
||||
this._timerId = 0;
|
||||
}
|
||||
|
||||
this._secondsLeft = 0;
|
||||
},
|
||||
|
||||
_constructListItemForApp: function(inhibitor, app) {
|
||||
let actor = new St.BoxLayout({ style_class: 'end-session-dialog-app-list-item',
|
||||
can_focus: true });
|
||||
actor.add(app.create_icon_texture(_ITEM_ICON_SIZE));
|
||||
|
||||
let textLayout = new St.BoxLayout({ vertical: true,
|
||||
y_expand: true,
|
||||
y_align: Clutter.ActorAlign.CENTER });
|
||||
actor.add(textLayout);
|
||||
|
||||
let nameLabel = new St.Label({ text: app.get_name(),
|
||||
style_class: 'end-session-dialog-app-list-item-name' });
|
||||
textLayout.add(nameLabel);
|
||||
actor.label_actor = nameLabel;
|
||||
|
||||
let [reason] = inhibitor.GetReasonSync();
|
||||
if (reason) {
|
||||
let reasonLabel = new St.Label({ text: reason,
|
||||
style_class: 'end-session-dialog-app-list-item-description' });
|
||||
textLayout.add(reasonLabel);
|
||||
}
|
||||
|
||||
return actor;
|
||||
},
|
||||
|
||||
_onInhibitorLoaded: function(inhibitor) {
|
||||
if (this._inhibitors.indexOf(inhibitor) < 0) {
|
||||
if (this._applications.indexOf(inhibitor) < 0) {
|
||||
// Stale inhibitor
|
||||
return;
|
||||
}
|
||||
@ -436,29 +457,92 @@ const EndSessionDialog = new Lang.Class({
|
||||
let app = findAppFromInhibitor(inhibitor);
|
||||
|
||||
if (app) {
|
||||
let [reason] = inhibitor.GetReasonSync();
|
||||
let item = new ListItem(app, reason);
|
||||
item.connect('activate',
|
||||
Lang.bind(this, function() {
|
||||
this.close(global.get_current_time());
|
||||
}));
|
||||
this._applicationList.add(item.actor, { x_fill: true });
|
||||
this._stopTimer();
|
||||
let actor = this._constructListItemForApp(inhibitor, app);
|
||||
this._applicationList.add(actor);
|
||||
} else {
|
||||
// inhibiting app is a service, not an application
|
||||
this._inhibitors.splice(this._inhibitors.indexOf(inhibitor), 1);
|
||||
this._applications.splice(this._applications.indexOf(inhibitor), 1);
|
||||
}
|
||||
|
||||
this._updateContent();
|
||||
this._sync();
|
||||
},
|
||||
|
||||
_constructListItemForSession: function(session) {
|
||||
let avatar = new UserWidget.Avatar(session.user, { iconSize: _ITEM_ICON_SIZE });
|
||||
avatar.update();
|
||||
|
||||
let userName = session.user.get_real_name() ? session.user.get_real_name() : session.username;
|
||||
let userLabelText;
|
||||
|
||||
if (session.remote)
|
||||
/* Translators: Remote here refers to a remote session, like a ssh login */
|
||||
userLabelText = _("%s (remote)").format(userName);
|
||||
else if (session.type == "tty")
|
||||
/* Translators: Console here refers to a tty like a VT console */
|
||||
userLabelText = _("%s (console)").format(userName);
|
||||
else
|
||||
userLabelText = userName;
|
||||
|
||||
let actor = new St.BoxLayout({ style_class: 'end-session-dialog-session-list-item',
|
||||
can_focus: true });
|
||||
actor.add(avatar.actor);
|
||||
|
||||
let nameLabel = new St.Label({ text: userLabelText,
|
||||
style_class: 'end-session-dialog-session-list-item-name',
|
||||
y_expand: true,
|
||||
y_align: Clutter.ActorAlign.CENTER });
|
||||
actor.add(nameLabel);
|
||||
actor.label_actor = nameLabel;
|
||||
|
||||
return actor;
|
||||
},
|
||||
|
||||
_loadSessions: function() {
|
||||
this._loginManager.listSessions(Lang.bind(this, function(result) {
|
||||
let n = 0;
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
let[id, uid, userName, seat, sessionPath] = result[i];
|
||||
let proxy = new LogindSession(Gio.DBus.system, 'org.freedesktop.login1', sessionPath);
|
||||
|
||||
if (proxy.Class != 'user')
|
||||
continue;
|
||||
|
||||
if (proxy.State == 'closing')
|
||||
continue;
|
||||
|
||||
if (proxy.Id == GLib.getenv('XDG_SESSION_ID'))
|
||||
continue;
|
||||
|
||||
let session = { user: this._userManager.get_user(userName),
|
||||
username: userName,
|
||||
type: proxy.Type,
|
||||
remote: proxy.Remote };
|
||||
this._sessions.push(session);
|
||||
|
||||
let actor = this._constructListItemForSession(session);
|
||||
this._sessionList.add(actor);
|
||||
|
||||
// limit the number of entries
|
||||
n++;
|
||||
if (n == MAX_USERS_IN_SESSION_DIALOG)
|
||||
break;
|
||||
}
|
||||
|
||||
this._sync();
|
||||
}));
|
||||
},
|
||||
|
||||
OpenAsync: function(parameters, invocation) {
|
||||
let [type, timestamp, totalSecondsToStayOpen, inhibitorObjectPaths] = parameters;
|
||||
this._totalSecondsToStayOpen = totalSecondsToStayOpen;
|
||||
this._inhibitors = [];
|
||||
this._applicationList.destroy_all_children();
|
||||
this._type = type;
|
||||
|
||||
this._applications = [];
|
||||
this._applicationList.destroy_all_children();
|
||||
|
||||
this._sessions = [];
|
||||
this._sessionList.destroy_all_children();
|
||||
|
||||
if (!(this._type in DialogContent)) {
|
||||
invocation.return_dbus_error('org.gnome.Shell.ModalDialog.TypeError',
|
||||
"Unknown dialog type requested");
|
||||
@ -470,9 +554,12 @@ const EndSessionDialog = new Lang.Class({
|
||||
this._onInhibitorLoaded(proxy);
|
||||
}));
|
||||
|
||||
this._inhibitors.push(inhibitor);
|
||||
this._applications.push(inhibitor);
|
||||
}
|
||||
|
||||
if (DialogContent[type].showOtherSessions)
|
||||
this._loadSessions();
|
||||
|
||||
this._updateButtons();
|
||||
|
||||
if (!this.open(timestamp)) {
|
||||
@ -481,12 +568,17 @@ const EndSessionDialog = new Lang.Class({
|
||||
return;
|
||||
}
|
||||
|
||||
this._updateContent();
|
||||
this._startTimer();
|
||||
this._sync();
|
||||
|
||||
let signalId = this.connect('opened',
|
||||
Lang.bind(this, function() {
|
||||
invocation.return_value(null);
|
||||
this.disconnect(signalId);
|
||||
}));
|
||||
},
|
||||
|
||||
Close: function(parameters, invocation) {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
|
@ -10,6 +10,7 @@ const Clutter = imports.gi.Clutter;;
|
||||
const Gettext = imports.gettext;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
@ -39,6 +40,22 @@ function _patchContainerClass(containerClass) {
|
||||
};
|
||||
}
|
||||
|
||||
function _patchLayoutClass(layoutClass, styleProps) {
|
||||
if (styleProps)
|
||||
layoutClass.prototype.hookup_style = function(container) {
|
||||
container.connect('style-changed', Lang.bind(this, function() {
|
||||
let node = container.get_theme_node();
|
||||
for (let prop in styleProps)
|
||||
this[prop] = node.get_length(styleProps[prop]);
|
||||
}));
|
||||
};
|
||||
layoutClass.prototype.child_set = function(actor, props) {
|
||||
let meta = this.get_child_meta(actor.get_parent(), actor);
|
||||
for (let prop in props)
|
||||
meta[prop] = props[prop];
|
||||
};
|
||||
}
|
||||
|
||||
function _makeLoggingFunc(func) {
|
||||
return function() {
|
||||
return func([].join.call(arguments, ', '));
|
||||
@ -60,6 +77,12 @@ function init() {
|
||||
_patchContainerClass(St.BoxLayout);
|
||||
_patchContainerClass(St.Table);
|
||||
|
||||
_patchLayoutClass(Clutter.TableLayout, { row_spacing: 'spacing-rows',
|
||||
column_spacing: 'spacing-columns' });
|
||||
_patchLayoutClass(Clutter.GridLayout, { row_spacing: 'spacing-rows',
|
||||
column_spacing: 'spacing-columns' });
|
||||
_patchLayoutClass(Clutter.BoxLayout, { spacing: 'spacing' });
|
||||
|
||||
Clutter.Actor.prototype.toString = function() {
|
||||
return St.describe_actor(this);
|
||||
};
|
||||
|
@ -215,7 +215,7 @@ const InstallExtensionDialog = new Lang.Class({
|
||||
},
|
||||
|
||||
_onCancelButtonPressed: function(button, event) {
|
||||
this.close(global.get_current_time());
|
||||
this.close();
|
||||
this._invocation.return_value(GLib.Variant.new('(s)', ['cancelled']));
|
||||
},
|
||||
|
||||
@ -257,7 +257,7 @@ const InstallExtensionDialog = new Lang.Class({
|
||||
gotExtensionZipFile(session, message, uuid, dir, callback, errback);
|
||||
}));
|
||||
|
||||
this.close(global.get_current_time());
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -106,11 +106,15 @@ function enableExtension(uuid) {
|
||||
|
||||
extensionOrder.push(uuid);
|
||||
|
||||
let stylesheetFile = extension.dir.get_child('stylesheet.css');
|
||||
if (stylesheetFile.query_exists(null)) {
|
||||
let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
|
||||
theme.load_stylesheet(stylesheetFile.get_path());
|
||||
extension.stylesheet = stylesheetFile;
|
||||
let stylesheetNames = [global.session_mode + '.css', 'stylesheet.css'];
|
||||
for (let i = 0; i < stylesheetNames.length; i++) {
|
||||
let stylesheetFile = extension.dir.get_child(stylesheetNames[i]);
|
||||
if (stylesheetFile.query_exists(null)) {
|
||||
let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
|
||||
theme.load_stylesheet(stylesheetFile.get_path());
|
||||
extension.stylesheet = stylesheetFile;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
extension.stateObj.enable();
|
||||
@ -129,6 +133,7 @@ function logExtensionError(uuid, error) {
|
||||
extension.state = ExtensionState.ERROR;
|
||||
if (!extension.errors)
|
||||
extension.errors = [];
|
||||
extension.errors.push(message);
|
||||
|
||||
log('Extension "%s" had error: %s'.format(uuid, message));
|
||||
_signals.emit('extension-state-changed', { uuid: uuid,
|
||||
@ -211,8 +216,16 @@ function initExtension(uuid) {
|
||||
_signals.emit('extension-loaded', uuid);
|
||||
}
|
||||
|
||||
function getEnabledExtensions() {
|
||||
let extensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
|
||||
if (!Array.isArray(Main.sessionMode.enabledExtensions))
|
||||
return extensions;
|
||||
|
||||
return Main.sessionMode.enabledExtensions.concat(extensions);
|
||||
}
|
||||
|
||||
function onEnabledExtensionsChanged() {
|
||||
let newEnabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
|
||||
let newEnabledExtensions = getEnabledExtensions();
|
||||
|
||||
if (!enabled)
|
||||
return;
|
||||
@ -246,7 +259,7 @@ function onEnabledExtensionsChanged() {
|
||||
|
||||
function _loadExtensions() {
|
||||
global.settings.connect('changed::' + ENABLED_EXTENSIONS_KEY, onEnabledExtensionsChanged);
|
||||
enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
|
||||
enabledExtensions = getEnabledExtensions();
|
||||
|
||||
let finder = new ExtensionUtils.ExtensionFinder();
|
||||
finder.connect('extension-found', function(signals, extension) {
|
||||
@ -279,7 +292,7 @@ function disableAllExtensions() {
|
||||
return;
|
||||
|
||||
if (initted) {
|
||||
enabledExtensions.forEach(function(uuid) {
|
||||
extensionOrder.slice().reverse().forEach(function(uuid) {
|
||||
disableExtension(uuid);
|
||||
});
|
||||
}
|
||||
@ -288,10 +301,17 @@ function disableAllExtensions() {
|
||||
}
|
||||
|
||||
function _sessionUpdated() {
|
||||
if (Main.sessionMode.allowExtensions)
|
||||
// For now sessionMode.allowExtensions controls extensions from both the
|
||||
// 'enabled-extensions' preference and the sessionMode.enabledExtensions
|
||||
// property; it might make sense to make enabledExtensions independent
|
||||
// from allowExtensions in the future
|
||||
if (Main.sessionMode.allowExtensions) {
|
||||
if (initted)
|
||||
onEnabledExtensionsChanged();
|
||||
enableAllExtensions();
|
||||
else
|
||||
} else {
|
||||
disableAllExtensions();
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
|
@ -1,45 +0,0 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Lang = imports.lang;
|
||||
|
||||
const Lightbox = imports.ui.lightbox;
|
||||
const Main = imports.ui.main;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
const FLASHSPOT_ANIMATION_TIME = 0.25; // seconds
|
||||
|
||||
const Flashspot = new Lang.Class({
|
||||
Name: 'Flashspot',
|
||||
Extends: Lightbox.Lightbox,
|
||||
|
||||
_init: function(area) {
|
||||
this.parent(Main.uiGroup, { inhibitEvents: true,
|
||||
width: area.width,
|
||||
height: area.height });
|
||||
|
||||
this.actor.style_class = 'flashspot';
|
||||
this.actor.set_position(area.x, area.y);
|
||||
},
|
||||
|
||||
fire: function() {
|
||||
this.actor.opacity = 0;
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 255,
|
||||
time: FLASHSPOT_ANIMATION_TIME,
|
||||
transition: 'linear',
|
||||
onComplete: Lang.bind(this, this._onFireShowComplete)
|
||||
});
|
||||
this.actor.show();
|
||||
},
|
||||
|
||||
_onFireShowComplete: function() {
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 0,
|
||||
time: FLASHSPOT_ANIMATION_TIME,
|
||||
transition: 'linear',
|
||||
onComplete: Lang.bind(this, function() {
|
||||
this.destroy();
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
65
js/ui/focusCaretTracker.js
Normal file
65
js/ui/focusCaretTracker.js
Normal file
@ -0,0 +1,65 @@
|
||||
/** -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
/*
|
||||
* Copyright 2012 Inclusive Design Research Centre, OCAD University.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author:
|
||||
* Joseph Scheuhammer <clown@alum.mit.edu>
|
||||
* Contributor:
|
||||
* Magdalen Berns <m.berns@sms.ed.ac.uk>
|
||||
*/
|
||||
|
||||
const Atspi = imports.gi.Atspi;
|
||||
const Lang = imports.lang;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const CARETMOVED = 'object:text-caret-moved';
|
||||
const STATECHANGED = 'object:state-changed';
|
||||
|
||||
const FocusCaretTracker = new Lang.Class({
|
||||
Name: 'FocusCaretTracker',
|
||||
|
||||
_init: function() {
|
||||
Atspi.init();
|
||||
Atspi.set_timeout(250, 250);
|
||||
this._atspiListener = Atspi.EventListener.new(Lang.bind(this, this._onChanged));
|
||||
},
|
||||
|
||||
_onChanged: function(event) {
|
||||
if (event.type.indexOf(STATECHANGED) == 0)
|
||||
this.emit('focus-changed', event);
|
||||
else if (event.type == CARETMOVED)
|
||||
this.emit('caret-moved', event);
|
||||
},
|
||||
|
||||
registerFocusListener: function() {
|
||||
return this._atspiListener.register(STATECHANGED + ':focused') &&
|
||||
this._atspiListener.register(STATECHANGED + ':selected');
|
||||
},
|
||||
|
||||
registerCaretListener: function() {
|
||||
return this._atspiListener.register(CARETMOVED);
|
||||
},
|
||||
|
||||
deregisterFocusListener: function() {
|
||||
return this._atspiListener.deregister(STATECHANGED + ':focused') &&
|
||||
this._atspiListener.deregister(STATECHANGED + ':selected');
|
||||
},
|
||||
|
||||
deregisterCaretListener: function() {
|
||||
return this._atspiListener.deregister(CARETMOVED);
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(FocusCaretTracker.prototype);
|
@ -1,4 +1,4 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
@ -10,15 +10,29 @@ const St = imports.gi.St;
|
||||
const Main = imports.ui.main;
|
||||
const Params = imports.misc.params;
|
||||
|
||||
function _navigateActor(actor) {
|
||||
if (!actor)
|
||||
return;
|
||||
let _capturedEventId = 0;
|
||||
let _grabHelperStack = [];
|
||||
function _onCapturedEvent(actor, event) {
|
||||
let grabHelper = _grabHelperStack[_grabHelperStack.length - 1];
|
||||
return grabHelper.onCapturedEvent(event);
|
||||
}
|
||||
|
||||
let needsGrab = true;
|
||||
if (actor instanceof St.Widget)
|
||||
needsGrab = !actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
if (needsGrab)
|
||||
actor.grab_key_focus();
|
||||
function _pushGrabHelper(grabHelper) {
|
||||
_grabHelperStack.push(grabHelper);
|
||||
|
||||
if (_capturedEventId == 0)
|
||||
_capturedEventId = global.stage.connect('captured-event', _onCapturedEvent);
|
||||
}
|
||||
|
||||
function _popGrabHelper(grabHelper) {
|
||||
let poppedHelper = _grabHelperStack.pop();
|
||||
if (poppedHelper != grabHelper)
|
||||
throw new Error("incorrect grab helper pop");
|
||||
|
||||
if (_grabHelperStack.length == 0) {
|
||||
global.stage.disconnect(_capturedEventId);
|
||||
_capturedEventId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// GrabHelper:
|
||||
@ -42,14 +56,9 @@ const GrabHelper = new Lang.Class({
|
||||
this._grabStack = [];
|
||||
|
||||
this._actors = [];
|
||||
this._capturedEventId = 0;
|
||||
this._eventId = 0;
|
||||
this._keyFocusNotifyId = 0;
|
||||
this._focusWindowChangedId = 0;
|
||||
this._ignoreRelease = false;
|
||||
|
||||
this._modalCount = 0;
|
||||
this._grabFocusCount = 0;
|
||||
},
|
||||
|
||||
// addActor:
|
||||
@ -78,9 +87,12 @@ const GrabHelper = new Lang.Class({
|
||||
},
|
||||
|
||||
_isWithinGrabbedActor: function(actor) {
|
||||
let currentActor = this.currentGrab.actor;
|
||||
while (actor) {
|
||||
if (this._actors.indexOf(actor) != -1)
|
||||
return true;
|
||||
if (actor == currentActor)
|
||||
return true;
|
||||
actor = actor.get_parent();
|
||||
}
|
||||
return false;
|
||||
@ -90,6 +102,14 @@ const GrabHelper = new Lang.Class({
|
||||
return this._grabStack[this._grabStack.length - 1] || {};
|
||||
},
|
||||
|
||||
get grabbed() {
|
||||
return this._grabStack.length > 0;
|
||||
},
|
||||
|
||||
get grabStack() {
|
||||
return this._grabStack;
|
||||
},
|
||||
|
||||
_findStackIndex: function(actor) {
|
||||
if (!actor)
|
||||
return -1;
|
||||
@ -101,6 +121,16 @@ const GrabHelper = new Lang.Class({
|
||||
return -1;
|
||||
},
|
||||
|
||||
_actorInGrabStack: function(actor) {
|
||||
while (actor) {
|
||||
let idx = this._findStackIndex(actor);
|
||||
if (idx >= 0)
|
||||
return idx;
|
||||
actor = actor.get_parent();
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
|
||||
isActorGrabbed: function(actor) {
|
||||
return this._findStackIndex(actor) >= 0;
|
||||
},
|
||||
@ -108,38 +138,37 @@ const GrabHelper = new Lang.Class({
|
||||
// grab:
|
||||
// @params: A bunch of parameters, see below
|
||||
//
|
||||
// Grabs the mouse and keyboard, according to the GrabHelper's
|
||||
// parameters. If @newFocus is not %null, then the keyboard focus
|
||||
// is moved to the first #StWidget:can-focus widget inside it.
|
||||
// The general effect of a "grab" is to ensure that the passed in actor
|
||||
// and all actors inside the grab get exclusive control of the mouse and
|
||||
// keyboard, with the grab automatically being dropped if the user tries
|
||||
// to dismiss it. The actor is passed in through @params.actor.
|
||||
//
|
||||
// The grab will automatically be dropped if:
|
||||
// - The user clicks outside the grabbed actors
|
||||
// - The user types Escape
|
||||
// - The keyboard focus is moved outside the grabbed actors
|
||||
// - A window is focused
|
||||
// grab() can be called multiple times, with the scope of the grab being
|
||||
// changed to a different actor every time. A nested grab does not have
|
||||
// to have its grabbed actor inside the parent grab actors.
|
||||
//
|
||||
// If @params.actor is not null, then it will be focused as the
|
||||
// new actor. If you attempt to grab an already focused actor, the
|
||||
// request to be focused will be ignored. The actor will not be
|
||||
// added to the grab stack, so do not call a paired ungrab().
|
||||
// Grabs can be automatically dropped if the user tries to dismiss it
|
||||
// in one of two ways: the user clicking outside the currently grabbed
|
||||
// actor, or the user typing the Escape key.
|
||||
//
|
||||
// If @params contains { modal: true }, then grab() will push a modal
|
||||
// on the owner of the GrabHelper. As long as there is at least one
|
||||
// { modal: true } actor on the grab stack, the grab will be kept.
|
||||
// When the last { modal: true } actor is ungrabbed, then the modal
|
||||
// will be dropped. A modal grab can fail if there is already a grab
|
||||
// in effect from aother application; in this case the function returns
|
||||
// false and nothing happens. Non-modal grabs can never fail.
|
||||
// If the user clicks outside the grabbed actors, and the clicked on
|
||||
// actor is part of a previous grab in the stack, grabs will be popped
|
||||
// until that grab is active. However, the click event will not be
|
||||
// replayed to the actor.
|
||||
//
|
||||
// If @params contains { grabFocus: true }, then if you call grab()
|
||||
// while the shell is outside the overview, it will set the stage
|
||||
// input mode to %Shell.StageInputMode.FOCUSED, and ungrab() will
|
||||
// revert it back, and re-focus the previously-focused window (if
|
||||
// another window hasn't been explicitly focused before then).
|
||||
// If the user types the Escape key, one grab from the grab stack will
|
||||
// be popped.
|
||||
//
|
||||
// When a grab is popped by user interacting as described above, if you
|
||||
// pass a callback as @params.onUngrab, it will be called with %true.
|
||||
//
|
||||
// If @params.focus is not null, we'll set the key focus directly
|
||||
// to that actor instead of navigating in @params.actor. This is for
|
||||
// use cases like menus, where we want to grab the menu actor, but keep
|
||||
// focus on the clicked on menu item.
|
||||
grab: function(params) {
|
||||
params = Params.parse(params, { actor: null,
|
||||
modal: false,
|
||||
grabFocus: false,
|
||||
focus: null,
|
||||
onUngrab: null });
|
||||
|
||||
let focus = global.stage.key_focus;
|
||||
@ -151,16 +180,18 @@ const GrabHelper = new Lang.Class({
|
||||
|
||||
params.savedFocus = focus;
|
||||
|
||||
if (params.modal && !this._takeModalGrab())
|
||||
if (!this._takeModalGrab())
|
||||
return false;
|
||||
|
||||
if (params.grabFocus && !this._takeFocusGrab(hadFocus))
|
||||
return false;
|
||||
|
||||
if (hadFocus || params.grabFocus)
|
||||
_navigateActor(newFocus);
|
||||
|
||||
this._grabStack.push(params);
|
||||
|
||||
if (params.focus) {
|
||||
params.focus.grab_key_focus();
|
||||
} else if (newFocus && hadFocus) {
|
||||
if (!newFocus.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false))
|
||||
newFocus.grab_key_focus();
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
@ -170,8 +201,7 @@ const GrabHelper = new Lang.Class({
|
||||
if (!Main.pushModal(this._owner, this._modalParams))
|
||||
return false;
|
||||
|
||||
this._capturedEventId = global.stage.connect('captured-event', Lang.bind(this, this._onCapturedEvent));
|
||||
this._eventId = global.stage.connect('event', Lang.bind(this, this._onEvent));
|
||||
_pushGrabHelper(this);
|
||||
}
|
||||
|
||||
this._modalCount++;
|
||||
@ -183,75 +213,14 @@ const GrabHelper = new Lang.Class({
|
||||
if (this._modalCount > 0)
|
||||
return;
|
||||
|
||||
if (this._capturedEventId > 0) {
|
||||
global.stage.disconnect(this._capturedEventId);
|
||||
this._capturedEventId = 0;
|
||||
}
|
||||
_popGrabHelper(this);
|
||||
|
||||
if (this._eventId > 0) {
|
||||
global.stage.disconnect(this._eventId);
|
||||
this._eventId = 0;
|
||||
}
|
||||
this._ignoreRelease = false;
|
||||
|
||||
Main.popModal(this._owner);
|
||||
global.sync_pointer();
|
||||
},
|
||||
|
||||
_takeFocusGrab: function(hadFocus) {
|
||||
let firstGrab = (this._grabFocusCount == 0);
|
||||
if (firstGrab) {
|
||||
let metaDisplay = global.screen.get_display();
|
||||
|
||||
this._grabbedFromKeynav = hadFocus;
|
||||
this._preGrabInputMode = global.stage_input_mode;
|
||||
this._prevFocusedWindow = metaDisplay.focus_window;
|
||||
|
||||
if (this._preGrabInputMode == Shell.StageInputMode.NONREACTIVE ||
|
||||
this._preGrabInputMode == Shell.StageInputMode.NORMAL) {
|
||||
global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
|
||||
}
|
||||
|
||||
this._keyFocusNotifyId = global.stage.connect('notify::key-focus', Lang.bind(this, this._onKeyFocusChanged));
|
||||
this._focusWindowChangedId = metaDisplay.connect('notify::focus-window', Lang.bind(this, this._focusWindowChanged));
|
||||
}
|
||||
|
||||
this._grabFocusCount++;
|
||||
return true;
|
||||
},
|
||||
|
||||
_releaseFocusGrab: function() {
|
||||
this._grabFocusCount--;
|
||||
if (this._grabFocusCount > 0)
|
||||
return;
|
||||
|
||||
if (this._keyFocusNotifyId > 0) {
|
||||
global.stage.disconnect(this._keyFocusNotifyId);
|
||||
this._keyFocusNotifyId = 0;
|
||||
}
|
||||
|
||||
if (!this._focusWindowChanged > 0) {
|
||||
let metaDisplay = global.screen.get_display();
|
||||
metaDisplay.disconnect(this._focusWindowChangedId);
|
||||
this._focusWindowChangedId = 0;
|
||||
}
|
||||
|
||||
let prePopInputMode = global.stage_input_mode;
|
||||
|
||||
if (this._grabbedFromKeynav) {
|
||||
if (this._preGrabInputMode == Shell.StageInputMode.FOCUSED &&
|
||||
prePopInputMode != Shell.StageInputMode.FULLSCREEN)
|
||||
global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
|
||||
}
|
||||
|
||||
if (this._prevFocusedWindow) {
|
||||
let metaDisplay = global.screen.get_display();
|
||||
if (!metaDisplay.focus_window) {
|
||||
metaDisplay.set_input_focus_window(this._prevFocusedWindow,
|
||||
false, global.get_current_time());
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// ignoreRelease:
|
||||
//
|
||||
// Make sure that the next button release event evaluated by the
|
||||
@ -265,12 +234,17 @@ const GrabHelper = new Lang.Class({
|
||||
// ungrab:
|
||||
// @params: The parameters for the grab; see below.
|
||||
//
|
||||
// Pops an actor from the grab stack, potentially dropping the grab.
|
||||
// Pops @params.actor from the grab stack, potentially dropping
|
||||
// the grab. If the actor is not on the grab stack, this call is
|
||||
// ignored with no ill effects.
|
||||
//
|
||||
// If the actor that was popped from the grab stack was not the actor
|
||||
// That was passed in, this call is ignored.
|
||||
// If the actor is not at the top of the grab stack, grabs are
|
||||
// popped until the grabbed actor is at the top of the grab stack.
|
||||
// The onUngrab callback for every grab is called for every popped
|
||||
// grab with the parameter %false.
|
||||
ungrab: function(params) {
|
||||
params = Params.parse(params, { actor: this.currentGrab.actor });
|
||||
params = Params.parse(params, { actor: this.currentGrab.actor,
|
||||
isUser: false });
|
||||
|
||||
let grabStackIndex = this._findStackIndex(params.actor);
|
||||
if (grabStackIndex < 0)
|
||||
@ -288,35 +262,36 @@ const GrabHelper = new Lang.Class({
|
||||
let poppedGrab = poppedGrabs[i];
|
||||
|
||||
if (poppedGrab.onUngrab)
|
||||
poppedGrab.onUngrab();
|
||||
poppedGrab.onUngrab(params.isUser);
|
||||
|
||||
if (poppedGrab.modal)
|
||||
this._releaseModalGrab();
|
||||
|
||||
if (poppedGrab.grabFocus)
|
||||
this._releaseFocusGrab();
|
||||
this._releaseModalGrab();
|
||||
}
|
||||
|
||||
if (hadFocus) {
|
||||
let poppedGrab = poppedGrabs[0];
|
||||
_navigateActor(poppedGrab.savedFocus);
|
||||
if (poppedGrab.savedFocus)
|
||||
poppedGrab.savedFocus.grab_key_focus();
|
||||
}
|
||||
},
|
||||
|
||||
_onCapturedEvent: function(actor, event) {
|
||||
onCapturedEvent: function(event) {
|
||||
let type = event.type();
|
||||
|
||||
if (type == Clutter.EventType.KEY_PRESS &&
|
||||
event.get_key_symbol() == Clutter.KEY_Escape) {
|
||||
this.ungrab({ isUser: true });
|
||||
return true;
|
||||
}
|
||||
|
||||
let press = type == Clutter.EventType.BUTTON_PRESS;
|
||||
let release = type == Clutter.EventType.BUTTON_RELEASE;
|
||||
let button = press || release;
|
||||
|
||||
if (release && this._ignoreRelease) {
|
||||
this._ignoreRelease = false;
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!button && this._modalCount == 0)
|
||||
return false;
|
||||
|
||||
if (this._isWithinGrabbedActor(event.get_source()))
|
||||
return false;
|
||||
|
||||
@ -328,33 +303,11 @@ const GrabHelper = new Lang.Class({
|
||||
// which should be a release event.
|
||||
if (press)
|
||||
this._ignoreRelease = true;
|
||||
this.ungrab({ actor: this._grabStack[0].actor });
|
||||
}
|
||||
|
||||
return this._modalCount > 0;
|
||||
},
|
||||
|
||||
// We catch 'event' rather than 'key-press-event' so that we get
|
||||
// a chance to run before the overview's own Escape check
|
||||
_onEvent: function(actor, event) {
|
||||
if (event.type() == Clutter.EventType.KEY_PRESS &&
|
||||
event.get_key_symbol() == Clutter.KEY_Escape) {
|
||||
this.ungrab();
|
||||
let i = this._actorInGrabStack(event.get_source()) + 1;
|
||||
this.ungrab({ actor: this._grabStack[i].actor, isUser: true });
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
},
|
||||
|
||||
_onKeyFocusChanged: function() {
|
||||
let focus = global.stage.key_focus;
|
||||
if (!focus || !this._isWithinGrabbedActor(focus))
|
||||
this.ungrab();
|
||||
},
|
||||
|
||||
_focusWindowChanged: function() {
|
||||
let metaDisplay = global.screen.get_display();
|
||||
if (metaDisplay.focus_window != null)
|
||||
this.ungrab();
|
||||
}
|
||||
});
|
||||
|
@ -3,107 +3,149 @@
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const IBus = imports.gi.IBus;
|
||||
const Lang = imports.lang;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const BoxPointer = imports.ui.boxpointer;
|
||||
const Main = imports.ui.main;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
|
||||
const MAX_CANDIDATES_PER_PAGE = 16;
|
||||
|
||||
const CandidateArea = new Lang.Class({
|
||||
Name: 'CandidateArea',
|
||||
Extends: PopupMenu.PopupBaseMenuItem,
|
||||
|
||||
_init: function() {
|
||||
this.parent({ reactive: false });
|
||||
|
||||
// St.Table exhibits some sizing problems so let's go with a
|
||||
// clutter layout manager for now.
|
||||
this._table = new Clutter.Actor();
|
||||
this.addActor(this._table);
|
||||
|
||||
this._tableLayout = new Clutter.TableLayout();
|
||||
this._table.set_layout_manager(this._tableLayout);
|
||||
|
||||
this._indexLabels = [];
|
||||
this._candidateLabels = [];
|
||||
this.actor = new St.BoxLayout({ vertical: true,
|
||||
visible: false });
|
||||
this._candidateBoxes = [];
|
||||
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
|
||||
this._indexLabels.push(new St.Label({ style_class: 'candidate-index' }));
|
||||
this._candidateLabels.push(new St.Label({ style_class: 'candidate-label' }));
|
||||
let box = new St.BoxLayout({ style_class: 'candidate-box',
|
||||
reactive: true,
|
||||
track_hover: true });
|
||||
box._indexLabel = new St.Label({ style_class: 'candidate-index' });
|
||||
box._candidateLabel = new St.Label({ style_class: 'candidate-label' });
|
||||
box.add(box._indexLabel, { y_fill: false });
|
||||
box.add(box._candidateLabel, { y_fill: false });
|
||||
this._candidateBoxes.push(box);
|
||||
this.actor.add(box);
|
||||
|
||||
let j = i;
|
||||
box.connect('button-release-event', Lang.bind(this, function(actor, event) {
|
||||
this.emit('candidate-clicked', j, event.get_button(), event.get_state());
|
||||
}));
|
||||
}
|
||||
|
||||
this._buttonBox = new St.BoxLayout({ style_class: 'candidate-page-button-box' });
|
||||
|
||||
this._previousButton = new St.Button({ style_class: 'candidate-page-button candidate-page-button-previous' });
|
||||
this._previousButton.child = new St.Icon({ style_class: 'candidate-page-button-icon' });
|
||||
this._buttonBox.add(this._previousButton, { expand: true });
|
||||
|
||||
this._nextButton = new St.Button({ style_class: 'candidate-page-button candidate-page-button-next' });
|
||||
this._nextButton.child = new St.Icon({ style_class: 'candidate-page-button-icon' });
|
||||
this._buttonBox.add(this._nextButton, { expand: true });
|
||||
|
||||
this.actor.add(this._buttonBox);
|
||||
|
||||
this._previousButton.connect('clicked', Lang.bind(this, function() {
|
||||
this.emit('previous-page');
|
||||
}));
|
||||
this._nextButton.connect('clicked', Lang.bind(this, function() {
|
||||
this.emit('next-page');
|
||||
}));
|
||||
|
||||
this._orientation = -1;
|
||||
this._cursorPosition = 0;
|
||||
},
|
||||
|
||||
_setOrientation: function(orientation) {
|
||||
setOrientation: function(orientation) {
|
||||
if (this._orientation == orientation)
|
||||
return;
|
||||
|
||||
this._orientation = orientation;
|
||||
|
||||
this._table.remove_all_children();
|
||||
|
||||
if (this._orientation == IBus.Orientation.HORIZONTAL)
|
||||
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
|
||||
this._tableLayout.pack(this._indexLabels[i], i*2, 0);
|
||||
this._tableLayout.pack(this._candidateLabels[i], i*2 + 1, 0);
|
||||
}
|
||||
else // VERTICAL || SYSTEM
|
||||
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
|
||||
this._tableLayout.pack(this._indexLabels[i], 0, i);
|
||||
this._tableLayout.pack(this._candidateLabels[i], 1, i);
|
||||
}
|
||||
if (this._orientation == IBus.Orientation.HORIZONTAL) {
|
||||
this.actor.vertical = false;
|
||||
this.actor.remove_style_class_name('vertical');
|
||||
this.actor.add_style_class_name('horizontal');
|
||||
this._previousButton.child.icon_name = 'go-previous-symbolic';
|
||||
this._nextButton.child.icon_name = 'go-next-symbolic';
|
||||
} else { // VERTICAL || SYSTEM
|
||||
this.actor.vertical = true;
|
||||
this.actor.add_style_class_name('vertical');
|
||||
this.actor.remove_style_class_name('horizontal');
|
||||
this._previousButton.child.icon_name = 'go-up-symbolic';
|
||||
this._nextButton.child.icon_name = 'go-down-symbolic';
|
||||
}
|
||||
},
|
||||
|
||||
setCandidates: function(indexes, candidates, orientation, cursorPosition, cursorVisible) {
|
||||
this._setOrientation(orientation);
|
||||
|
||||
setCandidates: function(indexes, candidates, cursorPosition, cursorVisible) {
|
||||
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
|
||||
let visible = i < candidates.length;
|
||||
this._indexLabels[i].visible = visible;
|
||||
this._candidateLabels[i].visible = visible;
|
||||
let box = this._candidateBoxes[i];
|
||||
box.visible = visible;
|
||||
|
||||
if (!visible)
|
||||
continue;
|
||||
|
||||
this._indexLabels[i].text = ((indexes && indexes[i]) ? indexes[i] : '%x.'.format(i + 1));
|
||||
this._candidateLabels[i].text = candidates[i];
|
||||
box._indexLabel.text = ((indexes && indexes[i]) ? indexes[i] : '%x'.format(i + 1));
|
||||
box._candidateLabel.text = candidates[i];
|
||||
}
|
||||
|
||||
this._candidateLabels[this._cursorPosition].remove_style_pseudo_class('selected');
|
||||
this._candidateBoxes[this._cursorPosition].remove_style_pseudo_class('selected');
|
||||
this._cursorPosition = cursorPosition;
|
||||
if (cursorVisible)
|
||||
this._candidateLabels[cursorPosition].add_style_pseudo_class('selected');
|
||||
this._candidateBoxes[cursorPosition].add_style_pseudo_class('selected');
|
||||
},
|
||||
|
||||
updateButtons: function(wrapsAround, page, nPages) {
|
||||
if (nPages < 2) {
|
||||
this._buttonBox.hide();
|
||||
return;
|
||||
}
|
||||
this._buttonBox.show();
|
||||
this._previousButton.reactive = wrapsAround || page > 0;
|
||||
this._nextButton.reactive = wrapsAround || page < nPages - 1;
|
||||
},
|
||||
});
|
||||
Signals.addSignalMethods(CandidateArea.prototype);
|
||||
|
||||
const CandidatePopup = new Lang.Class({
|
||||
Name: 'CandidatePopup',
|
||||
Extends: PopupMenu.PopupMenu,
|
||||
|
||||
_init: function() {
|
||||
this._cursor = new St.Bin({ opacity: 0 });
|
||||
Main.uiGroup.add_actor(this._cursor);
|
||||
|
||||
this.parent(this._cursor, 0, St.Side.TOP);
|
||||
this.actor.hide();
|
||||
Main.uiGroup.add_actor(this.actor);
|
||||
this._boxPointer = new BoxPointer.BoxPointer(St.Side.TOP);
|
||||
this._boxPointer.actor.visible = false;
|
||||
this._boxPointer.actor.style_class = 'candidate-popup-boxpointer';
|
||||
Main.layoutManager.addChrome(this._boxPointer.actor);
|
||||
|
||||
this._preeditTextItem = new PopupMenu.PopupMenuItem('', { reactive: false });
|
||||
this._preeditTextItem.actor.hide();
|
||||
this.addMenuItem(this._preeditTextItem);
|
||||
let box = new St.BoxLayout({ style_class: 'candidate-popup-content',
|
||||
vertical: true });
|
||||
this._boxPointer.bin.set_child(box);
|
||||
|
||||
this._auxTextItem = new PopupMenu.PopupMenuItem('', { reactive: false });
|
||||
this._auxTextItem.actor.hide();
|
||||
this.addMenuItem(this._auxTextItem);
|
||||
this._preeditText = new St.Label({ style_class: 'candidate-popup-text',
|
||||
visible: false });
|
||||
box.add(this._preeditText);
|
||||
|
||||
this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this._auxText = new St.Label({ style_class: 'candidate-popup-text',
|
||||
visible: false });
|
||||
box.add(this._auxText);
|
||||
|
||||
this._lookupTableItem = new CandidateArea();
|
||||
this._lookupTableItem.actor.hide();
|
||||
this.addMenuItem(this._lookupTableItem);
|
||||
this._candidateArea = new CandidateArea();
|
||||
box.add(this._candidateArea.actor);
|
||||
|
||||
this._candidateArea.connect('previous-page', Lang.bind(this, function() {
|
||||
this._panelService.page_up();
|
||||
}));
|
||||
this._candidateArea.connect('next-page', Lang.bind(this, function() {
|
||||
this._panelService.page_down();
|
||||
}));
|
||||
this._candidateArea.connect('candidate-clicked', Lang.bind(this, function(ca, index, button, state) {
|
||||
this._panelService.candidate_clicked(index, button, state);
|
||||
}));
|
||||
|
||||
this._panelService = null;
|
||||
},
|
||||
@ -117,66 +159,61 @@ const CandidatePopup = new Lang.Class({
|
||||
Lang.bind(this, function(ps, x, y, w, h) {
|
||||
this._cursor.set_position(x, y);
|
||||
this._cursor.set_size(w, h);
|
||||
if (this._boxPointer.actor.visible)
|
||||
this._boxPointer.setPosition(this._cursor, 0);
|
||||
}));
|
||||
panelService.connect('update-preedit-text',
|
||||
Lang.bind(this, function(ps, text, cursorPosition, visible) {
|
||||
if (visible)
|
||||
this._preeditTextItem.actor.show();
|
||||
else
|
||||
this._preeditTextItem.actor.hide();
|
||||
this._preeditText.visible = visible;
|
||||
this._updateVisibility();
|
||||
|
||||
this._preeditTextItem.actor.label_actor.text = text.get_text();
|
||||
this._preeditText.text = text.get_text();
|
||||
|
||||
let attrs = text.get_attributes();
|
||||
if (attrs)
|
||||
this._setTextAttributes(this._preeditTextItem.actor.label_actor.clutter_text,
|
||||
this._setTextAttributes(this._preeditText.clutter_text,
|
||||
attrs);
|
||||
}));
|
||||
panelService.connect('show-preedit-text',
|
||||
Lang.bind(this, function(ps) {
|
||||
this._preeditTextItem.actor.show();
|
||||
this._preeditText.show();
|
||||
this._updateVisibility();
|
||||
}));
|
||||
panelService.connect('hide-preedit-text',
|
||||
Lang.bind(this, function(ps) {
|
||||
this._preeditTextItem.actor.hide();
|
||||
this._preeditText.hide();
|
||||
this._updateVisibility();
|
||||
}));
|
||||
panelService.connect('update-auxiliary-text',
|
||||
Lang.bind(this, function(ps, text, visible) {
|
||||
if (visible)
|
||||
this._auxTextItem.actor.show();
|
||||
else
|
||||
this._auxTextItem.actor.hide();
|
||||
this._auxText.visible = visible;
|
||||
this._updateVisibility();
|
||||
|
||||
this._auxTextItem.actor.label_actor.text = text.get_text();
|
||||
this._auxText.text = text.get_text();
|
||||
}));
|
||||
panelService.connect('show-auxiliary-text',
|
||||
Lang.bind(this, function(ps) {
|
||||
this._auxTextItem.actor.show();
|
||||
this._auxText.show();
|
||||
this._updateVisibility();
|
||||
}));
|
||||
panelService.connect('hide-auxiliary-text',
|
||||
Lang.bind(this, function(ps) {
|
||||
this._auxTextItem.actor.hide();
|
||||
this._auxText.hide();
|
||||
this._updateVisibility();
|
||||
}));
|
||||
panelService.connect('update-lookup-table',
|
||||
Lang.bind(this, function(ps, lookupTable, visible) {
|
||||
if (visible)
|
||||
this._lookupTableItem.actor.show();
|
||||
else
|
||||
this._lookupTableItem.actor.hide();
|
||||
this._candidateArea.actor.visible = visible;
|
||||
this._updateVisibility();
|
||||
|
||||
let nCandidates = lookupTable.get_number_of_candidates();
|
||||
let cursorPos = lookupTable.get_cursor_pos();
|
||||
let pageSize = lookupTable.get_page_size();
|
||||
let nPages = Math.ceil(nCandidates / pageSize);
|
||||
let page = ((cursorPos == 0) ? 0 : Math.floor(cursorPos / pageSize));
|
||||
let startIndex = page * pageSize;
|
||||
let endIndex = Math.min((page + 1) * pageSize,
|
||||
lookupTable.get_number_of_candidates());
|
||||
let endIndex = Math.min((page + 1) * pageSize, nCandidates);
|
||||
|
||||
let indexes = [];
|
||||
let indexLabel;
|
||||
for (let i = 0; indexLabel = lookupTable.get_label(i); ++i)
|
||||
@ -186,37 +223,41 @@ const CandidatePopup = new Lang.Class({
|
||||
for (let i = startIndex; i < endIndex; ++i)
|
||||
candidates.push(lookupTable.get_candidate(i).get_text());
|
||||
|
||||
this._lookupTableItem.setCandidates(indexes,
|
||||
candidates,
|
||||
lookupTable.get_orientation(),
|
||||
cursorPos % pageSize,
|
||||
lookupTable.is_cursor_visible());
|
||||
this._candidateArea.setCandidates(indexes,
|
||||
candidates,
|
||||
cursorPos % pageSize,
|
||||
lookupTable.is_cursor_visible());
|
||||
this._candidateArea.setOrientation(lookupTable.get_orientation());
|
||||
this._candidateArea.updateButtons(lookupTable.is_round(), page, nPages);
|
||||
}));
|
||||
panelService.connect('show-lookup-table',
|
||||
Lang.bind(this, function(ps) {
|
||||
this._lookupTableItem.actor.show();
|
||||
this._candidateArea.actor.show();
|
||||
this._updateVisibility();
|
||||
}));
|
||||
panelService.connect('hide-lookup-table',
|
||||
Lang.bind(this, function(ps) {
|
||||
this._lookupTableItem.actor.hide();
|
||||
this._candidateArea.actor.hide();
|
||||
this._updateVisibility();
|
||||
}));
|
||||
panelService.connect('focus-out',
|
||||
Lang.bind(this, function(ps) {
|
||||
this.close(BoxPointer.PopupAnimation.NONE);
|
||||
this._boxPointer.hide(BoxPointer.PopupAnimation.NONE);
|
||||
}));
|
||||
},
|
||||
|
||||
_updateVisibility: function() {
|
||||
let isVisible = (this._preeditTextItem.actor.visible ||
|
||||
this._auxTextItem.actor.visible ||
|
||||
this._lookupTableItem.actor.visible);
|
||||
let isVisible = (this._preeditText.visible ||
|
||||
this._auxText.visible ||
|
||||
this._candidateArea.actor.visible);
|
||||
|
||||
if (isVisible)
|
||||
this.open(BoxPointer.PopupAnimation.NONE);
|
||||
else
|
||||
this.close(BoxPointer.PopupAnimation.NONE);
|
||||
if (isVisible) {
|
||||
this._boxPointer.setPosition(this._cursor, 0);
|
||||
this._boxPointer.show(BoxPointer.PopupAnimation.NONE);
|
||||
this._boxPointer.actor.raise_top();
|
||||
} else {
|
||||
this._boxPointer.hide(BoxPointer.PopupAnimation.NONE);
|
||||
}
|
||||
},
|
||||
|
||||
_setTextAttributes: function(clutterText, ibusAttrList) {
|
||||
|
@ -1,14 +1,20 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Lang = imports.lang;
|
||||
const Params = imports.misc.params;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
const ICON_SIZE = 48;
|
||||
const ICON_SIZE = 96;
|
||||
const MIN_ICON_SIZE = 16;
|
||||
|
||||
const EXTRA_SPACE_ANIMATION_TIME = 0.25;
|
||||
|
||||
const BaseIcon = new Lang.Class({
|
||||
Name: 'BaseIcon',
|
||||
@ -17,12 +23,19 @@ const BaseIcon = new Lang.Class({
|
||||
params = Params.parse(params, { createIcon: null,
|
||||
setSizeManually: false,
|
||||
showLabel: true });
|
||||
this.actor = new St.Bin({ style_class: 'overview-icon',
|
||||
|
||||
let styleClass = 'overview-icon';
|
||||
if (params.showLabel)
|
||||
styleClass += ' overview-icon-with-label';
|
||||
|
||||
this.actor = new St.Bin({ style_class: styleClass,
|
||||
x_fill: true,
|
||||
y_fill: true });
|
||||
this.actor._delegate = this;
|
||||
this.actor.connect('style-changed',
|
||||
Lang.bind(this, this._onStyleChanged));
|
||||
this.actor.connect('destroy',
|
||||
Lang.bind(this, this._onDestroy));
|
||||
|
||||
this._spacing = 0;
|
||||
|
||||
@ -52,6 +65,9 @@ const BaseIcon = new Lang.Class({
|
||||
this._setSizeManually = params.setSizeManually;
|
||||
|
||||
this.icon = null;
|
||||
|
||||
let cache = St.TextureCache.get_default();
|
||||
this._iconThemeChangedId = cache.connect('icon-theme-changed', Lang.bind(this, this._onIconThemeChanged));
|
||||
},
|
||||
|
||||
_allocate: function(actor, box, flags) {
|
||||
@ -146,7 +162,22 @@ const BaseIcon = new Lang.Class({
|
||||
size = found ? len : ICON_SIZE;
|
||||
}
|
||||
|
||||
if (this.iconSize == size && this._iconBin.child)
|
||||
return;
|
||||
|
||||
this._createIconTexture(size);
|
||||
},
|
||||
|
||||
_onDestroy: function() {
|
||||
if (this._iconThemeChangedId > 0) {
|
||||
let cache = St.TextureCache.get_default();
|
||||
cache.disconnect(this._iconThemeChangedId);
|
||||
this._iconThemeChangedId = 0;
|
||||
}
|
||||
},
|
||||
|
||||
_onIconThemeChanged: function() {
|
||||
this._createIconTexture(this.iconSize);
|
||||
}
|
||||
});
|
||||
|
||||
@ -156,16 +187,31 @@ const IconGrid = new Lang.Class({
|
||||
_init: function(params) {
|
||||
params = Params.parse(params, { rowLimit: null,
|
||||
columnLimit: null,
|
||||
xAlign: St.Align.MIDDLE });
|
||||
minRows: 1,
|
||||
minColumns: 1,
|
||||
fillParent: false,
|
||||
xAlign: St.Align.MIDDLE,
|
||||
padWithSpacing: false });
|
||||
this._rowLimit = params.rowLimit;
|
||||
this._colLimit = params.columnLimit;
|
||||
this._minRows = params.minRows;
|
||||
this._minColumns = params.minColumns;
|
||||
this._xAlign = params.xAlign;
|
||||
this._fillParent = params.fillParent;
|
||||
this._padWithSpacing = params.padWithSpacing;
|
||||
|
||||
this.topPadding = 0;
|
||||
this.bottomPadding = 0;
|
||||
this.rightPadding = 0;
|
||||
this.leftPadding = 0;
|
||||
|
||||
this.actor = new St.BoxLayout({ style_class: 'icon-grid',
|
||||
vertical: true });
|
||||
this._items = [];
|
||||
// Pulled from CSS, but hardcode some defaults here
|
||||
this._spacing = 0;
|
||||
this._hItemSize = this._vItemSize = ICON_SIZE;
|
||||
this._fixedHItemSize = this._fixedVItemSize = undefined;
|
||||
this._grid = new Shell.GenericContainer();
|
||||
this.actor.add(this._grid, { expand: true, y_align: St.Align.START });
|
||||
this.actor.connect('style-changed', Lang.bind(this, this._onStyleChanged));
|
||||
@ -176,16 +222,21 @@ const IconGrid = new Lang.Class({
|
||||
},
|
||||
|
||||
_getPreferredWidth: function (grid, forHeight, alloc) {
|
||||
let children = this._grid.get_children();
|
||||
if (this._fillParent)
|
||||
// Ignore all size requests of children and request a size of 0;
|
||||
// later we'll allocate as many children as fit the parent
|
||||
return;
|
||||
|
||||
let nChildren = this._grid.get_n_children();
|
||||
let nColumns = this._colLimit ? Math.min(this._colLimit,
|
||||
children.length)
|
||||
: children.length;
|
||||
let totalSpacing = Math.max(0, nColumns - 1) * this._spacing;
|
||||
nChildren)
|
||||
: nChildren;
|
||||
let totalSpacing = Math.max(0, nColumns - 1) * this._getSpacing();
|
||||
// Kind of a lie, but not really an issue right now. If
|
||||
// we wanted to support some sort of hidden/overflow that would
|
||||
// need higher level design
|
||||
alloc.min_size = this._hItemSize;
|
||||
alloc.natural_size = nColumns * this._hItemSize + totalSpacing;
|
||||
alloc.min_size = this._getHItemSize() + this.leftPadding + this.rightPadding;
|
||||
alloc.natural_size = nColumns * this._getHItemSize() + totalSpacing + this.leftPadding + this.rightPadding;
|
||||
},
|
||||
|
||||
_getVisibleChildren: function() {
|
||||
@ -197,8 +248,18 @@ const IconGrid = new Lang.Class({
|
||||
},
|
||||
|
||||
_getPreferredHeight: function (grid, forWidth, alloc) {
|
||||
if (this._fillParent)
|
||||
// Ignore all size requests of children and request a size of 0;
|
||||
// later we'll allocate as many children as fit the parent
|
||||
return;
|
||||
|
||||
let children = this._getVisibleChildren();
|
||||
let [nColumns, usedWidth] = this._computeLayout(forWidth);
|
||||
let nColumns;
|
||||
if (forWidth < 0)
|
||||
nColumns = children.length;
|
||||
else
|
||||
[nColumns, ] = this._computeLayout(forWidth);
|
||||
|
||||
let nRows;
|
||||
if (nColumns > 0)
|
||||
nRows = Math.ceil(children.length / nColumns);
|
||||
@ -206,57 +267,47 @@ const IconGrid = new Lang.Class({
|
||||
nRows = 0;
|
||||
if (this._rowLimit)
|
||||
nRows = Math.min(nRows, this._rowLimit);
|
||||
let totalSpacing = Math.max(0, nRows - 1) * this._spacing;
|
||||
let height = nRows * this._vItemSize + totalSpacing;
|
||||
let totalSpacing = Math.max(0, nRows - 1) * this._getSpacing();
|
||||
let height = nRows * this._getVItemSize() + totalSpacing + this.topPadding + this.bottomPadding;
|
||||
alloc.min_size = height;
|
||||
alloc.natural_size = height;
|
||||
},
|
||||
|
||||
_allocate: function (grid, box, flags) {
|
||||
if (this._fillParent) {
|
||||
// Reset the passed in box to fill the parent
|
||||
let parentBox = this.actor.get_parent().allocation;
|
||||
let gridBox = this.actor.get_theme_node().get_content_box(parentBox);
|
||||
box = this._grid.get_theme_node().get_content_box(gridBox);
|
||||
}
|
||||
|
||||
let children = this._getVisibleChildren();
|
||||
let availWidth = box.x2 - box.x1;
|
||||
let availHeight = box.y2 - box.y1;
|
||||
|
||||
let spacing = this._getSpacing();
|
||||
let [nColumns, usedWidth] = this._computeLayout(availWidth);
|
||||
|
||||
let leftPadding;
|
||||
let leftEmptySpace;
|
||||
switch(this._xAlign) {
|
||||
case St.Align.START:
|
||||
leftPadding = 0;
|
||||
leftEmptySpace = 0;
|
||||
break;
|
||||
case St.Align.MIDDLE:
|
||||
leftPadding = Math.floor((availWidth - usedWidth) / 2);
|
||||
leftEmptySpace = Math.floor((availWidth - usedWidth) / 2);
|
||||
break;
|
||||
case St.Align.END:
|
||||
leftPadding = availWidth - usedWidth;
|
||||
leftEmptySpace = availWidth - usedWidth;
|
||||
}
|
||||
|
||||
let x = box.x1 + leftPadding;
|
||||
let y = box.y1;
|
||||
let x = box.x1 + leftEmptySpace + this.leftPadding;
|
||||
let y = box.y1 + this.topPadding;
|
||||
let columnIndex = 0;
|
||||
let rowIndex = 0;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight]
|
||||
= children[i].get_preferred_size();
|
||||
let childBox = this._calculateChildBox(children[i], x, y, box);
|
||||
|
||||
/* Center the item in its allocation horizontally */
|
||||
let width = Math.min(this._hItemSize, childNaturalWidth);
|
||||
let childXSpacing = Math.max(0, width - childNaturalWidth) / 2;
|
||||
let height = Math.min(this._vItemSize, childNaturalHeight);
|
||||
let childYSpacing = Math.max(0, height - childNaturalHeight) / 2;
|
||||
|
||||
let childBox = new Clutter.ActorBox();
|
||||
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
|
||||
let _x = box.x2 - (x + width);
|
||||
childBox.x1 = Math.floor(_x - childXSpacing);
|
||||
} else {
|
||||
childBox.x1 = Math.floor(x + childXSpacing);
|
||||
}
|
||||
childBox.y1 = Math.floor(y + childYSpacing);
|
||||
childBox.x2 = childBox.x1 + width;
|
||||
childBox.y2 = childBox.y1 + height;
|
||||
|
||||
if (this._rowLimit && rowIndex >= this._rowLimit) {
|
||||
if (this._rowLimit && rowIndex >= this._rowLimit ||
|
||||
this._fillParent && childBox.y2 > availHeight - this.bottomPadding) {
|
||||
this._grid.set_skip_paint(children[i], true);
|
||||
} else {
|
||||
children[i].allocate(childBox, flags);
|
||||
@ -270,15 +321,38 @@ const IconGrid = new Lang.Class({
|
||||
}
|
||||
|
||||
if (columnIndex == 0) {
|
||||
y += this._vItemSize + this._spacing;
|
||||
x = box.x1 + leftPadding;
|
||||
y += this._getVItemSize() + spacing;
|
||||
x = box.x1 + leftEmptySpace + this.leftPadding;
|
||||
} else {
|
||||
x += this._hItemSize + this._spacing;
|
||||
x += this._getHItemSize() + spacing;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
childrenInRow: function(rowWidth) {
|
||||
_calculateChildBox: function(child, x, y, box) {
|
||||
let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight] =
|
||||
child.get_preferred_size();
|
||||
|
||||
/* Center the item in its allocation horizontally */
|
||||
let width = Math.min(this._getHItemSize(), childNaturalWidth);
|
||||
let childXSpacing = Math.max(0, width - childNaturalWidth) / 2;
|
||||
let height = Math.min(this._getVItemSize(), childNaturalHeight);
|
||||
let childYSpacing = Math.max(0, height - childNaturalHeight) / 2;
|
||||
|
||||
let childBox = new Clutter.ActorBox();
|
||||
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
|
||||
let _x = box.x2 - (x + width);
|
||||
childBox.x1 = Math.floor(_x - childXSpacing);
|
||||
} else {
|
||||
childBox.x1 = Math.floor(x + childXSpacing);
|
||||
}
|
||||
childBox.y1 = Math.floor(y + childYSpacing);
|
||||
childBox.x2 = childBox.x1 + width;
|
||||
childBox.y2 = childBox.y1 + height;
|
||||
return childBox;
|
||||
},
|
||||
|
||||
columnsForWidth: function(rowWidth) {
|
||||
return this._computeLayout(rowWidth)[0];
|
||||
},
|
||||
|
||||
@ -288,15 +362,17 @@ const IconGrid = new Lang.Class({
|
||||
|
||||
_computeLayout: function (forWidth) {
|
||||
let nColumns = 0;
|
||||
let usedWidth = 0;
|
||||
let usedWidth = this.leftPadding + this.rightPadding;
|
||||
let spacing = this._getSpacing();
|
||||
|
||||
while ((this._colLimit == null || nColumns < this._colLimit) &&
|
||||
(usedWidth + this._hItemSize <= forWidth)) {
|
||||
usedWidth += this._hItemSize + this._spacing;
|
||||
(usedWidth + this._getHItemSize() <= forWidth)) {
|
||||
usedWidth += this._getHItemSize() + spacing;
|
||||
nColumns += 1;
|
||||
}
|
||||
|
||||
if (nColumns > 0)
|
||||
usedWidth -= this._spacing;
|
||||
usedWidth -= spacing;
|
||||
|
||||
return [nColumns, usedWidth];
|
||||
},
|
||||
@ -309,15 +385,49 @@ const IconGrid = new Lang.Class({
|
||||
this._grid.queue_relayout();
|
||||
},
|
||||
|
||||
nRows: function(forWidth) {
|
||||
let children = this._getVisibleChildren();
|
||||
let nColumns = (forWidth < 0) ? children.length : this._computeLayout(forWidth)[0];
|
||||
let nRows = (nColumns > 0) ? Math.ceil(children.length / nColumns) : 0;
|
||||
if (this._rowLimit)
|
||||
nRows = Math.min(nRows, this._rowLimit);
|
||||
return nRows;
|
||||
},
|
||||
|
||||
rowsForHeight: function(forHeight) {
|
||||
return Math.floor((forHeight - (this.topPadding + this.bottomPadding) + this._getSpacing()) / (this._getVItemSize() + this._getSpacing()));
|
||||
},
|
||||
|
||||
usedHeightForNRows: function(nRows) {
|
||||
return (this._getVItemSize() + this._getSpacing()) * nRows - this._getSpacing() + this.topPadding + this.bottomPadding;
|
||||
},
|
||||
|
||||
usedWidth: function(forWidth) {
|
||||
return this.usedWidthForNColumns(this.columnsForWidth(forWidth));
|
||||
},
|
||||
|
||||
usedWidthForNColumns: function(columns) {
|
||||
let usedWidth = columns * (this._getHItemSize() + this._getSpacing());
|
||||
usedWidth -= this._getSpacing();
|
||||
return usedWidth + this.leftPadding + this.rightPadding;
|
||||
},
|
||||
|
||||
removeAll: function() {
|
||||
this._items = [];
|
||||
this._grid.destroy_all_children();
|
||||
},
|
||||
|
||||
addItem: function(actor, index) {
|
||||
addItem: function(item, index) {
|
||||
if (!item.icon || !item.icon instanceof BaseIcon) {
|
||||
log('Only items with a BaseIcon icon property can be added to IconGrid');
|
||||
return;
|
||||
}
|
||||
|
||||
this._items.push(item);
|
||||
if (index !== undefined)
|
||||
this._grid.insert_child_at_index(actor, index);
|
||||
this._grid.insert_child_at_index(item.actor, index);
|
||||
else
|
||||
this._grid.add_actor(actor);
|
||||
this._grid.add_actor(item.actor);
|
||||
},
|
||||
|
||||
getItemAtIndex: function(index) {
|
||||
@ -326,5 +436,311 @@ const IconGrid = new Lang.Class({
|
||||
|
||||
visibleItemsCount: function() {
|
||||
return this._grid.get_n_children() - this._grid.get_n_skip_paint();
|
||||
},
|
||||
|
||||
setSpacing: function(spacing) {
|
||||
this._fixedSpacing = spacing;
|
||||
},
|
||||
|
||||
_getSpacing: function() {
|
||||
return this._fixedSpacing ? this._fixedSpacing : this._spacing;
|
||||
},
|
||||
|
||||
_getHItemSize: function() {
|
||||
return this._fixedHItemSize ? this._fixedHItemSize : this._hItemSize;
|
||||
},
|
||||
|
||||
_getVItemSize: function() {
|
||||
return this._fixedVItemSize ? this._fixedVItemSize : this._vItemSize;
|
||||
},
|
||||
|
||||
_updateSpacingForSize: function(availWidth, availHeight) {
|
||||
let maxEmptyVArea = availHeight - this._minRows * this._getVItemSize();
|
||||
let maxEmptyHArea = availWidth - this._minColumns * this._getHItemSize();
|
||||
let maxHSpacing, maxVSpacing;
|
||||
|
||||
if (this._padWithSpacing) {
|
||||
// minRows + 1 because we want to put spacing before the first row, so it is like we have one more row
|
||||
// to divide the empty space
|
||||
maxVSpacing = Math.floor(maxEmptyVArea / (this._minRows +1));
|
||||
maxHSpacing = Math.floor(maxEmptyHArea / (this._minColumns +1));
|
||||
} else {
|
||||
if (this._minRows <= 1)
|
||||
maxVSpacing = maxEmptyVArea;
|
||||
else
|
||||
maxVSpacing = Math.floor(maxEmptyVArea / (this._minRows - 1));
|
||||
|
||||
if (this._minColumns <= 1)
|
||||
maxHSpacing = maxEmptyHArea;
|
||||
else
|
||||
maxHSpacing = Math.floor(maxEmptyHArea / (this._minColumns - 1));
|
||||
}
|
||||
|
||||
let maxSpacing = Math.min(maxHSpacing, maxVSpacing);
|
||||
// Limit spacing to the item size
|
||||
maxSpacing = Math.min(maxSpacing, Math.min(this._getVItemSize(), this._getHItemSize()));
|
||||
// The minimum spacing, regardless of whether it satisfies the row/columng minima,
|
||||
// is the spacing we get from CSS.
|
||||
let spacing = Math.max(this._spacing, maxSpacing);
|
||||
this.setSpacing(spacing);
|
||||
if (this._padWithSpacing)
|
||||
this.topPadding = this.rightPadding = this.bottomPadding = this.leftPadding = spacing;
|
||||
},
|
||||
|
||||
/**
|
||||
* This function must to be called before iconGrid allocation,
|
||||
* to know how much spacing can the grid has
|
||||
*/
|
||||
adaptToSize: function(availWidth, availHeight) {
|
||||
this._fixedHItemSize = this._hItemSize;
|
||||
this._fixedVItemSize = this._vItemSize;
|
||||
this._updateSpacingForSize(availWidth, availHeight);
|
||||
let spacing = this._getSpacing();
|
||||
|
||||
if (this.columnsForWidth(availWidth) < this._minColumns || this.rowsForHeight(availHeight) < this._minRows) {
|
||||
let neededWidth = this.usedWidthForNColumns(this._minColumns) - availWidth ;
|
||||
let neededHeight = this.usedHeightForNRows(this._minRows) - availHeight ;
|
||||
|
||||
let neededSpacePerItem = (neededWidth > neededHeight) ? Math.ceil(neededWidth / this._minColumns)
|
||||
: Math.ceil(neededHeight / this._minRows);
|
||||
this._fixedHItemSize = Math.max(this._hItemSize - neededSpacePerItem, MIN_ICON_SIZE);
|
||||
this._fixedVItemSize = Math.max(this._vItemSize - neededSpacePerItem, MIN_ICON_SIZE);
|
||||
|
||||
if (this._fixedHItemSize < MIN_ICON_SIZE)
|
||||
this._fixedHItemSize = MIN_ICON_SIZE;
|
||||
if (this._fixedVItemSize < MIN_ICON_SIZE)
|
||||
this._fixedVItemSize = MIN_ICON_SIZE;
|
||||
|
||||
this._updateSpacingForSize(availWidth, availHeight);
|
||||
}
|
||||
let scale = Math.min(this._fixedHItemSize, this._fixedVItemSize) / Math.max(this._hItemSize, this._vItemSize);
|
||||
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { this._updateChildrenScale(scale); }));
|
||||
},
|
||||
|
||||
// Note that this is ICON_SIZE as used by BaseIcon, not elsewhere in IconGrid; it's a bit messed up
|
||||
_updateChildrenScale: function(scale) {
|
||||
for (let i in this._items) {
|
||||
let newIconSize = Math.floor(ICON_SIZE * scale);
|
||||
this._items[i].icon.setIconSize(newIconSize);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const PaginatedIconGrid = new Lang.Class({
|
||||
Name: 'PaginatedIconGrid',
|
||||
Extends: IconGrid,
|
||||
|
||||
_init: function(params) {
|
||||
this.parent(params);
|
||||
this._nPages = 0;
|
||||
this._rowsPerPage = 0;
|
||||
this._spaceBetweenPages = 0;
|
||||
this._childrenPerPage = 0;
|
||||
},
|
||||
|
||||
_getPreferredHeight: function (grid, forWidth, alloc) {
|
||||
alloc.min_size = (this._availableHeightPerPageForItems() + this.bottomPadding + this.topPadding) * this._nPages + this._spaceBetweenPages * this._nPages;
|
||||
alloc.natural_size = (this._availableHeightPerPageForItems() + this.bottomPadding + this.topPadding) * this._nPages + this._spaceBetweenPages * this._nPages;
|
||||
},
|
||||
|
||||
_allocate: function (grid, box, flags) {
|
||||
if (this._childrenPerPage == 0)
|
||||
log('computePages() must be called before allocate(); pagination will not work.');
|
||||
|
||||
if (this._fillParent) {
|
||||
// Reset the passed in box to fill the parent
|
||||
let parentBox = this.actor.get_parent().allocation;
|
||||
let gridBox = this.actor.get_theme_node().get_content_box(parentBox);
|
||||
box = this._grid.get_theme_node().get_content_box(gridBox);
|
||||
}
|
||||
let children = this._getVisibleChildren();
|
||||
let availWidth = box.x2 - box.x1;
|
||||
let availHeight = box.y2 - box.y1;
|
||||
let spacing = this._getSpacing();
|
||||
let [nColumns, usedWidth] = this._computeLayout(availWidth);
|
||||
|
||||
let leftEmptySpace;
|
||||
switch(this._xAlign) {
|
||||
case St.Align.START:
|
||||
leftEmptySpace = 0;
|
||||
break;
|
||||
case St.Align.MIDDLE:
|
||||
leftEmptySpace = Math.floor((availWidth - usedWidth) / 2);
|
||||
break;
|
||||
case St.Align.END:
|
||||
leftEmptySpace = availWidth - usedWidth;
|
||||
}
|
||||
|
||||
let x = box.x1 + leftEmptySpace + this.leftPadding;
|
||||
let y = box.y1 + this.topPadding;
|
||||
let columnIndex = 0;
|
||||
let rowIndex = 0;
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let childBox = this._calculateChildBox(children[i], x, y, box);
|
||||
children[i].allocate(childBox, flags);
|
||||
this._grid.set_skip_paint(children[i], false);
|
||||
|
||||
columnIndex++;
|
||||
if (columnIndex == nColumns) {
|
||||
columnIndex = 0;
|
||||
rowIndex++;
|
||||
}
|
||||
if (columnIndex == 0) {
|
||||
y += this._getVItemSize() + spacing;
|
||||
if ((i + 1) % this._childrenPerPage == 0)
|
||||
y += this._spaceBetweenPages - spacing + this.bottomPadding + this.topPadding;
|
||||
x = box.x1 + leftEmptySpace + this.leftPadding;
|
||||
} else
|
||||
x += this._getHItemSize() + spacing;
|
||||
}
|
||||
},
|
||||
|
||||
_computePages: function (availWidthPerPage, availHeightPerPage) {
|
||||
let [nColumns, usedWidth] = this._computeLayout(availWidthPerPage);
|
||||
let nRows;
|
||||
let children = this._getVisibleChildren();
|
||||
if (nColumns > 0)
|
||||
nRows = Math.ceil(children.length / nColumns);
|
||||
else
|
||||
nRows = 0;
|
||||
if (this._rowLimit)
|
||||
nRows = Math.min(nRows, this._rowLimit);
|
||||
|
||||
let spacing = this._getSpacing();
|
||||
// We want to contain the grid inside the parent box with padding
|
||||
this._rowsPerPage = this.rowsForHeight(availHeightPerPage);
|
||||
this._nPages = Math.ceil(nRows / this._rowsPerPage);
|
||||
this._spaceBetweenPages = availHeightPerPage - (this.topPadding + this.bottomPadding) - this._availableHeightPerPageForItems();
|
||||
this._childrenPerPage = nColumns * this._rowsPerPage;
|
||||
},
|
||||
|
||||
adaptToSize: function(availWidth, availHeight) {
|
||||
this.parent(availWidth, availHeight);
|
||||
this._computePages(availWidth, availHeight);
|
||||
},
|
||||
|
||||
_availableHeightPerPageForItems: function() {
|
||||
return this.usedHeightForNRows(this._rowsPerPage) - (this.topPadding + this.bottomPadding);
|
||||
},
|
||||
|
||||
nPages: function() {
|
||||
return this._nPages;
|
||||
},
|
||||
|
||||
getPageY: function(pageNumber) {
|
||||
if (!this._nPages)
|
||||
return 0;
|
||||
|
||||
let firstPageItem = pageNumber * this._childrenPerPage
|
||||
let childBox = this._getVisibleChildren()[firstPageItem].get_allocation_box();
|
||||
return childBox.y1 - this.topPadding;
|
||||
},
|
||||
|
||||
getItemPage: function(item) {
|
||||
let children = this._getVisibleChildren();
|
||||
let index = children.indexOf(item);
|
||||
if (index == -1) {
|
||||
throw new Error('Item not found.');
|
||||
return 0;
|
||||
}
|
||||
return Math.floor(index / this._childrenPerPage);
|
||||
},
|
||||
|
||||
/**
|
||||
* openExtraSpace:
|
||||
* @sourceItem: the item for which to create extra space
|
||||
* @side: where @sourceItem should be located relative to the created space
|
||||
* @nRows: the amount of space to create
|
||||
*
|
||||
* Pan view to create extra space for @nRows above or below @sourceItem.
|
||||
*/
|
||||
openExtraSpace: function(sourceItem, side, nRows) {
|
||||
let children = this._getVisibleChildren();
|
||||
let index = children.indexOf(sourceItem.actor);
|
||||
if (index == -1) {
|
||||
throw new Error('Item not found.');
|
||||
return;
|
||||
}
|
||||
let pageIndex = Math.floor(index / this._childrenPerPage);
|
||||
let pageOffset = pageIndex * this._childrenPerPage;
|
||||
|
||||
let childrenPerRow = this._childrenPerPage / this._rowsPerPage;
|
||||
let sourceRow = Math.floor((index - pageOffset) / childrenPerRow);
|
||||
|
||||
let nRowsAbove = (side == St.Side.TOP) ? sourceRow + 1
|
||||
: sourceRow;
|
||||
let nRowsBelow = this._rowsPerPage - nRowsAbove;
|
||||
|
||||
let nRowsUp, nRowsDown;
|
||||
if (side == St.Side.TOP) {
|
||||
nRowsDown = Math.min(nRowsBelow, nRows);
|
||||
nRowsUp = nRows - nRowsDown;
|
||||
} else {
|
||||
nRowsUp = Math.min(nRowsAbove, nRows);
|
||||
nRowsDown = nRows - nRowsUp;
|
||||
}
|
||||
|
||||
let childrenDown = children.splice(pageOffset +
|
||||
nRowsAbove * childrenPerRow,
|
||||
nRowsBelow * childrenPerRow);
|
||||
let childrenUp = children.splice(pageOffset,
|
||||
nRowsAbove * childrenPerRow);
|
||||
|
||||
// Special case: On the last row with no rows below the icon,
|
||||
// there's no need to move any rows either up or down
|
||||
if (childrenDown.length == 0 && nRowsUp == 0) {
|
||||
this._translatedChildren = [];
|
||||
this.emit('space-opened');
|
||||
} else {
|
||||
this._translateChildren(childrenUp, Gtk.DirectionType.UP, nRowsUp);
|
||||
this._translateChildren(childrenDown, Gtk.DirectionType.DOWN, nRowsDown);
|
||||
this._translatedChildren = childrenUp.concat(childrenDown);
|
||||
}
|
||||
},
|
||||
|
||||
_translateChildren: function(children, direction, nRows) {
|
||||
let translationY = nRows * (this._getVItemSize() + this._getSpacing());
|
||||
if (translationY == 0)
|
||||
return;
|
||||
|
||||
if (direction == Gtk.DirectionType.UP)
|
||||
translationY *= -1;
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
children[i].translation_y = 0;
|
||||
let params = { translation_y: translationY,
|
||||
time: EXTRA_SPACE_ANIMATION_TIME,
|
||||
transition: 'easeInOutQuad'
|
||||
};
|
||||
if (i == (children.length - 1))
|
||||
params.onComplete = Lang.bind(this,
|
||||
function() {
|
||||
this.emit('space-opened');
|
||||
});
|
||||
Tweener.addTween(children[i], params);
|
||||
}
|
||||
},
|
||||
|
||||
closeExtraSpace: function() {
|
||||
if (!this._translatedChildren || !this._translatedChildren.length) {
|
||||
this.emit('space-closed');
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this._translatedChildren.length; i++) {
|
||||
if (!this._translatedChildren[i].translation_y)
|
||||
continue;
|
||||
Tweener.addTween(this._translatedChildren[i],
|
||||
{ translation_y: 0,
|
||||
time: EXTRA_SPACE_ANIMATION_TIME,
|
||||
transition: 'easeInOutQuad',
|
||||
onComplete: Lang.bind(this,
|
||||
function() {
|
||||
this.emit('space-closed');
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(PaginatedIconGrid.prototype);
|
||||
|
@ -2,18 +2,21 @@
|
||||
|
||||
const Caribou = imports.gi.Caribou;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const DBus = imports.dbus;
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const BoxPointer = imports.ui.boxpointer;
|
||||
const Layout = imports.ui.layout;
|
||||
const Main = imports.ui.main;
|
||||
const MessageTray = imports.ui.messageTray;
|
||||
|
||||
const KEYBOARD_REST_TIME = Layout.KEYBOARD_ANIMATION_TIME * 2 * 1000;
|
||||
|
||||
const KEYBOARD_SCHEMA = 'org.gnome.shell.keyboard';
|
||||
const KEYBOARD_TYPE = 'keyboard-type';
|
||||
|
||||
@ -56,14 +59,7 @@ const Key = new Lang.Class({
|
||||
if (this._key.name == 'Control_L' || this._key.name == 'Alt_L')
|
||||
this._key.latch = true;
|
||||
|
||||
this._key.connect('key-pressed', Lang.bind(this, function ()
|
||||
{ this.actor.checked = true }));
|
||||
this._key.connect('key-released', Lang.bind(this, function ()
|
||||
{ this.actor.checked = false; }));
|
||||
|
||||
if (this._extended_keys.length > 0) {
|
||||
this._grabbed = false;
|
||||
this._eventCaptureId = 0;
|
||||
this._key.connect('notify::show-subkeys', Lang.bind(this, this._onShowSubkeysChanged));
|
||||
this._boxPointer = new BoxPointer.BoxPointer(St.Side.BOTTOM,
|
||||
{ x_fill: true,
|
||||
@ -115,52 +111,23 @@ const Key = new Lang.Class({
|
||||
this._boxPointer.bin.add_actor(this._extended_keyboard);
|
||||
},
|
||||
|
||||
_onEventCapture: function (actor, event) {
|
||||
let source = event.get_source();
|
||||
let type = event.type();
|
||||
|
||||
if ((type == Clutter.EventType.BUTTON_PRESS ||
|
||||
type == Clutter.EventType.BUTTON_RELEASE) &&
|
||||
this._extended_keyboard.contains(source)) {
|
||||
source.extended_key.press();
|
||||
source.extended_key.release();
|
||||
return false;
|
||||
}
|
||||
if (type == Clutter.EventType.BUTTON_PRESS) {
|
||||
this._boxPointer.actor.hide();
|
||||
this._ungrab();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_ungrab: function () {
|
||||
global.stage.disconnect(this._eventCaptureId);
|
||||
this._eventCaptureId = 0;
|
||||
this._grabbed = false;
|
||||
Main.popModal(this.actor);
|
||||
get subkeys() {
|
||||
return this._boxPointer;
|
||||
},
|
||||
|
||||
_onShowSubkeysChanged: function () {
|
||||
if (this._key.show_subkeys) {
|
||||
this.actor.fake_release();
|
||||
this._boxPointer.actor.raise_top();
|
||||
this._boxPointer.setPosition(this.actor, 0.5);
|
||||
this._boxPointer.show(BoxPointer.PopupAnimation.FULL);
|
||||
this.emit('show-subkeys');
|
||||
this.actor.fake_release();
|
||||
this.actor.set_hover(false);
|
||||
if (!this._grabbed) {
|
||||
Main.pushModal(this.actor);
|
||||
this._eventCaptureId = global.stage.connect('captured-event', Lang.bind(this, this._onEventCapture));
|
||||
this._grabbed = true;
|
||||
}
|
||||
this._key.release();
|
||||
} else {
|
||||
if (this._grabbed)
|
||||
this._ungrab();
|
||||
this._boxPointer.hide(BoxPointer.PopupAnimation.FULL);
|
||||
this.emit('hide-subkeys');
|
||||
}
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(Key.prototype);
|
||||
|
||||
const Keyboard = new Lang.Class({
|
||||
// HACK: we can't set Name, because it collides with Name dbus property
|
||||
@ -175,16 +142,26 @@ const Keyboard = new Lang.Class({
|
||||
this._focusInExtendedKeys = false;
|
||||
|
||||
this._timestamp = global.display.get_current_time_roundtrip();
|
||||
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._redraw));
|
||||
|
||||
this._keyboardSettings = new Gio.Settings({ schema: KEYBOARD_SCHEMA });
|
||||
this._keyboardSettings.connect('changed', Lang.bind(this, this._settingsChanged));
|
||||
this._a11yApplicationsSettings = new Gio.Settings({ schema: A11Y_APPLICATIONS_SCHEMA });
|
||||
this._a11yApplicationsSettings.connect('changed', Lang.bind(this, this._settingsChanged));
|
||||
this._settingsChanged();
|
||||
},
|
||||
|
||||
init: function () {
|
||||
this._showIdleId = 0;
|
||||
this._subkeysBoxPointer = null;
|
||||
this._capturedEventId = 0;
|
||||
this._capturedPress = false;
|
||||
|
||||
this._keyboardVisible = false;
|
||||
Main.layoutManager.connect('keyboard-visible-changed', Lang.bind(this, function(o, visible) {
|
||||
this._keyboardVisible = visible;
|
||||
}));
|
||||
this._keyboardRequested = false;
|
||||
this._keyboardRestingId = 0;
|
||||
|
||||
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._redraw));
|
||||
this._redraw();
|
||||
},
|
||||
|
||||
@ -199,25 +176,19 @@ const Keyboard = new Lang.Class({
|
||||
if (this._keyboard)
|
||||
this._destroyKeyboard();
|
||||
|
||||
if (this._enableKeyboard) {
|
||||
// If we've been called because the setting actually just
|
||||
// changed to true (as opposed to being called from
|
||||
// this._init()), then we want to pop up the keyboard.
|
||||
let showKeyboard = (settings != null);
|
||||
|
||||
// However, caribou-gtk-module or this._onKeyFocusChanged
|
||||
// will probably immediately tell us to hide it, so we
|
||||
// have to fake things out so we'll ignore that request.
|
||||
if (showKeyboard)
|
||||
this._timestamp = global.display.get_current_time_roundtrip() + 1;
|
||||
this._setupKeyboard(showKeyboard);
|
||||
} else
|
||||
if (this._enableKeyboard)
|
||||
this._setupKeyboard();
|
||||
else
|
||||
Main.layoutManager.hideKeyboard(true);
|
||||
},
|
||||
|
||||
_destroyKeyboard: function() {
|
||||
if (this._keyboardNotifyId)
|
||||
this._keyboard.disconnect(this._keyboardNotifyId);
|
||||
if (this._keyboardGroupAddedId)
|
||||
this._keyboard.disconnect(this._keyboardGroupAddedId);
|
||||
if (this._keyboardGroupRemovedId)
|
||||
this._keyboard.disconnect(this._keyboardGroupRemovedId);
|
||||
if (this._focusNotifyId)
|
||||
global.stage.disconnect(this._focusNotifyId);
|
||||
this._keyboard = null;
|
||||
@ -227,7 +198,7 @@ const Keyboard = new Lang.Class({
|
||||
this._destroySource();
|
||||
},
|
||||
|
||||
_setupKeyboard: function(show) {
|
||||
_setupKeyboard: function() {
|
||||
this.actor = new St.BoxLayout({ name: 'keyboard', vertical: true, reactive: true });
|
||||
Main.layoutManager.keyboardBox.add_actor(this.actor);
|
||||
Main.layoutManager.trackChrome(this.actor);
|
||||
@ -248,12 +219,11 @@ const Keyboard = new Lang.Class({
|
||||
this.actor.text_direction = Clutter.TextDirection.LTR;
|
||||
|
||||
this._keyboardNotifyId = this._keyboard.connect('notify::active-group', Lang.bind(this, this._onGroupChanged));
|
||||
this._keyboardGroupAddedId = this._keyboard.connect('group-added', Lang.bind(this, this._onGroupAdded));
|
||||
this._keyboardGroupRemovedId = this._keyboard.connect('group-removed', Lang.bind(this, this._onGroupRemoved));
|
||||
this._focusNotifyId = global.stage.connect('notify::key-focus', Lang.bind(this, this._onKeyFocusChanged));
|
||||
|
||||
if (show)
|
||||
this.show(Main.layoutManager.focusIndex);
|
||||
else
|
||||
this._createSource();
|
||||
this._createSource();
|
||||
},
|
||||
|
||||
_onKeyFocusChanged: function () {
|
||||
@ -273,88 +243,102 @@ const Keyboard = new Lang.Class({
|
||||
return;
|
||||
|
||||
let time = global.get_current_time();
|
||||
if (focus instanceof Clutter.Text)
|
||||
this.Show(time);
|
||||
else
|
||||
if (!(focus instanceof Clutter.Text)) {
|
||||
this.Hide(time);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._showIdleId)
|
||||
this._showIdleId = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE,
|
||||
Lang.bind(this, function() { this.Show(time); }));
|
||||
},
|
||||
|
||||
_createLayersForGroup: function (gname) {
|
||||
let group = this._keyboard.get_group(gname);
|
||||
group.connect('notify::active-level', Lang.bind(this, this._onLevelChanged));
|
||||
let layers = {};
|
||||
let levels = group.get_levels();
|
||||
for (let j = 0; j < levels.length; ++j) {
|
||||
let lname = levels[j];
|
||||
let level = group.get_level(lname);
|
||||
let layout = new St.BoxLayout({ style_class: 'keyboard-layout',
|
||||
vertical: true });
|
||||
this._loadRows(level, layout);
|
||||
layers[lname] = layout;
|
||||
this.actor.add(layout, { x_fill: false });
|
||||
|
||||
layout.hide();
|
||||
}
|
||||
return layers;
|
||||
},
|
||||
|
||||
_addKeys: function () {
|
||||
let groups = this._keyboard.get_groups();
|
||||
for (let i = 0; i < groups.length; ++i) {
|
||||
let gname = groups[i];
|
||||
let group = this._keyboard.get_group(gname);
|
||||
group.connect('notify::active-level', Lang.bind(this, this._onLevelChanged));
|
||||
let layers = {};
|
||||
let levels = group.get_levels();
|
||||
for (let j = 0; j < levels.length; ++j) {
|
||||
let lname = levels[j];
|
||||
let level = group.get_level(lname);
|
||||
let layout = new St.BoxLayout({ style_class: 'keyboard-layout',
|
||||
vertical: true });
|
||||
this._loadRows(level, layout);
|
||||
layers[lname] = layout;
|
||||
this.actor.add(layout, { x_fill: false });
|
||||
|
||||
layout.hide();
|
||||
}
|
||||
this._groups[gname] = layers;
|
||||
this._groups[gname] = this._createLayersForGroup(gname);
|
||||
}
|
||||
|
||||
this._setActiveLayer();
|
||||
},
|
||||
|
||||
_getTrayIcon: function () {
|
||||
let trayButton = new St.Button ({ label: _("tray"),
|
||||
style_class: 'keyboard-key' });
|
||||
trayButton.key_width = 1;
|
||||
trayButton.connect('button-press-event', Lang.bind(this, function () {
|
||||
Main.messageTray.toggle();
|
||||
}));
|
||||
_onCapturedEvent: function(actor, event) {
|
||||
let type = event.type();
|
||||
let press = type == Clutter.EventType.BUTTON_PRESS;
|
||||
let release = type == Clutter.EventType.BUTTON_RELEASE;
|
||||
|
||||
Main.overview.connect('showing', Lang.bind(this, function () {
|
||||
trayButton.reactive = false;
|
||||
trayButton.add_style_pseudo_class('grayed');
|
||||
}));
|
||||
Main.overview.connect('hiding', Lang.bind(this, function () {
|
||||
trayButton.reactive = true;
|
||||
trayButton.remove_style_pseudo_class('grayed');
|
||||
}));
|
||||
Main.sessionMode.connect('updated', Lang.bind(this, function() {
|
||||
trayButton.reactive = !Main.sessionMode.isLocked;
|
||||
if (Main.sessionMode.isLocked)
|
||||
trayButton.add_style_pseudo_class('grayed');
|
||||
else
|
||||
trayButton.remove_style_pseudo_class('grayed');
|
||||
}));
|
||||
if (press)
|
||||
this._capturedPress = true;
|
||||
else if (release && this._capturedPress)
|
||||
this._hideSubkeys();
|
||||
|
||||
return trayButton;
|
||||
return true;
|
||||
},
|
||||
|
||||
_addRows : function (keys, layout) {
|
||||
let keyboard_row = new St.BoxLayout();
|
||||
for (let i = 0; i < keys.length; ++i) {
|
||||
let children = keys[i].get_children();
|
||||
let right_box = new St.BoxLayout({ style_class: 'keyboard-row' });
|
||||
let left_box = new St.BoxLayout({ style_class: 'keyboard-row' });
|
||||
let center_box = new St.BoxLayout({ style_class: 'keyboard-row' });
|
||||
let right_box = new St.BoxLayout({ style_class: 'keyboard-row' });
|
||||
for (let j = 0; j < children.length; ++j) {
|
||||
if (this._numOfHorizKeys == 0)
|
||||
this._numOfHorizKeys = children.length;
|
||||
let key = children[j];
|
||||
let button = new Key(key);
|
||||
|
||||
if (key.align == 'right')
|
||||
switch (key.align) {
|
||||
case 'right':
|
||||
right_box.add(button.actor);
|
||||
else
|
||||
break;
|
||||
case 'center':
|
||||
center_box.add(button.actor);
|
||||
break;
|
||||
case 'left':
|
||||
default:
|
||||
left_box.add(button.actor);
|
||||
break;
|
||||
}
|
||||
if (key.name == 'Caribou_Prefs') {
|
||||
key.connect('key-released', Lang.bind(this, this.hide));
|
||||
|
||||
// Add new key for hiding message tray
|
||||
right_box.add(this._getTrayIcon());
|
||||
}
|
||||
|
||||
button.connect('show-subkeys', Lang.bind(this, function() {
|
||||
if (this._subkeysBoxPointer)
|
||||
this._subkeysBoxPointer.hide(BoxPointer.PopupAnimation.FULL);
|
||||
this._subkeysBoxPointer = button.subkeys;
|
||||
this._subkeysBoxPointer.show(BoxPointer.PopupAnimation.FULL);
|
||||
if (!this._capturedEventId)
|
||||
this._capturedEventId = this.actor.connect('captured-event',
|
||||
Lang.bind(this, this._onCapturedEvent));
|
||||
}));
|
||||
button.connect('hide-subkeys', Lang.bind(this, function() {
|
||||
this._hideSubkeys();
|
||||
}));
|
||||
}
|
||||
keyboard_row.add(left_box, { expand: true, x_fill: false, x_align: St.Align.START });
|
||||
keyboard_row.add(center_box, { expand: true, x_fill: false, x_align: St.Align.MIDDLE });
|
||||
keyboard_row.add(right_box, { expand: true, x_fill: false, x_align: St.Align.END });
|
||||
}
|
||||
layout.add(keyboard_row);
|
||||
@ -427,6 +411,14 @@ const Keyboard = new Lang.Class({
|
||||
this._redraw();
|
||||
},
|
||||
|
||||
_onGroupAdded: function (keyboard, gname) {
|
||||
this._groups[gname] = this._createLayersForGroup(gname);
|
||||
},
|
||||
|
||||
_onGroupRemoved: function (keyboard, gname) {
|
||||
delete this._groups[gname];
|
||||
},
|
||||
|
||||
_setActiveLayer: function () {
|
||||
let active_group_name = this._keyboard.active_group;
|
||||
let active_group = this._keyboard.get_group(active_group_name);
|
||||
@ -462,7 +454,37 @@ const Keyboard = new Lang.Class({
|
||||
actor._extended_keys || actor.extended_key;
|
||||
},
|
||||
|
||||
_clearKeyboardRestTimer: function() {
|
||||
if (!this._keyboardRestingId)
|
||||
return;
|
||||
GLib.source_remove(this._keyboardRestingId);
|
||||
this._keyboardRestingId = 0;
|
||||
},
|
||||
|
||||
show: function (monitor) {
|
||||
this._keyboardRequested = true;
|
||||
|
||||
if (this._keyboardVisible) {
|
||||
if (monitor != Main.layoutManager.keyboardIndex) {
|
||||
Main.layoutManager.keyboardIndex = monitor;
|
||||
this._redraw();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this._clearKeyboardRestTimer();
|
||||
this._keyboardRestingId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
|
||||
KEYBOARD_REST_TIME,
|
||||
Lang.bind(this, function() {
|
||||
this._clearKeyboardRestTimer();
|
||||
this._show(monitor);
|
||||
}));
|
||||
},
|
||||
|
||||
_show: function(monitor) {
|
||||
if (!this._keyboardRequested)
|
||||
return;
|
||||
|
||||
Main.layoutManager.keyboardIndex = monitor;
|
||||
this._redraw();
|
||||
Main.layoutManager.showKeyboard();
|
||||
@ -470,10 +492,41 @@ const Keyboard = new Lang.Class({
|
||||
},
|
||||
|
||||
hide: function () {
|
||||
this._keyboardRequested = false;
|
||||
|
||||
if (!this._keyboardVisible)
|
||||
return;
|
||||
|
||||
this._clearKeyboardRestTimer();
|
||||
this._keyboardRestingId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
|
||||
KEYBOARD_REST_TIME,
|
||||
Lang.bind(this, function() {
|
||||
this._clearKeyboardRestTimer();
|
||||
this._hide();
|
||||
}));
|
||||
},
|
||||
|
||||
_hide: function() {
|
||||
if (this._keyboardRequested)
|
||||
return;
|
||||
|
||||
this._hideSubkeys();
|
||||
Main.layoutManager.hideKeyboard();
|
||||
this._createSource();
|
||||
},
|
||||
|
||||
_hideSubkeys: function() {
|
||||
if (this._subkeysBoxPointer) {
|
||||
this._subkeysBoxPointer.hide(BoxPointer.PopupAnimation.FULL);
|
||||
this._subkeysBoxPointer = null;
|
||||
}
|
||||
if (this._capturedEventId) {
|
||||
this.actor.disconnect(this._capturedEventId);
|
||||
this._capturedEventId = 0;
|
||||
}
|
||||
this._capturedPress = false;
|
||||
},
|
||||
|
||||
_moveTemporarily: function () {
|
||||
let currentWindow = global.screen.get_display().focus_window;
|
||||
let rect = currentWindow.get_outer_rect();
|
||||
@ -502,6 +555,13 @@ const Keyboard = new Lang.Class({
|
||||
return one - two;
|
||||
},
|
||||
|
||||
_clearShowIdle: function() {
|
||||
if (!this._showIdleId)
|
||||
return;
|
||||
GLib.source_remove(this._showIdleId);
|
||||
this._showIdleId = 0;
|
||||
},
|
||||
|
||||
// D-Bus methods
|
||||
Show: function(timestamp) {
|
||||
if (!this._enableKeyboard)
|
||||
@ -510,6 +570,8 @@ const Keyboard = new Lang.Class({
|
||||
if (this._compareTimestamp(timestamp, this._timestamp) < 0)
|
||||
return;
|
||||
|
||||
this._clearShowIdle();
|
||||
|
||||
if (timestamp != Clutter.CURRENT_TIME)
|
||||
this._timestamp = timestamp;
|
||||
this.show(Main.layoutManager.focusIndex);
|
||||
@ -522,6 +584,8 @@ const Keyboard = new Lang.Class({
|
||||
if (this._compareTimestamp(timestamp, this._timestamp) < 0)
|
||||
return;
|
||||
|
||||
this._clearShowIdle();
|
||||
|
||||
if (timestamp != Clutter.CURRENT_TIME)
|
||||
this._timestamp = timestamp;
|
||||
this.hide();
|
||||
@ -556,11 +620,7 @@ const KeyboardSource = new Lang.Class({
|
||||
this.keepTrayOnSummaryClick = true;
|
||||
},
|
||||
|
||||
handleSummaryClick: function() {
|
||||
let event = Clutter.get_current_event();
|
||||
if (event.type() != Clutter.EventType.BUTTON_RELEASE)
|
||||
return false;
|
||||
|
||||
handleSummaryClick: function(button) {
|
||||
this.open();
|
||||
return true;
|
||||
},
|
||||
|
1510
js/ui/layout.js
1510
js/ui/layout.js
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,7 @@
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Lang = imports.lang;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Params = imports.misc.params;
|
||||
@ -41,15 +42,11 @@ const Lightbox = new Lang.Class({
|
||||
params = Params.parse(params, { inhibitEvents: false,
|
||||
width: null,
|
||||
height: null,
|
||||
fadeInTime: null,
|
||||
fadeOutTime: null,
|
||||
fadeFactor: DEFAULT_FADE_FACTOR
|
||||
fadeFactor: DEFAULT_FADE_FACTOR,
|
||||
});
|
||||
|
||||
this._container = container;
|
||||
this._children = container.get_children();
|
||||
this._fadeInTime = params.fadeInTime;
|
||||
this._fadeOutTime = params.fadeOutTime;
|
||||
this._fadeFactor = params.fadeFactor;
|
||||
this.actor = new St.Bin({ x: 0,
|
||||
y: 0,
|
||||
@ -100,31 +97,39 @@ const Lightbox = new Lang.Class({
|
||||
}
|
||||
},
|
||||
|
||||
show: function() {
|
||||
if (this._fadeInTime) {
|
||||
show: function(fadeInTime) {
|
||||
fadeInTime = fadeInTime || 0;
|
||||
|
||||
Tweener.removeTweens(this.actor);
|
||||
if (fadeInTime != 0) {
|
||||
this.shown = false;
|
||||
this.actor.opacity = 0;
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 255 * this._fadeFactor,
|
||||
time: this._fadeInTime,
|
||||
time: fadeInTime,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this, function() {
|
||||
this.shown = true;
|
||||
this.emit('shown');
|
||||
})
|
||||
});
|
||||
} else {
|
||||
this.actor.opacity = 255 * this._fadeFactor;
|
||||
this.shown = true;
|
||||
this.emit('shown');
|
||||
}
|
||||
this.actor.show();
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
hide: function(fadeOutTime) {
|
||||
fadeOutTime = fadeOutTime || 0;
|
||||
|
||||
this.shown = false;
|
||||
if (this._fadeOutTime) {
|
||||
Tweener.removeTweens(this.actor);
|
||||
if (fadeOutTime != 0) {
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 0,
|
||||
time: this._fadeOutTime,
|
||||
time: fadeOutTime,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this, function() {
|
||||
this.actor.hide();
|
||||
@ -197,3 +202,4 @@ const Lightbox = new Lang.Class({
|
||||
this.highlight(null);
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(Lightbox.prototype);
|
||||
|
@ -39,6 +39,7 @@ var commandHeader = 'const Clutter = imports.gi.Clutter; ' +
|
||||
* in the shell core code too. */
|
||||
'const stage = global.stage; ' +
|
||||
/* Special lookingGlass functions */
|
||||
'const inspect = Lang.bind(Main.lookingGlass, Main.lookingGlass.inspect); ' +
|
||||
'const it = Main.lookingGlass.getIt(); ' +
|
||||
'const r = Lang.bind(Main.lookingGlass, Main.lookingGlass.getResult); ';
|
||||
|
||||
@ -307,10 +308,6 @@ const Result = new Lang.Class({
|
||||
box.add(resultTxt);
|
||||
let objLink = new ObjLink(this._lookingGlass, o);
|
||||
box.add(objLink.actor);
|
||||
let line = new Clutter.Rectangle({ name: 'Separator' });
|
||||
let padBin = new St.Bin({ name: 'Separator', x_fill: true, y_fill: true });
|
||||
padBin.add_actor(line);
|
||||
this.actor.add(padBin);
|
||||
}
|
||||
});
|
||||
|
||||
@ -684,7 +681,8 @@ const Memory = new Lang.Class({
|
||||
const Extensions = new Lang.Class({
|
||||
Name: 'Extensions',
|
||||
|
||||
_init: function() {
|
||||
_init: function(lookingGlass) {
|
||||
this._lookingGlass = lookingGlass;
|
||||
this.actor = new St.BoxLayout({ vertical: true,
|
||||
name: 'lookingGlassExtensions' });
|
||||
this._noExtensions = new St.Label({ style_class: 'lg-extensions-none',
|
||||
@ -851,8 +849,9 @@ const LookingGlass = new Lang.Class({
|
||||
this._updateFont();
|
||||
|
||||
// We want it to appear to slide out from underneath the panel
|
||||
Main.layoutManager.panelBox.add_actor(this.actor);
|
||||
this.actor.lower_bottom();
|
||||
Main.uiGroup.add_actor(this.actor);
|
||||
Main.uiGroup.set_child_below_sibling(this.actor,
|
||||
Main.layoutManager.panelBox);
|
||||
Main.layoutManager.panelBox.connect('allocation-changed',
|
||||
Lang.bind(this, this._queueResize));
|
||||
Main.layoutManager.keyboardBox.connect('allocation-changed',
|
||||
@ -871,8 +870,7 @@ const LookingGlass = new Lang.Class({
|
||||
inspectIcon.connect('button-press-event', Lang.bind(this, function () {
|
||||
let inspector = new Inspector(this);
|
||||
inspector.connect('target', Lang.bind(this, function(i, target, stageX, stageY) {
|
||||
this._pushResult('<inspect x:' + stageX + ' y:' + stageY + '>',
|
||||
target);
|
||||
this._pushResult('inspect(' + Math.round(stageX) + ', ' + Math.round(stageY) + ')', target);
|
||||
}));
|
||||
inspector.connect('closed', Lang.bind(this, function() {
|
||||
this.actor.show();
|
||||
@ -912,7 +910,7 @@ const LookingGlass = new Lang.Class({
|
||||
this._memory = new Memory();
|
||||
notebook.appendPage('Memory', this._memory.actor);
|
||||
|
||||
this._extensions = new Extensions();
|
||||
this._extensions = new Extensions(this);
|
||||
notebook.appendPage('Extensions', this._extensions.actor);
|
||||
|
||||
this._entry.clutter_text.connect('activate', Lang.bind(this, function (o, e) {
|
||||
@ -922,7 +920,7 @@ const LookingGlass = new Lang.Class({
|
||||
let text = o.get_text();
|
||||
// Ensure we don't get newlines in the command; the history file is
|
||||
// newline-separated.
|
||||
text.replace('\n', ' ');
|
||||
text = text.replace('\n', ' ');
|
||||
// Strip leading and trailing whitespace
|
||||
text = text.replace(/^\s+/g, '').replace(/\s+$/g, '');
|
||||
if (text == '')
|
||||
@ -950,9 +948,7 @@ const LookingGlass = new Lang.Class({
|
||||
|
||||
_updateFont: function() {
|
||||
let fontName = this._interfaceSettings.get_string('monospace-font-name');
|
||||
// This is mishandled by the scanner - should by Pango.FontDescription_from_string(fontName);
|
||||
// https://bugzilla.gnome.org/show_bug.cgi?id=595889
|
||||
let fontDesc = Pango.font_description_from_string(fontName);
|
||||
let fontDesc = Pango.FontDescription.from_string(fontName);
|
||||
// We ignore everything but size and style; you'd be crazy to set your system-wide
|
||||
// monospace font to be bold/oblique/etc. Could easily be added here.
|
||||
this.actor.style =
|
||||
@ -990,28 +986,18 @@ const LookingGlass = new Lang.Class({
|
||||
|
||||
_showCompletions: function(completions) {
|
||||
if (!this._completionActor) {
|
||||
let actor = new St.BoxLayout({ vertical: true });
|
||||
|
||||
this._completionText = new St.Label({ name: 'LookingGlassAutoCompletionText', style_class: 'lg-completions-text' });
|
||||
this._completionText.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
||||
this._completionText.clutter_text.line_wrap = true;
|
||||
actor.add(this._completionText);
|
||||
|
||||
let line = new Clutter.Rectangle();
|
||||
let padBin = new St.Bin({ x_fill: true, y_fill: true });
|
||||
padBin.add_actor(line);
|
||||
actor.add(padBin);
|
||||
|
||||
this._completionActor = actor;
|
||||
this._completionActor = new St.Label({ name: 'LookingGlassAutoCompletionText', style_class: 'lg-completions-text' });
|
||||
this._completionActor.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
||||
this._completionActor.clutter_text.line_wrap = true;
|
||||
this._evalBox.insert_child_below(this._completionActor, this._entryArea);
|
||||
}
|
||||
|
||||
this._completionText.set_text(completions.join(', '));
|
||||
this._completionActor.set_text(completions.join(', '));
|
||||
|
||||
// Setting the height to -1 allows us to get its actual preferred height rather than
|
||||
// whatever was last given in set_height by Tweener.
|
||||
this._completionActor.set_height(-1);
|
||||
let [minHeight, naturalHeight] = this._completionText.get_preferred_height(this._resultsArea.get_width());
|
||||
let [minHeight, naturalHeight] = this._completionActor.get_preferred_height(this._resultsArea.get_width());
|
||||
|
||||
// Don't reanimate if we are already visible
|
||||
if (this._completionActor.visible) {
|
||||
@ -1057,6 +1043,10 @@ const LookingGlass = new Lang.Class({
|
||||
this._entry.text = '';
|
||||
},
|
||||
|
||||
inspect: function(x, y) {
|
||||
return global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);
|
||||
},
|
||||
|
||||
getIt: function () {
|
||||
return this._it;
|
||||
},
|
||||
@ -1082,15 +1072,15 @@ const LookingGlass = new Lang.Class({
|
||||
let myWidth = primary.width * 0.7;
|
||||
let availableHeight = primary.height - Main.layoutManager.keyboardBox.height;
|
||||
let myHeight = Math.min(primary.height * 0.7, availableHeight * 0.9);
|
||||
this.actor.x = (primary.width - myWidth) / 2;
|
||||
this._hiddenY = this.actor.get_parent().height - myHeight - 4; // -4 to hide the top corners
|
||||
this.actor.x = primary.x + (primary.width - myWidth) / 2;
|
||||
this._hiddenY = primary.y + Main.layoutManager.panelBox.height - myHeight - 4; // -4 to hide the top corners
|
||||
this._targetY = this._hiddenY + myHeight;
|
||||
this.actor.y = this._hiddenY;
|
||||
this.actor.width = myWidth;
|
||||
this.actor.height = myHeight;
|
||||
this._objInspector.actor.set_size(Math.floor(myWidth * 0.8), Math.floor(myHeight * 0.8));
|
||||
this._objInspector.actor.set_position(primary.x + this.actor.x + Math.floor(myWidth * 0.1),
|
||||
primary.y + this._targetY + Math.floor(myHeight * 0.1));
|
||||
this._objInspector.actor.set_position(this.actor.x + Math.floor(myWidth * 0.1),
|
||||
this._targetY + Math.floor(myHeight * 0.1));
|
||||
},
|
||||
|
||||
insertObject: function(obj) {
|
||||
@ -1129,7 +1119,7 @@ const LookingGlass = new Lang.Class({
|
||||
if (this._open)
|
||||
return;
|
||||
|
||||
if (!Main.pushModal(this._entry, { keybindingMode: Main.KeybindingMode.LOOKING_GLASS }))
|
||||
if (!Main.pushModal(this._entry, { keybindingMode: Shell.KeyBindingMode.LOOKING_GLASS }))
|
||||
return;
|
||||
|
||||
this._notebook.selectIndex(0);
|
||||
@ -1160,7 +1150,7 @@ const LookingGlass = new Lang.Class({
|
||||
|
||||
Main.popModal(this._entry);
|
||||
|
||||
Tweener.addTween(this.actor, { time: 0.5 / St.get_slow_down_factor(),
|
||||
Tweener.addTween(this.actor, { time: Math.min(0.5 / St.get_slow_down_factor(), 0.5),
|
||||
transition: 'easeOutQuad',
|
||||
y: this._hiddenY,
|
||||
onComplete: Lang.bind(this, function () {
|
||||
|
@ -1,5 +1,6 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Atspi = imports.gi.Atspi;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GDesktopEnums = imports.gi.GDesktopEnums;
|
||||
const Gio = imports.gi.Gio;
|
||||
@ -7,8 +8,10 @@ const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const FocusCaretTracker = imports.ui.focusCaretTracker;
|
||||
const Main = imports.ui.main;
|
||||
const MagnifierDBus = imports.ui.magnifierDBus;
|
||||
const Params = imports.misc.params;
|
||||
@ -36,6 +39,8 @@ const CONTRAST_BLUE_KEY = 'contrast-blue';
|
||||
const LENS_MODE_KEY = 'lens-mode';
|
||||
const CLAMP_MODE_KEY = 'scroll-at-edges';
|
||||
const MOUSE_TRACKING_KEY = 'mouse-tracking';
|
||||
const FOCUS_TRACKING_KEY = 'focus-tracking';
|
||||
const CARET_TRACKING_KEY = 'caret-tracking';
|
||||
const SHOW_CROSS_HAIRS_KEY = 'show-cross-hairs';
|
||||
const CROSS_HAIRS_THICKNESS_KEY = 'cross-hairs-thickness';
|
||||
const CROSS_HAIRS_COLOR_KEY = 'cross-hairs-color';
|
||||
@ -52,10 +57,24 @@ const Magnifier = new Lang.Class({
|
||||
// Magnifier is a manager of ZoomRegions.
|
||||
this._zoomRegions = [];
|
||||
|
||||
// Export to dbus.
|
||||
magDBusService = new MagnifierDBus.ShellMagnifier();
|
||||
|
||||
let showAtLaunch = this._settingsInit();
|
||||
this.setActive(showAtLaunch);
|
||||
},
|
||||
|
||||
_initialize: function() {
|
||||
if (this._initialized)
|
||||
return;
|
||||
this._initialized = true;
|
||||
|
||||
this._settingsInitLate();
|
||||
|
||||
// Create small clutter tree for the magnified mouse.
|
||||
let xfixesCursor = Shell.XFixesCursor.get_for_stage(global.stage);
|
||||
let cursorTracker = Meta.CursorTracker.get_for_screen(global.screen);
|
||||
this._mouseSprite = new Clutter.Texture();
|
||||
xfixesCursor.update_texture_image(this._mouseSprite);
|
||||
Shell.util_cursor_tracker_to_clutter(cursorTracker, this._mouseSprite);
|
||||
this._cursorRoot = new Clutter.Actor();
|
||||
this._cursorRoot.add_actor(this._mouseSprite);
|
||||
|
||||
@ -67,15 +86,11 @@ const Magnifier = new Lang.Class({
|
||||
|
||||
let aZoomRegion = new ZoomRegion(this, this._cursorRoot);
|
||||
this._zoomRegions.push(aZoomRegion);
|
||||
let showAtLaunch = this._settingsInit(aZoomRegion);
|
||||
this._settingsInitRegion(aZoomRegion);
|
||||
aZoomRegion.scrollContentsTo(this.xMouse, this.yMouse);
|
||||
|
||||
xfixesCursor.connect('cursor-change', Lang.bind(this, this._updateMouseSprite));
|
||||
this._xfixesCursor = xfixesCursor;
|
||||
|
||||
// Export to dbus.
|
||||
magDBusService = new MagnifierDBus.ShellMagnifier();
|
||||
this.setActive(showAtLaunch);
|
||||
cursorTracker.connect('cursor-changed', Lang.bind(this, this._updateMouseSprite));
|
||||
this._cursorTracker = cursorTracker;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -83,7 +98,7 @@ const Magnifier = new Lang.Class({
|
||||
* Show the system mouse pointer.
|
||||
*/
|
||||
showSystemCursor: function() {
|
||||
this._xfixesCursor.show();
|
||||
this._cursorTracker.set_pointer_visible(true);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -91,7 +106,7 @@ const Magnifier = new Lang.Class({
|
||||
* Hide the system mouse pointer.
|
||||
*/
|
||||
hideSystemCursor: function() {
|
||||
this._xfixesCursor.hide();
|
||||
this._cursorTracker.set_pointer_visible(false);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -100,6 +115,12 @@ const Magnifier = new Lang.Class({
|
||||
* @activate: Boolean to activate or de-activate the magnifier.
|
||||
*/
|
||||
setActive: function(activate) {
|
||||
if (activate == this.isActive())
|
||||
return;
|
||||
|
||||
if (activate)
|
||||
this._initialize();
|
||||
|
||||
this._zoomRegions.forEach (function(zoomRegion, index, array) {
|
||||
zoomRegion.setActive(activate);
|
||||
});
|
||||
@ -112,7 +133,7 @@ const Magnifier = new Lang.Class({
|
||||
// Make sure system mouse pointer is shown when all zoom regions are
|
||||
// invisible.
|
||||
if (!activate)
|
||||
this._xfixesCursor.show();
|
||||
this._cursorTracker.set_pointer_visible(true);
|
||||
|
||||
// Notify interested parties of this change
|
||||
this.emit('active-changed', activate);
|
||||
@ -422,62 +443,73 @@ const Magnifier = new Lang.Class({
|
||||
//// Private methods ////
|
||||
|
||||
_updateMouseSprite: function() {
|
||||
this._xfixesCursor.update_texture_image(this._mouseSprite);
|
||||
let xHot = this._xfixesCursor.get_hot_x();
|
||||
let yHot = this._xfixesCursor.get_hot_y();
|
||||
Shell.util_cursor_tracker_to_clutter(this._cursorTracker, this._mouseSprite);
|
||||
let [xHot, yHot] = this._cursorTracker.get_hot();
|
||||
this._mouseSprite.set_anchor_point(xHot, yHot);
|
||||
},
|
||||
|
||||
_settingsInit: function(zoomRegion) {
|
||||
_settingsInitRegion: function(zoomRegion) {
|
||||
// Mag factor is accurate to two decimal places.
|
||||
let aPref = parseFloat(this._settings.get_double(MAG_FACTOR_KEY).toFixed(2));
|
||||
if (aPref != 0.0)
|
||||
zoomRegion.setMagFactor(aPref, aPref);
|
||||
|
||||
aPref = this._settings.get_enum(SCREEN_POSITION_KEY);
|
||||
if (aPref)
|
||||
zoomRegion.setScreenPosition(aPref);
|
||||
|
||||
zoomRegion.setLensMode(this._settings.get_boolean(LENS_MODE_KEY));
|
||||
zoomRegion.setClampScrollingAtEdges(!this._settings.get_boolean(CLAMP_MODE_KEY));
|
||||
|
||||
aPref = this._settings.get_enum(MOUSE_TRACKING_KEY);
|
||||
if (aPref)
|
||||
zoomRegion.setMouseTrackingMode(aPref);
|
||||
|
||||
aPref = this._settings.get_enum(FOCUS_TRACKING_KEY);
|
||||
if (aPref)
|
||||
zoomRegion.setFocusTrackingMode(aPref);
|
||||
|
||||
aPref = this._settings.get_enum(CARET_TRACKING_KEY);
|
||||
if (aPref)
|
||||
zoomRegion.setCaretTrackingMode(aPref);
|
||||
|
||||
aPref = this._settings.get_boolean(INVERT_LIGHTNESS_KEY);
|
||||
if (aPref)
|
||||
zoomRegion.setInvertLightness(aPref);
|
||||
|
||||
aPref = this._settings.get_double(COLOR_SATURATION_KEY);
|
||||
if (aPref)
|
||||
zoomRegion.setColorSaturation(aPref);
|
||||
|
||||
let bc = {};
|
||||
bc.r = this._settings.get_double(BRIGHT_RED_KEY);
|
||||
bc.g = this._settings.get_double(BRIGHT_GREEN_KEY);
|
||||
bc.b = this._settings.get_double(BRIGHT_BLUE_KEY);
|
||||
zoomRegion.setBrightness(bc);
|
||||
|
||||
bc.r = this._settings.get_double(CONTRAST_RED_KEY);
|
||||
bc.g = this._settings.get_double(CONTRAST_GREEN_KEY);
|
||||
bc.b = this._settings.get_double(CONTRAST_BLUE_KEY);
|
||||
zoomRegion.setContrast(bc);
|
||||
},
|
||||
|
||||
_settingsInit: function() {
|
||||
this._appSettings = new Gio.Settings({ schema: APPLICATIONS_SCHEMA });
|
||||
this._settings = new Gio.Settings({ schema: MAGNIFIER_SCHEMA });
|
||||
|
||||
if (zoomRegion) {
|
||||
// Mag factor is accurate to two decimal places.
|
||||
let aPref = parseFloat(this._settings.get_double(MAG_FACTOR_KEY).toFixed(2));
|
||||
if (aPref != 0.0)
|
||||
zoomRegion.setMagFactor(aPref, aPref);
|
||||
this._appSettings.connect('changed::' + SHOW_KEY, Lang.bind(this, function() {
|
||||
let active = this._appSettings.get_boolean(SHOW_KEY);
|
||||
this.setActive(active);
|
||||
}));
|
||||
|
||||
aPref = this._settings.get_enum(SCREEN_POSITION_KEY);
|
||||
if (aPref)
|
||||
zoomRegion.setScreenPosition(aPref);
|
||||
|
||||
zoomRegion.setLensMode(this._settings.get_boolean(LENS_MODE_KEY));
|
||||
zoomRegion.setClampScrollingAtEdges(!this._settings.get_boolean(CLAMP_MODE_KEY));
|
||||
|
||||
aPref = this._settings.get_enum(MOUSE_TRACKING_KEY);
|
||||
if (aPref)
|
||||
zoomRegion.setMouseTrackingMode(aPref);
|
||||
|
||||
aPref = this._settings.get_boolean(INVERT_LIGHTNESS_KEY);
|
||||
if (aPref)
|
||||
zoomRegion.setInvertLightness(aPref);
|
||||
|
||||
aPref = this._settings.get_double(COLOR_SATURATION_KEY);
|
||||
if (aPref)
|
||||
zoomRegion.setColorSaturation(aPref);
|
||||
|
||||
let bc = {};
|
||||
bc.r = this._settings.get_double(BRIGHT_RED_KEY);
|
||||
bc.g = this._settings.get_double(BRIGHT_GREEN_KEY);
|
||||
bc.b = this._settings.get_double(BRIGHT_BLUE_KEY);
|
||||
zoomRegion.setBrightness(bc);
|
||||
|
||||
bc.r = this._settings.get_double(CONTRAST_RED_KEY);
|
||||
bc.g = this._settings.get_double(CONTRAST_GREEN_KEY);
|
||||
bc.b = this._settings.get_double(CONTRAST_BLUE_KEY);
|
||||
zoomRegion.setContrast(bc);
|
||||
}
|
||||
return this._appSettings.get_boolean(SHOW_KEY);
|
||||
},
|
||||
|
||||
_settingsInitLate: function() {
|
||||
let showCrosshairs = this._settings.get_boolean(SHOW_CROSS_HAIRS_KEY);
|
||||
this.addCrosshairs();
|
||||
this.setCrosshairsVisible(showCrosshairs);
|
||||
|
||||
this._appSettings.connect('changed::' + SHOW_KEY,
|
||||
Lang.bind(this, function() {
|
||||
this.setActive(this._appSettings.get_boolean(SHOW_KEY));
|
||||
}));
|
||||
|
||||
this._settings.connect('changed::' + SCREEN_POSITION_KEY,
|
||||
Lang.bind(this, this._updateScreenPosition));
|
||||
this._settings.connect('changed::' + MAG_FACTOR_KEY,
|
||||
@ -488,6 +520,10 @@ const Magnifier = new Lang.Class({
|
||||
Lang.bind(this, this._updateClampMode));
|
||||
this._settings.connect('changed::' + MOUSE_TRACKING_KEY,
|
||||
Lang.bind(this, this._updateMouseTrackingMode));
|
||||
this._settings.connect('changed::' + FOCUS_TRACKING_KEY,
|
||||
Lang.bind(this, this._updateFocusTrackingMode));
|
||||
this._settings.connect('changed::' + CARET_TRACKING_KEY,
|
||||
Lang.bind(this, this._updateCaretTrackingMode));
|
||||
|
||||
this._settings.connect('changed::' + INVERT_LIGHTNESS_KEY,
|
||||
Lang.bind(this, this._updateInvertLightness));
|
||||
@ -537,8 +573,6 @@ const Magnifier = new Lang.Class({
|
||||
Lang.bind(this, function() {
|
||||
this.setCrosshairsClip(this._settings.get_boolean(CROSS_HAIRS_CLIP_KEY));
|
||||
}));
|
||||
|
||||
return this._appSettings.get_boolean(SHOW_KEY);
|
||||
},
|
||||
|
||||
_updateScreenPosition: function() {
|
||||
@ -585,6 +619,24 @@ const Magnifier = new Lang.Class({
|
||||
}
|
||||
},
|
||||
|
||||
_updateFocusTrackingMode: function() {
|
||||
// Applies only to the first zoom region.
|
||||
if (this._zoomRegions.length) {
|
||||
this._zoomRegions[0].setFocusTrackingMode(
|
||||
this._settings.get_enum(FOCUS_TRACKING_KEY)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
_updateCaretTrackingMode: function() {
|
||||
// Applies only to the first zoom region.
|
||||
if (this._zoomRegions.length) {
|
||||
this._zoomRegions[0].setCaretTrackingMode(
|
||||
this._settings.get_enum(CARET_TRACKING_KEY)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
_updateInvertLightness: function() {
|
||||
// Applies only to the first zoom region.
|
||||
if (this._zoomRegions.length) {
|
||||
@ -623,7 +675,7 @@ const Magnifier = new Lang.Class({
|
||||
contrast.b = this._settings.get_double(CONTRAST_BLUE_KEY);
|
||||
this._zoomRegions[0].setContrast(contrast);
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(Magnifier.prototype);
|
||||
|
||||
@ -632,8 +684,11 @@ const ZoomRegion = new Lang.Class({
|
||||
|
||||
_init: function(magnifier, mouseSourceActor) {
|
||||
this._magnifier = magnifier;
|
||||
this._focusCaretTracker = new FocusCaretTracker.FocusCaretTracker();
|
||||
|
||||
this._mouseTrackingMode = GDesktopEnums.MagnifierMouseTrackingMode.NONE;
|
||||
this._focusTrackingMode = GDesktopEnums.MagnifierFocusTrackingMode.NONE;
|
||||
this._caretTrackingMode = GDesktopEnums.MagnifierCaretTrackingMode.NONE;
|
||||
this._clampScrollingAtEdges = false;
|
||||
this._lensMode = false;
|
||||
this._screenPosition = GDesktopEnums.MagnifierScreenPosition.FULL_SCREEN;
|
||||
@ -659,9 +714,35 @@ const ZoomRegion = new Lang.Class({
|
||||
this._xMagFactor = 1;
|
||||
this._yMagFactor = 1;
|
||||
this._followingCursor = false;
|
||||
this._xFocus = 0;
|
||||
this._yFocus = 0;
|
||||
this._xCaret = 0;
|
||||
this._yCaret = 0;
|
||||
|
||||
Main.layoutManager.connect('monitors-changed',
|
||||
Lang.bind(this, this._monitorsChanged));
|
||||
this._focusCaretTracker.connect('caret-moved',
|
||||
Lang.bind(this, this._updateCaret));
|
||||
this._focusCaretTracker.connect('focus-changed',
|
||||
Lang.bind(this, this._updateFocus));
|
||||
},
|
||||
|
||||
_updateFocus: function(caller, event) {
|
||||
let component = event.source.get_component_iface();
|
||||
if (!component || event.detail1 != 1)
|
||||
return;
|
||||
let extents = component.get_extents(Atspi.CoordType.SCREEN);
|
||||
[this._xFocus, this._yFocus] = [extents.x, extents.y]
|
||||
this._centerFromFocusPosition();
|
||||
},
|
||||
|
||||
_updateCaret: function(caller, event) {
|
||||
let text = event.source.get_text_iface();
|
||||
if (!text)
|
||||
return;
|
||||
let extents = text.get_character_extents(text.get_caret_offset(), 0);
|
||||
[this._xCaret, this._yCaret] = [extents.x, extents.y];
|
||||
this._centerFromCaretPosition();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -669,14 +750,17 @@ const ZoomRegion = new Lang.Class({
|
||||
* @activate: Boolean to show/hide the ZoomRegion.
|
||||
*/
|
||||
setActive: function(activate) {
|
||||
if (activate && !this.isActive()) {
|
||||
if (activate == this.isActive())
|
||||
return;
|
||||
|
||||
if (activate) {
|
||||
this._createActors();
|
||||
if (this._isMouseOverRegion())
|
||||
this._magnifier.hideSystemCursor();
|
||||
this._updateMagViewGeometry();
|
||||
this._updateCloneGeometry();
|
||||
this._updateMousePosition();
|
||||
} else if (!activate && this.isActive()) {
|
||||
} else {
|
||||
this._destroyActors();
|
||||
}
|
||||
},
|
||||
@ -732,6 +816,30 @@ const ZoomRegion = new Lang.Class({
|
||||
return this._mouseTrackingMode;
|
||||
},
|
||||
|
||||
/**
|
||||
* setFocusTrackingMode
|
||||
* @mode: One of the enum FocusTrackingMode values.
|
||||
*/
|
||||
setFocusTrackingMode: function(mode) {
|
||||
this._focusTrackingMode = mode;
|
||||
if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.NONE)
|
||||
this._focusCaretTracker.deregisterFocusListener();
|
||||
else
|
||||
this._focusCaretTracker.registerFocusListener();
|
||||
},
|
||||
|
||||
/**
|
||||
* setCaretTrackingMode
|
||||
* @mode: One of the enum CaretTrackingMode values.
|
||||
*/
|
||||
setCaretTrackingMode: function(mode) {
|
||||
this._caretTrackingMode = mode;
|
||||
if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.NONE)
|
||||
this._focusCaretTracker.deregisterCaretListener();
|
||||
else
|
||||
this._focusCaretTracker.registerCaretListener();
|
||||
},
|
||||
|
||||
/**
|
||||
* setViewPort
|
||||
* Sets the position and size of the ZoomRegion on screen.
|
||||
@ -1023,20 +1131,6 @@ const ZoomRegion = new Lang.Class({
|
||||
this._magShaderEffects.setBrightness(this._brightness);
|
||||
},
|
||||
|
||||
/**
|
||||
* getBrightness:
|
||||
* Retrive the current brightness of the Zoom Region.
|
||||
* @return Object containing the brightness change for the red, green,
|
||||
* and blue channels.
|
||||
*/
|
||||
getBrightness: function() {
|
||||
let brightness = {};
|
||||
brightness.r = this._brightness.r;
|
||||
brightness.g = this._brightness.g;
|
||||
brightness.b = this._brightness.b;
|
||||
return brightness;
|
||||
},
|
||||
|
||||
/**
|
||||
* setContrast:
|
||||
* Alter the contrast of the magnified view.
|
||||
@ -1243,19 +1337,47 @@ const ZoomRegion = new Lang.Class({
|
||||
let yMouse = this._magnifier.yMouse;
|
||||
|
||||
if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PROPORTIONAL) {
|
||||
return this._centerFromMouseProportional(xMouse, yMouse);
|
||||
return this._centerFromPointProportional(xMouse, yMouse);
|
||||
}
|
||||
else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PUSH) {
|
||||
return this._centerFromMousePush(xMouse, yMouse);
|
||||
return this._centerFromPointPush(xMouse, yMouse);
|
||||
}
|
||||
else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.CENTERED) {
|
||||
return this._centerFromMouseCentered(xMouse, yMouse);
|
||||
return this._centerFromPointCentered(xMouse, yMouse);
|
||||
}
|
||||
|
||||
return null; // Should never be hit
|
||||
},
|
||||
|
||||
_centerFromMousePush: function(xMouse, yMouse) {
|
||||
_centerFromCaretPosition: function() {
|
||||
let xCaret = this._xCaret;
|
||||
let yCaret = this._yCaret;
|
||||
|
||||
if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.PROPORTIONAL)
|
||||
[xCaret, yCaret] = this._centerFromPointProportional(xCaret, yCaret);
|
||||
else if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.PUSH)
|
||||
[xCaret, yCaret] = this._centerFromPointPush(xCaret, yCaret);
|
||||
else if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.CENTERED)
|
||||
[xCaret, yCaret] = this._centerFromPointCentered(xCaret, yCaret);
|
||||
|
||||
this.scrollContentsTo(xCaret, yCaret);
|
||||
},
|
||||
|
||||
_centerFromFocusPosition: function() {
|
||||
let xFocus = this._xFocus;
|
||||
let yFocus = this._yFocus;
|
||||
|
||||
if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.PROPORTIONAL)
|
||||
[xFocus, yFocus] = this._centerFromPointProportional(xFocus, yFocus);
|
||||
else if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.PUSH)
|
||||
[xFocus, yFocus] = this._centerFromPointPush(xFocus, yFocus);
|
||||
else if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.CENTERED)
|
||||
[xFocus, yFocus] = this._centerFromPointCentered(xFocus, yFocus);
|
||||
|
||||
this.scrollContentsTo(xFocus, yFocus);
|
||||
},
|
||||
|
||||
_centerFromPointPush: function(xPoint, yPoint) {
|
||||
let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI();
|
||||
let [cursorWidth, cursorHeight] = this._mouseSourceActor.get_size();
|
||||
let xPos = xRoi + widthRoi / 2;
|
||||
@ -1263,20 +1385,20 @@ const ZoomRegion = new Lang.Class({
|
||||
let xRoiRight = xRoi + widthRoi - cursorWidth;
|
||||
let yRoiBottom = yRoi + heightRoi - cursorHeight;
|
||||
|
||||
if (xMouse < xRoi)
|
||||
xPos -= (xRoi - xMouse);
|
||||
else if (xMouse > xRoiRight)
|
||||
xPos += (xMouse - xRoiRight);
|
||||
if (xPoint < xRoi)
|
||||
xPos -= (xRoi - xPoint);
|
||||
else if (xPoint > xRoiRight)
|
||||
xPos += (xPoint - xRoiRight);
|
||||
|
||||
if (yMouse < yRoi)
|
||||
yPos -= (yRoi - yMouse);
|
||||
else if (yMouse > yRoiBottom)
|
||||
yPos += (yMouse - yRoiBottom);
|
||||
if (yPoint < yRoi)
|
||||
yPos -= (yRoi - yPoint);
|
||||
else if (yPoint > yRoiBottom)
|
||||
yPos += (yPoint - yRoiBottom);
|
||||
|
||||
return [xPos, yPos];
|
||||
},
|
||||
|
||||
_centerFromMouseProportional: function(xMouse, yMouse) {
|
||||
_centerFromPointProportional: function(xPoint, yPoint) {
|
||||
let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI();
|
||||
let halfScreenWidth = global.screen_width / 2;
|
||||
let halfScreenHeight = global.screen_height / 2;
|
||||
@ -1285,16 +1407,16 @@ const ZoomRegion = new Lang.Class({
|
||||
let unscaledPadding = Math.min(this._viewPortWidth, this._viewPortHeight) / 5;
|
||||
let xPadding = unscaledPadding / this._xMagFactor;
|
||||
let yPadding = unscaledPadding / this._yMagFactor;
|
||||
let xProportion = (xMouse - halfScreenWidth) / halfScreenWidth; // -1 ... 1
|
||||
let yProportion = (yMouse - halfScreenHeight) / halfScreenHeight; // -1 ... 1
|
||||
let xPos = xMouse - xProportion * (widthRoi / 2 - xPadding);
|
||||
let yPos = yMouse - yProportion * (heightRoi /2 - yPadding);
|
||||
let xProportion = (xPoint - halfScreenWidth) / halfScreenWidth; // -1 ... 1
|
||||
let yProportion = (yPoint - halfScreenHeight) / halfScreenHeight; // -1 ... 1
|
||||
let xPos = xPoint - xProportion * (widthRoi / 2 - xPadding);
|
||||
let yPos = yPoint - yProportion * (heightRoi /2 - yPadding);
|
||||
|
||||
return [xPos, yPos];
|
||||
},
|
||||
|
||||
_centerFromMouseCentered: function(xMouse, yMouse) {
|
||||
return [xMouse, yMouse];
|
||||
_centerFromPointCentered: function(xPoint, yPoint) {
|
||||
return [xPoint, yPoint];
|
||||
},
|
||||
|
||||
_screenToViewPort: function(screenX, screenY) {
|
||||
@ -1511,15 +1633,6 @@ const Crosshairs = new Lang.Class({
|
||||
this._vertBottomHair.set_opacity(opacity);
|
||||
},
|
||||
|
||||
/**
|
||||
* getOpacity:
|
||||
* Retriev how opaque the crosshairs are.
|
||||
* @return: A value between 0 (transparent) and 255 (opaque).
|
||||
*/
|
||||
getOpacity: function() {
|
||||
return this._horizLeftHair.get_opacity();
|
||||
},
|
||||
|
||||
/**
|
||||
* setLength:
|
||||
* Set the length of the vertical and horizontal lines in the crosshairs.
|
||||
@ -1563,15 +1676,6 @@ const Crosshairs = new Lang.Class({
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* getClip:
|
||||
* Get the dimensions of the clip rectangle.
|
||||
* @return: An array of the form [width, height].
|
||||
*/
|
||||
getClip: function() {
|
||||
return this._clipSize;
|
||||
},
|
||||
|
||||
/**
|
||||
* show:
|
||||
* Show the crosshairs.
|
||||
@ -1667,23 +1771,10 @@ const MagShaderEffects = new Lang.Class({
|
||||
this._inverse.set_enabled(invertFlag);
|
||||
},
|
||||
|
||||
/**
|
||||
* getInvertLightness:
|
||||
* Report whether the inversion effect is enabled.
|
||||
* @return: Boolean.
|
||||
*/
|
||||
getInvertLightness: function() {
|
||||
return this._inverse.get_enabled();
|
||||
},
|
||||
|
||||
setColorSaturation: function(factor) {
|
||||
this._colorDesaturation.set_factor(1.0 - factor);
|
||||
},
|
||||
|
||||
getColorSaturation: function() {
|
||||
return 1.0 - this._colorDesaturation.get_factor();
|
||||
},
|
||||
|
||||
/**
|
||||
* setBrightness:
|
||||
* Set the brightness of the magnified view.
|
||||
@ -1708,24 +1799,6 @@ const MagShaderEffects = new Lang.Class({
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* getBrightness:
|
||||
* Retrieve current brightness of the magnified view.
|
||||
* @return: Object containing the brightness for the red, green,
|
||||
* and blue channels. Values of 0.0 represent "standard"
|
||||
* brightness (no change), whereas values less or greater than
|
||||
* 0.0 indicate decreased or incresaed brightness, respectively.
|
||||
*/
|
||||
getBrightness: function() {
|
||||
let result = {};
|
||||
let [bRed, bGreen, bBlue] = this._brightnessContrast.get_brightness();
|
||||
result.r = bRed;
|
||||
result.g = bGreen;
|
||||
result.b = bBlue;
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the contrast of the magnified view.
|
||||
* @contrast: Object containing the contrast for the red, green,
|
||||
@ -1750,21 +1823,4 @@ const MagShaderEffects = new Lang.Class({
|
||||
bRed != NO_CHANGE || bGreen != NO_CHANGE || bBlue != NO_CHANGE
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve current contrast of the magnified view.
|
||||
* @return: Object containing the contrast for the red, green,
|
||||
* and blue channels. Values of 0.0 represent "standard"
|
||||
* contrast (no change), whereas values less or greater than
|
||||
* 0.0 indicate decreased or incresaed contrast, respectively.
|
||||
*/
|
||||
getContrast: function() {
|
||||
let resutl = {};
|
||||
let [cRed, cGreen, cBlue] = this._brightnessContrast.get_contrast();
|
||||
result.r = cRed;
|
||||
result.g = cGreen;
|
||||
result.b = cBlue;
|
||||
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
@ -4,14 +4,12 @@ const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const MAG_SERVICE_NAME = 'org.gnome.Magnifier';
|
||||
const MAG_SERVICE_PATH = '/org/gnome/Magnifier';
|
||||
const ZOOM_SERVICE_NAME = 'org.gnome.Magnifier.ZoomRegion';
|
||||
const ZOOM_SERVICE_PATH = '/org/gnome/Magnifier/ZoomRegion';
|
||||
|
||||
// Subset of gnome-mag's Magnifier dbus interface -- to be expanded. See:
|
||||
// http://git.gnome.org/browse/gnome-mag/tree/xml/...Magnifier.xml
|
||||
const MagnifierIface = <interface name={MAG_SERVICE_NAME}>
|
||||
const MagnifierIface = <interface name="org.gnome.Magnifier">
|
||||
<method name="setActive">
|
||||
<arg type="b" direction="in" />
|
||||
</method>
|
||||
@ -66,7 +64,7 @@ const MagnifierIface = <interface name={MAG_SERVICE_NAME}>
|
||||
|
||||
// Subset of gnome-mag's ZoomRegion dbus interface -- to be expanded. See:
|
||||
// http://git.gnome.org/browse/gnome-mag/tree/xml/...ZoomRegion.xml
|
||||
const ZoomRegionIface = <interface name={ZOOM_SERVICE_NAME}>
|
||||
const ZoomRegionIface = <interface name="org.gnome.Magnifier.ZoomRegion">
|
||||
<method name="setMagFactor">
|
||||
<arg type="d" direction="in" />
|
||||
<arg type="d" direction="in" />
|
||||
|
436
js/ui/main.js
436
js/ui/main.js
@ -18,39 +18,31 @@ const ExtensionSystem = imports.ui.extensionSystem;
|
||||
const ExtensionDownloader = imports.ui.extensionDownloader;
|
||||
const Keyboard = imports.ui.keyboard;
|
||||
const MessageTray = imports.ui.messageTray;
|
||||
const OsdWindow = imports.ui.osdWindow;
|
||||
const Overview = imports.ui.overview;
|
||||
const Panel = imports.ui.panel;
|
||||
const Params = imports.misc.params;
|
||||
const RunDialog = imports.ui.runDialog;
|
||||
const Layout = imports.ui.layout;
|
||||
const LoginManager = imports.misc.loginManager;
|
||||
const LookingGlass = imports.ui.lookingGlass;
|
||||
const NotificationDaemon = imports.ui.notificationDaemon;
|
||||
const WindowAttentionHandler = imports.ui.windowAttentionHandler;
|
||||
const Screencast = imports.ui.screencast;
|
||||
const ScreenShield = imports.ui.screenShield;
|
||||
const Scripting = imports.ui.scripting;
|
||||
const SessionMode = imports.ui.sessionMode;
|
||||
const ShellDBus = imports.ui.shellDBus;
|
||||
const ShellMountOperation = imports.ui.shellMountOperation;
|
||||
const UnlockDialog = imports.ui.unlockDialog;
|
||||
const WindowManager = imports.ui.windowManager;
|
||||
const Magnifier = imports.ui.magnifier;
|
||||
const XdndHandler = imports.ui.xdndHandler;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
const OVERRIDES_SCHEMA = 'org.gnome.shell.overrides';
|
||||
const DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff);
|
||||
|
||||
const KeybindingMode = {
|
||||
NONE: 0, // block all keybindings
|
||||
NORMAL: 1 << 0, // window mode
|
||||
OVERVIEW: 1 << 1,
|
||||
LOCK_SCREEN: 1 << 2,
|
||||
UNLOCK_SCREEN: 1 << 3,
|
||||
LOGIN_SCREEN: 1 << 4,
|
||||
MESSAGE_TRAY: 1 << 5,
|
||||
SYSTEM_MODAL: 1 << 6,
|
||||
LOOKING_GLASS: 1 << 7
|
||||
};
|
||||
const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard';
|
||||
const STICKY_KEYS_ENABLE = 'stickykeys-enable';
|
||||
|
||||
let componentManager = null;
|
||||
let panel = null;
|
||||
@ -63,12 +55,14 @@ let screenShield = null;
|
||||
let notificationDaemon = null;
|
||||
let windowAttentionHandler = null;
|
||||
let ctrlAltTabManager = null;
|
||||
let osdWindow = null;
|
||||
let sessionMode = null;
|
||||
let shellDBusService = null;
|
||||
let shellMountOpDBusService = null;
|
||||
let screenSaverDBus = null;
|
||||
let screencastService = null;
|
||||
let modalCount = 0;
|
||||
let keybindingMode = KeybindingMode.NORMAL;
|
||||
let keybindingMode = Shell.KeyBindingMode.NONE;
|
||||
let modalActorFocusStack = [];
|
||||
let uiGroup = null;
|
||||
let magnifier = null;
|
||||
@ -78,24 +72,30 @@ let layoutManager = null;
|
||||
let _startDate;
|
||||
let _defaultCssStylesheet = null;
|
||||
let _cssStylesheet = null;
|
||||
let _overridesSettings = null;
|
||||
|
||||
let background = null;
|
||||
let _a11ySettings = null;
|
||||
let dynamicWorkspacesSchema = null;
|
||||
|
||||
function _sessionUpdated() {
|
||||
_loadDefaultStylesheet();
|
||||
|
||||
wm.setCustomKeybindingHandler('panel-main-menu',
|
||||
KeybindingMode.NORMAL |
|
||||
KeybindingMode.OVERVIEW,
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW,
|
||||
sessionMode.hasOverview ? Lang.bind(overview, overview.toggle) : null);
|
||||
wm.allowKeybinding('overlay-key', KeybindingMode.NORMAL |
|
||||
KeybindingMode.OVERVIEW);
|
||||
wm.allowKeybinding('overlay-key', Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW);
|
||||
|
||||
wm.setCustomKeybindingHandler('panel-run-dialog',
|
||||
KeybindingMode.NORMAL |
|
||||
KeybindingMode.OVERVIEW,
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW,
|
||||
sessionMode.hasRunDialog ? openRunDialog : null);
|
||||
if (sessionMode.isGreeter)
|
||||
screenShield.showDialog();
|
||||
|
||||
if (!sessionMode.hasRunDialog) {
|
||||
if (runDialog)
|
||||
runDialog.close();
|
||||
if (lookingGlass)
|
||||
lookingGlass.close();
|
||||
}
|
||||
}
|
||||
|
||||
function start() {
|
||||
@ -103,17 +103,42 @@ function start() {
|
||||
global.logError = window.log;
|
||||
global.log = window.log;
|
||||
|
||||
if (!Meta.is_wayland_compositor)
|
||||
Meta.is_wayland_compositor = function () { return false; };
|
||||
|
||||
// Chain up async errors reported from C
|
||||
global.connect('notify-error', function (global, msg, detail) { notifyError(msg, detail); });
|
||||
|
||||
Gio.DesktopAppInfo.set_desktop_env('GNOME');
|
||||
|
||||
sessionMode = new SessionMode.SessionMode();
|
||||
Meta.prefs_override_preference_schema(sessionMode.buttonLayout[0], sessionMode.buttonLayout[1]);
|
||||
sessionMode.connect('sessions-loaded', _sessionsLoaded);
|
||||
sessionMode.init();
|
||||
}
|
||||
|
||||
function _sessionsLoaded() {
|
||||
sessionMode.connect('updated', _sessionUpdated);
|
||||
_initializePrefs();
|
||||
_initializeUI();
|
||||
|
||||
shellDBusService = new ShellDBus.GnomeShell();
|
||||
shellMountOpDBusService = new ShellMountOperation.GnomeShellMountOpHandler();
|
||||
|
||||
_sessionUpdated();
|
||||
}
|
||||
|
||||
function _initializePrefs() {
|
||||
let keys = new Gio.Settings({ schema: sessionMode.overridesSchema }).list_keys();
|
||||
for (let i = 0; i < keys.length; i++)
|
||||
Meta.prefs_override_preference_schema(keys[i], sessionMode.overridesSchema);
|
||||
|
||||
if (keys.indexOf('dynamic-workspaces') > -1)
|
||||
dynamicWorkspacesSchema = sessionMode.overridesSchema;
|
||||
else
|
||||
dynamicWorkspacesSchema = 'org.gnome.mutter';
|
||||
}
|
||||
|
||||
function _initializeUI() {
|
||||
// Ensure ShellWindowTracker and ShellAppUsage are initialized; this will
|
||||
// also initialize ShellAppSystem first. ShellAppSystem
|
||||
// needs to load all the .desktop files, and ShellWindowTracker
|
||||
@ -122,53 +147,30 @@ function start() {
|
||||
// and recalculate application associations, so to avoid
|
||||
// races for now we initialize it here. It's better to
|
||||
// be predictable anyways.
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
Shell.WindowTracker.get_default();
|
||||
Shell.AppUsage.get_default();
|
||||
|
||||
tracker.connect('startup-sequence-changed', _queueCheckWorkspaces);
|
||||
|
||||
// The stage is always covered so Clutter doesn't need to clear it; however
|
||||
// the color is used as the default contents for the Mutter root background
|
||||
// actor so set it anyways.
|
||||
global.stage.color = DEFAULT_BACKGROUND_COLOR;
|
||||
global.stage.no_clear_hint = true;
|
||||
|
||||
_defaultCssStylesheet = global.datadir + '/theme/gnome-shell.css';
|
||||
loadTheme();
|
||||
|
||||
// Set up stage hierarchy to group all UI actors under one container.
|
||||
uiGroup = new Shell.GenericContainer({ name: 'uiGroup' });
|
||||
uiGroup.connect('allocate',
|
||||
function (actor, box, flags) {
|
||||
let children = uiGroup.get_children();
|
||||
for (let i = 0; i < children.length; i++)
|
||||
children[i].allocate_preferred_size(flags);
|
||||
});
|
||||
uiGroup.connect('get-preferred-width',
|
||||
function(actor, forHeight, alloc) {
|
||||
let width = global.stage.width;
|
||||
[alloc.min_size, alloc.natural_size] = [width, width];
|
||||
});
|
||||
uiGroup.connect('get-preferred-height',
|
||||
function(actor, forWidth, alloc) {
|
||||
let height = global.stage.height;
|
||||
[alloc.min_size, alloc.natural_size] = [height, height];
|
||||
});
|
||||
global.window_group.reparent(uiGroup);
|
||||
global.overlay_group.reparent(uiGroup);
|
||||
global.stage.add_actor(uiGroup);
|
||||
_loadDefaultStylesheet();
|
||||
|
||||
// Setup the stage hierarchy early
|
||||
layoutManager = new Layout.LayoutManager();
|
||||
|
||||
// Various parts of the codebase still refers to Main.uiGroup
|
||||
// instead using the layoutManager. This keeps that code
|
||||
// working until it's updated.
|
||||
uiGroup = layoutManager.uiGroup;
|
||||
|
||||
screencastService = new Screencast.ScreencastService();
|
||||
xdndHandler = new XdndHandler.XdndHandler();
|
||||
ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager();
|
||||
osdWindow = new OsdWindow.OsdWindow();
|
||||
overview = new Overview.Overview();
|
||||
magnifier = new Magnifier.Magnifier();
|
||||
if (UnlockDialog.isSupported())
|
||||
screenShield = new ScreenShield.ScreenShield();
|
||||
else
|
||||
screenShield = new ScreenShield.ScreenShieldFallback();
|
||||
panel = new Panel.Panel();
|
||||
wm = new WindowManager.WindowManager();
|
||||
magnifier = new Magnifier.Magnifier();
|
||||
if (LoginManager.canLock())
|
||||
screenShield = new ScreenShield.ScreenShield();
|
||||
|
||||
panel = new Panel.Panel();
|
||||
messageTray = new MessageTray.MessageTray();
|
||||
keyboard = new Keyboard.Keyboard();
|
||||
notificationDaemon = new NotificationDaemon.NotificationDaemon();
|
||||
@ -176,19 +178,22 @@ function start() {
|
||||
componentManager = new Components.ComponentManager();
|
||||
|
||||
layoutManager.init();
|
||||
keyboard.init();
|
||||
overview.init();
|
||||
|
||||
global.screen.override_workspace_layout(Meta.ScreenCorner.TOPLEFT,
|
||||
false, -1, 1);
|
||||
global.display.connect('overlay-key', Lang.bind(overview, overview.toggle));
|
||||
sessionMode.connect('update', _sessionUpdated);
|
||||
_sessionUpdated();
|
||||
_a11ySettings = new Gio.Settings({ schema: A11Y_SCHEMA });
|
||||
|
||||
global.display.connect('overlay-key', Lang.bind(overview, function () {
|
||||
if (!_a11ySettings.get_boolean (STICKY_KEYS_ENABLE))
|
||||
overview.toggle();
|
||||
}));
|
||||
|
||||
// Provide the bus object for gnome-session to
|
||||
// initiate logouts.
|
||||
EndSessionDialog.init();
|
||||
|
||||
// We're ready for the session manager to move to the next phase
|
||||
Meta.register_with_session();
|
||||
|
||||
_startDate = new Date();
|
||||
|
||||
log('GNOME Shell started at ' + _startDate);
|
||||
@ -200,200 +205,35 @@ function start() {
|
||||
Scripting.runPerfScript(module, perfOutput);
|
||||
}
|
||||
|
||||
_overridesSettings = new Gio.Settings({ schema: OVERRIDES_SCHEMA });
|
||||
_overridesSettings.connect('changed::dynamic-workspaces', _queueCheckWorkspaces);
|
||||
|
||||
global.screen.connect('notify::n-workspaces', _nWorkspacesChanged);
|
||||
|
||||
global.screen.connect('window-entered-monitor', _windowEnteredMonitor);
|
||||
global.screen.connect('window-left-monitor', _windowLeftMonitor);
|
||||
global.screen.connect('restacked', _windowsRestacked);
|
||||
|
||||
_nWorkspacesChanged();
|
||||
|
||||
ExtensionDownloader.init();
|
||||
ExtensionSystem.init();
|
||||
|
||||
if (sessionMode.isGreeter && screenShield) {
|
||||
layoutManager.connect('startup-prepared', function() {
|
||||
screenShield.showDialog();
|
||||
});
|
||||
}
|
||||
|
||||
layoutManager.connect('startup-complete', function() {
|
||||
if (keybindingMode == Shell.KeyBindingMode.NONE) {
|
||||
keybindingMode = Shell.KeyBindingMode.NORMAL;
|
||||
}
|
||||
if (screenShield) {
|
||||
screenShield.lockIfWasLocked();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let _workspaces = [];
|
||||
let _checkWorkspacesId = 0;
|
||||
function _loadDefaultStylesheet() {
|
||||
if (!sessionMode.isPrimary)
|
||||
return;
|
||||
|
||||
/*
|
||||
* When the last window closed on a workspace is a dialog or splash
|
||||
* screen, we assume that it might be an initial window shown before
|
||||
* the main window of an application, and give the app a grace period
|
||||
* where it can map another window before we remove the workspace.
|
||||
*/
|
||||
const LAST_WINDOW_GRACE_TIME = 1000;
|
||||
let stylesheet = global.datadir + '/theme/' + sessionMode.stylesheetName;
|
||||
if (_defaultCssStylesheet == stylesheet)
|
||||
return;
|
||||
|
||||
function _checkWorkspaces() {
|
||||
let i;
|
||||
let emptyWorkspaces = [];
|
||||
|
||||
if (!Meta.prefs_get_dynamic_workspaces()) {
|
||||
_checkWorkspacesId = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = 0; i < _workspaces.length; i++) {
|
||||
let lastRemoved = _workspaces[i]._lastRemovedWindow;
|
||||
if ((lastRemoved &&
|
||||
(lastRemoved.get_window_type() == Meta.WindowType.SPLASHSCREEN ||
|
||||
lastRemoved.get_window_type() == Meta.WindowType.DIALOG ||
|
||||
lastRemoved.get_window_type() == Meta.WindowType.MODAL_DIALOG)) ||
|
||||
_workspaces[i]._keepAliveId)
|
||||
emptyWorkspaces[i] = false;
|
||||
else
|
||||
emptyWorkspaces[i] = true;
|
||||
}
|
||||
|
||||
let sequences = Shell.WindowTracker.get_default().get_startup_sequences();
|
||||
for (i = 0; i < sequences.length; i++) {
|
||||
let index = sequences[i].get_workspace();
|
||||
if (index >= 0 && index <= global.screen.n_workspaces)
|
||||
emptyWorkspaces[index] = false;
|
||||
}
|
||||
|
||||
let windows = global.get_window_actors();
|
||||
for (i = 0; i < windows.length; i++) {
|
||||
let win = windows[i];
|
||||
|
||||
if (win.get_meta_window().is_on_all_workspaces())
|
||||
continue;
|
||||
|
||||
let workspaceIndex = win.get_workspace();
|
||||
emptyWorkspaces[workspaceIndex] = false;
|
||||
}
|
||||
|
||||
// If we don't have an empty workspace at the end, add one
|
||||
if (!emptyWorkspaces[emptyWorkspaces.length -1]) {
|
||||
global.screen.append_new_workspace(false, global.get_current_time());
|
||||
emptyWorkspaces.push(false);
|
||||
}
|
||||
|
||||
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
|
||||
let removingCurrentWorkspace = (emptyWorkspaces[activeWorkspaceIndex] &&
|
||||
activeWorkspaceIndex < emptyWorkspaces.length - 1);
|
||||
// Don't enter the overview when removing multiple empty workspaces at startup
|
||||
let showOverview = (removingCurrentWorkspace &&
|
||||
!emptyWorkspaces.every(function(x) { return x; }));
|
||||
|
||||
if (removingCurrentWorkspace) {
|
||||
// "Merge" the empty workspace we are removing with the one at the end
|
||||
wm.blockAnimations();
|
||||
}
|
||||
|
||||
// Delete other empty workspaces; do it from the end to avoid index changes
|
||||
for (i = emptyWorkspaces.length - 2; i >= 0; i--) {
|
||||
if (emptyWorkspaces[i])
|
||||
global.screen.remove_workspace(_workspaces[i], global.get_current_time());
|
||||
}
|
||||
|
||||
if (removingCurrentWorkspace) {
|
||||
global.screen.get_workspace_by_index(global.screen.n_workspaces - 1).activate(global.get_current_time());
|
||||
wm.unblockAnimations();
|
||||
|
||||
if (!overview.visible && showOverview)
|
||||
overview.show();
|
||||
}
|
||||
|
||||
_checkWorkspacesId = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
function keepWorkspaceAlive(workspace, duration) {
|
||||
if (workspace._keepAliveId)
|
||||
Mainloop.source_remove(workspace._keepAliveId);
|
||||
|
||||
workspace._keepAliveId = Mainloop.timeout_add(duration, function() {
|
||||
workspace._keepAliveId = 0;
|
||||
_queueCheckWorkspaces();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function _windowRemoved(workspace, window) {
|
||||
workspace._lastRemovedWindow = window;
|
||||
_queueCheckWorkspaces();
|
||||
Mainloop.timeout_add(LAST_WINDOW_GRACE_TIME, function() {
|
||||
if (workspace._lastRemovedWindow == window) {
|
||||
workspace._lastRemovedWindow = null;
|
||||
_queueCheckWorkspaces();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function _windowLeftMonitor(metaScreen, monitorIndex, metaWin) {
|
||||
// If the window left the primary monitor, that
|
||||
// might make that workspace empty
|
||||
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 == 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);
|
||||
}
|
||||
|
||||
function _nWorkspacesChanged() {
|
||||
let oldNumWorkspaces = _workspaces.length;
|
||||
let newNumWorkspaces = global.screen.n_workspaces;
|
||||
|
||||
if (oldNumWorkspaces == newNumWorkspaces)
|
||||
return false;
|
||||
|
||||
let lostWorkspaces = [];
|
||||
if (newNumWorkspaces > oldNumWorkspaces) {
|
||||
let w;
|
||||
|
||||
// Assume workspaces are only added at the end
|
||||
for (w = oldNumWorkspaces; w < newNumWorkspaces; w++)
|
||||
_workspaces[w] = global.screen.get_workspace_by_index(w);
|
||||
|
||||
for (w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
|
||||
let workspace = _workspaces[w];
|
||||
workspace._windowAddedId = workspace.connect('window-added', _queueCheckWorkspaces);
|
||||
workspace._windowRemovedId = workspace.connect('window-removed', _windowRemoved);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Assume workspaces are only removed sequentially
|
||||
// (e.g. 2,3,4 - not 2,4,7)
|
||||
let removedIndex;
|
||||
let removedNum = oldNumWorkspaces - newNumWorkspaces;
|
||||
for (let w = 0; w < oldNumWorkspaces; w++) {
|
||||
let workspace = global.screen.get_workspace_by_index(w);
|
||||
if (_workspaces[w] != workspace) {
|
||||
removedIndex = w;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let lostWorkspaces = _workspaces.splice(removedIndex, removedNum);
|
||||
lostWorkspaces.forEach(function(workspace) {
|
||||
workspace.disconnect(workspace._windowAddedId);
|
||||
workspace.disconnect(workspace._windowRemovedId);
|
||||
});
|
||||
}
|
||||
|
||||
_queueCheckWorkspaces();
|
||||
|
||||
return false;
|
||||
_defaultCssStylesheet = stylesheet;
|
||||
loadTheme();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -430,11 +270,8 @@ 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 });
|
||||
let theme = new St.Theme ({ application_stylesheet: _cssStylesheet,
|
||||
default_stylesheet: _defaultCssStylesheet });
|
||||
|
||||
if (previousTheme) {
|
||||
let customStylesheets = previousTheme.get_custom_stylesheets();
|
||||
@ -476,17 +313,6 @@ function notifyError(msg, details) {
|
||||
notify(msg, details);
|
||||
}
|
||||
|
||||
function isWindowActorDisplayedOnWorkspace(win, workspaceIndex) {
|
||||
return win.get_workspace() == workspaceIndex ||
|
||||
(win.get_meta_window() && win.get_meta_window().is_on_all_workspaces());
|
||||
}
|
||||
|
||||
function getWindowActorsForWorkspace(workspaceIndex) {
|
||||
return global.get_window_actors().filter(function (win) {
|
||||
return isWindowActorDisplayedOnWorkspace(win, workspaceIndex);
|
||||
});
|
||||
}
|
||||
|
||||
function _findModal(actor) {
|
||||
for (let i = 0; i < modalActorFocusStack.length; i++) {
|
||||
if (modalActorFocusStack[i].actor == actor)
|
||||
@ -495,10 +321,6 @@ function _findModal(actor) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
function isInModalStack(actor) {
|
||||
return _findModal(actor) != -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* pushModal:
|
||||
* @actor: #ClutterActor which will be given keyboard focus
|
||||
@ -521,7 +343,7 @@ function isInModalStack(actor) {
|
||||
* - options: Meta.ModalOptions flags to indicate that the pointer is
|
||||
* already grabbed
|
||||
*
|
||||
* - keybindingMode: used to set the current Main.KeybindingMode to filter
|
||||
* - keybindingMode: used to set the current Shell.KeyBindingMode to filter
|
||||
* global keybindings; the default of NONE will filter
|
||||
* out all keybindings
|
||||
*
|
||||
@ -530,7 +352,7 @@ function isInModalStack(actor) {
|
||||
function pushModal(actor, params) {
|
||||
params = Params.parse(params, { timestamp: global.get_current_time(),
|
||||
options: 0,
|
||||
keybindingMode: KeybindingMode.NONE });
|
||||
keybindingMode: Shell.KeyBindingMode.NONE });
|
||||
|
||||
if (modalCount == 0) {
|
||||
if (!global.begin_modal(params.timestamp, params.options)) {
|
||||
@ -540,27 +362,26 @@ function pushModal(actor, params) {
|
||||
Meta.disable_unredirect_for_screen(global.screen);
|
||||
}
|
||||
|
||||
global.set_stage_input_mode(Shell.StageInputMode.FULLSCREEN);
|
||||
|
||||
modalCount += 1;
|
||||
let actorDestroyId = actor.connect('destroy', function() {
|
||||
let index = _findModal(actor);
|
||||
if (index >= 0)
|
||||
popModal(actor);
|
||||
});
|
||||
let curFocus = global.stage.get_key_focus();
|
||||
let curFocusDestroyId;
|
||||
if (curFocus != null) {
|
||||
curFocusDestroyId = curFocus.connect('destroy', function() {
|
||||
|
||||
let prevFocus = global.stage.get_key_focus();
|
||||
let prevFocusDestroyId;
|
||||
if (prevFocus != null) {
|
||||
prevFocusDestroyId = prevFocus.connect('destroy', function() {
|
||||
let index = _findModal(actor);
|
||||
if (index >= 0)
|
||||
modalActorFocusStack[index].actor = null;
|
||||
modalActorFocusStack[index].prevFocus = null;
|
||||
});
|
||||
}
|
||||
modalActorFocusStack.push({ actor: actor,
|
||||
focus: curFocus,
|
||||
destroyId: actorDestroyId,
|
||||
focusDestroyId: curFocusDestroyId,
|
||||
prevFocus: prevFocus,
|
||||
prevFocusDestroyId: prevFocusDestroyId,
|
||||
keybindingMode: keybindingMode });
|
||||
|
||||
keybindingMode = params.keybindingMode;
|
||||
@ -589,8 +410,7 @@ function popModal(actor, timestamp) {
|
||||
if (focusIndex < 0) {
|
||||
global.stage.set_key_focus(null);
|
||||
global.end_modal(timestamp);
|
||||
global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
|
||||
keybindingMode = KeybindingMode.NORMAL;
|
||||
keybindingMode = Shell.KeyBindingMode.NORMAL;
|
||||
|
||||
throw new Error('incorrect pop');
|
||||
}
|
||||
@ -601,18 +421,33 @@ function popModal(actor, timestamp) {
|
||||
record.actor.disconnect(record.destroyId);
|
||||
|
||||
if (focusIndex == modalActorFocusStack.length - 1) {
|
||||
if (record.focus)
|
||||
record.focus.disconnect(record.focusDestroyId);
|
||||
if (record.prevFocus)
|
||||
record.prevFocus.disconnect(record.prevFocusDestroyId);
|
||||
keybindingMode = record.keybindingMode;
|
||||
global.stage.set_key_focus(record.focus);
|
||||
global.stage.set_key_focus(record.prevFocus);
|
||||
} else {
|
||||
// If we have:
|
||||
// global.stage.set_focus(a);
|
||||
// Main.pushModal(b);
|
||||
// Main.pushModal(c);
|
||||
// Main.pushModal(d);
|
||||
//
|
||||
// then we have the stack:
|
||||
// [{ prevFocus: a, actor: b },
|
||||
// { prevFocus: b, actor: c },
|
||||
// { prevFocus: c, actor: d }]
|
||||
//
|
||||
// When actor c is destroyed/popped, if we only simply remove the
|
||||
// record, then the focus stack will be [a, c], rather than the correct
|
||||
// [a, b]. Shift the focus stack up before removing the record to ensure
|
||||
// that we get the correct result.
|
||||
let t = modalActorFocusStack[modalActorFocusStack.length - 1];
|
||||
if (t.focus)
|
||||
t.focus.disconnect(t.focusDestroyId);
|
||||
if (t.prevFocus)
|
||||
t.prevFocus.disconnect(t.prevFocusDestroyId);
|
||||
// Remove from the middle, shift the focus chain up
|
||||
for (let i = modalActorFocusStack.length - 1; i > focusIndex; i--) {
|
||||
modalActorFocusStack[i].focus = modalActorFocusStack[i - 1].focus;
|
||||
modalActorFocusStack[i].focusDestroyId = modalActorFocusStack[i - 1].focusDestroyId;
|
||||
modalActorFocusStack[i].prevFocus = modalActorFocusStack[i - 1].prevFocus;
|
||||
modalActorFocusStack[i].prevFocusDestroyId = modalActorFocusStack[i - 1].prevFocusDestroyId;
|
||||
modalActorFocusStack[i].keybindingMode = modalActorFocusStack[i - 1].keybindingMode;
|
||||
}
|
||||
}
|
||||
@ -622,9 +457,8 @@ function popModal(actor, timestamp) {
|
||||
return;
|
||||
|
||||
global.end_modal(timestamp);
|
||||
global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
|
||||
Meta.enable_unredirect_for_screen(global.screen);
|
||||
keybindingMode = KeybindingMode.NORMAL;
|
||||
keybindingMode = Shell.KeyBindingMode.NORMAL;
|
||||
}
|
||||
|
||||
function createLookingGlass() {
|
||||
|
1359
js/ui/messageTray.js
1359
js/ui/messageTray.js
File diff suppressed because it is too large
Load Diff
@ -14,6 +14,7 @@ const Atk = imports.gi.Atk;
|
||||
|
||||
const Params = imports.misc.params;
|
||||
|
||||
const Animation = imports.ui.animation;
|
||||
const Layout = imports.ui.layout;
|
||||
const Lightbox = imports.ui.lightbox;
|
||||
const Main = imports.ui.main;
|
||||
@ -22,6 +23,10 @@ const Tweener = imports.ui.tweener;
|
||||
const OPEN_AND_CLOSE_TIME = 0.1;
|
||||
const FADE_OUT_DIALOG_TIME = 1.0;
|
||||
|
||||
const WORK_SPINNER_ICON_SIZE = 24;
|
||||
const WORK_SPINNER_ANIMATION_DELAY = 1.0;
|
||||
const WORK_SPINNER_ANIMATION_TIME = 0.3;
|
||||
|
||||
const State = {
|
||||
OPENED: 0,
|
||||
CLOSED: 1,
|
||||
@ -37,14 +42,16 @@ const ModalDialog = new Lang.Class({
|
||||
params = Params.parse(params, { shellReactive: false,
|
||||
styleClass: null,
|
||||
parentActor: Main.uiGroup,
|
||||
keybindingMode: Main.KeybindingMode.SYSTEM_MODAL,
|
||||
shouldFadeIn: true });
|
||||
keybindingMode: Shell.KeyBindingMode.SYSTEM_MODAL,
|
||||
shouldFadeIn: true,
|
||||
destroyOnClose: true });
|
||||
|
||||
this.state = State.CLOSED;
|
||||
this._hasModal = false;
|
||||
this._keybindingMode = params.keybindingMode;
|
||||
this._shellReactive = params.shellReactive;
|
||||
this._shouldFadeIn = params.shouldFadeIn;
|
||||
this._destroyOnClose = params.destroyOnClose;
|
||||
|
||||
this._group = new St.Widget({ visible: false,
|
||||
x: 0,
|
||||
@ -58,55 +65,59 @@ const ModalDialog = new Lang.Class({
|
||||
|
||||
this._group.connect('destroy', Lang.bind(this, this._onGroupDestroy));
|
||||
|
||||
this._pressedKey = null;
|
||||
this._buttonKeys = {};
|
||||
this._group.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent));
|
||||
this._group.connect('key-release-event', Lang.bind(this, this._onKeyReleaseEvent));
|
||||
|
||||
this._backgroundBin = new St.Bin();
|
||||
this.backgroundStack = new St.Widget({ layout_manager: new Clutter.BinLayout() });
|
||||
this._backgroundBin = new St.Bin({ child: this.backgroundStack,
|
||||
x_fill: true, y_fill: true });
|
||||
this._monitorConstraint = new Layout.MonitorConstraint();
|
||||
this._backgroundBin.add_constraint(this._monitorConstraint);
|
||||
this._group.add_actor(this._backgroundBin);
|
||||
|
||||
this.dialogLayout = new St.BoxLayout({ style_class: 'modal-dialog',
|
||||
vertical: true });
|
||||
if (params.styleClass != null) {
|
||||
// modal dialogs are fixed width and grow vertically; set the request
|
||||
// mode accordingly so wrapped labels are handled correctly during
|
||||
// size requests.
|
||||
this.dialogLayout.request_mode = Clutter.RequestMode.HEIGHT_FOR_WIDTH;
|
||||
|
||||
if (params.styleClass != null)
|
||||
this.dialogLayout.add_style_class_name(params.styleClass);
|
||||
}
|
||||
|
||||
if (!this._shellReactive) {
|
||||
this._lightbox = new Lightbox.Lightbox(this._group,
|
||||
{ inhibitEvents: true });
|
||||
this._lightbox.highlight(this._backgroundBin);
|
||||
|
||||
let stack = new Shell.Stack();
|
||||
this._backgroundBin.child = stack;
|
||||
|
||||
this._eventBlocker = new Clutter.Group({ reactive: true });
|
||||
stack.add_actor(this._eventBlocker);
|
||||
stack.add_actor(this.dialogLayout);
|
||||
} else {
|
||||
this._backgroundBin.child = this.dialogLayout;
|
||||
this._eventBlocker = new Clutter.Actor({ reactive: true });
|
||||
this.backgroundStack.add_actor(this._eventBlocker);
|
||||
}
|
||||
this.backgroundStack.add_actor(this.dialogLayout);
|
||||
|
||||
|
||||
this.contentLayout = new St.BoxLayout({ vertical: true });
|
||||
this.dialogLayout.add(this.contentLayout,
|
||||
{ x_fill: true,
|
||||
{ expand: true,
|
||||
x_fill: true,
|
||||
y_fill: true,
|
||||
x_align: St.Align.MIDDLE,
|
||||
y_align: St.Align.START });
|
||||
|
||||
this.buttonLayout = new St.BoxLayout({ style_class: 'modal-dialog-button-box',
|
||||
visible: false,
|
||||
vertical: false });
|
||||
vertical: false });
|
||||
this.dialogLayout.add(this.buttonLayout,
|
||||
{ expand: true,
|
||||
x_align: St.Align.MIDDLE,
|
||||
{ x_align: St.Align.MIDDLE,
|
||||
y_align: St.Align.END });
|
||||
|
||||
global.focus_manager.add_group(this.dialogLayout);
|
||||
this._initialKeyFocus = this.dialogLayout;
|
||||
this._initialKeyFocusDestroyId = 0;
|
||||
this._savedKeyFocus = null;
|
||||
|
||||
this._workSpinner = null;
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
@ -120,7 +131,6 @@ const ModalDialog = new Lang.Class({
|
||||
|
||||
setButtons: function(buttons) {
|
||||
this.clearButtons();
|
||||
this.buttonLayout.visible = (buttons.length > 0);
|
||||
|
||||
for (let i = 0; i < buttons.length; i++) {
|
||||
let buttonInfo = buttons[i];
|
||||
@ -159,6 +169,7 @@ const ModalDialog = new Lang.Class({
|
||||
keys = [];
|
||||
|
||||
let button = new St.Button({ style_class: 'modal-dialog-button',
|
||||
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
|
||||
reactive: true,
|
||||
can_focus: true,
|
||||
label: label });
|
||||
@ -180,10 +191,55 @@ const ModalDialog = new Lang.Class({
|
||||
return button;
|
||||
},
|
||||
|
||||
_onKeyReleaseEvent: function(object, event) {
|
||||
let symbol = event.get_key_symbol();
|
||||
let buttonInfo = this._buttonKeys[symbol];
|
||||
placeSpinner: function(layoutInfo) {
|
||||
let spinnerIcon = global.datadir + '/theme/process-working.svg';
|
||||
this._workSpinner = new Animation.AnimatedIcon(spinnerIcon, WORK_SPINNER_ICON_SIZE);
|
||||
this._workSpinner.actor.opacity = 0;
|
||||
this._workSpinner.actor.show();
|
||||
|
||||
this.buttonLayout.add(this._workSpinner.actor, layoutInfo);
|
||||
},
|
||||
|
||||
setWorking: function(working) {
|
||||
if (!this._workSpinner)
|
||||
return;
|
||||
|
||||
Tweener.removeTweens(this._workSpinner.actor);
|
||||
if (working) {
|
||||
this._workSpinner.play();
|
||||
Tweener.addTween(this._workSpinner.actor,
|
||||
{ opacity: 255,
|
||||
delay: WORK_SPINNER_ANIMATION_DELAY,
|
||||
time: WORK_SPINNER_ANIMATION_TIME,
|
||||
transition: 'linear'
|
||||
});
|
||||
} else {
|
||||
Tweener.addTween(this._workSpinner.actor,
|
||||
{ opacity: 0,
|
||||
time: WORK_SPINNER_ANIMATION_TIME,
|
||||
transition: 'linear',
|
||||
onCompleteScope: this,
|
||||
onComplete: function() {
|
||||
if (this._workSpinner)
|
||||
this._workSpinner.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_onKeyPressEvent: function(object, event) {
|
||||
this._pressedKey = event.get_key_symbol();
|
||||
},
|
||||
|
||||
_onKeyReleaseEvent: function(object, event) {
|
||||
let pressedKey = this._pressedKey;
|
||||
this._pressedKey = null;
|
||||
|
||||
let symbol = event.get_key_symbol();
|
||||
if (symbol != pressedKey)
|
||||
return false;
|
||||
|
||||
let buttonInfo = this._buttonKeys[symbol];
|
||||
if (!buttonInfo)
|
||||
return false;
|
||||
|
||||
@ -266,6 +322,10 @@ const ModalDialog = new Lang.Class({
|
||||
function() {
|
||||
this.state = State.CLOSED;
|
||||
this._group.hide();
|
||||
this.emit('closed');
|
||||
|
||||
if (this._destroyOnClose)
|
||||
this.destroy();
|
||||
})
|
||||
});
|
||||
},
|
||||
@ -301,8 +361,9 @@ const ModalDialog = new Lang.Class({
|
||||
if (this._savedKeyFocus) {
|
||||
this._savedKeyFocus.grab_key_focus();
|
||||
this._savedKeyFocus = null;
|
||||
} else
|
||||
} else {
|
||||
this._initialKeyFocus.grab_key_focus();
|
||||
}
|
||||
|
||||
if (!this._shellReactive)
|
||||
this._eventBlocker.lower_bottom();
|
||||
|
@ -103,6 +103,126 @@ const STANDARD_TRAY_ICON_IMPLEMENTATIONS = {
|
||||
'ibus-ui-gtk': 'keyboard'
|
||||
};
|
||||
|
||||
const NotificationGenericPolicy = new Lang.Class({
|
||||
Name: 'NotificationGenericPolicy',
|
||||
Extends: MessageTray.NotificationPolicy,
|
||||
|
||||
_init: function() {
|
||||
// Don't chain to parent, it would try setting
|
||||
// our properties to the defaults
|
||||
|
||||
this.id = 'generic';
|
||||
|
||||
this._masterSettings = new Gio.Settings({ schema: 'org.gnome.desktop.notifications' });
|
||||
this._masterSettings.connect('changed', Lang.bind(this, this._changed));
|
||||
},
|
||||
|
||||
store: function() { },
|
||||
|
||||
destroy: function() {
|
||||
this._masterSettings.run_dispose();
|
||||
},
|
||||
|
||||
_changed: function(settings, key) {
|
||||
this.emit('policy-changed', key);
|
||||
},
|
||||
|
||||
get enable() {
|
||||
return true;
|
||||
},
|
||||
|
||||
get enableSound() {
|
||||
return true;
|
||||
},
|
||||
|
||||
get showBanners() {
|
||||
return this._masterSettings.get_boolean('show-banners');
|
||||
},
|
||||
|
||||
get forceExpanded() {
|
||||
return false;
|
||||
},
|
||||
|
||||
get showInLockScreen() {
|
||||
return this._masterSettings.get_boolean('show-in-lock-screen');
|
||||
},
|
||||
|
||||
get detailsInLockScreen() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
const NotificationApplicationPolicy = new Lang.Class({
|
||||
Name: 'NotificationApplicationPolicy',
|
||||
Extends: MessageTray.NotificationPolicy,
|
||||
|
||||
_init: function(id) {
|
||||
// Don't chain to parent, it would try setting
|
||||
// our properties to the defaults
|
||||
|
||||
this.id = id;
|
||||
this._canonicalId = this._canonicalizeId(id)
|
||||
|
||||
this._masterSettings = new Gio.Settings({ schema: 'org.gnome.desktop.notifications' });
|
||||
this._settings = new Gio.Settings({ schema: 'org.gnome.desktop.notifications.application',
|
||||
path: '/org/gnome/desktop/notifications/application/' + this._canonicalId + '/' });
|
||||
|
||||
this._masterSettings.connect('changed', Lang.bind(this, this._changed));
|
||||
this._settings.connect('changed', Lang.bind(this, this._changed));
|
||||
},
|
||||
|
||||
store: function() {
|
||||
this._settings.set_string('application-id', this.id + '.desktop');
|
||||
|
||||
let apps = this._masterSettings.get_strv('application-children');
|
||||
if (apps.indexOf(this._canonicalId) < 0) {
|
||||
apps.push(this._canonicalId);
|
||||
this._masterSettings.set_strv('application-children', apps);
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this._masterSettings.run_dispose();
|
||||
this._settings.run_dispose();
|
||||
},
|
||||
|
||||
_changed: function(settings, key) {
|
||||
this.emit('policy-changed', key);
|
||||
},
|
||||
|
||||
_canonicalizeId: function(id) {
|
||||
// Keys are restricted to lowercase alphanumeric characters and dash,
|
||||
// and two dashes cannot be in succession
|
||||
return id.toLowerCase().replace(/[^a-z0-9\-]/g, '-').replace(/--+/g, '-');
|
||||
},
|
||||
|
||||
get enable() {
|
||||
return this._settings.get_boolean('enable');
|
||||
},
|
||||
|
||||
get enableSound() {
|
||||
return this._settings.get_boolean('enable-sound-alerts');
|
||||
},
|
||||
|
||||
get showBanners() {
|
||||
return this._masterSettings.get_boolean('show-banners') &&
|
||||
this._settings.get_boolean('show-banners');
|
||||
},
|
||||
|
||||
get forceExpanded() {
|
||||
return this._settings.get_boolean('force-expanded');
|
||||
},
|
||||
|
||||
get showInLockScreen() {
|
||||
return this._masterSettings.get_boolean('show-in-lock-screen') &&
|
||||
this._settings.get_boolean('show-in-lock-screen');
|
||||
},
|
||||
|
||||
get detailsInLockScreen() {
|
||||
return this._settings.get_boolean('details-in-lock-screen');
|
||||
}
|
||||
});
|
||||
|
||||
const NotificationDaemon = new Lang.Class({
|
||||
Name: 'NotificationDaemon',
|
||||
|
||||
@ -124,45 +244,45 @@ const NotificationDaemon = new Lang.Class({
|
||||
Main.overview.connect('hidden',
|
||||
Lang.bind(this, this._onFocusAppChanged));
|
||||
|
||||
this._trayManager.manage_stage(global.stage, Main.messageTray.actor);
|
||||
this._trayManager.manage_screen(global.screen, Main.messageTray.actor);
|
||||
},
|
||||
|
||||
_iconForNotificationData: function(icon, hints) {
|
||||
// If an icon is not specified, we use 'image-data' or 'image-path' hint for an icon
|
||||
// and don't show a large image. There are currently many applications that use
|
||||
// notify_notification_set_icon_from_pixbuf() from libnotify, which in turn sets
|
||||
// the 'image-data' hint. These applications don't typically pass in 'app_icon'
|
||||
// argument to Notify() and actually expect the pixbuf to be shown as an icon.
|
||||
// So the logic here does the right thing for this case. If both an icon and either
|
||||
// one of 'image-data' or 'image-path' are specified, we show both an icon and
|
||||
// a large image.
|
||||
if (icon) {
|
||||
if (icon.substr(0, 7) == 'file://')
|
||||
return new Gio.FileIcon({ file: Gio.File.new_for_uri(icon) });
|
||||
else if (icon[0] == '/') {
|
||||
return new Gio.FileIcon({ file: Gio.File.new_for_path(icon) });
|
||||
} else
|
||||
return new Gio.ThemedIcon({ name: icon });
|
||||
} else if (hints['image-data']) {
|
||||
_imageForNotificationData: function(hints) {
|
||||
if (hints['image-data']) {
|
||||
let [width, height, rowStride, hasAlpha,
|
||||
bitsPerSample, nChannels, data] = hints['image-data'];
|
||||
return Shell.util_create_pixbuf_from_data(data, GdkPixbuf.Colorspace.RGB, hasAlpha,
|
||||
bitsPerSample, width, height, rowStride);
|
||||
} else if (hints['image-path']) {
|
||||
return new Gio.FileIcon({ file: Gio.File.new_for_path(hints['image-path']) });
|
||||
} else {
|
||||
let stockIcon;
|
||||
switch (hints.urgency) {
|
||||
case Urgency.LOW:
|
||||
case Urgency.NORMAL:
|
||||
stockIcon = 'gtk-dialog-info';
|
||||
break;
|
||||
case Urgency.CRITICAL:
|
||||
stockIcon = 'gtk-dialog-error';
|
||||
break;
|
||||
}
|
||||
return new Gio.ThemedIcon({ name: stockIcon });
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
_fallbackIconForNotificationData: function(hints) {
|
||||
let stockIcon;
|
||||
switch (hints.urgency) {
|
||||
case Urgency.LOW:
|
||||
case Urgency.NORMAL:
|
||||
stockIcon = 'gtk-dialog-info';
|
||||
break;
|
||||
case Urgency.CRITICAL:
|
||||
stockIcon = 'gtk-dialog-error';
|
||||
break;
|
||||
}
|
||||
return new Gio.ThemedIcon({ name: stockIcon });
|
||||
},
|
||||
|
||||
_iconForNotificationData: function(icon) {
|
||||
if (icon) {
|
||||
if (icon.substr(0, 7) == 'file://')
|
||||
return new Gio.FileIcon({ file: Gio.File.new_for_uri(icon) });
|
||||
else if (icon[0] == '/')
|
||||
return new Gio.FileIcon({ file: Gio.File.new_for_path(icon) });
|
||||
else
|
||||
return new Gio.ThemedIcon({ name: icon });
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
_lookupSource: function(title, pid, trayIcon) {
|
||||
@ -213,7 +333,7 @@ const NotificationDaemon = new Lang.Class({
|
||||
}
|
||||
}
|
||||
|
||||
let source = new Source(title, pid, sender, trayIcon);
|
||||
let source = new Source(title, pid, sender, trayIcon, ndata ? ndata.hints['desktop-entry'] : null);
|
||||
source.setTransient(isForTransientNotification);
|
||||
|
||||
if (!isForTransientNotification) {
|
||||
@ -355,12 +475,8 @@ const NotificationDaemon = new Lang.Class({
|
||||
[ndata.id, ndata.icon, ndata.summary, ndata.body,
|
||||
ndata.actions, ndata.hints, ndata.notification];
|
||||
|
||||
let gicon = this._iconForNotificationData(icon, hints);
|
||||
|
||||
if (notification == null) {
|
||||
notification = new MessageTray.Notification(source, summary, body,
|
||||
{ gicon: gicon,
|
||||
bannerMarkup: true });
|
||||
notification = new MessageTray.Notification(source);
|
||||
ndata.notification = notification;
|
||||
notification.connect('destroy', Lang.bind(this,
|
||||
function(n, reason) {
|
||||
@ -383,29 +499,38 @@ const NotificationDaemon = new Lang.Class({
|
||||
function(n, actionId) {
|
||||
this._emitActionInvoked(ndata.id, actionId);
|
||||
}));
|
||||
} else {
|
||||
notification.update(summary, body, { gicon: gicon,
|
||||
bannerMarkup: true,
|
||||
clear: true });
|
||||
}
|
||||
|
||||
// We only display a large image if an icon is also specified.
|
||||
if (icon && (hints['image-data'] || hints['image-path'])) {
|
||||
let image = null;
|
||||
if (hints['image-data']) {
|
||||
let [width, height, rowStride, hasAlpha,
|
||||
bitsPerSample, nChannels, data] = hints['image-data'];
|
||||
image = St.TextureCache.get_default().load_from_raw(data, hasAlpha,
|
||||
width, height, rowStride, notification.IMAGE_SIZE);
|
||||
} else if (hints['image-path']) {
|
||||
image = St.TextureCache.get_default().load_uri_async(GLib.filename_to_uri(hints['image-path'], null),
|
||||
notification.IMAGE_SIZE,
|
||||
notification.IMAGE_SIZE);
|
||||
}
|
||||
notification.setImage(image);
|
||||
} else {
|
||||
notification.unsetImage();
|
||||
}
|
||||
// Mark music notifications so they can be shown in the screen shield
|
||||
notification.isMusic = (ndata.hints['category'] == 'x-gnome.music');
|
||||
|
||||
let gicon = this._iconForNotificationData(icon, hints);
|
||||
let gimage = this._imageForNotificationData(hints);
|
||||
|
||||
let image = null;
|
||||
|
||||
// If an icon is not specified, we use 'image-data' or 'image-path' hint for an icon
|
||||
// and don't show a large image. There are currently many applications that use
|
||||
// notify_notification_set_icon_from_pixbuf() from libnotify, which in turn sets
|
||||
// the 'image-data' hint. These applications don't typically pass in 'app_icon'
|
||||
// argument to Notify() and actually expect the pixbuf to be shown as an icon.
|
||||
// So the logic here does the right thing for this case. If both an icon and either
|
||||
// one of 'image-data' or 'image-path' are specified, we show both an icon and
|
||||
// a large image.
|
||||
if (gicon && gimage)
|
||||
image = new St.Icon({ gicon: gimage,
|
||||
icon_size: notification.IMAGE_SIZE });
|
||||
else if (!gicon && gimage)
|
||||
gicon = gimage;
|
||||
else if (!gicon)
|
||||
gicon = this._fallbackIconForNotificationData(hints);
|
||||
|
||||
notification.update(summary, body, { gicon: gicon,
|
||||
bannerMarkup: true,
|
||||
clear: true,
|
||||
soundFile: hints['sound-file'],
|
||||
soundName: hints['sound-name'] });
|
||||
notification.setImage(image);
|
||||
|
||||
if (actions.length) {
|
||||
notification.setUseActionIcons(hints['action-icons'] == true);
|
||||
@ -435,7 +560,7 @@ const NotificationDaemon = new Lang.Class({
|
||||
// of the 'transient' hint with hints['transient'] rather than hints.transient
|
||||
notification.setTransient(hints['transient'] == true);
|
||||
|
||||
let sourceGIcon = source.useNotificationIcon ? this._iconForNotificationData(icon, hints) : null;
|
||||
let sourceGIcon = source.useNotificationIcon ? gicon : null;
|
||||
source.processNotification(notification, sourceGIcon);
|
||||
},
|
||||
|
||||
@ -459,7 +584,7 @@ const NotificationDaemon = new Lang.Class({
|
||||
// 'icon-multi',
|
||||
'icon-static',
|
||||
'persistence',
|
||||
// 'sound',
|
||||
'sound',
|
||||
];
|
||||
},
|
||||
|
||||
@ -515,12 +640,22 @@ const Source = new Lang.Class({
|
||||
Name: 'NotificationDaemonSource',
|
||||
Extends: MessageTray.Source,
|
||||
|
||||
_init: function(title, pid, sender, trayIcon) {
|
||||
_init: function(title, pid, sender, trayIcon, appId) {
|
||||
// Need to set the app before chaining up, so
|
||||
// methods called from the parent constructor can find it
|
||||
this.trayIcon = trayIcon;
|
||||
this.pid = pid;
|
||||
this.app = this._getApp(appId);
|
||||
|
||||
this.parent(title);
|
||||
|
||||
this.initialTitle = title;
|
||||
|
||||
this.pid = pid;
|
||||
if (this.app)
|
||||
this.title = this.app.get_name();
|
||||
else
|
||||
this.useNotificationIcon = true;
|
||||
|
||||
if (sender)
|
||||
this._nameWatcherId = Gio.DBus.session.watch_name(sender,
|
||||
Gio.BusNameWatcherFlags.NONE,
|
||||
@ -529,16 +664,19 @@ const Source = new Lang.Class({
|
||||
else
|
||||
this._nameWatcherId = 0;
|
||||
|
||||
this._setApp();
|
||||
if (this.app)
|
||||
this.title = this.app.get_name();
|
||||
else
|
||||
this.useNotificationIcon = true;
|
||||
|
||||
this.trayIcon = trayIcon;
|
||||
if (this.trayIcon) {
|
||||
this._setSummaryIcon(this.trayIcon);
|
||||
this.useNotificationIcon = false;
|
||||
// Try again finding the app, using the WM_CLASS from the tray icon
|
||||
this._setSummaryIcon(this.trayIcon);
|
||||
this.useNotificationIcon = false;
|
||||
}
|
||||
},
|
||||
|
||||
_createPolicy: function() {
|
||||
if (this.app) {
|
||||
let id = this.app.get_id().replace(/\.desktop$/,'');
|
||||
return new NotificationApplicationPolicy(id);
|
||||
} else {
|
||||
return new NotificationGenericPolicy();
|
||||
}
|
||||
},
|
||||
|
||||
@ -565,24 +703,22 @@ const Source = new Lang.Class({
|
||||
this.notify(notification);
|
||||
},
|
||||
|
||||
handleSummaryClick: function() {
|
||||
handleSummaryClick: function(button) {
|
||||
if (!this.trayIcon)
|
||||
return false;
|
||||
|
||||
let event = Clutter.get_current_event();
|
||||
if (event.type() != Clutter.EventType.BUTTON_RELEASE)
|
||||
return false;
|
||||
|
||||
// Left clicks are passed through only where there aren't unacknowledged
|
||||
// notifications, so it possible to open them in summary mode; right
|
||||
// clicks are always forwarded, as the right click menu is not useful for
|
||||
// tray icons
|
||||
if (event.get_button() == 1 &&
|
||||
if (button == 1 &&
|
||||
this.notifications.length > 0)
|
||||
return false;
|
||||
|
||||
let id = global.connect('notify::stage-input-mode', Lang.bind(this, function () {
|
||||
global.disconnect(id);
|
||||
let id = global.stage.connect('deactivate', Lang.bind(this, function () {
|
||||
global.stage.disconnect(id);
|
||||
this.trayIcon.click(event);
|
||||
}));
|
||||
|
||||
@ -590,7 +726,7 @@ const Source = new Lang.Class({
|
||||
return true;
|
||||
},
|
||||
|
||||
_getApp: function() {
|
||||
_getApp: function(appId) {
|
||||
let app;
|
||||
|
||||
app = Shell.WindowTracker.get_default().get_app_from_pid(this.pid);
|
||||
@ -598,7 +734,17 @@ const Source = new Lang.Class({
|
||||
return app;
|
||||
|
||||
if (this.trayIcon) {
|
||||
app = Shell.AppSystem.get_default().lookup_wmclass(this.trayIcon.wmclass);
|
||||
app = Shell.AppSystem.get_default().lookup_startup_wmclass(this.trayIcon.wm_class);
|
||||
if (app != null)
|
||||
return app;
|
||||
|
||||
app = Shell.AppSystem.get_default().lookup_desktop_wmclass(this.trayIcon.wm_class);
|
||||
if (app != null)
|
||||
return app;
|
||||
}
|
||||
|
||||
if (appId) {
|
||||
app = Shell.AppSystem.get_default().lookup_app(appId + '.desktop');
|
||||
if (app != null)
|
||||
return app;
|
||||
}
|
||||
@ -606,11 +752,11 @@ const Source = new Lang.Class({
|
||||
return null;
|
||||
},
|
||||
|
||||
_setApp: function() {
|
||||
_setApp: function(appId) {
|
||||
if (this.app)
|
||||
return;
|
||||
|
||||
this.app = this._getApp();
|
||||
this.app = this._getApp(appId);
|
||||
if (!this.app)
|
||||
return;
|
||||
|
||||
|
210
js/ui/osdWindow.js
Normal file
210
js/ui/osdWindow.js
Normal file
@ -0,0 +1,210 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Lang = imports.lang;
|
||||
const Layout = imports.ui.layout;
|
||||
const Main = imports.ui.main;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Tweener = imports.ui.tweener;
|
||||
const Meta = imports.gi.Meta;
|
||||
|
||||
const HIDE_TIMEOUT = 1500;
|
||||
const FADE_TIME = 0.1;
|
||||
const LEVEL_ANIMATION_TIME = 0.1;
|
||||
|
||||
const LevelBar = new Lang.Class({
|
||||
Name: 'LevelBar',
|
||||
|
||||
_init: function() {
|
||||
this._level = 0;
|
||||
|
||||
this.actor = new St.Bin({ style_class: 'level',
|
||||
x_fill: true, y_fill: true });
|
||||
this._bar = new St.DrawingArea();
|
||||
this._bar.connect('repaint', Lang.bind(this, this._repaint));
|
||||
|
||||
this.actor.set_child(this._bar);
|
||||
},
|
||||
|
||||
get level() {
|
||||
return this._level;
|
||||
},
|
||||
|
||||
set level(value) {
|
||||
let newValue = Math.max(0, Math.min(value, 100));
|
||||
if (newValue == this._level)
|
||||
return;
|
||||
this._level = newValue;
|
||||
this._bar.queue_repaint();
|
||||
},
|
||||
|
||||
_repaint: function() {
|
||||
let cr = this._bar.get_context();
|
||||
|
||||
let node = this.actor.get_theme_node();
|
||||
let radius = node.get_border_radius(0); // assume same radius for all corners
|
||||
Clutter.cairo_set_source_color(cr, node.get_foreground_color());
|
||||
|
||||
let [w, h] = this._bar.get_surface_size();
|
||||
w *= (this._level / 100.);
|
||||
|
||||
if (w == 0)
|
||||
return;
|
||||
|
||||
cr.moveTo(radius, 0);
|
||||
if (w >= radius)
|
||||
cr.arc(w - radius, radius, radius, 1.5 * Math.PI, 2. * Math.PI);
|
||||
else
|
||||
cr.lineTo(w, 0);
|
||||
if (w >= radius)
|
||||
cr.arc(w - radius, h - radius, radius, 0, 0.5 * Math.PI);
|
||||
else
|
||||
cr.lineTo(w, h);
|
||||
cr.arc(radius, h - radius, radius, 0.5 * Math.PI, Math.PI);
|
||||
cr.arc(radius, radius, radius, Math.PI, 1.5 * Math.PI);
|
||||
cr.fill();
|
||||
}
|
||||
});
|
||||
|
||||
const OsdWindow = new Lang.Class({
|
||||
Name: 'OsdWindow',
|
||||
|
||||
_init: function() {
|
||||
this._popupSize = 0;
|
||||
this.actor = new St.Widget({ x_expand: true,
|
||||
y_expand: true,
|
||||
x_align: Clutter.ActorAlign.CENTER,
|
||||
y_align: Clutter.ActorAlign.CENTER });
|
||||
this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true }));
|
||||
this._box = new St.BoxLayout({ style_class: 'osd-window',
|
||||
vertical: true });
|
||||
this.actor.add_actor(this._box);
|
||||
|
||||
this._box.connect('style-changed', Lang.bind(this, this._onStyleChanged));
|
||||
this._box.connect('notify::height', Lang.bind(this,
|
||||
function() {
|
||||
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this,
|
||||
function() {
|
||||
this._box.width = this._box.height;
|
||||
}));
|
||||
}));
|
||||
|
||||
this._icon = new St.Icon();
|
||||
this._box.add(this._icon, { expand: true });
|
||||
|
||||
this._label = new St.Label();
|
||||
this._box.add(this._label);
|
||||
|
||||
this._level = new LevelBar();
|
||||
this._box.add(this._level.actor);
|
||||
|
||||
this._hideTimeoutId = 0;
|
||||
this._reset();
|
||||
|
||||
Main.layoutManager.connect('monitors-changed',
|
||||
Lang.bind(this, this._monitorsChanged));
|
||||
this._monitorsChanged();
|
||||
|
||||
Main.uiGroup.add_child(this.actor);
|
||||
},
|
||||
|
||||
setIcon: function(icon) {
|
||||
this._icon.gicon = icon;
|
||||
},
|
||||
|
||||
setLabel: function(label) {
|
||||
this._label.visible = (label != undefined);
|
||||
if (label)
|
||||
this._label.text = label;
|
||||
},
|
||||
|
||||
setLevel: function(level) {
|
||||
this._level.actor.visible = (level != undefined);
|
||||
if (this.actor.visible)
|
||||
Tweener.addTween(this._level,
|
||||
{ level: level,
|
||||
time: LEVEL_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad' });
|
||||
else
|
||||
this._level.level = level;
|
||||
},
|
||||
|
||||
show: function() {
|
||||
if (!this._icon.gicon)
|
||||
return;
|
||||
|
||||
if (!this.actor.visible) {
|
||||
Meta.disable_unredirect_for_screen(global.screen);
|
||||
this.actor.show();
|
||||
this.actor.opacity = 0;
|
||||
this.actor.get_parent().set_child_above_sibling(this.actor, null);
|
||||
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 255,
|
||||
time: FADE_TIME,
|
||||
transition: 'easeOutQuad' });
|
||||
}
|
||||
|
||||
if (this._hideTimeoutId)
|
||||
Mainloop.source_remove(this._hideTimeoutId);
|
||||
this._hideTimeoutId = Mainloop.timeout_add(HIDE_TIMEOUT,
|
||||
Lang.bind(this, this._hide));
|
||||
},
|
||||
|
||||
cancel: function() {
|
||||
if (!this._hideTimeoutId)
|
||||
return;
|
||||
|
||||
Mainloop.source_remove(this._hideTimeoutId);
|
||||
this._hide();
|
||||
},
|
||||
|
||||
_hide: function() {
|
||||
this._hideTimeoutId = 0;
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 0,
|
||||
time: FADE_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this, function() {
|
||||
this._reset();
|
||||
Meta.enable_unredirect_for_screen(global.screen);
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
_reset: function() {
|
||||
this.actor.hide();
|
||||
this.setLabel(null);
|
||||
this.setLevel(null);
|
||||
},
|
||||
|
||||
_monitorsChanged: function() {
|
||||
/* assume 110x110 on a 640x480 display and scale from there */
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
let scalew = monitor.width / 640.0;
|
||||
let scaleh = monitor.height / 480.0;
|
||||
let scale = Math.min(scalew, scaleh);
|
||||
this._popupSize = 110 * Math.max(1, scale);
|
||||
|
||||
this._box.translation_y = monitor.height / 4;
|
||||
this._icon.icon_size = this._popupSize / 2;
|
||||
this._box.style_changed();
|
||||
},
|
||||
|
||||
_onStyleChanged: function() {
|
||||
let themeNode = this._box.get_theme_node();
|
||||
let horizontalPadding = themeNode.get_horizontal_padding();
|
||||
let verticalPadding = themeNode.get_vertical_padding();
|
||||
let topBorder = themeNode.get_border_width(St.Side.TOP);
|
||||
let bottomBorder = themeNode.get_border_width(St.Side.BOTTOM);
|
||||
let leftBorder = themeNode.get_border_width(St.Side.LEFT);
|
||||
let rightBorder = themeNode.get_border_width(St.Side.RIGHT);
|
||||
|
||||
let minWidth = this._popupSize - verticalPadding - leftBorder - rightBorder;
|
||||
let minHeight = this._popupSize - horizontalPadding - topBorder - bottomBorder;
|
||||
|
||||
this._box.style = 'min-height: %dpx;'.format(Math.max(minWidth, minHeight));
|
||||
}
|
||||
});
|
@ -10,53 +10,28 @@ const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Gdk = imports.gi.Gdk;
|
||||
|
||||
const Dash = imports.ui.dash;
|
||||
const Background = imports.ui.background;
|
||||
const DND = imports.ui.dnd;
|
||||
const LayoutManager = imports.ui.layout;
|
||||
const Main = imports.ui.main;
|
||||
const MessageTray = imports.ui.messageTray;
|
||||
const OverviewControls = imports.ui.overviewControls;
|
||||
const Panel = imports.ui.panel;
|
||||
const Params = imports.misc.params;
|
||||
const Tweener = imports.ui.tweener;
|
||||
const ViewSelector = imports.ui.viewSelector;
|
||||
const WorkspaceThumbnail = imports.ui.workspaceThumbnail;
|
||||
|
||||
// Time for initial animation going into Overview mode
|
||||
const ANIMATION_TIME = 0.25;
|
||||
|
||||
// XXX -- grab this automatically. Hard to do.
|
||||
const DASH_MAX_WIDTH = 96;
|
||||
// Must be less than ANIMATION_TIME, since we switch to
|
||||
// or from the overview completely after ANIMATION_TIME,
|
||||
// and don't want the shading animation to get cut off
|
||||
const SHADE_ANIMATION_TIME = .20;
|
||||
|
||||
const DND_WINDOW_SWITCH_TIMEOUT = 1250;
|
||||
const DND_WINDOW_SWITCH_TIMEOUT = 750;
|
||||
|
||||
const GLSL_DIM_EFFECT_DECLARATIONS = '';
|
||||
const GLSL_DIM_EFFECT_CODE = '\
|
||||
vec2 dist = cogl_tex_coord_in[0].xy - vec2(0.5, 0.5); \
|
||||
float elipse_radius = 0.5; \
|
||||
/* from https://bugzilla.gnome.org/show_bug.cgi?id=669798: \
|
||||
the alpha on the gradient goes from 165 at its darkest to 98 at its most transparent. */ \
|
||||
float y = 165.0 / 255.0; \
|
||||
float x = 98.0 / 255.0; \
|
||||
/* interpolate darkening value, based on distance from screen center */ \
|
||||
float val = min(length(dist), elipse_radius); \
|
||||
float a = mix(x, y, val / elipse_radius); \
|
||||
/* dim_factor varies from [1.0 -> 0.5] when overview is showing \
|
||||
We use it to smooth value, then we clamp it to valid color interval */ \
|
||||
a = clamp(a - cogl_color_in.r + 0.5, 0.0, 1.0); \
|
||||
/* We\'re blending between: color and black color (obviously omitted in the equation) */ \
|
||||
cogl_color_out.xyz = cogl_color_out.xyz * (1.0 - a); \
|
||||
cogl_color_out.a = 1.0;';
|
||||
|
||||
const SwipeScrollDirection = {
|
||||
NONE: 0,
|
||||
HORIZONTAL: 1,
|
||||
VERTICAL: 2
|
||||
};
|
||||
|
||||
const SwipeScrollResult = {
|
||||
CANCEL: 0,
|
||||
SWIPE: 1,
|
||||
CLICK: 2
|
||||
};
|
||||
const OVERVIEW_ACTIVATION_TIMEOUT = 0.5;
|
||||
|
||||
const ShellInfo = new Lang.Class({
|
||||
Name: 'ShellInfo',
|
||||
@ -117,6 +92,7 @@ const Overview = new Lang.Class({
|
||||
|
||||
_init: function() {
|
||||
this._overviewCreated = false;
|
||||
this._initCalled = false;
|
||||
|
||||
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
|
||||
this._sessionUpdated();
|
||||
@ -131,63 +107,52 @@ const Overview = new Lang.Class({
|
||||
|
||||
this._overviewCreated = true;
|
||||
|
||||
// The main BackgroundActor is inside global.window_group which is
|
||||
// The main Background actors are inside global.window_group which are
|
||||
// hidden when displaying the overview, so we create a new
|
||||
// one. Instances of this class share a single CoglTexture behind the
|
||||
// scenes which allows us to show the background with different
|
||||
// rendering options without duplicating the texture data.
|
||||
this._background = Meta.BackgroundActor.new_for_screen(global.screen);
|
||||
this._background.add_glsl_snippet(Meta.SnippetHook.FRAGMENT,
|
||||
GLSL_DIM_EFFECT_DECLARATIONS,
|
||||
GLSL_DIM_EFFECT_CODE,
|
||||
false);
|
||||
this._background.hide();
|
||||
global.overlay_group.add_actor(this._background);
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
|
||||
this._desktopFade = new St.Bin();
|
||||
global.overlay_group.add_actor(this._desktopFade);
|
||||
Main.layoutManager.overviewGroup.add_child(this._desktopFade);
|
||||
|
||||
this._spacing = 0;
|
||||
let layout = new Clutter.BinLayout();
|
||||
this._stack = new Clutter.Actor({ layout_manager: layout });
|
||||
this._stack.add_constraint(new LayoutManager.MonitorConstraint({ primary: true }));
|
||||
|
||||
/* Translators: This is the main view to select
|
||||
activities. See also note for "Activities" string. */
|
||||
this._group = new St.Widget({ name: 'overview',
|
||||
accessible_name: _("Overview"),
|
||||
reactive: true });
|
||||
this._group._delegate = this;
|
||||
this._group.connect('style-changed',
|
||||
Lang.bind(this, function() {
|
||||
let node = this._group.get_theme_node();
|
||||
let spacing = node.get_length('spacing');
|
||||
if (spacing != this._spacing) {
|
||||
this._spacing = spacing;
|
||||
this._relayout();
|
||||
}
|
||||
}));
|
||||
this._overview = new St.BoxLayout({ name: 'overview',
|
||||
accessible_name: _("Overview"),
|
||||
reactive: true,
|
||||
vertical: true,
|
||||
x_expand: true,
|
||||
y_expand: true });
|
||||
this._overview._delegate = this;
|
||||
|
||||
this._scrollDirection = SwipeScrollDirection.NONE;
|
||||
this._scrollAdjustment = null;
|
||||
this._capturedEventId = 0;
|
||||
this._buttonPressId = 0;
|
||||
this._backgroundGroup = new Meta.BackgroundGroup();
|
||||
Main.layoutManager.overviewGroup.add_child(this._backgroundGroup);
|
||||
this._bgManagers = [];
|
||||
|
||||
this._activationTime = 0;
|
||||
|
||||
this.visible = false; // animating to overview, in overview, animating out
|
||||
this._shown = false; // show() and not hide()
|
||||
this._shownTemporarily = false; // showTemporarily() and not hideTemporarily()
|
||||
this._modal = false; // have a modal grab
|
||||
this.animationInProgress = false;
|
||||
this._hideInProgress = false;
|
||||
this.visibleTarget = false;
|
||||
|
||||
// During transitions, we raise this to the top to avoid having the overview
|
||||
// area be reactive; it causes too many issues such as double clicks on
|
||||
// Dash elements, or mouseover handlers in the workspaces.
|
||||
this._coverPane = new Clutter.Rectangle({ opacity: 0,
|
||||
reactive: true });
|
||||
this._group.add_actor(this._coverPane);
|
||||
this._coverPane = new Clutter.Actor({ opacity: 0,
|
||||
reactive: true });
|
||||
Main.layoutManager.overviewGroup.add_child(this._coverPane);
|
||||
this._coverPane.connect('event', Lang.bind(this, function (actor, event) { return true; }));
|
||||
|
||||
|
||||
this._group.hide();
|
||||
global.overlay_group.add_actor(this._group);
|
||||
this._stack.add_actor(this._overview);
|
||||
Main.layoutManager.overviewGroup.add_child(this._stack);
|
||||
|
||||
this._coverPane.hide();
|
||||
|
||||
@ -199,11 +164,66 @@ const Overview = new Lang.Class({
|
||||
Main.xdndHandler.connect('drag-begin', Lang.bind(this, this._onDragBegin));
|
||||
Main.xdndHandler.connect('drag-end', Lang.bind(this, this._onDragEnd));
|
||||
|
||||
global.screen.connect('restacked', Lang.bind(this, this._onRestacked));
|
||||
|
||||
this._windowSwitchTimeoutId = 0;
|
||||
this._windowSwitchTimestamp = 0;
|
||||
this._lastActiveWorkspaceIndex = -1;
|
||||
this._lastHoveredWindow = null;
|
||||
this._needsFakePointerEvent = false;
|
||||
|
||||
if (this._initCalled)
|
||||
this.init();
|
||||
},
|
||||
|
||||
_updateBackgrounds: function() {
|
||||
for (let i = 0; i < this._bgManagers.length; i++)
|
||||
this._bgManagers[i].destroy();
|
||||
|
||||
this._bgManagers = [];
|
||||
|
||||
for (let i = 0; i < Main.layoutManager.monitors.length; i++) {
|
||||
let bgManager = new Background.BackgroundManager({ container: this._backgroundGroup,
|
||||
monitorIndex: i,
|
||||
effects: Meta.BackgroundEffects.VIGNETTE });
|
||||
this._bgManagers.push(bgManager);
|
||||
}
|
||||
},
|
||||
|
||||
_unshadeBackgrounds: function() {
|
||||
let backgrounds = this._backgroundGroup.get_children();
|
||||
for (let i = 0; i < backgrounds.length; i++) {
|
||||
let background = backgrounds[i]._delegate;
|
||||
|
||||
Tweener.addTween(background,
|
||||
{ brightness: 1.0,
|
||||
time: SHADE_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
Tweener.addTween(background,
|
||||
{ vignetteSharpness: 0.0,
|
||||
time: SHADE_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_shadeBackgrounds: function() {
|
||||
let backgrounds = this._backgroundGroup.get_children();
|
||||
for (let i = 0; i < backgrounds.length; i++) {
|
||||
let background = backgrounds[i]._delegate;
|
||||
|
||||
Tweener.addTween(background,
|
||||
{ brightness: 0.8,
|
||||
time: SHADE_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
Tweener.addTween(background,
|
||||
{ vignetteSharpness: 0.7,
|
||||
time: SHADE_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_sessionUpdated: function() {
|
||||
@ -216,49 +236,60 @@ const Overview = new Lang.Class({
|
||||
// signal handlers and so forth. So we create them after
|
||||
// construction in this init() method.
|
||||
init: function() {
|
||||
this._initCalled = true;
|
||||
|
||||
if (this.isDummy)
|
||||
return;
|
||||
|
||||
this._shellInfo = new ShellInfo();
|
||||
|
||||
// Add a clone of the panel to the overview so spacing and such is
|
||||
// automatic
|
||||
this._panelGhost = new St.Bin({ child: new Clutter.Clone({ source: Main.panel.actor }),
|
||||
reactive: false,
|
||||
opacity: 0 });
|
||||
this._overview.add_actor(this._panelGhost);
|
||||
|
||||
this._searchEntry = new St.Entry({ name: 'searchEntry',
|
||||
/* Translators: this is the text displayed
|
||||
in the search entry when no search is
|
||||
active; it should not exceed ~30
|
||||
characters. */
|
||||
hint_text: _("Type to search..."),
|
||||
hint_text: _("Type to search…"),
|
||||
track_hover: true,
|
||||
can_focus: true });
|
||||
this._group.add_actor(this._searchEntry);
|
||||
this._searchEntryBin = new St.Bin({ child: this._searchEntry,
|
||||
x_align: St.Align.MIDDLE });
|
||||
this._overview.add_actor(this._searchEntryBin);
|
||||
|
||||
this._dash = new Dash.Dash();
|
||||
this._viewSelector = new ViewSelector.ViewSelector(this._searchEntry,
|
||||
this._dash.showAppsButton);
|
||||
this._group.add_actor(this._viewSelector.actor);
|
||||
this._group.add_actor(this._dash.actor);
|
||||
// Create controls
|
||||
this._controls = new OverviewControls.ControlsManager(this._searchEntry);
|
||||
this._dash = this._controls.dash;
|
||||
this.viewSelector = this._controls.viewSelector;
|
||||
|
||||
// Add our same-line elements after the search entry
|
||||
this._overview.add(this._controls.actor, { y_fill: true, expand: true });
|
||||
this._controls.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
|
||||
|
||||
this._stack.add_actor(this._controls.indicatorActor);
|
||||
|
||||
// TODO - recalculate everything when desktop size changes
|
||||
this._dash.actor.add_constraint(this._viewSelector.constrainHeight);
|
||||
this.dashIconSize = this._dash.iconSize;
|
||||
this._dash.connect('icon-size-changed',
|
||||
Lang.bind(this, function() {
|
||||
this.dashIconSize = this._dash.iconSize;
|
||||
}));
|
||||
|
||||
// Translators: this is the name of the dock/favorites area on
|
||||
// the left of the overview
|
||||
Main.ctrlAltTabManager.addGroup(this._dash.actor, _("Dash"), 'user-bookmarks-symbolic');
|
||||
|
||||
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._relayout));
|
||||
this._relayout();
|
||||
},
|
||||
|
||||
addSearchProvider: function(provider) {
|
||||
this._viewSelector.addSearchProvider(provider);
|
||||
this.viewSelector.addSearchProvider(provider);
|
||||
},
|
||||
|
||||
removeSearchProvider: function(provider) {
|
||||
this._viewSelector.removeSearchProvider(provider);
|
||||
this.viewSelector.removeSearchProvider(provider);
|
||||
},
|
||||
|
||||
//
|
||||
@ -274,18 +305,25 @@ const Overview = new Lang.Class({
|
||||
},
|
||||
|
||||
_onDragBegin: function() {
|
||||
this._inXdndDrag = true;
|
||||
|
||||
DND.addDragMonitor(this._dragMonitor);
|
||||
// Remember the workspace we started from
|
||||
this._lastActiveWorkspaceIndex = global.screen.get_active_workspace_index();
|
||||
},
|
||||
|
||||
_onDragEnd: function(time) {
|
||||
if (this.animationInProgress)
|
||||
return;
|
||||
|
||||
this._inXdndDrag = false;
|
||||
|
||||
// In case the drag was canceled while in the overview
|
||||
// we have to go back to where we started and hide
|
||||
// the overview
|
||||
if (this._shownTemporarily) {
|
||||
if (this._shown) {
|
||||
global.screen.get_workspace_by_index(this._lastActiveWorkspaceIndex).activate(time);
|
||||
this.hideTemporarily();
|
||||
this.hide();
|
||||
}
|
||||
this._resetWindowSwitchTimeout();
|
||||
this._lastHoveredWindow = null;
|
||||
@ -333,7 +371,7 @@ const Overview = new Lang.Class({
|
||||
this._needsFakePointerEvent = true;
|
||||
Main.activateWindow(dragEvent.targetActor._delegate.metaWindow,
|
||||
this._windowSwitchTimestamp);
|
||||
this.hideTemporarily();
|
||||
this.hide();
|
||||
this._lastHoveredWindow = null;
|
||||
}));
|
||||
}
|
||||
@ -341,159 +379,15 @@ const Overview = new Lang.Class({
|
||||
return DND.DragMotionResult.CONTINUE;
|
||||
},
|
||||
|
||||
setScrollAdjustment: function(adjustment, direction) {
|
||||
_onScrollEvent: function(actor, event) {
|
||||
this.emit('scroll-event', event);
|
||||
},
|
||||
|
||||
addAction: function(action) {
|
||||
if (this.isDummy)
|
||||
return;
|
||||
|
||||
this._scrollAdjustment = adjustment;
|
||||
if (this._scrollAdjustment == null)
|
||||
this._scrollDirection = SwipeScrollDirection.NONE;
|
||||
else
|
||||
this._scrollDirection = direction;
|
||||
},
|
||||
|
||||
_onButtonPress: function(actor, event) {
|
||||
if (this._scrollDirection == SwipeScrollDirection.NONE
|
||||
|| event.get_button() != 1)
|
||||
return;
|
||||
|
||||
let [stageX, stageY] = event.get_coords();
|
||||
this._dragStartX = this._dragX = stageX;
|
||||
this._dragStartY = this._dragY = stageY;
|
||||
this._dragStartValue = this._scrollAdjustment.value;
|
||||
this._lastMotionTime = -1; // used to track "stopping" while swipe-scrolling
|
||||
this._capturedEventId = global.stage.connect('captured-event',
|
||||
Lang.bind(this, this._onCapturedEvent));
|
||||
this.emit('swipe-scroll-begin');
|
||||
},
|
||||
|
||||
_onCapturedEvent: function(actor, event) {
|
||||
let stageX, stageY;
|
||||
let threshold = Gtk.Settings.get_default().gtk_dnd_drag_threshold;
|
||||
|
||||
switch(event.type()) {
|
||||
case Clutter.EventType.BUTTON_RELEASE:
|
||||
[stageX, stageY] = event.get_coords();
|
||||
|
||||
// default to snapping back to the original value
|
||||
let newValue = this._dragStartValue;
|
||||
|
||||
let minValue = this._scrollAdjustment.lower;
|
||||
let maxValue = this._scrollAdjustment.upper - this._scrollAdjustment.page_size;
|
||||
|
||||
let direction;
|
||||
if (this._scrollDirection == SwipeScrollDirection.HORIZONTAL) {
|
||||
direction = stageX > this._dragStartX ? -1 : 1;
|
||||
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
|
||||
direction *= -1;
|
||||
} else {
|
||||
direction = stageY > this._dragStartY ? -1 : 1;
|
||||
}
|
||||
|
||||
// We default to scroll a full page size; both the first
|
||||
// and the last page may be smaller though, so we need to
|
||||
// adjust difference in those cases.
|
||||
let difference = direction * this._scrollAdjustment.page_size;
|
||||
if (this._dragStartValue + difference > maxValue)
|
||||
difference = maxValue - this._dragStartValue;
|
||||
else if (this._dragStartValue + difference < minValue)
|
||||
difference = minValue - this._dragStartValue;
|
||||
|
||||
// If the user has moved more than half the scroll
|
||||
// difference, we want to "settle" to the new value
|
||||
// even if the user stops dragging rather "throws" by
|
||||
// releasing during the drag.
|
||||
let distance = this._dragStartValue - this._scrollAdjustment.value;
|
||||
let noStop = Math.abs(distance / difference) > 0.5;
|
||||
|
||||
// We detect if the user is stopped by comparing the
|
||||
// timestamp of the button release with the timestamp of
|
||||
// the last motion. Experimentally, a difference of 0 or 1
|
||||
// millisecond indicates that the mouse is in motion, a
|
||||
// larger difference indicates that the mouse is stopped.
|
||||
if ((this._lastMotionTime > 0 &&
|
||||
this._lastMotionTime > event.get_time() - 2) ||
|
||||
noStop) {
|
||||
if (this._dragStartValue + difference >= minValue &&
|
||||
this._dragStartValue + difference <= maxValue)
|
||||
newValue += difference;
|
||||
}
|
||||
|
||||
let result;
|
||||
|
||||
// See if the user has moved the mouse enough to trigger
|
||||
// a drag
|
||||
if (Math.abs(stageX - this._dragStartX) < threshold &&
|
||||
Math.abs(stageY - this._dragStartY) < threshold) {
|
||||
// no motion? It's a click!
|
||||
result = SwipeScrollResult.CLICK;
|
||||
this.emit('swipe-scroll-end', result);
|
||||
} else {
|
||||
if (newValue == this._dragStartValue)
|
||||
result = SwipeScrollResult.CANCEL;
|
||||
else
|
||||
result = SwipeScrollResult.SWIPE;
|
||||
|
||||
// The event capture handler is disconnected
|
||||
// while scrolling to the final position, so
|
||||
// to avoid undesired prelights we raise
|
||||
// the cover pane.
|
||||
this._coverPane.raise_top();
|
||||
this._coverPane.show();
|
||||
|
||||
Tweener.addTween(this._scrollAdjustment,
|
||||
{ value: newValue,
|
||||
time: ANIMATION_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onCompleteScope: this,
|
||||
onComplete: function() {
|
||||
this._coverPane.hide();
|
||||
this.emit('swipe-scroll-end',
|
||||
result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
global.stage.disconnect(this._capturedEventId);
|
||||
this._capturedEventId = 0;
|
||||
|
||||
return result != SwipeScrollResult.CLICK;
|
||||
|
||||
case Clutter.EventType.MOTION:
|
||||
[stageX, stageY] = event.get_coords();
|
||||
let dx = this._dragX - stageX;
|
||||
let dy = this._dragY - stageY;
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
|
||||
this._dragX = stageX;
|
||||
this._dragY = stageY;
|
||||
this._lastMotionTime = event.get_time();
|
||||
|
||||
// See if the user has moved the mouse enough to trigger
|
||||
// a drag
|
||||
if (Math.abs(stageX - this._dragStartX) < threshold &&
|
||||
Math.abs(stageY - this._dragStartY) < threshold)
|
||||
return true;
|
||||
|
||||
if (this._scrollDirection == SwipeScrollDirection.HORIZONTAL) {
|
||||
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
|
||||
this._scrollAdjustment.value -= (dx / primary.width) * this._scrollAdjustment.page_size;
|
||||
else
|
||||
this._scrollAdjustment.value += (dx / primary.width) * this._scrollAdjustment.page_size;
|
||||
} else {
|
||||
this._scrollAdjustment.value += (dy / primary.height) * this._scrollAdjustment.page_size;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
// Block enter/leave events to avoid prelights
|
||||
// during swipe-scroll
|
||||
case Clutter.EventType.ENTER:
|
||||
case Clutter.EventType.LEAVE:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
this._overview.add_action(action);
|
||||
},
|
||||
|
||||
_getDesktopClone: function() {
|
||||
@ -518,48 +412,31 @@ const Overview = new Lang.Class({
|
||||
// when it is next shown.
|
||||
this.hide();
|
||||
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
let rtl = (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL);
|
||||
let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
|
||||
|
||||
let contentY = Main.panel.actor.height;
|
||||
let contentHeight = primary.height - contentY - Main.messageTray.actor.height;
|
||||
this._coverPane.set_position(0, workArea.y);
|
||||
this._coverPane.set_size(workArea.width, workArea.height);
|
||||
|
||||
this._group.set_position(primary.x, primary.y);
|
||||
this._group.set_size(primary.width, primary.height);
|
||||
this._updateBackgrounds();
|
||||
},
|
||||
|
||||
this._coverPane.set_position(0, contentY);
|
||||
this._coverPane.set_size(primary.width, contentHeight);
|
||||
_onRestacked: function() {
|
||||
let stack = global.get_window_actors();
|
||||
let stackIndices = {};
|
||||
|
||||
let searchWidth = this._searchEntry.get_width();
|
||||
let searchHeight = this._searchEntry.get_height();
|
||||
let searchX = (primary.width - searchWidth) / 2;
|
||||
let searchY = contentY + this._spacing;
|
||||
|
||||
let dashWidth = DASH_MAX_WIDTH;
|
||||
let dashY = searchY + searchHeight + this._spacing;
|
||||
let dashX;
|
||||
if (rtl) {
|
||||
this._dash.actor.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
|
||||
dashX = primary.width;
|
||||
} else {
|
||||
dashX = 0;
|
||||
for (let i = 0; i < stack.length; i++) {
|
||||
// Use the stable sequence for an integer to use as a hash key
|
||||
stackIndices[stack[i].get_meta_window().get_stable_sequence()] = i;
|
||||
}
|
||||
|
||||
let viewX = rtl ? 0 : dashWidth + this._spacing;
|
||||
let viewY = searchY + searchHeight + this._spacing;
|
||||
let viewWidth = primary.width - dashWidth - this._spacing;
|
||||
let viewHeight = contentHeight - this._spacing - viewY;
|
||||
|
||||
this._searchEntry.set_position(searchX, searchY);
|
||||
this._dash.actor.set_position(dashX, dashY);
|
||||
this._viewSelector.actor.set_position(viewX, viewY);
|
||||
this._viewSelector.actor.set_size(viewWidth, viewHeight);
|
||||
this.emit('windows-restacked', stackIndices);
|
||||
},
|
||||
|
||||
//// Public methods ////
|
||||
|
||||
beginItemDrag: function(source) {
|
||||
this.emit('item-drag-begin');
|
||||
this._inDrag = true;
|
||||
},
|
||||
|
||||
cancelledItemDrag: function(source) {
|
||||
@ -568,10 +445,12 @@ const Overview = new Lang.Class({
|
||||
|
||||
endItemDrag: function(source) {
|
||||
this.emit('item-drag-end');
|
||||
this._inDrag = false;
|
||||
},
|
||||
|
||||
beginWindowDrag: function(source) {
|
||||
this.emit('window-drag-begin');
|
||||
this._inDrag = true;
|
||||
},
|
||||
|
||||
cancelledWindowDrag: function(source) {
|
||||
@ -580,25 +459,29 @@ const Overview = new Lang.Class({
|
||||
|
||||
endWindowDrag: function(source) {
|
||||
this.emit('window-drag-end');
|
||||
this._inDrag = false;
|
||||
},
|
||||
|
||||
// show:
|
||||
//
|
||||
// Animates the overview visible and grabs mouse and keyboard input
|
||||
show : function() {
|
||||
show: function() {
|
||||
if (this.isDummy)
|
||||
return;
|
||||
if (this._shown)
|
||||
return;
|
||||
// Do this manually instead of using _syncInputMode, to handle failure
|
||||
if (!Main.pushModal(this._group, { keybindingMode: Main.KeybindingMode.OVERVIEW }))
|
||||
return;
|
||||
this._modal = true;
|
||||
this._animateVisible();
|
||||
this._shown = true;
|
||||
|
||||
this._buttonPressId = this._group.connect('button-press-event',
|
||||
Lang.bind(this, this._onButtonPress));
|
||||
if (!this._syncGrab())
|
||||
return;
|
||||
|
||||
Main.layoutManager.showOverview();
|
||||
this._animateVisible();
|
||||
},
|
||||
|
||||
focusSearch: function() {
|
||||
this.show();
|
||||
this._searchEntry.grab_key_focus();
|
||||
},
|
||||
|
||||
fadeInDesktop: function() {
|
||||
@ -629,60 +512,27 @@ const Overview = new Lang.Class({
|
||||
|
||||
this.visible = true;
|
||||
this.animationInProgress = true;
|
||||
this.visibleTarget = true;
|
||||
this._activationTime = Date.now() / 1000;
|
||||
|
||||
// All the the actors in the window group are completely obscured,
|
||||
// hiding the group holding them while the Overview is displayed greatly
|
||||
// increases performance of the Overview especially when there are many
|
||||
// windows visible.
|
||||
//
|
||||
// If we switched to displaying the actors in the Overview rather than
|
||||
// clones of them, this would obviously no longer be necessary.
|
||||
//
|
||||
// Disable unredirection while in the overview
|
||||
Meta.disable_unredirect_for_screen(global.screen);
|
||||
global.window_group.hide();
|
||||
this._group.show();
|
||||
this._background.show();
|
||||
this._viewSelector.show();
|
||||
this.viewSelector.show();
|
||||
|
||||
this._group.opacity = 0;
|
||||
Tweener.addTween(this._group,
|
||||
this._stack.opacity = 0;
|
||||
Tweener.addTween(this._stack,
|
||||
{ opacity: 255,
|
||||
transition: 'easeOutQuad',
|
||||
time: ANIMATION_TIME,
|
||||
onComplete: this._showDone,
|
||||
onCompleteScope: this
|
||||
});
|
||||
|
||||
Tweener.addTween(this._background,
|
||||
{ dim_factor: 0.8,
|
||||
time: ANIMATION_TIME,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
this._shadeBackgrounds();
|
||||
|
||||
this._coverPane.raise_top();
|
||||
this._coverPane.show();
|
||||
this.emit('showing');
|
||||
},
|
||||
|
||||
// showTemporarily:
|
||||
//
|
||||
// Animates the overview visible without grabbing mouse and keyboard input;
|
||||
// if show() has already been called, this has no immediate effect, but
|
||||
// will result in the overview not being hidden until hideTemporarily() is
|
||||
// called.
|
||||
showTemporarily: function() {
|
||||
if (this.isDummy)
|
||||
return;
|
||||
|
||||
if (this._shownTemporarily)
|
||||
return;
|
||||
|
||||
this._syncInputMode();
|
||||
this._animateVisible();
|
||||
this._shownTemporarily = true;
|
||||
},
|
||||
|
||||
// hide:
|
||||
//
|
||||
// Reverses the effect of show()
|
||||
@ -693,75 +543,77 @@ const Overview = new Lang.Class({
|
||||
if (!this._shown)
|
||||
return;
|
||||
|
||||
if (!this._shownTemporarily)
|
||||
this._animateNotVisible();
|
||||
let event = Clutter.get_current_event();
|
||||
if (event) {
|
||||
let type = event.type();
|
||||
let button = (type == Clutter.EventType.BUTTON_PRESS ||
|
||||
type == Clutter.EventType.BUTTON_RELEASE);
|
||||
let ctrl = (event.get_state() & Clutter.ModifierType.CONTROL_MASK) != 0;
|
||||
if (button && ctrl)
|
||||
return;
|
||||
}
|
||||
|
||||
this._animateNotVisible();
|
||||
|
||||
this._shown = false;
|
||||
this._syncInputMode();
|
||||
|
||||
if (this._buttonPressId > 0)
|
||||
this._group.disconnect(this._buttonPressId);
|
||||
this._buttonPressId = 0;
|
||||
},
|
||||
|
||||
// hideTemporarily:
|
||||
//
|
||||
// Reverses the effect of showTemporarily()
|
||||
hideTemporarily: function() {
|
||||
if (this.isDummy)
|
||||
return;
|
||||
|
||||
if (!this._shownTemporarily)
|
||||
return;
|
||||
|
||||
if (!this._shown)
|
||||
this._animateNotVisible();
|
||||
|
||||
this._shownTemporarily = false;
|
||||
this._syncInputMode();
|
||||
this._syncGrab();
|
||||
},
|
||||
|
||||
toggle: function() {
|
||||
if (this.isDummy)
|
||||
return;
|
||||
|
||||
if (this._shown)
|
||||
if (this.visible)
|
||||
this.hide();
|
||||
else
|
||||
this.show();
|
||||
},
|
||||
|
||||
// Checks if the Activities button is currently sensitive to
|
||||
// clicks. The first call to this function within the
|
||||
// OVERVIEW_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.
|
||||
shouldToggleByCornerOrButton: function() {
|
||||
if (this.animationInProgress)
|
||||
return false;
|
||||
if (this._inDrag)
|
||||
return false;
|
||||
if (this._activationTime == 0 || Date.now() / 1000 - this._activationTime > OVERVIEW_ACTIVATION_TIMEOUT)
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
|
||||
//// Private methods ////
|
||||
|
||||
_syncInputMode: function() {
|
||||
// We delay input mode changes during animation so that when removing the
|
||||
_syncGrab: function() {
|
||||
// We delay grab changes during animation so that when removing the
|
||||
// overview we don't have a problem with the release of a press/release
|
||||
// going to an application.
|
||||
if (this.animationInProgress)
|
||||
return;
|
||||
return true;
|
||||
|
||||
if (this._shown) {
|
||||
if (!this._modal) {
|
||||
if (Main.pushModal(this._group,
|
||||
{ keybindingMode: Main.KeybindingMode.OVERVIEW }))
|
||||
this._modal = true;
|
||||
else
|
||||
this.hide();
|
||||
let shouldBeModal = !this._inXdndDrag;
|
||||
if (shouldBeModal) {
|
||||
if (!this._modal) {
|
||||
if (Main.pushModal(this._overview,
|
||||
{ keybindingMode: Shell.KeyBindingMode.OVERVIEW })) {
|
||||
this._modal = true;
|
||||
} else {
|
||||
this.hide();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (this._shownTemporarily) {
|
||||
if (this._modal) {
|
||||
Main.popModal(this._group);
|
||||
this._modal = false;
|
||||
}
|
||||
global.stage_input_mode = Shell.StageInputMode.FULLSCREEN;
|
||||
} else {
|
||||
if (this._modal) {
|
||||
Main.popModal(this._group);
|
||||
Main.popModal(this._overview);
|
||||
this._modal = false;
|
||||
}
|
||||
else if (global.stage_input_mode == Shell.StageInputMode.FULLSCREEN)
|
||||
global.stage_input_mode = Shell.StageInputMode.NORMAL;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
_animateNotVisible: function() {
|
||||
@ -769,24 +621,19 @@ const Overview = new Lang.Class({
|
||||
return;
|
||||
|
||||
this.animationInProgress = true;
|
||||
this._hideInProgress = true;
|
||||
this.visibleTarget = false;
|
||||
|
||||
this._viewSelector.zoomFromOverview();
|
||||
this.viewSelector.zoomFromOverview();
|
||||
|
||||
// Make other elements fade out.
|
||||
Tweener.addTween(this._group,
|
||||
Tweener.addTween(this._stack,
|
||||
{ opacity: 0,
|
||||
transition: 'easeOutQuad',
|
||||
time: ANIMATION_TIME,
|
||||
onComplete: this._hideDone,
|
||||
onCompleteScope: this
|
||||
});
|
||||
|
||||
Tweener.addTween(this._background,
|
||||
{ dim_factor: 1.0,
|
||||
time: ANIMATION_TIME,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
this._unshadeBackgrounds();
|
||||
|
||||
this._coverPane.raise_top();
|
||||
this._coverPane.show();
|
||||
@ -800,10 +647,10 @@ const Overview = new Lang.Class({
|
||||
|
||||
this.emit('shown');
|
||||
// Handle any calls to hide* while we were showing
|
||||
if (!this._shown && !this._shownTemporarily)
|
||||
if (!this._shown)
|
||||
this._animateNotVisible();
|
||||
|
||||
this._syncInputMode();
|
||||
this._syncGrab();
|
||||
global.sync_pointer();
|
||||
},
|
||||
|
||||
@ -811,25 +658,21 @@ const Overview = new Lang.Class({
|
||||
// Re-enable unredirection
|
||||
Meta.enable_unredirect_for_screen(global.screen);
|
||||
|
||||
global.window_group.show();
|
||||
|
||||
this._viewSelector.hide();
|
||||
this.viewSelector.hide();
|
||||
this._desktopFade.hide();
|
||||
this._background.hide();
|
||||
this._group.hide();
|
||||
this._coverPane.hide();
|
||||
|
||||
this.visible = false;
|
||||
this.animationInProgress = false;
|
||||
this._hideInProgress = false;
|
||||
|
||||
this._coverPane.hide();
|
||||
|
||||
this.emit('hidden');
|
||||
// Handle any calls to show* while we were hiding
|
||||
if (this._shown || this._shownTemporarily)
|
||||
if (this._shown)
|
||||
this._animateVisible();
|
||||
else
|
||||
Main.layoutManager.hideOverview();
|
||||
|
||||
this._syncInputMode();
|
||||
this._syncGrab();
|
||||
|
||||
// Fake a pointer event if requested
|
||||
if (this._needsFakePointerEvent) {
|
||||
|
632
js/ui/overviewControls.js
Normal file
632
js/ui/overviewControls.js
Normal file
@ -0,0 +1,632 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const GObject = imports.gi.GObject;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Lang = imports.lang;
|
||||
const Meta = imports.gi.Meta;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const Dash = imports.ui.dash;
|
||||
const Main = imports.ui.main;
|
||||
const Params = imports.misc.params;
|
||||
const Tweener = imports.ui.tweener;
|
||||
const ViewSelector = imports.ui.viewSelector;
|
||||
const WorkspaceThumbnail = imports.ui.workspaceThumbnail;
|
||||
|
||||
const SIDE_CONTROLS_ANIMATION_TIME = 0.16;
|
||||
|
||||
function getRtlSlideDirection(direction, actor) {
|
||||
let rtl = (actor.text_direction == Clutter.TextDirection.RTL);
|
||||
if (rtl)
|
||||
direction = (direction == SlideDirection.LEFT) ?
|
||||
SlideDirection.RIGHT : SlideDirection.LEFT;
|
||||
|
||||
return direction;
|
||||
};
|
||||
|
||||
const SlideDirection = {
|
||||
LEFT: 0,
|
||||
RIGHT: 1
|
||||
};
|
||||
|
||||
const SlideLayout = new Lang.Class({
|
||||
Name: 'SlideLayout',
|
||||
Extends: Clutter.FixedLayout,
|
||||
|
||||
_init: function(params) {
|
||||
this._slideX = 1;
|
||||
this._direction = SlideDirection.LEFT;
|
||||
|
||||
this.parent(params);
|
||||
},
|
||||
|
||||
vfunc_get_preferred_width: function(container, forHeight) {
|
||||
let child = container.get_first_child();
|
||||
|
||||
let [minWidth, natWidth] = child.get_preferred_width(forHeight);
|
||||
|
||||
minWidth *= this._slideX;
|
||||
natWidth *= this._slideX;
|
||||
|
||||
return [minWidth, natWidth];
|
||||
},
|
||||
|
||||
vfunc_allocate: function(container, box, flags) {
|
||||
let child = container.get_first_child();
|
||||
|
||||
let [, , natWidth, natHeight] = child.get_preferred_size();
|
||||
let availWidth = Math.round(box.x2 - box.x1);
|
||||
let availHeight = Math.round(box.y2 - box.y1);
|
||||
|
||||
let realDirection = getRtlSlideDirection(this._direction, child);
|
||||
let translationX = (realDirection == SlideDirection.LEFT) ?
|
||||
(availWidth - natWidth) : (natWidth - availWidth);
|
||||
|
||||
let actorBox = new Clutter.ActorBox({ x1: translationX,
|
||||
y1: 0,
|
||||
x2: child.x_expand ? availWidth : natWidth,
|
||||
y2: child.y_expand ? availHeight : natHeight });
|
||||
|
||||
child.allocate(actorBox, flags);
|
||||
},
|
||||
|
||||
set slideX(value) {
|
||||
this._slideX = value;
|
||||
this.layout_changed();
|
||||
},
|
||||
|
||||
get slideX() {
|
||||
return this._slideX;
|
||||
},
|
||||
|
||||
set slideDirection(direction) {
|
||||
this._direction = direction;
|
||||
this.layout_changed();
|
||||
},
|
||||
|
||||
get slideDirection() {
|
||||
return this._direction;
|
||||
}
|
||||
});
|
||||
|
||||
const SlidingControl = new Lang.Class({
|
||||
Name: 'SlidingControl',
|
||||
|
||||
_init: function(params) {
|
||||
params = Params.parse(params, { slideDirection: SlideDirection.LEFT });
|
||||
|
||||
this.visible = true;
|
||||
this.inDrag = false;
|
||||
|
||||
this.layout = new SlideLayout();
|
||||
this.layout.slideDirection = params.slideDirection;
|
||||
this.actor = new St.Widget({ layout_manager: this.layout,
|
||||
style_class: 'overview-controls',
|
||||
clip_to_allocation: true });
|
||||
|
||||
Main.overview.connect('showing', Lang.bind(this, this._onOverviewShowing));
|
||||
|
||||
Main.overview.connect('item-drag-begin', Lang.bind(this, this._onDragBegin));
|
||||
Main.overview.connect('item-drag-end', Lang.bind(this, this._onDragEnd));
|
||||
Main.overview.connect('item-drag-cancelled', Lang.bind(this, this._onDragEnd));
|
||||
|
||||
Main.overview.connect('window-drag-begin', Lang.bind(this, this._onWindowDragBegin));
|
||||
Main.overview.connect('window-drag-cancelled', Lang.bind(this, this._onWindowDragEnd));
|
||||
Main.overview.connect('window-drag-end', Lang.bind(this, this._onWindowDragEnd));
|
||||
},
|
||||
|
||||
getSlide: function() {
|
||||
throw new Error('getSlide() must be overridden');
|
||||
},
|
||||
|
||||
updateSlide: function() {
|
||||
Tweener.addTween(this.layout, { slideX: this.getSlide(),
|
||||
time: SIDE_CONTROLS_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad' });
|
||||
},
|
||||
|
||||
getVisibleWidth: function() {
|
||||
let child = this.actor.get_first_child();
|
||||
let [, , natWidth, ] = child.get_preferred_size();
|
||||
return natWidth;
|
||||
},
|
||||
|
||||
_getTranslation: function() {
|
||||
let child = this.actor.get_first_child();
|
||||
let direction = getRtlSlideDirection(this.layout.slideDirection, child);
|
||||
let visibleWidth = this.getVisibleWidth();
|
||||
|
||||
if (direction == SlideDirection.LEFT)
|
||||
return - visibleWidth;
|
||||
else
|
||||
return visibleWidth;
|
||||
},
|
||||
|
||||
_updateTranslation: function() {
|
||||
let translationStart = 0;
|
||||
let translationEnd = 0;
|
||||
let translation = this._getTranslation();
|
||||
|
||||
if (this.visible) {
|
||||
translationStart = translation;
|
||||
} else {
|
||||
translationEnd = translation;
|
||||
}
|
||||
|
||||
if (this.actor.translation_x == translationEnd)
|
||||
return;
|
||||
|
||||
this.actor.translation_x = translationStart;
|
||||
Tweener.addTween(this.actor, { translation_x: translationEnd,
|
||||
time: SIDE_CONTROLS_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
},
|
||||
|
||||
_onOverviewShowing: function() {
|
||||
// reset any translation and make sure the actor is visible when
|
||||
// entering the overview
|
||||
this.visible = true;
|
||||
this.layout.slideX = this.getSlide();
|
||||
this.actor.translation_x = 0;
|
||||
},
|
||||
|
||||
_onWindowDragBegin: function() {
|
||||
this._onDragBegin();
|
||||
},
|
||||
|
||||
_onWindowDragEnd: function() {
|
||||
this._onDragEnd();
|
||||
},
|
||||
|
||||
_onDragBegin: function() {
|
||||
this.inDrag = true;
|
||||
this.actor.translation_x = 0;
|
||||
this.updateSlide();
|
||||
},
|
||||
|
||||
_onDragEnd: function() {
|
||||
this.inDrag = false;
|
||||
this.updateSlide();
|
||||
},
|
||||
|
||||
fadeIn: function() {
|
||||
Tweener.addTween(this.actor, { opacity: 255,
|
||||
time: SIDE_CONTROLS_ANIMATION_TIME / 2,
|
||||
transition: 'easeInQuad'
|
||||
});
|
||||
},
|
||||
|
||||
fadeHalf: function() {
|
||||
Tweener.addTween(this.actor, { opacity: 128,
|
||||
time: SIDE_CONTROLS_ANIMATION_TIME / 2,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
},
|
||||
|
||||
slideIn: function() {
|
||||
this.visible = true;
|
||||
this._updateTranslation();
|
||||
// we will update slideX and the translation from pageEmpty
|
||||
},
|
||||
|
||||
slideOut: function() {
|
||||
this.visible = false;
|
||||
this._updateTranslation();
|
||||
// we will update slideX from pageEmpty
|
||||
},
|
||||
|
||||
pageEmpty: function() {
|
||||
// When pageEmpty is received, there's no visible view in the
|
||||
// selector; this means we can now safely set the full slide for
|
||||
// the next page, since slideIn or slideOut might have been called,
|
||||
// changing the visiblity
|
||||
this.layout.slideX = this.getSlide();
|
||||
this._updateTranslation();
|
||||
}
|
||||
});
|
||||
|
||||
const ThumbnailsSlider = new Lang.Class({
|
||||
Name: 'ThumbnailsSlider',
|
||||
Extends: SlidingControl,
|
||||
|
||||
_init: function(thumbnailsBox) {
|
||||
this.parent({ slideDirection: SlideDirection.RIGHT });
|
||||
|
||||
this._thumbnailsBox = thumbnailsBox;
|
||||
|
||||
// SlideLayout reads the actor's expand flags to decide
|
||||
// whether to allocate the natural size to its child, or the whole
|
||||
// available allocation
|
||||
this._thumbnailsBox.actor.y_expand = true;
|
||||
|
||||
this.actor.request_mode = Clutter.RequestMode.WIDTH_FOR_HEIGHT;
|
||||
this.actor.reactive = true;
|
||||
this.actor.track_hover = true;
|
||||
this.actor.add_actor(this._thumbnailsBox.actor);
|
||||
|
||||
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this.updateSlide));
|
||||
Main.overview.connect('hiding', Lang.bind(this, this.slideOut));
|
||||
this.actor.connect('notify::hover', Lang.bind(this, this.updateSlide));
|
||||
this._thumbnailsBox.actor.bind_property('visible', this.actor, 'visible', GObject.BindingFlags.SYNC_CREATE);
|
||||
},
|
||||
|
||||
_getAlwaysZoomOut: function() {
|
||||
// Always show the pager when hover, during a drag, or if workspaces are
|
||||
// actually used, e.g. there are windows on more than one
|
||||
let alwaysZoomOut = this.actor.hover || this.inDrag || !Meta.prefs_get_dynamic_workspaces() || global.screen.n_workspaces > 2;
|
||||
|
||||
if (!alwaysZoomOut) {
|
||||
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
|
||||
* the thumbnail area without passing into the next monitor. */
|
||||
for (let i = 0; i < monitors.length; i++) {
|
||||
if (monitors[i].x >= primary.x + primary.width) {
|
||||
alwaysZoomOut = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return alwaysZoomOut;
|
||||
},
|
||||
|
||||
_onOverviewShowing: function() {
|
||||
this.visible = true;
|
||||
this.layout.slideX = this.getSlide();
|
||||
this.actor.translation_x = this._getTranslation();
|
||||
this.slideIn();
|
||||
},
|
||||
|
||||
getNonExpandedWidth: function() {
|
||||
let child = this.actor.get_first_child();
|
||||
return child.get_theme_node().get_length('visible-width');
|
||||
},
|
||||
|
||||
getSlide: function() {
|
||||
if (!this.visible)
|
||||
return 0;
|
||||
|
||||
let alwaysZoomOut = this._getAlwaysZoomOut();
|
||||
if (alwaysZoomOut)
|
||||
return 1;
|
||||
|
||||
let child = this.actor.get_first_child();
|
||||
let preferredHeight = child.get_preferred_height(-1)[1];
|
||||
let expandedWidth = child.get_preferred_width(preferredHeight)[1];
|
||||
|
||||
return this.getNonExpandedWidth() / expandedWidth;
|
||||
},
|
||||
|
||||
getVisibleWidth: function() {
|
||||
let alwaysZoomOut = this._getAlwaysZoomOut();
|
||||
if (alwaysZoomOut)
|
||||
return this.parent();
|
||||
else
|
||||
return this.getNonExpandedWidth();
|
||||
}
|
||||
});
|
||||
|
||||
const DashSlider = new Lang.Class({
|
||||
Name: 'DashSlider',
|
||||
Extends: SlidingControl,
|
||||
|
||||
_init: function(dash) {
|
||||
this.parent({ slideDirection: SlideDirection.LEFT });
|
||||
|
||||
this._dash = dash;
|
||||
|
||||
// SlideLayout reads the actor's expand flags to decide
|
||||
// whether to allocate the natural size to its child, or the whole
|
||||
// available allocation
|
||||
this._dash.actor.x_expand = true;
|
||||
this._dash.actor.y_expand = true;
|
||||
|
||||
this.actor.x_align = Clutter.ActorAlign.START;
|
||||
this.actor.y_expand = true;
|
||||
|
||||
this.actor.add_actor(this._dash.actor);
|
||||
|
||||
this._dash.connect('icon-size-changed', Lang.bind(this, this.updateSlide));
|
||||
Main.overview.connect('hiding', Lang.bind(this, this.slideOut));
|
||||
},
|
||||
|
||||
getSlide: function() {
|
||||
if (this.visible || this.inDrag)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
},
|
||||
|
||||
_onOverviewShowing: function() {
|
||||
this.visible = true;
|
||||
this.layout.slideX = this.getSlide();
|
||||
this.actor.translation_x = this._getTranslation();
|
||||
this.slideIn();
|
||||
},
|
||||
|
||||
_onWindowDragBegin: function() {
|
||||
this.fadeHalf();
|
||||
},
|
||||
|
||||
_onWindowDragEnd: function() {
|
||||
this.fadeIn();
|
||||
}
|
||||
});
|
||||
|
||||
const DashSpacer = new Lang.Class({
|
||||
Name: 'DashSpacer',
|
||||
Extends: St.Widget,
|
||||
|
||||
_init: function(params) {
|
||||
this.parent(params);
|
||||
|
||||
this._bindConstraint = null;
|
||||
},
|
||||
|
||||
setDashActor: function(dashActor) {
|
||||
if (this._bindConstraint) {
|
||||
this.remove_constraint(this._bindConstraint);
|
||||
this._bindConstraint = null;
|
||||
}
|
||||
|
||||
if (dashActor) {
|
||||
this._bindConstraint = new Clutter.BindConstraint({ source: dashActor,
|
||||
coordinate: Clutter.BindCoordinate.SIZE });
|
||||
this.add_constraint(this._bindConstraint);
|
||||
}
|
||||
},
|
||||
|
||||
vfunc_get_preferred_width: function(forHeight) {
|
||||
let box = this.get_allocation_box();
|
||||
let minWidth = this.parent(forHeight)[0];
|
||||
let natWidth = box.x2 - box.x1;
|
||||
return [minWidth, natWidth];
|
||||
},
|
||||
|
||||
vfunc_get_preferred_height: function(forWidth) {
|
||||
let box = this.get_allocation_box();
|
||||
let minHeight = this.parent(forWidth)[0];
|
||||
let natHeight = box.y2 - box.y1;
|
||||
return [minHeight, natHeight];
|
||||
}
|
||||
});
|
||||
|
||||
const MessagesIndicator = new Lang.Class({
|
||||
Name: 'MessagesIndicator',
|
||||
|
||||
_init: function(viewSelector) {
|
||||
this._count = 0;
|
||||
this._sources = [];
|
||||
this._viewSelector = viewSelector;
|
||||
|
||||
this._container = new St.BoxLayout({ style_class: 'messages-indicator-contents',
|
||||
reactive: true,
|
||||
track_hover: true,
|
||||
x_expand: true,
|
||||
y_expand: true,
|
||||
x_align: Clutter.ActorAlign.CENTER });
|
||||
|
||||
this._icon = new St.Icon({ icon_name: 'user-idle-symbolic',
|
||||
icon_size: 16 });
|
||||
this._container.add_actor(this._icon);
|
||||
|
||||
this._label = new St.Label();
|
||||
this._container.add_actor(this._label);
|
||||
|
||||
this._highlight = new St.Widget({ style_class: 'messages-indicator-highlight',
|
||||
x_expand: true,
|
||||
y_expand: true,
|
||||
y_align: Clutter.ActorAlign.END,
|
||||
visible: false });
|
||||
|
||||
this._container.connect('notify::hover', Lang.bind(this,
|
||||
function() {
|
||||
this._highlight.visible = this._container.hover;
|
||||
}));
|
||||
|
||||
let clickAction = new Clutter.ClickAction();
|
||||
this._container.add_action(clickAction);
|
||||
clickAction.connect('clicked', Lang.bind(this,
|
||||
function() {
|
||||
Main.messageTray.openTray();
|
||||
}));
|
||||
|
||||
Main.messageTray.connect('showing', Lang.bind(this,
|
||||
function() {
|
||||
this._highlight.visible = false;
|
||||
this._container.hover = false;
|
||||
}));
|
||||
|
||||
let layout = new Clutter.BinLayout();
|
||||
this.actor = new St.Widget({ layout_manager: layout,
|
||||
style_class: 'messages-indicator',
|
||||
y_expand: true,
|
||||
y_align: Clutter.ActorAlign.END,
|
||||
visible: false });
|
||||
this.actor.add_actor(this._container);
|
||||
this.actor.add_actor(this._highlight);
|
||||
|
||||
Main.messageTray.connect('source-added', Lang.bind(this, this._onSourceAdded));
|
||||
Main.messageTray.connect('source-removed', Lang.bind(this, this._onSourceRemoved));
|
||||
|
||||
let sources = Main.messageTray.getSources();
|
||||
sources.forEach(Lang.bind(this, function(source) { this._onSourceAdded(null, source); }));
|
||||
|
||||
this._viewSelector.connect('page-changed', Lang.bind(this, this._updateVisibility));
|
||||
Main.overview.connect('showing', Lang.bind(this, this._updateVisibility));
|
||||
},
|
||||
|
||||
_onSourceAdded: function(tray, source) {
|
||||
if (source.trayIcon)
|
||||
return;
|
||||
|
||||
if (source.isTransient)
|
||||
return;
|
||||
|
||||
source.connect('count-updated', Lang.bind(this, this._updateCount));
|
||||
this._sources.push(source);
|
||||
this._updateCount();
|
||||
},
|
||||
|
||||
_onSourceRemoved: function(tray, source) {
|
||||
this._sources.splice(this._sources.indexOf(source), 1);
|
||||
this._updateCount();
|
||||
},
|
||||
|
||||
_updateCount: function() {
|
||||
let count = 0;
|
||||
let hasChats = false;
|
||||
this._sources.forEach(Lang.bind(this,
|
||||
function(source) {
|
||||
count += source.indicatorCount;
|
||||
hasChats |= source.isChat;
|
||||
}));
|
||||
|
||||
this._count = count;
|
||||
this._label.text = ngettext("%d new message",
|
||||
"%d new messages",
|
||||
count).format(count);
|
||||
|
||||
this._icon.visible = hasChats;
|
||||
this._updateVisibility();
|
||||
},
|
||||
|
||||
_updateVisibility: function() {
|
||||
let activePage = this._viewSelector.getActivePage();
|
||||
let visible = ((this._count > 0) && (activePage == ViewSelector.ViewPage.WINDOWS));
|
||||
|
||||
this.actor.visible = visible;
|
||||
}
|
||||
});
|
||||
|
||||
const ControlsLayout = new Lang.Class({
|
||||
Name: 'ControlsLayout',
|
||||
Extends: Clutter.BinLayout,
|
||||
Signals: { 'allocation-changed': { flags: GObject.SignalFlags.RUN_LAST } },
|
||||
|
||||
vfunc_allocate: function(container, box, flags) {
|
||||
this.parent(container, box, flags);
|
||||
this.emit('allocation-changed');
|
||||
}
|
||||
});
|
||||
|
||||
const ControlsManager = new Lang.Class({
|
||||
Name: 'ControlsManager',
|
||||
|
||||
_init: function(searchEntry) {
|
||||
this.dash = new Dash.Dash();
|
||||
this._dashSlider = new DashSlider(this.dash);
|
||||
this._dashSpacer = new DashSpacer();
|
||||
this._dashSpacer.setDashActor(this._dashSlider.actor);
|
||||
|
||||
this._thumbnailsBox = new WorkspaceThumbnail.ThumbnailsBox();
|
||||
this._thumbnailsSlider = new ThumbnailsSlider(this._thumbnailsBox);
|
||||
|
||||
this.viewSelector = new ViewSelector.ViewSelector(searchEntry,
|
||||
this.dash.showAppsButton);
|
||||
this.viewSelector.connect('page-changed', Lang.bind(this, this._setVisibility));
|
||||
this.viewSelector.connect('page-empty', Lang.bind(this, this._onPageEmpty));
|
||||
|
||||
this._indicator = new MessagesIndicator(this.viewSelector);
|
||||
this.indicatorActor = this._indicator.actor;
|
||||
|
||||
let layout = new ControlsLayout();
|
||||
this.actor = new St.Widget({ layout_manager: layout,
|
||||
reactive: true,
|
||||
x_expand: true, y_expand: true,
|
||||
clip_to_allocation: true });
|
||||
this._group = new St.BoxLayout({ name: 'overview-group',
|
||||
x_expand: true, y_expand: true });
|
||||
this.actor.add_actor(this._group);
|
||||
|
||||
this.actor.add_actor(this._dashSlider.actor);
|
||||
|
||||
this._group.add_actor(this._dashSpacer);
|
||||
this._group.add(this.viewSelector.actor, { x_fill: true,
|
||||
expand: true });
|
||||
this._group.add_actor(this._thumbnailsSlider.actor);
|
||||
|
||||
layout.connect('allocation-changed', Lang.bind(this, this._updateWorkspacesGeometry));
|
||||
|
||||
Main.overview.connect('showing', Lang.bind(this, this._updateSpacerVisibility));
|
||||
Main.overview.connect('item-drag-begin', Lang.bind(this,
|
||||
function() {
|
||||
let activePage = this.viewSelector.getActivePage();
|
||||
if (activePage != ViewSelector.ViewPage.WINDOWS)
|
||||
this.viewSelector.fadeHalf();
|
||||
}));
|
||||
Main.overview.connect('item-drag-end', Lang.bind(this,
|
||||
function() {
|
||||
this.viewSelector.fadeIn();
|
||||
}));
|
||||
Main.overview.connect('item-drag-cancelled', Lang.bind(this,
|
||||
function() {
|
||||
this.viewSelector.fadeIn();
|
||||
}));
|
||||
},
|
||||
|
||||
_updateWorkspacesGeometry: function() {
|
||||
let [x, y] = this.actor.get_transformed_position();
|
||||
let [width, height] = this.actor.get_transformed_size();
|
||||
let geometry = { x: x, y: y, width: width, height: height };
|
||||
|
||||
let spacing = this.actor.get_theme_node().get_length('spacing');
|
||||
let dashWidth = this._dashSlider.getVisibleWidth() + spacing;
|
||||
let thumbnailsWidth = this._thumbnailsSlider.getNonExpandedWidth() + spacing;
|
||||
|
||||
geometry.width -= dashWidth;
|
||||
geometry.width -= thumbnailsWidth;
|
||||
|
||||
if (this.actor.get_text_direction() == Clutter.TextDirection.LTR)
|
||||
geometry.x += dashWidth;
|
||||
else
|
||||
geometry.x += thumbnailsWidth;
|
||||
|
||||
this.viewSelector.setWorkspacesFullGeometry(geometry);
|
||||
},
|
||||
|
||||
_setVisibility: function() {
|
||||
// Ignore the case when we're leaving the overview, since
|
||||
// actors will be made visible again when entering the overview
|
||||
// next time, and animating them while doing so is just
|
||||
// unnecessary noise
|
||||
if (!Main.overview.visible ||
|
||||
(Main.overview.animationInProgress && !Main.overview.visibleTarget))
|
||||
return;
|
||||
|
||||
let activePage = this.viewSelector.getActivePage();
|
||||
let dashVisible = (activePage == ViewSelector.ViewPage.WINDOWS ||
|
||||
activePage == ViewSelector.ViewPage.APPS);
|
||||
let thumbnailsVisible = (activePage == ViewSelector.ViewPage.WINDOWS);
|
||||
|
||||
if (dashVisible)
|
||||
this._dashSlider.slideIn();
|
||||
else
|
||||
this._dashSlider.slideOut();
|
||||
|
||||
if (thumbnailsVisible)
|
||||
this._thumbnailsSlider.slideIn();
|
||||
else
|
||||
this._thumbnailsSlider.slideOut();
|
||||
},
|
||||
|
||||
_updateSpacerVisibility: function() {
|
||||
if (Main.overview.animationInProgress && !Main.overview.visibleTarget)
|
||||
return;
|
||||
|
||||
let activePage = this.viewSelector.getActivePage();
|
||||
this._dashSpacer.visible = (activePage == ViewSelector.ViewPage.WINDOWS);
|
||||
},
|
||||
|
||||
_onPageEmpty: function() {
|
||||
this._dashSlider.pageEmpty();
|
||||
this._thumbnailsSlider.pageEmpty();
|
||||
|
||||
this._updateSpacerVisibility();
|
||||
}
|
||||
});
|
601
js/ui/panel.js
601
js/ui/panel.js
@ -15,13 +15,14 @@ const Signals = imports.signals;
|
||||
const Atk = imports.gi.Atk;
|
||||
|
||||
|
||||
const Animation = imports.ui.animation;
|
||||
const Config = imports.misc.config;
|
||||
const CtrlAltTab = imports.ui.ctrlAltTab;
|
||||
const DND = imports.ui.dnd;
|
||||
const Layout = imports.ui.layout;
|
||||
const Overview = imports.ui.overview;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const RemoteMenu = imports.ui.remoteMenu;
|
||||
const Main = imports.ui.main;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
@ -29,7 +30,6 @@ const PANEL_ICON_SIZE = 24;
|
||||
|
||||
const BUTTON_DND_ACTIVATION_TIMEOUT = 250;
|
||||
|
||||
const ANIMATED_ICON_UPDATE_TIMEOUT = 100;
|
||||
const SPINNER_ANIMATION_TIME = 0.2;
|
||||
|
||||
// To make sure the panel corners blend nicely with the panel,
|
||||
@ -75,81 +75,6 @@ function _unpremultiply(color) {
|
||||
blue: blue, alpha: color.alpha });
|
||||
};
|
||||
|
||||
const Animation = new Lang.Class({
|
||||
Name: 'Animation',
|
||||
|
||||
_init: function(filename, width, height, speed) {
|
||||
this.actor = new St.Bin();
|
||||
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
||||
this._speed = speed;
|
||||
|
||||
this._isLoaded = false;
|
||||
this._isPlaying = false;
|
||||
this._timeoutId = 0;
|
||||
this._frame = 0;
|
||||
this._animations = St.TextureCache.get_default().load_sliced_image (filename, width, height,
|
||||
Lang.bind(this, this._animationsLoaded));
|
||||
this.actor.set_child(this._animations);
|
||||
},
|
||||
|
||||
play: function() {
|
||||
if (this._isLoaded && this._timeoutId == 0) {
|
||||
if (this._frame == 0)
|
||||
this._showFrame(0);
|
||||
|
||||
this._timeoutId = Mainloop.timeout_add(this._speed, Lang.bind(this, this._update));
|
||||
}
|
||||
|
||||
this._isPlaying = true;
|
||||
},
|
||||
|
||||
stop: function() {
|
||||
if (this._timeoutId > 0) {
|
||||
Mainloop.source_remove(this._timeoutId);
|
||||
this._timeoutId = 0;
|
||||
}
|
||||
|
||||
this._isPlaying = false;
|
||||
},
|
||||
|
||||
_showFrame: function(frame) {
|
||||
let oldFrameActor = this._animations.get_child_at_index(this._frame);
|
||||
if (oldFrameActor)
|
||||
oldFrameActor.hide();
|
||||
|
||||
this._frame = (frame % this._animations.get_n_children());
|
||||
|
||||
let newFrameActor = this._animations.get_child_at_index(this._frame);
|
||||
if (newFrameActor)
|
||||
newFrameActor.show();
|
||||
},
|
||||
|
||||
_update: function() {
|
||||
this._showFrame(this._frame + 1);
|
||||
return true;
|
||||
},
|
||||
|
||||
_animationsLoaded: function() {
|
||||
this._isLoaded = true;
|
||||
|
||||
if (this._isPlaying)
|
||||
this.play();
|
||||
},
|
||||
|
||||
_onDestroy: function() {
|
||||
this.stop();
|
||||
}
|
||||
});
|
||||
|
||||
const AnimatedIcon = new Lang.Class({
|
||||
Name: 'AnimatedIcon',
|
||||
Extends: Animation,
|
||||
|
||||
_init: function(name, size) {
|
||||
this.parent(global.datadir + '/theme/' + name, size, size, ANIMATED_ICON_UPDATE_TIMEOUT);
|
||||
}
|
||||
});
|
||||
|
||||
const TextShadower = new Lang.Class({
|
||||
Name: 'TextShadower',
|
||||
|
||||
@ -203,7 +128,7 @@ const TextShadower = new Lang.Class({
|
||||
let child = children[i];
|
||||
let childBox = new Clutter.ActorBox();
|
||||
// The order of the labels here is arbitrary, except
|
||||
// we know the "real" label is at the end because Clutter.Group
|
||||
// we know the "real" label is at the end because Clutter.Actor
|
||||
// sorts by Z order
|
||||
switch (i) {
|
||||
case 0: // top
|
||||
@ -259,11 +184,11 @@ const AppMenuButton = new Lang.Class({
|
||||
this._actionGroupNotifyId = 0;
|
||||
|
||||
let bin = new St.Bin({ name: 'appMenu' });
|
||||
bin.connect('style-changed', Lang.bind(this, this._onStyleChanged));
|
||||
this.actor.add_actor(bin);
|
||||
|
||||
this.actor.bind_property("reactive", this.actor, "can-focus", 0);
|
||||
this.actor.reactive = false;
|
||||
this._targetIsCurrent = false;
|
||||
|
||||
this._container = new Shell.GenericContainer();
|
||||
bin.set_child(this._container);
|
||||
@ -281,51 +206,49 @@ const AppMenuButton = new Lang.Class({
|
||||
this._iconBox.connect('notify::allocation',
|
||||
Lang.bind(this, this._updateIconBoxClip));
|
||||
this._container.add_actor(this._iconBox);
|
||||
|
||||
this._hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' });
|
||||
this._container.add_actor(this._hbox);
|
||||
|
||||
this._label = new TextShadower();
|
||||
this._container.add_actor(this._label.actor);
|
||||
this._label.actor.y_align = Clutter.ActorAlign.CENTER;
|
||||
this._hbox.add_actor(this._label.actor);
|
||||
this._arrow = new St.Label({ text: '\u25BE',
|
||||
y_expand: true,
|
||||
y_align: Clutter.ActorAlign.CENTER });
|
||||
this._hbox.add_actor(this._arrow);
|
||||
|
||||
this._iconBottomClip = 0;
|
||||
|
||||
this._visible = !Main.overview.visible;
|
||||
if (!this._visible)
|
||||
this.actor.hide();
|
||||
Main.overview.connect('hiding', Lang.bind(this, function () {
|
||||
this.show();
|
||||
}));
|
||||
Main.overview.connect('showing', Lang.bind(this, function () {
|
||||
this.hide();
|
||||
}));
|
||||
this._overviewHidingId = Main.overview.connect('hiding', Lang.bind(this, this._sync));
|
||||
this._overviewShowingId = Main.overview.connect('showing', Lang.bind(this, this._sync));
|
||||
|
||||
this._stop = true;
|
||||
|
||||
this._spinner = new AnimatedIcon('process-working.svg',
|
||||
PANEL_ICON_SIZE);
|
||||
this._container.add_actor(this._spinner.actor);
|
||||
this._spinner.actor.hide();
|
||||
this._spinner.actor.lower_bottom();
|
||||
this._spinner = null;
|
||||
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
let appSys = Shell.AppSystem.get_default();
|
||||
tracker.connect('notify::focus-app', Lang.bind(this, this._focusAppChanged));
|
||||
appSys.connect('app-state-changed', Lang.bind(this, this._onAppStateChanged));
|
||||
|
||||
global.window_manager.connect('switch-workspace', Lang.bind(this, this._sync));
|
||||
this._focusAppNotifyId =
|
||||
tracker.connect('notify::focus-app', Lang.bind(this, this._focusAppChanged));
|
||||
this._appStateChangedSignalId =
|
||||
appSys.connect('app-state-changed', Lang.bind(this, this._onAppStateChanged));
|
||||
this._switchWorkspaceNotifyId =
|
||||
global.window_manager.connect('switch-workspace', Lang.bind(this, this._sync));
|
||||
|
||||
this._sync();
|
||||
},
|
||||
|
||||
show: function() {
|
||||
if (this._visible || Main.screenShield.locked)
|
||||
if (this._visible)
|
||||
return;
|
||||
|
||||
this._visible = true;
|
||||
this.actor.show();
|
||||
|
||||
if (!this._targetIsCurrent)
|
||||
return;
|
||||
|
||||
this.actor.reactive = true;
|
||||
|
||||
this.actor.show();
|
||||
Tweener.removeTweens(this.actor);
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 255,
|
||||
@ -339,11 +262,6 @@ const AppMenuButton = new Lang.Class({
|
||||
|
||||
this._visible = false;
|
||||
this.actor.reactive = false;
|
||||
if (!this._targetIsCurrent) {
|
||||
this.actor.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
Tweener.removeTweens(this.actor);
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 0,
|
||||
@ -355,19 +273,36 @@ const AppMenuButton = new Lang.Class({
|
||||
onCompleteScope: this });
|
||||
},
|
||||
|
||||
_onStyleChanged: function(actor) {
|
||||
let node = actor.get_theme_node();
|
||||
let [success, icon] = node.lookup_url('spinner-image', false);
|
||||
if (!success || this._spinnerIcon == icon)
|
||||
return;
|
||||
this._spinnerIcon = icon;
|
||||
this._spinner = new Animation.AnimatedIcon(this._spinnerIcon, PANEL_ICON_SIZE);
|
||||
this._hbox.add_actor(this._spinner.actor);
|
||||
this._spinner.actor.hide();
|
||||
},
|
||||
|
||||
_onIconBoxStyleChanged: function() {
|
||||
let node = this._iconBox.get_theme_node();
|
||||
this._iconBottomClip = node.get_length('app-icon-bottom-clip');
|
||||
this._updateIconBoxClip();
|
||||
},
|
||||
|
||||
_syncIcon: function() {
|
||||
if (!this._targetApp)
|
||||
return;
|
||||
|
||||
let icon = this._targetApp.get_faded_icon(2 * PANEL_ICON_SIZE, this._iconBox.text_direction);
|
||||
this._iconBox.set_child(icon);
|
||||
},
|
||||
|
||||
_onIconThemeChanged: function() {
|
||||
if (this._iconBox.child == null)
|
||||
return;
|
||||
|
||||
this._iconBox.child.destroy();
|
||||
let icon = this._targetApp.get_faded_icon(2 * PANEL_ICON_SIZE);
|
||||
this._iconBox.set_child(icon);
|
||||
this._syncIcon();
|
||||
},
|
||||
|
||||
_updateIconBoxClip: function() {
|
||||
@ -385,7 +320,10 @@ const AppMenuButton = new Lang.Class({
|
||||
return;
|
||||
|
||||
this._stop = true;
|
||||
this.actor.reactive = true;
|
||||
|
||||
if (this._spinner == null)
|
||||
return;
|
||||
|
||||
Tweener.addTween(this._spinner.actor,
|
||||
{ opacity: 0,
|
||||
time: SPINNER_ANIMATION_TIME,
|
||||
@ -401,7 +339,10 @@ const AppMenuButton = new Lang.Class({
|
||||
|
||||
startAnimation: function() {
|
||||
this._stop = false;
|
||||
this.actor.reactive = false;
|
||||
|
||||
if (this._spinner == null)
|
||||
return;
|
||||
|
||||
this._spinner.play();
|
||||
this._spinner.actor.show();
|
||||
},
|
||||
@ -410,7 +351,7 @@ const AppMenuButton = new Lang.Class({
|
||||
let [minSize, naturalSize] = this._iconBox.get_preferred_width(forHeight);
|
||||
alloc.min_size = minSize;
|
||||
alloc.natural_size = naturalSize;
|
||||
[minSize, naturalSize] = this._label.actor.get_preferred_width(forHeight);
|
||||
[minSize, naturalSize] = this._hbox.get_preferred_width(forHeight);
|
||||
alloc.min_size = alloc.min_size + Math.max(0, minSize - Math.floor(alloc.min_size / 2));
|
||||
alloc.natural_size = alloc.natural_size + Math.max(0, naturalSize - Math.floor(alloc.natural_size / 2));
|
||||
},
|
||||
@ -419,7 +360,7 @@ const AppMenuButton = new Lang.Class({
|
||||
let [minSize, naturalSize] = this._iconBox.get_preferred_height(forWidth);
|
||||
alloc.min_size = minSize;
|
||||
alloc.natural_size = naturalSize;
|
||||
[minSize, naturalSize] = this._label.actor.get_preferred_height(forWidth);
|
||||
[minSize, naturalSize] = this._hbox.get_preferred_height(forWidth);
|
||||
if (minSize > alloc.min_size)
|
||||
alloc.min_size = minSize;
|
||||
if (naturalSize > alloc.natural_size)
|
||||
@ -449,11 +390,10 @@ const AppMenuButton = new Lang.Class({
|
||||
|
||||
let iconWidth = childBox.x2 - childBox.x1;
|
||||
|
||||
[minWidth, minHeight, naturalWidth, naturalHeight] = this._label.actor.get_preferred_size();
|
||||
[minWidth, naturalWidth] = this._hbox.get_preferred_width(-1);
|
||||
|
||||
yPadding = Math.floor(Math.max(0, allocHeight - naturalHeight) / 2);
|
||||
childBox.y1 = yPadding;
|
||||
childBox.y2 = childBox.y1 + Math.min(naturalHeight, allocHeight);
|
||||
childBox.y1 = 0;
|
||||
childBox.y2 = allocHeight;
|
||||
|
||||
if (direction == Clutter.TextDirection.LTR) {
|
||||
childBox.x1 = Math.floor(iconWidth / 2);
|
||||
@ -462,21 +402,7 @@ const AppMenuButton = new Lang.Class({
|
||||
childBox.x2 = allocWidth - Math.floor(iconWidth / 2);
|
||||
childBox.x1 = Math.max(0, childBox.x2 - naturalWidth);
|
||||
}
|
||||
this._label.actor.allocate(childBox, flags);
|
||||
|
||||
if (direction == Clutter.TextDirection.LTR) {
|
||||
childBox.x1 = Math.floor(iconWidth / 2) + this._label.actor.width;
|
||||
childBox.x2 = childBox.x1 + this._spinner.actor.width;
|
||||
childBox.y1 = box.y1;
|
||||
childBox.y2 = box.y2 - 1;
|
||||
this._spinner.actor.allocate(childBox, flags);
|
||||
} else {
|
||||
childBox.x1 = -this._spinner.actor.width;
|
||||
childBox.x2 = childBox.x1 + this._spinner.actor.width;
|
||||
childBox.y1 = box.y1;
|
||||
childBox.y2 = box.y2 - 1;
|
||||
this._spinner.actor.allocate(childBox, flags);
|
||||
}
|
||||
this._hbox.allocate(childBox, flags);
|
||||
},
|
||||
|
||||
_onAppStateChanged: function(appSys, app) {
|
||||
@ -502,202 +428,167 @@ const AppMenuButton = new Lang.Class({
|
||||
// If the app has just lost focus to the panel, pretend
|
||||
// nothing happened; otherwise you can't keynav to the
|
||||
// app menu.
|
||||
if (global.stage_input_mode == Shell.StageInputMode.FOCUSED)
|
||||
if (global.stage.key_focus != null)
|
||||
return;
|
||||
}
|
||||
this._sync();
|
||||
},
|
||||
|
||||
_sync: function() {
|
||||
_findTargetApp: function() {
|
||||
let workspace = global.screen.get_active_workspace();
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
let focusedApp = tracker.focus_app;
|
||||
let lastStartedApp = null;
|
||||
let workspace = global.screen.get_active_workspace();
|
||||
if (focusedApp && focusedApp.is_on_workspace(workspace))
|
||||
return focusedApp;
|
||||
|
||||
for (let i = 0; i < this._startingApps.length; i++)
|
||||
if (this._startingApps[i].is_on_workspace(workspace))
|
||||
lastStartedApp = this._startingApps[i];
|
||||
return this._startingApps[i];
|
||||
|
||||
let targetApp = focusedApp != null ? focusedApp : lastStartedApp;
|
||||
return null;
|
||||
},
|
||||
|
||||
if (targetApp == null) {
|
||||
if (!this._targetIsCurrent)
|
||||
return;
|
||||
_sync: function() {
|
||||
let targetApp = this._findTargetApp();
|
||||
|
||||
this.actor.reactive = false;
|
||||
this._targetIsCurrent = false;
|
||||
|
||||
Tweener.removeTweens(this.actor);
|
||||
Tweener.addTween(this.actor, { opacity: 0,
|
||||
time: Overview.ANIMATION_TIME,
|
||||
transition: 'easeOutQuad' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!targetApp.is_on_workspace(workspace))
|
||||
return;
|
||||
|
||||
if (!this._targetIsCurrent) {
|
||||
this.actor.reactive = true;
|
||||
this._targetIsCurrent = true;
|
||||
|
||||
Tweener.removeTweens(this.actor);
|
||||
Tweener.addTween(this.actor, { opacity: 255,
|
||||
time: Overview.ANIMATION_TIME,
|
||||
transition: 'easeOutQuad' });
|
||||
}
|
||||
|
||||
if (targetApp == this._targetApp) {
|
||||
if (targetApp && targetApp.get_state() != Shell.AppState.STARTING) {
|
||||
this.stopAnimation();
|
||||
this._maybeSetMenu();
|
||||
if (this._targetApp != targetApp) {
|
||||
if (this._appMenuNotifyId) {
|
||||
this._targetApp.disconnect(this._appMenuNotifyId);
|
||||
this._appMenuNotifyId = 0;
|
||||
}
|
||||
if (this._actionGroupNotifyId) {
|
||||
this._targetApp.disconnect(this._actionGroupNotifyId);
|
||||
this._actionGroupNotifyId = 0;
|
||||
}
|
||||
|
||||
this._targetApp = targetApp;
|
||||
|
||||
if (this._targetApp) {
|
||||
this._appMenuNotifyId = this._targetApp.connect('notify::menu', Lang.bind(this, this._sync));
|
||||
this._actionGroupNotifyId = this._targetApp.connect('notify::action-group', Lang.bind(this, this._sync));
|
||||
this._label.setText(this._targetApp.get_name());
|
||||
this.actor.set_accessible_name(this._targetApp.get_name());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this._spinner.actor.hide();
|
||||
if (this._iconBox.child != null)
|
||||
this._iconBox.child.destroy();
|
||||
this._iconBox.hide();
|
||||
this._label.setText('');
|
||||
let visible = (this._targetApp != null && !Main.overview.visibleTarget);
|
||||
if (visible)
|
||||
this.show();
|
||||
else
|
||||
this.hide();
|
||||
|
||||
if (this._appMenuNotifyId)
|
||||
this._targetApp.disconnect(this._appMenuNotifyId);
|
||||
if (this._actionGroupNotifyId)
|
||||
this._targetApp.disconnect(this._actionGroupNotifyId);
|
||||
if (targetApp) {
|
||||
this._appMenuNotifyId = targetApp.connect('notify::menu', Lang.bind(this, this._sync));
|
||||
this._actionGroupNotifyId = targetApp.connect('notify::action-group', Lang.bind(this, this._sync));
|
||||
} else {
|
||||
this._appMenuNotifyId = 0;
|
||||
this._actionGroupNotifyId = 0;
|
||||
}
|
||||
|
||||
this._targetApp = targetApp;
|
||||
let icon = targetApp.get_faded_icon(2 * PANEL_ICON_SIZE);
|
||||
|
||||
this._label.setText(targetApp.get_name());
|
||||
this.setName(targetApp.get_name());
|
||||
|
||||
this._iconBox.set_child(icon);
|
||||
this._iconBox.show();
|
||||
|
||||
if (targetApp.get_state() == Shell.AppState.STARTING)
|
||||
let isBusy = (this._targetApp != null &&
|
||||
(this._targetApp.get_state() == Shell.AppState.STARTING ||
|
||||
this._targetApp.get_state() == Shell.AppState.BUSY));
|
||||
if (isBusy)
|
||||
this.startAnimation();
|
||||
else
|
||||
this._maybeSetMenu();
|
||||
this.stopAnimation();
|
||||
|
||||
this.actor.reactive = (visible && !isBusy);
|
||||
|
||||
this._syncIcon();
|
||||
this._maybeSetMenu();
|
||||
this.emit('changed');
|
||||
},
|
||||
|
||||
_maybeSetMenu: function() {
|
||||
let menu;
|
||||
|
||||
if (this._targetApp.action_group && this._targetApp.menu) {
|
||||
if (this.menu instanceof PopupMenu.RemoteMenu &&
|
||||
if (this._targetApp == null) {
|
||||
menu = null;
|
||||
} else if (this._targetApp.action_group && this._targetApp.menu) {
|
||||
if (this.menu instanceof RemoteMenu.RemoteMenu &&
|
||||
this.menu.actionGroup == this._targetApp.action_group)
|
||||
return;
|
||||
|
||||
menu = new PopupMenu.RemoteMenu(this.actor, this._targetApp.menu, this._targetApp.action_group);
|
||||
menu = new RemoteMenu.RemoteMenu(this.actor, this._targetApp.menu, this._targetApp.action_group);
|
||||
menu.connect('activate', Lang.bind(this, function() {
|
||||
let win = this._targetApp.get_windows()[0];
|
||||
win.check_alive(global.get_current_time());
|
||||
}));
|
||||
|
||||
} else {
|
||||
if (this.menu && !(this.menu instanceof PopupMenu.RemoteMenu))
|
||||
if (this.menu && this.menu.isDummyQuitMenu)
|
||||
return;
|
||||
|
||||
// fallback to older menu
|
||||
menu = new PopupMenu.PopupMenu(this.actor, 0.0, St.Side.TOP, 0);
|
||||
menu.isDummyQuitMenu = true;
|
||||
menu.addAction(_("Quit"), Lang.bind(this, function() {
|
||||
this._targetApp.request_quit();
|
||||
}));
|
||||
}
|
||||
|
||||
this.setMenu(menu);
|
||||
this._menuManager.addMenu(menu);
|
||||
if (menu)
|
||||
this._menuManager.addMenu(menu);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this._appStateChangedSignalId > 0) {
|
||||
let appSys = Shell.AppSystem.get_default();
|
||||
appSys.disconnect(this._appStateChangedSignalId);
|
||||
this._appStateChangedSignalId = 0;
|
||||
}
|
||||
if (this._focusAppNotifyId > 0) {
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
tracker.disconnect(this._focusAppNotifyId);
|
||||
this._focusAppNotifyId = 0;
|
||||
}
|
||||
if (this._overviewHidingId > 0) {
|
||||
Main.overview.disconnect(this._overviewHidingId);
|
||||
this._overviewHidingId = 0;
|
||||
}
|
||||
if (this._overviewShowingId > 0) {
|
||||
Main.overview.disconnect(this._overviewShowingId);
|
||||
this._overviewShowingId = 0;
|
||||
}
|
||||
if (this._switchWorkspaceNotifyId > 0) {
|
||||
global.window_manager.disconnect(this._switchWorkspaceNotifyId);
|
||||
this._switchWorkspaceNotifyId = 0;
|
||||
}
|
||||
|
||||
this.parent();
|
||||
}
|
||||
});
|
||||
|
||||
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.
|
||||
const ActivitiesButton = new Lang.Class({
|
||||
Name: 'ActivitiesButton',
|
||||
Extends: PanelMenu.Button,
|
||||
|
||||
_init: function() {
|
||||
this.parent(0.0);
|
||||
this.parent(0.0, null, true);
|
||||
this.actor.accessible_role = Atk.Role.TOGGLE_BUTTON;
|
||||
|
||||
let container = new Shell.GenericContainer();
|
||||
container.connect('get-preferred-width', Lang.bind(this, this._containerGetPreferredWidth));
|
||||
container.connect('get-preferred-height', Lang.bind(this, this._containerGetPreferredHeight));
|
||||
container.connect('allocate', Lang.bind(this, this._containerAllocate));
|
||||
this.actor.add_actor(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._label = new St.Label({ text: _("Activities"),
|
||||
y_align: Clutter.ActorAlign.CENTER });
|
||||
this.actor.add_actor(this._label);
|
||||
|
||||
this.actor.label_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();
|
||||
this.actor.add_accessible_state (Atk.StateType.CHECKED);
|
||||
}));
|
||||
Main.overview.connect('hiding', Lang.bind(this, function() {
|
||||
this.actor.remove_style_pseudo_class('overview');
|
||||
this._escapeMenuGrab();
|
||||
this.actor.remove_accessible_state (Atk.StateType.CHECKED);
|
||||
}));
|
||||
|
||||
this._xdndTimeOut = 0;
|
||||
},
|
||||
|
||||
_containerGetPreferredWidth: function(actor, forHeight, alloc) {
|
||||
[alloc.min_size, alloc.natural_size] = this._label.get_preferred_width(forHeight);
|
||||
},
|
||||
|
||||
_containerGetPreferredHeight: function(actor, forWidth, alloc) {
|
||||
[alloc.min_size, alloc.natural_size] = this._label.get_preferred_height(forWidth);
|
||||
},
|
||||
|
||||
_containerAllocate: 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_text_direction() == Clutter.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 DND.DragMotionResult.CONTINUE;
|
||||
@ -705,65 +596,37 @@ const ActivitiesButton = new Lang.Class({
|
||||
if (this._xdndTimeOut != 0)
|
||||
Mainloop.source_remove(this._xdndTimeOut);
|
||||
this._xdndTimeOut = Mainloop.timeout_add(BUTTON_DND_ACTIVATION_TIMEOUT,
|
||||
Lang.bind(this, this._xdndShowOverview, actor));
|
||||
Lang.bind(this, this._xdndToggleOverview, actor));
|
||||
|
||||
return DND.DragMotionResult.CONTINUE;
|
||||
},
|
||||
|
||||
_escapeMenuGrab: function() {
|
||||
if (this.menu.isOpen)
|
||||
this.menu.close();
|
||||
},
|
||||
|
||||
_onCapturedEvent: function(actor, event) {
|
||||
if (event.type() == Clutter.EventType.BUTTON_PRESS) {
|
||||
if (!this.hotCorner.shouldToggleOverviewOnClick())
|
||||
if (!Main.overview.shouldToggleByCornerOrButton())
|
||||
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();
|
||||
}
|
||||
Main.overview.toggle();
|
||||
this.menu.close();
|
||||
},
|
||||
|
||||
_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) {
|
||||
_xdndToggleOverview: 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);
|
||||
}
|
||||
}
|
||||
if (pickedActor == this.actor && Main.overview.shouldToggleByCornerOrButton())
|
||||
Main.overview.toggle();
|
||||
|
||||
Mainloop.source_remove(this._xdndTimeOut);
|
||||
this._xdndTimeOut = 0;
|
||||
@ -773,12 +636,9 @@ const ActivitiesButton = new Lang.Class({
|
||||
const PanelCorner = new Lang.Class({
|
||||
Name: 'PanelCorner',
|
||||
|
||||
_init: function(box, side) {
|
||||
_init: function(side) {
|
||||
this._side = side;
|
||||
|
||||
this._box = box;
|
||||
this._box.connect('style-changed', Lang.bind(this, this._boxStyleChanged));
|
||||
|
||||
this.actor = new St.DrawingArea({ style_class: 'panel-corner' });
|
||||
this.actor.connect('style-changed', Lang.bind(this, this._styleChanged));
|
||||
this.actor.connect('repaint', Lang.bind(this, this._repaint));
|
||||
@ -834,12 +694,12 @@ const PanelCorner = new Lang.Class({
|
||||
return children[index];
|
||||
},
|
||||
|
||||
_boxStyleChanged: function() {
|
||||
setStyleParent: function(box) {
|
||||
let side = this._side;
|
||||
|
||||
let rtlAwareContainer = this._box instanceof St.BoxLayout;
|
||||
let rtlAwareContainer = box instanceof St.BoxLayout;
|
||||
if (rtlAwareContainer &&
|
||||
this._box.get_text_direction() == Clutter.TextDirection.RTL) {
|
||||
box.get_text_direction() == Clutter.TextDirection.RTL) {
|
||||
if (this._side == St.Side.LEFT)
|
||||
side = St.Side.RIGHT;
|
||||
else if (this._side == St.Side.RIGHT)
|
||||
@ -848,9 +708,9 @@ const PanelCorner = new Lang.Class({
|
||||
|
||||
let button;
|
||||
if (side == St.Side.LEFT)
|
||||
button = this._findLeftmostButton(this._box);
|
||||
button = this._findLeftmostButton(box);
|
||||
else if (side == St.Side.RIGHT)
|
||||
button = this._findRightmostButton(this._box);
|
||||
button = this._findRightmostButton(box);
|
||||
|
||||
if (button) {
|
||||
if (this._button && this._buttonStyleChangedSignalId) {
|
||||
@ -877,7 +737,7 @@ const PanelCorner = new Lang.Class({
|
||||
|
||||
// The corner doesn't support theme transitions, so override
|
||||
// the .panel-button default
|
||||
button.style = 'transition-duration: 0';
|
||||
button.style = 'transition-duration: 0ms';
|
||||
}
|
||||
},
|
||||
|
||||
@ -890,8 +750,8 @@ const PanelCorner = new Lang.Class({
|
||||
let backgroundColor = node.get_color('-panel-corner-background-color');
|
||||
let borderColor = node.get_color('-panel-corner-border-color');
|
||||
|
||||
let noOverlap = borderColor.alpha == 0;
|
||||
let offsetY = noOverlap ? borderWidth : 0;
|
||||
let overlap = borderColor.alpha != 0;
|
||||
let offsetY = overlap ? 0 : borderWidth;
|
||||
|
||||
let cr = this.actor.get_context();
|
||||
cr.setOperator(Cairo.Operator.SOURCE);
|
||||
@ -915,17 +775,18 @@ const PanelCorner = new Lang.Class({
|
||||
Clutter.cairo_set_source_color(cr, over);
|
||||
cr.fill();
|
||||
|
||||
if (noOverlap)
|
||||
return;
|
||||
if (overlap) {
|
||||
let offset = borderWidth;
|
||||
Clutter.cairo_set_source_color(cr, backgroundColor);
|
||||
|
||||
let offset = borderWidth;
|
||||
Clutter.cairo_set_source_color(cr, backgroundColor);
|
||||
cr.save();
|
||||
cr.translate(xOffsetDirection * offset, - offset);
|
||||
cr.appendPath(savedPath);
|
||||
cr.fill();
|
||||
cr.restore();
|
||||
}
|
||||
|
||||
cr.save();
|
||||
cr.translate(xOffsetDirection * offset, - offset);
|
||||
cr.appendPath(savedPath);
|
||||
cr.fill();
|
||||
cr.restore();
|
||||
cr.$dispose();
|
||||
},
|
||||
|
||||
_styleChanged: function() {
|
||||
@ -939,31 +800,67 @@ const PanelCorner = new Lang.Class({
|
||||
}
|
||||
});
|
||||
|
||||
const AggregateMenu = new Lang.Class({
|
||||
Name: 'AggregateMenu',
|
||||
Extends: PanelMenu.Button,
|
||||
|
||||
_init: function() {
|
||||
this.parent(0.0, _("Settings"), false);
|
||||
this.menu.actor.add_style_class_name('aggregate-menu');
|
||||
|
||||
this._indicators = new St.BoxLayout({ style_class: 'panel-status-indicators-box' });
|
||||
this.actor.add_child(this._indicators);
|
||||
|
||||
this._network = new imports.ui.status.network.NMApplet();
|
||||
if (Config.HAVE_BLUETOOTH) {
|
||||
this._bluetooth = new imports.ui.status.bluetooth.Indicator();
|
||||
} else {
|
||||
this._bluetooth = null;
|
||||
}
|
||||
|
||||
this._power = new imports.ui.status.power.Indicator();
|
||||
this._rfkill = new imports.ui.status.rfkill.Indicator();
|
||||
this._volume = new imports.ui.status.volume.Indicator();
|
||||
this._brightness = new imports.ui.status.brightness.Indicator();
|
||||
this._system = new imports.ui.status.system.Indicator();
|
||||
this._screencast = new imports.ui.status.screencast.Indicator();
|
||||
|
||||
this._indicators.add_child(this._screencast.indicators);
|
||||
this._indicators.add_child(this._network.indicators);
|
||||
if (this._bluetooth) {
|
||||
this._indicators.add_child(this._bluetooth.indicators);
|
||||
}
|
||||
this._indicators.add_child(this._rfkill.indicators);
|
||||
this._indicators.add_child(this._volume.indicators);
|
||||
this._indicators.add_child(this._power.indicators);
|
||||
this._indicators.add_child(new St.Label({ text: '\u25BE',
|
||||
y_expand: true,
|
||||
y_align: Clutter.ActorAlign.CENTER }));
|
||||
|
||||
this.menu.addMenuItem(this._volume.menu);
|
||||
this.menu.addMenuItem(this._brightness.menu);
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this.menu.addMenuItem(this._network.menu);
|
||||
if (this._bluetooth) {
|
||||
this.menu.addMenuItem(this._bluetooth.menu);
|
||||
}
|
||||
this.menu.addMenuItem(this._rfkill.menu);
|
||||
this.menu.addMenuItem(this._power.menu);
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this.menu.addMenuItem(this._system.menu);
|
||||
},
|
||||
});
|
||||
|
||||
const PANEL_ITEM_IMPLEMENTATIONS = {
|
||||
'activities': ActivitiesButton,
|
||||
'aggregateMenu': AggregateMenu,
|
||||
'appMenu': AppMenuButton,
|
||||
'dateMenu': imports.ui.dateMenu.DateMenuButton,
|
||||
'a11y': imports.ui.status.accessibility.ATIndicator,
|
||||
'volume': imports.ui.status.volume.Indicator,
|
||||
'battery': imports.ui.status.power.Indicator,
|
||||
'lockScreen': imports.ui.status.lockScreenMenu.Indicator,
|
||||
'logo': imports.gdm.loginDialog.LogoMenuButton,
|
||||
'a11yGreeter': imports.ui.status.accessibility.ATGreeterIndicator,
|
||||
'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
|
||||
'powerMenu': imports.gdm.powerMenu.PowerMenuButton,
|
||||
'userMenu': imports.ui.userMenu.UserMenuButton
|
||||
};
|
||||
|
||||
if (Config.HAVE_BLUETOOTH)
|
||||
PANEL_ITEM_IMPLEMENTATIONS['bluetooth'] =
|
||||
imports.ui.status.bluetooth.Indicator;
|
||||
|
||||
try {
|
||||
PANEL_ITEM_IMPLEMENTATIONS['network'] =
|
||||
imports.ui.status.network.NMApplet;
|
||||
} catch(e) {
|
||||
log('NMApplet is not supported. It is possible that your NetworkManager version is too old');
|
||||
}
|
||||
|
||||
const Panel = new Lang.Class({
|
||||
Name: 'Panel',
|
||||
|
||||
@ -976,7 +873,7 @@ const Panel = new Lang.Class({
|
||||
|
||||
this.statusArea = {};
|
||||
|
||||
this.menuManager = new PopupMenu.PopupMenuManager(this);
|
||||
this.menuManager = new PopupMenu.PopupMenuManager(this, { keybindingMode: Shell.KeyBindingMode.TOPBAR_POPUP });
|
||||
|
||||
this._leftBox = new St.BoxLayout({ name: 'panelLeft' });
|
||||
this.actor.add_actor(this._leftBox);
|
||||
@ -985,17 +882,10 @@ const Panel = new Lang.Class({
|
||||
this._rightBox = new St.BoxLayout({ name: 'panelRight' });
|
||||
this.actor.add_actor(this._rightBox);
|
||||
|
||||
if (this.actor.get_text_direction() == Clutter.TextDirection.RTL)
|
||||
this._leftCorner = new PanelCorner(this._rightBox, St.Side.LEFT);
|
||||
else
|
||||
this._leftCorner = new PanelCorner(this._leftBox, St.Side.LEFT);
|
||||
|
||||
this._leftCorner = new PanelCorner(St.Side.LEFT);
|
||||
this.actor.add_actor(this._leftCorner.actor);
|
||||
|
||||
if (this.actor.get_text_direction() == Clutter.TextDirection.RTL)
|
||||
this._rightCorner = new PanelCorner(this._leftBox, St.Side.RIGHT);
|
||||
else
|
||||
this._rightCorner = new PanelCorner(this._rightBox, St.Side.RIGHT);
|
||||
this._rightCorner = new PanelCorner(St.Side.RIGHT);
|
||||
this.actor.add_actor(this._rightCorner.actor);
|
||||
|
||||
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||
@ -1003,6 +893,13 @@ const Panel = new Lang.Class({
|
||||
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
||||
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
|
||||
|
||||
Main.overview.connect('showing', Lang.bind(this, function () {
|
||||
this.actor.add_style_pseudo_class('overview');
|
||||
}));
|
||||
Main.overview.connect('hiding', Lang.bind(this, function () {
|
||||
this.actor.remove_style_pseudo_class('overview');
|
||||
}));
|
||||
|
||||
Main.layoutManager.panelBox.add(this.actor);
|
||||
Main.ctrlAltTabManager.addGroup(this.actor, _("Top Bar"), 'emblem-system-symbolic',
|
||||
{ sortGroup: CtrlAltTab.SortGroup.TOP });
|
||||
@ -1089,6 +986,9 @@ const Panel = new Lang.Class({
|
||||
},
|
||||
|
||||
_onButtonPress: function(actor, event) {
|
||||
if (Main.modalCount > 0)
|
||||
return false;
|
||||
|
||||
if (event.get_source() != actor)
|
||||
return false;
|
||||
|
||||
@ -1127,17 +1027,18 @@ const Panel = new Lang.Class({
|
||||
return true;
|
||||
},
|
||||
|
||||
openAppMenu: function() {
|
||||
toggleAppMenu: function() {
|
||||
let indicator = this.statusArea.appMenu;
|
||||
if (!indicator) // appMenu not supported by current session mode
|
||||
return;
|
||||
|
||||
let menu = indicator.menu;
|
||||
if (!indicator.actor.reactive || menu.isOpen)
|
||||
if (!indicator.actor.reactive)
|
||||
return;
|
||||
|
||||
menu.open();
|
||||
menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
menu.toggle();
|
||||
if (menu.isOpen)
|
||||
menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
},
|
||||
|
||||
set boxOpacity(value) {
|
||||
@ -1168,17 +1069,13 @@ const Panel = new Lang.Class({
|
||||
this._sessionStyle = Main.sessionMode.panelStyle;
|
||||
if (this._sessionStyle)
|
||||
this._addStyleClassName(this._sessionStyle);
|
||||
},
|
||||
|
||||
_initBox: function(elements, box) {
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
let role = elements[i];
|
||||
let constructor = PANEL_ITEM_IMPLEMENTATIONS[role];
|
||||
if (!constructor) {
|
||||
// panel icon is not supported (can happen for
|
||||
// bluetooth or network)
|
||||
continue;
|
||||
}
|
||||
if (this.actor.get_text_direction() == Clutter.TextDirection.RTL) {
|
||||
this._leftCorner.setStyleParent(this._rightBox);
|
||||
this._rightCorner.setStyleParent(this._leftBox);
|
||||
} else {
|
||||
this._leftCorner.setStyleParent(this._leftBox);
|
||||
this._rightCorner.setStyleParent(this._rightBox);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -86,13 +86,8 @@ const ButtonBox = new Lang.Class({
|
||||
childBox.x2 = availWidth - this._minHPadding;
|
||||
}
|
||||
|
||||
if (natHeight <= availHeight) {
|
||||
childBox.y1 = Math.floor((availHeight - natHeight) / 2);
|
||||
childBox.y2 = childBox.y1 + natHeight;
|
||||
} else {
|
||||
childBox.y1 = 0;
|
||||
childBox.y2 = availHeight;
|
||||
}
|
||||
childBox.y1 = 0;
|
||||
childBox.y2 = availHeight;
|
||||
|
||||
child.allocate(childBox, flags);
|
||||
},
|
||||
@ -106,17 +101,17 @@ const Button = new Lang.Class({
|
||||
this.parent({ reactive: true,
|
||||
can_focus: true,
|
||||
track_hover: true,
|
||||
accessible_name: nameText ? nameText : "",
|
||||
accessible_role: Atk.Role.MENU });
|
||||
|
||||
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
|
||||
this.actor.connect('key-press-event', Lang.bind(this, this._onSourceKeyPress));
|
||||
this.actor.connect('notify::visible', Lang.bind(this, this._onVisibilityChanged));
|
||||
|
||||
if (dontCreateMenu)
|
||||
this.menu = null;
|
||||
this.menu = new PopupMenu.PopupDummyMenu(this.actor);
|
||||
else
|
||||
this.setMenu(new PopupMenu.PopupMenu(this.actor, menuAlignment, St.Side.TOP, 0));
|
||||
|
||||
this.setName(nameText);
|
||||
},
|
||||
|
||||
setSensitive: function(sensitive) {
|
||||
@ -125,22 +120,6 @@ const Button = new Lang.Class({
|
||||
this.actor.track_hover = sensitive;
|
||||
},
|
||||
|
||||
setName: function(text) {
|
||||
if (text != null) {
|
||||
// This is the easiest way to provide a accessible name to
|
||||
// this widget. The label could be also used for other
|
||||
// purposes in the future.
|
||||
if (!this.label) {
|
||||
this.label = new St.Label({ text: text });
|
||||
this.actor.label_actor = this.label;
|
||||
} else
|
||||
this.label.text = text;
|
||||
} else {
|
||||
this.label = null;
|
||||
this.actor.label_actor = null;
|
||||
}
|
||||
},
|
||||
|
||||
setMenu: function(menu) {
|
||||
if (this.menu)
|
||||
this.menu.destroy();
|
||||
@ -183,7 +162,18 @@ const Button = new Lang.Class({
|
||||
return false;
|
||||
},
|
||||
|
||||
_onVisibilityChanged: function() {
|
||||
if (!this.menu)
|
||||
return;
|
||||
|
||||
if (!this.actor.visible)
|
||||
this.menu.close();
|
||||
},
|
||||
|
||||
_onMenuKeyPress: function(actor, event) {
|
||||
if (global.focus_manager.navigate_from_event(event))
|
||||
return true;
|
||||
|
||||
let symbol = event.get_key_symbol();
|
||||
if (symbol == Clutter.KEY_Left || symbol == Clutter.KEY_Right) {
|
||||
let group = global.focus_manager.get_group(this.actor);
|
||||
@ -205,10 +195,8 @@ const Button = new Lang.Class({
|
||||
// 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 = Main.layoutManager.primaryMonitor;
|
||||
this.menu.actor.style = ('max-height: ' +
|
||||
Math.round(monitor.height - Main.panel.actor.height) +
|
||||
'px;');
|
||||
let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
|
||||
this.menu.actor.style = ('max-height: ' + Math.round(workArea.height) + 'px;');
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
@ -223,48 +211,35 @@ const Button = new Lang.Class({
|
||||
});
|
||||
Signals.addSignalMethods(Button.prototype);
|
||||
|
||||
/* SystemStatusButton:
|
||||
/* SystemIndicator:
|
||||
*
|
||||
* This class manages one System Status indicator (network, keyboard,
|
||||
* volume, bluetooth...), which is just a PanelMenuButton with an
|
||||
* icon.
|
||||
* This class manages one system indicator, which are the icons
|
||||
* that you see at the top right. A system indicator is composed
|
||||
* of an icon and a menu section, which will be composed into the
|
||||
* aggregate menu.
|
||||
*/
|
||||
const SystemStatusButton = new Lang.Class({
|
||||
Name: 'SystemStatusButton',
|
||||
Extends: Button,
|
||||
const SystemIndicator = new Lang.Class({
|
||||
Name: 'SystemIndicator',
|
||||
|
||||
_init: function(iconName, nameText) {
|
||||
this.parent(0.0, nameText);
|
||||
this.actor.add_style_class_name('panel-status-button');
|
||||
|
||||
this._box = new St.BoxLayout({ style_class: 'panel-status-button-box' });
|
||||
this.actor.add_actor(this._box);
|
||||
|
||||
if (iconName)
|
||||
this.setIcon(iconName);
|
||||
_init: function() {
|
||||
this.indicators = new St.BoxLayout({ style_class: 'panel-status-indicators-box',
|
||||
reactive: true });
|
||||
this.indicators.hide();
|
||||
this.menu = new PopupMenu.PopupMenuSection();
|
||||
},
|
||||
|
||||
addIcon: function(gicon) {
|
||||
let icon = new St.Icon({ gicon: gicon,
|
||||
style_class: 'system-status-icon' });
|
||||
this._box.add_actor(icon);
|
||||
_syncIndicatorsVisible: function() {
|
||||
this.indicators.visible = this.indicators.get_children().some(function(actor) {
|
||||
return actor.visible;
|
||||
});
|
||||
},
|
||||
|
||||
_addIndicator: function() {
|
||||
let icon = new St.Icon({ style_class: 'system-status-icon' });
|
||||
this.indicators.add_actor(icon);
|
||||
icon.connect('notify::visible', Lang.bind(this, this._syncIndicatorsVisible));
|
||||
this._syncIndicatorsVisible();
|
||||
return icon;
|
||||
},
|
||||
|
||||
setIcon: function(iconName) {
|
||||
// Need to first add a NULL GIcon and then set icon_name, to ensure
|
||||
// compatibility with -symbolic fallbacks
|
||||
|
||||
if (!this.mainIcon)
|
||||
this.mainIcon = this.addIcon(null);
|
||||
this.mainIcon.icon_name = iconName;
|
||||
},
|
||||
|
||||
setGIcon: function(gicon) {
|
||||
if (this.mainIcon)
|
||||
this.mainIcon.gicon = gicon;
|
||||
else
|
||||
this.mainIcon = this.addIcon(gicon);
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(SystemIndicator.prototype);
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
const GnomeDesktop = imports.gi.GnomeDesktop;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
@ -41,10 +42,9 @@ const PointerWatcher = new Lang.Class({
|
||||
Name: 'PointerWatcher',
|
||||
|
||||
_init: function() {
|
||||
let idleMonitor = new GnomeDesktop.IdleMonitor();
|
||||
idleMonitor.connect('became-active', Lang.bind(this, this._onIdleMonitorBecameActive));
|
||||
idleMonitor.add_watch(IDLE_TIME, Lang.bind(this, this._onIdleMonitorBecameIdle));
|
||||
this._idle = idleMonitor.get_idletime() > IDLE_TIME;
|
||||
this._idleMonitor = Meta.IdleMonitor.get_core();
|
||||
this._idleMonitor.add_idle_watch(IDLE_TIME, Lang.bind(this, this._onIdleMonitorBecameIdle));
|
||||
this._idle = this._idleMonitor.get_idletime() > IDLE_TIME;
|
||||
this._watches = [];
|
||||
this.pointerX = null;
|
||||
this.pointerY = null;
|
||||
@ -87,6 +87,7 @@ const PointerWatcher = new Lang.Class({
|
||||
|
||||
_onIdleMonitorBecameIdle: function(monitor) {
|
||||
this._idle = true;
|
||||
this._idleMonitor.add_user_active_watch(Lang.bind(this, this._onIdleMonitorBecameActive));
|
||||
this._updateTimeout();
|
||||
},
|
||||
|
||||
|
1782
js/ui/popupMenu.js
1782
js/ui/popupMenu.js
File diff suppressed because it is too large
Load Diff
199
js/ui/remoteMenu.js
Normal file
199
js/ui/remoteMenu.js
Normal file
@ -0,0 +1,199 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Atk = imports.gi.Atk;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const ShellMenu = imports.gi.ShellMenu;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
|
||||
function stripMnemonics(label) {
|
||||
if (!label)
|
||||
return '';
|
||||
|
||||
// remove all underscores that are not followed by another underscore
|
||||
return label.replace(/_([^_])/, '$1');
|
||||
}
|
||||
|
||||
function _insertItem(menu, trackerItem, position) {
|
||||
let mapper;
|
||||
|
||||
if (trackerItem.get_is_separator())
|
||||
mapper = new RemoteMenuSeparatorItemMapper(trackerItem);
|
||||
else if (trackerItem.get_has_submenu())
|
||||
mapper = new RemoteMenuSubmenuItemMapper(trackerItem);
|
||||
else
|
||||
mapper = new RemoteMenuItemMapper(trackerItem);
|
||||
|
||||
let item = mapper.menuItem;
|
||||
menu.addMenuItem(item, position);
|
||||
}
|
||||
|
||||
function _removeItem(menu, position) {
|
||||
let items = menu._getMenuItems();
|
||||
items[position].destroy();
|
||||
}
|
||||
|
||||
const RemoteMenuSeparatorItemMapper = new Lang.Class({
|
||||
Name: 'RemoteMenuSeparatorItemMapper',
|
||||
|
||||
_init: function(trackerItem) {
|
||||
this._trackerItem = trackerItem;
|
||||
this.menuItem = new PopupMenu.PopupSeparatorMenuItem();
|
||||
this._trackerItem.connect('notify::label', Lang.bind(this, this._updateLabel));
|
||||
this._updateLabel();
|
||||
|
||||
this.menuItem.connect('destroy', function() {
|
||||
trackerItem.run_dispose();
|
||||
});
|
||||
},
|
||||
|
||||
_updateLabel: function() {
|
||||
this.menuItem.label.text = stripMnemonics(this._trackerItem.label);
|
||||
},
|
||||
});
|
||||
|
||||
const RequestSubMenu = new Lang.Class({
|
||||
Name: 'RequestSubMenu',
|
||||
Extends: PopupMenu.PopupSubMenuMenuItem,
|
||||
|
||||
_init: function() {
|
||||
this.parent('');
|
||||
this._requestOpen = false;
|
||||
},
|
||||
|
||||
_setOpenState: function(open) {
|
||||
this.emit('request-open', open);
|
||||
this._requestOpen = open;
|
||||
},
|
||||
|
||||
_getOpenState: function() {
|
||||
return this._requestOpen;
|
||||
},
|
||||
});
|
||||
|
||||
const RemoteMenuSubmenuItemMapper = new Lang.Class({
|
||||
Name: 'RemoteMenuSubmenuItemMapper',
|
||||
|
||||
_init: function(trackerItem) {
|
||||
this._trackerItem = trackerItem;
|
||||
this.menuItem = new RequestSubMenu();
|
||||
this._trackerItem.connect('notify::label', Lang.bind(this, this._updateLabel));
|
||||
this._updateLabel();
|
||||
|
||||
this._tracker = Shell.MenuTracker.new_for_item_submenu(this._trackerItem,
|
||||
_insertItem.bind(null, this.menuItem.menu),
|
||||
_removeItem.bind(null, this.menuItem.menu));
|
||||
|
||||
this.menuItem.connect('request-open', Lang.bind(this, function(menu, open) {
|
||||
this._trackerItem.request_submenu_shown(open);
|
||||
}));
|
||||
|
||||
this._trackerItem.connect('notify::submenu-shown', Lang.bind(this, function() {
|
||||
this.menuItem.setSubmenuShown(this._trackerItem.get_submenu_shown());
|
||||
}));
|
||||
|
||||
this.menuItem.connect('destroy', function() {
|
||||
trackerItem.run_dispose();
|
||||
});
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this._tracker.destroy();
|
||||
this.parent();
|
||||
},
|
||||
|
||||
_updateLabel: function() {
|
||||
this.menuItem.label.text = stripMnemonics(this._trackerItem.label);
|
||||
},
|
||||
});
|
||||
|
||||
const RemoteMenuItemMapper = new Lang.Class({
|
||||
Name: 'RemoteMenuItemMapper',
|
||||
|
||||
_init: function(trackerItem) {
|
||||
this._trackerItem = trackerItem;
|
||||
|
||||
this.menuItem = new PopupMenu.PopupBaseMenuItem();
|
||||
this._label = new St.Label();
|
||||
this.menuItem.actor.add_child(this._label);
|
||||
this.menuItem.actor.label_actor = this._label;
|
||||
|
||||
this.menuItem.connect('activate', Lang.bind(this, function() {
|
||||
this._trackerItem.activated();
|
||||
}));
|
||||
|
||||
this._trackerItem.bind_property('visible', this.menuItem.actor, 'visible', GObject.BindingFlags.SYNC_CREATE);
|
||||
|
||||
this._trackerItem.connect('notify::label', Lang.bind(this, this._updateLabel));
|
||||
this._trackerItem.connect('notify::sensitive', Lang.bind(this, this._updateSensitivity));
|
||||
this._trackerItem.connect('notify::role', Lang.bind(this, this._updateRole));
|
||||
this._trackerItem.connect('notify::toggled', Lang.bind(this, this._updateDecoration));
|
||||
|
||||
this._updateLabel();
|
||||
this._updateSensitivity();
|
||||
this._updateRole();
|
||||
|
||||
this.menuItem.connect('destroy', function() {
|
||||
trackerItem.run_dispose();
|
||||
});
|
||||
},
|
||||
|
||||
_updateLabel: function() {
|
||||
this._label.text = stripMnemonics(this._trackerItem.label);
|
||||
},
|
||||
|
||||
_updateSensitivity: function() {
|
||||
this.menuItem.setSensitive(this._trackerItem.sensitive);
|
||||
},
|
||||
|
||||
_updateDecoration: function() {
|
||||
let ornamentForRole = {};
|
||||
ornamentForRole[ShellMenu.MenuTrackerItemRole.RADIO] = PopupMenu.Ornament.DOT;
|
||||
ornamentForRole[ShellMenu.MenuTrackerItemRole.CHECK] = PopupMenu.Ornament.CHECK;
|
||||
|
||||
let ornament = PopupMenu.Ornament.NONE;
|
||||
if (this._trackerItem.toggled)
|
||||
ornament = ornamentForRole[this._trackerItem.role];
|
||||
|
||||
this.menuItem.setOrnament(ornament);
|
||||
},
|
||||
|
||||
_updateRole: function() {
|
||||
let a11yRoles = {};
|
||||
a11yRoles[ShellMenu.MenuTrackerItemRole.NORMAL] = Atk.Role.MENU_ITEM;
|
||||
a11yRoles[ShellMenu.MenuTrackerItemRole.RADIO] = Atk.Role.RADIO_MENU_ITEM;
|
||||
a11yRoles[ShellMenu.MenuTrackerItemRole.CHECK] = Atk.Role.CHECK_MENU_ITEM;
|
||||
|
||||
let a11yRole = a11yRoles[this._trackerItem.role];
|
||||
this.menuItem.actor.accessible_role = a11yRole;
|
||||
|
||||
this._updateDecoration();
|
||||
},
|
||||
});
|
||||
|
||||
const RemoteMenu = new Lang.Class({
|
||||
Name: 'RemoteMenu',
|
||||
Extends: PopupMenu.PopupMenu,
|
||||
|
||||
_init: function(sourceActor, model, actionGroup) {
|
||||
this.parent(sourceActor, 0.0, St.Side.TOP);
|
||||
|
||||
this._model = model;
|
||||
this._actionGroup = actionGroup;
|
||||
this._tracker = Shell.MenuTracker.new(this._actionGroup,
|
||||
this._model,
|
||||
null, /* action namespace */
|
||||
_insertItem.bind(null, this),
|
||||
_removeItem.bind(null, this));
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this._tracker.destroy();
|
||||
this.parent();
|
||||
},
|
||||
});
|
@ -1,11 +1,12 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const GdkPixbuf = imports.gi.GdkPixbuf;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const FileUtils = imports.misc.fileUtils;
|
||||
const Search = imports.ui.search;
|
||||
|
||||
const KEY_FILE_GROUP = 'Shell Search Provider';
|
||||
@ -29,148 +30,159 @@ const SearchProviderIface = <interface name="org.gnome.Shell.SearchProvider">
|
||||
</method>
|
||||
</interface>;
|
||||
|
||||
const SearchProvider2Iface = <interface name="org.gnome.Shell.SearchProvider2">
|
||||
<method name="GetInitialResultSet">
|
||||
<arg type="as" direction="in" />
|
||||
<arg type="as" direction="out" />
|
||||
</method>
|
||||
<method name="GetSubsearchResultSet">
|
||||
<arg type="as" direction="in" />
|
||||
<arg type="as" direction="in" />
|
||||
<arg type="as" direction="out" />
|
||||
</method>
|
||||
<method name="GetResultMetas">
|
||||
<arg type="as" direction="in" />
|
||||
<arg type="aa{sv}" direction="out" />
|
||||
</method>
|
||||
<method name="ActivateResult">
|
||||
<arg type="s" direction="in" />
|
||||
<arg type="as" direction="in" />
|
||||
<arg type="u" direction="in" />
|
||||
</method>
|
||||
<method name="LaunchSearch">
|
||||
<arg type="as" direction="in" />
|
||||
<arg type="u" direction="in" />
|
||||
</method>
|
||||
</interface>;
|
||||
|
||||
var SearchProviderProxy = Gio.DBusProxy.makeProxyWrapper(SearchProviderIface);
|
||||
var SearchProvider2Proxy = Gio.DBusProxy.makeProxyWrapper(SearchProvider2Iface);
|
||||
|
||||
function loadRemoteSearchProviders(addProviderCallback) {
|
||||
let loadState = { loadedProviders: [],
|
||||
objectPaths: {},
|
||||
numLoading: 0,
|
||||
addProviderCallback: addProviderCallback };
|
||||
let objectPaths = {};
|
||||
let loadedProviders = [];
|
||||
|
||||
let dataDirs = GLib.get_system_data_dirs();
|
||||
for (let i = 0; i < dataDirs.length; i++) {
|
||||
let path = GLib.build_filenamev([dataDirs[i], 'gnome-shell', 'search-providers']);
|
||||
let dir = Gio.file_new_for_path(path);
|
||||
function loadRemoteSearchProvider(file) {
|
||||
let keyfile = new GLib.KeyFile();
|
||||
let path = file.get_path();
|
||||
|
||||
dir.query_info_async('standard:type', Gio.FileQueryInfoFlags.NONE,
|
||||
GLib.PRIORITY_DEFAULT, null,
|
||||
function(object, res) {
|
||||
let exists = false;
|
||||
try {
|
||||
object.query_info_finish(res);
|
||||
exists = true;
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
if (!exists)
|
||||
return;
|
||||
|
||||
loadState.numLoading++;
|
||||
loadRemoteSearchProvidersFromDir(dir, loadState);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function loadRemoteSearchProvidersFromDir(dir, loadState) {
|
||||
let dirPath = dir.get_path();
|
||||
FileUtils.listDirAsync(dir, Lang.bind(this, function(files) {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
let keyfile = new GLib.KeyFile();
|
||||
let path = GLib.build_filenamev([dirPath, files[i].get_name()]);
|
||||
|
||||
try {
|
||||
keyfile.load_from_file(path, 0);
|
||||
} catch(e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!keyfile.has_group(KEY_FILE_GROUP))
|
||||
continue;
|
||||
|
||||
let remoteProvider;
|
||||
try {
|
||||
let group = KEY_FILE_GROUP;
|
||||
let busName = keyfile.get_string(group, 'BusName');
|
||||
let objectPath = keyfile.get_string(group, 'ObjectPath');
|
||||
|
||||
if (loadState.objectPaths[objectPath])
|
||||
continue;
|
||||
|
||||
let appInfo = null;
|
||||
try {
|
||||
let desktopId = keyfile.get_string(group, 'DesktopId');
|
||||
appInfo = Gio.DesktopAppInfo.new(desktopId);
|
||||
} catch (e) {
|
||||
log('Ignoring search provider ' + path + ': missing DesktopId');
|
||||
continue;
|
||||
}
|
||||
|
||||
remoteProvider = new RemoteSearchProvider(appInfo,
|
||||
busName,
|
||||
objectPath);
|
||||
loadState.objectPaths[objectPath] = remoteProvider;
|
||||
loadState.loadedProviders.push(remoteProvider);
|
||||
} catch(e) {
|
||||
log('Failed to add search provider %s: %s'.format(path, e.toString()));
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
keyfile.load_from_file(path, 0);
|
||||
} catch(e) {
|
||||
return;
|
||||
}
|
||||
|
||||
remoteProvidersDirLoaded(loadState);
|
||||
}));
|
||||
if (!keyfile.has_group(KEY_FILE_GROUP))
|
||||
return;
|
||||
|
||||
};
|
||||
let remoteProvider;
|
||||
try {
|
||||
let group = KEY_FILE_GROUP;
|
||||
let busName = keyfile.get_string(group, 'BusName');
|
||||
let objectPath = keyfile.get_string(group, 'ObjectPath');
|
||||
|
||||
function remoteProvidersDirLoaded(loadState) {
|
||||
loadState.numLoading--;
|
||||
if (loadState.numLoading > 0)
|
||||
return;
|
||||
if (objectPaths[objectPath])
|
||||
return;
|
||||
|
||||
let appInfo = null;
|
||||
try {
|
||||
let desktopId = keyfile.get_string(group, 'DesktopId');
|
||||
appInfo = Gio.DesktopAppInfo.new(desktopId);
|
||||
} catch (e) {
|
||||
log('Ignoring search provider ' + path + ': missing DesktopId');
|
||||
return;
|
||||
}
|
||||
|
||||
let version = '1';
|
||||
try {
|
||||
version = keyfile.get_string(group, 'Version');
|
||||
} catch (e) {
|
||||
// ignore error
|
||||
}
|
||||
|
||||
if (version >= 2)
|
||||
remoteProvider = new RemoteSearchProvider2(appInfo, busName, objectPath);
|
||||
else
|
||||
remoteProvider = new RemoteSearchProvider(appInfo, busName, objectPath);
|
||||
|
||||
objectPaths[objectPath] = remoteProvider;
|
||||
loadedProviders.push(remoteProvider);
|
||||
} catch(e) {
|
||||
log('Failed to add search provider %s: %s'.format(path, e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
let dataDirs = GLib.get_system_data_dirs();
|
||||
dataDirs.forEach(function(dataDir) {
|
||||
let path = GLib.build_filenamev([dataDir, 'gnome-shell', 'search-providers']);
|
||||
let dir = Gio.File.new_for_path(path);
|
||||
let fileEnum;
|
||||
try {
|
||||
fileEnum = dir.enumerate_children('standard::name,standard::type',
|
||||
Gio.FileQueryInfoFlags.NONE, null);
|
||||
} catch (e) {
|
||||
fileEnum = null;
|
||||
}
|
||||
if (fileEnum != null) {
|
||||
let info;
|
||||
while ((info = fileEnum.next_file(null)))
|
||||
loadRemoteSearchProvider(fileEnum.get_child(info));
|
||||
}
|
||||
});
|
||||
|
||||
let searchSettings = new Gio.Settings({ schema: Search.SEARCH_PROVIDERS_SCHEMA });
|
||||
let sortOrder = searchSettings.get_strv('sort-order');
|
||||
let numSorted = sortOrder.length;
|
||||
|
||||
loadState.loadedProviders.sort(
|
||||
function(providerA, providerB) {
|
||||
let idxA, idxB;
|
||||
let appIdA, appIdB;
|
||||
// Special case gnome-control-center to be always active and always first
|
||||
sortOrder.unshift('gnome-control-center.desktop');
|
||||
|
||||
appIdA = providerA.appInfo.get_id();
|
||||
appIdB = providerB.appInfo.get_id();
|
||||
loadedProviders.sort(function(providerA, providerB) {
|
||||
let idxA, idxB;
|
||||
let appIdA, appIdB;
|
||||
|
||||
idxA = sortOrder.indexOf(appIdA);
|
||||
idxB = sortOrder.indexOf(appIdB);
|
||||
appIdA = providerA.appInfo.get_id();
|
||||
appIdB = providerB.appInfo.get_id();
|
||||
|
||||
// if no provider is found in the order, use alphabetical order
|
||||
if ((idxA == -1) && (idxB == -1))
|
||||
return GLib.utf8_collate(providerA.title, providerB.title);
|
||||
idxA = sortOrder.indexOf(appIdA);
|
||||
idxB = sortOrder.indexOf(appIdB);
|
||||
|
||||
if (numSorted > 1) {
|
||||
// if providerA is the last, it goes after everything
|
||||
if ((idxA + 1) == numSorted)
|
||||
return 1;
|
||||
// if providerB is the last, it goes after everything
|
||||
else if ((idxB + 1) == numSorted)
|
||||
return -1;
|
||||
}
|
||||
// if no provider is found in the order, use alphabetical order
|
||||
if ((idxA == -1) && (idxB == -1)) {
|
||||
let nameA = providerA.appInfo.get_name();
|
||||
let nameB = providerB.appInfo.get_name();
|
||||
|
||||
// if providerA isn't found, it's sorted after providerB
|
||||
if (idxA == -1)
|
||||
return 1;
|
||||
return GLib.utf8_collate(nameA, nameB);
|
||||
}
|
||||
|
||||
// if providerB isn't found, it's sorted after providerA
|
||||
if (idxB == -1)
|
||||
return -1;
|
||||
// if providerA isn't found, it's sorted after providerB
|
||||
if (idxA == -1)
|
||||
return 1;
|
||||
|
||||
// finally, if both providers are found, return their order in the list
|
||||
return (idxA - idxB);
|
||||
});
|
||||
// if providerB isn't found, it's sorted after providerA
|
||||
if (idxB == -1)
|
||||
return -1;
|
||||
|
||||
loadState.loadedProviders.forEach(
|
||||
function(provider) {
|
||||
loadState.addProviderCallback(provider);
|
||||
});
|
||||
// finally, if both providers are found, return their order in the list
|
||||
return (idxA - idxB);
|
||||
});
|
||||
|
||||
loadedProviders.forEach(addProviderCallback);
|
||||
}
|
||||
|
||||
const RemoteSearchProvider = new Lang.Class({
|
||||
Name: 'RemoteSearchProvider',
|
||||
Extends: Search.SearchProvider,
|
||||
|
||||
_init: function(appInfo, dbusName, dbusPath) {
|
||||
this._proxy = new SearchProviderProxy(Gio.DBus.session,
|
||||
dbusName, dbusPath, Lang.bind(this, this._onProxyConstructed));
|
||||
_init: function(appInfo, dbusName, dbusPath, proxyType) {
|
||||
if (!proxyType)
|
||||
proxyType = SearchProviderProxy;
|
||||
|
||||
this.proxy = new proxyType(Gio.DBus.session,
|
||||
dbusName, dbusPath, Lang.bind(this, this._onProxyConstructed));
|
||||
|
||||
this.appInfo = appInfo;
|
||||
this.id = appInfo.get_id();
|
||||
this.isRemoteProvider = true;
|
||||
|
||||
this.parent(appInfo.get_name().toUpperCase(), appInfo, true);
|
||||
this._cancellable = new Gio.Cancellable();
|
||||
},
|
||||
|
||||
@ -179,38 +191,38 @@ const RemoteSearchProvider = new Lang.Class({
|
||||
},
|
||||
|
||||
createIcon: function(size, meta) {
|
||||
if (meta['gicon']) {
|
||||
return new St.Icon({ gicon: Gio.icon_new_for_string(meta['gicon']),
|
||||
icon_size: size });
|
||||
let gicon;
|
||||
if (meta['icon']) {
|
||||
gicon = Gio.icon_deserialize(meta['icon']);
|
||||
} else if (meta['gicon']) {
|
||||
gicon = Gio.icon_new_for_string(meta['gicon']);
|
||||
} else if (meta['icon-data']) {
|
||||
let [width, height, rowStride, hasAlpha,
|
||||
bitsPerSample, nChannels, data] = meta['icon-data'];
|
||||
let textureCache = St.TextureCache.get_default();
|
||||
return textureCache.load_from_raw(data, hasAlpha,
|
||||
width, height, rowStride, size);
|
||||
gicon = Shell.util_create_pixbuf_from_data(data, GdkPixbuf.Colorspace.RGB, hasAlpha,
|
||||
bitsPerSample, width, height, rowStride);
|
||||
}
|
||||
|
||||
// Ugh, but we want to fall back to something ...
|
||||
return new St.Icon({ icon_name: 'text-x-generic',
|
||||
return new St.Icon({ gicon: gicon,
|
||||
icon_size: size });
|
||||
},
|
||||
|
||||
_getResultsFinished: function(results, error) {
|
||||
if (error)
|
||||
return;
|
||||
this.searchSystem.pushResults(this, results[0]);
|
||||
this.searchSystem.setResults(this, results[0]);
|
||||
},
|
||||
|
||||
getInitialResultSet: function(terms) {
|
||||
this._cancellable.cancel();
|
||||
this._cancellable.reset();
|
||||
try {
|
||||
this._proxy.GetInitialResultSetRemote(terms,
|
||||
Lang.bind(this, this._getResultsFinished),
|
||||
this._cancellable);
|
||||
this.proxy.GetInitialResultSetRemote(terms,
|
||||
Lang.bind(this, this._getResultsFinished),
|
||||
this._cancellable);
|
||||
} catch(e) {
|
||||
log('Error calling GetInitialResultSet for provider %s: %s'.format( this.title, e.toString()));
|
||||
this.searchSystem.pushResults(this, []);
|
||||
log('Error calling GetInitialResultSet for provider %s: %s'.format(this.id, e.toString()));
|
||||
this.searchSystem.setResults(this, []);
|
||||
}
|
||||
},
|
||||
|
||||
@ -218,12 +230,12 @@ const RemoteSearchProvider = new Lang.Class({
|
||||
this._cancellable.cancel();
|
||||
this._cancellable.reset();
|
||||
try {
|
||||
this._proxy.GetSubsearchResultSetRemote(previousResults, newTerms,
|
||||
Lang.bind(this, this._getResultsFinished),
|
||||
this._cancellable);
|
||||
this.proxy.GetSubsearchResultSetRemote(previousResults, newTerms,
|
||||
Lang.bind(this, this._getResultsFinished),
|
||||
this._cancellable);
|
||||
} catch(e) {
|
||||
log('Error calling GetSubsearchResultSet for provider %s: %s'.format(this.title, e.toString()));
|
||||
this.searchSystem.pushResults(this, []);
|
||||
log('Error calling GetSubsearchResultSet for provider %s: %s'.format(this.id, e.toString()));
|
||||
this.searchSystem.setResults(this, []);
|
||||
}
|
||||
},
|
||||
|
||||
@ -235,10 +247,15 @@ const RemoteSearchProvider = new Lang.Class({
|
||||
let metas = results[0];
|
||||
let resultMetas = [];
|
||||
for (let i = 0; i < metas.length; i++) {
|
||||
for (let prop in metas[i])
|
||||
metas[i][prop] = metas[i][prop].deep_unpack();
|
||||
for (let prop in metas[i]) {
|
||||
// we can use the serialized icon variant directly
|
||||
if (prop != 'icon')
|
||||
metas[i][prop] = metas[i][prop].deep_unpack();
|
||||
}
|
||||
|
||||
resultMetas.push({ id: metas[i]['id'],
|
||||
name: metas[i]['name'],
|
||||
description: metas[i]['description'],
|
||||
createIcon: Lang.bind(this,
|
||||
this.createIcon, metas[i]) });
|
||||
}
|
||||
@ -249,18 +266,42 @@ const RemoteSearchProvider = new Lang.Class({
|
||||
this._cancellable.cancel();
|
||||
this._cancellable.reset();
|
||||
try {
|
||||
this._proxy.GetResultMetasRemote(ids,
|
||||
Lang.bind(this, this._getResultMetasFinished, callback),
|
||||
this._cancellable);
|
||||
this.proxy.GetResultMetasRemote(ids,
|
||||
Lang.bind(this, this._getResultMetasFinished, callback),
|
||||
this._cancellable);
|
||||
} catch(e) {
|
||||
log('Error calling GetResultMetas for provider %s: %s'.format(this.title, e.toString()));
|
||||
log('Error calling GetResultMetas for provider %s: %s'.format(this.id, e.toString()));
|
||||
callback([]);
|
||||
}
|
||||
},
|
||||
|
||||
activateResult: function(id) {
|
||||
this._proxy.ActivateResultRemote(id);
|
||||
this.proxy.ActivateResultRemote(id);
|
||||
},
|
||||
|
||||
launchSearch: function(terms) {
|
||||
// the provider is not compatible with the new version of the interface, launch
|
||||
// the app itself but warn so we can catch the error in logs
|
||||
log('Search provider ' + this.appInfo.get_id() + ' does not implement LaunchSearch');
|
||||
this.appInfo.launch([], global.create_app_launch_context());
|
||||
}
|
||||
});
|
||||
|
||||
const RemoteSearchProvider2 = new Lang.Class({
|
||||
Name: 'RemoteSearchProvider2',
|
||||
Extends: RemoteSearchProvider,
|
||||
|
||||
_init: function(appInfo, dbusName, dbusPath) {
|
||||
this.parent(appInfo, dbusName, dbusPath, SearchProvider2Proxy);
|
||||
|
||||
this.canLaunchSearch = true;
|
||||
},
|
||||
|
||||
activateResult: function(id, terms) {
|
||||
this.proxy.ActivateResultRemote(id, terms, global.get_current_time());
|
||||
},
|
||||
|
||||
launchSearch: function(terms) {
|
||||
this.proxy.LaunchSearchRemote(terms, global.get_current_time());
|
||||
}
|
||||
});
|
||||
|
@ -30,144 +30,13 @@ const EXEC_ARG_KEY = 'exec-arg';
|
||||
|
||||
const DIALOG_GROW_TIME = 0.1;
|
||||
|
||||
const CommandCompleter = new Lang.Class({
|
||||
Name: 'CommandCompleter',
|
||||
|
||||
_init : function() {
|
||||
this._changedCount = 0;
|
||||
this._paths = GLib.getenv('PATH').split(':');
|
||||
this._paths.push(GLib.get_home_dir());
|
||||
this._valid = false;
|
||||
this._updateInProgress = false;
|
||||
this._childs = new Array(this._paths.length);
|
||||
this._monitors = new Array(this._paths.length);
|
||||
for (let i = 0; i < this._paths.length; i++) {
|
||||
this._childs[i] = [];
|
||||
let file = Gio.file_new_for_path(this._paths[i]);
|
||||
let info;
|
||||
try {
|
||||
info = file.query_info(Gio.FILE_ATTRIBUTE_STANDARD_TYPE, Gio.FileQueryInfoFlags.NONE, null);
|
||||
} catch (e) {
|
||||
// FIXME catchall
|
||||
this._paths[i] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (info.get_attribute_uint32(Gio.FILE_ATTRIBUTE_STANDARD_TYPE) != Gio.FileType.DIRECTORY)
|
||||
continue;
|
||||
|
||||
this._paths[i] = file.get_path();
|
||||
this._monitors[i] = file.monitor_directory(Gio.FileMonitorFlags.NONE, null);
|
||||
if (this._monitors[i] != null) {
|
||||
this._monitors[i].connect('changed', Lang.bind(this, this._onChanged));
|
||||
}
|
||||
}
|
||||
this._paths = this._paths.filter(function(a) {
|
||||
return a != null;
|
||||
});
|
||||
this._update(0);
|
||||
},
|
||||
|
||||
update : function() {
|
||||
if (this._valid)
|
||||
return;
|
||||
this._update(0);
|
||||
},
|
||||
|
||||
_update : function(i) {
|
||||
if (i == 0 && this._updateInProgress)
|
||||
return;
|
||||
this._updateInProgress = true;
|
||||
this._changedCount = 0;
|
||||
this._i = i;
|
||||
if (i >= this._paths.length) {
|
||||
this._valid = true;
|
||||
this._updateInProgress = false;
|
||||
return;
|
||||
}
|
||||
let file = Gio.file_new_for_path(this._paths[i]);
|
||||
this._childs[this._i] = [];
|
||||
FileUtils.listDirAsync(file, Lang.bind(this, function (files) {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
this._childs[this._i].push(files[i].get_name());
|
||||
}
|
||||
this._update(this._i + 1);
|
||||
}));
|
||||
},
|
||||
|
||||
_onChanged : function(m, f, of, type) {
|
||||
if (!this._valid)
|
||||
return;
|
||||
let path = f.get_parent().get_path();
|
||||
let k = undefined;
|
||||
for (let i = 0; i < this._paths.length; i++) {
|
||||
if (this._paths[i] == path)
|
||||
k = i;
|
||||
}
|
||||
if (k === undefined) {
|
||||
return;
|
||||
}
|
||||
if (type == Gio.FileMonitorEvent.CREATED) {
|
||||
this._childs[k].push(f.get_basename());
|
||||
}
|
||||
if (type == Gio.FileMonitorEvent.DELETED) {
|
||||
this._changedCount++;
|
||||
if (this._changedCount > MAX_FILE_DELETED_BEFORE_INVALID) {
|
||||
this._valid = false;
|
||||
}
|
||||
let name = f.get_basename();
|
||||
this._childs[k] = this._childs[k].filter(function(e) {
|
||||
return e != name;
|
||||
});
|
||||
}
|
||||
if (type == Gio.FileMonitorEvent.UNMOUNTED) {
|
||||
this._childs[k] = [];
|
||||
}
|
||||
},
|
||||
|
||||
getCompletion: function(text) {
|
||||
let common = '';
|
||||
let notInit = true;
|
||||
if (!this._valid) {
|
||||
this._update(0);
|
||||
return common;
|
||||
}
|
||||
function _getCommon(s1, s2) {
|
||||
let k = 0;
|
||||
for (; k < s1.length && k < s2.length; k++) {
|
||||
if (s1[k] != s2[k])
|
||||
break;
|
||||
}
|
||||
if (k == 0)
|
||||
return '';
|
||||
return s1.substr(0, k);
|
||||
}
|
||||
function _hasPrefix(s1, prefix) {
|
||||
return s1.indexOf(prefix) == 0;
|
||||
}
|
||||
for (let i = 0; i < this._childs.length; i++) {
|
||||
for (let k = 0; k < this._childs[i].length; k++) {
|
||||
if (!_hasPrefix(this._childs[i][k], text))
|
||||
continue;
|
||||
if (notInit) {
|
||||
common = this._childs[i][k];
|
||||
notInit = false;
|
||||
}
|
||||
common = _getCommon(common, this._childs[i][k]);
|
||||
}
|
||||
}
|
||||
if (common.length)
|
||||
return common.substr(text.length);
|
||||
return common;
|
||||
}
|
||||
});
|
||||
|
||||
const RunDialog = new Lang.Class({
|
||||
Name: 'RunDialog',
|
||||
Extends: ModalDialog.ModalDialog,
|
||||
|
||||
_init : function() {
|
||||
this.parent({ styleClass: 'run-dialog' });
|
||||
this.parent({ styleClass: 'run-dialog',
|
||||
destroyOnClose: false });
|
||||
|
||||
this._lockdownSettings = new Gio.Settings({ schema: LOCKDOWN_SCHEMA });
|
||||
this._terminalSettings = new Gio.Settings({ schema: TERMINAL_SCHEMA });
|
||||
@ -242,8 +111,6 @@ const RunDialog = new Lang.Class({
|
||||
key: Clutter.Escape }]);
|
||||
|
||||
this._pathCompleter = new Gio.FilenameCompleter();
|
||||
this._commandCompleter = new CommandCompleter();
|
||||
this._group.connect('notify::visible', Lang.bind(this._commandCompleter, this._commandCompleter.update));
|
||||
|
||||
this._history = new History.HistoryManager({ gsettingsKey: HISTORY_KEY,
|
||||
entry: this._entryText });
|
||||
@ -259,18 +126,6 @@ const RunDialog = new Lang.Class({
|
||||
|
||||
return true;
|
||||
}
|
||||
if (symbol == Clutter.slash) {
|
||||
// Need preload data before get completion. GFilenameCompleter load content of parent directory.
|
||||
// Parent directory for /usr/include/ is /usr/. So need to add fake name('a').
|
||||
let text = o.get_text().concat('/a');
|
||||
let prefix;
|
||||
if (text.lastIndexOf(' ') == -1)
|
||||
prefix = text;
|
||||
else
|
||||
prefix = text.substr(text.lastIndexOf(' ') + 1);
|
||||
this._getCompletion(prefix);
|
||||
return false;
|
||||
}
|
||||
if (symbol == Clutter.Tab) {
|
||||
let text = o.get_text();
|
||||
let prefix;
|
||||
@ -282,8 +137,6 @@ const RunDialog = new Lang.Class({
|
||||
if (postfix != null && postfix.length > 0) {
|
||||
o.insert_text(postfix, -1);
|
||||
o.set_cursor_position(text.length + postfix.length);
|
||||
if (postfix[postfix.length - 1] == '/')
|
||||
this._getCompletion(text + postfix + 'a');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -291,11 +144,53 @@ const RunDialog = new Lang.Class({
|
||||
}));
|
||||
},
|
||||
|
||||
_getCommandCompletion: function(text) {
|
||||
function _getCommon(s1, s2) {
|
||||
if (s1 == null)
|
||||
return s2;
|
||||
|
||||
let k = 0;
|
||||
for (; k < s1.length && k < s2.length; k++) {
|
||||
if (s1[k] != s2[k])
|
||||
break;
|
||||
}
|
||||
if (k == 0)
|
||||
return '';
|
||||
return s1.substr(0, k);
|
||||
}
|
||||
|
||||
let paths = GLib.getenv('PATH').split(':');
|
||||
paths.push(GLib.get_home_dir());
|
||||
let someResults = paths.map(function(path) {
|
||||
let results = [];
|
||||
try {
|
||||
let file = Gio.File.new_for_path(path);
|
||||
let fileEnum = file.enumerate_children('standard::name', Gio.FileQueryInfoFlags.NONE, null);
|
||||
let info;
|
||||
while ((info = fileEnum.next_file(null))) {
|
||||
let name = info.get_name();
|
||||
if (name.slice(0, text.length) == text)
|
||||
results.push(name);
|
||||
}
|
||||
} catch (e if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND) &&
|
||||
!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_DIRECTORY))) {
|
||||
log(e);
|
||||
} finally {
|
||||
return results;
|
||||
}
|
||||
});
|
||||
let results = someResults.reduce(function(a, b) {
|
||||
return a.concat(b);
|
||||
}, []);
|
||||
let common = results.reduce(_getCommon, null);
|
||||
return common.substr(text.length);
|
||||
},
|
||||
|
||||
_getCompletion : function(text) {
|
||||
if (text.indexOf('/') != -1) {
|
||||
return this._pathCompleter.get_completion_suffix(text);
|
||||
} else {
|
||||
return this._commandCompleter.getCompletion(text);
|
||||
return this._getCommandCompletion(text);
|
||||
}
|
||||
},
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
149
js/ui/screencast.js
Normal file
149
js/ui/screencast.js
Normal file
@ -0,0 +1,149 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const Hash = imports.misc.hash;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const ScreencastIface = <interface name="org.gnome.Shell.Screencast">
|
||||
<method name="Screencast">
|
||||
<arg type="s" direction="in" name="file_template"/>
|
||||
<arg type="a{sv}" direction="in" name="options"/>
|
||||
<arg type="b" direction="out" name="success"/>
|
||||
<arg type="s" direction="out" name="filename_used"/>
|
||||
</method>
|
||||
<method name="ScreencastArea">
|
||||
<arg type="i" direction="in" name="x"/>
|
||||
<arg type="i" direction="in" name="y"/>
|
||||
<arg type="i" direction="in" name="width"/>
|
||||
<arg type="i" direction="in" name="height"/>
|
||||
<arg type="s" direction="in" name="file_template"/>
|
||||
<arg type="a{sv}" direction="in" name="options"/>
|
||||
<arg type="b" direction="out" name="success"/>
|
||||
<arg type="s" direction="out" name="filename_used"/>
|
||||
</method>
|
||||
<method name="StopScreencast">
|
||||
<arg type="b" direction="out" name="success"/>
|
||||
</method>
|
||||
</interface>;
|
||||
|
||||
const ScreencastService = new Lang.Class({
|
||||
Name: 'ScreencastService',
|
||||
|
||||
_init: function() {
|
||||
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(ScreencastIface, this);
|
||||
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell/Screencast');
|
||||
|
||||
Gio.DBus.session.own_name('org.gnome.Shell.Screencast', Gio.BusNameOwnerFlags.REPLACE, null, null);
|
||||
|
||||
this._recorders = new Hash.Map();
|
||||
|
||||
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
|
||||
},
|
||||
|
||||
get isRecording() {
|
||||
return this._recorders.size() > 0;
|
||||
},
|
||||
|
||||
_ensureRecorderForSender: function(sender) {
|
||||
let recorder = this._recorders.get(sender);
|
||||
if (!recorder) {
|
||||
recorder = new Shell.Recorder({ stage: global.stage,
|
||||
screen: global.screen });
|
||||
recorder._watchNameId =
|
||||
Gio.bus_watch_name(Gio.BusType.SESSION, sender, 0, null,
|
||||
Lang.bind(this, this._onNameVanished));
|
||||
this._recorders.set(sender, recorder);
|
||||
this.emit('updated');
|
||||
}
|
||||
return recorder;
|
||||
},
|
||||
|
||||
_sessionUpdated: function() {
|
||||
if (Main.sessionMode.allowScreencast)
|
||||
return;
|
||||
|
||||
for (let sender in this._recorders.keys())
|
||||
this._recorders.delete(sender);
|
||||
this.emit('updated');
|
||||
},
|
||||
|
||||
_onNameVanished: function(connection, name) {
|
||||
this._stopRecordingForSender(name);
|
||||
},
|
||||
|
||||
_stopRecordingForSender: function(sender) {
|
||||
let recorder = this._recorders.get(sender);
|
||||
if (!recorder)
|
||||
return false;
|
||||
|
||||
Gio.bus_unwatch_name(recorder._watchNameId);
|
||||
recorder.close();
|
||||
this._recorders.delete(sender);
|
||||
this.emit('updated');
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_applyOptionalParameters: function(recorder, options) {
|
||||
for (let option in options)
|
||||
options[option] = options[option].deep_unpack();
|
||||
|
||||
if (options['pipeline'])
|
||||
recorder.set_pipeline(options['pipeline']);
|
||||
if (options['framerate'])
|
||||
recorder.set_framerate(options['framerate']);
|
||||
if (options['draw-cursor'])
|
||||
recorder.set_draw_cursor(options['draw-cursor']);
|
||||
},
|
||||
|
||||
ScreencastAsync: function(params, invocation) {
|
||||
let returnValue = [false, ''];
|
||||
if (!Main.sessionMode.allowScreencast)
|
||||
invocation.return_value(GLib.Variant.new('(bs)', returnValue));
|
||||
|
||||
let sender = invocation.get_sender();
|
||||
let recorder = this._ensureRecorderForSender(sender);
|
||||
if (!recorder.is_recording()) {
|
||||
let [fileTemplate, options] = params;
|
||||
|
||||
recorder.set_file_template(fileTemplate);
|
||||
this._applyOptionalParameters(recorder, options);
|
||||
let [success, fileName] = recorder.record();
|
||||
returnValue = [success, fileName ? fileName : ''];
|
||||
}
|
||||
|
||||
invocation.return_value(GLib.Variant.new('(bs)', returnValue));
|
||||
},
|
||||
|
||||
ScreencastAreaAsync: function(params, invocation) {
|
||||
let returnValue = [false, ''];
|
||||
if (!Main.sessionMode.allowScreencast)
|
||||
invocation.return_value(GLib.Variant.new('(bs)', returnValue));
|
||||
|
||||
let sender = invocation.get_sender();
|
||||
let recorder = this._ensureRecorderForSender(sender);
|
||||
|
||||
if (!recorder.is_recording()) {
|
||||
let [x, y, width, height, fileTemplate, options] = params;
|
||||
|
||||
recorder.set_file_template(fileTemplate);
|
||||
recorder.set_area(x, y, width, height);
|
||||
this._applyOptionalParameters(recorder, options);
|
||||
let [success, fileName] = recorder.record();
|
||||
returnValue = [success, fileName ? fileName : ''];
|
||||
}
|
||||
|
||||
invocation.return_value(GLib.Variant.new('(bs)', returnValue));
|
||||
},
|
||||
|
||||
StopScreencastAsync: function(params, invocation) {
|
||||
let success = this._stopRecordingForSender(invocation.get_sender());
|
||||
invocation.return_value(GLib.Variant.new('(b)', [success]));
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(ScreencastService.prototype);
|
278
js/ui/screenshot.js
Normal file
278
js/ui/screenshot.js
Normal file
@ -0,0 +1,278 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Lang = imports.lang;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Lightbox = imports.ui.lightbox;
|
||||
const Main = imports.ui.main;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
const ScreenshotIface = <interface name="org.gnome.Shell.Screenshot">
|
||||
<method name="ScreenshotArea">
|
||||
<arg type="i" direction="in" name="x"/>
|
||||
<arg type="i" direction="in" name="y"/>
|
||||
<arg type="i" direction="in" name="width"/>
|
||||
<arg type="i" direction="in" name="height"/>
|
||||
<arg type="b" direction="in" name="flash"/>
|
||||
<arg type="s" direction="in" name="filename"/>
|
||||
<arg type="b" direction="out" name="success"/>
|
||||
<arg type="s" direction="out" name="filename_used"/>
|
||||
</method>
|
||||
<method name="ScreenshotWindow">
|
||||
<arg type="b" direction="in" name="include_frame"/>
|
||||
<arg type="b" direction="in" name="include_cursor"/>
|
||||
<arg type="b" direction="in" name="flash"/>
|
||||
<arg type="s" direction="in" name="filename"/>
|
||||
<arg type="b" direction="out" name="success"/>
|
||||
<arg type="s" direction="out" name="filename_used"/>
|
||||
</method>
|
||||
<method name="Screenshot">
|
||||
<arg type="b" direction="in" name="include_cursor"/>
|
||||
<arg type="b" direction="in" name="flash"/>
|
||||
<arg type="s" direction="in" name="filename"/>
|
||||
<arg type="b" direction="out" name="success"/>
|
||||
<arg type="s" direction="out" name="filename_used"/>
|
||||
</method>
|
||||
<method name="SelectArea">
|
||||
<arg type="i" direction="out" name="x"/>
|
||||
<arg type="i" direction="out" name="y"/>
|
||||
<arg type="i" direction="out" name="width"/>
|
||||
<arg type="i" direction="out" name="height"/>
|
||||
</method>
|
||||
<method name="FlashArea">
|
||||
<arg type="i" direction="in" name="x"/>
|
||||
<arg type="i" direction="in" name="y"/>
|
||||
<arg type="i" direction="in" name="width"/>
|
||||
<arg type="i" direction="in" name="height"/>
|
||||
</method>
|
||||
</interface>;
|
||||
|
||||
const ScreenshotService = new Lang.Class({
|
||||
Name: 'ScreenshotService',
|
||||
|
||||
_init: function() {
|
||||
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(ScreenshotIface, this);
|
||||
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell/Screenshot');
|
||||
|
||||
Gio.DBus.session.own_name('org.gnome.Shell.Screenshot', Gio.BusNameOwnerFlags.REPLACE, null, null);
|
||||
},
|
||||
|
||||
_onScreenshotComplete: function(obj, result, area, filenameUsed, flash, invocation) {
|
||||
if (flash && result) {
|
||||
let flashspot = new Flashspot(area);
|
||||
flashspot.fire();
|
||||
}
|
||||
|
||||
let retval = GLib.Variant.new('(bs)', [result, filenameUsed]);
|
||||
invocation.return_value(retval);
|
||||
},
|
||||
|
||||
ScreenshotAreaAsync : function (params, invocation) {
|
||||
let [x, y, width, height, flash, filename, callback] = params;
|
||||
if (height <= 0 || width <= 0) {
|
||||
invocation.return_error_literal(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED,
|
||||
"Invalid params");
|
||||
return;
|
||||
}
|
||||
let screenshot = new Shell.Screenshot();
|
||||
screenshot.screenshot_area (x, y, width, height, filename,
|
||||
Lang.bind(this, this._onScreenshotComplete,
|
||||
flash, invocation));
|
||||
},
|
||||
|
||||
ScreenshotWindowAsync : function (params, invocation) {
|
||||
let [include_frame, include_cursor, flash, filename] = params;
|
||||
let screenshot = new Shell.Screenshot();
|
||||
screenshot.screenshot_window (include_frame, include_cursor, filename,
|
||||
Lang.bind(this, this._onScreenshotComplete,
|
||||
flash, invocation));
|
||||
},
|
||||
|
||||
ScreenshotAsync : function (params, invocation) {
|
||||
let [include_cursor, flash, filename] = params;
|
||||
let screenshot = new Shell.Screenshot();
|
||||
screenshot.screenshot(include_cursor, filename,
|
||||
Lang.bind(this, this._onScreenshotComplete,
|
||||
flash, invocation));
|
||||
},
|
||||
|
||||
SelectAreaAsync: function (params, invocation) {
|
||||
let selectArea = new SelectArea();
|
||||
selectArea.show();
|
||||
selectArea.connect('finished', Lang.bind(this,
|
||||
function(selectArea, areaRectangle) {
|
||||
if (areaRectangle) {
|
||||
let retval = GLib.Variant.new('(iiii)',
|
||||
[areaRectangle.x, areaRectangle.y,
|
||||
areaRectangle.width, areaRectangle.height]);
|
||||
invocation.return_value(retval);
|
||||
} else {
|
||||
invocation.return_error_literal(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED,
|
||||
"Operation was cancelled");
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
FlashArea: function(x, y, width, height) {
|
||||
let flashspot = new Flashspot({ x : x, y : y, width: width, height: height});
|
||||
flashspot.fire();
|
||||
}
|
||||
});
|
||||
|
||||
const SelectArea = new Lang.Class({
|
||||
Name: 'SelectArea',
|
||||
|
||||
_init: function() {
|
||||
this._startX = -1;
|
||||
this._startY = -1;
|
||||
this._lastX = 0;
|
||||
this._lastY = 0;
|
||||
|
||||
this._initRubberbandColors();
|
||||
|
||||
this._group = new St.Widget({ visible: false,
|
||||
reactive: true,
|
||||
x: 0,
|
||||
y: 0 });
|
||||
Main.uiGroup.add_actor(this._group);
|
||||
|
||||
this._group.connect('button-press-event',
|
||||
Lang.bind(this, this._onButtonPress));
|
||||
this._group.connect('button-release-event',
|
||||
Lang.bind(this, this._onButtonRelease));
|
||||
this._group.connect('key-press-event',
|
||||
Lang.bind(this, this._onKeyPress));
|
||||
this._group.connect('motion-event',
|
||||
Lang.bind(this, this._onMotionEvent));
|
||||
|
||||
let constraint = new Clutter.BindConstraint({ source: global.stage,
|
||||
coordinate: Clutter.BindCoordinate.ALL });
|
||||
this._group.add_constraint(constraint);
|
||||
|
||||
this._rubberband = new Clutter.Rectangle({ color: this._background,
|
||||
has_border: true,
|
||||
border_width: 1,
|
||||
border_color: this._border });
|
||||
this._group.add_actor(this._rubberband);
|
||||
},
|
||||
|
||||
show: function() {
|
||||
if (!Main.pushModal(this._group) || this._group.visible)
|
||||
return;
|
||||
|
||||
global.screen.set_cursor(Meta.Cursor.CROSSHAIR);
|
||||
this._group.visible = true;
|
||||
},
|
||||
|
||||
_initRubberbandColors: function() {
|
||||
function colorFromRGBA(rgba) {
|
||||
return new Clutter.Color({ red: rgba.red * 255,
|
||||
green: rgba.green * 255,
|
||||
blue: rgba.blue * 255,
|
||||
alpha: rgba.alpha * 255 });
|
||||
}
|
||||
|
||||
let path = new Gtk.WidgetPath();
|
||||
path.append_type(Gtk.IconView);
|
||||
|
||||
let context = new Gtk.StyleContext();
|
||||
context.set_path(path);
|
||||
context.add_class('rubberband');
|
||||
|
||||
this._background = colorFromRGBA(context.get_background_color(Gtk.StateFlags.NORMAL));
|
||||
this._border = colorFromRGBA(context.get_border_color(Gtk.StateFlags.NORMAL));
|
||||
},
|
||||
|
||||
_getGeometry: function() {
|
||||
return { x: Math.min(this._startX, this._lastX),
|
||||
y: Math.min(this._startY, this._lastY),
|
||||
width: Math.abs(this._startX - this._lastX),
|
||||
height: Math.abs(this._startY - this._lastY) };
|
||||
},
|
||||
|
||||
_onKeyPress: function(actor, event) {
|
||||
if (event.get_key_symbol() == Clutter.Escape)
|
||||
this._destroy(null, false);
|
||||
|
||||
return;
|
||||
},
|
||||
|
||||
_onMotionEvent: function(actor, event) {
|
||||
if (this._startX == -1 || this._startY == -1)
|
||||
return false;
|
||||
|
||||
[this._lastX, this._lastY] = event.get_coords();
|
||||
let geometry = this._getGeometry();
|
||||
|
||||
this._rubberband.set_position(geometry.x, geometry.y);
|
||||
this._rubberband.set_size(geometry.width, geometry.height);
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
_onButtonPress: function(actor, event) {
|
||||
[this._startX, this._startY] = event.get_coords();
|
||||
this._rubberband.set_position(this._startX, this._startY);
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
_onButtonRelease: function(actor, event) {
|
||||
this._destroy(this._getGeometry(), true);
|
||||
return false;
|
||||
},
|
||||
|
||||
_destroy: function(geometry, fade) {
|
||||
Tweener.addTween(this._group,
|
||||
{ opacity: 0,
|
||||
time: fade ? 0.2 : 0,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this,
|
||||
function() {
|
||||
Main.popModal(this._group);
|
||||
this._group.destroy();
|
||||
global.screen.set_cursor(Meta.Cursor.DEFAULT);
|
||||
|
||||
this.emit('finished', geometry);
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(SelectArea.prototype);
|
||||
|
||||
const FLASHSPOT_ANIMATION_OUT_TIME = 0.5; // seconds
|
||||
|
||||
const Flashspot = new Lang.Class({
|
||||
Name: 'Flashspot',
|
||||
Extends: Lightbox.Lightbox,
|
||||
|
||||
_init: function(area) {
|
||||
this.parent(Main.uiGroup, { inhibitEvents: true,
|
||||
width: area.width,
|
||||
height: area.height });
|
||||
|
||||
this.actor.style_class = 'flashspot';
|
||||
this.actor.set_position(area.x, area.y);
|
||||
},
|
||||
|
||||
fire: function() {
|
||||
this.actor.show();
|
||||
this.actor.opacity = 255;
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 0,
|
||||
time: FLASHSPOT_ANIMATION_OUT_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this, function() {
|
||||
this.destroy();
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
202
js/ui/search.js
202
js/ui/search.js
@ -1,176 +1,10 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Signals = imports.signals;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
const FileUtils = imports.misc.fileUtils;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const SEARCH_PROVIDERS_SCHEMA = 'org.gnome.desktop.search-providers';
|
||||
|
||||
// Not currently referenced by the search API, but
|
||||
// this enumeration can be useful for provider
|
||||
// implementations.
|
||||
const MatchType = {
|
||||
NONE: 0,
|
||||
SUBSTRING: 1,
|
||||
PREFIX: 2
|
||||
};
|
||||
|
||||
const SearchResultDisplay = new Lang.Class({
|
||||
Name: 'SearchResultDisplay',
|
||||
|
||||
_init: function(provider) {
|
||||
this.provider = provider;
|
||||
this.actor = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* renderResults:
|
||||
* @results: List of identifier strings
|
||||
* @terms: List of search term strings
|
||||
*
|
||||
* Display the given search matches which resulted
|
||||
* from the given terms. It's expected that not
|
||||
* all results will fit in the space for the container
|
||||
* actor; in this case, show as many as makes sense
|
||||
* for your result type.
|
||||
*
|
||||
* The terms are useful for search match highlighting.
|
||||
*/
|
||||
renderResults: function(results, terms) {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
/**
|
||||
* clear:
|
||||
* Remove all results from this display.
|
||||
*/
|
||||
clear: function() {
|
||||
this.actor.destroy_all_children();
|
||||
},
|
||||
|
||||
/**
|
||||
* getVisibleResultCount:
|
||||
*
|
||||
* Returns: The number of actors visible.
|
||||
*/
|
||||
getVisibleResultCount: function() {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* SearchProvider:
|
||||
*
|
||||
* Subclass this object to add a new result type
|
||||
* to the search system, then call registerProvider()
|
||||
* in SearchSystem with an instance.
|
||||
* Search is asynchronous and uses the
|
||||
* getInitialResultSet()/getSubsearchResultSet() methods.
|
||||
*/
|
||||
const SearchProvider = new Lang.Class({
|
||||
Name: 'SearchProvider',
|
||||
|
||||
_init: function(title, appInfo, isRemoteProvider) {
|
||||
this.title = title;
|
||||
this.appInfo = appInfo;
|
||||
this.searchSystem = null;
|
||||
this.isRemoteProvider = !!isRemoteProvider;
|
||||
},
|
||||
|
||||
/**
|
||||
* getInitialResultSet:
|
||||
* @terms: Array of search terms, treated as logical AND
|
||||
*
|
||||
* Called when the user first begins a search (most likely
|
||||
* therefore a single term of length one or two), or when
|
||||
* a new term is added.
|
||||
*
|
||||
* Should "return" an array of result identifier strings representing
|
||||
* items which match the given search terms. This
|
||||
* is expected to be a substring match on the metadata for a given
|
||||
* item. Ordering of returned results is up to the discretion of the provider,
|
||||
* but you should follow these heruistics:
|
||||
*
|
||||
* * Put items where the term matches multiple criteria (e.g. name and
|
||||
* description) before single matches
|
||||
* * Put items which match on a prefix before non-prefix substring matches
|
||||
*
|
||||
* We say "return" above, but in order to make the query asynchronous, use
|
||||
* this.searchSystem.pushResults();. The return value should be ignored.
|
||||
*
|
||||
* This function should be fast; do not perform unindexed full-text searches
|
||||
* or network queries.
|
||||
*/
|
||||
getInitialResultSet: function(terms) {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
/**
|
||||
* getSubsearchResultSet:
|
||||
* @previousResults: Array of item identifiers
|
||||
* @newTerms: Updated search terms
|
||||
*
|
||||
* Called when a search is performed which is a "subsearch" of
|
||||
* the previous search; i.e. when every search term has exactly
|
||||
* one corresponding term in the previous search which is a prefix
|
||||
* of the new term.
|
||||
*
|
||||
* This allows search providers to only search through the previous
|
||||
* result set, rather than possibly performing a full re-query.
|
||||
*
|
||||
* Similar to getInitialResultSet, the return value for this will
|
||||
* be ignored; use this.searchSystem.pushResults();.
|
||||
*/
|
||||
getSubsearchResultSet: function(previousResults, newTerms) {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
/**
|
||||
* getResultMetas:
|
||||
* @ids: Result identifier strings
|
||||
*
|
||||
* Call callback with array of objects with 'id', 'name', (both strings) and
|
||||
* 'createIcon' (function(size) returning a Clutter.Texture) properties
|
||||
* with the same number of members as @ids
|
||||
*/
|
||||
getResultMetas: function(ids, callback) {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
/**
|
||||
* createResultActor:
|
||||
* @resultMeta: Object with result metadata
|
||||
* @terms: Array of search terms, should be used for highlighting
|
||||
*
|
||||
* Search providers may optionally override this to render a
|
||||
* particular serch result in a custom fashion. The default
|
||||
* implementation will show the icon next to the name.
|
||||
*
|
||||
* The actor should be an instance of St.Widget, with the style class
|
||||
* 'search-result-content'.
|
||||
*/
|
||||
createResultActor: function(resultMeta, terms) {
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* activateResult:
|
||||
* @id: Result identifier string
|
||||
*
|
||||
* Called when the user chooses a given result.
|
||||
*/
|
||||
activateResult: function(id) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(SearchProvider.prototype);
|
||||
|
||||
const SearchSystem = new Lang.Class({
|
||||
Name: 'SearchSystem',
|
||||
|
||||
@ -197,7 +31,7 @@ const SearchSystem = new Lang.Class({
|
||||
|
||||
let remoteIndex = this._remoteProviders.indexOf(provider);
|
||||
if (remoteIndex != -1)
|
||||
this._remoteProviders.splice(index, 1);
|
||||
this._remoteProviders.splice(remoteIndex, 1);
|
||||
},
|
||||
|
||||
getProviders: function() {
|
||||
@ -217,7 +51,7 @@ const SearchSystem = new Lang.Class({
|
||||
this._previousResults = [];
|
||||
},
|
||||
|
||||
pushResults: function(provider, results) {
|
||||
setResults: function(provider, results) {
|
||||
let i = this._providers.indexOf(provider);
|
||||
if (i == -1)
|
||||
return;
|
||||
@ -226,28 +60,18 @@ const SearchSystem = new Lang.Class({
|
||||
this.emit('search-updated', this._previousResults[i]);
|
||||
},
|
||||
|
||||
updateSearch: function(searchString) {
|
||||
searchString = searchString.replace(/^\s+/g, '').replace(/\s+$/g, '');
|
||||
if (searchString == '')
|
||||
return;
|
||||
|
||||
let terms = searchString.split(/\s+/);
|
||||
this.updateSearchResults(terms);
|
||||
},
|
||||
|
||||
updateSearchResults: function(terms) {
|
||||
if (!terms)
|
||||
return;
|
||||
|
||||
let isSubSearch = terms.length == this._previousTerms.length;
|
||||
if (isSubSearch) {
|
||||
for (let i = 0; i < terms.length; i++) {
|
||||
if (terms[i].indexOf(this._previousTerms[i]) != 0) {
|
||||
isSubSearch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let searchString = terms.join(' ');
|
||||
let previousSearchString = this._previousTerms.join(' ');
|
||||
if (searchString == previousSearchString)
|
||||
return;
|
||||
|
||||
let isSubSearch = false;
|
||||
if (this._previousTerms.length > 0)
|
||||
isSubSearch = searchString.indexOf(previousSearchString) == 0;
|
||||
|
||||
let previousResultsArr = this._previousResults;
|
||||
|
||||
@ -262,7 +86,7 @@ const SearchSystem = new Lang.Class({
|
||||
results.push([provider, []]);
|
||||
provider.getSubsearchResultSet(previousResults, terms);
|
||||
} catch (error) {
|
||||
log('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message);
|
||||
log('A ' + error.name + ' has occured in ' + provider.id + ': ' + error.message);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -272,10 +96,10 @@ const SearchSystem = new Lang.Class({
|
||||
results.push([provider, []]);
|
||||
provider.getInitialResultSet(terms);
|
||||
} catch (error) {
|
||||
log('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message);
|
||||
log('A ' + error.name + ' has occured in ' + provider.id + ': ' + error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(SearchSystem.prototype);
|
||||
|
@ -4,6 +4,7 @@ const Clutter = imports.gi.Clutter;
|
||||
const Lang = imports.lang;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
const Atk = imports.gi.Atk;
|
||||
|
||||
@ -11,10 +12,32 @@ const DND = imports.ui.dnd;
|
||||
const IconGrid = imports.ui.iconGrid;
|
||||
const Main = imports.ui.main;
|
||||
const Overview = imports.ui.overview;
|
||||
const Separator = imports.ui.separator;
|
||||
const Search = imports.ui.search;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
const MAX_SEARCH_RESULTS_ROWS = 1;
|
||||
const MAX_LIST_SEARCH_RESULTS_ROWS = 3;
|
||||
const MAX_GRID_SEARCH_RESULTS_ROWS = 1;
|
||||
|
||||
const MaxWidthBin = new Lang.Class({
|
||||
Name: 'MaxWidthBin',
|
||||
Extends: St.Bin,
|
||||
|
||||
vfunc_allocate: function(box, flags) {
|
||||
let themeNode = this.get_theme_node();
|
||||
let maxWidth = themeNode.get_max_width();
|
||||
let availWidth = box.x2 - box.x1;
|
||||
let adjustedBox = box;
|
||||
|
||||
if (availWidth > maxWidth) {
|
||||
let excessWidth = availWidth - maxWidth;
|
||||
adjustedBox.x1 += Math.floor(excessWidth / 2);
|
||||
adjustedBox.x2 -= Math.floor(excessWidth / 2);
|
||||
}
|
||||
|
||||
this.parent(adjustedBox, flags);
|
||||
}
|
||||
});
|
||||
|
||||
const SearchResult = new Lang.Class({
|
||||
Name: 'SearchResult',
|
||||
@ -22,33 +45,106 @@ const SearchResult = new Lang.Class({
|
||||
_init: function(provider, metaInfo, terms) {
|
||||
this.provider = provider;
|
||||
this.metaInfo = metaInfo;
|
||||
this.actor = new St.Button({ style_class: 'search-result',
|
||||
reactive: true,
|
||||
this.terms = terms;
|
||||
|
||||
this.actor = new St.Button({ reactive: true,
|
||||
can_focus: true,
|
||||
track_hover: true,
|
||||
x_align: St.Align.START,
|
||||
y_fill: true });
|
||||
this.actor._delegate = this;
|
||||
this._dragActorSource = null;
|
||||
|
||||
let content = provider.createResultActor(metaInfo, terms);
|
||||
if (content == null) {
|
||||
content = new St.Bin({ style_class: 'search-result-content',
|
||||
reactive: true,
|
||||
can_focus: true,
|
||||
track_hover: true,
|
||||
accessible_role: Atk.Role.PUSH_BUTTON });
|
||||
let icon = new IconGrid.BaseIcon(this.metaInfo['name'],
|
||||
{ createIcon: this.metaInfo['createIcon'] });
|
||||
content.set_child(icon.actor);
|
||||
this._dragActorSource = icon.icon;
|
||||
content.label_actor = icon.label;
|
||||
} else {
|
||||
if (content._delegate && content._delegate.getDragActorSource)
|
||||
this._dragActorSource = content._delegate.getDragActorSource();
|
||||
}
|
||||
this._content = content;
|
||||
this.actor._delegate = this;
|
||||
this.actor.connect('clicked', Lang.bind(this, this.activate));
|
||||
},
|
||||
|
||||
activate: function() {
|
||||
this.provider.activateResult(this.metaInfo.id, this.terms);
|
||||
Main.overview.toggle();
|
||||
},
|
||||
|
||||
setSelected: function(selected) {
|
||||
if (selected)
|
||||
this.actor.add_style_pseudo_class('selected');
|
||||
else
|
||||
this.actor.remove_style_pseudo_class('selected');
|
||||
}
|
||||
});
|
||||
|
||||
const ListSearchResult = new Lang.Class({
|
||||
Name: 'ListSearchResult',
|
||||
Extends: SearchResult,
|
||||
|
||||
ICON_SIZE: 64,
|
||||
|
||||
_init: function(provider, metaInfo, terms) {
|
||||
this.parent(provider, metaInfo, terms);
|
||||
|
||||
this.actor.style_class = 'list-search-result';
|
||||
this.actor.x_fill = true;
|
||||
|
||||
let content = new St.BoxLayout({ style_class: 'list-search-result-content',
|
||||
vertical: false });
|
||||
this.actor.set_child(content);
|
||||
|
||||
this.actor.connect('clicked', Lang.bind(this, this._onResultClicked));
|
||||
// An icon for, or thumbnail of, content
|
||||
let icon = this.metaInfo['createIcon'](this.ICON_SIZE);
|
||||
if (icon) {
|
||||
content.add(icon);
|
||||
}
|
||||
|
||||
let details = new St.BoxLayout({ vertical: true });
|
||||
content.add(details, { x_fill: true,
|
||||
y_fill: false,
|
||||
x_align: St.Align.START,
|
||||
y_align: St.Align.MIDDLE });
|
||||
|
||||
let title = new St.Label({ style_class: 'list-search-result-title',
|
||||
text: this.metaInfo['name'] })
|
||||
details.add(title, { x_fill: false,
|
||||
y_fill: false,
|
||||
x_align: St.Align.START,
|
||||
y_align: St.Align.START });
|
||||
this.actor.label_actor = title;
|
||||
|
||||
if (this.metaInfo['description']) {
|
||||
let description = new St.Label({ style_class: 'list-search-result-description' });
|
||||
description.clutter_text.set_markup(this.metaInfo['description']);
|
||||
details.add(description, { x_fill: false,
|
||||
y_fill: false,
|
||||
x_align: St.Align.START,
|
||||
y_align: St.Align.END });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const GridSearchResult = new Lang.Class({
|
||||
Name: 'GridSearchResult',
|
||||
Extends: SearchResult,
|
||||
|
||||
_init: function(provider, metaInfo, terms) {
|
||||
this.parent(provider, metaInfo, terms);
|
||||
|
||||
this.actor.style_class = 'grid-search-result';
|
||||
|
||||
let content = provider.createResultObject(metaInfo, terms);
|
||||
let dragSource = null;
|
||||
|
||||
if (content == null) {
|
||||
let actor = new St.Bin();
|
||||
let icon = new IconGrid.BaseIcon(this.metaInfo['name'],
|
||||
{ createIcon: this.metaInfo['createIcon'] });
|
||||
actor.set_child(icon.actor);
|
||||
actor.label_actor = icon.label;
|
||||
dragSource = icon.icon;
|
||||
content = { actor: actor, icon: icon };
|
||||
} else {
|
||||
if (content._delegate && content._delegate.getDragActorSource)
|
||||
dragSource = content._delegate.getDragActorSource();
|
||||
}
|
||||
|
||||
this.actor.set_child(content.actor);
|
||||
this.actor.label_actor = content.actor.label_actor;
|
||||
this.icon = content.icon;
|
||||
|
||||
let draggable = DND.makeDraggable(this.actor);
|
||||
draggable.connect('drag-begin',
|
||||
@ -63,32 +159,18 @@ const SearchResult = new Lang.Class({
|
||||
Lang.bind(this, function() {
|
||||
Main.overview.endItemDrag(this);
|
||||
}));
|
||||
},
|
||||
|
||||
setSelected: function(selected) {
|
||||
if (selected)
|
||||
this._content.add_style_pseudo_class('selected');
|
||||
else
|
||||
this._content.remove_style_pseudo_class('selected');
|
||||
},
|
||||
|
||||
activate: function() {
|
||||
this.provider.activateResult(this.metaInfo.id);
|
||||
Main.overview.toggle();
|
||||
},
|
||||
|
||||
_onResultClicked: function(actor) {
|
||||
this.activate();
|
||||
if (!dragSource)
|
||||
// not exactly right, but alignment problems are hard to notice
|
||||
dragSource = content;
|
||||
this._dragActorSource = dragSource;
|
||||
},
|
||||
|
||||
getDragActorSource: function() {
|
||||
if (this._dragActorSource)
|
||||
return this._dragActorSource;
|
||||
// not exactly right, but alignment problems are hard to notice
|
||||
return this._content;
|
||||
return this._dragActorSource;
|
||||
},
|
||||
|
||||
getDragActor: function(stageX, stageY) {
|
||||
getDragActor: function() {
|
||||
return this.metaInfo['createIcon'](Main.overview.dashIconSize);
|
||||
},
|
||||
|
||||
@ -96,79 +178,174 @@ const SearchResult = new Lang.Class({
|
||||
if (this.provider.dragActivateResult)
|
||||
this.provider.dragActivateResult(this.metaInfo.id, params);
|
||||
else
|
||||
this.provider.activateResult(this.metaInfo.id, params);
|
||||
this.provider.activateResult(this.metaInfo.id, this.terms);
|
||||
}
|
||||
});
|
||||
|
||||
const SearchResultsBase = new Lang.Class({
|
||||
Name: 'SearchResultsBase',
|
||||
|
||||
const GridSearchResults = new Lang.Class({
|
||||
Name: 'GridSearchResults',
|
||||
Extends: Search.SearchResultDisplay,
|
||||
_init: function(provider) {
|
||||
this.provider = provider;
|
||||
|
||||
_init: function(provider, grid) {
|
||||
this._terms = [];
|
||||
|
||||
this.actor = new St.BoxLayout({ style_class: 'search-section',
|
||||
vertical: true });
|
||||
|
||||
this._resultDisplayBin = new St.Bin({ x_fill: true,
|
||||
y_fill: true });
|
||||
this.actor.add(this._resultDisplayBin, { expand: true });
|
||||
|
||||
let separator = new Separator.HorizontalSeparator({ style_class: 'search-section-separator' });
|
||||
this.actor.add(separator.actor);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this.actor.destroy();
|
||||
this._terms = [];
|
||||
},
|
||||
|
||||
_clearResultDisplay: function() {
|
||||
},
|
||||
|
||||
clear: function() {
|
||||
this._clearResultDisplay();
|
||||
this.actor.hide();
|
||||
},
|
||||
|
||||
_keyFocusIn: function(actor) {
|
||||
this.emit('key-focus-in', actor);
|
||||
},
|
||||
|
||||
_setMoreIconVisible: function(visible) {
|
||||
},
|
||||
|
||||
updateSearch: function(providerResults, terms, callback) {
|
||||
this._terms = terms;
|
||||
|
||||
if (providerResults.length == 0) {
|
||||
this._clearResultDisplay();
|
||||
this.actor.hide();
|
||||
callback();
|
||||
} else {
|
||||
let maxResults = this._getMaxDisplayedResults();
|
||||
let results = providerResults.slice(0, maxResults);
|
||||
let hasMoreResults = results.length < providerResults.length;
|
||||
|
||||
this.provider.getResultMetas(results, Lang.bind(this, function(metas) {
|
||||
this.clear();
|
||||
|
||||
// To avoid CSS transitions causing flickering when
|
||||
// the first search result stays the same, we hide the
|
||||
// content while filling in the results.
|
||||
this.actor.hide();
|
||||
this._clearResultDisplay();
|
||||
this._renderResults(metas);
|
||||
this._setMoreIconVisible(hasMoreResults && this.provider.canLaunchSearch);
|
||||
this.actor.show();
|
||||
callback();
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const ListSearchResults = new Lang.Class({
|
||||
Name: 'ListSearchResults',
|
||||
Extends: SearchResultsBase,
|
||||
|
||||
_init: function(provider) {
|
||||
this.parent(provider);
|
||||
|
||||
this._grid = grid || new IconGrid.IconGrid({ rowLimit: MAX_SEARCH_RESULTS_ROWS,
|
||||
xAlign: St.Align.START });
|
||||
this.actor = new St.Bin({ x_align: St.Align.START });
|
||||
|
||||
this.actor.set_child(this._grid.actor);
|
||||
this._width = 0;
|
||||
this.actor.connect('notify::width', Lang.bind(this, function() {
|
||||
this._width = this.actor.width;
|
||||
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() {
|
||||
let results = this.getResultsForDisplay();
|
||||
if (results.length == 0)
|
||||
return;
|
||||
|
||||
provider.getResultMetas(results, Lang.bind(this, this.renderResults));
|
||||
this._container = new St.BoxLayout({ style_class: 'search-section-content' });
|
||||
this.providerIcon = new ProviderIcon(provider);
|
||||
this.providerIcon.connect('key-focus-in', Lang.bind(this, this._keyFocusIn));
|
||||
this.providerIcon.connect('clicked', Lang.bind(this,
|
||||
function() {
|
||||
provider.launchSearch(this._terms);
|
||||
Main.overview.toggle();
|
||||
}));
|
||||
}));
|
||||
this._notDisplayedResult = [];
|
||||
this._terms = [];
|
||||
this._pendingClear = false;
|
||||
|
||||
this._container.add(this.providerIcon, { x_fill: false,
|
||||
y_fill: false,
|
||||
x_align: St.Align.START,
|
||||
y_align: St.Align.START });
|
||||
|
||||
this._content = new St.BoxLayout({ style_class: 'list-search-results',
|
||||
vertical: true });
|
||||
this._container.add(this._content, { expand: true });
|
||||
|
||||
this._resultDisplayBin.set_child(this._container);
|
||||
},
|
||||
|
||||
getResultsForDisplay: function() {
|
||||
let alreadyVisible = this._pendingClear ? 0 : this._grid.visibleItemsCount();
|
||||
let canDisplay = this._grid.childrenInRow(this._width) * this._grid.getRowLimit()
|
||||
- alreadyVisible;
|
||||
|
||||
let numResults = Math.min(this._notDisplayedResult.length, canDisplay);
|
||||
|
||||
return this._notDisplayedResult.splice(0, numResults);
|
||||
_setMoreIconVisible: function(visible) {
|
||||
this.providerIcon.moreIcon.visible = true;
|
||||
},
|
||||
|
||||
getVisibleResultCount: function() {
|
||||
return this._grid.visibleItemsCount();
|
||||
_getMaxDisplayedResults: function() {
|
||||
return MAX_LIST_SEARCH_RESULTS_ROWS;
|
||||
},
|
||||
|
||||
setResults: function(results, terms) {
|
||||
// copy the lists
|
||||
this._notDisplayedResult = results.slice(0);
|
||||
this._terms = terms.slice(0);
|
||||
this._pendingClear = true;
|
||||
},
|
||||
|
||||
renderResults: function(metas) {
|
||||
_renderResults: function(metas) {
|
||||
for (let i = 0; i < metas.length; i++) {
|
||||
let display = new SearchResult(this.provider, metas[i], this._terms);
|
||||
this._grid.addItem(display.actor);
|
||||
let display = new ListSearchResult(this.provider, metas[i], this._terms);
|
||||
display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn));
|
||||
this._content.add_actor(display.actor);
|
||||
}
|
||||
},
|
||||
|
||||
clear: function () {
|
||||
this._grid.removeAll();
|
||||
this._pendingClear = false;
|
||||
_clearResultDisplay: function () {
|
||||
this._content.destroy_all_children();
|
||||
},
|
||||
|
||||
getFirstResult: function() {
|
||||
if (this.getVisibleResultCount() > 0)
|
||||
if (this._content.get_n_children() > 0)
|
||||
return this._content.get_child_at_index(0)._delegate;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(ListSearchResults.prototype);
|
||||
|
||||
const GridSearchResults = new Lang.Class({
|
||||
Name: 'GridSearchResults',
|
||||
Extends: SearchResultsBase,
|
||||
|
||||
_init: function(provider) {
|
||||
this.parent(provider);
|
||||
|
||||
this._grid = new IconGrid.IconGrid({ rowLimit: MAX_GRID_SEARCH_RESULTS_ROWS,
|
||||
xAlign: St.Align.START });
|
||||
this._bin = new St.Bin({ x_align: St.Align.MIDDLE });
|
||||
this._bin.set_child(this._grid.actor);
|
||||
|
||||
this._resultDisplayBin.set_child(this._bin);
|
||||
},
|
||||
|
||||
_getMaxDisplayedResults: function() {
|
||||
return this._grid.columnsForWidth(this._bin.width) * this._grid.getRowLimit();
|
||||
},
|
||||
|
||||
_renderResults: function(metas) {
|
||||
for (let i = 0; i < metas.length; i++) {
|
||||
let display = new GridSearchResult(this.provider, metas[i], this._terms);
|
||||
display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn));
|
||||
this._grid.addItem(display);
|
||||
}
|
||||
},
|
||||
|
||||
_clearResultDisplay: function () {
|
||||
this._grid.removeAll();
|
||||
},
|
||||
|
||||
getFirstResult: function() {
|
||||
if (this._grid.visibleItemsCount() > 0)
|
||||
return this._grid.getItemAtIndex(0)._delegate;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(GridSearchResults.prototype);
|
||||
|
||||
const SearchResults = new Lang.Class({
|
||||
Name: 'SearchResults',
|
||||
@ -182,27 +359,29 @@ const SearchResults = new Lang.Class({
|
||||
|
||||
this._content = new St.BoxLayout({ name: 'searchResultsContent',
|
||||
vertical: true });
|
||||
this._contentBin = new MaxWidthBin({ name: 'searchResultsBin',
|
||||
x_fill: true,
|
||||
y_fill: true,
|
||||
child: this._content });
|
||||
|
||||
let scrollView = new St.ScrollView({ x_fill: true,
|
||||
y_fill: false,
|
||||
style_class: 'vfade' });
|
||||
scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
scrollView.add_actor(this._content);
|
||||
let scrollChild = new St.BoxLayout();
|
||||
scrollChild.add(this._contentBin, { expand: true });
|
||||
|
||||
this.actor.add(scrollView, { x_fill: true,
|
||||
y_fill: true,
|
||||
expand: true,
|
||||
x_align: St.Align.START,
|
||||
y_align: St.Align.START });
|
||||
this.actor.connect('notify::mapped', Lang.bind(this,
|
||||
function() {
|
||||
if (!this.actor.mapped)
|
||||
return;
|
||||
this._scrollView = new St.ScrollView({ x_fill: true,
|
||||
y_fill: false,
|
||||
overlay_scrollbars: true,
|
||||
style_class: 'search-display vfade' });
|
||||
this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
this._scrollView.add_actor(scrollChild);
|
||||
let action = new Clutter.PanAction({ interpolate: true });
|
||||
action.connect('pan', Lang.bind(this, this._onPan));
|
||||
this._scrollView.add_action(action);
|
||||
|
||||
let adjustment = scrollView.vscroll.adjustment;
|
||||
let direction = Overview.SwipeScrollDirection.VERTICAL;
|
||||
Main.overview.setScrollAdjustment(adjustment, direction);
|
||||
}));
|
||||
this.actor.add(this._scrollView, { x_fill: true,
|
||||
y_fill: true,
|
||||
expand: true,
|
||||
x_align: St.Align.START,
|
||||
y_align: St.Align.START });
|
||||
|
||||
this._statusText = new St.Label({ style_class: 'search-statustext' });
|
||||
this._statusBin = new St.Bin({ x_align: St.Align.MIDDLE,
|
||||
@ -210,90 +389,77 @@ const SearchResults = new Lang.Class({
|
||||
this._content.add(this._statusBin, { expand: true });
|
||||
this._statusBin.add_actor(this._statusText);
|
||||
this._providers = this._searchSystem.getProviders();
|
||||
this._providerMeta = [];
|
||||
this._providerDisplays = {};
|
||||
for (let i = 0; i < this._providers.length; i++) {
|
||||
this.createProviderMeta(this._providers[i]);
|
||||
this.createProviderDisplay(this._providers[i]);
|
||||
}
|
||||
|
||||
this._highlightDefault = false;
|
||||
this._defaultResult = null;
|
||||
},
|
||||
|
||||
createProviderMeta: function(provider) {
|
||||
let providerBox = new St.BoxLayout({ style_class: 'search-section',
|
||||
vertical: true });
|
||||
let title = new St.Label({ style_class: 'search-section-header',
|
||||
text: provider.title });
|
||||
providerBox.add(title, { x_fill: false, x_align: St.Align.START });
|
||||
|
||||
let resultDisplayBin = new St.Bin({ style_class: 'search-section-results',
|
||||
x_fill: true,
|
||||
y_fill: true });
|
||||
providerBox.add(resultDisplayBin, { expand: true });
|
||||
let resultDisplay = new GridSearchResults(provider);
|
||||
resultDisplayBin.set_child(resultDisplay.actor);
|
||||
|
||||
this._providerMeta.push({ provider: provider,
|
||||
actor: providerBox,
|
||||
resultDisplay: resultDisplay });
|
||||
this._content.add(providerBox);
|
||||
_onPan: function(action) {
|
||||
let [dist, dx, dy] = action.get_motion_delta(0);
|
||||
let adjustment = this._scrollView.vscroll.adjustment;
|
||||
adjustment.value -= (dy / this.actor.height) * adjustment.page_size;
|
||||
return false;
|
||||
},
|
||||
|
||||
destroyProviderMeta: function(provider) {
|
||||
for (let i=0; i < this._providerMeta.length; i++) {
|
||||
let meta = this._providerMeta[i];
|
||||
if (meta.provider == provider) {
|
||||
meta.actor.destroy();
|
||||
this._providerMeta.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
_keyFocusIn: function(provider, actor) {
|
||||
Util.ensureActorVisibleInScrollView(this._scrollView, actor);
|
||||
},
|
||||
|
||||
createProviderDisplay: function(provider) {
|
||||
let providerDisplay = null;
|
||||
|
||||
if (provider.appInfo) {
|
||||
providerDisplay = new ListSearchResults(provider);
|
||||
} else {
|
||||
providerDisplay = new GridSearchResults(provider);
|
||||
}
|
||||
|
||||
providerDisplay.connect('key-focus-in', Lang.bind(this, this._keyFocusIn));
|
||||
this._providerDisplays[provider.id] = providerDisplay;
|
||||
this._content.add(providerDisplay.actor);
|
||||
},
|
||||
|
||||
destroyProviderDisplay: function(provider) {
|
||||
this._providerDisplays[provider.id].destroy();
|
||||
delete this._providerDisplays[provider.id];
|
||||
},
|
||||
|
||||
_clearDisplay: function() {
|
||||
for (let i = 0; i < this._providerMeta.length; i++) {
|
||||
let meta = this._providerMeta[i];
|
||||
meta.resultDisplay.clear();
|
||||
meta.actor.hide();
|
||||
for (let i = 0; i < this._providers.length; i++) {
|
||||
let provider = this._providers[i];
|
||||
let providerDisplay = this._providerDisplays[provider.id];
|
||||
providerDisplay.clear();
|
||||
}
|
||||
},
|
||||
|
||||
_clearDisplayForProvider: function(provider) {
|
||||
let meta = this._metaForProvider(provider);
|
||||
meta.resultDisplay.clear();
|
||||
meta.actor.hide();
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
this._searchSystem.reset();
|
||||
this._statusBin.hide();
|
||||
this._clearDisplay();
|
||||
this._defaultResult = null;
|
||||
},
|
||||
|
||||
startingSearch: function() {
|
||||
this.reset();
|
||||
this._statusText.set_text(_("Searching..."));
|
||||
this._statusText.set_text(_("Searching…"));
|
||||
this._statusBin.show();
|
||||
},
|
||||
|
||||
doSearch: function (searchString) {
|
||||
this._searchSystem.updateSearch(searchString);
|
||||
},
|
||||
|
||||
_metaForProvider: function(provider) {
|
||||
return this._providerMeta[this._providers.indexOf(provider)];
|
||||
},
|
||||
|
||||
_maybeSetInitialSelection: function() {
|
||||
let newDefaultResult = null;
|
||||
|
||||
for (let i = 0; i < this._providerMeta.length; i++) {
|
||||
let meta = this._providerMeta[i];
|
||||
for (let i = 0; i < this._providers.length; i++) {
|
||||
let provider = this._providers[i];
|
||||
let display = this._providerDisplays[provider.id];
|
||||
|
||||
if (!meta.actor.visible)
|
||||
if (!display.actor.visible)
|
||||
continue;
|
||||
|
||||
let firstResult = meta.resultDisplay.getFirstResult();
|
||||
let firstResult = display.getFirstResult();
|
||||
if (firstResult) {
|
||||
newDefaultResult = firstResult;
|
||||
break; // select this one!
|
||||
@ -313,11 +479,14 @@ const SearchResults = new Lang.Class({
|
||||
_updateStatusText: function () {
|
||||
let haveResults = false;
|
||||
|
||||
for (let i = 0; i < this._providerMeta.length; ++i)
|
||||
if (this._providerMeta[i].resultDisplay.getFirstResult()) {
|
||||
for (let i = 0; i < this._providers.length; i++) {
|
||||
let provider = this._providers[i];
|
||||
let display = this._providerDisplays[provider.id];
|
||||
if (display.getFirstResult()) {
|
||||
haveResults = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!haveResults) {
|
||||
this._statusText.set_text(_("No results."));
|
||||
@ -330,37 +499,12 @@ const SearchResults = new Lang.Class({
|
||||
_updateResults: function(searchSystem, results) {
|
||||
let terms = searchSystem.getTerms();
|
||||
let [provider, providerResults] = results;
|
||||
let meta = this._metaForProvider(provider);
|
||||
let display = this._providerDisplays[provider.id];
|
||||
|
||||
if (providerResults.length == 0) {
|
||||
this._clearDisplayForProvider(provider);
|
||||
meta.resultDisplay.setResults([], []);
|
||||
display.updateSearch(providerResults, terms, Lang.bind(this, function() {
|
||||
this._maybeSetInitialSelection();
|
||||
this._updateStatusText();
|
||||
} else {
|
||||
meta.resultDisplay.setResults(providerResults, terms);
|
||||
let results = meta.resultDisplay.getResultsForDisplay();
|
||||
|
||||
provider.getResultMetas(results, Lang.bind(this, function(metas) {
|
||||
this._clearDisplayForProvider(provider);
|
||||
meta.actor.show();
|
||||
|
||||
// Hiding drops the key focus if we have it
|
||||
let focus = global.stage.get_key_focus();
|
||||
// To avoid CSS transitions causing flickering when
|
||||
// the first search result stays the same, we hide the
|
||||
// content while filling in the results.
|
||||
this._content.hide();
|
||||
|
||||
meta.resultDisplay.renderResults(metas);
|
||||
this._maybeSetInitialSelection();
|
||||
this._updateStatusText();
|
||||
|
||||
this._content.show();
|
||||
if (this._content.contains(focus))
|
||||
global.stage.set_key_focus(focus);
|
||||
}));
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
activateDefault: function() {
|
||||
@ -386,10 +530,38 @@ const SearchResults = new Lang.Class({
|
||||
|
||||
let from = this._defaultResult ? this._defaultResult.actor : null;
|
||||
this.actor.navigate_focus(from, direction, false);
|
||||
if (this._defaultResult) {
|
||||
// The default result appears focused, so navigate directly to the
|
||||
// next result.
|
||||
this.actor.navigate_focus(global.stage.key_focus, direction, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const ProviderIcon = new Lang.Class({
|
||||
Name: 'ProviderIcon',
|
||||
Extends: St.Button,
|
||||
|
||||
PROVIDER_ICON_SIZE: 48,
|
||||
|
||||
_init: function(provider) {
|
||||
this.provider = provider;
|
||||
this.parent({ style_class: 'search-provider-icon',
|
||||
reactive: true,
|
||||
can_focus: true,
|
||||
accessible_name: provider.appInfo.get_name(),
|
||||
track_hover: true });
|
||||
|
||||
this._content = new St.Widget({ layout_manager: new Clutter.BinLayout() });
|
||||
this.set_child(this._content);
|
||||
|
||||
let rtl = (this.get_text_direction() == Clutter.TextDirection.RTL);
|
||||
|
||||
this.moreIcon = new St.Widget({ style_class: 'search-provider-icon-more',
|
||||
visible: false,
|
||||
x_align: rtl ? Clutter.ActorAlign.START : Clutter.ActorAlign.END,
|
||||
y_align: Clutter.ActorAlign.END,
|
||||
x_expand: true,
|
||||
y_expand: true });
|
||||
|
||||
let icon = new St.Icon({ icon_size: this.PROVIDER_ICON_SIZE,
|
||||
gicon: provider.appInfo.get_icon() });
|
||||
this._content.add_actor(icon);
|
||||
this._content.add_actor(this.moreIcon);
|
||||
}
|
||||
});
|
||||
|
35
js/ui/separator.js
Normal file
35
js/ui/separator.js
Normal file
@ -0,0 +1,35 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Cairo = imports.cairo;
|
||||
const Lang = imports.lang;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const HorizontalSeparator = new Lang.Class({
|
||||
Name: 'HorizontalSeparator',
|
||||
|
||||
_init: function (params) {
|
||||
this.actor = new St.DrawingArea(params);
|
||||
this.actor.connect('repaint', Lang.bind(this, this._onRepaint));
|
||||
},
|
||||
|
||||
_onRepaint: function(area) {
|
||||
let cr = area.get_context();
|
||||
let themeNode = area.get_theme_node();
|
||||
let [width, height] = area.get_surface_size();
|
||||
let margin = themeNode.get_length('-margin-horizontal');
|
||||
let gradientHeight = themeNode.get_length('-gradient-height');
|
||||
let startColor = themeNode.get_color('-gradient-start');
|
||||
let endColor = themeNode.get_color('-gradient-end');
|
||||
|
||||
let gradientWidth = (width - margin * 2);
|
||||
let gradientOffset = (height - gradientHeight) / 2;
|
||||
let pattern = new Cairo.LinearGradient(margin, gradientOffset, width - margin, gradientOffset + gradientHeight);
|
||||
pattern.addColorStopRGBA(0, startColor.red / 255, startColor.green / 255, startColor.blue / 255, startColor.alpha / 255);
|
||||
pattern.addColorStopRGBA(0.5, endColor.red / 255, endColor.green / 255, endColor.blue / 255, endColor.alpha / 255);
|
||||
pattern.addColorStopRGBA(1, startColor.red / 255, startColor.green / 255, startColor.blue / 255, startColor.alpha / 255);
|
||||
cr.setSource(pattern);
|
||||
cr.rectangle(margin, gradientOffset, gradientWidth, gradientHeight);
|
||||
cr.fill();
|
||||
cr.$dispose();
|
||||
}
|
||||
});
|
@ -1,8 +1,12 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const FileUtils = imports.misc.fileUtils;
|
||||
const Main = imports.ui.main;
|
||||
const Params = imports.misc.params;
|
||||
|
||||
@ -10,10 +14,15 @@ const DEFAULT_MODE = 'restrictive';
|
||||
|
||||
const _modes = {
|
||||
'restrictive': {
|
||||
parentMode: null,
|
||||
stylesheetName: 'gnome-shell.css',
|
||||
overridesSchema: 'org.gnome.shell.overrides',
|
||||
hasOverview: false,
|
||||
showCalendarEvents: false,
|
||||
allowSettings: false,
|
||||
allowExtensions: false,
|
||||
allowScreencast: false,
|
||||
enabledExtensions: [],
|
||||
hasRunDialog: false,
|
||||
hasWorkspaces: false,
|
||||
hasWindows: false,
|
||||
@ -21,7 +30,6 @@ const _modes = {
|
||||
isLocked: false,
|
||||
isGreeter: false,
|
||||
isPrimary: false,
|
||||
buttonLayout: ['button-layout', 'org.gnome.shell.overrides'],
|
||||
unlockDialog: null,
|
||||
components: [],
|
||||
panel: {
|
||||
@ -39,10 +47,9 @@ const _modes = {
|
||||
unlockDialog: imports.gdm.loginDialog.LoginDialog,
|
||||
components: ['polkitAgent'],
|
||||
panel: {
|
||||
left: ['logo'],
|
||||
left: [],
|
||||
center: ['dateMenu'],
|
||||
right: ['a11y', 'display', 'keyboard',
|
||||
'volume', 'battery', 'powerMenu']
|
||||
right: ['a11yGreeter', 'keyboard', 'aggregateMenu'],
|
||||
},
|
||||
panelStyle: 'login-screen'
|
||||
},
|
||||
@ -53,9 +60,9 @@ const _modes = {
|
||||
unlockDialog: undefined,
|
||||
components: ['polkitAgent', 'telepathyClient'],
|
||||
panel: {
|
||||
left: ['userMenu'],
|
||||
left: [],
|
||||
center: [],
|
||||
right: ['lockScreen']
|
||||
right: ['aggregateMenu']
|
||||
},
|
||||
panelStyle: 'lock-screen'
|
||||
},
|
||||
@ -65,28 +72,19 @@ const _modes = {
|
||||
unlockDialog: undefined,
|
||||
components: ['polkitAgent', 'telepathyClient'],
|
||||
panel: {
|
||||
left: ['userMenu'],
|
||||
left: [],
|
||||
center: [],
|
||||
right: ['a11y', 'keyboard', 'lockScreen']
|
||||
right: ['a11y', 'keyboard', 'aggregateMenu']
|
||||
},
|
||||
panelStyle: 'unlock-screen'
|
||||
},
|
||||
|
||||
'initial-setup': {
|
||||
isPrimary: true,
|
||||
components: ['keyring'],
|
||||
panel: {
|
||||
left: [],
|
||||
center: ['dateMenu'],
|
||||
right: ['a11y', 'keyboard', 'volume']
|
||||
}
|
||||
},
|
||||
|
||||
'user': {
|
||||
hasOverview: true,
|
||||
showCalendarEvents: true,
|
||||
allowSettings: true,
|
||||
allowExtensions: true,
|
||||
allowScreencast: true,
|
||||
hasRunDialog: true,
|
||||
hasWorkspaces: true,
|
||||
hasWindows: true,
|
||||
@ -95,55 +93,73 @@ const _modes = {
|
||||
isPrimary: true,
|
||||
unlockDialog: imports.ui.unlockDialog.UnlockDialog,
|
||||
components: ['networkAgent', 'polkitAgent', 'telepathyClient',
|
||||
'keyring', 'recorder', 'autorunManager', 'automountManager'],
|
||||
'keyring', 'autorunManager', 'automountManager'],
|
||||
panel: {
|
||||
left: ['activities', 'appMenu'],
|
||||
center: ['dateMenu'],
|
||||
right: ['a11y', 'keyboard', 'volume', 'bluetooth',
|
||||
'network', 'battery', 'userMenu']
|
||||
}
|
||||
},
|
||||
|
||||
'foo': {
|
||||
hasOverview: true,
|
||||
showCalendarEvents: true,
|
||||
allowSettings: true,
|
||||
allowExtensions: true,
|
||||
hasRunDialog: true,
|
||||
hasWorkspaces: true,
|
||||
hasWindows: true,
|
||||
hasNotifications: true,
|
||||
isLocked: false,
|
||||
isPrimary: true,
|
||||
buttonLayout: ['button-layout', 'org.gnome.shell-foo.overrides'],
|
||||
unlockDialog: imports.ui.unlockDialog.UnlockDialog,
|
||||
components: ['networkAgent', 'polkitAgent', 'telepathyClient',
|
||||
'keyring', 'recorder', 'autorunManager', 'automountManager'],
|
||||
panel: {
|
||||
left: ['activities', 'appMenu'],
|
||||
center: ['dateMenu'],
|
||||
right: ['a11y', 'keyboard', 'volume', 'bluetooth',
|
||||
'network', 'battery', 'userMenu']
|
||||
right: ['a11y', 'keyboard', 'aggregateMenu']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function _getModes(modesLoadedCallback) {
|
||||
FileUtils.collectFromDatadirsAsync('modes',
|
||||
{ processFile: _loadMode,
|
||||
loadedCallback: modesLoadedCallback,
|
||||
data: _modes });
|
||||
}
|
||||
|
||||
function _loadMode(file, info, loadedData) {
|
||||
let name = info.get_name();
|
||||
let suffix = name.indexOf('.json');
|
||||
let modeName = suffix == -1 ? name : name.slice(name, suffix);
|
||||
|
||||
if (loadedData.hasOwnProperty(modeName))
|
||||
return;
|
||||
|
||||
let fileContent, success, tag, newMode;
|
||||
try {
|
||||
[success, fileContent, tag] = file.load_contents(null);
|
||||
newMode = JSON.parse(fileContent);
|
||||
} catch(e) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadedData[modeName] = {};
|
||||
let propBlacklist = ['unlockDialog'];
|
||||
for (let prop in loadedData[DEFAULT_MODE]) {
|
||||
if (newMode[prop] !== undefined &&
|
||||
propBlacklist.indexOf(prop) == -1)
|
||||
loadedData[modeName][prop]= newMode[prop];
|
||||
}
|
||||
loadedData[modeName]['isPrimary'] = true;
|
||||
}
|
||||
|
||||
function listModes() {
|
||||
let modes = Object.getOwnPropertyNames(_modes);
|
||||
for (let i = 0; i < modes.length; i++)
|
||||
if (_modes[modes[i]].isPrimary)
|
||||
print(modes[i]);
|
||||
_getModes(function(modes) {
|
||||
let names = Object.getOwnPropertyNames(modes);
|
||||
for (let i = 0; i < names.length; i++)
|
||||
if (_modes[names[i]].isPrimary)
|
||||
print(names[i]);
|
||||
Mainloop.quit('listModes');
|
||||
});
|
||||
Mainloop.run('listModes');
|
||||
}
|
||||
|
||||
const SessionMode = new Lang.Class({
|
||||
Name: 'SessionMode',
|
||||
|
||||
_init: function() {
|
||||
global.connect('notify::session-mode', Lang.bind(this, this._sync));
|
||||
let mode = _modes[global.session_mode].isPrimary ? global.session_mode
|
||||
: 'user';
|
||||
this._modeStack = [mode];
|
||||
this._sync();
|
||||
init: function() {
|
||||
_getModes(Lang.bind(this, function(modes) {
|
||||
this._modes = modes;
|
||||
let primary = modes[global.session_mode] &&
|
||||
modes[global.session_mode].isPrimary;
|
||||
let mode = primary ? global.session_mode : 'user';
|
||||
this._modeStack = [mode];
|
||||
this._sync();
|
||||
|
||||
this.emit('sessions-loaded');
|
||||
}));
|
||||
},
|
||||
|
||||
pushMode: function(mode) {
|
||||
@ -170,8 +186,14 @@ const SessionMode = new Lang.Class({
|
||||
},
|
||||
|
||||
_sync: function() {
|
||||
let params = _modes[this.currentMode];
|
||||
params = Params.parse(params, _modes[DEFAULT_MODE]);
|
||||
let params = this._modes[this.currentMode];
|
||||
let defaults;
|
||||
if (params.parentMode)
|
||||
defaults = Params.parse(this._modes[params.parentMode],
|
||||
this._modes[DEFAULT_MODE]);
|
||||
else
|
||||
defaults = this._modes[DEFAULT_MODE];
|
||||
params = Params.parse(params, defaults);
|
||||
|
||||
// A simplified version of Lang.copyProperties, handles
|
||||
// undefined as a special case for "no change / inherit from previous mode"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user