Compare commits
	
		
			689 Commits
		
	
	
		
			3.10.4
			...
			gnome-3-12
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 1e047af492 | ||
|   | 0546819e8f | ||
|   | 62a6519db6 | ||
|   | a63821a57f | ||
|   | b7d2191059 | ||
|   | 68f5674e62 | ||
|   | ffa8ff730e | ||
|   | 80f9d1ff5d | ||
|   | 547005b3a2 | ||
|   | 36f051044c | ||
|   | 0d75416d80 | ||
|   | c96f95ab90 | ||
|   | 5ccbd3a821 | ||
|   | c9bd1d79cc | ||
|   | 07e6219704 | ||
|   | 8625d5b0cc | ||
|   | e021ca2135 | ||
|   | ba6a2cae9c | ||
|   | 5852c17f76 | ||
|   | 4fe0353991 | ||
|   | 3c4a898a91 | ||
|   | 0006feec67 | ||
|   | dd2ace2e37 | ||
|   | 0327bd0a9d | ||
|   | 42619cdd3b | ||
|   | 76ea1f4dca | ||
|   | e4e3252ffc | ||
|   | e9ae77186d | ||
|   | a0c146bbfd | ||
|   | 6d679148b6 | ||
|   | 07198c5890 | ||
|   | 4720bc3412 | ||
|   | 7113821964 | ||
|   | c2ae98209d | ||
|   | 21f0e422c0 | ||
|   | eb0f2b8b34 | ||
|   | 65e781a973 | ||
|   | 4590903159 | ||
|   | 8b6fcbea50 | ||
|   | c6067ffa1d | ||
|   | 7b4254da4e | ||
|   | 2f576e3076 | ||
|   | 60d7c3ad86 | ||
|   | 43fdc15ac0 | ||
|   | f082a58071 | ||
|   | ae5dfb5740 | ||
|   | e8fdba4f66 | ||
|   | 1030cdfe8a | ||
|   | ca2d626b59 | ||
|   | 2b4eaf6b3f | ||
|   | 6ce6e77d2a | ||
|   | 0117fcb0e7 | ||
|   | 79bebe849d | ||
|   | 25eadc5559 | ||
|   | a5784484e0 | ||
|   | a701b006c5 | ||
|   | 2d68bbf94e | ||
|   | df305314c1 | ||
|   | d03239c009 | ||
|   | 71ccad4399 | ||
|   | c916d43688 | ||
|   | 3e6d0bc252 | ||
|   | 79d0a848a4 | ||
|   | a6af33d450 | ||
|   | c6664adcce | ||
|   | 4e8a9470d1 | ||
|   | 1b88df9439 | ||
|   | ec42278654 | ||
|   | f435f249d0 | ||
|   | 9c88fec4fc | ||
|   | 9ecf466ce1 | ||
|   | a22fdea0e3 | ||
|   | 90e52d7266 | ||
|   | 74d9b6c2bf | ||
|   | b1e9873de5 | ||
|   | 6fdc52a64a | ||
|   | b7d6792de9 | ||
|   | c78dc55e65 | ||
|   | af74bded14 | ||
|   | e3c9a9c3e4 | ||
|   | c68eecaf1c | ||
|   | b0bdf7f6c3 | ||
|   | d0f69a72dc | ||
|   | 8d8c75d32d | ||
|   | e339e2658d | ||
|   | bc4a75a732 | ||
|   | e09e1bc3f5 | ||
|   | 87c50eb495 | ||
|   | be291ee4f9 | ||
|   | e634b49859 | ||
|   | 4dddaefa41 | ||
|   | cda60455f0 | ||
|   | a42b8870b0 | ||
|   | daa66a6de6 | ||
|   | 6c6aed84bc | ||
|   | c59314acc1 | ||
|   | dd3cc78be5 | ||
|   | 54b0b6eec5 | ||
|   | 41654b22b3 | ||
|   | 07cc60d65a | ||
|   | a7f9dc5114 | ||
|   | b5ae23d544 | ||
|   | 75347cb4f7 | ||
|   | 03a44b6ec2 | ||
|   | 17d2349c49 | ||
|   | 4e98c44052 | ||
|   | 0bef281d66 | ||
|   | 54af25ec24 | ||
|   | 86ab02f400 | ||
|   | ae01cd143f | ||
|   | 2974b29f15 | ||
|   | 1b78dd662b | ||
|   | c22264a0ca | ||
|   | 48c3e3f534 | ||
|   | 6ccc134ba6 | ||
|   | 9aa36d7851 | ||
|   | 3278f77739 | ||
|   | 5d24f48e3b | ||
|   | 573c1c86cc | ||
|   | 374c5967ba | ||
|   | 66b71a36ce | ||
|   | e70fd5a57a | ||
|   | 4589ce4d78 | ||
|   | d6197b0904 | ||
|   | 30fb2b0d99 | ||
|   | 5cdefc324d | ||
|   | b222d0fe44 | ||
|   | fe4fddf0d5 | ||
|   | c675c93733 | ||
|   | 29485ff24b | ||
|   | f6ed3d9f88 | ||
|   | 39a36cb510 | ||
|   | ff5550c82b | ||
|   | 7d5ce1a159 | ||
|   | c492415386 | ||
|   | 5616bbd45b | ||
|   | e117aa5297 | ||
|   | 17ac1382df | ||
|   | 057a026ea4 | ||
|   | 6ce6e86318 | ||
|   | 492558a2d2 | ||
|   | b78e00f372 | ||
|   | c2cc504837 | ||
|   | ac76940530 | ||
|   | 55d1c7e2ab | ||
|   | fdf264ff64 | ||
|   | e917b7ce0f | ||
|   | ec6facb9e7 | ||
|   | 60f3c09f90 | ||
|   | afdfd6cebc | ||
|   | edd66c40d9 | ||
|   | fc4bc5277a | ||
|   | 821768a414 | ||
|   | 522f3bf171 | ||
|   | fb7400ab85 | ||
|   | 210128f22b | ||
|   | 78ae233823 | ||
|   | 8f25da7cea | ||
|   | 7f52fdb435 | ||
|   | 0ba05b29b9 | ||
|   | ef8123e3a2 | ||
|   | 257e1f3096 | ||
|   | 6441ae77d9 | ||
|   | 3f0938072f | ||
|   | 2fe06a28aa | ||
|   | 38750ba798 | ||
|   | b4c01f8905 | ||
|   | 104d70c88e | ||
|   | 133a350f2f | ||
|   | 496ab55357 | ||
|   | a391758e31 | ||
|   | eaf8ad4949 | ||
|   | 2f583bdcf3 | ||
|   | db19012a41 | ||
|   | 62be46884e | ||
|   | 3f2e6a48a9 | ||
|   | ff124e5f74 | ||
|   | c07421c195 | ||
|   | de8348d3b9 | ||
|   | 184df8a853 | ||
|   | 12768a147c | ||
|   | 94161cea37 | ||
|   | 52a4ef7cf7 | ||
|   | 3432f71500 | ||
|   | 8282aa6c24 | ||
|   | 59f9eaa1c9 | ||
|   | 4433b735c4 | ||
|   | 9cd30fa6b5 | ||
|   | 2c7bbfb500 | ||
|   | 51a1d23bf9 | ||
|   | c02e6e82bc | ||
|   | e37a3fa7e6 | ||
|   | e23c2ffecc | ||
|   | 744f11d045 | ||
|   | b7eb1f3e8b | ||
|   | 3f28091e52 | ||
|   | b4ee86955d | ||
|   | 8b866efe92 | ||
|   | fb61ab8df7 | ||
|   | 990956ece7 | ||
|   | 414b592d53 | ||
|   | 751154d9da | ||
|   | 29addc499c | ||
|   | caa98de581 | ||
|   | 1fd1ec4312 | ||
|   | f4607626e4 | ||
|   | b494c15e4b | ||
|   | 3c0defa125 | ||
|   | f2df4d95de | ||
|   | fabcf20e06 | ||
|   | b9510b9ab7 | ||
|   | 52a2ebad04 | ||
|   | 89a2dc71fc | ||
|   | adb0de43d8 | ||
|   | e2a811a720 | ||
|   | 5bcafc5c17 | ||
|   | 2d24536caf | ||
|   | b088c4086b | ||
|   | 3c58f4abd3 | ||
|   | e2a9b27b2b | ||
|   | fcd5f06c09 | ||
|   | 6d93c8b3fd | ||
|   | 2663e1be5d | ||
|   | 0fa6be4614 | ||
|   | 46163a6607 | ||
|   | 645ef093f7 | ||
|   | 7551e134da | ||
|   | 5bec5fb6cb | ||
|   | c176af4da5 | ||
|   | 2631f03108 | ||
|   | 525c71658b | ||
|   | 10e5778deb | ||
|   | 6512a5fd6b | ||
|   | 1af40b1345 | ||
|   | 0418b68051 | ||
|   | a7283864e8 | ||
|   | 4950bad2a7 | ||
|   | 470ac0eae3 | ||
|   | 87abbf9b20 | ||
|   | 3e7e88cd5f | ||
|   | b7e1539699 | ||
|   | 8492f2ba24 | ||
|   | 737f4eb1c1 | ||
|   | 58191ea66b | ||
|   | 1f786df462 | ||
|   | fa4c481aed | ||
|   | d555fd7883 | ||
|   | fe7ece1f5a | ||
|   | 2bb3aed729 | ||
|   | 488a42696c | ||
|   | f43ff45683 | ||
|   | bde1451896 | ||
|   | fff2ca6f26 | ||
|   | bec57a6cee | ||
|   | a012ca4fac | ||
|   | 3ba49b0a50 | ||
|   | 314aa024b5 | ||
|   | 598f750859 | ||
|   | 8057848458 | ||
|   | e80c28a530 | ||
|   | 5d05b66902 | ||
|   | 59634b2cf5 | ||
|   | 7c3892f5a2 | ||
|   | 19406a238b | ||
|   | d6146197dd | ||
|   | 38f241479c | ||
|   | aa45999824 | ||
|   | 3b7593ed7f | ||
|   | f959cafb36 | ||
|   | e92d204d42 | ||
|   | f543161234 | ||
|   | 9cc1017912 | ||
|   | fc719c19f9 | ||
|   | ad97fc6855 | ||
|   | 407dc74502 | ||
|   | e5e764b402 | ||
|   | 65ad65fe52 | ||
|   | 8d09d20510 | ||
|   | 5a5b04b2b0 | ||
|   | 3113bac8e6 | ||
|   | 9217f2c916 | ||
|   | 32a49b7846 | ||
|   | 12ef034b7b | ||
|   | e70e4a21f2 | ||
|   | 7826fb4f04 | ||
|   | 8f1b8909dc | ||
|   | 3f7a989d38 | ||
|   | 4d3fd7598d | ||
|   | 620e3cef20 | ||
|   | 812a61939e | ||
|   | 793c6c2f7b | ||
|   | f8f4d0f646 | ||
|   | 3a92aa751f | ||
|   | 6882273aa0 | ||
|   | 3b0197620f | ||
|   | f6240e114c | ||
|   | 0f3c129b95 | ||
|   | 6f87b01c47 | ||
|   | 32110a9866 | ||
|   | ba459f4d20 | ||
|   | d868e6bfaf | ||
|   | 9f3499a7c3 | ||
|   | ccec7732a7 | ||
|   | 3b980a173f | ||
|   | 246139f90b | ||
|   | 4e85fb7d8d | ||
|   | ab32411b0c | ||
|   | 477f28a6bd | ||
|   | 96ef0a178d | ||
|   | ab603e7ef7 | ||
|   | d52104a62a | ||
|   | 8d8d1cfdd6 | ||
|   | 5451751513 | ||
|   | 92ae26bb9f | ||
|   | 3c8ee0c8cb | ||
|   | 20f76b8118 | ||
|   | d8eeeead18 | ||
|   | 5452162bc3 | ||
|   | a4adcba405 | ||
|   | 66da594382 | ||
|   | aa426842f2 | ||
|   | ed53a45228 | ||
|   | 2ddbcb2369 | ||
|   | 61a58ff3c9 | ||
|   | 638aee65c0 | ||
|   | f21c49f8da | ||
|   | 583d2cb4e4 | ||
|   | aa70dcfc8f | ||
|   | ffb61c425b | ||
|   | 858cf5e0c9 | ||
|   | 881dd4666e | ||
|   | c4aeaf7fe8 | ||
|   | ea1f5a8fc6 | ||
|   | 7f1e420a0a | ||
|   | 1272eaf07f | ||
|   | 2fe760cc4b | ||
|   | df3a50bae8 | ||
|   | 0db3605f33 | ||
|   | 173fa92116 | ||
|   | 02ca58c1eb | ||
|   | 4a22fe58bf | ||
|   | 3fc478a14b | ||
|   | 203bc674fe | ||
|   | a4a9f0a04c | ||
|   | 918e7fffeb | ||
|   | e1648de661 | ||
|   | f7c94e6343 | ||
|   | 2fba8e29e0 | ||
|   | de1bb4e203 | ||
|   | b4680a5c25 | ||
|   | d44f40d105 | ||
|   | 8d5771e302 | ||
|   | fb31f99aed | ||
|   | b97f3a9ecf | ||
|   | ac22172a6e | ||
|   | 57367380f5 | ||
|   | 7101cc3170 | ||
|   | 7051411be7 | ||
|   | bb8397b9b1 | ||
|   | bb8fa61cb4 | ||
|   | 10147ee331 | ||
|   | 3779ac2c8a | ||
|   | bdad4db9ec | ||
|   | 36c69124f7 | ||
|   | 7c8c811134 | ||
|   | ccfc9f3ab0 | ||
|   | d163b92e0b | ||
|   | 887a21afb9 | ||
|   | 634adc9f71 | ||
|   | 974f01cef7 | ||
|   | ef9ade3548 | ||
|   | 24fdb73b44 | ||
|   | ba06a87ba8 | ||
|   | e6be483755 | ||
|   | a36bfced47 | ||
|   | 4faf421d5a | ||
|   | a739455414 | ||
|   | 73f6e75d8d | ||
|   | 17e7f8057a | ||
|   | b62c157680 | ||
|   | 816f5162f9 | ||
|   | 9d8f8277aa | ||
|   | cca14053a4 | ||
|   | 427790f005 | ||
|   | 17845bf71e | ||
|   | 452f5ab3ba | ||
|   | 335744e78a | ||
|   | c9e24439b2 | ||
|   | 61c697b6db | ||
|   | 3227d4f3ed | ||
|   | 7e27afb645 | ||
|   | 9ba4790b4d | ||
|   | 3a26f7f4d5 | ||
|   | 8b99617513 | ||
|   | 587655f063 | ||
|   | 7e9ecf4eb2 | ||
|   | 5413010c60 | ||
|   | 7d13cf1587 | ||
|   | 24cd13935a | ||
|   | 1ae7dbec67 | ||
|   | 2b0a2ab3bc | ||
|   | 4ed0f3e5f0 | ||
|   | 9cacc703dd | ||
|   | 9ae70c6519 | ||
|   | 02b38fed49 | ||
|   | b2a65f809f | ||
|   | 2931869522 | ||
|   | 11d8640ba6 | ||
|   | 65ff947b5e | ||
|   | 2dd7db4808 | ||
|   | 1d7354696e | ||
|   | cbceac4c8a | ||
|   | 297877fbe2 | ||
|   | 0d92451c49 | ||
|   | c8a58dcb69 | ||
|   | a4dea25d76 | ||
|   | bfb0235fc6 | ||
|   | 6dcc3d637f | ||
|   | 9bb4d17e31 | ||
|   | 9df09db5fe | ||
|   | d8e28ec274 | ||
|   | d3905734c1 | ||
|   | 8fe7f923ec | ||
|   | 933f38390b | ||
|   | a4e019442f | ||
|   | d1c4e60636 | ||
|   | 765d0228c0 | ||
|   | 2d2020a20d | ||
|   | 03ab282f67 | ||
|   | 7f94cb1cad | ||
|   | a2a303bd72 | ||
|   | d34bf9a14d | ||
|   | 68faba6bde | ||
|   | 5c5b9cfd96 | ||
|   | 9d683f4767 | ||
|   | f2912bad95 | ||
|   | c8adfe0131 | ||
|   | 8b7e637e74 | ||
|   | 43cffd7c4a | ||
|   | f3dad3765e | ||
|   | 70c25141fc | ||
|   | b1b81a2672 | ||
|   | 46197bf262 | ||
|   | 58ec409e7f | ||
|   | c2d68599de | ||
|   | 65f00f3af2 | ||
|   | 6544326ffd | ||
|   | a23c206ccb | ||
|   | 1b152e6bd0 | ||
|   | d9624d9882 | ||
|   | 178b8471cc | ||
|   | 719d2092a7 | ||
|   | 2f3a4675da | ||
|   | 9513be664b | ||
|   | 64d8b7853a | ||
|   | 4174e57c13 | ||
|   | 88b395599a | ||
|   | b6d682c92c | ||
|   | 3b02894341 | ||
|   | f3feb13dfe | ||
|   | 114d8d0aba | ||
|   | 503a843bb3 | ||
|   | c4d91aff40 | ||
|   | 2ffe5faf6e | ||
|   | 5d2a03aa82 | ||
|   | f4c83d1221 | ||
|   | 100e91714b | ||
|   | dafb7b5259 | ||
|   | 92906e217c | ||
|   | 5c7b721879 | ||
|   | c27dcb0414 | ||
|   | 7fcae1e974 | ||
|   | cc4659f5c6 | ||
|   | 9fce12d6b4 | ||
|   | deb2f30b37 | ||
|   | 751a3f0e94 | ||
|   | fee2a07e08 | ||
|   | 17726abb0a | ||
|   | 01f740ce69 | ||
|   | b168ccb605 | ||
|   | 04a31a52ae | ||
|   | 6a236fb91e | ||
|   | 619fa1bff8 | ||
|   | 53b37e8d0c | ||
|   | f8eb8adfbe | ||
|   | 3c2aecb81f | ||
|   | efca9e11d6 | ||
|   | 49189e0e43 | ||
|   | ea86c9bafb | ||
|   | c361b6a85c | ||
|   | c7ff45045c | ||
|   | 090af35ea1 | ||
|   | 8e668ca633 | ||
|   | 4d9a16f33b | ||
|   | c9f2a0f694 | ||
|   | 46bac3069e | ||
|   | d21aa0d85f | ||
|   | 3e87d699eb | ||
|   | 23aa216908 | ||
|   | e34e681157 | ||
|   | acd543fe4b | ||
|   | c2df5939d6 | ||
|   | b52e74b615 | ||
|   | ec2bb039ae | ||
|   | aeb9f5775f | ||
|   | 47a20756b9 | ||
|   | a7aba1d585 | ||
|   | c89af0cea4 | ||
|   | 9d3a109946 | ||
|   | 079f1e6fff | ||
|   | 7249b11899 | ||
|   | 4cfb000812 | ||
|   | 5262a41619 | ||
|   | 887590730d | ||
|   | adb49bdf0b | ||
|   | 7f3aadc157 | ||
|   | eb1c85f3f5 | ||
|   | dbdc884c96 | ||
|   | 5166354e34 | ||
|   | 04ea95049a | ||
|   | ec62e49001 | ||
|   | 6fb21850d8 | ||
|   | 98b50fd942 | ||
|   | 729c962b7c | ||
|   | ebc15e60a8 | ||
|   | 85f2d94253 | ||
|   | 981a536cb5 | ||
|   | abf7c333b1 | ||
|   | b2f547e934 | ||
|   | 0870a25e2f | ||
|   | 1091e577a5 | ||
|   | 151ad16fe6 | ||
|   | e325258091 | ||
|   | 1139a02b40 | ||
|   | 4b90953226 | ||
|   | d77fc01580 | ||
|   | 216d84faeb | ||
|   | 0c9d95f183 | ||
|   | 913739d732 | ||
|   | 7ecb5af587 | ||
|   | 87f0e79749 | ||
|   | c85145d73c | ||
|   | eea689841b | ||
|   | e50a59361d | ||
|   | 9862185bda | ||
|   | fe05d35bbb | ||
|   | ba602c17d4 | ||
|   | 831bd07b0d | ||
|   | 175c5d9fc3 | ||
|   | 2639e30d9c | ||
|   | 78a0218a91 | ||
|   | e12bf8daed | ||
|   | 04e2072e2c | ||
|   | 7bafe20a34 | ||
|   | 554d5aeb7c | ||
|   | 3991d2729d | ||
|   | 5bc8a0860a | ||
|   | ad03fb0815 | ||
|   | e10d2a68f3 | ||
|   | 213ee8d381 | ||
|   | 52b1a1b835 | ||
|   | fce2930b85 | ||
|   | 735f589b1c | ||
|   | 69f17da5ca | ||
|   | 5c0ee02251 | ||
|   | 0cb4c7e437 | ||
|   | 842c792868 | ||
|   | 4ba8518462 | ||
|   | 143dfb6246 | ||
|   | da4238ec68 | ||
|   | 5f9e3edbe1 | ||
|   | 1c68aee577 | ||
|   | e8d9a4bd49 | ||
|   | 9520e87a38 | ||
|   | f9f5004909 | ||
|   | 4f7014b2d5 | ||
|   | 01dbfddb64 | ||
|   | 6266a22d86 | ||
|   | 09fe31179a | ||
|   | 78343f4837 | ||
|   | a5619bc0a3 | ||
|   | dcb28aad2a | ||
|   | 754cf172f5 | ||
|   | 7890af1659 | ||
|   | d27d9fe694 | ||
|   | 634a599db6 | ||
|   | 5d0d859a1f | ||
|   | 40c966fcd6 | ||
|   | d9245598a4 | ||
|   | 03b0f4b16b | ||
|   | 2daa0d057b | ||
|   | 76eca409a3 | ||
|   | d84b018ba7 | ||
|   | 027c3d1661 | ||
|   | 4965b1ca7b | ||
|   | 9cd7ea9371 | ||
|   | dc2468b27b | ||
|   | ea2451d882 | ||
|   | 252617bd70 | ||
|   | 8bd7003ea7 | ||
|   | 280203158c | ||
|   | d456c3f62e | ||
|   | f64d17963b | ||
|   | f12378cf7b | ||
|   | 15ff426be8 | ||
|   | c4a6837d56 | ||
|   | e6c28cf509 | ||
|   | a347a75617 | ||
|   | 7fc2183826 | ||
|   | b6c3c9891c | ||
|   | d401b493a4 | ||
|   | da1a8308b6 | ||
|   | 106d827a21 | ||
|   | 1ebb162a00 | ||
|   | 9d2791d9f8 | ||
|   | 04a00f6564 | ||
|   | a5dd44c77f | ||
|   | 8f86fd6bae | ||
|   | d5cd534320 | ||
|   | a5a6fd3bc2 | ||
|   | 287ddda5df | ||
|   | 7747f1c31d | ||
|   | 8097cbbbe3 | ||
|   | e4c07875a3 | ||
|   | 026fd4cf35 | ||
|   | 87016f9620 | ||
|   | 88393f0f65 | ||
|   | f5a9dbb348 | ||
|   | dbf3bb112c | ||
|   | f3186bd501 | ||
|   | 3f1a252b91 | ||
|   | 1240d6be76 | ||
|   | faf7b62f5c | ||
|   | 445011b1e5 | ||
|   | e630fec63a | ||
|   | 17421e8a63 | ||
|   | af06b78605 | ||
|   | 3749b09366 | ||
|   | 27cac10d0c | ||
|   | 0590962d36 | ||
|   | c0c20d49a5 | ||
|   | cf7cf45003 | ||
|   | 633dd0d9de | ||
|   | b929320d4c | ||
|   | 7296bedd8e | ||
|   | e9fbbf4000 | ||
|   | dd44219aa5 | ||
|   | 8cc1fe007d | ||
|   | c0b45c9fc4 | ||
|   | 41315f45a9 | ||
|   | 04d28a0eea | ||
|   | 1e9cd3f785 | ||
|   | f462dd6a4c | ||
|   | 34e75fc595 | ||
|   | dac513e046 | ||
|   | 2c538d247b | ||
|   | f3b7f61e54 | ||
|   | d47ecf19f5 | ||
|   | da19b344b5 | ||
|   | eb66407926 | ||
|   | 61c5b8e7d2 | ||
|   | 4b09d57ec2 | ||
|   | a16f699dfc | ||
|   | b908a3d70a | ||
|   | d519c7263e | ||
|   | 5dedc5d8ba | ||
|   | 3ca1784ff4 | ||
|   | b54d512f3f | ||
|   | 9d8fb19f55 | ||
|   | 1ac4ab7edc | ||
|   | c9b73ac731 | ||
|   | e0b87f1e14 | ||
|   | 394743efc8 | ||
|   | 27a86a4756 | ||
|   | 8ee0ef2cde | ||
|   | 43f4682ec4 | ||
|   | 5f081b8f8d | ||
|   | 5023542882 | ||
|   | 88d0731d80 | ||
|   | 06cb8c52d7 | ||
|   | 4d1358b7ed | ||
|   | 0cfa7c1c56 | ||
|   | 76928390a3 | ||
|   | af1f9cd76d | ||
|   | 2f39f3d146 | ||
|   | f72f39bc26 | ||
|   | 2659ba6bb4 | ||
|   | 0b8c0c202e | ||
|   | 37c8132632 | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -43,6 +43,8 @@ docs/reference/*/xml/ | ||||
| docs/reference/shell/doc-gen-* | ||||
| gtk-doc.make | ||||
| js/misc/config.js | ||||
| js/js-resources.c | ||||
| js/js-resources.h | ||||
| intltool-extract.in | ||||
| intltool-merge.in | ||||
| intltool-update.in | ||||
|   | ||||
							
								
								
									
										41
									
								
								COPYING
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								COPYING
									
									
									
									
									
								
							| @@ -1,12 +1,12 @@ | ||||
| 		    GNU GENERAL PUBLIC LICENSE | ||||
| 		       Version 2, June 1991 | ||||
|                     GNU GENERAL PUBLIC LICENSE | ||||
|                        Version 2, June 1991 | ||||
|  | ||||
|  Copyright (C) 1989, 1991 Free Software Foundation, Inc. | ||||
|      59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
|  Copyright (C) 1989, 1991 Free Software Foundation, Inc., | ||||
|  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  Everyone is permitted to copy and distribute verbatim copies | ||||
|  of this license document, but changing it is not allowed. | ||||
|  | ||||
| 			    Preamble | ||||
|                             Preamble | ||||
|  | ||||
|   The licenses for most software are designed to take away your | ||||
| freedom to share and change it.  By contrast, the GNU General Public | ||||
| @@ -15,7 +15,7 @@ software--to make sure the software is free for all its users.  This | ||||
| General Public License applies to most of the Free Software | ||||
| Foundation's software and to any other program whose authors commit to | ||||
| using it.  (Some other Free Software Foundation software is covered by | ||||
| the GNU Library General Public License instead.)  You can apply it to | ||||
| the GNU Lesser General Public License instead.)  You can apply it to | ||||
| your programs, too. | ||||
|  | ||||
|   When we speak of free software, we are referring to freedom, not | ||||
| @@ -55,8 +55,8 @@ patent must be licensed for everyone's free use or not licensed at all. | ||||
|  | ||||
|   The precise terms and conditions for copying, distribution and | ||||
| modification follow. | ||||
|  | ||||
| 		    GNU GENERAL PUBLIC LICENSE | ||||
|  | ||||
|                     GNU GENERAL PUBLIC LICENSE | ||||
|    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | ||||
|  | ||||
|   0. This License applies to any program or other work which contains | ||||
| @@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions: | ||||
|     License.  (Exception: if the Program itself is interactive but | ||||
|     does not normally print such an announcement, your work based on | ||||
|     the Program is not required to print an announcement.) | ||||
|  | ||||
|  | ||||
| These requirements apply to the modified work as a whole.  If | ||||
| identifiable sections of that work are not derived from the Program, | ||||
| and can be reasonably considered independent and separate works in | ||||
| @@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent | ||||
| access to copy the source code from the same place counts as | ||||
| distribution of the source code, even though third parties are not | ||||
| compelled to copy the source along with the object code. | ||||
|  | ||||
|  | ||||
|   4. You may not copy, modify, sublicense, or distribute the Program | ||||
| except as expressly provided under this License.  Any attempt | ||||
| otherwise to copy, modify, sublicense or distribute the Program is | ||||
| @@ -225,7 +225,7 @@ impose that choice. | ||||
|  | ||||
| This section is intended to make thoroughly clear what is believed to | ||||
| be a consequence of the rest of this License. | ||||
|  | ||||
|  | ||||
|   8. If the distribution and/or use of the Program is restricted in | ||||
| certain countries either by patents or by copyrighted interfaces, the | ||||
| original copyright holder who places the Program under this License | ||||
| @@ -255,7 +255,7 @@ make exceptions for this.  Our decision will be guided by the two goals | ||||
| of preserving the free status of all derivatives of our free software and | ||||
| of promoting the sharing and reuse of software generally. | ||||
|  | ||||
| 			    NO WARRANTY | ||||
|                             NO WARRANTY | ||||
|  | ||||
|   11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY | ||||
| FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN | ||||
| @@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER | ||||
| PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE | ||||
| POSSIBILITY OF SUCH DAMAGES. | ||||
|  | ||||
| 		     END OF TERMS AND CONDITIONS | ||||
|  | ||||
| 	    How to Apply These Terms to Your New Programs | ||||
|                      END OF TERMS AND CONDITIONS | ||||
|  | ||||
|             How to Apply These Terms to Your New Programs | ||||
|  | ||||
|   If you develop a new program, and you want it to be of the greatest | ||||
| possible use to the public, the best way to achieve this is to make it | ||||
| @@ -303,17 +303,16 @@ the "copyright" line and a pointer to where the full notice is found. | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|     GNU General Public License for more details. | ||||
|  | ||||
|     You should have received a copy of the GNU General Public License | ||||
|     along with this program; if not, write to the Free Software | ||||
|     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
|  | ||||
|     You should have received a copy of the GNU General Public License along | ||||
|     with this program; if not, write to the Free Software Foundation, Inc., | ||||
|     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||||
|  | ||||
| Also add information on how to contact you by electronic and paper mail. | ||||
|  | ||||
| If the program is interactive, make it output a short notice like this | ||||
| when it starts in an interactive mode: | ||||
|  | ||||
|     Gnomovision version 69, Copyright (C) year  name of author | ||||
|     Gnomovision version 69, Copyright (C) year name of author | ||||
|     Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | ||||
|     This is free software, and you are welcome to redistribute it | ||||
|     under certain conditions; type `show c' for details. | ||||
| @@ -336,5 +335,5 @@ necessary.  Here is a sample; alter the names: | ||||
| This General Public License does not permit incorporating your program into | ||||
| proprietary programs.  If your program is a subroutine library, you may | ||||
| consider it more useful to permit linking proprietary applications with the | ||||
| library.  If this is what you want to do, use the GNU Library General | ||||
| library.  If this is what you want to do, use the GNU Lesser General | ||||
| Public License instead of this License. | ||||
|   | ||||
| @@ -1,7 +1,11 @@ | ||||
| # Point to our macro directory and pick up user flags from the environment | ||||
| ACLOCAL_AMFLAGS  = -I m4 ${ACLOCAL_FLAGS} | ||||
|  | ||||
| SUBDIRS = data js src browser-plugin tests po docs | ||||
| SUBDIRS = data js src  tests po docs | ||||
|  | ||||
| if BUILD_BROWSER_PLUGIN | ||||
| SUBDIRS += browser-plugin | ||||
| endif | ||||
|  | ||||
| if ENABLE_MAN | ||||
| SUBDIRS += man | ||||
|   | ||||
							
								
								
									
										274
									
								
								NEWS
									
									
									
									
									
								
							
							
						
						
									
										274
									
								
								NEWS
									
									
									
									
									
								
							| @@ -1,3 +1,277 @@ | ||||
| 3.12.2 | ||||
| ====== | ||||
| * Fix turning off airplane mode [Giovanni; #728512] | ||||
| * Handle empty VPN keyfiles [Adel; #728681] | ||||
| * Fix setting zero-level in osdWindow [Bastien; #727384] | ||||
| * Fix removal of multiple workspace thumbnails at once [Florian; #728820] | ||||
| * Make airplane mode menu insensitive in lock screen [Giovanni; #729224] | ||||
| * Fix keynav for alternatives in AltSwitcher [Florian; #727259] | ||||
| * Fix zombie search providers showing up [Jasper; #728597] | ||||
|  | ||||
| Contributors: | ||||
|   Giovanni Campagna, Adel Gadllah, Florian Müllner, Bastien Nocera, | ||||
|   Jasper St. Pierre | ||||
|  | ||||
| Translations: | ||||
|   Wouter Bolsterlee [nl], Daniel Korostil [uk], Ihar Hrachyshka [be], | ||||
|   Giovanni Campagna [it], Carles Ferrando [ca@valencia] | ||||
|  | ||||
| 3.12.1 | ||||
| ====== | ||||
| * Ensure the currently focused app icon is viewable [Rui; #726759] | ||||
| * Improve language in location menu [Zeeshan; #726498] | ||||
| * Improve HiDpi support [Cosimo; #726907] | ||||
| * Set accessible role for window previews [Alejandro; #726670] | ||||
| * Fix bad antialiasing on panel menu buttons [Carlos; #727336] | ||||
| * Don't hide location menu [Zeeshan; #727398] | ||||
| * Fix IM candidate window obscuring current text [Rui; #727579] | ||||
| * Fix extension-prefs tool when linked with --as-needed [Florian; #727948] | ||||
| * Don't always extend struts to the screen edge [Florian; #663690] | ||||
|  | ||||
| Contributors: | ||||
|   Zeeshan Ali (Khattak), Cosimo Cecchi, Piotr Drąg, Rui Matos, Simon McVittie, | ||||
|   Florian Müllner, Alejandro Piñeiro, Carlos Soriano | ||||
|  | ||||
| Translations: | ||||
|   Khaled Hosny [ar], Piotr Drąg [pl], Yosef Or Boczko [he], | ||||
|   Antonio Fernandes C. Neto [pt_BR], Marek Černocký [cs], maria thukididu [el], | ||||
|   Andika Triwidada [id], Daniel Mustieles [es], Changwoo Ryu [ko], | ||||
|   Benjamin Steinwender [de], Sphinx Jiang [zh_CN], | ||||
|   Inaki Larranaga Murgoitio [eu], Marcus Lundblad [sv], Aurimas Černius [lt], | ||||
|   Stas Solovey [ru], Alexandre Franke [fr], Matej Urbančič [sl], | ||||
|   Fran Diéguez [gl], Pau Iranzo [ca], Luca Ferretti [it], Milo Casagrande [it], | ||||
|   Tiago S [pt], Victor Ibragimov [tg], Dirgita [id], Khoem Sokhem [km], | ||||
|   Rūdolfs Mazurs [lv], Balázs Úr [hu], Ask H. Larsen [da], Ikuya Awashiro [ja], | ||||
|   Мирослав Николић [sr, sr@latin] | ||||
|  | ||||
| 3.12.0 | ||||
| ====== | ||||
| * gdm: Reset greeter when coming back to login screen [Jasper; #726989] | ||||
|  | ||||
| Contributors: | ||||
|   Jasper St. Pierre | ||||
|  | ||||
| Translations: | ||||
|   Daniel Martinez [an], Yuri Myasoedov [ru], Inaki Larranaga Murgoitio [eu], | ||||
|   Abderrahim Kitouni [ar], Praveen Illa [te], Matej Urbančič [sl], | ||||
|   Chao-Hsiung Liao [zh_HK, zh_TW], Frédéric Péters [fr], | ||||
|   Мирослав Николић [sr, sr@latin], Ask H. Larsen [da], Kenneth Nielsen [da], | ||||
|   Jiro Matsuzawa [ja], Dušan Kazik [sk] | ||||
|  | ||||
| 3.11.92 | ||||
| ======= | ||||
| * calendar: Grab key focus after changing day [Volker; #725606] | ||||
| * gdm: Don't load user list if disabled [Florian; #725905] | ||||
| * Don't show network-offline in the top bar [Jasper; #725340] | ||||
| * Improve radial shade effect of modal dialogs [Giovanni; #725830] | ||||
| * Fix broken suspend-on-idle functionality [Giovanni; #712706] | ||||
| * Close wifi selection dialog when device disappears [Giovanni; #723935] | ||||
| * Don't close chats when pressing Escape [Giovanni; #724178] | ||||
| * Improve smartcard support in login/lock screen [Ray; #726262, #726263] | ||||
| * Wake up screen when resuming from suspend [Giovanni; #726378] | ||||
| * Make bluetooth and location items insensitive when locked [Florian; #726319] | ||||
| * Don't show bluetooth icon when there is no adapter [Giovanni; #725057] | ||||
| * Make sure to keep the OSK on top of modal dialogs [Rui; #719451] | ||||
| * Misc. bug fixes and cleanups [Giovanni, Ray, Adel, Daniel, Jasper, Florian; | ||||
|   #725832, #725958, #722149, #724977, #724798, #725020, #723976, #726119, | ||||
|   #726238, #585500, #704844, #726323, #726322, #726120, #726414] | ||||
|  | ||||
| Contributors: | ||||
|   Giovanni Campagna, Daniel Drake, Adel Gadllah, Rui Matos, Florian Müllner, | ||||
|   Volker Sobek, Jasper St. Pierre, Ray Strode | ||||
|  | ||||
| Translations: | ||||
|   Fabio Tomat [fur], Rafael Ferreira [pt_BR], Fran Diéguez [gl], | ||||
|   Marek Černocký [cs], Baurzhan Muftakhidinov [kk], Andika Triwidada [id], | ||||
|   A S Alam [pa], Rūdolfs Mazurs [lv], Wylmer Wang [zh_CN], | ||||
|   Aurimas Černius [lt], Cheng-Chia Tseng [zh_TW], Stas Solovey [ru], | ||||
|   Tiagosdot [pt], Benjamin Steinwender [de], Frédéric Peters [fr], | ||||
|   Daniel Korostil [uk], Yaron Shahrabani [he], Ville-Pekka Vainio [fi], | ||||
|   maria thukididu [el], Victor Ibragimov [tg], Kjartan Maraas [nb], | ||||
|   Gábor Kelemen [hu], Ask H. Larsen [da] | ||||
|  | ||||
| 3.11.91 | ||||
| ======= | ||||
| * Don't use network profile name in menu [Giovanni; #725586] | ||||
| * calendar: Make date label clickable to return to current date [Vit; #641366] | ||||
| * Misc. bug fixes [Florian, Zeeshan, Adel, Jasper, Dan, Volker; #724813, | ||||
|   #724686, #725082, #724870, #724779, #725533] | ||||
|  | ||||
| Contributors: | ||||
|   Zeeshan Ali (Khattak), Giovanni Campagna, Piotr Drąg, Adel Gadllah, | ||||
|   Florian Müllner, Volker Sobek, Vit Stanislav, Jasper St. Pierre, Dan Williams | ||||
|  | ||||
| Translations: | ||||
|   Victor Ibragimov [tg], Aurimas Černius [lt], Dimitris Spingos [el], | ||||
|   Andika Triwidada [id], Rafael Ferreira [pt_BR], Daniel Mustieles [es], | ||||
|   Baurzhan Muftakhidinov [kk], Marek Černocký [cs], Ihar Hrachyshka [be], | ||||
|   eternalhui [zh_CN], Yosef Or Boczko [he], Fran Diéguez [gl], | ||||
|   Khaled Hosny [ar], Ville-Pekka Vainio [fi], Piotr Drąg [pl], | ||||
|   Kjartan Maraas [nb], Changwoo Ryu [ko] | ||||
|  | ||||
| 3.11.90 | ||||
| ======= | ||||
| * Stop showing two bluetooth entries [Giovanni; #709353] | ||||
| * Improve styling of login/lock screen [Reda; #723833] | ||||
| * Fix magnifier crosshairs [Magdalen; #723709] | ||||
| * Make NetworkManager support optional [Michael; #669495] | ||||
| * Make middle-click open a new instance [Florian; #695010] | ||||
| * Scale the UI on high resolution displays [Cosimo, Adel; #705410, #724607] | ||||
| * Remove notification counter on screen shield [Carlos; #709275] | ||||
| * Improve app picker transition [Carlos; #722331] | ||||
| * Add geolocation indicator to status menu [Zeeshan; #723684] | ||||
| * Improve timestamps in chat notifications [Carlos; #708031, #715158] | ||||
| * Improve network menus [Giovanni; #723570] | ||||
| * Add "VPN Setting" item to VPN submenu [Giovanni; #709167] | ||||
| * Improve appearance of disclosure arrows [Carlos; #720206] | ||||
| * Add GSetting key to disable version validation of extensions [Adel; #724683] | ||||
| * Delay auto-removing empty workspaces [Florian; #709064] | ||||
| * Offer offline updates in the shutdown dialog [Kalev; #722898] | ||||
| * Animate tile previews [Florian; #665758] | ||||
| * Misc. bug fixes and cleanups [Giovanni, Ryan, Debarshi, Florian; #709128, | ||||
|   #722342, #723661, #724184, #724256, #724293, #724305, #722554, #724282, | ||||
|   #724690, #722928] | ||||
|  | ||||
| Contributors: | ||||
|   Zeeshan Ali (Khattak), Magdalen Berns, Michael Biebl, Giovanni Campagna, | ||||
|   Cosimo Cecchi, Adel Gadllah, Reda Lazri, Kalev Lember, Ryan Lortie, | ||||
|   Florian Müllner, Debarshi Ray, Carlos Soriano, Jasper St. Pierre, | ||||
|   Colin Walters | ||||
|  | ||||
| Translations: | ||||
|   Victor Ibragimov [tg], Daniel Mustieles [es], Khaled Hosny [ar], | ||||
|   Enrico Nicoletto [pt_BR], Yosef Or Boczko [he], Fran Diéguez [gl], | ||||
|   Marek Černocký [cs], Baurzhan Muftakhidinov [kk], Jorge Pérez Pérez [an], | ||||
|   Kjartan Maraas [nb], David Lüder [de], Daniel Korostil [uk], ngoswami [as], | ||||
|   Rafael Ferreira [pt_BR] | ||||
|  | ||||
| 3.11.5 | ||||
| ====== | ||||
| * Fix extension preference tool [Florian; #722334] | ||||
| * Fix keyboard activation of legacy tray icons [Giovanni; #721267] | ||||
| * Add radial background shade for modal dialogs [Giovanni; #669798] | ||||
| * Show attached modal windows in the overview [Giovanni; #650843] | ||||
| * Add support for desktop actions [Giovanni; #669603] | ||||
| * Indicate in system status when location service is used [Zeeshan; #709372] | ||||
| * Add support for extended app folder schema [Jasper; #723179] | ||||
| * Show status icon for wired network connections [Jasper; #708966] | ||||
| * Indicate airplane mode in network selection dialog [Giovanni; #709128] | ||||
| * Misc bug fixes and cleanups [Florian, Sebastian, Giovanni, Tim, Matt, Jasper; | ||||
|   #722417, #722494, #722547, #722593, #722434, #722787, #722690, #722840, | ||||
|   #722660, #722812, #723197, #722927, #723306, #723308, #723523, #709685, | ||||
|   #723570] | ||||
|  | ||||
| Contributors: | ||||
|   Zeeshan Ali (Khattak), Magdalen Berns, Giovanni Campagna, William Jon McCann, | ||||
|   Sebastian Keller, Tim Lunn, Florian Müllner, Carlos Soriano, | ||||
|   Jasper St. Pierre, Rico Tzschichholz, Matt Watson | ||||
|  | ||||
| Translations: | ||||
|   Marek Černocký [cs], Mattias Põldaru [et], Tong Hui [zh_CN], | ||||
|   Victor Ibragimov [tg], Enrico Nicoletto [pt_BR], Daniel Mustieles [es], | ||||
|   Fran Diéguez [gl], Kjartan Maraas [nb], Nilamdyuti Goswami [as], | ||||
|   Aurimas Černius [lt], Stas Solovey [ru], Yosef Or Boczko [he], | ||||
|   Jorge Pérez Pérez [an], Dimitris Spingos [el], Baurzhan Muftakhidinov [kk], | ||||
|   Chao-Hsiung Liao [zh_HK, zh_TW], Shankar Prasad [kn], Yaron Shahrabani [he], | ||||
|   Andika Triwidada [id] | ||||
|  | ||||
| 3.11.4 | ||||
| ====== | ||||
| * Fix removal of workspacaes that are not at the end [Giovanni; #721417] | ||||
| * Allow session mode to be specified in the environment [Ray; #720894] | ||||
| * Special-case launching of terminals [Debarshi; #695010] | ||||
| * Always show arrow if app switcher is scrollable [Jonh; #711467] | ||||
| * Implement new app folders system [Jasper; #722117] | ||||
| * Remove arrow from background menu [Tarun; #699608] | ||||
| * Misc bug fixes and cleanups [Giovanni, Andika, Florian, Ray; #721039, | ||||
|   #721439, #721507, #721629, #721868, #722210] | ||||
|  | ||||
| Contributors: | ||||
|   Giovanni Campagna, Piotr Drąg, Tarun Kumar Joshi, Florian Müllner, | ||||
|   Debarshi Ray, Jasper St. Pierre, Ray Strode, Andika Triwidada, Jonh Wendell | ||||
|  | ||||
| Translations: | ||||
|   Dušan Kazik [sk], Tong Hui [zh_CN], Benjamin Steinwender [de], | ||||
|   Matej Urbančič [sl], Jorge Pérez Pérez [an], Kjartan Maraas [nb], | ||||
|   Milo Casagrande [it], Rafael Ferreira [pt_BR], Marek Černocký [cs], | ||||
|   Daniel Mustieles [es], Adorilson Bezerra [pt_BR], Christian Kirbach [de], | ||||
|   Aurimas Černius [lt], Andika Triwidada [id], Baurzhan Muftakhidinov [kk], | ||||
|   Victor Ibragimov [tg], Yosef Or Boczko [he], Dimitris Spingos [el], | ||||
|   Fran Diéguez [gl] | ||||
|  | ||||
| 3.11.3 | ||||
| ====== | ||||
| * Fix fade effect of desktop icons [Florian; #707671] | ||||
| * Fix issues with background management code [Jasper; #709313] | ||||
| * Use new Glib facilities for application search [Jasper; #711631] | ||||
| * Add focus indication to session menu button [Sebastien; #710539] | ||||
| * Fix hover tracking for StEntries [Jasper; #706749] | ||||
| * Fix reentrancy issue in message tray [Jasper; #711694] | ||||
| * Tone down zoom animation on login/unlock [Jasper; #712362] | ||||
| * Allow specifying monitor for OSD [Carlos; #712664] | ||||
| * Fix resetting prompt on user switch [Ray; #710456] | ||||
| * Stop using gnome-bluetooth-applet [Bastien; #719341] | ||||
| * Add support for EAP-FAST password requests [Dan; #719813] | ||||
| * Fix entry focus of chat notifications [Jasper; #709853] | ||||
| * Make window previews keyboard navigatable [Jasper; #644306] | ||||
| * Fix app switcher order with dialog windows [Florian; #719824] | ||||
| * Allow remote search providers without icons [Debarshi; #719965] | ||||
| * Fix various alignment issues in RTL locales [Yosef; #712638, #712596, | ||||
|   #712594, #712600, #712579] | ||||
| * Misc. bug fixes and cleanups [Jasper, Florian, Giovanni, Dan; #712727, | ||||
|   #712753, #719378, #719730, #719803, #710115, #720017, #719815, #719567, | ||||
|   #720298] | ||||
|  | ||||
| Contributors: | ||||
|   Giovanni Campagna, Carlos Garnacho, Sebastien Lafargue, Tim Lunn, | ||||
|   Florian Müllner, Bastien Nocera, Yosef Or Boczko, Debarshi Ray, | ||||
|   Jasper St. Pierre, Ray Strode, Dan Williams | ||||
|  | ||||
| Translations: | ||||
|   Kjartan Maraas [nb], Reinout van Schouwen [nl], Rafael Ferreira [pt_BR], | ||||
|   Mattias Põldaru [et], Emin Tufan Çetin [tr], Jiri Grönroos [fi], | ||||
|   Khaled Hosny [ar], Fran Diéguez [gl], Victor Ibragimov [tg], | ||||
|   Daniel Mustieles [es] | ||||
|  | ||||
| 3.11.2 | ||||
| ====== | ||||
| * Cache search result display actors [Jasper; #704912] | ||||
| * Use username in userWidget if real name doesn't fit [Jasper; #706851] | ||||
| * Support shell_global_reexec_self() on OpenBSD [Antoine; #709571] | ||||
| * Support disabling browser plugin [Colin; #711218] | ||||
| * Restore support for 'disable-restart-buttons' [Florian; #711244] | ||||
| * Validate parameters of exposed DBus methods [Florian; #699752] | ||||
| * Connect applications to systemd journal if available [Colin; #711626] | ||||
| * Misc bug fixes and cleanups [Florian, Jasper; #711205, #698486, #711416, | ||||
|   #644306, #711555, #709806, #711631, #711732] | ||||
|  | ||||
| Contributors: | ||||
|   Cosimo Cecchi, Antoine Jacoutot, Florian Müllner, Jasper St. Pierre, | ||||
|   Rico Tzschichholz, Colin Walters | ||||
|  | ||||
| Translations: | ||||
|   Yuri Myasoedov [ru], Kjartan Maraas [nb], Efstathios Iosifidis [el], | ||||
|   Benjamin Steinwender [de], eternalhui [zh_CN], Shantha kumar [ta] | ||||
|  | ||||
| 3.11.1 | ||||
| ====== | ||||
| * power: Use UPower directly instead of gnome-settings-daemon [Bastien; #710273] | ||||
| * Implement support for new GTK+ notification API [Jasper, Giovanni, Florian; | ||||
|   #710137, #710596] | ||||
| * gdm: Don't allow user-list to fill up the entire screen [Florian; #710555] | ||||
| * Don't autostart remote search providers at login [Giovanni; #708830] | ||||
| * Fix spacing in end-session dialog [Sebastien; #710543] | ||||
| * Prepare for js24 [Tim; #711052] | ||||
| * Misc bug fixes and cleanups [Jasper, Florian, Adel, Tim, Sebastien; #710347, | ||||
|   #710144, #710541, #691409, #710745, #688331, #704912] | ||||
|  | ||||
| Contributors: | ||||
|   Giovanni Campagna, Adel Gadllah, Sebastien Lafargue, Tim Lunn, | ||||
|   Florian Müllner, Bastien Nocera, Jasper St. Pierre, Rico Tzschichholz | ||||
|  | ||||
| Translations: | ||||
|   Stas Solovey [ru], Yosef Or Boczko [he], Rafael Ferreira [pt_BR] | ||||
|  | ||||
| 3.10.1 | ||||
| ====== | ||||
| * Make sure lock screen is drawn once before switching user [Giovanni; #708051] | ||||
|   | ||||
							
								
								
									
										2
									
								
								README
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								README
									
									
									
									
									
								
							| @@ -8,7 +8,7 @@ For more information about GNOME Shell, including instructions on how | ||||
| to build GNOME Shell from source and how to get involved with the project, | ||||
| see: | ||||
|  | ||||
|  http://live.gnome.org/GnomeShell | ||||
|  https://wiki.gnome.org/Projects/GnomeShell | ||||
|  | ||||
| Bugs should be reported at http://bugzilla.gnome.org against the 'gnome-shell' | ||||
| product. | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| #!/bin/bash | ||||
| #!/bin/sh | ||||
| # Run this to generate all the initial makefiles, etc. | ||||
|  | ||||
| srcdir=`dirname $0` | ||||
|   | ||||
| @@ -13,9 +13,7 @@ | ||||
|  * General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program; if not, write to the Free Software | ||||
|  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA | ||||
|  * 02111-1307, USA. | ||||
|  * along with this program; if not, see <http://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  * Authors: | ||||
|  *      Jasper St. Pierre <jstpierre@mecheye.net> | ||||
| @@ -43,6 +41,8 @@ | ||||
|  | ||||
| #define PLUGIN_API_VERSION 5 | ||||
|  | ||||
| #define EXTENSION_DISABLE_VERSION_CHECK_KEY "disable-extension-version-validation" | ||||
|  | ||||
| typedef struct { | ||||
|   GDBusProxy *proxy; | ||||
| } PluginData; | ||||
| @@ -833,6 +833,16 @@ plugin_get_shell_version (PluginObject  *obj, | ||||
|   return ret; | ||||
| } | ||||
|  | ||||
| static gboolean | ||||
| plugin_get_version_validation_enabled (PluginObject  *obj, | ||||
|                                        NPVariant     *result) | ||||
| { | ||||
|   gboolean is_enabled = !g_settings_get_boolean (obj->settings, EXTENSION_DISABLE_VERSION_CHECK_KEY); | ||||
|   BOOLEAN_TO_NPVARIANT(is_enabled, *result); | ||||
|  | ||||
|   return TRUE; | ||||
| } | ||||
|  | ||||
| #define METHODS                                 \ | ||||
|   METHOD (list_extensions)                      \ | ||||
|   METHOD (get_info)                             \ | ||||
| @@ -852,6 +862,8 @@ static NPIdentifier api_version_id; | ||||
| static NPIdentifier shell_version_id; | ||||
| static NPIdentifier onextension_changed_id; | ||||
| static NPIdentifier onrestart_id; | ||||
| static NPIdentifier version_validation_enabled_id; | ||||
|  | ||||
|  | ||||
| static bool | ||||
| plugin_object_has_method (NPObject     *npobj, | ||||
| @@ -894,7 +906,8 @@ plugin_object_has_property (NPObject     *npobj, | ||||
|   return (name == onextension_changed_id || | ||||
|           name == onrestart_id || | ||||
|           name == api_version_id || | ||||
|           name == shell_version_id); | ||||
|           name == shell_version_id || | ||||
|           name == version_validation_enabled_id); | ||||
| } | ||||
|  | ||||
| static bool | ||||
| @@ -912,6 +925,8 @@ plugin_object_get_property (NPObject     *npobj, | ||||
|     return plugin_get_api_version (obj, result); | ||||
|   else if (name == shell_version_id) | ||||
|     return plugin_get_shell_version (obj, result); | ||||
|   else if (name == version_validation_enabled_id) | ||||
|     return plugin_get_version_validation_enabled (obj, result); | ||||
|   else if (name == onextension_changed_id) | ||||
|     { | ||||
|       if (obj->listener) | ||||
| @@ -990,6 +1005,7 @@ init_methods_and_properties (void) | ||||
|   /* this is the JS public API; it is manipulated through NPIdentifiers for speed */ | ||||
|   api_version_id = funcs.getstringidentifier ("apiVersion"); | ||||
|   shell_version_id = funcs.getstringidentifier ("shellVersion"); | ||||
|   version_validation_enabled_id = funcs.getstringidentifier ("versionValidationEnabled"); | ||||
|  | ||||
|   get_info_id = funcs.getstringidentifier ("getExtensionInfo"); | ||||
|   list_extensions_id = funcs.getstringidentifier ("listExtensions"); | ||||
|   | ||||
							
								
								
									
										106
									
								
								configure.ac
									
									
									
									
									
								
							
							
						
						
									
										106
									
								
								configure.ac
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| AC_PREREQ(2.63) | ||||
| AC_INIT([gnome-shell],[3.10.1],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell]) | ||||
| AC_INIT([gnome-shell],[3.12.2],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell]) | ||||
|  | ||||
| AC_CONFIG_HEADERS([config.h]) | ||||
| AC_CONFIG_SRCDIR([src/shell-global.c]) | ||||
| @@ -16,6 +16,7 @@ m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) | ||||
|  | ||||
| # Checks for programs. | ||||
| AC_PROG_CC | ||||
| AC_PROG_CXX | ||||
|  | ||||
| # Initialize libtool | ||||
| LT_PREREQ([2.2.6]) | ||||
| @@ -57,10 +58,25 @@ fi | ||||
|  | ||||
| AM_CONDITIONAL(BUILD_RECORDER, $build_recorder) | ||||
|  | ||||
| CLUTTER_MIN_VERSION=1.13.4 | ||||
| AC_ARG_ENABLE([systemd], | ||||
|               AS_HELP_STRING([--enable-systemd], [Use systemd]), | ||||
|               [enable_systemd=$enableval], | ||||
|               [enable_systemd=auto]) | ||||
| AS_IF([test x$enable_systemd != xno], [ | ||||
|   AC_MSG_CHECKING([for libsystemd-journal]) | ||||
|   PKG_CHECK_EXISTS([libsystemd-journal], | ||||
|                    [have_systemd=yes | ||||
|                     AC_DEFINE([HAVE_SYSTEMD], [1], [Define if we have systemd])], | ||||
|                    [have_systemd=no]) | ||||
|   AC_MSG_RESULT($have_systemd) | ||||
| ]) | ||||
|  | ||||
| AC_MSG_RESULT($enable_systemd) | ||||
|  | ||||
| CLUTTER_MIN_VERSION=1.15.90 | ||||
| GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1 | ||||
| GJS_MIN_VERSION=1.38.1 | ||||
| MUTTER_MIN_VERSION=3.10.1 | ||||
| GJS_MIN_VERSION=1.39.0 | ||||
| MUTTER_MIN_VERSION=3.12.1 | ||||
| GTK_MIN_VERSION=3.7.9 | ||||
| GIO_MIN_VERSION=2.37.0 | ||||
| LIBECAL_MIN_VERSION=3.5.3 | ||||
| @@ -70,7 +86,6 @@ POLKIT_MIN_VERSION=0.100 | ||||
| STARTUP_NOTIFICATION_MIN_VERSION=0.11 | ||||
| GCR_MIN_VERSION=3.7.5 | ||||
| GNOME_DESKTOP_REQUIRED_VERSION=3.7.90 | ||||
| GNOME_MENUS_REQUIRED_VERSION=3.5.3 | ||||
| NETWORKMANAGER_MIN_VERSION=0.9.8 | ||||
| PULSE_MIN_VERS=2.0 | ||||
|  | ||||
| @@ -80,7 +95,6 @@ SHARED_PCS="gio-unix-2.0 >= $GIO_MIN_VERSION | ||||
|             gtk+-3.0 >= $GTK_MIN_VERSION | ||||
|             atk-bridge-2.0 | ||||
|             gjs-internals-1.0 >= $GJS_MIN_VERSION | ||||
|             libgnome-menu-3.0 >= $GNOME_MENUS_REQUIRED_VERSION | ||||
|             $recorder_modules | ||||
|             gdk-x11-3.0 libsoup-2.4 | ||||
|             xtst | ||||
| @@ -91,9 +105,10 @@ SHARED_PCS="gio-unix-2.0 >= $GIO_MIN_VERSION | ||||
|             libcanberra libcanberra-gtk3 | ||||
|             telepathy-glib >= $TELEPATHY_GLIB_MIN_VERSION | ||||
|             polkit-agent-1 >= $POLKIT_MIN_VERSION | ||||
|             libnm-glib libnm-util >= $NETWORKMANAGER_MIN_VERSION | ||||
|             libnm-gtk >= $NETWORKMANAGER_MIN_VERSION | ||||
|             libsecret-unstable gcr-base-3 >= $GCR_MIN_VERSION" | ||||
|             gcr-base-3 >= $GCR_MIN_VERSION" | ||||
| if test x$have_systemd = xyes; then | ||||
|   SHARED_PCS="${SHARED_PCS} libsystemd-journal" | ||||
| fi | ||||
|  | ||||
| PKG_CHECK_MODULES(GNOME_SHELL, $SHARED_PCS) | ||||
| PKG_CHECK_MODULES(MUTTER, libmutter >= $MUTTER_MIN_VERSION) | ||||
| @@ -109,25 +124,25 @@ PKG_CHECK_MODULES(GNOME_SHELL_JS, gio-2.0 gjs-internals-1.0 >= $GJS_MIN_VERSION) | ||||
| PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-3.0 libcroco-0.6 >= 0.6.8 x11) | ||||
| PKG_CHECK_MODULES(SHELL_PERF_HELPER, gtk+-3.0 gio-2.0) | ||||
| PKG_CHECK_MODULES(SHELL_HOTPLUG_SNIFFER, gio-2.0 gdk-pixbuf-2.0) | ||||
| PKG_CHECK_MODULES(BROWSER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION json-glib-1.0 >= 0.13.2) | ||||
| PKG_CHECK_MODULES(TRAY, gtk+-3.0) | ||||
| PKG_CHECK_MODULES(GVC, libpulse >= $PULSE_MIN_VERS libpulse-mainloop-glib gobject-2.0) | ||||
| PKG_CHECK_MODULES(DESKTOP_SCHEMAS, gsettings-desktop-schemas >= 3.7.4) | ||||
| PKG_CHECK_MODULES(CARIBOU, caribou-1.0 >= 0.4.8) | ||||
|  | ||||
| AC_MSG_CHECKING([for bluetooth support]) | ||||
| PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 3.9.0], | ||||
|         [BLUETOOTH_DIR=`$PKG_CONFIG --variable=applet_libdir gnome-bluetooth-1.0` | ||||
| 	 BLUETOOTH_LIBS=`$PKG_CONFIG --variable=applet_libs gnome-bluetooth-1.0` | ||||
| 	 AC_SUBST([BLUETOOTH_LIBS],["$BLUETOOTH_LIBS"]) | ||||
| 	 AC_SUBST([BLUETOOTH_DIR],["$BLUETOOTH_DIR"]) | ||||
| 	 AC_DEFINE_UNQUOTED([BLUETOOTH_DIR],["$BLUETOOTH_DIR"],[Path to installed GnomeBluetooth typelib and library]) | ||||
| 	 AC_DEFINE([HAVE_BLUETOOTH],[1],[Define if you have libgnome-bluetooth-applet]) | ||||
| 	 AC_SUBST([HAVE_BLUETOOTH],[1]) | ||||
| 	 AC_MSG_RESULT([yes])], | ||||
| AC_ARG_ENABLE(browser-plugin, | ||||
|               [AS_HELP_STRING([--enable-browser-plugin], | ||||
|                               [Enable browser plugin [default=yes]])],, | ||||
|               enable_browser_plugin=yes) | ||||
| AS_IF([test x$enable_browser_plugin = xyes], [ | ||||
|   PKG_CHECK_MODULES(BROWSER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION json-glib-1.0 >= 0.13.2) | ||||
| ]) | ||||
| AM_CONDITIONAL(BUILD_BROWSER_PLUGIN, test x$enable_browser_plugin = xyes) | ||||
|  | ||||
| PKG_CHECK_MODULES(BLUETOOTH, gnome-bluetooth-1.0 >= 3.9.0, | ||||
|         [AC_DEFINE([HAVE_BLUETOOTH],[1],[Define if you have libgnome-bluetooth-applet]) | ||||
| 	 AC_SUBST([HAVE_BLUETOOTH],[1])], | ||||
| 	[AC_DEFINE([HAVE_BLUETOOTH],[0]) | ||||
| 	 AC_SUBST([HAVE_BLUETOOTH],[0]) | ||||
| 	 AC_MSG_RESULT([no])]) | ||||
| 	 AC_SUBST([HAVE_BLUETOOTH],[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) | ||||
| @@ -147,6 +162,9 @@ AC_SUBST(MUTTER_TYPELIB_DIR) | ||||
| GJS_CONSOLE=`$PKG_CONFIG --variable=gjs_console gjs-1.0` | ||||
| AC_SUBST(GJS_CONSOLE) | ||||
|  | ||||
| GLIB_COMPILE_RESOURCES=`$PKG_CONFIG --variable glib_compile_resources gio-2.0` | ||||
| AC_SUBST(GLIB_COMPILE_RESOURCES) | ||||
|  | ||||
| AC_CHECK_FUNCS(fdwalk) | ||||
| AC_CHECK_FUNCS(mallinfo) | ||||
| AC_CHECK_HEADERS([sys/resource.h]) | ||||
| @@ -162,6 +180,38 @@ if test "$langinfo_ok" = "yes"; then | ||||
|             [Define if _NL_TIME_FIRST_WEEKDAY is available]) | ||||
| fi | ||||
|  | ||||
| AC_ARG_ENABLE(networkmanager, | ||||
|              AS_HELP_STRING([--disable-networkmanager], | ||||
|                             [disable NetworkManager support  @<:@default=auto@:>@]),, | ||||
|               [enable_networkmanager=auto]) | ||||
|  | ||||
| if test "x$enable_networkmanager" != "xno"; then | ||||
|    PKG_CHECK_MODULES(NETWORKMANAGER, | ||||
|                      [libnm-glib | ||||
|                      libnm-util >= $NETWORKMANAGER_MIN_VERSION | ||||
|                      libnm-gtk >= $NETWORKMANAGER_MIN_VERSION | ||||
|                      libsecret-unstable], | ||||
|                      [have_networkmanager=yes], | ||||
|                      [have_networkmanager=no]) | ||||
|  | ||||
|    GNOME_SHELL_CFLAGS="$GNOME_SHELL_CFLAGS $NETWORKMANAGER_CFLAGS" | ||||
|    GNOME_SHELL_LIBS="$GNOME_SHELL_LIBS $NETWORKMANAGER_LIBS" | ||||
| else | ||||
|    have_networkmanager="no  (disabled)" | ||||
| fi | ||||
|  | ||||
| if test "x$have_networkmanager" = "xyes"; then | ||||
|    AC_DEFINE(HAVE_NETWORKMANAGER, [1], [Define if we have NetworkManager]) | ||||
|    AC_SUBST([HAVE_NETWORKMANAGER], [1]) | ||||
| else | ||||
|    if test "x$enable_networkmanager" = "xyes"; then | ||||
|       AC_MSG_ERROR([Couldn't find NetworkManager.]) | ||||
|    fi | ||||
|    AC_SUBST([HAVE_NETWORKMANAGER], [0]) | ||||
| fi | ||||
|  | ||||
| AM_CONDITIONAL(HAVE_NETWORKMANAGER, test "$have_networkmanager" = "yes") | ||||
|  | ||||
| # Sets GLIB_GENMARSHAL and GLIB_MKENUMS | ||||
| AM_PATH_GLIB_2_0() | ||||
|  | ||||
| @@ -203,3 +253,15 @@ AC_CONFIG_FILES([ | ||||
|   man/Makefile | ||||
| ]) | ||||
| AC_OUTPUT | ||||
|  | ||||
| echo " | ||||
| Build configuration: | ||||
|  | ||||
|        Prefix:                                 ${prefix} | ||||
|        Source code location:                   ${srcdir} | ||||
|        Compiler:                               ${CC} | ||||
|        Compiler Warnings:                      $enable_compile_warnings | ||||
|  | ||||
|        Support for NetworkManager:             $have_networkmanager | ||||
|        Support for GStreamer recording:        $build_recorder | ||||
| " | ||||
|   | ||||
| @@ -1,6 +1,3 @@ | ||||
| wandadir = $(pkgdatadir) | ||||
| dist_wanda_DATA = wanda.png | ||||
|  | ||||
| desktopdir=$(datadir)/applications | ||||
| desktop_DATA = gnome-shell.desktop gnome-shell-extension-prefs.desktop | ||||
| if HAVE_MUTTER_WAYLAND | ||||
| @@ -42,6 +39,7 @@ dist_theme_DATA =				\ | ||||
| 	theme/filter-selected-rtl.svg		\ | ||||
| 	theme/gnome-shell.css			\ | ||||
| 	theme/logged-in-indicator.svg		\ | ||||
| 	theme/menu-arrow-symbolic.svg		    \ | ||||
| 	theme/message-tray-background.png	\ | ||||
| 	theme/more-results.svg			\ | ||||
| 	theme/noise-texture.png			\ | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| Type=Application | ||||
| _Name=GNOME Shell (wayland compositor) | ||||
| _Comment=Window management and application launching | ||||
| Exec=@bindir@/mutter-launch -- gnome-shell-wayland --wayland | ||||
| Exec=@bindir@/mutter-launch -- gnome-shell-wayland --wayland --display-server | ||||
| X-GNOME-Bugzilla-Bugzilla=GNOME | ||||
| X-GNOME-Bugzilla-Product=gnome-shell | ||||
| X-GNOME-Bugzilla-Component=general | ||||
|   | ||||
| @@ -13,12 +13,21 @@ | ||||
|     </key> | ||||
|     <key name="enabled-extensions" type="as"> | ||||
|       <default>[]</default> | ||||
|       <_summary>Uuids of extensions to enable</_summary> | ||||
|       <_summary>UUIDs of extensions to enable</_summary> | ||||
|       <_description> | ||||
|         GNOME Shell extensions have a uuid property; this key lists extensions | ||||
|         GNOME Shell extensions have a UUID property; this key lists extensions | ||||
|         which should be loaded. Any extension that wants to be loaded needs | ||||
|         to be in this list. You can also manipulate this list with the | ||||
|         EnableExtension and DisableExtension DBus methods on org.gnome.Shell. | ||||
|         EnableExtension and DisableExtension D-Bus methods on org.gnome.Shell. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="disable-extension-version-validation" type="b"> | ||||
|       <default>false</default> | ||||
|       <_summary>Disables the validation of extension version compatibility</_summary> | ||||
|       <_description> | ||||
|         GNOME Shell will only load extensions that claim to support the current | ||||
|         running version. Enabling this option will disable this check and try to | ||||
|         load all extensions regardless of the versions they claim to support. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="favorite-apps" type="as"> | ||||
| @@ -29,14 +38,6 @@ | ||||
|         will be displayed in the favorites area. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="app-folder-categories" type="as"> | ||||
|       <default>[ 'Utilities', 'Sundry' ]</default> | ||||
|       <_summary>List of categories that should be displayed as folders</_summary> | ||||
|       <_description> | ||||
|         Each category name in this list will be represented as folder in the | ||||
|         application view, rather than being displayed inline in the main view. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="app-picker-view" type="u"> | ||||
|       <default>0</default> | ||||
|       <summary>App Picker View</summary> | ||||
| @@ -54,10 +55,10 @@ | ||||
|     </key> | ||||
|     <key name="always-show-log-out" type="b"> | ||||
|       <default>false</default> | ||||
|       <_summary>Always show the 'Log out' menuitem in the user menu.</_summary> | ||||
|       <_summary>Always show the 'Log out' menu item in the user menu.</_summary> | ||||
|       <_description> | ||||
|         This key overrides the automatic hiding of the 'Log out' | ||||
|         menuitem in single-user, single-session situations. | ||||
|         menu item in single-user, single-session situations. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="remember-mount-password" type="b"> | ||||
| @@ -73,6 +74,7 @@ | ||||
|     <child name="calendar" schema="org.gnome.shell.calendar"/> | ||||
|     <child name="keybindings" schema="org.gnome.shell.keybindings"/> | ||||
|     <child name="keyboard" schema="org.gnome.shell.keyboard"/> | ||||
|     <child name="location" schema="org.gnome.shell.location"/> | ||||
|   </schema> | ||||
|  | ||||
|   <schema id="org.gnome.shell.calendar" path="/org/gnome/shell/calendar/" | ||||
| @@ -124,6 +126,11 @@ | ||||
|         Keybinding to focus the active notification. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="pause-resume-tweens" type="as"> | ||||
|       <default>[]</default> | ||||
|       <summary>Keybinding that pauses and resumes all running tweens, for debugging purposes</summary> | ||||
|       <description></description> | ||||
|     </key> | ||||
|   </schema> | ||||
|  | ||||
|   <schema id="org.gnome.shell.keyboard" path="/org/gnome/shell/keyboard/" | ||||
| @@ -137,6 +144,32 @@ | ||||
|     </key> | ||||
|   </schema> | ||||
|  | ||||
|   <enum id="org.gnome.shell.geoclue.AccuracyLevel"> | ||||
|     <value value="0" nick="off"/> | ||||
|     <value value="1" nick="country"/> | ||||
|     <value value="4" nick="city"/> | ||||
|     <value value="5" nick="neighborhood"/> | ||||
|     <value value="6" nick="street"/> | ||||
|     <value value="8" nick="exact"/> | ||||
|   </enum> | ||||
|   <schema id="org.gnome.shell.location" | ||||
|           path="/org/gnome/shell/location/" | ||||
|           gettext-domain="@GETTEXT_PACKAGE@"> | ||||
|     <key name="max-accuracy-level" enum="org.gnome.shell.geoclue.AccuracyLevel"> | ||||
|       <default>'exact'</default> | ||||
|       <_summary>The maximum accuracy level of location.</_summary> | ||||
|       <_description> | ||||
|         Configures the maximum level of location accuracy applications are | ||||
|         allowed to see. Valid options are 'off' (disable location tracking), | ||||
|         'country', 'city', 'neighborhood', 'street', and 'exact' (typically | ||||
|         requires GPS receiver). Please keep in mind that this only controls | ||||
|         what GeoClue will allow applications to see and they can find user's | ||||
|         location on their own using network resources (albeit with street-level | ||||
|         accuracy at best). | ||||
|       </_description> | ||||
|     </key> | ||||
|   </schema> | ||||
|  | ||||
|   <schema id="org.gnome.shell.app-switcher" | ||||
|           path="/org/gnome/shell/app-switcher/" | ||||
|           gettext-domain="@GETTEXT_PACKAGE@"> | ||||
|   | ||||
| @@ -157,8 +157,9 @@ StScrollBar StButton#vhandle:active { | ||||
|     min-width: 200px; | ||||
| } | ||||
|  | ||||
| .unicode-arrow { | ||||
|     font-size: 120%; | ||||
| .popup-menu-arrow { | ||||
|     width: 16px; | ||||
|     height: 16px; | ||||
| } | ||||
|  | ||||
| .popup-submenu-menu-item:open { | ||||
| @@ -289,6 +290,20 @@ StScrollBar StButton#vhandle:active { | ||||
|     spacing: 10px; | ||||
| } | ||||
|  | ||||
| .nm-dialog-airplane-box { | ||||
|     spacing: 12px; | ||||
| } | ||||
|  | ||||
| .nm-dialog-airplane-headline { | ||||
|     font-size: 1.1em; | ||||
|     font-weight: bold; | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| .nm-dialog-airplane-text { | ||||
|     color: #999999; | ||||
| } | ||||
|  | ||||
| .nm-dialog-header-icon { | ||||
|     icon-size: 32px; | ||||
| } | ||||
| @@ -306,16 +321,13 @@ StScrollBar StButton#vhandle:active { | ||||
|     font-size: 12pt; | ||||
|     border-bottom: 1px solid #666; | ||||
|     padding: 12px; | ||||
| } | ||||
|  | ||||
| .nm-dialog-item:checked { | ||||
|     background-color: #333; | ||||
| } | ||||
|  | ||||
| .nm-dialog-item-box { | ||||
|     spacing: 20px; | ||||
| } | ||||
|  | ||||
| .nm-dialog-item:selected { | ||||
|     background-color: #333; | ||||
| } | ||||
|  | ||||
| .nm-dialog-icons { | ||||
|     spacing: .5em; | ||||
| } | ||||
| @@ -658,7 +670,7 @@ StScrollBar StButton#vhandle:active { | ||||
|     color: #e6e6e6; | ||||
|     border-radius: 32px; /* wish we could do 50% */ | ||||
|     padding: 13px; | ||||
|     border: 1px solid #5f5f5f; /* using rgba() is flaky unfortunately */ | ||||
|     border: 2px solid #5f5f5f; /* using rgba() is flaky unfortunately */ | ||||
| } | ||||
|  | ||||
| .system-menu-action:hover, | ||||
| @@ -666,7 +678,7 @@ StScrollBar StButton#vhandle:active { | ||||
|     color: white; | ||||
|     background-color: #4c4c4c; | ||||
|     border: none; | ||||
|     padding: 14px; | ||||
|     padding: 15px; | ||||
| } | ||||
|  | ||||
| .system-menu-action:active { | ||||
| @@ -967,6 +979,8 @@ StScrollBar StButton#vhandle:active { | ||||
|  | ||||
| .app-folder-icon { | ||||
|     padding: 5px; | ||||
|     spacing-rows: 5px; | ||||
|     spacing-columns: 5px; | ||||
| } | ||||
|  | ||||
| .dash-item-container > StButton { | ||||
| @@ -1292,12 +1306,18 @@ StScrollBar StButton#vhandle:active { | ||||
|     font-weight: bold; | ||||
|     text-align: center; | ||||
|     color: #eeeeec; | ||||
|     border-radius: 4px; | ||||
| } | ||||
|  | ||||
| .datemenu-date-label:hover, | ||||
| .datemenu-date-label:focus { | ||||
|     background-color: #999999; | ||||
| } | ||||
|  | ||||
| .datemenu-date-label:active { | ||||
|     background-color: #aaaaaa; | ||||
| } | ||||
|  | ||||
| .calendar-day-base { | ||||
|     font-size: 9pt; | ||||
|     text-align: center; | ||||
| @@ -1456,6 +1476,10 @@ StScrollBar StButton#vhandle:active { | ||||
|     color: #999999; | ||||
| } | ||||
|  | ||||
| .no-networks-box { | ||||
|     spacing: 12px; | ||||
| } | ||||
|  | ||||
| .notification { | ||||
|     border-radius: 10px 10px 0px 0px; | ||||
|     background: rgba(0,0,0,0.9); | ||||
| @@ -1633,8 +1657,8 @@ StScrollBar StButton#vhandle:active { | ||||
|     color: #888888; | ||||
| } | ||||
|  | ||||
| .chat-group-sent, .chat-group-meta { | ||||
|     padding: 8px 0; | ||||
| .chat-empty-line { | ||||
|     font-size: 4px; | ||||
| } | ||||
|  | ||||
| .chat-received { | ||||
| @@ -1659,6 +1683,7 @@ StScrollBar StButton#vhandle:active { | ||||
| .chat-meta-message { | ||||
|     padding-left: 4px; | ||||
|     font-size: 9pt; | ||||
|     font-weight: bold; | ||||
|     color: #bbbbbb; | ||||
| } | ||||
|  | ||||
| @@ -1860,6 +1885,27 @@ StScrollBar StButton#vhandle:active { | ||||
|     border-radius: 8px; | ||||
| } | ||||
|  | ||||
| /* Tile previews */ | ||||
| .tile-preview { | ||||
|     background-color: rgba(74, 144, 217, 0.35); | ||||
|     border: 1px solid #4a90d9; /* Adwaita selected bg color */ | ||||
| } | ||||
|  | ||||
| .tile-preview-left.on-primary { | ||||
|     /* keep in sync with -panel-corner-radius */ | ||||
|     border-radius: 6px 0 0 0; | ||||
| } | ||||
|  | ||||
| .tile-preview-right.on-primary { | ||||
|     /* keep in sync with -panel-corner-radius */ | ||||
|     border-radius: 0 6px 0 0; | ||||
| } | ||||
|  | ||||
| .tile-preview-left.tile-preview-right.on-primary { | ||||
|     /* keep in sync with -panel-corner-radius */ | ||||
|     border-radius: 6px 6px 0 0; | ||||
| } | ||||
|  | ||||
| /* Modal Dialogs */ | ||||
|  | ||||
| /* Dialog Subject Text Style */ | ||||
| @@ -1930,50 +1976,72 @@ StScrollBar StButton#vhandle:active { | ||||
|     padding-top: 20px; | ||||
| } | ||||
|  | ||||
| .end-session-dialog-subject { | ||||
| .end-session-dialog-layout { | ||||
|     padding-left: 17px; | ||||
|     padding-bottom: 20px; | ||||
| } | ||||
|  | ||||
| .end-session-dialog-subject:rtl { | ||||
|     padding-left: 0px; | ||||
| .end-session-dialog-layout:rtl { | ||||
|     padding-right: 17px; | ||||
| } | ||||
|  | ||||
| .end-session-dialog-description { | ||||
|     padding-left: 17px; | ||||
|     width: 28em; | ||||
|     padding-bottom: 10px; | ||||
| } | ||||
|  | ||||
| .end-session-dialog-description:rtl { | ||||
|     padding-right: 17px; | ||||
|     width: 28em; | ||||
|     padding-bottom: 10px; | ||||
|     text-align: right; | ||||
| } | ||||
|  | ||||
| .end-session-dialog-warning { | ||||
|     width: 28em; | ||||
|     color: #f57900; | ||||
|     padding-top: 6px; | ||||
| } | ||||
|  | ||||
| .end-session-dialog-warning:rtl { | ||||
|     width: 28em; | ||||
|     color: #f57900; | ||||
|     padding-top: 6px; | ||||
|     text-align: right; | ||||
| } | ||||
|  | ||||
| .end-session-dialog-logout-icon { | ||||
|     border: 2px solid #8b8b8b; | ||||
|     border-radius: 5px; | ||||
|     width: 32px; | ||||
|     height: 32px; | ||||
|     width: 48px; | ||||
|     height: 48px; | ||||
|     background-size: contain; | ||||
| } | ||||
|  | ||||
| .end-session-dialog-shutdown-icon { | ||||
|     color: #bebebe; | ||||
|     width: 32px; | ||||
|     height: 32px; | ||||
|     width: 48px; | ||||
|     height: 48px; | ||||
| } | ||||
|  | ||||
| .end-session-dialog-inhibitor-layout { | ||||
|     spacing: 16px; | ||||
|     max-height: 200px; | ||||
|     padding-right: 50px; | ||||
|     padding-left: 50px; | ||||
|     padding-right: 65px; | ||||
|     padding-left: 65px; | ||||
| } | ||||
|  | ||||
| .end-session-dialog-session-list, | ||||
| .end-session-dialog-app-list { | ||||
|     spacing: 1em; | ||||
| } | ||||
|  | ||||
| .end-session-dialog-list-header { | ||||
|     font-weight: bold; | ||||
| } | ||||
|  | ||||
| .end-session-dialog-list-header:rtl { | ||||
|     text-align: right; | ||||
| } | ||||
|  | ||||
| .end-session-dialog-app-list-item, | ||||
| .end-session-dialog-session-list-item { | ||||
|     spacing: 1em; | ||||
| @@ -2093,6 +2161,10 @@ StScrollBar StButton#vhandle:active { | ||||
|     color: #666666; | ||||
| } | ||||
|  | ||||
| .prompt-dialog-description:rtl { | ||||
|     text-align: right; | ||||
| } | ||||
|  | ||||
| .prompt-dialog-password-box { | ||||
|     spacing: 1em; | ||||
|     padding-bottom: 1em; | ||||
| @@ -2319,6 +2391,8 @@ StScrollBar StButton#vhandle:active { | ||||
| .login-dialog-user-list-item { | ||||
|     border-radius: 5px; | ||||
|     padding: .2em; | ||||
|     color: #bfbfbf; | ||||
|     text-shadow: black 0px 2px 2px; | ||||
| } | ||||
|  | ||||
| .login-dialog-user-list-item:ltr { | ||||
| @@ -2329,24 +2403,6 @@ StScrollBar StButton#vhandle:active { | ||||
|     padding-left: 1em; | ||||
| } | ||||
|  | ||||
| .login-dialog-user-list-item .login-dialog-user-list-item-name { | ||||
|     font-size: 20px; | ||||
|     padding-left: 18px; | ||||
|     font-weight: bold; | ||||
| } | ||||
|  | ||||
| .login-dialog-user-list:expanded .login-dialog-user-list-item { | ||||
|     color: #bfbfbf; | ||||
| } | ||||
|  | ||||
| .login-dialog-user-list-item, | ||||
| .login-dialog-user-list-item:hover .login-dialog-user-list-item-name, | ||||
| .login-dialog-user-list:expanded .login-dialog-user-list-item:focus .login-dialog-user-list-item-name, | ||||
| .login-dialog-user-list:expanded .login-dialog-user-list-item:logged-in { | ||||
|     color: #bfbfbf; | ||||
|     text-shadow: black 0px 2px 2px; | ||||
| } | ||||
|  | ||||
| .login-dialog-user-list-item:hover { | ||||
|     background-color: rgba(255,255,255,0.1); | ||||
| } | ||||
| @@ -2373,13 +2429,6 @@ StScrollBar StButton#vhandle:active { | ||||
|     background-color: #8b8b8b; | ||||
| } | ||||
|  | ||||
| .login-dialog-user-list-item-icon { | ||||
|     border: 2px solid #8b8b8b; | ||||
|     border-radius: 3px; | ||||
|     width: 64px; | ||||
|     height: 64px; | ||||
| } | ||||
|  | ||||
| .login-dialog-not-listed-label { | ||||
|     font-size: 10.5pt; | ||||
|     font-weight: bold; | ||||
| @@ -2387,6 +2436,10 @@ StScrollBar StButton#vhandle:active { | ||||
|     padding-top: 1em; | ||||
| } | ||||
|  | ||||
| .login-dialog-user-selection-box { | ||||
|     padding: 100px 0; | ||||
| } | ||||
|  | ||||
| .login-dialog-user-selection-box .login-dialog-not-listed-label { | ||||
|     padding-left: 2px; | ||||
| } | ||||
| @@ -2396,12 +2449,13 @@ StScrollBar StButton#vhandle:active { | ||||
|     color: #E8E8E8; | ||||
| } | ||||
|  | ||||
| .login-dialog-username { | ||||
| .login-dialog-username, | ||||
| .user-widget-label { | ||||
|     font-size: 16pt; | ||||
|     font-weight: bold; | ||||
|     text-align: left; | ||||
|     padding-left: 15px; | ||||
|     text-shadow: black 0px 4px 3px 0px; | ||||
|     text-shadow: rgba(0, 0, 0, 0.5) 0px 2px 1px 0px; | ||||
| } | ||||
|  | ||||
| .login-dialog-prompt-layout { | ||||
| @@ -2426,6 +2480,7 @@ StScrollBar StButton#vhandle:active { | ||||
| } | ||||
|  | ||||
| .login-dialog-session-list-button:hover, | ||||
| .login-dialog-session-list-button:focus, | ||||
| .login-dialog-session-list-button:active { | ||||
|     color: white; | ||||
| } | ||||
| @@ -2492,12 +2547,14 @@ StScrollBar StButton#vhandle:active { | ||||
| } | ||||
|  | ||||
| .user-widget-label { | ||||
|     font-size: 20px; | ||||
|     font-weight: bold; | ||||
|     text-align: left; | ||||
| } | ||||
|  | ||||
| .user-widget-label:ltr { | ||||
|     padding-left: 18px; | ||||
|     color:white; | ||||
|     text-shadow: black 0px 4px 3px 0px; | ||||
| } | ||||
|  | ||||
| .user-widget-label:rtl { | ||||
|     padding-right: 18px; | ||||
| } | ||||
|  | ||||
| /* Screen shield */ | ||||
| @@ -2624,4 +2681,5 @@ StScrollBar StButton#vhandle:active { | ||||
|  | ||||
| .background-menu { | ||||
|     -boxpointer-gap: 4px; | ||||
|     -arrow-rise: 0px; | ||||
| } | ||||
|   | ||||
							
								
								
									
										90
									
								
								data/theme/menu-arrow-symbolic.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								data/theme/menu-arrow-symbolic.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||||
|  | ||||
| <svg | ||||
|    xmlns:dc="http://purl.org/dc/elements/1.1/" | ||||
|    xmlns:cc="http://creativecommons.org/ns#" | ||||
|    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | ||||
|    xmlns:svg="http://www.w3.org/2000/svg" | ||||
|    xmlns="http://www.w3.org/2000/svg" | ||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||
|    width="16" | ||||
|    height="16" | ||||
|    id="svg3863" | ||||
|    version="1.1" | ||||
|    inkscape:version="0.48.4 r9939" | ||||
|    sodipodi:docname="menu-arrow.svg"> | ||||
|   <defs | ||||
|      id="defs3865" /> | ||||
|   <sodipodi:namedview | ||||
|      id="base" | ||||
|      pagecolor="#ffffff" | ||||
|      bordercolor="#666666" | ||||
|      borderopacity="1.0" | ||||
|      inkscape:pageopacity="0.0" | ||||
|      inkscape:pageshadow="2" | ||||
|      inkscape:zoom="15.836083" | ||||
|      inkscape:cx="-3.1641676" | ||||
|      inkscape:cy="11.823817" | ||||
|      inkscape:current-layer="layer1" | ||||
|      showgrid="true" | ||||
|      inkscape:grid-bbox="true" | ||||
|      inkscape:document-units="px" | ||||
|      showguides="true" | ||||
|      inkscape:guide-bbox="true" | ||||
|      inkscape:window-width="1366" | ||||
|      inkscape:window-height="702" | ||||
|      inkscape:window-x="0" | ||||
|      inkscape:window-y="27" | ||||
|      inkscape:window-maximized="1" | ||||
|      inkscape:snap-bbox="true"> | ||||
|     <sodipodi:guide | ||||
|        orientation="1,0" | ||||
|        position="15.996443,16.922964" | ||||
|        id="guide3873" /> | ||||
|     <sodipodi:guide | ||||
|        orientation="0,1" | ||||
|        position="28.041217,3.1256134" | ||||
|        id="guide3875" /> | ||||
|     <sodipodi:guide | ||||
|        orientation="0,1" | ||||
|        position="-0.80372916,24.469088" | ||||
|        id="guide3877" /> | ||||
|     <sodipodi:guide | ||||
|        orientation="1,0" | ||||
|        position="3.0363102,34.649657" | ||||
|        id="guide3879" /> | ||||
|     <sodipodi:guide | ||||
|        orientation="1,0" | ||||
|        position="29.023553,28.577037" | ||||
|        id="guide3881" /> | ||||
|     <inkscape:grid | ||||
|        type="xygrid" | ||||
|        id="grid2988" /> | ||||
|   </sodipodi:namedview> | ||||
|   <metadata | ||||
|      id="metadata3868"> | ||||
|     <rdf:RDF> | ||||
|       <cc:Work | ||||
|          rdf:about=""> | ||||
|         <dc:format>image/svg+xml</dc:format> | ||||
|         <dc:type | ||||
|            rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | ||||
|         <dc:title></dc:title> | ||||
|       </cc:Work> | ||||
|     </rdf:RDF> | ||||
|   </metadata> | ||||
|   <g | ||||
|      id="layer1" | ||||
|      inkscape:label="Layer 1" | ||||
|      inkscape:groupmode="layer" | ||||
|      transform="translate(0,-16)"> | ||||
|     <path | ||||
|        style="fill:#ffffff;fill-opacity:1;stroke:none" | ||||
|        d="m 4,23 8,0 -4,5 z" | ||||
|        id="path3883" | ||||
|        inkscape:connector-curvature="0" | ||||
|        sodipodi:nodetypes="cccc" /> | ||||
|   </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 2.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								data/wanda.png
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								data/wanda.png
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 13 KiB | 
| @@ -112,7 +112,7 @@ expand_content_files= | ||||
| # e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS) | ||||
| # e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib) | ||||
| GTKDOC_CFLAGS=$(GNOME_SHELL_CFLAGS) | ||||
| GTKDOC_LIBS=$(GNOME_SHELL_LIBS) $(BLUETOOTH_LIBS) $(top_builddir)/src/libgnome-shell-menu.la $(top_builddir)/src/libgnome-shell-base.la $(top_builddir)/src/libgnome-shell.la | ||||
| GTKDOC_LIBS=$(GNOME_SHELL_LIBS) $(top_builddir)/src/libgnome-shell-menu.la $(top_builddir)/src/libgnome-shell-base.la $(top_builddir)/src/libgnome-shell.la | ||||
|  | ||||
| # This includes the standard gtk-doc make rules, copied by gtkdocize. | ||||
| include $(top_srcdir)/gtk-doc.make | ||||
|   | ||||
| @@ -17,17 +17,15 @@ packages. If you are interested in building GNOME Shell from source, | ||||
| we would recommend building from version control using the build | ||||
| script described at: | ||||
|  | ||||
|  http://live.gnome.org/GnomeShell | ||||
|  https://wiki.gnome.org/Projects/GnomeShell | ||||
|  | ||||
| Not only will that give you the very latest version of this rapidly | ||||
| changing project, it will be much easier than get GNOME Shell and | ||||
| its dependencies to build from tarballs.</description> | ||||
|   <!-- | ||||
|   <homepage rdf:resource="http://live.gnome.org/GnomeShell" /> | ||||
|   --> | ||||
|   <homepage rdf:resource="https://wiki.gnome.org/Projects/GnomeShell" /> | ||||
|   <mailing-list rdf:resource="http://mail.gnome.org/mailman/listinfo/gnome-shell-list" /> | ||||
|   <download-page rdf:resource="http://download.gnome.org/sources/gnome-shell/" /> | ||||
|   <bug-database rdf:resource="http://bugzilla.gnome.org/browse.cgi?product=gnome-shell" /> | ||||
|   <bug-database rdf:resource="https://bugzilla.gnome.org/browse.cgi?product=gnome-shell" /> | ||||
|  | ||||
|   <category rdf:resource="http://api.gnome.org/doap-extensions#desktop" /> | ||||
|  | ||||
|   | ||||
							
								
								
									
										133
									
								
								js/Makefile.am
									
									
									
									
									
								
							
							
						
						
									
										133
									
								
								js/Makefile.am
									
									
									
									
									
								
							| @@ -1,125 +1,38 @@ | ||||
| NULL = | ||||
|  | ||||
| EXTRA_DIST = misc/config.js.in | ||||
| CLEANFILES = misc/config.js | ||||
| BUILT_SOURCES = | ||||
|  | ||||
| misc/config.js: misc/config.js.in Makefile | ||||
| 	[ -d $(@D) ] || $(mkdir_p) $(@D) ; \ | ||||
| 	sed -e "s|[@]PACKAGE_NAME@|$(PACKAGE_NAME)|g" \ | ||||
| 	    -e "s|[@]PACKAGE_VERSION@|$(PACKAGE_VERSION)|g" \ | ||||
| 	    -e "s|[@]HAVE_BLUETOOTH@|$(HAVE_BLUETOOTH)|g" \ | ||||
| 	    -e "s|[@]HAVE_NETWORKMANAGER@|$(HAVE_NETWORKMANAGER)|g" \ | ||||
| 	    -e "s|[@]GETTEXT_PACKAGE@|$(GETTEXT_PACKAGE)|g" \ | ||||
| 	    -e "s|[@]datadir@|$(datadir)|g" \ | ||||
| 	    -e "s|[@]libexecdir@|$(libexecdir)|g" \ | ||||
| 	    -e "s|[@]sysconfdir@|$(sysconfdir)|g" \ | ||||
|                $< > $@ | ||||
|  | ||||
| jsdir = $(pkgdatadir)/js | ||||
| js_resource_files = $(shell $(GLIB_COMPILE_RESOURCES) --sourcedir=$(srcdir) --generate-dependencies $(srcdir)/js-resources.gresource.xml) | ||||
| js-resources.h: js-resources.gresource.xml $(js_resource_files) misc/config.js | ||||
| 	$(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) --target=$@ --sourcedir=$(srcdir) --sourcedir=$(builddir) --generate --c-name shell_js_resources $< | ||||
| js-resources.c: js-resources.gresource.xml $(js_resource_files) misc/config.js | ||||
| 	$(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) --target=$@ --sourcedir=$(srcdir) --sourcedir=$(builddir) --generate --c-name shell_js_resources $< | ||||
|  | ||||
| nobase_dist_js_DATA = 	\ | ||||
| 	gdm/authPrompt.js	\ | ||||
| 	gdm/batch.js		\ | ||||
| 	gdm/fingerprint.js	\ | ||||
| 	gdm/loginDialog.js	\ | ||||
| 	gdm/oVirt.js		\ | ||||
| 	gdm/realmd.js		\ | ||||
| 	gdm/util.js		\ | ||||
| 	extensionPrefs/main.js	\ | ||||
| 	misc/config.js		\ | ||||
| 	misc/extensionUtils.js	\ | ||||
| 	misc/fileUtils.js	\ | ||||
| 	misc/gnomeSession.js	\ | ||||
| 	misc/hash.js		\ | ||||
| 	misc/history.js		\ | ||||
| 	misc/jsParse.js		\ | ||||
| 	misc/loginManager.js	\ | ||||
| 	misc/modemManager.js	\ | ||||
| 	misc/objectManager.js	\ | ||||
| 	misc/params.js		\ | ||||
| 	misc/smartcardManager.js \ | ||||
| 	misc/util.js		\ | ||||
| 	perf/core.js		\ | ||||
| 	ui/altTab.js		\ | ||||
| 	ui/animation.js		\ | ||||
| 	ui/appDisplay.js	\ | ||||
| 	ui/appFavorites.js	\ | ||||
| 	ui/backgroundMenu.js	\ | ||||
| 	ui/background.js	\ | ||||
| 	ui/boxpointer.js	\ | ||||
| 	ui/calendar.js		\ | ||||
| 	ui/checkBox.js		\ | ||||
| 	ui/ctrlAltTab.js	\ | ||||
| 	ui/dash.js		\ | ||||
| 	ui/dateMenu.js		\ | ||||
| 	ui/dnd.js		\ | ||||
| 	ui/endSessionDialog.js	\ | ||||
| 	ui/extensionSystem.js	\ | ||||
| 	ui/extensionDownloader.js \ | ||||
| 	ui/environment.js	\ | ||||
| 	ui/focusCaretTracker.js\ | ||||
| 	ui/ibusCandidatePopup.js\ | ||||
| 	ui/grabHelper.js	\ | ||||
| 	ui/iconGrid.js		\ | ||||
| 	ui/keyboard.js		\ | ||||
| 	ui/layout.js		\ | ||||
| 	ui/lightbox.js		\ | ||||
| 	ui/lookingGlass.js	\ | ||||
| 	ui/magnifier.js		\ | ||||
| 	ui/magnifierDBus.js	\ | ||||
| 	ui/main.js		\ | ||||
| 	ui/messageTray.js	\ | ||||
| 	ui/modalDialog.js	\ | ||||
| 	ui/separator.js		\ | ||||
| 	ui/sessionMode.js	\ | ||||
| 	ui/shellEntry.js	\ | ||||
| 	ui/shellMountOperation.js \ | ||||
| 	ui/slider.js		\ | ||||
| 	ui/notificationDaemon.js \ | ||||
| 	ui/osdWindow.js		\ | ||||
| 	ui/overview.js		\ | ||||
| 	ui/overviewControls.js	\ | ||||
| 	ui/panel.js		\ | ||||
| 	ui/panelMenu.js		\ | ||||
| 	ui/pointerWatcher.js    \ | ||||
| 	ui/popupMenu.js		\ | ||||
| 	ui/remoteSearch.js	\ | ||||
| 	ui/remoteMenu.js	\ | ||||
| 	ui/runDialog.js		\ | ||||
| 	ui/screencast.js	\ | ||||
| 	ui/screenshot.js	\ | ||||
|         ui/screenShield.js	\ | ||||
| 	ui/scripting.js		\ | ||||
| 	ui/search.js		\ | ||||
| 	ui/searchDisplay.js	\ | ||||
| 	ui/shellDBus.js		\ | ||||
| 	ui/status/accessibility.js	\ | ||||
| 	ui/status/brightness.js	\ | ||||
| 	ui/status/keyboard.js	\ | ||||
| 	ui/status/network.js	\ | ||||
| 	ui/status/power.js	\ | ||||
| 	ui/status/rfkill.js	\ | ||||
| 	ui/status/volume.js	\ | ||||
| 	ui/status/bluetooth.js	\ | ||||
| 	ui/status/screencast.js	\ | ||||
| 	ui/status/system.js	\ | ||||
| 	ui/switcherPopup.js	\ | ||||
| 	ui/tweener.js		\ | ||||
| 	ui/unlockDialog.js	\ | ||||
| 	ui/userWidget.js	\ | ||||
| 	ui/viewSelector.js	\ | ||||
| 	ui/wanda.js		\ | ||||
| 	ui/windowAttentionHandler.js	\ | ||||
| 	ui/windowManager.js	\ | ||||
| 	ui/workspace.js		\ | ||||
| 	ui/workspaceThumbnail.js	\ | ||||
| 	ui/workspacesView.js	\ | ||||
| 	ui/workspaceSwitcherPopup.js    \ | ||||
| 	ui/xdndHandler.js	\ | ||||
| 	ui/components/__init__.js		\ | ||||
| 	ui/components/autorunManager.js		\ | ||||
| 	ui/components/automountManager.js	\ | ||||
| 	ui/components/networkAgent.js		\ | ||||
| 	ui/components/polkitAgent.js		\ | ||||
| 	ui/components/telepathyClient.js	\ | ||||
| 	ui/components/keyring.js		\ | ||||
| js_built_sources = js-resources.c js-resources.h | ||||
|  | ||||
| BUILT_SOURCES += $(js_built_sources) | ||||
|  | ||||
| all-local: $(js_built_sources) | ||||
|  | ||||
| js_resource_dist_files = $(filter-out misc/config.js, $(js_resource_files)) | ||||
|  | ||||
| EXTRA_DIST = \ | ||||
| 	$(js_resource_dist_files) \ | ||||
| 	js-resources.gresource.xml \ | ||||
| 	misc/config.js.in \ | ||||
| 	$(NULL) | ||||
|  | ||||
| CLEANFILES = \ | ||||
| 	$(js_built_sources) \ | ||||
| 	$(NULL) | ||||
|   | ||||
| @@ -13,13 +13,15 @@ const _ = Gettext.gettext; | ||||
| const Config = imports.misc.config; | ||||
| const ExtensionUtils = imports.misc.extensionUtils; | ||||
|  | ||||
| const GnomeShellIface = <interface name="org.gnome.Shell.Extensions"> | ||||
| <signal name="ExtensionStatusChanged"> | ||||
|     <arg type="s" name="uuid"/> | ||||
|     <arg type="i" name="state"/> | ||||
|     <arg type="s" name="error"/> | ||||
| </signal> | ||||
| </interface>; | ||||
| const GnomeShellIface = '<node> \ | ||||
| <interface name="org.gnome.Shell.Extensions"> \ | ||||
| <signal name="ExtensionStatusChanged"> \ | ||||
|     <arg type="s" name="uuid"/> \ | ||||
|     <arg type="i" name="state"/> \ | ||||
|     <arg type="s" name="error"/> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const GnomeShellProxy = Gio.DBusProxy.makeProxyWrapper(GnomeShellIface); | ||||
|  | ||||
| @@ -204,11 +206,11 @@ const Application = new Lang.Class({ | ||||
|     _scanExtensions: function() { | ||||
|         let finder = new ExtensionUtils.ExtensionFinder(); | ||||
|         finder.connect('extension-found', Lang.bind(this, this._extensionFound)); | ||||
|         finder.connect('extensions-loaded', Lang.bind(this, this._extensionsLoaded)); | ||||
|         finder.scanExtensions(); | ||||
|         this._extensionsLoaded(); | ||||
|     }, | ||||
|  | ||||
|     _extensionFound: function(signals, extension) { | ||||
|     _extensionFound: function(finder, extension) { | ||||
|         let iter = this._model.append(); | ||||
|         this._model.set(iter, [0, 1], [extension.uuid, extension.metadata.name]); | ||||
|         this._extensionIters[extension.uuid] = iter; | ||||
|   | ||||
| @@ -80,6 +80,7 @@ const AuthPrompt = new Lang.Class({ | ||||
|                                if (event.get_key_symbol() == Clutter.KEY_Escape) { | ||||
|                                    this.cancel(); | ||||
|                                } | ||||
|                                return Clutter.EVENT_PROPAGATE; | ||||
|                            })); | ||||
|  | ||||
|         this._userWell = new St.Bin({ x_fill: true, | ||||
| @@ -93,7 +94,7 @@ const AuthPrompt = new Lang.Class({ | ||||
|  | ||||
|         this.actor.add(this._label, | ||||
|                        { expand: true, | ||||
|                          x_fill: true, | ||||
|                          x_fill: false, | ||||
|                          y_fill: true, | ||||
|                          x_align: St.Align.START }); | ||||
|         this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry', | ||||
| @@ -111,7 +112,7 @@ const AuthPrompt = new Lang.Class({ | ||||
|         this._message = new St.Label({ opacity: 0, | ||||
|                                        styleClass: 'login-dialog-message' }); | ||||
|         this._message.clutter_text.line_wrap = true; | ||||
|         this.actor.add(this._message, { x_fill: true, y_align: St.Align.START }); | ||||
|         this.actor.add(this._message, { x_fill: false, x_align: St.Align.START, y_align: St.Align.START }); | ||||
|  | ||||
|         this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box', | ||||
|                                              vertical: false }); | ||||
| @@ -263,10 +264,8 @@ const AuthPrompt = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _onReset: function() { | ||||
|         if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) { | ||||
|             this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; | ||||
|             this.reset(); | ||||
|         } | ||||
|         this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; | ||||
|         this.reset(); | ||||
|     }, | ||||
|  | ||||
|     addActorToDefaultButtonWell: function(actor) { | ||||
| @@ -451,8 +450,7 @@ const AuthPrompt = new Lang.Class({ | ||||
|             // respond to the request with the username | ||||
|             beginRequestType = BeginRequestType.PROVIDE_USERNAME; | ||||
|         } else if (this._userVerifier.serviceIsForeground(GdmUtil.OVIRT_SERVICE_NAME) || | ||||
|                    (this.smartcardDetected && | ||||
|                     this._userVerifier.serviceIsForeground(GdmUtil.SMARTCARD_SERVICE_NAME))) { | ||||
|                    this._userVerifier.serviceIsForeground(GdmUtil.SMARTCARD_SERVICE_NAME)) { | ||||
|             // We don't need to know the username if the user preempted the login screen | ||||
|             // with a smartcard or with preauthenticated oVirt credentials | ||||
|             beginRequestType = BeginRequestType.DONT_PROVIDE_USERNAME; | ||||
|   | ||||
| @@ -13,9 +13,7 @@ | ||||
|  * GNU General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program; if not, write to the Free Software | ||||
|  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA | ||||
|  * 02111-1307, USA. | ||||
|  * along with this program; if not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| const Lang = imports.lang; | ||||
|   | ||||
| @@ -5,11 +5,13 @@ const Lang = imports.lang; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const FprintManagerIface = <interface name='net.reactivated.Fprint.Manager'> | ||||
| <method name='GetDefaultDevice'> | ||||
|     <arg type='o' direction='out' /> | ||||
| </method> | ||||
| </interface>; | ||||
| const FprintManagerIface = '<node> \ | ||||
| <interface name="net.reactivated.Fprint.Manager"> \ | ||||
| <method name="GetDefaultDevice"> \ | ||||
|     <arg type="o" direction="out" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const FprintManagerInfo = Gio.DBusInterfaceInfo.new_for_xml(FprintManagerIface); | ||||
|  | ||||
|   | ||||
| @@ -13,9 +13,7 @@ | ||||
|  * GNU General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program; if not, write to the Free Software | ||||
|  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA | ||||
|  * 02111-1307, USA. | ||||
|  * along with this program; if not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| const AccountsService = imports.gi.AccountsService; | ||||
| @@ -38,6 +36,7 @@ const BoxPointer = imports.ui.boxpointer; | ||||
| const CtrlAltTab = imports.ui.ctrlAltTab; | ||||
| const GdmUtil = imports.gdm.util; | ||||
| const Layout = imports.ui.layout; | ||||
| const LoginManager = imports.misc.loginManager; | ||||
| const Main = imports.ui.main; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
| const Realmd = imports.gdm.realmd; | ||||
| @@ -59,7 +58,7 @@ const UserListItem = new Lang.Class({ | ||||
|         this._userChangedId = this.user.connect('changed', | ||||
|                                                  Lang.bind(this, this._onUserChanged)); | ||||
|  | ||||
|         let layout = new St.BoxLayout({ vertical: false }); | ||||
|         let layout = new St.BoxLayout({ vertical: true }); | ||||
|         this.actor = new St.Button({ style_class: 'login-dialog-user-list-item', | ||||
|                                      button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, | ||||
|                                      can_focus: true, | ||||
| @@ -68,39 +67,18 @@ const UserListItem = new Lang.Class({ | ||||
|                                      x_align: St.Align.START, | ||||
|                                      x_fill: true }); | ||||
|  | ||||
|         this._userAvatar = new UserWidget.Avatar(this.user, | ||||
|                                                  { styleClass: 'login-dialog-user-list-item-icon' }); | ||||
|         layout.add(this._userAvatar.actor); | ||||
|         let textLayout = new St.BoxLayout({ style_class: 'login-dialog-user-list-item-text-box', | ||||
|                                             vertical:    true }); | ||||
|         layout.add(textLayout, { expand: true }); | ||||
|  | ||||
|         this._nameLabel = new St.Label({ style_class: 'login-dialog-user-list-item-name' }); | ||||
|         this.actor.label_actor = this._nameLabel; | ||||
|         textLayout.add(this._nameLabel, | ||||
|                        { y_fill: false, | ||||
|                          y_align: St.Align.MIDDLE, | ||||
|                          expand: true }); | ||||
|         this._userWidget = new UserWidget.UserWidget(this.user); | ||||
|         layout.add(this._userWidget.actor); | ||||
|  | ||||
|         this._timedLoginIndicator = new St.Bin({ style_class: 'login-dialog-timed-login-indicator', | ||||
|                                                  scale_x: 0 }); | ||||
|         textLayout.add(this._timedLoginIndicator, | ||||
|                        { x_fill: true, | ||||
|                          x_align: St.Align.MIDDLE, | ||||
|                          y_fill: false, | ||||
|                          y_align: St.Align.END }); | ||||
|         layout.add(this._timedLoginIndicator); | ||||
|  | ||||
|         this.actor.connect('clicked', Lang.bind(this, this._onClicked)); | ||||
|         this._onUserChanged(); | ||||
|     }, | ||||
|  | ||||
|     _onUserChanged: function() { | ||||
|         this._nameLabel.set_text(this.user.get_real_name()); | ||||
|         this._userAvatar.update(); | ||||
|         this._updateLoggedIn(); | ||||
|     }, | ||||
|  | ||||
|     syncStyleClasses: function() { | ||||
|         this._updateLoggedIn(); | ||||
|     }, | ||||
|  | ||||
| @@ -189,7 +167,6 @@ const UserList = new Lang.Class({ | ||||
|         for (let userName in this._items) { | ||||
|             let item = this._items[userName]; | ||||
|             item.actor.sync_hover(); | ||||
|             item.syncStyleClasses(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -478,18 +455,6 @@ const LoginDialog = new Lang.Class({ | ||||
|         this.actor.add_child(this._logoBin); | ||||
|         this._updateLogo(); | ||||
|  | ||||
|         if (!this._userManager.is_loaded) | ||||
|             this._userManagerLoadedId = this._userManager.connect('notify::is-loaded', | ||||
|                                                                   Lang.bind(this, function() { | ||||
|                                                                       if (this._userManager.is_loaded) { | ||||
|                                                                           this._loadUserList(); | ||||
|                                                                           this._userManager.disconnect(this._userManagerLoadedId); | ||||
|                                                                           this._userManagerLoadedId = 0; | ||||
|                                                                       } | ||||
|                                                                   })); | ||||
|         else | ||||
|             this._loadUserList(); | ||||
|  | ||||
|         this._userList.connect('activate', | ||||
|                                Lang.bind(this, function(userList, item) { | ||||
|                                    this._onUserListActivated(item); | ||||
| @@ -505,7 +470,31 @@ const LoginDialog = new Lang.Class({ | ||||
|         this._sessionMenuButton.actor.show(); | ||||
|         this._authPrompt.addActorToDefaultButtonWell(this._sessionMenuButton.actor); | ||||
|  | ||||
|    }, | ||||
|         this._disableUserList = undefined; | ||||
|         this._userListLoaded = false; | ||||
|  | ||||
|         LoginManager.getLoginManager().getCurrentSessionProxy(Lang.bind(this, this._gotGreeterSessionProxy)); | ||||
|  | ||||
|         // If the user list is enabled, it should take key focus; make sure the | ||||
|         // screen shield is initialized first to prevent it from stealing the | ||||
|         // focus later | ||||
|         Main.layoutManager.connect('startup-complete', | ||||
|                                    Lang.bind(this, this._updateDisableUserList)); | ||||
|     }, | ||||
|  | ||||
|     _ensureUserListLoaded: function() { | ||||
|         if (!this._userManager.is_loaded) | ||||
|             this._userManagerLoadedId = this._userManager.connect('notify::is-loaded', | ||||
|                                                                   Lang.bind(this, function() { | ||||
|                                                                       if (this._userManager.is_loaded) { | ||||
|                                                                           this._loadUserList(); | ||||
|                                                                           this._userManager.disconnect(this._userManagerLoadedId); | ||||
|                                                                           this._userManagerLoadedId = 0; | ||||
|                                                                       } | ||||
|                                                                   })); | ||||
|         else | ||||
|             GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, this._loadUserList)); | ||||
|     }, | ||||
|  | ||||
|     _updateDisableUserList: function() { | ||||
|         let disableUserList = this._settings.get_boolean(GdmUtil.DISABLE_USER_LIST_KEY); | ||||
| @@ -548,9 +537,12 @@ const LoginDialog = new Lang.Class({ | ||||
|             return; | ||||
|  | ||||
|         this._logoBin.destroy_all_children(); | ||||
|         if (this._logoFileUri) | ||||
|         if (this._logoFileUri) { | ||||
|             let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; | ||||
|             this._logoBin.add_child(this._textureCache.load_uri_async(this._logoFileUri, | ||||
|                                                                       -1, _LOGO_ICON_HEIGHT)); | ||||
|                                                                       -1, _LOGO_ICON_HEIGHT, | ||||
|                                                                       scaleFactor)); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateLogo: function() { | ||||
| @@ -649,6 +641,36 @@ const LoginDialog = new Lang.Class({ | ||||
|         this._showPrompt(); | ||||
|     }, | ||||
|  | ||||
|     _loginScreenSessionActivated: function() { | ||||
|         if (this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.VERIFICATION_SUCCEEDED) | ||||
|             return; | ||||
|  | ||||
|         Tweener.addTween(this.actor, | ||||
|                          { opacity: 255, | ||||
|                            time: _FADE_ANIMATION_TIME, | ||||
|                            transition: 'easeOutQuad', | ||||
|                            onUpdate: function() { | ||||
|                                let children = Main.layoutManager.uiGroup.get_children(); | ||||
|  | ||||
|                                for (let i = 0; i < children.length; i++) { | ||||
|                                    if (children[i] != Main.layoutManager.screenShieldGroup) | ||||
|                                        children[i].opacity = this.actor.opacity; | ||||
|                                } | ||||
|                            }, | ||||
|                            onUpdateScope: this, | ||||
|                            onComplete: function() { | ||||
|                                this._authPrompt.reset(); | ||||
|                            }, | ||||
|                            onCompleteScope: this }); | ||||
|     }, | ||||
|  | ||||
|     _gotGreeterSessionProxy: function(proxy) { | ||||
|         proxy.connect('g-properties-changed', Lang.bind(this, function() { | ||||
|             if (proxy.Active) | ||||
|                 this._loginScreenSessionActivated(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _startSession: function(serviceName) { | ||||
|         Tweener.addTween(this.actor, | ||||
|                          { opacity: 0, | ||||
| @@ -666,7 +688,7 @@ const LoginDialog = new Lang.Class({ | ||||
|                            onComplete: function() { | ||||
|                                Mainloop.idle_add(Lang.bind(this, function() { | ||||
|                                    this._greeter.call_start_session_when_ready_sync(serviceName, true, null); | ||||
|                                    return false; | ||||
|                                    return GLib.SOURCE_REMOVE; | ||||
|                                })); | ||||
|                            }, | ||||
|                            onCompleteScope: this }); | ||||
| @@ -721,6 +743,7 @@ const LoginDialog = new Lang.Class({ | ||||
|                                                                      function() { | ||||
|                                                                          this._timedLoginAnimationTime -= _TIMED_LOGIN_IDLE_THRESHOLD; | ||||
|                                                                          hold.release(); | ||||
|                                                                          return GLib.SOURCE_REMOVE; | ||||
|                                                                      }); | ||||
|         return hold; | ||||
|     }, | ||||
| @@ -785,7 +808,7 @@ const LoginDialog = new Lang.Class({ | ||||
|         global.stage.connect('captured-event', | ||||
|                              Lang.bind(this, function(actor, event) { | ||||
|                                 if (this._timedLoginDelay == undefined) | ||||
|                                     return false; | ||||
|                                     return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|                                 if (event.type() == Clutter.EventType.KEY_PRESS || | ||||
|                                     event.type() == Clutter.EventType.BUTTON_PRESS) { | ||||
| @@ -798,7 +821,7 @@ const LoginDialog = new Lang.Class({ | ||||
|                                     this._resetTimedLogin(); | ||||
|                                 } | ||||
|  | ||||
|                                 return false; | ||||
|                                 return Clutter.EVENT_PROPAGATE; | ||||
|                              })); | ||||
|     }, | ||||
|  | ||||
| @@ -824,6 +847,7 @@ const LoginDialog = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _showUserList: function() { | ||||
|         this._ensureUserListLoaded(); | ||||
|         this._authPrompt.hide(); | ||||
|         this._sessionMenuButton.close(); | ||||
|         this._setUserListExpanded(true); | ||||
| @@ -867,14 +891,17 @@ const LoginDialog = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _loadUserList: function() { | ||||
|         if (this._userListLoaded) | ||||
|             return GLib.SOURCE_REMOVE; | ||||
|  | ||||
|         this._userListLoaded = true; | ||||
|  | ||||
|         let users = this._userManager.list_users(); | ||||
|  | ||||
|         for (let i = 0; i < users.length; i++) { | ||||
|             this._userList.addUser(users[i]); | ||||
|         } | ||||
|  | ||||
|         this._updateDisableUserList(); | ||||
|  | ||||
|         this._userManager.connect('user-added', | ||||
|                                   Lang.bind(this, function(userManager, user) { | ||||
|                                       this._userList.addUser(user); | ||||
| @@ -884,6 +911,8 @@ const LoginDialog = new Lang.Class({ | ||||
|                                   Lang.bind(this, function(userManager, user) { | ||||
|                                       this._userList.removeUser(user); | ||||
|                                   })); | ||||
|  | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|     }, | ||||
|  | ||||
|     open: function() { | ||||
| @@ -907,6 +936,10 @@ const LoginDialog = new Lang.Class({ | ||||
|         Main.ctrlAltTabManager.removeGroup(this.dialogLayout); | ||||
|     }, | ||||
|  | ||||
|     cancel: function() { | ||||
|         this._authPrompt.cancel(); | ||||
|     }, | ||||
|  | ||||
|     addCharacter: function(unichar) { | ||||
|         this._authPrompt.addCharacter(unichar); | ||||
|     }, | ||||
|   | ||||
| @@ -4,11 +4,13 @@ const Gio = imports.gi.Gio; | ||||
| const Lang = imports.lang; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const OVirtCredentialsIface = <interface name='org.ovirt.vdsm.Credentials'> | ||||
| <signal name="UserAuthenticated"> | ||||
|     <arg type="s" name="token"/> | ||||
| </signal> | ||||
| </interface>; | ||||
| const OVirtCredentialsIface = '<node> \ | ||||
| <interface name="org.ovirt.vdsm.Credentials"> \ | ||||
| <signal name="UserAuthenticated"> \ | ||||
|     <arg type="s" name="token"/> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const OVirtCredentialsInfo = Gio.DBusInterfaceInfo.new_for_xml(OVirtCredentialsIface); | ||||
|  | ||||
|   | ||||
| @@ -5,52 +5,58 @@ const Lang = imports.lang; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const ProviderIface = <interface name='org.freedesktop.realmd.Provider'> | ||||
|     <property name="Name" type="s" access="read"/> | ||||
|     <property name="Version" type="s" access="read"/> | ||||
|     <property name="Realms" type="ao" access="read"/> | ||||
|     <method name="Discover"> | ||||
|         <arg name="string" type="s" direction="in"/> | ||||
|         <arg name="options" type="a{sv}" direction="in"/> | ||||
|         <arg name="relevance" type="i" direction="out"/> | ||||
|         <arg name="realm" type="ao" direction="out"/> | ||||
|     </method> | ||||
| </interface>; | ||||
| const ProviderIface = '<node> \ | ||||
| <interface name="org.freedesktop.realmd.Provider"> \ | ||||
|     <property name="Name" type="s" access="read"/> \ | ||||
|     <property name="Version" type="s" access="read"/> \ | ||||
|     <property name="Realms" type="ao" access="read"/> \ | ||||
|     <method name="Discover"> \ | ||||
|         <arg name="string" type="s" direction="in"/> \ | ||||
|         <arg name="options" type="a{sv}" direction="in"/> \ | ||||
|         <arg name="relevance" type="i" direction="out"/> \ | ||||
|         <arg name="realm" type="ao" direction="out"/> \ | ||||
|     </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const Provider = Gio.DBusProxy.makeProxyWrapper(ProviderIface); | ||||
|  | ||||
| const ServiceIface = <interface name="org.freedesktop.realmd.Service"> | ||||
|     <method name="Cancel"> | ||||
|         <arg name="operation" type="s" direction="in"/> | ||||
|     </method> | ||||
|     <method name="Release" /> | ||||
|     <method name="SetLocale"> | ||||
|         <arg name="locale" type="s" direction="in"/> | ||||
|     </method> | ||||
|     <signal name="Diagnostics"> | ||||
|         <arg name="data" type="s"/> | ||||
|         <arg name="operation" type="s"/> | ||||
|     </signal> | ||||
| </interface>; | ||||
| const ServiceIface = '<node> \ | ||||
| <interface name="org.freedesktop.realmd.Service"> \ | ||||
|     <method name="Cancel"> \ | ||||
|         <arg name="operation" type="s" direction="in"/> \ | ||||
|     </method> \ | ||||
|     <method name="Release" /> \ | ||||
|     <method name="SetLocale"> \ | ||||
|         <arg name="locale" type="s" direction="in"/> \ | ||||
|     </method> \ | ||||
|     <signal name="Diagnostics"> \ | ||||
|         <arg name="data" type="s"/> \ | ||||
|         <arg name="operation" type="s"/> \ | ||||
|     </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const Service = Gio.DBusProxy.makeProxyWrapper(ServiceIface); | ||||
|  | ||||
| const RealmIface = <interface name="org.freedesktop.realmd.Realm"> | ||||
|     <property name="Name" type="s" access="read"/> | ||||
|     <property name="Configured" type="s" access="read"/> | ||||
|     <property name="Details" type="a(ss)" access="read"/> | ||||
|     <property name="LoginFormats" type="as" access="read"/> | ||||
|     <property name="LoginPolicy" type="s" access="read"/> | ||||
|     <property name="PermittedLogins" type="as" access="read"/> | ||||
|     <property name="SupportedInterfaces" type="as" access="read"/> | ||||
|     <method name="ChangeLoginPolicy"> | ||||
|         <arg name="login_policy" type="s" direction="in"/> | ||||
|         <arg name="permitted_add" type="as" direction="in"/> | ||||
|         <arg name="permitted_remove" type="as" direction="in"/> | ||||
|         <arg name="options" type="a{sv}" direction="in"/> | ||||
|     </method> | ||||
|     <method name="Deconfigure"> | ||||
|         <arg name="options" type="a{sv}" direction="in"/> | ||||
|     </method> | ||||
| </interface>; | ||||
| const RealmIface = '<node> \ | ||||
| <interface name="org.freedesktop.realmd.Realm"> \ | ||||
|     <property name="Name" type="s" access="read"/> \ | ||||
|     <property name="Configured" type="s" access="read"/> \ | ||||
|     <property name="Details" type="a(ss)" access="read"/> \ | ||||
|     <property name="LoginFormats" type="as" access="read"/> \ | ||||
|     <property name="LoginPolicy" type="s" access="read"/> \ | ||||
|     <property name="PermittedLogins" type="as" access="read"/> \ | ||||
|     <property name="SupportedInterfaces" type="as" access="read"/> \ | ||||
|     <method name="ChangeLoginPolicy"> \ | ||||
|         <arg name="login_policy" type="s" direction="in"/> \ | ||||
|         <arg name="permitted_add" type="as" direction="in"/> \ | ||||
|         <arg name="permitted_remove" type="as" direction="in"/> \ | ||||
|         <arg name="options" type="a{sv}" direction="in"/> \ | ||||
|     </method> \ | ||||
|     <method name="Deconfigure"> \ | ||||
|         <arg name="options" type="a{sv}" direction="in"/> \ | ||||
|     </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const Realm = Gio.DBusProxy.makeProxyWrapper(RealmIface); | ||||
|  | ||||
| const Manager = new Lang.Class({ | ||||
|   | ||||
| @@ -250,6 +250,7 @@ const ShellUserVerifier = new Lang.Class({ | ||||
|                                                        Lang.bind(this, function() { | ||||
|                                                            this._messageQueueTimeoutId = 0; | ||||
|                                                            this._queueMessageTimeout(); | ||||
|                                                            return GLib.SOURCE_REMOVE; | ||||
|                                                        })); | ||||
|     }, | ||||
|  | ||||
| @@ -297,7 +298,7 @@ const ShellUserVerifier = new Lang.Class({ | ||||
|  | ||||
|         if (!this._settings.get_boolean(SMARTCARD_AUTHENTICATION_KEY)) | ||||
|             smartcardDetected = false; | ||||
|         else if (this.reauthenticating) | ||||
|         else if (this._reauthOnly) | ||||
|             smartcardDetected = this._smartcardManager.hasInsertedLoginToken(); | ||||
|         else | ||||
|             smartcardDetected = this._smartcardManager.hasInsertedTokens(); | ||||
|   | ||||
							
								
								
									
										114
									
								
								js/js-resources.gresource.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								js/js-resources.gresource.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <gresources> | ||||
|   <gresource prefix="/org/gnome/shell"> | ||||
|     <file>gdm/authPrompt.js</file> | ||||
|     <file>gdm/batch.js</file> | ||||
|     <file>gdm/fingerprint.js</file> | ||||
|     <file>gdm/loginDialog.js</file> | ||||
|     <file>gdm/oVirt.js</file> | ||||
|     <file>gdm/realmd.js</file> | ||||
|     <file>gdm/util.js</file> | ||||
|  | ||||
|     <file>extensionPrefs/main.js</file> | ||||
|  | ||||
|     <file>misc/config.js</file> | ||||
|     <file>misc/extensionUtils.js</file> | ||||
|     <file>misc/fileUtils.js</file> | ||||
|     <file>misc/gnomeSession.js</file> | ||||
|     <file>misc/history.js</file> | ||||
|     <file>misc/jsParse.js</file> | ||||
|     <file>misc/loginManager.js</file> | ||||
|     <file>misc/modemManager.js</file> | ||||
|     <file>misc/objectManager.js</file> | ||||
|     <file>misc/params.js</file> | ||||
|     <file>misc/smartcardManager.js</file> | ||||
|     <file>misc/util.js</file> | ||||
|  | ||||
|     <file>perf/core.js</file> | ||||
|  | ||||
|     <file>ui/altTab.js</file> | ||||
|     <file>ui/animation.js</file> | ||||
|     <file>ui/appDisplay.js</file> | ||||
|     <file>ui/appFavorites.js</file> | ||||
|     <file>ui/backgroundMenu.js</file> | ||||
|     <file>ui/background.js</file> | ||||
|     <file>ui/boxpointer.js</file> | ||||
|     <file>ui/calendar.js</file> | ||||
|     <file>ui/checkBox.js</file> | ||||
|     <file>ui/ctrlAltTab.js</file> | ||||
|     <file>ui/dash.js</file> | ||||
|     <file>ui/dateMenu.js</file> | ||||
|     <file>ui/dnd.js</file> | ||||
|     <file>ui/endSessionDialog.js</file> | ||||
|     <file>ui/environment.js</file> | ||||
|     <file>ui/extensionDownloader.js</file> | ||||
|     <file>ui/extensionSystem.js</file> | ||||
|     <file>ui/focusCaretTracker.js</file> | ||||
|     <file>ui/grabHelper.js</file> | ||||
|     <file>ui/ibusCandidatePopup.js</file> | ||||
|     <file>ui/iconGrid.js</file> | ||||
|     <file>ui/keyboard.js</file> | ||||
|     <file>ui/layout.js</file> | ||||
|     <file>ui/lightbox.js</file> | ||||
|     <file>ui/lookingGlass.js</file> | ||||
|     <file>ui/magnifier.js</file> | ||||
|     <file>ui/magnifierDBus.js</file> | ||||
|     <file>ui/main.js</file> | ||||
|     <file>ui/messageTray.js</file> | ||||
|     <file>ui/modalDialog.js</file> | ||||
|     <file>ui/notificationDaemon.js</file> | ||||
|     <file>ui/osdWindow.js</file> | ||||
|     <file>ui/overview.js</file> | ||||
|     <file>ui/overviewControls.js</file> | ||||
|     <file>ui/panel.js</file> | ||||
|     <file>ui/panelMenu.js</file> | ||||
|     <file>ui/pointerWatcher.js</file> | ||||
|     <file>ui/popupMenu.js</file> | ||||
|     <file>ui/remoteMenu.js</file> | ||||
|     <file>ui/remoteSearch.js</file> | ||||
|     <file>ui/runDialog.js</file> | ||||
|     <file>ui/screenShield.js</file> | ||||
|     <file>ui/screencast.js</file> | ||||
|     <file>ui/screenshot.js</file> | ||||
|     <file>ui/scripting.js</file> | ||||
|     <file>ui/search.js</file> | ||||
|     <file>ui/separator.js</file> | ||||
|     <file>ui/sessionMode.js</file> | ||||
|     <file>ui/shellDBus.js</file> | ||||
|     <file>ui/shellEntry.js</file> | ||||
|     <file>ui/shellMountOperation.js</file> | ||||
|     <file>ui/slider.js</file> | ||||
|     <file>ui/switcherPopup.js</file> | ||||
|     <file>ui/tweener.js</file> | ||||
|     <file>ui/unlockDialog.js</file> | ||||
|     <file>ui/userWidget.js</file> | ||||
|     <file>ui/viewSelector.js</file> | ||||
|     <file>ui/windowAttentionHandler.js</file> | ||||
|     <file>ui/windowManager.js</file> | ||||
|     <file>ui/workspace.js</file> | ||||
|     <file>ui/workspaceSwitcherPopup.js</file> | ||||
|     <file>ui/workspaceThumbnail.js</file> | ||||
|     <file>ui/workspacesView.js</file> | ||||
|     <file>ui/xdndHandler.js</file> | ||||
|  | ||||
|     <file>ui/components/__init__.js</file> | ||||
|     <file>ui/components/autorunManager.js</file> | ||||
|     <file>ui/components/automountManager.js</file> | ||||
|     <file>ui/components/networkAgent.js</file> | ||||
|     <file>ui/components/polkitAgent.js</file> | ||||
|     <file>ui/components/telepathyClient.js</file> | ||||
|     <file>ui/components/keyring.js</file> | ||||
|  | ||||
|     <file>ui/status/accessibility.js</file> | ||||
|     <file>ui/status/brightness.js</file> | ||||
|     <file>ui/status/location.js</file> | ||||
|     <file>ui/status/keyboard.js</file> | ||||
|     <file>ui/status/network.js</file> | ||||
|     <file>ui/status/power.js</file> | ||||
|     <file>ui/status/rfkill.js</file> | ||||
|     <file>ui/status/volume.js</file> | ||||
|     <file>ui/status/bluetooth.js</file> | ||||
|     <file>ui/status/screencast.js</file> | ||||
|     <file>ui/status/system.js</file> | ||||
|   </gresource> | ||||
| </gresources> | ||||
| @@ -6,6 +6,8 @@ const PACKAGE_NAME = '@PACKAGE_NAME@'; | ||||
| const PACKAGE_VERSION = '@PACKAGE_VERSION@'; | ||||
| /* 1 if gnome-bluetooth is available, 0 otherwise */ | ||||
| const HAVE_BLUETOOTH = @HAVE_BLUETOOTH@; | ||||
| /* 1 if networkmanager is available, 0 otherwise */ | ||||
| const HAVE_NETWORKMANAGER = @HAVE_NETWORKMANAGER@; | ||||
| /* gettext package */ | ||||
| const GETTEXT_PACKAGE = '@GETTEXT_PACKAGE@'; | ||||
| /* locale dir */ | ||||
|   | ||||
| @@ -174,17 +174,9 @@ const ExtensionFinder = new Lang.Class({ | ||||
|         this.emit('extension-found', extension); | ||||
|     }, | ||||
|  | ||||
|     _extensionsLoaded: function() { | ||||
|         this.emit('extensions-loaded'); | ||||
|     }, | ||||
|  | ||||
|     scanExtensions: function() { | ||||
|         let perUserDir = Gio.File.new_for_path(global.userdatadir); | ||||
|         FileUtils.collectFromDatadirsAsync('extensions', | ||||
|                                            { processFile: Lang.bind(this, this._loadExtension), | ||||
|                                              loadedCallback: Lang.bind(this, this._extensionsLoaded), | ||||
|                                              includeUserDir: true, | ||||
|                                              data: perUserDir }); | ||||
|         FileUtils.collectFromDatadirs('extensions', true, Lang.bind(this, this._loadExtension, perUserDir)); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(ExtensionFinder.prototype); | ||||
|   | ||||
| @@ -5,80 +5,27 @@ const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| const Params = imports.misc.params; | ||||
|  | ||||
| function listDirAsync(file, callback) { | ||||
|     let allFiles = []; | ||||
|     file.enumerate_children_async('standard::name,standard::type', | ||||
|                                   Gio.FileQueryInfoFlags.NONE, | ||||
|                                   GLib.PRIORITY_LOW, null, function (obj, res) { | ||||
|         let enumerator = obj.enumerate_children_finish(res); | ||||
|         function onNextFileComplete(obj, res) { | ||||
|             let files = obj.next_files_finish(res); | ||||
|             if (files.length) { | ||||
|                 allFiles = allFiles.concat(files); | ||||
|                 enumerator.next_files_async(100, GLib.PRIORITY_LOW, null, onNextFileComplete); | ||||
|             } else { | ||||
|                 enumerator.close(null); | ||||
|                 callback(allFiles); | ||||
|             } | ||||
|         } | ||||
|         enumerator.next_files_async(100, GLib.PRIORITY_LOW, null, onNextFileComplete); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| 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; | ||||
|     } | ||||
|  | ||||
| function collectFromDatadirs(subdir, includeUserDir, processFile) { | ||||
|     let dataDirs = GLib.get_system_data_dirs(); | ||||
|     if (params.includeUserDir) | ||||
|     if (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); | ||||
|         let fileEnum; | ||||
|         try { | ||||
|             fileEnum = dir.enumerate_children('standard::name,standard::type', | ||||
|                                               Gio.FileQueryInfoFlags.NONE, null); | ||||
|         } catch (e) { | ||||
|             fileEnum = null; | ||||
|         } | ||||
|         if (fileEnum != null) { | ||||
|             let info; | ||||
|             while ((info = fileEnum.next_file(null))) | ||||
|                 processFile(fileEnum.get_child(info), info); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -4,15 +4,17 @@ const Gio = imports.gi.Gio; | ||||
| const Lang = imports.lang; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const PresenceIface = <interface name="org.gnome.SessionManager.Presence"> | ||||
| <method name="SetStatus"> | ||||
|     <arg type="u" direction="in"/> | ||||
| </method> | ||||
| <property name="status" type="u" access="readwrite"/> | ||||
| <signal name="StatusChanged"> | ||||
|     <arg type="u" direction="out"/> | ||||
| </signal> | ||||
| </interface>; | ||||
| const PresenceIface = '<node> \ | ||||
| <interface name="org.gnome.SessionManager.Presence"> \ | ||||
| <method name="SetStatus"> \ | ||||
|     <arg type="u" direction="in"/> \ | ||||
| </method> \ | ||||
| <property name="status" type="u" access="readwrite"/> \ | ||||
| <signal name="StatusChanged"> \ | ||||
|     <arg type="u" direction="out"/> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const PresenceStatus = { | ||||
|     AVAILABLE: 0, | ||||
| @@ -30,14 +32,16 @@ function Presence(initCallback, cancellable) { | ||||
| // Note inhibitors are immutable objects, so they don't | ||||
| // change at runtime (changes always come in the form | ||||
| // of new inhibitors) | ||||
| const InhibitorIface = <interface name="org.gnome.SessionManager.Inhibitor"> | ||||
| <method name="GetAppId"> | ||||
|     <arg type="s" direction="out" /> | ||||
| </method> | ||||
| <method name="GetReason"> | ||||
|     <arg type="s" direction="out" /> | ||||
| </method> | ||||
| </interface>; | ||||
| const InhibitorIface = '<node> \ | ||||
| <interface name="org.gnome.SessionManager.Inhibitor"> \ | ||||
| <method name="GetAppId"> \ | ||||
|     <arg type="s" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="GetReason"> \ | ||||
|     <arg type="s" direction="out" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| var InhibitorProxy = Gio.DBusProxy.makeProxyWrapper(InhibitorIface); | ||||
| function Inhibitor(objectPath, initCallback, cancellable) { | ||||
| @@ -45,27 +49,29 @@ function Inhibitor(objectPath, initCallback, cancellable) { | ||||
| } | ||||
|  | ||||
| // Not the full interface, only the methods we use | ||||
| const SessionManagerIface = <interface name="org.gnome.SessionManager"> | ||||
| <method name="Logout"> | ||||
|     <arg type="u" direction="in" /> | ||||
| </method> | ||||
| <method name="Shutdown" /> | ||||
| <method name="Reboot" /> | ||||
| <method name="CanShutdown"> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <method name="IsInhibited"> | ||||
|     <arg type="u" direction="in" /> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <property name="SessionIsActive" type="b" access="read"/> | ||||
| <signal name="InhibitorAdded"> | ||||
|     <arg type="o" direction="out"/> | ||||
| </signal> | ||||
| <signal name="InhibitorRemoved"> | ||||
|     <arg type="o" direction="out"/> | ||||
| </signal> | ||||
| </interface>; | ||||
| const SessionManagerIface = '<node> \ | ||||
| <interface name="org.gnome.SessionManager"> \ | ||||
| <method name="Logout"> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="Shutdown" /> \ | ||||
| <method name="Reboot" /> \ | ||||
| <method name="CanShutdown"> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="IsInhibited"> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <property name="SessionIsActive" type="b" access="read"/> \ | ||||
| <signal name="InhibitorAdded"> \ | ||||
|     <arg type="o" direction="out"/> \ | ||||
| </signal> \ | ||||
| <signal name="InhibitorRemoved"> \ | ||||
|     <arg type="o" direction="out"/> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| var SessionManagerProxy = Gio.DBusProxy.makeProxyWrapper(SessionManagerIface); | ||||
| function SessionManager(initCallback, cancellable) { | ||||
|   | ||||
							
								
								
									
										144
									
								
								js/misc/hash.js
									
									
									
									
									
								
							
							
						
						
									
										144
									
								
								js/misc/hash.js
									
									
									
									
									
								
							| @@ -1,144 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Lang = imports.lang; | ||||
| const System = imports.system; | ||||
|  | ||||
| const Params = imports.misc.params; | ||||
|  | ||||
| // This is an implementation of EcmaScript SameValue algorithm, | ||||
| // which returns true if two values are not observably distinguishable. | ||||
| // It was taken from http://wiki.ecmascript.org/doku.php?id=harmony:egal | ||||
| // | ||||
| // In the future, we may want to use the 'is' operator instead. | ||||
| function _sameValue(x, y) { | ||||
|     if (x === y) { | ||||
|         // 0 === -0, but they are not identical | ||||
|         return x !== 0 || 1 / x === 1 / y; | ||||
|     } | ||||
|  | ||||
|     // NaN !== NaN, but they are identical. | ||||
|     // NaNs are the only non-reflexive value, i.e., if x !== x, | ||||
|     // then x is a NaN. | ||||
|     // isNaN is broken: it converts its argument to number, so | ||||
|     // isNaN("foo") => true | ||||
|     return x !== x && y !== y; | ||||
| } | ||||
|  | ||||
| const _hashers = { | ||||
|     object: function(o) { return o ? System.addressOf(o) : 'null'; }, | ||||
|     function: function(f) { return System.addressOf(f); }, | ||||
|     string: function(s) { return s; }, | ||||
|     number: function(n) { return String(n); }, | ||||
|     undefined: function() { return 'undefined'; }, | ||||
| }; | ||||
|  | ||||
| /* Map is meant to be similar in usage to ES6 Map, which is | ||||
|    described at http://wiki.ecmascript.org/doku.php?id=harmony:simple_maps_and_sets, | ||||
|    without requiring more than ES5 + Gjs extensions. | ||||
|  | ||||
|    Known differences from other implementations: | ||||
|    Polyfills around the web usually implement HashMaps for | ||||
|    primitive values and reversed WeakMaps for object keys, | ||||
|    but we want real maps with real O(1) semantics in all cases, | ||||
|    and the easiest way is to have different hashers for different | ||||
|    types. | ||||
|  | ||||
|    Known differences from the ES6 specification: | ||||
|    - Map is a Lang.Class, not a ES6 class, so inheritance, | ||||
|      prototype, sealing, etc. work differently. | ||||
|    - items(), keys() and values() don't return iterators, | ||||
|      they return actual arrays, so they incur a full copy everytime | ||||
|      they're called, and they don't see changes if you mutate | ||||
|      the table while iterating | ||||
|      (admittedly, the ES6 spec is a bit unclear on this, and | ||||
|      the reference code would just blow up) | ||||
| */ | ||||
| const Map = new Lang.Class({ | ||||
|     Name: 'Map', | ||||
|  | ||||
|     _init: function(iterable) { | ||||
|         this._pool = { }; | ||||
|         this._size = 0; | ||||
|  | ||||
|         if (iterable) { | ||||
|             for (let i = 0; i < iterable.length; i++) { | ||||
|                 let [key, value] = iterable[i]; | ||||
|                 this.set(key, value); | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _hashKey: function(key) { | ||||
|         let type = typeof(key); | ||||
|         return type + ':' + _hashers[type](key); | ||||
|     }, | ||||
|  | ||||
|     _internalGet: function(key) { | ||||
|         let hash = this._hashKey(key); | ||||
|         let node = this._pool[hash]; | ||||
|  | ||||
|         if (node && _sameValue(node.key, key)) | ||||
|             return [true, node.value]; | ||||
|         else | ||||
|             return [false, null]; | ||||
|     }, | ||||
|  | ||||
|     get: function(key) { | ||||
|         return this._internalGet(key)[1]; | ||||
|     }, | ||||
|  | ||||
|     has: function(key) { | ||||
|         return this._internalGet(key)[0]; | ||||
|     }, | ||||
|  | ||||
|     set: function(key, value) { | ||||
|         let hash = this._hashKey(key); | ||||
|         let node = this._pool[hash]; | ||||
|  | ||||
|         if (node) { | ||||
|             node.key = key; | ||||
|             node.value = value; | ||||
|         } else { | ||||
|             this._pool[hash] = { key: key, value: value }; | ||||
|             this._size++; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     delete: function(key) { | ||||
|         let hash = this._hashKey(key); | ||||
|         let node = this._pool[hash]; | ||||
|  | ||||
|         if (node && _sameValue(node.key, key)) { | ||||
|             delete this._pool[hash]; | ||||
|             this._size--; | ||||
|             return [node.key, node.value]; | ||||
|         } else { | ||||
|             return [null, null]; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     keys: function() { | ||||
|         let pool = this._pool; | ||||
|         return Object.getOwnPropertyNames(pool).map(function(hash) { | ||||
|             return pool[hash].key; | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     values: function() { | ||||
|         let pool = this._pool; | ||||
|         return Object.getOwnPropertyNames(pool).map(function(hash) { | ||||
|             return pool[hash].value; | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     items: function() { | ||||
|         let pool = this._pool; | ||||
|         return Object.getOwnPropertyNames(pool).map(function(hash) { | ||||
|             return [pool[hash].key, pool[hash].value]; | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     size: function() { | ||||
|         return this._size; | ||||
|     }, | ||||
| }); | ||||
| @@ -89,7 +89,7 @@ const HistoryManager = new Lang.Class({ | ||||
|         } else if (symbol == Clutter.KEY_Down) { | ||||
|             return this._setNextItem(entry.get_text()); | ||||
|         } | ||||
|         return false; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _indexChanged: function() { | ||||
|   | ||||
| @@ -7,58 +7,67 @@ const Mainloop = imports.mainloop; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const SystemdLoginManagerIface = <interface name='org.freedesktop.login1.Manager'> | ||||
| <method name='Suspend'> | ||||
|     <arg type='b' direction='in'/> | ||||
| </method> | ||||
| <method name='CanSuspend'> | ||||
|     <arg type='s' direction='out'/> | ||||
| </method> | ||||
| <method name='Inhibit'> | ||||
|     <arg type='s' direction='in'/> | ||||
|     <arg type='s' direction='in'/> | ||||
|     <arg type='s' direction='in'/> | ||||
|     <arg type='s' direction='in'/> | ||||
|     <arg type='h' direction='out'/> | ||||
| </method> | ||||
| <method name='GetSession'> | ||||
|     <arg type='s' direction='in'/> | ||||
|     <arg type='o' direction='out'/> | ||||
| </method> | ||||
| <method name='ListSessions'> | ||||
|     <arg name='sessions' type='a(susso)' direction='out'/> | ||||
| </method> | ||||
| <signal name='PrepareForSleep'> | ||||
|     <arg type='b' direction='out'/> | ||||
| </signal> | ||||
| </interface>; | ||||
| const SystemdLoginManagerIface = '<node> \ | ||||
| <interface name="org.freedesktop.login1.Manager"> \ | ||||
| <method name="Suspend"> \ | ||||
|     <arg type="b" direction="in"/> \ | ||||
| </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> \ | ||||
| </node>'; | ||||
|  | ||||
| const SystemdLoginSessionIface = <interface name='org.freedesktop.login1.Session'> | ||||
| <signal name='Lock' /> | ||||
| <signal name='Unlock' /> | ||||
| </interface>; | ||||
| const SystemdLoginSessionIface = '<node> \ | ||||
| <interface name="org.freedesktop.login1.Session"> \ | ||||
| <signal name="Lock" /> \ | ||||
| <signal name="Unlock" /> \ | ||||
| <property name="Active" type="b" access="read" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const SystemdLoginManager = Gio.DBusProxy.makeProxyWrapper(SystemdLoginManagerIface); | ||||
| const SystemdLoginSession = Gio.DBusProxy.makeProxyWrapper(SystemdLoginSessionIface); | ||||
|  | ||||
| const ConsoleKitManagerIface = <interface name='org.freedesktop.ConsoleKit.Manager'> | ||||
| <method name='CanRestart'> | ||||
|     <arg type='b' direction='out'/> | ||||
| </method> | ||||
| <method name='CanStop'> | ||||
|     <arg type='b' direction='out'/> | ||||
| </method> | ||||
| <method name='Restart' /> | ||||
| <method name='Stop' /> | ||||
| <method name='GetCurrentSession'> | ||||
|     <arg type='o' direction='out' /> | ||||
| </method> | ||||
| </interface>; | ||||
| const ConsoleKitManagerIface = '<node> \ | ||||
| <interface name="org.freedesktop.ConsoleKit.Manager"> \ | ||||
| <method name="CanRestart"> \ | ||||
|     <arg type="b" direction="out"/> \ | ||||
| </method> \ | ||||
| <method name="CanStop"> \ | ||||
|     <arg type="b" direction="out"/> \ | ||||
| </method> \ | ||||
| <method name="Restart" /> \ | ||||
| <method name="Stop" /> \ | ||||
| <method name="GetCurrentSession"> \ | ||||
|     <arg type="o" direction="out" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const ConsoleKitSessionIface = <interface name='org.freedesktop.ConsoleKit.Session'> | ||||
| <signal name='Lock' /> | ||||
| <signal name='Unlock' /> | ||||
| </interface>; | ||||
| const ConsoleKitSessionIface = '<node> \ | ||||
| <interface name="org.freedesktop.ConsoleKit.Session"> \ | ||||
| <signal name="Lock" /> \ | ||||
| <signal name="Unlock" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const ConsoleKitSession = Gio.DBusProxy.makeProxyWrapper(ConsoleKitSessionIface); | ||||
| const ConsoleKitManager = Gio.DBusProxy.makeProxyWrapper(ConsoleKitManagerIface); | ||||
|   | ||||
| @@ -92,37 +92,41 @@ function _findProviderForSid(sid) { | ||||
| // The following are not the complete interfaces, just the methods we need | ||||
| // (or may need in the future) | ||||
|  | ||||
| const ModemGsmNetworkInterface = <interface name="org.freedesktop.ModemManager.Modem.Gsm.Network"> | ||||
| <method name="GetRegistrationInfo"> | ||||
|     <arg type="(uss)" direction="out" /> | ||||
| </method> | ||||
| <method name="GetSignalQuality"> | ||||
|     <arg type="u" direction="out" /> | ||||
| </method> | ||||
| <property name="AccessTechnology" type="u" access="read" /> | ||||
| <signal name="SignalQuality"> | ||||
|     <arg type="u" direction="out" /> | ||||
| </signal> | ||||
| <signal name="RegistrationInfo"> | ||||
|     <arg type="u" direction="out" /> | ||||
|     <arg type="s" direction="out" /> | ||||
|     <arg type="s" direction="out" /> | ||||
| </signal> | ||||
| </interface>; | ||||
| const ModemGsmNetworkInterface = '<node> \ | ||||
| <interface name="org.freedesktop.ModemManager.Modem.Gsm.Network"> \ | ||||
| <method name="GetRegistrationInfo"> \ | ||||
|     <arg type="(uss)" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="GetSignalQuality"> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
| </method> \ | ||||
| <property name="AccessTechnology" type="u" access="read" /> \ | ||||
| <signal name="SignalQuality"> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
| </signal> \ | ||||
| <signal name="RegistrationInfo"> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
|     <arg type="s" direction="out" /> \ | ||||
|     <arg type="s" direction="out" /> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const ModemGsmNetworkProxy = Gio.DBusProxy.makeProxyWrapper(ModemGsmNetworkInterface); | ||||
|  | ||||
| const ModemCdmaInterface = <interface name="org.freedesktop.ModemManager.Modem.Cdma"> | ||||
| <method name="GetSignalQuality"> | ||||
|     <arg type="u" direction="out" /> | ||||
| </method> | ||||
| <method name="GetServingSystem"> | ||||
|     <arg type="(usu)" direction="out" /> | ||||
| </method> | ||||
| <signal name="SignalQuality"> | ||||
|     <arg type="u" direction="out" /> | ||||
| </signal> | ||||
| </interface>; | ||||
| const ModemCdmaInterface = '<node> \ | ||||
| <interface name="org.freedesktop.ModemManager.Modem.Cdma"> \ | ||||
| <method name="GetSignalQuality"> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="GetServingSystem"> \ | ||||
|     <arg type="(usu)" direction="out" /> \ | ||||
| </method> \ | ||||
| <signal name="SignalQuality"> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const ModemCdmaProxy = Gio.DBusProxy.makeProxyWrapper(ModemCdmaInterface); | ||||
|  | ||||
| @@ -218,20 +222,26 @@ 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 BroadbandModemInterface = '<node> \ | ||||
| <interface name="org.freedesktop.ModemManager1.Modem"> \ | ||||
| <property name="SignalQuality" type="(ub)" access="read" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| 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 BroadbandModem3gppInterface = '<node> \ | ||||
| <interface name="org.freedesktop.ModemManager1.Modem.Modem3gpp"> \ | ||||
| <property name="OperatorCode" type="s" access="read" /> \ | ||||
| <property name="OperatorName" type="s" access="read" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const BroadbandModem3gppProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModem3gppInterface); | ||||
|  | ||||
| const BroadbandModemCdmaInterface = <interface name="org.freedesktop.ModemManager1.Modem.ModemCdma"> | ||||
| <property name="Sid" type="u" access="read" /> | ||||
| </interface>; | ||||
| const BroadbandModemCdmaInterface = '<node> \ | ||||
| <interface name="org.freedesktop.ModemManager1.Modem.ModemCdma"> \ | ||||
| <property name="Sid" type="u" access="read" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const BroadbandModemCdmaProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModemCdmaInterface); | ||||
|  | ||||
| const BroadbandModem = new Lang.Class({ | ||||
|   | ||||
| @@ -8,19 +8,21 @@ const Signals = imports.signals; | ||||
|  | ||||
| // Specified in the D-Bus specification here: | ||||
| // http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager | ||||
| const ObjectManagerIface = <interface name="org.freedesktop.DBus.ObjectManager"> | ||||
|   <method name="GetManagedObjects"> | ||||
|     <arg name="objects" type="a{oa{sa{sv}}}" direction="out"/> | ||||
|   </method> | ||||
|   <signal name="InterfacesAdded"> | ||||
|     <arg name="objectPath" type="o"/> | ||||
|     <arg name="interfaces" type="a{sa{sv}}" /> | ||||
|   </signal> | ||||
|   <signal name="InterfacesRemoved"> | ||||
|     <arg name="objectPath" type="o"/> | ||||
|     <arg name="interfaces" type="as" /> | ||||
|   </signal> | ||||
| </interface>; | ||||
| const ObjectManagerIface = '<node> \ | ||||
| <interface name="org.freedesktop.DBus.ObjectManager"> \ | ||||
|   <method name="GetManagedObjects"> \ | ||||
|     <arg name="objects" type="a{oa{sa{sv}}}" direction="out"/> \ | ||||
|   </method> \ | ||||
|   <signal name="InterfacesAdded"> \ | ||||
|     <arg name="objectPath" type="o"/> \ | ||||
|     <arg name="interfaces" type="a{sa{sv}}" /> \ | ||||
|   </signal> \ | ||||
|   <signal name="InterfacesRemoved"> \ | ||||
|     <arg name="objectPath" type="o"/> \ | ||||
|     <arg name="interfaces" type="as" /> \ | ||||
|   </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const ObjectManagerInfo = Gio.DBusInterfaceInfo.new_for_xml(ObjectManagerIface); | ||||
|  | ||||
|   | ||||
| @@ -7,12 +7,14 @@ const Signals = imports.signals; | ||||
|  | ||||
| const ObjectManager = imports.misc.objectManager; | ||||
|  | ||||
| const SmartcardTokenIface = <interface name="org.gnome.SettingsDaemon.Smartcard.Token"> | ||||
|   <property name="Name" type="s" access="read"/> | ||||
|   <property name="Driver" type="o" access="read"/> | ||||
|   <property name="IsInserted" type="b" access="read"/> | ||||
|   <property name="UsedToLogin" type="b" access="read"/> | ||||
| </interface>; | ||||
| const SmartcardTokenIface = '<node> \ | ||||
| <interface name="org.gnome.SettingsDaemon.Smartcard.Token"> \ | ||||
|   <property name="Name" type="s" access="read"/> \ | ||||
|   <property name="Driver" type="o" access="read"/> \ | ||||
|   <property name="IsInserted" type="b" access="read"/> \ | ||||
|   <property name="UsedToLogin" type="b" access="read"/> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| let _smartcardManager = null; | ||||
|  | ||||
|   | ||||
| @@ -89,7 +89,7 @@ function spawnApp(argv) { | ||||
|         let app = Gio.AppInfo.create_from_commandline(argv.join(' '), null, | ||||
|                                                       Gio.AppInfoCreateFlags.SUPPORTS_STARTUP_NOTIFICATION); | ||||
|  | ||||
|         let context = global.create_app_launch_context(); | ||||
|         let context = global.create_app_launch_context(0, -1); | ||||
|         app.launch([], context); | ||||
|     } catch(err) { | ||||
|         _handleSpawnError(argv[0], err); | ||||
| @@ -153,7 +153,7 @@ function trySpawnCommandLine(command_line) { | ||||
| } | ||||
|  | ||||
| function _handleSpawnError(command, err) { | ||||
|     let title = _("Execution of '%s' failed:").format(command); | ||||
|     let title = _("Execution of “%s” failed:").format(command); | ||||
|     Main.notifyError(title, err.message); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Meta = imports.gi.Meta; | ||||
| @@ -23,7 +24,7 @@ const WINDOW_PREVIEW_SIZE = 128; | ||||
| const APP_ICON_SIZE = 96; | ||||
| const APP_ICON_SIZE_SMALL = 48; | ||||
|  | ||||
| const iconSizes = [96, 64, 48, 32, 22]; | ||||
| const baseIconSizes = [96, 64, 48, 32, 22]; | ||||
|  | ||||
| const AppIconMode = { | ||||
|     THUMBNAIL_ONLY: 1, | ||||
| @@ -106,6 +107,8 @@ const AppSwitcherPopup = new Lang.Class({ | ||||
|  | ||||
|         this._switcherList = new AppSwitcher(apps, this); | ||||
|         this._items = this._switcherList.icons; | ||||
|         if (this._items.length == 0) | ||||
|             return false; | ||||
|  | ||||
|         return true; | ||||
|     }, | ||||
| @@ -164,6 +167,8 @@ const AppSwitcherPopup = new Lang.Class({ | ||||
|                 this._select(this._selectedIndex, this._nextWindow()); | ||||
|             else if (keysym == Clutter.Up) | ||||
|                 this._select(this._selectedIndex, null, true); | ||||
|             else | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|         } else { | ||||
|             if (keysym == Clutter.Left) | ||||
|                 this._select(this._previous()); | ||||
| @@ -171,7 +176,11 @@ const AppSwitcherPopup = new Lang.Class({ | ||||
|                 this._select(this._next()); | ||||
|             else if (keysym == Clutter.Down) | ||||
|                 this._select(this._selectedIndex, 0); | ||||
|             else | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|         } | ||||
|  | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     _scrollHandler: function(direction) { | ||||
| @@ -310,7 +319,7 @@ const AppSwitcherPopup = new Lang.Class({ | ||||
|             this._createThumbnails(); | ||||
|         this._thumbnailTimeoutId = 0; | ||||
|         this._thumbnailsFocused = false; | ||||
|         return false; | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|     }, | ||||
|  | ||||
|     _destroyThumbnails : function() { | ||||
| @@ -375,6 +384,9 @@ const WindowSwitcherPopup = new Lang.Class({ | ||||
|         this._switcherList = new WindowList(windows, mode); | ||||
|         this._items = this._switcherList.icons; | ||||
|  | ||||
|         if (this._items.length == 0) | ||||
|             return false; | ||||
|  | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
| @@ -397,7 +409,11 @@ const WindowSwitcherPopup = new Lang.Class({ | ||||
|                 this._select(this._previous()); | ||||
|             else if (keysym == Clutter.Right) | ||||
|                 this._select(this._next()); | ||||
|             else | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|         } | ||||
|  | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     _finish: function() { | ||||
| @@ -424,7 +440,6 @@ const AppIcon = new Lang.Class({ | ||||
|  | ||||
|     set_size: function(size) { | ||||
|         this.icon = this.app.create_icon_texture(size); | ||||
|         this._iconBin.set_size(size, size); | ||||
|         this._iconBin.child = this.icon; | ||||
|     } | ||||
| }); | ||||
| @@ -454,9 +469,10 @@ const AppSwitcher = new Lang.Class({ | ||||
|             appIcon.cachedWindows = allWindows.filter(function(w) { | ||||
|                 return windowTracker.get_window_app (w) == appIcon.app; | ||||
|             }); | ||||
|             if (workspace == null || appIcon.cachedWindows.length > 0) { | ||||
|             if (appIcon.cachedWindows.length > 0) | ||||
|                 this._addIcon(appIcon); | ||||
|             } | ||||
|             else if (workspace == null) | ||||
|                 throw new Error('%s appears to be running, but doesn\'t have any windows'.format(appIcon.app.get_name())); | ||||
|         } | ||||
|  | ||||
|         this._curApp = -1; | ||||
| @@ -472,12 +488,13 @@ const AppSwitcher = new Lang.Class({ | ||||
|             Mainloop.source_remove(this._mouseTimeOutId); | ||||
|     }, | ||||
|  | ||||
|     _getPreferredHeight: function (actor, forWidth, alloc) { | ||||
|     _setIconSize: function() { | ||||
|         let j = 0; | ||||
|         while(this._items.length > 1 && this._items[j].style_class != 'item-box') { | ||||
|                 j++; | ||||
|         } | ||||
|         let themeNode = this._items[j].get_theme_node(); | ||||
|  | ||||
|         let iconPadding = themeNode.get_horizontal_padding(); | ||||
|         let iconBorder = themeNode.get_border_width(St.Side.LEFT) + themeNode.get_border_width(St.Side.RIGHT); | ||||
|         let [iconMinHeight, iconNaturalHeight] = this.icons[j].label.get_preferred_height(-1); | ||||
| @@ -488,19 +505,22 @@ const AppSwitcher = new Lang.Class({ | ||||
|         let primary = Main.layoutManager.primaryMonitor; | ||||
|         let parentPadding = this.actor.get_parent().get_theme_node().get_horizontal_padding(); | ||||
|         let availWidth = primary.width - parentPadding - this.actor.get_theme_node().get_horizontal_padding(); | ||||
|         let height = 0; | ||||
|  | ||||
|         for(let i =  0; i < iconSizes.length; i++) { | ||||
|                 this._iconSize = iconSizes[i]; | ||||
|                 height = iconSizes[i] + iconSpacing; | ||||
|                 let w = height * this._items.length + totalSpacing; | ||||
|                 if (w <= availWidth) | ||||
|                         break; | ||||
|         } | ||||
|         let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; | ||||
|         let iconSizes = baseIconSizes.map(function(s) { | ||||
|             return s * scaleFactor; | ||||
|         }); | ||||
|  | ||||
|         if (this._items.length == 1) { | ||||
|             this._iconSize = iconSizes[0]; | ||||
|             height = iconSizes[0] + iconSpacing; | ||||
|             this._iconSize = baseIconSizes[0]; | ||||
|         } else { | ||||
|             for(let i =  0; i < baseIconSizes.length; i++) { | ||||
|                 this._iconSize = baseIconSizes[i]; | ||||
|                 let height = iconSizes[i] + iconSpacing; | ||||
|                 let w = height * this._items.length + totalSpacing; | ||||
|                 if (w <= availWidth) | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         for(let i = 0; i < this.icons.length; i++) { | ||||
| @@ -508,9 +528,11 @@ const AppSwitcher = new Lang.Class({ | ||||
|                 break; | ||||
|             this.icons[i].set_size(this._iconSize); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|         alloc.min_size = height; | ||||
|         alloc.natural_size = height; | ||||
|     _getPreferredHeight: function (actor, forWidth, alloc) { | ||||
|         this._setIconSize(); | ||||
|         this.parent(actor, forWidth, alloc); | ||||
|     }, | ||||
|  | ||||
|     _allocate: function (actor, box, flags) { | ||||
| @@ -542,7 +564,7 @@ const AppSwitcher = new Lang.Class({ | ||||
|                                                         Lang.bind(this, function () { | ||||
|                                                                             this._enterItem(index); | ||||
|                                                                             this._mouseTimeOutId = 0; | ||||
|                                                                             return false; | ||||
|                                                                             return GLib.SOURCE_REMOVE; | ||||
|                                                         })); | ||||
|         } else | ||||
|            this._itemEntered(index); | ||||
| @@ -643,17 +665,19 @@ const ThumbnailList = new Lang.Class({ | ||||
|         totalPadding += this.actor.get_theme_node().get_horizontal_padding() + this.actor.get_theme_node().get_vertical_padding(); | ||||
|         let [labelMinHeight, labelNaturalHeight] = this._labels[0].get_preferred_height(-1); | ||||
|         let spacing = this._items[0].child.get_theme_node().get_length('spacing'); | ||||
|         let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; | ||||
|         let thumbnailSize = THUMBNAIL_DEFAULT_SIZE * scaleFactor; | ||||
|  | ||||
|         availHeight = Math.min(availHeight - labelNaturalHeight - totalPadding - spacing, THUMBNAIL_DEFAULT_SIZE); | ||||
|         availHeight = Math.min(availHeight - labelNaturalHeight - totalPadding - spacing, thumbnailSize); | ||||
|         let binHeight = availHeight + this._items[0].get_theme_node().get_vertical_padding() + this.actor.get_theme_node().get_vertical_padding() - spacing; | ||||
|         binHeight = Math.min(THUMBNAIL_DEFAULT_SIZE, binHeight); | ||||
|         binHeight = Math.min(thumbnailSize, binHeight); | ||||
|  | ||||
|         for (let i = 0; i < this._thumbnailBins.length; i++) { | ||||
|             let mutterWindow = this._windows[i].get_compositor_private(); | ||||
|             if (!mutterWindow) | ||||
|                 continue; | ||||
|  | ||||
|             let clone = _createWindowClone(mutterWindow, THUMBNAIL_DEFAULT_SIZE); | ||||
|             let clone = _createWindowClone(mutterWindow, thumbnailSize); | ||||
|             this._thumbnailBins[i].set_height(binHeight); | ||||
|             this._thumbnailBins[i].add_actor(clone); | ||||
|             this._clones.push(clone); | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const St = imports.gi.St; | ||||
| @@ -20,7 +21,9 @@ const Animation = new Lang.Class({ | ||||
|         this._isPlaying = false; | ||||
|         this._timeoutId = 0; | ||||
|         this._frame = 0; | ||||
|         this._animations = St.TextureCache.get_default().load_sliced_image (filename, width, height, | ||||
|  | ||||
|         let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; | ||||
|         this._animations = St.TextureCache.get_default().load_sliced_image (filename, width, height, scaleFactor, | ||||
|                                                                             Lang.bind(this, this._animationsLoaded)); | ||||
|         this.actor.set_child(this._animations); | ||||
|     }, | ||||
| @@ -59,7 +62,7 @@ const Animation = new Lang.Class({ | ||||
|  | ||||
|     _update: function() { | ||||
|         this._showFrame(this._frame + 1); | ||||
|         return true; | ||||
|         return GLib.SOURCE_CONTINUE; | ||||
|     }, | ||||
|  | ||||
|     _animationsLoaded: function() { | ||||
|   | ||||
| @@ -5,7 +5,6 @@ const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const GObject = imports.gi.GObject; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const GMenu = imports.gi.GMenu; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Lang = imports.lang; | ||||
| const Signals = imports.signals; | ||||
| @@ -47,24 +46,40 @@ const INDICATORS_ANIMATION_MAX_TIME = 0.75; | ||||
| const PAGE_SWITCH_TRESHOLD = 0.2; | ||||
| const PAGE_SWITCH_TIME = 0.3; | ||||
|  | ||||
| // Recursively load a GMenuTreeDirectory; we could put this in ShellAppSystem too | ||||
| function _loadCategory(dir, view) { | ||||
|     let iter = dir.iter(); | ||||
|     let appSystem = Shell.AppSystem.get_default(); | ||||
|     let nextType; | ||||
|     while ((nextType = iter.next()) != GMenu.TreeItemType.INVALID) { | ||||
|         if (nextType == GMenu.TreeItemType.ENTRY) { | ||||
|             let entry = iter.get_entry(); | ||||
|             let app = appSystem.lookup_app_by_tree_entry(entry); | ||||
|             if (!entry.get_app_info().get_nodisplay()) | ||||
|                 view.addApp(app); | ||||
|         } else if (nextType == GMenu.TreeItemType.DIRECTORY) { | ||||
|             let itemDir = iter.get_directory(); | ||||
|             if (!itemDir.get_is_nodisplay()) | ||||
|                 _loadCategory(itemDir, view); | ||||
| const VIEWS_SWITCH_TIME = 0.4; | ||||
| const VIEWS_SWITCH_ANIMATION_DELAY = 0.1; | ||||
|  | ||||
| function _getCategories(info) { | ||||
|     let categoriesStr = info.get_categories(); | ||||
|     if (!categoriesStr) | ||||
|         return []; | ||||
|     return categoriesStr.split(';'); | ||||
| } | ||||
|  | ||||
| function _listsIntersect(a, b) { | ||||
|     for (let itemA of a) | ||||
|         if (b.indexOf(itemA) >= 0) | ||||
|             return true; | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| function _getFolderName(folder) { | ||||
|     let name = folder.get_string('name'); | ||||
|  | ||||
|     if (folder.get_boolean('translate')) { | ||||
|         let keyfile = new GLib.KeyFile(); | ||||
|         let path = 'desktop-directories/' + name; | ||||
|  | ||||
|         try { | ||||
|             keyfile.load_from_data_dirs(path, GLib.KeyFileFlags.NONE); | ||||
|             name = keyfile.get_locale_string('Desktop Entry', 'Name', null); | ||||
|         } catch(e) { | ||||
|             return name; | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|  | ||||
|     return name; | ||||
| } | ||||
|  | ||||
| const BaseAppView = new Lang.Class({ | ||||
|     Name: 'BaseAppView', | ||||
| @@ -84,6 +99,9 @@ const BaseAppView = new Lang.Class({ | ||||
|         else | ||||
|             this._grid = new IconGrid.IconGrid(gridParams); | ||||
|  | ||||
|         this._grid.connect('key-focus-in', Lang.bind(this, function(grid, actor) { | ||||
|             this._keyFocusIn(actor); | ||||
|         })); | ||||
|         // Standard hack for ClutterBinLayout | ||||
|         this._grid.actor.x_expand = true; | ||||
|  | ||||
| @@ -91,46 +109,43 @@ const BaseAppView = new Lang.Class({ | ||||
|         this._allItems = []; | ||||
|     }, | ||||
|  | ||||
|     _keyFocusIn: function(actor) { | ||||
|         // Nothing by default | ||||
|     }, | ||||
|  | ||||
|     removeAll: function() { | ||||
|         this._grid.removeAll(); | ||||
|         this._grid.destroyAll(); | ||||
|         this._items = {}; | ||||
|         this._allItems = []; | ||||
|     }, | ||||
|  | ||||
|     _getItemId: function(item) { | ||||
|         throw new Error('Not implemented'); | ||||
|     _redisplay: function() { | ||||
|         this.removeAll(); | ||||
|         this._loadApps(); | ||||
|     }, | ||||
|  | ||||
|     _createItemIcon: function(item) { | ||||
|         throw new Error('Not implemented'); | ||||
|     getAllItems: function() { | ||||
|         return this._allItems; | ||||
|     }, | ||||
|  | ||||
|     addItem: function(icon) { | ||||
|         let id = icon.id; | ||||
|         if (this._items[id] !== undefined) | ||||
|             return; | ||||
|  | ||||
|         this._allItems.push(icon); | ||||
|         this._items[id] = icon; | ||||
|     }, | ||||
|  | ||||
|     _compareItems: function(a, b) { | ||||
|         throw new Error('Not implemented'); | ||||
|     }, | ||||
|  | ||||
|     _addItem: function(item) { | ||||
|         let id = this._getItemId(item); | ||||
|         if (this._items[id] !== undefined) | ||||
|             return null; | ||||
|  | ||||
|         let itemIcon = this._createItemIcon(item); | ||||
|         this._allItems.push(item); | ||||
|         this._items[id] = itemIcon; | ||||
|  | ||||
|         return itemIcon; | ||||
|         return a.name.localeCompare(b.name); | ||||
|     }, | ||||
|  | ||||
|     loadGrid: function() { | ||||
|         this._allItems.sort(Lang.bind(this, this._compareItems)); | ||||
|  | ||||
|         for (let i = 0; i < this._allItems.length; i++) { | ||||
|             let id = this._getItemId(this._allItems[i]); | ||||
|             if (!id) | ||||
|                 continue; | ||||
|             this._grid.addItem(this._items[id]); | ||||
|         } | ||||
|  | ||||
|         this._allItems.sort(this._compareItems); | ||||
|         this._allItems.forEach(Lang.bind(this, function(item) { | ||||
|             this._grid.addItem(item); | ||||
|         })); | ||||
|         this.emit('view-loaded'); | ||||
|     }, | ||||
|  | ||||
| @@ -243,7 +258,8 @@ const PageIndicators = new Lang.Class({ | ||||
|             Tweener.addTween(children[i], | ||||
|                              { translation_x: 0, | ||||
|                                time: INDICATORS_BASE_TIME + delay * i, | ||||
|                                transition: 'easeInOutQuad' | ||||
|                                transition: 'easeInOutQuad', | ||||
|                                delay: VIEWS_SWITCH_ANIMATION_DELAY | ||||
|                              }); | ||||
|         } | ||||
|     } | ||||
| @@ -281,7 +297,7 @@ const AllView = new Lang.Class({ | ||||
|         this._pageIndicators.actor.connect('scroll-event', Lang.bind(this, this._onScroll)); | ||||
|         this.actor.add_actor(this._pageIndicators.actor); | ||||
|  | ||||
|         this._folderIcons = []; | ||||
|         this.folderIcons = []; | ||||
|  | ||||
|         this._stack = new St.Widget({ layout_manager: new Clutter.BinLayout() }); | ||||
|         let box = new St.BoxLayout({ vertical: true }); | ||||
| @@ -346,6 +362,76 @@ const AllView = new Lang.Class({ | ||||
|                     this._keyPressEventId = 0; | ||||
|                 } | ||||
|             })); | ||||
|  | ||||
|         this._redisplayWorkId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay)); | ||||
|  | ||||
|         Shell.AppSystem.get_default().connect('installed-changed', Lang.bind(this, function() { | ||||
|             Main.queueDeferredWork(this._redisplayWorkId); | ||||
|         })); | ||||
|         this._folderSettings = new Gio.Settings({ schema: 'org.gnome.desktop.app-folders' }); | ||||
|         this._folderSettings.connect('changed::folder-children', Lang.bind(this, function() { | ||||
|             Main.queueDeferredWork(this._redisplayWorkId); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     removeAll: function() { | ||||
|         this.folderIcons = []; | ||||
|         this.parent(); | ||||
|     }, | ||||
|  | ||||
|     _itemNameChanged: function(item) { | ||||
|         // If an item's name changed, we can pluck it out of where it's | ||||
|         // supposed to be and reinsert it where it's sorted. | ||||
|         let oldIdx = this._allItems.indexOf(item); | ||||
|         this._allItems.splice(oldIdx, 1); | ||||
|         let newIdx = Util.insertSorted(this._allItems, item, this._compareItems); | ||||
|  | ||||
|         this._grid.removeItem(item); | ||||
|         this._grid.addItem(item, newIdx); | ||||
|     }, | ||||
|  | ||||
|     _refilterApps: function() { | ||||
|         this._allItems.forEach(function(icon) { | ||||
|             if (icon instanceof AppIcon) | ||||
|                 icon.actor.visible = true; | ||||
|         }); | ||||
|  | ||||
|         this.folderIcons.forEach(Lang.bind(this, function(folder) { | ||||
|             let folderApps = folder.getAppIds(); | ||||
|             folderApps.forEach(Lang.bind(this, function(appId) { | ||||
|                 let appIcon = this._items[appId]; | ||||
|                 appIcon.actor.visible = false; | ||||
|             })); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _loadApps: function() { | ||||
|         let apps = Gio.AppInfo.get_all().filter(function(appInfo) { | ||||
|             return appInfo.should_show(); | ||||
|         }).map(function(app) { | ||||
|             return app.get_id(); | ||||
|         }); | ||||
|  | ||||
|         let appSys = Shell.AppSystem.get_default(); | ||||
|  | ||||
|         let folders = this._folderSettings.get_strv('folder-children'); | ||||
|         folders.forEach(Lang.bind(this, function(id) { | ||||
|             let path = this._folderSettings.path + 'folders/' + id + '/'; | ||||
|             let icon = new FolderIcon(id, path, this); | ||||
|             icon.connect('name-changed', Lang.bind(this, this._itemNameChanged)); | ||||
|             icon.connect('apps-changed', Lang.bind(this, this._refilterApps)); | ||||
|             this.addItem(icon); | ||||
|             this.folderIcons.push(icon); | ||||
|         })); | ||||
|  | ||||
|         apps.forEach(Lang.bind(this, function(appId) { | ||||
|             let app = appSys.lookup_app(appId); | ||||
|             let icon = new AppIcon(app); | ||||
|             this.addItem(icon); | ||||
|         })); | ||||
|  | ||||
|         this.loadGrid(); | ||||
|         this._refilterApps(); | ||||
|     }, | ||||
|  | ||||
|     getCurrentPageY: function() { | ||||
| @@ -415,7 +501,7 @@ const AllView = new Lang.Class({ | ||||
|  | ||||
|     _onScroll: function(actor, event) { | ||||
|         if (this._displayingPopup) | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|  | ||||
|         let direction = event.get_scroll_direction(); | ||||
|         if (direction == Clutter.ScrollDirection.UP) | ||||
| @@ -423,7 +509,7 @@ const AllView = new Lang.Class({ | ||||
|         else if (direction == Clutter.ScrollDirection.DOWN) | ||||
|             this.goToPage(this._currentPage + 1); | ||||
|  | ||||
|         return true; | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     _onPan: function(action) { | ||||
| @@ -454,63 +540,17 @@ const AllView = new Lang.Class({ | ||||
|  | ||||
|     _onKeyPressEvent: function(actor, event) { | ||||
|         if (this._displayingPopup) | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|  | ||||
|         if (event.get_key_symbol() == Clutter.Page_Up) { | ||||
|             this.goToPage(this._currentPage - 1); | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } else if (event.get_key_symbol() == Clutter.Page_Down) { | ||||
|             this.goToPage(this._currentPage + 1); | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _getItemId: function(item) { | ||||
|         if (item instanceof Shell.App) | ||||
|             return item.get_id(); | ||||
|         else if (item instanceof GMenu.TreeDirectory) | ||||
|             return item.get_menu_id(); | ||||
|         else | ||||
|             return null; | ||||
|     }, | ||||
|  | ||||
|     _createItemIcon: function(item) { | ||||
|         if (item instanceof Shell.App) | ||||
|             return new AppIcon(item); | ||||
|         else if (item instanceof GMenu.TreeDirectory) | ||||
|             return new FolderIcon(item, this); | ||||
|         else | ||||
|             return null; | ||||
|     }, | ||||
|  | ||||
|     _compareItems: function(itemA, itemB) { | ||||
|         // bit of a hack: rely on both ShellApp and GMenuTreeDirectory | ||||
|         // having a get_name() method | ||||
|         let nameA = GLib.utf8_collate_key(itemA.get_name(), -1); | ||||
|         let nameB = GLib.utf8_collate_key(itemB.get_name(), -1); | ||||
|         return (nameA > nameB) ? 1 : (nameA < nameB ? -1 : 0); | ||||
|     }, | ||||
|  | ||||
|     removeAll: function() { | ||||
|         this._folderIcons = []; | ||||
|         this.parent(); | ||||
|     }, | ||||
|  | ||||
|     addApp: function(app) { | ||||
|         let appIcon = this._addItem(app); | ||||
|         if (appIcon) | ||||
|             appIcon.actor.connect('key-focus-in', | ||||
|                                   Lang.bind(this, this._ensureIconVisible)); | ||||
|     }, | ||||
|  | ||||
|     addFolder: function(dir) { | ||||
|         let folderIcon = this._addItem(dir); | ||||
|         this._folderIcons.push(folderIcon); | ||||
|         if (folderIcon) | ||||
|             folderIcon.actor.connect('key-focus-in', | ||||
|                                      Lang.bind(this, this._ensureIconVisible)); | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     addFolderPopup: function(popup) { | ||||
| @@ -525,7 +565,7 @@ const AllView = new Lang.Class({ | ||||
|             })); | ||||
|     }, | ||||
|  | ||||
|     _ensureIconVisible: function(icon) { | ||||
|     _keyFocusIn: function(icon) { | ||||
|         let itemPage = this._grid.getItemPage(icon); | ||||
|         this.goToPage(itemPage); | ||||
|     }, | ||||
| @@ -577,8 +617,8 @@ const AllView = new Lang.Class({ | ||||
|         this._availWidth = availWidth; | ||||
|         this._availHeight = availHeight; | ||||
|         // Update folder views | ||||
|         for (let i = 0; i < this._folderIcons.length; i++) | ||||
|             this._folderIcons[i].adaptToSize(availWidth, availHeight); | ||||
|         for (let i = 0; i < this.folderIcons.length; i++) | ||||
|             this.folderIcons[i].adaptToSize(availWidth, availHeight); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(AllView.prototype); | ||||
| @@ -607,13 +647,18 @@ const FrequentView = new Lang.Class({ | ||||
|         this._noFrequentAppsLabel.hide(); | ||||
|  | ||||
|         this._usage = Shell.AppUsage.get_default(); | ||||
|  | ||||
|         this.actor.connect('notify::mapped', Lang.bind(this, function() { | ||||
|             if (this.actor.mapped) | ||||
|                 this._redisplay(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     hasUsefulData: function() { | ||||
|         return this._usage.get_most_used("").length >= MIN_FREQUENT_APPS_COUNT; | ||||
|     }, | ||||
|  | ||||
|     loadApps: function() { | ||||
|     _loadApps: function() { | ||||
|         let mostUsed = this._usage.get_most_used (""); | ||||
|         let hasUsefulData = this.hasUsefulData(); | ||||
|         this._noFrequentAppsLabel.visible = !hasUsefulData; | ||||
| @@ -691,16 +736,6 @@ const AppDisplay = new Lang.Class({ | ||||
|     Name: 'AppDisplay', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._appSystem = Shell.AppSystem.get_default(); | ||||
|         this._appSystem.connect('installed-changed', Lang.bind(this, function() { | ||||
|             Main.queueDeferredWork(this._allAppsWorkId); | ||||
|         })); | ||||
|         Main.overview.connect('showing', Lang.bind(this, function() { | ||||
|             Main.queueDeferredWork(this._frequentAppsWorkId); | ||||
|         })); | ||||
|         global.settings.connect('changed::app-folder-categories', Lang.bind(this, function() { | ||||
|             Main.queueDeferredWork(this._allAppsWorkId); | ||||
|         })); | ||||
|         this._privacySettings = new Gio.Settings({ schema: 'org.gnome.desktop.privacy' }); | ||||
|         this._privacySettings.connect('changed::remember-app-usage', | ||||
|                                       Lang.bind(this, this._updateFrequentVisibility)); | ||||
| @@ -754,22 +789,15 @@ const AppDisplay = new Lang.Class({ | ||||
|             initialView = Views.ALL; | ||||
|         this._showView(initialView); | ||||
|         this._updateFrequentVisibility(); | ||||
|  | ||||
|         // We need a dummy actor to catch the keyboard focus if the | ||||
|         // user Ctrl-Alt-Tabs here before the deferred work creates | ||||
|         // our real contents | ||||
|         this._focusDummy = new St.Bin({ can_focus: true }); | ||||
|         this._viewStack.add_actor(this._focusDummy); | ||||
|  | ||||
|         this._allAppsWorkId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplayAllApps)); | ||||
|         this._frequentAppsWorkId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplayFrequentApps)); | ||||
|     }, | ||||
|  | ||||
|     _showView: function(activeIndex) { | ||||
|         for (let i = 0; i < this._views.length; i++) { | ||||
|             let actor = this._views[i].view.actor; | ||||
|             let params = { time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME, | ||||
|                            opacity: (i == activeIndex) ? 255 : 0 }; | ||||
|  | ||||
|             let params = { time: VIEWS_SWITCH_TIME, | ||||
|                            opacity: (i == activeIndex) ? 255 : 0, | ||||
|                            delay: (i == activeIndex) ? VIEWS_SWITCH_ANIMATION_DELAY : 0 }; | ||||
|             if (i == activeIndex) | ||||
|                 actor.visible = true; | ||||
|             else | ||||
| @@ -796,52 +824,6 @@ const AppDisplay = new Lang.Class({ | ||||
|             this._showView(Views.ALL); | ||||
|     }, | ||||
|  | ||||
|     _redisplay: function() { | ||||
|         this._redisplayFrequentApps(); | ||||
|         this._redisplayAllApps(); | ||||
|     }, | ||||
|  | ||||
|     _redisplayFrequentApps: function() { | ||||
|         let view = this._views[Views.FREQUENT].view; | ||||
|  | ||||
|         view.removeAll(); | ||||
|         view.loadApps(); | ||||
|     }, | ||||
|  | ||||
|     _redisplayAllApps: function() { | ||||
|         let view = this._views[Views.ALL].view; | ||||
|  | ||||
|         view.removeAll(); | ||||
|  | ||||
|         let tree = this._appSystem.get_tree(); | ||||
|         let root = tree.get_root_directory(); | ||||
|  | ||||
|         let iter = root.iter(); | ||||
|         let nextType; | ||||
|         let folderCategories = global.settings.get_strv('app-folder-categories'); | ||||
|         while ((nextType = iter.next()) != GMenu.TreeItemType.INVALID) { | ||||
|             if (nextType == GMenu.TreeItemType.DIRECTORY) { | ||||
|                 let dir = iter.get_directory(); | ||||
|                 if (dir.get_is_nodisplay()) | ||||
|                     continue; | ||||
|  | ||||
|                 if (folderCategories.indexOf(dir.get_menu_id()) != -1) | ||||
|                     view.addFolder(dir); | ||||
|                 else | ||||
|                     _loadCategory(dir, view); | ||||
|             } | ||||
|         } | ||||
|         view.loadGrid(); | ||||
|  | ||||
|         if (this._focusDummy) { | ||||
|             let focused = this._focusDummy.has_key_focus(); | ||||
|             this._focusDummy.destroy(); | ||||
|             this._focusDummy = null; | ||||
|             if (focused) | ||||
|                 this.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     selectApp: function(id) { | ||||
|         this._showView(Views.ALL); | ||||
|         this._views[Views.ALL].view.selectApp(id); | ||||
| @@ -871,8 +853,8 @@ const AppSearchProvider = new Lang.Class({ | ||||
|     getResultMetas: function(apps, callback) { | ||||
|         let metas = []; | ||||
|         for (let i = 0; i < apps.length; i++) { | ||||
|             let app = apps[i]; | ||||
|             metas.push({ 'id': app, | ||||
|             let app = this._appSys.lookup_app(apps[i]); | ||||
|             metas.push({ 'id': app.get_id(), | ||||
|                          'name': app.get_name(), | ||||
|                          'createIcon': function(size) { | ||||
|                              return app.create_icon_texture(size); | ||||
| @@ -886,15 +868,29 @@ const AppSearchProvider = new Lang.Class({ | ||||
|         return results.slice(0, maxNumber); | ||||
|     }, | ||||
|  | ||||
|     getInitialResultSet: function(terms) { | ||||
|         this.searchSystem.setResults(this, this._appSys.initial_search(terms)); | ||||
|     getInitialResultSet: function(terms, callback, cancellable) { | ||||
|         let query = terms.join(' '); | ||||
|         let groups = Gio.DesktopAppInfo.search(query); | ||||
|         let usage = Shell.AppUsage.get_default(); | ||||
|         let results = []; | ||||
|         groups.forEach(function(group) { | ||||
|             group = group.filter(function(appID) { | ||||
|                 let app = Gio.DesktopAppInfo.new(appID); | ||||
|                 return app && app.should_show(); | ||||
|             }); | ||||
|             results = results.concat(group.sort(function(a, b) { | ||||
|                 return usage.compare('', a, b); | ||||
|             })); | ||||
|         }); | ||||
|         callback(results); | ||||
|     }, | ||||
|  | ||||
|     getSubsearchResultSet: function(previousResults, terms) { | ||||
|         this.searchSystem.setResults(this, this._appSys.subsearch(previousResults, terms)); | ||||
|     getSubsearchResultSet: function(previousResults, terms, callback, cancellable) { | ||||
|         this.getInitialResultSet(terms, callback, cancellable); | ||||
|     }, | ||||
|  | ||||
|     activateResult: function(app) { | ||||
|     activateResult: function(result) { | ||||
|         let app = this._appSys.lookup_app(result); | ||||
|         let event = Clutter.get_current_event(); | ||||
|         let modifiers = event ? event.get_state() : 0; | ||||
|         let openNewWindow = modifiers & Clutter.ModifierType.CONTROL_MASK; | ||||
| @@ -913,8 +909,8 @@ const AppSearchProvider = new Lang.Class({ | ||||
|         app.open_new_window(workspace); | ||||
|     }, | ||||
|  | ||||
|     createResultObject: function (resultMeta, terms) { | ||||
|         let app = resultMeta['id']; | ||||
|     createResultObject: function (resultMeta) { | ||||
|         let app = this._appSys.lookup_app(resultMeta['id']); | ||||
|         return new AppIcon(app); | ||||
|     } | ||||
| }); | ||||
| @@ -940,36 +936,27 @@ const FolderView = new Lang.Class({ | ||||
|         this.actor.add_action(action); | ||||
|     }, | ||||
|  | ||||
|     _getItemId: function(item) { | ||||
|         return item.get_id(); | ||||
|     }, | ||||
|  | ||||
|     _createItemIcon: function(item) { | ||||
|         return new AppIcon(item); | ||||
|     }, | ||||
|  | ||||
|     _compareItems: function(a, b) { | ||||
|         return a.compare_by_name(b); | ||||
|     }, | ||||
|  | ||||
|     addApp: function(app) { | ||||
|         this._addItem(app); | ||||
|     _keyFocusIn: function(actor) { | ||||
|         Util.ensureActorVisibleInScrollView(this.actor, actor); | ||||
|     }, | ||||
|  | ||||
|     createFolderIcon: function(size) { | ||||
|         let icon = new St.Widget({ layout_manager: new Clutter.BinLayout(), | ||||
|                                    style_class: 'app-folder-icon', | ||||
|                                    width: size, height: size }); | ||||
|         let layout = new Clutter.TableLayout(); | ||||
|         let icon = new St.Widget({ layout_manager: layout, | ||||
|                                    style_class: 'app-folder-icon' }); | ||||
|         layout.hookup_style(icon); | ||||
|         let subSize = Math.floor(FOLDER_SUBICON_FRACTION * size); | ||||
|  | ||||
|         let aligns = [ Clutter.ActorAlign.START, Clutter.ActorAlign.END ]; | ||||
|         for (let i = 0; i < Math.min(this._allItems.length, 4); i++) { | ||||
|             let texture = this._allItems[i].create_icon_texture(subSize); | ||||
|             let bin = new St.Bin({ child: texture, | ||||
|                                    x_expand: true, y_expand: true }); | ||||
|             bin.set_x_align(aligns[i % 2]); | ||||
|             bin.set_y_align(aligns[Math.floor(i / 2)]); | ||||
|             icon.add_actor(bin); | ||||
|         let numItems = this._allItems.length; | ||||
|         for (let i = 0; i < 4; i++) { | ||||
|             let bin; | ||||
|             if (i < numItems) { | ||||
|                 let texture = this._allItems[i].app.create_icon_texture(subSize); | ||||
|                 bin = new St.Bin({ child: texture }); | ||||
|             } else { | ||||
|                 bin = new St.Bin({ width: subSize, height: subSize }); | ||||
|             } | ||||
|             layout.pack(bin, i % 2, Math.floor(i / 2)); | ||||
|         } | ||||
|  | ||||
|         return icon; | ||||
| @@ -1041,10 +1028,12 @@ const FolderView = new Lang.Class({ | ||||
| const FolderIcon = new Lang.Class({ | ||||
|     Name: 'FolderIcon', | ||||
|  | ||||
|     _init: function(dir, parentView) { | ||||
|         this._dir = dir; | ||||
|     _init: function(id, path, parentView) { | ||||
|         this.id = id; | ||||
|         this._parentView = parentView; | ||||
|  | ||||
|         this._folder = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders.folder', | ||||
|                                           path: path }); | ||||
|         this.actor = new St.Button({ style_class: 'app-well-app app-folder', | ||||
|                                      button_mask: St.ButtonMask.ONE, | ||||
|                                      toggle_mode: true, | ||||
| @@ -1055,15 +1044,11 @@ const FolderIcon = new Lang.Class({ | ||||
|         // whether we need to update arrow side, position etc. | ||||
|         this._popupInvalidated = false; | ||||
|  | ||||
|         let label = this._dir.get_name(); | ||||
|         this.icon = new IconGrid.BaseIcon(label, | ||||
|                                           { createIcon: Lang.bind(this, this._createIcon), setSizeManually: true }); | ||||
|         this.icon = new IconGrid.BaseIcon('', { createIcon: Lang.bind(this, this._createIcon), setSizeManually: true }); | ||||
|         this.actor.set_child(this.icon.actor); | ||||
|         this.actor.label_actor = this.icon.label; | ||||
|  | ||||
|         this.view = new FolderView(); | ||||
|         _loadCategory(dir, this.view); | ||||
|         this.view.loadGrid(); | ||||
|  | ||||
|         this.actor.connect('clicked', Lang.bind(this, | ||||
|             function() { | ||||
| @@ -1076,6 +1061,63 @@ const FolderIcon = new Lang.Class({ | ||||
|                 if (!this.actor.mapped && this._popup) | ||||
|                     this._popup.popdown(); | ||||
|             })); | ||||
|  | ||||
|         this._folder.connect('changed', Lang.bind(this, this._redisplay)); | ||||
|         this._redisplay(); | ||||
|     }, | ||||
|  | ||||
|     getAppIds: function() { | ||||
|         return this.view.getAllItems().map(function(item) { | ||||
|             return item.id; | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     _updateName: function() { | ||||
|         let name = _getFolderName(this._folder); | ||||
|         if (this.name == name) | ||||
|             return; | ||||
|  | ||||
|         this.name = name; | ||||
|         this.icon.label.text = this.name; | ||||
|         this.emit('name-changed'); | ||||
|     }, | ||||
|  | ||||
|     _redisplay: function() { | ||||
|         this._updateName(); | ||||
|  | ||||
|         this.view.removeAll(); | ||||
|  | ||||
|         let excludedApps = this._folder.get_strv('excluded-apps'); | ||||
|         let appSys = Shell.AppSystem.get_default(); | ||||
|         let addAppId = (function addAppId(appId) { | ||||
|             if (excludedApps.indexOf(appId) >= 0) | ||||
|                 return; | ||||
|  | ||||
|             let app = appSys.lookup_app(appId); | ||||
|             if (!app) | ||||
|                 return; | ||||
|  | ||||
|             if (!app.get_app_info().should_show()) | ||||
|                 return; | ||||
|  | ||||
|             let icon = new AppIcon(app); | ||||
|             this.view.addItem(icon); | ||||
|         }).bind(this); | ||||
|  | ||||
|         let folderApps = this._folder.get_strv('apps'); | ||||
|         folderApps.forEach(addAppId); | ||||
|  | ||||
|         let folderCategories = this._folder.get_strv('categories'); | ||||
|         Gio.AppInfo.get_all().forEach(function(appInfo) { | ||||
|             let appCategories = _getCategories(appInfo); | ||||
|             if (!_listsIntersect(folderCategories, appCategories)) | ||||
|                 return; | ||||
|  | ||||
|             addAppId(appInfo.get_id()); | ||||
|         }); | ||||
|  | ||||
|         this.view.loadGrid(); | ||||
|         this.emit('apps-changed'); | ||||
|     }, | ||||
|  | ||||
|     _createIcon: function(iconSize) { | ||||
| @@ -1154,6 +1196,7 @@ const FolderIcon = new Lang.Class({ | ||||
|         this._popupInvalidated = true; | ||||
|     }, | ||||
| }); | ||||
| Signals.addSignalMethods(FolderIcon.prototype); | ||||
|  | ||||
| const AppFolderPopup = new Lang.Class({ | ||||
|     Name: 'AppFolderPopup', | ||||
| @@ -1207,13 +1250,13 @@ const AppFolderPopup = new Lang.Class({ | ||||
|  | ||||
|     _onKeyPress: function(actor, event) { | ||||
|         if (!this._isOpen) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         if (event.get_key_symbol() != Clutter.KEY_Escape) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         this.popdown(); | ||||
|         return true; | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     toggle: function() { | ||||
| @@ -1272,6 +1315,9 @@ const AppIcon = new Lang.Class({ | ||||
|  | ||||
|     _init : function(app, iconParams) { | ||||
|         this.app = app; | ||||
|         this.id = app.get_id(); | ||||
|         this.name = app.get_name(); | ||||
|  | ||||
|         this.actor = new St.Button({ style_class: 'app-well-app', | ||||
|                                      reactive: true, | ||||
|                                      button_mask: St.ButtonMask.ONE | St.ButtonMask.TWO, | ||||
| @@ -1352,13 +1398,15 @@ const AppIcon = new Lang.Class({ | ||||
|             this._removeMenuTimeout(); | ||||
|             this._menuTimeoutId = Mainloop.timeout_add(MENU_POPUP_TIMEOUT, | ||||
|                 Lang.bind(this, function() { | ||||
|                     this._menuTimeoutId = 0; | ||||
|                     this.popupMenu(); | ||||
|                     return GLib.SOURCE_REMOVE; | ||||
|                 })); | ||||
|         } else if (button == 3) { | ||||
|             this.popupMenu(); | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } | ||||
|         return false; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onClicked: function(actor, button) { | ||||
| @@ -1367,10 +1415,6 @@ const AppIcon = new Lang.Class({ | ||||
|         if (button == 1) { | ||||
|             this._onActivate(Clutter.get_current_event()); | ||||
|         } else if (button == 2) { | ||||
|             // Last workspace is always empty | ||||
|             let launchWorkspace = global.screen.get_workspace_by_index(global.screen.n_workspaces - 1); | ||||
|             launchWorkspace.activate(global.get_current_time()); | ||||
|             this.emit('launching'); | ||||
|             this.app.open_new_window(-1); | ||||
|             Main.overview.hide(); | ||||
|         } | ||||
| @@ -1429,7 +1473,6 @@ const AppIcon = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _onActivate: function (event) { | ||||
|         this.emit('launching'); | ||||
|         let modifiers = event.get_state(); | ||||
|  | ||||
|         if (modifiers & Clutter.ModifierType.CONTROL_MASK | ||||
| @@ -1481,8 +1524,6 @@ const AppIconMenu = new Lang.Class({ | ||||
|  | ||||
|         this._source = source; | ||||
|  | ||||
|         this.connect('activate', Lang.bind(this, this._onActivate)); | ||||
|  | ||||
|         this.actor.add_style_class_name('app-well-menu'); | ||||
|  | ||||
|         // Chain our visibility and lifecycle to that of the source | ||||
| @@ -1498,7 +1539,9 @@ const AppIconMenu = new Lang.Class({ | ||||
|     _redisplay: function() { | ||||
|         this.removeAll(); | ||||
|  | ||||
|         let windows = this._source.app.get_windows(); | ||||
|         let windows = this._source.app.get_windows().filter(function(w) { | ||||
|             return !w.skip_taskbar; | ||||
|         }); | ||||
|  | ||||
|         // Display the app windows menu items and the separator between windows | ||||
|         // of the current desktop and other windows. | ||||
| @@ -1506,25 +1549,54 @@ const AppIconMenu = new Lang.Class({ | ||||
|         let separatorShown = windows.length > 0 && windows[0].get_workspace() != activeWorkspace; | ||||
|  | ||||
|         for (let i = 0; i < windows.length; i++) { | ||||
|             if (!separatorShown && windows[i].get_workspace() != activeWorkspace) { | ||||
|             let window = windows[i]; | ||||
|             if (!separatorShown && window.get_workspace() != activeWorkspace) { | ||||
|                 this._appendSeparator(); | ||||
|                 separatorShown = true; | ||||
|             } | ||||
|             let item = this._appendMenuItem(windows[i].title); | ||||
|             item._window = windows[i]; | ||||
|             let item = this._appendMenuItem(window.title); | ||||
|             item.connect('activate', Lang.bind(this, function() { | ||||
|                 this.emit('activate-window', window); | ||||
|             })); | ||||
|         } | ||||
|  | ||||
|         if (!this._source.app.is_window_backed()) { | ||||
|             if (windows.length > 0) | ||||
|                 this._appendSeparator(); | ||||
|             this._appendSeparator(); | ||||
|  | ||||
|             this._newWindowMenuItem = this._appendMenuItem(_("New Window")); | ||||
|             this._newWindowMenuItem.connect('activate', Lang.bind(this, function() { | ||||
|                 this._source.app.open_new_window(-1); | ||||
|                 this.emit('activate-window', null); | ||||
|             })); | ||||
|             this._appendSeparator(); | ||||
|  | ||||
|             let appInfo = this._source.app.get_app_info(); | ||||
|             let actions = appInfo.list_actions(); | ||||
|             for (let i = 0; i < actions.length; i++) { | ||||
|                 let action = actions[i]; | ||||
|                 let item = this._appendMenuItem(appInfo.get_action_name(action)); | ||||
|                 item.connect('activate', Lang.bind(this, function(emitter, event) { | ||||
|                     this._source.app.launch_action(action, event.get_time(), -1); | ||||
|                     this.emit('activate-window', null); | ||||
|                 })); | ||||
|             } | ||||
|             this._appendSeparator(); | ||||
|  | ||||
|             let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id()); | ||||
|  | ||||
|             this._newWindowMenuItem = this._appendMenuItem(_("New Window")); | ||||
|             this._appendSeparator(); | ||||
|  | ||||
|             this._toggleFavoriteMenuItem = this._appendMenuItem(isFavorite ? _("Remove from Favorites") | ||||
|                                                                 : _("Add to Favorites")); | ||||
|             if (isFavorite) { | ||||
|                 let item = this._appendMenuItem(_("Remove from Favorites")); | ||||
|                 item.connect('activate', Lang.bind(this, function() { | ||||
|                     let favs = AppFavorites.getAppFavorites(); | ||||
|                     favs.removeFavorite(this._source.app.get_id()); | ||||
|                 })); | ||||
|             } else { | ||||
|                 let item = this._appendMenuItem(_("Add to Favorites")); | ||||
|                 item.connect('activate', Lang.bind(this, function() { | ||||
|                     let favs = AppFavorites.getAppFavorites(); | ||||
|                     favs.addFavorite(this._source.app.get_id()); | ||||
|                 })); | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -1543,24 +1615,6 @@ const AppIconMenu = new Lang.Class({ | ||||
|     popup: function(activatingButton) { | ||||
|         this._redisplay(); | ||||
|         this.open(); | ||||
|     }, | ||||
|  | ||||
|     _onActivate: function (actor, child) { | ||||
|         if (child._window) { | ||||
|             let metaWindow = child._window; | ||||
|             this.emit('activate-window', metaWindow); | ||||
|         } else if (child == this._newWindowMenuItem) { | ||||
|             this._source.app.open_new_window(-1); | ||||
|             this.emit('activate-window', null); | ||||
|         } else if (child == this._toggleFavoriteMenuItem) { | ||||
|             let favs = AppFavorites.getAppFavorites(); | ||||
|             let isFavorite = favs.isFavorite(this._source.app.get_id()); | ||||
|             if (isFavorite) | ||||
|                 favs.removeFavorite(this._source.app.get_id()); | ||||
|             else | ||||
|                 favs.addFavorite(this._source.app.get_id()); | ||||
|         } | ||||
|         this.close(); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(AppIconMenu.prototype); | ||||
|   | ||||
| @@ -50,11 +50,9 @@ const BackgroundCache = new Lang.Class({ | ||||
|                                         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; | ||||
|  | ||||
| @@ -88,7 +86,6 @@ const BackgroundCache = new Lang.Class({ | ||||
|         } | ||||
|  | ||||
|         this._patterns.push(content); | ||||
|  | ||||
|         return content; | ||||
|     }, | ||||
|  | ||||
| @@ -116,9 +113,9 @@ const BackgroundCache = new Lang.Class({ | ||||
|  | ||||
|     _removeContent: function(contentList, content) { | ||||
|         let index = contentList.indexOf(content); | ||||
|  | ||||
|         if (index >= 0) | ||||
|             contentList.splice(index, 1); | ||||
|         if (index < 0) | ||||
|             throw new Error("Trying to remove invalid content: " + content); | ||||
|         contentList.splice(index, 1); | ||||
|     }, | ||||
|  | ||||
|     removePatternContent: function(content) { | ||||
| @@ -128,12 +125,32 @@ const BackgroundCache = new Lang.Class({ | ||||
|     removeImageContent: function(content) { | ||||
|         let filename = content.get_filename(); | ||||
|  | ||||
|         if (filename && this._fileMonitors[filename]) | ||||
|         let hasOtherUsers = this._images.some(function(content) { return filename == content.get_filename(); }); | ||||
|         if (!hasOtherUsers) | ||||
|             delete this._fileMonitors[filename]; | ||||
|  | ||||
|         this._removeContent(this._images, content); | ||||
|     }, | ||||
|  | ||||
|     _attachCallerToFileLoad: function(caller, fileLoad) { | ||||
|         fileLoad.callers.push(caller); | ||||
|  | ||||
|         if (!caller.cancellable) | ||||
|             return; | ||||
|  | ||||
|         caller.cancellable.connect(Lang.bind(this, function() { | ||||
|             let idx = fileLoad.callers.indexOf(caller); | ||||
|             fileLoad.callers.splice(idx, 1); | ||||
|  | ||||
|             if (fileLoad.callers.length == 0) { | ||||
|                 fileLoad.cancellable.cancel(); | ||||
|  | ||||
|                 let idx = this._pendingFileLoads.indexOf(fileLoad); | ||||
|                 this._pendingFileLoads.splice(idx, 1); | ||||
|             } | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _loadImageContent: function(params) { | ||||
|         params = Params.parse(params, { monitorIndex: 0, | ||||
|                                         style: null, | ||||
| @@ -142,27 +159,28 @@ const BackgroundCache = new Lang.Class({ | ||||
|                                         cancellable: null, | ||||
|                                         onFinished: null }); | ||||
|  | ||||
|         let caller = { monitorIndex: params.monitorIndex, | ||||
|                        effects: params.effects, | ||||
|                        cancellable: params.cancellable, | ||||
|                        onFinished: params.onFinished }; | ||||
|  | ||||
|         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 }); | ||||
|             let fileLoad = this._pendingFileLoads[i]; | ||||
|  | ||||
|             if (fileLoad.filename == params.filename && | ||||
|                 fileLoad.style == params.style) { | ||||
|                 this._attachCallerToFileLoad(caller, fileLoad); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         this._pendingFileLoads.push({ filename: params.filename, | ||||
|                                       style: params.style, | ||||
|                                       callers: [{ shouldCopy: false, | ||||
|                                                   monitorIndex: params.monitorIndex, | ||||
|                                                   effects: params.effects, | ||||
|                                                   onFinished: params.onFinished }] }); | ||||
|         let fileLoad = { filename: params.filename, | ||||
|                          style: params.style, | ||||
|                          cancellable: new Gio.Cancellable(), | ||||
|                          callers: [] }; | ||||
|         this._attachCallerToFileLoad(caller, fileLoad); | ||||
|  | ||||
|         let content = new Meta.Background({ meta_screen: global.screen, | ||||
|                                             monitor: params.monitorIndex, | ||||
|                                             effects: params.effects }); | ||||
|         let content = new Meta.Background({ meta_screen: global.screen }); | ||||
|  | ||||
|         content.load_file_async(params.filename, | ||||
|                                 params.style, | ||||
| @@ -173,31 +191,26 @@ const BackgroundCache = new Lang.Class({ | ||||
|                                                   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 i = 0; i < fileLoad.callers.length; i++) { | ||||
|                                                   let caller = fileLoad.callers[i]; | ||||
|                                                   if (caller.onFinished) { | ||||
|                                                       let newContent; | ||||
|  | ||||
|                                                   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); | ||||
|                                                       if (content) { | ||||
|                                                           newContent = content.copy(caller.monitorIndex, caller.effects); | ||||
|                                                           this._images.push(newContent); | ||||
|                                                       } | ||||
|                                                   } | ||||
|  | ||||
|                                                   this._pendingFileLoads.splice(i, 1); | ||||
|                                                       caller.onFinished(newContent); | ||||
|                                                   } | ||||
|                                               } | ||||
|  | ||||
|                                               let idx = this._pendingFileLoads.indexOf(fileLoad); | ||||
|                                               this._pendingFileLoads.splice(idx, 1); | ||||
|                                           })); | ||||
|     }, | ||||
|  | ||||
| @@ -210,11 +223,9 @@ const BackgroundCache = new Lang.Class({ | ||||
|                                         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; | ||||
|  | ||||
| @@ -222,7 +233,7 @@ const BackgroundCache = new Lang.Class({ | ||||
|                 continue; | ||||
|  | ||||
|             if (params.style == GDesktopEnums.BackgroundStyle.SPANNED && | ||||
|                 this._images[i].monitor_index != this._monitorIndex) | ||||
|                 this._images[i].monitor != params.monitorIndex) | ||||
|                 continue; | ||||
|  | ||||
|             candidateContent = this._images[i]; | ||||
| @@ -262,6 +273,7 @@ const BackgroundCache = new Lang.Class({ | ||||
|             if (params.onLoaded) { | ||||
|                 GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, function() { | ||||
|                     params.onLoaded(this._animation); | ||||
|                     return GLib.SOURCE_REMOVE; | ||||
|                 })); | ||||
|             } | ||||
|         } | ||||
| @@ -276,6 +288,7 @@ const BackgroundCache = new Lang.Class({ | ||||
|                            if (params.onLoaded) { | ||||
|                                GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, function() { | ||||
|                                    params.onLoaded(this._animation); | ||||
|                                    return GLib.SOURCE_REMOVE; | ||||
|                                })); | ||||
|                            } | ||||
|                        })); | ||||
| @@ -315,7 +328,6 @@ const Background = new Lang.Class({ | ||||
|  | ||||
|         this._brightness = 1.0; | ||||
|         this._vignetteSharpness = 0.2; | ||||
|         this._saturation = 1.0; | ||||
|         this._cancellable = new Gio.Cancellable(); | ||||
|         this.isLoaded = false; | ||||
|  | ||||
| @@ -376,7 +388,7 @@ const Background = new Lang.Class({ | ||||
|  | ||||
|         GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, function() { | ||||
|             this.emit('loaded'); | ||||
|             return false; | ||||
|             return GLib.SOURCE_REMOVE; | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
| @@ -415,29 +427,26 @@ const Background = new Lang.Class({ | ||||
|         this._fileWatches[filename] = signalId; | ||||
|     }, | ||||
|  | ||||
|     _addImage: function(content, index, filename) { | ||||
|         content.saturation = this._saturation; | ||||
|         content.brightness = this._brightness; | ||||
|         content.vignette_sharpness = this._vignetteSharpness; | ||||
|     _ensureImage: function(index) { | ||||
|         if (this._images[index]) | ||||
|             return; | ||||
|  | ||||
|         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; | ||||
|     _updateImage: function(index, content, filename) { | ||||
|         content.brightness = this._brightness; | ||||
|         content.vignette_sharpness = this._vignetteSharpness; | ||||
|  | ||||
|         this._cache.removeImageContent(this._images[index].content); | ||||
|         this._images[index].content = content; | ||||
|         let image = this._images[index]; | ||||
|         if (image.content) | ||||
|             this._cache.removeImageContent(content); | ||||
|         image.content = content; | ||||
|         this._watchCacheFile(filename); | ||||
|     }, | ||||
|  | ||||
| @@ -485,11 +494,8 @@ const Background = new Lang.Class({ | ||||
|                                                   return; | ||||
|                                               } | ||||
|  | ||||
|                                               if (!this._images[i]) { | ||||
|                                                   this._addImage(content, i, files[i]); | ||||
|                                               } else { | ||||
|                                                   this._updateImage(content, i, files[i]); | ||||
|                                               } | ||||
|                                               this._ensureImage(i); | ||||
|                                               this._updateImage(i, content, files[i]); | ||||
|  | ||||
|                                               if (numPendingImages == 0) { | ||||
|                                                   this._setLoaded(); | ||||
| @@ -524,7 +530,7 @@ const Background = new Lang.Class({ | ||||
|                                                       Lang.bind(this, function() { | ||||
|                                                                     this._updateAnimationTimeoutId = 0; | ||||
|                                                                     this._updateAnimation(); | ||||
|                                                                     return false; | ||||
|                                                                     return GLib.SOURCE_REMOVE; | ||||
|                                                                 })); | ||||
|     }, | ||||
|  | ||||
| @@ -544,30 +550,33 @@ const Background = new Lang.Class({ | ||||
|                                            }); | ||||
|     }, | ||||
|  | ||||
|     _loadFile: function(filename) { | ||||
|     _loadImage: 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; | ||||
|                                           if (content) { | ||||
|                                               this._ensureImage(0); | ||||
|                                               this._updateImage(0, content, filename); | ||||
|                                           } | ||||
|  | ||||
|                                           this._addImage(content, 0, filename); | ||||
|                                           this._setLoaded(); | ||||
|                                       }) | ||||
|                                     }); | ||||
|     }, | ||||
|  | ||||
|     _loadFile: function(filename) { | ||||
|         if (filename.endsWith('.xml')) | ||||
|             this._loadAnimation(filename); | ||||
|         else | ||||
|             this._loadImage(filename); | ||||
|     }, | ||||
|  | ||||
|     _load: function () { | ||||
|         this._cache = getBackgroundCache(); | ||||
|  | ||||
|         this._loadPattern(this._cache); | ||||
|         this._loadPattern(); | ||||
|  | ||||
|         this._style = this._settings.get_enum(BACKGROUND_STYLE_KEY); | ||||
|         if (this._style == GDesktopEnums.BackgroundStyle.NONE) { | ||||
| @@ -590,24 +599,6 @@ const Background = new Lang.Class({ | ||||
|         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; | ||||
|     }, | ||||
| @@ -659,7 +650,13 @@ const SystemBackground = new Lang.Class({ | ||||
|                                           this.emit('loaded'); | ||||
|                                       }) | ||||
|                                     }); | ||||
|     } | ||||
|  | ||||
|         this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); | ||||
|     }, | ||||
|  | ||||
|     _onDestroy: function() { | ||||
|         this._cache.removeImageContent(this.actor.content); | ||||
|     }, | ||||
| }); | ||||
| Signals.addSignalMethods(SystemBackground.prototype); | ||||
|  | ||||
| @@ -747,30 +744,31 @@ const BackgroundManager = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _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; | ||||
|     _updateBackground: function() { | ||||
|         let newBackground = this._createBackground(); | ||||
|         newBackground.vignetteSharpness = this.background.vignetteSharpness; | ||||
|         newBackground.brightness = this.background.brightness; | ||||
|         newBackground.visible = this.background.visible; | ||||
|  | ||||
|         newBackground.loadedSignalId = newBackground.connect('loaded', | ||||
|             Lang.bind(this, function() { | ||||
|                 newBackground.disconnect(newBackground.loadedSignalId); | ||||
|                 newBackground.loadedSignalId = 0; | ||||
|                 Tweener.addTween(background.actor, | ||||
|  | ||||
|                 if (this._newBackground != newBackground) { | ||||
|                     /* Not interesting, we queued another load */ | ||||
|                     newBackground.actor.destroy(); | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 Tweener.addTween(this.background.actor, | ||||
|                                  { opacity: 0, | ||||
|                                    time: FADE_ANIMATION_TIME, | ||||
|                                    transition: 'easeOutQuad', | ||||
|                                    onComplete: Lang.bind(this, function() { | ||||
|                                        if (this._newBackground == newBackground) { | ||||
|                                            this.background = newBackground; | ||||
|                                            this._newBackground = null; | ||||
|                                        } else { | ||||
|                                            newBackground.actor.destroy(); | ||||
|                                        } | ||||
|  | ||||
|                                        background.actor.destroy(); | ||||
|                                        this.background.actor.destroy(); | ||||
|                                        this.background = newBackground; | ||||
|                                        this._newBackground = null; | ||||
|  | ||||
|                                        this.emit('changed'); | ||||
|                                    }) | ||||
| @@ -798,7 +796,7 @@ const BackgroundManager = new Lang.Class({ | ||||
|         background.changeSignalId = background.connect('changed', Lang.bind(this, function() { | ||||
|             background.disconnect(background.changeSignalId); | ||||
|             background.changeSignalId = 0; | ||||
|             this._updateBackground(background, this._monitorIndex); | ||||
|             this._updateBackground(); | ||||
|         })); | ||||
|  | ||||
|         background.actor.connect('destroy', Lang.bind(this, function() { | ||||
|   | ||||
| @@ -13,8 +13,8 @@ const BackgroundMenu = new Lang.Class({ | ||||
|     Name: 'BackgroundMenu', | ||||
|     Extends: PopupMenu.PopupMenu, | ||||
|  | ||||
|     _init: function(source) { | ||||
|         this.parent(source, 0, St.Side.TOP); | ||||
|     _init: function(layoutManager) { | ||||
|         this.parent(layoutManager.dummyCursor, 0, St.Side.TOP); | ||||
|  | ||||
|         this.addSettingsAction(_("Settings"), 'gnome-control-center.desktop'); | ||||
|         this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); | ||||
| @@ -22,23 +22,20 @@ const BackgroundMenu = new Lang.Class({ | ||||
|  | ||||
|         this.actor.add_style_class_name('background-menu'); | ||||
|  | ||||
|         Main.uiGroup.add_actor(this.actor); | ||||
|         layoutManager.uiGroup.add_actor(this.actor); | ||||
|         this.actor.hide(); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| function addBackgroundMenu(actor) { | ||||
|     let cursor = new St.Bin({ opacity: 0 }); | ||||
|     Main.uiGroup.add_actor(cursor); | ||||
|  | ||||
| function addBackgroundMenu(actor, layoutManager) { | ||||
|     actor.reactive = true; | ||||
|     actor._backgroundMenu = new BackgroundMenu(cursor); | ||||
|     actor._backgroundMenu = new BackgroundMenu(layoutManager); | ||||
|     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); | ||||
|         Main.layoutManager.setDummyCursorGeometry(x, y, 0, 0); | ||||
|         actor._backgroundMenu.open(BoxPointer.PopupAnimation.NONE); | ||||
|     } | ||||
|  | ||||
| @@ -59,10 +56,8 @@ function addBackgroundMenu(actor) { | ||||
|     actor.add_action(clickAction); | ||||
|  | ||||
|     actor.connect('destroy', function() { | ||||
|                       actor._backgroundMenu.destroy(); | ||||
|                       actor._backgroundMenu = null; | ||||
|                       actor._backgroundManager = null; | ||||
|  | ||||
|                       cursor.destroy(); | ||||
|                   }); | ||||
|         actor._backgroundMenu.destroy(); | ||||
|         actor._backgroundMenu = null; | ||||
|         actor._backgroundManager = null; | ||||
|     }); | ||||
| } | ||||
|   | ||||
| @@ -69,7 +69,7 @@ const BoxPointer = new Lang.Class({ | ||||
|     _muteInput: function() { | ||||
|         if (this._capturedEventId == 0) | ||||
|             this._capturedEventId = this.actor.connect('captured-event', | ||||
|                                                        function() { return true; }); | ||||
|                                                        function() { return Clutter.EVENT_STOP; }); | ||||
|     }, | ||||
|  | ||||
|     _unmuteInput: function() { | ||||
| @@ -121,6 +121,9 @@ const BoxPointer = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     hide: function(animate, onComplete) { | ||||
|         if (!this.actor.visible) | ||||
|             return; | ||||
|  | ||||
|         let xOffset = 0; | ||||
|         let yOffset = 0; | ||||
|         let themeNode = this.actor.get_theme_node(); | ||||
| @@ -284,38 +287,40 @@ const BoxPointer = new Lang.Class({ | ||||
|         let skipBottomLeft = false; | ||||
|         let skipBottomRight = false; | ||||
|  | ||||
|         switch (this._arrowSide) { | ||||
|         case St.Side.TOP: | ||||
|             if (this._arrowOrigin == x1) | ||||
|                 skipTopLeft = true; | ||||
|             else if (this._arrowOrigin == x2) | ||||
|                 skipTopRight = true; | ||||
|             break; | ||||
|         if (rise) { | ||||
|             switch (this._arrowSide) { | ||||
|             case St.Side.TOP: | ||||
|                 if (this._arrowOrigin == x1) | ||||
|                     skipTopLeft = true; | ||||
|                 else if (this._arrowOrigin == x2) | ||||
|                     skipTopRight = true; | ||||
|                 break; | ||||
|  | ||||
|         case St.Side.RIGHT: | ||||
|             if (this._arrowOrigin == y1) | ||||
|                 skipTopRight = true; | ||||
|             else if (this._arrowOrigin == y2) | ||||
|                 skipBottomRight = true; | ||||
|             break; | ||||
|             case St.Side.RIGHT: | ||||
|                 if (this._arrowOrigin == y1) | ||||
|                     skipTopRight = true; | ||||
|                 else if (this._arrowOrigin == y2) | ||||
|                     skipBottomRight = true; | ||||
|                 break; | ||||
|  | ||||
|         case St.Side.BOTTOM: | ||||
|             if (this._arrowOrigin == x1) | ||||
|                 skipBottomLeft = true; | ||||
|             else if (this._arrowOrigin == x2) | ||||
|                 skipBottomRight = true; | ||||
|             break; | ||||
|             case St.Side.BOTTOM: | ||||
|                 if (this._arrowOrigin == x1) | ||||
|                     skipBottomLeft = true; | ||||
|                 else if (this._arrowOrigin == x2) | ||||
|                     skipBottomRight = true; | ||||
|                 break; | ||||
|  | ||||
|         case St.Side.LEFT: | ||||
|             if (this._arrowOrigin == y1) | ||||
|                 skipTopLeft = true; | ||||
|             else if (this._arrowOrigin == y2) | ||||
|                 skipBottomLeft = true; | ||||
|             break; | ||||
|             case St.Side.LEFT: | ||||
|                 if (this._arrowOrigin == y1) | ||||
|                     skipTopLeft = true; | ||||
|                 else if (this._arrowOrigin == y2) | ||||
|                     skipBottomLeft = true; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         cr.moveTo(x1 + borderRadius, y1); | ||||
|         if (this._arrowSide == St.Side.TOP) { | ||||
|         if (this._arrowSide == St.Side.TOP && rise) { | ||||
|             if (skipTopLeft) { | ||||
|                 cr.moveTo(x1, y2 - borderRadius); | ||||
|                 cr.lineTo(x1, y1 - rise); | ||||
| @@ -337,7 +342,7 @@ const BoxPointer = new Lang.Class({ | ||||
|                    3*Math.PI/2, Math.PI*2); | ||||
|         } | ||||
|  | ||||
|         if (this._arrowSide == St.Side.RIGHT) { | ||||
|         if (this._arrowSide == St.Side.RIGHT && rise) { | ||||
|             if (skipTopRight) { | ||||
|                 cr.lineTo(x2 + rise, y1); | ||||
|                 cr.lineTo(x2 + rise, y1 + halfBase); | ||||
| @@ -358,7 +363,7 @@ const BoxPointer = new Lang.Class({ | ||||
|                    0, Math.PI/2); | ||||
|         } | ||||
|  | ||||
|         if (this._arrowSide == St.Side.BOTTOM) { | ||||
|         if (this._arrowSide == St.Side.BOTTOM && rise) { | ||||
|             if (skipBottomLeft) { | ||||
|                 cr.lineTo(x1 + halfBase, y2); | ||||
|                 cr.lineTo(x1, y2 + rise); | ||||
| @@ -379,7 +384,7 @@ const BoxPointer = new Lang.Class({ | ||||
|                    Math.PI/2, Math.PI); | ||||
|         } | ||||
|  | ||||
|         if (this._arrowSide == St.Side.LEFT) { | ||||
|         if (this._arrowSide == St.Side.LEFT && rise) { | ||||
|             if (skipTopLeft) { | ||||
|                 cr.lineTo(x1, y1 + halfBase); | ||||
|                 cr.lineTo(x1 - rise, y1); | ||||
|   | ||||
| @@ -17,16 +17,18 @@ const SHOW_WEEKDATE_KEY = 'show-weekdate'; | ||||
| // in org.gnome.desktop.interface | ||||
| const CLOCK_FORMAT_KEY        = 'clock-format'; | ||||
|  | ||||
| function _sameDay(dateA, dateB) { | ||||
|     return (dateA.getDate() == dateB.getDate() && | ||||
|             dateA.getMonth() == dateB.getMonth() && | ||||
|             dateA.getYear() == dateB.getYear()); | ||||
| } | ||||
|  | ||||
| function _sameYear(dateA, dateB) { | ||||
|     return (dateA.getYear() == dateB.getYear()); | ||||
| } | ||||
|  | ||||
| function _sameMonth(dateA, dateB) { | ||||
|     return _sameYear(dateA, dateB) && (dateA.getMonth() == dateB.getMonth()); | ||||
| } | ||||
|  | ||||
| function _sameDay(dateA, dateB) { | ||||
|     return _sameMonth(dateA, dateB) && (dateA.getDate() == dateB.getDate()); | ||||
| } | ||||
|  | ||||
| /* TODO: maybe needs config - right now we assume that Saturday and | ||||
|  * Sunday are non-work days (not true in e.g. Israel, it's Sunday and | ||||
|  * Monday there) | ||||
| @@ -190,16 +192,18 @@ const EmptyEventSource = new Lang.Class({ | ||||
| }); | ||||
| Signals.addSignalMethods(EmptyEventSource.prototype); | ||||
|  | ||||
| const CalendarServerIface = <interface name="org.gnome.Shell.CalendarServer"> | ||||
| <method name="GetEvents"> | ||||
|     <arg type="x" direction="in" /> | ||||
|     <arg type="x" direction="in" /> | ||||
|     <arg type="b" direction="in" /> | ||||
|     <arg type="a(sssbxxa{sv})" direction="out" /> | ||||
| </method> | ||||
| <property name="HasCalendars" type="b" access="read" /> | ||||
| <signal name="Changed" /> | ||||
| </interface>; | ||||
| const CalendarServerIface = '<node> \ | ||||
| <interface name="org.gnome.Shell.CalendarServer"> \ | ||||
| <method name="GetEvents"> \ | ||||
|     <arg type="x" direction="in" /> \ | ||||
|     <arg type="x" direction="in" /> \ | ||||
|     <arg type="b" direction="in" /> \ | ||||
|     <arg type="a(sssbxxa{sv})" direction="out" /> \ | ||||
| </method> \ | ||||
| <property name="HasCalendars" type="b" access="read" /> \ | ||||
| <signal name="Changed" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const CalendarServerInfo  = Gio.DBusInterfaceInfo.new_for_xml(CalendarServerIface); | ||||
|  | ||||
| @@ -327,25 +331,22 @@ const DBusEventSource = new Lang.Class({ | ||||
|             return; | ||||
|  | ||||
|         if (this._curRequestBegin && this._curRequestEnd){ | ||||
|             let callFlags = Gio.DBusCallFlags.NO_AUTO_START; | ||||
|             if (forceReload) | ||||
|                 callFlags = Gio.DBusCallFlags.NONE; | ||||
|             this._dbusProxy.GetEventsRemote(this._curRequestBegin.getTime() / 1000, | ||||
|                                             this._curRequestEnd.getTime() / 1000, | ||||
|                                             forceReload, | ||||
|                                             Lang.bind(this, this._onEventsReceived), | ||||
|                                             callFlags); | ||||
|                                             Gio.DBusCallFlags.NONE); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     requestRange: function(begin, end, forceReload) { | ||||
|         if (forceReload || !(_datesEqual(begin, this._lastRequestBegin) && _datesEqual(end, this._lastRequestEnd))) { | ||||
|     requestRange: function(begin, end) { | ||||
|         if (!(_datesEqual(begin, this._lastRequestBegin) && _datesEqual(end, this._lastRequestEnd))) { | ||||
|             this.isLoading = true; | ||||
|             this._lastRequestBegin = begin; | ||||
|             this._lastRequestEnd = end; | ||||
|             this._curRequestBegin = begin; | ||||
|             this._curRequestEnd = end; | ||||
|             this._loadEvents(forceReload); | ||||
|             this._loadEvents(false); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -402,6 +403,8 @@ const Calendar = new Lang.Class({ | ||||
|         // Start off with the current date | ||||
|         this._selectedDate = new Date(); | ||||
|  | ||||
|         this._shouldDateGrabFocus = false; | ||||
|  | ||||
|         this.actor = new St.Table({ homogeneous: false, | ||||
|                                     style_class: 'calendar', | ||||
|                                     reactive: true }); | ||||
| @@ -417,21 +420,21 @@ const Calendar = new Lang.Class({ | ||||
|     setEventSource: function(eventSource) { | ||||
|         this._eventSource = eventSource; | ||||
|         this._eventSource.connect('changed', Lang.bind(this, function() { | ||||
|             this._update(false); | ||||
|             this._rebuildCalendar(); | ||||
|             this._update(); | ||||
|         })); | ||||
|         this._update(true); | ||||
|         this._rebuildCalendar(); | ||||
|         this._update(); | ||||
|     }, | ||||
|  | ||||
|     // Sets the calendar to show a specific date | ||||
|     setDate: function(date, forceReload) { | ||||
|         if (!_sameDay(date, this._selectedDate)) { | ||||
|             this._selectedDate = date; | ||||
|             this._update(forceReload); | ||||
|             this.emit('selected-date-changed', new Date(this._selectedDate)); | ||||
|         } else { | ||||
|             if (forceReload) | ||||
|                 this._update(forceReload); | ||||
|         } | ||||
|     setDate: function(date) { | ||||
|         if (_sameDay(date, this._selectedDate)) | ||||
|             return; | ||||
|  | ||||
|         this._selectedDate = date; | ||||
|         this._update(); | ||||
|         this.emit('selected-date-changed', new Date(this._selectedDate)); | ||||
|     }, | ||||
|  | ||||
|     _buildHeader: function() { | ||||
| @@ -495,6 +498,7 @@ const Calendar = new Lang.Class({ | ||||
|             this._onNextMonthButtonClicked(); | ||||
|             break; | ||||
|         } | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onPrevMonthButtonClicked: function() { | ||||
| @@ -518,7 +522,7 @@ const Calendar = new Lang.Class({ | ||||
|  | ||||
|         this._backButton.grab_key_focus(); | ||||
|  | ||||
|         this.setDate(newDate, false); | ||||
|         this.setDate(newDate); | ||||
|     }, | ||||
|  | ||||
|     _onNextMonthButtonClicked: function() { | ||||
| @@ -542,28 +546,26 @@ const Calendar = new Lang.Class({ | ||||
|  | ||||
|         this._forwardButton.grab_key_focus(); | ||||
|  | ||||
|         this.setDate(newDate, false); | ||||
|         this.setDate(newDate); | ||||
|     }, | ||||
|  | ||||
|     _onSettingsChange: function() { | ||||
|         this._useWeekdate = this._settings.get_boolean(SHOW_WEEKDATE_KEY); | ||||
|         this._buildHeader(); | ||||
|         this._update(false); | ||||
|         this._rebuildCalendar(); | ||||
|         this._update(); | ||||
|     }, | ||||
|  | ||||
|     _update: function(forceReload) { | ||||
|     _rebuildCalendar: function() { | ||||
|         let now = new Date(); | ||||
|  | ||||
|         if (_sameYear(this._selectedDate, now)) | ||||
|             this._monthLabel.text = this._selectedDate.toLocaleFormat(this._headerFormatWithoutYear); | ||||
|         else | ||||
|             this._monthLabel.text = this._selectedDate.toLocaleFormat(this._headerFormat); | ||||
|  | ||||
|         // Remove everything but the topBox and the weekday labels | ||||
|         let children = this.actor.get_children(); | ||||
|         for (let i = this._firstDayIndex; i < children.length; i++) | ||||
|             children[i].destroy(); | ||||
|  | ||||
|         this._buttons = []; | ||||
|  | ||||
|         // 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 | ||||
| @@ -581,11 +583,13 @@ const Calendar = new Lang.Class({ | ||||
|         // Actually computing the number of weeks is complex, but we know that the | ||||
|         // problematic categories (2 and 4) always start on week start, and that | ||||
|         // all months at the end have 6 weeks. | ||||
|  | ||||
|         let beginDate = new Date(this._selectedDate); | ||||
|         beginDate.setDate(1); | ||||
|         beginDate.setSeconds(0); | ||||
|         beginDate.setHours(12); | ||||
|  | ||||
|         this._calendarBegin = new Date(beginDate); | ||||
|  | ||||
|         let year = beginDate.getYear(); | ||||
|  | ||||
|         let daysToWeekStart = (7 + beginDate.getDay() - this._weekStart) % 7; | ||||
| @@ -606,13 +610,10 @@ const Calendar = new Lang.Class({ | ||||
|             if (this._eventSource.isDummy) | ||||
|                 button.reactive = false; | ||||
|  | ||||
|             let iterStr = iter.toUTCString(); | ||||
|             button._date = new Date(iter); | ||||
|             button.connect('clicked', Lang.bind(this, function() { | ||||
|                 this._shouldDateGrabFocus = true; | ||||
|  | ||||
|                 let newlySelectedDate = new Date(iterStr); | ||||
|                 this.setDate(newlySelectedDate, false); | ||||
|  | ||||
|                 this.setDate(button._date); | ||||
|                 this._shouldDateGrabFocus = false; | ||||
|             })); | ||||
|  | ||||
| @@ -620,9 +621,9 @@ const Calendar = new Lang.Class({ | ||||
|             let styleClass = 'calendar-day-base calendar-day'; | ||||
|  | ||||
|             if (_isWorkDay(iter)) | ||||
|                 styleClass += ' calendar-work-day' | ||||
|                 styleClass += ' calendar-work-day'; | ||||
|             else | ||||
|                 styleClass += ' calendar-nonwork-day' | ||||
|                 styleClass += ' calendar-nonwork-day'; | ||||
|  | ||||
|             // Hack used in lieu of border-collapse - see gnome-shell.css | ||||
|             if (row == 2) | ||||
| @@ -639,7 +640,7 @@ const Calendar = new Lang.Class({ | ||||
|                 styleClass += ' calendar-other-month-day'; | ||||
|  | ||||
|             if (hasEvents) | ||||
|                 styleClass += ' calendar-day-with-events' | ||||
|                 styleClass += ' calendar-day-with-events'; | ||||
|  | ||||
|             button.style_class = styleClass; | ||||
|  | ||||
| @@ -647,12 +648,7 @@ const Calendar = new Lang.Class({ | ||||
|             this.actor.add(button, | ||||
|                            { row: row, col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7 }); | ||||
|  | ||||
|             if (_sameDay(this._selectedDate, iter)) { | ||||
|                 button.add_style_pseudo_class('active'); | ||||
|  | ||||
|                 if (this._shouldDateGrabFocus) | ||||
|                     button.grab_key_focus(); | ||||
|             } | ||||
|             this._buttons.push(button); | ||||
|  | ||||
|             if (this._useWeekdate && iter.getDay() == 4) { | ||||
|                 let label = new St.Label({ text: _getCalendarWeekForDate(iter).toString(), | ||||
| @@ -666,9 +662,32 @@ const Calendar = new Lang.Class({ | ||||
|             if (iter.getDay() == this._weekStart) | ||||
|                 row++; | ||||
|         } | ||||
|  | ||||
|         // Signal to the event source that we are interested in events | ||||
|         // only from this date range | ||||
|         this._eventSource.requestRange(beginDate, iter, forceReload); | ||||
|         this._eventSource.requestRange(beginDate, iter); | ||||
|     }, | ||||
|  | ||||
|     _update: function() { | ||||
|         let now = new Date(); | ||||
|  | ||||
|         if (_sameYear(this._selectedDate, now)) | ||||
|             this._monthLabel.text = this._selectedDate.toLocaleFormat(this._headerFormatWithoutYear); | ||||
|         else | ||||
|             this._monthLabel.text = this._selectedDate.toLocaleFormat(this._headerFormat); | ||||
|  | ||||
|         if (!this._calendarBegin || !_sameMonth(this._selectedDate, this._calendarBegin)) | ||||
|             this._rebuildCalendar(); | ||||
|  | ||||
|         this._buttons.forEach(Lang.bind(this, function(button) { | ||||
|             if (_sameDay(button._date, this._selectedDate)) { | ||||
|                 button.add_style_pseudo_class('active'); | ||||
|                 if (this._shouldDateGrabFocus) | ||||
|                     button.grab_key_focus(); | ||||
|             } | ||||
|             else | ||||
|                 button.remove_style_pseudo_class('active'); | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -77,7 +77,7 @@ const AutomountManager = new Lang.Class({ | ||||
|         })); | ||||
|  | ||||
|         this._mountAllId = 0; | ||||
|         return false; | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|     }, | ||||
|  | ||||
|     _onDriveConnected: function() { | ||||
| @@ -236,7 +236,7 @@ const AutomountManager = new Lang.Class({ | ||||
|     _allowAutorunExpire: function(volume) { | ||||
|         Mainloop.timeout_add_seconds(AUTORUN_EXPIRE_TIMEOUT_SECS, function() { | ||||
|             volume.allowAutorun = false; | ||||
|             return false; | ||||
|             return GLib.SOURCE_REMOVE; | ||||
|         }); | ||||
|     } | ||||
| }); | ||||
|   | ||||
| @@ -64,7 +64,7 @@ function startAppForMount(app, mount) { | ||||
|  | ||||
|     try { | ||||
|         retval = app.launch(files,  | ||||
|                             global.create_app_launch_context()) | ||||
|                             global.create_app_launch_context(0, -1)) | ||||
|     } catch (e) { | ||||
|         log('Unable to launch the application ' + app.get_name() | ||||
|             + ': ' + e.toString()); | ||||
| @@ -75,12 +75,14 @@ function startAppForMount(app, mount) { | ||||
|  | ||||
| /******************************************/ | ||||
|  | ||||
| const HotplugSnifferIface = <interface name="org.gnome.Shell.HotplugSniffer"> | ||||
| <method name="SniffURI"> | ||||
|     <arg type="s" direction="in" /> | ||||
|     <arg type="as" direction="out" /> | ||||
| </method> | ||||
| </interface>; | ||||
| const HotplugSnifferIface = '<node> \ | ||||
| <interface name="org.gnome.Shell.HotplugSniffer"> \ | ||||
| <method name="SniffURI"> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
|     <arg type="as" direction="out" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const HotplugSnifferProxy = Gio.DBusProxy.makeProxyWrapper(HotplugSnifferIface); | ||||
| function HotplugSniffer() { | ||||
|   | ||||
| @@ -45,7 +45,9 @@ const KeyringDialog = new Lang.Class({ | ||||
|         this.prompt.bind_property('message', subject, 'text', GObject.BindingFlags.SYNC_CREATE); | ||||
|  | ||||
|         this._messageBox.add(subject, | ||||
|                              { y_fill:  false, | ||||
|                              { x_fill: false, | ||||
|                                y_fill:  false, | ||||
|                                x_align: St.Align.START, | ||||
|                                y_align: St.Align.START }); | ||||
|  | ||||
|         let description = new St.Label({ style_class: 'prompt-dialog-description' }); | ||||
| @@ -136,6 +138,7 @@ const KeyringDialog = new Lang.Class({ | ||||
|         warning.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; | ||||
|         warning.clutter_text.line_wrap = true; | ||||
|         layout.pack(warning, 1, row); | ||||
|         layout.child_set(warning, { x_fill: false, x_align: Clutter.TableAlignment.START }); | ||||
|         this.prompt.bind_property('warning-visible', warning, 'visible', GObject.BindingFlags.SYNC_CREATE); | ||||
|         this.prompt.bind_property('warning', warning, 'text', GObject.BindingFlags.SYNC_CREATE); | ||||
|  | ||||
|   | ||||
| @@ -138,6 +138,8 @@ const NetworkSecretDialog = new Lang.Class({ | ||||
|                            key:    Clutter.KEY_Escape, | ||||
|                          }, | ||||
|                          this._okButton]); | ||||
|  | ||||
|         this._updateOkButton(); | ||||
|     }, | ||||
|  | ||||
|     _updateOkButton: function() { | ||||
| @@ -253,6 +255,7 @@ const NetworkSecretDialog = new Lang.Class({ | ||||
|         case 'leap': | ||||
|         case 'ttls': | ||||
|         case 'peap': | ||||
|         case 'fast': | ||||
|             // TTLS and PEAP are actually much more complicated, but this complication | ||||
|             // is not visible here since we only care about phase2 authentication | ||||
|             // (and don't even care of which one) | ||||
| @@ -306,7 +309,7 @@ const NetworkSecretDialog = new Lang.Class({ | ||||
|             wirelessSetting = this._connection.get_setting_wireless(); | ||||
|             ssid = NetworkManager.utils_ssid_to_utf8(wirelessSetting.get_ssid()); | ||||
|             content.title = _("Authentication required by wireless network"); | ||||
|             content.message = _("Passwords or encryption keys are required to access the wireless network '%s'.").format(ssid); | ||||
|             content.message = _("Passwords or encryption keys are required to access the wireless network “%s”.").format(ssid); | ||||
|             this._getWirelessSecrets(content.secrets, wirelessSetting); | ||||
|             break; | ||||
|         case '802-3-ethernet': | ||||
| @@ -333,7 +336,7 @@ const NetworkSecretDialog = new Lang.Class({ | ||||
|         case 'cdma': | ||||
|         case 'bluetooth': | ||||
|             content.title = _("Mobile broadband network password"); | ||||
|             content.message = _("A password is required to connect to '%s'.").format(connectionSetting.get_id()); | ||||
|             content.message = _("A password is required to connect to “%s”.").format(connectionSetting.get_id()); | ||||
|             this._getMobileSecrets(content.secrets, connectionType); | ||||
|             break; | ||||
|         default: | ||||
| @@ -370,6 +373,12 @@ const VPNRequestHandler = new Lang.Class({ | ||||
|             argv.push('-i'); | ||||
|         if (flags & NMClient.SecretAgentGetSecretsFlags.REQUEST_NEW) | ||||
|             argv.push('-r'); | ||||
|         if (authHelper.supportsHints) { | ||||
|             for (let i = 0; i < hints.length; i++) { | ||||
|                 argv.push('-t'); | ||||
|                 argv.push(hints[i]); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         this._newStylePlugin = authHelper.externalUIMode; | ||||
|  | ||||
| @@ -432,6 +441,7 @@ const VPNRequestHandler = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _vpnChildFinished: function(pid, status, requestObj) { | ||||
|         this._childWatch = 0; | ||||
|         if (this._newStylePlugin) { | ||||
|             // For new style plugin, all work is done in the async reading functions | ||||
|             // Just reap the process here | ||||
| @@ -506,10 +516,12 @@ const VPNRequestHandler = new Lang.Class({ | ||||
|  | ||||
|     _showNewStyleDialog: function() { | ||||
|         let keyfile = new GLib.KeyFile(); | ||||
|         let data; | ||||
|         let contentOverride; | ||||
|  | ||||
|         try { | ||||
|             let data = this._dataStdout.peek_buffer(); | ||||
|             data = this._dataStdout.peek_buffer(); | ||||
|  | ||||
|             keyfile.load_from_data(data.toString(), data.length, | ||||
|                                    GLib.KeyFileFlags.NONE); | ||||
|  | ||||
| @@ -542,13 +554,16 @@ const VPNRequestHandler = new Lang.Class({ | ||||
|                 } | ||||
|             } | ||||
|         } catch(e) { | ||||
|             logError(e, 'error while reading VPN plugin output keyfile'); | ||||
|             // No output is a valid case it means "both secrets are stored" | ||||
|             if (data.length > 0) { | ||||
|                 logError(e, 'error while reading VPN plugin output keyfile'); | ||||
|  | ||||
|             this._agent.respond(this._requestId, Shell.NetworkAgentResponse.INTERNAL_ERROR); | ||||
|             return; | ||||
|                 this._agent.respond(this._requestId, Shell.NetworkAgentResponse.INTERNAL_ERROR); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (contentOverride.secrets.length) { | ||||
|         if (contentOverride && contentOverride.secrets.length) { | ||||
|             // Only show the dialog if we actually have something to ask | ||||
|             this._shellDialog = new NetworkSecretDialog(this._agent, this._requestId, this._connection, 'vpn', [], contentOverride); | ||||
|             this._shellDialog.open(global.get_current_time()); | ||||
| @@ -582,7 +597,15 @@ const NetworkAgent = new Lang.Class({ | ||||
|     Name: 'NetworkAgent', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._native = new Shell.NetworkAgent({ identifier: 'org.gnome.Shell.NetworkAgent' }); | ||||
|         try { | ||||
|             this._native = new Shell.NetworkAgent({ identifier: 'org.gnome.Shell.NetworkAgent', | ||||
|                                                     capabilities: NMClient.SecretAgentCapabilities.VPN_HINTS | ||||
|                                                   }); | ||||
|         } catch(e) { | ||||
|             // Support older versions without NetworkAgent:capabilities | ||||
|             this._native = new Shell.NetworkAgent({ identifier: 'org.gnome.Shell.NetworkAgent' | ||||
|                                                   }); | ||||
|         } | ||||
|  | ||||
|         this._dialogs = { }; | ||||
|         this._vpnRequests = { }; | ||||
| @@ -682,16 +705,23 @@ const NetworkAgent = new Lang.Class({ | ||||
|                     let service = keyfile.get_string('VPN Connection', 'service'); | ||||
|                     let binary = keyfile.get_string('GNOME', 'auth-dialog'); | ||||
|                     let externalUIMode = false; | ||||
|                     let hints = false; | ||||
|  | ||||
|                     try { | ||||
|                         externalUIMode = keyfile.get_boolean('GNOME', 'supports-external-ui-mode'); | ||||
|                     } catch(e) { } // ignore errors if key does not exist | ||||
|  | ||||
|                     try { | ||||
|                         hints = keyfile.get_boolean('GNOME', 'supports-hints'); | ||||
|                     } catch(e) { } // ignore errors if key does not exist | ||||
|  | ||||
|                     let path = binary; | ||||
|                     if (!GLib.path_is_absolute(path)) { | ||||
|                         path = GLib.build_filenamev([Config.LIBEXECDIR, path]); | ||||
|                     } | ||||
|  | ||||
|                     if (GLib.file_test(path, GLib.FileTest.IS_EXECUTABLE)) | ||||
|                         this._vpnBinaries[service] = { fileName: path, externalUIMode: externalUIMode }; | ||||
|                         this._vpnBinaries[service] = { fileName: path, externalUIMode: externalUIMode, supportsHints: hints }; | ||||
|                     else | ||||
|                         throw new Error('VPN plugin at %s is not executable'.format(path)); | ||||
|                 } catch(e) { | ||||
|   | ||||
| @@ -54,7 +54,9 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|                                             text: _("Authentication Required") }); | ||||
|  | ||||
|         messageBox.add(this._subjectLabel, | ||||
|                        { y_fill:  false, | ||||
|                        { x_fill: false, | ||||
|                          y_fill:  false, | ||||
|                          x_align: St.Align.START, | ||||
|                          y_align: St.Align.START }); | ||||
|  | ||||
|         this._descriptionLabel = new St.Label({ style_class: 'prompt-dialog-description', | ||||
| @@ -63,7 +65,9 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|         this._descriptionLabel.clutter_text.line_wrap = true; | ||||
|  | ||||
|         messageBox.add(this._descriptionLabel, | ||||
|                        { y_fill:  true, | ||||
|                        { x_fill: false, | ||||
|                          y_fill:  true, | ||||
|                          x_align: St.Align.START, | ||||
|                          y_align: St.Align.START }); | ||||
|  | ||||
|         if (userNames.length > 1) { | ||||
| @@ -95,7 +99,8 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|         if (userIsRoot) { | ||||
|             let userLabel = new St.Label(({ style_class: 'polkit-dialog-user-root-label', | ||||
|                                             text: userRealName })); | ||||
|             messageBox.add(userLabel); | ||||
|             messageBox.add(userLabel, { x_fill: false, | ||||
|                                         x_align: St.Align.START }); | ||||
|         } else { | ||||
|             let userBox = new St.BoxLayout({ style_class: 'polkit-dialog-user-layout', | ||||
|                                              vertical: false }); | ||||
| @@ -137,7 +142,7 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|         this._errorMessageLabel = new St.Label({ style_class: 'prompt-dialog-error-label' }); | ||||
|         this._errorMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; | ||||
|         this._errorMessageLabel.clutter_text.line_wrap = true; | ||||
|         messageBox.add(this._errorMessageLabel); | ||||
|         messageBox.add(this._errorMessageLabel, { x_fill: false, x_align: St.Align.START }); | ||||
|         this._errorMessageLabel.hide(); | ||||
|  | ||||
|         this._infoMessageLabel = new St.Label({ style_class: 'prompt-dialog-info-label' }); | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| @@ -28,6 +29,8 @@ const SCROLLBACK_HISTORY_LINES = 10; | ||||
| // See Notification._onEntryChanged | ||||
| const COMPOSING_STOP_TIMEOUT = 5; | ||||
|  | ||||
| const CLOCK_FORMAT_KEY = 'clock-format'; | ||||
|  | ||||
| const NotificationDirection = { | ||||
|     SENT: 'chat-sent', | ||||
|     RECEIVED: 'chat-received' | ||||
| @@ -446,6 +449,7 @@ const ChatSource = new Lang.Class({ | ||||
|         this._closedId = this._channel.connect('invalidated', Lang.bind(this, this._channelClosed)); | ||||
|  | ||||
|         this._notification = new ChatNotification(this); | ||||
|         this._notification.connect('clicked', Lang.bind(this, this.open)); | ||||
|         this._notification.setUrgency(MessageTray.Urgency.HIGH); | ||||
|         this._notifyTimeoutId = 0; | ||||
|  | ||||
| @@ -544,20 +548,19 @@ const ChatSource = new Lang.Class({ | ||||
|         this._notification.update(this._notification.title, null, { customContent: true }); | ||||
|     }, | ||||
|  | ||||
|     open: function(notification) { | ||||
|           if (this._client.is_handling_channel(this._channel)) { | ||||
|               // We are handling the channel, try to pass it to Empathy | ||||
|               this._client.delegate_channels_async([this._channel], | ||||
|                   global.get_current_time(), | ||||
|                   'org.freedesktop.Telepathy.Client.Empathy.Chat', null); | ||||
|           } | ||||
|           else { | ||||
|               // We are not the handler, just ask to present the channel | ||||
|               let dbus = Tp.DBusDaemon.dup(); | ||||
|               let cd = Tp.ChannelDispatcher.new(dbus); | ||||
|     open: function() { | ||||
|         if (this._client.is_handling_channel(this._channel)) { | ||||
|             // We are handling the channel, try to pass it to Empathy | ||||
|             this._client.delegate_channels_async([this._channel], | ||||
|                                                  global.get_current_time(), | ||||
|                                                  'org.freedesktop.Telepathy.Client.Empathy.Chat', null); | ||||
|         } else { | ||||
|             // We are not the handler, just ask to present the channel | ||||
|             let dbus = Tp.DBusDaemon.dup(); | ||||
|             let cd = Tp.ChannelDispatcher.new(dbus); | ||||
|  | ||||
|               cd.present_channel_async(this._channel, global.get_current_time(), null); | ||||
|           } | ||||
|             cd.present_channel_async(this._channel, global.get_current_time(), null); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _getLogMessages: function() { | ||||
| @@ -621,7 +624,11 @@ const ChatSource = new Lang.Class({ | ||||
|             this.notify(); | ||||
|     }, | ||||
|  | ||||
|     _channelClosed: function() { | ||||
|     destroy: function(reason) { | ||||
|         if (this._destroyed) | ||||
|             return; | ||||
|  | ||||
|         this._destroyed = true; | ||||
|         this._channel.disconnect(this._closedId); | ||||
|         this._channel.disconnect(this._receivedId); | ||||
|         this._channel.disconnect(this._pendingId); | ||||
| @@ -631,7 +638,14 @@ const ChatSource = new Lang.Class({ | ||||
|         this._contact.disconnect(this._notifyAvatarId); | ||||
|         this._contact.disconnect(this._presenceChangedId); | ||||
|  | ||||
|         this.destroy(); | ||||
|         if (this._timestampTimeoutId) | ||||
|             Mainloop.source_remove(this._timestampTimeoutId); | ||||
|  | ||||
|         this.parent(reason); | ||||
|     }, | ||||
|  | ||||
|     _channelClosed: function() { | ||||
|         this.destroy(MessageTray.NotificationDestroyedReason.SOURCE_CLOSED); | ||||
|     }, | ||||
|  | ||||
|     /* All messages are new messages for Telepathy sources */ | ||||
| @@ -675,7 +689,7 @@ const ChatSource = new Lang.Class({ | ||||
|  | ||||
|         this._notifyTimeoutId = 0; | ||||
|  | ||||
|         return false; | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|     }, | ||||
|  | ||||
|     // This is called for both messages we send from | ||||
| @@ -894,14 +908,14 @@ const ChatNotification = new Lang.Class({ | ||||
|  | ||||
|         let group = props.group; | ||||
|         if (group != this._lastGroup) { | ||||
|             let style = 'chat-group-' + group; | ||||
|             this._lastGroup = group; | ||||
|             this._lastGroupActor = new St.BoxLayout({ style_class: style, | ||||
|                                                       vertical: true }); | ||||
|             this.addActor(this._lastGroupActor); | ||||
|             let emptyLine = new St.Label({ style_class: 'chat-empty-line' }); | ||||
|             this.addActor(emptyLine); | ||||
|         } | ||||
|  | ||||
|         this._lastGroupActor.add(body, props.childProps); | ||||
|         this._lastMessageBox = new St.BoxLayout({ vertical: false }); | ||||
|         this._lastMessageBox.add(body, props.childProps); | ||||
|         this.addActor(this._lastMessageBox); | ||||
|  | ||||
|         this.updated(); | ||||
|  | ||||
| @@ -930,50 +944,102 @@ const ChatNotification = new Lang.Class({ | ||||
|  | ||||
|         let format; | ||||
|  | ||||
|         // Show only the hour if date is on today | ||||
|         if(daysAgo < 1){ | ||||
|             format = "<b>%H:%M</b>"; | ||||
|         } | ||||
|         // 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 | ||||
|             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>"); | ||||
|         let desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' }); | ||||
|         let clockFormat = desktopSettings.get_string(CLOCK_FORMAT_KEY); | ||||
|  | ||||
|         } else if (date.getYear() == now.getYear()) { | ||||
|             /* Translators: this is the month name and day number followed by a time string. i.e. "May 25, 14:30"*/ | ||||
|             // xgettext:no-c-format | ||||
|             format = _("<b>%B</b> <b>%d</b>, <b>%H:%M</b>"); | ||||
|         } else { | ||||
|             /* Translators: this is the month name, day number, year number followed by a time string. i.e. "May 25 2012, 14:30"*/ | ||||
|             // xgettext:no-c-format | ||||
|             format = _("<b>%B</b> <b>%d</b> <b>%Y</b>, <b>%H:%M</b> "); | ||||
|         } | ||||
|         switch (clockFormat) { | ||||
|             case '24h': | ||||
|                 // Show only the time if date is on today | ||||
|                 if(daysAgo < 1){ | ||||
|                     /* Translators: Time in 24h format */ | ||||
|                     format = _("%H\u2236%M"); | ||||
|                 } | ||||
|                 // 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 in 24h format. i.e. "Yesterday, 14:30" */ | ||||
|                     // xgettext:no-c-format | ||||
|                     format = _("Yesterday, %H\u2236%M"); | ||||
|                 } | ||||
|                 // 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 in 24h format. i.e. "Monday, 14:30" */ | ||||
|                     // xgettext:no-c-format | ||||
|                     format = _("%A, %H\u2236%M"); | ||||
|  | ||||
|                 } else if (date.getYear() == now.getYear()) { | ||||
|                     /* Translators: this is the month name and day number | ||||
|                      followed by a time string in 24h format. | ||||
|                      i.e. "May 25, 14:30" */ | ||||
|                     // xgettext:no-c-format | ||||
|                     format = _("%B %d, %H\u2236%M"); | ||||
|                 } else { | ||||
|                     /* Translators: this is the month name, day number, year | ||||
|                      number followed by a time string in 24h format. | ||||
|                      i.e. "May 25 2012, 14:30" */ | ||||
|                     // xgettext:no-c-format | ||||
|                     format = _("%B %d %Y, %H\u2236%M"); | ||||
|                 } | ||||
|                 break; | ||||
|         default: | ||||
|             /* explicit fall-through */ | ||||
|             case '12h': | ||||
|                 // Show only the time if date is on today | ||||
|                 if(daysAgo < 1){ | ||||
|                     /* Translators: Time in 24h format */ | ||||
|                     format = _("%l\u2236%M %p"); | ||||
|                 } | ||||
|                 // 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 in 12h format. i.e. "Yesterday, 2:30 pm" */ | ||||
|                     // xgettext:no-c-format | ||||
|                     format = _("Yesterday, %l\u2236%M %p"); | ||||
|                 } | ||||
|                 // 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 in 12h format. i.e. "Monday, 2:30 pm" */ | ||||
|                     // xgettext:no-c-format | ||||
|                     format = _("%A, %l\u2236%M %p"); | ||||
|  | ||||
|                 } else if (date.getYear() == now.getYear()) { | ||||
|                     /* Translators: this is the month name and day number | ||||
|                      followed by a time string in 12h format. | ||||
|                      i.e. "May 25, 2:30 pm" */ | ||||
|                     // xgettext:no-c-format | ||||
|                     format = _("%B %d, %l\u2236%M %p"); | ||||
|                 } else { | ||||
|                     /* Translators: this is the month name, day number, year | ||||
|                      number followed by a time string in 12h format. | ||||
|                      i.e. "May 25 2012, 2:30 pm"*/ | ||||
|                     // xgettext:no-c-format | ||||
|                     format = _("%B %d %Y, %l\u2236%M %p"); | ||||
|                 } | ||||
|                 break; | ||||
|         } | ||||
|         return date.toLocaleFormat(format); | ||||
|     }, | ||||
|  | ||||
|     appendTimestamp: function() { | ||||
|         this._timestampTimeoutId = 0; | ||||
|  | ||||
|         let lastMessageTime = this._history[0].time; | ||||
|         let lastMessageDate = new Date(lastMessageTime * 1000); | ||||
|  | ||||
|         let timeLabel = this._append({ body: this._formatTimestamp(lastMessageDate), | ||||
|                                        group: 'meta', | ||||
|                                        styles: ['chat-meta-message'], | ||||
|                                        childProps: { expand: true, x_fill: false, | ||||
|                                                      x_align: St.Align.END }, | ||||
|                                        noTimestamp: true, | ||||
|                                        timestamp: lastMessageTime }); | ||||
|         let timeLabel = new St.Label({ text: this._formatTimestamp(lastMessageDate), | ||||
|                                        style_class: 'chat-meta-message', | ||||
|                                        x_expand: true, | ||||
|                                        y_expand: true, | ||||
|                                        x_align: Clutter.ActorAlign.END, | ||||
|                                        y_align: Clutter.ActorAlign.END }); | ||||
|  | ||||
|         this._lastMessageBox.add_actor(timeLabel); | ||||
|  | ||||
|         this._filterMessages(); | ||||
|  | ||||
|         return false; | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|     }, | ||||
|  | ||||
|     appendAliasChange: function(oldAlias, newAlias) { | ||||
| @@ -1011,7 +1077,7 @@ const ChatNotification = new Lang.Class({ | ||||
|  | ||||
|         this.source.setChatState(Tp.ChannelChatState.PAUSED); | ||||
|  | ||||
|         return false; | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|     }, | ||||
|  | ||||
|     _onEntryChanged: function() { | ||||
| @@ -1095,22 +1161,16 @@ const RoomInviteNotification = new Lang.Class({ | ||||
|          * for example. */ | ||||
|         this.addBody(_("%s is inviting you to join %s").format(inviter.get_alias(), channel.get_identifier())); | ||||
|  | ||||
|         this.addButton('decline', _("Decline")); | ||||
|         this.addButton('accept', _("Accept")); | ||||
|  | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             switch (action) { | ||||
|             case 'decline': | ||||
|                 dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, | ||||
|                                                 '', function(src, result) { | ||||
|                     src.leave_channels_finish(result)}); | ||||
|                 break; | ||||
|             case 'accept': | ||||
|                 dispatchOp.handle_with_time_async('', global.get_current_time(), | ||||
|                                                   function(src, result) { | ||||
|                     src.handle_with_time_finish(result)}); | ||||
|                 break; | ||||
|             } | ||||
|         this.addAction(_("Decline"), Lang.bind(this, function() { | ||||
|             dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, '', function(src, result) { | ||||
|                 src.leave_channels_finish(result); | ||||
|             }); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|         this.addAction(_("Accept"), Lang.bind(this, function() { | ||||
|             dispatchOp.handle_with_time_async('', global.get_current_time(), function(src, result) { | ||||
|                 src.handle_with_time_finish(result); | ||||
|             }); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|     } | ||||
| @@ -1136,23 +1196,17 @@ const AudioVideoNotification = new Lang.Class({ | ||||
|  | ||||
|         this.setUrgency(MessageTray.Urgency.CRITICAL); | ||||
|  | ||||
|         this.addButton('reject', _("Decline")); | ||||
|         this.addAction(_("Decline"), Lang.bind(this, function() { | ||||
|             dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, '', function(src, result) { | ||||
|                 src.leave_channels_finish(result); | ||||
|             }); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|         /* translators: this is a button label (verb), not a noun */ | ||||
|         this.addButton('answer', _("Answer")); | ||||
|  | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             switch (action) { | ||||
|             case 'reject': | ||||
|                 dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, | ||||
|                                                 '', function(src, result) { | ||||
|                     src.leave_channels_finish(result)}); | ||||
|                 break; | ||||
|             case 'answer': | ||||
|                 dispatchOp.handle_with_time_async('', global.get_current_time(), | ||||
|                                                   function(src, result) { | ||||
|                     src.handle_with_time_finish(result)}); | ||||
|                 break; | ||||
|             } | ||||
|         this.addAction(_("Answer"), Lang.bind(this, function() { | ||||
|             dispatchOp.handle_with_time_async('', global.get_current_time(), function(src, result) { | ||||
|                 src.handle_with_time_finish(result); | ||||
|             }); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|     } | ||||
| @@ -1176,22 +1230,16 @@ const FileTransferNotification = new Lang.Class({ | ||||
|                     { customContent: true }); | ||||
|         this.setResident(true); | ||||
|  | ||||
|         this.addButton('decline', _("Decline")); | ||||
|         this.addButton('accept', _("Accept")); | ||||
|  | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             switch (action) { | ||||
|             case 'decline': | ||||
|                 dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, | ||||
|                                                 '', function(src, result) { | ||||
|                     src.leave_channels_finish(result)}); | ||||
|                 break; | ||||
|             case 'accept': | ||||
|                 dispatchOp.handle_with_time_async('', global.get_current_time(), | ||||
|                                                   function(src, result) { | ||||
|                     src.handle_with_time_finish(result)}); | ||||
|                 break; | ||||
|             } | ||||
|         this.addAction(_("Decline"), Lang.bind(this, function() { | ||||
|             dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, '', function(src, result) { | ||||
|                 src.leave_channels_finish(result); | ||||
|             }); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|         this.addAction(_("Accept"), Lang.bind(this, function() { | ||||
|             dispatchOp.handle_with_time_async('', global.get_current_time(), function(src, result) { | ||||
|                 src.handle_with_time_finish(result); | ||||
|             }); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|     } | ||||
| @@ -1222,7 +1270,8 @@ const SubscriptionRequestNotification = new Lang.Class({ | ||||
|  | ||||
|         if (file) { | ||||
|             let uri = file.get_uri(); | ||||
|             iconBox.child = textureCache.load_uri_async(uri, iconBox._size, iconBox._size); | ||||
|             let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; | ||||
|             iconBox.child = textureCache.load_uri_async(uri, iconBox._size, iconBox._size, scaleFactor); | ||||
|         } | ||||
|         else { | ||||
|             iconBox.child = new St.Icon({ icon_name: 'avatar-default', | ||||
| @@ -1239,27 +1288,20 @@ const SubscriptionRequestNotification = new Lang.Class({ | ||||
|  | ||||
|         this.addActor(layout); | ||||
|  | ||||
|         this.addButton('decline', _("Decline")); | ||||
|         this.addButton('accept', _("Accept")); | ||||
|         this.addAction(_("Decline"), Lang.bind(this, function() { | ||||
|             contact.remove_async(function(src, result) { | ||||
|                 src.remove_finish(result); | ||||
|             }); | ||||
|         })); | ||||
|         this.addAction(_("Accept"), Lang.bind(this, function() { | ||||
|             // Authorize the contact and request to see his status as well | ||||
|             contact.authorize_publication_async(function(src, result) { | ||||
|                 src.authorize_publication_finish(result); | ||||
|             }); | ||||
|  | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             switch (action) { | ||||
|             case 'decline': | ||||
|                 contact.remove_async(function(src, result) { | ||||
|                     src.remove_finish(result)}); | ||||
|                 break; | ||||
|             case 'accept': | ||||
|                 // Authorize the contact and request to see his status as well | ||||
|                 contact.authorize_publication_async(function(src, result) { | ||||
|                     src.authorize_publication_finish(result)}); | ||||
|  | ||||
|                 contact.request_subscription_async('', function(src, result) { | ||||
|                     src.request_subscription_finish(result)}); | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             // rely on _subscriptionStatesChangedCb to destroy the | ||||
|             // notification | ||||
|             contact.request_subscription_async('', function(src, result) { | ||||
|                 src.request_subscription_finish(result); | ||||
|             }); | ||||
|         })); | ||||
|  | ||||
|         this._changedId = contact.connect('subscription-states-changed', | ||||
| @@ -1358,18 +1400,11 @@ const AccountNotification = new Lang.Class({ | ||||
|  | ||||
|         this._account = account; | ||||
|  | ||||
|         this.addButton('view', _("View account")); | ||||
|  | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             switch (action) { | ||||
|             case 'view': | ||||
|                 let cmd = 'empathy-accounts --select-account=' + | ||||
|                           account.get_path_suffix(); | ||||
|                 let app_info = Gio.app_info_create_from_commandline(cmd, null, 0); | ||||
|                 app_info.launch([], global.create_app_launch_context()); | ||||
|                 break; | ||||
|             } | ||||
|             this.destroy(); | ||||
|         this.addAction(_("View account"), Lang.bind(this, function() { | ||||
|             let cmd = 'empathy-accounts --select-account=' + | ||||
|                 account.get_path_suffix(); | ||||
|             let app_info = Gio.app_info_create_from_commandline(cmd, null, 0); | ||||
|             app_info.launch([], global.create_app_launch_context(0, -1)); | ||||
|         })); | ||||
|  | ||||
|         this._enabledId = account.connect('notify::enabled', | ||||
| @@ -1387,7 +1422,12 @@ const AccountNotification = new Lang.Class({ | ||||
|                 if (status == Tp.ConnectionStatus.CONNECTED) { | ||||
|                     this.destroy(); | ||||
|                 } else if (status == Tp.ConnectionStatus.DISCONNECTED) { | ||||
|                     this.update(this.title, this._getMessage(account.connection_error)); | ||||
|                     let connectionError = account.connection_error; | ||||
|  | ||||
|                     if (connectionError == Tp.error_get_dbus_name(Tp.Error.CANCELLED)) | ||||
|                         this.destroy(); | ||||
|                     else | ||||
|                         this.update(this.title, this._getMessage(connectionError)); | ||||
|                 } | ||||
|             })); | ||||
|     }, | ||||
|   | ||||
| @@ -165,6 +165,10 @@ const CtrlAltTabPopup = new Lang.Class({ | ||||
|             this._select(this._previous()); | ||||
|         else if (keysym == Clutter.Right) | ||||
|             this._select(this._next()); | ||||
|         else | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     _finish : function(time) { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Signals = imports.signals; | ||||
| const Lang = imports.lang; | ||||
| const Meta = imports.gi.Meta; | ||||
| @@ -380,6 +381,8 @@ const DashActor = new Lang.Class({ | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const baseIconSizes = [ 16, 22, 24, 32, 48, 64 ]; | ||||
|  | ||||
| const Dash = new Lang.Class({ | ||||
|     Name: 'Dash', | ||||
|  | ||||
| @@ -576,7 +579,8 @@ const Dash = new Lang.Class({ | ||||
|                     Lang.bind(this, function() { | ||||
|                         this._labelShowing = true; | ||||
|                         item.showLabel(); | ||||
|                         return false; | ||||
|                         this._showLabelTimeoutId = 0; | ||||
|                         return GLib.SOURCE_REMOVE; | ||||
|                     })); | ||||
|                 if (this._resetHoverTimeoutId > 0) { | ||||
|                     Mainloop.source_remove(this._resetHoverTimeoutId); | ||||
| @@ -592,7 +596,8 @@ const Dash = new Lang.Class({ | ||||
|                 this._resetHoverTimeoutId = Mainloop.timeout_add(DASH_ITEM_HOVER_TIMEOUT, | ||||
|                     Lang.bind(this, function() { | ||||
|                         this._labelShowing = false; | ||||
|                         return false; | ||||
|                         this._resetHoverTimeoutId = 0; | ||||
|                         return GLib.SOURCE_REMOVE; | ||||
|                     })); | ||||
|             } | ||||
|         } | ||||
| @@ -629,25 +634,24 @@ const Dash = new Lang.Class({ | ||||
|         let minHeight, natHeight; | ||||
|  | ||||
|         // Enforce the current icon size during the size request | ||||
|         let [currentWidth, currentHeight] = firstIcon.icon.get_size(); | ||||
|  | ||||
|         firstIcon.icon.set_size(this.iconSize, this.iconSize); | ||||
|         firstIcon.setIconSize(this.iconSize); | ||||
|         [minHeight, natHeight] = firstButton.get_preferred_height(-1); | ||||
|  | ||||
|         firstIcon.icon.set_size(currentWidth, currentHeight); | ||||
|         let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; | ||||
|         let iconSizes = baseIconSizes.map(function(s) { | ||||
|             return s * scaleFactor; | ||||
|         }); | ||||
|  | ||||
|         // Subtract icon padding and box spacing from the available height | ||||
|         availHeight -= iconChildren.length * (natHeight - this.iconSize) + | ||||
|         availHeight -= iconChildren.length * (natHeight - this.iconSize * scaleFactor) + | ||||
|                        (iconChildren.length - 1) * spacing; | ||||
|  | ||||
|         let availSize = availHeight / iconChildren.length; | ||||
|  | ||||
|         let iconSizes = [ 16, 22, 24, 32, 48, 64 ]; | ||||
|  | ||||
|         let newIconSize = 16; | ||||
|         let newIconSize = baseIconSizes[0]; | ||||
|         for (let i = 0; i < iconSizes.length; i++) { | ||||
|             if (iconSizes[i] < availSize) | ||||
|                 newIconSize = iconSizes[i]; | ||||
|                 newIconSize = baseIconSizes[i]; | ||||
|         } | ||||
|  | ||||
|         if (newIconSize == this.iconSize) | ||||
|   | ||||
| @@ -63,9 +63,14 @@ const DateMenuButton = new Lang.Class({ | ||||
|         hbox.add(vbox); | ||||
|  | ||||
|         // Date | ||||
|         this._date = new St.Label({ style_class: 'datemenu-date-label', | ||||
|                                     can_focus: true }); | ||||
|         vbox.add(this._date); | ||||
|         this._date = new St.Button({ style_class: 'datemenu-date-label', | ||||
|                                      can_focus: true, | ||||
|                                    }); | ||||
|         this._date.connect('clicked', | ||||
|                            Lang.bind(this, function() { | ||||
|                                this._calendar.setDate(new Date(), false); | ||||
|                            })); | ||||
|         vbox.add(this._date, { x_fill: false  }); | ||||
|  | ||||
|         this._eventList = new Calendar.EventsList(); | ||||
|         this._calendar = new Calendar.Calendar(); | ||||
| @@ -113,22 +118,7 @@ const DateMenuButton = new Lang.Class({ | ||||
|         this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) { | ||||
|             if (isOpen) { | ||||
|                 let now = new Date(); | ||||
|                 /* Passing true to setDate() forces events to be reloaded. We | ||||
|                  * want this behavior, because | ||||
|                  * | ||||
|                  *   o It will cause activation of the calendar server which is | ||||
|                  *     useful if it has crashed | ||||
|                  * | ||||
|                  *   o It will cause the calendar server to reload events which | ||||
|                  *     is useful if dynamic updates are not supported or not | ||||
|                  *     properly working | ||||
|                  * | ||||
|                  * Since this only happens when the menu is opened, the cost | ||||
|                  * isn't very big. | ||||
|                  */ | ||||
|                 this._calendar.setDate(now, true); | ||||
|                 // No need to update this._eventList as ::selected-date-changed | ||||
|                 // signal will fire | ||||
|                 this._calendar.setDate(now); | ||||
|             } | ||||
|         })); | ||||
|  | ||||
| @@ -201,7 +191,7 @@ const DateMenuButton = new Lang.Class({ | ||||
|          */ | ||||
|         let dateFormat = _("%A %B %e, %Y"); | ||||
|         let displayDate = new Date(); | ||||
|         this._date.set_text(displayDate.toLocaleFormat(dateFormat)); | ||||
|         this._date.set_label(displayDate.toLocaleFormat(dateFormat)); | ||||
|     }, | ||||
|  | ||||
|     _getCalendarApp: function() { | ||||
| @@ -217,7 +207,7 @@ const DateMenuButton = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _getClockApp: function() { | ||||
|         return Shell.AppSystem.get_default().lookup_app('gnome-clocks.desktop'); | ||||
|         return Shell.AppSystem.get_default().lookup_app('org.gnome.clocks.desktop'); | ||||
|     }, | ||||
|  | ||||
|     _onOpenCalendarActivate: function() { | ||||
| @@ -226,7 +216,7 @@ const DateMenuButton = new Lang.Class({ | ||||
|         let app = this._getCalendarApp(); | ||||
|         if (app.get_id() == 'evolution.desktop') | ||||
|             app = Gio.DesktopAppInfo.new('evolution-calendar.desktop'); | ||||
|         app.launch([], global.create_app_launch_context()); | ||||
|         app.launch([], global.create_app_launch_context(0, -1)); | ||||
|     }, | ||||
|  | ||||
|     _onOpenClocksActivate: function() { | ||||
|   | ||||
							
								
								
									
										31
									
								
								js/ui/dnd.js
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								js/ui/dnd.js
									
									
									
									
									
								
							| @@ -106,10 +106,10 @@ const _Draggable = new Lang.Class({ | ||||
|  | ||||
|     _onButtonPress : function (actor, event) { | ||||
|         if (event.get_button() != 1) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         if (Tweener.getTweenCount(actor)) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         this._buttonDown = true; | ||||
|         this._grabActor(); | ||||
| @@ -118,7 +118,7 @@ const _Draggable = new Lang.Class({ | ||||
|         this._dragStartX = stageX; | ||||
|         this._dragStartY = stageY; | ||||
|  | ||||
|         return false; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _grabActor: function() { | ||||
| @@ -164,11 +164,11 @@ const _Draggable = new Lang.Class({ | ||||
|             } else if (this._dragActor != null && !this._animationInProgress) { | ||||
|                 // Drag must have been cancelled with Esc. | ||||
|                 this._dragComplete(); | ||||
|                 return true; | ||||
|                 return Clutter.EVENT_STOP; | ||||
|             } else { | ||||
|                 // Drag has never started. | ||||
|                 this._ungrabActor(); | ||||
|                 return false; | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|             } | ||||
|         // We intercept MOTION event to figure out if the drag has started and to draw | ||||
|         // this._dragActor under the pointer when dragging is in progress | ||||
| @@ -184,11 +184,11 @@ const _Draggable = new Lang.Class({ | ||||
|             let symbol = event.get_key_symbol(); | ||||
|             if (symbol == Clutter.Escape) { | ||||
|                 this._cancelDrag(event.get_time()); | ||||
|                 return true; | ||||
|                 return Clutter.EVENT_STOP; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
| @@ -236,7 +236,7 @@ const _Draggable = new Lang.Class({ | ||||
|  | ||||
|         if (this.actor._delegate && this.actor._delegate.getDragActor) { | ||||
|             this._dragActor = this.actor._delegate.getDragActor(); | ||||
|             this._dragActor.reparent(Main.uiGroup); | ||||
|             Main.uiGroup.add_child(this._dragActor); | ||||
|             this._dragActor.raise_top(); | ||||
|             Shell.util_set_hidden_from_pick(this._dragActor, true); | ||||
|  | ||||
| @@ -285,7 +285,8 @@ const _Draggable = new Lang.Class({ | ||||
|             this._dragOffsetX = actorStageX - this._dragStartX; | ||||
|             this._dragOffsetY = actorStageY - this._dragStartY; | ||||
|  | ||||
|             this._dragActor.reparent(Main.uiGroup); | ||||
|             this._dragOrigParent.remove_actor(this._dragActor); | ||||
|             Main.uiGroup.add_child(this._dragActor); | ||||
|             this._dragActor.raise_top(); | ||||
|             Shell.util_set_hidden_from_pick(this._dragActor, true); | ||||
|         } | ||||
| @@ -345,6 +346,7 @@ const _Draggable = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _updateDragHover : function () { | ||||
|         this._updateHoverId = 0; | ||||
|         let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL, | ||||
|                                                                   this._dragX, this._dragY); | ||||
|         let dragEvent = { | ||||
| @@ -360,7 +362,7 @@ const _Draggable = new Lang.Class({ | ||||
|                 let result = motionFunc(dragEvent); | ||||
|                 if (result != DragMotionResult.CONTINUE) { | ||||
|                     global.screen.set_cursor(DRAG_CURSOR_MAP[result]); | ||||
|                     return false; | ||||
|                     return GLib.SOURCE_REMOVE; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @@ -378,18 +380,18 @@ const _Draggable = new Lang.Class({ | ||||
|                                                              0); | ||||
|                 if (result != DragMotionResult.CONTINUE) { | ||||
|                     global.screen.set_cursor(DRAG_CURSOR_MAP[result]); | ||||
|                     return false; | ||||
|                     return GLib.SOURCE_REMOVE; | ||||
|                 } | ||||
|             } | ||||
|             target = target.get_parent(); | ||||
|         } | ||||
|         global.screen.set_cursor(Meta.Cursor.DND_IN_DRAG); | ||||
|         return false; | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|     }, | ||||
|  | ||||
|     _queueUpdateDragHover: function() { | ||||
|         if (this._updateHoverId) | ||||
|             GLib.source_remove(this._updateHoverId); | ||||
|             return; | ||||
|  | ||||
|         this._updateHoverId = GLib.idle_add(GLib.PRIORITY_DEFAULT, | ||||
|                                             Lang.bind(this, this._updateDragHover)); | ||||
| @@ -555,7 +557,8 @@ const _Draggable = new Lang.Class({ | ||||
|  | ||||
|     _onAnimationComplete : function (dragActor, eventTime) { | ||||
|         if (this._dragOrigParent) { | ||||
|             dragActor.reparent(this._dragOrigParent); | ||||
|             Main.uiGroup.remove_child(this._dragActor); | ||||
|             this._dragOrigParent.add_actor(this._dragActor); | ||||
|             dragActor.set_scale(this._dragOrigScale, this._dragOrigScale); | ||||
|             dragActor.set_position(this._dragOrigX, this._dragOrigY); | ||||
|         } else { | ||||
|   | ||||
| @@ -13,9 +13,7 @@ | ||||
|  * GNU General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program; if not, write to the Free Software | ||||
|  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA | ||||
|  * 02111-1307, USA. | ||||
|  * along with this program; if not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| const Lang = imports.lang; | ||||
| @@ -27,9 +25,11 @@ const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Pango = imports.gi.Pango; | ||||
| const Polkit = imports.gi.Polkit; | ||||
| const St = imports.gi.St; | ||||
| const Shell = imports.gi.Shell; | ||||
|  | ||||
| const CheckBox = imports.ui.checkBox; | ||||
| const GnomeSession = imports.misc.gnomeSession; | ||||
| const LoginManager = imports.misc.loginManager; | ||||
| const ModalDialog = imports.ui.modalDialog; | ||||
| @@ -38,25 +38,29 @@ const UserWidget = imports.ui.userWidget; | ||||
|  | ||||
| let _endSessionDialog = null; | ||||
|  | ||||
| const TRIGGER_OFFLINE_UPDATE = '/usr/libexec/pk-trigger-offline-update'; | ||||
|  | ||||
| const _ITEM_ICON_SIZE = 48; | ||||
| const _DIALOG_ICON_SIZE = 32; | ||||
| const _DIALOG_ICON_SIZE = 48; | ||||
|  | ||||
| const GSM_SESSION_MANAGER_LOGOUT_FORCE = 2; | ||||
|  | ||||
| const EndSessionDialogIface = <interface name="org.gnome.SessionManager.EndSessionDialog"> | ||||
| <method name="Open"> | ||||
|     <arg type="u" direction="in" /> | ||||
|     <arg type="u" direction="in" /> | ||||
|     <arg type="u" direction="in" /> | ||||
|     <arg type="ao" direction="in" /> | ||||
| </method> | ||||
| <method name="Close" /> | ||||
| <signal name="ConfirmedLogout" /> | ||||
| <signal name="ConfirmedReboot" /> | ||||
| <signal name="ConfirmedShutdown" /> | ||||
| <signal name="Canceled" /> | ||||
| <signal name="Closed" /> | ||||
| </interface>; | ||||
| const EndSessionDialogIface = '<node> \ | ||||
| <interface name="org.gnome.SessionManager.EndSessionDialog"> \ | ||||
| <method name="Open"> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
|     <arg type="ao" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="Close" /> \ | ||||
| <signal name="ConfirmedLogout" /> \ | ||||
| <signal name="ConfirmedReboot" /> \ | ||||
| <signal name="ConfirmedShutdown" /> \ | ||||
| <signal name="Canceled" /> \ | ||||
| <signal name="Closed" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const logoutDialogContent = { | ||||
|     subjectWithUser: C_("title", "Log Out %s"), | ||||
| @@ -71,6 +75,7 @@ const logoutDialogContent = { | ||||
|                         "You will be logged out automatically in %d seconds.", | ||||
|                         seconds).format(seconds); | ||||
|     }, | ||||
|     showBatteryWarning: false, | ||||
|     confirmButtons: [{ signal: 'ConfirmedLogout', | ||||
|                        label:  C_("button", "Log Out") }], | ||||
|     iconStyleClass: 'end-session-dialog-logout-icon', | ||||
| @@ -79,11 +84,14 @@ const logoutDialogContent = { | ||||
|  | ||||
| const shutdownDialogContent = { | ||||
|     subject: C_("title", "Power Off"), | ||||
|     subjectWithUpdates: C_("title", "Install Updates & Power Off"), | ||||
|     description: function(seconds) { | ||||
|         return ngettext("The system will power off automatically in %d second.", | ||||
|                         "The system will power off automatically in %d seconds.", | ||||
|                         seconds).format(seconds); | ||||
|     }, | ||||
|     checkBoxText: C_("checkbox", "Install pending software updates"), | ||||
|     showBatteryWarning: true, | ||||
|     confirmButtons: [{ signal: 'ConfirmedReboot', | ||||
|                        label:  C_("button", "Restart") }, | ||||
|                      { signal: 'ConfirmedShutdown', | ||||
| @@ -100,6 +108,7 @@ const restartDialogContent = { | ||||
|                         "The system will restart automatically in %d seconds.", | ||||
|                         seconds).format(seconds); | ||||
|     }, | ||||
|     showBatteryWarning: false, | ||||
|     confirmButtons: [{ signal: 'ConfirmedReboot', | ||||
|                        label:  C_("button", "Restart") }], | ||||
|     iconName: 'view-refresh-symbolic', | ||||
| @@ -115,8 +124,11 @@ const restartInstallDialogContent = { | ||||
|                         "The system will automatically restart and install updates in %d seconds.", | ||||
|                         seconds).format(seconds); | ||||
|     }, | ||||
|     showBatteryWarning: true, | ||||
|     confirmButtons: [{ signal: 'ConfirmedReboot', | ||||
|                        label:  C_("button", "Restart & Install") }], | ||||
|                        label:  C_("button", "Restart & Install") }], | ||||
|     unusedFutureButtonForTranslation: C_("button", "Install & Power Off"), | ||||
|     unusedFutureCheckBoxForTranslation: C_("checkbox", "Power off after updates are installed"), | ||||
|     iconName: 'view-refresh-symbolic', | ||||
|     iconStyleClass: 'end-session-dialog-shutdown-icon', | ||||
|     showOtherSessions: true, | ||||
| @@ -131,16 +143,26 @@ const DialogContent = { | ||||
|  | ||||
| const MAX_USERS_IN_SESSION_DIALOG = 5; | ||||
|  | ||||
| const LogindSessionIface = <interface name='org.freedesktop.login1.Session'> | ||||
|     <property name="Id" type="s" access="read"/> | ||||
|     <property name="Remote" type="b" access="read"/> | ||||
|     <property name="Class" type="s" access="read"/> | ||||
|     <property name="Type" type="s" access="read"/> | ||||
|     <property name="State" type="s" access="read"/> | ||||
| </interface>; | ||||
| const LogindSessionIface = '<node> \ | ||||
| <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> \ | ||||
| </node>'; | ||||
|  | ||||
| const LogindSession = Gio.DBusProxy.makeProxyWrapper(LogindSessionIface); | ||||
|  | ||||
| const UPowerIface = '<node> \ | ||||
| <interface name="org.freedesktop.UPower"> \ | ||||
|     <property name="OnBattery" type="b" access="read"/> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const UPowerProxy = Gio.DBusProxy.makeProxyWrapper(UPowerIface); | ||||
|  | ||||
| function findAppFromInhibitor(inhibitor) { | ||||
|     let desktopFile; | ||||
|     try { | ||||
| @@ -193,6 +215,18 @@ function _setLabelText(label, text) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| function _setCheckBoxLabel(checkBox, text) { | ||||
|     let label = checkBox.getLabelActor(); | ||||
|  | ||||
|     if (text) { | ||||
|         label.set_text(text); | ||||
|         checkBox.actor.show(); | ||||
|     } else { | ||||
|         label.set_text(''); | ||||
|         checkBox.actor.hide(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| function init() { | ||||
|     // This always returns the same singleton object | ||||
|     // By instantiating it initially, we register the | ||||
| @@ -212,6 +246,20 @@ const EndSessionDialog = new Lang.Class({ | ||||
|         this._userManager = AccountsService.UserManager.get_default(); | ||||
|         this._user = this._userManager.get_user(GLib.get_user_name()); | ||||
|         this._updatesFile = Gio.File.new_for_path('/system-update'); | ||||
|         this._preparedUpdateFile = Gio.File.new_for_path('/var/lib/PackageKit/prepared-update'); | ||||
|  | ||||
|         this._powerProxy = new UPowerProxy(Gio.DBus.system, | ||||
|                                            'org.freedesktop.UPower', | ||||
|                                            '/org/freedesktop/UPower', | ||||
|                                            Lang.bind(this, function(proxy, error) { | ||||
|                                                if (error) { | ||||
|                                                    log(error.message); | ||||
|                                                    return; | ||||
|                                                } | ||||
|                                                this._powerProxy.connect('g-properties-changed', | ||||
|                                                                         Lang.bind(this, this._sync)); | ||||
|                                                this._sync(); | ||||
|                                            })); | ||||
|  | ||||
|         this._secondsLeft = 0; | ||||
|         this._totalSecondsToStayOpen = 0; | ||||
| @@ -238,14 +286,17 @@ const EndSessionDialog = new Lang.Class({ | ||||
|                                 x_align: St.Align.END, | ||||
|                                 y_align: St.Align.START }); | ||||
|  | ||||
|         let messageLayout = new St.BoxLayout({ vertical: true }); | ||||
|         let messageLayout = new St.BoxLayout({ vertical: true, | ||||
|                                                style_class: 'end-session-dialog-layout' }); | ||||
|         mainContentLayout.add(messageLayout, | ||||
|                               { y_align: St.Align.START }); | ||||
|  | ||||
|         this._subjectLabel = new St.Label({ style_class: 'end-session-dialog-subject' }); | ||||
|  | ||||
|         messageLayout.add(this._subjectLabel, | ||||
|                           { y_fill:  false, | ||||
|                           { x_fill: false, | ||||
|                             y_fill:  false, | ||||
|                             x_align: St.Align.START, | ||||
|                             y_align: St.Align.START }); | ||||
|  | ||||
|         this._descriptionLabel = new St.Label({ style_class: 'end-session-dialog-description' }); | ||||
| @@ -256,6 +307,16 @@ const EndSessionDialog = new Lang.Class({ | ||||
|                           { y_fill:  true, | ||||
|                             y_align: St.Align.START }); | ||||
|  | ||||
|         this._checkBox = new CheckBox.CheckBox(); | ||||
|         this._checkBox.actor.connect('clicked', Lang.bind(this, this._sync)); | ||||
|         messageLayout.add(this._checkBox.actor); | ||||
|  | ||||
|         this._batteryWarning = new St.Label({ style_class: 'end-session-dialog-warning', | ||||
|                                               text: _("Running on battery power: please plug in before installing updates.") }); | ||||
|         this._batteryWarning.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; | ||||
|         this._batteryWarning.clutter_text.line_wrap = true; | ||||
|         messageLayout.add(this._batteryWarning); | ||||
|  | ||||
|         this._scrollView = new St.ScrollView({ style_class: 'end-session-dialog-list' }); | ||||
|         this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); | ||||
|         this.contentLayout.add(this._scrollView, | ||||
| @@ -269,16 +330,24 @@ const EndSessionDialog = new Lang.Class({ | ||||
|  | ||||
|         this._applicationHeader = new St.Label({ style_class: 'end-session-dialog-list-header', | ||||
|                                                  text: _("Some applications are busy or have unsaved work.") }); | ||||
|         this._applicationList = new St.BoxLayout({ vertical: true }); | ||||
|         this._applicationList = new St.BoxLayout({ style_class: 'end-session-dialog-app-list', | ||||
|                                                    vertical: true }); | ||||
|         this._inhibitorSection.add_actor(this._applicationHeader); | ||||
|         this._inhibitorSection.add_actor(this._applicationList); | ||||
|  | ||||
|         this._sessionHeader = new St.Label({ style_class: 'end-session-dialog-list-header', | ||||
|                                              text: _("Other users are logged in.") }); | ||||
|         this._sessionList = new St.BoxLayout({ vertical: true }); | ||||
|         this._sessionList = new St.BoxLayout({ style_class: 'end-session-dialog-session-list', | ||||
|                                                vertical: true }); | ||||
|         this._inhibitorSection.add_actor(this._sessionHeader); | ||||
|         this._inhibitorSection.add_actor(this._sessionList); | ||||
|  | ||||
|         try { | ||||
|             this._updatesPermission = Polkit.Permission.new_sync("org.freedesktop.packagekit.trigger-offline-update", null, null); | ||||
|         } catch(e) { | ||||
|             log('No permission to trigger offline updates: %s'.format(e.toString())); | ||||
|         } | ||||
|  | ||||
|         this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(EndSessionDialogIface, this); | ||||
|         this._dbusImpl.export(Gio.DBus.session, '/org/gnome/SessionManager/EndSessionDialog'); | ||||
|     }, | ||||
| @@ -293,13 +362,22 @@ const EndSessionDialog = new Lang.Class({ | ||||
|         if (!open) | ||||
|             return; | ||||
|  | ||||
|         if (this._type == 2 && this._updatesFile.query_exists(null)) | ||||
|             this._type = 3; | ||||
|  | ||||
|         let dialogContent = DialogContent[this._type]; | ||||
|  | ||||
|         let subject = dialogContent.subject; | ||||
|  | ||||
|         // Use different title when we are installing updates | ||||
|         if (dialogContent.subjectWithUpdates && this._checkBox.actor.checked) | ||||
|             subject = dialogContent.subjectWithUpdates; | ||||
|  | ||||
|         if (dialogContent.showBatteryWarning) { | ||||
|             // Warn when running on battery power | ||||
|             if (this._powerProxy.OnBattery && this._checkBox.actor.checked) | ||||
|                 this._batteryWarning.opacity = 255; | ||||
|             else | ||||
|                 this._batteryWarning.opacity = 0; | ||||
|         } | ||||
|  | ||||
|         let description; | ||||
|         let displayTime = _roundSecondsToInterval(this._totalSecondsToStayOpen, | ||||
|                                                   this._secondsLeft, | ||||
| @@ -382,15 +460,75 @@ const EndSessionDialog = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _confirm: function(signal) { | ||||
|         this._fadeOutDialog(); | ||||
|         this._stopTimer(); | ||||
|         this._dbusImpl.emit_signal(signal, null); | ||||
|         let callback = Lang.bind(this, function() { | ||||
|             this._fadeOutDialog(); | ||||
|             this._stopTimer(); | ||||
|             this._dbusImpl.emit_signal(signal, null); | ||||
|         }); | ||||
|  | ||||
|         // Offline update not available; just emit the signal | ||||
|         if (!this._checkBox.actor.visible) { | ||||
|             callback(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Trigger the offline update as requested | ||||
|         if (this._checkBox.actor.checked) { | ||||
|             switch (signal) { | ||||
|                 case "ConfirmedReboot": | ||||
|                     this._triggerOfflineUpdateReboot(callback); | ||||
|                     break; | ||||
|                 case "ConfirmedShutdown": | ||||
|                     // To actually trigger the offline update, we need to | ||||
|                     // reboot to do the upgrade. When the upgrade is complete, | ||||
|                     // the computer will shut down automatically. | ||||
|                     signal = "ConfirmedReboot"; | ||||
|                     this._triggerOfflineUpdateShutdown(callback); | ||||
|                     break; | ||||
|                 default: | ||||
|                     callback(); | ||||
|                     break; | ||||
|             } | ||||
|         } else { | ||||
|             this._triggerOfflineUpdateCancel(callback); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _onOpened: function() { | ||||
|         this._sync(); | ||||
|     }, | ||||
|  | ||||
|     _triggerOfflineUpdateReboot: function(callback) { | ||||
|         this._pkexecSpawn([TRIGGER_OFFLINE_UPDATE, 'reboot'], callback); | ||||
|     }, | ||||
|  | ||||
|     _triggerOfflineUpdateShutdown: function(callback) { | ||||
|         this._pkexecSpawn([TRIGGER_OFFLINE_UPDATE, 'power-off'], callback); | ||||
|     }, | ||||
|  | ||||
|     _triggerOfflineUpdateCancel: function(callback) { | ||||
|         this._pkexecSpawn([TRIGGER_OFFLINE_UPDATE, '--cancel'], callback); | ||||
|     }, | ||||
|  | ||||
|     _pkexecSpawn: function(argv, callback) { | ||||
|         let ret, pid; | ||||
|         try { | ||||
|             [ret, pid] = GLib.spawn_async(null, ['pkexec'].concat(argv), null, | ||||
|                                           GLib.SpawnFlags.DO_NOT_REAP_CHILD | GLib.SpawnFlags.SEARCH_PATH, | ||||
|                                           null); | ||||
|         } catch (e) { | ||||
|             log('Error spawning "pkexec %s": %s'.format(argv.join(' '), e.toString())); | ||||
|             callback(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, function(pid, status) { | ||||
|             GLib.spawn_close_pid(pid); | ||||
|  | ||||
|             callback(); | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     _startTimer: function() { | ||||
|         let startTime = GLib.get_monotonic_time(); | ||||
|         this._secondsLeft = this._totalSecondsToStayOpen; | ||||
| @@ -403,14 +541,15 @@ const EndSessionDialog = new Lang.Class({ | ||||
|                 this._secondsLeft = this._totalSecondsToStayOpen - secondsElapsed; | ||||
|                 if (this._secondsLeft > 0) { | ||||
|                     this._sync(); | ||||
|                     return true; | ||||
|                     return GLib.SOURCE_CONTINUE; | ||||
|                 } | ||||
|  | ||||
|                 let dialogContent = DialogContent[this._type]; | ||||
|                 let button = dialogContent.confirmButtons[dialogContent.confirmButtons.length - 1]; | ||||
|                 this._confirm(button.signal); | ||||
|                 this._timerId = 0; | ||||
|  | ||||
|                 return false; | ||||
|                 return GLib.SOURCE_REMOVE; | ||||
|             })); | ||||
|     }, | ||||
|  | ||||
| @@ -537,6 +676,9 @@ const EndSessionDialog = new Lang.Class({ | ||||
|         this._totalSecondsToStayOpen = totalSecondsToStayOpen; | ||||
|         this._type = type; | ||||
|  | ||||
|         if (this._type == 2 && this._updatesFile.query_exists(null)) | ||||
|             this._type = 3; | ||||
|  | ||||
|         this._applications = []; | ||||
|         this._applicationList.destroy_all_children(); | ||||
|  | ||||
| @@ -549,6 +691,8 @@ const EndSessionDialog = new Lang.Class({ | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let dialogContent = DialogContent[this._type]; | ||||
|  | ||||
|         for (let i = 0; i < inhibitorObjectPaths.length; i++) { | ||||
|             let inhibitor = new GnomeSession.Inhibitor(inhibitorObjectPaths[i], Lang.bind(this, function(proxy, error) { | ||||
|                 this._onInhibitorLoaded(proxy); | ||||
| @@ -557,9 +701,23 @@ const EndSessionDialog = new Lang.Class({ | ||||
|             this._applications.push(inhibitor); | ||||
|         } | ||||
|  | ||||
|         if (DialogContent[type].showOtherSessions) | ||||
|         if (dialogContent.showOtherSessions) | ||||
|             this._loadSessions(); | ||||
|  | ||||
|         let preparedUpdate = this._preparedUpdateFile.query_exists(null); | ||||
|         let updateAlreadyTriggered = this._updatesFile.query_exists(null); | ||||
|         let updatesAllowed = this._updatesPermission && this._updatesPermission.allowed; | ||||
|  | ||||
|         _setCheckBoxLabel(this._checkBox, dialogContent.checkBoxText); | ||||
|         this._checkBox.actor.visible = (dialogContent.checkBoxText && preparedUpdate && updatesAllowed); | ||||
|         this._checkBox.actor.checked = (preparedUpdate && updateAlreadyTriggered); | ||||
|  | ||||
|         // We show the warning either together with the checkbox, or when | ||||
|         // updates have already been triggered, but the user doesn't have | ||||
|         // enough permissions to cancel them. | ||||
|         this._batteryWarning.visible = (dialogContent.showBatteryWarning && | ||||
|                                         (this._checkBox.actor.visible || preparedUpdate && updateAlreadyTriggered && !updatesAllowed)); | ||||
|  | ||||
|         this._updateButtons(); | ||||
|  | ||||
|         if (!this.open(timestamp)) { | ||||
|   | ||||
| @@ -5,6 +5,8 @@ imports.gi.versions.Gio = '2.0'; | ||||
| imports.gi.versions.Gdk = '3.0'; | ||||
| imports.gi.versions.GdkPixbuf = '2.0'; | ||||
| imports.gi.versions.Gtk = '3.0'; | ||||
| imports.gi.versions.TelepathyGLib = '0.12'; | ||||
| imports.gi.versions.TelepathyLogger = '0.2'; | ||||
|  | ||||
| const Clutter = imports.gi.Clutter;; | ||||
| const Gettext = imports.gettext; | ||||
|   | ||||
| @@ -201,7 +201,7 @@ const InstallExtensionDialog = new Lang.Class({ | ||||
|                            default: true | ||||
|                          }]); | ||||
|  | ||||
|         let message = _("Download and install '%s' from extensions.gnome.org?").format(info.name); | ||||
|         let message = _("Download and install “%s” from extensions.gnome.org?").format(info.name); | ||||
|  | ||||
|         let box = new St.BoxLayout(); | ||||
|         this.contentLayout.add(box); | ||||
|   | ||||
| @@ -38,6 +38,7 @@ const connect = Lang.bind(_signals, _signals.connect); | ||||
| const disconnect = Lang.bind(_signals, _signals.disconnect); | ||||
|  | ||||
| const ENABLED_EXTENSIONS_KEY = 'enabled-extensions'; | ||||
| const EXTENSION_DISABLE_VERSION_CHECK_KEY = 'disable-extension-version-validation'; | ||||
|  | ||||
| var initted = false; | ||||
| var enabled; | ||||
| @@ -76,7 +77,11 @@ function disableExtension(uuid) { | ||||
|         theme.unload_stylesheet(extension.stylesheet.get_path()); | ||||
|     } | ||||
|  | ||||
|     extension.stateObj.disable(); | ||||
|     try { | ||||
|         extension.stateObj.disable(); | ||||
|     } catch(e) { | ||||
|         logExtensionError(uuid, e); | ||||
|     } | ||||
|  | ||||
|     for (let i = 0; i < order.length; i++) { | ||||
|         let uuid = order[i]; | ||||
| @@ -89,8 +94,10 @@ function disableExtension(uuid) { | ||||
|  | ||||
|     extensionOrder.splice(orderIdx, 1); | ||||
|  | ||||
|     extension.state = ExtensionState.DISABLED; | ||||
|     _signals.emit('extension-state-changed', extension); | ||||
|     if ( extension.state != ExtensionState.ERROR ) { | ||||
|         extension.state = ExtensionState.DISABLED; | ||||
|         _signals.emit('extension-state-changed', extension); | ||||
|     } | ||||
| } | ||||
|  | ||||
| function enableExtension(uuid) { | ||||
| @@ -117,10 +124,15 @@ function enableExtension(uuid) { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     extension.stateObj.enable(); | ||||
|  | ||||
|     extension.state = ExtensionState.ENABLED; | ||||
|     _signals.emit('extension-state-changed', extension); | ||||
|     try { | ||||
|         extension.stateObj.enable(); | ||||
|         extension.state = ExtensionState.ENABLED; | ||||
|         _signals.emit('extension-state-changed', extension); | ||||
|         return; | ||||
|     } catch(e) { | ||||
|         logExtensionError(uuid, e); | ||||
|         return; | ||||
|     } | ||||
| } | ||||
|  | ||||
| function logExtensionError(uuid, error) { | ||||
| @@ -145,12 +157,15 @@ function loadExtension(extension) { | ||||
|     // Default to error, we set success as the last step | ||||
|     extension.state = ExtensionState.ERROR; | ||||
|  | ||||
|     if (ExtensionUtils.isOutOfDate(extension)) { | ||||
|     let checkVersion = !global.settings.get_boolean(EXTENSION_DISABLE_VERSION_CHECK_KEY); | ||||
|  | ||||
|     if (checkVersion && ExtensionUtils.isOutOfDate(extension)) { | ||||
|         extension.state = ExtensionState.OUT_OF_DATE; | ||||
|     } else { | ||||
|         let enabled = enabledExtensions.indexOf(extension.uuid) != -1; | ||||
|         if (enabled) { | ||||
|             initExtension(extension.uuid); | ||||
|             if (!initExtension(extension.uuid)) | ||||
|                 return; | ||||
|             if (extension.state == ExtensionState.DISABLED) | ||||
|                 enableExtension(extension.uuid); | ||||
|         } else { | ||||
| @@ -205,7 +220,12 @@ function initExtension(uuid) { | ||||
|     extensionModule = extension.imports.extension; | ||||
|  | ||||
|     if (extensionModule.init) { | ||||
|         extensionState = extensionModule.init(extension); | ||||
|         try { | ||||
|             extensionState = extensionModule.init(extension); | ||||
|         } catch(e) { | ||||
|             logExtensionError(uuid, e); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (!extensionState) | ||||
| @@ -214,6 +234,7 @@ function initExtension(uuid) { | ||||
|  | ||||
|     extension.state = ExtensionState.DISABLED; | ||||
|     _signals.emit('extension-loaded', uuid); | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| function getEnabledExtensions() { | ||||
| @@ -235,11 +256,7 @@ function onEnabledExtensionsChanged() { | ||||
|     newEnabledExtensions.filter(function(uuid) { | ||||
|         return enabledExtensions.indexOf(uuid) == -1; | ||||
|     }).forEach(function(uuid) { | ||||
|         try { | ||||
|             enableExtension(uuid); | ||||
|         } catch(e) { | ||||
|             logExtensionError(uuid, e); | ||||
|         } | ||||
|         enableExtension(uuid); | ||||
|     }); | ||||
|  | ||||
|     // Find and disable all the newly disabled extensions: UUIDs found in the | ||||
| @@ -247,27 +264,30 @@ function onEnabledExtensionsChanged() { | ||||
|     enabledExtensions.filter(function(item) { | ||||
|         return newEnabledExtensions.indexOf(item) == -1; | ||||
|     }).forEach(function(uuid) { | ||||
|         try { | ||||
|             disableExtension(uuid); | ||||
|         } catch(e) { | ||||
|             logExtensionError(uuid, e); | ||||
|         } | ||||
|         disableExtension(uuid); | ||||
|     }); | ||||
|  | ||||
|     enabledExtensions = newEnabledExtensions; | ||||
| } | ||||
|  | ||||
| function _onVersionValidationChanged() { | ||||
|     if (Main.sessionMode.allowExtensions) { | ||||
|         enabledExtensions.forEach(function(uuid) { | ||||
|             if (ExtensionUtils.extensions[uuid]) | ||||
|                 reloadExtension(ExtensionUtils.extensions[uuid]); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  | ||||
| function _loadExtensions() { | ||||
|     global.settings.connect('changed::' + ENABLED_EXTENSIONS_KEY, onEnabledExtensionsChanged); | ||||
|     global.settings.connect('changed::' + EXTENSION_DISABLE_VERSION_CHECK_KEY, _onVersionValidationChanged); | ||||
|  | ||||
|     enabledExtensions = getEnabledExtensions(); | ||||
|  | ||||
|     let finder = new ExtensionUtils.ExtensionFinder(); | ||||
|     finder.connect('extension-found', function(signals, extension) { | ||||
|         try { | ||||
|             loadExtension(extension); | ||||
|         } catch(e) { | ||||
|             logExtensionError(extension.uuid, e); | ||||
|         } | ||||
|     finder.connect('extension-found', function(finder, extension) { | ||||
|         loadExtension(extension); | ||||
|     }); | ||||
|     finder.scanExtensions(); | ||||
| } | ||||
|   | ||||
| @@ -32,9 +32,11 @@ const FocusCaretTracker = new Lang.Class({ | ||||
|     Name: 'FocusCaretTracker', | ||||
|  | ||||
|     _init: function() { | ||||
|         Atspi.init(); | ||||
|         Atspi.set_timeout(250, 250); | ||||
|         this._atspiListener = Atspi.EventListener.new(Lang.bind(this, this._onChanged)); | ||||
|  | ||||
|         this._atspiInited = false; | ||||
|         this._focusListenerRegistered = false; | ||||
|         this._caretListenerRegistered = false; | ||||
|     }, | ||||
|  | ||||
|     _onChanged: function(event) { | ||||
| @@ -44,22 +46,50 @@ const FocusCaretTracker = new Lang.Class({ | ||||
|             this.emit('caret-moved', event); | ||||
|     }, | ||||
|  | ||||
|     _initAtspi: function() { | ||||
|         if (!this._atspiInited) { | ||||
|             Atspi.init(); | ||||
|             Atspi.set_timeout(250, 250); | ||||
|             this._atspiInited = true; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     registerFocusListener: function() { | ||||
|         return this._atspiListener.register(STATECHANGED + ':focused') && | ||||
|                this._atspiListener.register(STATECHANGED + ':selected'); | ||||
|         if (this._focusListenerRegistered) | ||||
|             return; | ||||
|  | ||||
|         this._initAtspi(); | ||||
|  | ||||
|         this._atspiListener.register(STATECHANGED + ':focused'); | ||||
|         this._atspiListener.register(STATECHANGED + ':selected'); | ||||
|         this._focusListenerRegistered = true; | ||||
|     }, | ||||
|  | ||||
|     registerCaretListener: function() { | ||||
|         return this._atspiListener.register(CARETMOVED); | ||||
|         if (this._caretListenerRegistered) | ||||
|             return; | ||||
|  | ||||
|         this._initAtspi(); | ||||
|  | ||||
|         this._atspiListener.register(CARETMOVED); | ||||
|         this._caretListenerRegistered = true; | ||||
|     }, | ||||
|  | ||||
|     deregisterFocusListener: function() { | ||||
|         return this._atspiListener.deregister(STATECHANGED + ':focused') && | ||||
|                this._atspiListener.deregister(STATECHANGED + ':selected'); | ||||
|         if (!this._focusListenerRegistered) | ||||
|             return; | ||||
|  | ||||
|         this._atspiListener.deregister(STATECHANGED + ':focused'); | ||||
|         this._atspiListener.deregister(STATECHANGED + ':selected'); | ||||
|         this._focusListenerRegistered = false; | ||||
|     }, | ||||
|  | ||||
|     deregisterCaretListener: function() { | ||||
|         return this._atspiListener.deregister(CARETMOVED); | ||||
|         if (!this._caretListenerRegistered) | ||||
|             return; | ||||
|  | ||||
|         this._atspiListener.deregister(CARETMOVED); | ||||
|         this._caretListenerRegistered = false; | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(FocusCaretTracker.prototype); | ||||
|   | ||||
| @@ -280,7 +280,7 @@ const GrabHelper = new Lang.Class({ | ||||
|         if (type == Clutter.EventType.KEY_PRESS && | ||||
|             event.get_key_symbol() == Clutter.KEY_Escape) { | ||||
|             this.ungrab({ isUser: true }); | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } | ||||
|  | ||||
|         let press = type == Clutter.EventType.BUTTON_PRESS; | ||||
| @@ -289,14 +289,14 @@ const GrabHelper = new Lang.Class({ | ||||
|  | ||||
|         if (release && this._ignoreRelease) { | ||||
|             this._ignoreRelease = false; | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } | ||||
|  | ||||
|         if (this._isWithinGrabbedActor(event.get_source())) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         if (Main.keyboard.shouldTakeEvent(event)) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         if (button) { | ||||
|             // If we have a press event, ignore the next event, | ||||
| @@ -305,9 +305,9 @@ const GrabHelper = new Lang.Class({ | ||||
|                 this._ignoreRelease = true; | ||||
|             let i = this._actorInGrabStack(event.get_source()) + 1; | ||||
|             this.ungrab({ actor: this._grabStack[i].actor, isUser: true }); | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
| }); | ||||
|   | ||||
| @@ -32,6 +32,7 @@ const CandidateArea = new Lang.Class({ | ||||
|             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()); | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|             })); | ||||
|         } | ||||
|  | ||||
| @@ -114,9 +115,6 @@ const CandidatePopup = new Lang.Class({ | ||||
|     Name: 'CandidatePopup', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._cursor = new St.Bin({ opacity: 0 }); | ||||
|         Main.uiGroup.add_actor(this._cursor); | ||||
|  | ||||
|         this._boxPointer = new BoxPointer.BoxPointer(St.Side.TOP); | ||||
|         this._boxPointer.actor.visible = false; | ||||
|         this._boxPointer.actor.style_class = 'candidate-popup-boxpointer'; | ||||
| @@ -157,10 +155,9 @@ const CandidatePopup = new Lang.Class({ | ||||
|  | ||||
|         panelService.connect('set-cursor-location', | ||||
|                              Lang.bind(this, function(ps, x, y, w, h) { | ||||
|                                  this._cursor.set_position(x, y); | ||||
|                                  this._cursor.set_size(w, h); | ||||
|                                  Main.layoutManager.setDummyCursorGeometry(x, y, w, h); | ||||
|                                  if (this._boxPointer.actor.visible) | ||||
|                                      this._boxPointer.setPosition(this._cursor, 0); | ||||
|                                      this._boxPointer.setPosition(Main.layoutManager.dummyCursor, 0); | ||||
|                              })); | ||||
|         panelService.connect('update-preedit-text', | ||||
|                              Lang.bind(this, function(ps, text, cursorPosition, visible) { | ||||
| @@ -252,7 +249,7 @@ const CandidatePopup = new Lang.Class({ | ||||
|                          this._candidateArea.actor.visible); | ||||
|  | ||||
|         if (isVisible) { | ||||
|             this._boxPointer.setPosition(this._cursor, 0); | ||||
|             this._boxPointer.setPosition(Main.layoutManager.dummyCursor, 0); | ||||
|             this._boxPointer.show(BoxPointer.PopupAnimation.NONE); | ||||
|             this._boxPointer.actor.raise_top(); | ||||
|         } else { | ||||
|   | ||||
| @@ -143,11 +143,6 @@ const BaseIcon = new Lang.Class({ | ||||
|         this.icon = this.createIcon(this.iconSize); | ||||
|  | ||||
|         this._iconBin.child = this.icon; | ||||
|  | ||||
|         // The icon returned by createIcon() might actually be smaller than | ||||
|         // the requested icon size (for instance StTextureCache does this | ||||
|         // for fallback icons), so set the size explicitly. | ||||
|         this._iconBin.set_size(this.iconSize, this.iconSize); | ||||
|     }, | ||||
|  | ||||
|     _onStyleChanged: function() { | ||||
| @@ -219,6 +214,20 @@ const IconGrid = new Lang.Class({ | ||||
|         this._grid.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth)); | ||||
|         this._grid.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight)); | ||||
|         this._grid.connect('allocate', Lang.bind(this, this._allocate)); | ||||
|         this._grid.connect('actor-added', Lang.bind(this, this._childAdded)); | ||||
|         this._grid.connect('actor-removed', Lang.bind(this, this._childRemoved)); | ||||
|     }, | ||||
|  | ||||
|     _keyFocusIn: function(actor) { | ||||
|         this.emit('key-focus-in', actor); | ||||
|     }, | ||||
|  | ||||
|     _childAdded: function(grid, child) { | ||||
|         child._iconGridKeyFocusInId = child.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); | ||||
|     }, | ||||
|  | ||||
|     _childRemoved: function(grid, child) { | ||||
|         child.disconnect(child._iconGridKeyFocusInId); | ||||
|     }, | ||||
|  | ||||
|     _getPreferredWidth: function (grid, forHeight, alloc) { | ||||
| @@ -413,15 +422,18 @@ const IconGrid = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     removeAll: function() { | ||||
|         this._items = []; | ||||
|         this._grid.remove_all_children(); | ||||
|     }, | ||||
|  | ||||
|     destroyAll: function() { | ||||
|         this._items = []; | ||||
|         this._grid.destroy_all_children(); | ||||
|     }, | ||||
|  | ||||
|     addItem: function(item, index) { | ||||
|         if (!item.icon || !item.icon instanceof BaseIcon) { | ||||
|             log('Only items with a BaseIcon icon property can be added to IconGrid'); | ||||
|             return; | ||||
|         } | ||||
|         if (!item.icon instanceof BaseIcon) | ||||
|             throw new Error('Only items with a BaseIcon icon property can be added to IconGrid'); | ||||
|  | ||||
|         this._items.push(item); | ||||
|         if (index !== undefined) | ||||
| @@ -430,6 +442,10 @@ const IconGrid = new Lang.Class({ | ||||
|             this._grid.add_actor(item.actor); | ||||
|     }, | ||||
|  | ||||
|     removeItem: function(item) { | ||||
|         this._grid.remove_child(item.actor); | ||||
|     }, | ||||
|  | ||||
|     getItemAtIndex: function(index) { | ||||
|         return this._grid.get_child_at_index(index); | ||||
|     }, | ||||
| @@ -525,6 +541,7 @@ const IconGrid = new Lang.Class({ | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(IconGrid.prototype); | ||||
|  | ||||
| const PaginatedIconGrid = new Lang.Class({ | ||||
|     Name: 'PaginatedIconGrid', | ||||
|   | ||||
| @@ -23,27 +23,29 @@ const KEYBOARD_TYPE = 'keyboard-type'; | ||||
| const A11Y_APPLICATIONS_SCHEMA = 'org.gnome.desktop.a11y.applications'; | ||||
| const SHOW_KEYBOARD = 'screen-keyboard-enabled'; | ||||
|  | ||||
| const CaribouKeyboardIface = <interface name='org.gnome.Caribou.Keyboard'> | ||||
| <method name='Show'> | ||||
|     <arg type='u' direction='in' /> | ||||
| </method> | ||||
| <method name='Hide'> | ||||
|     <arg type='u' direction='in' /> | ||||
| </method> | ||||
| <method name='SetCursorLocation'> | ||||
|     <arg type='i' direction='in' /> | ||||
|     <arg type='i' direction='in' /> | ||||
|     <arg type='i' direction='in' /> | ||||
|     <arg type='i' direction='in' /> | ||||
| </method> | ||||
| <method name='SetEntryLocation'> | ||||
|     <arg type='i' direction='in' /> | ||||
|     <arg type='i' direction='in' /> | ||||
|     <arg type='i' direction='in' /> | ||||
|     <arg type='i' direction='in' /> | ||||
| </method> | ||||
| <property name='Name' access='read' type='s' /> | ||||
| </interface>; | ||||
| const CaribouKeyboardIface = '<node> \ | ||||
| <interface name="org.gnome.Caribou.Keyboard"> \ | ||||
| <method name="Show"> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="Hide"> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="SetCursorLocation"> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="SetEntryLocation"> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
| </method> \ | ||||
| <property name="Name" access="read" type="s" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const Key = new Lang.Class({ | ||||
|     Name: 'Key', | ||||
| @@ -80,8 +82,16 @@ const Key = new Lang.Class({ | ||||
|                                       style_class: 'keyboard-key' }); | ||||
|  | ||||
|         button.key_width = this._key.width; | ||||
|         button.connect('button-press-event', Lang.bind(this, function () { this._key.press(); })); | ||||
|         button.connect('button-release-event', Lang.bind(this, function () { this._key.release(); })); | ||||
|         button.connect('button-press-event', Lang.bind(this, | ||||
|             function () { | ||||
|                 this._key.press(); | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|             })); | ||||
|         button.connect('button-release-event', Lang.bind(this, | ||||
|             function () { | ||||
|                 this._key.release(); | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|             })); | ||||
|  | ||||
|         return button; | ||||
|     }, | ||||
| @@ -104,8 +114,16 @@ const Key = new Lang.Class({ | ||||
|             let label = this._getUnichar(extended_key); | ||||
|             let key = new St.Button({ label: label, style_class: 'keyboard-key' }); | ||||
|             key.extended_key = extended_key; | ||||
|             key.connect('button-press-event', Lang.bind(this, function () { extended_key.press(); })); | ||||
|             key.connect('button-release-event', Lang.bind(this, function () { extended_key.release(); })); | ||||
|             key.connect('button-press-event', Lang.bind(this, | ||||
|                 function () { | ||||
|                     extended_key.press(); | ||||
|                     return Clutter.EVENT_PROPAGATE; | ||||
|                 })); | ||||
|             key.connect('button-release-event', Lang.bind(this, | ||||
|                 function () { | ||||
|                     extended_key.release(); | ||||
|                     return Clutter.EVENT_PROPAGATE; | ||||
|                 })); | ||||
|             this._extended_keyboard.add(key); | ||||
|         } | ||||
|         this._boxPointer.bin.add_actor(this._extended_keyboard); | ||||
| @@ -250,7 +268,10 @@ const Keyboard = new Lang.Class({ | ||||
|  | ||||
|         if (!this._showIdleId) | ||||
|             this._showIdleId = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, | ||||
|                                              Lang.bind(this, function() { this.Show(time); })); | ||||
|                                              Lang.bind(this, function() { | ||||
|                                                  this.Show(time); | ||||
|                                                  return GLib.SOURCE_REMOVE; | ||||
|                                              })); | ||||
|     }, | ||||
|  | ||||
|     _createLayersForGroup: function (gname) { | ||||
| @@ -292,7 +313,7 @@ const Keyboard = new Lang.Class({ | ||||
|         else if (release && this._capturedPress) | ||||
|             this._hideSubkeys(); | ||||
|  | ||||
|         return true; | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     _addRows : function (keys, layout) { | ||||
| @@ -436,7 +457,6 @@ const Keyboard = new Lang.Class({ | ||||
|     _createSource: function () { | ||||
|         if (this._source == null) { | ||||
|             this._source = new KeyboardSource(this); | ||||
|             this._source.setTransient(true); | ||||
|             Main.messageTray.add(this._source); | ||||
|         } | ||||
|     }, | ||||
| @@ -478,6 +498,7 @@ const Keyboard = new Lang.Class({ | ||||
|                                                    Lang.bind(this, function() { | ||||
|                                                        this._clearKeyboardRestTimer(); | ||||
|                                                        this._show(monitor); | ||||
|                                                        return GLib.SOURCE_REMOVE; | ||||
|                                                    })); | ||||
|     }, | ||||
|  | ||||
| @@ -503,6 +524,7 @@ const Keyboard = new Lang.Class({ | ||||
|                                                    Lang.bind(this, function() { | ||||
|                                                        this._clearKeyboardRestTimer(); | ||||
|                                                        this._hide(); | ||||
|                                                        return GLib.SOURCE_REMOVE; | ||||
|                                                    })); | ||||
|     }, | ||||
|  | ||||
|   | ||||
							
								
								
									
										132
									
								
								js/ui/layout.js
									
									
									
									
									
								
							
							
						
						
									
										132
									
								
								js/ui/layout.js
									
									
									
									
									
								
							| @@ -4,7 +4,6 @@ const Clutter = imports.gi.Clutter; | ||||
| const GLib = imports.gi.GLib; | ||||
| const GObject = imports.gi.GObject; | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
| @@ -213,12 +212,21 @@ const LayoutManager = new Lang.Class({ | ||||
|         this.addChrome(this.trayBox); | ||||
|         this._setupTrayPressure(); | ||||
|  | ||||
|         this.modalDialogGroup = new St.Widget({ name: 'modalDialogGroup', | ||||
|                                                 layout_manager: new Clutter.BinLayout() }); | ||||
|         this.uiGroup.add_actor(this.modalDialogGroup); | ||||
|  | ||||
|         this.keyboardBox = new St.BoxLayout({ name: 'keyboardBox', | ||||
|                                               reactive: true, | ||||
|                                               track_hover: true }); | ||||
|         this.addChrome(this.keyboardBox); | ||||
|         this._keyboardHeightNotifyId = 0; | ||||
|  | ||||
|         // A dummy actor that tracks the mouse or text cursor, based on the | ||||
|         // position and size set in setDummyCursorGeometry. | ||||
|         this.dummyCursor = new St.Widget({ width: 0, height: 0 }); | ||||
|         this.uiGroup.add_actor(this.dummyCursor); | ||||
|  | ||||
|         global.stage.remove_actor(global.top_window_group); | ||||
|         this.uiGroup.add_actor(global.top_window_group); | ||||
|  | ||||
| @@ -352,26 +360,26 @@ const LayoutManager = new Lang.Class({ | ||||
|         this.emit('hot-corners-changed'); | ||||
|     }, | ||||
|  | ||||
|     _createBackground: function(monitorIndex) { | ||||
|     _addBackgroundMenu: function(bgManager) { | ||||
|         BackgroundMenu.addBackgroundMenu(bgManager.background.actor, this); | ||||
|     }, | ||||
|  | ||||
|     _createBackgroundManager: function(monitorIndex) { | ||||
|         let bgManager = new Background.BackgroundManager({ container: this._backgroundGroup, | ||||
|                                                            layoutManager: this, | ||||
|                                                            monitorIndex: monitorIndex }); | ||||
|         BackgroundMenu.addBackgroundMenu(bgManager.background.actor); | ||||
|  | ||||
|         bgManager.connect('changed', Lang.bind(this, function() { | ||||
|                               BackgroundMenu.addBackgroundMenu(bgManager.background.actor); | ||||
|                           })); | ||||
|         bgManager.connect('changed', Lang.bind(this, this._addBackgroundMenu)); | ||||
|         this._addBackgroundMenu(bgManager); | ||||
|  | ||||
|         this._bgManagers[monitorIndex] = bgManager; | ||||
|  | ||||
|         return bgManager.background; | ||||
|         return bgManager; | ||||
|     }, | ||||
|  | ||||
|     _createSecondaryBackgrounds: function() { | ||||
|     _showSecondaryBackgrounds: function() { | ||||
|         for (let i = 0; i < this.monitors.length; i++) { | ||||
|             if (i != this.primaryIndex) { | ||||
|                 let background = this._createBackground(i); | ||||
|  | ||||
|                 let background = this._bgManagers[i].background; | ||||
|                 background.actor.show(); | ||||
|                 background.actor.opacity = 0; | ||||
|                 Tweener.addTween(background.actor, | ||||
|                                  { opacity: 255, | ||||
| @@ -381,10 +389,6 @@ const LayoutManager = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _createPrimaryBackground: function() { | ||||
|         this._createBackground(this.primaryIndex); | ||||
|     }, | ||||
|  | ||||
|     _updateBackgrounds: function() { | ||||
|         let i; | ||||
|         for (i = 0; i < this._bgManagers.length; i++) | ||||
| @@ -395,11 +399,12 @@ const LayoutManager = new Lang.Class({ | ||||
|         if (Main.sessionMode.isGreeter) | ||||
|             return; | ||||
|  | ||||
|         if (this._startingUp) | ||||
|             return; | ||||
|  | ||||
|         for (let i = 0; i < this.monitors.length; i++) { | ||||
|             this._createBackground(i); | ||||
|             let bgManager = this._createBackgroundManager(i); | ||||
|             this._bgManagers.push(bgManager); | ||||
|  | ||||
|             if (i != this.primaryIndex && this._startingUp) | ||||
|                 bgManager.background.actor.hide(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -595,7 +600,7 @@ const LayoutManager = new Lang.Class({ | ||||
|         if (Main.sessionMode.isGreeter) { | ||||
|             this.panelBox.translation_y = -this.panelBox.height; | ||||
|         } else { | ||||
|             this._createPrimaryBackground(); | ||||
|             this._updateBackgrounds(); | ||||
|  | ||||
|             // We need to force an update of the regions now before we scale | ||||
|             // the UI group to get the coorect allocation for the struts. | ||||
| @@ -610,7 +615,7 @@ const LayoutManager = new Lang.Class({ | ||||
|  | ||||
|             this.uiGroup.set_pivot_point(x / global.screen_width, | ||||
|                                          y / global.screen_height); | ||||
|             this.uiGroup.scale_x = this.uiGroup.scale_y = 0.5; | ||||
|             this.uiGroup.scale_x = this.uiGroup.scale_y = 0.75; | ||||
|             this.uiGroup.opacity = 0; | ||||
|             global.window_group.set_clip(monitor.x, monitor.y, monitor.width, monitor.height); | ||||
|         } | ||||
| @@ -625,7 +630,7 @@ const LayoutManager = new Lang.Class({ | ||||
|         // when the system is bogged down | ||||
|         GLib.idle_add(GLib.PRIORITY_LOW, Lang.bind(this, function() { | ||||
|             this._startupAnimation(); | ||||
|             return false; | ||||
|             return GLib.SOURCE_REMOVE; | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
| @@ -673,7 +678,7 @@ const LayoutManager = new Lang.Class({ | ||||
|         this.keyboardBox.show(); | ||||
|  | ||||
|         if (!Main.sessionMode.isGreeter) { | ||||
|             this._createSecondaryBackgrounds(); | ||||
|             this._showSecondaryBackgrounds(); | ||||
|             global.window_group.remove_clip(); | ||||
|         } | ||||
|  | ||||
| @@ -723,6 +728,21 @@ const LayoutManager = new Lang.Class({ | ||||
|         this._updateRegions(); | ||||
|     }, | ||||
|  | ||||
|     // setDummyCursorGeometry: | ||||
|     // | ||||
|     // The cursor dummy is a standard widget commonly used for popup | ||||
|     // menus and box pointers to track, as the box pointer API only | ||||
|     // tracks actors. If you want to pop up a menu based on where the | ||||
|     // user clicked, or where the text cursor is, the cursor dummy | ||||
|     // is what you should use. Given that the menu should not track | ||||
|     // the actual mouse pointer as it moves, you need to call this | ||||
|     // function before you show the menu to ensure it is at the right | ||||
|     // position and has the right size. | ||||
|     setDummyCursorGeometry: function(x, y, w, h) { | ||||
|         this.dummyCursor.set_position(Math.round(x), Math.round(y)); | ||||
|         this.dummyCursor.set_size(Math.round(w), Math.round(h)); | ||||
|     }, | ||||
|  | ||||
|     // addChrome: | ||||
|     // @actor: an actor to add to the chrome | ||||
|     // @params: (optional) additional params | ||||
| @@ -814,13 +834,12 @@ const LayoutManager = new Lang.Class({ | ||||
|  | ||||
|         let actorData = Params.parse(params, defaultParams); | ||||
|         actorData.actor = actor; | ||||
|         actorData.isToplevel = actor.get_parent() == this.uiGroup; | ||||
|         actorData.visibleId = actor.connect('notify::visible', | ||||
|                                             Lang.bind(this, this._queueUpdateRegions)); | ||||
|         actorData.allocationId = actor.connect('notify::allocation', | ||||
|                                                Lang.bind(this, this._queueUpdateRegions)); | ||||
|         actorData.parentSetId = actor.connect('parent-set', | ||||
|                                               Lang.bind(this, this._actorReparented)); | ||||
|         actorData.destroyId = actor.connect('destroy', | ||||
|                                             Lang.bind(this, this._untrackActor)); | ||||
|         // Note that destroying actor will unset its parent, so we don't | ||||
|         // need to connect to 'destroy' too. | ||||
|  | ||||
| @@ -838,22 +857,11 @@ const LayoutManager = new Lang.Class({ | ||||
|         this._trackedActors.splice(i, 1); | ||||
|         actor.disconnect(actorData.visibleId); | ||||
|         actor.disconnect(actorData.allocationId); | ||||
|         actor.disconnect(actorData.parentSetId); | ||||
|         actor.disconnect(actorData.destroyId); | ||||
|  | ||||
|         this._queueUpdateRegions(); | ||||
|     }, | ||||
|  | ||||
|     _actorReparented: function(actor, oldParent) { | ||||
|         let newParent = actor.get_parent(); | ||||
|         if (!newParent) { | ||||
|             this._untrackActor(actor); | ||||
|         } else { | ||||
|             let i = this._findActor(actor); | ||||
|             let actorData = this._trackedActors[i]; | ||||
|             actorData.isToplevel = (newParent == this.uiGroup); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateVisibility: function() { | ||||
|         let windowsVisible = Main.sessionMode.hasWindows && !this._inOverview; | ||||
|  | ||||
| @@ -864,8 +872,6 @@ const LayoutManager = new Lang.Class({ | ||||
|             let actorData = this._trackedActors[i], visible; | ||||
|             if (!actorData.trackFullscreen) | ||||
|                 continue; | ||||
|             if (!actorData.isToplevel) | ||||
|                 continue; | ||||
|  | ||||
|             if (!windowsVisible) | ||||
|                 visible = true; | ||||
| @@ -905,8 +911,8 @@ const LayoutManager = new Lang.Class({ | ||||
|             return; | ||||
|  | ||||
|         if (!this._updateRegionIdle) | ||||
|             this._updateRegionIdle = Mainloop.idle_add(Lang.bind(this, this._updateRegions), | ||||
|                                                        Meta.PRIORITY_BEFORE_REDRAW); | ||||
|             this._updateRegionIdle = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, | ||||
|                                                     Lang.bind(this, this._updateRegions)); | ||||
|     }, | ||||
|  | ||||
|     _getWindowActorsForWorkspace: function(workspace) { | ||||
| @@ -937,7 +943,7 @@ const LayoutManager = new Lang.Class({ | ||||
|         let rects = [], struts = [], i; | ||||
|  | ||||
|         if (this._updateRegionIdle) { | ||||
|             Mainloop.source_remove(this._updateRegionIdle); | ||||
|             Meta.later_remove(this._updateRegionIdle); | ||||
|             delete this._updateRegionIdle; | ||||
|         } | ||||
|  | ||||
| @@ -1011,19 +1017,39 @@ const LayoutManager = new Lang.Class({ | ||||
|                     continue; | ||||
|  | ||||
|                 // Ensure that the strut rects goes all the way to the screen edge, | ||||
|                 // as this really what mutter expects. | ||||
|                 // as this really what mutter expects. However skip this step | ||||
|                 // in cases where this would render an entire monitor unusable. | ||||
|                 switch (side) { | ||||
|                 case Meta.Side.TOP: | ||||
|                     y1 = 0; | ||||
|                     let hasMonitorsAbove = this.monitors.some(Lang.bind(this, | ||||
|                         function(mon) { | ||||
|                             return this._isAboveOrBelowPrimary(mon) && | ||||
|                                    mon.y < primary.y; | ||||
|                         })); | ||||
|                     if (!hasMonitorsAbove) | ||||
|                         y1 = 0; | ||||
|                     break; | ||||
|                 case Meta.Side.BOTTOM: | ||||
|                     y2 = global.screen_height; | ||||
|                     if (this.primaryIndex == this.bottomIndex) | ||||
|                         y2 = global.screen_height; | ||||
|                     break; | ||||
|                 case Meta.Side.LEFT: | ||||
|                     x1 = 0; | ||||
|                     let hasMonitorsLeft = this.monitors.some(Lang.bind(this, | ||||
|                         function(mon) { | ||||
|                             return !this._isAboveOrBelowPrimary(mon) && | ||||
|                                    mon.x < primary.x; | ||||
|                         })); | ||||
|                     if (!hasMonitorsLeft) | ||||
|                         x1 = 0; | ||||
|                     break; | ||||
|                 case Meta.Side.RIGHT: | ||||
|                     x2 = global.screen_width; | ||||
|                     let hasMonitorsRight = this.monitors.some(Lang.bind(this, | ||||
|                         function(mon) { | ||||
|                             return !this._isAboveOrBelowPrimary(mon) && | ||||
|                                    mon.x > primary.x; | ||||
|                         })); | ||||
|                     if (!hasMonitorsRight) | ||||
|                         x2 = global.screen_width; | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
| @@ -1042,7 +1068,7 @@ const LayoutManager = new Lang.Class({ | ||||
|             workspace.set_builtin_struts(struts); | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(LayoutManager.prototype); | ||||
| @@ -1229,20 +1255,20 @@ const HotCorner = new Lang.Class({ | ||||
|             this._entered = true; | ||||
|             this._toggleOverview(); | ||||
|         } | ||||
|         return false; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onCornerLeft : function(actor, event) { | ||||
|         if (event.get_related() != this.actor) | ||||
|             this._entered = false; | ||||
|         // Consume event, otherwise this will confuse onEnvironsLeft | ||||
|         return true; | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     _onEnvironsLeft : function(actor, event) { | ||||
|         if (event.get_related() != this._corner) | ||||
|             this._entered = false; | ||||
|         return false; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     } | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -5,11 +5,67 @@ const Lang = imports.lang; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Signals = imports.signals; | ||||
| const St = imports.gi.St; | ||||
| const Shell = imports.gi.Shell; | ||||
|  | ||||
| const Params = imports.misc.params; | ||||
| const Tweener = imports.ui.tweener; | ||||
|  | ||||
| const DEFAULT_FADE_FACTOR = 0.4; | ||||
| const VIGNETTE_BRIGHTNESS = 0.8; | ||||
| const VIGNETTE_SHARPNESS = 0.7; | ||||
|  | ||||
| const VIGNETTE_DECLARATIONS = '\ | ||||
| uniform float brightness;\n\ | ||||
| uniform float vignette_sharpness;\n'; | ||||
|  | ||||
| const VIGNETTE_CODE = '\ | ||||
| cogl_color_out.a = cogl_color_in.a;\n\ | ||||
| cogl_color_out.rgb = vec3(0.0, 0.0, 0.0);\n\ | ||||
| vec2 position = cogl_tex_coord_in[0].xy - 0.5;\n\ | ||||
| float t = length(2.0 * position);\n\ | ||||
| t = clamp(t, 0.0, 1.0);\n\ | ||||
| float pixel_brightness = mix(1.0, 1.0 - vignette_sharpness, t);\n\ | ||||
| cogl_color_out.a = cogl_color_out.a * (1 - pixel_brightness * brightness);'; | ||||
|  | ||||
| const RadialShaderQuad = new Lang.Class({ | ||||
|     Name: 'RadialShaderQuad', | ||||
|     Extends: Shell.GLSLQuad, | ||||
|  | ||||
|     _init: function(params) { | ||||
|         this.parent(params); | ||||
|  | ||||
|         this._brightnessLocation = this.get_uniform_location('brightness'); | ||||
|         this._sharpnessLocation = this.get_uniform_location('vignette_sharpness'); | ||||
|  | ||||
|         this.brightness = 1.0; | ||||
|         this.vignetteSharpness = 0.0; | ||||
|     }, | ||||
|  | ||||
|     vfunc_build_pipeline: function() { | ||||
|         this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, | ||||
|                               VIGNETTE_DECLARATIONS, VIGNETTE_CODE, true); | ||||
|     }, | ||||
|  | ||||
|     get brightness() { | ||||
|         return this._brightness; | ||||
|     }, | ||||
|  | ||||
|     set brightness(v) { | ||||
|         this._brightness = v; | ||||
|         this.set_uniform_float(this._brightnessLocation, | ||||
|                                1, [this._brightness]); | ||||
|     }, | ||||
|  | ||||
|     get vignetteSharpness() { | ||||
|         return this._sharpness; | ||||
|     }, | ||||
|  | ||||
|     set vignetteSharpness(v) { | ||||
|         this._sharpness = v; | ||||
|         this.set_uniform_float(this._sharpnessLocation, | ||||
|                                1, [this._sharpness]); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| /** | ||||
|  * Lightbox: | ||||
| @@ -43,15 +99,23 @@ const Lightbox = new Lang.Class({ | ||||
|                                         width: null, | ||||
|                                         height: null, | ||||
|                                         fadeFactor: DEFAULT_FADE_FACTOR, | ||||
|                                         radialEffect: false, | ||||
|                                       }); | ||||
|  | ||||
|         this._container = container; | ||||
|         this._children = container.get_children(); | ||||
|         this._fadeFactor = params.fadeFactor; | ||||
|         this.actor = new St.Bin({ x: 0, | ||||
|                                   y: 0, | ||||
|                                   style_class: 'lightbox', | ||||
|                                   reactive: params.inhibitEvents }); | ||||
|         this._radialEffect = Clutter.feature_available(Clutter.FeatureFlags.SHADERS_GLSL) && params.radialEffect; | ||||
|         if (this._radialEffect) | ||||
|             this.actor = new RadialShaderQuad({ x: 0, | ||||
|                                                 y: 0, | ||||
|                                                 reactive: params.inhibitEvents }); | ||||
|         else | ||||
|             this.actor = new St.Bin({ x: 0, | ||||
|                                       y: 0, | ||||
|                                       opacity: 0, | ||||
|                                       style_class: 'lightbox', | ||||
|                                       reactive: params.inhibitEvents }); | ||||
|  | ||||
|         container.add_actor(this.actor); | ||||
|         this.actor.raise_top(); | ||||
| @@ -101,9 +165,18 @@ const Lightbox = new Lang.Class({ | ||||
|         fadeInTime = fadeInTime || 0; | ||||
|  | ||||
|         Tweener.removeTweens(this.actor); | ||||
|         if (fadeInTime != 0) { | ||||
|             this.shown = false; | ||||
|             this.actor.opacity = 0; | ||||
|         if (this._radialEffect) { | ||||
|             Tweener.addTween(this.actor, | ||||
|                              { brightness: VIGNETTE_BRIGHTNESS, | ||||
|                                vignetteSharpness: VIGNETTE_SHARPNESS, | ||||
|                                time: fadeInTime, | ||||
|                                transition: 'easeOutQuad', | ||||
|                                onComplete: Lang.bind(this, function() { | ||||
|                                    this.shown = true; | ||||
|                                    this.emit('shown'); | ||||
|                                }) | ||||
|                              }); | ||||
|         } else { | ||||
|             Tweener.addTween(this.actor, | ||||
|                              { opacity: 255 * this._fadeFactor, | ||||
|                                time: fadeInTime, | ||||
| @@ -113,11 +186,8 @@ const Lightbox = new Lang.Class({ | ||||
|                                    this.emit('shown'); | ||||
|                                }) | ||||
|                              }); | ||||
|         } else { | ||||
|             this.actor.opacity = 255 * this._fadeFactor; | ||||
|             this.shown = true; | ||||
|             this.emit('shown'); | ||||
|         } | ||||
|  | ||||
|         this.actor.show(); | ||||
|     }, | ||||
|  | ||||
| @@ -126,7 +196,18 @@ const Lightbox = new Lang.Class({ | ||||
|  | ||||
|         this.shown = false; | ||||
|         Tweener.removeTweens(this.actor); | ||||
|         if (fadeOutTime != 0) { | ||||
|         if (this._radialEffect) { | ||||
|             Tweener.addTween(this.actor, | ||||
|                              { brightness: 1.0, | ||||
|                                vignetteSharpness: 0.0, | ||||
|                                opacity: 0, | ||||
|                                time: fadeOutTime, | ||||
|                                transition: 'easeOutQuad', | ||||
|                                onComplete: Lang.bind(this, function() { | ||||
|                                    this.actor.hide(); | ||||
|                                }) | ||||
|                              }); | ||||
|         } else { | ||||
|             Tweener.addTween(this.actor, | ||||
|                              { opacity: 0, | ||||
|                                time: fadeOutTime, | ||||
| @@ -135,8 +216,6 @@ const Lightbox = new Lang.Class({ | ||||
|                                    this.actor.hide(); | ||||
|                                }) | ||||
|                              }); | ||||
|         } else { | ||||
|             this.actor.hide(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|   | ||||
| @@ -27,6 +27,8 @@ const CHEVRON = '>>> '; | ||||
| /* Imports...feel free to add here as needed */ | ||||
| var commandHeader = 'const Clutter = imports.gi.Clutter; ' + | ||||
|                     'const GLib = imports.gi.GLib; ' + | ||||
|                     'const GObject = imports.gi.GObject; ' + | ||||
|                     'const Gio = imports.gi.Gio; ' + | ||||
|                     'const Gtk = imports.gi.Gtk; ' + | ||||
|                     'const Mainloop = imports.mainloop; ' + | ||||
|                     'const Meta = imports.gi.Meta; ' + | ||||
| @@ -109,6 +111,7 @@ const AutoComplete = new Lang.Class({ | ||||
|             } | ||||
|             this._lastTabTime = currTime; | ||||
|         } | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     // Insert characters of text not already included in head at cursor position.  i.e., if text="abc" and head="a", | ||||
| @@ -558,7 +561,7 @@ const Inspector = new Lang.Class({ | ||||
|     _onKeyPressEvent: function (actor, event) { | ||||
|         if (event.get_key_symbol() == Clutter.Escape) | ||||
|             this._close(); | ||||
|         return true; | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     _onButtonPressEvent: function (actor, event) { | ||||
| @@ -567,7 +570,7 @@ const Inspector = new Lang.Class({ | ||||
|             this.emit('target', this._target, stageX, stageY); | ||||
|         } | ||||
|         this._close(); | ||||
|         return true; | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     _onScrollEvent: function (actor, event) { | ||||
| @@ -601,12 +604,12 @@ const Inspector = new Lang.Class({ | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|         return true; | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     _onMotionEvent: function (actor, event) { | ||||
|         this._update(event); | ||||
|         return true; | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     _update: function(event) { | ||||
| @@ -629,55 +632,6 @@ const Inspector = new Lang.Class({ | ||||
|  | ||||
| Signals.addSignalMethods(Inspector.prototype); | ||||
|  | ||||
| const Memory = new Lang.Class({ | ||||
|     Name: 'Memory', | ||||
|  | ||||
|     _init: function() { | ||||
|         this.actor = new St.BoxLayout({ vertical: true }); | ||||
|         this._glibc_uordblks = new St.Label(); | ||||
|         this.actor.add(this._glibc_uordblks); | ||||
|  | ||||
|         this._js_bytes = new St.Label(); | ||||
|         this.actor.add(this._js_bytes); | ||||
|  | ||||
|         this._gjs_boxed = new St.Label(); | ||||
|         this.actor.add(this._gjs_boxed); | ||||
|  | ||||
|         this._gjs_gobject = new St.Label(); | ||||
|         this.actor.add(this._gjs_gobject); | ||||
|  | ||||
|         this._gjs_function = new St.Label(); | ||||
|         this.actor.add(this._gjs_function); | ||||
|  | ||||
|         this._gjs_closure = new St.Label(); | ||||
|         this.actor.add(this._gjs_closure); | ||||
|  | ||||
|         this._last_gc_seconds_ago = new St.Label(); | ||||
|         this.actor.add(this._last_gc_seconds_ago); | ||||
|  | ||||
|         this._gcbutton = new St.Button({ label: 'Full GC', | ||||
|                                          style_class: 'lg-obj-inspector-button' }); | ||||
|         this._gcbutton.connect('clicked', Lang.bind(this, function () { System.gc(); this._renderText(); })); | ||||
|         this.actor.add(this._gcbutton, { x_align: St.Align.START, | ||||
|                                          x_fill: false }); | ||||
|  | ||||
|         this.actor.connect('notify::mapped', Lang.bind(this, this._renderText)); | ||||
|     }, | ||||
|  | ||||
|     _renderText: function() { | ||||
|         if (!this.actor.mapped) | ||||
|             return; | ||||
|         let memInfo = global.get_memory_info(); | ||||
|         this._glibc_uordblks.text = 'glibc_uordblks: ' + memInfo.glibc_uordblks; | ||||
|         this._js_bytes.text = 'js bytes: ' + memInfo.js_bytes; | ||||
|         this._gjs_boxed.text = 'gjs_boxed: ' + memInfo.gjs_boxed; | ||||
|         this._gjs_gobject.text = 'gjs_gobject: ' + memInfo.gjs_gobject; | ||||
|         this._gjs_function.text = 'gjs_function: ' + memInfo.gjs_function; | ||||
|         this._gjs_closure.text = 'gjs_closure: ' + memInfo.gjs_closure; | ||||
|         this._last_gc_seconds_ago.text = 'last_gc_seconds_ago: ' + memInfo.last_gc_seconds_ago; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const Extensions = new Lang.Class({ | ||||
|     Name: 'Extensions', | ||||
|  | ||||
| @@ -718,13 +672,13 @@ const Extensions = new Lang.Class({ | ||||
|     _onViewSource: function (actor) { | ||||
|         let extension = actor._extension; | ||||
|         let uri = extension.dir.get_uri(); | ||||
|         Gio.app_info_launch_default_for_uri(uri, global.create_app_launch_context()); | ||||
|         Gio.app_info_launch_default_for_uri(uri, global.create_app_launch_context(0, -1)); | ||||
|         this._lookingGlass.close(); | ||||
|     }, | ||||
|  | ||||
|     _onWebPage: function (actor) { | ||||
|         let extension = actor._extension; | ||||
|         Gio.app_info_launch_default_for_uri(extension.metadata.url, global.create_app_launch_context()); | ||||
|         Gio.app_info_launch_default_for_uri(extension.metadata.url, global.create_app_launch_context(0, -1)); | ||||
|         this._lookingGlass.close(); | ||||
|     }, | ||||
|  | ||||
| @@ -877,7 +831,22 @@ const LookingGlass = new Lang.Class({ | ||||
|                 global.stage.set_key_focus(this._entry); | ||||
|             })); | ||||
|             this.actor.hide(); | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         })); | ||||
|  | ||||
|         let gcIcon = new St.Icon({ icon_name: 'gnome-fs-trash-full', | ||||
|                                    icon_size: 24 }); | ||||
|         toolbar.add_actor(gcIcon); | ||||
|         gcIcon.reactive = true; | ||||
|         gcIcon.connect('button-press-event', Lang.bind(this, function () { | ||||
|            gcIcon.icon_name = 'gnome-fs-trash-empty'; | ||||
|            System.gc(); | ||||
|            this._timeoutId = Mainloop.timeout_add(500, Lang.bind(this, function () { | ||||
|                 gcIcon.icon_name = 'gnome-fs-trash-full'; | ||||
|                 Mainloop.source_remove(this._timeoutId); | ||||
|                 return GLib.SOURCE_REMOVE; | ||||
|            })); | ||||
|            return Clutter.EVENT_PROPAGATE; | ||||
|         })); | ||||
|  | ||||
|         let notebook = new Notebook(); | ||||
| @@ -907,9 +876,6 @@ const LookingGlass = new Lang.Class({ | ||||
|         this._windowList = new WindowList(this); | ||||
|         notebook.appendPage('Windows', this._windowList.actor); | ||||
|  | ||||
|         this._memory = new Memory(); | ||||
|         notebook.appendPage('Memory', this._memory.actor); | ||||
|  | ||||
|         this._extensions = new Extensions(this); | ||||
|         notebook.appendPage('Extensions', this._extensions.actor); | ||||
|  | ||||
| @@ -1102,7 +1068,7 @@ const LookingGlass = new Lang.Class({ | ||||
|             } else { | ||||
|                 this.close(); | ||||
|             } | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } | ||||
|         // Ctrl+PgUp and Ctrl+PgDown switches tabs in the notebook view | ||||
|         if (modifierState & Clutter.ModifierType.CONTROL_MASK) { | ||||
| @@ -1112,7 +1078,7 @@ const LookingGlass = new Lang.Class({ | ||||
|                 this._notebook.nextTab(); | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     open : function() { | ||||
|   | ||||
| @@ -11,6 +11,7 @@ const Mainloop = imports.mainloop; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const Background = imports.ui.background; | ||||
| const FocusCaretTracker = imports.ui.focusCaretTracker; | ||||
| const Main = imports.ui.main; | ||||
| const MagnifierDBus = imports.ui.magnifierDBus; | ||||
| @@ -57,20 +58,6 @@ const Magnifier = new Lang.Class({ | ||||
|         // Magnifier is a manager of ZoomRegions. | ||||
|         this._zoomRegions = []; | ||||
|  | ||||
|         // Export to dbus. | ||||
|         magDBusService = new MagnifierDBus.ShellMagnifier(); | ||||
|  | ||||
|         let showAtLaunch = this._settingsInit(); | ||||
|         this.setActive(showAtLaunch); | ||||
|     }, | ||||
|  | ||||
|     _initialize: function() { | ||||
|         if (this._initialized) | ||||
|             return; | ||||
|         this._initialized = true; | ||||
|  | ||||
|         this._settingsInitLate(); | ||||
|  | ||||
|         // Create small clutter tree for the magnified mouse. | ||||
|         let cursorTracker = Meta.CursorTracker.get_for_screen(global.screen); | ||||
|         this._mouseSprite = new Clutter.Texture(); | ||||
| @@ -86,11 +73,15 @@ const Magnifier = new Lang.Class({ | ||||
|  | ||||
|         let aZoomRegion = new ZoomRegion(this, this._cursorRoot); | ||||
|         this._zoomRegions.push(aZoomRegion); | ||||
|         this._settingsInitRegion(aZoomRegion); | ||||
|         let showAtLaunch = this._settingsInit(aZoomRegion); | ||||
|         aZoomRegion.scrollContentsTo(this.xMouse, this.yMouse); | ||||
|  | ||||
|         cursorTracker.connect('cursor-changed', Lang.bind(this, this._updateMouseSprite)); | ||||
|         this._cursorTracker = cursorTracker; | ||||
|  | ||||
|         // Export to dbus. | ||||
|         magDBusService = new MagnifierDBus.ShellMagnifier(); | ||||
|         this.setActive(showAtLaunch); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
| @@ -115,20 +106,21 @@ const Magnifier = new Lang.Class({ | ||||
|      * @activate:   Boolean to activate or de-activate the magnifier. | ||||
|      */ | ||||
|     setActive: function(activate) { | ||||
|         if (activate == this.isActive()) | ||||
|             return; | ||||
|  | ||||
|         if (activate) | ||||
|             this._initialize(); | ||||
|         let isActive = this.isActive(); | ||||
|  | ||||
|         this._zoomRegions.forEach (function(zoomRegion, index, array) { | ||||
|             zoomRegion.setActive(activate); | ||||
|         }); | ||||
|  | ||||
|         if (activate) | ||||
|             this.startTrackingMouse(); | ||||
|         else | ||||
|             this.stopTrackingMouse(); | ||||
|         if (isActive != activate) { | ||||
|             if (activate) { | ||||
|                 Meta.disable_unredirect_for_screen(global.screen); | ||||
|                 this.startTrackingMouse(); | ||||
|             } else { | ||||
|                 Meta.enable_unredirect_for_screen(global.screen); | ||||
|                 this.stopTrackingMouse(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Make sure system mouse pointer is shown when all zoom regions are | ||||
|         // invisible. | ||||
| @@ -448,68 +440,64 @@ const Magnifier = new Lang.Class({ | ||||
|         this._mouseSprite.set_anchor_point(xHot, yHot); | ||||
|     }, | ||||
|  | ||||
|     _settingsInitRegion: function(zoomRegion) { | ||||
|         // Mag factor is accurate to two decimal places. | ||||
|         let aPref = parseFloat(this._settings.get_double(MAG_FACTOR_KEY).toFixed(2)); | ||||
|         if (aPref != 0.0) | ||||
|             zoomRegion.setMagFactor(aPref, aPref); | ||||
|  | ||||
|         aPref = this._settings.get_enum(SCREEN_POSITION_KEY); | ||||
|         if (aPref) | ||||
|             zoomRegion.setScreenPosition(aPref); | ||||
|  | ||||
|         zoomRegion.setLensMode(this._settings.get_boolean(LENS_MODE_KEY)); | ||||
|         zoomRegion.setClampScrollingAtEdges(!this._settings.get_boolean(CLAMP_MODE_KEY)); | ||||
|  | ||||
|         aPref = this._settings.get_enum(MOUSE_TRACKING_KEY); | ||||
|         if (aPref) | ||||
|             zoomRegion.setMouseTrackingMode(aPref); | ||||
|  | ||||
|         aPref = this._settings.get_enum(FOCUS_TRACKING_KEY); | ||||
|         if (aPref) | ||||
|             zoomRegion.setFocusTrackingMode(aPref); | ||||
|  | ||||
|         aPref = this._settings.get_enum(CARET_TRACKING_KEY); | ||||
|         if (aPref) | ||||
|             zoomRegion.setCaretTrackingMode(aPref); | ||||
|  | ||||
|         aPref = this._settings.get_boolean(INVERT_LIGHTNESS_KEY); | ||||
|         if (aPref) | ||||
|             zoomRegion.setInvertLightness(aPref); | ||||
|  | ||||
|         aPref = this._settings.get_double(COLOR_SATURATION_KEY); | ||||
|         if (aPref) | ||||
|             zoomRegion.setColorSaturation(aPref); | ||||
|  | ||||
|         let bc = {}; | ||||
|         bc.r = this._settings.get_double(BRIGHT_RED_KEY); | ||||
|         bc.g = this._settings.get_double(BRIGHT_GREEN_KEY); | ||||
|         bc.b = this._settings.get_double(BRIGHT_BLUE_KEY); | ||||
|         zoomRegion.setBrightness(bc); | ||||
|  | ||||
|         bc.r = this._settings.get_double(CONTRAST_RED_KEY); | ||||
|         bc.g = this._settings.get_double(CONTRAST_GREEN_KEY); | ||||
|         bc.b = this._settings.get_double(CONTRAST_BLUE_KEY); | ||||
|         zoomRegion.setContrast(bc); | ||||
|     }, | ||||
|  | ||||
|     _settingsInit: function() { | ||||
|     _settingsInit: function(zoomRegion) { | ||||
|         this._appSettings = new Gio.Settings({ schema: APPLICATIONS_SCHEMA }); | ||||
|         this._settings = new Gio.Settings({ schema: MAGNIFIER_SCHEMA }); | ||||
|  | ||||
|         this._appSettings.connect('changed::' + SHOW_KEY, Lang.bind(this, function() { | ||||
|             let active = this._appSettings.get_boolean(SHOW_KEY); | ||||
|             this.setActive(active); | ||||
|         })); | ||||
|         if (zoomRegion) { | ||||
|             // Mag factor is accurate to two decimal places. | ||||
|             let aPref = parseFloat(this._settings.get_double(MAG_FACTOR_KEY).toFixed(2)); | ||||
|             if (aPref != 0.0) | ||||
|                 zoomRegion.setMagFactor(aPref, aPref); | ||||
|  | ||||
|         return this._appSettings.get_boolean(SHOW_KEY); | ||||
|     }, | ||||
|             aPref = this._settings.get_enum(SCREEN_POSITION_KEY); | ||||
|             if (aPref) | ||||
|                 zoomRegion.setScreenPosition(aPref); | ||||
|  | ||||
|             zoomRegion.setLensMode(this._settings.get_boolean(LENS_MODE_KEY)); | ||||
|             zoomRegion.setClampScrollingAtEdges(!this._settings.get_boolean(CLAMP_MODE_KEY)); | ||||
|  | ||||
|             aPref = this._settings.get_enum(MOUSE_TRACKING_KEY); | ||||
|             if (aPref) | ||||
|                 zoomRegion.setMouseTrackingMode(aPref); | ||||
|  | ||||
|             aPref = this._settings.get_enum(FOCUS_TRACKING_KEY); | ||||
|             if (aPref) | ||||
|                 zoomRegion.setFocusTrackingMode(aPref); | ||||
|  | ||||
|             aPref = this._settings.get_enum(CARET_TRACKING_KEY); | ||||
|             if (aPref) | ||||
|                 zoomRegion.setCaretTrackingMode(aPref); | ||||
|  | ||||
|             aPref = this._settings.get_boolean(INVERT_LIGHTNESS_KEY); | ||||
|             if (aPref) | ||||
|                 zoomRegion.setInvertLightness(aPref); | ||||
|  | ||||
|             aPref = this._settings.get_double(COLOR_SATURATION_KEY); | ||||
|             if (aPref) | ||||
|                 zoomRegion.setColorSaturation(aPref); | ||||
|  | ||||
|             let bc = {}; | ||||
|             bc.r = this._settings.get_double(BRIGHT_RED_KEY); | ||||
|             bc.g = this._settings.get_double(BRIGHT_GREEN_KEY); | ||||
|             bc.b = this._settings.get_double(BRIGHT_BLUE_KEY); | ||||
|             zoomRegion.setBrightness(bc); | ||||
|  | ||||
|             bc.r = this._settings.get_double(CONTRAST_RED_KEY); | ||||
|             bc.g = this._settings.get_double(CONTRAST_GREEN_KEY); | ||||
|             bc.b = this._settings.get_double(CONTRAST_BLUE_KEY); | ||||
|             zoomRegion.setContrast(bc); | ||||
|         } | ||||
|  | ||||
|     _settingsInitLate: function() { | ||||
|         let showCrosshairs = this._settings.get_boolean(SHOW_CROSS_HAIRS_KEY); | ||||
|         this.addCrosshairs(); | ||||
|         this.setCrosshairsVisible(showCrosshairs); | ||||
|  | ||||
|         this._appSettings.connect('changed::' + SHOW_KEY, | ||||
|                                   Lang.bind(this, function() { | ||||
|             this.setActive(this._appSettings.get_boolean(SHOW_KEY)); | ||||
|         })); | ||||
|  | ||||
|         this._settings.connect('changed::' + SCREEN_POSITION_KEY, | ||||
|                                Lang.bind(this, this._updateScreenPosition)); | ||||
|         this._settings.connect('changed::' + MAG_FACTOR_KEY, | ||||
| @@ -573,6 +561,7 @@ const Magnifier = new Lang.Class({ | ||||
|                                Lang.bind(this, function() { | ||||
|             this.setCrosshairsClip(this._settings.get_boolean(CROSS_HAIRS_CLIP_KEY)); | ||||
|         })); | ||||
|         return this._appSettings.get_boolean(SHOW_KEY); | ||||
|    }, | ||||
|  | ||||
|     _updateScreenPosition: function() { | ||||
| @@ -731,8 +720,16 @@ const ZoomRegion = new Lang.Class({ | ||||
|         let component = event.source.get_component_iface(); | ||||
|         if (!component || event.detail1 != 1) | ||||
|             return; | ||||
|         let extents = component.get_extents(Atspi.CoordType.SCREEN); | ||||
|         [this._xFocus, this._yFocus] = [extents.x, extents.y] | ||||
|         let extents; | ||||
|         try { | ||||
|             extents = component.get_extents(Atspi.CoordType.SCREEN); | ||||
|         } catch(e) { | ||||
|             log('Failed to read extents of focused component: ' + e.message); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         [this._xFocus, this._yFocus] = [extents.x + (extents.width / 2), | ||||
|                                         extents.y + (extents.height / 2)]; | ||||
|         this._centerFromFocusPosition(); | ||||
|     }, | ||||
|  | ||||
| @@ -740,7 +737,14 @@ const ZoomRegion = new Lang.Class({ | ||||
|         let text = event.source.get_text_iface(); | ||||
|         if (!text) | ||||
|             return; | ||||
|         let extents = text.get_character_extents(text.get_caret_offset(), 0); | ||||
|         let extents; | ||||
|         try { | ||||
|             extents = text.get_character_extents(text.get_caret_offset(), 0); | ||||
|         } catch(e) { | ||||
|             log('Failed to read extents of text caret: ' + e.message); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         [this._xCaret, this._yCaret] = [extents.x, extents.y]; | ||||
|         this._centerFromCaretPosition(); | ||||
|     }, | ||||
| @@ -763,6 +767,9 @@ const ZoomRegion = new Lang.Class({ | ||||
|         } else { | ||||
|             this._destroyActors(); | ||||
|         } | ||||
|  | ||||
|         this._syncCaretTracking(); | ||||
|         this._syncFocusTracking(); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
| @@ -822,10 +829,7 @@ const ZoomRegion = new Lang.Class({ | ||||
|      */ | ||||
|     setFocusTrackingMode: function(mode) { | ||||
|         this._focusTrackingMode = mode; | ||||
|         if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.NONE) | ||||
|             this._focusCaretTracker.deregisterFocusListener(); | ||||
|         else | ||||
|             this._focusCaretTracker.registerFocusListener(); | ||||
|         this._syncFocusTracking(); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
| @@ -834,10 +838,27 @@ const ZoomRegion = new Lang.Class({ | ||||
|      */ | ||||
|     setCaretTrackingMode: function(mode) { | ||||
|         this._caretTrackingMode = mode; | ||||
|         if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.NONE) | ||||
|             this._focusCaretTracker.deregisterCaretListener(); | ||||
|         this._syncCaretTracking(); | ||||
|     }, | ||||
|  | ||||
|     _syncFocusTracking: function() { | ||||
|         let enabled = this._focusTrackingMode != GDesktopEnums.MagnifierFocusTrackingMode.NONE && | ||||
|             this.isActive(); | ||||
|  | ||||
|         if (enabled) | ||||
|             this._focusCaretTracker.registerFocusListener(); | ||||
|         else | ||||
|             this._focusCaretTracker.deregisterFocusListener(); | ||||
|     }, | ||||
|  | ||||
|     _syncCaretTracking: function() { | ||||
|         let enabled = this._caretTrackingMode != GDesktopEnums.MagnifierCaretTrackingMode.NONE && | ||||
|             this.isActive(); | ||||
|  | ||||
|         if (enabled) | ||||
|             this._focusCaretTracker.registerCaretListener(); | ||||
|         else | ||||
|             this._focusCaretTracker.deregisterCaretListener(); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
| @@ -1178,13 +1199,17 @@ const ZoomRegion = new Lang.Class({ | ||||
|         // Add a background for when the magnified uiGroup is scrolled | ||||
|         // out of view (don't want to see desktop showing through). | ||||
|         this._background = new Clutter.Actor({ background_color: Main.DEFAULT_BACKGROUND_COLOR, | ||||
|                                                layout_manager: new Clutter.BinLayout(), | ||||
|                                                width: global.screen_width, | ||||
|                                                height: global.screen_height }); | ||||
|         let noiseTexture = (new Background.SystemBackground()).actor; | ||||
|         this._background.add_actor(noiseTexture); | ||||
|         mainGroup.add_actor(this._background); | ||||
|  | ||||
|         // Clone the group that contains all of UI on the screen.  This is the | ||||
|         // chrome, the windows, etc. | ||||
|         this._uiGroupClone = new Clutter.Clone({ source: Main.uiGroup }); | ||||
|         this._uiGroupClone = new Clutter.Clone({ source: Main.uiGroup, | ||||
|                                                  clip_to_allocation: true }); | ||||
|         mainGroup.add_actor(this._uiGroupClone); | ||||
|  | ||||
|         // Add either the given mouseSourceActor to the ZoomRegion, or a clone of | ||||
|   | ||||
| @@ -9,85 +9,89 @@ const ZOOM_SERVICE_PATH = '/org/gnome/Magnifier/ZoomRegion'; | ||||
|  | ||||
| // Subset of gnome-mag's Magnifier dbus interface -- to be expanded.  See: | ||||
| // http://git.gnome.org/browse/gnome-mag/tree/xml/...Magnifier.xml | ||||
| const MagnifierIface = <interface name="org.gnome.Magnifier"> | ||||
| <method name="setActive"> | ||||
|     <arg type="b" direction="in" /> | ||||
| </method> | ||||
| <method name="isActive"> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <method name="showCursor" /> | ||||
| <method name="hideCursor" /> | ||||
| <method name="createZoomRegion"> | ||||
|     <arg type="d" direction="in" /> | ||||
|     <arg type="d" direction="in" /> | ||||
|     <arg type="ai" direction="in" /> | ||||
|     <arg type="ai" direction="in" /> | ||||
|     <arg type="o" direction="out" /> | ||||
| </method> | ||||
| <method name="addZoomRegion"> | ||||
|     <arg type="o" direction="in" /> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <method name="getZoomRegions"> | ||||
|     <arg type="ao" direction="out" /> | ||||
| </method> | ||||
| <method name="clearAllZoomRegions" /> | ||||
| <method name="fullScreenCapable"> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <method name="setCrosswireSize"> | ||||
|     <arg type="i" direction="in" /> | ||||
| </method> | ||||
| <method name="getCrosswireSize"> | ||||
|     <arg type="i" direction="out" /> | ||||
| </method> | ||||
| <method name="setCrosswireLength"> | ||||
|     <arg type="i" direction="in" /> | ||||
| </method> | ||||
| <method name="getCrosswireLength"> | ||||
|     <arg type="i" direction="out" /> | ||||
| </method> | ||||
| <method name="setCrosswireClip"> | ||||
|     <arg type="b" direction="in" /> | ||||
| </method> | ||||
| <method name="getCrosswireClip"> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <method name="setCrosswireColor"> | ||||
|     <arg type="u" direction="in" /> | ||||
| </method> | ||||
| <method name="getCrosswireColor"> | ||||
|     <arg type="u" direction="out" /> | ||||
| </method> | ||||
| </interface>; | ||||
| const MagnifierIface = '<node> \ | ||||
| <interface name="org.gnome.Magnifier"> \ | ||||
| <method name="setActive"> \ | ||||
|     <arg type="b" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="isActive"> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="showCursor" /> \ | ||||
| <method name="hideCursor" /> \ | ||||
| <method name="createZoomRegion"> \ | ||||
|     <arg type="d" direction="in" /> \ | ||||
|     <arg type="d" direction="in" /> \ | ||||
|     <arg type="ai" direction="in" /> \ | ||||
|     <arg type="ai" direction="in" /> \ | ||||
|     <arg type="o" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="addZoomRegion"> \ | ||||
|     <arg type="o" direction="in" /> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="getZoomRegions"> \ | ||||
|     <arg type="ao" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="clearAllZoomRegions" /> \ | ||||
| <method name="fullScreenCapable"> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="setCrosswireSize"> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="getCrosswireSize"> \ | ||||
|     <arg type="i" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="setCrosswireLength"> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="getCrosswireLength"> \ | ||||
|     <arg type="i" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="setCrosswireClip"> \ | ||||
|     <arg type="b" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="getCrosswireClip"> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="setCrosswireColor"> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="getCrosswireColor"> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| // Subset of gnome-mag's ZoomRegion dbus interface -- to be expanded.  See: | ||||
| // http://git.gnome.org/browse/gnome-mag/tree/xml/...ZoomRegion.xml | ||||
| const ZoomRegionIface = <interface name="org.gnome.Magnifier.ZoomRegion"> | ||||
| <method name="setMagFactor"> | ||||
|     <arg type="d" direction="in" /> | ||||
|     <arg type="d" direction="in" /> | ||||
| </method> | ||||
| <method name="getMagFactor"> | ||||
|     <arg type="d" direction="out" /> | ||||
|     <arg type="d" direction="out" /> | ||||
| </method> | ||||
| <method name="setRoi"> | ||||
|     <arg type="ai" direction="in" /> | ||||
| </method> | ||||
| <method name="getRoi"> | ||||
|     <arg type="ai" direction="out" /> | ||||
| </method> | ||||
| <method name="shiftContentsTo"> | ||||
|     <arg type="i" direction="in" /> | ||||
|     <arg type="i" direction="in" /> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <method name="moveResize"> | ||||
|     <arg type="ai" direction="in" /> | ||||
| </method> | ||||
| </interface>; | ||||
| const ZoomRegionIface = '<node> \ | ||||
| <interface name="org.gnome.Magnifier.ZoomRegion"> \ | ||||
| <method name="setMagFactor"> \ | ||||
|     <arg type="d" direction="in" /> \ | ||||
|     <arg type="d" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="getMagFactor"> \ | ||||
|     <arg type="d" direction="out" /> \ | ||||
|     <arg type="d" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="setRoi"> \ | ||||
|     <arg type="ai" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="getRoi"> \ | ||||
|     <arg type="ai" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="shiftContentsTo"> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="moveResize"> \ | ||||
|     <arg type="ai" direction="in" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| // For making unique ZoomRegion DBus proxy object paths of the form: | ||||
| // '/org/gnome/Magnifier/ZoomRegion/zoomer0', | ||||
|   | ||||
| @@ -112,11 +112,6 @@ function start() { | ||||
|     Gio.DesktopAppInfo.set_desktop_env('GNOME'); | ||||
|  | ||||
|     sessionMode = new SessionMode.SessionMode(); | ||||
|     sessionMode.connect('sessions-loaded', _sessionsLoaded); | ||||
|     sessionMode.init(); | ||||
| } | ||||
|  | ||||
| function _sessionsLoaded() { | ||||
|     sessionMode.connect('updated', _sessionUpdated); | ||||
|     _initializePrefs(); | ||||
|     _initializeUI(); | ||||
| @@ -614,7 +609,7 @@ function queueDeferredWork(workId) { | ||||
|         _deferredTimeoutId = Mainloop.timeout_add_seconds(DEFERRED_TIMEOUT_SECONDS, function () { | ||||
|             _runAllDeferredWork(); | ||||
|             _deferredTimeoutId = 0; | ||||
|             return false; | ||||
|             return GLib.SOURCE_REMOVE; | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -19,7 +19,6 @@ const BoxPointer = imports.ui.boxpointer; | ||||
| const CtrlAltTab = imports.ui.ctrlAltTab; | ||||
| const GnomeSession = imports.misc.gnomeSession; | ||||
| const GrabHelper = imports.ui.grabHelper; | ||||
| const Hash = imports.misc.hash; | ||||
| const Lightbox = imports.ui.lightbox; | ||||
| const Main = imports.ui.main; | ||||
| const PointerWatcher = imports.ui.pointerWatcher; | ||||
| @@ -76,7 +75,7 @@ const Urgency = { | ||||
|     NORMAL: 1, | ||||
|     HIGH: 2, | ||||
|     CRITICAL: 3 | ||||
| } | ||||
| }; | ||||
|  | ||||
| function _fixMarkup(text, allowMarkup) { | ||||
|     if (allowMarkup) { | ||||
| @@ -187,7 +186,7 @@ const URLHighlighter = new Lang.Class({ | ||||
|             // The MessageTray doesn't actually hide us, so | ||||
|             // we need to check for paint opacities as well. | ||||
|             if (!actor.visible || actor.get_paint_opacity() == 0) | ||||
|                 return false; | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|             // Keep Notification.actor from seeing this and taking | ||||
|             // a pointer grab, which would block our button-release-event | ||||
| @@ -196,7 +195,7 @@ const URLHighlighter = new Lang.Class({ | ||||
|         })); | ||||
|         this.actor.connect('button-release-event', Lang.bind(this, function (actor, event) { | ||||
|             if (!actor.visible || actor.get_paint_opacity() == 0) | ||||
|                 return false; | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|             let urlId = this._findUrlAtPos(event); | ||||
|             if (urlId != -1) { | ||||
| @@ -204,14 +203,14 @@ const URLHighlighter = new Lang.Class({ | ||||
|                 if (url.indexOf(':') == -1) | ||||
|                     url = 'http://' + url; | ||||
|  | ||||
|                 Gio.app_info_launch_default_for_uri(url, global.create_app_launch_context()); | ||||
|                 return true; | ||||
|                 Gio.app_info_launch_default_for_uri(url, global.create_app_launch_context(0, -1)); | ||||
|                 return Clutter.EVENT_STOP; | ||||
|             } | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|         })); | ||||
|         this.actor.connect('motion-event', Lang.bind(this, function(actor, event) { | ||||
|             if (!actor.visible || actor.get_paint_opacity() == 0) | ||||
|                 return false; | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|             let urlId = this._findUrlAtPos(event); | ||||
|             if (urlId != -1 && !this._cursorChanged) { | ||||
| @@ -221,16 +220,17 @@ const URLHighlighter = new Lang.Class({ | ||||
|                 global.screen.set_cursor(Meta.Cursor.DEFAULT); | ||||
|                 this._cursorChanged = false; | ||||
|             } | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|         })); | ||||
|         this.actor.connect('leave-event', Lang.bind(this, function() { | ||||
|             if (!this.actor.visible || this.actor.get_paint_opacity() == 0) | ||||
|                 return; | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|             if (this._cursorChanged) { | ||||
|                 this._cursorChanged = false; | ||||
|                 global.screen.set_cursor(Meta.Cursor.DEFAULT); | ||||
|             } | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
| @@ -280,10 +280,6 @@ const URLHighlighter = new Lang.Class({ | ||||
|     } | ||||
| }); | ||||
|  | ||||
| function strHasSuffix(string, suffix) { | ||||
|     return string.substr(-suffix.length) == suffix; | ||||
| } | ||||
|  | ||||
| // NotificationPolicy: | ||||
| // An object that holds all bits of configurable policy related to a notification | ||||
| // source, such as whether to play sound or honour the critical bit. | ||||
| @@ -503,7 +499,6 @@ const Notification = new Lang.Class({ | ||||
|         this.focused = false; | ||||
|         this.acknowledged = false; | ||||
|         this._destroyed = false; | ||||
|         this._useActionIcons = false; | ||||
|         this._customContent = false; | ||||
|         this.bannerBodyText = null; | ||||
|         this.bannerBodyMarkup = false; | ||||
| @@ -836,17 +831,7 @@ const Notification = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     // addButton: | ||||
|     // @id: the action ID | ||||
|     // @label: the label for the action's button | ||||
|     // | ||||
|     // Adds a button with the given @label to the notification. All | ||||
|     // action buttons will appear in a single row at the bottom of | ||||
|     // the notification. | ||||
|     // | ||||
|     // If the button is clicked, the notification will emit the | ||||
|     // %action-invoked signal with @id as a parameter | ||||
|     addButton: function(id, label) { | ||||
|     addButton: function(button, callback) { | ||||
|         if (!this._buttonBox) { | ||||
|             let box = new St.BoxLayout({ style_class: 'notification-actions' }); | ||||
|             this.setActionArea(box, { x_expand: false, | ||||
| @@ -858,43 +843,37 @@ const Notification = new Lang.Class({ | ||||
|             global.focus_manager.add_group(this._buttonBox); | ||||
|         } | ||||
|  | ||||
|         let button = new St.Button({ can_focus: true }); | ||||
|         button._actionId = id; | ||||
|  | ||||
|         let iconName = strHasSuffix(id, '-symbolic') ? id : id + '-symbolic'; | ||||
|         if (this._useActionIcons && Gtk.IconTheme.get_default().has_icon(iconName)) { | ||||
|             button.add_style_class_name('notification-icon-button'); | ||||
|             button.child = new St.Icon({ icon_name: iconName }); | ||||
|         } else { | ||||
|             button.add_style_class_name('notification-button'); | ||||
|             button.label = label; | ||||
|         } | ||||
|  | ||||
|         this._buttonBox.add(button); | ||||
|         button.connect('clicked', Lang.bind(this, this._onActionInvoked, id)); | ||||
|         button.connect('clicked', Lang.bind(this, function() { | ||||
|             callback(); | ||||
|  | ||||
|             if (!this.resident) { | ||||
|                 // We don't hide a resident notification when the user invokes one of its actions, | ||||
|                 // because it is common for such notifications to update themselves with new | ||||
|                 // information based on the action. We'd like to display the updated information | ||||
|                 // in place, rather than pop-up a new notification. | ||||
|                 this.emit('done-displaying'); | ||||
|                 this.destroy(); | ||||
|             } | ||||
|         })); | ||||
|  | ||||
|         this.updated(); | ||||
|         return button; | ||||
|     }, | ||||
|  | ||||
|     // setButtonSensitive: | ||||
|     // @id: the action ID | ||||
|     // @sensitive: whether the button should be sensitive | ||||
|     // addAction: | ||||
|     // @label: the label for the action's button | ||||
|     // @callback: the callback for the action | ||||
|     // | ||||
|     // If the notification contains a button with action ID @id, | ||||
|     // its sensitivity will be set to @sensitive. Insensitive | ||||
|     // buttons cannot be clicked. | ||||
|     setButtonSensitive: function(id, sensitive) { | ||||
|         if (!this._buttonBox) | ||||
|             return; | ||||
|     // Adds a button with the given @label to the notification. All | ||||
|     // action buttons will appear in a single row at the bottom of | ||||
|     // the notification. | ||||
|     addAction: function(label, callback) { | ||||
|         let button = new St.Button({ style_class: 'notification-button', | ||||
|                                      label: label, | ||||
|                                      can_focus: true }); | ||||
|  | ||||
|         let button = this._buttonBox.get_children().filter(function(b) { | ||||
|             return b._actionId == id; | ||||
|         })[0]; | ||||
|  | ||||
|         if (!button || button.reactive == sensitive) | ||||
|             return; | ||||
|  | ||||
|         button.reactive = sensitive; | ||||
|         return this.addButton(button, callback); | ||||
|     }, | ||||
|  | ||||
|     setUrgency: function(urgency) { | ||||
| @@ -913,10 +892,6 @@ const Notification = new Lang.Class({ | ||||
|         this.forFeedback = forFeedback; | ||||
|     }, | ||||
|  | ||||
|     setUseActionIcons: function(useIcons) { | ||||
|         this._useActionIcons = useIcons; | ||||
|     }, | ||||
|  | ||||
|     _styleChanged: function() { | ||||
|         this._spacing = this._table.get_theme_node().get_length('spacing-columns'); | ||||
|     }, | ||||
| @@ -1026,6 +1001,9 @@ const Notification = new Lang.Class({ | ||||
|             Meta.later_add(Meta.LaterType.BEFORE_REDRAW, | ||||
|                            Lang.bind(this, | ||||
|                                      function() { | ||||
|                                          if (this._destroyed) | ||||
|                                              return false; | ||||
|  | ||||
|                                         if (this._canExpandContent()) { | ||||
|                                             this._addBannerBody(); | ||||
|                                             this._table.add_style_class_name('multi-line-notification'); | ||||
| @@ -1141,18 +1119,6 @@ const Notification = new Lang.Class({ | ||||
|         this.actor.add_style_class_name('notification-unexpanded'); | ||||
|     }, | ||||
|  | ||||
|     _onActionInvoked: function(actor, mouseButtonClicked, id) { | ||||
|         this.emit('action-invoked', id); | ||||
|         if (!this.resident) { | ||||
|             // We don't hide a resident notification when the user invokes one of its actions, | ||||
|             // because it is common for such notifications to update themselves with new | ||||
|             // information based on the action. We'd like to display the updated information | ||||
|             // in place, rather than pop-up a new notification. | ||||
|             this.emit('done-displaying'); | ||||
|             this.destroy(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _onClicked: function() { | ||||
|         this.emit('clicked'); | ||||
|         // We hide all types of notifications once the user clicks on them because the common | ||||
| @@ -1196,32 +1162,12 @@ const SourceActor = new Lang.Class({ | ||||
|         })); | ||||
|         this._actorDestroyed = false; | ||||
|  | ||||
|         this._counterLabel = new St.Label( {x_align: Clutter.ActorAlign.CENTER, | ||||
|                                             x_expand: true, | ||||
|                                             y_align: Clutter.ActorAlign.CENTER, | ||||
|                                             y_expand: true }); | ||||
|  | ||||
|         this._counterBin = new St.Bin({ style_class: 'summary-source-counter', | ||||
|                                         child: this._counterLabel, | ||||
|                                         layout_manager: new Clutter.BinLayout() }); | ||||
|         this._counterBin.hide(); | ||||
|  | ||||
|         this._counterBin.connect('style-changed', Lang.bind(this, function() { | ||||
|             let themeNode = this._counterBin.get_theme_node(); | ||||
|             this._counterBin.translation_x = themeNode.get_length('-shell-counter-overlap-x'); | ||||
|             this._counterBin.translation_y = themeNode.get_length('-shell-counter-overlap-y'); | ||||
|         })); | ||||
|  | ||||
|         this._iconBin = new St.Bin({ width: size, | ||||
|                                      height: size, | ||||
|                                      x_fill: true, | ||||
|                                      y_fill: true }); | ||||
|         let scale_factor = St.ThemeContext.get_for_stage(global.stage).scale_factor; | ||||
|         this._iconBin = new St.Bin({ x_fill: true, | ||||
|                                      height: size * scale_factor, | ||||
|                                      width: size * scale_factor }); | ||||
|  | ||||
|         this.actor.add_actor(this._iconBin); | ||||
|         this.actor.add_actor(this._counterBin); | ||||
|  | ||||
|         this._source.connect('count-updated', Lang.bind(this, this._updateCount)); | ||||
|         this._updateCount(); | ||||
|  | ||||
|         this._source.connect('icon-updated', Lang.bind(this, this._updateIcon)); | ||||
|         this._updateIcon(); | ||||
| @@ -1245,6 +1191,48 @@ const SourceActor = new Lang.Class({ | ||||
|     _allocate: function(actor, box, flags) { | ||||
|         // the iconBin should fill our entire box | ||||
|         this._iconBin.allocate(box, flags); | ||||
|     }, | ||||
|  | ||||
|     _updateIcon: function() { | ||||
|         if (this._actorDestroyed) | ||||
|             return; | ||||
|  | ||||
|         if (!this._iconSet) | ||||
|             this._iconBin.child = this._source.createIcon(this._size); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const SourceActorWithLabel = new Lang.Class({ | ||||
|     Name: 'SourceActorWithLabel', | ||||
|     Extends: SourceActor, | ||||
|  | ||||
|     _init: function(source, size) { | ||||
|         this.parent(source, size); | ||||
|  | ||||
|         this._counterLabel = new St.Label({ x_align: Clutter.ActorAlign.CENTER, | ||||
|                                             x_expand: true, | ||||
|                                             y_align: Clutter.ActorAlign.CENTER, | ||||
|                                             y_expand: true }); | ||||
|  | ||||
|         this._counterBin = new St.Bin({ style_class: 'summary-source-counter', | ||||
|                                         child: this._counterLabel, | ||||
|                                         layout_manager: new Clutter.BinLayout() }); | ||||
|         this._counterBin.hide(); | ||||
|  | ||||
|         this._counterBin.connect('style-changed', Lang.bind(this, function() { | ||||
|             let themeNode = this._counterBin.get_theme_node(); | ||||
|             this._counterBin.translation_x = themeNode.get_length('-shell-counter-overlap-x'); | ||||
|             this._counterBin.translation_y = themeNode.get_length('-shell-counter-overlap-y'); | ||||
|         })); | ||||
|  | ||||
|         this.actor.add_actor(this._counterBin); | ||||
|  | ||||
|         this._source.connect('count-updated', Lang.bind(this, this._updateCount)); | ||||
|         this._updateCount(); | ||||
|     }, | ||||
|  | ||||
|     _allocate: function(actor, box, flags) { | ||||
|         this.parent(actor, box, flags); | ||||
|  | ||||
|         let childBox = new Clutter.ActorBox(); | ||||
|  | ||||
| @@ -1267,14 +1255,6 @@ const SourceActor = new Lang.Class({ | ||||
|         this._counterBin.allocate(childBox, flags); | ||||
|     }, | ||||
|  | ||||
|     _updateIcon: function() { | ||||
|         if (this._actorDestroyed) | ||||
|             return; | ||||
|  | ||||
|         if (!this._iconSet) | ||||
|             this._iconBin.child = this._source.createIcon(this._size); | ||||
|     }, | ||||
|  | ||||
|     _updateCount: function() { | ||||
|         if (this._actorDestroyed) | ||||
|             return; | ||||
| @@ -1300,7 +1280,6 @@ const Source = new Lang.Class({ | ||||
|         this.title = title; | ||||
|         this.iconName = iconName; | ||||
|  | ||||
|         this.isTransient = false; | ||||
|         this.isChat = false; | ||||
|         this.isMuted = false; | ||||
|         this.keepTrayOnSummaryClick = false; | ||||
| @@ -1360,10 +1339,6 @@ const Source = new Lang.Class({ | ||||
|         return rightClickMenu; | ||||
|     }, | ||||
|  | ||||
|     setTransient: function(isTransient) { | ||||
|         this.isTransient = isTransient; | ||||
|     }, | ||||
|  | ||||
|     setTitle: function(newTitle) { | ||||
|         this.title = newTitle; | ||||
|         this.emit('title-changed'); | ||||
| @@ -1392,7 +1367,7 @@ const Source = new Lang.Class({ | ||||
|         if (this._mainIcon) | ||||
|             return; | ||||
|  | ||||
|         this._mainIcon = new SourceActor(this, this.SOURCE_ICON_SIZE); | ||||
|         this._mainIcon = new SourceActorWithLabel(this, this.SOURCE_ICON_SIZE); | ||||
|     }, | ||||
|  | ||||
|     // Unlike createIcon, this always returns the same actor; | ||||
| @@ -1418,9 +1393,7 @@ const Source = new Lang.Class({ | ||||
|         if (this.notifications.indexOf(notification) >= 0) | ||||
|             return; | ||||
|  | ||||
|         notification.connect('clicked', Lang.bind(this, this.open)); | ||||
|         notification.connect('destroy', Lang.bind(this, this._onNotificationDestroy)); | ||||
|  | ||||
|         this.notifications.push(notification); | ||||
|         this.emit('notification-added', notification); | ||||
|  | ||||
| @@ -1567,9 +1540,9 @@ const SummaryItem = new Lang.Class({ | ||||
|     _onKeyPress: function(actor, event) { | ||||
|         if (event.get_key_symbol() == Clutter.KEY_Up) { | ||||
|             actor.emit('clicked', 1); | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } | ||||
|         return false; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     prepareNotificationStackForShowing: function() { | ||||
| @@ -1664,7 +1637,7 @@ const MessageTrayMenu = new Lang.Class({ | ||||
|         this._clearItem = this.addAction(_("Clear Messages"), function() { | ||||
|             let toDestroy = tray.getSources().filter(function(source) { | ||||
|                 return source.isClearable; | ||||
|             }) | ||||
|             }); | ||||
|  | ||||
|             toDestroy.forEach(function(source) { | ||||
|                 source.destroy(); | ||||
| @@ -1827,6 +1800,7 @@ const MessageTray = new Lang.Class({ | ||||
|             this._setClickedSummaryItem(null); | ||||
|             this._updateState(); | ||||
|             actor.grab_key_focus(); | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|         })); | ||||
|         global.focus_manager.add_group(this.actor); | ||||
|         this._summary = new St.BoxLayout({ style_class: 'message-tray-summary', | ||||
| @@ -1941,7 +1915,7 @@ const MessageTray = new Lang.Class({ | ||||
|                               Shell.KeyBindingMode.OVERVIEW, | ||||
|                               Lang.bind(this, this._expandActiveNotification)); | ||||
|  | ||||
|         this._sources = new Hash.Map(); | ||||
|         this._sources = new Map(); | ||||
|         this._chatSummaryItemsCount = 0; | ||||
|  | ||||
|         this._trayDwellTimeoutId = 0; | ||||
| @@ -1978,7 +1952,7 @@ const MessageTray = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _updateNoMessagesLabel: function() { | ||||
|         this._noMessages.visible = this._sources.size() == 0; | ||||
|         this._noMessages.visible = this._sources.size == 0; | ||||
|     }, | ||||
|  | ||||
|     _sessionUpdated: function() { | ||||
| @@ -2032,32 +2006,37 @@ const MessageTray = new Lang.Class({ | ||||
|         this._trayDwellTimeoutId = 0; | ||||
|  | ||||
|         if (Main.layoutManager.bottomMonitor.inFullscreen) | ||||
|             return false; | ||||
|             return GLib.SOURCE_REMOVE; | ||||
|  | ||||
|         // We don't want to open the tray when a modal dialog | ||||
|         // is up, so we check the modal count for that. When we are in the | ||||
|         // overview we have to take the overview's modal push into account | ||||
|         if (Main.modalCount > (Main.overview.visible ? 1 : 0)) | ||||
|             return false; | ||||
|             return GLib.SOURCE_REMOVE; | ||||
|  | ||||
|         // If the user interacted with the focus window since we started the tray | ||||
|         // dwell (by clicking or typing), don't activate the message tray | ||||
|         let focusWindow = global.display.focus_window; | ||||
|         let currentUserTime = focusWindow ? focusWindow.user_time : 0; | ||||
|         if (currentUserTime != this._trayDwellUserTime) | ||||
|             return false; | ||||
|             return GLib.SOURCE_REMOVE; | ||||
|  | ||||
|         this.openTray(); | ||||
|         return false; | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|     }, | ||||
|  | ||||
|     _onNotificationKeyRelease: function(actor, event) { | ||||
|         if (event.get_key_symbol() == Clutter.KEY_Escape && event.get_state() == 0) { | ||||
|             this._closeNotification(); | ||||
|             return true; | ||||
|             this._expireNotification(); | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _expireNotification: function() { | ||||
|         this._notificationExpired = true; | ||||
|         this._updateState(); | ||||
|     }, | ||||
|  | ||||
|     _closeNotification: function() { | ||||
| @@ -2141,7 +2120,8 @@ const MessageTray = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _removeSource: function(source) { | ||||
|         let [, obj] = this._sources.delete(source); | ||||
|         let obj = this._sources.get(source); | ||||
|         this._sources.delete(source); | ||||
|         let summaryItem = obj.summaryItem; | ||||
|  | ||||
|         if (source.isChat) | ||||
| @@ -2162,7 +2142,7 @@ const MessageTray = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     getSources: function() { | ||||
|         return this._sources.keys(); | ||||
|         return [k for (k of this._sources.keys())]; | ||||
|     }, | ||||
|  | ||||
|     _onSourceEnableChanged: function(policy, source) { | ||||
| @@ -2212,7 +2192,10 @@ const MessageTray = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     toggleAndNavigate: function() { | ||||
|         if (this.toggle()) | ||||
|         if (!this.toggle()) | ||||
|             return; | ||||
|  | ||||
|         if (this._traySummoned) | ||||
|             this._summary.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); | ||||
|     }, | ||||
|  | ||||
| @@ -2375,7 +2358,7 @@ const MessageTray = new Lang.Class({ | ||||
|             this._updateNotificationTimeout(0); | ||||
|             this._updateState(); | ||||
|         } | ||||
|         return false; | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|     }, | ||||
|  | ||||
|     _escapeTray: function() { | ||||
| @@ -2392,6 +2375,13 @@ const MessageTray = new Lang.Class({ | ||||
|     // _updateState() figures out what (if anything) needs to be done | ||||
|     // at the present time. | ||||
|     _updateState: function() { | ||||
|         // If our state changes caused _updateState to be called, | ||||
|         // just exit now to prevent reentrancy issues. | ||||
|         if (this._updatingState) | ||||
|             return; | ||||
|  | ||||
|         this._updatingState = true; | ||||
|  | ||||
|         // Filter out acknowledged notifications. | ||||
|         this._notificationQueue = this._notificationQueue.filter(function(n) { | ||||
|             return !n.acknowledged; | ||||
| @@ -2409,20 +2399,19 @@ const MessageTray = new Lang.Class({ | ||||
|                     this._showNotification(); | ||||
|             } | ||||
|         } else if (this._notificationState == State.SHOWN) { | ||||
|             let pinned = this._pointerInNotification && !this._notificationRemoved; | ||||
|             let expired = (this._userActiveWhileNotificationShown && | ||||
|                            this._notificationTimeoutId == 0 && | ||||
|                            !(this._notification.urgency == Urgency.CRITICAL) && | ||||
|                            this._notification.urgency != Urgency.CRITICAL && | ||||
|                            !this._notification.focused && | ||||
|                            !this._pointerInNotification); | ||||
|                            !this._pointerInNotification) || this._notificationExpired; | ||||
|             let mustClose = (this._notificationRemoved || !hasNotifications || expired || this._traySummoned); | ||||
|  | ||||
|             if (mustClose) { | ||||
|                 let animate = hasNotifications && !this._notificationRemoved; | ||||
|                 this._hideNotification(animate); | ||||
|             } else if (pinned && !this._notification.expanded) { | ||||
|             } else if (this._pointerInNotification && !this._notification.expanded) { | ||||
|                 this._expandNotification(false); | ||||
|             } else if (pinned) { | ||||
|             } else if (this._pointerInNotification) { | ||||
|                 this._ensureNotificationFocused(); | ||||
|             } | ||||
|         } | ||||
| @@ -2469,11 +2458,16 @@ const MessageTray = new Lang.Class({ | ||||
|                                      this._desktopCloneState == State.SHOWN); | ||||
|         let desktopCloneShouldBeVisible = (trayShouldBeVisible); | ||||
|  | ||||
|         if (!desktopCloneIsVisible && desktopCloneShouldBeVisible) { | ||||
|         if (!desktopCloneIsVisible && desktopCloneShouldBeVisible) | ||||
|             this._showDesktopClone(); | ||||
|         } else if (desktopCloneIsVisible && !desktopCloneShouldBeVisible) { | ||||
|         else if (desktopCloneIsVisible && !desktopCloneShouldBeVisible) | ||||
|             this._hideDesktopClone(); | ||||
|         } | ||||
|  | ||||
|         this._updatingState = false; | ||||
|  | ||||
|         // Clean transient variables that are used to communicate actions | ||||
|         // to updateState() | ||||
|         this._notificationExpired = false; | ||||
|     }, | ||||
|  | ||||
|     _tween: function(actor, statevar, value, params) { | ||||
| @@ -2701,7 +2695,7 @@ const MessageTray = new Lang.Class({ | ||||
|  | ||||
|         this._lastSeenMouseX = x; | ||||
|         this._lastSeenMouseY = y; | ||||
|         return false; | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|     }, | ||||
|  | ||||
|     _hideNotification: function(animate) { | ||||
| @@ -2792,12 +2786,17 @@ const MessageTray = new Lang.Class({ | ||||
|         } else if (this._notification.y != expandedY) { | ||||
|             // Tween also opacity here, to override a possible tween that's | ||||
|             // currently hiding the notification. | ||||
|             this._tween(this._notificationWidget, '_notificationState', State.SHOWN, | ||||
|                         { y: expandedY, | ||||
|                           opacity: 255, | ||||
|                           time: ANIMATION_TIME, | ||||
|                           transition: 'easeOutQuad' | ||||
|                         }); | ||||
|             Tweener.addTween(this._notificationWidget, | ||||
|                              { y: expandedY, | ||||
|                                opacity: 255, | ||||
|                                time: ANIMATION_TIME, | ||||
|                                transition: 'easeOutQuad', | ||||
|                                // HACK: Drive the state machine here better, | ||||
|                                // instead of overwriting tweens | ||||
|                                onComplete: Lang.bind(this, function() { | ||||
|                                    this._notificationState = State.SHOWN; | ||||
|                                }), | ||||
|                              }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -2838,13 +2837,13 @@ const MessageTray = new Lang.Class({ | ||||
|                                                                                   Lang.bind(this, this._onSourceDoneDisplayingContent)); | ||||
|  | ||||
|         this._summaryBoxPointer.bin.child = child; | ||||
|         this._grabHelper.grab({ actor: this._summaryBoxPointer.bin.child, | ||||
|                                 onUngrab: Lang.bind(this, this._onSummaryBoxPointerUngrabbed) }); | ||||
|  | ||||
|         this._summaryBoxPointer.actor.opacity = 0; | ||||
|         this._summaryBoxPointer.actor.show(); | ||||
|         this._adjustSummaryBoxPointerPosition(); | ||||
|  | ||||
|         this._grabHelper.grab({ actor: this._summaryBoxPointer.bin.child, | ||||
|                                 onUngrab: Lang.bind(this, this._onSummaryBoxPointerUngrabbed) }); | ||||
|  | ||||
|         this._summaryBoxPointerState = State.SHOWING; | ||||
|         this._summaryBoxPointer.show(BoxPointer.PopupAnimation.FULL, Lang.bind(this, function() { | ||||
|             this._summaryBoxPointerState = State.SHOWN; | ||||
| @@ -2899,13 +2898,13 @@ const MessageTray = new Lang.Class({ | ||||
|         case Clutter.KEY_Escape: | ||||
|             this._setClickedSummaryItem(null); | ||||
|             this._updateState(); | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         case Clutter.KEY_Delete: | ||||
|             this._clickedSummaryItem.source.destroy(); | ||||
|             this._escapeTray(); | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } | ||||
|         return false; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onSummaryBoxPointerUngrabbed: function() { | ||||
| @@ -2943,8 +2942,6 @@ const MessageTray = new Lang.Class({ | ||||
|             this._summaryBoxPointerItem.doneShowingNotificationStack(); | ||||
|             this._summaryBoxPointerItem = null; | ||||
|  | ||||
|             if (source.isTransient && !this._reNotifyAfterHideNotification) | ||||
|                 source.destroy(NotificationDestroyedReason.EXPIRED); | ||||
|             if (this._reNotifyAfterHideNotification) { | ||||
|                 this._onNotify(this._reNotifyAfterHideNotification.source, this._reNotifyAfterHideNotification); | ||||
|                 this._reNotifyAfterHideNotification = null; | ||||
| @@ -2963,7 +2960,6 @@ const SystemNotificationSource = new Lang.Class({ | ||||
|  | ||||
|     _init: function() { | ||||
|         this.parent(_("System Information"), 'dialog-information-symbolic'); | ||||
|         this.setTransient(true); | ||||
|     }, | ||||
|  | ||||
|     open: function() { | ||||
|   | ||||
| @@ -41,7 +41,6 @@ const ModalDialog = new Lang.Class({ | ||||
|     _init: function(params) { | ||||
|         params = Params.parse(params, { shellReactive: false, | ||||
|                                         styleClass: null, | ||||
|                                         parentActor: Main.uiGroup, | ||||
|                                         keybindingMode: Shell.KeyBindingMode.SYSTEM_MODAL, | ||||
|                                         shouldFadeIn: true, | ||||
|                                         destroyOnClose: true }); | ||||
| @@ -57,7 +56,7 @@ const ModalDialog = new Lang.Class({ | ||||
|                                       x: 0, | ||||
|                                       y: 0, | ||||
|                                       accessible_role: Atk.Role.DIALOG }); | ||||
|         params.parentActor.add_actor(this._group); | ||||
|         Main.layoutManager.modalDialogGroup.add_actor(this._group); | ||||
|  | ||||
|         let constraint = new Clutter.BindConstraint({ source: global.stage, | ||||
|                                                       coordinate: Clutter.BindCoordinate.ALL }); | ||||
| @@ -89,7 +88,8 @@ const ModalDialog = new Lang.Class({ | ||||
|  | ||||
|         if (!this._shellReactive) { | ||||
|             this._lightbox = new Lightbox.Lightbox(this._group, | ||||
|                                                    { inhibitEvents: true }); | ||||
|                                                    { inhibitEvents: true, | ||||
|                                                      radialEffect: true }); | ||||
|             this._lightbox.highlight(this._backgroundBin); | ||||
|  | ||||
|             this._eventBlocker = new Clutter.Actor({ reactive: true }); | ||||
| @@ -229,6 +229,7 @@ const ModalDialog = new Lang.Class({ | ||||
|  | ||||
|     _onKeyPressEvent: function(object, event) { | ||||
|         this._pressedKey = event.get_key_symbol(); | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onKeyReleaseEvent: function(object, event) { | ||||
| @@ -237,21 +238,21 @@ const ModalDialog = new Lang.Class({ | ||||
|  | ||||
|         let symbol = event.get_key_symbol(); | ||||
|         if (symbol != pressedKey) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         let buttonInfo = this._buttonKeys[symbol]; | ||||
|         if (!buttonInfo) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         let button = buttonInfo['button']; | ||||
|         let action = buttonInfo['action']; | ||||
|  | ||||
|         if (action && button.reactive) { | ||||
|             action(); | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onGroupDestroy: function() { | ||||
|   | ||||
| @@ -4,6 +4,7 @@ const Clutter = imports.gi.Clutter; | ||||
| const GdkPixbuf = imports.gi.GdkPixbuf; | ||||
| 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 Mainloop = imports.mainloop; | ||||
| @@ -15,54 +16,56 @@ const MessageTray = imports.ui.messageTray; | ||||
| const Params = imports.misc.params; | ||||
| const Util = imports.misc.util; | ||||
|  | ||||
| let nextNotificationId = 1; | ||||
|  | ||||
| // Should really be defined in Gio.js | ||||
| const BusIface = <interface name="org.freedesktop.DBus"> | ||||
| <method name="GetConnectionUnixProcessID"> | ||||
|     <arg type="s" direction="in" /> | ||||
|     <arg type="u" direction="out" /> | ||||
| </method> | ||||
| </interface>; | ||||
| const BusIface = '<node> \ | ||||
| <interface name="org.freedesktop.DBus"> \ | ||||
| <method name="GetConnectionUnixProcessID"> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| var BusProxy = Gio.DBusProxy.makeProxyWrapper(BusIface); | ||||
| function Bus() { | ||||
|     return new BusProxy(Gio.DBus.session, 'org.freedesktop.DBus', '/org/freedesktop/DBus'); | ||||
| } | ||||
|  | ||||
| const NotificationDaemonIface = <interface name="org.freedesktop.Notifications"> | ||||
| <method name="Notify"> | ||||
|     <arg type="s" direction="in"/> | ||||
|     <arg type="u" direction="in"/> | ||||
|     <arg type="s" direction="in"/> | ||||
|     <arg type="s" direction="in"/> | ||||
|     <arg type="s" direction="in"/> | ||||
|     <arg type="as" direction="in"/> | ||||
|     <arg type="a{sv}" direction="in"/> | ||||
|     <arg type="i" direction="in"/> | ||||
|     <arg type="u" direction="out"/> | ||||
| </method> | ||||
| <method name="CloseNotification"> | ||||
|     <arg type="u" direction="in"/> | ||||
| </method> | ||||
| <method name="GetCapabilities"> | ||||
|     <arg type="as" direction="out"/> | ||||
| </method> | ||||
| <method name="GetServerInformation"> | ||||
|     <arg type="s" direction="out"/> | ||||
|     <arg type="s" direction="out"/> | ||||
|     <arg type="s" direction="out"/> | ||||
|     <arg type="s" direction="out"/> | ||||
| </method> | ||||
| <signal name="NotificationClosed"> | ||||
|     <arg type="u"/> | ||||
|     <arg type="u"/> | ||||
| </signal> | ||||
| <signal name="ActionInvoked"> | ||||
|     <arg type="u"/> | ||||
|     <arg type="s"/> | ||||
| </signal> | ||||
| </interface>; | ||||
| const FdoNotificationsIface = '<node> \ | ||||
| <interface name="org.freedesktop.Notifications"> \ | ||||
| <method name="Notify"> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="u" direction="in"/> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="as" direction="in"/> \ | ||||
|     <arg type="a{sv}" direction="in"/> \ | ||||
|     <arg type="i" direction="in"/> \ | ||||
|     <arg type="u" direction="out"/> \ | ||||
| </method> \ | ||||
| <method name="CloseNotification"> \ | ||||
|     <arg type="u" direction="in"/> \ | ||||
| </method> \ | ||||
| <method name="GetCapabilities"> \ | ||||
|     <arg type="as" direction="out"/> \ | ||||
| </method> \ | ||||
| <method name="GetServerInformation"> \ | ||||
|     <arg type="s" direction="out"/> \ | ||||
|     <arg type="s" direction="out"/> \ | ||||
|     <arg type="s" direction="out"/> \ | ||||
|     <arg type="s" direction="out"/> \ | ||||
| </method> \ | ||||
| <signal name="NotificationClosed"> \ | ||||
|     <arg type="u"/> \ | ||||
|     <arg type="u"/> \ | ||||
| </signal> \ | ||||
| <signal name="ActionInvoked"> \ | ||||
|     <arg type="u"/> \ | ||||
|     <arg type="s"/> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const NotificationClosedReason = { | ||||
|     EXPIRED: 1, | ||||
| @@ -103,11 +106,11 @@ const STANDARD_TRAY_ICON_IMPLEMENTATIONS = { | ||||
|     'ibus-ui-gtk': 'keyboard' | ||||
| }; | ||||
|  | ||||
| const NotificationDaemon = new Lang.Class({ | ||||
|     Name: 'NotificationDaemon', | ||||
| const FdoNotificationDaemon = new Lang.Class({ | ||||
|     Name: 'FdoNotificationDaemon', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(NotificationDaemonIface, this); | ||||
|         this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(FdoNotificationsIface, this); | ||||
|         this._dbusImpl.export(Gio.DBus.session, '/org/freedesktop/Notifications'); | ||||
|  | ||||
|         this._sources = []; | ||||
| @@ -115,6 +118,8 @@ const NotificationDaemon = new Lang.Class({ | ||||
|         this._notifications = {}; | ||||
|         this._busProxy = new Bus(); | ||||
|  | ||||
|         this._nextNotificationId = 1; | ||||
|  | ||||
|         this._trayManager = new Shell.TrayManager(); | ||||
|         this._trayIconAddedId = this._trayManager.connect('tray-icon-added', Lang.bind(this, this._onTrayIconAdded)); | ||||
|         this._trayIconRemovedId = this._trayManager.connect('tray-icon-removed', Lang.bind(this, this._onTrayIconRemoved)); | ||||
| @@ -176,12 +181,10 @@ const NotificationDaemon = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     // Returns the source associated with ndata.notification if it is set. | ||||
|     // Otherwise, returns the source associated with the title and pid if | ||||
|     // such source is stored in this._sources and the notification is not | ||||
|     // transient. If the existing or requested source is associated with | ||||
|     // a tray icon and passed in pid matches a pid of an existing source, | ||||
|     // the title match is ignored to enable representing a tray icon and | ||||
|     // notifications from the same application with a single source. | ||||
|     // If the existing or requested source is associated with a tray icon | ||||
|     // and passed in pid matches a pid of an existing source, the title | ||||
|     // match is ignored to enable representing a tray icon and notifications | ||||
|     // from the same application with a single source. | ||||
|     // | ||||
|     // If no existing source is found, a new source is created as long as | ||||
|     // pid is provided. | ||||
| @@ -199,32 +202,20 @@ const NotificationDaemon = new Lang.Class({ | ||||
|         if (ndata && ndata.notification) | ||||
|             return ndata.notification.source; | ||||
|  | ||||
|         let isForTransientNotification = (ndata && ndata.hints['transient'] == true); | ||||
|  | ||||
|         // We don't want to override a persistent notification | ||||
|         // with a transient one from the same sender, so we | ||||
|         // always create a new source object for new transient notifications | ||||
|         // and never add it to this._sources . | ||||
|         if (!isForTransientNotification) { | ||||
|             let source = this._lookupSource(title, pid, trayIcon); | ||||
|             if (source) { | ||||
|                 source.setTitle(title); | ||||
|                 return source; | ||||
|             } | ||||
|         let source = this._lookupSource(title, pid, trayIcon); | ||||
|         if (source) { | ||||
|             source.setTitle(title); | ||||
|             return source; | ||||
|         } | ||||
|  | ||||
|         let source = new Source(title, pid, sender, trayIcon, ndata ? ndata.hints['desktop-entry'] : null); | ||||
|         source.setTransient(isForTransientNotification); | ||||
|         let source = new FdoNotificationDaemonSource(title, pid, sender, trayIcon, ndata ? ndata.hints['desktop-entry'] : null); | ||||
|  | ||||
|         if (!isForTransientNotification) { | ||||
|             this._sources.push(source); | ||||
|             source.connect('destroy', Lang.bind(this, | ||||
|                 function() { | ||||
|                     let index = this._sources.indexOf(source); | ||||
|                     if (index >= 0) | ||||
|                         this._sources.splice(index, 1); | ||||
|                 })); | ||||
|         } | ||||
|         this._sources.push(source); | ||||
|         source.connect('destroy', Lang.bind(this, function() { | ||||
|             let index = this._sources.indexOf(source); | ||||
|             if (index >= 0) | ||||
|                 this._sources.splice(index, 1); | ||||
|         })); | ||||
|  | ||||
|         Main.messageTray.add(source); | ||||
|         return source; | ||||
| @@ -252,11 +243,11 @@ const NotificationDaemon = new Lang.Class({ | ||||
|               hints['category'] == 'presence.offline')) { | ||||
|             // Ignore replacesId since we already sent back a | ||||
|             // NotificationClosed for that id. | ||||
|             id = nextNotificationId++; | ||||
|             id = this._nextNotificationId++; | ||||
|             Mainloop.idle_add(Lang.bind(this, | ||||
|                                         function () { | ||||
|                                             this._emitNotificationClosed(id, NotificationClosedReason.DISMISSED); | ||||
|                                             return false; | ||||
|                                             return GLib.SOURCE_REMOVE; | ||||
|                                         })); | ||||
|             return invocation.return_value(GLib.Variant.new('(u)', [id])); | ||||
|         } | ||||
| @@ -296,7 +287,7 @@ const NotificationDaemon = new Lang.Class({ | ||||
|             ndata.notification = this._notifications[replacesId].notification; | ||||
|         } else { | ||||
|             replacesId = 0; | ||||
|             ndata.id = id = nextNotificationId++; | ||||
|             ndata.id = id = this._nextNotificationId++; | ||||
|         } | ||||
|         this._notifications[id] = ndata; | ||||
|  | ||||
| @@ -331,26 +322,29 @@ const NotificationDaemon = new Lang.Class({ | ||||
|             let [pid] = result; | ||||
|             source = this._getSource(appName, pid, ndata, sender, null); | ||||
|  | ||||
|             // We only store sender-pid entries for persistent sources. | ||||
|             // Removing the entries once the source is destroyed | ||||
|             // would result in the entries associated with transient | ||||
|             // sources removed once the notification is shown anyway. | ||||
|             // However, keeping these pairs would mean that we would | ||||
|             // possibly remove an entry associated with a persistent | ||||
|             // source when a transient source for the same sender is | ||||
|             // distroyed. | ||||
|             if (!source.isTransient) { | ||||
|                 this._senderToPid[sender] = pid; | ||||
|                 source.connect('destroy', Lang.bind(this, function() { | ||||
|                     delete this._senderToPid[sender]; | ||||
|                 })); | ||||
|             } | ||||
|             this._senderToPid[sender] = pid; | ||||
|             source.connect('destroy', Lang.bind(this, function() { | ||||
|                 delete this._senderToPid[sender]; | ||||
|             })); | ||||
|             this._notifyForSource(source, ndata); | ||||
|         })); | ||||
|  | ||||
|         return invocation.return_value(GLib.Variant.new('(u)', [id])); | ||||
|     }, | ||||
|  | ||||
|     _makeButton: function(id, label, useActionIcons) { | ||||
|         let button = new St.Button({ can_focus: true }); | ||||
|         let iconName = id.endsWith('-symbolic') ? id : id + '-symbolic'; | ||||
|         if (useActionIcons && Gtk.IconTheme.get_default().has_icon(iconName)) { | ||||
|             button.add_style_class_name('notification-icon-button'); | ||||
|             button.child = new St.Icon({ icon_name: iconName }); | ||||
|         } else { | ||||
|             button.add_style_class_name('notification-button'); | ||||
|             button.label = label; | ||||
|         } | ||||
|         return button; | ||||
|     }, | ||||
|  | ||||
|     _notifyForSource: function(source, ndata) { | ||||
|         let [id, icon, summary, body, actions, hints, notification] = | ||||
|             [ndata.id, ndata.icon, ndata.summary, ndata.body, | ||||
| @@ -376,10 +370,6 @@ const NotificationDaemon = new Lang.Class({ | ||||
|                     } | ||||
|                     this._emitNotificationClosed(ndata.id, notificationClosedReason); | ||||
|                 })); | ||||
|             notification.connect('action-invoked', Lang.bind(this, | ||||
|                 function(n, actionId) { | ||||
|                     this._emitActionInvoked(ndata.id, actionId); | ||||
|                 })); | ||||
|         } | ||||
|  | ||||
|         // Mark music notifications so they can be shown in the screen shield | ||||
| @@ -413,18 +403,33 @@ const NotificationDaemon = new Lang.Class({ | ||||
|                                              soundName: hints['sound-name'] }); | ||||
|         notification.setImage(image); | ||||
|  | ||||
|         let hasDefaultAction = false; | ||||
|  | ||||
|         if (actions.length) { | ||||
|             notification.setUseActionIcons(hints['action-icons'] == true); | ||||
|             let useActionIcons = (hints['action-icons'] == true); | ||||
|  | ||||
|             for (let i = 0; i < actions.length - 1; i += 2) { | ||||
|                 if (actions[i] == 'default') | ||||
|                     notification.connect('clicked', Lang.bind(this, | ||||
|                         function() { | ||||
|                             this._emitActionInvoked(ndata.id, "default"); | ||||
|                         })); | ||||
|                 else | ||||
|                     notification.addButton(actions[i], actions[i + 1]); | ||||
|                 let [actionId, label] = [actions[i], actions[i+1]]; | ||||
|                 if (actionId == 'default') { | ||||
|                     hasDefaultAction = true; | ||||
|                 } else { | ||||
|                     notification.addButton(this._makeButton(actionId, label, useActionIcons), Lang.bind(this, function() { | ||||
|                         this._emitActionInvoked(ndata.id, actionId); | ||||
|                     })); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (hasDefaultAction) { | ||||
|             notification.connect('clicked', Lang.bind(this, function() { | ||||
|                 this._emitActionInvoked(ndata.id, 'default'); | ||||
|             })); | ||||
|         } else { | ||||
|             notification.connect('clicked', Lang.bind(this, function() { | ||||
|                 source.open(); | ||||
|             })); | ||||
|         } | ||||
|  | ||||
|         switch (hints.urgency) { | ||||
|             case Urgency.LOW: | ||||
|                 notification.setUrgency(MessageTray.Urgency.LOW); | ||||
| @@ -517,8 +522,8 @@ const NotificationDaemon = new Lang.Class({ | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const Source = new Lang.Class({ | ||||
|     Name: 'NotificationDaemonSource', | ||||
| const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|     Name: 'FdoNotificationDaemonSource', | ||||
|     Extends: MessageTray.Source, | ||||
|  | ||||
|     _init: function(title, pid, sender, trayIcon, appId) { | ||||
| @@ -553,7 +558,7 @@ const Source = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _createPolicy: function() { | ||||
|         if (this.app) { | ||||
|         if (this.app && this.app.get_app_info()) { | ||||
|             let id = this.app.get_id().replace(/\.desktop$/,''); | ||||
|             return new MessageTray.NotificationApplicationPolicy(id); | ||||
|         } else { | ||||
| @@ -643,7 +648,7 @@ const Source = new Lang.Class({ | ||||
|         this.parent(title); | ||||
|     }, | ||||
|  | ||||
|     open: function(notification) { | ||||
|     open: function() { | ||||
|         this.openApp(); | ||||
|         this.destroyNonResidentNotifications(); | ||||
|     }, | ||||
| @@ -685,3 +690,290 @@ const Source = new Lang.Class({ | ||||
|         } | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const PRIORITY_URGENCY_MAP = { | ||||
|     low: MessageTray.Urgency.LOW, | ||||
|     normal: MessageTray.Urgency.NORMAL, | ||||
|     high: MessageTray.Urgency.HIGH, | ||||
|     urgent: MessageTray.Urgency.CRITICAL | ||||
| }; | ||||
|  | ||||
| const GtkNotificationDaemonNotification = new Lang.Class({ | ||||
|     Name: 'GtkNotificationDaemonNotification', | ||||
|     Extends: MessageTray.Notification, | ||||
|  | ||||
|     _init: function(source, notification) { | ||||
|         this.parent(source); | ||||
|         this._serialized = GLib.Variant.new('a{sv}', notification); | ||||
|  | ||||
|         let { "title": title, | ||||
|               "body": body, | ||||
|               "icon": gicon, | ||||
|               "urgent": urgent, | ||||
|               "priority": priority, | ||||
|               "buttons": buttons, | ||||
|               "default-action": defaultAction, | ||||
|               "default-action-target": defaultActionTarget } = notification; | ||||
|  | ||||
|         if (priority) { | ||||
|             let urgency = PRIORITY_URGENCY_MAP[priority.unpack()]; | ||||
|             this.setUrgency(urgency != undefined ? urgency : MessageTray.Urgency.NORMAL); | ||||
|         } else if (urgent) { | ||||
|             this.setUrgency(urgent.unpack() ? MessageTray.Urgency.CRITICAL | ||||
|                             : MessageTray.Urgency.NORMAL); | ||||
|         } else { | ||||
|             this.setUrgency(MessageTray.Urgency.NORMAL); | ||||
|         } | ||||
|  | ||||
|         if (buttons) { | ||||
|             buttons.deep_unpack().forEach(Lang.bind(this, function(button) { | ||||
|                 this.addAction(button.label.unpack(), | ||||
|                                Lang.bind(this, this._onButtonClicked, button)); | ||||
|             })); | ||||
|         } | ||||
|  | ||||
|         this._defaultAction = defaultAction ? defaultAction.unpack() : null; | ||||
|         this._defaultActionTarget = defaultActionTarget; | ||||
|  | ||||
|         this.update(title.unpack(), body ? body.unpack() : null, | ||||
|                     { gicon: gicon ? Gio.icon_deserialize(gicon) : null }); | ||||
|     }, | ||||
|  | ||||
|     _activateAction: function(namespacedActionId, target) { | ||||
|         if (namespacedActionId) { | ||||
|             if (namespacedActionId.startsWith('app.')) { | ||||
|                 let actionId = namespacedActionId.slice('app.'.length); | ||||
|                 this.source.activateAction(actionId, target); | ||||
|             } | ||||
|         } else { | ||||
|             this.source.open(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _onButtonClicked: function(button) { | ||||
|         let { 'action': action, 'target': actionTarget } = button; | ||||
|         this._activateAction(action.unpack(), actionTarget); | ||||
|     }, | ||||
|  | ||||
|     _onClicked: function() { | ||||
|         this._activateAction(this._defaultAction, this._defaultActionTarget); | ||||
|         this.parent(); | ||||
|     }, | ||||
|  | ||||
|     serialize: function() { | ||||
|         return this._serialized; | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| const FdoApplicationIface = '<node> \ | ||||
| <interface name="org.freedesktop.Application"> \ | ||||
| <method name="ActivateAction"> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
|     <arg type="av" direction="in" /> \ | ||||
|     <arg type="a{sv}" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="Activate"> \ | ||||
|     <arg type="a{sv}" direction="in" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const FdoApplicationProxy = Gio.DBusProxy.makeProxyWrapper(FdoApplicationIface); | ||||
|  | ||||
| function objectPathFromAppId(appId) { | ||||
|     return '/' + appId.replace(/\./g, '/'); | ||||
| } | ||||
|  | ||||
| function getPlatformData() { | ||||
|     let startupId = GLib.Variant.new('s', '_TIME' + global.get_current_time()); | ||||
|     return { "desktop-startup-id": startupId }; | ||||
| } | ||||
|  | ||||
| function InvalidAppError() {} | ||||
|  | ||||
| const GtkNotificationDaemonAppSource = new Lang.Class({ | ||||
|     Name: 'GtkNotificationDaemonAppSource', | ||||
|     Extends: MessageTray.Source, | ||||
|  | ||||
|     _init: function(appId) { | ||||
|         this._appId = appId; | ||||
|         this._objectPath = objectPathFromAppId(appId); | ||||
|  | ||||
|         this._app = Shell.AppSystem.get_default().lookup_app(appId + '.desktop'); | ||||
|         if (!this._app) | ||||
|             throw new InvalidAppError(); | ||||
|  | ||||
|         this._notifications = {}; | ||||
|  | ||||
|         this.parent(this._app.get_name()); | ||||
|     }, | ||||
|  | ||||
|     createIcon: function(size) { | ||||
|         return this._app.create_icon_texture(size); | ||||
|     }, | ||||
|  | ||||
|     _createPolicy: function() { | ||||
|         return new MessageTray.NotificationApplicationPolicy(this._appId); | ||||
|     }, | ||||
|  | ||||
|     _createApp: function() { | ||||
|         return new FdoApplicationProxy(Gio.DBus.session, this._appId, this._objectPath); | ||||
|     }, | ||||
|  | ||||
|     activateAction: function(actionId, target) { | ||||
|         let app = this._createApp(); | ||||
|         app.ActivateActionRemote(actionId, target ? [target] : [], getPlatformData()); | ||||
|     }, | ||||
|  | ||||
|     open: function() { | ||||
|         let app = this._createApp(); | ||||
|         app.ActivateRemote(getPlatformData()); | ||||
|     }, | ||||
|  | ||||
|     addNotification: function(notificationId, notificationParams, showBanner) { | ||||
|         if (this._notifications[notificationId]) | ||||
|             this._notifications[notificationId].destroy(); | ||||
|  | ||||
|         let notification = new GtkNotificationDaemonNotification(this, notificationParams); | ||||
|         notification.connect('destroy', Lang.bind(this, function() { | ||||
|             delete this._notifications[notificationId]; | ||||
|         })); | ||||
|         this._notifications[notificationId] = notification; | ||||
|  | ||||
|         if (showBanner) | ||||
|             this.notify(notification); | ||||
|         else | ||||
|             this.pushNotification(notification); | ||||
|     }, | ||||
|  | ||||
|     removeNotification: function(notificationId) { | ||||
|         if (this._notifications[notificationId]) | ||||
|             this._notifications[notificationId].destroy(MessageTray.NotificationDestroyedReason.SOURCE_CLOSED); | ||||
|     }, | ||||
|  | ||||
|     serialize: function() { | ||||
|         let notifications = []; | ||||
|         for (let notificationId in this._notifications) { | ||||
|             let notification = this._notifications[notificationId]; | ||||
|             notifications.push([notificationId, notification.serialize()]); | ||||
|         } | ||||
|         return [this._appId, notifications]; | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| const GtkNotificationsIface = '<node> \ | ||||
| <interface name="org.gtk.Notifications"> \ | ||||
| <method name="AddNotification"> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
|     <arg type="a{sv}" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="RemoveNotification"> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const GtkNotificationDaemon = new Lang.Class({ | ||||
|     Name: 'GtkNotificationDaemon', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._sources = {}; | ||||
|  | ||||
|         this._loadNotifications(); | ||||
|  | ||||
|         this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GtkNotificationsIface, this); | ||||
|         this._dbusImpl.export(Gio.DBus.session, '/org/gtk/Notifications'); | ||||
|  | ||||
|         Gio.DBus.session.own_name('org.gtk.Notifications', Gio.BusNameOwnerFlags.REPLACE, null, null); | ||||
|     }, | ||||
|  | ||||
|     _ensureAppSource: function(appId) { | ||||
|         if (this._sources[appId]) | ||||
|             return this._sources[appId]; | ||||
|  | ||||
|         let source = new GtkNotificationDaemonAppSource(appId); | ||||
|  | ||||
|         source.connect('destroy', Lang.bind(this, function() { | ||||
|             delete this._sources[appId]; | ||||
|             this._saveNotifications(); | ||||
|         })); | ||||
|         source.connect('count-updated', Lang.bind(this, this._saveNotifications)); | ||||
|         Main.messageTray.add(source); | ||||
|         this._sources[appId] = source; | ||||
|         return source; | ||||
|     }, | ||||
|  | ||||
|     _loadNotifications: function() { | ||||
|         this._isLoading = true; | ||||
|  | ||||
|         let value = global.get_persistent_state('a(sa(sv))', 'notifications'); | ||||
|         if (value) { | ||||
|             let sources = value.deep_unpack(); | ||||
|             sources.forEach(Lang.bind(this, function([appId, notifications]) { | ||||
|                 if (notifications.length == 0) | ||||
|                     return; | ||||
|  | ||||
|                 let source; | ||||
|                 try { | ||||
|                     source = this._ensureAppSource(appId); | ||||
|                 } catch(e if e instanceof InvalidAppError) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 notifications.forEach(function([notificationId, notification]) { | ||||
|                     source.addNotification(notificationId, notification.deep_unpack(), false); | ||||
|                 }); | ||||
|             })); | ||||
|         } | ||||
|  | ||||
|         this._isLoading = false; | ||||
|     }, | ||||
|  | ||||
|     _saveNotifications: function() { | ||||
|         if (this._isLoading) | ||||
|             return; | ||||
|  | ||||
|         let sources = []; | ||||
|         for (let appId in this._sources) { | ||||
|             let source = this._sources[appId]; | ||||
|             sources.push(source.serialize()); | ||||
|         } | ||||
|  | ||||
|         global.set_persistent_state('notifications', new GLib.Variant('a(sa(sv))', sources)); | ||||
|     }, | ||||
|  | ||||
|     AddNotificationAsync: function(params, invocation) { | ||||
|         let [appId, notificationId, notification] = params; | ||||
|  | ||||
|         let source; | ||||
|         try { | ||||
|             source = this._ensureAppSource(appId); | ||||
|         } catch(e if e instanceof InvalidAppError) { | ||||
|             invocation.return_dbus_error('org.gtk.Notifications.InvalidApp', 'The app by ID "%s" could not be found'.format(appId)); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         source.addNotification(notificationId, notification, true); | ||||
|  | ||||
|         invocation.return_value(null); | ||||
|     }, | ||||
|  | ||||
|     RemoveNotificationAsync: function(params, invocation) { | ||||
|         let [appId, notificationId] = params; | ||||
|         let source = this._sources[appId]; | ||||
|         if (source) | ||||
|             source.removeNotification(notificationId); | ||||
|  | ||||
|         invocation.return_value(null); | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| const NotificationDaemon = new Lang.Class({ | ||||
|     Name: 'NotificationDaemon', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._fdoNotificationDaemon = new FdoNotificationDaemon(); | ||||
|         this._gtkNotificationDaemon = new GtkNotificationDaemon(); | ||||
|     }, | ||||
| }); | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const GLib = imports.gi.GLib; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const Lang = imports.lang; | ||||
| @@ -65,6 +66,7 @@ const LevelBar = new Lang.Class({ | ||||
|         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(); | ||||
|         cr.$dispose(); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -77,7 +79,8 @@ const OsdWindow = new Lang.Class({ | ||||
|                                      y_expand: true, | ||||
|                                      x_align: Clutter.ActorAlign.CENTER, | ||||
|                                      y_align: Clutter.ActorAlign.CENTER }); | ||||
|         this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true })); | ||||
|         this._currentMonitor = undefined; | ||||
|         this.setMonitor (-1); | ||||
|         this._box = new St.BoxLayout({ style_class: 'osd-window', | ||||
|                                        vertical: true }); | ||||
|         this.actor.add_actor(this._box); | ||||
| @@ -122,13 +125,15 @@ const OsdWindow = new Lang.Class({ | ||||
|  | ||||
|     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; | ||||
|         if (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() { | ||||
| @@ -172,6 +177,7 @@ const OsdWindow = new Lang.Class({ | ||||
|                               Meta.enable_unredirect_for_screen(global.screen); | ||||
|                            }) | ||||
|                          }); | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|     }, | ||||
|  | ||||
|     _reset: function() { | ||||
| @@ -182,14 +188,21 @@ const OsdWindow = new Lang.Class({ | ||||
|  | ||||
|     _monitorsChanged: function() { | ||||
|         /* assume 110x110 on a 640x480 display and scale from there */ | ||||
|         let monitor = Main.layoutManager.primaryMonitor; | ||||
|         let monitor; | ||||
|  | ||||
|         if (this._currentMonitor >= 0) | ||||
|             monitor = Main.layoutManager.monitors[this._currentMonitor]; | ||||
|         else | ||||
|             monitor = Main.layoutManager.primaryMonitor; | ||||
|  | ||||
|         let scalew = monitor.width / 640.0; | ||||
|         let scaleh = monitor.height / 480.0; | ||||
|         let scale = Math.min(scalew, scaleh); | ||||
|         this._popupSize = 110 * Math.max(1, scale); | ||||
|  | ||||
|         let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; | ||||
|         this._icon.icon_size = this._popupSize / (2 * scaleFactor); | ||||
|         this._box.translation_y = monitor.height / 4; | ||||
|         this._icon.icon_size = this._popupSize / 2; | ||||
|         this._box.style_changed(); | ||||
|     }, | ||||
|  | ||||
| @@ -205,6 +218,27 @@ const OsdWindow = new Lang.Class({ | ||||
|         let minWidth = this._popupSize - verticalPadding - leftBorder - rightBorder; | ||||
|         let minHeight = this._popupSize - horizontalPadding - topBorder - bottomBorder; | ||||
|  | ||||
|         this._box.style = 'min-height: %dpx;'.format(Math.max(minWidth, minHeight)); | ||||
|         // minWidth/minHeight here are in real pixels, | ||||
|         // but the theme takes measures in unscaled dimensions | ||||
|         let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; | ||||
|         this._box.style = 'min-height: %dpx;'.format(Math.max(minWidth, minHeight) / scaleFactor); | ||||
|     }, | ||||
|  | ||||
|     setMonitor: function(index) { | ||||
|         let constraint; | ||||
|  | ||||
|         if (index < 0) | ||||
|             index = -1; | ||||
|         if (this._currentMonitor == index) | ||||
|             return; | ||||
|  | ||||
|         if (index < 0) | ||||
|             constraint = new Layout.MonitorConstraint({ primary: true }); | ||||
|         else | ||||
|             constraint = new Layout.MonitorConstraint({ index: index }); | ||||
|  | ||||
|         this.actor.clear_constraints(); | ||||
|         this.actor.add_constraint(constraint); | ||||
|         this._currentMonitor = index; | ||||
|     } | ||||
| }); | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Mainloop = imports.mainloop; | ||||
| @@ -13,6 +14,7 @@ const Gdk = imports.gi.Gdk; | ||||
| const Background = imports.ui.background; | ||||
| const DND = imports.ui.dnd; | ||||
| const LayoutManager = imports.ui.layout; | ||||
| const Lightbox = imports.ui.lightbox; | ||||
| const Main = imports.ui.main; | ||||
| const MessageTray = imports.ui.messageTray; | ||||
| const OverviewControls = imports.ui.overviewControls; | ||||
| @@ -78,10 +80,8 @@ const ShellInfo = new Lang.Class({ | ||||
|         } | ||||
|  | ||||
|         this._undoCallback = undoCallback; | ||||
|         if (undoCallback) { | ||||
|             notification.addButton('system-undo', _("Undo")); | ||||
|             notification.connect('action-invoked', Lang.bind(this, this._onUndoClicked)); | ||||
|         } | ||||
|         if (undoCallback) | ||||
|             notification.addAction(_("Undo"), Lang.bind(this, this._onUndoClicked)); | ||||
|  | ||||
|         this._source.notify(notification); | ||||
|     } | ||||
| @@ -114,9 +114,6 @@ const Overview = new Lang.Class({ | ||||
|         // rendering options without duplicating the texture data. | ||||
|         let monitor = Main.layoutManager.primaryMonitor; | ||||
|  | ||||
|         this._desktopFade = new St.Bin(); | ||||
|         Main.layoutManager.overviewGroup.add_child(this._desktopFade); | ||||
|  | ||||
|         let layout = new Clutter.BinLayout(); | ||||
|         this._stack = new Clutter.Actor({ layout_manager: layout }); | ||||
|         this._stack.add_constraint(new LayoutManager.MonitorConstraint({ primary: true })); | ||||
| @@ -135,6 +132,9 @@ const Overview = new Lang.Class({ | ||||
|         Main.layoutManager.overviewGroup.add_child(this._backgroundGroup); | ||||
|         this._bgManagers = []; | ||||
|  | ||||
|         this._desktopFade = new St.Widget(); | ||||
|         Main.layoutManager.overviewGroup.add_child(this._desktopFade); | ||||
|  | ||||
|         this._activationTime = 0; | ||||
|  | ||||
|         this.visible = false;           // animating to overview, in overview, animating out | ||||
| @@ -149,7 +149,7 @@ const Overview = new Lang.Class({ | ||||
|         this._coverPane = new Clutter.Actor({ opacity: 0, | ||||
|                                               reactive: true }); | ||||
|         Main.layoutManager.overviewGroup.add_child(this._coverPane); | ||||
|         this._coverPane.connect('event', Lang.bind(this, function (actor, event) { return true; })); | ||||
|         this._coverPane.connect('event', Lang.bind(this, function (actor, event) { return Clutter.EVENT_STOP; })); | ||||
|  | ||||
|         this._stack.add_actor(this._overview); | ||||
|         Main.layoutManager.overviewGroup.add_child(this._stack); | ||||
| @@ -197,11 +197,7 @@ const Overview = new Lang.Class({ | ||||
|  | ||||
|             Tweener.addTween(background, | ||||
|                              { brightness: 1.0, | ||||
|                                time: SHADE_ANIMATION_TIME, | ||||
|                                transition: 'easeOutQuad' | ||||
|                              }); | ||||
|             Tweener.addTween(background, | ||||
|                              { vignetteSharpness: 0.0, | ||||
|                                vignetteSharpness: 0.0, | ||||
|                                time: SHADE_ANIMATION_TIME, | ||||
|                                transition: 'easeOutQuad' | ||||
|                              }); | ||||
| @@ -214,12 +210,8 @@ const Overview = new Lang.Class({ | ||||
|             let background = backgrounds[i]._delegate; | ||||
|  | ||||
|             Tweener.addTween(background, | ||||
|                              { brightness: 0.8, | ||||
|                                time: SHADE_ANIMATION_TIME, | ||||
|                                transition: 'easeOutQuad' | ||||
|                              }); | ||||
|             Tweener.addTween(background, | ||||
|                              { vignetteSharpness: 0.7, | ||||
|                              { brightness: Lightbox.VIGNETTE_BRIGHTNESS, | ||||
|                                vignetteSharpness: Lightbox.VIGNETTE_SHARPNESS, | ||||
|                                time: SHADE_ANIMATION_TIME, | ||||
|                                transition: 'easeOutQuad' | ||||
|                              }); | ||||
| @@ -365,11 +357,13 @@ const Overview = new Lang.Class({ | ||||
|             this._lastHoveredWindow = dragEvent.targetActor._delegate.metaWindow; | ||||
|             this._windowSwitchTimeoutId = Mainloop.timeout_add(DND_WINDOW_SWITCH_TIMEOUT, | ||||
|                                             Lang.bind(this, function() { | ||||
|                                                 this._windowSwitchTimeoutId = 0; | ||||
|                                                 this._needsFakePointerEvent = true; | ||||
|                                                 Main.activateWindow(dragEvent.targetActor._delegate.metaWindow, | ||||
|                                                                     this._windowSwitchTimestamp); | ||||
|                                                 this.hide(); | ||||
|                                                 this._lastHoveredWindow = null; | ||||
|                                                 return GLib.SOURCE_REMOVE; | ||||
|                                             })); | ||||
|         } | ||||
|  | ||||
| @@ -378,6 +372,7 @@ const Overview = new Lang.Class({ | ||||
|  | ||||
|     _onScrollEvent: function(actor, event) { | ||||
|         this.emit('scroll-event', event); | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     addAction: function(action) { | ||||
| @@ -445,17 +440,17 @@ const Overview = new Lang.Class({ | ||||
|         this._inDrag = false; | ||||
|     }, | ||||
|  | ||||
|     beginWindowDrag: function(source) { | ||||
|         this.emit('window-drag-begin'); | ||||
|     beginWindowDrag: function(clone) { | ||||
|         this.emit('window-drag-begin', clone); | ||||
|         this._inDrag = true; | ||||
|     }, | ||||
|  | ||||
|     cancelledWindowDrag: function(source) { | ||||
|         this.emit('window-drag-cancelled'); | ||||
|     cancelledWindowDrag: function(clone) { | ||||
|         this.emit('window-drag-cancelled', clone); | ||||
|     }, | ||||
|  | ||||
|     endWindowDrag: function(source) { | ||||
|         this.emit('window-drag-end'); | ||||
|     endWindowDrag: function(clone) { | ||||
|         this.emit('window-drag-end', clone); | ||||
|         this._inDrag = false; | ||||
|     }, | ||||
|  | ||||
| @@ -491,8 +486,13 @@ const Overview = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     fadeOutDesktop: function() { | ||||
|         if (!this._desktopFade.child) | ||||
|             this._desktopFade.child = this._getDesktopClone(); | ||||
|         if (!this._desktopFade.get_n_children()) { | ||||
|             let clone = this._getDesktopClone(); | ||||
|             if (!clone) | ||||
|                 return; | ||||
|  | ||||
|             this._desktopFade.add_child(clone); | ||||
|         } | ||||
|  | ||||
|         this._desktopFade.opacity = 255; | ||||
|         this._desktopFade.show(); | ||||
|   | ||||
| @@ -36,6 +36,7 @@ const SlideLayout = new Lang.Class({ | ||||
|  | ||||
|     _init: function(params) { | ||||
|         this._slideX = 1; | ||||
|         this._translationX = 0; | ||||
|         this._direction = SlideDirection.LEFT; | ||||
|  | ||||
|         this.parent(params); | ||||
| @@ -66,9 +67,9 @@ const SlideLayout = new Lang.Class({ | ||||
|         let alignX = (realDirection == SlideDirection.LEFT) ? (availWidth - natWidth) : 0; | ||||
|  | ||||
|         let actorBox = new Clutter.ActorBox(); | ||||
|         actorBox.x1 = alignX; | ||||
|         actorBox.x2 = actorBox.x1 + child.x_expand ? availWidth : natWidth; | ||||
|         actorBox.y1 = 0; | ||||
|         actorBox.x1 = box.x1 + alignX + this._translationX; | ||||
|         actorBox.x2 = actorBox.x1 + (child.x_expand ? availWidth : natWidth); | ||||
|         actorBox.y1 = box.y1; | ||||
|         actorBox.y2 = actorBox.y1 + availHeight; | ||||
|  | ||||
|         child.allocate(actorBox, flags); | ||||
| @@ -90,7 +91,16 @@ const SlideLayout = new Lang.Class({ | ||||
|  | ||||
|     get slideDirection() { | ||||
|         return this._direction; | ||||
|     } | ||||
|     }, | ||||
|  | ||||
|     set translationX(value) { | ||||
|         this._translationX = value; | ||||
|         this.layout_changed(); | ||||
|     }, | ||||
|  | ||||
|     get translationX() { | ||||
|         return this._translationX; | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| const SlidingControl = new Lang.Class({ | ||||
| @@ -99,8 +109,8 @@ const SlidingControl = new Lang.Class({ | ||||
|     _init: function(params) { | ||||
|         params = Params.parse(params, { slideDirection: SlideDirection.LEFT }); | ||||
|  | ||||
|         this.visible = true; | ||||
|         this.inDrag = false; | ||||
|         this._visible = true; | ||||
|         this._inDrag = false; | ||||
|  | ||||
|         this.layout = new SlideLayout(); | ||||
|         this.layout.slideDirection = params.slideDirection; | ||||
| @@ -109,6 +119,7 @@ const SlidingControl = new Lang.Class({ | ||||
|                                      clip_to_allocation: true }); | ||||
|  | ||||
|         Main.overview.connect('showing', Lang.bind(this, this._onOverviewShowing)); | ||||
|         Main.overview.connect('hiding', Lang.bind(this, this._onOverviewHiding)); | ||||
|  | ||||
|         Main.overview.connect('item-drag-begin', Lang.bind(this, this._onDragBegin)); | ||||
|         Main.overview.connect('item-drag-end', Lang.bind(this, this._onDragEnd)); | ||||
| @@ -119,12 +130,12 @@ const SlidingControl = new Lang.Class({ | ||||
|         Main.overview.connect('window-drag-end', Lang.bind(this, this._onWindowDragEnd)); | ||||
|     }, | ||||
|  | ||||
|     getSlide: function() { | ||||
|     _getSlide: function() { | ||||
|         throw new Error('getSlide() must be overridden'); | ||||
|     }, | ||||
|  | ||||
|     updateSlide: function() { | ||||
|         Tweener.addTween(this.layout, { slideX: this.getSlide(), | ||||
|     _updateSlide: function() { | ||||
|         Tweener.addTween(this.layout, { slideX: this._getSlide(), | ||||
|                                         time: SIDE_CONTROLS_ANIMATION_TIME, | ||||
|                                         transition: 'easeOutQuad' }); | ||||
|     }, | ||||
| @@ -151,28 +162,30 @@ const SlidingControl = new Lang.Class({ | ||||
|         let translationEnd = 0; | ||||
|         let translation = this._getTranslation(); | ||||
|  | ||||
|         if (this.visible) { | ||||
|         if (this._visible) { | ||||
|             translationStart = translation; | ||||
|         } else { | ||||
|             translationEnd = translation; | ||||
|         } | ||||
|  | ||||
|         if (this.actor.translation_x == translationEnd) | ||||
|         if (this.layout.translationX == translationEnd) | ||||
|             return; | ||||
|  | ||||
|         this.actor.translation_x = translationStart; | ||||
|         Tweener.addTween(this.actor, { translation_x: translationEnd, | ||||
|                                        time: SIDE_CONTROLS_ANIMATION_TIME, | ||||
|                                        transition: 'easeOutQuad' | ||||
|                                      }); | ||||
|         this.layout.translationX = translationStart; | ||||
|         Tweener.addTween(this.layout, { translationX: 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; | ||||
|         this._visible = true; | ||||
|         this.layout.slideX = this._getSlide(); | ||||
|         this.layout.translationX = this._getTranslation(); | ||||
|         this.slideIn(); | ||||
|     }, | ||||
|  | ||||
|     _onOverviewHiding: function() { | ||||
|         this.slideOut(); | ||||
|     }, | ||||
|  | ||||
|     _onWindowDragBegin: function() { | ||||
| @@ -184,14 +197,14 @@ const SlidingControl = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _onDragBegin: function() { | ||||
|         this.inDrag = true; | ||||
|         this.actor.translation_x = 0; | ||||
|         this.updateSlide(); | ||||
|         this._inDrag = true; | ||||
|         this.layout.translationX = 0; | ||||
|         this._updateSlide(); | ||||
|     }, | ||||
|  | ||||
|     _onDragEnd: function() { | ||||
|         this.inDrag = false; | ||||
|         this.updateSlide(); | ||||
|         this._inDrag = false; | ||||
|         this._updateSlide(); | ||||
|     }, | ||||
|  | ||||
|     fadeIn: function() { | ||||
| @@ -209,13 +222,13 @@ const SlidingControl = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     slideIn: function() { | ||||
|         this.visible = true; | ||||
|         this._visible = true; | ||||
|         this._updateTranslation(); | ||||
|         // we will update slideX and the translation from pageEmpty | ||||
|     }, | ||||
|  | ||||
|     slideOut: function() { | ||||
|         this.visible = false; | ||||
|         this._visible = false; | ||||
|         this._updateTranslation(); | ||||
|         // we will update slideX from pageEmpty | ||||
|     }, | ||||
| @@ -225,7 +238,7 @@ const SlidingControl = new Lang.Class({ | ||||
|         // 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.layout.slideX = this._getSlide(); | ||||
|         this._updateTranslation(); | ||||
|     } | ||||
| }); | ||||
| @@ -244,16 +257,15 @@ const ThumbnailsSlider = new Lang.Class({ | ||||
|         this.actor.track_hover = true; | ||||
|         this.actor.add_actor(this._thumbnailsBox.actor); | ||||
|  | ||||
|         Main.layoutManager.connect('monitors-changed', Lang.bind(this, this.updateSlide)); | ||||
|         Main.overview.connect('hiding', Lang.bind(this, this.slideOut)); | ||||
|         this.actor.connect('notify::hover', Lang.bind(this, this.updateSlide)); | ||||
|         Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._updateSlide)); | ||||
|         this.actor.connect('notify::hover', Lang.bind(this, this._updateSlide)); | ||||
|         this._thumbnailsBox.actor.bind_property('visible', this.actor, 'visible', GObject.BindingFlags.SYNC_CREATE); | ||||
|     }, | ||||
|  | ||||
|     _getAlwaysZoomOut: function() { | ||||
|         // Always show the pager when hover, during a drag, or if workspaces are | ||||
|         // actually used, e.g. there are windows on more than one | ||||
|         let alwaysZoomOut = this.actor.hover || this.inDrag || !Meta.prefs_get_dynamic_workspaces() || global.screen.n_workspaces > 2; | ||||
|         let alwaysZoomOut = this.actor.hover || this._inDrag || !Meta.prefs_get_dynamic_workspaces() || global.screen.n_workspaces > 2; | ||||
|  | ||||
|         if (!alwaysZoomOut) { | ||||
|             let monitors = Main.layoutManager.monitors; | ||||
| @@ -273,20 +285,13 @@ const ThumbnailsSlider = new Lang.Class({ | ||||
|         return alwaysZoomOut; | ||||
|     }, | ||||
|  | ||||
|     _onOverviewShowing: function() { | ||||
|         this.visible = true; | ||||
|         this.layout.slideX = this.getSlide(); | ||||
|         this.actor.translation_x = this._getTranslation(); | ||||
|         this.slideIn(); | ||||
|     }, | ||||
|  | ||||
|     getNonExpandedWidth: function() { | ||||
|         let child = this.actor.get_first_child(); | ||||
|         return child.get_theme_node().get_length('visible-width'); | ||||
|     }, | ||||
|  | ||||
|     getSlide: function() { | ||||
|         if (!this.visible) | ||||
|     _getSlide: function() { | ||||
|         if (!this._visible) | ||||
|             return 0; | ||||
|  | ||||
|         let alwaysZoomOut = this._getAlwaysZoomOut(); | ||||
| @@ -323,29 +328,22 @@ const DashSlider = new Lang.Class({ | ||||
|         // available allocation | ||||
|         this._dash.actor.x_expand = true; | ||||
|  | ||||
|         this.actor.x_expand = true; | ||||
|         this.actor.x_align = Clutter.ActorAlign.START; | ||||
|         this.actor.y_expand = true; | ||||
|  | ||||
|         this.actor.add_actor(this._dash.actor); | ||||
|  | ||||
|         this._dash.connect('icon-size-changed', Lang.bind(this, this.updateSlide)); | ||||
|         Main.overview.connect('hiding', Lang.bind(this, this.slideOut)); | ||||
|         this._dash.connect('icon-size-changed', Lang.bind(this, this._updateSlide)); | ||||
|     }, | ||||
|  | ||||
|     getSlide: function() { | ||||
|         if (this.visible || this.inDrag) | ||||
|     _getSlide: function() { | ||||
|         if (this._visible || this._inDrag) | ||||
|             return 1; | ||||
|         else | ||||
|             return 0; | ||||
|     }, | ||||
|  | ||||
|     _onOverviewShowing: function() { | ||||
|         this.visible = true; | ||||
|         this.layout.slideX = this.getSlide(); | ||||
|         this.actor.translation_x = this._getTranslation(); | ||||
|         this.slideIn(); | ||||
|     }, | ||||
|  | ||||
|     _onWindowDragBegin: function() { | ||||
|         this.fadeHalf(); | ||||
|     }, | ||||
| @@ -462,9 +460,6 @@ const MessagesIndicator = new Lang.Class({ | ||||
|         if (source.trayIcon) | ||||
|             return; | ||||
|  | ||||
|         if (source.isTransient) | ||||
|             return; | ||||
|  | ||||
|         source.connect('count-updated', Lang.bind(this, this._updateCount)); | ||||
|         this._sources.push(source); | ||||
|         this._updateCount(); | ||||
|   | ||||
| @@ -213,7 +213,7 @@ const AppMenuButton = new Lang.Class({ | ||||
|         this._label = new TextShadower(); | ||||
|         this._label.actor.y_align = Clutter.ActorAlign.CENTER; | ||||
|         this._hbox.add_actor(this._label.actor); | ||||
|         this._arrow = PopupMenu.unicodeArrow(St.Side.BOTTOM); | ||||
|         this._arrow = PopupMenu.arrowIcon(St.Side.BOTTOM); | ||||
|         this._hbox.add_actor(this._arrow); | ||||
|  | ||||
|         this._iconBottomClip = 0; | ||||
| @@ -602,14 +602,15 @@ const ActivitiesButton = new Lang.Class({ | ||||
|     _onCapturedEvent: function(actor, event) { | ||||
|         if (event.type() == Clutter.EventType.BUTTON_PRESS) { | ||||
|             if (!Main.overview.shouldToggleByCornerOrButton()) | ||||
|                 return true; | ||||
|                 return Clutter.EVENT_STOP; | ||||
|         } | ||||
|         return false; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onButtonRelease: function() { | ||||
|         Main.overview.toggle(); | ||||
|         this.menu.close(); | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onKeyRelease: function(actor, event) { | ||||
| @@ -617,6 +618,7 @@ const ActivitiesButton = new Lang.Class({ | ||||
|         if (symbol == Clutter.KEY_Return || symbol == Clutter.KEY_space) { | ||||
|             Main.overview.toggle(); | ||||
|         } | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _xdndToggleOverview: function(actor) { | ||||
| @@ -628,6 +630,7 @@ const ActivitiesButton = new Lang.Class({ | ||||
|  | ||||
|         Mainloop.source_remove(this._xdndTimeOut); | ||||
|         this._xdndTimeOut = 0; | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -809,7 +812,11 @@ const AggregateMenu = new Lang.Class({ | ||||
|         this._indicators = new St.BoxLayout({ style_class: 'panel-status-indicators-box' }); | ||||
|         this.actor.add_child(this._indicators); | ||||
|  | ||||
|         this._network = new imports.ui.status.network.NMApplet(); | ||||
|         if (Config.HAVE_NETWORKMANAGER) { | ||||
|             this._network = new imports.ui.status.network.NMApplet(); | ||||
|         } else { | ||||
|             this._network = null; | ||||
|         } | ||||
|         if (Config.HAVE_BLUETOOTH) { | ||||
|             this._bluetooth = new imports.ui.status.bluetooth.Indicator(); | ||||
|         } else { | ||||
| @@ -822,24 +829,31 @@ const AggregateMenu = new Lang.Class({ | ||||
|         this._brightness = new imports.ui.status.brightness.Indicator(); | ||||
|         this._system = new imports.ui.status.system.Indicator(); | ||||
|         this._screencast = new imports.ui.status.screencast.Indicator(); | ||||
|         this._location = new imports.ui.status.location.Indicator(); | ||||
|  | ||||
|         this._indicators.add_child(this._screencast.indicators); | ||||
|         this._indicators.add_child(this._network.indicators); | ||||
|         this._indicators.add_child(this._location.indicators); | ||||
|         if (this._network) { | ||||
|             this._indicators.add_child(this._network.indicators); | ||||
|         } | ||||
|         if (this._bluetooth) { | ||||
|             this._indicators.add_child(this._bluetooth.indicators); | ||||
|         } | ||||
|         this._indicators.add_child(this._rfkill.indicators); | ||||
|         this._indicators.add_child(this._volume.indicators); | ||||
|         this._indicators.add_child(this._power.indicators); | ||||
|         this._indicators.add_child(PopupMenu.unicodeArrow(St.Side.BOTTOM)); | ||||
|         this._indicators.add_child(PopupMenu.arrowIcon(St.Side.BOTTOM)); | ||||
|  | ||||
|         this.menu.addMenuItem(this._volume.menu); | ||||
|         this.menu.addMenuItem(this._brightness.menu); | ||||
|         this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); | ||||
|         this.menu.addMenuItem(this._network.menu); | ||||
|         if (this._network) { | ||||
|             this.menu.addMenuItem(this._network.menu); | ||||
|         } | ||||
|         if (this._bluetooth) { | ||||
|             this.menu.addMenuItem(this._bluetooth.menu); | ||||
|         } | ||||
|         this.menu.addMenuItem(this._location.menu); | ||||
|         this.menu.addMenuItem(this._rfkill.menu); | ||||
|         this.menu.addMenuItem(this._power.menu); | ||||
|         this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); | ||||
| @@ -983,23 +997,23 @@ const Panel = new Lang.Class({ | ||||
|  | ||||
|     _onButtonPress: function(actor, event) { | ||||
|         if (Main.modalCount > 0) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         if (event.get_source() != actor) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         let button = event.get_button(); | ||||
|         if (button != 1) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         let focusWindow = global.display.focus_window; | ||||
|         if (!focusWindow) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         let dragWindow = focusWindow.is_attached_dialog() ? focusWindow.get_transient_for() | ||||
|                                                           : focusWindow; | ||||
|         if (!dragWindow) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         let rect = dragWindow.get_outer_rect(); | ||||
|         let [stageX, stageY] = event.get_coords(); | ||||
| @@ -1008,7 +1022,7 @@ const Panel = new Lang.Class({ | ||||
|                         stageX > rect.x && stageX < rect.x + rect.width; | ||||
|  | ||||
|         if (!allowDrag) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         global.display.begin_grab_op(global.screen, | ||||
|                                      dragWindow, | ||||
| @@ -1020,7 +1034,7 @@ const Panel = new Lang.Class({ | ||||
|                                      event.get_time(), | ||||
|                                      stageX, stageY); | ||||
|  | ||||
|         return true; | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     toggleAppMenu: function() { | ||||
|   | ||||
| @@ -137,29 +137,30 @@ const Button = new Lang.Class({ | ||||
|  | ||||
|     _onButtonPress: function(actor, event) { | ||||
|         if (!this.menu) | ||||
|             return; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         this.menu.toggle(); | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onSourceKeyPress: function(actor, event) { | ||||
|         if (!this.menu) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         let symbol = event.get_key_symbol(); | ||||
|         if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return) { | ||||
|             this.menu.toggle(); | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } else if (symbol == Clutter.KEY_Escape && this.menu.isOpen) { | ||||
|             this.menu.close(); | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } else if (symbol == Clutter.KEY_Down) { | ||||
|             if (!this.menu.isOpen) | ||||
|                 this.menu.toggle(); | ||||
|             this.menu.actor.navigate_focus(this.actor, Gtk.DirectionType.DOWN, false); | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } else | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onVisibilityChanged: function() { | ||||
| @@ -172,7 +173,7 @@ const Button = new Lang.Class({ | ||||
|  | ||||
|     _onMenuKeyPress: function(actor, event) { | ||||
|         if (global.focus_manager.navigate_from_event(event)) | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|  | ||||
|         let symbol = event.get_key_symbol(); | ||||
|         if (symbol == Clutter.KEY_Left || symbol == Clutter.KEY_Right) { | ||||
| @@ -180,10 +181,10 @@ const Button = new Lang.Class({ | ||||
|             if (group) { | ||||
|                 let direction = (symbol == Clutter.KEY_Left) ? Gtk.DirectionType.LEFT : Gtk.DirectionType.RIGHT; | ||||
|                 group.navigate_focus(this.actor, direction, false); | ||||
|                 return true; | ||||
|                 return Clutter.EVENT_STOP; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onOpenStateChanged: function(menu, open) { | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Meta = imports.gi.Meta; | ||||
| @@ -110,7 +111,7 @@ const PointerWatcher = new Lang.Class({ | ||||
|  | ||||
|     _onTimeout: function() { | ||||
|         this._updatePointer(); | ||||
|         return true; | ||||
|         return GLib.SOURCE_CONTINUE; | ||||
|     }, | ||||
|  | ||||
|     _updatePointer: function() { | ||||
|   | ||||
| @@ -45,28 +45,35 @@ function isPopupMenuItemVisible(child) { | ||||
| /** | ||||
|  * @side Side to which the arrow points. | ||||
|  */ | ||||
| function unicodeArrow(side) { | ||||
|     let arrowChar; | ||||
| function arrowIcon(side) { | ||||
|     let rotation; | ||||
|     switch (side) { | ||||
|         case St.Side.TOP: | ||||
|             arrowChar = '\u25B4'; | ||||
|             rotation = 180; | ||||
|             break; | ||||
|         case St.Side.RIGHT: | ||||
|             arrowChar = '\u25B8'; | ||||
|             rotation = - 90; | ||||
|             break; | ||||
|         case St.Side.BOTTOM: | ||||
|             arrowChar = '\u25BE'; | ||||
|             rotation = 0; | ||||
|             break; | ||||
|         case St.Side.LEFT: | ||||
|             arrowChar = '\u25C2'; | ||||
|             rotation = 90; | ||||
|             break; | ||||
|     } | ||||
|  | ||||
|     return new St.Label({ text: arrowChar, | ||||
|                           style_class: 'unicode-arrow', | ||||
|                           accessible_role: Atk.Role.ARROW, | ||||
|                           y_expand: true, | ||||
|                           y_align: Clutter.ActorAlign.CENTER }); | ||||
|     let gicon = new Gio.FileIcon({ file: Gio.File.new_for_path(global.datadir + | ||||
|                                              '/theme/menu-arrow-symbolic.svg') }); | ||||
|  | ||||
|     let arrow = new St.Icon({ style_class: 'popup-menu-arrow', | ||||
|                               gicon: gicon, | ||||
|                               accessible_role: Atk.Role.ARROW, | ||||
|                               y_expand: true, | ||||
|                               y_align: Clutter.ActorAlign.CENTER }); | ||||
|  | ||||
|     arrow.rotation_angle_z = rotation; | ||||
|  | ||||
|     return arrow; | ||||
| } | ||||
|  | ||||
| const PopupBaseMenuItem = new Lang.Class({ | ||||
| @@ -111,6 +118,7 @@ const PopupBaseMenuItem = new Lang.Class({ | ||||
|  | ||||
|         this.actor.connect('key-focus-in', Lang.bind(this, this._onKeyFocusIn)); | ||||
|         this.actor.connect('key-focus-out', Lang.bind(this, this._onKeyFocusOut)); | ||||
|         this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); | ||||
|     }, | ||||
|  | ||||
|     _getTopMenu: function() { | ||||
| @@ -126,7 +134,7 @@ const PopupBaseMenuItem = new Lang.Class({ | ||||
|  | ||||
|     _onButtonReleaseEvent: function (actor, event) { | ||||
|         this.activate(event); | ||||
|         return true; | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     _onKeyPressEvent: function (actor, event) { | ||||
| @@ -134,9 +142,9 @@ const PopupBaseMenuItem = new Lang.Class({ | ||||
|  | ||||
|         if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return) { | ||||
|             this.activate(event); | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } | ||||
|         return false; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onKeyFocusIn: function (actor) { | ||||
| @@ -192,6 +200,9 @@ const PopupBaseMenuItem = new Lang.Class({ | ||||
|  | ||||
|     destroy: function() { | ||||
|         this.actor.destroy(); | ||||
|     }, | ||||
|  | ||||
|     _onDestroy: function() { | ||||
|         this.emit('destroy'); | ||||
|     }, | ||||
|  | ||||
| @@ -868,14 +879,14 @@ const PopupSubMenu = new Lang.Class({ | ||||
|         if (animate) { | ||||
|             let [minHeight, naturalHeight] = this.actor.get_preferred_height(-1); | ||||
|             this.actor.height = 0; | ||||
|             this.actor._arrow_rotation = this._arrow.rotation_angle_z; | ||||
|             this.actor._arrowRotation = this._arrow.rotation_angle_z; | ||||
|             Tweener.addTween(this.actor, | ||||
|                              { _arrow_rotation: 90, | ||||
|                              { _arrowRotation: this.actor._arrowRotation + 90, | ||||
|                                height: naturalHeight, | ||||
|                                time: 0.25, | ||||
|                                onUpdateScope: this, | ||||
|                                onUpdate: function() { | ||||
|                                    this._arrow.rotation_angle_z = this.actor._arrow_rotation; | ||||
|                                    this._arrow.rotation_angle_z = this.actor._arrowRotation; | ||||
|                                }, | ||||
|                                onCompleteScope: this, | ||||
|                                onComplete: function() { | ||||
| @@ -883,7 +894,7 @@ const PopupSubMenu = new Lang.Class({ | ||||
|                                } | ||||
|                              }); | ||||
|         } else { | ||||
|             this._arrow.rotation_angle_z = 90; | ||||
|             this._arrow.rotation_angle_z = this.actor._arrowRotation + 90; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -901,14 +912,14 @@ const PopupSubMenu = new Lang.Class({ | ||||
|             animate = false; | ||||
|  | ||||
|         if (animate) { | ||||
|             this.actor._arrow_rotation = this._arrow.rotation_angle_z; | ||||
|             this.actor._arrowRotation = this._arrow.rotation_angle_z; | ||||
|             Tweener.addTween(this.actor, | ||||
|                              { _arrow_rotation: 0, | ||||
|                              { _arrowRotation: this.actor._arrowRotation - 90, | ||||
|                                height: 0, | ||||
|                                time: 0.25, | ||||
|                                onUpdateScope: this, | ||||
|                                onUpdate: function() { | ||||
|                                    this._arrow.rotation_angle_z = this.actor._arrow_rotation; | ||||
|                                    this._arrow.rotation_angle_z = this.actor._arrowRotation; | ||||
|                                }, | ||||
|                                onCompleteScope: this, | ||||
|                                onComplete: function() { | ||||
| @@ -917,7 +928,7 @@ const PopupSubMenu = new Lang.Class({ | ||||
|                                }, | ||||
|                              }); | ||||
|         } else { | ||||
|             this._arrow.rotation_angle_z = 0; | ||||
|             this._arrow.rotation_angle_z = this.actor._arrowRotation - 90; | ||||
|             this.actor.hide(); | ||||
|         } | ||||
|     }, | ||||
| @@ -928,10 +939,10 @@ const PopupSubMenu = new Lang.Class({ | ||||
|         if (this.isOpen && event.get_key_symbol() == Clutter.KEY_Left) { | ||||
|             this.close(BoxPointer.PopupAnimation.FULL); | ||||
|             this.sourceActor._delegate.setActive(true); | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -989,7 +1000,7 @@ const PopupSubMenuMenuItem = new Lang.Class({ | ||||
|                                      y_align: Clutter.ActorAlign.CENTER }); | ||||
|         this.actor.add_child(this.status); | ||||
|  | ||||
|         this._triangle = unicodeArrow(St.Side.RIGHT); | ||||
|         this._triangle = arrowIcon(St.Side.RIGHT); | ||||
|         this._triangle.pivot_point = new Clutter.Point({ x: 0.5, y: 0.6 }); | ||||
|  | ||||
|         this._triangleBin = new St.Widget({ y_expand: true, | ||||
| @@ -1056,10 +1067,10 @@ const PopupSubMenuMenuItem = new Lang.Class({ | ||||
|         if (symbol == Clutter.KEY_Right) { | ||||
|             this._setOpenState(true); | ||||
|             this.menu.actor.navigate_focus(null, Gtk.DirectionType.DOWN, false); | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } else if (symbol == Clutter.KEY_Left && this._getOpenState()) { | ||||
|             this._setOpenState(false); | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } | ||||
|  | ||||
|         return this.parent(actor, event); | ||||
| @@ -1071,6 +1082,7 @@ const PopupSubMenuMenuItem = new Lang.Class({ | ||||
|  | ||||
|     _onButtonReleaseEvent: function(actor) { | ||||
|         this._setOpenState(!this._getOpenState()); | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -1102,7 +1114,7 @@ const PopupMenuManager = new Lang.Class({ | ||||
|         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() { return this._onMenuSourceEnter(menu); })); | ||||
|             menudata.focusInId = source.connect('key-focus-in', Lang.bind(this, function() { this._onMenuSourceEnter(menu); })); | ||||
|         } | ||||
|  | ||||
| @@ -1114,7 +1126,7 @@ const PopupMenuManager = new Lang.Class({ | ||||
|  | ||||
|     removeMenu: function(menu) { | ||||
|         if (menu == this.activeMenu) | ||||
|             this._closeMenu(menu); | ||||
|             this._closeMenu(false, menu); | ||||
|  | ||||
|         let position = this._findMenu(menu); | ||||
|         if (position == -1) // not a menu we manage | ||||
| @@ -1164,13 +1176,13 @@ const PopupMenuManager = new Lang.Class({ | ||||
|  | ||||
|     _onMenuSourceEnter: function(menu) { | ||||
|         if (!this._grabHelper.grabbed) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         if (this._grabHelper.isActorGrabbed(menu.actor)) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         this._changeMenu(menu); | ||||
|         return false; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onMenuDestroy: function(menu) { | ||||
|   | ||||
| @@ -7,58 +7,63 @@ const Lang = imports.lang; | ||||
| const St = imports.gi.St; | ||||
| const Shell = imports.gi.Shell; | ||||
|  | ||||
| const FileUtils = imports.misc.fileUtils; | ||||
| const Search = imports.ui.search; | ||||
|  | ||||
| const KEY_FILE_GROUP = 'Shell Search Provider'; | ||||
|  | ||||
| const SearchProviderIface = <interface name="org.gnome.Shell.SearchProvider"> | ||||
| <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" /> | ||||
| </method> | ||||
| </interface>; | ||||
| const SearchProviderIface = '<node> \ | ||||
| <interface name="org.gnome.Shell.SearchProvider"> \ | ||||
| <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" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| 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>; | ||||
| const SearchProvider2Iface = '<node> \ | ||||
| <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> \ | ||||
| </node>'; | ||||
|  | ||||
| var SearchProviderProxy = Gio.DBusProxy.makeProxyWrapper(SearchProviderIface); | ||||
| var SearchProvider2Proxy = Gio.DBusProxy.makeProxyWrapper(SearchProvider2Iface); | ||||
| var SearchProviderProxyInfo = Gio.DBusInterfaceInfo.new_for_xml(SearchProviderIface); | ||||
| var SearchProvider2ProxyInfo = Gio.DBusInterfaceInfo.new_for_xml(SearchProvider2Iface); | ||||
|  | ||||
| function loadRemoteSearchProviders(addProviderCallback) { | ||||
| function loadRemoteSearchProviders(callback) { | ||||
|     let objectPaths = {}; | ||||
|     let loadedProviders = []; | ||||
|  | ||||
| @@ -112,30 +117,25 @@ function loadRemoteSearchProviders(addProviderCallback) { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     let dataDirs = GLib.get_system_data_dirs(); | ||||
|     dataDirs.forEach(function(dataDir) { | ||||
|         let path = GLib.build_filenamev([dataDir, 'gnome-shell', 'search-providers']); | ||||
|         let dir = Gio.File.new_for_path(path); | ||||
|         let fileEnum; | ||||
|         try { | ||||
|             fileEnum = dir.enumerate_children('standard::name,standard::type', | ||||
|                                               Gio.FileQueryInfoFlags.NONE, null); | ||||
|         } catch (e) { | ||||
|             fileEnum = null; | ||||
|         } | ||||
|         if (fileEnum != null) { | ||||
|             let info; | ||||
|             while ((info = fileEnum.next_file(null))) | ||||
|                 loadRemoteSearchProvider(fileEnum.get_child(info)); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     let searchSettings = new Gio.Settings({ schema: Search.SEARCH_PROVIDERS_SCHEMA }); | ||||
|     if (searchSettings.get_boolean('disable-external')) { | ||||
|         callback([]); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     FileUtils.collectFromDatadirs('search-providers', false, loadRemoteSearchProvider); | ||||
|  | ||||
|     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'); | ||||
|  | ||||
|     loadedProviders = loadedProviders.filter(function(provider) { | ||||
|         let appId = provider.appInfo.get_id(); | ||||
|         let disabled = searchSettings.get_strv('disabled'); | ||||
|         return disabled.indexOf(appId) == -1; | ||||
|     }); | ||||
|  | ||||
|     loadedProviders.sort(function(providerA, providerB) { | ||||
|         let idxA, idxB; | ||||
|         let appIdA, appIdB; | ||||
| @@ -166,32 +166,34 @@ function loadRemoteSearchProviders(addProviderCallback) { | ||||
|         return (idxA - idxB); | ||||
|     }); | ||||
|  | ||||
|     loadedProviders.forEach(addProviderCallback); | ||||
|     callback(loadedProviders); | ||||
| } | ||||
|  | ||||
| const RemoteSearchProvider = new Lang.Class({ | ||||
|     Name: 'RemoteSearchProvider', | ||||
|  | ||||
|     _init: function(appInfo, dbusName, dbusPath, proxyType) { | ||||
|         if (!proxyType) | ||||
|             proxyType = SearchProviderProxy; | ||||
|     _init: function(appInfo, dbusName, dbusPath, proxyInfo) { | ||||
|         if (!proxyInfo) | ||||
|             proxyInfo = SearchProviderProxyInfo; | ||||
|  | ||||
|         this.proxy = new proxyType(Gio.DBus.session, | ||||
|                 dbusName, dbusPath, Lang.bind(this, this._onProxyConstructed)); | ||||
|         this.proxy = new Gio.DBusProxy({ g_bus_type: Gio.BusType.SESSION, | ||||
|                                          g_name: dbusName, | ||||
|                                          g_object_path: dbusPath, | ||||
|                                          g_interface_info: proxyInfo, | ||||
|                                          g_interface_name: proxyInfo.name, | ||||
|                                          g_flags: (Gio.DBusProxyFlags.DO_NOT_AUTO_START_AT_CONSTRUCTION | | ||||
|                                                    Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES) }); | ||||
|         this.proxy.init_async(GLib.PRIORITY_DEFAULT, null, null); | ||||
|  | ||||
|         this.appInfo = appInfo; | ||||
|         this.id = appInfo.get_id(); | ||||
|         this.isRemoteProvider = true; | ||||
|  | ||||
|         this._cancellable = new Gio.Cancellable(); | ||||
|     }, | ||||
|  | ||||
|     _onProxyConstructed: function(proxy) { | ||||
|         // Do nothing | ||||
|     }, | ||||
|  | ||||
|     createIcon: function(size, meta) { | ||||
|         let gicon; | ||||
|         let gicon = null; | ||||
|         let icon = null; | ||||
|  | ||||
|         if (meta['icon']) { | ||||
|             gicon = Gio.icon_deserialize(meta['icon']); | ||||
|         } else if (meta['gicon']) { | ||||
| @@ -203,8 +205,10 @@ const RemoteSearchProvider = new Lang.Class({ | ||||
|                                                        bitsPerSample, width, height, rowStride); | ||||
|         } | ||||
|  | ||||
|         return new St.Icon({ gicon: gicon, | ||||
|                              icon_size: size }); | ||||
|         if (gicon) | ||||
|             icon = new St.Icon({ gicon: gicon, | ||||
|                                  icon_size: size }); | ||||
|         return icon; | ||||
|     }, | ||||
|  | ||||
|     filterResults: function(results, maxNumber) { | ||||
| @@ -217,40 +221,33 @@ const RemoteSearchProvider = new Lang.Class({ | ||||
|         return regularResults.slice(0, maxNumber).concat(specialResults.slice(0, maxNumber)); | ||||
|     }, | ||||
|  | ||||
|     _getResultsFinished: function(results, error) { | ||||
|         if (error) | ||||
|     _getResultsFinished: function(results, error, callback) { | ||||
|         if (error) { | ||||
|             if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) | ||||
|                 log('Received error from DBus search provider %s: %s'.format(this.id, String(error))); | ||||
|             callback([]); | ||||
|             return; | ||||
|         this.searchSystem.setResults(this, results[0]); | ||||
|         } | ||||
|  | ||||
|         callback(results[0]); | ||||
|     }, | ||||
|  | ||||
|     getInitialResultSet: function(terms) { | ||||
|         this._cancellable.cancel(); | ||||
|         this._cancellable.reset(); | ||||
|         try { | ||||
|             this.proxy.GetInitialResultSetRemote(terms, | ||||
|                                                  Lang.bind(this, this._getResultsFinished), | ||||
|                                                  this._cancellable); | ||||
|         } catch(e) { | ||||
|             log('Error calling GetInitialResultSet for provider %s: %s'.format(this.id, e.toString())); | ||||
|             this.searchSystem.setResults(this, []); | ||||
|         } | ||||
|     getInitialResultSet: function(terms, callback, cancellable) { | ||||
|         this.proxy.GetInitialResultSetRemote(terms, | ||||
|                                              Lang.bind(this, this._getResultsFinished, callback), | ||||
|                                              cancellable); | ||||
|     }, | ||||
|  | ||||
|     getSubsearchResultSet: function(previousResults, newTerms) { | ||||
|         this._cancellable.cancel(); | ||||
|         this._cancellable.reset(); | ||||
|         try { | ||||
|             this.proxy.GetSubsearchResultSetRemote(previousResults, newTerms, | ||||
|                                                    Lang.bind(this, this._getResultsFinished), | ||||
|                                                    this._cancellable); | ||||
|         } catch(e) { | ||||
|             log('Error calling GetSubsearchResultSet for provider %s: %s'.format(this.id, e.toString())); | ||||
|             this.searchSystem.setResults(this, []); | ||||
|         } | ||||
|     getSubsearchResultSet: function(previousResults, newTerms, callback, cancellable) { | ||||
|         this.proxy.GetSubsearchResultSetRemote(previousResults, newTerms, | ||||
|                                                Lang.bind(this, this._getResultsFinished, callback), | ||||
|                                                cancellable); | ||||
|     }, | ||||
|  | ||||
|     _getResultMetasFinished: function(results, error, callback) { | ||||
|         if (error) { | ||||
|             if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) | ||||
|                 log('Received error from DBus search provider %s during GetResultMetas: %s'.format(this.id, String(error))); | ||||
|             callback([]); | ||||
|             return; | ||||
|         } | ||||
| @@ -272,17 +269,10 @@ const RemoteSearchProvider = new Lang.Class({ | ||||
|         callback(resultMetas); | ||||
|     }, | ||||
|  | ||||
|     getResultMetas: function(ids, callback) { | ||||
|         this._cancellable.cancel(); | ||||
|         this._cancellable.reset(); | ||||
|         try { | ||||
|             this.proxy.GetResultMetasRemote(ids, | ||||
|                                             Lang.bind(this, this._getResultMetasFinished, callback), | ||||
|                                             this._cancellable); | ||||
|         } catch(e) { | ||||
|             log('Error calling GetResultMetas for provider %s: %s'.format(this.id, e.toString())); | ||||
|             callback([]); | ||||
|         } | ||||
|     getResultMetas: function(ids, callback, cancellable) { | ||||
|         this.proxy.GetResultMetasRemote(ids, | ||||
|                                         Lang.bind(this, this._getResultMetasFinished, callback), | ||||
|                                         cancellable); | ||||
|     }, | ||||
|  | ||||
|     activateResult: function(id) { | ||||
| @@ -293,7 +283,7 @@ const RemoteSearchProvider = new Lang.Class({ | ||||
|         // 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()); | ||||
|         this.appInfo.launch([], global.create_app_launch_context(0, -1)); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -302,7 +292,7 @@ const RemoteSearchProvider2 = new Lang.Class({ | ||||
|     Extends: RemoteSearchProvider, | ||||
|  | ||||
|     _init: function(appInfo, dbusName, dbusPath) { | ||||
|         this.parent(appInfo, dbusName, dbusPath, SearchProvider2Proxy); | ||||
|         this.parent(appInfo, dbusName, dbusPath, SearchProvider2ProxyInfo); | ||||
|  | ||||
|         this.canLaunchSearch = true; | ||||
|     }, | ||||
|   | ||||
| @@ -73,7 +73,9 @@ const RunDialog = new Lang.Class({ | ||||
|         let label = new St.Label({ style_class: 'run-dialog-label', | ||||
|                                    text: _("Enter a Command") }); | ||||
|  | ||||
|         this.contentLayout.add(label, { y_align: St.Align.START }); | ||||
|         this.contentLayout.add(label, { x_fill: false, | ||||
|                                         x_align: St.Align.START, | ||||
|                                         y_align: St.Align.START }); | ||||
|  | ||||
|         let entry = new St.Entry({ style_class: 'run-dialog-entry', | ||||
|                                    can_focus: true }); | ||||
| @@ -101,6 +103,8 @@ const RunDialog = new Lang.Class({ | ||||
|         this._errorMessage.clutter_text.line_wrap = true; | ||||
|  | ||||
|         this._errorBox.add(this._errorMessage, { expand: true, | ||||
|                                                  x_align: St.Align.START, | ||||
|                                                  x_fill: false, | ||||
|                                                  y_align: St.Align.MIDDLE, | ||||
|                                                  y_fill: false }); | ||||
|  | ||||
| @@ -124,7 +128,7 @@ const RunDialog = new Lang.Class({ | ||||
|                     !this.pushModal()) | ||||
|                     this.close(); | ||||
|  | ||||
|                 return true; | ||||
|                 return Clutter.EVENT_STOP; | ||||
|             } | ||||
|             if (symbol == Clutter.Tab) { | ||||
|                 let text = o.get_text(); | ||||
| @@ -138,9 +142,9 @@ const RunDialog = new Lang.Class({ | ||||
|                     o.insert_text(postfix, -1); | ||||
|                     o.set_cursor_position(text.length + postfix.length); | ||||
|                 } | ||||
|                 return true; | ||||
|                 return Clutter.EVENT_STOP; | ||||
|             } | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
| @@ -229,7 +233,7 @@ const RunDialog = new Lang.Class({ | ||||
|                     let file = Gio.file_new_for_path(path); | ||||
|                     try { | ||||
|                         Gio.app_info_launch_default_for_uri(file.get_uri(), | ||||
|                                                             global.create_app_launch_context()); | ||||
|                                                             global.create_app_launch_context(0, -1)); | ||||
|                     } catch (e) { | ||||
|                         // The exception from gjs contains an error string like: | ||||
|                         //     Error invoking Gio.app_info_launch_default_for_uri: No application | ||||
|   | ||||
| @@ -17,7 +17,6 @@ const TweenerEquations = imports.tweener.equations; | ||||
|  | ||||
| const Background = imports.ui.background; | ||||
| const GnomeSession = imports.misc.gnomeSession; | ||||
| const Hash = imports.misc.hash; | ||||
| const Layout = imports.ui.layout; | ||||
| const OVirt = imports.gdm.oVirt; | ||||
| const LoginManager = imports.misc.loginManager; | ||||
| @@ -115,7 +114,7 @@ const NotificationsBox = new Lang.Class({ | ||||
|         this.actor.add(this._musicBin); | ||||
|         this.actor.add(this._scrollView, { x_fill: true, x_align: St.Align.START }); | ||||
|  | ||||
|         this._sources = new Hash.Map(); | ||||
|         this._sources = new Map(); | ||||
|         Main.messageTray.getSources().forEach(Lang.bind(this, function(source) { | ||||
|             this._sourceAdded(Main.messageTray, source, true); | ||||
|         })); | ||||
| @@ -130,9 +129,8 @@ const NotificationsBox = new Lang.Class({ | ||||
|             this._sourceAddedId = 0; | ||||
|         } | ||||
|  | ||||
|         let items = this._sources.items(); | ||||
|         for (let i = 0; i < items.length; i++) { | ||||
|             let [source, obj] = items[i]; | ||||
|         let items = this._sources.entries(); | ||||
|         for (let [source, obj] of items) { | ||||
|             this._removeSource(source, obj); | ||||
|         } | ||||
|  | ||||
| @@ -198,8 +196,8 @@ const NotificationsBox = new Lang.Class({ | ||||
|  | ||||
|             let body = ''; | ||||
|             if (n.bannerBodyText) { | ||||
|                 body = n.bannerBodyMarkup ? n.bannerBodyText : | ||||
|                 GLib.markup_escape_text(n.bannerBodyMarkup, -1); | ||||
|                 body = n.bannerBodyMarkup ? n.bannerBodyText | ||||
|                                           : GLib.markup_escape_text(n.bannerBodyText, -1); | ||||
|             } | ||||
|  | ||||
|             let label = new St.Label({ style_class: 'screen-shield-notification-count-text' }); | ||||
| @@ -240,10 +238,6 @@ const NotificationsBox = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _sourceAdded: function(tray, source, initial) { | ||||
|         // Ignore transient sources | ||||
|         if (source.isTransient) | ||||
|             return; | ||||
|  | ||||
|         let obj = { | ||||
|             visible: source.policy.showInLockScreen, | ||||
|             detailed: source.policy.detailsInLockScreen, | ||||
| @@ -307,7 +301,7 @@ const NotificationsBox = new Lang.Class({ | ||||
|                              }); | ||||
|  | ||||
|             this._updateVisibility(); | ||||
|             Shell.util_wake_up_screen(); | ||||
|             this.emit('wake-up-screen'); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -333,7 +327,7 @@ const NotificationsBox = new Lang.Class({ | ||||
|  | ||||
|         this._updateVisibility(); | ||||
|         if (obj.sourceBox.visible) | ||||
|             Shell.util_wake_up_screen(); | ||||
|             this.emit('wake-up-screen'); | ||||
|     }, | ||||
|  | ||||
|     _visibleChanged: function(source, obj) { | ||||
| @@ -348,7 +342,7 @@ const NotificationsBox = new Lang.Class({ | ||||
|  | ||||
|         this._updateVisibility(); | ||||
|         if (obj.sourceBox.visible) | ||||
|             Shell.util_wake_up_screen(); | ||||
|             this.emit('wake-up-screen'); | ||||
|     }, | ||||
|  | ||||
|     _detailedChanged: function(source, obj) { | ||||
| @@ -386,6 +380,7 @@ const NotificationsBox = new Lang.Class({ | ||||
|         this._sources.delete(source); | ||||
|     }, | ||||
| }); | ||||
| Signals.addSignalMethods(NotificationsBox.prototype); | ||||
|  | ||||
| const Arrow = new Lang.Class({ | ||||
|     Name: 'Arrow', | ||||
| @@ -419,6 +414,7 @@ const Arrow = new Lang.Class({ | ||||
|         cr.lineTo(w/2, thickness); | ||||
|         cr.lineTo(w - thickness / 2, h - thickness / 2); | ||||
|         cr.stroke(); | ||||
|         cr.$dispose(); | ||||
|     }, | ||||
|  | ||||
|     vfunc_style_changed: function() { | ||||
| @@ -541,7 +537,7 @@ const ScreenShield = new Lang.Class({ | ||||
|  | ||||
|         this._smartcardManager = SmartcardManager.getSmartcardManager(); | ||||
|         this._smartcardManager.connect('smartcard-inserted', | ||||
|                                        Lang.bind(this, function(token) { | ||||
|                                        Lang.bind(this, function(manager, token) { | ||||
|                                            if (this._isLocked && token.UsedToLogin) | ||||
|                                                this._liftShield(true, 0); | ||||
|                                        })); | ||||
| @@ -673,11 +669,11 @@ const ScreenShield = new Lang.Class({ | ||||
|         // down after cancel. | ||||
|  | ||||
|         if (this._lockScreenState != MessageTray.State.SHOWN) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         let isEnter = (symbol == Clutter.KEY_Return || symbol == Clutter.KEY_KP_Enter); | ||||
|         if (!isEnter && !(GLib.unichar_isprint(unichar) || symbol == Clutter.KEY_Escape)) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         if (this._isLocked && | ||||
|             this._ensureUnlockDialog(true, true) && | ||||
| @@ -685,12 +681,12 @@ const ScreenShield = new Lang.Class({ | ||||
|             this._dialog.addCharacter(unichar); | ||||
|  | ||||
|         this._liftShield(true, 0); | ||||
|         return true; | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     _onLockScreenScroll: function(actor, event) { | ||||
|         if (this._lockScreenState != MessageTray.State.SHOWN) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         let delta = 0; | ||||
|         if (event.get_scroll_direction() == Clutter.ScrollDirection.UP) | ||||
| @@ -705,7 +701,7 @@ const ScreenShield = new Lang.Class({ | ||||
|             this._liftShield(true, 0); | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     _inhibitSuspend: function() { | ||||
| @@ -733,7 +729,7 @@ const ScreenShield = new Lang.Class({ | ||||
|         } else { | ||||
|             this._inhibitSuspend(); | ||||
|  | ||||
|             this._onUserBecameActive(); | ||||
|             this._wakeUpScreen(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -756,7 +752,7 @@ const ScreenShield = new Lang.Class({ | ||||
|                              }); | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|         return GLib.SOURCE_CONTINUE; | ||||
|     }, | ||||
|  | ||||
|     _onDragBegin: function() { | ||||
| @@ -852,7 +848,7 @@ const ScreenShield = new Lang.Class({ | ||||
|                                                        Lang.bind(this, function() { | ||||
|                                                            this._lockTimeoutId = 0; | ||||
|                                                            this.lock(false); | ||||
|                                                            return false; | ||||
|                                                            return GLib.SOURCE_REMOVE; | ||||
|                                                        })); | ||||
|         } | ||||
|  | ||||
| @@ -905,17 +901,11 @@ const ScreenShield = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     showDialog: function() { | ||||
|         // Ensure that the stage window is mapped, before taking a grab | ||||
|         // otherwise X errors out | ||||
|         Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { | ||||
|             if (!this._becomeModal()) { | ||||
|                 // In the login screen, this is a hard error. Fail-whale | ||||
|                 log('Could not acquire modal grab for the login screen. Aborting login process.'); | ||||
|                 Meta.quit(Meta.ExitCode.ERROR); | ||||
|             } | ||||
|  | ||||
|             return false; | ||||
|         })); | ||||
|         if (!this._becomeModal()) { | ||||
|             // In the login screen, this is a hard error. Fail-whale | ||||
|             log('Could not acquire modal grab for the login screen. Aborting login process.'); | ||||
|             Meta.quit(Meta.ExitCode.ERROR); | ||||
|         } | ||||
|  | ||||
|         this.actor.show(); | ||||
|         this._isGreeter = Main.sessionMode.isGreeter; | ||||
| @@ -1101,7 +1091,7 @@ const ScreenShield = new Lang.Class({ | ||||
|                 global.stage.disconnect(motionId); | ||||
|             } | ||||
|  | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|         })); | ||||
|         this._cursorTracker.set_pointer_visible(false); | ||||
|  | ||||
| @@ -1114,6 +1104,7 @@ const ScreenShield = new Lang.Class({ | ||||
|  | ||||
|             Mainloop.timeout_add(1000 * MANUAL_FADE_TIME, Lang.bind(this, function() { | ||||
|                 this._activateFade(this._shortLightbox, MANUAL_FADE_TIME); | ||||
|                 return GLib.SOURCE_REMOVE; | ||||
|             })); | ||||
|         } else { | ||||
|             if (params.fadeToBlack) | ||||
| @@ -1155,6 +1146,7 @@ const ScreenShield = new Lang.Class({ | ||||
|         this._lockScreenContents.add_actor(this._lockScreenContentsBox); | ||||
|  | ||||
|         this._notificationsBox = new NotificationsBox(); | ||||
|         this._wakeUpScreenId = this._notificationsBox.connect('wake-up-screen', Lang.bind(this, this._wakeUpScreen)); | ||||
|         this._lockScreenContentsBox.add(this._notificationsBox.actor, { x_fill: true, | ||||
|                                                                         y_fill: true, | ||||
|                                                                         expand: true }); | ||||
| @@ -1162,11 +1154,17 @@ const ScreenShield = new Lang.Class({ | ||||
|         this._hasLockScreen = true; | ||||
|     }, | ||||
|  | ||||
|     _wakeUpScreen: function() { | ||||
|         this._onUserBecameActive(); | ||||
|         this.emit('wake-up-screen'); | ||||
|     }, | ||||
|  | ||||
|     _clearLockScreen: function() { | ||||
|         this._clock.destroy(); | ||||
|         this._clock = null; | ||||
|  | ||||
|         if (this._notificationsBox) { | ||||
|             this._notificationsBox.disconnect(this._wakeUpScreenId); | ||||
|             this._notificationsBox.destroy(); | ||||
|             this._notificationsBox = null; | ||||
|         } | ||||
|   | ||||
| @@ -6,30 +6,31 @@ const Lang = imports.lang; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const Hash = imports.misc.hash; | ||||
| const Main = imports.ui.main; | ||||
|  | ||||
| const ScreencastIface = <interface name="org.gnome.Shell.Screencast"> | ||||
| <method name="Screencast"> | ||||
|     <arg type="s" direction="in" name="file_template"/> | ||||
|     <arg type="a{sv}" direction="in" name="options"/> | ||||
|     <arg type="b" direction="out" name="success"/> | ||||
|     <arg type="s" direction="out" name="filename_used"/> | ||||
| </method> | ||||
| <method name="ScreencastArea"> | ||||
|     <arg type="i" direction="in" name="x"/> | ||||
|     <arg type="i" direction="in" name="y"/> | ||||
|     <arg type="i" direction="in" name="width"/> | ||||
|     <arg type="i" direction="in" name="height"/> | ||||
|     <arg type="s" direction="in" name="file_template"/> | ||||
|     <arg type="a{sv}" direction="in" name="options"/> | ||||
|     <arg type="b" direction="out" name="success"/> | ||||
|     <arg type="s" direction="out" name="filename_used"/> | ||||
| </method> | ||||
| <method name="StopScreencast"> | ||||
|     <arg type="b" direction="out" name="success"/> | ||||
| </method> | ||||
| </interface>; | ||||
| const ScreencastIface = '<node> \ | ||||
| <interface name="org.gnome.Shell.Screencast"> \ | ||||
| <method name="Screencast"> \ | ||||
|     <arg type="s" direction="in" name="file_template"/> \ | ||||
|     <arg type="a{sv}" direction="in" name="options"/> \ | ||||
|     <arg type="b" direction="out" name="success"/> \ | ||||
|     <arg type="s" direction="out" name="filename_used"/> \ | ||||
| </method> \ | ||||
| <method name="ScreencastArea"> \ | ||||
|     <arg type="i" direction="in" name="x"/> \ | ||||
|     <arg type="i" direction="in" name="y"/> \ | ||||
|     <arg type="i" direction="in" name="width"/> \ | ||||
|     <arg type="i" direction="in" name="height"/> \ | ||||
|     <arg type="s" direction="in" name="file_template"/> \ | ||||
|     <arg type="a{sv}" direction="in" name="options"/> \ | ||||
|     <arg type="b" direction="out" name="success"/> \ | ||||
|     <arg type="s" direction="out" name="filename_used"/> \ | ||||
| </method> \ | ||||
| <method name="StopScreencast"> \ | ||||
|     <arg type="b" direction="out" name="success"/> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const ScreencastService = new Lang.Class({ | ||||
|     Name: 'ScreencastService', | ||||
| @@ -40,13 +41,13 @@ const ScreencastService = new Lang.Class({ | ||||
|  | ||||
|         Gio.DBus.session.own_name('org.gnome.Shell.Screencast', Gio.BusNameOwnerFlags.REPLACE, null, null); | ||||
|  | ||||
|         this._recorders = new Hash.Map(); | ||||
|         this._recorders = new Map(); | ||||
|  | ||||
|         Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated)); | ||||
|     }, | ||||
|  | ||||
|     get isRecording() { | ||||
|         return this._recorders.size() > 0; | ||||
|         return this._recorders.size > 0; | ||||
|     }, | ||||
|  | ||||
|     _ensureRecorderForSender: function(sender) { | ||||
| @@ -67,8 +68,7 @@ const ScreencastService = new Lang.Class({ | ||||
|         if (Main.sessionMode.allowScreencast) | ||||
|             return; | ||||
|  | ||||
|         for (let sender in this._recorders.keys()) | ||||
|             this._recorders.delete(sender); | ||||
|         this._recorders.clear(); | ||||
|         this.emit('updated'); | ||||
|     }, | ||||
|  | ||||
| @@ -103,8 +103,10 @@ const ScreencastService = new Lang.Class({ | ||||
|  | ||||
|     ScreencastAsync: function(params, invocation) { | ||||
|         let returnValue = [false, '']; | ||||
|         if (!Main.sessionMode.allowScreencast) | ||||
|         if (!Main.sessionMode.allowScreencast) { | ||||
|             invocation.return_value(GLib.Variant.new('(bs)', returnValue)); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let sender = invocation.get_sender(); | ||||
|         let recorder = this._ensureRecorderForSender(sender); | ||||
| @@ -122,8 +124,10 @@ const ScreencastService = new Lang.Class({ | ||||
|  | ||||
|     ScreencastAreaAsync: function(params, invocation) { | ||||
|         let returnValue = [false, '']; | ||||
|         if (!Main.sessionMode.allowScreencast) | ||||
|         if (!Main.sessionMode.allowScreencast) { | ||||
|             invocation.return_value(GLib.Variant.new('(bs)', returnValue)); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let sender = invocation.get_sender(); | ||||
|         let recorder = this._ensureRecorderForSender(sender); | ||||
| @@ -131,6 +135,16 @@ const ScreencastService = new Lang.Class({ | ||||
|         if (!recorder.is_recording()) { | ||||
|             let [x, y, width, height, fileTemplate, options] = params; | ||||
|  | ||||
|             if (x < 0 || y < 0 || | ||||
|                 width <= 0 || height <= 0 || | ||||
|                 x + width > global.screen_width || | ||||
|                 y + height > global.screen_height) { | ||||
|                 invocation.return_error_literal(Gio.IOErrorEnum, | ||||
|                                                 Gio.IOErrorEnum.CANCELLED, | ||||
|                                                 "Invalid params"); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             recorder.set_file_template(fileTemplate); | ||||
|             recorder.set_area(x, y, width, height); | ||||
|             this._applyOptionalParameters(recorder, options); | ||||
|   | ||||
| @@ -15,45 +15,47 @@ 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 ScreenshotIface = '<node> \ | ||||
| <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> \ | ||||
| </node>'; | ||||
|  | ||||
| const ScreenshotService = new Lang.Class({ | ||||
|     Name: 'ScreenshotService', | ||||
| @@ -65,6 +67,13 @@ const ScreenshotService = new Lang.Class({ | ||||
|         Gio.DBus.session.own_name('org.gnome.Shell.Screenshot', Gio.BusNameOwnerFlags.REPLACE, null, null); | ||||
|     }, | ||||
|  | ||||
|     _checkArea: function(x, y, width, height) { | ||||
|         return x >= 0 && y >= 0 && | ||||
|                width > 0 && height > 0 && | ||||
|                x + width <= global.screen_width && | ||||
|                y + height <= global.screen_height; | ||||
|     }, | ||||
|  | ||||
|     _onScreenshotComplete: function(obj, result, area, filenameUsed, flash, invocation) { | ||||
|         if (flash && result) { | ||||
|             let flashspot = new Flashspot(area); | ||||
| @@ -75,11 +84,31 @@ const ScreenshotService = new Lang.Class({ | ||||
|         invocation.return_value(retval); | ||||
|     }, | ||||
|  | ||||
|     _scaleArea: function(x, y, width, height) { | ||||
|         let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; | ||||
|         x *= scaleFactor; | ||||
|         y *= scaleFactor; | ||||
|         width *= scaleFactor; | ||||
|         height *= scaleFactor; | ||||
|         return [x, y, width, height]; | ||||
|     }, | ||||
|  | ||||
|     _unscaleArea: function(x, y, width, height) { | ||||
|         let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; | ||||
|         x /= scaleFactor; | ||||
|         y /= scaleFactor; | ||||
|         width /= scaleFactor; | ||||
|         height /= scaleFactor; | ||||
|         return [x, y, width, height]; | ||||
|     }, | ||||
|  | ||||
|     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"); | ||||
|         [x, y, width, height] = this._scaleArea(x, y, width, height); | ||||
|         if (!this._checkArea(x, y, width, height)) { | ||||
|             invocation.return_error_literal(Gio.IOErrorEnum, | ||||
|                                             Gio.IOErrorEnum.CANCELLED, | ||||
|                                             "Invalid params"); | ||||
|             return; | ||||
|         } | ||||
|         let screenshot = new Shell.Screenshot(); | ||||
| @@ -110,9 +139,9 @@ const ScreenshotService = new Lang.Class({ | ||||
|         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]); | ||||
|                     let retRectangle = this._unscaleArea(areaRectangle.x, areaRectangle.y, | ||||
|                         areaRectangle.width, areaRectangle.height); | ||||
|                     let retval = GLib.Variant.new('(iiii)', retRectangle); | ||||
|                     invocation.return_value(retval); | ||||
|                 } else { | ||||
|                     invocation.return_error_literal(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED, | ||||
| @@ -121,9 +150,18 @@ const ScreenshotService = new Lang.Class({ | ||||
|             })); | ||||
|     }, | ||||
|  | ||||
|     FlashArea: function(x, y, width, height) { | ||||
|     FlashAreaAsync: function(params, invocation) { | ||||
|         let [x, y, width, height] = params; | ||||
|         [x, y, width, height] = this._scaleArea(x, y, width, height); | ||||
|         if (!this._checkArea(x, y, width, height)) { | ||||
|             invocation.return_error_literal(Gio.IOErrorEnum, | ||||
|                                             Gio.IOErrorEnum.CANCELLED, | ||||
|                                             "Invalid params"); | ||||
|             return; | ||||
|         } | ||||
|         let flashspot = new Flashspot({ x : x, y : y, width: width, height: height}); | ||||
|         flashspot.fire(); | ||||
|         invocation.return_value(null); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -202,12 +240,12 @@ const SelectArea = new Lang.Class({ | ||||
|         if (event.get_key_symbol() == Clutter.Escape) | ||||
|             this._destroy(null, false); | ||||
|  | ||||
|         return; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onMotionEvent: function(actor, event) { | ||||
|         if (this._startX == -1 || this._startY == -1) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         [this._lastX, this._lastY] = event.get_coords(); | ||||
|         let geometry = this._getGeometry(); | ||||
| @@ -215,19 +253,19 @@ const SelectArea = new Lang.Class({ | ||||
|         this._rubberband.set_position(geometry.x, geometry.y); | ||||
|         this._rubberband.set_size(geometry.width, geometry.height); | ||||
|  | ||||
|         return false; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onButtonPress: function(actor, event) { | ||||
|         [this._startX, this._startY] = event.get_coords(); | ||||
|         this._rubberband.set_position(this._startX, this._startY); | ||||
|  | ||||
|         return false; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onButtonRelease: function(actor, event) { | ||||
|         this._destroy(this._getGeometry(), true); | ||||
|         return false; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _destroy: function(geometry, fade) { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Shell = imports.gi.Shell; | ||||
| @@ -41,7 +42,7 @@ function sleep(milliseconds) { | ||||
|     Mainloop.timeout_add(milliseconds, function() { | ||||
|                              if (cb) | ||||
|                                  cb(); | ||||
|                              return false; | ||||
|                              return GLib.SOURCE_REMOVE; | ||||
|                          }); | ||||
|  | ||||
|     return function(callback) { | ||||
| @@ -69,16 +70,18 @@ function waitLeisure() { | ||||
|     }; | ||||
| } | ||||
|  | ||||
| const PerfHelperIface = <interface name="org.gnome.Shell.PerfHelper"> | ||||
| <method name="CreateWindow"> | ||||
|     <arg type="i" direction="in" /> | ||||
|     <arg type="i" direction="in" /> | ||||
|     <arg type="b" direction="in" /> | ||||
|     <arg type="b" direction="in" /> | ||||
| </method> | ||||
| <method name="WaitWindows" /> | ||||
| <method name="DestroyWindows" /> | ||||
| </interface>; | ||||
| const PerfHelperIface = '<node> \ | ||||
| <interface name="org.gnome.Shell.PerfHelper"> \ | ||||
| <method name="CreateWindow"> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="b" direction="in" /> \ | ||||
|     <arg type="b" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="WaitWindows" /> \ | ||||
| <method name="DestroyWindows" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| var PerfHelperProxy = Gio.DBusProxy.makeProxyWrapper(PerfHelperIface); | ||||
| function PerfHelper() { | ||||
|   | ||||
							
								
								
									
										738
									
								
								js/ui/search.js
									
									
									
									
									
								
							
							
						
						
									
										738
									
								
								js/ui/search.js
									
									
									
									
									
								
							| @@ -1,105 +1,729 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Lang = imports.lang; | ||||
| const Gio = imports.gi.Gio; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Signals = imports.signals; | ||||
| const St = imports.gi.St; | ||||
| const Atk = imports.gi.Atk; | ||||
|  | ||||
| const AppDisplay = imports.ui.appDisplay; | ||||
| const DND = imports.ui.dnd; | ||||
| const IconGrid = imports.ui.iconGrid; | ||||
| const Main = imports.ui.main; | ||||
| const Overview = imports.ui.overview; | ||||
| const RemoteSearch = imports.ui.remoteSearch; | ||||
| const Separator = imports.ui.separator; | ||||
| const Util = imports.misc.util; | ||||
|  | ||||
| const SEARCH_PROVIDERS_SCHEMA = 'org.gnome.desktop.search-providers'; | ||||
|  | ||||
| const MAX_LIST_SEARCH_RESULTS_ROWS = 3; | ||||
| const MAX_GRID_SEARCH_RESULTS_ROWS = 1; | ||||
|  | ||||
| const SearchSystem = new Lang.Class({ | ||||
|     Name: 'SearchSystem', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._providers = []; | ||||
|         this._remoteProviders = []; | ||||
|         this.reset(); | ||||
|  | ||||
|         this._registerProvider(new AppDisplay.AppSearchProvider()); | ||||
|  | ||||
|         this._searchSettings = new Gio.Settings({ schema: 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)); | ||||
|  | ||||
|         this._reloadRemoteProviders(); | ||||
|  | ||||
|         this._cancellable = new Gio.Cancellable(); | ||||
|     }, | ||||
|  | ||||
|     registerProvider: function (provider) { | ||||
|         provider.searchSystem = this; | ||||
|     addProvider: function(provider) { | ||||
|         this._providers.push(provider); | ||||
|  | ||||
|         if (provider.isRemoteProvider) | ||||
|             this._remoteProviders.push(provider); | ||||
|         this.emit('providers-changed'); | ||||
|     }, | ||||
|  | ||||
|     unregisterProvider: function (provider) { | ||||
|     _reloadRemoteProviders: function() { | ||||
|         let remoteProviders = this._providers.filter(function(provider) { | ||||
|             return provider.isRemoteProvider; | ||||
|         }); | ||||
|         remoteProviders.forEach(Lang.bind(this, function(provider) { | ||||
|             this._unregisterProvider(provider); | ||||
|         })); | ||||
|  | ||||
|         RemoteSearch.loadRemoteSearchProviders(Lang.bind(this, function(providers) { | ||||
|             providers.forEach(Lang.bind(this, this._registerProvider)); | ||||
|         })); | ||||
|  | ||||
|         this.emit('providers-changed'); | ||||
|     }, | ||||
|  | ||||
|     _registerProvider: function (provider) { | ||||
|         this._providers.push(provider); | ||||
|     }, | ||||
|  | ||||
|     _unregisterProvider: function (provider) { | ||||
|         let index = this._providers.indexOf(provider); | ||||
|         if (index == -1) | ||||
|             return; | ||||
|         provider.searchSystem = null; | ||||
|         this._providers.splice(index, 1); | ||||
|  | ||||
|         let remoteIndex = this._remoteProviders.indexOf(provider); | ||||
|         if (remoteIndex != -1) | ||||
|             this._remoteProviders.splice(remoteIndex, 1); | ||||
|         if (provider.display) | ||||
|             provider.display.destroy(); | ||||
|     }, | ||||
|  | ||||
|     getProviders: function() { | ||||
|         return this._providers; | ||||
|     }, | ||||
|  | ||||
|     getRemoteProviders: function() { | ||||
|         return this._remoteProviders; | ||||
|     }, | ||||
|  | ||||
|     getTerms: function() { | ||||
|         return this._previousTerms; | ||||
|         return this._terms; | ||||
|     }, | ||||
|  | ||||
|     reset: function() { | ||||
|         this._previousTerms = []; | ||||
|         this._previousResults = []; | ||||
|         this._terms = []; | ||||
|         this._results = {}; | ||||
|     }, | ||||
|  | ||||
|     setResults: function(provider, results) { | ||||
|         let i = this._providers.indexOf(provider); | ||||
|         if (i == -1) | ||||
|             return; | ||||
|  | ||||
|         this._previousResults[i] = [provider, results]; | ||||
|         this.emit('search-updated', this._previousResults[i]); | ||||
|     _gotResults: function(results, provider) { | ||||
|         this._results[provider.id] = results; | ||||
|         this.emit('search-updated', provider, results); | ||||
|     }, | ||||
|  | ||||
|     updateSearchResults: function(terms) { | ||||
|     setTerms: function(terms) { | ||||
|         this._cancellable.cancel(); | ||||
|         this._cancellable.reset(); | ||||
|  | ||||
|         let previousResults = this._results; | ||||
|         let previousTerms = this._terms; | ||||
|         this.reset(); | ||||
|  | ||||
|         if (!terms) | ||||
|             return; | ||||
|  | ||||
|         let searchString = terms.join(' '); | ||||
|         let previousSearchString = this._previousTerms.join(' '); | ||||
|         let previousSearchString = previousTerms.join(' '); | ||||
|         if (searchString == previousSearchString) | ||||
|             return; | ||||
|  | ||||
|         let isSubSearch = false; | ||||
|         if (this._previousTerms.length > 0) | ||||
|         if (previousTerms.length > 0) | ||||
|             isSubSearch = searchString.indexOf(previousSearchString) == 0; | ||||
|  | ||||
|         let previousResultsArr = this._previousResults; | ||||
|         this._terms = terms; | ||||
|  | ||||
|         let results = []; | ||||
|         this._previousTerms = terms; | ||||
|         this._previousResults = results; | ||||
|  | ||||
|         if (isSubSearch) { | ||||
|             for (let i = 0; i < this._providers.length; i++) { | ||||
|                 let [provider, previousResults] = previousResultsArr[i]; | ||||
|                 try { | ||||
|                     results.push([provider, []]); | ||||
|                     provider.getSubsearchResultSet(previousResults, terms); | ||||
|                 } catch (error) { | ||||
|                     log('A ' + error.name + ' has occured in ' + provider.id + ': ' + error.message); | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             for (let i = 0; i < this._providers.length; i++) { | ||||
|                 let provider = this._providers[i]; | ||||
|                 try { | ||||
|                     results.push([provider, []]); | ||||
|                     provider.getInitialResultSet(terms); | ||||
|                 } catch (error) { | ||||
|                     log('A ' + error.name + ' has occured in ' + provider.id + ': ' + error.message); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         this._providers.forEach(Lang.bind(this, function(provider) { | ||||
|             let previousProviderResults = previousResults[provider.id]; | ||||
|             if (isSubSearch && previousProviderResults) | ||||
|                 provider.getSubsearchResultSet(previousProviderResults, terms, Lang.bind(this, this._gotResults, provider), this._cancellable); | ||||
|             else | ||||
|                 provider.getInitialResultSet(terms, Lang.bind(this, this._gotResults, provider), this._cancellable); | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(SearchSystem.prototype); | ||||
|  | ||||
| const MaxWidthBin = new Lang.Class({ | ||||
|     Name: 'MaxWidthBin', | ||||
|     Extends: St.Bin, | ||||
|  | ||||
|     vfunc_allocate: function(box, flags) { | ||||
|         let themeNode = this.get_theme_node(); | ||||
|         let maxWidth = themeNode.get_max_width(); | ||||
|         let availWidth = box.x2 - box.x1; | ||||
|         let adjustedBox = box; | ||||
|  | ||||
|         if (availWidth > maxWidth) { | ||||
|             let excessWidth = availWidth - maxWidth; | ||||
|             adjustedBox.x1 += Math.floor(excessWidth / 2); | ||||
|             adjustedBox.x2 -= Math.floor(excessWidth / 2); | ||||
|         } | ||||
|  | ||||
|         this.parent(adjustedBox, flags); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const SearchResult = new Lang.Class({ | ||||
|     Name: 'SearchResult', | ||||
|  | ||||
|     _init: function(provider, metaInfo) { | ||||
|         this.provider = provider; | ||||
|         this.metaInfo = metaInfo; | ||||
|  | ||||
|         this.actor = new St.Button({ reactive: true, | ||||
|                                      can_focus: true, | ||||
|                                      track_hover: true, | ||||
|                                      x_align: St.Align.START, | ||||
|                                      y_fill: true }); | ||||
|  | ||||
|         this.actor._delegate = this; | ||||
|         this.actor.connect('clicked', Lang.bind(this, this.activate)); | ||||
|     }, | ||||
|  | ||||
|     activate: function() { | ||||
|         this.emit('activate', this.metaInfo.id); | ||||
|     }, | ||||
|  | ||||
|     setSelected: function(selected) { | ||||
|         if (selected) | ||||
|             this.actor.add_style_pseudo_class('selected'); | ||||
|         else | ||||
|             this.actor.remove_style_pseudo_class('selected'); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(SearchResult.prototype); | ||||
|  | ||||
| const ListSearchResult = new Lang.Class({ | ||||
|     Name: 'ListSearchResult', | ||||
|     Extends: SearchResult, | ||||
|  | ||||
|     ICON_SIZE: 64, | ||||
|  | ||||
|     _init: function(provider, metaInfo) { | ||||
|         this.parent(provider, metaInfo); | ||||
|  | ||||
|         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) { | ||||
|         this.parent(provider, metaInfo); | ||||
|  | ||||
|         this.actor.style_class = 'grid-search-result'; | ||||
|  | ||||
|         let content = provider.createResultObject(metaInfo); | ||||
|         let dragSource = null; | ||||
|  | ||||
|         if (content == null) { | ||||
|             let actor = new St.Bin(); | ||||
|             let icon = new IconGrid.BaseIcon(this.metaInfo['name'], | ||||
|                                              { createIcon: this.metaInfo['createIcon'] }); | ||||
|             actor.set_child(icon.actor); | ||||
|             actor.label_actor = icon.label; | ||||
|             dragSource = icon.icon; | ||||
|             content = { actor: actor, icon: icon }; | ||||
|         } else { | ||||
|             if (content._delegate && content._delegate.getDragActorSource) | ||||
|                 dragSource = content._delegate.getDragActorSource(); | ||||
|         } | ||||
|  | ||||
|         this.actor.set_child(content.actor); | ||||
|         this.actor.label_actor = content.actor.label_actor; | ||||
|         this.icon = content.icon; | ||||
|  | ||||
|         let draggable = DND.makeDraggable(this.actor); | ||||
|         draggable.connect('drag-begin', | ||||
|                           Lang.bind(this, function() { | ||||
|                               Main.overview.beginItemDrag(this); | ||||
|                           })); | ||||
|         draggable.connect('drag-cancelled', | ||||
|                           Lang.bind(this, function() { | ||||
|                               Main.overview.cancelledItemDrag(this); | ||||
|                           })); | ||||
|         draggable.connect('drag-end', | ||||
|                           Lang.bind(this, function() { | ||||
|                               Main.overview.endItemDrag(this); | ||||
|                           })); | ||||
|  | ||||
|         if (!dragSource) | ||||
|             // not exactly right, but alignment problems are hard to notice | ||||
|             dragSource = content; | ||||
|         this._dragActorSource = dragSource; | ||||
|     }, | ||||
|  | ||||
|     getDragActorSource: function() { | ||||
|         return this._dragActorSource; | ||||
|     }, | ||||
|  | ||||
|     getDragActor: function() { | ||||
|         return this.metaInfo['createIcon'](Main.overview.dashIconSize); | ||||
|     }, | ||||
|  | ||||
|     shellWorkspaceLaunch: function(params) { | ||||
|         if (this.provider.dragActivateResult) | ||||
|             this.provider.dragActivateResult(this.metaInfo.id, params); | ||||
|         else | ||||
|             this.provider.activateResult(this.metaInfo.id, this.terms); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const SearchResultsBase = new Lang.Class({ | ||||
|     Name: 'SearchResultsBase', | ||||
|  | ||||
|     _init: function(provider) { | ||||
|         this.provider = provider; | ||||
|  | ||||
|         this._terms = []; | ||||
|  | ||||
|         this.actor = new St.BoxLayout({ style_class: 'search-section', | ||||
|                                         vertical: true }); | ||||
|  | ||||
|         this._resultDisplayBin = new St.Bin({ x_fill: true, | ||||
|                                               y_fill: true }); | ||||
|         this.actor.add(this._resultDisplayBin, { expand: true }); | ||||
|  | ||||
|         let separator = new Separator.HorizontalSeparator({ style_class: 'search-section-separator' }); | ||||
|         this.actor.add(separator.actor); | ||||
|  | ||||
|         this._resultDisplays = {}; | ||||
|  | ||||
|         this._cancellable = new Gio.Cancellable(); | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|         this.actor.destroy(); | ||||
|         this._terms = []; | ||||
|     }, | ||||
|  | ||||
|     _clearResultDisplay: function() { | ||||
|     }, | ||||
|  | ||||
|     clear: function() { | ||||
|         for (let resultId in this._resultDisplays) | ||||
|             this._resultDisplays[resultId].actor.destroy(); | ||||
|         this._resultDisplays = {}; | ||||
|         this._clearResultDisplay(); | ||||
|         this.actor.hide(); | ||||
|     }, | ||||
|  | ||||
|     _keyFocusIn: function(actor) { | ||||
|         this.emit('key-focus-in', actor); | ||||
|     }, | ||||
|  | ||||
|     _activateResult: function(result, id) { | ||||
|         this.provider.activateResult(id, this._terms); | ||||
|         Main.overview.toggle(); | ||||
|     }, | ||||
|  | ||||
|     _setMoreIconVisible: function(visible) { | ||||
|     }, | ||||
|  | ||||
|     _ensureResultActors: function(results, callback) { | ||||
|         let metasNeeded = results.filter(Lang.bind(this, function(resultId) { | ||||
|             return this._resultDisplays[resultId] === undefined; | ||||
|         })); | ||||
|  | ||||
|         if (metasNeeded.length === 0) { | ||||
|             callback(true); | ||||
|         } else { | ||||
|             this._cancellable.cancel(); | ||||
|             this._cancellable.reset(); | ||||
|  | ||||
|             this.provider.getResultMetas(metasNeeded, Lang.bind(this, function(metas) { | ||||
|                 if (metas.length == 0) { | ||||
|                     callback(false); | ||||
|                     return; | ||||
|                 } | ||||
|                 if (metas.length != metasNeeded.length) { | ||||
|                     log('Wrong number of result metas returned by search provider'); | ||||
|                     callback(false); | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 metasNeeded.forEach(Lang.bind(this, function(resultId, i) { | ||||
|                     let meta = metas[i]; | ||||
|                     let display = this._createResultDisplay(meta); | ||||
|                     display.connect('activate', Lang.bind(this, this._activateResult)); | ||||
|                     display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); | ||||
|                     this._resultDisplays[resultId] = display; | ||||
|                 })); | ||||
|                 callback(true); | ||||
|             }), this._cancellable); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     updateSearch: function(providerResults, terms, callback) { | ||||
|         this._terms = terms; | ||||
|  | ||||
|         if (providerResults.length == 0) { | ||||
|             this._clearResultDisplay(); | ||||
|             this.actor.hide(); | ||||
|             callback(); | ||||
|         } else { | ||||
|             let maxResults = this._getMaxDisplayedResults(); | ||||
|             let results = this.provider.filterResults(providerResults, maxResults); | ||||
|             let hasMoreResults = results.length < providerResults.length; | ||||
|  | ||||
|             this._ensureResultActors(results, Lang.bind(this, function(successful) { | ||||
|                 this._clearResultDisplay(); | ||||
|                 if (!successful) | ||||
|                     return; | ||||
|  | ||||
|                 // To avoid CSS transitions causing flickering when | ||||
|                 // the first search result stays the same, we hide the | ||||
|                 // content while filling in the results. | ||||
|                 this.actor.hide(); | ||||
|                 this._clearResultDisplay(); | ||||
|                 results.forEach(Lang.bind(this, function(resultId) { | ||||
|                     this._addItem(this._resultDisplays[resultId]); | ||||
|                 })); | ||||
|                 this._setMoreIconVisible(hasMoreResults && this.provider.canLaunchSearch); | ||||
|                 this.actor.show(); | ||||
|                 callback(); | ||||
|             })); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const ListSearchResults = new Lang.Class({ | ||||
|     Name: 'ListSearchResults', | ||||
|     Extends: SearchResultsBase, | ||||
|  | ||||
|     _init: function(provider) { | ||||
|         this.parent(provider); | ||||
|  | ||||
|         this._container = new St.BoxLayout({ style_class: 'search-section-content' }); | ||||
|         this.providerIcon = new ProviderIcon(provider); | ||||
|         this.providerIcon.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); | ||||
|         this.providerIcon.connect('clicked', Lang.bind(this, | ||||
|             function() { | ||||
|                 provider.launchSearch(this._terms); | ||||
|                 Main.overview.toggle(); | ||||
|             })); | ||||
|  | ||||
|         this._container.add(this.providerIcon, { x_fill: false, | ||||
|                                                  y_fill: false, | ||||
|                                                  x_align: St.Align.START, | ||||
|                                                  y_align: St.Align.START }); | ||||
|  | ||||
|         this._content = new St.BoxLayout({ style_class: 'list-search-results', | ||||
|                                            vertical: true }); | ||||
|         this._container.add(this._content, { expand: true }); | ||||
|  | ||||
|         this._resultDisplayBin.set_child(this._container); | ||||
|     }, | ||||
|  | ||||
|     _setMoreIconVisible: function(visible) { | ||||
|         this.providerIcon.moreIcon.visible = true; | ||||
|     }, | ||||
|  | ||||
|     _getMaxDisplayedResults: function() { | ||||
|         return MAX_LIST_SEARCH_RESULTS_ROWS; | ||||
|     }, | ||||
|  | ||||
|     _clearResultDisplay: function () { | ||||
|         this._content.remove_all_children(); | ||||
|     }, | ||||
|  | ||||
|     _createResultDisplay: function(meta) { | ||||
|         return new ListSearchResult(this.provider, meta); | ||||
|     }, | ||||
|  | ||||
|     _addItem: function(display) { | ||||
|         this._content.add_actor(display.actor); | ||||
|     }, | ||||
|  | ||||
|     getFirstResult: function() { | ||||
|         if (this._content.get_n_children() > 0) | ||||
|             return this._content.get_child_at_index(0)._delegate; | ||||
|         else | ||||
|             return null; | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(ListSearchResults.prototype); | ||||
|  | ||||
| const GridSearchResults = new Lang.Class({ | ||||
|     Name: 'GridSearchResults', | ||||
|     Extends: SearchResultsBase, | ||||
|  | ||||
|     _init: function(provider) { | ||||
|         this.parent(provider); | ||||
|  | ||||
|         this._grid = new IconGrid.IconGrid({ rowLimit: MAX_GRID_SEARCH_RESULTS_ROWS, | ||||
|                                              xAlign: St.Align.START }); | ||||
|         this._bin = new St.Bin({ x_align: St.Align.MIDDLE }); | ||||
|         this._bin.set_child(this._grid.actor); | ||||
|  | ||||
|         this._resultDisplayBin.set_child(this._bin); | ||||
|     }, | ||||
|  | ||||
|     _getMaxDisplayedResults: function() { | ||||
|         return this._grid.columnsForWidth(this._bin.width) * this._grid.getRowLimit(); | ||||
|     }, | ||||
|  | ||||
|     _renderResults: function(metas) { | ||||
|         for (let i = 0; i < metas.length; i++) { | ||||
|             let display = new GridSearchResult(this.provider, metas[i]); | ||||
|             display.connect('activate', Lang.bind(this, this._activateResult)); | ||||
|             display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); | ||||
|             this._grid.addItem(display); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _clearResultDisplay: function () { | ||||
|         this._grid.removeAll(); | ||||
|     }, | ||||
|  | ||||
|     _createResultDisplay: function(meta) { | ||||
|         return new GridSearchResult(this.provider, meta); | ||||
|     }, | ||||
|  | ||||
|     _addItem: function(display) { | ||||
|         this._grid.addItem(display); | ||||
|     }, | ||||
|  | ||||
|     getFirstResult: function() { | ||||
|         if (this._grid.visibleItemsCount() > 0) | ||||
|             return this._grid.getItemAtIndex(0)._delegate; | ||||
|         else | ||||
|             return null; | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(GridSearchResults.prototype); | ||||
|  | ||||
| const SearchResults = new Lang.Class({ | ||||
|     Name: 'SearchResults', | ||||
|  | ||||
|     _init: function() { | ||||
|         this.actor = new St.BoxLayout({ name: 'searchResults', | ||||
|                                         vertical: true }); | ||||
|  | ||||
|         this._content = new St.BoxLayout({ name: 'searchResultsContent', | ||||
|                                            vertical: true }); | ||||
|         this._contentBin = new MaxWidthBin({ name: 'searchResultsBin', | ||||
|                                              x_fill: true, | ||||
|                                              y_fill: true, | ||||
|                                              child: this._content }); | ||||
|  | ||||
|         let scrollChild = new St.BoxLayout(); | ||||
|         scrollChild.add(this._contentBin, { expand: true }); | ||||
|  | ||||
|         this._scrollView = new St.ScrollView({ x_fill: true, | ||||
|                                                y_fill: false, | ||||
|                                                overlay_scrollbars: true, | ||||
|                                                style_class: 'search-display vfade' }); | ||||
|         this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); | ||||
|         this._scrollView.add_actor(scrollChild); | ||||
|         let action = new Clutter.PanAction({ interpolate: true }); | ||||
|         action.connect('pan', Lang.bind(this, this._onPan)); | ||||
|         this._scrollView.add_action(action); | ||||
|  | ||||
|         this.actor.add(this._scrollView, { x_fill: true, | ||||
|                                            y_fill: true, | ||||
|                                            expand: true, | ||||
|                                            x_align: St.Align.START, | ||||
|                                            y_align: St.Align.START }); | ||||
|  | ||||
|         this._statusText = new St.Label({ style_class: 'search-statustext' }); | ||||
|         this._statusBin = new St.Bin({ x_align: St.Align.MIDDLE, | ||||
|                                        y_align: St.Align.MIDDLE }); | ||||
|         this._content.add(this._statusBin, { expand: true }); | ||||
|         this._statusBin.add_actor(this._statusText); | ||||
|  | ||||
|         this._highlightDefault = false; | ||||
|         this._defaultResult = null; | ||||
|  | ||||
|         this._searchSystem = new SearchSystem(); | ||||
|         this._searchSystem.connect('search-updated', Lang.bind(this, this._updateResults)); | ||||
|         this._searchSystem.connect('providers-changed', Lang.bind(this, this._updateProviderDisplays)); | ||||
|         this._updateProviderDisplays(); | ||||
|     }, | ||||
|  | ||||
|     _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, actor) { | ||||
|         Util.ensureActorVisibleInScrollView(this._scrollView, actor); | ||||
|     }, | ||||
|  | ||||
|     _ensureProviderDisplay: function(provider) { | ||||
|         if (provider.display) | ||||
|             return; | ||||
|  | ||||
|         let providerDisplay; | ||||
|         if (provider.appInfo) | ||||
|             providerDisplay = new ListSearchResults(provider); | ||||
|         else | ||||
|             providerDisplay = new GridSearchResults(provider); | ||||
|  | ||||
|         providerDisplay.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); | ||||
|         this._content.add(providerDisplay.actor); | ||||
|         provider.display = providerDisplay; | ||||
|     }, | ||||
|  | ||||
|     _updateProviderDisplays: function() { | ||||
|         this._searchSystem.getProviders().forEach(Lang.bind(this, this._ensureProviderDisplay)); | ||||
|     }, | ||||
|  | ||||
|     _clearDisplay: function() { | ||||
|         this._searchSystem.getProviders().forEach(function(provider) { | ||||
|             provider.display.clear(); | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     reset: function() { | ||||
|         this._searchSystem.reset(); | ||||
|         this._statusBin.hide(); | ||||
|         this._clearDisplay(); | ||||
|         this._defaultResult = null; | ||||
|     }, | ||||
|  | ||||
|     startingSearch: function() { | ||||
|         this.reset(); | ||||
|         this._statusText.set_text(_("Searching…")); | ||||
|         this._statusBin.show(); | ||||
|     }, | ||||
|  | ||||
|     setTerms: function(terms) { | ||||
|         this._searchSystem.setTerms(terms); | ||||
|     }, | ||||
|  | ||||
|     _maybeSetInitialSelection: function() { | ||||
|         let newDefaultResult = null; | ||||
|  | ||||
|         let providers = this._searchSystem.getProviders(); | ||||
|         for (let i = 0; i < providers.length; i++) { | ||||
|             let provider = providers[i]; | ||||
|             let display = provider.display; | ||||
|  | ||||
|             if (!display.actor.visible) | ||||
|                 continue; | ||||
|  | ||||
|             let firstResult = display.getFirstResult(); | ||||
|             if (firstResult) { | ||||
|                 newDefaultResult = firstResult; | ||||
|                 break; // select this one! | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (newDefaultResult != this._defaultResult) { | ||||
|             if (this._defaultResult) | ||||
|                 this._defaultResult.setSelected(false); | ||||
|             if (newDefaultResult) { | ||||
|                 newDefaultResult.setSelected(this._highlightDefault); | ||||
|                 if (this._highlightDefault) | ||||
|                     Util.ensureActorVisibleInScrollView(this._scrollView, newDefaultResult.actor); | ||||
|             } | ||||
|  | ||||
|             this._defaultResult = newDefaultResult; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateStatusText: function () { | ||||
|         let haveResults = this._searchSystem.getProviders().some(function(provider) { | ||||
|             let display = provider.display; | ||||
|             return (display.getFirstResult() != null); | ||||
|         }); | ||||
|  | ||||
|         if (!haveResults) { | ||||
|             this._statusText.set_text(_("No results.")); | ||||
|             this._statusBin.show(); | ||||
|         } else { | ||||
|             this._statusBin.hide(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateResults: function(searchSystem, provider, results) { | ||||
|         let terms = searchSystem.getTerms(); | ||||
|         let display = provider.display; | ||||
|  | ||||
|         display.updateSearch(results, terms, Lang.bind(this, function() { | ||||
|             this._maybeSetInitialSelection(); | ||||
|             this._updateStatusText(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     activateDefault: function() { | ||||
|         if (this._defaultResult) | ||||
|             this._defaultResult.activate(); | ||||
|     }, | ||||
|  | ||||
|     highlightDefault: function(highlight) { | ||||
|         this._highlightDefault = highlight; | ||||
|         if (this._defaultResult) { | ||||
|             this._defaultResult.setSelected(highlight); | ||||
|             if (highlight) | ||||
|                 Util.ensureActorVisibleInScrollView(this._scrollView, this._defaultResult.actor); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     navigateFocus: function(direction) { | ||||
|         let rtl = this.actor.get_text_direction() == Clutter.TextDirection.RTL; | ||||
|         if (direction == Gtk.DirectionType.TAB_BACKWARD || | ||||
|             direction == (rtl ? Gtk.DirectionType.RIGHT | ||||
|                               : Gtk.DirectionType.LEFT) || | ||||
|             direction == Gtk.DirectionType.UP) { | ||||
|             this.actor.navigate_focus(null, direction, false); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let from = this._defaultResult ? this._defaultResult.actor : null; | ||||
|         this.actor.navigate_focus(from, 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); | ||||
|     } | ||||
| }); | ||||
|   | ||||
| @@ -1,567 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Lang = imports.lang; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Signals = imports.signals; | ||||
| const St = imports.gi.St; | ||||
| const Atk = imports.gi.Atk; | ||||
|  | ||||
| const DND = imports.ui.dnd; | ||||
| const IconGrid = imports.ui.iconGrid; | ||||
| const Main = imports.ui.main; | ||||
| const Overview = imports.ui.overview; | ||||
| const Separator = imports.ui.separator; | ||||
| const Search = imports.ui.search; | ||||
| const Util = imports.misc.util; | ||||
|  | ||||
| const MAX_LIST_SEARCH_RESULTS_ROWS = 3; | ||||
| const MAX_GRID_SEARCH_RESULTS_ROWS = 1; | ||||
|  | ||||
| const MaxWidthBin = new Lang.Class({ | ||||
|     Name: 'MaxWidthBin', | ||||
|     Extends: St.Bin, | ||||
|  | ||||
|     vfunc_allocate: function(box, flags) { | ||||
|         let themeNode = this.get_theme_node(); | ||||
|         let maxWidth = themeNode.get_max_width(); | ||||
|         let availWidth = box.x2 - box.x1; | ||||
|         let adjustedBox = box; | ||||
|  | ||||
|         if (availWidth > maxWidth) { | ||||
|             let excessWidth = availWidth - maxWidth; | ||||
|             adjustedBox.x1 += Math.floor(excessWidth / 2); | ||||
|             adjustedBox.x2 -= Math.floor(excessWidth / 2); | ||||
|         } | ||||
|  | ||||
|         this.parent(adjustedBox, flags); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const SearchResult = new Lang.Class({ | ||||
|     Name: 'SearchResult', | ||||
|  | ||||
|     _init: function(provider, metaInfo, terms) { | ||||
|         this.provider = provider; | ||||
|         this.metaInfo = metaInfo; | ||||
|         this.terms = terms; | ||||
|  | ||||
|         this.actor = new St.Button({ reactive: true, | ||||
|                                      can_focus: true, | ||||
|                                      track_hover: true, | ||||
|                                      x_align: St.Align.START, | ||||
|                                      y_fill: true }); | ||||
|  | ||||
|         this.actor._delegate = this; | ||||
|         this.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.createResultObject(metaInfo, terms); | ||||
|         let dragSource = null; | ||||
|  | ||||
|         if (content == null) { | ||||
|             let actor = new St.Bin(); | ||||
|             let icon = new IconGrid.BaseIcon(this.metaInfo['name'], | ||||
|                                              { createIcon: this.metaInfo['createIcon'] }); | ||||
|             actor.set_child(icon.actor); | ||||
|             actor.label_actor = icon.label; | ||||
|             dragSource = icon.icon; | ||||
|             content = { actor: actor, icon: icon }; | ||||
|         } else { | ||||
|             if (content._delegate && content._delegate.getDragActorSource) | ||||
|                 dragSource = content._delegate.getDragActorSource(); | ||||
|         } | ||||
|  | ||||
|         this.actor.set_child(content.actor); | ||||
|         this.actor.label_actor = content.actor.label_actor; | ||||
|         this.icon = content.icon; | ||||
|  | ||||
|         let draggable = DND.makeDraggable(this.actor); | ||||
|         draggable.connect('drag-begin', | ||||
|                           Lang.bind(this, function() { | ||||
|                               Main.overview.beginItemDrag(this); | ||||
|                           })); | ||||
|         draggable.connect('drag-cancelled', | ||||
|                           Lang.bind(this, function() { | ||||
|                               Main.overview.cancelledItemDrag(this); | ||||
|                           })); | ||||
|         draggable.connect('drag-end', | ||||
|                           Lang.bind(this, function() { | ||||
|                               Main.overview.endItemDrag(this); | ||||
|                           })); | ||||
|  | ||||
|         if (!dragSource) | ||||
|             // not exactly right, but alignment problems are hard to notice | ||||
|             dragSource = content; | ||||
|         this._dragActorSource = dragSource; | ||||
|     }, | ||||
|  | ||||
|     getDragActorSource: function() { | ||||
|         return this._dragActorSource; | ||||
|     }, | ||||
|  | ||||
|     getDragActor: function() { | ||||
|         return this.metaInfo['createIcon'](Main.overview.dashIconSize); | ||||
|     }, | ||||
|  | ||||
|     shellWorkspaceLaunch: function(params) { | ||||
|         if (this.provider.dragActivateResult) | ||||
|             this.provider.dragActivateResult(this.metaInfo.id, params); | ||||
|         else | ||||
|             this.provider.activateResult(this.metaInfo.id, this.terms); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const SearchResultsBase = new Lang.Class({ | ||||
|     Name: 'SearchResultsBase', | ||||
|  | ||||
|     _init: function(provider) { | ||||
|         this.provider = provider; | ||||
|  | ||||
|         this._terms = []; | ||||
|  | ||||
|         this.actor = new St.BoxLayout({ style_class: 'search-section', | ||||
|                                         vertical: true }); | ||||
|  | ||||
|         this._resultDisplayBin = new St.Bin({ x_fill: true, | ||||
|                                               y_fill: true }); | ||||
|         this.actor.add(this._resultDisplayBin, { expand: true }); | ||||
|  | ||||
|         let separator = new Separator.HorizontalSeparator({ style_class: 'search-section-separator' }); | ||||
|         this.actor.add(separator.actor); | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|         this.actor.destroy(); | ||||
|         this._terms = []; | ||||
|     }, | ||||
|  | ||||
|     _clearResultDisplay: function() { | ||||
|     }, | ||||
|  | ||||
|     clear: function() { | ||||
|         this._clearResultDisplay(); | ||||
|         this.actor.hide(); | ||||
|     }, | ||||
|  | ||||
|     _keyFocusIn: function(actor) { | ||||
|         this.emit('key-focus-in', actor); | ||||
|     }, | ||||
|  | ||||
|     _setMoreIconVisible: function(visible) { | ||||
|     }, | ||||
|  | ||||
|     updateSearch: function(providerResults, terms, callback) { | ||||
|         this._terms = terms; | ||||
|  | ||||
|         if (providerResults.length == 0) { | ||||
|             this._clearResultDisplay(); | ||||
|             this.actor.hide(); | ||||
|             callback(); | ||||
|         } else { | ||||
|             let maxResults = this._getMaxDisplayedResults(); | ||||
|             let results = this.provider.filterResults(providerResults, maxResults); | ||||
|             let hasMoreResults = results.length < providerResults.length; | ||||
|  | ||||
|             this.provider.getResultMetas(results, Lang.bind(this, function(metas) { | ||||
|                 this.clear(); | ||||
|  | ||||
|                 // To avoid CSS transitions causing flickering when | ||||
|                 // the first search result stays the same, we hide the | ||||
|                 // content while filling in the results. | ||||
|                 this.actor.hide(); | ||||
|                 this._clearResultDisplay(); | ||||
|                 this._renderResults(metas); | ||||
|                 this._setMoreIconVisible(hasMoreResults && this.provider.canLaunchSearch); | ||||
|                 this.actor.show(); | ||||
|                 callback(); | ||||
|             })); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const ListSearchResults = new Lang.Class({ | ||||
|     Name: 'ListSearchResults', | ||||
|     Extends: SearchResultsBase, | ||||
|  | ||||
|     _init: function(provider) { | ||||
|         this.parent(provider); | ||||
|  | ||||
|         this._container = new St.BoxLayout({ style_class: 'search-section-content' }); | ||||
|         this.providerIcon = new ProviderIcon(provider); | ||||
|         this.providerIcon.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); | ||||
|         this.providerIcon.connect('clicked', Lang.bind(this, | ||||
|             function() { | ||||
|                 provider.launchSearch(this._terms); | ||||
|                 Main.overview.toggle(); | ||||
|             })); | ||||
|  | ||||
|         this._container.add(this.providerIcon, { x_fill: false, | ||||
|                                                  y_fill: false, | ||||
|                                                  x_align: St.Align.START, | ||||
|                                                  y_align: St.Align.START }); | ||||
|  | ||||
|         this._content = new St.BoxLayout({ style_class: 'list-search-results', | ||||
|                                            vertical: true }); | ||||
|         this._container.add(this._content, { expand: true }); | ||||
|  | ||||
|         this._resultDisplayBin.set_child(this._container); | ||||
|     }, | ||||
|  | ||||
|     _setMoreIconVisible: function(visible) { | ||||
|         this.providerIcon.moreIcon.visible = true; | ||||
|     }, | ||||
|  | ||||
|     _getMaxDisplayedResults: function() { | ||||
|         return MAX_LIST_SEARCH_RESULTS_ROWS; | ||||
|     }, | ||||
|  | ||||
|     _renderResults: function(metas) { | ||||
|         for (let i = 0; i < metas.length; i++) { | ||||
|             let display = new ListSearchResult(this.provider, metas[i], this._terms); | ||||
|             display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); | ||||
|             this._content.add_actor(display.actor); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _clearResultDisplay: function () { | ||||
|         this._content.destroy_all_children(); | ||||
|     }, | ||||
|  | ||||
|     getFirstResult: function() { | ||||
|         if (this._content.get_n_children() > 0) | ||||
|             return this._content.get_child_at_index(0)._delegate; | ||||
|         else | ||||
|             return null; | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(ListSearchResults.prototype); | ||||
|  | ||||
| const GridSearchResults = new Lang.Class({ | ||||
|     Name: 'GridSearchResults', | ||||
|     Extends: SearchResultsBase, | ||||
|  | ||||
|     _init: function(provider) { | ||||
|         this.parent(provider); | ||||
|  | ||||
|         this._grid = new IconGrid.IconGrid({ rowLimit: MAX_GRID_SEARCH_RESULTS_ROWS, | ||||
|                                              xAlign: St.Align.START }); | ||||
|         this._bin = new St.Bin({ x_align: St.Align.MIDDLE }); | ||||
|         this._bin.set_child(this._grid.actor); | ||||
|  | ||||
|         this._resultDisplayBin.set_child(this._bin); | ||||
|     }, | ||||
|  | ||||
|     _getMaxDisplayedResults: function() { | ||||
|         return this._grid.columnsForWidth(this._bin.width) * this._grid.getRowLimit(); | ||||
|     }, | ||||
|  | ||||
|     _renderResults: function(metas) { | ||||
|         for (let i = 0; i < metas.length; i++) { | ||||
|             let display = new GridSearchResult(this.provider, metas[i], this._terms); | ||||
|             display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); | ||||
|             this._grid.addItem(display); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _clearResultDisplay: function () { | ||||
|         this._grid.removeAll(); | ||||
|     }, | ||||
|  | ||||
|     getFirstResult: function() { | ||||
|         if (this._grid.visibleItemsCount() > 0) | ||||
|             return this._grid.getItemAtIndex(0)._delegate; | ||||
|         else | ||||
|             return null; | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(GridSearchResults.prototype); | ||||
|  | ||||
| const SearchResults = new Lang.Class({ | ||||
|     Name: 'SearchResults', | ||||
|  | ||||
|     _init: function(searchSystem) { | ||||
|         this._searchSystem = searchSystem; | ||||
|         this._searchSystem.connect('search-updated', Lang.bind(this, this._updateResults)); | ||||
|  | ||||
|         this.actor = new St.BoxLayout({ name: 'searchResults', | ||||
|                                         vertical: true }); | ||||
|  | ||||
|         this._content = new St.BoxLayout({ name: 'searchResultsContent', | ||||
|                                            vertical: true }); | ||||
|         this._contentBin = new MaxWidthBin({ name: 'searchResultsBin', | ||||
|                                              x_fill: true, | ||||
|                                              y_fill: true, | ||||
|                                              child: this._content }); | ||||
|  | ||||
|         let scrollChild = new St.BoxLayout(); | ||||
|         scrollChild.add(this._contentBin, { expand: true }); | ||||
|  | ||||
|         this._scrollView = new St.ScrollView({ x_fill: true, | ||||
|                                                y_fill: false, | ||||
|                                                overlay_scrollbars: true, | ||||
|                                                style_class: 'search-display vfade' }); | ||||
|         this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); | ||||
|         this._scrollView.add_actor(scrollChild); | ||||
|         let action = new Clutter.PanAction({ interpolate: true }); | ||||
|         action.connect('pan', Lang.bind(this, this._onPan)); | ||||
|         this._scrollView.add_action(action); | ||||
|  | ||||
|         this.actor.add(this._scrollView, { x_fill: true, | ||||
|                                            y_fill: true, | ||||
|                                            expand: true, | ||||
|                                            x_align: St.Align.START, | ||||
|                                            y_align: St.Align.START }); | ||||
|  | ||||
|         this._statusText = new St.Label({ style_class: 'search-statustext' }); | ||||
|         this._statusBin = new St.Bin({ x_align: St.Align.MIDDLE, | ||||
|                                        y_align: St.Align.MIDDLE }); | ||||
|         this._content.add(this._statusBin, { expand: true }); | ||||
|         this._statusBin.add_actor(this._statusText); | ||||
|         this._providers = this._searchSystem.getProviders(); | ||||
|         this._providerDisplays = {}; | ||||
|         for (let i = 0; i < this._providers.length; i++) { | ||||
|             this.createProviderDisplay(this._providers[i]); | ||||
|         } | ||||
|  | ||||
|         this._highlightDefault = false; | ||||
|         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, actor) { | ||||
|         Util.ensureActorVisibleInScrollView(this._scrollView, actor); | ||||
|     }, | ||||
|  | ||||
|     createProviderDisplay: function(provider) { | ||||
|         let providerDisplay = null; | ||||
|  | ||||
|         if (provider.appInfo) { | ||||
|             providerDisplay = new ListSearchResults(provider); | ||||
|         } else { | ||||
|             providerDisplay = new GridSearchResults(provider); | ||||
|         } | ||||
|  | ||||
|         providerDisplay.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); | ||||
|         this._providerDisplays[provider.id] = providerDisplay; | ||||
|         this._content.add(providerDisplay.actor); | ||||
|     }, | ||||
|  | ||||
|     destroyProviderDisplay: function(provider) { | ||||
|         this._providerDisplays[provider.id].destroy(); | ||||
|         delete this._providerDisplays[provider.id]; | ||||
|     }, | ||||
|  | ||||
|     _clearDisplay: function() { | ||||
|         for (let i = 0; i < this._providers.length; i++) { | ||||
|             let provider = this._providers[i]; | ||||
|             let providerDisplay = this._providerDisplays[provider.id]; | ||||
|             providerDisplay.clear(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     reset: function() { | ||||
|         this._searchSystem.reset(); | ||||
|         this._statusBin.hide(); | ||||
|         this._clearDisplay(); | ||||
|         this._defaultResult = null; | ||||
|     }, | ||||
|  | ||||
|     startingSearch: function() { | ||||
|         this.reset(); | ||||
|         this._statusText.set_text(_("Searching…")); | ||||
|         this._statusBin.show(); | ||||
|     }, | ||||
|  | ||||
|     _maybeSetInitialSelection: function() { | ||||
|         let newDefaultResult = null; | ||||
|  | ||||
|         for (let i = 0; i < this._providers.length; i++) { | ||||
|             let provider = this._providers[i]; | ||||
|             let display = this._providerDisplays[provider.id]; | ||||
|  | ||||
|             if (!display.actor.visible) | ||||
|                 continue; | ||||
|  | ||||
|             let firstResult = display.getFirstResult(); | ||||
|             if (firstResult) { | ||||
|                 newDefaultResult = firstResult; | ||||
|                 break; // select this one! | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (newDefaultResult != this._defaultResult) { | ||||
|             if (this._defaultResult) | ||||
|                 this._defaultResult.setSelected(false); | ||||
|             if (newDefaultResult) | ||||
|                 newDefaultResult.setSelected(this._highlightDefault); | ||||
|  | ||||
|             this._defaultResult = newDefaultResult; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateStatusText: function () { | ||||
|         let haveResults = false; | ||||
|  | ||||
|         for (let i = 0; i < this._providers.length; i++) { | ||||
|             let provider = this._providers[i]; | ||||
|             let display = this._providerDisplays[provider.id]; | ||||
|             if (display.getFirstResult()) { | ||||
|                 haveResults = true; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (!haveResults) { | ||||
|             this._statusText.set_text(_("No results.")); | ||||
|             this._statusBin.show(); | ||||
|         } else { | ||||
|             this._statusBin.hide(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateResults: function(searchSystem, results) { | ||||
|         let terms = searchSystem.getTerms(); | ||||
|         let [provider, providerResults] = results; | ||||
|         let display = this._providerDisplays[provider.id]; | ||||
|  | ||||
|         display.updateSearch(providerResults, terms, Lang.bind(this, function() { | ||||
|             this._maybeSetInitialSelection(); | ||||
|             this._updateStatusText(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     activateDefault: function() { | ||||
|         if (this._defaultResult) | ||||
|             this._defaultResult.activate(); | ||||
|     }, | ||||
|  | ||||
|     highlightDefault: function(highlight) { | ||||
|         this._highlightDefault = highlight; | ||||
|         if (this._defaultResult) | ||||
|             this._defaultResult.setSelected(highlight); | ||||
|     }, | ||||
|  | ||||
|     navigateFocus: function(direction) { | ||||
|         let rtl = this.actor.get_text_direction() == Clutter.TextDirection.RTL; | ||||
|         if (direction == Gtk.DirectionType.TAB_BACKWARD || | ||||
|             direction == (rtl ? Gtk.DirectionType.RIGHT | ||||
|                               : Gtk.DirectionType.LEFT) || | ||||
|             direction == Gtk.DirectionType.UP) { | ||||
|             this.actor.navigate_focus(null, direction, false); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let from = this._defaultResult ? this._defaultResult.actor : null; | ||||
|         this.actor.navigate_focus(from, 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); | ||||
|     } | ||||
| }); | ||||
| @@ -10,6 +10,8 @@ const FileUtils = imports.misc.fileUtils; | ||||
| const Main = imports.ui.main; | ||||
| const Params = imports.misc.params; | ||||
|  | ||||
| const Config = imports.misc.config; | ||||
|  | ||||
| const DEFAULT_MODE = 'restrictive'; | ||||
|  | ||||
| const _modes = { | ||||
| @@ -92,8 +94,12 @@ const _modes = { | ||||
|         isLocked: false, | ||||
|         isPrimary: true, | ||||
|         unlockDialog: imports.ui.unlockDialog.UnlockDialog, | ||||
|         components: ['networkAgent', 'polkitAgent', 'telepathyClient', | ||||
|         components: Config.HAVE_NETWORKMANAGER ? | ||||
|                     ['networkAgent', 'polkitAgent', 'telepathyClient', | ||||
|                      'keyring', 'autorunManager', 'automountManager'] : | ||||
|                     ['polkitAgent', 'telepathyClient', | ||||
|                      'keyring', 'autorunManager', 'automountManager'], | ||||
|  | ||||
|         panel: { | ||||
|             left: ['activities', 'appMenu'], | ||||
|             center: ['dateMenu'], | ||||
| @@ -102,19 +108,12 @@ const _modes = { | ||||
|     } | ||||
| }; | ||||
|  | ||||
| function _getModes(modesLoadedCallback) { | ||||
|     FileUtils.collectFromDatadirsAsync('modes', | ||||
|                                        { processFile: _loadMode, | ||||
|                                          loadedCallback: modesLoadedCallback, | ||||
|                                          data: _modes }); | ||||
| } | ||||
|  | ||||
| function _loadMode(file, info, loadedData) { | ||||
| function _loadMode(file, info) { | ||||
|     let name = info.get_name(); | ||||
|     let suffix = name.indexOf('.json'); | ||||
|     let modeName = suffix == -1 ? name : name.slice(name, suffix); | ||||
|  | ||||
|     if (loadedData.hasOwnProperty(modeName)) | ||||
|     if (_modes.hasOwnProperty(modeName)) | ||||
|         return; | ||||
|  | ||||
|     let fileContent, success, tag, newMode; | ||||
| @@ -125,19 +124,24 @@ function _loadMode(file, info, loadedData) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     loadedData[modeName] = {}; | ||||
|     _modes[modeName] = {}; | ||||
|     let propBlacklist = ['unlockDialog']; | ||||
|     for (let prop in loadedData[DEFAULT_MODE]) { | ||||
|     for (let prop in _modes[DEFAULT_MODE]) { | ||||
|         if (newMode[prop] !== undefined && | ||||
|             propBlacklist.indexOf(prop) == -1) | ||||
|             loadedData[modeName][prop]= newMode[prop]; | ||||
|             _modes[modeName][prop] = newMode[prop]; | ||||
|     } | ||||
|     loadedData[modeName]['isPrimary'] = true; | ||||
|     _modes[modeName]['isPrimary'] = true; | ||||
| } | ||||
|  | ||||
| function _loadModes() { | ||||
|     FileUtils.collectFromDatadirs('modes', false, _loadMode); | ||||
| } | ||||
|  | ||||
| function listModes() { | ||||
|     _getModes(function(modes) { | ||||
|         let names = Object.getOwnPropertyNames(modes); | ||||
|     _loadModes(); | ||||
|     Mainloop.idle_add(function() { | ||||
|         let names = Object.getOwnPropertyNames(_modes); | ||||
|         for (let i = 0; i < names.length; i++) | ||||
|             if (_modes[names[i]].isPrimary) | ||||
|                 print(names[i]); | ||||
| @@ -149,17 +153,13 @@ function listModes() { | ||||
| const SessionMode = new Lang.Class({ | ||||
|     Name: 'SessionMode', | ||||
|  | ||||
|     init: function() { | ||||
|         _getModes(Lang.bind(this, function(modes) { | ||||
|             this._modes = modes; | ||||
|             let primary = modes[global.session_mode] && | ||||
|                           modes[global.session_mode].isPrimary; | ||||
|             let mode = primary ? global.session_mode : 'user'; | ||||
|             this._modeStack = [mode]; | ||||
|             this._sync(); | ||||
|  | ||||
|             this.emit('sessions-loaded'); | ||||
|         })); | ||||
|     _init: function() { | ||||
|         _loadModes(); | ||||
|         let isPrimary = (_modes[global.session_mode] && | ||||
|                          _modes[global.session_mode].isPrimary); | ||||
|         let mode = isPrimary ? global.session_mode : 'user'; | ||||
|         this._modeStack = [mode]; | ||||
|         this._sync(); | ||||
|     }, | ||||
|  | ||||
|     pushMode: function(mode) { | ||||
| @@ -186,13 +186,13 @@ const SessionMode = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _sync: function() { | ||||
|         let params = this._modes[this.currentMode]; | ||||
|         let params = _modes[this.currentMode]; | ||||
|         let defaults; | ||||
|         if (params.parentMode) | ||||
|             defaults = Params.parse(this._modes[params.parentMode], | ||||
|                                     this._modes[DEFAULT_MODE]); | ||||
|             defaults = Params.parse(_modes[params.parentMode], | ||||
|                                     _modes[DEFAULT_MODE]); | ||||
|         else | ||||
|             defaults = this._modes[DEFAULT_MODE]; | ||||
|             defaults = _modes[DEFAULT_MODE]; | ||||
|         params = Params.parse(params, defaults); | ||||
|  | ||||
|         // A simplified version of Lang.copyProperties, handles | ||||
|   | ||||
| @@ -10,64 +10,68 @@ const Config = imports.misc.config; | ||||
| const ExtensionSystem = imports.ui.extensionSystem; | ||||
| const ExtensionDownloader = imports.ui.extensionDownloader; | ||||
| const ExtensionUtils = imports.misc.extensionUtils; | ||||
| const Hash = imports.misc.hash; | ||||
| const Main = imports.ui.main; | ||||
| const Screenshot = imports.ui.screenshot; | ||||
| const ViewSelector = imports.ui.viewSelector; | ||||
|  | ||||
| const GnomeShellIface = <interface name="org.gnome.Shell"> | ||||
| <method name="Eval"> | ||||
|     <arg type="s" direction="in" name="script" /> | ||||
|     <arg type="b" direction="out" name="success" /> | ||||
|     <arg type="s" direction="out" name="result" /> | ||||
| </method> | ||||
| <method name="FocusSearch"/> | ||||
| <method name="ShowOSD"> | ||||
|     <arg type="a{sv}" direction="in" name="params"/> | ||||
| </method> | ||||
| <method name="FocusApp"> | ||||
|     <arg type="s" direction="in" name="id"/> | ||||
| </method> | ||||
| <method name="ShowApplications" /> | ||||
| <method name="GrabAccelerator"> | ||||
|     <arg type="s" direction="in" name="accelerator"/> | ||||
|     <arg type="u" direction="in" name="flags"/> | ||||
|     <arg type="u" direction="out" name="action"/> | ||||
| </method> | ||||
| <method name="GrabAccelerators"> | ||||
|     <arg type="a(su)" direction="in" name="accelerators"/> | ||||
|     <arg type="au" direction="out" name="actions"/> | ||||
| </method> | ||||
| <method name="UngrabAccelerator"> | ||||
|     <arg type="u" direction="in" name="action"/> | ||||
|     <arg type="b" direction="out" name="success"/> | ||||
| </method> | ||||
| <signal name="AcceleratorActivated"> | ||||
|     <arg name="action" type="u" /> | ||||
|     <arg name="deviceid" type="u" /> | ||||
|     <arg name="timestamp" type="u" /> | ||||
| </signal> | ||||
| <property name="Mode" type="s" access="read" /> | ||||
| <property name="OverviewActive" type="b" access="readwrite" /> | ||||
| <property name="ShellVersion" type="s" access="read" /> | ||||
| </interface>; | ||||
| const GnomeShellIface = '<node> \ | ||||
| <interface name="org.gnome.Shell"> \ | ||||
| <method name="Eval"> \ | ||||
|     <arg type="s" direction="in" name="script" /> \ | ||||
|     <arg type="b" direction="out" name="success" /> \ | ||||
|     <arg type="s" direction="out" name="result" /> \ | ||||
| </method> \ | ||||
| <method name="FocusSearch"/> \ | ||||
| <method name="ShowOSD"> \ | ||||
|     <arg type="a{sv}" direction="in" name="params"/> \ | ||||
| </method> \ | ||||
| <method name="FocusApp"> \ | ||||
|     <arg type="s" direction="in" name="id"/> \ | ||||
| </method> \ | ||||
| <method name="ShowApplications" /> \ | ||||
| <method name="GrabAccelerator"> \ | ||||
|     <arg type="s" direction="in" name="accelerator"/> \ | ||||
|     <arg type="u" direction="in" name="flags"/> \ | ||||
|     <arg type="u" direction="out" name="action"/> \ | ||||
| </method> \ | ||||
| <method name="GrabAccelerators"> \ | ||||
|     <arg type="a(su)" direction="in" name="accelerators"/> \ | ||||
|     <arg type="au" direction="out" name="actions"/> \ | ||||
| </method> \ | ||||
| <method name="UngrabAccelerator"> \ | ||||
|     <arg type="u" direction="in" name="action"/> \ | ||||
|     <arg type="b" direction="out" name="success"/> \ | ||||
| </method> \ | ||||
| <signal name="AcceleratorActivated"> \ | ||||
|     <arg name="action" type="u" /> \ | ||||
|     <arg name="deviceid" type="u" /> \ | ||||
|     <arg name="timestamp" type="u" /> \ | ||||
| </signal> \ | ||||
| <property name="Mode" type="s" access="read" /> \ | ||||
| <property name="OverviewActive" type="b" access="readwrite" /> \ | ||||
| <property name="ShellVersion" type="s" access="read" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const ScreenSaverIface = <interface name="org.gnome.ScreenSaver"> | ||||
| <method name="Lock"> | ||||
| </method> | ||||
| <method name="GetActive"> | ||||
|     <arg name="active" direction="out" type="b" /> | ||||
| </method> | ||||
| <method name="SetActive"> | ||||
|     <arg name="value" direction="in" type="b" /> | ||||
| </method> | ||||
| <method name="GetActiveTime"> | ||||
|     <arg name="value" direction="out" type="u" /> | ||||
| </method> | ||||
| <signal name="ActiveChanged"> | ||||
|     <arg name="new_value" type="b" /> | ||||
| </signal> | ||||
| </interface>; | ||||
| const ScreenSaverIface = '<node> \ | ||||
| <interface name="org.gnome.ScreenSaver"> \ | ||||
| <method name="Lock"> \ | ||||
| </method> \ | ||||
| <method name="GetActive"> \ | ||||
|     <arg name="active" direction="out" type="b" /> \ | ||||
| </method> \ | ||||
| <method name="SetActive"> \ | ||||
|     <arg name="value" direction="in" type="b" /> \ | ||||
| </method> \ | ||||
| <method name="GetActiveTime"> \ | ||||
|     <arg name="value" direction="out" type="u" /> \ | ||||
| </method> \ | ||||
| <signal name="ActiveChanged"> \ | ||||
|     <arg name="new_value" type="b" /> \ | ||||
| </signal> \ | ||||
| <signal name="WakeUpScreen" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const GnomeShell = new Lang.Class({ | ||||
|     Name: 'GnomeShellDBus', | ||||
| @@ -79,8 +83,8 @@ const GnomeShell = new Lang.Class({ | ||||
|         this._extensionsService = new GnomeShellExtensions(); | ||||
|         this._screenshotService = new Screenshot.ScreenshotService(); | ||||
|  | ||||
|         this._grabbedAccelerators = new Hash.Map(); | ||||
|         this._grabbers = new Hash.Map(); | ||||
|         this._grabbedAccelerators = new Map(); | ||||
|         this._grabbers = new Map(); | ||||
|  | ||||
|         global.display.connect('accelerator-activated', Lang.bind(this, | ||||
|             function(display, action, deviceid, timestamp) { | ||||
| @@ -115,7 +119,7 @@ const GnomeShell = new Lang.Class({ | ||||
|                 returnValue = ''; | ||||
|             success = true; | ||||
|         } catch (e) { | ||||
|             returnValue = JSON.stringify(e); | ||||
|             returnValue = '' + e; | ||||
|             success = false; | ||||
|         } | ||||
|         return [success, returnValue]; | ||||
| @@ -129,11 +133,16 @@ const GnomeShell = new Lang.Class({ | ||||
|         for (let param in params) | ||||
|             params[param] = params[param].deep_unpack(); | ||||
|  | ||||
|         let monitorIndex = -1; | ||||
|         if (params['monitor']) | ||||
|             monitorIndex = params['monitor']; | ||||
|  | ||||
|         let icon = null; | ||||
|         if (params['icon']) | ||||
|             icon = Gio.Icon.new_for_string(params['icon']); | ||||
|  | ||||
|         Main.osdWindow.setIcon(icon); | ||||
|         Main.osdWindow.setMonitor (monitorIndex); | ||||
|         Main.osdWindow.setLabel(params['label']); | ||||
|         Main.osdWindow.setLevel(params['level']); | ||||
|  | ||||
| @@ -219,9 +228,8 @@ const GnomeShell = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _onGrabberBusNameVanished: function(connection, name) { | ||||
|         let grabs = this._grabbedAccelerators.items(); | ||||
|         for (let i = 0; i < grabs.length; i++) { | ||||
|             let [action, sender] = grabs[i]; | ||||
|         let grabs = this._grabbedAccelerators.entries(); | ||||
|         for (let [action, sender] of grabs) { | ||||
|             if (sender == name) | ||||
|                 this._ungrabAccelerator(action); | ||||
|         } | ||||
| @@ -246,41 +254,43 @@ const GnomeShell = new Lang.Class({ | ||||
|     ShellVersion: Config.PACKAGE_VERSION | ||||
| }); | ||||
|  | ||||
| const GnomeShellExtensionsIface = <interface name="org.gnome.Shell.Extensions"> | ||||
| <method name="ListExtensions"> | ||||
|     <arg type="a{sa{sv}}" direction="out" name="extensions" /> | ||||
| </method> | ||||
| <method name="GetExtensionInfo"> | ||||
|     <arg type="s" direction="in" name="extension" /> | ||||
|     <arg type="a{sv}" direction="out" name="info" /> | ||||
| </method> | ||||
| <method name="GetExtensionErrors"> | ||||
|     <arg type="s" direction="in" name="extension" /> | ||||
|     <arg type="as" direction="out" name="errors" /> | ||||
| </method> | ||||
| <signal name="ExtensionStatusChanged"> | ||||
|     <arg type="s" name="uuid"/> | ||||
|     <arg type="i" name="state"/> | ||||
|     <arg type="s" name="error"/> | ||||
| </signal> | ||||
| <method name="InstallRemoteExtension"> | ||||
|     <arg type="s" direction="in" name="uuid"/> | ||||
|     <arg type="s" direction="out" name="result"/> | ||||
| </method> | ||||
| <method name="UninstallExtension"> | ||||
|     <arg type="s" direction="in" name="uuid"/> | ||||
|     <arg type="b" direction="out" name="success"/> | ||||
| </method> | ||||
| <method name="LaunchExtensionPrefs"> | ||||
|     <arg type="s" direction="in" name="uuid"/> | ||||
| </method> | ||||
| <method name="ReloadExtension"> | ||||
|     <arg type="s" direction="in" name="uuid"/> | ||||
| </method> | ||||
| <method name="CheckForUpdates"> | ||||
| </method> | ||||
| <property name="ShellVersion" type="s" access="read" /> | ||||
| </interface>; | ||||
| const GnomeShellExtensionsIface = '<node> \ | ||||
| <interface name="org.gnome.Shell.Extensions"> \ | ||||
| <method name="ListExtensions"> \ | ||||
|     <arg type="a{sa{sv}}" direction="out" name="extensions" /> \ | ||||
| </method> \ | ||||
| <method name="GetExtensionInfo"> \ | ||||
|     <arg type="s" direction="in" name="extension" /> \ | ||||
|     <arg type="a{sv}" direction="out" name="info" /> \ | ||||
| </method> \ | ||||
| <method name="GetExtensionErrors"> \ | ||||
|     <arg type="s" direction="in" name="extension" /> \ | ||||
|     <arg type="as" direction="out" name="errors" /> \ | ||||
| </method> \ | ||||
| <signal name="ExtensionStatusChanged"> \ | ||||
|     <arg type="s" name="uuid"/> \ | ||||
|     <arg type="i" name="state"/> \ | ||||
|     <arg type="s" name="error"/> \ | ||||
| </signal> \ | ||||
| <method name="InstallRemoteExtension"> \ | ||||
|     <arg type="s" direction="in" name="uuid"/> \ | ||||
|     <arg type="s" direction="out" name="result"/> \ | ||||
| </method> \ | ||||
| <method name="UninstallExtension"> \ | ||||
|     <arg type="s" direction="in" name="uuid"/> \ | ||||
|     <arg type="b" direction="out" name="success"/> \ | ||||
| </method> \ | ||||
| <method name="LaunchExtensionPrefs"> \ | ||||
|     <arg type="s" direction="in" name="uuid"/> \ | ||||
| </method> \ | ||||
| <method name="ReloadExtension"> \ | ||||
|     <arg type="s" direction="in" name="uuid"/> \ | ||||
| </method> \ | ||||
| <method name="CheckForUpdates"> \ | ||||
| </method> \ | ||||
| <property name="ShellVersion" type="s" access="read" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const GnomeShellExtensions = new Lang.Class({ | ||||
|     Name: 'GnomeShellExtensionsDBus', | ||||
| @@ -362,8 +372,10 @@ const GnomeShellExtensions = new Lang.Class({ | ||||
|     LaunchExtensionPrefs: function(uuid) { | ||||
|         let appSys = Shell.AppSystem.get_default(); | ||||
|         let app = appSys.lookup_app('gnome-shell-extension-prefs.desktop'); | ||||
|         app.launch(global.display.get_current_time_roundtrip(), | ||||
|                    ['extension:///' + uuid], -1, null); | ||||
|         let info = app.get_app_info(); | ||||
|         let timestamp = global.display.get_current_time_roundtrip(); | ||||
|         info.launch_uris(['extension:///' + uuid], | ||||
|                          global.create_app_launch_context(timestamp, -1)); | ||||
|     }, | ||||
|  | ||||
|     ReloadExtension: function(uuid) { | ||||
| @@ -396,6 +408,9 @@ const ScreenSaverDBus = new Lang.Class({ | ||||
|         screenShield.connect('active-changed', Lang.bind(this, function(shield) { | ||||
|             this._dbusImpl.emit_signal('ActiveChanged', GLib.Variant.new('(b)', [shield.active])); | ||||
|         })); | ||||
|         screenShield.connect('wake-up-screen', Lang.bind(this, function(shield) { | ||||
|             this._dbusImpl.emit_signal('WakeUpScreen', null); | ||||
|         })); | ||||
|  | ||||
|         this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(ScreenSaverIface, this); | ||||
|         this._dbusImpl.export(Gio.DBus.session, '/org/gnome/ScreenSaver'); | ||||
|   | ||||
| @@ -132,14 +132,14 @@ function _setMenuAlignment(entry, stageX) { | ||||
| function _onButtonPressEvent(actor, event, entry) { | ||||
|     if (entry.menu.isOpen) { | ||||
|         entry.menu.close(BoxPointer.PopupAnimation.FULL); | ||||
|         return true; | ||||
|         return Clutter.EVENT_STOP; | ||||
|     } else if (event.get_button() == 3) { | ||||
|         let [stageX, stageY] = event.get_coords(); | ||||
|         _setMenuAlignment(entry, stageX); | ||||
|         entry.menu.open(BoxPointer.PopupAnimation.FULL); | ||||
|         return true; | ||||
|         return Clutter.EVENT_STOP; | ||||
|     } | ||||
|     return false; | ||||
|     return Clutter.EVENT_PROPAGATE; | ||||
| }; | ||||
|  | ||||
| function _onPopup(actor, entry) { | ||||
|   | ||||
| @@ -521,36 +521,38 @@ const ShellProcessesDialog = new Lang.Class({ | ||||
| }); | ||||
| Signals.addSignalMethods(ShellProcessesDialog.prototype); | ||||
|  | ||||
| const GnomeShellMountOpIface = <interface name="org.Gtk.MountOperationHandler"> | ||||
| <method name="AskPassword"> | ||||
|     <arg type="s" direction="in" name="object_id"/> | ||||
|     <arg type="s" direction="in" name="message"/> | ||||
|     <arg type="s" direction="in" name="icon_name"/> | ||||
|     <arg type="s" direction="in" name="default_user"/> | ||||
|     <arg type="s" direction="in" name="default_domain"/> | ||||
|     <arg type="u" direction="in" name="flags"/> | ||||
|     <arg type="u" direction="out" name="response"/> | ||||
|     <arg type="a{sv}" direction="out" name="response_details"/> | ||||
| </method> | ||||
| <method name="AskQuestion"> | ||||
|     <arg type="s" direction="in" name="object_id"/> | ||||
|     <arg type="s" direction="in" name="message"/> | ||||
|     <arg type="s" direction="in" name="icon_name"/> | ||||
|     <arg type="as" direction="in" name="choices"/> | ||||
|     <arg type="u" direction="out" name="response"/> | ||||
|     <arg type="a{sv}" direction="out" name="response_details"/> | ||||
| </method> | ||||
| <method name="ShowProcesses"> | ||||
|     <arg type="s" direction="in" name="object_id"/> | ||||
|     <arg type="s" direction="in" name="message"/> | ||||
|     <arg type="s" direction="in" name="icon_name"/> | ||||
|     <arg type="ai" direction="in" name="application_pids"/> | ||||
|     <arg type="as" direction="in" name="choices"/> | ||||
|     <arg type="u" direction="out" name="response"/> | ||||
|     <arg type="a{sv}" direction="out" name="response_details"/> | ||||
| </method> | ||||
| <method name="Close"/> | ||||
| </interface>; | ||||
| const GnomeShellMountOpIface = '<node> \ | ||||
| <interface name="org.Gtk.MountOperationHandler"> \ | ||||
| <method name="AskPassword"> \ | ||||
|     <arg type="s" direction="in" name="object_id"/> \ | ||||
|     <arg type="s" direction="in" name="message"/> \ | ||||
|     <arg type="s" direction="in" name="icon_name"/> \ | ||||
|     <arg type="s" direction="in" name="default_user"/> \ | ||||
|     <arg type="s" direction="in" name="default_domain"/> \ | ||||
|     <arg type="u" direction="in" name="flags"/> \ | ||||
|     <arg type="u" direction="out" name="response"/> \ | ||||
|     <arg type="a{sv}" direction="out" name="response_details"/> \ | ||||
| </method> \ | ||||
| <method name="AskQuestion"> \ | ||||
|     <arg type="s" direction="in" name="object_id"/> \ | ||||
|     <arg type="s" direction="in" name="message"/> \ | ||||
|     <arg type="s" direction="in" name="icon_name"/> \ | ||||
|     <arg type="as" direction="in" name="choices"/> \ | ||||
|     <arg type="u" direction="out" name="response"/> \ | ||||
|     <arg type="a{sv}" direction="out" name="response_details"/> \ | ||||
| </method> \ | ||||
| <method name="ShowProcesses"> \ | ||||
|     <arg type="s" direction="in" name="object_id"/> \ | ||||
|     <arg type="s" direction="in" name="message"/> \ | ||||
|     <arg type="s" direction="in" name="icon_name"/> \ | ||||
|     <arg type="ai" direction="in" name="application_pids"/> \ | ||||
|     <arg type="as" direction="in" name="choices"/> \ | ||||
|     <arg type="u" direction="out" name="response"/> \ | ||||
|     <arg type="a{sv}" direction="out" name="response_details"/> \ | ||||
| </method> \ | ||||
| <method name="Close"/> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const ShellMountOperationType = { | ||||
|     NONE: 0, | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ | ||||
|  | ||||
| const Atk = imports.gi.Atk; | ||||
| const Cairo = imports.cairo; | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Lang = imports.lang; | ||||
| const St = imports.gi.St; | ||||
| const Signals = imports.signals; | ||||
| const Atk = imports.gi.Atk; | ||||
|  | ||||
| const SLIDER_SCROLL_STEP = 0.05; /* Slider scrolling step in % */ | ||||
|  | ||||
| @@ -111,12 +111,12 @@ const Slider = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _startDragging: function(actor, event) { | ||||
|         this.startDragging(event); | ||||
|         return this.startDragging(event); | ||||
|     }, | ||||
|  | ||||
|     startDragging: function(event) { | ||||
|         if (this._dragging) | ||||
|             return false; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         this._dragging = true; | ||||
|  | ||||
| @@ -129,7 +129,7 @@ const Slider = new Lang.Class({ | ||||
|         let absX, absY; | ||||
|         [absX, absY] = event.get_coords(); | ||||
|         this._moveHandle(absX, absY); | ||||
|         return true; | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     _endDragging: function() { | ||||
| @@ -143,7 +143,7 @@ const Slider = new Lang.Class({ | ||||
|  | ||||
|             this.emit('drag-end'); | ||||
|         } | ||||
|         return true; | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     scroll: function(event) { | ||||
| @@ -151,7 +151,7 @@ const Slider = new Lang.Class({ | ||||
|         let delta; | ||||
|  | ||||
|         if (event.is_pointer_emulated()) | ||||
|             return; | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         if (direction == Clutter.ScrollDirection.DOWN) { | ||||
|             delta = -SLIDER_SCROLL_STEP; | ||||
| @@ -168,17 +168,18 @@ const Slider = new Lang.Class({ | ||||
|  | ||||
|         this.actor.queue_repaint(); | ||||
|         this.emit('value-changed', this._value); | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     _onScrollEvent: function(actor, event) { | ||||
|         this.scroll(event); | ||||
|         return this.scroll(event); | ||||
|     }, | ||||
|  | ||||
|     _motionEvent: function(actor, event) { | ||||
|         let absX, absY; | ||||
|         [absX, absY] = event.get_coords(); | ||||
|         this._moveHandle(absX, absY); | ||||
|         return true; | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     onKeyPressEvent: function (actor, event) { | ||||
| @@ -189,9 +190,9 @@ const Slider = new Lang.Class({ | ||||
|             this.actor.queue_repaint(); | ||||
|             this.emit('value-changed', this._value); | ||||
|             this.emit('drag-end'); | ||||
|             return true; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } | ||||
|         return false; | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _moveHandle: function(absX, absY) { | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const St = imports.gi.St; | ||||
| @@ -43,7 +44,7 @@ const ATIndicator = new Lang.Class({ | ||||
|         this._hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' }); | ||||
|         this._hbox.add_child(new St.Icon({ style_class: 'system-status-icon', | ||||
|                                            icon_name: 'preferences-desktop-accessibility-symbolic' })); | ||||
|         this._hbox.add_child(PopupMenu.unicodeArrow(St.Side.BOTTOM)); | ||||
|         this._hbox.add_child(PopupMenu.arrowIcon(St.Side.BOTTOM)); | ||||
|  | ||||
|         this.actor.add_child(this._hbox); | ||||
|  | ||||
| @@ -94,7 +95,7 @@ const ATIndicator = new Lang.Class({ | ||||
|  | ||||
|         this.actor.visible = alwaysShow || items.some(function(f) { return !!f.state; }); | ||||
|  | ||||
|         return false; | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|     }, | ||||
|  | ||||
|     _queueSyncMenuVisibility: function() { | ||||
|   | ||||
| @@ -2,16 +2,27 @@ | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const GLib = imports.gi.GLib; | ||||
| const GnomeBluetoothApplet = imports.gi.GnomeBluetoothApplet; | ||||
| const Gio = imports.gi.Gio; | ||||
| const GnomeBluetooth = imports.gi.GnomeBluetooth; | ||||
| const Lang = imports.lang; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const Main = imports.ui.main; | ||||
| const MessageTray = imports.ui.messageTray; | ||||
| const PanelMenu = imports.ui.panelMenu; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
|  | ||||
| const BUS_NAME = 'org.gnome.SettingsDaemon.Rfkill'; | ||||
| const OBJECT_PATH = '/org/gnome/SettingsDaemon/Rfkill'; | ||||
|  | ||||
| const RfkillManagerInterface = '<node> \ | ||||
| <interface name="org.gnome.SettingsDaemon.Rfkill"> \ | ||||
| <property name="BluetoothAirplaneMode" type="b" access="readwrite" /> \ | ||||
| <property name="BluetoothHasAirplaneMode" type="b" access="read" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const RfkillManagerProxy = Gio.DBusProxy.makeProxyWrapper(RfkillManagerInterface); | ||||
|  | ||||
| const Indicator = new Lang.Class({ | ||||
|     Name: 'BTIndicator', | ||||
|     Extends: PanelMenu.SystemIndicator, | ||||
| @@ -22,237 +33,76 @@ const Indicator = new Lang.Class({ | ||||
|         this._indicator = this._addIndicator(); | ||||
|         this._indicator.icon_name = 'bluetooth-active-symbolic'; | ||||
|  | ||||
|         this._proxy = new RfkillManagerProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH, | ||||
|                                              Lang.bind(this, function(proxy, error) { | ||||
|                                                  if (error) { | ||||
|                                                      log(error.message); | ||||
|                                                      return; | ||||
|                                                  } | ||||
|  | ||||
|                                                  this._sync(); | ||||
|                                              })); | ||||
|         this._proxy.connect('g-properties-changed', Lang.bind(this, this._sync)); | ||||
|  | ||||
|         // The Bluetooth menu only appears when Bluetooth is in use, | ||||
|         // so just statically build it with a "Turn Off" menu item. | ||||
|         this._item = new PopupMenu.PopupSubMenuMenuItem(_("Bluetooth"), true); | ||||
|         this._item.icon.icon_name = 'bluetooth-active-symbolic'; | ||||
|         this._item.menu.addAction(_("Turn Off"), Lang.bind(this, function() { | ||||
|             this._applet.killswitch_state = GnomeBluetooth.KillswitchState.SOFT_BLOCKED; | ||||
|             this._proxy.BluetoothAirplaneMode = true; | ||||
|         })); | ||||
|         this._item.menu.addSettingsAction(_("Bluetooth Settings"), 'gnome-bluetooth-panel.desktop'); | ||||
|         this.menu.addMenuItem(this._item); | ||||
|  | ||||
|         this._applet = new GnomeBluetoothApplet.Applet(); | ||||
|         this._applet.connect('devices-changed', Lang.bind(this, this._sync)); | ||||
|         this._client = new GnomeBluetooth.Client(); | ||||
|         this._model = this._client.get_model(); | ||||
|         this._model.connect('row-changed', Lang.bind(this, this._sync)); | ||||
|         this._model.connect('row-deleted', Lang.bind(this, this._sync)); | ||||
|         this._model.connect('row-inserted', Lang.bind(this, this._sync)); | ||||
|         Main.sessionMode.connect('updated', Lang.bind(this, this._sync)); | ||||
|         this._sync(); | ||||
|     }, | ||||
|  | ||||
|         this._applet.connect('pincode-request', Lang.bind(this, this._pinRequest)); | ||||
|         this._applet.connect('confirm-request', Lang.bind(this, this._confirmRequest)); | ||||
|         this._applet.connect('auth-request', Lang.bind(this, this._authRequest)); | ||||
|         this._applet.connect('auth-service-request', Lang.bind(this, this._authServiceRequest)); | ||||
|         this._applet.connect('cancel-request', Lang.bind(this, this._cancelRequest)); | ||||
|     _getDefaultAdapter: function() { | ||||
|         let [ret, iter] = this._model.get_iter_first(); | ||||
|         while (ret) { | ||||
|             let isDefault = this._model.get_value(iter, | ||||
|                                                   GnomeBluetooth.Column.DEFAULT); | ||||
|             if (isDefault) | ||||
|                 return iter; | ||||
|             ret = this._model.iter_next(iter); | ||||
|         } | ||||
|         return null; | ||||
|     }, | ||||
|  | ||||
|     _getNConnectedDevices: function() { | ||||
|         let adapter = this._getDefaultAdapter(); | ||||
|         if (!adapter) | ||||
|             return 0; | ||||
|  | ||||
|         let nDevices = 0; | ||||
|         let [ret, iter] = this._model.iter_children(adapter); | ||||
|         while (ret) { | ||||
|             let isConnected = this._model.get_value(iter, | ||||
|                                                     GnomeBluetooth.Column.CONNECTED); | ||||
|             if (isConnected) | ||||
|                 nDevices++; | ||||
|             ret = this._model.iter_next(iter); | ||||
|         } | ||||
|         return nDevices; | ||||
|     }, | ||||
|  | ||||
|     _sync: function() { | ||||
|         let connectedDevices = this._applet.get_devices().filter(function(device) { | ||||
|             return device.connected; | ||||
|         }); | ||||
|         let nDevices = connectedDevices.length; | ||||
|         let nDevices = this._getNConnectedDevices(); | ||||
|         let sensitive = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter; | ||||
|  | ||||
|         let on = nDevices > 0; | ||||
|         this._indicator.visible = on; | ||||
|         this._item.actor.visible = on; | ||||
|         this.menu.setSensitive(sensitive); | ||||
|         this._indicator.visible = nDevices > 0; | ||||
|         this._item.actor.visible = this._proxy.BluetoothHasAirplaneMode && !this._proxy.BluetoothAirplaneMode; | ||||
|  | ||||
|         if (on) | ||||
|         if (nDevices > 0) | ||||
|             this._item.status.text = ngettext("%d Connected Device", "%d Connected Devices", nDevices).format(nDevices); | ||||
|     }, | ||||
|  | ||||
|     _ensureSource: function() { | ||||
|         if (!this._source) { | ||||
|             this._source = new MessageTray.Source(_("Bluetooth"), 'bluetooth-active'); | ||||
|             this._source.policy = new MessageTray.NotificationApplicationPolicy('gnome-bluetooth-panel'); | ||||
|             Main.messageTray.add(this._source); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _authRequest: function(applet, device_path, name, long_name) { | ||||
|         this._ensureSource(); | ||||
|         this._source.notify(new AuthNotification(this._source, this._applet, device_path, name, long_name)); | ||||
|     }, | ||||
|  | ||||
|     _authServiceRequest: function(applet, device_path, name, long_name, uuid) { | ||||
|         this._ensureSource(); | ||||
|         this._source.notify(new AuthServiceNotification(this._source, this._applet, device_path, name, long_name, uuid)); | ||||
|     }, | ||||
|  | ||||
|     _confirmRequest: function(applet, device_path, name, long_name, pin) { | ||||
|         this._ensureSource(); | ||||
|         this._source.notify(new ConfirmNotification(this._source, this._applet, device_path, name, long_name, pin)); | ||||
|     }, | ||||
|  | ||||
|     _pinRequest: function(applet, device_path, name, long_name, numeric) { | ||||
|         this._ensureSource(); | ||||
|         this._source.notify(new PinNotification(this._source, this._applet, device_path, name, long_name, numeric)); | ||||
|     }, | ||||
|  | ||||
|     _cancelRequest: function() { | ||||
|         this._source.destroy(); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const AuthNotification = new Lang.Class({ | ||||
|     Name: 'AuthNotification', | ||||
|     Extends: MessageTray.Notification, | ||||
|  | ||||
|     _init: function(source, applet, device_path, name, long_name) { | ||||
|         this.parent(source, | ||||
|                     _("Bluetooth"), | ||||
|                     _("Authorization request from %s").format(name), | ||||
|                     { customContent: true }); | ||||
|         this.setResident(true); | ||||
|  | ||||
|         this._applet = applet; | ||||
|         this._devicePath = device_path; | ||||
|         this.addBody(_("Device %s wants to pair with this computer").format(long_name)); | ||||
|  | ||||
|         this.addButton('allow', _("Allow")); | ||||
|         this.addButton('deny', _("Deny")); | ||||
|  | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             if (action == 'allow') | ||||
|                 this._applet.agent_reply_confirm(this._devicePath, true); | ||||
|             else | ||||
|                 this._applet.agent_reply_confirm(this._devicePath, false); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const AuthServiceNotification = new Lang.Class({ | ||||
|     Name: 'AuthServiceNotification', | ||||
|     Extends: MessageTray.Notification, | ||||
|  | ||||
|     _init: function(source, applet, device_path, name, long_name, uuid) { | ||||
|         this.parent(source, | ||||
|                     _("Bluetooth"), | ||||
|                     _("Authorization request from %s").format(name), | ||||
|                     { customContent: true }); | ||||
|         this.setResident(true); | ||||
|  | ||||
|         this._applet = applet; | ||||
|         this._devicePath = device_path; | ||||
|         this.addBody(_("Device %s wants access to the service '%s'").format(long_name, uuid)); | ||||
|  | ||||
|         this.addButton('always-grant', _("Always grant access")); | ||||
|         this.addButton('grant', _("Grant this time only")); | ||||
|         this.addButton('reject', _("Reject")); | ||||
|  | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             switch (action) { | ||||
|             case 'always-grant': | ||||
|                 this._applet.agent_reply_auth_service(this._devicePath, true, true); | ||||
|                 break; | ||||
|             case 'grant': | ||||
|                 this._applet.agent_reply_auth_service(this._devicePath, true, false); | ||||
|                 break; | ||||
|             case 'reject': | ||||
|             default: | ||||
|                 this._applet.agent_reply_auth_service(this._devicePath, false, false); | ||||
|             } | ||||
|             this.destroy(); | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const ConfirmNotification = new Lang.Class({ | ||||
|     Name: 'ConfirmNotification', | ||||
|     Extends: MessageTray.Notification, | ||||
|  | ||||
|     _init: function(source, applet, device_path, name, long_name, pin) { | ||||
|         this.parent(source, | ||||
|                     _("Bluetooth"), | ||||
|                     /* Translators: argument is the device short name */ | ||||
|                     _("Pairing confirmation for %s").format(name), | ||||
|                     { customContent: true }); | ||||
|         this.setResident(true); | ||||
|  | ||||
|         this._applet = applet; | ||||
|         this._devicePath = device_path; | ||||
|         this.addBody(_("Device %s wants to pair with this computer").format(long_name)); | ||||
|         this.addBody(_("Please confirm whether the Passkey '%06d' matches the one on the device.").format(pin)); | ||||
|  | ||||
|         /* Translators: this is the verb, not the noun */ | ||||
|         this.addButton('matches', _("Matches")); | ||||
|         this.addButton('does-not-match', _("Does not match")); | ||||
|  | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             if (action == 'matches') | ||||
|                 this._applet.agent_reply_confirm(this._devicePath, true); | ||||
|             else | ||||
|                 this._applet.agent_reply_confirm(this._devicePath, false); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const PinNotification = new Lang.Class({ | ||||
|     Name: 'PinNotification', | ||||
|     Extends: MessageTray.Notification, | ||||
|  | ||||
|     _init: function(source, applet, device_path, name, long_name, numeric) { | ||||
|         this.parent(source, | ||||
|                     _("Bluetooth"), | ||||
|                     _("Pairing request for %s").format(name), | ||||
|                     { customContent: true }); | ||||
|         this.setResident(true); | ||||
|  | ||||
|         this._applet = applet; | ||||
|         this._devicePath = device_path; | ||||
|         this._numeric = numeric; | ||||
|         this.addBody(_("Device %s wants to pair with this computer").format(long_name)); | ||||
|         this.addBody(_("Please enter the PIN mentioned on the device.")); | ||||
|  | ||||
|         this._entry = new St.Entry(); | ||||
|         this._entry.connect('key-release-event', Lang.bind(this, function(entry, event) { | ||||
|             let key = event.get_key_symbol(); | ||||
|             if (key == Clutter.KEY_Return) { | ||||
|                 if (this._canActivateOkButton()) | ||||
|                     this.emit('action-invoked', 'ok'); | ||||
|                 return true; | ||||
|             } else if (key == Clutter.KEY_Escape) { | ||||
|                 this.emit('action-invoked', 'cancel'); | ||||
|                 return true; | ||||
|             } | ||||
|             return false; | ||||
|         })); | ||||
|         this.addActor(this._entry); | ||||
|  | ||||
|         this.addButton('ok', _("OK")); | ||||
|         this.addButton('cancel', _("Cancel")); | ||||
|  | ||||
|         this.setButtonSensitive('ok', this._canActivateOkButton()); | ||||
|         this._entry.clutter_text.connect('text-changed', Lang.bind(this, | ||||
|             function() { | ||||
|                 this.setButtonSensitive('ok', this._canActivateOkButton()); | ||||
|             })); | ||||
|  | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             if (action == 'ok') { | ||||
|                 if (this._numeric) { | ||||
|                     let num = parseInt(this._entry.text); | ||||
|                     if (isNaN(num)) { | ||||
|                         // user reply was empty, or was invalid | ||||
|                         // cancel the operation | ||||
|                         num = -1; | ||||
|                     } | ||||
|                     this._applet.agent_reply_passkey(this._devicePath, num); | ||||
|                 } else | ||||
|                     this._applet.agent_reply_pincode(this._devicePath, this._entry.text); | ||||
|             } else { | ||||
|                 if (this._numeric) | ||||
|                     this._applet.agent_reply_passkey(this._devicePath, -1); | ||||
|                 else | ||||
|                     this._applet.agent_reply_pincode(this._devicePath, null); | ||||
|             } | ||||
|             this.destroy(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _canActivateOkButton: function() { | ||||
|         // PINs have a fixed length of 6 | ||||
|         if (this._numeric) | ||||
|             return this._entry.clutter_text.text.length == 6; | ||||
|         else | ||||
|             return true; | ||||
|     } | ||||
|             this._item.status.text = _("Not Connected"); | ||||
|     }, | ||||
| }); | ||||
|   | ||||
| @@ -11,9 +11,11 @@ const Slider = imports.ui.slider; | ||||
| const BUS_NAME = 'org.gnome.SettingsDaemon.Power'; | ||||
| const OBJECT_PATH = '/org/gnome/SettingsDaemon/Power'; | ||||
|  | ||||
| const BrightnessInterface = <interface name="org.gnome.SettingsDaemon.Power.Screen"> | ||||
| <property name='Brightness' type='i' access='readwrite'/> | ||||
| </interface>; | ||||
| const BrightnessInterface = '<node> \ | ||||
| <interface name="org.gnome.SettingsDaemon.Power.Screen"> \ | ||||
| <property name="Brightness" type="i" access="readwrite"/> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const BrightnessProxy = Gio.DBusProxy.makeProxyWrapper(BrightnessInterface); | ||||
|  | ||||
| @@ -46,7 +48,7 @@ const Indicator = new Lang.Class({ | ||||
|         this._item.actor.add(icon); | ||||
|         this._item.actor.add(this._slider.actor, { expand: true }); | ||||
|         this._item.actor.connect('button-press-event', Lang.bind(this, function(actor, event) { | ||||
|             this._slider.startDragging(event); | ||||
|             return this._slider.startDragging(event); | ||||
|         })); | ||||
|         this._item.actor.connect('key-press-event', Lang.bind(this, function(actor, event) { | ||||
|             return this._slider.onKeyPressEvent(actor, event); | ||||
|   | ||||
| @@ -41,11 +41,13 @@ const MAX_INPUT_SOURCE_ACTIVATION_TIME = 4000; // ms | ||||
| const BUS_NAME = 'org.gnome.SettingsDaemon.Keyboard'; | ||||
| const OBJECT_PATH = '/org/gnome/SettingsDaemon/Keyboard'; | ||||
|  | ||||
| const KeyboardManagerInterface = <interface name="org.gnome.SettingsDaemon.Keyboard"> | ||||
| <method name="SetInputSource"> | ||||
|     <arg type="u" direction="in" /> | ||||
| </method> | ||||
| </interface>; | ||||
| const KeyboardManagerInterface = '<node> \ | ||||
| <interface name="org.gnome.SettingsDaemon.Keyboard"> \ | ||||
| <method name="SetInputSource"> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const KeyboardManagerProxy = Gio.DBusProxy.makeProxyWrapper(KeyboardManagerInterface); | ||||
|  | ||||
| @@ -290,6 +292,10 @@ const InputSourcePopup = new Lang.Class({ | ||||
|             this._select(this._previous()); | ||||
|         else if (keysym == Clutter.Right) | ||||
|             this._select(this._next()); | ||||
|         else | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     _finish : function() { | ||||
| @@ -339,7 +345,7 @@ const InputSourceIndicator = new Lang.Class({ | ||||
|  | ||||
|         this._hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' }); | ||||
|         this._hbox.add_child(this._container); | ||||
|         this._hbox.add_child(PopupMenu.unicodeArrow(St.Side.BOTTOM)); | ||||
|         this._hbox.add_child(PopupMenu.arrowIcon(St.Side.BOTTOM)); | ||||
|  | ||||
|         this.actor.add_child(this._hbox); | ||||
|         this.actor.add_style_class_name('panel-status-button'); | ||||
|   | ||||
							
								
								
									
										201
									
								
								js/ui/status/location.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								js/ui/status/location.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,201 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const GLib = imports.gi.GLib; | ||||
| const Gio = imports.gi.Gio; | ||||
| const Lang = imports.lang; | ||||
|  | ||||
| const Main = imports.ui.main; | ||||
| const PanelMenu = imports.ui.panelMenu; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
| const Shell = imports.gi.Shell; | ||||
|  | ||||
| const LOCATION_SCHEMA = 'org.gnome.shell.location'; | ||||
| const MAX_ACCURACY_LEVEL = 'max-accuracy-level'; | ||||
|  | ||||
| var GeoclueIface = '<node> \ | ||||
|   <interface name="org.freedesktop.GeoClue2.Manager"> \ | ||||
|     <property name="InUse" type="b" access="read"/> \ | ||||
|     <property name="AvailableAccuracyLevel" type="u" access="read"/> \ | ||||
|     <method name="AddAgent"> \ | ||||
|       <arg name="id" type="s" direction="in"/> \ | ||||
|     </method> \ | ||||
|   </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const GeoclueManager = Gio.DBusProxy.makeProxyWrapper(GeoclueIface); | ||||
|  | ||||
| var AgentIface = '<node> \ | ||||
|   <interface name="org.freedesktop.GeoClue2.Agent"> \ | ||||
|     <property name="MaxAccuracyLevel" type="u" access="read"/> \ | ||||
|     <method name="AuthorizeApp"> \ | ||||
|       <arg name="desktop_id" type="s" direction="in"/> \ | ||||
|       <arg name="req_accuracy_level" type="u" direction="in"/> \ | ||||
|       <arg name="authorized" type="b" direction="out"/> \ | ||||
|       <arg name="allowed_accuracy_level" type="u" direction="out"/> \ | ||||
|     </method> \ | ||||
|   </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const Indicator = new Lang.Class({ | ||||
|     Name: 'LocationIndicator', | ||||
|     Extends: PanelMenu.SystemIndicator, | ||||
|  | ||||
|     _init: function() { | ||||
|         this.parent(); | ||||
|  | ||||
|         this._settings = new Gio.Settings({ schema: LOCATION_SCHEMA }); | ||||
|         this._settings.connect('changed::' + MAX_ACCURACY_LEVEL, | ||||
|                                Lang.bind(this, this._onMaxAccuracyLevelChanged)); | ||||
|  | ||||
|         this._indicator = this._addIndicator(); | ||||
|         this._indicator.icon_name = 'find-location-symbolic'; | ||||
|  | ||||
|         this._item = new PopupMenu.PopupSubMenuMenuItem(_("Location"), true); | ||||
|         this._item.icon.icon_name = 'find-location-symbolic'; | ||||
|  | ||||
|         this._agent = Gio.DBusExportedObject.wrapJSObject(AgentIface, this); | ||||
|         this._agent.export(Gio.DBus.system, '/org/freedesktop/GeoClue2/Agent'); | ||||
|  | ||||
|         this._item.status.text = _("Enabled"); | ||||
|         this._onOffAction = this._item.menu.addAction(_("Disable"), Lang.bind(this, this._onOnOffAction)); | ||||
|  | ||||
|         this.menu.addMenuItem(this._item); | ||||
|  | ||||
|         this._watchId = Gio.bus_watch_name(Gio.BusType.SYSTEM, | ||||
|                                            'org.freedesktop.GeoClue2', | ||||
|                                            0, | ||||
|                                            Lang.bind(this, this._connectToGeoclue), | ||||
|                                            Lang.bind(this, this._onGeoclueVanished)); | ||||
|         Main.sessionMode.connect('updated', Lang.bind(this, this._onSessionUpdated)); | ||||
|         this._onSessionUpdated(); | ||||
|         this._onMaxAccuracyLevelChanged(); | ||||
|         this._connectToGeoclue(); | ||||
|     }, | ||||
|  | ||||
|     get MaxAccuracyLevel() { | ||||
|         return this._getMaxAccuracyLevel(); | ||||
|     }, | ||||
|  | ||||
|     // We (and geoclue) have currently no way to reliably identifying apps so | ||||
|     // for now, lets just authorize all apps as long as they provide a valid | ||||
|     // desktop ID. We also ensure they don't get more accuracy than global max. | ||||
|     AuthorizeApp: function(desktop_id, reqAccuracyLevel) { | ||||
|         var appSystem = Shell.AppSystem.get_default(); | ||||
|         var app = appSystem.lookup_app(desktop_id + ".desktop"); | ||||
|         if (app == null) { | ||||
|             return [false, 0]; | ||||
|         } | ||||
|  | ||||
|         let allowedAccuracyLevel = clamp(reqAccuracyLevel, 0, this._getMaxAccuracyLevel()); | ||||
|         return [true, allowedAccuracyLevel]; | ||||
|     }, | ||||
|  | ||||
|     _syncIndicator: function() { | ||||
|         if (this._proxy == null) { | ||||
|             this._indicator.visible = false; | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this._indicator.visible = this._proxy.InUse; | ||||
|         this._updateMenuLabels(); | ||||
|     }, | ||||
|  | ||||
|     _connectToGeoclue: function() { | ||||
|         if (this._proxy != null || this._connecting) | ||||
|             return false; | ||||
|  | ||||
|         this._connecting = true; | ||||
|         new GeoclueManager(Gio.DBus.system, | ||||
|                            'org.freedesktop.GeoClue2', | ||||
|                            '/org/freedesktop/GeoClue2/Manager', | ||||
|                            Lang.bind(this, this._onProxyReady)); | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _onProxyReady: function(proxy, error) { | ||||
|         if (error != null) { | ||||
|             log(error.message); | ||||
|             this._connecting = false; | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this._proxy = proxy; | ||||
|         this._propertiesChangedId = this._proxy.connect('g-properties-changed', | ||||
|                                                         Lang.bind(this, this._onGeocluePropsChanged)); | ||||
|  | ||||
|         this._availableAccuracyLevel = this._proxy.AvailableAccuracyLevel; | ||||
|         this._syncIndicator(); | ||||
|  | ||||
|         this._proxy.AddAgentRemote('gnome-shell', Lang.bind(this, this._onAgentRegistered)); | ||||
|     }, | ||||
|  | ||||
|     _onAgentRegistered: function(result, error) { | ||||
|         this._connecting = false; | ||||
|         this._notifyMaxAccuracyLevel(); | ||||
|  | ||||
|         if (error != null) | ||||
|             log(error.message); | ||||
|     }, | ||||
|  | ||||
|     _onGeoclueVanished: function() { | ||||
|         if (this._propertiesChangedId) { | ||||
|             this._proxy.disconnect(this._propertiesChangedId); | ||||
|             this._propertiesChangedId = 0; | ||||
|         } | ||||
|         this._proxy = null; | ||||
|  | ||||
|         this._syncIndicator(); | ||||
|     }, | ||||
|  | ||||
|     _onOnOffAction: function() { | ||||
|         if (this._getMaxAccuracyLevel() == 0) | ||||
|             this._settings.set_enum(MAX_ACCURACY_LEVEL, this._availableAccuracyLevel); | ||||
|         else | ||||
|             this._settings.set_enum(MAX_ACCURACY_LEVEL, 0); | ||||
|     }, | ||||
|  | ||||
|     _onSessionUpdated: function() { | ||||
|         let sensitive = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter; | ||||
|         this.menu.setSensitive(sensitive); | ||||
|     }, | ||||
|  | ||||
|     _updateMenuLabels: function() { | ||||
|         if (this._getMaxAccuracyLevel() == 0) { | ||||
|             this._item.status.text = _("Disabled"); | ||||
|             this._onOffAction.label.text = _("Enable"); | ||||
|         } else { | ||||
|             this._item.status.text = this._indicator.visible ? _("In Use") : _("Enabled"); | ||||
|             this._onOffAction.label.text = _("Disable"); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _onMaxAccuracyLevelChanged: function() { | ||||
|         this._updateMenuLabels(); | ||||
|  | ||||
|         // Gotta ensure geoclue is up and we are registered as agent to it | ||||
|         // before we emit the notify for this property change. | ||||
|         if (!this._connectToGeoclue()) | ||||
|             this._notifyMaxAccuracyLevel(); | ||||
|     }, | ||||
|  | ||||
|     _getMaxAccuracyLevel: function() { | ||||
|         return this._settings.get_enum(MAX_ACCURACY_LEVEL); | ||||
|     }, | ||||
|  | ||||
|     _notifyMaxAccuracyLevel: function() { | ||||
|         let variant = new GLib.Variant('u', this._getMaxAccuracyLevel()); | ||||
|         this._agent.emit_property_changed('MaxAccuracyLevel', variant); | ||||
|     }, | ||||
|  | ||||
|     _onGeocluePropsChanged: function(proxy, properties) { | ||||
|         let unpacked = properties.deep_unpack(); | ||||
|         if ("InUse" in unpacked) | ||||
|             this._syncIndicator(); | ||||
|         if ("AvailableAccuracyLevel" in unpacked) | ||||
|             this._availableAccuracyLevel = this._proxy.AvailableAccuracyLevel; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| function clamp(value, min, max) { | ||||
|     return Math.max(min, Math.min(max, value)); | ||||
| } | ||||
| @@ -9,19 +9,22 @@ const NetworkManager = imports.gi.NetworkManager; | ||||
| const NMClient = imports.gi.NMClient; | ||||
| const NMGtk = imports.gi.NMGtk; | ||||
| const Signals = imports.signals; | ||||
| const Shell = imports.gi.Shell; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const Hash = imports.misc.hash; | ||||
| const Animation = imports.ui.animation; | ||||
| const Main = imports.ui.main; | ||||
| const PanelMenu = imports.ui.panelMenu; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
| const MessageTray = imports.ui.messageTray; | ||||
| const ModalDialog = imports.ui.modalDialog; | ||||
| const ModemManager = imports.misc.modemManager; | ||||
| const Rfkill = imports.ui.status.rfkill; | ||||
| const Util = imports.misc.util; | ||||
|  | ||||
| const NMConnectionCategory = { | ||||
|     INVALID: 'invalid', | ||||
|     WIRED: 'wired', | ||||
|     WIRELESS: 'wireless', | ||||
|     WWAN: 'wwan', | ||||
|     VPN: 'vpn' | ||||
| @@ -102,18 +105,34 @@ const NMConnectionItem = new Lang.Class({ | ||||
|         this._activeConnection = null; | ||||
|         this._activeConnectionChangedId = 0; | ||||
|  | ||||
|         this._buildUI(); | ||||
|         this._sync(); | ||||
|     }, | ||||
|  | ||||
|     _buildUI: function() { | ||||
|         this.labelItem = new PopupMenu.PopupMenuItem(''); | ||||
|         this.labelItem.connect('activate', Lang.bind(this, this._toggle)); | ||||
|  | ||||
|         this.switchItem = new PopupMenu.PopupSwitchMenuItem(connection.get_id(), false); | ||||
|         this.switchItem.connect('toggled', Lang.bind(this, this._toggle)); | ||||
|  | ||||
|         this._sync(); | ||||
|         this.radioItem = new PopupMenu.PopupMenuItem(this._connection.get_id(), false); | ||||
|         this.radioItem.connect('activate', Lang.bind(this, this._activate)); | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|         this.labelItem.destroy(); | ||||
|         this.switchItem.destroy(); | ||||
|         this.radioItem.destroy(); | ||||
|     }, | ||||
|  | ||||
|     updateForConnection: function(connection) { | ||||
|         // connection should always be the same object | ||||
|         // (and object path) as this._connection, but | ||||
|         // this can be false if NetworkManager was restarted | ||||
|         // and picked up connections in a different order | ||||
|         // Just to be safe, we set it here again | ||||
|  | ||||
|         this._connection = connection; | ||||
|         this.radioItem.label.text = connection.get_id(); | ||||
|         this._sync(); | ||||
|         this.emit('name-changed'); | ||||
|     }, | ||||
|  | ||||
|     getName: function() { | ||||
| @@ -129,9 +148,8 @@ const NMConnectionItem = new Lang.Class({ | ||||
|  | ||||
|     _sync: function() { | ||||
|         let isActive = this.isActive(); | ||||
|         this.labelItem.label.text = isActive ? _("Turn Off") : _("Connect"); | ||||
|         this.switchItem.setToggleState(isActive); | ||||
|         this.switchItem.setStatus(this._getStatus()); | ||||
|         this.labelItem.label.text = isActive ? _("Turn Off") : this._section.getConnectLabel(); | ||||
|         this.radioItem.setOrnament(isActive ? PopupMenu.Ornament.DOT : PopupMenu.Ornament.NONE); | ||||
|         this.emit('icon-changed'); | ||||
|     }, | ||||
|  | ||||
| @@ -144,8 +162,11 @@ const NMConnectionItem = new Lang.Class({ | ||||
|         this._sync(); | ||||
|     }, | ||||
|  | ||||
|     _getStatus: function() { | ||||
|         return null; | ||||
|     _activate: function() { | ||||
|         if (this._activeConnection == null) | ||||
|             this._section.activateConnection(this._connection); | ||||
|  | ||||
|         this._sync(); | ||||
|     }, | ||||
|  | ||||
|     _connectionStateChanged: function(ac, newstate, reason) { | ||||
| @@ -176,15 +197,15 @@ const NMConnectionSection = new Lang.Class({ | ||||
|     _init: function(client) { | ||||
|         this._client = client; | ||||
|  | ||||
|         this._connectionItems = new Hash.Map(); | ||||
|         this._connectionItems = new Map(); | ||||
|         this._connections = []; | ||||
|  | ||||
|         this._labelSection = new PopupMenu.PopupMenuSection(); | ||||
|         this._switchSection = new PopupMenu.PopupMenuSection(); | ||||
|         this._radioSection = new PopupMenu.PopupMenuSection(); | ||||
|  | ||||
|         this.item = new PopupMenu.PopupSubMenuMenuItem('', true); | ||||
|         this.item.menu.addMenuItem(this._labelSection); | ||||
|         this.item.menu.addMenuItem(this._switchSection); | ||||
|         this.item.menu.addMenuItem(this._radioSection); | ||||
|  | ||||
|         this.connect('icon-changed', Lang.bind(this, this._sync)); | ||||
|     }, | ||||
| @@ -194,9 +215,9 @@ const NMConnectionSection = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _sync: function() { | ||||
|         let nItems = this._connectionItems.size(); | ||||
|         let nItems = this._connectionItems.size; | ||||
|  | ||||
|         this._switchSection.actor.visible = (nItems > 1); | ||||
|         this._radioSection.actor.visible = (nItems > 1); | ||||
|         this._labelSection.actor.visible = (nItems == 1); | ||||
|  | ||||
|         this.item.status.text = this._getStatus(); | ||||
| @@ -211,19 +232,12 @@ const NMConnectionSection = new Lang.Class({ | ||||
|             this.item.label.text = ''; | ||||
|     }, | ||||
|  | ||||
|     _getStatus: function() { | ||||
|         let values = this._connectionItems.values(); | ||||
|         for (let i = 0; i < values.length; i++) { | ||||
|             let item = values[i]; | ||||
|             if (item.isActive()) | ||||
|                 return item.getName(); | ||||
|         } | ||||
|  | ||||
|         return _("Off"); | ||||
|     _getMenuIcon: function() { | ||||
|         return this.getIndicatorIcon(); | ||||
|     }, | ||||
|  | ||||
|     _hasConnection: function(connection) { | ||||
|         return this._connectionItems.has(connection.get_uuid()); | ||||
|     getConnectLabel: function() { | ||||
|         return _("Connect"); | ||||
|     }, | ||||
|  | ||||
|     _connectionValid: function(connection) { | ||||
| @@ -231,10 +245,7 @@ const NMConnectionSection = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _connectionSortFunction: function(one, two) { | ||||
|         if (one._timestamp == two._timestamp) | ||||
|             return GLib.utf8_collate(one.get_id(), two.get_id()); | ||||
|  | ||||
|         return two._timestamp - one._timestamp; | ||||
|         return GLib.utf8_collate(one.get_id(), two.get_id()); | ||||
|     }, | ||||
|  | ||||
|     _makeConnectionItem: function(connection) { | ||||
| @@ -245,10 +256,20 @@ const NMConnectionSection = new Lang.Class({ | ||||
|         if (!this._connectionValid(connection)) | ||||
|             return; | ||||
|  | ||||
|         if (this._hasConnection(connection)) | ||||
|             return; | ||||
|         // This function is called everytime connection is added or updated | ||||
|         // In the usual case, we already added this connection and UUID | ||||
|         // didn't change. So we need to check if we already have an item, | ||||
|         // and update it for properties in the connection that changed | ||||
|         // (the only one we care about is the name) | ||||
|         // But it's also possible we didn't know about this connection | ||||
|         // (eg, during coldplug, or because it was updated and suddenly | ||||
|         // it's valid for this device), in which case we add a new item | ||||
|  | ||||
|         this._addConnection(connection); | ||||
|         let item = this._connectionItems.get(connection.get_uuid()); | ||||
|         if (item) | ||||
|             item.updateForConnection(connection); | ||||
|         else | ||||
|             this._addConnection(connection); | ||||
|     }, | ||||
|  | ||||
|     _addConnection: function(connection) { | ||||
| @@ -262,17 +283,23 @@ const NMConnectionSection = new Lang.Class({ | ||||
|         item.connect('activation-failed', Lang.bind(this, function(item, reason) { | ||||
|             this.emit('activation-failed', reason); | ||||
|         })); | ||||
|         item.connect('name-changed', Lang.bind(this, this._sync)); | ||||
|  | ||||
|         let pos = Util.insertSorted(this._connections, connection, Lang.bind(this, this._connectionSortFunction)); | ||||
|         this._labelSection.addMenuItem(item.labelItem, pos); | ||||
|         this._switchSection.addMenuItem(item.switchItem, pos); | ||||
|         this._radioSection.addMenuItem(item.radioItem, pos); | ||||
|         this._connectionItems.set(connection.get_uuid(), item); | ||||
|         this._sync(); | ||||
|     }, | ||||
|  | ||||
|     removeConnection: function(connection) { | ||||
|         this._connectionItems.get(connection.get_uuid()).destroy(); | ||||
|         this._connectionItems.delete(connection.get_uuid()); | ||||
|         let uuid = connection.get_uuid(); | ||||
|         let item = this._connectionItems.get(uuid); | ||||
|         if (item == undefined) | ||||
|             return; | ||||
|  | ||||
|         item.destroy(); | ||||
|         this._connectionItems.delete(uuid); | ||||
|  | ||||
|         let pos = this._connections.indexOf(connection); | ||||
|         this._connections.splice(pos, 1); | ||||
| @@ -293,11 +320,24 @@ const NMConnectionDevice = new Lang.Class({ | ||||
|         this._settings = settings; | ||||
|  | ||||
|         this._autoConnectItem = this.item.menu.addAction(_("Connect"), Lang.bind(this, this._autoConnect)); | ||||
|         this._deactivateItem = this._radioSection.addAction(_("Turn Off"), Lang.bind(this, this.deactivateConnection)); | ||||
|  | ||||
|         this._stateChangedId = this._device.connect('state-changed', Lang.bind(this, this._deviceStateChanged)); | ||||
|         this._activeConnectionChangedId = this._device.connect('notify::active-connection', Lang.bind(this, this._activeConnectionChanged)); | ||||
|     }, | ||||
|  | ||||
|     _canReachInternet: function() { | ||||
|         if (this._client.primary_connection != this._device.active_connection) | ||||
|             return true; | ||||
|  | ||||
|         return this._client.connectivity == NetworkManager.ConnectivityState.FULL; | ||||
|     }, | ||||
|  | ||||
|     _autoConnect: function() { | ||||
|         let connection = new NetworkManager.Connection(); | ||||
|         this._client.add_and_activate_connection(connection, this._device, null, null); | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|         if (this._stateChangedId) { | ||||
|             GObject.Object.prototype.disconnect.call(this._device, this._stateChangedId); | ||||
| @@ -305,7 +345,7 @@ const NMConnectionDevice = new Lang.Class({ | ||||
|         } | ||||
|         if (this._activeConnectionChangedId) { | ||||
|             GObject.Object.prototype.disconnect.call(this._device, this._activeConnectionChangedId); | ||||
|             this._stateChangedId = 0; | ||||
|             this._activeConnectionChangedId = 0; | ||||
|         } | ||||
|  | ||||
|         this.parent(); | ||||
| @@ -365,8 +405,9 @@ const NMConnectionDevice = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _sync: function() { | ||||
|         let nItems = this._connectionItems.size(); | ||||
|         let nItems = this._connectionItems.size; | ||||
|         this._autoConnectItem.actor.visible = (nItems == 0); | ||||
|         this._deactivateItem.actor.visible = this._device.state > NetworkManager.DeviceState.DISCONNECTED; | ||||
|         this.parent(); | ||||
|     }, | ||||
|  | ||||
| @@ -378,7 +419,7 @@ const NMConnectionDevice = new Lang.Class({ | ||||
|         case NetworkManager.DeviceState.DISCONNECTED: | ||||
|             return _("Off"); | ||||
|         case NetworkManager.DeviceState.ACTIVATED: | ||||
|             return this.parent(); | ||||
|             return _("Connected"); | ||||
|         case NetworkManager.DeviceState.UNMANAGED: | ||||
|             /* Translators: this is for network devices that are physically present but are not | ||||
|                under NetworkManager's control (and thus cannot be used in the menu) */ | ||||
| @@ -415,6 +456,48 @@ const NMConnectionDevice = new Lang.Class({ | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| const NMDeviceWired = new Lang.Class({ | ||||
|     Name: 'NMDeviceWired', | ||||
|     Extends: NMConnectionDevice, | ||||
|     category: NMConnectionCategory.WIRED, | ||||
|  | ||||
|     _init: function(client, device, settings) { | ||||
|         this.parent(client, device, settings); | ||||
|  | ||||
|         this.item.menu.addMenuItem(createSettingsAction(_("Wired Settings"), device)); | ||||
|     }, | ||||
|  | ||||
|     _hasCarrier: function() { | ||||
|         if (this._device instanceof NMClient.DeviceEthernet) | ||||
|             return this._device.carrier; | ||||
|         else | ||||
|             return true; | ||||
|     }, | ||||
|  | ||||
|     _sync: function() { | ||||
|         this.item.actor.visible = this._hasCarrier(); | ||||
|         this.parent(); | ||||
|     }, | ||||
|  | ||||
|     getIndicatorIcon: function() { | ||||
|         if (this._device.active_connection) { | ||||
|             let state = this._device.active_connection.state; | ||||
|  | ||||
|             if (state == NetworkManager.ActiveConnectionState.ACTIVATING) { | ||||
|                 return 'network-wired-acquiring-symbolic'; | ||||
|             } else if (state == NetworkManager.ActiveConnectionState.ACTIVATED) { | ||||
|                 if (this._canReachInternet()) | ||||
|                     return 'network-wired-symbolic'; | ||||
|                 else | ||||
|                     return 'network-wired-no-route-symbolic'; | ||||
|             } else { | ||||
|                 return 'network-wired-disconnected-symbolic'; | ||||
|             } | ||||
|         } else | ||||
|             return 'network-wired-disconnected-symbolic'; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const NMDeviceModem = new Lang.Class({ | ||||
|     Name: 'NMDeviceModem', | ||||
|     Extends: NMConnectionDevice, | ||||
| @@ -477,28 +560,20 @@ const NMDeviceModem = new Lang.Class({ | ||||
|             return this.parent(); | ||||
|     }, | ||||
|  | ||||
|     _getMenuIcon: function() { | ||||
|         if (this._device.active_connection) | ||||
|             return this.getIndicatorIcon(); | ||||
|         else | ||||
|     getIndicatorIcon: function() { | ||||
|         if (this._device.active_connection) { | ||||
|             if (this._device.active_connection.state == NetworkManager.ActiveConnectionState.ACTIVATING) | ||||
|                 return 'network-cellular-acquiring-symbolic'; | ||||
|  | ||||
|             return this._getSignalIcon(); | ||||
|         } else { | ||||
|             return 'network-cellular-signal-none-symbolic'; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _getSignalIcon: function() { | ||||
|         return 'network-cellular-signal-' + signalToIcon(this._mobileDevice.signal_quality) + '-symbolic'; | ||||
|     }, | ||||
|  | ||||
|     getIndicatorIcon: function() { | ||||
|         if (this._device.active_connection.state == NetworkManager.ActiveConnectionState.ACTIVATING) | ||||
|             return 'network-cellular-acquiring-symbolic'; | ||||
|  | ||||
|         if (!this._mobileDevice) { | ||||
|             // this can happen for bluetooth in PAN mode | ||||
|             return 'network-cellular-connected-symbolic'; | ||||
|         } | ||||
|  | ||||
|         return this._getSignalIcon(); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const NMDeviceBluetooth = new Lang.Class({ | ||||
| @@ -512,33 +587,26 @@ const NMDeviceBluetooth = new Lang.Class({ | ||||
|         this.item.menu.addMenuItem(createSettingsAction(_("Mobile Broadband Settings"), device)); | ||||
|     }, | ||||
|  | ||||
|     _autoConnect: function() { | ||||
|         // FIXME: DUN devices are configured like modems, so | ||||
|         // We need to spawn the mobile wizard | ||||
|         // but the network panel doesn't support bluetooth at the moment | ||||
|         // so we just create an empty connection and hope | ||||
|         // that this phone supports PAN | ||||
|  | ||||
|         let connection = new NetworkManager.Connection(); | ||||
|         this._client.add_and_activate_connection(connection, this._device, null, null); | ||||
|         return true; | ||||
|     _getDescription: function() { | ||||
|         return this._device.name; | ||||
|     }, | ||||
|  | ||||
|     _getMenuIcon: function() { | ||||
|         if (this._device.active_connection) | ||||
|             return this.getIndicatorIcon(); | ||||
|         else | ||||
|             return 'network-cellular-signal-none-symbolic'; | ||||
|     getConnectLabel: function() { | ||||
|         return _("Use as Internet connection"); | ||||
|     }, | ||||
|  | ||||
|     getIndicatorIcon: function() { | ||||
|         let state = this._device.active_connection.state; | ||||
|         if (state == NetworkManager.ActiveConnectionState.ACTIVATING) | ||||
|             return 'network-cellular-acquiring-symbolic'; | ||||
|         else if (state == NetworkManager.ActiveConnectionState.ACTIVATED) | ||||
|             return 'network-cellular-connected-symbolic'; | ||||
|         else | ||||
|         if (this._device.active_connection) { | ||||
|             let state = this._device.active_connection.state; | ||||
|             if (state == NetworkManager.ActiveConnectionState.ACTIVATING) | ||||
|                 return 'network-cellular-acquiring-symbolic'; | ||||
|             else if (state == NetworkManager.ActiveConnectionState.ACTIVATED) | ||||
|                 return 'network-cellular-connected-symbolic'; | ||||
|             else | ||||
|                 return 'network-cellular-signal-none-symbolic'; | ||||
|         } else { | ||||
|             return 'network-cellular-signal-none-symbolic'; | ||||
|         } | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -549,33 +617,30 @@ const NMWirelessDialogItem = new Lang.Class({ | ||||
|         this._network = network; | ||||
|         this._ap = network.accessPoints[0]; | ||||
|  | ||||
|         this.actor = new St.Button({ style_class: 'nm-dialog-item', | ||||
|                                      can_focus: true, | ||||
|                                      x_fill: true }); | ||||
|         this.actor = new St.BoxLayout({ style_class: 'nm-dialog-item', | ||||
|                                         can_focus: true, | ||||
|                                         reactive: true }); | ||||
|         this.actor.connect('key-focus-in', Lang.bind(this, function() { | ||||
|             this.emit('selected'); | ||||
|         })); | ||||
|         this.actor.connect('clicked', Lang.bind(this, function() { | ||||
|         let action = new Clutter.ClickAction(); | ||||
|         action.connect('clicked', Lang.bind(this, function() { | ||||
|             this.actor.grab_key_focus(); | ||||
|         })); | ||||
|  | ||||
|         this.actor.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent)); | ||||
|  | ||||
|         this._content = new St.BoxLayout({ style_class: 'nm-dialog-item-box' }); | ||||
|         this.actor.set_child(this._content); | ||||
|         this.actor.add_action(action); | ||||
|  | ||||
|         let title = ssidToLabel(this._ap.get_ssid()); | ||||
|         this._label = new St.Label({ text: title }); | ||||
|  | ||||
|         this.actor.label_actor = this._label; | ||||
|         this._content.add(this._label, { x_align: St.Align.START }); | ||||
|         this.actor.add(this._label, { x_align: St.Align.START }); | ||||
|  | ||||
|         this._selectedIcon = new St.Icon({ style_class: 'nm-dialog-icon', | ||||
|                                            icon_name: 'object-select-symbolic' }); | ||||
|         this._content.add(this._selectedIcon); | ||||
|         this.actor.add(this._selectedIcon); | ||||
|  | ||||
|         this._icons = new St.BoxLayout({ style_class: 'nm-dialog-icons' }); | ||||
|         this._content.add(this._icons, { expand: true, x_fill: false, x_align: St.Align.END }); | ||||
|         this.actor.add(this._icons, { expand: true, x_fill: false, x_align: St.Align.END }); | ||||
|  | ||||
|         this._secureIcon = new St.Icon({ style_class: 'nm-dialog-icon' }); | ||||
|         if (this._ap._secType != NMAccessPointSecurity.NONE) | ||||
| @@ -606,16 +671,6 @@ const NMWirelessDialogItem = new Lang.Class({ | ||||
|             return 'network-workgroup-symbolic'; | ||||
|         else | ||||
|             return 'network-wireless-signal-' + signalToIcon(this._ap.strength) + '-symbolic'; | ||||
|     }, | ||||
|  | ||||
|     _onKeyPressEvent: function(actor, event) { | ||||
|         let symbol = event.get_key_symbol(); | ||||
|         if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return | ||||
|             || symbol == Clutter.KEY_KP_Enter) { | ||||
|             this.emit('connect'); | ||||
|             return true; | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(NMWirelessDialogItem.prototype); | ||||
| @@ -630,6 +685,13 @@ const NMWirelessDialog = new Lang.Class({ | ||||
|         this._client = client; | ||||
|         this._device = device; | ||||
|  | ||||
|         this._wirelessEnabledChangedId = this._client.connect('notify::wireless-enabled', | ||||
|                                                               Lang.bind(this, this._syncView)); | ||||
|  | ||||
|         this._rfkill = Rfkill.getRfkillManager(); | ||||
|         this._airplaneModeChangedId = this._rfkill.connect('airplane-mode-changed', | ||||
|                                                            Lang.bind(this, this._syncView)); | ||||
|  | ||||
|         this._networks = []; | ||||
|         this._buildLayout(); | ||||
|  | ||||
| @@ -651,6 +713,12 @@ const NMWirelessDialog = new Lang.Class({ | ||||
|         this._selectedNetwork = null; | ||||
|         this._activeApChanged(); | ||||
|         this._updateSensitivity(); | ||||
|         this._syncView(); | ||||
|  | ||||
|         if (accessPoints.length == 0) { | ||||
|             /* If there are no visible access points, request a scan */ | ||||
|             this._device.request_scan_simple(null); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
| @@ -666,6 +734,14 @@ const NMWirelessDialog = new Lang.Class({ | ||||
|             GObject.Object.prototype.disconnect.call(this._device, this._activeApChangedId); | ||||
|             this._activeApChangedId = 0; | ||||
|         } | ||||
|         if (this._wirelessEnabledChangedId) { | ||||
|             this._client.disconnect(this._wirelessEnabledChangedId); | ||||
|             this._wirelessEnabledChangedId = 0; | ||||
|         } | ||||
|         if (this._airplaneModeChangedId) { | ||||
|             this._rfkill.disconnect(this._airplaneModeChangedId); | ||||
|             this._airplaneModeChangedId = 0; | ||||
|         } | ||||
|  | ||||
|         this.parent(); | ||||
|     }, | ||||
| @@ -687,13 +763,44 @@ const NMWirelessDialog = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _updateSensitivity: function() { | ||||
|         let connectSensitive = this._selectedNetwork && (this._selectedNetwork != this._activeNetwork); | ||||
|         let connectSensitive = this._client.wireless_enabled && this._selectedNetwork && (this._selectedNetwork != this._activeNetwork); | ||||
|         this._connectButton.reactive = connectSensitive; | ||||
|         this._connectButton.can_focus = connectSensitive; | ||||
|     }, | ||||
|  | ||||
|     _updateVisibility: function() { | ||||
|         this._noNetworksLabel.visible = (this._networks.length == 0); | ||||
|     _syncView: function() { | ||||
|         if (this._rfkill.airplaneMode) { | ||||
|             this._airplaneBox.show(); | ||||
|  | ||||
|             this._airplaneIcon.icon_name = 'airplane-mode-symbolic'; | ||||
|             this._airplaneHeadline.text = _("Airplane Mode is On"); | ||||
|             this._airplaneText.text = _("Wi-Fi is disabled when airplane mode is on."); | ||||
|             this._airplaneButton.label = _("Turn Off Airplane Mode"); | ||||
|  | ||||
|             this._airplaneButton.visible = !this._rfkill.hwAirplaneMode; | ||||
|             this._airplaneInactive.visible = this._rfkill.hwAirplaneMode; | ||||
|             this._noNetworksBox.hide(); | ||||
|         } else if (!this._client.wireless_enabled) { | ||||
|             this._airplaneBox.show(); | ||||
|  | ||||
|             this._airplaneIcon.icon_name = 'dialog-information-symbolic'; | ||||
|             this._airplaneHeadline.text = _("Wi-Fi is Off"); | ||||
|             this._airplaneText.text = _("Wi-Fi needs to be turned on in order to connect to a network."); | ||||
|             this._airplaneButton.label = _("Turn On Wi-Fi"); | ||||
|  | ||||
|             this._airplaneButton.show(); | ||||
|             this._airplaneInactive.hide(); | ||||
|             this._noNetworksBox.hide(); | ||||
|         } else { | ||||
|             this._airplaneBox.hide(); | ||||
|  | ||||
|             this._noNetworksBox.visible = (this._networks.length == 0); | ||||
|         } | ||||
|  | ||||
|         if (this._noNetworksBox.visible) | ||||
|             this._noNetworksSpinner.play(); | ||||
|         else | ||||
|             this._noNetworksSpinner.stop(); | ||||
|     }, | ||||
|  | ||||
|     _buildLayout: function() { | ||||
| @@ -727,11 +834,43 @@ const NMWirelessDialog = new Lang.Class({ | ||||
|         this._scrollView.add_actor(this._itemBox); | ||||
|         this._stack.add_child(this._scrollView); | ||||
|  | ||||
|         this._noNetworksLabel = new St.Label({ style_class: 'no-networks-label', | ||||
|         this._noNetworksBox = new St.BoxLayout({ vertical: true, | ||||
|                                                  style_class: 'no-networks-box', | ||||
|                                                  x_align: Clutter.ActorAlign.CENTER, | ||||
|                                                  y_align: Clutter.ActorAlign.CENTER }); | ||||
|  | ||||
|         this._noNetworksSpinner = new Animation.AnimatedIcon(global.datadir + '/theme/process-working.svg', 24, 24); | ||||
|         this._noNetworksBox.add_actor(this._noNetworksSpinner.actor); | ||||
|         this._noNetworksBox.add_actor(new St.Label({ style_class: 'no-networks-label', | ||||
|                                                      text: _("No Networks") })); | ||||
|         this._stack.add_child(this._noNetworksBox); | ||||
|  | ||||
|         this._airplaneBox = new St.BoxLayout({ vertical: true, | ||||
|                                                style_class: 'nm-dialog-airplane-box', | ||||
|                                                x_align: Clutter.ActorAlign.CENTER, | ||||
|                                                y_align: Clutter.ActorAlign.CENTER, | ||||
|                                                text: _("No Networks") }); | ||||
|         this._stack.add_child(this._noNetworksLabel); | ||||
|                                                y_align: Clutter.ActorAlign.CENTER }); | ||||
|         this._airplaneIcon = new St.Icon({ icon_size: 48 }); | ||||
|         this._airplaneHeadline = new St.Label({ style_class: 'nm-dialog-airplane-headline' }); | ||||
|         this._airplaneText = new St.Label({ style_class: 'nm-dialog-airplane-text' }); | ||||
|  | ||||
|         let airplaneSubStack = new St.Widget({ layout_manager: new Clutter.BinLayout }); | ||||
|         this._airplaneButton = new St.Button({ style_class: 'modal-dialog-button' }); | ||||
|         this._airplaneButton.connect('clicked', Lang.bind(this, function() { | ||||
|             if (this._rfkill.airplaneMode) | ||||
|                 this._rfkill.airplaneMode = false; | ||||
|             else | ||||
|                 this._client.wireless_enabled = true; | ||||
|         })); | ||||
|         airplaneSubStack.add_actor(this._airplaneButton); | ||||
|         this._airplaneInactive = new St.Label({ style_class: 'nm-dialog-airplane-text', | ||||
|                                                 text: _("Use hardware switch to turn off") }); | ||||
|         airplaneSubStack.add_actor(this._airplaneInactive); | ||||
|  | ||||
|         this._airplaneBox.add(this._airplaneIcon, { x_align: St.Align.MIDDLE }); | ||||
|         this._airplaneBox.add(this._airplaneHeadline, { x_align: St.Align.MIDDLE }); | ||||
|         this._airplaneBox.add(this._airplaneText, { x_align: St.Align.MIDDLE }); | ||||
|         this._airplaneBox.add(airplaneSubStack, { x_align: St.Align.MIDDLE }); | ||||
|         this._stack.add_child(this._airplaneBox); | ||||
|  | ||||
|         this.contentLayout.add(this._stack, { expand: true }); | ||||
|  | ||||
| @@ -748,16 +887,11 @@ const NMWirelessDialog = new Lang.Class({ | ||||
|  | ||||
|     _connect: function() { | ||||
|         let network = this._selectedNetwork; | ||||
|         let accessPoints = network.accessPoints; | ||||
|         if (network.connections.length > 0) { | ||||
|             let connection = network.connections[0]; | ||||
|             for (let i = 0; i < accessPoints.length; i++) { | ||||
|                 if (accessPoints[i].connection_valid(connection)) { | ||||
|                     this._client.activate_connection(connection, this._device, accessPoints[i].dbus_path, null); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             this._client.activate_connection(connection, this._device, null, null); | ||||
|         } else { | ||||
|             let accessPoints = network.accessPoints; | ||||
|             if ((accessPoints[0]._secType == NMAccessPointSecurity.WPA2_ENT) | ||||
|                 || (accessPoints[0]._secType == NMAccessPointSecurity.WPA_ENT)) { | ||||
|                 // 802.1x-enabled APs require further configuration, so they're | ||||
| @@ -929,7 +1063,7 @@ const NMWirelessDialog = new Lang.Class({ | ||||
|             this._itemBox.insert_child_at_index(network.item.actor, newPos); | ||||
|         } | ||||
|  | ||||
|         this._updateVisibility(); | ||||
|         this._syncView(); | ||||
|     }, | ||||
|  | ||||
|     _accessPointRemoved: function(device, accessPoint) { | ||||
| @@ -944,14 +1078,14 @@ const NMWirelessDialog = new Lang.Class({ | ||||
|         network.accessPoints.splice(res.ap, 1); | ||||
|  | ||||
|         if (network.accessPoints.length == 0) { | ||||
|             network.item.destroy(); | ||||
|             network.item.actor.destroy(); | ||||
|             this._networks.splice(res.network, 1); | ||||
|         } else { | ||||
|             network.item.updateBestAP(network.accessPoints[0]); | ||||
|             this._resortItems(); | ||||
|         } | ||||
|  | ||||
|         this._updateVisibility(); | ||||
|         this._syncView(); | ||||
|     }, | ||||
|  | ||||
|     _resortItems: function() { | ||||
| @@ -968,13 +1102,13 @@ const NMWirelessDialog = new Lang.Class({ | ||||
|  | ||||
|     _selectNetwork: function(network) { | ||||
|         if (this._selectedNetwork) | ||||
|             this._selectedNetwork.item.actor.checked = false; | ||||
|             this._selectedNetwork.item.actor.remove_style_pseudo_class('selected'); | ||||
|  | ||||
|         this._selectedNetwork = network; | ||||
|         this._updateSensitivity(); | ||||
|  | ||||
|         if (this._selectedNetwork) | ||||
|             this._selectedNetwork.item.actor.checked = true; | ||||
|             this._selectedNetwork.item.actor.add_style_pseudo_class('selected'); | ||||
|     }, | ||||
|  | ||||
|     _createNetworkItem: function(network) { | ||||
| @@ -984,9 +1118,6 @@ const NMWirelessDialog = new Lang.Class({ | ||||
|             Util.ensureActorVisibleInScrollView(this._scrollView, network.item.actor); | ||||
|             this._selectNetwork(network); | ||||
|         })); | ||||
|         network.item.connect('connect', Lang.bind(this, function() { | ||||
|             this._connect(); | ||||
|         })); | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| @@ -1039,6 +1170,10 @@ const NMDeviceWireless = new Lang.Class({ | ||||
|             this._client.disconnect(this._wirelessHwEnabledChangedId); | ||||
|             this._wirelessHwEnabledChangedId = 0; | ||||
|         } | ||||
|         if (this._dialog) { | ||||
|             this._dialog.destroy(); | ||||
|             this._dialog = null; | ||||
|         } | ||||
|  | ||||
|         this.item.destroy(); | ||||
|     }, | ||||
| @@ -1112,7 +1247,12 @@ const NMDeviceWireless = new Lang.Class({ | ||||
|     _getStatus: function() { | ||||
|         let ap = this._device.active_access_point; | ||||
|  | ||||
|         if (ap) | ||||
|         if (this._isHotSpotMaster()) | ||||
|             return _("Hotspot Active"); | ||||
|         else if (this._device.state >= NetworkManager.DeviceState.PREPARE && | ||||
|                  this._device.state < NetworkManager.DeviceState.ACTIVATED) | ||||
|             return _("Connecting"); | ||||
|         else if (ap) | ||||
|             return ssidToLabel(ap.get_ssid()); | ||||
|         else if (!this._client.wireless_hardware_enabled) | ||||
|             return _("Hardware Disabled"); | ||||
| @@ -1131,20 +1271,52 @@ const NMDeviceWireless = new Lang.Class({ | ||||
|             return 'network-wireless-signal-none-symbolic'; | ||||
|     }, | ||||
|  | ||||
|     _canReachInternet: function() { | ||||
|         if (this._client.primary_connection != this._device.active_connection) | ||||
|             return true; | ||||
|  | ||||
|         return this._client.connectivity == NetworkManager.ConnectivityState.FULL; | ||||
|     }, | ||||
|  | ||||
|     _isHotSpotMaster: function() { | ||||
|         if (!this._device.active_connection) | ||||
|             return false; | ||||
|  | ||||
|         let connection = this._settings.get_connection_by_path(this._device.active_connection.connection); | ||||
|         if (!connection) | ||||
|             return false; | ||||
|  | ||||
|         let ip4config = connection.get_setting_ip4_config(); | ||||
|         if (!ip4config) | ||||
|             return false; | ||||
|  | ||||
|         return ip4config.get_method() == NetworkManager.SETTING_IP4_CONFIG_METHOD_SHARED; | ||||
|     }, | ||||
|  | ||||
|     getIndicatorIcon: function() { | ||||
|         if (this._device.state >= NetworkManager.DeviceState.PREPARE && | ||||
|             this._device.state < NetworkManager.DeviceState.ACTIVATED) | ||||
|         if (this._device.state < NetworkManager.DeviceState.PREPARE) | ||||
|             return 'network-wireless-disconnected-symbolic'; | ||||
|         if (this._device.state < NetworkManager.DeviceState.ACTIVATED) | ||||
|             return 'network-wireless-acquiring-symbolic'; | ||||
|  | ||||
|         if (this._isHotSpotMaster()) | ||||
|             return 'network-wireless-hotspot-symbolic'; | ||||
|  | ||||
|         let ap = this._device.active_access_point; | ||||
|         if (!ap) { | ||||
|             if (this._device.mode != NM80211Mode.ADHOC) | ||||
|                 log('An active wireless connection, in infrastructure mode, involves no access point?'); | ||||
|  | ||||
|             return 'network-wireless-connected-symbolic'; | ||||
|             if (this._canReachInternet()) | ||||
|                 return 'network-wireless-connected-symbolic'; | ||||
|             else | ||||
|                 return 'network-wireless-no-route-symbolic'; | ||||
|         } | ||||
|  | ||||
|         return 'network-wireless-signal-' + signalToIcon(ap.strength) + '-symbolic'; | ||||
|         if (this._canReachInternet()) | ||||
|             return 'network-wireless-signal-' + signalToIcon(ap.strength) + '-symbolic'; | ||||
|         else | ||||
|             return 'network-wireless-no-route-symbolic'; | ||||
|     }, | ||||
| }); | ||||
| Signals.addSignalMethods(NMDeviceWireless.prototype); | ||||
| @@ -1160,6 +1332,22 @@ const NMVPNConnectionItem = new Lang.Class({ | ||||
|         return this._activeConnection.vpn_state != NetworkManager.VPNConnectionState.DISCONNECTED; | ||||
|     }, | ||||
|  | ||||
|     _buildUI: function() { | ||||
|         this.labelItem = new PopupMenu.PopupMenuItem(''); | ||||
|         this.labelItem.connect('activate', Lang.bind(this, this._toggle)); | ||||
|  | ||||
|         this.radioItem = new PopupMenu.PopupSwitchMenuItem(this._connection.get_id(), false); | ||||
|         this.radioItem.connect('toggled', Lang.bind(this, this._toggle)); | ||||
|     }, | ||||
|  | ||||
|     _sync: function() { | ||||
|         let isActive = this.isActive(); | ||||
|         this.labelItem.label.text = isActive ? _("Turn Off") : this._section.getConnectLabel(); | ||||
|         this.radioItem.setToggleState(isActive); | ||||
|         this.radioItem.setStatus(this._getStatus()); | ||||
|         this.emit('icon-changed'); | ||||
|     }, | ||||
|  | ||||
|     _getStatus: function() { | ||||
|         if (this._activeConnection == null) | ||||
|             return null; | ||||
| @@ -1229,19 +1417,53 @@ const NMVPNSection = new Lang.Class({ | ||||
|  | ||||
|     _init: function(client) { | ||||
|         this.parent(client); | ||||
|  | ||||
|         this._vpnSettings = new PopupMenu.PopupMenuItem(''); | ||||
|         this.item.menu.addMenuItem(this._vpnSettings); | ||||
|         this._vpnSettings.connect('activate', Lang.bind(this, this._onSettingsActivate)); | ||||
|  | ||||
|         this._sync(); | ||||
|     }, | ||||
|  | ||||
|     _sync: function() { | ||||
|         let nItems = this._connectionItems.size(); | ||||
|         let nItems = this._connectionItems.size; | ||||
|         this.item.actor.visible = (nItems > 0); | ||||
|  | ||||
|         if (nItems > 1) | ||||
|             this._vpnSettings.label.text = _("Network Settings"); | ||||
|         else | ||||
|             this._vpnSettings.label.text = _("VPN Settings"); | ||||
|  | ||||
|         this.parent(); | ||||
|     }, | ||||
|  | ||||
|     _onSettingsActivate: function() { | ||||
|         let nItems = this._connectionItems.size; | ||||
|         if (nItems > 1) { | ||||
|             let appSys = Shell.AppSystem.get_default(); | ||||
|             let app = appSys.lookup_app('gnome-network-panel.desktop'); | ||||
|             app.launch(0, -1); | ||||
|         } else { | ||||
|             let connection = this._connections[0]; | ||||
|             Util.spawnApp(['gnome-control-center', 'network', 'show-device', | ||||
|                            connection.get_path()]); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _getDescription: function() { | ||||
|         return _("VPN"); | ||||
|     }, | ||||
|  | ||||
|     _getStatus: function() { | ||||
|         let values = this._connectionItems.values(); | ||||
|         for (let item of values) { | ||||
|             if (item.isActive()) | ||||
|                 return item.getName(); | ||||
|         } | ||||
|  | ||||
|         return _("Off"); | ||||
|     }, | ||||
|  | ||||
|     _getMenuIcon: function() { | ||||
|         return this.getIndicatorIcon() || 'network-vpn-symbolic'; | ||||
|     }, | ||||
| @@ -1255,9 +1477,10 @@ const NMVPNSection = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     setActiveConnections: function(vpnConnections) { | ||||
|         this._connectionItems.values().forEach(function(item) { | ||||
|         let connections = this._connectionItems.values(); | ||||
|         for (let item of connections) { | ||||
|             item.setActiveConnection(null); | ||||
|         }); | ||||
|         } | ||||
|         vpnConnections.forEach(Lang.bind(this, function(a) { | ||||
|             let item = this._connectionItems.get(a._connection.get_uuid()); | ||||
|             item.setActiveConnection(a); | ||||
| @@ -1270,8 +1493,7 @@ const NMVPNSection = new Lang.Class({ | ||||
|  | ||||
|     getIndicatorIcon: function() { | ||||
|         let items = this._connectionItems.values(); | ||||
|         for (let i = 0; i < items.length; i++) { | ||||
|             let item = items[i]; | ||||
|         for (let item of items) { | ||||
|             let icon = item.getIndicatorIcon(); | ||||
|             if (icon) | ||||
|                 return icon; | ||||
| @@ -1293,6 +1515,7 @@ const NMApplet = new Lang.Class({ | ||||
|  | ||||
|         // Device types | ||||
|         this._dtypes = { }; | ||||
|         this._dtypes[NetworkManager.DeviceType.ETHERNET] = NMDeviceWired; | ||||
|         this._dtypes[NetworkManager.DeviceType.WIFI] = NMDeviceWireless; | ||||
|         this._dtypes[NetworkManager.DeviceType.MODEM] = NMDeviceModem; | ||||
|         this._dtypes[NetworkManager.DeviceType.BT] = NMDeviceBluetooth; | ||||
| @@ -1300,6 +1523,7 @@ const NMApplet = new Lang.Class({ | ||||
|  | ||||
|         // Connection types | ||||
|         this._ctypes = { }; | ||||
|         this._ctypes[NetworkManager.SETTING_WIRED_SETTING_NAME] = NMConnectionCategory.WIRED; | ||||
|         this._ctypes[NetworkManager.SETTING_WIRELESS_SETTING_NAME] = NMConnectionCategory.WIRELESS; | ||||
|         this._ctypes[NetworkManager.SETTING_BLUETOOTH_SETTING_NAME] = NMConnectionCategory.WWAN; | ||||
|         this._ctypes[NetworkManager.SETTING_CDMA_SETTING_NAME] = NMConnectionCategory.WWAN; | ||||
| @@ -1322,6 +1546,15 @@ const NMApplet = new Lang.Class({ | ||||
|         this._tryLateInit(); | ||||
|     }, | ||||
|  | ||||
|     _createDeviceCategory: function() { | ||||
|         let category = { | ||||
|             section: new PopupMenu.PopupMenuSection(), | ||||
|             devices: [ ], | ||||
|         }; | ||||
|         this.menu.addMenuItem(category.section); | ||||
|         return category; | ||||
|     }, | ||||
|  | ||||
|     _tryLateInit: function() { | ||||
|         if (!this._client || !this._settings) | ||||
|             return; | ||||
| @@ -1337,17 +1570,9 @@ const NMApplet = new Lang.Class({ | ||||
|         this._nmDevices = []; | ||||
|         this._devices = { }; | ||||
|  | ||||
|         this._devices.wireless = { | ||||
|             section: new PopupMenu.PopupMenuSection(), | ||||
|             devices: [ ], | ||||
|         }; | ||||
|         this.menu.addMenuItem(this._devices.wireless.section); | ||||
|  | ||||
|         this._devices.wwan = { | ||||
|             section: new PopupMenu.PopupMenuSection(), | ||||
|             devices: [ ], | ||||
|         }; | ||||
|         this.menu.addMenuItem(this._devices.wwan.section); | ||||
|         this._devices.wired = this._createDeviceCategory(); | ||||
|         this._devices.wireless = this._createDeviceCategory(); | ||||
|         this._devices.wwan = this._createDeviceCategory(); | ||||
|  | ||||
|         this._vpnSection = new NMVPNSection(this._client); | ||||
|         this._vpnSection.connect('activation-failed', Lang.bind(this, this._onActivationFailed)); | ||||
| @@ -1591,7 +1816,7 @@ const NMApplet = new Lang.Class({ | ||||
|     _connectionRemoved: function(connection) { | ||||
|         let pos = this._connections.indexOf(connection); | ||||
|         if (pos != -1) | ||||
|             this._connections.splice(connection, 1); | ||||
|             this._connections.splice(pos, 1); | ||||
|  | ||||
|         let section = connection._section; | ||||
|  | ||||
| @@ -1617,7 +1842,6 @@ const NMApplet = new Lang.Class({ | ||||
|         let connectionSettings = connection.get_setting_by_name(NetworkManager.SETTING_CONNECTION_SETTING_NAME); | ||||
|         connection._type = connectionSettings.type; | ||||
|         connection._section = this._ctypes[connection._type] || NMConnectionCategory.INVALID; | ||||
|         connection._timestamp = connectionSettings.timestamp; | ||||
|  | ||||
|         let section = connection._section; | ||||
|  | ||||
| @@ -1642,8 +1866,7 @@ const NMApplet = new Lang.Class({ | ||||
|  | ||||
|     _updateIcon: function() { | ||||
|         if (!this._client.networking_enabled || !this._mainConnection) { | ||||
|             this._primaryIndicator.icon_name = 'network-offline-symbolic'; | ||||
|             this._primaryIndicator.visible = true; | ||||
|             this._primaryIndicator.visible = false; | ||||
|         } else { | ||||
|             let dev = this._mainConnection._primaryDevice; | ||||
|             this._primaryIndicator.visible = (dev != null); | ||||
|   | ||||
| @@ -8,20 +8,22 @@ const Main = imports.ui.main; | ||||
| const PanelMenu = imports.ui.panelMenu; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
|  | ||||
| const BUS_NAME = 'org.gnome.SettingsDaemon.Power'; | ||||
| const OBJECT_PATH = '/org/gnome/SettingsDaemon/Power'; | ||||
| const BUS_NAME = 'org.freedesktop.UPower'; | ||||
| const OBJECT_PATH = '/org/freedesktop/UPower/devices/DisplayDevice'; | ||||
|  | ||||
| const PowerManagerInterface = <interface name="org.gnome.SettingsDaemon.Power"> | ||||
| <method name="GetDevices"> | ||||
|     <arg type="a(susdut)" direction="out" /> | ||||
| </method> | ||||
| <method name="GetPrimaryDevice"> | ||||
|     <arg type="(susdut)" direction="out" /> | ||||
| </method> | ||||
| <property name="Icon" type="s" access="read" /> | ||||
| </interface>; | ||||
| const DisplayDeviceInterface = '<node> \ | ||||
| <interface name="org.freedesktop.UPower.Device"> \ | ||||
|   <property name="Type" type="u" access="read"/> \ | ||||
|   <property name="State" type="u" access="read"/> \ | ||||
|   <property name="Percentage" type="d" access="read"/> \ | ||||
|   <property name="TimeToEmpty" type="x" access="read"/> \ | ||||
|   <property name="TimeToFull" type="x" access="read"/> \ | ||||
|   <property name="IsPresent" type="b" access="read"/> \ | ||||
|   <property name="IconName" type="s" access="read"/> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const PowerManagerProxy = Gio.DBusProxy.makeProxyWrapper(PowerManagerInterface); | ||||
| const PowerManagerProxy = Gio.DBusProxy.makeProxyWrapper(DisplayDeviceInterface); | ||||
|  | ||||
| const Indicator = new Lang.Class({ | ||||
|     Name: 'PowerIndicator', | ||||
| @@ -32,7 +34,7 @@ const Indicator = new Lang.Class({ | ||||
|  | ||||
|         this._indicator = this._addIndicator(); | ||||
|  | ||||
|         this._proxy = new PowerManagerProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH, | ||||
|         this._proxy = new PowerManagerProxy(Gio.DBus.system, BUS_NAME, OBJECT_PATH, | ||||
|                                             Lang.bind(this, function(proxy, error) { | ||||
|                                                 if (error) { | ||||
|                                                     log(error.message); | ||||
| @@ -43,7 +45,7 @@ const Indicator = new Lang.Class({ | ||||
|                                                 this._sync(); | ||||
|                                             })); | ||||
|  | ||||
|         this._item = new PopupMenu.PopupSubMenuMenuItem(_("Battery"), true); | ||||
|         this._item = new PopupMenu.PopupSubMenuMenuItem("", true); | ||||
|         this._item.menu.addSettingsAction(_("Power Settings"), 'gnome-power-panel.desktop'); | ||||
|         this.menu.addMenuItem(this._item); | ||||
|  | ||||
| @@ -56,11 +58,18 @@ const Indicator = new Lang.Class({ | ||||
|         this.menu.setSensitive(sensitive); | ||||
|     }, | ||||
|  | ||||
|     _statusForDevice: function(device) { | ||||
|         let [device_id, device_type, icon, percentage, state, seconds] = device; | ||||
|     _getStatus: function() { | ||||
|         let seconds = 0; | ||||
|  | ||||
|         if (state == UPower.DeviceState.FULLY_CHARGED) | ||||
|         if (this._proxy.State == UPower.DeviceState.FULLY_CHARGED) | ||||
|             return _("Fully Charged"); | ||||
|         else if (this._proxy.State == UPower.DeviceState.CHARGING) | ||||
|             seconds = this._proxy.TimeToFull; | ||||
|         else if (this._proxy.State == UPower.DeviceState.DISCHARGING) | ||||
|             seconds = this._proxy.TimeToEmpty; | ||||
|         // state is one of PENDING_CHARGING, PENDING_DISCHARGING | ||||
|         else | ||||
|             return _("Estimating…"); | ||||
|  | ||||
|         let time = Math.round(seconds / 60); | ||||
|         if (time == 0) { | ||||
| @@ -72,44 +81,43 @@ const Indicator = new Lang.Class({ | ||||
|         let minutes = time % 60; | ||||
|         let hours = Math.floor(time / 60); | ||||
|  | ||||
|         if (state == UPower.DeviceState.DISCHARGING) { | ||||
|         if (this._proxy.State == UPower.DeviceState.DISCHARGING) { | ||||
|             // Translators: this is <hours>:<minutes> Remaining (<percentage>) | ||||
|             return _("%d\u2236%02d Remaining (%d%%)").format(hours, minutes, percentage); | ||||
|             return _("%d\u2236%02d Remaining (%d%%)").format(hours, minutes, this._proxy.Percentage); | ||||
|         } | ||||
|  | ||||
|         if (state == UPower.DeviceState.CHARGING) { | ||||
|         if (this._proxy.State == UPower.DeviceState.CHARGING) { | ||||
|             // Translators: this is <hours>:<minutes> Until Full (<percentage>) | ||||
|             return _("%d\u2236%02d Until Full (%d%%)").format(hours, minutes, percentage); | ||||
|             return _("%d\u2236%02d Until Full (%d%%)").format(hours, minutes, this._proxy.Percentage); | ||||
|         } | ||||
|  | ||||
|         // state is one of PENDING_CHARGING, PENDING_DISCHARGING | ||||
|         return _("Estimating…"); | ||||
|         return null; | ||||
|     }, | ||||
|  | ||||
|     _sync: function() { | ||||
|         function isBattery(result) { | ||||
|             if (!result) | ||||
|                 return false; | ||||
|  | ||||
|             let [device] = result; | ||||
|             let [, deviceType] = device; | ||||
|             return (deviceType == UPower.DeviceKind.BATTERY); | ||||
|         // Do we have batteries or a UPS? | ||||
|         let visible = this._proxy.IsPresent; | ||||
|         if (visible) { | ||||
|             this._item.actor.show(); | ||||
|         } else { | ||||
|             // If there's no battery, then we use the power icon. | ||||
|             this._item.actor.hide(); | ||||
|             this._indicator.icon_name = 'system-shutdown-symbolic'; | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this._proxy.GetPrimaryDeviceRemote(Lang.bind(this, function(result, error) { | ||||
|             if (isBattery(result)) { | ||||
|                 let [device] = result; | ||||
|                 let [,, icon] = device; | ||||
|                 let gicon = Gio.icon_new_for_string(icon); | ||||
|                 this._indicator.gicon = gicon; | ||||
|                 this._item.icon.gicon = gicon; | ||||
|                 this._item.status.text = this._statusForDevice(device); | ||||
|                 this._item.actor.show(); | ||||
|             } else { | ||||
|                 // If there's no battery, then we use the power icon. | ||||
|                 this._indicator.icon_name = 'system-shutdown-symbolic'; | ||||
|                 this._item.actor.hide(); | ||||
|             } | ||||
|         })); | ||||
|         // The icons | ||||
|         let icon = this._proxy.IconName; | ||||
|         this._indicator.icon_name = icon; | ||||
|         this._item.icon.icon_name = icon; | ||||
|  | ||||
|         // The status label | ||||
|         this._item.status.text = this._getStatus(); | ||||
|  | ||||
|         // The sub-menu heading | ||||
|         if (this._proxy.Type == UPower.DeviceKind.UPS) | ||||
|             this._item.label.text = _("UPS"); | ||||
|         else | ||||
|             this._item.label.text = _("Battery"); | ||||
|     }, | ||||
| }); | ||||
|   | ||||
| @@ -2,19 +2,67 @@ | ||||
|  | ||||
| const Gio = imports.gi.Gio; | ||||
| const Lang = imports.lang; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const Main = imports.ui.main; | ||||
| const PanelMenu = imports.ui.panelMenu; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
|  | ||||
| const BUS_NAME = 'org.gnome.SettingsDaemon.Rfkill'; | ||||
| const OBJECT_PATH = '/org/gnome/SettingsDaemon/Rfkill'; | ||||
|  | ||||
| const RfkillManagerInterface = <interface name="org.gnome.SettingsDaemon.Rfkill"> | ||||
| <property name="AirplaneMode" type="b" access="readwrite" /> | ||||
| </interface>; | ||||
| const RfkillManagerInterface = '<node> \ | ||||
| <interface name="org.gnome.SettingsDaemon.Rfkill"> \ | ||||
| <property name="AirplaneMode" type="b" access="readwrite" /> \ | ||||
| <property name="HardwareAirplaneMode" type="b" access="read" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const RfkillManagerProxy = Gio.DBusProxy.makeProxyWrapper(RfkillManagerInterface); | ||||
|  | ||||
| const RfkillManager = new Lang.Class({ | ||||
|     Name: 'RfkillManager', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._proxy = new RfkillManagerProxy(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._changed)); | ||||
|                                                  this._changed(); | ||||
|                                              })); | ||||
|     }, | ||||
|  | ||||
|     get airplaneMode() { | ||||
|         return this._proxy.AirplaneMode; | ||||
|     }, | ||||
|  | ||||
|     set airplaneMode(v) { | ||||
|         this._proxy.AirplaneMode = v; | ||||
|     }, | ||||
|  | ||||
|     get hwAirplaneMode() { | ||||
|         return this._proxy.HardwareAirplaneMode; | ||||
|     }, | ||||
|  | ||||
|     _changed: function() { | ||||
|         this.emit('airplane-mode-changed'); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(RfkillManager.prototype); | ||||
|  | ||||
| var _manager; | ||||
| function getRfkillManager() { | ||||
|     if (_manager != null) | ||||
|         return _manager; | ||||
|  | ||||
|     _manager = new RfkillManager(); | ||||
|     return _manager; | ||||
| } | ||||
|  | ||||
| const Indicator = new Lang.Class({ | ||||
|     Name: 'RfkillIndicator', | ||||
|     Extends: PanelMenu.SystemIndicator, | ||||
| @@ -22,16 +70,8 @@ const Indicator = new Lang.Class({ | ||||
|     _init: function() { | ||||
|         this.parent(); | ||||
|  | ||||
|         this._proxy = new RfkillManagerProxy(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._sync)); | ||||
|                                                  this._sync(); | ||||
|                                              })); | ||||
|         this._manager = getRfkillManager(); | ||||
|         this._manager.connect('airplane-mode-changed', Lang.bind(this, this._sync)); | ||||
|  | ||||
|         this._indicator = this._addIndicator(); | ||||
|         this._indicator.icon_name = 'airplane-mode-symbolic'; | ||||
| @@ -43,16 +83,34 @@ const Indicator = new Lang.Class({ | ||||
|         this._item = new PopupMenu.PopupSubMenuMenuItem(_("Airplane Mode"), true); | ||||
|         this._item.icon.icon_name = 'airplane-mode-symbolic'; | ||||
|         this._item.status.text = _("On"); | ||||
|         this._item.menu.addAction(_("Turn Off"), Lang.bind(this, function() { | ||||
|             this._proxy.AirplaneMode = false; | ||||
|         this._offItem = this._item.menu.addAction(_("Turn Off"), Lang.bind(this, function() { | ||||
|             this._manager.airplaneMode = false; | ||||
|         })); | ||||
|         this._item.menu.addSettingsAction(_("Network Settings"), 'gnome-network-panel.desktop'); | ||||
|         this.menu.addMenuItem(this._item); | ||||
|  | ||||
|         Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated)); | ||||
|         this._sessionUpdated(); | ||||
|     }, | ||||
|  | ||||
|     _sessionUpdated: function() { | ||||
|         let sensitive = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter; | ||||
|         this.menu.setSensitive(sensitive); | ||||
|     }, | ||||
|  | ||||
|     _sync: function() { | ||||
|         let airplaneMode = this._proxy.AirplaneMode; | ||||
|         let airplaneMode = this._manager.airplaneMode; | ||||
|         let hwAirplaneMode = this._manager.hwAirplaneMode; | ||||
|         let changed = (airplaneMode != this._indicator.visible) || | ||||
|             (hwAirplaneMode != this._offItem.actor.visible); | ||||
|  | ||||
|         this._indicator.visible = airplaneMode; | ||||
|         this._item.actor.visible = airplaneMode; | ||||
|         this._offItem.setSensitive(!hwAirplaneMode); | ||||
|  | ||||
|         if (hwAirplaneMode) | ||||
|             this._offItem.label.text = _("Use hardware switch to turn off"); | ||||
|         else | ||||
|             this._offItem.label.text = _("Turn Off"); | ||||
|     }, | ||||
| }); | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user