Compare commits
846 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
262
NEWS
262
NEWS
@ -1,3 +1,265 @@
|
||||
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]
|
||||
|
28
configure.ac
28
configure.ac
@ -1,5 +1,5 @@
|
||||
AC_PREREQ(2.63)
|
||||
AC_INIT([gnome-shell],[3.7.5],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell])
|
||||
AC_INIT([gnome-shell],[3.9.2],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell])
|
||||
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
AC_CONFIG_SRCDIR([src/shell-global.c])
|
||||
@ -53,7 +53,7 @@ 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 xfixes)
|
||||
else
|
||||
AC_MSG_RESULT(no)
|
||||
fi
|
||||
@ -63,19 +63,18 @@ AM_CONDITIONAL(BUILD_RECORDER, $build_recorder)
|
||||
CLUTTER_MIN_VERSION=1.13.4
|
||||
GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1
|
||||
GJS_MIN_VERSION=1.35.4
|
||||
MUTTER_MIN_VERSION=3.7.5
|
||||
GTK_MIN_VERSION=3.3.9
|
||||
GIO_MIN_VERSION=2.35.0
|
||||
MUTTER_MIN_VERSION=3.9.2
|
||||
GTK_MIN_VERSION=3.7.9
|
||||
GIO_MIN_VERSION=2.37.0
|
||||
LIBECAL_MIN_VERSION=3.5.3
|
||||
LIBEDATASERVER_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_DESKTOP_REQUIRED_VERSION=3.7.90
|
||||
GNOME_MENUS_REQUIRED_VERSION=3.5.3
|
||||
NETWORKMANAGER_MIN_VERSION=0.9.6
|
||||
NETWORKMANAGER_MIN_VERSION=0.9.8
|
||||
PULSE_MIN_VERS=2.0
|
||||
|
||||
# Collect more than 20 libraries for a prize!
|
||||
@ -88,18 +87,16 @@ PKG_CHECK_MODULES(GNOME_SHELL, gio-unix-2.0 >= $GIO_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 libcanberra-gtk3
|
||||
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)
|
||||
libsecret-unstable gcr-base-3 >= $GCR_MIN_VERSION)
|
||||
|
||||
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)
|
||||
@ -109,6 +106,7 @@ PKG_CHECK_MODULES(BROWSER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION json-glib-1.0 >= 0
|
||||
PKG_CHECK_MODULES(TRAY, gtk+-3.0)
|
||||
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],
|
||||
@ -133,14 +131,6 @@ 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(XFixesCreatePointerBarrier)
|
||||
CFLAGS=$saved_CFLAGS
|
||||
LIBS=$saved_LIBS
|
||||
|
||||
MUTTER_GIR_DIR=`$PKG_CONFIG --variable=girdir libmutter`
|
||||
MUTTER_TYPELIB_DIR=`$PKG_CONFIG --variable=typelibdir libmutter`
|
||||
AC_SUBST(MUTTER_GIR_DIR)
|
||||
|
@ -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,3 +1,6 @@
|
||||
wandadir = $(pkgdatadir)
|
||||
dist_wanda_DATA = wanda.png
|
||||
|
||||
desktopdir=$(datadir)/applications
|
||||
desktop_DATA = gnome-shell.desktop gnome-shell-extension-prefs.desktop
|
||||
|
||||
@ -12,6 +15,7 @@ desktop_DATA = gnome-shell.desktop gnome-shell-extension-prefs.desktop
|
||||
|
||||
introspectiondir = $(datadir)/dbus-1/interfaces
|
||||
introspection_DATA = \
|
||||
org.gnome.Shell.Screencast.xml \
|
||||
org.gnome.Shell.Screenshot.xml \
|
||||
org.gnome.ShellSearchProvider.xml \
|
||||
org.gnome.ShellSearchProvider2.xml
|
||||
|
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>
|
@ -46,7 +46,7 @@
|
||||
<!--
|
||||
GetResultMetas:
|
||||
@identifiers: An array of result identifiers as returned by GetInitialResultSet() or GetSubsearchResultSet()
|
||||
@metas: A dictionary describing the given search result, containing 'id' and 'name' (both strings). 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).
|
||||
@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
|
||||
-->
|
||||
|
@ -46,7 +46,7 @@
|
||||
<!--
|
||||
GetResultMetas:
|
||||
@identifiers: An array of result identifiers as returned by GetInitialResultSet() or GetSubsearchResultSet()
|
||||
@metas: A dictionary describing the given search result, containing 'id' and 'name' (both strings). 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.
|
||||
@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
|
||||
-->
|
||||
|
@ -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>
|
||||
@ -109,6 +107,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>
|
||||
|
@ -38,7 +38,6 @@ stage {
|
||||
|
||||
/* small */
|
||||
.app-well-menu,
|
||||
.contact-details-status,
|
||||
.run-dialog-error-label {
|
||||
font-size: 9pt;
|
||||
}
|
||||
@ -126,9 +125,14 @@ StScrollBar StButton#vhandle:active {
|
||||
|
||||
/* PopupMenu */
|
||||
|
||||
.popup-menu-boxpointer {
|
||||
.popup-menu-ornament {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.popup-menu-boxpointer,
|
||||
.candidate-popup-boxpointer {
|
||||
-arrow-border-radius: 8px;
|
||||
-arrow-background-color: black;
|
||||
-arrow-background-color: rgba(0,0,0,0.9);
|
||||
-arrow-border-width: 2px;
|
||||
-arrow-border-color: #a5a5a5;
|
||||
-arrow-base: 24px;
|
||||
@ -286,24 +290,24 @@ StScrollBar StButton#vhandle:active {
|
||||
|
||||
/* Buttons */
|
||||
|
||||
.dash-search-button,
|
||||
.candidate-page-button,
|
||||
.notification-button,
|
||||
.notification-icon-button,
|
||||
.hotplug-notification-item,
|
||||
.hotplug-resident-eject-button,
|
||||
.modal-dialog-button {
|
||||
.modal-dialog-button,
|
||||
.app-view-control {
|
||||
border: 1px solid #8b8b8b;
|
||||
background-gradient-direction: vertical;
|
||||
background-gradient-start: rgba(255, 255, 255, 0.2);
|
||||
background-gradient-end: rgba(255, 255, 255, 0);
|
||||
}
|
||||
|
||||
.dash-search-button,
|
||||
.modal-dialog-button {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dash-search-button:hover,
|
||||
.candidate-page-button:hover,
|
||||
.notification-button:hover,
|
||||
.notification-icon-button:hover,
|
||||
.hotplug-notification-item:hover,
|
||||
@ -313,27 +317,37 @@ StScrollBar StButton#vhandle:active {
|
||||
background-gradient-end: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.dash-search-button:selected,
|
||||
.dash-search-button:focus,
|
||||
.notification-button:focus,
|
||||
.notification-icon-button:focus,
|
||||
.hotplug-notification-item:focus,
|
||||
.modal-dialog-button:focus {
|
||||
.modal-dialog-button:focus,
|
||||
.app-view-control:focus {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.dash-search-button:active,
|
||||
.dash-search-button:pressed,
|
||||
.app-view-control:focus {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.app-view-control:first-child:ltr:focus,
|
||||
.app-view-control:last-child:rtl:focus {
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
||||
.candidate-page-button:active,
|
||||
.candidate-page-button:pressed,
|
||||
.notification-button:active,
|
||||
.notification-icon-button:active,
|
||||
.hotplug-notification-item:active,
|
||||
.hotplug-resident-eject-button:active,
|
||||
.modal-dialog-button:active,
|
||||
.modal-dialog-button:pressed {
|
||||
.modal-dialog-button:pressed,
|
||||
.app-view-control:checked {
|
||||
background-gradient-start: rgba(255, 255, 255, 0);
|
||||
background-gradient-end: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.candidate-page-button:insensitive,
|
||||
.notification-button:insensitive,
|
||||
.notification-icon-button:insensitive,
|
||||
.modal-dialog-button:insensitive {
|
||||
@ -343,6 +357,27 @@ StScrollBar StButton#vhandle:active {
|
||||
background-color: rgba(102, 102, 102, 0.15);
|
||||
}
|
||||
|
||||
/* Common radii */
|
||||
|
||||
#searchEntry,
|
||||
.modal-dialog-button,
|
||||
.notification-button,
|
||||
.hotplug-notification-item,
|
||||
.app-view-controls {
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
.app-view-control:first-child:ltr,
|
||||
.app-view-control:last-child:rtl {
|
||||
border-radius: 18px 0px 0px 18px;
|
||||
border-right-width: 0px;
|
||||
}
|
||||
|
||||
.app-view-control:last-child:ltr,
|
||||
.app-view-control:first-child:rtl {
|
||||
border-radius: 0px 18px 18px 0px;
|
||||
}
|
||||
|
||||
/* Entries */
|
||||
|
||||
#searchEntry,
|
||||
@ -429,17 +464,14 @@ StScrollBar StButton#vhandle:active {
|
||||
background-color: black;
|
||||
font-weight: bold;
|
||||
height: 1.86em;
|
||||
transition-duration: 250ms;
|
||||
}
|
||||
|
||||
#panel.lock-screen {
|
||||
height: 2em;
|
||||
background-color: rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
#panel.unlock-screen,
|
||||
#panel.login-screen {
|
||||
height: 2em;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@ -484,6 +516,7 @@ StScrollBar StButton#vhandle:active {
|
||||
}
|
||||
|
||||
#appMenu {
|
||||
spinner-image: url("process-working.svg");
|
||||
spacing: 4px;
|
||||
}
|
||||
|
||||
@ -560,7 +593,7 @@ StScrollBar StButton#vhandle:active {
|
||||
}
|
||||
|
||||
.panel-menu {
|
||||
-boxpointer-gap: 4px
|
||||
-boxpointer-gap: 4px;
|
||||
}
|
||||
|
||||
.panel-status-button-box {
|
||||
@ -629,20 +662,8 @@ StScrollBar StButton#vhandle:active {
|
||||
spacing: 24px;
|
||||
}
|
||||
|
||||
#overview-group {
|
||||
spacing: 32px;
|
||||
}
|
||||
|
||||
.workspaces-display {
|
||||
spacing: 32px; /* needs to be the same value as #overview-group */
|
||||
}
|
||||
|
||||
.window-caption {
|
||||
spacing: 25px;
|
||||
}
|
||||
|
||||
.workspace-controls {
|
||||
visible-width: 32px; /* Amount visible before hovering */
|
||||
.overview-controls {
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
|
||||
.workspace-thumbnails-background {
|
||||
@ -662,6 +683,7 @@ StScrollBar StButton#vhandle:active {
|
||||
|
||||
.workspace-thumbnails {
|
||||
spacing: 11px;
|
||||
visible-width: 32px; /* Amount visible before hovering */
|
||||
}
|
||||
|
||||
.workspace-thumbnail-indicator {
|
||||
@ -671,6 +693,7 @@ StScrollBar StButton#vhandle:active {
|
||||
}
|
||||
|
||||
.window-caption {
|
||||
spacing: 25px;
|
||||
background: rgba(0,0,0,0.5);
|
||||
border-radius: 8px;
|
||||
padding: 4px 12px;
|
||||
@ -678,6 +701,10 @@ StScrollBar StButton#vhandle:active {
|
||||
border: 2px solid rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.window-caption:hover {
|
||||
border: 2px solid rgba(255, 255, 255, 0);
|
||||
}
|
||||
|
||||
.window-close, .notification-close {
|
||||
background-image: url("close-window.svg");
|
||||
background-size: 32px;
|
||||
@ -689,10 +716,6 @@ StScrollBar StButton#vhandle:active {
|
||||
-shell-close-overlap: 16px;
|
||||
}
|
||||
|
||||
.window-caption:hover {
|
||||
border: 2px solid rgba(255, 255, 255, 0);
|
||||
}
|
||||
|
||||
.window-clone-border {
|
||||
border: 4px solid rgba(255, 255, 255, 0.5);
|
||||
border-radius: 4px;
|
||||
@ -725,12 +748,38 @@ StScrollBar StButton#vhandle:active {
|
||||
.window-picker {
|
||||
-horizontal-spacing: 32px;
|
||||
-vertical-spacing: 32px;
|
||||
padding-left: 32px;
|
||||
padding-right: 32px;
|
||||
padding-bottom: 48px;
|
||||
}
|
||||
|
||||
.window-picker.external-monitor {
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.messages-indicator {
|
||||
color: #999999;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.messages-indicator-contents {
|
||||
spacing: 12px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.messages-indicator-contents:hover {
|
||||
color: white;
|
||||
text-shadow: black 0px 2px 2px;
|
||||
}
|
||||
|
||||
.messages-indicator-highlight {
|
||||
background-gradient-start: transparent;
|
||||
background-gradient-end: #999999;
|
||||
background-gradient-direction: vertical;
|
||||
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
/* Dash */
|
||||
|
||||
#dash {
|
||||
@ -758,7 +807,6 @@ StScrollBar StButton#vhandle:active {
|
||||
/* Search Box */
|
||||
|
||||
#searchEntry {
|
||||
border-radius: 17px;
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
@ -775,7 +823,7 @@ StScrollBar StButton#vhandle:active {
|
||||
/* Search Results */
|
||||
|
||||
#searchResults {
|
||||
padding: 20px 10px 10px 10px;
|
||||
padding: 20px 10px 0px 10px;
|
||||
spacing: 18px;
|
||||
}
|
||||
|
||||
@ -784,15 +832,11 @@ StScrollBar StButton#vhandle:active {
|
||||
}
|
||||
|
||||
#searchResultsContent {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
spacing: 16px;
|
||||
}
|
||||
|
||||
#searchResultsContent:rtl {
|
||||
padding-right: 0px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
/* This should be equal to #searchResultsContent spacing */
|
||||
spacing: 16px;
|
||||
@ -821,24 +865,6 @@ StScrollBar StButton#vhandle:active {
|
||||
spacing: 3px;
|
||||
}
|
||||
|
||||
/* Text labels are an odd number of pixels tall. The uneven top and bottom
|
||||
* padding compensates for this and ensures that the label is vertically
|
||||
* centered */
|
||||
.dash-search-button {
|
||||
border-radius: 16px;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 5px;
|
||||
width: 300px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dash-search-button:focus,
|
||||
.dash-search-button:selected {
|
||||
padding-top: 3px;
|
||||
padding-bottom: 4px;
|
||||
width: 298px;
|
||||
}
|
||||
|
||||
.dash-label {
|
||||
border-radius: 7px;
|
||||
padding: 4px 12px;
|
||||
@ -859,40 +885,29 @@ StScrollBar StButton#vhandle:active {
|
||||
icon-size: 96px;
|
||||
}
|
||||
|
||||
.all-app {
|
||||
padding: 16px 25px 16px 16px;
|
||||
.app-display {
|
||||
padding: 8px;
|
||||
spacing: 20px;
|
||||
}
|
||||
|
||||
.all-app:rtl {
|
||||
padding-right: 16px;
|
||||
padding-left: 25px;
|
||||
.app-view-controls {
|
||||
width: 250px;
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
|
||||
.app-filter {
|
||||
font-weight: bold;
|
||||
height: 2.85em;
|
||||
color: #aaa;
|
||||
width: 200px;
|
||||
.app-view-control {
|
||||
padding: 4px 16px;
|
||||
}
|
||||
|
||||
.app-filter:hover {
|
||||
color: #eee;
|
||||
.search-display > StBoxLayout,
|
||||
.all-apps > StBoxLayout,
|
||||
.frequent-apps > StBoxLayout {
|
||||
/* horizontal padding to make sure scrollbars or dash don't overlap content */
|
||||
padding: 0px 88px;
|
||||
}
|
||||
|
||||
.app-filter:selected {
|
||||
color: #ffffff;
|
||||
background-image: url("filter-selected-ltr.svg");
|
||||
background-position: 190px 10px;
|
||||
}
|
||||
|
||||
.app-filter:selected:rtl {
|
||||
background-image: url("filter-selected-rtl.svg");
|
||||
background-position: 10px 10px;
|
||||
}
|
||||
|
||||
.app-filter:focus {
|
||||
outline: 1px solid #aaa;
|
||||
.app-folder-icon {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.dash-item-container > StButton {
|
||||
@ -911,8 +926,6 @@ StScrollBar StButton#vhandle:active {
|
||||
}
|
||||
|
||||
.list-search-result-description {
|
||||
font-weight: bold;
|
||||
font-size: 12pt;
|
||||
color: #eeeeec;
|
||||
}
|
||||
|
||||
@ -934,12 +947,27 @@ StScrollBar StButton#vhandle:active {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.app-folder-popup {
|
||||
-arrow-border-radius: 8px;
|
||||
-arrow-background-color: black;
|
||||
-arrow-base: 24px;
|
||||
-arrow-rise: 11px;
|
||||
}
|
||||
|
||||
.app-folder-popup-bin {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.app-well-app.running > .overview-icon {
|
||||
text-shadow: black 0px 2px 2px;
|
||||
background-image: url("running-indicator.svg");
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.app-well-app.app-folder > .overview-icon {
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.app-well-app:hover > .overview-icon,
|
||||
.show-apps:hover > .overview-icon,
|
||||
.search-provider-icon:hover,
|
||||
@ -951,6 +979,14 @@ StScrollBar StButton#vhandle:active {
|
||||
color:white;
|
||||
}
|
||||
|
||||
.app-display .app-well-app > .overview-icon {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.list-search-result:hover .list-search-result-description {
|
||||
text-shadow: rgba(0,0,0,0.8) 0px 1px 2px;
|
||||
}
|
||||
|
||||
.show-apps {
|
||||
padding: 4px 0;
|
||||
}
|
||||
@ -963,6 +999,8 @@ StScrollBar StButton#vhandle:active {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.app-well-app:checked > .overview-icon,
|
||||
.app-well-app:active > .overview-icon,
|
||||
.show-apps:checked > .overview-icon,
|
||||
.show-apps:active > .overview-icon {
|
||||
background-gradient-start: rgba(255, 255, 255, .05);
|
||||
@ -1150,7 +1188,8 @@ StScrollBar StButton#vhandle:active {
|
||||
background-image: url("calendar-arrow-right.svg");
|
||||
}
|
||||
|
||||
.calendar-change-month-back:hover {
|
||||
.calendar-change-month-back:hover,
|
||||
.calendar-change-month-back:focus {
|
||||
background-color: #999999;
|
||||
}
|
||||
.calendar-change-month-back:active {
|
||||
@ -1168,7 +1207,8 @@ StScrollBar StButton#vhandle:active {
|
||||
background-image: url("calendar-arrow-left.svg");
|
||||
}
|
||||
|
||||
.calendar-change-month-forward:hover {
|
||||
.calendar-change-month-forward:hover,
|
||||
.calendar-change-month-forward:focus {
|
||||
background-color: #999999;
|
||||
}
|
||||
.calendar-change-month-forward:active {
|
||||
@ -1189,7 +1229,8 @@ StScrollBar StButton#vhandle:active {
|
||||
height: 2.4em;
|
||||
}
|
||||
|
||||
.calendar-day-base:hover {
|
||||
.calendar-day-base:hover,
|
||||
.calendar-day-base:focus {
|
||||
background-color: #777777;
|
||||
}
|
||||
|
||||
@ -1239,15 +1280,15 @@ StScrollBar StButton#vhandle:active {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.calendar-other-month-day {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.calendar-day-with-events {
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.calendar-other-month-day {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.events-header-vbox {
|
||||
spacing: 6pt;
|
||||
padding-right: .5em;
|
||||
@ -1321,16 +1362,6 @@ StScrollBar StButton#vhandle:active {
|
||||
height: 72px;
|
||||
}
|
||||
|
||||
#message-tray:keyboard {
|
||||
/* Same as the OSK */
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
#message-tray:overview {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
outline: 1px solid rgba(128, 128, 128, 0.3);
|
||||
}
|
||||
|
||||
.message-tray-summary {
|
||||
height: 72px;
|
||||
}
|
||||
@ -1430,12 +1461,13 @@ StScrollBar StButton#vhandle:active {
|
||||
}
|
||||
|
||||
.notification-button {
|
||||
border-radius: 18px;
|
||||
padding: 4px 42px 5px;
|
||||
-st-natural-width: 140px;
|
||||
padding: 4px 4px 5px;
|
||||
}
|
||||
|
||||
.notification-button:focus {
|
||||
padding: 3px 41px 4px;
|
||||
-st-natural-width: 138px;
|
||||
padding: 3px 4px 4px;
|
||||
}
|
||||
|
||||
.notification-icon-button {
|
||||
@ -1463,7 +1495,6 @@ StScrollBar StButton#vhandle:active {
|
||||
|
||||
.hotplug-notification-item {
|
||||
padding: 2px 10px;
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
.hotplug-notification-item:focus {
|
||||
@ -1602,12 +1633,26 @@ StScrollBar StButton#vhandle:active {
|
||||
-shell-counter-overlap-y: 13px;
|
||||
}
|
||||
|
||||
/* OSD */
|
||||
.osd-window {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
spacing: 1em;
|
||||
}
|
||||
|
||||
.osd-window .level {
|
||||
height: 0.6em;
|
||||
border-radius: 0.3em;
|
||||
background-color: rgba(190,190,190,0.2);
|
||||
}
|
||||
|
||||
/* App Switcher */
|
||||
.switcher-popup {
|
||||
padding: 8px;
|
||||
spacing: 16px;
|
||||
}
|
||||
|
||||
.osd-window,
|
||||
.switcher-list {
|
||||
background: rgba(0,0,0,0.8);
|
||||
border: 1px solid rgba(128,128,128,0.40);
|
||||
@ -1759,8 +1804,6 @@ StScrollBar StButton#vhandle:active {
|
||||
}
|
||||
|
||||
.modal-dialog-button {
|
||||
border-radius: 18px;
|
||||
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
padding: 4px 32px 5px;
|
||||
@ -2111,17 +2154,53 @@ StScrollBar StButton#vhandle:active {
|
||||
}
|
||||
|
||||
/* IBus Candidate Popup */
|
||||
|
||||
.candidate-popup-content {
|
||||
padding: 0.5em;
|
||||
spacing: 0.3em;
|
||||
}
|
||||
|
||||
.candidate-index {
|
||||
padding: 0.5em 0.5em 0.5em 0.5em;
|
||||
padding: 0 0.5em 0 0;
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
.candidate-label {
|
||||
padding: 0.5em 0.5em 0.5em 0.5em;
|
||||
.candidate-box {
|
||||
padding: 0.3em 0.5em 0.3em 0.5em;
|
||||
}
|
||||
|
||||
.candidate-label:selected {
|
||||
.candidate-box:selected {
|
||||
border-radius: 4px;
|
||||
background-color: rgba(255,255,255,0.33);
|
||||
background-color: rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
.candidate-box:hover {
|
||||
border-radius: 4px;
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
}
|
||||
.candidate-page-button-box {
|
||||
height: 2em;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.vertical .candidate-page-button-box {
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
.horizontal .candidate-page-button-box {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
.candidate-page-button-previous {
|
||||
border-radius: 4px 0px 0px 4px;
|
||||
}
|
||||
|
||||
.candidate-page-button-next {
|
||||
border-radius: 0px 4px 4px 0px;
|
||||
}
|
||||
|
||||
.candidate-page-button-icon {
|
||||
icon-size: 1em;
|
||||
}
|
||||
|
||||
/* Login Dialog */
|
||||
@ -2146,6 +2225,9 @@ StScrollBar StButton#vhandle:active {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
|
||||
padding-bottom: 80px;
|
||||
padding-top: 80px;
|
||||
|
||||
border-radius: 16px;
|
||||
min-height: 150px;
|
||||
max-height: 700px;
|
||||
@ -2263,52 +2345,47 @@ StScrollBar StButton#vhandle:active {
|
||||
width: 15em;
|
||||
}
|
||||
|
||||
.login-dialog-session-list {
|
||||
color: #ffffff;
|
||||
font-size: 10.5pt;
|
||||
.login-dialog-session-list,
|
||||
.login-dialog-session-list-item {
|
||||
color: #babdb6;
|
||||
}
|
||||
|
||||
.login-dialog-session-list-button:focus,
|
||||
.login-dialog-session-list-button:active,
|
||||
.login-dialog-session-list-button:hover,
|
||||
.login-dialog-session-list-item:focus,
|
||||
.login-dialog-session-list-item:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.login-dialog-session-list-button {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.login-dialog-session-list-button:focus {
|
||||
background-color: #4c4c4c;
|
||||
}
|
||||
|
||||
.login-dialog-session-list-button:active {
|
||||
background-color: #4c4c4c;
|
||||
}
|
||||
|
||||
.login-dialog-session-list-button:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.login-dialog-session-list-scroll-view {
|
||||
background-gradient-start: rgba(80,80,80,0.3);
|
||||
background-gradient-end: rgba(80,80,80,0.7);
|
||||
background-gradient-direction: vertical;
|
||||
box-shadow: inset 0px 2px 4px rgba(0,0,0,0.9);
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(80,80,80,1.0);
|
||||
padding: .5em;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.login-dialog-session-list-item:focus {
|
||||
background-color: #666666;
|
||||
.login-dialog-session-list-item {
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.login-dialog-session-list-triangle {
|
||||
padding-right: .5em;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
.login-dialog-session-list-item-box {
|
||||
spacing: .25em;
|
||||
padding-left: 6px;
|
||||
spacing: 6px;
|
||||
}
|
||||
|
||||
.login-dialog-session-list-item-dot {
|
||||
width: .75em;
|
||||
height: .75em;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.login-dialog-logo-bin {
|
||||
padding: 24px 0px;
|
||||
}
|
||||
|
||||
.login-dialog .modal-dialog-button-box {
|
||||
@ -2357,10 +2434,18 @@ StScrollBar StButton#vhandle:active {
|
||||
color: orange;
|
||||
}
|
||||
|
||||
.unlock-dialog-user-name-container {
|
||||
.user-widget {
|
||||
spacing: .4em;
|
||||
}
|
||||
|
||||
.user-widget-label {
|
||||
font-size: 16pt;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
padding-left: 15px;
|
||||
text-shadow: black 0px 4px 3px 0px;
|
||||
}
|
||||
|
||||
/* Screen shield */
|
||||
|
||||
.screen-shield-background {
|
||||
@ -2452,7 +2537,13 @@ StScrollBar StButton#vhandle:active {
|
||||
}
|
||||
|
||||
.input-source-switcher-symbol {
|
||||
font-size: 42pt;
|
||||
font-size: 34pt;
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
}
|
||||
|
||||
/* Background menu */
|
||||
|
||||
.background-menu {
|
||||
-boxpointer-gap: 4px;
|
||||
}
|
||||
|
BIN
data/wanda.png
Normal file
BIN
data/wanda.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
@ -46,6 +46,7 @@
|
||||
<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"/>
|
||||
|
@ -66,11 +66,4 @@ its dependencies to build from tarballs.</description>
|
||||
<gnome:userid>fmuellner</gnome:userid>
|
||||
</foaf:Person>
|
||||
</maintainer>
|
||||
<maintainer>
|
||||
<foaf:Person>
|
||||
<foaf:name>Ray Strode</foaf:name>
|
||||
<foaf:mbox rdf:resource="mailto:halfline@gmail.com" />
|
||||
<gnome:userid>halfline</gnome:userid>
|
||||
</foaf:Person>
|
||||
</maintainer>
|
||||
</Project>
|
||||
|
@ -39,6 +39,8 @@ nobase_dist_js_DATA = \
|
||||
ui/altTab.js \
|
||||
ui/appDisplay.js \
|
||||
ui/appFavorites.js \
|
||||
ui/backgroundMenu.js \
|
||||
ui/background.js \
|
||||
ui/boxpointer.js \
|
||||
ui/calendar.js \
|
||||
ui/checkBox.js \
|
||||
@ -67,13 +69,17 @@ nobase_dist_js_DATA = \
|
||||
ui/shellEntry.js \
|
||||
ui/shellMountOperation.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 \
|
||||
@ -91,6 +97,7 @@ nobase_dist_js_DATA = \
|
||||
ui/tweener.js \
|
||||
ui/unlockDialog.js \
|
||||
ui/userMenu.js \
|
||||
ui/userWidget.js \
|
||||
ui/viewSelector.js \
|
||||
ui/wanda.js \
|
||||
ui/windowAttentionHandler.js \
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Lang = imports.lang;
|
||||
const Pango = imports.gi.Pango;
|
||||
const Realmd = imports.gdm.realmd;
|
||||
const Signals = imports.signals;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
@ -39,85 +40,18 @@ const GdmUtil = imports.gdm.util;
|
||||
const Lightbox = imports.ui.lightbox;
|
||||
const Main = imports.ui.main;
|
||||
const ModalDialog = imports.ui.modalDialog;
|
||||
const Panel = imports.ui.panel;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const Tweener = imports.ui.tweener;
|
||||
const UserMenu = imports.ui.userMenu;
|
||||
const UserWidget = imports.ui.userWidget;
|
||||
|
||||
const _RESIZE_ANIMATION_TIME = 0.25;
|
||||
const _FADE_ANIMATION_TIME = 0.25;
|
||||
const _SCROLL_ANIMATION_TIME = 0.5;
|
||||
const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0;
|
||||
const _LOGO_ICON_HEIGHT = 16;
|
||||
|
||||
const WORK_SPINNER_ICON_SIZE = 24;
|
||||
const WORK_SPINNER_ANIMATION_DELAY = 1.0;
|
||||
const WORK_SPINNER_ANIMATION_TIME = 0.3;
|
||||
const _LOGO_ICON_HEIGHT = 48;
|
||||
|
||||
let _loginDialog = null;
|
||||
|
||||
function _smoothlyResizeActor(actor, width, height) {
|
||||
let finalWidth;
|
||||
let finalHeight;
|
||||
|
||||
if (width < 0)
|
||||
finalWidth = actor.width;
|
||||
else
|
||||
finalWidth = width;
|
||||
|
||||
if (height < 0)
|
||||
finalHeight = actor.height;
|
||||
else
|
||||
finalHeight = height;
|
||||
|
||||
actor.set_size(actor.width, actor.height);
|
||||
|
||||
if (actor.width == finalWidth && actor.height == finalHeight)
|
||||
return null;
|
||||
|
||||
let hold = new Batch.Hold();
|
||||
|
||||
Tweener.addTween(actor,
|
||||
{ width: finalWidth,
|
||||
height: finalHeight,
|
||||
time: _RESIZE_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this, function() {
|
||||
hold.release();
|
||||
})
|
||||
});
|
||||
return hold;
|
||||
}
|
||||
|
||||
const LogoMenuButton = new Lang.Class({
|
||||
Name: 'LogoMenuButton',
|
||||
Extends: PanelMenu.Button,
|
||||
|
||||
_init: function() {
|
||||
this.parent(0.0, null, true);
|
||||
|
||||
this._settings = new Gio.Settings({ schema: GdmUtil.LOGIN_SCREEN_SCHEMA });
|
||||
this._settings.connect('changed::' + GdmUtil.LOGO_KEY,
|
||||
Lang.bind(this, this._updateLogo));
|
||||
|
||||
this._iconBin = new St.Bin();
|
||||
this.actor.add_actor(this._iconBin);
|
||||
|
||||
this._updateLogo();
|
||||
},
|
||||
|
||||
_updateLogo: function() {
|
||||
let path = this._settings.get_string(GdmUtil.LOGO_KEY);
|
||||
let icon = null;
|
||||
|
||||
if (path) {
|
||||
let file = Gio.file_new_for_path(path);
|
||||
let cache = St.TextureCache.get_default();
|
||||
icon = cache.load_uri_async(file.get_uri(), -1, _LOGO_ICON_HEIGHT);
|
||||
}
|
||||
this._iconBin.set_child(icon);
|
||||
}
|
||||
});
|
||||
|
||||
const UserListItem = new Lang.Class({
|
||||
Name: 'UserListItem',
|
||||
|
||||
@ -246,155 +180,23 @@ const UserList = new Lang.Class({
|
||||
}
|
||||
},
|
||||
|
||||
_showItem: function(item) {
|
||||
let tasks = [function() {
|
||||
return GdmUtil.fadeInActor(item.actor);
|
||||
}];
|
||||
|
||||
let batch = new Batch.ConsecutiveBatch(this, tasks);
|
||||
return batch.run();
|
||||
},
|
||||
|
||||
_onItemActivated: function(activatedItem) {
|
||||
this.emit('activate', activatedItem);
|
||||
},
|
||||
|
||||
giveUpWhitespace: function() {
|
||||
let container = this.actor.get_parent();
|
||||
|
||||
container.child_set(this.actor, { expand: false });
|
||||
},
|
||||
|
||||
takeOverWhitespace: function() {
|
||||
let container = this.actor.get_parent();
|
||||
|
||||
container.child_set(this.actor, { expand: true });
|
||||
},
|
||||
|
||||
pinInPlace: function() {
|
||||
this._box.set_size(this._box.width, this._box.height);
|
||||
},
|
||||
|
||||
shrinkToNaturalHeight: function() {
|
||||
let oldWidth = this._box.width;
|
||||
let oldHeight = this._box.height;
|
||||
this._box.set_size(-1, -1);
|
||||
let [minHeight, naturalHeight] = this._box.get_preferred_height(-1);
|
||||
this._box.set_size(oldWidth, oldHeight);
|
||||
|
||||
let batch = new Batch.ConsecutiveBatch(this,
|
||||
[function() {
|
||||
return _smoothlyResizeActor(this._box, -1, naturalHeight);
|
||||
},
|
||||
|
||||
function() {
|
||||
this._box.set_size(-1, -1);
|
||||
}
|
||||
]);
|
||||
|
||||
return batch.run();
|
||||
},
|
||||
|
||||
hideItemsExcept: function(exception) {
|
||||
updateStyle: function(isExpanded) {
|
||||
let tasks = [];
|
||||
|
||||
for (let userName in this._items) {
|
||||
let item = this._items[userName];
|
||||
|
||||
item.actor.set_hover(false);
|
||||
item.actor.reactive = false;
|
||||
item.actor.can_focus = false;
|
||||
item.syncStyleClasses();
|
||||
item._timedLoginIndicator.scale_x = 0.;
|
||||
if (item != exception)
|
||||
tasks.push(function() {
|
||||
return GdmUtil.fadeOutActor(item.actor);
|
||||
});
|
||||
}
|
||||
|
||||
let batch = new Batch.ConsecutiveBatch(this,
|
||||
[function() {
|
||||
return GdmUtil.fadeOutActor(this.actor.vscroll);
|
||||
},
|
||||
|
||||
new Batch.ConcurrentBatch(this, tasks),
|
||||
|
||||
function() {
|
||||
this._box.remove_style_pseudo_class('expanded');
|
||||
}
|
||||
]);
|
||||
|
||||
return batch.run();
|
||||
},
|
||||
|
||||
hideItems: function() {
|
||||
return this.hideItemsExcept(null);
|
||||
},
|
||||
|
||||
_getExpandedHeight: function() {
|
||||
let hiddenActors = [];
|
||||
for (let userName in this._items) {
|
||||
let item = this._items[userName];
|
||||
if (!item.actor.visible) {
|
||||
item.actor.show();
|
||||
hiddenActors.push(item.actor);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._box.visible) {
|
||||
this._box.show();
|
||||
hiddenActors.push(this._box);
|
||||
}
|
||||
|
||||
this._box.set_size(-1, -1);
|
||||
let [minHeight, naturalHeight] = this._box.get_preferred_height(-1);
|
||||
|
||||
for (let i = 0; i < hiddenActors.length; i++) {
|
||||
let actor = hiddenActors[i];
|
||||
actor.hide();
|
||||
}
|
||||
|
||||
return naturalHeight;
|
||||
},
|
||||
|
||||
showItems: function() {
|
||||
let tasks = [];
|
||||
if (isExpanded)
|
||||
this._box.add_style_pseudo_class('expanded');
|
||||
else
|
||||
this._box.remove_style_pseudo_class('expanded');
|
||||
|
||||
for (let userName in this._items) {
|
||||
let item = this._items[userName];
|
||||
item.actor.sync_hover();
|
||||
item.actor.reactive = true;
|
||||
item.actor.can_focus = true;
|
||||
item.syncStyleClasses();
|
||||
tasks.push(function() {
|
||||
return this._showItem(item);
|
||||
});
|
||||
}
|
||||
|
||||
let batch = new Batch.ConsecutiveBatch(this,
|
||||
[function() {
|
||||
this.takeOverWhitespace();
|
||||
},
|
||||
|
||||
function() {
|
||||
let fullHeight = this._getExpandedHeight();
|
||||
return _smoothlyResizeActor(this._box, -1, fullHeight);
|
||||
},
|
||||
|
||||
function() {
|
||||
this._box.add_style_pseudo_class('expanded');
|
||||
},
|
||||
|
||||
new Batch.ConcurrentBatch(this, tasks),
|
||||
|
||||
function() {
|
||||
this.actor.set_size(-1, -1);
|
||||
},
|
||||
|
||||
function() {
|
||||
return GdmUtil.fadeInActor(this.actor.vscroll);
|
||||
}]);
|
||||
return batch.run();
|
||||
},
|
||||
|
||||
scrollToItem: function(item) {
|
||||
@ -566,7 +368,7 @@ const SessionList = new Lang.Class({
|
||||
box.add_actor(this._triangle);
|
||||
|
||||
let label = new St.Label({ style_class: 'login-dialog-session-list-label',
|
||||
text: _("Session...") });
|
||||
text: _("Session…") });
|
||||
box.add_actor(label);
|
||||
|
||||
this._button.connect('clicked',
|
||||
@ -716,25 +518,27 @@ const LoginDialog = new Lang.Class({
|
||||
Lang.bind(this, this._updateBanner));
|
||||
this._settings.connect('changed::' + GdmUtil.DISABLE_USER_LIST_KEY,
|
||||
Lang.bind(this, this._updateDisableUserList));
|
||||
this._settings.connect('changed::' + GdmUtil.LOGO_KEY,
|
||||
Lang.bind(this, this._updateLogo));
|
||||
|
||||
this._textureCache = St.TextureCache.get_default();
|
||||
this._textureCache.connect('texture-file-changed',
|
||||
Lang.bind(this, this._updateLogoTexture));
|
||||
|
||||
this._userSelectionBox = new St.BoxLayout({ style_class: 'login-dialog-user-selection-box',
|
||||
vertical: true });
|
||||
this.contentLayout.add(this._userSelectionBox);
|
||||
|
||||
this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner',
|
||||
text: '' });
|
||||
this.contentLayout.add(this._bannerLabel);
|
||||
this._userSelectionBox.add(this._bannerLabel);
|
||||
this._updateBanner();
|
||||
|
||||
this._titleLabel = new St.Label({ style_class: 'login-dialog-title',
|
||||
text: C_("title", "Sign In"),
|
||||
visible: false });
|
||||
|
||||
this.contentLayout.add(this._titleLabel,
|
||||
{ y_fill: false,
|
||||
y_align: St.Align.START });
|
||||
|
||||
this._userList = new UserList();
|
||||
this.contentLayout.add(this._userList.actor,
|
||||
{ expand: true,
|
||||
x_fill: true,
|
||||
y_fill: true });
|
||||
this._userSelectionBox.add(this._userList.actor,
|
||||
{ expand: true,
|
||||
x_fill: true,
|
||||
y_fill: true });
|
||||
|
||||
this.setInitialKeyFocus(this._userList.actor);
|
||||
|
||||
@ -745,6 +549,13 @@ const LoginDialog = new Lang.Class({
|
||||
x_fill: true,
|
||||
y_fill: true,
|
||||
x_align: St.Align.START });
|
||||
this._promptUser = new St.Bin({ x_fill: true,
|
||||
x_align: St.Align.START });
|
||||
this._promptBox.add(this._promptUser,
|
||||
{ x_align: St.Align.START,
|
||||
x_fill: true,
|
||||
y_fill: true,
|
||||
expand: true });
|
||||
this._promptLabel = new St.Label({ style_class: 'login-dialog-prompt-label' });
|
||||
|
||||
this._promptBox.add(this._promptLabel,
|
||||
@ -755,6 +566,7 @@ const LoginDialog = new Lang.Class({
|
||||
this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry',
|
||||
can_focus: true });
|
||||
this._promptEntryTextChangedId = 0;
|
||||
this._promptEntryActivateId = 0;
|
||||
this._promptBox.add(this._promptEntry,
|
||||
{ expand: true,
|
||||
x_fill: true,
|
||||
@ -769,7 +581,6 @@ const LoginDialog = new Lang.Class({
|
||||
this._promptBox.add(this._promptLoginHint);
|
||||
|
||||
this._signInButton = null;
|
||||
this._workSpinner = null;
|
||||
|
||||
this._sessionList = new SessionList();
|
||||
this._sessionList.connect('session-activated',
|
||||
@ -799,10 +610,15 @@ const LoginDialog = new Lang.Class({
|
||||
|
||||
this._notListedButton.connect('clicked', Lang.bind(this, this._hideUserListAndLogIn));
|
||||
|
||||
this.contentLayout.add(this._notListedButton,
|
||||
{ expand: false,
|
||||
x_align: St.Align.START,
|
||||
x_fill: true });
|
||||
this._userSelectionBox.add(this._notListedButton,
|
||||
{ expand: false,
|
||||
x_align: St.Align.START,
|
||||
x_fill: true });
|
||||
|
||||
this._logoBin = new St.Bin({ style_class: 'login-dialog-logo-bin', y_expand: true });
|
||||
this._logoBin.set_y_align(Clutter.ActorAlign.END);
|
||||
this.backgroundStack.add_actor(this._logoBin);
|
||||
this._updateLogo();
|
||||
|
||||
if (!this._userManager.is_loaded)
|
||||
this._userManagerLoadedId = this._userManager.connect('notify::is-loaded',
|
||||
@ -844,13 +660,33 @@ const LoginDialog = new Lang.Class({
|
||||
|
||||
if (enabled && text) {
|
||||
this._bannerLabel.set_text(text);
|
||||
this._fadeInBanner();
|
||||
this._bannerLabel.show();
|
||||
} else {
|
||||
this._fadeOutBanner();
|
||||
this._bannerLabel.hide();
|
||||
}
|
||||
},
|
||||
|
||||
_updateLogoTexture: function(cache, uri) {
|
||||
if (this._logoFileUri != uri)
|
||||
return;
|
||||
|
||||
let icon = null;
|
||||
if (this._logoFileUri)
|
||||
icon = this._textureCache.load_uri_async(this._logoFileUri,
|
||||
-1, _LOGO_ICON_HEIGHT);
|
||||
this._logoBin.set_child(icon);
|
||||
},
|
||||
|
||||
_updateLogo: function() {
|
||||
let path = this._settings.get_string(GdmUtil.LOGO_KEY);
|
||||
|
||||
this._logoFileUri = path ? Gio.file_new_for_path(path).get_uri() : null;
|
||||
this._updateLogoTexture(this._textureCache, this._logoFileUri);
|
||||
},
|
||||
|
||||
_reset: function() {
|
||||
this._userVerifier.clear();
|
||||
|
||||
this._updateSensitivity(true);
|
||||
this._promptMessage.hide();
|
||||
this._user = null;
|
||||
@ -866,7 +702,7 @@ const LoginDialog = new Lang.Class({
|
||||
this._promptEntry.text = '';
|
||||
|
||||
this._updateSensitivity(true);
|
||||
this._setWorking(false);
|
||||
this.setWorking(false);
|
||||
},
|
||||
|
||||
_onDefaultSessionChanged: function(client, sessionId) {
|
||||
@ -877,73 +713,50 @@ const LoginDialog = new Lang.Class({
|
||||
if (message) {
|
||||
this._promptMessage.text = message;
|
||||
this._promptMessage.styleClass = styleClass;
|
||||
GdmUtil.fadeInActor(this._promptMessage);
|
||||
this._promptMessage.show();
|
||||
} else {
|
||||
GdmUtil.fadeOutActor(this._promptMessage);
|
||||
this._promptMessage.hide();
|
||||
}
|
||||
},
|
||||
|
||||
_showLoginHint: function(verifier, message) {
|
||||
this._promptLoginHint.set_text(message)
|
||||
GdmUtil.fadeInActor(this._promptLoginHint);
|
||||
this._promptLoginHint.show();
|
||||
this._promptLoginHint.opacity = 255;
|
||||
},
|
||||
|
||||
_hideLoginHint: function() {
|
||||
GdmUtil.fadeOutActor(this._promptLoginHint);
|
||||
this._promptLoginHint.hide();
|
||||
this._promptLoginHint.set_text('');
|
||||
},
|
||||
|
||||
cancel: function() {
|
||||
this._userVerifier.cancel();
|
||||
},
|
||||
|
||||
_fadeInPrompt: function() {
|
||||
let tasks = [function() {
|
||||
return GdmUtil.fadeInActor(this._promptLabel);
|
||||
},
|
||||
|
||||
function() {
|
||||
return GdmUtil.fadeInActor(this._promptEntry);
|
||||
},
|
||||
|
||||
function() {
|
||||
// Show it with 0 opacity so we preallocate space for it
|
||||
// in the event we need to fade in the message
|
||||
this._promptLoginHint.opacity = 0;
|
||||
this._promptLoginHint.show();
|
||||
},
|
||||
|
||||
function() {
|
||||
return GdmUtil.fadeInActor(this._promptBox);
|
||||
},
|
||||
|
||||
function() {
|
||||
if (this._user && this._user.is_logged_in())
|
||||
return null;
|
||||
|
||||
if (!this._verifyingUser)
|
||||
return null;
|
||||
|
||||
return GdmUtil.fadeInActor(this._sessionList.actor);
|
||||
},
|
||||
|
||||
function() {
|
||||
this._promptEntry.grab_key_focus();
|
||||
}];
|
||||
|
||||
this._sessionList.actor.hide();
|
||||
let batch = new Batch.ConcurrentBatch(this, tasks);
|
||||
return batch.run();
|
||||
if (this._verifyingUser)
|
||||
this._userVerifier.cancel();
|
||||
else
|
||||
this._reset();
|
||||
},
|
||||
|
||||
_showPrompt: function(forSecret) {
|
||||
this._sessionList.actor.hide();
|
||||
this._promptLabel.show();
|
||||
this._promptEntry.show();
|
||||
this._promptLoginHint.opacity = 0;
|
||||
this._promptLoginHint.show();
|
||||
this._promptBox.opacity = 0;
|
||||
this._promptBox.show();
|
||||
Tweener.addTween(this._promptBox,
|
||||
{ opacity: 255,
|
||||
time: _FADE_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad' });
|
||||
|
||||
if ((this._user && !this._user.is_logged_in()) || this._verifyingUser)
|
||||
this._sessionList.actor.show();
|
||||
|
||||
this._promptEntry.grab_key_focus();
|
||||
|
||||
let hold = new Batch.Hold();
|
||||
|
||||
let tasks = [function() {
|
||||
return this._fadeInPrompt();
|
||||
},
|
||||
|
||||
function() {
|
||||
this._prepareDialog(forSecret, hold);
|
||||
},
|
||||
|
||||
@ -955,10 +768,6 @@ const LoginDialog = new Lang.Class({
|
||||
},
|
||||
|
||||
_prepareDialog: function(forSecret, hold) {
|
||||
this._workSpinner = new Panel.AnimatedIcon('process-working.svg', WORK_SPINNER_ICON_SIZE);
|
||||
this._workSpinner.actor.opacity = 0;
|
||||
this._workSpinner.actor.show();
|
||||
|
||||
this.buttonLayout.visible = true;
|
||||
this.clearButtons();
|
||||
|
||||
@ -971,12 +780,11 @@ const LoginDialog = new Lang.Class({
|
||||
y_fill: false,
|
||||
x_align: St.Align.START,
|
||||
y_align: St.Align.MIDDLE });
|
||||
this.buttonLayout.add(this._workSpinner.actor,
|
||||
{ expand: false,
|
||||
x_fill: false,
|
||||
y_fill: false,
|
||||
x_align: St.Align.END,
|
||||
y_align: St.Align.MIDDLE });
|
||||
this.placeSpinner({ expand: false,
|
||||
x_fill: false,
|
||||
y_fill: false,
|
||||
x_align: St.Align.END,
|
||||
y_align: St.Align.MIDDLE });
|
||||
this._signInButton = this.addButton({ action: Lang.bind(this, function() {
|
||||
hold.release();
|
||||
}),
|
||||
@ -995,6 +803,11 @@ const LoginDialog = new Lang.Class({
|
||||
Lang.bind(this, function() {
|
||||
this._updateSignInButtonSensitivity(this._promptEntry.text.length > 0);
|
||||
}));
|
||||
|
||||
this._promptEntryActivateId =
|
||||
this._promptEntry.clutter_text.connect('activate', function() {
|
||||
hold.release();
|
||||
});
|
||||
},
|
||||
|
||||
_updateSensitivity: function(sensitive) {
|
||||
@ -1019,56 +832,31 @@ const LoginDialog = new Lang.Class({
|
||||
this._promptEntryTextChangedId = 0;
|
||||
}
|
||||
|
||||
let tasks = [function() {
|
||||
this._setWorking(false);
|
||||
|
||||
return GdmUtil.fadeOutActor(this._promptBox);
|
||||
},
|
||||
|
||||
function() {
|
||||
this._promptLoginHint.hide();
|
||||
|
||||
this._updateSensitivity(true);
|
||||
this._promptEntry.set_text('');
|
||||
|
||||
this.clearButtons();
|
||||
this._workSpinner = null;
|
||||
this._signInButton = null;
|
||||
}];
|
||||
|
||||
let batch = new Batch.ConsecutiveBatch(this, tasks);
|
||||
|
||||
return batch.run();
|
||||
},
|
||||
|
||||
_setWorking: function(working) {
|
||||
if (!this._workSpinner)
|
||||
return;
|
||||
|
||||
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() {
|
||||
this._workSpinner.stop();
|
||||
}
|
||||
});
|
||||
if (this._promptEntryActivateId > 0) {
|
||||
this._promptEntry.clutter_text.disconnect(this._promptEntryActivateId);
|
||||
this._promptEntryActivateId = 0;
|
||||
}
|
||||
|
||||
this.setWorking(false);
|
||||
this._promptBox.hide();
|
||||
this._promptLoginHint.hide();
|
||||
|
||||
this._promptUser.set_child(null);
|
||||
|
||||
this._updateSensitivity(true);
|
||||
this._promptEntry.set_text('');
|
||||
|
||||
this._sessionList.close();
|
||||
this._promptLoginHint.hide();
|
||||
|
||||
this.clearButtons();
|
||||
this._signInButton = null;
|
||||
},
|
||||
|
||||
_askQuestion: function(verifier, serviceName, question, passwordChar) {
|
||||
this._promptLabel.set_text(question);
|
||||
|
||||
this._updateSensitivity(true);
|
||||
this._promptEntry.set_text('');
|
||||
this._promptEntry.clutter_text.set_password_char(passwordChar);
|
||||
|
||||
@ -1079,7 +867,7 @@ const LoginDialog = new Lang.Class({
|
||||
function() {
|
||||
let text = this._promptEntry.get_text();
|
||||
this._updateSensitivity(false);
|
||||
this._setWorking(true);
|
||||
this.setWorking(true);
|
||||
this._userVerifier.answerQuery(serviceName, text);
|
||||
}];
|
||||
|
||||
@ -1087,25 +875,79 @@ const LoginDialog = new Lang.Class({
|
||||
return batch.run();
|
||||
},
|
||||
|
||||
_showRealmLoginHint: function(realmManager, hint) {
|
||||
if (!hint)
|
||||
return;
|
||||
|
||||
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._showLoginHint(null, _("(e.g., user or %s)").format(hint));
|
||||
},
|
||||
|
||||
_askForUsernameAndLogIn: function() {
|
||||
this._promptLabel.set_text(_("Username: "));
|
||||
this._promptEntry.set_text('');
|
||||
this._promptEntry.clutter_text.set_password_char('');
|
||||
|
||||
let realmManager = new Realmd.Manager();
|
||||
let signalId = realmManager.connect('login-format-changed',
|
||||
Lang.bind(this, this._showRealmLoginHint));
|
||||
this._showRealmLoginHint(realmManager.loginFormat);
|
||||
|
||||
let tasks = [this._showPrompt,
|
||||
|
||||
function() {
|
||||
let userName = this._promptEntry.get_text();
|
||||
this._promptEntry.reactive = false;
|
||||
return this._beginVerificationForUser(userName);
|
||||
},
|
||||
|
||||
function() {
|
||||
realmManager.disconnect(signalId)
|
||||
realmManager.release();
|
||||
}];
|
||||
|
||||
let batch = new Batch.ConsecutiveBatch(this, tasks);
|
||||
return batch.run();
|
||||
},
|
||||
|
||||
_startSession: function(serviceName) {
|
||||
Tweener.addTween(this.dialogLayout,
|
||||
{ opacity: 0,
|
||||
time: _FADE_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onUpdate: function() {
|
||||
let children = Main.layoutManager.uiGroup.get_children();
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
if (children[i] != Main.layoutManager.screenShieldGroup)
|
||||
children[i].opacity = this.dialogLayout.opacity;
|
||||
}
|
||||
},
|
||||
onUpdateScope: this,
|
||||
onComplete: function() {
|
||||
Mainloop.idle_add(Lang.bind(this, function() {
|
||||
this._greeter.call_start_session_when_ready_sync(serviceName, true, null);
|
||||
return false;
|
||||
}));
|
||||
},
|
||||
onCompleteScope: this });
|
||||
},
|
||||
|
||||
_onSessionOpened: function(client, serviceName) {
|
||||
this._greeter.call_start_session_when_ready_sync(serviceName, true, null);
|
||||
if (!this._userVerifier.hasPendingMessages) {
|
||||
this._startSession(serviceName);
|
||||
} else {
|
||||
let signalId = this._userVerifier.connect('no-more-messages',
|
||||
Lang.bind(this, function() {
|
||||
this._userVerifier.disconnect(signalId);
|
||||
this._startSession(serviceName);
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
_waitForItemForUser: function(userName) {
|
||||
@ -1171,7 +1013,7 @@ const LoginDialog = new Lang.Class({
|
||||
function() {
|
||||
// If we're just starting out, start on the right
|
||||
// item.
|
||||
if (!this.is_loaded) {
|
||||
if (!this._userManager.is_loaded) {
|
||||
this._userList.jumpToItem(this._timedLoginItem);
|
||||
}
|
||||
},
|
||||
@ -1232,75 +1074,21 @@ const LoginDialog = new Lang.Class({
|
||||
}));
|
||||
},
|
||||
|
||||
_setUserListExpanded: function(expanded) {
|
||||
this._userList.updateStyle(expanded);
|
||||
this._userSelectionBox.visible = expanded;
|
||||
},
|
||||
|
||||
_hideUserListAndLogIn: function() {
|
||||
let tasks = [function() {
|
||||
return this._userList.hideItems();
|
||||
},
|
||||
|
||||
function() {
|
||||
return this._userList.giveUpWhitespace();
|
||||
},
|
||||
|
||||
function() {
|
||||
this._userList.actor.hide();
|
||||
},
|
||||
|
||||
new Batch.ConcurrentBatch(this, [this._fadeOutTitleLabel,
|
||||
this._fadeOutNotListedButton]),
|
||||
|
||||
function() {
|
||||
return this._askForUsernameAndLogIn();
|
||||
}];
|
||||
|
||||
let batch = new Batch.ConsecutiveBatch(this, tasks);
|
||||
batch.run();
|
||||
this._setUserListExpanded(false);
|
||||
GdmUtil.cloneAndFadeOutActor(this._userSelectionBox);
|
||||
this._askForUsernameAndLogIn();
|
||||
},
|
||||
|
||||
_showUserList: function() {
|
||||
let tasks = [this._hidePrompt,
|
||||
|
||||
new Batch.ConcurrentBatch(this, [this._fadeInTitleLabel,
|
||||
this._fadeInNotListedButton]),
|
||||
|
||||
function() {
|
||||
this._sessionList.close();
|
||||
this._promptLoginHint.hide();
|
||||
this._userList.actor.show();
|
||||
this._userList.actor.opacity = 255;
|
||||
return this._userList.showItems();
|
||||
},
|
||||
|
||||
function() {
|
||||
this._userList.actor.reactive = true;
|
||||
this._userList.actor.grab_key_focus();
|
||||
}];
|
||||
|
||||
let batch = new Batch.ConsecutiveBatch(this, tasks);
|
||||
batch.run();
|
||||
},
|
||||
|
||||
_fadeInBanner: function() {
|
||||
return GdmUtil.fadeInActor(this._bannerLabel);
|
||||
},
|
||||
|
||||
_fadeOutBanner: function() {
|
||||
return GdmUtil.fadeOutActor(this._bannerLabel);
|
||||
},
|
||||
|
||||
_fadeInTitleLabel: function() {
|
||||
return GdmUtil.fadeInActor(this._titleLabel);
|
||||
},
|
||||
|
||||
_fadeOutTitleLabel: function() {
|
||||
return GdmUtil.fadeOutActor(this._titleLabel);
|
||||
},
|
||||
|
||||
_fadeInNotListedButton: function() {
|
||||
return GdmUtil.fadeInActor(this._notListedButton);
|
||||
},
|
||||
|
||||
_fadeOutNotListedButton: function() {
|
||||
return GdmUtil.fadeOutActor(this._notListedButton);
|
||||
this._hidePrompt();
|
||||
this._setUserListExpanded(true);
|
||||
this._userList.actor.grab_key_focus();
|
||||
},
|
||||
|
||||
_beginVerificationForUser: function(userName) {
|
||||
@ -1311,38 +1099,30 @@ const LoginDialog = new Lang.Class({
|
||||
return hold;
|
||||
},
|
||||
|
||||
_onUserListActivated: function(activatedItem) {
|
||||
let userName;
|
||||
_beginVerificationForItem: function(item) {
|
||||
let userWidget = new UserWidget.UserWidget(item.user);
|
||||
this._promptUser.set_child(userWidget.actor);
|
||||
|
||||
let tasks = [function() {
|
||||
this._userList.actor.reactive = false;
|
||||
return this._userList.pinInPlace();
|
||||
},
|
||||
|
||||
function() {
|
||||
return this._userList.hideItemsExcept(activatedItem);
|
||||
},
|
||||
|
||||
function() {
|
||||
return this._userList.giveUpWhitespace();
|
||||
},
|
||||
|
||||
new Batch.ConcurrentBatch(this, [this._fadeOutTitleLabel,
|
||||
this._fadeOutNotListedButton]),
|
||||
|
||||
function() {
|
||||
return this._userList.shrinkToNaturalHeight();
|
||||
},
|
||||
|
||||
function() {
|
||||
userName = activatedItem.user.get_user_name();
|
||||
|
||||
let userName = item.user.get_user_name();
|
||||
return this._beginVerificationForUser(userName);
|
||||
}];
|
||||
let batch = new Batch.ConsecutiveBatch(this, tasks);
|
||||
return batch.run();
|
||||
},
|
||||
|
||||
_onUserListActivated: function(activatedItem) {
|
||||
let tasks = [function() {
|
||||
return GdmUtil.cloneAndFadeOutActor(this._userSelectionBox);
|
||||
},
|
||||
function() {
|
||||
this._setUserListExpanded(false);
|
||||
}];
|
||||
|
||||
this._user = activatedItem.user;
|
||||
|
||||
let batch = new Batch.ConsecutiveBatch(this, tasks);
|
||||
let batch = new Batch.ConcurrentBatch(this, [new Batch.ConsecutiveBatch(this, tasks),
|
||||
this._beginVerificationForItem(activatedItem)]);
|
||||
batch.run();
|
||||
},
|
||||
|
||||
@ -1371,15 +1151,6 @@ const LoginDialog = new Lang.Class({
|
||||
Lang.bind(this, function(userManager, user) {
|
||||
this._userList.removeUser(user);
|
||||
}));
|
||||
|
||||
// emitted in idle so caller doesn't have to explicitly check if
|
||||
// it's loaded immediately after construction
|
||||
// (since there's no way the caller could be listening for
|
||||
// 'loaded' yet)
|
||||
Mainloop.idle_add(Lang.bind(this, function() {
|
||||
this.emit('loaded');
|
||||
this.is_loaded = true;
|
||||
}));
|
||||
},
|
||||
|
||||
_onOpened: function() {
|
||||
@ -1394,5 +1165,9 @@ const LoginDialog = new Lang.Class({
|
||||
this.parent();
|
||||
|
||||
Main.ctrlAltTabManager.removeGroup(this.dialogLayout);
|
||||
}
|
||||
},
|
||||
|
||||
addCharacter: function(unichar) {
|
||||
this._promptEntry.clutter_text.insert_unichar(unichar);
|
||||
},
|
||||
});
|
||||
|
@ -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)
|
||||
|
172
js/gdm/util.js
172
js/gdm/util.js
@ -1,13 +1,14 @@
|
||||
// -*- 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 Batch = imports.gdm.batch;
|
||||
const Fprint = imports.gdm.fingerprint;
|
||||
const Realmd = imports.gdm.realmd;
|
||||
const Main = imports.ui.main;
|
||||
const Params = imports.misc.params;
|
||||
const Tweener = imports.ui.tweener;
|
||||
@ -15,6 +16,7 @@ const Tweener = imports.ui.tweener;
|
||||
const PASSWORD_SERVICE_NAME = 'gdm-password';
|
||||
const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint';
|
||||
const FADE_ANIMATION_TIME = 0.16;
|
||||
const CLONE_FADE_ANIMATION_TIME = 0.25;
|
||||
|
||||
const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen';
|
||||
const FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication';
|
||||
@ -25,6 +27,9 @@ 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
|
||||
|
||||
function fadeInActor(actor) {
|
||||
if (actor.opacity == 255 && actor.visible)
|
||||
return null;
|
||||
@ -71,6 +76,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',
|
||||
|
||||
@ -83,7 +116,9 @@ const ShellUserVerifier = new Lang.Class({
|
||||
this._settings = new Gio.Settings({ schema: LOGIN_SCREEN_SCHEMA });
|
||||
|
||||
this._fprintManager = new Fprint.FprintManager();
|
||||
this._realmManager = new Realmd.Manager();
|
||||
this._messageQueue = [];
|
||||
this._messageQueueTimeoutId = 0;
|
||||
this.hasPendingMessages = false;
|
||||
|
||||
this._failCounter = 0;
|
||||
},
|
||||
@ -123,13 +158,73 @@ 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._userVerifier.hasPendingMessages) {
|
||||
this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null);
|
||||
} else {
|
||||
let signalId = this._userVerifier.connect('no-more-messages',
|
||||
Lang.bind(this, function() {
|
||||
this._userVerifier.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.iconName);
|
||||
|
||||
this._messageQueueTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
|
||||
message.interval,
|
||||
Lang.bind(this, function() {
|
||||
this._messageQueueTimeoutId = 0;
|
||||
this._queueMessageTimeout();
|
||||
}));
|
||||
},
|
||||
|
||||
_queueMessage: function(message, iconName) {
|
||||
let interval = this._getIntervalForMessage(message);
|
||||
|
||||
this.hasPendingMessages = true;
|
||||
this._messageQueue.push({ text: message, interval: interval, iconName: iconName });
|
||||
this._queueMessageTimeout();
|
||||
},
|
||||
|
||||
_clearMessageQueue: function() {
|
||||
this.finishMessageQueue();
|
||||
|
||||
if (this._messageQueueTimeoutId != 0) {
|
||||
GLib.source_remove(this._messageQueueTimeoutId);
|
||||
this._messageQueueTimeoutId = 0;
|
||||
}
|
||||
this.emit('show-message', null, null);
|
||||
},
|
||||
|
||||
_checkForFingerprintReader: function() {
|
||||
@ -149,7 +244,7 @@ const ShellUserVerifier = new Lang.Class({
|
||||
logError(error, where);
|
||||
this._hold.release();
|
||||
|
||||
this.emit('show-message', _("Authentication error"), 'login-dialog-message-warning');
|
||||
this._queueMessage(_("Authentication error"), 'login-dialog-message-warning');
|
||||
this._verificationFailed(false);
|
||||
},
|
||||
|
||||
@ -268,7 +363,7 @@ const ShellUserVerifier = new Lang.Class({
|
||||
// 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(info, 'login-dialog-message-info');
|
||||
}
|
||||
},
|
||||
|
||||
@ -277,22 +372,7 @@ const ShellUserVerifier = new Lang.Class({
|
||||
// users who haven't enrolled their fingerprint.
|
||||
if (serviceName != PASSWORD_SERVICE_NAME)
|
||||
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, 'login-dialog-message-warning');
|
||||
},
|
||||
|
||||
_onInfoQuery: function(client, serviceName, question) {
|
||||
@ -300,10 +380,6 @@ const ShellUserVerifier = new Lang.Class({
|
||||
if (serviceName != PASSWORD_SERVICE_NAME)
|
||||
return;
|
||||
|
||||
this._showRealmLoginHint();
|
||||
this._realmLoginHintSignalId = this._realmManager.connect('login-format-changed',
|
||||
Lang.bind(this, this._showRealmLoginHint));
|
||||
|
||||
this.emit('ask-question', serviceName, question, '');
|
||||
},
|
||||
|
||||
@ -316,8 +392,6 @@ const ShellUserVerifier = new Lang.Class({
|
||||
},
|
||||
|
||||
_onReset: function() {
|
||||
this.clear();
|
||||
|
||||
// Clear previous attempts to authenticate
|
||||
this._failCounter = 0;
|
||||
|
||||
@ -328,6 +402,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 +422,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._userVerifier.hasPendingMessages) {
|
||||
this._retry();
|
||||
} else {
|
||||
let signalId = this._userVerifier.connect('no-more-messages',
|
||||
Lang.bind(this, function() {
|
||||
this._userVerifier.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._userVerifier.hasPendingMessages) {
|
||||
this._cancelAndReset();
|
||||
} else {
|
||||
let signalId = this._userVerifier.connect('no-more-messages',
|
||||
Lang.bind(this, function() {
|
||||
this._userVerifier.disconnect(signalId);
|
||||
this._cancelAndReset();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
this.emit('verification-failed');
|
||||
@ -362,11 +455,6 @@ const ShellUserVerifier = new Lang.Class({
|
||||
}
|
||||
|
||||
this.emit('hide-login-hint');
|
||||
|
||||
if (this._realmLoginHintSignalId) {
|
||||
this._realmManager.disconnect(this._realmLoginHintSignalId);
|
||||
this._realmLoginHintSignalId = 0;
|
||||
}
|
||||
},
|
||||
});
|
||||
Signals.addSignalMethods(ShellUserVerifier.prototype);
|
||||
|
@ -174,10 +174,15 @@ const ExtensionFinder = new Lang.Class({
|
||||
this.emit('extension-found', extension);
|
||||
},
|
||||
|
||||
_extensionsLoaded: function() {
|
||||
this.emit('extensions-loaded');
|
||||
},
|
||||
|
||||
scanExtensions: function() {
|
||||
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 });
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ const Map = new Lang.Class({
|
||||
|
||||
_init: function(iterable) {
|
||||
this._pool = { };
|
||||
this._size = 0;
|
||||
|
||||
if (iterable) {
|
||||
for (let i = 0; i < iterable.length; i++) {
|
||||
@ -99,6 +100,7 @@ const Map = new Lang.Class({
|
||||
node.value = value;
|
||||
} else {
|
||||
this._pool[hash] = { key: key, value: value };
|
||||
this._size++;
|
||||
}
|
||||
},
|
||||
|
||||
@ -108,6 +110,7 @@ const Map = new Lang.Class({
|
||||
|
||||
if (node && _sameValue(node.key, key)) {
|
||||
delete this._pool[hash];
|
||||
this._size--;
|
||||
return [node.key, node.value];
|
||||
} else {
|
||||
return [null, null];
|
||||
@ -136,6 +139,6 @@ const Map = new Lang.Class({
|
||||
},
|
||||
|
||||
size: function() {
|
||||
return Object.getOwnPropertyNames(this._pool).length;
|
||||
return this._size;
|
||||
},
|
||||
});
|
||||
|
@ -33,6 +33,13 @@ const SystemdLoginManagerIface = <interface name='org.freedesktop.login1.Manager
|
||||
<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>
|
||||
@ -69,7 +76,36 @@ 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++) {
|
||||
if (required[i] != reference[i])
|
||||
return required[i] < reference[i];
|
||||
}
|
||||
|
||||
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;
|
||||
@ -104,15 +140,23 @@ const LoginManagerSystemd = new Lang.Class({
|
||||
// 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;
|
||||
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);
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
canPowerOff: function(asyncCallback) {
|
||||
@ -142,6 +186,15 @@ const LoginManagerSystemd = new Lang.Class({
|
||||
});
|
||||
},
|
||||
|
||||
listSessions: function(asyncCallback) {
|
||||
this._proxy.ListSessionsRemote(function(result, error) {
|
||||
if (error)
|
||||
asyncCallback([]);
|
||||
else
|
||||
asyncCallback(result[0]);
|
||||
});
|
||||
},
|
||||
|
||||
powerOff: function() {
|
||||
this._proxy.PowerOffRemote(true);
|
||||
},
|
||||
@ -192,15 +245,23 @@ const LoginManagerConsoleKit = new Lang.Class({
|
||||
// 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;
|
||||
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);
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
canPowerOff: function(asyncCallback) {
|
||||
@ -222,7 +283,11 @@ const LoginManagerConsoleKit = new Lang.Class({
|
||||
},
|
||||
|
||||
canSuspend: function(asyncCallback) {
|
||||
return false;
|
||||
asyncCallback(false);
|
||||
},
|
||||
|
||||
listSessions: function(asyncCallback) {
|
||||
asyncCallback([]);
|
||||
},
|
||||
|
||||
powerOff: function() {
|
||||
|
@ -1,8 +1,13 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GLib = imports.gi.GLib;
|
||||
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 +18,7 @@ const _urlRegexp = new RegExp(
|
||||
'(^|' + _leadingJunk + ')' +
|
||||
'(' +
|
||||
'(?:' +
|
||||
'[a-z][\\w-]+://' + // scheme://
|
||||
'(?:http|https|ftp)://' + // scheme://
|
||||
'|' +
|
||||
'www\\d{0,3}[.]' + // www.
|
||||
'|' +
|
||||
@ -208,3 +213,63 @@ function insertSorted(array, val, cmp) {
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
function makeCloseButton() {
|
||||
let closeButton = new St.Button({ 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.
|
||||
closeButton.set_x_align(Clutter.ActorAlign.END);
|
||||
closeButton.set_y_align(Clutter.ActorAlign.START);
|
||||
|
||||
// XXX Clutter 2.0 workaround: ClutterBinLayout needs expand
|
||||
// to respect the alignments.
|
||||
closeButton.set_x_expand(true);
|
||||
closeButton.set_y_expand(true);
|
||||
|
||||
closeButton.connect('style-changed', function() {
|
||||
let themeNode = closeButton.get_theme_node();
|
||||
closeButton.translation_x = themeNode.get_length('-shell-close-overlap-x');
|
||||
closeButton.translation_y = themeNode.get_length('-shell-close-overlap-y');
|
||||
});
|
||||
|
||||
return closeButton;
|
||||
}
|
||||
|
||||
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' });
|
||||
}
|
||||
|
106
js/ui/altTab.js
106
js/ui/altTab.js
@ -98,43 +98,13 @@ const AppSwitcherPopup = new Lang.Class({
|
||||
}
|
||||
},
|
||||
|
||||
_getAppLists: function() {
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
let appSys = Shell.AppSystem.get_default();
|
||||
let allApps = appSys.get_running ();
|
||||
|
||||
let screen = global.screen;
|
||||
let display = screen.get_display();
|
||||
let windows = display.get_tab_list(Meta.TabList.NORMAL_ALL, screen,
|
||||
screen.get_active_workspace());
|
||||
|
||||
// windows is only the windows on the current workspace. For
|
||||
// each one, if it corresponds to an app we know, move that
|
||||
// app from allApps to apps.
|
||||
let apps = [];
|
||||
for (let i = 0; i < windows.length && allApps.length != 0; i++) {
|
||||
let app = tracker.get_window_app(windows[i]);
|
||||
let index = allApps.indexOf(app);
|
||||
if (index != -1) {
|
||||
apps.push(app);
|
||||
allApps.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Now @apps is a list of apps on the current workspace, in
|
||||
// standard Alt+Tab order (MRU except for minimized windows),
|
||||
// and allApps is a list of apps that only appear on other
|
||||
// workspaces, sorted by user_time, which is good enough.
|
||||
return [apps, allApps];
|
||||
},
|
||||
|
||||
_createSwitcher: function() {
|
||||
let [localApps, otherApps] = this._getAppLists();
|
||||
let apps = Shell.AppSystem.get_default().get_running ();
|
||||
|
||||
if (localApps.length == 0 && otherApps.length == 0)
|
||||
if (apps.length == 0)
|
||||
return false;
|
||||
|
||||
this._switcherList = new AppSwitcher(localApps, otherApps, this);
|
||||
this._switcherList = new AppSwitcher(apps, this);
|
||||
this._items = this._switcherList.icons;
|
||||
|
||||
return true;
|
||||
@ -262,15 +232,13 @@ const AppSwitcherPopup = new Lang.Class({
|
||||
},
|
||||
|
||||
_finish : function(timestamp) {
|
||||
this.parent();
|
||||
|
||||
let appIcon = this._items[this._selectedIndex];
|
||||
let window;
|
||||
if (this._currentWindow >= 0)
|
||||
window = appIcon.cachedWindows[this._currentWindow];
|
||||
if (this._currentWindow < 0)
|
||||
appIcon.app.activate_window(appIcon.cachedWindows[0], timestamp);
|
||||
else
|
||||
window = null;
|
||||
appIcon.app.activate_window(window, timestamp);
|
||||
Main.activateWindow(appIcon.cachedWindows[this._currentWindow], timestamp);
|
||||
|
||||
this.parent();
|
||||
},
|
||||
|
||||
_onDestroy : function() {
|
||||
@ -429,9 +397,9 @@ const WindowSwitcherPopup = new Lang.Class({
|
||||
},
|
||||
|
||||
_finish: function() {
|
||||
this.parent();
|
||||
|
||||
Main.activateWindow(this._items[this._selectedIndex].window);
|
||||
|
||||
this.parent();
|
||||
}
|
||||
});
|
||||
|
||||
@ -461,34 +429,26 @@ const AppSwitcher = new Lang.Class({
|
||||
Name: 'AppSwitcher',
|
||||
Extends: SwitcherPopup.SwitcherList,
|
||||
|
||||
_init : function(localApps, otherApps, altTabPopup) {
|
||||
_init : function(apps, altTabPopup) {
|
||||
this.parent(true);
|
||||
|
||||
// Construct the AppIcons, add to the popup
|
||||
let activeWorkspace = global.screen.get_active_workspace();
|
||||
let workspaceIcons = [];
|
||||
let otherIcons = [];
|
||||
for (let i = 0; i < localApps.length; i++) {
|
||||
let appIcon = new AppIcon(localApps[i]);
|
||||
// Cache the window list now; we don't handle dynamic changes here,
|
||||
// and we don't want to be continually retrieving it
|
||||
appIcon.cachedWindows = appIcon.app.get_windows();
|
||||
workspaceIcons.push(appIcon);
|
||||
}
|
||||
for (let i = 0; i < otherApps.length; i++) {
|
||||
let appIcon = new AppIcon(otherApps[i]);
|
||||
appIcon.cachedWindows = appIcon.app.get_windows();
|
||||
otherIcons.push(appIcon);
|
||||
}
|
||||
|
||||
this.icons = [];
|
||||
this._arrows = [];
|
||||
for (let i = 0; i < workspaceIcons.length; i++)
|
||||
this._addIcon(workspaceIcons[i]);
|
||||
if (workspaceIcons.length > 0 && otherIcons.length > 0)
|
||||
this.addSeparator();
|
||||
for (let i = 0; i < otherIcons.length; i++)
|
||||
this._addIcon(otherIcons[i]);
|
||||
|
||||
let windowTracker = Shell.WindowTracker.get_default();
|
||||
let allWindows = global.display.get_tab_list(Meta.TabList.NORMAL,
|
||||
global.screen, null);
|
||||
|
||||
// Construct the AppIcons, add to the popup
|
||||
for (let i = 0; i < apps.length; i++) {
|
||||
let appIcon = new AppIcon(apps[i]);
|
||||
// Cache the window list now; we don't handle dynamic changes here,
|
||||
// and we don't want to be continually retrieving it
|
||||
appIcon.cachedWindows = allWindows.filter(function(w) {
|
||||
return windowTracker.get_window_app (w) == appIcon.app;
|
||||
});
|
||||
this._addIcon(appIcon);
|
||||
}
|
||||
|
||||
this._curApp = -1;
|
||||
this._iconSize = 0;
|
||||
@ -514,8 +474,6 @@ const AppSwitcher = new Lang.Class({
|
||||
let [iconMinHeight, iconNaturalHeight] = this.icons[j].label.get_preferred_height(-1);
|
||||
let iconSpacing = iconNaturalHeight + iconPadding + iconBorder;
|
||||
let totalSpacing = this._list.spacing * (this._items.length - 1);
|
||||
if (this._separator)
|
||||
totalSpacing += this._separator.width + this._list.spacing;
|
||||
|
||||
// We just assume the whole screen here due to weirdness happing with the passed width
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
@ -638,24 +596,12 @@ const ThumbnailList = new Lang.Class({
|
||||
_init : function(windows) {
|
||||
this.parent(false);
|
||||
|
||||
let activeWorkspace = global.screen.get_active_workspace();
|
||||
|
||||
// We fake the value of 'separatorAdded' when the app has no window
|
||||
// on the current workspace, to avoid displaying a useless separator in
|
||||
// that case.
|
||||
let separatorAdded = windows.length == 0 || windows[0].get_workspace() != activeWorkspace;
|
||||
|
||||
this._labels = new Array();
|
||||
this._thumbnailBins = new Array();
|
||||
this._clones = new Array();
|
||||
this._windows = windows;
|
||||
|
||||
for (let i = 0; i < windows.length; i++) {
|
||||
if (!separatorAdded && windows[i].get_workspace() != activeWorkspace) {
|
||||
this.addSeparator();
|
||||
separatorAdded = true;
|
||||
}
|
||||
|
||||
let box = new St.BoxLayout({ style_class: 'thumbnail-box',
|
||||
vertical: true });
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const GMenu = imports.gi.GMenu;
|
||||
const Shell = imports.gi.Shell;
|
||||
@ -14,10 +15,12 @@ const Mainloop = imports.mainloop;
|
||||
const Atk = imports.gi.Atk;
|
||||
|
||||
const AppFavorites = imports.ui.appFavorites;
|
||||
const BoxPointer = imports.ui.boxpointer;
|
||||
const DND = imports.ui.dnd;
|
||||
const IconGrid = imports.ui.iconGrid;
|
||||
const Main = imports.ui.main;
|
||||
const Overview = imports.ui.overview;
|
||||
const OverviewControls = imports.ui.overviewControls;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Tweener = imports.ui.tweener;
|
||||
const Workspace = imports.ui.workspace;
|
||||
@ -26,256 +29,452 @@ const Util = imports.misc.util;
|
||||
|
||||
const MAX_APPLICATION_WORK_MILLIS = 75;
|
||||
const MENU_POPUP_TIMEOUT = 600;
|
||||
const SCROLL_TIME = 0.1;
|
||||
const MAX_COLUMNS = 6;
|
||||
|
||||
const INACTIVE_GRID_OPACITY = 77;
|
||||
const FOLDER_SUBICON_FRACTION = .4;
|
||||
|
||||
|
||||
// Recursively load a GMenuTreeDirectory; we could put this in ShellAppSystem too
|
||||
function _loadCategory(dir, view) {
|
||||
let iter = dir.iter();
|
||||
let appSystem = Shell.AppSystem.get_default();
|
||||
let nextType;
|
||||
while ((nextType = iter.next()) != GMenu.TreeItemType.INVALID) {
|
||||
if (nextType == GMenu.TreeItemType.ENTRY) {
|
||||
let entry = iter.get_entry();
|
||||
let app = appSystem.lookup_app_by_tree_entry(entry);
|
||||
if (!entry.get_app_info().get_nodisplay())
|
||||
view.addApp(app);
|
||||
} else if (nextType == GMenu.TreeItemType.DIRECTORY) {
|
||||
let itemDir = iter.get_directory();
|
||||
if (!itemDir.get_is_nodisplay())
|
||||
_loadCategory(itemDir, view);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const AlphabeticalView = new Lang.Class({
|
||||
Name: 'AlphabeticalView',
|
||||
Abstract: true,
|
||||
|
||||
_init: function() {
|
||||
this._grid = new IconGrid.IconGrid({ xAlign: St.Align.START });
|
||||
this._appSystem = Shell.AppSystem.get_default();
|
||||
this._grid = new IconGrid.IconGrid({ xAlign: St.Align.MIDDLE,
|
||||
columnLimit: MAX_COLUMNS });
|
||||
|
||||
this._pendingAppLaterId = 0;
|
||||
this._appIcons = {}; // desktop file id
|
||||
this._allApps = [];
|
||||
// Standard hack for ClutterBinLayout
|
||||
this._grid.actor.x_expand = true;
|
||||
|
||||
this._items = {};
|
||||
this._allItems = [];
|
||||
},
|
||||
|
||||
removeAll: function() {
|
||||
this._grid.removeAll();
|
||||
this._items = {};
|
||||
this._allItems = [];
|
||||
},
|
||||
|
||||
_getItemId: function(item) {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
_createItemIcon: function(item) {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
_compareItems: function(a, b) {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
_addItem: function(item) {
|
||||
let id = this._getItemId(item);
|
||||
if (this._items[id] !== undefined)
|
||||
return null;
|
||||
|
||||
let itemIcon = this._createItemIcon(item);
|
||||
this._allItems.push(item);
|
||||
this._items[id] = itemIcon;
|
||||
|
||||
return itemIcon;
|
||||
},
|
||||
|
||||
loadGrid: function() {
|
||||
this._allItems.sort(this._compareItems);
|
||||
|
||||
for (let i = 0; i < this._allItems.length; i++) {
|
||||
let id = this._getItemId(this._allItems[i]);
|
||||
if (!id)
|
||||
continue;
|
||||
this._grid.addItem(this._items[id].actor);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const FolderView = new Lang.Class({
|
||||
Name: 'FolderView',
|
||||
Extends: AlphabeticalView,
|
||||
|
||||
_init: function() {
|
||||
this.parent();
|
||||
this.actor = this._grid.actor;
|
||||
},
|
||||
|
||||
_getItemId: function(item) {
|
||||
return item.get_id();
|
||||
},
|
||||
|
||||
_createItemIcon: function(item) {
|
||||
return new AppIcon(item);
|
||||
},
|
||||
|
||||
_compareItems: function(a, b) {
|
||||
return a.compare_by_name(b);
|
||||
},
|
||||
|
||||
addApp: function(app) {
|
||||
this._addItem(app);
|
||||
},
|
||||
|
||||
createFolderIcon: function(size) {
|
||||
let icon = new St.Widget({ layout_manager: new Clutter.BinLayout(),
|
||||
style_class: 'app-folder-icon',
|
||||
width: size, height: size });
|
||||
let subSize = Math.floor(FOLDER_SUBICON_FRACTION * size);
|
||||
|
||||
let aligns = [ Clutter.ActorAlign.START, Clutter.ActorAlign.END ];
|
||||
for (let i = 0; i < Math.min(this._allItems.length, 4); i++) {
|
||||
let texture = this._allItems[i].create_icon_texture(subSize);
|
||||
let bin = new St.Bin({ child: texture,
|
||||
x_expand: true, y_expand: true });
|
||||
bin.set_x_align(aligns[i % 2]);
|
||||
bin.set_y_align(aligns[Math.floor(i / 2)]);
|
||||
icon.add_actor(bin);
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
});
|
||||
|
||||
const AllViewLayout = new Lang.Class({
|
||||
Name: 'AllViewLayout',
|
||||
Extends: Clutter.BinLayout,
|
||||
|
||||
vfunc_get_preferred_height: function(container, forWidth) {
|
||||
let minBottom = 0;
|
||||
let naturalBottom = 0;
|
||||
|
||||
for (let child = container.get_first_child();
|
||||
child;
|
||||
child = child.get_next_sibling()) {
|
||||
let childY = child.y;
|
||||
let [childMin, childNatural] = child.get_preferred_height(forWidth);
|
||||
|
||||
if (childMin + childY > minBottom)
|
||||
minBottom = childMin + childY;
|
||||
|
||||
if (childNatural + childY > naturalBottom)
|
||||
naturalBottom = childNatural + childY;
|
||||
}
|
||||
return [minBottom, naturalBottom];
|
||||
}
|
||||
});
|
||||
|
||||
const AllView = new Lang.Class({
|
||||
Name: 'AllView',
|
||||
Extends: AlphabeticalView,
|
||||
|
||||
_init: function() {
|
||||
this.parent();
|
||||
|
||||
this._grid.actor.y_align = Clutter.ActorAlign.START;
|
||||
this._grid.actor.y_expand = true;
|
||||
|
||||
let box = new St.BoxLayout({ vertical: true });
|
||||
box.add(this._grid.actor, { y_align: St.Align.START, expand: true });
|
||||
this._stack = new St.Widget({ layout_manager: new AllViewLayout() });
|
||||
this._stack.add_actor(this._grid.actor);
|
||||
this._eventBlocker = new St.Widget({ x_expand: true, y_expand: true });
|
||||
this._stack.add_actor(this._eventBlocker);
|
||||
box.add(this._stack, { y_align: St.Align.START, expand: true });
|
||||
|
||||
this.actor = new St.ScrollView({ x_fill: true,
|
||||
y_fill: false,
|
||||
y_align: St.Align.START,
|
||||
style_class: 'vfade' });
|
||||
x_expand: true,
|
||||
y_expand: true,
|
||||
overlay_scrollbars: true,
|
||||
style_class: 'all-apps vfade' });
|
||||
this.actor.add_actor(box);
|
||||
this.actor.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
let action = new Clutter.PanAction({ interpolate: true });
|
||||
action.connect('pan', Lang.bind(this, this._onPan));
|
||||
this.actor.add_action(action);
|
||||
|
||||
this._clickAction = new Clutter.ClickAction();
|
||||
this._clickAction.connect('clicked', Lang.bind(this, function() {
|
||||
if (!this._currentPopup)
|
||||
return;
|
||||
|
||||
let [x, y] = this._clickAction.get_coords();
|
||||
let actor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y);
|
||||
if (!this._currentPopup.actor.contains(actor))
|
||||
this._currentPopup.popdown();
|
||||
}));
|
||||
this._eventBlocker.add_action(this._clickAction);
|
||||
},
|
||||
|
||||
_onPan: function(action) {
|
||||
this._clickAction.release();
|
||||
|
||||
let [dist, dx, dy] = action.get_motion_delta(0);
|
||||
let adjustment = this.actor.vscroll.adjustment;
|
||||
adjustment.value -= (dy / this.actor.height) * adjustment.page_size;
|
||||
return false;
|
||||
},
|
||||
|
||||
removeAll: function() {
|
||||
this._grid.removeAll();
|
||||
this._appIcons = {};
|
||||
this._allApps = [];
|
||||
_getItemId: function(item) {
|
||||
if (item instanceof Shell.App)
|
||||
return item.get_id();
|
||||
else if (item instanceof GMenu.TreeDirectory)
|
||||
return item.get_menu_id();
|
||||
else
|
||||
return null;
|
||||
},
|
||||
|
||||
_createItemIcon: function(item) {
|
||||
if (item instanceof Shell.App)
|
||||
return new AppIcon(item);
|
||||
else if (item instanceof GMenu.TreeDirectory)
|
||||
return new FolderIcon(item, this);
|
||||
else
|
||||
return null;
|
||||
},
|
||||
|
||||
_compareItems: function(itemA, itemB) {
|
||||
// bit of a hack: rely on both ShellApp and GMenuTreeDirectory
|
||||
// having a get_name() method
|
||||
let nameA = GLib.utf8_collate_key(itemA.get_name(), -1);
|
||||
let nameB = GLib.utf8_collate_key(itemB.get_name(), -1);
|
||||
return (nameA > nameB) ? 1 : (nameA < nameB ? -1 : 0);
|
||||
},
|
||||
|
||||
addApp: function(app) {
|
||||
var id = app.get_id();
|
||||
if (this._appIcons[id] !== undefined)
|
||||
return;
|
||||
let appIcon = this._addItem(app);
|
||||
if (appIcon)
|
||||
appIcon.actor.connect('key-focus-in',
|
||||
Lang.bind(this, this._ensureIconVisible));
|
||||
},
|
||||
|
||||
let appIcon = new AppWellIcon(app);
|
||||
let pos = Util.insertSorted(this._allApps, app, function(a, b) {
|
||||
return a.compare_by_name(b);
|
||||
});
|
||||
this._grid.addItem(appIcon.actor, pos);
|
||||
appIcon.actor.connect('key-focus-in', Lang.bind(this, this._ensureIconVisible));
|
||||
addFolder: function(dir) {
|
||||
let folderIcon = this._addItem(dir);
|
||||
if (folderIcon)
|
||||
folderIcon.actor.connect('key-focus-in',
|
||||
Lang.bind(this, this._ensureIconVisible));
|
||||
},
|
||||
|
||||
this._appIcons[id] = appIcon;
|
||||
addFolderPopup: function(popup) {
|
||||
this._stack.add_actor(popup.actor);
|
||||
popup.connect('open-state-changed', Lang.bind(this,
|
||||
function(popup, isOpen) {
|
||||
this._eventBlocker.reactive = isOpen;
|
||||
this._currentPopup = isOpen ? popup : null;
|
||||
this._updateIconOpacities(isOpen);
|
||||
if (isOpen) {
|
||||
this._ensureIconVisible(popup.actor);
|
||||
this._grid.actor.y = popup.parentOffset;
|
||||
} else {
|
||||
this._grid.actor.y = 0;
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
_ensureIconVisible: function(icon) {
|
||||
let adjustment = this.actor.vscroll.adjustment;
|
||||
let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
|
||||
|
||||
let offset = 0;
|
||||
let vfade = this.actor.get_effect("vfade");
|
||||
if (vfade)
|
||||
offset = vfade.fade_offset;
|
||||
|
||||
// If this gets called as part of a right-click, the actor
|
||||
// will be needs_allocation, and so "icon.y" would return 0
|
||||
let box = icon.get_allocation_box();
|
||||
|
||||
if (box.y1 < value + offset)
|
||||
value = Math.max(0, box.y1 - offset);
|
||||
else if (box.y2 > value + pageSize - offset)
|
||||
value = Math.min(upper, box.y2 + offset - pageSize);
|
||||
else
|
||||
return;
|
||||
|
||||
Tweener.addTween(adjustment,
|
||||
{ value: value,
|
||||
time: SCROLL_TIME,
|
||||
transition: 'easeOutQuad' });
|
||||
Util.ensureActorVisibleInScrollView(this.actor, icon);
|
||||
},
|
||||
|
||||
setVisibleApps: function(apps) {
|
||||
if (apps == null) { // null implies "all"
|
||||
for (var id in this._appIcons) {
|
||||
var icon = this._appIcons[id];
|
||||
icon.actor.visible = true;
|
||||
}
|
||||
} else {
|
||||
// Set everything to not-visible, then set to visible what we should see
|
||||
for (var id in this._appIcons) {
|
||||
var icon = this._appIcons[id];
|
||||
icon.actor.visible = false;
|
||||
}
|
||||
for (var i = 0; i < apps.length; i++) {
|
||||
var app = apps[i];
|
||||
var id = app.get_id();
|
||||
var icon = this._appIcons[id];
|
||||
icon.actor.visible = true;
|
||||
}
|
||||
_updateIconOpacities: function(folderOpen) {
|
||||
for (let id in this._items) {
|
||||
if (folderOpen && !this._items[id].actor.checked)
|
||||
this._items[id].actor.opacity = INACTIVE_GRID_OPACITY;
|
||||
else
|
||||
this._items[id].actor.opacity = 255;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const ViewByCategories = new Lang.Class({
|
||||
Name: 'ViewByCategories',
|
||||
const FrequentView = new Lang.Class({
|
||||
Name: 'FrequentView',
|
||||
|
||||
_init: function() {
|
||||
this._grid = new IconGrid.IconGrid({ xAlign: St.Align.MIDDLE,
|
||||
fillParent: true,
|
||||
columnLimit: MAX_COLUMNS });
|
||||
this.actor = new St.Widget({ style_class: 'frequent-apps',
|
||||
x_expand: true, y_expand: true });
|
||||
this.actor.add_actor(this._grid.actor);
|
||||
|
||||
this._usage = Shell.AppUsage.get_default();
|
||||
},
|
||||
|
||||
removeAll: function() {
|
||||
this._grid.removeAll();
|
||||
},
|
||||
|
||||
loadApps: function() {
|
||||
let mostUsed = this._usage.get_most_used ("");
|
||||
for (let i = 0; i < mostUsed.length; i++) {
|
||||
if (!mostUsed[i].get_app_info().should_show())
|
||||
continue;
|
||||
let appIcon = new AppIcon(mostUsed[i]);
|
||||
this._grid.addItem(appIcon.actor, -1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const Views = {
|
||||
FREQUENT: 0,
|
||||
ALL: 1
|
||||
};
|
||||
|
||||
const AppDisplay = new Lang.Class({
|
||||
Name: 'AppDisplay',
|
||||
|
||||
_init: function() {
|
||||
this._appSystem = Shell.AppSystem.get_default();
|
||||
this.actor = new St.BoxLayout({ style_class: 'all-app' });
|
||||
this.actor._delegate = this;
|
||||
this._appSystem.connect('installed-changed', Lang.bind(this, function() {
|
||||
Main.queueDeferredWork(this._allAppsWorkId);
|
||||
}));
|
||||
Main.overview.connect('showing', Lang.bind(this, function() {
|
||||
Main.queueDeferredWork(this._frequentAppsWorkId);
|
||||
}));
|
||||
global.settings.connect('changed::app-folder-categories', Lang.bind(this, function() {
|
||||
Main.queueDeferredWork(this._allAppsWorkId);
|
||||
}));
|
||||
this._privacySettings = new Gio.Settings({ schema: 'org.gnome.desktop.privacy' });
|
||||
this._privacySettings.connect('changed::remember-app-usage',
|
||||
Lang.bind(this, this._updateFrequentVisibility));
|
||||
|
||||
this._view = new AlphabeticalView();
|
||||
this._views = [];
|
||||
|
||||
// categories can be -1 (the All view) or 0...n-1, where n
|
||||
// is the number of sections
|
||||
// -2 is a flag to indicate that nothing is selected
|
||||
// (used only before the actor is mapped the first time)
|
||||
this._currentCategory = -2;
|
||||
this._categories = [];
|
||||
let view, button;
|
||||
view = new FrequentView();
|
||||
button = new St.Button({ label: _("Frequent"),
|
||||
style_class: 'app-view-control',
|
||||
can_focus: true,
|
||||
x_expand: true });
|
||||
this._views[Views.FREQUENT] = { 'view': view, 'control': button };
|
||||
|
||||
this._categoryBox = new St.BoxLayout({ vertical: true,
|
||||
reactive: true,
|
||||
accessible_role: Atk.Role.LIST });
|
||||
this._categoryScroll = new St.ScrollView({ x_fill: false,
|
||||
y_fill: false,
|
||||
style_class: 'vfade' });
|
||||
this._categoryScroll.add_actor(this._categoryBox);
|
||||
this._categoryScroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
this.actor.add(this._view.actor, { expand: true, x_fill: true, y_fill: true });
|
||||
this.actor.add(this._categoryScroll, { expand: false, y_fill: false, y_align: St.Align.START });
|
||||
view = new AllView();
|
||||
button = new St.Button({ label: _("All"),
|
||||
style_class: 'app-view-control',
|
||||
can_focus: true,
|
||||
x_expand: true });
|
||||
this._views[Views.ALL] = { 'view': view, 'control': button };
|
||||
|
||||
// Always select the "All" filter when switching to the app view
|
||||
this.actor.connect('notify::mapped', Lang.bind(this,
|
||||
function() {
|
||||
if (this.actor.mapped && this._allCategoryButton)
|
||||
this._selectCategory(-1);
|
||||
}));
|
||||
this.actor = new St.BoxLayout({ style_class: 'app-display',
|
||||
vertical: true,
|
||||
x_expand: true, y_expand: true });
|
||||
|
||||
this._viewStack = new St.Widget({ layout_manager: new Clutter.BinLayout(),
|
||||
x_expand: true, y_expand: true });
|
||||
this.actor.add(this._viewStack, { expand: true });
|
||||
|
||||
let layout = new Clutter.BoxLayout({ homogeneous: true });
|
||||
this._controls = new St.Widget({ style_class: 'app-view-controls',
|
||||
layout_manager: layout });
|
||||
this.actor.add(new St.Bin({ child: this._controls }));
|
||||
|
||||
|
||||
for (let i = 0; i < this._views.length; i++) {
|
||||
this._viewStack.add_actor(this._views[i].view.actor);
|
||||
this._controls.add_actor(this._views[i].control);
|
||||
|
||||
let viewIndex = i;
|
||||
this._views[i].control.connect('clicked', Lang.bind(this,
|
||||
function(actor) {
|
||||
this._showView(viewIndex);
|
||||
}));
|
||||
}
|
||||
this._showView(Views.FREQUENT);
|
||||
this._updateFrequentVisibility();
|
||||
|
||||
// We need a dummy actor to catch the keyboard focus if the
|
||||
// user Ctrl-Alt-Tabs here before the deferred work creates
|
||||
// our real contents
|
||||
this._focusDummy = new St.Bin({ can_focus: true });
|
||||
this.actor.add(this._focusDummy);
|
||||
this._viewStack.add_actor(this._focusDummy);
|
||||
|
||||
this._allAppsWorkId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplayAllApps));
|
||||
this._frequentAppsWorkId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplayFrequentApps));
|
||||
},
|
||||
|
||||
_selectCategory: function(num) {
|
||||
if (this._currentCategory == num) // nothing to do
|
||||
return;
|
||||
|
||||
this._currentCategory = num;
|
||||
|
||||
if (num != -1) {
|
||||
var category = this._categories[num];
|
||||
this._allCategoryButton.remove_style_pseudo_class('selected');
|
||||
this._view.setVisibleApps(category.apps);
|
||||
} else {
|
||||
this._allCategoryButton.add_style_pseudo_class('selected');
|
||||
this._view.setVisibleApps(null);
|
||||
}
|
||||
|
||||
for (var i = 0; i < this._categories.length; i++) {
|
||||
if (i == num)
|
||||
this._categories[i].button.add_style_pseudo_class('selected');
|
||||
_showView: function(activeIndex) {
|
||||
for (let i = 0; i < this._views.length; i++) {
|
||||
let actor = this._views[i].view.actor;
|
||||
let params = { time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME,
|
||||
opacity: (i == activeIndex) ? 255 : 0 };
|
||||
if (i == activeIndex)
|
||||
actor.visible = true;
|
||||
else
|
||||
this._categories[i].button.remove_style_pseudo_class('selected');
|
||||
params.onComplete = function() { actor.hide(); };
|
||||
Tweener.addTween(actor, params);
|
||||
|
||||
if (i == activeIndex)
|
||||
this._views[i].control.add_style_pseudo_class('checked');
|
||||
else
|
||||
this._views[i].control.remove_style_pseudo_class('checked');
|
||||
}
|
||||
},
|
||||
|
||||
// Recursively load a GMenuTreeDirectory; we could put this in ShellAppSystem too
|
||||
_loadCategory: function(dir, appList) {
|
||||
var iter = dir.iter();
|
||||
var nextType;
|
||||
while ((nextType = iter.next()) != GMenu.TreeItemType.INVALID) {
|
||||
if (nextType == GMenu.TreeItemType.ENTRY) {
|
||||
var entry = iter.get_entry();
|
||||
var app = this._appSystem.lookup_app_by_tree_entry(entry);
|
||||
if (!entry.get_app_info().get_nodisplay()) {
|
||||
this._view.addApp(app);
|
||||
appList.push(app);
|
||||
}
|
||||
} else if (nextType == GMenu.TreeItemType.DIRECTORY) {
|
||||
var itemDir = iter.get_directory();
|
||||
if (!itemDir.get_is_nodisplay())
|
||||
this._loadCategory(itemDir, appList);
|
||||
}
|
||||
}
|
||||
_updateFrequentVisibility: function() {
|
||||
let enabled = this._privacySettings.get_boolean('remember-app-usage');
|
||||
this._views[Views.FREQUENT].control.visible = enabled;
|
||||
|
||||
let visibleViews = this._views.filter(function(v) {
|
||||
return v.control.visible;
|
||||
});
|
||||
this._controls.visible = visibleViews.length > 1;
|
||||
|
||||
if (!enabled && this._views[Views.FREQUENT].view.actor.visible)
|
||||
this._showView(Views.ALL);
|
||||
},
|
||||
|
||||
_addCategory: function(name, index, dir) {
|
||||
let apps;
|
||||
|
||||
if (dir != null) {
|
||||
apps = [];
|
||||
this._loadCategory(dir, apps);
|
||||
|
||||
if (apps.length == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
let button = new St.Button({ label: GLib.markup_escape_text (name, -1),
|
||||
style_class: 'app-filter',
|
||||
x_align: St.Align.START,
|
||||
can_focus: true ,
|
||||
accessible_role: Atk.Role.LIST_ITEM });
|
||||
button.connect('clicked', Lang.bind(this, function() {
|
||||
this._selectCategory(index);
|
||||
}));
|
||||
|
||||
if (dir == null) {
|
||||
this._allCategoryButton = button;
|
||||
} else {
|
||||
this._categories.push({ apps: apps,
|
||||
name: name,
|
||||
button: button });
|
||||
}
|
||||
|
||||
this._categoryBox.add(button, { expand: true, x_fill: true, y_fill: false });
|
||||
return true;
|
||||
_redisplay: function() {
|
||||
this._redisplayFrequentApps();
|
||||
this._redisplayAllApps();
|
||||
},
|
||||
|
||||
_removeAll: function() {
|
||||
this._view.removeAll();
|
||||
this._categories = [];
|
||||
this._categoryBox.destroy_all_children();
|
||||
_redisplayFrequentApps: function() {
|
||||
let view = this._views[Views.FREQUENT].view;
|
||||
|
||||
view.removeAll();
|
||||
view.loadApps();
|
||||
},
|
||||
|
||||
refresh: function() {
|
||||
this._removeAll();
|
||||
_redisplayAllApps: function() {
|
||||
let view = this._views[Views.ALL].view;
|
||||
|
||||
/* Translators: Filter to display all applications */
|
||||
this._addCategory(_("All"), -1, null);
|
||||
view.removeAll();
|
||||
|
||||
var tree = this._appSystem.get_tree();
|
||||
var root = tree.get_root_directory();
|
||||
let tree = this._appSystem.get_tree();
|
||||
let root = tree.get_root_directory();
|
||||
|
||||
var iter = root.iter();
|
||||
var nextType;
|
||||
var i = 0;
|
||||
let iter = root.iter();
|
||||
let nextType;
|
||||
let folderCategories = global.settings.get_strv('app-folder-categories');
|
||||
while ((nextType = iter.next()) != GMenu.TreeItemType.INVALID) {
|
||||
if (nextType == GMenu.TreeItemType.DIRECTORY) {
|
||||
var dir = iter.get_directory();
|
||||
let dir = iter.get_directory();
|
||||
if (dir.get_is_nodisplay())
|
||||
continue;
|
||||
|
||||
if (this._addCategory(dir.get_name(), i, dir))
|
||||
i++;
|
||||
if (folderCategories.indexOf(dir.get_menu_id()) != -1)
|
||||
view.addFolder(dir);
|
||||
else
|
||||
_loadCategory(dir, view);
|
||||
}
|
||||
}
|
||||
|
||||
this._selectCategory(-1);
|
||||
view.loadGrid();
|
||||
|
||||
if (this._focusDummy) {
|
||||
let focused = this._focusDummy.has_key_focus();
|
||||
@ -287,29 +486,6 @@ const ViewByCategories = new Lang.Class({
|
||||
}
|
||||
});
|
||||
|
||||
/* This class represents a display containing a collection of application items.
|
||||
* The applications are sorted based on their name.
|
||||
*/
|
||||
const AllAppDisplay = new Lang.Class({
|
||||
Name: 'AllAppDisplay',
|
||||
|
||||
_init: function() {
|
||||
this._appSystem = Shell.AppSystem.get_default();
|
||||
this._appSystem.connect('installed-changed', Lang.bind(this, function() {
|
||||
Main.queueDeferredWork(this._workId);
|
||||
}));
|
||||
|
||||
this._appView = new ViewByCategories();
|
||||
this.actor = new St.Bin({ child: this._appView.actor, x_fill: true, y_fill: true });
|
||||
|
||||
this._workId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay));
|
||||
},
|
||||
|
||||
_redisplay: function() {
|
||||
this._appView.refresh();
|
||||
}
|
||||
});
|
||||
|
||||
const AppSearchProvider = new Lang.Class({
|
||||
Name: 'AppSearchProvider',
|
||||
|
||||
@ -361,70 +537,183 @@ const AppSearchProvider = new Lang.Class({
|
||||
|
||||
createResultActor: function (resultMeta, terms) {
|
||||
let app = resultMeta['id'];
|
||||
let icon = new AppWellIcon(app);
|
||||
let icon = new AppIcon(app);
|
||||
return icon.actor;
|
||||
}
|
||||
});
|
||||
|
||||
const SettingsSearchProvider = new Lang.Class({
|
||||
Name: 'SettingsSearchProvider',
|
||||
const FolderIcon = new Lang.Class({
|
||||
Name: 'FolderIcon',
|
||||
|
||||
_init: function() {
|
||||
this.appInfo = Gio.DesktopAppInfo.new('gnome-control-center.desktop');
|
||||
this._appSys = Shell.AppSystem.get_default();
|
||||
_init: function(dir, parentView) {
|
||||
this._dir = dir;
|
||||
this._parentView = parentView;
|
||||
|
||||
this.actor = new St.Button({ style_class: 'app-well-app app-folder',
|
||||
button_mask: St.ButtonMask.ONE,
|
||||
toggle_mode: true,
|
||||
can_focus: true,
|
||||
x_fill: true,
|
||||
y_fill: true });
|
||||
this.actor._delegate = this;
|
||||
|
||||
let label = this._dir.get_name();
|
||||
this.icon = new IconGrid.BaseIcon(label,
|
||||
{ createIcon: Lang.bind(this, this._createIcon) });
|
||||
this.actor.set_child(this.icon.actor);
|
||||
this.actor.label_actor = this.icon.label;
|
||||
|
||||
this.view = new FolderView();
|
||||
this.view.actor.reactive = false;
|
||||
_loadCategory(dir, this.view);
|
||||
this.view.loadGrid();
|
||||
|
||||
this.actor.connect('clicked', Lang.bind(this,
|
||||
function() {
|
||||
this._ensurePopup();
|
||||
this._popup.toggle();
|
||||
}));
|
||||
this.actor.connect('notify::mapped', Lang.bind(this,
|
||||
function() {
|
||||
if (!this.actor.mapped && this._popup)
|
||||
this._popup.popdown();
|
||||
}));
|
||||
},
|
||||
|
||||
getResultMetas: function(prefs, callback) {
|
||||
let metas = [];
|
||||
for (let i = 0; i < prefs.length; i++) {
|
||||
let pref = prefs[i];
|
||||
metas.push({ 'id': pref,
|
||||
'name': pref.get_name(),
|
||||
'createIcon': function() { return null; }
|
||||
});
|
||||
_createIcon: function(size) {
|
||||
return this.view.createFolderIcon(size);
|
||||
},
|
||||
|
||||
_ensurePopup: function() {
|
||||
if (this._popup)
|
||||
return;
|
||||
|
||||
let spaceTop = this.actor.y;
|
||||
let spaceBottom = this._parentView.actor.height - (this.actor.y + this.actor.height);
|
||||
let side = spaceTop > spaceBottom ? St.Side.BOTTOM : St.Side.TOP;
|
||||
|
||||
this._popup = new AppFolderPopup(this, side);
|
||||
this._parentView.addFolderPopup(this._popup);
|
||||
|
||||
// Position the popup above or below the source icon
|
||||
if (side == St.Side.BOTTOM) {
|
||||
this._popup.actor.show();
|
||||
let closeButtonOffset = -this._popup.closeButton.translation_y;
|
||||
let y = this.actor.y - this._popup.actor.height;
|
||||
let yWithButton = y - closeButtonOffset;
|
||||
this._popup.parentOffset = yWithButton < 0 ? -yWithButton : 0;
|
||||
this._popup.actor.y = Math.max(y, closeButtonOffset);
|
||||
this._popup.actor.hide();
|
||||
} else {
|
||||
this._popup.actor.y = this.actor.y + this.actor.height;
|
||||
}
|
||||
callback(metas);
|
||||
|
||||
this._popup.connect('open-state-changed', Lang.bind(this,
|
||||
function(popup, isOpen) {
|
||||
if (!isOpen)
|
||||
this.actor.checked = false;
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
const AppFolderPopup = new Lang.Class({
|
||||
Name: 'AppFolderPopup',
|
||||
|
||||
_init: function(source, side) {
|
||||
this._source = source;
|
||||
this._view = source.view;
|
||||
this._arrowSide = side;
|
||||
|
||||
this._isOpen = false;
|
||||
this.parentOffset = 0;
|
||||
|
||||
this.actor = new St.Widget({ layout_manager: new Clutter.BinLayout(),
|
||||
visible: false,
|
||||
// We don't want to expand really, but look
|
||||
// at the layout manager of our parent...
|
||||
//
|
||||
// DOUBLE HACK: if you set one, you automatically
|
||||
// get the effect for the other direction too, so
|
||||
// we need to set the y_align
|
||||
x_expand: true,
|
||||
y_expand: true,
|
||||
x_align: Clutter.ActorAlign.CENTER,
|
||||
y_align: Clutter.ActorAlign.START });
|
||||
this._boxPointer = new BoxPointer.BoxPointer(this._arrowSide,
|
||||
{ style_class: 'app-folder-popup-bin',
|
||||
x_fill: true,
|
||||
y_fill: true,
|
||||
x_align: St.Align.START });
|
||||
|
||||
this._boxPointer.actor.style_class = 'app-folder-popup';
|
||||
this.actor.add_actor(this._boxPointer.actor);
|
||||
this._boxPointer.bin.set_child(this._view.actor);
|
||||
|
||||
this.closeButton = Util.makeCloseButton();
|
||||
this.closeButton.connect('clicked', Lang.bind(this, this.popdown));
|
||||
this.actor.add_actor(this.closeButton);
|
||||
|
||||
this._boxPointer.actor.bind_property('opacity', this.closeButton, 'opacity',
|
||||
GObject.BindingFlags.SYNC_CREATE);
|
||||
|
||||
global.focus_manager.add_group(this.actor);
|
||||
|
||||
source.actor.connect('destroy', Lang.bind(this,
|
||||
function() {
|
||||
this.actor.destroy();
|
||||
}));
|
||||
this.actor.connect('key-press-event', Lang.bind(this, this._onKeyPress));
|
||||
},
|
||||
|
||||
getInitialResultSet: function(terms) {
|
||||
this.searchSystem.pushResults(this, this._appSys.search_settings(terms));
|
||||
_onKeyPress: function(actor, event) {
|
||||
if (!this._isOpen)
|
||||
return false;
|
||||
|
||||
if (event.get_key_symbol() != Clutter.KEY_Escape)
|
||||
return false;
|
||||
|
||||
this.popdown();
|
||||
return true;
|
||||
},
|
||||
|
||||
getSubsearchResultSet: function(previousResults, terms) {
|
||||
this.searchSystem.pushResults(this, this._appSys.search_settings(terms));
|
||||
toggle: function() {
|
||||
if (this._isOpen)
|
||||
this.popdown();
|
||||
else
|
||||
this.popup();
|
||||
},
|
||||
|
||||
activateResult: function(pref) {
|
||||
pref.activate();
|
||||
popup: function() {
|
||||
if (this._isOpen)
|
||||
return;
|
||||
|
||||
this.actor.show();
|
||||
this.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
|
||||
this._boxPointer.setArrowActor(this._source.actor);
|
||||
this._boxPointer.show(BoxPointer.PopupAnimation.FADE |
|
||||
BoxPointer.PopupAnimation.SLIDE);
|
||||
|
||||
this._isOpen = true;
|
||||
this.emit('open-state-changed', true);
|
||||
},
|
||||
|
||||
launchSearch: function(terms) {
|
||||
// FIXME: this should be a remote search provider
|
||||
this.appInfo.launch([], global.create_app_launch_context());
|
||||
popdown: function() {
|
||||
if (!this._isOpen)
|
||||
return;
|
||||
|
||||
this._boxPointer.hide(BoxPointer.PopupAnimation.FADE |
|
||||
BoxPointer.PopupAnimation.SLIDE);
|
||||
this._isOpen = false;
|
||||
this.emit('open-state-changed', false);
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(AppFolderPopup.prototype);
|
||||
|
||||
const AppIcon = new Lang.Class({
|
||||
Name: 'AppIcon',
|
||||
Extends: IconGrid.BaseIcon,
|
||||
|
||||
_init : function(app, params) {
|
||||
this.app = app;
|
||||
|
||||
let label = this.app.get_name();
|
||||
|
||||
this.parent(label, params);
|
||||
},
|
||||
|
||||
createIcon: function(iconSize) {
|
||||
return this.app.create_icon_texture(iconSize);
|
||||
}
|
||||
});
|
||||
|
||||
const AppWellIcon = new Lang.Class({
|
||||
Name: 'AppWellIcon',
|
||||
|
||||
_init : function(app, iconParams, onActivateOverride) {
|
||||
_init : function(app, iconParams) {
|
||||
this.app = app;
|
||||
this.actor = new St.Button({ style_class: 'app-well-app',
|
||||
reactive: true,
|
||||
@ -434,13 +723,15 @@ const AppWellIcon = new Lang.Class({
|
||||
y_fill: true });
|
||||
this.actor._delegate = this;
|
||||
|
||||
this.icon = new AppIcon(app, iconParams);
|
||||
if (!iconParams)
|
||||
iconParams = {};
|
||||
|
||||
iconParams['createIcon'] = Lang.bind(this, this._createIcon);
|
||||
this.icon = new IconGrid.BaseIcon(app.get_name(), iconParams);
|
||||
this.actor.set_child(this.icon.actor);
|
||||
|
||||
this.actor.label_actor = this.icon.label;
|
||||
|
||||
// A function callback to override the default "app.activate()"; used by preferences
|
||||
this._onActivateOverride = onActivateOverride;
|
||||
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
|
||||
this.actor.connect('clicked', Lang.bind(this, this._onClicked));
|
||||
this.actor.connect('popup-menu', Lang.bind(this, this._onKeyboardPopupMenu));
|
||||
@ -479,6 +770,10 @@ const AppWellIcon = new Lang.Class({
|
||||
this._removeMenuTimeout();
|
||||
},
|
||||
|
||||
_createIcon: function(iconSize) {
|
||||
return this.app.create_icon_texture(iconSize);
|
||||
},
|
||||
|
||||
_removeMenuTimeout: function() {
|
||||
if (this._menuTimeoutId > 0) {
|
||||
Mainloop.source_remove(this._menuTimeoutId);
|
||||
@ -536,6 +831,7 @@ const AppWellIcon = new Lang.Class({
|
||||
popupMenu: function() {
|
||||
this._removeMenuTimeout();
|
||||
this.actor.fake_release();
|
||||
this._draggable.fakeRelease();
|
||||
|
||||
if (!this._menu) {
|
||||
this._menu = new AppIconMenu(this);
|
||||
@ -577,16 +873,13 @@ const AppWellIcon = new Lang.Class({
|
||||
this.emit('launching');
|
||||
let modifiers = event.get_state();
|
||||
|
||||
if (this._onActivateOverride) {
|
||||
this._onActivateOverride(event);
|
||||
if (modifiers & Clutter.ModifierType.CONTROL_MASK
|
||||
&& this.app.state == Shell.AppState.RUNNING) {
|
||||
this.app.open_new_window(-1);
|
||||
} else {
|
||||
if (modifiers & Clutter.ModifierType.CONTROL_MASK
|
||||
&& this.app.state == Shell.AppState.RUNNING) {
|
||||
this.app.open_new_window(-1);
|
||||
} else {
|
||||
this.app.activate();
|
||||
}
|
||||
this.app.activate();
|
||||
}
|
||||
|
||||
Main.overview.hide();
|
||||
},
|
||||
|
||||
@ -607,7 +900,7 @@ const AppWellIcon = new Lang.Class({
|
||||
return this.icon.icon;
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(AppWellIcon.prototype);
|
||||
Signals.addSignalMethods(AppIcon.prototype);
|
||||
|
||||
const AppIconMenu = new Lang.Class({
|
||||
Name: 'AppIconMenu',
|
||||
|
798
js/ui/background.js
Normal file
798
js/ui/background.js
Normal file
@ -0,0 +1,798 @@
|
||||
// -*- 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 });
|
||||
this.actor = new Meta.BackgroundGroup();
|
||||
this.actor._delegate = this;
|
||||
|
||||
this._destroySignalId = this.actor.connect('destroy',
|
||||
Lang.bind(this, this._destroy));
|
||||
|
||||
this._settings = new Gio.Settings({ schema: BACKGROUND_SCHEMA });
|
||||
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._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;
|
||||
},
|
||||
|
||||
_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 = Gio.File.new_for_uri(uri).get_path();
|
||||
|
||||
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 });
|
||||
|
||||
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 });
|
||||
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();
|
||||
});
|
||||
}
|
@ -38,6 +38,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();
|
||||
@ -228,6 +229,19 @@ const BoxPointer = new Lang.Class({
|
||||
_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');
|
||||
@ -537,6 +551,16 @@ 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
|
||||
|
@ -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;
|
||||
@ -62,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;
|
||||
}
|
||||
}
|
||||
@ -164,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) {
|
||||
@ -187,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) {
|
||||
@ -228,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;
|
||||
@ -279,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)
|
||||
@ -297,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;
|
||||
@ -371,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
|
||||
@ -407,16 +443,18 @@ const Calendar = new Lang.Class({
|
||||
this.actor.add(this._topBox,
|
||||
{ row: 0, col: 0, col_span: offsetCols + 7 });
|
||||
|
||||
let back = new St.Button({ style_class: 'calendar-change-month-back' });
|
||||
this._topBox.add(back);
|
||||
back.connect('clicked', Lang.bind(this, this._onPrevMonthButtonClicked));
|
||||
this._backButton = new St.Button({ style_class: 'calendar-change-month-back',
|
||||
can_focus: true });
|
||||
this._topBox.add(this._backButton);
|
||||
this._backButton.connect('clicked', Lang.bind(this, this._onPrevMonthButtonClicked));
|
||||
|
||||
this._monthLabel = new St.Label({style_class: 'calendar-month-label'});
|
||||
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',
|
||||
can_focus: true });
|
||||
this._topBox.add(this._forwardButton);
|
||||
this._forwardButton.connect('clicked', Lang.bind(this, this._onNextMonthButtonClicked));
|
||||
|
||||
// Add weekday labels...
|
||||
//
|
||||
@ -475,10 +513,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) {
|
||||
@ -497,7 +537,9 @@ const Calendar = new Lang.Class({
|
||||
}
|
||||
}
|
||||
|
||||
this.setDate(newDate, false);
|
||||
this._forwardButton.grab_key_focus();
|
||||
|
||||
this.setDate(newDate, false);
|
||||
},
|
||||
|
||||
_onSettingsChange: function() {
|
||||
@ -520,30 +562,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
|
||||
@ -563,9 +635,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'
|
||||
|
||||
@ -575,6 +644,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'});
|
||||
@ -583,17 +659,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);
|
||||
}
|
||||
});
|
||||
|
||||
@ -611,16 +683,8 @@ 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) {
|
||||
@ -637,9 +701,6 @@ const EventsList = new Lang.Class({
|
||||
},
|
||||
|
||||
_addPeriod: function(header, begin, end, includeDayName, showNothingScheduled) {
|
||||
if (!this._eventSource)
|
||||
return;
|
||||
|
||||
let events = this._eventSource.getEvents(begin, end);
|
||||
|
||||
let clockFormat = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);;
|
||||
@ -736,6 +797,9 @@ const EventsList = new Lang.Class({
|
||||
},
|
||||
|
||||
_update: function() {
|
||||
if (this._eventSource.isLoading)
|
||||
return;
|
||||
|
||||
let today = new Date();
|
||||
if (_sameDay (this._date, today)) {
|
||||
this._showToday();
|
||||
|
@ -4,6 +4,7 @@ const Lang = imports.lang;
|
||||
const Gio = imports.gi.Gio;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const GnomeSession = imports.misc.gnomeSession;
|
||||
const Main = imports.ui.main;
|
||||
const MessageTray = imports.ui.messageTray;
|
||||
const ShellMountOperation = imports.ui.shellMountOperation;
|
||||
@ -161,6 +162,7 @@ const AutorunManager = new Lang.Class({
|
||||
Name: 'AutorunManager',
|
||||
|
||||
_init: function() {
|
||||
this._session = new GnomeSession.SessionManager();
|
||||
this._volumeMonitor = Gio.VolumeMonitor.get();
|
||||
|
||||
this._transDispatcher = new AutorunTransientDispatcher(this);
|
||||
@ -290,6 +292,7 @@ const AutorunResidentSource = new Lang.Class({
|
||||
|
||||
_init: function(manager) {
|
||||
this.parent(_("Removable Devices"), 'media-removable');
|
||||
this.resident = true;
|
||||
|
||||
this._mounts = [];
|
||||
|
||||
|
@ -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 });
|
||||
@ -63,11 +63,17 @@ const KeyringDialog = new Lang.Class({
|
||||
|
||||
this._cancelButton = this.addButton({ label: '',
|
||||
action: Lang.bind(this, this._onCancelButton),
|
||||
key: Clutter.Escape });
|
||||
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: true, x_fill: false, x_align: St.Align.END });
|
||||
{ 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);
|
||||
@ -143,11 +149,19 @@ const KeyringDialog = new Lang.Class({
|
||||
},
|
||||
|
||||
_updateSensitivity: function(sensitive) {
|
||||
this._passwordEntry.reactive = sensitive;
|
||||
this._passwordEntry.clutter_text.editable = 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() {
|
||||
@ -222,7 +236,7 @@ const KeyringPrompter = new Lang.Class({
|
||||
enable: function() {
|
||||
this._prompter.register(Gio.DBus.session);
|
||||
this._dbusId = Gio.DBus.session.own_name('org.gnome.keyring.SystemPrompter',
|
||||
Gio.BusNameOwnerFlags.REPLACE, null, null);
|
||||
Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT, null, null);
|
||||
},
|
||||
|
||||
disable: function() {
|
||||
|
@ -587,18 +587,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,12 +613,15 @@ const NetworkAgent = new Lang.Class({
|
||||
this._vpnRequests[requestId].cancel(true);
|
||||
this._vpnRequests = { };
|
||||
|
||||
this._native.auto_register = false;
|
||||
if (this._native.registered)
|
||||
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;
|
||||
|
@ -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 });
|
||||
@ -161,26 +160,32 @@ const AuthenticationDialog = new Lang.Class({
|
||||
|
||||
this._cancelButton = this.addButton({ label: _("Cancel"),
|
||||
action: Lang.bind(this, this.cancel),
|
||||
key: Clutter.Escape });
|
||||
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: true, x_fill: false, x_align: St.Align.END });
|
||||
{ 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();
|
||||
},
|
||||
|
||||
@ -202,14 +207,14 @@ 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);
|
||||
}
|
||||
},
|
||||
|
||||
@ -219,6 +224,7 @@ const AuthenticationDialog = new Lang.Class({
|
||||
|
||||
this._okButton.can_focus = sensitive;
|
||||
this._okButton.reactive = sensitive;
|
||||
this.setWorking(!sensitive);
|
||||
},
|
||||
|
||||
_onEntryActivate: function() {
|
||||
@ -237,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),
|
||||
@ -258,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) {
|
||||
@ -303,6 +315,7 @@ const AuthenticationDialog = new Lang.Class({
|
||||
if (this._session) {
|
||||
if (!this._completed)
|
||||
this._session.cancel();
|
||||
this._completed = false;
|
||||
this._session = null;
|
||||
}
|
||||
},
|
||||
@ -317,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);
|
||||
@ -327,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));
|
||||
@ -335,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) {
|
||||
@ -356,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;
|
||||
|
@ -20,8 +20,7 @@ const Recorder = new Lang.Class({
|
||||
Main.wm.addKeybinding('toggle-recording',
|
||||
this._bindingSettings,
|
||||
Meta.KeyBindingFlags.NONE,
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW,
|
||||
Shell.KeyBindingMode.ALL,
|
||||
Lang.bind(this, this._toggleRecorder));
|
||||
},
|
||||
|
||||
|
@ -640,6 +640,10 @@ const ChatSource = new Lang.Class({
|
||||
return this._pendingMessages.length;
|
||||
},
|
||||
|
||||
get indicatorCount() {
|
||||
return this.count;
|
||||
},
|
||||
|
||||
get unseenCount() {
|
||||
return this.count;
|
||||
},
|
||||
|
@ -61,8 +61,7 @@ const CtrlAltTabManager = new Lang.Class({
|
||||
if (item.focusCallback) {
|
||||
item.focusCallback(timestamp);
|
||||
} else {
|
||||
if (global.stage_input_mode == Shell.StageInputMode.NONREACTIVE ||
|
||||
global.stage_input_mode == Shell.StageInputMode.NORMAL)
|
||||
if (global.stage_input_mode == Shell.StageInputMode.NORMAL)
|
||||
global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
|
||||
|
||||
item.root.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
@ -89,21 +88,33 @@ const CtrlAltTabManager = new Lang.Class({
|
||||
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');
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
206
js/ui/dash.js
206
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() {
|
||||
@ -361,6 +356,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 +398,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,
|
||||
@ -415,12 +429,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() {
|
||||
@ -455,7 +467,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();
|
||||
@ -491,9 +503,9 @@ const Dash = new Lang.Class({
|
||||
},
|
||||
|
||||
_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,7 +522,7 @@ 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());
|
||||
@ -570,13 +582,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 +601,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 +637,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 +657,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 +672,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 +744,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 +761,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,18 +779,20 @@ 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() {
|
||||
@ -818,7 +823,7 @@ 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--;
|
||||
}
|
||||
|
||||
@ -832,7 +837,7 @@ const Dash = new Lang.Class({
|
||||
if (this._dragPlaceholder) {
|
||||
this._dragPlaceholder.animateOutAndDestroy();
|
||||
this._animatingPlaceholdersCount++;
|
||||
this._dragPlaceholder.actor.connect('destroy',
|
||||
this._dragPlaceholder.connect('destroy',
|
||||
Lang.bind(this, function() {
|
||||
this._animatingPlaceholdersCount--;
|
||||
}));
|
||||
@ -847,7 +852,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,10 +861,9 @@ 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
|
||||
@ -897,10 +901,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)
|
||||
|
@ -49,11 +49,6 @@ 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.actor.add_actor(this._clockDisplay);
|
||||
|
||||
@ -90,22 +85,18 @@ const DateMenuButton = new Lang.Class({
|
||||
|
||||
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._openClocksItem = new PopupMenu.PopupMenuItem(_("Open Clocks"));
|
||||
this._openClocksItem.connect('activate', Lang.bind(this, this._onOpenClocksActivate));
|
||||
this._openClocksItem.actor.can_focus = false;
|
||||
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));
|
||||
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;
|
||||
}
|
||||
@ -157,12 +148,16 @@ const DateMenuButton = new Lang.Class({
|
||||
},
|
||||
|
||||
_appInstalledChanged: function() {
|
||||
let app = Shell.AppSystem.get_default().lookup_app('gnome-clocks.desktop');
|
||||
this._openClocksItem.actor.visible = app !== null;
|
||||
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;
|
||||
if (visible) {
|
||||
let alignment = 0.25;
|
||||
@ -177,8 +172,16 @@ const DateMenuButton = new Lang.Class({
|
||||
},
|
||||
|
||||
_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() {
|
||||
@ -187,10 +190,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
|
||||
@ -207,16 +210,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 app = Gio.AppInfo.get_default_for_type('text/calendar', false);
|
||||
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 = Shell.AppSystem.get_default().lookup_app('gnome-clocks.desktop');
|
||||
let app = this._getClockApp();
|
||||
app.activate();
|
||||
}
|
||||
});
|
||||
|
48
js/ui/dnd.js
48
js/ui/dnd.js
@ -43,9 +43,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 +83,13 @@ 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,6 +102,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).
|
||||
this._dragCancellable = true;
|
||||
|
||||
// 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
|
||||
@ -136,9 +137,10 @@ 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;
|
||||
},
|
||||
@ -203,6 +205,19 @@ const _Draggable = new Lang.Class({
|
||||
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
|
||||
@ -274,19 +289,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;
|
||||
@ -425,6 +440,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 +453,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) {
|
||||
|
@ -19,6 +19,7 @@
|
||||
*/
|
||||
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const AccountsService = imports.gi.AccountsService;
|
||||
@ -50,6 +51,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" />
|
||||
@ -223,7 +225,8 @@ 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());
|
||||
|
||||
@ -377,7 +380,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 +393,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) {
|
||||
@ -408,22 +418,34 @@ const EndSessionDialog = new Lang.Class({
|
||||
},
|
||||
|
||||
_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._updateDescription();
|
||||
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;
|
||||
},
|
||||
|
||||
@ -440,7 +462,7 @@ const EndSessionDialog = new Lang.Class({
|
||||
let item = new ListItem(app, reason);
|
||||
item.connect('activate',
|
||||
Lang.bind(this, function() {
|
||||
this.close(global.get_current_time());
|
||||
this.close();
|
||||
}));
|
||||
this._applicationList.add(item.actor, { x_fill: true });
|
||||
this._stopTimer();
|
||||
@ -488,5 +510,9 @@ const EndSessionDialog = new Lang.Class({
|
||||
invocation.return_value(null);
|
||||
this.disconnect(signalId);
|
||||
}));
|
||||
},
|
||||
|
||||
Close: function(parameters, invocation) {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -292,7 +292,7 @@ function disableAllExtensions() {
|
||||
return;
|
||||
|
||||
if (initted) {
|
||||
enabledExtensions.forEach(function(uuid) {
|
||||
extensionOrder.slice().reverse().forEach(function(uuid) {
|
||||
disableExtension(uuid);
|
||||
});
|
||||
}
|
||||
|
@ -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,17 +10,6 @@ const St = imports.gi.St;
|
||||
const Main = imports.ui.main;
|
||||
const Params = imports.misc.params;
|
||||
|
||||
function _navigateActor(actor) {
|
||||
if (!actor)
|
||||
return;
|
||||
|
||||
let needsGrab = true;
|
||||
if (actor instanceof St.Widget)
|
||||
needsGrab = !actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
if (needsGrab)
|
||||
actor.grab_key_focus();
|
||||
}
|
||||
|
||||
// GrabHelper:
|
||||
// @owner: the actor that owns the GrabHelper
|
||||
// @params: optional parameters to pass to Main.pushModal()
|
||||
@ -46,6 +35,7 @@ const GrabHelper = new Lang.Class({
|
||||
this._keyFocusNotifyId = 0;
|
||||
this._focusWindowChangedId = 0;
|
||||
this._ignoreRelease = false;
|
||||
this._isUngrabbingCount = 0;
|
||||
|
||||
this._modalCount = 0;
|
||||
this._grabFocusCount = 0;
|
||||
@ -77,7 +67,7 @@ const GrabHelper = new Lang.Class({
|
||||
},
|
||||
|
||||
_isWithinGrabbedActor: function(actor) {
|
||||
let currentActor = this.currentGrab.actor;
|
||||
let currentActor = this.currentGrab.actor;
|
||||
while (actor) {
|
||||
if (this._actors.indexOf(actor) != -1)
|
||||
return true;
|
||||
@ -178,15 +168,18 @@ const GrabHelper = new Lang.Class({
|
||||
if (params.grabFocus && !this._takeFocusGrab(hadFocus))
|
||||
return false;
|
||||
|
||||
if (params.focus)
|
||||
this._grabStack.push(params);
|
||||
|
||||
if (params.focus) {
|
||||
params.focus.grab_key_focus();
|
||||
else if (hadFocus || params.grabFocus)
|
||||
_navigateActor(newFocus);
|
||||
} else if (newFocus && (hadFocus || params.grabFocus)) {
|
||||
if (!newFocus.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false))
|
||||
newFocus.grab_key_focus();
|
||||
}
|
||||
|
||||
if ((params.grabFocus || params.modal) && !this._capturedEventId)
|
||||
this._capturedEventId = global.stage.connect('captured-event', Lang.bind(this, this._onCapturedEvent));
|
||||
|
||||
this._grabStack.push(params);
|
||||
return true;
|
||||
},
|
||||
|
||||
@ -218,10 +211,8 @@ const GrabHelper = new Lang.Class({
|
||||
this._grabbedFromKeynav = hadFocus;
|
||||
this._preGrabInputMode = global.stage_input_mode;
|
||||
|
||||
if (this._preGrabInputMode == Shell.StageInputMode.NONREACTIVE ||
|
||||
this._preGrabInputMode == Shell.StageInputMode.NORMAL) {
|
||||
if (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));
|
||||
@ -241,7 +232,7 @@ const GrabHelper = new Lang.Class({
|
||||
this._keyFocusNotifyId = 0;
|
||||
}
|
||||
|
||||
if (!this._focusWindowChanged > 0) {
|
||||
if (this._focusWindowChangedId > 0) {
|
||||
let metaDisplay = global.screen.get_display();
|
||||
metaDisplay.disconnect(this._focusWindowChangedId);
|
||||
this._focusWindowChangedId = 0;
|
||||
@ -255,7 +246,7 @@ const GrabHelper = new Lang.Class({
|
||||
global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
|
||||
}
|
||||
|
||||
global.screen.focus_default_window(global.get_current_time());
|
||||
global.screen.focus_default_window(global.display.get_current_time_roundtrip());
|
||||
},
|
||||
|
||||
// ignoreRelease:
|
||||
@ -283,6 +274,14 @@ const GrabHelper = new Lang.Class({
|
||||
if (grabStackIndex < 0)
|
||||
return;
|
||||
|
||||
// We may get key focus changes when calling onUngrab, which
|
||||
// would cause an extra ungrab() on the next actor in the
|
||||
// stack, which is wrong. Ignore key focus changes during the
|
||||
// ungrab, and restore the saved key focus ourselves afterwards.
|
||||
// We use a count as ungrab() may be re-entrant, as onUngrab()
|
||||
// may ungrab additional actors.
|
||||
this._isUngrabbingCount++;
|
||||
|
||||
let focus = global.stage.key_focus;
|
||||
let hadFocus = focus && this._isWithinGrabbedActor(focus);
|
||||
|
||||
@ -307,12 +306,17 @@ const GrabHelper = new Lang.Class({
|
||||
if (!this.grabbed && this._capturedEventId > 0) {
|
||||
global.stage.disconnect(this._capturedEventId);
|
||||
this._capturedEventId = 0;
|
||||
|
||||
this._ignoreRelease = false;
|
||||
}
|
||||
|
||||
if (hadFocus) {
|
||||
let poppedGrab = poppedGrabs[0];
|
||||
_navigateActor(poppedGrab.savedFocus);
|
||||
if (poppedGrab.savedFocus)
|
||||
poppedGrab.savedFocus.grab_key_focus();
|
||||
}
|
||||
|
||||
this._isUngrabbingCount--;
|
||||
},
|
||||
|
||||
_onCapturedEvent: function(actor, event) {
|
||||
@ -349,12 +353,16 @@ const GrabHelper = new Lang.Class({
|
||||
this._ignoreRelease = true;
|
||||
let i = this._actorInGrabStack(event.get_source()) + 1;
|
||||
this.ungrab({ actor: this._grabStack[i].actor, isUser: true });
|
||||
return true;
|
||||
}
|
||||
|
||||
return this._modalCount > 0;
|
||||
},
|
||||
|
||||
_onKeyFocusChanged: function() {
|
||||
if (this._isUngrabbingCount > 0)
|
||||
return;
|
||||
|
||||
let focus = global.stage.key_focus;
|
||||
if (!focus || !this._isWithinGrabbedActor(focus))
|
||||
this.ungrab({ isUser: true });
|
||||
|
@ -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) {
|
||||
|
@ -176,13 +176,16 @@ const IconGrid = new Lang.Class({
|
||||
_init: function(params) {
|
||||
params = Params.parse(params, { rowLimit: null,
|
||||
columnLimit: null,
|
||||
fillParent: false,
|
||||
xAlign: St.Align.MIDDLE });
|
||||
this._rowLimit = params.rowLimit;
|
||||
this._colLimit = params.columnLimit;
|
||||
this._xAlign = params.xAlign;
|
||||
this._fillParent = params.fillParent;
|
||||
|
||||
this.actor = new St.BoxLayout({ style_class: 'icon-grid',
|
||||
vertical: true });
|
||||
|
||||
// Pulled from CSS, but hardcode some defaults here
|
||||
this._spacing = 0;
|
||||
this._hItemSize = this._vItemSize = ICON_SIZE;
|
||||
@ -196,6 +199,11 @@ const IconGrid = new Lang.Class({
|
||||
},
|
||||
|
||||
_getPreferredWidth: function (grid, forHeight, 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._grid.get_children();
|
||||
let nColumns = this._colLimit ? Math.min(this._colLimit,
|
||||
children.length)
|
||||
@ -217,12 +225,20 @@ 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;
|
||||
if (forWidth < 0)
|
||||
let nColumns, spacing;
|
||||
if (forWidth < 0) {
|
||||
nColumns = children.length;
|
||||
else
|
||||
nColumns = this._computeLayout(forWidth)[0];
|
||||
spacing = this._spacing;
|
||||
} else {
|
||||
[nColumns, , spacing] = this._computeLayout(forWidth);
|
||||
}
|
||||
|
||||
let nRows;
|
||||
if (nColumns > 0)
|
||||
nRows = Math.ceil(children.length / nColumns);
|
||||
@ -230,18 +246,25 @@ 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 totalSpacing = Math.max(0, nRows - 1) * spacing;
|
||||
let height = nRows * this._vItemSize + totalSpacing;
|
||||
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 [nColumns, usedWidth] = this._computeLayout(availWidth);
|
||||
let [nColumns, usedWidth, spacing] = this._computeLayout(availWidth);
|
||||
|
||||
let leftPadding;
|
||||
switch(this._xAlign) {
|
||||
@ -280,7 +303,8 @@ const IconGrid = new Lang.Class({
|
||||
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._grid.set_skip_paint(children[i], true);
|
||||
} else {
|
||||
children[i].allocate(childBox, flags);
|
||||
@ -294,10 +318,10 @@ const IconGrid = new Lang.Class({
|
||||
}
|
||||
|
||||
if (columnIndex == 0) {
|
||||
y += this._vItemSize + this._spacing;
|
||||
y += this._vItemSize + spacing;
|
||||
x = box.x1 + leftPadding;
|
||||
} else {
|
||||
x += this._hItemSize + this._spacing;
|
||||
x += this._hItemSize + spacing;
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -313,16 +337,25 @@ const IconGrid = new Lang.Class({
|
||||
_computeLayout: function (forWidth) {
|
||||
let nColumns = 0;
|
||||
let usedWidth = 0;
|
||||
let spacing = this._spacing;
|
||||
|
||||
if (this._colLimit) {
|
||||
let itemWidth = this._hItemSize * this._colLimit;
|
||||
let emptyArea = forWidth - itemWidth;
|
||||
spacing = Math.max(this._spacing, emptyArea / (2 * this._colLimit));
|
||||
spacing = Math.round(spacing);
|
||||
}
|
||||
|
||||
while ((this._colLimit == null || nColumns < this._colLimit) &&
|
||||
(usedWidth + this._hItemSize <= forWidth)) {
|
||||
usedWidth += this._hItemSize + this._spacing;
|
||||
usedWidth += this._hItemSize + spacing;
|
||||
nColumns += 1;
|
||||
}
|
||||
|
||||
if (nColumns > 0)
|
||||
usedWidth -= this._spacing;
|
||||
usedWidth -= spacing;
|
||||
|
||||
return [nColumns, usedWidth];
|
||||
return [nColumns, usedWidth, spacing];
|
||||
},
|
||||
|
||||
_onStyleChanged: function() {
|
||||
|
@ -176,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;
|
||||
@ -204,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);
|
||||
@ -225,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 () {
|
||||
@ -260,58 +253,35 @@ const Keyboard = new Lang.Class({
|
||||
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();
|
||||
}));
|
||||
|
||||
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');
|
||||
}));
|
||||
|
||||
return trayButton;
|
||||
},
|
||||
|
||||
_onCapturedEvent: function(actor, event) {
|
||||
let type = event.type();
|
||||
let press = type == Clutter.EventType.BUTTON_PRESS;
|
||||
@ -329,23 +299,29 @@ const Keyboard = new Lang.Class({
|
||||
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() {
|
||||
@ -362,6 +338,7 @@ const Keyboard = new Lang.Class({
|
||||
}));
|
||||
}
|
||||
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);
|
||||
@ -434,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);
|
||||
|
1138
js/ui/layout.js
1138
js/ui/layout.js
File diff suppressed because it is too large
Load Diff
@ -308,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);
|
||||
}
|
||||
});
|
||||
|
||||
@ -685,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',
|
||||
@ -912,7 +909,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) {
|
||||
@ -950,9 +947,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 +985,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) {
|
||||
|
309
js/ui/main.js
309
js/ui/main.js
@ -18,11 +18,13 @@ 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;
|
||||
@ -31,15 +33,16 @@ 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 A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard';
|
||||
const STICKY_KEYS_ENABLE = 'stickykeys-enable';
|
||||
|
||||
let componentManager = null;
|
||||
let panel = null;
|
||||
let overview = null;
|
||||
@ -51,12 +54,13 @@ 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 modalCount = 0;
|
||||
let keybindingMode = Shell.KeyBindingMode.NORMAL;
|
||||
let keybindingMode = Shell.KeyBindingMode.NONE;
|
||||
let modalActorFocusStack = [];
|
||||
let uiGroup = null;
|
||||
let magnifier = null;
|
||||
@ -66,9 +70,11 @@ let layoutManager = null;
|
||||
let _startDate;
|
||||
let _defaultCssStylesheet = null;
|
||||
let _cssStylesheet = null;
|
||||
let _overridesSettings = null;
|
||||
let _a11ySettings = null;
|
||||
|
||||
function _sessionUpdated() {
|
||||
_loadDefaultStylesheet();
|
||||
|
||||
wm.setCustomKeybindingHandler('panel-main-menu',
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW,
|
||||
@ -80,8 +86,9 @@ function _sessionUpdated() {
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW,
|
||||
sessionMode.hasRunDialog ? openRunDialog : null);
|
||||
if (sessionMode.isGreeter)
|
||||
screenShield.showDialog();
|
||||
|
||||
if (!sessionMode.hasRunDialog && lookingGlass)
|
||||
lookingGlass.close();
|
||||
}
|
||||
|
||||
function start() {
|
||||
@ -95,11 +102,21 @@ function start() {
|
||||
Gio.DesktopAppInfo.set_desktop_env('GNOME');
|
||||
|
||||
sessionMode = new SessionMode.SessionMode();
|
||||
sessionMode.connect('updated', _loadDefaultStylesheet);
|
||||
sessionMode.connect('sessions-loaded', _sessionsLoaded);
|
||||
sessionMode.init();
|
||||
}
|
||||
|
||||
function _sessionsLoaded() {
|
||||
sessionMode.connect('updated', _sessionUpdated);
|
||||
_initializeUI();
|
||||
|
||||
shellDBusService = new ShellDBus.GnomeShell();
|
||||
shellMountOpDBusService = new ShellMountOperation.GnomeShellMountOpHandler();
|
||||
|
||||
_sessionUpdated();
|
||||
}
|
||||
|
||||
function _initializeUI() {
|
||||
// Ensure ShellWindowTracker and ShellAppUsage are initialized; this will
|
||||
// also initialize ShellAppSystem first. ShellAppSystem
|
||||
// needs to load all the .desktop files, and ShellWindowTracker
|
||||
@ -108,51 +125,28 @@ 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;
|
||||
|
||||
_loadDefaultStylesheet();
|
||||
|
||||
// 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);
|
||||
|
||||
// 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;
|
||||
|
||||
xdndHandler = new XdndHandler.XdndHandler();
|
||||
ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager();
|
||||
osdWindow = new OsdWindow.OsdWindow();
|
||||
overview = new Overview.Overview();
|
||||
wm = new WindowManager.WindowManager();
|
||||
magnifier = new Magnifier.Magnifier();
|
||||
if (UnlockDialog.isSupported())
|
||||
if (LoginManager.canLock())
|
||||
screenShield = new ScreenShield.ScreenShield();
|
||||
else
|
||||
screenShield = new ScreenShield.ScreenShieldFallback();
|
||||
|
||||
panel = new Panel.Panel();
|
||||
messageTray = new MessageTray.MessageTray();
|
||||
keyboard = new Keyboard.Keyboard();
|
||||
@ -163,16 +157,20 @@ function start() {
|
||||
layoutManager.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('updated', _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);
|
||||
@ -184,200 +182,23 @@ 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();
|
||||
}
|
||||
|
||||
let _workspaces = [];
|
||||
let _checkWorkspacesId = 0;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
function _checkWorkspaces() {
|
||||
let i;
|
||||
let emptyWorkspaces = [];
|
||||
|
||||
if (!Meta.prefs_get_dynamic_workspaces()) {
|
||||
_checkWorkspacesId = 0;
|
||||
return false;
|
||||
if (sessionMode.isGreeter && screenShield) {
|
||||
layoutManager.connect('startup-prepared', function() {
|
||||
screenShield.showDialog();
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
layoutManager.connect('startup-complete', function() {
|
||||
if (keybindingMode == Shell.KeyBindingMode.NONE) {
|
||||
keybindingMode = Shell.KeyBindingMode.NORMAL;
|
||||
}
|
||||
if (screenShield) {
|
||||
screenShield.lockIfWasLocked();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _loadDefaultStylesheet() {
|
||||
@ -430,7 +251,8 @@ function loadTheme() {
|
||||
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();
|
||||
@ -472,17 +294,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)
|
||||
@ -491,10 +302,6 @@ function _findModal(actor) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
function isInModalStack(actor) {
|
||||
return _findModal(actor) != -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* pushModal:
|
||||
* @actor: #ClutterActor which will be given keyboard focus
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -22,6 +22,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,
|
||||
@ -38,13 +42,15 @@ const ModalDialog = new Lang.Class({
|
||||
styleClass: null,
|
||||
parentActor: Main.uiGroup,
|
||||
keybindingMode: Shell.KeyBindingMode.SYSTEM_MODAL,
|
||||
shouldFadeIn: true });
|
||||
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,10 +64,14 @@ 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);
|
||||
@ -77,15 +87,10 @@ const ModalDialog = new Lang.Class({
|
||||
{ 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 });
|
||||
@ -106,6 +111,8 @@ const ModalDialog = new Lang.Class({
|
||||
this._initialKeyFocus = this.dialogLayout;
|
||||
this._initialKeyFocusDestroyId = 0;
|
||||
this._savedKeyFocus = null;
|
||||
|
||||
this._workSpinner = null;
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
@ -179,10 +186,57 @@ 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) {
|
||||
/* This is here because of recursive imports */
|
||||
const Panel = imports.ui.panel;
|
||||
let spinnerIcon = global.datadir + '/theme/process-working.svg';
|
||||
this._workSpinner = new Panel.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;
|
||||
|
||||
@ -265,6 +319,10 @@ const ModalDialog = new Lang.Class({
|
||||
function() {
|
||||
this.state = State.CLOSED;
|
||||
this._group.hide();
|
||||
this.emit('closed');
|
||||
|
||||
if (this._destroyOnClose)
|
||||
this.destroy();
|
||||
})
|
||||
});
|
||||
},
|
||||
|
@ -244,7 +244,7 @@ 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);
|
||||
},
|
||||
|
||||
_imageForNotificationData: function(hints) {
|
||||
@ -527,7 +527,9 @@ const NotificationDaemon = new Lang.Class({
|
||||
|
||||
notification.update(summary, body, { gicon: gicon,
|
||||
bannerMarkup: true,
|
||||
clear: true });
|
||||
clear: true,
|
||||
soundFile: hints['sound-file'],
|
||||
soundName: hints['sound-name'] });
|
||||
notification.setImage(image);
|
||||
|
||||
if (actions.length) {
|
||||
@ -582,7 +584,7 @@ const NotificationDaemon = new Lang.Class({
|
||||
// 'icon-multi',
|
||||
'icon-static',
|
||||
'persistence',
|
||||
// 'sound',
|
||||
'sound',
|
||||
];
|
||||
},
|
||||
|
||||
|
204
js/ui/osdWindow.js
Normal file
204
js/ui/osdWindow.js
Normal file
@ -0,0 +1,204 @@
|
||||
// -*- 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) {
|
||||
this.actor.show();
|
||||
this.actor.opacity = 0;
|
||||
|
||||
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._hideTimeoutId = 0;
|
||||
this._hide();
|
||||
},
|
||||
|
||||
_hide: function() {
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 0,
|
||||
time: FADE_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this, this._reset) });
|
||||
},
|
||||
|
||||
_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,38 +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;
|
||||
|
||||
// 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 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 250 at its darkest to 180 at its most transparent. */ \
|
||||
float y = 250.0 / 255.0; \
|
||||
float x = 180.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 OVERVIEW_ACTIVATION_TIMEOUT = 0.5;
|
||||
|
||||
const ShellInfo = new Lang.Class({
|
||||
Name: 'ShellInfo',
|
||||
@ -117,52 +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);
|
||||
|
||||
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._overview = new St.BoxLayout({ name: 'overview',
|
||||
accessible_name: _("Overview"),
|
||||
reactive: true,
|
||||
vertical: true });
|
||||
vertical: true,
|
||||
x_expand: true,
|
||||
y_expand: true });
|
||||
this._overview._delegate = this;
|
||||
|
||||
this._group = new St.BoxLayout({ name: 'overview-group' });
|
||||
this._backgroundGroup = new Meta.BackgroundGroup();
|
||||
Main.layoutManager.overviewGroup.add_child(this._backgroundGroup);
|
||||
this._bgManagers = [];
|
||||
|
||||
this._capturedEventId = 0;
|
||||
this._buttonPressId = 0;
|
||||
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._coverPane = new Clutter.Actor({ opacity: 0,
|
||||
reactive: true });
|
||||
this._overview.add_actor(this._coverPane);
|
||||
this._coverPane.connect('event', Lang.bind(this, function (actor, event) { return true; }));
|
||||
|
||||
this._overview.hide();
|
||||
global.overlay_group.add_actor(this._overview);
|
||||
this._stack.add_actor(this._overview);
|
||||
Main.layoutManager.overviewGroup.add_child(this._stack);
|
||||
|
||||
this._coverPane.hide();
|
||||
|
||||
@ -186,6 +176,56 @@ const Overview = new Lang.Class({
|
||||
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() {
|
||||
this.isDummy = !Main.sessionMode.hasOverview;
|
||||
this._createOverview();
|
||||
@ -215,43 +255,31 @@ const Overview = new Lang.Class({
|
||||
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._searchEntryBin = new St.Bin({ child: this._searchEntry,
|
||||
x_align: St.Align.MIDDLE });
|
||||
this._overview.add_actor(this._searchEntryBin);
|
||||
|
||||
// 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 = new Dash.Dash();
|
||||
this._group.add_actor(this._dash.actor);
|
||||
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');
|
||||
|
||||
this._viewSelector = new ViewSelector.ViewSelector(this._searchEntry,
|
||||
this._dash.showAppsButton);
|
||||
this._group.add(this._viewSelector.actor, { x_fill: true,
|
||||
expand: true });
|
||||
|
||||
// Add our same-line elements after the search entry
|
||||
this._overview.add(this._group, { y_fill: true,
|
||||
expand: true });
|
||||
|
||||
// Then account for message tray
|
||||
this._messageTrayGhost = new St.Bin({ style_class: 'message-tray-summary',
|
||||
reactive: false,
|
||||
opacity: 0,
|
||||
x_fill: true,
|
||||
y_fill: true });
|
||||
this._overview.add_actor(this._messageTrayGhost);
|
||||
|
||||
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._relayout));
|
||||
this._relayout();
|
||||
},
|
||||
@ -277,18 +305,22 @@ 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) {
|
||||
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;
|
||||
@ -336,7 +368,7 @@ const Overview = new Lang.Class({
|
||||
this._needsFakePointerEvent = true;
|
||||
Main.activateWindow(dragEvent.targetActor._delegate.metaWindow,
|
||||
this._windowSwitchTimestamp);
|
||||
this.hideTemporarily();
|
||||
this.hide();
|
||||
this._lastHoveredWindow = null;
|
||||
}));
|
||||
}
|
||||
@ -344,6 +376,10 @@ const Overview = new Lang.Class({
|
||||
return DND.DragMotionResult.CONTINUE;
|
||||
},
|
||||
|
||||
_onScrollEvent: function(actor, event) {
|
||||
this.emit('scroll-event', event);
|
||||
},
|
||||
|
||||
addAction: function(action) {
|
||||
if (this.isDummy)
|
||||
return;
|
||||
@ -373,16 +409,12 @@ const Overview = new Lang.Class({
|
||||
// when it is next shown.
|
||||
this.hide();
|
||||
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
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._overview.set_position(primary.x, primary.y);
|
||||
this._overview.set_size(primary.width, primary.height);
|
||||
|
||||
this._coverPane.set_position(0, contentY);
|
||||
this._coverPane.set_size(primary.width, contentHeight);
|
||||
this._updateBackgrounds();
|
||||
},
|
||||
|
||||
_onRestacked: function() {
|
||||
@ -401,6 +433,7 @@ const Overview = new Lang.Class({
|
||||
|
||||
beginItemDrag: function(source) {
|
||||
this.emit('item-drag-begin');
|
||||
this._inDrag = true;
|
||||
},
|
||||
|
||||
cancelledItemDrag: function(source) {
|
||||
@ -409,10 +442,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) {
|
||||
@ -421,23 +456,31 @@ 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;
|
||||
this._shown = true;
|
||||
this._syncInputMode();
|
||||
if (!this._modal)
|
||||
|
||||
if (!this._syncGrab())
|
||||
return;
|
||||
|
||||
Main.layoutManager.showOverview();
|
||||
this._animateVisible();
|
||||
},
|
||||
|
||||
focusSearch: function() {
|
||||
this.show();
|
||||
this._searchEntry.grab_key_focus();
|
||||
},
|
||||
|
||||
fadeInDesktop: function() {
|
||||
this._desktopFade.opacity = 0;
|
||||
this._desktopFade.show();
|
||||
@ -466,6 +509,8 @@ 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
|
||||
@ -477,49 +522,23 @@ const Overview = new Lang.Class({
|
||||
//
|
||||
// Disable unredirection while in the overview
|
||||
Meta.disable_unredirect_for_screen(global.screen);
|
||||
global.window_group.hide();
|
||||
this._overview.show();
|
||||
this._background.show();
|
||||
this._viewSelector.show();
|
||||
|
||||
this._overview.opacity = 0;
|
||||
Tweener.addTween(this._overview,
|
||||
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()
|
||||
@ -530,28 +549,20 @@ 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();
|
||||
},
|
||||
|
||||
// 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() {
|
||||
@ -564,37 +575,51 @@ const Overview = new Lang.Class({
|
||||
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._overview,
|
||||
{ keybindingMode: Shell.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._overview);
|
||||
this._modal = false;
|
||||
}
|
||||
global.stage_input_mode = Shell.StageInputMode.FULLSCREEN;
|
||||
} else {
|
||||
if (this._modal) {
|
||||
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() {
|
||||
@ -602,24 +627,19 @@ const Overview = new Lang.Class({
|
||||
return;
|
||||
|
||||
this.animationInProgress = true;
|
||||
this._hideInProgress = true;
|
||||
this.visibleTarget = false;
|
||||
|
||||
this._viewSelector.zoomFromOverview();
|
||||
|
||||
// Make other elements fade out.
|
||||
Tweener.addTween(this._overview,
|
||||
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();
|
||||
@ -633,10 +653,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();
|
||||
},
|
||||
|
||||
@ -644,25 +664,21 @@ const Overview = new Lang.Class({
|
||||
// Re-enable unredirection
|
||||
Meta.enable_unredirect_for_screen(global.screen);
|
||||
|
||||
global.window_group.show();
|
||||
|
||||
this._viewSelector.hide();
|
||||
this._desktopFade.hide();
|
||||
this._background.hide();
|
||||
this._overview.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) {
|
||||
|
601
js/ui/overviewControls.js
Normal file
601
js/ui/overviewControls.js
Normal file
@ -0,0 +1,601 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
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;
|
||||
// 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));
|
||||
this.actor.connect('notify::hover', Lang.bind(this, this.updateSlide));
|
||||
},
|
||||
|
||||
_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;
|
||||
},
|
||||
|
||||
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));
|
||||
},
|
||||
|
||||
getSlide: function() {
|
||||
if (this.visible || this.inDrag)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
},
|
||||
|
||||
_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 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;
|
||||
|
||||
this.actor = new St.Widget({ layout_manager: new Clutter.BinLayout(),
|
||||
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);
|
||||
|
||||
this._group.connect('notify::allocation', 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();
|
||||
}
|
||||
});
|
174
js/ui/panel.js
174
js/ui/panel.js
@ -18,10 +18,10 @@ const Atk = imports.gi.Atk;
|
||||
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;
|
||||
|
||||
@ -145,8 +145,8 @@ 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);
|
||||
_init: function(filename, size) {
|
||||
this.parent(filename, size, size, ANIMATED_ICON_UPDATE_TIMEOUT);
|
||||
}
|
||||
});
|
||||
|
||||
@ -203,7 +203,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,6 +259,7 @@ 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);
|
||||
@ -289,27 +290,25 @@ const AppMenuButton = new Lang.Class({
|
||||
this._visible = !Main.overview.visible;
|
||||
if (!this._visible)
|
||||
this.actor.hide();
|
||||
Main.overview.connect('hiding', Lang.bind(this, function () {
|
||||
this._overviewHidingId = Main.overview.connect('hiding', Lang.bind(this, function () {
|
||||
this.show();
|
||||
}));
|
||||
Main.overview.connect('showing', Lang.bind(this, function () {
|
||||
this._overviewShowingId = Main.overview.connect('showing', Lang.bind(this, function () {
|
||||
this.hide();
|
||||
}));
|
||||
|
||||
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();
|
||||
},
|
||||
@ -355,6 +354,18 @@ 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 AnimatedIcon(this._spinnerIcon, PANEL_ICON_SIZE);
|
||||
this._container.add_actor(this._spinner.actor);
|
||||
this._spinner.actor.hide();
|
||||
this._spinner.actor.lower_bottom();
|
||||
},
|
||||
|
||||
_onIconBoxStyleChanged: function() {
|
||||
let node = this._iconBox.get_theme_node();
|
||||
this._iconBottomClip = node.get_length('app-icon-bottom-clip');
|
||||
@ -386,6 +397,10 @@ const AppMenuButton = new Lang.Class({
|
||||
|
||||
this._stop = true;
|
||||
this.actor.reactive = true;
|
||||
|
||||
if (this._spinner == null)
|
||||
return;
|
||||
|
||||
Tweener.addTween(this._spinner.actor,
|
||||
{ opacity: 0,
|
||||
time: SPINNER_ANIMATION_TIME,
|
||||
@ -402,6 +417,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();
|
||||
},
|
||||
@ -464,6 +483,9 @@ const AppMenuButton = new Lang.Class({
|
||||
}
|
||||
this._label.actor.allocate(childBox, flags);
|
||||
|
||||
if (this._spinner == null)
|
||||
return;
|
||||
|
||||
if (direction == Clutter.TextDirection.LTR) {
|
||||
childBox.x1 = Math.floor(iconWidth / 2) + this._label.actor.width;
|
||||
childBox.x2 = childBox.x1 + this._spinner.actor.width;
|
||||
@ -547,14 +569,20 @@ const AppMenuButton = new Lang.Class({
|
||||
}
|
||||
|
||||
if (targetApp == this._targetApp) {
|
||||
if (targetApp && targetApp.get_state() != Shell.AppState.STARTING) {
|
||||
if (targetApp &&
|
||||
targetApp.get_state() != Shell.AppState.STARTING &&
|
||||
targetApp.get_state() != Shell.AppState.BUSY) {
|
||||
this.stopAnimation();
|
||||
this._maybeSetMenu();
|
||||
} else if (targetApp &&
|
||||
targetApp.get_state() == Shell.AppState.BUSY) {
|
||||
this.startAnimation();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this._spinner.actor.hide();
|
||||
if (this._spinner)
|
||||
this._spinner.actor.hide();
|
||||
if (this._iconBox.child != null)
|
||||
this._iconBox.child.destroy();
|
||||
this._iconBox.hide();
|
||||
@ -581,7 +609,8 @@ const AppMenuButton = new Lang.Class({
|
||||
this._iconBox.set_child(icon);
|
||||
this._iconBox.show();
|
||||
|
||||
if (targetApp.get_state() == Shell.AppState.STARTING)
|
||||
if (targetApp.get_state() == Shell.AppState.STARTING ||
|
||||
targetApp.get_state() == Shell.AppState.BUSY)
|
||||
this.startAnimation();
|
||||
else
|
||||
this._maybeSetMenu();
|
||||
@ -593,11 +622,11 @@ const AppMenuButton = new Lang.Class({
|
||||
let menu;
|
||||
|
||||
if (this._targetApp.action_group && this._targetApp.menu) {
|
||||
if (this.menu instanceof PopupMenu.RemoteMenu &&
|
||||
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());
|
||||
@ -617,6 +646,33 @@ const AppMenuButton = new Lang.Class({
|
||||
|
||||
this.setMenu(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();
|
||||
}
|
||||
});
|
||||
|
||||
@ -630,23 +686,15 @@ const ActivitiesButton = new Lang.Class({
|
||||
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.actor.add_actor(this._label);
|
||||
|
||||
this.actor.label_actor = this._label;
|
||||
|
||||
this.hotCorner = new Layout.HotCorner();
|
||||
container.add_actor(this.hotCorner.actor);
|
||||
|
||||
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));
|
||||
@ -661,44 +709,6 @@ const ActivitiesButton = new Lang.Class({
|
||||
}));
|
||||
|
||||
this._xdndTimeOut = 0;
|
||||
|
||||
// Since the hot corner uses stage coordinates, Clutter won't
|
||||
// queue relayouts for us when the panel moves. Queue a relayout
|
||||
// when that happens.
|
||||
Main.layoutManager.connect('panel-box-changed', Lang.bind(this, function() {
|
||||
container.queue_relayout();
|
||||
}));
|
||||
},
|
||||
|
||||
_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) {
|
||||
@ -708,14 +718,14 @@ 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;
|
||||
},
|
||||
|
||||
_onCapturedEvent: function(actor, event) {
|
||||
if (event.type() == Clutter.EventType.BUTTON_PRESS) {
|
||||
if (!this.hotCorner.shouldToggleOverviewOnClick())
|
||||
if (!Main.overview.shouldToggleByCornerOrButton())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -732,16 +742,12 @@ const ActivitiesButton = new Lang.Class({
|
||||
}
|
||||
},
|
||||
|
||||
_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;
|
||||
@ -920,10 +926,10 @@ const PANEL_ITEM_IMPLEMENTATIONS = {
|
||||
'appMenu': AppMenuButton,
|
||||
'dateMenu': imports.ui.dateMenu.DateMenuButton,
|
||||
'a11y': imports.ui.status.accessibility.ATIndicator,
|
||||
'a11yGreeter': imports.ui.status.accessibility.ATGreeterIndicator,
|
||||
'volume': imports.ui.status.volume.Indicator,
|
||||
'battery': imports.ui.status.power.Indicator,
|
||||
'lockScreen': imports.ui.status.lockScreenMenu.Indicator,
|
||||
'logo': imports.gdm.loginDialog.LogoMenuButton,
|
||||
'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
|
||||
'powerMenu': imports.gdm.powerMenu.PowerMenuButton,
|
||||
'userMenu': imports.ui.userMenu.UserMenuButton
|
||||
@ -952,7 +958,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);
|
||||
@ -1065,6 +1071,9 @@ const Panel = new Lang.Class({
|
||||
},
|
||||
|
||||
_onButtonPress: function(actor, event) {
|
||||
if (Main.modalCount > 0)
|
||||
return false;
|
||||
|
||||
if (event.get_source() != actor)
|
||||
return false;
|
||||
|
||||
@ -1103,17 +1112,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) {
|
||||
|
@ -184,6 +184,9 @@ const Button = new Lang.Class({
|
||||
},
|
||||
|
||||
_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 +208,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() {
|
||||
|
@ -42,8 +42,7 @@ const PointerWatcher = new Lang.Class({
|
||||
|
||||
_init: function() {
|
||||
this._idleMonitor = new GnomeDesktop.IdleMonitor();
|
||||
this._idleMonitor.connect('became-active', Lang.bind(this, this._onIdleMonitorBecameActive));
|
||||
this._idleMonitor.add_watch(IDLE_TIME, Lang.bind(this, this._onIdleMonitorBecameIdle));
|
||||
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;
|
||||
@ -87,6 +86,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();
|
||||
},
|
||||
|
||||
|
@ -19,6 +19,12 @@ const Tweener = imports.ui.tweener;
|
||||
|
||||
const SLIDER_SCROLL_STEP = 0.05; /* Slider scrolling step in % */
|
||||
|
||||
const Ornament = {
|
||||
NONE: 0,
|
||||
DOT: 1,
|
||||
CHECK: 2,
|
||||
};
|
||||
|
||||
function _ensureStyle(actor) {
|
||||
if (actor.get_children) {
|
||||
let children = actor.get_children();
|
||||
@ -53,7 +59,9 @@ const PopupBaseMenuItem = new Lang.Class({
|
||||
this.actor._delegate = this;
|
||||
|
||||
this._children = [];
|
||||
this._dot = null;
|
||||
this._ornament = Ornament.NONE;
|
||||
this._ornamentLabel = new St.Label({ style_class: 'popup-menu-ornament' });
|
||||
this.actor.add_actor(this._ornamentLabel);
|
||||
this._columnWidths = null;
|
||||
this._spacing = 0;
|
||||
this.active = false;
|
||||
@ -176,40 +184,24 @@ const PopupBaseMenuItem = new Lang.Class({
|
||||
this._removeChild(child);
|
||||
},
|
||||
|
||||
setShowDot: function(show) {
|
||||
if (show) {
|
||||
if (this._dot)
|
||||
return;
|
||||
setOrnament: function(ornament) {
|
||||
if (ornament == this._ornament)
|
||||
return;
|
||||
|
||||
this._dot = new St.DrawingArea({ style_class: 'popup-menu-item-dot' });
|
||||
this._dot.connect('repaint', Lang.bind(this, this._onRepaintDot));
|
||||
this.actor.add_actor(this._dot);
|
||||
this.actor.add_accessible_state (Atk.StateType.CHECKED);
|
||||
} else {
|
||||
if (!this._dot)
|
||||
return;
|
||||
this._ornament = ornament;
|
||||
|
||||
this._dot.destroy();
|
||||
this._dot = null;
|
||||
this.actor.remove_accessible_state (Atk.StateType.CHECKED);
|
||||
if (ornament == Ornament.DOT) {
|
||||
this._ornamentLabel.text = '\u2022';
|
||||
this.actor.add_accessible_state(Atk.StateType.CHECKED);
|
||||
} else if (ornament == Ornament.CHECK) {
|
||||
this._ornamentLabel.text = '\u2713';
|
||||
this.actor.add_accessible_state(Atk.StateType.CHECKED);
|
||||
} else if (ornament == Ornament.NONE) {
|
||||
this._ornamentLabel.text = '';
|
||||
this.actor.remove_accessible_state(Atk.StateType.CHECKED);
|
||||
}
|
||||
},
|
||||
|
||||
_onRepaintDot: function(area) {
|
||||
let cr = area.get_context();
|
||||
let [width, height] = area.get_surface_size();
|
||||
let color = area.get_theme_node().get_foreground_color();
|
||||
|
||||
cr.setSourceRGBA (
|
||||
color.red / 255,
|
||||
color.green / 255,
|
||||
color.blue / 255,
|
||||
color.alpha / 255);
|
||||
cr.arc(width / 2, height / 2, width / 3, 0, 2 * Math.PI);
|
||||
cr.fill();
|
||||
cr.$dispose();
|
||||
},
|
||||
|
||||
// This returns column widths in logical order (i.e. from the dot
|
||||
// to the image), not in visual order (left to right)
|
||||
getColumnWidths: function() {
|
||||
@ -280,26 +272,25 @@ const PopupBaseMenuItem = new Lang.Class({
|
||||
let height = box.y2 - box.y1;
|
||||
let direction = this.actor.get_text_direction();
|
||||
|
||||
if (this._dot) {
|
||||
// The dot is placed outside box
|
||||
// one quarter of padding from the border of the container
|
||||
// (so 3/4 from the inner border)
|
||||
// (padding is box.x1)
|
||||
let dotBox = new Clutter.ActorBox();
|
||||
let dotWidth = Math.round(box.x1 / 2);
|
||||
// The ornament is placed outside box
|
||||
// one quarter of padding from the border of the container
|
||||
// (so 3/4 from the inner border)
|
||||
// (padding is box.x1)
|
||||
let ornamentBox = new Clutter.ActorBox();
|
||||
let ornamentWidth = box.x1;
|
||||
|
||||
if (direction == Clutter.TextDirection.LTR) {
|
||||
dotBox.x1 = Math.round(box.x1 / 4);
|
||||
dotBox.x2 = dotBox.x1 + dotWidth;
|
||||
} else {
|
||||
dotBox.x2 = box.x2 + 3 * Math.round(box.x1 / 4);
|
||||
dotBox.x1 = dotBox.x2 - dotWidth;
|
||||
}
|
||||
dotBox.y1 = Math.round(box.y1 + (height - dotWidth) / 2);
|
||||
dotBox.y2 = dotBox.y1 + dotWidth;
|
||||
this._dot.allocate(dotBox, flags);
|
||||
ornamentBox.x1 = 0;
|
||||
ornamentBox.x2 = ornamentWidth;
|
||||
ornamentBox.y1 = box.y1;
|
||||
ornamentBox.y2 = box.y2;
|
||||
|
||||
if (direction == Clutter.TextDirection.RTL) {
|
||||
ornamentBox.x1 += box.x2;
|
||||
ornamentBox.x2 += box.x2;
|
||||
}
|
||||
|
||||
this._ornamentLabel.allocate(ornamentBox, flags);
|
||||
|
||||
let x;
|
||||
if (direction == Clutter.TextDirection.LTR)
|
||||
x = box.x1;
|
||||
@ -402,12 +393,19 @@ const PopupSeparatorMenuItem = new Lang.Class({
|
||||
Name: 'PopupSeparatorMenuItem',
|
||||
Extends: PopupBaseMenuItem,
|
||||
|
||||
_init: function () {
|
||||
_init: function (text) {
|
||||
this.parent({ reactive: false,
|
||||
can_focus: false});
|
||||
|
||||
this._box = new St.BoxLayout();
|
||||
this.addActor(this._box, { span: -1, expand: true });
|
||||
|
||||
this.label = new St.Label({ text: text || '' });
|
||||
this._box.add(this.label);
|
||||
this.actor.label_actor = this.label;
|
||||
|
||||
this._separator = new Separator.HorizontalSeparator({ style_class: 'popup-separator-menu-item' });
|
||||
this.addActor(this._separator.actor, { span: -1, expand: true });
|
||||
this._box.add(this._separator.actor, { expand: true });
|
||||
}
|
||||
});
|
||||
|
||||
@ -553,6 +551,10 @@ const PopupSliderMenuItem = new Lang.Class({
|
||||
|
||||
let handleRadius = themeNode.get_length('-slider-handle-radius');
|
||||
|
||||
let handleBorderWidth = themeNode.get_length('-slider-handle-border-width');
|
||||
let [hasHandleColor, handleBorderColor] =
|
||||
themeNode.lookup_color('-slider-handle-border-color', false);
|
||||
|
||||
let sliderWidth = width - 2 * handleRadius;
|
||||
let sliderHeight = themeNode.get_length('-slider-height');
|
||||
|
||||
@ -604,7 +606,16 @@ const PopupSliderMenuItem = new Lang.Class({
|
||||
color.blue / 255,
|
||||
color.alpha / 255);
|
||||
cr.arc(handleX, handleY, handleRadius, 0, 2 * Math.PI);
|
||||
cr.fill();
|
||||
cr.fillPreserve();
|
||||
if (hasHandleColor && handleBorderWidth) {
|
||||
cr.setSourceRGBA(
|
||||
handleBorderColor.red / 255,
|
||||
handleBorderColor.green / 255,
|
||||
handleBorderColor.blue / 255,
|
||||
handleBorderColor.alpha / 255);
|
||||
cr.setLineWidth(handleBorderWidth);
|
||||
cr.stroke();
|
||||
}
|
||||
cr.$dispose();
|
||||
},
|
||||
|
||||
@ -867,7 +878,6 @@ const PopupMenuBase = new Lang.Class({
|
||||
this.blockSourceEvents = false;
|
||||
|
||||
this._activeMenuItem = null;
|
||||
this._childMenus = [];
|
||||
this._settingsActions = { };
|
||||
|
||||
this._sessionUpdatedId = Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
|
||||
@ -914,29 +924,15 @@ const PopupMenuBase = new Lang.Class({
|
||||
},
|
||||
|
||||
isEmpty: function() {
|
||||
return this.box.get_n_children() == 0;
|
||||
let hasVisibleChildren = this.box.get_children().some(function(child) {
|
||||
return child.visible;
|
||||
});
|
||||
|
||||
return !hasVisibleChildren;
|
||||
},
|
||||
|
||||
isChildMenu: function(menu) {
|
||||
return this._childMenus.indexOf(menu) != -1;
|
||||
},
|
||||
|
||||
addChildMenu: function(menu) {
|
||||
if (this.isChildMenu(menu))
|
||||
return;
|
||||
|
||||
this._childMenus.push(menu);
|
||||
this.emit('child-menu-added', menu);
|
||||
},
|
||||
|
||||
removeChildMenu: function(menu) {
|
||||
let index = this._childMenus.indexOf(menu);
|
||||
|
||||
if (index == -1)
|
||||
return;
|
||||
|
||||
this._childMenus.splice(index, 1);
|
||||
this.emit('child-menu-removed', menu);
|
||||
isChildMenu: function() {
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1007,6 +1003,9 @@ const PopupMenuBase = new Lang.Class({
|
||||
},
|
||||
|
||||
_updateSeparatorVisibility: function(menuItem) {
|
||||
if (menuItem.label.text)
|
||||
return;
|
||||
|
||||
let children = this.box.get_children();
|
||||
|
||||
let index = children.indexOf(menuItem.actor);
|
||||
@ -1172,6 +1171,7 @@ const PopupMenuBase = new Lang.Class({
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this.close();
|
||||
this.removeAll();
|
||||
this.actor.destroy();
|
||||
|
||||
@ -1211,6 +1211,8 @@ const PopupMenu = new Lang.Class({
|
||||
|
||||
global.focus_manager.add_group(this.actor);
|
||||
this.actor.reactive = true;
|
||||
|
||||
this._childMenus = [];
|
||||
},
|
||||
|
||||
_boxGetPreferredWidth: function (actor, forHeight, alloc) {
|
||||
@ -1237,6 +1239,28 @@ const PopupMenu = new Lang.Class({
|
||||
this._boxPointer.setSourceAlignment(alignment);
|
||||
},
|
||||
|
||||
isChildMenu: function(menu) {
|
||||
return this._childMenus.indexOf(menu) != -1;
|
||||
},
|
||||
|
||||
addChildMenu: function(menu) {
|
||||
if (this.isChildMenu(menu))
|
||||
return;
|
||||
|
||||
this._childMenus.push(menu);
|
||||
this.emit('child-menu-added', menu);
|
||||
},
|
||||
|
||||
removeChildMenu: function(menu) {
|
||||
let index = this._childMenus.indexOf(menu);
|
||||
|
||||
if (index == -1)
|
||||
return;
|
||||
|
||||
this._childMenus.splice(index, 1);
|
||||
this.emit('child-menu-removed', menu);
|
||||
},
|
||||
|
||||
open: function(animate) {
|
||||
if (this.isOpen)
|
||||
return;
|
||||
@ -1258,6 +1282,10 @@ const PopupMenu = new Lang.Class({
|
||||
if (this._activeMenuItem)
|
||||
this._activeMenuItem.setActive(false);
|
||||
|
||||
this._childMenus.forEach(function(childMenu) {
|
||||
childMenu.close();
|
||||
});
|
||||
|
||||
if (this._boxPointer.actor.visible)
|
||||
this._boxPointer.hide(animate);
|
||||
|
||||
@ -1461,23 +1489,12 @@ const PopupMenuSection = new Lang.Class({
|
||||
this.actor = this.box;
|
||||
this.actor._delegate = this;
|
||||
this.isOpen = true;
|
||||
|
||||
// an array of externally managed separators
|
||||
this.separators = [];
|
||||
},
|
||||
|
||||
// deliberately ignore any attempt to open() or close(), but emit the
|
||||
// corresponding signal so children can still pick it up
|
||||
open: function() { this.emit('open-state-changed', true); },
|
||||
close: function() { this.emit('open-state-changed', false); },
|
||||
|
||||
destroy: function() {
|
||||
for (let i = 0; i < this.separators.length; i++)
|
||||
this.separators[i].destroy();
|
||||
this.separators = [];
|
||||
|
||||
this.parent();
|
||||
}
|
||||
});
|
||||
|
||||
const PopupSubMenuMenuItem = new Lang.Class({
|
||||
@ -1512,15 +1529,30 @@ const PopupSubMenuMenuItem = new Lang.Class({
|
||||
this.parent();
|
||||
},
|
||||
|
||||
setSubmenuShown: function(open) {
|
||||
if (open)
|
||||
this.menu.open(BoxPointer.PopupAnimation.FULL);
|
||||
else
|
||||
this.menu.close(BoxPointer.PopupAnimation.FULL);
|
||||
},
|
||||
|
||||
_setOpenState: function(open) {
|
||||
this.setSubmenuShown(open);
|
||||
},
|
||||
|
||||
_getOpenState: function() {
|
||||
return this.menu.isOpen;
|
||||
},
|
||||
|
||||
_onKeyPressEvent: function(actor, event) {
|
||||
let symbol = event.get_key_symbol();
|
||||
|
||||
if (symbol == Clutter.KEY_Right) {
|
||||
this.menu.open(BoxPointer.PopupAnimation.FULL);
|
||||
this._setOpenState(true);
|
||||
this.menu.actor.navigate_focus(null, Gtk.DirectionType.DOWN, false);
|
||||
return true;
|
||||
} else if (symbol == Clutter.KEY_Left && this.menu.isOpen) {
|
||||
this.menu.close();
|
||||
} else if (symbol == Clutter.KEY_Left && this._getOpenState()) {
|
||||
this._setOpenState(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1528,11 +1560,11 @@ const PopupSubMenuMenuItem = new Lang.Class({
|
||||
},
|
||||
|
||||
activate: function(event) {
|
||||
this.menu.open(BoxPointer.PopupAnimation.FULL);
|
||||
this._setOpenState(true);
|
||||
},
|
||||
|
||||
_onButtonReleaseEvent: function(actor) {
|
||||
this.menu.toggle();
|
||||
this._setOpenState(!this._getOpenState());
|
||||
}
|
||||
});
|
||||
|
||||
@ -1678,9 +1710,7 @@ const PopupComboBoxMenuItem = new Lang.Class({
|
||||
_getTopMenu: function() {
|
||||
let actor = this.actor.get_parent();
|
||||
while (actor) {
|
||||
if (actor._delegate &&
|
||||
(actor._delegate instanceof PopupMenu ||
|
||||
actor._delegate instanceof PopupComboMenu))
|
||||
if (actor._delegate && actor._delegate instanceof PopupMenu)
|
||||
return actor._delegate;
|
||||
|
||||
actor = actor.get_parent();
|
||||
@ -1780,269 +1810,15 @@ const PopupComboBoxMenuItem = new Lang.Class({
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* RemoteMenu:
|
||||
*
|
||||
* A PopupMenu that tracks a GMenuModel and shows its actions
|
||||
* (exposed by GApplication/GActionGroup)
|
||||
*/
|
||||
const RemoteMenu = new Lang.Class({
|
||||
Name: 'RemoteMenu',
|
||||
Extends: PopupMenu,
|
||||
|
||||
_init: function(sourceActor, model, actionGroup) {
|
||||
this.parent(sourceActor, 0.0, St.Side.TOP);
|
||||
|
||||
this.model = model;
|
||||
this.actionGroup = actionGroup;
|
||||
|
||||
this._actions = { };
|
||||
this._modelChanged(this.model, 0, 0, this.model.get_n_items(), this);
|
||||
|
||||
this._actionStateChangeId = this.actionGroup.connect('action-state-changed', Lang.bind(this, this._actionStateChanged));
|
||||
this._actionEnableChangeId = this.actionGroup.connect('action-enabled-changed', Lang.bind(this, this._actionEnabledChanged));
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this._actionStateChangeId) {
|
||||
this.actionGroup.disconnect(this._actionStateChangeId);
|
||||
this._actionStateChangeId = 0;
|
||||
}
|
||||
|
||||
if (this._actionEnableChangeId) {
|
||||
this.actionGroup.disconnect(this._actionEnableChangeId);
|
||||
this._actionEnableChangeId = 0;
|
||||
}
|
||||
|
||||
this.parent();
|
||||
},
|
||||
|
||||
_createMenuItem: function(model, index) {
|
||||
let labelValue = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_LABEL, null);
|
||||
let label = labelValue ? labelValue.deep_unpack() : '';
|
||||
// remove all underscores that are not followed by another underscore
|
||||
label = label.replace(/_([^_])/, '$1');
|
||||
|
||||
let section_link = model.get_item_link(index, Gio.MENU_LINK_SECTION);
|
||||
if (section_link) {
|
||||
let item = new PopupMenuSection();
|
||||
if (label) {
|
||||
let title = new PopupMenuItem(label, { reactive: false,
|
||||
style_class: 'popup-subtitle-menu-item' });
|
||||
item._titleMenuItem = title;
|
||||
title._ignored = true;
|
||||
item.addMenuItem(title);
|
||||
}
|
||||
this._modelChanged(section_link, 0, 0, section_link.get_n_items(), item);
|
||||
return [item, true, ''];
|
||||
}
|
||||
|
||||
let submenu_link = model.get_item_link(index, Gio.MENU_LINK_SUBMENU);
|
||||
|
||||
if (submenu_link) {
|
||||
let item = new PopupSubMenuMenuItem(label);
|
||||
this._modelChanged(submenu_link, 0, 0, submenu_link.get_n_items(), item.menu);
|
||||
return [item, false, ''];
|
||||
}
|
||||
|
||||
let action_id = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_ACTION, null).deep_unpack();
|
||||
if (!this.actionGroup.has_action(action_id)) {
|
||||
// the action may not be there yet, wait for action-added
|
||||
return [null, false, 'action-added'];
|
||||
}
|
||||
|
||||
if (!this._actions[action_id])
|
||||
this._actions[action_id] = { enabled: this.actionGroup.get_action_enabled(action_id),
|
||||
state: this.actionGroup.get_action_state(action_id),
|
||||
items: [ ],
|
||||
};
|
||||
let action = this._actions[action_id];
|
||||
let item, target, destroyId, specificSignalId;
|
||||
|
||||
if (action.state) {
|
||||
// Docs have get_state_hint(), except that the DBus protocol
|
||||
// has no provision for it (so ShellApp does not implement it,
|
||||
// and neither GApplication), and g_action_get_state_hint()
|
||||
// always returns null
|
||||
// Funny :)
|
||||
|
||||
switch (String.fromCharCode(action.state.classify())) {
|
||||
case 'b':
|
||||
item = new PopupSwitchMenuItem(label, action.state.get_boolean());
|
||||
action.items.push(item);
|
||||
specificSignalId = item.connect('toggled', Lang.bind(this, function(item) {
|
||||
this.actionGroup.activate_action(action_id, null);
|
||||
}));
|
||||
break;
|
||||
case 's':
|
||||
item = new PopupMenuItem(label);
|
||||
item._remoteTarget = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null).deep_unpack();
|
||||
action.items.push(item);
|
||||
item.setShowDot(action.state.deep_unpack() == item._remoteTarget);
|
||||
specificSignalId = item.connect('activate', Lang.bind(this, function(item) {
|
||||
this.actionGroup.activate_action(action_id, GLib.Variant.new_string(item._remoteTarget));
|
||||
}));
|
||||
break;
|
||||
default:
|
||||
log('Action "%s" has state of type %s, which is not supported'.format(action_id, action.state.get_type_string()));
|
||||
return [null, false, 'action-state-changed'];
|
||||
}
|
||||
} else {
|
||||
target = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null);
|
||||
item = new PopupMenuItem(label);
|
||||
action.items.push(item);
|
||||
specificSignalId = item.connect('activate', Lang.bind(this, function() {
|
||||
this.actionGroup.activate_action(action_id, target);
|
||||
}));
|
||||
}
|
||||
|
||||
item.actor.reactive = item.actor.can_focus = action.enabled;
|
||||
|
||||
destroyId = item.connect('destroy', Lang.bind(this, function() {
|
||||
item.disconnect(destroyId);
|
||||
item.disconnect(specificSignalId);
|
||||
|
||||
let pos = action.items.indexOf(item);
|
||||
if (pos != -1)
|
||||
action.items.splice(pos, 1);
|
||||
}));
|
||||
|
||||
return [item, false, ''];
|
||||
},
|
||||
|
||||
_modelChanged: function(model, position, removed, added, target) {
|
||||
let j, k;
|
||||
let j0, k0;
|
||||
|
||||
let currentItems = target._getMenuItems();
|
||||
|
||||
k0 = 0;
|
||||
// skip ignored items at the beginning
|
||||
while (k0 < currentItems.length && currentItems[k0]._ignored)
|
||||
k0++;
|
||||
// find the right menu item matching the model item
|
||||
for (j0 = 0; k0 < currentItems.length && j0 < position; j0++, k0++) {
|
||||
if (currentItems[k0]._ignored)
|
||||
k0++;
|
||||
}
|
||||
|
||||
if (removed == -1) {
|
||||
// special flag to indicate we should destroy everything
|
||||
for (k = k0; k < currentItems.length; k++)
|
||||
currentItems[k].destroy();
|
||||
} else {
|
||||
for (j = j0, k = k0; k < currentItems.length && j < j0 + removed; j++, k++) {
|
||||
currentItems[k].destroy();
|
||||
|
||||
if (currentItems[k]._ignored)
|
||||
j--;
|
||||
}
|
||||
}
|
||||
|
||||
for (j = j0, k = k0; j < j0 + added; j++, k++) {
|
||||
let [item, addSeparator, changeSignal] = this._createMenuItem(model, j);
|
||||
|
||||
if (item) {
|
||||
// separators must be added in the parent to make autohiding work
|
||||
if (addSeparator) {
|
||||
let separator = new PopupSeparatorMenuItem();
|
||||
item.separators.push(separator);
|
||||
separator._ignored = true;
|
||||
target.addMenuItem(separator, k+1);
|
||||
k++;
|
||||
}
|
||||
|
||||
target.addMenuItem(item, k);
|
||||
|
||||
if (addSeparator) {
|
||||
let separator = new PopupSeparatorMenuItem();
|
||||
item.separators.push(separator);
|
||||
separator._ignored = true;
|
||||
target.addMenuItem(separator, k+1);
|
||||
k++;
|
||||
}
|
||||
} else if (changeSignal) {
|
||||
let signalId = this.actionGroup.connect(changeSignal, Lang.bind(this, function(actionGroup, actionName) {
|
||||
actionGroup.disconnect(signalId);
|
||||
if (this._actions[actionName]) return;
|
||||
|
||||
// force a full update
|
||||
this._modelChanged(model, 0, -1, model.get_n_items(), target);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if (!model._changedId) {
|
||||
model._changedId = model.connect('items-changed', Lang.bind(this, this._modelChanged, target));
|
||||
model._destroyId = target.connect('destroy', function() {
|
||||
if (model._changedId)
|
||||
model.disconnect(model._changedId);
|
||||
if (model._destroyId)
|
||||
target.disconnect(model._destroyId);
|
||||
model._changedId = 0;
|
||||
model._destroyId = 0;
|
||||
});
|
||||
}
|
||||
|
||||
if (target instanceof PopupMenuSection) {
|
||||
if (target._titleMenuItem)
|
||||
target.actor.visible = target.numMenuItems != 1;
|
||||
else
|
||||
target.actor.visible = target.numMenuItems != 0;
|
||||
} else {
|
||||
let sourceItem = target.sourceActor._delegate;
|
||||
if (sourceItem instanceof PopupSubMenuMenuItem)
|
||||
sourceItem.actor.visible = target.numMenuItems != 0;
|
||||
}
|
||||
},
|
||||
|
||||
_actionStateChanged: function(actionGroup, action_id) {
|
||||
let action = this._actions[action_id];
|
||||
if (!action)
|
||||
return;
|
||||
|
||||
action.state = actionGroup.get_action_state(action_id);
|
||||
if (action.items.length) {
|
||||
switch (String.fromCharCode(action.state.classify())) {
|
||||
case 'b':
|
||||
for (let i = 0; i < action.items.length; i++)
|
||||
action.items[i].setToggleState(action.state.get_boolean());
|
||||
break;
|
||||
case 'd':
|
||||
for (let i = 0; i < action.items.length; i++)
|
||||
action.items[i].setValue(action.state.get_double());
|
||||
break;
|
||||
case 's':
|
||||
for (let i = 0; i < action.items.length; i++)
|
||||
action.items[i].setShowDot(action.items[i]._remoteTarget == action.state.deep_unpack());
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_actionEnabledChanged: function(actionGroup, action_id) {
|
||||
let action = this._actions[action_id];
|
||||
if (!action)
|
||||
return;
|
||||
|
||||
action.enabled = actionGroup.get_action_enabled(action_id);
|
||||
if (action.items.length) {
|
||||
for (let i = 0; i < action.items.length; i++) {
|
||||
let item = action.items[i];
|
||||
item.actor.reactive = item.actor.can_focus = action.enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Basic implementation of a menu manager.
|
||||
* Call addMenu to add menus
|
||||
*/
|
||||
const PopupMenuManager = new Lang.Class({
|
||||
Name: 'PopupMenuManager',
|
||||
|
||||
_init: function(owner) {
|
||||
_init: function(owner, grabParams) {
|
||||
this._owner = owner;
|
||||
this._grabHelper = new GrabHelper.GrabHelper(owner.actor);
|
||||
this._grabHelper = new GrabHelper.GrabHelper(owner.actor, grabParams);
|
||||
this._menus = [];
|
||||
},
|
||||
|
||||
@ -2075,7 +1851,7 @@ const PopupMenuManager = new Lang.Class({
|
||||
},
|
||||
|
||||
removeMenu: function(menu) {
|
||||
if (menu == this._activeMenu)
|
||||
if (menu == this.activeMenu)
|
||||
this._closeMenu(menu);
|
||||
|
||||
let position = this._findMenu(menu);
|
||||
@ -2099,9 +1875,9 @@ const PopupMenuManager = new Lang.Class({
|
||||
},
|
||||
|
||||
get activeMenu() {
|
||||
let actor = this._grabHelper.currentGrab.actor;
|
||||
if (actor)
|
||||
return actor._delegate;
|
||||
let firstGrab = this._grabHelper.grabStack[0];
|
||||
if (firstGrab)
|
||||
return firstGrab.actor._delegate;
|
||||
else
|
||||
return null;
|
||||
},
|
||||
@ -2112,6 +1888,8 @@ const PopupMenuManager = new Lang.Class({
|
||||
|
||||
_onMenuOpenState: function(menu, open) {
|
||||
if (open) {
|
||||
if (this.activeMenu && !this.activeMenu.isChildMenu(menu))
|
||||
this.activeMenu.close(BoxPointer.PopupAnimation.FADE);
|
||||
this._grabHelper.grab({ actor: menu.actor, modal: true, focus: menu.sourceActor,
|
||||
onUngrab: Lang.bind(this, this._closeMenu, menu) });
|
||||
} else {
|
||||
@ -2128,13 +1906,8 @@ const PopupMenuManager = new Lang.Class({
|
||||
},
|
||||
|
||||
_changeMenu: function(newMenu) {
|
||||
let oldMenu = this.activeMenu;
|
||||
if (oldMenu) {
|
||||
oldMenu.close(BoxPointer.PopupAnimation.FADE);
|
||||
newMenu.open(BoxPointer.PopupAnimation.FADE);
|
||||
} else {
|
||||
newMenu.open(BoxPointer.PopupAnimation.FULL);
|
||||
}
|
||||
newMenu.open(this.activeMenu ? BoxPointer.PopupAnimation.FADE
|
||||
: BoxPointer.PopupAnimation.FULL);
|
||||
},
|
||||
|
||||
_onMenuSourceEnter: function(menu) {
|
||||
|
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.addActor(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();
|
||||
},
|
||||
});
|
@ -123,7 +123,9 @@ function loadRemoteSearchProvider(file, info, data) {
|
||||
function remoteProvidersLoaded(loadState) {
|
||||
let searchSettings = new Gio.Settings({ schema: Search.SEARCH_PROVIDERS_SCHEMA });
|
||||
let sortOrder = searchSettings.get_strv('sort-order');
|
||||
let numSorted = sortOrder.length;
|
||||
|
||||
// Special case gnome-control-center to be always active and always first
|
||||
sortOrder.unshift('gnome-control-center.desktop');
|
||||
|
||||
loadState.loadedProviders.sort(
|
||||
function(providerA, providerB) {
|
||||
@ -144,15 +146,6 @@ function remoteProvidersLoaded(loadState) {
|
||||
return GLib.utf8_collate(nameA, nameB);
|
||||
}
|
||||
|
||||
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 providerA isn't found, it's sorted after providerB
|
||||
if (idxA == -1)
|
||||
return 1;
|
||||
@ -194,7 +187,9 @@ const RemoteSearchProvider = new Lang.Class({
|
||||
|
||||
createIcon: function(size, meta) {
|
||||
let gicon;
|
||||
if (meta['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,
|
||||
@ -247,10 +242,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]) });
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -13,6 +13,7 @@ const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
const TweenerEquations = imports.tweener.equations;
|
||||
|
||||
const Background = imports.ui.background;
|
||||
const GnomeSession = imports.misc.gnomeSession;
|
||||
const Hash = imports.misc.hash;
|
||||
const Layout = imports.ui.layout;
|
||||
@ -29,7 +30,7 @@ const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver';
|
||||
const LOCK_ENABLED_KEY = 'lock-enabled';
|
||||
const LOCK_DELAY_KEY = 'lock-delay';
|
||||
|
||||
const CURTAIN_SLIDE_TIME = 0.3;
|
||||
const LOCKED_STATE_STR = 'screenShield.locked';
|
||||
// fraction of screen height the arrow must reach before completing
|
||||
// the slide up automatically
|
||||
const ARROW_DRAG_THRESHOLD = 0.1;
|
||||
@ -38,51 +39,22 @@ const ARROW_DRAG_THRESHOLD = 0.1;
|
||||
const N_ARROWS = 3;
|
||||
const ARROW_ANIMATION_TIME = 0.6;
|
||||
const ARROW_ANIMATION_PEAK_OPACITY = 0.4;
|
||||
|
||||
// The distance in px that the lock screen will move to when pressing
|
||||
// a key that has no effect in the lock screen (bumping it)
|
||||
const BUMP_SIZE = 25;
|
||||
const BUMP_TIME = 0.3;
|
||||
const ARROW_IDLE_TIME = 30000; // ms
|
||||
|
||||
const SUMMARY_ICON_SIZE = 48;
|
||||
|
||||
// Lightbox fading times
|
||||
// STANDARD_FADE_TIME is used when the session goes idle, while
|
||||
// SHORT_FADE_TIME is used when requesting lock explicitly from the user menu
|
||||
// ScreenShield animation time
|
||||
// - STANDARD_FADE_TIME is used when the session goes idle
|
||||
// - MANUAL_FADE_TIME is used for lowering the shield when asked by the user,
|
||||
// or when cancelling the dialog
|
||||
// - BACKGROUND_FADE_TIME is used when the background changes to crossfade to new background
|
||||
// - CURTAIN_SLIDE_TIME is used when raising the shield before unlocking
|
||||
// - INITIAL_FADE_IN_TIME is used for the initial fade in at startup
|
||||
const STANDARD_FADE_TIME = 10;
|
||||
const SHORT_FADE_TIME = 0.3;
|
||||
|
||||
function sample(offx, offy) {
|
||||
return 'texel += texture2D (sampler, tex_coord.st + pixel_step * ' +
|
||||
'vec2 (' + offx + ',' + offy + '));\n'
|
||||
}
|
||||
const GLSL_BLUR_EFFECT_DECLARATIONS = ' \
|
||||
uniform vec2 pixel_step;\n \
|
||||
uniform float desaturation;\n \
|
||||
vec4 apply_blur(in sampler2D sampler, in vec2 tex_coord) {\n \
|
||||
vec4 texel;\n \
|
||||
texel = texture2D (sampler, tex_coord.st);\n'
|
||||
+ sample(-1.0, -1.0)
|
||||
+ sample( 0.0, -1.0)
|
||||
+ sample(+1.0, -1.0)
|
||||
+ sample(-1.0, 0.0)
|
||||
+ sample(+1.0, 0.0)
|
||||
+ sample(-1.0, +1.0)
|
||||
+ sample( 0.0, +1.0)
|
||||
+ sample(+1.0, +1.0) + ' \
|
||||
texel /= 9.0;\n \
|
||||
return texel;\n \
|
||||
}\n \
|
||||
vec3 desaturate (const vec3 color)\n \
|
||||
{\n \
|
||||
const vec3 gray_conv = vec3 (0.299, 0.587, 0.114);\n \
|
||||
vec3 gray = vec3 (dot (gray_conv, color));\n \
|
||||
return vec3 (mix (color.rgb, gray, desaturation));\n \
|
||||
}';
|
||||
const GLSL_BLUR_EFFECT_CODE = ' \
|
||||
cogl_texel = apply_blur(cogl_sampler, cogl_tex_coord.st);\n \
|
||||
cogl_texel.rgb = desaturate(cogl_texel.rgb);\n';
|
||||
|
||||
const MANUAL_FADE_TIME = 0.3;
|
||||
const BACKGROUND_FADE_TIME = 1.0;
|
||||
const CURTAIN_SLIDE_TIME = 0.3;
|
||||
const INITIAL_FADE_IN_TIME = 0.25;
|
||||
|
||||
const Clock = new Lang.Class({
|
||||
Name: 'ScreenShieldClock',
|
||||
@ -202,7 +174,10 @@ const NotificationsBox = new Lang.Class({
|
||||
|
||||
_makeNotificationDetailedSource: function(source, box) {
|
||||
let sourceActor = new MessageTray.SourceActor(source, SUMMARY_ICON_SIZE);
|
||||
box.add(sourceActor.actor, { y_fill: true });
|
||||
let sourceBin = new St.Bin({ y_align: St.Align.START,
|
||||
x_align: St.Align.START,
|
||||
child: sourceActor.actor });
|
||||
box.add(sourceBin);
|
||||
|
||||
let textBox = new St.BoxLayout({ vertical: true });
|
||||
box.add(textBox, { y_fill: false, y_align: St.Align.START });
|
||||
@ -240,6 +215,7 @@ const NotificationsBox = new Lang.Class({
|
||||
|
||||
if (musicNotification != null &&
|
||||
this._musicBin.child == null) {
|
||||
musicNotification.acknowledged = true;
|
||||
if (musicNotification.actor.get_parent() != null)
|
||||
musicNotification.actor.get_parent().remove_actor(musicNotification.actor);
|
||||
this._musicBin.child = musicNotification.actor;
|
||||
@ -272,6 +248,7 @@ const NotificationsBox = new Lang.Class({
|
||||
sourceCountChangedId: 0,
|
||||
sourceTitleChangedId: 0,
|
||||
sourceUpdatedId: 0,
|
||||
sourceNotifyId: 0,
|
||||
musicNotification: null,
|
||||
sourceBox: null,
|
||||
titleLabel: null,
|
||||
@ -282,6 +259,12 @@ const NotificationsBox = new Lang.Class({
|
||||
this._showSource(source, obj, obj.sourceBox);
|
||||
this._notificationBox.add(obj.sourceBox, { x_fill: false, x_align: St.Align.START });
|
||||
|
||||
if (obj.musicNotification) {
|
||||
obj.sourceNotifyId = source.connect('notify', Lang.bind(this, function(source, notification) {
|
||||
notification.acknowledged = true;
|
||||
}));
|
||||
}
|
||||
|
||||
obj.sourceCountChangedId = source.connect('count-updated', Lang.bind(this, function(source) {
|
||||
this._countChanged(source, obj);
|
||||
}));
|
||||
@ -362,6 +345,8 @@ const NotificationsBox = new Lang.Class({
|
||||
if (obj.musicNotification) {
|
||||
this._musicBin.child = null;
|
||||
obj.musicNotification = null;
|
||||
|
||||
source.disconnect(obj.sourceNotifyId);
|
||||
}
|
||||
|
||||
source.disconnect(obj.sourceDestroyId);
|
||||
@ -457,8 +442,8 @@ const ScreenShield = new Lang.Class({
|
||||
name: 'lockScreenGroup',
|
||||
visible: false,
|
||||
});
|
||||
this._lockScreenGroup.connect('key-release-event',
|
||||
Lang.bind(this, this._onLockScreenKeyRelease));
|
||||
this._lockScreenGroup.connect('key-press-event',
|
||||
Lang.bind(this, this._onLockScreenKeyPress));
|
||||
this._lockScreenGroup.connect('scroll-event',
|
||||
Lang.bind(this, this._onLockScreenScroll));
|
||||
Main.ctrlAltTabManager.addGroup(this._lockScreenGroup, _("Lock"), 'changes-prevent-symbolic');
|
||||
@ -467,22 +452,20 @@ const ScreenShield = new Lang.Class({
|
||||
name: 'lockScreenContents' });
|
||||
this._lockScreenContents.add_constraint(new Layout.MonitorConstraint({ primary: true }));
|
||||
|
||||
let backgroundActor = Meta.BackgroundActor.new_for_screen(global.screen);
|
||||
backgroundActor.add_glsl_snippet(Meta.SnippetHook.TEXTURE_LOOKUP,
|
||||
GLSL_BLUR_EFFECT_DECLARATIONS,
|
||||
GLSL_BLUR_EFFECT_CODE,
|
||||
true);
|
||||
backgroundActor.set_uniform_float('desaturation',
|
||||
1, 1, [0.6]);
|
||||
backgroundActor.connect('notify::size', function(actor) {
|
||||
actor.set_uniform_float('pixel_step', 2, 1, [1/actor.width, 1/actor.height]);
|
||||
});
|
||||
|
||||
this._background = new St.Bin({ style_class: 'screen-shield-background',
|
||||
child: backgroundActor });
|
||||
this._lockScreenGroup.add_actor(this._background);
|
||||
this._lockScreenGroup.add_actor(this._lockScreenContents);
|
||||
|
||||
this._backgroundGroup = new Clutter.Actor();
|
||||
|
||||
this._lockScreenGroup.add_actor(this._backgroundGroup);
|
||||
this._backgroundGroup.lower_bottom();
|
||||
this._bgManagers = [];
|
||||
|
||||
this._updateBackgrounds();
|
||||
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._updateBackgrounds));
|
||||
|
||||
this._arrowAnimationId = 0;
|
||||
this._arrowWatchId = 0;
|
||||
this._arrowActiveWatchId = 0;
|
||||
this._arrowContainer = new St.BoxLayout({ style_class: 'screen-shield-arrows',
|
||||
vertical: true,
|
||||
x_align: Clutter.ActorAlign.CENTER,
|
||||
@ -506,9 +489,16 @@ const ScreenShield = new Lang.Class({
|
||||
|
||||
this._lockDialogGroup = new St.Widget({ x_expand: true,
|
||||
y_expand: true,
|
||||
opacity: 0,
|
||||
pivot_point: new Clutter.Point({ x: 0.5, y: 0.5 }),
|
||||
name: 'lockDialogGroup' });
|
||||
|
||||
Tweener.addTween(this._lockDialogGroup,
|
||||
{ opacity: 255,
|
||||
time: INITIAL_FADE_IN_TIME,
|
||||
transition: 'easeInQuad',
|
||||
});
|
||||
|
||||
this.actor.add_actor(this._lockDialogGroup);
|
||||
this.actor.add_actor(this._lockScreenGroup);
|
||||
|
||||
@ -527,14 +517,18 @@ const ScreenShield = new Lang.Class({
|
||||
this._screenSaverDBus = new ShellDBus.ScreenSaverDBus(this);
|
||||
|
||||
this._inhibitor = null;
|
||||
this._aboutToSuspend = false;
|
||||
this._loginManager = LoginManager.getLoginManager();
|
||||
this._loginManager.connect('prepare-for-sleep',
|
||||
Lang.bind(this, this._prepareForSleep));
|
||||
this._inhibitSuspend();
|
||||
|
||||
this._loginSession = this._loginManager.getCurrentSessionProxy();
|
||||
this._loginSession.connectSignal('Lock', Lang.bind(this, function() { this.lock(false); }));
|
||||
this._loginSession.connectSignal('Unlock', Lang.bind(this, function() { this.deactivate(false); }));
|
||||
this._loginManager.getCurrentSessionProxy(Lang.bind(this,
|
||||
function(sessionProxy) {
|
||||
this._loginSession = sessionProxy;
|
||||
this._loginSession.connectSignal('Lock', Lang.bind(this, function() { this.lock(false); }));
|
||||
this._loginSession.connectSignal('Unlock', Lang.bind(this, function() { this.deactivate(false); }));
|
||||
}));
|
||||
|
||||
this._settings = new Gio.Settings({ schema: SCREENSAVER_SCHEMA });
|
||||
|
||||
@ -557,6 +551,34 @@ const ScreenShield = new Lang.Class({
|
||||
this.idleMonitor = new GnomeDesktop.IdleMonitor();
|
||||
},
|
||||
|
||||
_createBackground: function(monitorIndex) {
|
||||
let monitor = Main.layoutManager.monitors[monitorIndex];
|
||||
let widget = new St.Widget({ style_class: 'screen-shield-background',
|
||||
x: monitor.x,
|
||||
y: monitor.y,
|
||||
width: monitor.width,
|
||||
height: monitor.height });
|
||||
|
||||
let bgManager = new Background.BackgroundManager({ container: widget,
|
||||
monitorIndex: monitorIndex,
|
||||
controlPosition: false });
|
||||
|
||||
this._bgManagers.push(bgManager);
|
||||
|
||||
this._backgroundGroup.add_child(widget);
|
||||
},
|
||||
|
||||
_updateBackgrounds: function() {
|
||||
for (let i = 0; i < this._bgManagers.length; i++)
|
||||
this._bgManagers[i].destroy();
|
||||
|
||||
this._bgManagers = [];
|
||||
this._backgroundGroup.destroy_all_children();
|
||||
|
||||
for (let i = 0; i < Main.layoutManager.monitors.length; i++)
|
||||
this._createBackground(i);
|
||||
},
|
||||
|
||||
_liftShield: function(onPrimary, velocity) {
|
||||
if (this._isLocked) {
|
||||
this._ensureUnlockDialog(onPrimary, true /* allowCancel */);
|
||||
@ -566,28 +588,43 @@ const ScreenShield = new Lang.Class({
|
||||
}
|
||||
},
|
||||
|
||||
_onLockScreenKeyRelease: function(actor, event) {
|
||||
_becomeModal: function() {
|
||||
if (this._isModal)
|
||||
return true;
|
||||
|
||||
this._isModal = Main.pushModal(this.actor, { keybindingMode: Shell.KeyBindingMode.LOCK_SCREEN });
|
||||
if (this._isModal)
|
||||
return true;
|
||||
|
||||
// We failed to get a pointer grab, it means that
|
||||
// something else has it. Try with a keyboard grab only
|
||||
this._isModal = Main.pushModal(this.actor, { options: Meta.ModalOptions.POINTER_ALREADY_GRABBED,
|
||||
keybindingMode: Shell.KeyBindingMode.LOCK_SCREEN });
|
||||
return this._isModal;
|
||||
},
|
||||
|
||||
_onLockScreenKeyPress: function(actor, event) {
|
||||
let symbol = event.get_key_symbol();
|
||||
let unichar = event.get_key_unicode();
|
||||
|
||||
// Do nothing if the lock screen is not fully shown.
|
||||
// This avoids reusing the previous (and stale) unlock
|
||||
// dialog if esc is pressed while the curtain is going
|
||||
// down after cancel.
|
||||
// Similarly, don't bump if the lock screen is not showing or is
|
||||
// animating, as the bump overrides the animation and would
|
||||
// remove any onComplete handler.
|
||||
|
||||
if (this._lockScreenState != MessageTray.State.SHOWN)
|
||||
return false;
|
||||
|
||||
if (symbol == Clutter.KEY_Escape ||
|
||||
symbol == Clutter.KEY_Return ||
|
||||
symbol == Clutter.KEY_KP_Enter) {
|
||||
this._liftShield(false, 0);
|
||||
return true;
|
||||
}
|
||||
let isEnter = (symbol == Clutter.KEY_Return || symbol == Clutter.KEY_KP_Enter);
|
||||
if (!isEnter && !(GLib.unichar_isprint(unichar) || symbol == Clutter.KEY_Escape))
|
||||
return false;
|
||||
|
||||
this._bumpLockScreen();
|
||||
this._ensureUnlockDialog(true, true);
|
||||
|
||||
if (GLib.unichar_isgraph(unichar))
|
||||
this._dialog.addCharacter(unichar);
|
||||
|
||||
this._liftShield(true, 0);
|
||||
return true;
|
||||
},
|
||||
|
||||
@ -625,6 +662,8 @@ const ScreenShield = new Lang.Class({
|
||||
},
|
||||
|
||||
_prepareForSleep: function(loginManager, aboutToSuspend) {
|
||||
this._aboutToSuspend = aboutToSuspend;
|
||||
|
||||
if (aboutToSuspend) {
|
||||
if (!this._settings.get_boolean(LOCK_ENABLED_KEY)) {
|
||||
this._uninhibitSuspend();
|
||||
@ -689,7 +728,7 @@ const ScreenShield = new Lang.Class({
|
||||
// restore the lock screen to its original place
|
||||
// try to use the same speed as the normal animation
|
||||
let h = global.stage.height;
|
||||
let time = CURTAIN_SLIDE_TIME * (-this._lockScreenGroup.y) / h;
|
||||
let time = MANUAL_FADE_TIME * (-this._lockScreenGroup.y) / h;
|
||||
Tweener.removeTweens(this._lockScreenGroup);
|
||||
Tweener.addTween(this._lockScreenGroup,
|
||||
{ y: 0,
|
||||
@ -723,10 +762,18 @@ const ScreenShield = new Lang.Class({
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._isModal) {
|
||||
Main.pushModal(this.actor, { keybindingMode: Shell.KeyBindingMode.LOCK_SCREEN });
|
||||
this._isModal = true;
|
||||
}
|
||||
if (!this._becomeModal()) {
|
||||
// We could not become modal, so we can't activate the
|
||||
// screenshield. The user is probably very upset at this
|
||||
// point, but any application using global grabs is broken
|
||||
// Just tell him to stop using this app
|
||||
//
|
||||
// XXX: another option is to kick the user into the gdm login
|
||||
// screen, where we're not affected by grabs
|
||||
Main.notifyError(_("Unable to lock"),
|
||||
_("Lock was blocked by an application"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._lightbox.actor.visible ||
|
||||
this._isActive) {
|
||||
@ -746,9 +793,7 @@ const ScreenShield = new Lang.Class({
|
||||
if (this._activationTime == 0)
|
||||
this._activationTime = GLib.get_monotonic_time();
|
||||
|
||||
if (this._becameActiveId == 0)
|
||||
this._becameActiveId = this.idleMonitor.connect('became-active',
|
||||
Lang.bind(this, this._onUserBecameActive));
|
||||
this._becameActiveId = this.idleMonitor.add_user_active_watch(Lang.bind(this, this._onUserBecameActive));
|
||||
|
||||
let shouldLock = this._settings.get_boolean(LOCK_ENABLED_KEY) && !this._isLocked;
|
||||
|
||||
@ -782,7 +827,7 @@ const ScreenShield = new Lang.Class({
|
||||
// session is effectivelly locked now, it's time to build and show
|
||||
// the lock screen
|
||||
|
||||
this.idleMonitor.disconnect(this._becameActiveId);
|
||||
this.idleMonitor.remove_watch(this._becameActiveId);
|
||||
this._becameActiveId = 0;
|
||||
|
||||
let lightboxWasShown = this._lightbox.shown;
|
||||
@ -803,9 +848,10 @@ const ScreenShield = new Lang.Class({
|
||||
// Ensure that the stage window is mapped, before taking a grab
|
||||
// otherwise X errors out
|
||||
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() {
|
||||
if (!this._isModal) {
|
||||
Main.pushModal(this.actor);
|
||||
this._isModal = true;
|
||||
if (!this._becomeModal()) {
|
||||
// In the login screen, this is a hard error. Fail-whale
|
||||
log('Could not acquire modal grab for the login screen. Aborting login process.');
|
||||
Meta.quit(Meta.ExitCode.ERROR);
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -818,19 +864,12 @@ const ScreenShield = new Lang.Class({
|
||||
this._hideLockScreen(false, 0);
|
||||
},
|
||||
|
||||
_bumpLockScreen: function() {
|
||||
Tweener.removeTweens(this._lockScreenGroup);
|
||||
Tweener.addTween(this._lockScreenGroup,
|
||||
{ y: -BUMP_SIZE,
|
||||
time: BUMP_TIME / 2,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: function() {
|
||||
Tweener.addTween(this,
|
||||
{ y: 0,
|
||||
time: BUMP_TIME / 2,
|
||||
transition: 'easeInQuad' });
|
||||
}
|
||||
});
|
||||
_hideLockScreenComplete: function() {
|
||||
if (Main.sessionMode.currentMode == 'lock-screen')
|
||||
Main.sessionMode.popMode('lock-screen');
|
||||
|
||||
this._lockScreenState = MessageTray.State.HIDDEN;
|
||||
this._lockScreenGroup.hide();
|
||||
},
|
||||
|
||||
_hideLockScreen: function(animate, velocity) {
|
||||
@ -856,21 +895,13 @@ const ScreenShield = new Lang.Class({
|
||||
{ y: -h,
|
||||
time: time,
|
||||
transition: 'easeInQuad',
|
||||
onComplete: function() {
|
||||
this._lockScreenState = MessageTray.State.HIDDEN;
|
||||
this._lockScreenGroup.hide();
|
||||
},
|
||||
onCompleteScope: this,
|
||||
onComplete: Lang.bind(this, this._hideLockScreenComplete),
|
||||
});
|
||||
} else {
|
||||
this._lockScreenState = MessageTray.State.HIDDEN;
|
||||
this._lockScreenGroup.hide();
|
||||
this._hideLockScreenComplete();
|
||||
}
|
||||
|
||||
global.stage.show_cursor();
|
||||
|
||||
if (Main.sessionMode.currentMode == 'lock-screen')
|
||||
Main.sessionMode.popMode('lock-screen');
|
||||
},
|
||||
|
||||
_ensureUnlockDialog: function(onPrimary, allowCancel) {
|
||||
@ -886,14 +917,12 @@ const ScreenShield = new Lang.Class({
|
||||
|
||||
|
||||
let time = global.get_current_time();
|
||||
this._dialog.connect('loaded', Lang.bind(this, function() {
|
||||
if (!this._dialog.open(time, onPrimary)) {
|
||||
// This is kind of an impossible error: we're already modal
|
||||
// by the time we reach this...
|
||||
log('Could not open login dialog: failed to acquire grab');
|
||||
this.deactivate(true);
|
||||
}
|
||||
}));
|
||||
if (!this._dialog.open(time, onPrimary)) {
|
||||
// This is kind of an impossible error: we're already modal
|
||||
// by the time we reach this...
|
||||
log('Could not open login dialog: failed to acquire grab');
|
||||
this.deactivate(true);
|
||||
}
|
||||
|
||||
this._dialog.connect('failed', Lang.bind(this, this._onUnlockFailed));
|
||||
this._dialog.connect('unlocked', Lang.bind(this, this._onUnlockSucceded));
|
||||
@ -930,7 +959,7 @@ const ScreenShield = new Lang.Class({
|
||||
Tweener.removeTweens(this._lockScreenGroup);
|
||||
Tweener.addTween(this._lockScreenGroup,
|
||||
{ y: 0,
|
||||
time: SHORT_FADE_TIME,
|
||||
time: MANUAL_FADE_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: function() {
|
||||
this._lockScreenShown();
|
||||
@ -947,7 +976,7 @@ const ScreenShield = new Lang.Class({
|
||||
Tweener.removeTweens(this._lockDialogGroup);
|
||||
Tweener.addTween(this._lockDialogGroup,
|
||||
{ opacity: 255,
|
||||
time: SHORT_FADE_TIME,
|
||||
time: MANUAL_FADE_TIME,
|
||||
transition: 'easeOutQuad' });
|
||||
} else {
|
||||
this._lockDialogGroup.opacity = 255;
|
||||
@ -959,16 +988,60 @@ const ScreenShield = new Lang.Class({
|
||||
Main.sessionMode.pushMode('lock-screen');
|
||||
},
|
||||
|
||||
_startArrowAnimation: function() {
|
||||
this._arrowActiveWatchId = 0;
|
||||
|
||||
if (!this._arrowAnimationId) {
|
||||
this._arrowAnimationId = Mainloop.timeout_add(6000, Lang.bind(this, this._animateArrows));
|
||||
this._animateArrows();
|
||||
}
|
||||
|
||||
if (!this._arrowWatchId)
|
||||
this._arrowWatchId = this.idleMonitor.add_idle_watch(ARROW_IDLE_TIME,
|
||||
Lang.bind(this, this._pauseArrowAnimation));
|
||||
},
|
||||
|
||||
_pauseArrowAnimation: function() {
|
||||
if (this._arrowAnimationId) {
|
||||
Mainloop.source_remove(this._arrowAnimationId);
|
||||
this._arrowAnimationId = 0;
|
||||
}
|
||||
|
||||
if (!this._arrowActiveWatchId)
|
||||
this._arrowActiveWatchId = this.idleMonitor.add_user_active_watch(Lang.bind(this, this._startArrowAnimation));
|
||||
},
|
||||
|
||||
_stopArrowAnimation: function() {
|
||||
if (this._arrowAnimationId) {
|
||||
Mainloop.source_remove(this._arrowAnimationId);
|
||||
this._arrowAnimationId = 0;
|
||||
}
|
||||
if (this._arrowActiveWatchId) {
|
||||
this.idleMonitor.remove_watch(this._arrowActiveWatchId);
|
||||
this._arrowActiveWatchId = 0;
|
||||
}
|
||||
if (this._arrowWatchId) {
|
||||
this.idleMonitor.remove_watch(this._arrowWatchId);
|
||||
this._arrowWatchId = 0;
|
||||
}
|
||||
},
|
||||
|
||||
_checkArrowAnimation: function() {
|
||||
let idleTime = this.idleMonitor.get_idletime();
|
||||
|
||||
if (idleTime < ARROW_IDLE_TIME)
|
||||
this._startArrowAnimation();
|
||||
else
|
||||
this._pauseArrowAnimation();
|
||||
},
|
||||
|
||||
_lockScreenShown: function() {
|
||||
if (this._dialog && !this._isGreeter) {
|
||||
this._dialog.destroy();
|
||||
this._dialog = null;
|
||||
}
|
||||
|
||||
if (this._arrowAnimationId)
|
||||
Mainloop.source_remove(this._arrowAnimationId);
|
||||
this._arrowAnimationId = Mainloop.timeout_add(6000, Lang.bind(this, this._animateArrows));
|
||||
this._animateArrows();
|
||||
this._checkArrowAnimation();
|
||||
|
||||
let motionId = global.stage.connect('captured-event', function(stage, event) {
|
||||
if (event.type() == Clutter.EventType.MOTION) {
|
||||
@ -990,7 +1063,8 @@ const ScreenShield = new Lang.Class({
|
||||
if (prevIsActive != this._isActive)
|
||||
this.emit('active-changed');
|
||||
|
||||
this._uninhibitSuspend();
|
||||
if (this._aboutToSuspend)
|
||||
this._uninhibitSuspend();
|
||||
|
||||
this.emit('lock-screen-shown');
|
||||
},
|
||||
@ -1030,12 +1104,9 @@ const ScreenShield = new Lang.Class({
|
||||
this._notificationsBox = null;
|
||||
}
|
||||
|
||||
this._lockScreenContentsBox.destroy();
|
||||
this._stopArrowAnimation();
|
||||
|
||||
if (this._arrowAnimationId) {
|
||||
Mainloop.source_remove(this._arrowAnimationId);
|
||||
this._arrowAnimationId = 0;
|
||||
}
|
||||
this._lockScreenContentsBox.destroy();
|
||||
|
||||
this._hasLockScreen = false;
|
||||
},
|
||||
@ -1055,6 +1126,22 @@ const ScreenShield = new Lang.Class({
|
||||
deactivate: function(animate) {
|
||||
this._hideLockScreen(animate, 0);
|
||||
|
||||
if (this._hasLockScreen)
|
||||
this._clearLockScreen();
|
||||
|
||||
if (Main.sessionMode.currentMode == 'lock-screen')
|
||||
Main.sessionMode.popMode('lock-screen');
|
||||
if (Main.sessionMode.currentMode == 'unlock-dialog')
|
||||
Main.sessionMode.popMode('unlock-dialog');
|
||||
|
||||
if (this._dialog && !this._isGreeter)
|
||||
this._dialog.popModal();
|
||||
|
||||
if (this._isModal) {
|
||||
Main.popModal(this.actor);
|
||||
this._isModal = false;
|
||||
}
|
||||
|
||||
Tweener.addTween(this._lockDialogGroup, {
|
||||
scale_x: 0,
|
||||
scale_y: 0,
|
||||
@ -1066,30 +1153,16 @@ const ScreenShield = new Lang.Class({
|
||||
},
|
||||
|
||||
_completeDeactivate: function() {
|
||||
if (this._hasLockScreen)
|
||||
this._clearLockScreen();
|
||||
|
||||
if (this._dialog && !this._isGreeter) {
|
||||
this._dialog.destroy();
|
||||
this._dialog = null;
|
||||
}
|
||||
|
||||
this._lightbox.hide();
|
||||
|
||||
if (this._isModal) {
|
||||
Main.popModal(this.actor);
|
||||
this._isModal = false;
|
||||
}
|
||||
|
||||
this.actor.hide();
|
||||
|
||||
if (Main.sessionMode.currentMode == 'lock-screen')
|
||||
Main.sessionMode.popMode('lock-screen');
|
||||
if (Main.sessionMode.currentMode == 'unlock-dialog')
|
||||
Main.sessionMode.popMode('unlock-dialog');
|
||||
|
||||
if (this._becameActiveId != 0) {
|
||||
this.idleMonitor.disconnect(this._becameActiveId);
|
||||
this.idleMonitor.remove_watch(this._becameActiveId);
|
||||
this._becameActiveId = 0;
|
||||
}
|
||||
|
||||
@ -1103,6 +1176,7 @@ const ScreenShield = new Lang.Class({
|
||||
this._isLocked = false;
|
||||
this.emit('active-changed');
|
||||
this.emit('locked-changed');
|
||||
global.set_runtime_state(LOCKED_STATE_STR, null);
|
||||
},
|
||||
|
||||
activate: function(animate) {
|
||||
@ -1119,6 +1193,7 @@ const ScreenShield = new Lang.Class({
|
||||
}
|
||||
|
||||
this._resetLockScreen(animate, animate);
|
||||
global.set_runtime_state(LOCKED_STATE_STR, GLib.Variant.new('b', true));
|
||||
|
||||
// We used to set isActive and emit active-changed here,
|
||||
// but now we do that from lockScreenShown, which means
|
||||
@ -1133,9 +1208,11 @@ const ScreenShield = new Lang.Class({
|
||||
},
|
||||
|
||||
lock: function(animate) {
|
||||
if (!this._isModal) {
|
||||
Main.pushModal(this.actor, { keybindingMode: Shell.KeyBindingMode.LOCK_SCREEN });
|
||||
this._isModal = true;
|
||||
// Warn the user if we can't become modal
|
||||
if (!this._becomeModal()) {
|
||||
Main.notifyError(_("Unable to lock"),
|
||||
_("Lock was blocked by an application"));
|
||||
return;
|
||||
}
|
||||
|
||||
this._isLocked = true;
|
||||
@ -1143,64 +1220,15 @@ const ScreenShield = new Lang.Class({
|
||||
|
||||
this.emit('locked-changed');
|
||||
},
|
||||
|
||||
// If the previous shell crashed, and gnome-session restarted us, then re-lock
|
||||
lockIfWasLocked: function() {
|
||||
let wasLocked = global.get_runtime_state('b', LOCKED_STATE_STR);
|
||||
if (wasLocked === null)
|
||||
return;
|
||||
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() {
|
||||
this.lock(false);
|
||||
}));
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(ScreenShield.prototype);
|
||||
|
||||
/* Fallback code to handle session locking using gnome-screensaver,
|
||||
in case the required GDM dependency is not there
|
||||
*/
|
||||
const ScreenShieldFallback = new Lang.Class({
|
||||
Name: 'ScreenShieldFallback',
|
||||
|
||||
_init: function() {
|
||||
Util.spawn(['gnome-screensaver']);
|
||||
|
||||
this._proxy = new Gio.DBusProxy({ g_connection: Gio.DBus.session,
|
||||
g_name: 'org.gnome.ScreenSaver',
|
||||
g_object_path: '/org/gnome/ScreenSaver',
|
||||
g_interface_name: 'org.gnome.ScreenSaver',
|
||||
g_flags: (Gio.DBusProxyFlags.DO_NOT_AUTO_START |
|
||||
Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES),
|
||||
});
|
||||
this._proxy.init(null);
|
||||
|
||||
this._proxy.connect('g-signal', Lang.bind(this, this._onSignal));
|
||||
this._proxy.connect('notify::g-name-owner', Lang.bind(this, this._onNameOwnerChanged));
|
||||
},
|
||||
|
||||
_onNameOwnerChanged: function(object, pspec) {
|
||||
if (this._proxy.g_name_owner)
|
||||
[this._locked] = this._proxy.call_sync('GetActive', null,
|
||||
Gio.DBusCallFlags.NONE, -1, null).deep_unpack();
|
||||
else
|
||||
this._locked = false;
|
||||
|
||||
this.emit('active-changed', this._locked);
|
||||
},
|
||||
|
||||
_onSignal: function(proxy, senderName, signalName, params) {
|
||||
if (signalName == 'ActiveChanged') {
|
||||
[this._locked] = params.deep_unpack();
|
||||
this.emit('active-changed', this._locked);
|
||||
}
|
||||
},
|
||||
|
||||
get locked() {
|
||||
return this._locked;
|
||||
},
|
||||
|
||||
lock: function() {
|
||||
this._proxy.call('Lock', null, Gio.DBusCallFlags.NONE, -1, null,
|
||||
Lang.bind(this, function(proxy, result) {
|
||||
proxy.call_finish(result);
|
||||
|
||||
this.emit('lock-screen-shown');
|
||||
}));
|
||||
},
|
||||
|
||||
unlock: function() {
|
||||
this._proxy.call('SetActive', GLib.Variant.new('(b)', false),
|
||||
Gio.DBusCallFlags.NONE, -1, null, null);
|
||||
},
|
||||
});
|
||||
Signals.addSignalMethods(ScreenShieldFallback.prototype);
|
||||
|
140
js/ui/screencast.js
Normal file
140
js/ui/screencast.js
Normal file
@ -0,0 +1,140 @@
|
||||
// -*- 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 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._sessionModeChanged));
|
||||
},
|
||||
|
||||
_ensureRecorderForSender: function(sender) {
|
||||
let recorder = this._recorders.get(sender);
|
||||
if (!recorder) {
|
||||
recorder = new Shell.Recorder({ stage: global.stage });
|
||||
recorder._watchNameId =
|
||||
Gio.bus_watch_name(Gio.BusType.SESSION, sender, 0, null,
|
||||
Lang.bind(this, this._onNameVanished));
|
||||
this._recorders.set(sender, recorder);
|
||||
}
|
||||
return recorder;
|
||||
},
|
||||
|
||||
_sessionModeChanged: function() {
|
||||
if (Main.sessionMode.allowScreencast)
|
||||
return;
|
||||
|
||||
for (let sender in this._recorders.keys())
|
||||
this._recorders.delete(sender);
|
||||
},
|
||||
|
||||
_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);
|
||||
|
||||
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]));
|
||||
}
|
||||
});
|
@ -76,6 +76,11 @@ const ScreenshotService = new Lang.Class({
|
||||
|
||||
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,
|
||||
|
@ -60,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;
|
||||
|
||||
@ -110,6 +100,6 @@ const SearchSystem = new Lang.Class({
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
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;
|
||||
|
||||
@ -13,6 +14,7 @@ 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_LIST_SEARCH_RESULTS_ROWS = 3;
|
||||
const MAX_GRID_SEARCH_RESULTS_ROWS = 1;
|
||||
@ -102,11 +104,11 @@ const ListSearchResult = new Lang.Class({
|
||||
y_fill: false,
|
||||
x_align: St.Align.START,
|
||||
y_align: St.Align.START });
|
||||
this.actor.label_actor = title;
|
||||
|
||||
// TODO: should highlight terms in the description here
|
||||
if (this.metaInfo['description']) {
|
||||
let description = new St.Label({ style_class: 'list-search-result-description',
|
||||
text: '"' + 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,
|
||||
@ -140,6 +142,7 @@ const GridSearchResult = new Lang.Class({
|
||||
}
|
||||
|
||||
this.actor.set_child(content);
|
||||
this.actor.label_actor = content.label_actor;
|
||||
|
||||
let draggable = DND.makeDraggable(this.actor);
|
||||
draggable.connect('drag-begin',
|
||||
@ -228,9 +231,14 @@ const ListSearchResults = new Lang.Class({
|
||||
this._pendingClear = true;
|
||||
},
|
||||
|
||||
_keyFocusIn: function(icon) {
|
||||
this.emit('key-focus-in', icon);
|
||||
},
|
||||
|
||||
renderResults: function(metas) {
|
||||
for (let i = 0; i < metas.length; i++) {
|
||||
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);
|
||||
}
|
||||
},
|
||||
@ -247,6 +255,7 @@ const ListSearchResults = new Lang.Class({
|
||||
return null;
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(ListSearchResults.prototype);
|
||||
|
||||
const GridSearchResults = new Lang.Class({
|
||||
Name: 'GridSearchResults',
|
||||
@ -289,9 +298,14 @@ const GridSearchResults = new Lang.Class({
|
||||
this._pendingClear = true;
|
||||
},
|
||||
|
||||
_keyFocusIn: function(icon) {
|
||||
this.emit('key-focus-in', icon);
|
||||
},
|
||||
|
||||
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.actor);
|
||||
}
|
||||
},
|
||||
@ -308,6 +322,7 @@ const GridSearchResults = new Lang.Class({
|
||||
return null;
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(GridSearchResults.prototype);
|
||||
|
||||
const SearchResults = new Lang.Class({
|
||||
Name: 'SearchResults',
|
||||
@ -331,7 +346,8 @@ const SearchResults = new Lang.Class({
|
||||
|
||||
this._scrollView = new St.ScrollView({ x_fill: true,
|
||||
y_fill: false,
|
||||
style_class: 'vfade' });
|
||||
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 });
|
||||
@ -366,6 +382,10 @@ const SearchResults = new Lang.Class({
|
||||
return false;
|
||||
},
|
||||
|
||||
_keyFocusIn: function(provider, icon) {
|
||||
Util.ensureActorVisibleInScrollView(this._scrollView, icon);
|
||||
},
|
||||
|
||||
createProviderMeta: function(provider) {
|
||||
let providerBox = new St.BoxLayout({ style_class: 'search-section',
|
||||
vertical: true });
|
||||
@ -379,6 +399,8 @@ const SearchResults = new Lang.Class({
|
||||
resultDisplay = new GridSearchResults(provider);
|
||||
}
|
||||
|
||||
resultDisplay.connect('key-focus-in', Lang.bind(this, this._keyFocusIn));
|
||||
|
||||
let resultDisplayBin = new St.Bin({ child: resultDisplay.actor,
|
||||
x_fill: true,
|
||||
y_fill: true });
|
||||
@ -423,18 +445,15 @@ const SearchResults = new Lang.Class({
|
||||
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)];
|
||||
},
|
||||
@ -560,6 +579,7 @@ const ProviderIcon = new Lang.Class({
|
||||
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() });
|
||||
|
@ -45,9 +45,9 @@ const _modes = {
|
||||
unlockDialog: imports.gdm.loginDialog.LoginDialog,
|
||||
components: ['polkitAgent'],
|
||||
panel: {
|
||||
left: ['logo'],
|
||||
left: [],
|
||||
center: ['dateMenu'],
|
||||
right: ['a11y', 'display', 'keyboard',
|
||||
right: ['a11yGreeter', 'display', 'keyboard',
|
||||
'volume', 'battery', 'powerMenu']
|
||||
},
|
||||
panelStyle: 'login-screen'
|
||||
@ -78,16 +78,6 @@ const _modes = {
|
||||
panelStyle: 'unlock-screen'
|
||||
},
|
||||
|
||||
'initial-setup': {
|
||||
isPrimary: true,
|
||||
components: ['keyring'],
|
||||
panel: {
|
||||
left: [],
|
||||
center: ['dateMenu'],
|
||||
right: ['a11y', 'keyboard', 'volume']
|
||||
}
|
||||
},
|
||||
|
||||
'user': {
|
||||
hasOverview: true,
|
||||
showCalendarEvents: true,
|
||||
@ -158,12 +148,7 @@ function listModes() {
|
||||
const SessionMode = new Lang.Class({
|
||||
Name: 'SessionMode',
|
||||
|
||||
_init: function() {
|
||||
global.connect('notify::session-mode', Lang.bind(this, this._sync));
|
||||
this._modes = _modes;
|
||||
this._modeStack = [DEFAULT_MODE];
|
||||
this._sync();
|
||||
|
||||
init: function() {
|
||||
_getModes(Lang.bind(this, function(modes) {
|
||||
this._modes = modes;
|
||||
let primary = modes[global.session_mode] &&
|
||||
@ -171,6 +156,8 @@ const SessionMode = new Lang.Class({
|
||||
let mode = primary ? global.session_mode : 'user';
|
||||
this._modeStack = [mode];
|
||||
this._sync();
|
||||
|
||||
this.emit('sessions-loaded');
|
||||
}));
|
||||
},
|
||||
|
||||
@ -197,6 +184,10 @@ const SessionMode = new Lang.Class({
|
||||
return this._modeStack[this._modeStack.length - 1];
|
||||
},
|
||||
|
||||
get allowScreencast() {
|
||||
return this.components.indexOf('recorder') != -1;
|
||||
},
|
||||
|
||||
_sync: function() {
|
||||
let params = this._modes[this.currentMode];
|
||||
let defaults;
|
||||
|
@ -1,15 +1,18 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Lang = imports.lang;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const Config = imports.misc.config;
|
||||
const ExtensionSystem = imports.ui.extensionSystem;
|
||||
const ExtensionDownloader = imports.ui.extensionDownloader;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Hash = imports.misc.hash;
|
||||
const Main = imports.ui.main;
|
||||
const Screencast = imports.ui.screencast;
|
||||
const Screenshot = imports.ui.screenshot;
|
||||
|
||||
const GnomeShellIface = <interface name="org.gnome.Shell">
|
||||
@ -18,6 +21,27 @@ const GnomeShellIface = <interface name="org.gnome.Shell">
|
||||
<arg type="b" direction="out" name="success" />
|
||||
<arg type="s" direction="out" name="result" />
|
||||
</method>
|
||||
<method name="FocusSearch"/>
|
||||
<method name="ShowOSD">
|
||||
<arg type="a{sv}" direction="in" name="params"/>
|
||||
</method>
|
||||
<method name="GrabAccelerator">
|
||||
<arg type="s" direction="in" name="accelerator"/>
|
||||
<arg type="u" direction="in" name="flags"/>
|
||||
<arg type="u" direction="out" name="action"/>
|
||||
</method>
|
||||
<method name="GrabAccelerators">
|
||||
<arg type="a(su)" direction="in" name="accelerators"/>
|
||||
<arg type="au" direction="out" name="actions"/>
|
||||
</method>
|
||||
<method name="UngrabAccelerator">
|
||||
<arg type="u" direction="in" name="action"/>
|
||||
<arg type="b" direction="out" name="success"/>
|
||||
</method>
|
||||
<signal name="AcceleratorActivated">
|
||||
<arg name="action" type="u" />
|
||||
<arg name="deviceid" type="u" />
|
||||
</signal>
|
||||
<property name="Mode" type="s" access="read" />
|
||||
<property name="OverviewActive" type="b" access="readwrite" />
|
||||
<property name="ShellVersion" type="s" access="read" />
|
||||
@ -47,8 +71,17 @@ const GnomeShell = new Lang.Class({
|
||||
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellIface, this);
|
||||
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell');
|
||||
|
||||
this._extensionsSerivce = new GnomeShellExtensions();
|
||||
this._extensionsService = new GnomeShellExtensions();
|
||||
this._screencastService = new Screencast.ScreencastService();
|
||||
this._screenshotService = new Screenshot.ScreenshotService();
|
||||
|
||||
this._grabbedAccelerators = new Hash.Map();
|
||||
this._grabbers = new Hash.Map();
|
||||
|
||||
global.display.connect('accelerator-activated', Lang.bind(this,
|
||||
function(display, action, deviceid) {
|
||||
this._emitAcceleratorActivated(action, deviceid);
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -67,7 +100,7 @@ const GnomeShell = new Lang.Class({
|
||||
*/
|
||||
Eval: function(code) {
|
||||
if (!global.settings.get_boolean('development-tools'))
|
||||
return [false, null];
|
||||
return [false, ''];
|
||||
|
||||
let returnValue;
|
||||
let success;
|
||||
@ -84,6 +117,106 @@ const GnomeShell = new Lang.Class({
|
||||
return [success, returnValue];
|
||||
},
|
||||
|
||||
FocusSearch: function() {
|
||||
Main.overview.focusSearch();
|
||||
},
|
||||
|
||||
ShowOSD: function(params) {
|
||||
for (let param in params)
|
||||
params[param] = params[param].deep_unpack();
|
||||
|
||||
let icon = null;
|
||||
if (params['icon'])
|
||||
icon = Gio.Icon.new_for_string(params['icon']);
|
||||
|
||||
Main.osdWindow.setIcon(icon);
|
||||
Main.osdWindow.setLabel(params['label']);
|
||||
Main.osdWindow.setLevel(params['level']);
|
||||
|
||||
Main.osdWindow.show();
|
||||
},
|
||||
|
||||
GrabAcceleratorAsync: function(params, invocation) {
|
||||
let [accel, flags] = params;
|
||||
let sender = invocation.get_sender();
|
||||
let bindingAction = this._grabAcceleratorForSender(accel, flags, sender);
|
||||
return invocation.return_value(GLib.Variant.new('(u)', [bindingAction]));
|
||||
},
|
||||
|
||||
GrabAcceleratorsAsync: function(params, invocation) {
|
||||
let [accels] = params;
|
||||
let sender = invocation.get_sender();
|
||||
let bindingActions = [];
|
||||
for (let i = 0; i < accels.length; i++) {
|
||||
let [accel, flags] = accels[i];
|
||||
bindingActions.push(this._grabAcceleratorForSender(accel, flags, sender));
|
||||
}
|
||||
return invocation.return_value(GLib.Variant.new('(au)', [bindingActions]));
|
||||
},
|
||||
|
||||
UngrabAcceleratorAsync: function(params, invocation) {
|
||||
let [action] = params;
|
||||
let grabbedBy = this._grabbedAccelerators.get(action);
|
||||
if (invocation.get_sender() != grabbedBy)
|
||||
return invocation.return_value(GLib.Variant.new('(b)', [false]));
|
||||
|
||||
let ungrabSucceeded = global.display.ungrab_accelerator(action);
|
||||
if (ungrabSucceeded)
|
||||
this._grabbedAccelerators.delete(action);
|
||||
return invocation.return_value(GLib.Variant.new('(b)', [ungrabSucceeded]));
|
||||
},
|
||||
|
||||
_emitAcceleratorActivated: function(action, deviceid) {
|
||||
let destination = this._grabbedAccelerators.get(action);
|
||||
if (!destination)
|
||||
return;
|
||||
|
||||
let connection = this._dbusImpl.get_connection();
|
||||
let info = this._dbusImpl.get_info();
|
||||
connection.emit_signal(destination,
|
||||
this._dbusImpl.get_object_path(),
|
||||
info ? info.name : null,
|
||||
'AcceleratorActivated',
|
||||
GLib.Variant.new('(uu)', [action, deviceid]));
|
||||
},
|
||||
|
||||
_grabAcceleratorForSender: function(accelerator, flags, sender) {
|
||||
let bindingAction = global.display.grab_accelerator(accelerator);
|
||||
if (bindingAction == Meta.KeyBindingAction.NONE)
|
||||
return Meta.KeyBindingAction.NONE;
|
||||
|
||||
let bindingName = Meta.external_binding_name_for_action(bindingAction);
|
||||
Main.wm.allowKeybinding(bindingName, flags);
|
||||
|
||||
this._grabbedAccelerators.set(bindingAction, sender);
|
||||
|
||||
if (!this._grabbers.has(sender)) {
|
||||
let id = Gio.bus_watch_name(Gio.BusType.SESSION, sender, 0, null,
|
||||
Lang.bind(this, this._onGrabberBusNameVanished));
|
||||
this._grabbers.set(sender, id);
|
||||
}
|
||||
|
||||
return bindingAction;
|
||||
},
|
||||
|
||||
_ungrabAccelerator: function(action) {
|
||||
let ungrabSucceeded = global.display.ungrab_accelerator(action);
|
||||
if (ungrabSucceeded)
|
||||
this._grabbedAccelerators.delete(action);
|
||||
},
|
||||
|
||||
_onGrabberBusNameVanished: function(connection, name) {
|
||||
let grabs = this._grabbedAccelerators.items();
|
||||
for (let i = 0; i < grabs.length; i++) {
|
||||
let [action, sender] = grabs[i];
|
||||
if (sender == name)
|
||||
this._ungrabAccelerator(action);
|
||||
}
|
||||
Gio.bus_unwatch_name(this._grabbers.get(name));
|
||||
this._grabbers.delete(name);
|
||||
},
|
||||
|
||||
|
||||
Mode: global.session_mode,
|
||||
|
||||
get OverviewActive() {
|
||||
@ -271,7 +404,7 @@ const ScreenSaverDBus = new Lang.Class({
|
||||
if (active)
|
||||
this._screenShield.activate(true);
|
||||
else
|
||||
this._screenShield.unlock(false);
|
||||
this._screenShield.deactivate(false);
|
||||
},
|
||||
|
||||
GetActive: function() {
|
||||
|
@ -1,8 +1,11 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Lang = imports.lang;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const BoxPointer = imports.ui.boxpointer;
|
||||
const Main = imports.ui.main;
|
||||
const Params = imports.misc.params;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
@ -11,9 +14,7 @@ const EntryMenu = new Lang.Class({
|
||||
Name: 'ShellEntryMenu',
|
||||
Extends: PopupMenu.PopupMenu,
|
||||
|
||||
_init: function(entry, params) {
|
||||
params = Params.parse (params, { isPassword: false });
|
||||
|
||||
_init: function(entry) {
|
||||
this.parent(entry, 0, St.Side.TOP);
|
||||
|
||||
this.actor.add_style_class_name('entry-context-menu');
|
||||
@ -34,8 +35,6 @@ const EntryMenu = new Lang.Class({
|
||||
this._pasteItem = item;
|
||||
|
||||
this._passwordItem = null;
|
||||
if (params.isPassword)
|
||||
this._makePasswordItem();
|
||||
|
||||
Main.uiGroup.add_actor(this.actor);
|
||||
this.actor.hide();
|
||||
@ -50,47 +49,45 @@ const EntryMenu = new Lang.Class({
|
||||
},
|
||||
|
||||
get isPassword() {
|
||||
return this._passwordItem != null;
|
||||
return this._passwordItem != null;
|
||||
},
|
||||
|
||||
set isPassword(v) {
|
||||
if (v == this.isPassword)
|
||||
return;
|
||||
if (v == this.isPassword)
|
||||
return;
|
||||
|
||||
if (v)
|
||||
this._makePasswordItem();
|
||||
else {
|
||||
this._passwordItem.destroy();
|
||||
this._passwordItem = null;
|
||||
}
|
||||
if (v) {
|
||||
this._makePasswordItem();
|
||||
this._entry.input_purpose = Gtk.InputPurpose.PASSWORD;
|
||||
} else {
|
||||
this._passwordItem.destroy();
|
||||
this._passwordItem = null;
|
||||
this._entry.input_purpose = Gtk.InputPurpose.FREE_FORM;
|
||||
}
|
||||
},
|
||||
|
||||
open: function() {
|
||||
open: function(animate) {
|
||||
this._updatePasteItem();
|
||||
this._updateCopyItem();
|
||||
if (this._passwordItem)
|
||||
this._updatePasswordItem();
|
||||
|
||||
this.parent(animate);
|
||||
this._entry.add_style_pseudo_class('focus');
|
||||
|
||||
let direction = Gtk.DirectionType.TAB_FORWARD;
|
||||
if (!this.actor.navigate_focus(null, direction, false))
|
||||
this.actor.grab_key_focus();
|
||||
|
||||
this.parent();
|
||||
this._entry.add_style_pseudo_class('focus');
|
||||
},
|
||||
|
||||
close: function() {
|
||||
this._entry.grab_key_focus();
|
||||
this.parent();
|
||||
},
|
||||
|
||||
_updateCopyItem: function() {
|
||||
let selection = this._entry.clutter_text.get_selection();
|
||||
this._copyItem.setSensitive(selection && selection != '');
|
||||
this._copyItem.setSensitive(!this._entry.clutter_text.password_char &&
|
||||
selection && selection != '');
|
||||
},
|
||||
|
||||
_updatePasteItem: function() {
|
||||
this._clipboard.get_text(Lang.bind(this,
|
||||
this._clipboard.get_text(St.ClipboardType.CLIPBOARD, Lang.bind(this,
|
||||
function(clipboard, text) {
|
||||
this._pasteItem.setSensitive(text && text != '');
|
||||
}));
|
||||
@ -106,11 +103,11 @@ const EntryMenu = new Lang.Class({
|
||||
|
||||
_onCopyActivated: function() {
|
||||
let selection = this._entry.clutter_text.get_selection();
|
||||
this._clipboard.set_text(selection);
|
||||
this._clipboard.set_text(St.ClipboardType.CLIPBOARD, selection);
|
||||
},
|
||||
|
||||
_onPasteActivated: function() {
|
||||
this._clipboard.get_text(Lang.bind(this,
|
||||
this._clipboard.get_text(St.ClipboardType.CLIPBOARD, Lang.bind(this,
|
||||
function(clipboard, text) {
|
||||
if (!text)
|
||||
return;
|
||||
@ -134,12 +131,12 @@ function _setMenuAlignment(entry, stageX) {
|
||||
|
||||
function _onButtonPressEvent(actor, event, entry) {
|
||||
if (entry.menu.isOpen) {
|
||||
entry.menu.close();
|
||||
entry.menu.close(BoxPointer.PopupAnimation.FULL);
|
||||
return true;
|
||||
} else if (event.get_button() == 3) {
|
||||
let [stageX, stageY] = event.get_coords();
|
||||
_setMenuAlignment(entry, stageX);
|
||||
entry.menu.open();
|
||||
entry.menu.open(BoxPointer.PopupAnimation.FULL);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -149,14 +146,17 @@ function _onPopup(actor, entry) {
|
||||
let [success, textX, textY, lineHeight] = entry.clutter_text.position_to_coords(-1);
|
||||
if (success)
|
||||
entry.menu.setSourceAlignment(textX / entry.width);
|
||||
entry.menu.open();
|
||||
entry.menu.open(BoxPointer.PopupAnimation.FULL);
|
||||
};
|
||||
|
||||
function addContextMenu(entry, params) {
|
||||
if (entry.menu)
|
||||
return;
|
||||
|
||||
entry.menu = new EntryMenu(entry, params);
|
||||
params = Params.parse (params, { isPassword: false });
|
||||
|
||||
entry.menu = new EntryMenu(entry);
|
||||
entry.menu.isPassword = params.isPassword;
|
||||
entry._menuManager = new PopupMenu.PopupMenuManager({ actor: entry });
|
||||
entry._menuManager.addMenu(entry.menu);
|
||||
|
||||
@ -167,4 +167,10 @@ function addContextMenu(entry, params) {
|
||||
entry.connect('button-press-event', Lang.bind(null, _onButtonPressEvent, entry));
|
||||
|
||||
entry.connect('popup-menu', Lang.bind(null, _onPopup, entry));
|
||||
|
||||
entry.connect('destroy', function() {
|
||||
entry.menu.destroy();
|
||||
entry.menu = null;
|
||||
entry._menuManager = null;
|
||||
});
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
@ -14,12 +15,7 @@ const KEY_MOUSE_KEYS_ENABLED = 'mousekeys-enable';
|
||||
|
||||
const APPLICATIONS_SCHEMA = 'org.gnome.desktop.a11y.applications';
|
||||
|
||||
const DPI_LOW_REASONABLE_VALUE = 50;
|
||||
const DPI_HIGH_REASONABLE_VALUE = 500;
|
||||
|
||||
const DPI_FACTOR_LARGE = 1.25;
|
||||
const DPI_FACTOR_LARGER = 1.5;
|
||||
const DPI_FACTOR_LARGEST = 2.0;
|
||||
|
||||
const WM_SCHEMA = 'org.gnome.desktop.wm.preferences';
|
||||
const KEY_VISUAL_BELL = 'visual-bell';
|
||||
@ -74,6 +70,25 @@ const ATIndicator = new Lang.Class({
|
||||
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this.menu.addSettingsAction(_("Universal Access Settings"), 'gnome-universal-access-panel.desktop');
|
||||
|
||||
this._syncMenuVisibility();
|
||||
},
|
||||
|
||||
_syncMenuVisibility: function() {
|
||||
this._syncMenuVisibilityIdle = 0;
|
||||
|
||||
let items = this.menu._getMenuItems();
|
||||
|
||||
this.actor.visible = items.some(function(f) { return !!f.state; });
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
_queueSyncMenuVisibility: function() {
|
||||
if (this._syncMenuVisibilityIdle)
|
||||
return;
|
||||
|
||||
this._syncMenuVisbilityIdle = Mainloop.idle_add(Lang.bind(this, this._syncMenuVisibility));
|
||||
},
|
||||
|
||||
_buildItemExtended: function(string, initial_value, writable, on_set) {
|
||||
@ -95,9 +110,11 @@ const ATIndicator = new Lang.Class({
|
||||
function(enabled) {
|
||||
return settings.set_boolean(key, enabled);
|
||||
});
|
||||
settings.connect('changed::'+key, function() {
|
||||
settings.connect('changed::'+key, Lang.bind(this, function() {
|
||||
widget.setToggleState(settings.get_boolean(key));
|
||||
});
|
||||
|
||||
this._queueSyncMenuVisibility();
|
||||
}));
|
||||
return widget;
|
||||
},
|
||||
|
||||
@ -129,7 +146,7 @@ const ATIndicator = new Lang.Class({
|
||||
wmSettings.reset(KEY_WM_THEME);
|
||||
}
|
||||
});
|
||||
interfaceSettings.connect('changed::' + KEY_GTK_THEME, function() {
|
||||
interfaceSettings.connect('changed::' + KEY_GTK_THEME, Lang.bind(this, function() {
|
||||
let value = interfaceSettings.get_string(KEY_GTK_THEME);
|
||||
if (value == HIGH_CONTRAST_THEME) {
|
||||
highContrast.setToggleState(true);
|
||||
@ -137,7 +154,9 @@ const ATIndicator = new Lang.Class({
|
||||
highContrast.setToggleState(false);
|
||||
gtkTheme = value;
|
||||
}
|
||||
});
|
||||
|
||||
this._queueSyncMenuVisibility();
|
||||
}));
|
||||
interfaceSettings.connect('changed::' + KEY_ICON_THEME, function() {
|
||||
let value = interfaceSettings.get_string(KEY_ICON_THEME);
|
||||
if (value != HIGH_CONTRAST_THEME)
|
||||
@ -166,11 +185,22 @@ const ATIndicator = new Lang.Class({
|
||||
else
|
||||
settings.reset(KEY_TEXT_SCALING_FACTOR);
|
||||
});
|
||||
settings.connect('changed::' + KEY_TEXT_SCALING_FACTOR, function() {
|
||||
settings.connect('changed::' + KEY_TEXT_SCALING_FACTOR, Lang.bind(this, function() {
|
||||
let factor = settings.get_double(KEY_TEXT_SCALING_FACTOR);
|
||||
let active = (factor > 1.0);
|
||||
widget.setToggleState(active);
|
||||
});
|
||||
|
||||
this._queueSyncMenuVisibility();
|
||||
}));
|
||||
return widget;
|
||||
}
|
||||
});
|
||||
|
||||
const ATGreeterIndicator = new Lang.Class({
|
||||
Name: 'ATGreeterIndicator',
|
||||
Extends: ATIndicator,
|
||||
|
||||
// Override visibility handling to be always visible
|
||||
_syncMenuVisibility: function() { },
|
||||
_queueSyncMenuVisibility: function() { }
|
||||
});
|
||||
|
@ -56,8 +56,8 @@ const Indicator = new Lang.Class({
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
|
||||
this._fullMenuItems = [new PopupMenu.PopupSeparatorMenuItem(),
|
||||
new PopupMenu.PopupMenuItem(_("Send Files to Device...")),
|
||||
new PopupMenu.PopupMenuItem(_("Set Up a New Device...")),
|
||||
new PopupMenu.PopupMenuItem(_("Send Files to Device…")),
|
||||
new PopupMenu.PopupMenuItem(_("Set Up a New Device…")),
|
||||
new PopupMenu.PopupSeparatorMenuItem()];
|
||||
this._hasDevices = false;
|
||||
|
||||
@ -236,7 +236,7 @@ const Indicator = new Lang.Class({
|
||||
}
|
||||
|
||||
if (device.capabilities & GnomeBluetoothApplet.Capabilities.OBEX_PUSH) {
|
||||
item.menu.addAction(_("Send Files..."), Lang.bind(this, function() {
|
||||
item.menu.addAction(_("Send Files…"), Lang.bind(this, function() {
|
||||
this._applet.send_to_address(device.bdaddr, device.alias);
|
||||
}));
|
||||
}
|
||||
@ -363,7 +363,7 @@ const ConfirmNotification = new Lang.Class({
|
||||
this._applet = applet;
|
||||
this._devicePath = device_path;
|
||||
this.addBody(_("Device %s wants to pair with this computer").format(long_name));
|
||||
this.addBody(_("Please confirm whether the PIN '%06d' matches the one on the device.").format(pin));
|
||||
this.addBody(_("Please confirm whether the Passkey '%06d' matches the one on the device.").format(pin));
|
||||
|
||||
/* Translators: this is the verb, not the noun */
|
||||
this.addButton('matches', _("Matches"));
|
||||
|
@ -33,6 +33,33 @@ const KEY_INPUT_SOURCES = 'sources';
|
||||
const INPUT_SOURCE_TYPE_XKB = 'xkb';
|
||||
const INPUT_SOURCE_TYPE_IBUS = 'ibus';
|
||||
|
||||
// This is the longest we'll keep the keyboard frozen until an input
|
||||
// source is active.
|
||||
const MAX_INPUT_SOURCE_ACTIVATION_TIME = 4000; // ms
|
||||
|
||||
const BUS_NAME = 'org.gnome.SettingsDaemon.Keyboard';
|
||||
const OBJECT_PATH = '/org/gnome/SettingsDaemon/Keyboard';
|
||||
|
||||
const KeyboardManagerInterface =
|
||||
<interface name="org.gnome.SettingsDaemon.Keyboard">
|
||||
<method name="SetInputSource">
|
||||
<arg type="u" direction="in" />
|
||||
</method>
|
||||
</interface>;
|
||||
|
||||
const KeyboardManagerProxy = Gio.DBusProxy.makeProxyWrapper(KeyboardManagerInterface);
|
||||
|
||||
function releaseKeyboard() {
|
||||
if (Main.modalCount > 0)
|
||||
global.display.unfreeze_keyboard(global.get_current_time());
|
||||
else
|
||||
global.display.ungrab_keyboard(global.get_current_time());
|
||||
}
|
||||
|
||||
function holdKeyboard() {
|
||||
global.freeze_keyboard(global.get_current_time());
|
||||
}
|
||||
|
||||
const IBusManager = new Lang.Class({
|
||||
Name: 'IBusManager',
|
||||
|
||||
@ -45,26 +72,24 @@ const IBusManager = new Lang.Class({
|
||||
this._readyCallback = readyCallback;
|
||||
this._candidatePopup = new IBusCandidatePopup.CandidatePopup();
|
||||
|
||||
this._ibus = null;
|
||||
this._panelService = null;
|
||||
this._engines = {};
|
||||
this._ready = false;
|
||||
this._registerPropertiesId = 0;
|
||||
this._currentEngineName = null;
|
||||
|
||||
this._nameWatcherId = Gio.DBus.session.watch_name(IBus.SERVICE_IBUS,
|
||||
Gio.BusNameWatcherFlags.NONE,
|
||||
Lang.bind(this, this._onNameAppeared),
|
||||
Lang.bind(this, this._clear));
|
||||
this._ibus = IBus.Bus.new_async();
|
||||
this._ibus.connect('connected', Lang.bind(this, this._onConnected));
|
||||
this._ibus.connect('disconnected', Lang.bind(this, this._clear));
|
||||
// Need to set this to get 'global-engine-changed' emitions
|
||||
this._ibus.set_watch_ibus_signal(true);
|
||||
this._ibus.connect('global-engine-changed', Lang.bind(this, this._engineChanged));
|
||||
},
|
||||
|
||||
_clear: function() {
|
||||
if (this._panelService)
|
||||
this._panelService.destroy();
|
||||
if (this._ibus)
|
||||
this._ibus.destroy();
|
||||
|
||||
this._ibus = null;
|
||||
this._panelService = null;
|
||||
this._candidatePopup.setPanelService(null);
|
||||
this._engines = {};
|
||||
@ -76,18 +101,12 @@ const IBusManager = new Lang.Class({
|
||||
this._readyCallback(false);
|
||||
},
|
||||
|
||||
_onNameAppeared: function() {
|
||||
this._ibus = IBus.Bus.new_async();
|
||||
this._ibus.connect('connected', Lang.bind(this, this._onConnected));
|
||||
},
|
||||
|
||||
_onConnected: function() {
|
||||
this._ibus.list_engines_async(-1, null, Lang.bind(this, this._initEngines));
|
||||
this._ibus.request_name_async(IBus.SERVICE_PANEL,
|
||||
IBus.BusNameFlag.REPLACE_EXISTING,
|
||||
-1, null,
|
||||
Lang.bind(this, this._initPanelService));
|
||||
this._ibus.connect('disconnected', Lang.bind(this, this._clear));
|
||||
},
|
||||
|
||||
_initEngines: function(ibus, result) {
|
||||
@ -109,9 +128,6 @@ const IBusManager = new Lang.Class({
|
||||
this._panelService = new IBus.PanelService({ connection: this._ibus.get_connection(),
|
||||
object_path: IBus.PATH_PANEL });
|
||||
this._candidatePopup.setPanelService(this._panelService);
|
||||
// Need to set this to get 'global-engine-changed' emitions
|
||||
this._ibus.set_watch_ibus_signal(true);
|
||||
this._ibus.connect('global-engine-changed', Lang.bind(this, this._engineChanged));
|
||||
this._panelService.connect('update-property', Lang.bind(this, this._updateProperty));
|
||||
// If an engine is already active we need to get its properties
|
||||
this._ibus.get_global_engine_async(-1, null, Lang.bind(this, function(i, result) {
|
||||
@ -140,6 +156,9 @@ const IBusManager = new Lang.Class({
|
||||
},
|
||||
|
||||
_engineChanged: function(bus, engineName) {
|
||||
if (!this._ready)
|
||||
return;
|
||||
|
||||
this._currentEngineName = engineName;
|
||||
|
||||
if (this._registerPropertiesId != 0)
|
||||
@ -364,6 +383,15 @@ const InputSourceIndicator = new Lang.Class({
|
||||
this._ibusManager.connect('property-updated', Lang.bind(this, this._ibusPropertyUpdated));
|
||||
this._inputSourcesChanged();
|
||||
|
||||
this._keyboardManager = new KeyboardManagerProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH,
|
||||
function(proxy, error) {
|
||||
if (error)
|
||||
log(error.message);
|
||||
});
|
||||
this._keyboardManager.g_default_timeout = MAX_INPUT_SOURCE_ACTIVATION_TIME;
|
||||
|
||||
global.display.connect('modifiers-accelerator-activated', Lang.bind(this, this._modifiersSwitcher));
|
||||
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this._showLayoutItem = this.menu.addAction(_("Show Keyboard Layout"), Lang.bind(this, this._showLayout));
|
||||
|
||||
@ -397,7 +425,43 @@ const InputSourceIndicator = new Lang.Class({
|
||||
this._inputSourcesChanged();
|
||||
},
|
||||
|
||||
_modifiersSwitcher: function() {
|
||||
let sourceIndexes = Object.keys(this._inputSources);
|
||||
if (sourceIndexes.length == 0) {
|
||||
releaseKeyboard();
|
||||
return true;
|
||||
}
|
||||
|
||||
let is = this._currentSource;
|
||||
if (!is)
|
||||
is = this._inputSources[sourceIndexes[0]];
|
||||
|
||||
let nextIndex = is.index + 1;
|
||||
if (nextIndex > sourceIndexes[sourceIndexes.length - 1])
|
||||
nextIndex = 0;
|
||||
|
||||
while (!(is = this._inputSources[nextIndex]))
|
||||
nextIndex += 1;
|
||||
|
||||
is.activate();
|
||||
return true;
|
||||
},
|
||||
|
||||
_switchInputSource: function(display, screen, window, binding) {
|
||||
if (this._mruSources.length < 2)
|
||||
return;
|
||||
|
||||
// HACK: Fall back on simple input source switching since we
|
||||
// can't show a popup switcher while a GrabHelper grab is in
|
||||
// effect without considerable work to consolidate the usage
|
||||
// of pushModal/popModal and grabHelper. See
|
||||
// https://bugzilla.gnome.org/show_bug.cgi?id=695143 .
|
||||
if (Main.keybindingMode == Shell.KeyBindingMode.MESSAGE_TRAY ||
|
||||
Main.keybindingMode == Shell.KeyBindingMode.TOPBAR_POPUP) {
|
||||
this._modifiersSwitcher();
|
||||
return;
|
||||
}
|
||||
|
||||
let popup = new InputSourcePopup(this._mruSources, this._keybindingAction, this._keybindingActionBackward);
|
||||
let modifiers = binding.get_modifiers();
|
||||
let backwards = modifiers & Meta.VirtualModifier.SHIFT_MASK;
|
||||
@ -410,6 +474,14 @@ const InputSourceIndicator = new Lang.Class({
|
||||
let newSourceIndex = this._settings.get_uint(KEY_CURRENT_INPUT_SOURCE);
|
||||
let newSource = this._inputSources[newSourceIndex];
|
||||
|
||||
let oldSource;
|
||||
[oldSource, this._currentSource] = [this._currentSource, newSource];
|
||||
|
||||
if (oldSource) {
|
||||
oldSource.menuItem.setOrnament(PopupMenu.Ornament.NONE);
|
||||
oldSource.indicatorLabel.hide();
|
||||
}
|
||||
|
||||
if (!newSource || (nVisibleSources < 2 && !newSource.properties)) {
|
||||
// This source index might be invalid if we weren't able
|
||||
// to build a menu item for it, so we hide ourselves since
|
||||
@ -424,16 +496,8 @@ const InputSourceIndicator = new Lang.Class({
|
||||
|
||||
this.actor.show();
|
||||
|
||||
let oldSource;
|
||||
[oldSource, this._currentSource] = [this._currentSource, newSource];
|
||||
|
||||
if (oldSource) {
|
||||
oldSource.menuItem.setShowDot(false);
|
||||
this._container.set_skip_paint(oldSource.indicatorLabel, true);
|
||||
}
|
||||
|
||||
newSource.menuItem.setShowDot(true);
|
||||
this._container.set_skip_paint(newSource.indicatorLabel, false);
|
||||
newSource.menuItem.setOrnament(PopupMenu.Ornament.DOT);
|
||||
newSource.indicatorLabel.show();
|
||||
|
||||
this._buildPropSection(newSource.properties);
|
||||
|
||||
@ -456,6 +520,7 @@ const InputSourceIndicator = new Lang.Class({
|
||||
|
||||
this._inputSources = {};
|
||||
this._ibusSources = {};
|
||||
this._currentSource = null;
|
||||
|
||||
let inputSourcesByShortName = {};
|
||||
|
||||
@ -484,10 +549,8 @@ const InputSourceIndicator = new Lang.Class({
|
||||
let is = new InputSource(type, id, displayName, shortName, i);
|
||||
|
||||
is.connect('activate', Lang.bind(this, function() {
|
||||
if (this._currentSource.index == is.index)
|
||||
return;
|
||||
this._settings.set_value(KEY_CURRENT_INPUT_SOURCE,
|
||||
GLib.Variant.new_uint32(is.index));
|
||||
holdKeyboard();
|
||||
this._keyboardManager.SetInputSourceRemote(is.index, releaseKeyboard);
|
||||
}));
|
||||
|
||||
if (!(is.shortName in inputSourcesByShortName))
|
||||
@ -510,8 +573,8 @@ const InputSourceIndicator = new Lang.Class({
|
||||
|
||||
this.menu.addMenuItem(is.menuItem, menuIndex++);
|
||||
|
||||
is.indicatorLabel.hide();
|
||||
this._container.add_actor(is.indicatorLabel);
|
||||
this._container.set_skip_paint(is.indicatorLabel, true);
|
||||
}
|
||||
|
||||
let sourcesList = [];
|
||||
@ -657,7 +720,8 @@ const InputSourceIndicator = new Lang.Class({
|
||||
item.prop = prop;
|
||||
radioGroup.push(item);
|
||||
item.radioGroup = radioGroup;
|
||||
item.setShowDot(prop.get_state() == IBus.PropState.CHECKED);
|
||||
item.setOrnament(prop.get_state() == IBus.PropState.CHECKED ?
|
||||
PopupMenu.Ornament.DOT : PopupMenu.Ornament.NONE);
|
||||
item.connect('activate', Lang.bind(this, function() {
|
||||
if (item.prop.get_state() == IBus.PropState.CHECKED)
|
||||
return;
|
||||
@ -665,12 +729,12 @@ const InputSourceIndicator = new Lang.Class({
|
||||
let group = item.radioGroup;
|
||||
for (let i = 0; i < group.length; ++i) {
|
||||
if (group[i] == item) {
|
||||
item.setShowDot(true);
|
||||
item.setOrnament(PopupMenu.Ornament.DOT);
|
||||
item.prop.set_state(IBus.PropState.CHECKED);
|
||||
this._ibusManager.activateProperty(item.prop.get_key(),
|
||||
IBus.PropState.CHECKED);
|
||||
} else {
|
||||
group[i].setShowDot(false);
|
||||
group[i].setOrnament(PopupMenu.Ornament.NONE);
|
||||
group[i].prop.set_state(IBus.PropState.UNCHECKED);
|
||||
this._ibusManager.activateProperty(group[i].prop.get_key(),
|
||||
IBus.PropState.UNCHECKED);
|
||||
@ -700,7 +764,7 @@ const InputSourceIndicator = new Lang.Class({
|
||||
item.prop = prop;
|
||||
item.connect('activate', Lang.bind(this, function() {
|
||||
this._ibusManager.activateProperty(item.prop.get_key(),
|
||||
IBus.PropState.CHECKED);
|
||||
item.prop.get_state());
|
||||
}));
|
||||
break;
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -96,7 +96,7 @@ const Indicator = new Lang.Class({
|
||||
if (time == 0) {
|
||||
// 0 is reported when UPower does not have enough data
|
||||
// to estimate battery life
|
||||
this._batteryItem.label.text = _("Estimating...");
|
||||
this._batteryItem.label.text = _("Estimating…");
|
||||
} else {
|
||||
let minutes = time % 60;
|
||||
let hours = Math.floor(time / 60);
|
||||
|
@ -307,7 +307,7 @@ const Indicator = new Lang.Class({
|
||||
this._headphoneIcon.visible = value;
|
||||
}));
|
||||
|
||||
this._headphoneIcon = this.addIcon(new Gio.ThemedIcon({ name: 'headphones-symbolic' }));
|
||||
this._headphoneIcon = this.addIcon(new Gio.ThemedIcon({ name: 'audio-headphones-symbolic' }));
|
||||
this._headphoneIcon.visible = false;
|
||||
|
||||
this.menu.addMenuItem(this._volumeMenu);
|
||||
|
@ -160,6 +160,7 @@ const SwitcherPopup = new Lang.Class({
|
||||
// disturbed by the popup briefly flashing.
|
||||
this._initialDelayTimeoutId = Mainloop.timeout_add(POPUP_DELAY_TIMEOUT,
|
||||
Lang.bind(this, function () {
|
||||
Main.osdWindow.cancel();
|
||||
this.actor.opacity = 255;
|
||||
this._initialDelayTimeoutId = 0;
|
||||
}));
|
||||
@ -304,7 +305,7 @@ const SwitcherList = new Lang.Class({
|
||||
this.actor.connect('allocate', Lang.bind(this, this._allocateTop));
|
||||
|
||||
// Here we use a GenericContainer so that we can force all the
|
||||
// children except the separator to have the same width.
|
||||
// children to have the same width.
|
||||
this._list = new Shell.GenericContainer({ style_class: 'switcher-list-item-container' });
|
||||
this._list.spacing = 0;
|
||||
this._list.connect('style-changed', Lang.bind(this, function() {
|
||||
@ -339,7 +340,6 @@ const SwitcherList = new Lang.Class({
|
||||
|
||||
this._items = [];
|
||||
this._highlighted = -1;
|
||||
this._separator = null;
|
||||
this._squareItems = squareItems;
|
||||
this._minSize = 0;
|
||||
this._scrollableRight = true;
|
||||
@ -402,12 +402,6 @@ const SwitcherList = new Lang.Class({
|
||||
this._itemEntered(index);
|
||||
},
|
||||
|
||||
addSeparator: function () {
|
||||
let box = new St.Bin({ style_class: 'separator' });
|
||||
this._separator = box;
|
||||
this._list.add_actor(box);
|
||||
},
|
||||
|
||||
highlight: function(index, justOutline) {
|
||||
if (this._highlighted != -1) {
|
||||
this._items[this._highlighted].remove_style_pseudo_class('outlined');
|
||||
@ -515,14 +509,8 @@ const SwitcherList = new Lang.Class({
|
||||
_getPreferredWidth: function (actor, forHeight, alloc) {
|
||||
let [maxChildMin, maxChildNat] = this._maxChildWidth(forHeight);
|
||||
|
||||
let separatorWidth = 0;
|
||||
if (this._separator) {
|
||||
let [sepMin, sepNat] = this._separator.get_preferred_width(forHeight);
|
||||
separatorWidth = sepNat + this._list.spacing;
|
||||
}
|
||||
|
||||
let totalSpacing = this._list.spacing * (this._items.length - 1);
|
||||
alloc.min_size = this._items.length * maxChildMin + separatorWidth + totalSpacing;
|
||||
alloc.min_size = this._items.length * maxChildMin + totalSpacing;
|
||||
alloc.natural_size = alloc.min_size;
|
||||
this._minSize = alloc.min_size;
|
||||
},
|
||||
@ -553,14 +541,7 @@ const SwitcherList = new Lang.Class({
|
||||
let [maxChildMin, maxChildNat] = this._maxChildWidth(childHeight);
|
||||
let totalSpacing = this._list.spacing * (this._items.length - 1);
|
||||
|
||||
let separatorWidth = 0;
|
||||
if (this._separator) {
|
||||
let [sepMin, sepNat] = this._separator.get_preferred_width(childHeight);
|
||||
separatorWidth = sepNat;
|
||||
totalSpacing += this._list.spacing;
|
||||
}
|
||||
|
||||
let childWidth = Math.floor(Math.max(0, box.x2 - box.x1 - totalSpacing - separatorWidth) / this._items.length);
|
||||
let childWidth = Math.floor(Math.max(0, box.x2 - box.x1 - totalSpacing) / this._items.length);
|
||||
|
||||
let x = 0;
|
||||
let children = this._list.get_children();
|
||||
@ -580,14 +561,6 @@ const SwitcherList = new Lang.Class({
|
||||
children[i].allocate(childBox, flags);
|
||||
|
||||
x += this._list.spacing + childWidth;
|
||||
} else if (children[i] == this._separator) {
|
||||
// We want the separator to be more compact than the rest.
|
||||
childBox.x1 = x;
|
||||
childBox.y1 = 0;
|
||||
childBox.x2 = x + separatorWidth;
|
||||
childBox.y2 = childHeight;
|
||||
children[i].allocate(childBox, flags);
|
||||
x += this._list.spacing + separatorWidth;
|
||||
} else {
|
||||
// Something else, eg, AppSwitcher's arrows;
|
||||
// we don't allocate it.
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Shell = imports.gi.Shell;
|
||||
@ -73,6 +74,9 @@ function _wrapTweening(target, tweeningParameters) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!Gtk.Settings.get_default().gtk_enable_animations)
|
||||
tweeningParameters['time'] = 0.000001;
|
||||
|
||||
_addHandler(target, tweeningParameters, 'onStart', _tweenStarted);
|
||||
_addHandler(target, tweeningParameters, 'onComplete', _tweenCompleted);
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ const Panel = imports.ui.panel;
|
||||
const ShellEntry = imports.ui.shellEntry;
|
||||
const Tweener = imports.ui.tweener;
|
||||
const UserMenu = imports.ui.userMenu;
|
||||
const UserWidget = imports.ui.userWidget;
|
||||
|
||||
const Batch = imports.gdm.batch;
|
||||
const GdmUtil = imports.gdm.util;
|
||||
@ -26,88 +27,6 @@ const LoginDialog = imports.gdm.loginDialog;
|
||||
// The timeout before going back automatically to the lock screen (in seconds)
|
||||
const IDLE_TIMEOUT = 2 * 60;
|
||||
|
||||
function versionCompare(required, reference) {
|
||||
required = required.split('.');
|
||||
reference = reference.split('.');
|
||||
|
||||
for (let i = 0; i < required.length; i++) {
|
||||
if (required[i] != reference[i])
|
||||
return required[i] < reference[i];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function isSupported() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// A widget showing the user avatar and name
|
||||
const UserWidget = new Lang.Class({
|
||||
Name: 'UserWidget',
|
||||
|
||||
_init: function(user) {
|
||||
this._user = user;
|
||||
|
||||
this.actor = new St.BoxLayout({ style_class: 'unlock-dialog-user-name-container',
|
||||
vertical: false });
|
||||
|
||||
this._avatar = new UserMenu.UserAvatarWidget(user);
|
||||
this.actor.add(this._avatar.actor,
|
||||
{ x_fill: true, y_fill: true });
|
||||
|
||||
this._label = new St.Label({ style_class: 'login-dialog-username' });
|
||||
this.actor.add(this._label,
|
||||
{ expand: true,
|
||||
x_fill: true,
|
||||
y_fill: false,
|
||||
y_align: St.Align.MIDDLE });
|
||||
|
||||
this._userLoadedId = this._user.connect('notify::is-loaded',
|
||||
Lang.bind(this, this._updateUser));
|
||||
this._userChangedId = this._user.connect('changed',
|
||||
Lang.bind(this, this._updateUser));
|
||||
if (this._user.is_loaded)
|
||||
this._updateUser();
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this._userLoadedId != 0) {
|
||||
this._user.disconnect(this._userLoadedId);
|
||||
this._userLoadedId = 0;
|
||||
}
|
||||
|
||||
if (this._userChangedId != 0) {
|
||||
this._user.disconnect(this._userChangedId);
|
||||
this._userChangedId = 0;
|
||||
}
|
||||
|
||||
this.actor.destroy();
|
||||
},
|
||||
|
||||
_updateUser: function() {
|
||||
if (this._user.is_loaded)
|
||||
this._label.text = this._user.get_real_name();
|
||||
else
|
||||
this._label.text = '';
|
||||
|
||||
this._avatar.update();
|
||||
}
|
||||
});
|
||||
|
||||
const UnlockDialog = new Lang.Class({
|
||||
Name: 'UnlockDialog',
|
||||
Extends: ModalDialog.ModalDialog,
|
||||
@ -128,6 +47,7 @@ const UnlockDialog = new Lang.Class({
|
||||
|
||||
this._greeterClient = new Gdm.Client();
|
||||
this._userVerifier = new GdmUtil.ShellUserVerifier(this._greeterClient, { reauthenticationOnly: true });
|
||||
this._userVerified = false;
|
||||
|
||||
this._userVerifier.connect('ask-question', Lang.bind(this, this._onAskQuestion));
|
||||
this._userVerifier.connect('show-message', Lang.bind(this, this._showMessage));
|
||||
@ -138,7 +58,7 @@ const UnlockDialog = new Lang.Class({
|
||||
this._userVerifier.connect('show-login-hint', Lang.bind(this, this._showLoginHint));
|
||||
this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._hideLoginHint));
|
||||
|
||||
this._userWidget = new UserWidget(this._user);
|
||||
this._userWidget = new UserWidget.UserWidget(this._user);
|
||||
this.contentLayout.add_actor(this._userWidget.actor);
|
||||
|
||||
this._promptLayout = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout',
|
||||
@ -150,6 +70,7 @@ const UnlockDialog = new Lang.Class({
|
||||
|
||||
this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry',
|
||||
can_focus: true });
|
||||
this._promptEntry.clutter_text.connect('activate', Lang.bind(this, this._doUnlock));
|
||||
this._promptEntry.clutter_text.set_password_char('\u25cf');
|
||||
ShellEntry.addContextMenu(this._promptEntry, { isPassword: true });
|
||||
this.setInitialKeyFocus(this._promptEntry);
|
||||
@ -170,9 +91,6 @@ const UnlockDialog = new Lang.Class({
|
||||
this._promptLoginHint.hide();
|
||||
this.contentLayout.add_actor(this._promptLoginHint);
|
||||
|
||||
this._workSpinner = new Panel.AnimatedIcon('process-working.svg', LoginDialog.WORK_SPINNER_ICON_SIZE);
|
||||
this._workSpinner.actor.opacity = 0;
|
||||
|
||||
this.allowCancel = false;
|
||||
this.buttonLayout.visible = true;
|
||||
this.addButton({ label: _("Cancel"),
|
||||
@ -183,12 +101,11 @@ const UnlockDialog = new Lang.Class({
|
||||
y_fill: false,
|
||||
x_align: St.Align.START,
|
||||
y_align: St.Align.MIDDLE });
|
||||
this.buttonLayout.add(this._workSpinner.actor,
|
||||
{ expand: false,
|
||||
x_fill: false,
|
||||
y_fill: false,
|
||||
x_align: St.Align.END,
|
||||
y_align: St.Align.MIDDLE });
|
||||
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: _("Unlock"),
|
||||
action: Lang.bind(this, this._doUnlock),
|
||||
default: true },
|
||||
@ -221,15 +138,10 @@ const UnlockDialog = new Lang.Class({
|
||||
let batch = new Batch.Hold();
|
||||
this._userVerifier.begin(this._userName, batch);
|
||||
|
||||
GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, function() {
|
||||
this.emit('loaded');
|
||||
return false;
|
||||
}));
|
||||
|
||||
Main.ctrlAltTabManager.addGroup(this.dialogLayout, _("Unlock Window"), 'dialog-password-symbolic');
|
||||
|
||||
this._idleMonitor = new GnomeDesktop.IdleMonitor();
|
||||
this._idleWatchId = this._idleMonitor.add_watch(IDLE_TIMEOUT * 1000, Lang.bind(this, this._escape));
|
||||
this._idleWatchId = this._idleMonitor.add_idle_watch(IDLE_TIMEOUT * 1000, Lang.bind(this, this._escape));
|
||||
},
|
||||
|
||||
_updateSensitivity: function(sensitive) {
|
||||
@ -247,28 +159,6 @@ const UnlockDialog = new Lang.Class({
|
||||
this._okButton.can_focus = sensitive;
|
||||
},
|
||||
|
||||
_setWorking: function(working) {
|
||||
if (working) {
|
||||
this._workSpinner.play();
|
||||
Tweener.addTween(this._workSpinner.actor,
|
||||
{ opacity: 255,
|
||||
delay: LoginDialog.WORK_SPINNER_ANIMATION_DELAY,
|
||||
time: LoginDialog.WORK_SPINNER_ANIMATION_TIME,
|
||||
transition: 'linear'
|
||||
});
|
||||
} else {
|
||||
Tweener.addTween(this._workSpinner.actor,
|
||||
{ opacity: 0,
|
||||
time: LoginDialog.WORK_SPINNER_ANIMATION_TIME,
|
||||
transition: 'linear',
|
||||
onCompleteScope: this,
|
||||
onComplete: function() {
|
||||
this._workSpinner.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_showMessage: function(userVerifier, message, styleClass) {
|
||||
if (message) {
|
||||
this._promptMessage.text = message;
|
||||
@ -299,7 +189,7 @@ const UnlockDialog = new Lang.Class({
|
||||
|
||||
this._currentQuery = serviceName;
|
||||
this._updateSensitivity(true);
|
||||
this._setWorking(false);
|
||||
this.setWorking(false);
|
||||
},
|
||||
|
||||
_showLoginHint: function(verifier, message) {
|
||||
@ -318,7 +208,7 @@ const UnlockDialog = new Lang.Class({
|
||||
// the actual reply to GDM will be sent as soon as asked
|
||||
this._firstQuestionAnswer = this._promptEntry.text;
|
||||
this._updateSensitivity(false);
|
||||
this._setWorking(true);
|
||||
this.setWorking(true);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -329,30 +219,47 @@ const UnlockDialog = new Lang.Class({
|
||||
this._currentQuery = null;
|
||||
|
||||
this._updateSensitivity(false);
|
||||
this._setWorking(true);
|
||||
this.setWorking(true);
|
||||
|
||||
this._userVerifier.answerQuery(query, this._promptEntry.text);
|
||||
},
|
||||
|
||||
_onVerificationComplete: function() {
|
||||
_finishUnlock: function() {
|
||||
this._userVerifier.clear();
|
||||
this.emit('unlocked');
|
||||
},
|
||||
|
||||
_onVerificationComplete: function() {
|
||||
this._userVerified = true;
|
||||
if (!this._userVerifier.hasPendingMessages) {
|
||||
this._finishUnlock();
|
||||
} else {
|
||||
let signalId = this._userVerifier.connect('no-more-messages',
|
||||
Lang.bind(this, function() {
|
||||
this._userVerifier.disconnect(signalId);
|
||||
this._finishUnlock();
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
_onReset: function() {
|
||||
this.emit('failed');
|
||||
if (!this._userVerified) {
|
||||
this._userVerifier.clear();
|
||||
this.emit('failed');
|
||||
}
|
||||
},
|
||||
|
||||
_onVerificationFailed: function() {
|
||||
this._currentQuery = null;
|
||||
this._firstQuestion = true;
|
||||
this._userVerified = false;
|
||||
|
||||
this._promptEntry.text = '';
|
||||
this._promptEntry.clutter_text.set_password_char('\u25cf');
|
||||
this._promptEntry.menu.isPassword = true;
|
||||
|
||||
this._updateSensitivity(false);
|
||||
this._setWorking(false);
|
||||
this.setWorking(false);
|
||||
},
|
||||
|
||||
_escape: function() {
|
||||
@ -385,4 +292,8 @@ const UnlockDialog = new Lang.Class({
|
||||
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
addCharacter: function(unichar) {
|
||||
this._promptEntry.clutter_text.insert_unichar(unichar);
|
||||
},
|
||||
});
|
||||
|
@ -4,17 +4,20 @@ const AccountsService = imports.gi.AccountsService;
|
||||
const Gdm = imports.gi.Gdm;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Lang = imports.lang;
|
||||
const Pango = imports.gi.Pango;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
const Tp = imports.gi.TelepathyGLib;
|
||||
const Atk = imports.gi.Atk;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
|
||||
const BoxPointer = imports.ui.boxpointer;
|
||||
const GnomeSession = imports.misc.gnomeSession;
|
||||
const LoginManager = imports.misc.loginManager;
|
||||
const Main = imports.ui.main;
|
||||
const ModalDialog = imports.ui.modalDialog;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Params = imports.misc.params;
|
||||
@ -31,6 +34,8 @@ const SHOW_FULL_NAME_IN_TOP_BAR_KEY = 'show-full-name-in-top-bar';
|
||||
|
||||
const DIALOG_ICON_SIZE = 64;
|
||||
|
||||
const MAX_USERS_IN_SESSION_DIALOG = 5;
|
||||
|
||||
const IMStatus = {
|
||||
AVAILABLE: 0,
|
||||
BUSY: 1,
|
||||
@ -41,6 +46,17 @@ const IMStatus = {
|
||||
LAST: 6
|
||||
};
|
||||
|
||||
|
||||
const SystemdLoginSessionIface = <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 SystemdLoginSession = Gio.DBusProxy.makeProxyWrapper(SystemdLoginSessionIface);
|
||||
|
||||
// Adapted from gdm/gui/user-switch-applet/applet.c
|
||||
//
|
||||
// Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
|
||||
@ -68,7 +84,7 @@ const UserAvatarWidget = new Lang.Class({
|
||||
|
||||
update: function() {
|
||||
let iconFile = this._user.get_icon_file();
|
||||
if (!GLib.file_test(iconFile, GLib.FileTest.EXISTS))
|
||||
if (iconFile && !GLib.file_test(iconFile, GLib.FileTest.EXISTS))
|
||||
iconFile = null;
|
||||
|
||||
if (iconFile) {
|
||||
@ -584,7 +600,8 @@ const UserMenuButton = new Lang.Class({
|
||||
Lang.bind(this, this._updateHaveShutdown));
|
||||
|
||||
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
|
||||
Main.screenShield.connect('locked-changed', Lang.bind(this, this._updatePresenceIcon));
|
||||
if (Main.screenShield)
|
||||
Main.screenShield.connect('locked-changed', Lang.bind(this, this._updatePresenceIcon));
|
||||
this._sessionUpdated();
|
||||
},
|
||||
|
||||
@ -640,7 +657,7 @@ const UserMenuButton = new Lang.Class({
|
||||
|
||||
_updateLockScreen: function() {
|
||||
let allowLockScreen = !this._lockdownSettings.get_boolean(DISABLE_LOCK_SCREEN_KEY);
|
||||
this._lockScreenItem.actor.visible = allowLockScreen;
|
||||
this._lockScreenItem.actor.visible = allowLockScreen && LoginManager.canLock();
|
||||
},
|
||||
|
||||
_updateInstallUpdates: function() {
|
||||
@ -848,7 +865,8 @@ const UserMenuButton = new Lang.Class({
|
||||
_onLoginScreenActivate: function() {
|
||||
this.menu.close(BoxPointer.PopupAnimation.NONE);
|
||||
Main.overview.hide();
|
||||
Main.screenShield.lock(false);
|
||||
if (Main.screenShield)
|
||||
Main.screenShield.lock(false);
|
||||
Gdm.goto_login_session_sync(null);
|
||||
},
|
||||
|
||||
@ -864,12 +882,114 @@ const UserMenuButton = new Lang.Class({
|
||||
this._session.RebootRemote();
|
||||
},
|
||||
|
||||
_openSessionWarnDialog: function(sessions) {
|
||||
let dialog = new ModalDialog.ModalDialog();
|
||||
let subjectLabel = new St.Label({ style_class: 'end-session-dialog-subject',
|
||||
text: _("Other users are logged in.") });
|
||||
dialog.contentLayout.add(subjectLabel, { y_fill: true,
|
||||
y_align: St.Align.START });
|
||||
|
||||
let descriptionLabel = new St.Label({ style_class: 'end-session-dialog-description'});
|
||||
descriptionLabel.set_text(_("Shutting down might cause them to lose unsaved work."));
|
||||
descriptionLabel.clutter_text.line_wrap = true;
|
||||
dialog.contentLayout.add(descriptionLabel, { x_fill: true,
|
||||
y_fill: true,
|
||||
y_align: St.Align.START });
|
||||
|
||||
let scrollView = new St.ScrollView({ style_class: 'end-session-dialog-app-list' });
|
||||
scrollView.add_style_class_name('vfade');
|
||||
scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
dialog.contentLayout.add(scrollView, { x_fill: true, y_fill: true });
|
||||
|
||||
let userList = new St.BoxLayout({ vertical: true });
|
||||
scrollView.add_actor(userList);
|
||||
|
||||
for (let i = 0; i < sessions.length; i++) {
|
||||
let session = sessions[i];
|
||||
let userEntry = new St.BoxLayout({ style_class: 'login-dialog-user-list-item',
|
||||
vertical: false });
|
||||
let avatar = new UserAvatarWidget(session.user);
|
||||
avatar.update();
|
||||
userEntry.add(avatar.actor);
|
||||
|
||||
let userLabelText = "";;
|
||||
let userName = session.user.get_real_name() ?
|
||||
session.user.get_real_name() : session.username;
|
||||
|
||||
if (session.info.remote)
|
||||
/* Translators: Remote here refers to a remote session, like a ssh login */
|
||||
userLabelText = _("%s (remote)").format(userName);
|
||||
else if (session.info.type == "tty")
|
||||
/* Translators: Console here refers to a tty like a VT console */
|
||||
userLabelText = _("%s (console)").format(userName);
|
||||
else
|
||||
userLabelText = userName;
|
||||
|
||||
let textLayout = new St.BoxLayout({ style_class: 'login-dialog-user-list-item-text-box',
|
||||
vertical: true });
|
||||
textLayout.add(new St.Label({ text: userLabelText }),
|
||||
{ y_fill: false,
|
||||
y_align: St.Align.MIDDLE,
|
||||
expand: true });
|
||||
userEntry.add(textLayout, { expand: true });
|
||||
userList.add(userEntry, { x_fill: true });
|
||||
}
|
||||
|
||||
let cancelButton = { label: _("Cancel"),
|
||||
action: function() { dialog.close(); },
|
||||
key: Clutter.Escape };
|
||||
|
||||
let powerOffButton = { label: _("Power Off"), action: Lang.bind(this, function() {
|
||||
dialog.close();
|
||||
this._session.ShutdownRemote();
|
||||
}), default: true };
|
||||
|
||||
dialog.setButtons([cancelButton, powerOffButton]);
|
||||
|
||||
dialog.open();
|
||||
},
|
||||
|
||||
_onSuspendOrPowerOffActivate: function() {
|
||||
Main.overview.hide();
|
||||
|
||||
if (this._haveShutdown &&
|
||||
this._suspendOrPowerOffItem.state == PopupMenu.PopupAlternatingMenuItemState.DEFAULT) {
|
||||
this._session.ShutdownRemote();
|
||||
this._loginManager.listSessions(Lang.bind(this,
|
||||
function(result) {
|
||||
let sessions = [];
|
||||
let n = 0;
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
let[id, uid, userName, seat, sessionPath] = result[i];
|
||||
let proxy = new SystemdLoginSession(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;
|
||||
|
||||
sessions.push({ user: this._userManager.get_user(userName),
|
||||
username: userName,
|
||||
info: { type: proxy.Type,
|
||||
remote: proxy.Remote }
|
||||
});
|
||||
|
||||
// limit the number of entries
|
||||
n++;
|
||||
if (n == MAX_USERS_IN_SESSION_DIALOG)
|
||||
break;
|
||||
}
|
||||
|
||||
if (n != 0)
|
||||
this._openSessionWarnDialog(sessions);
|
||||
else
|
||||
this._session.ShutdownRemote();
|
||||
}));
|
||||
} else {
|
||||
this.menu.close(BoxPointer.PopupAnimation.NONE);
|
||||
this._loginManager.suspend();
|
||||
|
60
js/ui/userWidget.js
Normal file
60
js/ui/userWidget.js
Normal file
@ -0,0 +1,60 @@
|
||||
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
//
|
||||
// A widget showing the user avatar and name
|
||||
const AccountsService = imports.gi.AccountsService;
|
||||
const Lang = imports.lang;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const UserMenu = imports.ui.userMenu;
|
||||
|
||||
const UserWidget = new Lang.Class({
|
||||
Name: 'UserWidget',
|
||||
|
||||
_init: function(user) {
|
||||
this._user = user;
|
||||
|
||||
this.actor = new St.BoxLayout({ style_class: 'user-widget',
|
||||
vertical: false });
|
||||
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
||||
|
||||
this._avatar = new UserMenu.UserAvatarWidget(user);
|
||||
this.actor.add(this._avatar.actor,
|
||||
{ x_fill: true, y_fill: true });
|
||||
|
||||
this._label = new St.Label({ style_class: 'user-widget-label' });
|
||||
this.actor.add(this._label,
|
||||
{ expand: true,
|
||||
x_fill: true,
|
||||
y_fill: false,
|
||||
y_align: St.Align.MIDDLE });
|
||||
|
||||
this._userLoadedId = this._user.connect('notify::is-loaded',
|
||||
Lang.bind(this, this._updateUser));
|
||||
this._userChangedId = this._user.connect('changed',
|
||||
Lang.bind(this, this._updateUser));
|
||||
if (this._user.is_loaded)
|
||||
this._updateUser();
|
||||
},
|
||||
|
||||
_onDestroy: function() {
|
||||
if (this._userLoadedId != 0) {
|
||||
this._user.disconnect(this._userLoadedId);
|
||||
this._userLoadedId = 0;
|
||||
}
|
||||
|
||||
if (this._userChangedId != 0) {
|
||||
this._user.disconnect(this._userChangedId);
|
||||
this._userChangedId = 0;
|
||||
}
|
||||
},
|
||||
|
||||
_updateUser: function() {
|
||||
if (this._user.is_loaded)
|
||||
this._label.text = this._user.get_real_name();
|
||||
else
|
||||
this._label.text = '';
|
||||
|
||||
this._avatar.update();
|
||||
}
|
||||
});
|
@ -12,6 +12,7 @@ const St = imports.gi.St;
|
||||
|
||||
const AppDisplay = imports.ui.appDisplay;
|
||||
const Main = imports.ui.main;
|
||||
const OverviewControls = imports.ui.overviewControls;
|
||||
const Params = imports.misc.params;
|
||||
const RemoteSearch = imports.ui.remoteSearch;
|
||||
const Search = imports.ui.search;
|
||||
@ -23,6 +24,12 @@ const WorkspacesView = imports.ui.workspacesView;
|
||||
|
||||
const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';
|
||||
|
||||
const ViewPage = {
|
||||
WINDOWS: 1,
|
||||
APPS: 2,
|
||||
SEARCH: 3
|
||||
};
|
||||
|
||||
const FocusTrap = new Lang.Class({
|
||||
Name: 'FocusTrap',
|
||||
Extends: St.Widget,
|
||||
@ -35,6 +42,14 @@ const FocusTrap = new Lang.Class({
|
||||
}
|
||||
});
|
||||
|
||||
function getTermsForSearchString(searchString) {
|
||||
searchString = searchString.replace(/^\s+/g, '').replace(/\s+$/g, '');
|
||||
if (searchString == '')
|
||||
return [];
|
||||
|
||||
let terms = searchString.split(/\s+/);
|
||||
return terms;
|
||||
}
|
||||
|
||||
const ViewSelector = new Lang.Class({
|
||||
Name: 'ViewSelector',
|
||||
@ -42,6 +57,7 @@ const ViewSelector = new Lang.Class({
|
||||
_init : function(searchEntry, showAppsButton) {
|
||||
this.actor = new Shell.Stack({ name: 'viewSelector' });
|
||||
|
||||
this._showAppsBlocked = false;
|
||||
this._showAppsButton = showAppsButton;
|
||||
this._showAppsButton.connect('notify::checked', Lang.bind(this, this._onShowAppsButtonToggled));
|
||||
|
||||
@ -67,11 +83,10 @@ const ViewSelector = new Lang.Class({
|
||||
this._entry.connect('notify::mapped', Lang.bind(this, this._onMapped));
|
||||
global.stage.connect('notify::key-focus', Lang.bind(this, this._onStageKeyFocusChanged));
|
||||
|
||||
this._inactiveIcon = new St.Icon({ style_class: 'search-entry-icon',
|
||||
icon_name: 'edit-find-symbolic' });
|
||||
this._activeIcon = new St.Icon({ style_class: 'search-entry-icon',
|
||||
icon_name: 'edit-clear-symbolic' });
|
||||
this._entry.set_secondary_icon(this._inactiveIcon);
|
||||
this._entry.set_primary_icon(new St.Icon({ style_class: 'search-entry-icon',
|
||||
icon_name: 'edit-find-symbolic' }));
|
||||
this._clearIcon = new St.Icon({ style_class: 'search-entry-icon',
|
||||
icon_name: 'edit-clear-symbolic' });
|
||||
|
||||
this._iconClickedId = 0;
|
||||
this._capturedEventId = 0;
|
||||
@ -80,7 +95,7 @@ const ViewSelector = new Lang.Class({
|
||||
this._workspacesPage = this._addPage(this._workspacesDisplay.actor,
|
||||
_("Windows"), 'emblem-documents-symbolic');
|
||||
|
||||
this._appDisplay = new AppDisplay.AllAppDisplay();
|
||||
this._appDisplay = new AppDisplay.AppDisplay();
|
||||
this._appsPage = this._addPage(this._appDisplay.actor,
|
||||
_("Applications"), 'view-grid-symbolic');
|
||||
|
||||
@ -98,7 +113,6 @@ const ViewSelector = new Lang.Class({
|
||||
// Wanda comes obviously first
|
||||
this.addSearchProvider(new Wanda.WandaSearchProvider());
|
||||
this.addSearchProvider(new AppDisplay.AppSearchProvider());
|
||||
this.addSearchProvider(new AppDisplay.SettingsSearchProvider());
|
||||
|
||||
// Load remote search providers provided by applications
|
||||
RemoteSearch.loadRemoteSearchProviders(Lang.bind(this, this.addSearchProvider));
|
||||
@ -114,9 +128,6 @@ const ViewSelector = new Lang.Class({
|
||||
|
||||
global.focus_manager.add_group(this._searchResults.actor);
|
||||
|
||||
Main.overview.connect('item-drag-begin',
|
||||
Lang.bind(this, this._resetShowAppsButton));
|
||||
|
||||
this._stageKeyPressId = 0;
|
||||
Main.overview.connect('showing', Lang.bind(this,
|
||||
function () {
|
||||
@ -139,6 +150,14 @@ const ViewSelector = new Lang.Class({
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW,
|
||||
Lang.bind(this, this._toggleAppsPage));
|
||||
|
||||
Main.wm.addKeybinding('toggle-overview',
|
||||
new Gio.Settings({ schema: SHELL_KEYBINDINGS_SCHEMA }),
|
||||
Meta.KeyBindingFlags.NONE,
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW,
|
||||
Lang.bind(Main.overview, Main.overview.toggle));
|
||||
|
||||
},
|
||||
|
||||
_toggleAppsPage: function() {
|
||||
@ -149,6 +168,7 @@ const ViewSelector = new Lang.Class({
|
||||
show: function() {
|
||||
this._activePage = this._workspacesPage;
|
||||
|
||||
this.reset();
|
||||
this._appsPage.hide();
|
||||
this._searchPage.hide();
|
||||
this._workspacesDisplay.show();
|
||||
@ -156,7 +176,7 @@ const ViewSelector = new Lang.Class({
|
||||
if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows())
|
||||
Main.overview.fadeOutDesktop();
|
||||
|
||||
this._showPage(this._workspacesPage);
|
||||
this._showPage(this._workspacesPage, true);
|
||||
},
|
||||
|
||||
zoomFromOverview: function() {
|
||||
@ -166,6 +186,10 @@ const ViewSelector = new Lang.Class({
|
||||
Main.overview.fadeInDesktop();
|
||||
},
|
||||
|
||||
setWorkspacesFullGeometry: function(geom) {
|
||||
this._workspacesDisplay.setWorkspacesFullGeometry(geom);
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
this._workspacesDisplay.hide();
|
||||
},
|
||||
@ -192,29 +216,40 @@ const ViewSelector = new Lang.Class({
|
||||
return page;
|
||||
},
|
||||
|
||||
_showPage: function(page) {
|
||||
if(page == this._activePage)
|
||||
_fadePageIn: function(oldPage) {
|
||||
if (oldPage)
|
||||
oldPage.hide();
|
||||
|
||||
this.emit('page-empty');
|
||||
|
||||
this._activePage.show();
|
||||
Tweener.addTween(this._activePage,
|
||||
{ opacity: 255,
|
||||
time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
},
|
||||
|
||||
_showPage: function(page, noFade) {
|
||||
if (page == this._activePage)
|
||||
return;
|
||||
|
||||
if(this._activePage) {
|
||||
Tweener.addTween(this._activePage,
|
||||
let oldPage = this._activePage;
|
||||
this._activePage = page;
|
||||
this.emit('page-changed');
|
||||
|
||||
if (oldPage && !noFade)
|
||||
Tweener.addTween(oldPage,
|
||||
{ opacity: 0,
|
||||
time: 0.1,
|
||||
time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this,
|
||||
function() {
|
||||
this._activePage.hide();
|
||||
this._activePage = page;
|
||||
this._fadePageIn(oldPage);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
page.show();
|
||||
Tweener.addTween(page,
|
||||
{ opacity: 255,
|
||||
time: 0.1,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
else
|
||||
this._fadePageIn(oldPage);
|
||||
},
|
||||
|
||||
_a11yFocusPage: function(page) {
|
||||
@ -223,15 +258,19 @@ const ViewSelector = new Lang.Class({
|
||||
},
|
||||
|
||||
_onShowAppsButtonToggled: function() {
|
||||
if (this._searchActive)
|
||||
this.reset();
|
||||
else
|
||||
this._showPage(this._showAppsButton.checked ? this._appsPage
|
||||
: this._workspacesPage);
|
||||
if (this._showAppsBlocked)
|
||||
return;
|
||||
|
||||
this._showPage(this._showAppsButton.checked ?
|
||||
this._appsPage : this._workspacesPage);
|
||||
},
|
||||
|
||||
_resetShowAppsButton: function() {
|
||||
this._showAppsBlocked = true;
|
||||
this._showAppsButton.checked = false;
|
||||
this._showAppsBlocked = false;
|
||||
|
||||
this._showPage(this._workspacesPage, true);
|
||||
},
|
||||
|
||||
_onStageKeyPress: function(actor, event) {
|
||||
@ -247,12 +286,11 @@ const ViewSelector = new Lang.Class({
|
||||
if (this._searchActive)
|
||||
this.reset();
|
||||
else if (this._showAppsButton.checked)
|
||||
this._resetShowAppsButton();
|
||||
this._showAppsButton.checked = false;
|
||||
else
|
||||
Main.overview.hide();
|
||||
return true;
|
||||
} else if (Clutter.keysym_to_unicode(symbol) ||
|
||||
(symbol == Clutter.BackSpace && this._searchActive)) {
|
||||
} else if (this._shouldTriggerSearch(symbol)) {
|
||||
this.startSearch(event);
|
||||
} else if (!this._searchActive) {
|
||||
if (symbol == Clutter.Tab || symbol == Clutter.Down) {
|
||||
@ -317,6 +355,17 @@ const ViewSelector = new Lang.Class({
|
||||
}
|
||||
},
|
||||
|
||||
_shouldTriggerSearch: function(symbol) {
|
||||
let unicode = Clutter.keysym_to_unicode(symbol);
|
||||
if (unicode == 0)
|
||||
return false;
|
||||
|
||||
if (getTermsForSearchString(String.fromCharCode(unicode)).length > 0)
|
||||
return true;
|
||||
|
||||
return symbol == Clutter.BackSpace && this._searchActive;
|
||||
},
|
||||
|
||||
startSearch: function(event) {
|
||||
global.stage.set_key_focus(this._text);
|
||||
this._text.event(event, true);
|
||||
@ -328,15 +377,17 @@ const ViewSelector = new Lang.Class({
|
||||
},
|
||||
|
||||
_onTextChanged: function (se, prop) {
|
||||
let terms = getTermsForSearchString(this._entry.get_text());
|
||||
|
||||
let searchPreviouslyActive = this._searchActive;
|
||||
this._searchActive = this._entry.get_text() != '';
|
||||
this._searchActive = (terms.length > 0);
|
||||
|
||||
let startSearch = this._searchActive && !searchPreviouslyActive;
|
||||
if (startSearch)
|
||||
this._searchResults.startingSearch();
|
||||
|
||||
if (this._searchActive) {
|
||||
this._entry.set_secondary_icon(this._activeIcon);
|
||||
this._entry.set_secondary_icon(this._clearIcon);
|
||||
|
||||
if (this._iconClickedId == 0)
|
||||
this._iconClickedId = this._entry.connect('secondary-icon-clicked',
|
||||
@ -356,7 +407,7 @@ const ViewSelector = new Lang.Class({
|
||||
this._searchTimeoutId = 0;
|
||||
}
|
||||
|
||||
this._entry.set_secondary_icon(this._inactiveIcon);
|
||||
this._entry.set_secondary_icon(null);
|
||||
this._searchCancelled();
|
||||
}
|
||||
},
|
||||
@ -423,9 +474,10 @@ const ViewSelector = new Lang.Class({
|
||||
|
||||
_doSearch: function () {
|
||||
this._searchTimeoutId = 0;
|
||||
let text = this._text.get_text().replace(/^\s+/g, '').replace(/\s+$/g, '');
|
||||
this._searchResults.doSearch(text);
|
||||
|
||||
let terms = getTermsForSearchString(this._entry.get_text());
|
||||
|
||||
this._searchSystem.updateSearchResults(terms);
|
||||
this._showPage(this._searchPage);
|
||||
},
|
||||
|
||||
@ -462,6 +514,31 @@ const ViewSelector = new Lang.Class({
|
||||
removeSearchProvider: function(provider) {
|
||||
this._searchSystem.unregisterProvider(provider);
|
||||
this._searchResults.destroyProviderMeta(provider);
|
||||
},
|
||||
|
||||
getActivePage: function() {
|
||||
if (this._activePage == this._workspacesPage)
|
||||
return ViewPage.WINDOWS;
|
||||
else if (this._activePage == this._appsPage)
|
||||
return ViewPage.APPS;
|
||||
else
|
||||
return ViewPage.SEARCH;
|
||||
},
|
||||
|
||||
fadeIn: function() {
|
||||
let actor = this._activePage;
|
||||
Tweener.addTween(actor, { opacity: 255,
|
||||
time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME / 2,
|
||||
transition: 'easeInQuad'
|
||||
});
|
||||
},
|
||||
|
||||
fadeHalf: function() {
|
||||
let actor = this._activePage;
|
||||
Tweener.addTween(actor, { opacity: 128,
|
||||
time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME / 2,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(ViewSelector.prototype);
|
||||
|
@ -11,12 +11,14 @@ const Layout = imports.ui.layout;
|
||||
const Main = imports.ui.main;
|
||||
const Panel = imports.ui.panel;
|
||||
|
||||
// we could make these gsettings
|
||||
const FISH_NAME = 'wanda';
|
||||
const FISH_FILENAME = 'wanda.png';
|
||||
const FISH_SPEED = 300;
|
||||
const FISH_COMMAND = 'fortune';
|
||||
// The size of an individual frame in the animation
|
||||
const FISH_HEIGHT = 22;
|
||||
const FISH_WIDTH = 36;
|
||||
|
||||
const GNOME_PANEL_PIXMAPDIR = '../gnome-panel/fish';
|
||||
const FISH_GROUP = 'Fish Animation';
|
||||
|
||||
const MAGIC_FISH_KEY = 'free the fish';
|
||||
@ -26,33 +28,16 @@ const WandaIcon = new Lang.Class({
|
||||
Extends: IconGrid.BaseIcon,
|
||||
|
||||
_init : function(fish, label, params) {
|
||||
this._fish = fish;
|
||||
let file = GLib.build_filenamev([global.datadir, GNOME_PANEL_PIXMAPDIR, fish + '.fish']);
|
||||
|
||||
if (GLib.file_test(file, GLib.FileTest.EXISTS)) {
|
||||
this._keyfile = new GLib.KeyFile();
|
||||
this._keyfile.load_from_file(file, GLib.KeyFileFlags.NONE);
|
||||
|
||||
this._imageFile = GLib.build_filenamev([global.datadir, GNOME_PANEL_PIXMAPDIR,
|
||||
this._keyfile.get_string(FISH_GROUP, 'image')]);
|
||||
|
||||
let tmpPixbuf = GdkPixbuf.Pixbuf.new_from_file(this._imageFile);
|
||||
|
||||
this._imgHeight = tmpPixbuf.height;
|
||||
this._imgWidth = tmpPixbuf.width / this._keyfile.get_integer(FISH_GROUP, 'frames');
|
||||
} else {
|
||||
this._imageFile = null;
|
||||
}
|
||||
|
||||
this.parent(label, params);
|
||||
|
||||
this._fish = fish;
|
||||
this._imageFile = GLib.build_filenamev([global.datadir, fish + '.png']);
|
||||
|
||||
this._imgHeight = FISH_HEIGHT;
|
||||
this._imgWidth = FISH_WIDTH;
|
||||
},
|
||||
|
||||
createIcon: function(iconSize) {
|
||||
if (!this._imageFile) {
|
||||
return new St.Icon({ icon_name: 'face-smile',
|
||||
icon_size: iconSize });
|
||||
}
|
||||
|
||||
this._animations = new Panel.Animation(this._imageFile, this._imgWidth, this._imgHeight, FISH_SPEED);
|
||||
this._animations.play();
|
||||
return this._animations.actor;
|
||||
|
@ -4,6 +4,7 @@ const Clutter = imports.gi.Clutter;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
@ -66,6 +67,209 @@ function getWindowDimmer(actor) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
const WorkspaceTracker = new Lang.Class({
|
||||
Name: 'WorkspaceTracker',
|
||||
|
||||
_init: function(wm) {
|
||||
this._wm = wm;
|
||||
|
||||
this._workspaces = [];
|
||||
this._checkWorkspacesId = 0;
|
||||
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
tracker.connect('startup-sequence-changed', Lang.bind(this, this._queueCheckWorkspaces));
|
||||
|
||||
global.screen.connect('notify::n-workspaces', Lang.bind(this, this._nWorkspacesChanged));
|
||||
|
||||
global.screen.connect('window-entered-monitor', Lang.bind(this, this._windowEnteredMonitor));
|
||||
global.screen.connect('window-left-monitor', Lang.bind(this, this._windowLeftMonitor));
|
||||
global.screen.connect('restacked', Lang.bind(this, this._windowsRestacked));
|
||||
|
||||
this._overrideSettings = new Gio.Settings({ schema: 'org.gnome.shell.overrides' });
|
||||
this._overrideSettings.connect('changed::dynamic-workspaces', Lang.bind(this, this._queueCheckWorkspaces));
|
||||
|
||||
this._nWorkspacesChanged();
|
||||
},
|
||||
|
||||
_checkWorkspaces: function() {
|
||||
let i;
|
||||
let emptyWorkspaces = [];
|
||||
|
||||
if (!Meta.prefs_get_dynamic_workspaces()) {
|
||||
this._checkWorkspacesId = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = 0; i < this._workspaces.length; i++) {
|
||||
let lastRemoved = this._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)) ||
|
||||
this._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
|
||||
this._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(this._workspaces[i], global.get_current_time());
|
||||
}
|
||||
|
||||
if (removingCurrentWorkspace) {
|
||||
global.screen.get_workspace_by_index(global.screen.n_workspaces - 1).activate(global.get_current_time());
|
||||
this._wm.unblockAnimations();
|
||||
|
||||
if (!Main.overview.visible && showOverview)
|
||||
Main.overview.show();
|
||||
}
|
||||
|
||||
this._checkWorkspacesId = 0;
|
||||
return false;
|
||||
},
|
||||
|
||||
keepWorkspaceAlive: function(workspace, duration) {
|
||||
if (workspace._keepAliveId)
|
||||
Mainloop.source_remove(workspace._keepAliveId);
|
||||
|
||||
workspace._keepAliveId = Mainloop.timeout_add(duration, Lang.bind(this, function() {
|
||||
workspace._keepAliveId = 0;
|
||||
this._queueCheckWorkspaces();
|
||||
return false;
|
||||
}));
|
||||
},
|
||||
|
||||
_windowRemoved: function(workspace, window) {
|
||||
workspace._lastRemovedWindow = window;
|
||||
this._queueCheckWorkspaces();
|
||||
Mainloop.timeout_add(LAST_WINDOW_GRACE_TIME, Lang.bind(this, function() {
|
||||
if (workspace._lastRemovedWindow == window) {
|
||||
workspace._lastRemovedWindow = null;
|
||||
this._queueCheckWorkspaces();
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
},
|
||||
|
||||
_windowLeftMonitor: function(metaScreen, monitorIndex, metaWin) {
|
||||
// If the window left the primary monitor, that
|
||||
// might make that workspace empty
|
||||
if (monitorIndex == Main.layoutManager.primaryIndex)
|
||||
this._queueCheckWorkspaces();
|
||||
},
|
||||
|
||||
_windowEnteredMonitor: function(metaScreen, monitorIndex, metaWin) {
|
||||
// If the window entered the primary monitor, that
|
||||
// might make that workspace non-empty
|
||||
if (monitorIndex == Main.layoutManager.primaryIndex)
|
||||
this._queueCheckWorkspaces();
|
||||
},
|
||||
|
||||
_windowsRestacked: function() {
|
||||
// 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();
|
||||
},
|
||||
|
||||
_queueCheckWorkspaces: function() {
|
||||
if (this._checkWorkspacesId == 0)
|
||||
this._checkWorkspacesId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, this._checkWorkspaces));
|
||||
},
|
||||
|
||||
_nWorkspacesChanged: function() {
|
||||
let oldNumWorkspaces = this._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++)
|
||||
this._workspaces[w] = global.screen.get_workspace_by_index(w);
|
||||
|
||||
for (w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
|
||||
let workspace = this._workspaces[w];
|
||||
workspace._windowAddedId = workspace.connect('window-added', Lang.bind(this, this._queueCheckWorkspaces));
|
||||
workspace._windowRemovedId = workspace.connect('window-removed', Lang.bind(this, this._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 (this._workspaces[w] != workspace) {
|
||||
removedIndex = w;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let lostWorkspaces = this._workspaces.splice(removedIndex, removedNum);
|
||||
lostWorkspaces.forEach(function(workspace) {
|
||||
workspace.disconnect(workspace._windowAddedId);
|
||||
workspace.disconnect(workspace._windowRemovedId);
|
||||
});
|
||||
}
|
||||
|
||||
this._queueCheckWorkspaces();
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
const WindowManager = new Lang.Class({
|
||||
Name: 'WindowManager',
|
||||
|
||||
@ -136,6 +340,90 @@ const WindowManager = new Lang.Class({
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('switch-to-workspace-1',
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('switch-to-workspace-2',
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('switch-to-workspace-3',
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('switch-to-workspace-4',
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('switch-to-workspace-5',
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('switch-to-workspace-6',
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('switch-to-workspace-7',
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('switch-to-workspace-8',
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('switch-to-workspace-9',
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('switch-to-workspace-10',
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('switch-to-workspace-11',
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('switch-to-workspace-12',
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.OVERVIEW,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('move-to-workspace-1',
|
||||
Shell.KeyBindingMode.NORMAL,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('move-to-workspace-2',
|
||||
Shell.KeyBindingMode.NORMAL,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('move-to-workspace-3',
|
||||
Shell.KeyBindingMode.NORMAL,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('move-to-workspace-4',
|
||||
Shell.KeyBindingMode.NORMAL,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('move-to-workspace-5',
|
||||
Shell.KeyBindingMode.NORMAL,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('move-to-workspace-6',
|
||||
Shell.KeyBindingMode.NORMAL,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('move-to-workspace-7',
|
||||
Shell.KeyBindingMode.NORMAL,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('move-to-workspace-8',
|
||||
Shell.KeyBindingMode.NORMAL,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('move-to-workspace-9',
|
||||
Shell.KeyBindingMode.NORMAL,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('move-to-workspace-10',
|
||||
Shell.KeyBindingMode.NORMAL,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('move-to-workspace-11',
|
||||
Shell.KeyBindingMode.NORMAL,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('move-to-workspace-12',
|
||||
Shell.KeyBindingMode.NORMAL,
|
||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||
this.setCustomKeybindingHandler('switch-applications',
|
||||
Shell.KeyBindingMode.NORMAL,
|
||||
Lang.bind(this, this._startAppSwitcher));
|
||||
@ -172,8 +460,9 @@ const WindowManager = new Lang.Class({
|
||||
this.addKeybinding('open-application-menu',
|
||||
new Gio.Settings({ schema: SHELL_KEYBINDINGS_SCHEMA }),
|
||||
Meta.KeyBindingFlags.NONE,
|
||||
Shell.KeyBindingMode.NORMAL,
|
||||
Lang.bind(this, this._openAppMenu));
|
||||
Shell.KeyBindingMode.NORMAL |
|
||||
Shell.KeyBindingMode.TOPBAR_POPUP,
|
||||
Lang.bind(this, this._toggleAppMenu));
|
||||
|
||||
Main.overview.connect('showing', Lang.bind(this, function() {
|
||||
for (let i = 0; i < this._dimmedWindows.length; i++)
|
||||
@ -183,6 +472,11 @@ const WindowManager = new Lang.Class({
|
||||
for (let i = 0; i < this._dimmedWindows.length; i++)
|
||||
this._dimWindow(this._dimmedWindows[i]);
|
||||
}));
|
||||
|
||||
this._workspaceTracker = new WorkspaceTracker(this);
|
||||
|
||||
global.screen.override_workspace_layout(Meta.ScreenCorner.TOPLEFT,
|
||||
false, -1, 1);
|
||||
},
|
||||
|
||||
setCustomKeybindingHandler: function(name, modes, handler) {
|
||||
@ -245,29 +539,9 @@ const WindowManager = new Lang.Class({
|
||||
|
||||
this._minimizing.push(actor);
|
||||
|
||||
let xDest, yDest, xScale, yScale;
|
||||
let [success, geom] = actor.meta_window.get_icon_geometry();
|
||||
if (success) {
|
||||
xDest = geom.x;
|
||||
yDest = geom.y;
|
||||
xScale = geom.width / actor.width;
|
||||
yScale = geom.height / actor.height;
|
||||
} else {
|
||||
/* scale window down to 0x0. */
|
||||
let monitor = Main.layoutManager.findMonitorForWindow(actor.meta_window);
|
||||
xDest = monitor.x;
|
||||
yDest = monitor.y;
|
||||
xScale = 0.0;
|
||||
yScale = 0.0;
|
||||
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
|
||||
xDest += monitor.width;
|
||||
}
|
||||
|
||||
Tweener.addTween(actor,
|
||||
{ scale_x: xScale,
|
||||
scale_y: yScale,
|
||||
x: xDest,
|
||||
y: yDest,
|
||||
if (actor.meta_window.is_monitor_sized()) {
|
||||
Tweener.addTween(actor,
|
||||
{ opacity: 0,
|
||||
time: WINDOW_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: this._minimizeWindowDone,
|
||||
@ -277,12 +551,46 @@ const WindowManager = new Lang.Class({
|
||||
onOverwriteScope: this,
|
||||
onOverwriteParams: [shellwm, actor]
|
||||
});
|
||||
} else {
|
||||
let xDest, yDest, xScale, yScale;
|
||||
let [success, geom] = actor.meta_window.get_icon_geometry();
|
||||
if (success) {
|
||||
xDest = geom.x;
|
||||
yDest = geom.y;
|
||||
xScale = geom.width / actor.width;
|
||||
yScale = geom.height / actor.height;
|
||||
} else {
|
||||
let monitor = Main.layoutManager.monitors[actor.meta_window.get_monitor()];
|
||||
xDest = monitor.x;
|
||||
yDest = monitor.y;
|
||||
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
|
||||
xDest += monitor.width;
|
||||
xScale = 0;
|
||||
yScale = 0;
|
||||
}
|
||||
|
||||
Tweener.addTween(actor,
|
||||
{ scale_x: xScale,
|
||||
scale_y: yScale,
|
||||
x: xDest,
|
||||
y: yDest,
|
||||
time: WINDOW_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: this._minimizeWindowDone,
|
||||
onCompleteScope: this,
|
||||
onCompleteParams: [shellwm, actor],
|
||||
onOverwrite: this._minimizeWindowOverwritten,
|
||||
onOverwriteScope: this,
|
||||
onOverwriteParams: [shellwm, actor]
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_minimizeWindowDone : function(shellwm, actor) {
|
||||
if (this._removeEffect(this._minimizing, actor)) {
|
||||
Tweener.removeTweens(actor);
|
||||
actor.set_scale(1.0, 1.0);
|
||||
actor.set_opacity(255);
|
||||
actor.move_anchor_point_from_gravity(Clutter.Gravity.NORTH_WEST);
|
||||
|
||||
shellwm.completed_minimize(actor);
|
||||
@ -533,11 +841,11 @@ const WindowManager = new Lang.Class({
|
||||
if (direction == Meta.MotionDirection.UP ||
|
||||
direction == Meta.MotionDirection.UP_LEFT ||
|
||||
direction == Meta.MotionDirection.UP_RIGHT)
|
||||
yDest = global.screen_height;
|
||||
yDest = global.screen_height - Main.panel.actor.height;
|
||||
else if (direction == Meta.MotionDirection.DOWN ||
|
||||
direction == Meta.MotionDirection.DOWN_LEFT ||
|
||||
direction == Meta.MotionDirection.DOWN_RIGHT)
|
||||
yDest = -global.screen_height;
|
||||
yDest = -global.screen_height + Main.panel.actor.height;
|
||||
|
||||
if (direction == Meta.MotionDirection.LEFT ||
|
||||
direction == Meta.MotionDirection.UP_LEFT ||
|
||||
@ -550,9 +858,9 @@ const WindowManager = new Lang.Class({
|
||||
|
||||
let switchData = {};
|
||||
this._switchData = switchData;
|
||||
switchData.inGroup = new Clutter.Group();
|
||||
switchData.outGroup = new Clutter.Group();
|
||||
switchData.movingWindowBin = new Clutter.Group();
|
||||
switchData.inGroup = new Clutter.Actor();
|
||||
switchData.outGroup = new Clutter.Actor();
|
||||
switchData.movingWindowBin = new Clutter.Actor();
|
||||
switchData.windows = [];
|
||||
|
||||
let wgroup = global.window_group;
|
||||
@ -579,7 +887,7 @@ const WindowManager = new Lang.Class({
|
||||
switchData.windows.push({ window: window,
|
||||
parent: window.get_parent() });
|
||||
window.reparent(switchData.inGroup);
|
||||
window.show_all();
|
||||
window.show();
|
||||
}
|
||||
}
|
||||
|
||||
@ -665,27 +973,39 @@ const WindowManager = new Lang.Class({
|
||||
Main.ctrlAltTabManager.popup(backwards, binding.get_name(), binding.get_mask());
|
||||
},
|
||||
|
||||
_openAppMenu : function(display, screen, window, event, binding) {
|
||||
Main.panel.openAppMenu();
|
||||
_toggleAppMenu : function(display, screen, window, event, binding) {
|
||||
Main.panel.toggleAppMenu();
|
||||
},
|
||||
|
||||
_showWorkspaceSwitcher : function(display, screen, window, binding) {
|
||||
if (screen.n_workspaces == 1)
|
||||
return;
|
||||
|
||||
let [action,,,direction] = binding.get_name().split('-');
|
||||
let direction = Meta.MotionDirection[direction.toUpperCase()];
|
||||
let [action,,,target] = binding.get_name().split('-');
|
||||
let newWs;
|
||||
let direction;
|
||||
|
||||
if (isNaN(target)) {
|
||||
direction = Meta.MotionDirection[target.toUpperCase()];
|
||||
newWs = screen.get_active_workspace().get_neighbor(direction);
|
||||
} else if (target > 0) {
|
||||
target--;
|
||||
newWs = screen.get_workspace_by_index(target);
|
||||
|
||||
if (screen.get_active_workspace().index() > target)
|
||||
direction = Meta.MotionDirection.UP;
|
||||
else
|
||||
direction = Meta.MotionDirection.DOWN;
|
||||
}
|
||||
|
||||
if (direction != Meta.MotionDirection.UP &&
|
||||
direction != Meta.MotionDirection.DOWN)
|
||||
return;
|
||||
|
||||
if (action == 'switch')
|
||||
newWs = this.actionMoveWorkspace(direction);
|
||||
this.actionMoveWorkspace(newWs);
|
||||
else
|
||||
newWs = this.actionMoveWindow(window, direction);
|
||||
this.actionMoveWindow(window, newWs);
|
||||
|
||||
if (!Main.overview.visible) {
|
||||
if (this._workspaceSwitcherPopup == null) {
|
||||
@ -698,31 +1018,27 @@ const WindowManager = new Lang.Class({
|
||||
}
|
||||
},
|
||||
|
||||
actionMoveWorkspace: function(direction) {
|
||||
actionMoveWorkspace: function(workspace) {
|
||||
let activeWorkspace = global.screen.get_active_workspace();
|
||||
let toActivate = activeWorkspace.get_neighbor(direction);
|
||||
|
||||
if (activeWorkspace != toActivate)
|
||||
toActivate.activate(global.get_current_time());
|
||||
if (activeWorkspace != workspace)
|
||||
workspace.activate(global.get_current_time());
|
||||
|
||||
return toActivate;
|
||||
},
|
||||
|
||||
actionMoveWindow: function(window, direction) {
|
||||
actionMoveWindow: function(window, workspace) {
|
||||
let activeWorkspace = global.screen.get_active_workspace();
|
||||
let toActivate = activeWorkspace.get_neighbor(direction);
|
||||
|
||||
if (activeWorkspace != toActivate) {
|
||||
if (activeWorkspace != workspace) {
|
||||
// This won't have any effect for "always sticky" windows
|
||||
// (like desktop windows or docks)
|
||||
|
||||
this._movingWindow = window;
|
||||
window.change_workspace(toActivate);
|
||||
window.change_workspace(workspace);
|
||||
|
||||
global.display.clear_mouse_mode();
|
||||
toActivate.activate_with_focus (window, global.get_current_time());
|
||||
workspace.activate_with_focus (window, global.get_current_time());
|
||||
}
|
||||
|
||||
return toActivate;
|
||||
},
|
||||
});
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -55,10 +55,9 @@ const WorkspaceSwitcherPopup = new Lang.Class({
|
||||
|
||||
_getPreferredHeight : function (actor, forWidth, alloc) {
|
||||
let children = this._list.get_children();
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
|
||||
|
||||
let availHeight = primary.height;
|
||||
availHeight -= Main.panel.actor.height;
|
||||
let availHeight = workArea.height;
|
||||
availHeight -= this.actor.get_theme_node().get_vertical_padding();
|
||||
availHeight -= this._container.get_theme_node().get_vertical_padding();
|
||||
availHeight -= this._list.get_theme_node().get_vertical_padding();
|
||||
@ -67,7 +66,7 @@ const WorkspaceSwitcherPopup = new Lang.Class({
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let [childMinHeight, childNaturalHeight] = children[i].get_preferred_height(-1);
|
||||
let [childMinWidth, childNaturalWidth] = children[i].get_preferred_width(childNaturalHeight);
|
||||
height += childNaturalHeight * primary.width / primary.height;
|
||||
height += childNaturalHeight * workArea.width / workArea.height;
|
||||
}
|
||||
|
||||
let spacing = this._itemSpacing * (global.screen.n_workspaces - 1);
|
||||
@ -81,8 +80,8 @@ const WorkspaceSwitcherPopup = new Lang.Class({
|
||||
},
|
||||
|
||||
_getPreferredWidth : function (actor, forHeight, alloc) {
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
this._childWidth = Math.round(this._childHeight * primary.width / primary.height);
|
||||
let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
|
||||
this._childWidth = Math.round(this._childHeight * workArea.width / workArea.height);
|
||||
|
||||
alloc.min_size = this._childWidth;
|
||||
alloc.natural_size = this._childWidth;
|
||||
@ -122,12 +121,11 @@ const WorkspaceSwitcherPopup = new Lang.Class({
|
||||
|
||||
}
|
||||
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
|
||||
let [containerMinHeight, containerNatHeight] = this._container.get_preferred_height(global.screen_width);
|
||||
let [containerMinWidth, containerNatWidth] = this._container.get_preferred_width(containerNatHeight);
|
||||
this._container.x = primary.x + Math.floor((primary.width - containerNatWidth) / 2);
|
||||
this._container.y = primary.y + Main.panel.actor.height +
|
||||
Math.floor(((primary.height - Main.panel.actor.height) - containerNatHeight) / 2);
|
||||
this._container.x = workArea.x + Math.floor((workArea.width - containerNatWidth) / 2);
|
||||
this._container.y = workArea.y + Math.floor((workArea.height - containerNatHeight) / 2);
|
||||
},
|
||||
|
||||
_show : function() {
|
||||
|
@ -9,6 +9,7 @@ const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Background = imports.ui.background;
|
||||
const DND = imports.ui.dnd;
|
||||
const Main = imports.ui.main;
|
||||
const Tweener = imports.ui.tweener;
|
||||
@ -61,12 +62,35 @@ const WindowClone = new Lang.Class({
|
||||
this.inDrag = false;
|
||||
},
|
||||
|
||||
// Find the actor just below us, respecting reparenting done
|
||||
// by DND code
|
||||
getActualStackAbove: function() {
|
||||
if (this._stackAbove == null)
|
||||
return null;
|
||||
|
||||
if (this.inDrag) {
|
||||
if (this._stackAbove._delegate)
|
||||
return this._stackAbove._delegate.getActualStackAbove();
|
||||
else
|
||||
return null;
|
||||
} else {
|
||||
return this._stackAbove;
|
||||
}
|
||||
},
|
||||
|
||||
setStackAbove: function (actor) {
|
||||
this._stackAbove = actor;
|
||||
if (this._stackAbove == null)
|
||||
|
||||
// Don't apply the new stacking now, it will be applied
|
||||
// when dragging ends and window are stacked again
|
||||
if (actor.inDrag)
|
||||
return;
|
||||
|
||||
let actualAbove = this.getActualStackAbove();
|
||||
if (actualAbove == null)
|
||||
this.actor.lower_bottom();
|
||||
else
|
||||
this.actor.raise(this._stackAbove);
|
||||
this.actor.raise(actualAbove);
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
@ -165,18 +189,20 @@ const WorkspaceThumbnail = new Lang.Class({
|
||||
style_class: 'workspace-thumbnail' });
|
||||
this.actor._delegate = this;
|
||||
|
||||
this._contents = new Clutter.Group();
|
||||
this.actor.add_actor(this._contents);
|
||||
this._contents = new Clutter.Actor();
|
||||
this.actor.add_child(this._contents);
|
||||
|
||||
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
||||
|
||||
this._background = Meta.BackgroundActor.new_for_screen(global.screen);
|
||||
this._contents.add_actor(this._background);
|
||||
this._createBackground();
|
||||
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
this.setPorthole(monitor.x, monitor.y, monitor.width, monitor.height);
|
||||
|
||||
let windows = global.get_window_actors().filter(this._isWorkspaceWindow, this);
|
||||
let windows = global.get_window_actors().filter(Lang.bind(this, function(actor) {
|
||||
let win = actor.meta_window;
|
||||
return win.located_on_workspace(metaWorkspace);
|
||||
}));
|
||||
|
||||
// Create clones for windows that should be visible in the Overview
|
||||
this._windows = [];
|
||||
@ -210,6 +236,12 @@ const WorkspaceThumbnail = new Lang.Class({
|
||||
this._collapseFraction = 0; // Not collapsed
|
||||
},
|
||||
|
||||
_createBackground: function() {
|
||||
this._bgManager = new Background.BackgroundManager({ monitorIndex: Main.layoutManager.primaryIndex,
|
||||
container: this._contents,
|
||||
effects: Meta.BackgroundEffects.NONE });
|
||||
},
|
||||
|
||||
setPorthole: function(x, y, width, height) {
|
||||
this._portholeX = x;
|
||||
this._portholeY = y;
|
||||
@ -233,7 +265,7 @@ const WorkspaceThumbnail = new Lang.Class({
|
||||
let clone = this._windows[i];
|
||||
let metaWindow = clone.metaWindow;
|
||||
if (i == 0) {
|
||||
clone.setStackAbove(this._background);
|
||||
clone.setStackAbove(this._bgManager.background.actor);
|
||||
} else {
|
||||
let previousClone = this._windows[i - 1];
|
||||
clone.setStackAbove(previousClone.actor);
|
||||
@ -352,7 +384,8 @@ const WorkspaceThumbnail = new Lang.Class({
|
||||
},
|
||||
|
||||
destroy : function() {
|
||||
this.actor.destroy();
|
||||
if (this.actor)
|
||||
this.actor.destroy();
|
||||
},
|
||||
|
||||
workspaceRemoved : function() {
|
||||
@ -373,19 +406,20 @@ const WorkspaceThumbnail = new Lang.Class({
|
||||
_onDestroy: function(actor) {
|
||||
this.workspaceRemoved();
|
||||
|
||||
if (this._bgManager) {
|
||||
this._bgManager.destroy();
|
||||
this._bgManager = null;
|
||||
}
|
||||
|
||||
this._windows = [];
|
||||
this.actor = null;
|
||||
},
|
||||
|
||||
// Tests if @win belongs to this workspace
|
||||
_isWorkspaceWindow : function (win) {
|
||||
return Main.isWindowActorDisplayedOnWorkspace(win, this.metaWorkspace.index());
|
||||
},
|
||||
|
||||
// Tests if @win belongs to this workspace and monitor
|
||||
_isMyWindow : function (win) {
|
||||
return this._isWorkspaceWindow(win) &&
|
||||
(!win.get_meta_window() || win.get_meta_window().get_monitor() == this.monitorIndex);
|
||||
// Tests if @actor belongs to this workspace and monitor
|
||||
_isMyWindow : function (actor) {
|
||||
let win = actor.meta_window;
|
||||
return win.located_on_workspace(this.metaWorkspace) &&
|
||||
(win.get_monitor() == this.monitorIndex);
|
||||
},
|
||||
|
||||
// Tests if @win should be shown in the Overview
|
||||
@ -418,7 +452,7 @@ const WorkspaceThumbnail = new Lang.Class({
|
||||
this._contents.add_actor(clone.actor);
|
||||
|
||||
if (this._windows.length == 0)
|
||||
clone.setStackAbove(this._background);
|
||||
clone.setStackAbove(this._bgManager.background.actor);
|
||||
else
|
||||
clone.setStackAbove(this._windows[this._windows.length - 1].actor);
|
||||
|
||||
@ -528,6 +562,7 @@ const ThumbnailsBox = new Lang.Class({
|
||||
this._dropPlaceholderPos = -1;
|
||||
this._dropPlaceholder = new St.Bin({ style_class: 'placeholder' });
|
||||
this.actor.add_actor(this._dropPlaceholder);
|
||||
this._spliceIndex = -1;
|
||||
|
||||
this._targetScale = 0;
|
||||
this._scale = 0;
|
||||
@ -544,7 +579,6 @@ const ThumbnailsBox = new Lang.Class({
|
||||
|
||||
this.actor.connect('button-press-event', function() { return true; });
|
||||
this.actor.connect('button-release-event', Lang.bind(this, this._onButtonRelease));
|
||||
this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
|
||||
|
||||
Main.overview.connect('showing',
|
||||
Lang.bind(this, this._createThumbnails));
|
||||
@ -583,7 +617,7 @@ const ThumbnailsBox = new Lang.Class({
|
||||
let thumbnail = this._thumbnails[i]
|
||||
let [w, h] = thumbnail.actor.get_transformed_size();
|
||||
if (y >= thumbnail.actor.y && y <= thumbnail.actor.y + h) {
|
||||
thumbnail.activate(event.time);
|
||||
thumbnail.activate(event.get_time());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -711,6 +745,8 @@ const ThumbnailsBox = new Lang.Class({
|
||||
return win.get_workspace() >= newWorkspaceIndex;
|
||||
});
|
||||
|
||||
this._spliceIndex = newWorkspaceIndex;
|
||||
|
||||
// ... move them down one.
|
||||
windows.forEach(function(win) {
|
||||
win.meta_window.change_workspace_by_index(win.get_workspace() + 1,
|
||||
@ -732,6 +768,14 @@ const ThumbnailsBox = new Lang.Class({
|
||||
WORKSPACE_KEEP_ALIVE_TIME);
|
||||
}
|
||||
|
||||
// Start the animation on the workspace (which is actually
|
||||
// an old one which just became empty)
|
||||
let thumbnail = this._thumbnails[newWorkspaceIndex];
|
||||
this._setThumbnailState(thumbnail, ThumbnailState.NEW);
|
||||
thumbnail.slidePosition = 1;
|
||||
|
||||
this._queueUpdateStates();
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@ -759,14 +803,7 @@ const ThumbnailsBox = new Lang.Class({
|
||||
this._stateCounts[ThumbnailState[key]] = 0;
|
||||
|
||||
// The "porthole" is the portion of the screen that we show in the workspaces
|
||||
let panelHeight = Main.panel.actor.height;
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
this._porthole = {
|
||||
x: monitor.x,
|
||||
y: monitor.y + panelHeight,
|
||||
width: monitor.width,
|
||||
height: monitor.height - panelHeight
|
||||
};
|
||||
this._porthole = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
|
||||
|
||||
this.addThumbnails(0, global.screen.n_workspaces);
|
||||
|
||||
@ -826,7 +863,8 @@ const ThumbnailsBox = new Lang.Class({
|
||||
this._thumbnails.push(thumbnail);
|
||||
this.actor.add_actor(thumbnail.actor);
|
||||
|
||||
if (start > 0) { // not the initial fill
|
||||
if (start > 0 && this._spliceIndex == -1) {
|
||||
// not the initial fill, and not splicing via DND
|
||||
thumbnail.state = ThumbnailState.NEW;
|
||||
thumbnail.slidePosition = 1; // start slid out
|
||||
this._haveNewThumbnails = true;
|
||||
@ -841,6 +879,9 @@ const ThumbnailsBox = new Lang.Class({
|
||||
|
||||
// The thumbnails indicator actually needs to be on top of the thumbnails
|
||||
this._indicator.raise_top();
|
||||
|
||||
// Clear the splice index, we got the message
|
||||
this._spliceIndex = -1;
|
||||
},
|
||||
|
||||
removeThumbnails: function(start, count) {
|
||||
@ -1000,8 +1041,6 @@ const ThumbnailsBox = new Lang.Class({
|
||||
// See comment about this._background in _init()
|
||||
let themeNode = this._background.get_theme_node();
|
||||
|
||||
forWidth = themeNode.adjust_for_width(forWidth);
|
||||
|
||||
// Note that for getPreferredWidth/Height we cheat a bit and skip propagating
|
||||
// the size request to our children because we know how big they are and know
|
||||
// that the actors aren't depending on the virtual functions being called.
|
||||
@ -1217,16 +1256,5 @@ const ThumbnailsBox = new Lang.Class({
|
||||
},
|
||||
onCompleteScope: this
|
||||
});
|
||||
},
|
||||
|
||||
_onScrollEvent: function (actor, event) {
|
||||
switch (event.get_scroll_direction()) {
|
||||
case Clutter.ScrollDirection.UP:
|
||||
Main.wm.actionMoveWorkspace(Meta.MotionDirection.UP);
|
||||
break;
|
||||
case Clutter.ScrollDirection.DOWN:
|
||||
Main.wm.actionMoveWorkspace(Meta.MotionDirection.DOWN);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -23,14 +23,25 @@ const MAX_WORKSPACES = 16;
|
||||
|
||||
const OVERRIDE_SCHEMA = 'org.gnome.shell.overrides';
|
||||
|
||||
const CONTROLS_POP_IN_TIME = 0.1;
|
||||
function rectEqual(one, two) {
|
||||
if (one == two)
|
||||
return true;
|
||||
|
||||
if (!one || !two)
|
||||
return false;
|
||||
|
||||
return (one.x == two.x &&
|
||||
one.y == two.y &&
|
||||
one.width == two.width &&
|
||||
one.height == two.height);
|
||||
}
|
||||
|
||||
const WorkspacesView = new Lang.Class({
|
||||
Name: 'WorkspacesView',
|
||||
|
||||
_init: function(workspaces) {
|
||||
this.actor = new St.Widget({ style_class: 'workspaces-view' });
|
||||
this.actor = new St.Widget({ style_class: 'workspaces-view',
|
||||
reactive: true });
|
||||
|
||||
// The actor itself isn't a drop target, so we don't want to pick on its area
|
||||
this.actor.set_size(0, 0);
|
||||
@ -44,14 +55,9 @@ const WorkspacesView = new Lang.Class({
|
||||
this._updateWorkspaceActors(false);
|
||||
}));
|
||||
|
||||
this._width = 0;
|
||||
this._height = 0;
|
||||
this._x = 0;
|
||||
this._y = 0;
|
||||
this._clipX = 0;
|
||||
this._clipY = 0;
|
||||
this._clipWidth = 0;
|
||||
this._clipHeight = 0;
|
||||
this._fullGeometry = null;
|
||||
this._actualGeometry = null;
|
||||
|
||||
this._spacing = 0;
|
||||
this._animating = false; // tweening
|
||||
this._scrolling = false; // swipe-scrolling
|
||||
@ -90,8 +96,8 @@ const WorkspacesView = new Lang.Class({
|
||||
this._overviewShownId =
|
||||
Main.overview.connect('shown',
|
||||
Lang.bind(this, function() {
|
||||
this.actor.set_clip(this._clipX, this._clipY,
|
||||
this._clipWidth, this._clipHeight);
|
||||
this.actor.set_clip(this._fullGeometry.x, this._fullGeometry.y,
|
||||
this._fullGeometry.width, this._fullGeometry.height);
|
||||
}));
|
||||
|
||||
this.scrollAdjustment = new St.Adjustment({ value: activeWorkspaceIndex,
|
||||
@ -129,11 +135,9 @@ const WorkspacesView = new Lang.Class({
|
||||
continue;
|
||||
|
||||
let ws = new Workspace.Workspace(null, i);
|
||||
ws.setGeometry(monitors[i].x,
|
||||
monitors[i].y,
|
||||
monitors[i].width,
|
||||
monitors[i].height);
|
||||
global.overlay_group.add_actor(ws.actor);
|
||||
ws.setFullGeometry(monitors[i]);
|
||||
ws.setActualGeometry(monitors[i]);
|
||||
Main.layoutManager.overviewGroup.add_actor(ws.actor);
|
||||
this._extraWorkspaces.push(ws);
|
||||
}
|
||||
},
|
||||
@ -144,25 +148,24 @@ const WorkspacesView = new Lang.Class({
|
||||
this._extraWorkspaces = [];
|
||||
},
|
||||
|
||||
setGeometry: function(x, y, width, height) {
|
||||
if (this._x == x && this._y == y &&
|
||||
this._width == width && this._height == height)
|
||||
return;
|
||||
setFullGeometry: function(geom) {
|
||||
if (rectEqual(this._fullGeometry, geom))
|
||||
return;
|
||||
|
||||
this._width = width;
|
||||
this._height = height;
|
||||
this._x = x;
|
||||
this._y = y;
|
||||
this._fullGeometry = geom;
|
||||
|
||||
for (let i = 0; i < this._workspaces.length; i++)
|
||||
this._workspaces[i].setGeometry(x, y, width, height);
|
||||
this._workspaces[i].setFullGeometry(geom);
|
||||
},
|
||||
|
||||
setClipRect: function(x, y, width, height) {
|
||||
this._clipX = x;
|
||||
this._clipY = y;
|
||||
this._clipWidth = width;
|
||||
this._clipHeight = height;
|
||||
setActualGeometry: function(geom) {
|
||||
if (rectEqual(this._actualGeometry, geom))
|
||||
return;
|
||||
|
||||
this._actualGeometry = geom;
|
||||
|
||||
for (let i = 0; i < this._workspaces.length; i++)
|
||||
this._workspaces[i].setActualGeometry(geom);
|
||||
},
|
||||
|
||||
_lookupWorkspaceForMetaWindow: function (metaWindow) {
|
||||
@ -203,11 +206,6 @@ const WorkspacesView = new Lang.Class({
|
||||
this._extraWorkspaces[i].syncStacking(stackIndices);
|
||||
},
|
||||
|
||||
updateWindowPositions: function() {
|
||||
for (let w = 0; w < this._workspaces.length; w++)
|
||||
this._workspaces[w].positionWindows(Workspace.WindowPositionFlags.ANIMATE);
|
||||
},
|
||||
|
||||
_scrollToActive: function() {
|
||||
let active = global.screen.get_active_workspace_index();
|
||||
|
||||
@ -227,7 +225,7 @@ const WorkspacesView = new Lang.Class({
|
||||
|
||||
Tweener.removeTweens(workspace.actor);
|
||||
|
||||
let y = (w - active) * (this._height + this._spacing);
|
||||
let y = (w - active) * (this._fullGeometry.height + this._spacing);
|
||||
|
||||
if (showAnimation) {
|
||||
let params = { y: y,
|
||||
@ -298,8 +296,9 @@ const WorkspacesView = new Lang.Class({
|
||||
|
||||
if (newNumWorkspaces > oldNumWorkspaces) {
|
||||
for (let w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
|
||||
this._workspaces[w].setGeometry(this._x, this._y,
|
||||
this._width, this._height);
|
||||
this._workspaces[w].setFullGeometry(this._fullGeometry);
|
||||
if (this._actualGeometry)
|
||||
this._workspaces[w].setActualGeometry(this._actualGeometry);
|
||||
this.actor.add_actor(this._workspaces[w].actor);
|
||||
}
|
||||
|
||||
@ -403,6 +402,15 @@ const WorkspacesView = new Lang.Class({
|
||||
let current = Math.round(adj.value);
|
||||
|
||||
if (active != current) {
|
||||
if (!this._workspaces[current]) {
|
||||
// The current workspace was destroyed. This could happen
|
||||
// when you are on the last empty workspace, and consolidate
|
||||
// windows using the thumbnail bar.
|
||||
// In that case, the intended behavior is to stay on the empty
|
||||
// workspace, which is the last one, so pick it.
|
||||
current = this._workspaces.length - 1;
|
||||
}
|
||||
|
||||
let metaWorkspace = this._workspaces[current].metaWorkspace;
|
||||
metaWorkspace.activate(global.get_current_time());
|
||||
}
|
||||
@ -437,23 +445,9 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
Name: 'WorkspacesDisplay',
|
||||
|
||||
_init: function() {
|
||||
this.actor = new Shell.GenericContainer({ style_class: 'workspaces-display' });
|
||||
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 = new St.Widget({ clip_to_allocation: true });
|
||||
this.actor.connect('notify::allocation', Lang.bind(this, this._updateWorkspacesActualGeometry));
|
||||
this.actor.connect('parent-set', Lang.bind(this, this._parentSet));
|
||||
this.actor.set_clip_to_allocation(true);
|
||||
|
||||
this._spacing = 0;
|
||||
this.actor.connect('style-changed', Lang.bind(this,
|
||||
function() {
|
||||
let node = this.actor.get_theme_node();
|
||||
let spacing = node.get_length('spacing');
|
||||
if (spacing != this._spacing) {
|
||||
this._spacing = spacing;
|
||||
this._updateWorkspacesGeometry();
|
||||
}
|
||||
}));
|
||||
|
||||
let clickAction = new Clutter.ClickAction()
|
||||
clickAction.connect('clicked', Lang.bind(this, function(action) {
|
||||
@ -474,6 +468,11 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
this._workspacesViews[i].startSwipeScroll();
|
||||
return true;
|
||||
}));
|
||||
panAction.connect('gesture-cancel', Lang.bind(this, function() {
|
||||
clickAction.release();
|
||||
for (let i = 0; i < this._workspacesViews.length; i++)
|
||||
this._workspacesViews[i].endSwipeScroll();
|
||||
}));
|
||||
panAction.connect('gesture-end', Lang.bind(this, function() {
|
||||
clickAction.release();
|
||||
for (let i = 0; i < this._workspacesViews.length; i++)
|
||||
@ -482,23 +481,8 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
Main.overview.addAction(panAction);
|
||||
this.actor.bind_property('mapped', panAction, 'enabled', GObject.BindingFlags.SYNC_CREATE);
|
||||
|
||||
let controls = new St.Bin({ style_class: 'workspace-controls',
|
||||
request_mode: Clutter.RequestMode.WIDTH_FOR_HEIGHT,
|
||||
y_align: St.Align.START,
|
||||
y_fill: true });
|
||||
this._controls = controls;
|
||||
this.actor.add_actor(controls);
|
||||
|
||||
controls.reactive = true;
|
||||
controls.track_hover = true;
|
||||
controls.connect('notify::hover',
|
||||
Lang.bind(this, this._onControlsHoverChanged));
|
||||
|
||||
this._primaryIndex = Main.layoutManager.primaryIndex;
|
||||
|
||||
this._thumbnailsBox = new WorkspaceThumbnail.ThumbnailsBox();
|
||||
controls.add_actor(this._thumbnailsBox.actor);
|
||||
|
||||
this._workspacesViews = [];
|
||||
this._workspaces = [];
|
||||
this._primaryScrollAdjustment = null;
|
||||
@ -509,43 +493,15 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
this._workspacesOnlyOnPrimaryChanged));
|
||||
this._workspacesOnlyOnPrimaryChanged();
|
||||
|
||||
this._inDrag = false;
|
||||
this._cancelledDrag = false;
|
||||
|
||||
this._controlsInitiallyHovered = false;
|
||||
this._alwaysZoomOut = false;
|
||||
this._zoomOut = false;
|
||||
this._zoomFraction = 0;
|
||||
|
||||
this._updateAlwaysZoom();
|
||||
|
||||
// If we stop hiding the overview on layout changes, we will need to
|
||||
// update the _workspacesViews here
|
||||
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._updateAlwaysZoom));
|
||||
|
||||
Main.xdndHandler.connect('drag-begin', Lang.bind(this, function(){
|
||||
this._alwaysZoomOut = true;
|
||||
}));
|
||||
|
||||
Main.xdndHandler.connect('drag-end', Lang.bind(this, function(){
|
||||
this._alwaysZoomOut = false;
|
||||
this._updateAlwaysZoom();
|
||||
}));
|
||||
|
||||
global.screen.connect('notify::n-workspaces',
|
||||
Lang.bind(this, this._workspacesChanged));
|
||||
|
||||
this._switchWorkspaceNotifyId = 0;
|
||||
|
||||
this._itemDragBeginId = 0;
|
||||
this._itemDragCancelledId = 0;
|
||||
this._itemDragEndId = 0;
|
||||
this._windowDragBeginId = 0;
|
||||
this._windowDragCancelledId = 0;
|
||||
this._windowDragEndId = 0;
|
||||
this._notifyOpacityId = 0;
|
||||
this._swipeScrollBeginId = 0;
|
||||
this._swipeScrollEndId = 0;
|
||||
this._scrollEventId = 0;
|
||||
|
||||
this._fullGeometry = null;
|
||||
},
|
||||
|
||||
_onPan: function(action) {
|
||||
@ -556,49 +512,13 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
},
|
||||
|
||||
show: function() {
|
||||
if(!this._alwaysZoomOut) {
|
||||
let [mouseX, mouseY] = global.get_pointer();
|
||||
let [x, y] = this._controls.get_transformed_position();
|
||||
let [width, height] = this._controls.get_transformed_size();
|
||||
let visibleWidth = this._controls.get_theme_node().get_length('visible-width');
|
||||
let rtl = (Clutter.get_default_text_direction () == Clutter.TextDirection.RTL);
|
||||
if(rtl)
|
||||
x = x + width - visibleWidth;
|
||||
if(mouseX > x - 0.5 && mouseX < x + visibleWidth + 0.5 &&
|
||||
mouseY > y - 0.5 && mouseY < y + height + 0.5)
|
||||
this._controlsInitiallyHovered = true;
|
||||
}
|
||||
|
||||
this._zoomOut = this._alwaysZoomOut;
|
||||
this._zoomFraction = this._alwaysZoomOut ? 1 : 0;
|
||||
this._updateZoom();
|
||||
|
||||
this._controls.show();
|
||||
|
||||
this._updateWorkspacesViews();
|
||||
|
||||
this._restackedNotifyId =
|
||||
Main.overview.connect('windows-restacked',
|
||||
Lang.bind(this, this._onRestacked));
|
||||
|
||||
if (this._itemDragBeginId == 0)
|
||||
this._itemDragBeginId = Main.overview.connect('item-drag-begin',
|
||||
Lang.bind(this, this._dragBegin));
|
||||
if (this._itemDragCancelledId == 0)
|
||||
this._itemDragCancelledId = Main.overview.connect('item-drag-cancelled',
|
||||
Lang.bind(this, this._dragCancelled));
|
||||
if (this._itemDragEndId == 0)
|
||||
this._itemDragEndId = Main.overview.connect('item-drag-end',
|
||||
Lang.bind(this, this._dragEnd));
|
||||
if (this._windowDragBeginId == 0)
|
||||
this._windowDragBeginId = Main.overview.connect('window-drag-begin',
|
||||
Lang.bind(this, this._dragBegin));
|
||||
if (this._windowDragCancelledId == 0)
|
||||
this._windowDragCancelledId = Main.overview.connect('window-drag-cancelled',
|
||||
Lang.bind(this, this._dragCancelled));
|
||||
if (this._windowDragEndId == 0)
|
||||
this._windowDragEndId = Main.overview.connect('window-drag-end',
|
||||
Lang.bind(this, this._dragEnd));
|
||||
if (this._scrollEventId == 0)
|
||||
this._scrollEventId = Main.overview.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
|
||||
},
|
||||
|
||||
zoomFromOverview: function() {
|
||||
@ -608,38 +528,13 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
this._controls.hide();
|
||||
|
||||
if (!this._alwaysZoomOut)
|
||||
this.zoomFraction = 0;
|
||||
|
||||
if (this._restackedNotifyId > 0){
|
||||
Main.overview.disconnect(this._restackedNotifyId);
|
||||
this._restackedNotifyId = 0;
|
||||
}
|
||||
if (this._itemDragBeginId > 0) {
|
||||
Main.overview.disconnect(this._itemDragBeginId);
|
||||
this._itemDragBeginId = 0;
|
||||
}
|
||||
if (this._itemDragCancelledId > 0) {
|
||||
Main.overview.disconnect(this._itemDragCancelledId);
|
||||
this._itemDragCancelledId = 0;
|
||||
}
|
||||
if (this._itemDragEndId > 0) {
|
||||
Main.overview.disconnect(this._itemDragEndId);
|
||||
this._itemDragEndId = 0;
|
||||
}
|
||||
if (this._windowDragBeginId > 0) {
|
||||
Main.overview.disconnect(this._windowDragBeginId);
|
||||
this._windowDragBeginId = 0;
|
||||
}
|
||||
if (this._windowDragCancelledId > 0) {
|
||||
Main.overview.disconnect(this._windowDragCancelledId);
|
||||
this._windowDragCancelledId = 0;
|
||||
}
|
||||
if (this._windowDragEndId > 0) {
|
||||
Main.overview.disconnect(this._windowDragEndId);
|
||||
this._windowDragEndId = 0;
|
||||
if (this._scrollEventId > 0) {
|
||||
Main.overview.disconnect(this._scrollEventId);
|
||||
this._scrollEventId = 0;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this._workspacesViews.length; i++)
|
||||
@ -686,6 +581,7 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
this._workspaces.push(monitorWorkspaces);
|
||||
|
||||
let view = new WorkspacesView(monitorWorkspaces);
|
||||
view.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
|
||||
if (this._workspacesOnlyOnPrimary || i == this._primaryIndex) {
|
||||
this._scrollAdjustment = view.scrollAdjustment;
|
||||
this._scrollAdjustment.connect('notify::value',
|
||||
@ -694,10 +590,11 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
this._workspacesViews.push(view);
|
||||
}
|
||||
|
||||
this._updateWorkspacesGeometry();
|
||||
this._updateWorkspacesFullGeometry();
|
||||
this._updateWorkspacesActualGeometry();
|
||||
|
||||
for (let i = 0; i < this._workspacesViews.length; i++)
|
||||
global.overlay_group.add_actor(this._workspacesViews[i].actor);
|
||||
Main.layoutManager.overviewGroup.add_actor(this._workspacesViews[i].actor);
|
||||
},
|
||||
|
||||
_scrollValueChanged: function() {
|
||||
@ -728,76 +625,6 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
return this._getPrimaryView().getActiveWorkspace().hasMaximizedWindows();
|
||||
},
|
||||
|
||||
// zoomFraction property allows us to tween the controls sliding in and out
|
||||
set zoomFraction(fraction) {
|
||||
this._zoomFraction = fraction;
|
||||
this.actor.queue_relayout();
|
||||
},
|
||||
|
||||
get zoomFraction() {
|
||||
return this._zoomFraction;
|
||||
},
|
||||
|
||||
_updateAlwaysZoom: function() {
|
||||
// Always show the pager if workspaces are actually used,
|
||||
// e.g. there are windows on more than one
|
||||
this._alwaysZoomOut = global.screen.n_workspaces > 2;
|
||||
|
||||
if (this._alwaysZoomOut)
|
||||
return;
|
||||
|
||||
let monitors = Main.layoutManager.monitors;
|
||||
let primary = Main.layoutManager.primaryMonitor;
|
||||
|
||||
/* Look for any monitor to the right of the primary, if there is
|
||||
* one, we always keep zoom out, otherwise its hard to reach
|
||||
* 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) {
|
||||
this._alwaysZoomOut = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_getPreferredWidth: function (actor, forHeight, alloc) {
|
||||
// pass through the call in case the child needs it, but report 0x0
|
||||
this._controls.get_preferred_width(forHeight);
|
||||
},
|
||||
|
||||
_getPreferredHeight: function (actor, forWidth, alloc) {
|
||||
// pass through the call in case the child needs it, but report 0x0
|
||||
this._controls.get_preferred_height(forWidth);
|
||||
},
|
||||
|
||||
_allocate: function (actor, box, flags) {
|
||||
let childBox = new Clutter.ActorBox();
|
||||
|
||||
let totalWidth = box.x2 - box.x1;
|
||||
|
||||
// width of the controls
|
||||
let [controlsMin, controlsNatural] = this._controls.get_preferred_width(box.y2 - box.y1);
|
||||
|
||||
// Amount of space on the screen we reserve for the visible control
|
||||
let controlsVisible = this._controls.get_theme_node().get_length('visible-width');
|
||||
let controlsReserved = controlsVisible * (1 - this._zoomFraction) + controlsNatural * this._zoomFraction;
|
||||
|
||||
let rtl = (Clutter.get_default_text_direction () == Clutter.TextDirection.RTL);
|
||||
if (rtl) {
|
||||
childBox.x2 = controlsReserved;
|
||||
childBox.x1 = childBox.x2 - controlsNatural;
|
||||
} else {
|
||||
childBox.x1 = totalWidth - controlsReserved;
|
||||
childBox.x2 = childBox.x1 + controlsNatural;
|
||||
}
|
||||
|
||||
childBox.y1 = 0;
|
||||
childBox.y2 = box.y2- box.y1;
|
||||
this._controls.allocate(childBox, flags);
|
||||
|
||||
this._updateWorkspacesGeometry();
|
||||
},
|
||||
|
||||
_parentSet: function(actor, oldParent) {
|
||||
if (oldParent && this._notifyOpacityId)
|
||||
oldParent.disconnect(this._notifyOpacityId);
|
||||
@ -811,7 +638,7 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
|
||||
// This is kinda hackish - we want the primary view to
|
||||
// appear as parent of this.actor, though in reality it
|
||||
// is added directly to overlay_group
|
||||
// is added directly to Main.layoutManager.overviewGroup
|
||||
this._notifyOpacityId = newParent.connect('notify::opacity',
|
||||
Lang.bind(this, function() {
|
||||
let opacity = this.actor.get_parent().opacity;
|
||||
@ -824,51 +651,48 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
}));
|
||||
},
|
||||
|
||||
_updateWorkspacesGeometry: function() {
|
||||
// This geometry should always be the fullest geometry
|
||||
// the workspaces switcher can ever be allocated, as if
|
||||
// the sliding controls were never slid in at all.
|
||||
setWorkspacesFullGeometry: function(geom) {
|
||||
this._fullGeometry = geom;
|
||||
this._updateWorkspacesFullGeometry();
|
||||
},
|
||||
|
||||
_updateWorkspacesFullGeometry: function() {
|
||||
if (!this._workspacesViews.length)
|
||||
return;
|
||||
|
||||
let fullWidth = this.actor.allocation.x2 - this.actor.allocation.x1;
|
||||
let fullHeight = this.actor.allocation.y2 - this.actor.allocation.y1;
|
||||
|
||||
let width = fullWidth;
|
||||
let height = fullHeight;
|
||||
|
||||
let [controlsMin, controlsNatural] = this._controls.get_preferred_width(height);
|
||||
let controlsVisible = this._controls.get_theme_node().get_length('visible-width');
|
||||
|
||||
let [x, y] = this.actor.get_transformed_position();
|
||||
|
||||
let rtl = (Clutter.get_default_text_direction () == Clutter.TextDirection.RTL);
|
||||
|
||||
let clipWidth = width - controlsVisible;
|
||||
let clipHeight = fullHeight;
|
||||
let clipX = rtl ? x + controlsVisible : x;
|
||||
let clipY = y + (fullHeight - clipHeight) / 2;
|
||||
|
||||
let widthAdjust = this._zoomOut ? controlsNatural : controlsVisible;
|
||||
widthAdjust += this._spacing;
|
||||
width -= widthAdjust;
|
||||
if (rtl)
|
||||
x += widthAdjust;
|
||||
|
||||
let monitors = Main.layoutManager.monitors;
|
||||
let m = 0;
|
||||
for (let i = 0; i < monitors.length; i++) {
|
||||
if (i == this._primaryIndex) {
|
||||
this._workspacesViews[m].setClipRect(clipX, clipY,
|
||||
clipWidth, clipHeight);
|
||||
this._workspacesViews[m].setGeometry(x, y, width, height);
|
||||
this._workspacesViews[m].setFullGeometry(this._fullGeometry);
|
||||
m++;
|
||||
} else if (!this._workspacesOnlyOnPrimary) {
|
||||
this._workspacesViews[m].setClipRect(monitors[i].x,
|
||||
monitors[i].y,
|
||||
monitors[i].width,
|
||||
monitors[i].height);
|
||||
this._workspacesViews[m].setGeometry(monitors[i].x,
|
||||
monitors[i].y,
|
||||
monitors[i].width,
|
||||
monitors[i].height);
|
||||
this._workspacesViews[m].setFullGeometry(monitors[i]);
|
||||
m++;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_updateWorkspacesActualGeometry: function() {
|
||||
if (!this._workspacesViews.length)
|
||||
return;
|
||||
|
||||
let [x, y] = this.actor.get_transformed_position();
|
||||
let width = this.actor.allocation.x2 - this.actor.allocation.x1;
|
||||
let height = this.actor.allocation.y2 - this.actor.allocation.y1;
|
||||
let geometry = { x: x, y: y, width: width, height: height };
|
||||
|
||||
let monitors = Main.layoutManager.monitors;
|
||||
let m = 0;
|
||||
for (let i = 0; i < monitors.length; i++) {
|
||||
if (i == this._primaryIndex) {
|
||||
this._workspacesViews[m].setActualGeometry(geometry);
|
||||
m++;
|
||||
} else if (!this._workspacesOnlyOnPrimary) {
|
||||
this._workspacesViews[m].setActualGeometry(monitors[i]);
|
||||
m++;
|
||||
}
|
||||
}
|
||||
@ -880,9 +704,6 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
},
|
||||
|
||||
_workspacesChanged: function() {
|
||||
this._updateAlwaysZoom();
|
||||
this._updateZoom();
|
||||
|
||||
if (!this._workspacesViews.length)
|
||||
return;
|
||||
|
||||
@ -936,66 +757,23 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
newNumWorkspaces);
|
||||
},
|
||||
|
||||
_updateZoom : function() {
|
||||
if (Main.overview.animationInProgress)
|
||||
return;
|
||||
|
||||
let shouldZoom = this._alwaysZoomOut || this._controls.hover;
|
||||
if (shouldZoom != this._zoomOut) {
|
||||
this._zoomOut = shouldZoom;
|
||||
this._updateWorkspacesGeometry();
|
||||
|
||||
if (!this._workspacesViews.length)
|
||||
return;
|
||||
|
||||
Tweener.addTween(this,
|
||||
{ zoomFraction: this._zoomOut ? 1 : 0,
|
||||
time: WORKSPACE_SWITCH_TIME,
|
||||
transition: 'easeOutQuad' });
|
||||
|
||||
for (let i = 0; i < this._workspacesViews.length; i++)
|
||||
this._workspacesViews[i].updateWindowPositions();
|
||||
_onScrollEvent: function(actor, event) {
|
||||
if (!this.actor.mapped)
|
||||
return false;
|
||||
let activeWs = global.screen.get_active_workspace();
|
||||
let ws;
|
||||
switch (event.get_scroll_direction()) {
|
||||
case Clutter.ScrollDirection.UP:
|
||||
ws = activeWs.get_neighbor(Meta.MotionDirection.UP);
|
||||
break;
|
||||
case Clutter.ScrollDirection.DOWN:
|
||||
ws = activeWs.get_neighbor(Meta.MotionDirection.DOWN);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
_onControlsHoverChanged: function() {
|
||||
if(!this._controls.hover)
|
||||
this._controlsInitiallyHovered = false;
|
||||
if(!this._controlsInitiallyHovered)
|
||||
this._updateZoom();
|
||||
},
|
||||
|
||||
_dragBegin: function() {
|
||||
this._inDrag = true;
|
||||
this._cancelledDrag = false;
|
||||
this._dragMonitor = {
|
||||
dragMotion: Lang.bind(this, this._onDragMotion)
|
||||
};
|
||||
DND.addDragMonitor(this._dragMonitor);
|
||||
},
|
||||
|
||||
_dragCancelled: function() {
|
||||
this._cancelledDrag = true;
|
||||
DND.removeDragMonitor(this._dragMonitor);
|
||||
},
|
||||
|
||||
_onDragMotion: function(dragEvent) {
|
||||
let controlsHovered = this._controls.contains(dragEvent.targetActor);
|
||||
this._controls.set_hover(controlsHovered);
|
||||
|
||||
return DND.DragMotionResult.CONTINUE;
|
||||
},
|
||||
|
||||
_dragEnd: function() {
|
||||
this._inDrag = false;
|
||||
|
||||
// We do this deferred because drag-end is emitted before dnd.js emits
|
||||
// event/leave events that were suppressed during the drag. If we didn't
|
||||
// defer this, we'd zoom out then immediately zoom in because of the
|
||||
// enter event we received. That would normally be invisible but we
|
||||
// might as well avoid it.
|
||||
Meta.later_add(Meta.LaterType.BEFORE_REDRAW,
|
||||
Lang.bind(this, this._updateZoom));
|
||||
Main.wm.actionMoveWorkspace(ws);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(WorkspacesDisplay.prototype);
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Lang = imports.lang;
|
||||
const Main = imports.ui.main;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const DND = imports.ui.dnd;
|
||||
@ -15,19 +16,12 @@ const XdndHandler = new Lang.Class({
|
||||
this._cursorWindowClone = null;
|
||||
|
||||
// Used as a drag actor in case we don't have a cursor window clone
|
||||
this._dummy = new Clutter.Rectangle({ width: 1, height: 1, opacity: 0 });
|
||||
global.stage.add_actor(this._dummy);
|
||||
this._dummy = new Clutter.Actor({ width: 1, height: 1, opacity: 0 });
|
||||
Main.uiGroup.add_actor(this._dummy);
|
||||
Shell.util_set_hidden_from_pick(this._dummy, true);
|
||||
this._dummy.hide();
|
||||
|
||||
// Mutter delays the creation of the output window as long
|
||||
// as possible to avoid flicker. In case a plugin wants to
|
||||
// access it directly it has to connect to the stage's show
|
||||
// signal. (see comment in compositor.c:meta_compositor_manage_screen)
|
||||
global.stage.connect('show', function () {
|
||||
global.init_xdnd();
|
||||
return false;
|
||||
});
|
||||
global.init_xdnd();
|
||||
|
||||
global.connect('xdnd-enter', Lang.bind(this, this._onEnter));
|
||||
global.connect('xdnd-position-changed', Lang.bind(this, this._onPositionChanged));
|
||||
@ -74,7 +68,7 @@ const XdndHandler = new Lang.Class({
|
||||
source: cursorWindow});
|
||||
|
||||
this._cursorWindowClone = new Clutter.Clone({ source: cursorWindow });
|
||||
global.overlay_group.add_actor(this._cursorWindowClone);
|
||||
Main.uiGroup.add_actor(this._cursorWindowClone);
|
||||
Shell.util_set_hidden_from_pick(this._cursorWindowClone, true);
|
||||
|
||||
// Make sure that the clone has the same position as the source
|
||||
@ -88,7 +82,7 @@ const XdndHandler = new Lang.Class({
|
||||
},
|
||||
|
||||
_onPositionChanged: function(obj, x, y) {
|
||||
let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y);
|
||||
let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);
|
||||
|
||||
// Make sure that the cursor window is on top
|
||||
if (this._cursorWindowClone)
|
||||
|
@ -125,6 +125,12 @@
|
||||
<listitem><para>List possible modes and exit</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--clutter-display=<replaceable>DISPLAY</replaceable></option></term>
|
||||
|
||||
<listitem><para>Clutter the option display (otherwise ignored)</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
</variablelist>
|
||||
|
||||
</refsect1>
|
||||
|
@ -28,6 +28,7 @@ gu
|
||||
he
|
||||
hi
|
||||
hu
|
||||
ia
|
||||
id
|
||||
it
|
||||
ja
|
||||
@ -60,6 +61,7 @@ sr@latin
|
||||
sv
|
||||
ta
|
||||
te
|
||||
tg
|
||||
th
|
||||
tr
|
||||
ug
|
||||
|
@ -1,3 +1,6 @@
|
||||
# List of source files containing translatable strings.
|
||||
# Please keep this file sorted alphabetically.
|
||||
[encoding: UTF-8]
|
||||
data/50-gnome-shell-screenshot.xml.in
|
||||
data/50-gnome-shell-system.xml.in
|
||||
data/gnome-shell.desktop.in.in
|
||||
@ -10,6 +13,7 @@ js/gdm/util.js
|
||||
js/misc/util.js
|
||||
js/ui/appDisplay.js
|
||||
js/ui/appFavorites.js
|
||||
js/ui/backgroundMenu.js
|
||||
js/ui/calendar.js
|
||||
js/ui/components/automountManager.js
|
||||
js/ui/components/autorunManager.js
|
||||
@ -29,6 +33,7 @@ js/ui/lookingGlass.js
|
||||
js/ui/main.js
|
||||
js/ui/messageTray.js
|
||||
js/ui/notificationDaemon.js
|
||||
js/ui/overviewControls.js
|
||||
js/ui/overview.js
|
||||
js/ui/panel.js
|
||||
js/ui/popupMenu.js
|
||||
@ -50,6 +55,7 @@ js/ui/viewSelector.js
|
||||
js/ui/wanda.js
|
||||
js/ui/windowAttentionHandler.js
|
||||
src/calendar-server/evolution-calendar.desktop.in.in
|
||||
# Please do not remove this file from POTFILES.in. Run "git submodule init && git submodule update" to get it.
|
||||
src/gvc/gvc-mixer-control.c
|
||||
src/main.c
|
||||
src/shell-app.c
|
||||
|
1114
po/ca@valencia.po
1114
po/ca@valencia.po
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user