Compare commits
1213 Commits
Author | SHA1 | Date | |
---|---|---|---|
16dc6ce41a | |||
1dadcee5c9 | |||
bed3bb45f7 | |||
18d850d7d6 | |||
f920fd8b6f | |||
b9da6d9ef6 | |||
3be489c69e | |||
cab092aad7 | |||
0451cba343 | |||
12334cd9ad | |||
9d18a2dff4 | |||
869e1dc241 | |||
b878f3fc4a | |||
2aeabcc38b | |||
94778c0dac | |||
390491b37a | |||
f8ea825577 | |||
f77ad7d184 | |||
e467a53def | |||
9cd2e12915 | |||
627a6587b4 | |||
9b3b419f7a | |||
f37ed44837 | |||
dddc333407 | |||
75714fb784 | |||
5a6342a5fe | |||
e07379b406 | |||
54bfe34273 | |||
bef876e4b4 | |||
6ef9ab6a0b | |||
53ffdd8d68 | |||
ba198034cb | |||
58e66b9e1b | |||
ac7efcb43a | |||
b514b9ec7c | |||
f69942eb35 | |||
f28827f625 | |||
f20a6b3635 | |||
7f7ff3ce3e | |||
0abb0756ba | |||
070ed9396c | |||
307004a455 | |||
d097327bd8 | |||
f72f501931 | |||
e9584cfcab | |||
aec0e75d73 | |||
a6b4d49454 | |||
1cc726593e | |||
5001bd8810 | |||
46aa70aa09 | |||
41cf447c45 | |||
b328fd7aed | |||
917a24580b | |||
af36182879 | |||
160c721afc | |||
ee0040ef1c | |||
9e60a55dd7 | |||
57f27572ae | |||
ceae032112 | |||
d3ab367fcd | |||
0ceefb48c8 | |||
a9815ae1e9 | |||
79140fd132 | |||
6c0f48ce25 | |||
da7ab7057d | |||
499ae609dd | |||
e78b3644e4 | |||
ce6b39bd1c | |||
603f528b47 | |||
5fecd07045 | |||
8edd7ad32e | |||
3a27d0b849 | |||
b351536dee | |||
eeea8559b6 | |||
17591117c1 | |||
c4470fd1e6 | |||
45fc7ec01f | |||
dc54472ca5 | |||
4b3bf05aaf | |||
ec014a7ecf | |||
a8c87f3bab | |||
b39e76200a | |||
01deb9ef7d | |||
5c89568cca | |||
6119b44746 | |||
7af4a7bf88 | |||
04e4d69662 | |||
a87e0f028a | |||
31774a7711 | |||
a32f27a2aa | |||
805a409318 | |||
42d45bd14a | |||
5870709fbc | |||
0fdb7430ff | |||
4771f80d6f | |||
91405583fd | |||
f864303aac | |||
e5ebf4a2b2 | |||
725a36e37a | |||
78dacfa865 | |||
16547fb3c2 | |||
9eeabf79f9 | |||
81b71cc143 | |||
8301acd4d6 | |||
f80afb1755 | |||
71c6b60054 | |||
95a1b874d8 | |||
07de96ede9 | |||
209014b083 | |||
127f10e7a8 | |||
67615a0cbc | |||
dde20f0c76 | |||
ddced79475 | |||
e80795b7f0 | |||
080337a4e8 | |||
884af3c986 | |||
edc95b2cb7 | |||
96239e95ec | |||
331ad76675 | |||
29152d3022 | |||
73fa4b1cbd | |||
7766a91e8c | |||
b7bad765aa | |||
5af09a60e9 | |||
ef623cfbbd | |||
ce5faba185 | |||
40d9ed535b | |||
d5675765f3 | |||
65067c24cc | |||
e2463cb501 | |||
de2f2d7ef1 | |||
7cdb75e7ce | |||
1502e308b8 | |||
dd1fe68e5c | |||
85c1dc5a0c | |||
7b3a689aad | |||
2c28a12cca | |||
324021fd64 | |||
1dceae97c6 | |||
0bef925b51 | |||
0ba1f29e40 | |||
3b1e536822 | |||
9a83662a18 | |||
5bcead5b5c | |||
d8f9502ea2 | |||
020128b9ca | |||
1566a4e607 | |||
fbd59631ee | |||
6c36856499 | |||
f4cda49988 | |||
a6b49fe7d6 | |||
26966b2bf3 | |||
36a7429aa0 | |||
7be1fe09f1 | |||
62bf08d323 | |||
260c082c4e | |||
5e0ff7fd56 | |||
aac312ca34 | |||
a90401454a | |||
5679e6b3a9 | |||
194574bb0e | |||
337d2da38a | |||
df848aa084 | |||
0cc1615252 | |||
d4259fa8aa | |||
157d7e2b59 | |||
ee50904147 | |||
ec39aa3890 | |||
beb0fdf4b8 | |||
443fe813c3 | |||
69cdc5a9b9 | |||
dac54a6019 | |||
e5ca53e56c | |||
7a79236bab | |||
cd37e2908f | |||
3368e49aa7 | |||
9e31f05861 | |||
b52f4ed25b | |||
00eb764880 | |||
6d011a3700 | |||
f9d9caf417 | |||
b50702dd52 | |||
e74a82b9d6 | |||
12fa983242 | |||
1607df835a | |||
7b705dd670 | |||
323e3028b6 | |||
7e1b4692e2 | |||
c1dd971ce9 | |||
39610e6933 | |||
fdb189102d | |||
e54d095064 | |||
e6634c56d3 | |||
deb77a4dde | |||
90ea27c423 | |||
19533f87e3 | |||
e6c5c84133 | |||
404aaa5658 | |||
2c70df25f5 | |||
db959ac2eb | |||
bbf0dce390 | |||
1cbb8b9851 | |||
96387cce47 | |||
570fc68cb1 | |||
e6ce0057af | |||
a3f625fe39 | |||
bdfe459d64 | |||
95ec8ef5e1 | |||
b8198716d9 | |||
071a4e5f83 | |||
d99cd71408 | |||
030e6aa507 | |||
f146b01e3e | |||
c249ff9046 | |||
8b48560c81 | |||
bdbea2463b | |||
8fb2263471 | |||
ea55c36a3a | |||
37595ff3e7 | |||
cca008b73c | |||
b9dcbd9d33 | |||
b6bf8d5b2d | |||
43ed66cf26 | |||
57eae1be43 | |||
b394d184cc | |||
81e01b6f88 | |||
52efe32e0f | |||
e7886734c4 | |||
083c37a7b2 | |||
b24a10aa00 | |||
e1ef14d12b | |||
1a33de91e2 | |||
6e15e2d72a | |||
d58b715c52 | |||
804ff8b5a5 | |||
6e89d2f46a | |||
e99d69b7d9 | |||
f25416c3f5 | |||
a7bb6a2781 | |||
41f14e0e89 | |||
01bd10f485 | |||
476eacd5ca | |||
ca2fb74f41 | |||
8a2baf5b6e | |||
d44fc3aa1b | |||
7d78c42dfc | |||
a361180745 | |||
310dc10c4d | |||
96e02c4c2e | |||
afb3b9f029 | |||
d5e647a191 | |||
a1d37617a8 | |||
2541f85942 | |||
07fd23dc5e | |||
946311b2a3 | |||
4868032215 | |||
fb0cf64536 | |||
b37afcdba1 | |||
f644bee831 | |||
6fcc7e3e23 | |||
95602eb85d | |||
9f3afdf928 | |||
627a2412d2 | |||
acffd1e792 | |||
62ca4ba624 | |||
6ea8f35343 | |||
1bd8c67041 | |||
7425b382d6 | |||
5f61b57d63 | |||
9db73767d9 | |||
c562245c16 | |||
9525216d78 | |||
28a71a29e6 | |||
7b06d34ba4 | |||
656d24e477 | |||
df0f03d831 | |||
1db6d15677 | |||
08a0479c9e | |||
6682b7dfa5 | |||
aad5d98b43 | |||
a8a4a85dac | |||
ad71b969b2 | |||
8bcb10391e | |||
8e7d74bc3b | |||
629b6faa22 | |||
ef1e27966d | |||
ea0eb4ba09 | |||
f83ad77c08 | |||
af219ce9e7 | |||
2ac88a7fa6 | |||
c495ff8ad8 | |||
0e12215614 | |||
b4b13b0cb9 | |||
677cdfd6c8 | |||
e9f18c6d8d | |||
8640e18d1f | |||
300c4d8432 | |||
92c877493a | |||
ff4926be35 | |||
df3872f665 | |||
fcded2ea2b | |||
420f0109ee | |||
b4678a1cee | |||
5d3c90197c | |||
d5d517748c | |||
de3d3c15a5 | |||
3db253998b | |||
d3f978a19d | |||
a50ddd6d8c | |||
37700b4b1a | |||
a8f9871725 | |||
a4a5492782 | |||
9ebb80502a | |||
dcacbb37c3 | |||
34443da7ab | |||
09c14f1a10 | |||
e801647083 | |||
0122cee0ae | |||
1188b1be36 | |||
905c4932d1 | |||
6484da26f0 | |||
079041cf1f | |||
b8df7798bf | |||
e4af954a95 | |||
f3df62f27e | |||
4b3c9e826d | |||
ba4780cbd3 | |||
b1a6940188 | |||
3c8325f1f3 | |||
5fa9581db3 | |||
96001d86e1 | |||
963e808d98 | |||
d124ca377f | |||
a3d3d81447 | |||
244121d920 | |||
adf95fb90d | |||
f8446f1bfc | |||
b922035d4f | |||
b75fc2bde3 | |||
10e16cca22 | |||
6628214484 | |||
e3c9c46c1c | |||
6a1b341336 | |||
65f96494f8 | |||
6e3e2d9f29 | |||
25d8debb11 | |||
426b227f03 | |||
2fbd9b95a7 | |||
9758029c11 | |||
f21b8206af | |||
4f8f79a907 | |||
c9e2f16404 | |||
2658754295 | |||
77d8ff3620 | |||
c4621119b3 | |||
a2a292d48e | |||
8349b70ba8 | |||
dd6fe58475 | |||
386929e84b | |||
43a355e07a | |||
1a5d2ac459 | |||
21403b19e0 | |||
cb7778d433 | |||
07c0105c83 | |||
7da186d4e9 | |||
75885c3095 | |||
5d275389af | |||
75589b4450 | |||
a9b12d5d73 | |||
6a00a504d4 | |||
6b9cac928b | |||
b292384380 | |||
01ac5aeb0d | |||
1a41ee0c30 | |||
1950a67e15 | |||
65303d027a | |||
fae838b054 | |||
eab4c7bce9 | |||
6bb38b873b | |||
ba5860cdd6 | |||
ee62863759 | |||
4a3a61bea1 | |||
f3901eb668 | |||
b53d1df4df | |||
df9b0c548e | |||
3056fc4710 | |||
e411a4af21 | |||
3430012136 | |||
809295c03d | |||
c9783b38c4 | |||
44e4cbf04a | |||
049695b914 | |||
7b1aab3759 | |||
5e87fea1ee | |||
a187111a26 | |||
3044a6b517 | |||
824fbe09c2 | |||
764eed7599 | |||
ccb81919f7 | |||
4505c38b58 | |||
d9770dcffb | |||
9936f97fcb | |||
4cf09f46e7 | |||
6ffe3a424c | |||
641794d458 | |||
73d9ac6c01 | |||
3205d1e16a | |||
ec61004622 | |||
ae93d258a5 | |||
12845f0eef | |||
6b4f524620 | |||
dec0baa147 | |||
340609d149 | |||
9a30c3d722 | |||
e19a927579 | |||
53a595885a | |||
ebd7406386 | |||
66cdbc5cad | |||
060c049056 | |||
31270c36d2 | |||
0aa26e9134 | |||
b45a2d7335 | |||
07676d483d | |||
6436452bc9 | |||
60ad374514 | |||
b611ddda5e | |||
186bd156dd | |||
3628c81885 | |||
180000a531 | |||
5f995c64d4 | |||
fe2c2014de | |||
754df5ebe2 | |||
d8b7ac9044 | |||
5f3b04ecbd | |||
1f1aba4a32 | |||
1390cd4048 | |||
31cdac7bfb | |||
0705901bce | |||
87495575bc | |||
b292bba092 | |||
5c7f0a0ad8 | |||
2df86e1966 | |||
41406e3be2 | |||
7ed4bd67b8 | |||
8ce3531de8 | |||
579e53f02c | |||
1379c6b404 | |||
153d3045ed | |||
278686dc48 | |||
9b9f33bc8b | |||
24984458de | |||
e3eb4a20a5 | |||
001bbd36f5 | |||
382cc52baa | |||
2af368ab82 | |||
f514160fae | |||
30e4dcef90 | |||
4618e11406 | |||
f887b77e36 | |||
fb527139a0 | |||
261fbef516 | |||
32e8a9b377 | |||
5f95351074 | |||
1c57c0e651 | |||
ec6ee27f29 | |||
70234edd02 | |||
7e24a696bd | |||
c3ed936776 | |||
235ec7cb2e | |||
4f8586d81d | |||
6cd1e38425 | |||
f7512dc540 | |||
45415a742f | |||
2247065dc5 | |||
c550e2ccf5 | |||
bc75e5ec44 | |||
5b39496008 | |||
4016da6632 | |||
f2edcb9bdf | |||
3d8a87563d | |||
93bde0cae2 | |||
30179bb60d | |||
498bc21133 | |||
af7a34521f | |||
7fc2b33a0a | |||
a5d3f7785a | |||
f1cdce38a6 | |||
65e4652142 | |||
569797d7c5 | |||
35a7c94cd1 | |||
c8365b7444 | |||
8c5a343729 | |||
22ddec46ab | |||
a4e70ba4ca | |||
978ac65cae | |||
eb5a836822 | |||
ad1b9b71ae | |||
60257f422a | |||
6576d4852c | |||
c30661c44c | |||
2d9cf195d7 | |||
a21e76caab | |||
a534a6bf09 | |||
2b0f219bfd | |||
33dde63256 | |||
4dc9540325 | |||
2b1a661614 | |||
2138fc2349 | |||
cdc8dd88e9 | |||
2a5f8e84bb | |||
6f8540f25a | |||
151c699f5c | |||
9133f6d352 | |||
ab276e4e86 | |||
cdc4f85e47 | |||
b2fb84e361 | |||
4ab1e893f0 | |||
70bc3d178b | |||
777f145128 | |||
2f0181ac44 | |||
83b4b698a0 | |||
a10a294a2e | |||
34036a3b51 | |||
911bc03381 | |||
0240aeac8d | |||
a43be186d3 | |||
fb63f48d0a | |||
c0279df3c6 | |||
e614b993f2 | |||
8035f30305 | |||
af0b5e6176 | |||
1bf996c705 | |||
33f69481f7 | |||
354fb6b8c6 | |||
d30477b6f8 | |||
496b5d62ae | |||
3652002a68 | |||
c0e5719271 | |||
997f851031 | |||
eaf184b585 | |||
0616261a94 | |||
d2c45f428f | |||
6054ab35cb | |||
5ba0c6404b | |||
f738c2be9d | |||
12ac2e5534 | |||
60985b396a | |||
d9b33e01ee | |||
0335f2726a | |||
23717cc4d7 | |||
4422f301b4 | |||
1981da1b4e | |||
a130f50107 | |||
c40198046c | |||
e5b44a3970 | |||
5e7443706b | |||
39e75e932a | |||
b8a8edc513 | |||
b1051365fa | |||
a935cd0c5d | |||
62ce90795f | |||
6bcad45392 | |||
8747c0c58d | |||
b91d9c2867 | |||
140935bca3 | |||
67c7abaac3 | |||
11bd7dc7de | |||
406f65cb23 | |||
2344706462 | |||
fcc32fca7e | |||
7f587fd4da | |||
f47c0601ce | |||
3d638c692e | |||
e6ef3ea24f | |||
8dd880d0c8 | |||
393a3a079f | |||
ceb035e9b3 | |||
e38570437e | |||
f0203d1f19 | |||
e0c5a61be5 | |||
526a16298c | |||
443f02cd92 | |||
b682c8e052 | |||
d07c8dcd9c | |||
9bcce4f271 | |||
eab497a814 | |||
a8c8df6ee8 | |||
b3549f421d | |||
ae0accb5a4 | |||
d367566db9 | |||
35812208e7 | |||
afaff724f6 | |||
ec8dfb0cad | |||
7798da8dff | |||
89a49ce72e | |||
655dce6a4b | |||
2cf403a3e9 | |||
4351ad2414 | |||
fd2944fa8d | |||
f4626cf8ad | |||
65bf0d20e7 | |||
2f496de98a | |||
644b8304ab | |||
5c4570fcaa | |||
ac0bd3b116 | |||
7ad881d0ec | |||
9a25224890 | |||
3f6f597093 | |||
9ab22fe551 | |||
9794e71a86 | |||
8cb3884fae | |||
098bd4509b | |||
4dc5bac72f | |||
b150869b51 | |||
daf8be9f78 | |||
d14cf1e80b | |||
605d34008e | |||
d3135414cc | |||
b3f29b46cf | |||
4545719e18 | |||
cc60afa31a | |||
85743ede7e | |||
8e231cb2ec | |||
72405cd43f | |||
a9ad9d5e6d | |||
2d79c7333f | |||
7a79cfd76b | |||
6600d6b6d9 | |||
8ca25aed8b | |||
1db353a0fc | |||
2d384eb546 | |||
5d1de33026 | |||
f2ba49fa35 | |||
772ae1cae4 | |||
1d267f2642 | |||
cba299160a | |||
ea33193e73 | |||
ef69c228fd | |||
7ba0f07732 | |||
07696086a2 | |||
426581eb1e | |||
fe88811a40 | |||
30aaa6e26c | |||
4bd071bf3c | |||
309ac65447 | |||
fe246470ce | |||
660cfe707c | |||
a0d7d7bc4b | |||
758764ea75 | |||
6935c62a61 | |||
a34d4d9124 | |||
c49bb5aa03 | |||
427750d6af | |||
b9ad5f8727 | |||
c776826425 | |||
a8ede18db5 | |||
9aa84ec54a | |||
91a6520c3b | |||
45fde4888e | |||
484ef5f2f6 | |||
cdd354739a | |||
420f544bf3 | |||
15b33189ae | |||
685a9f36f6 | |||
3b57812e29 | |||
d583c48992 | |||
0e636ea67e | |||
c9ce1bd30f | |||
f2cbc3192c | |||
56d76791f1 | |||
df2cab877f | |||
27ad8305e5 | |||
c97b4dd48e | |||
59ecd610b1 | |||
42c1285ead | |||
622c1c9236 | |||
2d88508f9d | |||
86efba4ecc | |||
90fae00aa6 | |||
d497ed4040 | |||
c4a21ae5ad | |||
5e5798bee9 | |||
d793077b91 | |||
3e826f9249 | |||
2adfb60dc4 | |||
f7212cf80c | |||
69556643bb | |||
f0ebc84840 | |||
490eb59ea3 | |||
16a9391726 | |||
adf8ba67d2 | |||
c37b222cbf | |||
72f8f2beb1 | |||
16bb9c17f5 | |||
ca44977d92 | |||
bcf294475f | |||
3b31774dd3 | |||
c106347c59 | |||
2679be9d97 | |||
4d59368c7d | |||
3f29680fb6 | |||
8f1df14ae7 | |||
7159e360d9 | |||
65723bcac5 | |||
73ae451cb4 | |||
dc0ea3a248 | |||
9548cd8341 | |||
8410fc38c9 | |||
0c9c2168b3 | |||
2690f54743 | |||
22783813c5 | |||
b5ed46a5af | |||
1a3f17e296 | |||
9281129f07 | |||
0e9ddd3b99 | |||
d87db04e55 | |||
906ec3c8a9 | |||
449575ceae | |||
6255c77eba | |||
c33622f2b3 | |||
e4860acb58 | |||
e96867f757 | |||
57bd43baf3 | |||
3a4e595d32 | |||
dcad22bfa8 | |||
acba0e47d8 | |||
dd19459e18 | |||
8be3c5ed21 | |||
51726d8de7 | |||
8f41c6bad8 | |||
e294abc567 | |||
9f9518c872 | |||
5faf7cb59e | |||
4b095d532c | |||
4288761235 | |||
73388f30fd | |||
2d9ddd4bc8 | |||
55aa0cf303 | |||
52ca15b514 | |||
bd383888de | |||
1bd349485f | |||
155f9dc1b1 | |||
fe7ee1edc3 | |||
c11cbff605 | |||
fbc629266f | |||
75d44dca6b | |||
3e6b794a33 | |||
a757ce48a1 | |||
507f29a7bd | |||
e5f4f77073 | |||
f07fee538d | |||
fa1420b384 | |||
724a2bd72f | |||
d525d02348 | |||
4920cf2b98 | |||
f41d0938a4 | |||
924a405829 | |||
fc9225e24a | |||
ac202cbdd3 | |||
cedd68c942 | |||
4231c879ef | |||
42e58a4b72 | |||
ebd1bc83c9 | |||
0bc9d7455f | |||
5274166f8c | |||
5566aa4588 | |||
ab638a4f54 | |||
86a8452ab7 | |||
8c96c16eac | |||
45c2e6575d | |||
a786a7dc55 | |||
6adf5cbee5 | |||
1964b54627 | |||
c9d0e82c52 | |||
1a4948f0f2 | |||
66da3f5668 | |||
9ebeb64570 | |||
571aaece2e | |||
f60fb954a2 | |||
1d136cacfb | |||
994021d77f | |||
8899325a0e | |||
43876a9357 | |||
d6cace32f5 | |||
2388de455b | |||
b14b3ab276 | |||
32ccb779c6 | |||
52536e94e5 | |||
5d6b3abd26 | |||
79682bbcb0 | |||
770ff19313 | |||
8d4855f100 | |||
7d4e14f384 | |||
f162dd7e87 | |||
06dc12e217 | |||
36edff9e15 | |||
34a0c079de | |||
84b581ae13 | |||
587c93eadf | |||
b5aa549e0c | |||
5fb6738612 | |||
685262fc5e | |||
8802f6cf23 | |||
2698d0db29 | |||
2fa7230133 | |||
658243eae4 | |||
7282bc8859 | |||
07511cb21a | |||
f71f767a3c | |||
176daa1469 | |||
5487f8cf93 | |||
944762ac83 | |||
c66068c435 | |||
d479c936b8 | |||
c5a3f784dd | |||
529d012865 | |||
8a1b83417a | |||
6c64c0873c | |||
d2c6149923 | |||
9f98f6f02d | |||
2f7c0b1985 | |||
f61548234a | |||
6eef830ba4 | |||
88a9b7648c | |||
b7678493f9 | |||
a9ec8a354a | |||
a42d35deab | |||
2008feb7da | |||
c41424b57b | |||
3bd5563a7e | |||
eff8ec00c4 | |||
1256af7b9a | |||
b38ecaf925 | |||
bd5d945fb2 | |||
15063ef3d5 | |||
3b8a125732 | |||
9860b1c677 | |||
a0e340f06e | |||
57d3bec95c | |||
be10f3c2b5 | |||
f7af96dbb2 | |||
468a855f04 | |||
429f9e1d15 | |||
5de91197ae | |||
bc6b4b01ea | |||
e89ce7358a | |||
bdf0a6fd0b | |||
783abd4f5f | |||
28aa9201f0 | |||
378df515d0 | |||
04d68c6e36 | |||
5308d12239 | |||
15cac0157c | |||
fc696bc054 | |||
5a7b7b7389 | |||
9c9cf6f5ab | |||
0155739bef | |||
d0902fa28b | |||
3aa0d455c9 | |||
835d4e4b58 | |||
6075332f2f | |||
c5d8484e19 | |||
575ab0d609 | |||
b48a7d5cfd | |||
9af107feff | |||
70b5db16d3 | |||
5072ea7e47 | |||
13b83c95b7 | |||
e48dbe65f1 | |||
14fb51e6d7 | |||
b42af9aa99 | |||
ca2ee22827 | |||
13a2b28bf1 | |||
04bf75d137 | |||
76c589702f | |||
c11f89e72a | |||
cfacb05461 | |||
c4ba31cb23 | |||
db0eacddc5 | |||
62972f5138 | |||
6fe263f19f | |||
b6a87acb71 | |||
784b04b191 | |||
3af9f636af | |||
16c0585b95 | |||
fc9a96ac85 | |||
9dfc3af9d7 | |||
8dc63932fc | |||
066e5cddb5 | |||
27ffad2148 | |||
41db363b06 | |||
184ac11c93 | |||
673aa3c5f2 | |||
2dd1c04bf3 | |||
4df1222ce7 | |||
208121646c | |||
f47a11f792 | |||
36fc3a5c96 | |||
ed12c9d611 | |||
4153feeb15 | |||
349c642d10 | |||
4728105f43 | |||
1f46a0dc26 | |||
7ff7ced504 | |||
a43ee41b0b | |||
ec7ade4ee6 | |||
9b846cbb83 | |||
2cc7fd07f8 | |||
72d54d9915 | |||
b390b82e9d | |||
1ef9ee13a3 | |||
6822795bb8 | |||
187dec2816 | |||
31f75e4607 | |||
92083eaf76 | |||
6b40c3974d | |||
c528401b62 | |||
8a17f512f4 | |||
e725f8a0fe | |||
525d3c2619 | |||
aba46720c0 | |||
2fb1d707fe | |||
dd06aa98ad | |||
cfb66ca153 | |||
0b4660702b | |||
9760227c66 | |||
5faeaa2028 | |||
cd58f9f9e5 | |||
00338bbc4b | |||
31d14a2fb0 | |||
49fa0ddebc | |||
d50c3e6c67 | |||
b39f56e813 | |||
ead0286ca6 | |||
6eb05af306 | |||
6b1e632621 | |||
29cb10fed8 | |||
a88433dba6 | |||
7e5f1fe411 | |||
00c78d33e4 | |||
50fbf9982e | |||
71ad39b6e3 | |||
3d329cdf2d | |||
21af5c8f73 | |||
716d7bb93e | |||
98f4b99446 | |||
830e701d13 | |||
57d9e7d5aa | |||
5fc16bb05b | |||
cb08bd2e2e | |||
961dba7faa | |||
41f933b89e | |||
d4dc1c87c5 | |||
e58fbeea17 | |||
2492dc50db | |||
b4f5f1e461 | |||
69347fff09 | |||
5c439f4e9c | |||
c84236ed73 | |||
ae1a3a0cb2 | |||
b3fea016f1 | |||
7c93639e59 | |||
affcd5c7de | |||
e00eb06f66 | |||
5ecc204d57 | |||
2a5eed1eb4 | |||
d517c13d7a | |||
bb88265d78 | |||
858694f4cc | |||
a46321baa0 | |||
9808e8ab0d | |||
7bd7b53845 | |||
056cfc9dc6 | |||
b7b60d103e | |||
14fd0eb73e | |||
ca73017100 | |||
123fb350ce | |||
1a27d7dfc0 | |||
b0c6d44515 | |||
6092e01428 | |||
e20ff5ae45 | |||
621a0d70e2 | |||
855b238ec5 | |||
96f44e1959 | |||
242dc8cddd | |||
b8a2004872 | |||
63f08bf2c2 | |||
4734a9ac18 | |||
81cd1e6c40 | |||
0b9cdea7d2 | |||
f63fceb2ea | |||
f0f7165ab6 | |||
2511f60d39 | |||
c0f868dd56 | |||
a112bfdaa2 | |||
4535a70f08 | |||
b59529e579 | |||
393c23820a | |||
e6a3958e45 | |||
59a7fdd2c9 | |||
c3cab28c9b | |||
4c55a6f436 | |||
c10e4c3117 | |||
3b70094151 | |||
a370697385 | |||
8a7c0313f6 | |||
00201f7e6c | |||
5e3111bad8 | |||
a327c10c60 | |||
938bc07e3c | |||
d053d45a6d | |||
5200da6eec | |||
b0523860d9 | |||
1a1215527f | |||
360c018c9e | |||
7bed964ab2 | |||
989d0a5682 | |||
b121c25184 | |||
e0d127b3e4 | |||
a0c56c74e1 | |||
f4b61a7f24 | |||
b2400fb883 | |||
f25832d1b6 | |||
cf363171aa | |||
38c8569d16 | |||
9791d15f39 | |||
80e7f5832b | |||
6e46ddaad3 | |||
8499e4aab2 | |||
5eae1851a1 | |||
c48a246ccb | |||
f084011a61 | |||
bd40cf194c | |||
2434af7e8a | |||
ced7fa9f01 | |||
28b559e812 | |||
76d776245b | |||
0d9f70492e | |||
b58f502dd6 | |||
5f367248c5 | |||
c2065cc3e2 | |||
034408971d | |||
490206b5b2 | |||
d8540819af | |||
d1a9aec526 | |||
e4b681a5a5 | |||
52036871d1 | |||
4fc6a804f5 | |||
08d2ca300a | |||
dc2ec0a8f9 | |||
2cfed952bb | |||
a7da137778 | |||
5ae2f87ce9 | |||
c4f6619fbd | |||
65edbc1424 | |||
1f2d7fa28f | |||
2407a0c4e2 | |||
7f8bfcc939 | |||
fae4cb9e56 | |||
063bbb02f2 | |||
15e1470c50 | |||
f602993aa9 | |||
271508c0a8 | |||
50f96d1c9c | |||
809544800b | |||
e757b06987 | |||
6f2da1baf1 | |||
f30dcad8a7 | |||
7dc235511e | |||
644c210fe2 | |||
68e8f98671 | |||
1228db6ac1 | |||
86596749fa | |||
76229a3601 | |||
95abdeb919 | |||
df0151d338 | |||
ee2f12fe81 | |||
10c104529f | |||
cbc8ec6508 | |||
8e7758e280 | |||
9aefbd189c | |||
b9d50584d8 | |||
e5f7390c09 | |||
797b1ff8bb | |||
09b738045c | |||
4fd690333a | |||
5ef5e25a7f | |||
87e8770cbc | |||
cac9d120be | |||
1ae0fadbf4 | |||
65497e089a | |||
2d763bd033 | |||
4ebf396cf5 | |||
6faa50d496 | |||
6fcd6298c8 | |||
5c16be5dbb | |||
e3abb6f178 | |||
70736be4eb | |||
1c3e7330f3 | |||
6e4c89b310 | |||
73b4a0ef5f | |||
d88002c4ed | |||
025c63c045 | |||
b189cbd46e | |||
a21ddb5914 | |||
e04a4c3923 | |||
ae0821e07b | |||
a508bece36 | |||
2c9969a8f4 | |||
dd9fc91f6f | |||
39d9838cc1 | |||
1397c7c624 | |||
c09fcba94f | |||
c77b38fc4f | |||
aef9b733e5 | |||
348208617b | |||
2c34c8e20f | |||
d19fa731d6 | |||
59bb1cc387 | |||
56909d0646 | |||
04da29c939 | |||
307f7a3024 | |||
e80bfa39f5 | |||
29714922ea | |||
88192114ac | |||
9d78208b76 | |||
d817bf0395 | |||
4d51056226 | |||
a607174a25 | |||
37d6a624b7 | |||
0c807bddaf | |||
a0470bfc66 | |||
b9463d23e8 | |||
04debd1623 | |||
9d31576cf5 | |||
8daca28a90 | |||
9899604261 | |||
687e1eabed | |||
eb09f34114 | |||
fbeb446ed7 | |||
92033ce0f5 | |||
9171bab5e5 | |||
f9819eb7b0 | |||
85728f0d15 | |||
9396849d56 | |||
6f5e5672bb | |||
b936e60876 | |||
fa4bd91213 | |||
81eeef7d3c | |||
a7b5134820 | |||
92a01c67ba | |||
71c23613b5 | |||
4f876995de | |||
9cf4a76196 | |||
9efe5287e4 | |||
8c4b34de4e | |||
93e3559dc3 | |||
caa0f63e1f | |||
a4e29e1244 | |||
599f2f43e3 | |||
3a453c5f73 | |||
98b313c75f | |||
8b796d80a7 | |||
719a8908a6 | |||
fed007ecae | |||
990443465f | |||
1f183b8a4e | |||
96556eb959 | |||
3ffeeac577 | |||
d7929a2340 | |||
667019a8c1 | |||
d9b46b4782 | |||
e249218a9d | |||
a2b1946b01 | |||
96a80f7ba0 | |||
9955fbf4f8 | |||
14966b0cd0 | |||
6e1a8f16a8 | |||
d106191e6a | |||
418cf6281e | |||
bdeb7d86b6 | |||
a0e0cc1038 | |||
78f6dec73b | |||
f6458f215f | |||
c7e0d547c4 | |||
fa54cfa0c3 | |||
31ea3f737c | |||
2a8a8065a8 | |||
d3ba002313 | |||
f18fd8d959 | |||
d54f7b13fb | |||
9c8b75206c | |||
15273c7f22 | |||
0ea8217c55 | |||
1735f28f5a | |||
8de4070f7f | |||
45e64f453f | |||
62dc5f2ac6 | |||
86c85a752e | |||
3de0ebf7fd | |||
1496ba0bbd | |||
f5974f6793 | |||
928ea3bb01 | |||
b7acb1d488 | |||
360d94dd67 | |||
671a813135 | |||
c005417f9b | |||
b5ec68bda3 | |||
b4ed29094d | |||
284ab031b8 | |||
6ad5064c09 | |||
3a8e723a36 | |||
dd3484b93f |
4
.gitignore
vendored
4
.gitignore
vendored
@ -38,6 +38,7 @@ docs/reference/*/*.txt
|
|||||||
docs/reference/*/*.types
|
docs/reference/*/*.types
|
||||||
docs/reference/*/html/
|
docs/reference/*/html/
|
||||||
docs/reference/*/xml/
|
docs/reference/*/xml/
|
||||||
|
docs/reference/shell/doc-gen-*
|
||||||
gtk-doc.make
|
gtk-doc.make
|
||||||
js/misc/config.js
|
js/misc/config.js
|
||||||
intltool-extract.in
|
intltool-extract.in
|
||||||
@ -45,6 +46,7 @@ intltool-merge.in
|
|||||||
intltool-update.in
|
intltool-update.in
|
||||||
libtool
|
libtool
|
||||||
m4/
|
m4/
|
||||||
|
man/gnome-shell.1
|
||||||
omf.make
|
omf.make
|
||||||
po/*.gmo
|
po/*.gmo
|
||||||
po/gnome-shell.pot
|
po/gnome-shell.pot
|
||||||
@ -84,6 +86,8 @@ src/test-theme
|
|||||||
src/st.h
|
src/st.h
|
||||||
src/stamp-st.h
|
src/stamp-st.h
|
||||||
src/stamp-st.h.tmp
|
src/stamp-st.h.tmp
|
||||||
|
src/st-scroll-view-fade-generated.c
|
||||||
|
src/stamp-st-scroll-view-fade-generated.c
|
||||||
stamp-h1
|
stamp-h1
|
||||||
tests/run-test.sh
|
tests/run-test.sh
|
||||||
xmldocs.make
|
xmldocs.make
|
||||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "src/gvc"]
|
||||||
|
path = src/gvc
|
||||||
|
url = git://git.gnome.org/libgnome-volume-control
|
@ -1,7 +1,11 @@
|
|||||||
# Point to our macro directory and pick up user flags from the environment
|
# Point to our macro directory and pick up user flags from the environment
|
||||||
ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
|
ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
|
||||||
|
|
||||||
SUBDIRS = data js src browser-plugin tests po man docs
|
SUBDIRS = data js src browser-plugin tests po docs
|
||||||
|
|
||||||
|
if ENABLE_MAN
|
||||||
|
SUBDIRS += man
|
||||||
|
endif
|
||||||
|
|
||||||
EXTRA_DIST = \
|
EXTRA_DIST = \
|
||||||
.project \
|
.project \
|
||||||
@ -12,6 +16,7 @@ EXTRA_DIST = \
|
|||||||
# These are files checked into Git that we don't want to distribute
|
# These are files checked into Git that we don't want to distribute
|
||||||
DIST_EXCLUDE = \
|
DIST_EXCLUDE = \
|
||||||
.gitignore \
|
.gitignore \
|
||||||
|
.gitmodules \
|
||||||
gnome-shell.doap \
|
gnome-shell.doap \
|
||||||
HACKING \
|
HACKING \
|
||||||
MAINTAINERS \
|
MAINTAINERS \
|
||||||
@ -21,4 +26,4 @@ distcheck-hook:
|
|||||||
@echo "Checking disted files against files in git"
|
@echo "Checking disted files against files in git"
|
||||||
@$(srcdir)/tools/check-for-missing.py $(srcdir) $(distdir) $(DIST_EXCLUDE)
|
@$(srcdir)/tools/check-for-missing.py $(srcdir) $(distdir) $(DIST_EXCLUDE)
|
||||||
|
|
||||||
DISTCHECK_CONFIGURE_FLAGS = --enable-gtk-doc
|
DISTCHECK_CONFIGURE_FLAGS = --enable-gtk-doc --enable-man
|
||||||
|
400
NEWS
400
NEWS
@ -1,3 +1,403 @@
|
|||||||
|
3.8.0
|
||||||
|
=====
|
||||||
|
* Remove blur and desaturation from lock screen [Jasper; #696322]
|
||||||
|
* Remove scroll view fade near edges [Adel; #696404]
|
||||||
|
* dateMenu: Open calendar component when using Evolution [Florian; #696432]
|
||||||
|
* Fix unlocking on fast user switch [Cosimo; #696287]
|
||||||
|
* Tweak screen shield animation [Rui; #696380]
|
||||||
|
* Fix major memory leak when changing backgrounds [Ray; #696157]
|
||||||
|
* Miscellaneous bug fixes [Jasper, Adel, Florian; #696199, #696212, #696422,
|
||||||
|
#696447, #696235]
|
||||||
|
|
||||||
|
Contributors:
|
||||||
|
Giovanni Campagna, Cosimo Cecchi, Adel Gadllah, Rui Matos, Florian Müllner,
|
||||||
|
Jasper St. Pierre, Ray Strode
|
||||||
|
|
||||||
|
Translations:
|
||||||
|
Alexandre Franke [fr], Victor Ibragimov [tg], Arash Mousavi [fa],
|
||||||
|
Gabor Kelemen [hu], Sandeep Sheshrao Shedmake [mr], ManojKumar Giri [or],
|
||||||
|
Shantha kumar [ta], Rajesh Ranjan [hi], Stas Solovey [ru],
|
||||||
|
Shankar Prasad [kn], Dušan Kazik [sk], Ihar Hrachyshka [be],
|
||||||
|
Wouter Bolsterlee [nl], Kris Thomsen [da], Jiro Matsuzawa [ja],
|
||||||
|
Daniel Korostil [uk], Ani Peter [ml], Krishnababu Krothapalli [te],
|
||||||
|
Mantas Kriaučiūnas [lt], Praveen Illa [te]
|
||||||
|
|
||||||
|
3.7.92
|
||||||
|
======
|
||||||
|
* Drop fallback lock implementation [Florian; #693403]
|
||||||
|
* Don't let the user trigger message-tray when in fullscreen [Jasper; #694997]
|
||||||
|
* Scroll search results when using keynav [Jasper; #689681]
|
||||||
|
* Allow raising the shield by starting to type the password [Jasper; #686740]
|
||||||
|
* Improve tracking of fullscreen windows [Owen; #649748]
|
||||||
|
* Improve animation of new windows in overview [Giovanni; #695582]
|
||||||
|
* workspace switcher: Animate new workspaces created by DND [Giovanni; #685285]
|
||||||
|
* Give user time to read messages on login screen [Ray; #694688]
|
||||||
|
* Misc bug fixes and cleanups [Jasper, Ray, Florian, Cosimo, Giovanni, Adel,
|
||||||
|
Stef, Takao, Rui, Neil; #695154, #694993, #695272, #691578, #694321, #695338,
|
||||||
|
#695409, #695458, #695526, #695601, #695471, #695324, #695650, #695656,
|
||||||
|
#695659, #695485, #695395, #694951, #695824, #695841, #695801, #694321,
|
||||||
|
#693708, #695800, #695607, #695882, #691578, #685851, #694371, #690857,
|
||||||
|
#694092, #695747, #696007, #693438, #696064, #696102
|
||||||
|
|
||||||
|
Contributors:
|
||||||
|
Giovanni Campagna, Cosimo Cecchi, Allan Day, Takao Fujiwara, Adel Gadllah,
|
||||||
|
Tim Lunn, Rui Matos, Florian Müllner, Neil Roberts, Jasper St. Pierre,
|
||||||
|
Ray Strode, Stef Walter, Colin Walters, Owen W. Taylor
|
||||||
|
|
||||||
|
Translations:
|
||||||
|
Nilamdyuti Goswami [as], Chao-Hsiung Liao [zh_HK, zh_TW],
|
||||||
|
Yuri Myasoedov [ru], Gheyret Kenji [ug], Baurzhan Muftakhidinov [kk],
|
||||||
|
Ville-Pekka Vainio [fi], Matej Urbančič [sl],
|
||||||
|
Мирослав Николић [sr, sr@latin], Rūdolfs Mazurs [lv], Christian Kirbach [de],
|
||||||
|
Andika Triwidada [id], Gil Forcada [ca], Mattias Põldaru [et],
|
||||||
|
Duarte Loreto [pt], Adam Matoušek [cs], Changwoo Ryu [ko],
|
||||||
|
Ihar Hrachyshka [be], Carles Ferrando [ca@valencia], Sweta Kothari [gu]
|
||||||
|
|
||||||
|
3.7.91
|
||||||
|
======
|
||||||
|
* overview: Fade out controls during DND that are not targets [Cosimo; #686984]
|
||||||
|
* overview: Keep open when a Control key is held [Florian; #686984]
|
||||||
|
* Improve login screen => session transition [Ray; #694321]
|
||||||
|
* Center application grid horizontally [Florian; #694261]
|
||||||
|
* Fix hiding panel when fullscreen windows span multiple monitors [Adel; 646861]
|
||||||
|
* Tweak thresholds of pressure barrier [Jasper; #694467]
|
||||||
|
* Tweak window picker layout [Jasper; #694902]
|
||||||
|
* Expose key grab DBus API to gnome-settings-daemon [Florian; #643111]
|
||||||
|
* Don't always show message tray in overview, add message indicator
|
||||||
|
[Cosimo; #687787]
|
||||||
|
* Tweak startup animation [Ray; #694326]
|
||||||
|
* Add OSD popups and expose them to gnome-settings-daemon [Florian; #613543]
|
||||||
|
* Move loupe icon to the start of the search entry [Jasper; #695069]
|
||||||
|
* Don't show the input switcher with less than 2 items [Rui; 695000]
|
||||||
|
* Fix auto-completion of system modals immediately upon display [Stef; #692937]
|
||||||
|
* Ignore workspaces in alt-tab [Florian; #661156]
|
||||||
|
* Disable copying text from password entries [Florian; #695104]
|
||||||
|
* Use standard styling for ibus candidate popups [Allan; #694796]
|
||||||
|
* Fix calendar changing height on month changes [Giovanni; #641383]
|
||||||
|
* Port the hot corner to use pressure barriers [Jasper; #663661]
|
||||||
|
* Misc bug fixes and cleanups: [Hashem, Giovanni, Alban, Jasper, Cosimo,
|
||||||
|
Florian, Adel, Daniel, Matthias, Ray, Rui, Guillaume, Stef; #685849, #690643,
|
||||||
|
#694292, #693814, #694234, #694365, #694287, #694336, #694256, #694261,
|
||||||
|
#663601, #694441, #694284, #694463, #694475, #687248, #694394, #694320,
|
||||||
|
#694701, #694784, #694858, #694906, #694327, #694876, #694905, #694969,
|
||||||
|
#694970, #694988, #695006, #695001, #694998, #695023, #695002, #695073,
|
||||||
|
#695126, #687748, #694837, #693907, #679851, #694988]
|
||||||
|
|
||||||
|
Contributors:
|
||||||
|
Giovanni Campagna, Cosimo Cecchi, Matthias Clasen, Alban Crequy, Allan Day,
|
||||||
|
Guillaume Desmottes, Adel Gadllah, Rui Matos, Daniel Mustieles,
|
||||||
|
Hashem Nasarat, Jasper St. Pierre, Ray Strode, Stef Walter
|
||||||
|
|
||||||
|
Translations:
|
||||||
|
Yuri Myasoedov [ru], Adam Matoušek [cs], Piotr Drąg [pl], Matej Urbančič [sl],
|
||||||
|
Sweta Kothari [gu], Kjartan Maraas [nb], Nguyễn Thái Ngọc Duy [vi],
|
||||||
|
Chao-Hsiung Liao [zh_HK, zh_TW], Dimitris Spingos [el],
|
||||||
|
Inaki Larranaga Murgoitio [eu], Luca Ferretti [it], A S Alam [pa],
|
||||||
|
Gheyret Kenji [ug], Stas Solovey [ru], Enrico Nicoletto [pt_BR],
|
||||||
|
Fran Diéguez [gl], Daniel Mustieles [es], Aurimas Černius [lt]
|
||||||
|
|
||||||
|
3.7.90
|
||||||
|
======
|
||||||
|
* Let GNOME Shell work on EGL and GLES2 [Neil; #693225, #693438, #693339]
|
||||||
|
* Implement middle-click paste [Jasper; #645019]
|
||||||
|
* Fix top bar transition between session modes [Rui; #692966]
|
||||||
|
* Trigger the message tray with downward pressure [Jasper; #677215]
|
||||||
|
* Don't ask for a password on shutdown [Adel; #693385]
|
||||||
|
* Add a context menu to the message tray [Adel; #691035, #693887]
|
||||||
|
* Use unicode formatting in the date menu [Matthias; #689251]
|
||||||
|
* Use proper ellipsis instead of three dots [Jeremy; #689542]
|
||||||
|
* Tweak screen shield animation [Giovanni; #691964]
|
||||||
|
* Always hide the OSK when showing the message tray [Florian; #662687]
|
||||||
|
* Support sound in notifications [Giovanni; #642831]
|
||||||
|
* Place application popup menus above chrome [Jasper; #633620]
|
||||||
|
* Hide overview elements while searching [Cosimo; #682050]
|
||||||
|
* Implement updated IBus candidate popup designs [Rui; #691902]
|
||||||
|
* Add support for enable-animations preference [Cosimo; #655746]
|
||||||
|
* Don't always show the message tray in the overview [Cosimo; #693987]
|
||||||
|
* Improve arrangement of window previews [Adel; #690313]
|
||||||
|
* Remove builtin settings provider [Giovanni; #690824]
|
||||||
|
* Minimize fullscreen windows when they end up in the background [Adel; #693991]
|
||||||
|
* Add context menu to the background actor [Jasper; #681540]
|
||||||
|
* Handle backgrounds in the shell, improve startup animation [Ray; #682429]
|
||||||
|
* Hide universal access menu when not needed [Giovanni; #681528]
|
||||||
|
* Implement updated app picker designs [Florian; #694192]
|
||||||
|
* Improve login manager -> session transition [Ray; #694062]
|
||||||
|
* Don't use a grid layout in window picker [Adel; #694210]
|
||||||
|
* Use scroll wheel for workspace switching rather than zoom [Florian; #686639]
|
||||||
|
* Misc bug fixes and cleanups: [Jasper, Florian, Debarshi, Adel, Matthias,
|
||||||
|
Giovanni, Daiki, Rico, Bastien, Cosimo, Ray, Allan, Antonio; #693284,
|
||||||
|
#692680, #691746, #693303, #693162, #693161, #693522, #693385, #691715,
|
||||||
|
#688915, #689106, #682429, #693570, #693737, #693458, #692845, 693836,
|
||||||
|
#681540, #679925, #688227, #692773, #693909, #683288, #693854, #693746,
|
||||||
|
#693931, #693924, #693940, #693970, #693935, #693937, #693974, #693936,
|
||||||
|
#693975, #693822, #694030, #685849, #694052, #694035, #694038, #694079,
|
||||||
|
#694064, #681735, #694100, #694057, #694070, #693572, #693896, #686984,
|
||||||
|
#694123, #694125, #693756, #693757, #687556, #694215, 694062, #694227,
|
||||||
|
#694240, #694234, #694264, 694276, 694282, #694241, #689394, #694202,
|
||||||
|
#694265, #694289, #691806, #694290, #694296]
|
||||||
|
|
||||||
|
Contributors:
|
||||||
|
Jeremy Bicha, Giovanni Campagna, Cosimo Cecchi, Matthias Clasen, Allan Day,
|
||||||
|
António Fernandes, Adel Gadllah, Rui Matos, Florian Müllner, Bastien Nocera,
|
||||||
|
Debarshi Ray, Neil Roberts, Jasper St. Pierre, Ray Strode, Rico Tzschichholz,
|
||||||
|
Daiki Ueno
|
||||||
|
|
||||||
|
Translations:
|
||||||
|
Yasumichi Akahoshi [ja], Yoji TOYODA [ja], Dušan Kazik [sk],
|
||||||
|
Wouter Bolsterlee [nl], Matej Urbančič [sl], Gheyret Kenji [ug],
|
||||||
|
Ivaylo Valkov [bg], Daniel Korostil [uk], Gheyret Kenji [ug],
|
||||||
|
Daniel Mustieles [es], Anish A [ml], Gil Forcada [ca],
|
||||||
|
Carles Ferrando [ca@valencia], Мирослав Николић [sr, sr@latin],
|
||||||
|
Aurimas Černius [lt], Rafael Ferreira [pt_BR], Fran Diéguez [gl],
|
||||||
|
Piotr Drąg [pl], Luca Ferretti [it], A S Alam [pa]
|
||||||
|
|
||||||
|
3.7.5
|
||||||
|
=====
|
||||||
|
* MessageTray: pass keyboard events to tray icons [Giovanni; #687425]
|
||||||
|
* network: add support for virtual devices (vlan, bond, bridge) [Dan; #677144]
|
||||||
|
* gdm: Allow right-clicking buttons for left-handed users [Jasper; #688748]
|
||||||
|
* Make list search results span all available horizontal space [Tanner; #691967]
|
||||||
|
* Make Show-Applications button depress when held down [Hashem; #692319]
|
||||||
|
* Set a max width on search results [Cosimo; #692453]
|
||||||
|
* Reserve scrollbar allocation for automatic policy [Cosimo; #686881]
|
||||||
|
* Improve scaling algorithm for window thumbnails [Jasper; #686944]
|
||||||
|
* Fix launching settings panels after g-c-c changes [Jasper; #692483]
|
||||||
|
* Stop launching applications from empty searches [Hashem; #692391]
|
||||||
|
* Implement per-source notification filtering [Giovanni; #685926]
|
||||||
|
* ScreenShield: Omit ActiveChanged() signal at end of fade [Giovanni; #691964]
|
||||||
|
* ScreenShield: Lower the shield on idle before locking [Giovanni; #692560]
|
||||||
|
* Make previews of minimized windows translucent in overview [Florian; #692999]
|
||||||
|
* windowManager: Respect icon geometry when minimizing [Florian; #692997]
|
||||||
|
* ScreenShield: Only show lock icon when actually locked [Giovanni; #693007]
|
||||||
|
* general: Use & instead of 'and' for Settings panels [Jeremy; #689590]
|
||||||
|
* network: Add support for new ModemManager1 interface [Aleksander; #687359]
|
||||||
|
* network: Handle LTE-only modems as GSM ones [Aleksander; #688144]
|
||||||
|
* mobile-providers: Port to libnm-gtk [Aleksander; #688943]
|
||||||
|
* general: Consistently use Title Case in top bar [Jeremy; #689589]
|
||||||
|
* panel: Add :overview pseudo class while in overview [Florian; #693218]
|
||||||
|
* sessionMode: Add support for mode-specific styling [Florian; #693219]
|
||||||
|
* loginManager: Make suspend a NOP in the ConsoleKit patch [Florian; #693162]
|
||||||
|
* screenShield: Inhibit suspend until the screen is locked [Florian; #686482]
|
||||||
|
* Misc bug fixes and cleanups [Jasper, Giovanni, Rui, Cosimo, Florian, Stefano,
|
||||||
|
Adel, Yanko; #691745, #691731, #690171, #689091, #691976, #691963, #684279,
|
||||||
|
#692052, #692091, #642831, #692454, #692715, #692678, #692723, #692677,
|
||||||
|
#683986, #692693, #692749, #692948, #692995, #692996, #692994, #677215,
|
||||||
|
#692586, #693067, #693031, #693049, #643111, #693161, #693220]
|
||||||
|
|
||||||
|
Contributors:
|
||||||
|
Jeremy Bicha, Giovanni Campagna, Cosimo Cecchi, Tanner Doshier,
|
||||||
|
Stefano Facchini, Adel Gadllah, Yanko Kaneti, Rui Matos, Aleksander Morgado,
|
||||||
|
Florian Müllner, Hashem Nasarat, Jasper St. Pierre, Dan Winship
|
||||||
|
|
||||||
|
Translations:
|
||||||
|
Duarte Loreto [pt], Daniel Mustieles [es], Kjartan Maraas [nb],
|
||||||
|
Nilamdyuti Goswami [as], Мирослав Николић [sr,sr@latin],
|
||||||
|
Tobias Endrigkeit [de], Fabio Tomat [fur], Matej Urbančič [sl], A S Alam [pa],
|
||||||
|
Inaki Larranaga Murgoitio [eu], Piotr Drąg [pl], Wouter Bolsterlee [nl],
|
||||||
|
Gheyret Kenji [ug], Yaron Shahrabani [he], Chao-Hsiung Liao [zh_HK,zh_TW],
|
||||||
|
Milo Casagrande [it], Benjamin Steinwender [de]
|
||||||
|
|
||||||
|
3.7.4.1
|
||||||
|
=======
|
||||||
|
* userMenu: Use show-full-name-in-top-bar setting [Bastien; #689561]
|
||||||
|
* dateMenu: Add "Open Clocks" entry [Mathieu; #644390]
|
||||||
|
* screenshot: Immediately show the flash spot [Jasper; #691875]
|
||||||
|
* Misc. bug fixes [Rico, Jeremy]
|
||||||
|
|
||||||
|
Contributors:
|
||||||
|
Jeremy Bicha, Mathieu Bridon, Bastien Nocera, Jasper St. Pierre,
|
||||||
|
Rico Tzschichholz
|
||||||
|
|
||||||
|
Translations:
|
||||||
|
Ihar Hrachyshka [be]
|
||||||
|
|
||||||
|
3.7.4
|
||||||
|
=====
|
||||||
|
* Make menu separators crisp [Giovanni, Allan; #641745]
|
||||||
|
* power: Update for new D-Bus name [Bastien; #690506]
|
||||||
|
* Add smooth scrolling support [Jasper; #687573]
|
||||||
|
* Tweak notification layout [Allan; #688506]
|
||||||
|
* Ping the active window when using the app menu [Giovanni; #684340]
|
||||||
|
* Make password entries insensitive after submission [Jasper; #690594, #690895]
|
||||||
|
* Honor lock-delay GSettings key [Giovanni, Matthias; #690766, #691170]
|
||||||
|
* Use text/calendar preferred app as the calendar app [Giovanni; #690767]
|
||||||
|
* lookingGlass: Move to an inspect() function [Jasper; #690726]
|
||||||
|
* Make OSK animation quicker, snappier [Rui; #688642]
|
||||||
|
* Allow to close chat notifications with Escape [Jasper; #690897]
|
||||||
|
* Honor org.gnome.desktop.screensaver.user-switch-enabled [Giovanni; #691042]
|
||||||
|
* Add a SelectArea() DBus method [Cosimo; #687954]
|
||||||
|
* Support non-absolute paths when saving screenshots [Cosimo; #688004]
|
||||||
|
* OSK: Fix extended keys popups [Rui; #674955]
|
||||||
|
* Don't hide or show the keyboard immediately [Rui; #688646]
|
||||||
|
* Improve padding in power menu [Giovanni; #689297]
|
||||||
|
* Add per-window input source switching [Rui; #691414]
|
||||||
|
* Misc bug fixes and cleanups [Rico, Jasper, Giovanni, Rui, Florian, Dan;
|
||||||
|
#690608, #690589, #690539, #687081, #690667, #690665, #690666, #685856,
|
||||||
|
#690858, #690895, #680414, #690965, #691019, #690590, #681376, #690180,
|
||||||
|
#685513, #689263, #691553, #691720, #691743, #691750]
|
||||||
|
|
||||||
|
Contributors:
|
||||||
|
Giovanni Campagna, Cosimo Cecchi, Matthias Clasen, Allan Day, Rui Matos,
|
||||||
|
Florian Müllner, Bastien Nocera, Jasper St. Pierre, Rico Tzschichholz,
|
||||||
|
Dan Winship
|
||||||
|
|
||||||
|
Translations:
|
||||||
|
Matej Urbančič [sl], Kjartan Maraas [nb], Mattias Põldaru [et],
|
||||||
|
Yaron Shahrabani [he], Aurimas Černius [lt], Khaled Hosny [ar],
|
||||||
|
Fran Diéguez [gl], Daniel Mustieles [es], Piotr Drąg [pl], Balázs Úr [hu],
|
||||||
|
Baurzhan Muftakhidinov [kk], Tobias Endrigkeit [de], Dušan Kazik [sk],
|
||||||
|
Aron Xu [zh_CN], Gheyret Kenji [ug]
|
||||||
|
|
||||||
|
3.7.3
|
||||||
|
=====
|
||||||
|
* Add 'No Messages' label when message tray is empty [Victoria; #686738]
|
||||||
|
* Use better icons in Ctrl-Alt-Tab popup [Stéphane; #641303]
|
||||||
|
* Show the OSK on the monitor where the focused window lives [Giovanni; #685856]
|
||||||
|
* Highlight window clone and caption when hovered [Giovanni, Marc; #665310]
|
||||||
|
* Improve login process indication [Stéphane; #687113]
|
||||||
|
* Omit empty categories in apps view [Stéphane; #687970]
|
||||||
|
* Style panel differently according to mode [Florian; #684573]
|
||||||
|
* Make it possible to hide the user name [Matthias; #688577]
|
||||||
|
* Consolidate and improve chat connection notifications [Giovanni; #687213]
|
||||||
|
* Improve notification scrollbar appearance [Carlos; #688393]
|
||||||
|
* Fade scroll view fade near scrolling edges [Jasper; #689249]
|
||||||
|
* Add a read-only org.gnome.Shell.Mode property [Debarshi; #689300]
|
||||||
|
* Don't close message tray after using context menus [Giovanni; #689296]
|
||||||
|
* Port swipe-scrolling to ClutterPanAction [Jasper, Florian; #689062, #689552]
|
||||||
|
* Remember state of 'Remember Password' checkbox [Ron; #688039]
|
||||||
|
* Improve timestamp format in chat notifications [Carlos; #680989]
|
||||||
|
* Improve style of missed-messages counter [Carlos; #686472]
|
||||||
|
* Omit connection failure notifications if cancelled by user [Giovanni; #684823]
|
||||||
|
* Add window-based Alt-Tab popup [Florian; #688913]
|
||||||
|
* Support external session mode definitions [Florian; #689304]
|
||||||
|
* Support session-mode-specific extensions [Florian; #689305]
|
||||||
|
* Support 'parentMode' property in session modes [Florian; #689308]
|
||||||
|
* Support a new org.gnome.ShellSearchProvider2 DBus interface
|
||||||
|
[Cosimo; #689735, #690009]
|
||||||
|
* Add "windows" to Ctrl-Alt-Tab popup [Jasper; #689653]
|
||||||
|
* Port PopupMenu to GrabHelper [Jasper; #689109, #689954]
|
||||||
|
* Show headphone icon when headphones are plugged in [Giovanni; #675902]
|
||||||
|
* Display (non-app) search results as list [Tanner, Cosimo; #681797]
|
||||||
|
* Skip diacritical marks in search terms [Aleksander; #648587]
|
||||||
|
* Expose all engine options in input sources [Giovanni, Rui; #682318]
|
||||||
|
* Add input source switcher popup [Rui; #682315]
|
||||||
|
* Add minimal support for InfiniBand in network menu [Dan; #677150]
|
||||||
|
* Misc bug fixes and cleanups [Sebastian, Aleksander, Giovanni, Tim, Cosimo,
|
||||||
|
Florian, Matthias, Rui, Lionel, Colin, Piotr, Guillaume, Bastien, Tanner,
|
||||||
|
Carlos, Stéphane, Jakub; #688422, #688379, #688750, #688771, #686800,
|
||||||
|
#688133, #688895, #688966, #683986, #688004, #689108, #689029, #683449,
|
||||||
|
#688196, #689304, #689243, #689295, #689325, #689400, #679168, #689568,
|
||||||
|
#689537, #689528, #689749, #689789, #689353, #689820, #689868, #689778,
|
||||||
|
#689959, #688589, #688589, #689955, #687250, #689965, #690046, #690049,
|
||||||
|
#689884, #682286, #690173, #690174, #672941, #689876, #687881, #690171,
|
||||||
|
#690241, #690312, #690175, #687955, #650843, #688234, #690427
|
||||||
|
|
||||||
|
Contributors:
|
||||||
|
Giovanni Campagna, Cosimo Cecchi, Matthias Clasen, Stéphane Démurget,
|
||||||
|
Guillaume Desmottes, Tanner Doshier, Piotr Drąg, Sebastian Keller,
|
||||||
|
Lionel Landwerlin, Tim Lunn, Victoria Martínez de la Cruz, Aleksander Morgado,
|
||||||
|
Florian Müllner, Bastien Nocera, Marc Plano-Lesay, Carlos Soriano Sánchez,
|
||||||
|
Jakub Steiner, Jasper St. Pierre, Colin Walters, Dan Winship, Ron Yorston
|
||||||
|
|
||||||
|
Translations:
|
||||||
|
Yuri Myasoedov [ru], Wouter Bolsterlee [nl], Yaron Shahrabani [he],
|
||||||
|
Nilamdyuti Goswami [as], Ani Peter [ml], Kjartan Maraas [nb],
|
||||||
|
Dr.T.Vasudevan [ta], A S Alam [pa], Shankar Prasad [kn], Khaled Hosny [ar],
|
||||||
|
Daniel Mustieles [es], Dušan Kazik [sk]
|
||||||
|
|
||||||
|
3.7.2
|
||||||
|
=====
|
||||||
|
* Enforce RTL in he for messages that might end up as LTR [Florian; #686630]
|
||||||
|
* gdm: Move logo into the panel [Florian; #685852]
|
||||||
|
* Hide notifications when closed button is clicked [Jasper, Florian; #682237]
|
||||||
|
* Tweak screenShield animations [Rui; #686745]
|
||||||
|
* Restore Fittsability of summary items in message tray [Florian; #686474]
|
||||||
|
* Save screencasts as recent item [Ray; #680647]
|
||||||
|
* overview: Resize window captions on content change [Giovanni, Alex; #620874]
|
||||||
|
* App search: Match GenericName too [Matthias; #687121]
|
||||||
|
* runDialog: Better match style of other modal dialogs [Florian, Allan; #687127]
|
||||||
|
* Improve the button insensitive style [Stéphane; #687110]
|
||||||
|
* network: Don't use a global switch for all VPN connections [Giovanni; #682929]
|
||||||
|
* appMenu: Update on icon theme changes [Florian; #687224]
|
||||||
|
* Show 'Log out' in more situations [Matthias; #686736]
|
||||||
|
* Add a setting to force the 'Log out' menuitem [Matthias; #686057]
|
||||||
|
* overview: Improve styling of search box [Stéphane; #686479]
|
||||||
|
* Implement 'disable-user-list' in login screen [Ray; #660660]
|
||||||
|
* Fix auto-scroll to bottom in chat notifications [Sjoerd; #686571]
|
||||||
|
* Show feedback notifications when user is busy [Stéphane; #662900]
|
||||||
|
* Disable login button when there is no input [Stéphane; #687112]
|
||||||
|
* Use non-linear overview shade for background [Giovanni, Pierre-Eric; #669798]
|
||||||
|
* Reduce blocking in compositor thread [Simon, Jasper; #687465]
|
||||||
|
* network: new country-specific type to gather providers [Aleksander; #687356]
|
||||||
|
* Update man page [Matthias; #680601]
|
||||||
|
* st-entry: Change the pointer cursor on enter/leave events [Thomas; #687130]
|
||||||
|
* screenShield: Blur and desaturate the background [Giovanni, Cosimo; #682536]
|
||||||
|
* Change height of chat notifications to have more context [Carlos; #665255]
|
||||||
|
* screenShield: Account for motion velocity when hiding [Giovanni; #682537]
|
||||||
|
* screenShield: hide the cursor while the lock screen is on [Giovanni; #682535]
|
||||||
|
* Support remote search provider settings [Cosimo; #687491]
|
||||||
|
* unlockDialog: Improve label of confirmation button [Stéphane; #687656]
|
||||||
|
* userMenu: Rename "System Settings" item to "Settings" [Elad; #687738]
|
||||||
|
* messageTray: Add keybinding to focus current notification [Stéphane; #652082]
|
||||||
|
* Remove shell-screen-grabber [Neil; #685915]
|
||||||
|
* main: Stop using Metacity's keybinding files [Florian; #687672]
|
||||||
|
* Bluetooth: Remove ObexFTP functionality [Bastien; #688160]
|
||||||
|
* a11y: Also set WM theme when HighContrast is switched on [Cosimo; #688256]
|
||||||
|
* network: Rework multiple NIC support [Giovanni; #677142]
|
||||||
|
* Rework keybindings to allow selective blocking/processing [Florian; #688202]
|
||||||
|
* recorder: Show indicator on primary monitor [Adel; #688470]
|
||||||
|
* recorder: Set frame duration to fix broken video headers [Adel; #688487]
|
||||||
|
* Misc. bugfixes and cleanups [Florian, Jasper, Giovanni, Matthew, Stéphane,
|
||||||
|
Allan, Daiki, Owen, Alejandro, Jean-François, Cosimo, Sebastian, Adel, Alban;
|
||||||
|
#686484, #686728, #686805, #686574, #686763, #682428, #687132, #685239,
|
||||||
|
#687189, #687226, #658091, #670687, #687457, #687242, #687287, #687020,
|
||||||
|
#686583, #661194, #687491, #657315, #687958, #683986, #688089, #687708,
|
||||||
|
#686530, #684810, #688181, #688475, #688557, #688507, #638351]
|
||||||
|
|
||||||
|
Contributors:
|
||||||
|
Elad Alfassa, Matthew Barnes, Alban Browaeys, Giovanni Campagna,
|
||||||
|
Cosimo Cecchi, Matthias Clasen, Allan Day, Stéphane Démurget,
|
||||||
|
Jean-François Fortin Tam, Adel Gadllah, Alex Hultman, Sebastian Keller,
|
||||||
|
Rui Matos, Simon McVittie, Aleksander Morgado, Florian Müllner,
|
||||||
|
Bastien Nocera, Pierre-Eric Pelloux-Prayer, Alejandro Piñeiro, Neil Roberts,
|
||||||
|
Sjoerd Simons, Carlos Soriano Sánchez, Jasper St. Pierre, Ray Strode,
|
||||||
|
Owen Taylor, Daiki Ueno, Thomas Wood
|
||||||
|
|
||||||
|
Translations:
|
||||||
|
Dušan Kazik [sk], Pavol Klačanský [sk], Piotr Drąg [pl], Yuri Myasoedov [ru],
|
||||||
|
Marek Černocký [cs], Kjartan Maraas [nb], Wolfgang Stöggl [de],
|
||||||
|
Yaron Shahrabani [he], Fran Diéguez [gl], Mattias Põldaru [et]
|
||||||
|
|
||||||
|
3.7.1
|
||||||
|
=====
|
||||||
|
* Add shortcut to open application view directly [Jeremy; #685738]
|
||||||
|
* Expose '<Super>F10' shortcut in System Settings [Florian; #672909]
|
||||||
|
* Clean up timestamp format in chat notifications [Carlos; #680989]
|
||||||
|
* loginScreen: Add support for 'disable-restart-buttons' [Florian; #686247]
|
||||||
|
* Update textures automatically on file changes [Florian; #679268]
|
||||||
|
* Implement org.gnome.ScreenSaver.GetActiveTime [Giovanni; #686064]
|
||||||
|
* Add missing translations for GSetting schema [Giovanni; #686413]
|
||||||
|
* Hide workspace switcher completely when it's not necessary [Seif; #686483]
|
||||||
|
* Explicitly load gnome-screensaver when not running GDM [Tim; #683060]
|
||||||
|
* Port to GnomeIdleMonitor [Jasper; #682224]
|
||||||
|
* Set Empathy as preferred handler when delegating channels [Xavier; #686296]
|
||||||
|
* Allow testing GDM login dialog from the session [Giovanni; #683725]
|
||||||
|
* Use all available space for windows in window picker [Jasper, Pierre-Eric;
|
||||||
|
#582650]
|
||||||
|
* Use logind for suspend if available [Florian; #686482]
|
||||||
|
* Misc. fixes and cleanups [Jasper, Florian, Adel, Rui; #677426, #680426,
|
||||||
|
#686233, #686241, #686318, #686240, #686484, #686002, #684650, #686487]
|
||||||
|
|
||||||
|
Contributors:
|
||||||
|
Jeremy Bicha, Giovanni Campagna, Xavier Claessens, Adel Gadllah, Seif Lotfy,
|
||||||
|
Tim Lunn, Rui Matos, Florian Müllner, Pierre-Eric Pelloux-Prayer,
|
||||||
|
Carlos Soriano, Jasper St. Pierre
|
||||||
|
|
||||||
|
Translations:
|
||||||
|
Andika Triwidada [id], Matej Urbančič [sl], Ihar Hrachyshka [be],
|
||||||
|
Daniel Mustieles [es], Fran Diéguez [gl], Takayuki KUSANO [ja],
|
||||||
|
Мирослав Николић [sr, sr@latin], Dušan Kazik [sk], Tom Tryfonidis [el]
|
||||||
|
|
||||||
3.6.1
|
3.6.1
|
||||||
=====
|
=====
|
||||||
* dash: Make padding even on the top/bottom of the dash [Jasper; #684619]
|
* dash: Make padding even on the top/bottom of the dash [Jasper; #684619]
|
||||||
|
@ -13,6 +13,14 @@ PKG_NAME="gnome-shell"
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Fetch submodules if needed
|
||||||
|
if test ! -f src/gvc/Makefile.am;
|
||||||
|
then
|
||||||
|
echo "+ Setting up submodules"
|
||||||
|
git submodule init
|
||||||
|
fi
|
||||||
|
git submodule update
|
||||||
|
|
||||||
which gnome-autogen.sh || {
|
which gnome-autogen.sh || {
|
||||||
echo "You need to install gnome-common from GNOME Git (or from"
|
echo "You need to install gnome-common from GNOME Git (or from"
|
||||||
echo "your OS vendor's package manager)."
|
echo "your OS vendor's package manager)."
|
||||||
|
@ -153,8 +153,6 @@ NP_Initialize(NPNetscapeFuncs *pfuncs, NPPluginFuncs *plugin)
|
|||||||
/* global initialization routine, called once when plugin
|
/* global initialization routine, called once when plugin
|
||||||
is loaded */
|
is loaded */
|
||||||
|
|
||||||
g_type_init ();
|
|
||||||
|
|
||||||
g_debug ("plugin loaded");
|
g_debug ("plugin loaded");
|
||||||
|
|
||||||
memcpy (&funcs, pfuncs, sizeof (funcs));
|
memcpy (&funcs, pfuncs, sizeof (funcs));
|
||||||
|
141
configure.ac
141
configure.ac
@ -1,5 +1,5 @@
|
|||||||
AC_PREREQ(2.63)
|
AC_PREREQ(2.63)
|
||||||
AC_INIT([gnome-shell],[3.6.1],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell])
|
AC_INIT([gnome-shell],[3.8.0],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell])
|
||||||
|
|
||||||
AC_CONFIG_HEADERS([config.h])
|
AC_CONFIG_HEADERS([config.h])
|
||||||
AC_CONFIG_SRCDIR([src/shell-global.c])
|
AC_CONFIG_SRCDIR([src/shell-global.c])
|
||||||
@ -16,8 +16,6 @@ m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])
|
|||||||
|
|
||||||
# Checks for programs.
|
# Checks for programs.
|
||||||
AC_PROG_CC
|
AC_PROG_CC
|
||||||
# Needed for per-target cflags, like in gnomeshell-taskpanel
|
|
||||||
AM_PROG_CC_C_O
|
|
||||||
|
|
||||||
# Initialize libtool
|
# Initialize libtool
|
||||||
LT_PREREQ([2.2.6])
|
LT_PREREQ([2.2.6])
|
||||||
@ -36,6 +34,8 @@ AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE",
|
|||||||
|
|
||||||
PKG_PROG_PKG_CONFIG([0.22])
|
PKG_PROG_PKG_CONFIG([0.22])
|
||||||
|
|
||||||
|
AC_PATH_PROG([XSLTPROC], [xsltproc])
|
||||||
|
|
||||||
GLIB_GSETTINGS
|
GLIB_GSETTINGS
|
||||||
|
|
||||||
# Get a value to substitute into gnome-shell.in
|
# Get a value to substitute into gnome-shell.in
|
||||||
@ -52,30 +52,30 @@ AC_MSG_CHECKING([for GStreamer (needed for recording functionality)])
|
|||||||
if $PKG_CONFIG --exists gstreamer-1.0 '>=' $GSTREAMER_MIN_VERSION ; then
|
if $PKG_CONFIG --exists gstreamer-1.0 '>=' $GSTREAMER_MIN_VERSION ; then
|
||||||
AC_MSG_RESULT(yes)
|
AC_MSG_RESULT(yes)
|
||||||
build_recorder=true
|
build_recorder=true
|
||||||
recorder_modules="gstreamer-1.0 gstreamer-base-1.0 x11"
|
recorder_modules="gstreamer-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
|
else
|
||||||
AC_MSG_RESULT(no)
|
AC_MSG_RESULT(no)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
AM_CONDITIONAL(BUILD_RECORDER, $build_recorder)
|
AM_CONDITIONAL(BUILD_RECORDER, $build_recorder)
|
||||||
|
|
||||||
CLUTTER_MIN_VERSION=1.11.11
|
CLUTTER_MIN_VERSION=1.13.4
|
||||||
GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1
|
GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1
|
||||||
GJS_MIN_VERSION=1.33.2
|
GJS_MIN_VERSION=1.35.4
|
||||||
MUTTER_MIN_VERSION=3.6.1
|
MUTTER_MIN_VERSION=3.8.0
|
||||||
GTK_MIN_VERSION=3.3.9
|
GTK_MIN_VERSION=3.7.9
|
||||||
GIO_MIN_VERSION=2.31.6
|
GIO_MIN_VERSION=2.35.0
|
||||||
LIBECAL_MIN_VERSION=3.5.3
|
LIBECAL_MIN_VERSION=3.5.3
|
||||||
LIBEDATASERVER_MIN_VERSION=3.5.3
|
LIBEDATASERVER_MIN_VERSION=3.5.3
|
||||||
LIBEDATASERVERUI_MIN_VERSION=3.5.3
|
|
||||||
TELEPATHY_GLIB_MIN_VERSION=0.17.5
|
TELEPATHY_GLIB_MIN_VERSION=0.17.5
|
||||||
TELEPATHY_LOGGER_MIN_VERSION=0.2.4
|
|
||||||
POLKIT_MIN_VERSION=0.100
|
POLKIT_MIN_VERSION=0.100
|
||||||
STARTUP_NOTIFICATION_MIN_VERSION=0.11
|
STARTUP_NOTIFICATION_MIN_VERSION=0.11
|
||||||
GCR_MIN_VERSION=3.3.90
|
GCR_MIN_VERSION=3.3.90
|
||||||
GNOME_DESKTOP_REQUIRED_VERSION=3.5.1
|
GNOME_DESKTOP_REQUIRED_VERSION=3.7.90
|
||||||
GNOME_MENUS_REQUIRED_VERSION=3.5.3
|
GNOME_MENUS_REQUIRED_VERSION=3.5.3
|
||||||
|
NETWORKMANAGER_MIN_VERSION=0.9.6
|
||||||
|
PULSE_MIN_VERS=2.0
|
||||||
|
|
||||||
# Collect more than 20 libraries for a prize!
|
# Collect more than 20 libraries for a prize!
|
||||||
PKG_CHECK_MODULES(GNOME_SHELL, gio-unix-2.0 >= $GIO_MIN_VERSION
|
PKG_CHECK_MODULES(GNOME_SHELL, gio-unix-2.0 >= $GIO_MIN_VERSION
|
||||||
@ -87,43 +87,26 @@ PKG_CHECK_MODULES(GNOME_SHELL, gio-unix-2.0 >= $GIO_MIN_VERSION
|
|||||||
libgnome-menu-3.0 >= $GNOME_MENUS_REQUIRED_VERSION
|
libgnome-menu-3.0 >= $GNOME_MENUS_REQUIRED_VERSION
|
||||||
$recorder_modules
|
$recorder_modules
|
||||||
gdk-x11-3.0 libsoup-2.4
|
gdk-x11-3.0 libsoup-2.4
|
||||||
gl
|
|
||||||
clutter-x11-1.0 >= $CLUTTER_MIN_VERSION
|
clutter-x11-1.0 >= $CLUTTER_MIN_VERSION
|
||||||
clutter-glx-1.0 >= $CLUTTER_MIN_VERSION
|
clutter-glx-1.0 >= $CLUTTER_MIN_VERSION
|
||||||
libstartup-notification-1.0 >= $STARTUP_NOTIFICATION_MIN_VERSION
|
libstartup-notification-1.0 >= $STARTUP_NOTIFICATION_MIN_VERSION
|
||||||
gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION
|
gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION
|
||||||
libcanberra
|
libcanberra libcanberra-gtk3
|
||||||
telepathy-glib >= $TELEPATHY_GLIB_MIN_VERSION
|
telepathy-glib >= $TELEPATHY_GLIB_MIN_VERSION
|
||||||
telepathy-logger-0.2 >= $TELEPATHY_LOGGER_MIN_VERSION
|
|
||||||
polkit-agent-1 >= $POLKIT_MIN_VERSION xfixes
|
polkit-agent-1 >= $POLKIT_MIN_VERSION xfixes
|
||||||
libnm-glib libnm-util gnome-keyring-1
|
libnm-glib libnm-util >= $NETWORKMANAGER_MIN_VERSION
|
||||||
gcr-3 >= $GCR_MIN_VERSION
|
libnm-gtk >= $NETWORKMANAGER_MIN_VERSION
|
||||||
gnome-desktop-3.0 >= $GNOME_DESKTOP_REQUIRED_VERSION)
|
libsecret-unstable gcr-3 >= $GCR_MIN_VERSION)
|
||||||
|
|
||||||
PKG_CHECK_MODULES(SHELL_PERF_HELPER, gtk+-3.0 gio-2.0)
|
|
||||||
|
|
||||||
PKG_CHECK_MODULES(SHELL_HOTPLUG_SNIFFER, gio-2.0 gdk-pixbuf-2.0)
|
|
||||||
|
|
||||||
PKG_CHECK_MODULES(BROWSER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION json-glib-1.0 >= 0.13.2)
|
|
||||||
|
|
||||||
GNOME_KEYBINDINGS_KEYSDIR=`$PKG_CONFIG --variable keysdir gnome-keybindings`
|
|
||||||
AC_SUBST([GNOME_KEYBINDINGS_KEYSDIR])
|
|
||||||
|
|
||||||
GOBJECT_INTROSPECTION_CHECK([$GOBJECT_INTROSPECTION_MIN_VERSION])
|
|
||||||
|
|
||||||
saved_CFLAGS=$CFLAGS
|
|
||||||
saved_LIBS=$LIBS
|
|
||||||
CFLAGS=$GNOME_SHELL_CFLAGS
|
|
||||||
LIBS=$GNOME_SHELL_LIBS
|
|
||||||
AC_CHECK_FUNCS(JS_NewGlobalObject XFixesCreatePointerBarrier)
|
|
||||||
CFLAGS=$saved_CFLAGS
|
|
||||||
LIBS=$saved_LIBS
|
|
||||||
|
|
||||||
PKG_CHECK_MODULES(GNOME_SHELL_JS, gio-2.0 gjs-internals-1.0 >= $GJS_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.2 x11)
|
PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-3.0 libcroco-0.6 >= 0.6.8 x11)
|
||||||
|
PKG_CHECK_MODULES(SHELL_PERF_HELPER, gtk+-3.0 gio-2.0)
|
||||||
|
PKG_CHECK_MODULES(SHELL_HOTPLUG_SNIFFER, gio-2.0 gdk-pixbuf-2.0)
|
||||||
|
PKG_CHECK_MODULES(BROWSER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION json-glib-1.0 >= 0.13.2)
|
||||||
PKG_CHECK_MODULES(TRAY, gtk+-3.0)
|
PKG_CHECK_MODULES(TRAY, gtk+-3.0)
|
||||||
PKG_CHECK_MODULES(GVC, libpulse libpulse-mainloop-glib gobject-2.0)
|
PKG_CHECK_MODULES(GVC, libpulse >= $PULSE_MIN_VERS libpulse-mainloop-glib gobject-2.0)
|
||||||
PKG_CHECK_MODULES(DESKTOP_SCHEMAS, gsettings-desktop-schemas >= 3.5.4)
|
PKG_CHECK_MODULES(DESKTOP_SCHEMAS, gsettings-desktop-schemas >= 3.7.4)
|
||||||
|
PKG_CHECK_MODULES(CARIBOU, caribou-1.0 >= 0.4.8)
|
||||||
|
|
||||||
AC_MSG_CHECKING([for bluetooth support])
|
AC_MSG_CHECKING([for bluetooth support])
|
||||||
PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 3.1.0],
|
PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 3.1.0],
|
||||||
@ -139,36 +122,14 @@ PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 3.1.0],
|
|||||||
AC_SUBST([HAVE_BLUETOOTH],[0])
|
AC_SUBST([HAVE_BLUETOOTH],[0])
|
||||||
AC_MSG_RESULT([no])])
|
AC_MSG_RESULT([no])])
|
||||||
|
|
||||||
PKG_CHECK_MODULES(CALENDAR_SERVER, libecal-1.2 >= $LIBECAL_MIN_VERSION libedataserver-1.2 >= $LIBEDATASERVER_MIN_VERSION libedataserverui-3.0 >= $LIBEDATASERVERUI_MIN_VERSION gio-2.0)
|
PKG_CHECK_MODULES(CALENDAR_SERVER, libecal-1.2 >= $LIBECAL_MIN_VERSION libedataserver-1.2 >= $LIBEDATASERVER_MIN_VERSION gio-2.0)
|
||||||
AC_SUBST(CALENDAR_SERVER_CFLAGS)
|
AC_SUBST(CALENDAR_SERVER_CFLAGS)
|
||||||
AC_SUBST(CALENDAR_SERVER_LIBS)
|
AC_SUBST(CALENDAR_SERVER_LIBS)
|
||||||
|
|
||||||
AC_ARG_WITH(systemd,
|
GNOME_KEYBINDINGS_KEYSDIR=`$PKG_CONFIG --variable keysdir gnome-keybindings`
|
||||||
AS_HELP_STRING([--with-systemd],
|
AC_SUBST([GNOME_KEYBINDINGS_KEYSDIR])
|
||||||
[Add systemd support]),
|
|
||||||
[with_systemd=$withval], [with_systemd=auto])
|
|
||||||
|
|
||||||
PKG_CHECK_MODULES(SYSTEMD,
|
GOBJECT_INTROSPECTION_CHECK([$GOBJECT_INTROSPECTION_MIN_VERSION])
|
||||||
[libsystemd-login libsystemd-daemon],
|
|
||||||
[have_systemd=yes], [have_systemd=no])
|
|
||||||
|
|
||||||
if test "x$with_systemd" = "xauto" ; then
|
|
||||||
if test x$have_systemd = xno ; then
|
|
||||||
use_systemd=no
|
|
||||||
else
|
|
||||||
use_systemd=yes
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
use_systemd=$with_systemd
|
|
||||||
fi
|
|
||||||
|
|
||||||
if test "x$use_systemd" = "xyes"; then
|
|
||||||
if test "x$have_systemd" = "xno"; then
|
|
||||||
AC_MSG_ERROR([Systemd support explicitly required, but systemd not found])
|
|
||||||
fi
|
|
||||||
|
|
||||||
AC_DEFINE(WITH_SYSTEMD, 1, [systemd support])
|
|
||||||
fi
|
|
||||||
|
|
||||||
MUTTER_GIR_DIR=`$PKG_CONFIG --variable=girdir libmutter`
|
MUTTER_GIR_DIR=`$PKG_CONFIG --variable=girdir libmutter`
|
||||||
MUTTER_TYPELIB_DIR=`$PKG_CONFIG --variable=typelibdir libmutter`
|
MUTTER_TYPELIB_DIR=`$PKG_CONFIG --variable=typelibdir libmutter`
|
||||||
@ -195,45 +156,22 @@ fi
|
|||||||
|
|
||||||
# Sets GLIB_GENMARSHAL and GLIB_MKENUMS
|
# Sets GLIB_GENMARSHAL and GLIB_MKENUMS
|
||||||
AM_PATH_GLIB_2_0()
|
AM_PATH_GLIB_2_0()
|
||||||
G_IR_SCANNER=`$PKG_CONFIG --variable=g_ir_scanner gobject-introspection-1.0`
|
|
||||||
AC_SUBST(G_IR_SCANNER)
|
|
||||||
G_IR_COMPILER=`$PKG_CONFIG --variable=g_ir_compiler gobject-introspection-1.0`
|
|
||||||
AC_SUBST(G_IR_COMPILER)
|
|
||||||
G_IR_GENERATE=`$PKG_CONFIG --variable=g_ir_generate gobject-introspection-1.0`
|
|
||||||
AC_SUBST(G_IR_GENERATE)
|
|
||||||
GIRDIR=`$PKG_CONFIG --variable=girdir gobject-introspection-1.0`
|
|
||||||
AC_SUBST(GIRDIR)
|
|
||||||
TYPELIBDIR="$($PKG_CONFIG --variable=typelibdir gobject-introspection-1.0)"
|
|
||||||
AC_SUBST(TYPELIBDIR)
|
|
||||||
|
|
||||||
GTK_DOC_CHECK([1.15], [--flavour no-tmpl])
|
GTK_DOC_CHECK([1.15], [--flavour no-tmpl])
|
||||||
|
|
||||||
# Stay command-line compatible with the gnome-common configure option. Here
|
AC_ARG_ENABLE(man,
|
||||||
# minimum/yes/maximum are the same, however.
|
[AS_HELP_STRING([--enable-man],
|
||||||
AC_ARG_ENABLE(compile_warnings,
|
[generate man pages [default=yes]])],,
|
||||||
AS_HELP_STRING([--enable-compile-warnings=@<:@no/minimum/yes/maximum/error@:>@],[Turn on compiler warnings]),,
|
enable_man=yes)
|
||||||
enable_compile_warnings=error)
|
if test "$enable_man" != no; then
|
||||||
|
AC_PATH_PROG([XSLTPROC], [xsltproc])
|
||||||
changequote(,)dnl
|
if test -z "$XSLTPROC"; then
|
||||||
if test "$enable_compile_warnings" != no ; then
|
AC_MSG_ERROR([xsltproc is required for --enable-man])
|
||||||
if test "x$GCC" = "xyes"; then
|
|
||||||
case " $CFLAGS " in
|
|
||||||
*[\ \ ]-Wall[\ \ ]*) ;;
|
|
||||||
*) CFLAGS="$CFLAGS -Wall" ;;
|
|
||||||
esac
|
|
||||||
case " $CFLAGS " in
|
|
||||||
*[\ \ ]-Wmissing-prototypes[\ \ ]*) ;;
|
|
||||||
*) CFLAGS="$CFLAGS -Wmissing-prototypes" ;;
|
|
||||||
esac
|
|
||||||
if test "$enable_compile_warnings" = error ; then
|
|
||||||
case " $CFLAGS " in
|
|
||||||
*[\ \ ]-Werror[\ \ ]*) ;;
|
|
||||||
*) CFLAGS="$CFLAGS -Werror -Wno-error=deprecated-declarations" ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
changequote([,])dnl
|
AM_CONDITIONAL(ENABLE_MAN, test "$enable_man" != no)
|
||||||
|
|
||||||
|
GNOME_COMPILE_WARNINGS([error])
|
||||||
|
|
||||||
AC_ARG_ENABLE(jhbuild-wrapper-script,
|
AC_ARG_ENABLE(jhbuild-wrapper-script,
|
||||||
AS_HELP_STRING([--enable-jhbuild-wrapper-script],[Make "gnome-shell" script work for jhbuild]),,enable_jhbuild_wrapper_script=no)
|
AS_HELP_STRING([--enable-jhbuild-wrapper-script],[Make "gnome-shell" script work for jhbuild]),,enable_jhbuild_wrapper_script=no)
|
||||||
@ -254,6 +192,7 @@ AC_CONFIG_FILES([
|
|||||||
js/Makefile
|
js/Makefile
|
||||||
src/calendar-server/evolution-calendar.desktop.in
|
src/calendar-server/evolution-calendar.desktop.in
|
||||||
src/Makefile
|
src/Makefile
|
||||||
|
src/gvc/Makefile
|
||||||
browser-plugin/Makefile
|
browser-plugin/Makefile
|
||||||
tests/Makefile
|
tests/Makefile
|
||||||
po/Makefile.in
|
po/Makefile.in
|
||||||
|
@ -8,5 +8,14 @@
|
|||||||
<KeyListEntry name="toggle-message-tray"
|
<KeyListEntry name="toggle-message-tray"
|
||||||
_description="Show the message tray"/>
|
_description="Show the message tray"/>
|
||||||
|
|
||||||
|
<KeyListEntry name="focus-active-notification"
|
||||||
|
_description="Focus the active notification"/>
|
||||||
|
|
||||||
|
<KeyListEntry name="toggle-application-view"
|
||||||
|
_description="Show all applications"/>
|
||||||
|
|
||||||
|
<KeyListEntry name="open-application-menu"
|
||||||
|
_description="Open the application menu"/>
|
||||||
|
|
||||||
</KeyListEntries>
|
</KeyListEntries>
|
||||||
|
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
wandadir = $(pkgdatadir)
|
||||||
|
dist_wanda_DATA = wanda.png
|
||||||
|
|
||||||
desktopdir=$(datadir)/applications
|
desktopdir=$(datadir)/applications
|
||||||
desktop_DATA = gnome-shell.desktop gnome-shell-extension-prefs.desktop
|
desktop_DATA = gnome-shell.desktop gnome-shell-extension-prefs.desktop
|
||||||
|
|
||||||
@ -11,7 +14,10 @@ desktop_DATA = gnome-shell.desktop gnome-shell-extension-prefs.desktop
|
|||||||
@INTLTOOL_DESKTOP_RULE@
|
@INTLTOOL_DESKTOP_RULE@
|
||||||
|
|
||||||
introspectiondir = $(datadir)/dbus-1/interfaces
|
introspectiondir = $(datadir)/dbus-1/interfaces
|
||||||
introspection_DATA = org.gnome.ShellSearchProvider.xml
|
introspection_DATA = \
|
||||||
|
org.gnome.Shell.Screenshot.xml \
|
||||||
|
org.gnome.ShellSearchProvider.xml \
|
||||||
|
org.gnome.ShellSearchProvider2.xml
|
||||||
|
|
||||||
themedir = $(pkgdatadir)/theme
|
themedir = $(pkgdatadir)/theme
|
||||||
dist_theme_DATA = \
|
dist_theme_DATA = \
|
||||||
@ -32,6 +38,7 @@ dist_theme_DATA = \
|
|||||||
theme/gnome-shell.css \
|
theme/gnome-shell.css \
|
||||||
theme/logged-in-indicator.svg \
|
theme/logged-in-indicator.svg \
|
||||||
theme/message-tray-background.png \
|
theme/message-tray-background.png \
|
||||||
|
theme/more-results.svg \
|
||||||
theme/noise-texture.png \
|
theme/noise-texture.png \
|
||||||
theme/panel-button-border.svg \
|
theme/panel-button-border.svg \
|
||||||
theme/panel-button-highlight-narrow.svg \
|
theme/panel-button-highlight-narrow.svg \
|
||||||
|
128
data/org.gnome.Shell.Screenshot.xml
Normal file
128
data/org.gnome.Shell.Screenshot.xml
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
<!DOCTYPE node PUBLIC
|
||||||
|
'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
|
||||||
|
'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
|
||||||
|
<node>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
org.gnome.Shell.Screenshot:
|
||||||
|
@short_description: Screenshot interface
|
||||||
|
|
||||||
|
The interface used to capture pictures of the screen contents.
|
||||||
|
-->
|
||||||
|
<interface name="org.gnome.Shell.Screenshot">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Screenshot:
|
||||||
|
@filename: The filename for the screenshot
|
||||||
|
@include_cursor: Whether to include the cursor image or not
|
||||||
|
@flash: Whether to flash the screen or not
|
||||||
|
@success: whether the screenshot was captured
|
||||||
|
@filename_used: the file where the screenshot was saved
|
||||||
|
|
||||||
|
Takes a screenshot of the whole screen and saves it
|
||||||
|
in @filename as png image, it returns a boolean
|
||||||
|
indicating whether the operation was successful or not.
|
||||||
|
@filename can either be an absolute path or a basename, in
|
||||||
|
which case the screenshot will be saved in the $XDG_PICTURES_DIR
|
||||||
|
or the home directory if it doesn't exist. The filename used
|
||||||
|
to save the screenshot will be returned in @filename_used.
|
||||||
|
-->
|
||||||
|
<method name="Screenshot">
|
||||||
|
<arg type="b" direction="in" name="include_cursor"/>
|
||||||
|
<arg type="b" direction="in" name="flash"/>
|
||||||
|
<arg type="s" direction="in" name="filename"/>
|
||||||
|
<arg type="b" direction="out" name="success"/>
|
||||||
|
<arg type="s" direction="out" name="filename_used"/>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
ScreenshotWindow:
|
||||||
|
@include_frame: Whether to include the frame or not
|
||||||
|
@include_cursor: Whether to include the cursor image or not
|
||||||
|
@flash: Whether to flash the window area or not
|
||||||
|
@filename: The filename for the screenshot
|
||||||
|
@success: whether the screenshot was captured
|
||||||
|
@filename_used: the file where the screenshot was saved
|
||||||
|
|
||||||
|
Takes a screenshot of the focused window (optionally omitting the frame)
|
||||||
|
and saves it in @filename as png image, it returns a boolean
|
||||||
|
indicating whether the operation was successful or not.
|
||||||
|
@filename can either be an absolute path or a basename, in
|
||||||
|
which case the screenshot will be saved in the $XDG_PICTURES_DIR
|
||||||
|
or the home directory if it doesn't exist. The filename used
|
||||||
|
to save the screenshot will be returned in @filename_used.
|
||||||
|
-->
|
||||||
|
<method name="ScreenshotWindow">
|
||||||
|
<arg type="b" direction="in" name="include_frame"/>
|
||||||
|
<arg type="b" direction="in" name="include_cursor"/>
|
||||||
|
<arg type="b" direction="in" name="flash"/>
|
||||||
|
<arg type="s" direction="in" name="filename"/>
|
||||||
|
<arg type="b" direction="out" name="success"/>
|
||||||
|
<arg type="s" direction="out" name="filename_used"/>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
ScreenshotArea:
|
||||||
|
@x: the X coordinate of the area to capture
|
||||||
|
@y: the Y coordinate of the area to capture
|
||||||
|
@width: the width of the area to capture
|
||||||
|
@height: the height of the area to capture
|
||||||
|
@flash: whether to flash the area or not
|
||||||
|
@filename: the filename for the screenshot
|
||||||
|
@success: whether the screenshot was captured
|
||||||
|
@filename_used: the file where the screenshot was saved
|
||||||
|
|
||||||
|
Takes a screenshot of the passed in area and saves it
|
||||||
|
in @filename as png image, it returns a boolean
|
||||||
|
indicating whether the operation was successful or not.
|
||||||
|
@filename can either be an absolute path or a basename, in
|
||||||
|
which case the screenshot will be saved in the $XDG_PICTURES_DIR
|
||||||
|
or the home directory if it doesn't exist. The filename used
|
||||||
|
to save the screenshot will be returned in @filename_used.
|
||||||
|
-->
|
||||||
|
<method name="ScreenshotArea">
|
||||||
|
<arg type="i" direction="in" name="x"/>
|
||||||
|
<arg type="i" direction="in" name="y"/>
|
||||||
|
<arg type="i" direction="in" name="width"/>
|
||||||
|
<arg type="i" direction="in" name="height"/>
|
||||||
|
<arg type="b" direction="in" name="flash"/>
|
||||||
|
<arg type="s" direction="in" name="filename"/>
|
||||||
|
<arg type="b" direction="out" name="success"/>
|
||||||
|
<arg type="s" direction="out" name="filename_used"/>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
FlashArea:
|
||||||
|
@x: the X coordinate of the area to flash
|
||||||
|
@y: the Y coordinate of the area to flash
|
||||||
|
@width: the width of the area to flash
|
||||||
|
@height: the height of the area to flash
|
||||||
|
|
||||||
|
Renders a flash spot effect in the specified rectangle of the screen.
|
||||||
|
-->
|
||||||
|
<method name="FlashArea">
|
||||||
|
<arg type="i" direction="in" name="x"/>
|
||||||
|
<arg type="i" direction="in" name="y"/>
|
||||||
|
<arg type="i" direction="in" name="width"/>
|
||||||
|
<arg type="i" direction="in" name="height"/>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
SelectArea:
|
||||||
|
@x: the X coordinate of the selected area
|
||||||
|
@y: the Y coordinate of the selected area
|
||||||
|
@width: the width of the selected area
|
||||||
|
@height: the height of the selected area
|
||||||
|
|
||||||
|
Interactively allows the user to select a rectangular area of
|
||||||
|
the screen, and returns its coordinates.
|
||||||
|
-->
|
||||||
|
<method name="SelectArea">
|
||||||
|
<arg type="i" direction="out" name="x"/>
|
||||||
|
<arg type="i" direction="out" name="y"/>
|
||||||
|
<arg type="i" direction="out" name="width"/>
|
||||||
|
<arg type="i" direction="out" name="height"/>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
</interface>
|
||||||
|
</node>
|
@ -2,146 +2,72 @@
|
|||||||
'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
|
'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
|
||||||
'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
|
'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
|
||||||
<node>
|
<node>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
org.gnome.Shell.SearchProvider:
|
||||||
|
@short_description: Search provider interface
|
||||||
|
|
||||||
|
The interface used for integrating into GNOME Shell's search
|
||||||
|
interface. This interface is deprecated, and org.gnome.Shell.SearchProvider2 should be used instead.
|
||||||
|
-->
|
||||||
<interface name="org.gnome.Shell.SearchProvider">
|
<interface name="org.gnome.Shell.SearchProvider">
|
||||||
<doc:doc>
|
|
||||||
<doc:description>
|
|
||||||
<doc:para>
|
|
||||||
The interface used for integrating into GNOME Shell's search
|
|
||||||
interface.
|
|
||||||
</doc:para>
|
|
||||||
</doc:description>
|
|
||||||
</doc:doc>
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
GetInitialResultSet:
|
||||||
|
@terms: Array of search terms, which the provider should treat as logical AND.
|
||||||
|
@results: An array of result identifier strings representing items which match the given search terms. Identifiers must be unique within the provider's domain, but other than that may be chosen freely by the provider.
|
||||||
|
|
||||||
|
Called when the user first begins a search.
|
||||||
|
-->
|
||||||
<method name="GetInitialResultSet">
|
<method name="GetInitialResultSet">
|
||||||
<doc:doc>
|
<arg type="as" name="terms" direction="in" />
|
||||||
<doc:description>
|
<arg type="as" name="results" direction="out" />
|
||||||
<doc:para>
|
|
||||||
Called when the user first begins a search.
|
|
||||||
</doc:para>
|
|
||||||
</doc:description>
|
|
||||||
</doc:doc>
|
|
||||||
<arg type="as" direction="in">
|
|
||||||
<doc:doc>
|
|
||||||
<doc:summary>
|
|
||||||
<doc:para>
|
|
||||||
Array of search terms, which the provider should treat as
|
|
||||||
logical AND.
|
|
||||||
</doc:para>
|
|
||||||
</doc:summary>
|
|
||||||
</doc:doc>
|
|
||||||
</arg>
|
|
||||||
<arg type="as" direction="out">
|
|
||||||
<doc:doc>
|
|
||||||
<doc:summary>
|
|
||||||
<doc:para>
|
|
||||||
An array of result identifier strings representing items which
|
|
||||||
match the given search terms. Identifiers must be unique within
|
|
||||||
the provider's domain, but other than that may be chosen freely
|
|
||||||
by the provider.
|
|
||||||
</doc:para>
|
|
||||||
</doc:summary>
|
|
||||||
</doc:doc>
|
|
||||||
</arg>
|
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
GetSubsearchResultSet:
|
||||||
|
@previous_results: Array of results previously returned by GetInitialResultSet().
|
||||||
|
@terms: Array of updated search terms, which the provider should treat as logical AND.
|
||||||
|
@results: An array of result identifier strings representing items which match the given search terms. Identifiers must be unique within the provider's domain, but other than that may be chosen freely by the provider.
|
||||||
|
|
||||||
|
Called when a search is performed which is a "subsearch" of
|
||||||
|
the previous search, e.g. the method may return less results, but
|
||||||
|
not more or different results.
|
||||||
|
|
||||||
|
This allows search providers to only search through the previous
|
||||||
|
result set, rather than possibly performing a full re-query.
|
||||||
|
-->
|
||||||
<method name="GetSubsearchResultSet">
|
<method name="GetSubsearchResultSet">
|
||||||
<doc:doc>
|
<arg type="as" name="previous_results" direction="in" />
|
||||||
<doc:description>
|
<arg type="as" name="terms" direction="in" />
|
||||||
<doc:para>
|
<arg type="as" name="results" direction="out" />
|
||||||
Called when a search is performed which is a "subsearch" of
|
|
||||||
the previous search, e.g. the method may return less results, but
|
|
||||||
not more or different results.
|
|
||||||
|
|
||||||
This allows search providers to only search through the previous
|
|
||||||
result set, rather than possibly performing a full re-query.
|
|
||||||
</doc:para>
|
|
||||||
</doc:description>
|
|
||||||
</doc:doc>
|
|
||||||
<arg type="as" direction="in">
|
|
||||||
<doc:doc>
|
|
||||||
<doc:summary>
|
|
||||||
<doc:para>
|
|
||||||
Array of item identifiers
|
|
||||||
</doc:para>
|
|
||||||
</doc:summary>
|
|
||||||
</doc:doc>
|
|
||||||
</arg>
|
|
||||||
<arg type="as" direction="in">
|
|
||||||
<doc:doc>
|
|
||||||
<doc:summary>
|
|
||||||
<doc:para>
|
|
||||||
Array of updated search terms, which the provider should treat as
|
|
||||||
logical AND.
|
|
||||||
</doc:para>
|
|
||||||
</doc:summary>
|
|
||||||
</doc:doc>
|
|
||||||
</arg>
|
|
||||||
<arg type="as" direction="out">
|
|
||||||
<doc:doc>
|
|
||||||
<doc:summary>
|
|
||||||
<doc:para>
|
|
||||||
An array of result identifier strings representing items which
|
|
||||||
match the given search terms. Identifiers must be unique within
|
|
||||||
the provider's domain, but other than that may be chosen freely
|
|
||||||
by the provider.
|
|
||||||
</doc:para>
|
|
||||||
</doc:summary>
|
|
||||||
</doc:doc>
|
|
||||||
</arg>
|
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
GetResultMetas:
|
||||||
|
@identifiers: An array of result identifiers as returned by GetInitialResultSet() or GetSubsearchResultSet()
|
||||||
|
@metas: A dictionary describing the given search result, containing a human-readable 'name' (string), along with the result identifier this meta is for, 'id' (string). Optionally, either 'gicon' (a serialized GIcon) or 'icon-data' (raw image data as (iiibiiay) - width, height, rowstride, has-alpha, bits per sample, channels, data) can be specified if the result can be better served with a thumbnail of the content (such as with images). A 'description' field (string) may also be specified if more context would help the user find the desired result.
|
||||||
|
|
||||||
|
Return an array of meta data used to display each given result
|
||||||
|
-->
|
||||||
<method name="GetResultMetas">
|
<method name="GetResultMetas">
|
||||||
<doc:doc>
|
<arg type="as" name="identifiers" direction="in" />
|
||||||
<doc:description>
|
<arg type="aa{sv}" name="metas" direction="out" />
|
||||||
<doc:para>
|
|
||||||
Return an array of meta data used to display each given result
|
|
||||||
</doc:para>
|
|
||||||
</doc:description>
|
|
||||||
</doc:doc>
|
|
||||||
<arg type="as" direction="in">
|
|
||||||
<doc:doc>
|
|
||||||
<doc:summary>
|
|
||||||
<doc:para>
|
|
||||||
An array of result identifiers as returned by
|
|
||||||
GetInitialResultSet() or GetSubsearchResultSet()
|
|
||||||
</doc:para>
|
|
||||||
</doc:summary>
|
|
||||||
</doc:doc>
|
|
||||||
</arg>
|
|
||||||
<arg type="aa{sv}" direction="out">
|
|
||||||
<doc:doc>
|
|
||||||
<doc:summary>
|
|
||||||
<doc:para>
|
|
||||||
A dictionary describing the given search result, containing
|
|
||||||
'id', 'name' (both strings) and either 'icon' (a serialized
|
|
||||||
GIcon) or 'icon-data' (raw image data as (iiibiiay) - width,
|
|
||||||
height, rowstride, has-alpha, bits per sample, channels, data)
|
|
||||||
</doc:para>
|
|
||||||
</doc:summary>
|
|
||||||
</doc:doc>
|
|
||||||
</arg>
|
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
ActivateResult:
|
||||||
|
@identifier: A result identifier as returned by GetInitialResultSet() or GetSubsearchResultSet()
|
||||||
|
|
||||||
|
Called when the users chooses a given result. The result should
|
||||||
|
be displayed in the application associated with the corresponding
|
||||||
|
provider.
|
||||||
|
|
||||||
|
This method is deprecated, and providers should implement ActivateResult2()
|
||||||
|
instead.
|
||||||
|
-->
|
||||||
<method name="ActivateResult">
|
<method name="ActivateResult">
|
||||||
<doc:doc>
|
<arg type="s" name="identifier" direction="in" />
|
||||||
<doc:description>
|
|
||||||
<doc:para>
|
|
||||||
Called when the users chooses a given result. The result should
|
|
||||||
be displayed in the application associated with the corresponding
|
|
||||||
provider.
|
|
||||||
</doc:para>
|
|
||||||
</doc:description>
|
|
||||||
</doc:doc>
|
|
||||||
<arg type="s" direction="in">
|
|
||||||
<doc:doc>
|
|
||||||
<doc:summary>
|
|
||||||
<doc:para>
|
|
||||||
A result identifier as returned by GetInitialResultSet() or
|
|
||||||
GetSubsearchResultSet()
|
|
||||||
</doc:para>
|
|
||||||
</doc:summary>
|
|
||||||
</doc:doc>
|
|
||||||
</arg>
|
|
||||||
</method>
|
</method>
|
||||||
</interface>
|
</interface>
|
||||||
</node>
|
</node>
|
||||||
|
87
data/org.gnome.ShellSearchProvider2.xml
Normal file
87
data/org.gnome.ShellSearchProvider2.xml
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<!DOCTYPE node PUBLIC
|
||||||
|
'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
|
||||||
|
'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
|
||||||
|
<node>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
org.gnome.Shell.SearchProvider2:
|
||||||
|
@short_description: Search provider interface
|
||||||
|
|
||||||
|
The interface used for integrating into GNOME Shell's search
|
||||||
|
interface (version 2).
|
||||||
|
-->
|
||||||
|
<interface name="org.gnome.Shell.SearchProvider2">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
GetInitialResultSet:
|
||||||
|
@terms: Array of search terms, which the provider should treat as logical AND.
|
||||||
|
@results: An array of result identifier strings representing items which match the given search terms. Identifiers must be unique within the provider's domain, but other than that may be chosen freely by the provider.
|
||||||
|
|
||||||
|
Called when the user first begins a search.
|
||||||
|
-->
|
||||||
|
<method name="GetInitialResultSet">
|
||||||
|
<arg type="as" name="terms" direction="in" />
|
||||||
|
<arg type="as" name="results" direction="out" />
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
GetSubsearchResultSet:
|
||||||
|
@previous_results: Array of results previously returned by GetInitialResultSet().
|
||||||
|
@terms: Array of updated search terms, which the provider should treat as logical AND.
|
||||||
|
@results: An array of result identifier strings representing items which match the given search terms. Identifiers must be unique within the provider's domain, but other than that may be chosen freely by the provider.
|
||||||
|
|
||||||
|
Called when a search is performed which is a "subsearch" of
|
||||||
|
the previous search, e.g. the method may return less results, but
|
||||||
|
not more or different results.
|
||||||
|
|
||||||
|
This allows search providers to only search through the previous
|
||||||
|
result set, rather than possibly performing a full re-query.
|
||||||
|
-->
|
||||||
|
<method name="GetSubsearchResultSet">
|
||||||
|
<arg type="as" name="previous_results" direction="in" />
|
||||||
|
<arg type="as" name="terms" direction="in" />
|
||||||
|
<arg type="as" name="results" direction="out" />
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
GetResultMetas:
|
||||||
|
@identifiers: An array of result identifiers as returned by GetInitialResultSet() or GetSubsearchResultSet()
|
||||||
|
@metas: A dictionary describing the given search result, containing a human-readable 'name' (string), along with the result identifier this meta is for, 'id' (string). Optionally, either 'gicon' (a serialized GIcon) or 'icon-data' (raw image data as (iiibiiay) - width, height, rowstride, has-alpha, bits per sample, channels, data) can be specified if the result can be better served with a thumbnail of the content (such as with images). A 'description' field (string) may also be specified if more context would help the user find the desired result.
|
||||||
|
|
||||||
|
Return an array of meta data used to display each given result
|
||||||
|
-->
|
||||||
|
<method name="GetResultMetas">
|
||||||
|
<arg type="as" name="identifiers" direction="in" />
|
||||||
|
<arg type="aa{sv}" name="metas" direction="out" />
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
ActivateResult:
|
||||||
|
@identifier: A result identifier as returned by GetInitialResultSet() or GetSubsearchResultSet()
|
||||||
|
@terms: Array of search terms, which the provider should treat as logical AND.
|
||||||
|
@timestamp: A timestamp of the user interaction that triggered this call
|
||||||
|
|
||||||
|
Called when the users chooses a given result. The result should
|
||||||
|
be displayed in the application associated with the corresponding
|
||||||
|
provider. The provided search terms can be used to allow launching a full search in
|
||||||
|
the application.
|
||||||
|
-->
|
||||||
|
<method name="ActivateResult">
|
||||||
|
<arg type="s" name="identifier" direction="in" />
|
||||||
|
<arg type="as" name="terms" direction="in" />
|
||||||
|
<arg type="u" name="timestamp" direction="in" />
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
LaunchSearch:
|
||||||
|
@terms: Array of search terms, which the provider should treat as logical AND.
|
||||||
|
@timestamp: A timestamp of the user interaction that triggered this call
|
||||||
|
|
||||||
|
Asks the search provider to launch a full search in the application for the provided terms.
|
||||||
|
-->
|
||||||
|
<method name="LaunchSearch">
|
||||||
|
<arg type="as" name="terms" direction="in" />
|
||||||
|
<arg type="u" name="timestamp" direction="in" />
|
||||||
|
</method>
|
||||||
|
</interface>
|
||||||
|
</node>
|
@ -39,6 +39,14 @@
|
|||||||
will be displayed in the favorites area.
|
will be displayed in the favorites area.
|
||||||
</_description>
|
</_description>
|
||||||
</key>
|
</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">
|
<key name="command-history" type="as">
|
||||||
<default>[]</default>
|
<default>[]</default>
|
||||||
<_summary>History for command (Alt-F2) dialog</_summary>
|
<_summary>History for command (Alt-F2) dialog</_summary>
|
||||||
@ -57,6 +65,24 @@ value here is from the TpConnectionPresenceType enumeration.</_summary>
|
|||||||
<_summary>Internally used to store the last session presence status for the user. The
|
<_summary>Internally used to store the last session presence status for the user. The
|
||||||
value here is from the GsmPresenceStatus enumeration.</_summary>
|
value here is from the GsmPresenceStatus enumeration.</_summary>
|
||||||
</key>
|
</key>
|
||||||
|
<key name="always-show-log-out" type="b">
|
||||||
|
<default>false</default>
|
||||||
|
<_summary>Always show the 'Log out' menuitem in the user menu.</_summary>
|
||||||
|
<_description>
|
||||||
|
This key overrides the automatic hiding of the 'Log out'
|
||||||
|
menuitem in single-user, single-session situations.
|
||||||
|
</_description>
|
||||||
|
</key>
|
||||||
|
<key name="remember-mount-password" type="b">
|
||||||
|
<default>false</default>
|
||||||
|
<_summary>Whether to remember password for mounting encrypted or remote filesystems</_summary>
|
||||||
|
<_description>
|
||||||
|
The shell will request a password when an encrypted device or a
|
||||||
|
remote filesystem is mounted. If the password can be saved for
|
||||||
|
future use a 'Remember Password' checkbox will be present.
|
||||||
|
This key sets the default state of the checkbox.
|
||||||
|
</_description>
|
||||||
|
</key>
|
||||||
<child name="calendar" schema="org.gnome.shell.calendar"/>
|
<child name="calendar" schema="org.gnome.shell.calendar"/>
|
||||||
<child name="recorder" schema="org.gnome.shell.recorder"/>
|
<child name="recorder" schema="org.gnome.shell.recorder"/>
|
||||||
<child name="keybindings" schema="org.gnome.shell.keybindings"/>
|
<child name="keybindings" schema="org.gnome.shell.keybindings"/>
|
||||||
@ -83,6 +109,14 @@ value here is from the GsmPresenceStatus enumeration.</_summary>
|
|||||||
Keybinding to open the application menu.
|
Keybinding to open the application menu.
|
||||||
</_description>
|
</_description>
|
||||||
</key>
|
</key>
|
||||||
|
<key name="toggle-application-view" type="as">
|
||||||
|
<default>["<Super>a"]</default>
|
||||||
|
<_summary>Keybinding to open the "Show Applications" view</_summary>
|
||||||
|
<_description>
|
||||||
|
Keybinding to open the "Show Applications" view of the Activities
|
||||||
|
Overview.
|
||||||
|
</_description>
|
||||||
|
</key>
|
||||||
<key name="toggle-message-tray" type="as">
|
<key name="toggle-message-tray" type="as">
|
||||||
<default>["<Super>m"]</default>
|
<default>["<Super>m"]</default>
|
||||||
<_summary>Keybinding to toggle the visibility of the message tray</_summary>
|
<_summary>Keybinding to toggle the visibility of the message tray</_summary>
|
||||||
@ -90,6 +124,13 @@ value here is from the GsmPresenceStatus enumeration.</_summary>
|
|||||||
Keybinding to toggle the visibility of the message tray.
|
Keybinding to toggle the visibility of the message tray.
|
||||||
</_description>
|
</_description>
|
||||||
</key>
|
</key>
|
||||||
|
<key name="focus-active-notification" type="as">
|
||||||
|
<default>["<Super>n"]</default>
|
||||||
|
<_summary>Keybinding to focus the active notification</_summary>
|
||||||
|
<_description>
|
||||||
|
Keybinding to focus the active notification.
|
||||||
|
</_description>
|
||||||
|
</key>
|
||||||
<key name="toggle-recording" type="as">
|
<key name="toggle-recording" type="as">
|
||||||
<default><![CDATA[['<Control><Shift><Alt>r']]]></default>
|
<default><![CDATA[['<Control><Shift><Alt>r']]]></default>
|
||||||
<_summary>Keybinding to toggle the screen recorder</_summary>
|
<_summary>Keybinding to toggle the screen recorder</_summary>
|
||||||
@ -148,47 +189,75 @@ value here is from the GsmPresenceStatus enumeration.</_summary>
|
|||||||
</key>
|
</key>
|
||||||
</schema>
|
</schema>
|
||||||
|
|
||||||
<schema id="org.gnome.shell.overrides" path="/org/gnome/shell/overrides/">
|
<enum id="org.gnome.shell.window-switcher.AppIconMode">
|
||||||
|
<value value="1" nick="thumbnail-only"/>
|
||||||
|
<value value="2" nick="app-icon-only"/>
|
||||||
|
<value value="3" nick="both"/>
|
||||||
|
</enum>
|
||||||
|
<schema id="org.gnome.shell.window-switcher"
|
||||||
|
path="/org/gnome/shell/window-switcher/"
|
||||||
|
gettext-domain="@GETTEXT_PACKAGE@">
|
||||||
|
<key name="app-icon-mode" enum="org.gnome.shell.window-switcher.AppIconMode">
|
||||||
|
<default>'both'</default>
|
||||||
|
<_summary>The application icon mode.</_summary>
|
||||||
|
<_description>
|
||||||
|
Configures how the windows are shown in the switcher. Valid possibilities
|
||||||
|
are 'thumbnail-only' (shows a thumbnail of the window), 'app-icon-only'
|
||||||
|
(shows only the application icon) or 'both'.
|
||||||
|
</_description>
|
||||||
|
</key>
|
||||||
|
<key type="b" name="current-workspace-only">
|
||||||
|
<default>false</default>
|
||||||
|
<summary>Limit switcher to current workspace.</summary>
|
||||||
|
<description>
|
||||||
|
If true, only windows from the current workspace are shown in the switcher.
|
||||||
|
Otherwise, all windows are included.
|
||||||
|
</description>
|
||||||
|
</key>
|
||||||
|
</schema>
|
||||||
|
|
||||||
|
<schema id="org.gnome.shell.overrides" path="/org/gnome/shell/overrides/"
|
||||||
|
gettext-domain="@GETTEXT_PACKAGE@">
|
||||||
<key name="attach-modal-dialogs" type="b">
|
<key name="attach-modal-dialogs" type="b">
|
||||||
<default>true</default>
|
<default>true</default>
|
||||||
<summary>Attach modal dialog to the parent window</summary>
|
<_summary>Attach modal dialog to the parent window</_summary>
|
||||||
<description>
|
<_description>
|
||||||
This key overrides the key in org.gnome.mutter when running
|
This key overrides the key in org.gnome.mutter when running
|
||||||
GNOME Shell.
|
GNOME Shell.
|
||||||
</description>
|
</_description>
|
||||||
</key>
|
</key>
|
||||||
|
|
||||||
<key name="button-layout" type="s">
|
<key name="button-layout" type="s">
|
||||||
<default>":close"</default>
|
<default>":close"</default>
|
||||||
<summary>Arrangement of buttons on the titlebar</summary>
|
<_summary>Arrangement of buttons on the titlebar</_summary>
|
||||||
<description>
|
<_description>
|
||||||
This key overrides the key in org.gnome.desktop.wm.preferences when
|
This key overrides the key in org.gnome.desktop.wm.preferences when
|
||||||
running GNOME Shell.
|
running GNOME Shell.
|
||||||
</description>
|
</_description>
|
||||||
</key>
|
</key>
|
||||||
|
|
||||||
<key name="edge-tiling" type="b">
|
<key name="edge-tiling" type="b">
|
||||||
<default>true</default>
|
<default>true</default>
|
||||||
<summary>Enable edge tiling when dropping windows on screen edges</summary>
|
<_summary>Enable edge tiling when dropping windows on screen edges</_summary>
|
||||||
<description>
|
<_description>
|
||||||
This key overrides the key in org.gnome.mutter when running GNOME Shell.
|
This key overrides the key in org.gnome.mutter when running GNOME Shell.
|
||||||
</description>
|
</_description>
|
||||||
</key>
|
</key>
|
||||||
|
|
||||||
<key name="dynamic-workspaces" type="b">
|
<key name="dynamic-workspaces" type="b">
|
||||||
<default>true</default>
|
<default>true</default>
|
||||||
<summary>Workspaces are managed dynamically</summary>
|
<_summary>Workspaces are managed dynamically</_summary>
|
||||||
<description>
|
<_description>
|
||||||
This key overrides the key in org.gnome.mutter when running GNOME Shell.
|
This key overrides the key in org.gnome.mutter when running GNOME Shell.
|
||||||
</description>
|
</_description>
|
||||||
</key>
|
</key>
|
||||||
|
|
||||||
<key name="workspaces-only-on-primary" type="b">
|
<key name="workspaces-only-on-primary" type="b">
|
||||||
<default>true</default>
|
<default>true</default>
|
||||||
<summary>Workspaces only on primary monitor</summary>
|
<_summary>Workspaces only on primary monitor</_summary>
|
||||||
<description>
|
<_description>
|
||||||
This key overrides the key in org.gnome.mutter when running GNOME Shell.
|
This key overrides the key in org.gnome.mutter when running GNOME Shell.
|
||||||
</description>
|
</_description>
|
||||||
</key>
|
</key>
|
||||||
|
|
||||||
<key name="focus-change-on-pointer-rest" type="b">
|
<key name="focus-change-on-pointer-rest" type="b">
|
||||||
|
File diff suppressed because it is too large
Load Diff
109
data/theme/more-results.svg
Normal file
109
data/theme/more-results.svg
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
id="svg12430"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.48.3.1 r9886"
|
||||||
|
sodipodi:docname="more-results.svg">
|
||||||
|
<defs
|
||||||
|
id="defs12432" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#7a7a7a"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="1"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="1"
|
||||||
|
inkscape:cx="8.3155237"
|
||||||
|
inkscape:cy="0.89548874"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="g14642-3-0"
|
||||||
|
showgrid="false"
|
||||||
|
borderlayer="true"
|
||||||
|
inkscape:showpageshadow="false"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1376"
|
||||||
|
inkscape:window-x="1200"
|
||||||
|
inkscape:window-y="187"
|
||||||
|
inkscape:window-maximized="1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid13002" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata12435">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-1036.3622)">
|
||||||
|
<g
|
||||||
|
style="display:inline"
|
||||||
|
transform="translate(-141.99984,638.37113)"
|
||||||
|
inkscape:label="zoom-in"
|
||||||
|
id="g14642-3-0">
|
||||||
|
<path
|
||||||
|
sodipodi:type="inkscape:offset"
|
||||||
|
inkscape:radius="0"
|
||||||
|
inkscape:original="M 145.1875 400 C 144.5248 400 144 400.54899 144 401.21875 L 144 410.78125 C 144 411.45101 144.5248 412 145.1875 412 L 154.8125 412 C 155.4752 412 156 411.45101 156 410.78125 L 156 401.21875 C 156 400.54899 155.4752 400 154.8125 400 L 145.1875 400 z M 149 403 L 151 403 L 151 405 L 153 405 L 153 407 L 151 407 L 151 409 L 149 409 L 149 407 L 147 407 L 147 405 L 149 405 L 149 403 z "
|
||||||
|
xlink:href="#rect11749-5-0-1-8"
|
||||||
|
style="color:#bebebe;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;opacity:0.8"
|
||||||
|
id="path13004"
|
||||||
|
inkscape:href="#rect11749-5-0-1-8"
|
||||||
|
d="M 145.1875,400 C 144.5248,400 144,400.54899 144,401.21875 l 0,9.5625 c 0,0.66976 0.5248,1.21875 1.1875,1.21875 l 9.625,0 c 0.6627,0 1.1875,-0.54899 1.1875,-1.21875 l 0,-9.5625 C 156,400.54899 155.4752,400 154.8125,400 L 145.1875,400 z m 3.8125,3 2,0 0,2 2,0 0,2 -2,0 0,2 -2,0 0,-2 -2,0 0,-2 2,0 L 149,403 Z"
|
||||||
|
transform="translate(0,1)" />
|
||||||
|
<use
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
xlink:href="#path13004"
|
||||||
|
id="use11960"
|
||||||
|
transform="translate(1,-1)"
|
||||||
|
width="16"
|
||||||
|
height="16" />
|
||||||
|
<use
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
xlink:href="#use11960"
|
||||||
|
id="use11962"
|
||||||
|
transform="translate(-2,0)"
|
||||||
|
width="16"
|
||||||
|
height="16" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="color:#bebebe;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible"
|
||||||
|
d="M 145.1875,400 C 144.5248,400 144,400.54899 144,401.21875 l 0,9.5625 c 0,0.66976 0.5248,1.21875 1.1875,1.21875 l 9.625,0 c 0.6627,0 1.1875,-0.54899 1.1875,-1.21875 l 0,-9.5625 C 156,400.54899 155.4752,400 154.8125,400 L 145.1875,400 z m 3.8125,3 2,0 0,2 2,0 0,2 -2,0 0,2 -2,0 0,-2 -2,0 0,-2 2,0 L 149,403 Z"
|
||||||
|
id="rect11749-5-0-1-8" />
|
||||||
|
<rect
|
||||||
|
style="fill:none;stroke:none"
|
||||||
|
id="rect3620-5-4"
|
||||||
|
width="15.981825"
|
||||||
|
height="16"
|
||||||
|
x="142"
|
||||||
|
y="398"
|
||||||
|
rx="0"
|
||||||
|
ry="0" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.1 KiB |
BIN
data/wanda.png
Normal file
BIN
data/wanda.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
@ -76,9 +76,30 @@ endif
|
|||||||
# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
|
# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
|
||||||
HTML_IMAGES=
|
HTML_IMAGES=
|
||||||
|
|
||||||
|
doc-gen-org.gnome.Shell.SearchProvider.xml: $(top_srcdir)/data/org.gnome.ShellSearchProvider.xml
|
||||||
|
gdbus-codegen \
|
||||||
|
--interface-prefix org.gnome.ShellSearchProvider. \
|
||||||
|
--generate-docbook doc-gen \
|
||||||
|
$(top_srcdir)/data/org.gnome.ShellSearchProvider.xml
|
||||||
|
|
||||||
|
doc-gen-org.gnome.Shell.SearchProvider2.xml: $(top_srcdir)/data/org.gnome.ShellSearchProvider2.xml
|
||||||
|
gdbus-codegen \
|
||||||
|
--interface-prefix org.gnome.ShellSearchProvider2. \
|
||||||
|
--generate-docbook doc-gen \
|
||||||
|
$(top_srcdir)/data/org.gnome.ShellSearchProvider2.xml
|
||||||
|
|
||||||
|
doc-gen-org.gnome.Shell.Screenshot.xml: $(top_srcdir)/data/org.gnome.Shell.Screenshot.xml
|
||||||
|
gdbus-codegen \
|
||||||
|
--interface-prefix org.gnome.Shell.Screenshot. \
|
||||||
|
--generate-docbook doc-gen \
|
||||||
|
$(top_srcdir)/data/org.gnome.Shell.Screenshot.xml
|
||||||
|
|
||||||
# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
|
# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
|
||||||
# e.g. content_files=running.sgml building.sgml changes-2.0.sgml
|
# e.g. content_files=running.sgml building.sgml changes-2.0.sgml
|
||||||
content_files=
|
content_files= \
|
||||||
|
doc-gen-org.gnome.Shell.SearchProvider.xml \
|
||||||
|
doc-gen-org.gnome.Shell.SearchProvider2.xml \
|
||||||
|
doc-gen-org.gnome.Shell.Screenshot.xml
|
||||||
|
|
||||||
# SGML files where gtk-doc abbrevations (#GtkWidget) are expanded
|
# SGML files where gtk-doc abbrevations (#GtkWidget) are expanded
|
||||||
# These files must be listed here *and* in content_files
|
# These files must be listed here *and* in content_files
|
||||||
|
@ -43,12 +43,13 @@
|
|||||||
</chapter>
|
</chapter>
|
||||||
<chapter>
|
<chapter>
|
||||||
<title>Integration helpers and utilities</title>
|
<title>Integration helpers and utilities</title>
|
||||||
|
<xi:include href="doc-gen-org.gnome.Shell.SearchProvider.xml"/>
|
||||||
|
<xi:include href="doc-gen-org.gnome.Shell.SearchProvider2.xml"/>
|
||||||
<xi:include href="xml/shell-global.xml"/>
|
<xi:include href="xml/shell-global.xml"/>
|
||||||
<xi:include href="xml/shell-wm.xml"/>
|
<xi:include href="xml/shell-wm.xml"/>
|
||||||
<xi:include href="xml/shell-xfixes-cursor.xml"/>
|
<xi:include href="xml/shell-xfixes-cursor.xml"/>
|
||||||
<xi:include href="xml/shell-util.xml"/>
|
<xi:include href="xml/shell-util.xml"/>
|
||||||
<xi:include href="xml/shell-mount-operation.xml"/>
|
<xi:include href="xml/shell-mount-operation.xml"/>
|
||||||
<xi:include href="xml/shell-mobile-providers.xml"/>
|
|
||||||
<xi:include href="xml/shell-network-agent.xml"/>
|
<xi:include href="xml/shell-network-agent.xml"/>
|
||||||
<xi:include href="xml/shell-polkit-authentication-agent.xml"/>
|
<xi:include href="xml/shell-polkit-authentication-agent.xml"/>
|
||||||
<xi:include href="xml/shell-tp-client.xml"/>
|
<xi:include href="xml/shell-tp-client.xml"/>
|
||||||
|
@ -52,13 +52,6 @@ its dependencies to build from tarballs.</description>
|
|||||||
<gnome:userid>walters</gnome:userid>
|
<gnome:userid>walters</gnome:userid>
|
||||||
</foaf:Person>
|
</foaf:Person>
|
||||||
</maintainer>
|
</maintainer>
|
||||||
<maintainer>
|
|
||||||
<foaf:Person>
|
|
||||||
<foaf:name>Dan Winship</foaf:name>
|
|
||||||
<foaf:mbox rdf:resource="mailto:danw@gnome.org" />
|
|
||||||
<gnome:userid>danw</gnome:userid>
|
|
||||||
</foaf:Person>
|
|
||||||
</maintainer>
|
|
||||||
<maintainer>
|
<maintainer>
|
||||||
<foaf:Person>
|
<foaf:Person>
|
||||||
<foaf:name>Marina Zhurakhinskaya</foaf:name>
|
<foaf:name>Marina Zhurakhinskaya</foaf:name>
|
||||||
|
@ -28,6 +28,7 @@ nobase_dist_js_DATA = \
|
|||||||
misc/extensionUtils.js \
|
misc/extensionUtils.js \
|
||||||
misc/fileUtils.js \
|
misc/fileUtils.js \
|
||||||
misc/gnomeSession.js \
|
misc/gnomeSession.js \
|
||||||
|
misc/hash.js \
|
||||||
misc/history.js \
|
misc/history.js \
|
||||||
misc/jsParse.js \
|
misc/jsParse.js \
|
||||||
misc/loginManager.js \
|
misc/loginManager.js \
|
||||||
@ -38,6 +39,8 @@ nobase_dist_js_DATA = \
|
|||||||
ui/altTab.js \
|
ui/altTab.js \
|
||||||
ui/appDisplay.js \
|
ui/appDisplay.js \
|
||||||
ui/appFavorites.js \
|
ui/appFavorites.js \
|
||||||
|
ui/backgroundMenu.js \
|
||||||
|
ui/background.js \
|
||||||
ui/boxpointer.js \
|
ui/boxpointer.js \
|
||||||
ui/calendar.js \
|
ui/calendar.js \
|
||||||
ui/checkBox.js \
|
ui/checkBox.js \
|
||||||
@ -49,7 +52,6 @@ nobase_dist_js_DATA = \
|
|||||||
ui/extensionSystem.js \
|
ui/extensionSystem.js \
|
||||||
ui/extensionDownloader.js \
|
ui/extensionDownloader.js \
|
||||||
ui/environment.js \
|
ui/environment.js \
|
||||||
ui/flashspot.js \
|
|
||||||
ui/ibusCandidatePopup.js\
|
ui/ibusCandidatePopup.js\
|
||||||
ui/grabHelper.js \
|
ui/grabHelper.js \
|
||||||
ui/iconGrid.js \
|
ui/iconGrid.js \
|
||||||
@ -62,17 +64,21 @@ nobase_dist_js_DATA = \
|
|||||||
ui/main.js \
|
ui/main.js \
|
||||||
ui/messageTray.js \
|
ui/messageTray.js \
|
||||||
ui/modalDialog.js \
|
ui/modalDialog.js \
|
||||||
|
ui/separator.js \
|
||||||
ui/sessionMode.js \
|
ui/sessionMode.js \
|
||||||
ui/shellEntry.js \
|
ui/shellEntry.js \
|
||||||
ui/shellMountOperation.js \
|
ui/shellMountOperation.js \
|
||||||
ui/notificationDaemon.js \
|
ui/notificationDaemon.js \
|
||||||
|
ui/osdWindow.js \
|
||||||
ui/overview.js \
|
ui/overview.js \
|
||||||
|
ui/overviewControls.js \
|
||||||
ui/panel.js \
|
ui/panel.js \
|
||||||
ui/panelMenu.js \
|
ui/panelMenu.js \
|
||||||
ui/pointerWatcher.js \
|
ui/pointerWatcher.js \
|
||||||
ui/popupMenu.js \
|
ui/popupMenu.js \
|
||||||
ui/remoteSearch.js \
|
ui/remoteSearch.js \
|
||||||
ui/runDialog.js \
|
ui/runDialog.js \
|
||||||
|
ui/screenshot.js \
|
||||||
ui/screenShield.js \
|
ui/screenShield.js \
|
||||||
ui/scripting.js \
|
ui/scripting.js \
|
||||||
ui/search.js \
|
ui/search.js \
|
||||||
@ -85,9 +91,11 @@ nobase_dist_js_DATA = \
|
|||||||
ui/status/power.js \
|
ui/status/power.js \
|
||||||
ui/status/volume.js \
|
ui/status/volume.js \
|
||||||
ui/status/bluetooth.js \
|
ui/status/bluetooth.js \
|
||||||
|
ui/switcherPopup.js \
|
||||||
ui/tweener.js \
|
ui/tweener.js \
|
||||||
ui/unlockDialog.js \
|
ui/unlockDialog.js \
|
||||||
ui/userMenu.js \
|
ui/userMenu.js \
|
||||||
|
ui/userWidget.js \
|
||||||
ui/viewSelector.js \
|
ui/viewSelector.js \
|
||||||
ui/wanda.js \
|
ui/wanda.js \
|
||||||
ui/windowAttentionHandler.js \
|
ui/windowAttentionHandler.js \
|
||||||
|
@ -45,6 +45,7 @@ const Application = new Lang.Class({
|
|||||||
this._extensionPrefsModules = {};
|
this._extensionPrefsModules = {};
|
||||||
|
|
||||||
this._extensionIters = {};
|
this._extensionIters = {};
|
||||||
|
this._startupUuid = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
_buildModel: function() {
|
_buildModel: function() {
|
||||||
@ -203,6 +204,7 @@ const Application = new Lang.Class({
|
|||||||
_scanExtensions: function() {
|
_scanExtensions: function() {
|
||||||
let finder = new ExtensionUtils.ExtensionFinder();
|
let finder = new ExtensionUtils.ExtensionFinder();
|
||||||
finder.connect('extension-found', Lang.bind(this, this._extensionFound));
|
finder.connect('extension-found', Lang.bind(this, this._extensionFound));
|
||||||
|
finder.connect('extensions-loaded', Lang.bind(this, this._extensionsLoaded));
|
||||||
finder.scanExtensions();
|
finder.scanExtensions();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -212,6 +214,11 @@ const Application = new Lang.Class({
|
|||||||
this._extensionIters[extension.uuid] = iter;
|
this._extensionIters[extension.uuid] = iter;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_extensionsLoaded: function() {
|
||||||
|
if (this._startupUuid && this._extensionAvailable(this._startupUuid))
|
||||||
|
this._selectExtension(this._startupUuid);
|
||||||
|
this._startupUuid = null;
|
||||||
|
},
|
||||||
|
|
||||||
_onActivate: function() {
|
_onActivate: function() {
|
||||||
this._window.present();
|
this._window.present();
|
||||||
@ -232,10 +239,10 @@ const Application = new Lang.Class({
|
|||||||
// Strip off "extension:///" prefix which fakes a URI, if it exists
|
// Strip off "extension:///" prefix which fakes a URI, if it exists
|
||||||
uuid = stripPrefix(uuid, "extension:///");
|
uuid = stripPrefix(uuid, "extension:///");
|
||||||
|
|
||||||
if (!this._extensionAvailable(uuid))
|
if (this._extensionAvailable(uuid))
|
||||||
return 1;
|
this._selectExtension(uuid);
|
||||||
|
else
|
||||||
this._selectExtension(uuid);
|
this._startupUuid = uuid;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -18,11 +18,12 @@
|
|||||||
* 02111-1307, USA.
|
* 02111-1307, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const Gio = imports.gi.Gio;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
const UPowerGlib = imports.gi.UPowerGlib;
|
|
||||||
|
|
||||||
const LoginManager = imports.misc.loginManager;
|
const LoginManager = imports.misc.loginManager;
|
||||||
|
|
||||||
|
const GdmUtil = imports.gdm.util;
|
||||||
const PanelMenu = imports.ui.panelMenu;
|
const PanelMenu = imports.ui.panelMenu;
|
||||||
const PopupMenu = imports.ui.popupMenu;
|
const PopupMenu = imports.ui.popupMenu;
|
||||||
|
|
||||||
@ -33,15 +34,14 @@ const PowerMenuButton = new Lang.Class({
|
|||||||
_init: function() {
|
_init: function() {
|
||||||
/* Translators: accessible name of the power menu in the login screen */
|
/* Translators: accessible name of the power menu in the login screen */
|
||||||
this.parent('system-shutdown-symbolic', _("Power"));
|
this.parent('system-shutdown-symbolic', _("Power"));
|
||||||
this._upClient = new UPowerGlib.Client();
|
|
||||||
|
|
||||||
this._loginManager = LoginManager.getLoginManager();
|
this._loginManager = LoginManager.getLoginManager();
|
||||||
|
|
||||||
this._createSubMenu();
|
this._settings = new Gio.Settings({ schema: GdmUtil.LOGIN_SCREEN_SCHEMA });
|
||||||
|
this._settings.connect('changed::disable-restart-buttons',
|
||||||
|
Lang.bind(this, this._updateVisibility));
|
||||||
|
|
||||||
this._upClient.connect('notify::can-suspend',
|
this._createSubMenu();
|
||||||
Lang.bind(this, this._updateHaveSuspend));
|
|
||||||
this._updateHaveSuspend();
|
|
||||||
|
|
||||||
// ConsoleKit doesn't send notifications when shutdown/reboot
|
// ConsoleKit doesn't send notifications when shutdown/reboot
|
||||||
// are disabled, so we update the menu item each time the menu opens
|
// are disabled, so we update the menu item each time the menu opens
|
||||||
@ -50,15 +50,17 @@ const PowerMenuButton = new Lang.Class({
|
|||||||
if (open) {
|
if (open) {
|
||||||
this._updateHaveShutdown();
|
this._updateHaveShutdown();
|
||||||
this._updateHaveRestart();
|
this._updateHaveRestart();
|
||||||
|
this._updateHaveSuspend();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
this._updateHaveShutdown();
|
this._updateHaveShutdown();
|
||||||
this._updateHaveRestart();
|
this._updateHaveRestart();
|
||||||
|
this._updateHaveSuspend();
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateVisibility: function() {
|
_updateVisibility: function() {
|
||||||
let shouldBeVisible = (this._haveSuspend || this._haveShutdown || this._haveRestart);
|
let shouldBeVisible = (this._haveSuspend || this._haveShutdown || this._haveRestart);
|
||||||
this.actor.visible = shouldBeVisible;
|
this.actor.visible = shouldBeVisible && !this._settings.get_boolean('disable-restart-buttons');
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateHaveShutdown: function() {
|
_updateHaveShutdown: function() {
|
||||||
@ -78,9 +80,11 @@ const PowerMenuButton = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_updateHaveSuspend: function() {
|
_updateHaveSuspend: function() {
|
||||||
this._haveSuspend = this._upClient.get_can_suspend();
|
this._loginManager.canSuspend(Lang.bind(this, function(result) {
|
||||||
this._suspendItem.actor.visible = this._haveSuspend;
|
this._haveSuspend = result;
|
||||||
this._updateVisibility();
|
this._suspendItem.actor.visible = this._haveSuspend;
|
||||||
|
this._updateVisibility();
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
_createSubMenu: function() {
|
_createSubMenu: function() {
|
||||||
@ -103,8 +107,10 @@ const PowerMenuButton = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_onActivateSuspend: function() {
|
_onActivateSuspend: function() {
|
||||||
if (this._haveSuspend)
|
if (!this._haveSuspend)
|
||||||
this._upClient.suspend_sync(null);
|
return;
|
||||||
|
|
||||||
|
this._loginManager.suspend();
|
||||||
},
|
},
|
||||||
|
|
||||||
_onActivateRestart: function() {
|
_onActivateRestart: function() {
|
||||||
|
149
js/gdm/util.js
149
js/gdm/util.js
@ -1,6 +1,8 @@
|
|||||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
|
||||||
|
const Clutter = imports.gi.Clutter;
|
||||||
const Gio = imports.gi.Gio;
|
const Gio = imports.gi.Gio;
|
||||||
|
const GLib = imports.gi.GLib;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
const Mainloop = imports.mainloop;
|
const Mainloop = imports.mainloop;
|
||||||
const Signals = imports.signals;
|
const Signals = imports.signals;
|
||||||
@ -15,6 +17,7 @@ const Tweener = imports.ui.tweener;
|
|||||||
const PASSWORD_SERVICE_NAME = 'gdm-password';
|
const PASSWORD_SERVICE_NAME = 'gdm-password';
|
||||||
const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint';
|
const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint';
|
||||||
const FADE_ANIMATION_TIME = 0.16;
|
const FADE_ANIMATION_TIME = 0.16;
|
||||||
|
const CLONE_FADE_ANIMATION_TIME = 0.25;
|
||||||
|
|
||||||
const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen';
|
const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen';
|
||||||
const FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication';
|
const FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication';
|
||||||
@ -23,6 +26,10 @@ const BANNER_MESSAGE_TEXT_KEY = 'banner-message-text';
|
|||||||
const ALLOWED_FAILURES_KEY = 'allowed-failures';
|
const ALLOWED_FAILURES_KEY = 'allowed-failures';
|
||||||
|
|
||||||
const LOGO_KEY = 'logo';
|
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) {
|
function fadeInActor(actor) {
|
||||||
if (actor.opacity == 255 && actor.visible)
|
if (actor.opacity == 255 && actor.visible)
|
||||||
@ -70,6 +77,34 @@ function fadeOutActor(actor) {
|
|||||||
return hold;
|
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({
|
const ShellUserVerifier = new Lang.Class({
|
||||||
Name: 'ShellUserVerifier',
|
Name: 'ShellUserVerifier',
|
||||||
|
|
||||||
@ -83,6 +118,9 @@ const ShellUserVerifier = new Lang.Class({
|
|||||||
|
|
||||||
this._fprintManager = new Fprint.FprintManager();
|
this._fprintManager = new Fprint.FprintManager();
|
||||||
this._realmManager = new Realmd.Manager();
|
this._realmManager = new Realmd.Manager();
|
||||||
|
this._messageQueue = [];
|
||||||
|
this._messageQueueTimeoutId = 0;
|
||||||
|
this.hasPendingMessages = false;
|
||||||
|
|
||||||
this._failCounter = 0;
|
this._failCounter = 0;
|
||||||
},
|
},
|
||||||
@ -122,10 +160,73 @@ const ShellUserVerifier = new Lang.Class({
|
|||||||
this._userVerifier.run_dispose();
|
this._userVerifier.run_dispose();
|
||||||
this._userVerifier = null;
|
this._userVerifier = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._clearMessageQueue();
|
||||||
},
|
},
|
||||||
|
|
||||||
answerQuery: function(serviceName, answer) {
|
answerQuery: function(serviceName, answer) {
|
||||||
this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, 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);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_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() {
|
_checkForFingerprintReader: function() {
|
||||||
@ -145,7 +246,7 @@ const ShellUserVerifier = new Lang.Class({
|
|||||||
logError(error, where);
|
logError(error, where);
|
||||||
this._hold.release();
|
this._hold.release();
|
||||||
|
|
||||||
this.emit('show-message', _("Authentication error"), 'login-dialog-message-warning');
|
this._queueMessage(_("Authentication error"), 'login-dialog-message-warning');
|
||||||
this._verificationFailed(false);
|
this._verificationFailed(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -264,7 +365,7 @@ const ShellUserVerifier = new Lang.Class({
|
|||||||
// to indicate the user can swipe their finger instead
|
// to indicate the user can swipe their finger instead
|
||||||
this.emit('show-login-hint', _("(or swipe finger)"));
|
this.emit('show-login-hint', _("(or swipe finger)"));
|
||||||
} else if (serviceName == PASSWORD_SERVICE_NAME) {
|
} else if (serviceName == PASSWORD_SERVICE_NAME) {
|
||||||
this.emit('show-message', info, 'login-dialog-message-info');
|
this._queueMessage(info, 'login-dialog-message-info');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -273,7 +374,7 @@ const ShellUserVerifier = new Lang.Class({
|
|||||||
// users who haven't enrolled their fingerprint.
|
// users who haven't enrolled their fingerprint.
|
||||||
if (serviceName != PASSWORD_SERVICE_NAME)
|
if (serviceName != PASSWORD_SERVICE_NAME)
|
||||||
return;
|
return;
|
||||||
this.emit('show-message', problem, 'login-dialog-message-warning');
|
this._queueMessage(problem, 'login-dialog-message-warning');
|
||||||
},
|
},
|
||||||
|
|
||||||
_showRealmLoginHint: function() {
|
_showRealmLoginHint: function() {
|
||||||
@ -312,8 +413,6 @@ const ShellUserVerifier = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_onReset: function() {
|
_onReset: function() {
|
||||||
this.clear();
|
|
||||||
|
|
||||||
// Clear previous attempts to authenticate
|
// Clear previous attempts to authenticate
|
||||||
this._failCounter = 0;
|
this._failCounter = 0;
|
||||||
|
|
||||||
@ -324,27 +423,45 @@ const ShellUserVerifier = new Lang.Class({
|
|||||||
this.emit('verification-complete');
|
this.emit('verification-complete');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_cancelAndReset: function() {
|
||||||
|
this.cancel();
|
||||||
|
this._onReset();
|
||||||
|
},
|
||||||
|
|
||||||
|
_retry: function() {
|
||||||
|
this.begin(this._userName, new Batch.Hold());
|
||||||
|
},
|
||||||
|
|
||||||
_verificationFailed: function(retry) {
|
_verificationFailed: function(retry) {
|
||||||
// For Not Listed / enterprise logins, immediately reset
|
// For Not Listed / enterprise logins, immediately reset
|
||||||
// the dialog
|
// the dialog
|
||||||
// Otherwise, we allow ALLOWED_FAILURES attempts. After that, we
|
// Otherwise, we allow ALLOWED_FAILURES attempts. After that, we
|
||||||
// go back to the welcome screen.
|
// go back to the welcome screen.
|
||||||
|
|
||||||
|
this._failCounter++;
|
||||||
let canRetry = retry && this._userName &&
|
let canRetry = retry && this._userName &&
|
||||||
this._failCounter < this._settings.get_int(ALLOWED_FAILURES_KEY);
|
this._failCounter < this._settings.get_int(ALLOWED_FAILURES_KEY);
|
||||||
|
|
||||||
if (canRetry) {
|
if (canRetry) {
|
||||||
this._failCounter++;
|
if (!this._userVerifier.hasPendingMessages) {
|
||||||
|
this._retry();
|
||||||
this.clear();
|
} else {
|
||||||
this.begin(this._userName, new Batch.Hold());
|
let signalId = this._userVerifier.connect('no-more-messages',
|
||||||
|
Lang.bind(this, function() {
|
||||||
|
this._userVerifier.disconnect(signalId);
|
||||||
|
this._retry();
|
||||||
|
}));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Allow some time to see the message, then reset everything
|
if (!this._userVerifier.hasPendingMessages) {
|
||||||
Mainloop.timeout_add(3000, Lang.bind(this, function() {
|
this._cancelAndReset();
|
||||||
this.cancel();
|
} else {
|
||||||
|
let signalId = this._userVerifier.connect('no-more-messages',
|
||||||
this._onReset();
|
Lang.bind(this, function() {
|
||||||
}));
|
this._userVerifier.disconnect(signalId);
|
||||||
|
this._cancelAndReset();
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.emit('verification-failed');
|
this.emit('verification-failed');
|
||||||
|
@ -11,6 +11,7 @@ const Gio = imports.gi.Gio;
|
|||||||
const ShellJS = imports.gi.ShellJS;
|
const ShellJS = imports.gi.ShellJS;
|
||||||
|
|
||||||
const Config = imports.misc.config;
|
const Config = imports.misc.config;
|
||||||
|
const FileUtils = imports.misc.fileUtils;
|
||||||
|
|
||||||
const ExtensionType = {
|
const ExtensionType = {
|
||||||
SYSTEM: 1,
|
SYSTEM: 1,
|
||||||
@ -150,53 +151,40 @@ function installImporter(extension) {
|
|||||||
const ExtensionFinder = new Lang.Class({
|
const ExtensionFinder = new Lang.Class({
|
||||||
Name: 'ExtensionFinder',
|
Name: 'ExtensionFinder',
|
||||||
|
|
||||||
_scanExtensionsInDirectory: function(dir, type) {
|
_loadExtension: function(extensionDir, info, perUserDir) {
|
||||||
let fileEnum;
|
let fileType = info.get_file_type();
|
||||||
let file, info;
|
if (fileType != Gio.FileType.DIRECTORY)
|
||||||
try {
|
return;
|
||||||
fileEnum = dir.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null);
|
let uuid = info.get_name();
|
||||||
} catch(e) {
|
let existing = extensions[uuid];
|
||||||
if (e.domain != Gio.io_error_quark() || e.code != Gio.IOErrorEnum.NOT_FOUND)
|
if (existing) {
|
||||||
logError(e, 'Could not enumerate extensions directory');
|
log('Extension %s already installed in %s. %s will not be loaded'.format(uuid, existing.path, extensionDir.get_path()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
while ((info = fileEnum.next_file(null)) != null) {
|
let extension;
|
||||||
let fileType = info.get_file_type();
|
let type = extensionDir.has_prefix(perUserDir) ? ExtensionType.PER_USER
|
||||||
if (fileType != Gio.FileType.DIRECTORY)
|
: ExtensionType.SYSTEM;
|
||||||
continue;
|
try {
|
||||||
let uuid = info.get_name();
|
extension = createExtensionObject(uuid, extensionDir, type);
|
||||||
let extensionDir = dir.get_child(uuid);
|
} catch(e) {
|
||||||
|
logError(e, 'Could not load extension %s'.format(uuid));
|
||||||
let existing = extensions[uuid];
|
return;
|
||||||
if (existing) {
|
|
||||||
log('Extension %s already installed in %s. %s will not be loaded'.format(uuid, existing.path, extensionDir.get_path()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let extension;
|
|
||||||
try {
|
|
||||||
extension = createExtensionObject(uuid, extensionDir, type);
|
|
||||||
} catch(e) {
|
|
||||||
logError(e, 'Could not load extension %s'.format(uuid));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
this.emit('extension-found', extension);
|
|
||||||
}
|
}
|
||||||
fileEnum.close(null);
|
this.emit('extension-found', extension);
|
||||||
|
},
|
||||||
|
|
||||||
|
_extensionsLoaded: function() {
|
||||||
|
this.emit('extensions-loaded');
|
||||||
},
|
},
|
||||||
|
|
||||||
scanExtensions: function() {
|
scanExtensions: function() {
|
||||||
let userExtensionsDir = Gio.File.new_for_path(GLib.build_filenamev([global.userdatadir, 'extensions']));
|
let perUserDir = Gio.File.new_for_path(global.userdatadir);
|
||||||
this._scanExtensionsInDirectory(userExtensionsDir, ExtensionType.PER_USER);
|
FileUtils.collectFromDatadirsAsync('extensions',
|
||||||
|
{ processFile: Lang.bind(this, this._loadExtension),
|
||||||
let systemDataDirs = GLib.get_system_data_dirs();
|
loadedCallback: Lang.bind(this, this._extensionsLoaded),
|
||||||
for (let i = 0; i < systemDataDirs.length; i++) {
|
includeUserDir: true,
|
||||||
let dirPath = GLib.build_filenamev([systemDataDirs[i], 'gnome-shell', 'extensions']);
|
data: perUserDir });
|
||||||
let dir = Gio.file_new_for_path(dirPath);
|
|
||||||
if (dir.query_exists(null))
|
|
||||||
this._scanExtensionsInDirectory(dir, ExtensionType.SYSTEM);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Signals.addSignalMethods(ExtensionFinder.prototype);
|
Signals.addSignalMethods(ExtensionFinder.prototype);
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
const Gio = imports.gi.Gio;
|
const Gio = imports.gi.Gio;
|
||||||
const GLib = imports.gi.GLib;
|
const GLib = imports.gi.GLib;
|
||||||
|
const Lang = imports.lang;
|
||||||
|
const Params = imports.misc.params;
|
||||||
|
|
||||||
function listDirAsync(file, callback) {
|
function listDirAsync(file, callback) {
|
||||||
let allFiles = [];
|
let allFiles = [];
|
||||||
file.enumerate_children_async(Gio.FILE_ATTRIBUTE_STANDARD_NAME,
|
file.enumerate_children_async('standard::name,standard::type',
|
||||||
Gio.FileQueryInfoFlags.NONE,
|
Gio.FileQueryInfoFlags.NONE,
|
||||||
GLib.PRIORITY_LOW, null, function (obj, res) {
|
GLib.PRIORITY_LOW, null, function (obj, res) {
|
||||||
let enumerator = obj.enumerate_children_finish(res);
|
let enumerator = obj.enumerate_children_finish(res);
|
||||||
@ -23,6 +25,63 @@ function listDirAsync(file, callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _collectFromDirectoryAsync(dir, loadState) {
|
||||||
|
function done() {
|
||||||
|
loadState.numLoading--;
|
||||||
|
if (loadState.loadedCallback &&
|
||||||
|
loadState.numLoading == 0)
|
||||||
|
loadState.loadedCallback(loadState.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.query_info_async('standard::type', Gio.FileQueryInfoFlags.NONE,
|
||||||
|
GLib.PRIORITY_DEFAULT, null, function(object, res) {
|
||||||
|
try {
|
||||||
|
object.query_info_finish(res);
|
||||||
|
} catch (e) {
|
||||||
|
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
|
||||||
|
log(e.message);
|
||||||
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listDirAsync(dir, Lang.bind(this, function(infos) {
|
||||||
|
for (let i = 0; i < infos.length; i++)
|
||||||
|
loadState.processFile(dir.get_child(infos[i].get_name()),
|
||||||
|
infos[i], loadState.data);
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectFromDatadirsAsync(subdir, params) {
|
||||||
|
params = Params.parse(params, { includeUserDir: false,
|
||||||
|
processFile: null,
|
||||||
|
loadedCallback: null,
|
||||||
|
data: null });
|
||||||
|
let loadState = { data: params.data,
|
||||||
|
numLoading: 0,
|
||||||
|
loadedCallback: params.loadedCallback,
|
||||||
|
processFile: params.processFile };
|
||||||
|
|
||||||
|
if (params.processFile == null) {
|
||||||
|
if (params.loadedCallback)
|
||||||
|
params.loadedCallback(params.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dataDirs = GLib.get_system_data_dirs();
|
||||||
|
if (params.includeUserDir)
|
||||||
|
dataDirs.unshift(GLib.get_user_data_dir());
|
||||||
|
loadState.numLoading = dataDirs.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < dataDirs.length; i++) {
|
||||||
|
let path = GLib.build_filenamev([dataDirs[i], 'gnome-shell', subdir]);
|
||||||
|
let dir = Gio.File.new_for_path(path);
|
||||||
|
|
||||||
|
_collectFromDirectoryAsync(dir, loadState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function deleteGFile(file) {
|
function deleteGFile(file) {
|
||||||
// Work around 'delete' being a keyword in JS.
|
// Work around 'delete' being a keyword in JS.
|
||||||
return file['delete'](null);
|
return file['delete'](null);
|
||||||
|
@ -58,6 +58,7 @@ const SessionManagerIface = <interface name="org.gnome.SessionManager">
|
|||||||
<arg type="u" direction="in" />
|
<arg type="u" direction="in" />
|
||||||
<arg type="b" direction="out" />
|
<arg type="b" direction="out" />
|
||||||
</method>
|
</method>
|
||||||
|
<property name="SessionIsActive" type="b" access="read"/>
|
||||||
<signal name="InhibitorAdded">
|
<signal name="InhibitorAdded">
|
||||||
<arg type="o" direction="out"/>
|
<arg type="o" direction="out"/>
|
||||||
</signal>
|
</signal>
|
||||||
|
141
js/misc/hash.js
Normal file
141
js/misc/hash.js
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
|
||||||
|
const Lang = imports.lang;
|
||||||
|
const System = imports.system;
|
||||||
|
|
||||||
|
const Params = imports.misc.params;
|
||||||
|
|
||||||
|
// This is an implementation of EcmaScript SameValue algorithm,
|
||||||
|
// which returns true if two values are not observably distinguishable.
|
||||||
|
// It was taken from http://wiki.ecmascript.org/doku.php?id=harmony:egal
|
||||||
|
//
|
||||||
|
// In the future, we may want to use the 'is' operator instead.
|
||||||
|
function _sameValue(x, y) {
|
||||||
|
if (x === y) {
|
||||||
|
// 0 === -0, but they are not identical
|
||||||
|
return x !== 0 || 1 / x === 1 / y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NaN !== NaN, but they are identical.
|
||||||
|
// NaNs are the only non-reflexive value, i.e., if x !== x,
|
||||||
|
// then x is a NaN.
|
||||||
|
// isNaN is broken: it converts its argument to number, so
|
||||||
|
// isNaN("foo") => true
|
||||||
|
return x !== x && y !== y;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _hashers = {
|
||||||
|
object: function(o) { return o ? System.addressOf(o) : 'null'; },
|
||||||
|
function: function(f) { return System.addressOf(f); },
|
||||||
|
string: function(s) { return s; },
|
||||||
|
number: function(n) { return String(n); },
|
||||||
|
undefined: function() { return 'undefined'; },
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Map is meant to be similar in usage to ES6 Map, which is
|
||||||
|
described at http://wiki.ecmascript.org/doku.php?id=harmony:simple_maps_and_sets,
|
||||||
|
without requiring more than ES5 + Gjs extensions.
|
||||||
|
|
||||||
|
Known differences from other implementations:
|
||||||
|
Polyfills around the web usually implement HashMaps for
|
||||||
|
primitive values and reversed WeakMaps for object keys,
|
||||||
|
but we want real maps with real O(1) semantics in all cases,
|
||||||
|
and the easiest way is to have different hashers for different
|
||||||
|
types.
|
||||||
|
|
||||||
|
Known differences from the ES6 specification:
|
||||||
|
- Map is a Lang.Class, not a ES6 class, so inheritance,
|
||||||
|
prototype, sealing, etc. work differently.
|
||||||
|
- items(), keys() and values() don't return iterators,
|
||||||
|
they return actual arrays, so they incur a full copy everytime
|
||||||
|
they're called, and they don't see changes if you mutate
|
||||||
|
the table while iterating
|
||||||
|
(admittedly, the ES6 spec is a bit unclear on this, and
|
||||||
|
the reference code would just blow up)
|
||||||
|
*/
|
||||||
|
const Map = new Lang.Class({
|
||||||
|
Name: 'Map',
|
||||||
|
|
||||||
|
_init: function(iterable) {
|
||||||
|
this._pool = { };
|
||||||
|
|
||||||
|
if (iterable) {
|
||||||
|
for (let i = 0; i < iterable.length; i++) {
|
||||||
|
let [key, value] = iterable[i];
|
||||||
|
this.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_hashKey: function(key) {
|
||||||
|
let type = typeof(key);
|
||||||
|
return type + ':' + _hashers[type](key);
|
||||||
|
},
|
||||||
|
|
||||||
|
_internalGet: function(key) {
|
||||||
|
let hash = this._hashKey(key);
|
||||||
|
let node = this._pool[hash];
|
||||||
|
|
||||||
|
if (node && _sameValue(node.key, key))
|
||||||
|
return [true, node.value];
|
||||||
|
else
|
||||||
|
return [false, null];
|
||||||
|
},
|
||||||
|
|
||||||
|
get: function(key) {
|
||||||
|
return this._internalGet(key)[1];
|
||||||
|
},
|
||||||
|
|
||||||
|
has: function(key) {
|
||||||
|
return this._internalGet(key)[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
set: function(key, value) {
|
||||||
|
let hash = this._hashKey(key);
|
||||||
|
let node = this._pool[hash];
|
||||||
|
|
||||||
|
if (node) {
|
||||||
|
node.key = key;
|
||||||
|
node.value = value;
|
||||||
|
} else {
|
||||||
|
this._pool[hash] = { key: key, value: value };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
delete: function(key) {
|
||||||
|
let hash = this._hashKey(key);
|
||||||
|
let node = this._pool[hash];
|
||||||
|
|
||||||
|
if (node && _sameValue(node.key, key)) {
|
||||||
|
delete this._pool[hash];
|
||||||
|
return [node.key, node.value];
|
||||||
|
} else {
|
||||||
|
return [null, null];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
keys: function() {
|
||||||
|
let pool = this._pool;
|
||||||
|
return Object.getOwnPropertyNames(pool).map(function(hash) {
|
||||||
|
return pool[hash].key;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
values: function() {
|
||||||
|
let pool = this._pool;
|
||||||
|
return Object.getOwnPropertyNames(pool).map(function(hash) {
|
||||||
|
return pool[hash].value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
items: function() {
|
||||||
|
let pool = this._pool;
|
||||||
|
return Object.getOwnPropertyNames(pool).map(function(hash) {
|
||||||
|
return [pool[hash].key, pool[hash].value];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
size: function() {
|
||||||
|
return Object.getOwnPropertyNames(this._pool).length;
|
||||||
|
},
|
||||||
|
});
|
@ -3,7 +3,9 @@
|
|||||||
const GLib = imports.gi.GLib;
|
const GLib = imports.gi.GLib;
|
||||||
const Gio = imports.gi.Gio;
|
const Gio = imports.gi.Gio;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
|
const Mainloop = imports.mainloop;
|
||||||
const Shell = imports.gi.Shell;
|
const Shell = imports.gi.Shell;
|
||||||
|
const Signals = imports.signals;
|
||||||
|
|
||||||
const SystemdLoginManagerIface = <interface name='org.freedesktop.login1.Manager'>
|
const SystemdLoginManagerIface = <interface name='org.freedesktop.login1.Manager'>
|
||||||
<method name='PowerOff'>
|
<method name='PowerOff'>
|
||||||
@ -12,12 +14,35 @@ const SystemdLoginManagerIface = <interface name='org.freedesktop.login1.Manager
|
|||||||
<method name='Reboot'>
|
<method name='Reboot'>
|
||||||
<arg type='b' direction='in'/>
|
<arg type='b' direction='in'/>
|
||||||
</method>
|
</method>
|
||||||
|
<method name='Suspend'>
|
||||||
|
<arg type='b' direction='in'/>
|
||||||
|
</method>
|
||||||
<method name='CanPowerOff'>
|
<method name='CanPowerOff'>
|
||||||
<arg type='s' direction='out'/>
|
<arg type='s' direction='out'/>
|
||||||
</method>
|
</method>
|
||||||
<method name='CanReboot'>
|
<method name='CanReboot'>
|
||||||
<arg type='s' direction='out'/>
|
<arg type='s' direction='out'/>
|
||||||
</method>
|
</method>
|
||||||
|
<method name='CanSuspend'>
|
||||||
|
<arg type='s' direction='out'/>
|
||||||
|
</method>
|
||||||
|
<method name='Inhibit'>
|
||||||
|
<arg type='s' direction='in'/>
|
||||||
|
<arg type='s' direction='in'/>
|
||||||
|
<arg type='s' direction='in'/>
|
||||||
|
<arg type='s' direction='in'/>
|
||||||
|
<arg type='h' direction='out'/>
|
||||||
|
</method>
|
||||||
|
<method name='GetSession'>
|
||||||
|
<arg type='s' direction='in'/>
|
||||||
|
<arg type='o' direction='out'/>
|
||||||
|
</method>
|
||||||
|
<method name='ListSessions'>
|
||||||
|
<arg name='sessions' type='a(susso)' direction='out'/>
|
||||||
|
</method>
|
||||||
|
<signal name='PrepareForSleep'>
|
||||||
|
<arg type='b' direction='out'/>
|
||||||
|
</signal>
|
||||||
</interface>;
|
</interface>;
|
||||||
|
|
||||||
const SystemdLoginSessionIface = <interface name='org.freedesktop.login1.Session'>
|
const SystemdLoginSessionIface = <interface name='org.freedesktop.login1.Session'>
|
||||||
@ -43,12 +68,6 @@ const ConsoleKitManagerIface = <interface name='org.freedesktop.ConsoleKit.Manag
|
|||||||
</interface>;
|
</interface>;
|
||||||
|
|
||||||
const ConsoleKitSessionIface = <interface name='org.freedesktop.ConsoleKit.Session'>
|
const ConsoleKitSessionIface = <interface name='org.freedesktop.ConsoleKit.Session'>
|
||||||
<method name='IsActive'>
|
|
||||||
<arg type='b' direction='out' />
|
|
||||||
</method>
|
|
||||||
<signal name='ActiveChanged'>
|
|
||||||
<arg type='b' direction='out' />
|
|
||||||
</signal>
|
|
||||||
<signal name='Lock' />
|
<signal name='Lock' />
|
||||||
<signal name='Unlock' />
|
<signal name='Unlock' />
|
||||||
</interface>;
|
</interface>;
|
||||||
@ -60,6 +79,35 @@ function haveSystemd() {
|
|||||||
return GLib.access("/sys/fs/cgroup/systemd", 0) >= 0;
|
return GLib.access("/sys/fs/cgroup/systemd", 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;
|
let _loginManager = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -85,24 +133,30 @@ const LoginManagerSystemd = new Lang.Class({
|
|||||||
this._proxy = new SystemdLoginManager(Gio.DBus.system,
|
this._proxy = new SystemdLoginManager(Gio.DBus.system,
|
||||||
'org.freedesktop.login1',
|
'org.freedesktop.login1',
|
||||||
'/org/freedesktop/login1');
|
'/org/freedesktop/login1');
|
||||||
|
this._proxy.connectSignal('PrepareForSleep',
|
||||||
|
Lang.bind(this, this._prepareForSleep));
|
||||||
},
|
},
|
||||||
|
|
||||||
// Having this function is a bit of a hack since the Systemd and ConsoleKit
|
// 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
|
// session objects have different interfaces - but in both cases there are
|
||||||
// Lock/Unlock signals, and that's all we count upon at the moment.
|
// Lock/Unlock signals, and that's all we count upon at the moment.
|
||||||
getCurrentSessionProxy: function() {
|
getCurrentSessionProxy: function(callback) {
|
||||||
if (!this._currentSession) {
|
if (this._currentSession) {
|
||||||
this._currentSession = new SystemdLoginSession(Gio.DBus.system,
|
callback (this._currentSession);
|
||||||
'org.freedesktop.login1',
|
return;
|
||||||
'/org/freedesktop/login1/session/' +
|
|
||||||
GLib.getenv('XDG_SESSION_ID'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._currentSession;
|
this._proxy.GetSessionRemote(GLib.getenv('XDG_SESSION_ID'), Lang.bind(this,
|
||||||
},
|
function(result, error) {
|
||||||
|
if (error) {
|
||||||
get sessionActive() {
|
logError(error, 'Could not get a proxy for the current session');
|
||||||
return Shell.session_is_active_for_systemd();
|
} else {
|
||||||
|
this._currentSession = new SystemdLoginSession(Gio.DBus.system,
|
||||||
|
'org.freedesktop.login1',
|
||||||
|
result[0]);
|
||||||
|
callback(this._currentSession);
|
||||||
|
}
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
canPowerOff: function(asyncCallback) {
|
canPowerOff: function(asyncCallback) {
|
||||||
@ -123,14 +177,61 @@ const LoginManagerSystemd = new Lang.Class({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
canSuspend: function(asyncCallback) {
|
||||||
|
this._proxy.CanSuspendRemote(function(result, error) {
|
||||||
|
if (error)
|
||||||
|
asyncCallback(false);
|
||||||
|
else
|
||||||
|
asyncCallback(result[0] != 'no');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
listSessions: function(asyncCallback) {
|
||||||
|
this._proxy.ListSessionsRemote(function(result, error) {
|
||||||
|
if (error)
|
||||||
|
asyncCallback([]);
|
||||||
|
else
|
||||||
|
asyncCallback(result[0]);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
powerOff: function() {
|
powerOff: function() {
|
||||||
this._proxy.PowerOffRemote(true);
|
this._proxy.PowerOffRemote(true);
|
||||||
},
|
},
|
||||||
|
|
||||||
reboot: function() {
|
reboot: function() {
|
||||||
this._proxy.RebootRemote(true);
|
this._proxy.RebootRemote(true);
|
||||||
|
},
|
||||||
|
|
||||||
|
suspend: function() {
|
||||||
|
this._proxy.SuspendRemote(true);
|
||||||
|
},
|
||||||
|
|
||||||
|
inhibit: function(reason, callback) {
|
||||||
|
let inVariant = GLib.Variant.new('(ssss)',
|
||||||
|
['sleep',
|
||||||
|
'GNOME Shell',
|
||||||
|
reason,
|
||||||
|
'delay']);
|
||||||
|
this._proxy.call_with_unix_fd_list('Inhibit', inVariant, 0, -1, null, null,
|
||||||
|
Lang.bind(this, function(proxy, result) {
|
||||||
|
let fd = -1;
|
||||||
|
try {
|
||||||
|
let [outVariant, fdList] = proxy.call_with_unix_fd_list_finish(result);
|
||||||
|
fd = fdList.steal_fds(outVariant.deep_unpack())[0];
|
||||||
|
callback(new Gio.UnixInputStream({ fd: fd }));
|
||||||
|
} catch(e) {
|
||||||
|
logError(e, "Error getting systemd inhibitor");
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
_prepareForSleep: function(proxy, sender, [aboutToSuspend]) {
|
||||||
|
this.emit('prepare-for-sleep', aboutToSuspend);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Signals.addSignalMethods(LoginManagerSystemd.prototype);
|
||||||
|
|
||||||
const LoginManagerConsoleKit = new Lang.Class({
|
const LoginManagerConsoleKit = new Lang.Class({
|
||||||
Name: 'LoginManagerConsoleKit',
|
Name: 'LoginManagerConsoleKit',
|
||||||
@ -144,28 +245,23 @@ const LoginManagerConsoleKit = new Lang.Class({
|
|||||||
// Having this function is a bit of a hack since the Systemd and ConsoleKit
|
// 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
|
// session objects have different interfaces - but in both cases there are
|
||||||
// Lock/Unlock signals, and that's all we count upon at the moment.
|
// Lock/Unlock signals, and that's all we count upon at the moment.
|
||||||
getCurrentSessionProxy: function() {
|
getCurrentSessionProxy: function(callback) {
|
||||||
if (!this._currentSession) {
|
if (this._currentSession) {
|
||||||
let [currentSessionId] = this._proxy.GetCurrentSessionSync();
|
callback (this._currentSession);
|
||||||
this._currentSession = new ConsoleKitSession(Gio.DBus.system,
|
return;
|
||||||
'org.freedesktop.ConsoleKit',
|
|
||||||
currentSessionId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._currentSession;
|
this._proxy.GetCurrentSessionRemote(Lang.bind(this,
|
||||||
},
|
function(result, error) {
|
||||||
|
if (error) {
|
||||||
get sessionActive() {
|
logError(error, 'Could not get a proxy for the current session');
|
||||||
if (this._sessionActive !== undefined)
|
} else {
|
||||||
return this._sessionActive;
|
this._currentSession = new ConsoleKitSession(Gio.DBus.system,
|
||||||
|
'org.freedesktop.ConsoleKit',
|
||||||
let session = this.getCurrentSessionProxy();
|
result[0]);
|
||||||
session.connectSignal('ActiveChanged', Lang.bind(this, function(object, senderName, [isActive]) {
|
callback(this._currentSession);
|
||||||
this._sessionActive = isActive;
|
}
|
||||||
}));
|
}));
|
||||||
[this._sessionActive] = session.IsActiveSync();
|
|
||||||
|
|
||||||
return this._sessionActive;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
canPowerOff: function(asyncCallback) {
|
canPowerOff: function(asyncCallback) {
|
||||||
@ -186,12 +282,29 @@ const LoginManagerConsoleKit = new Lang.Class({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
canSuspend: function(asyncCallback) {
|
||||||
|
asyncCallback(false);
|
||||||
|
},
|
||||||
|
|
||||||
|
listSessions: function(asyncCallback) {
|
||||||
|
asyncCallback([]);
|
||||||
|
},
|
||||||
|
|
||||||
powerOff: function() {
|
powerOff: function() {
|
||||||
this._proxy.StopRemote();
|
this._proxy.StopRemote();
|
||||||
},
|
},
|
||||||
|
|
||||||
reboot: function() {
|
reboot: function() {
|
||||||
this._proxy.RestartRemote();
|
this._proxy.RestartRemote();
|
||||||
|
},
|
||||||
|
|
||||||
|
suspend: function() {
|
||||||
|
this.emit('prepare-for-sleep', true);
|
||||||
|
this.emit('prepare-for-sleep', false);
|
||||||
|
},
|
||||||
|
|
||||||
|
inhibit: function(reason, callback) {
|
||||||
|
callback(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
Signals.addSignalMethods(LoginManagerConsoleKit.prototype);
|
||||||
|
@ -2,9 +2,93 @@
|
|||||||
|
|
||||||
const Gio = imports.gi.Gio;
|
const Gio = imports.gi.Gio;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
const Shell = imports.gi.Shell;
|
const NMGtk = imports.gi.NMGtk;
|
||||||
const Signals = imports.signals;
|
const Signals = imports.signals;
|
||||||
|
|
||||||
|
// _getMobileProvidersDatabase:
|
||||||
|
//
|
||||||
|
// Gets the database of mobile providers, with references between MCCMNC/SID and
|
||||||
|
// operator name
|
||||||
|
//
|
||||||
|
let _mpd;
|
||||||
|
function _getMobileProvidersDatabase() {
|
||||||
|
if (_mpd == null) {
|
||||||
|
try {
|
||||||
|
_mpd = new NMGtk.MobileProvidersDatabase();
|
||||||
|
_mpd.init(null);
|
||||||
|
} catch (e) {
|
||||||
|
log(e.message);
|
||||||
|
_mpd = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _mpd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// _findProviderForMccMnc:
|
||||||
|
// @operator_name: operator name
|
||||||
|
// @operator_code: operator code
|
||||||
|
//
|
||||||
|
// Given an operator name string (which may not be a real operator name) and an
|
||||||
|
// operator code string, tries to find a proper operator name to display.
|
||||||
|
//
|
||||||
|
function _findProviderForMccMnc(operator_name, operator_code) {
|
||||||
|
if (operator_name) {
|
||||||
|
if (operator_name.length != 0 &&
|
||||||
|
(operator_name.length > 6 || operator_name.length < 5)) {
|
||||||
|
// this looks like a valid name, i.e. not an MCCMNC (that some
|
||||||
|
// devices return when not yet connected
|
||||||
|
return operator_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(parseInt(operator_name))) {
|
||||||
|
// name is definitely not a MCCMNC, so it may be a name
|
||||||
|
// after all; return that
|
||||||
|
return operator_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let needle;
|
||||||
|
if ((!operator_name || operator_name.length == 0) && operator_code)
|
||||||
|
needle = operator_code;
|
||||||
|
else if (operator_name && (operator_name.length == 6 || operator_name.length == 5))
|
||||||
|
needle = operator_name;
|
||||||
|
else // nothing to search
|
||||||
|
return null;
|
||||||
|
|
||||||
|
let mpd = _getMobileProvidersDatabase();
|
||||||
|
if (mpd) {
|
||||||
|
let provider = mpd.lookup_3gpp_mcc_mnc(needle);
|
||||||
|
if (provider)
|
||||||
|
return provider.get_name();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// _findProviderForSid:
|
||||||
|
// @sid: System Identifier of the serving CDMA network
|
||||||
|
//
|
||||||
|
// Tries to find the operator name corresponding to the given SID
|
||||||
|
//
|
||||||
|
function _findProviderForSid(sid) {
|
||||||
|
if (sid == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
let mpd = _getMobileProvidersDatabase();
|
||||||
|
if (mpd) {
|
||||||
|
let provider = mpd.lookup_cdma_sid(sid);
|
||||||
|
if (provider)
|
||||||
|
return provider.get_name();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Support for the old ModemManager interface (MM < 0.7)
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
// The following are not the complete interfaces, just the methods we need
|
// The following are not the complete interfaces, just the methods we need
|
||||||
// (or may need in the future)
|
// (or may need in the future)
|
||||||
|
|
||||||
@ -42,14 +126,6 @@ const ModemCdmaInterface = <interface name="org.freedesktop.ModemManager.Modem.C
|
|||||||
|
|
||||||
const ModemCdmaProxy = Gio.DBusProxy.makeProxyWrapper(ModemCdmaInterface);
|
const ModemCdmaProxy = Gio.DBusProxy.makeProxyWrapper(ModemCdmaInterface);
|
||||||
|
|
||||||
let _providersTable;
|
|
||||||
function _getProvidersTable() {
|
|
||||||
if (_providersTable)
|
|
||||||
return _providersTable;
|
|
||||||
let [providers, countryCodes] = Shell.mobile_providers_parse();
|
|
||||||
return _providersTable = providers;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ModemGsm = new Lang.Class({
|
const ModemGsm = new Lang.Class({
|
||||||
Name: 'ModemGsm',
|
Name: 'ModemGsm',
|
||||||
|
|
||||||
@ -65,7 +141,7 @@ const ModemGsm = new Lang.Class({
|
|||||||
this.emit('notify::signal-quality');
|
this.emit('notify::signal-quality');
|
||||||
}));
|
}));
|
||||||
this._proxy.connectSignal('RegistrationInfo', Lang.bind(this, function(proxy, sender, [status, code, name]) {
|
this._proxy.connectSignal('RegistrationInfo', Lang.bind(this, function(proxy, sender, [status, code, name]) {
|
||||||
this.operator_name = this._findOperatorName(name, code);
|
this.operator_name = _findProviderForMccMnc(name, code);
|
||||||
this.emit('notify::operator-name');
|
this.emit('notify::operator-name');
|
||||||
}));
|
}));
|
||||||
this._proxy.GetRegistrationInfoRemote(Lang.bind(this, function([result], err) {
|
this._proxy.GetRegistrationInfoRemote(Lang.bind(this, function([result], err) {
|
||||||
@ -75,7 +151,7 @@ const ModemGsm = new Lang.Class({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let [status, code, name] = result;
|
let [status, code, name] = result;
|
||||||
this.operator_name = this._findOperatorName(name, code);
|
this.operator_name = _findProviderForMccMnc(name, code);
|
||||||
this.emit('notify::operator-name');
|
this.emit('notify::operator-name');
|
||||||
}));
|
}));
|
||||||
this._proxy.GetSignalQualityRemote(Lang.bind(this, function(result, err) {
|
this._proxy.GetSignalQualityRemote(Lang.bind(this, function(result, err) {
|
||||||
@ -88,67 +164,6 @@ const ModemGsm = new Lang.Class({
|
|||||||
}
|
}
|
||||||
this.emit('notify::signal-quality');
|
this.emit('notify::signal-quality');
|
||||||
}));
|
}));
|
||||||
},
|
|
||||||
|
|
||||||
_findOperatorName: function(name, opCode) {
|
|
||||||
if (name.length != 0 && (name.length > 6 || name.length < 5)) {
|
|
||||||
// this looks like a valid name, i.e. not an MCCMNC (that some
|
|
||||||
// devices return when not yet connected
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
if (isNaN(parseInt(name))) {
|
|
||||||
// name is definitely not a MCCMNC, so it may be a name
|
|
||||||
// after all; return that
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
let needle;
|
|
||||||
if (name.length == 0 && opCode)
|
|
||||||
needle = opCode;
|
|
||||||
else if (name.length == 6 || name.length == 5)
|
|
||||||
needle = name;
|
|
||||||
else // nothing to search
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return this._findProviderForMCCMNC(needle);
|
|
||||||
},
|
|
||||||
|
|
||||||
_findProviderForMCCMNC: function(needle) {
|
|
||||||
let table = _getProvidersTable();
|
|
||||||
let needlemcc = needle.substring(0, 3);
|
|
||||||
let needlemnc = needle.substring(3, needle.length);
|
|
||||||
|
|
||||||
let name2, name3;
|
|
||||||
for (let iter in table) {
|
|
||||||
let providers = table[iter];
|
|
||||||
|
|
||||||
// Search through each country's providers
|
|
||||||
for (let i = 0; i < providers.length; i++) {
|
|
||||||
let provider = providers[i];
|
|
||||||
|
|
||||||
// Search through MCC/MNC list
|
|
||||||
let list = provider.get_gsm_mcc_mnc();
|
|
||||||
for (let j = 0; j < list.length; j++) {
|
|
||||||
let mccmnc = list[j];
|
|
||||||
|
|
||||||
// Match both 2-digit and 3-digit MNC; prefer a
|
|
||||||
// 3-digit match if found, otherwise a 2-digit one.
|
|
||||||
if (mccmnc.mcc != needlemcc)
|
|
||||||
continue; // MCC was wrong
|
|
||||||
|
|
||||||
if (!name3 && needle.length == 6 && needlemnc == mccmnc.mnc)
|
|
||||||
name3 = provider.name;
|
|
||||||
|
|
||||||
if (!name2 && needlemnc.substring(0, 2) == mccmnc.mnc.substring(0, 2))
|
|
||||||
name2 = provider.name;
|
|
||||||
|
|
||||||
if (name2 && name3)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return name3 || name2 || null;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Signals.addSignalMethods(ModemGsm.prototype);
|
Signals.addSignalMethods(ModemGsm.prototype);
|
||||||
@ -188,40 +203,99 @@ const ModemCdma = new Lang.Class({
|
|||||||
// it will return an error if the device is not connected
|
// it will return an error if the device is not connected
|
||||||
this.operator_name = null;
|
this.operator_name = null;
|
||||||
} else {
|
} else {
|
||||||
let [bandClass, band, id] = result;
|
let [bandClass, band, sid] = result;
|
||||||
if (name.length > 0)
|
|
||||||
this.operator_name = this._findProviderForSid(id);
|
this.operator_name = _findProviderForSid(sid)
|
||||||
else
|
|
||||||
this.operator_name = null;
|
|
||||||
}
|
}
|
||||||
this.emit('notify::operator-name');
|
this.emit('notify::operator-name');
|
||||||
}));
|
}));
|
||||||
},
|
|
||||||
|
|
||||||
_findProviderForSid: function(sid) {
|
|
||||||
if (sid == 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
let table = _getProvidersTable();
|
|
||||||
|
|
||||||
// Search through each country
|
|
||||||
for (let iter in table) {
|
|
||||||
let providers = table[iter];
|
|
||||||
|
|
||||||
// Search through each country's providers
|
|
||||||
for (let i = 0; i < providers.length; i++) {
|
|
||||||
let provider = providers[i];
|
|
||||||
let cdma_sid = provider.get_cdma_sid();
|
|
||||||
|
|
||||||
// Search through CDMA SID list
|
|
||||||
for (let j = 0; j < cdma_sid.length; j++) {
|
|
||||||
if (cdma_sid[j] == sid)
|
|
||||||
return provider.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Signals.addSignalMethods(ModemCdma.prototype);
|
Signals.addSignalMethods(ModemCdma.prototype);
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Support for the new ModemManager1 interface (MM >= 0.7)
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const BroadbandModemInterface = <interface name="org.freedesktop.ModemManager1.Modem">
|
||||||
|
<property name="SignalQuality" type="(ub)" access="read" />
|
||||||
|
</interface>;
|
||||||
|
const BroadbandModemProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModemInterface);
|
||||||
|
|
||||||
|
const BroadbandModem3gppInterface = <interface name="org.freedesktop.ModemManager1.Modem.Modem3gpp">
|
||||||
|
<property name="OperatorCode" type="s" access="read" />
|
||||||
|
<property name="OperatorName" type="s" access="read" />
|
||||||
|
</interface>;
|
||||||
|
const BroadbandModem3gppProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModem3gppInterface);
|
||||||
|
|
||||||
|
const BroadbandModemCdmaInterface = <interface name="org.freedesktop.ModemManager1.Modem.ModemCdma">
|
||||||
|
<property name="Sid" type="u" access="read" />
|
||||||
|
</interface>;
|
||||||
|
const BroadbandModemCdmaProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModemCdmaInterface);
|
||||||
|
|
||||||
|
const BroadbandModem = new Lang.Class({
|
||||||
|
Name: 'BroadbandModem',
|
||||||
|
|
||||||
|
_init: function(path, capabilities) {
|
||||||
|
this._proxy = new BroadbandModemProxy(Gio.DBus.system, 'org.freedesktop.ModemManager1', path);
|
||||||
|
this._proxy_3gpp = new BroadbandModem3gppProxy(Gio.DBus.system, 'org.freedesktop.ModemManager1', path);
|
||||||
|
this._proxy_cdma = new BroadbandModemCdmaProxy(Gio.DBus.system, 'org.freedesktop.ModemManager1', path);
|
||||||
|
this._capabilities = capabilities;
|
||||||
|
|
||||||
|
this._proxy.connect('g-properties-changed', Lang.bind(this, function(proxy, properties) {
|
||||||
|
if ('SignalQuality' in properties.deep_unpack())
|
||||||
|
this._reloadSignalQuality();
|
||||||
|
}));
|
||||||
|
this._reloadSignalQuality();
|
||||||
|
|
||||||
|
this._proxy_3gpp.connect('g-properties-changed', Lang.bind(this, function(proxy, properties) {
|
||||||
|
let unpacked = properties.deep_unpack();
|
||||||
|
if ('OperatorName' in unpacked || 'OperatorCode' in unpacked)
|
||||||
|
this._reload3gppOperatorName();
|
||||||
|
}));
|
||||||
|
this._reload3gppOperatorName();
|
||||||
|
|
||||||
|
this._proxy_cdma.connect('g-properties-changed', Lang.bind(this, function(proxy, properties) {
|
||||||
|
let unpacked = properties.deep_unpack();
|
||||||
|
if ('Nid' in unpacked || 'Sid' in unpacked)
|
||||||
|
this._reloadCdmaOperatorName();
|
||||||
|
}));
|
||||||
|
this._reloadCdmaOperatorName();
|
||||||
|
},
|
||||||
|
|
||||||
|
_reloadSignalQuality: function() {
|
||||||
|
let [quality, recent] = this._proxy.SignalQuality;
|
||||||
|
this.signal_quality = quality;
|
||||||
|
this.emit('notify::signal-quality');
|
||||||
|
},
|
||||||
|
|
||||||
|
_reloadOperatorName: function() {
|
||||||
|
let new_name = "";
|
||||||
|
if (this.operator_name_3gpp && this.operator_name_3gpp.length > 0)
|
||||||
|
new_name += this.operator_name_3gpp;
|
||||||
|
|
||||||
|
if (this.operator_name_cdma && this.operator_name_cdma.length > 0) {
|
||||||
|
if (new_name != "")
|
||||||
|
new_name += ", ";
|
||||||
|
new_name += this.operator_name_cdma;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.operator_name = new_name;
|
||||||
|
this.emit('notify::operator-name');
|
||||||
|
},
|
||||||
|
|
||||||
|
_reload3gppOperatorName: function() {
|
||||||
|
let name = this._proxy_3gpp.OperatorName;
|
||||||
|
let code = this._proxy_3gpp.OperatorCode;
|
||||||
|
this.operator_name_3gpp = _findProviderForMccMnc(name, code);
|
||||||
|
this._reloadOperatorName();
|
||||||
|
},
|
||||||
|
|
||||||
|
_reloadCdmaOperatorName: function() {
|
||||||
|
let sid = this._proxy_cdma.Sid;
|
||||||
|
this.operator_name_cdma = _findProviderForSid(sid);
|
||||||
|
this._reloadOperatorName();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Signals.addSignalMethods(BroadbandModem.prototype);
|
||||||
|
145
js/misc/util.js
145
js/misc/util.js
@ -1,8 +1,13 @@
|
|||||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
|
||||||
|
const Clutter = imports.gi.Clutter;
|
||||||
const GLib = imports.gi.GLib;
|
const GLib = imports.gi.GLib;
|
||||||
|
const St = imports.gi.St;
|
||||||
|
|
||||||
const Main = imports.ui.main;
|
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
|
// http://daringfireball.net/2010/07/improved_regex_for_matching_urls
|
||||||
const _balancedParens = '\\((?:[^\\s()<>]+|(?:\\(?:[^\\s()<>]+\\)))*\\)';
|
const _balancedParens = '\\((?:[^\\s()<>]+|(?:\\(?:[^\\s()<>]+\\)))*\\)';
|
||||||
@ -159,86 +164,6 @@ function killall(processName) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This was ported from network-manager-applet
|
|
||||||
// Copyright 2007 - 2011 Red Hat, Inc.
|
|
||||||
// Author: Dan Williams <dcbw@redhat.com>
|
|
||||||
|
|
||||||
const _IGNORED_WORDS = [
|
|
||||||
'Semiconductor',
|
|
||||||
'Components',
|
|
||||||
'Corporation',
|
|
||||||
'Communications',
|
|
||||||
'Company',
|
|
||||||
'Corp.',
|
|
||||||
'Corp',
|
|
||||||
'Co.',
|
|
||||||
'Inc.',
|
|
||||||
'Inc',
|
|
||||||
'Incorporated',
|
|
||||||
'Ltd.',
|
|
||||||
'Limited.',
|
|
||||||
'Intel',
|
|
||||||
'chipset',
|
|
||||||
'adapter',
|
|
||||||
'[hex]',
|
|
||||||
'NDIS',
|
|
||||||
'Module'
|
|
||||||
];
|
|
||||||
|
|
||||||
const _IGNORED_PHRASES = [
|
|
||||||
'Multiprotocol MAC/baseband processor',
|
|
||||||
'Wireless LAN Controller',
|
|
||||||
'Wireless LAN Adapter',
|
|
||||||
'Wireless Adapter',
|
|
||||||
'Network Connection',
|
|
||||||
'Wireless Cardbus Adapter',
|
|
||||||
'Wireless CardBus Adapter',
|
|
||||||
'54 Mbps Wireless PC Card',
|
|
||||||
'Wireless PC Card',
|
|
||||||
'Wireless PC',
|
|
||||||
'PC Card with XJACK(r) Antenna',
|
|
||||||
'Wireless cardbus',
|
|
||||||
'Wireless LAN PC Card',
|
|
||||||
'Technology Group Ltd.',
|
|
||||||
'Communication S.p.A.',
|
|
||||||
'Business Mobile Networks BV',
|
|
||||||
'Mobile Broadband Minicard Composite Device',
|
|
||||||
'Mobile Communications AB',
|
|
||||||
'(PC-Suite Mode)'
|
|
||||||
];
|
|
||||||
|
|
||||||
function fixupPCIDescription(desc) {
|
|
||||||
desc = desc.replace(/[_,]/, ' ');
|
|
||||||
|
|
||||||
/* Attempt to shorten ID by ignoring certain phrases */
|
|
||||||
for (let i = 0; i < _IGNORED_PHRASES.length; i++) {
|
|
||||||
let item = _IGNORED_PHRASES[i];
|
|
||||||
let pos = desc.indexOf(item);
|
|
||||||
if (pos != -1) {
|
|
||||||
let before = desc.substring(0, pos);
|
|
||||||
let after = desc.substring(pos + item.length, desc.length);
|
|
||||||
desc = before + after;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Attmept to shorten ID by ignoring certain individual words */
|
|
||||||
let words = desc.split(' ');
|
|
||||||
let out = [ ];
|
|
||||||
for (let i = 0; i < words.length; i++) {
|
|
||||||
let item = words[i];
|
|
||||||
|
|
||||||
// skip empty items (that come out from consecutive spaces)
|
|
||||||
if (item.length == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (_IGNORED_WORDS.indexOf(item) == -1) {
|
|
||||||
out.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return out.join(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
// lowerBound:
|
// lowerBound:
|
||||||
// @array: an array or array-like object, already sorted
|
// @array: an array or array-like object, already sorted
|
||||||
// according to @cmp
|
// according to @cmp
|
||||||
@ -288,3 +213,63 @@ function insertSorted(array, val, cmp) {
|
|||||||
|
|
||||||
return pos;
|
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' });
|
||||||
|
}
|
||||||
|
945
js/ui/altTab.js
945
js/ui/altTab.js
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -84,9 +84,12 @@ const AppFavorites = new Lang.Class({
|
|||||||
|
|
||||||
let app = Shell.AppSystem.get_default().lookup_app(appId);
|
let app = Shell.AppSystem.get_default().lookup_app(appId);
|
||||||
|
|
||||||
Main.overview.setMessage(_("%s has been added to your favorites.").format(app.get_name()), Lang.bind(this, function () {
|
Main.overview.setMessage(_("%s has been added to your favorites.").format(app.get_name()),
|
||||||
this._removeFavorite(appId);
|
{ forFeedback: true,
|
||||||
}));
|
undoCallback: Lang.bind(this, function () {
|
||||||
|
this._removeFavorite(appId);
|
||||||
|
})
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
addFavorite: function(appId) {
|
addFavorite: function(appId) {
|
||||||
@ -116,9 +119,11 @@ const AppFavorites = new Lang.Class({
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
Main.overview.setMessage(_("%s has been removed from your favorites.").format(app.get_name()),
|
Main.overview.setMessage(_("%s has been removed from your favorites.").format(app.get_name()),
|
||||||
Lang.bind(this, function () {
|
{ forFeedback: true,
|
||||||
this._addFavorite(appId, pos);
|
undoCallback: Lang.bind(this, function () {
|
||||||
}));
|
this._addFavorite(appId, pos);
|
||||||
|
})
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Signals.addSignalMethods(AppFavorites.prototype);
|
Signals.addSignalMethods(AppFavorites.prototype);
|
||||||
|
787
js/ui/background.js
Normal file
787
js/ui/background.js
Normal file
@ -0,0 +1,787 @@
|
|||||||
|
// -*- 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) {
|
||||||
|
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,
|
||||||
|
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(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.background == background) {
|
||||||
|
this.background = newBackground;
|
||||||
|
this._newBackground = null;
|
||||||
|
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);
|
58
js/ui/backgroundMenu.js
Normal file
58
js/ui/backgroundMenu.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// -*- 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();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
clickAction.connect('clicked', function(action) {
|
||||||
|
if (action.get_button() == 3)
|
||||||
|
openMenu();
|
||||||
|
});
|
||||||
|
actor.add_action(clickAction);
|
||||||
|
}
|
@ -38,6 +38,7 @@ const BoxPointer = new Lang.Class({
|
|||||||
this._arrowSide = arrowSide;
|
this._arrowSide = arrowSide;
|
||||||
this._userArrowSide = arrowSide;
|
this._userArrowSide = arrowSide;
|
||||||
this._arrowOrigin = 0;
|
this._arrowOrigin = 0;
|
||||||
|
this._arrowActor = null;
|
||||||
this.actor = new St.Bin({ x_fill: true,
|
this.actor = new St.Bin({ x_fill: true,
|
||||||
y_fill: true });
|
y_fill: true });
|
||||||
this._container = new Shell.GenericContainer();
|
this._container = new Shell.GenericContainer();
|
||||||
@ -141,6 +142,7 @@ const BoxPointer = new Lang.Class({
|
|||||||
|
|
||||||
this._muteInput();
|
this._muteInput();
|
||||||
|
|
||||||
|
Tweener.removeTweens(this);
|
||||||
Tweener.addTween(this, { opacity: fade ? 0 : 255,
|
Tweener.addTween(this, { opacity: fade ? 0 : 255,
|
||||||
xOffset: xOffset,
|
xOffset: xOffset,
|
||||||
yOffset: yOffset,
|
yOffset: yOffset,
|
||||||
@ -219,31 +221,27 @@ const BoxPointer = new Lang.Class({
|
|||||||
this.bin.allocate(childBox, flags);
|
this.bin.allocate(childBox, flags);
|
||||||
|
|
||||||
if (this._sourceActor && this._sourceActor.mapped) {
|
if (this._sourceActor && this._sourceActor.mapped) {
|
||||||
this._reposition(this._sourceActor, this._arrowAlignment);
|
this._reposition();
|
||||||
|
this._updateFlip();
|
||||||
if (this._shouldFlip()) {
|
|
||||||
switch (this._arrowSide) {
|
|
||||||
case St.Side.TOP:
|
|
||||||
this._arrowSide = St.Side.BOTTOM;
|
|
||||||
break;
|
|
||||||
case St.Side.BOTTOM:
|
|
||||||
this._arrowSide = St.Side.TOP;
|
|
||||||
break;
|
|
||||||
case St.Side.LEFT:
|
|
||||||
this._arrowSide = St.Side.RIGHT;
|
|
||||||
break;
|
|
||||||
case St.Side.RIGHT:
|
|
||||||
this._arrowSide = St.Side.LEFT;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
this._reposition(this._sourceActor, this._arrowAlignment);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_drawBorder: function(area) {
|
_drawBorder: function(area) {
|
||||||
let themeNode = this.actor.get_theme_node();
|
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 borderWidth = themeNode.get_length('-arrow-border-width');
|
||||||
let base = themeNode.get_length('-arrow-base');
|
let base = themeNode.get_length('-arrow-base');
|
||||||
let rise = themeNode.get_length('-arrow-rise');
|
let rise = themeNode.get_length('-arrow-rise');
|
||||||
@ -404,11 +402,11 @@ const BoxPointer = new Lang.Class({
|
|||||||
cr.setLineWidth(borderWidth);
|
cr.setLineWidth(borderWidth);
|
||||||
cr.stroke();
|
cr.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cr.$dispose();
|
||||||
},
|
},
|
||||||
|
|
||||||
setPosition: function(sourceActor, alignment) {
|
setPosition: function(sourceActor, alignment) {
|
||||||
this._arrowSide = this._userArrowSide;
|
|
||||||
|
|
||||||
// We need to show it now to force an allocation,
|
// We need to show it now to force an allocation,
|
||||||
// so that we can query the correct size.
|
// so that we can query the correct size.
|
||||||
this.actor.show();
|
this.actor.show();
|
||||||
@ -416,7 +414,8 @@ const BoxPointer = new Lang.Class({
|
|||||||
this._sourceActor = sourceActor;
|
this._sourceActor = sourceActor;
|
||||||
this._arrowAlignment = alignment;
|
this._arrowAlignment = alignment;
|
||||||
|
|
||||||
this._reposition(sourceActor, alignment);
|
this._reposition();
|
||||||
|
this._updateFlip();
|
||||||
},
|
},
|
||||||
|
|
||||||
setSourceAlignment: function(alignment) {
|
setSourceAlignment: function(alignment) {
|
||||||
@ -428,7 +427,10 @@ const BoxPointer = new Lang.Class({
|
|||||||
this.setPosition(this._sourceActor, this._arrowAlignment);
|
this.setPosition(this._sourceActor, this._arrowAlignment);
|
||||||
},
|
},
|
||||||
|
|
||||||
_reposition: function(sourceActor, alignment) {
|
_reposition: function() {
|
||||||
|
let sourceActor = this._sourceActor;
|
||||||
|
let alignment = this._arrowAlignment;
|
||||||
|
|
||||||
// Position correctly relative to the sourceActor
|
// Position correctly relative to the sourceActor
|
||||||
let sourceNode = sourceActor.get_theme_node();
|
let sourceNode = sourceActor.get_theme_node();
|
||||||
let sourceContentBox = sourceNode.get_content_box(sourceActor.get_allocation_box());
|
let sourceContentBox = sourceNode.get_content_box(sourceActor.get_allocation_box());
|
||||||
@ -549,10 +551,20 @@ const BoxPointer = new Lang.Class({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// @actor: an actor relative to which the arrow is positioned.
|
||||||
|
// Differently from setPosition, this will not move the boxpointer itself,
|
||||||
|
// on the arrow
|
||||||
|
setArrowActor: function(actor) {
|
||||||
|
if (this._arrowActor != actor) {
|
||||||
|
this._arrowActor = actor;
|
||||||
|
this._border.queue_repaint();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
_shiftActor : function() {
|
_shiftActor : function() {
|
||||||
// Since the position of the BoxPointer depends on the allocated size
|
// Since the position of the BoxPointer depends on the allocated size
|
||||||
// of the BoxPointer and the position of the source actor, trying
|
// of the BoxPointer and the position of the source actor, trying
|
||||||
// to position the BoxPoiner via the x/y properties will result in
|
// to position the BoxPointer via the x/y properties will result in
|
||||||
// allocation loops and warnings. Instead we do the positioning via
|
// allocation loops and warnings. Instead we do the positioning via
|
||||||
// the anchor point, which is independent of allocation, and leave
|
// the anchor point, which is independent of allocation, and leave
|
||||||
// x == y == 0.
|
// x == y == 0.
|
||||||
@ -560,37 +572,47 @@ const BoxPointer = new Lang.Class({
|
|||||||
-(this._yPosition + this._yOffset));
|
-(this._yPosition + this._yOffset));
|
||||||
},
|
},
|
||||||
|
|
||||||
_shouldFlip: function() {
|
_calculateArrowSide: function(arrowSide) {
|
||||||
let sourceAllocation = Shell.util_get_transformed_allocation(this._sourceActor);
|
let sourceAllocation = Shell.util_get_transformed_allocation(this._sourceActor);
|
||||||
let boxAllocation = Shell.util_get_transformed_allocation(this.actor);
|
let [minWidth, minHeight, boxWidth, boxHeight] = this._container.get_preferred_size();
|
||||||
let boxWidth = boxAllocation.x2 - boxAllocation.x1;
|
|
||||||
let boxHeight = boxAllocation.y2 - boxAllocation.y1;
|
|
||||||
let monitor = Main.layoutManager.findMonitorForActor(this.actor);
|
let monitor = Main.layoutManager.findMonitorForActor(this.actor);
|
||||||
|
|
||||||
switch (this._arrowSide) {
|
switch (arrowSide) {
|
||||||
case St.Side.TOP:
|
case St.Side.TOP:
|
||||||
if (boxAllocation.y2 > monitor.y + monitor.height &&
|
if (sourceAllocation.y2 + boxHeight > monitor.y + monitor.height &&
|
||||||
boxHeight < sourceAllocation.y1 - monitor.y)
|
boxHeight < sourceAllocation.y1 - monitor.y)
|
||||||
return true;
|
return St.Side.BOTTOM;
|
||||||
break;
|
break;
|
||||||
case St.Side.BOTTOM:
|
case St.Side.BOTTOM:
|
||||||
if (boxAllocation.y1 < monitor.y &&
|
if (sourceAllocation.y1 - boxHeight < monitor.y &&
|
||||||
boxHeight < monitor.y + monitor.height - sourceAllocation.y2)
|
boxHeight < monitor.y + monitor.height - sourceAllocation.y2)
|
||||||
return true;
|
return St.Side.TOP;
|
||||||
break;
|
break;
|
||||||
case St.Side.LEFT:
|
case St.Side.LEFT:
|
||||||
if (boxAllocation.x2 > monitor.x + monitor.width &&
|
if (sourceAllocation.y2 + boxWidth > monitor.x + monitor.width &&
|
||||||
boxWidth < sourceAllocation.x1 - monitor.x)
|
boxWidth < sourceAllocation.x1 - monitor.x)
|
||||||
return true;
|
return St.Side.RIGHT;
|
||||||
break;
|
break;
|
||||||
case St.Side.RIGHT:
|
case St.Side.RIGHT:
|
||||||
if (boxAllocation.x1 < monitor.x &&
|
if (sourceAllocation.y1 - boxWidth < monitor.x &&
|
||||||
boxWidth < monitor.x + monitor.width - sourceAllocation.x2)
|
boxWidth < monitor.x + monitor.width - sourceAllocation.x2)
|
||||||
return true;
|
return St.Side.LEFT;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return arrowSide;
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateFlip: function() {
|
||||||
|
let arrowSide = this._calculateArrowSide(this._userArrowSide);
|
||||||
|
if (this._arrowSide != arrowSide) {
|
||||||
|
this._arrowSide = arrowSide;
|
||||||
|
this._reposition();
|
||||||
|
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() {
|
||||||
|
this._container.queue_relayout();
|
||||||
|
return false;
|
||||||
|
}));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
set xOffset(offset) {
|
set xOffset(offset) {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const Clutter = imports.gi.Clutter;
|
const Clutter = imports.gi.Clutter;
|
||||||
const Gio = imports.gi.Gio;
|
const Gio = imports.gi.Gio;
|
||||||
|
const GLib = imports.gi.GLib;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
const St = imports.gi.St;
|
const St = imports.gi.St;
|
||||||
const Signals = imports.signals;
|
const Signals = imports.signals;
|
||||||
@ -11,7 +12,6 @@ const Mainloop = imports.mainloop;
|
|||||||
const Shell = imports.gi.Shell;
|
const Shell = imports.gi.Shell;
|
||||||
|
|
||||||
const MSECS_IN_DAY = 24 * 60 * 60 * 1000;
|
const MSECS_IN_DAY = 24 * 60 * 60 * 1000;
|
||||||
const WEEKDATE_HEADER_WIDTH_DIGITS = 3;
|
|
||||||
const SHOW_WEEKDATE_KEY = 'show-weekdate';
|
const SHOW_WEEKDATE_KEY = 'show-weekdate';
|
||||||
|
|
||||||
// in org.gnome.desktop.interface
|
// in org.gnome.desktop.interface
|
||||||
@ -63,15 +63,18 @@ function _formatEventTime(event, clockFormat) {
|
|||||||
} else {
|
} else {
|
||||||
switch (clockFormat) {
|
switch (clockFormat) {
|
||||||
case '24h':
|
case '24h':
|
||||||
/* Translators: Shown in calendar event list, if 24h format */
|
/* Translators: Shown in calendar event list, if 24h format,
|
||||||
ret = event.date.toLocaleFormat(C_("event list time", "%H:%M"));
|
\u2236 is a ratio character, similar to : */
|
||||||
|
ret = event.date.toLocaleFormat(C_("event list time", "%H\u2236%M"));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
/* explicit fall-through */
|
/* explicit fall-through */
|
||||||
case '12h':
|
case '12h':
|
||||||
/* Transators: Shown in calendar event list, if 12h format */
|
/* Transators: Shown in calendar event list, if 12h format,
|
||||||
ret = event.date.toLocaleFormat(C_("event list time", "%l:%M %p"));
|
\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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,15 +98,6 @@ function _getCalendarWeekForDate(date) {
|
|||||||
return weekNumber;
|
return weekNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getDigitWidth(actor){
|
|
||||||
let context = actor.get_pango_context();
|
|
||||||
let themeNode = actor.get_theme_node();
|
|
||||||
let font = themeNode.get_font();
|
|
||||||
let metrics = context.get_metrics(font, context.get_language());
|
|
||||||
let width = metrics.get_approximate_digit_width();
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getCalendarDayAbbreviation(dayNumber) {
|
function _getCalendarDayAbbreviation(dayNumber) {
|
||||||
let abbreviations = [
|
let abbreviations = [
|
||||||
/* Translators: Calendar grid abbreviation for Sunday.
|
/* Translators: Calendar grid abbreviation for Sunday.
|
||||||
@ -174,6 +168,12 @@ const EmptyEventSource = new Lang.Class({
|
|||||||
Name: 'EmptyEventSource',
|
Name: 'EmptyEventSource',
|
||||||
|
|
||||||
_init: function() {
|
_init: function() {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.isDummy = true;
|
||||||
|
this.hasCalendars = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
},
|
},
|
||||||
|
|
||||||
requestRange: function(begin, end) {
|
requestRange: function(begin, end) {
|
||||||
@ -197,21 +197,18 @@ const CalendarServerIface = <interface name="org.gnome.Shell.CalendarServer">
|
|||||||
<arg type="b" direction="in" />
|
<arg type="b" direction="in" />
|
||||||
<arg type="a(sssbxxa{sv})" direction="out" />
|
<arg type="a(sssbxxa{sv})" direction="out" />
|
||||||
</method>
|
</method>
|
||||||
|
<property name="HasCalendars" type="b" access="read" />
|
||||||
<signal name="Changed" />
|
<signal name="Changed" />
|
||||||
</interface>;
|
</interface>;
|
||||||
|
|
||||||
const CalendarServerInfo = Gio.DBusInterfaceInfo.new_for_xml(CalendarServerIface);
|
const CalendarServerInfo = Gio.DBusInterfaceInfo.new_for_xml(CalendarServerIface);
|
||||||
|
|
||||||
function CalendarServer() {
|
function CalendarServer() {
|
||||||
var self = new Gio.DBusProxy({ g_connection: Gio.DBus.session,
|
return new Gio.DBusProxy({ g_connection: Gio.DBus.session,
|
||||||
g_interface_name: CalendarServerInfo.name,
|
g_interface_name: CalendarServerInfo.name,
|
||||||
g_interface_info: CalendarServerInfo,
|
g_interface_info: CalendarServerInfo,
|
||||||
g_name: 'org.gnome.Shell.CalendarServer',
|
g_name: 'org.gnome.Shell.CalendarServer',
|
||||||
g_object_path: '/org/gnome/Shell/CalendarServer',
|
g_object_path: '/org/gnome/Shell/CalendarServer' });
|
||||||
g_flags: Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES });
|
|
||||||
|
|
||||||
self.init(null);
|
|
||||||
return self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _datesEqual(a, b) {
|
function _datesEqual(a, b) {
|
||||||
@ -238,18 +235,49 @@ const DBusEventSource = new Lang.Class({
|
|||||||
|
|
||||||
_init: function() {
|
_init: function() {
|
||||||
this._resetCache();
|
this._resetCache();
|
||||||
|
this.isLoading = false;
|
||||||
|
this.isDummy = false;
|
||||||
|
|
||||||
|
this._initialized = false;
|
||||||
this._dbusProxy = new CalendarServer();
|
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() {
|
this._dbusProxy.connectSignal('Changed', Lang.bind(this, this._onChanged));
|
||||||
if (this._dbusProxy.g_name_owner)
|
|
||||||
this._onNameAppeared();
|
this._dbusProxy.connect('notify::g-name-owner', Lang.bind(this, function() {
|
||||||
else
|
if (this._dbusProxy.g_name_owner)
|
||||||
this._onNameVanished();
|
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() {
|
_resetCache: function() {
|
||||||
this._events = [];
|
this._events = [];
|
||||||
this._lastRequestBegin = null;
|
this._lastRequestBegin = null;
|
||||||
@ -270,8 +298,9 @@ const DBusEventSource = new Lang.Class({
|
|||||||
this._loadEvents(false);
|
this._loadEvents(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
_onEventsReceived: function([appointments]) {
|
_onEventsReceived: function(results, error) {
|
||||||
let newEvents = [];
|
let newEvents = [];
|
||||||
|
let appointments = results ? results[0] : null;
|
||||||
if (appointments != null) {
|
if (appointments != null) {
|
||||||
for (let n = 0; n < appointments.length; n++) {
|
for (let n = 0; n < appointments.length; n++) {
|
||||||
let a = appointments[n];
|
let a = appointments[n];
|
||||||
@ -288,10 +317,15 @@ const DBusEventSource = new Lang.Class({
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._events = newEvents;
|
this._events = newEvents;
|
||||||
|
this.isLoading = false;
|
||||||
this.emit('changed');
|
this.emit('changed');
|
||||||
},
|
},
|
||||||
|
|
||||||
_loadEvents: function(forceReload) {
|
_loadEvents: function(forceReload) {
|
||||||
|
// Ignore while loading
|
||||||
|
if (!this._initialized)
|
||||||
|
return;
|
||||||
|
|
||||||
if (this._curRequestBegin && this._curRequestEnd){
|
if (this._curRequestBegin && this._curRequestEnd){
|
||||||
let callFlags = Gio.DBusCallFlags.NO_AUTO_START;
|
let callFlags = Gio.DBusCallFlags.NO_AUTO_START;
|
||||||
if (forceReload)
|
if (forceReload)
|
||||||
@ -306,6 +340,7 @@ const DBusEventSource = new Lang.Class({
|
|||||||
|
|
||||||
requestRange: function(begin, end, forceReload) {
|
requestRange: function(begin, end, forceReload) {
|
||||||
if (forceReload || !(_datesEqual(begin, this._lastRequestBegin) && _datesEqual(end, this._lastRequestEnd))) {
|
if (forceReload || !(_datesEqual(begin, this._lastRequestBegin) && _datesEqual(end, this._lastRequestEnd))) {
|
||||||
|
this.isLoading = true;
|
||||||
this._lastRequestBegin = begin;
|
this._lastRequestBegin = begin;
|
||||||
this._lastRequestEnd = end;
|
this._lastRequestEnd = end;
|
||||||
this._curRequestBegin = begin;
|
this._curRequestBegin = begin;
|
||||||
@ -344,8 +379,6 @@ const Calendar = new Lang.Class({
|
|||||||
|
|
||||||
_init: function() {
|
_init: function() {
|
||||||
this._weekStart = Shell.util_get_week_start();
|
this._weekStart = Shell.util_get_week_start();
|
||||||
this._weekdate = NaN;
|
|
||||||
this._digitWidth = NaN;
|
|
||||||
this._settings = new Gio.Settings({ schema: 'org.gnome.shell.calendar' });
|
this._settings = new Gio.Settings({ schema: 'org.gnome.shell.calendar' });
|
||||||
|
|
||||||
this._settings.connect('changed::' + SHOW_WEEKDATE_KEY, Lang.bind(this, this._onSettingsChange));
|
this._settings.connect('changed::' + SHOW_WEEKDATE_KEY, Lang.bind(this, this._onSettingsChange));
|
||||||
@ -382,19 +415,11 @@ const Calendar = new Lang.Class({
|
|||||||
// @eventSource: is an object implementing the EventSource API, e.g. the
|
// @eventSource: is an object implementing the EventSource API, e.g. the
|
||||||
// requestRange(), getEvents(), hasEvents() methods and the ::changed signal.
|
// requestRange(), getEvents(), hasEvents() methods and the ::changed signal.
|
||||||
setEventSource: function(eventSource) {
|
setEventSource: function(eventSource) {
|
||||||
if (this._eventSource) {
|
|
||||||
this._eventSource.disconnect(this._eventSourceChangedId);
|
|
||||||
this._eventSource = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._eventSource = eventSource;
|
this._eventSource = eventSource;
|
||||||
|
this._eventSource.connect('changed', Lang.bind(this, function() {
|
||||||
if (this._eventSource) {
|
this._update(false);
|
||||||
this._eventSourceChangedId = this._eventSource.connect('changed', Lang.bind(this, function() {
|
}));
|
||||||
this._update(false);
|
this._update(true);
|
||||||
}));
|
|
||||||
this._update(true);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Sets the calendar to show a specific date
|
// Sets the calendar to show a specific date
|
||||||
@ -418,8 +443,6 @@ const Calendar = new Lang.Class({
|
|||||||
this.actor.add(this._topBox,
|
this.actor.add(this._topBox,
|
||||||
{ row: 0, col: 0, col_span: offsetCols + 7 });
|
{ row: 0, col: 0, col_span: offsetCols + 7 });
|
||||||
|
|
||||||
this.actor.connect('style-changed', Lang.bind(this, this._onStyleChange));
|
|
||||||
|
|
||||||
let back = new St.Button({ style_class: 'calendar-change-month-back' });
|
let back = new St.Button({ style_class: 'calendar-change-month-back' });
|
||||||
this._topBox.add(back);
|
this._topBox.add(back);
|
||||||
back.connect('clicked', Lang.bind(this, this._onPrevMonthButtonClicked));
|
back.connect('clicked', Lang.bind(this, this._onPrevMonthButtonClicked));
|
||||||
@ -456,18 +479,6 @@ const Calendar = new Lang.Class({
|
|||||||
this._firstDayIndex = this.actor.get_n_children();
|
this._firstDayIndex = this.actor.get_n_children();
|
||||||
},
|
},
|
||||||
|
|
||||||
_onStyleChange: function(actor, event) {
|
|
||||||
// width of a digit in pango units
|
|
||||||
this._digitWidth = _getDigitWidth(this.actor) / Pango.SCALE;
|
|
||||||
this._setWeekdateHeaderWidth();
|
|
||||||
},
|
|
||||||
|
|
||||||
_setWeekdateHeaderWidth: function() {
|
|
||||||
if (this.digitWidth != NaN && this._useWeekdate && this._weekdateHeader) {
|
|
||||||
this._weekdateHeader.set_width (this._digitWidth * WEEKDATE_HEADER_WIDTH_DIGITS);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_onScroll : function(actor, event) {
|
_onScroll : function(actor, event) {
|
||||||
switch (event.get_scroll_direction()) {
|
switch (event.get_scroll_direction()) {
|
||||||
case Clutter.ScrollDirection.UP:
|
case Clutter.ScrollDirection.UP:
|
||||||
@ -545,20 +556,44 @@ const Calendar = new Lang.Class({
|
|||||||
children[i].destroy();
|
children[i].destroy();
|
||||||
|
|
||||||
// Start at the beginning of the week before the start of the month
|
// 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);
|
let beginDate = new Date(this._selectedDate);
|
||||||
beginDate.setDate(1);
|
beginDate.setDate(1);
|
||||||
beginDate.setSeconds(0);
|
beginDate.setSeconds(0);
|
||||||
beginDate.setHours(12);
|
beginDate.setHours(12);
|
||||||
|
let year = beginDate.getYear();
|
||||||
|
|
||||||
let daysToWeekStart = (7 + beginDate.getDay() - this._weekStart) % 7;
|
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 iter = new Date(beginDate);
|
||||||
let row = 2;
|
let row = 2;
|
||||||
while (true) {
|
// nRows here means 6 weeks + one header + one navbar
|
||||||
|
let nRows = 8;
|
||||||
|
while (row < 8) {
|
||||||
let button = new St.Button({ label: iter.getDate().toString() });
|
let button = new St.Button({ label: iter.getDate().toString() });
|
||||||
let rtl = button.get_text_direction() == Clutter.TextDirection.RTL;
|
let rtl = button.get_text_direction() == Clutter.TextDirection.RTL;
|
||||||
|
|
||||||
if (!this._eventSource)
|
if (this._eventSource.isDummy)
|
||||||
button.reactive = false;
|
button.reactive = false;
|
||||||
|
|
||||||
let iterStr = iter.toUTCString();
|
let iterStr = iter.toUTCString();
|
||||||
@ -567,8 +602,9 @@ const Calendar = new Lang.Class({
|
|||||||
this.setDate(newlySelectedDate, false);
|
this.setDate(newlySelectedDate, false);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let hasEvents = this._eventSource && this._eventSource.hasEvents(iter);
|
let hasEvents = this._eventSource.hasEvents(iter);
|
||||||
let styleClass = 'calendar-day-base calendar-day';
|
let styleClass = 'calendar-day-base calendar-day';
|
||||||
|
|
||||||
if (_isWorkDay(iter))
|
if (_isWorkDay(iter))
|
||||||
styleClass += ' calendar-work-day'
|
styleClass += ' calendar-work-day'
|
||||||
else
|
else
|
||||||
@ -608,17 +644,13 @@ const Calendar = new Lang.Class({
|
|||||||
}
|
}
|
||||||
|
|
||||||
iter.setTime(iter.getTime() + MSECS_IN_DAY);
|
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.getDay() == this._weekStart)
|
||||||
if (iter.getMonth() > this._selectedDate.getMonth() || iter.getYear() > this._selectedDate.getYear())
|
|
||||||
break;
|
|
||||||
row++;
|
row++;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Signal to the event source that we are interested in events
|
// Signal to the event source that we are interested in events
|
||||||
// only from this date range
|
// only from this date range
|
||||||
if (this._eventSource)
|
this._eventSource.requestRange(beginDate, iter, forceReload);
|
||||||
this._eventSource.requestRange(beginDate, iter, forceReload);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -636,16 +668,8 @@ const EventsList = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
setEventSource: function(eventSource) {
|
setEventSource: function(eventSource) {
|
||||||
if (this._eventSource) {
|
|
||||||
this._eventSource.disconnect(this._eventSourceChangedId);
|
|
||||||
this._eventSource = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._eventSource = eventSource;
|
this._eventSource = eventSource;
|
||||||
|
this._eventSource.connect('changed', Lang.bind(this, this._update));
|
||||||
if (this._eventSource) {
|
|
||||||
this._eventSourceChangedId = this._eventSource.connect('changed', Lang.bind(this, this._update));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_addEvent: function(dayNameBox, timeBox, eventTitleBox, includeDayName, day, time, desc) {
|
_addEvent: function(dayNameBox, timeBox, eventTitleBox, includeDayName, day, time, desc) {
|
||||||
@ -662,9 +686,6 @@ const EventsList = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_addPeriod: function(header, begin, end, includeDayName, showNothingScheduled) {
|
_addPeriod: function(header, begin, end, includeDayName, showNothingScheduled) {
|
||||||
if (!this._eventSource)
|
|
||||||
return;
|
|
||||||
|
|
||||||
let events = this._eventSource.getEvents(begin, end);
|
let events = this._eventSource.getEvents(begin, end);
|
||||||
|
|
||||||
let clockFormat = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);;
|
let clockFormat = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);;
|
||||||
@ -761,6 +782,9 @@ const EventsList = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_update: function() {
|
_update: function() {
|
||||||
|
if (this._eventSource.isLoading)
|
||||||
|
return;
|
||||||
|
|
||||||
let today = new Date();
|
let today = new Date();
|
||||||
if (_sameDay (this._date, today)) {
|
if (_sameDay (this._date, today)) {
|
||||||
this._showToday();
|
this._showToday();
|
||||||
|
@ -8,7 +8,6 @@ const Params = imports.misc.params;
|
|||||||
const Shell = imports.gi.Shell;
|
const Shell = imports.gi.Shell;
|
||||||
|
|
||||||
const GnomeSession = imports.misc.gnomeSession;
|
const GnomeSession = imports.misc.gnomeSession;
|
||||||
const LoginManager = imports.misc.loginManager;
|
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
const ShellMountOperation = imports.ui.shellMountOperation;
|
const ShellMountOperation = imports.ui.shellMountOperation;
|
||||||
|
|
||||||
@ -33,7 +32,6 @@ const AutomountManager = new Lang.Class({
|
|||||||
Lang.bind(this, this._InhibitorsChanged));
|
Lang.bind(this, this._InhibitorsChanged));
|
||||||
this._inhibited = false;
|
this._inhibited = false;
|
||||||
|
|
||||||
this._loginManager = LoginManager.getLoginManager();
|
|
||||||
this._volumeMonitor = Gio.VolumeMonitor.get();
|
this._volumeMonitor = Gio.VolumeMonitor.get();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -85,25 +83,29 @@ const AutomountManager = new Lang.Class({
|
|||||||
_onDriveConnected: function() {
|
_onDriveConnected: function() {
|
||||||
// if we're not in the current ConsoleKit session,
|
// if we're not in the current ConsoleKit session,
|
||||||
// or screensaver is active, don't play sounds
|
// or screensaver is active, don't play sounds
|
||||||
if (!this._loginManager.sessionActive)
|
if (!this._session.SessionIsActive)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
global.play_theme_sound(0, 'device-added-media');
|
global.play_theme_sound(0, 'device-added-media',
|
||||||
|
_("External drive connected"),
|
||||||
|
null);
|
||||||
},
|
},
|
||||||
|
|
||||||
_onDriveDisconnected: function() {
|
_onDriveDisconnected: function() {
|
||||||
// if we're not in the current ConsoleKit session,
|
// if we're not in the current ConsoleKit session,
|
||||||
// or screensaver is active, don't play sounds
|
// or screensaver is active, don't play sounds
|
||||||
if (!this._loginManager.sessionActive)
|
if (!this._session.SessionIsActive)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
global.play_theme_sound(0, 'device-removed-media');
|
global.play_theme_sound(0, 'device-removed-media',
|
||||||
|
_("External drive disconnected"),
|
||||||
|
null);
|
||||||
},
|
},
|
||||||
|
|
||||||
_onDriveEjectButton: function(monitor, drive) {
|
_onDriveEjectButton: function(monitor, drive) {
|
||||||
// TODO: this code path is not tested, as the GVfs volume monitor
|
// TODO: this code path is not tested, as the GVfs volume monitor
|
||||||
// doesn't emit this signal just yet.
|
// doesn't emit this signal just yet.
|
||||||
if (!this._loginManager.sessionActive)
|
if (!this._session.SessionIsActive)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// we force stop/eject in this case, so we don't have to pass a
|
// we force stop/eject in this case, so we don't have to pass a
|
||||||
@ -143,7 +145,7 @@ const AutomountManager = new Lang.Class({
|
|||||||
if (params.checkSession) {
|
if (params.checkSession) {
|
||||||
// if we're not in the current ConsoleKit session,
|
// if we're not in the current ConsoleKit session,
|
||||||
// don't attempt automount
|
// don't attempt automount
|
||||||
if (!this._loginManager.sessionActive)
|
if (!this._session.SessionIsActive)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ const Lang = imports.lang;
|
|||||||
const Gio = imports.gi.Gio;
|
const Gio = imports.gi.Gio;
|
||||||
const St = imports.gi.St;
|
const St = imports.gi.St;
|
||||||
|
|
||||||
const LoginManager = imports.misc.loginManager;
|
const GnomeSession = imports.misc.gnomeSession;
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
const MessageTray = imports.ui.messageTray;
|
const MessageTray = imports.ui.messageTray;
|
||||||
const ShellMountOperation = imports.ui.shellMountOperation;
|
const ShellMountOperation = imports.ui.shellMountOperation;
|
||||||
@ -45,14 +45,12 @@ function isMountRootHidden(root) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isMountNonLocal(mount) {
|
function isMountNonLocal(mount) {
|
||||||
// If the mount doesn't have an associated volume, that means it could
|
// If the mount doesn't have an associated volume, that means it's
|
||||||
// be a remote filesystem. For certain kinds of local filesystems,
|
// an uninteresting filesystem. Most devices that we care about will
|
||||||
// like digital cameras and music players, there's no associated
|
// have a mount, like media players and USB sticks.
|
||||||
// gvfs volume, so err on the side of caution and assume it's a local
|
|
||||||
// filesystem to allow the prompt.
|
|
||||||
let volume = mount.get_volume();
|
let volume = mount.get_volume();
|
||||||
if (volume == null)
|
if (volume == null)
|
||||||
return false;
|
return true;
|
||||||
|
|
||||||
return (volume.get_identifier("class") == "network");
|
return (volume.get_identifier("class") == "network");
|
||||||
}
|
}
|
||||||
@ -164,8 +162,7 @@ const AutorunManager = new Lang.Class({
|
|||||||
Name: 'AutorunManager',
|
Name: 'AutorunManager',
|
||||||
|
|
||||||
_init: function() {
|
_init: function() {
|
||||||
this._loginManager = LoginManager.getLoginManager();
|
this._session = new GnomeSession.SessionManager();
|
||||||
|
|
||||||
this._volumeMonitor = Gio.VolumeMonitor.get();
|
this._volumeMonitor = Gio.VolumeMonitor.get();
|
||||||
|
|
||||||
this._transDispatcher = new AutorunTransientDispatcher(this);
|
this._transDispatcher = new AutorunTransientDispatcher(this);
|
||||||
@ -217,7 +214,7 @@ const AutorunManager = new Lang.Class({
|
|||||||
_onMountAdded: function(monitor, mount) {
|
_onMountAdded: function(monitor, mount) {
|
||||||
// don't do anything if our session is not the currently
|
// don't do anything if our session is not the currently
|
||||||
// active one
|
// active one
|
||||||
if (!this._loginManager.sessionActive)
|
if (!this._session.SessionIsActive)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this._processMount(mount, true);
|
this._processMount(mount, true);
|
||||||
@ -295,7 +292,7 @@ const AutorunResidentSource = new Lang.Class({
|
|||||||
|
|
||||||
_init: function(manager) {
|
_init: function(manager) {
|
||||||
this.parent(_("Removable Devices"), 'media-removable');
|
this.parent(_("Removable Devices"), 'media-removable');
|
||||||
this.showInLockScreen = false;
|
this.resident = true;
|
||||||
|
|
||||||
this._mounts = [];
|
this._mounts = [];
|
||||||
|
|
||||||
@ -303,6 +300,10 @@ const AutorunResidentSource = new Lang.Class({
|
|||||||
this._notification = new AutorunResidentNotification(this._manager, this);
|
this._notification = new AutorunResidentNotification(this._manager, this);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_createPolicy: function() {
|
||||||
|
return new MessageTray.NotificationPolicy({ showInLockScreen: false });
|
||||||
|
},
|
||||||
|
|
||||||
buildRightClickMenu: function() {
|
buildRightClickMenu: function() {
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
@ -546,9 +547,8 @@ const AutorunTransientSource = new Lang.Class({
|
|||||||
this.notify(this._notification);
|
this.notify(this._notification);
|
||||||
},
|
},
|
||||||
|
|
||||||
createIcon: function(size) {
|
getIcon: function() {
|
||||||
return new St.Icon({ gicon: this.mount.get_icon(),
|
return this.mount.get_icon();
|
||||||
icon_size: size });
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -60,18 +60,14 @@ const KeyringDialog = new Lang.Class({
|
|||||||
|
|
||||||
this._controlTable = null;
|
this._controlTable = null;
|
||||||
|
|
||||||
let buttons = [{ label: '',
|
|
||||||
action: Lang.bind(this, this._onCancelButton),
|
|
||||||
key: Clutter.Escape
|
|
||||||
},
|
|
||||||
{ label: '',
|
|
||||||
action: Lang.bind(this, this._onContinueButton),
|
|
||||||
default: true
|
|
||||||
}]
|
|
||||||
|
|
||||||
this.setButtons(buttons);
|
this._cancelButton = this.addButton({ label: '',
|
||||||
this._cancelButton = buttons[0].button;
|
action: Lang.bind(this, this._onCancelButton),
|
||||||
this._continueButton = buttons[1].button;
|
key: Clutter.Escape });
|
||||||
|
this._continueButton = this.addButton({ label: '',
|
||||||
|
action: Lang.bind(this, this._onContinueButton),
|
||||||
|
default: true },
|
||||||
|
{ expand: true, 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('cancel-label', this._cancelButton, 'label', GObject.BindingFlags.SYNC_CREATE);
|
||||||
this.prompt.bind_property('continue-label', this._continueButton, 'label', GObject.BindingFlags.SYNC_CREATE);
|
this.prompt.bind_property('continue-label', this._continueButton, 'label', GObject.BindingFlags.SYNC_CREATE);
|
||||||
@ -146,6 +142,14 @@ const KeyringDialog = new Lang.Class({
|
|||||||
this._messageBox.add(table, { x_fill: true, y_fill: true });
|
this._messageBox.add(table, { x_fill: true, y_fill: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_updateSensitivity: function(sensitive) {
|
||||||
|
this._passwordEntry.reactive = sensitive;
|
||||||
|
this._passwordEntry.clutter_text.editable = sensitive;
|
||||||
|
|
||||||
|
this._continueButton.can_focus = sensitive;
|
||||||
|
this._continueButton.reactive = sensitive;
|
||||||
|
},
|
||||||
|
|
||||||
_ensureOpen: function() {
|
_ensureOpen: function() {
|
||||||
// NOTE: ModalDialog.open() is safe to call if the dialog is
|
// NOTE: ModalDialog.open() is safe to call if the dialog is
|
||||||
// already open - it just returns true without side-effects
|
// already open - it just returns true without side-effects
|
||||||
@ -167,12 +171,14 @@ const KeyringDialog = new Lang.Class({
|
|||||||
_onShowPassword: function(prompt) {
|
_onShowPassword: function(prompt) {
|
||||||
this._buildControlTable();
|
this._buildControlTable();
|
||||||
this._ensureOpen();
|
this._ensureOpen();
|
||||||
|
this._updateSensitivity(true);
|
||||||
this._passwordEntry.grab_key_focus();
|
this._passwordEntry.grab_key_focus();
|
||||||
},
|
},
|
||||||
|
|
||||||
_onShowConfirm: function(prompt) {
|
_onShowConfirm: function(prompt) {
|
||||||
this._buildControlTable();
|
this._buildControlTable();
|
||||||
this._ensureOpen();
|
this._ensureOpen();
|
||||||
|
this._updateSensitivity(true);
|
||||||
this._continueButton.grab_key_focus();
|
this._continueButton.grab_key_focus();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -192,6 +198,7 @@ const KeyringDialog = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_onContinueButton: function() {
|
_onContinueButton: function() {
|
||||||
|
this._updateSensitivity(false);
|
||||||
this.prompt.complete();
|
this.prompt.complete();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -215,7 +222,7 @@ const KeyringPrompter = new Lang.Class({
|
|||||||
enable: function() {
|
enable: function() {
|
||||||
this._prompter.register(Gio.DBus.session);
|
this._prompter.register(Gio.DBus.session);
|
||||||
this._dbusId = Gio.DBus.session.own_name('org.gnome.keyring.SystemPrompter',
|
this._dbusId = Gio.DBus.session.own_name('org.gnome.keyring.SystemPrompter',
|
||||||
Gio.BusNameOwnerFlags.REPLACE, null, null);
|
Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT, null, null);
|
||||||
},
|
},
|
||||||
|
|
||||||
disable: function() {
|
disable: function() {
|
||||||
|
@ -408,7 +408,10 @@ const VPNRequestHandler = new Lang.Class({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
cancel: function() {
|
cancel: function(respond) {
|
||||||
|
if (respond)
|
||||||
|
this._agent.respond(this._requestId, Shell.NetworkAgentResponse.USER_CANCELED);
|
||||||
|
|
||||||
if (this._newStylePlugin && this._shellDialog) {
|
if (this._newStylePlugin && this._shellDialog) {
|
||||||
this._shellDialog.close(global.get_current_time());
|
this._shellDialog.close(global.get_current_time());
|
||||||
this._shellDialog.destroy();
|
this._shellDialog.destroy();
|
||||||
@ -584,25 +587,41 @@ const NetworkAgent = new Lang.Class({
|
|||||||
Name: 'NetworkAgent',
|
Name: 'NetworkAgent',
|
||||||
|
|
||||||
_init: function() {
|
_init: function() {
|
||||||
this._native = new Shell.NetworkAgent({ auto_register: false,
|
this._native = new Shell.NetworkAgent({ identifier: 'org.gnome.Shell.NetworkAgent' });
|
||||||
identifier: 'org.gnome.Shell.NetworkAgent' });
|
|
||||||
|
|
||||||
this._dialogs = { };
|
this._dialogs = { };
|
||||||
this._vpnRequests = { };
|
this._vpnRequests = { };
|
||||||
|
|
||||||
this._native.connect('new-request', Lang.bind(this, this._newRequest));
|
this._native.connect('new-request', Lang.bind(this, this._newRequest));
|
||||||
this._native.connect('cancel-request', Lang.bind(this, this._cancelRequest));
|
this._native.connect('cancel-request', Lang.bind(this, this._cancelRequest));
|
||||||
|
|
||||||
|
this._enabled = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
enable: function() {
|
enable: function() {
|
||||||
this._native.register();
|
this._enabled = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
disable: function() {
|
disable: function() {
|
||||||
this._native.unregister();
|
let requestId;
|
||||||
|
|
||||||
|
for (requestId in this._dialogs)
|
||||||
|
this._dialogs[requestId].cancel();
|
||||||
|
this._dialogs = { };
|
||||||
|
|
||||||
|
for (requestId in this._vpnRequests)
|
||||||
|
this._vpnRequests[requestId].cancel(true);
|
||||||
|
this._vpnRequests = { };
|
||||||
|
|
||||||
|
this._enabled = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
_newRequest: function(agent, requestId, connection, settingName, hints, flags) {
|
_newRequest: function(agent, requestId, connection, settingName, hints, flags) {
|
||||||
|
if (!this._enabled) {
|
||||||
|
agent.respond(requestId, Shell.NetworkAgentResponse.USER_CANCELED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (settingName == 'vpn') {
|
if (settingName == 'vpn') {
|
||||||
this._vpnRequest(requestId, connection, hints, flags);
|
this._vpnRequest(requestId, connection, hints, flags);
|
||||||
return;
|
return;
|
||||||
@ -622,7 +641,7 @@ const NetworkAgent = new Lang.Class({
|
|||||||
this._dialogs[requestId].destroy();
|
this._dialogs[requestId].destroy();
|
||||||
delete this._dialogs[requestId];
|
delete this._dialogs[requestId];
|
||||||
} else if (this._vpnRequests[requestId]) {
|
} else if (this._vpnRequests[requestId]) {
|
||||||
this._vpnRequests[requestId].cancel();
|
this._vpnRequests[requestId].cancel(false);
|
||||||
delete this._vpnRequests[requestId];
|
delete this._vpnRequests[requestId];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -49,7 +49,7 @@ const AuthenticationDialog = new Lang.Class({
|
|||||||
let messageBox = new St.BoxLayout({ style_class: 'prompt-dialog-message-layout',
|
let messageBox = new St.BoxLayout({ style_class: 'prompt-dialog-message-layout',
|
||||||
vertical: true });
|
vertical: true });
|
||||||
mainContentBox.add(messageBox,
|
mainContentBox.add(messageBox,
|
||||||
{ y_align: St.Align.START });
|
{ expand: true, y_align: St.Align.START });
|
||||||
|
|
||||||
this._subjectLabel = new St.Label({ style_class: 'prompt-dialog-headline',
|
this._subjectLabel = new St.Label({ style_class: 'prompt-dialog-headline',
|
||||||
text: _("Authentication Required") });
|
text: _("Authentication Required") });
|
||||||
@ -159,14 +159,13 @@ const AuthenticationDialog = new Lang.Class({
|
|||||||
messageBox.add(this._nullMessageLabel);
|
messageBox.add(this._nullMessageLabel);
|
||||||
this._nullMessageLabel.show();
|
this._nullMessageLabel.show();
|
||||||
|
|
||||||
this.setButtons([{ label: _("Cancel"),
|
this._cancelButton = this.addButton({ label: _("Cancel"),
|
||||||
action: Lang.bind(this, this.cancel),
|
action: Lang.bind(this, this.cancel),
|
||||||
key: Clutter.Escape
|
key: Clutter.Escape });
|
||||||
},
|
this._okButton = this.addButton({ label: _("Authenticate"),
|
||||||
{ label: _("Authenticate"),
|
action: Lang.bind(this, this._onAuthenticateButtonPressed),
|
||||||
action: Lang.bind(this, this._onAuthenticateButtonPressed),
|
default: true },
|
||||||
default: true
|
{ expand: true, x_fill: false, x_align: St.Align.END });
|
||||||
}]);
|
|
||||||
|
|
||||||
this._doneEmitted = false;
|
this._doneEmitted = false;
|
||||||
|
|
||||||
@ -214,8 +213,17 @@ const AuthenticationDialog = new Lang.Class({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_updateSensitivity: function(sensitive) {
|
||||||
|
this._passwordEntry.reactive = sensitive;
|
||||||
|
this._passwordEntry.clutter_text.editable = sensitive;
|
||||||
|
|
||||||
|
this._okButton.can_focus = sensitive;
|
||||||
|
this._okButton.reactive = sensitive;
|
||||||
|
},
|
||||||
|
|
||||||
_onEntryActivate: function() {
|
_onEntryActivate: function() {
|
||||||
let response = this._passwordEntry.get_text();
|
let response = this._passwordEntry.get_text();
|
||||||
|
this._updateSensitivity(false);
|
||||||
this._session.response(response);
|
this._session.response(response);
|
||||||
// When the user responds, dismiss already shown info and
|
// When the user responds, dismiss already shown info and
|
||||||
// error texts (if any)
|
// error texts (if any)
|
||||||
@ -269,6 +277,7 @@ const AuthenticationDialog = new Lang.Class({
|
|||||||
this._passwordBox.show();
|
this._passwordBox.show();
|
||||||
this._passwordEntry.set_text('');
|
this._passwordEntry.set_text('');
|
||||||
this._passwordEntry.grab_key_focus();
|
this._passwordEntry.grab_key_focus();
|
||||||
|
this._updateSensitivity(true);
|
||||||
this._ensureOpen();
|
this._ensureOpen();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -326,11 +335,19 @@ const AuthenticationAgent = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
enable: function() {
|
enable: function() {
|
||||||
this._native.register();
|
try {
|
||||||
|
this._native.register();
|
||||||
|
} catch(e) {
|
||||||
|
log('Failed to register AuthenticationAgent');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
disable: function() {
|
disable: function() {
|
||||||
this._native.unregister();
|
try {
|
||||||
|
this._native.unregister();
|
||||||
|
} catch(e) {
|
||||||
|
log('Failed to unregister AuthenticationAgent');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_onInitiate: function(nativeAgent, actionId, message, iconName, cookie, userNames) {
|
_onInitiate: function(nativeAgent, actionId, message, iconName, cookie, userNames) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
|
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
|
const Main = imports.ui.main;
|
||||||
|
|
||||||
const Gio = imports.gi.Gio;
|
const Gio = imports.gi.Gio;
|
||||||
const Meta = imports.gi.Meta;
|
const Meta = imports.gi.Meta;
|
||||||
@ -16,13 +17,16 @@ const Recorder = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
enable: function() {
|
enable: function() {
|
||||||
global.display.add_keybinding('toggle-recording',
|
Main.wm.addKeybinding('toggle-recording',
|
||||||
this._bindingSettings,
|
this._bindingSettings,
|
||||||
Meta.KeyBindingFlags.NONE, Lang.bind(this, this._toggleRecorder));
|
Meta.KeyBindingFlags.NONE,
|
||||||
|
Shell.KeyBindingMode.NORMAL |
|
||||||
|
Shell.KeyBindingMode.OVERVIEW,
|
||||||
|
Lang.bind(this, this._toggleRecorder));
|
||||||
},
|
},
|
||||||
|
|
||||||
disable: function() {
|
disable: function() {
|
||||||
global.display.remove_keybinding('toggle-recording');
|
Main.wm.removeKeybinding('toggle-recording');
|
||||||
},
|
},
|
||||||
|
|
||||||
_ensureRecorder: function() {
|
_ensureRecorder: function() {
|
||||||
@ -41,7 +45,7 @@ const Recorder = new Lang.Class({
|
|||||||
recorder.set_framerate(this._recorderSettings.get_int('framerate'));
|
recorder.set_framerate(this._recorderSettings.get_int('framerate'));
|
||||||
/* Translators: this is a filename used for screencast recording */
|
/* Translators: this is a filename used for screencast recording */
|
||||||
// xgettext:no-c-format
|
// xgettext:no-c-format
|
||||||
recorder.set_filename(_("Screencast from %d %t") + '.' + this._recorderSettings.get_string('file-extension'));
|
recorder.set_file_template(_("Screencast from %d %t") + '.' + this._recorderSettings.get_string('file-extension'));
|
||||||
let pipeline = this._recorderSettings.get_string('pipeline');
|
let pipeline = this._recorderSettings.get_string('pipeline');
|
||||||
|
|
||||||
if (!pipeline.match(/^\s*$/))
|
if (!pipeline.match(/^\s*$/))
|
||||||
|
@ -13,6 +13,7 @@ const Tp = imports.gi.TelepathyGLib;
|
|||||||
const History = imports.misc.history;
|
const History = imports.misc.history;
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
const MessageTray = imports.ui.messageTray;
|
const MessageTray = imports.ui.messageTray;
|
||||||
|
const NotificationDaemon = imports.ui.notificationDaemon;
|
||||||
const Params = imports.misc.params;
|
const Params = imports.misc.params;
|
||||||
const PopupMenu = imports.ui.popupMenu;
|
const PopupMenu = imports.ui.popupMenu;
|
||||||
|
|
||||||
@ -383,25 +384,12 @@ const TelepathyClient = new Lang.Class({
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Display notification to ask user to accept/reject request */
|
/* Display notification to ask user to accept/reject request */
|
||||||
let source = this._ensureSubscriptionSource();
|
let source = this._ensureAppSource();
|
||||||
|
|
||||||
let notif = new SubscriptionRequestNotification(source, contact);
|
let notif = new SubscriptionRequestNotification(source, contact);
|
||||||
source.notify(notif);
|
source.notify(notif);
|
||||||
},
|
},
|
||||||
|
|
||||||
_ensureSubscriptionSource: function() {
|
|
||||||
if (this._subscriptionSource == null) {
|
|
||||||
this._subscriptionSource = new MessageTray.Source(_("Subscription request"),
|
|
||||||
'gtk-dialog-question');
|
|
||||||
Main.messageTray.add(this._subscriptionSource);
|
|
||||||
this._subscriptionSource.connect('destroy', Lang.bind(this, function () {
|
|
||||||
this._subscriptionSource = null;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._subscriptionSource;
|
|
||||||
},
|
|
||||||
|
|
||||||
_accountConnectionStatusNotifyCb: function(account) {
|
_accountConnectionStatusNotifyCb: function(account) {
|
||||||
let connectionError = account.connection_error;
|
let connectionError = account.connection_error;
|
||||||
|
|
||||||
@ -415,7 +403,7 @@ const TelepathyClient = new Lang.Class({
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
/* Display notification that account failed to connect */
|
/* Display notification that account failed to connect */
|
||||||
let source = this._ensureAccountSource();
|
let source = this._ensureAppSource();
|
||||||
|
|
||||||
notif = new AccountNotification(source, account, connectionError);
|
notif = new AccountNotification(source, account, connectionError);
|
||||||
this._accountNotifications[account.get_object_path()] = notif;
|
this._accountNotifications[account.get_object_path()] = notif;
|
||||||
@ -425,17 +413,18 @@ const TelepathyClient = new Lang.Class({
|
|||||||
source.notify(notif);
|
source.notify(notif);
|
||||||
},
|
},
|
||||||
|
|
||||||
_ensureAccountSource: function() {
|
_ensureAppSource: function() {
|
||||||
if (this._accountSource == null) {
|
if (this._appSource == null) {
|
||||||
this._accountSource = new MessageTray.Source(_("Connection error"),
|
this._appSource = new MessageTray.Source(_("Chat"), 'empathy');
|
||||||
'gtk-dialog-error');
|
this._appSource.policy = new NotificationDaemon.NotificationApplicationPolicy('empathy');
|
||||||
Main.messageTray.add(this._accountSource);
|
|
||||||
this._accountSource.connect('destroy', Lang.bind(this, function () {
|
Main.messageTray.add(this._appSource);
|
||||||
this._accountSource = null;
|
this._appSource.connect('destroy', Lang.bind(this, function () {
|
||||||
|
this._appSource = null;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._accountSource;
|
return this._appSource;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -492,12 +481,16 @@ const ChatSource = new Lang.Class({
|
|||||||
}));
|
}));
|
||||||
item.connect('activate', Lang.bind(this, function() {
|
item.connect('activate', Lang.bind(this, function() {
|
||||||
this.setMuted(!this.isMuted);
|
this.setMuted(!this.isMuted);
|
||||||
this.emit('done-displaying-content');
|
this.emit('done-displaying-content', false);
|
||||||
}));
|
}));
|
||||||
rightClickMenu.add(item.actor);
|
rightClickMenu.add(item.actor);
|
||||||
return rightClickMenu;
|
return rightClickMenu;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_createPolicy: function() {
|
||||||
|
return new NotificationDaemon.NotificationApplicationPolicy('empathy');
|
||||||
|
},
|
||||||
|
|
||||||
_updateAlias: function() {
|
_updateAlias: function() {
|
||||||
let oldAlias = this.title;
|
let oldAlias = this.title;
|
||||||
let newAlias = this._contact.get_alias();
|
let newAlias = this._contact.get_alias();
|
||||||
@ -509,51 +502,42 @@ const ChatSource = new Lang.Class({
|
|||||||
this._notification.appendAliasChange(oldAlias, newAlias);
|
this._notification.appendAliasChange(oldAlias, newAlias);
|
||||||
},
|
},
|
||||||
|
|
||||||
createIcon: function(size) {
|
getIcon: function() {
|
||||||
this._iconBox = new St.Bin({ style_class: 'avatar-box' });
|
|
||||||
this._iconBox._size = size;
|
|
||||||
let textureCache = St.TextureCache.get_default();
|
|
||||||
let file = this._contact.get_avatar_file();
|
let file = this._contact.get_avatar_file();
|
||||||
|
|
||||||
if (file) {
|
if (file) {
|
||||||
let uri = file.get_uri();
|
return new Gio.FileIcon({ file: file });
|
||||||
this._iconBox.child = textureCache.load_uri_async(uri, this._iconBox._size, this._iconBox._size);
|
|
||||||
} else {
|
} else {
|
||||||
this._iconBox.child = new St.Icon({ icon_name: 'avatar-default',
|
return new Gio.ThemedIcon({ name: 'avatar-default' });
|
||||||
icon_size: this._iconBox._size });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._iconBox;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createSecondaryIcon: function() {
|
getSecondaryIcon: function() {
|
||||||
let iconBox = new St.Bin();
|
let iconName;
|
||||||
iconBox.child = new St.Icon({ style_class: 'secondary-icon' });
|
|
||||||
let presenceType = this._contact.get_presence_type();
|
let presenceType = this._contact.get_presence_type();
|
||||||
|
|
||||||
switch (presenceType) {
|
switch (presenceType) {
|
||||||
case Tp.ConnectionPresenceType.AVAILABLE:
|
case Tp.ConnectionPresenceType.AVAILABLE:
|
||||||
iconBox.child.icon_name = 'user-available';
|
iconName = 'user-available';
|
||||||
break;
|
break;
|
||||||
case Tp.ConnectionPresenceType.BUSY:
|
case Tp.ConnectionPresenceType.BUSY:
|
||||||
iconBox.child.icon_name = 'user-busy';
|
iconName = 'user-busy';
|
||||||
break;
|
break;
|
||||||
case Tp.ConnectionPresenceType.OFFLINE:
|
case Tp.ConnectionPresenceType.OFFLINE:
|
||||||
iconBox.child.icon_name = 'user-offline';
|
iconName = 'user-offline';
|
||||||
break;
|
break;
|
||||||
case Tp.ConnectionPresenceType.HIDDEN:
|
case Tp.ConnectionPresenceType.HIDDEN:
|
||||||
iconBox.child.icon_name = 'user-invisible';
|
iconName = 'user-invisible';
|
||||||
break;
|
break;
|
||||||
case Tp.ConnectionPresenceType.AWAY:
|
case Tp.ConnectionPresenceType.AWAY:
|
||||||
iconBox.child.icon_name = 'user-away';
|
iconName = 'user-away';
|
||||||
break;
|
break;
|
||||||
case Tp.ConnectionPresenceType.EXTENDED_AWAY:
|
case Tp.ConnectionPresenceType.EXTENDED_AWAY:
|
||||||
iconBox.child.icon_name = 'user-idle';
|
iconName = 'user-idle';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
iconBox.child.icon_name = 'user-offline';
|
iconName = 'user-offline';
|
||||||
}
|
}
|
||||||
return iconBox;
|
return new Gio.ThemedIcon({ name: iconName });
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateAvatarIcon: function() {
|
_updateAvatarIcon: function() {
|
||||||
@ -564,7 +548,9 @@ const ChatSource = new Lang.Class({
|
|||||||
open: function(notification) {
|
open: function(notification) {
|
||||||
if (this._client.is_handling_channel(this._channel)) {
|
if (this._client.is_handling_channel(this._channel)) {
|
||||||
// We are handling the channel, try to pass it to Empathy
|
// We are handling the channel, try to pass it to Empathy
|
||||||
this._client.delegate_channels_async([this._channel], global.get_current_time(), '', null);
|
this._client.delegate_channels_async([this._channel],
|
||||||
|
global.get_current_time(),
|
||||||
|
'org.freedesktop.Telepathy.Client.Empathy.Chat', null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// We are not the handler, just ask to present the channel
|
// We are not the handler, just ask to present the channel
|
||||||
@ -654,6 +640,10 @@ const ChatSource = new Lang.Class({
|
|||||||
return this._pendingMessages.length;
|
return this._pendingMessages.length;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get indicatorCount() {
|
||||||
|
return this.count;
|
||||||
|
},
|
||||||
|
|
||||||
get unseenCount() {
|
get unseenCount() {
|
||||||
return this.count;
|
return this.count;
|
||||||
},
|
},
|
||||||
@ -733,7 +723,7 @@ const ChatSource = new Lang.Class({
|
|||||||
|
|
||||||
title = GLib.markup_escape_text(this.title, -1);
|
title = GLib.markup_escape_text(this.title, -1);
|
||||||
|
|
||||||
this._notification.update(this._notification.title, null, { customContent: true, secondaryIcon: this.createSecondaryIcon() });
|
this._notification.update(this._notification.title, null, { customContent: true, secondaryGIcon: this.getSecondaryIcon() });
|
||||||
|
|
||||||
if (message)
|
if (message)
|
||||||
msg += ' <i>(' + GLib.markup_escape_text(message, -1) + ')</i>';
|
msg += ' <i>(' + GLib.markup_escape_text(message, -1) + ')</i>';
|
||||||
@ -751,8 +741,7 @@ const ChatSource = new Lang.Class({
|
|||||||
_ackMessages: function() {
|
_ackMessages: function() {
|
||||||
// Don't clear our messages here, tp-glib will send a
|
// Don't clear our messages here, tp-glib will send a
|
||||||
// 'pending-message-removed' for each one.
|
// 'pending-message-removed' for each one.
|
||||||
this._channel.ack_all_pending_messages_async(Lang.bind(this, function(src, result) {
|
this._channel.ack_all_pending_messages_async(null);
|
||||||
this._channel.ack_all_pending_messages_finish(result);}));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -761,7 +750,7 @@ const ChatNotification = new Lang.Class({
|
|||||||
Extends: MessageTray.Notification,
|
Extends: MessageTray.Notification,
|
||||||
|
|
||||||
_init: function(source) {
|
_init: function(source) {
|
||||||
this.parent(source, source.title, null, { customContent: true, secondaryIcon: source.createSecondaryIcon() });
|
this.parent(source, source.title, null, { customContent: true, secondaryGIcon: source.getSecondaryIcon() });
|
||||||
this.setResident(true);
|
this.setResident(true);
|
||||||
|
|
||||||
this._responseEntry = new St.Entry({ style_class: 'chat-response',
|
this._responseEntry = new St.Entry({ style_class: 'chat-response',
|
||||||
@ -778,16 +767,19 @@ const ChatNotification = new Lang.Class({
|
|||||||
this.emit('unfocused');
|
this.emit('unfocused');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._oldMaxScrollAdjustment = 0;
|
|
||||||
this._createScrollArea();
|
this._createScrollArea();
|
||||||
this._lastGroup = null;
|
this._lastGroup = null;
|
||||||
this._lastGroupActor = null;
|
this._lastGroupActor = null;
|
||||||
|
|
||||||
|
// Keep track of the bottom position for the current adjustment and
|
||||||
|
// force a scroll to the bottom if things change while we were at the
|
||||||
|
// bottom
|
||||||
|
this._oldMaxScrollValue = this._scrollArea.vscroll.adjustment.value;
|
||||||
|
this._scrollArea.add_style_class_name('chat-notification-scrollview');
|
||||||
this._scrollArea.vscroll.adjustment.connect('changed', Lang.bind(this, function(adjustment) {
|
this._scrollArea.vscroll.adjustment.connect('changed', Lang.bind(this, function(adjustment) {
|
||||||
let currentValue = adjustment.value + adjustment.page_size;
|
if (adjustment.value == this._oldMaxScrollValue)
|
||||||
if (currentValue == this._oldMaxScrollAdjustment)
|
|
||||||
this.scrollTo(St.Side.BOTTOM);
|
this.scrollTo(St.Side.BOTTOM);
|
||||||
this._oldMaxScrollAdjustment = adjustment.upper;
|
this._oldMaxScrollValue = Math.max(adjustment.lower, adjustment.upper - adjustment.page_size);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._inputHistory = new History.HistoryManager({ entry: this._responseEntry.clutter_text });
|
this._inputHistory = new History.HistoryManager({ entry: this._responseEntry.clutter_text });
|
||||||
@ -939,24 +931,30 @@ const ChatNotification = new Lang.Class({
|
|||||||
|
|
||||||
let format;
|
let format;
|
||||||
|
|
||||||
// Show a week day and time if date is in the last week
|
// Show only the hour if date is on today
|
||||||
if (daysAgo < 1 || (daysAgo < 7 && now.getDay() != date.getDay())) {
|
if(daysAgo < 1){
|
||||||
/* Translators: this is a time format string followed by a date.
|
format = "<b>%H:%M</b>";
|
||||||
If applicable, replace %X with a strftime format valid for your
|
}
|
||||||
locale, without seconds. */
|
// Show the word "Yesterday" and time if date is on yesterday
|
||||||
|
else if(daysAgo <2){
|
||||||
|
/* Translators: this is the word "Yesterday" followed by a time string. i.e. "Yesterday, 14:30"*/
|
||||||
// xgettext:no-c-format
|
// xgettext:no-c-format
|
||||||
format = _("Sent at <b>%X</b> on <b>%A</b>");
|
format = _("<b>Yesterday</b>, <b>%H:%M</b>");
|
||||||
|
}
|
||||||
|
// Show a week day and time if date is in the last week
|
||||||
|
else if (daysAgo < 7) {
|
||||||
|
/* Translators: this is the week day name followed by a time string. i.e. "Monday, 14:30*/
|
||||||
|
// xgettext:no-c-format
|
||||||
|
format = _("<b>%A</b>, <b>%H:%M</b>");
|
||||||
|
|
||||||
} else if (date.getYear() == now.getYear()) {
|
} else if (date.getYear() == now.getYear()) {
|
||||||
/* Translators: this is a time format in the style of "Wednesday, May 25",
|
/* Translators: this is the month name and day number followed by a time string. i.e. "May 25, 14:30"*/
|
||||||
shown when you get a chat message in the same year. */
|
|
||||||
// xgettext:no-c-format
|
// xgettext:no-c-format
|
||||||
format = _("Sent on <b>%A</b>, <b>%B %d</b>");
|
format = _("<b>%B</b> <b>%d</b>, <b>%H:%M</b>");
|
||||||
} else {
|
} else {
|
||||||
/* Translators: this is a time format in the style of "Wednesday, May 25, 2012",
|
/* Translators: this is the month name, day number, year number followed by a time string. i.e. "May 25 2012, 14:30"*/
|
||||||
shown when you get a chat message in a different year. */
|
|
||||||
// xgettext:no-c-format
|
// xgettext:no-c-format
|
||||||
format = _("Sent on <b>%A</b>, <b>%B %d</b>, %Y");
|
format = _("<b>%B</b> <b>%d</b> <b>%Y</b>, <b>%H:%M</b> ");
|
||||||
}
|
}
|
||||||
|
|
||||||
return date.toLocaleFormat(format);
|
return date.toLocaleFormat(format);
|
||||||
@ -969,8 +967,7 @@ const ChatNotification = new Lang.Class({
|
|||||||
let timeLabel = this._append({ body: this._formatTimestamp(lastMessageDate),
|
let timeLabel = this._append({ body: this._formatTimestamp(lastMessageDate),
|
||||||
group: 'meta',
|
group: 'meta',
|
||||||
styles: ['chat-meta-message'],
|
styles: ['chat-meta-message'],
|
||||||
childProps: { expand: true, x_fill: false,
|
childProps: { expand: true, x_fill: false },
|
||||||
x_align: St.Align.END },
|
|
||||||
noTimestamp: true,
|
noTimestamp: true,
|
||||||
timestamp: lastMessageTime });
|
timestamp: lastMessageTime });
|
||||||
|
|
||||||
@ -1062,6 +1059,10 @@ const ApproverSource = new Lang.Class({
|
|||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_createPolicy: function() {
|
||||||
|
return new NotificationDaemon.NotificationApplicationPolicy('empathy');
|
||||||
|
},
|
||||||
|
|
||||||
destroy: function() {
|
destroy: function() {
|
||||||
if (this._invalidId != 0) {
|
if (this._invalidId != 0) {
|
||||||
this._dispatchOp.disconnect(this._invalidId);
|
this._dispatchOp.disconnect(this._invalidId);
|
||||||
@ -1071,9 +1072,8 @@ const ApproverSource = new Lang.Class({
|
|||||||
this.parent();
|
this.parent();
|
||||||
},
|
},
|
||||||
|
|
||||||
createIcon: function(size) {
|
getIcon: function() {
|
||||||
return new St.Icon({ gicon: this._gicon,
|
return this._gicon;
|
||||||
icon_size: size });
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1134,7 +1134,7 @@ const AudioVideoNotification = new Lang.Class({
|
|||||||
this.parent(source, title, null, { customContent: true });
|
this.parent(source, title, null, { customContent: true });
|
||||||
this.setResident(true);
|
this.setResident(true);
|
||||||
|
|
||||||
this.addButton('reject', _("Reject"));
|
this.addButton('reject', _("Decline"));
|
||||||
/* translators: this is a button label (verb), not a noun */
|
/* translators: this is a button label (verb), not a noun */
|
||||||
this.addButton('answer', _("Answer"));
|
this.addButton('answer', _("Answer"));
|
||||||
|
|
||||||
@ -1351,29 +1351,18 @@ const AccountNotification = new Lang.Class({
|
|||||||
this.parent(source,
|
this.parent(source,
|
||||||
/* translators: argument is the account name, like
|
/* translators: argument is the account name, like
|
||||||
* name@jabber.org for example. */
|
* name@jabber.org for example. */
|
||||||
_("Connection to %s failed").format(account.get_display_name()),
|
_("Unable to connect to %s").format(account.get_display_name()),
|
||||||
null, { customContent: true });
|
this._getMessage(connectionError));
|
||||||
|
|
||||||
this._label = new St.Label();
|
|
||||||
this.addActor(this._label);
|
|
||||||
this._updateMessage(connectionError);
|
|
||||||
|
|
||||||
this._account = account;
|
this._account = account;
|
||||||
|
|
||||||
this.addButton('reconnect', _("Reconnect"));
|
this.addButton('view', _("View account"));
|
||||||
this.addButton('edit', _("Edit account"));
|
|
||||||
|
|
||||||
this.connect('action-invoked', Lang.bind(this, function(self, action) {
|
this.connect('action-invoked', Lang.bind(this, function(self, action) {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'reconnect':
|
case 'view':
|
||||||
// If it fails again, a new notification should pop up with the
|
let cmd = 'empathy-accounts --select-account=' +
|
||||||
// new error.
|
account.get_path_suffix();
|
||||||
account.reconnect_async(null);
|
|
||||||
break;
|
|
||||||
case 'edit':
|
|
||||||
let cmd = '/usr/bin/empathy-accounts'
|
|
||||||
+ ' --select-account=%s'
|
|
||||||
.format(account.get_path_suffix());
|
|
||||||
let app_info = Gio.app_info_create_from_commandline(cmd, null, 0);
|
let app_info = Gio.app_info_create_from_commandline(cmd, null, 0);
|
||||||
app_info.launch([], global.create_app_launch_context());
|
app_info.launch([], global.create_app_launch_context());
|
||||||
break;
|
break;
|
||||||
@ -1396,19 +1385,19 @@ const AccountNotification = new Lang.Class({
|
|||||||
if (status == Tp.ConnectionStatus.CONNECTED) {
|
if (status == Tp.ConnectionStatus.CONNECTED) {
|
||||||
this.destroy();
|
this.destroy();
|
||||||
} else if (status == Tp.ConnectionStatus.DISCONNECTED) {
|
} else if (status == Tp.ConnectionStatus.DISCONNECTED) {
|
||||||
this._updateMessage(account.connection_error);
|
this.update(this.title, this._getMessage(account.connection_error));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateMessage: function(connectionError) {
|
_getMessage: function(connectionError) {
|
||||||
let message;
|
let message;
|
||||||
if (connectionError in _connectionErrorMessages) {
|
if (connectionError in _connectionErrorMessages) {
|
||||||
message = _connectionErrorMessages[connectionError];
|
message = _connectionErrorMessages[connectionError];
|
||||||
} else {
|
} else {
|
||||||
message = _("Unknown reason");
|
message = _("Unknown reason");
|
||||||
}
|
}
|
||||||
this._label.set_text(message);
|
return message;
|
||||||
},
|
},
|
||||||
|
|
||||||
destroy: function() {
|
destroy: function() {
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
|
||||||
const Clutter = imports.gi.Clutter;
|
const Clutter = imports.gi.Clutter;
|
||||||
const Gdk = imports.gi.Gdk;
|
|
||||||
const Gtk = imports.gi.Gtk;
|
const Gtk = imports.gi.Gtk;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
const Meta = imports.gi.Meta;
|
const Meta = imports.gi.Meta;
|
||||||
const Shell = imports.gi.Shell;
|
const Shell = imports.gi.Shell;
|
||||||
const St = imports.gi.St;
|
const St = imports.gi.St;
|
||||||
|
|
||||||
const AltTab = imports.ui.altTab;
|
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
|
const SwitcherPopup = imports.ui.switcherPopup;
|
||||||
const Params = imports.misc.params;
|
const Params = imports.misc.params;
|
||||||
const Tweener = imports.ui.tweener;
|
const Tweener = imports.ui.tweener;
|
||||||
|
|
||||||
@ -27,6 +26,9 @@ const CtrlAltTabManager = new Lang.Class({
|
|||||||
|
|
||||||
_init: function() {
|
_init: function() {
|
||||||
this._items = [];
|
this._items = [];
|
||||||
|
this.addGroup(global.window_group, _("Windows"),
|
||||||
|
'emblem-documents-symbolic', { sortGroup: SortGroup.TOP,
|
||||||
|
focusCallback: Lang.bind(this, this._focusWindows) });
|
||||||
},
|
},
|
||||||
|
|
||||||
addGroup: function(root, name, icon, params) {
|
addGroup: function(root, name, icon, params) {
|
||||||
@ -40,11 +42,13 @@ const CtrlAltTabManager = new Lang.Class({
|
|||||||
|
|
||||||
this._items.push(item);
|
this._items.push(item);
|
||||||
root.connect('destroy', Lang.bind(this, function() { this.removeGroup(root); }));
|
root.connect('destroy', Lang.bind(this, function() { this.removeGroup(root); }));
|
||||||
global.focus_manager.add_group(root);
|
if (root instanceof St.Widget)
|
||||||
|
global.focus_manager.add_group(root);
|
||||||
},
|
},
|
||||||
|
|
||||||
removeGroup: function(root) {
|
removeGroup: function(root) {
|
||||||
global.focus_manager.remove_group(root);
|
if (root instanceof St.Widget)
|
||||||
|
global.focus_manager.remove_group(root);
|
||||||
for (let i = 0; i < this._items.length; i++) {
|
for (let i = 0; i < this._items.length; i++) {
|
||||||
if (this._items[i].root == root) {
|
if (this._items[i].root == root) {
|
||||||
this._items.splice(i, 1);
|
this._items.splice(i, 1);
|
||||||
@ -53,11 +57,9 @@ const CtrlAltTabManager = new Lang.Class({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
focusGroup: function(item) {
|
focusGroup: function(item, timestamp) {
|
||||||
if (item.window) {
|
if (item.focusCallback) {
|
||||||
Main.activateWindow(item.window);
|
item.focusCallback(timestamp);
|
||||||
} else if (item.focusCallback) {
|
|
||||||
item.focusCallback();
|
|
||||||
} else {
|
} else {
|
||||||
if (global.stage_input_mode == Shell.StageInputMode.NONREACTIVE ||
|
if (global.stage_input_mode == Shell.StageInputMode.NONREACTIVE ||
|
||||||
global.stage_input_mode == Shell.StageInputMode.NORMAL)
|
global.stage_input_mode == Shell.StageInputMode.NORMAL)
|
||||||
@ -75,24 +77,14 @@ const CtrlAltTabManager = new Lang.Class({
|
|||||||
if (a.sortGroup != b.sortGroup)
|
if (a.sortGroup != b.sortGroup)
|
||||||
return a.sortGroup - b.sortGroup;
|
return a.sortGroup - b.sortGroup;
|
||||||
|
|
||||||
let y;
|
let ax, bx, y;
|
||||||
if (a.x == undefined) {
|
[ax, y] = a.proxy.get_transformed_position();
|
||||||
if (a.window)
|
[bx, y] = b.proxy.get_transformed_position();
|
||||||
a.x = a.window.get_compositor_private().x;
|
|
||||||
else
|
|
||||||
[a.x, y] = a.proxy.get_transformed_position();
|
|
||||||
}
|
|
||||||
if (b.x == undefined) {
|
|
||||||
if (b.window)
|
|
||||||
b.x = b.window.get_compositor_private().x;
|
|
||||||
else
|
|
||||||
[b.x, y] = b.proxy.get_transformed_position();
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.x - b.x;
|
return ax - bx;
|
||||||
},
|
},
|
||||||
|
|
||||||
popup: function(backwards, mask) {
|
popup: function(backward, binding, mask) {
|
||||||
// Start with the set of focus groups that are currently mapped
|
// Start with the set of focus groups that are currently mapped
|
||||||
let items = this._items.filter(function (item) { return item.proxy.mapped; });
|
let items = this._items.filter(function (item) { return item.proxy.mapped; });
|
||||||
|
|
||||||
@ -110,8 +102,12 @@ const CtrlAltTabManager = new Lang.Class({
|
|||||||
icon = app.create_icon_texture(POPUP_APPICON_SIZE);
|
icon = app.create_icon_texture(POPUP_APPICON_SIZE);
|
||||||
else
|
else
|
||||||
icon = textureCache.bind_pixbuf_property(windows[i], 'icon');
|
icon = textureCache.bind_pixbuf_property(windows[i], 'icon');
|
||||||
items.push({ window: windows[i],
|
items.push({ name: windows[i].title,
|
||||||
name: windows[i].title,
|
proxy: windows[i].get_compositor_private(),
|
||||||
|
focusCallback: Lang.bind(windows[i],
|
||||||
|
function(timestamp) {
|
||||||
|
Main.activateWindow(this, timestamp);
|
||||||
|
}),
|
||||||
iconActor: icon,
|
iconActor: icon,
|
||||||
sortGroup: SortGroup.MIDDLE });
|
sortGroup: SortGroup.MIDDLE });
|
||||||
}
|
}
|
||||||
@ -123,187 +119,63 @@ const CtrlAltTabManager = new Lang.Class({
|
|||||||
items.sort(Lang.bind(this, this._sortItems));
|
items.sort(Lang.bind(this, this._sortItems));
|
||||||
|
|
||||||
if (!this._popup) {
|
if (!this._popup) {
|
||||||
this._popup = new CtrlAltTabPopup();
|
this._popup = new CtrlAltTabPopup(items);
|
||||||
this._popup.show(items, backwards, mask);
|
this._popup.show(backward, binding, mask);
|
||||||
|
|
||||||
this._popup.actor.connect('destroy',
|
this._popup.actor.connect('destroy',
|
||||||
Lang.bind(this, function() {
|
Lang.bind(this, function() {
|
||||||
this._popup = null;
|
this._popup = null;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_focusWindows: function(timestamp) {
|
||||||
|
global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
|
||||||
|
global.stage.key_focus = null;
|
||||||
|
global.screen.focus_default_window(timestamp);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function mod(a, b) {
|
|
||||||
return (a + b) % b;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CtrlAltTabPopup = new Lang.Class({
|
const CtrlAltTabPopup = new Lang.Class({
|
||||||
Name: 'CtrlAltTabPopup',
|
Name: 'CtrlAltTabPopup',
|
||||||
|
Extends: SwitcherPopup.SwitcherPopup,
|
||||||
|
|
||||||
_init : function() {
|
_createSwitcher: function() {
|
||||||
this.actor = new Shell.GenericContainer({ name: 'ctrlAltTabPopup',
|
this._switcherList = new CtrlAltTabSwitcher(this._items);
|
||||||
reactive: true });
|
return true;
|
||||||
|
|
||||||
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
|
||||||
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
|
||||||
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
|
||||||
|
|
||||||
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
|
||||||
|
|
||||||
this._haveModal = false;
|
|
||||||
this._modifierMask = 0;
|
|
||||||
this._selection = 0;
|
|
||||||
|
|
||||||
Main.uiGroup.add_actor(this.actor);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_getPreferredWidth: function (actor, forHeight, alloc) {
|
_initialSelection: function(backward, binding) {
|
||||||
let primary = Main.layoutManager.primaryMonitor;
|
if (binding == 'switch-panels') {
|
||||||
|
if (backward)
|
||||||
alloc.min_size = primary.width;
|
this._selectedIndex = this._items.length - 1;
|
||||||
alloc.natural_size = primary.width;
|
} else if (binding == 'switch-panels-backward') {
|
||||||
},
|
if (!backward)
|
||||||
|
this._selectedIndex = this._items.length - 1;
|
||||||
_getPreferredHeight: function (actor, forWidth, alloc) {
|
|
||||||
let primary = Main.layoutManager.primaryMonitor;
|
|
||||||
|
|
||||||
alloc.min_size = primary.height;
|
|
||||||
alloc.natural_size = primary.height;
|
|
||||||
},
|
|
||||||
|
|
||||||
_allocate: function (actor, box, flags) {
|
|
||||||
let childBox = new Clutter.ActorBox();
|
|
||||||
let primary = Main.layoutManager.primaryMonitor;
|
|
||||||
|
|
||||||
let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
|
|
||||||
let vPadding = this.actor.get_theme_node().get_vertical_padding();
|
|
||||||
let hPadding = this.actor.get_theme_node().get_horizontal_padding();
|
|
||||||
|
|
||||||
let [childMinHeight, childNaturalHeight] = this._switcher.actor.get_preferred_height(primary.width - hPadding);
|
|
||||||
let [childMinWidth, childNaturalWidth] = this._switcher.actor.get_preferred_width(childNaturalHeight);
|
|
||||||
childBox.x1 = Math.max(primary.x + leftPadding, primary.x + Math.floor((primary.width - childNaturalWidth) / 2));
|
|
||||||
childBox.x2 = Math.min(primary.x + primary.width - hPadding, childBox.x1 + childNaturalWidth);
|
|
||||||
childBox.y1 = primary.y + Math.floor((primary.height - childNaturalHeight) / 2);
|
|
||||||
childBox.y2 = childBox.y1 + childNaturalHeight;
|
|
||||||
this._switcher.actor.allocate(childBox, flags);
|
|
||||||
},
|
|
||||||
|
|
||||||
show : function(items, startBackwards, mask) {
|
|
||||||
if (!Main.pushModal(this.actor))
|
|
||||||
return false;
|
|
||||||
this._haveModal = true;
|
|
||||||
this._modifierMask = AltTab.primaryModifier(mask);
|
|
||||||
|
|
||||||
this._keyPressEventId = this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
|
|
||||||
this._keyReleaseEventId = this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
|
|
||||||
|
|
||||||
this._items = items;
|
|
||||||
this._switcher = new CtrlAltTabSwitcher(items);
|
|
||||||
this.actor.add_actor(this._switcher.actor);
|
|
||||||
|
|
||||||
if (startBackwards)
|
|
||||||
this._selection = this._items.length - 1;
|
|
||||||
this._select(this._selection);
|
|
||||||
|
|
||||||
let [x, y, mods] = global.get_pointer();
|
|
||||||
if (!(mods & this._modifierMask)) {
|
|
||||||
this._finish();
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
this._select(this._selectedIndex);
|
||||||
this.actor.opacity = 0;
|
|
||||||
this.actor.show();
|
|
||||||
Tweener.addTween(this.actor,
|
|
||||||
{ opacity: 255,
|
|
||||||
time: POPUP_FADE_TIME,
|
|
||||||
transition: 'easeOutQuad'
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_next : function() {
|
_keyPressHandler: function(keysym, backwards, action) {
|
||||||
return mod(this._selection + 1, this._items.length);
|
if (action == Meta.KeyBindingAction.SWITCH_PANELS)
|
||||||
},
|
this._select(backwards ? this._previous() : this._next());
|
||||||
|
else if (action == Meta.KeyBindingAction.SWITCH_PANELS_BACKWARD)
|
||||||
_previous : function() {
|
this._select(backwards ? this._next() : this._previous());
|
||||||
return mod(this._selection - 1, this._items.length);
|
else if (keysym == Clutter.Left)
|
||||||
},
|
|
||||||
|
|
||||||
_keyPressEvent : function(actor, event) {
|
|
||||||
let keysym = event.get_key_symbol();
|
|
||||||
let shift = (event.get_state() & Clutter.ModifierType.SHIFT_MASK);
|
|
||||||
if (shift && keysym == Clutter.KEY_Tab)
|
|
||||||
keysym = Clutter.ISO_Left_Tab;
|
|
||||||
|
|
||||||
if (keysym == Clutter.KEY_Escape)
|
|
||||||
this.destroy();
|
|
||||||
else if (keysym == Clutter.KEY_Tab)
|
|
||||||
this._select(this._next());
|
|
||||||
else if (keysym == Clutter.KEY_ISO_Left_Tab)
|
|
||||||
this._select(this._previous());
|
this._select(this._previous());
|
||||||
else if (keysym == Clutter.KEY_Left)
|
else if (keysym == Clutter.Right)
|
||||||
this._select(this._previous());
|
|
||||||
else if (keysym == Clutter.KEY_Right)
|
|
||||||
this._select(this._next());
|
this._select(this._next());
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_keyReleaseEvent : function(actor, event) {
|
_finish : function(time) {
|
||||||
let [x, y, mods] = global.get_pointer();
|
this.parent(time);
|
||||||
let state = mods & this._modifierMask;
|
Main.ctrlAltTabManager.focusGroup(this._items[this._selectedIndex], time);
|
||||||
|
|
||||||
if (state == 0)
|
|
||||||
this._finish();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_finish : function() {
|
|
||||||
this.destroy();
|
|
||||||
|
|
||||||
Main.ctrlAltTabManager.focusGroup(this._items[this._selection]);
|
|
||||||
},
|
|
||||||
|
|
||||||
_popModal: function() {
|
|
||||||
if (this._haveModal) {
|
|
||||||
Main.popModal(this.actor);
|
|
||||||
this._haveModal = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
destroy : function() {
|
|
||||||
this._popModal();
|
|
||||||
Tweener.addTween(this.actor,
|
|
||||||
{ opacity: 0,
|
|
||||||
time: POPUP_FADE_TIME,
|
|
||||||
transition: 'easeOutQuad',
|
|
||||||
onComplete: Lang.bind(this,
|
|
||||||
function() {
|
|
||||||
this.actor.destroy();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_onDestroy : function() {
|
|
||||||
this._popModal();
|
|
||||||
if (this._keyPressEventId)
|
|
||||||
this.actor.disconnect(this._keyPressEventId);
|
|
||||||
if (this._keyReleaseEventId)
|
|
||||||
this.actor.disconnect(this._keyReleaseEventId);
|
|
||||||
},
|
|
||||||
|
|
||||||
_select : function(num) {
|
|
||||||
this._selection = num;
|
|
||||||
this._switcher.highlight(num);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const CtrlAltTabSwitcher = new Lang.Class({
|
const CtrlAltTabSwitcher = new Lang.Class({
|
||||||
Name: 'CtrlAltTabSwitcher',
|
Name: 'CtrlAltTabSwitcher',
|
||||||
Extends: AltTab.SwitcherList,
|
Extends: SwitcherPopup.SwitcherList,
|
||||||
|
|
||||||
_init : function(items) {
|
_init : function(items) {
|
||||||
this.parent(true);
|
this.parent(true);
|
||||||
|
256
js/ui/dash.js
256
js/ui/dash.js
@ -22,12 +22,8 @@ const DASH_ITEM_LABEL_HIDE_TIME = 0.1;
|
|||||||
const DASH_ITEM_HOVER_TIMEOUT = 300;
|
const DASH_ITEM_HOVER_TIMEOUT = 300;
|
||||||
|
|
||||||
function getAppFromSource(source) {
|
function getAppFromSource(source) {
|
||||||
if (source instanceof AppDisplay.AppWellIcon) {
|
if (source instanceof AppDisplay.AppIcon) {
|
||||||
let appSystem = Shell.AppSystem.get_default();
|
return source.app;
|
||||||
return appSystem.lookup_app(source.getId());
|
|
||||||
} else if (source.metaWindow) {
|
|
||||||
let tracker = Shell.WindowTracker.get_default();
|
|
||||||
return tracker.get_window_app(source.metaWindow);
|
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -37,30 +33,26 @@ function getAppFromSource(source) {
|
|||||||
// when requesting a size
|
// when requesting a size
|
||||||
const DashItemContainer = new Lang.Class({
|
const DashItemContainer = new Lang.Class({
|
||||||
Name: 'DashItemContainer',
|
Name: 'DashItemContainer',
|
||||||
|
Extends: St.Widget,
|
||||||
|
|
||||||
_init: function() {
|
_init: function() {
|
||||||
this.actor = new Shell.GenericContainer({ style_class: 'dash-item-container' });
|
this.parent({ 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._labelText = "";
|
this._labelText = "";
|
||||||
this.label = new St.Label({ style_class: 'dash-label'});
|
this.label = new St.Label({ style_class: 'dash-label'});
|
||||||
this.label.hide();
|
this.label.hide();
|
||||||
Main.layoutManager.addChrome(this.label);
|
Main.layoutManager.addChrome(this.label);
|
||||||
this.actor.label_actor = this.label;
|
this.label_actor = this.label;
|
||||||
|
|
||||||
this.child = null;
|
this.child = null;
|
||||||
this._childScale = 1;
|
this._childScale = 0;
|
||||||
this._childOpacity = 255;
|
this._childOpacity = 0;
|
||||||
this.animatingOut = false;
|
this.animatingOut = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
_allocate: function(actor, box, flags) {
|
vfunc_allocate: function(box, flags) {
|
||||||
|
this.set_allocation(box, flags);
|
||||||
|
|
||||||
if (this.child == null)
|
if (this.child == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -82,28 +74,28 @@ const DashItemContainer = new Lang.Class({
|
|||||||
this.child.allocate(childBox, flags);
|
this.child.allocate(childBox, flags);
|
||||||
},
|
},
|
||||||
|
|
||||||
_getPreferredHeight: function(actor, forWidth, alloc) {
|
vfunc_get_preferred_height: function(forWidth) {
|
||||||
alloc.min_size = 0;
|
let themeNode = this.get_theme_node();
|
||||||
alloc.natural_size = 0;
|
|
||||||
|
|
||||||
if (this.child == null)
|
if (this.child == null)
|
||||||
return;
|
return [0, 0];
|
||||||
|
|
||||||
|
forWidth = themeNode.adjust_for_width(forWidth);
|
||||||
let [minHeight, natHeight] = this.child.get_preferred_height(forWidth);
|
let [minHeight, natHeight] = this.child.get_preferred_height(forWidth);
|
||||||
alloc.min_size += minHeight * this.child.scale_y;
|
return themeNode.adjust_preferred_height(minHeight * this.child.scale_y,
|
||||||
alloc.natural_size += natHeight * this.child.scale_y;
|
natHeight * this.child.scale_y);
|
||||||
},
|
},
|
||||||
|
|
||||||
_getPreferredWidth: function(actor, forHeight, alloc) {
|
vfunc_get_preferred_width: function(forHeight) {
|
||||||
alloc.min_size = 0;
|
let themeNode = this.get_theme_node();
|
||||||
alloc.natural_size = 0;
|
|
||||||
|
|
||||||
if (this.child == null)
|
if (this.child == null)
|
||||||
return;
|
return [0, 0];
|
||||||
|
|
||||||
|
forHeight = themeNode.adjust_for_height(forHeight);
|
||||||
let [minWidth, natWidth] = this.child.get_preferred_width(forHeight);
|
let [minWidth, natWidth] = this.child.get_preferred_width(forHeight);
|
||||||
alloc.min_size = minWidth * this.child.scale_y;
|
return themeNode.adjust_preferred_width(minWidth * this.child.scale_y,
|
||||||
alloc.natural_size = natWidth * this.child.scale_y;
|
natWidth * this.child.scale_y);
|
||||||
},
|
},
|
||||||
|
|
||||||
showLabel: function() {
|
showLabel: function() {
|
||||||
@ -114,9 +106,9 @@ const DashItemContainer = new Lang.Class({
|
|||||||
this.label.opacity = 0;
|
this.label.opacity = 0;
|
||||||
this.label.show();
|
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 labelHeight = this.label.get_height();
|
||||||
let yOffset = Math.floor((itemHeight - labelHeight) / 2)
|
let yOffset = Math.floor((itemHeight - labelHeight) / 2)
|
||||||
@ -130,7 +122,7 @@ const DashItemContainer = new Lang.Class({
|
|||||||
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
|
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
|
||||||
x = stageX - this.label.get_width() - xOffset;
|
x = stageX - this.label.get_width() - xOffset;
|
||||||
else
|
else
|
||||||
x = stageX + this.actor.get_width() + xOffset;
|
x = stageX + this.get_width() + xOffset;
|
||||||
|
|
||||||
this.label.set_position(x, y);
|
this.label.set_position(x, y);
|
||||||
Tweener.addTween(this.label,
|
Tweener.addTween(this.label,
|
||||||
@ -142,6 +134,7 @@ const DashItemContainer = new Lang.Class({
|
|||||||
|
|
||||||
setLabelText: function(text) {
|
setLabelText: function(text) {
|
||||||
this._labelText = text;
|
this._labelText = text;
|
||||||
|
this.child.accessible_name = text;
|
||||||
},
|
},
|
||||||
|
|
||||||
hideLabel: function () {
|
hideLabel: function () {
|
||||||
@ -159,22 +152,25 @@ const DashItemContainer = new Lang.Class({
|
|||||||
if (this.child == actor)
|
if (this.child == actor)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.actor.destroy_all_children();
|
this.destroy_all_children();
|
||||||
|
|
||||||
this.child = actor;
|
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)
|
if (this.child == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.childScale = 0;
|
let time = animate ? DASH_ANIMATION_TIME : 0;
|
||||||
this.childOpacity = 0;
|
|
||||||
Tweener.addTween(this,
|
Tweener.addTween(this,
|
||||||
{ childScale: 1.0,
|
{ childScale: 1.0,
|
||||||
childOpacity: 255,
|
childOpacity: 255,
|
||||||
time: DASH_ANIMATION_TIME,
|
time: time,
|
||||||
transition: 'easeOutQuad'
|
transition: 'easeOutQuad'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -183,7 +179,7 @@ const DashItemContainer = new Lang.Class({
|
|||||||
if (this.label)
|
if (this.label)
|
||||||
this.label.destroy();
|
this.label.destroy();
|
||||||
|
|
||||||
this.actor.destroy();
|
this.parent();
|
||||||
},
|
},
|
||||||
|
|
||||||
animateOutAndDestroy: function() {
|
animateOutAndDestroy: function() {
|
||||||
@ -191,19 +187,18 @@ const DashItemContainer = new Lang.Class({
|
|||||||
this.label.destroy();
|
this.label.destroy();
|
||||||
|
|
||||||
if (this.child == null) {
|
if (this.child == null) {
|
||||||
this.actor.destroy();
|
this.destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.animatingOut = true;
|
this.animatingOut = true;
|
||||||
this.childScale = 1.0;
|
|
||||||
Tweener.addTween(this,
|
Tweener.addTween(this,
|
||||||
{ childScale: 0.0,
|
{ childScale: 0.0,
|
||||||
childOpacity: 0,
|
childOpacity: 0,
|
||||||
time: DASH_ANIMATION_TIME,
|
time: DASH_ANIMATION_TIME,
|
||||||
transition: 'easeOutQuad',
|
transition: 'easeOutQuad',
|
||||||
onComplete: Lang.bind(this, function() {
|
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,
|
this.child.set_scale_with_gravity(scale, scale,
|
||||||
Clutter.Gravity.CENTER);
|
Clutter.Gravity.CENTER);
|
||||||
this.actor.queue_relayout();
|
this.queue_relayout();
|
||||||
},
|
},
|
||||||
|
|
||||||
get childScale() {
|
get childScale() {
|
||||||
@ -230,7 +225,7 @@ const DashItemContainer = new Lang.Class({
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
this.child.set_opacity(opacity);
|
this.child.set_opacity(opacity);
|
||||||
this.actor.queue_redraw();
|
this.queue_redraw();
|
||||||
},
|
},
|
||||||
|
|
||||||
get childOpacity() {
|
get childOpacity() {
|
||||||
@ -259,7 +254,6 @@ const ShowAppsIcon = new Lang.Class({
|
|||||||
|
|
||||||
this.setChild(this.toggleButton);
|
this.setChild(this.toggleButton);
|
||||||
this.setDragApp(null);
|
this.setDragApp(null);
|
||||||
this.toggleButton.label_actor = this.label;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_createIcon: function(size) {
|
_createIcon: function(size) {
|
||||||
@ -362,6 +356,23 @@ const DashActor = new Lang.Class({
|
|||||||
childBox.y1 = contentBox.y2 - showAppsNatHeight;
|
childBox.y1 = contentBox.y2 - showAppsNatHeight;
|
||||||
childBox.y2 = contentBox.y2;
|
childBox.y2 = contentBox.y2;
|
||||||
showAppsButton.allocate(childBox, flags);
|
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];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -387,12 +398,14 @@ const Dash = new Lang.Class({
|
|||||||
this._container.add_actor(this._box);
|
this._container.add_actor(this._box);
|
||||||
|
|
||||||
this._showAppsIcon = new ShowAppsIcon();
|
this._showAppsIcon = new ShowAppsIcon();
|
||||||
|
this._showAppsIcon.childScale = 1;
|
||||||
|
this._showAppsIcon.childOpacity = 255;
|
||||||
this._showAppsIcon.icon.setIconSize(this.iconSize);
|
this._showAppsIcon.icon.setIconSize(this.iconSize);
|
||||||
this._hookUpLabel(this._showAppsIcon);
|
this._hookUpLabel(this._showAppsIcon);
|
||||||
|
|
||||||
this.showAppsButton = this._showAppsIcon.toggleButton;
|
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 = new St.Bin({ child: this._container });
|
||||||
this.actor.connect('notify::height', Lang.bind(this,
|
this.actor.connect('notify::height', Lang.bind(this,
|
||||||
@ -416,12 +429,10 @@ const Dash = new Lang.Class({
|
|||||||
Lang.bind(this, this._onDragEnd));
|
Lang.bind(this, this._onDragEnd));
|
||||||
Main.overview.connect('item-drag-cancelled',
|
Main.overview.connect('item-drag-cancelled',
|
||||||
Lang.bind(this, this._onDragCancelled));
|
Lang.bind(this, this._onDragCancelled));
|
||||||
Main.overview.connect('window-drag-begin',
|
|
||||||
Lang.bind(this, this._onDragBegin));
|
// Translators: this is the name of the dock/favorites area on
|
||||||
Main.overview.connect('window-drag-cancelled',
|
// the left of the overview
|
||||||
Lang.bind(this, this._onDragCancelled));
|
Main.ctrlAltTabManager.addGroup(this.actor, _("Dash"), 'user-bookmarks-symbolic');
|
||||||
Main.overview.connect('window-drag-end',
|
|
||||||
Lang.bind(this, this._onDragEnd));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_onDragBegin: function() {
|
_onDragBegin: function() {
|
||||||
@ -456,7 +467,7 @@ const Dash = new Lang.Class({
|
|||||||
return DND.DragMotionResult.CONTINUE;
|
return DND.DragMotionResult.CONTINUE;
|
||||||
|
|
||||||
let showAppsHovered =
|
let showAppsHovered =
|
||||||
this._showAppsIcon.actor.contains(dragEvent.targetActor);
|
this._showAppsIcon.contains(dragEvent.targetActor);
|
||||||
|
|
||||||
if (!this._box.contains(dragEvent.targetActor) || showAppsHovered)
|
if (!this._box.contains(dragEvent.targetActor) || showAppsHovered)
|
||||||
this._clearDragPlaceholder();
|
this._clearDragPlaceholder();
|
||||||
@ -492,32 +503,51 @@ const Dash = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_createAppItem: function(app) {
|
_createAppItem: function(app) {
|
||||||
let display = new AppDisplay.AppWellIcon(app,
|
let appIcon = new AppDisplay.AppIcon(app,
|
||||||
{ setSizeManually: true,
|
{ setSizeManually: true,
|
||||||
showLabel: false });
|
showLabel: false });
|
||||||
display._draggable.connect('drag-begin',
|
appIcon._draggable.connect('drag-begin',
|
||||||
Lang.bind(this, function() {
|
Lang.bind(this, function() {
|
||||||
display.actor.opacity = 50;
|
appIcon.actor.opacity = 50;
|
||||||
}));
|
}));
|
||||||
display._draggable.connect('drag-end',
|
appIcon._draggable.connect('drag-end',
|
||||||
Lang.bind(this, function() {
|
Lang.bind(this, function() {
|
||||||
display.actor.opacity = 255;
|
appIcon.actor.opacity = 255;
|
||||||
}));
|
}));
|
||||||
|
appIcon.connect('menu-state-changed',
|
||||||
|
Lang.bind(this, function(appIcon, opened) {
|
||||||
|
this._itemMenuStateChanged(item, opened);
|
||||||
|
}));
|
||||||
|
|
||||||
let item = new DashItemContainer();
|
let item = new DashItemContainer();
|
||||||
item.setChild(display.actor);
|
item.setChild(appIcon.actor);
|
||||||
|
|
||||||
|
// Override default AppIcon label_actor, now the
|
||||||
|
// accessible_name is set at DashItemContainer.setLabelText
|
||||||
|
appIcon.actor.label_actor = null;
|
||||||
item.setLabelText(app.get_name());
|
item.setLabelText(app.get_name());
|
||||||
// Override default AppWellIcon label_actor
|
|
||||||
display.actor.label_actor = item.label;
|
appIcon.icon.setIconSize(this.iconSize);
|
||||||
display.icon.setIconSize(this.iconSize);
|
|
||||||
this._hookUpLabel(item);
|
this._hookUpLabel(item);
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_itemMenuStateChanged: function(item, opened) {
|
||||||
|
// When the menu closes, it calls sync_hover, which means
|
||||||
|
// that the notify::hover handler does everything we need to.
|
||||||
|
if (opened) {
|
||||||
|
if (this._showLabelTimeoutId > 0) {
|
||||||
|
Mainloop.source_remove(this._showLabelTimeoutId);
|
||||||
|
this._showLabelTimeoutId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.hideLabel();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
_onHover: function (item) {
|
_onHover: function (item) {
|
||||||
if (item.child.get_hover() && !item.child._delegate.isMenuUp) {
|
if (item.child.get_hover()) {
|
||||||
if (this._showLabelTimeoutId == 0) {
|
if (this._showLabelTimeoutId == 0) {
|
||||||
let timeout = this._labelShowing ? 0 : DASH_ITEM_HOVER_TIMEOUT;
|
let timeout = this._labelShowing ? 0 : DASH_ITEM_HOVER_TIMEOUT;
|
||||||
this._showLabelTimeoutId = Mainloop.timeout_add(timeout,
|
this._showLabelTimeoutId = Mainloop.timeout_add(timeout,
|
||||||
@ -552,13 +582,13 @@ const Dash = new Lang.Class({
|
|||||||
// animating out (which means they will be destroyed at the end of
|
// animating out (which means they will be destroyed at the end of
|
||||||
// the animation)
|
// the animation)
|
||||||
let iconChildren = this._box.get_children().filter(function(actor) {
|
let iconChildren = this._box.get_children().filter(function(actor) {
|
||||||
return actor._delegate.child &&
|
return actor.child &&
|
||||||
actor._delegate.child._delegate &&
|
actor.child._delegate &&
|
||||||
actor._delegate.child._delegate.icon &&
|
actor.child._delegate.icon &&
|
||||||
!actor._delegate.animatingOut;
|
!actor.animatingOut;
|
||||||
});
|
});
|
||||||
|
|
||||||
iconChildren.push(this._showAppsIcon.actor);
|
iconChildren.push(this._showAppsIcon);
|
||||||
|
|
||||||
if (this._maxHeight == -1)
|
if (this._maxHeight == -1)
|
||||||
return;
|
return;
|
||||||
@ -571,23 +601,18 @@ const Dash = new Lang.Class({
|
|||||||
let availHeight = maxContent.y2 - maxContent.y1;
|
let availHeight = maxContent.y2 - maxContent.y1;
|
||||||
let spacing = themeNode.get_length('spacing');
|
let spacing = themeNode.get_length('spacing');
|
||||||
|
|
||||||
|
let firstButton = iconChildren[0].child;
|
||||||
let firstIcon = iconChildren[0]._delegate.child._delegate.icon;
|
let firstIcon = firstButton._delegate.icon;
|
||||||
|
|
||||||
let minHeight, natHeight;
|
let minHeight, natHeight;
|
||||||
|
|
||||||
// Enforce the current icon size during the size request if
|
// Enforce the current icon size during the size request
|
||||||
// the icon is animating
|
let [currentWidth, currentHeight] = firstIcon.icon.get_size();
|
||||||
if (firstIcon._animating) {
|
|
||||||
let [currentWidth, currentHeight] = firstIcon.icon.get_size();
|
|
||||||
|
|
||||||
firstIcon.icon.set_size(this.iconSize, this.iconSize);
|
firstIcon.icon.set_size(this.iconSize, this.iconSize);
|
||||||
[minHeight, natHeight] = iconChildren[0].get_preferred_height(-1);
|
[minHeight, natHeight] = firstButton.get_preferred_height(-1);
|
||||||
|
|
||||||
firstIcon.icon.set_size(currentWidth, currentHeight);
|
firstIcon.icon.set_size(currentWidth, currentHeight);
|
||||||
} else {
|
|
||||||
[minHeight, natHeight] = iconChildren[0].get_preferred_height(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subtract icon padding and box spacing from the available height
|
// Subtract icon padding and box spacing from the available height
|
||||||
availHeight -= iconChildren.length * (natHeight - this.iconSize) +
|
availHeight -= iconChildren.length * (natHeight - this.iconSize) +
|
||||||
@ -612,15 +637,17 @@ const Dash = new Lang.Class({
|
|||||||
|
|
||||||
let scale = oldIconSize / newIconSize;
|
let scale = oldIconSize / newIconSize;
|
||||||
for (let i = 0; i < iconChildren.length; i++) {
|
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
|
// Set the new size immediately, to keep the icons' sizes
|
||||||
// in sync with this.iconSize
|
// in sync with this.iconSize
|
||||||
icon.setIconSize(this.iconSize);
|
icon.setIconSize(this.iconSize);
|
||||||
|
|
||||||
// Don't animate the icon size change when the overview
|
// Don't animate the icon size change when the overview
|
||||||
// is not visible or when initially filling the dash
|
// is transitioning, not visible or when initially filling
|
||||||
if (!Main.overview.visible || !this._shownInitially)
|
// the dash
|
||||||
|
if (!Main.overview.visible || Main.overview.animationInProgress ||
|
||||||
|
!this._shownInitially)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
let [targetWidth, targetHeight] = icon.icon.get_size();
|
let [targetWidth, targetHeight] = icon.icon.get_size();
|
||||||
@ -630,15 +657,11 @@ const Dash = new Lang.Class({
|
|||||||
icon.icon.set_size(icon.icon.width * scale,
|
icon.icon.set_size(icon.icon.width * scale,
|
||||||
icon.icon.height * scale);
|
icon.icon.height * scale);
|
||||||
|
|
||||||
icon._animating = true;
|
|
||||||
Tweener.addTween(icon.icon,
|
Tweener.addTween(icon.icon,
|
||||||
{ width: targetWidth,
|
{ width: targetWidth,
|
||||||
height: targetHeight,
|
height: targetHeight,
|
||||||
time: DASH_ANIMATION_TIME,
|
time: DASH_ANIMATION_TIME,
|
||||||
transition: 'easeOutQuad',
|
transition: 'easeOutQuad',
|
||||||
onComplete: function() {
|
|
||||||
icon._animating = false;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -649,13 +672,13 @@ const Dash = new Lang.Class({
|
|||||||
let running = this._appSystem.get_running();
|
let running = this._appSystem.get_running();
|
||||||
|
|
||||||
let children = this._box.get_children().filter(function(actor) {
|
let children = this._box.get_children().filter(function(actor) {
|
||||||
return actor._delegate.child &&
|
return actor.child &&
|
||||||
actor._delegate.child._delegate &&
|
actor.child._delegate &&
|
||||||
actor._delegate.child._delegate.app;
|
actor.child._delegate.app;
|
||||||
});
|
});
|
||||||
// Apps currently in the dash
|
// Apps currently in the dash
|
||||||
let oldApps = children.map(function(actor) {
|
let oldApps = children.map(function(actor) {
|
||||||
return actor._delegate.child._delegate.app;
|
return actor.child._delegate.app;
|
||||||
});
|
});
|
||||||
// Apps supposed to be in the dash
|
// Apps supposed to be in the dash
|
||||||
let newApps = [];
|
let newApps = [];
|
||||||
@ -721,7 +744,7 @@ const Dash = new Lang.Class({
|
|||||||
let insertHere = newApps[newIndex + 1] &&
|
let insertHere = newApps[newIndex + 1] &&
|
||||||
newApps[newIndex + 1] == oldApps[oldIndex];
|
newApps[newIndex + 1] == oldApps[oldIndex];
|
||||||
let alreadyRemoved = removedActors.reduce(function(result, actor) {
|
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];
|
return result || removedApp == newApps[newIndex];
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
@ -738,14 +761,15 @@ const Dash = new Lang.Class({
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < addedItems.length; i++)
|
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);
|
addedItems[i].pos);
|
||||||
|
|
||||||
for (let i = 0; i < removedActors.length; i++) {
|
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 hidden
|
// Don't animate item removal when the overview is transitioning
|
||||||
if (Main.overview.visible)
|
// or hidden
|
||||||
|
if (Main.overview.visible && !Main.overview.animationInProgress)
|
||||||
item.animateOutAndDestroy();
|
item.animateOutAndDestroy();
|
||||||
else
|
else
|
||||||
item.destroy();
|
item.destroy();
|
||||||
@ -755,17 +779,20 @@ const Dash = new Lang.Class({
|
|||||||
|
|
||||||
// Skip animations on first run when adding the initial set
|
// Skip animations on first run when adding the initial set
|
||||||
// of items, to avoid all items zooming in at once
|
// 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;
|
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 hidden
|
// Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744
|
||||||
if (!Main.overview.visible)
|
// Without it, StBoxLayout may use a stale size cache
|
||||||
return;
|
this._box.queue_relayout();
|
||||||
|
|
||||||
for (let i = 0; i < addedItems.length; i++)
|
|
||||||
addedItems[i].item.animateIn();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_clearDragPlaceholder: function() {
|
_clearDragPlaceholder: function() {
|
||||||
@ -796,7 +823,7 @@ const Dash = new Lang.Class({
|
|||||||
// the remove target has the same size as "normal" items, we don't
|
// the remove target has the same size as "normal" items, we don't
|
||||||
// need to do the same adjustment there.
|
// need to do the same adjustment there.
|
||||||
if (this._dragPlaceholder) {
|
if (this._dragPlaceholder) {
|
||||||
boxHeight -= this._dragPlaceholder.actor.height;
|
boxHeight -= this._dragPlaceholder.height;
|
||||||
numChildren--;
|
numChildren--;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -810,7 +837,7 @@ const Dash = new Lang.Class({
|
|||||||
if (this._dragPlaceholder) {
|
if (this._dragPlaceholder) {
|
||||||
this._dragPlaceholder.animateOutAndDestroy();
|
this._dragPlaceholder.animateOutAndDestroy();
|
||||||
this._animatingPlaceholdersCount++;
|
this._animatingPlaceholdersCount++;
|
||||||
this._dragPlaceholder.actor.connect('destroy',
|
this._dragPlaceholder.connect('destroy',
|
||||||
Lang.bind(this, function() {
|
Lang.bind(this, function() {
|
||||||
this._animatingPlaceholdersCount--;
|
this._animatingPlaceholdersCount--;
|
||||||
}));
|
}));
|
||||||
@ -825,7 +852,7 @@ const Dash = new Lang.Class({
|
|||||||
// an animation
|
// an animation
|
||||||
let fadeIn;
|
let fadeIn;
|
||||||
if (this._dragPlaceholder) {
|
if (this._dragPlaceholder) {
|
||||||
this._dragPlaceholder.actor.destroy();
|
this._dragPlaceholder.destroy();
|
||||||
fadeIn = false;
|
fadeIn = false;
|
||||||
} else {
|
} else {
|
||||||
fadeIn = true;
|
fadeIn = true;
|
||||||
@ -834,10 +861,9 @@ const Dash = new Lang.Class({
|
|||||||
this._dragPlaceholder = new DragPlaceholderItem();
|
this._dragPlaceholder = new DragPlaceholderItem();
|
||||||
this._dragPlaceholder.child.set_width (this.iconSize);
|
this._dragPlaceholder.child.set_width (this.iconSize);
|
||||||
this._dragPlaceholder.child.set_height (this.iconSize / 2);
|
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);
|
this._dragPlaceholderPos);
|
||||||
if (fadeIn)
|
this._dragPlaceholder.show(fadeIn);
|
||||||
this._dragPlaceholder.animateIn();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the drag placeholder if we are not in the
|
// Remove the drag placeholder if we are not in the
|
||||||
@ -875,10 +901,10 @@ const Dash = new Lang.Class({
|
|||||||
let children = this._box.get_children();
|
let children = this._box.get_children();
|
||||||
for (let i = 0; i < this._dragPlaceholderPos; i++) {
|
for (let i = 0; i < this._dragPlaceholderPos; i++) {
|
||||||
if (this._dragPlaceholder &&
|
if (this._dragPlaceholder &&
|
||||||
children[i] == this._dragPlaceholder.actor)
|
children[i] == this._dragPlaceholder)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
let childId = children[i]._delegate.child._delegate.app.get_id();
|
let childId = children[i].child._delegate.app.get_id();
|
||||||
if (childId == id)
|
if (childId == id)
|
||||||
continue;
|
continue;
|
||||||
if (childId in favorites)
|
if (childId in favorites)
|
||||||
|
@ -32,6 +32,7 @@ function _onVertSepRepaint (area)
|
|||||||
cr.setDash([1, 3], 1); // Hard-code for now
|
cr.setDash([1, 3], 1); // Hard-code for now
|
||||||
cr.setLineWidth(stippleWidth);
|
cr.setLineWidth(stippleWidth);
|
||||||
cr.stroke();
|
cr.stroke();
|
||||||
|
cr.$dispose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const DateMenuButton = new Lang.Class({
|
const DateMenuButton = new Lang.Class({
|
||||||
@ -83,12 +84,26 @@ const DateMenuButton = new Lang.Class({
|
|||||||
}));
|
}));
|
||||||
vbox.add(this._calendar.actor);
|
vbox.add(this._calendar.actor);
|
||||||
|
|
||||||
item = this.menu.addSettingsAction(_("Date and Time Settings"), 'gnome-datetime-panel.desktop');
|
let separator = new PopupMenu.PopupSeparatorMenuItem();
|
||||||
if (item) {
|
separator.setColumnWidths(1);
|
||||||
let separator = new PopupMenu.PopupSeparatorMenuItem();
|
vbox.add(separator.actor, {y_align: St.Align.END, expand: true, y_fill: false});
|
||||||
separator.setColumnWidths(1);
|
|
||||||
vbox.add(separator.actor, {y_align: St.Align.END, expand: true, y_fill: false});
|
|
||||||
|
|
||||||
|
this._openCalendarItem = new PopupMenu.PopupMenuItem(_("Open Calendar"));
|
||||||
|
this._openCalendarItem.connect('activate', Lang.bind(this, this._onOpenCalendarActivate));
|
||||||
|
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.show_on_set_parent = false;
|
||||||
item.actor.can_focus = false;
|
item.actor.can_focus = false;
|
||||||
item.actor.reparent(vbox);
|
item.actor.reparent(vbox);
|
||||||
@ -108,16 +123,6 @@ const DateMenuButton = new Lang.Class({
|
|||||||
// Event list
|
// Event list
|
||||||
vbox.add(this._eventList.actor, { expand: true });
|
vbox.add(this._eventList.actor, { expand: true });
|
||||||
|
|
||||||
this._openCalendarItem = new PopupMenu.PopupMenuItem(_("Open Calendar"));
|
|
||||||
this._openCalendarItem.connect('activate', Lang.bind(this, this._onOpenCalendarActivate));
|
|
||||||
this._openCalendarItem.actor.can_focus = false;
|
|
||||||
vbox.add(this._openCalendarItem.actor, {y_align: St.Align.END, expand: true, y_fill: false});
|
|
||||||
|
|
||||||
this._calendarSettings = new Gio.Settings({ schema: 'org.gnome.desktop.default-applications.office.calendar' });
|
|
||||||
this._calendarSettings.connect('changed::exec',
|
|
||||||
Lang.bind(this, this._calendarSettingsChanged));
|
|
||||||
this._calendarSettingsChanged();
|
|
||||||
|
|
||||||
// Whenever the menu is opened, select today
|
// Whenever the menu is opened, select today
|
||||||
this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) {
|
this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
@ -151,14 +156,15 @@ const DateMenuButton = new Lang.Class({
|
|||||||
this._sessionUpdated();
|
this._sessionUpdated();
|
||||||
},
|
},
|
||||||
|
|
||||||
_calendarSettingsChanged: function() {
|
_appInstalledChanged: function() {
|
||||||
let exec = this._calendarSettings.get_string('exec');
|
let app = Shell.AppSystem.get_default().lookup_app('gnome-clocks.desktop');
|
||||||
let fullExec = GLib.find_program_in_path(exec);
|
this._openClocksItem.actor.visible = app !== null;
|
||||||
this._openCalendarItem.actor.visible = fullExec != null;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_setEventsVisibility: function(visible) {
|
_updateEventsVisibility: function() {
|
||||||
|
let visible = this._eventSource.hasCalendars;
|
||||||
this._openCalendarItem.actor.visible = visible;
|
this._openCalendarItem.actor.visible = visible;
|
||||||
|
this._openClocksItem.actor.visible = visible;
|
||||||
this._separator.visible = visible;
|
this._separator.visible = visible;
|
||||||
if (visible) {
|
if (visible) {
|
||||||
let alignment = 0.25;
|
let alignment = 0.25;
|
||||||
@ -173,8 +179,16 @@ const DateMenuButton = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_setEventSource: function(eventSource) {
|
_setEventSource: function(eventSource) {
|
||||||
|
if (this._eventSource)
|
||||||
|
this._eventSource.destroy();
|
||||||
|
|
||||||
this._calendar.setEventSource(eventSource);
|
this._calendar.setEventSource(eventSource);
|
||||||
this._eventList.setEventSource(eventSource);
|
this._eventList.setEventSource(eventSource);
|
||||||
|
|
||||||
|
this._eventSource = eventSource;
|
||||||
|
this._eventSource.connect('notify::has-calendars', Lang.bind(this, function() {
|
||||||
|
this._updateEventsVisibility();
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
_sessionUpdated: function() {
|
_sessionUpdated: function() {
|
||||||
@ -183,10 +197,10 @@ const DateMenuButton = new Lang.Class({
|
|||||||
if (showEvents) {
|
if (showEvents) {
|
||||||
eventSource = new Calendar.DBusEventSource();
|
eventSource = new Calendar.DBusEventSource();
|
||||||
} else {
|
} else {
|
||||||
eventSource = null;
|
eventSource = new Calendar.EmptyEventSource();
|
||||||
}
|
}
|
||||||
this._setEventSource(eventSource);
|
this._setEventSource(eventSource);
|
||||||
this._setEventsVisibility(showEvents);
|
this._updateEventsVisibility();
|
||||||
|
|
||||||
// This needs to be handled manually, as the code to
|
// This needs to be handled manually, as the code to
|
||||||
// autohide separators doesn't work across the vbox
|
// autohide separators doesn't work across the vbox
|
||||||
@ -205,24 +219,16 @@ const DateMenuButton = new Lang.Class({
|
|||||||
|
|
||||||
_onOpenCalendarActivate: function() {
|
_onOpenCalendarActivate: function() {
|
||||||
this.menu.close();
|
this.menu.close();
|
||||||
let tool = this._calendarSettings.get_string('exec');
|
|
||||||
if (tool.length == 0 || tool.substr(0, 9) == 'evolution') {
|
let app = Gio.AppInfo.get_default_for_type('text/calendar', false);
|
||||||
// TODO: pass the selected day
|
if (app.get_id() == 'evolution')
|
||||||
let app = Shell.AppSystem.get_default().lookup_app('evolution-calendar.desktop');
|
app = Gio.DesktopAppInfo.new('evolution-calendar');
|
||||||
app.activate();
|
app.launch([], global.create_app_launch_context());
|
||||||
} else {
|
},
|
||||||
let needTerm = this._calendarSettings.get_boolean('needs-term');
|
|
||||||
if (needTerm) {
|
_onOpenClocksActivate: function() {
|
||||||
let terminalSettings = new Gio.Settings({ schema: 'org.gnome.desktop.default-applications.terminal' });
|
this.menu.close();
|
||||||
let term = terminalSettings.get_string('exec');
|
let app = Shell.AppSystem.get_default().lookup_app('gnome-clocks.desktop');
|
||||||
let arg = terminalSettings.get_string('exec-arg');
|
app.activate();
|
||||||
if (arg != '')
|
|
||||||
Util.spawn([term, arg, tool]);
|
|
||||||
else
|
|
||||||
Util.spawn([term, tool]);
|
|
||||||
} else {
|
|
||||||
Util.spawnCommandLine(tool)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
30
js/ui/dnd.js
30
js/ui/dnd.js
@ -85,11 +85,13 @@ const _Draggable = new Lang.Class({
|
|||||||
|
|
||||||
this.actor.connect('destroy', Lang.bind(this, function() {
|
this.actor.connect('destroy', Lang.bind(this, function() {
|
||||||
this._actorDestroyed = true;
|
this._actorDestroyed = true;
|
||||||
|
|
||||||
// If the drag actor is destroyed and we were going to fix
|
// If the drag actor is destroyed and we were going to fix
|
||||||
// up its hover state, fix up the parent hover state instead
|
// up its hover state, fix up the parent hover state instead
|
||||||
if (this.actor == this._firstLeaveActor)
|
if (this.actor == this._firstLeaveActor)
|
||||||
this._firstLeaveActor = this._dragOrigParent;
|
this._firstLeaveActor = this._dragOrigParent;
|
||||||
if (this._dragInProgress)
|
|
||||||
|
if (this._dragInProgress && this._dragCancellable)
|
||||||
this._cancelDrag(global.get_current_time());
|
this._cancelDrag(global.get_current_time());
|
||||||
this.disconnectAll();
|
this.disconnectAll();
|
||||||
}));
|
}));
|
||||||
@ -102,6 +104,7 @@ const _Draggable = new Lang.Class({
|
|||||||
this._buttonDown = false; // The mouse button has been pressed and has not yet been released.
|
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._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._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.
|
// 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
|
// But we remember the actors that we first left/last entered so we can
|
||||||
@ -136,9 +139,10 @@ const _Draggable = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_ungrabActor: function() {
|
_ungrabActor: function() {
|
||||||
Clutter.ungrab_pointer();
|
|
||||||
if (!this._onEventId)
|
if (!this._onEventId)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
Clutter.ungrab_pointer();
|
||||||
this.actor.disconnect(this._onEventId);
|
this.actor.disconnect(this._onEventId);
|
||||||
this._onEventId = null;
|
this._onEventId = null;
|
||||||
},
|
},
|
||||||
@ -203,6 +207,19 @@ const _Draggable = new Lang.Class({
|
|||||||
return false;
|
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:
|
* startDrag:
|
||||||
* @stageX: X coordinate of event
|
* @stageX: X coordinate of event
|
||||||
@ -234,7 +251,7 @@ const _Draggable = new Lang.Class({
|
|||||||
this._dragY = this._dragStartY = stageY;
|
this._dragY = this._dragStartY = stageY;
|
||||||
|
|
||||||
if (this.actor._delegate && this.actor._delegate.getDragActor) {
|
if (this.actor._delegate && this.actor._delegate.getDragActor) {
|
||||||
this._dragActor = this.actor._delegate.getDragActor(this._dragStartX, this._dragStartY);
|
this._dragActor = this.actor._delegate.getDragActor();
|
||||||
this._dragActor.reparent(Main.uiGroup);
|
this._dragActor.reparent(Main.uiGroup);
|
||||||
this._dragActor.raise_top();
|
this._dragActor.raise_top();
|
||||||
Shell.util_set_hidden_from_pick(this._dragActor, true);
|
Shell.util_set_hidden_from_pick(this._dragActor, true);
|
||||||
@ -425,6 +442,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) {
|
while (target) {
|
||||||
if (target._delegate && target._delegate.acceptDrop) {
|
if (target._delegate && target._delegate.acceptDrop) {
|
||||||
let [r, targX, targY] = target.transform_stage_point(dropX, dropY);
|
let [r, targX, targY] = target.transform_stage_point(dropX, dropY);
|
||||||
@ -433,8 +455,6 @@ const _Draggable = new Lang.Class({
|
|||||||
targX,
|
targX,
|
||||||
targY,
|
targY,
|
||||||
event.get_time())) {
|
event.get_time())) {
|
||||||
if (this._actorDestroyed)
|
|
||||||
return true;
|
|
||||||
// If it accepted the drop without taking the actor,
|
// If it accepted the drop without taking the actor,
|
||||||
// handle it ourselves.
|
// handle it ourselves.
|
||||||
if (this._dragActor.get_parent() == Main.uiGroup) {
|
if (this._dragActor.get_parent() == Main.uiGroup) {
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
|
const Mainloop = imports.mainloop;
|
||||||
const Signals = imports.signals;
|
const Signals = imports.signals;
|
||||||
|
|
||||||
const AccountsService = imports.gi.AccountsService;
|
const AccountsService = imports.gi.AccountsService;
|
||||||
@ -50,6 +51,7 @@ const EndSessionDialogIface = <interface name="org.gnome.SessionManager.EndSessi
|
|||||||
<arg type="u" direction="in" />
|
<arg type="u" direction="in" />
|
||||||
<arg type="ao" direction="in" />
|
<arg type="ao" direction="in" />
|
||||||
</method>
|
</method>
|
||||||
|
<method name="Close" />
|
||||||
<signal name="ConfirmedLogout" />
|
<signal name="ConfirmedLogout" />
|
||||||
<signal name="ConfirmedReboot" />
|
<signal name="ConfirmedReboot" />
|
||||||
<signal name="ConfirmedShutdown" />
|
<signal name="ConfirmedShutdown" />
|
||||||
@ -105,7 +107,7 @@ const restartDialogContent = {
|
|||||||
endDescription: _("Restarting the system."),
|
endDescription: _("Restarting the system."),
|
||||||
confirmButtons: [{ signal: 'ConfirmedReboot',
|
confirmButtons: [{ signal: 'ConfirmedReboot',
|
||||||
label: C_("button", "Restart") }],
|
label: C_("button", "Restart") }],
|
||||||
iconName: 'system-shutdown-symbolic',
|
iconName: 'view-refresh-symbolic',
|
||||||
iconStyleClass: 'end-session-dialog-shutdown-icon'
|
iconStyleClass: 'end-session-dialog-shutdown-icon'
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -377,7 +379,12 @@ const EndSessionDialog = new Lang.Class({
|
|||||||
let signal = dialogContent.confirmButtons[i].signal;
|
let signal = dialogContent.confirmButtons[i].signal;
|
||||||
let label = dialogContent.confirmButtons[i].label;
|
let label = dialogContent.confirmButtons[i].label;
|
||||||
buttons.push({ action: Lang.bind(this, function() {
|
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 });
|
label: label });
|
||||||
}
|
}
|
||||||
@ -385,15 +392,17 @@ const EndSessionDialog = new Lang.Class({
|
|||||||
this.setButtons(buttons);
|
this.setButtons(buttons);
|
||||||
},
|
},
|
||||||
|
|
||||||
close: function() {
|
close: function(skipSignal) {
|
||||||
this.parent();
|
this.parent();
|
||||||
this._dbusImpl.emit_signal('Closed', null);
|
|
||||||
|
if (!skipSignal)
|
||||||
|
this._dbusImpl.emit_signal('Closed', null);
|
||||||
},
|
},
|
||||||
|
|
||||||
cancel: function() {
|
cancel: function() {
|
||||||
this._stopTimer();
|
this._stopTimer();
|
||||||
this._dbusImpl.emit_signal('Canceled', null);
|
this._dbusImpl.emit_signal('Canceled', null);
|
||||||
this.close(global.get_current_time());
|
this.close();
|
||||||
},
|
},
|
||||||
|
|
||||||
_confirm: function(signal) {
|
_confirm: function(signal) {
|
||||||
@ -408,22 +417,34 @@ const EndSessionDialog = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_startTimer: function() {
|
_startTimer: function() {
|
||||||
|
let startTime = GLib.get_monotonic_time();
|
||||||
this._secondsLeft = this._totalSecondsToStayOpen;
|
this._secondsLeft = this._totalSecondsToStayOpen;
|
||||||
Tweener.addTween(this,
|
|
||||||
{ _secondsLeft: 0,
|
this._timerId = Mainloop.timeout_add_seconds(1, Lang.bind(this,
|
||||||
time: this._secondsLeft,
|
function() {
|
||||||
transition: 'linear',
|
let currentTime = GLib.get_monotonic_time();
|
||||||
onUpdate: Lang.bind(this, this._updateDescription),
|
let secondsElapsed = ((currentTime - startTime) / 1000000);
|
||||||
onComplete: Lang.bind(this, function() {
|
|
||||||
let dialogContent = DialogContent[this._type];
|
this._secondsLeft = this._totalSecondsToStayOpen - secondsElapsed;
|
||||||
let button = dialogContent.confirmButtons[dialogContent.confirmButtons.length - 1];
|
if (this._secondsLeft > 0) {
|
||||||
this._confirm(button.signal);
|
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() {
|
_stopTimer: function() {
|
||||||
Tweener.removeTweens(this);
|
if (this._timerId != 0) {
|
||||||
|
Mainloop.source_remove(this._timerId);
|
||||||
|
this._timerId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
this._secondsLeft = 0;
|
this._secondsLeft = 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -440,7 +461,7 @@ const EndSessionDialog = new Lang.Class({
|
|||||||
let item = new ListItem(app, reason);
|
let item = new ListItem(app, reason);
|
||||||
item.connect('activate',
|
item.connect('activate',
|
||||||
Lang.bind(this, function() {
|
Lang.bind(this, function() {
|
||||||
this.close(global.get_current_time());
|
this.close();
|
||||||
}));
|
}));
|
||||||
this._applicationList.add(item.actor, { x_fill: true });
|
this._applicationList.add(item.actor, { x_fill: true });
|
||||||
this._stopTimer();
|
this._stopTimer();
|
||||||
@ -488,5 +509,9 @@ const EndSessionDialog = new Lang.Class({
|
|||||||
invocation.return_value(null);
|
invocation.return_value(null);
|
||||||
this.disconnect(signalId);
|
this.disconnect(signalId);
|
||||||
}));
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
Close: function(parameters, invocation) {
|
||||||
|
this.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -215,7 +215,7 @@ const InstallExtensionDialog = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_onCancelButtonPressed: function(button, event) {
|
_onCancelButtonPressed: function(button, event) {
|
||||||
this.close(global.get_current_time());
|
this.close();
|
||||||
this._invocation.return_value(GLib.Variant.new('(s)', ['cancelled']));
|
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);
|
gotExtensionZipFile(session, message, uuid, dir, callback, errback);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.close(global.get_current_time());
|
this.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -106,11 +106,15 @@ function enableExtension(uuid) {
|
|||||||
|
|
||||||
extensionOrder.push(uuid);
|
extensionOrder.push(uuid);
|
||||||
|
|
||||||
let stylesheetFile = extension.dir.get_child('stylesheet.css');
|
let stylesheetNames = [global.session_mode + '.css', 'stylesheet.css'];
|
||||||
if (stylesheetFile.query_exists(null)) {
|
for (let i = 0; i < stylesheetNames.length; i++) {
|
||||||
let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
|
let stylesheetFile = extension.dir.get_child(stylesheetNames[i]);
|
||||||
theme.load_stylesheet(stylesheetFile.get_path());
|
if (stylesheetFile.query_exists(null)) {
|
||||||
extension.stylesheet = stylesheetFile;
|
let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
|
||||||
|
theme.load_stylesheet(stylesheetFile.get_path());
|
||||||
|
extension.stylesheet = stylesheetFile;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension.stateObj.enable();
|
extension.stateObj.enable();
|
||||||
@ -126,13 +130,10 @@ function logExtensionError(uuid, error) {
|
|||||||
|
|
||||||
let message = '' + error;
|
let message = '' + error;
|
||||||
|
|
||||||
if (error.state)
|
extension.state = ExtensionState.ERROR;
|
||||||
extension.state = error.state;
|
|
||||||
else
|
|
||||||
extension.state = ExtensionState.ERROR;
|
|
||||||
|
|
||||||
if (!extension.errors)
|
if (!extension.errors)
|
||||||
extension.errors = [];
|
extension.errors = [];
|
||||||
|
extension.errors.push(message);
|
||||||
|
|
||||||
log('Extension "%s" had error: %s'.format(uuid, message));
|
log('Extension "%s" had error: %s'.format(uuid, message));
|
||||||
_signals.emit('extension-state-changed', { uuid: uuid,
|
_signals.emit('extension-state-changed', { uuid: uuid,
|
||||||
@ -145,18 +146,16 @@ function loadExtension(extension) {
|
|||||||
extension.state = ExtensionState.ERROR;
|
extension.state = ExtensionState.ERROR;
|
||||||
|
|
||||||
if (ExtensionUtils.isOutOfDate(extension)) {
|
if (ExtensionUtils.isOutOfDate(extension)) {
|
||||||
let error = new Error('extension is not compatible with current GNOME Shell and/or GJS version');
|
extension.state = ExtensionState.OUT_OF_DATE;
|
||||||
error.state = ExtensionState.OUT_OF_DATE;
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
let enabled = enabledExtensions.indexOf(extension.uuid) != -1;
|
|
||||||
if (enabled) {
|
|
||||||
initExtension(extension.uuid);
|
|
||||||
if (extension.state == ExtensionState.DISABLED)
|
|
||||||
enableExtension(extension.uuid);
|
|
||||||
} else {
|
} else {
|
||||||
extension.state = ExtensionState.INITIALIZED;
|
let enabled = enabledExtensions.indexOf(extension.uuid) != -1;
|
||||||
|
if (enabled) {
|
||||||
|
initExtension(extension.uuid);
|
||||||
|
if (extension.state == ExtensionState.DISABLED)
|
||||||
|
enableExtension(extension.uuid);
|
||||||
|
} else {
|
||||||
|
extension.state = ExtensionState.INITIALIZED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_signals.emit('extension-state-changed', extension);
|
_signals.emit('extension-state-changed', extension);
|
||||||
@ -217,8 +216,16 @@ function initExtension(uuid) {
|
|||||||
_signals.emit('extension-loaded', uuid);
|
_signals.emit('extension-loaded', uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getEnabledExtensions() {
|
||||||
|
let extensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
|
||||||
|
if (!Array.isArray(Main.sessionMode.enabledExtensions))
|
||||||
|
return extensions;
|
||||||
|
|
||||||
|
return Main.sessionMode.enabledExtensions.concat(extensions);
|
||||||
|
}
|
||||||
|
|
||||||
function onEnabledExtensionsChanged() {
|
function onEnabledExtensionsChanged() {
|
||||||
let newEnabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
|
let newEnabledExtensions = getEnabledExtensions();
|
||||||
|
|
||||||
if (!enabled)
|
if (!enabled)
|
||||||
return;
|
return;
|
||||||
@ -252,7 +259,7 @@ function onEnabledExtensionsChanged() {
|
|||||||
|
|
||||||
function _loadExtensions() {
|
function _loadExtensions() {
|
||||||
global.settings.connect('changed::' + ENABLED_EXTENSIONS_KEY, onEnabledExtensionsChanged);
|
global.settings.connect('changed::' + ENABLED_EXTENSIONS_KEY, onEnabledExtensionsChanged);
|
||||||
enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
|
enabledExtensions = getEnabledExtensions();
|
||||||
|
|
||||||
let finder = new ExtensionUtils.ExtensionFinder();
|
let finder = new ExtensionUtils.ExtensionFinder();
|
||||||
finder.connect('extension-found', function(signals, extension) {
|
finder.connect('extension-found', function(signals, extension) {
|
||||||
@ -294,10 +301,17 @@ function disableAllExtensions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _sessionUpdated() {
|
function _sessionUpdated() {
|
||||||
if (Main.sessionMode.allowExtensions)
|
// For now sessionMode.allowExtensions controls extensions from both the
|
||||||
|
// 'enabled-extensions' preference and the sessionMode.enabledExtensions
|
||||||
|
// property; it might make sense to make enabledExtensions independent
|
||||||
|
// from allowExtensions in the future
|
||||||
|
if (Main.sessionMode.allowExtensions) {
|
||||||
|
if (initted)
|
||||||
|
onEnabledExtensionsChanged();
|
||||||
enableAllExtensions();
|
enableAllExtensions();
|
||||||
else
|
} else {
|
||||||
disableAllExtensions();
|
disableAllExtensions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
||||||
|
|
||||||
const Lang = imports.lang;
|
|
||||||
|
|
||||||
const Lightbox = imports.ui.lightbox;
|
|
||||||
const Main = imports.ui.main;
|
|
||||||
const Tweener = imports.ui.tweener;
|
|
||||||
|
|
||||||
const FLASHSPOT_ANIMATION_TIME = 0.25; // seconds
|
|
||||||
|
|
||||||
const Flashspot = new Lang.Class({
|
|
||||||
Name: 'Flashspot',
|
|
||||||
Extends: Lightbox.Lightbox,
|
|
||||||
|
|
||||||
_init: function(area) {
|
|
||||||
this.parent(Main.uiGroup, { inhibitEvents: true,
|
|
||||||
width: area.width,
|
|
||||||
height: area.height });
|
|
||||||
|
|
||||||
this.actor.style_class = 'flashspot';
|
|
||||||
this.actor.set_position(area.x, area.y);
|
|
||||||
},
|
|
||||||
|
|
||||||
fire: function() {
|
|
||||||
this.actor.opacity = 0;
|
|
||||||
Tweener.addTween(this.actor,
|
|
||||||
{ opacity: 255,
|
|
||||||
time: FLASHSPOT_ANIMATION_TIME,
|
|
||||||
transition: 'linear',
|
|
||||||
onComplete: Lang.bind(this, this._onFireShowComplete)
|
|
||||||
});
|
|
||||||
this.actor.show();
|
|
||||||
},
|
|
||||||
|
|
||||||
_onFireShowComplete: function() {
|
|
||||||
Tweener.addTween(this.actor,
|
|
||||||
{ opacity: 0,
|
|
||||||
time: FLASHSPOT_ANIMATION_TIME,
|
|
||||||
transition: 'linear',
|
|
||||||
onComplete: Lang.bind(this, function() {
|
|
||||||
this.destroy();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
@ -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 Clutter = imports.gi.Clutter;
|
||||||
const Gtk = imports.gi.Gtk;
|
const Gtk = imports.gi.Gtk;
|
||||||
@ -10,19 +10,9 @@ const St = imports.gi.St;
|
|||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
const Params = imports.misc.params;
|
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:
|
// GrabHelper:
|
||||||
// @owner: the actor that owns the GrabHelper
|
// @owner: the actor that owns the GrabHelper
|
||||||
|
// @params: optional parameters to pass to Main.pushModal()
|
||||||
//
|
//
|
||||||
// Creates a new GrabHelper object, for dealing with keyboard and pointer grabs
|
// Creates a new GrabHelper object, for dealing with keyboard and pointer grabs
|
||||||
// associated with a set of actors.
|
// associated with a set of actors.
|
||||||
@ -34,17 +24,18 @@ function _navigateActor(actor) {
|
|||||||
const GrabHelper = new Lang.Class({
|
const GrabHelper = new Lang.Class({
|
||||||
Name: 'GrabHelper',
|
Name: 'GrabHelper',
|
||||||
|
|
||||||
_init: function(owner) {
|
_init: function(owner, params) {
|
||||||
this._owner = owner;
|
this._owner = owner;
|
||||||
|
this._modalParams = params;
|
||||||
|
|
||||||
this._grabStack = [];
|
this._grabStack = [];
|
||||||
|
|
||||||
this._actors = [];
|
this._actors = [];
|
||||||
this._capturedEventId = 0;
|
this._capturedEventId = 0;
|
||||||
this._eventId = 0;
|
|
||||||
this._keyFocusNotifyId = 0;
|
this._keyFocusNotifyId = 0;
|
||||||
this._focusWindowChangedId = 0;
|
this._focusWindowChangedId = 0;
|
||||||
this._ignoreRelease = false;
|
this._ignoreRelease = false;
|
||||||
|
this._isUngrabbingCount = 0;
|
||||||
|
|
||||||
this._modalCount = 0;
|
this._modalCount = 0;
|
||||||
this._grabFocusCount = 0;
|
this._grabFocusCount = 0;
|
||||||
@ -76,9 +67,12 @@ const GrabHelper = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_isWithinGrabbedActor: function(actor) {
|
_isWithinGrabbedActor: function(actor) {
|
||||||
|
let currentActor = this.currentGrab.actor;
|
||||||
while (actor) {
|
while (actor) {
|
||||||
if (this._actors.indexOf(actor) != -1)
|
if (this._actors.indexOf(actor) != -1)
|
||||||
return true;
|
return true;
|
||||||
|
if (actor == currentActor)
|
||||||
|
return true;
|
||||||
actor = actor.get_parent();
|
actor = actor.get_parent();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -88,6 +82,14 @@ const GrabHelper = new Lang.Class({
|
|||||||
return this._grabStack[this._grabStack.length - 1] || {};
|
return this._grabStack[this._grabStack.length - 1] || {};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get grabbed() {
|
||||||
|
return this._grabStack.length > 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
get grabStack() {
|
||||||
|
return this._grabStack;
|
||||||
|
},
|
||||||
|
|
||||||
_findStackIndex: function(actor) {
|
_findStackIndex: function(actor) {
|
||||||
if (!actor)
|
if (!actor)
|
||||||
return -1;
|
return -1;
|
||||||
@ -99,6 +101,16 @@ const GrabHelper = new Lang.Class({
|
|||||||
return -1;
|
return -1;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_actorInGrabStack: function(actor) {
|
||||||
|
while (actor) {
|
||||||
|
let idx = this._findStackIndex(actor);
|
||||||
|
if (idx >= 0)
|
||||||
|
return idx;
|
||||||
|
actor = actor.get_parent();
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
},
|
||||||
|
|
||||||
isActorGrabbed: function(actor) {
|
isActorGrabbed: function(actor) {
|
||||||
return this._findStackIndex(actor) >= 0;
|
return this._findStackIndex(actor) >= 0;
|
||||||
},
|
},
|
||||||
@ -138,6 +150,7 @@ const GrabHelper = new Lang.Class({
|
|||||||
params = Params.parse(params, { actor: null,
|
params = Params.parse(params, { actor: null,
|
||||||
modal: false,
|
modal: false,
|
||||||
grabFocus: false,
|
grabFocus: false,
|
||||||
|
focus: null,
|
||||||
onUngrab: null });
|
onUngrab: null });
|
||||||
|
|
||||||
let focus = global.stage.key_focus;
|
let focus = global.stage.key_focus;
|
||||||
@ -155,21 +168,26 @@ const GrabHelper = new Lang.Class({
|
|||||||
if (params.grabFocus && !this._takeFocusGrab(hadFocus))
|
if (params.grabFocus && !this._takeFocusGrab(hadFocus))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (hadFocus || params.grabFocus)
|
|
||||||
_navigateActor(newFocus);
|
|
||||||
|
|
||||||
this._grabStack.push(params);
|
this._grabStack.push(params);
|
||||||
|
|
||||||
|
if (params.focus) {
|
||||||
|
params.focus.grab_key_focus();
|
||||||
|
} 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));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
_takeModalGrab: function() {
|
_takeModalGrab: function() {
|
||||||
let firstGrab = (this._modalCount == 0);
|
let firstGrab = (this._modalCount == 0);
|
||||||
if (firstGrab) {
|
if (firstGrab) {
|
||||||
if (!Main.pushModal(this._owner))
|
if (!Main.pushModal(this._owner, this._modalParams))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
this._capturedEventId = global.stage.connect('captured-event', Lang.bind(this, this._onCapturedEvent));
|
|
||||||
this._eventId = global.stage.connect('event', Lang.bind(this, this._onEvent));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._modalCount++;
|
this._modalCount++;
|
||||||
@ -181,16 +199,6 @@ const GrabHelper = new Lang.Class({
|
|||||||
if (this._modalCount > 0)
|
if (this._modalCount > 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (this._capturedEventId > 0) {
|
|
||||||
global.stage.disconnect(this._capturedEventId);
|
|
||||||
this._capturedEventId = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._eventId > 0) {
|
|
||||||
global.stage.disconnect(this._eventId);
|
|
||||||
this._eventId = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Main.popModal(this._owner);
|
Main.popModal(this._owner);
|
||||||
global.sync_pointer();
|
global.sync_pointer();
|
||||||
},
|
},
|
||||||
@ -202,7 +210,6 @@ const GrabHelper = new Lang.Class({
|
|||||||
|
|
||||||
this._grabbedFromKeynav = hadFocus;
|
this._grabbedFromKeynav = hadFocus;
|
||||||
this._preGrabInputMode = global.stage_input_mode;
|
this._preGrabInputMode = global.stage_input_mode;
|
||||||
this._prevFocusedWindow = metaDisplay.focus_window;
|
|
||||||
|
|
||||||
if (this._preGrabInputMode == Shell.StageInputMode.NONREACTIVE ||
|
if (this._preGrabInputMode == Shell.StageInputMode.NONREACTIVE ||
|
||||||
this._preGrabInputMode == Shell.StageInputMode.NORMAL) {
|
this._preGrabInputMode == Shell.StageInputMode.NORMAL) {
|
||||||
@ -227,7 +234,7 @@ const GrabHelper = new Lang.Class({
|
|||||||
this._keyFocusNotifyId = 0;
|
this._keyFocusNotifyId = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._focusWindowChanged > 0) {
|
if (this._focusWindowChangedId > 0) {
|
||||||
let metaDisplay = global.screen.get_display();
|
let metaDisplay = global.screen.get_display();
|
||||||
metaDisplay.disconnect(this._focusWindowChangedId);
|
metaDisplay.disconnect(this._focusWindowChangedId);
|
||||||
this._focusWindowChangedId = 0;
|
this._focusWindowChangedId = 0;
|
||||||
@ -241,13 +248,7 @@ const GrabHelper = new Lang.Class({
|
|||||||
global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
|
global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._prevFocusedWindow) {
|
global.screen.focus_default_window(global.display.get_current_time_roundtrip());
|
||||||
let metaDisplay = global.screen.get_display();
|
|
||||||
if (!metaDisplay.focus_window) {
|
|
||||||
metaDisplay.set_input_focus_window(this._prevFocusedWindow,
|
|
||||||
false, global.get_current_time());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// ignoreRelease:
|
// ignoreRelease:
|
||||||
@ -268,12 +269,21 @@ const GrabHelper = new Lang.Class({
|
|||||||
// If the actor that was popped from the grab stack was not the actor
|
// If the actor that was popped from the grab stack was not the actor
|
||||||
// That was passed in, this call is ignored.
|
// That was passed in, this call is ignored.
|
||||||
ungrab: function(params) {
|
ungrab: function(params) {
|
||||||
params = Params.parse(params, { actor: this.currentGrab.actor });
|
params = Params.parse(params, { actor: this.currentGrab.actor,
|
||||||
|
isUser: false });
|
||||||
|
|
||||||
let grabStackIndex = this._findStackIndex(params.actor);
|
let grabStackIndex = this._findStackIndex(params.actor);
|
||||||
if (grabStackIndex < 0)
|
if (grabStackIndex < 0)
|
||||||
return;
|
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 focus = global.stage.key_focus;
|
||||||
let hadFocus = focus && this._isWithinGrabbedActor(focus);
|
let hadFocus = focus && this._isWithinGrabbedActor(focus);
|
||||||
|
|
||||||
@ -286,7 +296,7 @@ const GrabHelper = new Lang.Class({
|
|||||||
let poppedGrab = poppedGrabs[i];
|
let poppedGrab = poppedGrabs[i];
|
||||||
|
|
||||||
if (poppedGrab.onUngrab)
|
if (poppedGrab.onUngrab)
|
||||||
poppedGrab.onUngrab();
|
poppedGrab.onUngrab(params.isUser);
|
||||||
|
|
||||||
if (poppedGrab.modal)
|
if (poppedGrab.modal)
|
||||||
this._releaseModalGrab();
|
this._releaseModalGrab();
|
||||||
@ -295,21 +305,38 @@ const GrabHelper = new Lang.Class({
|
|||||||
this._releaseFocusGrab();
|
this._releaseFocusGrab();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.grabbed && this._capturedEventId > 0) {
|
||||||
|
global.stage.disconnect(this._capturedEventId);
|
||||||
|
this._capturedEventId = 0;
|
||||||
|
|
||||||
|
this._ignoreRelease = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (hadFocus) {
|
if (hadFocus) {
|
||||||
let poppedGrab = poppedGrabs[0];
|
let poppedGrab = poppedGrabs[0];
|
||||||
_navigateActor(poppedGrab.savedFocus);
|
if (poppedGrab.savedFocus)
|
||||||
|
poppedGrab.savedFocus.grab_key_focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._isUngrabbingCount--;
|
||||||
},
|
},
|
||||||
|
|
||||||
_onCapturedEvent: function(actor, event) {
|
_onCapturedEvent: function(actor, event) {
|
||||||
let type = event.type();
|
let type = event.type();
|
||||||
|
|
||||||
|
if (type == Clutter.EventType.KEY_PRESS &&
|
||||||
|
event.get_key_symbol() == Clutter.KEY_Escape) {
|
||||||
|
this.ungrab({ isUser: true });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
let press = type == Clutter.EventType.BUTTON_PRESS;
|
let press = type == Clutter.EventType.BUTTON_PRESS;
|
||||||
let release = type == Clutter.EventType.BUTTON_RELEASE;
|
let release = type == Clutter.EventType.BUTTON_RELEASE;
|
||||||
let button = press || release;
|
let button = press || release;
|
||||||
|
|
||||||
if (release && this._ignoreRelease) {
|
if (release && this._ignoreRelease) {
|
||||||
this._ignoreRelease = false;
|
this._ignoreRelease = false;
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!button && this._modalCount == 0)
|
if (!button && this._modalCount == 0)
|
||||||
@ -326,33 +353,26 @@ const GrabHelper = new Lang.Class({
|
|||||||
// which should be a release event.
|
// which should be a release event.
|
||||||
if (press)
|
if (press)
|
||||||
this._ignoreRelease = true;
|
this._ignoreRelease = true;
|
||||||
this.ungrab({ actor: this._grabStack[0].actor });
|
let i = this._actorInGrabStack(event.get_source()) + 1;
|
||||||
|
this.ungrab({ actor: this._grabStack[i].actor, isUser: true });
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._modalCount > 0;
|
return this._modalCount > 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
// We catch 'event' rather than 'key-press-event' so that we get
|
|
||||||
// a chance to run before the overview's own Escape check
|
|
||||||
_onEvent: function(actor, event) {
|
|
||||||
if (event.type() == Clutter.EventType.KEY_PRESS &&
|
|
||||||
event.get_key_symbol() == Clutter.KEY_Escape) {
|
|
||||||
this.ungrab();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
_onKeyFocusChanged: function() {
|
_onKeyFocusChanged: function() {
|
||||||
|
if (this._isUngrabbingCount > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
let focus = global.stage.key_focus;
|
let focus = global.stage.key_focus;
|
||||||
if (!focus || !this._isWithinGrabbedActor(focus))
|
if (!focus || !this._isWithinGrabbedActor(focus))
|
||||||
this.ungrab();
|
this.ungrab({ isUser: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
_focusWindowChanged: function() {
|
_focusWindowChanged: function() {
|
||||||
let metaDisplay = global.screen.get_display();
|
let metaDisplay = global.screen.get_display();
|
||||||
if (metaDisplay.focus_window != null)
|
if (metaDisplay.focus_window != null)
|
||||||
this.ungrab();
|
this.ungrab({ isUser: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -3,107 +3,149 @@
|
|||||||
const Clutter = imports.gi.Clutter;
|
const Clutter = imports.gi.Clutter;
|
||||||
const IBus = imports.gi.IBus;
|
const IBus = imports.gi.IBus;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
|
const Signals = imports.signals;
|
||||||
const St = imports.gi.St;
|
const St = imports.gi.St;
|
||||||
|
|
||||||
const BoxPointer = imports.ui.boxpointer;
|
const BoxPointer = imports.ui.boxpointer;
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
const PopupMenu = imports.ui.popupMenu;
|
|
||||||
|
|
||||||
const MAX_CANDIDATES_PER_PAGE = 16;
|
const MAX_CANDIDATES_PER_PAGE = 16;
|
||||||
|
|
||||||
const CandidateArea = new Lang.Class({
|
const CandidateArea = new Lang.Class({
|
||||||
Name: 'CandidateArea',
|
Name: 'CandidateArea',
|
||||||
Extends: PopupMenu.PopupBaseMenuItem,
|
|
||||||
|
|
||||||
_init: function() {
|
_init: function() {
|
||||||
this.parent({ reactive: false });
|
this.actor = new St.BoxLayout({ vertical: true,
|
||||||
|
visible: false });
|
||||||
// St.Table exhibits some sizing problems so let's go with a
|
this._candidateBoxes = [];
|
||||||
// clutter layout manager for now.
|
|
||||||
this._table = new Clutter.Actor();
|
|
||||||
this.addActor(this._table);
|
|
||||||
|
|
||||||
this._tableLayout = new Clutter.TableLayout();
|
|
||||||
this._table.set_layout_manager(this._tableLayout);
|
|
||||||
|
|
||||||
this._indexLabels = [];
|
|
||||||
this._candidateLabels = [];
|
|
||||||
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
|
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
|
||||||
this._indexLabels.push(new St.Label({ style_class: 'candidate-index' }));
|
let box = new St.BoxLayout({ style_class: 'candidate-box',
|
||||||
this._candidateLabels.push(new St.Label({ style_class: 'candidate-label' }));
|
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._orientation = -1;
|
||||||
this._cursorPosition = 0;
|
this._cursorPosition = 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
_setOrientation: function(orientation) {
|
setOrientation: function(orientation) {
|
||||||
if (this._orientation == orientation)
|
if (this._orientation == orientation)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this._orientation = orientation;
|
this._orientation = orientation;
|
||||||
|
|
||||||
this._table.remove_all_children();
|
if (this._orientation == IBus.Orientation.HORIZONTAL) {
|
||||||
|
this.actor.vertical = false;
|
||||||
if (this._orientation == IBus.Orientation.HORIZONTAL)
|
this.actor.remove_style_class_name('vertical');
|
||||||
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
|
this.actor.add_style_class_name('horizontal');
|
||||||
this._tableLayout.pack(this._indexLabels[i], i*2, 0);
|
this._previousButton.child.icon_name = 'go-previous-symbolic';
|
||||||
this._tableLayout.pack(this._candidateLabels[i], i*2 + 1, 0);
|
this._nextButton.child.icon_name = 'go-next-symbolic';
|
||||||
}
|
} else { // VERTICAL || SYSTEM
|
||||||
else // VERTICAL || SYSTEM
|
this.actor.vertical = true;
|
||||||
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
|
this.actor.add_style_class_name('vertical');
|
||||||
this._tableLayout.pack(this._indexLabels[i], 0, i);
|
this.actor.remove_style_class_name('horizontal');
|
||||||
this._tableLayout.pack(this._candidateLabels[i], 1, i);
|
this._previousButton.child.icon_name = 'go-up-symbolic';
|
||||||
}
|
this._nextButton.child.icon_name = 'go-down-symbolic';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setCandidates: function(indexes, candidates, orientation, cursorPosition, cursorVisible) {
|
setCandidates: function(indexes, candidates, cursorPosition, cursorVisible) {
|
||||||
this._setOrientation(orientation);
|
|
||||||
|
|
||||||
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
|
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
|
||||||
let visible = i < candidates.length;
|
let visible = i < candidates.length;
|
||||||
this._indexLabels[i].visible = visible;
|
let box = this._candidateBoxes[i];
|
||||||
this._candidateLabels[i].visible = visible;
|
box.visible = visible;
|
||||||
|
|
||||||
if (!visible)
|
if (!visible)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
this._indexLabels[i].text = ((indexes && indexes[i]) ? indexes[i] : '%x.'.format(i + 1));
|
box._indexLabel.text = ((indexes && indexes[i]) ? indexes[i] : '%x'.format(i + 1));
|
||||||
this._candidateLabels[i].text = candidates[i];
|
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;
|
this._cursorPosition = cursorPosition;
|
||||||
if (cursorVisible)
|
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({
|
const CandidatePopup = new Lang.Class({
|
||||||
Name: 'CandidatePopup',
|
Name: 'CandidatePopup',
|
||||||
Extends: PopupMenu.PopupMenu,
|
|
||||||
|
|
||||||
_init: function() {
|
_init: function() {
|
||||||
this._cursor = new St.Bin({ opacity: 0 });
|
this._cursor = new St.Bin({ opacity: 0 });
|
||||||
Main.uiGroup.add_actor(this._cursor);
|
Main.uiGroup.add_actor(this._cursor);
|
||||||
|
|
||||||
this.parent(this._cursor, 0, St.Side.TOP);
|
this._boxPointer = new BoxPointer.BoxPointer(St.Side.TOP);
|
||||||
this.actor.hide();
|
this._boxPointer.actor.visible = false;
|
||||||
Main.uiGroup.add_actor(this.actor);
|
this._boxPointer.actor.style_class = 'candidate-popup-boxpointer';
|
||||||
|
Main.layoutManager.addChrome(this._boxPointer.actor);
|
||||||
|
|
||||||
this._preeditTextItem = new PopupMenu.PopupMenuItem('', { reactive: false });
|
let box = new St.BoxLayout({ style_class: 'candidate-popup-content',
|
||||||
this._preeditTextItem.actor.hide();
|
vertical: true });
|
||||||
this.addMenuItem(this._preeditTextItem);
|
this._boxPointer.bin.set_child(box);
|
||||||
|
|
||||||
this._auxTextItem = new PopupMenu.PopupMenuItem('', { reactive: false });
|
this._preeditText = new St.Label({ style_class: 'candidate-popup-text',
|
||||||
this._auxTextItem.actor.hide();
|
visible: false });
|
||||||
this.addMenuItem(this._auxTextItem);
|
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._candidateArea = new CandidateArea();
|
||||||
this._lookupTableItem.actor.hide();
|
box.add(this._candidateArea.actor);
|
||||||
this.addMenuItem(this._lookupTableItem);
|
|
||||||
|
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;
|
this._panelService = null;
|
||||||
},
|
},
|
||||||
@ -117,66 +159,61 @@ const CandidatePopup = new Lang.Class({
|
|||||||
Lang.bind(this, function(ps, x, y, w, h) {
|
Lang.bind(this, function(ps, x, y, w, h) {
|
||||||
this._cursor.set_position(x, y);
|
this._cursor.set_position(x, y);
|
||||||
this._cursor.set_size(w, h);
|
this._cursor.set_size(w, h);
|
||||||
|
if (this._boxPointer.actor.visible)
|
||||||
|
this._boxPointer.setPosition(this._cursor, 0);
|
||||||
}));
|
}));
|
||||||
panelService.connect('update-preedit-text',
|
panelService.connect('update-preedit-text',
|
||||||
Lang.bind(this, function(ps, text, cursorPosition, visible) {
|
Lang.bind(this, function(ps, text, cursorPosition, visible) {
|
||||||
if (visible)
|
this._preeditText.visible = visible;
|
||||||
this._preeditTextItem.actor.show();
|
|
||||||
else
|
|
||||||
this._preeditTextItem.actor.hide();
|
|
||||||
this._updateVisibility();
|
this._updateVisibility();
|
||||||
|
|
||||||
this._preeditTextItem.actor.label_actor.text = text.get_text();
|
this._preeditText.text = text.get_text();
|
||||||
|
|
||||||
let attrs = text.get_attributes();
|
let attrs = text.get_attributes();
|
||||||
if (attrs)
|
if (attrs)
|
||||||
this._setTextAttributes(this._preeditTextItem.actor.label_actor.clutter_text,
|
this._setTextAttributes(this._preeditText.clutter_text,
|
||||||
attrs);
|
attrs);
|
||||||
}));
|
}));
|
||||||
panelService.connect('show-preedit-text',
|
panelService.connect('show-preedit-text',
|
||||||
Lang.bind(this, function(ps) {
|
Lang.bind(this, function(ps) {
|
||||||
this._preeditTextItem.actor.show();
|
this._preeditText.show();
|
||||||
this._updateVisibility();
|
this._updateVisibility();
|
||||||
}));
|
}));
|
||||||
panelService.connect('hide-preedit-text',
|
panelService.connect('hide-preedit-text',
|
||||||
Lang.bind(this, function(ps) {
|
Lang.bind(this, function(ps) {
|
||||||
this._preeditTextItem.actor.hide();
|
this._preeditText.hide();
|
||||||
this._updateVisibility();
|
this._updateVisibility();
|
||||||
}));
|
}));
|
||||||
panelService.connect('update-auxiliary-text',
|
panelService.connect('update-auxiliary-text',
|
||||||
Lang.bind(this, function(ps, text, visible) {
|
Lang.bind(this, function(ps, text, visible) {
|
||||||
if (visible)
|
this._auxText.visible = visible;
|
||||||
this._auxTextItem.actor.show();
|
|
||||||
else
|
|
||||||
this._auxTextItem.actor.hide();
|
|
||||||
this._updateVisibility();
|
this._updateVisibility();
|
||||||
|
|
||||||
this._auxTextItem.actor.label_actor.text = text.get_text();
|
this._auxText.text = text.get_text();
|
||||||
}));
|
}));
|
||||||
panelService.connect('show-auxiliary-text',
|
panelService.connect('show-auxiliary-text',
|
||||||
Lang.bind(this, function(ps) {
|
Lang.bind(this, function(ps) {
|
||||||
this._auxTextItem.actor.show();
|
this._auxText.show();
|
||||||
this._updateVisibility();
|
this._updateVisibility();
|
||||||
}));
|
}));
|
||||||
panelService.connect('hide-auxiliary-text',
|
panelService.connect('hide-auxiliary-text',
|
||||||
Lang.bind(this, function(ps) {
|
Lang.bind(this, function(ps) {
|
||||||
this._auxTextItem.actor.hide();
|
this._auxText.hide();
|
||||||
this._updateVisibility();
|
this._updateVisibility();
|
||||||
}));
|
}));
|
||||||
panelService.connect('update-lookup-table',
|
panelService.connect('update-lookup-table',
|
||||||
Lang.bind(this, function(ps, lookupTable, visible) {
|
Lang.bind(this, function(ps, lookupTable, visible) {
|
||||||
if (visible)
|
this._candidateArea.actor.visible = visible;
|
||||||
this._lookupTableItem.actor.show();
|
|
||||||
else
|
|
||||||
this._lookupTableItem.actor.hide();
|
|
||||||
this._updateVisibility();
|
this._updateVisibility();
|
||||||
|
|
||||||
|
let nCandidates = lookupTable.get_number_of_candidates();
|
||||||
let cursorPos = lookupTable.get_cursor_pos();
|
let cursorPos = lookupTable.get_cursor_pos();
|
||||||
let pageSize = lookupTable.get_page_size();
|
let pageSize = lookupTable.get_page_size();
|
||||||
|
let nPages = Math.ceil(nCandidates / pageSize);
|
||||||
let page = ((cursorPos == 0) ? 0 : Math.floor(cursorPos / pageSize));
|
let page = ((cursorPos == 0) ? 0 : Math.floor(cursorPos / pageSize));
|
||||||
let startIndex = page * pageSize;
|
let startIndex = page * pageSize;
|
||||||
let endIndex = Math.min((page + 1) * pageSize,
|
let endIndex = Math.min((page + 1) * pageSize, nCandidates);
|
||||||
lookupTable.get_number_of_candidates());
|
|
||||||
let indexes = [];
|
let indexes = [];
|
||||||
let indexLabel;
|
let indexLabel;
|
||||||
for (let i = 0; indexLabel = lookupTable.get_label(i); ++i)
|
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)
|
for (let i = startIndex; i < endIndex; ++i)
|
||||||
candidates.push(lookupTable.get_candidate(i).get_text());
|
candidates.push(lookupTable.get_candidate(i).get_text());
|
||||||
|
|
||||||
this._lookupTableItem.setCandidates(indexes,
|
this._candidateArea.setCandidates(indexes,
|
||||||
candidates,
|
candidates,
|
||||||
lookupTable.get_orientation(),
|
cursorPos % pageSize,
|
||||||
cursorPos % pageSize,
|
lookupTable.is_cursor_visible());
|
||||||
lookupTable.is_cursor_visible());
|
this._candidateArea.setOrientation(lookupTable.get_orientation());
|
||||||
|
this._candidateArea.updateButtons(lookupTable.is_round(), page, nPages);
|
||||||
}));
|
}));
|
||||||
panelService.connect('show-lookup-table',
|
panelService.connect('show-lookup-table',
|
||||||
Lang.bind(this, function(ps) {
|
Lang.bind(this, function(ps) {
|
||||||
this._lookupTableItem.actor.show();
|
this._candidateArea.actor.show();
|
||||||
this._updateVisibility();
|
this._updateVisibility();
|
||||||
}));
|
}));
|
||||||
panelService.connect('hide-lookup-table',
|
panelService.connect('hide-lookup-table',
|
||||||
Lang.bind(this, function(ps) {
|
Lang.bind(this, function(ps) {
|
||||||
this._lookupTableItem.actor.hide();
|
this._candidateArea.actor.hide();
|
||||||
this._updateVisibility();
|
this._updateVisibility();
|
||||||
}));
|
}));
|
||||||
panelService.connect('focus-out',
|
panelService.connect('focus-out',
|
||||||
Lang.bind(this, function(ps) {
|
Lang.bind(this, function(ps) {
|
||||||
this.close(BoxPointer.PopupAnimation.NONE);
|
this._boxPointer.hide(BoxPointer.PopupAnimation.NONE);
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateVisibility: function() {
|
_updateVisibility: function() {
|
||||||
let isVisible = (this._preeditTextItem.actor.visible ||
|
let isVisible = (this._preeditText.visible ||
|
||||||
this._auxTextItem.actor.visible ||
|
this._auxText.visible ||
|
||||||
this._lookupTableItem.actor.visible);
|
this._candidateArea.actor.visible);
|
||||||
|
|
||||||
if (isVisible)
|
if (isVisible) {
|
||||||
this.open(BoxPointer.PopupAnimation.NONE);
|
this._boxPointer.setPosition(this._cursor, 0);
|
||||||
else
|
this._boxPointer.show(BoxPointer.PopupAnimation.NONE);
|
||||||
this.close(BoxPointer.PopupAnimation.NONE);
|
this._boxPointer.actor.raise_top();
|
||||||
|
} else {
|
||||||
|
this._boxPointer.hide(BoxPointer.PopupAnimation.NONE);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_setTextAttributes: function(clutterText, ibusAttrList) {
|
_setTextAttributes: function(clutterText, ibusAttrList) {
|
||||||
|
@ -23,6 +23,8 @@ const BaseIcon = new Lang.Class({
|
|||||||
this.actor._delegate = this;
|
this.actor._delegate = this;
|
||||||
this.actor.connect('style-changed',
|
this.actor.connect('style-changed',
|
||||||
Lang.bind(this, this._onStyleChanged));
|
Lang.bind(this, this._onStyleChanged));
|
||||||
|
this.actor.connect('destroy',
|
||||||
|
Lang.bind(this, this._onDestroy));
|
||||||
|
|
||||||
this._spacing = 0;
|
this._spacing = 0;
|
||||||
|
|
||||||
@ -52,6 +54,9 @@ const BaseIcon = new Lang.Class({
|
|||||||
this._setSizeManually = params.setSizeManually;
|
this._setSizeManually = params.setSizeManually;
|
||||||
|
|
||||||
this.icon = null;
|
this.icon = null;
|
||||||
|
|
||||||
|
let cache = St.TextureCache.get_default();
|
||||||
|
this._iconThemeChangedId = cache.connect('icon-theme-changed', Lang.bind(this, this._onIconThemeChanged));
|
||||||
},
|
},
|
||||||
|
|
||||||
_allocate: function(actor, box, flags) {
|
_allocate: function(actor, box, flags) {
|
||||||
@ -146,7 +151,22 @@ const BaseIcon = new Lang.Class({
|
|||||||
size = found ? len : ICON_SIZE;
|
size = found ? len : ICON_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.iconSize == size && this._iconBin.child)
|
||||||
|
return;
|
||||||
|
|
||||||
this._createIconTexture(size);
|
this._createIconTexture(size);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onDestroy: function() {
|
||||||
|
if (this._iconThemeChangedId > 0) {
|
||||||
|
let cache = St.TextureCache.get_default();
|
||||||
|
cache.disconnect(this._iconThemeChangedId);
|
||||||
|
this._iconThemeChangedId = 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onIconThemeChanged: function() {
|
||||||
|
this._createIconTexture(this.iconSize);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -156,13 +176,16 @@ const IconGrid = new Lang.Class({
|
|||||||
_init: function(params) {
|
_init: function(params) {
|
||||||
params = Params.parse(params, { rowLimit: null,
|
params = Params.parse(params, { rowLimit: null,
|
||||||
columnLimit: null,
|
columnLimit: null,
|
||||||
|
fillParent: false,
|
||||||
xAlign: St.Align.MIDDLE });
|
xAlign: St.Align.MIDDLE });
|
||||||
this._rowLimit = params.rowLimit;
|
this._rowLimit = params.rowLimit;
|
||||||
this._colLimit = params.columnLimit;
|
this._colLimit = params.columnLimit;
|
||||||
this._xAlign = params.xAlign;
|
this._xAlign = params.xAlign;
|
||||||
|
this._fillParent = params.fillParent;
|
||||||
|
|
||||||
this.actor = new St.BoxLayout({ style_class: 'icon-grid',
|
this.actor = new St.BoxLayout({ style_class: 'icon-grid',
|
||||||
vertical: true });
|
vertical: true });
|
||||||
|
|
||||||
// Pulled from CSS, but hardcode some defaults here
|
// Pulled from CSS, but hardcode some defaults here
|
||||||
this._spacing = 0;
|
this._spacing = 0;
|
||||||
this._hItemSize = this._vItemSize = ICON_SIZE;
|
this._hItemSize = this._vItemSize = ICON_SIZE;
|
||||||
@ -176,6 +199,11 @@ const IconGrid = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_getPreferredWidth: function (grid, forHeight, alloc) {
|
_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 children = this._grid.get_children();
|
||||||
let nColumns = this._colLimit ? Math.min(this._colLimit,
|
let nColumns = this._colLimit ? Math.min(this._colLimit,
|
||||||
children.length)
|
children.length)
|
||||||
@ -197,8 +225,20 @@ const IconGrid = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_getPreferredHeight: function (grid, forWidth, alloc) {
|
_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 children = this._getVisibleChildren();
|
||||||
let [nColumns, usedWidth] = this._computeLayout(forWidth);
|
let nColumns, spacing;
|
||||||
|
if (forWidth < 0) {
|
||||||
|
nColumns = children.length;
|
||||||
|
spacing = this._spacing;
|
||||||
|
} else {
|
||||||
|
[nColumns, , spacing] = this._computeLayout(forWidth);
|
||||||
|
}
|
||||||
|
|
||||||
let nRows;
|
let nRows;
|
||||||
if (nColumns > 0)
|
if (nColumns > 0)
|
||||||
nRows = Math.ceil(children.length / nColumns);
|
nRows = Math.ceil(children.length / nColumns);
|
||||||
@ -206,18 +246,25 @@ const IconGrid = new Lang.Class({
|
|||||||
nRows = 0;
|
nRows = 0;
|
||||||
if (this._rowLimit)
|
if (this._rowLimit)
|
||||||
nRows = Math.min(nRows, 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;
|
let height = nRows * this._vItemSize + totalSpacing;
|
||||||
alloc.min_size = height;
|
alloc.min_size = height;
|
||||||
alloc.natural_size = height;
|
alloc.natural_size = height;
|
||||||
},
|
},
|
||||||
|
|
||||||
_allocate: function (grid, box, flags) {
|
_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 children = this._getVisibleChildren();
|
||||||
let availWidth = box.x2 - box.x1;
|
let availWidth = box.x2 - box.x1;
|
||||||
let availHeight = box.y2 - box.y1;
|
let availHeight = box.y2 - box.y1;
|
||||||
|
|
||||||
let [nColumns, usedWidth] = this._computeLayout(availWidth);
|
let [nColumns, usedWidth, spacing] = this._computeLayout(availWidth);
|
||||||
|
|
||||||
let leftPadding;
|
let leftPadding;
|
||||||
switch(this._xAlign) {
|
switch(this._xAlign) {
|
||||||
@ -256,7 +303,8 @@ const IconGrid = new Lang.Class({
|
|||||||
childBox.x2 = childBox.x1 + width;
|
childBox.x2 = childBox.x1 + width;
|
||||||
childBox.y2 = childBox.y1 + height;
|
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);
|
this._grid.set_skip_paint(children[i], true);
|
||||||
} else {
|
} else {
|
||||||
children[i].allocate(childBox, flags);
|
children[i].allocate(childBox, flags);
|
||||||
@ -270,10 +318,10 @@ const IconGrid = new Lang.Class({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (columnIndex == 0) {
|
if (columnIndex == 0) {
|
||||||
y += this._vItemSize + this._spacing;
|
y += this._vItemSize + spacing;
|
||||||
x = box.x1 + leftPadding;
|
x = box.x1 + leftPadding;
|
||||||
} else {
|
} else {
|
||||||
x += this._hItemSize + this._spacing;
|
x += this._hItemSize + spacing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -289,16 +337,25 @@ const IconGrid = new Lang.Class({
|
|||||||
_computeLayout: function (forWidth) {
|
_computeLayout: function (forWidth) {
|
||||||
let nColumns = 0;
|
let nColumns = 0;
|
||||||
let usedWidth = 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) &&
|
while ((this._colLimit == null || nColumns < this._colLimit) &&
|
||||||
(usedWidth + this._hItemSize <= forWidth)) {
|
(usedWidth + this._hItemSize <= forWidth)) {
|
||||||
usedWidth += this._hItemSize + this._spacing;
|
usedWidth += this._hItemSize + spacing;
|
||||||
nColumns += 1;
|
nColumns += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nColumns > 0)
|
if (nColumns > 0)
|
||||||
usedWidth -= this._spacing;
|
usedWidth -= spacing;
|
||||||
|
|
||||||
return [nColumns, usedWidth];
|
return [nColumns, usedWidth, spacing];
|
||||||
},
|
},
|
||||||
|
|
||||||
_onStyleChanged: function() {
|
_onStyleChanged: function() {
|
||||||
|
@ -2,43 +2,27 @@
|
|||||||
|
|
||||||
const Caribou = imports.gi.Caribou;
|
const Caribou = imports.gi.Caribou;
|
||||||
const Clutter = imports.gi.Clutter;
|
const Clutter = imports.gi.Clutter;
|
||||||
const DBus = imports.dbus;
|
|
||||||
const Gdk = imports.gi.Gdk;
|
const Gdk = imports.gi.Gdk;
|
||||||
const Gio = imports.gi.Gio;
|
const Gio = imports.gi.Gio;
|
||||||
const GLib = imports.gi.GLib;
|
const GLib = imports.gi.GLib;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
const Shell = imports.gi.Shell;
|
const Shell = imports.gi.Shell;
|
||||||
|
const Signals = imports.signals;
|
||||||
const St = imports.gi.St;
|
const St = imports.gi.St;
|
||||||
|
|
||||||
const BoxPointer = imports.ui.boxpointer;
|
const BoxPointer = imports.ui.boxpointer;
|
||||||
|
const Layout = imports.ui.layout;
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
const MessageTray = imports.ui.messageTray;
|
const MessageTray = imports.ui.messageTray;
|
||||||
|
|
||||||
|
const KEYBOARD_REST_TIME = Layout.KEYBOARD_ANIMATION_TIME * 2 * 1000;
|
||||||
|
|
||||||
const KEYBOARD_SCHEMA = 'org.gnome.shell.keyboard';
|
const KEYBOARD_SCHEMA = 'org.gnome.shell.keyboard';
|
||||||
const KEYBOARD_TYPE = 'keyboard-type';
|
const KEYBOARD_TYPE = 'keyboard-type';
|
||||||
|
|
||||||
const A11Y_APPLICATIONS_SCHEMA = 'org.gnome.desktop.a11y.applications';
|
const A11Y_APPLICATIONS_SCHEMA = 'org.gnome.desktop.a11y.applications';
|
||||||
const SHOW_KEYBOARD = 'screen-keyboard-enabled';
|
const SHOW_KEYBOARD = 'screen-keyboard-enabled';
|
||||||
|
|
||||||
// Key constants taken from Antler
|
|
||||||
// FIXME: ought to be moved into libcaribou
|
|
||||||
const PRETTY_KEYS = {
|
|
||||||
'BackSpace': '\u232b',
|
|
||||||
'space': ' ',
|
|
||||||
'Return': '\u23ce',
|
|
||||||
'Caribou_Prefs': '\u2328',
|
|
||||||
'Caribou_ShiftUp': '\u2b06',
|
|
||||||
'Caribou_ShiftDown': '\u2b07',
|
|
||||||
'Caribou_Emoticons': '\u263a',
|
|
||||||
'Caribou_Symbols': '123',
|
|
||||||
'Caribou_Symbols_More': '{#*',
|
|
||||||
'Caribou_Alpha': 'Abc',
|
|
||||||
'Tab': 'Tab',
|
|
||||||
'Escape': 'Esc',
|
|
||||||
'Control_L': 'Ctrl',
|
|
||||||
'Alt_L': 'Alt'
|
|
||||||
};
|
|
||||||
|
|
||||||
const CaribouKeyboardIface = <interface name='org.gnome.Caribou.Keyboard'>
|
const CaribouKeyboardIface = <interface name='org.gnome.Caribou.Keyboard'>
|
||||||
<method name='Show'>
|
<method name='Show'>
|
||||||
<arg type='u' direction='in' />
|
<arg type='u' direction='in' />
|
||||||
@ -75,14 +59,7 @@ const Key = new Lang.Class({
|
|||||||
if (this._key.name == 'Control_L' || this._key.name == 'Alt_L')
|
if (this._key.name == 'Control_L' || this._key.name == 'Alt_L')
|
||||||
this._key.latch = true;
|
this._key.latch = true;
|
||||||
|
|
||||||
this._key.connect('key-pressed', Lang.bind(this, function ()
|
|
||||||
{ this.actor.checked = true }));
|
|
||||||
this._key.connect('key-released', Lang.bind(this, function ()
|
|
||||||
{ this.actor.checked = false; }));
|
|
||||||
|
|
||||||
if (this._extended_keys.length > 0) {
|
if (this._extended_keys.length > 0) {
|
||||||
this._grabbed = false;
|
|
||||||
this._eventCaptureId = 0;
|
|
||||||
this._key.connect('notify::show-subkeys', Lang.bind(this, this._onShowSubkeysChanged));
|
this._key.connect('notify::show-subkeys', Lang.bind(this, this._onShowSubkeysChanged));
|
||||||
this._boxPointer = new BoxPointer.BoxPointer(St.Side.BOTTOM,
|
this._boxPointer = new BoxPointer.BoxPointer(St.Side.BOTTOM,
|
||||||
{ x_fill: true,
|
{ x_fill: true,
|
||||||
@ -98,17 +75,7 @@ const Key = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_makeKey: function () {
|
_makeKey: function () {
|
||||||
let label = this._key.name;
|
let label = GLib.markup_escape_text(this._key.label, -1);
|
||||||
|
|
||||||
if (label.length > 1) {
|
|
||||||
let pretty = PRETTY_KEYS[label];
|
|
||||||
if (pretty)
|
|
||||||
label = pretty;
|
|
||||||
else
|
|
||||||
label = this._getUnichar(this._key);
|
|
||||||
}
|
|
||||||
|
|
||||||
label = GLib.markup_escape_text(label, -1);
|
|
||||||
let button = new St.Button ({ label: label,
|
let button = new St.Button ({ label: label,
|
||||||
style_class: 'keyboard-key' });
|
style_class: 'keyboard-key' });
|
||||||
|
|
||||||
@ -144,52 +111,23 @@ const Key = new Lang.Class({
|
|||||||
this._boxPointer.bin.add_actor(this._extended_keyboard);
|
this._boxPointer.bin.add_actor(this._extended_keyboard);
|
||||||
},
|
},
|
||||||
|
|
||||||
_onEventCapture: function (actor, event) {
|
get subkeys() {
|
||||||
let source = event.get_source();
|
return this._boxPointer;
|
||||||
let type = event.type();
|
|
||||||
|
|
||||||
if ((type == Clutter.EventType.BUTTON_PRESS ||
|
|
||||||
type == Clutter.EventType.BUTTON_RELEASE) &&
|
|
||||||
this._extended_keyboard.contains(source)) {
|
|
||||||
source.extended_key.press();
|
|
||||||
source.extended_key.release();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (type == Clutter.EventType.BUTTON_PRESS) {
|
|
||||||
this._boxPointer.actor.hide();
|
|
||||||
this._ungrab();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
_ungrab: function () {
|
|
||||||
global.stage.disconnect(this._eventCaptureId);
|
|
||||||
this._eventCaptureId = 0;
|
|
||||||
this._grabbed = false;
|
|
||||||
Main.popModal(this.actor);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_onShowSubkeysChanged: function () {
|
_onShowSubkeysChanged: function () {
|
||||||
if (this._key.show_subkeys) {
|
if (this._key.show_subkeys) {
|
||||||
this.actor.fake_release();
|
|
||||||
this._boxPointer.actor.raise_top();
|
this._boxPointer.actor.raise_top();
|
||||||
this._boxPointer.setPosition(this.actor, 0.5);
|
this._boxPointer.setPosition(this.actor, 0.5);
|
||||||
this._boxPointer.show(BoxPointer.PopupAnimation.FULL);
|
this.emit('show-subkeys');
|
||||||
|
this.actor.fake_release();
|
||||||
this.actor.set_hover(false);
|
this.actor.set_hover(false);
|
||||||
if (!this._grabbed) {
|
|
||||||
Main.pushModal(this.actor);
|
|
||||||
this._eventCaptureId = global.stage.connect('captured-event', Lang.bind(this, this._onEventCapture));
|
|
||||||
this._grabbed = true;
|
|
||||||
}
|
|
||||||
this._key.release();
|
|
||||||
} else {
|
} else {
|
||||||
if (this._grabbed)
|
this.emit('hide-subkeys');
|
||||||
this._ungrab();
|
|
||||||
this._boxPointer.hide(BoxPointer.PopupAnimation.FULL);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Signals.addSignalMethods(Key.prototype);
|
||||||
|
|
||||||
const Keyboard = new Lang.Class({
|
const Keyboard = new Lang.Class({
|
||||||
// HACK: we can't set Name, because it collides with Name dbus property
|
// HACK: we can't set Name, because it collides with Name dbus property
|
||||||
@ -204,16 +142,26 @@ const Keyboard = new Lang.Class({
|
|||||||
this._focusInExtendedKeys = false;
|
this._focusInExtendedKeys = false;
|
||||||
|
|
||||||
this._timestamp = global.display.get_current_time_roundtrip();
|
this._timestamp = global.display.get_current_time_roundtrip();
|
||||||
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._redraw));
|
|
||||||
|
|
||||||
this._keyboardSettings = new Gio.Settings({ schema: KEYBOARD_SCHEMA });
|
this._keyboardSettings = new Gio.Settings({ schema: KEYBOARD_SCHEMA });
|
||||||
this._keyboardSettings.connect('changed', Lang.bind(this, this._settingsChanged));
|
this._keyboardSettings.connect('changed', Lang.bind(this, this._settingsChanged));
|
||||||
this._a11yApplicationsSettings = new Gio.Settings({ schema: A11Y_APPLICATIONS_SCHEMA });
|
this._a11yApplicationsSettings = new Gio.Settings({ schema: A11Y_APPLICATIONS_SCHEMA });
|
||||||
this._a11yApplicationsSettings.connect('changed', Lang.bind(this, this._settingsChanged));
|
this._a11yApplicationsSettings.connect('changed', Lang.bind(this, this._settingsChanged));
|
||||||
this._settingsChanged();
|
this._settingsChanged();
|
||||||
},
|
|
||||||
|
|
||||||
init: function () {
|
this._showIdleId = 0;
|
||||||
|
this._subkeysBoxPointer = null;
|
||||||
|
this._capturedEventId = 0;
|
||||||
|
this._capturedPress = false;
|
||||||
|
|
||||||
|
this._keyboardVisible = false;
|
||||||
|
Main.layoutManager.connect('keyboard-visible-changed', Lang.bind(this, function(o, visible) {
|
||||||
|
this._keyboardVisible = visible;
|
||||||
|
}));
|
||||||
|
this._keyboardRequested = false;
|
||||||
|
this._keyboardRestingId = 0;
|
||||||
|
|
||||||
|
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._redraw));
|
||||||
this._redraw();
|
this._redraw();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -228,25 +176,19 @@ const Keyboard = new Lang.Class({
|
|||||||
if (this._keyboard)
|
if (this._keyboard)
|
||||||
this._destroyKeyboard();
|
this._destroyKeyboard();
|
||||||
|
|
||||||
if (this._enableKeyboard) {
|
if (this._enableKeyboard)
|
||||||
// If we've been called because the setting actually just
|
this._setupKeyboard();
|
||||||
// changed to true (as opposed to being called from
|
else
|
||||||
// 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
|
|
||||||
Main.layoutManager.hideKeyboard(true);
|
Main.layoutManager.hideKeyboard(true);
|
||||||
},
|
},
|
||||||
|
|
||||||
_destroyKeyboard: function() {
|
_destroyKeyboard: function() {
|
||||||
if (this._keyboardNotifyId)
|
if (this._keyboardNotifyId)
|
||||||
this._keyboard.disconnect(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)
|
if (this._focusNotifyId)
|
||||||
global.stage.disconnect(this._focusNotifyId);
|
global.stage.disconnect(this._focusNotifyId);
|
||||||
this._keyboard = null;
|
this._keyboard = null;
|
||||||
@ -256,7 +198,7 @@ const Keyboard = new Lang.Class({
|
|||||||
this._destroySource();
|
this._destroySource();
|
||||||
},
|
},
|
||||||
|
|
||||||
_setupKeyboard: function(show) {
|
_setupKeyboard: function() {
|
||||||
this.actor = new St.BoxLayout({ name: 'keyboard', vertical: true, reactive: true });
|
this.actor = new St.BoxLayout({ name: 'keyboard', vertical: true, reactive: true });
|
||||||
Main.layoutManager.keyboardBox.add_actor(this.actor);
|
Main.layoutManager.keyboardBox.add_actor(this.actor);
|
||||||
Main.layoutManager.trackChrome(this.actor);
|
Main.layoutManager.trackChrome(this.actor);
|
||||||
@ -277,12 +219,11 @@ const Keyboard = new Lang.Class({
|
|||||||
this.actor.text_direction = Clutter.TextDirection.LTR;
|
this.actor.text_direction = Clutter.TextDirection.LTR;
|
||||||
|
|
||||||
this._keyboardNotifyId = this._keyboard.connect('notify::active-group', Lang.bind(this, this._onGroupChanged));
|
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));
|
this._focusNotifyId = global.stage.connect('notify::key-focus', Lang.bind(this, this._onKeyFocusChanged));
|
||||||
|
|
||||||
if (show)
|
this._createSource();
|
||||||
this.show();
|
|
||||||
else
|
|
||||||
this._createSource();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_onKeyFocusChanged: function () {
|
_onKeyFocusChanged: function () {
|
||||||
@ -302,88 +243,102 @@ const Keyboard = new Lang.Class({
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
let time = global.get_current_time();
|
let time = global.get_current_time();
|
||||||
if (focus instanceof Clutter.Text)
|
if (!(focus instanceof Clutter.Text)) {
|
||||||
this.Show(time);
|
|
||||||
else
|
|
||||||
this.Hide(time);
|
this.Hide(time);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._showIdleId)
|
||||||
|
this._showIdleId = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE,
|
||||||
|
Lang.bind(this, function() { this.Show(time); }));
|
||||||
|
},
|
||||||
|
|
||||||
|
_createLayersForGroup: function (gname) {
|
||||||
|
let group = this._keyboard.get_group(gname);
|
||||||
|
group.connect('notify::active-level', Lang.bind(this, this._onLevelChanged));
|
||||||
|
let layers = {};
|
||||||
|
let levels = group.get_levels();
|
||||||
|
for (let j = 0; j < levels.length; ++j) {
|
||||||
|
let lname = levels[j];
|
||||||
|
let level = group.get_level(lname);
|
||||||
|
let layout = new St.BoxLayout({ style_class: 'keyboard-layout',
|
||||||
|
vertical: true });
|
||||||
|
this._loadRows(level, layout);
|
||||||
|
layers[lname] = layout;
|
||||||
|
this.actor.add(layout, { x_fill: false });
|
||||||
|
|
||||||
|
layout.hide();
|
||||||
|
}
|
||||||
|
return layers;
|
||||||
},
|
},
|
||||||
|
|
||||||
_addKeys: function () {
|
_addKeys: function () {
|
||||||
let groups = this._keyboard.get_groups();
|
let groups = this._keyboard.get_groups();
|
||||||
for (let i = 0; i < groups.length; ++i) {
|
for (let i = 0; i < groups.length; ++i) {
|
||||||
let gname = groups[i];
|
let gname = groups[i];
|
||||||
let group = this._keyboard.get_group(gname);
|
this._groups[gname] = this._createLayersForGroup(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._setActiveLayer();
|
this._setActiveLayer();
|
||||||
},
|
},
|
||||||
|
|
||||||
_getTrayIcon: function () {
|
_onCapturedEvent: function(actor, event) {
|
||||||
let trayButton = new St.Button ({ label: _("tray"),
|
let type = event.type();
|
||||||
style_class: 'keyboard-key' });
|
let press = type == Clutter.EventType.BUTTON_PRESS;
|
||||||
trayButton.key_width = 1;
|
let release = type == Clutter.EventType.BUTTON_RELEASE;
|
||||||
trayButton.connect('button-press-event', Lang.bind(this, function () {
|
|
||||||
Main.messageTray.toggle();
|
|
||||||
}));
|
|
||||||
|
|
||||||
Main.overview.connect('showing', Lang.bind(this, function () {
|
if (press)
|
||||||
trayButton.reactive = false;
|
this._capturedPress = true;
|
||||||
trayButton.add_style_pseudo_class('grayed');
|
else if (release && this._capturedPress)
|
||||||
}));
|
this._hideSubkeys();
|
||||||
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;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
_addRows : function (keys, layout) {
|
_addRows : function (keys, layout) {
|
||||||
let keyboard_row = new St.BoxLayout();
|
let keyboard_row = new St.BoxLayout();
|
||||||
for (let i = 0; i < keys.length; ++i) {
|
for (let i = 0; i < keys.length; ++i) {
|
||||||
let children = keys[i].get_children();
|
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 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) {
|
for (let j = 0; j < children.length; ++j) {
|
||||||
if (this._numOfHorizKeys == 0)
|
if (this._numOfHorizKeys == 0)
|
||||||
this._numOfHorizKeys = children.length;
|
this._numOfHorizKeys = children.length;
|
||||||
let key = children[j];
|
let key = children[j];
|
||||||
let button = new Key(key);
|
let button = new Key(key);
|
||||||
|
|
||||||
if (key.align == 'right')
|
switch (key.align) {
|
||||||
|
case 'right':
|
||||||
right_box.add(button.actor);
|
right_box.add(button.actor);
|
||||||
else
|
break;
|
||||||
|
case 'center':
|
||||||
|
center_box.add(button.actor);
|
||||||
|
break;
|
||||||
|
case 'left':
|
||||||
|
default:
|
||||||
left_box.add(button.actor);
|
left_box.add(button.actor);
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (key.name == 'Caribou_Prefs') {
|
if (key.name == 'Caribou_Prefs') {
|
||||||
key.connect('key-released', Lang.bind(this, this.hide));
|
key.connect('key-released', Lang.bind(this, this.hide));
|
||||||
|
|
||||||
// Add new key for hiding message tray
|
|
||||||
right_box.add(this._getTrayIcon());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.connect('show-subkeys', Lang.bind(this, function() {
|
||||||
|
if (this._subkeysBoxPointer)
|
||||||
|
this._subkeysBoxPointer.hide(BoxPointer.PopupAnimation.FULL);
|
||||||
|
this._subkeysBoxPointer = button.subkeys;
|
||||||
|
this._subkeysBoxPointer.show(BoxPointer.PopupAnimation.FULL);
|
||||||
|
if (!this._capturedEventId)
|
||||||
|
this._capturedEventId = this.actor.connect('captured-event',
|
||||||
|
Lang.bind(this, this._onCapturedEvent));
|
||||||
|
}));
|
||||||
|
button.connect('hide-subkeys', Lang.bind(this, function() {
|
||||||
|
this._hideSubkeys();
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
keyboard_row.add(left_box, { expand: true, x_fill: false, x_align: St.Align.START });
|
keyboard_row.add(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 });
|
keyboard_row.add(right_box, { expand: true, x_fill: false, x_align: St.Align.END });
|
||||||
}
|
}
|
||||||
layout.add(keyboard_row);
|
layout.add(keyboard_row);
|
||||||
@ -404,7 +359,7 @@ const Keyboard = new Lang.Class({
|
|||||||
if (!this._enableKeyboard)
|
if (!this._enableKeyboard)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let monitor = Main.layoutManager.bottomMonitor;
|
let monitor = Main.layoutManager.keyboardMonitor;
|
||||||
let maxHeight = monitor.height / 3;
|
let maxHeight = monitor.height / 3;
|
||||||
this.actor.width = monitor.width;
|
this.actor.width = monitor.width;
|
||||||
|
|
||||||
@ -456,6 +411,14 @@ const Keyboard = new Lang.Class({
|
|||||||
this._redraw();
|
this._redraw();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onGroupAdded: function (keyboard, gname) {
|
||||||
|
this._groups[gname] = this._createLayersForGroup(gname);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onGroupRemoved: function (keyboard, gname) {
|
||||||
|
delete this._groups[gname];
|
||||||
|
},
|
||||||
|
|
||||||
_setActiveLayer: function () {
|
_setActiveLayer: function () {
|
||||||
let active_group_name = this._keyboard.active_group;
|
let active_group_name = this._keyboard.active_group;
|
||||||
let active_group = this._keyboard.get_group(active_group_name);
|
let active_group = this._keyboard.get_group(active_group_name);
|
||||||
@ -491,18 +454,79 @@ const Keyboard = new Lang.Class({
|
|||||||
actor._extended_keys || actor.extended_key;
|
actor._extended_keys || actor.extended_key;
|
||||||
},
|
},
|
||||||
|
|
||||||
show: function () {
|
_clearKeyboardRestTimer: function() {
|
||||||
this._redraw();
|
if (!this._keyboardRestingId)
|
||||||
|
return;
|
||||||
|
GLib.source_remove(this._keyboardRestingId);
|
||||||
|
this._keyboardRestingId = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function (monitor) {
|
||||||
|
this._keyboardRequested = true;
|
||||||
|
|
||||||
|
if (this._keyboardVisible) {
|
||||||
|
if (monitor != Main.layoutManager.keyboardIndex) {
|
||||||
|
Main.layoutManager.keyboardIndex = monitor;
|
||||||
|
this._redraw();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._clearKeyboardRestTimer();
|
||||||
|
this._keyboardRestingId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
|
||||||
|
KEYBOARD_REST_TIME,
|
||||||
|
Lang.bind(this, function() {
|
||||||
|
this._clearKeyboardRestTimer();
|
||||||
|
this._show(monitor);
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
_show: function(monitor) {
|
||||||
|
if (!this._keyboardRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Main.layoutManager.keyboardIndex = monitor;
|
||||||
|
this._redraw();
|
||||||
Main.layoutManager.showKeyboard();
|
Main.layoutManager.showKeyboard();
|
||||||
this._destroySource();
|
this._destroySource();
|
||||||
},
|
},
|
||||||
|
|
||||||
hide: function () {
|
hide: function () {
|
||||||
|
this._keyboardRequested = false;
|
||||||
|
|
||||||
|
if (!this._keyboardVisible)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._clearKeyboardRestTimer();
|
||||||
|
this._keyboardRestingId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
|
||||||
|
KEYBOARD_REST_TIME,
|
||||||
|
Lang.bind(this, function() {
|
||||||
|
this._clearKeyboardRestTimer();
|
||||||
|
this._hide();
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
_hide: function() {
|
||||||
|
if (this._keyboardRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._hideSubkeys();
|
||||||
Main.layoutManager.hideKeyboard();
|
Main.layoutManager.hideKeyboard();
|
||||||
this._createSource();
|
this._createSource();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_hideSubkeys: function() {
|
||||||
|
if (this._subkeysBoxPointer) {
|
||||||
|
this._subkeysBoxPointer.hide(BoxPointer.PopupAnimation.FULL);
|
||||||
|
this._subkeysBoxPointer = null;
|
||||||
|
}
|
||||||
|
if (this._capturedEventId) {
|
||||||
|
this.actor.disconnect(this._capturedEventId);
|
||||||
|
this._capturedEventId = 0;
|
||||||
|
}
|
||||||
|
this._capturedPress = false;
|
||||||
|
},
|
||||||
|
|
||||||
_moveTemporarily: function () {
|
_moveTemporarily: function () {
|
||||||
let currentWindow = global.screen.get_display().focus_window;
|
let currentWindow = global.screen.get_display().focus_window;
|
||||||
let rect = currentWindow.get_outer_rect();
|
let rect = currentWindow.get_outer_rect();
|
||||||
@ -531,6 +555,13 @@ const Keyboard = new Lang.Class({
|
|||||||
return one - two;
|
return one - two;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_clearShowIdle: function() {
|
||||||
|
if (!this._showIdleId)
|
||||||
|
return;
|
||||||
|
GLib.source_remove(this._showIdleId);
|
||||||
|
this._showIdleId = 0;
|
||||||
|
},
|
||||||
|
|
||||||
// D-Bus methods
|
// D-Bus methods
|
||||||
Show: function(timestamp) {
|
Show: function(timestamp) {
|
||||||
if (!this._enableKeyboard)
|
if (!this._enableKeyboard)
|
||||||
@ -539,9 +570,11 @@ const Keyboard = new Lang.Class({
|
|||||||
if (this._compareTimestamp(timestamp, this._timestamp) < 0)
|
if (this._compareTimestamp(timestamp, this._timestamp) < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
this._clearShowIdle();
|
||||||
|
|
||||||
if (timestamp != Clutter.CURRENT_TIME)
|
if (timestamp != Clutter.CURRENT_TIME)
|
||||||
this._timestamp = timestamp;
|
this._timestamp = timestamp;
|
||||||
this.show();
|
this.show(Main.layoutManager.focusIndex);
|
||||||
},
|
},
|
||||||
|
|
||||||
Hide: function(timestamp) {
|
Hide: function(timestamp) {
|
||||||
@ -551,6 +584,8 @@ const Keyboard = new Lang.Class({
|
|||||||
if (this._compareTimestamp(timestamp, this._timestamp) < 0)
|
if (this._compareTimestamp(timestamp, this._timestamp) < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
this._clearShowIdle();
|
||||||
|
|
||||||
if (timestamp != Clutter.CURRENT_TIME)
|
if (timestamp != Clutter.CURRENT_TIME)
|
||||||
this._timestamp = timestamp;
|
this._timestamp = timestamp;
|
||||||
this.hide();
|
this.hide();
|
||||||
@ -585,16 +620,13 @@ const KeyboardSource = new Lang.Class({
|
|||||||
this.keepTrayOnSummaryClick = true;
|
this.keepTrayOnSummaryClick = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSummaryClick: function() {
|
handleSummaryClick: function(button) {
|
||||||
let event = Clutter.get_current_event();
|
|
||||||
if (event.type() != Clutter.EventType.BUTTON_RELEASE)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
this.open();
|
this.open();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
open: function() {
|
open: function() {
|
||||||
this._keyboard.show();
|
// Show the OSK below the message tray
|
||||||
|
this._keyboard.show(Main.layoutManager.bottomIndex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
1484
js/ui/layout.js
1484
js/ui/layout.js
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,7 @@
|
|||||||
const Clutter = imports.gi.Clutter;
|
const Clutter = imports.gi.Clutter;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
const Meta = imports.gi.Meta;
|
const Meta = imports.gi.Meta;
|
||||||
|
const Signals = imports.signals;
|
||||||
const St = imports.gi.St;
|
const St = imports.gi.St;
|
||||||
|
|
||||||
const Params = imports.misc.params;
|
const Params = imports.misc.params;
|
||||||
@ -101,6 +102,7 @@ const Lightbox = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
show: function() {
|
show: function() {
|
||||||
|
Tweener.removeTweens(this.actor);
|
||||||
if (this._fadeInTime) {
|
if (this._fadeInTime) {
|
||||||
this.shown = false;
|
this.shown = false;
|
||||||
this.actor.opacity = 0;
|
this.actor.opacity = 0;
|
||||||
@ -110,17 +112,20 @@ const Lightbox = new Lang.Class({
|
|||||||
transition: 'easeOutQuad',
|
transition: 'easeOutQuad',
|
||||||
onComplete: Lang.bind(this, function() {
|
onComplete: Lang.bind(this, function() {
|
||||||
this.shown = true;
|
this.shown = true;
|
||||||
|
this.emit('shown');
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.actor.opacity = 255 * this._fadeFactor;
|
this.actor.opacity = 255 * this._fadeFactor;
|
||||||
this.shown = true;
|
this.shown = true;
|
||||||
|
this.emit('shown');
|
||||||
}
|
}
|
||||||
this.actor.show();
|
this.actor.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
hide: function() {
|
hide: function() {
|
||||||
this.shown = false;
|
this.shown = false;
|
||||||
|
Tweener.removeTweens(this.actor);
|
||||||
if (this._fadeOutTime) {
|
if (this._fadeOutTime) {
|
||||||
Tweener.addTween(this.actor,
|
Tweener.addTween(this.actor,
|
||||||
{ opacity: 0,
|
{ opacity: 0,
|
||||||
@ -197,3 +202,4 @@ const Lightbox = new Lang.Class({
|
|||||||
this.highlight(null);
|
this.highlight(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Signals.addSignalMethods(Lightbox.prototype);
|
||||||
|
@ -39,6 +39,7 @@ var commandHeader = 'const Clutter = imports.gi.Clutter; ' +
|
|||||||
* in the shell core code too. */
|
* in the shell core code too. */
|
||||||
'const stage = global.stage; ' +
|
'const stage = global.stage; ' +
|
||||||
/* Special lookingGlass functions */
|
/* Special lookingGlass functions */
|
||||||
|
'const inspect = Lang.bind(Main.lookingGlass, Main.lookingGlass.inspect); ' +
|
||||||
'const it = Main.lookingGlass.getIt(); ' +
|
'const it = Main.lookingGlass.getIt(); ' +
|
||||||
'const r = Lang.bind(Main.lookingGlass, Main.lookingGlass.getResult); ';
|
'const r = Lang.bind(Main.lookingGlass, Main.lookingGlass.getResult); ';
|
||||||
|
|
||||||
@ -684,7 +685,8 @@ const Memory = new Lang.Class({
|
|||||||
const Extensions = new Lang.Class({
|
const Extensions = new Lang.Class({
|
||||||
Name: 'Extensions',
|
Name: 'Extensions',
|
||||||
|
|
||||||
_init: function() {
|
_init: function(lookingGlass) {
|
||||||
|
this._lookingGlass = lookingGlass;
|
||||||
this.actor = new St.BoxLayout({ vertical: true,
|
this.actor = new St.BoxLayout({ vertical: true,
|
||||||
name: 'lookingGlassExtensions' });
|
name: 'lookingGlassExtensions' });
|
||||||
this._noExtensions = new St.Label({ style_class: 'lg-extensions-none',
|
this._noExtensions = new St.Label({ style_class: 'lg-extensions-none',
|
||||||
@ -871,8 +873,7 @@ const LookingGlass = new Lang.Class({
|
|||||||
inspectIcon.connect('button-press-event', Lang.bind(this, function () {
|
inspectIcon.connect('button-press-event', Lang.bind(this, function () {
|
||||||
let inspector = new Inspector(this);
|
let inspector = new Inspector(this);
|
||||||
inspector.connect('target', Lang.bind(this, function(i, target, stageX, stageY) {
|
inspector.connect('target', Lang.bind(this, function(i, target, stageX, stageY) {
|
||||||
this._pushResult('<inspect x:' + stageX + ' y:' + stageY + '>',
|
this._pushResult('inspect(' + Math.round(stageX) + ', ' + Math.round(stageY) + ')', target);
|
||||||
target);
|
|
||||||
}));
|
}));
|
||||||
inspector.connect('closed', Lang.bind(this, function() {
|
inspector.connect('closed', Lang.bind(this, function() {
|
||||||
this.actor.show();
|
this.actor.show();
|
||||||
@ -912,7 +913,7 @@ const LookingGlass = new Lang.Class({
|
|||||||
this._memory = new Memory();
|
this._memory = new Memory();
|
||||||
notebook.appendPage('Memory', this._memory.actor);
|
notebook.appendPage('Memory', this._memory.actor);
|
||||||
|
|
||||||
this._extensions = new Extensions();
|
this._extensions = new Extensions(this);
|
||||||
notebook.appendPage('Extensions', this._extensions.actor);
|
notebook.appendPage('Extensions', this._extensions.actor);
|
||||||
|
|
||||||
this._entry.clutter_text.connect('activate', Lang.bind(this, function (o, e) {
|
this._entry.clutter_text.connect('activate', Lang.bind(this, function (o, e) {
|
||||||
@ -950,9 +951,7 @@ const LookingGlass = new Lang.Class({
|
|||||||
|
|
||||||
_updateFont: function() {
|
_updateFont: function() {
|
||||||
let fontName = this._interfaceSettings.get_string('monospace-font-name');
|
let fontName = this._interfaceSettings.get_string('monospace-font-name');
|
||||||
// This is mishandled by the scanner - should by Pango.FontDescription_from_string(fontName);
|
let fontDesc = Pango.FontDescription.from_string(fontName);
|
||||||
// https://bugzilla.gnome.org/show_bug.cgi?id=595889
|
|
||||||
let fontDesc = Pango.font_description_from_string(fontName);
|
|
||||||
// We ignore everything but size and style; you'd be crazy to set your system-wide
|
// 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.
|
// monospace font to be bold/oblique/etc. Could easily be added here.
|
||||||
this.actor.style =
|
this.actor.style =
|
||||||
@ -1057,6 +1056,10 @@ const LookingGlass = new Lang.Class({
|
|||||||
this._entry.text = '';
|
this._entry.text = '';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
inspect: function(x, y) {
|
||||||
|
return global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);
|
||||||
|
},
|
||||||
|
|
||||||
getIt: function () {
|
getIt: function () {
|
||||||
return this._it;
|
return this._it;
|
||||||
},
|
},
|
||||||
@ -1129,7 +1132,7 @@ const LookingGlass = new Lang.Class({
|
|||||||
if (this._open)
|
if (this._open)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!Main.pushModal(this._entry))
|
if (!Main.pushModal(this._entry, { keybindingMode: Shell.KeyBindingMode.LOOKING_GLASS }))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this._notebook.selectIndex(0);
|
this._notebook.selectIndex(0);
|
||||||
|
288
js/ui/main.js
288
js/ui/main.js
@ -18,10 +18,13 @@ const ExtensionSystem = imports.ui.extensionSystem;
|
|||||||
const ExtensionDownloader = imports.ui.extensionDownloader;
|
const ExtensionDownloader = imports.ui.extensionDownloader;
|
||||||
const Keyboard = imports.ui.keyboard;
|
const Keyboard = imports.ui.keyboard;
|
||||||
const MessageTray = imports.ui.messageTray;
|
const MessageTray = imports.ui.messageTray;
|
||||||
|
const OsdWindow = imports.ui.osdWindow;
|
||||||
const Overview = imports.ui.overview;
|
const Overview = imports.ui.overview;
|
||||||
const Panel = imports.ui.panel;
|
const Panel = imports.ui.panel;
|
||||||
|
const Params = imports.misc.params;
|
||||||
const RunDialog = imports.ui.runDialog;
|
const RunDialog = imports.ui.runDialog;
|
||||||
const Layout = imports.ui.layout;
|
const Layout = imports.ui.layout;
|
||||||
|
const LoginManager = imports.misc.loginManager;
|
||||||
const LookingGlass = imports.ui.lookingGlass;
|
const LookingGlass = imports.ui.lookingGlass;
|
||||||
const NotificationDaemon = imports.ui.notificationDaemon;
|
const NotificationDaemon = imports.ui.notificationDaemon;
|
||||||
const WindowAttentionHandler = imports.ui.windowAttentionHandler;
|
const WindowAttentionHandler = imports.ui.windowAttentionHandler;
|
||||||
@ -30,7 +33,6 @@ const Scripting = imports.ui.scripting;
|
|||||||
const SessionMode = imports.ui.sessionMode;
|
const SessionMode = imports.ui.sessionMode;
|
||||||
const ShellDBus = imports.ui.shellDBus;
|
const ShellDBus = imports.ui.shellDBus;
|
||||||
const ShellMountOperation = imports.ui.shellMountOperation;
|
const ShellMountOperation = imports.ui.shellMountOperation;
|
||||||
const UnlockDialog = imports.ui.unlockDialog;
|
|
||||||
const WindowManager = imports.ui.windowManager;
|
const WindowManager = imports.ui.windowManager;
|
||||||
const Magnifier = imports.ui.magnifier;
|
const Magnifier = imports.ui.magnifier;
|
||||||
const XdndHandler = imports.ui.xdndHandler;
|
const XdndHandler = imports.ui.xdndHandler;
|
||||||
@ -50,11 +52,13 @@ let screenShield = null;
|
|||||||
let notificationDaemon = null;
|
let notificationDaemon = null;
|
||||||
let windowAttentionHandler = null;
|
let windowAttentionHandler = null;
|
||||||
let ctrlAltTabManager = null;
|
let ctrlAltTabManager = null;
|
||||||
|
let osdWindow = null;
|
||||||
let sessionMode = null;
|
let sessionMode = null;
|
||||||
let shellDBusService = null;
|
let shellDBusService = null;
|
||||||
let shellMountOpDBusService = null;
|
let shellMountOpDBusService = null;
|
||||||
let screenSaverDBus = null;
|
let screenSaverDBus = null;
|
||||||
let modalCount = 0;
|
let modalCount = 0;
|
||||||
|
let keybindingMode = Shell.KeyBindingMode.NONE;
|
||||||
let modalActorFocusStack = [];
|
let modalActorFocusStack = [];
|
||||||
let uiGroup = null;
|
let uiGroup = null;
|
||||||
let magnifier = null;
|
let magnifier = null;
|
||||||
@ -66,12 +70,23 @@ let _defaultCssStylesheet = null;
|
|||||||
let _cssStylesheet = null;
|
let _cssStylesheet = null;
|
||||||
let _overridesSettings = null;
|
let _overridesSettings = null;
|
||||||
|
|
||||||
let background = null;
|
|
||||||
|
|
||||||
function _sessionUpdated() {
|
function _sessionUpdated() {
|
||||||
Meta.keybindings_set_custom_handler('panel-run-dialog', sessionMode.hasRunDialog ? openRunDialog : null);
|
_loadDefaultStylesheet();
|
||||||
if (sessionMode.isGreeter)
|
|
||||||
screenShield.showDialog();
|
wm.setCustomKeybindingHandler('panel-main-menu',
|
||||||
|
Shell.KeyBindingMode.NORMAL |
|
||||||
|
Shell.KeyBindingMode.OVERVIEW,
|
||||||
|
sessionMode.hasOverview ? Lang.bind(overview, overview.toggle) : null);
|
||||||
|
wm.allowKeybinding('overlay-key', Shell.KeyBindingMode.NORMAL |
|
||||||
|
Shell.KeyBindingMode.OVERVIEW);
|
||||||
|
|
||||||
|
wm.setCustomKeybindingHandler('panel-run-dialog',
|
||||||
|
Shell.KeyBindingMode.NORMAL |
|
||||||
|
Shell.KeyBindingMode.OVERVIEW,
|
||||||
|
sessionMode.hasRunDialog ? openRunDialog : null);
|
||||||
|
|
||||||
|
if (!sessionMode.hasRunDialog && lookingGlass)
|
||||||
|
lookingGlass.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
@ -85,9 +100,21 @@ function start() {
|
|||||||
Gio.DesktopAppInfo.set_desktop_env('GNOME');
|
Gio.DesktopAppInfo.set_desktop_env('GNOME');
|
||||||
|
|
||||||
sessionMode = new SessionMode.SessionMode();
|
sessionMode = new SessionMode.SessionMode();
|
||||||
|
sessionMode.connect('sessions-loaded', _sessionsLoaded);
|
||||||
|
sessionMode.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _sessionsLoaded() {
|
||||||
|
sessionMode.connect('updated', _sessionUpdated);
|
||||||
|
_initializeUI();
|
||||||
|
|
||||||
shellDBusService = new ShellDBus.GnomeShell();
|
shellDBusService = new ShellDBus.GnomeShell();
|
||||||
shellMountOpDBusService = new ShellMountOperation.GnomeShellMountOpHandler();
|
shellMountOpDBusService = new ShellMountOperation.GnomeShellMountOpHandler();
|
||||||
|
|
||||||
|
_sessionUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _initializeUI() {
|
||||||
// Ensure ShellWindowTracker and ShellAppUsage are initialized; this will
|
// Ensure ShellWindowTracker and ShellAppUsage are initialized; this will
|
||||||
// also initialize ShellAppSystem first. ShellAppSystem
|
// also initialize ShellAppSystem first. ShellAppSystem
|
||||||
// needs to load all the .desktop files, and ShellWindowTracker
|
// needs to load all the .desktop files, and ShellWindowTracker
|
||||||
@ -101,48 +128,26 @@ function start() {
|
|||||||
|
|
||||||
tracker.connect('startup-sequence-changed', _queueCheckWorkspaces);
|
tracker.connect('startup-sequence-changed', _queueCheckWorkspaces);
|
||||||
|
|
||||||
// The stage is always covered so Clutter doesn't need to clear it; however
|
_loadDefaultStylesheet();
|
||||||
// the color is used as the default contents for the Mutter root background
|
|
||||||
// actor so set it anyways.
|
|
||||||
global.stage.color = DEFAULT_BACKGROUND_COLOR;
|
|
||||||
global.stage.no_clear_hint = true;
|
|
||||||
|
|
||||||
_defaultCssStylesheet = global.datadir + '/theme/gnome-shell.css';
|
|
||||||
loadTheme();
|
|
||||||
|
|
||||||
// Set up stage hierarchy to group all UI actors under one container.
|
|
||||||
uiGroup = new Shell.GenericContainer({ name: 'uiGroup' });
|
|
||||||
uiGroup.connect('allocate',
|
|
||||||
function (actor, box, flags) {
|
|
||||||
let children = uiGroup.get_children();
|
|
||||||
for (let i = 0; i < children.length; i++)
|
|
||||||
children[i].allocate_preferred_size(flags);
|
|
||||||
});
|
|
||||||
uiGroup.connect('get-preferred-width',
|
|
||||||
function(actor, forHeight, alloc) {
|
|
||||||
let width = global.stage.width;
|
|
||||||
[alloc.min_size, alloc.natural_size] = [width, width];
|
|
||||||
});
|
|
||||||
uiGroup.connect('get-preferred-height',
|
|
||||||
function(actor, forWidth, alloc) {
|
|
||||||
let height = global.stage.height;
|
|
||||||
[alloc.min_size, alloc.natural_size] = [height, height];
|
|
||||||
});
|
|
||||||
global.window_group.reparent(uiGroup);
|
|
||||||
global.overlay_group.reparent(uiGroup);
|
|
||||||
global.stage.add_actor(uiGroup);
|
|
||||||
|
|
||||||
|
// Setup the stage hierarchy early
|
||||||
layoutManager = new Layout.LayoutManager();
|
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();
|
xdndHandler = new XdndHandler.XdndHandler();
|
||||||
ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager();
|
ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager();
|
||||||
|
osdWindow = new OsdWindow.OsdWindow();
|
||||||
overview = new Overview.Overview();
|
overview = new Overview.Overview();
|
||||||
magnifier = new Magnifier.Magnifier();
|
|
||||||
if (UnlockDialog.isSupported())
|
|
||||||
screenShield = new ScreenShield.ScreenShield();
|
|
||||||
else
|
|
||||||
screenShield = new ScreenShield.ScreenShieldFallback();
|
|
||||||
panel = new Panel.Panel();
|
|
||||||
wm = new WindowManager.WindowManager();
|
wm = new WindowManager.WindowManager();
|
||||||
|
magnifier = new Magnifier.Magnifier();
|
||||||
|
if (LoginManager.canLock())
|
||||||
|
screenShield = new ScreenShield.ScreenShield();
|
||||||
|
|
||||||
|
panel = new Panel.Panel();
|
||||||
messageTray = new MessageTray.MessageTray();
|
messageTray = new MessageTray.MessageTray();
|
||||||
keyboard = new Keyboard.Keyboard();
|
keyboard = new Keyboard.Keyboard();
|
||||||
notificationDaemon = new NotificationDaemon.NotificationDaemon();
|
notificationDaemon = new NotificationDaemon.NotificationDaemon();
|
||||||
@ -150,24 +155,20 @@ function start() {
|
|||||||
componentManager = new Components.ComponentManager();
|
componentManager = new Components.ComponentManager();
|
||||||
|
|
||||||
layoutManager.init();
|
layoutManager.init();
|
||||||
keyboard.init();
|
|
||||||
overview.init();
|
overview.init();
|
||||||
|
|
||||||
global.screen.override_workspace_layout(Meta.ScreenCorner.TOPLEFT,
|
global.screen.override_workspace_layout(Meta.ScreenCorner.TOPLEFT,
|
||||||
false, -1, 1);
|
false, -1, 1);
|
||||||
Meta.keybindings_set_custom_handler('panel-main-menu', Lang.bind(overview, overview.toggle));
|
|
||||||
global.display.connect('overlay-key', Lang.bind(overview, overview.toggle));
|
global.display.connect('overlay-key', Lang.bind(overview, overview.toggle));
|
||||||
|
|
||||||
sessionMode.connect('update', _sessionUpdated);
|
|
||||||
_sessionUpdated();
|
|
||||||
|
|
||||||
// Provide the bus object for gnome-session to
|
// Provide the bus object for gnome-session to
|
||||||
// initiate logouts.
|
// initiate logouts.
|
||||||
EndSessionDialog.init();
|
EndSessionDialog.init();
|
||||||
|
|
||||||
_startDate = new Date();
|
// We're ready for the session manager to move to the next phase
|
||||||
|
Meta.register_with_session();
|
||||||
|
|
||||||
global.stage.connect('captured-event', _globalKeyPressHandler);
|
_startDate = new Date();
|
||||||
|
|
||||||
log('GNOME Shell started at ' + _startDate);
|
log('GNOME Shell started at ' + _startDate);
|
||||||
|
|
||||||
@ -191,6 +192,18 @@ function start() {
|
|||||||
|
|
||||||
ExtensionDownloader.init();
|
ExtensionDownloader.init();
|
||||||
ExtensionSystem.init();
|
ExtensionSystem.init();
|
||||||
|
|
||||||
|
if (sessionMode.isGreeter && screenShield) {
|
||||||
|
layoutManager.connect('startup-prepared', function() {
|
||||||
|
screenShield.showDialog();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
layoutManager.connect('startup-complete', function() {
|
||||||
|
if (keybindingMode == Shell.KeyBindingMode.NONE) {
|
||||||
|
keybindingMode = Shell.KeyBindingMode.NORMAL;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let _workspaces = [];
|
let _workspaces = [];
|
||||||
@ -374,6 +387,18 @@ function _nWorkspacesChanged() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _loadDefaultStylesheet() {
|
||||||
|
if (!sessionMode.isPrimary)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let stylesheet = global.datadir + '/theme/' + sessionMode.stylesheetName;
|
||||||
|
if (_defaultCssStylesheet == stylesheet)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_defaultCssStylesheet = stylesheet;
|
||||||
|
loadTheme();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getThemeStylesheet:
|
* getThemeStylesheet:
|
||||||
*
|
*
|
||||||
@ -454,91 +479,6 @@ function notifyError(msg, details) {
|
|||||||
notify(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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function encapsulates hacks to make certain global keybindings
|
|
||||||
// work even when we are in one of our modes where global keybindings
|
|
||||||
// are disabled with a global grab. (When there is a global grab, then
|
|
||||||
// all key events will be delivered to the stage, so ::captured-event
|
|
||||||
// on the stage can be used for global keybindings.)
|
|
||||||
function _globalKeyPressHandler(actor, event) {
|
|
||||||
if (modalCount == 0)
|
|
||||||
return false;
|
|
||||||
if (event.type() != Clutter.EventType.KEY_PRESS)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!sessionMode.allowKeybindingsWhenModal) {
|
|
||||||
if (modalCount > (overview.visible ? 1 : 0))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let symbol = event.get_key_symbol();
|
|
||||||
let keyCode = event.get_key_code();
|
|
||||||
let ignoredModifiers = global.display.get_ignored_modifier_mask();
|
|
||||||
let modifierState = event.get_state() & ~ignoredModifiers;
|
|
||||||
|
|
||||||
// This relies on the fact that Clutter.ModifierType is the same as Gdk.ModifierType
|
|
||||||
let action = global.display.get_keybinding_action(keyCode, modifierState);
|
|
||||||
|
|
||||||
if (action == Meta.KeyBindingAction.SWITCH_PANELS) {
|
|
||||||
ctrlAltTabManager.popup(modifierState & Clutter.ModifierType.SHIFT_MASK,
|
|
||||||
modifierState);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (action) {
|
|
||||||
// left/right would effectively act as synonyms for up/down if we enabled them;
|
|
||||||
// but that could be considered confusing; we also disable them in the main view.
|
|
||||||
//
|
|
||||||
// case Meta.KeyBindingAction.WORKSPACE_LEFT:
|
|
||||||
// if (!sessionMode.hasWorkspaces)
|
|
||||||
// return false;
|
|
||||||
//
|
|
||||||
// wm.actionMoveWorkspaceLeft();
|
|
||||||
// return true;
|
|
||||||
// case Meta.KeyBindingAction.WORKSPACE_RIGHT:
|
|
||||||
// if (!sessionMode.hasWorkspaces)
|
|
||||||
// return false;
|
|
||||||
//
|
|
||||||
// wm.actionMoveWorkspaceRight();
|
|
||||||
// return true;
|
|
||||||
case Meta.KeyBindingAction.WORKSPACE_UP:
|
|
||||||
if (!sessionMode.hasWorkspaces)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
wm.actionMoveWorkspace(Meta.MotionDirection.UP);
|
|
||||||
return true;
|
|
||||||
case Meta.KeyBindingAction.WORKSPACE_DOWN:
|
|
||||||
if (!sessionMode.hasWorkspaces)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
wm.actionMoveWorkspace(Meta.MotionDirection.DOWN);
|
|
||||||
return true;
|
|
||||||
case Meta.KeyBindingAction.PANEL_RUN_DIALOG:
|
|
||||||
case Meta.KeyBindingAction.COMMAND_2:
|
|
||||||
if (!sessionMode.hasRunDialog)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
openRunDialog();
|
|
||||||
return true;
|
|
||||||
case Meta.KeyBindingAction.PANEL_MAIN_MENU:
|
|
||||||
case Meta.KeyBindingAction.OVERLAY_KEY:
|
|
||||||
overview.hide();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _findModal(actor) {
|
function _findModal(actor) {
|
||||||
for (let i = 0; i < modalActorFocusStack.length; i++) {
|
for (let i = 0; i < modalActorFocusStack.length; i++) {
|
||||||
if (modalActorFocusStack[i].actor == actor)
|
if (modalActorFocusStack[i].actor == actor)
|
||||||
@ -547,14 +487,10 @@ function _findModal(actor) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isInModalStack(actor) {
|
|
||||||
return _findModal(actor) != -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pushModal:
|
* pushModal:
|
||||||
* @actor: #ClutterActor which will be given keyboard focus
|
* @actor: #ClutterActor which will be given keyboard focus
|
||||||
* @timestamp: optional timestamp
|
* @params: optional parameters
|
||||||
*
|
*
|
||||||
* Ensure we are in a mode where all keyboard and mouse input goes to
|
* Ensure we are in a mode where all keyboard and mouse input goes to
|
||||||
* the stage, and focus @actor. Multiple calls to this function act in
|
* the stage, and focus @actor. Multiple calls to this function act in
|
||||||
@ -565,21 +501,27 @@ function isInModalStack(actor) {
|
|||||||
* modal stack returns to this actor, reset the focus to the actor
|
* modal stack returns to this actor, reset the focus to the actor
|
||||||
* which was focused at the time pushModal() was invoked.
|
* which was focused at the time pushModal() was invoked.
|
||||||
*
|
*
|
||||||
* @timestamp is optionally used to associate the call with a specific user
|
* @params may be used to provide the following parameters:
|
||||||
* initiated event. If not provided then the value of
|
* - timestamp: used to associate the call with a specific user initiated
|
||||||
* global.get_current_time() is assumed.
|
* event. If not provided then the value of
|
||||||
|
* global.get_current_time() is assumed.
|
||||||
*
|
*
|
||||||
* @options: optional Meta.ModalOptions flags to indicate that the
|
* - options: Meta.ModalOptions flags to indicate that the pointer is
|
||||||
* pointer is alrady grabbed
|
* already grabbed
|
||||||
|
*
|
||||||
|
* - keybindingMode: used to set the current Shell.KeyBindingMode to filter
|
||||||
|
* global keybindings; the default of NONE will filter
|
||||||
|
* out all keybindings
|
||||||
*
|
*
|
||||||
* Returns: true iff we successfully acquired a grab or already had one
|
* Returns: true iff we successfully acquired a grab or already had one
|
||||||
*/
|
*/
|
||||||
function pushModal(actor, timestamp, options) {
|
function pushModal(actor, params) {
|
||||||
if (timestamp == undefined)
|
params = Params.parse(params, { timestamp: global.get_current_time(),
|
||||||
timestamp = global.get_current_time();
|
options: 0,
|
||||||
|
keybindingMode: Shell.KeyBindingMode.NONE });
|
||||||
|
|
||||||
if (modalCount == 0) {
|
if (modalCount == 0) {
|
||||||
if (!global.begin_modal(timestamp, options ? options : 0)) {
|
if (!global.begin_modal(params.timestamp, params.options)) {
|
||||||
log('pushModal: invocation of begin_modal failed');
|
log('pushModal: invocation of begin_modal failed');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -594,20 +536,23 @@ function pushModal(actor, timestamp, options) {
|
|||||||
if (index >= 0)
|
if (index >= 0)
|
||||||
popModal(actor);
|
popModal(actor);
|
||||||
});
|
});
|
||||||
let curFocus = global.stage.get_key_focus();
|
|
||||||
let curFocusDestroyId;
|
let prevFocus = global.stage.get_key_focus();
|
||||||
if (curFocus != null) {
|
let prevFocusDestroyId;
|
||||||
curFocusDestroyId = curFocus.connect('destroy', function() {
|
if (prevFocus != null) {
|
||||||
|
prevFocusDestroyId = prevFocus.connect('destroy', function() {
|
||||||
let index = _findModal(actor);
|
let index = _findModal(actor);
|
||||||
if (index >= 0)
|
if (index >= 0)
|
||||||
modalActorFocusStack[index].actor = null;
|
modalActorFocusStack[index].prevFocus = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
modalActorFocusStack.push({ actor: actor,
|
modalActorFocusStack.push({ actor: actor,
|
||||||
focus: curFocus,
|
|
||||||
destroyId: actorDestroyId,
|
destroyId: actorDestroyId,
|
||||||
focusDestroyId: curFocusDestroyId });
|
prevFocus: prevFocus,
|
||||||
|
prevFocusDestroyId: prevFocusDestroyId,
|
||||||
|
keybindingMode: keybindingMode });
|
||||||
|
|
||||||
|
keybindingMode = params.keybindingMode;
|
||||||
global.stage.set_key_focus(actor);
|
global.stage.set_key_focus(actor);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -634,6 +579,7 @@ function popModal(actor, timestamp) {
|
|||||||
global.stage.set_key_focus(null);
|
global.stage.set_key_focus(null);
|
||||||
global.end_modal(timestamp);
|
global.end_modal(timestamp);
|
||||||
global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
|
global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
|
||||||
|
keybindingMode = Shell.KeyBindingMode.NORMAL;
|
||||||
|
|
||||||
throw new Error('incorrect pop');
|
throw new Error('incorrect pop');
|
||||||
}
|
}
|
||||||
@ -644,17 +590,34 @@ function popModal(actor, timestamp) {
|
|||||||
record.actor.disconnect(record.destroyId);
|
record.actor.disconnect(record.destroyId);
|
||||||
|
|
||||||
if (focusIndex == modalActorFocusStack.length - 1) {
|
if (focusIndex == modalActorFocusStack.length - 1) {
|
||||||
if (record.focus)
|
if (record.prevFocus)
|
||||||
record.focus.disconnect(record.focusDestroyId);
|
record.prevFocus.disconnect(record.prevFocusDestroyId);
|
||||||
global.stage.set_key_focus(record.focus);
|
keybindingMode = record.keybindingMode;
|
||||||
|
global.stage.set_key_focus(record.prevFocus);
|
||||||
} else {
|
} else {
|
||||||
|
// If we have:
|
||||||
|
// global.stage.set_focus(a);
|
||||||
|
// Main.pushModal(b);
|
||||||
|
// Main.pushModal(c);
|
||||||
|
// Main.pushModal(d);
|
||||||
|
//
|
||||||
|
// then we have the stack:
|
||||||
|
// [{ prevFocus: a, actor: b },
|
||||||
|
// { prevFocus: b, actor: c },
|
||||||
|
// { prevFocus: c, actor: d }]
|
||||||
|
//
|
||||||
|
// When actor c is destroyed/popped, if we only simply remove the
|
||||||
|
// record, then the focus stack will be [a, c], rather than the correct
|
||||||
|
// [a, b]. Shift the focus stack up before removing the record to ensure
|
||||||
|
// that we get the correct result.
|
||||||
let t = modalActorFocusStack[modalActorFocusStack.length - 1];
|
let t = modalActorFocusStack[modalActorFocusStack.length - 1];
|
||||||
if (t.focus)
|
if (t.prevFocus)
|
||||||
t.focus.disconnect(t.focusDestroyId);
|
t.prevFocus.disconnect(t.prevFocusDestroyId);
|
||||||
// Remove from the middle, shift the focus chain up
|
// Remove from the middle, shift the focus chain up
|
||||||
for (let i = modalActorFocusStack.length - 1; i > focusIndex; i--) {
|
for (let i = modalActorFocusStack.length - 1; i > focusIndex; i--) {
|
||||||
modalActorFocusStack[i].focus = modalActorFocusStack[i - 1].focus;
|
modalActorFocusStack[i].prevFocus = modalActorFocusStack[i - 1].prevFocus;
|
||||||
modalActorFocusStack[i].focusDestroyId = modalActorFocusStack[i - 1].focusDestroyId;
|
modalActorFocusStack[i].prevFocusDestroyId = modalActorFocusStack[i - 1].prevFocusDestroyId;
|
||||||
|
modalActorFocusStack[i].keybindingMode = modalActorFocusStack[i - 1].keybindingMode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
modalActorFocusStack.splice(focusIndex, 1);
|
modalActorFocusStack.splice(focusIndex, 1);
|
||||||
@ -665,6 +628,7 @@ function popModal(actor, timestamp) {
|
|||||||
global.end_modal(timestamp);
|
global.end_modal(timestamp);
|
||||||
global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
|
global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
|
||||||
Meta.enable_unredirect_for_screen(global.screen);
|
Meta.enable_unredirect_for_screen(global.screen);
|
||||||
|
keybindingMode = Shell.KeyBindingMode.NORMAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createLookingGlass() {
|
function createLookingGlass() {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -20,7 +20,6 @@ const Main = imports.ui.main;
|
|||||||
const Tweener = imports.ui.tweener;
|
const Tweener = imports.ui.tweener;
|
||||||
|
|
||||||
const OPEN_AND_CLOSE_TIME = 0.1;
|
const OPEN_AND_CLOSE_TIME = 0.1;
|
||||||
const FADE_IN_BUTTONS_TIME = 0.33;
|
|
||||||
const FADE_OUT_DIALOG_TIME = 1.0;
|
const FADE_OUT_DIALOG_TIME = 1.0;
|
||||||
|
|
||||||
const State = {
|
const State = {
|
||||||
@ -38,10 +37,12 @@ const ModalDialog = new Lang.Class({
|
|||||||
params = Params.parse(params, { shellReactive: false,
|
params = Params.parse(params, { shellReactive: false,
|
||||||
styleClass: null,
|
styleClass: null,
|
||||||
parentActor: Main.uiGroup,
|
parentActor: Main.uiGroup,
|
||||||
|
keybindingMode: Shell.KeyBindingMode.SYSTEM_MODAL,
|
||||||
shouldFadeIn: true });
|
shouldFadeIn: true });
|
||||||
|
|
||||||
this.state = State.CLOSED;
|
this.state = State.CLOSED;
|
||||||
this._hasModal = false;
|
this._hasModal = false;
|
||||||
|
this._keybindingMode = params.keybindingMode;
|
||||||
this._shellReactive = params.shellReactive;
|
this._shellReactive = params.shellReactive;
|
||||||
this._shouldFadeIn = params.shouldFadeIn;
|
this._shouldFadeIn = params.shouldFadeIn;
|
||||||
|
|
||||||
@ -57,7 +58,9 @@ const ModalDialog = new Lang.Class({
|
|||||||
|
|
||||||
this._group.connect('destroy', Lang.bind(this, this._onGroupDestroy));
|
this._group.connect('destroy', Lang.bind(this, this._onGroupDestroy));
|
||||||
|
|
||||||
this._actionKeys = {};
|
this._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._group.connect('key-release-event', Lang.bind(this, this._onKeyReleaseEvent));
|
||||||
|
|
||||||
this._backgroundBin = new St.Bin();
|
this._backgroundBin = new St.Bin();
|
||||||
@ -79,7 +82,7 @@ const ModalDialog = new Lang.Class({
|
|||||||
let stack = new Shell.Stack();
|
let stack = new Shell.Stack();
|
||||||
this._backgroundBin.child = stack;
|
this._backgroundBin.child = stack;
|
||||||
|
|
||||||
this._eventBlocker = new Clutter.Group({ reactive: true });
|
this._eventBlocker = new Clutter.Actor({ reactive: true });
|
||||||
stack.add_actor(this._eventBlocker);
|
stack.add_actor(this._eventBlocker);
|
||||||
stack.add_actor(this.dialogLayout);
|
stack.add_actor(this.dialogLayout);
|
||||||
} else {
|
} else {
|
||||||
@ -94,10 +97,9 @@ const ModalDialog = new Lang.Class({
|
|||||||
x_align: St.Align.MIDDLE,
|
x_align: St.Align.MIDDLE,
|
||||||
y_align: St.Align.START });
|
y_align: St.Align.START });
|
||||||
|
|
||||||
this._buttonLayout = new St.BoxLayout({ style_class: 'modal-dialog-button-box',
|
this.buttonLayout = new St.BoxLayout({ style_class: 'modal-dialog-button-box',
|
||||||
visible: false,
|
vertical: false });
|
||||||
vertical: false });
|
this.dialogLayout.add(this.buttonLayout,
|
||||||
this.dialogLayout.add(this._buttonLayout,
|
|
||||||
{ expand: true,
|
{ expand: true,
|
||||||
x_align: St.Align.MIDDLE,
|
x_align: St.Align.MIDDLE,
|
||||||
y_align: St.Align.END });
|
y_align: St.Align.END });
|
||||||
@ -112,37 +114,16 @@ const ModalDialog = new Lang.Class({
|
|||||||
this._group.destroy();
|
this._group.destroy();
|
||||||
},
|
},
|
||||||
|
|
||||||
setActionKey: function(key, action) {
|
clearButtons: function() {
|
||||||
this._actionKeys[key] = action;
|
this.buttonLayout.destroy_all_children();
|
||||||
|
this._buttonKeys = {};
|
||||||
},
|
},
|
||||||
|
|
||||||
setButtons: function(buttons) {
|
setButtons: function(buttons) {
|
||||||
let hadChildren = this._buttonLayout.get_children() > 0;
|
this.clearButtons();
|
||||||
|
|
||||||
this._buttonLayout.destroy_all_children();
|
|
||||||
this._actionKeys = {};
|
|
||||||
|
|
||||||
this._buttonLayout.visible = (buttons.length > 0);
|
|
||||||
|
|
||||||
for (let i = 0; i < buttons.length; i++) {
|
for (let i = 0; i < buttons.length; i++) {
|
||||||
let buttonInfo = buttons[i];
|
let buttonInfo = buttons[i];
|
||||||
let label = buttonInfo['label'];
|
|
||||||
let action = buttonInfo['action'];
|
|
||||||
let key = buttonInfo['key'];
|
|
||||||
let isDefault = buttonInfo['default'];
|
|
||||||
|
|
||||||
if (isDefault && !key) {
|
|
||||||
this._actionKeys[Clutter.KEY_KP_Enter] = action;
|
|
||||||
this._actionKeys[Clutter.KEY_ISO_Enter] = action;
|
|
||||||
key = Clutter.KEY_Return;
|
|
||||||
}
|
|
||||||
|
|
||||||
buttonInfo.button = new St.Button({ style_class: 'modal-dialog-button',
|
|
||||||
reactive: true,
|
|
||||||
can_focus: true,
|
|
||||||
label: label });
|
|
||||||
if (isDefault)
|
|
||||||
buttonInfo.button.add_style_pseudo_class('default');
|
|
||||||
|
|
||||||
let x_alignment;
|
let x_alignment;
|
||||||
if (buttons.length == 1)
|
if (buttons.length == 1)
|
||||||
@ -154,43 +135,72 @@ const ModalDialog = new Lang.Class({
|
|||||||
else
|
else
|
||||||
x_alignment = St.Align.MIDDLE;
|
x_alignment = St.Align.MIDDLE;
|
||||||
|
|
||||||
if (!this._initialKeyFocusDestroyId)
|
this.addButton(buttonInfo, { expand: true,
|
||||||
this._initialKeyFocus = buttonInfo.button;
|
x_fill: false,
|
||||||
this._buttonLayout.add(buttonInfo.button,
|
y_fill: false,
|
||||||
{ expand: true,
|
x_align: x_alignment,
|
||||||
x_fill: false,
|
y_align: St.Align.MIDDLE });
|
||||||
y_fill: false,
|
|
||||||
x_align: x_alignment,
|
|
||||||
y_align: St.Align.MIDDLE });
|
|
||||||
|
|
||||||
buttonInfo.button.connect('clicked', action);
|
|
||||||
|
|
||||||
if (key)
|
|
||||||
this._actionKeys[key] = action;
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Fade in buttons if there weren't any before
|
addButton: function(buttonInfo, layoutInfo) {
|
||||||
if (!hadChildren && buttons.length > 0) {
|
let label = buttonInfo['label'];
|
||||||
this._buttonLayout.opacity = 0;
|
let action = buttonInfo['action'];
|
||||||
Tweener.addTween(this._buttonLayout,
|
let key = buttonInfo['key'];
|
||||||
{ opacity: 255,
|
let isDefault = buttonInfo['default'];
|
||||||
time: FADE_IN_BUTTONS_TIME,
|
|
||||||
transition: 'easeOutQuad',
|
|
||||||
onComplete: Lang.bind(this, function() {
|
|
||||||
this.emit('buttons-set');
|
|
||||||
})
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.emit('buttons-set');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let keys;
|
||||||
|
|
||||||
|
if (key)
|
||||||
|
keys = [key];
|
||||||
|
else if (isDefault)
|
||||||
|
keys = [Clutter.KEY_Return, Clutter.KEY_KP_Enter, Clutter.KEY_ISO_Enter];
|
||||||
|
else
|
||||||
|
keys = [];
|
||||||
|
|
||||||
|
let button = new St.Button({ style_class: 'modal-dialog-button',
|
||||||
|
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
|
||||||
|
reactive: true,
|
||||||
|
can_focus: true,
|
||||||
|
label: label });
|
||||||
|
button.connect('clicked', action);
|
||||||
|
|
||||||
|
buttonInfo['button'] = button;
|
||||||
|
|
||||||
|
if (isDefault)
|
||||||
|
button.add_style_pseudo_class('default');
|
||||||
|
|
||||||
|
if (!this._initialKeyFocusDestroyId)
|
||||||
|
this._initialKeyFocus = button;
|
||||||
|
|
||||||
|
for (let i in keys)
|
||||||
|
this._buttonKeys[keys[i]] = buttonInfo;
|
||||||
|
|
||||||
|
this.buttonLayout.add(button, layoutInfo);
|
||||||
|
|
||||||
|
return button;
|
||||||
|
},
|
||||||
|
|
||||||
|
_onKeyPressEvent: function(object, event) {
|
||||||
|
this._pressedKey = event.get_key_symbol();
|
||||||
},
|
},
|
||||||
|
|
||||||
_onKeyReleaseEvent: function(object, event) {
|
_onKeyReleaseEvent: function(object, event) {
|
||||||
let symbol = event.get_key_symbol();
|
let pressedKey = this._pressedKey;
|
||||||
let action = this._actionKeys[symbol];
|
this._pressedKey = null;
|
||||||
|
|
||||||
if (action) {
|
let symbol = event.get_key_symbol();
|
||||||
|
if (symbol != pressedKey)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let buttonInfo = this._buttonKeys[symbol];
|
||||||
|
if (!buttonInfo)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let button = buttonInfo['button'];
|
||||||
|
let action = buttonInfo['action'];
|
||||||
|
|
||||||
|
if (action && button.reactive) {
|
||||||
action();
|
action();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -243,7 +253,7 @@ const ModalDialog = new Lang.Class({
|
|||||||
if (this.state == State.OPENED || this.state == State.OPENING)
|
if (this.state == State.OPENED || this.state == State.OPENING)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (!this.pushModal(timestamp))
|
if (!this.pushModal({ timestamp: timestamp }))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
this._fadeOpen(onPrimary);
|
this._fadeOpen(onPrimary);
|
||||||
@ -266,6 +276,7 @@ const ModalDialog = new Lang.Class({
|
|||||||
function() {
|
function() {
|
||||||
this.state = State.CLOSED;
|
this.state = State.CLOSED;
|
||||||
this._group.hide();
|
this._group.hide();
|
||||||
|
this.emit('closed');
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -293,7 +304,8 @@ const ModalDialog = new Lang.Class({
|
|||||||
pushModal: function (timestamp) {
|
pushModal: function (timestamp) {
|
||||||
if (this._hasModal)
|
if (this._hasModal)
|
||||||
return true;
|
return true;
|
||||||
if (!Main.pushModal(this._group, timestamp))
|
if (!Main.pushModal(this._group, { timestamp: timestamp,
|
||||||
|
keybindingMode: this._keybindingMode }))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
this._hasModal = true;
|
this._hasModal = true;
|
||||||
|
@ -103,6 +103,126 @@ const STANDARD_TRAY_ICON_IMPLEMENTATIONS = {
|
|||||||
'ibus-ui-gtk': 'keyboard'
|
'ibus-ui-gtk': 'keyboard'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const NotificationGenericPolicy = new Lang.Class({
|
||||||
|
Name: 'NotificationGenericPolicy',
|
||||||
|
Extends: MessageTray.NotificationPolicy,
|
||||||
|
|
||||||
|
_init: function() {
|
||||||
|
// Don't chain to parent, it would try setting
|
||||||
|
// our properties to the defaults
|
||||||
|
|
||||||
|
this.id = 'generic';
|
||||||
|
|
||||||
|
this._masterSettings = new Gio.Settings({ schema: 'org.gnome.desktop.notifications' });
|
||||||
|
this._masterSettings.connect('changed', Lang.bind(this, this._changed));
|
||||||
|
},
|
||||||
|
|
||||||
|
store: function() { },
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
|
this._masterSettings.run_dispose();
|
||||||
|
},
|
||||||
|
|
||||||
|
_changed: function(settings, key) {
|
||||||
|
this.emit('policy-changed', key);
|
||||||
|
},
|
||||||
|
|
||||||
|
get enable() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
get enableSound() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
get showBanners() {
|
||||||
|
return this._masterSettings.get_boolean('show-banners');
|
||||||
|
},
|
||||||
|
|
||||||
|
get forceExpanded() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
get showInLockScreen() {
|
||||||
|
return this._masterSettings.get_boolean('show-in-lock-screen');
|
||||||
|
},
|
||||||
|
|
||||||
|
get detailsInLockScreen() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const NotificationApplicationPolicy = new Lang.Class({
|
||||||
|
Name: 'NotificationApplicationPolicy',
|
||||||
|
Extends: MessageTray.NotificationPolicy,
|
||||||
|
|
||||||
|
_init: function(id) {
|
||||||
|
// Don't chain to parent, it would try setting
|
||||||
|
// our properties to the defaults
|
||||||
|
|
||||||
|
this.id = id;
|
||||||
|
this._canonicalId = this._canonicalizeId(id)
|
||||||
|
|
||||||
|
this._masterSettings = new Gio.Settings({ schema: 'org.gnome.desktop.notifications' });
|
||||||
|
this._settings = new Gio.Settings({ schema: 'org.gnome.desktop.notifications.application',
|
||||||
|
path: '/org/gnome/desktop/notifications/application/' + this._canonicalId + '/' });
|
||||||
|
|
||||||
|
this._masterSettings.connect('changed', Lang.bind(this, this._changed));
|
||||||
|
this._settings.connect('changed', Lang.bind(this, this._changed));
|
||||||
|
},
|
||||||
|
|
||||||
|
store: function() {
|
||||||
|
this._settings.set_string('application-id', this.id + '.desktop');
|
||||||
|
|
||||||
|
let apps = this._masterSettings.get_strv('application-children');
|
||||||
|
if (apps.indexOf(this._canonicalId) < 0) {
|
||||||
|
apps.push(this._canonicalId);
|
||||||
|
this._masterSettings.set_strv('application-children', apps);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
|
this._masterSettings.run_dispose();
|
||||||
|
this._settings.run_dispose();
|
||||||
|
},
|
||||||
|
|
||||||
|
_changed: function(settings, key) {
|
||||||
|
this.emit('policy-changed', key);
|
||||||
|
},
|
||||||
|
|
||||||
|
_canonicalizeId: function(id) {
|
||||||
|
// Keys are restricted to lowercase alphanumeric characters and dash,
|
||||||
|
// and two dashes cannot be in succession
|
||||||
|
return id.toLowerCase().replace(/[^a-z0-9\-]/g, '-').replace(/--+/g, '-');
|
||||||
|
},
|
||||||
|
|
||||||
|
get enable() {
|
||||||
|
return this._settings.get_boolean('enable');
|
||||||
|
},
|
||||||
|
|
||||||
|
get enableSound() {
|
||||||
|
return this._settings.get_boolean('enable-sound-alerts');
|
||||||
|
},
|
||||||
|
|
||||||
|
get showBanners() {
|
||||||
|
return this._masterSettings.get_boolean('show-banners') &&
|
||||||
|
this._settings.get_boolean('show-banners');
|
||||||
|
},
|
||||||
|
|
||||||
|
get forceExpanded() {
|
||||||
|
return this._settings.get_boolean('force-expanded');
|
||||||
|
},
|
||||||
|
|
||||||
|
get showInLockScreen() {
|
||||||
|
return this._masterSettings.get_boolean('show-in-lock-screen') &&
|
||||||
|
this._settings.get_boolean('show-in-lock-screen');
|
||||||
|
},
|
||||||
|
|
||||||
|
get detailsInLockScreen() {
|
||||||
|
return this._settings.get_boolean('details-in-lock-screen');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const NotificationDaemon = new Lang.Class({
|
const NotificationDaemon = new Lang.Class({
|
||||||
Name: 'NotificationDaemon',
|
Name: 'NotificationDaemon',
|
||||||
|
|
||||||
@ -124,45 +244,45 @@ const NotificationDaemon = new Lang.Class({
|
|||||||
Main.overview.connect('hidden',
|
Main.overview.connect('hidden',
|
||||||
Lang.bind(this, this._onFocusAppChanged));
|
Lang.bind(this, this._onFocusAppChanged));
|
||||||
|
|
||||||
this._trayManager.manage_stage(global.stage, Main.messageTray.actor);
|
this._trayManager.manage_screen(global.screen, Main.messageTray.actor);
|
||||||
},
|
},
|
||||||
|
|
||||||
_iconForNotificationData: function(icon, hints) {
|
_imageForNotificationData: function(hints) {
|
||||||
// If an icon is not specified, we use 'image-data' or 'image-path' hint for an icon
|
if (hints['image-data']) {
|
||||||
// and don't show a large image. There are currently many applications that use
|
|
||||||
// notify_notification_set_icon_from_pixbuf() from libnotify, which in turn sets
|
|
||||||
// the 'image-data' hint. These applications don't typically pass in 'app_icon'
|
|
||||||
// argument to Notify() and actually expect the pixbuf to be shown as an icon.
|
|
||||||
// So the logic here does the right thing for this case. If both an icon and either
|
|
||||||
// one of 'image-data' or 'image-path' are specified, we show both an icon and
|
|
||||||
// a large image.
|
|
||||||
if (icon) {
|
|
||||||
if (icon.substr(0, 7) == 'file://')
|
|
||||||
return new Gio.FileIcon({ file: Gio.File.new_for_uri(icon) });
|
|
||||||
else if (icon[0] == '/') {
|
|
||||||
return new Gio.FileIcon({ file: Gio.File.new_for_path(icon) });
|
|
||||||
} else
|
|
||||||
return new Gio.ThemedIcon({ name: icon });
|
|
||||||
} else if (hints['image-data']) {
|
|
||||||
let [width, height, rowStride, hasAlpha,
|
let [width, height, rowStride, hasAlpha,
|
||||||
bitsPerSample, nChannels, data] = hints['image-data'];
|
bitsPerSample, nChannels, data] = hints['image-data'];
|
||||||
return Shell.util_create_pixbuf_from_data(data, GdkPixbuf.Colorspace.RGB, hasAlpha,
|
return Shell.util_create_pixbuf_from_data(data, GdkPixbuf.Colorspace.RGB, hasAlpha,
|
||||||
bitsPerSample, width, height, rowStride);
|
bitsPerSample, width, height, rowStride);
|
||||||
} else if (hints['image-path']) {
|
} else if (hints['image-path']) {
|
||||||
return new Gio.FileIcon({ file: Gio.File.new_for_path(hints['image-path']) });
|
return new Gio.FileIcon({ file: Gio.File.new_for_path(hints['image-path']) });
|
||||||
} else {
|
|
||||||
let stockIcon;
|
|
||||||
switch (hints.urgency) {
|
|
||||||
case Urgency.LOW:
|
|
||||||
case Urgency.NORMAL:
|
|
||||||
stockIcon = 'gtk-dialog-info';
|
|
||||||
break;
|
|
||||||
case Urgency.CRITICAL:
|
|
||||||
stockIcon = 'gtk-dialog-error';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return new Gio.ThemedIcon({ name: stockIcon });
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
_fallbackIconForNotificationData: function(hints) {
|
||||||
|
let stockIcon;
|
||||||
|
switch (hints.urgency) {
|
||||||
|
case Urgency.LOW:
|
||||||
|
case Urgency.NORMAL:
|
||||||
|
stockIcon = 'gtk-dialog-info';
|
||||||
|
break;
|
||||||
|
case Urgency.CRITICAL:
|
||||||
|
stockIcon = 'gtk-dialog-error';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return new Gio.ThemedIcon({ name: stockIcon });
|
||||||
|
},
|
||||||
|
|
||||||
|
_iconForNotificationData: function(icon) {
|
||||||
|
if (icon) {
|
||||||
|
if (icon.substr(0, 7) == 'file://')
|
||||||
|
return new Gio.FileIcon({ file: Gio.File.new_for_uri(icon) });
|
||||||
|
else if (icon[0] == '/')
|
||||||
|
return new Gio.FileIcon({ file: Gio.File.new_for_path(icon) });
|
||||||
|
else
|
||||||
|
return new Gio.ThemedIcon({ name: icon });
|
||||||
|
}
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
_lookupSource: function(title, pid, trayIcon) {
|
_lookupSource: function(title, pid, trayIcon) {
|
||||||
@ -213,7 +333,7 @@ const NotificationDaemon = new Lang.Class({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let source = new Source(title, pid, sender, trayIcon);
|
let source = new Source(title, pid, sender, trayIcon, ndata ? ndata.hints['desktop-entry'] : null);
|
||||||
source.setTransient(isForTransientNotification);
|
source.setTransient(isForTransientNotification);
|
||||||
|
|
||||||
if (!isForTransientNotification) {
|
if (!isForTransientNotification) {
|
||||||
@ -355,14 +475,8 @@ const NotificationDaemon = new Lang.Class({
|
|||||||
[ndata.id, ndata.icon, ndata.summary, ndata.body,
|
[ndata.id, ndata.icon, ndata.summary, ndata.body,
|
||||||
ndata.actions, ndata.hints, ndata.notification];
|
ndata.actions, ndata.hints, ndata.notification];
|
||||||
|
|
||||||
let gicon = this._iconForNotificationData(icon, hints);
|
|
||||||
let iconActor = new St.Icon({ gicon: gicon,
|
|
||||||
icon_size: MessageTray.NOTIFICATION_ICON_SIZE });
|
|
||||||
|
|
||||||
if (notification == null) {
|
if (notification == null) {
|
||||||
notification = new MessageTray.Notification(source, summary, body,
|
notification = new MessageTray.Notification(source);
|
||||||
{ icon: iconActor,
|
|
||||||
bannerMarkup: true });
|
|
||||||
ndata.notification = notification;
|
ndata.notification = notification;
|
||||||
notification.connect('destroy', Lang.bind(this,
|
notification.connect('destroy', Lang.bind(this,
|
||||||
function(n, reason) {
|
function(n, reason) {
|
||||||
@ -385,29 +499,38 @@ const NotificationDaemon = new Lang.Class({
|
|||||||
function(n, actionId) {
|
function(n, actionId) {
|
||||||
this._emitActionInvoked(ndata.id, actionId);
|
this._emitActionInvoked(ndata.id, actionId);
|
||||||
}));
|
}));
|
||||||
} else {
|
|
||||||
notification.update(summary, body, { icon: iconActor,
|
|
||||||
bannerMarkup: true,
|
|
||||||
clear: true });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We only display a large image if an icon is also specified.
|
// Mark music notifications so they can be shown in the screen shield
|
||||||
if (icon && (hints['image-data'] || hints['image-path'])) {
|
notification.isMusic = (ndata.hints['category'] == 'x-gnome.music');
|
||||||
let image = null;
|
|
||||||
if (hints['image-data']) {
|
let gicon = this._iconForNotificationData(icon, hints);
|
||||||
let [width, height, rowStride, hasAlpha,
|
let gimage = this._imageForNotificationData(hints);
|
||||||
bitsPerSample, nChannels, data] = hints['image-data'];
|
|
||||||
image = St.TextureCache.get_default().load_from_raw(data, hasAlpha,
|
let image = null;
|
||||||
width, height, rowStride, notification.IMAGE_SIZE);
|
|
||||||
} else if (hints['image-path']) {
|
// If an icon is not specified, we use 'image-data' or 'image-path' hint for an icon
|
||||||
image = St.TextureCache.get_default().load_uri_async(GLib.filename_to_uri(hints['image-path'], null),
|
// and don't show a large image. There are currently many applications that use
|
||||||
notification.IMAGE_SIZE,
|
// notify_notification_set_icon_from_pixbuf() from libnotify, which in turn sets
|
||||||
notification.IMAGE_SIZE);
|
// the 'image-data' hint. These applications don't typically pass in 'app_icon'
|
||||||
}
|
// argument to Notify() and actually expect the pixbuf to be shown as an icon.
|
||||||
notification.setImage(image);
|
// So the logic here does the right thing for this case. If both an icon and either
|
||||||
} else {
|
// one of 'image-data' or 'image-path' are specified, we show both an icon and
|
||||||
notification.unsetImage();
|
// a large image.
|
||||||
}
|
if (gicon && gimage)
|
||||||
|
image = new St.Icon({ gicon: gimage,
|
||||||
|
icon_size: notification.IMAGE_SIZE });
|
||||||
|
else if (!gicon && gimage)
|
||||||
|
gicon = gimage;
|
||||||
|
else if (!gicon)
|
||||||
|
gicon = this._fallbackIconForNotificationData(hints);
|
||||||
|
|
||||||
|
notification.update(summary, body, { gicon: gicon,
|
||||||
|
bannerMarkup: true,
|
||||||
|
clear: true,
|
||||||
|
soundFile: hints['sound-file'],
|
||||||
|
soundName: hints['sound-name'] });
|
||||||
|
notification.setImage(image);
|
||||||
|
|
||||||
if (actions.length) {
|
if (actions.length) {
|
||||||
notification.setUseActionIcons(hints['action-icons'] == true);
|
notification.setUseActionIcons(hints['action-icons'] == true);
|
||||||
@ -437,7 +560,7 @@ const NotificationDaemon = new Lang.Class({
|
|||||||
// of the 'transient' hint with hints['transient'] rather than hints.transient
|
// of the 'transient' hint with hints['transient'] rather than hints.transient
|
||||||
notification.setTransient(hints['transient'] == true);
|
notification.setTransient(hints['transient'] == true);
|
||||||
|
|
||||||
let sourceGIcon = source.useNotificationIcon ? this._iconForNotificationData(icon, hints) : null;
|
let sourceGIcon = source.useNotificationIcon ? gicon : null;
|
||||||
source.processNotification(notification, sourceGIcon);
|
source.processNotification(notification, sourceGIcon);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -461,7 +584,7 @@ const NotificationDaemon = new Lang.Class({
|
|||||||
// 'icon-multi',
|
// 'icon-multi',
|
||||||
'icon-static',
|
'icon-static',
|
||||||
'persistence',
|
'persistence',
|
||||||
// 'sound',
|
'sound',
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -517,12 +640,22 @@ const Source = new Lang.Class({
|
|||||||
Name: 'NotificationDaemonSource',
|
Name: 'NotificationDaemonSource',
|
||||||
Extends: MessageTray.Source,
|
Extends: MessageTray.Source,
|
||||||
|
|
||||||
_init: function(title, pid, sender, trayIcon) {
|
_init: function(title, pid, sender, trayIcon, appId) {
|
||||||
|
// Need to set the app before chaining up, so
|
||||||
|
// methods called from the parent constructor can find it
|
||||||
|
this.trayIcon = trayIcon;
|
||||||
|
this.pid = pid;
|
||||||
|
this.app = this._getApp(appId);
|
||||||
|
|
||||||
this.parent(title);
|
this.parent(title);
|
||||||
|
|
||||||
this.initialTitle = title;
|
this.initialTitle = title;
|
||||||
|
|
||||||
this.pid = pid;
|
if (this.app)
|
||||||
|
this.title = this.app.get_name();
|
||||||
|
else
|
||||||
|
this.useNotificationIcon = true;
|
||||||
|
|
||||||
if (sender)
|
if (sender)
|
||||||
this._nameWatcherId = Gio.DBus.session.watch_name(sender,
|
this._nameWatcherId = Gio.DBus.session.watch_name(sender,
|
||||||
Gio.BusNameWatcherFlags.NONE,
|
Gio.BusNameWatcherFlags.NONE,
|
||||||
@ -531,16 +664,19 @@ const Source = new Lang.Class({
|
|||||||
else
|
else
|
||||||
this._nameWatcherId = 0;
|
this._nameWatcherId = 0;
|
||||||
|
|
||||||
this._setApp();
|
|
||||||
if (this.app)
|
|
||||||
this.title = this.app.get_name();
|
|
||||||
else
|
|
||||||
this.useNotificationIcon = true;
|
|
||||||
|
|
||||||
this.trayIcon = trayIcon;
|
|
||||||
if (this.trayIcon) {
|
if (this.trayIcon) {
|
||||||
this._setSummaryIcon(this.trayIcon);
|
// Try again finding the app, using the WM_CLASS from the tray icon
|
||||||
this.useNotificationIcon = false;
|
this._setSummaryIcon(this.trayIcon);
|
||||||
|
this.useNotificationIcon = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_createPolicy: function() {
|
||||||
|
if (this.app) {
|
||||||
|
let id = this.app.get_id().replace(/\.desktop$/,'');
|
||||||
|
return new NotificationApplicationPolicy(id);
|
||||||
|
} else {
|
||||||
|
return new NotificationGenericPolicy();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -567,19 +703,17 @@ const Source = new Lang.Class({
|
|||||||
this.notify(notification);
|
this.notify(notification);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSummaryClick: function() {
|
handleSummaryClick: function(button) {
|
||||||
if (!this.trayIcon)
|
if (!this.trayIcon)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
let event = Clutter.get_current_event();
|
let event = Clutter.get_current_event();
|
||||||
if (event.type() != Clutter.EventType.BUTTON_RELEASE)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Left clicks are passed through only where there aren't unacknowledged
|
// Left clicks are passed through only where there aren't unacknowledged
|
||||||
// notifications, so it possible to open them in summary mode; right
|
// notifications, so it possible to open them in summary mode; right
|
||||||
// clicks are always forwarded, as the right click menu is not useful for
|
// clicks are always forwarded, as the right click menu is not useful for
|
||||||
// tray icons
|
// tray icons
|
||||||
if (event.get_button() == 1 &&
|
if (button == 1 &&
|
||||||
this.notifications.length > 0)
|
this.notifications.length > 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -592,7 +726,7 @@ const Source = new Lang.Class({
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
_getApp: function() {
|
_getApp: function(appId) {
|
||||||
let app;
|
let app;
|
||||||
|
|
||||||
app = Shell.WindowTracker.get_default().get_app_from_pid(this.pid);
|
app = Shell.WindowTracker.get_default().get_app_from_pid(this.pid);
|
||||||
@ -600,7 +734,13 @@ const Source = new Lang.Class({
|
|||||||
return app;
|
return app;
|
||||||
|
|
||||||
if (this.trayIcon) {
|
if (this.trayIcon) {
|
||||||
app = Shell.AppSystem.get_default().lookup_wmclass(this.trayIcon.wmclass);
|
app = Shell.AppSystem.get_default().lookup_wmclass(this.trayIcon.wm_class);
|
||||||
|
if (app != null)
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appId) {
|
||||||
|
app = Shell.AppSystem.get_default().lookup_app(appId + '.desktop');
|
||||||
if (app != null)
|
if (app != null)
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
@ -608,11 +748,11 @@ const Source = new Lang.Class({
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
_setApp: function() {
|
_setApp: function(appId) {
|
||||||
if (this.app)
|
if (this.app)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.app = this._getApp();
|
this.app = this._getApp(appId);
|
||||||
if (!this.app)
|
if (!this.app)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
179
js/ui/osdWindow.js
Normal file
179
js/ui/osdWindow.js
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
// -*- 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 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.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._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.layoutManager.addChrome(this.actor, { affectsInputRegion: false });
|
||||||
|
},
|
||||||
|
|
||||||
|
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);
|
||||||
|
let size = 110 * Math.max(1, scale);
|
||||||
|
|
||||||
|
this._box.set_size(size, size);
|
||||||
|
this._box.translation_y = monitor.height / 4;
|
||||||
|
|
||||||
|
this._icon.icon_size = size / 2;
|
||||||
|
}
|
||||||
|
});
|
@ -10,10 +10,13 @@ const St = imports.gi.St;
|
|||||||
const Shell = imports.gi.Shell;
|
const Shell = imports.gi.Shell;
|
||||||
const Gdk = imports.gi.Gdk;
|
const Gdk = imports.gi.Gdk;
|
||||||
|
|
||||||
|
const Background = imports.ui.background;
|
||||||
const Dash = imports.ui.dash;
|
const Dash = imports.ui.dash;
|
||||||
const DND = imports.ui.dnd;
|
const DND = imports.ui.dnd;
|
||||||
|
const LayoutManager = imports.ui.layout;
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
const MessageTray = imports.ui.messageTray;
|
const MessageTray = imports.ui.messageTray;
|
||||||
|
const OverviewControls = imports.ui.overviewControls;
|
||||||
const Panel = imports.ui.panel;
|
const Panel = imports.ui.panel;
|
||||||
const Params = imports.misc.params;
|
const Params = imports.misc.params;
|
||||||
const Tweener = imports.ui.tweener;
|
const Tweener = imports.ui.tweener;
|
||||||
@ -23,22 +26,14 @@ const WorkspaceThumbnail = imports.ui.workspaceThumbnail;
|
|||||||
// Time for initial animation going into Overview mode
|
// Time for initial animation going into Overview mode
|
||||||
const ANIMATION_TIME = 0.25;
|
const ANIMATION_TIME = 0.25;
|
||||||
|
|
||||||
// We split the screen vertically between the dash and the view selector.
|
// Must be less than ANIMATION_TIME, since we switch to
|
||||||
const DASH_SPLIT_FRACTION = 0.1;
|
// or from the overview completely after ANIMATION_TIME,
|
||||||
|
// and don't want the shading animation to get cut off
|
||||||
|
const SHADE_ANIMATION_TIME = .20;
|
||||||
|
|
||||||
const DND_WINDOW_SWITCH_TIMEOUT = 1250;
|
const DND_WINDOW_SWITCH_TIMEOUT = 1250;
|
||||||
|
|
||||||
const SwipeScrollDirection = {
|
const OVERVIEW_ACTIVATION_TIMEOUT = 0.5;
|
||||||
NONE: 0,
|
|
||||||
HORIZONTAL: 1,
|
|
||||||
VERTICAL: 2
|
|
||||||
};
|
|
||||||
|
|
||||||
const SwipeScrollResult = {
|
|
||||||
CANCEL: 0,
|
|
||||||
SWIPE: 1,
|
|
||||||
CLICK: 2
|
|
||||||
};
|
|
||||||
|
|
||||||
const ShellInfo = new Lang.Class({
|
const ShellInfo = new Lang.Class({
|
||||||
Name: 'ShellInfo',
|
Name: 'ShellInfo',
|
||||||
@ -57,7 +52,14 @@ const ShellInfo = new Lang.Class({
|
|||||||
this._source.destroy();
|
this._source.destroy();
|
||||||
},
|
},
|
||||||
|
|
||||||
setMessage: function(text, undoCallback, undoLabel) {
|
setMessage: function(text, options) {
|
||||||
|
options = Params.parse(options, { undoCallback: null,
|
||||||
|
forFeedback: false
|
||||||
|
});
|
||||||
|
|
||||||
|
let undoCallback = options.undoCallback;
|
||||||
|
let forFeedback = options.forFeedback;
|
||||||
|
|
||||||
if (this._source == null) {
|
if (this._source == null) {
|
||||||
this._source = new MessageTray.SystemNotificationSource();
|
this._source = new MessageTray.SystemNotificationSource();
|
||||||
this._source.connect('destroy', Lang.bind(this,
|
this._source.connect('destroy', Lang.bind(this,
|
||||||
@ -71,6 +73,7 @@ const ShellInfo = new Lang.Class({
|
|||||||
if (this._source.notifications.length == 0) {
|
if (this._source.notifications.length == 0) {
|
||||||
notification = new MessageTray.Notification(this._source, text, null);
|
notification = new MessageTray.Notification(this._source, text, null);
|
||||||
notification.setTransient(true);
|
notification.setTransient(true);
|
||||||
|
notification.setForFeedback(forFeedback);
|
||||||
} else {
|
} else {
|
||||||
notification = this._source.notifications[0];
|
notification = this._source.notifications[0];
|
||||||
notification.update(text, null, { clear: true });
|
notification.update(text, null, { clear: true });
|
||||||
@ -78,10 +81,8 @@ const ShellInfo = new Lang.Class({
|
|||||||
|
|
||||||
this._undoCallback = undoCallback;
|
this._undoCallback = undoCallback;
|
||||||
if (undoCallback) {
|
if (undoCallback) {
|
||||||
notification.addButton('system-undo',
|
notification.addButton('system-undo', _("Undo"));
|
||||||
undoLabel ? undoLabel : _("Undo"));
|
notification.connect('action-invoked', Lang.bind(this, this._onUndoClicked));
|
||||||
notification.connect('action-invoked',
|
|
||||||
Lang.bind(this, this._onUndoClicked));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._source.notify(notification);
|
this._source.notify(notification);
|
||||||
@ -93,6 +94,7 @@ const Overview = new Lang.Class({
|
|||||||
|
|
||||||
_init: function() {
|
_init: function() {
|
||||||
this._overviewCreated = false;
|
this._overviewCreated = false;
|
||||||
|
this._initCalled = false;
|
||||||
|
|
||||||
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
|
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
|
||||||
this._sessionUpdated();
|
this._sessionUpdated();
|
||||||
@ -107,59 +109,62 @@ const Overview = new Lang.Class({
|
|||||||
|
|
||||||
this._overviewCreated = true;
|
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
|
// hidden when displaying the overview, so we create a new
|
||||||
// one. Instances of this class share a single CoglTexture behind the
|
// one. Instances of this class share a single CoglTexture behind the
|
||||||
// scenes which allows us to show the background with different
|
// scenes which allows us to show the background with different
|
||||||
// rendering options without duplicating the texture data.
|
// rendering options without duplicating the texture data.
|
||||||
this._background = Meta.BackgroundActor.new_for_screen(global.screen);
|
let monitor = Main.layoutManager.primaryMonitor;
|
||||||
this._background.hide();
|
|
||||||
global.overlay_group.add_actor(this._background);
|
|
||||||
|
|
||||||
this._desktopFade = new St.Bin();
|
this._desktopFade = new St.Bin();
|
||||||
global.overlay_group.add_actor(this._desktopFade);
|
global.overlay_group.add_actor(this._desktopFade);
|
||||||
|
|
||||||
this._spacing = 0;
|
let layout = new Clutter.BinLayout();
|
||||||
|
this._stack = new Clutter.Actor({ layout_manager: layout });
|
||||||
|
this._stack.add_constraint(new LayoutManager.MonitorConstraint({ primary: true }));
|
||||||
|
|
||||||
/* Translators: This is the main view to select
|
/* Translators: This is the main view to select
|
||||||
activities. See also note for "Activities" string. */
|
activities. See also note for "Activities" string. */
|
||||||
this._group = new St.Widget({ name: 'overview',
|
this._overview = new St.BoxLayout({ name: 'overview',
|
||||||
accessible_name: _("Overview"),
|
accessible_name: _("Overview"),
|
||||||
reactive: true });
|
reactive: true,
|
||||||
this._group._delegate = this;
|
vertical: true,
|
||||||
this._group.connect('style-changed',
|
x_expand: true,
|
||||||
Lang.bind(this, function() {
|
y_expand: true });
|
||||||
let node = this._group.get_theme_node();
|
this._overview._delegate = this;
|
||||||
let spacing = node.get_length('spacing');
|
|
||||||
if (spacing != this._spacing) {
|
|
||||||
this._spacing = spacing;
|
|
||||||
this._relayout();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
this._scrollDirection = SwipeScrollDirection.NONE;
|
this._groupStack = new St.Widget({ layout_manager: new Clutter.BinLayout(),
|
||||||
this._scrollAdjustment = null;
|
x_expand: true, y_expand: true,
|
||||||
this._capturedEventId = 0;
|
clip_to_allocation: true });
|
||||||
this._buttonPressId = 0;
|
this._group = new St.BoxLayout({ name: 'overview-group',
|
||||||
|
reactive: true,
|
||||||
|
x_expand: true, y_expand: true });
|
||||||
|
this._groupStack.add_actor(this._group);
|
||||||
|
|
||||||
|
this._backgroundGroup = new Meta.BackgroundGroup();
|
||||||
|
global.overlay_group.add_child(this._backgroundGroup);
|
||||||
|
this._backgroundGroup.hide();
|
||||||
|
this._bgManagers = [];
|
||||||
|
|
||||||
|
this._activationTime = 0;
|
||||||
|
|
||||||
this.visible = false; // animating to overview, in overview, animating out
|
this.visible = false; // animating to overview, in overview, animating out
|
||||||
this._shown = false; // show() and not hide()
|
this._shown = false; // show() and not hide()
|
||||||
this._shownTemporarily = false; // showTemporarily() and not hideTemporarily()
|
|
||||||
this._modal = false; // have a modal grab
|
this._modal = false; // have a modal grab
|
||||||
this.animationInProgress = false;
|
this.animationInProgress = false;
|
||||||
this._hideInProgress = false;
|
this.visibleTarget = false;
|
||||||
|
|
||||||
// During transitions, we raise this to the top to avoid having the overview
|
// 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
|
// area be reactive; it causes too many issues such as double clicks on
|
||||||
// Dash elements, or mouseover handlers in the workspaces.
|
// Dash elements, or mouseover handlers in the workspaces.
|
||||||
this._coverPane = new Clutter.Rectangle({ opacity: 0,
|
this._coverPane = new Clutter.Rectangle({ opacity: 0,
|
||||||
reactive: true });
|
reactive: true });
|
||||||
this._group.add_actor(this._coverPane);
|
this._overview.add_actor(this._coverPane);
|
||||||
this._coverPane.connect('event', Lang.bind(this, function (actor, event) { return true; }));
|
this._coverPane.connect('event', Lang.bind(this, function (actor, event) { return true; }));
|
||||||
|
|
||||||
|
this._stack.hide();
|
||||||
this._group.hide();
|
this._stack.add_actor(this._overview);
|
||||||
global.overlay_group.add_actor(this._group);
|
global.overlay_group.add_actor(this._stack);
|
||||||
|
|
||||||
this._coverPane.hide();
|
this._coverPane.hide();
|
||||||
|
|
||||||
@ -171,11 +176,67 @@ const Overview = new Lang.Class({
|
|||||||
Main.xdndHandler.connect('drag-begin', Lang.bind(this, this._onDragBegin));
|
Main.xdndHandler.connect('drag-begin', Lang.bind(this, this._onDragBegin));
|
||||||
Main.xdndHandler.connect('drag-end', Lang.bind(this, this._onDragEnd));
|
Main.xdndHandler.connect('drag-end', Lang.bind(this, this._onDragEnd));
|
||||||
|
|
||||||
|
global.screen.connect('restacked', Lang.bind(this, this._onRestacked));
|
||||||
|
this._group.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
|
||||||
|
|
||||||
this._windowSwitchTimeoutId = 0;
|
this._windowSwitchTimeoutId = 0;
|
||||||
this._windowSwitchTimestamp = 0;
|
this._windowSwitchTimestamp = 0;
|
||||||
this._lastActiveWorkspaceIndex = -1;
|
this._lastActiveWorkspaceIndex = -1;
|
||||||
this._lastHoveredWindow = null;
|
this._lastHoveredWindow = null;
|
||||||
this._needsFakePointerEvent = false;
|
this._needsFakePointerEvent = false;
|
||||||
|
|
||||||
|
if (this._initCalled)
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateBackgrounds: function() {
|
||||||
|
for (let i = 0; i < this._bgManagers.length; i++)
|
||||||
|
this._bgManagers[i].destroy();
|
||||||
|
|
||||||
|
this._bgManagers = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < Main.layoutManager.monitors.length; i++) {
|
||||||
|
let bgManager = new Background.BackgroundManager({ container: this._backgroundGroup,
|
||||||
|
monitorIndex: i,
|
||||||
|
effects: Meta.BackgroundEffects.VIGNETTE });
|
||||||
|
this._bgManagers.push(bgManager);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_unshadeBackgrounds: function() {
|
||||||
|
let backgrounds = this._backgroundGroup.get_children();
|
||||||
|
for (let i = 0; i < backgrounds.length; i++) {
|
||||||
|
let background = backgrounds[i]._delegate;
|
||||||
|
|
||||||
|
Tweener.addTween(background,
|
||||||
|
{ brightness: 1.0,
|
||||||
|
time: SHADE_ANIMATION_TIME,
|
||||||
|
transition: 'easeOutQuad'
|
||||||
|
});
|
||||||
|
Tweener.addTween(background,
|
||||||
|
{ vignetteSharpness: 0.0,
|
||||||
|
time: SHADE_ANIMATION_TIME,
|
||||||
|
transition: 'easeOutQuad'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_shadeBackgrounds: function() {
|
||||||
|
let backgrounds = this._backgroundGroup.get_children();
|
||||||
|
for (let i = 0; i < backgrounds.length; i++) {
|
||||||
|
let background = backgrounds[i]._delegate;
|
||||||
|
|
||||||
|
Tweener.addTween(background,
|
||||||
|
{ brightness: 0.8,
|
||||||
|
time: SHADE_ANIMATION_TIME,
|
||||||
|
transition: 'easeOutQuad'
|
||||||
|
});
|
||||||
|
Tweener.addTween(background,
|
||||||
|
{ vignetteSharpness: 0.7,
|
||||||
|
time: SHADE_ANIMATION_TIME,
|
||||||
|
transition: 'easeOutQuad'
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_sessionUpdated: function() {
|
_sessionUpdated: function() {
|
||||||
@ -188,39 +249,65 @@ const Overview = new Lang.Class({
|
|||||||
// signal handlers and so forth. So we create them after
|
// signal handlers and so forth. So we create them after
|
||||||
// construction in this init() method.
|
// construction in this init() method.
|
||||||
init: function() {
|
init: function() {
|
||||||
|
this._initCalled = true;
|
||||||
|
|
||||||
if (this.isDummy)
|
if (this.isDummy)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this._shellInfo = new ShellInfo();
|
this._shellInfo = new ShellInfo();
|
||||||
|
|
||||||
|
// Add a clone of the panel to the overview so spacing and such is
|
||||||
|
// automatic
|
||||||
|
this._panelGhost = new St.Bin({ child: new Clutter.Clone({ source: Main.panel.actor }),
|
||||||
|
reactive: false,
|
||||||
|
opacity: 0 });
|
||||||
|
this._overview.add_actor(this._panelGhost);
|
||||||
|
|
||||||
this._searchEntry = new St.Entry({ name: 'searchEntry',
|
this._searchEntry = new St.Entry({ name: 'searchEntry',
|
||||||
/* Translators: this is the text displayed
|
/* Translators: this is the text displayed
|
||||||
in the search entry when no search is
|
in the search entry when no search is
|
||||||
active; it should not exceed ~30
|
active; it should not exceed ~30
|
||||||
characters. */
|
characters. */
|
||||||
hint_text: _("Type to search..."),
|
hint_text: _("Type to search…"),
|
||||||
track_hover: true,
|
track_hover: true,
|
||||||
can_focus: true });
|
can_focus: true });
|
||||||
this._group.add_actor(this._searchEntry);
|
this._searchEntryBin = new St.Bin({ child: this._searchEntry,
|
||||||
|
x_align: St.Align.MIDDLE });
|
||||||
|
this._overview.add_actor(this._searchEntryBin);
|
||||||
|
|
||||||
|
// Create controls
|
||||||
this._dash = new Dash.Dash();
|
this._dash = new Dash.Dash();
|
||||||
this._viewSelector = new ViewSelector.ViewSelector(this._searchEntry,
|
this._viewSelector = new ViewSelector.ViewSelector(this._searchEntry,
|
||||||
this._dash.showAppsButton);
|
this._dash.showAppsButton);
|
||||||
this._group.add_actor(this._viewSelector.actor);
|
this._thumbnailsBox = new WorkspaceThumbnail.ThumbnailsBox();
|
||||||
this._group.add_actor(this._dash.actor);
|
this._controls = new OverviewControls.ControlsManager(this._dash,
|
||||||
|
this._thumbnailsBox,
|
||||||
|
this._viewSelector);
|
||||||
|
|
||||||
|
this._controls.dashActor.x_align = Clutter.ActorAlign.START;
|
||||||
|
this._controls.dashActor.y_expand = true;
|
||||||
|
|
||||||
|
// Put the dash in a separate layer to allow content to be centered
|
||||||
|
this._groupStack.add_actor(this._controls.dashActor);
|
||||||
|
|
||||||
|
// Pack all the actors into the group
|
||||||
|
this._group.add_actor(this._controls.dashSpacer);
|
||||||
|
this._group.add(this._viewSelector.actor, { x_fill: true,
|
||||||
|
expand: true });
|
||||||
|
this._group.add_actor(this._controls.thumbnailsActor);
|
||||||
|
|
||||||
|
// Add our same-line elements after the search entry
|
||||||
|
this._overview.add(this._groupStack, { y_fill: true, expand: true });
|
||||||
|
|
||||||
|
this._stack.add_actor(this._controls.indicatorActor);
|
||||||
|
|
||||||
// TODO - recalculate everything when desktop size changes
|
// TODO - recalculate everything when desktop size changes
|
||||||
this._dash.actor.add_constraint(this._viewSelector.constrainHeight);
|
|
||||||
this.dashIconSize = this._dash.iconSize;
|
this.dashIconSize = this._dash.iconSize;
|
||||||
this._dash.connect('icon-size-changed',
|
this._dash.connect('icon-size-changed',
|
||||||
Lang.bind(this, function() {
|
Lang.bind(this, function() {
|
||||||
this.dashIconSize = this._dash.iconSize;
|
this.dashIconSize = this._dash.iconSize;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Translators: this is the name of the dock/favorites area on
|
|
||||||
// the left of the overview
|
|
||||||
Main.ctrlAltTabManager.addGroup(this._dash.actor, _("Dash"), 'user-bookmarks-symbolic');
|
|
||||||
|
|
||||||
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._relayout));
|
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._relayout));
|
||||||
this._relayout();
|
this._relayout();
|
||||||
},
|
},
|
||||||
@ -233,26 +320,35 @@ const Overview = new Lang.Class({
|
|||||||
this._viewSelector.removeSearchProvider(provider);
|
this._viewSelector.removeSearchProvider(provider);
|
||||||
},
|
},
|
||||||
|
|
||||||
setMessage: function(text, undoCallback, undoLabel) {
|
//
|
||||||
|
// options:
|
||||||
|
// - undoCallback (function): the callback to be called if undo support is needed
|
||||||
|
// - forFeedback (boolean): whether the message is for direct feedback of a user action
|
||||||
|
//
|
||||||
|
setMessage: function(text, options) {
|
||||||
if (this.isDummy)
|
if (this.isDummy)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this._shellInfo.setMessage(text, undoCallback, undoLabel);
|
this._shellInfo.setMessage(text, options);
|
||||||
},
|
},
|
||||||
|
|
||||||
_onDragBegin: function() {
|
_onDragBegin: function() {
|
||||||
|
this._inXdndDrag = true;
|
||||||
|
|
||||||
DND.addDragMonitor(this._dragMonitor);
|
DND.addDragMonitor(this._dragMonitor);
|
||||||
// Remember the workspace we started from
|
// Remember the workspace we started from
|
||||||
this._lastActiveWorkspaceIndex = global.screen.get_active_workspace_index();
|
this._lastActiveWorkspaceIndex = global.screen.get_active_workspace_index();
|
||||||
},
|
},
|
||||||
|
|
||||||
_onDragEnd: function(time) {
|
_onDragEnd: function(time) {
|
||||||
|
this._inXdndDrag = false;
|
||||||
|
|
||||||
// In case the drag was canceled while in the overview
|
// In case the drag was canceled while in the overview
|
||||||
// we have to go back to where we started and hide
|
// we have to go back to where we started and hide
|
||||||
// the overview
|
// the overview
|
||||||
if (this._shownTemporarily) {
|
if (this._shown) {
|
||||||
global.screen.get_workspace_by_index(this._lastActiveWorkspaceIndex).activate(time);
|
global.screen.get_workspace_by_index(this._lastActiveWorkspaceIndex).activate(time);
|
||||||
this.hideTemporarily();
|
this.hide();
|
||||||
}
|
}
|
||||||
this._resetWindowSwitchTimeout();
|
this._resetWindowSwitchTimeout();
|
||||||
this._lastHoveredWindow = null;
|
this._lastHoveredWindow = null;
|
||||||
@ -300,7 +396,7 @@ const Overview = new Lang.Class({
|
|||||||
this._needsFakePointerEvent = true;
|
this._needsFakePointerEvent = true;
|
||||||
Main.activateWindow(dragEvent.targetActor._delegate.metaWindow,
|
Main.activateWindow(dragEvent.targetActor._delegate.metaWindow,
|
||||||
this._windowSwitchTimestamp);
|
this._windowSwitchTimestamp);
|
||||||
this.hideTemporarily();
|
this.hide();
|
||||||
this._lastHoveredWindow = null;
|
this._lastHoveredWindow = null;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -308,159 +404,15 @@ const Overview = new Lang.Class({
|
|||||||
return DND.DragMotionResult.CONTINUE;
|
return DND.DragMotionResult.CONTINUE;
|
||||||
},
|
},
|
||||||
|
|
||||||
setScrollAdjustment: function(adjustment, direction) {
|
_onScrollEvent: function(actor, event) {
|
||||||
|
this.emit('scroll-event', event);
|
||||||
|
},
|
||||||
|
|
||||||
|
addAction: function(action) {
|
||||||
if (this.isDummy)
|
if (this.isDummy)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this._scrollAdjustment = adjustment;
|
this._overview.add_action(action);
|
||||||
if (this._scrollAdjustment == null)
|
|
||||||
this._scrollDirection = SwipeScrollDirection.NONE;
|
|
||||||
else
|
|
||||||
this._scrollDirection = direction;
|
|
||||||
},
|
|
||||||
|
|
||||||
_onButtonPress: function(actor, event) {
|
|
||||||
if (this._scrollDirection == SwipeScrollDirection.NONE
|
|
||||||
|| event.get_button() != 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
let [stageX, stageY] = event.get_coords();
|
|
||||||
this._dragStartX = this._dragX = stageX;
|
|
||||||
this._dragStartY = this._dragY = stageY;
|
|
||||||
this._dragStartValue = this._scrollAdjustment.value;
|
|
||||||
this._lastMotionTime = -1; // used to track "stopping" while swipe-scrolling
|
|
||||||
this._capturedEventId = global.stage.connect('captured-event',
|
|
||||||
Lang.bind(this, this._onCapturedEvent));
|
|
||||||
this.emit('swipe-scroll-begin');
|
|
||||||
},
|
|
||||||
|
|
||||||
_onCapturedEvent: function(actor, event) {
|
|
||||||
let stageX, stageY;
|
|
||||||
let threshold = Gtk.Settings.get_default().gtk_dnd_drag_threshold;
|
|
||||||
|
|
||||||
switch(event.type()) {
|
|
||||||
case Clutter.EventType.BUTTON_RELEASE:
|
|
||||||
[stageX, stageY] = event.get_coords();
|
|
||||||
|
|
||||||
// default to snapping back to the original value
|
|
||||||
let newValue = this._dragStartValue;
|
|
||||||
|
|
||||||
let minValue = this._scrollAdjustment.lower;
|
|
||||||
let maxValue = this._scrollAdjustment.upper - this._scrollAdjustment.page_size;
|
|
||||||
|
|
||||||
let direction;
|
|
||||||
if (this._scrollDirection == SwipeScrollDirection.HORIZONTAL) {
|
|
||||||
direction = stageX > this._dragStartX ? -1 : 1;
|
|
||||||
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
|
|
||||||
direction *= -1;
|
|
||||||
} else {
|
|
||||||
direction = stageY > this._dragStartY ? -1 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We default to scroll a full page size; both the first
|
|
||||||
// and the last page may be smaller though, so we need to
|
|
||||||
// adjust difference in those cases.
|
|
||||||
let difference = direction * this._scrollAdjustment.page_size;
|
|
||||||
if (this._dragStartValue + difference > maxValue)
|
|
||||||
difference = maxValue - this._dragStartValue;
|
|
||||||
else if (this._dragStartValue + difference < minValue)
|
|
||||||
difference = minValue - this._dragStartValue;
|
|
||||||
|
|
||||||
// If the user has moved more than half the scroll
|
|
||||||
// difference, we want to "settle" to the new value
|
|
||||||
// even if the user stops dragging rather "throws" by
|
|
||||||
// releasing during the drag.
|
|
||||||
let distance = this._dragStartValue - this._scrollAdjustment.value;
|
|
||||||
let noStop = Math.abs(distance / difference) > 0.5;
|
|
||||||
|
|
||||||
// We detect if the user is stopped by comparing the
|
|
||||||
// timestamp of the button release with the timestamp of
|
|
||||||
// the last motion. Experimentally, a difference of 0 or 1
|
|
||||||
// millisecond indicates that the mouse is in motion, a
|
|
||||||
// larger difference indicates that the mouse is stopped.
|
|
||||||
if ((this._lastMotionTime > 0 &&
|
|
||||||
this._lastMotionTime > event.get_time() - 2) ||
|
|
||||||
noStop) {
|
|
||||||
if (this._dragStartValue + difference >= minValue &&
|
|
||||||
this._dragStartValue + difference <= maxValue)
|
|
||||||
newValue += difference;
|
|
||||||
}
|
|
||||||
|
|
||||||
let result;
|
|
||||||
|
|
||||||
// See if the user has moved the mouse enough to trigger
|
|
||||||
// a drag
|
|
||||||
if (Math.abs(stageX - this._dragStartX) < threshold &&
|
|
||||||
Math.abs(stageY - this._dragStartY) < threshold) {
|
|
||||||
// no motion? It's a click!
|
|
||||||
result = SwipeScrollResult.CLICK;
|
|
||||||
this.emit('swipe-scroll-end', result);
|
|
||||||
} else {
|
|
||||||
if (newValue == this._dragStartValue)
|
|
||||||
result = SwipeScrollResult.CANCEL;
|
|
||||||
else
|
|
||||||
result = SwipeScrollResult.SWIPE;
|
|
||||||
|
|
||||||
// The event capture handler is disconnected
|
|
||||||
// while scrolling to the final position, so
|
|
||||||
// to avoid undesired prelights we raise
|
|
||||||
// the cover pane.
|
|
||||||
this._coverPane.raise_top();
|
|
||||||
this._coverPane.show();
|
|
||||||
|
|
||||||
Tweener.addTween(this._scrollAdjustment,
|
|
||||||
{ value: newValue,
|
|
||||||
time: ANIMATION_TIME,
|
|
||||||
transition: 'easeOutQuad',
|
|
||||||
onCompleteScope: this,
|
|
||||||
onComplete: function() {
|
|
||||||
this._coverPane.hide();
|
|
||||||
this.emit('swipe-scroll-end',
|
|
||||||
result);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
global.stage.disconnect(this._capturedEventId);
|
|
||||||
this._capturedEventId = 0;
|
|
||||||
|
|
||||||
return result != SwipeScrollResult.CLICK;
|
|
||||||
|
|
||||||
case Clutter.EventType.MOTION:
|
|
||||||
[stageX, stageY] = event.get_coords();
|
|
||||||
let dx = this._dragX - stageX;
|
|
||||||
let dy = this._dragY - stageY;
|
|
||||||
let primary = Main.layoutManager.primaryMonitor;
|
|
||||||
|
|
||||||
this._dragX = stageX;
|
|
||||||
this._dragY = stageY;
|
|
||||||
this._lastMotionTime = event.get_time();
|
|
||||||
|
|
||||||
// See if the user has moved the mouse enough to trigger
|
|
||||||
// a drag
|
|
||||||
if (Math.abs(stageX - this._dragStartX) < threshold &&
|
|
||||||
Math.abs(stageY - this._dragStartY) < threshold)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (this._scrollDirection == SwipeScrollDirection.HORIZONTAL) {
|
|
||||||
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
|
|
||||||
this._scrollAdjustment.value -= (dx / primary.width) * this._scrollAdjustment.page_size;
|
|
||||||
else
|
|
||||||
this._scrollAdjustment.value += (dx / primary.width) * this._scrollAdjustment.page_size;
|
|
||||||
} else {
|
|
||||||
this._scrollAdjustment.value += (dy / primary.height) * this._scrollAdjustment.page_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// Block enter/leave events to avoid prelights
|
|
||||||
// during swipe-scroll
|
|
||||||
case Clutter.EventType.ENTER:
|
|
||||||
case Clutter.EventType.LEAVE:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_getDesktopClone: function() {
|
_getDesktopClone: function() {
|
||||||
@ -485,42 +437,24 @@ const Overview = new Lang.Class({
|
|||||||
// when it is next shown.
|
// when it is next shown.
|
||||||
this.hide();
|
this.hide();
|
||||||
|
|
||||||
let primary = Main.layoutManager.primaryMonitor;
|
let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
|
||||||
let rtl = (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL);
|
|
||||||
|
|
||||||
let contentY = Main.panel.actor.height;
|
this._coverPane.set_position(0, workArea.y);
|
||||||
let contentHeight = primary.height - contentY - Main.messageTray.actor.height;
|
this._coverPane.set_size(workArea.width, workArea.height);
|
||||||
|
|
||||||
this._group.set_position(primary.x, primary.y);
|
this._updateBackgrounds();
|
||||||
this._group.set_size(primary.width, primary.height);
|
},
|
||||||
|
|
||||||
this._coverPane.set_position(0, contentY);
|
_onRestacked: function() {
|
||||||
this._coverPane.set_size(primary.width, contentHeight);
|
let stack = global.get_window_actors();
|
||||||
|
let stackIndices = {};
|
||||||
|
|
||||||
let searchWidth = this._searchEntry.get_width();
|
for (let i = 0; i < stack.length; i++) {
|
||||||
let searchHeight = this._searchEntry.get_height();
|
// Use the stable sequence for an integer to use as a hash key
|
||||||
let searchX = (primary.width - searchWidth) / 2;
|
stackIndices[stack[i].get_meta_window().get_stable_sequence()] = i;
|
||||||
let searchY = contentY + this._spacing;
|
|
||||||
|
|
||||||
let dashWidth = Math.round(DASH_SPLIT_FRACTION * primary.width);
|
|
||||||
let dashY = searchY + searchHeight + this._spacing;
|
|
||||||
let dashX;
|
|
||||||
if (rtl) {
|
|
||||||
this._dash.actor.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
|
|
||||||
dashX = primary.width;
|
|
||||||
} else {
|
|
||||||
dashX = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let viewX = rtl ? 0 : dashWidth + this._spacing;
|
this.emit('windows-restacked', stackIndices);
|
||||||
let viewY = searchY + searchHeight + this._spacing;
|
|
||||||
let viewWidth = primary.width - dashWidth - this._spacing;
|
|
||||||
let viewHeight = contentHeight - this._spacing - viewY;
|
|
||||||
|
|
||||||
this._searchEntry.set_position(searchX, searchY);
|
|
||||||
this._dash.actor.set_position(dashX, dashY);
|
|
||||||
this._viewSelector.actor.set_position(viewX, viewY);
|
|
||||||
this._viewSelector.actor.set_size(viewWidth, viewHeight);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
//// Public methods ////
|
//// Public methods ////
|
||||||
@ -557,15 +491,12 @@ const Overview = new Lang.Class({
|
|||||||
return;
|
return;
|
||||||
if (this._shown)
|
if (this._shown)
|
||||||
return;
|
return;
|
||||||
// Do this manually instead of using _syncInputMode, to handle failure
|
|
||||||
if (!Main.pushModal(this._group))
|
|
||||||
return;
|
|
||||||
this._modal = true;
|
|
||||||
this._animateVisible();
|
|
||||||
this._shown = true;
|
this._shown = true;
|
||||||
|
|
||||||
this._buttonPressId = this._group.connect('button-press-event',
|
if (!this._syncInputMode())
|
||||||
Lang.bind(this, this._onButtonPress));
|
return;
|
||||||
|
|
||||||
|
this._animateVisible();
|
||||||
},
|
},
|
||||||
|
|
||||||
fadeInDesktop: function() {
|
fadeInDesktop: function() {
|
||||||
@ -596,6 +527,8 @@ const Overview = new Lang.Class({
|
|||||||
|
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.animationInProgress = true;
|
this.animationInProgress = true;
|
||||||
|
this.visibleTarget = true;
|
||||||
|
this._activationTime = Date.now() / 1000;
|
||||||
|
|
||||||
// All the the actors in the window group are completely obscured,
|
// All the the actors in the window group are completely obscured,
|
||||||
// hiding the group holding them while the Overview is displayed greatly
|
// hiding the group holding them while the Overview is displayed greatly
|
||||||
@ -607,49 +540,25 @@ const Overview = new Lang.Class({
|
|||||||
//
|
//
|
||||||
// Disable unredirection while in the overview
|
// Disable unredirection while in the overview
|
||||||
Meta.disable_unredirect_for_screen(global.screen);
|
Meta.disable_unredirect_for_screen(global.screen);
|
||||||
global.window_group.hide();
|
this._stack.show();
|
||||||
this._group.show();
|
this._backgroundGroup.show();
|
||||||
this._background.show();
|
|
||||||
this._viewSelector.show();
|
this._viewSelector.show();
|
||||||
|
|
||||||
this._group.opacity = 0;
|
this._stack.opacity = 0;
|
||||||
Tweener.addTween(this._group,
|
Tweener.addTween(this._stack,
|
||||||
{ opacity: 255,
|
{ opacity: 255,
|
||||||
transition: 'easeOutQuad',
|
transition: 'easeOutQuad',
|
||||||
time: ANIMATION_TIME,
|
time: ANIMATION_TIME,
|
||||||
onComplete: this._showDone,
|
onComplete: this._showDone,
|
||||||
onCompleteScope: this
|
onCompleteScope: this
|
||||||
});
|
});
|
||||||
|
this._shadeBackgrounds();
|
||||||
Tweener.addTween(this._background,
|
|
||||||
{ dim_factor: 0.4,
|
|
||||||
time: ANIMATION_TIME,
|
|
||||||
transition: 'easeOutQuad'
|
|
||||||
});
|
|
||||||
|
|
||||||
this._coverPane.raise_top();
|
this._coverPane.raise_top();
|
||||||
this._coverPane.show();
|
this._coverPane.show();
|
||||||
this.emit('showing');
|
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:
|
// hide:
|
||||||
//
|
//
|
||||||
// Reverses the effect of show()
|
// Reverses the effect of show()
|
||||||
@ -660,44 +569,46 @@ const Overview = new Lang.Class({
|
|||||||
if (!this._shown)
|
if (!this._shown)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!this._shownTemporarily)
|
let event = Clutter.get_current_event();
|
||||||
this._animateNotVisible();
|
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._shown = false;
|
||||||
this._syncInputMode();
|
this._syncInputMode();
|
||||||
|
|
||||||
if (this._buttonPressId > 0)
|
|
||||||
this._group.disconnect(this._buttonPressId);
|
|
||||||
this._buttonPressId = 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
// hideTemporarily:
|
|
||||||
//
|
|
||||||
// Reverses the effect of showTemporarily()
|
|
||||||
hideTemporarily: function() {
|
|
||||||
if (this.isDummy)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!this._shownTemporarily)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!this._shown)
|
|
||||||
this._animateNotVisible();
|
|
||||||
|
|
||||||
this._shownTemporarily = false;
|
|
||||||
this._syncInputMode();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
toggle: function() {
|
toggle: function() {
|
||||||
if (this.isDummy)
|
if (this.isDummy)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (this._shown)
|
if (this.visible)
|
||||||
this.hide();
|
this.hide();
|
||||||
else
|
else
|
||||||
this.show();
|
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._activationTime == 0 || Date.now() / 1000 - this._activationTime > OVERVIEW_ACTIVATION_TIMEOUT)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
//// Private methods ////
|
//// Private methods ////
|
||||||
|
|
||||||
_syncInputMode: function() {
|
_syncInputMode: function() {
|
||||||
@ -705,29 +616,32 @@ const Overview = new Lang.Class({
|
|||||||
// overview we don't have a problem with the release of a press/release
|
// overview we don't have a problem with the release of a press/release
|
||||||
// going to an application.
|
// going to an application.
|
||||||
if (this.animationInProgress)
|
if (this.animationInProgress)
|
||||||
return;
|
return true;
|
||||||
|
|
||||||
if (this._shown) {
|
if (this._shown) {
|
||||||
if (!this._modal) {
|
let shouldBeModal = !this._inXdndDrag;
|
||||||
if (Main.pushModal(this._group))
|
if (shouldBeModal) {
|
||||||
this._modal = true;
|
if (!this._modal) {
|
||||||
else
|
if (Main.pushModal(this._overview,
|
||||||
this.hide();
|
{ keybindingMode: Shell.KeyBindingMode.OVERVIEW })) {
|
||||||
|
this._modal = true;
|
||||||
|
} else {
|
||||||
|
this.hide();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
global.stage_input_mode = Shell.StageInputMode.FULLSCREEN;
|
||||||
}
|
}
|
||||||
} else if (this._shownTemporarily) {
|
|
||||||
if (this._modal) {
|
|
||||||
Main.popModal(this._group);
|
|
||||||
this._modal = false;
|
|
||||||
}
|
|
||||||
global.stage_input_mode = Shell.StageInputMode.FULLSCREEN;
|
|
||||||
} else {
|
} else {
|
||||||
if (this._modal) {
|
if (this._modal) {
|
||||||
Main.popModal(this._group);
|
Main.popModal(this._overview);
|
||||||
this._modal = false;
|
this._modal = false;
|
||||||
}
|
}
|
||||||
else if (global.stage_input_mode == Shell.StageInputMode.FULLSCREEN)
|
else if (global.stage_input_mode == Shell.StageInputMode.FULLSCREEN)
|
||||||
global.stage_input_mode = Shell.StageInputMode.NORMAL;
|
global.stage_input_mode = Shell.StageInputMode.NORMAL;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
_animateNotVisible: function() {
|
_animateNotVisible: function() {
|
||||||
@ -735,24 +649,19 @@ const Overview = new Lang.Class({
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
this.animationInProgress = true;
|
this.animationInProgress = true;
|
||||||
this._hideInProgress = true;
|
this.visibleTarget = false;
|
||||||
|
|
||||||
this._viewSelector.zoomFromOverview();
|
this._viewSelector.zoomFromOverview();
|
||||||
|
|
||||||
// Make other elements fade out.
|
// Make other elements fade out.
|
||||||
Tweener.addTween(this._group,
|
Tweener.addTween(this._stack,
|
||||||
{ opacity: 0,
|
{ opacity: 0,
|
||||||
transition: 'easeOutQuad',
|
transition: 'easeOutQuad',
|
||||||
time: ANIMATION_TIME,
|
time: ANIMATION_TIME,
|
||||||
onComplete: this._hideDone,
|
onComplete: this._hideDone,
|
||||||
onCompleteScope: this
|
onCompleteScope: this
|
||||||
});
|
});
|
||||||
|
this._unshadeBackgrounds();
|
||||||
Tweener.addTween(this._background,
|
|
||||||
{ dim_factor: 1.0,
|
|
||||||
time: ANIMATION_TIME,
|
|
||||||
transition: 'easeOutQuad'
|
|
||||||
});
|
|
||||||
|
|
||||||
this._coverPane.raise_top();
|
this._coverPane.raise_top();
|
||||||
this._coverPane.show();
|
this._coverPane.show();
|
||||||
@ -766,7 +675,7 @@ const Overview = new Lang.Class({
|
|||||||
|
|
||||||
this.emit('shown');
|
this.emit('shown');
|
||||||
// Handle any calls to hide* while we were showing
|
// Handle any calls to hide* while we were showing
|
||||||
if (!this._shown && !this._shownTemporarily)
|
if (!this._shown)
|
||||||
this._animateNotVisible();
|
this._animateNotVisible();
|
||||||
|
|
||||||
this._syncInputMode();
|
this._syncInputMode();
|
||||||
@ -777,22 +686,19 @@ const Overview = new Lang.Class({
|
|||||||
// Re-enable unredirection
|
// Re-enable unredirection
|
||||||
Meta.enable_unredirect_for_screen(global.screen);
|
Meta.enable_unredirect_for_screen(global.screen);
|
||||||
|
|
||||||
global.window_group.show();
|
|
||||||
|
|
||||||
this._viewSelector.hide();
|
this._viewSelector.hide();
|
||||||
this._desktopFade.hide();
|
this._desktopFade.hide();
|
||||||
this._background.hide();
|
this._backgroundGroup.hide();
|
||||||
this._group.hide();
|
this._stack.hide();
|
||||||
|
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
this.animationInProgress = false;
|
this.animationInProgress = false;
|
||||||
this._hideInProgress = false;
|
|
||||||
|
|
||||||
this._coverPane.hide();
|
this._coverPane.hide();
|
||||||
|
|
||||||
this.emit('hidden');
|
this.emit('hidden');
|
||||||
// Handle any calls to show* while we were hiding
|
// Handle any calls to show* while we were hiding
|
||||||
if (this._shown || this._shownTemporarily)
|
if (this._shown)
|
||||||
this._animateVisible();
|
this._animateVisible();
|
||||||
|
|
||||||
this._syncInputMode();
|
this._syncInputMode();
|
||||||
|
554
js/ui/overviewControls.js
Normal file
554
js/ui/overviewControls.js
Normal file
@ -0,0 +1,554 @@
|
|||||||
|
// -*- 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 Main = imports.ui.main;
|
||||||
|
const Params = imports.misc.params;
|
||||||
|
const Tweener = imports.ui.tweener;
|
||||||
|
const ViewSelector = imports.ui.viewSelector;
|
||||||
|
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
|
||||||
|
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];
|
||||||
|
let visibleWidth = child.get_theme_node().get_length('visible-width');
|
||||||
|
|
||||||
|
return visibleWidth / expandedWidth;
|
||||||
|
},
|
||||||
|
|
||||||
|
getVisibleWidth: function() {
|
||||||
|
let alwaysZoomOut = this._getAlwaysZoomOut();
|
||||||
|
if (alwaysZoomOut)
|
||||||
|
return this.parent();
|
||||||
|
|
||||||
|
let child = this.actor.get_first_child();
|
||||||
|
return child.get_theme_node().get_length('visible-width');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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.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(dash, thumbnails, viewSelector) {
|
||||||
|
this._dashSlider = new DashSlider(dash);
|
||||||
|
this.dashActor = this._dashSlider.actor;
|
||||||
|
this.dashSpacer = new DashSpacer();
|
||||||
|
this.dashSpacer.setDashActor(this.dashActor);
|
||||||
|
|
||||||
|
this._thumbnailsSlider = new ThumbnailsSlider(thumbnails);
|
||||||
|
this.thumbnailsActor = this._thumbnailsSlider.actor;
|
||||||
|
|
||||||
|
this._indicator = new MessagesIndicator(viewSelector);
|
||||||
|
this.indicatorActor = this._indicator.actor;
|
||||||
|
|
||||||
|
this._viewSelector = viewSelector;
|
||||||
|
this._viewSelector.connect('page-changed', Lang.bind(this, this._setVisibility));
|
||||||
|
this._viewSelector.connect('page-empty', Lang.bind(this, this._onPageEmpty));
|
||||||
|
|
||||||
|
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();
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
_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();
|
||||||
|
}
|
||||||
|
});
|
288
js/ui/panel.js
288
js/ui/panel.js
@ -18,7 +18,6 @@ const Atk = imports.gi.Atk;
|
|||||||
const Config = imports.misc.config;
|
const Config = imports.misc.config;
|
||||||
const CtrlAltTab = imports.ui.ctrlAltTab;
|
const CtrlAltTab = imports.ui.ctrlAltTab;
|
||||||
const DND = imports.ui.dnd;
|
const DND = imports.ui.dnd;
|
||||||
const Layout = imports.ui.layout;
|
|
||||||
const Overview = imports.ui.overview;
|
const Overview = imports.ui.overview;
|
||||||
const PopupMenu = imports.ui.popupMenu;
|
const PopupMenu = imports.ui.popupMenu;
|
||||||
const PanelMenu = imports.ui.panelMenu;
|
const PanelMenu = imports.ui.panelMenu;
|
||||||
@ -75,33 +74,41 @@ function _unpremultiply(color) {
|
|||||||
blue: blue, alpha: color.alpha });
|
blue: blue, alpha: color.alpha });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Animation = new Lang.Class({
|
||||||
|
Name: 'Animation',
|
||||||
|
|
||||||
const AnimatedIcon = new Lang.Class({
|
_init: function(filename, width, height, speed) {
|
||||||
Name: 'AnimatedIcon',
|
this.actor = new St.Bin();
|
||||||
|
|
||||||
_init: function(name, size) {
|
|
||||||
this.actor = new St.Bin({ visible: false });
|
|
||||||
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
||||||
this.actor.connect('notify::visible', Lang.bind(this, this._onVisibleNotify));
|
this._speed = speed;
|
||||||
|
|
||||||
|
this._isLoaded = false;
|
||||||
|
this._isPlaying = false;
|
||||||
this._timeoutId = 0;
|
this._timeoutId = 0;
|
||||||
this._frame = 0;
|
this._frame = 0;
|
||||||
this._animations = St.TextureCache.get_default().load_sliced_image (global.datadir + '/theme/' + name, size, size);
|
this._animations = St.TextureCache.get_default().load_sliced_image (filename, width, height,
|
||||||
|
Lang.bind(this, this._animationsLoaded));
|
||||||
this.actor.set_child(this._animations);
|
this.actor.set_child(this._animations);
|
||||||
},
|
},
|
||||||
|
|
||||||
_disconnectTimeout: function() {
|
play: function() {
|
||||||
|
if (this._isLoaded && this._timeoutId == 0) {
|
||||||
|
if (this._frame == 0)
|
||||||
|
this._showFrame(0);
|
||||||
|
|
||||||
|
this._timeoutId = Mainloop.timeout_add(this._speed, Lang.bind(this, this._update));
|
||||||
|
}
|
||||||
|
|
||||||
|
this._isPlaying = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
stop: function() {
|
||||||
if (this._timeoutId > 0) {
|
if (this._timeoutId > 0) {
|
||||||
Mainloop.source_remove(this._timeoutId);
|
Mainloop.source_remove(this._timeoutId);
|
||||||
this._timeoutId = 0;
|
this._timeoutId = 0;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
_onVisibleNotify: function() {
|
this._isPlaying = false;
|
||||||
if (this.actor.visible)
|
|
||||||
this._timeoutId = Mainloop.timeout_add(ANIMATED_ICON_UPDATE_TIMEOUT, Lang.bind(this, this._update));
|
|
||||||
else
|
|
||||||
this._disconnectTimeout();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_showFrame: function(frame) {
|
_showFrame: function(frame) {
|
||||||
@ -121,8 +128,24 @@ const AnimatedIcon = new Lang.Class({
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_animationsLoaded: function() {
|
||||||
|
this._isLoaded = true;
|
||||||
|
|
||||||
|
if (this._isPlaying)
|
||||||
|
this.play();
|
||||||
|
},
|
||||||
|
|
||||||
_onDestroy: function() {
|
_onDestroy: function() {
|
||||||
this._disconnectTimeout();
|
this.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const AnimatedIcon = new Lang.Class({
|
||||||
|
Name: 'AnimatedIcon',
|
||||||
|
Extends: Animation,
|
||||||
|
|
||||||
|
_init: function(name, size) {
|
||||||
|
this.parent(global.datadir + '/theme/' + name, size, size, ANIMATED_ICON_UPDATE_TIMEOUT);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -179,7 +202,7 @@ const TextShadower = new Lang.Class({
|
|||||||
let child = children[i];
|
let child = children[i];
|
||||||
let childBox = new Clutter.ActorBox();
|
let childBox = new Clutter.ActorBox();
|
||||||
// The order of the labels here is arbitrary, except
|
// 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
|
// sorts by Z order
|
||||||
switch (i) {
|
switch (i) {
|
||||||
case 0: // top
|
case 0: // top
|
||||||
@ -247,6 +270,10 @@ const AppMenuButton = new Lang.Class({
|
|||||||
this._container.connect('get-preferred-height', Lang.bind(this, this._getContentPreferredHeight));
|
this._container.connect('get-preferred-height', Lang.bind(this, this._getContentPreferredHeight));
|
||||||
this._container.connect('allocate', Lang.bind(this, this._contentAllocate));
|
this._container.connect('allocate', Lang.bind(this, this._contentAllocate));
|
||||||
|
|
||||||
|
let textureCache = St.TextureCache.get_default();
|
||||||
|
textureCache.connect('icon-theme-changed',
|
||||||
|
Lang.bind(this, this._onIconThemeChanged));
|
||||||
|
|
||||||
this._iconBox = new Shell.Slicer({ name: 'appMenuIcon' });
|
this._iconBox = new Shell.Slicer({ name: 'appMenuIcon' });
|
||||||
this._iconBox.connect('style-changed',
|
this._iconBox.connect('style-changed',
|
||||||
Lang.bind(this, this._onIconBoxStyleChanged));
|
Lang.bind(this, this._onIconBoxStyleChanged));
|
||||||
@ -273,6 +300,7 @@ const AppMenuButton = new Lang.Class({
|
|||||||
this._spinner = new AnimatedIcon('process-working.svg',
|
this._spinner = new AnimatedIcon('process-working.svg',
|
||||||
PANEL_ICON_SIZE);
|
PANEL_ICON_SIZE);
|
||||||
this._container.add_actor(this._spinner.actor);
|
this._container.add_actor(this._spinner.actor);
|
||||||
|
this._spinner.actor.hide();
|
||||||
this._spinner.actor.lower_bottom();
|
this._spinner.actor.lower_bottom();
|
||||||
|
|
||||||
let tracker = Shell.WindowTracker.get_default();
|
let tracker = Shell.WindowTracker.get_default();
|
||||||
@ -286,7 +314,7 @@ const AppMenuButton = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
show: function() {
|
show: function() {
|
||||||
if (this._visible || Main.screenShield.locked)
|
if (this._visible)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this._visible = true;
|
this._visible = true;
|
||||||
@ -332,6 +360,15 @@ const AppMenuButton = new Lang.Class({
|
|||||||
this._updateIconBoxClip();
|
this._updateIconBoxClip();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onIconThemeChanged: function() {
|
||||||
|
if (this._iconBox.child == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._iconBox.child.destroy();
|
||||||
|
let icon = this._targetApp.get_faded_icon(2 * PANEL_ICON_SIZE);
|
||||||
|
this._iconBox.set_child(icon);
|
||||||
|
},
|
||||||
|
|
||||||
_updateIconBoxClip: function() {
|
_updateIconBoxClip: function() {
|
||||||
let allocation = this._iconBox.allocation;
|
let allocation = this._iconBox.allocation;
|
||||||
if (this._iconBottomClip > 0)
|
if (this._iconBottomClip > 0)
|
||||||
@ -354,6 +391,7 @@ const AppMenuButton = new Lang.Class({
|
|||||||
transition: "easeOutQuad",
|
transition: "easeOutQuad",
|
||||||
onCompleteScope: this,
|
onCompleteScope: this,
|
||||||
onComplete: function() {
|
onComplete: function() {
|
||||||
|
this._spinner.stop();
|
||||||
this._spinner.actor.opacity = 255;
|
this._spinner.actor.opacity = 255;
|
||||||
this._spinner.actor.hide();
|
this._spinner.actor.hide();
|
||||||
}
|
}
|
||||||
@ -363,6 +401,7 @@ const AppMenuButton = new Lang.Class({
|
|||||||
startAnimation: function() {
|
startAnimation: function() {
|
||||||
this._stop = false;
|
this._stop = false;
|
||||||
this.actor.reactive = false;
|
this.actor.reactive = false;
|
||||||
|
this._spinner.play();
|
||||||
this._spinner.actor.show();
|
this._spinner.actor.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -558,12 +597,18 @@ const AppMenuButton = new Lang.Class({
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
menu = new PopupMenu.RemoteMenu(this.actor, this._targetApp.menu, this._targetApp.action_group);
|
menu = new PopupMenu.RemoteMenu(this.actor, this._targetApp.menu, this._targetApp.action_group);
|
||||||
|
menu.connect('activate', Lang.bind(this, function() {
|
||||||
|
let win = this._targetApp.get_windows()[0];
|
||||||
|
win.check_alive(global.get_current_time());
|
||||||
|
}));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (this.menu && !(this.menu instanceof PopupMenu.RemoteMenu))
|
if (this.menu.isDummyQuitMenu)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// fallback to older menu
|
// fallback to older menu
|
||||||
menu = new PopupMenu.PopupMenu(this.actor, 0.0, St.Side.TOP, 0);
|
menu = new PopupMenu.PopupMenu(this.actor, 0.0, St.Side.TOP, 0);
|
||||||
|
menu.isDummyQuitMenu = true;
|
||||||
menu.addAction(_("Quit"), Lang.bind(this, function() {
|
menu.addAction(_("Quit"), Lang.bind(this, function() {
|
||||||
this._targetApp.request_quit();
|
this._targetApp.request_quit();
|
||||||
}));
|
}));
|
||||||
@ -576,88 +621,39 @@ const AppMenuButton = new Lang.Class({
|
|||||||
|
|
||||||
Signals.addSignalMethods(AppMenuButton.prototype);
|
Signals.addSignalMethods(AppMenuButton.prototype);
|
||||||
|
|
||||||
// Activities button. Because everything else in the top bar is a
|
|
||||||
// PanelMenu.Button, it simplifies some things to make this be one too.
|
|
||||||
// We just hack it up to not actually have a menu attached to it.
|
|
||||||
const ActivitiesButton = new Lang.Class({
|
const ActivitiesButton = new Lang.Class({
|
||||||
Name: 'ActivitiesButton',
|
Name: 'ActivitiesButton',
|
||||||
Extends: PanelMenu.Button,
|
Extends: PanelMenu.Button,
|
||||||
|
|
||||||
_init: function() {
|
_init: function() {
|
||||||
this.parent(0.0);
|
this.parent(0.0, null, true);
|
||||||
this.actor.accessible_role = Atk.Role.TOGGLE_BUTTON;
|
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';
|
this.actor.name = 'panelActivities';
|
||||||
|
|
||||||
/* Translators: If there is no suitable word for "Activities"
|
/* Translators: If there is no suitable word for "Activities"
|
||||||
in your language, you can use the word for "Overview". */
|
in your language, you can use the word for "Overview". */
|
||||||
this._label = new St.Label({ text: _("Activities") });
|
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.actor.label_actor = this._label;
|
||||||
|
|
||||||
this.hotCorner = new Layout.HotCorner();
|
|
||||||
container.add_actor(this.hotCorner.actor);
|
|
||||||
|
|
||||||
// Hack up our menu...
|
|
||||||
this.menu.open = Lang.bind(this, this._onMenuOpenRequest);
|
|
||||||
this.menu.close = Lang.bind(this, this._onMenuCloseRequest);
|
|
||||||
this.menu.toggle = Lang.bind(this, this._onMenuToggleRequest);
|
|
||||||
|
|
||||||
this.actor.connect('captured-event', Lang.bind(this, this._onCapturedEvent));
|
this.actor.connect('captured-event', Lang.bind(this, this._onCapturedEvent));
|
||||||
this.actor.connect_after('button-release-event', Lang.bind(this, this._onButtonRelease));
|
this.actor.connect_after('button-release-event', Lang.bind(this, this._onButtonRelease));
|
||||||
this.actor.connect_after('key-release-event', Lang.bind(this, this._onKeyRelease));
|
this.actor.connect_after('key-release-event', Lang.bind(this, this._onKeyRelease));
|
||||||
|
|
||||||
Main.overview.connect('showing', Lang.bind(this, function() {
|
Main.overview.connect('showing', Lang.bind(this, function() {
|
||||||
this.actor.add_style_pseudo_class('overview');
|
this.actor.add_style_pseudo_class('overview');
|
||||||
this._escapeMenuGrab();
|
|
||||||
this.actor.add_accessible_state (Atk.StateType.CHECKED);
|
this.actor.add_accessible_state (Atk.StateType.CHECKED);
|
||||||
}));
|
}));
|
||||||
Main.overview.connect('hiding', Lang.bind(this, function() {
|
Main.overview.connect('hiding', Lang.bind(this, function() {
|
||||||
this.actor.remove_style_pseudo_class('overview');
|
this.actor.remove_style_pseudo_class('overview');
|
||||||
this._escapeMenuGrab();
|
|
||||||
this.actor.remove_accessible_state (Atk.StateType.CHECKED);
|
this.actor.remove_accessible_state (Atk.StateType.CHECKED);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._xdndTimeOut = 0;
|
this._xdndTimeOut = 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
_containerGetPreferredWidth: function(actor, forHeight, alloc) {
|
|
||||||
[alloc.min_size, alloc.natural_size] = this._label.get_preferred_width(forHeight);
|
|
||||||
},
|
|
||||||
|
|
||||||
_containerGetPreferredHeight: function(actor, forWidth, alloc) {
|
|
||||||
[alloc.min_size, alloc.natural_size] = this._label.get_preferred_height(forWidth);
|
|
||||||
},
|
|
||||||
|
|
||||||
_containerAllocate: function(actor, box, flags) {
|
|
||||||
this._label.allocate(box, flags);
|
|
||||||
|
|
||||||
// The hot corner needs to be outside any padding/alignment
|
|
||||||
// that has been imposed on us
|
|
||||||
let primary = Main.layoutManager.primaryMonitor;
|
|
||||||
let hotBox = new Clutter.ActorBox();
|
|
||||||
let ok, x, y;
|
|
||||||
if (actor.get_text_direction() == Clutter.TextDirection.LTR) {
|
|
||||||
[ok, x, y] = actor.transform_stage_point(primary.x, primary.y)
|
|
||||||
} else {
|
|
||||||
[ok, x, y] = actor.transform_stage_point(primary.x + primary.width, primary.y);
|
|
||||||
// hotCorner.actor has northeast gravity, so we don't need
|
|
||||||
// to adjust x for its width
|
|
||||||
}
|
|
||||||
|
|
||||||
hotBox.x1 = Math.round(x);
|
|
||||||
hotBox.x2 = hotBox.x1 + this.hotCorner.actor.width;
|
|
||||||
hotBox.y1 = Math.round(y);
|
|
||||||
hotBox.y2 = hotBox.y1 + this.hotCorner.actor.height;
|
|
||||||
this.hotCorner.actor.allocate(hotBox, flags);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleDragOver: function(source, actor, x, y, time) {
|
handleDragOver: function(source, actor, x, y, time) {
|
||||||
if (source != Main.xdndHandler)
|
if (source != Main.xdndHandler)
|
||||||
return DND.DragMotionResult.CONTINUE;
|
return DND.DragMotionResult.CONTINUE;
|
||||||
@ -665,65 +661,36 @@ const ActivitiesButton = new Lang.Class({
|
|||||||
if (this._xdndTimeOut != 0)
|
if (this._xdndTimeOut != 0)
|
||||||
Mainloop.source_remove(this._xdndTimeOut);
|
Mainloop.source_remove(this._xdndTimeOut);
|
||||||
this._xdndTimeOut = Mainloop.timeout_add(BUTTON_DND_ACTIVATION_TIMEOUT,
|
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;
|
return DND.DragMotionResult.CONTINUE;
|
||||||
},
|
},
|
||||||
|
|
||||||
_escapeMenuGrab: function() {
|
|
||||||
if (this.menu.isOpen)
|
|
||||||
this.menu.close();
|
|
||||||
},
|
|
||||||
|
|
||||||
_onCapturedEvent: function(actor, event) {
|
_onCapturedEvent: function(actor, event) {
|
||||||
if (event.type() == Clutter.EventType.BUTTON_PRESS) {
|
if (event.type() == Clutter.EventType.BUTTON_PRESS) {
|
||||||
if (!this.hotCorner.shouldToggleOverviewOnClick())
|
if (!Main.overview.shouldToggleByCornerOrButton())
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
_onMenuOpenRequest: function() {
|
|
||||||
this.menu.isOpen = true;
|
|
||||||
this.menu.emit('open-state-changed', true);
|
|
||||||
},
|
|
||||||
|
|
||||||
_onMenuCloseRequest: function() {
|
|
||||||
this.menu.isOpen = false;
|
|
||||||
this.menu.emit('open-state-changed', false);
|
|
||||||
},
|
|
||||||
|
|
||||||
_onMenuToggleRequest: function() {
|
|
||||||
this.menu.isOpen = !this.menu.isOpen;
|
|
||||||
this.menu.emit('open-state-changed', this.menu.isOpen);
|
|
||||||
},
|
|
||||||
|
|
||||||
_onButtonRelease: function() {
|
_onButtonRelease: function() {
|
||||||
if (this.menu.isOpen) {
|
Main.overview.toggle();
|
||||||
this.menu.close();
|
|
||||||
Main.overview.toggle();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_onKeyRelease: function(actor, event) {
|
_onKeyRelease: function(actor, event) {
|
||||||
let symbol = event.get_key_symbol();
|
let symbol = event.get_key_symbol();
|
||||||
if (symbol == Clutter.KEY_Return || symbol == Clutter.KEY_space) {
|
if (symbol == Clutter.KEY_Return || symbol == Clutter.KEY_space) {
|
||||||
if (this.menu.isOpen)
|
|
||||||
this.menu.close();
|
|
||||||
Main.overview.toggle();
|
Main.overview.toggle();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_xdndShowOverview: function(actor) {
|
_xdndToggleOverview: function(actor) {
|
||||||
let [x, y, mask] = global.get_pointer();
|
let [x, y, mask] = global.get_pointer();
|
||||||
let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);
|
let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);
|
||||||
|
|
||||||
if (pickedActor == this.actor) {
|
if (pickedActor == this.actor && Main.overview.shouldToggleByCornerOrButton())
|
||||||
if (!Main.overview.visible && !Main.overview.animationInProgress) {
|
Main.overview.toggle();
|
||||||
Main.overview.showTemporarily();
|
|
||||||
Main.overview.beginItemDrag(actor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Mainloop.source_remove(this._xdndTimeOut);
|
Mainloop.source_remove(this._xdndTimeOut);
|
||||||
this._xdndTimeOut = 0;
|
this._xdndTimeOut = 0;
|
||||||
@ -733,12 +700,9 @@ const ActivitiesButton = new Lang.Class({
|
|||||||
const PanelCorner = new Lang.Class({
|
const PanelCorner = new Lang.Class({
|
||||||
Name: 'PanelCorner',
|
Name: 'PanelCorner',
|
||||||
|
|
||||||
_init: function(box, side) {
|
_init: function(side) {
|
||||||
this._side = side;
|
this._side = side;
|
||||||
|
|
||||||
this._box = box;
|
|
||||||
this._box.connect('style-changed', Lang.bind(this, this._boxStyleChanged));
|
|
||||||
|
|
||||||
this.actor = new St.DrawingArea({ style_class: 'panel-corner' });
|
this.actor = new St.DrawingArea({ style_class: 'panel-corner' });
|
||||||
this.actor.connect('style-changed', Lang.bind(this, this._styleChanged));
|
this.actor.connect('style-changed', Lang.bind(this, this._styleChanged));
|
||||||
this.actor.connect('repaint', Lang.bind(this, this._repaint));
|
this.actor.connect('repaint', Lang.bind(this, this._repaint));
|
||||||
@ -794,12 +758,12 @@ const PanelCorner = new Lang.Class({
|
|||||||
return children[index];
|
return children[index];
|
||||||
},
|
},
|
||||||
|
|
||||||
_boxStyleChanged: function() {
|
setStyleParent: function(box) {
|
||||||
let side = this._side;
|
let side = this._side;
|
||||||
|
|
||||||
let rtlAwareContainer = this._box instanceof St.BoxLayout;
|
let rtlAwareContainer = box instanceof St.BoxLayout;
|
||||||
if (rtlAwareContainer &&
|
if (rtlAwareContainer &&
|
||||||
this._box.get_text_direction() == Clutter.TextDirection.RTL) {
|
box.get_text_direction() == Clutter.TextDirection.RTL) {
|
||||||
if (this._side == St.Side.LEFT)
|
if (this._side == St.Side.LEFT)
|
||||||
side = St.Side.RIGHT;
|
side = St.Side.RIGHT;
|
||||||
else if (this._side == St.Side.RIGHT)
|
else if (this._side == St.Side.RIGHT)
|
||||||
@ -808,9 +772,9 @@ const PanelCorner = new Lang.Class({
|
|||||||
|
|
||||||
let button;
|
let button;
|
||||||
if (side == St.Side.LEFT)
|
if (side == St.Side.LEFT)
|
||||||
button = this._findLeftmostButton(this._box);
|
button = this._findLeftmostButton(box);
|
||||||
else if (side == St.Side.RIGHT)
|
else if (side == St.Side.RIGHT)
|
||||||
button = this._findRightmostButton(this._box);
|
button = this._findRightmostButton(box);
|
||||||
|
|
||||||
if (button) {
|
if (button) {
|
||||||
if (this._button && this._buttonStyleChangedSignalId) {
|
if (this._button && this._buttonStyleChangedSignalId) {
|
||||||
@ -837,7 +801,7 @@ const PanelCorner = new Lang.Class({
|
|||||||
|
|
||||||
// The corner doesn't support theme transitions, so override
|
// The corner doesn't support theme transitions, so override
|
||||||
// the .panel-button default
|
// the .panel-button default
|
||||||
button.style = 'transition-duration: 0';
|
button.style = 'transition-duration: 0ms';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -850,10 +814,13 @@ const PanelCorner = new Lang.Class({
|
|||||||
let backgroundColor = node.get_color('-panel-corner-background-color');
|
let backgroundColor = node.get_color('-panel-corner-background-color');
|
||||||
let borderColor = node.get_color('-panel-corner-border-color');
|
let borderColor = node.get_color('-panel-corner-border-color');
|
||||||
|
|
||||||
|
let overlap = borderColor.alpha != 0;
|
||||||
|
let offsetY = overlap ? 0 : borderWidth;
|
||||||
|
|
||||||
let cr = this.actor.get_context();
|
let cr = this.actor.get_context();
|
||||||
cr.setOperator(Cairo.Operator.SOURCE);
|
cr.setOperator(Cairo.Operator.SOURCE);
|
||||||
|
|
||||||
cr.moveTo(0, 0);
|
cr.moveTo(0, offsetY);
|
||||||
if (this._side == St.Side.LEFT)
|
if (this._side == St.Side.LEFT)
|
||||||
cr.arc(cornerRadius,
|
cr.arc(cornerRadius,
|
||||||
borderWidth + cornerRadius,
|
borderWidth + cornerRadius,
|
||||||
@ -862,7 +829,7 @@ const PanelCorner = new Lang.Class({
|
|||||||
cr.arc(0,
|
cr.arc(0,
|
||||||
borderWidth + cornerRadius,
|
borderWidth + cornerRadius,
|
||||||
cornerRadius, 3 * Math.PI / 2, 2 * Math.PI);
|
cornerRadius, 3 * Math.PI / 2, 2 * Math.PI);
|
||||||
cr.lineTo(cornerRadius, 0);
|
cr.lineTo(cornerRadius, offsetY);
|
||||||
cr.closePath();
|
cr.closePath();
|
||||||
|
|
||||||
let savedPath = cr.copyPath();
|
let savedPath = cr.copyPath();
|
||||||
@ -872,14 +839,18 @@ const PanelCorner = new Lang.Class({
|
|||||||
Clutter.cairo_set_source_color(cr, over);
|
Clutter.cairo_set_source_color(cr, over);
|
||||||
cr.fill();
|
cr.fill();
|
||||||
|
|
||||||
let offset = borderWidth;
|
if (overlap) {
|
||||||
Clutter.cairo_set_source_color(cr, backgroundColor);
|
let offset = borderWidth;
|
||||||
|
Clutter.cairo_set_source_color(cr, backgroundColor);
|
||||||
|
|
||||||
cr.save();
|
cr.save();
|
||||||
cr.translate(xOffsetDirection * offset, - offset);
|
cr.translate(xOffsetDirection * offset, - offset);
|
||||||
cr.appendPath(savedPath);
|
cr.appendPath(savedPath);
|
||||||
cr.fill();
|
cr.fill();
|
||||||
cr.restore();
|
cr.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
cr.$dispose();
|
||||||
},
|
},
|
||||||
|
|
||||||
_styleChanged: function() {
|
_styleChanged: function() {
|
||||||
@ -898,9 +869,11 @@ const PANEL_ITEM_IMPLEMENTATIONS = {
|
|||||||
'appMenu': AppMenuButton,
|
'appMenu': AppMenuButton,
|
||||||
'dateMenu': imports.ui.dateMenu.DateMenuButton,
|
'dateMenu': imports.ui.dateMenu.DateMenuButton,
|
||||||
'a11y': imports.ui.status.accessibility.ATIndicator,
|
'a11y': imports.ui.status.accessibility.ATIndicator,
|
||||||
|
'a11yGreeter': imports.ui.status.accessibility.ATGreeterIndicator,
|
||||||
'volume': imports.ui.status.volume.Indicator,
|
'volume': imports.ui.status.volume.Indicator,
|
||||||
'battery': imports.ui.status.power.Indicator,
|
'battery': imports.ui.status.power.Indicator,
|
||||||
'lockScreen': imports.ui.status.lockScreenMenu.Indicator,
|
'lockScreen': imports.ui.status.lockScreenMenu.Indicator,
|
||||||
|
'logo': imports.gdm.loginDialog.LogoMenuButton,
|
||||||
'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
|
'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
|
||||||
'powerMenu': imports.gdm.powerMenu.PowerMenuButton,
|
'powerMenu': imports.gdm.powerMenu.PowerMenuButton,
|
||||||
'userMenu': imports.ui.userMenu.UserMenuButton
|
'userMenu': imports.ui.userMenu.UserMenuButton
|
||||||
@ -925,14 +898,9 @@ const Panel = new Lang.Class({
|
|||||||
reactive: true });
|
reactive: true });
|
||||||
this.actor._delegate = this;
|
this.actor._delegate = this;
|
||||||
|
|
||||||
this.statusArea = {};
|
this._sessionStyle = null;
|
||||||
|
|
||||||
Main.overview.connect('shown', Lang.bind(this, function () {
|
this.statusArea = {};
|
||||||
this.actor.add_style_class_name('in-overview');
|
|
||||||
}));
|
|
||||||
Main.overview.connect('hiding', Lang.bind(this, function () {
|
|
||||||
this.actor.remove_style_class_name('in-overview');
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.menuManager = new PopupMenu.PopupMenuManager(this);
|
this.menuManager = new PopupMenu.PopupMenuManager(this);
|
||||||
|
|
||||||
@ -943,17 +911,10 @@ const Panel = new Lang.Class({
|
|||||||
this._rightBox = new St.BoxLayout({ name: 'panelRight' });
|
this._rightBox = new St.BoxLayout({ name: 'panelRight' });
|
||||||
this.actor.add_actor(this._rightBox);
|
this.actor.add_actor(this._rightBox);
|
||||||
|
|
||||||
if (this.actor.get_text_direction() == Clutter.TextDirection.RTL)
|
this._leftCorner = new PanelCorner(St.Side.LEFT);
|
||||||
this._leftCorner = new PanelCorner(this._rightBox, St.Side.LEFT);
|
|
||||||
else
|
|
||||||
this._leftCorner = new PanelCorner(this._leftBox, St.Side.LEFT);
|
|
||||||
|
|
||||||
this.actor.add_actor(this._leftCorner.actor);
|
this.actor.add_actor(this._leftCorner.actor);
|
||||||
|
|
||||||
if (this.actor.get_text_direction() == Clutter.TextDirection.RTL)
|
this._rightCorner = new PanelCorner(St.Side.RIGHT);
|
||||||
this._rightCorner = new PanelCorner(this._leftBox, St.Side.RIGHT);
|
|
||||||
else
|
|
||||||
this._rightCorner = new PanelCorner(this._rightBox, St.Side.RIGHT);
|
|
||||||
this.actor.add_actor(this._rightCorner.actor);
|
this.actor.add_actor(this._rightCorner.actor);
|
||||||
|
|
||||||
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||||
@ -961,8 +922,15 @@ const Panel = new Lang.Class({
|
|||||||
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
||||||
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
|
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
|
||||||
|
|
||||||
|
Main.overview.connect('showing', Lang.bind(this, function () {
|
||||||
|
this.actor.add_style_pseudo_class('overview');
|
||||||
|
}));
|
||||||
|
Main.overview.connect('hiding', Lang.bind(this, function () {
|
||||||
|
this.actor.remove_style_pseudo_class('overview');
|
||||||
|
}));
|
||||||
|
|
||||||
Main.layoutManager.panelBox.add(this.actor);
|
Main.layoutManager.panelBox.add(this.actor);
|
||||||
Main.ctrlAltTabManager.addGroup(this.actor, _("Top Bar"), 'start-here-symbolic',
|
Main.ctrlAltTabManager.addGroup(this.actor, _("Top Bar"), 'emblem-system-symbolic',
|
||||||
{ sortGroup: CtrlAltTab.SortGroup.TOP });
|
{ sortGroup: CtrlAltTab.SortGroup.TOP });
|
||||||
|
|
||||||
Main.sessionMode.connect('updated', Lang.bind(this, this._updatePanel));
|
Main.sessionMode.connect('updated', Lang.bind(this, this._updatePanel));
|
||||||
@ -1047,6 +1015,9 @@ const Panel = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_onButtonPress: function(actor, event) {
|
_onButtonPress: function(actor, event) {
|
||||||
|
if (Main.modalCount > 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (event.get_source() != actor)
|
if (event.get_source() != actor)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -1119,17 +1090,20 @@ const Panel = new Lang.Class({
|
|||||||
this._updateBox(panel.left, this._leftBox);
|
this._updateBox(panel.left, this._leftBox);
|
||||||
this._updateBox(panel.center, this._centerBox);
|
this._updateBox(panel.center, this._centerBox);
|
||||||
this._updateBox(panel.right, this._rightBox);
|
this._updateBox(panel.right, this._rightBox);
|
||||||
},
|
|
||||||
|
|
||||||
_initBox: function(elements, box) {
|
if (this._sessionStyle)
|
||||||
for (let i = 0; i < elements.length; i++) {
|
this._removeStyleClassName(this._sessionStyle);
|
||||||
let role = elements[i];
|
|
||||||
let constructor = PANEL_ITEM_IMPLEMENTATIONS[role];
|
this._sessionStyle = Main.sessionMode.panelStyle;
|
||||||
if (!constructor) {
|
if (this._sessionStyle)
|
||||||
// panel icon is not supported (can happen for
|
this._addStyleClassName(this._sessionStyle);
|
||||||
// bluetooth or network)
|
|
||||||
continue;
|
if (this.actor.get_text_direction() == Clutter.TextDirection.RTL) {
|
||||||
}
|
this._leftCorner.setStyleParent(this._rightBox);
|
||||||
|
this._rightCorner.setStyleParent(this._leftBox);
|
||||||
|
} else {
|
||||||
|
this._leftCorner.setStyleParent(this._leftBox);
|
||||||
|
this._rightCorner.setStyleParent(this._rightBox);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1207,5 +1181,17 @@ const Panel = new Lang.Class({
|
|||||||
this.statusArea[role] = indicator;
|
this.statusArea[role] = indicator;
|
||||||
this._addToPanelBox(role, indicator, position, boxContainer);
|
this._addToPanelBox(role, indicator, position, boxContainer);
|
||||||
return indicator;
|
return indicator;
|
||||||
|
},
|
||||||
|
|
||||||
|
_addStyleClassName: function(className) {
|
||||||
|
this.actor.add_style_class_name(className);
|
||||||
|
this._rightCorner.actor.add_style_class_name(className);
|
||||||
|
this._leftCorner.actor.add_style_class_name(className);
|
||||||
|
},
|
||||||
|
|
||||||
|
_removeStyleClassName: function(className) {
|
||||||
|
this.actor.remove_style_class_name(className);
|
||||||
|
this._rightCorner.actor.remove_style_class_name(className);
|
||||||
|
this._leftCorner.actor.remove_style_class_name(className);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -112,7 +112,7 @@ const Button = new Lang.Class({
|
|||||||
this.actor.connect('key-press-event', Lang.bind(this, this._onSourceKeyPress));
|
this.actor.connect('key-press-event', Lang.bind(this, this._onSourceKeyPress));
|
||||||
|
|
||||||
if (dontCreateMenu)
|
if (dontCreateMenu)
|
||||||
this.menu = null;
|
this.menu = new PopupMenu.PopupDummyMenu(this.actor);
|
||||||
else
|
else
|
||||||
this.setMenu(new PopupMenu.PopupMenu(this.actor, menuAlignment, St.Side.TOP, 0));
|
this.setMenu(new PopupMenu.PopupMenu(this.actor, menuAlignment, St.Side.TOP, 0));
|
||||||
|
|
||||||
@ -205,16 +205,15 @@ const Button = new Lang.Class({
|
|||||||
// Setting the max-height won't do any good if the minimum height of the
|
// 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
|
// 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
|
// scrollable so the minimum height is smaller than the natural height
|
||||||
let monitor = Main.layoutManager.primaryMonitor;
|
let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
|
||||||
this.menu.actor.style = ('max-height: ' +
|
this.menu.actor.style = ('max-height: ' + Math.round(workArea.height) + 'px;');
|
||||||
Math.round(monitor.height - Main.panel.actor.height) +
|
|
||||||
'px;');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
destroy: function() {
|
destroy: function() {
|
||||||
this.actor._delegate = null;
|
this.actor._delegate = null;
|
||||||
|
|
||||||
this.menu.destroy();
|
if (this.menu)
|
||||||
|
this.menu.destroy();
|
||||||
this.actor.destroy();
|
this.actor.destroy();
|
||||||
|
|
||||||
this.emit('destroy');
|
this.emit('destroy');
|
||||||
@ -243,18 +242,21 @@ const SystemStatusButton = new Lang.Class({
|
|||||||
this.setIcon(iconName);
|
this.setIcon(iconName);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get icons() {
|
||||||
|
return this._box.get_children();
|
||||||
|
},
|
||||||
|
|
||||||
addIcon: function(gicon) {
|
addIcon: function(gicon) {
|
||||||
let icon = new St.Icon({ gicon: gicon,
|
let icon = new St.Icon({ gicon: gicon,
|
||||||
style_class: 'system-status-icon' });
|
style_class: 'system-status-icon' });
|
||||||
this._box.add_actor(icon);
|
this._box.add_actor(icon);
|
||||||
|
|
||||||
|
this.emit('icons-changed');
|
||||||
|
|
||||||
return icon;
|
return icon;
|
||||||
},
|
},
|
||||||
|
|
||||||
setIcon: function(iconName) {
|
setIcon: function(iconName) {
|
||||||
// Need to first add a NULL GIcon and then set icon_name, to ensure
|
|
||||||
// compatibility with -symbolic fallbacks
|
|
||||||
|
|
||||||
if (!this.mainIcon)
|
if (!this.mainIcon)
|
||||||
this.mainIcon = this.addIcon(null);
|
this.mainIcon = this.addIcon(null);
|
||||||
this.mainIcon.icon_name = iconName;
|
this.mainIcon.icon_name = iconName;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
const Mainloop = imports.mainloop;
|
const Mainloop = imports.mainloop;
|
||||||
|
const GnomeDesktop = imports.gi.GnomeDesktop;
|
||||||
const Shell = imports.gi.Shell;
|
const Shell = imports.gi.Shell;
|
||||||
|
|
||||||
// We stop polling if the user is idle for more than this amount of time
|
// We stop polling if the user is idle for more than this amount of time
|
||||||
@ -40,10 +41,9 @@ const PointerWatcher = new Lang.Class({
|
|||||||
Name: 'PointerWatcher',
|
Name: 'PointerWatcher',
|
||||||
|
|
||||||
_init: function() {
|
_init: function() {
|
||||||
let idleMonitor = Shell.IdleMonitor.get();
|
this._idleMonitor = new GnomeDesktop.IdleMonitor();
|
||||||
idleMonitor.add_watch(IDLE_TIME,
|
this._idleMonitor.add_idle_watch(IDLE_TIME, Lang.bind(this, this._onIdleMonitorBecameIdle));
|
||||||
Lang.bind(this, this._onIdleMonitorWatch));
|
this._idle = this._idleMonitor.get_idletime() > IDLE_TIME;
|
||||||
this._idle = idleMonitor.get_idletime() > IDLE_TIME;
|
|
||||||
this._watches = [];
|
this._watches = [];
|
||||||
this.pointerX = null;
|
this.pointerX = null;
|
||||||
this.pointerY = null;
|
this.pointerY = null;
|
||||||
@ -78,11 +78,15 @@ const PointerWatcher = new Lang.Class({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_onIdleMonitorWatch: function(monitor, id, userBecameIdle) {
|
_onIdleMonitorBecameActive: function(monitor) {
|
||||||
this._idle = userBecameIdle;
|
this._idle = false;
|
||||||
if (!userBecameIdle)
|
this._updatePointer();
|
||||||
this._updatePointer();
|
this._updateTimeout();
|
||||||
|
},
|
||||||
|
|
||||||
|
_onIdleMonitorBecameIdle: function(monitor) {
|
||||||
|
this._idle = true;
|
||||||
|
this._idleMonitor.add_user_active_watch(Lang.bind(this, this._onIdleMonitorBecameActive));
|
||||||
this._updateTimeout();
|
this._updateTimeout();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
|
||||||
const Cairo = imports.cairo;
|
|
||||||
const Clutter = imports.gi.Clutter;
|
const Clutter = imports.gi.Clutter;
|
||||||
const GLib = imports.gi.GLib;
|
const GLib = imports.gi.GLib;
|
||||||
const Gtk = imports.gi.Gtk;
|
const Gtk = imports.gi.Gtk;
|
||||||
@ -12,8 +11,10 @@ const St = imports.gi.St;
|
|||||||
const Atk = imports.gi.Atk;
|
const Atk = imports.gi.Atk;
|
||||||
|
|
||||||
const BoxPointer = imports.ui.boxpointer;
|
const BoxPointer = imports.ui.boxpointer;
|
||||||
|
const GrabHelper = imports.ui.grabHelper;
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
const Params = imports.misc.params;
|
const Params = imports.misc.params;
|
||||||
|
const Separator = imports.ui.separator;
|
||||||
const Tweener = imports.ui.tweener;
|
const Tweener = imports.ui.tweener;
|
||||||
|
|
||||||
const SLIDER_SCROLL_STEP = 0.05; /* Slider scrolling step in % */
|
const SLIDER_SCROLL_STEP = 0.05; /* Slider scrolling step in % */
|
||||||
@ -206,6 +207,7 @@ const PopupBaseMenuItem = new Lang.Class({
|
|||||||
color.alpha / 255);
|
color.alpha / 255);
|
||||||
cr.arc(width / 2, height / 2, width / 3, 0, 2 * Math.PI);
|
cr.arc(width / 2, height / 2, width / 3, 0, 2 * Math.PI);
|
||||||
cr.fill();
|
cr.fill();
|
||||||
|
cr.$dispose();
|
||||||
},
|
},
|
||||||
|
|
||||||
// This returns column widths in logical order (i.e. from the dot
|
// This returns column widths in logical order (i.e. from the dot
|
||||||
@ -404,29 +406,8 @@ const PopupSeparatorMenuItem = new Lang.Class({
|
|||||||
this.parent({ reactive: false,
|
this.parent({ reactive: false,
|
||||||
can_focus: false});
|
can_focus: false});
|
||||||
|
|
||||||
this._drawingArea = new St.DrawingArea({ style_class: 'popup-separator-menu-item' });
|
this._separator = new Separator.HorizontalSeparator({ style_class: 'popup-separator-menu-item' });
|
||||||
this.addActor(this._drawingArea, { span: -1, expand: true });
|
this.addActor(this._separator.actor, { span: -1, expand: true });
|
||||||
this._drawingArea.connect('repaint', Lang.bind(this, this._onRepaint));
|
|
||||||
},
|
|
||||||
|
|
||||||
_onRepaint: function(area) {
|
|
||||||
let cr = area.get_context();
|
|
||||||
let themeNode = area.get_theme_node();
|
|
||||||
let [width, height] = area.get_surface_size();
|
|
||||||
let margin = themeNode.get_length('-margin-horizontal');
|
|
||||||
let gradientHeight = themeNode.get_length('-gradient-height');
|
|
||||||
let startColor = themeNode.get_color('-gradient-start');
|
|
||||||
let endColor = themeNode.get_color('-gradient-end');
|
|
||||||
|
|
||||||
let gradientWidth = (width - margin * 2);
|
|
||||||
let gradientOffset = (height - gradientHeight) / 2;
|
|
||||||
let pattern = new Cairo.LinearGradient(margin, gradientOffset, width - margin, gradientOffset + gradientHeight);
|
|
||||||
pattern.addColorStopRGBA(0, startColor.red / 255, startColor.green / 255, startColor.blue / 255, startColor.alpha / 255);
|
|
||||||
pattern.addColorStopRGBA(0.5, endColor.red / 255, endColor.green / 255, endColor.blue / 255, endColor.alpha / 255);
|
|
||||||
pattern.addColorStopRGBA(1, startColor.red / 255, startColor.green / 255, startColor.blue / 255, startColor.alpha / 255);
|
|
||||||
cr.setSource(pattern);
|
|
||||||
cr.rectangle(margin, gradientOffset, gradientWidth, gradientHeight);
|
|
||||||
cr.fill();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -624,6 +605,7 @@ const PopupSliderMenuItem = new Lang.Class({
|
|||||||
color.alpha / 255);
|
color.alpha / 255);
|
||||||
cr.arc(handleX, handleY, handleRadius, 0, 2 * Math.PI);
|
cr.arc(handleX, handleY, handleRadius, 0, 2 * Math.PI);
|
||||||
cr.fill();
|
cr.fill();
|
||||||
|
cr.$dispose();
|
||||||
},
|
},
|
||||||
|
|
||||||
_startDragging: function(actor, event) {
|
_startDragging: function(actor, event) {
|
||||||
@ -656,20 +638,34 @@ const PopupSliderMenuItem = new Lang.Class({
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
_onScrollEvent: function (actor, event) {
|
scroll: function(event) {
|
||||||
let direction = event.get_scroll_direction();
|
let direction = event.get_scroll_direction();
|
||||||
|
let delta;
|
||||||
|
|
||||||
|
if (event.is_pointer_emulated())
|
||||||
|
return;
|
||||||
|
|
||||||
if (direction == Clutter.ScrollDirection.DOWN) {
|
if (direction == Clutter.ScrollDirection.DOWN) {
|
||||||
this._value = Math.max(0, this._value - SLIDER_SCROLL_STEP);
|
delta = -SLIDER_SCROLL_STEP;
|
||||||
}
|
} else if (direction == Clutter.ScrollDirection.UP) {
|
||||||
else if (direction == Clutter.ScrollDirection.UP) {
|
delta = +SLIDER_SCROLL_STEP;
|
||||||
this._value = Math.min(1, this._value + SLIDER_SCROLL_STEP);
|
} else if (direction == Clutter.ScrollDirection.SMOOTH) {
|
||||||
|
let [dx, dy] = event.get_scroll_delta();
|
||||||
|
// Even though the slider is horizontal, use dy to match
|
||||||
|
// the UP/DOWN above.
|
||||||
|
delta = -dy / 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._value = Math.min(Math.max(0, this._value + delta), 1);
|
||||||
|
|
||||||
this._slider.queue_repaint();
|
this._slider.queue_repaint();
|
||||||
this.emit('value-changed', this._value);
|
this.emit('value-changed', this._value);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onScrollEvent: function(actor, event) {
|
||||||
|
this.scroll(event);
|
||||||
|
},
|
||||||
|
|
||||||
_motionEvent: function(actor, event) {
|
_motionEvent: function(actor, event) {
|
||||||
let absX, absY;
|
let absX, absY;
|
||||||
[absX, absY] = event.get_coords();
|
[absX, absY] = event.get_coords();
|
||||||
@ -871,7 +867,6 @@ const PopupMenuBase = new Lang.Class({
|
|||||||
this.blockSourceEvents = false;
|
this.blockSourceEvents = false;
|
||||||
|
|
||||||
this._activeMenuItem = null;
|
this._activeMenuItem = null;
|
||||||
this._childMenus = [];
|
|
||||||
this._settingsActions = { };
|
this._settingsActions = { };
|
||||||
|
|
||||||
this._sessionUpdatedId = Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
|
this._sessionUpdatedId = Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
|
||||||
@ -893,7 +888,7 @@ const PopupMenuBase = new Lang.Class({
|
|||||||
|
|
||||||
addSettingsAction: function(title, desktopFile) {
|
addSettingsAction: function(title, desktopFile) {
|
||||||
let menuItem = this.addAction(title, function() {
|
let menuItem = this.addAction(title, function() {
|
||||||
let app = Shell.AppSystem.get_default().lookup_setting(desktopFile);
|
let app = Shell.AppSystem.get_default().lookup_app(desktopFile);
|
||||||
|
|
||||||
if (!app) {
|
if (!app) {
|
||||||
log('Settings panel for desktop file ' + desktopFile + ' could not be loaded!');
|
log('Settings panel for desktop file ' + desktopFile + ' could not be loaded!');
|
||||||
@ -918,29 +913,15 @@ const PopupMenuBase = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
isEmpty: function() {
|
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) {
|
isChildMenu: function() {
|
||||||
return this._childMenus.indexOf(menu) != -1;
|
return false;
|
||||||
},
|
|
||||||
|
|
||||||
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);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1204,7 +1185,6 @@ const PopupMenu = new Lang.Class({
|
|||||||
this.actor = this._boxPointer.actor;
|
this.actor = this._boxPointer.actor;
|
||||||
this.actor._delegate = this;
|
this.actor._delegate = this;
|
||||||
this.actor.style_class = 'popup-menu-boxpointer';
|
this.actor.style_class = 'popup-menu-boxpointer';
|
||||||
this.actor.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent));
|
|
||||||
|
|
||||||
this._boxWrapper = new Shell.GenericContainer();
|
this._boxWrapper = new Shell.GenericContainer();
|
||||||
this._boxWrapper.connect('get-preferred-width', Lang.bind(this, this._boxGetPreferredWidth));
|
this._boxWrapper.connect('get-preferred-width', Lang.bind(this, this._boxGetPreferredWidth));
|
||||||
@ -1216,6 +1196,8 @@ const PopupMenu = new Lang.Class({
|
|||||||
|
|
||||||
global.focus_manager.add_group(this.actor);
|
global.focus_manager.add_group(this.actor);
|
||||||
this.actor.reactive = true;
|
this.actor.reactive = true;
|
||||||
|
|
||||||
|
this._childMenus = [];
|
||||||
},
|
},
|
||||||
|
|
||||||
_boxGetPreferredWidth: function (actor, forHeight, alloc) {
|
_boxGetPreferredWidth: function (actor, forHeight, alloc) {
|
||||||
@ -1234,15 +1216,6 @@ const PopupMenu = new Lang.Class({
|
|||||||
this.box.allocate(box, flags);
|
this.box.allocate(box, flags);
|
||||||
},
|
},
|
||||||
|
|
||||||
_onKeyPressEvent: function(actor, event) {
|
|
||||||
if (event.get_key_symbol() == Clutter.Escape) {
|
|
||||||
this.close(BoxPointer.PopupAnimation.FULL);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
setArrowOrigin: function(origin) {
|
setArrowOrigin: function(origin) {
|
||||||
this._boxPointer.setArrowOrigin(origin);
|
this._boxPointer.setArrowOrigin(origin);
|
||||||
},
|
},
|
||||||
@ -1251,6 +1224,28 @@ const PopupMenu = new Lang.Class({
|
|||||||
this._boxPointer.setSourceAlignment(alignment);
|
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) {
|
open: function(animate) {
|
||||||
if (this.isOpen)
|
if (this.isOpen)
|
||||||
return;
|
return;
|
||||||
@ -1269,19 +1264,46 @@ const PopupMenu = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
close: function(animate) {
|
close: function(animate) {
|
||||||
if (!this.isOpen)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (this._activeMenuItem)
|
if (this._activeMenuItem)
|
||||||
this._activeMenuItem.setActive(false);
|
this._activeMenuItem.setActive(false);
|
||||||
|
|
||||||
this._boxPointer.hide(animate);
|
this._childMenus.forEach(function(childMenu) {
|
||||||
|
childMenu.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this._boxPointer.actor.visible)
|
||||||
|
this._boxPointer.hide(animate);
|
||||||
|
|
||||||
|
if (!this.isOpen)
|
||||||
|
return;
|
||||||
|
|
||||||
this.isOpen = false;
|
this.isOpen = false;
|
||||||
this.emit('open-state-changed', false);
|
this.emit('open-state-changed', false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const PopupDummyMenu = new Lang.Class({
|
||||||
|
Name: 'PopupDummyMenu',
|
||||||
|
|
||||||
|
_init: function(sourceActor) {
|
||||||
|
this.sourceActor = sourceActor;
|
||||||
|
this.actor = sourceActor;
|
||||||
|
this.actor._delegate = this;
|
||||||
|
},
|
||||||
|
|
||||||
|
isChildMenu: function() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
open: function() { this.emit('open-state-changed', true); },
|
||||||
|
close: function() { this.emit('open-state-changed', false); },
|
||||||
|
toggle: function() {},
|
||||||
|
destroy: function() {
|
||||||
|
this.emit('destroy');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
Signals.addSignalMethods(PopupDummyMenu.prototype);
|
||||||
|
|
||||||
const PopupSubMenu = new Lang.Class({
|
const PopupSubMenu = new Lang.Class({
|
||||||
Name: 'PopupSubMenu',
|
Name: 'PopupSubMenu',
|
||||||
Extends: PopupMenuBase,
|
Extends: PopupMenuBase,
|
||||||
@ -1536,7 +1558,6 @@ const PopupComboMenu = new Lang.Class({
|
|||||||
|
|
||||||
this.actor = this.box;
|
this.actor = this.box;
|
||||||
this.actor._delegate = this;
|
this.actor._delegate = this;
|
||||||
this.actor.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent));
|
|
||||||
this.actor.connect('key-focus-in', Lang.bind(this, this._onKeyFocusIn));
|
this.actor.connect('key-focus-in', Lang.bind(this, this._onKeyFocusIn));
|
||||||
sourceActor.connect('style-changed',
|
sourceActor.connect('style-changed',
|
||||||
Lang.bind(this, this._onSourceActorStyleChanged));
|
Lang.bind(this, this._onSourceActorStyleChanged));
|
||||||
@ -1544,15 +1565,6 @@ const PopupComboMenu = new Lang.Class({
|
|||||||
global.focus_manager.add_group(this.actor);
|
global.focus_manager.add_group(this.actor);
|
||||||
},
|
},
|
||||||
|
|
||||||
_onKeyPressEvent: function(actor, event) {
|
|
||||||
if (event.get_key_symbol() == Clutter.Escape) {
|
|
||||||
this.close(BoxPointer.PopupAnimation.FULL);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
_onKeyFocusIn: function(actor) {
|
_onKeyFocusIn: function(actor) {
|
||||||
let items = this._getMenuItems();
|
let items = this._getMenuItems();
|
||||||
let activeItem = items[this._activeItemPos];
|
let activeItem = items[this._activeItemPos];
|
||||||
@ -1679,9 +1691,7 @@ const PopupComboBoxMenuItem = new Lang.Class({
|
|||||||
_getTopMenu: function() {
|
_getTopMenu: function() {
|
||||||
let actor = this.actor.get_parent();
|
let actor = this.actor.get_parent();
|
||||||
while (actor) {
|
while (actor) {
|
||||||
if (actor._delegate &&
|
if (actor._delegate && actor._delegate instanceof PopupMenu)
|
||||||
(actor._delegate instanceof PopupMenu ||
|
|
||||||
actor._delegate instanceof PopupComboMenu))
|
|
||||||
return actor._delegate;
|
return actor._delegate;
|
||||||
|
|
||||||
actor = actor.get_parent();
|
actor = actor.get_parent();
|
||||||
@ -2043,17 +2053,8 @@ const PopupMenuManager = new Lang.Class({
|
|||||||
|
|
||||||
_init: function(owner) {
|
_init: function(owner) {
|
||||||
this._owner = owner;
|
this._owner = owner;
|
||||||
this.grabbed = false;
|
this._grabHelper = new GrabHelper.GrabHelper(owner.actor);
|
||||||
|
|
||||||
this._eventCaptureId = 0;
|
|
||||||
this._enterEventId = 0;
|
|
||||||
this._leaveEventId = 0;
|
|
||||||
this._keyFocusNotifyId = 0;
|
|
||||||
this._activeMenu = null;
|
|
||||||
this._menus = [];
|
this._menus = [];
|
||||||
this._menuStack = [];
|
|
||||||
this._preGrabInputMode = null;
|
|
||||||
this._grabbedFromKeynav = false;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
addMenu: function(menu, position) {
|
addMenu: function(menu, position) {
|
||||||
@ -2072,6 +2073,8 @@ const PopupMenuManager = new Lang.Class({
|
|||||||
|
|
||||||
let source = menu.sourceActor;
|
let source = menu.sourceActor;
|
||||||
if (source) {
|
if (source) {
|
||||||
|
if (!menu.blockSourceEvents)
|
||||||
|
this._grabHelper.addActor(source);
|
||||||
menudata.enterId = source.connect('enter-event', Lang.bind(this, function() { this._onMenuSourceEnter(menu); }));
|
menudata.enterId = source.connect('enter-event', Lang.bind(this, function() { this._onMenuSourceEnter(menu); }));
|
||||||
menudata.focusInId = source.connect('key-focus-in', Lang.bind(this, function() { this._onMenuSourceEnter(menu); }));
|
menudata.focusInId = source.connect('key-focus-in', Lang.bind(this, function() { this._onMenuSourceEnter(menu); }));
|
||||||
}
|
}
|
||||||
@ -2084,7 +2087,7 @@ const PopupMenuManager = new Lang.Class({
|
|||||||
|
|
||||||
removeMenu: function(menu) {
|
removeMenu: function(menu) {
|
||||||
if (menu == this._activeMenu)
|
if (menu == this._activeMenu)
|
||||||
this._closeMenu();
|
this._closeMenu(menu);
|
||||||
|
|
||||||
let position = this._findMenu(menu);
|
let position = this._findMenu(menu);
|
||||||
if (position == -1) // not a menu we manage
|
if (position == -1) // not a menu we manage
|
||||||
@ -2101,79 +2104,29 @@ const PopupMenuManager = new Lang.Class({
|
|||||||
if (menudata.focusInId)
|
if (menudata.focusInId)
|
||||||
menu.sourceActor.disconnect(menudata.focusInId);
|
menu.sourceActor.disconnect(menudata.focusInId);
|
||||||
|
|
||||||
|
if (menu.sourceActor)
|
||||||
|
this._grabHelper.removeActor(menu.sourceActor);
|
||||||
this._menus.splice(position, 1);
|
this._menus.splice(position, 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
_grab: function() {
|
get activeMenu() {
|
||||||
Main.pushModal(this._owner.actor);
|
let actor = this._grabHelper.currentGrab.actor;
|
||||||
|
if (actor)
|
||||||
this._eventCaptureId = global.stage.connect('captured-event', Lang.bind(this, this._onEventCapture));
|
return actor._delegate;
|
||||||
// captured-event doesn't see enter/leave events
|
else
|
||||||
this._enterEventId = global.stage.connect('enter-event', Lang.bind(this, this._onEventCapture));
|
return null;
|
||||||
this._leaveEventId = global.stage.connect('leave-event', Lang.bind(this, this._onEventCapture));
|
|
||||||
this._keyFocusNotifyId = global.stage.connect('notify::key-focus', Lang.bind(this, this._onKeyFocusChanged));
|
|
||||||
|
|
||||||
this.grabbed = true;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_ungrab: function() {
|
ignoreRelease: function() {
|
||||||
global.stage.disconnect(this._eventCaptureId);
|
return this._grabHelper.ignoreRelease();
|
||||||
this._eventCaptureId = 0;
|
|
||||||
global.stage.disconnect(this._enterEventId);
|
|
||||||
this._enterEventId = 0;
|
|
||||||
global.stage.disconnect(this._leaveEventId);
|
|
||||||
this._leaveEventId = 0;
|
|
||||||
global.stage.disconnect(this._keyFocusNotifyId);
|
|
||||||
this._keyFocusNotifyId = 0;
|
|
||||||
|
|
||||||
this.grabbed = false;
|
|
||||||
Main.popModal(this._owner.actor);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_onMenuOpenState: function(menu, open) {
|
_onMenuOpenState: function(menu, open) {
|
||||||
if (open) {
|
if (open) {
|
||||||
if (this._activeMenu && this._activeMenu.isChildMenu(menu)) {
|
this._grabHelper.grab({ actor: menu.actor, modal: true, focus: menu.sourceActor,
|
||||||
this._menuStack.push(this._activeMenu);
|
onUngrab: Lang.bind(this, this._closeMenu, menu) });
|
||||||
menu.actor.grab_key_focus();
|
|
||||||
}
|
|
||||||
this._activeMenu = menu;
|
|
||||||
} else {
|
} else {
|
||||||
if (this._menuStack.length > 0) {
|
this._grabHelper.ungrab({ actor: menu.actor });
|
||||||
this._activeMenu = this._menuStack.pop();
|
|
||||||
if (menu.sourceActor)
|
|
||||||
menu.sourceActor.grab_key_focus();
|
|
||||||
this._didPop = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check what the focus was before calling pushModal/popModal
|
|
||||||
let focus = global.stage.key_focus;
|
|
||||||
let hadFocus = focus && this._activeMenuContains(focus);
|
|
||||||
|
|
||||||
if (open) {
|
|
||||||
if (!this.grabbed) {
|
|
||||||
this._preGrabInputMode = global.stage_input_mode;
|
|
||||||
this._grabbedFromKeynav = hadFocus;
|
|
||||||
this._grab();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hadFocus)
|
|
||||||
focus.grab_key_focus();
|
|
||||||
else
|
|
||||||
menu.actor.grab_key_focus();
|
|
||||||
} else if (menu == this._activeMenu) {
|
|
||||||
if (this.grabbed)
|
|
||||||
this._ungrab();
|
|
||||||
this._activeMenu = null;
|
|
||||||
|
|
||||||
if (this._grabbedFromKeynav) {
|
|
||||||
if (this._preGrabInputMode == Shell.StageInputMode.FOCUSED)
|
|
||||||
global.stage_input_mode = Shell.StageInputMode.FOCUSED;
|
|
||||||
if (hadFocus && menu.sourceActor)
|
|
||||||
menu.sourceActor.grab_key_focus();
|
|
||||||
else if (focus)
|
|
||||||
focus.grab_key_focus();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -2185,87 +2138,38 @@ const PopupMenuManager = new Lang.Class({
|
|||||||
this.removeMenu(childMenu);
|
this.removeMenu(childMenu);
|
||||||
},
|
},
|
||||||
|
|
||||||
// change the currently-open menu without dropping grab
|
|
||||||
_changeMenu: function(newMenu) {
|
_changeMenu: function(newMenu) {
|
||||||
if (this._activeMenu) {
|
let oldMenu = this.activeMenu;
|
||||||
// _onOpenMenuState will drop the grab if it sees
|
if (oldMenu) {
|
||||||
// this._activeMenu being closed; so clear _activeMenu
|
|
||||||
// before closing it to keep that from happening
|
|
||||||
let oldMenu = this._activeMenu;
|
|
||||||
this._activeMenu = null;
|
|
||||||
for (let i = this._menuStack.length - 1; i >= 0; i--)
|
|
||||||
this._menuStack[i].close(BoxPointer.PopupAnimation.FADE);
|
|
||||||
oldMenu.close(BoxPointer.PopupAnimation.FADE);
|
oldMenu.close(BoxPointer.PopupAnimation.FADE);
|
||||||
newMenu.open(BoxPointer.PopupAnimation.FADE);
|
newMenu.open(BoxPointer.PopupAnimation.FADE);
|
||||||
} else
|
} else {
|
||||||
newMenu.open(BoxPointer.PopupAnimation.FULL);
|
newMenu.open(BoxPointer.PopupAnimation.FULL);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_onMenuSourceEnter: function(menu) {
|
_onMenuSourceEnter: function(menu) {
|
||||||
if (!this.grabbed || menu == this._activeMenu)
|
if (!this._grabHelper.grabbed)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (this._activeMenu && this._activeMenu.isChildMenu(menu))
|
if (this._grabHelper.isActorGrabbed(menu.actor))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (this._menuStack.indexOf(menu) != -1)
|
let isChildMenu = this._grabHelper.grabStack.some(function(grab) {
|
||||||
return false;
|
let existingMenu = grab.actor._delegate;
|
||||||
|
return existingMenu.isChildMenu(menu);
|
||||||
if (this._menuStack.length > 0 && this._menuStack[0].isChildMenu(menu))
|
});
|
||||||
|
if (isChildMenu)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
this._changeMenu(menu);
|
this._changeMenu(menu);
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
_onKeyFocusChanged: function() {
|
|
||||||
if (!this.grabbed || !this._activeMenu)
|
|
||||||
return;
|
|
||||||
|
|
||||||
let focus = global.stage.key_focus;
|
|
||||||
if (focus) {
|
|
||||||
if (this._activeMenuContains(focus))
|
|
||||||
return;
|
|
||||||
if (this._menuStack.length > 0)
|
|
||||||
return;
|
|
||||||
if (focus._delegate && focus._delegate.menu &&
|
|
||||||
this._findMenu(focus._delegate.menu) != -1)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._closeMenu();
|
|
||||||
},
|
|
||||||
|
|
||||||
_onMenuDestroy: function(menu) {
|
_onMenuDestroy: function(menu) {
|
||||||
this.removeMenu(menu);
|
this.removeMenu(menu);
|
||||||
},
|
},
|
||||||
|
|
||||||
_activeMenuContains: function(actor) {
|
|
||||||
return this._activeMenu != null
|
|
||||||
&& (this._activeMenu.actor.contains(actor) ||
|
|
||||||
(this._activeMenu.sourceActor && this._activeMenu.sourceActor.contains(actor)));
|
|
||||||
},
|
|
||||||
|
|
||||||
_eventIsOnActiveMenu: function(event) {
|
|
||||||
return this._activeMenuContains(event.get_source());
|
|
||||||
},
|
|
||||||
|
|
||||||
_shouldBlockEvent: function(event) {
|
|
||||||
let src = event.get_source();
|
|
||||||
|
|
||||||
if (this._activeMenu != null && this._activeMenu.actor.contains(src))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (let i = 0; i < this._menus.length; i++) {
|
|
||||||
let menu = this._menus[i].menu;
|
|
||||||
if (menu.sourceActor && !menu.blockSourceEvents && menu.sourceActor.contains(src)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
_findMenu: function(item) {
|
_findMenu: function(item) {
|
||||||
for (let i = 0; i < this._menus.length; i++) {
|
for (let i = 0; i < this._menus.length; i++) {
|
||||||
let menudata = this._menus[i];
|
let menudata = this._menus[i];
|
||||||
@ -2275,41 +2179,11 @@ const PopupMenuManager = new Lang.Class({
|
|||||||
return -1;
|
return -1;
|
||||||
},
|
},
|
||||||
|
|
||||||
_onEventCapture: function(actor, event) {
|
_closeMenu: function(isUser, menu) {
|
||||||
if (!this.grabbed)
|
// If this isn't a user action, we called close()
|
||||||
return false;
|
// on the BoxPointer ourselves, so we shouldn't
|
||||||
|
// reanimate.
|
||||||
if (this._owner.menuEventFilter &&
|
if (isUser)
|
||||||
this._owner.menuEventFilter(event))
|
menu.close(BoxPointer.PopupAnimation.FULL);
|
||||||
return true;
|
|
||||||
|
|
||||||
if (this._didPop) {
|
|
||||||
this._didPop = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let activeMenuContains = this._eventIsOnActiveMenu(event);
|
|
||||||
let eventType = event.type();
|
|
||||||
|
|
||||||
if (eventType == Clutter.EventType.BUTTON_RELEASE) {
|
|
||||||
if (activeMenuContains) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
this._closeMenu();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if (eventType == Clutter.EventType.BUTTON_PRESS && !activeMenuContains) {
|
|
||||||
this._closeMenu();
|
|
||||||
return true;
|
|
||||||
} else if (!this._shouldBlockEvent(event)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
_closeMenu: function() {
|
|
||||||
if (this._activeMenu != null)
|
|
||||||
this._activeMenu.close(BoxPointer.PopupAnimation.FULL);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
|
||||||
|
const GdkPixbuf = imports.gi.GdkPixbuf;
|
||||||
const Gio = imports.gi.Gio;
|
const Gio = imports.gi.Gio;
|
||||||
const GLib = imports.gi.GLib;
|
const GLib = imports.gi.GLib;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
const St = imports.gi.St;
|
const St = imports.gi.St;
|
||||||
|
const Shell = imports.gi.Shell;
|
||||||
|
|
||||||
const FileUtils = imports.misc.fileUtils;
|
const FileUtils = imports.misc.fileUtils;
|
||||||
const Search = imports.ui.search;
|
const Search = imports.ui.search;
|
||||||
@ -29,105 +31,183 @@ const SearchProviderIface = <interface name="org.gnome.Shell.SearchProvider">
|
|||||||
</method>
|
</method>
|
||||||
</interface>;
|
</interface>;
|
||||||
|
|
||||||
var SearchProviderProxy = Gio.DBusProxy.makeProxyWrapper(SearchProviderIface);
|
const SearchProvider2Iface = <interface name="org.gnome.Shell.SearchProvider2">
|
||||||
|
<method name="GetInitialResultSet">
|
||||||
|
<arg type="as" direction="in" />
|
||||||
|
<arg type="as" direction="out" />
|
||||||
|
</method>
|
||||||
|
<method name="GetSubsearchResultSet">
|
||||||
|
<arg type="as" direction="in" />
|
||||||
|
<arg type="as" direction="in" />
|
||||||
|
<arg type="as" direction="out" />
|
||||||
|
</method>
|
||||||
|
<method name="GetResultMetas">
|
||||||
|
<arg type="as" direction="in" />
|
||||||
|
<arg type="aa{sv}" direction="out" />
|
||||||
|
</method>
|
||||||
|
<method name="ActivateResult">
|
||||||
|
<arg type="s" direction="in" />
|
||||||
|
<arg type="as" direction="in" />
|
||||||
|
<arg type="u" direction="in" />
|
||||||
|
</method>
|
||||||
|
<method name="LaunchSearch">
|
||||||
|
<arg type="as" direction="in" />
|
||||||
|
<arg type="u" direction="in" />
|
||||||
|
</method>
|
||||||
|
</interface>;
|
||||||
|
|
||||||
|
var SearchProviderProxy = Gio.DBusProxy.makeProxyWrapper(SearchProviderIface);
|
||||||
|
var SearchProvider2Proxy = Gio.DBusProxy.makeProxyWrapper(SearchProvider2Iface);
|
||||||
|
|
||||||
function loadRemoteSearchProviders(addProviderCallback) {
|
function loadRemoteSearchProviders(addProviderCallback) {
|
||||||
let dataDirs = GLib.get_system_data_dirs();
|
let data = { loadedProviders: [],
|
||||||
let loadedProviders = {};
|
objectPaths: {},
|
||||||
for (let i = 0; i < dataDirs.length; i++) {
|
addProviderCallback: addProviderCallback };
|
||||||
let path = GLib.build_filenamev([dataDirs[i], 'gnome-shell', 'search-providers']);
|
FileUtils.collectFromDatadirsAsync('search-providers',
|
||||||
let dir = Gio.file_new_for_path(path);
|
{ loadedCallback: remoteProvidersLoaded,
|
||||||
if (!dir.query_exists(null))
|
processFile: loadRemoteSearchProvider,
|
||||||
continue;
|
data: data
|
||||||
loadRemoteSearchProvidersFromDir(dir, loadedProviders, addProviderCallback);
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadRemoteSearchProvider(file, info, data) {
|
||||||
|
let keyfile = new GLib.KeyFile();
|
||||||
|
let path = file.get_path();
|
||||||
|
|
||||||
|
try {
|
||||||
|
keyfile.load_from_file(path, 0);
|
||||||
|
} catch(e) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
function loadRemoteSearchProvidersFromDir(dir, loadedProviders, addProviderCallback) {
|
if (!keyfile.has_group(KEY_FILE_GROUP))
|
||||||
let dirPath = dir.get_path();
|
return;
|
||||||
FileUtils.listDirAsync(dir, Lang.bind(this, function(files) {
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
|
||||||
let keyfile = new GLib.KeyFile();
|
|
||||||
let path = GLib.build_filenamev([dirPath, files[i].get_name()]);
|
|
||||||
|
|
||||||
try {
|
let remoteProvider;
|
||||||
keyfile.load_from_file(path, 0);
|
try {
|
||||||
} catch(e) {
|
let group = KEY_FILE_GROUP;
|
||||||
continue;
|
let busName = keyfile.get_string(group, 'BusName');
|
||||||
}
|
let objectPath = keyfile.get_string(group, 'ObjectPath');
|
||||||
|
|
||||||
if (!keyfile.has_group(KEY_FILE_GROUP))
|
if (data.objectPaths[objectPath])
|
||||||
continue;
|
return;
|
||||||
|
|
||||||
let remoteProvider, title;
|
let appInfo = null;
|
||||||
try {
|
try {
|
||||||
let group = KEY_FILE_GROUP;
|
let desktopId = keyfile.get_string(group, 'DesktopId');
|
||||||
let busName = keyfile.get_string(group, 'BusName');
|
appInfo = Gio.DesktopAppInfo.new(desktopId);
|
||||||
let objectPath = keyfile.get_string(group, 'ObjectPath');
|
} catch (e) {
|
||||||
|
log('Ignoring search provider ' + path + ': missing DesktopId');
|
||||||
if (loadedProviders[objectPath])
|
return;
|
||||||
continue;
|
|
||||||
|
|
||||||
let appInfo = null;
|
|
||||||
try {
|
|
||||||
let desktopId = keyfile.get_string(group, 'DesktopId');
|
|
||||||
appInfo = Gio.DesktopAppInfo.new(desktopId);
|
|
||||||
} catch (e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
let icon;
|
|
||||||
if (appInfo) {
|
|
||||||
icon = appInfo.get_icon();
|
|
||||||
title = appInfo.get_name();
|
|
||||||
} else {
|
|
||||||
let iconName = keyfile.get_string(group, 'Icon');
|
|
||||||
icon = new Gio.ThemedIcon({ name: iconName });
|
|
||||||
title = keyfile.get_locale_string(group, 'Title', null);
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteProvider = new RemoteSearchProvider(title,
|
|
||||||
icon,
|
|
||||||
busName,
|
|
||||||
objectPath);
|
|
||||||
loadedProviders[objectPath] = remoteProvider;
|
|
||||||
} catch(e) {
|
|
||||||
log('Failed to add search provider "%s": %s'.format(title, e.toString()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
addProviderCallback(remoteProvider);
|
|
||||||
}
|
}
|
||||||
}));
|
|
||||||
|
|
||||||
};
|
let version = '1';
|
||||||
|
try {
|
||||||
|
version = keyfile.get_string(group, 'Version');
|
||||||
|
} catch (e) {
|
||||||
|
// ignore error
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version >= 2)
|
||||||
|
remoteProvider = new RemoteSearchProvider2(appInfo, busName, objectPath);
|
||||||
|
else
|
||||||
|
remoteProvider = new RemoteSearchProvider(appInfo, busName, objectPath);
|
||||||
|
|
||||||
|
data.objectPaths[objectPath] = remoteProvider;
|
||||||
|
data.loadedProviders.push(remoteProvider);
|
||||||
|
} catch(e) {
|
||||||
|
log('Failed to add search provider %s: %s'.format(path, e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function remoteProvidersLoaded(loadState) {
|
||||||
|
let searchSettings = new Gio.Settings({ schema: Search.SEARCH_PROVIDERS_SCHEMA });
|
||||||
|
let sortOrder = searchSettings.get_strv('sort-order');
|
||||||
|
|
||||||
|
// Special case gnome-control-center to be always active and always first
|
||||||
|
sortOrder.unshift('gnome-control-center.desktop');
|
||||||
|
|
||||||
|
let numSorted = sortOrder.length;
|
||||||
|
|
||||||
|
loadState.loadedProviders.sort(
|
||||||
|
function(providerA, providerB) {
|
||||||
|
let idxA, idxB;
|
||||||
|
let appIdA, appIdB;
|
||||||
|
|
||||||
|
appIdA = providerA.appInfo.get_id();
|
||||||
|
appIdB = providerB.appInfo.get_id();
|
||||||
|
|
||||||
|
idxA = sortOrder.indexOf(appIdA);
|
||||||
|
idxB = sortOrder.indexOf(appIdB);
|
||||||
|
|
||||||
|
// if no provider is found in the order, use alphabetical order
|
||||||
|
if ((idxA == -1) && (idxB == -1)) {
|
||||||
|
let nameA = providerA.appInfo.get_name();
|
||||||
|
let nameB = providerB.appInfo.get_name();
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// if providerB isn't found, it's sorted after providerA
|
||||||
|
if (idxB == -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// finally, if both providers are found, return their order in the list
|
||||||
|
return (idxA - idxB);
|
||||||
|
});
|
||||||
|
|
||||||
|
loadState.loadedProviders.forEach(
|
||||||
|
function(provider) {
|
||||||
|
loadState.addProviderCallback(provider);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const RemoteSearchProvider = new Lang.Class({
|
const RemoteSearchProvider = new Lang.Class({
|
||||||
Name: 'RemoteSearchProvider',
|
Name: 'RemoteSearchProvider',
|
||||||
Extends: Search.SearchProvider,
|
|
||||||
|
|
||||||
_init: function(title, icon, dbusName, dbusPath) {
|
_init: function(appInfo, dbusName, dbusPath, proxyType) {
|
||||||
this._proxy = new SearchProviderProxy(Gio.DBus.session,
|
if (!proxyType)
|
||||||
dbusName, dbusPath);
|
proxyType = SearchProviderProxy;
|
||||||
|
|
||||||
|
this.proxy = new proxyType(Gio.DBus.session,
|
||||||
|
dbusName, dbusPath, Lang.bind(this, this._onProxyConstructed));
|
||||||
|
|
||||||
|
this.appInfo = appInfo;
|
||||||
|
this.id = appInfo.get_id();
|
||||||
|
this.isRemoteProvider = true;
|
||||||
|
|
||||||
this.parent(title.toUpperCase());
|
|
||||||
this._cancellable = new Gio.Cancellable();
|
this._cancellable = new Gio.Cancellable();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onProxyConstructed: function(proxy) {
|
||||||
|
// Do nothing
|
||||||
|
},
|
||||||
|
|
||||||
createIcon: function(size, meta) {
|
createIcon: function(size, meta) {
|
||||||
|
let gicon;
|
||||||
if (meta['gicon']) {
|
if (meta['gicon']) {
|
||||||
return new St.Icon({ gicon: Gio.icon_new_for_string(meta['gicon']),
|
gicon = Gio.icon_new_for_string(meta['gicon']);
|
||||||
icon_size: size });
|
|
||||||
} else if (meta['icon-data']) {
|
} else if (meta['icon-data']) {
|
||||||
let [width, height, rowStride, hasAlpha,
|
let [width, height, rowStride, hasAlpha,
|
||||||
bitsPerSample, nChannels, data] = meta['icon-data'];
|
bitsPerSample, nChannels, data] = meta['icon-data'];
|
||||||
let textureCache = St.TextureCache.get_default();
|
gicon = Shell.util_create_pixbuf_from_data(data, GdkPixbuf.Colorspace.RGB, hasAlpha,
|
||||||
return textureCache.load_from_raw(data, hasAlpha,
|
bitsPerSample, width, height, rowStride);
|
||||||
width, height, rowStride, size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ugh, but we want to fall back to something ...
|
return new St.Icon({ gicon: gicon,
|
||||||
return new St.Icon({ icon_name: 'text-x-generic',
|
|
||||||
icon_size: size });
|
icon_size: size });
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -141,11 +221,11 @@ const RemoteSearchProvider = new Lang.Class({
|
|||||||
this._cancellable.cancel();
|
this._cancellable.cancel();
|
||||||
this._cancellable.reset();
|
this._cancellable.reset();
|
||||||
try {
|
try {
|
||||||
this._proxy.GetInitialResultSetRemote(terms,
|
this.proxy.GetInitialResultSetRemote(terms,
|
||||||
Lang.bind(this, this._getResultsFinished),
|
Lang.bind(this, this._getResultsFinished),
|
||||||
this._cancellable);
|
this._cancellable);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
log('Error calling GetInitialResultSet for provider %s: %s'.format( this.title, e.toString()));
|
log('Error calling GetInitialResultSet for provider %s: %s'.format(this.id, e.toString()));
|
||||||
this.searchSystem.pushResults(this, []);
|
this.searchSystem.pushResults(this, []);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -154,11 +234,11 @@ const RemoteSearchProvider = new Lang.Class({
|
|||||||
this._cancellable.cancel();
|
this._cancellable.cancel();
|
||||||
this._cancellable.reset();
|
this._cancellable.reset();
|
||||||
try {
|
try {
|
||||||
this._proxy.GetSubsearchResultSetRemote(previousResults, newTerms,
|
this.proxy.GetSubsearchResultSetRemote(previousResults, newTerms,
|
||||||
Lang.bind(this, this._getResultsFinished),
|
Lang.bind(this, this._getResultsFinished),
|
||||||
this._cancellable);
|
this._cancellable);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
log('Error calling GetSubsearchResultSet for provider %s: %s'.format(this.title, e.toString()));
|
log('Error calling GetSubsearchResultSet for provider %s: %s'.format(this.id, e.toString()));
|
||||||
this.searchSystem.pushResults(this, []);
|
this.searchSystem.pushResults(this, []);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -175,6 +255,7 @@ const RemoteSearchProvider = new Lang.Class({
|
|||||||
metas[i][prop] = metas[i][prop].deep_unpack();
|
metas[i][prop] = metas[i][prop].deep_unpack();
|
||||||
resultMetas.push({ id: metas[i]['id'],
|
resultMetas.push({ id: metas[i]['id'],
|
||||||
name: metas[i]['name'],
|
name: metas[i]['name'],
|
||||||
|
description: metas[i]['description'],
|
||||||
createIcon: Lang.bind(this,
|
createIcon: Lang.bind(this,
|
||||||
this.createIcon, metas[i]) });
|
this.createIcon, metas[i]) });
|
||||||
}
|
}
|
||||||
@ -185,18 +266,42 @@ const RemoteSearchProvider = new Lang.Class({
|
|||||||
this._cancellable.cancel();
|
this._cancellable.cancel();
|
||||||
this._cancellable.reset();
|
this._cancellable.reset();
|
||||||
try {
|
try {
|
||||||
this._proxy.GetResultMetasRemote(ids,
|
this.proxy.GetResultMetasRemote(ids,
|
||||||
Lang.bind(this, this._getResultMetasFinished, callback),
|
Lang.bind(this, this._getResultMetasFinished, callback),
|
||||||
this._cancellable);
|
this._cancellable);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
log('Error calling GetResultMetas for provider %s: %s'.format(this.title, e.toString()));
|
log('Error calling GetResultMetas for provider %s: %s'.format(this.id, e.toString()));
|
||||||
callback([]);
|
callback([]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
activateResult: function(id) {
|
activateResult: function(id) {
|
||||||
this._proxy.ActivateResultRemote(id);
|
this.proxy.ActivateResultRemote(id);
|
||||||
|
},
|
||||||
|
|
||||||
|
launchSearch: function(terms) {
|
||||||
|
// the provider is not compatible with the new version of the interface, launch
|
||||||
|
// the app itself but warn so we can catch the error in logs
|
||||||
|
log('Search provider ' + this.appInfo.get_id() + ' does not implement LaunchSearch');
|
||||||
|
this.appInfo.launch([], global.create_app_launch_context());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const RemoteSearchProvider2 = new Lang.Class({
|
||||||
|
Name: 'RemoteSearchProvider2',
|
||||||
|
Extends: RemoteSearchProvider,
|
||||||
|
|
||||||
|
_init: function(appInfo, dbusName, dbusPath) {
|
||||||
|
this.parent(appInfo, dbusName, dbusPath, SearchProvider2Proxy);
|
||||||
|
|
||||||
|
this.canLaunchSearch = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
activateResult: function(id, terms) {
|
||||||
|
this.proxy.ActivateResultRemote(id, terms, global.get_current_time());
|
||||||
|
},
|
||||||
|
|
||||||
|
launchSearch: function(terms) {
|
||||||
|
this.proxy.LaunchSearchRemote(terms, global.get_current_time());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
@ -30,138 +30,6 @@ const EXEC_ARG_KEY = 'exec-arg';
|
|||||||
|
|
||||||
const DIALOG_GROW_TIME = 0.1;
|
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({
|
const RunDialog = new Lang.Class({
|
||||||
Name: 'RunDialog',
|
Name: 'RunDialog',
|
||||||
Extends: ModalDialog.ModalDialog,
|
Extends: ModalDialog.ModalDialog,
|
||||||
@ -202,11 +70,12 @@ const RunDialog = new Lang.Class({
|
|||||||
|
|
||||||
|
|
||||||
let label = new St.Label({ style_class: 'run-dialog-label',
|
let label = new St.Label({ style_class: 'run-dialog-label',
|
||||||
text: _("Please enter a command:") });
|
text: _("Enter a Command") });
|
||||||
|
|
||||||
this.contentLayout.add(label, { y_align: St.Align.START });
|
this.contentLayout.add(label, { y_align: St.Align.START });
|
||||||
|
|
||||||
let entry = new St.Entry({ style_class: 'run-dialog-entry' });
|
let entry = new St.Entry({ style_class: 'run-dialog-entry',
|
||||||
|
can_focus: true });
|
||||||
ShellEntry.addContextMenu(entry);
|
ShellEntry.addContextMenu(entry);
|
||||||
|
|
||||||
entry.label_actor = label;
|
entry.label_actor = label;
|
||||||
@ -219,7 +88,9 @@ const RunDialog = new Lang.Class({
|
|||||||
|
|
||||||
this.contentLayout.add(this._errorBox, { expand: true });
|
this.contentLayout.add(this._errorBox, { expand: true });
|
||||||
|
|
||||||
let errorIcon = new St.Icon({ icon_name: 'dialog-error', icon_size: 24, style_class: 'run-dialog-error-icon' });
|
let errorIcon = new St.Icon({ icon_name: 'dialog-error-symbolic',
|
||||||
|
icon_size: 24,
|
||||||
|
style_class: 'run-dialog-error-icon' });
|
||||||
|
|
||||||
this._errorBox.add(errorIcon, { y_align: St.Align.MIDDLE });
|
this._errorBox.add(errorIcon, { y_align: St.Align.MIDDLE });
|
||||||
|
|
||||||
@ -234,9 +105,11 @@ const RunDialog = new Lang.Class({
|
|||||||
|
|
||||||
this._errorBox.hide();
|
this._errorBox.hide();
|
||||||
|
|
||||||
|
this.setButtons([{ action: Lang.bind(this, this.close),
|
||||||
|
label: _("Close"),
|
||||||
|
key: Clutter.Escape }]);
|
||||||
|
|
||||||
this._pathCompleter = new Gio.FilenameCompleter();
|
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,
|
this._history = new History.HistoryManager({ gsettingsKey: HISTORY_KEY,
|
||||||
entry: this._entryText });
|
entry: this._entryText });
|
||||||
@ -244,34 +117,14 @@ const RunDialog = new Lang.Class({
|
|||||||
let symbol = e.get_key_symbol();
|
let symbol = e.get_key_symbol();
|
||||||
if (symbol == Clutter.Return || symbol == Clutter.KP_Enter) {
|
if (symbol == Clutter.Return || symbol == Clutter.KP_Enter) {
|
||||||
this.popModal();
|
this.popModal();
|
||||||
if (e.get_state() & Clutter.ModifierType.CONTROL_MASK)
|
this._run(o.get_text(),
|
||||||
this._run(o.get_text(), true);
|
e.get_state() & Clutter.ModifierType.CONTROL_MASK);
|
||||||
else
|
if (!this._commandError ||
|
||||||
this._run(o.get_text(), false);
|
!this.pushModal())
|
||||||
if (!this._commandError)
|
|
||||||
this.close();
|
this.close();
|
||||||
else {
|
|
||||||
if (!this.pushModal())
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (symbol == Clutter.Escape) {
|
|
||||||
this.close();
|
|
||||||
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) {
|
if (symbol == Clutter.Tab) {
|
||||||
let text = o.get_text();
|
let text = o.get_text();
|
||||||
let prefix;
|
let prefix;
|
||||||
@ -283,8 +136,6 @@ const RunDialog = new Lang.Class({
|
|||||||
if (postfix != null && postfix.length > 0) {
|
if (postfix != null && postfix.length > 0) {
|
||||||
o.insert_text(postfix, -1);
|
o.insert_text(postfix, -1);
|
||||||
o.set_cursor_position(text.length + postfix.length);
|
o.set_cursor_position(text.length + postfix.length);
|
||||||
if (postfix[postfix.length - 1] == '/')
|
|
||||||
this._getCompletion(text + postfix + 'a');
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -292,11 +143,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) {
|
_getCompletion : function(text) {
|
||||||
if (text.indexOf('/') != -1) {
|
if (text.indexOf('/') != -1) {
|
||||||
return this._pathCompleter.get_completion_suffix(text);
|
return this._pathCompleter.get_completion_suffix(text);
|
||||||
} else {
|
} else {
|
||||||
return this._commandCompleter.getCompletion(text);
|
return this._getCommandCompletion(text);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
277
js/ui/screenshot.js
Normal file
277
js/ui/screenshot.js
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
|
||||||
|
const Clutter = imports.gi.Clutter;
|
||||||
|
const Gdk = imports.gi.Gdk;
|
||||||
|
const Gio = imports.gi.Gio;
|
||||||
|
const GLib = imports.gi.GLib;
|
||||||
|
const Gtk = imports.gi.Gtk;
|
||||||
|
const Lang = imports.lang;
|
||||||
|
const Shell = imports.gi.Shell;
|
||||||
|
const Signals = imports.signals;
|
||||||
|
const St = imports.gi.St;
|
||||||
|
|
||||||
|
const Lightbox = imports.ui.lightbox;
|
||||||
|
const Main = imports.ui.main;
|
||||||
|
const Tweener = imports.ui.tweener;
|
||||||
|
|
||||||
|
const ScreenshotIface = <interface name="org.gnome.Shell.Screenshot">
|
||||||
|
<method name="ScreenshotArea">
|
||||||
|
<arg type="i" direction="in" name="x"/>
|
||||||
|
<arg type="i" direction="in" name="y"/>
|
||||||
|
<arg type="i" direction="in" name="width"/>
|
||||||
|
<arg type="i" direction="in" name="height"/>
|
||||||
|
<arg type="b" direction="in" name="flash"/>
|
||||||
|
<arg type="s" direction="in" name="filename"/>
|
||||||
|
<arg type="b" direction="out" name="success"/>
|
||||||
|
<arg type="s" direction="out" name="filename_used"/>
|
||||||
|
</method>
|
||||||
|
<method name="ScreenshotWindow">
|
||||||
|
<arg type="b" direction="in" name="include_frame"/>
|
||||||
|
<arg type="b" direction="in" name="include_cursor"/>
|
||||||
|
<arg type="b" direction="in" name="flash"/>
|
||||||
|
<arg type="s" direction="in" name="filename"/>
|
||||||
|
<arg type="b" direction="out" name="success"/>
|
||||||
|
<arg type="s" direction="out" name="filename_used"/>
|
||||||
|
</method>
|
||||||
|
<method name="Screenshot">
|
||||||
|
<arg type="b" direction="in" name="include_cursor"/>
|
||||||
|
<arg type="b" direction="in" name="flash"/>
|
||||||
|
<arg type="s" direction="in" name="filename"/>
|
||||||
|
<arg type="b" direction="out" name="success"/>
|
||||||
|
<arg type="s" direction="out" name="filename_used"/>
|
||||||
|
</method>
|
||||||
|
<method name="SelectArea">
|
||||||
|
<arg type="i" direction="out" name="x"/>
|
||||||
|
<arg type="i" direction="out" name="y"/>
|
||||||
|
<arg type="i" direction="out" name="width"/>
|
||||||
|
<arg type="i" direction="out" name="height"/>
|
||||||
|
</method>
|
||||||
|
<method name="FlashArea">
|
||||||
|
<arg type="i" direction="in" name="x"/>
|
||||||
|
<arg type="i" direction="in" name="y"/>
|
||||||
|
<arg type="i" direction="in" name="width"/>
|
||||||
|
<arg type="i" direction="in" name="height"/>
|
||||||
|
</method>
|
||||||
|
</interface>;
|
||||||
|
|
||||||
|
const ScreenshotService = new Lang.Class({
|
||||||
|
Name: 'ScreenshotService',
|
||||||
|
|
||||||
|
_init: function() {
|
||||||
|
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(ScreenshotIface, this);
|
||||||
|
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell/Screenshot');
|
||||||
|
|
||||||
|
Gio.DBus.session.own_name('org.gnome.Shell.Screenshot', Gio.BusNameOwnerFlags.REPLACE, null, null);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onScreenshotComplete: function(obj, result, area, filenameUsed, flash, invocation) {
|
||||||
|
if (flash && result) {
|
||||||
|
let flashspot = new Flashspot(area);
|
||||||
|
flashspot.fire();
|
||||||
|
}
|
||||||
|
|
||||||
|
let retval = GLib.Variant.new('(bs)', [result, filenameUsed]);
|
||||||
|
invocation.return_value(retval);
|
||||||
|
},
|
||||||
|
|
||||||
|
ScreenshotAreaAsync : function (params, invocation) {
|
||||||
|
let [x, y, width, height, flash, filename, callback] = params;
|
||||||
|
if (height <= 0 || width <= 0) {
|
||||||
|
invocation.return_error_literal(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED,
|
||||||
|
"Invalid params");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let screenshot = new Shell.Screenshot();
|
||||||
|
screenshot.screenshot_area (x, y, width, height, filename,
|
||||||
|
Lang.bind(this, this._onScreenshotComplete,
|
||||||
|
flash, invocation));
|
||||||
|
},
|
||||||
|
|
||||||
|
ScreenshotWindowAsync : function (params, invocation) {
|
||||||
|
let [include_frame, include_cursor, flash, filename] = params;
|
||||||
|
let screenshot = new Shell.Screenshot();
|
||||||
|
screenshot.screenshot_window (include_frame, include_cursor, filename,
|
||||||
|
Lang.bind(this, this._onScreenshotComplete,
|
||||||
|
flash, invocation));
|
||||||
|
},
|
||||||
|
|
||||||
|
ScreenshotAsync : function (params, invocation) {
|
||||||
|
let [include_cursor, flash, filename] = params;
|
||||||
|
let screenshot = new Shell.Screenshot();
|
||||||
|
screenshot.screenshot(include_cursor, filename,
|
||||||
|
Lang.bind(this, this._onScreenshotComplete,
|
||||||
|
flash, invocation));
|
||||||
|
},
|
||||||
|
|
||||||
|
SelectAreaAsync: function (params, invocation) {
|
||||||
|
let selectArea = new SelectArea();
|
||||||
|
selectArea.show();
|
||||||
|
selectArea.connect('finished', Lang.bind(this,
|
||||||
|
function(selectArea, areaRectangle) {
|
||||||
|
if (areaRectangle) {
|
||||||
|
let retval = GLib.Variant.new('(iiii)',
|
||||||
|
[areaRectangle.x, areaRectangle.y,
|
||||||
|
areaRectangle.width, areaRectangle.height]);
|
||||||
|
invocation.return_value(retval);
|
||||||
|
} else {
|
||||||
|
invocation.return_error_literal(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED,
|
||||||
|
"Operation was cancelled");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
FlashArea: function(x, y, width, height) {
|
||||||
|
let flashspot = new Flashspot({ x : x, y : y, width: width, height: height});
|
||||||
|
flashspot.fire();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const SelectArea = new Lang.Class({
|
||||||
|
Name: 'SelectArea',
|
||||||
|
|
||||||
|
_init: function() {
|
||||||
|
this._startX = -1;
|
||||||
|
this._startY = -1;
|
||||||
|
this._lastX = 0;
|
||||||
|
this._lastY = 0;
|
||||||
|
|
||||||
|
this._initRubberbandColors();
|
||||||
|
|
||||||
|
this._group = new St.Widget({ visible: false,
|
||||||
|
reactive: true,
|
||||||
|
x: 0,
|
||||||
|
y: 0 });
|
||||||
|
Main.uiGroup.add_actor(this._group);
|
||||||
|
|
||||||
|
this._group.connect('button-press-event',
|
||||||
|
Lang.bind(this, this._onButtonPress));
|
||||||
|
this._group.connect('button-release-event',
|
||||||
|
Lang.bind(this, this._onButtonRelease));
|
||||||
|
this._group.connect('key-press-event',
|
||||||
|
Lang.bind(this, this._onKeyPress));
|
||||||
|
this._group.connect('motion-event',
|
||||||
|
Lang.bind(this, this._onMotionEvent));
|
||||||
|
|
||||||
|
let constraint = new Clutter.BindConstraint({ source: global.stage,
|
||||||
|
coordinate: Clutter.BindCoordinate.ALL });
|
||||||
|
this._group.add_constraint(constraint);
|
||||||
|
|
||||||
|
this._rubberband = new Clutter.Rectangle({ color: this._background,
|
||||||
|
has_border: true,
|
||||||
|
border_width: 1,
|
||||||
|
border_color: this._border });
|
||||||
|
this._group.add_actor(this._rubberband);
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function() {
|
||||||
|
if (!Main.pushModal(this._group) || this._group.visible)
|
||||||
|
return;
|
||||||
|
|
||||||
|
global.set_cursor(Shell.Cursor.CROSSHAIR);
|
||||||
|
this._group.visible = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
_initRubberbandColors: function() {
|
||||||
|
function colorFromRGBA(rgba) {
|
||||||
|
return new Clutter.Color({ red: rgba.red * 255,
|
||||||
|
green: rgba.green * 255,
|
||||||
|
blue: rgba.blue * 255,
|
||||||
|
alpha: rgba.alpha * 255 });
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = new Gtk.WidgetPath();
|
||||||
|
path.append_type(Gtk.IconView);
|
||||||
|
|
||||||
|
let context = new Gtk.StyleContext();
|
||||||
|
context.set_path(path);
|
||||||
|
context.add_class('rubberband');
|
||||||
|
|
||||||
|
this._background = colorFromRGBA(context.get_background_color(Gtk.StateFlags.NORMAL));
|
||||||
|
this._border = colorFromRGBA(context.get_border_color(Gtk.StateFlags.NORMAL));
|
||||||
|
},
|
||||||
|
|
||||||
|
_getGeometry: function() {
|
||||||
|
return { x: Math.min(this._startX, this._lastX),
|
||||||
|
y: Math.min(this._startY, this._lastY),
|
||||||
|
width: Math.abs(this._startX - this._lastX),
|
||||||
|
height: Math.abs(this._startY - this._lastY) };
|
||||||
|
},
|
||||||
|
|
||||||
|
_onKeyPress: function(actor, event) {
|
||||||
|
if (event.get_key_symbol() == Clutter.Escape)
|
||||||
|
this._destroy(null, false);
|
||||||
|
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
_onMotionEvent: function(actor, event) {
|
||||||
|
if (this._startX == -1 || this._startY == -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
[this._lastX, this._lastY] = event.get_coords();
|
||||||
|
let geometry = this._getGeometry();
|
||||||
|
|
||||||
|
this._rubberband.set_position(geometry.x, geometry.y);
|
||||||
|
this._rubberband.set_size(geometry.width, geometry.height);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
_onButtonPress: function(actor, event) {
|
||||||
|
[this._startX, this._startY] = event.get_coords();
|
||||||
|
this._rubberband.set_position(this._startX, this._startY);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
_onButtonRelease: function(actor, event) {
|
||||||
|
this._destroy(this._getGeometry(), true);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
_destroy: function(geometry, fade) {
|
||||||
|
Tweener.addTween(this._group,
|
||||||
|
{ opacity: 0,
|
||||||
|
time: fade ? 0.2 : 0,
|
||||||
|
transition: 'easeOutQuad',
|
||||||
|
onComplete: Lang.bind(this,
|
||||||
|
function() {
|
||||||
|
Main.popModal(this._group);
|
||||||
|
this._group.destroy();
|
||||||
|
global.unset_cursor();
|
||||||
|
|
||||||
|
this.emit('finished', geometry);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Signals.addSignalMethods(SelectArea.prototype);
|
||||||
|
|
||||||
|
const FLASHSPOT_ANIMATION_OUT_TIME = 0.5; // seconds
|
||||||
|
|
||||||
|
const Flashspot = new Lang.Class({
|
||||||
|
Name: 'Flashspot',
|
||||||
|
Extends: Lightbox.Lightbox,
|
||||||
|
|
||||||
|
_init: function(area) {
|
||||||
|
this.parent(Main.uiGroup, { inhibitEvents: true,
|
||||||
|
width: area.width,
|
||||||
|
height: area.height });
|
||||||
|
|
||||||
|
this.actor.style_class = 'flashspot';
|
||||||
|
this.actor.set_position(area.x, area.y);
|
||||||
|
},
|
||||||
|
|
||||||
|
fire: function() {
|
||||||
|
this.actor.show();
|
||||||
|
this.actor.opacity = 255;
|
||||||
|
Tweener.addTween(this.actor,
|
||||||
|
{ opacity: 0,
|
||||||
|
time: FLASHSPOT_ANIMATION_OUT_TIME,
|
||||||
|
transition: 'easeOutQuad',
|
||||||
|
onComplete: Lang.bind(this, function() {
|
||||||
|
this.destroy();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
208
js/ui/search.js
208
js/ui/search.js
@ -1,183 +1,25 @@
|
|||||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
// -*- 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 Lang = imports.lang;
|
||||||
const Signals = imports.signals;
|
const Signals = imports.signals;
|
||||||
const Shell = imports.gi.Shell;
|
|
||||||
const Util = imports.misc.util;
|
|
||||||
|
|
||||||
const FileUtils = imports.misc.fileUtils;
|
const SEARCH_PROVIDERS_SCHEMA = 'org.gnome.desktop.search-providers';
|
||||||
const Main = imports.ui.main;
|
|
||||||
|
|
||||||
// Not currently referenced by the search API, but
|
|
||||||
// this enumeration can be useful for provider
|
|
||||||
// implementations.
|
|
||||||
const MatchType = {
|
|
||||||
NONE: 0,
|
|
||||||
SUBSTRING: 1,
|
|
||||||
PREFIX: 2
|
|
||||||
};
|
|
||||||
|
|
||||||
const SearchResultDisplay = new Lang.Class({
|
|
||||||
Name: 'SearchResultDisplay',
|
|
||||||
|
|
||||||
_init: function(provider) {
|
|
||||||
this.provider = provider;
|
|
||||||
this.actor = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* renderResults:
|
|
||||||
* @results: List of identifier strings
|
|
||||||
* @terms: List of search term strings
|
|
||||||
*
|
|
||||||
* Display the given search matches which resulted
|
|
||||||
* from the given terms. It's expected that not
|
|
||||||
* all results will fit in the space for the container
|
|
||||||
* actor; in this case, show as many as makes sense
|
|
||||||
* for your result type.
|
|
||||||
*
|
|
||||||
* The terms are useful for search match highlighting.
|
|
||||||
*/
|
|
||||||
renderResults: function(results, terms) {
|
|
||||||
throw new Error('Not implemented');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* clear:
|
|
||||||
* Remove all results from this display.
|
|
||||||
*/
|
|
||||||
clear: function() {
|
|
||||||
this.actor.destroy_all_children();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* getVisibleResultCount:
|
|
||||||
*
|
|
||||||
* Returns: The number of actors visible.
|
|
||||||
*/
|
|
||||||
getVisibleResultCount: function() {
|
|
||||||
throw new Error('Not implemented');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SearchProvider:
|
|
||||||
*
|
|
||||||
* Subclass this object to add a new result type
|
|
||||||
* to the search system, then call registerProvider()
|
|
||||||
* in SearchSystem with an instance.
|
|
||||||
* Search is asynchronous and uses the
|
|
||||||
* getInitialResultSet()/getSubsearchResultSet() methods.
|
|
||||||
*/
|
|
||||||
const SearchProvider = new Lang.Class({
|
|
||||||
Name: 'SearchProvider',
|
|
||||||
|
|
||||||
_init: function(title) {
|
|
||||||
this.title = title;
|
|
||||||
this.searchSystem = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* getInitialResultSet:
|
|
||||||
* @terms: Array of search terms, treated as logical AND
|
|
||||||
*
|
|
||||||
* Called when the user first begins a search (most likely
|
|
||||||
* therefore a single term of length one or two), or when
|
|
||||||
* a new term is added.
|
|
||||||
*
|
|
||||||
* Should "return" an array of result identifier strings representing
|
|
||||||
* items which match the given search terms. This
|
|
||||||
* is expected to be a substring match on the metadata for a given
|
|
||||||
* item. Ordering of returned results is up to the discretion of the provider,
|
|
||||||
* but you should follow these heruistics:
|
|
||||||
*
|
|
||||||
* * Put items where the term matches multiple criteria (e.g. name and
|
|
||||||
* description) before single matches
|
|
||||||
* * Put items which match on a prefix before non-prefix substring matches
|
|
||||||
*
|
|
||||||
* We say "return" above, but in order to make the query asynchronous, use
|
|
||||||
* this.searchSystem.pushResults();. The return value should be ignored.
|
|
||||||
*
|
|
||||||
* This function should be fast; do not perform unindexed full-text searches
|
|
||||||
* or network queries.
|
|
||||||
*/
|
|
||||||
getInitialResultSet: function(terms) {
|
|
||||||
throw new Error('Not implemented');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* getSubsearchResultSet:
|
|
||||||
* @previousResults: Array of item identifiers
|
|
||||||
* @newTerms: Updated search terms
|
|
||||||
*
|
|
||||||
* Called when a search is performed which is a "subsearch" of
|
|
||||||
* the previous search; i.e. when every search term has exactly
|
|
||||||
* one corresponding term in the previous search which is a prefix
|
|
||||||
* of the new term.
|
|
||||||
*
|
|
||||||
* This allows search providers to only search through the previous
|
|
||||||
* result set, rather than possibly performing a full re-query.
|
|
||||||
*
|
|
||||||
* Similar to getInitialResultSet, the return value for this will
|
|
||||||
* be ignored; use this.searchSystem.pushResults();.
|
|
||||||
*/
|
|
||||||
getSubsearchResultSet: function(previousResults, newTerms) {
|
|
||||||
throw new Error('Not implemented');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* getResultMetas:
|
|
||||||
* @ids: Result identifier strings
|
|
||||||
*
|
|
||||||
* Call callback with array of objects with 'id', 'name', (both strings) and
|
|
||||||
* 'createIcon' (function(size) returning a Clutter.Texture) properties
|
|
||||||
* with the same number of members as @ids
|
|
||||||
*/
|
|
||||||
getResultMetas: function(ids, callback) {
|
|
||||||
throw new Error('Not implemented');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* createResultActor:
|
|
||||||
* @resultMeta: Object with result metadata
|
|
||||||
* @terms: Array of search terms, should be used for highlighting
|
|
||||||
*
|
|
||||||
* Search providers may optionally override this to render a
|
|
||||||
* particular serch result in a custom fashion. The default
|
|
||||||
* implementation will show the icon next to the name.
|
|
||||||
*
|
|
||||||
* The actor should be an instance of St.Widget, with the style class
|
|
||||||
* 'search-result-content'.
|
|
||||||
*/
|
|
||||||
createResultActor: function(resultMeta, terms) {
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* activateResult:
|
|
||||||
* @id: Result identifier string
|
|
||||||
*
|
|
||||||
* Called when the user chooses a given result.
|
|
||||||
*/
|
|
||||||
activateResult: function(id) {
|
|
||||||
throw new Error('Not implemented');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Signals.addSignalMethods(SearchProvider.prototype);
|
|
||||||
|
|
||||||
const SearchSystem = new Lang.Class({
|
const SearchSystem = new Lang.Class({
|
||||||
Name: 'SearchSystem',
|
Name: 'SearchSystem',
|
||||||
|
|
||||||
_init: function() {
|
_init: function() {
|
||||||
this._providers = [];
|
this._providers = [];
|
||||||
|
this._remoteProviders = [];
|
||||||
this.reset();
|
this.reset();
|
||||||
},
|
},
|
||||||
|
|
||||||
registerProvider: function (provider) {
|
registerProvider: function (provider) {
|
||||||
provider.searchSystem = this;
|
provider.searchSystem = this;
|
||||||
this._providers.push(provider);
|
this._providers.push(provider);
|
||||||
|
|
||||||
|
if (provider.isRemoteProvider)
|
||||||
|
this._remoteProviders.push(provider);
|
||||||
},
|
},
|
||||||
|
|
||||||
unregisterProvider: function (provider) {
|
unregisterProvider: function (provider) {
|
||||||
@ -186,12 +28,20 @@ const SearchSystem = new Lang.Class({
|
|||||||
return;
|
return;
|
||||||
provider.searchSystem = null;
|
provider.searchSystem = null;
|
||||||
this._providers.splice(index, 1);
|
this._providers.splice(index, 1);
|
||||||
|
|
||||||
|
let remoteIndex = this._remoteProviders.indexOf(provider);
|
||||||
|
if (remoteIndex != -1)
|
||||||
|
this._remoteProviders.splice(index, 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
getProviders: function() {
|
getProviders: function() {
|
||||||
return this._providers;
|
return this._providers;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getRemoteProviders: function() {
|
||||||
|
return this._remoteProviders;
|
||||||
|
},
|
||||||
|
|
||||||
getTerms: function() {
|
getTerms: function() {
|
||||||
return this._previousTerms;
|
return this._previousTerms;
|
||||||
},
|
},
|
||||||
@ -210,28 +60,18 @@ const SearchSystem = new Lang.Class({
|
|||||||
this.emit('search-updated', this._previousResults[i]);
|
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) {
|
updateSearchResults: function(terms) {
|
||||||
if (!terms)
|
if (!terms)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let isSubSearch = terms.length == this._previousTerms.length;
|
let searchString = terms.join(' ');
|
||||||
if (isSubSearch) {
|
let previousSearchString = this._previousTerms.join(' ');
|
||||||
for (let i = 0; i < terms.length; i++) {
|
if (searchString == previousSearchString)
|
||||||
if (terms[i].indexOf(this._previousTerms[i]) != 0) {
|
return;
|
||||||
isSubSearch = false;
|
|
||||||
break;
|
let isSubSearch = false;
|
||||||
}
|
if (this._previousTerms.length > 0)
|
||||||
}
|
isSubSearch = searchString.indexOf(previousSearchString) == 0;
|
||||||
}
|
|
||||||
|
|
||||||
let previousResultsArr = this._previousResults;
|
let previousResultsArr = this._previousResults;
|
||||||
|
|
||||||
@ -246,7 +86,7 @@ const SearchSystem = new Lang.Class({
|
|||||||
results.push([provider, []]);
|
results.push([provider, []]);
|
||||||
provider.getSubsearchResultSet(previousResults, terms);
|
provider.getSubsearchResultSet(previousResults, terms);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message);
|
log('A ' + error.name + ' has occured in ' + provider.id + ': ' + error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -256,10 +96,10 @@ const SearchSystem = new Lang.Class({
|
|||||||
results.push([provider, []]);
|
results.push([provider, []]);
|
||||||
provider.getInitialResultSet(terms);
|
provider.getInitialResultSet(terms);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message);
|
log('A ' + error.name + ' has occured in ' + provider.id + ': ' + error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
Signals.addSignalMethods(SearchSystem.prototype);
|
Signals.addSignalMethods(SearchSystem.prototype);
|
||||||
|
@ -4,6 +4,7 @@ const Clutter = imports.gi.Clutter;
|
|||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
const Gtk = imports.gi.Gtk;
|
const Gtk = imports.gi.Gtk;
|
||||||
const Meta = imports.gi.Meta;
|
const Meta = imports.gi.Meta;
|
||||||
|
const Signals = imports.signals;
|
||||||
const St = imports.gi.St;
|
const St = imports.gi.St;
|
||||||
const Atk = imports.gi.Atk;
|
const Atk = imports.gi.Atk;
|
||||||
|
|
||||||
@ -11,10 +12,32 @@ const DND = imports.ui.dnd;
|
|||||||
const IconGrid = imports.ui.iconGrid;
|
const IconGrid = imports.ui.iconGrid;
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
const Overview = imports.ui.overview;
|
const Overview = imports.ui.overview;
|
||||||
|
const Separator = imports.ui.separator;
|
||||||
const Search = imports.ui.search;
|
const Search = imports.ui.search;
|
||||||
|
const Util = imports.misc.util;
|
||||||
|
|
||||||
const MAX_SEARCH_RESULTS_ROWS = 1;
|
const MAX_LIST_SEARCH_RESULTS_ROWS = 3;
|
||||||
|
const MAX_GRID_SEARCH_RESULTS_ROWS = 1;
|
||||||
|
|
||||||
|
const MaxWidthBin = new Lang.Class({
|
||||||
|
Name: 'MaxWidthBin',
|
||||||
|
Extends: St.Bin,
|
||||||
|
|
||||||
|
vfunc_allocate: function(box, flags) {
|
||||||
|
let themeNode = this.get_theme_node();
|
||||||
|
let maxWidth = themeNode.get_max_width();
|
||||||
|
let availWidth = box.x2 - box.x1;
|
||||||
|
let adjustedBox = box;
|
||||||
|
|
||||||
|
if (availWidth > maxWidth) {
|
||||||
|
let excessWidth = availWidth - maxWidth;
|
||||||
|
adjustedBox.x1 += Math.floor(excessWidth / 2);
|
||||||
|
adjustedBox.x2 -= Math.floor(excessWidth / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.parent(adjustedBox, flags);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const SearchResult = new Lang.Class({
|
const SearchResult = new Lang.Class({
|
||||||
Name: 'SearchResult',
|
Name: 'SearchResult',
|
||||||
@ -22,33 +45,104 @@ const SearchResult = new Lang.Class({
|
|||||||
_init: function(provider, metaInfo, terms) {
|
_init: function(provider, metaInfo, terms) {
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.metaInfo = metaInfo;
|
this.metaInfo = metaInfo;
|
||||||
this.actor = new St.Button({ style_class: 'search-result',
|
this.terms = terms;
|
||||||
reactive: true,
|
|
||||||
|
this.actor = new St.Button({ reactive: true,
|
||||||
|
can_focus: true,
|
||||||
|
track_hover: true,
|
||||||
x_align: St.Align.START,
|
x_align: St.Align.START,
|
||||||
y_fill: true });
|
y_fill: true });
|
||||||
|
|
||||||
this.actor._delegate = this;
|
this.actor._delegate = this;
|
||||||
this._dragActorSource = null;
|
this.actor.connect('clicked', Lang.bind(this, this.activate));
|
||||||
|
},
|
||||||
|
|
||||||
|
activate: function() {
|
||||||
|
this.provider.activateResult(this.metaInfo.id, this.terms);
|
||||||
|
Main.overview.toggle();
|
||||||
|
},
|
||||||
|
|
||||||
|
setSelected: function(selected) {
|
||||||
|
if (selected)
|
||||||
|
this.actor.add_style_pseudo_class('selected');
|
||||||
|
else
|
||||||
|
this.actor.remove_style_pseudo_class('selected');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const ListSearchResult = new Lang.Class({
|
||||||
|
Name: 'ListSearchResult',
|
||||||
|
Extends: SearchResult,
|
||||||
|
|
||||||
|
ICON_SIZE: 64,
|
||||||
|
|
||||||
|
_init: function(provider, metaInfo, terms) {
|
||||||
|
this.parent(provider, metaInfo, terms);
|
||||||
|
|
||||||
|
this.actor.style_class = 'list-search-result';
|
||||||
|
this.actor.x_fill = true;
|
||||||
|
|
||||||
|
let content = new St.BoxLayout({ style_class: 'list-search-result-content',
|
||||||
|
vertical: false });
|
||||||
|
this.actor.set_child(content);
|
||||||
|
|
||||||
|
// An icon for, or thumbnail of, content
|
||||||
|
let icon = this.metaInfo['createIcon'](this.ICON_SIZE);
|
||||||
|
if (icon) {
|
||||||
|
content.add(icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
let details = new St.BoxLayout({ vertical: true });
|
||||||
|
content.add(details, { x_fill: true,
|
||||||
|
y_fill: false,
|
||||||
|
x_align: St.Align.START,
|
||||||
|
y_align: St.Align.MIDDLE });
|
||||||
|
|
||||||
|
let title = new St.Label({ style_class: 'list-search-result-title',
|
||||||
|
text: this.metaInfo['name'] })
|
||||||
|
details.add(title, { x_fill: false,
|
||||||
|
y_fill: false,
|
||||||
|
x_align: St.Align.START,
|
||||||
|
y_align: St.Align.START });
|
||||||
|
this.actor.label_actor = title;
|
||||||
|
|
||||||
|
if (this.metaInfo['description']) {
|
||||||
|
let description = new St.Label({ style_class: 'list-search-result-description' });
|
||||||
|
description.clutter_text.set_markup(this.metaInfo['description']);
|
||||||
|
details.add(description, { x_fill: false,
|
||||||
|
y_fill: false,
|
||||||
|
x_align: St.Align.START,
|
||||||
|
y_align: St.Align.END });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const GridSearchResult = new Lang.Class({
|
||||||
|
Name: 'GridSearchResult',
|
||||||
|
Extends: SearchResult,
|
||||||
|
|
||||||
|
_init: function(provider, metaInfo, terms) {
|
||||||
|
this.parent(provider, metaInfo, terms);
|
||||||
|
|
||||||
|
this.actor.style_class = 'grid-search-result';
|
||||||
|
|
||||||
let content = provider.createResultActor(metaInfo, terms);
|
let content = provider.createResultActor(metaInfo, terms);
|
||||||
|
let dragSource = null;
|
||||||
|
|
||||||
if (content == null) {
|
if (content == null) {
|
||||||
content = new St.Bin({ style_class: 'search-result-content',
|
content = new St.Bin();
|
||||||
reactive: true,
|
|
||||||
can_focus: true,
|
|
||||||
track_hover: true,
|
|
||||||
accessible_role: Atk.Role.PUSH_BUTTON });
|
|
||||||
let icon = new IconGrid.BaseIcon(this.metaInfo['name'],
|
let icon = new IconGrid.BaseIcon(this.metaInfo['name'],
|
||||||
{ createIcon: this.metaInfo['createIcon'] });
|
{ createIcon: this.metaInfo['createIcon'] });
|
||||||
content.set_child(icon.actor);
|
content.set_child(icon.actor);
|
||||||
this._dragActorSource = icon.icon;
|
|
||||||
content.label_actor = icon.label;
|
content.label_actor = icon.label;
|
||||||
|
dragSource = icon.icon;
|
||||||
} else {
|
} else {
|
||||||
if (content._delegate && content._delegate.getDragActorSource)
|
if (content._delegate && content._delegate.getDragActorSource)
|
||||||
this._dragActorSource = content._delegate.getDragActorSource();
|
dragSource = content._delegate.getDragActorSource();
|
||||||
}
|
}
|
||||||
this._content = content;
|
|
||||||
this.actor.set_child(content);
|
|
||||||
|
|
||||||
this.actor.connect('clicked', Lang.bind(this, this._onResultClicked));
|
this.actor.set_child(content);
|
||||||
|
this.actor.label_actor = content.label_actor;
|
||||||
|
|
||||||
let draggable = DND.makeDraggable(this.actor);
|
let draggable = DND.makeDraggable(this.actor);
|
||||||
draggable.connect('drag-begin',
|
draggable.connect('drag-begin',
|
||||||
@ -63,32 +157,18 @@ const SearchResult = new Lang.Class({
|
|||||||
Lang.bind(this, function() {
|
Lang.bind(this, function() {
|
||||||
Main.overview.endItemDrag(this);
|
Main.overview.endItemDrag(this);
|
||||||
}));
|
}));
|
||||||
},
|
|
||||||
|
|
||||||
setSelected: function(selected) {
|
if (!dragSource)
|
||||||
if (selected)
|
// not exactly right, but alignment problems are hard to notice
|
||||||
this._content.add_style_pseudo_class('selected');
|
dragSource = content;
|
||||||
else
|
this._dragActorSource = dragSource;
|
||||||
this._content.remove_style_pseudo_class('selected');
|
|
||||||
},
|
|
||||||
|
|
||||||
activate: function() {
|
|
||||||
this.provider.activateResult(this.metaInfo.id);
|
|
||||||
Main.overview.toggle();
|
|
||||||
},
|
|
||||||
|
|
||||||
_onResultClicked: function(actor) {
|
|
||||||
this.activate();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getDragActorSource: function() {
|
getDragActorSource: function() {
|
||||||
if (this._dragActorSource)
|
return this._dragActorSource;
|
||||||
return this._dragActorSource;
|
|
||||||
// not exactly right, but alignment problems are hard to notice
|
|
||||||
return this._content;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getDragActor: function(stageX, stageY) {
|
getDragActor: function() {
|
||||||
return this.metaInfo['createIcon'](Main.overview.dashIconSize);
|
return this.metaInfo['createIcon'](Main.overview.dashIconSize);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -96,51 +176,52 @@ const SearchResult = new Lang.Class({
|
|||||||
if (this.provider.dragActivateResult)
|
if (this.provider.dragActivateResult)
|
||||||
this.provider.dragActivateResult(this.metaInfo.id, params);
|
this.provider.dragActivateResult(this.metaInfo.id, params);
|
||||||
else
|
else
|
||||||
this.provider.activateResult(this.metaInfo.id, params);
|
this.provider.activateResult(this.metaInfo.id, this.terms);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ListSearchResults = new Lang.Class({
|
||||||
|
Name: 'ListSearchResults',
|
||||||
|
|
||||||
const GridSearchResults = new Lang.Class({
|
_init: function(provider) {
|
||||||
Name: 'GridSearchResults',
|
this.provider = provider;
|
||||||
Extends: Search.SearchResultDisplay,
|
|
||||||
|
|
||||||
_init: function(provider, grid) {
|
this.actor = new St.BoxLayout({ style_class: 'search-section-content' });
|
||||||
this.parent(provider);
|
this.providerIcon = new ProviderIcon(provider);
|
||||||
|
this.providerIcon.connect('clicked', Lang.bind(this,
|
||||||
this._grid = grid || new IconGrid.IconGrid({ rowLimit: MAX_SEARCH_RESULTS_ROWS,
|
function() {
|
||||||
xAlign: St.Align.START });
|
provider.launchSearch(this._terms);
|
||||||
this.actor = new St.Bin({ x_align: St.Align.START });
|
Main.overview.toggle();
|
||||||
|
|
||||||
this.actor.set_child(this._grid.actor);
|
|
||||||
this._width = 0;
|
|
||||||
this.actor.connect('notify::width', Lang.bind(this, function() {
|
|
||||||
this._width = this.actor.width;
|
|
||||||
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() {
|
|
||||||
let results = this.getResultsForDisplay();
|
|
||||||
if (results.length == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
provider.getResultMetas(results, Lang.bind(this, this.renderResults));
|
|
||||||
}));
|
}));
|
||||||
}));
|
|
||||||
|
this.actor.add(this.providerIcon, { x_fill: false,
|
||||||
|
y_fill: false,
|
||||||
|
x_align: St.Align.START,
|
||||||
|
y_align: St.Align.START });
|
||||||
|
|
||||||
|
this._content = new St.BoxLayout({ style_class: 'list-search-results',
|
||||||
|
vertical: true });
|
||||||
|
this.actor.add(this._content, { expand: true });
|
||||||
|
|
||||||
this._notDisplayedResult = [];
|
this._notDisplayedResult = [];
|
||||||
this._terms = [];
|
this._terms = [];
|
||||||
this._pendingClear = false;
|
this._pendingClear = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
getResultsForDisplay: function() {
|
getResultsForDisplay: function() {
|
||||||
let alreadyVisible = this._pendingClear ? 0 : this._grid.visibleItemsCount();
|
let alreadyVisible = this._pendingClear ? 0 : this.getVisibleResultCount();
|
||||||
let canDisplay = this._grid.childrenInRow(this._width) * this._grid.getRowLimit()
|
let canDisplay = MAX_LIST_SEARCH_RESULTS_ROWS - alreadyVisible;
|
||||||
- alreadyVisible;
|
|
||||||
|
|
||||||
let numResults = Math.min(this._notDisplayedResult.length, canDisplay);
|
let newResults = this._notDisplayedResult.splice(0, canDisplay);
|
||||||
|
return newResults;
|
||||||
return this._notDisplayedResult.splice(0, numResults);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getVisibleResultCount: function() {
|
getVisibleResultCount: function() {
|
||||||
return this._grid.visibleItemsCount();
|
return this._content.get_n_children();
|
||||||
|
},
|
||||||
|
|
||||||
|
hasMoreResults: function() {
|
||||||
|
return this._notDisplayedResult.length > 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
setResults: function(results, terms) {
|
setResults: function(results, terms) {
|
||||||
@ -150,9 +231,81 @@ const GridSearchResults = new Lang.Class({
|
|||||||
this._pendingClear = true;
|
this._pendingClear = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_keyFocusIn: function(icon) {
|
||||||
|
this.emit('key-focus-in', icon);
|
||||||
|
},
|
||||||
|
|
||||||
renderResults: function(metas) {
|
renderResults: function(metas) {
|
||||||
for (let i = 0; i < metas.length; i++) {
|
for (let i = 0; i < metas.length; i++) {
|
||||||
let display = new SearchResult(this.provider, metas[i], this._terms);
|
let display = new ListSearchResult(this.provider, metas[i], this._terms);
|
||||||
|
display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn));
|
||||||
|
this._content.add_actor(display.actor);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
clear: function () {
|
||||||
|
this._content.destroy_all_children();
|
||||||
|
this._pendingClear = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
getFirstResult: function() {
|
||||||
|
if (this.getVisibleResultCount() > 0)
|
||||||
|
return this._content.get_child_at_index(0)._delegate;
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Signals.addSignalMethods(ListSearchResults.prototype);
|
||||||
|
|
||||||
|
const GridSearchResults = new Lang.Class({
|
||||||
|
Name: 'GridSearchResults',
|
||||||
|
|
||||||
|
_init: function(provider) {
|
||||||
|
this.provider = provider;
|
||||||
|
|
||||||
|
this._grid = new IconGrid.IconGrid({ rowLimit: MAX_GRID_SEARCH_RESULTS_ROWS,
|
||||||
|
xAlign: St.Align.START });
|
||||||
|
this.actor = new St.Bin({ x_align: St.Align.MIDDLE });
|
||||||
|
|
||||||
|
this.actor.set_child(this._grid.actor);
|
||||||
|
|
||||||
|
this._notDisplayedResult = [];
|
||||||
|
this._terms = [];
|
||||||
|
this._pendingClear = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
getResultsForDisplay: function() {
|
||||||
|
let alreadyVisible = this._pendingClear ? 0 : this._grid.visibleItemsCount();
|
||||||
|
let canDisplay = this._grid.childrenInRow(this.actor.width) * this._grid.getRowLimit()
|
||||||
|
- alreadyVisible;
|
||||||
|
|
||||||
|
let newResults = this._notDisplayedResult.splice(0, canDisplay);
|
||||||
|
return newResults;
|
||||||
|
},
|
||||||
|
|
||||||
|
getVisibleResultCount: function() {
|
||||||
|
return this._grid.visibleItemsCount();
|
||||||
|
},
|
||||||
|
|
||||||
|
hasMoreResults: function() {
|
||||||
|
return this._notDisplayedResult.length > 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
setResults: function(results, terms) {
|
||||||
|
// copy the lists
|
||||||
|
this._notDisplayedResult = results.slice(0);
|
||||||
|
this._terms = terms.slice(0);
|
||||||
|
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);
|
this._grid.addItem(display.actor);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -169,6 +322,7 @@ const GridSearchResults = new Lang.Class({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Signals.addSignalMethods(GridSearchResults.prototype);
|
||||||
|
|
||||||
const SearchResults = new Lang.Class({
|
const SearchResults = new Lang.Class({
|
||||||
Name: 'SearchResults',
|
Name: 'SearchResults',
|
||||||
@ -182,27 +336,29 @@ const SearchResults = new Lang.Class({
|
|||||||
|
|
||||||
this._content = new St.BoxLayout({ name: 'searchResultsContent',
|
this._content = new St.BoxLayout({ name: 'searchResultsContent',
|
||||||
vertical: true });
|
vertical: true });
|
||||||
|
this._contentBin = new MaxWidthBin({ name: 'searchResultsBin',
|
||||||
|
x_fill: true,
|
||||||
|
y_fill: true,
|
||||||
|
child: this._content });
|
||||||
|
|
||||||
let scrollView = new St.ScrollView({ x_fill: true,
|
let scrollChild = new St.BoxLayout();
|
||||||
y_fill: false,
|
scrollChild.add(this._contentBin, { expand: true });
|
||||||
style_class: 'vfade' });
|
|
||||||
scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
|
||||||
scrollView.add_actor(this._content);
|
|
||||||
|
|
||||||
this.actor.add(scrollView, { x_fill: true,
|
this._scrollView = new St.ScrollView({ x_fill: true,
|
||||||
y_fill: true,
|
y_fill: false,
|
||||||
expand: true,
|
overlay_scrollbars: true,
|
||||||
x_align: St.Align.START,
|
style_class: 'search-display vfade' });
|
||||||
y_align: St.Align.START });
|
this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||||
this.actor.connect('notify::mapped', Lang.bind(this,
|
this._scrollView.add_actor(scrollChild);
|
||||||
function() {
|
let action = new Clutter.PanAction({ interpolate: true });
|
||||||
if (!this.actor.mapped)
|
action.connect('pan', Lang.bind(this, this._onPan));
|
||||||
return;
|
this._scrollView.add_action(action);
|
||||||
|
|
||||||
let adjustment = scrollView.vscroll.adjustment;
|
this.actor.add(this._scrollView, { x_fill: true,
|
||||||
let direction = Overview.SwipeScrollDirection.VERTICAL;
|
y_fill: true,
|
||||||
Main.overview.setScrollAdjustment(adjustment, direction);
|
expand: true,
|
||||||
}));
|
x_align: St.Align.START,
|
||||||
|
y_align: St.Align.START });
|
||||||
|
|
||||||
this._statusText = new St.Label({ style_class: 'search-statustext' });
|
this._statusText = new St.Label({ style_class: 'search-statustext' });
|
||||||
this._statusBin = new St.Bin({ x_align: St.Align.MIDDLE,
|
this._statusBin = new St.Bin({ x_align: St.Align.MIDDLE,
|
||||||
@ -219,22 +375,43 @@ const SearchResults = new Lang.Class({
|
|||||||
this._defaultResult = null;
|
this._defaultResult = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onPan: function(action) {
|
||||||
|
let [dist, dx, dy] = action.get_motion_delta(0);
|
||||||
|
let adjustment = this._scrollView.vscroll.adjustment;
|
||||||
|
adjustment.value -= (dy / this.actor.height) * adjustment.page_size;
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
_keyFocusIn: function(provider, icon) {
|
||||||
|
Util.ensureActorVisibleInScrollView(this._scrollView, icon);
|
||||||
|
},
|
||||||
|
|
||||||
createProviderMeta: function(provider) {
|
createProviderMeta: function(provider) {
|
||||||
let providerBox = new St.BoxLayout({ style_class: 'search-section',
|
let providerBox = new St.BoxLayout({ style_class: 'search-section',
|
||||||
vertical: true });
|
vertical: true });
|
||||||
let title = new St.Label({ style_class: 'search-section-header',
|
let providerIcon = null;
|
||||||
text: provider.title });
|
let resultDisplay = null;
|
||||||
providerBox.add(title, { x_fill: false, x_align: St.Align.START });
|
|
||||||
|
|
||||||
let resultDisplayBin = new St.Bin({ style_class: 'search-section-results',
|
if (provider.appInfo) {
|
||||||
|
resultDisplay = new ListSearchResults(provider);
|
||||||
|
providerIcon = resultDisplay.providerIcon;
|
||||||
|
} else {
|
||||||
|
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,
|
x_fill: true,
|
||||||
y_fill: true });
|
y_fill: true });
|
||||||
providerBox.add(resultDisplayBin, { expand: true });
|
providerBox.add(resultDisplayBin, { expand: true });
|
||||||
let resultDisplay = new GridSearchResults(provider);
|
|
||||||
resultDisplayBin.set_child(resultDisplay.actor);
|
let separator = new Separator.HorizontalSeparator({ style_class: 'search-section-separator' });
|
||||||
|
providerBox.add(separator.actor);
|
||||||
|
|
||||||
this._providerMeta.push({ provider: provider,
|
this._providerMeta.push({ provider: provider,
|
||||||
actor: providerBox,
|
actor: providerBox,
|
||||||
|
icon: providerIcon,
|
||||||
resultDisplay: resultDisplay });
|
resultDisplay: resultDisplay });
|
||||||
this._content.add(providerBox);
|
this._content.add(providerBox);
|
||||||
},
|
},
|
||||||
@ -268,18 +445,15 @@ const SearchResults = new Lang.Class({
|
|||||||
this._searchSystem.reset();
|
this._searchSystem.reset();
|
||||||
this._statusBin.hide();
|
this._statusBin.hide();
|
||||||
this._clearDisplay();
|
this._clearDisplay();
|
||||||
|
this._defaultResult = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
startingSearch: function() {
|
startingSearch: function() {
|
||||||
this.reset();
|
this.reset();
|
||||||
this._statusText.set_text(_("Searching..."));
|
this._statusText.set_text(_("Searching…"));
|
||||||
this._statusBin.show();
|
this._statusBin.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
doSearch: function (searchString) {
|
|
||||||
this._searchSystem.updateSearch(searchString);
|
|
||||||
},
|
|
||||||
|
|
||||||
_metaForProvider: function(provider) {
|
_metaForProvider: function(provider) {
|
||||||
return this._providerMeta[this._providers.indexOf(provider)];
|
return this._providerMeta[this._providers.indexOf(provider)];
|
||||||
},
|
},
|
||||||
@ -341,6 +515,11 @@ const SearchResults = new Lang.Class({
|
|||||||
meta.resultDisplay.setResults(providerResults, terms);
|
meta.resultDisplay.setResults(providerResults, terms);
|
||||||
let results = meta.resultDisplay.getResultsForDisplay();
|
let results = meta.resultDisplay.getResultsForDisplay();
|
||||||
|
|
||||||
|
if (meta.icon)
|
||||||
|
meta.icon.moreIcon.visible =
|
||||||
|
meta.resultDisplay.hasMoreResults() &&
|
||||||
|
provider.canLaunchSearch;
|
||||||
|
|
||||||
provider.getResultMetas(results, Lang.bind(this, function(metas) {
|
provider.getResultMetas(results, Lang.bind(this, function(metas) {
|
||||||
this._clearDisplayForProvider(provider);
|
this._clearDisplayForProvider(provider);
|
||||||
meta.actor.show();
|
meta.actor.show();
|
||||||
@ -386,10 +565,38 @@ const SearchResults = new Lang.Class({
|
|||||||
|
|
||||||
let from = this._defaultResult ? this._defaultResult.actor : null;
|
let from = this._defaultResult ? this._defaultResult.actor : null;
|
||||||
this.actor.navigate_focus(from, direction, false);
|
this.actor.navigate_focus(from, direction, false);
|
||||||
if (this._defaultResult) {
|
}
|
||||||
// The default result appears focused, so navigate directly to the
|
});
|
||||||
// next result.
|
|
||||||
this.actor.navigate_focus(global.stage.key_focus, direction, false);
|
const ProviderIcon = new Lang.Class({
|
||||||
}
|
Name: 'ProviderIcon',
|
||||||
|
Extends: St.Button,
|
||||||
|
|
||||||
|
PROVIDER_ICON_SIZE: 48,
|
||||||
|
|
||||||
|
_init: function(provider) {
|
||||||
|
this.provider = provider;
|
||||||
|
this.parent({ style_class: 'search-provider-icon',
|
||||||
|
reactive: true,
|
||||||
|
can_focus: true,
|
||||||
|
accessible_name: provider.appInfo.get_name(),
|
||||||
|
track_hover: true });
|
||||||
|
|
||||||
|
this._content = new St.Widget({ layout_manager: new Clutter.BinLayout() });
|
||||||
|
this.set_child(this._content);
|
||||||
|
|
||||||
|
let rtl = (this.get_text_direction() == Clutter.TextDirection.RTL);
|
||||||
|
|
||||||
|
this.moreIcon = new St.Widget({ style_class: 'search-provider-icon-more',
|
||||||
|
visible: false,
|
||||||
|
x_align: rtl ? Clutter.ActorAlign.START : Clutter.ActorAlign.END,
|
||||||
|
y_align: Clutter.ActorAlign.END,
|
||||||
|
x_expand: true,
|
||||||
|
y_expand: true });
|
||||||
|
|
||||||
|
let icon = new St.Icon({ icon_size: this.PROVIDER_ICON_SIZE,
|
||||||
|
gicon: provider.appInfo.get_icon() });
|
||||||
|
this._content.add_actor(icon);
|
||||||
|
this._content.add_actor(this.moreIcon);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
35
js/ui/separator.js
Normal file
35
js/ui/separator.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
|
||||||
|
const Cairo = imports.cairo;
|
||||||
|
const Lang = imports.lang;
|
||||||
|
const St = imports.gi.St;
|
||||||
|
|
||||||
|
const HorizontalSeparator = new Lang.Class({
|
||||||
|
Name: 'HorizontalSeparator',
|
||||||
|
|
||||||
|
_init: function (params) {
|
||||||
|
this.actor = new St.DrawingArea(params);
|
||||||
|
this.actor.connect('repaint', Lang.bind(this, this._onRepaint));
|
||||||
|
},
|
||||||
|
|
||||||
|
_onRepaint: function(area) {
|
||||||
|
let cr = area.get_context();
|
||||||
|
let themeNode = area.get_theme_node();
|
||||||
|
let [width, height] = area.get_surface_size();
|
||||||
|
let margin = themeNode.get_length('-margin-horizontal');
|
||||||
|
let gradientHeight = themeNode.get_length('-gradient-height');
|
||||||
|
let startColor = themeNode.get_color('-gradient-start');
|
||||||
|
let endColor = themeNode.get_color('-gradient-end');
|
||||||
|
|
||||||
|
let gradientWidth = (width - margin * 2);
|
||||||
|
let gradientOffset = (height - gradientHeight) / 2;
|
||||||
|
let pattern = new Cairo.LinearGradient(margin, gradientOffset, width - margin, gradientOffset + gradientHeight);
|
||||||
|
pattern.addColorStopRGBA(0, startColor.red / 255, startColor.green / 255, startColor.blue / 255, startColor.alpha / 255);
|
||||||
|
pattern.addColorStopRGBA(0.5, endColor.red / 255, endColor.green / 255, endColor.blue / 255, endColor.alpha / 255);
|
||||||
|
pattern.addColorStopRGBA(1, startColor.red / 255, startColor.green / 255, startColor.blue / 255, startColor.alpha / 255);
|
||||||
|
cr.setSource(pattern);
|
||||||
|
cr.rectangle(margin, gradientOffset, gradientWidth, gradientHeight);
|
||||||
|
cr.fill();
|
||||||
|
cr.$dispose();
|
||||||
|
}
|
||||||
|
});
|
@ -1,8 +1,12 @@
|
|||||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
// -*- 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 Lang = imports.lang;
|
||||||
|
const Mainloop = imports.mainloop;
|
||||||
const Signals = imports.signals;
|
const Signals = imports.signals;
|
||||||
|
|
||||||
|
const FileUtils = imports.misc.fileUtils;
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
const Params = imports.misc.params;
|
const Params = imports.misc.params;
|
||||||
|
|
||||||
@ -10,11 +14,13 @@ const DEFAULT_MODE = 'restrictive';
|
|||||||
|
|
||||||
const _modes = {
|
const _modes = {
|
||||||
'restrictive': {
|
'restrictive': {
|
||||||
|
parentMode: null,
|
||||||
|
stylesheetName: 'gnome-shell.css',
|
||||||
hasOverview: false,
|
hasOverview: false,
|
||||||
showCalendarEvents: false,
|
showCalendarEvents: false,
|
||||||
allowSettings: false,
|
allowSettings: false,
|
||||||
allowExtensions: false,
|
allowExtensions: false,
|
||||||
allowKeybindingsWhenModal: false,
|
enabledExtensions: [],
|
||||||
hasRunDialog: false,
|
hasRunDialog: false,
|
||||||
hasWorkspaces: false,
|
hasWorkspaces: false,
|
||||||
hasWindows: false,
|
hasWindows: false,
|
||||||
@ -29,21 +35,22 @@ const _modes = {
|
|||||||
center: [],
|
center: [],
|
||||||
right: []
|
right: []
|
||||||
},
|
},
|
||||||
|
panelStyle: null
|
||||||
},
|
},
|
||||||
|
|
||||||
'gdm': {
|
'gdm': {
|
||||||
allowKeybindingsWhenModal: true,
|
|
||||||
hasNotifications: true,
|
hasNotifications: true,
|
||||||
isGreeter: true,
|
isGreeter: true,
|
||||||
isPrimary: true,
|
isPrimary: true,
|
||||||
unlockDialog: imports.gdm.loginDialog.LoginDialog,
|
unlockDialog: imports.gdm.loginDialog.LoginDialog,
|
||||||
components: ['polkitAgent'],
|
components: ['polkitAgent'],
|
||||||
panel: {
|
panel: {
|
||||||
left: [],
|
left: ['logo'],
|
||||||
center: ['dateMenu'],
|
center: ['dateMenu'],
|
||||||
right: ['a11y', 'display', 'keyboard',
|
right: ['a11yGreeter', 'display', 'keyboard',
|
||||||
'volume', 'battery', 'powerMenu']
|
'volume', 'battery', 'powerMenu']
|
||||||
}
|
},
|
||||||
|
panelStyle: 'login-screen'
|
||||||
},
|
},
|
||||||
|
|
||||||
'lock-screen': {
|
'lock-screen': {
|
||||||
@ -56,6 +63,7 @@ const _modes = {
|
|||||||
center: [],
|
center: [],
|
||||||
right: ['lockScreen']
|
right: ['lockScreen']
|
||||||
},
|
},
|
||||||
|
panelStyle: 'lock-screen'
|
||||||
},
|
},
|
||||||
|
|
||||||
'unlock-dialog': {
|
'unlock-dialog': {
|
||||||
@ -67,15 +75,17 @@ const _modes = {
|
|||||||
center: [],
|
center: [],
|
||||||
right: ['a11y', 'keyboard', 'lockScreen']
|
right: ['a11y', 'keyboard', 'lockScreen']
|
||||||
},
|
},
|
||||||
|
panelStyle: 'unlock-screen'
|
||||||
},
|
},
|
||||||
|
|
||||||
'initial-setup': {
|
'initial-setup': {
|
||||||
|
hasWindows: true,
|
||||||
isPrimary: true,
|
isPrimary: true,
|
||||||
components: ['keyring'],
|
components: ['keyring'],
|
||||||
panel: {
|
panel: {
|
||||||
left: [],
|
left: [],
|
||||||
center: ['dateMenu'],
|
center: ['dateMenu'],
|
||||||
right: ['a11y', 'keyboard', 'volume']
|
right: ['a11yGreeter', 'keyboard', 'volume', 'battery']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -102,22 +112,64 @@ const _modes = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function _getModes(modesLoadedCallback) {
|
||||||
|
FileUtils.collectFromDatadirsAsync('modes',
|
||||||
|
{ processFile: _loadMode,
|
||||||
|
loadedCallback: modesLoadedCallback,
|
||||||
|
data: _modes });
|
||||||
|
}
|
||||||
|
|
||||||
|
function _loadMode(file, info, loadedData) {
|
||||||
|
let name = info.get_name();
|
||||||
|
let suffix = name.indexOf('.json');
|
||||||
|
let modeName = suffix == -1 ? name : name.slice(name, suffix);
|
||||||
|
|
||||||
|
if (loadedData.hasOwnProperty(modeName))
|
||||||
|
return;
|
||||||
|
|
||||||
|
let fileContent, success, tag, newMode;
|
||||||
|
try {
|
||||||
|
[success, fileContent, tag] = file.load_contents(null);
|
||||||
|
newMode = JSON.parse(fileContent);
|
||||||
|
} catch(e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedData[modeName] = {};
|
||||||
|
let propBlacklist = ['unlockDialog'];
|
||||||
|
for (let prop in loadedData[DEFAULT_MODE]) {
|
||||||
|
if (newMode[prop] !== undefined &&
|
||||||
|
propBlacklist.indexOf(prop) == -1)
|
||||||
|
loadedData[modeName][prop]= newMode[prop];
|
||||||
|
}
|
||||||
|
loadedData[modeName]['isPrimary'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
function listModes() {
|
function listModes() {
|
||||||
let modes = Object.getOwnPropertyNames(_modes);
|
_getModes(function(modes) {
|
||||||
for (let i = 0; i < modes.length; i++)
|
let names = Object.getOwnPropertyNames(modes);
|
||||||
if (_modes[modes[i]].isPrimary)
|
for (let i = 0; i < names.length; i++)
|
||||||
print(modes[i]);
|
if (_modes[names[i]].isPrimary)
|
||||||
|
print(names[i]);
|
||||||
|
Mainloop.quit('listModes');
|
||||||
|
});
|
||||||
|
Mainloop.run('listModes');
|
||||||
}
|
}
|
||||||
|
|
||||||
const SessionMode = new Lang.Class({
|
const SessionMode = new Lang.Class({
|
||||||
Name: 'SessionMode',
|
Name: 'SessionMode',
|
||||||
|
|
||||||
_init: function() {
|
init: function() {
|
||||||
global.connect('notify::session-mode', Lang.bind(this, this._sync));
|
_getModes(Lang.bind(this, function(modes) {
|
||||||
let mode = _modes[global.session_mode].isPrimary ? global.session_mode
|
this._modes = modes;
|
||||||
: 'user';
|
let primary = modes[global.session_mode] &&
|
||||||
this._modeStack = [mode];
|
modes[global.session_mode].isPrimary;
|
||||||
this._sync();
|
let mode = primary ? global.session_mode : 'user';
|
||||||
|
this._modeStack = [mode];
|
||||||
|
this._sync();
|
||||||
|
|
||||||
|
this.emit('sessions-loaded');
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
pushMode: function(mode) {
|
pushMode: function(mode) {
|
||||||
@ -144,8 +196,14 @@ const SessionMode = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_sync: function() {
|
_sync: function() {
|
||||||
let params = _modes[this.currentMode];
|
let params = this._modes[this.currentMode];
|
||||||
params = Params.parse(params, _modes[DEFAULT_MODE]);
|
let defaults;
|
||||||
|
if (params.parentMode)
|
||||||
|
defaults = Params.parse(this._modes[params.parentMode],
|
||||||
|
this._modes[DEFAULT_MODE]);
|
||||||
|
else
|
||||||
|
defaults = this._modes[DEFAULT_MODE];
|
||||||
|
params = Params.parse(params, defaults);
|
||||||
|
|
||||||
// A simplified version of Lang.copyProperties, handles
|
// A simplified version of Lang.copyProperties, handles
|
||||||
// undefined as a special case for "no change / inherit from previous mode"
|
// undefined as a special case for "no change / inherit from previous mode"
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
|
||||||
const Lang = imports.lang;
|
|
||||||
const Gio = imports.gi.Gio;
|
const Gio = imports.gi.Gio;
|
||||||
const GLib = imports.gi.GLib;
|
const GLib = imports.gi.GLib;
|
||||||
|
const Lang = imports.lang;
|
||||||
|
const Meta = imports.gi.Meta;
|
||||||
const Shell = imports.gi.Shell;
|
const Shell = imports.gi.Shell;
|
||||||
|
|
||||||
const Config = imports.misc.config;
|
const Config = imports.misc.config;
|
||||||
const ExtensionSystem = imports.ui.extensionSystem;
|
const ExtensionSystem = imports.ui.extensionSystem;
|
||||||
const ExtensionDownloader = imports.ui.extensionDownloader;
|
const ExtensionDownloader = imports.ui.extensionDownloader;
|
||||||
const ExtensionUtils = imports.misc.extensionUtils;
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
const Flashspot = imports.ui.flashspot;
|
const Hash = imports.misc.hash;
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
|
const Screenshot = imports.ui.screenshot;
|
||||||
|
|
||||||
const GnomeShellIface = <interface name="org.gnome.Shell">
|
const GnomeShellIface = <interface name="org.gnome.Shell">
|
||||||
<method name="Eval">
|
<method name="Eval">
|
||||||
@ -18,34 +20,27 @@ const GnomeShellIface = <interface name="org.gnome.Shell">
|
|||||||
<arg type="b" direction="out" name="success" />
|
<arg type="b" direction="out" name="success" />
|
||||||
<arg type="s" direction="out" name="result" />
|
<arg type="s" direction="out" name="result" />
|
||||||
</method>
|
</method>
|
||||||
<method name="ScreenshotArea">
|
<method name="ShowOSD">
|
||||||
<arg type="i" direction="in" name="x"/>
|
<arg type="a{sv}" direction="in" name="params"/>
|
||||||
<arg type="i" direction="in" name="y"/>
|
</method>
|
||||||
<arg type="i" direction="in" name="width"/>
|
<method name="GrabAccelerator">
|
||||||
<arg type="i" direction="in" name="height"/>
|
<arg type="s" direction="in" name="accelerator"/>
|
||||||
<arg type="b" direction="in" name="flash"/>
|
<arg type="u" direction="in" name="flags"/>
|
||||||
<arg type="s" direction="in" name="filename"/>
|
<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"/>
|
<arg type="b" direction="out" name="success"/>
|
||||||
</method>
|
</method>
|
||||||
<method name="ScreenshotWindow">
|
<signal name="AcceleratorActivated">
|
||||||
<arg type="b" direction="in" name="include_frame"/>
|
<arg name="action" type="u" />
|
||||||
<arg type="b" direction="in" name="include_cursor"/>
|
<arg name="deviceid" type="u" />
|
||||||
<arg type="b" direction="in" name="flash"/>
|
</signal>
|
||||||
<arg type="s" direction="in" name="filename"/>
|
<property name="Mode" type="s" access="read" />
|
||||||
<arg type="b" direction="out" name="success"/>
|
|
||||||
</method>
|
|
||||||
<method name="Screenshot">
|
|
||||||
<arg type="b" direction="in" name="include_cursor"/>
|
|
||||||
<arg type="b" direction="in" name="flash"/>
|
|
||||||
<arg type="s" direction="in" name="filename"/>
|
|
||||||
<arg type="b" direction="out" name="success"/>
|
|
||||||
</method>
|
|
||||||
<method name="FlashArea">
|
|
||||||
<arg type="i" direction="in" name="x"/>
|
|
||||||
<arg type="i" direction="in" name="y"/>
|
|
||||||
<arg type="i" direction="in" name="width"/>
|
|
||||||
<arg type="i" direction="in" name="height"/>
|
|
||||||
</method>
|
|
||||||
<property name="OverviewActive" type="b" access="readwrite" />
|
<property name="OverviewActive" type="b" access="readwrite" />
|
||||||
<property name="ShellVersion" type="s" access="read" />
|
<property name="ShellVersion" type="s" access="read" />
|
||||||
</interface>;
|
</interface>;
|
||||||
@ -59,6 +54,9 @@ const ScreenSaverIface = <interface name="org.gnome.ScreenSaver">
|
|||||||
<method name="SetActive">
|
<method name="SetActive">
|
||||||
<arg name="value" direction="in" type="b" />
|
<arg name="value" direction="in" type="b" />
|
||||||
</method>
|
</method>
|
||||||
|
<method name="GetActiveTime">
|
||||||
|
<arg name="value" direction="out" type="u" />
|
||||||
|
</method>
|
||||||
<signal name="ActiveChanged">
|
<signal name="ActiveChanged">
|
||||||
<arg name="new_value" type="b" />
|
<arg name="new_value" type="b" />
|
||||||
</signal>
|
</signal>
|
||||||
@ -71,7 +69,16 @@ const GnomeShell = new Lang.Class({
|
|||||||
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellIface, this);
|
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellIface, this);
|
||||||
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell');
|
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell');
|
||||||
|
|
||||||
this._extensionsSerivce = new GnomeShellExtensions();
|
this._extensionsService = new GnomeShellExtensions();
|
||||||
|
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);
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -107,81 +114,103 @@ const GnomeShell = new Lang.Class({
|
|||||||
return [success, returnValue];
|
return [success, returnValue];
|
||||||
},
|
},
|
||||||
|
|
||||||
_onScreenshotComplete: function(obj, result, area, flash, invocation) {
|
ShowOSD: function(params) {
|
||||||
if (flash) {
|
for (let param in params)
|
||||||
let flashspot = new Flashspot.Flashspot(area);
|
params[param] = params[param].deep_unpack();
|
||||||
flashspot.fire();
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
let retval = GLib.Variant.new('(b)', [result]);
|
return bindingAction;
|
||||||
invocation.return_value(retval);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
_ungrabAccelerator: function(action) {
|
||||||
* ScreenshotArea:
|
let ungrabSucceeded = global.display.ungrab_accelerator(action);
|
||||||
* @x: The X coordinate of the area
|
if (ungrabSucceeded)
|
||||||
* @y: The Y coordinate of the area
|
this._grabbedAccelerators.delete(action);
|
||||||
* @width: The width of the area
|
|
||||||
* @height: The height of the area
|
|
||||||
* @flash: Whether to flash the area or not
|
|
||||||
* @filename: The filename for the screenshot
|
|
||||||
*
|
|
||||||
* Takes a screenshot of the passed in area and saves it
|
|
||||||
* in @filename as png image, it returns a boolean
|
|
||||||
* indicating whether the operation was successful or not.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
ScreenshotAreaAsync : function (params, invocation) {
|
|
||||||
let [x, y, width, height, flash, filename, callback] = params;
|
|
||||||
let screenshot = new Shell.Screenshot();
|
|
||||||
screenshot.screenshot_area (x, y, width, height, filename,
|
|
||||||
Lang.bind(this, this._onScreenshotComplete,
|
|
||||||
flash, invocation));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
_onGrabberBusNameVanished: function(connection, name) {
|
||||||
* ScreenshotWindow:
|
let grabs = this._grabbedAccelerators.items();
|
||||||
* @include_frame: Whether to include the frame or not
|
for (let i = 0; i < grabs.length; i++) {
|
||||||
* @include_cursor: Whether to include the cursor image or not
|
let [action, sender] = grabs[i];
|
||||||
* @flash: Whether to flash the window area or not
|
if (sender == name)
|
||||||
* @filename: The filename for the screenshot
|
this._ungrabAccelerator(action);
|
||||||
*
|
}
|
||||||
* Takes a screenshot of the focused window (optionally omitting the frame)
|
Gio.bus_unwatch_name(this._grabbers.get(name));
|
||||||
* and saves it in @filename as png image, it returns a boolean
|
this._grabbers.delete(name);
|
||||||
* indicating whether the operation was successful or not.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
ScreenshotWindowAsync : function (params, invocation) {
|
|
||||||
let [include_frame, include_cursor, flash, filename] = params;
|
|
||||||
let screenshot = new Shell.Screenshot();
|
|
||||||
screenshot.screenshot_window (include_frame, include_cursor, filename,
|
|
||||||
Lang.bind(this, this._onScreenshotComplete,
|
|
||||||
flash, invocation));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Screenshot:
|
|
||||||
* @filename: The filename for the screenshot
|
|
||||||
* @include_cursor: Whether to include the cursor image or not
|
|
||||||
* @flash: Whether to flash the screen or not
|
|
||||||
*
|
|
||||||
* Takes a screenshot of the whole screen and saves it
|
|
||||||
* in @filename as png image, it returns a boolean
|
|
||||||
* indicating whether the operation was successful or not.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
ScreenshotAsync : function (params, invocation) {
|
|
||||||
let [include_cursor, flash, filename] = params;
|
|
||||||
let screenshot = new Shell.Screenshot();
|
|
||||||
screenshot.screenshot(include_cursor, filename,
|
|
||||||
Lang.bind(this, this._onScreenshotComplete,
|
|
||||||
flash, invocation));
|
|
||||||
},
|
|
||||||
|
|
||||||
FlashArea: function(x, y, width, height) {
|
Mode: global.session_mode,
|
||||||
let flashspot = new Flashspot.Flashspot({ x : x, y : y, width: width, height: height});
|
|
||||||
flashspot.fire();
|
|
||||||
},
|
|
||||||
|
|
||||||
get OverviewActive() {
|
get OverviewActive() {
|
||||||
return Main.overview.visible;
|
return Main.overview.visible;
|
||||||
@ -344,8 +373,8 @@ const ScreenSaverDBus = new Lang.Class({
|
|||||||
this.parent();
|
this.parent();
|
||||||
|
|
||||||
this._screenShield = screenShield;
|
this._screenShield = screenShield;
|
||||||
screenShield.connect('lock-status-changed', Lang.bind(this, function(shield) {
|
screenShield.connect('active-changed', Lang.bind(this, function(shield) {
|
||||||
this._dbusImpl.emit_signal('ActiveChanged', GLib.Variant.new('(b)', [shield.locked]));
|
this._dbusImpl.emit_signal('ActiveChanged', GLib.Variant.new('(b)', [shield.active]));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(ScreenSaverIface, this);
|
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(ScreenSaverIface, this);
|
||||||
@ -366,12 +395,20 @@ const ScreenSaverDBus = new Lang.Class({
|
|||||||
|
|
||||||
SetActive: function(active) {
|
SetActive: function(active) {
|
||||||
if (active)
|
if (active)
|
||||||
this._screenShield.lock(true);
|
this._screenShield.activate(true);
|
||||||
else
|
else
|
||||||
this._screenShield.unlock();
|
this._screenShield.unlock(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
GetActive: function() {
|
GetActive: function() {
|
||||||
return this._screenShield.locked;
|
return this._screenShield.active;
|
||||||
}
|
},
|
||||||
|
|
||||||
|
GetActiveTime: function() {
|
||||||
|
let started = this._screenShield.activationTime;
|
||||||
|
if (started > 0)
|
||||||
|
return Math.floor((GLib.get_monotonic_time() - started) / 1000000);
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
|
||||||
const Clutter = imports.gi.Clutter;
|
const Clutter = imports.gi.Clutter;
|
||||||
const Gtk = imports.gi.Gtk;
|
const Gtk = imports.gi.Gtk;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
const St = imports.gi.St;
|
const St = imports.gi.St;
|
||||||
|
|
||||||
|
const BoxPointer = imports.ui.boxpointer;
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
const Params = imports.misc.params;
|
const Params = imports.misc.params;
|
||||||
const PopupMenu = imports.ui.popupMenu;
|
const PopupMenu = imports.ui.popupMenu;
|
||||||
@ -65,32 +68,33 @@ const EntryMenu = new Lang.Class({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
open: function() {
|
open: function(animate) {
|
||||||
this._updatePasteItem();
|
this._updatePasteItem();
|
||||||
this._updateCopyItem();
|
this._updateCopyItem();
|
||||||
if (this._passwordItem)
|
if (this._passwordItem)
|
||||||
this._updatePasswordItem();
|
this._updatePasswordItem();
|
||||||
|
|
||||||
|
this.parent(animate);
|
||||||
|
this._entry.add_style_pseudo_class('focus');
|
||||||
|
|
||||||
let direction = Gtk.DirectionType.TAB_FORWARD;
|
let direction = Gtk.DirectionType.TAB_FORWARD;
|
||||||
if (!this.actor.navigate_focus(null, direction, false))
|
if (!this.actor.navigate_focus(null, direction, false))
|
||||||
this.actor.grab_key_focus();
|
this.actor.grab_key_focus();
|
||||||
|
|
||||||
this.parent();
|
|
||||||
this._entry.add_style_pseudo_class('focus');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
close: function() {
|
close: function(animate) {
|
||||||
this._entry.grab_key_focus();
|
this._entry.grab_key_focus();
|
||||||
this.parent();
|
this.parent(animate);
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateCopyItem: function() {
|
_updateCopyItem: function() {
|
||||||
let selection = this._entry.clutter_text.get_selection();
|
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() {
|
_updatePasteItem: function() {
|
||||||
this._clipboard.get_text(Lang.bind(this,
|
this._clipboard.get_text(St.ClipboardType.CLIPBOARD, Lang.bind(this,
|
||||||
function(clipboard, text) {
|
function(clipboard, text) {
|
||||||
this._pasteItem.setSensitive(text && text != '');
|
this._pasteItem.setSensitive(text && text != '');
|
||||||
}));
|
}));
|
||||||
@ -106,11 +110,11 @@ const EntryMenu = new Lang.Class({
|
|||||||
|
|
||||||
_onCopyActivated: function() {
|
_onCopyActivated: function() {
|
||||||
let selection = this._entry.clutter_text.get_selection();
|
let selection = this._entry.clutter_text.get_selection();
|
||||||
this._clipboard.set_text(selection);
|
this._clipboard.set_text(St.ClipboardType.CLIPBOARD, selection);
|
||||||
},
|
},
|
||||||
|
|
||||||
_onPasteActivated: function() {
|
_onPasteActivated: function() {
|
||||||
this._clipboard.get_text(Lang.bind(this,
|
this._clipboard.get_text(St.ClipboardType.CLIPBOARD, Lang.bind(this,
|
||||||
function(clipboard, text) {
|
function(clipboard, text) {
|
||||||
if (!text)
|
if (!text)
|
||||||
return;
|
return;
|
||||||
@ -134,12 +138,12 @@ function _setMenuAlignment(entry, stageX) {
|
|||||||
|
|
||||||
function _onButtonPressEvent(actor, event, entry) {
|
function _onButtonPressEvent(actor, event, entry) {
|
||||||
if (entry.menu.isOpen) {
|
if (entry.menu.isOpen) {
|
||||||
entry.menu.close();
|
entry.menu.close(BoxPointer.PopupAnimation.FULL);
|
||||||
return true;
|
return true;
|
||||||
} else if (event.get_button() == 3) {
|
} else if (event.get_button() == 3) {
|
||||||
let [stageX, stageY] = event.get_coords();
|
let [stageX, stageY] = event.get_coords();
|
||||||
_setMenuAlignment(entry, stageX);
|
_setMenuAlignment(entry, stageX);
|
||||||
entry.menu.open();
|
entry.menu.open(BoxPointer.PopupAnimation.FULL);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -149,7 +153,7 @@ function _onPopup(actor, entry) {
|
|||||||
let [success, textX, textY, lineHeight] = entry.clutter_text.position_to_coords(-1);
|
let [success, textX, textY, lineHeight] = entry.clutter_text.position_to_coords(-1);
|
||||||
if (success)
|
if (success)
|
||||||
entry.menu.setSourceAlignment(textX / entry.width);
|
entry.menu.setSourceAlignment(textX / entry.width);
|
||||||
entry.menu.open();
|
entry.menu.open(BoxPointer.PopupAnimation.FULL);
|
||||||
};
|
};
|
||||||
|
|
||||||
function addContextMenu(entry, params) {
|
function addContextMenu(entry, params) {
|
||||||
|
@ -19,6 +19,8 @@ const ShellEntry = imports.ui.shellEntry;
|
|||||||
|
|
||||||
const LIST_ITEM_ICON_SIZE = 48;
|
const LIST_ITEM_ICON_SIZE = 48;
|
||||||
|
|
||||||
|
const REMEMBER_MOUNT_PASSWORD_KEY = 'remember-mount-password';
|
||||||
|
|
||||||
/* ------ Common Utils ------- */
|
/* ------ Common Utils ------- */
|
||||||
function _setLabelText(label, text) {
|
function _setLabelText(label, text) {
|
||||||
if (text) {
|
if (text) {
|
||||||
@ -387,7 +389,8 @@ const ShellMountPasswordDialog = new Lang.Class({
|
|||||||
if (flags & Gio.AskPasswordFlags.SAVING_SUPPORTED) {
|
if (flags & Gio.AskPasswordFlags.SAVING_SUPPORTED) {
|
||||||
this._rememberChoice = new CheckBox.CheckBox();
|
this._rememberChoice = new CheckBox.CheckBox();
|
||||||
this._rememberChoice.getLabelActor().text = _("Remember Password");
|
this._rememberChoice.getLabelActor().text = _("Remember Password");
|
||||||
this._rememberChoice.actor.checked = true;
|
this._rememberChoice.actor.checked =
|
||||||
|
global.settings.get_boolean(REMEMBER_MOUNT_PASSWORD_KEY);
|
||||||
this._messageBox.add(this._rememberChoice.actor);
|
this._messageBox.add(this._rememberChoice.actor);
|
||||||
} else {
|
} else {
|
||||||
this._rememberChoice = null;
|
this._rememberChoice = null;
|
||||||
@ -419,6 +422,8 @@ const ShellMountPasswordDialog = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_onEntryActivate: function() {
|
_onEntryActivate: function() {
|
||||||
|
global.settings.set_boolean(REMEMBER_MOUNT_PASSWORD_KEY,
|
||||||
|
this._rememberChoice && this._rememberChoice.actor.checked);
|
||||||
this.emit('response', 1,
|
this.emit('response', 1,
|
||||||
this._passwordEntry.get_text(),
|
this._passwordEntry.get_text(),
|
||||||
this._rememberChoice &&
|
this._rememberChoice &&
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const Gio = imports.gi.Gio;
|
const Gio = imports.gi.Gio;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
|
const Mainloop = imports.mainloop;
|
||||||
|
|
||||||
const PanelMenu = imports.ui.panelMenu;
|
const PanelMenu = imports.ui.panelMenu;
|
||||||
const PopupMenu = imports.ui.popupMenu;
|
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 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_LARGE = 1.25;
|
||||||
const DPI_FACTOR_LARGER = 1.5;
|
|
||||||
const DPI_FACTOR_LARGEST = 2.0;
|
|
||||||
|
|
||||||
const WM_SCHEMA = 'org.gnome.desktop.wm.preferences';
|
const WM_SCHEMA = 'org.gnome.desktop.wm.preferences';
|
||||||
const KEY_VISUAL_BELL = 'visual-bell';
|
const KEY_VISUAL_BELL = 'visual-bell';
|
||||||
@ -27,6 +23,7 @@ const KEY_VISUAL_BELL = 'visual-bell';
|
|||||||
const DESKTOP_INTERFACE_SCHEMA = 'org.gnome.desktop.interface';
|
const DESKTOP_INTERFACE_SCHEMA = 'org.gnome.desktop.interface';
|
||||||
const KEY_GTK_THEME = 'gtk-theme';
|
const KEY_GTK_THEME = 'gtk-theme';
|
||||||
const KEY_ICON_THEME = 'icon-theme';
|
const KEY_ICON_THEME = 'icon-theme';
|
||||||
|
const KEY_WM_THEME = 'theme';
|
||||||
const KEY_TEXT_SCALING_FACTOR = 'text-scaling-factor';
|
const KEY_TEXT_SCALING_FACTOR = 'text-scaling-factor';
|
||||||
|
|
||||||
const HIGH_CONTRAST_THEME = 'HighContrast';
|
const HIGH_CONTRAST_THEME = 'HighContrast';
|
||||||
@ -73,6 +70,25 @@ const ATIndicator = new Lang.Class({
|
|||||||
|
|
||||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||||
this.menu.addSettingsAction(_("Universal Access Settings"), 'gnome-universal-access-panel.desktop');
|
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) {
|
_buildItemExtended: function(string, initial_value, writable, on_set) {
|
||||||
@ -94,47 +110,63 @@ const ATIndicator = new Lang.Class({
|
|||||||
function(enabled) {
|
function(enabled) {
|
||||||
return settings.set_boolean(key, 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));
|
widget.setToggleState(settings.get_boolean(key));
|
||||||
});
|
|
||||||
|
this._queueSyncMenuVisibility();
|
||||||
|
}));
|
||||||
return widget;
|
return widget;
|
||||||
},
|
},
|
||||||
|
|
||||||
_buildHCItem: function() {
|
_buildHCItem: function() {
|
||||||
let settings = new Gio.Settings({ schema: DESKTOP_INTERFACE_SCHEMA });
|
let interfaceSettings = new Gio.Settings({ schema: DESKTOP_INTERFACE_SCHEMA });
|
||||||
let gtkTheme = settings.get_string(KEY_GTK_THEME);
|
let wmSettings = new Gio.Settings({ schema: WM_SCHEMA });
|
||||||
let iconTheme = settings.get_string(KEY_ICON_THEME);
|
let gtkTheme = interfaceSettings.get_string(KEY_GTK_THEME);
|
||||||
|
let iconTheme = interfaceSettings.get_string(KEY_ICON_THEME);
|
||||||
|
let wmTheme = wmSettings.get_string(KEY_WM_THEME);
|
||||||
let hasHC = (gtkTheme == HIGH_CONTRAST_THEME);
|
let hasHC = (gtkTheme == HIGH_CONTRAST_THEME);
|
||||||
let highContrast = this._buildItemExtended(
|
let highContrast = this._buildItemExtended(
|
||||||
_("High Contrast"),
|
_("High Contrast"),
|
||||||
hasHC,
|
hasHC,
|
||||||
settings.is_writable(KEY_GTK_THEME) && settings.is_writable(KEY_ICON_THEME),
|
interfaceSettings.is_writable(KEY_GTK_THEME) &&
|
||||||
|
interfaceSettings.is_writable(KEY_ICON_THEME) &&
|
||||||
|
wmSettings.is_writable(KEY_WM_THEME),
|
||||||
function (enabled) {
|
function (enabled) {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
settings.set_string(KEY_GTK_THEME, HIGH_CONTRAST_THEME);
|
interfaceSettings.set_string(KEY_GTK_THEME, HIGH_CONTRAST_THEME);
|
||||||
settings.set_string(KEY_ICON_THEME, HIGH_CONTRAST_THEME);
|
interfaceSettings.set_string(KEY_ICON_THEME, HIGH_CONTRAST_THEME);
|
||||||
|
wmSettings.set_string(KEY_WM_THEME, HIGH_CONTRAST_THEME);
|
||||||
} else if(!hasHC) {
|
} else if(!hasHC) {
|
||||||
settings.set_string(KEY_GTK_THEME, gtkTheme);
|
interfaceSettings.set_string(KEY_GTK_THEME, gtkTheme);
|
||||||
settings.set_string(KEY_ICON_THEME, iconTheme);
|
interfaceSettings.set_string(KEY_ICON_THEME, iconTheme);
|
||||||
|
wmSettings.set_string(KEY_WM_THEME, wmTheme);
|
||||||
} else {
|
} else {
|
||||||
settings.reset(KEY_GTK_THEME);
|
interfaceSettings.reset(KEY_GTK_THEME);
|
||||||
settings.reset(KEY_ICON_THEME);
|
interfaceSettings.reset(KEY_ICON_THEME);
|
||||||
|
wmSettings.reset(KEY_WM_THEME);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
settings.connect('changed::' + KEY_GTK_THEME, function() {
|
interfaceSettings.connect('changed::' + KEY_GTK_THEME, Lang.bind(this, function() {
|
||||||
let value = settings.get_string(KEY_GTK_THEME);
|
let value = interfaceSettings.get_string(KEY_GTK_THEME);
|
||||||
if (value == HIGH_CONTRAST_THEME) {
|
if (value == HIGH_CONTRAST_THEME) {
|
||||||
highContrast.setToggleState(true);
|
highContrast.setToggleState(true);
|
||||||
} else {
|
} else {
|
||||||
highContrast.setToggleState(false);
|
highContrast.setToggleState(false);
|
||||||
gtkTheme = value;
|
gtkTheme = value;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
settings.connect('changed::' + KEY_ICON_THEME, function() {
|
this._queueSyncMenuVisibility();
|
||||||
let value = settings.get_string(KEY_ICON_THEME);
|
}));
|
||||||
|
interfaceSettings.connect('changed::' + KEY_ICON_THEME, function() {
|
||||||
|
let value = interfaceSettings.get_string(KEY_ICON_THEME);
|
||||||
if (value != HIGH_CONTRAST_THEME)
|
if (value != HIGH_CONTRAST_THEME)
|
||||||
iconTheme = value;
|
iconTheme = value;
|
||||||
});
|
});
|
||||||
|
wmSettings.connect('changed::' + KEY_WM_THEME, function() {
|
||||||
|
let value = wmSettings.get_string(KEY_WM_THEME);
|
||||||
|
if (value != HIGH_CONTRAST_THEME)
|
||||||
|
wmTheme = value;
|
||||||
|
});
|
||||||
return highContrast;
|
return highContrast;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -153,11 +185,22 @@ const ATIndicator = new Lang.Class({
|
|||||||
else
|
else
|
||||||
settings.reset(KEY_TEXT_SCALING_FACTOR);
|
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 factor = settings.get_double(KEY_TEXT_SCALING_FACTOR);
|
||||||
let active = (factor > 1.0);
|
let active = (factor > 1.0);
|
||||||
widget.setToggleState(active);
|
widget.setToggleState(active);
|
||||||
});
|
|
||||||
|
this._queueSyncMenuVisibility();
|
||||||
|
}));
|
||||||
return widget;
|
return widget;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ATGreeterIndicator = new Lang.Class({
|
||||||
|
Name: 'ATGreeterIndicator',
|
||||||
|
Extends: ATIndicator,
|
||||||
|
|
||||||
|
// Override visibility handling to be always visible
|
||||||
|
_syncMenuVisibility: function() { },
|
||||||
|
_queueSyncMenuVisibility: function() { }
|
||||||
|
});
|
||||||
|
@ -9,6 +9,7 @@ const St = imports.gi.St;
|
|||||||
|
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
const MessageTray = imports.ui.messageTray;
|
const MessageTray = imports.ui.messageTray;
|
||||||
|
const NotificationDaemon = imports.ui.notificationDaemon;
|
||||||
const PanelMenu = imports.ui.panelMenu;
|
const PanelMenu = imports.ui.panelMenu;
|
||||||
const PopupMenu = imports.ui.popupMenu;
|
const PopupMenu = imports.ui.popupMenu;
|
||||||
|
|
||||||
@ -55,8 +56,8 @@ const Indicator = new Lang.Class({
|
|||||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||||
|
|
||||||
this._fullMenuItems = [new PopupMenu.PopupSeparatorMenuItem(),
|
this._fullMenuItems = [new PopupMenu.PopupSeparatorMenuItem(),
|
||||||
new PopupMenu.PopupMenuItem(_("Send Files to Device...")),
|
new PopupMenu.PopupMenuItem(_("Send Files to Device…")),
|
||||||
new PopupMenu.PopupMenuItem(_("Set up a New Device...")),
|
new PopupMenu.PopupMenuItem(_("Set Up a New Device…")),
|
||||||
new PopupMenu.PopupSeparatorMenuItem()];
|
new PopupMenu.PopupSeparatorMenuItem()];
|
||||||
this._hasDevices = false;
|
this._hasDevices = false;
|
||||||
|
|
||||||
@ -80,7 +81,7 @@ const Indicator = new Lang.Class({
|
|||||||
this._applet.connect('notify::show-full-menu', Lang.bind(this, this._updateFullMenu));
|
this._applet.connect('notify::show-full-menu', Lang.bind(this, this._updateFullMenu));
|
||||||
this._updateFullMenu();
|
this._updateFullMenu();
|
||||||
|
|
||||||
this.menu.addSettingsAction(_("Bluetooth Settings"), 'bluetooth-properties.desktop');
|
this.menu.addSettingsAction(_("Bluetooth Settings"), 'gnome-bluetooth-panel.desktop');
|
||||||
|
|
||||||
this._applet.connect('pincode-request', Lang.bind(this, this._pinRequest));
|
this._applet.connect('pincode-request', Lang.bind(this, this._pinRequest));
|
||||||
this._applet.connect('confirm-request', Lang.bind(this, this._confirmRequest));
|
this._applet.connect('confirm-request', Lang.bind(this, this._confirmRequest));
|
||||||
@ -235,26 +236,10 @@ const Indicator = new Lang.Class({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (device.capabilities & GnomeBluetoothApplet.Capabilities.OBEX_PUSH) {
|
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);
|
this._applet.send_to_address(device.bdaddr, device.alias);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
if (device.capabilities & GnomeBluetoothApplet.Capabilities.OBEX_FILE_TRANSFER) {
|
|
||||||
item.menu.addAction(_("Browse Files..."), Lang.bind(this, function(event) {
|
|
||||||
this._applet.browse_address(device.bdaddr, event.get_time(),
|
|
||||||
Lang.bind(this, function(applet, result) {
|
|
||||||
try {
|
|
||||||
applet.browse_address_finish(result);
|
|
||||||
} catch (e) {
|
|
||||||
this._ensureSource();
|
|
||||||
this._source.notify(new MessageTray.Notification(this._source,
|
|
||||||
_("Bluetooth"),
|
|
||||||
_("Error browsing device"),
|
|
||||||
{ body: _("The requested device cannot be browsed, error is '%s'").format(e) }));
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (device.type) {
|
switch (device.type) {
|
||||||
case GnomeBluetoothApplet.Type.KEYBOARD:
|
case GnomeBluetoothApplet.Type.KEYBOARD:
|
||||||
@ -302,6 +287,7 @@ const Indicator = new Lang.Class({
|
|||||||
_ensureSource: function() {
|
_ensureSource: function() {
|
||||||
if (!this._source) {
|
if (!this._source) {
|
||||||
this._source = new MessageTray.Source(_("Bluetooth"), 'bluetooth-active');
|
this._source = new MessageTray.Source(_("Bluetooth"), 'bluetooth-active');
|
||||||
|
this._source.policy = new NotificationDaemon.NotificationApplicationPolicy('gnome-bluetooth-panel');
|
||||||
Main.messageTray.add(this._source);
|
Main.messageTray.add(this._source);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -369,6 +355,7 @@ const ConfirmNotification = new Lang.Class({
|
|||||||
_init: function(source, applet, device_path, name, long_name, pin) {
|
_init: function(source, applet, device_path, name, long_name, pin) {
|
||||||
this.parent(source,
|
this.parent(source,
|
||||||
_("Bluetooth"),
|
_("Bluetooth"),
|
||||||
|
/* Translators: argument is the device short name */
|
||||||
_("Pairing confirmation for %s").format(name),
|
_("Pairing confirmation for %s").format(name),
|
||||||
{ customContent: true });
|
{ customContent: true });
|
||||||
this.setResident(true);
|
this.setResident(true);
|
||||||
@ -378,6 +365,7 @@ const ConfirmNotification = new Lang.Class({
|
|||||||
this.addBody(_("Device %s wants to pair with this computer").format(long_name));
|
this.addBody(_("Device %s wants to pair with this computer").format(long_name));
|
||||||
this.addBody(_("Please confirm whether the PIN '%06d' matches the one on the device.").format(pin));
|
this.addBody(_("Please confirm whether the PIN '%06d' matches the one on the device.").format(pin));
|
||||||
|
|
||||||
|
/* Translators: this is the verb, not the noun */
|
||||||
this.addButton('matches', _("Matches"));
|
this.addButton('matches', _("Matches"));
|
||||||
this.addButton('does-not-match', _("Does not match"));
|
this.addButton('does-not-match', _("Does not match"));
|
||||||
|
|
||||||
@ -460,10 +448,5 @@ const PinNotification = new Lang.Class({
|
|||||||
return this._entry.clutter_text.text.length == 6;
|
return this._entry.clutter_text.text.length == 6;
|
||||||
else
|
else
|
||||||
return true;
|
return true;
|
||||||
},
|
|
||||||
|
|
||||||
grabFocus: function(lockTray) {
|
|
||||||
this.parent(lockTray);
|
|
||||||
global.stage.set_key_focus(this._entry);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
|
||||||
|
const Clutter = imports.gi.Clutter;
|
||||||
const Gio = imports.gi.Gio;
|
const Gio = imports.gi.Gio;
|
||||||
const GLib = imports.gi.GLib;
|
const GLib = imports.gi.GLib;
|
||||||
const GnomeDesktop = imports.gi.GnomeDesktop;
|
const GnomeDesktop = imports.gi.GnomeDesktop;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
|
const Meta = imports.gi.Meta;
|
||||||
const Shell = imports.gi.Shell;
|
const Shell = imports.gi.Shell;
|
||||||
const Signals = imports.signals;
|
const Signals = imports.signals;
|
||||||
const St = imports.gi.St;
|
const St = imports.gi.St;
|
||||||
@ -21,6 +23,7 @@ try {
|
|||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
const PopupMenu = imports.ui.popupMenu;
|
const PopupMenu = imports.ui.popupMenu;
|
||||||
const PanelMenu = imports.ui.panelMenu;
|
const PanelMenu = imports.ui.panelMenu;
|
||||||
|
const SwitcherPopup = imports.ui.switcherPopup;
|
||||||
const Util = imports.misc.util;
|
const Util = imports.misc.util;
|
||||||
|
|
||||||
const DESKTOP_INPUT_SOURCES_SCHEMA = 'org.gnome.desktop.input-sources';
|
const DESKTOP_INPUT_SOURCES_SCHEMA = 'org.gnome.desktop.input-sources';
|
||||||
@ -47,6 +50,7 @@ const IBusManager = new Lang.Class({
|
|||||||
this._engines = {};
|
this._engines = {};
|
||||||
this._ready = false;
|
this._ready = false;
|
||||||
this._registerPropertiesId = 0;
|
this._registerPropertiesId = 0;
|
||||||
|
this._currentEngineName = null;
|
||||||
|
|
||||||
this._nameWatcherId = Gio.DBus.session.watch_name(IBus.SERVICE_IBUS,
|
this._nameWatcherId = Gio.DBus.session.watch_name(IBus.SERVICE_IBUS,
|
||||||
Gio.BusNameWatcherFlags.NONE,
|
Gio.BusNameWatcherFlags.NONE,
|
||||||
@ -66,6 +70,10 @@ const IBusManager = new Lang.Class({
|
|||||||
this._engines = {};
|
this._engines = {};
|
||||||
this._ready = false;
|
this._ready = false;
|
||||||
this._registerPropertiesId = 0;
|
this._registerPropertiesId = 0;
|
||||||
|
this._currentEngineName = null;
|
||||||
|
|
||||||
|
if (this._readyCallback)
|
||||||
|
this._readyCallback(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
_onNameAppeared: function() {
|
_onNameAppeared: function() {
|
||||||
@ -89,12 +97,10 @@ const IBusManager = new Lang.Class({
|
|||||||
let name = enginesList[i].get_name();
|
let name = enginesList[i].get_name();
|
||||||
this._engines[name] = enginesList[i];
|
this._engines[name] = enginesList[i];
|
||||||
}
|
}
|
||||||
|
this._updateReadiness();
|
||||||
} else {
|
} else {
|
||||||
this._clear();
|
this._clear();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._updateReadiness();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_initPanelService: function(ibus, result) {
|
_initPanelService: function(ibus, result) {
|
||||||
@ -105,27 +111,36 @@ const IBusManager = new Lang.Class({
|
|||||||
this._candidatePopup.setPanelService(this._panelService);
|
this._candidatePopup.setPanelService(this._panelService);
|
||||||
// Need to set this to get 'global-engine-changed' emitions
|
// Need to set this to get 'global-engine-changed' emitions
|
||||||
this._ibus.set_watch_ibus_signal(true);
|
this._ibus.set_watch_ibus_signal(true);
|
||||||
this._ibus.connect('global-engine-changed', Lang.bind(this, this._resetProperties));
|
this._ibus.connect('global-engine-changed', Lang.bind(this, this._engineChanged));
|
||||||
this._panelService.connect('update-property', Lang.bind(this, this._updateProperty));
|
this._panelService.connect('update-property', Lang.bind(this, this._updateProperty));
|
||||||
this._resetProperties();
|
// 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) {
|
||||||
|
let engine;
|
||||||
|
try {
|
||||||
|
engine = this._ibus.get_global_engine_async_finish(result);
|
||||||
|
if (!engine)
|
||||||
|
return;
|
||||||
|
} catch(e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._engineChanged(this._ibus, engine.get_name());
|
||||||
|
}));
|
||||||
|
this._updateReadiness();
|
||||||
} else {
|
} else {
|
||||||
this._clear();
|
this._clear();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._updateReadiness();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateReadiness: function() {
|
_updateReadiness: function() {
|
||||||
this._ready = (Object.keys(this._engines).length > 0 &&
|
this._ready = (Object.keys(this._engines).length > 0 &&
|
||||||
this._panelService != null);
|
this._panelService != null);
|
||||||
|
|
||||||
if (this._ready && this._readyCallback)
|
if (this._readyCallback)
|
||||||
this._readyCallback();
|
this._readyCallback(this._ready);
|
||||||
},
|
},
|
||||||
|
|
||||||
_resetProperties: function() {
|
_engineChanged: function(bus, engineName) {
|
||||||
this.emit('properties-registered', null);
|
this._currentEngineName = engineName;
|
||||||
|
|
||||||
if (this._registerPropertiesId != 0)
|
if (this._registerPropertiesId != 0)
|
||||||
return;
|
return;
|
||||||
@ -138,25 +153,18 @@ const IBusManager = new Lang.Class({
|
|||||||
this._panelService.disconnect(this._registerPropertiesId);
|
this._panelService.disconnect(this._registerPropertiesId);
|
||||||
this._registerPropertiesId = 0;
|
this._registerPropertiesId = 0;
|
||||||
|
|
||||||
this.emit('properties-registered', props);
|
this.emit('properties-registered', this._currentEngineName, props);
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateProperty: function(panel, prop) {
|
_updateProperty: function(panel, prop) {
|
||||||
this.emit('property-updated', prop);
|
this.emit('property-updated', this._currentEngineName, prop);
|
||||||
},
|
},
|
||||||
|
|
||||||
activateProperty: function(key, state) {
|
activateProperty: function(key, state) {
|
||||||
this._panelService.property_activate(key, state);
|
this._panelService.property_activate(key, state);
|
||||||
},
|
},
|
||||||
|
|
||||||
hasProperties: function(id) {
|
|
||||||
if (id == 'anthy')
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
getEngineDesc: function(id) {
|
getEngineDesc: function(id) {
|
||||||
if (!IBus || !this._ready)
|
if (!IBus || !this._ready)
|
||||||
return null;
|
return null;
|
||||||
@ -181,16 +189,127 @@ const LayoutMenuItem = new Lang.Class({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const InputSource = new Lang.Class({
|
||||||
|
Name: 'InputSource',
|
||||||
|
|
||||||
|
_init: function(type, id, displayName, shortName, index) {
|
||||||
|
this.type = type;
|
||||||
|
this.id = id;
|
||||||
|
this.displayName = displayName;
|
||||||
|
this._shortName = shortName;
|
||||||
|
this.index = index;
|
||||||
|
|
||||||
|
this._menuItem = new LayoutMenuItem(this.displayName, this._shortName);
|
||||||
|
this._menuItem.connect('activate', Lang.bind(this, this.activate));
|
||||||
|
this._indicatorLabel = new St.Label({ text: this._shortName });
|
||||||
|
|
||||||
|
this.properties = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
|
this._menuItem.destroy();
|
||||||
|
this._indicatorLabel.destroy();
|
||||||
|
},
|
||||||
|
|
||||||
|
get shortName() {
|
||||||
|
return this._shortName;
|
||||||
|
},
|
||||||
|
|
||||||
|
set shortName(v) {
|
||||||
|
this._shortName = v;
|
||||||
|
this._menuItem.indicator.set_text(v);
|
||||||
|
this._indicatorLabel.set_text(v);
|
||||||
|
},
|
||||||
|
|
||||||
|
get menuItem() {
|
||||||
|
return this._menuItem;
|
||||||
|
},
|
||||||
|
|
||||||
|
get indicatorLabel() {
|
||||||
|
return this._indicatorLabel;
|
||||||
|
},
|
||||||
|
|
||||||
|
activate: function() {
|
||||||
|
this.emit('activate');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
Signals.addSignalMethods(InputSource.prototype);
|
||||||
|
|
||||||
|
const InputSourcePopup = new Lang.Class({
|
||||||
|
Name: 'InputSourcePopup',
|
||||||
|
Extends: SwitcherPopup.SwitcherPopup,
|
||||||
|
|
||||||
|
_init: function(items, action, actionBackward) {
|
||||||
|
this.parent(items);
|
||||||
|
|
||||||
|
this._action = action;
|
||||||
|
this._actionBackward = actionBackward;
|
||||||
|
},
|
||||||
|
|
||||||
|
_createSwitcher: function() {
|
||||||
|
this._switcherList = new InputSourceSwitcher(this._items);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
_initialSelection: function(backward, binding) {
|
||||||
|
if (binding == 'switch-input-source') {
|
||||||
|
if (backward)
|
||||||
|
this._selectedIndex = this._items.length - 1;
|
||||||
|
} else if (binding == 'switch-input-source-backward') {
|
||||||
|
if (!backward)
|
||||||
|
this._selectedIndex = this._items.length - 1;
|
||||||
|
}
|
||||||
|
this._select(this._selectedIndex);
|
||||||
|
},
|
||||||
|
|
||||||
|
_keyPressHandler: function(keysym, backwards, action) {
|
||||||
|
if (action == this._action)
|
||||||
|
this._select(backwards ? this._previous() : this._next());
|
||||||
|
else if (action == this._actionBackward)
|
||||||
|
this._select(backwards ? this._next() : this._previous());
|
||||||
|
else if (keysym == Clutter.Left)
|
||||||
|
this._select(this._previous());
|
||||||
|
else if (keysym == Clutter.Right)
|
||||||
|
this._select(this._next());
|
||||||
|
},
|
||||||
|
|
||||||
|
_finish : function() {
|
||||||
|
this.parent();
|
||||||
|
|
||||||
|
this._items[this._selectedIndex].activate();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const InputSourceSwitcher = new Lang.Class({
|
||||||
|
Name: 'InputSourceSwitcher',
|
||||||
|
Extends: SwitcherPopup.SwitcherList,
|
||||||
|
|
||||||
|
_init: function(items) {
|
||||||
|
this.parent(true);
|
||||||
|
|
||||||
|
for (let i = 0; i < items.length; i++)
|
||||||
|
this._addIcon(items[i]);
|
||||||
|
},
|
||||||
|
|
||||||
|
_addIcon: function(item) {
|
||||||
|
let box = new St.BoxLayout({ vertical: true });
|
||||||
|
|
||||||
|
let bin = new St.Bin({ style_class: 'input-source-switcher-symbol' });
|
||||||
|
let symbol = new St.Label({ text: item.shortName });
|
||||||
|
bin.set_child(symbol);
|
||||||
|
box.add(bin, { x_fill: false, y_fill: false } );
|
||||||
|
|
||||||
|
let text = new St.Label({ text: item.displayName });
|
||||||
|
box.add(text, { x_fill: false });
|
||||||
|
|
||||||
|
this.addItem(box, text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const InputSourceIndicator = new Lang.Class({
|
const InputSourceIndicator = new Lang.Class({
|
||||||
Name: 'InputSourceIndicator',
|
Name: 'InputSourceIndicator',
|
||||||
Extends: PanelMenu.Button,
|
Extends: PanelMenu.Button,
|
||||||
|
|
||||||
_propertiesWhitelist: [
|
|
||||||
'InputMode',
|
|
||||||
'TypingMode',
|
|
||||||
'DictMode'
|
|
||||||
],
|
|
||||||
|
|
||||||
_init: function() {
|
_init: function() {
|
||||||
this.parent(0.0, _("Keyboard"));
|
this.parent(0.0, _("Keyboard"));
|
||||||
|
|
||||||
@ -201,14 +320,36 @@ const InputSourceIndicator = new Lang.Class({
|
|||||||
this.actor.add_actor(this._container);
|
this.actor.add_actor(this._container);
|
||||||
this.actor.add_style_class_name('panel-status-button');
|
this.actor.add_style_class_name('panel-status-button');
|
||||||
|
|
||||||
this._labelActors = {};
|
// All valid input sources currently in the gsettings
|
||||||
this._layoutItems = {};
|
// KEY_INPUT_SOURCES list indexed by their index there
|
||||||
|
this._inputSources = {};
|
||||||
|
// All valid input sources currently in the gsettings
|
||||||
|
// KEY_INPUT_SOURCES list of type INPUT_SOURCE_TYPE_IBUS
|
||||||
|
// indexed by the IBus ID
|
||||||
|
this._ibusSources = {};
|
||||||
|
|
||||||
|
this._currentSource = null;
|
||||||
|
|
||||||
|
// All valid input sources currently in the gsettings
|
||||||
|
// KEY_INPUT_SOURCES list ordered by most recently used
|
||||||
|
this._mruSources = [];
|
||||||
|
this._keybindingAction =
|
||||||
|
Main.wm.addKeybinding('switch-input-source',
|
||||||
|
new Gio.Settings({ schema: "org.gnome.desktop.wm.keybindings" }),
|
||||||
|
Meta.KeyBindingFlags.REVERSES,
|
||||||
|
Shell.KeyBindingMode.ALL & ~Shell.KeyBindingMode.MESSAGE_TRAY,
|
||||||
|
Lang.bind(this, this._switchInputSource));
|
||||||
|
this._keybindingActionBackward =
|
||||||
|
Main.wm.addKeybinding('switch-input-source-backward',
|
||||||
|
new Gio.Settings({ schema: "org.gnome.desktop.wm.keybindings" }),
|
||||||
|
Meta.KeyBindingFlags.REVERSES |
|
||||||
|
Meta.KeyBindingFlags.REVERSED,
|
||||||
|
Shell.KeyBindingMode.ALL & ~Shell.KeyBindingMode.MESSAGE_TRAY,
|
||||||
|
Lang.bind(this, this._switchInputSource));
|
||||||
this._settings = new Gio.Settings({ schema: DESKTOP_INPUT_SOURCES_SCHEMA });
|
this._settings = new Gio.Settings({ schema: DESKTOP_INPUT_SOURCES_SCHEMA });
|
||||||
this._settings.connect('changed::' + KEY_CURRENT_INPUT_SOURCE, Lang.bind(this, this._currentInputSourceChanged));
|
this._settings.connect('changed::' + KEY_CURRENT_INPUT_SOURCE, Lang.bind(this, this._currentInputSourceChanged));
|
||||||
this._settings.connect('changed::' + KEY_INPUT_SOURCES, Lang.bind(this, this._inputSourcesChanged));
|
this._settings.connect('changed::' + KEY_INPUT_SOURCES, Lang.bind(this, this._inputSourcesChanged));
|
||||||
|
|
||||||
this._currentSourceIndex = this._settings.get_uint(KEY_CURRENT_INPUT_SOURCE);
|
|
||||||
this._xkbInfo = new GnomeDesktop.XkbInfo();
|
this._xkbInfo = new GnomeDesktop.XkbInfo();
|
||||||
|
|
||||||
this._propSeparator = new PopupMenu.PopupSeparatorMenuItem();
|
this._propSeparator = new PopupMenu.PopupSeparatorMenuItem();
|
||||||
@ -217,9 +358,8 @@ const InputSourceIndicator = new Lang.Class({
|
|||||||
this.menu.addMenuItem(this._propSection);
|
this.menu.addMenuItem(this._propSection);
|
||||||
this._propSection.actor.hide();
|
this._propSection.actor.hide();
|
||||||
|
|
||||||
this._properties = null;
|
this._ibusReady = false;
|
||||||
|
this._ibusManager = new IBusManager(Lang.bind(this, this._ibusReadyCallback));
|
||||||
this._ibusManager = new IBusManager(Lang.bind(this, this._inputSourcesChanged));
|
|
||||||
this._ibusManager.connect('properties-registered', Lang.bind(this, this._ibusPropertiesRegistered));
|
this._ibusManager.connect('properties-registered', Lang.bind(this, this._ibusPropertiesRegistered));
|
||||||
this._ibusManager.connect('property-updated', Lang.bind(this, this._ibusPropertyUpdated));
|
this._ibusManager.connect('property-updated', Lang.bind(this, this._ibusPropertyUpdated));
|
||||||
this._inputSourcesChanged();
|
this._inputSourcesChanged();
|
||||||
@ -230,7 +370,14 @@ const InputSourceIndicator = new Lang.Class({
|
|||||||
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
|
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
|
||||||
this._sessionUpdated();
|
this._sessionUpdated();
|
||||||
|
|
||||||
this.menu.addSettingsAction(_("Region and Language Settings"), 'gnome-region-panel.desktop');
|
this.menu.addSettingsAction(_("Region & Language Settings"), 'gnome-region-panel.desktop');
|
||||||
|
|
||||||
|
this._sourcesPerWindow = false;
|
||||||
|
this._focusWindowNotifyId = 0;
|
||||||
|
this._overviewShowingId = 0;
|
||||||
|
this._overviewHiddenId = 0;
|
||||||
|
this._settings.connect('changed::per-window', Lang.bind(this, this._sourcesPerWindowChanged));
|
||||||
|
this._sourcesPerWindowChanged();
|
||||||
},
|
},
|
||||||
|
|
||||||
_sessionUpdated: function() {
|
_sessionUpdated: function() {
|
||||||
@ -241,18 +388,40 @@ const InputSourceIndicator = new Lang.Class({
|
|||||||
this._showLayoutItem.actor.visible = Main.sessionMode.allowSettings;
|
this._showLayoutItem.actor.visible = Main.sessionMode.allowSettings;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_ibusReadyCallback: function(ready) {
|
||||||
|
if (this._ibusReady == ready)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._ibusReady = ready;
|
||||||
|
this._mruSources = [];
|
||||||
|
this._inputSourcesChanged();
|
||||||
|
},
|
||||||
|
|
||||||
|
_switchInputSource: function(display, screen, window, binding) {
|
||||||
|
if (this._mruSources.length < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let popup = new InputSourcePopup(this._mruSources, this._keybindingAction, this._keybindingActionBackward);
|
||||||
|
let modifiers = binding.get_modifiers();
|
||||||
|
let backwards = modifiers & Meta.VirtualModifier.SHIFT_MASK;
|
||||||
|
if (!popup.show(backwards, binding.get_name(), binding.get_mask()))
|
||||||
|
popup.destroy();
|
||||||
|
},
|
||||||
|
|
||||||
_currentInputSourceChanged: function() {
|
_currentInputSourceChanged: function() {
|
||||||
let nVisibleSources = Object.keys(this._layoutItems).length;
|
let nVisibleSources = Object.keys(this._inputSources).length;
|
||||||
let newCurrentSourceIndex = this._settings.get_uint(KEY_CURRENT_INPUT_SOURCE);
|
let newSourceIndex = this._settings.get_uint(KEY_CURRENT_INPUT_SOURCE);
|
||||||
let newLayoutItem = this._layoutItems[newCurrentSourceIndex];
|
let newSource = this._inputSources[newSourceIndex];
|
||||||
let hasProperties;
|
|
||||||
|
|
||||||
if (newLayoutItem)
|
let oldSource;
|
||||||
hasProperties = this._ibusManager.hasProperties(newLayoutItem.ibusEngineId);
|
[oldSource, this._currentSource] = [this._currentSource, newSource];
|
||||||
else
|
|
||||||
hasProperties = false;
|
|
||||||
|
|
||||||
if (!newLayoutItem || (nVisibleSources < 2 && !hasProperties)) {
|
if (oldSource) {
|
||||||
|
oldSource.menuItem.setShowDot(false);
|
||||||
|
oldSource.indicatorLabel.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newSource || (nVisibleSources < 2 && !newSource.properties)) {
|
||||||
// This source index might be invalid if we weren't able
|
// This source index might be invalid if we weren't able
|
||||||
// to build a menu item for it, so we hide ourselves since
|
// to build a menu item for it, so we hide ourselves since
|
||||||
// we can't fix it here. *shrug*
|
// we can't fix it here. *shrug*
|
||||||
@ -266,106 +435,117 @@ const InputSourceIndicator = new Lang.Class({
|
|||||||
|
|
||||||
this.actor.show();
|
this.actor.show();
|
||||||
|
|
||||||
if (this._layoutItems[this._currentSourceIndex]) {
|
newSource.menuItem.setShowDot(true);
|
||||||
this._layoutItems[this._currentSourceIndex].setShowDot(false);
|
newSource.indicatorLabel.show();
|
||||||
this._container.set_skip_paint(this._labelActors[this._currentSourceIndex], true);
|
|
||||||
}
|
|
||||||
|
|
||||||
newLayoutItem.setShowDot(true);
|
this._buildPropSection(newSource.properties);
|
||||||
|
|
||||||
let newLabelActor = this._labelActors[newCurrentSourceIndex];
|
for (let i = 1; i < this._mruSources.length; ++i)
|
||||||
this._container.set_skip_paint(newLabelActor, false);
|
if (this._mruSources[i] == newSource) {
|
||||||
|
let currentSource = this._mruSources.splice(i, 1);
|
||||||
|
this._mruSources = currentSource.concat(this._mruSources);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasProperties)
|
this._changePerWindowSource();
|
||||||
newLabelActor.set_text(newLayoutItem.indicator.get_text());
|
|
||||||
|
|
||||||
this._currentSourceIndex = newCurrentSourceIndex;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_inputSourcesChanged: function() {
|
_inputSourcesChanged: function() {
|
||||||
let sources = this._settings.get_value(KEY_INPUT_SOURCES);
|
let sources = this._settings.get_value(KEY_INPUT_SOURCES);
|
||||||
let nSources = sources.n_children();
|
let nSources = sources.n_children();
|
||||||
|
|
||||||
for (let i in this._layoutItems)
|
for (let i in this._inputSources)
|
||||||
this._layoutItems[i].destroy();
|
this._inputSources[i].destroy();
|
||||||
|
|
||||||
for (let i in this._labelActors)
|
this._inputSources = {};
|
||||||
this._labelActors[i].destroy();
|
this._ibusSources = {};
|
||||||
|
|
||||||
this._layoutItems = {};
|
let inputSourcesByShortName = {};
|
||||||
this._labelActors = {};
|
|
||||||
|
|
||||||
let infos = [];
|
|
||||||
let infosByShortName = {};
|
|
||||||
|
|
||||||
for (let i = 0; i < nSources; i++) {
|
for (let i = 0; i < nSources; i++) {
|
||||||
let info = { exists: false };
|
let displayName;
|
||||||
|
let shortName;
|
||||||
let [type, id] = sources.get_child_value(i).deep_unpack();
|
let [type, id] = sources.get_child_value(i).deep_unpack();
|
||||||
|
let exists = false;
|
||||||
|
|
||||||
if (type == INPUT_SOURCE_TYPE_XKB) {
|
if (type == INPUT_SOURCE_TYPE_XKB) {
|
||||||
[info.exists, info.displayName, info.shortName, , ] =
|
[exists, displayName, shortName, , ] =
|
||||||
this._xkbInfo.get_layout_info(id);
|
this._xkbInfo.get_layout_info(id);
|
||||||
} else if (type == INPUT_SOURCE_TYPE_IBUS) {
|
} else if (type == INPUT_SOURCE_TYPE_IBUS) {
|
||||||
let engineDesc = this._ibusManager.getEngineDesc(id);
|
let engineDesc = this._ibusManager.getEngineDesc(id);
|
||||||
if (engineDesc) {
|
if (engineDesc) {
|
||||||
let language = IBus.get_language_name(engineDesc.get_language());
|
let language = IBus.get_language_name(engineDesc.get_language());
|
||||||
|
exists = true;
|
||||||
info.exists = true;
|
displayName = language + ' (' + engineDesc.get_longname() + ')';
|
||||||
info.displayName = language + ' (' + engineDesc.get_longname() + ')';
|
shortName = this._makeEngineShortName(engineDesc);
|
||||||
info.shortName = this._makeEngineShortName(engineDesc);
|
|
||||||
info.ibusEngineId = id;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!info.exists)
|
if (!exists)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
info.sourceIndex = i;
|
let is = new InputSource(type, id, displayName, shortName, i);
|
||||||
|
|
||||||
if (!(info.shortName in infosByShortName))
|
is.connect('activate', Lang.bind(this, function() {
|
||||||
infosByShortName[info.shortName] = [];
|
if (this._currentSource && this._currentSource.index == is.index)
|
||||||
infosByShortName[info.shortName].push(info);
|
return;
|
||||||
infos.push(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < infos.length; i++) {
|
|
||||||
let info = infos[i];
|
|
||||||
if (infosByShortName[info.shortName].length > 1) {
|
|
||||||
let sub = infosByShortName[info.shortName].indexOf(info) + 1;
|
|
||||||
info.shortName += String.fromCharCode(0x2080 + sub);
|
|
||||||
}
|
|
||||||
|
|
||||||
let item = new LayoutMenuItem(info.displayName, info.shortName);
|
|
||||||
item.ibusEngineId = info.ibusEngineId;
|
|
||||||
this._layoutItems[info.sourceIndex] = item;
|
|
||||||
this.menu.addMenuItem(item, i);
|
|
||||||
item.connect('activate', Lang.bind(this, function() {
|
|
||||||
this._settings.set_value(KEY_CURRENT_INPUT_SOURCE,
|
this._settings.set_value(KEY_CURRENT_INPUT_SOURCE,
|
||||||
GLib.Variant.new_uint32(info.sourceIndex));
|
GLib.Variant.new_uint32(is.index));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let shortLabel = new St.Label({ text: info.shortName });
|
if (!(is.shortName in inputSourcesByShortName))
|
||||||
this._labelActors[info.sourceIndex] = shortLabel;
|
inputSourcesByShortName[is.shortName] = [];
|
||||||
this._container.add_actor(shortLabel);
|
inputSourcesByShortName[is.shortName].push(is);
|
||||||
this._container.set_skip_paint(shortLabel, true);
|
|
||||||
|
this._inputSources[is.index] = is;
|
||||||
|
|
||||||
|
if (is.type == INPUT_SOURCE_TYPE_IBUS)
|
||||||
|
this._ibusSources[is.id] = is;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let menuIndex = 0;
|
||||||
|
for (let i in this._inputSources) {
|
||||||
|
let is = this._inputSources[i];
|
||||||
|
if (inputSourcesByShortName[is.shortName].length > 1) {
|
||||||
|
let sub = inputSourcesByShortName[is.shortName].indexOf(is) + 1;
|
||||||
|
is.shortName += String.fromCharCode(0x2080 + sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.menu.addMenuItem(is.menuItem, menuIndex++);
|
||||||
|
|
||||||
|
is.indicatorLabel.hide();
|
||||||
|
this._container.add_actor(is.indicatorLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sourcesList = [];
|
||||||
|
for (let i in this._inputSources)
|
||||||
|
sourcesList.push(this._inputSources[i]);
|
||||||
|
|
||||||
|
let mruSources = [];
|
||||||
|
for (let i = 0; i < this._mruSources.length; i++) {
|
||||||
|
for (let j = 0; j < sourcesList.length; j++)
|
||||||
|
if (this._mruSources[i].type == sourcesList[j].type &&
|
||||||
|
this._mruSources[i].id == sourcesList[j].id) {
|
||||||
|
mruSources = mruSources.concat(sourcesList.splice(j, 1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._mruSources = mruSources.concat(sourcesList);
|
||||||
|
|
||||||
this._currentInputSourceChanged();
|
this._currentInputSourceChanged();
|
||||||
},
|
},
|
||||||
|
|
||||||
_showLayout: function() {
|
_showLayout: function() {
|
||||||
Main.overview.hide();
|
Main.overview.hide();
|
||||||
|
|
||||||
let sources = this._settings.get_value(KEY_INPUT_SOURCES);
|
let source = this._currentSource;
|
||||||
let current = this._settings.get_uint(KEY_CURRENT_INPUT_SOURCE);
|
|
||||||
let [type, id] = sources.get_child_value(current).deep_unpack();
|
|
||||||
let xkbLayout = '';
|
let xkbLayout = '';
|
||||||
let xkbVariant = '';
|
let xkbVariant = '';
|
||||||
|
|
||||||
if (type == INPUT_SOURCE_TYPE_XKB) {
|
if (source.type == INPUT_SOURCE_TYPE_XKB) {
|
||||||
[, , , xkbLayout, xkbVariant] = this._xkbInfo.get_layout_info(id);
|
[, , , xkbLayout, xkbVariant] = this._xkbInfo.get_layout_info(source.id);
|
||||||
} else if (type == INPUT_SOURCE_TYPE_IBUS) {
|
} else if (source.type == INPUT_SOURCE_TYPE_IBUS) {
|
||||||
let engineDesc = this._ibusManager.getEngineDesc(id);
|
let engineDesc = this._ibusManager.getEngineDesc(source.id);
|
||||||
if (engineDesc) {
|
if (engineDesc) {
|
||||||
xkbLayout = engineDesc.get_layout();
|
xkbLayout = engineDesc.get_layout();
|
||||||
xkbVariant = '';
|
xkbVariant = '';
|
||||||
@ -394,26 +574,25 @@ const InputSourceIndicator = new Lang.Class({
|
|||||||
return String.fromCharCode(0x2328); // keyboard glyph
|
return String.fromCharCode(0x2328); // keyboard glyph
|
||||||
},
|
},
|
||||||
|
|
||||||
_propertyWhitelisted: function(prop) {
|
_ibusPropertiesRegistered: function(im, engineName, props) {
|
||||||
for (let i = 0; i < this._propertiesWhitelist.length; ++i) {
|
let source = this._ibusSources[engineName];
|
||||||
let key = prop.get_key();
|
if (!source)
|
||||||
if (key.substr(0, this._propertiesWhitelist[i].length) == this._propertiesWhitelist[i])
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
_ibusPropertiesRegistered: function(im, props) {
|
|
||||||
this._properties = props;
|
|
||||||
this._buildPropSection();
|
|
||||||
},
|
|
||||||
|
|
||||||
_ibusPropertyUpdated: function(im, prop) {
|
|
||||||
if (!this._propertyWhitelisted(prop))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (this._updateSubProperty(this._properties, prop))
|
source.properties = props;
|
||||||
this._buildPropSection();
|
|
||||||
|
if (source == this._currentSource)
|
||||||
|
this._currentInputSourceChanged();
|
||||||
|
},
|
||||||
|
|
||||||
|
_ibusPropertyUpdated: function(im, engineName, prop) {
|
||||||
|
let source = this._ibusSources[engineName];
|
||||||
|
if (!source)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this._updateSubProperty(source.properties, prop) &&
|
||||||
|
source == this._currentSource)
|
||||||
|
this._currentInputSourceChanged();
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateSubProperty: function(props, prop) {
|
_updateSubProperty: function(props, prop) {
|
||||||
@ -433,25 +612,12 @@ const InputSourceIndicator = new Lang.Class({
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateIndicatorLabel: function(text) {
|
_buildPropSection: function(properties) {
|
||||||
let layoutItem = this._layoutItems[this._currentSourceIndex];
|
|
||||||
let hasProperties;
|
|
||||||
|
|
||||||
if (layoutItem)
|
|
||||||
hasProperties = this._ibusManager.hasProperties(layoutItem.ibusEngineId);
|
|
||||||
else
|
|
||||||
hasProperties = false;
|
|
||||||
|
|
||||||
if (hasProperties)
|
|
||||||
this._labelActors[this._currentSourceIndex].set_text(text);
|
|
||||||
},
|
|
||||||
|
|
||||||
_buildPropSection: function() {
|
|
||||||
this._propSeparator.actor.hide();
|
this._propSeparator.actor.hide();
|
||||||
this._propSection.actor.hide();
|
this._propSection.actor.hide();
|
||||||
this._propSection.removeAll();
|
this._propSection.removeAll();
|
||||||
|
|
||||||
this._buildPropSubMenu(this._propSection, this._properties);
|
this._buildPropSubMenu(this._propSection, properties);
|
||||||
|
|
||||||
if (!this._propSection.isEmpty()) {
|
if (!this._propSection.isEmpty()) {
|
||||||
this._propSection.actor.show();
|
this._propSection.actor.show();
|
||||||
@ -468,8 +634,7 @@ const InputSourceIndicator = new Lang.Class({
|
|||||||
for (let i = 0; (p = props.get(i)) != null; ++i) {
|
for (let i = 0; (p = props.get(i)) != null; ++i) {
|
||||||
let prop = p;
|
let prop = p;
|
||||||
|
|
||||||
if (!this._propertyWhitelisted(prop) ||
|
if (!prop.get_visible())
|
||||||
!prop.get_visible())
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (prop.get_key() == 'InputMode') {
|
if (prop.get_key() == 'InputMode') {
|
||||||
@ -480,15 +645,17 @@ const InputSourceIndicator = new Lang.Class({
|
|||||||
text = prop.get_label().get_text();
|
text = prop.get_label().get_text();
|
||||||
|
|
||||||
if (text && text.length > 0 && text.length < 3)
|
if (text && text.length > 0 && text.length < 3)
|
||||||
this._updateIndicatorLabel(text);
|
this._currentSource.indicatorLabel.set_text(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
let item;
|
let item;
|
||||||
let type = prop.get_prop_type();
|
switch (prop.get_prop_type()) {
|
||||||
if (type == IBus.PropType.MENU) {
|
case IBus.PropType.MENU:
|
||||||
item = new PopupMenu.PopupSubMenuMenuItem(prop.get_label().get_text());
|
item = new PopupMenu.PopupSubMenuMenuItem(prop.get_label().get_text());
|
||||||
this._buildPropSubMenu(item.menu, prop.get_sub_props());
|
this._buildPropSubMenu(item.menu, prop.get_sub_props());
|
||||||
} else if (type == IBus.PropType.RADIO) {
|
break;
|
||||||
|
|
||||||
|
case IBus.PropType.RADIO:
|
||||||
item = new PopupMenu.PopupMenuItem(prop.get_label().get_text());
|
item = new PopupMenu.PopupMenuItem(prop.get_label().get_text());
|
||||||
item.prop = prop;
|
item.prop = prop;
|
||||||
radioGroup.push(item);
|
radioGroup.push(item);
|
||||||
@ -513,7 +680,39 @@ const InputSourceIndicator = new Lang.Class({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
} else {
|
break;
|
||||||
|
|
||||||
|
case IBus.PropType.TOGGLE:
|
||||||
|
item = new PopupMenu.PopupSwitchMenuItem(prop.get_label().get_text(), prop.get_state() == IBus.PropState.CHECKED);
|
||||||
|
item.prop = prop;
|
||||||
|
item.connect('toggled', Lang.bind(this, function() {
|
||||||
|
if (item.state) {
|
||||||
|
item.prop.set_state(IBus.PropState.CHECKED);
|
||||||
|
this._ibusManager.activateProperty(item.prop.get_key(),
|
||||||
|
IBus.PropState.CHECKED);
|
||||||
|
} else {
|
||||||
|
item.prop.set_state(IBus.PropState.UNCHECKED);
|
||||||
|
this._ibusManager.activateProperty(item.prop.get_key(),
|
||||||
|
IBus.PropState.UNCHECKED);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IBus.PropType.NORMAL:
|
||||||
|
item = new PopupMenu.PopupMenuItem(prop.get_label().get_text());
|
||||||
|
item.prop = prop;
|
||||||
|
item.connect('activate', Lang.bind(this, function() {
|
||||||
|
this._ibusManager.activateProperty(item.prop.get_key(),
|
||||||
|
item.prop.get_state());
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IBus.PropType.SEPARATOR:
|
||||||
|
item = new PopupMenu.PopupSeparatorMenuItem();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
log ('IBus property %s has invalid type %d'.format(prop.get_key(), type));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -522,14 +721,91 @@ const InputSourceIndicator = new Lang.Class({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_getNewInputSource: function(current) {
|
||||||
|
for (let i in this._inputSources) {
|
||||||
|
let is = this._inputSources[i];
|
||||||
|
if (is.type == current.type &&
|
||||||
|
is.id == current.id)
|
||||||
|
return is;
|
||||||
|
}
|
||||||
|
return this._currentSource;
|
||||||
|
},
|
||||||
|
|
||||||
|
_getCurrentWindow: function() {
|
||||||
|
if (Main.overview.visible)
|
||||||
|
return Main.overview;
|
||||||
|
else
|
||||||
|
return global.display.focus_window;
|
||||||
|
},
|
||||||
|
|
||||||
|
_setPerWindowInputSource: function() {
|
||||||
|
let window = this._getCurrentWindow();
|
||||||
|
if (!window)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!window._inputSources) {
|
||||||
|
window._inputSources = this._inputSources;
|
||||||
|
window._currentSource = this._currentSource;
|
||||||
|
} else if (window._inputSources == this._inputSources) {
|
||||||
|
window._currentSource.activate();
|
||||||
|
} else {
|
||||||
|
window._inputSources = this._inputSources;
|
||||||
|
window._currentSource = this._getNewInputSource(window._currentSource);
|
||||||
|
window._currentSource.activate();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_sourcesPerWindowChanged: function() {
|
||||||
|
this._sourcesPerWindow = this._settings.get_boolean('per-window');
|
||||||
|
|
||||||
|
if (this._sourcesPerWindow && this._focusWindowNotifyId == 0) {
|
||||||
|
this._focusWindowNotifyId = global.display.connect('notify::focus-window',
|
||||||
|
Lang.bind(this, this._setPerWindowInputSource));
|
||||||
|
this._overviewShowingId = Main.overview.connect('showing',
|
||||||
|
Lang.bind(this, this._setPerWindowInputSource));
|
||||||
|
this._overviewHiddenId = Main.overview.connect('hidden',
|
||||||
|
Lang.bind(this, this._setPerWindowInputSource));
|
||||||
|
} else if (!this._sourcesPerWindow && this._focusWindowNotifyId != 0) {
|
||||||
|
global.display.disconnect(this._focusWindowNotifyId);
|
||||||
|
this._focusWindowNotifyId = 0;
|
||||||
|
Main.overview.disconnect(this._overviewShowingId);
|
||||||
|
this._overviewShowingId = 0;
|
||||||
|
Main.overview.disconnect(this._overviewHiddenId);
|
||||||
|
this._overviewHiddenId = 0;
|
||||||
|
|
||||||
|
let windows = global.get_window_actors().map(function(w) {
|
||||||
|
return w.meta_window;
|
||||||
|
});
|
||||||
|
for (let i = 0; i < windows.length; ++i) {
|
||||||
|
delete windows[i]._inputSources;
|
||||||
|
delete windows[i]._currentSource;
|
||||||
|
}
|
||||||
|
delete Main.overview._inputSources;
|
||||||
|
delete Main.overview._currentSource;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_changePerWindowSource: function() {
|
||||||
|
if (!this._sourcesPerWindow)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let window = this._getCurrentWindow();
|
||||||
|
if (!window)
|
||||||
|
return;
|
||||||
|
|
||||||
|
window._inputSources = this._inputSources;
|
||||||
|
window._currentSource = this._currentSource;
|
||||||
|
},
|
||||||
|
|
||||||
_containerGetPreferredWidth: function(container, for_height, alloc) {
|
_containerGetPreferredWidth: function(container, for_height, alloc) {
|
||||||
// Here, and in _containerGetPreferredHeight, we need to query
|
// Here, and in _containerGetPreferredHeight, we need to query
|
||||||
// for the height of all children, but we ignore the results
|
// for the height of all children, but we ignore the results
|
||||||
// for those we don't actually display.
|
// for those we don't actually display.
|
||||||
let max_min_width = 0, max_natural_width = 0;
|
let max_min_width = 0, max_natural_width = 0;
|
||||||
|
|
||||||
for (let i in this._labelActors) {
|
for (let i in this._inputSources) {
|
||||||
let [min_width, natural_width] = this._labelActors[i].get_preferred_width(for_height);
|
let is = this._inputSources[i];
|
||||||
|
let [min_width, natural_width] = is.indicatorLabel.get_preferred_width(for_height);
|
||||||
max_min_width = Math.max(max_min_width, min_width);
|
max_min_width = Math.max(max_min_width, min_width);
|
||||||
max_natural_width = Math.max(max_natural_width, natural_width);
|
max_natural_width = Math.max(max_natural_width, natural_width);
|
||||||
}
|
}
|
||||||
@ -541,8 +817,9 @@ const InputSourceIndicator = new Lang.Class({
|
|||||||
_containerGetPreferredHeight: function(container, for_width, alloc) {
|
_containerGetPreferredHeight: function(container, for_width, alloc) {
|
||||||
let max_min_height = 0, max_natural_height = 0;
|
let max_min_height = 0, max_natural_height = 0;
|
||||||
|
|
||||||
for (let i in this._labelActors) {
|
for (let i in this._inputSources) {
|
||||||
let [min_height, natural_height] = this._labelActors[i].get_preferred_height(for_width);
|
let is = this._inputSources[i];
|
||||||
|
let [min_height, natural_height] = is.indicatorLabel.get_preferred_height(for_width);
|
||||||
max_min_height = Math.max(max_min_height, min_height);
|
max_min_height = Math.max(max_min_height, min_height);
|
||||||
max_natural_height = Math.max(max_natural_height, natural_height);
|
max_natural_height = Math.max(max_natural_height, natural_height);
|
||||||
}
|
}
|
||||||
@ -558,7 +835,9 @@ const InputSourceIndicator = new Lang.Class({
|
|||||||
box.y2 -= box.y1;
|
box.y2 -= box.y1;
|
||||||
box.y1 = 0;
|
box.y1 = 0;
|
||||||
|
|
||||||
for (let i in this._labelActors)
|
for (let i in this._inputSources) {
|
||||||
this._labelActors[i].allocate_align_fill(box, 0.5, 0, false, false, flags);
|
let is = this._inputSources[i];
|
||||||
|
is.indicatorLabel.allocate_align_fill(box, 0.5, 0, false, false, flags);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -10,48 +10,53 @@ const PanelMenu = imports.ui.panelMenu;
|
|||||||
const PopupMenu = imports.ui.popupMenu;
|
const PopupMenu = imports.ui.popupMenu;
|
||||||
const VolumeMenu = imports.ui.status.volume;
|
const VolumeMenu = imports.ui.status.volume;
|
||||||
|
|
||||||
|
const FakeStatusIcon = new Lang.Class({
|
||||||
|
Name: 'FakeStatusIcon',
|
||||||
|
|
||||||
|
_init: function(button) {
|
||||||
|
this.actor = new St.BoxLayout({ style_class: 'panel-status-button-box' });
|
||||||
|
this._button = button;
|
||||||
|
this._button.connect('icons-updated', Lang.bind(this, this._reconstructIcons));
|
||||||
|
this._button.actor.bind_property('visible', this.actor, 'visible',
|
||||||
|
GObject.BindingFlags.SYNC_CREATE);
|
||||||
|
this._reconstructIcons();
|
||||||
|
},
|
||||||
|
|
||||||
|
_reconstructIcons: function() {
|
||||||
|
this.actor.destroy_all_children();
|
||||||
|
this._button.icons.forEach(Lang.bind(this, function(icon) {
|
||||||
|
let newIcon = new St.Icon({ style_class: 'system-status-icon' });
|
||||||
|
icon.bind_property('gicon', newIcon, 'gicon',
|
||||||
|
GObject.BindingFlags.SYNC_CREATE);
|
||||||
|
icon.bind_property('visible', newIcon, 'visible',
|
||||||
|
GObject.BindingFlags.SYNC_CREATE);
|
||||||
|
this.actor.add_actor(newIcon);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const Indicator = new Lang.Class({
|
const Indicator = new Lang.Class({
|
||||||
Name: 'LockScreenMenuIndicator',
|
Name: 'LockScreenMenuIndicator',
|
||||||
Extends: PanelMenu.SystemStatusButton,
|
Extends: PanelMenu.SystemStatusButton,
|
||||||
|
|
||||||
_init: function() {
|
_init: function() {
|
||||||
this.parent(null, _("Volume, network, battery"));
|
this.parent(null, _("Volume, network, battery"));
|
||||||
|
this._box.style_class = 'lock-screen-status-button-box';
|
||||||
|
|
||||||
this._volume = Main.panel.statusArea.volume;
|
this._volumeControl = VolumeMenu.getMixerControl();
|
||||||
if (this._volume) {
|
this._volumeMenu = new VolumeMenu.VolumeMenu(this._volumeControl);
|
||||||
this._volumeIcon = this.addIcon(null);
|
this.menu.addMenuItem(this._volumeMenu);
|
||||||
this._volume.mainIcon.bind_property('gicon', this._volumeIcon, 'gicon',
|
|
||||||
GObject.BindingFlags.SYNC_CREATE);
|
|
||||||
this._volume.mainIcon.bind_property('visible', this._volumeIcon, 'visible',
|
|
||||||
GObject.BindingFlags.SYNC_CREATE);
|
|
||||||
|
|
||||||
this._volumeControl = VolumeMenu.getMixerControl();
|
this._volume = new FakeStatusIcon(Main.panel.statusArea.volume);
|
||||||
this._volumeMenu = new VolumeMenu.VolumeMenu(this._volumeControl);
|
this._box.add_child(this._volume.actor);
|
||||||
this.menu.addMenuItem(this._volumeMenu);
|
|
||||||
|
// Network may not exist if the user doesn't have NetworkManager
|
||||||
|
if (Main.panel.statusArea.network) {
|
||||||
|
this._network = new FakeStatusIcon(Main.panel.statusArea.network);
|
||||||
|
this._box.add_child(this._network.actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._network = Main.panel.statusArea.network;
|
this._battery = new FakeStatusIcon(Main.panel.statusArea.battery);
|
||||||
if (this._network) {
|
this._box.add_child(this._battery.actor);
|
||||||
this._networkIcon = this.addIcon(null);
|
|
||||||
this._network.mainIcon.bind_property('gicon', this._networkIcon, 'gicon',
|
|
||||||
GObject.BindingFlags.SYNC_CREATE);
|
|
||||||
this._network.mainIcon.bind_property('visible', this._networkIcon, 'visible',
|
|
||||||
GObject.BindingFlags.SYNC_CREATE);
|
|
||||||
|
|
||||||
this._networkSecondaryIcon = this.addIcon(null);
|
|
||||||
this._network.secondaryIcon.bind_property('gicon', this._networkSecondaryIcon, 'gicon',
|
|
||||||
GObject.BindingFlags.SYNC_CREATE);
|
|
||||||
this._network.secondaryIcon.bind_property('visible', this._networkSecondaryIcon, 'visible',
|
|
||||||
GObject.BindingFlags.SYNC_CREATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._battery = Main.panel.statusArea.battery;
|
|
||||||
if (this._battery) {
|
|
||||||
this._batteryIcon = this.addIcon(null);
|
|
||||||
this._battery.mainIcon.bind_property('gicon', this._batteryIcon, 'gicon',
|
|
||||||
GObject.BindingFlags.SYNC_CREATE);
|
|
||||||
this._battery.mainIcon.bind_property('visible', this._batteryIcon, 'visible',
|
|
||||||
GObject.BindingFlags.SYNC_CREATE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -7,7 +7,7 @@ const St = imports.gi.St;
|
|||||||
const PanelMenu = imports.ui.panelMenu;
|
const PanelMenu = imports.ui.panelMenu;
|
||||||
const PopupMenu = imports.ui.popupMenu;
|
const PopupMenu = imports.ui.popupMenu;
|
||||||
|
|
||||||
const BUS_NAME = 'org.gnome.SettingsDaemon';
|
const BUS_NAME = 'org.gnome.SettingsDaemon.Power';
|
||||||
const OBJECT_PATH = '/org/gnome/SettingsDaemon/Power';
|
const OBJECT_PATH = '/org/gnome/SettingsDaemon/Power';
|
||||||
|
|
||||||
const UPDeviceType = {
|
const UPDeviceType = {
|
||||||
@ -54,14 +54,23 @@ const Indicator = new Lang.Class({
|
|||||||
_init: function() {
|
_init: function() {
|
||||||
this.parent('battery-missing-symbolic', _("Battery"));
|
this.parent('battery-missing-symbolic', _("Battery"));
|
||||||
|
|
||||||
this._proxy = new PowerManagerProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH);
|
this._proxy = new PowerManagerProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH,
|
||||||
|
Lang.bind(this, function(proxy, error) {
|
||||||
|
if (error) {
|
||||||
|
log(error.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._proxy.connect('g-properties-changed',
|
||||||
|
Lang.bind(this, this._devicesChanged));
|
||||||
|
this._devicesChanged();
|
||||||
|
}));
|
||||||
|
|
||||||
this._deviceItems = [ ];
|
this._deviceItems = [ ];
|
||||||
this._hasPrimary = false;
|
this._hasPrimary = false;
|
||||||
this._primaryDeviceId = null;
|
this._primaryDeviceId = null;
|
||||||
|
|
||||||
this._batteryItem = new PopupMenu.PopupMenuItem('', { reactive: false });
|
this._batteryItem = new PopupMenu.PopupMenuItem('', { reactive: false });
|
||||||
this._primaryPercentage = new St.Label();
|
this._primaryPercentage = new St.Label({ style_class: 'popup-battery-percentage' });
|
||||||
this._batteryItem.addActor(this._primaryPercentage, { align: St.Align.END });
|
this._batteryItem.addActor(this._primaryPercentage, { align: St.Align.END });
|
||||||
this.menu.addMenuItem(this._batteryItem);
|
this.menu.addMenuItem(this._batteryItem);
|
||||||
|
|
||||||
@ -70,10 +79,6 @@ const Indicator = new Lang.Class({
|
|||||||
|
|
||||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||||
this.menu.addSettingsAction(_("Power Settings"), 'gnome-power-panel.desktop');
|
this.menu.addSettingsAction(_("Power Settings"), 'gnome-power-panel.desktop');
|
||||||
|
|
||||||
this._proxy.connect('g-properties-changed',
|
|
||||||
Lang.bind(this, this._devicesChanged));
|
|
||||||
this._devicesChanged();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_readPrimaryDevice: function() {
|
_readPrimaryDevice: function() {
|
||||||
@ -91,12 +96,12 @@ const Indicator = new Lang.Class({
|
|||||||
if (time == 0) {
|
if (time == 0) {
|
||||||
// 0 is reported when UPower does not have enough data
|
// 0 is reported when UPower does not have enough data
|
||||||
// to estimate battery life
|
// to estimate battery life
|
||||||
this._batteryItem.label.text = _("Estimating...");
|
this._batteryItem.label.text = _("Estimating…");
|
||||||
} else {
|
} else {
|
||||||
let minutes = time % 60;
|
let minutes = time % 60;
|
||||||
let hours = Math.floor(time / 60);
|
let hours = Math.floor(time / 60);
|
||||||
let timestring;
|
let timestring;
|
||||||
if (time > 60) {
|
if (time >= 60) {
|
||||||
if (minutes == 0) {
|
if (minutes == 0) {
|
||||||
timestring = ngettext("%d hour remaining", "%d hours remaining", hours).format(hours);
|
timestring = ngettext("%d hour remaining", "%d hours remaining", hours).format(hours);
|
||||||
} else {
|
} else {
|
||||||
@ -183,7 +188,8 @@ const DeviceItem = new Lang.Class({
|
|||||||
this._box.add_actor(this._label);
|
this._box.add_actor(this._label);
|
||||||
this.addActor(this._box);
|
this.addActor(this._box);
|
||||||
|
|
||||||
let percentLabel = new St.Label({ text: C_("percent of battery remaining", "%d%%").format(Math.round(percentage)) });
|
let percentLabel = new St.Label({ text: C_("percent of battery remaining", "%d%%").format(Math.round(percentage)),
|
||||||
|
style_class: 'popup-battery-percentage' });
|
||||||
this.addActor(percentLabel, { align: St.Align.END });
|
this.addActor(percentLabel, { align: St.Align.END });
|
||||||
//FIXME: ideally we would like to expose this._label and percentLabel
|
//FIXME: ideally we would like to expose this._label and percentLabel
|
||||||
this.actor.label_actor = percentLabel;
|
this.actor.label_actor = percentLabel;
|
||||||
@ -192,9 +198,9 @@ const DeviceItem = new Lang.Class({
|
|||||||
_deviceTypeToString: function(type) {
|
_deviceTypeToString: function(type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case UPDeviceType.AC_POWER:
|
case UPDeviceType.AC_POWER:
|
||||||
return _("AC adapter");
|
return _("AC Adapter");
|
||||||
case UPDeviceType.BATTERY:
|
case UPDeviceType.BATTERY:
|
||||||
return _("Laptop battery");
|
return _("Laptop Battery");
|
||||||
case UPDeviceType.UPS:
|
case UPDeviceType.UPS:
|
||||||
return _("UPS");
|
return _("UPS");
|
||||||
case UPDeviceType.MONITOR:
|
case UPDeviceType.MONITOR:
|
||||||
@ -206,9 +212,9 @@ const DeviceItem = new Lang.Class({
|
|||||||
case UPDeviceType.PDA:
|
case UPDeviceType.PDA:
|
||||||
return _("PDA");
|
return _("PDA");
|
||||||
case UPDeviceType.PHONE:
|
case UPDeviceType.PHONE:
|
||||||
return _("Cell phone");
|
return _("Cell Phone");
|
||||||
case UPDeviceType.MEDIA_PLAYER:
|
case UPDeviceType.MEDIA_PLAYER:
|
||||||
return _("Media player");
|
return _("Media Player");
|
||||||
case UPDeviceType.TABLET:
|
case UPDeviceType.TABLET:
|
||||||
return _("Tablet");
|
return _("Tablet");
|
||||||
case UPDeviceType.COMPUTER:
|
case UPDeviceType.COMPUTER:
|
||||||
|
@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
const Clutter = imports.gi.Clutter;
|
const Clutter = imports.gi.Clutter;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
|
const Gio = imports.gi.Gio;
|
||||||
const Gvc = imports.gi.Gvc;
|
const Gvc = imports.gi.Gvc;
|
||||||
const St = imports.gi.St;
|
const St = imports.gi.St;
|
||||||
|
const Signals = imports.signals;
|
||||||
|
|
||||||
const PanelMenu = imports.ui.panelMenu;
|
const PanelMenu = imports.ui.panelMenu;
|
||||||
const PopupMenu = imports.ui.popupMenu;
|
const PopupMenu = imports.ui.popupMenu;
|
||||||
@ -25,122 +27,189 @@ function getMixerControl() {
|
|||||||
return _mixerControl;
|
return _mixerControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VolumeMenu = new Lang.Class({
|
const StreamSlider = new Lang.Class({
|
||||||
Name: 'VolumeMenu',
|
Name: 'StreamSlider',
|
||||||
Extends: PopupMenu.PopupMenuSection,
|
|
||||||
|
|
||||||
_init: function(control) {
|
|
||||||
this.parent();
|
|
||||||
|
|
||||||
|
_init: function(control, title) {
|
||||||
this._control = control;
|
this._control = control;
|
||||||
this._control.connect('state-changed', Lang.bind(this, this._onControlStateChanged));
|
|
||||||
this._control.connect('default-sink-changed', Lang.bind(this, this._readOutput));
|
this.item = new PopupMenu.PopupMenuSection();
|
||||||
this._control.connect('default-source-changed', Lang.bind(this, this._readInput));
|
|
||||||
|
this._title = new PopupMenu.PopupMenuItem(title, { reactive: false });
|
||||||
|
this._slider = new PopupMenu.PopupSliderMenuItem(0);
|
||||||
|
this._slider.connect('value-changed', Lang.bind(this, this._sliderChanged));
|
||||||
|
this._slider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange));
|
||||||
|
|
||||||
|
this.item.addMenuItem(this._title);
|
||||||
|
this.item.addMenuItem(this._slider);
|
||||||
|
|
||||||
|
this._stream = null;
|
||||||
|
this._shouldShow = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
get stream() {
|
||||||
|
return this._stream;
|
||||||
|
},
|
||||||
|
|
||||||
|
set stream(stream) {
|
||||||
|
if (this._stream) {
|
||||||
|
this._disconnectStream(this._stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._stream = stream;
|
||||||
|
|
||||||
|
if (this._stream) {
|
||||||
|
this._connectStream(this._stream);
|
||||||
|
this._updateVolume();
|
||||||
|
} else {
|
||||||
|
this.emit('stream-updated');
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updateVisibility();
|
||||||
|
},
|
||||||
|
|
||||||
|
_disconnectStream: function(stream) {
|
||||||
|
stream.disconnect(this._mutedChangedId);
|
||||||
|
this._mutedChangedId = 0;
|
||||||
|
stream.disconnect(this._volumeChangedId);
|
||||||
|
this._volumeChangedId = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
_connectStream: function(stream) {
|
||||||
|
this._mutedChangedId = stream.connect('notify::is-muted', Lang.bind(this, this._updateVolume));
|
||||||
|
this._volumeChangedId = stream.connect('notify::volume', Lang.bind(this, this._updateVolume));
|
||||||
|
},
|
||||||
|
|
||||||
|
_shouldBeVisible: function() {
|
||||||
|
return this._stream != null;
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateVisibility: function() {
|
||||||
|
let visible = this._shouldBeVisible();
|
||||||
|
this._title.actor.visible = visible;
|
||||||
|
this._slider.actor.visible = visible;
|
||||||
|
},
|
||||||
|
|
||||||
|
scroll: function(event) {
|
||||||
|
this._slider.scroll(event);
|
||||||
|
},
|
||||||
|
|
||||||
|
setValue: function(value) {
|
||||||
|
// piggy-back off of sliderChanged
|
||||||
|
this._slider.setValue(value);
|
||||||
|
},
|
||||||
|
|
||||||
|
_sliderChanged: function(slider, value, property) {
|
||||||
|
if (!this._stream)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let volume = value * this._control.get_vol_max_norm();
|
||||||
|
let prevMuted = this._stream.is_muted;
|
||||||
|
if (volume < 1) {
|
||||||
|
this._stream.volume = 0;
|
||||||
|
if (!prevMuted)
|
||||||
|
this._stream.change_is_muted(true);
|
||||||
|
} else {
|
||||||
|
this._stream.volume = volume;
|
||||||
|
if (prevMuted)
|
||||||
|
this._stream.change_is_muted(false);
|
||||||
|
}
|
||||||
|
this._stream.push_volume();
|
||||||
|
},
|
||||||
|
|
||||||
|
_notifyVolumeChange: function() {
|
||||||
|
global.cancel_theme_sound(VOLUME_NOTIFY_ID);
|
||||||
|
global.play_theme_sound(VOLUME_NOTIFY_ID,
|
||||||
|
'audio-volume-change',
|
||||||
|
_("Volume changed"),
|
||||||
|
Clutter.get_current_event ());
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateVolume: function() {
|
||||||
|
let muted = this._stream.is_muted;
|
||||||
|
this._slider.setValue(muted ? 0 : (this._stream.volume / this._control.get_vol_max_norm()));
|
||||||
|
this.emit('stream-updated');
|
||||||
|
},
|
||||||
|
|
||||||
|
getIcon: function() {
|
||||||
|
if (!this._stream)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
let volume = this._stream.volume;
|
||||||
|
if (this._stream.is_muted || volume <= 0) {
|
||||||
|
return 'audio-volume-muted-symbolic';
|
||||||
|
} else {
|
||||||
|
let n = Math.floor(3 * volume / this._control.get_vol_max_norm()) + 1;
|
||||||
|
if (n < 2)
|
||||||
|
return 'audio-volume-low-symbolic';
|
||||||
|
if (n >= 3)
|
||||||
|
return 'audio-volume-high-symbolic';
|
||||||
|
return 'audio-volume-medium-symbolic';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Signals.addSignalMethods(StreamSlider.prototype);
|
||||||
|
|
||||||
|
const OutputStreamSlider = new Lang.Class({
|
||||||
|
Name: 'OutputStreamSlider',
|
||||||
|
Extends: StreamSlider,
|
||||||
|
|
||||||
|
_connectStream: function(stream) {
|
||||||
|
this.parent(stream);
|
||||||
|
this._portChangedId = stream.connect('notify::port', Lang.bind(this, this._portChanged));
|
||||||
|
this._portChanged();
|
||||||
|
},
|
||||||
|
|
||||||
|
_findHeadphones: function(sink) {
|
||||||
|
// This only works for external headphones (e.g. bluetooth)
|
||||||
|
if (sink.get_form_factor() == 'headset' ||
|
||||||
|
sink.get_form_factor() == 'headphone')
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// a bit hackish, but ALSA/PulseAudio have a number
|
||||||
|
// of different identifiers for headphones, and I could
|
||||||
|
// not find the complete list
|
||||||
|
if (sink.get_ports().length > 0)
|
||||||
|
return sink.get_port().port.indexOf('headphone') >= 0;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
_disconnectStream: function(stream) {
|
||||||
|
this.parent(stream);
|
||||||
|
stream.disconnect(this._portChangedId);
|
||||||
|
this._portChangedId = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
_portChanged: function() {
|
||||||
|
let hasHeadphones = this._findHeadphones(this._stream);
|
||||||
|
if (hasHeadphones != this._hasHeadphones) {
|
||||||
|
this._hasHeadphones = hasHeadphones;
|
||||||
|
this.emit('headphones-changed', this._hasHeadphones);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const InputStreamSlider = new Lang.Class({
|
||||||
|
Name: 'InputStreamSlider',
|
||||||
|
Extends: StreamSlider,
|
||||||
|
|
||||||
|
_init: function(control, title) {
|
||||||
|
this.parent(control, title);
|
||||||
this._control.connect('stream-added', Lang.bind(this, this._maybeShowInput));
|
this._control.connect('stream-added', Lang.bind(this, this._maybeShowInput));
|
||||||
this._control.connect('stream-removed', Lang.bind(this, this._maybeShowInput));
|
this._control.connect('stream-removed', Lang.bind(this, this._maybeShowInput));
|
||||||
this._volumeMax = this._control.get_vol_max_norm();
|
|
||||||
|
|
||||||
this._output = null;
|
|
||||||
this._outputVolumeId = 0;
|
|
||||||
this._outputMutedId = 0;
|
|
||||||
/* Translators: This is the label for audio volume */
|
|
||||||
this._outputTitle = new PopupMenu.PopupMenuItem(_("Volume"), { reactive: false });
|
|
||||||
this._outputSlider = new PopupMenu.PopupSliderMenuItem(0);
|
|
||||||
this._outputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_output'));
|
|
||||||
this._outputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange));
|
|
||||||
this.addMenuItem(this._outputTitle);
|
|
||||||
this.addMenuItem(this._outputSlider);
|
|
||||||
|
|
||||||
this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
|
||||||
|
|
||||||
this._input = null;
|
|
||||||
this._inputVolumeId = 0;
|
|
||||||
this._inputMutedId = 0;
|
|
||||||
this._inputTitle = new PopupMenu.PopupMenuItem(_("Microphone"), { reactive: false });
|
|
||||||
this._inputSlider = new PopupMenu.PopupSliderMenuItem(0);
|
|
||||||
this._inputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_input'));
|
|
||||||
this._inputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange));
|
|
||||||
this.addMenuItem(this._inputTitle);
|
|
||||||
this.addMenuItem(this._inputSlider);
|
|
||||||
|
|
||||||
this._onControlStateChanged();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
scroll: function(direction) {
|
_connectStream: function(stream) {
|
||||||
let currentVolume = this._output.volume;
|
this.parent(stream);
|
||||||
|
this._maybeShowInput();
|
||||||
if (direction == Clutter.ScrollDirection.DOWN) {
|
|
||||||
let prev_muted = this._output.is_muted;
|
|
||||||
this._output.volume = Math.max(0, currentVolume - this._volumeMax * VOLUME_ADJUSTMENT_STEP);
|
|
||||||
if (this._output.volume < 1) {
|
|
||||||
this._output.volume = 0;
|
|
||||||
if (!prev_muted)
|
|
||||||
this._output.change_is_muted(true);
|
|
||||||
}
|
|
||||||
this._output.push_volume();
|
|
||||||
}
|
|
||||||
else if (direction == Clutter.ScrollDirection.UP) {
|
|
||||||
this._output.volume = Math.min(this._volumeMax, currentVolume + this._volumeMax * VOLUME_ADJUSTMENT_STEP);
|
|
||||||
this._output.change_is_muted(false);
|
|
||||||
this._output.push_volume();
|
|
||||||
}
|
|
||||||
|
|
||||||
this._notifyVolumeChange();
|
|
||||||
},
|
|
||||||
|
|
||||||
_onControlStateChanged: function() {
|
|
||||||
if (this._control.get_state() == Gvc.MixerControlState.READY) {
|
|
||||||
this._readOutput();
|
|
||||||
this._readInput();
|
|
||||||
this._maybeShowInput();
|
|
||||||
} else {
|
|
||||||
this.emit('icon-changed', null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_readOutput: function() {
|
|
||||||
if (this._outputVolumeId) {
|
|
||||||
this._output.disconnect(this._outputVolumeId);
|
|
||||||
this._output.disconnect(this._outputMutedId);
|
|
||||||
this._outputVolumeId = 0;
|
|
||||||
this._outputMutedId = 0;
|
|
||||||
}
|
|
||||||
this._output = this._control.get_default_sink();
|
|
||||||
if (this._output) {
|
|
||||||
this._outputMutedId = this._output.connect('notify::is-muted', Lang.bind(this, this._mutedChanged, '_output'));
|
|
||||||
this._outputVolumeId = this._output.connect('notify::volume', Lang.bind(this, this._volumeChanged, '_output'));
|
|
||||||
this._mutedChanged (null, null, '_output');
|
|
||||||
this._volumeChanged (null, null, '_output');
|
|
||||||
} else {
|
|
||||||
this._outputSlider.setValue(0);
|
|
||||||
this.emit('icon-changed', 'audio-volume-muted-symbolic');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_readInput: function() {
|
|
||||||
if (this._inputVolumeId) {
|
|
||||||
this._input.disconnect(this._inputVolumeId);
|
|
||||||
this._input.disconnect(this._inputMutedId);
|
|
||||||
this._inputVolumeId = 0;
|
|
||||||
this._inputMutedId = 0;
|
|
||||||
}
|
|
||||||
this._input = this._control.get_default_source();
|
|
||||||
if (this._input) {
|
|
||||||
this._inputMutedId = this._input.connect('notify::is-muted', Lang.bind(this, this._mutedChanged, '_input'));
|
|
||||||
this._inputVolumeId = this._input.connect('notify::volume', Lang.bind(this, this._volumeChanged, '_input'));
|
|
||||||
this._mutedChanged (null, null, '_input');
|
|
||||||
this._volumeChanged (null, null, '_input');
|
|
||||||
} else {
|
|
||||||
this._inputTitle.actor.hide();
|
|
||||||
this._inputSlider.actor.hide();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_maybeShowInput: function() {
|
_maybeShowInput: function() {
|
||||||
// only show input widgets if any application is recording audio
|
// only show input widgets if any application is recording audio
|
||||||
let showInput = false;
|
let showInput = false;
|
||||||
let recordingApps = this._control.get_source_outputs();
|
let recordingApps = this._control.get_source_outputs();
|
||||||
if (this._input && recordingApps) {
|
if (this._stream && recordingApps) {
|
||||||
for (let i = 0; i < recordingApps.length; i++) {
|
for (let i = 0; i < recordingApps.length; i++) {
|
||||||
let outputStream = recordingApps[i];
|
let outputStream = recordingApps[i];
|
||||||
let id = outputStream.get_application_id();
|
let id = outputStream.get_application_id();
|
||||||
@ -153,63 +222,70 @@ const VolumeMenu = new Lang.Class({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._inputTitle.actor.visible = showInput;
|
this._showInput = showInput;
|
||||||
this._inputSlider.actor.visible = showInput;
|
this._updateVisibility();
|
||||||
},
|
},
|
||||||
|
|
||||||
_volumeToIcon: function(volume) {
|
_shouldBeVisible: function() {
|
||||||
if (volume <= 0) {
|
return this.parent() && this._showInput;
|
||||||
return 'audio-volume-muted-symbolic';
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const VolumeMenu = new Lang.Class({
|
||||||
|
Name: 'VolumeMenu',
|
||||||
|
Extends: PopupMenu.PopupMenuSection,
|
||||||
|
|
||||||
|
_init: function(control) {
|
||||||
|
this.parent();
|
||||||
|
|
||||||
|
this.hasHeadphones = false;
|
||||||
|
|
||||||
|
this._control = control;
|
||||||
|
this._control.connect('state-changed', Lang.bind(this, this._onControlStateChanged));
|
||||||
|
this._control.connect('default-sink-changed', Lang.bind(this, this._readOutput));
|
||||||
|
this._control.connect('default-source-changed', Lang.bind(this, this._readInput));
|
||||||
|
|
||||||
|
/* Translators: This is the label for audio volume */
|
||||||
|
this._output = new OutputStreamSlider(this._control, _("Volume"));
|
||||||
|
this._output.connect('stream-updated', Lang.bind(this, function() {
|
||||||
|
this.emit('icon-changed');
|
||||||
|
}));
|
||||||
|
this._output.connect('headphones-changed', Lang.bind(this, function(stream, value) {
|
||||||
|
this.emit('headphones-changed', value);
|
||||||
|
}));
|
||||||
|
this.addMenuItem(this._output.item);
|
||||||
|
|
||||||
|
this._input = new InputStreamSlider(this._control, _("Microphone"));
|
||||||
|
this.addMenuItem(this._input.item);
|
||||||
|
|
||||||
|
this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||||
|
|
||||||
|
this._onControlStateChanged();
|
||||||
|
},
|
||||||
|
|
||||||
|
scroll: function(event) {
|
||||||
|
this._output.scroll(event);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onControlStateChanged: function() {
|
||||||
|
if (this._control.get_state() == Gvc.MixerControlState.READY) {
|
||||||
|
this._readInput();
|
||||||
|
this._readOutput();
|
||||||
} else {
|
} else {
|
||||||
let n = Math.floor(3 * volume / this._volumeMax) + 1;
|
this.emit('icon-changed');
|
||||||
if (n < 2)
|
|
||||||
return 'audio-volume-low-symbolic';
|
|
||||||
if (n >= 3)
|
|
||||||
return 'audio-volume-high-symbolic';
|
|
||||||
return 'audio-volume-medium-symbolic';
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_sliderChanged: function(slider, value, property) {
|
_readOutput: function() {
|
||||||
if (this[property] == null) {
|
this._output.stream = this._control.get_default_sink();
|
||||||
log ('Volume slider changed for %s, but %s does not exist'.format(property, property));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let volume = value * this._volumeMax;
|
|
||||||
let prev_muted = this[property].is_muted;
|
|
||||||
if (volume < 1) {
|
|
||||||
this[property].volume = 0;
|
|
||||||
if (!prev_muted)
|
|
||||||
this[property].change_is_muted(true);
|
|
||||||
} else {
|
|
||||||
this[property].volume = volume;
|
|
||||||
if (prev_muted)
|
|
||||||
this[property].change_is_muted(false);
|
|
||||||
}
|
|
||||||
this[property].push_volume();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_notifyVolumeChange: function() {
|
_readInput: function() {
|
||||||
global.cancel_theme_sound(VOLUME_NOTIFY_ID);
|
this._input.stream = this._control.get_default_source();
|
||||||
global.play_theme_sound(VOLUME_NOTIFY_ID, 'audio-volume-change');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_mutedChanged: function(object, param_spec, property) {
|
getIcon: function() {
|
||||||
let muted = this[property].is_muted;
|
return this._output.getIcon();
|
||||||
let slider = this[property+'Slider'];
|
|
||||||
slider.setValue(muted ? 0 : (this[property].volume / this._volumeMax));
|
|
||||||
if (property == '_output') {
|
|
||||||
if (muted)
|
|
||||||
this.emit('icon-changed', 'audio-volume-muted-symbolic');
|
|
||||||
else
|
|
||||||
this.emit('icon-changed', this._volumeToIcon(this._output.volume));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_volumeChanged: function(object, param_spec, property) {
|
|
||||||
this[property+'Slider'].setValue(this[property].volume / this._volumeMax);
|
|
||||||
if (property == '_output' && !this._output.is_muted)
|
|
||||||
this.emit('icon-changed', this._volumeToIcon(this._output.volume));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -222,11 +298,17 @@ const Indicator = new Lang.Class({
|
|||||||
|
|
||||||
this._control = getMixerControl();
|
this._control = getMixerControl();
|
||||||
this._volumeMenu = new VolumeMenu(this._control);
|
this._volumeMenu = new VolumeMenu(this._control);
|
||||||
this._volumeMenu.connect('icon-changed', Lang.bind(this, function(menu, icon) {
|
this._volumeMenu.connect('icon-changed', Lang.bind(this, function(menu) {
|
||||||
this._hasPulseAudio = (icon != null);
|
let icon = this._volumeMenu.getIcon();
|
||||||
|
this.actor.visible = (icon != null);
|
||||||
this.setIcon(icon);
|
this.setIcon(icon);
|
||||||
this._syncVisibility();
|
|
||||||
}));
|
}));
|
||||||
|
this._volumeMenu.connect('headphones-changed', Lang.bind(this, function(menu, value) {
|
||||||
|
this._headphoneIcon.visible = value;
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._headphoneIcon = this.addIcon(new Gio.ThemedIcon({ name: 'headphones-symbolic' }));
|
||||||
|
this._headphoneIcon.visible = false;
|
||||||
|
|
||||||
this.menu.addMenuItem(this._volumeMenu);
|
this.menu.addMenuItem(this._volumeMenu);
|
||||||
|
|
||||||
@ -236,12 +318,7 @@ const Indicator = new Lang.Class({
|
|||||||
this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
|
this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
|
||||||
},
|
},
|
||||||
|
|
||||||
_syncVisibility: function() {
|
|
||||||
this.actor.visible = this._hasPulseAudio;
|
|
||||||
this.mainIcon.visible = this._hasPulseAudio;
|
|
||||||
},
|
|
||||||
|
|
||||||
_onScrollEvent: function(actor, event) {
|
_onScrollEvent: function(actor, event) {
|
||||||
this._volumeMenu.scroll(event.get_scroll_direction());
|
this._volumeMenu.scroll(event);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
616
js/ui/switcherPopup.js
Normal file
616
js/ui/switcherPopup.js
Normal file
@ -0,0 +1,616 @@
|
|||||||
|
// -*- 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 Mainloop = imports.mainloop;
|
||||||
|
const Meta = imports.gi.Meta;
|
||||||
|
const Shell = imports.gi.Shell;
|
||||||
|
const Signals = imports.signals;
|
||||||
|
const St = imports.gi.St;
|
||||||
|
|
||||||
|
const Main = imports.ui.main;
|
||||||
|
const Tweener = imports.ui.tweener;
|
||||||
|
|
||||||
|
const POPUP_DELAY_TIMEOUT = 150; // milliseconds
|
||||||
|
|
||||||
|
const POPUP_SCROLL_TIME = 0.10; // seconds
|
||||||
|
const POPUP_FADE_OUT_TIME = 0.1; // seconds
|
||||||
|
|
||||||
|
const DISABLE_HOVER_TIMEOUT = 500; // milliseconds
|
||||||
|
|
||||||
|
function mod(a, b) {
|
||||||
|
return (a + b) % b;
|
||||||
|
}
|
||||||
|
|
||||||
|
function primaryModifier(mask) {
|
||||||
|
if (mask == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
let primary = 1;
|
||||||
|
while (mask > 1) {
|
||||||
|
mask >>= 1;
|
||||||
|
primary <<= 1;
|
||||||
|
}
|
||||||
|
return primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SwitcherPopup = new Lang.Class({
|
||||||
|
Name: 'SwitcherPopup',
|
||||||
|
Abstract: true,
|
||||||
|
|
||||||
|
_init: function(items) {
|
||||||
|
this._switcherList = null;
|
||||||
|
|
||||||
|
this._items = items || [];
|
||||||
|
this._selectedIndex = 0;
|
||||||
|
|
||||||
|
this.actor = new Shell.GenericContainer({ style_class: 'switcher-popup',
|
||||||
|
reactive: true,
|
||||||
|
visible: false });
|
||||||
|
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||||
|
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
||||||
|
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
||||||
|
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
||||||
|
|
||||||
|
Main.uiGroup.add_actor(this.actor);
|
||||||
|
|
||||||
|
this._haveModal = false;
|
||||||
|
this._modifierMask = 0;
|
||||||
|
|
||||||
|
this._motionTimeoutId = 0;
|
||||||
|
this._initialDelayTimeoutId = 0;
|
||||||
|
|
||||||
|
// Initially disable hover so we ignore the enter-event if
|
||||||
|
// the switcher appears underneath the current pointer location
|
||||||
|
this._disableHover();
|
||||||
|
},
|
||||||
|
|
||||||
|
_getPreferredWidth: function(actor, forHeight, alloc) {
|
||||||
|
let primary = Main.layoutManager.primaryMonitor;
|
||||||
|
|
||||||
|
alloc.min_size = primary.width;
|
||||||
|
alloc.natural_size = primary.width;
|
||||||
|
},
|
||||||
|
|
||||||
|
_getPreferredHeight: function(actor, forWidth, alloc) {
|
||||||
|
let primary = Main.layoutManager.primaryMonitor;
|
||||||
|
|
||||||
|
alloc.min_size = primary.height;
|
||||||
|
alloc.natural_size = primary.height;
|
||||||
|
},
|
||||||
|
|
||||||
|
_allocate: function(actor, box, flags) {
|
||||||
|
let childBox = new Clutter.ActorBox();
|
||||||
|
let primary = Main.layoutManager.primaryMonitor;
|
||||||
|
|
||||||
|
let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
|
||||||
|
let rightPadding = this.actor.get_theme_node().get_padding(St.Side.RIGHT);
|
||||||
|
let bottomPadding = this.actor.get_theme_node().get_padding(St.Side.BOTTOM);
|
||||||
|
let vPadding = this.actor.get_theme_node().get_vertical_padding();
|
||||||
|
let hPadding = leftPadding + rightPadding;
|
||||||
|
|
||||||
|
// Allocate the switcherList
|
||||||
|
// We select a size based on an icon size that does not overflow the screen
|
||||||
|
let [childMinHeight, childNaturalHeight] = this._switcherList.actor.get_preferred_height(primary.width - hPadding);
|
||||||
|
let [childMinWidth, childNaturalWidth] = this._switcherList.actor.get_preferred_width(childNaturalHeight);
|
||||||
|
childBox.x1 = Math.max(primary.x + leftPadding, primary.x + Math.floor((primary.width - childNaturalWidth) / 2));
|
||||||
|
childBox.x2 = Math.min(primary.x + primary.width - rightPadding, childBox.x1 + childNaturalWidth);
|
||||||
|
childBox.y1 = primary.y + Math.floor((primary.height - childNaturalHeight) / 2);
|
||||||
|
childBox.y2 = childBox.y1 + childNaturalHeight;
|
||||||
|
this._switcherList.actor.allocate(childBox, flags);
|
||||||
|
},
|
||||||
|
|
||||||
|
_createSwitcher: function() {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
_initialSelection: function(backward, binding) {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function(backward, binding, mask) {
|
||||||
|
if (!this._createSwitcher())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!Main.pushModal(this.actor)) {
|
||||||
|
// Probably someone else has a pointer grab, try again with keyboard only
|
||||||
|
if (!Main.pushModal(this.actor, { options: Meta.ModalOptions.POINTER_ALREADY_GRABBED })) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._haveModal = true;
|
||||||
|
this._modifierMask = primaryModifier(mask);
|
||||||
|
|
||||||
|
this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
|
||||||
|
this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
|
||||||
|
|
||||||
|
this.actor.connect('button-press-event', Lang.bind(this, this._clickedOutside));
|
||||||
|
this.actor.connect('scroll-event', Lang.bind(this, this._scrollEvent));
|
||||||
|
|
||||||
|
this.actor.add_actor(this._switcherList.actor);
|
||||||
|
this._switcherList.connect('item-activated', Lang.bind(this, this._itemActivated));
|
||||||
|
this._switcherList.connect('item-entered', Lang.bind(this, this._itemEntered));
|
||||||
|
|
||||||
|
// Need to force an allocation so we can figure out whether we
|
||||||
|
// need to scroll when selecting
|
||||||
|
this.actor.opacity = 0;
|
||||||
|
this.actor.show();
|
||||||
|
this.actor.get_allocation_box();
|
||||||
|
|
||||||
|
if (this._items.length > 1)
|
||||||
|
this._selectedIndex = 1;
|
||||||
|
else
|
||||||
|
this._selectedIndex = 0;
|
||||||
|
|
||||||
|
this._initialSelection(backward, binding);
|
||||||
|
|
||||||
|
// There's a race condition; if the user released Alt before
|
||||||
|
// we got the grab, then we won't be notified. (See
|
||||||
|
// https://bugzilla.gnome.org/show_bug.cgi?id=596695 for
|
||||||
|
// details.) So we check now. (Have to do this after updating
|
||||||
|
// selection.)
|
||||||
|
let [x, y, mods] = global.get_pointer();
|
||||||
|
if (!(mods & this._modifierMask)) {
|
||||||
|
this._finish(global.get_current_time());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We delay showing the popup so that fast Alt+Tab users aren't
|
||||||
|
// 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;
|
||||||
|
}));
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
_next: function() {
|
||||||
|
return mod(this._selectedIndex + 1, this._items.length);
|
||||||
|
},
|
||||||
|
|
||||||
|
_previous: function() {
|
||||||
|
return mod(this._selectedIndex - 1, this._items.length);
|
||||||
|
},
|
||||||
|
|
||||||
|
_keyPressHandler: function(keysym, backwards, action) {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
_keyPressEvent: function(actor, event) {
|
||||||
|
let keysym = event.get_key_symbol();
|
||||||
|
let event_state = event.get_state();
|
||||||
|
let backwards = event_state & Clutter.ModifierType.SHIFT_MASK;
|
||||||
|
let action = global.display.get_keybinding_action(event.get_key_code(), event_state);
|
||||||
|
|
||||||
|
this._disableHover();
|
||||||
|
|
||||||
|
if (keysym == Clutter.Escape)
|
||||||
|
this.destroy();
|
||||||
|
else
|
||||||
|
this._keyPressHandler(keysym, backwards, action);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
_keyReleaseEvent: function(actor, event) {
|
||||||
|
let [x, y, mods] = global.get_pointer();
|
||||||
|
let state = mods & this._modifierMask;
|
||||||
|
|
||||||
|
if (state == 0)
|
||||||
|
this._finish(event.get_time());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
_clickedOutside: function(actor, event) {
|
||||||
|
this.destroy();
|
||||||
|
},
|
||||||
|
|
||||||
|
_scrollHandler: function(direction) {
|
||||||
|
if (direction == Clutter.ScrollDirection.UP)
|
||||||
|
this._select(this._previous());
|
||||||
|
else if (direction == Clutter.ScrollDirection.DOWN)
|
||||||
|
this._select(this._next());
|
||||||
|
},
|
||||||
|
|
||||||
|
_scrollEvent: function(actor, event) {
|
||||||
|
this._scrollHandler(event.get_scroll_direction());
|
||||||
|
},
|
||||||
|
|
||||||
|
_itemActivatedHandler: function(n) {
|
||||||
|
this._select(n);
|
||||||
|
},
|
||||||
|
|
||||||
|
_itemActivated: function(switcher, n) {
|
||||||
|
this._itemActivatedHandler(n);
|
||||||
|
this._finish(global.get_current_time());
|
||||||
|
},
|
||||||
|
|
||||||
|
_itemEnteredHandler: function(n) {
|
||||||
|
this._select(n);
|
||||||
|
},
|
||||||
|
|
||||||
|
_itemEntered: function(switcher, n) {
|
||||||
|
if (!this.mouseActive)
|
||||||
|
return;
|
||||||
|
this._itemEnteredHandler(n);
|
||||||
|
},
|
||||||
|
|
||||||
|
_disableHover: function() {
|
||||||
|
this.mouseActive = false;
|
||||||
|
|
||||||
|
if (this._motionTimeoutId != 0)
|
||||||
|
Mainloop.source_remove(this._motionTimeoutId);
|
||||||
|
|
||||||
|
this._motionTimeoutId = Mainloop.timeout_add(DISABLE_HOVER_TIMEOUT, Lang.bind(this, this._mouseTimedOut));
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseTimedOut: function() {
|
||||||
|
this._motionTimeoutId = 0;
|
||||||
|
this.mouseActive = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
_popModal: function() {
|
||||||
|
if (this._haveModal) {
|
||||||
|
Main.popModal(this.actor);
|
||||||
|
this._haveModal = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
|
this._popModal();
|
||||||
|
if (this.actor.visible) {
|
||||||
|
Tweener.addTween(this.actor,
|
||||||
|
{ opacity: 0,
|
||||||
|
time: POPUP_FADE_OUT_TIME,
|
||||||
|
transition: 'easeOutQuad',
|
||||||
|
onComplete: Lang.bind(this,
|
||||||
|
function() {
|
||||||
|
this.actor.destroy();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} else
|
||||||
|
this.actor.destroy();
|
||||||
|
},
|
||||||
|
|
||||||
|
_finish: function(timestamp) {
|
||||||
|
this.destroy();
|
||||||
|
},
|
||||||
|
|
||||||
|
_onDestroy: function() {
|
||||||
|
this._popModal();
|
||||||
|
|
||||||
|
if (this._motionTimeoutId != 0)
|
||||||
|
Mainloop.source_remove(this._motionTimeoutId);
|
||||||
|
if (this._initialDelayTimeoutId != 0)
|
||||||
|
Mainloop.source_remove(this._initialDelayTimeoutId);
|
||||||
|
},
|
||||||
|
|
||||||
|
_select: function(num) {
|
||||||
|
this._selectedIndex = num;
|
||||||
|
this._switcherList.highlight(num);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const SwitcherList = new Lang.Class({
|
||||||
|
Name: 'SwitcherList',
|
||||||
|
|
||||||
|
_init : function(squareItems) {
|
||||||
|
this.actor = new Shell.GenericContainer({ style_class: 'switcher-list' });
|
||||||
|
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._allocateTop));
|
||||||
|
|
||||||
|
// Here we use a GenericContainer so that we can force all the
|
||||||
|
// 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() {
|
||||||
|
this._list.spacing = this._list.get_theme_node().get_length('spacing');
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._list.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||||
|
this._list.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
||||||
|
this._list.connect('allocate', Lang.bind(this, this._allocate));
|
||||||
|
|
||||||
|
this._scrollView = new St.ScrollView({ style_class: 'hfade',
|
||||||
|
enable_mouse_scrolling: false });
|
||||||
|
this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER);
|
||||||
|
|
||||||
|
let scrollBox = new St.BoxLayout();
|
||||||
|
scrollBox.add_actor(this._list);
|
||||||
|
this._scrollView.add_actor(scrollBox);
|
||||||
|
this.actor.add_actor(this._scrollView);
|
||||||
|
|
||||||
|
// Those arrows indicate whether scrolling in one direction is possible
|
||||||
|
this._leftArrow = new St.DrawingArea({ style_class: 'switcher-arrow',
|
||||||
|
pseudo_class: 'highlighted' });
|
||||||
|
this._leftArrow.connect('repaint', Lang.bind(this,
|
||||||
|
function() { drawArrow(this._leftArrow, St.Side.LEFT); }));
|
||||||
|
this._rightArrow = new St.DrawingArea({ style_class: 'switcher-arrow',
|
||||||
|
pseudo_class: 'highlighted' });
|
||||||
|
this._rightArrow.connect('repaint', Lang.bind(this,
|
||||||
|
function() { drawArrow(this._rightArrow, St.Side.RIGHT); }));
|
||||||
|
|
||||||
|
this.actor.add_actor(this._leftArrow);
|
||||||
|
this.actor.add_actor(this._rightArrow);
|
||||||
|
|
||||||
|
this._items = [];
|
||||||
|
this._highlighted = -1;
|
||||||
|
this._squareItems = squareItems;
|
||||||
|
this._minSize = 0;
|
||||||
|
this._scrollableRight = true;
|
||||||
|
this._scrollableLeft = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
_allocateTop: function(actor, box, flags) {
|
||||||
|
let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
|
||||||
|
let rightPadding = this.actor.get_theme_node().get_padding(St.Side.RIGHT);
|
||||||
|
|
||||||
|
let childBox = new Clutter.ActorBox();
|
||||||
|
let scrollable = this._minSize > box.x2 - box.x1;
|
||||||
|
|
||||||
|
box.y1 -= this.actor.get_theme_node().get_padding(St.Side.TOP);
|
||||||
|
box.y2 += this.actor.get_theme_node().get_padding(St.Side.BOTTOM);
|
||||||
|
this._scrollView.allocate(box, flags);
|
||||||
|
|
||||||
|
let arrowWidth = Math.floor(leftPadding / 3);
|
||||||
|
let arrowHeight = arrowWidth * 2;
|
||||||
|
childBox.x1 = leftPadding / 2;
|
||||||
|
childBox.y1 = this.actor.height / 2 - arrowWidth;
|
||||||
|
childBox.x2 = childBox.x1 + arrowWidth;
|
||||||
|
childBox.y2 = childBox.y1 + arrowHeight;
|
||||||
|
this._leftArrow.allocate(childBox, flags);
|
||||||
|
this._leftArrow.opacity = (this._scrollableLeft && scrollable) ? 255 : 0;
|
||||||
|
|
||||||
|
arrowWidth = Math.floor(rightPadding / 3);
|
||||||
|
arrowHeight = arrowWidth * 2;
|
||||||
|
childBox.x1 = this.actor.width - arrowWidth - rightPadding / 2;
|
||||||
|
childBox.y1 = this.actor.height / 2 - arrowWidth;
|
||||||
|
childBox.x2 = childBox.x1 + arrowWidth;
|
||||||
|
childBox.y2 = childBox.y1 + arrowHeight;
|
||||||
|
this._rightArrow.allocate(childBox, flags);
|
||||||
|
this._rightArrow.opacity = (this._scrollableRight && scrollable) ? 255 : 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
addItem : function(item, label) {
|
||||||
|
let bbox = new St.Button({ style_class: 'item-box',
|
||||||
|
reactive: true });
|
||||||
|
|
||||||
|
bbox.set_child(item);
|
||||||
|
this._list.add_actor(bbox);
|
||||||
|
|
||||||
|
let n = this._items.length;
|
||||||
|
bbox.connect('clicked', Lang.bind(this, function() { this._onItemClicked(n); }));
|
||||||
|
bbox.connect('enter-event', Lang.bind(this, function() { this._onItemEnter(n); }));
|
||||||
|
|
||||||
|
bbox.label_actor = label;
|
||||||
|
|
||||||
|
this._items.push(bbox);
|
||||||
|
|
||||||
|
return bbox;
|
||||||
|
},
|
||||||
|
|
||||||
|
_onItemClicked: function (index) {
|
||||||
|
this._itemActivated(index);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onItemEnter: function (index) {
|
||||||
|
this._itemEntered(index);
|
||||||
|
},
|
||||||
|
|
||||||
|
highlight: function(index, justOutline) {
|
||||||
|
if (this._highlighted != -1) {
|
||||||
|
this._items[this._highlighted].remove_style_pseudo_class('outlined');
|
||||||
|
this._items[this._highlighted].remove_style_pseudo_class('selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
this._highlighted = index;
|
||||||
|
|
||||||
|
if (this._highlighted != -1) {
|
||||||
|
if (justOutline)
|
||||||
|
this._items[this._highlighted].add_style_pseudo_class('outlined');
|
||||||
|
else
|
||||||
|
this._items[this._highlighted].add_style_pseudo_class('selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
let adjustment = this._scrollView.hscroll.adjustment;
|
||||||
|
let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
|
||||||
|
let [absItemX, absItemY] = this._items[index].get_transformed_position();
|
||||||
|
let [result, posX, posY] = this.actor.transform_stage_point(absItemX, 0);
|
||||||
|
let [containerWidth, containerHeight] = this.actor.get_transformed_size();
|
||||||
|
if (posX + this._items[index].get_width() > containerWidth)
|
||||||
|
this._scrollToRight();
|
||||||
|
else if (this._items[index].allocation.x1 - value < 0)
|
||||||
|
this._scrollToLeft();
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_scrollToLeft : function() {
|
||||||
|
let adjustment = this._scrollView.hscroll.adjustment;
|
||||||
|
let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
|
||||||
|
|
||||||
|
let item = this._items[this._highlighted];
|
||||||
|
|
||||||
|
if (item.allocation.x1 < value)
|
||||||
|
value = Math.min(0, item.allocation.x1);
|
||||||
|
else if (item.allocation.x2 > value + pageSize)
|
||||||
|
value = Math.max(upper, item.allocation.x2 - pageSize);
|
||||||
|
|
||||||
|
this._scrollableRight = true;
|
||||||
|
Tweener.addTween(adjustment,
|
||||||
|
{ value: value,
|
||||||
|
time: POPUP_SCROLL_TIME,
|
||||||
|
transition: 'easeOutQuad',
|
||||||
|
onComplete: Lang.bind(this, function () {
|
||||||
|
if (this._highlighted == 0) {
|
||||||
|
this._scrollableLeft = false;
|
||||||
|
this.actor.queue_relayout();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_scrollToRight : function() {
|
||||||
|
let adjustment = this._scrollView.hscroll.adjustment;
|
||||||
|
let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
|
||||||
|
|
||||||
|
let item = this._items[this._highlighted];
|
||||||
|
|
||||||
|
if (item.allocation.x1 < value)
|
||||||
|
value = Math.max(0, item.allocation.x1);
|
||||||
|
else if (item.allocation.x2 > value + pageSize)
|
||||||
|
value = Math.min(upper, item.allocation.x2 - pageSize);
|
||||||
|
|
||||||
|
this._scrollableLeft = true;
|
||||||
|
Tweener.addTween(adjustment,
|
||||||
|
{ value: value,
|
||||||
|
time: POPUP_SCROLL_TIME,
|
||||||
|
transition: 'easeOutQuad',
|
||||||
|
onComplete: Lang.bind(this, function () {
|
||||||
|
if (this._highlighted == this._items.length - 1) {
|
||||||
|
this._scrollableRight = false;
|
||||||
|
this.actor.queue_relayout();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_itemActivated: function(n) {
|
||||||
|
this.emit('item-activated', n);
|
||||||
|
},
|
||||||
|
|
||||||
|
_itemEntered: function(n) {
|
||||||
|
this.emit('item-entered', n);
|
||||||
|
},
|
||||||
|
|
||||||
|
_maxChildWidth: function (forHeight) {
|
||||||
|
let maxChildMin = 0;
|
||||||
|
let maxChildNat = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < this._items.length; i++) {
|
||||||
|
let [childMin, childNat] = this._items[i].get_preferred_width(forHeight);
|
||||||
|
maxChildMin = Math.max(childMin, maxChildMin);
|
||||||
|
maxChildNat = Math.max(childNat, maxChildNat);
|
||||||
|
|
||||||
|
if (this._squareItems) {
|
||||||
|
let [childMin, childNat] = this._items[i].get_preferred_height(-1);
|
||||||
|
maxChildMin = Math.max(childMin, maxChildMin);
|
||||||
|
maxChildNat = Math.max(childNat, maxChildNat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [maxChildMin, maxChildNat];
|
||||||
|
},
|
||||||
|
|
||||||
|
_getPreferredWidth: function (actor, forHeight, alloc) {
|
||||||
|
let [maxChildMin, maxChildNat] = this._maxChildWidth(forHeight);
|
||||||
|
|
||||||
|
let totalSpacing = this._list.spacing * (this._items.length - 1);
|
||||||
|
alloc.min_size = this._items.length * maxChildMin + totalSpacing;
|
||||||
|
alloc.natural_size = alloc.min_size;
|
||||||
|
this._minSize = alloc.min_size;
|
||||||
|
},
|
||||||
|
|
||||||
|
_getPreferredHeight: function (actor, forWidth, alloc) {
|
||||||
|
let maxChildMin = 0;
|
||||||
|
let maxChildNat = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < this._items.length; i++) {
|
||||||
|
let [childMin, childNat] = this._items[i].get_preferred_height(-1);
|
||||||
|
maxChildMin = Math.max(childMin, maxChildMin);
|
||||||
|
maxChildNat = Math.max(childNat, maxChildNat);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._squareItems) {
|
||||||
|
let [childMin, childNat] = this._maxChildWidth(-1);
|
||||||
|
maxChildMin = Math.max(childMin, maxChildMin);
|
||||||
|
maxChildNat = maxChildMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc.min_size = maxChildMin;
|
||||||
|
alloc.natural_size = maxChildNat;
|
||||||
|
},
|
||||||
|
|
||||||
|
_allocate: function (actor, box, flags) {
|
||||||
|
let childHeight = box.y2 - box.y1;
|
||||||
|
|
||||||
|
let [maxChildMin, maxChildNat] = this._maxChildWidth(childHeight);
|
||||||
|
let totalSpacing = this._list.spacing * (this._items.length - 1);
|
||||||
|
|
||||||
|
let childWidth = Math.floor(Math.max(0, box.x2 - box.x1 - totalSpacing) / this._items.length);
|
||||||
|
|
||||||
|
let x = 0;
|
||||||
|
let children = this._list.get_children();
|
||||||
|
let childBox = new Clutter.ActorBox();
|
||||||
|
|
||||||
|
let primary = Main.layoutManager.primaryMonitor;
|
||||||
|
let parentRightPadding = this.actor.get_parent().get_theme_node().get_padding(St.Side.RIGHT);
|
||||||
|
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
if (this._items.indexOf(children[i]) != -1) {
|
||||||
|
let [childMin, childNat] = children[i].get_preferred_height(childWidth);
|
||||||
|
let vSpacing = (childHeight - childNat) / 2;
|
||||||
|
childBox.x1 = x;
|
||||||
|
childBox.y1 = vSpacing;
|
||||||
|
childBox.x2 = x + childWidth;
|
||||||
|
childBox.y2 = childBox.y1 + childNat;
|
||||||
|
children[i].allocate(childBox, flags);
|
||||||
|
|
||||||
|
x += this._list.spacing + childWidth;
|
||||||
|
} else {
|
||||||
|
// Something else, eg, AppSwitcher's arrows;
|
||||||
|
// we don't allocate it.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Signals.addSignalMethods(SwitcherList.prototype);
|
||||||
|
|
||||||
|
function drawArrow(area, side) {
|
||||||
|
let themeNode = area.get_theme_node();
|
||||||
|
let borderColor = themeNode.get_border_color(side);
|
||||||
|
let bodyColor = themeNode.get_foreground_color();
|
||||||
|
|
||||||
|
let [width, height] = area.get_surface_size ();
|
||||||
|
let cr = area.get_context();
|
||||||
|
|
||||||
|
cr.setLineWidth(1.0);
|
||||||
|
Clutter.cairo_set_source_color(cr, borderColor);
|
||||||
|
|
||||||
|
switch (side) {
|
||||||
|
case St.Side.TOP:
|
||||||
|
cr.moveTo(0, height);
|
||||||
|
cr.lineTo(Math.floor(width * 0.5), 0);
|
||||||
|
cr.lineTo(width, height);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case St.Side.BOTTOM:
|
||||||
|
cr.moveTo(width, 0);
|
||||||
|
cr.lineTo(Math.floor(width * 0.5), height);
|
||||||
|
cr.lineTo(0, 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case St.Side.LEFT:
|
||||||
|
cr.moveTo(width, height);
|
||||||
|
cr.lineTo(0, Math.floor(height * 0.5));
|
||||||
|
cr.lineTo(width, 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case St.Side.RIGHT:
|
||||||
|
cr.moveTo(0, 0);
|
||||||
|
cr.lineTo(width, Math.floor(height * 0.5));
|
||||||
|
cr.lineTo(0, height);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cr.strokePreserve();
|
||||||
|
|
||||||
|
Clutter.cairo_set_source_color(cr, bodyColor);
|
||||||
|
cr.fill();
|
||||||
|
cr.$dispose();
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const Clutter = imports.gi.Clutter;
|
const Clutter = imports.gi.Clutter;
|
||||||
const GLib = imports.gi.GLib;
|
const GLib = imports.gi.GLib;
|
||||||
|
const Gtk = imports.gi.Gtk;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
const Mainloop = imports.mainloop;
|
const Mainloop = imports.mainloop;
|
||||||
const Shell = imports.gi.Shell;
|
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, 'onStart', _tweenStarted);
|
||||||
_addHandler(target, tweeningParameters, 'onComplete', _tweenCompleted);
|
_addHandler(target, tweeningParameters, 'onComplete', _tweenCompleted);
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ const Clutter = imports.gi.Clutter;
|
|||||||
const Gdm = imports.gi.Gdm;
|
const Gdm = imports.gi.Gdm;
|
||||||
const Gio = imports.gi.Gio;
|
const Gio = imports.gi.Gio;
|
||||||
const GLib = imports.gi.GLib;
|
const GLib = imports.gi.GLib;
|
||||||
|
const GnomeDesktop = imports.gi.GnomeDesktop;
|
||||||
const Gtk = imports.gi.Gtk;
|
const Gtk = imports.gi.Gtk;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
const Signals = imports.signals;
|
const Signals = imports.signals;
|
||||||
@ -13,98 +14,19 @@ const St = imports.gi.St;
|
|||||||
|
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
const ModalDialog = imports.ui.modalDialog;
|
const ModalDialog = imports.ui.modalDialog;
|
||||||
|
const Panel = imports.ui.panel;
|
||||||
const ShellEntry = imports.ui.shellEntry;
|
const ShellEntry = imports.ui.shellEntry;
|
||||||
const Tweener = imports.ui.tweener;
|
const Tweener = imports.ui.tweener;
|
||||||
const UserMenu = imports.ui.userMenu;
|
const UserMenu = imports.ui.userMenu;
|
||||||
|
const UserWidget = imports.ui.userWidget;
|
||||||
|
|
||||||
const Batch = imports.gdm.batch;
|
const Batch = imports.gdm.batch;
|
||||||
const GdmUtil = imports.gdm.util;
|
const GdmUtil = imports.gdm.util;
|
||||||
|
const LoginDialog = imports.gdm.loginDialog;
|
||||||
|
|
||||||
// The timeout before going back automatically to the lock screen (in seconds)
|
// The timeout before going back automatically to the lock screen (in seconds)
|
||||||
const IDLE_TIMEOUT = 2 * 60;
|
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({
|
const UnlockDialog = new Lang.Class({
|
||||||
Name: 'UnlockDialog',
|
Name: 'UnlockDialog',
|
||||||
Extends: ModalDialog.ModalDialog,
|
Extends: ModalDialog.ModalDialog,
|
||||||
@ -112,6 +34,7 @@ const UnlockDialog = new Lang.Class({
|
|||||||
_init: function(parentActor) {
|
_init: function(parentActor) {
|
||||||
this.parent({ shellReactive: true,
|
this.parent({ shellReactive: true,
|
||||||
styleClass: 'login-dialog',
|
styleClass: 'login-dialog',
|
||||||
|
keybindingMode: Shell.KeyBindingMode.UNLOCK_SCREEN,
|
||||||
parentActor: parentActor
|
parentActor: parentActor
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -124,6 +47,7 @@ const UnlockDialog = new Lang.Class({
|
|||||||
|
|
||||||
this._greeterClient = new Gdm.Client();
|
this._greeterClient = new Gdm.Client();
|
||||||
this._userVerifier = new GdmUtil.ShellUserVerifier(this._greeterClient, { reauthenticationOnly: true });
|
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('ask-question', Lang.bind(this, this._onAskQuestion));
|
||||||
this._userVerifier.connect('show-message', Lang.bind(this, this._showMessage));
|
this._userVerifier.connect('show-message', Lang.bind(this, this._showMessage));
|
||||||
@ -134,7 +58,7 @@ const UnlockDialog = new Lang.Class({
|
|||||||
this._userVerifier.connect('show-login-hint', Lang.bind(this, this._showLoginHint));
|
this._userVerifier.connect('show-login-hint', Lang.bind(this, this._showLoginHint));
|
||||||
this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._hideLoginHint));
|
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.contentLayout.add_actor(this._userWidget.actor);
|
||||||
|
|
||||||
this._promptLayout = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout',
|
this._promptLayout = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout',
|
||||||
@ -146,10 +70,13 @@ const UnlockDialog = new Lang.Class({
|
|||||||
|
|
||||||
this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry',
|
this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry',
|
||||||
can_focus: true });
|
can_focus: true });
|
||||||
|
this._promptEntry.clutter_text.connect('activate', Lang.bind(this, this._doUnlock));
|
||||||
this._promptEntry.clutter_text.set_password_char('\u25cf');
|
this._promptEntry.clutter_text.set_password_char('\u25cf');
|
||||||
ShellEntry.addContextMenu(this._promptEntry, { isPassword: true });
|
ShellEntry.addContextMenu(this._promptEntry, { isPassword: true });
|
||||||
this.setInitialKeyFocus(this._promptEntry);
|
this.setInitialKeyFocus(this._promptEntry);
|
||||||
this._promptEntry.clutter_text.connect('activate', Lang.bind(this, this._doUnlock));
|
this._promptEntry.clutter_text.connect('text-changed', Lang.bind(this, function() {
|
||||||
|
this._updateOkButtonSensitivity(this._promptEntry.text.length > 0);
|
||||||
|
}));
|
||||||
|
|
||||||
this._promptLayout.add(this._promptEntry,
|
this._promptLayout.add(this._promptEntry,
|
||||||
{ expand: true,
|
{ expand: true,
|
||||||
@ -164,54 +91,108 @@ const UnlockDialog = new Lang.Class({
|
|||||||
this._promptLoginHint.hide();
|
this._promptLoginHint.hide();
|
||||||
this.contentLayout.add_actor(this._promptLoginHint);
|
this.contentLayout.add_actor(this._promptLoginHint);
|
||||||
|
|
||||||
let cancelButton = { label: _("Cancel"),
|
this._workSpinner = new Panel.AnimatedIcon('process-working.svg', LoginDialog.WORK_SPINNER_ICON_SIZE);
|
||||||
action: Lang.bind(this, this._escape),
|
this._workSpinner.actor.opacity = 0;
|
||||||
key: Clutter.KEY_Escape };
|
|
||||||
this._okButton = { label: _("Unlock"),
|
this.allowCancel = false;
|
||||||
action: Lang.bind(this, this._doUnlock),
|
this.buttonLayout.visible = true;
|
||||||
default: true };
|
this.addButton({ label: _("Cancel"),
|
||||||
this.setButtons([cancelButton, this._okButton]);
|
action: Lang.bind(this, this._escape),
|
||||||
|
key: Clutter.KEY_Escape },
|
||||||
|
{ expand: true,
|
||||||
|
x_fill: false,
|
||||||
|
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._okButton = this.addButton({ label: _("Unlock"),
|
||||||
|
action: Lang.bind(this, this._doUnlock),
|
||||||
|
default: true },
|
||||||
|
{ expand: false,
|
||||||
|
x_fill: false,
|
||||||
|
y_fill: false,
|
||||||
|
x_align: St.Align.END,
|
||||||
|
y_align: St.Align.MIDDLE });
|
||||||
|
|
||||||
|
let screenSaverSettings = new Gio.Settings({ schema: 'org.gnome.desktop.screensaver' });
|
||||||
|
if (screenSaverSettings.get_boolean('user-switch-enabled')) {
|
||||||
|
let otherUserLabel = new St.Label({ text: _("Log in as another user"),
|
||||||
|
style_class: 'login-dialog-not-listed-label' });
|
||||||
|
this._otherUserButton = new St.Button({ style_class: 'login-dialog-not-listed-button',
|
||||||
|
can_focus: true,
|
||||||
|
child: otherUserLabel,
|
||||||
|
reactive: true,
|
||||||
|
x_align: St.Align.START,
|
||||||
|
x_fill: true });
|
||||||
|
this._otherUserButton.connect('clicked', Lang.bind(this, this._otherUserClicked));
|
||||||
|
this.dialogLayout.add(this._otherUserButton,
|
||||||
|
{ x_align: St.Align.START,
|
||||||
|
x_fill: false });
|
||||||
|
} else {
|
||||||
|
this._otherUserButton = null;
|
||||||
|
}
|
||||||
|
|
||||||
this._updateSensitivity(true);
|
this._updateSensitivity(true);
|
||||||
|
|
||||||
let otherUserLabel = new St.Label({ text: _("Log in as another user"),
|
|
||||||
style_class: 'login-dialog-not-listed-label' });
|
|
||||||
this._otherUserButton = new St.Button({ style_class: 'login-dialog-not-listed-button',
|
|
||||||
can_focus: true,
|
|
||||||
child: otherUserLabel,
|
|
||||||
reactive: true,
|
|
||||||
x_align: St.Align.START,
|
|
||||||
x_fill: true });
|
|
||||||
this._otherUserButton.connect('clicked', Lang.bind(this, this._otherUserClicked));
|
|
||||||
this.dialogLayout.add(this._otherUserButton,
|
|
||||||
{ x_align: St.Align.START,
|
|
||||||
x_fill: false });
|
|
||||||
|
|
||||||
let batch = new Batch.Hold();
|
let batch = new Batch.Hold();
|
||||||
this._userVerifier.begin(this._userName, batch);
|
this._userVerifier.begin(this._userName, batch);
|
||||||
|
|
||||||
GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, function() {
|
Main.ctrlAltTabManager.addGroup(this.dialogLayout, _("Unlock Window"), 'dialog-password-symbolic');
|
||||||
this.emit('loaded');
|
|
||||||
return false;
|
|
||||||
}));
|
|
||||||
|
|
||||||
this._idleMonitor = Shell.IdleMonitor.get();
|
this._idleMonitor = new GnomeDesktop.IdleMonitor();
|
||||||
// this dialog is only created after user activity (curtain drag or
|
this._idleWatchId = this._idleMonitor.add_idle_watch(IDLE_TIMEOUT * 1000, Lang.bind(this, this._escape));
|
||||||
// escape key press), so the timeout will fire after IDLE_TIMEOUT seconds of inactivity
|
|
||||||
this._idleWatchId = this._idleMonitor.add_watch(IDLE_TIMEOUT * 1000, Lang.bind(this, this._escape));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateSensitivity: function(sensitive) {
|
_updateSensitivity: function(sensitive) {
|
||||||
this._promptEntry.reactive = sensitive;
|
this._promptEntry.reactive = sensitive;
|
||||||
this._promptEntry.clutter_text.editable = sensitive;
|
this._promptEntry.clutter_text.editable = sensitive;
|
||||||
this._okButton.button.reactive = sensitive;
|
this._updateOkButtonSensitivity(sensitive && this._promptEntry.text.length > 0);
|
||||||
this._okButton.button.can_focus = sensitive;
|
if (this._otherUserButton) {
|
||||||
|
this._otherUserButton.reactive = sensitive;
|
||||||
|
this._otherUserButton.can_focus = sensitive;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateOkButtonSensitivity: function(sensitive) {
|
||||||
|
this._okButton.reactive = sensitive;
|
||||||
|
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) {
|
_showMessage: function(userVerifier, message, styleClass) {
|
||||||
this._promptMessage.text = message;
|
if (message) {
|
||||||
this._promptMessage.styleClass = styleClass;
|
this._promptMessage.text = message;
|
||||||
GdmUtil.fadeInActor(this._promptMessage);
|
this._promptMessage.styleClass = styleClass;
|
||||||
|
GdmUtil.fadeInActor(this._promptMessage);
|
||||||
|
} else {
|
||||||
|
GdmUtil.fadeOutActor(this._promptMessage);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_onAskQuestion: function(verifier, serviceName, question, passwordChar) {
|
_onAskQuestion: function(verifier, serviceName, question, passwordChar) {
|
||||||
@ -234,6 +215,7 @@ const UnlockDialog = new Lang.Class({
|
|||||||
|
|
||||||
this._currentQuery = serviceName;
|
this._currentQuery = serviceName;
|
||||||
this._updateSensitivity(true);
|
this._updateSensitivity(true);
|
||||||
|
this._setWorking(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
_showLoginHint: function(verifier, message) {
|
_showLoginHint: function(verifier, message) {
|
||||||
@ -252,6 +234,7 @@ const UnlockDialog = new Lang.Class({
|
|||||||
// the actual reply to GDM will be sent as soon as asked
|
// the actual reply to GDM will be sent as soon as asked
|
||||||
this._firstQuestionAnswer = this._promptEntry.text;
|
this._firstQuestionAnswer = this._promptEntry.text;
|
||||||
this._updateSensitivity(false);
|
this._updateSensitivity(false);
|
||||||
|
this._setWorking(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,32 +245,54 @@ const UnlockDialog = new Lang.Class({
|
|||||||
this._currentQuery = null;
|
this._currentQuery = null;
|
||||||
|
|
||||||
this._updateSensitivity(false);
|
this._updateSensitivity(false);
|
||||||
|
this._setWorking(true);
|
||||||
|
|
||||||
this._userVerifier.answerQuery(query, this._promptEntry.text);
|
this._userVerifier.answerQuery(query, this._promptEntry.text);
|
||||||
},
|
},
|
||||||
|
|
||||||
_onVerificationComplete: function() {
|
_finishUnlock: function() {
|
||||||
this._userVerifier.clear();
|
this._userVerifier.clear();
|
||||||
this.emit('unlocked');
|
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() {
|
_onReset: function() {
|
||||||
this.emit('failed');
|
if (!this._userVerified) {
|
||||||
|
this._userVerifier.clear();
|
||||||
|
this.emit('failed');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_onVerificationFailed: function() {
|
_onVerificationFailed: function() {
|
||||||
this._currentQuery = null;
|
this._currentQuery = null;
|
||||||
this._firstQuestion = true;
|
this._firstQuestion = true;
|
||||||
|
this._userVerified = false;
|
||||||
|
|
||||||
|
this._promptEntry.text = '';
|
||||||
this._promptEntry.clutter_text.set_password_char('\u25cf');
|
this._promptEntry.clutter_text.set_password_char('\u25cf');
|
||||||
this._promptEntry.menu.isPassword = true;
|
this._promptEntry.menu.isPassword = true;
|
||||||
|
|
||||||
this._updateSensitivity(false);
|
this._updateSensitivity(false);
|
||||||
|
this._setWorking(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
_escape: function() {
|
_escape: function() {
|
||||||
this._userVerifier.cancel();
|
if (this.allowCancel) {
|
||||||
this.emit('failed');
|
this._userVerifier.cancel();
|
||||||
|
this.emit('failed');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_otherUserClicked: function(button, event) {
|
_otherUserClicked: function(button, event) {
|
||||||
@ -313,4 +318,8 @@ const UnlockDialog = new Lang.Class({
|
|||||||
|
|
||||||
this.destroy();
|
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 Gdm = imports.gi.Gdm;
|
||||||
const Gio = imports.gi.Gio;
|
const Gio = imports.gi.Gio;
|
||||||
const GLib = imports.gi.GLib;
|
const GLib = imports.gi.GLib;
|
||||||
|
const Gtk = imports.gi.Gtk;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
const Pango = imports.gi.Pango;
|
const Pango = imports.gi.Pango;
|
||||||
const Shell = imports.gi.Shell;
|
const Shell = imports.gi.Shell;
|
||||||
const St = imports.gi.St;
|
const St = imports.gi.St;
|
||||||
const Tp = imports.gi.TelepathyGLib;
|
const Tp = imports.gi.TelepathyGLib;
|
||||||
const UPowerGlib = imports.gi.UPowerGlib;
|
|
||||||
const Atk = imports.gi.Atk;
|
const Atk = imports.gi.Atk;
|
||||||
|
const Clutter = imports.gi.Clutter;
|
||||||
|
|
||||||
const BoxPointer = imports.ui.boxpointer;
|
const BoxPointer = imports.ui.boxpointer;
|
||||||
const GnomeSession = imports.misc.gnomeSession;
|
const GnomeSession = imports.misc.gnomeSession;
|
||||||
|
const LoginManager = imports.misc.loginManager;
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
|
const ModalDialog = imports.ui.modalDialog;
|
||||||
const PanelMenu = imports.ui.panelMenu;
|
const PanelMenu = imports.ui.panelMenu;
|
||||||
const PopupMenu = imports.ui.popupMenu;
|
const PopupMenu = imports.ui.popupMenu;
|
||||||
const Params = imports.misc.params;
|
const Params = imports.misc.params;
|
||||||
@ -22,13 +25,17 @@ const Util = imports.misc.util;
|
|||||||
|
|
||||||
const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown';
|
const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown';
|
||||||
const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver';
|
const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver';
|
||||||
|
const PRIVACY_SCHEMA = 'org.gnome.desktop.privacy'
|
||||||
const DISABLE_USER_SWITCH_KEY = 'disable-user-switching';
|
const DISABLE_USER_SWITCH_KEY = 'disable-user-switching';
|
||||||
const DISABLE_LOCK_SCREEN_KEY = 'disable-lock-screen';
|
const DISABLE_LOCK_SCREEN_KEY = 'disable-lock-screen';
|
||||||
const DISABLE_LOG_OUT_KEY = 'disable-log-out';
|
const DISABLE_LOG_OUT_KEY = 'disable-log-out';
|
||||||
const LOCK_ENABLED_KEY = 'lock-enabled';
|
const ALWAYS_SHOW_LOG_OUT_KEY = 'always-show-log-out';
|
||||||
|
const SHOW_FULL_NAME_IN_TOP_BAR_KEY = 'show-full-name-in-top-bar';
|
||||||
|
|
||||||
const DIALOG_ICON_SIZE = 64;
|
const DIALOG_ICON_SIZE = 64;
|
||||||
|
|
||||||
|
const MAX_USERS_IN_SESSION_DIALOG = 5;
|
||||||
|
|
||||||
const IMStatus = {
|
const IMStatus = {
|
||||||
AVAILABLE: 0,
|
AVAILABLE: 0,
|
||||||
BUSY: 1,
|
BUSY: 1,
|
||||||
@ -39,6 +46,17 @@ const IMStatus = {
|
|||||||
LAST: 6
|
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
|
// Adapted from gdm/gui/user-switch-applet/applet.c
|
||||||
//
|
//
|
||||||
// Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
|
// Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
|
||||||
@ -66,18 +84,13 @@ const UserAvatarWidget = new Lang.Class({
|
|||||||
|
|
||||||
update: function() {
|
update: function() {
|
||||||
let iconFile = this._user.get_icon_file();
|
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;
|
iconFile = null;
|
||||||
|
|
||||||
if (iconFile) {
|
if (iconFile) {
|
||||||
let file = Gio.File.new_for_path(iconFile);
|
let file = Gio.File.new_for_path(iconFile);
|
||||||
this.actor.child = null;
|
this.actor.child = null;
|
||||||
this.actor.style = 'background-image: url("%s");'.format(iconFile);
|
this.actor.style = 'background-image: url("%s");'.format(iconFile);
|
||||||
|
|
||||||
// AccountsService uses a fixed location for avatar images, so
|
|
||||||
// we need to clear out all cached data to pick up image changes,
|
|
||||||
// see https://bugzilla.gnome.org/show_bug.cgi?id=679268
|
|
||||||
this.actor.clear_background_image();
|
|
||||||
} else {
|
} else {
|
||||||
this.actor.style = null;
|
this.actor.style = null;
|
||||||
this.actor.child = new St.Icon({ icon_name: 'avatar-default-symbolic',
|
this.actor.child = new St.Icon({ icon_name: 'avatar-default-symbolic',
|
||||||
@ -192,7 +205,7 @@ const IMStatusChooserItem = new Lang.Class({
|
|||||||
item = new IMStatusItem(_("Idle"), 'user-idle-symbolic');
|
item = new IMStatusItem(_("Idle"), 'user-idle-symbolic');
|
||||||
this._combo.addMenuItem(item, IMStatus.IDLE);
|
this._combo.addMenuItem(item, IMStatus.IDLE);
|
||||||
|
|
||||||
item = new IMStatusItem(_("Unavailable"), 'user-offline-symbolic');
|
item = new IMStatusItem(_("Offline"), 'user-offline-symbolic');
|
||||||
this._combo.addMenuItem(item, IMStatus.OFFLINE);
|
this._combo.addMenuItem(item, IMStatus.OFFLINE);
|
||||||
|
|
||||||
this._combo.connect('active-item-changed',
|
this._combo.connect('active-item-changed',
|
||||||
@ -480,6 +493,7 @@ const UserMenuButton = new Lang.Class({
|
|||||||
|
|
||||||
this._screenSaverSettings = new Gio.Settings({ schema: SCREENSAVER_SCHEMA });
|
this._screenSaverSettings = new Gio.Settings({ schema: SCREENSAVER_SCHEMA });
|
||||||
this._lockdownSettings = new Gio.Settings({ schema: LOCKDOWN_SCHEMA });
|
this._lockdownSettings = new Gio.Settings({ schema: LOCKDOWN_SCHEMA });
|
||||||
|
this._privacySettings = new Gio.Settings({ schema: PRIVACY_SCHEMA });
|
||||||
|
|
||||||
this._userManager = AccountsService.UserManager.get_default();
|
this._userManager = AccountsService.UserManager.get_default();
|
||||||
|
|
||||||
@ -487,10 +501,11 @@ const UserMenuButton = new Lang.Class({
|
|||||||
this._presence = new GnomeSession.Presence();
|
this._presence = new GnomeSession.Presence();
|
||||||
this._session = new GnomeSession.SessionManager();
|
this._session = new GnomeSession.SessionManager();
|
||||||
this._haveShutdown = true;
|
this._haveShutdown = true;
|
||||||
|
this._haveSuspend = true;
|
||||||
|
|
||||||
this._accountMgr = Tp.AccountManager.dup();
|
this._accountMgr = Tp.AccountManager.dup();
|
||||||
|
|
||||||
this._upClient = new UPowerGlib.Client();
|
this._loginManager = LoginManager.getLoginManager();
|
||||||
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
||||||
|
|
||||||
this._iconBox = new St.Bin();
|
this._iconBox = new St.Bin();
|
||||||
@ -553,9 +568,14 @@ const UserMenuButton = new Lang.Class({
|
|||||||
Lang.bind(this, this._updateSwitchUser));
|
Lang.bind(this, this._updateSwitchUser));
|
||||||
this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY,
|
this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY,
|
||||||
Lang.bind(this, this._updateLogout));
|
Lang.bind(this, this._updateLogout));
|
||||||
|
|
||||||
this._lockdownSettings.connect('changed::' + DISABLE_LOCK_SCREEN_KEY,
|
this._lockdownSettings.connect('changed::' + DISABLE_LOCK_SCREEN_KEY,
|
||||||
Lang.bind(this, this._updateLockScreen));
|
Lang.bind(this, this._updateLockScreen));
|
||||||
|
global.settings.connect('changed::' + ALWAYS_SHOW_LOG_OUT_KEY,
|
||||||
|
Lang.bind(this, this._updateLogout));
|
||||||
|
this._screenSaverSettings.connect('changed::' + SHOW_FULL_NAME_IN_TOP_BAR_KEY,
|
||||||
|
Lang.bind(this, this._updateUserName));
|
||||||
|
this._privacySettings.connect('changed::' + SHOW_FULL_NAME_IN_TOP_BAR_KEY,
|
||||||
|
Lang.bind(this, this._updateUserName));
|
||||||
this._updateSwitchUser();
|
this._updateSwitchUser();
|
||||||
this._updateLogout();
|
this._updateLogout();
|
||||||
this._updateLockScreen();
|
this._updateLockScreen();
|
||||||
@ -570,15 +590,18 @@ const UserMenuButton = new Lang.Class({
|
|||||||
// the lockdown setting changes, which should be close enough.
|
// the lockdown setting changes, which should be close enough.
|
||||||
this.menu.connect('open-state-changed', Lang.bind(this,
|
this.menu.connect('open-state-changed', Lang.bind(this,
|
||||||
function(menu, open) {
|
function(menu, open) {
|
||||||
if (open)
|
if (!open)
|
||||||
this._updateHaveShutdown();
|
return;
|
||||||
|
|
||||||
|
this._updateHaveShutdown();
|
||||||
|
this._updateHaveSuspend();
|
||||||
}));
|
}));
|
||||||
this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY,
|
this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY,
|
||||||
Lang.bind(this, this._updateHaveShutdown));
|
Lang.bind(this, this._updateHaveShutdown));
|
||||||
|
|
||||||
this._upClient.connect('notify::can-suspend', Lang.bind(this, this._updateSuspendOrPowerOff));
|
|
||||||
|
|
||||||
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
|
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
|
||||||
|
if (Main.screenShield)
|
||||||
|
Main.screenShield.connect('locked-changed', Lang.bind(this, this._updatePresenceIcon));
|
||||||
this._sessionUpdated();
|
this._sessionUpdated();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -591,6 +614,7 @@ const UserMenuButton = new Lang.Class({
|
|||||||
|
|
||||||
this.setSensitive(!Main.sessionMode.isLocked);
|
this.setSensitive(!Main.sessionMode.isLocked);
|
||||||
this._updatePresenceIcon();
|
this._updatePresenceIcon();
|
||||||
|
this._updateUserName();
|
||||||
},
|
},
|
||||||
|
|
||||||
_onDestroy: function() {
|
_onDestroy: function() {
|
||||||
@ -599,7 +623,10 @@ const UserMenuButton = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_updateUserName: function() {
|
_updateUserName: function() {
|
||||||
if (this._user.is_loaded)
|
let settings = this._privacySettings;
|
||||||
|
if (Main.sessionMode.isLocked)
|
||||||
|
settings = this._screenSaverSettings;
|
||||||
|
if (this._user.is_loaded && settings.get_boolean(SHOW_FULL_NAME_IN_TOP_BAR_KEY))
|
||||||
this._name.set_text(this._user.get_real_name());
|
this._name.set_text(this._user.get_real_name());
|
||||||
else
|
else
|
||||||
this._name.set_text("");
|
this._name.set_text("");
|
||||||
@ -619,15 +646,18 @@ const UserMenuButton = new Lang.Class({
|
|||||||
|
|
||||||
_updateLogout: function() {
|
_updateLogout: function() {
|
||||||
let allowLogout = !this._lockdownSettings.get_boolean(DISABLE_LOG_OUT_KEY);
|
let allowLogout = !this._lockdownSettings.get_boolean(DISABLE_LOG_OUT_KEY);
|
||||||
|
let alwaysShow = global.settings.get_boolean(ALWAYS_SHOW_LOG_OUT_KEY);
|
||||||
|
let systemAccount = this._user.system_account;
|
||||||
|
let localAccount = this._user.local_account;
|
||||||
let multiUser = this._userManager.has_multiple_users;
|
let multiUser = this._userManager.has_multiple_users;
|
||||||
let multiSession = Gdm.get_session_ids().length > 1;
|
let multiSession = Gdm.get_session_ids().length > 1;
|
||||||
|
|
||||||
this._logoutItem.actor.visible = allowLogout && (multiUser || multiSession);
|
this._logoutItem.actor.visible = allowLogout && (alwaysShow || multiUser || multiSession || systemAccount || !localAccount);
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateLockScreen: function() {
|
_updateLockScreen: function() {
|
||||||
let allowLockScreen = !this._lockdownSettings.get_boolean(DISABLE_LOCK_SCREEN_KEY);
|
let allowLockScreen = !this._lockdownSettings.get_boolean(DISABLE_LOCK_SCREEN_KEY);
|
||||||
this._lockScreenItem.actor.visible = allowLockScreen;
|
this._lockScreenItem.actor.visible = allowLockScreen && LoginManager.canLock();
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateInstallUpdates: function() {
|
_updateInstallUpdates: function() {
|
||||||
@ -646,9 +676,15 @@ const UserMenuButton = new Lang.Class({
|
|||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateSuspendOrPowerOff: function() {
|
_updateHaveSuspend: function() {
|
||||||
this._haveSuspend = this._upClient.get_can_suspend();
|
this._loginManager.canSuspend(Lang.bind(this,
|
||||||
|
function(result) {
|
||||||
|
this._haveSuspend = result;
|
||||||
|
this._updateSuspendOrPowerOff();
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateSuspendOrPowerOff: function() {
|
||||||
if (!this._suspendOrPowerOffItem)
|
if (!this._suspendOrPowerOffItem)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -685,6 +721,11 @@ const UserMenuButton = new Lang.Class({
|
|||||||
this._iconBox.child = this._idleIcon;
|
this._iconBox.child = this._idleIcon;
|
||||||
else
|
else
|
||||||
this._iconBox.child = this._offlineIcon;
|
this._iconBox.child = this._offlineIcon;
|
||||||
|
|
||||||
|
if (Main.sessionMode.isLocked)
|
||||||
|
this._iconBox.visible = Main.screenShield.locked;
|
||||||
|
else
|
||||||
|
this._iconBox.visible = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
_setupAccounts: function() {
|
_setupAccounts: function() {
|
||||||
@ -745,7 +786,7 @@ const UserMenuButton = new Lang.Class({
|
|||||||
item = new PopupMenu.PopupSeparatorMenuItem();
|
item = new PopupMenu.PopupSeparatorMenuItem();
|
||||||
this.menu.addMenuItem(item);
|
this.menu.addMenuItem(item);
|
||||||
|
|
||||||
item = new PopupMenu.PopupMenuItem(_("System Settings"));
|
item = new PopupMenu.PopupMenuItem(_("Settings"));
|
||||||
item.connect('activate', Lang.bind(this, this._onPreferencesActivate));
|
item.connect('activate', Lang.bind(this, this._onPreferencesActivate));
|
||||||
this.menu.addMenuItem(item);
|
this.menu.addMenuItem(item);
|
||||||
this._systemSettings = item;
|
this._systemSettings = item;
|
||||||
@ -805,7 +846,7 @@ const UserMenuButton = new Lang.Class({
|
|||||||
|
|
||||||
_onMyAccountActivate: function() {
|
_onMyAccountActivate: function() {
|
||||||
Main.overview.hide();
|
Main.overview.hide();
|
||||||
let app = Shell.AppSystem.get_default().lookup_setting('gnome-user-accounts-panel.desktop');
|
let app = Shell.AppSystem.get_default().lookup_app('gnome-user-accounts-panel.desktop');
|
||||||
app.activate();
|
app.activate();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -824,7 +865,8 @@ const UserMenuButton = new Lang.Class({
|
|||||||
_onLoginScreenActivate: function() {
|
_onLoginScreenActivate: function() {
|
||||||
this.menu.close(BoxPointer.PopupAnimation.NONE);
|
this.menu.close(BoxPointer.PopupAnimation.NONE);
|
||||||
Main.overview.hide();
|
Main.overview.hide();
|
||||||
Main.screenShield.lock(false);
|
if (Main.screenShield)
|
||||||
|
Main.screenShield.lock(false);
|
||||||
Gdm.goto_login_session_sync(null);
|
Gdm.goto_login_session_sync(null);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -840,24 +882,117 @@ const UserMenuButton = new Lang.Class({
|
|||||||
this._session.RebootRemote();
|
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() {
|
_onSuspendOrPowerOffActivate: function() {
|
||||||
Main.overview.hide();
|
Main.overview.hide();
|
||||||
|
|
||||||
if (this._haveShutdown &&
|
if (this._haveShutdown &&
|
||||||
this._suspendOrPowerOffItem.state == PopupMenu.PopupAlternatingMenuItemState.DEFAULT) {
|
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 {
|
} else {
|
||||||
if (this._screenSaverSettings.get_boolean(LOCK_ENABLED_KEY)) {
|
this.menu.close(BoxPointer.PopupAnimation.NONE);
|
||||||
let tmpId = Main.screenShield.connect('lock-screen-shown', Lang.bind(this, function() {
|
this._loginManager.suspend();
|
||||||
Main.screenShield.disconnect(tmpId);
|
|
||||||
|
|
||||||
this._upClient.suspend_sync(null);
|
|
||||||
}));
|
|
||||||
|
|
||||||
Main.screenShield.lock(true);
|
|
||||||
} else {
|
|
||||||
this._upClient.suspend_sync(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
61
js/ui/userWidget.js
Normal file
61
js/ui/userWidget.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
|
||||||
|
// -*- 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._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();
|
||||||
|
},
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
@ -1,6 +1,7 @@
|
|||||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
|
||||||
const Clutter = imports.gi.Clutter;
|
const Clutter = imports.gi.Clutter;
|
||||||
|
const Gio = imports.gi.Gio;
|
||||||
const Gtk = imports.gi.Gtk;
|
const Gtk = imports.gi.Gtk;
|
||||||
const Mainloop = imports.mainloop;
|
const Mainloop = imports.mainloop;
|
||||||
const Meta = imports.gi.Meta;
|
const Meta = imports.gi.Meta;
|
||||||
@ -11,6 +12,8 @@ const St = imports.gi.St;
|
|||||||
|
|
||||||
const AppDisplay = imports.ui.appDisplay;
|
const AppDisplay = imports.ui.appDisplay;
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
|
const OverviewControls = imports.ui.overviewControls;
|
||||||
|
const Params = imports.misc.params;
|
||||||
const RemoteSearch = imports.ui.remoteSearch;
|
const RemoteSearch = imports.ui.remoteSearch;
|
||||||
const Search = imports.ui.search;
|
const Search = imports.ui.search;
|
||||||
const SearchDisplay = imports.ui.searchDisplay;
|
const SearchDisplay = imports.ui.searchDisplay;
|
||||||
@ -19,6 +22,13 @@ const Tweener = imports.ui.tweener;
|
|||||||
const Wanda = imports.ui.wanda;
|
const Wanda = imports.ui.wanda;
|
||||||
const WorkspacesView = imports.ui.workspacesView;
|
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({
|
const FocusTrap = new Lang.Class({
|
||||||
Name: 'FocusTrap',
|
Name: 'FocusTrap',
|
||||||
@ -32,26 +42,28 @@ 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({
|
const ViewSelector = new Lang.Class({
|
||||||
Name: 'ViewSelector',
|
Name: 'ViewSelector',
|
||||||
|
|
||||||
_init : function(searchEntry, showAppsButton) {
|
_init : function(searchEntry, showAppsButton) {
|
||||||
this.actor = new St.BoxLayout({ name: 'viewSelector',
|
this.actor = new Shell.Stack({ name: 'viewSelector' });
|
||||||
vertical: true });
|
|
||||||
|
|
||||||
|
this._showAppsBlocked = false;
|
||||||
this._showAppsButton = showAppsButton;
|
this._showAppsButton = showAppsButton;
|
||||||
this._showAppsButton.connect('notify::checked', Lang.bind(this, this._onShowAppsButtonToggled));
|
this._showAppsButton.connect('notify::checked', Lang.bind(this, this._onShowAppsButtonToggled));
|
||||||
|
|
||||||
this._pageArea = new Shell.Stack();
|
|
||||||
this.actor.add(this._pageArea, { x_fill: true,
|
|
||||||
y_fill: true,
|
|
||||||
expand: true });
|
|
||||||
|
|
||||||
this._activePage = null;
|
this._activePage = null;
|
||||||
|
|
||||||
this.active = false;
|
this._searchActive = false;
|
||||||
this._searchPending = false;
|
|
||||||
this._searchTimeoutId = 0;
|
this._searchTimeoutId = 0;
|
||||||
|
|
||||||
this._searchSystem = new Search.SearchSystem();
|
this._searchSystem = new Search.SearchSystem();
|
||||||
@ -71,32 +83,36 @@ const ViewSelector = new Lang.Class({
|
|||||||
this._entry.connect('notify::mapped', Lang.bind(this, this._onMapped));
|
this._entry.connect('notify::mapped', Lang.bind(this, this._onMapped));
|
||||||
global.stage.connect('notify::key-focus', Lang.bind(this, this._onStageKeyFocusChanged));
|
global.stage.connect('notify::key-focus', Lang.bind(this, this._onStageKeyFocusChanged));
|
||||||
|
|
||||||
this._inactiveIcon = new St.Icon({ style_class: 'search-entry-icon',
|
this._entry.set_primary_icon(new St.Icon({ style_class: 'search-entry-icon',
|
||||||
icon_name: 'edit-find-symbolic' });
|
icon_name: 'edit-find-symbolic' }));
|
||||||
this._activeIcon = new St.Icon({ style_class: 'search-entry-icon',
|
this._clearIcon = new St.Icon({ style_class: 'search-entry-icon',
|
||||||
icon_name: 'edit-clear-symbolic' });
|
icon_name: 'edit-clear-symbolic' });
|
||||||
this._entry.set_secondary_icon(this._inactiveIcon);
|
|
||||||
|
|
||||||
this._iconClickedId = 0;
|
this._iconClickedId = 0;
|
||||||
this._capturedEventId = 0;
|
this._capturedEventId = 0;
|
||||||
|
|
||||||
this._workspacesDisplay = new WorkspacesView.WorkspacesDisplay();
|
this._workspacesDisplay = new WorkspacesView.WorkspacesDisplay();
|
||||||
this._workspacesPage = this._addPage(this._workspacesDisplay.actor, null,
|
this._workspacesPage = this._addPage(this._workspacesDisplay.actor,
|
||||||
_("Windows"), 'text-x-generic-symbolic');
|
_("Windows"), 'emblem-documents-symbolic');
|
||||||
|
|
||||||
this._appDisplay = new AppDisplay.AllAppDisplay();
|
this._appDisplay = new AppDisplay.AppDisplay();
|
||||||
this._appsPage = this._addPage(this._appDisplay.actor, null,
|
this._appsPage = this._addPage(this._appDisplay.actor,
|
||||||
_("Applications"), 'system-run-symbolic');
|
_("Applications"), 'view-grid-symbolic');
|
||||||
|
|
||||||
this._searchResults = new SearchDisplay.SearchResults(this._searchSystem);
|
this._searchResults = new SearchDisplay.SearchResults(this._searchSystem);
|
||||||
this._searchPage = this._addPage(this._searchResults.actor, this._entry,
|
this._searchPage = this._addPage(this._searchResults.actor,
|
||||||
_("Search"), 'edit-find-symbolic');
|
_("Search"), 'edit-find-symbolic',
|
||||||
|
{ a11yFocus: this._entry });
|
||||||
|
|
||||||
|
this._searchSettings = new Gio.Settings({ schema: Search.SEARCH_PROVIDERS_SCHEMA });
|
||||||
|
this._searchSettings.connect('changed::disabled', Lang.bind(this, this._reloadRemoteProviders));
|
||||||
|
this._searchSettings.connect('changed::disable-external', Lang.bind(this, this._reloadRemoteProviders));
|
||||||
|
this._searchSettings.connect('changed::sort-order', Lang.bind(this, this._reloadRemoteProviders));
|
||||||
|
|
||||||
// Default search providers
|
// Default search providers
|
||||||
// Wanda comes obviously first
|
// Wanda comes obviously first
|
||||||
this.addSearchProvider(new Wanda.WandaSearchProvider());
|
this.addSearchProvider(new Wanda.WandaSearchProvider());
|
||||||
this.addSearchProvider(new AppDisplay.AppSearchProvider());
|
this.addSearchProvider(new AppDisplay.AppSearchProvider());
|
||||||
this.addSearchProvider(new AppDisplay.SettingsSearchProvider());
|
|
||||||
|
|
||||||
// Load remote search providers provided by applications
|
// Load remote search providers provided by applications
|
||||||
RemoteSearch.loadRemoteSearchProviders(Lang.bind(this, this.addSearchProvider));
|
RemoteSearch.loadRemoteSearchProviders(Lang.bind(this, this.addSearchProvider));
|
||||||
@ -112,9 +128,6 @@ const ViewSelector = new Lang.Class({
|
|||||||
|
|
||||||
global.focus_manager.add_group(this._searchResults.actor);
|
global.focus_manager.add_group(this._searchResults.actor);
|
||||||
|
|
||||||
Main.overview.connect('item-drag-begin',
|
|
||||||
Lang.bind(this, this._resetShowAppsButton));
|
|
||||||
|
|
||||||
this._stageKeyPressId = 0;
|
this._stageKeyPressId = 0;
|
||||||
Main.overview.connect('showing', Lang.bind(this,
|
Main.overview.connect('showing', Lang.bind(this,
|
||||||
function () {
|
function () {
|
||||||
@ -131,21 +144,23 @@ const ViewSelector = new Lang.Class({
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Public constraints which may be used to tie actors' height or
|
Main.wm.addKeybinding('toggle-application-view',
|
||||||
// vertical position to the current tab's content; as the content's
|
new Gio.Settings({ schema: SHELL_KEYBINDINGS_SCHEMA }),
|
||||||
// height and position depend on the view selector's style properties
|
Meta.KeyBindingFlags.NONE,
|
||||||
// (e.g. font size, padding, spacing, ...) it would be extremely hard
|
Shell.KeyBindingMode.NORMAL |
|
||||||
// and ugly to get these from the outside. While it would be possible
|
Shell.KeyBindingMode.OVERVIEW,
|
||||||
// to use position and height properties directly, outside code would
|
Lang.bind(this, this._toggleAppsPage));
|
||||||
// need to ensure that the content is properly allocated before
|
},
|
||||||
// accessing the properties.
|
|
||||||
this.constrainHeight = new Clutter.BindConstraint({ source: this._pageArea,
|
_toggleAppsPage: function() {
|
||||||
coordinate: Clutter.BindCoordinate.HEIGHT });
|
Main.overview.show();
|
||||||
|
this._showAppsButton.checked = !this._showAppsButton.checked;
|
||||||
},
|
},
|
||||||
|
|
||||||
show: function() {
|
show: function() {
|
||||||
this._activePage = this._workspacesPage;
|
this._activePage = this._workspacesPage;
|
||||||
|
|
||||||
|
this.reset();
|
||||||
this._appsPage.hide();
|
this._appsPage.hide();
|
||||||
this._searchPage.hide();
|
this._searchPage.hide();
|
||||||
this._workspacesDisplay.show();
|
this._workspacesDisplay.show();
|
||||||
@ -153,7 +168,7 @@ const ViewSelector = new Lang.Class({
|
|||||||
if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows())
|
if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows())
|
||||||
Main.overview.fadeOutDesktop();
|
Main.overview.fadeOutDesktop();
|
||||||
|
|
||||||
this._showPage(this._workspacesPage);
|
this._showPage(this._workspacesPage, true);
|
||||||
},
|
},
|
||||||
|
|
||||||
zoomFromOverview: function() {
|
zoomFromOverview: function() {
|
||||||
@ -167,14 +182,16 @@ const ViewSelector = new Lang.Class({
|
|||||||
this._workspacesDisplay.hide();
|
this._workspacesDisplay.hide();
|
||||||
},
|
},
|
||||||
|
|
||||||
_addPage: function(actor, a11yFocus, name, a11yIcon) {
|
_addPage: function(actor, name, a11yIcon, params) {
|
||||||
|
params = Params.parse(params, { a11yFocus: null });
|
||||||
|
|
||||||
let page = new St.Bin({ child: actor,
|
let page = new St.Bin({ child: actor,
|
||||||
x_align: St.Align.START,
|
x_align: St.Align.START,
|
||||||
y_align: St.Align.START,
|
y_align: St.Align.START,
|
||||||
x_fill: true,
|
x_fill: true,
|
||||||
y_fill: true });
|
y_fill: true });
|
||||||
if (a11yFocus)
|
if (params.a11yFocus)
|
||||||
Main.ctrlAltTabManager.addGroup(a11yFocus, name, a11yIcon);
|
Main.ctrlAltTabManager.addGroup(params.a11yFocus, name, a11yIcon);
|
||||||
else
|
else
|
||||||
Main.ctrlAltTabManager.addGroup(actor, name, a11yIcon,
|
Main.ctrlAltTabManager.addGroup(actor, name, a11yIcon,
|
||||||
{ proxy: this.actor,
|
{ proxy: this.actor,
|
||||||
@ -183,33 +200,44 @@ const ViewSelector = new Lang.Class({
|
|||||||
this._a11yFocusPage(page);
|
this._a11yFocusPage(page);
|
||||||
})
|
})
|
||||||
});;
|
});;
|
||||||
this._pageArea.add_actor(page);
|
this.actor.add_actor(page);
|
||||||
return page
|
return page;
|
||||||
},
|
},
|
||||||
|
|
||||||
_showPage: function(page) {
|
_fadePageIn: function(oldPage) {
|
||||||
if(page == this._activePage)
|
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;
|
return;
|
||||||
|
|
||||||
if(this._activePage) {
|
let oldPage = this._activePage;
|
||||||
Tweener.addTween(this._activePage,
|
this._activePage = page;
|
||||||
|
this.emit('page-changed');
|
||||||
|
|
||||||
|
if (oldPage && !noFade)
|
||||||
|
Tweener.addTween(oldPage,
|
||||||
{ opacity: 0,
|
{ opacity: 0,
|
||||||
time: 0.1,
|
time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME,
|
||||||
transition: 'easeOutQuad',
|
transition: 'easeOutQuad',
|
||||||
onComplete: Lang.bind(this,
|
onComplete: Lang.bind(this,
|
||||||
function() {
|
function() {
|
||||||
this._activePage.hide();
|
this._fadePageIn(oldPage);
|
||||||
this._activePage = page;
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
else
|
||||||
|
this._fadePageIn(oldPage);
|
||||||
page.show();
|
|
||||||
Tweener.addTween(page,
|
|
||||||
{ opacity: 255,
|
|
||||||
time: 0.1,
|
|
||||||
transition: 'easeOutQuad'
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_a11yFocusPage: function(page) {
|
_a11yFocusPage: function(page) {
|
||||||
@ -218,33 +246,41 @@ const ViewSelector = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_onShowAppsButtonToggled: function() {
|
_onShowAppsButtonToggled: function() {
|
||||||
if (this.active)
|
if (this._showAppsBlocked)
|
||||||
this.reset();
|
return;
|
||||||
else
|
|
||||||
this._showPage(this._showAppsButton.checked ? this._appsPage
|
this._showPage(this._showAppsButton.checked ?
|
||||||
: this._workspacesPage);
|
this._appsPage : this._workspacesPage);
|
||||||
},
|
},
|
||||||
|
|
||||||
_resetShowAppsButton: function() {
|
_resetShowAppsButton: function() {
|
||||||
|
this._showAppsBlocked = true;
|
||||||
this._showAppsButton.checked = false;
|
this._showAppsButton.checked = false;
|
||||||
|
this._showAppsBlocked = false;
|
||||||
|
|
||||||
|
this._showPage(this._workspacesPage, true);
|
||||||
},
|
},
|
||||||
|
|
||||||
_onStageKeyPress: function(actor, event) {
|
_onStageKeyPress: function(actor, event) {
|
||||||
|
// Ignore events while anything but the overview has
|
||||||
|
// pushed a modal (system modals, looking glass, ...)
|
||||||
|
if (Main.modalCount > 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
let modifiers = event.get_state();
|
let modifiers = event.get_state();
|
||||||
let symbol = event.get_key_symbol();
|
let symbol = event.get_key_symbol();
|
||||||
|
|
||||||
if (symbol == Clutter.Escape) {
|
if (symbol == Clutter.Escape) {
|
||||||
if (this.active)
|
if (this._searchActive)
|
||||||
this.reset();
|
this.reset();
|
||||||
else if (this._showAppsButton.checked)
|
else if (this._showAppsButton.checked)
|
||||||
this._resetShowAppsButton();
|
this._showAppsButton.checked = false;
|
||||||
else
|
else
|
||||||
Main.overview.hide();
|
Main.overview.hide();
|
||||||
return true;
|
return true;
|
||||||
} else if (Clutter.keysym_to_unicode(symbol) ||
|
} else if (this._shouldTriggerSearch(symbol)) {
|
||||||
(symbol == Clutter.BackSpace && this.active)) {
|
|
||||||
this.startSearch(event);
|
this.startSearch(event);
|
||||||
} else if (!this.active) {
|
} else if (!this._searchActive) {
|
||||||
if (symbol == Clutter.Tab || symbol == Clutter.Down) {
|
if (symbol == Clutter.Tab || symbol == Clutter.Down) {
|
||||||
this._activePage.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
this._activePage.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||||
return true;
|
return true;
|
||||||
@ -307,6 +343,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) {
|
startSearch: function(event) {
|
||||||
global.stage.set_key_focus(this._text);
|
global.stage.set_key_focus(this._text);
|
||||||
this._text.event(event, true);
|
this._text.event(event, true);
|
||||||
@ -318,39 +365,39 @@ const ViewSelector = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_onTextChanged: function (se, prop) {
|
_onTextChanged: function (se, prop) {
|
||||||
let searchPreviouslyActive = this.active;
|
let terms = getTermsForSearchString(this._entry.get_text());
|
||||||
this.active = this._entry.get_text() != '';
|
|
||||||
this._searchPending = this.active && !searchPreviouslyActive;
|
let searchPreviouslyActive = this._searchActive;
|
||||||
if (this._searchPending) {
|
this._searchActive = (terms.length > 0);
|
||||||
|
|
||||||
|
let startSearch = this._searchActive && !searchPreviouslyActive;
|
||||||
|
if (startSearch)
|
||||||
this._searchResults.startingSearch();
|
this._searchResults.startingSearch();
|
||||||
}
|
|
||||||
if (this.active) {
|
|
||||||
this._entry.set_secondary_icon(this._activeIcon);
|
|
||||||
|
|
||||||
if (this._iconClickedId == 0) {
|
if (this._searchActive) {
|
||||||
|
this._entry.set_secondary_icon(this._clearIcon);
|
||||||
|
|
||||||
|
if (this._iconClickedId == 0)
|
||||||
this._iconClickedId = this._entry.connect('secondary-icon-clicked',
|
this._iconClickedId = this._entry.connect('secondary-icon-clicked',
|
||||||
Lang.bind(this, function() {
|
Lang.bind(this, this.reset));
|
||||||
this.reset();
|
|
||||||
}));
|
if (this._searchTimeoutId == 0)
|
||||||
}
|
this._searchTimeoutId = Mainloop.timeout_add(150,
|
||||||
} else {
|
Lang.bind(this, this._doSearch));
|
||||||
if (this._iconClickedId > 0)
|
} else {
|
||||||
this._entry.disconnect(this._iconClickedId);
|
if (this._iconClickedId > 0) {
|
||||||
this._iconClickedId = 0;
|
this._entry.disconnect(this._iconClickedId);
|
||||||
|
this._iconClickedId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
this._entry.set_secondary_icon(this._inactiveIcon);
|
|
||||||
this._searchCancelled();
|
|
||||||
}
|
|
||||||
if (!this.active) {
|
|
||||||
if (this._searchTimeoutId > 0) {
|
if (this._searchTimeoutId > 0) {
|
||||||
Mainloop.source_remove(this._searchTimeoutId);
|
Mainloop.source_remove(this._searchTimeoutId);
|
||||||
this._searchTimeoutId = 0;
|
this._searchTimeoutId = 0;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
|
this._entry.set_secondary_icon(null);
|
||||||
|
this._searchCancelled();
|
||||||
}
|
}
|
||||||
if (this._searchTimeoutId > 0)
|
|
||||||
return;
|
|
||||||
this._searchTimeoutId = Mainloop.timeout_add(150, Lang.bind(this, this._doSearch));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_onKeyPress: function(entry, event) {
|
_onKeyPress: function(entry, event) {
|
||||||
@ -360,16 +407,7 @@ const ViewSelector = new Lang.Class({
|
|||||||
this.reset();
|
this.reset();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (symbol == Clutter.Return || symbol == Clutter.KP_Enter) {
|
} else if (this._searchActive) {
|
||||||
// We can't connect to 'activate' here because search providers
|
|
||||||
// might want to do something with the modifiers in activateDefault.
|
|
||||||
if (this._searchTimeoutId > 0) {
|
|
||||||
Mainloop.source_remove(this._searchTimeoutId);
|
|
||||||
this._doSearch();
|
|
||||||
}
|
|
||||||
this._searchResults.activateDefault();
|
|
||||||
return true;
|
|
||||||
} else if (this.active) {
|
|
||||||
let arrowNext, nextDirection;
|
let arrowNext, nextDirection;
|
||||||
if (entry.get_text_direction() == Clutter.TextDirection.RTL) {
|
if (entry.get_text_direction() == Clutter.TextDirection.RTL) {
|
||||||
arrowNext = Clutter.Left;
|
arrowNext = Clutter.Left;
|
||||||
@ -393,6 +431,15 @@ const ViewSelector = new Lang.Class({
|
|||||||
} else if (symbol == arrowNext && this._text.position == -1) {
|
} else if (symbol == arrowNext && this._text.position == -1) {
|
||||||
this._searchResults.navigateFocus(nextDirection);
|
this._searchResults.navigateFocus(nextDirection);
|
||||||
return true;
|
return true;
|
||||||
|
} else if (symbol == Clutter.Return || symbol == Clutter.KP_Enter) {
|
||||||
|
// We can't connect to 'activate' here because search providers
|
||||||
|
// might want to do something with the modifiers in activateDefault.
|
||||||
|
if (this._searchTimeoutId > 0) {
|
||||||
|
Mainloop.source_remove(this._searchTimeoutId);
|
||||||
|
this._doSearch();
|
||||||
|
}
|
||||||
|
this._searchResults.activateDefault();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -415,13 +462,39 @@ const ViewSelector = new Lang.Class({
|
|||||||
|
|
||||||
_doSearch: function () {
|
_doSearch: function () {
|
||||||
this._searchTimeoutId = 0;
|
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);
|
this._showPage(this._searchPage);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_shouldUseSearchProvider: function(provider) {
|
||||||
|
// the disable-external GSetting only affects remote providers
|
||||||
|
if (!provider.isRemoteProvider)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (this._searchSettings.get_boolean('disable-external'))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let appId = provider.appInfo.get_id();
|
||||||
|
let disable = this._searchSettings.get_strv('disabled');
|
||||||
|
return disable.indexOf(appId) == -1;
|
||||||
|
},
|
||||||
|
|
||||||
|
_reloadRemoteProviders: function() {
|
||||||
|
// removeSearchProvider() modifies the provider list we iterate on,
|
||||||
|
// so make a copy first
|
||||||
|
let remoteProviders = this._searchSystem.getRemoteProviders().slice(0);
|
||||||
|
|
||||||
|
remoteProviders.forEach(Lang.bind(this, this.removeSearchProvider));
|
||||||
|
RemoteSearch.loadRemoteSearchProviders(Lang.bind(this, this.addSearchProvider));
|
||||||
|
},
|
||||||
|
|
||||||
addSearchProvider: function(provider) {
|
addSearchProvider: function(provider) {
|
||||||
|
if (!this._shouldUseSearchProvider(provider))
|
||||||
|
return;
|
||||||
|
|
||||||
this._searchSystem.registerProvider(provider);
|
this._searchSystem.registerProvider(provider);
|
||||||
this._searchResults.createProviderMeta(provider);
|
this._searchResults.createProviderMeta(provider);
|
||||||
},
|
},
|
||||||
@ -429,6 +502,31 @@ const ViewSelector = new Lang.Class({
|
|||||||
removeSearchProvider: function(provider) {
|
removeSearchProvider: function(provider) {
|
||||||
this._searchSystem.unregisterProvider(provider);
|
this._searchSystem.unregisterProvider(provider);
|
||||||
this._searchResults.destroyProviderMeta(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);
|
Signals.addSignalMethods(ViewSelector.prototype);
|
||||||
|
@ -9,14 +9,16 @@ const St = imports.gi.St;
|
|||||||
const IconGrid = imports.ui.iconGrid;
|
const IconGrid = imports.ui.iconGrid;
|
||||||
const Layout = imports.ui.layout;
|
const Layout = imports.ui.layout;
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
const Search = imports.ui.search;
|
const Panel = imports.ui.panel;
|
||||||
|
|
||||||
// we could make these gsettings
|
|
||||||
const FISH_NAME = 'wanda';
|
const FISH_NAME = 'wanda';
|
||||||
|
const FISH_FILENAME = 'wanda.png';
|
||||||
const FISH_SPEED = 300;
|
const FISH_SPEED = 300;
|
||||||
const FISH_COMMAND = 'fortune';
|
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 FISH_GROUP = 'Fish Animation';
|
||||||
|
|
||||||
const MAGIC_FISH_KEY = 'free the fish';
|
const MAGIC_FISH_KEY = 'free the fish';
|
||||||
@ -26,47 +28,19 @@ const WandaIcon = new Lang.Class({
|
|||||||
Extends: IconGrid.BaseIcon,
|
Extends: IconGrid.BaseIcon,
|
||||||
|
|
||||||
_init : function(fish, label, params) {
|
_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.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) {
|
createIcon: function(iconSize) {
|
||||||
if (!this._imageFile) {
|
this._animations = new Panel.Animation(this._imageFile, this._imgWidth, this._imgHeight, FISH_SPEED);
|
||||||
return new St.Icon({ icon_name: 'face-smile',
|
this._animations.play();
|
||||||
icon_size: iconSize });
|
return this._animations.actor;
|
||||||
}
|
|
||||||
|
|
||||||
this._animations = St.TextureCache.get_default().load_sliced_image(this._imageFile, this._imgWidth, this._imgHeight);
|
|
||||||
this._animations.connect('notify::mapped', Lang.bind(this, function() {
|
|
||||||
if (this._animations.mapped && !this._timeoutId) {
|
|
||||||
this._timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, FISH_SPEED, Lang.bind(this, this._update));
|
|
||||||
|
|
||||||
this._i = 0;
|
|
||||||
this._update();
|
|
||||||
} else if (!this._animations.mapped && this._timeoutId) {
|
|
||||||
GLib.source_remove(this._timeoutId);
|
|
||||||
this._timeoutId = 0;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
return this._animations;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_createIconTexture: function(size) {
|
_createIconTexture: function(size) {
|
||||||
@ -74,28 +48,14 @@ const WandaIcon = new Lang.Class({
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
this.parent(size);
|
this.parent(size);
|
||||||
},
|
}
|
||||||
|
|
||||||
_update: function() {
|
|
||||||
let n = this._animations.get_n_children();
|
|
||||||
if (n == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._animations.get_child_at_index(this._i).hide();
|
|
||||||
this._i = (this._i + 1) % n;
|
|
||||||
this._animations.get_child_at_index(this._i).show();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const WandaIconBin = new Lang.Class({
|
const WandaIconBin = new Lang.Class({
|
||||||
Name: 'WandaIconBin',
|
Name: 'WandaIconBin',
|
||||||
|
|
||||||
_init: function(fish, label, params) {
|
_init: function(fish, label, params) {
|
||||||
this.actor = new St.Bin({ style_class: 'search-result-content',
|
this.actor = new St.Bin({ reactive: true,
|
||||||
reactive: true,
|
|
||||||
track_hover: true });
|
track_hover: true });
|
||||||
this.icon = new WandaIcon(fish, label, params);
|
this.icon = new WandaIcon(fish, label, params);
|
||||||
|
|
||||||
@ -156,10 +116,9 @@ function capitalize(str) {
|
|||||||
|
|
||||||
const WandaSearchProvider = new Lang.Class({
|
const WandaSearchProvider = new Lang.Class({
|
||||||
Name: 'WandaSearchProvider',
|
Name: 'WandaSearchProvider',
|
||||||
Extends: Search.SearchProvider,
|
|
||||||
|
|
||||||
_init: function() {
|
_init: function() {
|
||||||
this.parent(_("Your favorite Easter Egg"));
|
this.id = 'wanda';
|
||||||
},
|
},
|
||||||
|
|
||||||
getResultMetas: function(fish, callback) {
|
getResultMetas: function(fish, callback) {
|
||||||
@ -185,7 +144,7 @@ const WandaSearchProvider = new Lang.Class({
|
|||||||
this.getInitialResultSet(terms);
|
this.getInitialResultSet(terms);
|
||||||
},
|
},
|
||||||
|
|
||||||
activateResult: function(fish, params) {
|
activateResult: function(fish) {
|
||||||
if (this._dialog)
|
if (this._dialog)
|
||||||
this._dialog.destroy();
|
this._dialog.destroy();
|
||||||
this._dialog = new FortuneDialog(capitalize(fish), FISH_COMMAND);
|
this._dialog = new FortuneDialog(capitalize(fish), FISH_COMMAND);
|
||||||
|
@ -39,6 +39,8 @@ const WindowAttentionHandler = new Lang.Class({
|
|||||||
let [title, banner] = this._getTitleAndBanner(app, window);
|
let [title, banner] = this._getTitleAndBanner(app, window);
|
||||||
|
|
||||||
let notification = new MessageTray.Notification(source, title, banner);
|
let notification = new MessageTray.Notification(source, title, banner);
|
||||||
|
notification.setForFeedback(true);
|
||||||
|
|
||||||
source.notify(notification);
|
source.notify(notification);
|
||||||
|
|
||||||
source.signalIDs.push(window.connect('notify::title', Lang.bind(this, function() {
|
source.signalIDs.push(window.connect('notify::title', Lang.bind(this, function() {
|
||||||
|
@ -83,6 +83,8 @@ const WindowManager = new Lang.Class({
|
|||||||
|
|
||||||
this._animationBlockCount = 0;
|
this._animationBlockCount = 0;
|
||||||
|
|
||||||
|
this._allowedKeybindings = {};
|
||||||
|
|
||||||
this._switchData = null;
|
this._switchData = null;
|
||||||
this._shellwm.connect('kill-switch-workspace', Lang.bind(this, this._switchWorkspaceDone));
|
this._shellwm.connect('kill-switch-workspace', Lang.bind(this, this._switchWorkspaceDone));
|
||||||
this._shellwm.connect('kill-window-effects', Lang.bind(this, function (shellwm, actor) {
|
this._shellwm.connect('kill-window-effects', Lang.bind(this, function (shellwm, actor) {
|
||||||
@ -99,38 +101,79 @@ const WindowManager = new Lang.Class({
|
|||||||
this._shellwm.connect('unmaximize', Lang.bind(this, this._unmaximizeWindow));
|
this._shellwm.connect('unmaximize', Lang.bind(this, this._unmaximizeWindow));
|
||||||
this._shellwm.connect('map', Lang.bind(this, this._mapWindow));
|
this._shellwm.connect('map', Lang.bind(this, this._mapWindow));
|
||||||
this._shellwm.connect('destroy', Lang.bind(this, this._destroyWindow));
|
this._shellwm.connect('destroy', Lang.bind(this, this._destroyWindow));
|
||||||
|
this._shellwm.connect('filter-keybinding', Lang.bind(this, this._filterKeybinding));
|
||||||
|
|
||||||
this._workspaceSwitcherPopup = null;
|
this._workspaceSwitcherPopup = null;
|
||||||
Meta.keybindings_set_custom_handler('switch-to-workspace-left',
|
this.setCustomKeybindingHandler('switch-to-workspace-left',
|
||||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
Shell.KeyBindingMode.NORMAL |
|
||||||
Meta.keybindings_set_custom_handler('switch-to-workspace-right',
|
Shell.KeyBindingMode.OVERVIEW,
|
||||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||||
Meta.keybindings_set_custom_handler('switch-to-workspace-up',
|
this.setCustomKeybindingHandler('switch-to-workspace-right',
|
||||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
Shell.KeyBindingMode.NORMAL |
|
||||||
Meta.keybindings_set_custom_handler('switch-to-workspace-down',
|
Shell.KeyBindingMode.OVERVIEW,
|
||||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||||
Meta.keybindings_set_custom_handler('move-to-workspace-left',
|
this.setCustomKeybindingHandler('switch-to-workspace-up',
|
||||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
Shell.KeyBindingMode.NORMAL |
|
||||||
Meta.keybindings_set_custom_handler('move-to-workspace-right',
|
Shell.KeyBindingMode.OVERVIEW,
|
||||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||||
Meta.keybindings_set_custom_handler('move-to-workspace-up',
|
this.setCustomKeybindingHandler('switch-to-workspace-down',
|
||||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
Shell.KeyBindingMode.NORMAL |
|
||||||
Meta.keybindings_set_custom_handler('move-to-workspace-down',
|
Shell.KeyBindingMode.OVERVIEW,
|
||||||
Lang.bind(this, this._showWorkspaceSwitcher));
|
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||||
Meta.keybindings_set_custom_handler('switch-windows',
|
this.setCustomKeybindingHandler('move-to-workspace-left',
|
||||||
Lang.bind(this, this._startAppSwitcher));
|
Shell.KeyBindingMode.NORMAL |
|
||||||
Meta.keybindings_set_custom_handler('switch-group',
|
Shell.KeyBindingMode.OVERVIEW,
|
||||||
Lang.bind(this, this._startAppSwitcher));
|
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||||
Meta.keybindings_set_custom_handler('switch-windows-backward',
|
this.setCustomKeybindingHandler('move-to-workspace-right',
|
||||||
Lang.bind(this, this._startAppSwitcher));
|
Shell.KeyBindingMode.NORMAL |
|
||||||
Meta.keybindings_set_custom_handler('switch-group-backward',
|
Shell.KeyBindingMode.OVERVIEW,
|
||||||
Lang.bind(this, this._startAppSwitcher));
|
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||||
Meta.keybindings_set_custom_handler('switch-panels',
|
this.setCustomKeybindingHandler('move-to-workspace-up',
|
||||||
Lang.bind(this, this._startA11ySwitcher));
|
Shell.KeyBindingMode.NORMAL |
|
||||||
global.display.add_keybinding('open-application-menu',
|
Shell.KeyBindingMode.OVERVIEW,
|
||||||
new Gio.Settings({ schema: SHELL_KEYBINDINGS_SCHEMA }),
|
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||||
Meta.KeyBindingFlags.NONE,
|
this.setCustomKeybindingHandler('move-to-workspace-down',
|
||||||
Lang.bind(this, this._openAppMenu));
|
Shell.KeyBindingMode.NORMAL |
|
||||||
|
Shell.KeyBindingMode.OVERVIEW,
|
||||||
|
Lang.bind(this, this._showWorkspaceSwitcher));
|
||||||
|
this.setCustomKeybindingHandler('switch-applications',
|
||||||
|
Shell.KeyBindingMode.NORMAL,
|
||||||
|
Lang.bind(this, this._startAppSwitcher));
|
||||||
|
this.setCustomKeybindingHandler('switch-group',
|
||||||
|
Shell.KeyBindingMode.NORMAL,
|
||||||
|
Lang.bind(this, this._startAppSwitcher));
|
||||||
|
this.setCustomKeybindingHandler('switch-applications-backward',
|
||||||
|
Shell.KeyBindingMode.NORMAL,
|
||||||
|
Lang.bind(this, this._startAppSwitcher));
|
||||||
|
this.setCustomKeybindingHandler('switch-group-backward',
|
||||||
|
Shell.KeyBindingMode.NORMAL,
|
||||||
|
Lang.bind(this, this._startAppSwitcher));
|
||||||
|
this.setCustomKeybindingHandler('switch-windows',
|
||||||
|
Shell.KeyBindingMode.NORMAL,
|
||||||
|
Lang.bind(this, this._startWindowSwitcher));
|
||||||
|
this.setCustomKeybindingHandler('switch-windows-backward',
|
||||||
|
Shell.KeyBindingMode.NORMAL,
|
||||||
|
Lang.bind(this, this._startWindowSwitcher));
|
||||||
|
this.setCustomKeybindingHandler('switch-panels',
|
||||||
|
Shell.KeyBindingMode.NORMAL |
|
||||||
|
Shell.KeyBindingMode.OVERVIEW |
|
||||||
|
Shell.KeyBindingMode.LOCK_SCREEN |
|
||||||
|
Shell.KeyBindingMode.UNLOCK_SCREEN |
|
||||||
|
Shell.KeyBindingMode.LOGIN_SCREEN,
|
||||||
|
Lang.bind(this, this._startA11ySwitcher));
|
||||||
|
this.setCustomKeybindingHandler('switch-panels-backward',
|
||||||
|
Shell.KeyBindingMode.NORMAL |
|
||||||
|
Shell.KeyBindingMode.OVERVIEW |
|
||||||
|
Shell.KeyBindingMode.LOCK_SCREEN |
|
||||||
|
Shell.KeyBindingMode.UNLOCK_SCREEN |
|
||||||
|
Shell.KeyBindingMode.LOGIN_SCREEN,
|
||||||
|
Lang.bind(this, this._startA11ySwitcher));
|
||||||
|
|
||||||
|
this.addKeybinding('open-application-menu',
|
||||||
|
new Gio.Settings({ schema: SHELL_KEYBINDINGS_SCHEMA }),
|
||||||
|
Meta.KeyBindingFlags.NONE,
|
||||||
|
Shell.KeyBindingMode.NORMAL,
|
||||||
|
Lang.bind(this, this._openAppMenu));
|
||||||
|
|
||||||
Main.overview.connect('showing', Lang.bind(this, function() {
|
Main.overview.connect('showing', Lang.bind(this, function() {
|
||||||
for (let i = 0; i < this._dimmedWindows.length; i++)
|
for (let i = 0; i < this._dimmedWindows.length; i++)
|
||||||
@ -142,6 +185,27 @@ const WindowManager = new Lang.Class({
|
|||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setCustomKeybindingHandler: function(name, modes, handler) {
|
||||||
|
if (Meta.keybindings_set_custom_handler(name, handler))
|
||||||
|
this.allowKeybinding(name, modes);
|
||||||
|
},
|
||||||
|
|
||||||
|
addKeybinding: function(name, settings, flags, modes, handler) {
|
||||||
|
let action = global.display.add_keybinding(name, settings, flags, handler);
|
||||||
|
if (action != Meta.KeyBindingAction.NONE)
|
||||||
|
this.allowKeybinding(name, modes);
|
||||||
|
return action;
|
||||||
|
},
|
||||||
|
|
||||||
|
removeKeybinding: function(name) {
|
||||||
|
if (global.display.remove_keybinding(name))
|
||||||
|
this.allowKeybinding(name, Shell.KeyBindingMode.NONE);
|
||||||
|
},
|
||||||
|
|
||||||
|
allowKeybinding: function(name, modes) {
|
||||||
|
this._allowedKeybindings[name] = modes;
|
||||||
|
},
|
||||||
|
|
||||||
blockAnimations: function() {
|
blockAnimations: function() {
|
||||||
this._animationBlockCount++;
|
this._animationBlockCount++;
|
||||||
},
|
},
|
||||||
@ -157,7 +221,9 @@ const WindowManager = new Lang.Class({
|
|||||||
_shouldAnimateActor: function(actor) {
|
_shouldAnimateActor: function(actor) {
|
||||||
if (!this._shouldAnimate())
|
if (!this._shouldAnimate())
|
||||||
return false;
|
return false;
|
||||||
return actor.meta_window.get_window_type() == Meta.WindowType.NORMAL;
|
let windowType = actor.meta_window.get_window_type();
|
||||||
|
return windowType == Meta.WindowType.NORMAL ||
|
||||||
|
windowType == Meta.WindowType.MODAL_DIALOG;
|
||||||
},
|
},
|
||||||
|
|
||||||
_removeEffect : function(list, actor) {
|
_removeEffect : function(list, actor) {
|
||||||
@ -176,23 +242,12 @@ const WindowManager = new Lang.Class({
|
|||||||
}
|
}
|
||||||
|
|
||||||
actor.set_scale(1.0, 1.0);
|
actor.set_scale(1.0, 1.0);
|
||||||
actor.move_anchor_point_from_gravity(Clutter.Gravity.CENTER);
|
|
||||||
|
|
||||||
/* scale window down to 0x0.
|
|
||||||
* maybe TODO: get icon geometry passed through and move the window towards it?
|
|
||||||
*/
|
|
||||||
this._minimizing.push(actor);
|
this._minimizing.push(actor);
|
||||||
|
|
||||||
let primary = Main.layoutManager.primaryMonitor;
|
if (actor.meta_window.is_monitor_sized()) {
|
||||||
let xDest = primary.x;
|
Tweener.addTween(actor,
|
||||||
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
|
{ opacity: 0,
|
||||||
xDest += primary.width;
|
|
||||||
|
|
||||||
Tweener.addTween(actor,
|
|
||||||
{ scale_x: 0.0,
|
|
||||||
scale_y: 0.0,
|
|
||||||
x: xDest,
|
|
||||||
y: 0,
|
|
||||||
time: WINDOW_ANIMATION_TIME,
|
time: WINDOW_ANIMATION_TIME,
|
||||||
transition: 'easeOutQuad',
|
transition: 'easeOutQuad',
|
||||||
onComplete: this._minimizeWindowDone,
|
onComplete: this._minimizeWindowDone,
|
||||||
@ -202,12 +257,46 @@ const WindowManager = new Lang.Class({
|
|||||||
onOverwriteScope: this,
|
onOverwriteScope: this,
|
||||||
onOverwriteParams: [shellwm, actor]
|
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) {
|
_minimizeWindowDone : function(shellwm, actor) {
|
||||||
if (this._removeEffect(this._minimizing, actor)) {
|
if (this._removeEffect(this._minimizing, actor)) {
|
||||||
Tweener.removeTweens(actor);
|
Tweener.removeTweens(actor);
|
||||||
actor.set_scale(1.0, 1.0);
|
actor.set_scale(1.0, 1.0);
|
||||||
|
actor.set_opacity(255);
|
||||||
actor.move_anchor_point_from_gravity(Clutter.Gravity.NORTH_WEST);
|
actor.move_anchor_point_from_gravity(Clutter.Gravity.NORTH_WEST);
|
||||||
|
|
||||||
shellwm.completed_minimize(actor);
|
shellwm.completed_minimize(actor);
|
||||||
@ -307,57 +396,56 @@ const WindowManager = new Lang.Class({
|
|||||||
|
|
||||||
actor._windowType = type;
|
actor._windowType = type;
|
||||||
}));
|
}));
|
||||||
if (actor.meta_window.is_attached_dialog()) {
|
|
||||||
this._checkDimming(actor.get_meta_window().get_transient_for());
|
|
||||||
if (this._shouldAnimate()) {
|
|
||||||
actor.set_scale(1.0, 0.0);
|
|
||||||
actor.scale_gravity = Clutter.Gravity.CENTER;
|
|
||||||
actor.show();
|
|
||||||
this._mapping.push(actor);
|
|
||||||
|
|
||||||
Tweener.addTween(actor,
|
|
||||||
{ scale_y: 1,
|
|
||||||
time: WINDOW_ANIMATION_TIME,
|
|
||||||
transition: "easeOutQuad",
|
|
||||||
onComplete: this._mapWindowDone,
|
|
||||||
onCompleteScope: this,
|
|
||||||
onCompleteParams: [shellwm, actor],
|
|
||||||
onOverwrite: this._mapWindowOverwrite,
|
|
||||||
onOverwriteScope: this,
|
|
||||||
onOverwriteParams: [shellwm, actor]
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
shellwm.completed_map(actor);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this._shouldAnimateActor(actor)) {
|
if (!this._shouldAnimateActor(actor)) {
|
||||||
shellwm.completed_map(actor);
|
shellwm.completed_map(actor);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
actor.opacity = 0;
|
if (actor.meta_window.is_attached_dialog()) {
|
||||||
actor.show();
|
/* Scale the window from the center of the parent */
|
||||||
|
this._checkDimming(actor.get_meta_window().get_transient_for());
|
||||||
|
actor.set_scale(1.0, 0.0);
|
||||||
|
actor.scale_gravity = Clutter.Gravity.CENTER;
|
||||||
|
actor.show();
|
||||||
|
this._mapping.push(actor);
|
||||||
|
|
||||||
/* Fade window in */
|
Tweener.addTween(actor,
|
||||||
this._mapping.push(actor);
|
{ scale_y: 1,
|
||||||
Tweener.addTween(actor,
|
time: WINDOW_ANIMATION_TIME,
|
||||||
{ opacity: 255,
|
transition: "easeOutQuad",
|
||||||
time: WINDOW_ANIMATION_TIME,
|
onComplete: this._mapWindowDone,
|
||||||
transition: 'easeOutQuad',
|
onCompleteScope: this,
|
||||||
onComplete: this._mapWindowDone,
|
onCompleteParams: [shellwm, actor],
|
||||||
onCompleteScope: this,
|
onOverwrite: this._mapWindowOverwrite,
|
||||||
onCompleteParams: [shellwm, actor],
|
onOverwriteScope: this,
|
||||||
onOverwrite: this._mapWindowOverwrite,
|
onOverwriteParams: [shellwm, actor]
|
||||||
onOverwriteScope: this,
|
});
|
||||||
onOverwriteParams: [shellwm, actor]
|
} else {
|
||||||
});
|
/* Fade window in */
|
||||||
|
actor.opacity = 0;
|
||||||
|
actor.show();
|
||||||
|
this._mapping.push(actor);
|
||||||
|
|
||||||
|
Tweener.addTween(actor,
|
||||||
|
{ opacity: 255,
|
||||||
|
time: WINDOW_ANIMATION_TIME,
|
||||||
|
transition: 'easeOutQuad',
|
||||||
|
onComplete: this._mapWindowDone,
|
||||||
|
onCompleteScope: this,
|
||||||
|
onCompleteParams: [shellwm, actor],
|
||||||
|
onOverwrite: this._mapWindowOverwrite,
|
||||||
|
onOverwriteScope: this,
|
||||||
|
onOverwriteParams: [shellwm, actor]
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_mapWindowDone : function(shellwm, actor) {
|
_mapWindowDone : function(shellwm, actor) {
|
||||||
if (this._removeEffect(this._mapping, actor)) {
|
if (this._removeEffect(this._mapping, actor)) {
|
||||||
Tweener.removeTweens(actor);
|
Tweener.removeTweens(actor);
|
||||||
actor.opacity = 255;
|
actor.opacity = 255;
|
||||||
|
actor.scale_y = 1;
|
||||||
shellwm.completed_map(actor);
|
shellwm.completed_map(actor);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -379,18 +467,21 @@ const WindowManager = new Lang.Class({
|
|||||||
return win != window;
|
return win != window;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this._shouldAnimateActor(actor)) {
|
||||||
|
shellwm.completed_destroy(actor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._destroying.push(actor);
|
||||||
|
|
||||||
if (window.is_attached_dialog()) {
|
if (window.is_attached_dialog()) {
|
||||||
let parent = window.get_transient_for();
|
let parent = window.get_transient_for();
|
||||||
this._checkDimming(parent, window);
|
this._checkDimming(parent, window);
|
||||||
if (!this._shouldAnimate()) {
|
|
||||||
shellwm.completed_destroy(actor);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
actor.set_scale(1.0, 1.0);
|
actor.set_scale(1.0, 1.0);
|
||||||
actor.scale_gravity = Clutter.Gravity.CENTER;
|
actor.scale_gravity = Clutter.Gravity.CENTER;
|
||||||
actor.show();
|
actor.show();
|
||||||
this._destroying.push(actor);
|
|
||||||
|
|
||||||
actor._parentDestroyId = parent.connect('unmanaged', Lang.bind(this, function () {
|
actor._parentDestroyId = parent.connect('unmanaged', Lang.bind(this, function () {
|
||||||
Tweener.removeTweens(actor);
|
Tweener.removeTweens(actor);
|
||||||
@ -424,6 +515,21 @@ const WindowManager = new Lang.Class({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_filterKeybinding: function(shellwm, binding) {
|
||||||
|
if (Main.keybindingMode == Shell.KeyBindingMode.NONE)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// There's little sense in implementing a keybinding in mutter and
|
||||||
|
// not having it work in NORMAL mode; handle this case generically
|
||||||
|
// so we don't have to explicitly allow all builtin keybindings in
|
||||||
|
// NORMAL mode.
|
||||||
|
if (Main.keybindingMode == Shell.KeyBindingMode.NORMAL &&
|
||||||
|
binding.is_builtin())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return !(this._allowedKeybindings[binding.get_name()] & Main.keybindingMode);
|
||||||
|
},
|
||||||
|
|
||||||
_switchWorkspace : function(shellwm, from, to, direction) {
|
_switchWorkspace : function(shellwm, from, to, direction) {
|
||||||
if (!this._shouldAnimate()) {
|
if (!this._shouldAnimate()) {
|
||||||
shellwm.completed_switch_workspace();
|
shellwm.completed_switch_workspace();
|
||||||
@ -441,11 +547,11 @@ const WindowManager = new Lang.Class({
|
|||||||
if (direction == Meta.MotionDirection.UP ||
|
if (direction == Meta.MotionDirection.UP ||
|
||||||
direction == Meta.MotionDirection.UP_LEFT ||
|
direction == Meta.MotionDirection.UP_LEFT ||
|
||||||
direction == Meta.MotionDirection.UP_RIGHT)
|
direction == Meta.MotionDirection.UP_RIGHT)
|
||||||
yDest = global.screen_height;
|
yDest = global.screen_height - Main.panel.actor.height;
|
||||||
else if (direction == Meta.MotionDirection.DOWN ||
|
else if (direction == Meta.MotionDirection.DOWN ||
|
||||||
direction == Meta.MotionDirection.DOWN_LEFT ||
|
direction == Meta.MotionDirection.DOWN_LEFT ||
|
||||||
direction == Meta.MotionDirection.DOWN_RIGHT)
|
direction == Meta.MotionDirection.DOWN_RIGHT)
|
||||||
yDest = -global.screen_height;
|
yDest = -global.screen_height + Main.panel.actor.height;
|
||||||
|
|
||||||
if (direction == Meta.MotionDirection.LEFT ||
|
if (direction == Meta.MotionDirection.LEFT ||
|
||||||
direction == Meta.MotionDirection.UP_LEFT ||
|
direction == Meta.MotionDirection.UP_LEFT ||
|
||||||
@ -458,9 +564,9 @@ const WindowManager = new Lang.Class({
|
|||||||
|
|
||||||
let switchData = {};
|
let switchData = {};
|
||||||
this._switchData = switchData;
|
this._switchData = switchData;
|
||||||
switchData.inGroup = new Clutter.Group();
|
switchData.inGroup = new Clutter.Actor();
|
||||||
switchData.outGroup = new Clutter.Group();
|
switchData.outGroup = new Clutter.Actor();
|
||||||
switchData.movingWindowBin = new Clutter.Group();
|
switchData.movingWindowBin = new Clutter.Actor();
|
||||||
switchData.windows = [];
|
switchData.windows = [];
|
||||||
|
|
||||||
let wgroup = global.window_group;
|
let wgroup = global.window_group;
|
||||||
@ -487,7 +593,7 @@ const WindowManager = new Lang.Class({
|
|||||||
switchData.windows.push({ window: window,
|
switchData.windows.push({ window: window,
|
||||||
parent: window.get_parent() });
|
parent: window.get_parent() });
|
||||||
window.reparent(switchData.inGroup);
|
window.reparent(switchData.inGroup);
|
||||||
window.show_all();
|
window.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -546,7 +652,20 @@ const WindowManager = new Lang.Class({
|
|||||||
if (this._workspaceSwitcherPopup != null)
|
if (this._workspaceSwitcherPopup != null)
|
||||||
this._workspaceSwitcherPopup.destroy();
|
this._workspaceSwitcherPopup.destroy();
|
||||||
|
|
||||||
let tabPopup = new AltTab.AltTabPopup();
|
let tabPopup = new AltTab.AppSwitcherPopup();
|
||||||
|
|
||||||
|
let modifiers = binding.get_modifiers();
|
||||||
|
let backwards = modifiers & Meta.VirtualModifier.SHIFT_MASK;
|
||||||
|
if (!tabPopup.show(backwards, binding.get_name(), binding.get_mask()))
|
||||||
|
tabPopup.destroy();
|
||||||
|
},
|
||||||
|
|
||||||
|
_startWindowSwitcher : function(display, screen, window, binding) {
|
||||||
|
/* prevent a corner case where both popups show up at once */
|
||||||
|
if (this._workspaceSwitcherPopup != null)
|
||||||
|
this._workspaceSwitcherPopup.destroy();
|
||||||
|
|
||||||
|
let tabPopup = new AltTab.WindowSwitcherPopup();
|
||||||
|
|
||||||
let modifiers = binding.get_modifiers();
|
let modifiers = binding.get_modifiers();
|
||||||
let backwards = modifiers & Meta.VirtualModifier.SHIFT_MASK;
|
let backwards = modifiers & Meta.VirtualModifier.SHIFT_MASK;
|
||||||
@ -557,7 +676,7 @@ const WindowManager = new Lang.Class({
|
|||||||
_startA11ySwitcher : function(display, screen, window, binding) {
|
_startA11ySwitcher : function(display, screen, window, binding) {
|
||||||
let modifiers = binding.get_modifiers();
|
let modifiers = binding.get_modifiers();
|
||||||
let backwards = modifiers & Meta.VirtualModifier.SHIFT_MASK;
|
let backwards = modifiers & Meta.VirtualModifier.SHIFT_MASK;
|
||||||
Main.ctrlAltTabManager.popup(backwards, binding.get_mask());
|
Main.ctrlAltTabManager.popup(backwards, binding.get_name(), binding.get_mask());
|
||||||
},
|
},
|
||||||
|
|
||||||
_openAppMenu : function(display, screen, window, event, binding) {
|
_openAppMenu : function(display, screen, window, event, binding) {
|
||||||
|
1166
js/ui/workspace.js
1166
js/ui/workspace.js
File diff suppressed because it is too large
Load Diff
@ -55,10 +55,9 @@ const WorkspaceSwitcherPopup = new Lang.Class({
|
|||||||
|
|
||||||
_getPreferredHeight : function (actor, forWidth, alloc) {
|
_getPreferredHeight : function (actor, forWidth, alloc) {
|
||||||
let children = this._list.get_children();
|
let children = this._list.get_children();
|
||||||
let primary = Main.layoutManager.primaryMonitor;
|
let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
|
||||||
|
|
||||||
let availHeight = primary.height;
|
let availHeight = workArea.height;
|
||||||
availHeight -= Main.panel.actor.height;
|
|
||||||
availHeight -= this.actor.get_theme_node().get_vertical_padding();
|
availHeight -= this.actor.get_theme_node().get_vertical_padding();
|
||||||
availHeight -= this._container.get_theme_node().get_vertical_padding();
|
availHeight -= this._container.get_theme_node().get_vertical_padding();
|
||||||
availHeight -= this._list.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++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
let [childMinHeight, childNaturalHeight] = children[i].get_preferred_height(-1);
|
let [childMinHeight, childNaturalHeight] = children[i].get_preferred_height(-1);
|
||||||
let [childMinWidth, childNaturalWidth] = children[i].get_preferred_width(childNaturalHeight);
|
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);
|
let spacing = this._itemSpacing * (global.screen.n_workspaces - 1);
|
||||||
@ -81,8 +80,8 @@ const WorkspaceSwitcherPopup = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_getPreferredWidth : function (actor, forHeight, alloc) {
|
_getPreferredWidth : function (actor, forHeight, alloc) {
|
||||||
let primary = Main.layoutManager.primaryMonitor;
|
let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
|
||||||
this._childWidth = Math.round(this._childHeight * primary.width / primary.height);
|
this._childWidth = Math.round(this._childHeight * workArea.width / workArea.height);
|
||||||
|
|
||||||
alloc.min_size = this._childWidth;
|
alloc.min_size = this._childWidth;
|
||||||
alloc.natural_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 [containerMinHeight, containerNatHeight] = this._container.get_preferred_height(global.screen_width);
|
||||||
let [containerMinWidth, containerNatWidth] = this._container.get_preferred_width(containerNatHeight);
|
let [containerMinWidth, containerNatWidth] = this._container.get_preferred_width(containerNatHeight);
|
||||||
this._container.x = primary.x + Math.floor((primary.width - containerNatWidth) / 2);
|
this._container.x = workArea.x + Math.floor((workArea.width - containerNatWidth) / 2);
|
||||||
this._container.y = primary.y + Main.panel.actor.height +
|
this._container.y = workArea.y + Math.floor((workArea.height - containerNatHeight) / 2);
|
||||||
Math.floor(((primary.height - Main.panel.actor.height) - containerNatHeight) / 2);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_show : function() {
|
_show : function() {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user